DLAI-大模型后训练笔记-全-

DLAI 大模型后训练笔记(全)

001:课程介绍 🎯

在本节课中,我们将要学习大型语言模型后训练的基本概念、主要方法及其应用场景。课程由华盛顿大学助理教授、Nexus S联合创始人Ban Hua Z讲授,他将分享其在训练和微调众多模型方面的丰富经验。

训练一个大型语言模型通常包含两个主要阶段。首先是预训练阶段,模型学习从海量文本中预测下一个词或标记。从计算和成本角度看,这是训练的主体部分,对于非常大的模型,可能需要处理数万亿甚至数十万亿的文本标记,耗时可能长达数月。

后训练阶段

上一节我们介绍了预训练,本节中我们来看看后训练。后训练是指模型被进一步训练以执行更具体的任务,例如回答问题。这个阶段通常使用规模小得多的数据集,速度更快,成本也更低。在本课程中,你将学习三种常见的后训练和定制LLM的方法,并且你将实际下载一个预训练模型,以一种相对计算成本可承受的方式进行后训练。

以下是三种核心的后训练技术:

  1. 监督微调:这种方法在标注好的“提示-响应”数据对上训练模型,旨在通过学习复制这种理想的输入-输出关系,让模型学会遵循指令或使用工具。监督微调对于引入新行为或对模型进行重大改变尤其有效。在课程中,你将微调一个小型模型以遵循指令。
  2. 直接偏好优化:DPO通过向模型展示好的和坏的答案来教导它。对于同一个提示,DPO给出两个选项,其中一个比另一个更受偏好。通过一个对比损失函数,DPO推动模型更接近好的响应,远离差的响应。例如,如果模型说“我是你的助手”,但你希望它说“我是你的AI助手”,你可以将前者标记为差响应,后者标记为好响应。你将使用DPO来改变一个小型模型的身份设定。
  3. 在线强化学习:这是第三种技术。你给模型提供提示,它生成响应,然后一个奖励函数对这些答案的质量进行评分。模型根据这些奖励分数进行更新。获取奖励模型的一种方法是基于人类对响应质量的判断,然后训练一个函数来分配与人类判断一致的分数。最常用的算法是近端策略优化。另一种获取奖励的方式是通过可验证奖励,这适用于具有客观正确性标准的任务,如数学或编程。你可以使用数学检查器或单元测试来客观衡量生成的数学解或代码是否正确。这种正确性度量就构成了奖励函数。一个利用此类奖励函数的强大方法是组相对策略优化,由DeepSeek引入。在本课程中,你将使用GRPO来训练一个小型模型解决数学问题。

课程安排与致谢

本课程的第一课将概述后训练方法。在这节课中,你将学习何时应该进行后训练,以及你可以从哪些后训练选项中进行选择。

许多人为创建本课程提供了帮助,特别感谢Alex、来自UC Berkeley的Tntaohao,以及来自DeepSeek AI的Gegari。

本节课中我们一起学习了大型语言模型后训练的核心阶段与三种关键技术:监督微调、直接偏好优化和在线强化学习。理解这些方法为后续动手实践奠定了基础。接下来,让我们进入下一个视频,开始深入探索。

002:后训练方法介绍 🧠

在本节课中,我们将学习后训练方法的基本概念。后训练是大型语言模型开发流程中的关键步骤,它能让基础模型学会遵循指令、进行对话或执行特定任务。

什么是后训练?

上一节我们介绍了语言模型的整体训练流程,本节中我们来看看后训练的具体定位。

通常,训练语言模型始于一个随机初始化的模型,首先进行预训练。预训练的目标是从海量数据中学习通用知识,数据来源包括维基百科、从整个互联网爬取的Common Crawl数据集,或Github上的代码数据。预训练完成后,我们得到一个基础模型,它能够预测下一个词或标记(token),这里的每个标记是图中高亮显示的子词。

从这个基础模型出发,下一步就是进行后训练。后训练旨在从精心策划的数据中学习如何回应,这些数据包括聊天数据、工具使用数据或智能体数据。经过这个过程,通常会得到一个指令模型聊天模型,它能够响应用户的指令或与用户对话。例如,当用户提问“法国的首都是什么?”时,模型能够回答“法国的首都是巴黎”。

在此之后,还可以进行进一步的或持续的后训练,以改变模型的行为或增强模型的某些特定能力。最终,我们得到一个定制化模型,它专精于某些领域或具有特定的行为模式。例如,它可能能够为任何指令写出更好的SQL查询。

理解后训练方法

为了更好地理解后训练方法,让我们先回顾一下在人工智能训练中使用的不同方法。

预训练方法

我们首先从预训练方法开始,这通常被视为无监督学习

通常,我们可以从一个非常大规模的无标签文本语料库开始,这包括维基百科、Common Crawl或Github等。从这些语料库中通常可以提取超过2万亿个标记(token)并进行训练。训练通常在几个段落或句子上进行。

作为一个最小化的例子,模型可能会看到像“I like cats”这样的句子。在这种情况下,训练目标是最小化每个标记在给定之前所有标记条件下的负对数概率

用公式表示,即最小化:
-log P(“I”) - log P(“like” | “I”) - log P(“cats” | “I like”)

通过这种方式,我们训练模型根据已看到的所有前序标记来预测下一个标记。

后训练方法

预训练之后,会采用不同的后训练方法。以下是几种主要方法:

1. 监督微调

监督微调是最简单且最流行的后训练方法之一,被视为监督学习模仿学习

以下是其数据准备和训练过程:

  • 需要创建标注好的“提示-回答”对数据集。
  • 提示通常是给模型的指令,回答是模型应该给出的理想回应。
  • 通常只需要1000到10亿个标记,远少于预训练的规模。
  • 训练损失的最大区别在于,只对回答部分的标记进行训练,而不对提示部分的标记进行训练

2. 直接偏好优化

我们还有更高级的后训练方法,第二种是直接偏好优化

以下是其数据准备和训练过程:

  • 创建的数据集格式为:一个提示,配上一个好的回答和一个坏的回答。
  • 对于任何给定的提示,可以生成多个回答,选择一个被认为是好的,另一个被认为是坏的。
  • 训练模型使其远离坏的回答,并从好的回答中学习
  • 通常也只需要1000到10亿个标记。
  • 有一个更复杂的损失函数用于直接偏好优化,我们将在后续具体课程中详细讲解。

3. 在线强化学习

后训练中的第三种方法是在线强化学习

