斯坦福-CS224n-自然语言处理-2025-笔记-全-

斯坦福 CS224n 自然语言处理 2025 笔记(全)

1:引言与词向量 🎓

在本节课中,我们将学习自然语言处理(NLP)的基本概念,并深入探讨如何通过深度学习技术,特别是词向量,来让计算机理解和表示词语的含义。我们将从课程介绍开始,逐步了解人类语言的特性,并重点学习一个经典且强大的词向量模型——Word2Vec。

课程介绍与概述 📋

这门课程旨在教授深度学习在自然语言处理领域的应用。课程内容将从基础概念开始,逐步深入到当前最先进的方法,如Transformer和大型语言模型。课程不仅关注技术实现,也探讨人类语言的结构及其复杂性,并最终指导大家构建实用的NLP系统。

课程评估包括四次作业和一个期末项目。作业旨在巩固理论知识并提升实践能力,而期末项目则提供了两种选择:一个带有详细指导的默认项目,或一个完全由学生自主设计的项目。

人类语言与词义 🗣️

人类语言不仅是交流的工具,也是高级思维和规划的脚手架。与我们的近亲(如黑猩猩)相比,语言是人类取得巨大成就的关键因素。它使我们能够跨越时空分享知识,从青铜时代发展到拥有智能手机的现代文明。

语言也是一个灵活的社会工具,充满了不精确性、细微差别和情感色彩。词语的含义并非一成不变,而是随着人类的使用而不断演变和创新,这种创新常常由年轻人推动。

深度学习与NLP的进展 🚀

在过去的十年里,深度学习极大地推动了NLP的发展。早期的突破出现在机器翻译领域,神经网络系统使得实时、高质量的翻译成为可能,极大地改变了跨语言交流的方式。

现代NLP系统已经能够理解问题并从文档中提取答案,而不仅仅是进行关键词匹配。这背后是复杂的神经网络在协同工作。自2019年以来,大型语言模型(如GPT-2)展示了生成流畅文本的强大能力,它们不仅能写出语法正确的句子,还能理解上下文并生成连贯的叙述。

如今,以ChatGPT为代表的多模态模型(或称“基础模型”)能够处理文本、图像等多种形式的信息,展现了人工智能在理解和生成内容方面的巨大潜力。

词义表示:从传统方法到词向量 🔤

传统上,词语的“意义”常被定义为符号(词语)与其所指代的事物或概念之间的对应关系,这被称为指称语义。在计算机中,早期常用“独热编码”来表示词语,即每个词对应一个很长的向量,其中只有一个位置是1,其余为0。例如:

motel = [0, 0, 0, 1, 0, ..., 0]
hotel = [0, 0, 0, 0, 1, ..., 0]

这种方法的问题是,它无法表示词语之间的相似性。motelhotel的向量点积为0,在数学上是正交的,没有关联。

另一种思路是分布语义,其核心思想是“一个词的意义由其上下文决定”。基于此,我们引入词向量(也称为词嵌入)的概念。每个词被表示为一个稠密的、相对较短的实数向量(例如300维)。语义相近的词,其向量在空间中的位置也更接近。

Word2Vec 算法详解 🧠

Word2Vec是2013年提出的一种简单而高效的词向量学习算法。其核心思想是:利用大量文本数据,通过一个简单的预测任务来学习词向量。

基本思想

算法遍历文本中的每一个位置。在每个位置,我们有一个中心词(例如“into”)和其窗口内的上下文词(例如前后各两个词:“problems”、“turning”、“banking”、“crises”)。模型的目标是,根据中心词的向量,尽可能准确地预测其周围会出现哪些词。

目标函数

我们希望最大化整个语料库中所有上下文词出现的似然概率。为了方便优化,我们将其转化为最小化平均负对数似然。具体公式如下:

J(θ) = - (1/T) * Σ Σ log P(w_{t+j} | w_t; θ)

其中,T是文本总词数,求和遍历所有中心词位置t和其上下文窗口内的位置jθ代表模型的所有参数,即每个词作为中心词和上下文词时的两种向量。

概率计算与Softmax

如何用词向量计算概率P(w_o | w_c)?我们使用两个词向量的点积来衡量其相似度,然后通过Softmax函数将其转化为概率分布:

P(w_o | w_c) = exp(u_o^T v_c) / Σ_{w=1}^{V} exp(u_w^T v_c)

这里,v_c是中心词w_c的向量,u_o是上下文词w_o的向量,V是词汇表大小。Softmax确保所有可能上下文词的概率之和为1,并放大了点积较大的词的概率。

优化与梯度下降

我们的目标是找到一组词向量参数θ,使得目标函数J(θ)最小化。这通过梯度下降实现。我们计算目标函数对所有参数的梯度(偏导数),然后沿着梯度反方向更新参数,逐步逼近最优解。

对于中心词向量v_c的梯度,经过推导可得到如下形式:

∂J / ∂v_c ≈ u_o - Σ_{x=1}^{V} P(w_x | w_c) * u_x

这个结果非常直观:梯度是观察到的上下文词向量u_o模型预测的上下文词向量的加权平均之间的差值。当预测的分布与实际情况一致时,梯度为零,模型达到最优。

通过在整个语料库上反复进行这种预测和参数更新,模型最终能学习到蕴含丰富语义信息的词向量。

总结 📝

本节课我们一起探讨了NLP课程的概览、人类语言的特性以及深度学习在NLP中的革命性进展。我们重点学习了词向量的概念,它是对传统独热编码表示的重大改进,能够捕捉词语之间的语义相似性。最后,我们深入剖析了Word2Vec算法的原理,了解了它如何通过一个简单的上下文预测任务,利用梯度下降从大量文本中自动学习出有意义的词向量表示。词向量是现代深度学习NLP的基石,为后续学习更复杂的模型奠定了基础。

2:词向量与语言模型 🧠📚

在本节课中,我们将深入学习词向量的核心知识,并初步了解神经网络的基础。我们将从优化基础开始,深入探讨Word2Vec及其变体,然后介绍一些基于计数的替代方法。接着,我们将讨论词向量的评估、词义消歧问题,并最终引入神经网络分类器的概念,为后续课程打下基础。

课程组织与回顾 📅

上一讲我们介绍了词向量的基本概念和Word2Vec模型。我们有一个损失函数,需要通过计算其梯度来确定优化的方向。最简单的算法是梯度下降:计算梯度,然后沿着梯度的反方向(即下坡方向)前进一小步,不断重复此过程直至收敛。

然而,对于大规模数据,计算整个数据集的梯度非常耗时。因此,我们通常使用随机梯度下降。其核心思想是:每次只从数据中选取一小部分(例如16或32个数据项),基于这个子集计算梯度和损失函数。这个梯度虽然是对真实梯度的有噪声估计,但不仅计算速度快,而且这种噪声有时反而有助于神经网络跳出局部最优,找到更好的解。

Word2Vec算法详解 🔍

Word2Vec的基本思想是:为每个词分配一个随机的向量表示,然后遍历语料库,根据当前的中心词预测其上下文词的概率。通过计算预测误差和梯度,我们不断更新词向量,使其能更好地预测上下文。

值得注意的是,词向量必须用小的随机数初始化。如果全部初始化为0,模型将无法学习,因为所有向量初始相同会导致错误的对称性。

在Word2Vec中,模型的参数就是这些词向量(包括中心词向量和上下文词向量,我们通常将它们视为独立的)。我们通过计算中心词向量与各个上下文词向量的点积,并利用softmax函数将其转化为概率分布,然后与真实的上下文词进行比较,从而得到误差。

这是一个典型的词袋模型:它不考虑句子的结构或词语的左右顺序,只是平等地预测中心词周围每个位置出现某个词的概率。

词向量的魔力与实践 ✨

尽管方法简单,但通过大量文本学习到的词向量能够捕捉丰富的语义信息。例如,我们可以使用gensim库加载预训练的词向量(如GloVe)进行探索:

  • 相似性查询:查询与“USA”最相似的词,会得到“Canada”、“America”等。
  • 类比推理:这是Word2Vec最著名的特性。例如,通过向量运算 king - man + woman,得到的结果向量最接近的词是“queen”。这展示了词向量空间中的线性关系,可以回答“男人之于国王,如同女人之于?”这类问题。
  • 文化知识:模型甚至能捕捉文化知识,例如“Australia : beer :: France : ?”的答案是“champagne”。

Word2Vec的变体与细节 ⚙️

原始的Word2Vec论文提出了两种模型:Skip-gram(用中心词预测上下文)和连续词袋模型(用上下文预测中心词)。Skip-gram更简单且效果很好。

在计算损失时,原始的朴素softmax需要对词汇表中的所有词进行计算,当词汇量很大时(例如40万),计算开销巨大。因此,人们提出了改进方法:

  • 负采样:不再计算所有词的softmax,而是构建一个更简单的目标:让模型能够区分真实的上下文词(正样本)和随机采样的一些非上下文词(负样本)。目标函数变为最大化正样本的得分,同时最小化负样本的得分。这里使用sigmoid(逻辑)函数将得分转化为概率。
  • 采样分布:选择负样本时,并非均匀随机,而是根据词的一元分布(即词频)进行采样,但通常会对词频取3/4次方,以适当提高低频词被采样的概率。

基于计数的替代方法:共现矩阵与GloVe 🔢

除了预测型方法,我们也可以直接从统计角度构建词向量。一个直观的想法是构建词-词共现矩阵:统计每个词在其上下文窗口中与其他词共同出现的次数。

然而,直接使用这个矩阵(维度为 词汇量 × 词汇量)作为词向量非常庞大且稀疏。因此,我们需要对其进行降维。常用方法是奇异值分解,它可以得到词的低维表示。早期的潜在语义分析就是基于此思想,但效果一般。

后来,研究者们改进了基于计数的方法,其中GloVe模型是一个重要代表。GloVe的核心思想是:共现概率的比率可以编码语义成分。例如,通过比较“ice”和“steam”分别与“solid”、“gas”的共现概率比值,可以捕捉到“固体/气体”这一语义维度。GloVe模型的目标是让两个词向量的点积尽可能接近它们共现概率的对数。

词向量的评估 📊

在自然语言处理中,评估方法分为两类:

  1. 内部评估:针对特定子任务进行快速评估,有助于理解模型组件本身,但可能与最终任务目标不直接相关。对于词向量,常见的内部评估任务有:

    • 词向量类比:计算模型在类比任务上的准确率。
    • 词相似度:计算模型给出的词对相似度与人工评判的相似度之间的相关性。
  2. 外部评估:在真实的下游任务(如命名实体识别、情感分析、机器翻译)中评估模型效果。这能直接反映模型的实用价值,但评估过程更复杂、更间接。例如,在命名实体识别任务中加入词向量特征,通常能显著提升系统性能。

词义与词向量 🎭

大多数词都有多个含义(例如,“bank”有“银行”和“河岸”之意)。如何处理多义词是词向量面临的一个挑战。

一种方法是进行词义消歧:根据上下文将词的每个出现实例聚类到不同的词义上,然后为每个词义学习独立的词向量。这种方法可行,但划分词义本身是主观且困难的。

实际上,更常见的做法是只学习一个词向量。这个向量是所有不同词义向量的加权平均(或称“叠加”)。这反映了语言学中的一个观点:词义并非离散的多个“义项”,而是一个在语义空间中的连续概率分布。有趣的是,借助稀疏编码理论,有时可以从这个单一向量中恢复出各个词义的分量。

神经网络分类器入门 🧮

现在,让我们开始接触神经网络分类器。以命名实体识别任务为例:我们需要根据词的上下文来判断其类别(如“地点”、“人名”或“非实体”)。

我们可以构建一个窗口分类器:取目标词及其前后若干个词构成一个窗口,将窗口中所有词的词向量拼接起来,输入到一个神经网络中进行分类。

一个简单的神经网络层可以表示为:
h = f(Wx + b)
其中,x是输入向量(如拼接的词向量),W是权重矩阵,b是偏置向量,f是一个非线性激活函数(如sigmoid函数)。通过一层或多层这样的变换,我们可以在网络末端连接一个线性分类器(如逻辑回归)来输出最终类别概率。

与传统的线性分类器(如逻辑回归、SVM)不同,神经网络同时学习词的表征和分类器的参数。这使得它在原始输入空间中可以表示非常复杂的非线性决策边界。

在实现时,我们通常使用交叉熵损失。在分类任务中,当真实标签是“one-hot”形式(即正确类别为1,其余为0)时,最小化交叉熵损失等价于最大化正确类别的对数似然(即最小化负对数似然)。

总结 🎯

本节课我们一起深入探讨了词向量。我们从Word2Vec的基本原理和优化方法出发,了解了其如何从大量文本中学习到蕴含丰富语义的词向量。我们还探讨了基于计数的GloVe模型,以及评估词向量的内部和外部方法。针对词的多义性,我们分析了离散词义表示与连续分布表示的优劣。最后,我们引入了神经网络分类器的基本概念,看到了如何将词向量应用于具体的分类任务,并初步了解了神经网络层的基本运算。下一周,我们将更深入地探讨神经网络的数学原理和应用。

3:反向传播与神经网络 🧠

在本节课中,我们将要学习神经网络的核心计算原理:反向传播算法。我们将从数学基础开始,理解如何计算复杂函数的梯度,然后探讨如何高效地实现这些计算,从而让神经网络能够通过梯度下降进行学习。


1. 神经网络的基本结构与非线性激活函数

上一节我们介绍了神经网络的基本概念,本节中我们来看看其核心计算单元。

神经网络由多层“神经元”组成,每一层对输入进行线性变换(矩阵乘法与偏置加法),然后通过一个非线性激活函数。这种结构使得神经网络能够学习输入数据中复杂的中间表示,这些表示对于最终完成预测任务(如判断一个词是否为地点)是有用的。

激活函数至关重要,因为仅靠线性变换的堆叠无法增加网络的表示能力。历史上,人们使用过多种激活函数:

  • 阈值单元:输出0或1,但缺乏梯度,不利于学习。
  • Sigmoid / Tanh:具有平滑的梯度,早期被广泛使用。Tanh本质上是缩放和平移后的Sigmoid:tanh(x) = 2 * sigmoid(2x) - 1
  • 修正线性单元 (ReLU):公式为 f(x) = max(0, x)。它在正区间梯度恒为1,计算高效,但负区间梯度为0(“神经元死亡”)。
  • Leaky ReLU / Parametric ReLU:为了解决ReLU的“死亡”问题,在负区间引入一个小的、固定的或可学习的斜率。
  • Swish / GELU:近年来在Transformer模型中常用的复杂激活函数,它们在大部分区间近似线性,但在负区间有特殊的曲线形态。

2. 梯度下降与矩阵微积分基础 🧮

神经网络的训练依赖于梯度下降。我们需要计算损失函数 J(θ) 关于所有参数 θ 的梯度 ∇θ J(θ),然后沿负梯度方向更新参数。

对于多变量函数,我们使用矩阵微积分(本质上是单变量微积分在高维的推广)来计算梯度。

  • 标量对向量的梯度:函数 f: R^n → R 的梯度是一个向量,包含对每个输入变量的偏导数:∇x f = [∂f/∂x1, ∂f/∂x2, ..., ∂f/∂xn]^T
  • 向量对向量的梯度(雅可比矩阵):函数 f: R^n → R^m 的雅可比矩阵 J 是一个 m×n 矩阵,其中 J[i,j] = ∂f_i/∂x_j。神经网络的一层计算就对应这样一个函数。
  • 链式法则:对于复合函数 f(g(x)),其导数为 (∂f/∂g) * (∂g/∂x)。在矩阵形式下,就是雅可比矩阵的乘积。

以下是几个关键运算的梯度示例:

  • 元素级激活函数:若 h = f(z),其中 f 逐元素作用,则 ∂h/∂z 是一个对角矩阵,对角线元素为 f'(z_i)
  • 线性层:对于 z = Wx + b,有 ∂z/∂x = W^T∂z/∂b = I(单位矩阵)。
  • 点积:对于 s = u^T h,有 ∂s/∂h = u^T

3. 手动推导一个简单网络的梯度 ✍️

让我们通过一个具体例子来实践。考虑一个用于中心词地点分类的简单网络:

  1. 计算隐藏层:z = Wx + b
  2. 应用激活函数:h = f(z)
  3. 计算得分:s = u^T h

我们的目标是计算标量得分 s 对各个参数 W, b, u 和输入 x 的梯度。

计算 ∂s/∂b
我们应用链式法则:∂s/∂b = (∂s/∂h) * (∂h/∂z) * (∂z/∂b)

  • ∂s/∂h = u^T
  • ∂h/∂z = diag(f'(z)) (对角矩阵)
  • ∂z/∂b = I
    因此,∂s/∂b = u^T ○ f'(z),其中 表示逐元素乘法(Hadamard积)。在实践中,我们常遵循形状约定,使梯度与参数形状一致,所以 ∂s/∂b 的结果就是向量 δ = (u ○ f'(z))

计算 ∂s/∂W
同样使用链式法则:∂s/∂W = (∂s/∂h) * (∂h/∂z) * (∂z/∂W)
注意前两项 (∂s/∂h)*(∂h/∂z) 与计算 ∂s/∂b 时相同,即上游梯度 δ^T。我们只需计算最后一项 ∂z/∂W。根据形状约定,最终结果为:
∂s/∂W = δ * x^T
这是一个外积,其形状与权重矩阵 W 相同。

这个推导过程揭示了反向传播的核心思想:共享计算。上游梯度 δ 被计算一次,然后用于更新所有与该层相关的参数(Wb)。


4. 反向传播算法:计算图与自动微分 ⚙️

上一节我们手动推导了梯度,本节中我们来看看如何系统化、高效地完成这个过程,这就是反向传播算法。

我们可以将任何计算表示为计算图,其中节点是变量或运算,边表示数据流。例如,我们的网络可以表示为:x → (Wx+b) → z → f(z) → h → (u^T h) → s

  • 前向传播:按照图的拓扑顺序,从输入到输出计算每个节点的值。
  • 反向传播:从输出开始,逆向计算梯度。
    • 每个节点接收来自其输出方向的上游梯度
    • 节点根据其本地运算计算局部梯度(即该运算输出的雅可比矩阵)。
    • 节点将上游梯度与局部梯度相乘,得到传递给每个输入的下游梯度
    • 如果一个节点有多个输出分支(如变量 y 被用于后续多个计算),则传递给该节点的梯度是来自所有下游分支梯度的

关键规则示例

  • 加法节点:梯度均匀分配。c = a + b,则 ∂L/∂a = ∂L/∂c∂L/∂b = ∂L/∂c
  • 乘法节点:梯度切换输入。c = a * b,则 ∂L/∂a = (∂L/∂c) * b∂L/∂b = (∂L/∂c) * a
  • Max节点:梯度路由到最大值输入。c = max(a, b),若 a > b,则 ∂L/∂a = ∂L/∂c∂L/∂b = 0

通过这种方式,反向传播的复杂度与前向传播同阶,避免了重复计算,非常高效。现代深度学习框架(如PyTorch)的核心就是基于计算图的自动微分系统。


5. 实现细节与梯度检查 🔍

在框架中实现一个新层时,我们需要定义其前向和反向传播函数。

  • 前向函数:接收输入,计算输出,并缓存中间结果(如输入值),供反向传播使用。
  • 反向函数:接收上游梯度,利用缓存的前向值计算局部梯度,然后返回传递给每个输入的下游梯度。

为了确保反向传播实现的正确性,我们可以使用梯度检查

梯度检查通过数值方法近似梯度:对于参数 θ,计算 (J(θ+ε) - J(θ-ε)) / (2ε)。这个数值梯度应该与我们反向传播计算出的解析梯度非常接近(例如,相对误差在 1e-7 量级)。虽然数值梯度计算缓慢,不适合训练,但它是验证代码正确性的强大工具。


总结

本节课中我们一起学习了神经网络训练的核心——反向传播。

  1. 我们回顾了神经网络的层式结构和各种非线性激活函数的作用。
  2. 我们建立了矩阵微积分的基础,理解了如何计算向量和矩阵函数的梯度。
  3. 我们手动推导了一个简单神经网络中所有参数的梯度,并理解了形状约定计算共享的重要性。
  4. 我们引入了计算图模型,系统地阐述了反向传播算法,即通过链式法则和高效的共享计算,从输出到输入逆向传播梯度。
  5. 最后,我们讨论了在框架中的实现方式以及使用梯度检查来验证正确性的实践方法。

理解这些原理不仅有助于你完成相关作业,更能让你在模型出现问题时(如梯度消失/爆炸)具备调试和思考的能力,而不仅仅是把神经网络当作一个黑箱魔法。

4:依存句法分析 🧠

在本节课中,我们将学习人类语言中的句法结构,特别是依存句法分析。我们将探讨为什么理解句子结构对语言理解至关重要,并介绍如何构建一个基于神经网络的依存句法分析器。


句法结构:两种视角

上一节我们介绍了课程背景,本节中我们来看看描述人类语言句法结构的两种主要方式。

短语结构语法

第一种方式是短语结构语法,计算机科学家通常称之为上下文无关文法。其核心思想是:单词可以归为少数基本类别(词性),这些类别可以组合成更大的单元(短语),进而构成句子。

以下是构建名词短语的规则示例:

NP -> Det (Adj)* N (PP)*

