详解基于人类反馈的强化学习 (RLHF)算法原理

InstructGPT:让人工智能更听话的技术

InstructGPT 是一种训练大型语言模型(就像 GPT-3)的技术,让它们更好地理解人类的指令,并给出更符合我们期望的回答。这种技术的核心是基于人类反馈的强化学习 (RLHF)。简单来说,就是让人来告诉模型,什么样的回答是好的,然后模型通过学习不断改进自己。

InstructGPT 的训练过程可以分为三个主要步骤:

  • 监督微调 (SFT)

    • 原理:先准备一批高质量的"问答"数据,这些"问答"不是随便写的,而是人根据特定的指令认真编写的。然后,用这些数据来"教"预训练好的 GPT-3 模型,让它学会理解各种指令,并给出对应的回答。

    • 实际应用例子: 假设我们要训练一个可以生成菜谱的模型。

      • 指令:"写一个西红柿炒鸡蛋的菜谱"。

      • 人工编写的期望输出:

        markdown 复制代码
        菜名:西红柿炒鸡蛋
        材料:西红柿2个,鸡蛋3个,葱少许,盐适量,糖少许,食用油适量
        步骤:
        1. 西红柿洗净切块,鸡蛋打散加少许盐。
        2. 葱切葱花。
        3. 热锅冷油,倒入鸡蛋液炒至凝固盛出。
        4. 锅中再放少许油,放入西红柿块翻炒至软烂。
        5. 倒入炒好的鸡蛋,加入盐和糖调味,翻炒均匀。
        6. 撒上葱花即可出锅。
      • 然后用大量类似的"指令-菜谱"数据来微调 GPT-3 模型。

    • Demo 代码 :虽然直接微调 GPT-3 这样的模型需要大量的计算资源,但我们可以用更小的模型和数据集来演示这个过程。比如,使用 Hugging Face 的 transformers 库:

      python 复制代码
      from transformers import AutoModelForCausalLM, AutoTokenizer, Trainer, TrainingArguments
      
      # 加载预训练模型和 tokenizer
      model_name = "distilgpt2"  # 使用一个较小的 GPT-2 模型作为例子
      model = AutoModelForCausalLM.from_pretrained(model_name)
      tokenizer = AutoTokenizer.from_pretrained(model_name)
      tokenizer.pad_token = tokenizer.eos_token
      
      # 准备训练数据
      train_data = [
          {"instruction": "写一个西红柿炒鸡蛋的菜谱", "output": "菜名:西红柿炒鸡蛋\n材料:西红柿2个,鸡蛋3个...\n步骤:1. 西红柿洗净切块...\n"},
          {"instruction": "写一个宫保鸡丁的菜谱", "output": "菜名:宫保鸡丁\n材料:鸡胸肉200克,花生米...\n步骤:1. 鸡胸肉切丁...\n"}
      ]
      
      # 格式化数据
      def format_data(data_point):
          text = "Instruction: " + data_point["instruction"] + "\nOutput: " + data_point["output"]
          return text
      
      train_encodings = tokenizer([format_data(item) for item in train_data], truncation=True, padding=True, return_tensors="pt")
      
      # 定义 Trainer
      training_args = TrainingArguments(
          output_dir="./results",
          num_train_epochs=3,
          per_device_train_batch_size=1,
          logging_dir="./logs",
      )
      
      class RecipeDataset:
          def __init__(self, encodings):
              self.encodings = encodings
      
          def __getitem__(self, idx):
              return {key: val[idx].clone().detach() for key, val in self.encodings.items()}
      
          def __len__(self):
              return len(self.encodings.input_ids)
      
      train_dataset = RecipeDataset(train_encodings)
      
      
      trainer = Trainer(
          model=model,
          args=training_args,
          train_dataset=train_dataset,
          tokenizer=tokenizer,
      )
      
      # 开始训练
      trainer.train()
      
      # 使用模型生成菜谱
      def generate_recipe(instruction):
          input_text = "Instruction: " + instruction + "\nOutput: "
          input_ids = tokenizer.encode(input_text, return_tensors="pt")
          output = model.generate(input_ids, max_length=200, num_return_sequences=1, no_repeat_ngram_size=2)
          recipe = tokenizer.decode(output[0], skip_special_tokens=True)
          return recipe
      
      instruction = "写一个麻婆豆腐的菜谱"
      recipe = generate_recipe(instruction)
      print(recipe)
      • 代码解释
        • 首先,我们加载了一个预训练的 distilgpt2 模型和一个 tokenizer。
        • 然后,我们准备了一些训练数据,包括指令和对应的输出。
        • 我们定义了一个 format_data 函数,将指令和输出拼接成一个字符串,作为模型的输入。
        • 我们使用 tokenizer 将训练数据编码成模型可以理解的格式。
        • 我们定义了一个 RecipeDataset 类,用于加载和处理训练数据。
        • 我们使用 Trainer 类来训练模型。
        • 最后,我们定义了一个 generate_recipe 函数,用于使用模型生成菜谱。
  • 训练奖励模型 (RM)

    • 原理:让模型针对同一个指令生成多个不同的回答,然后让人对这些回答进行排序,选出最好的。奖励模型通过学习人类对不同模型输出的偏好来进行训练。

    • 实际应用例子: 仍然以菜谱生成为例。

      • 指令:"写一个西红柿炒鸡蛋的菜谱"。
      • 模型生成了三个不同的菜谱:
        • 菜谱 1:步骤比较简单,但描述不够详细。
        • 菜谱 2:步骤非常详细,但有些步骤过于繁琐。
        • 菜谱 3:步骤适中,描述也比较清晰。
      • 人工对这三个菜谱进行排序:菜谱 3 > 菜谱 2 > 菜谱 1。
      • 然后用大量类似的排序数据来训练奖励模型,让它学会判断哪个菜谱更好。
    • Demo 代码:训练奖励模型也需要大量的数据和计算资源。我们可以用一个简单的例子来演示这个过程。

      python 复制代码
      import torch
      import torch.nn as nn
      import torch.optim as optim
      
      # 定义奖励模型
      class RewardModel(nn.Module):
          def __init__(self, input_size):
              super(RewardModel, self).__init__()
              self.linear1 = nn.Linear(input_size, 64)
              self.linear2 = nn.Linear(64, 1)
      
          def forward(self, x):
              x = torch.relu(self.linear1(x))
              x = self.linear2(x)
              return x
      
      # 准备训练数据
      # 假设我们已经有了模型生成的多个菜谱的 embedding,以及人工对这些菜谱的排序
      recipe1_embedding = torch.randn(1, 10) # 假设 embedding 的维度是 10
      recipe2_embedding = torch.randn(1, 10)
      recipe3_embedding = torch.randn(1, 10)
      
      # 假设人工排序是 recipe3 > recipe2 > recipe1
      # 我们需要将排序转换成奖励值
      reward1 = 1.0 # 最差的菜谱的奖励值
      reward2 = 2.0
      reward3 = 3.0 # 最好的菜谱的奖励值
      
      # 定义损失函数
      loss_fn = nn.MarginRankingLoss(margin=1.0) # 使用 MarginRankingLoss 来学习排序
      
      # 定义模型和优化器
      model = RewardModel(10)
      optimizer = optim.Adam(model.parameters(), lr=0.001)
      
      # 训练模型
      for i in range(100):
          optimizer.zero_grad()
      
          # 计算每个菜谱的奖励值
          score1 = model(recipe1_embedding)
          score2 = model(recipe2_embedding)
          score3 = model(recipe3_embedding)
      
          # 使用 MarginRankingLoss 计算损失
          loss = loss_fn(score3, score2, torch.tensor([1])) + loss_fn(score2, score1, torch.tensor([1]))
      
          # 反向传播和更新参数
          loss.backward()
          optimizer.step()
      
          if i % 10 == 0:
              print(f"Epoch {i}, Loss: {loss.item()}")
      
      # 使用奖励模型预测菜谱的奖励值
      print(f"Recipe 1 Reward: {model(recipe1_embedding).item()}")
      print(f"Recipe 2 Reward: {model(recipe2_embedding).item()}")
      print(f"Recipe 3 Reward: {model(recipe3_embedding).item()}")
      • 代码解释
        • 首先,我们定义了一个简单的奖励模型,它接受一个菜谱的 embedding 作为输入,并输出一个奖励值。
        • 然后,我们准备了一些训练数据,包括模型生成的多个菜谱的 embedding,以及人工对这些菜谱的排序。
        • 我们将人工排序转换成奖励值,最好的菜谱的奖励值最高,最差的菜谱的奖励值最低。
        • 我们使用 MarginRankingLoss 作为损失函数,它可以学习排序关系。
        • 我们使用 Adam 优化器来训练模型。
        • 最后,我们使用训练好的奖励模型来预测菜谱的奖励值。
  • 强化学习 (RL)

    • 原理:使用奖励模型作为"裁判",让模型不断尝试生成新的回答。如果生成的回答能够获得更高的奖励,就说明这个回答更好,模型就会学习并记住这种回答方式。同时,为了防止模型"跑偏",我们会限制它不要偏离原始预训练模型的预测太远。

    • 实际应用例子: 仍然以菜谱生成为例。

      • 模型生成一个新的菜谱。
      • 奖励模型对这个菜谱进行评分。
      • 如果奖励很高,就说明这个菜谱很好,模型就会学习并记住这种生成菜谱的方式。
      • 如果奖励很低,就说明这个菜谱不好,模型就会尝试生成新的菜谱。
      • 同时,为了防止模型生成一些不靠谱的菜谱(比如用奇怪的食材),我们会限制它不要偏离原始预训练模型的预测太远。
    • Demo 代码:强化学习的训练过程非常复杂,需要大量的计算资源。我们可以用一个简单的例子来演示这个过程。

      python 复制代码
      import torch
      import torch.nn as nn
      import torch.optim as optim
      from torch.distributions import Categorical
      
      # 定义策略模型(Policy Model),这里简化为一个简单的线性模型
      class PolicyModel(nn.Module):
          def __init__(self, input_size, output_size):
              super(PolicyModel, self).__init__()
              self.linear = nn.Linear(input_size, output_size)
      
          def forward(self, x):
              x = torch.softmax(self.linear(x), dim=-1)  # 输出概率分布
              return x
      
      # 模拟奖励模型(Reward Model),这里简化为一个函数
      def reward_model(action):
          # 假设 action 是一个 one-hot 向量,代表选择的菜谱
          # 这里简单地给不同的菜谱不同的奖励
          if action == 0:  # 假设 action 0 是一个好的菜谱
              return 1.0
          elif action == 1:
              return 0.5
          else:
              return 0.0
      
      # 超参数
      input_size = 10  # 假设输入状态的维度是 10
      output_size = 3  # 假设有 3 个可能的动作(菜谱)
      learning_rate = 0.01
      gamma = 0.99  # 折扣因子
      
      # 初始化模型和优化器
      policy_model = PolicyModel(input_size, output_size)
      optimizer = optim.Adam(policy_model.parameters(), lr=learning_rate)
      
      # 训练循环
      num_episodes = 100
      for episode in range(num_episodes):
          # 模拟一个 episode
          state = torch.randn(1, input_size)  # 初始状态
          log_probs = []
          rewards = []
          actions = []
      
          for t in range(10):  # 每个 episode 运行 10 步
              # 1. 策略模型根据当前状态输出一个动作的概率分布
              probs = policy_model(state)
              m = Categorical(probs)  # 创建一个类别分布
              action = m.sample()  # 根据概率分布采样一个动作
              log_prob = m.log_prob(action)  # 计算这个动作的对数概率
      
              # 2. 执行动作,获得奖励
              reward = reward_model(action.item())
      
              # 3. 记录 log_prob 和 reward
              log_probs.append(log_prob)
              rewards.append(reward)
              actions.append(action)
      
              # 4. 更新状态(这里简化为随机更新)
              state = torch.randn(1, input_size)
      
          # 计算 discounted rewards
          discounted_rewards = []
          R = 0
          for r in reversed(rewards):
              R = r + gamma * R
              discounted_rewards.insert(0, R)
          discounted_rewards = torch.tensor(discounted_rewards)
          # discounted_rewards = (discounted_rewards - discounted_rewards.mean()) / (discounted_rewards.std() + 1e-8)  # 标准化
      
          # 计算 loss
          policy_loss = []
          for log_prob, R in zip(log_probs, discounted_rewards):
              policy_loss.append(-log_prob * R)
          policy_loss = torch.cat(policy_loss).sum()
      
          # 反向传播和优化
          optimizer.zero_grad()
          policy_loss.backward()
          optimizer.step()
      
          # 打印 episode 信息
          print(f"Episode {episode}, Total Reward: {sum(rewards)}, Policy Loss: {policy_loss.item()}")
      • 代码解释
        • 首先,我们定义了一个简单的策略模型 PolicyModel,它根据当前状态(state)输出一个动作(action)的概率分布。
        • 我们定义了一个模拟的奖励模型 reward_model,它根据选择的动作(菜谱)给出奖励。
        • 在训练循环中,我们模拟多个 episodes。
        • 在每个 episode 中,策略模型根据当前状态输出一个动作的概率分布,然后我们根据这个概率分布采样一个动作。
        • 我们执行这个动作,获得奖励,并记录 log_prob 和 reward。
        • 然后,我们计算 discounted rewards,也就是考虑了折扣因子的未来奖励。
        • 我们使用 discounted rewards 来计算 policy loss,并使用反向传播来更新策略模型的参数。

