【Qwen2.5+Ollama+PPO+RLHF强化学习训练】通过RLHF方式使用PPO算法,对本地部署的大模型强化学习

概要

强化学习主要使用在SFT后一步,可以减少训练成本并增强大模型的探索能力。本文将介绍从零开始部署模型并RLHF的过程。

整体架构流程

RLHF的PPO算法需要三个模型:奖励模型(评估一个问题的得分)、策略模型(告诉大模型做什么决策更好)、价值模型(告诉大模型做什么事是更有价值的)
奖励模型需要自己训练,而策略模型、价值模型在本文中,笔者通过大模型进行了简单实现。

一、制作数据集

此处笔者准备了一个数据集自动生成器。以芯片尺寸为例。

import random
import pandas as pd
from langchain_ollama import ChatOllama

# 初始化大模型
llm = ChatOllama(model="qwen2.5:7b")

# 定义生成样本数量
NUM_SAMPLES = 1000

# 定义芯片尺寸范围(假设单位为 mm)
MIN_DIM = 1
MAX_DIM = 100

# 定义提示词模板
PROMPT_TEMPLATE = """
你是一个芯片设计顾问,需要根据芯片的尺寸(长、宽、高)推荐调整方向。
输入格式为:[长度, 宽度, 高度](单位:mm)。
请分析后输出一个 JSON 对象,包含:
- "action": "increase"(增大) / "decrease"(减小) / "no_change"(不动)
- "reason": 简短说明推荐理由(可选)

示例:
输入:[50, 30, 20]
输出:{{"action": "increase", "reason": "散热需求增加"}}

现在请分析以下输入:
输入:{dimensions}
输出:
"""

# 生成数据集
data = []
for i in range(NUM_SAMPLES):
    # 随机生成芯片尺寸
    length = random.randint(MIN_DIM, MAX_DIM)
    width = random.randint(MIN_DIM, MAX_DIM)
    height = random.randint(MIN_DIM, MAX_DIM)

    # 构造输入
    input_dimensions = f"[{length}, {width}, {height}]"

    # 调用大模型生成推荐
    prompt = PROMPT_TEMPLATE.format(dimensions=input_dimensions)
    response = llm.invoke(prompt).content.strip()

    # 解析输出(假设模型返回 JSON 格式)
    try:
        action = "no_change"  # 默认值
        if "increase" in response.lower():
            action = "increase"
        elif "decrease" in response.lower():
            action = "decrease"

        data.append({
            "length": length,
            "width": width,
            "height": height,
            "action": action
        })
    except Exception as e:
        print(f"解析错误(输入:{input_dimensions}): {e}")
        continue  # 跳过解析失败的样本

# 保存为 CSV 文件
df = pd.DataFrame(data)
df.to_csv("chip_dimension_reward_dataset.csv", index=False)

print(f"已生成 {len(data)} 条样本,保存至 chip_dimension_reward_dataset.csv")

二、训练奖励模型(Reward Model, RM):

依赖人类标注的偏好数据(如对多个回答的排序或二元比较)。
例如,给定一个问题,人类标注员选择“回答A比回答B更好”,RM 通过学习这些偏好,为每个回答分配一个标量奖励值(如 A 得分 0.8,B 得分 0.3)。
RM 的损失函数通常基于 Bradley-Terry 模型,确保偏好关系与得分一致。

import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import classification_report, accuracy_score
from sklearn.preprocessing import StandardScaler
import joblib

# 1. 加载数据集
data_path = "chip_dimension_reward_dataset.csv"
df = pd.read_csv(data_path)

# 2. 数据预处理
X = df[["length", "width", "height"]]  # 输入特征
y = df["action"]                        # 标签(increase / decrease / no_change)

# 将类别标签编码为整数
y = y.replace({"increase": 0, "decrease": 1, "no_change": 2})

# 可选:标准化输入特征(适用于线性模型)
scaler = StandardScaler()
X = scaler.fit_transform(X)

# 3. 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42
)

# 4. 训练模型(这里用随机森林分类器)
model = RandomForestClassifier(
    n_estimators=100,
    max_depth=10,
    random_state=42
)
model.fit(X_train, y_train)

# 5. 模型评估
y_pred = model.predict(X_test)
print("模型评估报告:")
print(classification_report(y_test, y_pred, target_names=["increase", "decrease", "no_change"]))
print(f"准确率: {accuracy_score(y_test, y_pred):.2%}")

