05:注意力机制与Transformer架构 🧠

05:注意力机制与Transformer架构 🧠在本节课中 我们将要学习什么是自然语言处理 并了解构建 NLP 系统的不同方法 我们将从一个简单的基于规则的系统入手 逐步过渡到机器学习方法 为后续深入学习奠定基础 自然语言处理是一门技术 它使计算机能够处理 生成自然语言文本并与之交互 其核心方面包括 学习有用的语言表示 为下游任务学习有效的语言表征 生成语言 创建文本或代码 用于对话 翻译或问答等场景 作为更大系统的一部分

大家好,我是讯享网,很高兴认识大家。这里提供最前沿的Ai技术和互联网信息。



在本节课中,我们将要学习什么是自然语言处理,并了解构建NLP系统的不同方法。我们将从一个简单的基于规则的系统入手,逐步过渡到机器学习方法,为后续深入学习奠定基础。

自然语言处理是一门技术,它使计算机能够处理、生成自然语言文本并与之交互。其核心方面包括:

  • 学习有用的语言表示:为下游任务学习有效的语言表征。
  • 生成语言:创建文本或代码,用于对话、翻译或问答等场景。
  • 作为更大系统的一部分:语言可以作为与环境交互、获取视觉观察并最终采取行动的更大系统的一部分。

以下是当前NLP系统的几个示例,它们展示了该领域的强大能力。

该系统能够根据提示生成包含多种语言和代码的文本。例如,当要求生成一段展示其能力的文本,并包含英语、日语和Python代码时,模型可以快速生成相应的内容并执行代码。

该系统能够根据科学文献内容回答问题。例如,当输入一篇论文并要求总结其关键贡献时,系统会从数据存储中检索相关信息,并使用语言模型生成高质量的摘要。

该系统能够根据自然语言指令执行软件工程任务。例如,当要求学习PyTorch中的矩阵乘法并创建示例文件时,代理会浏览文档、生成代码示例,并能根据后续请求修改代码。

这些系统背后都有学术论文支撑,本课程将涵盖构建这些系统所需的基础概念。

构建NLP系统的目标是创建一个函数,将涉及语言的输入X映射到输出Y。常见的任务包括:

  • 语言建模:给定文本,预测后续内容。
  • 翻译:将一种语言的文本转换为另一种语言。
  • 文本分类:为文本分配一个标签。
  • 语言分析:从文本中提取结构。
  • 图像描述:输入图像,输出描述性文本。

构建此类系统主要有三种方法。

人类利用领域专业知识和直觉,手动定义函数规则。例如,构建一个文本分类器来判断文档是否与体育相关。

# 一个简单的基于规则的体育分类器示例 sports_keywords = [“篮球”, “足球”, “比赛”] def rule_based_classifier(text): for keyword in sports_keywords: if keyword in text: return “体育” return “其他” 

使用一个通用的语言模型,通过提供指令(提示)来引导其完成任务。例如,通过提示让语言模型判断句子是否与体育相关。

提示:如果以下句子是关于体育的,请回复“体育”,否则回复“其他”。 句子:昨晚的篮球比赛非常精彩。 

收集特定任务的数据集,通过调整模型参数从数据中自动学习函数。这通常需要将数据分为训练集、开发集和测试集。

这三种方法对数据的需求不同:基于规则和提示法原则上可以无需数据,但为了评估和调整,通常需要一些数据进行抽查或构建开发集;而训练方法则需要专门的训练数据来学习参数。

为了更具体地理解构建过程,我们通过一个情感分析任务来演示基于规则的系统构建。任务目标是判断一条影评的情感是积极、消极还是中性。

构建过程分为三个步骤。

将原始文本(如影评)转换为可供处理的数据结构(如数值向量)。我们手动定义“好词”(如love, good)和“坏词”(如hate, bad)列表,并统计它们在文本中出现的次数。

为每个情感类别计算一个得分。我们手动设置权重(如好词权重为1,坏词权重为-1)和偏置项(如0.5),然后通过加权和计算最终得分。

公式score = (count_good * weight_good) + (count_bad * weight_bad) + bias

根据得分决定情感类别。例如,得分大于0为积极,小于0为消极,等于0为中性。

在一个示例数据集上运行这个简单分类器,准确率约为43%。通过分析错误案例,我们发现了一些局限性。

上一节我们介绍了如何构建一个简单的基于规则的系统,本节我们来看看这种方法面临的主要挑战。

  • 低频词处理:难以手动覆盖所有词汇,尤其是低频词。
  • 词形变化:无法有效处理单词的不同形态(如loved, loving)。
  • 否定处理:难以捕捉复杂的否定逻辑(如“not nearly as dreadful”)。
  • 隐喻与类比:无法理解非字面含义的表达。
  • 多语言支持:为每种语言重新构建系统工作量巨大。

这些局限性促使我们转向更强大的机器学习方法。

机器学习方法的核心是利用数据集(训练集、开发集、测试集)通过学习算法自动获得特征提取器和权重参数,并通过推理算法进行预测。

一个初步的尝试是词袋模型。其核心思想是:

  1. 将每个单词(词元)表示为一个独热编码向量
  2. 将句子中所有词的独热向量相加,得到一个特征向量。
  3. 学习一个权重向量(或矩阵,用于多分类),与特征向量做点积得到每个类别的得分。

代码概念

# 假设词汇表为[“I”, “love”, “this”, “movie”] # 句子“I love movie”的特征向量为 [1, 1, 0, 1] # 学习到的权重向量 W = [w1, w2, w3, w4] # 得分 score = [1, 1, 0, 1] · [w1, w2, w3, w4] 

使用一种简单的学习算法(如结构化感知机)训练后,该模型在开发集上的准确率提升至约58.8%。通过检查权重,我们可以发现模型可能学习了某些虚假特征(如“might”在训练集中与负面评价偶然关联),导致了过拟合现象(训练集准确率75%显著高于开发集)。

尽管词袋模型比手动规则进了一步,但它仍存在明显缺陷:

  • 忽略词序:“词袋”之名即源于此。
  • 未解决词形变化:“love”和“loved”被视为完全不同的词。
  • 缺乏词义相似性:无法理解“good”和“great”的相似性。
  • 难以处理组合特征:如“don‘t love”需要结合多个词的信息。
  • 未利用句子结构:忽略了语法和句法信息。

为了解决这些问题,我们需要更强大的模型。

神经网络提供了更强大的解决方案。其核心思想是在流程中引入更多可学习的组件:

  1. 词嵌入:不再使用独热编码,而是为每个词元学习一个稠密的向量表示。这个向量能够捕捉语义信息。
  2. 深度特征提取:将这些词向量输入一个复杂的神经网络(如循环神经网络RNN或Transformer),网络可以学习到比简单求和更好的句子或文本表示。

这种“学习表示”的思想是现代NLP系统(包括大型语言模型)的核心。即使是最先进的模型,其基本流程也与此类似:输入词元 -> 计算中间表示 -> 通过输出层得到预测结果。

本课程将围绕几个核心主题展开。

我们将深入探讨如何表示单词(分词)、语言建模问题(从计数模型到最先进模型),以及序列建模架构(如RNN和Transformer)。你们将在作业中动手构建一个类似LLaMA的Transformer语言模型。

我们将涵盖现代系统的训练与推理方法、上下文学习/提示、预训练、微调以及强化学习。

我们将学习如何评估文本生成系统、进行实验设计、处理人工标注数据,以及使用调试和可解释性技术。

课程后半部分将调研当前的研究热点和工业界重要方向,包括:

  • 高级预训练与后训练技术。
  • 检索与检索增强生成(RAG)。
  • 长序列处理策略。
  • 效率优化(如量化和蒸馏)。
  • 模型集成与混合专家系统。
  • 复杂推理、智能体、多模态NLP、多语言NLP以及偏差与公平性等应用与社会影响。
  • 课前/课后阅读:课程网站提供推荐阅读材料(用于深化理解)和参考文献列表。
  • 课堂互动:鼓励随时提问,课程包含代码/数据讲解环节。
  • 课后测验:每节课后有相关测验,用于巩固知识。
  • 作业安排
    1. 作业1(个人):构建并微调自己的LLaMA风格语言模型。
    2. 作业2(小组):构建一个复杂的检索增强生成系统。
    3. 作业3(最终项目第一部分):文献调研与基线结果复现。
    4. 作业4(最终项目):完成一个研究型项目,提交报告并进行海报展示。

本节课我们一起学习了自然语言处理的基本定义,了解了当前强大的NLP系统实例。我们通过构建一个简单的基于规则的情感分析系统,直观感受了其构建流程与局限性。接着,我们探讨了如何通过机器学习方法(如词袋模型)进行改进,并指出了其不足。最后,我们引入了神经网络作为更强大的解决方案,并概述了本课程将涵盖的基础概念与高级主题。从下节课开始,我们将深入探讨词表示与语言建模的细节。

在本节课中,我们将学习如何用神经网络来表示文本并进行分类。我们将从基础的词袋模型出发,探讨其局限性,并介绍三种关键技术来克服这些局限:子词模型、连续词嵌入和神经网络模型。我们还将学习训练神经网络所需的核心概念,包括损失函数和梯度计算。


首先,关于课程候补名单,这是一个自动化的流程,我无法进行个人干预。如果你在候补名单上,暂时无法加入 Piazza 或 Canvas。课程允许最多放弃两次测验成绩,并且总成绩计算时会自动放弃最低的三次测验成绩。因此,即使错过五次测验,理论上也能挽回所有成绩。如果你成功从候补名单中移除,请给我和助教发邮件,以便将你添加到 Piazza 和 Canvas。如需放弃测验成绩,请在 Piazza 上发帖,我们会手动处理。

上一讲我们介绍了一个非常简单的分类器,称为词袋模型。我们将词元表示为独热向量,然后求和得到特征向量。我们讨论了这种模型的一些缺陷,例如:

  • 无法处理词形变化或复合词。
  • 无法捕捉词之间的相似性(如“love”与“adore”)。
  • 无法处理组合特征(如“don‘t love”和“don’t hate”)。
  • 无法处理句子结构。

本节课,我们将介绍三种技术来克服部分缺陷,下一讲将介绍第四种。

  1. 子词模型:帮助解决词形变化或复合词问题。
  2. 连续词嵌入:捕捉词之间的相似性,并带来其他好处。
  3. 神经网络模型:提供多种优势,其中之一是能够捕捉组合特征。

接下来的课程中,我们将介绍能够建模完整序列并捕捉句子或文档级结构的方法。


本节中,我们来看看如何将单词拆分成更小的单元,即子词。基本思想是,我们不再将句子中的每个单词视为一个整体,而是将其拆分成多个词元。

例如,句子“The companies are expanding”中的单词“companies”可以被拆分为“com”和“panies”两个词元,“expanding”可以被拆分为“exp”和“anding”。这样,不同的词形变化(如“expand”,“expands”,“expanded”)可以共享一些子词(如“exp”),从而实现参数共享。同时,这也有助于减少参数量,节省计算和内存资源。

这引出了一个核心问题:分词。其目标是将原始文本序列转换成一个词元序列。所有可能词元的集合称为词汇表

有多种分词策略:

  • 按空白字符分割:这是我们上节课使用的方法。
  • 子词分割:如上所述。
  • 字符级分割:将每个字符作为一个词元。

我们需要一个既富有表现力(能处理多种语言和代码)又高效(词汇表大小和序列长度适中)的分词方案。在实践中,我们通常选择介于单词级和字符级之间的方案。

你可以访问 tiktokenizer.vercel.app 这个网站,查看不同语言模型(如 GPT-4、Code Llama)如何对文本进行分词。你会发现不同模型的分词方式(如对数字、日语、代码的处理)和生成的序列长度各不相同。

那么,在实践中如何实现分词呢?

第一步是选择初始词汇表。一个想法是使用 UTF-8 编码。Unicode 标准包含了约15万个字符,可以表达几乎所有文本。但直接使用 Unicode 字符作为词汇表,会导致词汇表巨大且序列很长。实际上,计算机使用 UTF-8 编码将 Unicode 字符存储为字节序列。UTF-8 可以用最多4个字节表示任何 Unicode 字符,因此我们只需要一个包含256种可能字节的最小词汇表。但这又会导致序列过长。

第二个关键思想是 字节对编码。这是许多先进系统使用的算法。其思路是:

  1. 从一个基础词汇表(如256个字节)开始。
  2. 准备一个训练集。
  3. 迭代处理训练集,找到最常见的词元对。
  4. 将最常见的词元对合并成一个新的词元,并添加到词汇表中。
  5. 重复此过程,逐步合并词元。

例如,从训练集开始,第一次迭代发现“e”和空格最常见,于是创建新词元“e_”。替换文本后,继续寻找下一个最常见的词元对,如“t”和空格,依此类推,逐步构建子词词汇表。

以下是实践中可用的工具:

  • Tiktoken:由 OpenAI 编写,可以加载他们预训练的分词器。
  • SentencePiece:支持训练自己的 BPE 分词器。它通常以 Unicode 字符作为基础词汇表,并可以设置选项,当遇到未在合并词汇表中的 Unicode 字符时,回退到 UTF-8 字节。

需要注意的是,分词结果严重依赖于 BPE 训练数据。如果某些语言在训练数据中代表性不足,其词汇可能永远不会被合并,导致序列很长。因此,在实践中需要确保 BPE 训练数据的平衡性(例如,包含多种编程语言)。此外,分词结果有时具有任意性,研究人员会在训练时采用技术(如使用多种分词方式)来增强模型对不同分词的鲁棒性,有时也会手动定义规则(例如,规定数字总是按单个数字分词)。


上一节我们介绍了如何将文本分割成词元。本节中,我们来看看如何为这些词元学习有意义的向量表示。

基本思想是,我们不再使用稀疏的独热向量来表示词元,而是使用密集向量连续嵌入。对于词汇表中的每个词元,我们都有一个 D 维向量。这个过程通过一个嵌入层实现,该层本质上是一个矩阵,其中一维是词汇表大小,另一维是嵌入维度 D。查找操作就是选择对应词元的列向量。从概念上讲,这相当于用嵌入矩阵乘以一个独热向量。

基于此,我们可以构建一个连续词袋模型作为词袋模型的升级版:

  1. 将句子中的每个词元通过嵌入层转换为向量。
  2. 将这些向量求和,得到一个隐藏向量 h(维度为 D)。
  3. 为了进行分类,我们将 h 通过一个线性层(即乘以一个权重矩阵并加上偏置项),得到每个类别的分数。

这个架构中需要学习的参数包括:嵌入层的向量、线性层的权重矩阵和偏置项。

在代码中,我们可以这样实现:定义嵌入层和线性输出层,然后执行以下步骤:嵌入词元、求和(或平均)、通过输出层。

使用连续向量的好处是,我们可以在向量空间中定义相似性。如果两个词在某种意义上是相似的,那么它们的向量表示在空间中也应该接近。此外,向量的每个维度可以被解释为某种特征,由学习算法自动发现对任务有用的表示。在实践中,嵌入维度可能设置为512或1024,需要使用降维技术(如 t-SNE)将其可视化到二维平面。


上一节我们介绍了连续词嵌入,但简单的求和操作仍然难以捕捉复杂的组合特征(如“don‘t love”)。本节中,我们来看看如何通过神经网络架构来解决这个问题。

下一步的复杂化是深度连续词袋模型。我们从词袋模型(求和)开始,然后添加多个神经网络层,最后通过一个线性层将隐藏维度映射到类别数量。

关键区别在于,我们引入了激活函数(如 tanh、ReLU)。在此之前的所有操作(矩阵乘法、加偏置)都是线性的。引入非线性激活函数极大地增加了网络的表达能力,使其能够建模更复杂的函数模式。一旦引入非线性,并通过堆叠这些非线性变换层,整个网络就能学习非常复杂的特征组合。

如果没有非线性,仅仅堆叠两个线性层等价于另一个单一的线性层(因为矩阵乘法是线性的)。因此,非线性激活是深度架构的核心。

在代码中实现时,只需在连续词袋模型的基础上添加额外的线性层和激活函数。

现在,这个网络可以学习特征组合。例如,第二层的某个神经元可以接收来自第一层隐藏向量不同维度的输入,这些维度可能分别代表“否定”和“积极/消极情感”等特征。第二层能够将这些特征进行非线性组合和变换。


我们已经了解了模型架构,本节中我们来看看如何训练这些神经网络模型。训练通常使用梯度下降,包含三个步骤:

  1. 定义损失函数
  2. 计算损失函数关于模型参数的梯度
  3. 沿着减少损失的方向更新参数

我们将介绍两种在实践中非常流行的损失函数。

第一种是二元交叉熵损失,用于二分类任务(如情感分类为正面/负面)。模型输出一个标量,我们通过 sigmoid 函数将其转换为属于正类的概率 p(介于0和1之间)。给定真实标签 y(0或1),二元交叉熵损失公式为:
L = -[y * log(p) + (1 - y) * log(1 - p)]
其直观意义是:当模型对正确类别的预测概率很低时,施加高惩罚;预测概率很高时,惩罚趋近于零。










第二种是交叉熵损失,用于多分类任务。模型输出一个分数向量(称为 logits),通过 softmax 函数转换为概率分布。对于真实类别 y(通常表示为独热向量),交叉熵损失是正确类别预测概率的负对数:
L = -log(p_y)
这等价于最小化模型预测分布与真实分布(独热向量)之间的 KL 散度。通过最小化交叉熵,我们试图使这两个分布相互接近。










在代码中,这些损失函数在 PyTorch 等库中已有高效实现。


定义了损失函数后,我们需要计算其关于参数的梯度。虽然可以手动推导(例如对简单模型使用链式法则),但这非常繁琐。现代深度学习框架的核心功能是自动微分

关键思想是将计算表示为计算图。图中的节点是张量(数据)或操作(函数),边表示函数的输入。每个操作节点都知道如何执行前向传播(计算函数值)和反向传播(计算函数关于其输入的局部梯度)。

在反向传播过程中,我们利用链式法则,从最终损失开始,沿着图反向传播梯度。每个节点接收来自上游的梯度,将其与自己的局部梯度相乘,然后将结果传递给下游节点。这样,我们可以高效地计算出损失关于图中任何参数的梯度。

自动微分使得研究人员能够轻松尝试不同的神经架构,而无需手动推导梯度,极大地推动了深度学习的发展。

如今,主要的深度学习框架有:

  • PyTorch:由 Meta 开发,采用动态计算图(命令式编程),在 NLP 领域应用广泛,易于调试。
  • TensorFlow/JAX:由 Google 开发。TensorFlow 早期采用静态计算图。JAX 采用函数式编程范式,更易于并行化和计算高阶导数,在科学计算和大规模模型训练中常见。

本节课我们一起学习了神经文本表示与分类的基础知识。我们回顾了词袋模型的局限性,并介绍了三种改进技术:子词模型(通过 BPE 分词解决词形变化问题)、连续词嵌入(学习词的有意义向量表示)以及神经网络模型(通过非线性激活捕捉复杂特征组合)。我们还深入探讨了训练神经网络的核心组件:二元交叉熵和交叉熵损失函数,以及通过计算图和自动微分进行梯度计算的原理。最后,我们简要介绍了主流的深度学习框架。这些概念为后续学习语言建模等更高级的主题奠定了基础。

在本节课中,我们将要学习语言建模的基础知识。语言建模是自然语言处理中的核心任务,它旨在为所有可能的序列(如句子或文档)定义一个概率分布。我们将从最简单的模型开始,逐步理解其核心概念,这些概念适用于任何复杂的语言模型。

上一节我们介绍了不同类型的预测任务。现在,我们来看看一种更复杂的预测形式——结构化预测。

在结构化预测中,可能的输出空间呈指数级增长,甚至可能是无限的。例如,为一个句子中的每个词分配词性标签,或将一个句子翻译成另一种语言。如果词汇表大小为 V,序列长度为 t,那么可能的输出组合数量是 V^t。这需要与之前课程不同的建模方法。

另一个重要区分是无条件预测与条件预测。

  • 无条件预测/建模:预测单个变量的概率,语言模型的核心即是如此。
  • 条件预测:基于某个输入变量(条件)来预测输出。

语言模型的核心是一个概率分布 P(x),它覆盖了所有可能的序列 x。可以将其想象为一个为每个可能句子分配概率的系统。

为什么这很有用?主要有两个用途:

  1. 评分:可以为不同的序列打分。例如,判断“Jane went to the store.”(语法正确)和“Store the went Jane to.”(语法错误)哪个更可能,从而过滤掉不合语法的句子。
  2. 生成:可以从分布中采样,生成新的序列。更重要的是,可以通过条件生成将语言模型用于各种输入-输出任务。例如,给定一个英语句子(上下文或提示),让模型生成其日语翻译。

通过条件生成,语言模型可以变得非常通用,能够处理问答、分类、语法纠正等多种任务。

面对庞大的输出空间,传统方法是构建自回归语言模型。它基于概率论中的链式法则:

P(x1, x2, ..., xT) = ∏ P(xt | x1, ..., x{t-1})

这个公式将建模整个序列的难题,分解为建模给定上文时下一个词的概率这个相对简单的问题。因此,构建语言模型的关键就变成了:如何基于前面的词来预测下一个词。

我们将按以下路线学习几种方法:

  1. Bigram模型:最简单的模型之一。
  2. N-gram模型:Bigram的扩展。
  3. 神经语言模型:使用神经网络进行预测。
    后续课程将学习循环神经网络和Transformer,它们是现代大语言模型的基础。



Bigram模型做了一个非常简单的假设:预测下一个词时,只考虑前一个词,忽略更早的历史。因此得名“Bi-”(双)gram。

以下是训练一个Bigram模型的步骤:

训练语言模型的目标是学习一个模型,使其尽可能接近真实的数据分布。我们拥有一个数据集,并将其分为训练集、验证集和测试集。

Bigram模型的训练不涉及神经网络,仅基于计数。其参数 θ(可视为一个 V x V 的矩阵)通过以下方式估计:
P(xt | x{t-1}) = count(x{t-1}, xt) / ∑_v count(x{t-1}, v)
其中 count 是在训练语料库中统计的共现次数。










这种计数方法实际上对应于极大似然估计。MLE的目标是找到一组参数 θ,使得模型为训练数据分配的概率(似然)最大:
θ_MLE = argmax_θ ∏ P(x; θ)
取对数后,等价于最大化对数似然:argmax_θ ∑ log P(x; θ)










使用对数有两大好处:一是将连乘转化为求和,便于计算;二是能提高数值稳定性,避免极小数相乘导致的下溢问题。

从KL散度的角度可以更深刻地理解MLE:最小化模型分布与真实数据分布之间的KL散度,在只有有限数据集的情况下,等价于在数据集上最大化对数似然。

有了训练好的模型,就可以进行自回归生成:

  1. 从序列开始标记 开始。
  2. 将当前上下文(最初是 )输入模型,得到下一个词的概率分布。
  3. 从这个分布中采样,得到新词。
  4. 将新词加入上下文,重复步骤2-3,直到采样到序列结束标记

虽然Bigram模型生成的文本质量有限(因为上下文太短),但相比完全随机(均匀采样)的基线,它已经学到了有用的结构信息。

我们通常评估语言模型为数据分配概率的能力。两个常用指标是:

  1. 对数似然:模型为整个测试集分配的总对数概率。通常除以词数进行归一化,得到每词对数似然。为了得到“越低越好”的指标,常报告负对数似然
  2. 困惑度:由每词对数似然计算得来:Perplexity = exp(-平均每词对数似然)。困惑度有一个直观解释:它表示模型在预测下一个词时,平均需要“猜测”多少次才能猜中(如果根据模型分布进行采样)。因此,困惑度越低,模型越好。

Bigram模型的主要限制是上下文太短。一个自然的扩展是N-gram模型,它考虑前 N-1 个词来预测下一个词。

然而,N-gram模型面临一个核心问题:随着 N 增大,许多 N 元组在训练数据中出现的次数极少甚至为零(数据稀疏问题)。这会导致概率估计不准确或为零。

为了解决零概率问题,研究者提出了平滑技术,例如拉普拉斯平滑(加一平滑),即为所有可能的N-gram组合预先加一个小的伪计数,确保没有零概率。

N-gram模型还有其他固有缺陷:

  • 无法共享相似词的信息:例如,“bought a car”和“purchased a car”的统计完全独立。
  • 无法处理长距离依赖:要预测“racket”,可能需要知道前面较远处的“tennis”,但N-gram模型受限于固定窗口。

尽管如此,N-gram模型训练和推理速度极快,且能精确记忆训练数据中的模式,因此在某些需要快速处理的场景(如大规模数据预处理过滤)中仍有应用。常用的工具有 KenLM

上一节我们看到了基于计数的模型的局限性。本节中,我们来看看如何使用神经网络来构建更好的语言模型。

神经语言模型(如前馈神经网络语言模型)仍然基于N-gram的假设,即使用固定长度的上下文。但关键区别在于,它使用一个神经网络来计算下一个词的概率分布,而不是依靠计数。

模型结构如下:

  1. 嵌入层:将上下文中的每个词映射为词向量。
  2. 拼接层:将所有上下文词向量拼接成一个长向量。
  3. 前馈神经网络层:一个或多个全连接层,并带有非线性激活函数(如ReLU),用于提取特征。
  4. 输出层:一个线性层将隐藏状态映射到词汇表大小的向量(称为logits),然后通过 Softmax 函数转换为概率分布。

这个网络可以看作一个特征提取器,它将上下文编码为一个隐藏向量,然后与输出权重矩阵相乘,为每个可能的下一词打分。

训练同样采用极大似然估计。对于每个训练样本(一个序列),损失函数是模型为序列中真实下一个词分配的负对数概率的总和。

这恰好等同于交叉熵损失。在语言建模中,真实的下一个词是一个one-hot向量,模型输出是词汇表上的概率分布。交叉熵损失鼓励模型为正确的下一个词分配高概率。

因此,语言建模这个复杂问题,被巧妙地简化为一个分类问题(预测下一个词),并使用交叉熵损失进行优化。

神经语言模型的优势在于:

  • 共享相似词信息:通过词嵌入,相似词可以具有相近的向量,从而使相似上下文获得相似的隐藏状态。
  • 学习复杂特征:神经网络可以学习基于上下文任意子集的复杂特征组合。
  • 更好的泛化:即使某个特定上下文未在训练数据中出现,模型也能基于相似上下文进行合理预测。

然而,它仍然受限于固定的上下文窗口,无法有效建模长距离依赖。这需要更强大的架构(如RNN、Transformer)来解决。

在构建和训练神经语言模型(及其他深度学习模型)时,有一些重要的实践概念需要了解。

我们希望模型能泛化到未见过的数据,而不仅仅是拟合训练集。

  • 训练集:用于训练模型参数。
  • 验证集/开发集:用于在训练过程中监控模型性能、调整超参数(如学习率、网络层数)和选择**模型。防止模型过拟合训练集。
  • 测试集:在最终模型选定后,用于提供模型泛化能力的无偏估计。在整个调参过程中不应使用

如果模型在训练集上损失下降,但在验证集上损失上升,这是过拟合的典型标志。可以通过早停、正则化等方法缓解。

神经网络的权重不能简单地初始化为零或随机大数。不恰当的初始化会导致梯度消失或爆炸,阻碍训练。

  • Xavier初始化 是一种常用方法,它根据权重矩阵的输入和输出维度来调整初始化的范围,旨在保持各层激活值和梯度的方差稳定。

随机梯度下降是基础优化器。更先进的优化器如 Adam 能带来更稳定、更快的收敛。

  • Adam结合了动量(平滑梯度更新方向,减少振荡)和自适应学习率(为每个参数计算不同的学习率,基于梯度平方的移动平均)。
  • 虽然Adam有更多超参数(如 β1, β2, ε),但通常只需重点调整学习率

学习率对训练至关重要。常见的策略是使用学习率调度,例如余弦退火调度:训练初期使用较高学习率进行探索,后期逐渐降低以精细调整。

  • 热身:在调度开始前,先使用一个很小的学习率训练少量步数,让优化器(如Adam)的移动平均统计量稳定下来,然后再升至初始学习率。

为提高计算效率,我们通常将多个样本组合成一个批次进行并行处理。

  • 对于序列数据,一个批次的形状通常是 [batch_size, sequence_length]
  • 由于序列长度可能不同,需要对短序列进行填充,并使用掩码在计算损失时忽略填充部分。

本节课中我们一起学习了语言建模的基础知识。

  • 我们首先理解了语言模型是一个为序列定义概率分布的系统,可用于评分和生成。
  • 我们学习了如何通过自回归链式法则将序列建模问题分解为下一个词预测问题。
  • 我们从最简单的Bigram模型入手,理解了基于计数的训练、极大似然估计的原理,以及生成评估(困惑度)的基本方法。
  • 接着,我们探讨了N-gram模型的扩展及其面临的数据稀疏和平滑问题。
  • 然后,我们引入了神经语言模型,它使用神经网络来预测下一个词,通过交叉熵损失进行训练,并能更好地共享相似词的信息和学习复杂特征。
  • 最后,我们回顾了进行深度学习实验时的一些关键实践概念,包括数据集划分、参数初始化、优化器选择和学习率调度等。

这些基础概念为后续学习更强大的循环神经网络和Transformer语言模型奠定了坚实的基础。

在本节课中,我们将学习序列建模,特别是语言建模。我们将重点介绍循环神经网络和循环语言模型。与上节课讨论的具有短上下文限制的N-gram模型和前馈神经网络语言模型不同,循环神经网络至少在理论上拥有无限的上下文窗口。我们将探讨其原理、训练方法,以及实践中遇到的梯度消失问题。此外,我们还将介绍更先进的循环架构、编码器-解码器模型,以及解决长序列信息压缩问题的关键技术——注意力机制。


上一节我们回顾了语言建模的基础。本节中,我们来看看循环神经网络。

循环神经网络是一种序列模型。它接收一个序列作为输入,并为序列中的每个标记生成一个隐藏向量表示。其核心思想是,在处理当前标记时,会结合前一个时间步的隐藏状态,从而在理论上能够考虑整个历史上下文。

  • h_t 是当前时间步的隐藏状态(一个向量)。
  • h_{t-1} 是前一个时间步的隐藏状态。
  • x_t 是当前时间步的输入(例如词嵌入)。
  • W_hW_x 是权重矩阵。
  • b 是偏置项。
  • f 是非线性激活函数(如tanh)。

模型在所有时间步共享同一套参数(W_h, W_x, b)。

循环神经网络可用于多种任务。

1. 序列分类
你可以将整个输入序列输入RNN,演化其隐藏状态,然后取最终的隐藏状态进行分类(例如,情感分析:正面、中性、负面)。



2. 语言建模
在语言建模任务中,RNN在每个时间步接收一个标记,并预测下一个标记。具体做法是,将当前时间步的隐藏状态 h_t 通过一个输出矩阵 W_o 映射到词汇表维度,然后应用Softmax函数得到下一个标记的概率分布。
o_t = softmax(W_o * h_t)











上一节介绍了RNN的结构和应用。本节中,我们来看看如何训练它。

训练RNN,特别是用于语言建模时,我们使用最大似然估计。在每个时间步,我们计算模型预测的下一个标记分布与真实下一个标记之间的损失(通常使用交叉熵损失)。然后将所有时间步的损失求和,得到总损失。

由于RNN的每个操作(线性变换、激活函数、损失计算)都是可微的,我们可以构建一个计算图,并直接使用反向传播算法来更新参数。因为参数在所有时间步共享,梯度会从各个时间步累积起来,共同更新这一套参数。这个过程有时被称为通过时间的反向传播

需要注意的是,由于隐藏状态 h_t 的计算依赖于 h_{t-1},依此类推,训练RNN在时间步之间存在顺序依赖,这使得其训练过程难以并行化。

在推理阶段(即使用训练好的模型生成文本),我们以自回归的方式进行:

  1. 从序列开始标记(如 )开始。
  2. 模型输出下一个标记的概率分布,我们从中采样得到一个标记。
  3. 将该标记作为输入,并将当前的隐藏状态传递到下一个时间步
  4. 重复步骤2和3,直到生成序列结束标记或达到所需长度。

RNN在生成时的一个优点是内存使用恒定,因为它只需要保留当前的隐藏状态向量。


虽然RNN理论上能处理长距离依赖,但标准的RNN在实践中会遇到梯度消失问题。

当通过时间反向传播时,梯度需要跨越多个时间步。由于每个时间步都涉及权重矩阵的连乘和激活函数(如tanh)导数的连乘(其值在0到1之间),如果这些连乘的结果小于1,梯度会随着时间步回溯而指数级衰减。这意味着模型难以学习到序列中远距离标记之间的依赖关系。相反,如果权重过大,则可能导致梯度爆炸

为了解决梯度消失问题,研究者提出了更先进的循环单元,其核心思想是引入门控机制残差连接

门控机制允许模型学习控制信息流。例如,一个“更新门”可以决定在多大程度上用新的候选状态更新隐藏状态,以及在多大程度上保留旧的隐藏状态。如果模型需要记住长期信息,它可以将更新门的值设得很低,从而几乎完全保留之前的隐藏状态,使得梯度更容易传播。

残差连接(或称加性连接)直接将前一隐藏状态加到新的候选状态上。这使得模型只需学习隐藏状态的“增量”变化,而不是整个新状态,也有助于梯度流动。

以下是两种广泛应用的门控循环单元:

GRU(门控循环单元)
GRU使用更新门和重置门来控制信息流。其更新公式更复杂,但本质是让模型学会何时更新、何时保留记忆。



LSTM(长短期记忆网络)
LSTM使用了输入门、遗忘门、输出门和细胞状态,结构更为复杂,但在处理长序列依赖方面非常有效。



在实践中,你可以直接使用PyTorch等框架中提供的 nn.GRUnn.LSTM 模块来替代基本的 nn.RNN


上一节我们解决了RNN的长程依赖问题。本节中,我们来看看如何处理输入-输出式的序列任务,例如机器翻译。

编码器-解码器模型专为条件生成任务设计,如机器翻译、文本摘要、对话生成等。

  • 编码器:通常是一个RNN,用于处理输入序列(如英文句子),并将其编码成一个上下文向量(通常是编码器最终的隐藏状态)。这个向量旨在概括整个输入序列的信息。
  • 解码器:通常是另一个RNN,以编码器产生的上下文向量为条件,自回归地生成输出序列(如日文句子)。上下文向量可用于初始化解码器的隐藏状态,或融入其每一步的计算中。

然而,将整个输入序列压缩成单个向量存在信息瓶颈问题,尤其是在处理长序列时。解码器在生成过程的每一步都只能访问这同一个、固定的上下文向量。

注意力机制 应运而生,它允许解码器在生成的每一步,动态地、有选择地关注输入序列的不同部分。

其核心思想如下:

  1. 编码器不再只输出一个最终向量,而是为输入序列的每个标记都输出一个隐藏状态(称为 )。
  2. 在解码器的每个时间步,我们有一个当前的隐藏状态(称为 查询)。
  3. 计算查询与所有相关性分数(可通过点积、双线性变换或简单的线性变换实现)。
  4. 对这些分数应用Softmax,得到一组注意力权重,其和为1。
  5. 计算的加权和(此时键与值通常相同),得到当前时间步专用的上下文向量

这样,在生成输出标记时,模型可以“注意”到输入序列中最相关的部分。例如,在将“European Economic Area”翻译成法语时,生成“European”时模型会高度关注输入中的“European”一词。

注意力机制显著提升了编码器-解码器模型在机器翻译等任务上的性能,并且其权重分布本身具有可解释性。


本节课中我们一起学习了序列建模的核心组件。

  • 我们介绍了循环神经网络,它是一种能够处理序列数据的强大模型,通过隐藏状态传递历史信息。
  • 我们探讨了标准RNN的梯度消失问题,并了解了GRULSTM等更先进的门控架构如何通过引入门控和残差连接来缓解此问题。
  • 我们学习了编码器-解码器框架,它专门用于输入到输出的序列生成任务。
  • 最后,我们深入探讨了注意力机制,它通过允许模型在生成时动态聚焦于输入序列的不同部分,极大地增强了对长序列和复杂依赖关系的建模能力。

这些概念,特别是注意力机制,为下一节课我们将要学习的Transformer模型奠定了重要基础。Transformer完全基于注意力机制构建,并进一步解决了RNN在训练时难以并行化的问题。

在本节课中,我们将学习Transformer架构,这是当前自然语言处理领域最先进的模型架构。我们将从注意力机制的基本概念开始,逐步深入到Transformer的核心组件,并了解其自提出以来的关键改进。


上一节我们介绍了序列模型,特别是循环神经网络。本节中,我们将探讨另一种强大的序列建模方法——基于注意力机制的Transformer。Transformer完全摒弃了循环结构,通过注意力机制并行处理整个序列,从而在性能和训练效率上取得了突破。


注意力机制的核心思想是:为序列中的每个词元生成一个向量表示,然后在生成下一个词元时,根据“注意力权重”对所有这些向量进行加权组合。

在上一节的编码器-解码器模型中,我们看到了交叉注意力的例子。解码器中的词元会“关注”编码器序列中的不同部分。

自注意力是Transformer的关键。在自注意力中,序列中的每个词元会关注同一序列中的所有其他词元(包括自身),从而捕捉词元之间的长距离依赖关系。

注意力机制涉及三个核心概念:

  • 查询向量:代表当前需要生成或处理的词元。
  • 键向量:代表序列中所有可供“查询”的词元。
  • 值向量:代表序列中所有可供“提取”信息的词元。

计算过程如下:

  1. 为每个查询-键对计算一个注意力分数。
  2. 对所有分数进行Softmax归一化,使其和为1,得到注意力权重。
  3. 使用这些权重对值向量进行加权求和,得到输出向量。

在Transformer中,使用的注意力分数计算方式是缩放点积注意力
score(q, k) = (q · k) / sqrt(d_k)
其中 d_k 是键向量的维度。除以 sqrt(d_k) 是为了防止点积结果随维度增大而过大,使训练更稳定。











Transformer于2017年在论文《Attention Is All You Need》中提出。它是一个完全基于注意力机制的序列到序列模型,在机器翻译等任务上超越了当时的循环模型。其核心优势在于易于在GPU上并行化,因为所有操作都基于矩阵乘法。

Transformer架构主要分为编码器-解码器型和仅解码器型。现代大语言模型通常采用仅解码器架构。其核心思想是设计一个精心构建的层(块),然后将其堆叠多次(如12层、50层)以构建深度模型。

以下是构建Transformer层的五个关键概念:

由于Transformer没有循环结构,它无法像RNN那样通过隐藏状态隐式地感知词元顺序。因此,必须显式地将位置信息注入模型。

在原始Transformer中,使用正弦位置编码。它为序列中的每个位置生成一个独特的向量,该向量由不同频率的正弦和余弦函数组合而成。其公式为:
PE(pos, 2i) = sin(pos / 10000^(2i/d_model))
PE(pos, 2i+1) = cos(pos / 10000^(2i/d_model))
其中 pos 是位置,i 是维度索引,d_model 是模型维度。

















如今更常见的做法是使用可学习的位置嵌入,即为每个可能的位置(直到一个最大长度)学习一个向量。然而,这种方法无法泛化到训练时未见过的更长序列。

这是Transformer层的核心操作。它将我们之前讨论的注意力机制以高效的矩阵形式实现。

给定整个序列的查询矩阵 Q、键矩阵 K 和值矩阵 V(均为序列长度 × 模型维度),注意力计算为:
Attention(Q, K, V) = softmax( (Q K^T) / sqrt(d_k) ) V
这里 Q K^T 一次性计算了所有位置对之间的注意力分数,得到一个方阵。经过Softmax和缩放后,与 V 相乘得到输出。










为了让模型能够同时关注来自不同位置的不同类型的信息,Transformer采用了多头注意力

其实现方式是:

  1. Q, K, V 通过线性层投影到较低维度。
  2. 沿特征维度将它们分割成多个“头”。
  3. 在每个头上独立进行缩放点积注意力计算。
  4. 将所有头的输出拼接起来,再通过一个线性层投影回原始维度。

这样,不同的头可以学习关注句子中不同的方面(如语法、语义、长距离依赖等)。

注意力掩码:对于语言建模等任务,在预测下一个词元时,模型不应“看到”未来的信息。因此,需要应用一个因果注意力掩码,将未来位置的注意力分数设置为负无穷(在Softmax后变为0)。

深度神经网络容易遇到梯度消失或爆炸的问题。Transformer通过以下技术缓解:

  • 层归一化:对单个样本所有特征维度的输出进行归一化(减去均值,除以标准差),然后使用可学习的缩放和偏置参数进行调整。这有助于稳定每层的输出范围。
    LayerNorm(x) = γ * (x - μ) / σ + β



  • 残差连接:将某一层的输入直接加到其输出上,即 输出 = 层函数(输入) + 输入。这为梯度提供了直接回传的路径,有助于训练非常深的网络。

在原始Transformer中,层归一化放在注意力层和FFN层之后。现代架构(如LLaMA)通常采用前置层归一化,即将层归一化放在子层(注意力、FFN)之前,这被证明能带来更好的优化稳定性。

在注意力层之后,Transformer会应用一个前馈网络。这是一个简单的两层全连接网络,通常包含一个非线性激活函数(如ReLU或GELU)。
FFN(x) = W_2 * Activation(W_1 * x + b_1) + b_2
它的作用是对注意力层的输出进行进一步的非线性变换和特征组合。











自原始Transformer提出以来,研究者们对其进行了多项改进,这些改进在现代大语言模型(如LLaMA)中广泛应用。

为了解决绝对位置编码外推性差的问题,RoPE 被提出。它的核心思想是:让词元嵌入向量的点积结果仅依赖于它们的相对位置,而不是绝对位置。

RoPE通过将位置信息以旋转矩阵的形式融入查询和键向量的计算中来实现这一点。其公式涉及复数旋转,最终确保 q_m^T k_n 的结果是 m-n(相对位置)的函数。RoPE具有良好的外推性,是LLaMA等模型采用的位置编码方法。

在标准多头注意力中,查询头、键头和值头的数量是相等的。分组查询注意力 减少了键头和值头的数量,让多个查询头共享同一组键和值。

例如,如果有8个查询头,可以只使用4个键/值头(每组2个查询头共享)。极端情况下,所有查询头共享同一组键和值,称为多查询注意力。这种方法能显著减少参数量和推理时的内存访问,从而加速推理。


本节课我们一起学习了Transformer架构。我们从注意力机制的基本原理出发,详细剖析了Transformer的五大核心组件:位置编码、缩放点积自注意力、多头注意力、残差连接与层归一化以及前馈网络。最后,我们探讨了RoPE、RMSNorm和分组查询注意力等关键改进,这些改进使得现代大语言模型更高效、更强大。Transformer奠定了当前大语言模型的基础,其思想将贯穿本课程的后续内容。

在本节课中,我们将学习预训练的核心概念。预训练是过去六七年中变得非常流行的一种范式,其基本思想是先在一个大型数据集上训练一个基础模型,然后将其适配到多种不同的下游任务中。我们将探讨预训练的目标、数据的重要性以及如何通过计算规模来理解模型性能。

上一节我们介绍了序列模型和Transformer,本节中我们来看看一种不同的模型构建范式:预训练。

预训练的基本思想是,首先在一个大规模数据集上训练一个单一的模型,我们称之为基础模型。然后,使用某种技术将这个基础模型适配到具体的任务上。例如,你可以将同一个基础模型适配到情感分析、翻译、对话或问题解决等任务。

以下是两种主要的适配方法:

  • 微调:收集与任务相关的少量数据(例如,日语到英语的翻译对),然后在这个数据上调整基础模型的参数,最终得到一个针对特定任务的新模型。
  • 提示:通过自然语言描述任务来引导模型,例如输入“将这句话翻译成英语:...”,模型会尝试从左到右生成翻译结果。这种方法无需改变模型参数。

预训练之所以有吸引力,主要基于以下几个原因:

  • 迁移学习:利用从预训练任务中学到的知识,帮助模型在新的任务上更快、更高效地学习。
  • 数据效率:对于目标任务,可能只需要更少的训练数据,甚至在某些提示场景下无需任何数据。
  • 性能提升:使用预训练模型可能达到仅用目标任务数据无法达到的更高性能。
  • 多功能性:一个模型可以服务于多种任务,非常方便,也可以作为研究的良好起点。

一个预训练模型(如BERT、GPT-3、LLaMA)的性能和特性主要由四个因素决定:

  1. 模型架构:底层使用的神经网络架构,如今主要是Transformer及其变体。
  2. 预训练目标:用于训练模型的目标函数。
  3. 数据:用于预训练的数据集。
  4. 超参数:训练过程中使用的具体设置,如学习率、训练时长等。

本节课我们将重点讨论预训练目标和数据。

预训练目标定义了模型在训练过程中要解决的具体问题。我们将介绍两种主要的目标。

掩码语言建模在2018年左右非常流行,以BERT模型为代表。其核心思想是随机掩盖输入序列中的一些词元,然后让模型预测这些被掩盖的词元。

具体操作如下:从训练语料中取一个序列,随机掩盖其中一定比例(例如15%)的词元。模型需要根据上下文来预测这些被掩盖的词元。有时,为了增加难度,也会用随机词元替换原词元,或者保持原词元不变但仍要求模型预测,这有助于模型学习判断词元是否合适。

掩码语言模型训练出的通常是编码器模型,主要用于后续的微调。例如,在情感分析任务中,可以在预训练好的BERT模型上添加一个额外的输出层(如一个将隐藏维度映射到3个情感类别的权重矩阵),然后使用交叉熵损失对整个模型进行微调。通常,会使用序列开头特殊的[CLS]标记的隐藏向量作为分类依据。

自回归语言建模是我们目前更常见的目标,用于训练如GPT、LLaMA等模型。其目标是让模型根据之前的所有词元,从左到右预测序列中的下一个词元。

这本质上是在最小化交叉熵损失,让模型学习对下一个词元进行分类。通过在大规模语料上训练模型完成此任务,模型不仅学会了预测下一个词,也隐式地建模了数据的分布。

自回归语言模型既可用于微调,也因其从左到右的生成特性而天然支持提示。

数据是预训练中至关重要的因素。更多的数据通常能带来更低的损失和更好的模型。数据的质量、数量和覆盖范围都极其关键。

目前,大规模预训练数据的主要来源是网络数据,例如公开的Common Crawl网络爬虫数据集。然而,原始的网络数据(HTML)包含大量噪声和无关内容,不能直接用于训练。构建高质量预训练数据集通常涉及三个主要步骤:

以下是数据处理的关键步骤:

  • 提取:从原始HTML中提取出干净的文本内容,同时需要特别注意保留特殊格式,如数学公式(LaTeX)或代码片段。
  • 过滤:应用各种过滤器来提升数据质量,例如语言过滤(只保留英文)、重复行过滤、基于分类器过滤特定领域内容(如数学、教育内容)。
  • 去重:在大规模数据集中,存在大量重复或近似重复的页面。使用如MinHash等方法进行去重至关重要,可以防止模型过度记忆重复内容并提升训练效率。

最终的数据集通常是多种来源数据的混合。例如,一个现代预训练数据集可能包含网络数据、代码数据(如GitHub)、维基百科、书籍以及特定领域的高质量数据(如数学解题数据)。

混合比例的选择会影响模型在不同任务上的表现。如果希望模型擅长编码,就需要提高代码数据的比例;如果希望模型擅长数学推理,就需要加入更多高质量的数学数据。通过精心设计的数据混合,可以训练出能力更均衡、更强大的模型。

在预训练中,我们常常需要考虑如何最有效地利用计算资源。一个有用的框架是从“计算量”的角度来思考。

这意味着,要增加计算投入,你可以选择训练一个更大的模型(增加N),或者用更多的数据训练一个现有规模的模型(增加D)。

研究发现,在“计算最优”的配置下(即对于给定的计算预算,模型大小和数据量达到**平衡),模型的损失(可以理解为“错误率”)与所使用的计算量之间存在可预测的幂律关系。

这意味着,只要按比例增加模型大小和训练数据,就能可预测地提升模型性能。这条规律被称为“缩放定律”。

缩放定律不仅有助于预测大规模训练的结果,还能指导超参数选择。例如,可以在小规模上运行大量实验,根据缩放定律预测出在大规模训练时应使用的**模型大小、训练词元数、批次大小或学习率,从而节省巨大的试错成本。

本节课我们一起学习了预训练的核心概念。我们首先了解了预训练的基本思想,即先在大规模数据上训练一个通用基础模型,再将其适配到各种任务。接着,我们探讨了两种主要的预训练目标:掩码语言建模和自回归语言建模。然后,我们深入分析了预训练数据的重要性,包括数据来源、处理流程(提取、过滤、去重)以及数据混合策略。最后,我们介绍了从计算规模角度理解预训练的框架,以及重要的缩放定律,它揭示了模型性能与计算资源投入之间的可预测关系。这些知识为我们理解和使用当今强大的语言模型奠定了基础。

在本节课中,我们将学习如何在不修改模型参数的情况下,通过操作模型的输出来获得不同的生成行为。我们将一个训练好的语言模型 M 视为一个固定的条件概率分布,并探讨如何从这个分布中获取高质量、符合要求的文本序列。

上一节我们介绍了模型 M 可以被视为一个条件概率分布。具体来说,给定输入 X 和已生成的前缀 Y_{ ,模型会输出词汇表中所有下一个词 Y_j 的概率分布。

公式P(Y_j | X, Y_1, ..., Y_{j-1})

现代语言模型通常是局部归一化的。这意味着我们逐词预测概率,并通过相乘得到整个序列的概率。因此,随着生成序列变长,其总概率永远不会增加(因为总是乘以一个小于1的数)。这也意味着模型的分数可能无法完全反映序列级别的约束。

模型的概率分布可以提供一个关于其预测的置信度的粗略感知。理想情况下,我们希望模型是校准的,即模型为某个答案分配50%的概率时,该答案正确的频率也应该是50%。然而,这取决于模型的训练方式。

此外,模型的分布几乎永远不会将所有概率质量分配给单个词元。对于任何任务,总会有一些不理想或错误的输出被分配非零的概率。

现在,我们有了这个概率分布模型,如何从中得到一个输出呢?我们知道在每个时间步,模型都会给出下一个词元的概率分布。我们希望逐词生成一个看起来“好”的输出。

一个合理的起点是尝试寻找模型认为最可能的输出,这被称为最大后验概率解码模式寻找解码

如果只寻找一个词元,这很简单:我们采用贪婪解码,即直接选择每个时间步概率最高的词元。公式Y_j = argmax P(Y_j | X, Y_{

然而,对于更长的序列,贪婪解码可能无法得到全局概率最高的序列。因为一个高概率的前缀之后,概率质量可能变得非常分散。

为了解决这个问题,我们引入束搜索。束搜索维护一个固定数量(束宽)的候选序列,在每一步扩展这些候选,然后根据序列概率(前缀所有词元概率的乘积)保留最好的几个。这有助于避免错过那些前缀概率稍低但整体概率很高的序列。

在Hugging Face中,可以通过设置 do_sample=False 并调整 num_beams 参数来实现贪婪解码(束宽为1)或束搜索。

然而,最高概率的输出并不总是**输出。

  1. 排序问题:在概率分布顶部的几个输出之间,可能没有明确的优劣之分,它们可能只是同一语义的不同表达方式。
  2. 长度偏差:由于局部归一化,模型天然倾向于生成更短的序列(概率乘积更少)。为了公平比较不同长度的序列,实践中会引入长度惩罚
  3. 重复问题:贪婪解码可能导致严重的重复。解决方法包括训练更好的模型,或在解码时对已生成的词元施加惩罚(如重复惩罚)。
  4. 非典型性:分布的模式(最可能序列)可能并不“典型”。例如,一个60%概率朝上的硬币,连续100次朝上是最可能的单一结果,但这并不典型。我们可能更希望得到能代表分布特征的输出。
  5. 束搜索的诅咒:经验表明,使用过大的束宽进行搜索,虽然能找到概率更高的序列,但下游任务性能反而可能下降。这表明我们真正需要的可能只是接近模式(而非精确模式)的输出。

鉴于上述问题,我们可以有意让束搜索变得更近似:

  • 多样化束搜索:在剪枝步骤中修改评分,避免选择过于相似的序列,以获得更多样化的输出。
  • 随机束搜索:在每一步,不是选择Top-K个词元,而是根据概率分布进行采样,从而探索分布顶部附近的更多可能性。

如果我们不需要分布的“顶峰”,而只是希望获得能代表分布特征的输出,那么采样是一个自然的选择。

最直接的采样方法是祖先采样:在每一步,直接从模型定义的条件概率分布中抽取下一个词元。

然而,模型的原始分布存在长尾问题:虽然每个低概率“坏”词元被单独抽中的几率很小,但成千上万个这样的词元加起来,抽中长尾区域的总体概率却相当高。我们需要方法来避免这些退化输出。

以下是几种常用的采样修正方法:

  1. Top-K 采样:每一步只从概率最高的K个候选词元中采样。
  2. Top-P(核)采样:每一步从累积概率达到阈值P的最小候选词元集合中采样。
  3. Epsilon 采样:排除所有概率低于阈值ε的词元。
  4. 温度调节:在Softmax之前应用温度参数 TT < 1 使分布更尖锐(确定性增强),T > 1 使分布更平坦(多样性增强)。公式P'(y) = softmax(logits / T)

这些方法在Hugging Face和许多API中都很常见。

另一种较新的方法是对比解码。它使用一个“专家”模型和一个“业余”模型,通过从专家模型的逻辑值中减去业余模型的逻辑值,形成一个新分布进行采样。这可以用来抑制业余模型容易犯的某些错误(如毒性、语言混杂等),从而控制生成内容。

有时我们需要生成符合特定格式或满足语义约束的文本。

例如,我们希望模型输出符合特定JSON Schema的有效JSON。我们可以将约束表示为有限状态自动机。在解码的每一步,只允许生成能引导至有效后续状态的词元。这样可以保证输出符合约束。

在实现时,可能会遇到分词边界问题:约束可能迫使模型以不自然的方式组合词元。词元修复技术可以回退并寻找以所需前缀开头的词元来解决这个问题。

在Hugging Face中,可以通过 guidance 库或使用 LogitsProcessor 来实现复杂的约束逻辑。

对于更模糊的语义约束(例如“不要提及爬山”),仅靠指令可能不够。我们有以下几种方法:

  1. 词表黑名单:简单地将特定词元的概率设为零。但无法处理同义词或相关表述。
  2. 序列级过滤:生成多个完整序列,然后过滤掉违反约束的。但效率较低。
  3. 未来判别器:训练一个小的辅助模型(未来判别器),预测在当前前缀下添加某个词元后,最终序列满足约束的可能性。然后将这个分数与原始模型概率相乘,指导下一步生成。这允许融入任何有标签数据的语义约束。
  4. 奖励增强解码:可以看作是未来判别器思想与奖励模型的结合。它使用一个训练好的奖励模型,并设法在解码时估计部分序列的预期最终奖励,从而在每一步偏向高奖励的生成方向。这种方法无需额外训练,就能接近RLHF的效果。

本节课我们一起学习了多种解码算法。我们主要关注的是在词元级别应用函数 F 来操纵下一个词元的分布 P(Y_j | ...)。这包括:

  • 优化类方法:如贪婪解码、束搜索,旨在寻找高概率输出。
  • 采样类方法:如Top-K、Top-P、温度采样,旨在获得能代表分布特征的输出。
  • 约束融入方法:如自动机约束、未来判别器,旨在满足格式或语义要求。

这些方法在输出质量、多样性、推理速度约束满足度之间存在权衡。选择哪种方法取决于你的具体任务优先级。

需要强调的是,模型训练完成后,解码策略的选择同样至关重要。库提供的“默认”参数可能针对特定任务设定,也可能未经仔细调优。直接使用默认值可能会损失显著的性能。

最后,我们简要提到了两个未深入讨论的方向:

  1. 序列级解码:在完整或部分序列上操作的函数 G,例如重排序、选择等。任何词元级方法产生的序列都可以作为这些序列级方法的输入。
  2. 加速解码:例如推测解码等技术,旨在不牺牲质量的前提下提升生成速度。

理解并合理运用解码算法,是释放预训练语言模型潜力的关键一步。

在本节课中,我们将要学习提示(Prompting)与上下文学习(In-Context Learning)的基本概念、策略和实用技巧。这些技术是引导大型语言模型执行特定任务的核心方法。

提示是一种通过提供文本指令来引导模型(无论是预训练模型还是微调模型)做出特定决策的方法。从概率模型的角度看,提示是条件输入 x 的一部分,模型基于此生成输出 y,即 P(y|x)

上一节我们介绍了提示的基本概念,本节中我们来看看如何具体使用提示来完成输入输出任务。

一种实用的方法是通过模板来格式化提示,然后让模型生成答案,最后对输出进行后处理。这样可以将一个通用的文本补全模型转化为执行特定输入输出任务的工具。

以下是使用模板的一个例子:

# 模板示例 template = "输入:{input_text} 情感分析:" # 将用户输入“I love this movie”填入模板 prompt = template.format(input_text="I love this movie") # 将prompt送入模型,期望得到如“积极”的输出 

现代模型通常被微调为聊天助手,使用特定的消息格式(如JSON)进行交互。其中,“系统提示”(System Prompt)用于设定对话的整体行为准则,它在对话开始时提供,并影响整个会话。

一个典型的聊天格式示例如下:

{ "messages": [ {"role": "system", "content": "你是一个乐于助人的AI助手。"}, {"role": "user", "content": "请将以下电影评论分类为积极或消极:I love this movie"} ] } 

在底层,这些JSON结构会被转换为特殊的令牌序列供模型处理。

模型生成文本后,通常需要进行后处理以适配具体任务。解码算法(如贪婪解码、采样、束搜索)负责生成文本。

以下是常见的后处理策略:

  • 直接使用:将模型输出作为最终结果。
  • 格式化输出:例如,模型输出Markdown或代码,可进行相应渲染。
  • 输出选择:从生成的文本中提取关键信息(如特定单词、数字、代码块)。
  • 输出映射:将模型输出映射到预定义的类别或进行等价性转换(例如,使用符号计算系统检查数学表达式的等价性)。

上下文学习,也称为少样本提示(Few-Shot Prompting),是指在提示中提供少量输入输出示例,以指导模型执行新任务。这种方法可能让模型在上下文中“学习”新的任务模式。

以下是上下文学习的一些现象和分析:

  • 任务识别与偏置:在某些任务(如数学)上,仅提供输入问题(不提供解决方案示例)可能比同时提供输入输出示例效果更好。这可能是因为模型通过输入识别了任务,而提供的输出示例反而可能将解决方案偏置到与示例相似的路径上。
  • 标签重映射:在情感分析等任务中,如果在上下文示例中翻转标签(例如,将“积极”标为“消极”),模型在少量示例下性能较差(可能仍在检索原有任务知识),但在提供大量示例后,性能可能提升,显示出一定的上下文学习能力。
  • 示例数量与顺序:提供过多示例可能导致性能下降,类似于“过拟合”。此外,示例的顺序也可能对模型性能产生显著影响,不同排序会导致性能波动。

设计有效的提示既有手动方法,也有自动优化技术。

手动设计提示时,需确保格式与模型训练时使用的格式一致,细微差别(如空格)可能极大影响性能。指令应清晰、精确,明确说明任务、输出格式和约束条件。

自动提示工程旨在通过算法搜索最优提示。

  • 离散空间搜索:使用语言模型本身生成候选提示,然后在下游任务上评估这些提示的性能,进行迭代优化。
  • 连续空间搜索(提示微调):这是一种参数高效的微调方法。核心思想是冻结预训练模型的所有参数,仅为每个任务学习一个连续的“提示嵌入”向量。这个向量在输入前预先添加到模型,通过梯度下降优化,使其能引导模型在特定任务上表现更好。相关方法还有前缀微调(Prefix Tuning)。

基于对任务结构的理解,研究者开发了一些有效的提示模式。

思维链(Chain-of-Thought)提示要求模型在给出最终答案前,先输出中间推理步骤。这不仅使推理过程更透明,而且实质上为模型提供了“自适应计算时间”,允许其通过多步生成来解决复杂问题,从而提高了在数学、推理等任务上的性能。后来发现,即使不提供示例,仅使用“让我们逐步思考”这样的零样本指令也能激发模型的思维链推理。

这种模式引导模型将推理过程编写成可执行代码(如Python)。然后,外部代码解释器执行该代码并返回结果。这使得模型可以将复杂的计算(如算术、符号操作)委托给外部工具,自身专注于高级规划和逻辑。

可以将复杂的任务分解为多个子步骤,每个步骤由一个独立的模型调用或外部函数调用(如搜索引擎、代码解释器)完成。这种“提示链”可以视为一种程序,其中语言模型调用是函数。框架如LangChain和DSP支持构建和优化这类语言模型程序。

本节课我们一起学习了提示与上下文学习的核心内容。我们介绍了提示的基础,包括使用模板、系统提示和输出后处理。我们探讨了上下文学习的机制和现象。我们还讲解了手动与自动的提示工程策略,并分析了几种强大的提示模式,如思维链、程序辅助模型和提示链。掌握这些技术对于有效利用大型语言模型至关重要。

在本节课中,我们将要学习微调。微调是使预训练模型适应特定任务的关键技术。我们将探讨其基本概念、具体方法、相关技巧以及如何高效地执行微调。


上一节我们回顾了预训练和提示两种模型适应范式。本节中,我们来看看另一种核心范式:微调。

微调的基本思想是,我们从一个预训练好的基础模型开始,然后使用特定任务相关的数据,通过优化一个损失函数来调整模型参数,使其专门化于该任务。

在标准语言模型微调中,我们从一个预训练模型 P_θ(y|x) 开始。我们收集一个包含输入 x 和输出 y 的数据集。然后,我们优化以下损失函数:

L(θ) = - Σ log P_θ(y|x)

这个目标函数被称为最大似然估计,其对应的损失函数是交叉熵损失。这与我们在预训练中使用的下一个词元预测目标本质上是相同的,只是现在我们是在特定任务的数据集上应用它。

虽然我们主要讨论语言模型,但微调的思想并不局限于语言模型。例如,你也可以微调像BERT这样的掩码语言模型,或者图像分类模型。

以下是不同任务微调数据的例子:

  • 文本摘要:输入 x 是一篇完整的论文,输出 y 是其摘要。
  • 数学解题:输入 x 是一个数学问题描述,输出 y 是解题步骤。
  • 指令跟随:输入 x 是一个指令(例如“将以下句子翻译成英文”),输出 y 是对应的响应。

这些数据集可以从网上获取,用于训练模型执行特定任务。

以下是一个使用Hugging Face库进行简单微调的代码框架核心部分:

# 假设 model 和 tokenizer 已加载 # 假设 train_dataloader 已准备 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/67431f0f3726bc2694d1bd200dbf1b83_60.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/67431f0f3726bc2694d1bd200dbf1b83_62.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/67431f0f3726bc2694d1bd200dbf1b83_64.png) for epoch in range(num_epochs): for batch in train_dataloader: inputs = batch[‘input_ids’].to(device) attention_mask = batch[‘attention_mask’].to(device) labels = batch[‘labels’].to(device) outputs = model(input_ids=inputs, attention_mask=attention_mask, labels=labels) loss = outputs.loss loss.backward() optimizer.step() optimizer.zero_grad() 

这个训练循环遍历数据集,计算损失(Hugging Face模型通常内置了标签移位处理),并进行反向传播以更新模型参数。


了解了微调的基本操作后,我们来看看它的效果和一些重要影响。

微调使用预训练模型的主要好处之一是数据效率更高。与从零开始训练相比,基于预训练模型进行微调通常能用少得多的任务数据达到相同甚至更好的性能。

从概率分布的角度看,微调可以理解为将模型从广泛的预训练数据分布,“窄化”到特定任务的数据分布上。这通过最小化模型分布与微调数据分布之间的KL散度来实现。

这种“窄化”带来了一些影响:

  1. 任务专精化:微调后的模型在目标任务上表现更好,但可能在其他任务上表现下降。
  2. 格式依赖性:如果微调数据有特定格式(如特殊的起始/结束标记),模型会学习这种格式。在推理时改变格式可能导致性能下降。
  3. 可能遗忘少样本学习能力:专注于特定数据可能会削弱预训练模型原有的通过少量示例学习新任务的能力。

上一节我们看到了针对单一任务的微调。本节中,我们来看看一个强大的技巧:指令微调。它旨在训练一个模型来执行多种任务。

指令微调的核心洞察是,许多NLP任务都可以被构造成同一种格式:一个描述任务的指令,加上任务的输入,模型需要生成对应的输出

例如:

  • 指令:“将以下日语句子翻译成英语。”
  • 输入:“こんにちは、世界。”
  • 输出:“Hello, world.”

通过收集一个包含多种任务(翻译、摘要、问答、代码生成等)的大型数据集,并将每个样本都格式化为(指令,输入,输出)的形式,然后在这个混合数据集上微调模型,我们就可以得到一个能够遵循指令、处理多种任务的通用模型。

指令数据可以通过多种方式获得:

  1. 人工编写模板:研究人员为现有任务数据集(如GLUE、SuperGLUE)手动编写多种指令模板。
  2. 人工创作:通过众包平台,让人工标注者直接创作多样化的指令和回应。
  3. 模型自生成:使用一个强大的语言模型(如GPT-4),基于少量种子示例,自动生成大量的指令-回应对。这种方法可以低成本地扩展数据规模,但需要注意生成数据的质量和正确性。

指令微调的关键发现是,模型不仅能在训练见过的任务上表现良好,还展现出对未见过的任务的泛化能力。


指令微调让我们得到了通用的指令跟随模型。但在构建像ChatGPT这样的聊天助手时,微调过程还有一些特殊考虑。

聊天模型的微调数据格式与单轮指令有所不同,它通常包含:

  1. 系统提示:定义助手的角色、行为准则或可用工具(例如:“你是一个乐于助人且无害的助手。”)。
  2. 多轮对话:数据是用户和助手之间交替的多轮对话历史。
  3. 更自然的交互:指令和输入通常融合在更自然的对话上下文中,而非严格的“指令+输入”格式。

因此,构建高质量的聊天模型需要专门收集或构造包含系统提示和多轮对话的数据集。例如,通过公开部署模型并征得用户同意来收集真实对话,或者人工模拟多轮对话。


我们讨论了用人类标注或模型生成的数据来微调模型。当数据来自另一个(通常更大、更强的)模型时,这个过程与知识蒸馏密切相关。

知识蒸馏是一种模型压缩和提升技术,其核心是让一个“学生”模型去学习一个“教师”模型的输出行为。

有两种主要方式:

  1. 词元级蒸馏:学生模型被训练去匹配教师模型在每个预测步骤输出的完整词元概率分布。这使用了教师模型的“软标签”所包含的丰富信息。
  2. 序列级蒸馏:直接用教师模型生成完整的输出序列(例如,对一系列提示生成回答),然后用这些(提示,回答)对来微调学生模型。这正是我们在“模型自生成指令数据”场景中做的事情。

序列级蒸馏虽然只使用了“硬标签”(生成的文本序列),但理论上也在最小化学生模型与教师模型输出分布之间的KL散度。如今,许多优秀的较小模型(名称中常带有“distill”字样)都是通过从像GPT-4这样的大模型中蒸馏知识训练而来的。


微调大型模型(如数百亿参数)需要巨大的计算资源和内存。为了降低门槛,研究人员开发了高效微调技术。

全参数微调需要存储模型参数、梯度、优化器状态(如Adam的动量和方差),这对GPU内存是巨大挑战。例如,一个650亿参数的模型,仅FP16参数就需130GB内存。

一种流行的解决方案是 LoRA。其核心思想是:不更新原始模型庞大的参数,而是注入并训练一系列小得多的“适配器”参数

具体来说,对于模型中的某个权重矩阵 W,LoRA冻结原始的 W,并引入两个低秩矩阵 AB。前向传播时,计算变为:
h = Wx + BAx
其中,BA 就是需要训练的、参数量很少的适配器。训练完成后,可以将 BA 加到 W 上,合并成一个模型,无需在推理时保留额外结构。










更进一步的 QLoRA 技术,还将原始模型权重量化到更低精度(如4比特),以进一步节省内存,同时保持适配器在高精度下训练,使得在单张消费级GPU上微调超大模型成为可能。


本节课中我们一起学习了:

  1. 微调基础:通过任务数据优化预训练模型,使其适应特定任务,核心是最大似然估计/交叉熵损失。
  2. 指令微调:一种将多种任务格式化为(指令,输入,输出)并进行混合微调的技巧,能产生通用的指令跟随模型。
  3. 聊天模型微调:在指令微调基础上,需处理系统提示和多轮对话等特定格式。
  4. 知识蒸馏:利用教师模型(如大模型或人类)生成数据来训练学生模型,是一种强大的模型压缩和性能提升方法。
  5. 高效微调:以LoRA为代表的技术,通过仅更新少量适配器参数,大幅降低了微调大型模型的计算和内存需求。

微调是将强大的预训练语言模型应用于实际问题的关键桥梁,理解其原理和方法对于从事现代NLP研究和应用至关重要。

在本节课中,我们将学习强化学习在高级自然语言处理和语言模型中的应用。我们将探讨强化学习的基本概念、核心算法,以及如何将其应用于优化语言模型,使其更好地遵循特定任务标准。


传统的语言模型训练通常使用最大似然估计方法。然而,这种方法存在一些局限性,例如任务目标可能不完全对齐、数据不匹配以及暴露偏差问题。强化学习提供了一种替代方案,它通过定义奖励函数来直接优化我们关心的任务目标,并让模型通过与环境交互来生成数据,从而更好地模拟测试时的行为。

上一节我们回顾了传统微调方法的局限性,本节中我们将深入探讨强化学习如何解决这些问题。


在强化学习中,奖励函数用于评估模型生成输出的质量。我们可以将奖励函数大致分为两类:基于规则的奖励和基于模型的奖励。

基于规则的奖励是指那些可以通过编写程序自动验证输出属性的奖励函数。例如:

  • 数学问题解答:检查最终答案是否正确。
  • 代码生成:运行测试用例,检查通过的比例。
  • 诗歌创作:检查生成的诗句是否恰好为五行。

以下是基于规则的奖励的一些潜在缺点:

  • 规则过于具体:奖励函数可能只适用于特定情况或输出格式。
  • 模型可能“欺骗”奖励:模型可能只优化被测量的特定属性,而忽略其他重要方面(例如,只生成五行文本,而不考虑其是否为诗)。

基于模型的奖励使用一个训练好的模型(通常是语言模型)来评估输出。主要有两种类型:

  1. 直接评估模型:模型被微调以接收提示和输出,并直接给出一个标量分数,用于衡量输出的某个属性(例如,帮助性、安全性)。例如,可以训练一个分类器来预测输出是否“安全”。
  2. 偏好模型:这是目前非常流行的方法。模型接收一对输出,并学习判断哪个输出更受偏好。训练数据包含人类标注的偏好对(y+ 优于 y-)。常用的损失函数鼓励模型为偏好输出分配比非偏好输出更高的分数:

    loss = -log(sigmoid(R(y+) - R(y-)))

    这个损失函数鼓励模型在偏好输出和非偏好输出的得分之间产生尽可能大的差距。


在介绍了奖励函数之后,我们来看看如何优化它们。这就是强化学习的核心。

强化学习通常在一个称为马尔可夫决策过程的框架中进行。它包含以下几个要素:

  • 状态:当前的情境(例如,游戏画面、已生成的文本)。
  • 动作:模型可以采取的操作(例如,移动方向、生成下一个词)。
  • 策略:根据状态选择动作的模型(在NLP中就是语言模型)。
  • 环境:接收当前状态和动作,并返回新状态和奖励的系统。
  • 奖励:环境根据动作结果给出的标量反馈。

在语言生成的背景下,我们可以这样映射:

  • 状态:提示 x 加上已生成的令牌。
  • 动作:生成下一个令牌。
  • 策略:语言模型 π_θ
  • 环境:简单地将生成的令牌追加到当前状态。
  • 奖励:通常在完整序列生成后,由奖励函数 R 给出。

目标就是学习一个策略 π_θ,以最大化期望累积奖励。


策略梯度是强化学习的基础算法。其目标是优化策略参数 θ,以最大化期望奖励 J(θ) = E[R(y)]

直观理解是:根据生成输出 y 所获得的奖励 R(y) 来调整其对数概率 log π_θ(y|x)。高奖励的输出概率会被提高,低奖励的则被降低。

在实践中,我们通过从策略中采样输出 ŷ 来近似这个期望,并使用以下损失函数进行梯度下降:
loss = -log π_θ(ŷ|x) * R(ŷ)



与最大似然估计相比,策略梯度有两个关键区别:

  1. 数据(输出)是由当前策略生成的,而非固定的数据集。
  2. 使用奖励函数对对数概率进行重新加权,而不仅仅是最大化概率。

基本的策略梯度算法在实践中可能不稳定。以下是几种常用的改进技术:

为了防止模型过度优化奖励而偏离合理的语言分布(即“奖励黑客”),我们可以在目标中添加一个KL散度惩罚项,鼓励新策略 π_θ 接近一个参考策略 π_ref(例如,初始的预训练模型)。
目标:最大化 E[R(y)] - β * KL(π_θ || π_ref)
这可以作为一个额外的奖励项或独立的损失项加入。










原始的策略梯度使用奖励 R 作为权重。我们可以用优势函数 A = R - b 替代,其中 b基线。基线 b 是对给定状态下期望奖励的估计。

  • 作用:优势函数衡量的是动作相对于平均水平的“好坏”程度。它减少了梯度估计的方差,使训练更稳定。
  • 基线估计方法
    • 同一提示下多个输出的平均奖励(用于GRPO算法)。
    • 训练期间奖励的滑动平均
    • 训练一个价值函数模型来预测给定状态的期望奖励。

结合以上技术,一个典型的现代RL训练循环包括:用当前策略生成输出,计算包含KL惩罚和基线的优势函数,然后使用PPO损失更新策略。


掌握了奖励函数和优化算法后,我们来看两个具体的应用实例。

RLHF是训练对齐人类偏好的语言模型的经典流程,包含三个步骤:

  1. 收集人类偏好数据:给定提示,让人类标注员对不同模型输出的好坏进行排序。
  2. 训练奖励模型:使用偏好数据训练一个模型,使其能为输出打分,偏好输出得分更高。
  3. 使用PPO优化策略:以第一步的预训练模型为初始策略,使用第二步的奖励模型作为 R,并加入KL散度惩罚,运行PPO算法优化语言模型。

DeepSeek的R1模型使用强化学习来提升数学问题解决能力:

  • 设置:将整个“思维链+最终答案”的生成视为一个动作。
  • 奖励:一个简单的基于规则的奖励——最终答案是否正确。
  • 算法:使用PPO进行优化,并采用组相对策略优化(即使用同一问题下多个生成输出的平均奖励作为基线)。
  • 效果:模型学会了更有效地利用思维链进行推理,并在数学基准测试上取得了显著的性能提升。

本节课我们一起学习了强化学习在高级NLP中的应用。我们从最大似然估计的局限性出发,引入了强化学习框架。我们探讨了两种主要的奖励函数:基于规则和基于模型(特别是偏好模型)。然后,我们深入学习了策略梯度这一核心算法,并介绍了KL散度惩罚、优势函数、基线和PPO等关键技术来稳定训练。最后,我们通过RLHF和数学推理两个实例,看到了这些技术如何在实际的先进系统中应用。强化学习为直接优化复杂、序列级的任务目标提供了强大的工具,特别是在模型需要与环境交互或对齐人类偏好的场景中。

在本节课中,我们将要学习基准测试与评估在自然语言处理中的重要性。我们将探讨为什么需要基准测试、优秀基准测试应具备的特性,并介绍一些广泛使用的基准测试及其对应的评估指标。

基准测试的主要目的是追踪模型性能的进展,并以此比较不同模型的表现。例如,我们有两个模型A和B,需要判断哪个模型更好。由于当今的模型是通用系统,能够流畅地生成任意文本,因此这个问题可能非常困难。

基准测试可以服务于这个目的,展示你的模型相对于其他模型的表现如何。例如,在DeepSeek R1的论文中,他们将自己的模型与当时被认为最先进的OpenAI o1模型在六个基准测试上进行比较,以说明其模型性能与o1相当或略优。

同样,在GPT-4的技术报告中,他们也在多个基准测试上比较了GPT-4与GPT-3.5等先前最先进模型的表现,以展示新模型的进步。

除了比较模型,基准测试还可以用来衡量语言模型改进的速度。例如,通过观察闭源模型与开源模型在MMLU等基准测试上的性能差距随时间变化,我们可以看到开源模型正在变得越来越好。

因此,构建好的基准测试非常重要,因为它能推动进步,并告诉我们整体上做得如何。

在构建基准测试时,我们可能需要考虑多个方面。今天,我将分享我认为重要的五个不同方面:难度、多样性、实用性、可复现性和数据污染。我们将逐一探讨。

关于难度,我们可以从研究员Jason Wei分享的图表中学到两点。以MATH基准测试为例,在2021年初,该基准测试非常困难,最先进模型的性能仅为个位数百分比。但随着时间推移,最先进模型的性能迅速提升。到了2025年,人们认为MATH对于最先进的系统来说是一个相对简单的基准测试,因为**性能已超过90%。

使用这些基准测试告诉我们,拥有一个高难度的基准测试非常重要。因为如果基准测试太简单,它将无法区分两个系统。如果MATH在2021年是一个好的测试平台,那么如今它可能不再像以前那样有效。这也是人们不断提出越来越难的基准测试的原因之一。

关于多样性,在一篇名为“Mixed Eval”的论文中,他们使用嵌入来可视化不同问题的分布。在右侧图表中,你可以看到存在许多不同的领域,例如顶部的编程、数学、物理、化学等STEM内容,以及底部更主观的内容,如体育、音乐、娱乐、语法和语言、社交动态等。

我认为在构建基准测试时考虑多样性非常重要。原因是,假设我们只使用Winogrande,其分布偏向于右下角。在这种情况下,如果我们衡量性能,我们将只跟踪右下角领域的表现,这意味着它不够全面,无法反映全貌。首先,同时使用多个基准测试非常重要,这样我们才能评估不同领域的表现。其次,在单个基准测试中,拥有多样化的问题也很重要,因为我们不希望被少数问题所困。

我想讨论的第三点是实用性。我认为很多人忽略了这方面,但花点时间思考一下是很有益的。例如,这是MATH基准测试中的一个实例,它是一个数学文字问题,类似于奥林匹克风格的问题。长期以来,甚至直到现在,人们一直使用这个基准测试来评估语言模型的数学性能。但如果我们思考一下,我的问题是:为什么我们需要一个擅长解决这类数学文字问题的语言模型?

如果将其分为三点,可能有多种原因。第一,我认为如果一个模型在MATH数据集上表现良好,这可能意味着它可以作为更复杂任务的基础。我的意思是,数学是进行更高级事情(例如金融分析)的一项非常重要的基本技能。因此,如果一个模型在MATH数据集上得分很高,这可能意味着它也可能成为我们关心的、需要数学的任务的良好模型。

第二个例子是,这类数学问题可能直接对某些用户有用。例如,如果一个高中生向ChatGPT询问他的家庭作业,那么一个在MATH数据集上表现良好的模型对他或她来说可能非常有用。

最后,我认为对于研究人员来说,在MATH数据集上取得良好性能可以让我们解决更抽象的研究问题,例如“AI模型能推理吗?”定义推理的含义非常模糊,但使用我们可能都同意需要推理的不同数据集,我们可以回答这类问题。

因此,实用性意味着在这个基准测试上取得高分是否真的有意义,还是仅仅是一个数字?

这是另一个来自HumanEval数据集的例子,该基准测试衡量模型解决LeetCode风格编码问题的能力。同样,我们可以思考为什么语言模型首先需要解决这些LeetCode问题。第一个原因可能是,它可以作为更复杂任务的基础。例如,从2024年开始,人们对构建能够实现整个代码库的编码代理产生了浓厚兴趣。在这种情况下,在HumanEval数据集上表现良好可能意味着它也有可能解决更复杂的问题。

第二点是,它也可能对关心类似问题的目标用户有用。例如,假设我正在准备编码面试,然后我被这个归并排序问题难住了,在这种情况下,我可以请ChatGPT实现代码,然后我可以学习如何编码,因此它对现实世界的用户可能有用。

最后一点同样是,它可以作为解决研究问题的媒介,例如回答“AI模型能自我调试吗?”这类问题。

我想讨论的第一个方面是可复现性,这是人们经常忽略的一个方面,但我认为它非常重要。例如,这是一篇名为“量化语言模型对提示设计中虚假特征的敏感性”的论文中的图表。这张图表非常令人惊讶,因为通常当你要求模型解决问答问题时,你可能会考虑这种蓝色框格式,即提供一个段落,然后要求它输出答案。当然,你可以尝试不同的提示模板,令人惊讶的是,论文显示,如果你尝试不同的模板,性能可能会有很大差异。

这意味着,假设一篇论文的研究人员使用了其中一个提示,而你想复现分数,却无意中尝试了不同的提示(因为他们没有分享他们的提示),在这种情况下,如果你得到了低分,你将无法判断问题出在哪里。因此,这意味着在设计基准测试时,可复现性是一个重要的方面。

这是社区中很多人讨论过的另一个例子。有一个名为MMLU的基准测试,在这个基准测试中,你必须在四个选项中选择一个。实现这一点有多种方式。原始的MMLU论文实现如下:他们通过提供选项来提示语言模型,语言模型会输出A、B、C或D,然后你取逻辑值,看哪个逻辑值最高,然后将其用作预测。但在他们的实现中,假设这里有一个名为“siggote”的标记获得了最高值,在他们的实现中,他们丢弃了这个标记的逻辑值,只比较了A、B、C和D的逻辑值。因此,即使“siggote”获得了最高的逻辑值,它也不会被选为最终预测。

但还有另一篇来自斯坦福的论文或项目叫做HELM,它也测量了不同模型在MMLU上的分数。在他们的实现中,他们考虑了所有不同标记的逻辑概率。在这种情况下,标记“Zyiggote”将被选中,而不是D。因此,它将被标记为错误。

还有一个广泛使用的评估基准叫做来自Eusai的LM Eval Harness。与之前的两个项目或论文不同,在这个仓库中,他们不仅提供了选项,还提供了选项的文本描述,然后测量整个序列的逻辑概率,然后对每个选项的标记数量进行平均,并选择概率最高的选项。因此,在这种情况下,它也会与我之前展示的两个例子有所不同。

Hugging Face使用了这三种不同的实现,并测量了不同模型的性能,这非常令人担忧,因为你可以看到,根据你使用的实现方式,你可能会得到不同的分数和模型排名。如果你没有意识到这一点,并且没有考虑实现同一基准测试的其他方法,你最终只会使用一种实现方式,然后得到与原始论文不同的结果,并且无法判断问题出在哪里。

因此,在考虑基准测试的可复现性时,这类方面非常重要。

我想讨论的最后一点是数据污染,我认为这已成为一个非常关键的问题,尤其是在当今的系统中,因为模型是在互联网上的大量文本上进行训练的。

简单回顾一下,在你的指令微调或微调课程中,你可能已经了解到,语言模型在预训练期间接受了大量信息的训练,并在后训练期间接受了广泛任务的训练。这是2023年谷歌发布名为PaLM和Flan-T5模型时的一幅图表。在这篇论文中,当时非常令人惊讶,因为他们训练了将近1800个来自先前NLP论文的数据集,然后他们在像MMLU和BIG-Bench这样的保留挑战性任务上进行了测试。

在这种情况下,划分什么是测试集变得非常模糊,因为实际上许多实例可能在不同的基准测试中相互重叠。因此,即使你看到有一个保留任务,它实际上可能包含在你的训练数据中,也可能包含在你的预训练语料库中,这非常令人担忧。

令人担忧的原因是,我们希望通过语言模型检查的是其泛化到未见过的、新任务的能力。例如,很多论文希望检查他们的语言模型在未见任务上的性能,看它们是否能处理这些任务。

一个很好的例子是,有一篇名为GSM1K的论文。对于不了解的人来说,GSM8K是一个广泛使用的数据集,但一家名为Scale AI的数据标注公司收集了1000个看起来与GSM8K非常相似的问题,然后测量了不同模型的性能。你可以看到,在左侧图表中,这显示了与GSM8K和GSM1K相比的差异或delta值。你可以看到,许多模型的得分相对低于它们在GSM8K上的得分,这意味着GSM8K测试集可能已经包含在模型的训练数据中,导致只在GSM8K上获得高分。

他们还展示了这张图表,其中X轴是GSM8K上的性能,Y轴是GSM1K上的性能。由于数字或点位于y=x线以下,这意味着几乎所有模型在GSM1K上的得分都较低,即使它们具有相同的分布和相似的难度。

接下来,我想讨论一些广泛使用的基准测试及其对应的指标。我将首先讨论一些用于多项选择问答(即分类)的数据集,然后讨论一些用于评估语言生成的指标和基准测试。

对于多项选择问答,如果你回顾这张图表,我想提到的一个趋势是,基准测试往往会随着时间的推移而饱和。因此,即使最先进模型的得分在先前时间段很低,它也会在某个时刻被征服,然后该基准测试自然退役。这种趋势在不同的基准测试中都有观察到。

但我想在今天提到一些例子。一个例子是这个名为HellaSwag的基准测试。这是一个用于评估模型常识推理能力的基准测试。这是一个问题的示例:给定这段视频,他们生成了一个问题,其中一位女士在外面拿着一个桶和一只狗,狗跑来跑去试图躲避洗澡,然后我们必须找到完成先前上下文的句子。在这种情况下,答案将是C:她把狗弄湿了,然后它又跑开了。

在当时,最先进的模型是一个名为BERT的模型,我不确定你是否听说过它,在仅解码器语言模型之前,它曾被广泛使用。在当时,HellaSwag被认为是一个非常具有挑战性的基准测试,因为BERT模型的得分非常低。

我想在这里提到的一点是,在HellaSwag之前,同一作者有一个名为SWAG的数据集,它基本上测量的是相同的内容。在HellaSwag被引入时,SWAG也已经被最先进的模型征服了。因此,如果你看这里的右侧图表,你可以看到SWAG在不同问题类型上的得分非常高,因为它的范围大约在70到80左右。

他们收集了更具挑战性的问题,并扰乱了问题格式,最终使这个HellaSwag数据更具挑战性,因为你可以看到,与SWAG相比,最先进的性能变得更低了。

这是另一个例子,一个名为SuperGLUE的基准测试,这同样也是来自一个名为GLUE的基准测试的后续工作。正如你在标题中看到的,人们的名字是“一个用于通用语言理解系统的更粘性的基准测试”。

在这个SuperGLUE基准测试中,我认为包含了9或10个分类任务。这是一个问题的示例:给定这段文字,语言模型必须预测这个问题:A&W Root Beer是百事可乐的产品吗?答案将是否定的。同样,也有一个自然语言推理问题,即NLI问题,然后预测两个句子是否匹配,以及这类问题。

我想讨论的一点是,在SuperGLUE论文中,他们也有一个与我之前展示的非常相似的图表。这是不同最先进模型在他们先前名为GLUE的基准测试上的性能图表。你可以看到,随着时间的推移,这里的黑线是人类性能,而蓝线(即语言最先进模型的性能)趋于迅速增长。实际上,这里最先进的系统XLNet是在CMU训练的。为了克服这个问题,他们提出了一个名为SuperGLUE的新基准测试。

这是另一个名为MMLU的数据集。与之前的分类基准测试(如SuperGLUE或HellaSwag)相比,这个基准测试的不同之处在于,它需要不同领域的专业知识。因此,问题涵盖57个学科,包括专业和学术领域。这同样是一个计算加速度率的物理问题示例。

他们也有这张类似的图表,比较了GPT-3(在2020年被认为是最先进的)的性能,并显示在他们新的基准测试MMLU上,最先进模型的得分低于先前的不同基准测试。但是,MMLU也趋于被征服,因为当前模型的得分非常高。因此,最近出现了一个名为MMLU Pro的基准测试,在之前的MMLU基准测试中只有四个选项,但在MMLU Pro数据集中,他们将选项扩展到了4到10个选项之间。

对于这个物理问题,你可以看到他们有7个选项,你必须选择答案A。

你可以看到,总体趋势是,当新的基准测试出现时,它会与之前的基准测试进行比较,显示最先进模型在其基准测试上的性能较低。

总结一下,你可以看到,首先有SWAG和GLUE,然后被HellaSwag和SuperGLUE取代。接着出现了MMLU,最近又出现了MMLU Pro。一个非常难以解决的重要问题是,我们是否能像当前进展一样提出更具挑战性的基准测试。问题是,如果我回到之前的幻灯片,你可以看到这里的总体趋势是,新的挑战性基准测试往往比以前更快地被解决。因此,在MMLU上达到90分以上花了将近四五年时间。但是,例如,这里有一个名为GPQA的数据集,这是一个非常困难的博士水平多项选择问答问题。你可以看到,它只花了大约一年左右的时间就达到了非常高的分数。因此,模型正变得越来越迅速智能,以至于提出一个好的基准测试来测试它们变得更具挑战性。

因此,我们如何提出更具挑战性的基准测试以匹配改进的进展,这是一个开放性问题。

接下来,我想讨论语言生成评估。人们可能对评估生成能力感兴趣的一个动机是,作为人类,当我们使用ChatGPT时,我们不会给它不同的选项来选择,那将是一种非常不自然的交互方式。更自然的方式是,我们只是问一个问题,然后它生成一个自由形式的回答。然后我们可以判断回答的好坏,看它是否满足了我们想要它做的事情。

然而,这里的问题是,与多项选择问答基准测试相比,评估自由形式的回答本身就非常具有挑战性。因为对于多项选择问答,你只需要测量准确率,因此你可以判断出,例如,在100个问题中,模型答对了57个,然后你可以得出结论。但对于自由形式的回答,存在一个从非常好、中等好到非常差等等的频谱,自动标注这种回答的好坏注释非常困难。

因此,有一些基准测试相对容易评估。例如,我今天多次提到的GSM8K相对容易衡量,因为尽管模型必须生成一个长的思维链或其推理过程,但我们关心的是它是否得到了最终正确答案。因此,除非我们可以解析这个最终预测,否则我们可以使用精确匹配,然后判断模型是否正确解决了问题。当然,可能存在模型思维链逻辑错误但答案正确的情况,也有一些研究试图解决这个问题,但在实践中,很多人使用这个数据集时只检查最终答案的正确性。

这是另一个相对容易评估的例子。有一个名为HumanEval的数据集,用于测试Python编码任务,它包含LeetCode风格的编码问题。在这种情况下,判断代码是否正确非常容易,因为基准测试有一些测试用例,就像我们实际编写LeetCode代码一样,然后我们可以验证如果代码通过了所有测试用例,它就是正确的。在这种情况下,很多论文使用一个名为pass@k的指标,意思是当模型生成解决方案k次时,是否至少有一次正确。

然而,对于开放式问题,问题变得非常具有挑战性。假设我问ChatGPT:“在日本做生意时有哪些商务礼仪规范?”然后我得到了两个回答,一个来自ChatGPT,一个来自DeepSeek R1。总体而言,两者看起来都很长且非常正式,因为它具有结构化的输出,因此很难判断哪个回答更好,除非你非常仔细地观察。

因此,长期以来许多论文面临的问题是:我们如何自动评估此类回答的质量?作为一个工作示例,假设我有一个问题:“定期锻炼有什么好处?”然后我的模型生成了五个词:“锻炼改善情绪和健康”,然后我有一个参考答案:“定期锻炼有益于健康和情绪”。

评估此预测的传统方法是使用名为ROUGE的指标。使用ROUGE时,你计算预测和参考答案之间的单词重叠度。例如,当我们使用名为ROUGE-1的指标时,我们会计算预测和参考答案之间有多少单词重叠。在这种情况下,有四个单词重叠:锻炼、健康、和、情绪。你可以计算精确率:4除以5,因为预测有五个单词,其中四个重叠。然后你也可以计算召回率:参考答案的六个单词中有四个重叠。然后你可以取调和平均数并计算F1分数,并将ROUGE-1报告为F1分数。同样,你也可以计算ROUGE-2。

这是我们可以发现ROUGE指标不完美的地方,因为如果你看预测和参考答案,并寻找重叠的双词,你会发现完全没有重叠,因为参考答案中没有“锻炼改善”。因此,在这种情况下,你会得到精确率和召回率均为零,然后得到零F1分数。ROUGE-L查看最长公共子序列,然后你可以看到预测中有“锻炼情绪”,参考答案中也存在“锻炼情绪”,所以有一个重叠。另一个重叠是“锻炼和”,因为参考答案中也有“锻炼和”。因此,我们有两个公共子序列,然后如果我们计算,精确率将是2除以5,召回率将是2除以6,然后我们将得到0.364的F1分数。这就是我们使用ROUGE的方式,但正如我提到的,这并不理想,因为即使预测和参考答案非常相似,ROUGE指标可能无法充分捕捉预测是否足够好。

为了克服这个问题,人们引入了新的指标。是的,我举了一个我们调换“情绪”和“健康”顺序的例子。但这也是事实,如果你使用其他词表示“情绪”,它将无法捕捉那种关系。

为了克服这个问题,引入了一个名为BERTScore的新指标。BERTScore指标试图做的是测量预测和参考答案之间的单词相似度。我们要做的是,给定参考句子和预测或候选句子,我们将通过BERT模型获取每个单词的CLS标记或嵌入,然后计算单词之间的成对余弦相似度,然后我们可以得到这种矩阵,然后我们可以计算每行中的最高值,然后平均这些值,并乘以一个称为IDF权重的值。IDF权重是逆文档频率,这在信息检索中广泛使用,它考虑的是单词是否频繁出现,因此它有点像根据频率进行调整。这是可选的。然后,在此之后,我们可以获得一个单一的标量值分数,表示候选句子与参考答案的相似程度。

另一种方法是使用“LLM即评委”。这是当今很多人正在探索的一个广泛开放的研究领域,主要思想相对简单:提示一个语言模型来评估给定回答的质量。它可以是类似“提供3分(满分5分)”的评分,或者给定两个回答,判断哪个回答更好。

在本节课中,我们将学习如何设计实验、选择研究问题,并探讨与数据量选择、人工标注和评估相关的一系列实用话题。这些内容对于构建你的课程项目(如作业3和4)以及进行更广泛的NLP研究都至关重要。

上一节我们介绍了科学方法的基本框架。本节中,我们来看看如何迈出第一步:提出好的研究问题。

研究动机大致可分为两类:

  • 应用驱动型研究:旨在解决特定应用场景中的问题,或改进现有系统在特定应用上的表现。
  • 好奇心驱动型研究:源于对语言或模型本身的好奇心,旨在探索和理解某些现象。

以下是这两种研究的一些例子:

应用驱动型研究示例:

  • 情感分析:为读者提供文章的简洁情感摘要。
  • 对话式问答:旨在构建更称职的对话助手。
  • 自底向上抽象摘要:旨在改进神经网络摘要方法的内容选择能力。
  • 子词分词:旨在简化多语言模型的训练流程。

好奇心驱动型研究示例:

  • 真实新闻与讽刺/虚假新闻的语言差异:探索不同文本类型的语言特性。
  • 所有语言对语言建模来说都同样困难吗?:探究语言模型在不同语言上的表现差异。
  • BERT模型中特定语言学信息的编码位置:旨在理解和解释神经模型的工作原理。

在确定了研究问题后,下一步是调研该领域已有的工作。

以下是进行文献调研的基本方法:

  • 使用Google Scholar、Semantic Scholar等工具搜索相关论文。
  • 阅读论文摘要,快速判断其相关性。
  • 对于高度相关的论文,深入阅读细节。
  • 撰写论文总结,以巩固自己的理解并发现知识盲区。

在NLP领域,常见的论文来源包括:

  • 预印本平台:arXiv。
  • 顶级会议:NeurIPS, ICLR, ICML, CoLM, ACL, NAACL, EMNLP。
  • 期刊:TMLR。
  • 论文库:ACL Anthology。

调研时,可以采取“由点及面”的策略:从一篇核心论文出发,查看其引用的文献(向前追溯)和引用它的文献(向后追踪),快速构建相关知识网络。

重要提醒:文献调研虽重要,但也要避免过度投入而迟迟不开始自己的研究。有时,先动手尝试解决问题,再回头查阅文献,看看自己的思路是否新颖或已有解决方案,也是一种有效的策略。

在明确了研究问题和相关背景后,我们需要形成一个或多个具体的、可验证的假设。

一个清晰的假设通常是一个“是/否”问题,这有助于设计实验来验证它。例如,问题“方法X能否提升任务Y的性能?”对应的假设可能是“方法X能显著提升任务Y的性能”。

以下是一些论文中研究问题与假设的示例:

  • 论文《Are all languages equally hard to language model?》
    • 研究问题:所有语言对语言建模来说都同样困难吗?
    • 隐含假设:尽管存在粗略的跨语言可比性,但并非所有语言都同样容易建模,我们的方法也并非对所有语言都同样有效。
  • 论文《What makes a good podcast?》
    • 研究问题:是什么让一个播客具有广泛的吸引力?
    • 隐含假设:减少填充词和不流畅表达,或融入情感,可能提升听众参与度,但此前缺乏定量研究。
  • 论文《Contextualized Speech Translation》
    • 研究问题:上下文信息是否以及如何有益于端到端语音翻译?
    • 隐含假设:融入上下文信息将提升语音翻译的性能。

明确列出你的研究问题和假设,不仅能帮助你组织思路,还能为后续的实验设计和结果阐述提供清晰的框架。

现在,我们进入实验阶段。核心步骤是:获取能回答研究问题的数据,运行实验并计算指标,然后分析差异的显著性和影响。

数据获取通常有以下几种途径:

  • 沿用前人工作的数据:如果你的研究建立在现有工作之上,使用其公开的数据集和实验设置是一个良好的起点。
  • 复用其他任务的数据集:有时,你可以使用其他成熟任务的数据集来验证新方法的通用性。例如,DPO方法曾使用情感分析数据集进行评估。
  • 创建新数据:对于全新的问题,可能需要人工标注新数据。这虽然耗时,但若能公开,将对社区有重要贡献。

你可以通过Hugging Face Datasets、LREC等平台查找现有数据集。

如果需要人工标注数据,你需要考虑以下几点:

1. 需要多少数据?
对于测试集,你需要足够的数据以确保观察到的性能差异具有统计显著性。这可以通过功效分析来估算。例如,如果你想证明模型A比模型B的准确率高2%,功效分析可以告诉你在给定的显著性水平(如p<0.05)下,需要多少测试样本才能有足够的把握检测到这个差异。



一个简单的思想是:差异越小,需要的样本量越大;差异越大,所需的样本量可以越小。

2. 如何采样数据?
采样数据时应考虑覆盖的领域、任务、语言变体、标注者人口统计学特征等因素。遵循类似“Datasheets for Datasets”的规范,记录数据集的创建动机、组成、标注过程等信息,有助于提高研究的可重复性和伦理性。



3. 如何制定标注指南?
清晰、详细的标注指南至关重要。以宾州树库(Penn Treebank)的句法标注指南为例,它详细定义了词性标签并给出了疑难案例,这保证了标注的一致性。



4. 如何招募标注者?

  • 自行标注:适用于小规模或需要专业知识的项目。
  • 同事/同学协助:在学术环境中常见。
  • 众包平台:如Amazon Mechanical Turk(适用于通用任务)或特定领域的专业标注平台(如需要专家知识)。注意,涉及付费的人员研究通常需要获得机构审查委员会(IRB)的批准。

5. 如何评估标注质量?
可以通过计算标注者间一致性指标来衡量,例如:



  • 科恩卡帕系数:适用于两名标注者、分类任务的情况。其公式为:
    κ = (P_o - P_e) / (1 - P_e)
    其中,P_o是观察到的实际一致率,P_e是期望一致率(即随机情况下的一致率)。










  • 弗莱斯卡帕系数:科恩卡帕对多名标注者的推广。
  • 克里恩多夫阿尔法系数:更灵活,能处理多名标注者、序数数据、区间数据甚至缺失数据。

通常认为,κ > 0.8 表示几乎完美一致,0.6-0.8 表示高度一致,0.4-0.6 表示中度一致。

进行实验需要计算资源,你可以利用:

  • 课程提供的AWS积分。
  • Google Cloud的免费额度或研究资助。
  • Google Colab的免费GPU(适合小型实验)。
  • 所在研究机构的计算集群。
  • 个人拥有的硬件。

实验完成后,深入分析数据至关重要。这包括:

  • 定量分析:计算各项指标,进行显著性检验。
  • 定性分析:人工检查模型的错误案例,理解其失败模式。很多时候,仅仅仔细查看数据本身就能发现问题的关键。

最后一步是将你的发现清晰地呈现出来。撰写研究论文是一项需要练习的技能。

一个极好的参考资源是论文《Writing Papers》。它提供了从结构、叙事到细节的全面建议。对于需要撰写课程项目报告的同学来说,阅读这份资料并思考如何将其建议融入你的写作中,会非常有帮助。

请记住,优秀的写作源于不断的练习和获取反馈。

本节课我们一起学习了NLP研究中的实验设计与执行流程。我们从如何识别好的研究方向和问题出发,探讨了进行文献调研的方法,学习了如何将问题转化为可验证的假设。接着,我们深入讨论了实验阶段的核心环节:数据获取与标注、计算资源利用以及数据分析。最后,我们简要提及了报告研究成果的重要性。希望这些内容能帮助你更好地规划和开展你的NLP研究项目。

在本节课中,我们将要学习智能体的基本概念。智能体是当前快速发展的一个领域,我们将介绍其核心思想、常见环境以及构建智能体的基本模式。

智能体可以被定义为一个策略。它接收一个状态(或观察)作为输入,并输出一个动作(或动作的概率分布)。这个策略需要在某个环境中运行,并通过最大化奖励来达成目标。

公式:智能体 = 策略 π,其中 π: 状态 S → 动作 A。

上一节我们介绍了智能体的基本定义,本节中我们来看看智能体运行的具体环境。

智能体必须在特定的环境中运行。以下是几种常见的环境类型:

文本环境将观察和动作都表示为文本。一个早期的例子是“World of Bits”环境,它模拟了简单的网页交互任务。

以下是“World of Bits”环境中的关键组件:

  • 观察:可以是网页的截图(图像),也可以是网页的DOM结构(JSON文本)。
  • 动作:例如 move_mouse_to(x, y)click_element(ref_id)
  • 奖励:通常根据完成任务的速度或准确性来定义。

视觉环境为智能体提供图像作为观察。这使得智能体可以像人类一样“看到”界面。例如,“Visual Web Arena”环境模拟了真实的网站(如购物网站、论坛),智能体需要根据屏幕截图来执行点击、输入等操作。

这类环境旨在构建通用的计算机使用智能体。环境就是整个操作系统,智能体可以执行任何计算机操作,如打开文件、编辑代码、使用应用程序等。例如,“OS World”和基于VS Code构建的环境就属于此类。

上一节我们了解了智能体运行的各种环境,本节中我们来看看如何构建一个智能体。

构建智能体,尤其是基于大语言模型的智能体,有几个常见的模式。

通过精心设计的提示词,引导大语言模型扮演智能体的角色。提示通常包含三个部分:

代码示例(提示结构):

系统提示:你是一个软件工程智能体,擅长修复代码错误。 可用工具描述: - search_file(keyword): 在代码库中搜索包含关键词的文件。 - edit_file(path, line, new_content): 编辑指定文件的某一行。 当前观察:这里是当前的文件列表和错误信息。 任务:修复这个导致程序崩溃的bug。 

让模型在输出最终动作前,先输出其“思考”过程。这有助于模型进行更复杂的推理。同时,现代大语言模型API通常支持“工具调用”功能,可以明确定义智能体可用的动作。

代码示例(工具调用格式):

{ “type”: “function”, “function”: { “name”: “execute_bash”, “description”: “在终端执行bash命令”, “parameters”: { “type”: “object”, “properties”: { “command”: {“type”: “string”} } } } } 

对于视觉环境,一个挑战是让模型精确定位屏幕上的元素。“标记集”是一种有效技术,它在截图上的可交互元素周围添加带编号的方框。这样,智能体只需输出“点击标记12”,而无需估算复杂的像素坐标。

一些工作致力于训练通用的计算机使用智能体(如Anthropic的研究)。这类智能体拥有基础的动作集(如键盘输入、鼠标点击、执行命令、截图),理论上可以完成任何计算机任务。它们可以根据需要主动“截图”来获取当前的视觉状态。

在选择智能体方案时,需要考虑以下权衡:

以下是不同智能体方案的优缺点:

  • 文本智能体:在特定领域任务上可能非常高效和准确,但灵活性和通用性较差。
  • 视觉智能体:更接近人类的交互方式,能处理为视觉设计的界面,但处理图像成本更高,且精确定位仍是挑战。
  • 通用计算机智能体:灵活性最高,但当前在基础操作的可靠性、安全性以及成本方面面临更大挑战。

另一种思路是采用多智能体系统,让不同的专用智能体协同工作,由一个总控智能体进行调度,以平衡效率、成本和能力。

本节课中我们一起学习了智能体的核心概念。我们首先将智能体定义为一个在环境中运行、旨在最大化奖励的策略。接着,我们探讨了文本、视觉和计算机使用等不同类型的智能体环境。然后,我们介绍了构建智能体的几种基本模式,包括提示工程、思维链、工具调用、标记集技术以及通用智能体架构。最后,我们讨论了不同设计选择之间的权衡。智能体是一个充满活力且快速演进的研究领域,为构建能够理解和操作数字世界的系统开辟了新的可能性。

在本节课中,我们将学习量化技术,这是一种通过降低神经网络中数值的表示精度(例如,从16位降至8位或4位),来压缩模型大小、减少内存占用并提升推理速度的方法。我们将探讨量化如何使大型基础模型更易于访问,以及它在个人使用和工业部署中的不同考量。


上一节我们介绍了课程背景,本节中我们来看看神经网络的核心计算模式。理解这一点是进行高效优化的关键。

在AI中,95%的内存和计算都来自于矩阵乘法。因此,优化矩阵乘法是解决大多数效率问题的核心。

以下是几种常见的近似优化方法及其优劣:

  • 低秩近似:将权重矩阵投影到更小的维度。虽然能减少计算和内存,但通常会导致性能显著下降。
  • 稀疏化:移除接近零的权重。理论上能提升速度和减少内存,但在现代硬件上,稀疏算法的实现效率低下,通常反而更慢且需要更多内存。
  • 量化:将计算从16位精度降至8位或更低精度。这能使速度提升一倍,内存占用减半。关键在于如何在压缩的同时维持模型质量,这也是本课程的重点。

上一节我们了解了量化的优势,本节中我们来看看其基本原理和面临的核心挑战。

量化的目标是将高精度数值(如16位)压缩到低精度数值(如4位)。一个标准方法是先重新缩放数据,使其范围与目标数据类型(如4位整数的-7到7)匹配,然后投影到最接近的离散值上,以充分利用所有比特。

然而,量化面临一个主要挑战:异常值。神经网络中可能存在非常大的数值(异常值)。如果直接重新缩放整个分布,这些异常值会迫使缩放因子过大,导致分布中大多数有效数值被压缩到少数几个量化区间内,从而丢失大量信息。

这类似于直方图统计:如果存在一个远离主体的异常值,那么用于表示主体数据的“分箱”数量就会大大减少,信息损失严重。在深度学习中,这些异常值通常代表重要信息,不能简单地丢弃。


上一节我们讨论了异常值带来的问题,本节中我们来看看如何通过分析其结构来解决这个问题。

早期最好的量化方法在模型规模增大时会突然失效,性能降至随机水平。研究发现,根本原因在于异常值。当语言模型规模达到一定程度(约67亿参数)时,其计算模式会发生改变,出现高度结构化的异常值。

具体表现为:对于给定的模型,所有层、所有输入数据中的异常值都稳定地出现在同一个特征维度上。这种模式与语言建模性能相关,一旦出现,异常值的幅度会变得非常大。

基于这一发现,可以设计一个简单的算法:

  1. 识别出包含异常值的特征维度(通常只占总维度的约0.1%)。
  2. 将该维度对应的计算保留在16位精度下进行。
  3. 其余99.9%的计算则在8位精度下进行。
  4. 最后将两部分结果相加。

这样,我们就能在99.9%的计算使用8位的情况下,恢复出完整的16位矩阵乘法效果,从而实现高效且高质量的8位量化推理。


上一节我们实现了高效的8位量化,本节中我们探讨一个更深入的问题:如何最大化每比特的性能密度

对于内存受限的设备,目标不是无限制地压缩,而是在给定比特预算下获得**性能。研究发现,4位量化通常能提供**的性能密度权衡。

将模型量化为4位时,虽然性能相较16位略有损失,但由于内存占用减半,其“性能/比特”密度仍然高于8位。而进一步降至3位时,性能会出现显著退化,不再具有竞争力。这一规律在众多模型中得到验证。

提升4位量化质量的一个关键参数是分块大小。通过将权重向量分成小块,并对每个块独立进行量化(包括独立的缩放因子),可以有效地隔离块内的异常值影响。研究表明,分块大小在64左右时效果较优。现代硬件(如英伟达Blackwell GPU)已开始原生支持这种分块量化,使其在保持高速的同时精度更高。

然而,量化精度似乎存在理论极限。对于训练而言,分析表明,在当前的数据规模下,6位量化可能已无优势。这意味着,如果计算速度的提升依赖于精度降低,而我们已接近精度下限,那么未来通过降低精度来获取更大算力增益的空间将非常有限,这可能会影响模型规模进一步扩大的速度。


上一节我们讨论了推理时的量化,本节中我们来看看如何将量化应用于模型的微调过程。

微调大型模型面临的主要挑战同样是内存需求。直接对4位量化的模型进行微调并更新4位权重会导致优化噪声过大,性能下降。

解决方案是结合低秩适应 技术:

  1. 将预训练模型量化为4位并冻结其权重。
  2. 在模型中添加少量的、可训练的适配器层,这些层保持16位精度。
  3. 在微调时,仅更新这些16位的适配器权重,而4位的主模型权重保持不变。

这种方法被称为 QLoRA。它大幅降低了微调所需的内存,使得在单个消费级GPU上微调大型模型成为可能。实验表明,使用4位NF(正态分布最优量化)数据类型的QLoRA,其微调性能与16位全参数微调相当。


上一节我们学习了使模型更易用的技术,本节中我们从实际应用的角度,看看不同角色对效率的需求有何不同。

对于最终用户而言,核心诉求是每秒生成的令牌数,他们希望获得快速的响应,并且模型能在本地设备上运行。

对于服务提供商(公司)而言,核心诉求是每美元生成的令牌数,即成本效益。他们需要通过大批次处理用户请求来尽可能提高GPU的利用率。推理分为两个阶段:

  • 预填充阶段:并行处理整个提示,效率较高。
  • 解码阶段:逐个令牌生成,需要缓存之前的键值对,内存密集且严重依赖大批次来提高GPU利用率。

模拟分析显示,在单GPU上:

  • 批次大小为1时,GPU利用率低于1%,用户端令牌速度很快,但公司成本极高。
  • 批次大小为512时,GPU利用率可达67%,用户端令牌速度降至可接受水平,是较合理的平衡点。
  • 批次大小极大时(如16000),GPU利用率近100%,但每个用户的令牌速度极慢,且键值缓存所需内存可能远超GPU容量。

量化在此场景下的影响

  • 对于用户(小批次场景),4位量化能显著提升令牌速度,非常有益。
  • 对于公司(大批次场景),如果量化操作(如反量化)未得到硬件原生支持,其在软件层面的开销会抵消内存节省带来的优势,可能导致速度变慢。因此,工业部署更倾向于使用硬件支持良好的8位量化。

此外,跨多GPU并行推理时,网络通信带宽是关键的瓶颈,需要专用的高速互联硬件才能实现高效扩展。


本节课中我们一起学习了量化技术如何使大型基础模型变得更高效、更易于访问。我们从矩阵乘法的核心计算模式出发,理解了量化的基本原理和异常值带来的挑战。通过分析大规模模型中异常值的结构化特征,我们找到了实现高性能8位量化的方法。接着,我们探讨了4位量化的性能密度极限,以及通过QLoRA技术实现高效的4位模型微调。最后,我们从用户和公司两种不同视角,分析了量化在推理场景下的实际权衡与应用策略。量化是连接前沿AI研究与广泛实际应用的关键桥梁之一。

在本节课中,我们将学习如何通过并行化技术来训练大规模语言模型。我们将探讨单GPU训练的基础概念,然后深入讲解多种在多GPU环境下提升训练效率和内存利用率的策略。

上一节我们介绍了预训练的基本概念。本节中,我们来看看在单个GPU上进行训练时,我们需要关注的计算与内存问题。

训练过程主要涉及前向传播、反向传播和优化器更新。这需要消耗大量的计算资源(浮点运算)和内存资源(存储模型权重、梯度、优化器状态和激活值)。

内存使用则主要来自以下几个方面:

  • 模型参数:通常以半精度(如BFloat16)存储。
  • 梯度:通常以全精度(如FP32)存储。
  • 优化器状态:例如Adam优化器中的动量和方差状态。
  • 激活值:前向传播过程中产生的中间结果。

随着模型规模或批次大小的增加,内存可能成为瓶颈。以下是两种常用的内存优化技术。

为了执行反向传播,需要前向传播中计算的激活值。保存所有激活值会占用大量内存。

激活重计算技术通过只保存部分关键层的激活值(检查点),在反向传播需要时,仅重新计算丢失的中间激活值。这以增加计算时间为代价,显著降低了内存占用。

当GPU内存不足以容纳目标批次大小时,可以使用梯度累积技术。

梯度累积将一个大批次拆分为多个小批次(微批次)。依次对每个微批次进行前向和反向传播,但累积梯度而不立即更新权重。在所有微批次处理完毕后,再使用累积的平均梯度执行一次优化器更新。

当模型过大无法放入单卡,或需要加速训练时,我们需要使用多GPU并行策略。接下来,我们将介绍三种核心策略。

数据并行是最直观的策略。其核心思想是在每个GPU上复制一份完整的模型。

以下是数据并行的执行步骤:

  1. 将训练数据批次分割成多个微批次。
  2. 每个GPU独立处理一个微批次,完成前向和反向传播,计算出本地梯度。
  3. 通过“All-Reduce”通信操作在所有GPU间同步并平均梯度。
  4. 每个GPU使用平均后的梯度更新其本地的模型副本。

为了减少GPU在通信时的空闲时间,可以采用计算与通信重叠梯度分桶等技术。

当模型本身太大,无法放入单个GPU时,需要使用模型并行。张量并行是一种细粒度的模型并行,它将单个层的权重矩阵运算拆分到多个GPU上。

例如,对于一个线性层 Y = X * W,可以通过按列或按行分割权重矩阵 W 来实现并行:

  • 按列分割:每个GPU持有 W 的一部分列,需要广播输入 X,最后拼接各GPU的输出。
  • 按行分割:每个GPU持有 W 的一部分行,最后通过All-Reduce求和得到输出 Y

在Transformer中,前馈网络层和注意力头都可以采用特定的张量并行策略。这种方法减少了单卡内存压力,但引入了层内频繁的通信开销,因此通常只在单个服务器节点内使用。

流水线并行是一种粗粒度的模型并行策略。它将模型的不同层组放置在不同的GPU上。

例如,将第1-4层放在GPU0,第5-8层放在GPU1。数据像流水线一样依次流过各个GPU。这种方法的主要挑战是流水线气泡:即某些GPU完成计算后必须等待其他GPU,造成空闲。

为了减少气泡,可以采用如“一前向一反向”等调度策略,让不同微批次的计算和反向传播重叠进行,从而提高设备利用率。

在数据并行中,每个GPU都保存完整的模型、梯度和优化器状态副本,存在冗余。零冗余优化器(ZeRO)通过分片来消除这种冗余。

ZeRO有三个主要的优化阶段:

  1. 优化器状态分片:每个GPU只存储一部分模型参数的优化器状态。
  2. 增加梯度分片:每个GPU只存储对应那部分参数的梯度。
  3. 增加参数分片:每个GPU只存储一部分模型参数。在前向/反向传播需要时,临时从其他GPU收集完整参数。

ZeRO-3(完全分片数据并行)能最大程度节省内存,但通信开销也最高。它常与数据并行结合使用,以支持用更多GPU训练超大模型。

在实际训练超大模型时,需要组合使用上述策略。一般的配置思路是:

  1. 确保模型能放入内存:首先使用流水线并行或张量并行来拆分模型。
  2. 达到目标全局批次大小:结合使用数据并行和梯度累积。
  3. 优化吞吐量:在满足前两点的基础上,调整并行配置以最大化MFU。

常见的组合模式是:在节点间使用流水线并行,在节点内使用张量并行,再叠加数据并行和ZeRO优化。

本节课中我们一起学习了大规模语言模型训练中的并行化与规模化技术。我们从单GPU的计算内存瓶颈出发,介绍了梯度检查点和梯度累积等基础技术。然后,我们深入探讨了数据并行、张量并行和流水线并行这三种核心的多GPU并行策略,以及用于内存优化的ZeRO技术。最后,我们了解了如何将这些策略组合使用,以高效地训练参数量达数百亿甚至千亿级别的模型。理解这些概念对于从事前沿大模型研发至关重要。

在本节课中,我们将要学习如何处理长序列建模或长上下文模型。这是一个非常活跃的研究领域,涉及从系统优化到新架构设计的多种方法。我们将首先探讨传统Transformer模型在处理长序列时面临的挑战,然后介绍一系列改进技术,最后了解一种全新的架构——状态空间模型。

上一节我们介绍了模型并行化的不同方式。本节中,我们来看看与处理超长序列相关的另一种并行化思路。

为什么长序列建模是一个难题?主要原因有以下几点:

  • 内存复杂度:Transformer模型的内存需求随序列长度呈二次方增长,这会导致严重的内存瓶颈。
  • 计算复杂度:注意力机制的计算量同样随序列长度呈二次方增长,影响训练和推理速度。
  • 训练数据:获取高质量、格式统一的长上下文训练数据本身具有挑战性。

长序列建模的应用场景非常广泛,包括:

  • 处理长文档(如书籍、法律文件)
  • 代码库理解与生成
  • 长视频或音频内容分析
  • 智能体(Agent)的长期历史跟踪
  • 基因组序列分析

为了衡量模型在长上下文任务上的性能,研究人员设计了一些基准测试和诊断任务。

以下是几个重要的评估方式:

  • Long Range Arena:包含多种具有长程依赖关系的任务(不限于NLP)。
  • SCROLLS:一个NLP特定的基准,包含需要对长文档进行总结、问答等任务。
  • 针在干草堆(Needle in a Haystack):一个简单的诊断任务,用于测试模型能否从超长上下文中准确检索出特定信息。早期模型在此任务上表现不佳。
  • 上下文学习(In-Context Learning):通过增加上下文中的示例数量,可以提升模型在新任务上的表现,这本身也是长上下文能力的一种体现。

现在,让我们回顾一下Transformer注意力机制为何会成为处理长序列的瓶颈。

其中,计算 QK^T 会得到一个形状为 [batch_size, num_heads, sequence_length, sequence_length] 的矩阵。这导致了 O(n^2) 的内存和计算复杂度。

在实际的GPU硬件中,快速但容量小的SRAM与慢速但容量大的HBM(高带宽内存)之间的数据迁移会成为主要瓶颈。当序列很长时,注意力矩阵无法放入SRAM,频繁的数据交换会极大降低速度。

为了解决这个问题,研究人员提出了在线Softmax技巧。其核心思想是将Softmax计算分解为分子和分母的增量更新,从而避免存储整个 n x n 的注意力矩阵。

对于一个查询向量 q,我们可以按顺序遍历所有键值对 (k_i, v_i)

  1. 计算权重 w_i = exp(q·k_i)
  2. 增量更新分子 numer += w_i * v_i
  3. 增量更新分母 denom += w_i
    最终输出为 numer / denom。这种方法将内存复杂度从 O(n^2) 降低到了 O(n)



基于这一思想,后续工作如 Flash Attention 通过编写高效的CUDA内核,更好地管理GPU内存层次结构,显著加速了训练和推理。而 Ring Attention 则进一步将这种在线计算分布到多个设备上,实现了“上下文并行”,从而能够处理极长的序列。

另一个关键问题是外推(Extrapolation):如果一个模型只在固定长度(如4000个令牌)的序列上训练,当输入更长的序列时,它能否正常工作?

答案通常是:不能。问题主要出在位置编码上:

  • 绝对位置编码(可学习的):模型只学习了前4000个位置的嵌入向量。对于第4001个位置,它使用的是未经过训练的随机向量,导致模型行为不可预测。
  • 绝对位置编码(固定的,如正弦编码):虽然具有周期性,但模型在训练中未见过的绝对位置对应的向量模式仍然是新的,可能导致分布外问题。
  • 相对位置编码(如RoPE):理论上应该能更好地外推,但模型在训练过程中可能巧妙地学会了依赖绝对位置的特征,因此在实践中,未经调整的RoPE外推能力也有限。

为了解决外推问题,主要有两种互补的策略:

  1. 在更长序列上继续训练(Post-training):收集或合成更长的文档数据,对预训练模型进行继续训练,使其适应更长的上下文。
  2. 调整位置编码:例如位置插值(Position Interpolation),将原始的位置索引缩放,使其落入模型训练时所见的范围;或者调整RoPE的基频(base frequency),改变其周期,使其衰减更慢,从而更好地泛化到更长距离。

前面我们讨论了在Transformer框架内改进长序列处理的方法。现在,我们来看看一种不同的架构——状态空间模型(State Space Models, SSM),它融合了RNN和CNN的思想。

首先,回顾一下RNN的优势与劣势:

  • 优势:理论上具有无限上下文,推理时内存效率高(只需维护一个隐藏状态)。
  • 劣势:训练难以并行化(序列计算依赖),且存在梯度消失/爆炸问题。

状态空间模型的关键洞见在于双重视角

  1. 循环视角:如上式所示,可以进行高效、与序列长度无关的推理。
  2. 卷积视角:通过巧妙的数学变换,整个系统可以等价地视为与一个全局卷积核进行卷积操作。这个卷积核可以通过参数 A_bar, B_bar, C 计算得到。由于卷积操作可以并行计算,因此训练过程也可以高效并行化

这样,SSM就同时获得了RNN的高效推理能力和CNN的高效训练能力。

基于这个基础框架,出现了两个重要的变体:

  • S4 (Structured State Space Sequence Model):其主要创新是为控制状态更新的矩阵 A 设计了一种特殊的结构化形式(通过HiPPO理论),这有助于模型更好地记忆和保留历史信息。
  • Mamba:它引入了选择性(Selectivity) 机制。在S4中,参数 A, B, C 是时间不变的。而Mamba让这些参数成为当前输入 x_t 的函数,使模型能够动态地决定保留或忽略哪些信息,从而在处理需要选择性关注的任务上表现更佳。Mamba通过并行扫描(Parallel Scan) 算法来实现这种条件参数化下的高效训练。

状态空间模型在需要超长上下文的任务上展现了强大潜力,例如在长达百万令牌的基因组数据或分钟级音频数据上,其性能优于传统Transformer。

本节课中我们一起学习了长上下文模型的相关知识。

我们首先了解了长序列建模的动机、挑战和应用场景。然后,深入分析了Transformer模型在处理长序列时的瓶颈,特别是注意力机制的二次方复杂度问题,并介绍了高效注意力计算(如在线Softmax、Flash Attention、Ring Attention)来克服这一瓶颈。

接着,我们探讨了模型的外推问题,分析了不同位置编码的局限性,并介绍了通过继续训练调整位置编码来提升模型长上下文能力的策略。

最后,我们介绍了一种超越Transformer的新架构——状态空间模型。它巧妙地将RNN的高效推理和CNN的高效训练结合起来,并通过S4、Mamba等改进,在超长序列建模任务上取得了显著成果。

长上下文建模仍然是一个快速发展的领域,新的方法和技术不断涌现,为处理更复杂、更庞大的序列数据开辟了道路。

在本节课中,我们将要学习大型语言模型的高级推理策略。这些策略旨在利用推理时的计算资源,通过更智能的生成和评估方法来提升模型在复杂任务上的性能。

我们之前讨论了模型训练的两个主要阶段:预训练和后训练。今天,我们将聚焦于另一个关键阶段:推理。给定一个训练好的模型,我们需要使用算法来生成一个或多个输出。我们已经了解了一些基础的解码算法(如温度采样、束搜索)和提示模式(如思维链)。本节课将深入探讨更高级的推理策略,这些策略主要分为两大类:多次生成生成长序列。通过应用这些策略,我们可以在推理阶段通过增加计算量来获得更好的性能,这类似于训练阶段的扩展定律。


上一节我们介绍了推理阶段的基本概念,本节中我们来看看如何通过多次调用生成器并结合评估器来提升性能。我们将这类方法统称为元生成算法

并行生成的核心思想是,对于同一个输入,我们多次调用生成模型以产生多个候选输出,然后通过一个聚合函数来选择最终输出。

以下是几种常见的聚合策略:

  • **输出选择:为每个生成的输出分配一个分数,然后选择分数最高的输出。这需要一个评分函数 V(也称为奖励模型、评估器或价值函数)。其公式可表示为:最终输出 = argmax_{i} V(输出_i)
  • 投票法:假设每个输出都由一个推理路径和一个最终答案组成。我们忽略中间推理,仅对最终答案进行多数投票。
  • 加权投票法:结合了奖励模型和投票法。我们使用奖励模型为每个完整的输出序列(包括推理)打分,然后按答案对这些分数进行加权求和,选择总分最高的答案。

加权投票法在理论上有一个很好的性质:当生成样本数趋于无穷时,其准确率会收敛于对所有可能推理路径进行边缘化后选择**答案的准确率。提升性能的上限取决于生成器 G 和奖励模型 V 的质量。

与仅在最后一步进行评估不同,树搜索策略尝试在生成的中间步骤就进行评估和引导。

设计树搜索算法需要考虑几个关键部分:

  1. 状态定义:如何将生成过程分解为树中的节点(例如,数学解题的每一步)。
  2. 评分函数:如何为每个中间状态分配一个分数(例如,使用“过程奖励模型”来评估当前解题步骤的正确性)。
  3. 搜索算法:使用何种算法遍历树(如**优先搜索、深度优先搜索)。

这种方法允许模型进行回溯,并根据当前路径的潜力动态分配探索预算。它在需要结构化推理且能获得高质量中间反馈的任务(如定理证明、智能体环境)上表现良好。

这种策略从一个初始输出开始,然后使用一个修正模型来改进它,并可以循环此过程。

设计此类系统时,反馈信息的来源至关重要:

  • 外部反馈:利用任务环境提供的额外信息。例如,在代码生成中,编译器或形式化验证器提供的详细错误信息可以极大地帮助修正模型定位和修复问题。
  • 内部反馈:让模型自己生成反馈并自我修正。这种方法在任务易于自我评估时可能有效,但在接近模型能力边界的复杂任务(如数学推理)上,模型生成的反馈可能噪声太大,导致修正效果不佳甚至变差。

上一节我们探讨了通过多次生成来提升性能的方法,本节中我们来看看另一种思路:让模型单次生成一个很长的序列,特别是长思维链

其核心思想是:训练模型在给出最终答案前,先生成一段内部的“思考”过程。在推理时,我们使用简单的解码算法(如贪婪解码),但期望模型能在这段长的思考序列内部,自主地进行推理、验证、回溯和自我修正。

典型的训练方法是使用强化学习。模型的策略是生成“思考+答案”,奖励仅基于最终答案的正确性。通过训练,模型学会如何利用思考过程来最大化奖励。研究发现,随着训练进行,模型生成的思考长度会增加,并且在这些思考中能观察到表达不确定性、分支回溯、重新尝试、验证检查等模式。

最近的研究表明,通过训练模型遵守长度约束,可以在思考长度(生成的令牌数)和任务准确率之间建立平滑的权衡关系,形成一种新的“推理阶段扩展定律”。


本节课中我们一起学习了大型语言模型的高级推理策略。我们了解到,可以通过在推理时投入更多计算资源来提升模型性能,主要途径有两种:

  1. 多次生成:包括并行生成(如**输出选择、投票法)、树搜索和精炼/自我修正。这些方法通常需要结合额外的评估模型或利用环境反馈。
  2. 生成长序列:通过训练模型生成包含内部推理的长思维链,使其在单次生成中就能完成复杂的思考过程。

这些策略为我们提供了在模型训练完成后,进一步挖掘其潜力的强大工具。选择何种策略取决于具体任务、可用的反馈信息以及计算预算。这是一个快速发展的研究领域,未来可能会出现更多创新的方法。

在本节课中,我们将要学习如何高效地运行语言模型进行推理。我们将探讨影响推理速度的关键因素,并介绍一系列旨在提升单次生成、序列生成以及多序列批处理效率的技术和方法。

高效推理是实际应用语言模型时面临的核心挑战。无论是个人在笔记本电脑上运行模型,还是企业通过API服务海量用户,都需要考虑如何平衡生成速度、资源消耗和模型质量。本节课将介绍衡量推理效率的指标,分析硬件瓶颈,并深入讲解几种关键的优化技术。

在深入具体方法之前,我们需要理解几个衡量和影响推理效率的基础概念。

评估推理效率主要关注两个指标:延迟和吞吐量。

  • 延迟:指用户提交请求到获得完整响应所需的时间。它直接影响用户体验。一个关键子指标是“首词时间”,即从发送提示词到模型开始生成第一个词元所花费的时间。
  • 吞吐量:指在单位时间内系统能够处理完成的请求数量。它衡量的是系统的整体处理能力。一个常用指标是“每秒处理词元数”。

通常需要在延迟、吞吐量和模型质量三者之间进行权衡。例如,使用更大的批处理尺寸可以提高吞吐量,但可能会增加单个请求的延迟;使用更小、更快的模型可以降低延迟,但可能会牺牲一些生成质量。

推理效率很大程度上受底层硬件性能的限制,主要存在三类瓶颈:

  1. 内存容量:GPU等设备的显存大小限制了可加载的模型大小、批处理尺寸和上下文长度。
  2. 计算能力:设备的每秒浮点运算次数决定了执行矩阵乘法等核心操作的速度。
  3. 内存带宽:数据在设备内存和处理器之间传输的速度。当需要频繁加载大量模型权重或激活值时,这可能成为主要瓶颈。

根据主要限制因素,操作可分为计算受限型内存受限型。优化策略也需针对不同的瓶颈进行设计。

Transformer模型推理通常分为两个阶段,并利用键值缓存来优化。

  • 预填充阶段:处理输入的提示词,计算并缓存注意力机制中的键和值。
  • 解码阶段:基于提示词的键值缓存,自回归地生成后续词元。每生成一个新词元,只需计算该词元的查询向量,并与缓存的键值进行注意力计算,无需为整个已生成序列重新计算注意力,从而避免了二次复杂度。

键值缓存的大小公式为:缓存大小 = 2 * 批处理尺寸 * 序列长度 * 注意力头数 * 每头维度 * 数据类型大小。管理好键值缓存对内存效率至关重要。

批处理是指同时处理多个输入序列。由于GPU擅长并行计算,批处理通常能显著提高吞吐量,尤其是在处理大量独立请求(如数据集推理、强化学习环境)时。然而,在单用户交互式场景(如聊天)中,批处理的优势可能不明显,甚至可能因等待组批而增加延迟。

上一节我们介绍了推理的基础概念和瓶颈,本节中我们来看看如何优化生成单个词元这一核心操作的速度。主要思路包括减少数据移动、更好地利用硬件以及减少计算操作本身。

以下是几种关键的单步生成优化技术:

  • 量化:通过降低模型权重的数值精度(例如从FP32到INT4)来减少模型大小和内存传输量。这直接缓解了内存带宽瓶颈,并能加快加载速度。许多在边缘设备(如笔记本电脑)上运行的模型都经过了量化。
  • 模型压缩与蒸馏:通过知识蒸馏等技术,训练一个参数更少但性能接近原大模型的小模型。参数量的减少直接降低了内存占用和计算量。例如,一个1.5B参数的蒸馏模型比70B参数的原模型运行起来要快得多。
  • 分组查询注意力:减少Transformer中注意力头的数量。这直接缩小了键值缓存的尺寸,从而减少了缓存读取和写入所需的内存带宽,提升了推理速度。
  • Flash Attention:一种优化的注意力计算算法。它通过重新组织计算顺序,利用硬件内存层次结构,减少对高带宽内存的访问,并融合多个操作,从而在保持数值精度的同时显著提升注意力计算的速度和内存效率。
  • 混合专家模型:一种模型架构。它将庞大的前馈网络层分解为多个“专家”子网络,并通过一个路由机制为每个输入动态选择少数几个专家进行计算。这样,每次前向传播只激活部分参数,大幅减少了实际浮点运算次数。

优化了单步生成后,我们自然会问:在生成整个序列的过程中,是否还有额外的优化空间?答案是肯定的。一种巧妙的方法是推测解码

推测解码的核心思想是:许多待预测的词元其实相对容易。我们可以用一个更小、更快的“草稿模型”来预先生成若干步(即“推测”),然后让原始大模型(“目标模型”)通过一次前向传播来并行验证这些推测词元。如果草稿模型的输出被目标模型接受,我们就节省了多次大模型前向传播的开销;如果不接受,则回退到使用大模型正常生成。

这种方法的关键在于,即使使用草稿模型进行推测,通过精心设计的拒绝采样算法,我们仍然可以保证最终生成的序列在统计上与大模型独立生成的结果分布一致。推测解码在生成长文本或使用大上下文窗口时效果尤为显著。

在需要同时处理多个序列的场景下,例如批量处理用户请求或运行“**N采样”等算法时,序列间往往存在共享的片段。本节中我们来看看如何利用这种冗余来复用计算,从而提升效率。

以下是两种重要的多序列优化技术:

  • PagedAttention 与 vLLM:vLLM是一个高性能推理库,其核心是PagedAttention技术。它借鉴操作系统内存分页的思想,高效管理键值缓存内存。传统方法为每个序列连续分配缓存空间,容易产生碎片化。PagedAttention允许非连续存储,通过块表和映射机制灵活分配,极大提高了内存利用率和吞吐量。
  • RadixAttention 与 SGLang:SGLang是另一个运行时引擎,其核心是RadixAttention。它使用一种称为基数树的数据结构来存储和管理共享的提示词前缀及其对应的键值缓存。这种方法能高效处理更复杂的共享模式,例如共享系统提示、少样本示例,或在树搜索、思维链等复杂生成策略中复用中间状态。SGLang还提供了一种高级前端语言,让用户能以更直观的方式编写复杂的语言模型程序(如分支、循环),并由运行时自动优化执行。

了解理论后,以下是一些在实践中可用的高效推理工具:

  • 本地运行:LM Studio、Ollama 等应用程序提供了友好的界面,方便用户在个人电脑上下载、管理和运行量化后的模型。
  • 代码集成
    • MLC LLM:一个机器学习编译框架,支持将LLM部署到多种后端(如Web浏览器、iOS、Android),实现跨平台高效推理。
    • vLLM:专注于高吞吐量场景的推理库,特别适合批量处理和研究实验。
    • SGLang:擅长执行复杂的、结构化的生成任务,并通过RadixAttention优化多序列间的计算复用。

本节课中我们一起学习了语言模型高效推理的各个方面。我们首先了解了衡量效率的指标(延迟与吞吐量)和硬件瓶颈。接着,我们探讨了三个层次的优化技术:在单步生成层面,可以通过量化、Flash Attention等方法减少计算和内存开销;在序列生成层面,推测解码能利用小模型加速长序列生成;在多序列批处理层面,vLLM和SGLang等工具通过智能内存管理和缓存复用机制大幅提升吞吐量。掌握这些知识,将帮助你根据实际应用场景(个人使用、批量实验或商业服务)选择合适的技术和工具,以优化语言模型的推理性能。

在本节课中,我们将学习如何通过后训练技术进一步提升语言模型的性能。我们将结合课程中已学过的强化学习、指令微调等概念,从一个新的视角——即如何通过一系列“配方”或流程来改进现有模型——进行深入探讨。课程将重点介绍基于人类反馈的强化学习(RLHF)和直接偏好优化(DPO)这两种核心方法。

后训练的目标是,在获得一个基础预训练模型后,通过一系列方法使其在下游任务(如对话、问题解决)中表现更好。我们将不再孤立地看待这些方法,而是将它们整合成一套改进模型的流程。

上一节我们回顾了预训练和基础适应方法,本节中我们来看看如何通过高级后训练流程系统性地提升模型。

RLHF是一个包含三个主要阶段的流程,旨在利用人类偏好数据来对齐和优化语言模型。

首先,我们需要一个在目标任务上表现尚可的模型作为起点。这通过监督微调实现。

以下是SFT阶段的关键步骤:

  • 数据准备:收集一个包含输入(提示)和期望输出(响应)的数据集。输出可以是人工编写的,也可以是其他模型生成的。
  • 模型训练:使用标准的监督学习(最大似然估计)在这个数据集上微调预训练模型。
  • 目的:让模型初步学会任务的基本格式和要求,为后续优化打下基础。

在拥有SFT模型后,我们需要一个能够量化输出好坏的函数,即奖励模型。但直接获取数值化奖励很困难,因此我们通常先收集偏好数据。

以下是收集和训练奖励模型的步骤:

  • 收集偏好数据:对于一组输入提示,生成多个输出(通常使用SFT模型),然后通过人工标注或使用强大语言模型(如GPT-4)来判断哪个输出更优。这样就得到了形如 (输入x, 优选输出y+, 次选输出y-) 的数据对。
  • 训练奖励模型:基于布拉德利-特里模型,训练一个模型 R_θ(x, y),使其为优选输出分配比次选输出更高的分数。其损失函数为:
    L(θ) = -E_{(x, y+, y-)} [log σ(R_θ(x, y+) - R_θ(x, y-))]
    其中 σ 是sigmoid函数。这个损失函数鼓励奖励模型对优选和次选输出给出有区分度的分数。










现在,我们有了初始策略(SFT模型)和奖励函数(奖励模型),可以使用强化学习来进一步优化策略模型。

以下是此阶段的核心机制与挑战:

  • 目标函数:我们不仅要最大化期望奖励,还要防止模型偏离原始SFT模型太远(避免灾难性遗忘或奖励黑客行为)。因此,目标函数包含一个KL散度惩罚项:
    objective(π) = E_{x∼D, y∼π(·|x)} [R_θ(x, y)] - β * KL(π(·|x) || π_ref(·|x))
    其中 π_ref 是参考模型(通常是SFT模型),β 是控制偏离程度的超参数。










  • 优化算法:近端策略优化是常用的算法。它通过限制新旧策略之间的更新幅度来稳定训练。
  • 优势估计:在策略梯度更新中,我们需要为每个生成步骤分配一个“优势值”,以衡量该动作相对于平均水平的优劣。常用方法是广义优势估计,它平衡了估计的偏差和方差。

上一节我们介绍了RLHF的整体流程,本节中我们来看看另一种更简洁的优化方法。

DPO提供了一种替代RLHF的算法,它无需显式训练奖励模型,而是直接利用偏好数据来优化策略模型。

其核心思想源于RLHF中KL约束目标的最优解形式。可以证明,最优策略 π* 与参考策略 π_ref 和奖励函数 R 存在如下关系:
π*(y|x) ∝ π_ref(y|x) * exp(R(x, y) / β)



DPO的巧妙之处在于,将上述关系式代入布拉德利-特里模型的偏好概率公式中,从而消去了奖励函数 R,得到了一个直接关于策略模型参数 θ 的损失函数:
L_DPO(π_θ) = -E_{(x, y+, y-)} [log σ( β * log(π_θ(y+|x) / π_ref(y+|x)) - β * log(π_θ(y-|x) / π_ref(y-|x)) )]



通过最小化这个损失函数,我们可以直接优化策略模型 π_θ,使其输出的偏好概率与数据中的偏好一致,而无需经过奖励建模和复杂的强化学习循环。

本节课我们一起学习了两种主流的后训练对齐方法。

  • RLHF:是一个多阶段的在线学习流程。它灵活通用,可用于各种奖励信号(不限于人类偏好),但流程复杂,需要训练奖励模型和可能的价值函数,计算成本较高。
  • DPO:是一种离线学习方法。它直接利用静态的偏好数据集进行优化,训练更简单、稳定,但依赖于偏好数据的质量和覆盖度,缺乏在线探索新数据的能力。

在实际应用中,可以根据任务特性、数据 availability 和计算资源来选择合适的方法。例如,对于拥有高质量、大规模偏好数据集的任务,DPO可能是一个高效的选择;而对于需要模型与环境(或奖励函数)持续交互、在线探索的任务,则可能更适合使用RLHF框架。

社区已经提供了许多库来简化这些后训练流程的实现:

  • TRL:Hugging Face 发布的库,支持 SFT、奖励模型训练、PPO 和 DPO。
  • Varl:另一个功能强大的库,提供细粒度的配置选项,支持 GAE、GRPO 等多种优势估计器和训练细节调整。

这些工具将课程中讨论的复杂细节封装起来,让研究人员和开发者能够更便捷地应用这些高级后训练技术。

在本节课中,我们将要学习如何构建能够同时理解文本和图像的模型。我们将从基础的视觉架构开始,逐步深入到如何将视觉信息与语言模型相结合,最终构建出能够处理多模态输入的文本生成模型。

上一节我们介绍了课程的整体目标,本节中我们来看看如何让模型“看见”图像。核心问题在于,我们需要一种方法将图像转换为语言模型能够处理的格式——即一个向量序列。

为了处理图像,我们需要一种不同于纯文本模型的神经网络架构。一种广泛使用的方法是视觉变换器。其核心思想非常简单:将图像分割成小块,然后将每个小块转换为一个向量,从而形成一个向量序列。

以下是其工作原理的步骤:

  1. 分割图像:将输入图像分割成固定大小的网格(例如 14x14 像素的块)。
  2. 展平与投影:将每个图像块展平成一个一维向量,然后通过一个可学习的权重矩阵将其投影到目标维度(例如 1024 维)。
  3. 添加位置信息:为每个向量添加位置嵌入,以保留其在原始图像中的空间信息。
  4. 输入变换器:将得到的向量序列(可额外添加一个特殊的 [CLS] 向量)输入到一个标准的变换器编码器中。

公式:对于每个图像块,其嵌入向量计算方式为:
z_i = W * Flatten(Patch_i) + p_i
其中 W 是投影矩阵,p_i 是位置嵌入。










通过这种方式,我们成功地将二维图像转换为一维向量序列,从而可以像处理文本一样,使用变换器模型来处理视觉信息。

现在我们已经知道如何将图像输入模型,接下来需要学习如何获得有意义的图像向量表示。本节中我们来看看一个名为 CLIP 的对比学习方法,它能够学习图像和文本在共享空间中的联合表示。

CLIP 的目标是学习两个编码器:一个图像编码器和一个文本编码器,使得匹配的图像-文本对在向量空间中彼此接近,而不匹配的则彼此远离。

核心训练过程

  1. 收集一个大规模的图像-文本对数据集。
  2. 对于一个批次中的 N 个图像-文本对,使用图像编码器和文本编码器分别得到 N 个图像向量和 N 个文本向量。
  3. 计算所有图像向量和文本向量之间的相似度矩阵(例如点积)。
  4. 使用对比损失函数进行训练,该损失鼓励正确配对的相似度得分高,错误配对的得分低。

代码:对比损失的一个简化实现如下:

# I: 图像特征矩阵 [N, D] # T: 文本特征矩阵 [N, D] logits = I @ T.T # 相似度矩阵 [N, N] labels = torch.arange(N) # 对角线位置是正确配对 loss_i = F.cross_entropy(logits, labels) # 图像到文本的分类损失 loss_t = F.cross_entropy(logits.T, labels) # 文本到图像的分类损失 loss = (loss_i + loss_t) / 2 

训练完成后,CLIP 模型能够为任意图像和文本生成高质量的向量表示,并且可以实现强大的零样本图像分类能力(例如,将“一张狗的照片”等文本提示与图像进行匹配)。

在掌握了图像表示方法后,本节中我们来看看如何将其与一个大型语言模型结合,构建出真正的多模态对话模型。我们将以 LLaVA 架构为例。

LLaVA 的思路直观而有效:

  1. 视觉编码:使用一个预训练的视觉编码器(如 CLIP 的视觉变换器)处理输入图像,得到一系列图像特征向量。
  2. 特征对齐:通过一个简单的投影层(通常是一个线性层或多层感知机),将图像特征向量的维度对齐到语言模型的嵌入空间。
  3. 多模态输入:将处理后的图像特征向量序列与文本指令的标记嵌入序列拼接在一起,形成一个多模态输入序列。
  4. 语言模型生成:将这个组合序列输入一个预训练的大型语言模型(LLM),并仅对文本部分进行生成损失的计算和微调。

以下是模型整合的关键步骤列表:

  • 输入:原始图像 + 文本指令。
  • 视觉处理:图像 -> 视觉编码器 -> 图像特征向量序列。
  • 维度对齐:图像特征向量 -> 投影层 -> 与 LLM 嵌入维度匹配的向量。
  • 序列构建[图像向量1, ..., 图像向量M, 文本标记1, ..., 文本标记N]
  • 训练:将构建的序列输入 LLM,使用标准的下一个标记预测目标进行微调,但忽略对图像向量的预测。

通过这种设计,LLaVA 能够理解图像内容并根据文本指令生成相关的文本回复,例如描述图像、回答关于图像的问题等。

最后,我们通过一个近期的高级模型 MoMA 来回顾并深化所学概念。MoMA 遵循了与我们之前描述的类似流程,但通过一些创新优化达到了先进的性能。

MoMA 的核心改进点包括:

  1. 多尺度视觉编码:不仅输入完整图像,还输入多个局部裁剪图像,为模型提供从全局到细节的多层次视觉信息。
  2. 高效特征处理:对 CLIP 编码器输出的特征图进行池化操作,以减少序列长度并保留关键信息。
  3. 高质量数据混合:精心构建了包含多种任务类型的大规模训练数据,例如:
    • 人类撰写的详细图像描述。
    • 指向并识别图像中特定物体的任务(视觉定位)。
    • 涉及计数、阅读、推理的复杂任务。

这些技术使得 MoMA 在需要精细理解图像细节和进行复杂推理的多模态任务上表现出色。

本节课中我们一起学习了多模态建模的基础。我们从视觉变换器开始,了解了如何将图像转换为序列数据。接着,我们探讨了CLIP模型,它通过对比学习在共享空间中对齐图像和文本的表示。然后,我们介绍了LLaVA架构,它展示了如何将视觉编码器与大型语言模型简单而有效地结合。最后,通过MoMA模型的案例,我们看到了如何通过多尺度输入和高质量数据来优化这些基础组件,构建出强大的多模态系统。

下节课,我们将把焦点从“理解”转向“生成”,探索那些不仅能理解图像和文本,还能生成图像的模型。

在本节课中,我们将探讨人工智能在数学领域的应用,特别是形式化数学这一方向。我们将了解如何利用语言模型和智能体来辅助数学推理与证明,并讨论如何弥合非形式化推理与形式化验证之间的鸿沟。


上一节我们介绍了人工智能在数学领域的应用前景。本节中,我们来看看数学知识在人工智能系统中的两种主要表示方式。

数学可以表示为非形式化数学形式化数学

  • 非形式化数学:指将数学表示为原始数据,如文本、图像或语音。这种方式非常灵活,也是大多数人接触和使用数学的方式(例如LaTeX书写的作业或arXiv上的论文)。其核心难点在于无法保证内容的正确性。语言模型可能在中间步骤犯下细微错误,这对于自主学习或数学研究中的自主应用来说是个问题。
  • 形式化数学:指将数学表示为特定编程语言(如Lean、Isabelle)中的源代码。你可以编写一个定理(如 1 + 1 = 2)及其证明。如果代码能编译通过,你就获得了证明正确的形式化保证。这与非形式化数学形成了鲜明对比。

以下是几个形式化证明语言的例子:

  • Lean:近年来在数学和AI社区都取得了激动人心的进展。
  • Isabelle:因其某些特性,使其更适合与语言模型一起编写证明。


为了让大家对形式化数学有直观感受,我们来看一个简单的Lean演示。

我们想证明一个简单的传递性定理:如果 RS 的子集,且 ST 的子集,那么 RT 的子集。

在Lean中,代码看起来是这样的:

example (R S T : Set α) (h1 : R ⊆ S) (h2 : S ⊆ T) : R ⊆ T := by intro x hx have hx' : x ∈ S := h1 hx exact h2 hx' 

这是一个交互式定理证明器。当你编写证明时,它会跟踪当前的证明状态。在上面的代码中,introhaveexact 都是策略,用于逐步构建证明。如果所有目标都完成(即没有剩余需要证明的东西),则证明完成。如果代码有误(例如使用了错误的假设),Lean会报错。

这种形式化方法正在数学社区中获得关注。例如,数学家陶哲轩最近使用Lean将他一篇论文中的定理证明进行了形式化。这得益于 Mathlib 项目——一个庞大的、社区贡献的Lean数学库,它包含了大量基础事实和定理,使得形式化工作不必从零开始。

数学社区对此感兴趣的原因包括:

  1. 提供了新的协作方式:可以将大定理分解成多个部分,由不同的人编写证明代码,由于所有代码都可验证,因此可以信任。
  2. 提供即时反馈和正确性保证。

那么,形式化数学为什么对人工智能和语言模型有意义呢?

  1. 可验证性:这可以用来防止幻觉或错误的数学/代码生成。
  2. 强化学习的奖励信号:证明助手中的正确性信号可以作为改进模型的绝佳奖励函数。
  3. 丰富的推理问题谱系:可以构建从极其简单到任意困难的、与数学相关的形式化推理任务来测试模型。

回顾语言模型与形式化数学的历史,OpenAI在2020年的GPT-f是开创性工作之一。该模型能够生成证明的后续步骤,甚至在当时就能找到比原有证明更简洁的版本,展示了语言模型生成证明的潜力。

此后,该领域进展迅速。例如,DeepSeek-Prover等模型已经能够解决国际数学奥林匹克竞赛级别的问题。陶哲轩也提到在形式化过程中使用Copilot来辅助完成一些常规验证。

既然形式化数学有这么多好处,为什么不是所有人和AI系统都只使用它呢?

原因在于非形式化推理与系统所需的形式化推理之间仍存在巨大鸿沟。许多非形式化的想法、直觉甚至标准的LaTeX证明书写方式,都很难在形式化系统中表达。在Lean中,你必须明确写出推理的每一步,并且通常需要深入了解系统(如知道特定引理的名称、语法等)。

因此,本次讲座将围绕弥合这两个领域的主题展开,试图构建兼具非形式化推理的灵活性和形式化推理的可验证性优点的系统。


首先,我们探讨如何训练模型在生成形式化证明时进行非形式化思考。这部分基于我们在ICLR上发表的论文《Lean-STaR》。

我们处于神经定理证明的设置中。可以将证明视为一个交互过程:Lean给出当前证明状态,语言模型生成下一步证明(一个策略),然后反馈给Lean,如此循环,直到证明完成或放弃。

训练语言模型需要输入-输出对。通常,通过从大量定理和证明中提取所有状态和后续步骤来构建数据集。生成证明时,常用方法是进行树搜索(如Best-first search),从多个候选步骤中选择得分最高的进行扩展。

我们感兴趣的是,能否用非形式化思考来增强这个过程?即,不让模型只训练于形式化代码,而是训练它在每一步形式化推理之前生成非形式化的思考。

这样做可能有益的原因:

  1. 规划机会:让模型有机会规划即将做什么,思考不同策略。
  2. 多样化搜索空间:在增加了思考的形式化空间中进行搜索。
  3. 增加表达能力:已有研究表明,加入思维链可以增加生成模型的计算容量或表达能力。

关键挑战在于,互联网上并不存在配对了格式良好的非形式化思考的Lean证明。因此,我们需要设计算法来产生能生成思考的初始模型,并训练它生成更好的思考。

我们开发的算法称为 Lean-STaR,基于自教推理器(STaR)的思想。其核心是通过强化学习学习生成思考。

算法分为两个阶段:

  1. 生成初始模型:使用一个巧妙的技巧——不是提示语言模型根据状态生成思考,而是给它状态之后的步骤,让它回顾性地创建描述该转换的思考。我们在Mathlib数据集上这样做,训练模型接收状态并输出思考和下一步。
  2. 改进循环:使用模型生成带有思考的证明,用Lean检查正确性,然后利用这些反馈改进模型。

对于证明生成,我们设计了并行采样方法:多个链同时尝试生成证明,如果某步出错就重试,直到耗尽预算。这种方法不依赖分数来优先探索,只是从模型中采样。

对于学习算法,我们使用了最简单的算法:保存成功的证明,将其添加到数据集中,重新训练模型,然后重复。这种方法被称为专家迭代或拒绝微调。

评估结果:在MiniF2F数学竞赛问题基准上,我们的方法在增加了思考后,随着推理预算(搜索步数)的增加,表现出更有利的缩放行为,表明思考有助于多样化搜索空间。最终,我们的模型性能超过了基于GPT-4的基线方法。

如今,结合非形式化思考的想法正迅速成为增强LLM定理证明器的标准做法。例如,DeepSeek-Prover和最新的一些模型都采用了在证明步骤前交替进行非形式化推理和代码生成的模式,并使用在线强化学习算法进行训练。

小结:我们讨论了Lean-STaR方法。一个观察是,仅训练于形式化代码可能不足以学习产生代码所需的底层思维过程。我们给出了一种训练模型生成思考的算法。


考虑如何将一个非形式化证明写成形式化证明。我们可以将非形式化证明分解为多个步骤。在Isabelle等语言中,有一种方式可以编写类似于非形式化证明的证明,它创建中间陈述,然后仍需证明每一个陈述。低层步骤的证明可能看起来与非形式化证明不同,例如调用SMT求解器。

在Isabelle和其他交互式定理证明器中,存在称为锤子的自动化工具(如Sledgehammer)。它们非常有用,可以接受当前证明状态,调用外部自动证明器(如一阶逻辑、高阶逻辑或SMT求解器)来尝试证明。这对于人类编写证明时很有帮助,可以写出高层步骤,然后通过调用锤子来填补细节。

然而,锤子不擅长处理IMO这类问题,因为证明搜索空间太大。同时,锤子也无法自动创建高层的“have”语句。因此,我们希望结合两种证明器:一种能生成高层步骤,另一种能填补空白。

方法一:Draft, Sketch, Proof
我们在2023年ICLR上提出了这个方法。其流程是:



  1. 从一个非形式化定理开始,并给出形式化定理陈述。
  2. 由人类或LLM生成一个非形式化证明草稿。
  3. 由LLM将草稿翻译成一个形式化草图,其中低层步骤被标记出来。
  4. 使用低层证明器(如Sledgehammer)尝试填补所有空白。如果成功,则获得完整证明。

这种方法与之前逐步生成的方法不同,它可以生成多个可能的非形式化证明和草图,然后并行尝试填补空白,任何一个成功都意味着证明完成。

实验表明,在MiniF2F基准上,随着采样次数增加,性能提升。有趣的是,使用LLM生成的非形式化证明最终超过了使用人类编写的非形式化证明的性能。

方法二:LeanHammer
由于Lean没有像Sledgehammer那样的锤子工具,我们致力于为Lean构建一个低层证明器或“锤子”,以支持这种基于草图的证明。这是一项正在进行的工作。



一个锤子将自动定理证明器集成到交互式定理证明器中。核心挑战是前提选择:需要为自动证明器提供一系列有用的定义、定理和引理。如果提供得当,可以缩小搜索空间。最朴素的方法是提供所有前提,但在Lean Mathlib中约有25万个,这太多了。

我们使用检索技术来解决前提选择问题。训练一个检索模型,给定待证明目标,检索出相关前提。我们使用对比损失训练了一个小型模型(约1亿参数)。然后将这个神经检索器集成到一个与自动证明器交互的树搜索(A*搜索)中。

用户可以在证明的任何步骤调用这个锤子命令,它会自动进行前提选择、调用外部证明器并尝试重建证明。演示表明,它可以用来填补教科书Lean证明中缺失的低层步骤,帮助人类专注于高层推理。

实验显示,我们训练的神经检索器性能良好,并且与现有的符号检索器互补,结合使用可以接近使用人类证明中出现的前提的性能。

小结:我们讨论了两种结合非形式化证明器的方法:Draft, Sketch, Proof 和 LeanHammer。一个有趣的发现是,即使以巧妙的方式使用小型神经网络,也能完成一些非平凡的任务。


最后,我们简要探讨一个未来方向:如何构建工具来辅助那些超越竞赛问题的、更复杂的研究级项目。

像陶哲轩这样的数学家正在尝试使用Lean来创建他们论文中结果的形式化版本。这个过程通常分为三步:

  1. 蓝图:基于论文,非形式化地仔细思考如何构建形式化证明的结构,包括需要的新定义、中间引理等。
  2. 形式化:实际在Lean中编程实现蓝图,可能需要反复修订。
  3. 证明:编写最终的定理证明。

那么,语言模型或AI智能体可以在哪里帮助这个过程呢?一个雄心勃勃的目标是从arXiv论文开始,直接生成完美的形式化代码。虽然这可能在未来实现,但我们更关注当前能做的、可信的简单事情。

一个方向是查看蓝图,并尝试填补其中较小的、辅助性的引理。这些引理对于最终证明是必需的,但对人类专家来说可能繁琐,如果能让语言模型完成将很有用。

实现这一点存在两个障碍:

  1. 工具可及性:许多先进的定理证明系统(如DeepMind的AlphaProof)并不易于访问或运行速度较慢,不适合快速填补小空白。
  2. 运行时间:需要考虑运行大规模树搜索的时间成本。

不过,已有一些可用工具。例如,我们构建的 LLMLean 工具,它在Lean内部创建了一个接口,可以调用语言模型,并能在Lean中检查生成的建议,过滤错误或验证是否完成证明。这对于学习Lean或快速尝试证明小引理很有帮助。

另一个问题是,如何恰当地对模型进行基准测试,以使其更好地处理这类证明?当前许多基准测试基于数学竞赛问题。这些问题虽然复杂,但通常是自包含的,依赖于一组标准结果。而研究项目则涉及新的定义和引理,证明可能依赖于训练中从未见过的东西,这是一个不同的设置。

为此,我们提出了一个新的基准测试 MiniCTX,用于测试这种更真实的证明场景。我们观察到,在现实项目中,证明依赖于不同类型的上下文(如代码库、新定义、不同引理)。我们构建的基准测试基于实际的Lean项目,并设置了时间截止点,确保测试需要真正的泛化能力。我们可以定期更新基准测试以保持领先于语言模型的训练截止时间。

初步实验表明,在处理现实项目时,模型是否接收上下文(如文件中的前置代码) 对性能影响巨大,这与在竞赛问题上的表现不同。目前许多先进的证明系统不考虑项目上下文,因此在这方面有改进空间。我们提供了能够接收前置代码的模型,以及所有的数据和评估代码。

小结:我们简要概述了自动形式化研究级数学的两个挑战:实际使用的工具,以及对模型进行基准测试和衡量进展的方式。


本节课我们一起探讨了人工智能在数学领域的三个方向:

  1. 非形式化思考:训练模型在形式化证明中生成非形式化的思维链,以增强规划和搜索能力。
  2. 非形式化证明器:结合高层草图生成与低层自动化工具,以更接近人类的方式构建证明。
  3. 研究级数学:构建工具和基准测试,以辅助和评估AI在复杂、真实的数学研究项目中的应用。

这些方向正被我们和社区积极探索。希望本节课能让你对这个研究领域有更深入的了解。

在本节课中,我们将继续探讨多模态建模。上一节我们介绍了能够接收文本和图像并输出文本的模型。本节中,我们将关注更灵活的方法,这些方法可以接收文本和图像,并输出文本和图像,或者仅输出图像。

首先,为了描述后续的方法,我们需要对四种不同的生成式建模范式有一个高层次的了解。这些范式在处理图像时经常出现,它们之间有许多权衡。

以下是四种主要的生成式建模范式:

  1. 自回归模型:这是我们课程中讨论最多的范式,也是当前文本生成的标准设置。它逐个生成序列中的元素(如词元或像素),每个元素的生成都依赖于之前已生成的元素。其核心思想基于概率的链式法则。
    • 公式P(x) = ∏ P(x_i | x_
  2. 变分自编码器:这种方法旨在学习数据的潜在表示。它包含一个编码器,将输入数据压缩为一个潜在向量;以及一个解码器,从潜在向量重建数据。通过约束潜在空间(如使其接近高斯分布),可以从该分布中采样并生成新数据。
  3. 生成对抗网络:这种方法包含一个生成器和一个判别器。生成器从潜在向量生成图像,判别器则试图区分真实图像和生成器生成的假图像。两者在对抗过程中共同训练。
  4. 扩散模型:这种方法通过一个逐步添加噪声的过程将数据转化为简单分布(如高斯噪声),然后训练一个模型学习逆向的去噪过程,从而从噪声中生成数据。

我们的目标是将课程中讨论的语言模型扩展,使其能够生成图像。一个核心思路是为图像创建离散的词元

如果我们可以将图像表示为一系列离散的数字(词元索引),就像文本词元一样,那么我们就可以使用标准的 Transformer 语言模型来处理和生成这些词元序列。生成后,再通过一个“解词元器”将这些数字转换回图像。

这听起来可能有些不可思议,但我们将看到一种实现方法。这种方法的关键在于学习一个图像“词元器”和“解词元器”。

首先,我们可以做一个极端的尝试:直接将图像视为像素序列,并尝试在像素上学习一个语言模型。

具体做法是,将图像展平为一维像素序列,每个位置对应一个颜色值。然后,使用与训练语言模型相同的目标(最大化似然)来训练模型。生成时,可以使用课程中讨论的任何生成算法(如采样、Top-K采样等)。

这种方法在2016年曾用RNN实现(PixelRNN),后来也尝试过使用CNN和Transformer。一个关键的挑战是将其扩展到更大的图像。例如,一个1024x1024的三通道图像将产生超过300万个“词元”。这会导致序列长度爆炸式增长,并带来Transformer二次注意力复杂度的计算和内存问题。虽然可以通过稀疏注意力等架构修改来解决,但这仍然是一个挑战。

现在,我们转向第二种方法:学习一个离散的图像词元器和解词元器。这样可以将图像压缩为更少的词元(例如,约1000个),从而大幅降低序列长度。

以下是实现这一目标的两个非常相似的方法。

这种方法基于之前提到的变分自编码器,但将其扩展为离散形式。

核心流程

  1. 编码:编码器将输入图像 x 映射为一个连续的向量。
  2. 量化:引入一个码本,其中包含一组预定义的原型向量。通过计算距离(如L2距离),将上一步的连续向量映射为码本中最近的原型向量。这个原型向量对应的索引,就是离散的词元。
  3. 解码:解码器接收这个被选中的原型向量(而非原始连续向量),并尝试重建原始图像。

训练目标:损失函数包含三部分:

  1. 重建损失:确保解码器能根据量化后的向量良好地重建图像。
  2. 码本损失:将码本中的原型向量拉近其对应的编码器输出。
  3. 承诺损失:鼓励编码器输出更接近某个特定的码本向量。

由于“选择最近邻”这一操作不可微分,训练时使用直通估计器 来传递梯度。简单来说,就是在反向传播时,忽略选择操作的不可微性,直接将解码器输出的梯度复制给编码器输出。

通过这种方式,我们可以将一张图像编码为一个离散词元序列(例如,32x32的网格对应1024个词元),并能从这些词元重建图像。

VQ-GAN 是 VQ-VAE 的一个变体,它用生成对抗网络的对抗性损失替代了 VAE 中的重建损失(如均方误差)。

核心流程:与 VQ-VAE 类似,包含编码、量化、解码步骤。
关键区别:在训练解码器(此时称为生成器)时,不仅使用重建损失,还引入了一个判别器。判别器试图区分真实图像和生成器重建的图像,而生成器的目标是生成能以假乱真的图像来欺骗判别器。这种对抗训练通常能产生视觉质量更高、细节更丰富的图像。



一旦我们拥有了图像词元器(编码器)和解词元器(解码器),构建多模态生成模型就变得直接了。

步骤

  1. 扩展词汇表:将图像词元(例如,8000个)添加到语言模型现有的文本词汇表中。
  2. 准备数据:收集大量文本和图像交错的数据(例如,来自网页)。使用文本分词器和图像词元器将所有内容转换为统一的离散词元序列。
  3. 训练模型:在这个混合的词元序列上,使用标准的自回归语言模型目标(如下一个词元预测)训练一个大型 Transformer 模型。
  4. 生成:模型可以接收包含文本和图像词元的提示,并自回归地生成后续的词元序列。当序列中包含图像词元时,使用图像解词元器将其转换为最终图像。

实际案例

  • Chameleon:Meta 发布的模型,使用 VQ-GAN 作为图像词元器/解词元器,在包含10万亿词元的文本-图像交错数据上从头训练了一个巨型 Transformer,实现了高质量的文本与图像交错生成。
  • 其他模型:许多近期研究也采用了类似范式,通过微调现有语言模型或从头训练,使其能够处理和生成图像词元。

由于时间关系,我们无法深入探讨扩散模型,但它是当前图像生成领域非常重要的一类方法。以下是其核心思想及一些关键论文,供有兴趣的同学进一步学习。

核心思想:扩散模型通过一个前向过程逐步向数据添加噪声,直到数据变成纯高斯噪声。然后,训练一个反向过程(去噪模型)学习如何从噪声中逐步恢复出数据。生成时,从随机噪声开始,多次应用训练好的去噪模型,即可得到一张新图像。

关键论文索引

  • 奠基工作:《Denoising Diffusion Probabilistic Models》和《Score-Based Generative Modeling through Stochastic Differential Equations》几乎同时提出了扩散模型的核心思想。
  • 性能突破:《Diffusion Models Beat GANs on Image Synthesis》通过一系列改进,使扩散模型在图像生成质量上超越了当时的GANs,引起了广泛关注。
  • 文本到图像:《GLIDE: Towards Photorealistic Image Generation and Editing with Text-Guided Diffusion Models》和后续的 DALL-E 2 等工作,将强大的文本编码器(如CLIP)与扩散模型结合,实现了高质量的文本条件图像生成。

本节课我们一起学习了如何扩展语言模型以支持图像生成。我们首先回顾了四种生成式建模范式。然后,重点探讨了通过为图像创建离散词元来统一文本和图像表示的方法。我们详细介绍了 VQ-VAEVQ-GAN 这两种学习图像词元器/解词元器的技术。最后,我们看到了如何将这些组件与标准 Transformer 结合,训练出能够接收并生成交错文本和图像的强大多模态模型。尽管扩散模型是当前图像生成的另一大主流,但基于离散词元的方法因其与语言模型的天然兼容性,为实现真正的多模态对话和生成提供了清晰且强大的路径。

在本节课中,我们将学习自然语言处理(NLP)的基本概念、核心框架以及本课程的整体结构。我们将从NLP的定义出发,探讨构建NLP系统的不同方法,并引入一个通用的“评分函数”框架,该框架将贯穿整个课程。


自然语言处理涉及使计算机能够处理、生成和与语言交互的技术。NLP的关键方面包括:

  • 学习有用的表示:从文本等数据中捕捉某种意义,以便用于下游任务。
  • 生成语言:创建文本或代码,用于对话、翻译、问答等任务。
  • 结合行动:将语言与在环境中执行行动相结合,以解决问题或实现目标。

以下是三个NLP系统示例:

  1. 大型语言模型对话:用户要求模型生成包含英文、日文和Python代码的文本,模型能够理解并生成相应内容。
  2. 检索增强生成:用户输入论文查询,系统检索相关文档并利用语言模型生成关键贡献的摘要。
  3. 软件工程智能体:用户提出学习需求,智能体通过搜索、编写和编辑代码来提供交互式示例。

这些任务通常涉及某种输入 X 和输出 Y,其中至少一方包含语言。例如:

  • 输入文本,输出标签 → 文本分类。
  • 输入文本,输出另一种语言的文本 → 翻译。
  • 输入图像,输出文本 → 图像描述。
  • 输入搜索查询,输出文档列表 → 信息检索。
  • 输入环境状态,输出行动 → 智能体决策。

构建NLP系统主要有以下几种范式,其数据需求各不相同:

  1. 基于规则的系统:手动编写规则(例如,匹配特定关键词来判断文档类别)。数据需求低,但扩展性和泛化能力差。
  2. 监督学习:使用成对的输入-输出数据 (X, Y) 训练模型,使其学习从输入到输出的映射。需要标注数据集。
  3. 强化学习:在环境中设置状态、行动和奖励函数,智能体通过试错学习最大化奖励的策略。需要定义环境和奖励。
  4. 提示:直接向预训练语言模型描述任务,可能提供少量示例。数据需求极低,依赖模型已有的知识。

选择哪种方法取决于可用的数据形式、监督信号以及任务目标。


为了理解为什么需要机器学习方法,我们先尝试构建一个简单的基于规则的分类器。

任务:给定电影评论,判断其情感是正面、负面还是中性。

  • 输入 X:句子。
  • 输出 Y:标签 {-1(负面), 0(中性), 1(正面)}

通用模式:构建分类器可分为三个步骤,这同样适用于更复杂的系统:

  1. 特征提取:将输入句子 X 转换为特征向量 φ(x)。例如,统计句子中“好词”和“坏词”的数量。
  2. 计算分数:通过权重向量 w 与特征向量的加权和计算分数:score = w · φ(x)。权重 w 是系统的参数。
  3. 推理决策:根据分数符号决定类别(例如,正分数为正面,负分数为负面)。

代码示例结果:一个手工编写“好词/坏词”列表及权重的简单规则系统,在测试集上准确率仅约42%。分析错误发现,系统难以处理:

  • 低频词。
  • 单词的不同形态(如 entertaining vs entertained)。
  • 否定结构(如 “not dreadful”)。
  • 隐喻或类比。
  • 扩展到新语言需要大量重复工作。

这激励我们转向学习系统:利用训练数据自动学习特征提取和权重设置。


我们可以将许多NLP任务统一为学习一个评分函数 S_θ(x, y),它为输入 x 和候选输出 y 的兼容性打分。分数越高,表示 y 越可能是给定 x 的正确输出。

  • 对于分类y 是类别标签。我们可以设计函数,为正确标签打高分,错误标签打低分。
  • 对于生成/序列任务y 可以是一段文本、一个行动等。

从评分到概率:通过 softmax 函数,可以将评分函数转换为条件概率分布:
P_θ(y | x) = exp(S_θ(x, y)) / Σ_{y'} exp(S_θ(x, y'))
其中,分母是归一化项(配分函数),确保所有可能 y 的概率之和为1。










意义:一旦有了概率模型 P_θ(y | x),我们就可以:

  1. 推理:寻找最可能的输出 argmax_y P_θ(y | x)
  2. 采样:根据概率分布生成多样化的输出。
  3. 作为策略:在强化学习中,可以将 P_θ(action | state) 视为智能体的策略。

大型语言模型本质上就是这样的概率模型,其输入 x 是上下文,输出 y 是下一个词或一段文本。


要实例化并学习一个评分函数,我们需要决定以下三点:

  1. 参数化:评分函数 S_θ(x, y) 的具体形式是什么?即模型架构和参数 θ 是什么?
    • 示例:词袋模型。将每个词表示为独热向量,对句子中所有词的向量求和得到特征,再进行线性加权。
    • 局限性:忽略词序、无法处理词形变化、缺乏词义相似性概念。
    • 解决方案:使用神经网络(如RNN、Transformer)自动学习更好的特征表示。
  2. 学习算法:如何利用训练数据 {(x_i, y_i)} 自动调整参数 θ
    • 需要定义损失函数 L(θ),衡量模型预测与真实数据的差距。
    • 使用优化算法(如梯度下降、结构化感知机)最小化损失。
    • 示例:结构化感知机算法。对每个训练样本,用当前模型预测,若预测错误则更新权重:w = w + φ(x, y_true) - φ(x, y_pred)
  3. 推理算法:学习完成后,如何对新的输入 x 做出决策?
    • 对于分类:y* = argmax_y S_θ(x, y)
    • 对于生成:可能是贪婪解码、束搜索或从 P_θ(y|x) 中采样。

代码示例结果:使用词袋特征和结构化感知机算法学习的分类器,训练准确率约80%,测试准确率约58%。存在泛化差距。分析学到的权重可以发现一些有趣但有时不合理的模式(例如,标点符号被赋予了高权重)。


本课程将围绕上述框架展开,分为基础部分和高级专题。

第一部分:基础

  • 深度学习与语言建模基础回顾。
  • 关键架构:注意力机制与Transformer。
  • 学习范式:预训练、微调。
  • 推理策略:解码算法。

第二部分:高级专题

  • 架构进阶:长序列建模、混合专家模型。
  • 模型类型:检索增强生成、多模态模型、扩散模型(用于文本)。
  • 学习与推理进阶:强化学习、智能体、高效推理与部署。
  • 评估与研究技能:实验设计、文献综述。

课程形式

  • 课前:阅读指定材料。
  • 课中:讲授、代码示例、问答讨论。
  • 课后:基于课堂内容和阅读材料的小测验。
  • 作业与项目:4个作业。前两个为编码作业,后两个与期末研究项目相关(文献综述、基线复现、最终项目)。最终需提交报告并进行海报展示。

课程政策要点

  • 迟交政策:作业1-3共有5个迟交日,作业4无延期。
  • 测验:每次课后发布,次日截止,会去掉最低的3次成绩。
  • 出勤:强烈建议现场参与。与项目相关的课程必须出席。
  • 候补名单:由系统自动管理,无法手动调整。

本节课中,我们一起学习了NLP的基本定义、构建系统的不同范式,并重点介绍了一个以评分函数为核心的通用学习框架。我们通过对比规则系统与学习系统的优劣,引出了使用神经网络学习强大表示的必要性。最后,我们概述了本课程将如何深入探讨该框架下的参数化、学习和推理等核心问题。在接下来的课程中,我们将从神经网络和语言建模的基础开始,逐步深入到现代NLP的前沿架构与方法。

在本节课中,我们将要学习如何为自然语言处理任务构建更强大的表示。我们将从上一讲的内容出发,深入探讨如何通过学习得到连续、稠密的向量表示,并介绍神经网络模型和训练它们的关键技术。

上一讲我们介绍了基于词袋模型的简单分类器,并指出了其局限性。本节中,我们将看看如何通过子词模型、词嵌入和神经网络来克服这些限制。

首先,我们来探讨如何更有效地表示词汇。词袋模型的一个主要问题是无法处理词汇的形态变化和相似性。子词模型通过将单词拆分成更小的单元(子词或标记)来解决这个问题。

以下是子词模型的核心优势:

  • 信息共享: 例如,后缀“-ing”可以出现在许多不同的单词中(如“running”、“expanding”),模型可以学习到这个子词的通用信息。
  • 词汇表大小可控: 如果为互联网上的每一个单词都创建一个独立的标记,词汇表将极其庞大且稀疏。子词模型通过共享更少的标记来减少计算成本并提高数据利用率。

这个过程被称为分词,即将原始文本数据映射到来自某个词汇表的离散标记序列。分词策略需要在表达能力和效率之间取得平衡:

  • 表达能力: 分词策略必须能够表示任何文本。
  • 效率: 词汇表不应过大(导致稀疏),也不应过小(导致序列过长)。

在实践中,一种流行的分词算法是字节对编码。其核心思想很简单:

  1. 从一个基础词汇表开始(例如所有字节或字符)。
  2. 在训练数据中统计所有连续标记对的出现频率。
  3. 将出现频率最高的标记对合并为一个新的标记,并加入词汇表。
  4. 重复此过程,直到词汇表达到预定大小或满足其他条件。

这个过程能自动从数据中学习出常见的子词单元。

解决了分词问题后,我们不再使用稀疏的独热向量来表示标记,而是使用稠密的连续向量,即词嵌入。

这通过引入一个嵌入层来实现。嵌入层可以看作一个矩阵 E,其大小为 [词汇表大小 V, 嵌入维度 D]。每个标记对应矩阵中的一列(一个 D 维向量)。查找标记 i 的嵌入向量等价于将标记的独热向量与矩阵 E 相乘:

embedding_i = one_hot(i) * E

现在,我们可以构建一个连续词袋模型:对句子中的每个标记,查找其嵌入向量,然后将这些向量求和,得到一个句子的连续表示。虽然这仍然忽略了词序,但表示本身是稠密且可学习的。

为了进行分类,我们通常在求和得到的句子向量上应用一个线性层(全连接层)。线性层包含一个权重矩阵 W(大小为 [输出类别数 K, 嵌入维度 D])和一个偏置向量 b

scores = (sentence_embedding * W^T) + b

模型需要学习的参数包括嵌入层矩阵 E、线性层的权重 W 和偏置 b

简单的线性组合能力有限。为了学习更复杂的特征组合,我们引入神经网络,核心是增加非线性激活函数并堆叠多层。

我们可以构建一个深度连续词袋模型

  1. 获取标记的嵌入向量并求和,得到句子表示 h0
  2. h0 输入一个线性层:h1 = W1 * h0 + b1
  3. h1 应用一个非线性激活函数(如 tanh):a1 = tanh(h1)
  4. 可以继续堆叠更多的线性层和激活函数。
  5. 最后,通过一个输出线性层得到分类分数。

如果没有非线性激活函数,堆叠多个线性层等价于单个线性层,无法增加模型的表达能力。非线性激活函数(如 tanh, ReLU)使神经网络能够逼近更复杂的函数。

现在我们已经有了模型架构,接下来需要学习如何设置其参数。这通过梯度下降优化损失函数来实现。

对于分类任务,一个常用且重要的损失函数是交叉熵损失。我们以多分类为例:

  1. 模型通过 softmax 函数将输出分数转换为概率分布 P_model
  2. 真实标签通常表示为独热向量,可视为一个概率分布 P_data
  3. 交叉熵损失的目标是让 P_model 尽可能接近 P_data。对于独热标签,损失简化为 L = -log(P_model[correct_class])。这意味着模型会努力提高正确类别的预测概率。

交叉熵损失最小化等价于最小化模型分布与数据分布之间的KL散度

得到损失函数后,我们需要计算损失相对于每个模型参数的梯度。这通过反向传播算法实现,其核心是链式法则。现代深度学习框架(如PyTorch)使用自动微分来自动完成梯度计算。

最后,我们使用随机梯度下降等优化算法来更新参数:

参数 = 参数 - 学习率 * 梯度

本节课中我们一起学习了构建现代NLP系统表示层的基础组件:从子词分词到稠密词嵌入,再到引入非线性激活的神经网络架构。我们还探讨了如何使用交叉熵损失和基于梯度的优化方法来训练这些模型。这些概念是理解后续更复杂序列模型(如RNN、Transformer)的基石。

在本节课中,我们将学习语言建模的基础知识。我们将从最简单的语言模型开始,逐步构建到使用神经网络的模型,为后续课程中探讨更先进的大型语言模型技术打下基础。

语言模型的核心是一个概率分布。它定义在某个序列空间上,例如所有英文句子的集合或所有可能的词元组合。这个模型为每个可能的序列分配一个概率值。

拥有一个概率分布后,我们可以用它做几件事:

  • 为序列评分:比较不同序列的概率,选择概率更高的序列。这在机器翻译等任务中用于从多个候选译文中选择**结果。
  • 从分布中采样:通过采样生成新的序列,即文本生成。
  • 条件生成:通过为模型提供输入上下文(条件),可以生成符合该上下文的输出。这构成了机器翻译、问答、指令跟随等多种任务的基础。

直接对整个序列空间建模非常困难。自回归建模的关键在于使用概率链式法则来分解问题。

公式
P(x1, x2, ..., xT) = P(x1) * P(x2|x1) * P(x3|x1, x2) * ... * P(xT|x1, ..., xT-1)



这个公式表明,整个序列的联合概率可以分解为一系列下一个词元预测的条件概率的乘积。这样,我们将对整个序列空间的建模问题,转化为了对相对较小的词表(例如5万个词元)进行多次预测的问题。这种模型被称为自回归模型

上一节我们介绍了自回归建模的核心思想,本节中我们来看看如何实现一个最简单的自回归语言模型:Bigram模型。

Bigram模型做了一个极端的假设:下一个词元的概率只依赖于前一个词元。例如,在句子“The dog ran quickly”中,模型认为“ran”的概率只取决于“dog”,而与“The”无关。

我们的目标是让模型分布 P_θ 尽可能接近真实的数据分布 P_data。我们通过最大化模型在训练数据上的似然来实现,这等价于最小化模型分布与数据分布之间的KL散度。

对于Bigram模型,最大似然估计有一个非常直观的实现:计数

公式
P(xt | xt-1) = count(xt-1, xt) / count(xt-1)



以下是训练步骤:

  1. 在数据集的每个序列开头和结尾添加特殊标记(如 )。
  2. 遍历训练数据,统计每一个“前一个词元-当前词元”对(即Bigram)出现的次数。
  3. 对于给定的前一个词元 xt-1,其下一个词元是 xt 的概率,等于 (xt-1, xt) 出现的次数除以 xt-1 出现的总次数。

通过这种简单的计数,我们就得到了Bigram模型的所有参数。

训练好模型后,我们可以用它来生成新的序列。自回归模型的生成过程非常直接:

  1. 从序列开始标记 开始。
  2. 根据当前上下文(对于Bigram模型,就是最后一个词元),从模型预测的下一个词元概率分布中采样一个词元。
  3. 将采样到的词元添加到序列末尾,作为新的上下文。
  4. 重复步骤2和3,直到采样到序列结束标记

这个过程等价于从完整的序列分布中采样。

我们如何客观地评估一个语言模型的好坏?常用的自动评估指标是困惑度

公式
困惑度 = exp( - (1/N) * Σ log P_model(xt | context) )
其中,N是测试集的总词元数。










解读

  • 困惑度衡量的是模型在预测下一个词元时的“不确定程度”。
  • 困惑度越低越好。一个完美的模型(总是能确定性地预测出正确的下一个词元)的困惑度为1。
  • 可以直观理解为:模型在预测每个位置时,平均需要从多少个等可能的候选词中做选择。

对于Bigram模型,我们可以计算它在测试名字集上的困惑度,作为其性能的量化指标。

尽管Bigram模型引入了许多核心概念(最大似然估计、自回归生成、困惑度评估),但它本身非常简陋:

  • 上下文窗口极短:只考虑前一个词元,无法捕捉长距离依赖。
  • 缺乏泛化能力:完全基于表面形式的计数,无法理解“cat”和“dog”是相似的实体。

为了捕捉更长的上下文,很自然的想法是扩展Bigram模型,得到N-gram模型。N-gram模型假设下一个词元的概率依赖于前 n-1 个词元。

公式
P(xt | xt-n+1, ..., xt-1) = count(xt-n+1, ..., xt) / count(xt-n+1, ..., xt-1)



然而,随着 n 增大,问题也随之而来:数据稀疏性。许多长的上下文组合在训练数据中可能从未出现,导致计数为零,进而使得概率估计为零或不稳定。

为了解决数据稀疏问题,人们提出了平滑技术。其核心思想是“劫富济贫”,从高频N-gram中匀出一些概率质量分配给未见或低频的N-gram。一个最简单的例子是加一平滑(拉普拉斯平滑),即为所有可能的N-gram计数都加1,然后再计算概率。

上一节我们看到了基于计数的N-gram模型的局限性。本节中,我们来看看如何使用神经网络来参数化语言模型,从而引入词与词之间的相似性概念,获得更好的泛化能力。

神经语言模型仍然可以是N-gram式的(即固定上下文长度),但它用神经网络代替了查表计数。其核心优势在于:通过连续的词嵌入表示,模型可以学习到“cat”和“dog”具有相似的嵌入向量,因此在类似上下文中可以做出相似的预测。

一个简单的神经Bigram/Trigram模型架构如下:

  1. 嵌入层:将上下文中的每个词元(例如前2个词)转换为对应的词嵌入向量。
  2. 拼接:将这些词嵌入向量拼接成一个长的特征向量。
  3. 隐藏层:将拼接后的向量输入一个或多个全连接层(使用ReLU等激活函数),得到上下文的高级表示。
  4. 输出层:最后一个全连接层将隐藏状态映射到词表大小的向量,每个值对应一个词元作为下一个词元的“得分”。
  5. Softmax:对得分向量应用Softmax函数,将其转换为下一个词元的概率分布。

训练神经语言模型的目标与之前一致:最大化训练数据的似然。通过概率链式法则,这等价于最小化模型在每个位置预测真实下一个词元的负对数概率之和。

关键联系:这个损失函数正是交叉熵损失。在之前的分类任务中,我们预测的是文档类别(如正面/负面);在这里,我们预测的是下一个词元类别(词表中的所有词)。因此,我们可以直接使用相同的交叉熵损失函数和梯度下降优化器来训练语言模型。

神经语言模型通过参数共享和分布式表示,部分解决了纯计数模型的问题:

  1. 相似性泛化:相似词具有相似嵌入,导致相似上下文获得相似的下一个词元分布。
  2. 更高效的参数利用:模型参数数量不再随词表大小和N的增大而爆炸式增长,而是由网络结构决定。

然而,固定窗口的神经N-gram模型仍然无法有效处理长距离依赖。这需要更强大的序列模型架构,如循环神经网络(RNN)或Transformer,我们将在后续课程中探讨。

在结束前,我们回顾一下构建和评估机器学习系统时的一些重要实践原则。

为了可靠地评估模型的泛化能力,必须将数据划分为三部分:

  • 训练集:用于调整模型参数。
  • 验证集(开发集):用于在训练过程中监控模型性能,调整超参数(如层数、学习率),并进行模型选择。
  • 测试集:仅在最终评估时使用一次,用于提供对所选模型泛化性能的无偏估计。

绝对不要根据测试集的结果反复调整模型,否则会导致模型“过拟合”测试集,其报告的性能将不再可靠。

概率值通常非常小,连乘会导致下溢(数值归零)。因此,在实践中我们总是处理对数概率,将连乘转化为连加,保证数值计算的稳定性。


本节课中我们一起学习了语言建模的基础。我们从最基础的Bigram计数模型出发,理解了自回归建模、最大似然估计、文本生成和困惑度评估。接着,我们探讨了N-gram模型的扩展及其平滑技术,并指出了计数模型的根本局限。最后,我们引入了神经语言模型,它用神经网络参数化下一个词元的分布,通过嵌入和神经网络层引入了语义相似性,为更强大的序列建模奠定了基础。下一节课,我们将深入探讨能够处理任意长序列和长距离依赖的神经网络架构。

在本节课中,我们将要学习一种重要的神经网络架构——循环神经网络。我们将了解其基本工作原理、如何用于语言建模和分类任务,并探讨其训练过程中的关键挑战,如梯度消失问题。最后,我们将介绍编码器-解码器架构以及注意力机制的核心思想。


上一节我们介绍了语言建模和N-gram模型,其架构存在上下文长度固定的局限性。本节中我们来看看一种能够处理任意长度序列的架构——循环神经网络。

循环神经网络的核心思想是,在处理序列时,模型会维护一个“隐藏状态”,该状态会随着序列的推进而更新,从而在理论上能够编码无限长的历史信息。

RNN通过一个循环单元来处理序列。在每个时间步 t,它接收当前输入 x_t 和上一个隐藏状态 h_{t-1},并输出新的隐藏状态 h_t

以下是计算新隐藏状态 h_t 的公式:

h_t = tanh(W_h * h_{t-1} + W_x * x_t + b)

其中:

  • W_hW_x 是需要学习的权重矩阵。
  • b 是偏置项。
  • tanh 是非线性激活函数(也可使用ReLU等)。

这个公式表明,当前状态是前一个状态和当前输入的线性组合,再经过非线性变换的结果。

RNN本身是一种序列模型,可以应用于不同的任务。

  • 序列分类:例如情感分析。我们可以将整个序列输入RNN,然后取最后一个时间步的隐藏状态 h_T 作为整个序列的表示,再通过一个输出层进行分类。
  • 语言建模:这是自回归任务。在每个时间步 t,我们使用当前的隐藏状态 h_t 通过一个输出层来预测下一个词 x_{t+1} 的概率分布。

训练RNN时,我们使用反向传播算法。由于计算图在时间上展开,这个过程在历史上被称为通过时间的反向传播。模型的参数(W_h, W_x, b)在所有时间步是共享的。


虽然基础RNN在理论上能处理长序列,但在实际训练中会遇到一个严重问题:梯度消失/爆炸

在通过时间的反向传播中,梯度需要沿着时间步反向传播。当序列很长时,梯度会连续乘以许多小于1的项(例如tanh的导数),导致传到较早时间步的梯度变得极小,使得模型难以学习长距离依赖关系。反之,若梯度大于1,则可能发生梯度爆炸,导致训练不稳定。

为了解决梯度问题,研究者提出了两种主要思路:

  1. 门控机制:让模型学会控制信息的流动。例如,设置一个介于0和1之间的“门”。当门为0时,隐藏状态几乎保持不变,便于保留长期信息;当门为1时,则用新信息更新状态。
  2. 残差连接:不直接学习新的隐藏状态 h_t,而是学习当前状态与前一个状态的差值(残差),即 h_t = h_{t-1} + f(x_t, h_{t-1})。这有助于梯度直接流过,缓解消失问题。

基于这些思想,发展出了更强大的循环单元:

  • 门控循环单元:结合了更新门和重置门,能有效控制历史信息的保留与更新。
  • 长短期记忆网络:引入了额外的“细胞状态”来专门保存长期记忆,并通过输入门、遗忘门、输出门进行精细控制。

在实践中,我们可以直接用这些改进单元(如GRU或LSTM)替换基础RNN单元,以获得更好的性能。


RNN不仅能用于单一序列任务(如建模或分类),还能用于序列到序列的任务,如机器翻译、文本摘要等。为此,我们引入了编码器-解码器架构。

该架构包含两部分:

  1. 编码器:一个RNN,用于处理输入序列,并将其最终隐藏状态作为整个输入序列的“上下文向量”。
  2. 解码器:另一个RNN,以上下文向量为初始状态,自回归地生成输出序列。

然而,这种方法有一个明显缺点:无论输入多长,都必须将所有信息压缩进一个固定大小的上下文向量中,这在处理长序列时会造成信息瓶颈。

为了解决上述瓶颈,注意力机制被引入。其核心思想是:在解码器的每一个时间步,动态地计算一个新的上下文向量,而不是只使用编码器最终的那个。

具体步骤如下:

  1. 编码器为输入序列的每个位置 i 生成一个隐藏状态 h_i(称为“键”)。
  2. 在解码器的时间步 t,我们有一个当前的隐藏状态 s_t(称为“查询”)。
  3. 计算查询 s_t 与所有键 h_i注意力分数(例如通过点积或一个小神经网络)。
  4. 对这些分数进行Softmax归一化,得到注意力权重,它表示解码当前词时应该“关注”输入序列的哪些部分。
  5. 将注意力权重作为系数,对编码器的所有隐藏状态进行加权求和,得到当前时间步的上下文向量 c_t
  6. 解码器结合 s_tc_t 来预测下一个词。

注意力机制允许模型在生成每个输出词时,有选择地聚焦于输入序列的不同部分,大大提升了长序列任务的表现。可视化注意力权重图,常能直观展示模型的对齐关系(例如在翻译中,源语言词与目标语言词的对应关系)。

注意力机制的代价是,解码每个词时都需要计算与所有输入位置的关系,导致计算复杂度随序列长度呈平方级增长,失去了基础RNN的线性复杂度优势。


在训练神经网络模型时,有几个重要的实践原则:

我们通常将数据分为三部分:

  • 训练集:用于更新模型参数。
  • 验证集:用于调整超参数(如学习率、网络层数)和监控训练过程,用于更新参数。
  • 测试集:仅在最终评估模型性能时使用一次,以反映其真实泛化能力。

过拟合是指模型在训练集上表现很好,但在验证集上表现变差的现象。其典型特征是训练损失持续下降,但验证损失在经过一个最低点后开始上升。

应对过拟合的策略包括:

  • 使用早停法:当验证损失不再下降(或开始上升)时,停止训练。
  • 添加正则化:如权重衰减。
  • 调整模型复杂度或使用如Dropout等技术。

训练深度网络时,权重初始化至关重要。不恰当的初始化(如全零或方差过大/过小)可能导致梯度问题,使训练难以进行或收敛缓慢。常用的初始化方法如Xavier初始化,旨在确保各层激活值的方差在正向和反向传播中保持稳定,从而有助于深度网络的训练。


本节课中我们一起学习了循环神经网络的基本原理及其在序列建模中的应用。我们探讨了其训练中的梯度消失挑战,并介绍了GRU、LSTM等改进架构。接着,我们了解了用于序列到序列任务的编码器-解码器框架,以及解决其信息瓶颈问题的关键创新——注意力机制。最后,我们回顾了模型训练中关于数据划分、过拟合和权重初始化的实用要点。这些概念为理解后续更复杂的模型(如Transformer)奠定了重要基础。

在本节课中,我们将学习Transformer架构,这是现代高级自然语言处理(NLP)中主要使用的架构。我们将看到,虽然可以对Transformer进行许多不同的修改,但其核心思想构成了当前语言模型的基础。课程后期,我们还将讨论其他正在积极研究的架构方面。总的来说,Transformer是高级NLP,特别是语言模型的主要工作核心。

首先,我们来回顾一下上节课的内容。

在上节课中,我们定义了序列模型或序列架构的概念。序列架构接收一个词元序列,并为每个位置生成一个向量。我们称这个向量为隐藏状态,或与该位置相关的向量表示。一旦拥有了序列架构,就可以将其用于不同任务,例如通过添加一个输出层来参数化语言模型,该层将隐藏状态映射到每个可能的下一个词元的分数,从而实现自回归语言建模。

设计序列架构有多种方式。以下是三种主要方法:

  1. 循环(Recurrence):将前一个位置的隐藏状态输入以计算下一个隐藏状态。上节课我们详细讨论了循环神经网络。
  2. 卷积(Convolution):每个隐藏状态基于当前位置周围的一个小窗口计算。通过堆叠这些层,每个位置可以看到更广的范围。
  3. 注意力(Attention):模型考虑所有可能的位置,并训练其选择与生成当前位置表示相关的位置。

本节课,我们将重点讨论基于注意力的方法,并介绍完全依赖注意力的Transformer架构。

注意力机制的核心思想是:当我们生成序列时(例如在解码或翻译时),我们希望对输入序列的向量进行加权求和,权重由注意力分数决定。这与编码器-解码器架构中的交叉注意力类似,但注意力也可以用于构建序列本身的表示,这被称为自注意力(Self-Attention)

无论是交叉注意力还是自注意力,计算注意力都归结为加权求和。我们通常使用查询(Query)、键(Key)和值(Value) 的信息检索类比来描述这个过程。

以下是计算注意力的步骤:

  1. 计算注意力分数:对于每个查询向量和键向量对,计算一个分数。常见方法包括点积、加性注意力等。Transformer采用了缩放点积注意力(Scaled Dot-Product Attention)
    • 公式Attention(Q, K, V) = softmax(QK^T / sqrt(d_k)) V
    • 其中,Q是查询矩阵,K是键矩阵,V是值矩阵,d_k是键向量的维度。缩放因子 sqrt(d_k) 用于防止点积结果过大导致梯度消失。
  2. 归一化分数:通常使用softmax函数将分数归一化为0到1之间,且总和为1。这可以看作模型在分配一个固定的“注意力预算”。
  3. 加权求和:使用归一化后的注意力分数对值向量进行加权求和,得到注意力层的输出。

Transformer最初在2017年的论文《Attention Is All You Need》中提出。它完全基于注意力机制,摒弃了循环结构,使其易于在GPU上并行化,从而能够大规模扩展。Transformer架构主要有两种形式:编码器-解码器仅解码器。本节课我们主要关注用于语言模型的仅解码器架构。

一个Transformer模型由堆叠的Transformer层(或块)组成。每个层包含几个关键组件。接下来,我们将逐一介绍。

首先,我们需要将输入词元序列转换为向量。这通过一个可学习的词嵌入层完成,它为词汇表中的每个词元分配一个向量。

然而,纯粹的注意力机制没有位置概念。为了解决这个问题,Transformer引入了位置编码(Positional Encoding),将位置信息注入到输入中。

  • 绝对位置编码(Absolute Positional Encoding):为每个位置学习一个独立的向量,然后将其加到对应词元的嵌入向量中。
    • 缺点:无法很好地泛化到训练时未见过的更长序列位置。
  • 相对位置编码(Relative Positional Encoding):让模型关注词元之间的相对距离,而非绝对位置。这有助于更好地泛化到不同长度的序列。
    • RoPE(Rotary Positional Embedding):一种流行的相对位置编码方法,它通过旋转矩阵来编码相对位置信息,满足“点积仅依赖于相对位置”的理想性质。你将在作业中实现它。

这是Transformer的核心。自注意力意味着使用序列本身的向量作为查询、键和值。

  • 多头注意力(Multi-Head Attention):与其让单个注意力头学习所有需要关注的信息,不如使用多个注意力头。每个头可以学习关注序列的不同方面(例如语法、语义等)。具体实现是将查询、键、值矩阵通过不同的线性投影拆分到多个“头”上,在每个头上独立计算注意力,最后将结果拼接起来。
    • 代码概念
      # 伪代码示意 Q = linear(x).view(batch, seq_len, num_heads, head_dim) K = linear(x).view(batch, seq_len, num_heads, head_dim) V = linear(x).view(batch, seq_len, num_heads, head_dim) # 计算每个头的注意力 attention_output = compute_attention(Q, K, V) # shape: (batch, seq_len, num_heads, head_dim) # 合并多头输出 output = attention_output.view(batch, seq_len, -1) 
  • 掩码(Masking):在语言建模中,为了预测下一个词,模型不应该“看到”未来的词。因此,在计算注意力分数时,我们会使用一个掩码矩阵,将未来位置的注意力分数设置为一个极大的负值(如 -inf),这样经过softmax后,这些位置的权重就为0。

随着网络层数加深,梯度消失/爆炸和激活值范围失控的问题会出现。Transformer采用了以下技术:

  • 层归一化(Layer Normalization):对单个样本的所有特征维度进行归一化(减去均值,除以标准差),然后使用可学习的增益(gain)和偏置(bias)参数进行缩放和偏移。这有助于稳定训练。
    • RMSNorm:一种改进的归一化方法,它只使用均方根(Root Mean Square)进行缩放,移除了减均值的操作,更简单且在实践中表现良好。
  • 残差连接(Residual Connection):将某一层的输入直接加到其输出上,即 输出 = F(输入) + 输入。这使得网络可以学习输入的变化量(残差),而非完整的输出,极大地缓解了深度网络中的梯度消失问题,并允许训练非常深的网络。

在自注意力层之后,每个位置的特征会独立地通过一个前馈网络(Feed-Forward Network, FFN)。这通常是一个简单的两层全连接网络,中间包含一个非线性激活函数(如ReLU或GELU),用于对注意力输出进行进一步的非线性变换和特征提取。

一个标准的Transformer层(块)通常按以下顺序执行操作(以“预归一化”结构为例,这在现代模型中更常见):

  1. 输入 x
  2. 层归一化(LN1)
  3. 多头自注意力(带残差连接):x = x + Attention(LN1(x))
  4. 层归一化(LN2)
  5. 前馈网络(带残差连接):x = x + FFN(LN2(x))
  6. 输出 x

多个这样的层堆叠起来,就构成了Transformer模型。

自原始Transformer提出以来,出现了许多改进:

  • 位置编码:从绝对位置编码发展到RoPE等相对位置编码。
  • 归一化位置:从“后归一化”(原始Transformer)变为更稳定的“预归一化”。
  • 归一化方法:从LayerNorm变为更简单的RMSNorm。
  • 注意力机制:引入了分组查询注意力(Grouped Query Attention, GQA)。它让多个查询头共享同一组键和值,减少了推理时需要缓存的内存大小,提升了推理速度,同时保持了大部分性能。
  • 优化器:训练Transformer等大型模型通常使用Adam优化器。它结合了动量(Momentum)RMSProp的思想,为每个参数自适应地调整学习率。动量帮助平滑优化路径,RMSProp根据历史梯度幅值调整更新步长。虽然Adam需要存储每个参数的动量和方差估计,占用额外内存,但其稳定的性能使其成为首选。

这些改进在许多现代模型(如LLaMA)中得到了应用。

本节课我们一起深入学习了Transformer架构。我们从注意力机制的基本原理出发,详细剖析了Transformer的各个核心组件:输入嵌入与位置编码、缩放点积自注意力与多头注意力、层归一化与残差连接以及前馈网络。我们还探讨了针对原始Transformer的一系列重要改进,如RoPE、预归一化、RMSNorm、分组查询注意力以及Adam优化器。理解这些基础组件和优化策略,是掌握现代大型语言模型工作原理的关键。在接下来的课程和作业中,你将有机会亲手实现其中的部分组件,从而获得更深刻的理解。

在本节课中,我们将要学习预训练。预训练已成为大多数先进自然语言处理系统的关键组成部分。它是一种初始阶段,使模型能够更高效地利用数据,并习得多种能力,在自然语言处理领域变得非常重要。

到目前为止,在本课程中,我们已经讨论了多种内容,包括分类任务、语言建模任务,以及各种架构,如循环神经网络、简单前馈神经网络和上一讲中的Transformer。在所有这些示例中,我们都是从头开始训练模型,这意味着我们将权重初始化为随机数,然后运行代码得到一个模型。这种模型通常只针对一个特定任务。

然而,现在我们已经进入了一个基于预训练的新范式。其基本思想是,首先花费大量计算和资源预训练一个单一模型,然后可以以较低的计算成本将该模型适配到许多不同的任务上。

以下是预训练的基本流程:

  1. 收集数据:尽可能多地收集数据。
  2. 运行预训练算法:对收集的数据运行预训练算法。
  3. 获得基础模型:运行算法后,得到一个模型,这个模型可以称为基础模型或基础模型。
  4. 适配到不同任务:然后尝试将该模型适配到不同的任务,例如情感分析、翻译、遵循指令、解决问题等。

对于适配,我们将在后续几讲中深入探讨。主要有两种方式:

  • 提示:我们直接使用模型,不运行任何额外的学习算法来更新其权重。我们只是在输入上下文中描述任务,可能给出一些任务示例,然后尝试让模型执行任务。
  • 微调:我们拥有特定任务的输入-输出对数据,然后运行优化算法来更新模型的权重,使其专门化于该任务。

本节课,我们将专注于第一步:预训练。

预训练的核心思想是迁移学习,即从一个任务中获取知识,然后应用到另一个任务。具体来说:

  • 数据效率:当执行新任务时,可能只需要较少的数据就能达到给定的性能水平。
  • 性能提升:有时,仅凭手头的数据从头训练模型可能无法达到使用预训练模型并进行微调所能达到的性能水平。
  • 便利性:预训练阶段虽然资源消耗大,但开发新模型时,适配预训练模型到特定任务可能比从头训练更高效。

当我们考虑预训练时,有几个主要因素在起作用:

  1. 架构:模型使用的架构,如今通常是基于Transformer的各种变体。
  2. 任务:模型在预训练期间被训练执行的任务。
  3. 数据:用于预训练的数据。
  4. 超参数:学习率、批大小等超参数的选择,对于大规模训练至关重要。

上一节我们介绍了预训练的整体概念和关键因素,本节中我们来看看两种具体的预训练任务。首先是掩码语言建模

掩码语言建模的任务是:给定一个输入序列,我们随机掩码掉其中一些标记,然后训练模型来预测这些被掩码的标记。其损失函数可以形式化地表示为:

L(θ) = - Σ_{x ∈ D} E_{m ~ M} [ Σ_{i ∈ masked(x)} log P_θ(x_i | m(x)) ] 

其中,D是预训练数据集,M是掩码函数,masked(x)是被掩码标记的索引集合。

这种方法可以看作是一种去噪任务:模型接收一个被“损坏”(掩码)的输入,并尝试重建原始输入。需要注意的是,这种目标函数被称为伪似然,它并不完全等同于标准的自回归模型的最大似然估计。

一个著名的掩码语言模型是BERT。其架构基于Transformer,预训练数据来源于书籍语料库和英文维基百科。BERT的掩码策略如下:

  • 80%的时间,用特殊的 [MASK] 标记替换输入标记。
  • 10%的时间,用一个随机标记替换输入标记。
  • 10%的时间,保持原标记不变。

这种策略迫使模型不仅要预测缺失的标记,还要学会识别不合理的随机标记。

掩码语言建模的一个潜在缺点是,它依赖于双向上下文(即同时利用过去和未来的标记)进行预测,这使得它不太适合直接用于生成任务,因为标准的自左向右生成无法利用未来信息。因此,这类模型通常用于学习高质量的表示,然后通过添加输出层进行微调,以完成分类等任务。

上一节我们探讨了掩码语言建模,本节我们转向另一种主流的预训练任务:自回归语言建模

自回归语言建模的任务是:给定一些上文标记,预测下一个标记。其损失函数是标准的最大似然估计

L(θ) = - Σ_{x ∈ D} Σ_{t=1}^{|x|} log P_θ(x_t | x_{ 
                                                               
    
                                                                 

优化这个目标等价于让模型参数 θ 定义的分布 P_θ 尽可能接近真实数据分布 P*,即最小化两者之间的KL散度。

一个有趣的观点是,自回归语言建模本质上是在学习压缩数据。根据香农的信息论,最优编码的期望长度等于数据分布的熵。当我们使用一个语言模型进行编码时,产生的编码长度等于交叉熵。而交叉熵可以分解为数据分布的熵加上模型分布与真实分布之间的KL散度。因此,最小化语言建模损失(即交叉熵)就是在最小化KL散度,从而学习更高效的数据压缩方式。这意味着模型为了压缩数据,必须学习数据中可重用的表示甚至潜在的生成规律。

从预训练的损失函数出发,我们可以将影响预训练效果的因素归纳为三类:

  1. 架构:设计新的架构或增加参数数量。
  2. 数据:用于预训练的数据集。
  3. 超参数与优化:学习率、批大小等,以及如何高效利用大量GPU进行计算。

在实践中,数据部分往往投入了大量的时间和精力。

观察GPT-2和LLaMA等模型的演进,可以发现模型参数量和预训练数据量都在大幅增长。例如,LLaMA-1使用了约1.4万亿个标记进行训练。训练损失曲线显示,增加训练数据量和模型规模通常都能持续降低损失。

为了评估预训练模型在任务上的表现,而无需修改模型,可以使用少样本提示的方法。例如,在输入中描述任务并给出几个示例,然后让模型根据模式完成新输入。研究表明,随着预训练损失的降低,模型在多种任务上的少样本性能通常也会提升。

获取高质量预训练数据是一个复杂的过程,主要涉及:

  • 来源:网络数据是主要来源,例如Common Crawl项目提供的网页快照。
  • 提取:从HTML等原始格式中提取出干净的文本内容。
  • 过滤:根据语言、内容质量等标准过滤掉低质量或无关的页面。
  • 去重:使用如MinHash等算法在大规模数据上高效去除重复内容。

这些步骤中的每一个决策都可能最终影响模型的性能。

除了数量和质量,数据的覆盖范围也很重要。预训练数据应涵盖我们关心的领域,并以合适的比例混合。例如,如果希望模型擅长代码,就需要在数据中包含足够比例的高质量代码数据。实践中,预训练数据通常是多种来源的混合,例如通用网页数据、代码数据、数学数据、书籍数据等。

我们观察到,增加模型大小和训练数据量都能提升性能。那么,在给定的计算预算下,如何最优地分配资源呢?

计算量可以用一个近似公式估算:计算量 ≈ 6 * 模型参数量 * 训练标记数

扩展定律研究了计算量、模型大小、数据量以及最终损失之间的关系。研究发现,对于给定的总计算预算,存在一个计算最优配置,即模型大小和训练数据量的一个**比例。按照这个比例分配资源,可以最有效地降低损失。

这体现了所谓的“苦涩的教训”:充分利用计算的一般方法最终是最有效的。

本节课我们一起学习了预训练。我们首先了解了预训练的基本概念和动机,即通过大规模无监督学习获取通用知识,再迁移到下游任务。接着,我们深入探讨了两种核心的预训练任务:掩码语言建模和自回归语言建模,并分析了它们的原理和特点。然后,我们审视了预训练实践中的关键因素,特别是数据获取、处理和混合的策略。最后,我们介绍了扩展定律的概念,它指导我们如何在计算预算、模型规模和训练数据量之间做出最优权衡。预训练是现代自然语言处理的基石,理解这些内容为进一步学习模型适配、提示工程和规模化训练奠定了基础。

在本节课中,我们将要学习如何利用预训练好的语言模型,在不更新其参数的情况下,使其执行各种任务。这种方法被称为提示上下文学习。我们将从基础概念讲起,探讨不同的提示策略,并深入了解如何通过提示工程来优化模型的表现。


上一节我们介绍了预训练模型。本节中,我们来看看如何利用这些模型执行具体任务。

提示的基本思想是:给定一个输入 X,我们将其放入一个特定的格式(即提示)中,以引导模型执行某项任务。

例如,输入是 I love advanced NLP。如果我们直接将其输入模型,模型并不清楚要做什么。我们可以编写一个提示,将任务描述和输入结合起来:

Clify this sentence's sentiment as either positive or negative. Sentence: I love advanced NLP. 

然后,我们让语言模型生成输出,例如 The sentence is positive.。这样,模型就完成了情感分析任务。

在深入探讨提示策略之前,我们需要了解模型如何生成输出。这通常通过自回归采样算法实现。

其基本流程是一个循环:

  1. 给定当前上下文(包含提示和已生成的令牌),模型预测下一个令牌的概率分布。
  2. 从这个分布中采样一个令牌。
  3. 将采样到的令牌追加到上下文中。
  4. 重复此过程,直到达到最大生成长度或遇到停止令牌。

这个过程可以用以下伪代码描述:

context = prompt while not stop_condition: next_token_distribution = language_model(context) next_token = sample_from_distribution(next_token_distribution) context = context + next_token 

了解了基本概念后,我们来看看几种基础的提示策略。

最基础的用法是不提供明确的任务指令,只给模型一段文本的开头,让它进行文本补全。这是语言模型在预训练后最自然的能力。

例如,提示为:When a dog sees a squirrel, it will usually
模型可能会生成:bark a lot, especially at close range.start to chase after it.



如果我们希望模型执行特定任务,可以提供一个指令提示,但不提供任何输入-输出示例。这被称为零样本学习

例如,对于情感分析任务,提示可以是:

Classify the sentence's sentiment as positive or negative. Sentence: I love advanced NLP. 

零样本学习的优势在于数据效率极高,不需要准备任何训练样本。但其局限性也很明显:模型输出格式不可控,可能生成无关内容,且性能高度依赖于指令的编写。

为了改善零样本学习的不足,我们可以在提示中提供少量输入-输出示例。这被称为少样本学习。由于这些示例是作为上下文提供给模型的,而非用于参数更新,因此这个过程也特称为上下文学习

例如,情感分析的少样本提示:

Classify the sentence's sentiment as positive or negative. Sentence: This is such a cool lecture. Classification: positive Sentence: I did not enjoy the movie. Classification: negative Sentence: I love advanced NLP. Classification: 

示例不仅教会了模型任务本身,也明确了输出格式。通常,少样本提示能获得比零样本更稳定、格式更规范的输出。


上一节我们介绍了基础的提示策略。本节中,我们将深入探讨一系列优化提示效果的技巧,统称为提示工程

许多现代语言模型被微调为聊天助手。与它们的交互遵循特定的消息格式,通常包含系统用户助手三种角色。

  • 系统提示:在对话开始时设定,用于定义助手的行为准则、知识边界和输出格式。
  • 用户消息:用户的问题或指令。
  • 助手消息:模型的回复。

这些消息在内部被格式化为一个特殊的字符串再输入模型。通过精心设计系统提示,可以显著控制模型的行为。例如,系统提示可以要求模型“逐步思考”、“使用Markdown格式”或“避免生成有害内容”。

对于数学、推理等复杂任务,直接要求模型给出最终答案往往效果不佳。思维链提示要求模型在给出最终答案前,先输出其推理步骤。

少样本思维链提示示例:

Question: A zoo has 15 lions and 10 tigers. How many big cats are there in total? Let's think step by step. There are 15 lions and 10 tigers. So total big cats = 15 + 10 = 25. The answer is 25. ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/a4fc2100f8bc978a2a746e9d8a8fb79e_49.png) Question: Sarah has 5 apples. She gives 2 to Mike. How many apples does she have left? Let's think step by step. Sarah started with 5 apples. After giving away 2, she has 5 - 2 = 3 apples left. The answer is 3. ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/a4fc2100f8bc978a2a746e9d8a8fb79e_51.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/a4fc2100f8bc978a2a746e9d8a8fb79e_53.png) Question: There are 8 birds on a branch. 3 fly away. How many are left? 

研究表明,思维链提示不仅模仿了人类的推理过程,实际上为模型提供了“多步计算”的能力,使其能够解决一些单步推理无法解决的问题。有趣的是,有时仅需在指令中加入“让我们逐步思考”这句话,就能激发模型的思维链输出。

对于一些涉及计算的任务,让语言模型生成可执行的代码(如Python),然后由外部解释器执行,往往比让模型直接计算更可靠。这种方法被称为程序辅助语言模型

提示示例:

Question: A store sells apples for $2 each and oranges for $1.5 each. If I buy 3 apples and 2 oranges, how much do I pay? Let's solve this using Python. python apple_price = 2 orange_price = 1.5 total = 3 * apple_price + 2 * orange_price print(total) 

The answer is 9.0.

 这启发了更广泛的智能体概念:语言模型可以学会调用各种外部工具(计算器、搜索引擎、API)来完成任务。 提示链与自问自答 对于需要多步信息检索的任务,可以设计提示链,让模型学会提出子问题、调用工具(如搜索引擎)、整合信息,最终给出答案。这构成了复杂智能体行为的基础。 自动化提示优化 手动设计**提示可能很耗时。一种思路是使用语言模型本身来生成和优化提示。基本流程是: 1. 定义任务和评估指标。 2. 让语言模型生成一批候选提示。 3. 用评估指标测试每个提示的性能。 4. 选择性能最好的提示,或将其加入示例集,让模型生成更好的提示,如此迭代。 例如,一个自动化过程可能发现“深呼吸,然后一步步解决这个问题”这个提示,在数学任务上比“让我们逐步思考”表现更好。 --- 总结与讨论 本节课中,我们一起学习了上下文学习与提示工程的核心内容。 提示方法的优点: * 数据高效:通常需要很少甚至不需要标注数据。 * 无需更新参数:节省计算成本,适用于无法微调的托管模型。 * 灵活通用:理论上可应用于无限多的任务。 提示方法的缺点与挑战: * 性能依赖提示:提示的微小变化可能导致性能大幅波动。 * 工程复杂:存在示例顺序、标签平衡等“玄学”问题,且**提示因模型而异。 * 存在性能上限:模型的能力受限于其预训练知识,提示无法教会模型其根本不会的东西。 * 输出不可控:存在格式错误、生成无关内容的风险。 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/a4fc2100f8bc978a2a746e9d8a8fb79e_69.png) 总而言之,提示是一种强大而灵活的工具,尤其适合快速原型构建和低数据场景。然而,对于需要最高性能、稳定性和可控性的生产级应用,通常需要结合模型微调等更深入的技术。 # 8:微调与知识蒸馏 🎯 在本节课中,我们将学习如何通过微调来使预训练模型适应特定任务,并探讨指令微调与知识蒸馏这两种关键技术。这些方法是当前自然语言处理领域将通用模型转化为实用系统的核心手段。 --- 微调基础 上一节我们回顾了预训练和提示工程。本节中,我们来看看微调。微调可以定义为:基于梯度下降,对预训练模型继续进行训练。 更正式地说,给定预训练模型参数 `θ₀` 和包含输入输出对 `(x, y)` 的任务数据集,微调旨在解决以下优化问题: ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/ada0ecb465f880fd7845cefc7bde1fec_1.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/ada0ecb465f880fd7845cefc7bde1fec_2.png) `min_θ E_{(x,y)} [L(f_θ(x), y)]` 其中 `L` 是损失函数。我们的目标是调整模型参数 `θ`,以最小化在任务数据上的损失。为了防止过拟合,通常会结合使用正则化技术,例如 Dropout 或权重衰减。 --- ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/ada0ecb465f880fd7845cefc7bde1fec_4.png) 微调示例:分类任务 现在,我们通过两个具体例子来理解微调。首先,我们看看如何微调模型用于分类任务。 假设我们有一个预训练模型(例如基于掩码语言建模或自回归语言建模训练的 Transformer)。对于情感分析任务,我们的数据包含输入文本 `x`(例如 “I love NLP”)和类别标签 `y`(例如 “积极”)。 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/ada0ecb465f880fd7845cefc7bde1fec_6.png) 以下是微调分类模型的关键步骤: ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/ada0ecb465f880fd7845cefc7bde1fec_8.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/ada0ecb465f880fd7845cefc7bde1fec_10.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/ada0ecb465f880fd7845cefc7bde1fec_12.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/ada0ecb465f880fd7845cefc7bde1fec_14.png) 1. 添加输出层:在预训练模型的顶层添加一个新的线性输出层(也称为输出头)。该层的权重矩阵 `W` 的维度为 `(d, K)`,其中 `d` 是隐藏层维度,`K` 是类别数量。 2. 计算损失:通常使用交叉熵损失函数,鼓励模型为正确标签分配高概率。 3. 优化参数:使用优化器(如 Adam 或 SGD)遍历数据批次,通过梯度下降同时更新新添加的输出层和预训练模型的参数。 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/ada0ecb465f880fd7845cefc7bde1fec_16.png) 通过这种方式,模型能够利用预训练获得的一般语言表示,并针对特定分类任务进行调整。 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/ada0ecb465f880fd7845cefc7bde1fec_18.png) --- 微调示例:生成任务 接下来,我们看看如何微调模型用于文本生成任务,例如序列反转。 与分类任务不同,生成任务通常不需要添加新的输出层。我们可以直接使用预训练的语言模型架构进行微调。 数据格式为输入文本 `x`(例如 “反转名称:abc”)和输出文本 `y`(例如 “cba”)。在训练时,我们只对输出序列部分的 token 预测计算损失(通常也是交叉熵损失),而忽略输入序列部分的损失。 这种方法使模型学会在给定特定指令或上下文后,生成期望的输出序列。其优势在于,我们无需修改模型架构,只需准备格式正确的配对数据即可开始微调。 --- 参数高效微调 微调所有参数可能计算成本高昂,且容易在小数据集上过拟合。因此,参数高效微调技术应运而生。 一种广泛使用的方法是 LoRA。其核心思想是:不直接更新庞大的预训练权重矩阵 `W₀`,而是学习一个低秩的更新矩阵 `ΔW`。 具体公式为:`W = W₀ + ΔW = W₀ + B A`,其中 `B` 和 `A` 是可训练的低秩矩阵,其秩 `r` 远小于原始矩阵维度。在微调时,我们冻结 `W₀`,只更新 `B` 和 `A` 的参数。训练完成后,可以将 `ΔW` 加到 `W₀` 上,合并为一个单一的模型进行部署。 LoRA 在保持强大表现力的同时,显著减少了需要训练和存储的参数数量,实现了效率与性能的平衡。 --- 指令微调 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/ada0ecb465f880fd7845cefc7bde1fec_20.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/ada0ecb465f880fd7845cefc7bde1fec_22.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/ada0ecb465f880fd7845cefc7bde1fec_24.png) 如果我们希望模型能泛化到多种任务,而不仅仅是单一任务,该怎么办?这时就需要指令微调。 指令微调的数据形式为 `(指令, 输入, 输出)`。例如: * 指令:`“将以下英文翻译成中文:”` * 输入:`“Hello, world.”` * 输出:`“你好,世界。”` 通过在包含多种任务指令的数据集上进行微调,模型能够学会遵循指令,并泛化到未见过的任务形式上。构建高质量指令数据集的方法包括: * 基于模板转换现有 NLP 数据集。 * 人工撰写。 * 使用大语言模型自动生成。 指令微调是打造通用对话助手(如 ChatGPT)的关键前置步骤,使模型能够理解并响应多样化的用户请求。 --- 知识蒸馏 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/ada0ecb465f880fd7845cefc7bde1fec_26.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/ada0ecb465f880fd7845cefc7bde1fec_28.png) 最后,我们探讨知识蒸馏。当缺乏高质量人工标注数据时,可以利用一个强大的“教师模型”来教导一个较小的“学生模型”。 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/ada0ecb465f880fd7845cefc7bde1fec_30.png) 主要有两种方式: ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/ada0ecb465f880fd7845cefc7bde1fec_32.png) 1. Token 级蒸馏:让学生模型模仿教师模型在每个时间步输出的完整 token 概率分布(软标签),而不仅仅是真实的 one-hot 标签。这通常通过最小化两者之间的 KL 散度来实现。 2. 数据级蒸馏:利用教师模型生成大量的 `(输入, 输出)` 配对数据,然后用这些数据直接微调学生模型。这相当于让学生模型学习教师模型的“行为”。 在数据级蒸馏中,还可以引入过滤机制,只保留教师模型生成的高质量样本用于训练,从而有可能让学生模型的表现超越教师模型。 --- 总结 本节课中我们一起学习了: 1. 微调:通过梯度更新,使预训练模型适应特定任务(分类或生成)。 2. 参数高效微调:以 LoRA 为例,学习如何以更低的成本有效微调大模型。 3. 指令微调:通过多任务指令数据训练,使模型获得遵循指令和任务泛化的能力。 4. 知识蒸馏:利用大模型(教师)的知识来训练小模型(学生),是提升模型效率和性能的重要技术。 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/ada0ecb465f880fd7845cefc7bde1fec_34.png) 这些技术构成了将基础语言模型转化为实际应用的核心工具箱,理解和掌握它们对于从事现代 NLP 工作至关重要。 # 9:解码算法 🧠 在本节课中,我们将学习如何使用语言模型生成文本,即解码算法。我们将探讨两种主要的解码策略:基于优化的方法和基于采样的方法,并了解一种用于加速解码的关键技术。 --- 课程回顾与解码定位 在之前的课程中,我们讨论了建模(如自回归模型)、架构(如前馈网络、循环网络、Transformer)以及学习(如最大似然估计、预训练与微调)。本节课属于第三个范畴:推理。这意味着,在模型及其参数确定之后,我们将探讨如何实际使用它来生成文本。 --- 解码算法基础 解码算法可以非正式地定义为:一种选择下一个令牌的策略,最终生成一个完整的输出序列。 我们处理的是自回归语言模型,它将序列建模过程分解为一系列下一个令牌的分布。我们用 `Y` 表示完整序列,用 `Y_t` 表示单个令牌。在每个步骤 `t`,模型会给出一个基于当前上下文的下一个令牌概率分布 `P(Y_t | Y_ 
                                                                  
    
                                                                    
                                                                      0` 时,接近贪婪解码。 * τ > 1:使分布更“平坦”,增加多样性,但可能降低连贯性。 在实践中,温度采样、Top-k 和 Top-p 常被结合使用,是当前LLM API中的标准配置。 --- 解码加速:键值缓存 ⚡ 解码速度在实际应用中至关重要。对于Transformer模型,一个关键的加速技术是键值缓存。 问题:在自回归解码的每一步,模型都需要计算当前查询与所有过去位置的键和值进行注意力计算。如果每次都重新计算所有过去位置的键和值,会产生大量冗余计算。 解决方案:缓存过去所有时间步已计算好的键和值向量。 * 在生成第 `t` 个令牌时,我们只需要计算当前第 `t` 个位置的键 (`k_t`) 和值 (`v_t`)。 * 对于位置 `1` 到 `t-1` 的键和值,直接从缓存中读取。 * 将新计算的 `k_t`, `v_t` 追加到缓存中,供后续步骤使用。 优势:这将每一步的计算复杂度从 `O(t^2)` 量级的矩阵乘法,降低为 `O(t)` 量级的矩阵-向量运算,并避免了重复的前向传播,从而带来显著的加速,尤其生成长序列时。 注意:键值缓存带来了内存读写的新瓶颈,这也是后续高级解码加速技术(如下一讲将涉及的)需要重点优化的方向。 --- ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/d297abfbfbc5e71abc4fe2b2_21.png) 总结 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/d297abfbfbc5e71abc4fe2b2_23.png) 本节课我们一起学习了语言模型的核心解码算法: 1. 基于优化的解码:旨在找到最可能的序列,包括贪婪解码和束搜索。它们可能产生重复或非典型的输出。 2. 基于采样的解码:旨在从模型分布中随机生成序列,包括祖先采样及其改进版(Top-k, Top-p, 温度采样)。它们能产生更多样化、通常更连贯的文本,是现代LLM交互中的主流方法。 3. 解码加速:介绍了键值缓存这一关键技术,通过避免Transformer在解码时的重复计算来大幅提升生成速度。 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/d297abfbfbc5e71abc4fe2b2_25.png) 理解这些基础算法是有效使用和优化语言模型生成能力的基石。在后续关于高级推理策略的课程中,我们将在此基础上,探讨生成多个输出、处理长序列以及更深入的解码加速技术。 # 10:检索与RAG ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_1.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_3.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_5.png) 在本节课中,我们将要学习检索增强生成(RAG)的核心概念、工作原理以及如何构建一个有效的RAG系统。RAG通过结合参数化语言模型和非参数化外部知识库,显著提升了模型在事实准确性、时效性和可验证性方面的表现,是现代AI系统的关键组成部分。 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_7.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_9.png) --- ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_11.png) 概述:为什么需要检索增强生成? ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_13.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_15.png) 为了理解为什么检索增强语言模型如此强大并被广泛使用,让我们先简要回顾一下标准语言模型的构建方式。 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_17.png) 它们的训练涉及两个主要组成部分:海量预训练数据和语言模型本身。在预训练期间,会采样诸如由OpenAI开发的ChatGPT等文档,模型被给予前缀并预测下一个词元。我们更新模型参数,使语言模型学会最大化正确词元的概率。这个过程使语言模型能够将广泛的世界知识、语言理解和生成能力完全封装在其参数内部。 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_19.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_21.png) 然而,我们许多人已经注意到此类参数化语言模型存在显著缺陷。最著名的问题之一是“幻觉”,即模型生成包含大量事实错误的输出。例如,我要求ChatGPT解释我的研究并列出我的一些论文。最初,模型正确地识别出我是一名研究检索增强的研究员,这是准确的。然而,当它开始生成论文标题列表时,情况很快变得糟糕。它提到的第一篇论文是“REALM”,但这并非由我撰写。第二篇论文与我的先前工作相关,但这个论文标题并不存在。由此可见,语言模型常常在预训练数据中代表性不足的长尾知识上表现不佳。 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_23.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_25.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_27.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_29.png) 我们如何克服这种长尾幻觉?这里的挑战在于,对于不太流行的概念,即使它们出现在预训练数据中,语言模型也无法有效地将其记忆或编码在参数中。那么,如果我们能直接搜索预训练期间使用的大规模非参数化数据,定位一些关于罕见实体(如Akari Asai)的文档,并基于它们进行推理来更新答案呢?这本质上就是检索增强语言模型背后的核心理念。我们发现,即使在现有语言模型之上,这也能显著减少幻觉。 此外,检索增强语言模型相比参数化模型具有许多关键优势。参数化语言模型的另一个主要限制是,由于预训练数据通常是提前收集的,其知识会迅速过时。持续用新数据训练模型是一种解决方案,但这极其昂贵,因为现在语言模型的训练数据和模型规模比我们过去拥有的模型要大得多。因此,即使是像GPT-4这样的模型,仍然依赖于2023年或2024年初的信息。检索增强语言模型可以通过动态更新其外部数据存储来解决这个问题。与其重新训练庞大的语言模型,我们只需用新数据更新数据存储,使模型能够生成更及时的信息。我们发现,这种方法非常有效,能使模型基于甚至上周的新闻来更新其预测。 另外,由于现在除了模型响应外,我们还有了这种引文或证据,这使我们能够比不提供解释的普通参数化语言模型更容易地验证答案。这种可验证性在事实准确性至关重要的领域(如医疗或科学领域)非常有帮助。 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_31.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_33.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_35.png) 我希望我们现在已经达成共识:检索增强语言模型对于构建基于语言的系统非常有效,尤其是在可靠性或适应性至关重要的场景中。事实上,检索增强语言模型正变得无处不在。OpenAI已经部署了ChatGPT搜索或Deep Research系统,两者都利用检索增强技术来提供更准确、更及时的响应。在今天的讲座中,我将讨论如何构建此类系统。这些产品不仅限于OpenAI的系统,也包括许多其他公司产品,如Google Gemini或其他搜索引擎系统如Perplexity或You.com。 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_37.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_39.png) 除了这些面向用户的聊天应用,检索增强语言模型也广泛应用于各行各业,以自动化核心任务并开发面向客户的产品,使其成为现代AI系统的关键组成部分。 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_41.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_43.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_44.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_46.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_47.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_48.png) 今天,我将通过提供其核心技术的高级概述,来教大家如何构建此类检索增强语言模型。 --- ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_50.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_51.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_53.png) 数据存储:构建知识库的基石 在深入每个组件的细节之前,让我先更具体地描述一下检索增强语言模型是如何工作的。 这些模型通过结合一个大型非参数化数据存储(一个文档集合)来增强参数化语言模型(如GPT-4)。文档链接取决于模型,但通常每个文档由几百个词组成。检索模型从数据存储中识别与输入查询最相关的文档。具体来说,给定用户输入,我们使用检索系统计算查询X与数据存储中文档的相似度,以识别具有最高相似度得分的Top K个文档D。然后,我们整合检索到的文档D来增强语言模型的预测。请注意,整合检索到的文档有许多不同的方式。 今天,我将讨论如何改进检索增强语言模型的每个关键组件:数据存储、检索模型、语言模型以及它们的整合方式。 首先,我将简要介绍数据存储的构建,因为数据存储的质量是构建有效检索增强语言模型的基础。我将讨论应包含何种类型的文档,应如何处理和构建数据存储,以及其他确保**性能的重要考量。 数据存储的选择与处理 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_55.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_56.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_58.png) 首先,让我们考虑检索增强语言模型的数据存储中应该有什么数据。数据存储的选择在很大程度上取决于目标应用。 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_60.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_62.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_64.png) 如果你的目标是回答基于事实的问题,那么像英文维基百科这样广泛使用且高质量的来源确实是一个很好的候选。许多NLP论文使用英文维基百科来增强模型在基于事实的开放域问答类应用中的预测。 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_66.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_67.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_69.png) 然而,如果你的应用更专业化,例如为代码生成或问答助手构建检索增强系统,那么你需要更策略性地构建你的数据存储。对于代码相关的应用,你的来源可能包括GitHub历史记录、官方文档或社区论坛(如Stack Overflow)的混合。从这些先前工作中得出的关键结论是,数据存储的选择应仔细与你的应用特定需求对齐,以确保高质量和有效的系统性能。 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_71.png) 另一种方法是使用编码了所有内容的海量数据存储。我们之前曾在这方面工作,并构建了一个包含1.4万亿词元的数据存储,这接近语言模型预训练数据的规模。我们发现这对于上游和下游任务都非常有效。正如你在图表中所见,如果我们从左到右按十亿词元规模扩展数据,我们可以在语言模型困惑度和下游任务方面看到巨大的性能提升。 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_73.png) 但这里的关键挑战在于,拥有如此庞大的数据存储可能非常昂贵。因为现在我们可能有数千万甚至数十亿的文档,每个文档都需要我们生成嵌入向量,并且我们需要在推理时拥有大量的GPU、CPU和内存来确保快速检索。因此,在数据存储的规模和推理效率之间存在权衡。 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_75.png) 许多论文直接使用现有的生产搜索引擎API(如Google搜索)来克服托管海量数据存储的挑战。但如果我们使用这些黑盒API,也会面临许多新挑战,例如我们无法在没有它们的情况下复现结果,或者这些搜索系统主要是为人类用户优化的,目前尚不清楚它们是否对语言模型推理是最优的。 文档处理流程 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_77.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_79.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_81.png) 一旦我们确定了一组有用的文档,我们应该如何处理这些文档?即使在更容易获得干净文本文档的英文维基百科中,也存在许多开放性问题。例如,每篇维基百科文章通常比几百甚至几千个词要长得多,如果我们简单地将它们输入语言模型或检索系统,可能已经超出了上下文窗口的限制。 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_83.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_85.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_87.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_89.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_91.png) 因此,我们需要将这些文章处理成一组文档。以下是典型文档处理流程的关键步骤: ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_93.png) 1. 创建原始数据:这涉及从适当来源收集相关网页数据,然后进行清理以确保一致性和可用性。例如,我们下载维基百科文章并处理HTML以提取纯文本。 2. 将纯文本分块成文档:与其将整篇文章作为单个检索单元,我们通常将其分解成更小的块以提高检索准确性。一种常见的分块策略是将文本分割成固定大小的片段,例如每200个词。另一种方法是语义分割,即利用段落标签、换行符或章节标题等结构元素,在拆分文档时保留更有意义的上下文片段。 3. 应用启发式过滤:分块后,应用启发式过滤来优化数据存储也很有用。例如,即使在维基百科中,也有许多没有任何文本或文本极短的文章。先前的研究发现,如果我们在后处理阶段过滤掉这些质量极低或无用的文档,可以大幅提高模型性能。 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_95.png) 因此,在收集初始数据后,仔细处理数据以从中获得**效果非常重要。 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_97.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_99.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_101.png) 本节小结 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_103.png) 在本节中,我们讨论了数据存储。一个重要的结论是选择合适的数据存储并仔细处理这些数据存储。扩展数据存储可能很有帮助,但它需要额外的计算资源。因此,我建议首先找到一个规模适中且易于管理的数据存储非常重要。 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_105.png) --- ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_107.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_108.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_110.png) 检索模型:寻找相关信息 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_112.png) 现在让我们讨论如何使用数据存储构建检索系统。正如我之前讨论的,对于某些应用,人们使用更像生产搜索引擎的系统,但如果你有更具体的下游任务,构建自己的本地检索系统可能非常有帮助。我听说高级NLP的作业是构建一个关于CMU的FAQ的检索增强生成系统。因此,对于这些情况,你可能需要实际构建自己的本地检索系统。希望本节能帮助你学习如何操作。 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_114.png) 检索系统概述 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_116.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_118.png) 检索系统的目标是识别与查询最相关的一组文档,我们通常计算查询与数据存储中所有文档之间的相似度得分。 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_120.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_122.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_124.png) 根据计算相似度的方式,存在许多不同类型的检索系统。在信息检索社区,稀疏检索系统已经广泛使用了数十年。这些早期检索系统通常计算文档中的词项频率来构建索引,这不需要训练。 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_126.png) 最近,密集或神经检索系统被证明更有效。它们训练神经检索模型来计算文档和查询的嵌入向量,并在更密集的嵌入空间中计算嵌入相似度。 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_128.png) 最后,稀疏或密集检索系统通常与重排序模型结合,后者通过生成查询相关的文档表示来计算文档和查询之间的最终相似度得分。 我稍后将详细讨论每一种,但通常这些系统的成本从左到右递增,而性能也往往变得更好。 稀疏检索系统:TF-IDF 首先,让我们谈谈基于关键词的稀疏检索系统。对于第一个检索系统,我们基于词项频率或词频生成文档和查询的嵌入。 我们从一个词汇表开始。例如,这里我们有包含“what”、“can”、“NLP”、“language”、“Amazon”等词的词汇表,我们有Z个文档,现在有一个新查询。 对于每个文档或查询,我们构建一个二进制词袋向量。如果词出现在文档中,则其条目为1,否则为0。例如,对于这个文档D,“what is like candy like”,那么“what”、“candy”、“is”、“like”的条目为1,而所有其他词为0。这个二进制向量通常称为one-hot或多热表示。但这有一个限制。在某些文档中,一些词出现多次,例如这里“life”在文档D1中出现了两次,但这不会在这些one-hot向量中体现。 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_130.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_131.png) 因此,词频计数向量计算词在文档中的出现次数,考虑了文档内的这种词频。这里,“life”出现了两次,我们将原始的one-hot向量更新为2。 基于此,现在我们计算词频和逆文档频率。词频计算词项T在文档D中的频率除以文档中的总词数。这里,如果我们考虑T1“what”在D1中的TF,给定D1“what is the candy like”,那么D1中总共有6个词,而“what”在这个文档中出现一次,所以我们将得到大约0.16作为TF得分。另一方面,逆文档频率计算语料库中文档总数(例如,这里有3个文档)除以包含该词项的文档数,然后取这个数的对数。在这种情况下,词项T1“what”在D1中将得到大约1.0,因为我们取log(3/1)。因此,最终,词项“what”在这3个文档中的TF-IDF得分将是0.18。我们为所有文档中的所有词计算这个TF-IDF得分,然后一旦获得查询,我们就计算查询中每个词的TF-IDF得分,然后计算这些稀疏嵌入的相似度。 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_133.png) 请注意,由于我们同时考虑TF和IDF得分,即使相同的词项在不同文档中也可能获得不同的权重,因为TF得分不同。例如,“what”在D1中得到0.16,但在D2中只得到0.05。此外,即使在同一个文档内,出现次数相同的词项也可能获得不同的数值,因为我们考虑了IDF得分。在D2中,“NLP”和“language”都出现了一次,但“language”得分更高,因为这个词没有出现在任何其他文档中,所以我们认为这个词比其他在多个文档中出现的词更重要。 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_135.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_137.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_139.png) 在我们计算了稀疏嵌入的余弦相似度之后,我们计算查询与数据存储中所有文档的相似度。在这种情况下,D1将获得最高分0.40。 现在你也许能看到使用这种稀疏嵌入系统的问题。当我们看这三个文档时,在我看来,D2或D3与问题“What is NLP?”具有更高的相似性,尤其是D2直接回答了“NLP is an acronym for natural language processing”。但这个文档与查询的相似度得分最低。这是因为,首先,这种词频方法只考虑词项级别的相似性,不能很好地捕捉语义相似性。例如,D1也像是一个问题并包含“what is like”,这与“What is NLP”在词形上相似。其次,这种词汇相似性无法捕捉不同的表达方式,例如,这里D2说“NLP is natural language processing”,但这没有被完全捕捉,因为我们没有很好地处理这些同义表达。另一个问题是,这是简单的词袋模型,不考虑词序,所以一个有趣的例子是“Dog bites man”和“Man bites dog”在TF-IDF得分上看起来相似,这显然在某些情况下是个问题。 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_141.png) 密集检索系统 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_143.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_145.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_147.png) 因此,最近我们观察到神经密集检索系统可以克服这些挑战,并优于稀疏检索系统。 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_149.png) 基本思想保持不变:我们想要计算查询无关的文档嵌入,然后生成查询嵌入,并基于嵌入空间计算相似度。但现在,我们不再基于词频生成嵌入,而是使用强大的神经模型,通过训练好的编码器模型生成密集的、可变的文档和查询嵌入。 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_151.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_153.png) 给定编码器模型,我们预先计算所有文档的嵌入,然后给定查询,我们使用相同或不同的编码器系统生成查询的密集嵌入。之后,我们可以通过执行快速最近邻搜索来计算这些嵌入的相似度,以找到Top K个最相关的文档。 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_155.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_157.png) 有多种方法可以生成这种密集嵌入。我相信你们有像BERT和RoBERTa以及掩码语言模型这样的模型。一种常见的方法是取此类模型[CLS]标记的输出表示,它已经是一个维度浮点向量。另一种流行的策略是取整个输出词元序列的输出向量的均值、最大值或平均池化。这些方法可以直接应用于没有特殊标记(如[CLS])的非掩码语言模型(如Llama),并且已被证明相当有效。 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_159.png) 这些不同策略的经验比较是任务依赖的,正如Expert和SCIBERT论文所示,所以如果可能,我建议你尝试不同的表示方法。 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_161.png) 高效检索与模型训练 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_163.png) 另外,我想讨论另一个关键要素:快速最近邻搜索。特别是当我们将搜索扩展到大规模时,例如在数亿甚至数十亿文档上进行检索,最近邻搜索的效率就非常重要。近似最近邻搜索是理论机器学习、算法和更广泛AI社区的另一个重大研究课题,因此我不会深入细节,但如果你感兴趣,FAISS是该领域最广泛使用的库之一,它对许多不同类型的索引有很好的概述和实现。 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_165.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_167.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_169.png) 具体来说,如果我们处理的是像维基百科这样规模为数千万文档的数据,那么你可能只需要使用FAISS索引,它执行精确的最近邻搜索。但如果你需要处理更大的数据存储,那么另一个流行的替代方案是HNSW,它构建分层索引以加速搜索,但这种方法需要更多的内存和存储资源来构建索引。 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_171.png) 实际应用的另一个考虑因素是索引的总大小。如果你的嵌入规模为数千万(如维基百科),那么即使使用最好的检索模型,你最终也会消耗大约100GB的内存。为了最小化索引大小,你也可以探索量化或二值化嵌入方法。例如,我们有一些先前工作生成二进制嵌入而不是浮点嵌入,通过这样做,我们可以将维基百科的总嵌入大小从60GB减少到2GB。 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_173.png) 现在,我也想简要谈谈如何训练这样的检索系统。我们通常使用对比学习目标,模型学习将查询嵌入拉近其正例文档,同时推离负例文档嵌入。具体来说,我们最小化一个对比目标,该目标比较查询-正例相似度与查询-负例相似度之和。一个好的编码器系统对正例文档有高分,使损失趋近于零;而一个差的编码器系统对正例分配低分或对负例分配高分,这会使损失大得多。 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_175.png) 训练的关键在于在训练时选择好的正例和负例。通常,我们无法在训练时使用整个索引,因为这可能非常昂贵。因此,我们通常使用一种称为“批次内负例”的技术来构建好的负例和正例文档对。一个巧妙的想法是,给定查询以及同一小批次中所有这些查询的正例文档,我们将其他查询的正例文档作为这个特定查询Q1的负例。这种批次内负例虽然想法相当简单,但已被证明对于训练密集检索系统非常有效。 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_177.png) 此外,还有一些论文尝试进行更复杂的负例文档采样策略,我们称之为“困难负例”,原始的DPR论文对如何构建这些负例和正例文档有一些讨论,所以如果你感兴趣,请查看该论文。 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_179.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_181.png) 还有一些新技术可以改进检索系统的训练。在DPR的方法中,我们需要有查询和正例文档对来开始训练。然而,当我们尝试扩展训练时,通常没有这样的标注对。因此,有一些论文尝试通过使用无监督训练来扩展检索模型训练,这很像语言模型。这篇名为“Contriever”的论文有一个非常好的方案,并展示了无监督训练的有效性,其中一项技术是他们在从一篇文档中提取句子时使用独立裁剪技术,然后将这些句子对标记为正例对,因为我们可以假设来自同一文档的句子比来自不同文档的句子对更可能相关。Contriever虽然发布于2022年,但仍然是较小规模检索模型中最强的模型之一。 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_183.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_185.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_186.png) 如今检索领域的另一个流行技术是对检索模型进行指令微调,就像语言模型指令微调一样。最初,检索系统通常针对不同的检索用例进行设计和训练,例如我们有用于维基百科、代码或问答任务的不同系统。然而,现在有一种新的检索模型趋势,它们可以通过接收指令(除了原始查询外)来执行许多不同的检索任务。使其工作的核心技术是设计好的困难负例样本,以便我们可以鼓励模型使用这些额外的任务指令。 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_188.png) 重排序模型 最后,我想简要提一下重排序模型,它通常能在稀疏或密集检索模型的基础上给我们带来显著的额外增益。在稀疏和密集检索模型中,我们基于完全独立计算的嵌入来计算查询和文档之间的相似度。但这不允许我们捕捉查询和文档之间更复杂的交互。因此,交叉编码器的想法是使用编码器模型联合编码问题和文档,然后基于这个联合嵌入进行分类或计算相关性得分。现在我们可以获得以查询为条件的表示,正如你所见,这里有更多的交互,我们发现这种交叉编码器在现有的稀疏和密集编码器模型之上可能非常强大。 评估指标与基准 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_190.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/afcbff71aca946d448f6ab6c1bd96d98_192.png) 现在我想简要谈谈这些不同的检索模型表现如何,但在讨论结果之前,让我简要回顾一下评估指标,因为我们也需要理解指标来理解模型的能力。我不会深入探讨每个指标如何计算不同模型的性能,但我想强调检索指标主要有两大类。一类是无序检索指标,只考虑正例文档是否出现在Top K检索结果集中,不考虑任何排序。另一类指标则考虑检索结果的排序。这很重要,例如,如果我们必须在顶部结果中呈现**结果,或者我们想确保顶部结果都是相关的。因此,你应该更关注哪个指标取决于你的应用。但通常,像MRR@10这样的指标在常见基准 # 11:多模态建模 I 👁️📝 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/11990dd9eb909dd20f1329c3d_1.png) 在本节课中,我们将要学习多模态建模的基础知识,特别是如何让模型同时理解和处理文本与图像。我们将从核心架构开始,探讨如何将图像转化为模型可以理解的表示,并最终学习如何将这些表示整合到语言模型中,使其能够根据图像生成文本。 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/11990dd9eb909dd20f1329c3d_3.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/11990dd9eb909dd20f1329c3d_5.png) --- ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/11990dd9eb909dd20f1329c3d_7.png) 多模态建模概览 我们之前主要关注文本到文本的模型。多模态建模则扩展了输入和输出的范围。我们可以将任务分为几类: * 文本 + 图像 -> 文本:模型接收图像和文本作为输入,但只生成文本作为输出。这是本节课的重点。 * 文本 -> 图像:模型根据文本描述生成图像。 * 文本 + 图像 -> 文本 + 图像:模型可以接收和生成交错的文本与图像,这是最通用的形式。 多模态模型在高级自然语言处理中应用广泛,例如网页智能体、计算机操作助手以及需要视觉推理的任务。 --- 第一部分:视觉 Transformer 架构 🏗️ 上一节我们介绍了多模态任务的基本分类。本节中,我们来看看如何为图像设计合适的神经网络架构,以便将其输入到模型中。 我们的目标是将图像表示为一系列向量(类似于文本的词元嵌入),这样就可以利用为序列数据开发的强大架构(如 Transformer)来处理图像。 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/11990dd9eb909dd20f1329c3d_9.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/11990dd9eb909dd20f1329c3d_11.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/11990dd9eb909dd20f1329c3d_13.png) 视觉 Transformer 的核心思想是将图像分割成多个小块(patch),将每个块展平为向量,然后像处理文本序列一样,使用标准的 Transformer 编码器来处理这些向量序列。 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/11990dd9eb909dd20f1329c3d_15.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/11990dd9eb909dd20f1329c3d_17.png) 以下是视觉 Transformer 的工作流程: 1. 图像分块:将输入图像(例如 224x224 像素,3个颜色通道)分割成固定大小(例如 16x16)的块。 2. 块嵌入:将每个图像块展平为一个向量。对于一个 16x16x3 的块,会得到一个长度为 768 的向量。 3. 线性投影:通过一个可学习的权重矩阵,将每个块向量投影到 Transformer 模型所需的隐藏维度(例如 768)。 4. 添加位置嵌入:为每个块嵌入添加位置信息,以保留图像的空间结构。可以使用绝对位置编码或更高级的方法(如 RoPE 的视觉变体)。 5. 添加 [CLS] 标记:在序列开头添加一个特殊的分类标记,其最终表示可用于分类等下游任务。 6. Transformer 编码:将得到的序列(块嵌入 + 位置嵌入 + [CLS] 标记)输入标准的 Transformer 编码器。 7. 输出头:对于特定任务(如图像分类),可以在 [CLS] 标记的输出上添加一个输出层进行微调。 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/11990dd9eb909dd20f1329c3d_19.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/11990dd9eb909dd20f1329c3d_21.png) 与之前主导图像领域的卷积神经网络相比,视觉 Transformer 在足够多的数据和计算资源下,展现出更优越的扩展性能。 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/11990dd9eb909dd20f1329c3d_23.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/11990dd9eb909dd20f1329c3d_24.png) --- ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/11990dd9eb909dd20f1329c3d_26.png) 第二部分:学习图像表示:CLIP 🎯 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/11990dd9eb909dd20f1329c3d_28.png) 上一节我们介绍了处理图像的 Transformer 架构。本节中,我们来看看如何训练这样的模型,使其学习到高质量的图像(和文本)表示。 传统的图像模型通常在带有单一类别标签的数据集上进行预训练(例如,将图片分类为“猫”)。这种方法忽略了图像可能包含的丰富信息(例如,数量、颜色、空间关系)。CLIP 提出了一种利用互联网上大量存在的“图像-文本对”进行预训练的方法。 CLIP 的目标是学习一个图像编码器和一个文本编码器,使得配对图像和文本的表示在向量空间中尽可能接近,而不配对的距离尽可能远。 其训练使用了一种对比学习损失函数。假设我们有一个批次包含 N 个图像-文本对。分别通过图像编码器和文本编码器后,我们得到 N 个图像向量和 N 个文本向量。我们计算所有图像向量和文本向量之间的相似度(例如点积),形成一个 N x N 的矩阵。矩阵的对角线是配对样本的相似度,非对角线是非配对样本的相似度。 损失函数鼓励提高对角线上的相似度,同时降低非对角线上的相似度。具体形式类似于带 softmax 的交叉熵损失,可以从图像角度(对于每个文本,在 N 个图像中选择正确的那个)和文本角度(对于每个图像,在 N 个文本中选择正确的那个)分别计算,然后取平均。 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/11990dd9eb909dd20f1329c3d_30.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/11990dd9eb909dd20f1329c3d_31.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/11990dd9eb909dd20f1329c3d_33.png) 通过在海量(例如 4 亿对)图像-文本数据上训练,CLIP 学习到了强大的、对齐的跨模态表示。一个关键应用是零样本分类:要分类一张图像,只需将可能的类别名称构造成提示(如“一张 [类别] 的照片”),用文本编码器得到其表示,然后计算它们与图像表示的相似度,选择相似度最高的类别即可,无需针对该分类任务进行任何微调。 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/11990dd9eb909dd20f1329c3d_35.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/11990dd9eb909dd20f1329c3d_37.png) --- 第三部分:整合到语言模型:从 LLaVA 到 MoLMO 🤖 上一节我们学习了如何获得高质量的图像表示。本节中,我们来看看如何将这些表示整合到大型语言模型中,从而构建能够理解图像并生成文本的模型。 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/11990dd9eb909dd20f1329c3d_39.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/11990dd9eb909dd20f1329c3d_41.png) 核心思想非常简单直接。我们有一个预训练好的语言模型(LLM)和一个预训练好的视觉编码器(如 CLIP 的图像编码器)。我们需要一种方式将视觉编码器输出的图像表示“喂给”语言模型。 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/11990dd9eb909dd20f1329c3d_43.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/11990dd9eb909dd20f1329c3d_45.png) 具体步骤如下: 1. 图像预处理与编码:将输入图像分割成块,通过视觉编码器(如 ViT)得到一系列图像特征向量。这些向量可以被称为“图像词元”。 2. 特征投影:由于视觉编码器输出的特征空间可能与语言模型的词嵌入空间不一致,我们通常学习一个投影层(可以是一个简单的线性层或小型神经网络),将图像特征映射到语言模型的嵌入维度。 3. 输入构造:将投影后的图像特征向量与文本的词元嵌入向量拼接在一起,形成一个混合序列。 4. 训练/微调:在包含(图像,指令,文本输出)的数据集上对模型进行训练。关键点:损失函数只计算在文本输出词元上,图像输入位置不产生损失。 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/11990dd9eb909dd20f1329c3d_47.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/11990dd9eb909dd20f1329c3d_49.png) 这个流程是 LLaVA 等模型的基础。一个更近期的例子是 AI2 的 MoLMO 模型,它遵循了相同的配方,并在各个环节进行了优化,例如: * 图像预处理:除了整图,还使用多尺度裁剪来获取不同区域的细节特征。 * 数据构建:组合使用了人工标注、模型生成等多种技术,创建了丰富的指令微调数据,包括描述、问答、指向(输出图像中的坐标)等任务。 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/11990dd9eb909dd20f1329c3d_51.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/11990dd9eb909dd20f1329c3d_52.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/11990dd9eb909dd20f1329c3d_54.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/11990dd9eb909dd20f1329c3d_55.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/11990dd9eb909dd20f1329c3d_57.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/11990dd9eb909dd20f1329c3d_59.png) 通过这种方式,我们可以得到一个既能理解图像内容,又能以自然语言进行对话和推理的多模态语言模型。 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/11990dd9eb909dd20f1329c3d_61.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/11990dd9eb909dd20f1329c3d_63.png) --- 总结 本节课中我们一起学习了多模态建模入门的关键思想: 1. 视觉 Transformer:将图像视为序列,使用 Transformer 架构进行处理。 2. CLIP:通过对比学习目标,利用图像-文本对数据学习对齐的跨模态表示。 3. 多模态 LLM 整合:将预训练的视觉编码器与语言模型结合,通过投影和微调,使 LLM 具备视觉理解能力。 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/11990dd9eb909dd20f1329c3d_65.png) 这些核心概念构成了当前许多先进多模态系统的基础。在下节课中,我们将探讨模型如何生成图像,以及处理交错文本和图像的更通用方法。 # 12:多模态建模 II 🖼️ ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/dfce5a8ca1ac1e3c0eac2bff4_1.png) 在本节课中,我们将继续学习多模态建模。上一讲我们主要关注了接收文本和图像并生成文本的模型。本节我们将探讨能够同时生成文本和图像的模型,并简要介绍用于生成高质量图像的扩散模型。 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/dfce5a8ca1ac1e3c0eac2bff4_3.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/dfce5a8ca1ac1e3c0eac2bff4_5.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/dfce5a8ca1ac1e3c0eac2bff4_7.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/dfce5a8ca1ac1e3c0eac2bff4_8.png) 生成模型概览 上一节我们介绍了CLIP等模型,它们擅长生成嵌入向量,但并未说明如何生成完整的图像。当我们想要生成内容时,就需要考虑生成模型。 在图像领域,我们不一定使用文本模型中常见的自回归模型,但可以尝试以某种方式应用它们。我们将讨论直接对像素建模,以及对这些图像“标记”进行建模的方法。随后,我们将介绍其他范式,例如扩散模型。 总的来说,生成模型的目标是学习一个数据分布 `P*(x)`。对于语言模型,数据是文本;对于图像生成,数据则是图像。我们训练一个模型来近似这个分布,然后通过采样等方式生成新的图像。 目前,本课程主要关注的是顶部的自回归模型范式。在图像领域,还有其他非常流行且性能优异的范式。我们将简要介绍变分自编码器,它将成为后续流程的一部分。同样,我们也会简要介绍生成对抗网络,它也将用于图像的标记化和去标记化。最后,我们将讨论扩散模型,它在生成高质量图像时非常重要。 自回归模型 我们已经在文本生成中广泛使用了自回归模型。对于文本,我们自然地将其分解为不同的标记。实际上,标记不一定只针对文本,我们可以想象将相同的方法用于文本以外的内容。 一种思路是:为图像定义一个一致的生成顺序,例如从左上角开始,按照光栅扫描顺序进行。那么,每一步或每个像素就像一个标记,我们可以用这种方式生成图像。在每个步骤,我们拥有迄今为止的图像,并希望生成下一个单元,即下一个像素。这或许是一种可行的方法。 变分自编码器 变分自编码器的方法看起来有很大不同。我们从真实图像(数据集中的图像)开始。我们需要学习两个模型:编码器和解码器。编码器学习将图像映射到某个连续的潜在向量。解码器则学习将潜在向量映射回图像。 如果我们能做到这一点,并且能将这些潜在向量保持在某种一致的空间中,那么我们就可以随机生成一个潜在向量,将其送入解码器,从而得到一个随机采样的图像。我们将简要讨论如何训练这种模型。 生成对抗网络 GANs的方法与上述两者略有不同。它采用了一种巧妙的对抗博弈思想。这里同样有两个模型:判别器和生成器。我们向判别器提供成对的示例:有时是真实图像,有时是生成器生成的假图像。判别器的目标是检测给定的图像是真实的还是虚假的。生成器的目标则是从一些随机噪声开始,生成逼真的图像。其基本思想是训练生成器,使其学会“欺骗”判别器,即生成判别器认为是真实的图像。 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/dfce5a8ca1ac1e3c0eac2bff4_10.png) 扩散模型 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/dfce5a8ca1ac1e3c0eac2bff4_12.png) 扩散模型的核心思想是一个加噪和去噪的过程。它包括前向过程和反向过程。前向过程从真实数据开始,逐步添加噪声,最终得到完全随机的噪声(例如从高斯分布中采样)。扩散模型的目标是学习逆转这个过程。它在每个时间步学习如何对图像进行轻微的去噪。如果模型能很好地做到这一点,就可以多次应用这个过程,最终生成真实的图像。 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/dfce5a8ca1ac1e3c0eac2bff4_14.png) 基于这些概述,你可以看到这些范式至少在乍看之下与自回归模型有很大不同。 构建多模态语言模型 现在,让我们思考一个具体目标:构建一个既能生成文本又能生成图像的语言模型。这看起来会是什么样子? 我们需要一种将图像标记化的方法,即将图像转换为不同的标记。此外,我们还需要一个去标记器。假设模型像普通语言模型一样产生了一些标记,我们如何将这些标记映射成视觉图像?我们的目标是实现这一点,同时不改变Transformer本身,只是让这个语言模型能够产生文本标记或图像标记。 这里的关键似乎是:标记器应该做什么? 首先,我们将尝试之前提到的方法:如果我们不一定使用标记,而是尝试直接生成单个像素呢?这样,将输出转换为图像的问题就不复存在了,因为你已经拥有了像素,也就有了图像。但这不是最终的解决方案,我们还将讨论另一种创建良好标记器和去标记器的方法。 策略一:将图像视为像素序列 第一种策略是基本上将图像视为像素序列,然后学习一个基于像素序列的语言模型。具体来说,我们取一张图像,将其高度、宽度和通道维度展平成一个长序列。序列的每个元素是1到256之间的一个数字,对应RGB像素值。 然后,我们可以获取大量图像,将它们转换为这些序列,形成一个数据集。接着应用我们讨论过的相同训练过程(最大似然估计),训练这个语言模型:给定迄今为止的像素序列,预测下一个像素。希望这个过程现在看起来很熟悉,这本质上就是我们用文本模型所做的。 事实证明,人们已经尝试过这种方法。这里有一个2016年的例子,他们当时没有使用Transformer,而是使用了RNN。你可以看到一些结果:他们按照光栅扫描顺序,用图像的一部分提示模型,然后让它补全。原始图像可能是一个人在游泳之类的。模型生成了不同的补全结果。同样,对于动物图像,它也生成了不同的补全结果。 然后,你可以尝试改进这个方法,比如使用不同的架构或在更多数据上训练。这里的基本思想仍然是进行自回归建模和图像生成。 你也可以尝试使用Transformer,图像看起来可能会好一些。但当你思考这个问题时,一个关键的挑战是序列长度。假设我们要生成一个1000x1000、3个通道的图像,那么基本上就有300万个标记,对于一张图像来说序列非常长。想象一下,如果一个智能体在其历史记录中有多张图像,那么很快就会有1000万甚至1亿个标记。因此,早期的研究通常致力于开发内存高效的注意力机制,而不是使用标准注意力,并尝试修改Transformer架构。 总的来说,这种方法会遇到序列过长的问题,这也会减慢生成速度。生成300万个标记来得到一张图像可能需要很长时间。因此,问题是:能否仍然尝试使用自回归模型,但不一定预测单个像素值,而是预测图像的某种更压缩的表示?这就是我们接下来要讨论的内容。 策略二:学习更好的图像标记 如果我们把像素看作标记,那么问题是:能否为生成图像找到更好的标记概念?为此,我们需要使用一些巧妙的想法。我将介绍一种称为“向量量化变分自编码器”的思想。 我们再次希望学习一种能紧凑表示图像的标记器,这样我们就不需要生成300万个标记来得到一张图像。标记是离散的项,我们不想要连续的东西,因为这样我们就可以像生成文本一样使用语言模型。我们可能希望的是:假设我们引入了8000个不同的标记,并且希望一张图像只有1000个标记长。这与300万个标记相比有很大不同:词汇量更大(从256变为8000),但序列长度希望会短得多。那么问题是如何实现这一点,甚至如何思考这个问题。 有一个很酷的方法叫做向量量化VAE,它使用了变分自编码器的思想。基本思路是:我们想学习一些离散的代码,这些代码足以让解码器生成图像、重建图像。如果我们能实现这一点,即将图像编码为离散代码,然后从中完美重建图像,那么这基本上就满足了我们想要的东西:一种紧凑的图像表示方法。 你可以看到,VAE框架是 potentially 合适的。但棘手的是这些标记是离散的。为了解释这个想法,我们需要更多地了解VAE。让我先简要介绍一下标准的VAE,它有一个连续的潜在变量Z。 标准VAE假设潜在变量Z是连续的,并假设数据是按以下方式生成的:首先,我们采样一个潜在变量;然后,通过解码器将其转换为数据;编码器则是将数据x映射到潜在变量的东西。 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/dfce5a8ca1ac1e3c0eac2bff4_16.png) 我们引入一些符号:解码器是 `P_θ`,编码器是 `Q_φ`(这些是不同的参数),`P_z` 通常称为先验分布。 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/dfce5a8ca1ac1e3c0eac2bff4_18.png) 训练VAE的方法是平衡两个不同的目标。首先,我们希望模型在给定某个潜在变量时能够很好地重建图像。为此,可以使用重建损失,它表示解码器在给定潜在变量时应为图像分配高概率。这基本上是说解码器应该学习以不同潜在变量为条件的图像分布。 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/dfce5a8ca1ac1e3c0eac2bff4_20.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/dfce5a8ca1ac1e3c0eac2bff4_21.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/dfce5a8ca1ac1e3c0eac2bff4_23.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/dfce5a8ca1ac1e3c0eac2bff4_25.png) 其次,正如我一直提到的,我们不希望潜在变量是任意的,我们希望它们表现良好,以便可以轻松地从模型中生成样本。为此,可以添加一个正则化项。它基本上是说编码器产生的潜在变量分布应该接近某个标准高斯分布。我们希望学习这个编码器和解码器,并使编码器的输出接近标准高斯分布,但它们应该包含一些信息,解码器应该能够利用这些信息来重建输入。 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/dfce5a8ca1ac1e3c0eac2bff4_27.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/dfce5a8ca1ac1e3c0eac2bff4_29.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/dfce5a8ca1ac1e3c0eac2bff4_30.png) 最终的损失函数由这两项组成:正则化项和重建损失项。它涉及到从编码器生成潜在变量,然后尝试用解码器重建它们。我们将在课程结束时看还剩多少时间,也许我会为未来的学期补充更详细的推导。但希望我已经给出了这些项来源的足够理解:一个重建损失和一个正则化器。 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/dfce5a8ca1ac1e3c0eac2bff4_32.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/dfce5a8ca1ac1e3c0eac2bff4_33.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/dfce5a8ca1ac1e3c0eac2bff4_35.png) 这就是训练VAE的快速方法。你会有一些图像的数据分布,然后编写代码来计算这个损失并进行优化。如果你这样做(这是原始VAE论文中的内容),你会得到一些有趣的结果,如果你以前没见过,会觉得特别酷。这基本上展示了潜在空间中学习到的一些结构。例如,你得到数字“7”对应的向量和数字“0”对应的向量,然后可以在它们之间进行简单的线性插值,得到一系列向量。然后将每个向量通过解码器,实际生成图像。很酷的是,它最终看起来像是不同数字之间的插值。同样,你也可以对人脸进行这种操作,在不同类型的面部表情之间进行插值。 这展示了:首先,我们能够从向量重建图像,因为每个图像都始于一个向量;其次,它表明潜在空间在某种程度上是有结构的。你还可以直接从高斯分布中采样潜在向量,然后传递给解码器,从而得到随机采样的数字或人脸。 这就是标准VAE的内容。在当时,它引入了一种进行生成建模的新方法,并引发了大量关于改进这些模型的研究。在很长一段时间里,这些模型基本上是生成图像的最先进技术,并且有很多与变分自编码器相关的后续工作。 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/dfce5a8ca1ac1e3c0eac2bff4_37.png) 其中之一是,人们开始思考:如果能让这个潜在向量基本上是离散的而不是连续的,会怎样?这实际上对我们的用例很有用。这些标准VAE为我们提供了一种使用连续向量编码和解码图像的方法。但如果我们可以用离散的方式表示事物,那么我们基本上就有了这种标记器和去标记器。你可以应用VAE编码器来标记化图像。同样,如果你产生了一些这样的VAE标记,你可以运行解码器将其转换为图像。这就是基本思想。 为此开发的一种技术称为VQ-VAE或向量量化VAE。这个想法很酷:我们再次有编码器和解码器。我们从编码器产生连续向量开始。现在我们实际上想把它变成离散的东西。所以我们有一个叫做量化器的东西。量化器在高层面上会取这个连续向量并将其转换为一个离散值。 具体做法是使用一个码本。码本中有一定数量的连续向量。假设这里有八个不同的向量。基本思想是进行查找:给定编码器产生的向量,找到码本中最接近的向量(就L2距离而言)。然后,通过这个查找,你可以将编码器值与码本中的离散索引关联起来。例如,假设最近的向量是这里的第五个,那么我们基本上说我们将图像的这一部分编码为数字5。 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/dfce5a8ca1ac1e3c0eac2bff4_39.png) 然后,你的解码器将接收与这个数字对应的向量(因为你可以将输出视为一个数字,然后查找该向量并将其提供给解码器),并尝试生成一些图像。这就是VQ-VAE的基本设置。接下来的问题是:如何训练它?可能会出现哪些问题? ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/dfce5a8ca1ac1e3c0eac2bff4_41.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/dfce5a8ca1ac1e3c0eac2bff4_43.png) 实际上,在讨论这些问题之前,有没有人对这个设置中可能出错的地方或可能面临的挑战有什么想法? 首先,VQ-VAE使用离散化标记,那么它的特异性会不会也非常有限?是的,这是一个问题。图像基本上是,比如说,1000x1000,每个值有256种可能,所以可能性空间是256的(1000*1000)次方,这是一个巨大的空间。而可能的离散序列空间(即可能的码本序列)要小得多。因此,你基本上不可避免地会有一些损失,一种有损压缩。是的,这是一个问题。 另一个棘手的问题是,我们在这里进行的查找是一种离散操作,不是连续可微的操作。因此,在尝试反向传播时,必须找到处理梯度的方法。具体做法是这样的:他们有一个包含三个不同项的损失函数。首先,是重建项,这个可能不太令人惊讶,它基本上是说给定你的潜在变量,要重建图像。第二项是说应该更新码本嵌入。你可能从一些随机嵌入开始,可能希望让它们更接近模型实际产生的向量。另一方面,你希望编码器实际产生接近码本中向量的向量。特别是,如果你想选择一个码本向量,你基本上需要“承诺”选择其中一个。这里的技巧是,在每个项中,你只能对一个东西应用梯度,否则每一项都会通过将它们设置为彼此相等来最小化。此外,你必须处理无法通过这个选择操作进行反向传播的问题。 他们在这里使用了所谓的“直通梯度估计器”。这基本上是一种非常朴素的梯度估计器。同样,挑战在于我们无法通过这个操作进行反向传播,因此我们没有梯度可以用于链式法则。直通估计器说,只需从这里获取梯度,复制它,并在反向传播中使用它。你可以看到这里可能有一些启发式方法,然后有这个梯度估计器,所以可能不完美,但 potentially 是有效的。 固有的限制是之前提到的那个:即使你训练得很好,你最终也是在尝试用一个相当少量的离散数字来表示所有图像空间中的一张图像。 在实践中,你实际上可以做的是拥有这种结构化的表示。这里我们说,我们将这些数字中的每一个与二维网格上的每个位置关联起来。因此,表示图像时,你可以将其视为一个序列,但也可以将其视为排列在二维网格中的一些离散值。然后,你可以获取它们对应的向量,并为解码器应用某种图像架构。这里他们使用了卷积神经网络,但总体思想并不一定依赖于你用于编码器或解码器的架构。 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/dfce5a8ca1ac1e3c0eac2bff4_45.png) 这是一些实际论文中的图片。这里有一些例子。我整理了一个代码示例,这是一个非常简单的例子,但这里有一个实际上由OpenAI训练并用于他们的DALL-E系统的例子。也许令人惊讶的是,这在某种程度上是有效的。你可以看到这里有一个编码器,一个解码器,以及生成的中间标记。它基本上用这些离散数字表示这只企鹅,然后能够在一定程度上重建图像,虽然有点噪点。 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/dfce5a8ca1ac1e3c0eac2bff4_47.png) 这个特定的方法实际上仍然在一些模型中使用。这是基于VQ-VAE的标记器和去标记器的一个例子。我认为他们使用了稍微不同的实现细节,比如可能使用了不同的梯度估计器等,但总体思想是我展示的那个。 然后,我还在notebook中整理了这个例子,它使用的是MNIST数据集。这里有不同的数字,然后你可以查看损失。重建损失最终相当小。模型实际上并没有使用全部128个码本条目,可能是因为这些图像相当简单。 你可以看到一个例子,看起来它学会了在所有这些图像中,这部分只是空白。所以它可以用这个单一的标记来表示它。在这里,你看到一些不同的变化和标记的不同用法。然后,很酷的是解码器能够从这些信息中重建图像。 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/dfce5a8ca1ac1e3c0eac2bff4_49.png) 现在,你可能想象,如果你有一张信息密集的图像,比如我现在的笔记本电脑截图,那么你的码本也必须编码关于截图中所有文本的信息。因此,可能这种方法对于这种信息密集的图像不起作用,但至少对于这些简单的图像是有效的。 你可以在这里查看其他内容,展示了一些不同的变化。在继续之前,让我在这里暂停一下,看看有没有问题。 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/dfce5a8ca1ac1e3c0eac2bff4_51.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/dfce5a8ca1ac1e3c0eac2bff4_52.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/dfce5a8ca1ac1e3c0eac2bff4_54.png) 问: 为什么要把图像离散化? 答: 我们稍后会看到,一旦你将图像离散化,我们就可以基本上使用语言模型来预测这些标记。然后,一旦预测了标记,你就可以运行解码器,现在你就有了生成的图像。 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/dfce5a8ca1ac1e3c0eac2bff4_56.png) 问: 这个过程(查看生成器生成的图像是否与原始图像相似)是为了帮助我们理解潜在空间和学习的解码器是否足够好,还是为了迭代改进? 答: 是的。这里我们想看看模型是否有效。你可以想象一种情况,模型产生随机标记,然后解码器产生随机图像。这表明它提出的编码要么不足以重建图像,要么意味着你学习的解码器不是很好。所以,是的,其中一个指标基本上是解码器能否在给定图像编码的情况下重建图像。这用于诊断VAE的训练。你通常还会检查其他方面,比如样本质量。编码器可能学会将东西放在潜在空间的任何地方。如果你从正态分布采样并尝试解码,可能会得到一些随机图像。理想情况下,你希望能够重建图像,并且希望有一个表现良好的潜在空间,以便轻松采样图像。你可以检查不同的问题,例如潜在空间是否表现良好,可以尝试采样并查看样本质量。你还可以查看KL散度是很大还是很校此外,你可以查看重建质量。你可能需要查看多个指标来确定是什么导致了糟糕的重建。 使用GAN进行标记化 事实证明,你不一定必须用VAE来做这件事,你也可以用GAN来尝试做同样的事情。也许我不会花太多时间在这上面,但它基本上试图用GAN实现同样的目标。 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/dfce5a8ca1ac1e3c0eac2bff4_58.png) 这篇论文有趣的地方在于,它被称为VQ-GAN。有趣的是,他们实际上在得到的标记化图像上训练了这个自回归模型,就像Sililio之前说的那样。这里他们再次有了这种标记化和去标记化图像的方法。然后,他们取一个非常大的图像数据集,将它们转换为标记。在这里,你可以看到,你可以直接学习这个自回归语言模型来生成标记。然后,如果你生成了其中一个标记序列,你就可以尝试生成图像。 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/dfce5a8ca1ac1e3c0eac2bff4_60.png) 如果你以前没见过GAN,那么这是基本的设置。这里有一个生成器和一个编码器(对于VQ-GAN),然后有一个判别器。这可能不足以全面介绍GAN,但这是基本的设置和基本的损失函数。你希望判别器为真实的东西(即x)分配1,为假的东西(即x_hat)分配0。这个x_hat是通过首先编码图像然后生成它来产生的。这就是基本的设置。 我想强调这篇论文的原因是,在不同的论文中,人们使用VQ-GAN而不是VQ-VAE,而且他们实际上还进行了第二步:标记化大量图像,然后训练一个自回归Transformer。这里再次强调,记住我们开始时需要用300万个标记序列来表示一张图像。现在他们实际上训练了一个模型,其中每张图像只有1024个标记。 然后这里有一些例子。他们在这里做的是ImageNet,这些通常是单个物体的图片。他们能够从中得到一些还不错的样本。 就像我说的,通常在这些论文中,他们会做不同的变化和不同的任务。所以你可以,比如说,从这里开始,然后把它变成一个实际的场景,等等。 但我想回到我们最初的目标:尝试将这个整合到一个语言模型中。实际上,我们已经看到了如何做到这一点,因为我们现在有了这个标记器和去标记器。接下来,我们可以简单地将这些标记添加到语言模型的词汇表中,并且可以在文本和图像标记上训练或微调一个模型。 这方面的一个例子是Meta在2024年发布的一个名为Chameleon的模型。你可以看到这里基本上有我们学过的不同部分。他们有某种图像标记器,结果是VQ-GAN。同样,他们有这个图像去标记器。然后他们做的是,基本上组装了这种大规模的预训练数据,其中不仅包含文本,还包含图像,可能包括大量网络数据和手动整理的数据。他们就这样在10万亿这样的标记数据上训练了一个模型。同样,你可以进行微调,你可以有一个提示,比如“我可以用这个烤什么?”和一些图片,然后输出是“这是香蕉面包的食谱”,也许你还会生成一张香蕉面包的图片。这里的想法是,我们真的 potentially 实现了使用语言模型生成文本和图像的想法。 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/dfce5a8ca1ac1e3c0eac2bff4_62.png) 这里有一个例子,他们的响应是交错的。你可以看到在响应中,它生成了一些文本,生成了一张图像,然后进行了解释,又生 # 第13讲:评估与基准测试 📊 在本节课中,我们将学习如何评估自然语言处理系统的性能,以及“性能好”具体意味着什么。我们将探讨常见的评估基准、评估指标,并介绍一些用于分析评估结果的基本统计方法。 --- 概述 到目前为止,我们已经讨论了模型的学习(如预训练和微调)和推理(如使用采样或贪婪解码生成输出)。今天,我们将重点讨论如何评估模型的性能,以及如何解读评估结果。这对于比较不同系统、为模型部署提供依据,或在学术研究中严谨地得出结论都至关重要。 --- 为什么需要评估? 评估模型性能有多种原因: * 你可能需要在两个不同的系统之间做出选择(例如,在课程项目或工作中)。 * 你可能需要为部署一个成本高昂且影响用户体验的模型提供理由。 * 在进行实证研究并希望发表论文时,你需要一个严谨的框架来评估系统并解释结果。 --- 简单的评估方法:损失函数 一种简单的评估方法是使用损失函数。在训练语言模型时,我们通常使用负对数概率作为损失(即最大似然估计)。 具体做法是:将数据集划分为训练集、验证集和测试集,然后分别计算模型在这些子集上的损失。 * 训练损失:可以反映训练过程的情况,用于比较不同的超参数设置。 * 验证损失:衡量模型的泛化能力,有助于检测过拟合。 * 测试损失:在最终评估时,可以基于此比较不同方法。 然而,损失函数并不总能完美反映我们在实际任务中关心的性能。 --- 损失函数与实际任务指标的差异 考虑一个例子:我们声称 Llama 33B 模型比 Llama 13B 模型“更好”。如果仅看训练损失,33B 模型确实更低,似乎更好。 但如果我们真正关心的是某个具体的下游任务(例如问答),并测量模型在该任务上的准确率,结果可能并非如此。在某些情况下,两个模型的性能可能不相上下。 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/059c3df1434edb32c01743decaaa385d_1.png) 这表明,损失函数的降低并不总是等同于特定任务性能的提升。 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/059c3df1434edb32c01743decaaa385d_3.png) 一个更显著的例子来自 DeepMind 的 AlphaCode 论文。在代码生成任务中,模型的验证损失在训练后期开始上升(出现过拟合迹象),但模型实际解决问题的成功率却持续上升。这是因为模型可能将概率质量分配给了与验证集不同的解决方案,但它仍然具备解决问题的能力。 因此,我们需要将我们真正关心的任务指标纳入评估体系。 --- 评估框架的正式定义 我们可以将评估过程形式化如下: ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/059c3df1434edb32c01743decaaa385d_5.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/059c3df1434edb32c01743decaaa385d_7.png) 1. 我们有一个模型(如语言模型)。 2. 给定一个输入。 3. 运行某个推理算法(如采样、贪婪解码、束搜索)来生成输出。 4. 使用一个任务指标 M 来评估输出。该指标通常将模型的预测 `y_hat` 与目标答案(或测试用例等额外信息)进行比较。 我们可以将模型需要处理的各种任务视为一个任务分布。我们的目标是让模型在所有可能任务上具有较高的期望任务性能。 然而,在实践中,我们并没有这个“神奇”的任务分布。因此,我们通常使用有限数量的评估数据集,每个数据集及其对应的指标构成一个评估基准。 --- 常见的评估基准示例 对于通用语言模型,通常会在一系列基准上进行评估,以展示其广泛的能力。以下是几个常见基准: MMLU(大规模多任务语言理解) * 内容:涵盖57个不同学科的多项选择题(如微观经济学、概念物理)。 * 评估方式:模型生成答案(如A/B/C/D),指标是精确匹配,即生成的答案是否与数据集中的答案相同。 * 思考:该基准主要评估学术知识,但“通用知识”的定义可能更广,且知识本身并非静态。 HumanEval(代码生成) * 内容:要求模型根据问题描述和函数签名,生成完整的Python代码来解决编程问题。 * 评估方式:执行生成的代码,检查是否通过所有预定义的测试用例。常用指标是 Pass@K,即生成K个解决方案,看是否有任何一个能通过所有测试。 * 思考:该指标只关心是否通过测试,不评估代码效率、可读性或是否代表了真实的软件开发场景。 GSM8K(小学数学题) * 内容:小学数学文字题。 * 评估方式:通常让模型生成思维链和最终答案。指标是检查最终答案是否与数据集中的答案匹配。 * 思考:优点是评估简单直接;缺点是它不评估推理步骤的正确性,一个答案正确但推理错误的结果也会被计为正确。 --- 优秀基准应具备的属性 在设计或选择一个基准时,需要考虑以下属性: 广度与多样性 基准应涵盖与你的用例相关的所有输入-输出功能。对于通用模型,这意味着需要评估各种类型的用户提示(如技术问题、日常生活、社交互动等)。 深度与难度 基准需要能够区分不同性能水平的模型。随着模型能力提升,许多基准会变得饱和(即所有先进模型都能取得接近满分),从而失去区分度。因此,需要不断开发更具挑战性的基准。 任务效用 有时,基准直接代表你想要解决的实际下游任务。有时,它只是某种通用能力(如数学推理)的代理。需要明确基准与最终目标之间的关系。 鲁棒性 模型在基准上的高性能,不应仅仅是因为“记住”了特定形式的问题或利用了评估指标的漏洞。 * 输入变化:对输入进行微小修改(如改写问题),模型性能不应大幅下降。 * 指标漏洞:例如,在HumanEval中,生成的代码可能恰好通过给定的少数测试用例,但并未真正解决问题。为此,人们开发了 HumanEval+ 等改进版本,添加了更多测试用例。 数据污染 由于模型在大量互联网数据上训练,而许多基准问题可能已在网上(如论坛、教程)出现过,这可能导致模型通过记忆而非泛化来获得高分。评估时需要注意区分,并尝试使用训练截止日期之后创建的新数据来构建基准。 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/059c3df1434edb32c01743decaaa385d_9.png) 评估效率 * 人工评估:准确但昂贵、缓慢,且可能主观。 * 自动指标:快速、廉价,但可能过于简化(如只检查最终答案)。 * 语言模型即评委(LM-as-a-Judge):使用另一个(通常更强的)语言模型来评估输出质量,例如比较两个输出的优劣并计算胜率。这种方法效率较高,但评估质量依赖于评委模型的能力,且可能存在位置偏差、长度偏好等问题。 * 众包平台(如Chatbot Arena):让真实用户比较不同模型的输出。这更接近真实用户体验,但同样可能受输出格式、长度等表面因素影响。 --- ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/059c3df1434edb32c01743decaaa385d_11.png) 评估结果的统计分析 当我们获得一个基准分数(例如,准确率为75%)时,如何解读?我们需要考虑评估过程中的随机性。 设置与标准误 我们可以将评估数据集视为从某个“问题总体”中抽取的样本(大小为N)。模型在每个问题上的得分(如0或1)的样本均值 `S_bar` 是我们观察到的分数。 我们关心的是模型在无限多问题上的真实平均性能 `μ`。 根据中心极限定理,`S_bar` 的分布会趋近于正态分布。其波动大小可以用标准误来度量,公式为: `SE = sqrt(Var / N)` 对于二分类任务(得分0或1),标准误的估计公式简化为: `SE = sqrt( (S_bar * (1 - S_bar)) / N )` 置信区间 利用标准误,我们可以构建置信区间(例如95%置信区间)。它给出了真实均值 `μ` 可能落入的一个范围。区间越宽,说明我们的估计越不确定。 假设检验:比较两个模型 当我们比较两个模型A和B的分数时,我们想知道观察到的差异是否具有统计显著性。 我们可以设立零假设:两个模型的真实性能相同(差异为0)。 然后计算差异的Z分数:`Z = (S_bar_A - S_bar_B) / SE_diff` P值表示在零假设成立的前提下,观察到当前差异(或更极端差异)的概率。如果P值小于一个阈值(如0.05),我们就有证据拒绝零假设,认为两个模型的性能存在显著差异。 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/059c3df1434edb32c01743decaaa385d_13.png) 注意独立性假设 上述分析通常假设数据点之间是独立的。但在某些基准中(如MGSM,同一数学问题被翻译成多种语言),问题之间存在聚类结构。这时需要使用更复杂的方法(如聚类标准误)来准确估计不确定性,否则会低估标准误。 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/059c3df1434edb32c01743decaaa385d_15.png) --- 总结 在本节课中,我们一起学习了: 1. 评估的重要性:为模型选择、部署和研究提供依据。 2. 评估框架:从损失函数过渡到以任务为中心的指标。 3. 常见基准:如MMLU、HumanEval、GSM8K,并分析了它们的特点和局限性。 4. 优秀基准的属性:包括广度、深度、效用、鲁棒性、避免数据污染和评估效率。 5. 统计基础:如何通过计算标准误、置信区间和进行假设检验,来严谨地解读基准分数,并得出模型间性能差异是否显著的结论。 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/059c3df1434edb32c01743decaaa385d_17.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/059c3df1434edb32c01743decaaa385d_19.png) 评估是机器学习中至关重要且复杂的一环。理解基准的构成、指标的局限以及统计推断的方法,将帮助你在研究和实践中做出更可靠、更有意义的判断。 # 14:研究技能与实验设计 🧪 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/ea62bb4a0bfcbfbd2cd835f0956c6873_1.png) 在本节课中,我们将学习研究技能与实验设计。课程内容混合了非技术性的研究思路与一些技术性的统计方法,这些方法对于设计实验(例如确定所需数据量)和评估数据(例如计算标注者间一致性)至关重要。 统计基础回顾 📊 上一讲我们介绍了基本的统计框架,用于从数据中得出结论并判断其统计显著性。本节中,我们首先回顾这些概念。 假设我们有一个评估数据集,其中包含 `n` 个独立抽取的问题。模型在每个问题上的得分(例如,0或1)可以视为从某个总体分布中抽取的样本。我们观测到的平均得分是样本均值,而模型在所有可能问题上的真实平均得分是总体均值 `μ`。 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/ea62bb4a0bfcbfbd2cd835f0956c6873_3.png) 根据大数定律,随着样本量 `n` 的增加,样本均值会收敛于总体均值。但在实践中,我们使用有限的 `n`,这会引入随机误差。我们可以将样本均值视为服从一个高斯(正态)分布,该分布有其均值和方差。 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/ea62bb4a0bfcbfbd2cd835f0956c6873_5.png) 我们可以估计标准误,它衡量了样本均值的变异程度。对于得分为0或1的二项分布情况,标准误的计算公式可以简化为: ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/ea62bb4a0bfcbfbd2cd835f0956c6873_7.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/ea62bb4a0bfcbfbd2cd835f0956c6873_9.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/ea62bb4a0bfcbfbd2cd835f0956c6873_10.png) 
                                                                    

标准误 = sqrt( (p * (1 - p)) / n )

其中 `p` 是观测到的平均得分。 有了标准误,我们可以构建置信区间。例如,对于一个95%的置信区间,我们会在样本均值两侧各扩展约1.96倍的标准误。在结果报告中加入置信区间,有助于直观地理解测量值的变异程度。例如,在一个小数据集上,标准误可能较大,表明结果的不确定性更高。 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/ea62bb4a0bfcbfbd2cd835f0956c6873_12.png) 处理数据依赖性 🔗 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/ea62bb4a0bfcbfbd2cd835f0956c6873_14.png) 上一节我们假设数据点是独立同分布的。但在某些数据集中,数据点之间存在依赖关系。本节中我们来看看如何处理这种情况。 以MGSM数据集为例,它包含不同语言版本的同一数学问题,因此不同问题之间并非完全独立。为了更准确地估计标准误,我们需要考虑这种聚类结构。 此时,标准误的估计公式会考虑聚类内的相关性。调整后的标准误通常会比忽略依赖关系时计算出的值更高,这更真实地反映了数据的噪声水平。在比较模型时,如果置信区间有较大重叠,我们就不能轻易得出一个模型显著优于另一个的结论。 假设检验 🧐 仅仅观察置信区间可能不足以做出明确判断。本节中,我们将学习如何使用假设检验来基于数据做出原则性决策。 我们感兴趣的问题是:两个模型的性能是否存在真实差异?我们可以设立: * 零假设 (H₀):两个模型的真实平均得分相同(差异为0)。 * 备择假设 (H₁):两个模型的真实平均得分不同(差异不为0)。 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/ea62bb4a0bfcbfbd2cd835f0956c6873_16.png) 我们可以计算两个模型得分差异的均值及其标准误,然后计算Z分数: 

Z = (均值差异) / (差异的标准误)

Z分数衡量了观测到的差异相对于其变异的大小。 接着,我们可以计算p值:在零假设成立(即模型无差异)的前提下,观测到当前差异(或更极端差异)的概率。如果p值非常小(例如小于0.05),我们就有足够的证据拒绝零假设,认为差异是统计显著的。反之,如果p值较大,则观测到的差异很可能源于随机噪声。 利用配对数据 📈 如果我们有两个模型在完全相同的一组问题上的得分,就可以利用这种配对关系进行更精确的比较。本节中我们来看看配对检验的优势。 配对检验考虑了问题的难度差异:有些问题对所有模型都容易,有些则都难。通过计算每个问题上两个模型得分的差异,然后分析这些差异的均值和标准误,我们可以得到更准确的统计检验结果。这种方法通常比独立样本检验(即忽略问题配对关系)的统计功效更高,更容易检测出真实的差异。 实验设计概述 🧬 前面的部分侧重于如何解释已有的实验结果。从本节开始,我们将探讨如何从头开始设计和实施自己的研究项目,这通常遵循科学方法的框架。 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/ea62bb4a0bfcbfbd2cd835f0956c6873_18.png) 科学方法通常包含以下步骤,我们将结合NLP研究来理解: 1. 观察与提出问题 2. 调研相关领域 3. 提出假设 4. 通过实验进行检验 5. 分析数据 6. 得出结论并交流 提出研究问题与假设 💡 研究始于一个好的问题。寻找研究思路通常有两种途径: * 应用驱动:旨在解决特定实际问题或提升现有系统性能(例如,让语言模型在手机上运行更快)。 * 好奇心驱动:旨在探索和理解基本现象(例如,所有语言对语言建模来说难度是否相同?)。 也可以从现有研究(自底向上)或从基本原理/新想法(自顶向下)出发。两种方式各有优劣:自底向上更稳妥但可能限制创新;自顶向下可能产生突破但也可能脱离实际。 确定研究领域后,需要将其转化为可检验的假设。假设应具体、可证伪。例如,将“所有语言是否同样难?”转化为“不同语言的语言建模难度存在显著差异”。 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/ea62bb4a0bfcbfbd2cd835f0956c6873_20.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/ea62bb4a0bfcbfbd2cd835f0956c6873_22.png) 文献调研与数据准备 📚 在深入研究之前,进行文献调研至关重要。以下是查找论文的一些途径: * 学术会议/期刊:ACL、EMNLP、NAACL、NeurIPS、ICLR、ICML等。 * 预印本平台:arXiv。 * 学术搜索引擎:Google Scholar,可用于查找引用某篇论文的后续工作,或通过论文的参考文献追溯奠基性研究。 阅读论文时,可采用速读(了解核心主张)和精读(深入理解关键论文)相结合的方式。文献调研能避免重复工作并拓宽视野,但也需注意不要被现有思路过度束缚。 接下来是数据准备。对于实验,我们需要训练数据和评估数据。 * 使用现有数据集:Hugging Face Datasets、Papers with Code等平台提供了丰富资源。 * 创建新数据集:如果现有数据不适用,则需自行创建和标注。 确定数据规模与质量评估 📏 一个关键问题是:需要标注多少数据?这可以通过功效分析来估算。我们需要设定: * 显著性水平:错误拒绝零假设的概率(通常设为0.05)。 * 统计功效:当真实差异存在时,正确检测到它的概率(通常希望高于0.8)。 * 最小可检测效应:你关心并能检测到的最小性能差异。 利用一个先导实验的小规模数据,可以估计模型得分的方差,进而通过公式估算出所需的总样本量。更多的数据通常能降低方差,提高检验的灵敏度。 对于训练数据,通常“越多越好”,但也可以通过实验观察性能随数据量增加的收益曲线。 数据标注与一致性评估 ✍️ 如果需要人工标注,请遵循以下步骤: 1. 制定清晰明确的标注指南,定义要测量的内容。 2. 进行小规模试标注,根据反馈完善指南。 3. 正式标注,可自行标注、请同事帮忙或雇佣标注员(需注意伦理审查,如IRB)。 4. 评估标注质量,计算标注者间一致性。 评估一致性时,常用科恩卡帕系数。它衡量了标注者之间超出随机预期的一致程度。计算公式为: 

κ = (P_o - P_e) / (1 - P_e)

其中 `P_o` 是观测到的一致比例,`P_e` 是随机情况下预期的一致比例。κ值越高,表明标注结果越可靠。 总结 📝 ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/ea62bb4a0bfcbfbd2cd835f0956c6873_24.png) ![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/cmu-11711-adv-nlp/img/ea62bb4a0bfcbfbd2cd835f0956c6873_26.png) 本节课中我们一起学习了研究技能与实验设计的核心内容。我们回顾了用于结果分析的统计基础,包括标准误、置信区间和假设检验。接着,我们探讨了完整的研究流程:从提出可检验的假设,到进行文献调研和数据准备,再到通过功效分析确定实验规模,并确保数据标注的质量。掌握这些技能将帮助你更严谨地设计、执行和评估NLP研究项目。 # 15:强化学习基础 🎮 在本节课中,我们将学习强化学习的基础知识。强化学习是机器学习的一个分支,它关注智能体如何通过与环境的交互来学习,以最大化某种奖励信号。虽然本次课程内容与自然语言处理没有直接关系,但这些基础概念对于理解后续如何将强化学习应用于语言模型至关重要。 课程概述 我们首先将回顾之前主要使用的监督微调方法及其局限性。然后,我们将介绍强化学习的基本框架,包括马尔可夫决策过程、策略、奖励等核心概念。接着,我们会深入探讨一类称为“策略梯度”的强化学习算法,了解其基本原理、实现方式以及如何通过引入折扣因子、基线等技巧来改进其稳定性和效率。最后,我们会简要展望如何将这些工具应用于语言模型。 --- 监督微调的回顾与局限性 在之前的课程中,我们主要使用基于监督微调的学习算法。具体流程是:从一个经过预训练的基础模型开始,然后使用包含指令、输入和输出的任务数据对模型进行微调,最终得到一个能够遵循指令的模型。 我们使用的算法是最大似然估计。其目标是,给定输入 `X` 和输出 `Y` 的数据集,最大化模型预测输出序列中下一个词的概率。其目标函数可以表示为: `maximize Σ log P(y_t | x, y_ 
                                                                  
    
                                                                    

这种奖励形式的优点是可靠、易于实现。它也可以扩展到其他任务,例如数学解题,只需检查最终答案是否与标准答案匹配。然而,其缺点是需要事先知道正确答案,并且模型可能生成过程错误但答案巧合正确的“投机”解。

我们使用单步MDP组相对策略优化 算法。其核心包括两个部分:

1. 组相对优势
对于每个输入提示,我们生成K个不同的输出(例如K=8),形成一个“组”。优势值通过组内奖励的相对比较来计算:



  • 计算组内奖励的均值 mean_reward
  • 每个输出的优势值为其奖励减去组内均值:advantage = reward - mean_reward
  • 有时还会用组内奖励的标准差进行归一化。

这种方法无需学习一个独立的价值函数网络,节省了计算成本。其思想是:在组内提升高奖励输出的概率,降低低奖励输出的概率。

2. PPO损失
我们使用近端策略优化损失,它包含两个关键思想:



  • 重要性采样比率:使用旧策略模型和新策略模型生成概率的比率,来衡量策略更新的幅度。
    ratio = π_new(y|x) / π_old(y|x)



  • 裁剪:对比率进行裁剪,防止单次更新步幅过大,保持训练稳定性。
    最终损失函数结合了裁剪后的比率和计算出的优势值。



以下是训练循环的核心步骤:

  1. 生成:用当前模型为一批输入提示生成多个输出。
  2. 评估:计算每个输出的奖励和组相对优势。
  3. 更新:固定旧策略模型,计算新策略的概率,利用PPO损失进行多次参数更新。
  4. 同步:每隔一定步数,用更新后的模型参数替换旧策略模型。

通过实验,经过强化学习训练的模型在字符串反转测试集上的准确率从监督微调后的约65%提升到了92%以上,证明了该方法的有效性。

本节我们通过字符串反转案例,介绍了监督微调冷启动基于可验证奖励的强化学习 以及组相对策略优化 等核心概念。接下来,我们将在一个更复杂的任务——数学解题中看到类似框架的应用。

本节我们将以DeepSeek-R1论文为例,探讨如何将RLVR框架应用于解决数学问题。其任务形式与字符串反转类似,但更具挑战性。

  • 输入:数学问题描述 x
  • 输出:包含“思维链”和最终答案的文本。模型被鼓励在 标签内进行任意形式的思考,然后在 标签内给出答案。
  • MDP:采用单步MDP,将整个生成序列(可能很长)视为一个动作。
  • 奖励函数
    1. 答案正确性:检查最终答案是否匹配标准答案(0/1奖励)。
    2. 格式奖励:额外奖励模型正确使用 标签。
  • 算法:使用GRPO,即结合了组相对优势和PPO损失,并额外添加了KL散度惩罚项以防止策略偏离初始模型太远。

研究人员使用一个包含约26,000个数学问题(从竞赛到奥数级别)的数据集进行训练。结果展示了两个有趣的现象:

  1. 性能提升:在训练过程中,模型在评估集上的解题准确率稳步上升。
  2. 涌现的长思维链:随着训练进行,模型在 `` 标签内生成的“思考”过程变得越来越长(可达上万个词元)。模型不仅进行线性推理,还展现出回溯尝试不同策略在发现错误时重新评估等复杂行为。这表明,通过简单的强化学习框架,可以激励模型发展出复杂的内部推理过程。

DeepSeek-R1的训练分为多个阶段:

  1. 从零开始:直接从预训练模型开始进行GRPO训练。虽然有效,但可能产生如中英文混杂等不受控行为。
  2. 模型蒸馏:用第一阶段模型生成大量输入-输出对,过滤整理后,作为监督微调数据训练一个新模型。这能稳定输出分布(如固定使用一种语言)。
  3. 扩展与对齐:进一步将训练扩展到数学之外的任务,并引入基于人类偏好的学习,这引出了我们下一个案例。

上一节我们看到了在奖励函数明确可计算的任务上,强化学习的强大能力。然而,对于像“生成令人满意的聊天回复”这样更主观、更广泛的任务,定义奖励函数变得非常困难。接下来,我们将探讨如何通过从人类反馈中学习强化学习 来解决这一问题。

我们的目标是优化一个聊天机器人,使其生成令人类用户满意的回复。这里的挑战在于“满意”是主观且难以编程定义的。

由于无法编写规则式奖励函数,我们需要学习一个奖励模型。主要有两种策略:

1. 直接评估模型

  • 收集一个数据集,其中包含 (输入x, 输出y, 人工评分r)
  • 训练一个模型 R(x, y) 来直接预测评分 r
  • 适用于定义相对具体的属性(如“安全性”),但对于“整体质量”这种抽象概念,数据收集和标注非常困难。

2. 偏好学习模型

  • 收集偏好数据:对于同一个输入 x,给出两个输出 y1y2,由人工标注哪个更好。这比直接评分更容易。
  • 训练一个奖励模型 R(x, y),其目标是使得对于偏好数据 (x, y^+, y^-),始终有 R(x, y^+) > R(x, y^-)
  • 这通常通过Bradley-Terry模型推导出的损失函数来实现:
    Loss = -log(σ(R(x, y^+) - R(x, y^-)))
    其中 σ 是sigmoid函数。这个损失函数鼓励奖励模型对更受偏好的输出给出更高的分数。










从人类反馈中学习强化学习 通常包含三个步骤:

  1. 监督微调:在高质量的 (提示, 回复) 对话数据上微调模型,使其初步掌握任务格式和基本能力。
  2. 奖励模型训练
    • 收集偏好数据:使用SFT模型为一批提示生成多个回复,然后通过人工标注或使用一个更强大的AI模型(如GPT-4)来评判这些回复的优劣,形成偏好对。
    • 训练RM:使用上述偏好损失函数训练奖励模型 R_φ(x, y)
  3. 强化学习优化
    • 使用PPO等算法,以奖励模型 R_φ(x, y) 作为奖励信号,优化语言模型策略 π_θ
    • 关键挑战:奖励黑客:由于奖励模型是学习得来的且不完美,强化学习智能体会千方百计寻找漏洞来获得高奖励,可能导致模型输出无意义但能骗过RM的内容,甚至使实际性能下降。
    • 解决方案:KL散度惩罚:为了抑制奖励黑客和防止模型行为退化,在强化学习的目标函数中加入一个KL散度惩罚项,约束优化后的策略 π_θ 不要偏离初始的SFT策略 π_ref 太远。最终优化目标为:
      objective = E[R_φ(x, y)] - β * KL(π_θ(y|x) || π_ref(y|x))
      其中 β 是控制惩罚强度的系数。










根据奖励来源、优势计算方式和损失函数的不同,衍生出多种算法:

算法简称 奖励来源 优势计算 关键损失组件 适用场景 GRPO 可验证奖励 组相对优势 PPO裁剪 + KL惩罚 数学解题、代码生成等 PPO (标准) 学习到的奖励模型 价值函数基线 PPO裁剪 + KL惩罚 通用对话对齐 Reinforce 任意 蒙特卡洛回报 策略梯度 基础算法,常作为基准

本节课我们一起探索了将强化学习应用于大语言模型的三个核心案例:

  1. 字符串反转:我们学习了基于可验证奖励的强化学习 的基本流程,包括监督微调冷启动、使用组相对优势PPO损失 进行优化。
  2. 数学解题:通过DeepSeek-R1论文,我们看到相同的RLVR框架如何催生模型复杂的长思维链推理能力。
  3. 人类偏好对齐:对于奖励难以定义的任务,我们介绍了从人类反馈中学习强化学习 的完整管道:监督微调、训练偏好奖励模型、以及使用带KL散度惩罚的强化学习来优化策略并规避奖励黑客问题。

这些案例展示了强化学习为语言模型训练提供的灵活性和强大能力,使其能够直接优化复杂目标,并适应从明确规则到主观偏好的各种任务。理解这些基本组件将有助于你跟上该领域快速发展的研究。

在本节课中,我们将要学习智能体的概念,特别是基于语言模型的智能体。我们将探讨智能体的定义、它们运行的不同环境、常见的设计模式,并了解如何利用强化学习算法来训练这些智能体模型。

上一节我们介绍了语言模型和马尔可夫决策过程的基本概念。本节中,我们来看看如何将它们结合起来,构成一个智能体。

我们可以从强化学习的角度来定义智能体。智能体可以被视为在某个环境中运行的一个策略。策略是一个从状态映射到动作概率分布的映射,它通过与环境的交互来接收状态和奖励,并执行动作。

因此,智能体的核心在于其运行的环境,以及如何实现这个策略,特别是语言模型在其中扮演的角色。

为了理解智能体的运作方式,我们先来看一些早期的智能体环境示例。

MiniWoB(Mini World of Bits)是一个几年前提出的环境,它包含一系列非常简单的网页任务。每个任务都是一个独立的网页(由HTML和JavaScript构成),顶部会给出任务描述。智能体需要通过点击等操作与环境交互,以完成任务。

例如,一个最简单的任务可能是“点击标出的按钮”。智能体需要找到并点击正确的按钮,任务即告成功。奖励通常与完成任务的速度相关,例如,每过一秒获得-1的奖励,以鼓励快速解决问题。

我们可以通过代码以编程方式访问这个环境。环境会提供一个循环,智能体接收观察结果,产生动作,并将其提交给环境。

在设计智能体时,一个关键问题是如何表示观察和动作。对于网页导航任务,通常有两种方法:

  • 文本表示:将当前网页的HTML或DOM元素结构转化为文本。
  • 图像表示:将当前网页的截图作为输入。

动作则由环境设计者定义。在MiniWoB中,动作可能包括“将光标移动到指定坐标”、“点击元素”等。

以下是设计智能体的几种方式:

1. 基于规则的智能体
我们可以设计一个不依赖机器学习、完全基于规则的智能体。例如,解析网页的JSON表示,找到文本为“1”的元素,然后执行“点击该元素”的动作。这种智能体可以完美解决特定任务。



然而,这种智能体的主要缺点是缺乏通用性。它只能解决一个非常狭窄的任务,无法直接应用于新环境。

2. 语言模型智能体
语言模型智能体是指在决策过程中使用语言模型来帮助确定下一步动作。我们需要将状态和动作表示为文本标记(tokens)。



具体实现时,我们会编写一个提示词(prompt),描述智能体的角色、可用动作、当前观察(如网页的文本表示)以及任务描述。然后,语言模型根据提示生成文本形式的动作(例如,“click element [id]”),再由一段代码(称为“脚手架代码”)解析输出并转换为环境可执行的动作。

这种方法的优势在于其潜在的通用性。通过精心设计的提示词,同一个智能体框架可能适用于多种不同的任务。

3. 视觉语言模型智能体
我们还可以使用视觉语言模型,将网页截图作为观察输入。这要求模型能够理解图像内容并提取相关信息。



然而,这种方法面临“落地”的挑战。例如,模型需要将生成的坐标准确地映射到屏幕上的点击位置,这对当前最先进的视觉语言模型来说仍然相当困难。此外,如何有效管理智能体的历史交互记录(长期记忆)也是一个开放的研究问题。

另一类备受关注的智能体是“计算机使用智能体”或图形用户界面智能体。这类智能体的设计理念是做出最少的假设:

  • 观察:与人类看到的一样,即计算机屏幕的截图。
  • 动作:通用计算机操作,如点击、打字等。

理论上,这种智能体能够执行任何人类可以通过计算机完成的任务,从而成为一种非常通用的智能体。

智能体作为在环境中执行动作的策略,可以通过语言模型以不同方式构建。文本智能体使用文本来表示状态和动作,而视觉智能体则额外使用图像。

近年来,智能体在多个领域受到广泛关注,主要包括:

  • 软件工程:超越简单的代码生成,智能体可以访问运行代码、文件操作等工具,完成更复杂的任务,如处理GitHub工单。
  • 深度研究:智能体可以接收查询(如“编译这份研究报告”),通过链式思考、网络搜索、结果分析等一系列动作,最终生成报告。
  • 网页智能体:理论上可以执行任何网页浏览任务。

接下来,我们看看智能体在不同类型环境中的具体应用。

对于文本环境,高层策略是:选择一个感兴趣的领域或问题,然后思考模型需要哪些动作或工具来解决它。

软件工程智能体示例
以软件工程智能体为例,其动作可能包括:搜索代码库、执行代码、编辑文件等。环境需要实际实现这些功能。例如,当模型发出“搜索代码库”动作时,环境需要真正执行搜索并返回格式化的结果。



一个著名的例子是SWE-agent。它定义了一系列命令(如“打开路径”、“跳转到行号”、“搜索文件”),并在文件系统和终端上实现这些功能。智能体接收GitHub问题描述,通过链式思考,调用这些工具来浏览代码库、思考解决方案、编写代码并运行测试。

深度研究系统示例
WebGPT是一个早期的例子,专注于回答问题。智能体的观察是问题本身,动作包括:使用查询搜索互联网、点击链接、在页面中查找文本、引用文本、上下滚动页面等。最终,智能体在收集足够信息后生成带有引用的答案。这种方法使模型能够与外部环境(网络)交互,而不仅仅是依赖内部知识。



在视觉环境中,观察是图像形式。我们之前已经看到了MiniWoB的视觉版本示例。

更复杂和现实的环境是“Visual Web Arena”。它创建了模拟的网页(如假Reddit、购物网站),并定义了点击、打字等动作。智能体接收网页的视觉表示,并输出文本形式的动作。

“OS World”是一个流行的计算机使用智能体基准测试环境。它涵盖了操作系统上各种软件(如Excel、Photoshop、代码编辑器)的大量任务。任务可能很简单,例如“在Photoshop中调整某个设置”或“在Excel中更新记账表”。单个智能体需要能够处理所有这些不同应用的任务。

奖励设计在这种通用环境中变得具有挑战性。对于每个任务,需要设计方法来检查任务是否成功完成(例如,检查某个按钮是否被点击,或某个计算是否正确)。

通用计算机使用智能体与专门为软件工程设计的智能体相比,其优势在于单一模型可应用于多种场景,但同时也面临更多技术挑战,如动作的精确落地(点击、打字的位置)。

在构建智能体时,有几个常见的设计模式。

链式思考是指提示或训练模型在采取动作之前先输出一个“思考”过程。例如,模型可能会先分析“为了解决这个问题,我们首先需要……”,然后再输出具体的编辑动作。

从建模角度看,这可以视为策略在内部生成一个思考,然后产生动作分布;也可以视为动作被分解为思考和动作两个部分。在训练时,损失函数通常同时应用于思考和动作。

脚手架代码是指所有用于构建智能体、使其能够与环境交互的结构化代码。这通常包括:

  • 观察与动作的循环管理。
  • 定义不同动作的格式和语法。
  • 回合间的状态管理(例如,将历史记录附加到提示中)。
  • 定义系统提示词,描述模型的角色和能力。

例如,OpenHands项目提供了一个用于代码智能体的开源脚手架。它包含系统提示、可用工具的描述、示例以及执行工具调用、格式化结果并反馈给模型的循环代码。脚手架的设计直接影响智能体的通用性和能力。

除了强化学习视角,另一个有用的视角是“增强语言模型”。即将语言模型与外部功能(工具)增强,如计算器、搜索引擎、数据库等。

这需要为工具定义一致的接口(通常使用JSON模式描述工具的名称、描述、参数)。语言模型需要被训练成能够理解工具描述并生成正确的工具调用格式。然后,环境(或开发者)执行该工具调用并将结果返回给模型。

许多商业API(如OpenAI)已经支持这种工具调用功能。在计算机使用模型中,除了基础的点击、打字,也常提供如字符串替换、文件创建、Bash命令等更高级的工具。

最后,我们探讨如何将之前学到的强化学习算法应用于训练智能体。关键区别在于环境本身变得“有意义”。

在之前的文本生成强化学习中,环境相对简单(主要是追加生成的token),奖励通常在序列末尾计算。我们可以将其视为一个单步MDP。

而在智能体设置中,环境转换包含了实际意义。例如,“搜索”动作会导致环境返回搜索结果;“点击”动作会改变屏幕状态。因此,我们必须考虑多步MDP。

此外,环境交互可能变得昂贵(需要实际运行搜索、操作图形界面等),并且对于复杂任务,如何充分探索环境、如何设计有效的奖励函数(特别是对于长序列任务)都成为新的挑战。

近期有研究尝试将GRPO等算法应用于计算机使用智能体。其流程包括:

  1. 给定任务指令和当前屏幕截图(可能包含历史),模型输出思考链和动作。
  2. 在环境中展开轨迹。
  3. 应用损失函数。由于是多步环境,损失需要应用于轨迹中的每个状态-动作对。研究中常使用从最终奖励计算出的单一优势值,并应用于所有步骤。
  4. 一个技巧是:只对高熵状态(即模型决策不确定性高的状态)应用GRPO损失,因为许多步骤(如常规点击)可能是平凡且信息量低的。
  5. 计算效率:运行环境轨迹(涉及虚拟机操作)可能成为CPU瓶颈,需要集群化环境实例并优化 rollout 和模型更新的流程。

这只是一个例子,说明了当前的研究方向。更广泛的问题包括:如何在多步环境中进行信用分配、如何设计中间奖励、以及如何高效地进行大规模强化学习训练。

本节课中我们一起学习了智能体的核心概念。我们首先将智能体定义为在环境中运行的策略,并探讨了文本和视觉两种表示方法。接着,我们回顾了智能体应用的多种环境,包括网页任务、软件工程、深度研究和通用计算机操作。然后,我们分析了智能体的关键设计模式:链式思考、脚手架代码和工具使用视角。最后,我们讨论了将强化学习应用于智能体训练时所面临的独特挑战和当前的研究进展,例如处理多步MDP和昂贵的环境交互。智能体研究是一个快速发展的领域,在通用性与安全性等方面仍存在许多开放性问题。

在本节课中,我们将学习如何扩展语言模型的预训练规模。具体来说,我们将探讨并行计算和分布式训练的关键概念与策略。即使你从未预训练过语言模型,这些概念和模式在其他场景中也十分有用。

上一节我们介绍了课程背景,本节中我们来看看在单个GPU上进行训练的基础知识。理解这些是学习并行策略的前提。

一个典型的训练过程包含以下几个主要部分:

  • 模型:由多个层组成。
  • 前向传播:在模型各层中执行计算。
  • 后向传播:计算梯度。
  • 优化步骤:应用优化算法(如Adam)更新模型参数。

我们需要考虑两个关键方面:计算性能和内存需求。

计算性能通常用浮点运算次数来衡量。对于Transformer语言模型,一个常用的近似公式是:
总FLOPs ≈ 6 * 模型参数量 * 处理的令牌数
这个公式源于OpenAI 2020年关于缩放定律的论文。










衡量计算效率的一个关键指标是模型浮点运算利用率。其计算公式为:
MFU = 实际达到的FLOPS / 硬件的理论峰值FLOPS
MFU衡量了在训练过程中对可用计算资源的利用效率。效率低下的原因可能包括设备间通信、内存带宽限制或设备空闲时间。










内存使用则体现在多个方面:

  • 存储模型权重
  • 存储梯度
  • 存储优化器状态(如Adam中的动量、方差)。
  • 存储前向传播过程中产生的中间激活值

当模型或批次过大导致内存不足时,可以采用以下策略:

激活重计算:在反向传播过程中,不保存所有中间激活值,而是在需要时临时重新计算它们。这以增加计算量为代价,显著减少了内存占用。

梯度累积:将一个大批次拆分为多个微批次。依次在每个微批次上进行前向和后向传播,但累积梯度而不立即更新参数。在所有微批次处理完毕后,对累积的梯度求平均并执行一次优化步骤。这允许在内存有限的情况下使用更大的有效批次大小,但代价是计算变为串行。

上一节我们介绍了单GPU上的训练与优化,本节中我们来看看如何在多个GPU上并行化训练。主要有四种策略。

这是最直观的策略。我们将模型完整地复制到每个GPU上,然后将不同的数据批次(微批次)分发到各个GPU并行处理。

以下是实现的关键步骤:

  1. 每个GPU拥有完整的模型副本。
  2. 每个GPU处理一个不同的微批次,独立进行前向和后向传播,计算本地梯度。
  3. 在所有GPU之间同步并平均梯度。这一步通常通过All-Reduce通信原语实现。
  4. 每个GPU使用平均后的梯度更新自己的模型副本。

数据并行的优点是实现相对简单,能有效利用多GPU处理更多数据。但其局限性在于:

  • 每个GPU必须能容纳整个模型。
  • 随着GPU数量增加,通信开销会降低每GPU的吞吐量。
  • 无法训练超出单GPU内存容量的大型模型。

当模型过大无法放入单个GPU时,可以采用张量并行。该策略将单个层内的矩阵运算(如线性层、注意力头)拆分到多个GPU上。

其核心思想是利用矩阵乘法的特性。例如,对于一个线性层 Y = XA,可以将权重矩阵 A 按列分割。每个GPU持有 A 的一部分列,并计算输出的对应部分,最后通过All-Gather操作收集结果。

以下是不同层的拆分示例:

  • 线性层:可按列或行拆分权重矩阵。
  • 注意力层:可将不同的注意力头分配到不同的GPU上计算。

张量并行允许训练参数量远超单GPU内存的模型,但需要对模型架构进行精细改造,并且层内通信频繁,开销较大。

流水线并行将模型的不同层组放置在不同的GPU上。数据像通过工厂流水线一样,依次经过各个GPU上的层组。

最简单的实现(朴素流水线)会导致严重的设备空闲(称为“流水线气泡”),因为在前向或后向传播过程中,大部分GPU在等待其他GPU的计算结果。

为了减少气泡,可以采用微批次交错的策略。即同时处理多个微批次,当一个微批次在GPU A上完成某些层的计算后,GPU A可以立即开始处理下一个微批次,而前一个微批次则在GPU B上继续后续计算。这样能更充分地利用所有设备。

流水线并行非常适合层数很深的大模型,并且跨节点通信模式可能更优。其挑战在于需要精心调度微批次以最小化气泡。

零冗余优化器是一种内存优化技术,通过将优化器状态、梯度和模型参数分片到多个GPU上来减少内存冗余。

它有几个不同级别:

  • ZeRO-1:分片优化器状态。
  • ZeRO-2:分片优化器状态和梯度。
  • ZeRO-3:分片优化器状态、梯度和模型参数。

级别越高,单GPU内存占用越小,甚至可以在一张GPU上微调非常大的模型。但代价是通信开销急剧增加,因为在前向或后向计算需要某些参数时,必须通过All-Gather操作从其他GPU临时收集它们。

上一节我们介绍了四种主要的并行策略,本节中我们来看看如何将它们组合使用,并了解实践中的考量。

在实际训练中,这些策略通常是互补和组合使用的。例如,对于一个大型训练任务:

  • 可以使用流水线并行张量并行来使大型模型能够被装载到多个GPU的内存中。
  • 在此基础上,可以使用数据并行来进一步增加处理的数据吞吐量。
  • 在每组数据并行副本内部,可能还会使用梯度累积来达到目标全局批次大小。

选择**配置是一个高维优化问题,需要权衡模型大小、可用GPU数量、内存、通信带宽和目标批次大小。通常需要根据具体硬件条件和模型架构进行实验和调整。

一些现成的深度学习库(如PyTorch的FSDP、NVIDIA的Megatron-LM)已经实现了这些并行策略,用户可以通过配置参数来组合使用它们。

本节课中我们一起学习了扩展语言模型训练规模的核心技术与策略。

我们首先回顾了单GPU训练的基础,了解了计算效率和内存使用的衡量方式,并学习了激活重计算梯度累积两种内存优化技巧。

接着,我们深入探讨了四种主流的分布式训练策略:

  1. 数据并行:复制模型,拆分数据,适用于模型能放入单GPU的场景。
  2. 张量并行:拆分单个层的计算,用于训练超大规模模型。
  3. 流水线并行:将模型不同层组放置于不同GPU,通过微批次交错减少空闲时间。
  4. 零冗余优化器:通过分片优化器状态、梯度和参数来极大节省内存,但增加通信。

最后,我们了解到这些策略在实践中需要根据具体任务和硬件资源进行组合与调优,现有的一些高级框架提供了相应的工具支持。掌握这些知识有助于理解如何高效利用大规模计算资源来训练先进的AI模型。

在本节课中,我们将要学习量化技术。量化是一种通过使用更少的比特数来表示模型权重和激活值,从而减少大型语言模型内存占用和计算开销的关键技术。这对于在资源受限的设备(如个人笔记本电脑或单GPU环境)上运行或微调大模型至关重要。

上一节我们介绍了量化的动机,本节中我们来看看计算机如何表示数字,这是理解量化的基础。

计算机使用比特序列来表示数据。一个比特是0或1,8个比特构成一个字节。数字的存储方式取决于其类型。

以下是三种主要的数字类型及其表示方法:

  • 无符号整数:仅表示非负整数(0, 1, 2, 3...)。例如,一个8位无符号整数(U8)的范围是0到255。其值由比特位对应的2的幂次方求和得到。
    • 公式值 = Σ (bit_i * 2^i),其中i是比特位置(从0开始)。
  • 有符号整数:可表示正负整数。通常使用最高位表示符号(0为正,1为负),其余位表示数值,常用“二进制补码”编码。
    • 公式:一个8位有符号整数(INT8)的范围是-128到127。
  • 浮点数:用于表示带小数点的实数。采用类似科学计数法的方式,但在二进制(base-2)下工作。一个浮点数由符号位、指数位和有效数字位(尾数)组成。
    • 公式值 = (-1)^符号 * (1 + 尾数) * 2^(指数 - 偏差)

常见的浮点数格式包括:

  • FP32(单精度):通常称为“全精度”,使用32位(1位符号,8位指数,23位尾数)。
  • FP16(半精度):使用16位(1位符号,5位指数,10位尾数)。范围较小,精度较低。
  • BF16(Brain Float 16):由Google Brain提出,使用16位(1位符号,8位指数,7位尾数)。它保持了与FP32相同的指数范围,但牺牲了部分尾数精度,这在训练神经网络时有助于避免因数值过大而导致的崩溃。

理解了数字表示后,我们现在可以探讨量化的核心概念。量化本质上是将数值从一个较大的集合(通常是连续的)映射到一个较小的离散集合的过程。

在语言模型背景下,我们的目标通常是将高精度格式(如FP32、BF16)的模型参数转换为低精度格式(如INT8、INT4)。矩阵乘法(占模型大部分参数和计算)是量化的主要目标。

以下是两种基础的量化方法:

  • 绝对值最大值量化:该方法找出张量中的绝对值最大值,然后将所有值按比例缩放至目标整数范围(例如INT8的-127到127)。
    • 公式量化值 = round(原始值 / abs_max * 127)
    • 反量化近似原始值 = 量化值 * abs_max / 127
  • 零点量化:该方法利用目标整数范围的最小值和最大值,进行缩放和偏移,以更好地利用整个整数范围,尤其适用于数据分布不对称的情况(如ReLU激活后的值全为非负)。
    • 公式scale = (量化范围最大值 - 量化范围最小值) / (原始最大值 - 原始最小值)
    • 公式zero_point = 量化范围最小值 - round(原始最小值 * scale)
    • 量化量化值 = round(原始值 * scale) + zero_point
    • 反量化近似原始值 = (量化值 - zero_point) / scale

然而,这些基础方法有一个共同问题:对异常值非常敏感。如果权重或激活值中存在个别极大或极小的异常值,整个量化范围的精度都会因此严重下降。

上一节提到异常值会破坏量化效果,本节中我们来看一个专门解决该问题的开创性工作:LLM.int8()。

研究发现,在参数量超过67亿的大型Transformer模型中,激活值的某些特征列会持续出现异常大的值(异常值)。这些异常值虽然只占全部特征的约0.1%,但会导致传统的INT8量化性能急剧下降。

LLM.int8() 的核心思想是将矩阵乘法分解为两部分:

  1. 异常值部分:识别出包含异常值的特征列及其对应的权重行,这部分计算使用高精度(如FP16)执行。
  2. 常规值部分:其余绝大多数不包含异常值的特征和权重,使用高效的INT8量化进行计算。

最后,将两部分的结果相加,得到最终的输出。这种方法几乎完全恢复了FP16模型的性能,同时显著节省了内存和计算量。它已被集成到 bitsandbytes 等流行库中。

除了研究导向的方法,社区中也涌现出非常实用的量化工具。一个典型的生态系统是GGML/GGUF + llama.cpp。

以下是该生态系统的组成部分:

  • GGML:一个为张量操作设计的库,定义了一系列量化方法(如Q4_K_M,Q5_K_S)。这些方法通常将权重分块,对每个块独立进行量化,并可能对网络中不同层采用混合精度。
  • GGUF:一种模型文件格式,用于存储使用GGML量化方案的模型架构和权重。
  • llama.cpp:一个用C++编写的高效推理库,支持加载GGUF格式的模型,并能在多种硬件(CPU、Mac Metal、CUDA)上运行。

许多最新模型(如Qwen、Llama)都会官方发布GGUF量化版本。用户可以通过llama.cpp在消费级硬件(如苹果笔记本电脑)上本地运行这些量化后的大模型,实现可用的推理速度。

到目前为止,我们讨论的都是对已训练好的模型进行量化(后训练量化)。然而,量化也可以与训练过程结合,以得到更好的效果。

以下是两种主要的结合方式:

  • 量化感知训练:在微调或训练阶段,在计算图中模拟量化(舍入、缩放)和反量化操作。模型在训练时就能“感知”到量化带来的误差,并学会适应它,从而在最终部署为低精度模型时表现更好。
  • 用于高效微调的量化:例如 QLoRA 技术。它首先将待微调的大模型权重量化为4位,以节省内存。然后,在微调过程中,模型更新(通过LoRA适配器)则以更高精度(如BF16)进行。这使得在单张显存较小的GPU上微调超大规模模型(如650亿参数)成为可能。

本节课中我们一起学习了量化技术。我们从计算机如何表示数字(整数、浮点数)这一基础开始。然后,探讨了量化的基本方法(绝对值最大值、零点量化)及其对异常值的敏感性。接着,我们深入了解了处理异常值的LLM.int8()方法,以及社区中广泛使用的GGUF/llama.cpp实践方案。最后,我们简要介绍了量化如何与训练过程结合,包括量化感知训练和QLoRA等高效微调技术。掌握这些知识,将帮助你理解如何在资源有限的环境下高效地部署和运用大型语言模型。

在本节课中,我们将要学习长上下文模型。这是一个非常活跃的研究领域。如果你使用过语言模型或智能体,可能会经常遇到上下文长度不足的问题。为了解决这个问题,研究者们提出了许多有趣的建模思路和选择。希望通过今天的课程,你能对长序列建模有深入的理解。

首先,我们需要明确“长上下文”的含义。在传统的自然语言处理任务中,例如翻译单个句子,序列长度可能只有20个词元。然而,如今使用像ChatGPT这样的智能体时,我们处理的序列要长得多。例如,一个短文档可能有100个词元,长文档可能达到10,000个词元,而一本书可能包含50,000甚至300,000个词元。此外,如果我们考虑将视频或代码库表示为词元序列,长度可能达到百万甚至十亿级别。基因组序列同样非常长,一个基因组可能包含30亿个核苷酸。

上一节我们介绍了长序列的应用场景,本节我们来看看建模长序列的挑战。主要困难源于我们广泛使用的Transformer模型。

计算与内存的二次方增长:Transformer中的注意力操作在内存和计算量上都随序列长度呈二次方增长。具体公式如下:

  • 注意力分数计算:Attention(Q, K, V) = softmax(QK^T / sqrt(d_k)) V
  • 内存复杂度:O(batch_size * seq_len^2)
  • 计算复杂度:O(batch_size * seq_len^2 * d_model)

训练数据稀缺:获取真正具有长程依赖关系的训练数据可能很困难,特别是对于特定用例。

训练成本高昂:在长序列上训练模型的计算成本非常高。

为了评估模型的长上下文能力,研究者们设计了一系列基准测试。

以下是早期的一些基准测试:

  • 长距离竞技场:包含一系列需要引用长序列信息的任务,其中许多是非NLP的合成任务。
  • SCROLLS数据集:包含需要总结或回答长文档问题的NLP风格任务,输入长度在104到105词之间。

一个有趣的发现是“迷失在中间”现象:即使模型声称支持长上下文,其性能也可能偏向于序列的开头或结尾,而忽略中间部分的信息。

另一个流行的探测任务是“大海捞针”:在一个长文档中插入一个特定信息(“针”),然后要求模型找出它。这可以测试模型从长上下文中检索特定信息的能力。

此外,研究者还通过测试上下文学习能力来展示长上下文的使用:随着上下文示例数量的增加,模型性能得到提升。

其他任务还包括对话理解(模型能否利用对话中的长期上下文)以及信息聚合(如Ulong基准测试,要求模型从上下文的多个位置聚合信息)。即使在最新的模型上,随着上下文长度增加,在这些任务上的性能仍然会下降。

上一节我们了解了评估长上下文能力的基准,本节我们来看看改进Transformer以处理长序列的第一类方法:内存高效计算。

核心挑战在于注意力机制中softmax(QK^T)步骤会产生一个seq_len x seq_len的矩阵,导致内存的二次方消耗。在GPU上,这迫使数据在高速但容量小的SRAM与低速但容量大的HBM之间频繁传输,成为主要瓶颈。

解决方案的关键在于在线Softmax。该算法允许我们无需存储整个seq_len^2矩阵即可计算注意力。其核心思想是将Softmax分解为分子和分母两部分,并在线性内存内进行累积计算。伪代码如下:

# 初始化分子和分母累加器 numerator = zeros_like(V) denominator = 0 for i in range(seq_len): weight = exp(Q[t] * K[i]) # 计算当前键的权重 numerator += weight * V[i] # 累加加权值 denominator += weight # 累加分母 output = numerator / denominator # 得到最终输出 

基于这一思想,出现了更高效的实现:

  • Flash Attention:将在线Softmax与巧妙的GPU内存管理(分块计算)结合,编写了高效的CUDA内核,显著加快了训练和推理速度。
  • 环注意力:将长序列的注意力计算分布到多个设备上,这是一种新的并行维度——上下文并行。它允许在多个GPU上训练极长序列的模型。

除了内存问题,另一个挑战是外推:即模型在推理时处理比训练时更长的序列的能力。问题通常出在位置编码上。

  • 绝对位置编码(学习的或固定的如正弦/余弦编码):对于训练时未见过的位置,模型会得到“陌生”的编码,导致性能下降。
  • 相对位置编码(如RoPE):理论上应能外推,但在实践中,随着序列变长,注意力分数可能会衰减,模型仍可能失败。

解决外推问题主要有两种思路:

1. 在更长序列上训练:直接让模型在更长的文档上进行微调或后训练。这需要获取或合成长上下文数据。

2. 改进位置编码:针对RoPE等编码进行修改。
* 调整基频:在长序列后训练中增加RoPE的基频率。
* 位置插值:将超出训练长度的位置索引缩放(插值)回训练范围。这些技术可以在一定程度上缓解外推问题。










上一节我们讨论了改进Transformer的方法,本节我们来看看另一条路径:完全不同的架构——状态空间模型。它旨在结合RNN和CNN的优点,实现高效训练和无限长上下文推理。

状态空间模型的核心是一个线性动态系统,其离散形式可表示为:

h_t = A * h_{t-1} + B * x_t y_t = C * h_t 

其中,h_t是隐藏状态,x_t是输入,y_t是输出,A, B, C是可学习矩阵。这看起来像一个线性RNN,支持常数内存的序列推理。

关键在于,得益于其线性特性,同一模型可以等价地视为一个全局卷积操作,使用一个卷积核K

y = x * K 

其中卷积核KA, B, C决定。这种“卷积模式”允许在训练时进行高效的并行计算。

最初的S4模型通过使用结构化矩阵(如HIPPO矩阵)改进了基础状态空间模型,以更好地保留长期记忆。

Mamba模型在此基础上引入了选择性机制:使B, C矩阵和步长参数Δ成为输入的函数(即Δ = f(x_t))。这使得模型能够根据当前输入选择性地保留或忽略信息,显著提升了在需要上下文引用(如少样本学习)的任务上的性能。选择性机制破坏了完美的卷积视图,但通过并行扫描算法和优化的CUDA内核,Mamba仍然实现了高效的训练。

实验表明,Mamba在语言建模上能匹配改进版Transformer的性能,并且在基因组、音频等超长序列数据上表现优异。

本节课我们一起学习了长序列建模。

  • 我们首先了解了长上下文的应用场景和建模挑战。
  • 接着,我们探讨了用于评估长上下文能力的各种基准测试。
  • 然后,我们深入研究了改进Transformer的两类方法:通过在线SoftmaxFlash Attention环注意力实现内存高效计算;以及通过在更长序列上训练改进位置编码来提升外推能力。
  • 最后,我们介绍了一种有前景的Transformer替代架构——状态空间模型,特别是Mamba,它通过结合RNN的递归推理和CNN的并行训练,为高效处理超长序列提供了新的思路。这是一个非常活跃的研究领域,未来可能会有更多创新出现。

在本节课中,我们将要学习专家混合模型。这是一种在现代前沿语言模型中广泛使用的架构,它通过稀疏激活的方式,在保持计算量相对固定的同时,显著增加模型的总参数量,从而提升模型性能。我们将从核心概念出发,逐步探讨其设计、路由、训练方法以及系统层面的考量。

上一节我们介绍了课程概述,本节中我们来看看专家混合模型的核心思想。

专家混合模型的关键思想是改造Transformer架构。具体做法是,选取Transformer中的特定层——通常是前馈网络层——并将其替换为一种新型的层。

标准的Transformer前馈网络层处理每个令牌。而在专家混合层中,我们不再使用单一的前馈网络,而是准备多个前馈网络,每个被称为一个“专家”。对于每个输入令牌,一个路由机制会动态地选择其中一个或少数几个专家来处理它,其他专家则不被激活。

这种设计带来了稀疏性。模型可以拥有大量的总参数,但对于任何一个具体的训练或推理样本,只有一小部分参数会被激活和使用。这相当于在固定计算量的前提下,为模型增加了一个新的扩展维度——专家数量。

上一节我们了解了核心概念,本节中我们来看看其重要性。

专家混合模型具有吸引人的特性,并得到了大量实证结果的支持。其核心优势在于,它提供了一种新的模型扩展方式:在保持模型推理或训练时所用浮点运算次数不变的前提下,通过增加专家数量来增加模型的总参数量。

研究表明,增加专家数量通常能有效降低测试损失,提升模型性能。这意味着在预训练阶段,我们可以更高效地利用计算资源,获得更好的模型。此外,在推理时,尽管模型总参数量庞大,但激活的参数数量相对较少,这使得其效率可以媲美参数量小得多的稠密模型。

以下是支持这些结论的一些关键观察:

  • 对于固定的计算预算,专家混合模型性能优于稠密模型。
  • 在达到相同性能时,专家混合模型所需的训练时间或计算量更少。
  • 在激活参数量相同的情况下,专家混合模型显著优于稠密模型。

目前,包括Gemini、DeepSeek、Mixtral等在内的许多前沿模型都采用了专家混合架构,这充分证明了其有效性。

上一节我们看到了专家混合模型的优势,本节中我们来深入探讨其具体设计。

目前,常见的做法是在Transformer的每一个前馈网络层都应用专家混合替换。早期的模型可能每隔几层放置一个专家混合层,但现代模型倾向于在所有层使用。

专家数量是一个关键的超参数。增加专家数量可以增加总参数量,但通常会遇到收益递减的情况。实践中,专家数量通常在64或128左右。

另一种思路是细粒度专家。在总参数量不变的前提下,将专家切分成更多、但维度更小的专家单元,同时增加每次激活的专家数量。这可以大幅增加模型路由选择的灵活性,公式上表现为组合数 C(专家总数, 激活数) 的显著增长。

一些模型引入了共享专家的概念。即固定有一或两个专家被所有令牌路由到,用于学习通用信息;其余专家则进行动态路由,可能专注于更专门的任务。这种设计旨在减少专家间的冗余,但不同研究对其效果结论不一。

上一节我们讨论了专家设计,本节中我们来看看如何决定将令牌发送给哪个专家,即路由机制。

路由的目标是:为序列中的每个令牌,选择一个或多个专家进行处理。为此,我们需要计算每个“令牌-专家”对的分数,然后根据某种策略选择专家。

当前主流方法采用一种非常简单的方式计算分数。为每个专家 i 引入一个权重向量 W_i。对于输入令牌的隐藏状态 h_t,其与专家 i 的分数通过点积计算:score_{i,t} = h_t · W_i。之后,通常会对所有专家的分数进行Softmax归一化,得到每个专家处理该令牌的概率分布。

主要有两种选择策略:

  1. 令牌选择:每个令牌独立地选择分数最高的前 K 个专家。这是目前最常用的策略。
  2. 专家选择:每个专家独立地选择分数最高的前 K 个令牌进行处理。

此外,还有更简单的哈希路由(无学习)或更复杂的强化学习、最优传输等方法,但当前实践中以令牌选择策略为主。

以下是令牌选择策略的优缺点分析:

  • 优点
    • 确保每个令牌都被分配给专家。
    • 实现相对简单。
  • 缺点
    • 负载均衡挑战:可能导致某些专家分配到过多令牌,而其他专家闲置。
    • 可能引发令牌丢弃:在分布式系统中,如果单个专家分配的令牌超过其设备容量,多出的令牌可能被丢弃,直接跳过该层计算,影响性能。
    • 操作不可微:Top-K选择操作本身不可微,梯度仅能通过被选中的专家传播。

上一节我们介绍了路由机制,本节中我们探讨如何训练专家混合模型。

训练专家混合模型时,除了标准的语言建模损失外,通常还需要引入额外的辅助损失函数,以解决路由带来的挑战,特别是负载均衡问题。

这些启发式损失函数在实践中被证明对稳定训练和获得良好性能至关重要。

上一节我们讨论了训练损失,本节中简要看看系统层面的新挑战。

专家混合模型引入了新的并行维度——专家并行。其核心思想是将不同的专家放置在不同的计算设备上。在计算时,需要根据路由决策,将令牌张量分散到存有所需专家的设备上,计算完成后再收集结果。

这涉及到大量的设备间通信。因此,负载均衡不仅影响模型效率,也直接影响系统通信开销和整体训练速度。专家并行需要与数据并行、流水线并行等其他并行策略协同工作,以在超大规模模型训练中实现高效计算。

上一节我们了解了系统并行性,本节中我们通过两个具体案例来回顾所学知识。

  • 规模:总参数量160亿,激活参数量28亿。
  • 专家设计:64个专家,每次激活8个。其中包含2个共享专家,62个细粒度专家。
  • 路由:令牌选择策略。
  • 并行策略:主要使用流水线并行,将专家放在同一设备,避免了令牌丢弃。
  • 损失函数:使用了专家平衡损失和设备级平衡损失。
  • 关键结果:仅用40%的预训练计算量,即达到了其70亿参数稠密模型的性能。

  • 规模:总参数量70亿,激活参数量13亿。
  • 专家设计:使用细粒度专家,无共享专家。
  • 路由:令牌选择策略,并设计了避免令牌丢弃的方案。
  • 损失函数:使用了专家平衡损失和Z-损失。
  • 关键结果:由于性能提升更快,达到稠密模型同等性能的速度快了约2倍。

本节课中我们一起学习了专家混合模型。我们从其核心思想——用多个稀疏激活的前馈网络专家层替换标准Transformer前馈层——出发,逐步探讨了其重要性、专家数量与粒度等设计选择、主流的令牌选择路由机制及其利弊、训练时必需的平衡损失和Z-损失,以及专家并行这一系统挑战。最后,通过分析DeepSeek-MoE和OLMo-MoE两个案例,我们回顾了这些概念在实际模型中的应用。专家混合模型已成为扩展大语言模型能力的关键架构之一,理解其原理对于跟进当前前沿模型发展至关重要。

在本节课中,我们将学习高级推理策略。这是一个在过去一年中取得了巨大进展、非常令人兴奋的领域。我们将探讨如何通过不同的推理时(test-time)计算策略来提升语言模型的性能,而无需重新训练模型。

上一节我们介绍了推理的基本概念,本节中我们来看看为什么这个领域如此重要。

推理,广义上指的是使用模型和某种推理算法生成输出。在语言模型的语境下,就是生成文本或其他离散序列。

这个领域之所以令人兴奋,是因为它提供了一种新的“扩展”维度。例如,OpenAI 的 o1 模型展示了随着“测试时计算”(即生成更多令牌)的增加,模型在困难基准测试上的准确率会显著提升。这意味着,通过更聪明的推理策略,我们可以让现有模型发挥出更强的能力。

实现这种测试时计算提升主要有两种思路:

  1. 并行生成:针对同一个问题,多次生成不同的答案,然后通过某种方式(如评估器)选择**答案。
  2. 生成长链思考:让模型生成更长的、逐步推理的“思维链”,从而更有可能得出正确答案。

接下来,我们将深入探讨这两种思路的具体策略。

并行生成的核心思想很简单:我们多次调用生成器模型,得到多个候选输出,然后通过一个聚合策略选出最终答案。我们可以将其抽象为一个“元生成器”算法。

以下是两种常见的并行生成模式。

这种策略首先生成 N 个候选答案,然后使用一个奖励模型(或评估器)为每个答案打分,最后选择得分最高的那个。

核心公式

最终答案 = argmax_{i=1 to N} [ 奖励模型(输入, 候选答案_i) ] 

这种方法的效果高度依赖于奖励模型的质量。一个完美的奖励模型(例如,在形式化证明中能自动验证证明正确性的工具)可以带来巨大的性能提升。

然而,在大多数实际场景中,我们没有完美的奖励模型,需要训练一个学习型奖励模型。一个著名的案例是 OpenAI 在 GSM8K(小学数学题)数据集上的工作。

GSM8K 案例研究
他们训练了一个“验证器”模型,其目标是预测一个生成的解题过程最终是否正确。有趣的是,他们不仅让模型在序列末尾预测标签,还让它在每一个令牌位置都预测最终的正确答案标签。这类似于强化学习中的价值函数,旨在为每个“状态”(部分解)预测最终的“回报”(是否正确)。实验表明,这种“逐令牌损失”比仅在末尾预测效果更好。



奖励模型的潜在问题
如果奖励模型不完美,可能会发生“奖励黑客”现象。即模型生成的答案在奖励模型那里得分很高,但实际上是错误的。随着采样数量 N 的增加,这种过优化风险可能增大。



为了获得更好的奖励信号,研究者尝试了“过程奖励模型”。即不仅标注最终答案对错,还让人工标注解题每一步的正确性。这种模型能更精确地定位错误,但获取标注数据的成本很高。

让奖励模型也进行思考
最近的研究表明,让奖励模型自身也生成一个“思维链”来评估答案,可以提升其判断能力。这相当于在评估阶段也增加了计算量。



这种策略不需要额外的奖励模型。它同样生成多个候选答案,但聚合方式是基于答案本身的“投票”。

操作流程

  1. 针对同一问题生成多个思维链和最终答案。
  2. 统计所有最终答案的出现频率。
  3. 选择出现频率最高的答案作为最终输出。

优势与局限

  • 优势:无需训练奖励模型,实现简单。
  • 局限:需要定义答案的“等价性”。在数学题(答案明确)上很有效,但在生成文章等开放任务中较难应用。

实验表明,自洽性方法能显著提升贪婪解码的性能。即使是对于 DeepSeek-R1 这类擅长长链思考的模型,结合自洽性投票(如采样16次)仍能带来额外收益。

加权投票
我们可以结合奖励模型和投票,进行加权投票。每个答案的票数由其奖励模型得分进行加权。当奖励模型质量较高时,加权投票可能优于简单的多数投票或**N采样。



从理论上看,当采样数量趋于无穷时,自洽性投票的准确率会收敛于模型所有可能推理路径中,得出正确答案的总概率质量。提升这个上限需要要么改进生成器模型本身,要么使用更好的奖励模型进行加权。

迭代优化的核心思想是让模型能够根据反馈来修正其输出。这是一个循环过程:生成 -> 获得反馈 -> 修正 -> 再次生成,直到满足条件。

反馈的质量是迭代优化成功的关键。我们可以将反馈分为两类:

反馈信息来自模型外部,提供新的信息。

  • 示例:代码生成后,运行测试用例,将错误信息作为反馈。
  • 优势:反馈准确、具体,能明确指示错误位置。
  • 效果:在拥有完美验证器的场景(如形式化验证的代码生成)中,迭代优化能取得显著效果,其性能提升可以超越并行采样的上限。

模型尝试自我评估,为自己生成反馈。

  • 示例:让模型检查自己的解题步骤是否有误,然后重写。
  • 挑战:效果不稳定。研究表明,在复杂推理任务上,模型可能经常将正确解改为错误解,或将错误解改为正确解,整体改进有限。
  • 适用场景:在评价标准明确、易于自查的任务上更有效(例如,检查生成摘要是否包含了要求的所有关键点)。

一个简单的类比是猜单词游戏:如果每次都能准确知道哪些字母位置错了(完美外在反馈),修正效率会极高。如果反馈有噪声(错误定位不准),修正效果就会下降。

这引出了一个重要的工程问题:给定固定的推理计算预算,我们应该使用更大的模型(生成次数少),还是较小的模型(但可以生成更多次候选答案)?

推理成本可以近似为:模型参数量 × 生成的令牌数。我们可以像研究预训练扩展定律一样,寻找在特定计算预算下,模型大小和生成数量之间的最优配置。

研究表明,对于中等难度的问题和预算,使用较小模型配合并行采样策略(如自洽性)可能是最优选择。然而,对于最困难的问题,大型模型本身更强的能力通常仍是最终解决方案的必要条件。这为实际部署提供了指导:可以根据查询的难度和可用的计算资源,在大小模型之间进行动态选择。

最后,我们回到第二种思路:生成长链思考。我们可以不设计复杂的外部推理算法,而是直接训练模型学会在内部进行“思考”和“搜索”。

例如,通过强化学习训练模型生成极长的思维链,以最大化最终答案的正确性。像 DeepSeek-R1 这样的模型,其生成的思维链中会自发出现“分支”、“回溯”、“重新评估”等类似搜索的行为。这意味着,模型在一定程度上内化了一些推理策略。

研究者还探索了如何控制这种思考的长度,例如通过“预算强制”方法,让模型在指定令牌数内完成思考。结果表明,思考长度与问题解决准确率之间存在权衡关系,更长的思考通常(但并非总是)带来更高的准确率。

本节课我们一起学习了高级推理策略。我们主要探讨了两种利用测试时计算提升模型性能的途径:

  1. 并行生成:通过多次采样并聚合结果来提升性能。核心方法包括:
    • **N采样:依赖奖励模型选择**输出。
    • 自洽性投票:通过多数表决选择最常见答案,无需额外模型。
    • 加权投票:结合奖励模型与投票机制。
  2. 迭代优化:通过反馈循环逐步修正输出。其效果高度依赖于反馈质量,外在反馈通常比内在反馈更可靠、有效。

此外,我们还了解了推理扩展定律的概念,即在计算预算约束下权衡模型大小与生成数量,以及长链思维模型如何将复杂的推理策略内化到模型自身的生成过程中。

这些策略为我们提供了强大的工具,能够在模型训练完成后,进一步挖掘其潜力,以适应不同难度和资源约束的应用场景。

小讯
上一篇 2026-03-27 23:31
下一篇 2026-03-27 23:29

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/248122.html