通过这三个步骤,InstructGPT 能够更好地理解用户意图,生成更符合人类偏好的输出。虽然它还不能做到完美,但它代表了人工智能发展的一个重要方向:让人工智能真正为人类服务。

相关推荐
学编程~ing的Ли7 分钟前
C语言——排序(冒泡,选择,插入)
c语言·算法·排序算法
pchmi8 分钟前
C# OpenCV机器视觉:智能水果采摘
人工智能·opencv·c#·机器视觉
江河地笑17 分钟前
逻辑回归不能解决非线性问题,而svm可以解决
算法·支持向量机·逻辑回归
EterNity_TiMe_27 分钟前
【人工智能】deepseek R1模型在蓝耘智算平台的搭建与机器学习的探索
人工智能·python·机器学习·deepseek
灵魂画师向阳29 分钟前
白嫖RTX 4090?Stable Diffusion:如何给线稿人物快速上色?
java·大数据·人工智能·ai作画·stable diffusion
Excuse_lighttime1 小时前
堆排序
java·开发语言·数据结构·算法·排序算法
睡不着还睡不醒1 小时前
【力扣】146.LRU缓存
算法·leetcode·职场和发展
新加坡内哥谈技术1 小时前
ChunkKV:优化 KV 缓存压缩,让 LLM 长文本推理更高效
人工智能·科技·深度学习·语言模型·机器人
Narnat1 小时前
opencv打开摄像头出现读取帧错误问题
人工智能·opencv·计算机视觉
珠江上上上1 小时前
支持向量机原理
人工智能·深度学习·算法·机器学习·支持向量机·数据挖掘