其中:

  • NP 代表名词短语
  • Det 代表限定词(如 the, a
  • Adj 代表形容词(如 large, cuddly
  • N 代表名词(如 cat, door
  • PP 代表介词短语(如 by the door
  • * 表示可以出现零次或多次

这种表示法通过树状图展示句子的层级结构,例如句子 Look in the large crate in the kitchen by the door 的短语结构树。

依存语法

第二种方式是依存语法,这也是本节课的重点。依存语法不关注短语的层级组合,而是关注词与词之间的非对称修饰关系,即一个词(核心词/中心词)如何支配或修饰另一个词(依存词)。

在依存结构中,我们用箭头连接核心词和依存词,箭头方向通常从核心词指向依存词。例如,在句子 Look in the large crate in the kitchen by the door 中:

  • 动词 look 是整个句子的核心。
  • look 支配介词短语 in the large crate
  • 名词 crate 是介词 in 的宾语,同时被形容词 large、介词短语 in the kitchenby the door 所修饰。

这种表示法能更直接地体现“谁修饰谁”的语义关系。


句法歧义与理解的重要性

理解了基本结构后,我们来看看为什么句法分析如此重要:因为自然语言充满了歧义

人类交流是线性的词流或音流,但理解过程需要将其转化为结构化的意义,明确词与词之间的修饰关系。许多句子有不止一种合法的句法分析,导致多种可能的含义。

以下是几种常见的歧义类型及其示例:

  • 介词短语附着歧义:介词短语可以修饰前面不同的成分。

    • 例句:Scientists count whales from space.
    • 解读1:科学家在太空数鲸鱼。(from space 修饰 count
    • 解读2:科学家数来自太空的鲸鱼。(from space 修饰 whales
  • 并列结构范围歧义:并列连词连接的范围不明确。

    • 例句:Shuttle veteran and longtime NASA executive Fred Gregory appointed to board.
    • 解读1:一个人(Fred Gregory),他既是航天老将又是NASA高管。
    • 解读2:两个人,一个航天老将和一位NASA高管(Fred Gregory)。
  • 不定式附着歧义:不定式短语可以修饰不同的先行词。

    • 例句:Mutilated body washes up on Rio beach to be used for Olympic beach volleyball.
    • 解读1:里约海滩将被用于奥运沙滩排球。
    • 解读2:残缺的尸体将被用于奥运沙滩排球。

这些歧义无法像编程语言那样通过局部规则完全消除,人类(和模型)需要依赖上下文和世界知识来选择最合理的解释。因此,构建能够分析句子结构的模型对于正确理解语言至关重要。


依存句法分析详解

本节我们将深入探讨依存句法的具体细节、历史以及如何通过数据驱动的方法来构建分析器。

依存关系与树结构

在依存语法中,句子结构被表示为一棵依存树

  • 树中的节点是句子中的词(通常添加一个虚拟的ROOT节点作为起点)。
  • 树中的边(箭头)表示从核心词到依存词的语法关系(如 nsubj 名词主语、dobj 直接宾语、amod 形容词修饰语等)。
  • 通常假设树是投射性的,即依存弧不会交叉。但自然语言中确实存在非投射性(交叉)依存,例如疑问句中的成分移位(Who did Bill buy the coffee from?)。

从规则到数据:树库的兴起

早期NLP试图用手写规则来刻画句法,但面临覆盖度低和无法处理歧义两大难题。从20世纪80年代末开始,领域转向了数据驱动的方法:人工标注大量句子的句法结构(构建树库),然后在其上训练统计或机器学习模型。

树库(如通用依存树库)提供了:

  1. 可重用的训练数据:用于训练句法分析器等模型。
  2. 分布频率信息:帮助模型学习常见的修饰模式。
  3. 系统评估标准:可以客观衡量不同分析器的性能。

基于转移的依存句法分析

有了标注数据,我们如何自动分析新句子的结构呢?最流行的方法之一是基于转移的句法分析。其核心思想是模拟一个状态机,通过一系列“转移动作”逐步构建依存树。

分析器维护两个主要数据结构:

  • :存放已处理但尚未确定依存关系的词和短语片段。
  • 缓冲区:存放待处理的输入词。

分析从初始状态(栈中只有 ROOT,缓冲区有全部输入词)开始,通过反复执行以下三种基本动作之一,直到达到终止状态(缓冲区为空,栈中只剩 ROOT):

  1. 移进:将缓冲区第一个词移到栈顶。
  2. 左弧:将栈顶第二个词作为核心词,栈顶第一个词作为其依存词,建立从左向右的弧(核心词在右),然后将依存词(栈顶词)弹出栈。
  3. 右弧:将栈顶第一个词作为核心词,栈顶第二个词作为其依存词,建立从右向左的弧(核心词在左),然后将依存词(栈顶第二个词)弹出栈。

通过选择不同的动作序列,可以构建出不同的依存树。而机器学习模型的任务,就是在每个分析状态,预测下一步应该采取哪个动作


神经依存句法分析

基于转移的框架很高效(线性时间复杂度),但早期模型使用稀疏的符号特征,存在特征组合爆炸、数据稀疏等问题。神经网络的引入解决了这些问题。

神经依存分析器的架构

神经依存分析器使用稠密的分布式表示(词向量、词性标签向量、依存关系标签向量)来替代稀疏的符号特征。在分析的每一步,模型抽取当前状态的关键信息(如栈顶的两个词、缓冲区的第一个词及其词性、已有的依存关系等),将它们对应的向量拼接成一个大的输入向量 x

然后,该向量被送入一个神经网络进行分类。一个典型的架构是:

h = ReLU(W1 * x + b1)        # 隐藏层
scores = W2 * h + b2         # 得分层
probabilities = softmax(scores) # 得到各动作的概率分布

其中 W1, b1, W2, b2 是可学习的参数。模型输出 ShiftLeft-ArcRight-Arc 等动作的概率,选择概率最高的动作执行。

这种方法的优势在于:

  • 泛化能力强:即使遇到未见过词的具体组合,相似的词向量也能提供有用的信号。
  • 特征计算高效:避免了手工特征组合的计算开销。
  • 非线性建模:神经网络能够捕捉更复杂的特征交互。

另一种方法:基于图的依存分析

除了基于转移的方法,还有基于图的依存分析。其思路不同:为句子中每对可能的词(i, j)计算一个分数,表示 j 作为 i 的依存词的可能性(或反之)。然后,任务转化为寻找一个最大生成树(或最小代价树),使得所有词都被连接起来形成一棵树,并且树的总分数最高(或总代价最小)。

神经方法也可以用于基于图的分析器,通过神经网络来更精准地计算词对之间的关联分数。这类分析器通常比贪婪的基于转移的分析器准确度略高,但计算复杂度也更高。


总结

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

  1. 句法结构的两种表示:短语结构语法和依存语法,后者直接刻画词与词之间的修饰关系。
  2. 句法歧义的重要性:自然语言句子普遍存在多种结构解释,理解结构是理解语义的基础。
  3. 数据驱动的句法分析:通过标注树库训练模型,是现代句法分析的主流方法。
  4. 基于转移的神经依存分析:通过维护栈和缓冲区状态,使用神经网络预测一系列动作(移进、左弧、右弧)来线性地构建依存树,兼顾了效率与准确率。
  5. 分析器的评估:通过比较预测的依存弧与标注数据中的正确弧,计算未标记依存准确率或标记依存准确率。

句法分析是深入理解语言的基础工具。虽然当今的大语言模型看似直接处理词序列,但其内部机制很可能隐含地构建并利用了类似的结构化信息。掌握句法分析的基本原理,将帮助你更好地理解语言模型的运作方式。

5:循环神经网络 🧠

在本节课中,我们将学习语言模型的基本概念,并深入探讨一种用于构建语言模型的神经网络架构——循环神经网络。我们将从传统的N-gram语言模型讲起,逐步过渡到基于神经网络的模型,并重点介绍循环神经网络的工作原理、训练方法及其应用。


概述:从统计模型到神经网络

上一节我们介绍了神经网络的基础概念和优化技巧。本节中,我们将聚焦于自然语言处理中的一个核心任务:语言建模。语言模型旨在预测序列中下一个词出现的概率,是许多NLP应用(如输入法预测、搜索引擎补全)的基础。我们将首先回顾基于统计的N-gram模型,然后探讨如何用神经网络,特别是循环神经网络,来构建更强大的语言模型。


传统方法:N-gram语言模型

在神经网络流行之前,N-gram语言模型是构建语言模型的主流方法。其核心思想是:仅使用最近的 n-1 个词来预测下一个词,这被称为马尔可夫假设。

N-gram模型的定义

一个N-gram模型通过统计文本中短序列(N-gram)的出现频率来估计概率。公式如下:

P(w_t | w_{t-1}, w_{t-2}, ..., w_{t-n+1}) ≈ Count(w_{t-n+1}, ..., w_t) / Count(w_{t-n+1}, ..., w_{t-1})

例如,在一个三元组(trigram)模型中,我们使用前两个词来预测第三个词。

N-gram模型的优缺点

以下是N-gram模型的主要特点:

  • 优点:模型简单,易于构建。只需在大型文本语料库中计数即可。
  • 缺点
    • 稀疏性问题:随着N增大,需要存储的N-gram数量呈指数级增长,且许多序列在训练数据中从未出现,导致概率估计为零。
    • 上下文有限:受限于固定的窗口大小 n,无法利用更早的上下文信息。
    • 缺乏泛化:模型本质上只是记忆和检索固定的词组,无法理解词与词之间的深层关系。

尽管通过“加平滑”和“回退”等技术可以缓解稀疏性问题,但N-gram模型的能力存在根本性限制。这促使研究者转向能够捕捉更长距离依赖关系的神经网络模型。


神经网络语言模型

为了克服N-gram模型的局限性,我们可以使用神经网络来构建语言模型。最初的尝试是固定窗口神经网络语言模型

模型架构

该模型将固定长度的上下文词向量拼接起来,输入到一个前馈神经网络中,网络的输出层是一个Softmax层,用于预测词汇表中所有词作为下一个词的概率。

h = f(W * concat(x_{t-1}, x_{t-2}, ..., x_{t-n}) + b)
P(w_t) = softmax(U * h + c)

优势与局限

与N-gram模型相比,神经网络模型通过连续的词向量表示和隐藏层计算,提供了更好的泛化能力,并解决了稀疏性问题。然而,它仍然存在两个主要缺陷:

  1. 固定窗口:模型只能看到固定数量的上文,无法处理任意长度的序列。
  2. 参数效率低:同一个词出现在窗口的不同位置时,是由网络中完全不同的参数处理的,这不符合语言直觉(词的意义应与其位置相对独立)。

为了解决这些问题,我们需要一种能够处理变长输入并共享参数 across 时间的网络架构,这就是循环神经网络


循环神经网络

循环神经网络是一种专为序列数据设计的神经网络。它的核心思想是引入一个“隐藏状态”,该状态在每一步都会被更新,并作为对之前所有已见信息的“记忆”。

RNN的基本结构

在每一个时间步 t,RNN执行以下操作:

  1. 接收当前输入词向量 x_t
  2. 结合上一个时间步的隐藏状态 h_{t-1}
  3. 计算新的隐藏状态 h_t
  4. 基于 h_t 预测下一个词的概率分布。

其核心计算公式如下:

h_t = σ(W_h * h_{t-1} + W_e * x_t + b)

其中,σ 通常是非线性激活函数,如 tanh。用于预测下一个词的公式为:

P(w_{t+1}) = softmax(U * h_t + c)

RNN的优势

  • 处理任意长度输入:理论上可以处理无限长的序列。
  • 参数共享:矩阵 W_hW_e 在每个时间步被重复使用,大大减少了参数量,并允许模型在不同位置以相同方式处理相同的词。
  • 捕捉长距离依赖:隐藏状态理论上包含了从序列开始到当前的所有信息。

RNN的挑战

  • 顺序计算:由于每一步的计算都依赖于前一步的结果,RNN无法进行高效的并行计算,训练速度较慢。
  • 梯度消失/爆炸:在反向传播时,梯度需要跨越多个时间步进行链式相乘。这可能导致梯度变得极小(消失)或极大(爆炸),使得模型难以学习长距离的依赖关系。这是简单RNN在实际中“遗忘”较早信息的主要原因。

训练RNN语言模型

训练RNN语言模型的目标是最大化模型对整个训练语料库的预测概率。

损失函数

我们使用交叉熵损失。对于序列中的每个位置,我们计算模型预测的概率分布与真实下一个词的one-hot向量之间的交叉熵。整个序列的损失是各个时间步损失的平均值。

J(θ) = - (1/T) * Σ_{t=1}^{T} log P_θ(w_t | w_{<t})

教师强制

在训练时,我们通常使用教师强制 策略。即,在每个时间步,我们将真实的上一词(来自训练数据)作为输入,而不是将模型自己生成的词作为输入。这简化了训练过程,使模型能更快地学习到数据中的规律。

通过时间反向传播

为了计算损失函数对参数(如 W_h, W_e)的梯度,我们需要使用通过时间反向传播 算法。其要点是:

  1. 沿着时间轴展开RNN计算图。
  2. 像训练一个非常深的前馈网络一样进行反向传播。
  3. 由于参数 W_hW_e 在每个时间步被复用,最终的梯度是每个时间步梯度之和。

在实践中,为了计算效率,我们通常会将长序列切分成较短的片段(例如100个词)进行训练,这被称为截断的BPTT


文本生成与RNN应用

训练好的RNN语言模型可以用于文本生成。生成过程是一个“滚出”过程:

  1. 给定一个起始符号(如 <s>)和初始隐藏状态(通常为零向量)。
  2. 模型输出一个概率分布,我们从中采样得到一个词。
  3. 将这个生成的词作为下一个时间步的输入,并更新隐藏状态。
  4. 重复步骤2和3,直到生成结束符号(如 </s>)或达到长度限制。

应用示例

RNN不仅可用于词语级别的生成,还可用于字符级别,从而创造出新词或模仿特定风格(如生成油漆颜色名称)。通过将隐藏状态初始化为特定条件(如一个RGB颜色值),我们可以构建条件生成模型。


总结

本节课我们一起学习了语言模型的基本概念及其演进。我们从基于计数的N-gram模型出发,指出了其上下文有限和稀疏性的问题。接着,我们探讨了如何用神经网络构建语言模型,并重点介绍了循环神经网络这一关键架构。RNN通过其循环结构和共享参数的特性,能够处理变长序列并捕捉一定程度的上下文依赖。我们详细讲解了RNN的前向计算、通过时间反向传播的训练方法,以及如何用它进行文本生成。尽管基础的RNN存在训练慢和难以学习长程依赖的挑战,但它为理解更强大的序列模型(如LSTM、GRU,以及我们后续会讲到的Transformer)奠定了重要基础。

6:序列到序列模型 🧠➡️📝

在本节课中,我们将深入学习语言模型和循环神经网络(RNN),特别是介绍一种更高级的循环神经网络变体——长短期记忆网络(LSTM)。我们还将探讨如何将RNN应用于一个核心NLP任务:神经机器翻译。


语言模型评估与困惑度 📊

上一节我们介绍了语言模型,它是一种预测下一个单词的系统。本节中,我们来看看如何更严谨地评估语言模型。

标准评估方法是使用困惑度。语言模型为一段文本(通常是人类撰写的)分配一个概率分数。模型预测文本中连续单词的能力越强,其困惑度就越低,模型也就越好。

困惑度本质上是交叉熵损失的指数形式。其计算公式如下:

困惑度 = exp(交叉熵损失)

困惑度数值可以直观地理解为模型在每个时间步需要做出的“均匀选择”的数量。例如,困惑度为64意味着模型相当于在每一步掷一个64面的骰子来猜测正确单词。

以下是不同模型在语言建模任务上的困惑度进展:

  • N-gram模型(使用Kneser-Ney平滑):困惑度约为67。
  • 早期RNN模型:与最大熵模型结合后,困惑度可达51。
  • LSTM模型:显著提升,困惑度可降至43甚至30。

现代最先进的语言模型困惑度已降至个位数,表明其预测能力非常强大。


RNN的挑战:梯度消失与爆炸 ⚠️

在训练RNN时,我们通过反向传播损失来更新参数。当沿着长序列反向传播时,我们需要连续乘以一系列偏导数(雅可比矩阵)。这会导致两个主要问题:

  1. 梯度消失:当这些连续相乘的矩阵特征值小于1时,梯度会随着反向传播而指数级缩小,直至消失。这使得模型难以学习长距离的依赖关系。
  2. 梯度爆炸:当特征值大于1时,梯度会指数级增长,导致参数更新过大,模型训练不稳定甚至崩溃。

梯度消失问题尤其严重,它限制了RNN的“有效记忆”长度。简单的RNN通常只能有效利用大约7个时间步之前的信息,这并不比传统的5-gram模型有显著优势。

处理梯度爆炸:一个简单而有效的技巧是梯度裁剪。其核心思想是,如果计算出的梯度范数超过一个阈值(例如5、10或20),就按比例缩小整个梯度向量,使其范数等于该阈值。

如果 ||g|| > 阈值:
    g = (阈值 / ||g||) * g

处理梯度消失:这需要更根本的架构改进,从而引出了LSTM。


长短期记忆网络(LSTM) 🧠

为了解决梯度消失和长期记忆问题,研究者提出了LSTM。其核心思想是引入一个细胞状态 作为“传送带”,专门用于长期保存信息,并通过门控机制 精细控制信息的流入、保留和流出。

LSTM在每一步计算三个门控向量(遗忘门 f_t、输入门 i_t、输出门 o_t),每个向量的值在0到1之间。以下是LSTM单元的核心计算公式:

# 门控计算(共享相似结构)
f_t = σ(W_f * [h_{t-1}, x_t] + b_f)  # 遗忘门:决定从细胞状态中丢弃什么信息
i_t = σ(W_i * [h_{t-1}, x_t] + b_i)  # 输入门:决定哪些新信息存入细胞状态
o_t = σ(W_o * [h_{t-1}, x_t] + b_o)  # 输出门:决定基于细胞状态输出什么

# 候选细胞状态
~C_t = tanh(W_C * [h_{t-1}, x_t] + b_C)

# 更新细胞状态(关键步骤:加法取代乘法)
C_t = f_t ⊙ C_{t-1} + i_t ⊙ ~C_t

# 更新隐藏状态
h_t = o_t ⊙ tanh(C_t)

LSTM的关键优势在于细胞状态的更新公式中的加法操作。与简单RNN中连续的矩阵乘法不同,加法操作使得梯度可以更稳定地流动,有效缓解了梯度消失问题,让网络能够学习长距离依赖。


RNN/LSTM的应用与变体 🔧

掌握了基础架构后,我们来看看RNN/LSTM在NLP中的多种应用及其常见变体。

主要应用包括

  • 序列标注:如词性标注、命名实体识别,为每个输入单词分配一个标签。
  • 句子编码:用于情感分析等任务。可以取最后一个隐藏状态,或对所有隐藏状态进行池化(如平均或最大池化)作为句子表示。
  • 条件语言模型:用于机器翻译、摘要生成、语音识别等。模型根据输入源(如另一种语言的句子)来生成目标文本。

常见架构变体

  • 双向RNN/LSTM:同时运行前向和后向RNN,并将每个时间步的两个隐藏状态连接起来。这为每个单词提供了包含左右上下文的表示,常用于编码器,但不用于文本生成。
  • 堆叠RNN/LSTM:使用多个RNN层,深层网络可以学习更抽象的特征表示。在实践中,2到3层的堆叠LSTM很常见,并能带来性能提升。
  • 残差连接与高速网络:受计算机视觉中ResNet的启发,在RNN层之间添加捷径连接,有助于缓解深度网络中的梯度消失问题。

神经机器翻译 🌐➡️🌐

机器翻译是NLP的核心任务之一。早期基于规则的尝试效果有限,后来统计机器翻译(SMT)取得了进展,但其系统复杂且由多个独立模块组成。

神经机器翻译(NMT)的革命性在于使用一个单一的、端到端的神经网络来完成翻译任务,通常采用序列到序列 架构。

Seq2Seq模型核心

  1. 编码器:通常是一个RNN(如LSTM),读取并编码整个源语言句子。其最终隐藏状态被视为整个句子的“语义摘要”。
  2. 解码器:另一个RNN(如LSTM),以编码器的最终隐藏状态为初始条件,开始生成目标语言单词。它是一个条件语言模型,在每一步基于已生成的部分翻译和源句编码来预测下一个单词。

训练过程:使用平行语料库(源句-目标句对)。将源句输入编码器,解码器以编码器状态为起点,并尝试逐词预测真实的目标句。计算每个位置的预测损失,平均后通过反向传播同时更新编码器和解码器的所有权重,实现端到端训练。

神经机器翻译在2014年左右出现后,其翻译质量迅速超越耕耘了数十年的统计机器翻译系统,并在短短几年内被各大科技公司广泛部署,成为深度学习在NLP领域第一个重大成功案例。


总结 📚

本节课我们一起学习了以下核心内容:

  • 语言模型评估:使用困惑度作为标准评估指标。
  • RNN的局限性:梯度消失和爆炸问题严重限制了简单RNN处理长序列依赖的能力。
  • LSTM:通过引入细胞状态和门控机制(遗忘门、输入门、输出门),利用加法操作稳定梯度流,显著提升了长期记忆和建模能力。
  • RNN/LSTM的应用:可用于序列标注、句子编码和作为条件语言模型。
  • 神经机器翻译:采用编码器-解码器(Seq2Seq)框架,用一个端到端的神经网络实现了高质量的翻译,是深度学习在NLP中的里程碑式应用。

通过学习,你现在应该理解为何LSTM能有效解决传统RNN的长期依赖问题,并掌握序列到序列模型在机器翻译等任务上的基本原理。

7:注意力机制与期末项目 🧠

在本节课中,我们将首先学习如何评估机器翻译的质量,然后深入探讨一个非常核心且强大的概念——注意力机制。注意力机制最初在机器翻译的背景下被提出,如今已成为神经网络,特别是Transformer架构中的基础组件。最后,我们将介绍本课程的期末项目安排。

机器翻译评估:BLEU分数 📊

上一讲我们介绍了基于多层LSTM的机器翻译系统。该系统将源语言句子编码,然后使用解码器逐词生成目标语言翻译。为了判断翻译质量的好坏,我们需要一个评估标准。

在机器翻译领域,人们提出了数百种自动评估方法。然而,至今最常用、最具影响力的指标是BLEU。在深度学习时代之前,评估翻译的唯一可靠方法是人工评判。虽然人工评估仍是黄金标准,但为了快速迭代和模型训练,我们需要自动化的评估方法。

BLEU的核心思想是:将机器翻译的输出与一个或多个人工翻译的参考译文进行比较。它通过计算机器翻译与参考译文之间重叠的n-gram(如1-gram, 2-gram, 3-gram, 4-gram)数量来打分。重叠越多,分数越高。

以下是BLEU评估的一个简单示例:

  • 参考译文1: the airport is on the west side of the city
  • 参考译文2: the international airport is located west of the city
  • 机器翻译: the airport is west of the city

我们可以找到重叠的n-gram,如 the airportwest of the city 等。BLEU分数理论上在0到100之间,但几乎不可能达到100,因为翻译存在多种可能性。通常,分数达到20+意味着翻译基本可理解,达到30+或40+则表明翻译质量已经相当不错。

BLEU也存在一些缺陷,例如,好的翻译可能因为用词与参考译文不同而得分低,而差的翻译也可能因为偶然匹配到一些词而得分。此外,BLEU还包含一个针对过短翻译的惩罚项,以防止系统只翻译简单的部分。

注意力机制的引入 🔍

在上一讲介绍的编码器-解码器架构中,编码器需要将整个源句子的信息压缩到最后一个隐藏状态向量中,然后解码器仅基于这一个向量来生成整个翻译。对于长句子来说,这是一个信息瓶颈,显得不太合理。

人类翻译时,并不是一次性记住整个句子再翻译,而是在翻译每个词时,会回看源句子中相关的部分。注意力机制正是模拟了这一过程。

注意力机制的核心思想是:在解码器生成目标语言的每一个词时,都让它能够直接“查看”编码器在所有时间步的隐藏状态,并从中选取最相关的信息。

注意力机制的工作原理

以下是注意力机制的工作步骤图示与说明:

  1. 计算注意力分数:在解码器的每个时间步 t,我们有一个解码器隐藏状态 s_t。我们将 s_t 与编码器的每一个隐藏状态 h_1, h_2, ..., h_n 进行比较,为每个编码器位置计算一个注意力分数 e_{t,i}。这个分数表示在生成当前目标词时,源句子第 i 个词的重要性。
  2. 生成注意力分布:将所有位置的注意力分数通过 softmax 函数进行归一化,得到一个概率分布 α_t。这个分布就是“注意力权重”,它告诉我们解码器应该“关注”源句子每个部分的程度。
    • 公式: α_t = softmax(e_t)
  3. 计算上下文向量:使用注意力权重对编码器的所有隐藏状态进行加权求和,得到一个上下文向量 c_t。这个向量融合了源句子中与当前生成词最相关的信息。
    • 公式: c_t = Σ_i (α_{t,i} * h_i)
  4. 生成输出:将上下文向量 c_t 与解码器当前的隐藏状态 s_t 连接起来,形成一个更丰富的表示。然后将这个组合向量输入到一个前馈层和softmax层,以预测下一个要输出的词。

通过这种方式,模型在翻译“hit”时,可能会更多地关注源句中的“frappe”;在翻译“me”时,则会关注“moi”。这提供了很好的可解释性,我们可以通过可视化注意力权重来理解模型在翻译每个词时正在“看”源句子的哪个部分。

注意力分数的不同计算方式

上面提到,计算注意力分数 e_{t,i} 有多种方法:

  • 点积注意力: e_{t,i} = s_t^T * h_i
    • 最简单直接,但要求 s_th_i 维度相同,且假设所有维度都同等重要地用于匹配。
  • 乘法注意力(双线性注意力): e_{t,i} = s_t^T * W * h_i
    • 引入一个可学习的权重矩阵 W,让模型学习如何更有效地匹配解码器和编码器的信息。参数较多。
  • 加性注意力: e_{t,i} = v^T * tanh(W_1 * h_i + W_2 * s_t)
    • 使用一个小型神经网络来计算分数,更为灵活,但计算更复杂。
  • 缩放点积注意力(Transformer所用): e_{t,i} = (W_q s_t)^T * (W_k h_i) / sqrt(d_k)
    • 这是当前最主流的方法。先将查询(s_t)和键(h_i)通过不同的权重矩阵(W_q, W_k)投影到低维空间,再进行点积,并除以一个缩放因子以稳定训练。

注意力机制极大地提升了神经机器翻译的性能,解决了信息瓶颈和梯度消失问题,并因其可解释性而备受青睐。它现在已成为各种神经网络架构中的通用模块。

期末项目介绍 📝

课程成绩的49%来自于期末项目。项目有两种选择:默认项目自定义项目。团队规模为1-3人。

项目选择

  • 默认项目:围绕一个简化的BERT模型展开。你需要完成其实现,在情感分析任务上进行微调,并在此基础上进行扩展(例如,尝试对比学习、低秩适配等技术)。该项目提供明确指导和排行榜,适合缺乏研究经验或希望有清晰目标的同学。
  • 自定义项目:你可以选择自己感兴趣的研究课题。项目必须实质性地涉及自然语言和神经网络。例如,可以研究多模态模型、大语言模型的上下文学习、模型可解释性分析等。这适合有明确想法、希望体验完整研究流程的同学。

项目流程与要求

  1. 项目提案:需要提交一份最多4页的提案,其中必须包含:
    • 研究论文评述:对与你课题相关的一篇关键论文进行批判性总结(占大部分分数)。
    • 项目计划:清晰阐述你的目标、方法、将要使用的数据集、评估指标和基线模型。
    • 伦理考量:分析项目若部署到现实世界可能面临的伦理挑战及缓解措施。
  2. 项目中期检查:用于确保项目进度正常,例如应已搭建好实验环境并可以运行基线模型。
  3. 最终提交:提交项目代码和一份最多8页的项目报告。报告应像学术论文一样,包含摘要、引言、相关工作、方法、实验、结果分析和结论。评分将主要基于这份报告。

计算资源 💻

由于当前GPU资源紧张,课程提供的免费云计算资源有限。你需要发挥创造性来获取资源:

  • 每位同学可获得$50的Google Cloud Platform(GCP)信用。
  • 利用各大云平台(如Google Colab, Kaggle, AWS SageMaker Lab)的新用户免费额度。
  • 考虑低成本GPU提供商(如Modal, Fast.ai)。
  • Together AI 为课程提供了$50的API额度,可用于访问各类大语言模型,适合进行大模型相关的实验项目。

项目构思建议

在构思自定义项目时,可以考虑以下方向:

  • 应用型:针对某个具体任务(如信息抽取、文本生成)构建或微调模型。
  • 方法改进型:提出或尝试一种新的神经网络技术或训练方法。
  • 大模型应用:利用大语言模型的API进行上下文学习、智能体构建或程序生成。
  • 分析型:分析模型的内部工作机制(可解释性)或其在某些语言现象上的行为。
  • 理论型:对某些模型或算法的性质进行理论分析。

关键建议:无论选择哪种项目,确保有一个合适的基线模型进行比较,并规划好可行的实验评估。项目的价值在于你所做的增量贡献深入分析

总结 🎯

本节课我们一起学习了两个主要内容:

  1. 机器翻译的BLEU评估方法:这是一种基于n-gram重叠的自动化评估指标,尽管不完美,但仍是领域标准。
  2. 注意力机制:这是一个革命性的概念,它允许解码器在生成每个词时动态地关注源句子的不同部分,从而显著提升了序列到序列模型(如机器翻译)的性能,并解决了长序列信息压缩的瓶颈问题。我们了解了其工作原理、不同分数计算方式及其带来的优势。
  3. 期末项目安排:我们详细介绍了课程期末项目的两种形式、具体要求、时间节点以及可用的计算资源,为同学们启动自己的研究项目提供了清晰的指引。

从下一讲开始,我们将进入Transformer架构的学习,而注意力机制正是其最核心的组成部分。

8:自注意力机制和Transformer模型 🎓

课程概述

在本节课中,我们将要学习自注意力机制和Transformer模型。这些概念是现代自然语言处理乃至更广泛人工智能领域大多数进展的基础,是一个非常有趣且重要的主题。


回顾:循环神经网络的局限性

上一节我们介绍了基于循环神经网络(RNN/LSTM)和注意力机制的经典架构。本节中,我们来看看这些架构存在的一些问题。

循环神经网络存在两个主要问题:

  1. 线性交互距离问题:RNN按顺序(从左到右或从右到左)处理序列。虽然相邻词语(如“美味的披萨”)经常相互影响,但长距离依赖(如句子“那位去商店买了食材、喜欢大蒜的厨师……是……”中“厨师”和“是”的关系)需要信息经过许多时间步才能交互。梯度需要从“是”传播回“厨师”,这使得学习长距离依赖变得困难。LSTM在长距离梯度建模上比简单RNN更好,但并不完美。

  2. 顺序计算与并行化问题:RNN的前向和反向传播具有 O(序列长度) 量级的不可并行化操作。要计算时间步 t 的隐藏状态,必须先计算时间步 t-1 的状态,依此类推。这形成了一个计算图,随着序列长度增长,无法并行化的操作数量也线性增长,无法充分利用GPU的并行计算能力。


自注意力机制:核心思想

如果不使用循环,那用什么?答案是注意力机制。

注意力机制将每个词的表示视为一个查询,用于访问并整合一组值中的信息。与RNN不同,自注意力机制允许序列中的每个词直接与所有其他词(包括距离很远的词)进行交互。

以下是自注意力机制的关键优势:

  • 解决线性交互距离:无论词语相距多远,都可以通过注意力直接建立联系。
  • 解决并行化问题:可以同时计算序列中所有词的注意力表示,因为计算一个词的注意力不需要依赖其他词的计算结果。

自注意力机制的数学描述

让我们更深入地探讨自注意力的数学原理。自注意力可以看作是在一个“键-值”存储中进行模糊查找。

  • 传统查找表:给定一个查询,精确匹配一个键,返回对应的值。
    • 公式output = value[key == query]
  • 注意力机制:查询与所有键进行软匹配(计算相似度),然后对值进行加权求和。
    • 公式
      1. 相似度得分:e_ij = Query_i · Key_j
      2. 注意力权重:α_ij = softmax(e_ij)
      3. 输出:Output_i = Σ_j (α_ij * Value_j)

具体计算步骤如下:

  1. 我们有一个词序列 w_1, ..., w_n
  2. 通过嵌入矩阵 E 将每个词转换为词向量 x_i ∈ R^d
  3. 使用三个不同的可学习权重矩阵(W_Q, W_K, W_V)将每个词向量 x_i 分别转换为查询向量 q_i、键向量 k_i 和值向量 v_i
    • 代码描述
      q_i = x_i @ W_Q  # 形状: (d,)
      k_i = x_i @ W_K  # 形状: (d,)
      v_i = x_i @ W_V  # 形状: (d,)
      
  4. 对于序列中的每个位置 i,计算其查询 q_i 与所有位置 j 的键 k_j 的点积,得到相似度得分 e_ij
  5. e_ii 对所有 j 的得分)应用softmax函数,得到归一化的注意力权重 α_ij
  6. 输出 o_i 是所有权重 α_ij 与对应值向量 v_j 的加权和。

构建最小自注意力模块:需要解决的问题

基本的自注意力机制不能直接替代LSTM,我们需要解决几个问题才能将其作为基础构建模块。

1. 序列顺序问题

自注意力本身是对集合的操作,没有内置的词语位置信息。例如,句子“Zuko made his uncle”和“His uncle made Zuko”经过自注意力计算后会得到完全相同的表示,但这显然不对。

解决方案:位置编码
我们将每个序列位置(1, 2, 3, ...)也表示为一个 d 维向量 p_i,然后将其加到词嵌入 x_i 上。

  • 正弦位置编码:使用不同频率的正弦和余弦函数生成位置向量,可能有助于外推到更长的序列。
  • 可学习位置编码:直接学习一个位置嵌入矩阵 P ∈ R^(d×N),其中 N 是最大序列长度。这种方法简单有效,但无法处理长度超过 N 的序列。

2. 非线性问题

基本的自注意力只是一系列线性操作(加权平均)。如果重复应用自注意力而不引入非线性,最终只是在反复平均值向量。

解决方案:前馈网络
在每个自注意力层之后,为每个位置的输出向量独立地应用一个前馈神经网络(通常是两层MLP,中间有非线性激活函数,如ReLU)。

  • 代码描述
    # 假设 attention_output 形状为 (n, d)
    ff_output = gelu(attention_output @ W1 + b1) @ W2 + b2  # W1: (d, d_ff), W2: (d_ff, d)
    

这为模型提供了非线性变换能力,是“深度学习魔法”的关键部分。

3. 信息泄露问题(用于解码)

在诸如机器翻译或语言建模的任务中,在预测当前位置时,我们不能“偷看”未来的词。

解决方案:掩码
我们仍然计算所有词对之间的注意力得分 e_ij,但在计算softmax之前,将未来位置(j > i)的得分 e_ij 设置为一个极大的负数(如 -1e9)。这样,在softmax之后,未来位置的注意力权重就变成了0。

  • 代码描述
    # 创建一个下三角矩阵(包含对角线)作为掩码
    mask = torch.tril(torch.ones(seq_len, seq_len))
    # 将未来位置的得分设置为负无穷
    scores = scores.masked_fill(mask == 0, float('-inf'))
    attention_weights = F.softmax(scores, dim=-1)
    

Transformer架构详解 🏗️

上面介绍的是最小自注意力架构。在实践中,广泛使用的是Transformer架构,它引入了一些关键改进。

核心组件1:多头注意力

单一的注意力头可能难以同时捕捉词语间多种不同类型的关系。例如,在表示“学习”这个词时,我们可能想同时关注“斯坦福CS224N”(实体信息)和“我去……学习”(句法信息)。

多头注意力允许模型在不同的表示子空间中并行地关注序列的不同部分。

  1. 将模型维度 d 划分为 h 个头,每个头的维度为 d_h = d / h
  2. 为每个头 l 使用独立的可学习投影矩阵 W_Q^l, W_K^l, W_V^l,将输入投影到 d_h 维。
  3. 在每个头上独立地计算缩放点积注意力。
  4. 将所有头的输出连接起来,最后通过一个线性层 W_O 进行混合。
  • 矩阵运算形式(高效实现)
    # Q, K, V 形状: (batch, seq_len, d)
    batch, seq_len, d = Q.shape
    # 重塑为 (batch, seq_len, num_heads, d_k)
    Q = Q.view(batch, seq_len, num_heads, d_k).transpose(1, 2)
    K = K.view(batch, seq_len, num_heads, d_k).transpose(1, 2)
    V = V.view(batch, seq_len, num_heads, d_k).transpose(1, 2)
    # 计算注意力,然后重塑回来
    attn_output = scaled_dot_product_attention(Q, K, V, mask) # 形状: (batch, num_heads, seq_len, d_k)
    attn_output = attn_output.transpose(1, 2).contiguous().view(batch, seq_len, d)
    output = attn_output @ W_O
    

核心组件2:缩放点积注意力

当模型维度 d 较大时,查询和键的点积值可能变得很大,导致softmax函数的梯度很小。为了解决这个问题,在计算点积后,将得分除以 sqrt(d_k)(键向量的维度)。

  • 公式e_ij = (q_i · k_j) / sqrt(d_k)

核心组件3:残差连接与层归一化

这两个是帮助模型更快、更稳定训练的重要技巧。

  • 残差连接:将子层(如自注意力层或前馈层)的输入直接加到其输出上。公式为:LayerOutput(x) = Sublayer(x) + x。这有助于缓解梯度消失问题,并使网络在初始化时接近恒等映射。
  • 层归一化:对单个样本、单个时间步的特征向量进行标准化,使其均值为0,方差为1,然后应用可学习的缩放和偏移参数。
    • 公式
      • μ = mean(x), σ = std(x)
      • LN(x) = γ * ( (x - μ) / sqrt(σ^2 + ε) ) + β
        其中 γβ 是可学习参数,ε 是一个小常数用于数值稳定性。

在Transformer图中,残差连接和层归一化通常被画在一起,称为“Add & Norm”模块。


Transformer的三种架构

基于上述组件,我们可以构建三种主要的Transformer架构:

  1. Transformer解码器:用于语言建模等自回归任务。包含掩码多头自注意力层(防止看到未来信息),后接Add & Norm和前馈网络,重复多次形成多个块。
  2. Transformer编码器:用于需要双向上下文的任务(如句子分类)。与解码器几乎相同,但不使用掩码,允许每个词关注序列中的所有词。
  3. Transformer编码器-解码器:用于序列到序列任务(如机器翻译)。这是原始论文《Attention Is All You Need》中提出的架构。
    • 编码器:处理源语言序列(双向,无掩码)。
    • 解码器:生成目标语言序列(自回归,使用掩码自注意力)。解码器还有一个额外的交叉注意力层,其中查询来自解码器上一层的输出,而键和值来自编码器的最终输出。这允许解码器在生成每个词时关注源序列的相关部分。

Transformer的影响与挑战

Transformer架构带来了革命性的影响:

  • 并行计算:极大地提高了训练效率,能够利用更多数据和更强大的算力。
  • 预训练革命:为BERT、GPT等大规模预训练模型奠定了基础,在几乎所有NLP基准测试上取得了突破性进展。

然而,Transformer也面临一些挑战:

  • 计算复杂度:自注意力的计算复杂度是 O(n^2),其中 n 是序列长度。这对于处理长文档或书籍是一大瓶颈(相比之下,RNN是 O(n))。这是当前一个活跃的研究领域。
  • 位置表示:绝对位置编码可能不是表示位置信息的最佳方式,对于长序列外推能力有限。
  • 结构探索:尽管Transformer非常成功,但其架构仍有改进空间,例如前馈网络的设计、归一化方式等。

课程总结

本节课中我们一起学习了自注意力机制和Transformer模型的核心原理。我们从分析循环神经网络的局限性出发,引入了自注意力作为解决方案,并详细阐述了其数学形式。接着,我们探讨了构建实用自注意力模块需要解决的序列顺序、非线性和信息泄露问题。最后,我们深入剖析了Transformer架构的关键组件:多头注意力、缩放点积注意力、残差连接和层归一化,并介绍了编码器、解码器以及编码器-解码器三种架构形式。Transformer以其卓越的并行能力和表示能力,已成为现代自然语言处理的基石模型。

9:预训练 🚀

在本节课中,我们将要学习预训练(Pretraining)这一现代自然语言处理中的核心主题。我们将探讨如何通过大规模无标注文本数据来训练模型,使其学习到丰富的语言知识,并理解如何将这些知识应用到下游任务中。


1. 子词建模:超越有限词汇表

在之前的课程中,我们假设了一个有限的词汇表 V。然而,语言是动态的,新词不断涌现,并且许多语言具有复杂的形态变化。为了解决这个问题,我们引入了子词建模(Subword Modeling)。

子词建模的核心思想是:我们不试图定义所有可能的单词,而是将词汇表定义为包含单词的组成部分(子词)。这样,即使遇到未见过的单词,我们也可以将其拆分为已知的子词序列来表示。

以下是构建子词词汇表的基本算法:

  1. 从所有单个字符开始。
  2. 在数据集中找到最常相邻出现的字符对。
  3. 将这个字符对作为一个新的“子词”单元加入词汇表。
  4. 用这个新的子词单元替换数据中对应的字符对。
  5. 重复步骤2-4,直到词汇表达到预定大小或满足其他停止条件。

通过这种方式,常见的单词(如“hat”)可能作为一个整体保留在词汇表中,而罕见或复杂的单词(如“tasty”或“transformerify”)则可能被拆分为多个子词(如 T A AA A AS T Y)。这比将所有未知词映射到同一个 <UNK> 标记要有效得多。


2. 从静态词向量到上下文表示

我们之前学习的 Word2Vec 等方法为每个词提供了一个静态的向量表示。然而,正如语言学家 J.R. Firth 所说:“一个词的完整意义总是依赖于语境的。” 同一个词在不同语境下可能有不同含义(例如,“record” 作为名词和动词)。

因此,我们需要能够根据上下文动态调整的词表示。这通过循环神经网络(RNN)或 Transformer 等架构来实现,它们能为序列中的每个词生成一个依赖于其上下文的向量表示。


3. 预训练范式:原理与优势

预训练-微调(Pretraining-Finetuning)范式是现代 NLP 的基石。其核心思想是:先在一个大规模无标注文本语料库上训练一个模型,学习通用的语言知识;然后,在一个特定任务(如情感分析)的较小标注数据集上对这个模型进行微调。

预训练目标通常是某种形式的输入重建。例如,我们可以随机遮盖(Mask)输入句子中的一些词,然后训练模型去预测这些被遮盖的词。为了完成这个看似简单的任务,模型必须学习语法、语义、常识推理甚至世界知识。

为什么预训练有效?

  1. 数据规模:无标注文本数据(如互联网文本)的数量级远超特定任务的标注数据。
  2. 更好的参数初始化:预训练得到的模型参数 θ_hat 已经编码了大量语言结构。微调过程从 θ_hat 开始进行梯度下降,更可能收敛到一个泛化能力强的解。
  3. 任务通用性:语言建模等预训练任务比单一的下游任务(如电影评论分类)更能促使模型学习广泛、通用的语言模式。

4. 编码器预训练:BERT 与掩码语言建模

编码器(如 Transformer Encoder)可以同时看到输入序列的所有位置(双向上下文)。因此,我们不能直接用标准的下一个词预测(语言建模)来预训练它,因为任务会变得平凡(模型可以直接“偷看”答案)。

解决方案是掩码语言建模(Masked Language Modeling, MLM),由 BERT 模型推广。其过程如下:

  1. 随机选择输入序列中约15%的子词标记。
  2. 对于这些被选中的标记:
    • 80%的概率替换为特殊的 [MASK] 标记。
    • 10%的概率替换为随机词。
    • 10%的概率保持不变。
  3. 模型的任务是基于完整的上下文,预测这些位置原始的单词是什么。

BERT 的输入表示由三部分组成:

  • 词嵌入(Token Embeddings)
  • 位置嵌入(Position Embeddings)
  • 段落嵌入(Segment Embeddings):用于区分两个不同的文本片段(如一对句子)。

BERT 还使用了下一句预测(Next Sentence Prediction, NSP) 作为辅助任务,以学习句子间关系。但后续研究发现,仅使用 MLM 并延长训练上下文长度(如 RoBERTa 模型)通常效果更好。

如何使用预训练的编码器?
在预训练阶段,模型顶部有一个线性层用于词汇表预测。对于下游任务,我们移除这个顶层,并根据任务需求添加新的输出层。例如,对于句子分类,我们可以取 [CLS] 标记对应的输出向量,接一个分类器进行微调。


5. 编码器-解码器预训练:T5 与跨度损坏

编码器-解码器架构(如 Transformer)常用于序列到序列任务(如机器翻译、摘要)。其预训练需要同时考虑编码器的双向理解和解码器的生成能力。

一种有效的方法是跨度损坏(Span Corruption),由 T5 模型使用。其过程如下:

  1. 在输入文本中随机遮盖掉连续的多个词(一个“跨度”),并用特殊的掩码标记(如 <X><Y>)替换。
  2. 编码器接收被损坏的文本。
  3. 解码器的目标是按顺序生成被遮盖的原始跨度内容,目标序列形如:<X> 被遮盖的词1 被遮盖的词2 <Y> 被遮盖的词3 ...

这种方法让模型学习在给定双向上下文的情况下,生成缺失的文本片段,非常适用于需要理解和生成的任务。


6. 解码器预训练:GPT 系列与上下文学习

解码器(如 Transformer Decoder)使用因果注意力掩码,只能看到当前及之前的词,天然适合标准的下一个词预测任务(语言建模)。因此,其预训练就是在大规模文本上进行语言建模。

从 GPT、GPT-2 到 GPT-3,模型规模和数据量急剧增长,带来了新的能力——上下文学习(In-Context Learning)

上下文学习是指:仅通过向模型提供任务描述和少量示例(作为输入提示),而不更新其参数(即不进行微调),模型就能根据提示中的模式完成新任务。例如,给出几个“英文单词 -> 法文翻译”的例子,然后给出一个新的英文单词,模型可能输出正确的法文翻译。

这种能力在较小模型中不明显,但在像 GPT-3(1750亿参数)这样的大模型中涌现出来。它表明,大规模语言建模不仅学习了语言统计规律,还隐式地学习了如何根据指令和示例执行任务。

思维链(Chain-of-Thought)提示进一步提升了上下文学习的性能。其做法是:在提示的示例中,不仅给出问题和答案,还给出一步步的推理过程。当模型面对新问题时,它也会先生成推理步骤,再给出最终答案,这显著提高了复杂推理任务的准确性。


7. 模型缩放与更多思考

缩放定律(Scaling Laws):研究表明,模型性能随着参数数量、训练数据量和计算量的增加而可预测地提升。但需要平衡三者,例如 Chinchilla 模型表明,在相同计算预算下,使用更多数据训练一个稍小的模型,可能比训练一个超大的模型更有效。

预训练学到了什么? 模型学到了广泛的知识:词汇、语法、事实、常识推理、情感,甚至编程模式。然而,它同样会学习并放大训练数据中存在的社会偏见(如种族、性别偏见)。

高效微调(Parameter-Efficient Fine-Tuning):微调所有参数成本高昂。高效微调技术(如前缀微调、LoRA)通过只微调少量新增参数或低秩参数矩阵,在保持大部分预训练参数不变的情况下进行适配,取得了与全参数微调相近的效果,且更节省资源。


总结

本节课我们一起深入探讨了预训练技术。我们从子词建模解决了词汇表外词的问题开始,回顾了从静态词向量到上下文表示的演进。我们详细分析了预训练-微调范式的原理与巨大优势,并分别介绍了编码器(BERT/MLM)编码器-解码器(T5/跨度损坏)解码器(GPT/语言建模) 这三类主流的预训练架构与方法。最后,我们探讨了上下文学习思维链等大模型涌现能力,以及模型缩放、社会偏见和高效微调等重要议题。预训练是当今 NLP 发展的核心驱动力,理解其思想和技术是深入该领域的关键。

10:自然语言生成 🚀

大家好,我是Lisa,是NLP组的三年级博士生,导师是Percy和Tatsu。今天我将为大家讲解自然语言生成,这也是我的研究方向,因此我非常兴奋,并乐于在讲座期间和课后回答关于自然语言生成的任何问题。自然语言生成是一个非常激动人心且发展迅速的领域。今天我们将探讨自然语言生成的所有精彩内容。

在进入激动人心的部分之前,我需要做一些通知。首先,请务必在今天午夜之前注册AWS,这关系到你们的作业5以及最终项目。其次,项目提案将于下周二截止。作业4应该已经截止了,希望你们在机器翻译等任务中有所收获。作业5今天已经发布,截止日期是周五午夜。最后,我们将在本周五举办一个关于Hugging Face Transformer库的教程。如果你们的最终项目涉及实现Transformer或使用大语言模型,建议参加这个教程,它会非常有帮助。再次提醒,请记得注册AWS,这是最终硬性截止日期。

现在,让我们进入今天的主要话题——激动人心的自然语言生成内容。今天我们将讨论什么是自然语言生成,回顾一些模型,探讨如何从语言模型中解码以及如何训练语言模型,我们还将讨论评估方法。最后,我们将探讨当前自然语言生成系统的伦理和风险考量。这些自然语言生成技术非常令人兴奋,因为它们让我们更接近解释ChatGPT等流行模型的魔力。从实践角度看,如果你们的最终项目涉及文本生成,这些知识也会有所帮助。

什么是自然语言生成?🤔

自然语言生成是一个非常广泛的类别。人们通常将NLP分为自然语言理解和自然语言生成。理解部分主要指任务的输入是自然语言,例如语义解析、自然语言推理等。而自然语言生成则指任务的输出是自然语言。自然语言生成关注的是为人类使用而生成流畅、连贯且有用的语言输出的系统。

历史上,有许多基于规则的系统,例如模板或填充。但如今,深度学习几乎为每一个文本生成系统提供动力。因此,今天的讲座将主要关注深度学习方法。

首先,自然语言生成有哪些例子?它实际上无处不在,包括我们的作业。机器翻译是自然语言生成的一种形式,其输入是源语言中的某些话语,输出是目标语言中生成的文本。数字助手,如Siri或Alexa,也是自然语言生成系统,它们接收对话历史并生成对话的延续。还有摘要系统,接收长文档(如研究文章),然后将其总结为易于阅读的几句话。

除了这些经典任务,还有一些更有趣的用途,如创意故事写作,你可以用故事情节提示语言模型,然后它会生成与情节一致的创意故事。还有数据到文本的生成,你给语言模型一些数据库或表格,目标是输出表格内容的文本描述。最后,还有基于视觉描述的自然语言生成系统,如图像描述或基于图像的讲故事。

一个非常酷的例子是流行的ChatGPT模型。ChatGPT也是一个自然语言生成系统。它非常通用,因此你可以使用不同的提示让它执行许多不同的任务。例如,我们可以使用ChatGPT来模拟聊天机器人。它可以回答关于10岁孩子创意礼物的问题。它可以用于诗歌生成,例如,我们可以要求它生成一首关于排序算法的诗。虽然我不会说它非常有诗意,但至少它具有诗歌的格式,并且内容实际上是正确的。ChatGPT还可以用于一些非常有用的场景,如网络搜索。例如,Bing通过与ChatGPT结合,有推特用户表示,ChatGPT的魔力在于它实际上让人们乐于使用Bing。

有如此多的任务实际上属于自然语言生成的范畴。那么我们如何对这些任务进行分类呢?一种常见的方法是考虑任务的开放性程度。我们在这里画一条线来表示开放性的光谱。在一端,我们有像机器翻译和摘要这样的任务。我们认为它们不是非常开放,因为对于每个源句子,输出几乎由输入决定。因为基本上我们是在做机器翻译,语义应该与输入句子完全相同。所以你可以重新表述输出的方式只有几种,例如“当局宣布今天是国家假日”,你可以稍微改写为“今天是当局宣布的国家假日”,但实际的空间确实很小,因为你必须确保语义不变。因此,我们可以说这里的输出空间并不多样化。

移动到光谱的中间,有对话任务,例如任务驱动对话或闲聊对话。我们可以看到,对于每个对话输入,有多个可能的回应,自由度在这里增加了。例如,我们可以回应说“很好,你呢?”,或者说“谢谢关心,勉强应付所有的作业”。这里我们观察到实际上有多种方式可以继续这个对话。这就是我们说输出空间变得越来越多样化的地方。

在光谱的另一端,有非常开放的生成任务,如故事生成。给定输入“给我写一个关于三只小壁虎的故事”,有无数种方式可以继续这个提示。我们可以写它们去学校、盖房子等等。这里的有效输出空间极其庞大。我们称之为开放式生成。

很难在开放式和非开放式任务之间划出明确的界限,但我们仍然尝试给出一个粗略的分类。开放式生成指的是输出分布具有高度自由度的任务,而非开放式生成任务指的是输入几乎肯定决定输出生成的任务。非开放式生成的例子包括机器翻译和摘要,开放式生成的例子包括故事生成、闲聊对话、任务导向对话等。

我们如何形式化这种分类?一种形式化的方法是计算自然语言生成系统的熵。高熵意味着我们处于光谱的右侧,即更开放;低熵意味着我们处于光谱的左侧,即更不开放。这两类自然语言生成任务实际上需要不同的解码和训练方法,我们将在后面讨论。

回顾自然语言生成模型与训练 📚

现在,让我们回顾一下之前的讲座,回顾我们之前学过的自然语言生成模型和训练。

我们讨论了自然语言生成的基础。自回归语言模型的工作原理如下:在每个时间步,我们的模型将一系列标记作为输入,这里是 Y_<t,输出基本上是新的标记 Y_t。为了决定 Y_t,我们首先使用模型为词汇表中的每个标记分配一个分数,记为 S。然后我们应用softmax来得到下一个标记分布 P。我们根据这个下一个标记分布选择一个标记。

类似地,一旦我们预测了 Y_t_hat,我们将其作为输入传回语言模型,预测 Y_hat_t+1,然后我们递归地这样做,直到到达序列的结尾。

对于我们所讨论的两种类型的自然语言生成任务,即开放式和非开放式任务,它们倾向于偏好不同的模型架构。对于像机器翻译这样的非开放式任务,我们通常使用编码器-解码器系统,其中我们刚刚讨论的自回归解码器充当解码器,然后我们有另一个双向编码器来编码输入。这类似于你们在作业4中实现的内容,因为编码器类似于双向LSTM,解码器是另一个自回归的LSTM。

对于更开放的任务,通常自回归生成模型是唯一的组件。当然,这些架构并不是硬性约束,因为仅使用自回归解码器也可以进行机器翻译,而编码器-解码器模型也可以用于故事生成。这目前更像是一种惯例,但这是一个合理的惯例,因为与编码器-解码器模型相比,仅使用解码器模型进行机器翻译往往会损害性能,而使用编码器-解码器模型进行开放式生成似乎与仅使用解码器模型达到相似的性能。因此,如果你有计算预算来训练编码器-解码器模型,可能只训练一个更大的解码器模型会更好。这更像是一个资源分配问题,而不是架构是否与你的任务类型匹配的问题。

那么,我们如何训练这样的语言模型呢?在之前的讲座中,我们谈到语言模型是通过最大似然来训练的。基本上,我们试图最大化给定前面单词的下一个标记 Y_t 的概率。这是我们的优化目标。在每个时间步,这可以被视为一个分类任务,因为我们试图从词汇表中的所有剩余单词中区分出实际的单词 Y_t_star。这也被称为教师强制,因为在每个时间步,我们使用黄金标准单词 Y_star_<t 作为模型的输入。而在生成时,你可能无法访问 Y_star,因此你必须使用模型自己的预测将其反馈给模型以生成下一个标记,这被称为学生强制,我们将在后面详细讨论。

在推理时,我们的解码算法将定义一个函数,从这个分布中选择一个标记。我们已经讨论过,我们可以使用语言模型来计算这个 P,即下一个标记分布。然后,根据我们的符号,G 是解码算法,它帮助我们选择实际用于 Y_t 的标记。

明显的解码算法是在每个时间步贪婪地选择概率最高的标记作为 Y_t_hat。这个基本算法在某种程度上是有效的,因为它在你的作业4中表现良好。我们可以采取两种主要途径来改进。我们可以决定改进解码,也可以决定改进训练。当然,我们还可以做其他事情,比如改进训练数据和模型架构,但在本讲座中,我们将专注于解码和训练。

自然语言生成模型的解码算法 🔍

现在,让我们谈谈自然语言生成模型的解码算法是如何工作的。

在每个时间步,我们的模型为每个标记计算一个分数向量。它接收前面的上下文 Y_<t 并产生一个分数 S。然后我们尝试通过应用softmax来归一化这些分数,得到概率分布 P。我们的解码算法被定义为这个函数 G,它接收概率分布并尝试将其映射到某个单词,基本上尝试从这个概率分布中选择一个标记。

在机器翻译讲座中,我们讨论了贪婪解码,它选择这个 P 分布中概率最高的标记。我们还讨论了束搜索,它与贪婪解码具有相同的目标,即我们都试图找到基于模型定义的最可能的字符串。但对于束搜索,我们实际上探索了更广泛的候选范围,通过始终在束中保留K个候选者。

总的来说,这种最大概率解码对于像机器翻译和摘要这样的低熵任务是有益的,但对于开放式生成实际上会遇到更多问题。当我们尝试进行开放式文本生成时,最可能的字符串实际上非常重复。

正如我们在这个例子中看到的,上下文是完全正常的。它是关于一只独角兽试图说英语。但续写的开头部分看起来很棒,是有效的英语,谈论科学,但突然开始重复,重复一个机构的名字。

为什么会发生这种情况?如果我们看一下这个图,它显示了语言模型分配给序列“我不知道”的概率。我们可以看到这里有规律的概率模式。但如果我们重复这个短语“我不知道”10次,我们可以看到它们的负对数似然有下降趋势。Y轴是负对数概率,我们可以看到这个下降趋势,这意味着随着重复的继续,模型实际上具有更高的概率,这相当奇怪,因为它表明存在一种自我放大效应。所以重复越多,模型对这个重复就越有信心。

这种情况会持续下去。我们可以看到,对于“我累了”重复100次,我们可以看到持续下降的趋势,直到模型几乎100%确定它会继续重复相同的内容。不幸的是,这个问题并没有通过架构得到解决。红色曲线是LSTM模型,蓝色曲线是Transformer模型。我们可以看到两种模型都遭受同样的问题,而且规模也不能解决这个问题。所以我们有点相信规模是NLP中的神奇因素,但即使是拥有1750亿参数的模型,如果我们试图找到最可能的字符串,仍然会遭受重复问题。

那么我们如何减少重复呢?一种经典的方法是进行n-gram阻塞。原理非常简单。基本上,你只是不想看到相同的n-gram出现两次。如果我们设置n为3,那么对于任何包含短语“我很高兴”的文本,下次你看到前缀“我很”时,n-gram阻塞会自动将“高兴”的概率设置为0,这样你就永远不会再看到这个三元组。但显然,这种n-gram阻塞启发式方法存在一些问题,因为有时在文本中看到一个人的名字出现两次、三次甚至更多次是很常见的。但n-gram阻塞会消除这种可能性。

那么有哪些更好的选择呢?例如,我们可以使用不同的训练目标。我们可以通过最大似然以外的目标进行训练。在这种方法中,模型实际上会因为生成已经见过的标记而受到惩罚。这有点像将n-gram阻塞的想法放入训练时间,而不是在解码时强制这个约束。在训练时,我们只是降低重复的概率。另一个训练目标是覆盖损失,它使用注意力机制来防止重复。基本上,如果你尝试正则化并强制你的注意力,使得每个标记总是关注不同的单词,那么你很可能不会重复,因为重复往往发生在你有相似的注意力模式时。

另一个不同的角度是,我们可以使用不同的解码目标,而不是搜索最可能的字符串。也许我们可以搜索最大化两个模型对数概率差异的字符串,例如,我们想要最大化大模型减去小模型的对数概率。这样,因为两个模型都是重复的,所以它们会相互抵消。因此,在应用这个新目标后,重复的内容实际上会受到惩罚,因为它们相互抵消了。

这就引出了一个更广泛的问题:对于开放式文本生成,寻找最可能的字符串甚至是一个合理的事情吗?答案可能是否定的,因为这并不真正符合人类的模式。我们可以在图中看到,橙色曲线是人类模式,蓝色曲线是使用束搜索生成的机器文本。我们可以看到,在人类谈话中,实际上存在很多不确定性,正如概率的波动所示,对于某些单词,我们可能非常确定,对于其他单词,我们可能有点不确定。而对于模型分布,它总是非常确定,总是为序列分配概率1。因为我们现在看到两种分布之间存在不匹配,所以这有点表明,也许寻找最可能的字符串根本不是正确的解码目标。

在继续之前,有什么问题吗?有人问这是否是检测文本是否由ChatGPT生成的底层机制。并不完全是,因为这只能检测人类也能检测到的非常简单的重复问题。为了避免我们之前讨论的问题,我将讨论一些其他解码家族,它们生成更稳健的文本,其概率分布看起来像橙色曲线。所以我不认为这是用于水印或检测的答案。有人问,绘制人类文本和机器生成文本的概率是否是检测文本是否由模型或人生成的一种方法。我的回答是,我不这么认为,但这可能是一个有趣的研究方向。因为我觉得有更稳健的解码方法可以生成波动很大的文本。

那么,让我们谈谈能够生成波动文本的解码算法。既然寻找最可能的字符串是一个坏主意,我们还应该做什么?我们如何模拟人类模式?答案是在解码中引入随机性和随机性。

假设我们从这个分布中采样一个标记。基本上,我们尝试从这个分布中采样 Y_t_hat。它是随机的,因此你基本上可以采样分布中的任何标记。之前,你被限制选择最高概率的标记,但现在你可以选择“浴室”等标记。然而,采样引入了一系列新问题,因为我们从未真正将任何标记的概率归零。普通的采样会使词汇表中的每个标记都成为可行的选项。在某些不幸的情况下,我们可能会得到一个不好的单词。假设我们已经有了一个训练有素的模型,即使分布的大部分概率质量都集中在有限的好选项上,分布的尾部仍然会很长,因为我们的词汇表中有很多单词。因此,如果我们把所有长尾加起来,它们仍然具有相当大的质量。从统计学上讲,这被称为重尾分布,而语言正是一种重尾分布。

例如,许多标记在这个上下文中可能是完全错误的。然后,假设我们有一个好的语言模型,我们给它们每个分配非常小的概率。但这并不能真正解决问题,因为这样的标记太多了。所以作为一个群体,它们仍然有很高的被选中的机会。对于这个长尾问题的解决方案是,我们应该直接切断尾部。我们应该直接将我们不想要的概率归零。一个想法是称为top-K采样,其思想是我们只从概率分布中的前K个标记中采样。

到目前为止有什么问题吗?有人问,我们之前看到的模型在图中也有一些非常低概率的样本,top-K采样如何处理这个问题?top-K基本上会消除这些样本。它使得生成超低概率标记变得不可能。从技术上讲,它并不完全模拟这种模式,因为现在你没有超低概率标记,而人类可以以流畅的方式生成超低概率标记。是的,这可能是人们用于检测机器生成文本的另一个提示。这也取决于你想要生成的文本类型,对于更创新性的写作,你可能希望有更正确的选择。当然,K是一个超参数,根据任务类型,你会选择不同的K值。对于封闭式任务,K应该较小;对于开放式任务,K应该较大。

有人问,为什么我们不考虑加权采样的情况,而不是只看top-K?我们不做加权采样类型的情况,所以我们仍然有很小但非零的概率选择。我认为top-K也是加权的。top-K只是将分布的尾部归零。但对于它没有归零的部分,并不是在K个标记中均匀选择,它仍然试图根据你计算的分数按比例选择。这可能是因为计算效率更高,因为你不需要为17000个单词做计算。是的,top-K解码的一个好处是你的softmax会接收更少的候选者。但这不是主要原因。

我们讨论了这个部分。然后,这是top-K采样的公式。现在我们只从概率分布中的前K个标记中采样。正如我们所说,K是一个超参数,我们可以将K设置得大或小。如果我们增加K,这意味着我们使输出更加多样化,但风险是包含一些不好的标记。如果我们减少K,那么我们做出更保守和安全的选择,但生成的内容可能相当通用和无聊。

那么top-K解码足够好吗?答案并不完全是,因为我们仍然可以发现top-K解码的一些问题。例如,在上下文“她说我从未____”中,仍然有许多有效的选项,如“18岁”,但这些单词被归零了,因为它们不在前K个候选者中。这实际上导致你的生成系统的召回率很差。类似地,top-K的另一个失败是它也可能过早地切断。在这个例子中,“代码”并不是一个有效的答案,根据常识,你可能不想吃一段代码,但概率仍然非零,这意味着模型仍然可能将“代码”作为输出采样,尽管概率很低,但仍然可能发生。这意味着生成模型的精确度很差。

鉴于top-K解码的这些问题,我们如何解决它们?我们如何解决没有单一的K适合所有情况的问题?这基本上是因为我们采样的概率分布是动态的。当概率分布相对平坦时,设置小的K会移除许多可行的选项。因此,我们希望在这种情况下K更大。类似地,当分布P过于挑剔时,那么高的K会允许太多选项成为可行的。相反,我们可能希望K更小,这样我们更安全。这里的解决方案是,也许K只是一个不好的超参数。与其做K,我们应该考虑概率。我们应该考虑如何从累积概率质量的前P百分位数中采样标记。

现在,进行top-P采样的优势在于,我们实际上为每个不同的分布设置了一个自适应的K。让我解释一下自适应K的含义。在第一个分布中,这是一个典型的语言幂律分布。进行top-K采样意味着我们选择前K个,但进行top-P采样意味着我们放大到可能类似于top-K效果的东西。但如果我们有一个相对平坦的分布,如蓝色分布,我们可以看到进行top-P意味着我们包含更多的候选者。然后如果我们有一个更集中的分布,如绿色分布,进行top-P意味着我们实际上包含更少的候选者。因此,通过实际选择概率分布中的前P百分位数,我们实际上拥有一个更灵活的K,从而更好地了解模型中的好选项是什么。

关于top-P和top-K解码有什么问题吗?一切都清楚了吗?听起来不错。回到那个问题,进行top-K并不一定能节省计算。或者,整个想法并不是为了节省计算,因为在top-P的情况下,为了选择前P百分位数,我们仍然需要计算整个词汇表的softmax,以便我们能够正确计算P。因此,它并不能节省计算,但能提高性能。

除了我们讨论的top-K和top-P之外,还有更多的解码算法。有一些更近的方法,如典型采样,其思想是我们希望基于分布的熵来重新加权分数,并尝试生成概率更接近数据分布负熵的文本。这有点

11:大语言模型的后训练 🚀

在本节课中,我们将要学习如何将基础的大语言模型(例如仅经过预训练的模型)转变为像ChatGPT这样能够理解并遵循用户意图的强大助手。我们将从零样本和少样本学习开始,逐步深入到指令微调,最后探讨如何通过优化人类偏好(例如RLHF和DPO)来对齐模型。


概述:从预训练到ChatGPT的旅程

近年来,随着GPT等大语言模型的兴起,机器学习领域变得异常激动人心。我们花费了数百万美元和巨大的计算资源来预训练这些模型,其核心目标仅仅是预测下一个文本标记。然而,在这个过程中,模型不仅学习了语法和事实知识,还意外地获得了对代理、信念、行动甚至代码和数学的理解能力,使其能够作为通用的多任务助手。

本节课的核心问题是:我们如何从一个简单的“预测下一个词”的模型,转变为一个能够智能响应用户指令的模型?我们将沿着这条路径,探索提示工程、指令微调以及基于人类偏好的优化方法。


零样本与少样本上下文学习 🧠

上一节我们介绍了预训练模型的基本能力。本节中,我们来看看如何不更新模型参数,仅通过巧妙的提示(Prompting)来让模型执行新任务。

零样本学习

零样本学习指的是模型在没有见过任何任务示例的情况下,仅根据任务描述就能执行该任务。例如,GPT-2模型展示了这种能力。

核心思路:由于模型是文本预测器,我们可以通过设计输入文本来“诱导”它完成任务。

  • 示例:若要模型总结一篇新闻,可以在文章后加上“TL;DR”(Too Long; Didn‘t Read),模型就会基于其在互联网上见过的类似模式,生成总结。
  • 示例:进行指代消解时,可以比较模型对句子不同补全方式的概率。例如,对于句子“The cat couldn‘t fit into the hat because it was too big.”,分别用“the cat”和“the hat”替换“it”,看哪个补全的概率更高。

少样本学习

少样本学习是指我们为模型在输入中提供少量任务示例(例如,3-5个),模型就能学会并执行类似的新任务。

核心思路:在输入上下文中提供任务范例,让模型通过类比进行学习。

  • 示例:进行翻译时,在输入中先给几个“英文->法文”的例句,然后给出新的英文句子,模型就会生成法文翻译。
  • 效果:研究表明,随着模型规模(参数和数据)的增大,少样本学习的性能可以迅速接近甚至超越专门在该任务上微调的模型。这种能力似乎是“涌现”出来的。

思维链提示

对于涉及多步推理的复杂任务(如数学应用题),简单的少样本提示可能不够。

核心思路:在提示中不仅展示输入和输出,还展示得出输出的推理过程(即“思维链”)。

  • 示例:与其只给“问题->答案”,不如给“问题->推理步骤1, 步骤2, ... -> 答案”。
  • 效果:这能显著提升模型在复杂推理任务上的表现。甚至,仅通过添加“让我们一步步思考”这样的指令(零样本思维链),也能诱导出推理行为,大幅提升性能。

关键启示:与这些模型交互时,需要思考如何在预训练数据分布中诱导出我们想要的行为模式。


指令微调 📝

上一节我们看到,通过巧妙的提示可以让模型执行任务,但这需要“诱导”,且受上下文长度限制。本节中,我们来看看如何通过微调,让模型直接学会遵循指令。

指令微调的目标

预训练模型的目标是预测下一个词,而不是协助用户。因此,当直接要求它“向6岁孩子解释登月”时,它可能会继续生成“6岁孩子可能还会问什么问题”,而不是直接给出解释。指令微调的目标就是让模型与用户意图“对齐”,使其能直接、可靠地遵循各种指令。

指令微调的方法

方法出奇地简单直接:

  1. 收集数据:构建一个庞大的指令-输出对数据集。指令涵盖广泛的任务类型:问答、总结、翻译、代码生成、推理等。
  2. 微调模型:使用标准的监督学习(下一个词预测损失),在这个数据集上微调预训练模型。

公式:损失函数仍然是标准的语言建模损失:
L = -Σ log P(y_i | x_i, θ)
其中 (x_i, y_i) 是指令-输出对。

效果与评估

  • 规模效应:模型越大,指令微调带来的提升越明显。大模型能更好地吸收和泛化各种指令。
  • 评估基准:使用如MMLU(大规模多任务语言理解)等基准来评估模型在广泛知识领域上的表现。随着模型和数据规模的扩大,模型在这些基准上的分数持续提升,现已接近或达到人类水平。
  • 高质量数据:研究表明,使用高质量、多样化的指令数据至关重要。甚至可以利用更强的模型(如GPT-4)来为指令生成输出,从而创建训练数据。

指令微调的局限性

尽管强大,指令微调仍有不足:

  1. 数据收集成本高:为复杂任务获取人类标注的答案非常昂贵。
  2. 不适合开放式任务:对于创意写作等没有唯一正确答案的任务,难以提供标准的“正确”输出。
  3. 平等对待所有错误:语言建模损失平等地惩罚所有词级错误,但有些错误(如将“奇幻剧”说成“音乐剧”)比另一些更严重。
  4. 受限于人类水平:模型的答案质量可能被人类标注者的能力所限制。

基于人类偏好的优化:RLHF与DPO 🏆

上一节我们通过指令微调让模型学会了遵循指令,但其优化目标(预测下一个词)与最终目标(生成人类喜欢的输出)仍存在根本性错配。本节中,我们来看看如何直接优化人类偏好。

强化学习人类反馈

RLHF是ChatGPT等模型采用的核心技术,分为三步:

  1. 监督微调:即上一节的指令微调,得到一个基础模型。
  2. 奖励模型训练:收集人类对模型多个输出的偏好排序数据(例如,A输出比B输出好),训练一个奖励模型来预测人类偏好。
  3. 强化学习优化:使用奖励模型作为信号,通过强化学习(如PPO算法)优化语言模型,使其生成高奖励的输出,同时避免偏离原始模型太远。

核心优化目标
max_θ E_(y∼p_θ(.|x)) [R_φ(x, y)] - β * KL(p_θ(.|x) || p_ref(.|x))
其中,R_φ是奖励模型,KL散度项防止模型“放飞自我”,β是控制偏离程度的超参数。

挑战:RLHF流程复杂,对超参数敏感,实现和调试难度大。

直接偏好优化

DPO是一种更简单、高效的替代方案,它绕过了显式的奖励模型训练和复杂的强化学习。

核心洞见:对于给定的最优策略(语言模型)和参考策略(初始模型),存在一个闭式解,可以将奖励函数用策略本身表示出来:
R(x, y) = β * log(p_θ(y|x) / p_ref(y|x)) + log Z(x)
其中 Z(x) 是一个难以计算的归一化常数。

巧妙之处:当我们将这个奖励表达式代入基于偏好比较的Bradley-Terry损失函数时,归一化常数 Z(x) 被抵消了!

DPO损失函数
L_DPO = -E_(x, y_w, y_l) [log σ( β * log(p_θ(y_w|x)/p_ref(y_w|x)) - β * log(p_θ(y_l|x)/p_ref(y_l|x)) )]
其中 y_w 是偏好输出,y_l 是非偏好输出,σ 是sigmoid函数。

本质:DPO将偏好优化问题转化为了一个简单的分类损失,直接优化语言模型参数 θ,使其对偏好输出的概率高于非偏好输出。

优势:实现简单,计算高效,效果与RLHF相当,已成为众多开源模型和最新生产模型(如Llama 3)的首选方法。

对齐后的效果

经过RLHF或DPO优化的模型,与仅进行指令微调的模型相比,表现出更符合人类偏好的特性:

  • 更有帮助且无害:输出更详细、组织更好、更安全。
  • 减少冗余:倾向于生成更简洁、信息密度更高的答案(尽管早期版本存在冗长问题)。
  • 泛化性更强:能更好地处理训练数据中未明确出现的复杂或创意性指令。

总结与展望 🔮

本节课中,我们一起学习了将大语言模型从预训练状态转变为像ChatGPT这样的智能助手的关键步骤:

  1. 提示学习:通过零样本、少样本和思维链提示,激发预训练模型已有的能力。
  2. 指令微调:通过在海量指令数据上进行监督微调,使模型学会遵循多样化的用户指令。
  3. 偏好优化:通过RLHF或DPO,直接根据人类偏好优化模型输出,使其不仅正确,而且有用、无害、符合期待。

我们看到了规模(模型参数、数据量)在整个流程中的根本性作用,以及数据质量目标设计的重要性。尽管取得了巨大成功,该领域仍面临幻觉、奖励黑客、评估困难等挑战。

从简单的下一个词预测,到能与人类自然、有用、安全对话的助手,这是一段融合了规模法则、算法创新和人类反馈的非凡旅程。理解这些后训练技术,是理解当今最先进语言模型如何工作的关键。

12:基准测试与评估 📊

在本节课中,我们将要学习机器学习模型开发流程中至关重要的环节:基准测试与评估。我们将探讨不同评估方法、其适用场景以及当前大语言模型评估面临的挑战。

概述:为何需要评估?

我的机器学习模型开发心智模型包含以下几个步骤:

  1. 训练模型:需要一个可微分的损失函数来指导优化。
  2. 开发阶段:进行超参数调优或早停,需要评估指标来指导决策。
  3. 模型选择:为特定任务选择性能最佳的模型。
  4. 部署模型:需要可信、任务特定的评估来确保模型足够好,可以投入生产。
  5. 发表成果:在标准基准上评估模型,以便与同行交流模型质量。

在流程的不同阶段,评估性能的方式和侧重点各不相同。例如,训练时需要快速、廉价、可微分的评估;而部署时则需要高度可信、任务特定的绝对指标。

学术界的基准测试

基准测试是推动领域进步的主要驱动力。例如,MMLU基准测试的准确率在过去几年从约25%(接近随机猜测)提升到了约90%。

学术界基准测试的特点包括:

  • 可复现性与标准化:确保多年内的研究具有可比性。
  • 易于使用:研究者资源有限,需要快速、廉价的评估。
  • 指标不必完美:关键在于指标能反映领域在长期内的进步趋势。
  • 难度平衡:基准既不能太复杂(导致所有方法表现随机),也不能太简单(导致基线方法难以超越)。

NLP任务的两大类型

NLP任务主要分为两类:封闭式任务和开放式任务。

封闭式任务评估 🔒

封闭式任务有有限数量的潜在答案(通常少于10个),且通常只有一个或少数几个正确答案。这本质上是标准的机器学习问题。

以下是封闭式任务的例子:

  • 情感分析:判断文本情感是积极还是消极。典型基准:IMDB、SST。
  • 蕴含识别:判断一个假设是否被给定文本所蕴含。典型基准:SNLI。
  • 词性标注:典型基准:Penn Treebank。
  • 命名实体识别:典型基准:CoNLL。
  • 共指消解:确定代词指代哪个名词。
  • 问答:根据给定文本回答问题。

评估这些任务通常使用标准的机器学习指标,如准确率、精确率、召回率、F1分数。多任务基准测试(如SuperGLUE)会汇总多个任务的性能来评估模型的通用语言能力。

需要注意的问题:

  • 指标选择至关重要:例如,在垃圾邮件分类中,如果90%的邮件不是垃圾邮件,仅预测“非垃圾邮件”就能获得90%的准确率,但这毫无意义。此时应关注精确率、召回率和F1分数。
  • 指标聚合问题:将不同任务的不同指标(如准确率、F1分数、相关系数)简单平均可能不合理。
  • 标签来源与虚假相关性:例如,在SNLI数据集中,模型仅根据假设中是否包含否定词就能取得不错的效果,而无需理解前提。

开放式任务评估 🌐

开放式任务有许多可能的正确答案,且无法穷举。答案的正确性通常是一个连续谱(例如,写一本书的质量有高低之分)。

典型的开放式任务包括:

  • 文本摘要:典型基准:CNN/DailyMail。
  • 机器翻译
  • 指令遵循:这是当前最通用的任务,可以视作所有任务的母任务。

评估开放式任务更具挑战性,主要方法有三类:基于内容重叠的指标、基于模型的指标和人工评估。

开放式任务评估方法

1. 基于内容重叠的指标

这类指标通过比较模型生成文本与人工编写的参考文本在词汇上的相似度来评估。它们快速、高效。

常用指标:

  • BLEU:侧重于精确率,即生成的n-gram有多少出现在参考文本中。常用于机器翻译。
  • ROUGE:侧重于召回率,即参考文本中的n-gram有多少出现在生成文本中。常用于文本摘要。

这些指标的主要问题在于它们不考虑语义相似性。例如:

  • 参考答案:Yes.
  • 生成答案1:Yes. (BLEU得分高)
  • 生成答案2:Yup. (BLEU得分为0,但语义正确)
  • 生成答案3:Heck, no. (BLEU得分可能不低,但语义完全错误)

2. 基于模型的指标

为了克服词汇匹配的局限性,研究者转向使用词嵌入或上下文表示来捕捉语义相似性。

演进过程:

  • 词嵌入平均:计算生成文本和参考文本的词嵌入平均值,然后比较余弦相似度。
  • BERTScore:使用BERT等预训练模型获取上下文相关的词表示,并进行更智能的比对。
  • BLEURT:在预训练模型(如BERT)的基础上,使用人类标注数据对评估任务进行微调,让模型学会评估。

基于参考评估的局限性:评估质量严重依赖于参考文本的质量。研究表明,如果参考文本质量不高(如新闻摘要中使用的文章要点),ROUGE等指标与人类评价的相关性会很差。

3. 人工评估 👥

人工评估被视为开放式任务评估的黄金标准,也是开发新自动评估方法的基础。

人工评估的挑战:

  • 速度慢、成本高
  • 标注者间不一致:即使经过详细培训,不同标注者的意见也可能不一致。
  • 标注者自身不一致:同一个人在不同时间可能给出不同评价。
  • 难以复现:一项研究发现,仅5%的人工评估实验具备可复现性。
  • 仅评估精确率而非召回率:只能评价已生成的文本,无法评估所有可能的生成。
  • 激励错位:众包工作者可能追求速度而非质量,导致评估存在偏差(如偏好更长的答案)。

进行人工评估时,需要仔细设计任务描述、展示方式、评估维度(流畅性、连贯性、常识等),并谨慎选择和管理标注者。

当前大语言模型的评估

如何评估像ChatGPT这样的聊天机器人?这非常复杂,因为模型可以处理任意任务,生成任意长度的文本。

主流评估方法

  1. 困惑度:查看训练或验证损失。有趣的是,预训练困惑度与下游任务性能高度相关,可作为快速评估的参考,但需注意其在不同数据集和分词器间的不可比性。
  2. 综合基准平均:在大量自动化评估的基准测试上取平均分,以衡量通用能力。常见基准包括:
    • MMLU:57个学科的多项选择题,是目前最受信赖的基准之一。
    • GSM8K:小学数学题。
    • 代码能力评估:易于通过测试用例进行自动化评估。
    • 智能体评估:评估模型使用API或在沙盒环境中执行任务的能力,但构建评估环境复杂。
  3. 竞技场式评估:让两个模型回答相同问题,由人类或另一个LLM判断哪个更好。这是当前评估聊天模型的主流方法。
    • Chatbot Arena:用户在线对战,通过Elo评分排名。
    • 使用LLM进行评估:例如AlpacaEval和MT-Bench,使用GPT-4等强大模型作为评判员。这种方法速度更快、成本更低,且与人类评价的一致性有时甚至高于人类自身的一致性(因为LLM方差更小)。

使用LLM评估的注意事项:

  • 存在偏差:LLM可能偏好更长的输出、列表形式或自身生成的答案(自我偏好),但自我偏好通常没有想象中严重。
  • 提示词敏感性:评估结果可能对提示词细节敏感,需要通过重新加权等方式进行校准。

当前评估面临的挑战

  1. 一致性问题:即使是简单的多项选择题,评估结果也可能因选项格式(ABCD vs. 随机符号)、解码方式(约束解码 vs. 最高似然)或评分方式(生成字母 vs. 计算序列对数似然)的不同而产生巨大差异。MMLU曾因不同实现方式导致分数差异显著。
  2. 数据污染:如果测试数据被包含在模型的训练集中,评估结果将失去意义。对于闭源模型,很难检测是否存在污染。缓解方法包括使用私有测试集、动态测试集或通过检测模型对测试数据顺序的敏感性来进行估计。
  3. 评估单一化
    • 语言单一:大部分研究只关注英语,忽视了多语言能力。
    • 指标单一:通常只关注性能指标,忽略了计算效率、公平性、偏见等重要维度。
    • 样本等权:对所有测试样本平等对待,可能对少数群体不公平,也忽略了不同样本的实际价值差异。
  4. LLM评估的偏见放大:使用GPT-4等模型进行评估虽然高效,但如果该模型存在系统性偏见,这种偏见会被整个研究社区放大。研究表明,经过微调的模型倾向于反映特定人口统计学群体(如白人、东南亚裔、高学历者)的偏好。
  5. 缺乏改变的动力:学术界倾向于沿用旧有基准(如BLEU)以便与历史工作比较,审稿人也常要求提供传统指标结果,这使得转向更优但不同的评估体系变得困难。

总结与要点

本节课我们一起学习了机器学习模型评估的各个方面:

  1. 评估目的多样:在训练、开发、选择、部署和发表等不同阶段,评估的目标和所需属性不同。
  2. 封闭式任务评估:本质是标准机器学习,但需谨慎选择指标并注意数据问题。
  3. 开放式任务评估
    • 基于内容重叠的指标:如BLEU、ROUGE,快速但有语义局限性。
    • 基于模型的指标:如BERTScore、BLEURT,能更好地捕捉语义。
    • 人工评估:黄金标准,但实施复杂、成本高、一致性差。
  4. 大语言模型评估:当前主要通过综合基准平均和竞技场式比较(使用人类或LLM作为评判员)来进行。
  5. 主要挑战:评估结果的一致性、训练数据污染、评估体系的单一性(语言、指标、价值观)以及改变现状的学术惯性。

最重要的建议是:不要盲目相信数字。 最好的评估方式通常是亲自检查模型的输出。在现实世界中,如果发现评估指标不佳,应该有勇气切换评估方式。

13:GPU训练与参数高效微调 🚀

在本节课中,我们将要学习如何利用GPU高效地训练大型深度学习模型,并了解参数高效微调技术。我们将从计算机如何表示数字开始,逐步深入到混合精度训练、多GPU并行训练策略,最后探讨如何通过LoRA等方法以更少的资源微调大模型。


计算机中的数字表示:浮点数

上一节我们介绍了课程概述,本节中我们来看看计算机如何表示数字,特别是浮点数,这对于理解后续的优化技术至关重要。

浮点数在计算机中以特定格式存储。FP32(单精度浮点数)使用32位(4字节)内存。其结构可以简化为:1位符号位 + 8位指数位(范围) + 23位尾数位(精度)。指数位决定了数字可表示的范围,尾数位决定了精度。

为了节省内存,我们可以使用FP16(半精度浮点数),它仅使用16位(2字节)。其结构为:1位符号位 + 5位指数位 + 10位尾数位。与FP32相比,FP16的动态范围更小,精度也更低。

以下是两种浮点格式关键属性的代码表示:

# 在PyTorch中查看浮点属性
import torch
print(f"FP16 epsilon: {torch.finfo(torch.float16).eps}")  # 最小可加数
print(f"FP16 smallest normal: {torch.finfo(torch.float16).smallest_normal}") # 最小正规格化数
print(f"FP32 epsilon: {torch.finfo(torch.float32).eps}")

直接使用FP16训练神经网络会遇到两个主要问题:

  1. 下溢:由于动态范围小,许多梯度值(通常很小)会被舍入为0。
  2. 精度损失:更新不够精确,可能影响模型性能。

混合精度训练 🎯

为了解决FP16的问题,我们引入了混合精度训练。其核心思想是:在内存中同时维护FP16和FP32两份模型参数副本

以下是混合精度训练的基本流程:

  1. 维护一份FP32的“主权重”。
  2. 前向传播时,将FP32权重转换为FP16进行计算。
  3. 计算得到FP16的损失和梯度。
  4. 将FP16梯度转换回FP32。
  5. 用FP32梯度更新FP32主权重。
  6. 将更新后的FP32主权重复制回FP16模型。

然而,梯度下溢问题依然存在。解决方案是梯度缩放

  1. 在前向传播后,将损失值乘以一个缩放因子(例如1024)。
  2. 在FP16下进行反向传播,计算梯度。此时梯度也被放大了,减少了被舍入为0的可能。
  3. 将FP16梯度转换为FP32后,再除以相同的缩放因子,得到真实的梯度值。
  4. 用真实的梯度更新权重。

在PyTorch中,这可以通过GradScalerautocast上下文管理器轻松实现。

但梯度缩放需要动态调整因子,较为复杂。一个更优的解决方案是使用BFloat16。BFloat16使用8位指数(与FP32范围相同)和7位尾数(精度更低)。其公式可表示为:BFloat16 = 1位符号位 + 8位指数位 + 7位尾数位。由于具有与FP32相同的动态范围,它避免了梯度下溢问题,且无需梯度缩放,在支持它的GPU(如A100)上训练更快、更省内存。


多GPU训练:从DP到FSDP ⚙️

当我们拥有多个GPU时,如何协同工作以加速训练?让我们从基础开始。

在单GPU训练中,GPU内存需要存储:模型参数(FP16)、梯度(FP16)、优化器状态(如Adam的动量和方差,FP32)。

分布式数据并行

分布式数据并行是最简单的多GPU训练方法。

  1. 将数据集分割,每个GPU获得一部分数据。
  2. 每个GPU上都有完整的模型副本。
  3. 每个GPU独立进行前向和反向传播,计算本地梯度。
  4. 使用All-Reduce通信原语在所有GPU间同步梯度(求平均)。
  5. 每个GPU用同步后的梯度更新自己的模型参数,保持模型一致。

All-Reduce的通信开销是 2字节 * 参数量(因为梯度是FP16)。DP的主要问题是内存效率低,每个GPU都需要存储完整的模型、梯度和优化器状态。

Zero Redundancy Optimizer

为了优化内存,微软提出了ZeRO(零冗余优化器)技术。其核心思想是将优化器状态、梯度甚至模型参数进行分片存储,而非在每个GPU上保存完整副本。

ZeRO Stage 1:分片优化器状态。

  • 每个GPU只存储一部分参数的优化器状态。
  • 在梯度同步时,使用 Reduce-Scatter 操作:每个GPU将完整梯度中属于其他GPU负责的部分发送过去。
  • 每个GPU用收到的梯度更新自己负责的那部分参数。
  • 更新后,使用 All-Gather 操作同步所有参数,使每个GPU获得更新后的完整模型。
  • 关键点:All-Reduce = Reduce-Scatter + All-Gather。因此Stage 1在通信开销上与DP相同,但节省了优化器状态的内存。

ZeRO Stage 2:分片优化器状态和梯度。

  • 每个GPU只存储一部分参数的梯度和优化器状态。
  • 在反向传播时,每计算完一层的梯度,就立即将其发送给负责该层对应参数分片的GPU,然后释放内存。
  • 这通过 Reduce 操作实现(将多个GPU上同一参数的梯度汇总到一个GPU)。
  • 同样,最后需要All-Gather同步参数。通信开销与Stage 1基本相同,但进一步节省了梯度内存。

ZeRO Stage 3 / FSDP:分片优化器状态、梯度和模型参数。

  • 当模型太大,单个GPU连参数都装不下时使用。
  • 模型参数被分片存储在不同GPU上。
  • 前向传播时,对于当前层,通过All-Gather从所有GPU收集该层完整参数,计算后释放。
  • 反向传播时,再次All-Gather收集参数计算梯度,然后通过Reduce-Scatter将梯度汇总到负责对应参数分片的GPU上。
  • 通信开销显著增加(两次All-Gather + 一次Reduce-Scatter),但这是训练超大模型的必要手段。

需要注意的是,FSDP的性能与如何将模型划分为“FSDP单元”的策略密切相关。


参数高效微调:LoRA 🎨

当模型过于庞大,即使使用FSDP也无法进行全参数微调时,或者我们希望以更环保、更高效的方式适配模型到新任务时,参数高效微调技术就派上了用场。

全参数微调更新模型的所有参数,存储和计算成本高昂。参数高效微调的核心思想是:仅更新一小部分参数,或者添加一小部分可训练的新参数

其中,LoRA(低秩适应)是一种流行且有效的方法。它基于一个观察:大模型微调时的权重更新矩阵往往具有较低的“内在秩”。

LoRA的具体做法是:

  • 对于预训练模型中的某个权重矩阵 W ∈ R^(d×k),我们冻结它,不进行更新。
  • 我们引入一个低秩的增量更新 ΔWΔW 由两个小矩阵的乘积构成:ΔW = B A,其中 A ∈ R^(r×k), B ∈ R^(d×r),秩 r << min(d, k)。
  • 因此,前向传播公式变为:h = Wx + (α/r) ΔW x = Wx + (α/r) BAx
  • 其中 α 是一个缩放超参数,用于控制新任务知识相对于预训练知识的权重。
  • 在训练过程中,只有 AB 是可训练的参数,数量远少于原始参数。

LoRA的优势包括:

  1. 内存效率高:只需存储和优化少量参数。
  2. 切换任务快:只需替换不同的LoRA权重矩阵即可。
  3. 性能接近全微调:即使秩r很小,也能取得很好效果。

在实践中,通常将LoRA应用于Transformer自注意力模块中的查询和值投影矩阵。超参数设置通常以秩 r=8 和缩放因子 α=1 作为良好的起点。


实践指南流程图 📋

以下是训练大模型时的决策流程图,可用于指导你的最终项目:

  1. 始终使用混合精度训练。如果GPU支持(如A100, H100),优先使用BFloat16。
  2. 检查单卡内存:尝试在单个GPU上运行批次大小为1的训练。
    • 如果成功:尝试增大批次大小,并启用ZeRO Stage 2(几乎无额外开销)来节省内存以支持更大批次。
    • 如果失败(内存不足):尝试启用ZeRO Stage 3(FSDP)来分片模型参数。也可以尝试梯度检查点技术。
  3. 如果FSDP仍无法解决:考虑使用参数高效微调技术,如LoRA。
    • LoRA核心设置:应用于注意力层的查询和值矩阵,秩 r=8,缩放因子 alpha=1

总结

本节课中我们一起学习了高效训练与微调大型深度学习模型的关键技术。我们从浮点表示和混合精度训练的原理出发,理解了如何节省内存和加速计算。接着,我们深入探讨了多GPU训练策略,从简单的数据并行到复杂的完全分片数据并行,了解了如何通过通信与计算重叠来扩展模型训练规模。最后,面对极致的大模型,我们介绍了参数高效微调的代表性方法LoRA,它通过低秩适配实现了以极小参数量达成任务适配的目标。掌握这些技术,将帮助你更从容地应对大规模深度学习项目的计算资源挑战。

14:脑机接口 🧠💻

在本节课中,我们将学习脑机接口的基本原理、发展历史、技术实现以及其在帮助言语和运动功能丧失者恢复沟通方面的应用。我们将从动机出发,了解脑机接口如何工作,并探讨其未来的可能性与伦理挑战。

动机:为什么需要脑机接口?🎯

我们首先来看一个视频,了解为什么我们需要构建脑机接口。视频中的霍华德在21岁时因严重中风而陷入闭锁状态,他失去了所有运动能力,无法说话。他曾经喜欢外出踢足球、交朋友、表达情感,但这一切都失去了。最重要的是,他无法通过言语来表达自己,无法释放情感。

霍华德只是众多因神经系统疾病(如脑损伤、中风或肌萎缩侧索硬化症)而导致严重言语和运动障碍,甚至完全丧失言语能力的个体之一。对于他们来说,生活极具挑战性。试想一下,你无法说话,无法移动,但大脑功能却完全正常。所有的梦想都可能因此破灭。

对于像霍华德这样的人,他们与外界和亲人沟通的方式是通过辅助通信设备,例如视频中看到的字母板。字母板上有按物理方式组织的字母,对于像霍华德这样可能仍有残余眼球运动能力的人来说,他们可以用目光告诉朋友他们正在看哪里,然后朋友根据目光判断他想说的字母。想象一下,如果想说一个句子,这个过程是多么缓慢,可能需要几分钟才能表达出“你好吗?我今天感觉不舒服”这样简单的意思。

另一种替代方法是使用眼动追踪设备,这样人们就可以通过眼动在虚拟键盘上打字。但是,整天盯着电脑屏幕对他们来说非常疲劳。而且,这些人不像我们,即使他们仍有残余的眼球运动能力,移动眼睛对他们来说也非常困难,同样非常疲劳。

最近,一家名为Neuralink的公司发布了一些视频,展示了不同的可能性。该公司正在开发一种可植入头皮内部的微小设备,用于读取大脑信号。这里的希望在于,对于像霍华德这样的人,他们的大脑仍然功能完好。因此,我们希望通过这种直接与大脑交互的接口,让他们仍然能够用大脑控制电脑甚至机器人,帮助他们过上正常的生活。

这是他们的参与者纳兰的引述,他对能够使用这种最先进的脑机接口与家人联系并养活自己感到非常兴奋。对于像霍华德或许多失去身体和语言控制能力的人来说,脑机接口可以给他们带来希望。这就是我们今天要探讨的动机:我们试图利用脑机接口真正帮助这些人。

脑机接口简史 📜

在深入探讨脑机接口如何工作的细节之前,我们先简要回顾一下脑机接口的历史,以帮助大家理解为什么我们可以将如此微小的设备植入大脑,并突然能够解读大脑的活动。这里有很多有趣的故事。

首先,回到19世纪,一位名叫理查德·卡顿的英国科学家开始在动物身上进行实验。他发现,实际上可以从大脑中测量电活动。更重要的是,如果你让动物执行某些任务,比如移动头部,那么你可以看到它们的电活动以某种方式发生变化。我认为这是科学家们进行的早期实验,表明实际上可以从大脑中解码出一些信号。但我们仍然不知道这些电信号的确切含义。

时间快进到1924年,一位名叫汉斯·伯杰的德国科学家发明了一种叫做脑电图(EEG)的设备。基本上,它是一种可以放置在头皮外部的电极,用于测量类似波形的信号。汉斯·伯杰的发现是,首先,他是第一位发现可以从头部电极测量到这种波形信号的科学家。然后他发现,这种信号的频率根据患者的状态(功能状态)而有很大不同。例如,如果患者处于非常平静的状态,会产生大约10到20赫兹的慢速阿尔法波。如果患者睁开眼睛并执行一些认知要求高的任务,则会看到非常尖锐的贝塔波。因此,他是第一位发现可以使用这种设备测量大脑电信号的科学家。

这里还有一个有趣的故事:汉斯·伯杰曾是一名士兵。有一天他在马背上训练时摔了下来,遭受了脑震荡。他还有一个双胞胎妹妹。故事是这样的,在同一天,他的妹妹感觉有些不对劲,开始担心她的哥哥。于是,他的妹妹给他发了一封电报,询问他是否安好。这激发了汉斯·伯杰的兴趣,他认为可能存在一种叫做心灵感应的东西,可以通过这种脑波连接两个人。这就是他开始研究心理学和神经科学,并发明了至今仍在使用的脑电图(用于诊断癫痫等)的动机。

之后,人们开始使用这种脑电图设备来尝试检测大脑中的这种波状活动,并尝试控制波的频率。例如,一些音乐家开始使用脑电图设备来表演音乐。这是在20世纪50年代进行的一个非常酷的实验。你已经可以看到,人们开始有了这样的想法:实际上可以绕过你的身体,直接用大脑连接到一些外部设备并控制该设备。这里的想法是,如果我们能利用同样的想法来帮助像霍华德这样的人,也许可以帮助他们控制机械臂。

但是,这种外部测量设备(如脑电图)的问题是信号非常微弱。想象一下,你的大脑正在产生大量信号,我们知道大脑有很多神经元。神经元实际上产生大量信号。如果你只是将一些电极放在头皮上,那么你实际测量的是数百万神经元放电的平均值。打个比方,如果你试图听隔壁房间的人在说什么,但我们只能试图弄清楚这个房间里的人在说什么,我们听到的是很多声音的混合,我们可能只能分辨出他们可能处于愉快的情绪中,或者他们已经得出了结论,但无法确切知道他们想说什么。因此,这里的限制是,这种脑电图设备只能给我们提供非常低精度或低分辨率的信号。我们想要获得更好的信号。

我认为答案是尝试进入大脑内部,将电极放置在神经元旁边,直接测量这些神经元的活动。为了本次讲座的目的,我们将主要关注大脑中称为运动皮层的区域。正如你们中一些人可能已经知道的,大脑有不同的区域负责不同的任务。在大脑中心有一个称为运动皮层的区域,它基本上控制着你身体的所有肌肉。这里的希望是,如果我们能理解这里编码的神经元信息,那么也许我们可以解码这些信息,并利用这些信息帮助像霍华德这样的人控制外部手臂或再次说话。

基础神经科学 🧬

以下是一些非常基础的神经科学知识。我们知道有一种叫做神经元的细胞。每一个这样的东西都叫做神经元,这是神经元的胞体,这是轴突。这是另一个神经元。神经元通过称为突触的微小结构连接。如果一个神经元想要将信息传递给另一个神经元,就像在人工神经网络中一样,你有一些神经元,现在你想将信息发送到下一层,这个神经元基本上会产生一些动作电位,这只是一些电信号,用来向另一个神经元表明那里有一些信息。

如果你将电极放在这个神经元的轴突上并测量膜电位,你会得到类似这样的东西:X轴是时间,Y轴是测量的电势,然后你会看到这种非常尖锐的尖峰。如果你放大这些尖峰,你会看到神经元放电的典型特征,即电压突然上升然后下降。基本上,你在神经元处测量到的是非常尖锐的尖峰,这就是将电极放在神经元旁边会得到的结果。

那么,我们如何弄清楚在这种尖峰序列中编码了什么样的信息呢?我们可以进行一些行为任务实验。例如,假设我们仍然在监听单个神经元。这个神经元是,比如说,我们在这个实验中使用一只猴子。我们训练猴子做两件事:一件事是试图指示猴子将手向左或向右移动。然后我们测量该单个神经元放电的所有尖峰,试图了解该神经元编码了什么样的信息。

你在这里看到的是,每一行基本上是该神经元的尖峰序列,正如你在这里看到的,每条垂直线只是该神经元的一个尖峰。每一行是一次试验,一次试验意味着猴子试图朝一个方向移动它的手。然后,垂直线……你可以在这里看到,神经元在不同试验中的放电似乎略有不同。我认为这是神经元的一个基本特性:它非常嘈杂。不像在人工神经网络中,你输入一些东西,总是得到一些输出。而在真实的神经网络中,在真实的神经元中,事物非常嘈杂。因此,有时它们在相同的实验条件下放电稍快,但有时放电稍慢。

在这里,我们试图测量的是,当猴子将其肢体向左或向右移动时,这个神经元编码了什么样的信息。然后,我们还可以将这些信息编码分为两个阶段:准备阶段和执行阶段。执行阶段意味着猴子实际上正在移动它的手臂,而准备阶段意味着猴子正准备移动,但保持手臂固定。它实际上会在这个“开始”时间移动它的手臂。你可以在这里看到,这个神经元似乎在执行阶段(当猴子的手向右移动时)放电很多。当猴子准备向左移动时,它也放电稍多。这意味着也许这个神经元编码了一些运动方向。

基本上,如果你能对许多不同的神经元和许多不同的方向重复这些实验,最终科学家们发现,对于单个神经元,如果你将该神经元的放电率(即每秒产生多少尖峰)拟合到不同的运动方向,你可以拟合出一种余弦调谐曲线。这个调谐曲线的含义是,Y轴是放电率,水平轴是运动方向。这个神经元在运动方向为某个参考方向的180度时最喜欢放电,然后放电逐渐减少。

这就是科学家们发现的关于单个神经元如何编码运动信息的第一件事。然后,如果你测量多个神经元,你会发现每个神经元可能编码非常不同的信息。例如,这里的这个神经元,它的调谐曲线稍微向右偏移,幅度也向下偏移。因此,它的偏好方向可能在250度左右。现在,有了两个神经元,你实际上可以解码出更多关于预期运动方向的信息。例如,对于单个神经元,假设现在我测量的放电率约为每秒30个尖峰,那么可能有两个运动方向:120度和240度。然而,有了第二个神经元,你可以看到,假设我们测量第二个神经元每秒放电约5个尖峰,那么我们可以精确地确定实际运动方向是120度,而不是另一个。

然而,我们知道神经元有一些噪音,所以我们实际上不能仅用两个神经元来确切地判断运动方向。例如,在第三种情况下,由于噪音,实际的、真实的地面实况放电率是那些灰线,但由于噪音,放电率稍微偏移到那些虚线。你可以看到,如果我们能解码运动方向是120度,但在这种情况下,可能性变成了有四种可能性,我们不能唯一确定。

然而,你可以看到,也许猴子试图移动的方向更可能是120度左右,而不是50度左右,另一个则更像是大于240度。那么,我们如何处理这种嘈杂的神经元呢?我们如何仍然能够更准确、唯一地从这种多神经元记录中解码预期的运动呢?

我认为我们基本上可以在这里使用机器学习。我们可以将其视为一种分类问题。这里我们绘制的是,每个点基本上是两个神经元的放电组合,颜色基本上代表了预期的运动方向。然后,如果你以某种方式在这里训练一个机器学习分类器,那么你基本上可以看到我们可以绘制一些决策边界,比如说在右侧,如果我们得到的新测量值的放电率以某种方式落在这里,那么我们可能知道猴子正试图向左方向移动。

所以,我想在这里,我们知道我们可以进行这种单神经元测量。我们可以测量多个神经元的放电率。然后通过训练机器学习模型,我们可以使用这些带有神经数据的机器学习模型来推断可能的运动方向。这就是我们如何构建脑机接口的方法。

如何记录这些信号?📡

现在,我们基本上知道我们可以将一些电极推入大脑的运动皮层,测量一些信号。然后我们知道神经元如何编码这些信号。然后我们也可以构建一个机器学习解码器来解码这些信号。基本上,我们有一些方法能够构建脑机接口,以便我们能够解释一个仍然功能完好、完全正常的大脑试图做什么。

还有一件事是,我们如何记录这些信号。这是一张非常复杂的图,但不用担心所有细节。我想在这里展示的是,基本上有很多不同的技术可以用来记录大脑信号。但当你考虑这些技术时,你可以将其视为在这种二维空间中。Y轴是关于空间分辨率。所以,你在Y轴上走得越高,意味着你基本上可以测量大脑的非常大区域的平均大脑活动。而如果你沿着Y轴向下走,意味着你实际上可以测量到非常精细的尺度,例如单个神经元。

而这里的水平轴意味着时间分辨率。这意味着对于像这种单神经元记录这样的技术,你基本上可以测量每个时间点(例如1毫秒)该单个神经元的电势。而对于像功能磁共振成像这样的记录技术,它基本上测量的是小脑区域的血液流动,它只能测量大约5秒或1秒内该小脑区域的平均血液流动变化。这真的是很多信息的平均,因为我们知道神经元放电速度非常快,神经元的电势变化通常在1毫秒的数量级。如果你只能测量大约1秒的东西,你基本上是在平均、平滑掉很多信息。

理想情况下,我们希望同时具有高空间分辨率和高时间分辨率。目前,在我们实验室的许多临床试验中,我们将使用这种微电极阵列。每个电极就像一根微小的针,可以测量几个神经元的信号,然后将这些针插入一个像指甲大小的小方块中,你可以测量大约数百个神经元。

脑机接口应用示例:恢复运动与沟通 🎮

现在,我们有了这种设备来测量神经元。让我们举一个更具体的例子,看看我们如何做到这一点。

假设某人患有脊髓损伤,失去了与身体的连接。他的思维仍然完全正常。这里的问题是,我们是否仍然可以从他的运动皮层解码出什么样的信息,以便我们可以解码这些信息,然后利用这些信息控制他自己的手臂或人工手臂。

我们要做的是尝试将这种微电极阵列放入他的运动皮层,真正穿透他的运动皮层表面。每个电极,正如你在这里看到的,是这种微小的针,那些灰色三角形是神经元的大小。因此,每个电极可能测量其周围多个神经元的局部场电位。

我们可以实时将所有信息通过这种电线传输到计算机。然后,我们在计算机上得到的是,例如,这里的每个方块基本上是那个电极的测量值。如果我们进行一些行为实验,就像我们刚才展示的那样,我们也许可以找出每个电极的调谐曲线。例如,这个电极的偏好方向可能是向左。我们可以为其他通道重复行为实验,可能训练一个机器学习解码器来找出每个通道编码的偏好方向。

一旦我们训练了解码器,那么在测试时,我们基本上可以要求我们的参与者(他植入了脑机接口)尝试想象将手移动到某个方向。然后解码器试图找出他试图移动的方向。这就是基本思想。

让我展示一个演示。这是我们实验室在2017年发表的一项研究。在这里,你看到一位参与者正在用她的思维在虚拟键盘上打字。底部显示的是打字速度,以每分钟正确字符数衡量。它最高达到40,平均可能在20左右。我认为这真的很神奇,想想像霍华德这样的人,过去必须使用这种字母板来沟通。现在,有了这种脑机接口,他可以通过电脑完全自主地进行沟通。这比字母板有了巨大的改进。

在这个实验中,她仍然睁着眼睛。即使她闭上眼睛,它仍然有效,但她不会有视觉反馈,她不知道她在哪里打字。如果她想输入一个字符,不看键盘的话。好的,这是我接下来要展示的内容。

参与者是在复制一个句子,所以我们知道地面实况,然后我们可以测量错误率。点击动作或选择动作是否容易区分?或者是否有某种方式知道用户正在按下?或者她是否想象一个鼠标?实际上,这是一个非常好的问题。正如我刚才提到的,我们可以解码运动,我们也可以解码不同的手势,比如移动肘部。因此,她可以想象不同的运动,然后我们基本上可以将解码出的这些运动映射到点击信号或不同的信号。

这基本上只是展示了基于我们对大脑的了解,我们可以从像T6(她的代号)这样的人身上解码一些尝试的运动,我们真的可以通过这种脑机接口帮助这类人重新获得沟通能力。

正如我之前提到的,你也可以使用脑机接口来控制机械臂。例如,在这种情况下,参与者是加州理工学院的T6,他正在用他的思维控制这个机械臂,为他拿一杯饮料。此外,你还可以做一些事情,比如恢复工作能力。正如刚才有人提到的,也许我们可以尝试恢复不同的沟通方式。例如,刚才我们只是使用运动,然后通过恢复运动来控制电脑。但是,我们直接恢复书写能力怎么样?因为书写是一种非常自然的沟通方式。因此,我们实验室的研究员弗兰克·威利特在2021年发表了一篇论文,展示了你实际上可以实现这种书写脑机接口,并且表明它实际上比之前的方法快得多。

恢复言语:更高的挑战 🗣️

现在我们已经看到,有不同的方式来恢复沟通。这里只是不同沟通方式的测量结果。在最左边的是吸吹接口,非常慢。基本上,这意味着对于某些不能真正移动但还能呼吸的人来说,他们可以通过吸气和吹气来表示“是”和“否”来沟通。这非常慢,可能每分钟5个单词。对于一个正常人,我真的很惊讶平均每分钟只能写大约13或14个单词。这真的很慢,但也许这只是平均速度。

在最右边的是自然沟通,每分钟可以达到150到160个单词。把一切都放在这里来看,我刚才向你们展示的二维光标每分钟可以做8个单词,书写每分钟可以做大约18个单词。因此,与字母板或这种眼动追踪相比,我们确实取得了很大进步,但仍然远远低于自然对话的速度。

所以,下一个问题基本上是,我们如何才能达到那个水平?我们能否通过脑机接口真正恢复自然的言语?我认为这里存在一个巨大的障碍。首先,大脑中的语言处理是一个非常复杂的过程。例如,这里显示了所有涉及语言的大脑区域。我们仍然不知道这究竟是如何发生的,但这只是我们对语言在大脑中如何处理的最佳猜测。在最右边,你看到有很多与知识和推理相关的大脑区域,中心可能是涉及语义和句法的区域,最左边是关于言语感知和言语产生的区域。

语言非常复杂,所以也许这里的希望是我们可以从运动皮层开始,从语言的运动规划开始,因为刚才我向你们展示的,我们已经知道运动皮层如何编码运动,我们也知道为了产生语言,我们需要说话。然后,也许我们可以将一些电极放入运动皮层的这个部分,这个部分实际上控制我们的口面部肌肉。然后尝试在那里解码一些信息,看看我们是否真的能恢复言语。

实际上,能够恢复言语比恢复运动更复杂。我想说的是,言语的产生真的是很多复杂的运动,而且非常迅速。它不仅仅是把手移动到某个方向。因此,恢复言语比仅仅解码每个发音器官的运动要困难得多。

与其试图解码每个发音器官的运动,因为这非常困难,而且对于像霍华德这样失去言语能力的人来说,实际上很难测量他们的发音器官运动。相反,也许我们可以尝试解码这种离散的音素,而不是这种连续的言语发音器官运动,因为我们知道所有语言都可以分解为这种基本的语音单位。例如,对于英语,我们知道有不同的元音和不同的辅音。它们与你在口中放置舌头的方式以及你放置不同发音器官的方式相关。因此,这里我们试图解码的不是实际的发音器官运动,而是这种离散的音素标记。

之前有研究表明,如果你在运动皮层上放置一些电极,那么你实际上可以通过测量运动皮层中的电活动来区分不同的音素。因此,有可能仅仅通过将电极放入运动皮层来恢复言语。

事实上,在2021年,加州大学旧金山分校的研究人员证明,使用这种皮层脑电图记录技术构建这种小词汇量言语脑机接口是可行的。皮层脑电图和微电极阵列之间的区别在于,微电极阵列实际上穿透皮层,而皮层脑电图停留在皮层表面。因此,它不记录单个神经元放电,仍然记录小区域上的一些平均神经活动。因此,与微电极阵列相比,它的分辨率稍低。这就是为什么他们的原型是这种小词汇量脑机接口,只能以大约75%的准确率解码50个单词。但这仍然是非常令人兴奋的工作,展示了通过将一些电极放入运动皮层,你实际上可以实现言语解码。

我们实验室的研究:高性能言语神经假体 🏆

好的,现在我将介绍我们实验室进行的研究,即构建高性能言语神经假体。在2022年,我们招募了一位代号

15:推理与智能体 🧠🤖

在本节课中,我们将要学习语言模型在推理和作为智能体执行任务两个方面的应用。我们将首先探讨如何让语言模型在数学、几何等领域进行推理,然后了解如何让语言模型在具体环境中执行动作。

推理:语言模型能思考吗?🤔

上一节我们介绍了课程概述,本节中我们来看看什么是推理。推理的核心是利用事实和逻辑来得出结论。具体而言,推理可以分为三种主要类型:

以下是三种主要的推理类型:

  1. 演绎推理:从逻辑规则和前提推导出确定的结论。例如:所有哺乳动物都有肾脏,所有鲸鱼都是哺乳动物,因此所有鲸鱼都有肾脏。
  2. 归纳推理:从观察到的现象中推导出一般性结论。例如:每次看到有翅膀的生物通常是鸟,因此看到有翅膀的生物时,可以推断它很可能是鸟。
  3. 溯因推理:根据观察到的结果,推断出可能的解释。例如:看到一辆无法启动的汽车,引擎下有液体,可能推断出汽车的散热器有泄漏。

除了这种分类,推理还可以分为形式推理和非形式推理。形式推理使用公理和形式逻辑规则来推导真值条件。非形式推理则涉及日常情境,使用常识得出结论。在本节课中,当我们提到“推理”时,通常指的是非形式的演绎推理,并且常常涉及多个步骤。

语言模型与推理

在之前的课程中,我们了解到大型语言模型非常擅长生成符合人类偏好和约束的文本延续。现在,我们尝试回答它们是否也能进行推理。

一种最基本的方法是提示。我们已经见过一种流行的方法,称为思维链提示。这种方法让语言模型在给出答案之前,先生成推理步骤。我们可以通过提供一些包含明确推理步骤的上下文示例,让模型在测试时模仿。

语言模型另一个令人惊讶的特性是,有时你甚至不需要展示这些上下文示例,只需提示“让我们一步步思考”,模型就能在给出答案前生成推理依据。

另一种流行的提示方法是自洽性。这里我们不是贪婪地采样一个推理链和答案,而是采样多个推理步骤和相应的多个答案。然后,我们选择出现频率最高的答案。其理念是,如果一个答案在多个推理链中都出现,即大多数推理链都同意这个答案,那么它更可能是正确的。研究发现,在多种数学推理任务上,这种简单的自洽性方法能显著提升标准思维链提示的性能。

多步推理与问题分解

之前提到,我将讨论多步推理。多步推理的一个主要方面是将一个大问题分解为若干子部分,回答每个子问题,然后将所有内容组合成解决方案。

这种分解策略被整合到另一种提示方法中,称为从易到难提示。其思想是,给定一个问题,首先将其分解为子问题。然后,语言模型回答每个子问题,并基于子问题的答案生成最终答案。

一个有趣的实验表明,有时模型可以从少量推理步骤泛化到更多推理步骤。例如,在一个数学应用题中,如果向模型展示一个包含两个推理步骤的提示作为上下文示例,我们发现它甚至在需要超过五个推理步骤的示例上也能继续泛化,其表现远优于标准思维链提示。

通过蒸馏提升小模型的推理能力

到目前为止,我们探讨了不同的提示方法,以从语言模型中获取推理行为。我们能否做得更多?我们可能感兴趣的是,与其让大型语言模型进行推理,不如让较小的语言模型也具备这种推理能力。

一种流行的方法是蒸馏,即通过教一个小型模型模仿大型模型来对其进行微调。这就是Orca模型的做法。Orca 在一个较小的 130 亿参数 Llama 语言模型上,使用 GPT-4 生成的解释进行微调。

构建这些数据相当简单,包含三个步骤:

  1. 从 FLANv2 集合中获取各种指令。FLANv2 是一个数据集,汇集了多个数据集,包含指令、问题和答案。
  2. 使用这些指令和一条系统消息来提示 GPT-4 或 ChatGPT。系统消息的目标是让模型生成信息丰富的解释以及答案。
  3. 一旦获得这些解释,就用它们来微调小得多的 130 亿参数 Llama 模型。

在 Big-Bench Hard 基准上的评估

到目前为止,我们主要关注数学推理。现在转向一个不同的推理基准:Big-Bench Hard。这是另一个用于多步推理的数据集。

它包含 23 个不同的子任务,涵盖多种推理类型,例如评估布尔表达式、日期理解、几何形状识别等。这些任务对语言模型来说相当具有挑战性。

评估结果显示,经过专门在 GPT-4 解释数据上微调的 Orca 模型,在整体上表现优于 ChatGPT 和未经过此类广泛解释训练的 Vicuna 模型。

自我训练与迭代改进

有人可能会问,为什么不直接在大型语言模型上使用它自己的推理链进行微调呢?这也被探索过,其中一种方法叫做强化自我训练

它交替进行两个阶段:

  1. 生成阶段:给定一个推理问题,让语言模型生成多个推理链,然后根据这些推理链是否得出正确答案进行过滤。
  2. 更新阶段:使用过滤后的推理链微调语言模型。

然后可以迭代进行:使用更新后的模型生成更好的推理链,再用这些更好的推理链进一步微调模型。

结果很有希望。在一些数据集上,随着自我训练迭代次数的增加,性能略有提升。更重要的是,当模型在自己的推理链上进行多次迭代自我训练后,其性能甚至可以超过基于人类提供推理链进行微调的模型。

对语言模型推理能力的质疑

现在,让我们重新审视一开始提出的问题:语言模型真的在推理吗?一种回答方式是应用所有这些方法并查看基准测试结果。但也许正确的方式是更系统化,设计反事实任务,并仔细考虑可能的数据污染问题。

首先,关于思维链的忠实性:模型生成的推理链是否真的影响了其答案?实验表明,在某些情况下,即使提前终止推理链或故意在推理链中引入错误,模型的答案也可能不变。这意味着有时这些推理链可能是模型答案的事后解释。

其次,关于泛化与记忆:如果模型能在十进制下做加法,这是因为它理解了算术,还是仅仅因为训练数据中有完全相同的例子?为了测试这一点,可以创建反事实任务,例如进行九进制加法。如果模型在九进制下的准确率与十进制相同,那么可以推断它可能理解了加法。然而,实验结果显示,当转向这些反事实或分布外设置时,性能通常会显著下降,这可能表明模型更多是记忆而非系统性的推理。

另一项关于类比推理的研究也得出了类似结论。当改变任务描述或使用的字母表时,模型性能显著下降,而人类受试者则表现稳定。这表明模型的“推理”可能缺乏系统性。

语言模型智能体 🕹️

现在,我们将转换话题,讨论语言模型智能体。这与推理相关,因为智能体需要为实现高级目标而进行多步推断,考虑后置条件、对象可用性和世界中的不确定性。

基础术语与历史方法

我们有一个智能体(通常是神经网络)和一个环境。智能体从环境接收观察,基于观察发出动作,同时还接收一个语言指令 G。这个设置有许多名称,如数字智能体、语言条件策略或指令跟随智能体。

在语言模型出现之前,构建这类智能体主要有三种思路:

  1. 语义解析:将问题视为机器翻译,将自然语言命令直接映射为可执行的逻辑形式或动作序列。
  2. 计划推断与执行:从指令和动作序列中推断出一个可执行计划,训练模型从指令生成计划,然后由一个丰富的执行模型来执行这些计划。
  3. 强化学习:直接使用强化学习来学习一个策略,该策略输出能最大化某种奖励的动作,奖励条件基于自然语言指令和观察。

现代方法:将决策视为生成建模

那么,在 2024 年我们如何做呢?也许最有启发性的方式是思考我们试图实现什么:我们试图在给定某个目标的情况下,对轨迹(动作序列)进行建模。

人们很快意识到,可以将决策问题视为一种生成式轨迹建模问题。可以使用一个 Transformer 模型,输入到目前为止的动作历史、当前状态以及任务指示(可以是自然语言字符串),然后训练它预测下一个动作。这本质上是一个自回归语言模型。

因此,一种简单的方法是在循环中提示语言模型

  1. 以文本形式提供动作空间(例如,点击、输入)。
  2. 提供指令。
  3. 提供到目前为止接收到的动作和观察序列。
  4. 要求模型预测下一个动作。

这本质上是在循环中进行思维链提示,但希望将决策问题转化为自回归建模后能够奏效。

评估环境与基准

现在,我们来看看用于评估语言模型作为智能体的不同环境。

MiniWoB 是一个简单的沙盒环境,评估基本的浏览器交互,如转发邮件、点击按钮等。任务通常很短(少于三个动作),但即使是最好的语言模型的零样本性能也远非完美。

WebArena 是一个更接近真实网站的沙盒环境,涵盖电子商务、社交媒体、地图工具等,并支持多标签浏览。它评估功能正确性。

WebLINX 数据集包含在真实网站上的交互,支持多标签浏览,并引入了与用户通信的新动作。

训练智能体:超越人工演示

标准的做法是使用上下文学习和少量示例。对于任何新的网站或用例,通常需要人工执行任务,并将这些演示作为上下文示例输入给语言模型。但这显然不可扩展。

因此,我们可以使用本节课前半部分看到的思想:让语言模型生成输出(这里是动作轨迹),然后将其用作监督信号。具体方法如下:

  1. 随机探索:让一个智能体在环境中随机探索,生成轨迹。
  2. 轨迹重标注:使用第二个语言模型为这些轨迹生成描述(即猜测它完成了什么指令)。如果描述与轨迹匹配良好,则保留。
  3. 条件生成与迭代:基于生成的指令,让语言模型生成新的、更有针对性的轨迹。再次使用重标注器评估,如果不好,则重新标注轨迹并迭代,直到获得良好的指令-轨迹对。
  4. 使用合成数据:获得合成示例集合后,可以用它们进行微调,或者更简单地,用这些合成演示替换上下文学习中的少量人工演示。

实验表明,这种方法能在 MiniWoB 等基准上带来合理的性能提升。

多模态智能体:处理视觉输入

到目前为止,我们只处理了文本。但在实际应用中,获取每个环境的 HTML 并输入到语言模型的上下文中可能不切实际。也许更好的方式是直接显示环境的像素图像。

因此,我们来看一些用于构建智能体的视觉语言模型例子。

LLaVA 的思想类似于 Orca,使用 GPT-4 根据图像的文本描述生成指令和回答,然后联合微调一个图像编码器和一个文本解码器,最终得到一个能输出语言响应的图像编码器。

Pix2Struct 的方法也类似,包含图像编码器和文本解码器。它引入了一项新的预训练任务:屏蔽网页截图的部分区域,然后要求 Transformer 解码器生成对应于被屏蔽区域的 HTML。这似乎是一种更自然的预训练目标,可以实现更好的图像-文本交互。

挑战与现状

目前,这仍然是一个新兴的应用领域,存在巨大的“提示差距”。即使不进行大量提示工程,不使用针对每个环境精心设计的少量示例,即使是最好的语言模型在非常简单的任务(如 MiniWoB)上也远非完美。

即使是简单的 MiniWoB,在从单步指令映射到多步(5或10步)指令映射时,性能也会显著下降。长程规划在这些简单基准上仍然非常困难。

在更复杂的基准如 WebArena 上,即使经过提示和少量示例,最佳模型的任务成功率与人类水平之间也存在巨大差距。模型犯的错误也可能很奇怪,例如在密码字段中输入电子邮件,或重复搜索词导致无结果。因此,这个领域还有很大的改进空间。

总结 📝

本节课我们一起探讨了两个主要话题。

我们首先探讨了语言模型中的推理。我们看到有几种方法可以让语言模型表现出类似推理的行为,例如通过各种方式提示它们(思维链提示、自洽性提示、从易到难提示)。我们还可以通过从大模型生成推理链来微调专门的小模型,以提升其推理能力。或者,可以直接在大模型上使用其自身的推理链进行迭代自我训练,有时性能可以持续提升,甚至超过基于人类推理链的微调。然而,另一方面,如果我们进行反事实评估,会发现并不清楚模型表现好是因为真正的推理能力,还是因为这些问题在训练数据中以某种形式出现过。

在第二部分,我们探讨了语言模型智能体。我们回顾了历史上人们构建具身智能体的视角,然后看到可以将决策问题重新定义为因果语言建模。接着,我们考察了人们用语言模型进行决策建模的各种方式,其中大部分涉及提示和上下文学习。我们还看到了一种生成合成演示的方法,类似于第一个模块中的思想,即在环境中进行探索和迭代重标注。我们考察的语言模型大多是纯文本的,也看到了一些可以同时接受文本和视觉输入的多模态模型例子。最后,我们看到相关基准非常具有挑战性,模型会犯一些简单的错误,人类性能与模型性能之间还存在巨大差距,未来有很大的改进空间。

16:DPO之后:语言模型对齐的现状与未来 🧠

在本节课中,我们将跟随Nathan Lambert的演讲,深入探讨语言模型对齐领域在DPO(直接偏好优化)方法出现后的最新进展。我们将回顾对齐技术的发展历史,分析当前的核心挑战,并展望未来的研究方向。

概述:从RLHF到DPO的演变

上一节我们介绍了语言模型的基础训练。本节中,我们来看看模型训练完成后,如何通过“后训练”或“对齐”技术使其行为更符合人类期望。这一阶段正变得越来越重要。

语言模型发展简史 📜

以下是语言模型发展的关键节点:

  • 早期基础:从Claude Shannon的信息论开始,自回归损失函数逐渐展现出潜力。
  • 深度学习兴起:2017年的Transformer架构、2018年的GPT、ELMo和BERT模型是NLP领域的基石。
  • 规模化定律:GPT-2等模型证明了模型规模与性能的关联。
  • 实用性觉醒:2020年左右,大规模语言模型的实用性开始被广泛认识。
  • ChatGPT的里程碑:2022年底ChatGPT的发布,标志着RLHF(基于人类反馈的强化学习)等对齐技术成为打造实用模型的关键一环。

对齐技术核心概念 🔧

在深入DPO之前,我们需要明确一些核心概念。

指令微调 vs. 监督微调

指令微调旨在训练模型遵循指令,使其更易用、更互动。监督微调则更侧重于特定领域的任务适应。两者都使用自回归损失函数,但目标不同。

对齐与RLHF

“对齐”是一个宽泛的目标,即让模型行为与用户意图一致。RLHF是实现对齐的一种具体方法,它利用人类对模型输出的偏好数据(例如,选择A回复优于B回复)进行训练。

深入DPO:简化对齐流程 🚀

上一节我们提到了RLHF的复杂性。本节中,我们来看看DPO如何极大地简化了这一流程。

RLHF的传统挑战

传统的RLHF流程复杂,其目标函数通常表示为:
最大化 E[奖励函数] - β * KL(当前策略 || 初始策略)
这需要分别训练奖励模型和优化策略,涉及复杂的工程实现。

DPO的核心创新

DPO的关键在于,它通过数学推导,将上述复杂的强化学习优化问题,转化为一个可以直接通过梯度下降解决的分类问题。其损失函数简洁明了。

以下是DPO损失函数的简化表示(基于Bradley-Terry模型):

# 伪代码示意DPO损失计算
loss = -log(sigmoid(beta * (log_policy_chosen - log_policy_rejected) - (log_ref_chosen - log_ref_rejected)))

其中,log_policy 是当前策略模型的对数概率,log_ref 是参考模型(通常为SFT后的模型)的对数概率,chosenrejected 分别代表被偏好和未被偏好的回复。

DPO的优势非常明显:

  1. 实现简单:无需构建独立的奖励模型和复杂的RL训练循环。
  2. 计算高效:训练更稳定,所需计算资源相对较少。
  3. 易于调试:作为一个标准的监督学习损失,更容易分析和调整。

因此,DPO已成为开源社区和学术界进行对齐研究的首选起点。

DPO的实践之路与数据依赖 📊

DPO论文于2023年5月发表,但直到几个月后,基于DPO的高质量模型才大量出现。这凸显了数据在这一过程中的关键作用。

以下是推动开源对齐模型发展的几个关键数据集:

  • ShareGPT:早期的人类指令-回复数据,虽然来源存在争议,但极大地推动了Vicuna等模型的发展。
  • OpenAssistant:一个由社区精心策划的包含提示、回复和偏好的高质量数据集。
  • UltraFeedback:一个由GPT-4标注的大规模合成偏好数据集,为Zephyr等模型的成功奠定了基础。

这些数据集的共同点是,它们提供了模型训练所必需的(提示, 获胜回复, 拒绝回复)三元组。目前,高质量、多样化的偏好数据仍然是制约对齐研究的主要瓶颈之一。

评估挑战:奖励模型基准 🎯

要系统性地改进对齐技术,我们需要更好的评估工具。一个核心问题是:如何知道一个奖励模型的好坏?

奖励模型的角色

在RLHF框架中,奖励模型为策略模型(即我们最终使用的语言模型)的更新提供学习信号。评估奖励模型的质量,对于理解整个对齐流程至关重要。

RewardBench基准

为了评估奖励模型,我们引入了RewardBench。它通过收集涵盖多种能力(如聊天、推理、安全)的提示和人类标注的偏好对,来测试奖励模型能否与人类判断保持一致。

评估发现:

  • 性能饱和:许多开源奖励模型在通用任务上表现接近。
  • 挑战依然存在:在“Chat-Hard”等需要细致理解的任务上,所有模型都有很大提升空间。
  • 安全与有用性的权衡:评估清晰地揭示了不同模型在“安全拒绝”和“有用回答”之间的不同倾向。

这个基准表明,尽管进展迅速,但奖励模型(乃至整个对齐技术)远未达到完美,特别是在处理复杂、微妙的任务时。

DPO vs. PPO:在线数据的重要性 ⚖️

既然DPO如此简便,业界为何仍在研究和使用更复杂的PPO(近端策略优化)呢?核心区别在于数据的使用方式

离线 vs. 在线学习

  • DPO(离线):使用一个固定的、预先收集的数据集进行训练。数据可能来自各种不同的模型。
  • PPO(在线):在训练过程中,使用当前策略模型生成新的回复,并用最新的奖励模型进行评分。数据分布随着策略更新而动态变化。

实验洞察

我们的实验表明,在相同数据上,PPO通常能比DPO带来小幅但一致的性能提升(约1-2%)。然而,PPO的实现和调优要复杂得多,且训练速度慢,因为它需要实时生成文本。

近期多项研究指出,“在线”学习——即使用当前模型生成的数据进行训练——对于突破性能瓶颈可能至关重要。这引出了新的研究方向:如何让DPO也能融入在线学习的优势?

未来方向与行业动态 🧭

展望未来,语言模型对齐研究有几个关键方向:

  1. 数据创新:创建更多样、更高质量、针对特定能力(如复杂推理)的偏好数据集。
  2. 方法演进:探索超越成对偏好的方法(如多答案排序、细粒度评分),以及融合在线学习的改进型DPO算法。
  3. 模型尺度:加强对小规模模型(如7B、13B参数)的对齐研究,这更具学术可行性和实用价值。
  4. 评估细化:开发更能反映真实用户体验和特定关注点(如个性化)的评估标准。

从行业实践(如Meta的Llama 3)来看,领先的机构很可能采用混合策略:在模型开发的不同阶段,灵活结合监督微调、拒绝采样、DPO和PPO等多种技术,以在有限时间内达到最佳效果。

总结

本节课中我们一起学习了DPO方法如何简化了语言模型的对齐流程,并深入探讨了当前对齐领域的核心:数据瓶颈评估挑战以及在线学习的重要性。我们看到,尽管DPO降低了入门门槛,但要达到最先进的性能,仍需在数据生成、方法创新和系统评估上持续探索。对齐技术正处于快速发展期,为研究者提供了广阔的探索空间。

17:卷积神经网络和树递归神经网络 📚

概述

在本节课中,我们将学习两种用于自然语言处理的神经网络技术:卷积神经网络(CNNs)和树递归神经网络(Tree RNNs)。虽然这些技术如今已不如Transformer模型那样被广泛使用,但了解它们有助于我们理解NLP领域的技术演进和不同思路。


卷积神经网络(CNNs)在NLP中的应用 🧠

上一节我们介绍了课程背景,本节中我们来看看卷积神经网络如何应用于语言处理。

与循环神经网络(RNNs)按顺序处理句子不同,卷积神经网络的基本思想类似于N-gram模型。它能够处理单词的N-gram(如二元组或三元组),并为每个N-gram生成神经表示。

卷积操作的基本原理

在视觉领域,卷积神经网络通过滑动一个由权重定义的“掩码”(或过滤器)在图像上操作,计算每个位置的得分。对于语言,我们处理的是一维的单词序列,而非二维图像。

以下是处理一个句子“tentative deal reached to keep government open”的示例步骤:

  1. 每个单词用一个词向量表示(例如,4维向量)。
  2. 定义一个应用于三元组的过滤器。
  3. 将该过滤器沿序列向下滑动,计算过滤器与每个三元组的点积,得到一个值。
  4. 通常,我们会加上一个偏置项,并通过一个非线性函数(如Sigmoid)进行处理。

这样,我们就为每个三元组计算出了一个值。这个过程就是单过滤器的卷积操作。

多过滤器与池化操作

通常,我们会使用多个过滤器来捕获文本的不同特征。

以下是处理多过滤器的步骤:

  1. 定义多个过滤器,每个过滤器可能关注不同的语言特征(例如,人称代词“I, my, we, our”或言语动词“think, say, told”)。
  2. 为每个过滤器和每个N-gram计算一个值,从而得到一个新的向量表示。

为了汇总所有过滤器的信息,最常用的方法是最大池化(Max Pooling)。最大池化的思想是:每个过滤器就像一个特征检测器,我们只关心该特征是否在文本的任何位置被强烈激活。因此,我们取每个过滤器在所有位置上的最大值作为该特征的最终输出。

另一种方法是平均池化(Average Pooling),它计算特征在所有位置上的平均值。虽然有时也会使用,但实践表明,对于神经网络学习到的特征类型,最大池化通常更有效。

PyTorch实现

在PyTorch中,可以使用Conv1d来实现一维卷积。关键参数包括:

  • out_channels: 过滤器的数量。
  • kernel_size: 过滤器的大小(例如,对于三元组,大小为3)。
    之后,可以使用最大池化操作来汇总结果。

其他卷积变体

除了基本的卷积和池化,还有一些其他技术:

以下是几种可选的卷积操作:

  • 步长(Stride): 控制过滤器滑动的步幅。例如,步长为2意味着过滤器每次移动两个位置,减少了N-gram之间的重叠。
  • 局部最大池化(Local Max Pooling): 不是在整个序列上进行全局最大池化,而是在局部窗口(例如,每两个元素)上进行池化,可以保留更多位置信息。
  • K-max池化(K-max Pooling): 保留每个通道中前K个最大值,而不是仅保留一个最大值,可以检测特征是否在多个位置出现。
  • 扩张卷积(Dilated Convolution): 允许过滤器跳过一些位置来组合元素,从而在不增加参数的情况下扩大感受野。这在语音处理中比在自然语言处理中更常见。

经典CNN模型案例研究 📄

上一节我们介绍了CNN的基本工具,本节中我们来看看两个利用卷积进行自然语言处理的经典工作。

1. Yoon Kim的句子分类模型(2014)

这是将CNN应用于NLP最著名的早期工作之一,主要用于情感分类等句子分类任务。

该模型的核心流程如下:

  1. 获取句子中每个单词的词向量。
  2. 应用多种不同尺寸(如二元组、三元组、四元组)的卷积过滤器。
  3. 对每个过滤器的输出进行最大池化,得到一个标量值。
  4. 将所有过滤器的池化结果拼接成一个固定长度的句子向量。
  5. 将该向量输入一个简单的Softmax分类器,得到最终分类(如正面/负面情感)。

该模型的一个创新点是处理了词向量微调(Word Vector Fine-tuning) 的难题。在小型监督数据集上微调预训练词向量时,未出现在训练集中的词其向量不会更新,可能导致语义关系混乱。Yoon Kim的解决方案是:为每个过滤器通道创建两个副本,一个使用可微调的词向量,另一个固定使用原始词向量,从而兼顾了两者的优点。

实验表明,这个简单的CNN架构在当时的情感分析等任务上取得了非常有竞争力的结果。

2. 深度字符级CNN(VD-CNN, 2017)

这个模型试图将视觉领域成功的“深度”和“原始信号处理”理念引入NLP。

以下是VD-CNN架构的主要特点:

  • 输入: 原始字符,而非单词。
  • 架构: 非常深的卷积网络(如29层),包含卷积块、残差连接和局部池化。
  • 过程: 从字符级表示开始,通过多层卷积和池化,逐渐组合成更大的文本单元表示。
  • 输出: 使用K-max池化保留最重要的激活,最后通过全连接层进行分类。

该模型在多个文本分类数据集上达到了当时的先进水平,证明了仅从字符开始,通过深度CNN也能有效进行文本理解。


树递归神经网络(Tree RNNs)🌳

上一节我们探讨了基于序列和N-gram的CNN模型,本节中我们来看看另一种基于语言结构的思路:树递归神经网络。

人类语言具有递归的层次化结构,即相同的句法结构可以嵌套在自身内部。树递归神经网络的目标就是根据句子的短语结构树,自底向上地组合词向量,为每个短语计算出一个语义表示。

基本原理

最简单的树递归神经网络操作如下:
给定两个子节点(词或短语)的向量表示 c1c2,父节点向量 p 通过以下方式计算:
p = f(W * [c1; c2] + b)
其中,[c1; c2] 表示向量拼接,W 是权重矩阵,b 是偏置,f 是非线性激活函数。同时,还可以用另一个参数向量对 p 进行点积,得到一个分数,用于判断这个组合是否构成一个合理的句法成分。这个分数可以用于构建贪婪句法分析器。

递归神经张量网络(RNTN)

为了更灵活地处理不同的语义组合方式(例如,“红球”中形容词修饰名词,与“踢球”中动词支配宾语的方式不同),我们提出了递归神经张量网络(RNTN)

RNTN的核心是引入了神经张量层。它不再仅仅是将子向量拼接后做线性变换,而是学习一个三维张量,允许子向量之间进行更复杂的 multiplicative interactions,从而能根据组合类型动态调整语义计算方式。

在情感分析中的应用

我们在斯坦福情感树库(Stanford Sentiment Treebank) 上应用了RNTN。这个数据集不仅标注了整个句子的情感,还标注了句子中每个短语的情感,为模型提供了更丰富的监督信号。

RNTN模型能够正确建模复杂的语义组合现象,尤其是否定(Negation) 的语义作用。例如:

  • 它能理解“incredibly dull”(非常无聊)整体仍是负面的。
  • 更重要的是,它能正确判断“definitely not dull”(绝对不无聊)是正面的,即对负面词的否定会使其转向正面。这一点是简单的N-gram模型或早期的Tree RNN难以做到的。

尽管Transformer模型在整体性能上远超Tree RNN,但在精确建模某些语义组合(如否定、量化词的作用范围)方面,基于树结构的模型在理念上仍有其优势。


总结

本节课我们一起学习了两种重要的神经网络架构在NLP中的应用。

首先,我们探讨了卷积神经网络(CNNs)。它通过滑动过滤器捕获局部N-gram特征,并利用池化操作(尤其是最大池化)来汇总信息,在文本分类任务上表现优异。我们分析了Yoon Kim的经典句子分类模型和深度字符级VD-CNN模型。

其次,我们介绍了树递归神经网络(Tree RNNs)。这种模型受语言递归结构启发,沿句法树自底向上组合语义,能够更精细地建模短语含义。递归神经张量网络(RNTN)通过引入张量操作增强了组合的灵活性,并在细粒度情感分析任务上展示了其捕捉复杂语义组合(如否定)的能力。

虽然当前Transformer模型已成为主流,但了解CNN和Tree RNN的历史贡献和独特视角,对于全面理解NLP技术的发展脉络和未来可能的融合方向仍然非常有价值。

18:NLP、语言学与哲学 🌐🧠

在本节课中,我们将回顾CS224N课程的核心思想,探讨当前大型语言模型的现状与挑战,并深入讨论符号系统与神经网络、语言的意义以及人工智能的未来风险等哲学与语言学层面的问题。


课程核心思想回顾 📚

上一讲我们探讨了模型评估与推理。本节中,我们来看看贯穿整个CS224N课程的主要脉络。

我们首先从词向量开始,逐步构建了神经NLP系统的概念。我们从简单的前馈网络扩展到序列模型、语言模型、RNN和LSTM,然后引入了具有深远影响力的新模型——Transformer。在此基础上,我们进一步探讨了近年来构建高性能NLP系统的范式:先进行预训练,再通过各种技术进行后训练,最终形成理解语言能力强大的通用基础模型。之后,我们还讨论了基准测试和推理等特定主题。

以下是本课程涉及的几个核心思想:

  • 稠密表示与分布式语义:我们通过神经网络的隐藏表示来获取稠密向量。其核心思想是分布式语义,即通过上下文来表征词语。第一句口号是:“You shall know a word by the company it keeps”(观其伴,知其义)。这个思想驱动了现代NLP大多数成功的方法。
  • 大规模深度神经网络的训练:我们探讨了训练大规模深度神经网络的挑战与机遇,以及人们如何逐步发展出残差连接等技巧,使得训练过程更加稳定和可行。
  • 序列模型与Transformer:我们讨论了序列模型的优缺点,以及Transformer架构如何通过并行化在很大程度上解决了这些问题。
  • 基于语言建模的预训练:语言建模(预测上下文中的词语)看似简单,但它演变成了一种通用的预训练任务。各种语言知识和世界知识都有助于更好地完成这个任务,从而催生了今天我们所拥有的强大、知识丰富的模型。
  • 缩放定律:迄今为止,我们观察到一个经验事实:随着数据、计算量和模型规模的数量级增长,模型性能似乎呈现极其线性的提升

NLP的开放问题与挑战 ⚠️

尽管取得了巨大进展,但NLP领域远未解决所有问题。人们仍在许多方面努力改进。

泛化与记忆

一个核心问题是,这些模型在多大程度上是真正学会了泛化能力,而不仅仅是擅长记忆。大型预训练语言模型的许多优势源于它们见过海量数据,因此“知道”一切。它们见过所有模式,知道如何使用。有时,大型语言模型更像一个庞大的知识库,而非 necessarily 具备人类那种解决新问题和泛化的智能。

一个有趣的事实是,在某些方面,Transformer模型比之前的LSTM模型泛化能力更差。一项研究显示,在从有限数据中学习由有限自动机生成的数据模式时,LSTM在所见数据规模下几乎完美泛化,而Transformer则需要看到多得多的数据才能学好模式。人类智能的一个主要特征正是能够从非常有限的曝光中学习和理解事物,而我们的模型似乎并不总是如此。

模型的可解释性

人们对神经网络内部发生了什么抱有浓厚兴趣。很多时候,神经网络仍然像是黑箱,我们并不真正了解它们如何工作。因此,有很多工作开始更仔细地审视神经网络计算内部发生的事情,这被称为机械可解释性因果抽象,但这个问题远未解决。

多语言与低资源语言

一个远未解决(在许多方面可能无法解决)的问题是处理世界上所有其他语言的多语言问题。必须意识到,对于英语所看到的一切,其他语言从现代语言模型中获得的要更差。

虽然有好消息——例如,GPT-4在许多大语言上的表现已经超过了GPT-3.5在英语上的表现——但这仍然都是大语言。真正的问题是,当涉及到小语种、低资源语言时会怎样?世界上绝大多数语言的使用者不足百万,许多语言主要是口语,书面文本非常有限。目前尚不清楚我们课程后半部分讨论的这类语言技术如何扩展到这些语言,因为根本没有足够的数据来构建我们所看到的这类模型。

基准测试的完整性

本课程中,大家应该已经了解到评估是我们工作的核心部分。进步的很大一部分是由定义模型应达到的评估标准驱动的。然而,许多人担心,随着大型公司近期发布的封闭模型,所有基准都可能被“污染”而不可信。一个担忧是,由于网络上数据量巨大,这些大型语言模型的预训练数据中可能已经包含了大量挑战性问题,导致模型只是记住了答案,而非真正公平地解决它们。因此,如何保持基准测试的完整性是一个问题。

领域特定NLP与社会文化问题

另一个重要领域是让NLP在不同的技术领域(如生物医学、临床医学、法律等)发挥作用。这些领域在词汇、用法上存在很大差异,既有巨大的潜在益处,也存在因语言理解不完整而造成伤害的风险。例如,在法律NLP领域,研究发现模型生成法律答案时的幻觉率(即编造内容的比例)相当高。

此外,还需要解决NLP的社会和文化方面问题。NLP系统仍然对某些文化、宗教存在严重偏见,其习得的社会规范可能对某些群体不利。同时,也存在 underrepresented groups 在获得所需NLP技术方面的问题。


当前大型语言模型的现状 🤖

接下来,我想就我们目前所处的最佳语言模型(如GPT-4)的状态提供一些视角。一方面,这些模型的性能令人惊叹。即使作为一个在NLP领域工作多年的人,我也觉得它们在某些方面仍然像魔法一样难以解释。

例如,当我要求ChatGPT-4写一首十四行诗来解释Transformer神经网络架构,且每一行都以字母T开头时,它成功地产出了符合要求、基本押韵、大致符合抑扬格五音步的诗句。这仍然让我感到震惊,我甚至无法向自己令人信服地解释这个大型Transformer是如何做到的。

作为自然语言理解和世界理解设备,这些模型显然已经在许多情境下达到了非常可用的门槛。一项研究让波士顿咨询集团的顾问使用GPT-4完成任务,结果发现使用GPT-4的小组平均多完成了12%的任务,速度快了25%,且结果质量被判定高出40%。这显示了LLM在帮助人们完成实际工作方面的显著成效。有趣的是,使用这些LLM似乎是一个巨大的均衡器,对于自身技能较弱的人帮助更大。

但另一方面,也有不那么乐观的研究。例如,一项研究评估GPT-4能否写出与《纽约客》小说家质量相当的小说,结果发现GPT-4在创意写作方面被评定为比人类作家差3到10倍。因此,人类仍有希望。

我认为这描绘了我们当前的双重图景:在某些方面,这些模型很棒、很有用;在其他方面,它们并不那么出色。这种状况在未来几年可能仍将持续。


符号方法与神经网络 🔣🕸️

我想回过头来,多谈谈从20世纪60年代到2010年左右主导AI的符号方法,以及我在这里称之为控制论的方法。从非常真实的意义上说,神经网络是控制论传统的延续,而非始于20世纪50年代和60年代的AI传统。

在这个背景下,斯坦福大学是“符号系统”项目的发源地。这个名称源于项目创立时的理念,即研究有意义的符号系统(如人类语言、逻辑和编程语言)以及处理这些符号的系统(如大脑、计算机和复杂的社会系统)。这与典型的认知科学观点形成对比,后者更侧重于将心智和智能视为自然发生的现象。

在AI术语中,AI作为一个领域及其名称的出现,是围绕符号方法展开的。约翰·麦卡锡提出了“人工智能”这个新名称,以区别于当时MIT诺伯特·维纳等人所追求的控制论方法。麦卡锡的背景是数学家和逻辑学家,他希望构建一个类似于数学和逻辑的人工智能。纽厄尔和西蒙进一步发展了这种立场,提出了物理符号系统假说,认为物理符号系统是产生一般智能行为的必要且充分条件。这构成了经典AI的基础。

相比之下,控制论起源于控制和通信领域,更接近电气工程背景,旨在统一动物(可能多于人类)与机器之间的控制和通信思想。神经网络正是在控制论传统下首次被探索的。

那么,对于NLP和语言,我们如何看待这一点?我认为,毫无疑问,语言是一个符号系统。即使在口语中,人类语言的结构也是一个符号系统,我们拥有代表声音(音素)的符号。但是,反对纽厄尔和西蒙的观点,人类使用符号系统进行交流,并不意味着符号的处理器——人脑——必须是一个物理符号系统。同样,我们也不必把NLP的计算机处理器设计成物理符号系统。大脑显然更像神经网络模型,而且神经模型可能比符号处理器更能扩展并更好地捕捉语言处理。

这就引出了一个问题:为什么人类会发明符号系统进行交流?我认为主流观点(在我看来是合理的)是,拥有符号系统能提供信号可靠性。当存在离散的、分离的目标点时,即使在信号退化的情况下,也能很好地恢复信息。

那么,主要以描述符号系统方式发展的语言学,其角色是什么?我认为正确的思考方式是,语言学为我们思考语言习得、处理和理解时提供了问题、概念和区分。随着NLP和AI的进一步发展,能够处理大量低层次任务后,语言学家经常讨论的更高层次概念,如组合性系统性泛化,在构建神经系统的AI语境中变得越来越受关注。

在更实际的层面上,我认为我们不一定需要相信所有语言学理论的细节,但对于人类语言的结构和行为方式,我们的大部分广泛理解是正确的。因此,当我们思考NLP系统、理解它们的行为、评估它们是否具有某些属性、构思评估方法时,很多工作都是基于语言学理解进行的。


语言的意义:指称论与使用论 🧭

我想再谈谈我们应该为语言使用哪种语义学。这回到了我之前提到的词向量问题。

在语言哲学或语言学语义学中占主导地位的语义学概念是模型论或指称语义学,即词语的意义是它们在世界中的指称。例如,“电脑”这个词的意义就是所有电脑的集合。这与分布式语义学形成对比,后者认为词语的意义在于理解其使用的上下文,这实际上是我们神经模型所使用的方法。

传统的理解人类语言意义的方式(如果你上过逻辑课,可能会见过)是:我们有一个句子,比如“红苹果在桌子上”,然后我们将其写成某种逻辑表示(如一阶谓词演算)。在早期,逻辑学家阿尔弗雷德·塔斯基认为,无法通过谈论人类语言来讨论意义,因为人类语言“不可能地不连贯”。但他的学生理查德·蒙塔古反对这一观点,认为形式语言和自然语言之间不存在重要的理论差异,并开始构建描述自然语言句子意义的形式语义学。蒙塔古的工作成为语言学中语义学研究的基础,也是NLP历史上(大约1960年至2010年代)进行自然语言理解的主要模型。

在这种图景下,要理解一个句子,我们首先对其进行句法解析,然后根据蒙塔古提出的思路,通过查词典获取词义,并利用人类语言的组合性,根据词语的意义及其组合方式,逐步计算出更大短语和从句的意义,最终构建出句子的意义表示。这种技术被用于从20世纪60年代到21世纪初构建的自然语言理解系统,并在机器学习语境下发展为语义解析。这些系统可以在有限领域内工作,但总是极其脆弱。

有趣的是,有证据表明人类会做类似的事情:他们分析句子结构,并以自底向上、基本投射的方式计算意义。但这显然不是我们当前Transformer模型的做法。那么,我们当前的神经语言模型是否提供了合适的意义函数?这是一个复杂的问题。在许多方面,它们似乎确实做到了,它们对你输入的任何句子都表现出惊人的理解能力。但仍有一些真正的担忧,即它们是否在走捷径,或者在一定程度上工作,而没有达到人类那种具有系统性泛化的组合性理解。

这就是传统的指称语义学观点,它与意义的使用理论形成对比。意义的使用理论认为,词语的意义在于其使用方式。维特根斯坦在其后期著作《哲学研究》中提出了这一观点。他质疑指称论,认为将意义视为与词语同类但不同的“事物”(符号与其指称物)是奇怪的。他主张,意义就像金钱,其意义在于它在世界中的使用方式,而不是指向金钱本身。

那么,这种分布式的、使用的意义理论是一种好的意义理论吗?有些人,比如本德及其合作者,不接受这种理论作为意义或语义学理论。他们认为,只有拥有形式与意义之间的指称关系才算拥有意义。

但我认为这过于狭隘。我认为我们必须论证,意义产生于将词语与其他事物联系起来。虽然从某种意义上说,将词语与现实世界中的事物联系起来是 privileged,但这并不是 grounding 意义的唯一方式。你可以在虚拟世界中拥有意义,也可以通过将一个词与人类语言中的其他事物联系起来而拥有意义。此外,我认为意义不是一个非0即1的东西。你可以或多或少地理解词语和短语的意义。

以“Chennai”(一种印度乐器)这个词为例。如果你见过或拿过它,你就有了经典的 grounded 意义。如果没有,我可以给你看一张图片,这给了你部分意义。如果我告诉你它是一种有点像双簧管的传统印度乐器,即使你从未见过,你也理解了它的一些意义。如果我给你一个文本使用示例(例如,“Chennai players sat... playing their pipes...”),你从那个例子中知道了一些事情:你对其声音有了一种描述(“wailing”),并且知道它与婚礼有关。这是你仅仅拿着或看着它,甚至听人演奏所无法获得的。从这个意义上说,我认为意义来自各种联系。


人工智能的未来与风险 🚨

最后一个话题,我们的人工智能未来。是的,我们对AI未来有不同的担忧。

一个担忧是我们是否会失业。这是一个有趣的问题,但也是一个长期存在的恐惧。从1928年《纽约时报》谈到“节省劳力的机器”导致失业,到1961年《时代》杂志担忧自动化淘汰半熟练工人,这种恐惧至今尚未完全实现。目前,总体而言,几乎每个人都有工作,许多人每周工作时间很长。

另一个更严重的担忧是,几乎所有的钱是否会流向5到10家巨大的科技巨头。这似乎是我们目前正在走向的方向。现代网络和AI人才的集中倾向于鼓励这种结果。但这本质上是一个政治和社会问题,而非技术问题。

下一个问题是,我们是否应该害怕即将到来的奇点——当机器拥有超越人类水平的人工通用智能时?特别是,这样的事件是否会威胁人类生存?近年来,随着对AI存在性风险的讨论进入主流,这种担忧急剧增加。但我不太相信这些担忧。相反,我认为越来越多的人开始反驳它们。例如,Keras的创建者弗朗索瓦·肖莱认为,不存在任何可能代表人类灭绝风险的AI模型或技术。Meta的AI负责人扬·勒昆称存在性风险论述是“不理智的”。

许多人认为,对存在性风险的关注(更愤世嫉俗地说,其目的)是为了转移人们对公司部署自动化系统所带来的直接危害的注意力,这些危害包括偏见、工人剥削、版权侵犯、虚假信息、权力日益集中以及领先AI公司的监管俘获。在关于惊人AI及其所能做的一切(如完成作业、生成美妙图像)的讨论背后,存在着许多问题:虚假信息、欺骗、幻觉、决策同质化问题、侵犯版权和人类创造力、大量排放、侵蚀丰富的人类实践等。

我们需要意识到AI可能带来的当前危害。对于NLP,也存在各种我们提到过的危害,包括生成冒犯性内容、生成不真实内容以及助长虚假信息。虚假信息这一点很有趣:如果模型能很好地推理文本,它们是否也能在向用户传播错误信息或观点时具有说服力?也许存在新的可能性,可以进行非常个性化的虚假信息传播,比传统的政治广告方式更容易说服人类。已有初步证据表明这是真的。也许最糟糕的还不是技术领域,视觉伪造品在政治语境中可能更具说服力。我们很可能在不久的将来看到AI生成的伪造品对政治体系产生重大影响的事件。

因此,我认为我们真正应该担心的不是存在性风险,而是拥有权力的人和机构将用AI做什么。这是我们多次在社交媒体上注意到的模式。新技术被掌握新科技选项的强大个人和组织所捕获,AI和机器学习正越来越多地用于监视和控制。我们目前在世界各地都看到了这一点。


总结与结语 🌟

在本节课中,我们一起回顾了CS224N的核心思想,探讨了当前大型语言模型的成就与局限,深入分析了符号系统与神经网络在语言处理中的角色,并从哲学层面审视了语言意义的两种主要理论(指称论与使用论)。最后,我们讨论了人工智能未来可能带来的社会风险与挑战,强调了关注当下实际危害、促进技术民主化与教育普及的重要性。

正如卡尔·萨根在《魔鬼出没的世界》中所警示的,我们面临的真正风险可能不是技术失控,而是在技术力量集中于少数人手中时,公众失去理解和质疑的能力,从而滑向迷信与蒙昧。因此,像斯坦福大学这样的地方所提供的教育,以及支持广泛知识传播的开源精神,显得尤为重要。

感谢大家学习CS224N课程。

19:多模态深度学习 🎯

概述

在本节课中,我们将要学习多模态深度学习。多模态学习涉及整合来自不同类型数据(如文本、图像、音频)的信息,以构建更智能、更接近人类理解世界方式的模型。我们将从早期模型开始,深入探讨特征提取与融合的核心技术,回顾多模态基础模型的发展历程,并讨论评估方法与其他模态的应用。


早期多模态模型

上一节我们介绍了课程概述,本节中我们来看看多模态深度学习的早期探索。在深度学习革命之前,已有许多相关工作。从深度学习的角度来看,早期工作始于将视觉模型与语言模型对齐。

基本思路是,我们一方面有一个视觉模型,另一方面有一个语言模型。语言模型可以是基本的词嵌入模型。我们需要在一个多模态空间中对齐它们。实现方法是使用某种相似性度量、评分函数或核函数,并通过最大间隔或间隔损失来学习如何在嵌入空间中排列这些点。

相似的点需要拉近,不相似的点需要推远。在这个多模态嵌入空间中,可以实现有趣的跨模态迁移。例如,可以获取“汽车”或“马”的词嵌入,然后在嵌入空间中找到与之接近的图像,从而解决检索问题。

以下是早期模型的一些关键特点:

  • 跨模态迁移:可以在图像和文本之间进行迁移学习。
  • 多模态词嵌入:通过结合图像和文本信息,可以获得更能代表人类语义理解的词向量。例如,对于“猫”这个词,我们既可以通过阅读维基百科的定义来理解,也可以通过观察猫的图片来理解。对于许多人来说,图片可能更接近“猫”这个概念的含义。
  • 视觉词袋模型:一种早期优雅的方法,通过类似SIFT的算法找到图像关键点,获取特征描述符,然后使用K-means聚类,最终得到类似于文本词袋模型的视觉表示。

在深度学习兴起后,研究者开始将这些思想应用于深度神经网络。早期版本使用卷积神经网络(CNN)提取图像特征,并与词嵌入结合,形成多模态词向量。更高级的方法包括使用Skip-gram模型来预测图像特征。

然而,词是有限的,我们真正关心的是句子。因此,研究者开始关注句子表示,以及如何将句子表示与图像对齐。这里的损失函数与词和图片对齐时类似,但编码器换成了句子编码器(如LSTM或递归神经网络)。研究表明,通过预测图片来学习句子表示(即“接地”的句子表示),可以得到非常好的句子语义表示,并能迁移到其他NLP任务(如情感分类)中。

随着序列到序列架构的出现,研究者将用于机器翻译的文本编码器替换为CNN,从而实现了图像描述生成。在这个过程中,注意力机制发挥了关键作用,它允许模型在生成描述时对齐图像中的特定区域。

最后,生成对抗网络(GAN)也是早期重要模型之一。其基本思想是通过生成器和判别器的对抗训练,使生成器能够生成以文本为条件的逼真图像,这为后来的文本到图像生成(如Stable Diffusion)奠定了基础。


特征与融合技术 🔧

上一节我们回顾了早期模型,本节中我们将深入探讨多模态学习的两个核心构建模块:特征提取与信息融合。

首先,我们需要理解为什么并非所有任务都采用多模态方法。存在几个挑战:某些模态(尤其是文本)可能主导模型,导致其他模态被忽略;额外的模态可能引入噪声,使问题更复杂;数据可能并非总是多模态的(例如,有些帖子只有文本或只有图片);模型设计如何有效结合不同信息也相当复杂。

特征提取

  • 文本特征化:在Transformer时代,我们通常将文本编码为 [批次大小, 序列长度, 嵌入维度] 的三维张量。
  • 图像特征化:更为复杂。早期常用区域特征,即先使用目标检测器(如YOLO、R-CNN)处理图像,获取边界框和标签,然后用CNN主干网络编码每个子图像的特征。另一种方法是使用密集特征,如Vision Transformer(ViT),将图像分割成块,然后使用标准的Transformer架构进行处理。

多模态融合

假设我们有两个向量 UV,代表不同模态的特征。融合它们的方式多种多样。

以下是几种主要的融合策略:

  • 早期融合:在处理的早期阶段就合并不同模态的特征,例如在注意力机制中同时关注所有模态的信息。
  • 中期融合:先分别处理各模态,然后在中间层进行结合。
  • 晚期融合:将不同模态完全分开处理,只在最后阶段(如得分或逻辑层)进行组合。

融合的具体操作可以是简单的(如内积、拼接、逐元素相乘),也可以是复杂的(如使用注意力机制、双线性池化等)。大部分多模态文献本质上都在探讨如何进行最佳融合。

一个有趣的例子是FiLM模型,它使用一个向量(来自文本编码)来调制CNN每一层的特征图,通过乘性向量γ和加性偏置向量β来实现,从而让视觉网络根据文本信息进行自适应调整。


对比模型与晚期融合 ⚖️

上一节我们介绍了多种融合策略,本节我们重点看看晚期融合,即当前常说的对比模型

其基本思想是:完全独立地处理不同模态,只在最后阶段通过一个相似性评分进行组合。最著名的实例是OpenAI的CLIP模型。

CLIP的核心是对比损失函数,与早期方法中的思想一致,但采用了批内负采样。其架构简单:文本编码器和图像编码器都是Transformer(图像编码器是ViT)。该模型的成功关键在于:1)完全使用Transformer;2)在大量网络数据(约3亿图像-文本对)上训练。

CLIP的强大之处在于其零样本预测能力。由于网络图像描述通常是“一张猫在做某事的照片”这样的句子,而非简单的“猫”标签,因此可以通过提示(如“一张{物体}的照片”)让模型进行零样本图像分类,这类似于大语言模型中的提示工程。

在CLIP之后,谷歌的ALIGN模型采用了相同思路,但使用了更大规模的数据(18亿图像-文本对),获得了更好的性能。开源组织LAION创建了高质量数据集(如包含50亿样本的多语言版本),为Stable Diffusion等模型的成功提供了数据基础。


多模态基础模型发展史 📜

上一节我们讨论了对比模型,本节我们将梳理多模态基础模型的发展脉络。许多思想层层递进,可以看到架构逐渐复杂,但核心通常离不开更多的数据和计算。

BERT的出现是一个转折点。研究者开始思考如何将BERT改造成多模态模型。直观的想法包括:将CNN特征与BERT的[CLS]令牌拼接后进行分类;或者使用区域特征作为输入。

随后涌现了大量论文,基本都在探索如何将BERT与视觉信息结合,主要区别在于融合方式:

  • 单流架构:如Visual BERT,将图像区域特征与文本令牌拼接后,输入同一个Transformer。
  • 双流架构:如ViLBERT,使用两个平行的Transformer,在每一层通过交叉注意力(或共注意力)交换信息。

这些模型通常进行多模态预训练,任务包括掩码语言建模、图像-文本匹配等。也有工作(如MMBT)表明,有时可以冻结BERT,仅学习将图像特征投影到BERT的令牌空间,然后在特定任务上微调,也能得到不错的效果,这避免了繁琐的多模态预训练阶段。

趋势是逐渐摆脱对预提取区域特征的依赖。VT模型首次完全使用图像块(而非区域特征)作为输入,将BERT和ViT真正集成到一个模型中。

然而,一项名为“揭开多模态预训练面纱”的研究指出,当在相同数据、相同方式下训练时,许多模型创新带来的差异微乎其微,性能提升更多源于数据和算力,而非架构本身。

另一个重要趋势是构建统一的基础模型,例如FLAVA模型。它旨在成为一个能同时处理视觉、语言以及视觉-语言任务的单一模型,在图像分类、文本理解和多模态推理等多种任务上表现良好。

当前,生成式模型成为焦点。从对比式、判别式模型转向生成式模型(如生成文本描述或图像)能获得更丰富的表示。CoCa等模型引入了文本解码器进行生成。

在大语言模型时代,一个流行的方法是冻结语言模型,仅学习如何将其他模态的特征投影到语言模型的输入空间。例如,Flamingo模型冻结了Chinchilla语言模型,通过“感知重采样器”处理多幅图像,并使用“门控交叉注意力”将视觉信息注入冻结的语言模型层之前,实现了强大的多模态对话和推理能力。

BLIP-2模型进一步推进了这一思路,仅学习图像编码器到冻结语言模型(如OPT)或编码器-解码器模型(如Flan-T5)之间的投影,就取得了令人印象深刻的效果,展示了语言模型本身的强大能力。

此外,多模态思维链提示也被证明能显著提升模型在复杂推理任务(如科学问答、瑞文矩阵)上的性能。


模型评估 📊

上一节我们回顾了模型的发展,本节我们来看看如何评估多模态系统。构建酷炫的模型很重要,但进行恰当的评估同样关键,尤其是在学术研究中。

评估需要精心设计的数据集。COCO数据集是里程碑式的,它提供了丰富的图像标注(分割、边界框、标签)和每个图像的五条描述,推动了图像描述、跨模态检索等任务的发展。

视觉问答(VQA)是另一个备受关注的任务。但早期版本的VQA数据集存在缺陷,模型仅通过文本问题就能获得高准确率,图像信息变得无关紧要。这警示我们基准设计必须谨慎。后续的GQA等数据集在设计上有所改进。

为了真正衡量多模态理解,需要构建那些必须结合多模态信息才能解决的数据集。例如,Hateful Memes数据集要求模型理解图像和文本之间的互动关系(通常是讽刺或恶意)来进行分类。通过精心构造“良性混淆样本”(即替换图像或文本中的一个模态),确保了任务的多模态必要性。有趣的是,该数据集的竞赛结果表明,当时的许多多模态预训练技术带来的提升有限,说明仍有很长的路要走。

另一个例子是Winoground数据集,它专注于评估模型的组合性理解能力。数据集中包含描述相同词语但顺序不同的文本对(如“植物环绕灯泡” vs “灯泡环绕植物”),对应完全不同的图像。如果模型仅依赖数据分布的偏见,而无法理解视觉-语言的组合语义,则表现不佳。研究显示,当时的先进模型在此类任务上的表现可能低于随机猜测。

即使是DALL-E 2这样的先进生成模型,在生成“勺子少于叉子”或“叉子少于勺子”的图像时,也会因为训练数据中“勺子”图片更多而产生偏差。这再次表明,模型在很大程度上是其训练数据的反映。


其他模态与应用 🌐

上一节我们讨论了评估,本节我们将视野扩展到图像和文本之外的其他模态。视觉是主导模态,但并非唯一。

  • 语音/音频:处理方式与视觉有相似之处。例如,OpenAI的Whisper模型将音频的梅尔频谱图输入Transformer编码器-解码器架构进行转录。有趣的是,音频有时可以“视觉化”处理(如通过频谱图),然后使用CNN提取特征。
  • 视频:可以看作是图像的序列。许多方法通过采样关键帧,然后应用标准的视觉-语言Transformer进行处理。例如,MERLOT模型同时处理视频、音频和文本,向能消费所有模态的统一基础模型迈进。
  • 具身AI与模拟环境:这是一个有趣的方向,智能体在模拟环境中通过交互、感知和语言指令来学习,更接近人类的学习方式。虽然目前因高质量数据获取难而热度稍减,但长期来看潜力巨大。
  • 3D与点云生成:类似于文本到图像生成,现在也可以根据文本提示生成3D模型或点云,在自动设计等领域有应用前景。
  • 嗅觉嵌入:一个探索性的方向。通过分析代表气味的化学化合物组合,可以构建“嗅觉向量”。研究表明,对于具体物体(如“苹果”),基于化学化合物的嗅觉表示与人类相似性判断的相关性,可能高于纯语言向量。这提示,要真正理解人类语义,或许需要考虑更广泛的感知模态。

未来展望与总结 🚀

在本节课中,我们一起学习了多模态深度学习的核心概念、技术演进和评估方法。

总结如下:

  1. 核心思想:多模态学习旨在整合不同数据类型,构建更全面理解世界的模型,其动力源于人类认知的多模态性、互联网数据的多模态性,以及为大型模型提供更多数据的需求。
  2. 关键技术:包括特征提取(如ViT、区域特征)和多模态融合(早期、中期、晚期融合)。对比学习(如CLIP)和基于大语言模型的冻结调优(如Flamingo、BLIP-2)是当前重要范式。
  3. 发展历程:从早期的跨模态对齐、多模态词嵌入,到基于BERT的视觉-语言模型,再到统一的、生成式的多模态基础模型,模型能力不断增强。
  4. 评估挑战:需要精心设计的数据集来确保评估的是真正的多模态理解能力,避免模型利用数据集偏差或单一模态信息即可解决问题。
  5. 模态扩展:研究已从图像-文本扩展到音频、视频、3D乃至嗅觉等领域,最终趋势是构建能处理任意模态组合的统一基础模型。

未来趋势可能包括:

  • 统一的基础模型:一个模型处理所有模态和任务。
  • 缩放定律研究:探索不同模态数据与模型性能之间的定量关系。
  • 检索增强生成(RAG):将检索机制与多模态生成结合。
  • 更好的评估与测量:开发更可靠、更能反映真实理解能力的评估基准。

多模态深度学习是一个快速发展的前沿领域,虽然已取得显著进展,但在组合推理、减少偏见、实现真正稳健的理解等方面仍存在许多开放问题和研究机会。

20:模型可解释性与编辑 🧠

在本节课中,我们将学习模型可解释性与编辑的核心概念。我们将探讨如何理解机器学习模型(尤其是大型语言模型)的内部工作机制,以及如何与这些模型进行有效的“对话”。课程内容将涵盖当前可解释性工具的局限性、知识在模型中的定位与编辑,以及通过观察和干预研究智能体行为的新方法。


今天,我很荣幸地介绍我们最后一位客座演讲者——Been Kim。

Been Kim是Google Brain的一名资深研究科学家。如果你对谷歌的职级体系有所了解,就会知道“资深”这个词意味着她是一位非常优秀的研究科学家。

我在今天的午餐中了解到,Been最初在首尔国立大学学习机械工程,但后来转向了计算机科学,并在麻省理工学院获得了博士学位。在那里,她开始研究机器学习模型的可解释性与可解释性。虽然她今天会谈到她工作的不同部分,但她近期工作的一个主题,尤其吸引我作为NLP研究者的,是“我们应该使用更高级、人类可解释的语言来进行人与机器之间的交流”。欢迎Been,期待你的演讲。

谢谢邀请,很荣幸来到这里。这是我见过最多雨的斯坦福。我昨晚抵达,但我住在西雅图,所以这很常见。不过我今天还是看到了蓝天,我觉得这很好,我很喜欢这里。

今天,我将分享一些我追逐与机器交流的梦想。

如果你在这门课上,你可能会同意(当然不一定)大型语言模型和生成模型非常酷,令人印象深刻。但你可能也同意,它们有点令人不安。不仅因为它们表现优异,还因为我们不太确定这项技术将走向何方。十年后,我们回顾时会说这项技术是净收益,还是会说那是灾难性的?我们不知道会发生什么。

最终,我希望做的,也许也是我们都希望做的,是让这项技术造福人类。我知道在十年后,或者二十年,甚至更早,我的孩子会问我:“妈妈,你研究过这个AI的东西吗?我看过你的一些演讲。你知道这会如何深刻地改变我们的生活吗?你为此做了什么?”我必须回答这个问题,我真的希望我能对他说些好的事情。

所以,我最初的想法,或者说当前的想法是:如果我们最终目标是造福人类,为什么不直接为此优化?为什么要等待?我们如何受益?受益的方式有很多。但一种方式是将它视为一位同事,一位在某些方面非常擅长的同事。这位同事并不完美,但足够擅长某件事,以至于你想向他们学习。不过,一个区别是,这位同事有点奇怪。它可能有非常不同的价值观,对世界有非常不同的体验,可能不像我们那样关心生存,也许死亡对它来说不是个事。所以,在我们的对话中,你必须处理好这一点。

当你第一次遇到一个如此不同的人时,你会怎么做?你会尝试进行对话,去弄清楚:你是如何做到的?你如何解决存在数十年的蛋白质折叠问题?你如何击败世界围棋冠军?看起来如此轻松。你使用的是和我们一样的科学知识语言(原子、分子),还是以完全不同的方式思考世界?更重要的是,我们如何合作?

我有一个非常想与之对话的“外星人”,那就是AlphaGo。AlphaGo在2016年击败了世界围棋冠军李世石。李世石来自韩国,我也来自韩国。我观看了每一场比赛。他在韩国乃至全世界都是个大人物。在其中一场比赛中,AlphaGo下出了被称为“第37手”的一步棋。有多少人看过AlphaGo的比赛?有多少人记得第37手?是的,有几个人记得。我记得当时的评论员,在整个比赛期间滔滔不绝,突然变得非常安静。他说:“嗯……这步棋非常奇怪。”我当时就知道,我眼前发生了一些非常有趣的事情,AlphaGo做出了我们将永远铭记的事情。果然,这步棋扭转了AlphaGo的比赛局势,并最终使其赢得其中一场比赛。直到今天,围棋棋手们仍在分析这步棋并讨论它,人们谈论说这不是人类会幻想出来的棋步。问题是:AlphaGo是如何知道这是一步好棋的?

我的梦想是通过与机器交流、对话来学习新事物,从而使人类能够获得解决重要问题(如医学和科学等)的新视角。这不仅仅是发现新事物。想想奖励黑客问题,你必须与某人进行有意义的对话,才能真正弄清楚他们的真实目标是什么。所以,在某种程度上,解决这个问题是解决AI安全问题的超集。

那么,我们如何进行这种对话呢?对话假设我们在交流中共享一些共同的词汇来交换意义,并最终交换知识。自然地,表征在这种对话中扮演着关键角色。我们可以在左边将其可视化,左边圆圈代表人类所知事物的表征空间,右边圆圈代表机器所知事物的表征空间。左边的圆圈里会有像“这只狗毛茸茸的”这样的东西,你知道那是什么意思,因为我们都有大致相似的词汇。但在右边,我们有像“第37手”这样的东西,我们人类还没有对应的表征。我们如何进行对话?我们的表征空间需要重叠,重叠越多,对话就会越好。人类都擅长学习新事物,就像这里的每个人都在学习新东西一样,所以我们可以通过学习新概念和词汇来扩展我们的所知。我相信,这样做将帮助我们构建能更好地与我们的价值观和目标对齐的机器。

这是我之前做的一个演讲。如果你对我们为实现这个目标所做的一些工作感到好奇,我强烈推荐这个YouTube视频,它是一个半小时的主题演讲,你可以快进观看。

但今天,我将更多地谈谈我的希望和梦想。希望在今天结束时,你的希望和梦想也在那里。首先,我要设定一下期望。在这次演讲结束时,我们仍然不知道第37手是如何产生的。抱歉,这需要一些时间。事实上,这次演讲的第一部分将讲述我们如何在这个进程中倒退。

在取得进展方面,我仍然只是我们理解第37手整个旅程中非常小的一部分。当然,这个旅程不会是单一的路径,会有许多不同的分支出现,就像Transformer这样的核心思想帮助了许多领域一样,这里也会类似。所以,在第二部分,我将谈谈我们在理解强化学习中涌现行为方面的一些工作。我将要讨论的所有技术原则上都适用于NLP。

回到我们的希望和梦想,第37手。首先,让我们思考一下我们如何实现这个梦想。退一步想,我们必须问:我们是否有工具来首先评估机器到底知道什么?过去十年,机器学习领域有许多发展,旨在开发工具来理解和评估这个紫色圆圈(机器所知)。那么,这准确吗?不幸的是,许多最近的研究表明,机器实际知道的和我们认为机器知道的之间存在巨大差距。识别并弥合这个差距很重要,因为这些工具将成为理解第37手的基础。

这些工具是什么?有多少人熟悉显著图?很多人,但你不必解释它是什么。显著图是流行的可解释性方法之一。简单来说,在ImageNet上,你有一张像这样的图片,一只鸟。解释将采用相同图片的形式,但每个像素都关联一个数字,这个数字应该暗示该像素对预测这张图片的重要性。其中一个重要性的定义是,该数字表示该像素周围函数的样子。例如,如果我增加像素Xj,也许在Xj附近,函数像黄色曲线那样上升,或者函数是平坦的,或者像绿色曲线那样下降。所以,如果它像蓝色或红色曲线那样平坦,也许这个特征与预测鸟无关;如果它上升,那么它可能更重要,因为x值增加,函数值上升(这里的函数值如预测值)。

让我们思考一下为什么这个差距可能存在的一些原因。有几种方式(并非详尽无遗,它们有些重叠),但有助于我们思考:也许假设是错误的。这个“外星人”,我们训练的机器,在一个完全不同的、也许是完全不同的表征空间中工作,对世界有非常不同的体验。所以,假设它像我们一样看待世界,就像拥有联觉现象一样,人类倾向于将它们联系起来。也许机器也有,也许没有。所以,也许我们对这些机器的假设是错误的。也许我们的期望不匹配。我们以为它在做X,但实际上它在做Y。也许它超出了我们的理解,也许它展示的是人类无法理解的超人类能力。

我将更深入地探讨我们的一些工作,这是更近期的工作。再次回到之前关于显著图的故事,我们将尝试使用其中一些方法。

在2018年,我们偶然发现了一个相当令人震惊的现象。当时我们实际上在尝试写一篇不同的论文,当然,Anzi也在这里。但我们正在测试一些东西,然后我们意识到,训练过的网络和未训练的网络具有非常相似的显著图。换句话说,随机预测和有意义的预测给了我相同的解释。这令人困惑。我们以为有bug,但结果发现没有。实际上,它们在定性和定量上都是无法区分的。这很令人震惊。

但随后我们想,也许这只是个别情况,也许它在实践中仍然以某种方式有效。所以我们在后续论文中测试了这一点:好吧,如果模型有错误,比如标签错误、虚假相关性,或者在测试时出现分布外数据,如果我们故意插入这些错误,解释能否告诉我们模型有问题?结果发现,这也不太正确。你可能会想,哦,也许是虚假相关性。另一项后续工作也表明情况并非如此。我们很失望。

但我们仍然说,也许没有理论证明,也许这又是一个实验室环境下的测试。我们让研究生测试了这个系统,也许还有一些希望。这是更近期的工作,我们在其中从理论上证明,其中一些非常流行的方法并不比随机猜测更好。我将稍微谈谈这个。

我漏掉了一个人,我漏掉了作者列表中的Pangwei。这也是与Pangwei合作的工作。

首先,让我们谈谈我们对这个工具的期望。开发这种方法(IG和SHAP)的原始论文讨论了IG如何用于计算每个特征的贡献。这意味着,当工具将零归因分配给像素X时,我们会说,好吧,像素X未被函数使用。这意味着,如果我扰动这个X,f将不敏感。事实上,这就是它在实践中的使用方式。这是一篇发表在《自然》杂志上的论文,它使用SHAP来确定医学试验中的资格标准。

我们在这项工作中表明,这些看似非常自然的推论都不成立。事实上,仅仅因为流行的归因方法告诉你归因是X,你无法得出关于实际模型行为的任何结论。这是如何运作的?

这里有多少人做理论证明?有几个,很好。我会告诉你,我也是从这个项目中学到理论证明的。我告诉你我们进行这项工作的方式是,首先思考这个问题,然后将其表述为我们知道如何解决的其他问题。在这种情况下,我们将其表述为假设检验,因为一旦你将其表述为假设检验(是或否),你就可以使用许多统计工具来证明这一点。

假设是什么?假设是:我是一个用户。我从其中一个工具得到了一个归因值,并且我有一个心理模型,认为这个特征是重要的,或者可能不重要。那么假设就是,这是真的还是假的。我们表明,无论你有什么假设,你都无法比随机猜测更好地证实或否定这个假设检验。这意味着,是的,有时它是正确的,但如果你无法验证是或否,你就不会做假设检验,因为如果它和随机猜测一样好,那做它有什么意义呢?结果是,是的,对于这个图,它只是我们结果的可视化。如果你绘制真阴性和真阳性,这条线是随机猜测线(因为这是最差的方法,这是最好的方法),所有等距离的点都在这条线上。我们知道的方法,如SHAP、IG,都落在这条随机猜测线以下。这是个坏消息。

但也许,也许由于某些原因,这在实践中仍然有效。也许我们有一些假设在实践中没有完全满足。那么,这种现象在实践中是否成立?答案是肯定的,我们做了测试。我们现在有更多的图像图和更大的模型,但在这里我们测试了两个具体的最终任务,这些任务在可解释性中很重要,或者人们使用这些方法来进行补救或检测虚假相关性。补救(对于那些不熟悉的人来说)是指,你被拒绝了贷款,你想知道如果我年龄更大,是否会有更高的机会获得贷款。所以我调整这个特征,看看我的值是否上升。这是一个非常合理的任务,人们一直在做,具有重要的社会意义。

对于这两个具体的最终任务,它们都归结为我刚才谈到的假设检验框架。它们都围绕随机猜测线,或者比随机猜测更差。你可能会说,哦,不。这不好。很多人都在使用这些工具,我们该怎么办?

我们对此有一个非常简单的想法。人们喜欢开发复杂的工具。我真的希望你不是那种人。因为很多时候,简单的方法有效。奥卡姆剃刀原理,而且简单的方法很优雅。也许很多时候它们有效是有原因的。它们简单,你能理解它们,它们有意义。所以让我们在这里试试这个想法。再次,你的目标是估计函数形状。你做什么?嗯,最简单的事情是,你有一个兴趣点,你在该点周围采样,并在该点周围评估函数。如果它上升,也许函数在上升;如果它下降,也许函数在下降,对吧?这是你可以蛮力解决的最简单方法。但问题是,我们需要多少样本?这里,这个方程是你在提升这条线,通过添加那个额外的项。它与样本数量成正比,你拥有的样本越多,你的估计就越好,这很合理。还有输出差异,你关心多少分辨率?你关心0.0001到0.1,还是只关心斜率0到斜率1?但这是你关心的分辨率,当然还有特征数量。所以,如果你担心基于函数形状做出某些结论,三步走。简单。

那么,我们能否使用这些流行的方法推断模型行为?答案是否定的。这在理论和实践中都成立。我们目前正在研究更大的模型,以一次又一次地展示经验证据,表明是的,它真的不起作用。请在使用这些方法之前三思而后行。此外,模型依赖的样本复杂度。如果你的函数有点疯狂,当然,你需要更多的样本。那么,我们如何描述这些函数的定义是什么?最后,我们还没有完全放弃,因为这些方法在经济学和沙普利值等方面有很好的基础。所以,也许存在更狭窄的条件,这些方法在这些条件下有效。我们相信这样的条件确实存在。我们只需要弄清楚是什么时候。

一旦我们弄清楚那个条件是什么,那么给定一个函数,我可以测试它并说,是的,我可以在这里使用SHAP;是的,我可以在这里使用IG;或者不,我不能。那仍然会非常有用。这是正在进行的工作。

在我进入下一个话题之前,有什么问题吗?是的。你的这些发现,它们只适用于计算机视觉,还是也适用于NLP?任何具有函数的模型。实际上,简单的证明可以显示它适用于任何函数。还有其他问题吗?很好。

Chris,这最后一个问题与你刚才提到的要点有关。在过去的几年里,有几十个,也许几百个人写论文使用沙普利值。你的猜测是,这些工作大部分都无效,还是其中很多可能没问题?无论什么条件下它可能是可以的。对于这个问题有两个答案。我的假设检验结果表明它是随机的,对吧?所以在乐观的情况下,也许这些论文中有50%碰巧对了。另一方面,即使SHAP不完美,也许它有点错误,但即使如此,如果它最终帮助了人类,无论那是什么,帮助医生更高效,识别错误等等,并且如果他们用正确的控制测试设置进行了正确的验证,那么我认为这是好的。你知道,你以某种方式弄清楚了如何让这些嘈杂的工具与人在回路中协同工作。这也很好。我个人真的很喜欢SHAP论文,我和Scott是好朋友,我喜欢他所有的工作。只是我认为我们需要缩小我们的期望,以便我们的期望能更好地对齐。

好的。我将谈谈另一项具有类似风格的工作。现在是NLP领域。

这是众多论文中的一篇,就像我们最终写的许多其他论文一样,是一次偶然的发现。最初,Peter作为实习生加入,我们认为我们要在大型语言模型中定位伦理知识,然后也许编辑它们,让它们更符合伦理。我们开始吧。然后我们想,哦,有David Bau等人的论文,我也很喜欢David的工作,让我们用那个。这就是这项工作的开始。

但随后我们开始深入研究并实现ROME。事情不太对劲。所以我们做了一个又一个的健全性检查实验,最终写了一篇完全不同的论文,我即将要谈到的这篇。

这篇论文,ROME,对于不熟悉的人来说(我稍后会详细说明),是关于编辑模型的。所以你首先在模型中定位知识,比如“太空针塔在西雅图”。这是一个事实,你的知识。你定位它们。因为你能够定位,所以你可以修改它来编辑那个事实。这就像是它的全部承诺。事实上,文献中的许多定位或编辑方法都是这样被激励的。

但我们表明,这个假设实际上并不成立。老实说,我仍然不太明白为什么这不相关。我会更多地谈谈这个,因为这对我们来说是一个大问题。这是相当活跃的工作。相当大比例的事实性知识存储在那些被识别为拥有知识的层之外。你可以,你可以看到,你稍后会看到更多细节。事实上,事实所在的位置与你编辑该位置的效果之间的相关性是完全不相关的,所以它们彼此毫无关系。

所以我们想,好吧,也许是编辑定义的问题。编辑可以有很多不同的含义。所以让我们想想编辑事物的不同方式。我们尝试了很多方法,但收效甚微。我们找不到一个编辑定义能真正与定位方法(特别是ROME)很好地关联起来。

让我们简要谈谈ROME的工作原理。这张幻灯片省略了很多细节,但大致上你会明白。ROME是Meng等人2022年的工作,他们有一个所谓的因果追踪算法。它的工作方式是,你将在特定的反事实数据集上运行模型,该数据集包含主语-关系-宾语三元组,比如“太空针塔位于西雅图”。所以你会进行一次“干净”运行,在“太空针塔位于西雅图”上一次运行。你存储每个模块、每个值激活。然后在第二次运行中,他们称之为“损坏”运行,你将在“太空针塔”或“太空”处添加噪声。然后,你将在每个模块进行干预,就像通过将该模块复制到损坏运行中一样,就好像那个特定模块从未被干扰过,从未被添加噪声。这是一个典型的干预案例,你假设其他条件不变,只改变这一个模块。得到正确答案的概率是多少?在这个案例中,是“西雅图”的概率,给定我知道模型并且我在其上进行了干预。最终,你会得到像这样的图,其中每个层和每个标记都有一个分数,表示如果我在该层的该标记上进行干预,我恢复正确答案的可能性有多大?因为如果我恢复了正确答案,那就是存储知识的模块。非常合理的算法,我找不到这个算法中的技术缺陷。我其实挺喜欢它的。

但是,当我们开始使用他们使用的相同模型(GPT-J)查看这个时,我们意识到很多这些事实……ROME只使用第6层进行编辑,因为据称这是整个数据集中编辑大多数事实性知识的最佳层,他们展示了编辑成功等等。但我们意识到真相看起来像右边的图。红线是第6层,他们的扩展论文(称为MEMIT)编辑了多层,那是蓝色区域。黑色条是知识实际达到峰值的层的直方图(如果你测试每一层)。正如你所看到的,没有很多事实落入那个区域。事实上,每个事实都有不同的峰值区域。所以对于很多事实来说,第6层并不是最佳层。但编辑确实有效。它真的有效,我们能够复制那个结果。所以我们想,我们该怎么做才能找到这些伦理知识?我们如何找到编辑的最佳层?这就是我们开始的地方。但随后我们想,你知道吗,退一步。我们实际上要先做一个健全性检查,以确保追踪效应(即定位)意味着更好的编辑结果。就在这时,一切都开始崩溃了。

首先让我们定义一些指标。编辑成功,这是重写分数,与ROME论文使用的分数相同。我们使用这个。追踪效应,这是定位。是概率,你可以在幻灯片上看到。

当我们绘制追踪效应和重写分数(编辑方法)之间的关系时,红线意味着完美的相关性。这是我们的假设,认为它们会完美相关,这也是我们进行定位的原因。实际的线是黄色的。它接近0,实际上是负的。在这个特定的数据集中。这甚至不是不相关,而是负相关。我们没有就此止步。我们非常困惑。我们将对每一层都这样做,并找出R

21:Python 教程 🐍

在本教程中,我们将学习 Python 编程语言的基础知识,特别是与 NumPy 库相关的部分。这些知识对于完成本课程的作业至关重要。我们将从 Python 的基本概念开始,逐步深入到 NumPy 的核心功能,包括数组操作和高效的数学计算。


为什么选择 Python?🤔

上一节我们介绍了本教程的目标,本节中我们来看看为什么 Python 是机器学习和自然语言处理领域的首选语言。

Python 是一种高级语言,语法接近英语,易于学习和使用。它拥有丰富的科学计算库,类似于 MATLAB。在深度学习中,许多主流框架(如 PyTorch 和 TensorFlow)都直接提供 Python 接口。因此,Python 因其易用性和强大的生态系统而被广泛采用。


Python 语言基础 🏗️

本节将介绍 Python 的基本语法和概念,为后续学习 NumPy 打下基础。

变量与赋值

在 Python 中,变量可以存储各种类型的值。赋值操作使用等号 =。Python 是动态类型语言,无需预先声明变量类型,并且可以随时重新赋值。

x = 10
x = "hello"  # 这是有效的

基本运算

Python 支持常见的数学运算。

x = 10
y = 3
print(x + y)   # 加法
print(x / y)   # 除法
print(x ** y)  # 幂运算,x 的 y 次方

类型转换

有时需要显式转换数据类型。

x = 10
y = 3
result = float(x) / float(y)  # 确保结果为浮点数
print(int(result))            # 转换为整数
print(str(x) + str(y))        # 转换为字符串并拼接

布尔值与逻辑运算

布尔值 TrueFalse 首字母必须大写。Python 使用 andornot 进行逻辑运算,使用 ==!= 进行相等性比较。

x = 3
y = 4
if x == 3 and y == 4:
    print(True)

代码块与缩进

Python 使用缩进(空格或制表符)来定义代码块,例如在函数或循环中。通常使用 2 或 4 个空格,但必须在整个代码中保持一致。

if x > 0:
    print("x is positive")  # 缩进表示属于 if 语句的代码块

常见数据结构 📚

了解了基础语法后,我们来看看 Python 中几种核心的数据结构。

列表

列表是可变的有序集合,可以包含不同类型的元素。它们非常适合需要频繁增删元素的场景。

以下是列表的基本操作:

  • 创建与索引:列表使用方括号 [] 创建,索引从 0 开始。
    names = ["Zach", "Jay"]
    print(names[0])  # 输出: Zach
    
  • 添加元素:使用 append 方法在列表末尾添加元素。
    names.append("Richard")
    
  • 列表拼接:使用 ++= 运算符可以连接列表。
    names += ["Abi", "Kevin"]
    
  • 列表切片:使用切片语法 [start:end] 可以获取列表的一部分。start 包含,end 不包含。
    numbers = [0, 1, 2, 3, 4, 5, 6]
    print(numbers[0:3])  # 输出: [0, 1, 2]
    print(numbers[:3])   # 同上,start 默认为 0
    print(numbers[3:])   # 输出: [3, 4, 5, 6]
    print(numbers[:])    # 复制整个列表
    
  • 负索引:使用负数可以从列表末尾开始索引。
    print(numbers[-1])   # 输出最后一个元素: 6
    print(numbers[-3:])  # 输出最后三个元素: [4, 5, 6]
    

元组

元组是不可变的有序集合。一旦创建,其元素不能被修改。元组使用圆括号 () 创建。

names = ("Zach", "Jay")
print(names[0])  # 输出: Zach
# names[0] = "NewName"  # 这行会报错,因为元组不可变

创建单个元素的元组时,需要在元素后加一个逗号。

single_tuple = (5,)

字典

字典是一种键值对映射,在其他语言中常被称为哈希表。它非常适合快速查找和映射关系。

以下是字典的基本操作:

  • 创建与访问:字典使用花括号 {} 创建,通过键来访问值。
    phonebook = {"Zach": "123-4567", "Jay": "987-6543"}
    print(phonebook["Zach"])  # 输出: 123-4567
    
  • 检查键是否存在:使用 in 关键字。
    print("Zach" in phonebook)  # 输出: True
    print("Monsie" in phonebook) # 输出: False
    
  • 删除条目:使用 del 语句。
    del phonebook["Jay"]
    

循环 🔄

循环是遍历列表、元组、字典等集合的高效方式。Python 提供了简洁的循环语法。

for 循环

遍历数字序列可以使用 range 函数。

for i in range(5):
    print(i)  # 输出 0, 1, 2, 3, 4

直接遍历列表元素更为简单。

names = ["Zach", "Jay", "Richard"]
for name in names:
    print(name)

如果需要同时获取元素及其索引,可以使用 enumerate 函数。

for index, name in enumerate(names):
    print(f"Index {index}: {name}")

遍历字典

遍历字典可以分别访问其键、值或键值对。

phonebook = {"Zach": "123", "Jay": "456"}
for key in phonebook:           # 遍历键
    print(key)
for value in phonebook.values(): # 遍历值
    print(value)
for key, value in phonebook.items(): # 遍历键值对
    print(key, value)

NumPy 简介 🧮

在掌握了 Python 基础后,我们进入本教程的核心部分:NumPy。NumPy 是 Python 中用于高效科学计算的基础库,尤其在处理多维数组和矩阵运算时表现出色。

NumPy 的核心是 ndarray(N维数组)对象。与 Python 原生列表不同,NumPy 数组在内存中连续存储,并且许多底层操作由优化过的 C 代码执行,这使得数值计算速度极快。在深度学习中,我们经常需要处理大量的数值数据(如词向量、权重矩阵),NumPy 为此提供了强大的支持。

数组创建与形状

首先,我们看看如何创建 NumPy 数组并理解其形状。

import numpy as np

# 创建一维数组(向量)
x = np.array([1, 2, 3])
print(x.shape)  # 输出: (3,)

# 创建二维数组(矩阵)
z = np.array([[6, 7], [8, 9]])
print(z.shape)  # 输出: (2, 2)

# 使用 reshape 改变数组形状
a = np.arange(10)  # 创建 0 到 9 的数组
b = a.reshape((5, 2)) # 重塑为 5 行 2 列
print(b.shape)  # 输出: (5, 2)

形状 (3,) 表示一个包含 3 个元素的一维数组。形状 (2, 2) 表示一个 2 行 2 列的二维数组。reshape 操作必须保证新形状的元素总数与原数组一致。

数组操作

NumPy 提供了丰富的数组操作函数。

  • 聚合函数:如 max, min, sum。可以通过 axis 参数指定沿哪个维度进行计算。
    x = np.array([[1, 2], [3, 4], [5, 6]])
    print(np.max(x))           # 整个数组的最大值: 6
    print(np.max(x, axis=0))   # 沿行(垂直方向)求最大值: [5, 6]
    print(np.max(x, axis=1))   # 沿列(水平方向)求最大值: [2, 4, 6]
    
    axis=0 表示沿着行的方向(向下),跨行比较。axis=1 表示沿着列的方向(向右),跨列比较。
  • 元素级运算:使用 */+- 等运算符会对数组中的对应元素进行运算。
    a = np.array([1, 2, 3])
    b = np.array([4, 5, 6])
    print(a * b)  # 输出: [4, 10, 18]
    
  • 矩阵乘法:使用 np.dot@ 运算符或 np.matmul 函数。
    A = np.array([[1, 2], [3, 4]])
    B = np.array([[5, 6], [7, 8]])
    print(np.dot(A, B))
    # 等价于 print(A @ B)
    # 输出: [[19 22]
    #        [43 50]]
    
    矩阵乘法要求第一个数组的列数等于第二个数组的行数。
  • 点积:对于一维数组,np.dot 计算的是向量点积(对应元素相乘后求和)。
    v1 = np.array([1, 2, 3])
    v2 = np.array([4, 5, 6])
    print(np.dot(v1, v2))  # 输出: 32 (1*4 + 2*5 + 3*6)
    

数组索引与切片

NumPy 数组的索引和切片方式与列表类似,但功能更强大。

x = np.array([[1, 2, 3, 4],
              [5, 6, 7, 8],
              [9, 10, 11, 12]])

# 选择特定的行
rows = x[[0, 2], :]  # 选择第 0 行和第 2 行,所有列
print(rows)

# 布尔索引
print(x[x > 5])  # 输出所有大于 5 的元素

# 使用 np.newaxis 增加维度
v = np.array([1, 2, 3])
v_col = v[:, np.newaxis]  # 形状从 (3,) 变为 (3, 1),列向量
print(v_col.shape)

广播机制 📡

广播是 NumPy 最强大且独特的特性之一。它允许不同形状的数组进行数学运算,而无需显式复制数据,这大大提升了代码的简洁性和执行效率。

广播的核心规则是:两个数组的形状从后向前逐元素比较。如果两个维度相等,或者其中一个为 1,则它们是“兼容”的。在运算时,形状为 1 的维度会被“拉伸”以匹配另一个数组的对应维度。

让我们看几个例子:

import numpy as np

![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/stf-cs224n-nlp-2025/img/b9c22da3b4fcb6ec6ac2c97bfa96f948_5.png)

# 示例 1: 矩阵的每一行加上一个行向量
A = np.zeros((3, 4))       # 形状 (3, 4)
row_vector = np.array([1, 2, 3, 4]) # 形状 (4,)
# row_vector 被广播为形状 (3, 4),每一行都是 [1,2,3,4]
result = A + row_vector
print(result)
# 输出:
# [[1. 2. 3. 4.]
#  [1. 2. 3. 4.]
#  [1. 2. 3. 4.]]

# 示例 2: 矩阵的每一列加上一个列向量
A = np.zeros((3, 4))          # 形状 (3, 4)
col_vector = np.array([[1], [2], [3]]) # 形状 (3, 1)
# col_vector 被广播为形状 (3, 4),每一列都是 [1,2,3]^T
result = A + col_vector
print(result)
# 输出:
# [[1. 1. 1. 1.]
#  [2. 2. 2. 2.]
#  [3. 3. 3. 3.]]

# 示例 3: 两个向量相加,生成一个矩阵
a = np.array([1, 2, 3])    # 形状 (3,)
b = np.array([[4], [5], [6]]) # 形状 (3, 1)
# a 被广播为 (3, 3),每行都是 [1,2,3]
# b 被广播为 (3, 3),每列都是 [4,5,6]^T
result = a + b
print(result)
# 输出:
# [[5 6 7]
#  [6 7 8]
#  [7 8 9]]

重要提示:如果两个数组在某个维度上既不相等,也不等于 1,则广播失败。例如,形状 (6,)(3,) 的数组不能直接广播。


高效编程技巧 ⚡

在数据科学和深度学习中,效率至关重要。以下是一些利用 NumPy 进行高效编程的原则:

  1. 避免显式循环:对大型数组使用 Python 的 forwhile 循环会非常慢。应尽量使用 NumPy 的向量化操作和广播机制。
    # 低效做法
    x = np.random.rand(1000, 1000)
    for i in range(1000, 2000):
        for j in range(1000):
            x[i, j] += 5
    
    # 高效做法
    x[1000:2000, :] += 5
    
  2. 利用切片和索引:直接使用数组切片进行批量操作,而不是遍历每个元素。
  3. 理解并应用广播:广播能自动处理许多需要复制数据的场景,写出更简洁、更快的代码。

总结 📝

本节课中我们一起学习了 Python 和 NumPy 的核心知识,为后续的深度学习与自然语言处理任务打下基础。

我们首先了解了 Python 因其简洁性和强大的科学计算库而被选为 AI 领域的主流语言。接着,我们学习了 Python 的基本语法、变量、数据结构(列表、元组、字典)以及循环控制。

然后,我们深入探讨了 NumPy 库,它是高效数值计算的基石。我们学会了如何创建和操作多维数组,理解了数组的形状、索引和切片。我们重点掌握了矩阵运算和强大的广播机制,后者允许我们对不同形状的数组进行智能的数学运算。

最后,我们强调了向量化编程的重要性,即避免使用低效的显式循环,转而利用 NumPy 的内置函数和广播来提升代码性能。

掌握这些内容将帮助你更顺利地完成课程作业,并在未来的学习和项目中有效地处理数据和实现算法。

22:PyTorch 入门教程 🚀

在本节课中,我们将要学习 PyTorch 的基础知识。PyTorch 是一个深度学习框架,它主要做两件事:一是让张量的创建和操作变得非常容易,并能利用 GPU 进行计算;二是让构建神经网络的过程变得更加简单。我们将从张量开始,了解它们与 NumPy 数组的相似之处,然后学习自动微分(autograd),最后探讨如何构建和训练神经网络模型。


1. 张量基础 📦

上一节我们介绍了课程概述,本节中我们来看看 PyTorch 的核心数据结构——张量。

张量可以看作是 PyTorch 中与 NumPy 数组等价的多维数组。它们用于表示数据,并执行神经网络所需的各种矩阵运算。例如,一张图像可以表示为一个 256x256 的张量(宽x高)。如果有一批包含红、绿、蓝三个通道的图像,那么张量将是四维的:[批次大小, 通道数, 宽度, 高度]

创建张量

我们可以从 Python 列表轻松创建张量。

import torch

![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/stf-cs224n-nlp-2025/img/9b7251760da30367c6f48f4fed9d956e_20.png)

![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/stf-cs224n-nlp-2025/img/9b7251760da30367c6f48f4fed9d956e_22.png)

# 从一个列表创建张量
my_list = [[1, 2, 3], [4, 5, 6]]
my_tensor = torch.tensor(my_list)
print(my_tensor)

指定数据类型

张量包含数据类型,我们可以显式指定。

# 指定数据类型为 float32
my_tensor_float = torch.tensor(my_list, dtype=torch.float32)
print(my_tensor_float)


2. 张量操作 🛠️

现在我们已经知道如何创建张量,接下来看看有哪些操作可以应用于它们。

实用创建函数

以下是快速初始化张量的方法。

# 创建全零张量
zeros_tensor = torch.zeros((2, 3))
print(zeros_tensor)

![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/stf-cs224n-nlp-2025/img/9b7251760da30367c6f48f4fed9d956e_43.png)

# 创建全一张量
ones_tensor = torch.ones((2, 3))
print(ones_tensor)

# 创建范围张量
range_tensor = torch.arange(1, 11)
print(range_tensor)

元素级运算

默认情况下,加法、乘法等 Python 运算是元素级的。

tensor = torch.tensor([[1, 2], [3, 4]])
result_add = tensor + 2
result_mul = tensor * 2
print(result_add)
print(result_mul)

矩阵乘法

我们可以使用 torch.matmul@ 运算符进行矩阵乘法。

A = torch.randn(3, 2)
B = torch.randn(2, 4)
C = torch.matmul(A, B)  # 或 C = A @ B
print(C.shape)  # 输出: torch.Size([3, 4])


3. 张量形状与索引 🔍

理解张量的形状和如何进行索引切片至关重要,尤其是在调试时。

查看与重塑形状

# 查看形状
tensor_3d = torch.randn(3, 2, 4)
print(tensor_3d.shape)  # 输出: torch.Size([3, 2, 4])

# 重塑形状
flat_tensor = torch.arange(1, 16)
reshaped_tensor = flat_tensor.view(5, 3)  # 或 .reshape(5, 3)
print(reshaped_tensor)

索引与切片

索引规则与 NumPy 类似。

x = torch.tensor([[[1, 2], [3, 4]], [[5, 6], [7, 8]], [[9, 10], [11, 12]]])
print(x.shape)  # torch.Size([3, 2, 2])

![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/stf-cs224n-nlp-2025/img/9b7251760da30367c6f48f4fed9d956e_65.png)

# 索引第一个维度
print(x[0])      # 形状: (2, 2)
# 切片
print(x[0:2])    # 形状: (2, 2, 2)
# 列表索引
print(x[[0, 2]]) # 形状: (2, 2, 2)

提取标量值

当需要将单元素张量转换为 Python 标量时,使用 .item()

scalar_tensor = torch.tensor([5.0])
value = scalar_tensor.item()
print(value)  # 输出: 5.0


4. 自动微分(Autograd) 🤖

PyTorch 最强大的特性之一是自动微分。它自动计算并存储梯度,应用链式法则,使我们无需手动实现反向传播。

基本使用

要计算梯度,需要将张量的 requires_grad 属性设置为 True

# 创建一个需要梯度的张量
x = torch.tensor(2.0, requires_grad=True)

![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/stf-cs224n-nlp-2025/img/9b7251760da30367c6f48f4fed9d956e_106.png)

# 定义一个函数
y = 3 * x ** 2

![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/stf-cs224n-nlp-2025/img/9b7251760da30367c6f48f4fed9d956e_108.png)

![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/stf-cs224n-nlp-2025/img/9b7251760da30367c6f48f4fed9d956e_109.png)

![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/stf-cs224n-nlp-2025/img/9b7251760da30367c6f48f4fed9d956e_111.png)

![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/stf-cs224n-nlp-2025/img/9b7251760da30367c6f48f4fed9d956e_113.png)

![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/stf-cs224n-nlp-2025/img/9b7251760da30367c6f48f4fed9d956e_114.png)

# 计算梯度
y.backward()

# 查看 x 的梯度
print(x.grad)  # 输出: tensor(12.) 因为 dy/dx = 6x, 6*2=12

梯度累积

默认情况下,梯度是累积的。在训练循环中,每次迭代前需要手动清零。

# 再次计算梯度(梯度会累积)
z = 3 * x ** 2
z.backward()
print(x.grad)  # 输出: tensor(24.) 因为 12 + 12 = 24

# 清零梯度
x.grad.zero_()


5. 构建神经网络 🧠

现在,我们将利用 PyTorch 的 nn.Module 来构建神经网络。

线性层

线性层执行 y = xA^T + b 操作。

import torch.nn as nn

# 定义线性层:输入维度4,输出维度2
linear_layer = nn.Linear(4, 2)

# 创建输入数据 (batch_size=2, 任意维度..., 特征维度=4)
input_data = torch.randn(2, 3, 4)
output = linear_layer(input_data)
print(output.shape)  # 输出: torch.Size([2, 3, 2])

激活函数与顺序模型

我们可以使用 nn.Sequential 将多个层组合在一起。

# 定义一个简单的网络:线性层 -> Sigmoid激活
model = nn.Sequential(
    nn.Linear(10, 5),
    nn.Sigmoid(),
    nn.Linear(5, 1)
)

input_vec = torch.randn(3, 10)
output = model(input_vec)
print(output.shape)

自定义网络模块

通过继承 nn.Module 来定义更复杂的网络。

class MultilayerPerceptron(nn.Module):
    def __init__(self, input_size, hidden_size):
        super().__init__()
        self.network = nn.Sequential(
            nn.Linear(input_size, hidden_size),
            nn.Sigmoid(),
            nn.Linear(hidden_size, 1),
            nn.Sigmoid()
        )

    def forward(self, x):
        return self.network(x)

# 实例化并使用模型
model = MultilayerPerceptron(10, 5)
sample_input = torch.randn(4, 10)
prediction = model(sample_input)
print(prediction.shape)

6. 训练循环:优化与损失 🏃

构建好网络后,我们需要定义损失函数和优化器来训练它。

定义优化器与损失函数

import torch.optim as optim

![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/stf-cs224n-nlp-2025/img/9b7251760da30367c6f48f4fed9d956e_136.png)

![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/stf-cs224n-nlp-2025/img/9b7251760da30367c6f48f4fed9d956e_138.png)

model = MultilayerPerceptron(10, 5)
optimizer = optim.Adam(model.parameters(), lr=0.01)
criterion = nn.CrossEntropyLoss()  # 对于分类任务
# 或 criterion = nn.MSELoss() 对于回归任务

完整的训练循环

以下是训练神经网络的标准步骤。

# 假设我们有训练数据 X_train 和标签 y_train
X_train = torch.randn(100, 10)
y_train = torch.randint(0, 2, (100,)).long()  # 二分类标签

![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/stf-cs224n-nlp-2025/img/9b7251760da30367c6f48f4fed9d956e_142.png)

num_epochs = 10

![](https://github.com/OpenDocCN/dsai-notes-pt2-zh/raw/master/docs/stf-cs224n-nlp-2025/img/9b7251760da30367c6f48f4fed9d956e_144.png)

for epoch in range(num_epochs):
    # 1. 清零梯度
    optimizer.zero_grad()

    # 2. 前向传播
    predictions = model(X_train)

    # 3. 计算损失
    loss = criterion(predictions, y_train)

    # 4. 反向传播
    loss.backward()

    # 5. 更新参数
    optimizer.step()

    # 打印损失
    print(f'Epoch {epoch+1}, Loss: {loss.item():.4f}')

总结 📝

本节课中我们一起学习了 PyTorch 的核心概念。我们从张量开始,了解了它的创建、操作和索引。然后,我们探索了自动微分(autograd),这是 PyTorch 实现反向传播的引擎。接着,我们学习了如何使用 nn.Module 和预定义层来构建神经网络。最后,我们整合了优化器损失函数,编写了一个完整的训练循环

PyTorch 的强大之处在于,它抽象了梯度计算和参数更新的复杂性,让你可以专注于模型架构和实验。在接下来的课程和作业中,你将运用这些基础知识来解决实际的自然语言处理任务。

23:Hugging Face Transformers 库使用指南 🤗

在本教程中,我们将学习如何使用 Hugging Face Transformers 库。这是一个非常有用且高效的工具,能让你轻松使用现成的、基于 Transformer 架构的 NLP 模型。无论你是为了期末项目,还是未来的其他应用,掌握这个库都非常有帮助。它尤其与 PyTorch 配合得很好。

📚 概述与准备工作

上一节我们介绍了本教程的目标。本节中,我们来看看如何开始使用。

首先,Hugging Face 的官方文档非常出色,提供了大量教程、指南和可运行的 Notebook。如果你有任何疑问,那里是最好的查阅之处。

在开始编码前,我们需要安装必要的 Python 包。

!pip install transformers datasets

transformers 库提供了大量预训练模型,而 datasets 库则提供了一些可用于各种任务的数据集。在本教程中,我们将以情感分析任务为例。我们还会导入一些辅助函数来帮助我们理解编码过程。

from transformers import AutoTokenizer, AutoModelForSequenceClassification
import torch

🔍 第一步:从 Hub 获取模型

使用 Hugging Face 库的第一步,通常是从 Hugging Face Hub 上找到一个合适的模型。

Hub 上提供了海量的不同模型,例如 BERT、GPT-2、T5 等。这些预训练模型的权重都可以免费下载。你可以根据侧边栏的任务分类(如零样本分类)来寻找特定任务上表现优秀的模型。

基本上,无论你想做什么任务,很可能都能在 Hub 上找到对应的模型。

在本例中,我们将进行情感分析。选择模型后,我们需要两样东西:

  1. 分词器:负责将原始文本分割成模型可以理解的标记(Token)。
  2. 模型本身:用于接收分词后的输入并做出预测。

分词器将文本转换为词汇 ID(离散的数字),模型则基于这些 ID 进行预测。

🛠️ 初始化分词器与模型

我们可以使用 AutoTokenizerAutoModelForSequenceClassification 来自动加载与所选模型对应的分词器和模型架构。

model_name = "distilbert-base-uncased-finetuned-sst-2-english"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForSequenceClassification.from_pretrained(model_name)

AutoTokenizer 会自动为你选择与模型匹配的分词器,确保标记能正确映射到模型训练时使用的词汇表索引。AutoModelForSequenceClassification 则会加载一个专门用于序列分类(如情感分析)的模型头部。

📖 理解分词器

分词器用于对模型的输入进行预处理,将原始字符串映射为模型可以理解的数字 ID。

主要有两种分词器:Python 写的和 Rust 写的(tokenizer fast)。AutoTokenizer 默认会使用速度更快的 Fast 版本。两者功能上差异不大,主要影响推理速度。

以下是使用分词器的基本方法:

input_str = "Hugging Face Transformers is great."
model_inputs = tokenizer(input_str, return_tensors="pt")
print(model_inputs)

分词器的输出是一个字典,主要包含:

  • input_ids:每个标记对应的数字 ID。
  • attention_mask:注意力掩码,用于 Transformer 模型。

你可以通过字典键或属性来访问它们:

ids = model_inputs["input_ids"]
# 或者
ids = model_inputs.input_ids

分词过程内部包含多个步骤:分割文本、转换为 ID、添加模型所需的特殊标记(如 [CLS], [SEP])。对于 Fast 分词器,你还可以获取更详细的信息,如字符到单词的映射。

⚙️ 高级分词功能

在实际应用中,我们经常需要处理批量数据,并保证它们长度一致。这时就需要填充和截断。

以下是相关功能的示例:

# 处理单个句子,并返回 PyTorch 张量
inputs = tokenizer("Hello, world!", return_tensors="pt")

# 处理多个句子,并自动填充到相同长度
sentences = ["I love NLP.", "Hugging Face is amazing."]
batch_inputs = tokenizer(sentences, padding=True, truncation=True, return_tensors="pt")
print(batch_inputs)

设置 padding=Truetruncation=True 后,分词器会自动将短句填充、长句截断,并生成对应的注意力掩码。填充标记的 ID 通常是 0。

你还可以使用 batch_decode 方法将 ID 批量转换回文本,并选择是否跳过特殊标记。

decoded = tokenizer.batch_decode(batch_inputs.input_ids, skip_special_tokens=True)
print(decoded)

🧠 使用模型进行预测

现在我们已经了解了分词器,接下来看看如何使用模型。

Hugging Face 的预训练模型通常具有相同的底层架构,但针对不同任务(如序列分类、掩码语言建模)配备了不同的“头部”。我们可以使用特定的类(如 AutoModelForSequenceClassification)来加载带有合适头部的模型。

模型主要分为三类:

  1. 编码器模型:如 BERT,适用于理解任务(分类、问答)。
  2. 解码器模型:如 GPT-2,适用于生成任务。
  3. 编码器-解码器模型:如 BART、T5,适用于序列到序列任务(翻译、摘要)。

选择模型时必须与任务匹配。例如,你不能将仅含编码器的 DistilBERT 用作序列到序列模型。

将分词后的输入传递给模型有两种方式:

# 方式一:显式指定参数
outputs = model(input_ids=model_inputs.input_ids, attention_mask=model_inputs.attention_mask)

# 方式二:使用 ** 解包字典(更简洁)
outputs = model(**model_inputs)

两种方式结果相同。模型输出包含 logits(未归一化的预测分数)。对于二分类任务,logits 的形状是 [batch_size, 2]

logits = outputs.logits
print(logits)

要得到最终的预测类别,可以对 logitsargmax

predicted_class_id = logits.argmax().item()
print(f"Predicted class ID: {predicted_class_id}")

📊 计算损失与训练准备

Hugging Face 模型本质上是 PyTorch 模块,因此可以像训练普通 PyTorch 模型一样训练它们。

模型输出可以直接包含损失值,前提是你传递了标签:

labels = torch.tensor([1]) # 假设是正面情感
outputs = model(**model_inputs, labels=labels)
loss = outputs.loss
print(loss)

有了损失,你就可以调用 loss.backward() 进行反向传播,并使用优化器更新模型参数。这为模型微调奠定了基础。

🔬 探索模型内部状态

为了理解模型的运作机制,我们可以提取其内部状态,如隐藏状态和注意力权重。

在初始化模型时,通过设置参数可以要求输出这些信息:

model = AutoModelForSequenceClassification.from_pretrained(model_name, output_attentions=True, output_hidden_states=True)
model.eval() # 设置为评估模式,关闭 dropout 等训练特性

with torch.no_grad():
    outputs = model(**model_inputs)

# 获取所有层的隐藏状态和注意力权重
all_hidden_states = outputs.hidden_states
all_attentions = outputs.attentions

hidden_states 是一个元组,包含模型每一层的输出表示。attentions 也是一个元组,包含每一层每个注意力头的注意力权重矩阵。分析这些数据有助于进行模型可解释性研究。

🏋️ 实战:微调模型

在实际项目中,你通常需要对预训练模型进行微调以适应特定任务。下面我们以情感分析为例,演示微调流程。

1. 加载数据集

我们使用 Hugging Face datasets 库加载 IMDB 电影评论数据集,并创建一个小的训练和验证集用于演示。

from datasets import load_dataset, DatasetDict

# 加载数据集并取子集
raw_datasets = load_dataset("imdb")
small_train_dataset = raw_datasets["train"].shuffle(seed=42).select(range(128))
small_eval_dataset = raw_datasets["train"].shuffle(seed=42).select(range(128, 160))

2. 预处理数据集

使用 map 函数和分词器对整个数据集进行批量分词处理。

def tokenize_function(examples):
    return tokenizer(examples["text"], padding="max_length", truncation=True)

tokenized_datasets = DatasetDict({
    "train": small_train_dataset.map(tokenize_function, batched=True),
    "eval": small_eval_dataset.map(tokenize_function, batched=True)
})

# 重命名列以符合模型输入要求,并设置格式为 PyTorch 张量
tokenized_datasets = tokenized_datasets.remove_columns(["text"])
tokenized_datasets = tokenized_datasets.rename_column("label", "labels")
tokenized_datasets.set_format("torch")

3. 创建 DataLoader

使用 PyTorch 的 DataLoader 来加载批数据。

from torch.utils.data import DataLoader

train_dataloader = DataLoader(tokenized_datasets["train"], shuffle=True, batch_size=16)
eval_dataloader = DataLoader(tokenized_datasets["eval"], batch_size=16)

4. 训练循环(PyTorch 原生方式)

你可以像训练任何 PyTorch 模型一样编写训练循环。Transformers 库还提供了专门的优化器和学习率调度器。

from transformers import AdamW, get_linear_schedule_with_warmup

optimizer = AdamW(model.parameters(), lr=5e-5)
num_epochs = 3
num_training_steps = num_epochs * len(train_dataloader)
lr_scheduler = get_linear_schedule_with_warmup(optimizer, num_warmup_steps=0, num_training_steps=num_training_steps)

model.train()
for epoch in range(num_epochs):
    for batch in train_dataloader:
        outputs = model(**batch)
        loss = outputs.loss
        loss.backward()
        optimizer.step()
        lr_scheduler.step()
        optimizer.zero_grad()

5. 使用 Trainer API(更简洁的方式)

Hugging Face 提供了更高级的 Trainer API,它封装了训练循环、评估、日志记录等复杂逻辑,使代码更简洁。

from transformers import Trainer, TrainingArguments

training_args = TrainingArguments(
    output_dir="./results",
    num_train_epochs=3,
    per_device_train_batch_size=16,
    per_device_eval_batch_size=16,
    evaluation_strategy="epoch",
    logging_dir="./logs",
)

def compute_metrics(eval_pred):
    logits, labels = eval_pred
    predictions = logits.argmax(axis=-1)
    # 这里可以计算准确率等指标,需要导入 evaluate 库
    # accuracy = evaluate.load("accuracy")
    # return accuracy.compute(predictions=predictions, references=labels)
    return {"dummy_metric": (predictions == labels).mean()}

trainer = Trainer(
    model=model,
    args=training_args,
    train_dataset=tokenized_datasets["train"],
    eval_dataset=tokenized_datasets["eval"],
    compute_metrics=compute_metrics,
)

trainer.train()

使用 Trainer 可以轻松添加回调函数,例如早停(Early Stopping)或自定义日志记录。

训练完成后,可以评估模型并保存检查点:

# 评估
eval_results = trainer.evaluate()
print(eval_results)

# 预测
predictions = trainer.predict(tokenized_datasets["eval"])
print(predictions.predictions.shape)

# 保存模型
trainer.save_model("./my_finetuned_model")

要加载微调后的模型,只需像之前一样使用 from_pretrained 并指定保存的路径。

🎯 总结

在本教程中,我们一起学习了如何使用 Hugging Face Transformers 库的核心功能:

  1. 从 Hub 选择和加载模型与分词器
  2. 使用分词器对文本进行预处理,包括填充和截断。
  3. 将处理后的输入传递给模型,并获得预测结果和损失。
  4. 探索模型的内部状态,如隐藏层和注意力权重。
  5. 对预训练模型进行微调,我们介绍了两种方式:原生的 PyTorch 训练循环以及更便捷的 Trainer API。

Hugging Face 生态系统极大地简化了 NLP 模型的实验和应用流程。通过掌握这些基础知识,你将能够高效地利用强大的预训练模型来解决各种自然语言处理任务。

posted @ 2026-03-26 13:14  布客飞龙IV  阅读(34)  评论(0)    收藏  举报