以下是其数据准备和训练过程:

  • 通常只需要准备提示和一个奖励函数。
  • 从提示开始,要求语言模型自身生成回答。
  • 使用奖励函数为该回答生成奖励。
  • 利用该信号更新模型。
  • 在这种情况下,可能有100到1000万甚至更多的提示。
  • 目标是最大化提示和回答的奖励,其中回答实际上是由语言模型自身生成的。

成功进行后训练的要素

通常,成功的后训练需要三个关键要素。

第一个要素是数据和算法的良好协同设计。

正如我们讨论的,不同的后训练算法选择,包括SFT、DPO或不同的在线强化学习算法,如Reinforce、PPO等,各自需要准备略有不同的数据结构。数据和算法的良好协同设计对于后训练的成功至关重要。

第二个要素是一个可靠且高效的库,能正确实现大多数算法。

这包括Hugging Face的TRL,它是第一个易于使用并实现了本课程提到的大多数算法的库。在本课程的大部分编码实践中,我们将使用这个TRL库。除了Hugging Face TRL,我还推荐你尝试更复杂、内存效率更高的库,包括OpenAI的VLLM和NVIDIA的NeMo。

第三个要素是适当的评估套件。

需要理解在后训练前后,需要什么样的评估套件来跟踪模型性能,并确保模型始终表现良好。

以下是一个当前通常跟踪的语言模型评估的不完整列表:

  • Chatbot Arena:基于人类偏好的聊天评估,人们可以投票选出在他们看来更好的模型,作为人类偏好的替代指标。
  • 聊天模型评估:包括Arena、Evol、MT-Bench或Arena Hard。
  • 静态基准测试:例如,LiveCode Bench是流行的代码基准测试;AIME 2024/2025可能是近期流行的针对硬核数学问题的数学评估数据。
  • 知识与推理相关数据:如GPQA或MMLU Pro。
  • 指令遵循评估数据:如IFeval。
  • 函数调用与智能体评估:包括BIG-Bench、AgentBench或Tool。
  • 多轮工具使用评估:如OsTAL Bench和ToolEmu,更侧重于多轮工具使用场景。

在此列出所有评估指标,我想指出:提升任何一个基准测试的分数可能很容易,但要在不损害其他领域的情况下提升某些基准测试或改变特定模型行为,则要困难得多。在本课程中,我们将探索哪种方法能在不损害其他领域的情况下带来最佳改进。

何时需要进行后训练?

最后,我想指出,并非在每个用例中都必须对模型进行后训练。不同的场景可能有更适合你用例的不同方法。

以下是不同场景的适用方法:

  • 简单指令遵循:如果你只想让模型遵循少量指令,例如“不要讨论某些敏感话题”或“不要将你的公司与某些其他公司比较”,通常可以通过提示工程轻松实现。这种方法简单,但在极端情况下,模型可能并不总是遵循你提供的所有指令。
  • 查询实时数据库或知识库:在这种情况下,检索增强生成或基于搜索的方法可能效果更好,因为它能适应快速变化的知识库。
  • 创建领域特定模型:例如医疗语言模型或网络安全语言模型。在这些情况下,真正重要的是持续预训练,然后是更标准的后训练,让模型先学习知识,再学习如何与用户对话。对于持续预训练,通常需要注入在预训练数据中未见过的大规模领域知识,理想情况下,这些领域知识至少应超过10亿个标记。
  • 紧密遵循指令或提升特定能力:如果你的用例是关于紧密遵循两个或更多指令,或者你想提升某些目标能力,例如创建一个强大的SQL模型、函数调用模型或推理模型,那么后训练最能提供帮助。它可以帮助可靠地改变模型行为并提升目标能力。如果后训练操作不当,可能会损害你没有训练过的其他能力。

总结

本节课中,我们一起学习了什么是后训练、如何进行后训练以及何时需要进行后训练。在下一节课中,我们将深入探讨第一种后训练方法:监督微调。

003:监督微调基础 🧠

在本节课中,我们将学习监督微调的基本概念,包括其方法、常见应用场景以及高质量数据整理的原则。

概述

监督微调通常可以被视为对示例回答的模仿。你可以从任意一个语言模型开始,该模型能够根据给定的提示预测回答。这可以是一个基础模型,当用户提问时,基础模型可能只是预测下一个最可能的词元,因此它可能只是延续并预测一个非常相似的问题,而不是回答问题。

为了在这些基础模型上执行监督微调,我们通常需要创建一些标注数据,其格式为用户问题和理想的助手回答。数据格式可能如下:用户问“告诉我你的身份”,助手回答“我是Llama”或任何你希望它具备的身份模型。用户也可能问“你好吗?”,助手回答“我很好”。通过准备大量此类标注数据,我们就可以开始进行监督微调,模仿标注数据中提供的示例回答。

监督微调的工作原理是通过最小化给定提示下回答的负对数似然。当我们对所有标注数据求和时,就得到了损失函数。我们将在下一张幻灯片中更深入地探讨这个损失函数。

对基础模型执行监督微调后,通常会得到一个微调模型或指令模型,如果操作正确,该模型能够恰当地回应用户的任何查询。

监督微调损失函数

现在,让我们仔细看看这里的公式。监督微调通常最小化回答的负对数似然,最小化负对数似然等价于最大似然估计,这里使用的是交叉熵损失。

对于索引为 i 的任何数据,其中第 i 条数据是一个特定的提示-回答对,监督微调的损失将是给定提示下回答的对数概率的负值。这可以进一步写为负对数似然,其中似然是回答中所有词元在给定包括提示词元在内的所有先前词元条件下的概率的乘积。

用公式表示,对于单个数据点 i(提示 x_i,回答 y_i),损失 L_i 为:
L_i = -log P(y_i | x_i)
这可以分解为:
L_i = - Σ_{t=1}^{T} log P(y_i^t | x_i, y_i^{<t})
其中 T 是回答 y_i 的长度,y_i^t 是回答中的第 t 个词元,y_i^{<t}t 之前的所有词元。

通过这种方式,我们训练模型最大化在给定提示下输出你所提供回答的可能性。这就是为什么监督微调试图模仿这些示例回答。

监督微调的适用场景

以下是监督微调的一些最佳或最合适的应用场景。

第一个场景是想要快速启动一个新的模型行为。例如,你可能希望将一个预训练的语言模型转变为指令模型,或者将一个非推理模型转变为推理模型。也可能存在特定场景,你希望模型使用某些工具,而无需在提示中提供工具描述,模型会假设它已经可以访问这些工具并在回答中调用它们。在这些情况下,监督微调对于快速启动此类模型行为非常理想。

第二个应用场景是提升模型的特定能力。这里我想强调的一个场景是,通过使用更大模型生成的高质量合成数据来训练较小的模型,从而提炼其能力。在这种情况下,你本质上是在使用监督微调将更大模型的能力提炼到小模型中。