# 6. 保存模型和标准化器(便于 PPO 使用)
joblib.dump(model, "reward_model.pkl")
joblib.dump(scaler, "scaler.pkl")

print("模型已训练并保存为 reward_model.pkl 和 scaler.pkl")

训练成功后,使用以下代码测试是否可以完成评测

import joblib
import numpy as np

# 加载模型和标准化器
model = joblib.load("reward_model.pkl")
scaler = joblib.load("scaler.pkl")


# 定义一个函数用于预测芯片尺寸调整方向
def predict_action(length, width, height):
    # 输入特征
    input_data = np.array([[length, width, height]])

    # 标准化输入
    scaled_input = scaler.transform(input_data)

    # 预测动作类别(0: increase, 1: decrease, 2: no_change)
    action_class = model.predict(scaled_input)[0]

    # 可选:返回对应的动作名称
    action_labels = ["increase", "decrease", "no_change"]
    return action_labels[action_class]
print(predict_action(10, 20, 30))
print(predict_action(85, 93, 10))

三、PPO算法

import gym
from gym import spaces
import numpy as np
import pandas as pd
from langchain_ollama import ChatOllama
from stable_baselines3 import PPO
from stable_baselines3.common.policies import ActorCriticPolicy
import torch
import torch.nn as nn

class ChipOptimizationEnv(gym.Env):
    def __init__(self, dataset_path="./dataset/chip_ppo_dataset.csv"):
        super(ChipOptimizationEnv, self).__init__()
        self.llm = ChatOllama(model="qwen2.5:7b")
        self.dataset = pd.read_csv(dataset_path)
        self.current_idx = 0
        self.max_steps = len(self.dataset)

        # 动作空间:0: 增加宽度, 1: 减少宽度, 2: 增加长度, 3: 减少长度
        self.action_space = spaces.Discrete(4)
        # 观察空间:宽度、长度、功耗、性能评分
        self.observation_space = spaces.Box(low=0, high=np.inf, shape=(4,), dtype=np.float32)

        self.current_chip = None
        self.step_count = 0
        self.max_steps_per_episode = 10

    def reset(self):
        self.current_idx = np.random.randint(0, self.max_steps)
        self.current_chip = self.dataset.iloc[self.current_idx].copy()
        self.step_count = 0
        return self._get_observation()

    def _get_observation(self):
        return np.array([
            self.current_chip["width_nm"],
            self.current_chip["length_nm"],
            self.current_chip["power_mw"],
            self.current_chip["performance_score"]
        ], dtype=np.float32)

    def _compute_reward(self, action):
        # 规则奖励:基于性能和功耗
        rule_reward = self.current_chip["performance_score"] / 100.0 - self.current_chip["power_mw"] / 500.0
        # QWEN2.5 奖励:调用模型评估动作
        prompt = (
            f"芯片参数:宽度={self.current_chip['width_nm']:.2f}nm, "
            f"长度={self.current_chip['length_nm']:.2f}nm, "
            f"功耗={self.current_chip['power_mw']:.2f}mW, "
            f"性能评分={self.current_chip['performance_score']:.2f}, "
            f"动作={action}(0: 增加宽度, 1: 减少宽度, 2: 增加长度, 3: 减少长度)。\n"
            f"请评估该动作的优化效果(返回 0 到 1 的评分,1 表示最佳优化)。"
        )
        response = self.llm.invoke(prompt)
        try:
            llm_reward = float(response.content.strip())
        except:
            llm_reward = 0.5  # 默认值
        # 综合奖励
        reward = 0.7 * rule_reward + 0.3 * llm_reward
        if action == self.current_chip["optimal_action"]:
            reward += 0.2  # 额外奖励
        return reward

    def step(self, action):
        self.step_count += 1
        adjustment = 5.0
        # 调整芯片参数
        if action == 0:
            self.current_chip["width_nm"] += adjustment
        elif action == 1:
            self.current_chip["width_nm"] = max(5.0, self.current_chip["width_nm"] - adjustment)
        elif action == 2:
            self.current_chip["length_nm"] += adjustment
        elif action == 3:
            self.current_chip["length_nm"] = max(5.0, self.current_chip["length_nm"] - adjustment)

        # 计算奖励
        reward = self._compute_reward(action)
        done = self.step_count >= self.max_steps_per_episode or self.current_chip["performance_score"] >= 95.0
        info = {"chip_id": self.current_chip["chip_id"]}

        return self._get_observation(), reward, done, info