高质量监督微调数据整理原则

以下是推荐的高质量监督微调数据整理方法的几个原则和常见方法。

第一种方法是提炼,正如我们之前讨论的,可以从一个更强的指令模型生成回答,然后让一个较小的模型模仿这些生成的回答。

第二种方法可以是“K选最佳”或拒绝采样,你可以从你想要训练的同一原始模型生成多个回答,然后使用奖励函数或其他自动方法从中选择最佳的一个。通过这种方式,你可以获得最佳回答,并尝试模仿模型自身生成的这些最佳回答。

第三种方法是过滤思路,你可以从Hugging Face或内部数据库收集的大规模监督微调数据开始,然后根据回答的质量和提示的多样性对其进行过滤,以获得规模较小但质量更高且足够多样化的监督微调数据。

除了这里提到的常见方法,我还想强调,通常在监督微调数据整理中,对于提升能力而言,质量远比数量重要。如果你有1000条真正高质量且多样化的数据,其监督微调结果通常能胜过100万条混合质量数据的结果。其原理在于,监督微调通常需要模仿你提供的所有数据。如果混合质量数据中存在一些非常糟糕的回答,模型将被迫模仿此类回答,从而导致性能下降。因此,数据质量对于监督微调的成功至关重要。

全参数微调与参数高效微调

最后,我想强调模型调优中一个正交的方向,它与任何后训练方法都是完全平行和正交的,那就是全参数微调与参数高效微调之间的选择。

在全参数微调中,假设我们有一个神经网络层,其中 h 是该层的输出,W 是该层的原始权重矩阵,x 是该层的输入。当人们进行全参数微调时,我们通常会添加一个增量 ΔW,这个 ΔW 是通过梯度下降计算得出的,并且其维度与原始权重 W 完全相同。这种方式下,你必须使用一个额外的 D x D 矩阵来进行模型更新。

存在一种替代方法,称为参数高效微调。我们仍然有原始层,输出 h,层输入 x 和该层的原始权重 W。但是,我们不直接添加一个与原始权重 W 大小相同的增量 ΔW,而是可以添加另外两个较小矩阵的乘法,即 B 乘以 A,其中 B 是一个 D x R 维的矩阵,A 是一个 R x D 维的矩阵,而 R 通常远小于 D。在这种情况下,你需要更新的有效参数数量仅仅是 BA 中的参数总数,这可以远小于原始权重 W 的大小。通过这种方式,你在计算过程中节省了大量内存,同时也使计算更加高效。

我想在此说明,左侧的全参数微调和右侧的参数高效微调都可以与我们在此讨论的任何后训练方法结合使用,包括监督微调、直接偏好优化和在线强化学习。因此,在以下任何方法中,选择全参数微调还是参数高效微调取决于你。像LoRA这样的参数高效微调方法通常能节省大量内存,但另一方面,由于其可调参数较少,学习效果可能稍差,遗忘也较少。

总结

本节课中,我们一起学习了监督微调的细节,以及全参数微调与参数高效微调之间的区别。在下一课中,我们将进行一些关于监督微调的编码实践,将一个基础模型转变为指令模型。我们下节课见。

004:SFT实践指南 🚀

在本节课中,我们将学习如何在一个小规模数据集上构建监督微调(SFT)的完整流程。我们将从加载基础模型开始,准备标注数据,执行SFT训练,并最终评估微调后模型的性能。


导入必要的库

首先,我们需要导入所有必要的库。这些库将帮助我们加载模型、处理数据、配置训练参数以及执行训练过程。

import torch
import pandas as pd
from datasets import Dataset
from transformers import TrainingArguments, AutoTokenizer, AutoModelForCausalLM
from trl import SFTTrainer, DataCollatorForCompletionOnlyLM, SFTConfig

定义辅助函数

为了简化后续操作,我们将定义几个辅助函数。这些函数将用于生成模型回复、测试模型、加载模型以及展示数据集。

1. 生成回复函数

此函数接收模型、分词器、用户消息等参数,并生成模型的回复。

def generate_response(model, tokenizer, user_message, system_message=None, max_new_tokens=128):
    messages = []
    if system_message:
        messages.append({"role": "system", "content": system_message})
    messages.append({"role": "user", "content": user_message})

    prompt = tokenizer.apply_chat_template(messages, tokenize=False)
    inputs = tokenizer(prompt, return_tensors="pt").to(model.device)

    outputs = model.generate(**inputs, max_new_tokens=max_new_tokens)
    response = tokenizer.decode(outputs[0], skip_special_tokens=True)
    return response

2. 测试模型函数

此函数用于测试模型在一系列问题上的表现。

def test_model_with_questions(model, tokenizer, questions, system_message=None, title="测试结果"):
    print(f"\n=== {title} ===")
    for q in questions:
        response = generate_response(model, tokenizer, q, system_message)
        print(f"用户: {q}")
        print(f"助手: {response}\n")

3. 加载模型与分词器

此函数负责从Hugging Face加载指定的模型和分词器。

def load_model_and_tokenizer(model_name, use_gpu=False):
    tokenizer = AutoTokenizer.from_pretrained(model_name)
    model = AutoModelForCausalLM.from_pretrained(model_name)

    if use_gpu:
        model = model.to("cuda")

    if tokenizer.chat_template is None:
        chat_template = """{% for message in messages %}
        {% if message['role'] == 'system' %}
        {{ '系统: ' + message['content'] }}
        {% elif message['role'] == 'user' %}
        {{ '用户: ' + message['content'] }}
        {% elif message['role'] == 'assistant' %}
        {{ '助手: ' + message['content'] }}
        {% endif %}
        {% endfor %}"""
        tokenizer.chat_template = chat_template

    return model, tokenizer

4. 展示数据集

此函数以表格形式展示数据集的内容,便于查看。

def display_dataset(data):
    df = pd.DataFrame(data)
    print(df.to_string(index=False))

加载并测试基础模型

在开始SFT之前,我们先加载一个未经微调的基础模型,并观察它对简单问题的回复。

use_gpu = False  # 根据你的环境设置
test_questions = [
    "用一句话介绍语言模型。",
    "计算1加1减1等于多少?",
    "线程和进程的区别是什么?"
]

# 加载基础模型
base_model_name = "Qwen/Qwen2.5-1.5B"  # 示例模型,实际可使用更小的模型
base_model, base_tokenizer = load_model_and_tokenizer(base_model_name, use_gpu)

# 测试基础模型
test_model_with_questions(base_model, base_tokenizer, test_questions, title="基础模型测试")

运行上述代码后,你会发现基础模型的回复可能不连贯或不符合指令,因为它没有经过针对对话任务的专门训练。


准备SFT训练数据

接下来,我们需要准备用于监督微调的标注数据。数据通常包含用户指令和期望的助手回复对。

以下是示例数据格式:

training_data = [
    {"instruction": "用一句话介绍语言模型。", "response": "语言模型是一种基于统计或神经网络的技术,用于预测和生成自然语言文本。"},
    {"instruction": "计算1加1减1等于多少?", "response": "1 + 1 - 1 = 1。"},
    {"instruction": "线程和进程的区别是什么?", "response": "进程是操作系统资源分配的基本单位,而线程是进程内执行调度的基本单位,线程共享进程的资源。"},
    # ... 更多数据样本
]

你可以根据需求扩展这个数据集,涵盖更多样化的指令和回复。


配置SFT训练参数

现在,我们来配置SFT训练所需的关键超参数。这些参数直接影响训练的效果和效率。

sft_config = SFTConfig(
    learning_rate=2e-5,          # 学习率,需要根据模型和数据调整
    num_train_epochs=1,          # 训练轮数
    per_device_train_batch_size=1, # 每个设备的批次大小
    gradient_accumulation_steps=8, # 梯度累积步数,用于增大有效批次大小
    gradient_checkpointing=False, # 是否使用梯度检查点以节省内存
    logging_steps=10,            # 日志记录频率
    save_steps=500,              # 模型保存步数
    output_dir="./sft_results"   # 输出目录
)

关键概念解释

  • 有效批次大小 = per_device_train_batch_size × gradient_accumulation_steps × GPU数量。它决定了每次参数更新前处理的样本总数。
  • 梯度累积:在内存有限时,通过多次前向传播累积梯度,模拟更大批次训练的效果。

执行SFT训练

配置好参数后,我们使用SFTTrainer来启动训练过程。

# 将数据转换为Hugging Face Dataset格式
dataset = Dataset.from_list(training_data)

# 定义数据整理器,确保模型只对“response”部分计算损失
data_collator = DataCollatorForCompletionOnlyLM(
    instruction_template="instruction",
    response_template="response",
    tokenizer=base_tokenizer
)

# 初始化SFT训练器
trainer = SFTTrainer(
    model=base_model,
    args=sft_config,
    train_dataset=dataset,
    tokenizer=base_tokenizer,
    data_collator=data_collator
)

# 开始训练
trainer.train()

训练过程中,你会看到进度条和损失值日志。对于小模型和小数据集,训练可能在几分钟内完成。


评估微调后的模型

训练完成后,我们加载微调后的模型,并使用同样的问题进行测试,观察性能提升。

# 测试微调后的模型
test_model_with_questions(trainer.model, base_tokenizer, test_questions, title="SFT微调后模型测试")

与基础模型相比,微调后的模型应该能给出更相关、更自然的回复。当然,由于我们使用的是小模型和极小数据集,效果可能有限。要获得更好的性能,需要使用更大的模型(如Qwen2.5-7B)和更丰富的数据集进行完整训练。


总结

在本节课中,我们一起学习了监督微调(SFT)的完整实践流程:

  1. 导入库与定义辅助函数:我们建立了生成回复、测试模型和加载模型的基础工具。
  2. 测试基础模型:我们观察到未经微调的基础模型在对话任务上表现不佳。
  3. 准备训练数据:我们创建了包含指令-回复对的标注数据集。
  4. 配置训练参数:我们学习了如何设置学习率、批次大小等关键超参数。
  5. 执行SFT训练:我们使用SFTTrainer在小数据集上对模型进行了微调。
  6. 评估模型:我们验证了微调后模型在对话能力上的提升。

通过这个流程,你可以将任何基础语言模型转化为一个能够遵循指令、进行流畅对话的助手模型。要获得最佳效果,请务必使用更强大的计算资源和更高质量的大规模数据集。

005:直接偏好优化基础 🧠

在本节课中,我们将学习直接偏好优化的基本概念,包括其方法、常见用例以及构建高质量DPO数据的原则。

概述

直接偏好优化是一种用于微调语言模型的技术,它通过对比学习的方式,利用人类或模型标注的偏好数据,引导模型生成更符合期望的回复。

DPO方法详解

上一节我们介绍了DPO的基本概念,本节中我们来看看其详细的工作原理。

通常,DPO可被视为一种从正负回复中进行对比学习的方法。与监督微调类似,我们可以从任何语言模型开始,通常建议从一个指令微调模型开始,该模型已能回答用户的一些基本问题。

假设用户提问“你是谁”,而模型回答“我是Lama”。在此场景下,我们希望通过标注员准备的对比数据来改变模型的“身份”。标注员可以是人类,也可以是模型本身。

在这种情况下,用户可能提问“告诉我你的身份”。为了让DPO工作,我们需要准备至少两个回复。我们可以准备一个回复说“我是AI助手”,另一个回复说“我是Lama”。其中,“我是AI助手”被标记为偏好回复,“我是Lama”被标记为非偏好回复。这样,我们试图鼓励模型在面对身份相关问题时,更倾向于回答“我是AI助手”而非“我是Lama”。

收集此类对比数据后,就可以在此语言模型上使用准备好的数据和特定的损失函数执行DPO。我们将在本节课稍后深入探讨这个损失函数。

在语言模型上执行DPO后,我们将得到一个微调后的模型。理想情况下,该模型能从这些正负样本中学习。在本例中,它将尝试模仿偏好样本。如果用户进一步提问“你是谁”,助手将回答“我是AI助手”而非“Lama”。通过这种方式,我们使用DPO方法改变了模型的“身份”。

DPO损失函数

了解了DPO的基本流程后,现在我们来深入其核心——损失函数。

通常,DPO被视为最小化对比损失,该损失惩罚负面回复并鼓励正面回复。DPO损失实际上是一个基于奖励模型奖励差异的交叉熵损失。

让我们仔细看看这个DPO损失函数,它是一个负对数西格玛函数,函数内部是某个对数差。其中,σ是sigmoid函数,β是一个非常重要的超参数,需要在DPO训练过程中进行调整。β值越高,这个对数差就越重要。

在这个括号内,我们有两个对数差,分别关注正样本和负样本。首先,我们来看两个概率比值的对数。分子π_θ是微调模型,我们关注的是给定提示词时,微调模型生成正面回复的概率。分母π_ref是参考模型,它是原始模型的固定副本,其权重被冻结且不可调整。我们只关注原始模型在给定提示词下生成那些正面回复的概率。