class CustomPolicy(ActorCriticPolicy):
    def __init__(self, observation_space, action_space, lr_schedule, *args, **kwargs):
        super(CustomPolicy, self).__init__(observation_space, action_space, lr_schedule, *args, **kwargs)
        self.llm = ChatOllama(model="qwen2.5:7b")
        # 自定义策略网络和价值网络
        self.actor = nn.Sequential(
            nn.Linear(observation_space.shape[0], 64),
            nn.ReLU(),
            nn.Linear(64, 64),
            nn.ReLU(),
            nn.Linear(64, action_space.n),
            nn.Softmax(dim=-1)
        )
        self.critic = nn.Sequential(
            nn.Linear(observation_space.shape[0], 64),
            nn.ReLU(),
            nn.Linear(64, 64),
            nn.ReLU(),
            nn.Linear(64, 1)
        )

    def forward(self, obs, deterministic=False):
        obs_tensor = obs if isinstance(obs, torch.Tensor) else torch.tensor(obs, dtype=torch.float32)
        # 策略模型:生成动作分布
        action_probs = self.actor(obs_tensor)
        # 结合 QWEN2.5 建议
        prompt = (
            f"芯片参数:宽度={obs_tensor[0]:.2f}nm, 长度={obs_tensor[1]:.2f}nm, "
            f"功耗={obs_tensor[2]:.2f}mW, 性能评分={obs_tensor[3]:.2f}。\n"
            f"建议调整方向(0: 增加宽度, 1: 减少宽度, 2: 增加长度, 3: 减少长度):"
        )
        response = self.llm.invoke(prompt)
        try:
            action_suggested = int(response.content.strip())
            action_probs[action_suggested] += 0.5  # 增强建议动作的概率
            action_probs = action_probs / action_probs.sum()  # 归一化
        except:
            pass
        # 选择动作
        action = torch.argmax(action_probs, dim=-1) if deterministic else torch.multinomial(action_probs, 1)
        # 价值模型:估计状态值
        value = self.critic(obs_tensor)
        return action, value, torch.log(action_probs)

# 创建环境
env = ChipOptimizationEnv()

# 初始化 PPO 代理
agent = PPO(CustomPolicy, env, verbose=1, learning_rate=3e-4, batch_size=64)

# 训练代理
agent.learn(total_timesteps=10000)

# 保存模型
agent.save("ppo_qwen2.5_chip_model")

训练成功后用以下代码测试

obs = env.reset()
done = False
total_reward = 0
while not done:
    action, _ = agent.predict(obs)
    obs, reward, done, info = env.step(action)
    total_reward += reward
    print(f"Chip ID: {info['chip_id']}, Action: {action}, Reward: {reward:.2f}")
print(f"Total reward: {total_reward:.2f}")

总结

1.PPO,Proximal Policy Optimization,近端策略优化
训练奖励模型(Reward Model, RM):
依赖人类标注的偏好数据(如对多个回答的排序或二元比较)。
例如,给定一个问题,人类标注员选择“回答A比回答B更好”,RM 通过学习这些偏好,为每个回答分配一个标量奖励值(如 A 得分 0.8,B 得分 0.3)。
RM 的损失函数通常基于 Bradley-Terry 模型,确保偏好关系与得分一致。

强化学习优化策略:
使用 PPO 算法,以 RM 输出的奖励为信号,优化模型策略(Policy)。
PPO 通过限制新旧策略的更新幅度(如 KL 散度约束),避免训练不稳定,逐步提升模型生成高奖励回答的能力。

具体步骤
1.训练reward模型(通常在SFT之后)(通常用能力差不多的大模型)
将输出的LM Head(hidden_size * vocabulary_size)改造为REWARD HEAD(hidden_size * 1)
模型会评估出数据集中的chosen 和 rejected 的值

2.PPO模型
策略模型 价值模型 奖励模型 (其中策略、价值模型都是需要训练的模型,输出是策略模型)
(价值模型用来使模型知道怎么做更好,策略模型让模型知道怎么做决策)
随后比较训练模型和基准模型的KL散度
Reward值 = Score + KL*-0.2
Loss值 采样方法用广义优势法:GAE算法 + V值 使用训练模型、基准模型、重要度采样模型

Logo

火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。

更多推荐