类似地,对于负样本,我们也有对数比率,其中π_θ是您要微调的模型,π_ref是固定的参考模型。本质上,这个对数比率项可以被视为奖励模型的一种重新参数化。如果您将其视为奖励模型,那么这个DPO损失本质上是正样本与负样本之间奖励差异的sigmoid函数。本质上,DPO试图最大化正样本的奖励,并最小化负样本的奖励。

关于为何这种对数比率可以被视为此类奖励模型的重新参数化,建议您阅读原始的DPO论文以获取详细信息。

DPO最佳用例

理解了DPO的原理后,我们来看看它的典型应用场景。

以下是DPO的一些最佳用例:

  • 改变模型行为:当您希望对模型回复进行小幅修改时,DPO通常非常有效。这包括改变模型身份、提升模型的多语言回复能力、指令遵循能力,或改变模型某些与安全性相关的回复。
  • 提升模型能力:通常,如果操作得当,由于其能同时看到好样本和坏样本的对比性质,DPO在提升模型能力方面可能优于监督微调。特别是当您能进行在线DPO时,它可能比离线DPO更能提升能力。

高质量DPO数据构建原则

为了有效利用DPO,构建高质量的数据至关重要。以下是构建高质量DPO数据的几个原则和方法。

以下是几种高质量DPO数据构建的常见方法:

  • 修正法:通常可以从原始模型生成回复,将该回复作为负样本,然后对其进行一些增强以使其成为正样本。一个最简单的例子是改变模型身份。您可以从当前模型自身生成的负样本开始,例如对于“你是谁”这个问题,模型可能回答“我是Lama”。您可以直接进行修改,将“Lama”替换为您想要的任何模型身份。在本例中,我们希望模型对同一问题回答“我是AI助手”,因此我们将该回复标记为正样本。通过这种方式,您可以使用这种基于修正的方法,自动创建大规模、高质量的对比数据用于DPO训练。
  • 在线/同策略DPO:这可以视为在线或同策略DPO的一个特例,您希望从模型自身的分布中同时生成正例和负例。本质上,您可以为同一提示词从当前要调整的模型中生成多个回复,然后收集最佳回复作为正样本,最差回复作为负样本。通常,为了确定哪个回复更好、哪个更差,您可以使用某些奖励函数或人工判断来完成这项工作。

需要注意的第二点是避免DPO过拟合。因为DPO本质上是进行某种奖励学习,当偏好答案与非偏好答案相比存在某些可学习的“捷径”时,它很容易过拟合。

这里有一个例子:当正样本总是包含一些特殊词汇,而负样本没有时,在此数据上训练可能会非常脆弱,并且可能需要更多的超参数调整才能使DPO在此生效。

总结

在本节课中,我们详细介绍了DPO训练及其数据构建的原则。下一节课,我们将深入一个关于使用DPO改变模型身份的编码实践。期待与您共同探索。

006:DPO实战指南 🚀

在本节课中,我们将学习如何在一个小规模训练数据集上构建完整的DPO(直接偏好优化)训练流程。我们将从一个具有特定身份(“K”)的指令微调模型开始,通过创建偏好对比数据,将其身份更改为“DeepQuin”,并最终训练出一个具有新身份的模型。

概述

上一节我们介绍了DPO的理论基础。本节中,我们来看看如何将理论付诸实践,通过代码实现一个完整的DPO训练流程。我们将使用一个小型模型和数据集来演示,以便于理解和快速运行。

准备环境与模型

首先,我们需要导入必要的库并加载基础模型。

以下是实现DPO所需的库:

import torch
from transformers import AutoTokenizer, AutoModelForCausalLM
from trl import DPOConfig, DPOTrainer
from datasets import load_dataset

接下来,我们加载一个指令微调模型并进行初始测试。我们将使用CPU进行演示以降低硬件要求。

use_gpu = False
model_name = "Qwen/Qwen2.5-0.5B-Instruct"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForCausalLM.from_pretrained(model_name)

让我们用几个关于身份的问题来测试原始模型:

questions = [
    "What is your name?",
    "Who are you?",
    "Tell me about your name and organization."
]
# 生成回答并打印

测试结果显示,模型会一致地回答:“我是K,一个由阿里云训练的语言模型。”这表明模型具有明确的“K”身份。

创建DPO训练数据

DPO训练需要“偏好”(chosen)和“非偏好”(rejected)的答案对。我们的目标是将身份从“K”改为“DeepQuin”。

以下是构建DPO聊天数据集的步骤:

  1. 我们从Hugging Face加载一个包含身份相关对话的原始数据集。
  2. 对于每条数据,我们提取用户的最后一个问题作为提示(prompt)。
  3. 我们使用当前模型为该提示生成一个回答,作为“非偏好”(rejected)回答。
  4. 我们创建“偏好”(chosen)回答,方法是将“非偏好”回答中的所有“K”替换为“DeepQuin”。
  5. 我们还需要替换系统提示,因为原始系统提示也包含了模型的身份信息。
def build_dpo_chat(item):
    prompt = item['conversations'][-2]['value'] # 假设最后一条用户消息
    # 使用模型生成回答(作为rejected)
    inputs = tokenizer(prompt, return_tensors="pt")
    outputs = model.generate(**inputs)
    rejected_response = tokenizer.decode(outputs[0], skip_special_tokens=True)

    # 创建chosen回答:将rejected中的‘K’替换为‘DeepQuin’
    chosen_response = rejected_response.replace('K', 'DeepQuin')

    return {
        "prompt": prompt,
        "chosen": chosen_response,
        "rejected": rejected_response
    }

我们将这个函数应用到原始数据上。为了加速演示,我们只处理前5条数据。

raw_data = load_dataset("identity_dataset")
small_data = raw_data.select(range(5))
dpo_dataset = small_data.map(build_dpo_chat, remove_columns=...)

处理后的数据中,“chosen”回答总是自称“DeepQuin”,而“rejected”回答则自称“K”。

配置与运行DPO训练

数据准备就绪后,我们就可以开始DPO训练了。

首先,我们设置DPO的训练配置。这与SFT(监督微调)的配置类似,但多了一个关键的超参数 beta

dpo_config = DPOConfig(
    per_device_train_batch_size=4,
    gradient_accumulation_steps=2,
    num_train_epochs=1,
    learning_rate=5e-5,
    logging_steps=10,
    beta=0.1, # DPO特有的超参数,控制偏好强度
    use_cpu=not use_gpu
)

beta 参数在DPO的原始公式中至关重要,它决定了模型对偏好对数差异的重视程度。你需要结合学习率一起调整它,以获得最佳性能。

现在,我们初始化DPOTrainer并开始训练。

trainer = DPOTrainer(
    model=model,
    ref_model=None, # 将自动创建当前模型的副本作为参考模型
    args=dpo_config,
    tokenizer=tokenizer,
    train_dataset=dpo_dataset,
)

trainer.train()

由于我们使用的是小模型和小数据集(仅100个样本),训练会很快完成。在完整数据集上训练更大的模型(如Qwen2.5-7B)才能达到之前展示的、将身份完全改为“DeepQuin”的效果。

验证训练结果

训练完成后,我们再次用相同的问题测试模型。

# 使用训练后的模型生成回答
for question in questions:
    inputs = tokenizer(question, return_tensors="pt")
    outputs = model.generate(**inputs)
    print(tokenizer.decode(outputs[0], skip_special_tokens=True))

在小规模演示中,变化可能不明显。但在完整训练后,模型的回答将从“我是K”变为“我是DeepQuin”,而模型的其他知识和能力保持不变。

总结

本节课中我们一起学习了DPO的完整实战流程。我们从加载和测试一个基础模型开始,然后逐步创建了用于改变模型身份的偏好对比数据,接着配置并运行了DPO训练,最后验证了训练结果。这个过程清晰地展示了如何利用DPO技术,以相对直接的方式引导大型语言模型的行为朝向人类偏好。

在下一节课中,我们将学习在线强化学习的基础知识。

007:在线强化学习基础 🧠

在本节课中,我们将学习在线强化学习的基本概念,包括其方法、常见用例以及高质量数据整理的原则。


在线与离线学习

上一节我们介绍了在线强化学习的概念,本节中我们来看看它与离线学习的关键区别。

在在线学习中,模型通过实时生成新的响应来学习。它收集这些新响应及其对应的奖励,并使用这些数据来更新自身,同时在此过程中不断探索新的响应。

相比之下,在离线学习中,模型完全从预先收集好的“提示-响应-奖励”三元组数据中学习。在整个学习过程中,模型不会生成任何新的响应。

我们通常所说的“在线强化学习”,指的就是在线学习环境下的强化学习方法。


在线强化学习流程概览

了解了基本区别后,我们来更详细地看看在线强化学习是如何工作的。

其核心流程是让模型自行探索更好的响应。具体步骤如下:

  1. 从一批提示开始。
  2. 将提示发送给现有的语言模型。
  3. 语言模型基于这些提示生成对应的响应。
  4. 将得到的“提示-响应”对发送给奖励函数。
  5. 奖励函数负责为每一对“提示-响应”标注一个奖励值。
  6. 我们得到一组包含提示、响应和奖励的数据。
  7. 使用这些数据来更新语言模型。

在模型更新阶段,有多种算法可选。本节课我们将介绍其中两种:近端策略优化(PPO)和分组相对策略优化(GRPO)。


奖励函数的选择

在深入算法之前,我们先探讨在线强化学习中奖励函数的不同选择。以下是两种主要类型:

1. 基于偏好的奖励模型

这种方法通常涉及以下步骤:

  • 让模型生成多个响应,或从不同来源收集响应。
  • 由人类评估员判断哪个响应更好。
  • 基于这些人类偏好数据训练一个奖励模型。

训练时,我们设计一个损失函数,鼓励模型为更受偏好的响应分配更高的奖励。一个常见的损失函数公式是:

loss = -log(σ(R_j - R_k))

其中,R_jR_k 分别是响应 J 和 K 的奖励分数,σ 是 sigmoid 函数。当人类标注者认为响应 J 优于 K 时,此损失函数会促使 R_j 高于 R_k

这种奖励模型通常从一个现有的指令微调模型初始化,然后在大量人类或机器生成的偏好数据上进行训练。它适用于开放式生成任务,如提升聊天能力或安全性,但在基于正确性的领域(如数学、代码)可能不够精确。

2. 可验证的奖励函数

对于数学、代码等有明确答案的领域,我们可以设计可验证的奖励函数。

  • 数学问题:检查模型的答案是否与提供的标准答案匹配。
  • 代码问题:通过运行单元测试来验证代码的正确性。我们可以提供格式为(测试输入, 期望输出)的测试用例,然后执行模型生成的代码,看输出是否匹配。
# 示例:一个简单的代码验证思路
def verify_code(generated_code, test_cases):
    for input_val, expected_output in test_cases:
        # 在安全沙箱中执行 generated_code
        actual_output = execute_in_sandbox(generated_code, input_val)
        if actual_output != expected_output:
            return 0.0 # 奖励为0
    return 1.0 # 所有测试通过,奖励为1

虽然创建可验证的奖励(如准备数学数据集的标准答案、编写单元测试、搭建安全的代码执行环境)需要更多前期努力,但它能提供比奖励模型更可靠、更精确的反馈。因此,这种方法常被用于训练擅长推理(如数学、编程)的模型。


两种在线学习算法:PPO 与 GRPO

现在,让我们深入比较两种流行的在线学习算法。

近端策略优化(PPO)

PPO 被用于训练初代 ChatGPT,其流程如下图所示:

  1. 策略模型:即我们要训练的语言模型本身(图中黄色可训练模块)。
  2. 生成响应:策略模型接收查询 Q,生成输出 O。
  3. 三个关键组件
    • 参考模型:原始模型的副本,权重冻结(图中蓝色冻结模块)。用于计算 KL 散度,防止新模型偏离原始模型太远。
    • 奖励模型:接收查询 Q 和输出 O,给出奖励 R,指导策略模型更新。
    • 价值模型:一个可训练的“评论家”模型,用于为响应中的每个令牌分配信用值,从而将响应级别的奖励分解为令牌级别的奖励。
  4. 优势估计:使用广义优势估计方法,结合奖励 R 和价值模型的输出,计算出一个“优势”值 A。这个 A 表征了每个令牌对整个响应质量的贡献。
  5. 目标函数:PPO 的核心是最大化当前策略 π_θ 下的期望优势。但由于数据来自旧策略 π_θ_old,它使用了重要性采样技巧。其目标函数(简化形式)涉及一个重要度比率:

L(θ) = E[ min( r(θ) * A, clip(r(θ), 1-ε, 1+ε) * A ) ]

其中 r(θ) = π_θ(a|s) / π_θ_old(a|s)A 是优势估计,clip 操作防止比率变化过大,确保训练稳定。

简而言之,PPO 通过一个额外的价值模型为每个令牌精细地分配不同的优势值,从而进行更精细的优化。

分组相对策略优化(GRPO)

GRPO 由 DeepSeek 提出并广泛使用,其流程与 PPO 相似,但有关键区别:

  1. 生成一组响应:对于同一个查询 Q,策略模型生成 G 个不同的响应(O1 到 Og)。
  2. 计算奖励:使用参考模型和奖励模型,为这组响应中的每一个计算 KL 散度和奖励。
  3. 计算相对优势:在这组响应内部,通过某种分组计算(例如,基于奖励的排序或归一化)得出每个响应的相对奖励。这个相对奖励就被直接当作该响应中所有令牌的优势值 A。
  4. 更新模型:使用这个统一的优势值 A 来更新策略模型。

在得到优势值 A 之后的更新步骤,GRPO 与 PPO 非常相似。主要区别在于优势估计的方式:

  • PPO:依赖一个需要额外训练的价值模型,为响应中的每个令牌计算不同的优势值。
  • GRPO:摒弃了价值模型,通过对一组响应进行组内比较,为同一响应中的所有令牌赋予相同的优势值。

算法对比与总结

最后,我们来总结一下 PPO 与 GRPO 的对比及其适用场景。

以下是两者的核心对比:

  • GRPO

    • 设计特点:更适合二元的、基于正确性的奖励。
    • 样本效率:由于只为完整响应分配信用,通常需要更多样本来进行有效学习。
    • 内存效率:无需价值模型,GPU 内存占用更少。
    • 优势粒度:为同一响应中的所有令牌提供均匀的优势反馈。
  • PPO

    • 设计特点:与奖励模型或二元奖励函数都能良好配合。
    • 样本效率:如果价值函数训练良好,可以更高效地利用样本。
    • 内存效率:需要额外的价值模型,GPU 内存占用更多。
    • 优势粒度:通过价值模型为每个令牌提供细粒度的、不同的优势反馈。

本节课总结

在本节课中,我们一起学习了在线强化学习与离线学习的区别,并深入探讨了两种重要的在线强化学习算法:分组相对策略优化(GRPO)和近端策略优化(PPO)。我们了解了它们的工作流程、核心区别以及各自的优缺点。

在下一节课中,我们将实际运用 GRPO 方法来提升一个语言模型的数学能力。敬请期待!

008:在线强化学习实践 🚀

在本节课中,我们将学习如何构建一个用于组相对策略优化的完整流程。GRPO是一种流行的在线强化学习方法,其核心思想是让模型在环境中自主探索并生成更好的回答,然后根据奖励信号进行自我优化。

概述

我们将从准备一组数学问题开始,让当前的语言模型生成多个回答。接着,我们会创建一个简单的奖励函数来评估回答是否正确。最后,利用收集到的提示、回答和奖励数据,通过GRPO算法来更新语言模型。下面,让我们一步步通过代码来实现这个过程。

准备工作

与之前的DPO和SFT课程类似,我们首先需要导入必要的库。不同之处在于,本次我们将使用GRPO训练器和配置来设置训练环境。

# 导入必要的库
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
from datasets import load_dataset
# ... 其他GRPO相关导入

为了进行数学能力评估,我们使用GSM8K数据集。首先进行一些基础设置。

use_gpu = False  # 如果在自己的GPU机器上运行,可设置为True
system_prompt = "你是一个有帮助的系统,请逐步解决问题,并始终将最终的数字答案放在一个方框内。"

这个系统提示词至关重要,它能引导模型以规范的格式输出答案,便于后续提取和对比。

定义奖励函数

奖励函数对于GRPO训练和GSM8K评估都至关重要。它接收模型生成的回答和标准答案,并输出奖励值。

import re

def reward_function(completions, ground_truth):
    """
    计算模型回答的奖励。
    参数:
        completions: 模型生成的回答文本
        ground_truth: 标准答案
    返回:
        reward: 1(正确)或 0(错误)
    """
    # 使用正则表达式匹配方框内的内容
    pattern = r'\[\[(.*?)\]\]'  # 假设答案格式为 [[答案]]
    match = re.search(pattern, completions)
    if match:
        model_answer = match.group(1).strip()
    else:
        model_answer = ""
    # 比较模型答案与标准答案
    reward = 1 if model_answer == ground_truth else 0
    return reward

奖励函数公式可以概括为:
奖励 = 1, 如果 模型答案 == 标准答案;否则 奖励 = 0

让我们测试一下这个函数。

# 测试正面例子
positive_prediction = "首先,经过几步计算... 最终答案是 [[72]]。"
positive_ground_truth = "72"
print(reward_function(positive_prediction, positive_ground_truth))  # 输出: 1

# 测试负面例子
negative_prediction = "计算结果是 [[71]]。"
negative_ground_truth = "72"
print(reward_function(negative_prediction, negative_ground_truth))  # 输出: 0

加载与预处理评估数据

现在,我们加载GSM8K数据集的测试部分,并进行预处理,以便用于评估。

# 加载数据集
dataset = load_dataset("openai/gsm8k", split="test")
# 为了加速,只取前5条数据
eval_data = dataset.select(range(5))

def preprocess_function(example):
    """
    预处理数据,提取标准答案并构建完整提示。
    """
    # 从原始答案中提取标准答案(假设答案在 #### 后)
    ground_truth = example['answer'].split('#### ')[-1].strip()
    # 构建完整提示:系统提示 + 用户问题
    full_prompt = system_prompt + "\n\n问题:" + example['question']
    return {"ground_truth": ground_truth, "prompt": full_prompt}

# 应用预处理
processed_eval_data = eval_data.map(preprocess_function)

预处理后的数据包含两列:ground_truth(标准答案)和prompt(完整提示)。让我们查看一下。

print(processed_eval_data[0])

评估初始模型

在开始训练之前,我们先评估一下初始模型(例如Qwen2.5-1.5B)在数学问题上的表现。

# 加载模型和分词器
model_name = "Qwen/Qwen2.5-1.5B-Instruct"
model = AutoModelForCausalLM.from_pretrained(model_name)
tokenizer = AutoTokenizer.from_pretrained(model_name)

def evaluate_model(model, tokenizer, eval_data):
    """
    评估模型在给定数据上的表现。
    """
    predictions = []
    labels = []
    for item in eval_data:
        prompt = item['prompt']
        ground_truth = item['ground_truth']
        # 生成回答
        inputs = tokenizer(prompt, return_tensors="pt")
        outputs = model.generate(**inputs, max_new_tokens=150)
        response = tokenizer.decode(outputs[0], skip_special_tokens=True)
        # 记录预测和标签
        predictions.append(response)
        labels.append(ground_truth)
        # 打印结果以供检查
        print(f"回答: {response[:100]}...")
        print(f"标准答案: {ground_truth}")
    # 计算准确率
    correct = 0
    for pred, label in zip(predictions, labels):
        if reward_function(pred, label) == 1:
            correct += 1
    accuracy = correct / len(predictions)
    print(f"评估准确率: {accuracy * 100:.2f}%")
    return accuracy

initial_accuracy = evaluate_model(model, tokenizer, processed_eval_data)

在本次示例评估中,模型在5个问题上的准确率可能较低(例如20%),这主要是由于生成长度限制和评估样本过少造成的。在实际应用中,应使用完整的测试集并允许更长的生成篇幅。

准备训练数据与GRPO配置

上一节我们完成了模型评估,本节我们来看看如何准备训练数据并设置GRPO训练。

首先,我们加载GSM8K的训练集并进行同样的预处理。

# 加载训练数据
train_dataset = load_dataset("openai/gsm8k", split="train")
# 应用预处理函数
processed_train_data = train_dataset.map(preprocess_function)
# 移除不必要的列,只保留我们需要的
processed_train_data = processed_train_data.remove_columns(['question', 'answer'])
# 如果不用GPU,选择少量数据加速演示
if not use_gpu:
    processed_train_data = processed_train_data.select(range(100))

接下来,配置GRPO训练的关键参数。

from grpo_config import GRPOConfig  # 假设有相应的配置类

grpo_config = GRPOConfig(
    batch_size=4,
    num_epochs=3,
    learning_rate=1e-5,
    logging_steps=10,
    num_generations=4  # GRPO关键参数:为每个提示生成多个回答
)

num_generations 是GRPO特有的超参数,它控制为同一个提示生成多少个回答。在训练时,模型会基于这些回答之间的相对奖励差异进行优化。实践中,这个值可以设置得更高(如64或128),以获得更多样化的比较样本。

执行GRPO训练

现在,我们拥有配置、数据集和奖励函数,可以开始GRPO训练了。

from grpo_trainer import GRPOTrainer  # 假设有相应的训练器

trainer = GRPOTrainer(
    model=model,
    config=grpo_config,
    train_dataset=processed_train_data,
    reward_function=reward_function,
    tokenizer=tokenizer
)

# 开始训练
trainer.train()

训练过程可能需要较长时间。值得注意的是,如果使用一个能力很弱的小模型开始训练,由于它几乎无法答对任何问题,所有回答的奖励都是0,训练损失可能始终为0,看不到明显优化。当换用更大的基础模型(如Qwen2.5B)时,才能观察到训练损失的变化和模型性能的提升。

评估训练后的模型

训练完成后,我们评估优化后模型的性能。

# 加载训练好的模型(此处为演示,加载一个预训练好的模型路径)
trained_model_path = "./path_to_trained_model"
trained_model = AutoModelForCausalLM.from_pretrained(trained_model_path)
trained_tokenizer = AutoTokenizer.from_pretrained(trained_model_path)

final_accuracy = evaluate_model(trained_model, trained_tokenizer, processed_eval_data)
print(f"训练后模型评估准确率: {final_accuracy * 100:.2f}%")

在示例中,一个经过更充分GRPO训练的模型在5个样本上的准确率可能提升到40%。但请注意,为了获得有意义的比较,必须在完整的GSM8K测试集上进行评估,而不仅仅是少数几个样本。

总结

本节课中,我们一起学习了在线强化学习(特别是GRPO)的完整实践流程。

  1. 数据准备与评估:我们学习了如何加载和预处理数学评估数据集(GSM8K),并构建了一个评估流程来测试模型的初始能力。
  2. 奖励函数设计:我们定义了一个核心的奖励函数,它通过比较模型输出和标准答案来提供训练信号。
  3. GRPO训练流程:我们配置了GRPO训练器,理解了关键超参数(如num_generations)的作用,并启动了训练过程。我们了解到,GRPO通过让模型为同一问题生成多个回答,并利用它们之间的相对奖励进行优化。
  4. 结果评估:最后,我们评估了训练后模型的性能,并强调了在完整数据集上进行评估的重要性。

通过本教程,你掌握了使用GRPO算法微调大型语言模型以提升其特定任务(如数学推理)能力的基本方法。记住,成功应用在线强化学习的关键在于设计合适的奖励函数、准备高质量的数据,并进行充分的评估。

009:总结 🎯

在本节课中,我们将回顾并总结几种流行的后训练方法,包括它们的原理、优缺点以及适用场景。通过对比分析,我们将理解不同方法如何影响模型的行为与性能。


回顾主要后训练方法

上一节我们介绍了后训练的整体框架,本节中我们来看看几种具体方法的总结与对比。

以下是三种主要后训练方法的概述:

  • 监督微调

    • 原理:通过最大化示例回答的概率来模仿这些回答。其目标是使模型生成的响应尽可能接近提供的标准答案。
    • 优点:实现简单,能快速引导模型产生新的行为。
    • 缺点:可能损害训练数据未包含的其他任务的性能。
  • 在线强化学习

    • 原理:最大化对模型生成回答的奖励函数。其核心是让模型通过试错,学习生成能获得更高奖励的响应。
    • 优点:能在不损害未见任务性能的前提下提升模型能力。
    • 缺点:实现最为复杂,且需要精心设计奖励函数才能达到最佳效果。
  • 直接偏好优化

    • 原理:鼓励好的回答,同时抑制差的回答。它以对比的方式训练模型,直接优化模型的偏好。
    • 优点:擅长纠正错误行为和提升特定能力。
    • 缺点:可能容易过拟合,其实现复杂度介于监督微调和在线强化学习之间。

在线强化学习 vs. 监督微调:性能差异分析

在对比了各种方法后,我们深入探讨一个关键点:为何在线强化学习通常比监督微调更不容易导致性能下降。

这个过程通常如下:当向语言模型发送提示(Prompt)并让其生成多个回答(R1, R2, R3)时,在线强化学习会从模型自己生成的每个回答中获得奖励信号,并据此反馈和更新模型。

本质上,在线强化学习试图在模型自身原有的能力分布范围内调整其行为

另一方面,对于监督微调,虽然语言模型在收到提示后可能仍会内在产生多种不同的回答思路,但要求模仿的示例回答可能与模型倾向于生成的所有回答都截然不同。

在这种情况下,监督微调可能会将模型“牵引”至一个陌生的方向,从而冒然改变模型原有的能力结构,带来不必要的风险。


课程总结

本节课中,我们一起学习了大型语言模型后训练的核心方法。我们回顾了监督微调、在线强化学习和直接偏好优化的原理与特点,并重点分析了在线强化学习在保持模型整体性能方面的潜在优势。理解这些方法的差异,有助于我们在实际应用中根据目标做出合适的选择。

期待大家未来能运用这些知识构建出出色的模型应用。

posted @ 2026-03-26 08:13  绝不原创的飞龙  阅读(4)  评论(0)    收藏  举报