UCB-CS182-深度学习笔记-全-
UCB CS182 深度学习笔记(全)

课程 P1:CS 182 第一讲,第一部分 - 引言 🎯
在本节课中,我们将要学习深度学习中的一个核心概念:表示学习。我们将从一个关于多语言机器翻译的生动例子开始,探讨模型如何通过构建一种“语言无关”的内部表示,来解决传统方法中数据稀缺的难题。这个故事将帮助我们理解,为什么自动学习有效的表示是深度学习如此强大的关键。
多语言机器翻译的挑战 🌐

世界上有六千多种不同的语言。然而,如果我们想训练一个模型从一种语言翻译成另一种语言(例如用于机器翻译系统),我们通常需要配对数据。这意味着我们需要一种语言的句子示例和其在另一种语言中对应的翻译。
例如,英语句子“I think, therefore I am”对应于法语句子“Je pense, donc je suis”。训练机器翻译系统的标准方法是:收集一个大型的配对语料库,其中包含源语言句子及其对应的目标语言翻译,然后在此基础上训练一个翻译模型。一旦模型训练完成,它就可以接收源语言句子并生成目标语言的译文。
但是,考虑到语言种类的繁多,这种方法可能相当麻烦,尤其是对于使用人数较少或数据稀缺的低资源语言。例如,现有数据集中可能很少有将马耳他语翻译成藏语的配对句子。当谷歌的研究人员面临这个问题时,他们想出了一个更具创意的解决方案,这个方案也阐明了深度学习的一个重要原则。
从单一模型到多语言模型 🔄
上一节我们介绍了传统机器翻译对配对数据的依赖。本节中我们来看看一种创新的解决方案:多语言机器翻译模型。
一个标准的机器翻译系统会为每一对语言训练单独的模型。例如,英语到法语一个模型,英语到西班牙语另一个模型。
而研究人员提出的方法是:建立一个单一的多语言模型。这个模型可以读取任何语言的句子,并在接收到指定目标语言的附加输入后,将其翻译成任何其他语言。

以下是这种模型的工作方式:
- 你将句子输入模型,无论它是什么语言。
- 你同时告诉模型:“把它变成西班牙语”。
- 模型会输出相应的西班牙语句子。
至关重要的是,这种模型可以在与标准模型集合完全相同的数据上进行训练。你只需要将所有配对数据(如英语-法语句子)标记上目标语言“法语”,将所有法语-西班牙语配对数据标记上目标语言“西班牙语”,以此类推。训练这样的模型不需要任何新数据,只是以不同的方式利用已有数据。
为什么多语言模型有效? 💡
上一节我们介绍了多语言模型的架构。本节中我们来探讨它为何有效。
许多语言之间存在共性。例如,会说西班牙语的人,虽然不会说意大利语,但经常能对意大利语单词的含义做出有根据的猜测,因为两种语言有相似的词根和规则。
这种多语言模型,即使在某种语言的配对数据较少时,也能利用从其他语言中学到的知识,做出有根据的翻译猜测。
这样的模型能做的另一件事是,在没有见过特定语言对的情况下执行翻译。例如,如果模型学习过“英语到西班牙语”和“日语到中文”,但从未见过“英语到中文”,它可能已经建立了英语句子的某种通用内部表示。当它读入一个英语句子时,可以将其转换为这种通用表示,然后再解码成任何目标语言(包括中文)。
实验发现与洞察 🧪
上一节我们讨论了多语言模型的潜力。本节中我们来看看研究人员通过实验发现了什么。
他们发现:
- 提升效率:这种方法尤其在翻译低资源语言时比单一模型更有效。加入更多通用语言(如西班牙语、法语)的数据,也能提高翻译成相关低资源语言的质量。
- 零样本翻译:模型可以实现零样本机器翻译。这意味着模型可以翻译它从未见过配对数据的语言对(例如,只学过“英语到法语”和“英语到西班牙语”,但能完成“法语到西班牙语”的翻译)。这证明了模型内部确实形成了某种语言无关的表示。
- 有趣的插值实验:研究人员让模型生成两种目标语言的“混合”翻译(例如,40%西班牙语风格 + 60%法语风格)。通过观察输出如何随混合比例平滑变化,可以洞察模型对语言的理解。
以下是一个具体实验示例:
- 任务:将英语翻译成西班牙语和葡萄牙语的混合。
- 观察:随着葡萄牙语权重的增加,输出句子中的单词被逐渐替换,从纯西班牙语平滑过渡到纯葡萄牙语。
- 另一个例子:在英语到俄语/白俄罗斯语的翻译插值中,在中间比例(如44%俄语,56%白俄罗斯语)时,模型竟然生成了乌克兰语的句子。这很有道理,因为白俄罗斯语数据稀缺,模型利用了从乌克兰语(一种相关语言)中学到的知识来填补空白。
核心启示:表示学习的力量 🧠
上一节我们看到了多语言模型的有趣行为。本节中我们来提炼其背后的核心思想。
这个多语言翻译的故事说明了深度学习的一个重要观点:关键在于学习合适的表示。
传统的翻译观是字面地将一种语言的句子变成另一种语言的句子。而另一种思路是:
- 源语言句子是说话者某个思想(或语义内容)的体现。
- 这种思想在本质上是语言无关的。
- 如果我们能找出表达这个思想的语言无关的表示,再想象一个目标语言使用者会如何用他们的语言表达同样的思想,就能实现翻译。
这样,我们就不再需要所有语言对的配对数据,只需要知道每种语言如何与这个通用的“思想表示”相互转换。这使得机器翻译问题变得简单得多。
当然,模型并非真正理解“思想”,但它确实学习到了一种比原始单词更接近语义、对语言变化更鲁棒的内部表示。自动学习这种强大的表示,正是深度学习的关键能力。
从机器学习到表示学习 📈
上一节我们通过翻译案例引入了“表示”的概念。本节中我们正式将其与经典机器学习对比。
机器学习问题的经典观点是:从输入 X 预测输出 Y 的问题。我们通常拟合一个模型(如线性模型 Y = wX + b)来建立 X 到 Y 的映射。
但在实践中,X(如图像、声音、句子)通常非常复杂,内部包含丰富结构。像深度学习这样的技术之所以强大,主要在于它能够自动从这些复杂输入中学习到适合做预测的有效表示。

深度学习方法的力量,就在于能够从数据中自动学习这样的表示,从而处理极其复杂的输入和任务。
总结 📝
本节课中我们一起学习了:
- 多语言机器翻译模型如何通过单一模型处理多种语言翻译,缓解了低资源语言的数据稀缺问题。
- 模型有效的关键在于它学习了语言无关的内部表示,使得零样本翻译成为可能。
- 实验表明,模型可以通过表示在不同语言间进行平滑插值,并利用语言间的共性。
- 这个故事的核心启示是:深度学习的强大之处在于自动学习有意义的表示。将原始、复杂的数据(如文本、图像)转化为更适合解决任务(如翻译、分类)的表示形式,是许多深度学习成功应用的基础。

这种表示学习的思想,将贯穿我们后续的整个课程。
📊 CS 182 课程 3-3:误差分析
在本节课中,我们将学习如何诊断机器学习模型的问题,并系统地选择超参数。我们将探讨过拟合与欠拟合的识别方法,以及如何通过划分数据集来指导模型开发与评估。


🎯 概述:机器学习工作流程
一个核心问题是:我们如何知道模型是过拟合还是欠拟合?如何选择算法和超参数?仅凭训练损失低来做决定并不明智,因为这无法揭示偏差与方差的权衡。训练损失低可能意味着欠拟合(高偏差),但即使训练损失为零,模型也可能因过拟合(高方差)而在新数据上表现糟糕。因此,我们需要一个更系统的工作流程。
📂 数据集划分
标准的机器学习工作流程始于数据集的划分。通常,我们将数据集分为两部分:训练集 和 验证集。训练集约占数据的80%至90%,用于训练模型参数;验证集则占剩余部分,用于模型选择和超参数调优。
训练损失记为 L(θ, D_train),即模型在训练数据上的经验风险。验证损失记为 L(θ, D_val),它使用未参与训练的数据,是对模型真实风险的无偏估计。通过比较两者,我们可以诊断过拟合与欠拟合。
🔍 诊断与调整策略
以下是基于训练损失和验证损失的诊断与调整步骤:
- 检查训练损失:如果训练损失过高(例如,在猫狗分类任务中错误率高达35%),则模型可能欠拟合。
- 应对欠拟合:可以尝试减少正则化强度、改进优化算法、添加更多特征或使用更复杂的模型(增加参数),以提升模型对训练数据的拟合能力。
- 检查验证损失:如果训练损失很低(例如1%),但验证损失显著更高(例如20%),则模型可能过拟合。
- 应对过拟合:可以增加正则化强度、简化模型(降低模型容量)或收集更多训练数据。
这个过程可以迭代进行,直至获得满意的结果。



⚙️ 超参数的选择
超参数的选择需要根据其目的,分配到不同的数据集上:
- 训练集:用于选择直接影响优化过程的超参数,例如学习率 α。因为这些参数的目标是最小化训练损失。
- 验证集:用于选择影响模型容量和过拟合的超参数与设计,例如:
- 模型类别(逻辑回归 vs. 神经网络)
- 网络层数
- 特征选择
- 正则化系数 λ
验证集帮助我们做出那些需要在偏差与方差间取得平衡的决策。
📈 学习曲线分析
在实践中,我们并非孤立地查看最终损失,而是观察学习曲线。学习曲线以优化步数(如梯度下降迭代次数)为横轴,以损失值为纵轴,通常同时绘制训练损失和验证损失。
以下是两种典型的学习曲线形态:
- 过拟合曲线:训练损失持续下降,但验证损失在下降到某一点后开始上升。这表明模型开始“记忆”训练数据,损害了泛化能力。
- 欠拟合曲线:训练损失和验证损失在下降后都维持在较高的水平,且两者差距很小。这表明模型能力不足,无法捕捉数据中的基本模式。
通过观察学习曲线,我们可以更直观地判断模型状态。例如,若出现验证损失上升的过拟合迹象,应增加正则化;若出现欠拟合,则应减少正则化或增加模型复杂度。

⏹️ 早停法
一个自然的想法是:既然我们能在学习曲线上看到验证损失的最低点,能否在优化过程中,当验证损失即将上升时提前停止训练?这种方法被称为早停。
早停是一种有效防止过拟合的正则化技术。然而,它并不能完全替代其他正则化方法。即使通过早停在最佳点停止,模型的最终性能可能仍不如结合了适当权重衰减(L2正则化)等方法的模型。早停是工具箱中有用的工具,但非万能。
🧪 测试集:最终评估


遵循上述流程并得到满意模型后,如何向他人报告模型的最终性能?不能使用验证损失,因为验证集已被用于选择超参数,其评估结果会偏向于这些选择,不再是模型泛化能力的无偏估计。
为了解决这个问题,我们需要将数据集进一步划分为三部分:
- 训练集:用于训练模型参数 θ 和选择优化类超参数。
- 验证集:用于选择模型结构、正则化强度等。
- 测试集:在模型完全确定后,仅用于一次性的最终性能评估。测试集在整个开发过程中必须保持“纯净”,绝不用于任何形式的训练或选择,以确保评估结果的公正性。

📝 总结
本节课我们一起学习了机器学习中的误差分析与模型选择流程:
- 误差来源:偏差(模型能力不足)和方差(模型对训练数据过于敏感)。误差可近似表示为 误差 ≈ 偏差² + 方差。
- 过拟合与欠拟合:过拟合对应高方差,欠拟合对应高偏差。
- 权衡方法:通过仔细选择模型复杂度、特征和正则化来平衡偏差与方差。
- 工作流程核心:划分训练集、验证集和测试集。
- 训练集用于优化参数。
- 验证集用于选择超参数和模型设计。
- 测试集用于最终性能报告。
- 分析工具:学习曲线是诊断模型状态的重要工具,早停法是一种实用的正则化技术。

通过这套系统的方法,我们可以更科学地开发、调试和评估机器学习模型。

课程 P11:CS 182 第4讲 第1部分 - 优化 🧠
在本节课中,我们将深入探讨优化算法,特别是梯度下降法。我们将回顾其基本原理,分析其在复杂损失函数(如神经网络)中面临的挑战,并讨论如何改进优化方向。
回顾梯度下降法
在第二讲中,我们首次讨论了逻辑回归的梯度下降。首先,让我们从梯度下降的回顾开始。
我们有一个损失函数。在第二讲中,我们了解到一个很好的损失函数是负对数似然,这里用 l(θ) 表示。但通常,你可以使用任何你想要的损失函数。
假设参数 θ 是二维的,我们可以将损失函数可视化。两个水平轴代表 θ 的两个维度,垂直轴代表损失值。这张图代表了损失函数的“地形”,显示了 θ 如何影响 l(θ)。

如果我们从某个点(由黑色圆圈表示)开始,找到使 l(θ) 最小化的最佳 θ 值的方法之一,就是找出一个可以前进的方向(用箭头表示),该方向能减少 l(θ)。我们朝着那个方向移动,希望最终收敛到尽可能好的值。
向特定方向移动意味着:找到使 l(θ) 减小的方向 v,然后将 θ 更新为 θ + α * v。其中,α 是一个被称为学习率或步长的小常数。直觉上,它表示你向 v 方向移动的速度。
你不想移动得太快,因为如果 α 非常大,你可能会越过最低点,从另一边出来,最终落在一个比起点损失值更高的地方。因此,你需要使用一个较小的常数。

如何选择前进方向


在一维情况下最容易获得直觉。假设 θ 是一维的,这是我们的函数图。如果我们位于某个特定点,可以计算该点的斜率。如果斜率为负,我们向右移动;如果斜率为正,我们向左移动。这将使我们更接近最小值。
一般情况下,对于多元函数,我们对每个维度单独应用这个过程。我们如何知道斜率?通过计算导数。在更高维度上,对于每个维度,我们沿着该维度斜率相反的方向移动。斜率由该维度的偏导数给出。
因此,v1 被设置为 -∂l/∂θ1,v2 被设置为 -∂l/∂θ2。通常,我们可以将所有偏导数堆叠成一个向量。这个向量称为梯度。梯度是一个向量,其每个分量都是 l(θ) 关于 θ 相应维度的偏导数。这个向量的维数与 θ 的维数相同。
简单来说,你只需将方向 v 设为梯度的负值。

在今天的讲座中,我们将详细讨论梯度下降有效的情况、效果不佳的情况以及如何改进它。我们会看到许多梯度下降的可视化图。
一种常见的可视化是等高线图。在这种图中,线条对应于损失函数的等高线。沿着同一条等高线的所有 θ 值,其 l(θ) 具有相同的值。这很像地图上的高程图。
在这些图中,我们将用直线可视化梯度下降的优化路径。每个线段的端点代表一次梯度下降迭代后的 θ 值。点之间的线段表示步骤,该步骤是梯度方向乘以学习率 α。
梯度下降演示与挑战

让我们看一个梯度下降的演示。在这张图中,我们正在优化一个有两个参数的函数。橙色大圆圈代表起点,较小的圆圈代表终点。
这个函数并不容易优化,它有一个狭窄的山谷(类似于峡谷)。当我移动起点时,你可以立即看到梯度下降的步骤。步骤沿着山谷向下移动,然后逐渐沿着山谷前进。
当我增加步长时,步骤会沿着山谷走得更远。但随着步长变大,当你到达谷底时,你实际上会冲上另一边的斜坡,最终向右摆动。你越过了最低点,又从另一端回来,如此往复。
从这个可视化中,我们看到梯度下降面临许多挑战:
- 振荡问题:我们从一边冲上去,又从另一边回来。
- 无法达到精确最优:当我们沿着山谷前进时,速度变得越来越慢,实际上并没有达到确切的最低点。
梯度下降方向的局限性

梯度下降并不总是朝着最优方向移动。它总是采取最陡下降的方向,但这并不总是直接指向最优解的方向。
在这张图中,从起点迈出的第一步实际上正在远离最优解。它正沿着山谷向下移动,但向下移动使它离最佳状态更远。梯度下降选择最陡峭的方向,而不是最好的方向。
想象一个椭圆形的坑,最优解在中间。最陡下降的方向总是垂直于等高线的切线。如果我们朝那个方向走,最终会进入山谷,也许从另一边出去一点。在下一个点,最陡的方向又会改变,以此类推。这正是我们在演示中看到的。
给定无限的梯度步长,最终这将达到最优,但可能需要很长时间。最陡峭的方向并不总是最好的。
现在我们想知道:有没有一种方法可以更好地计算出方向?有没有办法计算出一个直接走向最优的方向,而不仅仅是最陡下降的方向?我们稍后会讨论这个问题。实际上有一些方法可以计算更好的方向,但首先让我们更好地理解梯度下降带来的挑战。
损失函数的性质:凸性
之前讨论逻辑回归时,我们画的损失曲面非常好。“好”意味着梯度下降能很好地处理这个损失面。为什么?这个损失面有很多优点,其中之一是没有狭窄陡峭的山谷。从任何一点出发,最陡下降方向都会指向最优解。
但我们在做机器学习时,损失函数真的都很好吗?逻辑回归是一种分类方法,其中类别的概率由输入的线性函数的 softmax 给出。参数 θ 出现在指数中。我们取概率的对数,就得到了负对数似然损失。
逻辑回归的负对数似然损失保证是所谓的凸函数。凸函数通常很好。

凸函数是什么意思?为什么凸函数往往优化得很好?这里有一个一维函数的例子。一个函数是凸的,如果该函数上任意两点之间的线段完全位于函数图像的上方。也就是说,取任意两点,在它们之间画一条线,这条线将完全在图像上方,不会与图像相交。
相比之下,非凸函数则不是这样。如果我在两点之间画一条线,它可能会与图像相交。非凸函数可能有两个甚至多个局部最优值。但即使函数只有一个最优值,它也可以是非凸的(例如存在平台区域)。
凸函数意味着它只有一个最优值(全局最优)。但非凸函数并不意味着一定有多个最优值。
凸函数的好处在于,像梯度下降这样的简单算法对凸函数有很强的理论保证。可以证明,在凸函数上,梯度下降会在多项式次数的迭代内找到全局最优解。
但这并不意味着梯度下降对所有凸函数都有效。例如,之前幻灯片左下角的函数也是凸的,但梯度下降需要走非常曲折的路径才能达到最优。不过,凸函数通常是我们能期望的更好的情况之一。
神经网络的损失曲面

逻辑回归是一个相对简单的算法。神经网络则是一个更复杂的函数,具有许多参数和多层表示,后接更复杂的数学操作(如 softmax)。这些操作的精确定义将在后面的讲座中介绍。
很难想象神经网络的损失曲面,因为神经网络有非常多的参数,因此无法用两个参数来绘制有意义的图像。但我们可以尝试。

李等人的论文《Visualizing the Loss Landscape of Neural Networks》使用了一些巧妙的降维技巧,试图大致可视化神经网络的损失景观。虽然无法精确可视化,但通过降维,我们可以得到一些看起来有点现实的图像,至少能给我们一些直觉。
例如,一个在 ImageNet 上训练的、没有跳跃连接(skip connections)的 ResNet 模型的损失景观图。将这张图与之前逻辑回归美丽的损失景观进行比较,这东西看起来更糟。这里有局部最优、平坦的高原,非常不凸,有很多复杂的特征。
有些网络结构更好。例如,一个带有跳跃连接的 ResNet 模型(我们稍后会讨论什么是跳跃连接)的损失景观看起来更好一些。虽然仍然有很多平坦区域,但至少没有那么多的峭壁和缝隙。神经网络的损失景观肯定不是凸的。
损失景观中的关键特征
在损失景观中,我们应该关注三个重要的地理特征:
- 局部最优
- 高原
- 鞍点
有趣的是,按知名度排序,局部最优最出名,鞍点最不出名。但按问题的严重性排序,局部最优实际上是最不糟糕的问题之一,而鞍点是最糟糕的问题之一。
以下是这些特征的详细说明:
1. 局部最优
这是非凸损失景观最明显的问题之一。你可能有多个点处的导数都为零(梯度为零)。在这些局部最优点中的任何一个,都没有改进的方向;任何移动都只会增加损失值。
全局最小值也是一个局部最小值。在全局最小值处,任何步骤都会增加损失值,但这没关系,因为你已经是最好的了。但图中的其他局部最优点也有这个性质。
这是人们过去担心神经网络的一个重要原因。众所周知,神经网络具有非凸损失景观,因此它们必须具有局部最优。原则上,局部最优可能非常可怕,因为梯度下降可能收敛到一个比全局最优任意差的解。当导数都为零时,你无法知道这是全局最优还是某个很差的局部最优。
有点意外的是,对于非常大的神经网络,局部最优实际上变得不那么重要了。这在经验和理论上都有研究。具有大量参数的神经网络在其损失景观中肯定有局部最优,但似乎没有太多的局部最优比全局最优差很多。这不是一件明显的事情,实际上有点令人惊讶。

一张来自论文《Loss Surfaces in Multilayer Networks》的图说明了这一点。横轴显示梯度下降发现的解的损失值(负对数似然),纵轴表示达到该损失的频率。不同颜色代表不同大小的网络(参数更多)。
对于最小的网络(黄色),不同的损失值分布较广。随着网络变大(红色),实现的损失值分布变得更加紧密。这意味着,虽然仍然存在局部最优,但这些局部最优的损失值都非常相似,都很好。没有非常糟糕的局部最优,或者即使有,也非常罕见。这是一个经验观察,也有相当多的理论分析支持。有强有力的证据表明,大型神经网络的局部最优并没有那么糟糕。
2. 高原
高原不那么明显。我们可以想象一个损失景观,其中有一个很大的区域,损失的梯度非常小。我们在之前的峡谷底部演示中看到了这样的东西。峡谷两侧有非常陡峭的坡度,但到了底部,梯度很小,让你沿着峡谷缓慢移动。
高原在神经网络损失景观中非常常见。你不能仅仅选择微小的学习率来防止在谷底的振荡,因为如果你选择很小的学习率,你要花很长时间才能穿过高原。这实际上是一个很大的问题。稍后我们将学习动量,这对解决这种问题很有帮助。

3. 鞍点
鞍点在一维函数中不存在,但对于更高维的函数,鞍点实际上相当常见。鞍点可以看作是某些维度上的局部最小值,同时是其他维度上的局部最大值。在鞍点处,关于函数各维度的偏导数为零,但函数在某些维度上增加,在其他维度上减少。
一个二维鞍点的可视化显示在左图中间。我显示了一个表示梯度向量的抖动图。当你远离鞍点时,梯度箭头指向正确的方向(函数减少的方向)。但当你靠近鞍点时,梯度消失,变得非常非常小。这意味着即使你最终会离开鞍点,当你接近它时,你只能迈出非常微小的步伐,就像一个非常糟糕的高原。
右图上的绿线显示了梯度流,即梯度下降遵循的轨迹。当它越来越接近鞍点时,梯度下降开始变得非常缓慢。
你可能会认为这是一个深奥的问题:有多大几率恰好落在梯度在某些方向减少、在其他方向增加的点上?在低维度中,这似乎不太可能。但事实上,神经网络损失景观中最关键的点就是鞍点。这是因为在高维空间中,事情的工作方式与低维空间不同。
高维空间中的临界点
让我们先多谈谈这个概念。什么是临界点?临界点是梯度为零的任何点,即 l(θ) 关于 θ 每一维的所有偏导数都为零。

临界点有不同的类型(局部最小值、局部最大值、鞍点)。判断方法是通过看二阶导数。
在一维中,如果你处于临界点:
- 二阶导数为正:图形向上弯曲,你是局部最小值。
- 二阶导数为负:图形向下弯曲,你是局部最大值。
在更高维度中,你可以有一些二阶导数为正,一些为负。通常,我们构造一个称为海森矩阵的矩阵。海森矩阵基本上是梯度的二阶导数等价物。梯度是一个向量,每个分量都是关于 θ 相应维度的一阶偏导数。海森矩阵是一个矩阵,其中每个项都是关于 θ 中两个分量的二阶偏导数。
在鞍点处的海森矩阵,其对角线项对于某些维度是正的,对于其他维度是负的。例如,对于一个二维函数,海森矩阵可能是 [[1, 0], [0, -1]]。这意味着沿一个维度的二阶导数为正,沿另一个维度的二阶导数为负。
对于一个局部最小值或最大值,所有的对角线条目都是同号的(或更一般地,矩阵是正定或负定的)。
现在想象一下在更高维度会发生什么。在更高维度中,海森矩阵有许多条目(例如 1000x1000)。所有对角线条目都具有相同符号的情况有多频繁?对于一个二维函数,两者都是正或都是负似乎很有可能。但在更高维度中,似乎不太可能每一个对角线条目都是正的或每一个都是负的。
因此,在高维空间中,大多数临界点实际上是鞍点。这实际上有点令人惊讶,如果你只考虑二维函数的直觉。但在高维中,只是不太可能所有的条目都有相同的符号。这就是为什么在高维中,大多数梯度为零的点实际上是鞍点。
我们应该朝哪个方向优化?
在我们谈论最陡下降(梯度下降的作用)之前,我们需要考虑在这些复杂的神经网络损失景观中,我们如何找到改进的方向,带领我们绕过所有的“龙和怪物”(局部最优、高原、鞍点),最终达到最优解。
记住,我们并不总是朝着最优的方向前进。如果我们选择最陡下降方向,由于振荡、高原和鞍点,我们会遇到各种各样的问题。我们想要的是不要来回摆动,而是找到一个更好的方向,一个更直接地走向最优解的方向。

总结
在本节课中,我们一起学习了:
- 梯度下降法的基本原理:通过计算损失函数的梯度(偏导数向量),并沿其反方向以一定学习率更新参数,以最小化损失。
- 梯度下降的局限性:最陡下降方向不总是最优方向,可能导致振荡、收敛缓慢,尤其是在狭窄山谷中。
- 损失函数的性质:凸函数具有良好的优化特性(单一全局最优),逻辑回归的损失是凸的,但神经网络的损失景观通常是非凸的。
- 神经网络损失景观的关键挑战:
- 局部最优:对于大型神经网络,局部最优的损失值往往与全局最优相近,问题相对不严重。
- 高原:梯度很小的平坦区域,会显著减慢优化速度。
- 鞍点:在高维损失空间中,大多数梯度为零的点是鞍点(某些维度上最小,其他维度上最大),梯度在鞍点附近消失,导致优化停滞,这是非常严重的问题。
- 高维空间的特点:在高维空间中,鞍点远比局部最小值或最大值普遍。


在接下来的部分,我们将探讨如何改进基础的梯度下降法,例如引入动量等技巧,以更好地应对这些挑战。
课程 P12:CS 182 - 第四讲 - 第二部分 - 优化 🚀
在本节课中,我们将要学习几种改进梯度下降的优化算法。我们将从牛顿法这一理想但计算昂贵的算法开始,然后探讨几种在实际深度学习中被广泛使用的、更高效的算法,包括动量、RMSprop 和 Adam。这些方法旨在加速收敛并更稳定地找到损失函数的最小值。
牛顿法:一个理想但昂贵的方向 🔍
上一节我们介绍了梯度下降及其局限性。本节中我们来看看一种理论上更优的改进方向计算方法——牛顿法。
牛顿法是一种比梯度下降更复杂的优化算法。它对于近似椭圆形的函数,能直接指向最优解。然而,在训练神经网络时,我们通常不会使用牛顿法,因为它需要计算海森矩阵(Hessian matrix,二阶导数矩阵)及其逆矩阵,计算量非常巨大,对于参数数量庞大的神经网络来说是不可行的。

牛顿法的核心思想是利用函数的泰勒展开式进行局部近似。对于一个函数 ( f(x) ),在点 ( x_0 ) 处,我们可以用其二阶泰勒展开来近似:
[
f(x) \approx f(x_0) + f'(x_0)(x - x_0) + \frac{1}{2} f''(x_0)(x - x_0)^2
]
这相当于用一条抛物线来局部近似原函数。
对于多变量损失函数 ( L(\theta) ),其近似形式为:
[
L(\theta) \approx L(\theta_0) + \nabla L(\theta_0)^T (\theta - \theta_0) + \frac{1}{2} (\theta - \theta_0)^T H (\theta - \theta_0)
]
其中,( \nabla L(\theta_0) ) 是梯度,( H ) 是海森矩阵。这个二次函数的最小值可以通过求导并令其为零来解析求解,得到牛顿法的更新规则:
[
\theta^* = \theta_0 - H^{-1} \nabla L(\theta_0)
]
这个更新规则看起来很像梯度下降(( \theta_{new} = \theta - \alpha \nabla L(\theta) )),只是用逆海森矩阵 ( H^{-1} ) 对梯度进行了变换。
牛顿法的主要问题在于计算成本。对于一个有 ( n ) 个参数的模型,梯度计算复杂度约为 ( O(n) ),而海森矩阵是 ( n \times n ) 的,计算其逆矩阵的复杂度高达 ( O(n^3) )。对于现代大型神经网络(参数可达数十亿),这是不可行的。虽然存在一些技巧可以避免显式构建整个海森矩阵,但这些方法通常非常复杂,在实践中并不常用。

动量:平滑更新方向 ⚡
由于牛顿法不实用,我们需要寻找其他能在保持 ( O(n) ) 计算复杂度的同时加速梯度下降的方法。首先介绍的方法是动量。
动量的直觉来源于物理:如果一个球在碗中滚动,它具有惯性,会平滑掉路径上的振荡。在优化中,如果连续的梯度更新方向不一致(例如在狭窄山谷中振荡),将它们平均起来可以得到一个更指向谷底的方向。如果方向一致,则动量会加速朝该方向前进。

动量法的更新规则如下:
[
\begin{aligned}
g_k &= \nabla L(\theta_k) \
m_k &= \mu m_{k-1} + g_k \quad \text{(其中 } \mu \text{ 是动量系数,如 0.9)} \
\theta_{k+1} &= \theta_k - \alpha m_k
\end{aligned}
]
这里,( m_k ) 是“动量”项,它累积了过去的梯度信息,( \mu ) 决定了保留多少历史信息。这个规则计算简单,只需额外存储一个动量向量 ( m ),几乎不增加计算开销。
动量法是一种启发式改进,没有很强的理论保证。一个更有理论依据的变体是 Nesterov 加速梯度,它在凸优化问题上有更好的收敛保证。但由于本课程聚焦深度学习实践,我们主要使用标准的动量法。
自适应学习率与梯度缩放 📏

梯度下降的另一个问题是不同维度上梯度的尺度可能差异很大。梯度的符号(正负)告诉我们每个维度应该朝哪个方向移动,但其大小(绝对值)提供的信息较少,且在整个优化过程中可能剧烈变化,使得学习率 ( \alpha ) 难以调整。

例如,在使用平方误差损失时,梯度包含 ( (f(x) - y) ) 项。当预测误差很大时,梯度也很大,容易越过最优点;当接近最优时,梯度又变得很小,进展缓慢。
为了解决这个问题,我们希望归一化每个维度上的梯度步长,使其大致处于同一量级。以下是两种常用的方法。
RMSprop
RMSprop(均方根传播)算法估计每个维度上梯度大小的移动平均值,并用它来缩放当前梯度。

以下是 RMSprop 的更新步骤:
- 计算当前梯度 ( g_k = \nabla L(\theta_k) )。
- 更新梯度平方的移动平均 ( s_k ):
[
s_k = \beta s_{k-1} + (1 - \beta) g_k^2
]
(这里 ( g_k^2 ) 表示逐元素平方,( \beta ) 是衰减率,如 0.9)。 - 更新参数:
[
\theta_{k+1} = \theta_k - \frac{\alpha}{\sqrt{s_k + \epsilon}} \cdot g_k
]
(( \epsilon ) 是一个很小的数,如 ( 10^{-8} ),防止除以零)。
这样,每个维度的更新步长都被其历史平均幅度所归一化。
Adagrad
Adagrad 是另一个自适应学习率算法,它与 RMSprop 类似,但累积的是梯度平方的总和,而非移动平均。
以下是 Adagrad 的更新步骤:
- 计算当前梯度 ( g_k = \nabla L(\theta_k) )。
- 累积梯度平方和 ( s_k ):
[
s_k = s_{k-1} + g_k^2
] - 更新参数:
[
\theta_{k+1} = \theta_k - \frac{\alpha}{\sqrt{s_k + \epsilon}} \cdot g_k
]
Adagrad 对凸优化问题有很好的理论保证。然而,对于非凸问题(如神经网络训练),由于 ( s_k ) 随时间单调递增,导致有效学习率持续下降,可能在到达平坦区域后变得过小,从而停止进展。相比之下,RMSprop 因为使用移动平均(会遗忘久远的历史),在非凸问题上通常表现更好。

Adam:结合动量与自适应学习率 🤖
最后,我们介绍 Adam(自适应矩估计)算法,它结合了动量(一阶矩估计)和 RMSprop(二阶矩估计)的思想,是当前深度学习中最流行的优化器之一。
以下是 Adam 算法的完整步骤:
- 计算当前梯度:( g_k = \nabla L(\theta_k) )。
- 更新有偏一阶矩估计(动量):
[
m_k = \beta_1 m_{k-1} + (1 - \beta_1) g_k
] - 更新有偏二阶矩估计(梯度平方的移动平均):
[
v_k = \beta_2 v_{k-1} + (1 - \beta_2) g_k^2
] - 计算偏差校正(为了缓解初始估计值为零带来的冷启动问题):
[
\begin{aligned}
\hat{m}_k &= \frac{m_k}{1 - \beta_1^k} \
\hat{v}_k &= \frac{v_k}{1 - \beta_2^k}
\end{aligned}
] - 更新参数:
[
\theta_{k+1} = \theta_k - \frac{\alpha}{\sqrt{\hat{v}_k} + \epsilon} \cdot \hat{m}_k
]
在实践中,Adam 的常用超参数设置为:学习率 ( \alpha = 0.001 ),( \beta_1 = 0.9 ),( \beta_2 = 0.999 ),( \epsilon = 10^{-8} )。( \beta_2 ) 通常比 ( \beta_1 ) 更接近 1,因为梯度大小的估计需要比梯度方向(动量)更稳定的历史信息。

本节课中我们一起学习了从牛顿法到 Adam 的一系列优化算法。我们了解到,虽然牛顿法提供了理想的方向,但其计算成本过高。因此,实践中我们使用动量来平滑更新方向,并使用如 RMSprop 和 Adam 等算法来自适应地调整每个维度的学习率,从而更高效、更稳定地训练深度神经网络。Adam 因其出色的性能,已成为许多深度学习任务的首选优化器。
课程 P13:CS 182 - 第4讲 - 第3部分 - 优化 🚀
在本节课中,我们将要学习随机优化方法,特别是随机梯度下降及其变体。我们将了解为什么传统的梯度下降方法在处理大规模数据集(如深度学习中的常见情况)时效率低下,并学习如何通过使用数据子集(小批量)来加速优化过程。我们还将讨论如何调整学习率等超参数以获得最佳性能。
从批量梯度下降到随机梯度下降
上一节我们介绍了利用动量和自适应方法(如RMSprop)来加速梯度下降。然而,所有这些版本的梯度下降在实际的深度学习中仍面临挑战。
假设我们使用负对数似然损失函数。计算损失函数通常需要对训练集中的所有数据点求和。在深度学习中,数据集往往非常庞大,例如ImageNet训练集有约150万张图像。如果每次参数更新都需要对所有图像求和,计算将极其耗时,而有效优化神经网络可能需要数百万次梯度更新,这令人望而却步。
我们可以认识到,损失函数实际上是对期望值的基于样本的估计。具体来说,损失总和 L(θ) = Σ L_i(θ) 是对数据分布下期望损失 E[L(θ)] 的一个无偏估计。基于样本的期望估计对于任何数量的样本都是无偏的。使用更少的样本会得到一个噪声更大的估计,但它仍然是无偏的,这意味着如果多次重复此过程并取平均,我们会得到正确的答案。
因此,为了让梯度下降在计算上更快,我们可以简单地每次更新只使用训练集的一个子集。我们选择一个大小为 B 的样本子集(其中 B 远小于总样本数 M),计算这些样本上的平均梯度。当然,如果我们只固定使用这一个子集,就会浪费大部分数据。所以,我们可以在每个梯度步骤都选择一个新的、随机的子集。这样,我们用少量样本估计的梯度会围绕真实梯度波动,并带有一些噪声,但其期望值是正确的。
小批量随机梯度下降的工作原理
以下是小批量随机梯度下降的基本步骤:
- 采样小批量:从整个训练集
D中随机采样一个大小为B的子集B。通常,B可能是32、64或256,远小于总数据量(例如150万)。 - 计算近似梯度:使用这个小批量数据估计损失函数的梯度。公式如下:
g ≈ (1/B) * Σ ∇L_i(θ),其中求和是在小批量B中的所有样本i上进行的。 - 执行参数更新:使用这个近似的梯度
g进行梯度下降更新:θ = θ - η * g,其中η是学习率。
你可以将上一节中介绍的所有技巧(如动量、Adam等)应用到这里,只需用这个近似的梯度 g 替换掉完整的梯度即可。
实践中的高效实现:数据洗牌
理论上,随机梯度下降要求每一步都从数据集中独立随机采样一个新的小批量。然而,在实践中,由于随机内存访问速度较慢,这会破坏缓存一致性,影响效率。
更高效的做法是:预先将整个数据集随机打乱一次。然后,按顺序从这个打乱后的数据集中抽取小批量。例如,第一次取前 B 个样本,第二次取接下来的 B 个样本,依此类推。当遍历完整个数据集后,再从头开始循环。
从技术上讲,这与“每一步都独立随机采样”不完全相同,因为你会在多个周期(Epoch)中以相同的顺序看到数据点。但由于初始顺序是随机的,在实践中效果同样好,且计算效率更高。这就是我们在实际编程项目和深度学习中普遍使用的方法。
学习率的选择与调整
现在我们有了完整的优化方法(如带动量的小批量SGD或Adam),接下来讨论如何调整这些算法的学习率。
在下图中,纵轴代表训练损失,横轴代表周期数。一个周期(Epoch)是指算法完整遍历整个数据集一次。对于随机梯度下降,由于每一步只使用部分数据,完成一个周期需要很多个梯度步骤(总数据量 / 小批量大小)。
- 红色曲线:展示了学习率设置恰当时,训练损失随周期下降的理想情况。
- 绿色曲线:损失下降得非常缓慢。这表明学习率设置得过低。在深度学习中,过低的学习率不仅导致优化慢,还可能使模型陷入较差的局部最优或高原,无法达到好的效果。
- 黄色曲线:初期下降很快,但随后停滞在一个较高的损失值上。这表明学习率设置得过高。过高的学习率会导致参数更新步长过大,在最优解附近反复震荡甚至发散,无法收敛到精细的最优解。

一个自然的想法是:能否在开始时使用较高的学习率快速下降,后期切换到较低的学习率进行精细调整? 答案是肯定的,这种策略被称为学习率衰减。
下图展示了在ImageNet上训练AlexNet时,采用学习率衰减策略的效果(纵轴是准确率,越高越好)。他们定期(例如每训练一段时间)将学习率除以10。可以看到,每次学习率下降后,模型性能(准确率)都能突破之前的平台期,得到进一步提升。

因此,为了获得最佳性能,为带动量的SGD设置一个学习率衰减计划是非常常见的策略。对于Adam等自适应优化器,通常不需要学习率衰减也能工作得很好,但尝试使用也可能带来提升。
超参数调优指南
随机梯度下降及其变体引入了更多需要调优的超参数。以下是调优要点:
以下是需要调整的主要超参数及其一般准则:
- 批量大小
B:较大的批量能提供噪声更小的梯度估计,通常更稳定,可能得到更好的解,但计算成本更高(需要更多内存)。有时受限于硬件(如显存),只能使用较小的批量。一些研究发现在特定任务(如GANs)中,非常大的批量可能带来益处。通常,批量大小的选择是计算效率与梯度估计质量之间的权衡。 - 学习率
η:这是最关键的超参数之一。对于SGD(无动量或带动量),你需要仔细调整。原则是:使用你能使用的最大学习率,只要它不导致训练不稳定(震荡或发散)。对于深度学习,较大的有效学习率通常优于过小的学习率。同时,配合学习率衰减计划往往能取得更好效果。对于Adam,通常可以使用默认学习率(如0.001)作为起点。 - 动量参数
β:对于带动量的SGD,动量系数通常设置为0.9或0.99附近。需要根据具体问题稍作调整。 - Adam的参数
β1,β2:通常保持其默认值(如0.9和0.999)即可,调整它们带来的收益通常不大。
根据什么指标进行调优?
从原理上讲,优化算法的超参数(如学习率、批量大小)主要影响训练损失的优化过程,它们本身不是正则化器。因此,传统上应根据训练损失的下降情况来调整这些参数。
然而,优化和泛化之间存在复杂的关系。最近的研究表明,随机梯度下降本身具有一定的正则化效果。直觉是:当模型开始过拟合时,不同小批量数据计算出的梯度方向会差异很大。这种噪声实际上可能阻止模型过度拟合训练数据,从而提升泛化能力。
因此,在实践中,许多人也会根据验证集损失来调整优化器的超参数。这样做很方便,并且可能间接地帮助找到既利于优化又利于泛化的超参数设置。
算法选择建议
- 如果你刚入门或想快速实现:使用 Adam 优化器。它通常对学习率不那么敏感,更容易调优,是一个不错的默认选择。
- 如果你追求最佳性能并愿意精细调优:考虑使用 带动量的SGD,并为其设计一个学习率衰减计划。许多从业者认为,经过良好调优的带动量SGD能够达到甚至超过Adam的最终性能。


本节课中我们一起学习了随机优化的核心思想。我们了解了为什么需要从小批量数据中估计梯度,掌握了小批量随机梯度下降的算法步骤及其高效实现方式(数据洗牌)。我们深入探讨了学习率的重要性,学习了如何通过观察损失曲线判断学习率设置是否合适,并介绍了学习率衰减策略。最后,我们总结了关键超参数的调优指南,并对不同优化算法的适用场景给出了建议。掌握这些知识将帮助你更有效地训练深度学习模型。

课程5.1:神经网络与反向传播 🧠
在本节课中,我们将学习神经网络的基本工作原理,特别是如何通过计算图来理解和构建它们。我们将从简单的线性回归和逻辑回归模型开始,逐步过渡到更复杂的多层神经网络,并理解其背后的核心概念。
计算图:可视化机器学习程序 📊
上一节我们介绍了机器学习算法本质上是一个带有参数的程序。本节中我们来看看如何用计算图来可视化这个程序。

计算图是一种有向无环图,其中节点代表数学运算。它清晰地展示了数据(如输入 x 和标签 y)与参数(如 θ)如何通过一系列运算最终产生一个标量损失值。这对于理解模型和后续的梯度计算至关重要。
例如,一个简单的线性回归模型可以表示为以下计算图:
- 输入
x1和x2。 - 参数
θ1和θ2。 - 计算
x1 * θ1和x2 * θ2。 - 将两个乘积相加,得到预测值。
- 从预测值中减去真实标签
y。 - 将差值平方,得到均方误差损失。
这个图完整描述了线性回归的均方误差损失计算过程。
向量化表示:简化计算图 🔢
使用标量绘制计算图会显得繁琐。我们可以利用向量和矩阵来简化表示。

对于线性回归,我们可以将输入 x 和参数 θ 都视为向量。计算图中的乘法节点则变为点积运算 x^T θ。这样,整个计算图变得更加简洁,但数学含义完全相同。
在后续的图表中,除非特别说明,x、θ 等变量通常都代表向量或矩阵。
逻辑回归的计算图 🧮
逻辑回归是一种分类算法,其损失函数是负对数似然。让我们画出它的计算图。

逻辑回归的预测由 Softmax 函数作用于线性模型给出。负对数似然损失公式为:
L = - (x^T θ_y) + log( sum_{y‘} exp(x^T θ_{y‘}) )
其中 θ_y 是对应于真实标签 y 的参数向量。
以下是构建该计算图的步骤:
- 将输入
x与每个类别的权重向量θ_{y‘}做点积,得到一个分数向量。 - 将真实标签
y表示为一个独热向量(例如,对于两个类别,标签1表示为[1, 0],标签2表示为[0, 1])。 - 计算分数向量与独热向量的点积,这相当于选取了真实类别对应的分数,得到损失公式的第一部分
x^T θ_y。 - 对分数向量中的每个元素求指数,然后求和,再取对数,得到损失公式的第二部分。
- 将第二部分减去第一部分(注意公式前的负号),得到最终的负对数似然损失。
这个图清晰地展示了逻辑回归如何将输入映射为类别概率并计算损失。
矩阵表示与简化视图 🎯

我们可以用矩阵形式进一步简化逻辑回归的计算图。
将所有权重向量堆叠成矩阵 Θ,那么 Θ * x 就直接得到了所有类别的分数向量。然后应用 Softmax 函数得到概率向量,再与独热标签向量 y 计算点积并取负对数,得到交叉熵损失(即负对数似然)。
基于机器学习模型的共性,我们可以绘制更简洁的计算图:
- 数据 (
x,y):x是输入,在图中向前传播;y是目标,仅出现在损失计算中。 - 参数 (
θ):通常只影响图中某个特定的局部操作(如一个线性变换层)。
因此,我们可以将逻辑回归简洁地画为一个操作链:
x → 线性层 (Θ) → Softmax → 交叉熵损失 (y)。
这消除了图中的冗余信息,突出了核心结构。

神经网络的常见图示 🖼️
在神经网络的研究中,常见的图示方法侧重于展示数据的流动和变换。
一种典型画法是将变量(数据)可视化为方块或层:
- 输入
x作为一个向量。 - 线性层 将
x转换为中间表示z^1。该层包含参数矩阵W^1,但通常在图中不显式画出。 - 激活函数(如 Sigmoid)将
z^1转换为激活值a^1。 - 可能还有更多的线性层和激活函数层。
- 最后是 Softmax 层 和 交叉熵损失。

更简化的研究论文图示会省略损失函数和具体的运算框,仅用带标签的箭头表示层与层之间的变换(如“Linear”、“Sigmoid”)。这种图示与详细的计算图是等价的,只是侧重点不同。
超越逻辑回归:学习特征 🚀
逻辑回归在数据近似线性可分时效果很好。但如果数据复杂(如棋盘格分布),线性模型就无法很好地区分类别。
解决方案是使用特征映射。我们不是直接在原始输入 x 上做逻辑回归,而是在一个转换后的特征空间 φ(x) 中进行,其中 φ(x) 可能包含 x 的高次项、交叉项等。关键问题是如何自动学习这些有用的特征,而不是手动设计。
一个想法是:让每个特征本身都是一个简化逻辑回归模型(二分类)的输出。具体来说,每个特征 φ_i 的计算方式是:
φ_i = σ(w_i^T x)
其中 σ 是 Sigmoid 函数(即二分类 Softmax),w_i 是学习到的权重向量。
如果我们有很多这样的特征,可以用矩阵形式简洁地表示:
φ = σ(W^1 x)
这里 W^1 是一个矩阵,其每一行都是一个权重向量 w_i^T,σ 是逐元素应用的 Sigmoid 函数。

这实际上就是在原始输入 x 之上添加了一个“层”:一个线性变换(W^1 x)后接一个非线性激活函数(σ)。然后,我们在这个学习到的特征 φ 之上,再进行标准的逻辑回归(线性层 + Softmax)。这就构成了一个最简单的神经网络。
激活函数与深度网络 🌊

我们称 σ 这样的非线性函数为激活函数。它的存在至关重要。

如果激活函数是线性的(例如恒等函数 f(x)=x),那么多个线性层的堆叠 W^2 (W^1 x) 可以合并为单个线性层 (W^2 W^1) x,这并没有增加模型的表达能力。而非线性激活函数的引入,使得多层堆叠能够学习输入数据中极其复杂的模式和表示。
因此,神经网络可以看作是在“线性代数千层面”:在矩阵乘法层(线性变换)和非线性激活层之间交替。只要激活函数是非线性的,多层堆叠就是有意义的。

我们可以构建更深的网络,例如:
x → 线性层 → Sigmoid → 线性层 → Sigmoid → 线性层 → Softmax → 损失
每一层都在学习更抽象、更复杂的特征。
演示与名称由来 💡
通过工具演示可以看到,对于线性不可分数据,简单的逻辑回归性能有限,而引入隐藏层(即神经网络)后,模型能够学习到复杂的决策边界,从而有效分类。

最后,解释一下“神经”网络的名称由来。这源于对人脑神经元的极度简化建模:
- 树突:接收来自其他神经元的信号,对应神经网络中上一层的输出。
- 细胞体:对输入信号进行加权求和(
w^T x),对应线性变换。 - 激活:根据求和结果决定是否“放电”及放电强度,对应激活函数(如 Sigmoid)。
- 轴突:将输出信号传递给下游神经元,对应本层的输出。
因此,人工神经网络中的每个单元(神经元)都模拟了这种“接收信号-加权求和-非线性激活-传递信号”的过程。

总结 ✨
本节课中我们一起学习了:
- 如何使用计算图来可视化和理解机器学习模型。
- 如何将逻辑回归表示为计算图,并用向量/矩阵形式进行简化。
- 神经网络的核心思想:通过堆叠线性层和非线性激活函数层来自动学习多层次的特征表示。
- 激活函数必须是非线性的,否则深层网络会退化为浅层网络。
- 神经网络的图示惯例及其与生物神经元的粗略类比。

在接下来的课程中,我们将探讨如何“搅拌”这堆线性代数千层面——即如何使用反向传播算法来高效计算梯度,从而训练这些复杂的神经网络。
课程 P15:CS 182 - 第5讲 - 第2部分 - 反向传播 🧠
在本节课中,我们将学习如何训练多层神经网络。核心是推导并理解反向传播算法,该算法能自动计算神经网络中所有参数的梯度,从而让我们能够使用梯度下降等优化方法高效地训练网络。
概述
在上一节中,我们讨论了如何设计多层神经网络。本节中,我们将探讨如何训练它们,即如何构建一个完整的学习算法。我们将遵循第二课中解决机器学习问题的标准步骤:
- 定义模型类(我们已经定义了神经网络)。
- 定义损失函数(我们沿用负对数似然)。
- 选择优化器(我们将使用随机梯度下降)。

为了将梯度下降应用于神经网络,我们需要计算损失函数相对于每个参数的梯度。虽然理论上可以手动推导这些导数,但对于复杂的网络结构,这非常耗时且容易出错。因此,我们需要一种能自动计算梯度的算法,这就是反向传播。它是几乎所有现代深度学习方法的基石。
链式法则回顾 🔗
神经网络由许多函数组合而成。对复合函数求导,需要用到微积分中的链式法则。
考虑一个简单的复合函数:z = f(g(x))。我们想计算 dz/dx。链式法则告诉我们:
dz/dx = (dz/dy) * (dy/dx)
其中 y = g(x)。

对于多元函数(这是神经网络中的常见情况),链式法则依然适用,但需要小心处理矩阵和向量的维度顺序。
假设 x 是 n 维向量,g: R^n -> R^m,f: R^m -> R(输出标量,如损失)。那么:
dz/dx = (dz/dy) * (dy/dx)
这里:
dz/dy是一个m维的列向量(每个元素是z对y中一个分量的偏导)。dy/dx是一个n x m的雅可比矩阵(第i行第j列是y_j对x_i的偏导)。- 因此,
dz/dx是一个n维的列向量,通过矩阵-向量乘法得到。
本课程约定:函数的导数(梯度)表示为其输入的列向量。这使得我们存储的梯度维度与原始参数维度完全一致,在深度学习中非常方便。
神经网络中的梯度计算 🧮
让我们回顾一个简单的两层神经网络:
- 输入
x - 线性层:
z1 = W1 * x - 激活函数(如Sigmoid):
a1 = σ(z1) - 线性层:
z2 = W2 * a1 - 输出层(Softmax)与交叉熵损失:
L
参数是权重矩阵 W1 和 W2。我们可以直接应用链式法则写出梯度:
-
损失
L对W2的梯度:
dL/dW2 = (dL/dz2) * (dz2/dW2) -
损失
L对W1的梯度(链条更长):
dL/dW1 = (dL/dz2) * (dz2/da1) * (da1/dz1) * (dz1/dW1)
理论上,我们可以计算出每一个雅可比矩阵(如 dz2/da1, da1/dz1),然后把它们乘起来。然而,如果中间变量维度是 n,那么每个雅可比矩阵是 n x n,矩阵乘法是 O(n^3) 的操作,对于大型网络(如AlexNet有4096维的层)来说,计算成本极高。

反向传播的高效直觉 💡
我们注意到一个关键点:损失函数 L 是标量。因此,链条最末端的 dL/dz2 是一个向量,而不是矩阵。
高效算法的直觉是:从右向左计算,避免显式构造和相乘大型雅可比矩阵,而是进行一系列矩阵-向量乘法(O(n^2))。
步骤如下:
- 计算
δ2 = dL/dz2(向量)。 - 计算
dL/dW2 = (dz2/dW2) * δ2(矩阵-向量乘,得到dL/dW2)。 - 将
δ2向左传播:γ1 = (dz2/da1) * δ2(矩阵-向量乘,得到新向量)。 - 继续向左:
δ1 = (da1/dz1) * γ1(通常是对角矩阵乘向量,很高效)。 - 计算
dL/dW1 = (dz1/dW1) * δ1。

这样,每一步都是 O(n^2) 的操作,总成本远低于直接计算 O(n^3) 的矩阵链乘积。
通用反向传播算法 ⚙️
基于以上直觉,我们可以形式化通用的反向传播算法。假设网络有 N 层,每层可视为一个函数 f,其输入为 x_f,参数为 θ_f。
算法步骤:
-
前向传播:运行网络,计算并保存每一层的输入
x_f、输出a_f以及中间变量z_f(对于线性层)。 -
初始化:设
δ为损失L对网络最后一层输出z_N的梯度,即δ = dL/dz_N。 -
反向传播循环(从最后一层到第一层):
对于网络中的每个函数f(按反向顺序):- 计算参数梯度:如果
f有参数θ_f,则计算dL/dθ_f = (df/dθ_f) * δ并保存。这里df/dθ_f是函数输出对其参数的雅可比。 - 更新 δ:计算
δ = (df/dx_f) * δ。这会将δ更新为损失L对当前函数f的输入x_f的梯度,以便用于前一层。 - 循环继续,
δ就像是一个梯度信号,从输出层逐层反向流回输入层。
- 计算参数梯度:如果
算法要点:
δ在循环开始时,总是损失对当前层输出的梯度。- 在计算完当前层参数梯度后,
δ被更新为损失对当前层输入的梯度。 - 对于无参数层(如激活函数),只执行更新
δ的步骤。

算法示例演示 📝
让我们用之前的两层网络来演练:
- 前向传播:计算
z1, a1, z2, L。 - 初始化:
δ = dL/dz2。 - 循环第一轮(最后一层线性层):
- 函数
f:z2 = W2 * a1。参数θ_f = W2,输入x_f = a1。 - 计算参数梯度:
dL/dW2 = (dz2/dW2) * δ。 - 更新
δ:δ = (dz2/da1) * δ。现在δ是dL/da1。
- 函数
- 循环第二轮(Sigmoid激活层):
- 函数
f:a1 = σ(z1)。无参数。 - 更新
δ:δ = (da1/dz1) * δ。现在δ是dL/dz1。
- 函数
- 循环第三轮(第一层线性层):
- 函数
f:z1 = W1 * x。参数θ_f = W1,输入x_f = x。 - 计算参数梯度:
dL/dW1 = (dz1/dW1) * δ。 - 更新
δ:δ = (dz1/dx) * δ(至此,所有参数梯度已计算完毕)。
- 函数
通过这个过程,我们高效地计算出了 dL/dW1 和 dL/dW2,而无需显式构造和相乘整个链条上的大型雅可比矩阵。

总结
本节课中,我们一起学习了反向传播算法的原理与推导。
- 我们首先回顾了链式法则在多元函数中的应用及其矩阵表示。
- 接着,我们分析了直接计算神经网络梯度时面临的计算效率问题(
O(n^3)复杂度)。 - 然后,我们发现了利用损失函数为标量的特性,通过从右向左进行矩阵-向量乘法来高效计算梯度的核心直觉。
- 最后,我们形式化了通用的反向传播算法,它通过前向传播存储中间变量,再反向迭代每一层,计算参数梯度并传播梯度信号,从而以
O(n^2)的复杂度计算出所有参数的梯度。

掌握反向传播是理解深度学习如何工作的关键。有了它,我们就能利用梯度下降自动优化任何神经网络结构,这是训练现代深度模型的基础。

课程 P16:CS 182 第5讲 第3部分 - 反向传播 🧠
在本节课中,我们将学习神经网络的实际实现细节,特别是反向传播算法。我们将讨论激活函数的选择、偏置项的重要性,并详细推导线性层、Sigmoid和ReLU激活函数的反向传播梯度计算。
概述 📋
在上一节中,我们介绍了神经网络的基本结构。本节中,我们将探讨如何实际训练一个神经网络,包括层数、大小、激活函数的选择以及反向传播的具体实现细节。

激活函数的选择 ⚙️
在训练神经网络之前,我们需要确定使用何种激活函数。在第一部分中,我们学习了Sigmoid函数,它适用于将每个特征视为二元逻辑回归问题。然而,Sigmoid并非唯一选择。
一个特别受欢迎的选择是整流线性单元,简称ReLU。ReLU函数的定义是取前一层激活值 z 与零之间的最大值。
公式:
a = max(0, z)
与S形弯曲的Sigmoid函数不同,ReLU在正区间是线性的,在负区间输出为零。起初,这看起来可能不够非线性,但实际上它在实践中表现优异。

以下是ReLU的两个主要优点:
- 计算高效:对于大型神经网络,ReLU能显著提升计算速度。
- 梯度简单:其导数计算非常简单,这进一步提高了计算效率并易于使用。
因此,在实践中,ReLU是目前最流行的激活函数,尽管Sigmoid和Tanh函数有时也会被使用。

偏置项的重要性 ➕
接下来,我们必须讨论神经网络实际实现中的另一个关键细节:偏置项。
之前描述的线性层 z = W * a 存在一个主要限制:如果输入 a 是零向量,那么输出 z 也将永远是零。这意味着这样的神经网络无法表示某些函数,不是一个通用的函数逼近器。
解决方法是引入一个偏置向量 b。这样,线性层变为:
公式:
z = W * a + b
这有时也被称为仿射层。拥有偏置项和非线性激活函数(如Sigmoid)的神经网络,只要层足够大,就可以以任意精度逼近任何函数。没有偏置项,则无法做到这一点。

因此,神经网络的参数通常包括权重矩阵 W1、W2 和偏置向量 b1、b2。这几乎成为线性层的标准配置。
反向传播的实现 🔄
现在,我们已经具备了实现神经网络的大部分要素。为了实际实现反向传播,我们需要能够为网络中的每个函数 f 计算两个关键量:
- 损失函数
L对函数f的参数θ_f的梯度:dL/dθ_f - 损失函数
L对函数f的输入x_f的梯度:dL/dx_f
我们不需要显式构造整个雅可比矩阵,只需实现一个函数来计算雅可比矩阵与上游梯度 δ 的乘积即可。我们需要为线性层、Softmax加交叉熵损失、Sigmoid、ReLU等函数完成此操作。其中,线性层的计算最为复杂,因为它包含可学习的参数。


线性层的反向传播
线性层的表达式为:
公式:
z = W * a + b
其中,输入 x_f = a,参数 θ_f 包含 W 和 b。
我们需要计算三个梯度:
dz/dW * δ(关于权重)dz/db * δ(关于偏置)dz/da * δ(关于输入,用于向后传递)

1. 关于权重 W 的梯度
W 是一个矩阵,dz/dW 是一个三维张量。计算 (dz/dW) * δ 的最终结果是一个与 W 同维的矩阵。通过标量推导,可以得到一个简洁的表达式:
公式:
dL/dW = δ * a^T
这实际上是梯度 δ 与输入激活 a 的转置的外积。
2. 关于偏置 b 的梯度
dz/db 是单位矩阵。因此:
公式:
dL/db = δ
计算非常简单。
3. 关于输入 a 的梯度

dz/da 实际上是权重矩阵 W 的转置。因此:
公式:
dL/da = W^T * δ
这个结果将作为上一层的上游梯度 δ 继续反向传播。

Sigmoid 层的反向传播
Sigmoid函数是逐元素操作,其雅可比矩阵是对角矩阵。单元素的导数为:
公式:
dσ(z)/dz = σ(z) * (1 - σ(z))
因此,对于整个向量,(dσ/dz) * δ 的计算就是逐元素相乘:
公式:
δ_new = δ ⊙ (σ(z) * (1 - σ(z)))
其中 ⊙ 表示逐元素乘法。Sigmoid层没有参数,因此无需计算关于参数的梯度。
ReLU 层的反向传播
ReLU函数也是逐元素操作,其导数更简单:
公式:
dReLU(z)/dz = 1 (如果 z > 0), 否则为 0
因此,反向传播时:
公式:
δ_new = δ ⊙ mask
其中 mask 是一个与 z 同维的向量,在 z > 0 的位置为1,否则为0。

总结 🎯

本节课中,我们一起学习了神经网络实现的核心细节。
- 我们首先讨论了激活函数的选择,指出ReLU因其计算高效和梯度简单的优点成为当前最流行的选择。
- 接着,我们强调了偏置项的重要性,它使神经网络成为通用的函数逼近器。
- 最后,我们深入剖析了反向传播算法的具体实现,推导了线性层、Sigmoid层和ReLU层的梯度计算公式。

反向传播算法从最后一层开始,逐层向前计算损失对每层参数和输入的梯度。对于每个函数,我们都需要计算两个雅可比矩阵与上游梯度的乘积。掌握这些基础组件的梯度计算,是理解和构建复杂神经网络的关键。
🖼️ 课程 P17:卷积神经网络(CS 182 - 第6讲 - 第1部分)

在本节课中,我们将学习如何构建能够处理图像的神经网络,即卷积神经网络。我们将了解为何传统神经网络在处理图像时效率低下,并探索卷积操作如何利用图像的局部特性来大幅减少参数数量,同时提升模型对图像平移等变化的鲁棒性。
传统神经网络处理图像的挑战
上一节我们介绍了神经网络的基本结构。本节中我们来看看,如果直接将上一节课开发的神经网络应用于图像分类任务,会遇到哪些困难。

第一个线性层读取图像并产生第一个激活向量。该层中的权重数量等于第一个隐藏层的激活数 Z 乘以整个图像的像素值数量。

对于一个 128 x 128 x 3 的图像(128像素高,128像素宽,3个颜色通道),输入共有 128 * 128 * 3 = 49,152 个数字。如果第一个隐藏层有64个维度,那么第一个权重矩阵的参数总数将是 64 * 49,152 ≈ 3.15 百万。
这仅仅是第一层,且隐藏层维度很小。在实践中,我们可能需要更多的隐藏单元,导致参数量巨大。此外,还有一个更根本的问题:如果图像中的小狗向左或向右移动哪怕一个像素,对于这个网络来说,输入将变得完全不同,模型难以识别。
利用图像的局部特性
为了解决上述问题,我们观察到许多视觉特征本质上是局部的。


识别一张小狗的照片,我们可能先提取边缘,再识别局部区域如耳朵和鼻子。这些特征都是局部的。判断特定位置是否有边缘,只需要查看附近的像素;识别鼻子可能需要查看稍大的区域,但仍然是局部的。
神经科学研究也表明,哺乳动物大脑的视觉处理初期是局部的,存在空间相干特征探测器。因此,我们可以设计一种神经网络,先进行局部操作以减少参数量,待信息浓缩后再进行全局操作。
核心思想是:判断一个图像块是否包含某个特征,仅查看局部图像块就足够了。
卷积操作:局部特征提取
基于局部性的观察,我们尝试构建一个网络,一次只查看图像的一个小区域(补丁),并且不同位置共享相同的特征检测器。

假设我们使用一个 3x3 的补丁,并且图像有3个颜色通道,那么每个补丁有 3 * 3 * 3 = 27 个数值。如果我们想为此计算64个特征,参数数量仅为 64 * 27 = 1,728。这与之前全连接层的300多万参数相比,是巨大的减少。
这个小网络(也称为过滤器或卷积核)会被滑动应用到图像的每一个 3x3 补丁上(补丁之间通常重叠)。对于每个位置,它都会输出一个特征向量(例如长度为64)。这样,我们就把原始的 128x128x3 图像,转换成了一个 128x128x64 的特征图。
公式描述:对于一个二维卷积操作,输出特征图在位置 (i, j) 的值由以下公式计算:
output[i, j] = sum( input[i+m, j+n] * filter[m, n] ) + bias
其中求和遍历过滤器 filter 的所有位置 (m, n)。
在卷积操作之后,我们必须像在常规神经网络中一样,应用一个非线性激活函数(如ReLU)。
池化操作:下采样与特征强化
卷积减少了参数,但特征图的大小(分辨率)仍然和原始图像一样大,甚至深度(特征数量)更多。为了进一步缩减数据量并增强特征的鲁棒性,我们引入池化操作。
最常见的是最大池化。我们以 2x2 最大池化为例:将特征图划分为不重叠的 2x2 区域,对每个区域中的每个通道,取该区域内所有激活值的最大值。
为什么最大池化有效? 如果特征图中的激活值表示某个特征存在的程度,那么取一个区域内的最大值,就相当于判断“这个特征是否在该区域的任何地方出现”。这使网络对特征的小幅平移更加不敏感。
经过 2x2 最大池化后,特征图的高度和宽度会减半(例如从 128x128 变为 64x64),但深度(通道数)保持不变。
构建卷积神经网络架构
现在我们可以组合这些操作来构建一个完整的卷积神经网络。
一个典型的顺序是:
- 卷积层 + 非线性激活
- 池化层(通常是最大池化)
- 重复步骤1和2多次
- 将最后的特征图展平成一个一维向量
- 连接一个或多个全连接层(就像传统的神经网络)进行最终分类或回归。
关键参数:
- 过滤器大小:如
3x3,5x5。通常使用奇数尺寸。 - 过滤器数量:决定输出特征图的深度。
- 池化大小:如
2x2,4x4。 - 步长:过滤器每次滑动的像素数。
- 填充:处理图像边缘时,通过在边缘补零来保持输出尺寸。
实例:LeNet-5网络
让我们看一个经典的卷积神经网络实例——LeNet-5,它用于手写数字识别。


- 输入:
32x32的手写数字图像。 - C1层:卷积层,使用6个
5x5的过滤器,得到6个28x28的特征图。(尺寸变小是因为卷积未使用填充)。 - S2层:池化层(子采样),
2x2最大池化,得到6个14x14的特征图。 - C3层:卷积层,使用16个
5x5的过滤器,得到16个10x10的特征图。 - S4层:池化层,
2x2最大池化,得到16个5x5的特征图。 - 展平:将16个
5x5的特征图展平为16*5*5=400个神经元。 - 全连接层:连接传统的全连接层进行最终10个数字类别的分类。
重要提醒:在每一个卷积层(C1, C3)之后,实际上都跟着一个非线性激活函数(如Sigmoid或ReLU),图中虽未明确画出,但这是架构的必需部分。
总结
本节课中我们一起学习了卷积神经网络的核心概念:
- 动机:全连接网络处理图像参数量巨大且对平移敏感。
- 核心思想:利用图像的局部性,使用共享权重的过滤器在图像上滑动进行特征提取。
- 卷积层:执行局部特征检测,后接非线性激活函数。
- 池化层(尤其是最大池化):对特征图进行下采样,减少数据量并增强平移不变性。
- 典型架构:交替堆叠卷积层、激活函数和池化层,最后连接全连接层。
- 实例:通过LeNet-5网络了解了卷积神经网络的实际组成和工作流程。

通过这种设计,卷积神经网络能够以更少的参数、更高的效率有效地处理图像数据,并成为计算机视觉领域的基石。
课程 P18:CS 182 讲座 6 - 第 2 部分 - 卷积网络 🧠
在本节课中,我们将学习卷积神经网络(CNN)的核心实现细节。我们将从卷积的基本思想回顾开始,然后深入探讨如何用数学公式和代码描述卷积操作,最后讨论填充(Padding)和步长(Stride)等关键概念。
卷积网络回顾 🔄

上一节我们介绍了卷积网络的基本思想。本节中,我们来看看如何具体实现这些操作。
卷积层是一种避免图像处理需要数百万参数的方法。它也能更有效地学习,因为在一个位置有用的特征,在图像的其他位置也可能有用。我们构建一个小型过滤器,并将其滑动到输入图像的每个位置。因此,每一层都是局部的,因为每个过滤器只关注一个小区域,但它会查看每一个这样的区域,从而在下一个激活图中产生一个不同的点。
每一层都会产生一个可以视为图像的三维体积,其宽度和高度大致与前一层相同,但通道数等于过滤器数量。然后,在每个位置独立地应用激活函数或非线性变换。
如果你想降低这些层的分辨率(例如,为了最终得到一个单一的输出),你需要进行池化操作。最常见的池化类型是最大池化,它对每一层的激活图进行采样,在每个不重叠的区域中取最大值。这使得网络对微小的平移变化具有鲁棒性。

最后,在通过卷积、非线性和池化将分辨率降低到足够小之后,你需要将其转换为一个全连接层。最终,你会得到一个足够小的三维体积,可以将其展平为一个向量,然后送入标准的全连接线性层。
这是卷积神经网络背后的基本思想。现在,让我们来谈谈如何真正实现这一切。
张量与多维数组 📦
我们需要使用n维数组,也称为张量。在深度学习中,我们通常将张量简化为n维数组。例如:
- 输入图像 是一个三维数组,维度为:
[高度, 宽度, 通道数]。 - 过滤器 是一个四维数组,维度为:
[过滤器高度, 过滤器宽度, 输出通道数, 输入通道数]。 - 激活图 通常也是三维的:
[高度, 宽度, 通道数]。

这与标准神经网络不同,标准神经网络中的权重是二维矩阵,激活是一维向量。在卷积网络中,过滤器总是比激活图多一个维度。
对于卷积操作,高度和宽度维度并不直接匹配。卷积是在每个位置执行一个微小的矩阵乘法,就像每个位置的一个微小的线性层,然后将这个操作滑动到每个位置。
卷积运算的数学描述 🧮
让我们用公式来描述卷积运算。假设我们有一个输入激活图 A1,其维度为 [H_in, W_in, C_in]。我们想通过卷积得到 Z2,其维度为 [H_out, W_out, C_out]。权重 W2 是一个四维张量,维度为 [H_f, W_f, C_out, C_in]。
输出特征图 Z2 在位置 (i, j, k) 的值由以下公式计算:
公式 1:逐元素求和形式
Z2[i, j, k] = Σ_{l=0}^{H_f-1} Σ_{m=0}^{W_f-1} Σ_{n=0}^{C_in-1} ( W2[l, m, k, n] * A1[i + l - (H_f-1)/2, j + m - (W_f-1)/2, n] )
其中,i + l - (H_f-1)/2 和 j + m - (W_f-1)/2 确保了过滤器以 (i, j) 为中心进行滑动。这看起来有些复杂,主要是为了精确表达索引关系。
公式 2:向量化形式(更清晰)
我们也可以用矩阵向量乘法来表示每个位置的操作:
Z2[i, j] = Σ_{l=0}^{H_f-1} Σ_{m=0}^{W_f-1} ( W2[l, m] * A1[i + l - (H_f-1)/2, j + m - (W_f-1)/2] )
这里,Z2[i, j] 是一个长度为 C_out 的向量,W2[l, m] 是一个 C_out x C_in 的矩阵,A1[...] 是一个长度为 C_in 的向量。这个形式更清楚地表明,卷积是在每个位置执行一个微小的线性层(矩阵乘法)。
完成卷积得到 Z2 后,别忘了应用非线性激活函数(如 ReLU)来得到 A2。
在实践中,这些操作通常使用高效的矩阵乘法在 GPU 上实现,因为虽然参数不多,但计算量非常大(需要在每个位置重复计算)。

边界处理:填充(Padding)⚙️
到目前为止,我们巧妙地避开了边界问题。当过滤器滑动到图像边缘时,部分索引可能落在图像之外。有两种主要的处理方法:
1. 有效卷积(Valid Convolution)
这种方法直接剪掉边缘。不允许过滤器在索引无效(为负数或超出图像宽度)的位置进行计算。
- 结果:输出特征图的尺寸会缩小。
- 尺寸计算:如果输入尺寸为
(H_in, W_in),过滤器尺寸为(H_f, W_f),则输出尺寸为:
例如,H_out = H_in - (H_f - 1) W_out = W_in - (W_f - 1)32x32的输入经过5x5的过滤器后,会得到28x28的输出。这种收缩在深层网络中可能很明显。
2. 零填充(Zero Padding)
这种方法在图像边界外围填充零,使得过滤器可以在每个位置(包括角落)进行计算。
- 结果:输出特征图的尺寸得以保持(如果填充足够)。
- 常用策略:为了保持输入输出尺寸相同,通常设置填充大小
P = (F-1)/2(当过滤器尺寸F为奇数时)。 - 注意:由于填充的是零,通常需要先将输入图像进行归一化(例如减去均值),使得零值代表平均强度,这样效果更好。
零填充因其概念简单且能保持空间维度而非常流行。
降低计算成本:步长(Stride)🏃

标准的卷积层结构计算成本可能很高,因为需要在输入图像的每个位置评估过滤器。为了降低计算量,可以引入步长卷积。
步长(Stride) 指的是过滤器每次滑动时跳过的像素数。默认步长为 1(每个位置都计算)。如果步长为 2,则过滤器每次移动 2 个像素,从而跳过中间的位置。
- 与池化的区别:池化是在每个位置计算后,再对区域取最大值。而步长卷积是直接跳过某些位置不计算。
- 优点:步长卷积能显著减少计算量,尤其是在网络早期激活图分辨率很大的时候。
- 输出尺寸:如果输入尺寸为
(H_in, W_in),过滤器尺寸为(H_f, W_f),步长为S,填充为P,则输出尺寸为:H_out = floor( (H_in + 2P - H_f) / S ) + 1 W_out = floor( (W_in + 2P - W_f) / S ) + 1

步长卷积是实现快速神经网络的一个重要工具。
总结 📝
本节课中,我们一起学习了卷积神经网络的核心实现细节:
- 核心数据结构:我们使用张量(多维数组)来表示图像、过滤器和激活图。
- 卷积运算:其本质是在输入特征图的每个位置,进行一个微小的矩阵乘法(线性变换),然后滑动到所有位置。我们用数学公式清晰地描述了这一过程。
- 边界处理:我们探讨了处理图像边缘的两种方法——有效卷积(会缩小尺寸)和零填充(能保持尺寸)。
- 步长:为了降低计算成本,我们引入了步长卷积,通过跳过一些位置来高效地降低特征图的空间分辨率。

理解这些实现细节,是构建和优化高效卷积神经网络的基础。下一部分,我们将可能看到这些概念在实际网络架构中的应用。

课程 P19:CS 182 - 第6讲 - 第3部分 - 卷积网络 🧠

在本节课中,我们将通过几个实际的卷积神经网络(CNN)模型,来了解之前学习的组件是如何在计算机视觉任务中应用的。我们将重点分析三个里程碑式的网络:AlexNet、VGGNet 和 ResNet,理解它们的设计思路、演变过程以及为何能取得突破性成果。

AlexNet:深度学习的里程碑 🏆
上一节我们介绍了卷积网络的基本组件。本节中,我们来看看第一个在大型视觉识别挑战中取得突破性成果的网络——AlexNet。
AlexNet 由 Alex Krizhevsky 等人在 2012 年提出,并在 ImageNet 大规模视觉识别挑战赛(ILSVRC)中首次击败了传统的浅层学习方法。这是一个标志性的事件,因为它证明了深度神经网络在处理大规模、复杂视觉任务上的巨大潜力。
ImageNet 数据集之所以重要,原因如下:
- 规模巨大:训练集包含约150万张图像,远超当时仅包含数万张图像的数据集。
- 任务困难:包含1000个视觉类别,且图像内容复杂、背景杂乱,更贴近真实世界场景。
以下是 AlexNet 的核心架构设计:

- 输入层:接收 224x224x3 的 RGB 图像。
- 卷积层 1 (Conv1):使用 96 个 11x11 的过滤器,步幅 (stride) 为 4,无零填充。公式表示为:
Conv(11x11, stride=4, filters=96)。此操作将图像尺寸大幅缩减。 - 池化层 1:使用 3x3 的最大池化,步幅为 2,这是一个重叠池化。
- 局部响应归一化 (LRN):对局部区域激活值进行归一化(现已不常用)。
- 卷积层 2 (Conv2):使用 256 个 5x5 的过滤器,步幅为 1。
- 池化层 2:再次使用 3x3 最大池化,步幅为 2。
- 后续卷积层 (Conv3, Conv4, Conv5):均使用 3x3 的过滤器,数量分别为 384, 384, 256,并采用零填充以保持空间尺寸。
- 池化层 3:3x3 最大池化,步幅为 2,将特征图尺寸降至 6x6x256。
- 全连接层:将 6x6x256 的特征图展平为长度为 9216 的向量,随后连接两个大小为 4096 的全连接层。
- 输出层:最后一个全连接层输出 1000 个值(对应1000个类别),后接 Softmax 函数产生分类概率。
AlexNet 在每个卷积层和全连接层(除输出层外)后都使用了 ReLU 激活函数。其设计模式清晰:随着网络加深,空间分辨率降低,特征通道数增加;最后通过全连接层整合信息并分类。虽然其层数(8层)在今天看来不算深,但它奠定了现代CNN的基础结构。
VGGNet:模块化与深度化 🧱
AlexNet 展示了深度网络的有效性,但其设计较为特设化。接下来,我们看看如何通过模块化设计来构建更深的网络——VGGNet。

VGGNet 的核心思想是使用小型卷积核(3x3)的堆叠来替代大型卷积核,并通过重复简单的构建块来增加网络深度。这种设计带来了更好的非线性表达能力,且参数更少。

以下是 VGGNet(以 VGG-16 为例)的架构特点:

- 持续使用小卷积核:全部使用 3x3 的卷积过滤器,步幅为 1,并配合 1 的零填充以保持分辨率。两个连续的 3x3 卷积层其感受野相当于一个 5x5 卷积层,但参数更少、非线性更强。
- 清晰的阶段划分:网络由多个阶段组成,每个阶段结束后通过一个 2x2、步幅为2的最大池化层将空间尺寸减半。
- 通道数翻倍:每当空间尺寸减半后,下一阶段的卷积通道数会翻倍(从64到128,到256,最后到512),以保留更多抽象信息。
- 深度全连接层:在卷积阶段结束后,特征图被展平并送入3个全连接层(两个4096维,一个1000维)。
VGGNet 的模式非常规整:[Conv-Conv-Pool] -> [Conv-Conv-Pool] -> [Conv-Conv-Conv-Pool] -> [Conv-Conv-Conv-Pool] -> [Conv-Conv-Conv-Pool] -> FC -> FC -> FC。这种模块化、同质化的设计使得构建和修改深度网络变得更加容易。然而,其参数量主要集中在末端的全连接层,这在一定程度上是低效的。

ResNet:残差学习与极深网络 🚀
VGGNet 将网络深度推向了十几层,但人们发现,简单地堆叠更多层数,网络性能反而会下降。这被称为退化问题。ResNet 通过引入“残差块”巧妙地解决了这个问题,使得训练数百甚至上千层的网络成为可能。
ResNet 的核心创新是残差连接。在传统的层叠中,每一层直接学习一个目标映射 H(x)。而在残差块中,让层学习残差映射 F(x) = H(x) - x,那么原始映射就变成了 H(x) = F(x) + x。

一个基本的残差块结构如下(以两个3x3卷积层为例):
# 伪代码表示一个残差块
def residual_block(x):
identity = x # 保留输入
out = Conv3x3(x)
out = ReLU(out)
out = Conv3x3(out)
out = out + identity # 关键步骤:添加跳跃连接
out = ReLU(out)
return out
跳跃连接(即公式中的 + x)使得梯度在反向传播时可以直接流过,缓解了梯度消失或爆炸问题,让超深网络的训练变得可行。

ResNet 的整体架构也做了优化:
- 末端使用全局平均池化:取代 VGG 中巨大的全连接层,ResNet 在最后一个卷积层后直接进行全局平均池化,将每个特征通道的所有值平均,得到一个固定长度的向量。这大幅减少了参数数量。
- 下采样:通过卷积步幅为2或专门的池化层来降低分辨率,同时通过1x1卷积来增加通道数。
通过堆叠大量残差块,ResNet 可以轻松扩展到152层(ResNet-152)甚至更深,并在 ImageNet 上取得了超越人类水平的识别精度。残差学习的思想已成为构建现代深度神经网络的基石。

总结 📝

本节课我们一起学习了卷积神经网络发展史上的三个关键模型:
- AlexNet:证明了深度CNN在复杂视觉任务上的强大能力,奠定了现代CNN的基本结构。
- VGGNet:通过堆叠小卷积核和模块化设计,使网络构建更规整,深度进一步增加。
- ResNet:引入残差学习和跳跃连接,从根本上解决了超深网络的训练难题,将网络深度和性能提升到了新的高度。


从 AlexNet 的特设化设计,到 VGGNet 的模块化,再到 ResNet 的残差连接,我们看到网络架构设计越来越注重梯度流动的稳定性和参数计算的效率。这些核心思想至今仍在深刻地影响着深度学习模型的设计。

🧠 课程 P2:CS 182 - 深度学习导论(第1讲,第2部分)

在本节课中,我们将学习深度学习的基本概念、课程概述以及核心的机器学习思想。我们将从课程内容介绍开始,逐步深入到机器学习和深度学习的定义与区别。





📚 课程概述与政策


上一部分我们介绍了课程的基本信息,本节中我们将详细说明课程内容、作业安排以及相关政策。

课程内容简介
本课程旨在介绍深度学习领域的现状。我们将涵盖以下主题:
- 神经网络架构
- 优化算法
- 自然语言处理与计算机视觉中的应用
- 强化学习
- 高级主题,如迁移学习或元学习
作业与考核
以下是本课程的主要考核组成部分:
- 四次编程作业:需要编写并运行代码,实现神经网络基础、卷积网络、循环神经网络、自然语言处理及强化学习相关任务。
- 两次期中考试:具体形式待定,可能为线上考试。
- 一个期末项目:这是课程的重要组成部分,占总成绩比重较大。本科生需从指定主题中选择,研究生项目则更具开放性。
评分政策
课程成绩构成如下:
- 期中考试:30%(每次占15%)
- 编程作业:40%(每次占10%)
- 期末项目:30%
课程先决条件
为了顺利完成本课程,你需要具备以下知识:
- 数学基础:扎实的微积分和线性代数知识,特别是多元导数和矩阵运算。
- 概率论:熟悉连续随机变量、高斯分布等概念。推荐学习过CS 189或具备同等统计学背景。
- 编程能力:能够熟练使用Python进行编程。
如果你对先决条件不确定,可以在课程初期通过尝试作业来进行自我评估。
🤖 什么是机器学习与深度学习?
在了解了课程安排后,我们现在进入核心概念部分。本节我们将探讨机器学习和深度学习的基本思想。
机器学习的基本问题
考虑一个基础问题:编写一个程序,输入一张图片,输出其中是否包含小狗。直接编写规则(如“有两只耳朵、两只眼睛”)非常困难,因为“耳朵”、“眼睛”在像素层面难以定义,且规则充满例外。
因此,关键思想是:当输入到输出的映射规则复杂且充满特殊情况时,提供数据示例比手动编写规则更容易。机器学习正是适用于此类情况的方法。
机器学习的数学表示

以一个更简单的二维点分类问题(标记为X或O)为例。我们可以用一个带参数的函数来表示这个“程序”:
公式:f_θ(x) = sign(θ_1 * x_1 + θ_2 * x_2 + θ_3)

其中,x 是输入(点的坐标),θ 是参数,f_θ(x) 输出分类结果。学习的过程就是找到正确的参数 θ,使得函数对训练数据给出正确答案。
一般情况下,我们有一个参数化函数:y = f(x; θ) 或写作 f_θ(x) = y。θ 是学习的参数,f 可以是任意复杂的表达式。
从浅层学习到深度学习
对于像识别小狗这样复杂的问题,简单的线性函数(如上述公式)无法胜任。传统方法(可称为“浅层学习”或基于特征的学习)分两步:
- 使用手工设计的固定函数从原始输入
x中提取特征φ(x)(例如,计算机视觉中的边缘特征)。 - 在这些特征上使用学习到的简单模型(如线性分类器)进行预测。

这种方法的问题是,设计好的特征非常困难。

深度学习的核心思想是:让特征 φ(x) 本身也包含可学习的参数。并且,我们可以堆叠多层这样的学习转换。
深度学习是具有多层学习表示的机器学习。每一层都将前一层的输出转换为更高级、更抽象的表示。例如,在图像识别中,底层可能学习到边缘,中层学习到部件(如眼睛、耳朵),高层学习到整个物体(如小狗)。这些高级表示对无关变量(如光照、背景)更不敏感,从而使最终预测更容易、更鲁棒。
通常,这个多层参数化函数就是一个深度神经网络。当所有层的参数都为了最终任务目标(如准确分类)而共同学习时,这被称为端到端学习。

🎯 总结
本节课中,我们一起学习了以下内容:
- 课程概述:了解了CS 182/282课程将涵盖的核心主题、作业与考核方式以及相关的课程政策。
- 机器学习动机:理解了当规则复杂且充满例外时,从数据中学习比手动编程更有效。
- 基本框架:认识了参数化函数
f_θ(x)的概念,其中θ是学习的关键。 - 深度学习核心:掌握了深度学习通过堆叠多层可学习的转换,自动从数据中学习层次化、抽象的特征表示,从而实现端到端学习。


通过本讲,你应该对深度学习课程的全貌以及其试图解决的根本问题有了初步的认识。在接下来的课程中,我们将深入这些概念的技术细节。
课程 P20:CS 182 第七讲 第一部分 - 初始化与批归一化 🧠

在本节课中,我们将学习如何让神经网络更好地训练。我们将讨论输入/输出标准化、激活归一化、批归一化技术、权重初始化方法,以及如何获得表现良好的梯度。这些技巧对于成功训练神经网络至关重要。
为什么训练神经网络会遇到困难?🤔

上一节我们介绍了神经网络训练的基本流程。本节中我们来看看为什么即使正确实现了所有步骤,训练过程也可能不顺利。
神经网络优化过程本身存在问题,其优化空间复杂且充满挑战。神经网络训练需要大量技巧,了解这些技巧与理解理论细节同等重要。
输入标准化:解决尺度差异问题 📏
如果我们的输入数据在不同维度上具有非常不同的尺度,这会给神经网络优化带来巨大困难。让我们通过一个例子来理解这个问题。
假设我们有一个二维输入的神经网络。在某些情况下,第一个维度的数值范围和大小可能远大于第二个维度。这种尺度差异在现实数据中很常见,例如将距离(公里)和速度(米/秒)组合在一起时。

尺度差异如何影响梯度?
为了理解为什么尺度差异会造成困难,让我们考虑第一线性层权重 W1 的梯度。根据链式法则和反向传播,损失 L 对 W1 的导数可以表示为:
dL/dW1 = δ * x^T
其中 δ 是反向传播的向量,x 是该层的输入。当 x 的不同维度尺度差异很大时,导数 δ * x^T 中不同项的大小也会差异巨大。这意味着权重矩阵不同维度的梯度大小不一。

- 在简单情况下:
x各维度尺度相近,梯度方向大致指向局部最优。 - 在困难情况下:梯度方向可能并不指向最优解,导致优化过程需要很多小步,效率低下。
因此,我们希望输入中的所有条目都大致处于相同的比例。
何时需要标准化?
- 图像数据:像素值通常在固定范围(如0-1或0-255),尺度一致,通常无需担心。
- 离散输入(如NLP):使用 one-hot 向量,值为0或1,尺度一致,不是问题。
- 实值输入:当不同特征(如温度、湿度百分比)尺度差异巨大时,标准化至关重要。
如何进行标准化?

标准化(或归一化)是指将输入数据转换为均值为0、标准差为1的分布。这种转换不改变数据包含的信息。
对于数据集中的每个数据点 x_i,我们进行如下转换:

x_i' = (x_i - E[x]) / sqrt(E[(x_i - E[x])^2])
其中 E[x] 是数据集中 x 的均值,分母是 x 的标准差。此操作按维度独立进行。
标准化可以防止因尺度差异导致的梯度条件恶劣问题,并使网络平等对待所有输入特征。对于回归问题,对输出进行标准化也是一个好主意。
批归一化:标准化中间层激活 🔄
上一节我们讨论了输入标准化。本节中我们来看看如何将标准化的思想应用到网络中间层的激活上。
即使输入尺度一致,网络中间层的激活也可能出现尺度差异。我们可以尝试标准化这些激活。但问题是,激活的均值和标准差会在训练过程中随着网络参数的变化而改变。

批归一化的基本思想
基本思想很直接:在每一层(例如线性变换和ReLU之后)计算该层激活的均值和标准差,然后用它们来标准化激活。

假设第一层激活为 z = ReLU(W1 * x + b1)。我们计算该批次数据激活的均值 μ 和标准差 σ,然后对每个数据点的激活进行转换:
z'_i = (z_i - μ) / σ
从“朴素版本”到“批”归一化
“朴素版本”的问题是,每次网络参数更新后,都需要在整个数据集上重新计算 μ 和 σ,这非常昂贵。
批归一化的关键改进是:仅使用当前小批量(mini-batch)的数据来估计 μ 和 σ。这就是它名称的由来。

假设一个批次有 B 个样本,索引为 i1 到 iB,则批次均值 μ_B 和批次标准差 σ_B 计算如下:
μ_B = (1/B) * Σ_{k=i1}^{iB} z_k
σ_B = sqrt((1/B) * Σ_{k=i1}^{iB} (z_k - μ_B)^2)
批归一化层的完整形式
在实际实现中,批归一化层通常还会引入两个可学习的参数:缩放参数 γ 和偏移参数 β。标准化后的输出会经过一个仿射变换:
output = γ * z' + β
这样,网络可以学习是否以及如何恢复原始的激活分布。γ 和 β 是与激活维度相同的向量。

简而言之,批归一化层接收输入激活,计算当前批次的 μ 和 σ 进行标准化,然后应用可学习的缩放和偏移。
如何训练带有批归一化的网络?
像训练普通网络一样使用反向传播。你需要为批归一化层计算关于其输入 z 以及参数 γ 和 β 的梯度。这些计算涉及一些微积分,但原理是直接的。

批归一化的实践细节与放置位置 ⚙️
现在我们已经理解了批归一化的原理。本节中我们来看看在实践中如何使用它,特别是应该把它放在网络的什么位置。

批归一化层应该放在哪里?
通常,我们在每个线性层(或卷积层)之后都放置一个批归一化层。但一个关键的选择是:放在非线性激活函数之前还是之后?
以下是两种常见选择:
- 线性层 -> 非线性激活(如ReLU)-> 批归一化
- 线性层 -> 批归一化 -> 非线性激活(如ReLU)

原版批归一化论文建议放在非线性激活之前。但两种方式在实践中都有使用且效果良好。最好的方法是进行实验,看看哪种在你的任务上表现更好。
使用批归一化的好处
以下是使用批归一化的一些主要优势:
- 允许使用更大的学习率:因为它改善了梯度的条件数,使优化景观更平滑。
- 加速训练:模型通常能更快地达到更高的精度。
- 减少对某些正则化技术的依赖:例如,使用批归一化后,可能不需要那么强的 Dropout。
训练与部署时的注意事项
- 训练时:使用当前小批次的统计数据
μ_B和σ_B。 - 部署/测试时:我们不再有“批次”。标准的做法是:在训练完成后,在整个训练集上计算每个批归一化层固定的
μ和σ(通常是运行均值和方差),并在测试时使用这些固定值。 - 参数折叠:在部署时,理论上可以将批归一化层(及其固定的
μ,σ,γ,β)的参数“折叠”进前一个线性层,从而移除批归一化层,简化网络结构。
总结 📝
本节课中我们一起学习了改善神经网络训练效果的关键技巧。
我们首先探讨了输入标准化的重要性,它解决了不同特征尺度差异导致的梯度问题。接着,我们深入学习了批归一化这一强大技术,它通过标准化每一层的激活,使中间层的梯度也保持良好的行为,从而允许使用更大的学习率、加速训练并提升模型稳定性。我们还讨论了批归一化的不同放置位置及其在训练与部署时的实践细节。

掌握这些初始化与归一化技术,是成功训练深度神经网络的重要基础。在接下来的课程中,我们将继续探讨其他优化技巧和最佳实践。
课程 P21:CS 182 第7讲 第2部分 - 初始化与批归一化 🧠

在本节课中,我们将学习神经网络训练中的两个关键主题:权重初始化和批归一化。我们将首先探讨为什么初始化至关重要,并介绍几种确保网络激活值规模稳定的方法。接着,我们会讨论更高级的初始化技术,并了解当训练出现问题时,如何通过梯度裁剪进行补救。
权重初始化的重要性
上一节我们讨论了网络架构,本节中我们来看看如何为网络设置合适的初始权重。选择初始化方案的主要目标是确保网络中的梯度行为合理,这可以通过确保各层激活值保持大致相同的规模来实现。
我们希望网络中激活的整体规模不会随着层数的增加而变得过大或过小。因此,我们将讨论一些基本的初始化方法,以确保激活值处于合理范围,并且其规模不会随网络加深而增长或收缩。

梯度与雅可比矩阵

提醒一下,我们可以将某一层权重关于损失的导数,表示为该层与损失之间所有函数雅可比矩阵的乘积。具体公式如下:
[
\frac{\partial L}{\partial W^{(l)}} = J_1 \cdot J_2 \cdot ... \cdot J_k
]
这里我们并不太关心每个雅可比矩阵 ( J ) 的具体形式,可以将其视为一系列矩阵相乘。正如我们之前讨论的,如果将许多小于1的数相乘,其乘积会趋近于零;如果大于1,则会趋向无穷大。只有当它们都接近1时,乘积才会保持在一个合理的范围内。对于矩阵而言,这意味着其特征值应接近1。
因此,更高级的初始化方法会尝试初始化网络,使得这些雅可比矩阵的特征值接近1。但首先,我们将讨论基本的初始化方法,这些方法仅试图保持各层激活值的规模大致相同。在大多数情况下,这已经足够好了。
简单的初始化及其问题
如果我们有一个“精灵”能直接为我们初始化到全局最优解,那将是最理想的情况。但我们并不知道最优解在哪里。因此,在没有“精灵”的情况下,我们不会试图将权重初始化为接近某个好的解,而是尝试在初始化时,让神经网络的梯度质量较高,这意味着梯度实际上指向一个优化的方向。
一个非常简单的选择是将权重初始化为小的随机数,例如从均值为0、标准差为0.01的正态分布中采样。您不应将所有权重初始化为0,因为那样所有梯度都将为零。
对于小型网络,这种小随机数初始化确实有效。但随着网络加深,这会很快导致问题。想象一下,您的输入大致在尺度1上,将其乘以标准差为0.01的权重,您会得到尺度约为0.01的数字。经过非线性激活函数后,可能保持相似尺度。进入下一层时,再次乘以约0.01的权重,最终得到尺度约为0.0001的数字。如此,您的激活值将呈指数级下降。
下图实验展示了一个10层网络的初始化情况:
- 蓝色曲线:每层激活值的平均值。虽然平均值很快归零(这没问题),但...
- 红色曲线:每层激活值的标准差。输入的标准差约为1,但随着层数增加,标准差急剧下降至接近零。
- 底部直方图:每层的激活值分布。第一层具有健康的分布,但到了第四、五层之后,激活值几乎没有变化,基本都变成了零。

为什么这很糟糕?回顾线性层的梯度公式:梯度 = δ * (该层激活值的转置)。如果激活值为零,那么梯度也为零。梯度为零意味着无法进行任何学习更新。这对应于优化地形中的一个巨大“高原”,是我们最不希望网络初始化的地方。
设计更好的初始化方案
下面我们考虑如何设计一个更好的初始化方案。假设我们将初始化网络中的某个线性层权重 ( W )。我们将权重矩阵中的每个元素 ( W_{i,j} ) 初始化为均值为0、方差为 ( \sigma_w^2 ) 的高斯分布。我们的目标是找出应该将 ( \sigma_w ) 设置为何值。
我们可以问的一个问题是:给定 ( \sigma_w ) 的值,经过这个线性层后,输出 ( z ) 的大小大致是多少?( z ) 中第 ( i ) 项的公式为:
[
z_i = \sum_j W_{i,j} \cdot a_j + b_i
]
我们假设偏置 ( b ) 初始化为0(这通常没问题),并且前一层的激活值 ( a_j ) 本身大致服从均值为0、方差为 ( \sigma_a^2 ) 的高斯分布(这是一个合理的粗略模型,尤其当输入被标准化后)。
如果均值都为零,那么 ( z ) 的大小基本上是其标准差的平方根。计算 ( z_i ) 的方差(因为均值为0,方差即 ( E[z_i^2] )):
[
\text{Var}(z_i) = \sum_j \text{Var}(W_{i,j}) \cdot \text{Var}(a_j) = d_a \cdot \sigma_w^2 \cdot \sigma_a^2
]
其中 ( d_a ) 是输入向量的维度(即对 ( j ) 求和的数量)。因此,( z ) 中每个元素的方差大致为 ( d_a \sigma_w^2 \sigma_a^2 )。
由此,我们得到了关键洞察:如果前一层的激活方差为 ( \sigma_a^2 ),那么下一层的激活方差将为 ( d_a \sigma_w^2 \sigma_a^2 )。因此:
- 如果 ( d_a \sigma_w^2 > 1 ),幅度将随每一层增加。
- 如果 ( d_a \sigma_w^2 < 1 ),幅度将随每一层收缩。

为了让激活的幅度在层与层之间保持不变,我们需要确保 ( d_a \sigma_w^2 \approx 1 )。我们可以做的一件事是选择权重的方差 ( \sigma_w^2 = 1 / d_a ),或者说,选择权重的标准差为 ( 1 / \sqrt{d_a} )。

Xavier 初始化

这个基本原则——将权重的标准差设为 ( 1 / \sqrt{\text{输入维度}} )——有时被称为 Xavier 初始化。它效果很好。
使用 Xavier 初始化后,之前实验中标准差下降的速度大大减缓。对于一个中等深度的网络,这实际上会工作得很好,并且在实践中被广泛使用。
但为什么标准差仍在缓慢下降?我们遗漏了一个小细节:非线性激活函数。
修正非线性效应:以 ReLU 为例
我们忘记了在线性层之后会应用非线性函数,而非线性函数会改变值的尺度。深层网络几乎总是使用 ReLU,因为它比 Sigmoid 表现好得多。
问题是,ReLU 会将许多激活值置零。如果激活值真的呈均值为0的正态分布(通常不是,但作为粗略模型),那么大约一半会是负数,从而被 ReLU 置零。这将减少方差。

如果 ( z ) 的原始方差是 ( \sigma_z^2 ),将一半数字置零后,总体方差会降低(大约减半)。虽然“减半”看起来不多,但在非常深的网络中(如150层),它会累积起来产生显著影响。
解决方法很简单:认识到大约一半的数字会被归零,我们可以简单地将方差放大两倍,或者等效地将标准差放大 ( \sqrt{2} ) 倍。这意味着不是将标准差设为 ( 1 / \sqrt{d_a} ),而是设为 ( \sqrt{2 / d_a} )。
这个“一半因子”实际上是在 ResNet 论文中提出的。对于残差网络,它最终产生了相当大的影响。使用这个修正因子后,各层的标准差基本上能保持在同一范围内。

关于偏置初始化的细节
之前我们说将偏置向量初始化为零。但 ReLU 再次带来了一点问题:如果我们将偏置初始化为零左右,平均仍会有一半的单元输出为零(“死亡”),这可能导致零梯度。
因此,将偏置初始化为一个小的正常数(如 0.1)也很常见,这可以避免有过多的负值被 ReLU 置零。通常不会同时使用“一半因子”和正偏置初始化,因为它们可能不会很好地协同工作。
高级初始化:正交初始化
Xavier 初始化对于大多数前馈、卷积或全连接网络来说是一个不错的选择。但有时,基于正交矩阵的更高级技术效果很好,并且能帮助我们理解网络初始化的原理。
回想一下,损失关于某一层权重的导数是由许多雅可比矩阵的乘积给出的。只有当所有这些雅可比矩阵都接近单位矩阵时,乘积才会合理。“矩阵接近1”意味着其特征值接近1。
对于任何一个雅可比矩阵 ( J ),我们可以通过奇异值分解将其写为三个矩阵的乘积:( J = U \Lambda V^T )。其中 ( U ) 和 ( V ) 是正交矩阵(它们旋转向量但不改变其长度),( \Lambda ) 是对角矩阵,其对角线元素是 ( J ) 的奇异值(对于非方阵)或特征值的体现。
粗略地说,( \Lambda ) 捕获了矩阵所做的所有缩放,而 ( U ) 和 ( V ) 捕获了所有旋转。如果我们能迫使 ( \Lambda ) 成为单位矩阵(即每个维度上的缩放因子为1),那么就能确保这一大串雅可比矩阵的乘积不会产生巨大或微小的数字。
我们并不直接设置雅可比矩阵,但请记住,线性层的雅可比矩阵就是其权重矩阵的转置。因此,如果我们想让这些雅可比矩阵的特征值在1左右,我们需要对权重矩阵 ( W ) 进行奇异值分解,并迫使 ( \Lambda ) 成为单位矩阵。
实现正交初始化的一种方法是:
- 构造一个随机矩阵(例如,从标准正态分布采样)。
- 对其进行奇异值分解,得到 ( U, \Lambda, V )。
- 丢弃 ( \Lambda ),仅使用正交矩阵 ( U ) 或 ( V )(选择形状合适的那个)作为初始化后的权重矩阵。
以下是正交初始化的示例代码(概念上):
# 假设我们想要一个 m x n 的权重矩阵
m, n = ...
# 1. 构造随机矩阵
a = np.random.randn(m, n)
# 2. 进行奇异值分解
u, s, vh = np.linalg.svd(a, full_matrices=False)
# 3. 使用正交矩阵进行初始化(选择形状合适的那个)
if m > n:
w_init = u
else:
w_init = vh
这提供了一种更有原则的方法来确保雅可比矩阵行为良好,尽管它本身没有解释 ReLU 的影响,需要额外修正。
最后的补救措施:梯度裁剪
最后,我们讨论一个与初始化相关但属于“最后手段”的话题:当一切出错时怎么办?即使我们正确初始化、使用批归一化、标准化输入,由于深度学习优化非常复杂,有时仍可能遇到“梯度爆炸”的问题。

您可能初始化网络,沿着最陡下降方向前进,一切良好。然后突然,一个巨大的梯度出现,完全扰乱了网络,将参数弹射到一个糟糕的区域(如大高原),导致优化停滞。这可能是因为在损失函数的某个尖锐区域步长太大,或是因为某些运算(如除以一个很小的数)产生了巨大值。
当这种情况发生时,您可能会看到损失突然变得非常大或变成 NaN,然后训练失败。通常这会形成一个正反馈循环:一个异常大的梯度导致参数进入更糟的区域,进而产生更大的梯度。
解决方案:梯度裁剪
一个非常简单且能很大程度上缓解这些问题的解决方案是 梯度裁剪。当然,您首先应确保这不是由代码错误引起的。
梯度裁剪主要有两种方式:
- 按值裁剪:将梯度中的每个元素裁剪到区间
[-C, C],确保其绝对值不大于某个常数C。 - 按范数裁剪:如果梯度的范数(长度)超过某个阈值
C,则将整个梯度向量按比例缩放,使其范数等于C。这保持了梯度的方向,只改变了其大小。
两种选择都是合理的,取决于偏好。剩下的关键选择是如何设定 C。
有时您可以根据问题直觉手动选择 C。如果没有,您可以:
- 先进行几个周期的正常训练(假设训练没有爆炸)。
- 观察这几个周期内“健康”梯度的幅度(例如,梯度范数的平均值或每个维度的平均大小)。
- 根据观察到的值来设定
C(例如,设定为观察值的两倍)。

请记住,梯度裁剪是万不得已的补救措施。如果您遵循了其他所有良好实践(如使用批归一化),不应该频繁遇到梯度爆炸,除非存在错误。因此,启发式地选择 C 是可以接受的。
总结
在本节课中,我们一起学习了神经网络训练中权重初始化和应对训练问题的关键策略:
- 初始化的重要性:糟糕的初始化会导致激活值消失或爆炸,使得梯度为零或无穷大,训练无法进行。
- Xavier 初始化:通过将权重标准差设为 ( 1 / \sqrt{\text{输入维度}} ) 来保持激活方差稳定,这是一个强大且广泛使用的基础方法。
- 针对 ReLU 的修正:由于 ReLU 会将一半激活置零,需要将方差放大两倍,即使用 ( \sqrt{2 / \text{输入维度}} ) 的标准差。
- 高级初始化:正交初始化通过迫使权重矩阵正交,来确保反向传播中雅可比矩阵的乘积行为良好,这是一种更有原则的方法。
- 梯度裁剪:当遇到罕见的梯度爆炸问题时,按值或按范数裁剪梯度是一个简单有效的补救措施,可以作为训练稳定器的最后一道防线。

正确的初始化是成功训练深度网络的基石,而梯度裁剪则提供了应对意外情况的宝贵安全网。
课程 P22:CS 182 第7讲 第3部分 - 初始化与批归一化 🧠
在本节课中,我们将学习如何通过优化和正则化技术来提升神经网络的性能。我们将重点讨论集成学习与Dropout方法,并简要介绍如何选择超参数。

集成学习:组合多个模型以提升性能
上一节我们讨论了优化神经网络的方法。本节中,我们来看看如何通过组合多个模型来减少方差,提升泛化能力。
神经网络通常包含大量参数,有时甚至超过数据点的数量,这可能导致高方差。这意味着在不同数据集上训练会得到差异很大的模型。集成学习的基本思想是:训练多个高方差的模型,它们可能在正确答案上达成一致,但在错误答案上各有不同。通过组合这些模型的预测,我们可以得到一个更稳健的解决方案。

以下是集成学习的基本步骤:
- 训练多个独立的模型。
- 组合这些模型的预测(例如,平均概率或多数投票)。
自助法集成
理论上,我们可以通过自助法来生成多个独立的数据集。具体方法是:从原始数据集中有放回地随机抽取N个样本,构成一个新的、大小相同的数据集。重复此过程M次,得到M个不同的数据集,并在每个数据集上独立训练一个模型。
实践中的简化集成

在实践中,由于深度学习的训练过程本身具有随机性(如随机初始化、小批量随机采样),即使在相同数据集上训练,也会得到不同的模型。因此,通常只需在同一训练集上训练多个独立初始化的模型,然后组合它们的预测,即可获得集成学习的好处。

高效集成策略

为了降低计算成本,可以采用以下策略:
- 共享主干网络:让多个模型共享底层的特征提取层(如卷积层),仅让顶部的分类层(全连接层)独立训练和集成。
- 快照集成:在单个模型的训练过程中,定期保存模型参数快照。将这些来自不同训练阶段的快照视为集成中的不同模型。
- 参数平均:一种更简单的技巧是直接平均多个模型的参数,这在实际中也能产生不错的效果。

Dropout:一种高效的近似集成方法 🎯

上一节我们介绍了如何构建显式的模型集成。本节中,我们来看看一种能在一个网络内隐式实现巨大集成的技术——Dropout。

Dropout的核心思想是:在训练阶段,随机将神经网络中一部分隐藏层节点的输出激活值设置为零(即“丢弃”)。这可以迫使网络学习更鲁棒、冗余的特征表示,因为任何特征都可能随时被“丢弃”。

在代码中,Dropout可以这样实现(以某一层为例):
# 前向传播计算激活
h1 = np.maximum(0, np.dot(W1, X) + b1)
# 生成Dropout掩码(以50%概率丢弃)
mask = (np.random.rand(*h1.shape) < p) / p # 注意除以p是为了缩放
# 应用Dropout
h1 *= mask
Dropout在测试时的处理

在测试阶段,我们不再随机丢弃神经元。为了近似训练时巨大集成的平均效果,常见的做法是:
- 权重缩放:在测试时,将所有前一层到应用了Dropout的层的权重乘以保留概率
p(训练时未丢弃的概率)。 - 激活缩放:另一种等价做法是,在训练时,对未丢弃的神经元的激活值乘以
1/p,而在测试时正常前向传播。
这两种方法都是为了确保测试时神经元的期望输入尺度与训练时保持一致。
超参数调优指南 ⚙️

我们已介绍了多种影响模型优化和泛化的技术。本节最后,我们简要讨论如何系统地调整这些方法引入的超参数。
超参数主要分为两类:
- 影响优化的参数:如学习率、动量、初始化参数。这些参数选择不当会直接影响训练过程的收敛,通常可以通过观察训练初期的损失曲线来调整。
- 影响泛化的参数:如网络层数、隐层大小、Dropout率、集成模型数量。这些参数主要影响模型在验证集上的性能,需要在训练一定程度后,根据验证误差来调整。

超参数搜索策略
以下是进行超参数搜索的实用建议:

- 粗调后精调:首先在较宽的范围(通常在对数尺度上)进行随机搜索,运行少量训练周期,快速筛选出表现较好的参数区域。然后在该区域缩小范围,进行更密集的搜索。
- 优先使用随机搜索:与网格搜索相比,随机搜索在超参数重要性不均等时更高效,因为它能确保对每个超参数进行更多独立的探索。
- 观察学习曲线:学习曲线能提供丰富信息。例如,训练损失先下降后上升可能表明学习率过高。
本节课中我们一起学习了:如何通过集成学习组合多个模型来降低方差、提升性能;Dropout作为一种高效的内部正则化与近似集成方法的工作原理与实现;以及系统化进行超参数调优的基本策略。这些技术是构建强大、稳定神经网络模型的重要组成部分。
课程 P23:CS 182 第8讲 第1部分 - 计算机视觉 🖼️
在本节课中,我们将要学习深度学习在计算机视觉领域的应用。我们将从回顾分类问题开始,逐步深入到更复杂的任务,包括目标定位、目标检测和语义分割。我们将了解这些任务的基本概念、评估方法以及它们如何扩展了传统的分类范式。
从分类到定位 🔍

上一讲我们讨论了卷积神经网络如何将图像映射到单个分类标签。本节中我们来看看计算机视觉中更广泛的问题设置。
在分类问题中,网络输出一个离散变量,指示图像中存在的对象类型。这个设置有时显得局限,因为它不考虑对象在图像中的位置。
一个更细致的问题是目标定位,或同步分类与定位。在这个设置中,模型需要同时输出对象的类别及其位置。位置通常用一个边界框来表示,这是一个轴对齐的矩形,由其在图像中的 (x, y) 坐标、宽度 w 和高度 h 定义。
一个更复杂的问题是目标检测。与定位不同,目标检测需要处理图像中可能存在的多个对象。模型的目标是为图像中识别出的每一个对象输出一个边界框和类别标签。

我们可以将这个想法更进一步,发展出语义分割或场景理解。其目标是为图像中的每一个像素分配一个语义类别标签,从而精确地勾勒出每个对象的形状。

在今天的课程中,我们将按从简到繁的顺序,依次讨论目标定位、目标检测和语义分割。
目标定位与评估 📏
目标定位是目标检测的基础。本节中我们来看看如何形式化这个问题以及如何评估定位的准确性。

在常规分类中,我们的数据集由元组 (x_i, y_i) 组成,其中 x_i 是图像,y_i 是分类标签。对于目标定位,标签 y_i 变得更复杂,它是一个包含五部分信息的列表:语义类别 L_i、边界框的 x 和 y 坐标、以及边界框的宽度 w 和高度 h。这些坐标和尺寸通常以像素为单位表示。
在讨论具体方法之前,我们需要定义如何衡量定位的准确性。分类准确率很容易计算,但定位需要同时考虑类别正确性和边界框的位置。
一个常见的评估指标是交并比。其核心思想是量化预测边界框与真实边界框之间的重叠程度,同时控制它们自身的绝对大小。
交并比的计算公式如下:
IoU = Area_of_Intersection / Area_of_Union
其中,Area_of_Intersection 是两个边界框重叠区域的面积,Area_of_Union 是两个边界框覆盖的总面积。
交并比的值在0到1之间。值越接近1,表示重叠度越高,定位越准确。通常,在评估时,我们会设定一个阈值(例如0.5)。如果模型预测的类别正确,并且其预测边界框与真实边界框的IoU大于该阈值,我们就认为这次定位是正确的。
以下是评估定位准确性的关键点:
- 交并比是衡量边界框重叠度的标准指标。
- 它是一个评估指标,而非训练时使用的损失函数,因为IoU的优化较为困难。
- 具体的评估协议(如IoU阈值)取决于所使用的数据集和基准测试。

本节课中我们一起学习了计算机视觉中超越简单分类的几种核心任务:目标定位、目标检测和语义分割。我们重点介绍了目标定位的问题定义,并学习了使用交并比来评估定位准确性的方法。理解这些基础概念是后续学习更复杂检测与分割模型的关键。
🖼️ 课程 P24:CS 182 - 第8讲 - 第2部分 - 计算机视觉
在本节课中,我们将要学习对象定位的核心概念与方法。我们将探讨如何让计算机视觉模型不仅识别图像中的物体是什么,还能精确地找到它在图像中的位置。
🎯 对象定位的问题定义
对象定位的目标是预测图像中单个主要对象的边界框。边界框通常由四个坐标表示:中心点的 x 和 y 坐标,以及框的宽度 w 和高度 h。
🔧 方法一:回归方法
上一节我们介绍了对象定位的基本概念,本节中我们来看看第一种实现方法:将其视为回归问题。
我们可以从一个传统的图像分类器开始。具体做法是,在其卷积层之后,添加另一组全连接层。这组新层的工作不是输出语义类别,而是输出边界框的四个坐标值:x、y、w 和 h。
以下是训练这种网络的一种方式:
- 联合训练整个网络,计算一个综合的损失函数。
- 这个损失函数是分类损失(例如交叉熵损失)和回归损失(针对边界框坐标)的加权和。
- 回归损失可以是均方误差或基于高斯分布的对数似然。
- 通过反向传播将两个损失同时传播到网络中。
关于如何正确设置这种多任务目标存在一些细节,本节课没有时间深入讨论。如果你想了解更多,可以查阅后续会提到的 YOLO 等论文,它们在这方面是很好的例子。
另一种常用的设置是分两步训练:
- 首先,像上一节课那样训练分类器。
- 然后,固定训练好的卷积层权重,仅在其之上训练新的回归头。

这两种都是合理的选择,具体取决于系统的设计细节。

这是一个非常简单的设计,可以作为对象定位系统的快速起点。然而,它通常不是效果最好的方法,我们接下来会讨论原因。
🔍 方法二:滑动窗口方法
除了直接回归坐标,另一种思路是使用滑动窗口。
我们可以这样思考:不是对整个图像分类,而是对图像中的每一个可能的小块(补丁)进行分类。我们遍历所有可能的裁剪区域,将每个区域输入分类器。分类器预测“是猫”概率最高的那个区域,就可以作为边界框。
需要注意的是,分类器通常需要固定尺寸的输入。因此,无论裁剪出什么形状的补丁,都必须调整到分类器期望的大小,这可能导致图像被拉伸或压扁。只要分类器在训练时见过各种长宽比的图像,这通常不是问题。
现在,关键问题是我们应该选择哪些裁剪区域?一个简单的解决方案是:在一定范围内,尝试所有可能的裁剪。
以下是实现步骤:
- 考虑不同尺度:从整个图像开始,然后以不同比例(如2的幂次,或更细粒度的20%、50%等)放大图像。在每个缩放后的图像上,用固定大小的窗口进行平铺扫描。
- 考虑不同宽高比:除了均匀缩放,还可以对图像进行各向异性缩放(如垂直拉伸或水平拉伸)。在拉伸后的图像上平铺窗口,这些窗口对应原始图像中不同形状(如又宽又矮或又高又瘦)的边界框。
- 密集平铺:在实践中,平铺会非常密集,窗口之间可能只偏移一个像素,从而产生大量候选框。

一旦我们获得了所有候选窗口的分类概率,就可以选择概率最高的窗口作为最终边界框。更复杂的方法会使用非极大值抑制等技术,但对于单对象定位,选择最大值是一个不错的启发式方法。
🤝 方法三:结合回归与滑动窗口(OverFeat)
为什么要把两种方法结合起来?因为它们可以互补彼此的弱点。
- 滑动窗口简单,但为了覆盖每个像素可能非常昂贵,且边界框可能不够精确。
- 回归方法可以直接输出连续坐标,但单独使用可能不够鲁棒。

结合两者的思路是:对于滑动窗口产生的每个图像补丁,网络不仅输出该补丁包含对象的概率,还输出一个相对于该补丁的边界框坐标偏移量。这样,回归头可以对初始的滑动窗口位置进行精细调整,无需枚举所有可能的缩放和长宽比。
OverFeat 论文是这种方法的经典实现。其训练方式是:
- 首先对网络进行标准的图像分类预训练。
- 然后在分类网络提取的特征之上,训练回归头。
- 在推理时,将图像以多种尺度输入网络,对于每个位置(滑动窗口),同时得到类别概率和精修的边界框坐标。
最后,需要融合来自不同位置和尺度的预测。一种简单的方法是选择概率最高的窗口。OverFeat 论文则采用了一种更复杂的加权平均策略,按概率对来自不同窗口的边界框预测进行融合,从而得到最终结果。
这种方法在计算上可以利用卷积网络的特性进行大幅优化。
⚡ 高效计算:全卷积网络
滑动窗口方法的一个明显问题是计算成本高。如果有数百个窗口,计算成本会成百倍增加。
幸运的是,我们可以利用卷积网络的特性来大幅减少计算量。关键在于:相邻的滑动窗口在图像上有大量重叠区域,因此它们在卷积层中计算出的激活值也是高度重叠的。我们不需要为每个窗口从头计算卷积。
我们可以将网络末端的全连接层重新解释为卷积层。具体来说:
- 最后一个全连接层可以看作是一个
1x1的卷积。 - 这样,整个网络就变成了一个全卷积网络。
- 我们可以将任意大小的图像一次性输入这个全卷积网络。
- 网络的输出将是一个空间特征图,其中每个位置上的值就对应原始图像中该位置滑动窗口的分类结果。
这种转变允许我们在一次前向传播中,高效地得到图像所有位置上的预测,实现了计算的高度复用。这是一个非常强大的思想,在物体检测和图像分割等领域也会反复出现。
📝 本节总结
本节课中我们一起学习了对象定位的几种核心方法:
- 回归方法:在分类网络基础上添加回归头,直接预测边界框坐标
(x, y, w, h)。 - 滑动窗口方法:对图像所有可能的区域进行分类,选择概率最高的区域作为边界框。
- 结合方法:如 OverFeat,为每个滑动窗口同时预测类别和边界框偏移,兼具两者的优点。
- 高效实现:通过将网络转换为全卷积网络,可以一次性计算所有滑动窗口的预测,极大提升效率。

这些构建块是理解现代物体检测系统的基础。
🖼️ 课程 P25:CS 182 - 第8讲 - 第3部分 - 计算机视觉
在本节课中,我们将学习计算机视觉中的一个核心任务:目标检测。我们将探讨如何让模型识别图像中的多个物体,并为每个物体输出其类别和边界框。我们将介绍几种主流方法,包括滑动窗口、YOLO和R-CNN系列,并理解它们背后的核心思想。
🎯 目标检测问题定义
上一节我们讨论了单目标定位。本节中,我们来看看更一般的情况:多目标检测。
在目标检测任务中,我们有一张输入图像,图像中可能包含数量不定的多个物体。对于每个物体,模型需要输出其类别(例如“猫”、“狗”)以及一个边界框。边界框通常由四个参数定义:中心点坐标 (x, y)、宽度 w 和高度 h。
因此,对于一张图像,模型的理想输出是一个列表:
- 第一个物体的类别 c_i1 和边界框 (x_i1, y_i1, w_i1, h_i1)
- 第二个物体的类别 c_i2 和边界框 (x_i2, y_i2, w_i2, h_i2)
- … 以此类推,直到第 n 个物体。
这立即带来一个挑战:模型需要输出可变数量的预测结果。
🔍 方法一:滑动窗口与朴素思路
一个直观的想法是沿用滑动窗口的方法。模型在图像的每个可能位置(窗口)上,都输出一个类别预测和一个边界框预测。

以下是具体步骤:
- 模型在图像上滑动一个窗口。
- 对于每个窗口位置,模型预测该窗口内包含某类物体的概率。
- 我们设定一个概率阈值。只有当某个窗口对某类别的预测概率高于此阈值时,我们才认为该窗口检测到了一个物体,并输出其类别和边界框。
然而,这种方法存在一个明显问题:重叠检测。一个物体可能被多个高分窗口同时覆盖,导致对同一个物体输出大量重叠的边界框。

为了解决重叠问题,我们可以引入 非极大值抑制。其核心思想是:对于一组相互重叠的检测框,只保留置信度(概率)最高的那一个,抑制掉其他所有框。即使某个位置有很高的概率(如98%),如果其邻近位置有一个概率更高的检测(如99%),那么它就不会被输出。NMS的细节实现(如如何定义“重叠”)属于传统计算机视觉范畴,此处不深入展开。
📦 方法二:固定数量输出与YOLO
另一种思路是让模型直接输出一个固定长度的列表,例如包含 B 个预测(每个预测包含类别和边界框)。在推理时,我们只输出列表中置信度高于阈值的预测。
但这种方法本身也有局限:我们需要预先设定 B 的值。如果 B 设得太小,模型可能无法检测图像中所有的物体;如果 B 设得太大,训练会变得困难。
有趣的是,当我们将滑动窗口的思想与固定输出的思想结合起来时,就得到了非常有效的现代目标检测方法,例如 YOLO。
YOLO(You Only Look Once)的核心思想如下:
- 将输入图像划分为一个 S × S 的网格(例如 7×7)。
- 每个网格单元负责预测以该单元为中心的物体。
- 每个网格单元会输出 B 个边界框(通常 B=2),每个边界框包含:
- 框的位置参数 (x, y, w, h)
- 一个置信度分数,表示该框包含物体的可能性以及预测框与真实框的重合度(IoU)。
- C 个类别的条件概率。
- 在最终输出时,我们根据置信度分数进行阈值过滤,并使用非极大值抑制来去除冗余框。


在训练时,需要将图像中的每个真实边界框分配给一个网格单元。分配规则是:选择那个其预测的边界框与真实框IoU最高的网格单元。如果一个网格单元没有被分配任何真实框,则其置信度目标被设置为0。
YOLO的优势在于速度快,它是“单阶段”检测器的代表。其输出物体数量受网格数 S×S×B 限制,但在实际场景中通常够用。

🧩 方法三:区域提议与R-CNN系列
另一类重要的方法是 R-CNN 系列,其核心是 区域提议。这类方法通常分为两个阶段。
以下是R-CNN的基本流程:
- 区域提议:首先使用一种算法(在深度学习时代之前就已存在)从图像中提取出可能包含物体的区域(称为“提议”或“感兴趣区域”)。这相当于一个更智能的“滑动窗口”,只关注可能有物体的区域。
- 分类与精修:将每个提议区域裁剪出来,缩放成统一大小,然后送入一个卷积神经网络进行分类,并回归出更精确的边界框。


原始的R-CNN效率很低,因为每个区域都要独立通过CNN。后续的 Fast R-CNN 对其进行了重大改进。
Fast R-CNN 的工作流程如下:
- 将整张图像输入一个卷积网络,生成一个卷积特征图。
- 将第一步得到的区域提议,映射到这个特征图上。
- 对每个映射到特征图上的区域,通过 RoI Pooling 层提取出一个固定大小的特征向量。
- 将这个特征向量输入全连接层,同时完成物体分类和边界框回归。


这种方法的关键在于,卷积计算在整张图像上只进行一次,然后在共享的特征图上处理所有区域,大大提升了效率。整个网络可以端到端地进行训练。
更进一步的 Faster R-CNN 甚至用神经网络(区域提议网络,RPN)替代了传统的区域提议算法,使得提议生成也融入了深度学习框架。

R-CNN系列是“两阶段”检测器的代表,通常精度较高,但速度相对YOLO这类单阶段检测器慢一些。在实际应用中,YOLO和Faster R-CNN的性能在不同时期和不同数据集上互有胜负。
📚 延伸阅读与总结
本节课中,我们一起学习了目标检测的基本概念和几种主流方法。
以下是核心方法的总结与推荐阅读:
- YOLO(单阶段代表):思路直接,将检测视为回归问题。推荐阅读原始论文《You Only Look Once: Unified, Real-Time Object Detection》,并了解其后续版本(v2, v3, v4等)的改进。
- R-CNN系列(两阶段代表):通过区域提议和分类两个阶段完成检测。推荐阅读三部曲:R-CNN, Fast R-CNN, Faster R-CNN 的原始论文。
- 其他单阶段方法:SSD(Single Shot MultiBox Detector) 是另一个高效的单阶段检测器,设计上也很有借鉴意义。
目标检测是计算机视觉的基石任务之一。理解YOLO和R-CNN这两类设计哲学,将为学习更复杂的视觉任务打下坚实基础。你可以根据对速度或精度的不同需求,选择适合的模型架构。


课程 P26:CS 182 第8讲 第4部分 - 计算机视觉 🖼️
概述
在本节课中,我们将要学习计算机视觉中的一个重要任务:语义分割。我们将了解它的基本概念、面临的挑战以及如何通过特定的网络架构(如全卷积网络和U-Net)来实现它。
什么是语义分割?
到目前为止,我们已经讨论了输出类别标签和边界框的方法。现在,我们要讨论的方法试图为图像中的每一个像素分配一个语义类别标签。如果你想精确地知道物体在图像中的位置和形状,这种方法会很有帮助。
在某些方面,语义分割实际上更简单一点。与物体检测不同,你不再需要处理可变数量的输出,因为每个像素都有一个独立的输出标签。从这个意义上说,你不必担心物体是否存在或存在多少个物体。
但它也更复杂一点,因为现在的输出规模要大得多。你的输出分辨率与原始图像相同。


核心思想:逐像素分类
一个非常简单的、思考语义分割的概念起点,就是把它看作一个逐像素分类的分类器。
你可以想象使用一个完整的卷积神经网络分类器,将其“聚焦”在图像中的每个像素上(通常通过零填充等方式处理边缘)。这或多或少就是语义分割方法试图做的事情。

技术挑战与解决方案
然而,直接应用上述观点存在技术挑战。当我们考虑在每个位置使用一个不同的分类器时,这仍然适用于位置数量远小于图像像素数的情况。传统的神经网络架构通过卷积层降低了分辨率。
但现在的问题是,我们希望输出具有与输入相同的分辨率。我们真的希望每个像素都有一个类别,而不仅仅是每个滑动窗口位置。
如果我们不进行下采样(例如,始终使用零填充、步长为1的卷积且没有池化层),那么所有的卷积响应图都将与原始图像具有相同的分辨率。最后,我们可以为图像中的每个位置输出一个类别标签。
你绝对可以这样做,但这在计算上非常昂贵,因为你会有巨大的卷积响应图,并且需要相当多的层来获取广泛的上下文信息。这意味着你需要巨大的过滤器或很多层,这两种方式都非常昂贵。

# 概念性伪代码:一个保持分辨率的卷积堆栈(计算昂贵)
# 输入: 图像 (H, W, C_in)
# 输出: 分割图 (H, W, C_out)
for pixel in all_pixels:
patch = extract_patch_around_pixel(image, pixel)
label = classifier(patch) # 一个深度卷积网络
segmentation_map[pixel] = label
瓶颈架构:下采样与上采样
在实践中,实现语义分割的主流方法是设计一个网络架构,使得逐像素分类在计算上易于处理。
我们能想象的一个概念性架构是:首先有一个卷积堆栈(类似于VGG或ResNet用于分类的部分),它会降低分辨率。一旦我们降低了分辨率,我们就需要在中间进行一些低分辨率但高深度的处理,这基本上整合了整个图像的信息。然后,我们需要上采样以恢复到图像的原始分辨率,最后为每个像素输出一个标签。
所以,从概念上来说,这里的一切都很简单。问题在于:我们如何进行上采样?
我们学习过的卷积操作要么保持分辨率,要么降低分辨率。现在我们需要了解一种能提高分辨率的操作,可以称之为上采样或转置卷积(有时也被不准确地称为反卷积)。
转置卷积(上采样)
让我们先谈谈常规卷积如何降低分辨率。如果你有一个步长为2的常规卷积(不做填充),一个5x5的图像会变成一个2x2的图像。
转置卷积基本上与此相反。一种思考方式是,它是一个“步长小于1”的卷积。例如,步长为1/2意味着你在输入上移动“半个像素”(实际上通过插值实现)。另一种简单的方法是,你可以简单地在输出特征图的多个位置写入值。
从数学上看,对于一个2x2的输入和一个5x5的输出,过滤器是一个四维张量。它对输出块中的每个位置、每个通道和输入中的每个通道都有一个权重值。它会将输入通道乘以某个数字,并写入输出块的对应位置。
这里的一个小陷阱是,不同过滤器输出的区域会重叠。一个非常简单的处理重叠的方法是对重叠位置的值进行平均。例如,中心像素可能会从四个独立的过滤器得到预测值,然后将它们平均起来。
解池化操作
我们最初的网络还有池化层,这是另一种降低分辨率的方式。这意味着如果我们之后想上采样,必须以某种方式“撤销”池化。你可以进行解池化操作。
一个简单的方法是复制值。例如,将2x2的输入通过复制变成4x4的输出。
另一个更聪明、效果也很好的技巧是:在执行池化操作(如最大池化)时,保存最大值所在位置的索引。之后在执行解池化时,将低分辨率特征图中的值写入高分辨率特征图中对应的索引位置,其他位置填充零。

当然,这要求你的网络结构是对称的:在编码器部分的每个池化层,在解码器部分都需要有一个对应的解池化层。
全卷积网络

一种非常简单的架构是瓶颈架构。你通常使用传统的卷积网络(如VGG或ResNet)作为第一部分(编码器),将图像压缩到一个低维的“瓶颈”表示中。然后,你将其翻转过来,使用转置卷积和解池化操作(解码器)使其恢复到原始分辨率。
这就是论文《Fully Convolutional Networks for Semantic Segmentation》中使用的基本设计。他们取一个标准的现有网络(如VGG),将其最后的全连接层替换为卷积层,并将所有的池化层在解码器中变为解池化层,将卷积层变为转置卷积层。
提升细节:U-Net与跳跃连接
你可能会想到这种方法的一个问题:一些空间信息可能在池化过程中丢失了。虽然之后的解池化和转置卷积能让分辨率恢复,但它们可能无法恢复精细的空间细节。
因此,一个想法是:也许可以将多种分辨率的信息在上采样过程中结合起来。具体来说,可以将低分辨率特征图上采样的结果,与编码器阶段通过跳跃连接保存的、相同分辨率的更高层特征图结合起来。
跳跃连接很像残差连接。这就是U-Net设计背后的核心思想。U-Net已被广泛应用于语义分割以及生成对抗网络等许多领域。
在U-Net架构中,每次上采样时,激活由两部分组成:
- 前一个较低分辨率层的上采样结果。
- 编码器中间阶段、具有相同分辨率的原始层的激活。
这两部分被连接(Concatenate)在一起。这样,更高分辨率的特征图(可能编码了边缘、边界等有用的高频细节)可以直接传递到解码器,帮助生成与物体实际边缘完美对齐的分割结果。
# U-Net 跳跃连接的概念性表示
# encoder_activations: 编码器各层的特征图
# x: 解码器当前层的特征图
upsampled = transpose_conv(previous_decoder_layer)
skip_data = encoder_activations[corresponding_level]
combined = concatenate([upsampled, skip_data])
current_output = conv_layer(combined)
总结:计算机视觉四大任务

在这一点上,我们已经讨论了四个标准的计算机视觉问题:
- 图像分类 🏷️:识别图像中存在的主要物体类别。(上节课内容)
- 目标定位 📍:在图像中定位一个物体,输出其边界框。
- 目标检测 🔍:在图像中检测多个物体,输出每个物体的类别和边界框。
- 语义分割 🖼️:为图像中的每个像素输出一个语义类别标签。
你可以看到这些任务有很多共同的主题,例如共享的卷积神经网络骨干,以及滑动窗口(或类似思想)的应用。但它们之间也有区别,例如回归边界框的需求等。
希望本节课能为你提供一些计算机视觉的背景知识,特别是如果你想在最终项目中尝试类似的任务。如果你想了解更多,强烈建议阅读相关的论文(如Fully Convolutional Networks, U-Net等)。

本节课中,我们一起学习了:
- 语义分割的目标是为图像中每个像素分配类别标签。
- 直接进行逐像素分类计算成本高昂,因此常用编码器-解码器架构。
- 转置卷积和解池化是用于上采样的关键技术。
- 跳跃连接(如U-Net中使用的)能有效结合不同分辨率的特征,提升分割细节。
- 语义分割是图像分类、目标定位、目标检测这一系列视觉任务的自然延伸。
课程 P27:CS 182 - 第九讲 - 第一部分:可视化与风格迁移 🎨🧠

在本节课中,我们将学习如何理解卷积神经网络(CNN)在做什么,以及如何利用它们来生成图像。我们将从分析CNN的激活模式开始,探讨其与哺乳动物视觉系统的相似之处,并学习如何可视化网络内部的特征。最后,我们将了解如何利用这些技术进行创造性的图像修改。
从神经科学到卷积网络 🧬➡️🤖
在深入卷积网络的细节之前,让我们先回顾一些神经科学领域的著名实验。胡贝尔和威塞尔对哺乳动物大脑视觉处理的研究非常著名。他们向猫展示不同方向的视觉刺激,并记录其大脑神经元的激活情况。
他们发现,猫脑中的单个神经细胞负责识别不同方向的边缘。下图展示了他们实验的示意图,屏幕上显示了猫不同方向的边缘。


通过这个实验,他们得以重建猫大脑中不同神经元对不同方向边缘的反应模式。这很有道理,因为视觉世界中的物体是由边缘勾勒出来的。因此,如果能有效检测不同位置、不同方向的边缘,就能实现良好的视觉处理。
事实上,这些实验被广泛复现。我们知道,哺乳动物大脑的早期视觉处理倾向于识别定向边缘。这些神经元的感受野(即它们最积极响应的模式)看起来像是一个小边缘,一边明亮,另一边黑暗。

这种模式表明那里存在一个边缘。那么,我们也可以问:卷积神经网络“看到”的是什么?它们是否也以类似于大脑的方式看待事物?

事实上,卷积神经网络的早期层倾向于学习非常类似于胡贝尔-威塞尔实验中观察到的定向边缘检测器。当然,我们更感兴趣的是:后面的层发生了什么?我们为什么关心这个问题?因为这能让我们解释神经网络实际关注的是什么,判断它们识别图像是否出于“正确”的原因。这有助于我们理解模型何时有效、何时可能失效,并允许我们比较不同的模型和架构。
非常相似的概念也可以帮助我们基于卷积神经网络的响应来操纵图像,夸大图像的某些特征。例如,你可以问一个网络在《蒙娜丽莎》中看到了哪些种类的动物,然后夸大那些特征。或者,你甚至可以让它产生幻觉:如果《蒙娜丽莎》是由不同视觉风格的艺术家绘制的,会是什么样子?我们将看到,用于分析网络响应的相同技术,实际上可以用于以这些创造性和艺术性的方式修改图像。

可视化卷积神经网络的滤波器与激活 🔍
首先,我们来谈谈如何可视化卷积神经网络的响应和滤波器。我们需要明确:我们应该可视化什么?我们有两个选择。
第一个选择是可视化滤波器本身。 也就是说,我们不关心激活或图像,而是直接查看滤波器,看看它是否能给我们一些关于其功能的直觉。对于第一个卷积层,这相对简单,因为滤波器直接作用于图像像素。
我们可以这样思考:下图展示了一组不同的滤波器。我们可以问:每个滤波器对应什么图像特征?假设我们有一张狗的图片,然后取一个滤波器,想象把它覆盖在图像的一个小块(补丁)上。现在问自己:如果你计算这个滤波器与图像像素的点积(即对应元素相乘后求和),这个点积会是一个很大的正数、很大的负数,还是接近零?
如果它是一个很大的正数或负数,意味着滤波器对该补丁的响应非常强烈。如果是一个接近零的小数字,则意味着它对那个补丁没有反应。例如,在狗耳朵的边缘,一边是一种颜色,另一边是另一种颜色,那么其中一个定向边缘滤波器可能会对此产生非常强烈的响应。在这种情况下,因为背景比狗的颜色浅,滤波器指向另一个方向,它实际上会产生很大的负响应。但你可以想象,在网络的其他地方,可能存在这个滤波器的“负片”,或者在其他位置产生很大的正响应。
如果把这个滤波器覆盖在边缘没有对齐的地方,例如这里的边缘是从左下到右下,但狗的轮廓是从左上到右下,因为它们没有对齐,点积就不会很大,因此滤波器不会在此位置“激活”。这就是你如何获得滤波器真正代表什么的直觉。
不幸的是,这真的只适用于第一层滤波器。因为第二层滤波器不是用图像像素表示的,它们是用第一层滤波器的激活来表示的,这些对我们来说几乎是不可解释的。
第二个选择是尝试可视化激活特定神经元的刺激。 卷积网络中的神经元是特定滤波器的特征图。通常,当我们对“什么刺激激活了特定神经元”这个问题感兴趣时,我们实际上是在问所有图像位置的问题。我们可以问是什么激活了第l层、第p个滤波器在坐标(m, n)处的单元,但通常我们不太关心具体坐标,所以我们更关心的问题是:什么激活了第l层、第p个滤波器在所有位置的平均值?

因此,对于我们的卷积网络,我们可以取中间某一层,要求某个滤波器:什么样的图像补丁会使这个滤波器的输出变大?下图中的文字(虽然视频中被剪掉)说明了这一点:是什么图像补丁使这个滤波器的输出变大?

我们实际上可以尝试生成能使特定层中特定滤波器输出很大的图像补丁。然后我们可以看看那个补丁是什么样子的。我们会发现一些能响应人脸的滤波器,一些能响应狗的滤波器,以及一些能响应更抽象事物(如圆形形状)的滤波器。

这种探测卷积网络的方式在概念上与我前面提到的胡贝尔和威塞尔的实验非常相似。就像他们在猫的大脑里放置电极,测量哪些视觉刺激能最大程度地激活哪个神经元。我们正在对我们的网络进行探测,试图理解哪些视觉刺激能最大程度地激活那个滤波器。
可视化滤波器与激活的实践方法 🛠️
上一节我们介绍了可视化网络内部表示的两种思路。本节中,我们来看看具体的实践方法。
直接可视化第一层滤波器
这很简单,你只需直接打印出滤波器中的数值。例如,假设在VGG网络的第一层有64个滤波器,你可以把它们排列在一个有64个单元格的网格中,每个单元格对应一个不同的滤波器。你可以猜测其中一些是在检测边缘,一些在检测不同的频率。这几乎是你所期望的。

事实上,这有点引人注目。我们看到的、在大多数卷积网络第一层学到的滤波器,看起来与在哺乳动物大脑中观察到的感受野极其相似。这其实并非偶然。事实上,虽然最初这对计算机视觉研究人员来说有些震惊,但现在我们知道,几乎任何合理的学习算法,当应用于真实的图像补丁时,实际上都会发现这些定向边缘滤波器,包括独立分量分析、K均值、稀疏编码等。原因并不是所有这些算法的工作方式与大脑相同,而是因为这些是自然图像中的主要特征。自然图像不仅仅是像素的随机排列,它们向我们呈现的物体和场景主要由边缘组成。所以这些网络学习边缘作为第一层滤波器是非常有意义的,并且非常一致。
但同样,我们很不幸不能以一种有意义的方式可视化更高层的滤波器。

可视化高层神经元的响应
对于更高的层,我们必须使用选项二:可视化激活神经元的刺激。那么,我们如何可视化神经元的响应呢?
一个想法是寻找能最大程度激发特定单元的现有图像。具体做法是:收集大量图像,评估每个图像在每个层、每个滤波器上的激活值,然后为每个滤波器根据激活程度对这些图像进行排序。最后,我们可以看到最能激发该滤波器的图像。

让我给你们举一个小例子。假设我在看第l层中的第p个滤波器。红框代表该滤波器的感受野,它覆盖了图像的大约四分之一。我在图像上滑动这个感受野,数字表示滤波器的激活值。例如,这里是12.3,这里是3.7,这里是17.1,这里是2.1,这里是42.1。
基于这些数字,你认为这个滤波器实际上在关注什么?它在图像中寻找什么?注意,滤波器的反应往往更强烈,当感受野覆盖在狗的眼睛上时(无论是左眼还是右眼)。因此,你可能会得出结论:这个滤波器正在寻找类似“狗眼”的特征。
事实上,如果我们真的做这个实验,用一个真实的网络查看特定单元的顶部激活区域(下图来自R-CNN的原始论文,针对VGG或AlexNet网络的第5层池化层单元),你会发现有些滤波器显然对特定、通常有语义意义的事物有反应。

以下是观察到的模式:
- 第一个滤波器似乎对人的脸和上半身有反应。
- 第二个滤波器似乎主要对狗有反应,虽然它也能捕捉到有圆圈的东西(可能因为它真的在寻找像两只眼睛和一个鼻子这样的特征)。
- 第三个滤波器似乎对玫瑰和有红色斑点的排列有反应。
- 第四个滤波器似乎对文本有反应。
- 第五个滤波器对房屋有反应。
- 第六个滤波器对圆形闪亮的形状有反应,包括秃顶男人闪亮的额头和抛光木制家具的尖端。
并非所有特征在语义上都超级有意义(例如,将闪亮的额头和抛光的木头聚集在一起),但从视觉上看,它们是相似的。所以,这可能是网络中的一个中等层次特征。
通过梯度分析可视化像素级影响
我们可以做一件不同的事情:不是仅仅寻找已经存在的、能激发网络特定滤波器的图像,而是尝试找出哪些特定的像素对某个单元的值影响最大。这可能会让我们更清楚地了解这个单元到底在做什么。
(注:单位、神经元或滤波器在CNN上下文中通常是同义词。“神经元”是一个更古老的术语,指神经网络激活中的特定数值;“单位”是最近的术语;在CNN中,它们也被称为“滤波器”。)
我们想找到对某个滤波器值影响最大的像素。“影响”意味着什么?它意味着如果你改变某个像素x_{i,j},第l层、位置(m,n)、滤波器p的激活a^l_{m,n,p}会发生重大变化。什么量化了这种变化程度?这正是导数所做的。
如果我们取某个像素x_{i,j}和某个激活a^l_{m,n,p},那么该激活相对于该像素的偏导数∂a^l_{m,n,p} / ∂x_{i,j}直接量化了该像素对该单元的影响有多大。
通常,我们不关心滤波器在特定位置的激活,而更关心滤波器在所有位置的平均激活。在这种情况下,我们会在特征图的所有位置上求和,然后观察通道。但为了简单起见,这里我们不这样做。
那么我们如何得到这个偏导数呢?事实证明,我们可以用计算参数导数的完全相同的方法——反向传播——来计算这些偏导数。这其实并不难。
这是我们之前看到的反向传播伪代码:
- 前向传播:计算神经网络中的所有激活。
- 反向传播:初始化
delta作为损失相对于最后一层激活的导数。 - 对于从最后一层到第一层的每一层:
- 计算损失相对于该层参数的导数(这里我们不关心这个)。
- 计算前一层的
delta:将旧的delta乘以该层的雅可比矩阵。
当你到达第一层(即输入图像)时,delta最终会成为你的损失相对于原始输入的导数。
我们要做的就是运行反向传播,但以稍微不同的方式初始化delta。我们不在最后一层放置一个损失函数,而是在第l层初始化delta,作为我们想要的单位相对于该层值的导数。初始化有一个非常特殊的结构:除了我们想要的单位(m,n,p)处为1,其他地方全为0。因为∂a^l_{m,n,p} / ∂a^l_{m,n,p} = 1。
所以,你只需将delta初始化为与第l层的激活图相同的大小,除了你想探测的位置设为1,其他地方都设为0。这就像在猫的大脑里放置一个电极。如果你想探测某个滤波器在所有位置上的激活和,那么就在该滤波器的所有位置都放1,其他滤波器的所有位置放0。
然后,从第l层运行反向传播,一直运行回图像。最后读出的梯度图大小与输入图像相同,其中的数值代表了图像中每个像素对你关心的单元的影响有多大。
在实际实现方面,这其实很简单。你可以使用与训练神经网络时完全相同的反向传播实现,只是现在,你要用它来计算这些激活关于图像的导数。


让我们看看这在实践中是如何工作的。假设你有一个类似AlexNet的网络,你输入一张猫的图片。如果你真的去计算中间层激活相对于输入图像中每个像素的导数,你会得到类似左上图的结果。效果不是很好,你可以看出它大致是猫的形状,但到处都是噪音,我们真的不知道发生了什么。
但是,如果你稍微修改反向传播过程,你实际上可以得到右上图。这张修改后的图片清晰多了。你可以看到这个单位在寻找什么:背景中的大部分草都没有出现,这个单元主要强调猫的脸(大眼睛、大鼻子等),而不太关注其他部分。事实上,即使是眼睛,特定的颜色通道本身实际上是有意义的。在这张梯度图像中,眼睛其实是蓝色的,这意味着如果你把这只猫的眼睛弄得更蓝,会更激活那个单元。所以,这是一个寻找蓝色大眼睛的单元。这也许是合理的,如果你想在照片中找到可爱的小猫。
引导反向传播:一种改进的可视化技巧 🧭
那么,这个修改是什么?这里的修改叫做引导反向传播。这是《Striving for Simplicity: The All Convolutional Net》论文中介绍的一个技巧。它有点像“黑客”行为,没有一个完美的理论解释,但它似乎大大提高了通过分析这些梯度得到的图像质量。
引导反向传播背后的想法是:普通的反向传播不容易解释,因为网络中的许多其他单元会为给定单元贡献正梯度和负梯度。所以,单元a^l_{m,n,p}可能会受到前面层其他单元的积极影响,也可能会受到抑制(负面影响)。这些消极的抑制往往非常复杂,而积极的贡献往往更简单一点。不完全清楚为什么是这样,但似乎就是这样。
所以,也许如果我们只保持正的梯度,我们将避免一些复杂的负面贡献,并获得一个更干净的信号。因此,引导反向传播在每次反向通过ReLU激活函数时,引入了一个启发式的变化。
通常,当你通过ReLU反向传播时:
- 如果ReLU之前的激活为正,那么你只需将你的
delta乘以1。 - 如果为负(意味着激活被抑制了),你乘以0。
在引导反向传播中,你额外做了一件事:如果输入的梯度(即delta)为负,你也将其设为零。也就是说,当梯度反向穿过一个ReLU时,如果该梯度是负值,就将其置零。这在常规的反向传播中不应该发生,因为在常规反向传播中,如果前向传播时输入为正,那么ReLU的雅可比矩阵是1,所以即使反向传播回来的梯度是负的,该梯度也应该通过ReLU传播回来。而引导反向传播的方法只是说“不,不要那样做,把它设为零”。
这几乎就像在反向传播时也加上一个ReLU。那么为什么这能起作用呢?这并不完全明显。直觉是它消除了这些负梯度(抑制性梯度),而抑制性梯度往往比支持特定单元的梯度更复杂。所以,通过只保留正梯度(即那些让单元激活更强的梯度),去掉那些让它激活更弱的梯度,我们似乎能恢复更多关于特定单元正在寻找的东西的可解释的印象。但这有点启发式。


如果引导反向传播的工作方式还不完全清楚,也许论文中的这张图片会有助于说明它。

这里,f_i是第l+1层ReLU之前的激活,δ_i是那一层的梯度(delta)。你可以看到:
- 常规的反向传播只是将第
l层的导数设置为下一层的导数乘以一个指示函数(该指示函数在输入为正时为1,为负时为0)。这是因为ReLU的导数在输入为正时为1,为负时为0。 - 引导反向传播(最下面一行)还在导数本身为负时将其归零。所以,只有正导数会被传播回去。这有点启发式,但这就是它的工作原理。

因此,事情是这样的:论文的作者和我们一起想出了第6层和第9层不同单元的一些可视化(可能是针对VGG网络)。他们生成这些可视化的方式是:首先找到能最大程度激活不同单元的图像补丁,然后他们用这种梯度方法来计算在这些补丁中,哪些特定的像素负责激活。
你可以看到,在CONV6(一些较低级别的特征)中,激活它们的补丁似乎是……例如,第一排主要代表狗鼻子的圆圈,但负责激活的特定像素并不总是鼻子本身。在CONV9(网络中更高,感受野更大,特征更复杂),你可以开始看到一些更有趣的模式。你可以看到对第二排人的脸有明显的偏好,对第三行圆圈的明显偏好。你也会注意到在每个图像中,有些东西实际上被“删除”了(即梯度很低)。例如,在最下面一行的左图中,这是一个似乎偏好圆形东西的单元。对于左边的牛仔,牛仔帽被非常强调,而衬衫相对不那么强调,尤其是在图像的底部。所以,这真的是在研究这些补丁的特殊性质。
总结 📝
在本节课中,我们一起学习了如何可视化和理解卷积神经网络。
- 我们首先回顾了神经科学的背景,了解了哺乳动物视觉系统如何检测边缘,并发现CNN的早期层学习了非常相似的特征。
- 我们探讨了可视化CNN的两种主要方法:直接可视化第一层滤波器,以及通过寻找最大激活刺激或计算梯度来可视化高层神经元的响应。
- 我们详细介绍了引导反向传播这一技巧,它通过只传播正梯度,得到了更清晰、更可解释的、关于特定神经元关注哪些像素的可视化结果。
- 这些可视化技术不仅帮助我们理解网络内部的工作机制、诊断模型,也为后续进行创造性的图像操作(如风格迁移)奠定了基础。
理解网络“看到”什么,是信任、改进和创造性使用这些强大模型的关键一步。在下一部分,我们将看到如何利用这些原理进行风格迁移和图像生成。
CS 182 课程笔记:第9讲 - 第2部分:可视化与风格迁移 🎨
在本节课中,我们将学习如何通过反向传播来可视化神经网络的特征,并探索如何通过优化图像来最大化特定神经元或类别的激活。我们将讨论如何生成能够代表网络“想法”的图像,并了解如何通过正则化技术使这些生成的图像更符合人类的感知。
上一节我们讨论了如何可视化梯度。本节中,我们来看看如何通过优化图像本身来最大化特定单元的激活。
我们之前尝试找到激活某个单元的图像区域,并分析哪些像素负责激活。现在,我们将尝试生成整个图像区域,甚至是从网络中完全生成的图像。
之前我们计算了给定图像中哪些像素会影响特定单元。现在我们要做的是计算特定单元相对于图像的导数,然后修改图像以激活该单元。这种方法很好,因为我们实际上不需要任何特定的初始图像。我们可以从一个完全随机的图像(如随机噪声)开始,然后迭代修改以优化它,使其最大限度地激活特定单元。这可能更好,因为它提供了对单元功能的更公正的调查,而不是在现有图像中寻找激活。
如果我们只是简单地使用这个程序,它可能不会很好地工作。那么,这个优化问题具体是什么呢?它是试图找到图像 x,使得激活 a(x) 最大化。更一般地,我们可以将其视为找到一个 x,以最大化某个目标函数 S(x),其中 S(x) 可以指单元或类别标签的任何组合。例如,我们可能希望在所有图像位置最大化特定过滤器的激活,或者最大化特定类别的概率,试图找到网络认为的该类别的原型图像。

这个方法本身效果不佳的原因是,它太容易产生真正疯狂的图像。回想一下猫神经衍生物的图片,如果我们想优化图像以更好地激活这个单元,我们可以看到该单元在猫眼睛变得更蓝时有正导数。因此,如果我们把蓝色通道设置为一个非常大的值,它会创造出拥有巨大、无限明亮蓝眼睛的“超级猫”。单元会对此印象深刻,但这不是我们想要的。我们不想要疯狂的图像,我们希望以某种方式限制这个过程,使产生的图像更符合感知。
因此,我们需要某种正则化器来防止生成疯狂的图像。对这些可视化技术的大量研究实际上处理了不同的方法来正则化图像,以防止这些非常极端的解决方案。
一个非常简单且通常有效的选择是简单地正则化像素激活的平方和,本质上是我们之前看到的L2范数正则化器。这是有道理的,因为如果我们有可能产生一只拥有无限明亮蓝眼睛的超级猫,这将有一个非常大的范数,正则化器会对此非常不满意,从而阻止这个解决方案。虽然它不能阻止所有极端情况,但总体而言,它是一个明智的正则化器。
因此,如果我们加上这个正则化器,然后进行优化,我们需要决定最大化哪个单元。让我们从选择 S(x) 作为类别标签开始,这样更容易理解。这意味着我们试图找到最大化网络对“火烈鸟”类别置信度的图像。

使其工作的一个重要细节是:不是最大化类别的概率,而是希望在Softmax函数之前最大化激活值。这意味着在最后一层,有一个线性层输出值,然后输入到Softmax。我们希望最大化这个线性输出值,而不是Softmax后的概率。原因在于Softmax函数会除以所有其他类别的激活值。如果你最大化概率,你可能会得到一个非常疯狂的图像,它不太可能是其他类别,因此更有可能是目标类别。但如果你在Softmax之前最大化激活值,你实际上是在优化与特定类别最相似的东西。
以下是论文《深度卷积网络的可视化:图像分类模型和显著性图》中的一些结果。当然,这些图像不太逼真,因为它们不是从任何真实的照片开始的。它们从一些基本噪声开始,然后运行梯度下降过程并加上正则化器,生成在网络中最大限度地激活该类别的图像。
但是如果你仔细观察这些照片,你可以看到有一些合理的模式出现。例如,左上角的“哑铃”图像绝对不像真实的哑铃,但它看起来确实像两个重物中间夹着一根棍子。斑点狗的图片绝对不像真实的狗,但里面肯定有一些斑点。这表明,当模型试图将某物归类为斑点狗时,它寻找的是斑点。电脑键盘的图像中有一些看起来像按钮的矩形网格。基特狐狸的图像有狐狸脸部的形状。左下角的鹅也明显有一些鹅的轮廓。

这可能会让我们对这些网络寻找的东西有一点了解。例如,它暗示如果图片中有一只鹅,你要把那只鹅复制二十倍,会更像鹅。所以,鹅越多,越像鹅。这也表明这个网络不太关心形状的特定轮廓,就网络而言,斑点狗真的就是关于斑点纹理。
因此,这可以让我们了解网络可能擅长什么,以及它可能不太擅长什么。

以下是另一篇论文《通过深度可视化理解神经网络》中的一些可视化结果。它采用了相似的原则,但使用了一个更细致的正则化器。他们使用的正则化器有点难以表达为一个简单的函数,但程序如下:使用梯度更新图像,然后对图像进行轻微模糊处理。直觉上,这样做可以防止优化器产生真正疯狂的、愚弄分类器的高频细节。模糊处理倾向于强调低频信息的重要性,从而产生更连贯的形状。这可以防止微小的噪声扰乱网络。然后重复这个过程:用梯度更新、模糊、将非常小的激活值降至零,并重复。
这是他们为特定类别生成的图像。这些可视化现在开始看起来更容易解释。例如,火烈鸟的图像实际上有看起来像火烈鸟的轮廓。对于“心兽”(可能是某种长着鹿角的动物),图像中反复出现长着大鹿角的鹿一样的头。台球桌的图像中肯定有桌子的形状。旅行车的图像中,你几乎可以看到一些窗户和一两个轮子。所以,一些合理的模式正在发生。
利用同样的方法,也可以分析哪些图像区域能最大限度地激活特定层。在这里,他们选择了一个类别,然后在该类别的特定层最大化激活。你可以看到,在“海盗船”类别的第八层,它往往有一个更全面的视图,你实际上可以看出有一些带桅杆的帆船。而在第七层,它看到了一些更精细的细节,类似于温莎领带。当我们进入较低的层时,事情变得更抽象,因为这些层的感受野更小。例如,在第四层,它实际上是在看基本的几何基元,如圆圈和旋转的物体,而不是整个物体。在第二层或第一层,模式则更加基础。


本节课中,我们一起学习了如何通过优化和正则化技术来可视化神经网络内部的特征表示。我们看到了如何生成能最大化特定类别或神经元激活的图像,并了解了正则化(如L2范数和模糊处理)对于生成可感知图像的重要性。这些技术帮助我们更好地理解神经网络“看到”和“思考”世界的方式。
🎨 课程 P29:CS 182 讲座 9 - 第 3 部分:可视化与风格迁移
在本节课中,我们将学习如何利用卷积神经网络(CNN)的特征表示来修改图像。我们将探讨两种技术:DeepDream 和神经风格迁移。前者用于放大图像中网络“看到”的模式,后者则用于将一幅图像的风格应用到另一幅图像的内容上。

🧠 DeepDream:放大网络“幻觉”

上一节我们介绍了如何可视化CNN的过滤器。本节中,我们来看看如何利用类似的技术来主动修改图像,使其更符合网络对特定特征的“认知”。这种技术被称为 DeepDream。
其核心思想是:给定一张输入图像,我们选择CNN的某一层,计算其激活值,然后通过梯度上升(而非下降)来修改输入图像,以增大该层激活值的总和。这会使网络在该图像中“看到”的特征变得更加明显。

以下是实现DeepDream的关键步骤:
- 选择目标层:在预训练CNN(如VGG)中选择一个中间层。
- 前向传播:将输入图像通过网络,计算目标层的激活值
A。 - 设置梯度:将损失函数
L定义为该层激活值的总和,即L = sum(A)。反向传播时,目标层激活的梯度被设置为1。 - 梯度上升:计算损失相对于输入图像的梯度,并使用该梯度来增加(而非减少)激活值总和,从而修改输入图像。
- 加入抖动:在每次迭代中对图像施加微小的随机平移(抖动),以防止优化过程陷入局部极值并产生不自然的像素级 artifacts。
以下是一段简化的伪代码,描述了核心循环:
# 假设:model是预训练CNN,layer是目标层,image是输入图像
for i in range(num_iterations):
# 1. 加入微小抖动
image = jitter(image)
# 2. 前向传播,获取目标层激活
activations = model.forward_up_to_layer(image, layer)
# 3. 损失即为激活值之和
loss = activations.sum()
# 4. 计算损失相对于输入图像的梯度
gradient = model.backward_from_layer(loss, layer)
# 5. 梯度上升:沿梯度方向更新图像以增大激活
image = image + learning_rate * gradient
从一张普通的云朵图片开始,经过DeepDream处理,网络可能会强化其中类似动物或物体的微弱模式,从而生成充满奇幻细节的图像。
🖼️ 神经风格迁移:融合内容与风格
DeepDream关注于单张图像内部的模式放大。现在,我们提出一个更复杂的问题:如何将一幅图像(如名画)的风格,与另一幅图像(如照片)的内容结合起来?这就是神经风格迁移。

直觉是:图像的“风格”可以由CNN特征之间的统计关系(如协方差)来刻画,而“内容”则由特征在空间上的具体位置来体现。
如何量化风格:Gram矩阵
我们使用 Gram矩阵 来量化风格。对于CNN的某一层,其激活图尺寸为 [height, width, num_channels]。我们忽略空间位置信息,计算不同通道(即不同特征过滤器)激活值之间的相关性。

Gram矩阵 G 的计算公式如下:
G[l]_{k, m} = Σ_i Σ_j F[l]_{i, j, k} * F[l]_{i, j, m} / N
其中:
l表示第l层。F[l]是该层的激活。i, j遍历所有空间位置。k, m遍历所有通道(特征过滤器)。N是归一化常数(如总像素数height * width)。
Gram矩阵的物理意义:其元素 G_{k, m} 代表了特征 k 和特征 m 在整个图像中同时出现的程度。它捕捉了纹理、笔触等风格信息,但丢弃了物体的空间布局信息。
如何量化内容:特征激活匹配
内容则相对直接。我们选择CNN中较高的一层(能捕捉物体整体形状而非细节),并直接最小化生成图像与内容图像在该层激活值之间的差异。
内容损失 L_content 通常使用均方误差(MSE):
L_content = Σ_i, j, k (F[l]_gen_{i, j, k} - F[l]_content_{i, j, k})^2
这里我们保留空间位置 (i, j),强调物体形状和布局的匹配。
总损失与优化
风格迁移的目标是生成一张新图像 X,它同时匹配内容图像的内容和风格图像的风格。因此,总损失函数是内容损失和风格损失的加权和:
L_total = α * L_content + β * L_style
其中:
L_style = Σ_l w_l * || G[l]_gen - G[l]_style ||^2,即各层Gram矩阵的差异之和,w_l是各层的权重。α和β是超参数,用于控制内容与风格的相对重要性。
优化过程从一张随机噪声图像(或内容图像的副本)开始,通过梯度下降不断调整像素值,以最小化 L_total。
通过精心选择网络层和超参数,该算法能生成令人惊叹的结果,例如将照片转换为具有梵高《星夜》风格或毕加索立体派风格的画作。

📝 总结
本节课中,我们一起学习了两种基于CNN特征操作的高级图像处理技术:

- DeepDream:通过梯度上升最大化CNN特定层的激活,来放大和“幻觉化”输入图像中存在的模式。其核心是修改图像以增强网络已检测到的特征。
- 神经风格迁移:通过分离和重组图像的“内容”与“风格”,将一幅画的风格应用到另一幅图的内容上。其核心工具是:
- Gram矩阵:用于量化风格(特征间的统计关系)。
- 特征激活匹配:用于保留内容(特征的空间布局)。
这两种技术展示了深度神经网络特征表示的力量,不仅可用于理解模型,还能直接用于创造性的图像生成和艺术创作。
课程 P3:CS 182 深度学习导论 - 第1讲第3部分 🧠
在本节课中,我们将要学习深度学习的历史发展脉络、使其成功的关键因素,以及它与生物神经系统的联系。我们将探讨为何深度学习在近年来取得突破性进展,并理解其背后的核心概念。
深度学习的历史脉络 📜
上一节我们介绍了深度学习的基本概念,本节中我们来看看它的发展历程。深度学习并非一蹴而就,其发展经历了多个关键阶段。
二十世纪五十年代,艾伦·图灵在其开创性论文中首次描述了机器学习的可能性。他提出的某些方法,在今天看来已初具人工神经网络的雏形。

1957年,罗森布拉特提出了感知器及其学习算法。感知器本质上是一个进行两类线性分离的模型。其数学形式可表示为:


公式: y = sign(w·x + b)
其中 w 是权重向量,x 是输入向量,b 是偏置项,sign 是符号函数。
当时,感知器让人们感到兴奋,因为它提供了一个实际可行的学习算法。然而,在1969年,明斯基和佩珀特出版了一本书,指出了神经网络的一些基本局限性。例如,为给定问题找到全局最优的神经网络非常困难。这些认识在当时引起了广泛关注。
尽管如此,神经网络的研究仍在继续。1986年,反向传播算法的提出为训练深度神经网络提供了实用方法。1989年,出现了用于手写数字识别的神经网络,在识别邮政编码等任务上取得了良好效果。

从20世纪90年代到21世纪初,机器学习社区的兴趣主要集中在概率方法、凸优化和相对简单的浅层模型上。这些模型的理论分析更为透彻。

转折点出现在2006年左右,深度神经网络开始获得更多关注。2012年,AlexNet论文的发表是一个里程碑事件。该模型在ImageNet图像识别挑战赛上击败了所有其他方法,标志着深度学习时代的到来。
深度学习成功的关键因素 🔑
那么,是什么因素促成了深度学习的成功呢?理解这些因素,能使我们对上述历史时间线有更清晰的认识。

以下是使深度学习发挥作用的三个关键因素:

- 大模型(多层网络):深度学习有效性的一个重要原因是其学习高级表征的能力。高级表征是抽象的、能捕捉有意义的语义概念。要获得这种高级概念,需要许多层的简单处理叠加起来。层数越多,模型的表示能力通常越强。
- 大型数据集:当模型具有很多层时,参数数量会非常庞大。训练这些参数需要大量的数据。此外,深度学习要学习的任务(如视觉识别)本身非常复杂,类比人类需要大量视觉经验才能发展出强大的视觉系统,因此机器学习系统需要大数据集并不奇怪。
- 强大的计算能力:在大型数据集上训练具有海量参数的深度模型,需要强大的计算资源。只有这样,训练过程才能在合理的时间内完成,使模型具有实用价值。
这三者相辅相成,共同构成了深度学习爆发的基础。

模型规模、数据与计算的演进 📈
上一节我们提到了三个关键因素,本节中我们来看看它们的具体演进过程。
模型层数与规模
模型的深度(层数)和宽度(每层的单元数)显著增长。例如:
- 1989年的LeNet手写数字识别网络有7层。
- 2012年的AlexNet有8层,但每层的单元数远多于LeNet。
- 近年来的先进模型(如ResNet-152)有152层,甚至存在超过1000层的模型。

研究表明,在ImageNet挑战赛上,随着层数增加,模型的错误率持续下降(例如从AlexNet的16.4%降至ResNet-152的3.57%),证明了更多层级的有效性。
数据集大小
数据集规模也经历了巨大增长:
- MNIST(20世纪90年代):包含6万张训练图像的手写数字数据集。
- ImageNet(2009年):包含约150万张标注图像的庞大数据集,是深度学习复兴的关键推动力。
- 工业级数据集:像Facebook、Google这样的大公司使用的数据集往往比ImageNet还要大一到两个数量级。
更大的数据集为训练复杂模型提供了必要的“燃料”。

计算成本
训练和运行深度模型需要巨大的计算量。衡量标准之一是执行网络所需的浮点运算次数(FLOPs)。
- AlexNet需要约0.7 GFLOPs进行单次推断。
- 现代的先进模型可能需要数十甚至数百倍的GFLOPs。
训练成本则更为惊人。例如,训练著名的BERT语言模型在16个专用TPU上需要约54小时。这凸显了深度学习对强大计算能力的依赖。
这些因素共同表明:随着我们增加模型规模、数据量和计算资源,深度学习的性能可以持续提升。虽然获取这些资源成本高昂,但这恰恰说明了深度学习的可扩展性和强大潜力。

深度学习的核心主题 🎯

在课程中,我们将反复遇到几个核心主题。
模型容量与自动化
模型容量指的是模型能够表示的不同函数的范围。深度学习使用高容量模型,这带来了高度自动化——我们无需预先精心设计特征,模型可以从数据中自动学习。更大的容量意味着模型可能包含更优的解。
归纳偏差
归纳偏差是内置在模型中的、帮助其有效学习的假设或偏好。它是机器学习的“先天”部分。深度学习的艺术在于设计通用、广泛适用的归纳偏差,使其不限制模型从数据中学习的能力,同时在“内置知识”和“从数据学习”之间找到平衡。
可扩展性
可扩展性是指算法性能随着数据、模型容量和计算资源的增加而持续提升的能力。深度学习的许多成功都源于其良好的可扩展性。
为何称为“神经网络”? 🧬
早期神经网络是受生物神经元启发而提出的简化模型。

一个生物神经元通过树突接收来自其他神经元的信号,在细胞体中进行整合,如果达到阈值,则通过轴突发送信号到下游神经元。
人工神经元是对这一过程的粗略模拟:
- 它接收来自上游神经元(单元)的输入信号
x_i。 - 对这些信号进行加权求和:
z = Σ (w_i * x_i) + b。 - 对求和结果
z应用一个非线性激活函数f,如ReLU:a = f(z) = max(0, z)。 - 将输出
a传递到下游神经元。
代码示例(单个神经元的前向传播):
def artificial_neuron(inputs, weights, bias):
z = sum(w * x for w, x in zip(weights, inputs)) + bias
a = max(0, z) # 使用ReLU激活函数
return a
需要明确的是,现代深度学习与大脑建模关联不大,其核心是解决机器学习问题。人工神经元是一个高度简化的抽象,忽略了许多生物细节。然而,有趣的是,深度神经网络学习到的特征(如对特定方向的边缘敏感)与哺乳动物初级视觉皮层中观察到的特征具有统计相似性。这或许表明,对于某些复杂问题(如视觉处理),足够强大的学习机制会收敛到相似的解决方案上,就像飞机和鸟都有翅膀一样,因为它们都是应对“飞行”这一问题的有效设计。
总结 ✨


本节课中我们一起学习了深度学习的发展简史,了解了促使其成功的三个关键支柱:大模型(深度)、大数据集和强计算力。我们探讨了深度学习的核心主题,包括模型容量、归纳偏差和可扩展性。最后,我们了解了“神经网络”这一名称的生物学起源,以及深度学习与生物视觉系统之间有趣但需谨慎看待的联系。这些基础知识为我们后续深入探索深度学习的具体算法和模型奠定了坚实的框架。
📚 课程 P30:CS 182 - 第10讲 - 第1部分:循环神经网络
在本节课中,我们将要学习循环神经网络的基本概念。循环神经网络是一种能够处理可变长度序列数据的神经网络结构,广泛应用于自然语言处理、语音识别和视频分析等领域。
🧠 概述:处理可变长度输入
到目前为止,我们讨论的神经网络通常处理固定尺寸的输入,例如一张由固定像素组成的图像。然而,在许多实际应用中,输入是可变长度的序列,例如英语句子、音频片段或视频帧序列。
一个简单的处理方法是填充所有序列至最大长度,例如用零填充短序列。这种方法对于短序列效果尚可,但无法很好地扩展到非常长的序列,因为填充会导致输入变得非常大且难以处理。

🔄 循环神经网络的基本设计
上一节我们介绍了处理可变长度输入的挑战,本节中我们来看看循环神经网络的核心设计思想。
循环神经网络采用了一种不同的方法。其基本思想是:网络的层数等于输入序列的长度。每一层接收两个输入:前一层的激活值和当前时间步的输入值。第一层之前的激活值被设为零向量。
以下是每一层的核心计算步骤:
- 将前一层的激活值
a_{l-1}与当前输入x_t连接(concatenate)起来,形成一个新向量。 - 对这个连接后的向量进行线性变换:
z_l = W_l * [a_{l-1}; x_t] + b_l。 - 对
z_l应用非线性激活函数(如 ReLU),得到当前层的激活值a_l = σ(z_l)。
在这个设计中,每一层似乎都有自己独立的权重矩阵 W_l 和偏置 b_l。然而,这会导致一个问题:对于很长的序列,我们需要非常多的权重矩阵,且序列前部的权重可能因训练数据不足而得不到充分训练。
🤝 权重共享:从可变深度到循环
为了解决上述问题,循环神经网络引入了权重共享的关键概念。这意味着,在所有时间步(或网络层)中,我们都使用相同的权重矩阵 W 和偏置 b。

因此,核心公式变为:
z_t = W * [a_{t-1}; x_t] + b
a_t = σ(z_t)
其中,t 代表时间步。权重共享带来了巨大优势:
- 参数效率:无论序列多长,我们只需要学习一组参数
(W, b)。 - 泛化能力:模型在处理序列中任何位置的数据时,都使用相同的知识(参数)。
- 处理任意长度:理论上,训练好的模型可以处理比训练时所见更长的序列。
这种设计也常被描绘成一个随时间展开的网络,其中同一层(同一组参数)在每一个时间步被重复使用,并接收上一个时间步的输出作为输入,从而形成了“循环”的结构。
🧮 训练循环神经网络:反向传播的调整
我们已经了解了RNN的前向传播过程。接下来,我们看看如何训练它,这需要对标准的反向传播算法进行一些调整。

主要挑战在于参数是跨时间步共享的。在标准反向传播中,我们计算每一层参数的梯度并直接更新。但在RNN中,共享参数 W 和 b 对序列中每一个时间步的损失都有贡献。
因此,计算损失 L 对参数 θ(即 W 或 b)的梯度时,我们需要将所有时间步的贡献累加起来。

具体调整如下:
- 初始化参数梯度
dL/dθ为0。 - 在反向传播过程中,当计算到第
t时间步时,我们会得到该时间步的局部梯度(dL/dθ)_t。 - 我们不覆盖全局梯度,而是将其累加:
dL/dθ += (dL/dθ)_t。 - 最终,使用这个累积的梯度来更新参数。

这确保了参数更新考虑了其在所有时间步上的总影响。
📊 处理多输出序列:序列到序列模型

上一节我们介绍了如何处理多输入,本节中我们来看看当模型需要在每个时间步都产生输出时该怎么办。这在序列生成任务中很常见,例如为图像生成文本描述(图像是单一输入,文本是序列输出),或者预测视频的未来帧。
以下是基本的多输出RNN设计:
- 在每个时间步
t,网络像之前一样计算隐藏状态a_t。 - 然后,
a_t会通过一个额外的“读出”或“解码器”函数f来生成该时间步的输出ŷ_t。f可以是一个简单的线性层加Softmax(用于分类),也可以是更复杂的网络。 - 每个输出
ŷ_t都会与一个真实标签y_t比较,并计算一个损失L_t(例如交叉熵损失)。 - 序列的总损失是各个时间步损失之和:
L_total = Σ L_t。

训练这种网络需要处理计算图中的分支问题,因为每个隐藏状态 a_t 同时贡献给当前输出 ŷ_t 和下一个时间步的隐藏状态 a_{t+1}。
🕸️ 广义反向传播:处理计算图分支
为了训练具有多输出的RNN,我们需要使用广义的反向传播算法(或称反向模式自动微分)。其核心规则是处理节点有多个下游节点的情况。
规则很简单:在反向传播时,如果一个节点的输出被多个后续节点使用,那么传播回该节点的梯度(δ)应该是从所有后续节点传来的梯度之和。
以下是计算步骤:
- 从最终损失
L_total开始反向传播。 - 对于计算图中的每个节点(代表一个操作,如线性层或激活函数),它会收到来自其所有直接后继节点的梯度
δ_in。 - 如果该节点有多个后继,则将接收到的所有
δ_in相加。 - 然后,该节点计算其关于输入的梯度
δ_out(用于继续反向传播)和关于其参数的梯度(用于更新),计算方式与标准链式法则相同。
通过这种方式,梯度可以正确地通过具有分支的计算图(如多输出RNN)进行反向传播,确保所有参数都根据其对总损失的整体贡献进行更新。



🎯 总结

本节课中我们一起学习了循环神经网络的基础知识。我们首先了解了处理可变长度输入的必要性,然后探讨了RNN的核心设计:通过权重共享的“可变深度”网络来处理序列。我们详细介绍了其前向传播过程,并学习了如何通过调整反向传播(梯度累加和广义反向传播)来训练这种网络。最后,我们将其扩展到多输出场景,构成了强大的序列到序列模型的基础。理解这些概念是掌握更高级RNN变体(如LSTM、GRU)和序列建模任务的关键。
课程 P31:CS 182 第10讲 第2部分 - 循环神经网络 🧠

在本节课中,我们将学习如何有效地训练循环神经网络。我们将探讨训练RNN时遇到的核心挑战——梯度消失与爆炸问题,并介绍一种强大的解决方案:长短期记忆网络。
训练RNN的挑战 🧗
上一节我们介绍了RNN的基本工作原理,本节中我们来看看如何训练它们。
RNN在最基本的层面上是非常深的网络。在实践中,我们可以使用序列长度为100甚至1000的RNN。因此,它们变成了非常非常深的网络。参数在不同层之间共享的事实也使计算更具挑战性。你可以把它看作是一个参数:第一步的参数对下游的步骤有巨大的影响,因为这些影响会叠加在一起。而最后一步的参数可能影响相对较低。这就产生了一些数值问题。
想象一下我们的序列长度是一千,这就像反向传播需要通过一千多层。我们之前看到,如果应用链式法则,这和将一大堆不同的矩阵相乘是一样的。如果我们把许多数字乘在一起,如果有足够多的数字,我们只会得到两种情况:
- 如果数字都小于1,那么结果趋近于零。
- 如果数字都大于1,那么结果趋近于无穷大。
所以,我们只有在相乘的所有数字都接近1时,才能得到合理的答案。我们在乘什么呢?是所有这些层的雅可比矩阵。因此,我们希望这些雅可比矩阵的特征值接近1。如果它们不接近1,那我们就有麻烦了。
- 如果它们小于1,我们会遇到所谓的梯度消失,这意味着梯度的大小随着层数呈指数下降。
- 如果它们大于1,我们会遇到所谓的梯度爆炸,这意味着梯度的大小随着层数呈指数增长。
为什么是指数级的?因为我们将所有这些数字相乘在一起。如果我们有五层,每层的特征值数量级为 C,那么总的系数就是 C^5,所以它是指数级的步数。因此,我们真的希望这些雅可比矩阵的特征值接近1。

爆炸梯度并不太难处理,因为我们总是可以裁剪梯度来防止它们爆炸。RNN最大的挑战来自梯度消失。
梯度消失的直观解释 🤔
RNN中的梯度消失有一个相当简单直观的解释。如果你的梯度消失了,这意味着来自后面时间步的梯度信号永远无法到达前面的时间步。因此,你在某个时间步产生的损失,对网络在更早时间步的行为几乎没有影响。
当这种情况发生时,你的神经网络变得无法维持长期记忆。也许你在第3步看到了一些信息,这对于在第900步得到正确答案至关重要。但如果第900步的梯度信号永远无法传回第3步,那么网络就永远不会知道它需要在第3步记住那个信息,以便在第900步使用它。这非常糟糕,它阻止了RNN记住长期信息。因此,我们真的非常想解决这个问题。
促进梯度流动的方法 🛠️
那么,我们可以做些什么来促进RNN中更好的梯度流动呢?基本思想与我们之前看到的相似:我们真的希望当前时间步的梯度接近1。
我们具体关心的是哪个梯度?在每一层,我们执行一个操作:我们将先前的激活 a_{t-1} 与当前输入 x_t 连接起来,应用线性运算,然后应用非线性激活函数。我们可以称之为RNN动态函数,用字母 Q 表示。所以这个运算是:
a_t = Q(a_{t-1}, x_t)
当我们谈论梯度消失时,我们关心的特殊导数是RNN动态函数的雅可比矩阵,即 dQ / da_{t-1}(Q 相对于先前激活的导数)。如果我们只关心良好的梯度流动,我们真的希望这个导数接近单位矩阵,或者至少其特征值接近1。因为如果是这样,那么梯度既不会消失也不会爆炸。
当然,我们并不想总是强迫这个导数正好是单位矩阵,因为有时我们确实想忘记信息,有时我们想以各种有趣的方式改变它们。所以仅仅强迫它是单位矩阵是行不通的。你只想在需要记住信息时让它接近单位矩阵。
所以,在某些时候,你只是想:至少对于激活向量中的某些坐标,我们只需要记住它们,让它们保持不变。也许对于其他坐标,你希望合并新的输入。但你并不总是希望这样,有时你确实想修改甚至丢弃这些激活中的信息。所以,我们需要一个设计,让网络可以决定它想记住什么。当它决定要记住时,导数应该接近单位矩阵;当它想忘记时,导数可能接近零。
LSTM:长短期记忆网络 🏰
这里有一个有趣的RNN设计,或多或少地完成了这个目标。直觉是,对于向量 a_{t-1} 中的第 i 个条目:
- 如果你想记住它,那么
da_t[i] / da_{t-1}[i]应该接近1。 - 如果你想忘记它,那么
da_t[i] / da_{t-1}[i]应该接近0。

我们要做的是,为每个单元设计一个小型神经回路,来决定是记住还是忘记。我们将有一个称为细胞状态的东西(用 a_{t-1} 表示,有时人们用 C 表示)。我们要将细胞状态 a_{t-1} 乘以一个介于0到1之间的数字 f_t(称为遗忘门)。直觉上,如果它被设置为0,那你就忘了之前的内容;如果设置为1,那你就记住了它。然后,我们要通过添加一些东西 g_t 来修改它。
所以新的细胞状态 a_t 是:
a_t = a_{t-1} * f_t + g_t
直觉上可以这样理解:
- 如果
f_t接近0,那么就用g_t替换细胞状态。 - 如果
f_t接近1且g_t接近0,那么就保持细胞状态不变。 - 如果
f_t接近1且g_t不为0,那么就以附加的方式修改细胞状态。
现在,关键是我们从哪里得到 f_t 和 g_t?这就是LSTM设计中更复杂的部分。
LSTM单元详解
以下是LSTM单元的完整设计。它可能看起来有点复杂,但每个部分都有其理由。
在时间步 t,我们从前一步得到两个状态:细胞状态 a_{t-1} 和隐藏状态 h_{t-1}。我们还有当前输入 x_t。
我们要做的第一件事是形成一个新向量,将 h_{t-1} 和 x_t 连接起来:[h_{t-1}, x_t]。然后,我们对其应用一个线性层。这个线性层的输出维度是普通RNN的四倍。我们将这个四倍的输出分成四个部分,每个部分都是一个向量,维度与 h_{t-1} 相同:
f_t_bar-> 遗忘门候选i_t_bar-> 输入门候选g_t_bar-> 细胞状态修改候选o_t_bar-> 输出门候选
以下是每个部分的处理流程:
-
遗忘门 (
f_t):将第一部分f_t_bar通过sigmoid函数,使其范围在0到1之间。这就成了遗忘门f_t。它逐点乘以先前的细胞状态a_{t-1}。
f_t = sigmoid(W_f * [h_{t-1}, x_t] + b_f) -
输入门 (
i_t) 和候选值 (~g_t):- 将第二部分
i_t_bar通过sigmoid函数,得到输入门i_t,范围在0到1之间。 - 将第三部分
g_t_bar通过一个非线性函数(传统上用tanh,范围在-1到1之间),得到候选细胞状态~g_t。
i_t = sigmoid(W_i * [h_{t-1}, x_t] + b_i)
~g_t = tanh(W_g * [h_{t-1}, x_t] + b_g)
- 将第二部分
-
更新细胞状态 (
a_t):新的细胞状态a_t由遗忘门控制的旧状态和输入门控制的新候选值组合而成。
a_t = f_t * a_{t-1} + i_t * ~g_t -
输出门 (
o_t) 和隐藏状态 (h_t):- 将第四部分
o_t_bar通过sigmoid函数,得到输出门o_t。 - 将新的细胞状态
a_t通过tanh函数(将其值规范到-1到1之间)。 - 将规范后的
a_t逐点乘以输出门o_t,得到新的隐藏状态h_t。这个h_t也用作该时间步的输出(如果需要)。
o_t = sigmoid(W_o * [h_{t-1}, x_t] + b_o)
h_t = o_t * tanh(a_t)
- 将第四部分
这个设计可能看起来很复杂,但有一种方法可以让你对正在发生的事情有一些直觉:我们试图让细胞状态 a_t 充当一个主要的线性信号通道。实际上,我们没有对 a_t 直接应用非线性函数。应用于 a_t 的所有操作都是线性的(乘以0到1之间的数,或添加向量)。所有的非线性都应用于隐藏状态 h_t。
结果,a_t 对 a_{t-1} 的导数变得非常简单:da_t / da_{t-1} = f_t(遗忘门直接决定了它)。因此,没有任何复杂的非线性函数影响 a_t 的导数,这使得梯度流动表现得更好。同时,通过 g_t 和非线性读出来进行信息修改。

为什么LSTM效果更好?🚀

LSTM单元实际上工作得更好的最简单原因是,细胞状态 a_t 在每一步都以非常简单、受控的方式被修改。它的变化很小,变化的大小完全由遗忘门 f_t 决定。
你可以把 a_t 看作是长期记忆。循环状态的另一部分 h_t 一直在变化,你可以把 h_t 看作是短期记忆,它执行更复杂的非线性处理,但很难长时间保留信息。a_t 有一个相对简单得多的线性处理流程,但它能在很长一段时间内保留信息。
实践要点与总结 📝
关于RNN在实践中应用的一些实用笔记:
- RNN几乎总是在每个时间步都有一个输入和一个输出。
- 像第一部分中那样朴素的RNN在实践中几乎不起作用。
- 如果你想要一个RNN,现在你可能会使用类似LSTM单元的东西。LSTM单元工作得很好,它们虽然有点复杂和武断,但比朴素的RNN要好得多。
- 它们仍然比标准的全连接或卷积网络需要更多的超参数调优(如梯度裁剪、学习率等)。
- 也有一些处理序列的RNN替代方案,在实践中可能工作得更好,例如时间卷积网络和Transformer(基于注意力机制的模型),我们将在以后学习它们。
- LSTM单元虽然复杂,但一旦实现,它们可以像任何其他类型的层一样使用。通常,为了封装LSTM单元中的所有复杂性,你只需编写一段代码将其抽象出来,然后像使用卷积层或线性层一样使用它。
- 如果你觉得LSTM细胞的任意性和复杂性很困扰,有一些更简单的变体工作得也一样好。一种非常流行的变体叫做门控循环单元。GRU基本上像LSTM,但没有输出门,
h_t总是设置为对a_t应用某个非线性(如tanh)。这也很有效。LSTM真正重要的部分基本上是遗忘门。

本节课总结:在本节课中,我们一起学习了训练循环神经网络的核心挑战——梯度消失与爆炸问题。我们深入探讨了长短期记忆网络的设计原理,了解了其通过细胞状态和门控机制(遗忘门、输入门、输出门)来维持长期记忆、促进梯度流动的巧妙方式。最后,我们了解了LSTM在实践中的应用及其一些变体。掌握LSTM是理解现代序列建模的重要一步。
课程 P32:CS 182 讲座 10 - 第 3 部分 - 循环神经网络 🧠
在本节课中,我们将学习如何利用循环神经网络构建解决实际问题的实用模型。我们将重点讨论自回归模型、结构化预测以及训练RNN时遇到的关键挑战和解决方案。
自回归模型与结构化预测 📝
上一节我们介绍了RNN的基本概念,本节中我们来看看如何将其应用于实际问题。在实践中,我们使用的大多数RNN具有多个输入和多个输出。这初看可能令人困惑,因为许多问题要么是单输入多输出,要么是多输入单输出。我们通常使用多输入多输出RNN的原因是,大多数需要多个输出的问题中,输出之间存在很强的依赖关系。
这类问题有时被称为结构化预测。之所以称为结构化预测,是因为预测的输出序列(如文本)具有内部结构,这与没有结构的单个离散标签不同。例如,一个句子是否有效,取决于其内部单词之间的关系,而不仅仅是其内容是否正确。
结构化预测示例
假设我们有一个简单的文本生成任务,训练集包含三个句子:
- I think therefore I am.
- I like machine learning.
- I am a neural network.
我们将单词视为范畴变量。网络在第一步接收提示词“I”,然后需要预测下一个词。如果网络在每个时间步独立地从Softmax分布中采样,即使它学到了正确的概率分布,也可能生成无意义的序列(如“I think machine am”),因为它不知道上一步实际采样了哪个词。
核心问题:过去的输出应该影响未来的输出。
解决方案:将上一步的输出作为当前步的输入。这样,网络在生成“think”后,就知道下一步是在预测“I think”之后的词,而不是任意句子中的第三个词。
这种基本设计被广泛应用于需要输出结构化序列的任务,如图像描述、视频预测等。

训练自回归模型 🏋️

现在我们来探讨如何有效地训练这种模型。
基础训练方法
最直接的训练方法是:将输入设置为整个训练序列,将真实输出设置为相同的序列,但偏移一步。
公式表示:
- 输入
X:句子中除最后一个词外的所有词元。 - 真实输出
Y:所有词元向后移动一位,最后一个位置用停止符替换。
例如,对于句子“I think therefore I am”:
- 输入
X= [I, think, therefore, I] - 输出
Y= [think, therefore, I, am,<STOP>]
这样,网络被训练为:看到“I”应输出“think”,看到“I think”应输出“therefore”,以此类推。在测试时,网络生成一个词元,将其作为下一步的输入,直到生成停止符。
分布转移问题

然而,上述方法存在一个潜在问题:分布转移。
问题描述:在训练时,网络总是以真实的序列作为输入。但在测试(生成)时,它接收的是自己上一步的(可能错误的)输出作为输入。即使网络在第一步只犯了一个小错误(例如,以10%的低概率采样了一个不常见的词“drive”),这个“陌生”的输入也可能导致网络在后续步骤中完全混乱,因为它从未在训练中见过这样的上下文组合,从而生成无意义的垃圾输出。
核心矛盾:训练分布(真实字符串)与测试分布(模型生成的字符串)不同。
解决方案:计划采样 📅

为了解决分布转移问题,我们可以采用一种称为计划采样的技术。
基本思想:在训练过程中,我们并不总是将真实的上一词元作为输入。相反,我们以一定的概率 p 使用真实词元,以概率 1-p 使用模型自己在上一步生成的输出作为当前步的输入。
关键点:模型无法区分输入是来自真实数据还是自己的生成,因此它必须学会处理这两种情况。
采样概率计划

如何选择概率 p?
- 如果总是使用模型自己的输出(
p=0),在训练初期模型输出质量很差,会导致训练困难。 - 如果总是使用真实输入(
p=1),则无法缓解测试时的分布转移。
推荐策略:使用一个随时间衰减的计划。
- 训练初期:
p较高(例如0.9),主要使用真实输入,稳定学习。 - 训练后期:
p逐渐降低至0,主要使用模型自身的输出,使其适应测试时的条件。
计划可以是线性的或指数的。这确保了模型在最终训练阶段,主要学习如何根据自己过去的(已改善的)输出来生成后续内容。

RNN的架构变体与实现 🏗️
RNN提供了极大的灵活性,可以适配多种输入输出模式。以下是常见的几种架构:
1. 输入输出模式
- 一对多:单一输入,序列输出。适用于图像描述(输入图像,输出文本)。
- 多对一:序列输入,单一输出。适用于活动识别(输入视频帧序列,输出活动分类标签)。
- 多对多(编码器-解码器):先读取整个输入序列,再生成整个输出序列。适用于机器翻译。
- 多对多(逐帧):每个时间步都有输入和输出。适用于视频逐帧标注。
在实践中,即使是“一对多”任务,也常通过自回归方式实现为“多对多”,即将上一步的输出作为下一步的输入,以捕获输出间的依赖关系。

2. 网络结构细节
- 编码器-解码器组合:常见的是使用CNN作为编码器处理图像,然后将特征序列输入RNN解码器生成文本。
- 堆叠RNN层:可以将一个RNN层的输出作为另一个RNN层的输入,以增加模型容量。
- 双向RNN:包含两层RNN,一层正向处理序列,一层反向处理序列。这样每个时间步的决策都能融合过去和未来的信息。适用于如语音识别等任务,其中当前时刻的标签可能受后续声音影响。
# 概念性伪代码 forward_output = RNN_forward(input_sequence) backward_output = RNN_backward(reverse(input_sequence)) combined_output = concatenate(forward_output, backward_output)

3. 循环卷积层
理论上,可以将LSTM中的矩阵乘法替换为卷积操作,形成循环卷积层,使网络在空间和时间维度上都具备循环特性,但计算成本较高。


RNN的生动应用示例 ✨

RNN在生成任务上表现出强大的能力:

- 文本生成:使用莎士比亚文集训练RNN,模型可以生成语法和风格相似的伪莎士比亚文本。
- 代码生成:使用LaTeX源代码训练RNN,模型能生成可以编译的伪代码片段,包含看似合理的定理和引理结构。
- 现代语言模型:如GPT-2等模型,在给定提示后,能生成连贯、合理且包含微妙语义细节的长篇文本。
这些示例展示了RNN及其变体在理解和生成复杂序列数据方面的潜力。
总结 📚

本节课我们一起学习了循环神经网络在实际应用中的关键知识:
- 结构化预测与自回归模型:为了生成具有内部结构的序列(如文本),我们采用自回归方式,将上一步的输出作为当前步的输入。
- 训练挑战:直接使用“教师强制”训练会导致分布转移问题,即训练(真实数据输入)与测试(模型自身输出输入)的分布不匹配。
- 解决方案:计划采样通过在训练中随机混合真实输入和模型自身输出,并让混合概率随时间衰减,有效缓解了分布转移。
- 架构灵活性:RNN支持一对多、多对一、多对多等多种模式,并可通过堆叠层、双向设计和结合其他网络(如CNN)来增强功能。
- 强大应用:RNN是序列生成任务的基础,能够创作文本、代码等,展示了其捕获数据中长期依赖关系和复杂模式的能力。

RNN及其改进型(如LSTM、GRU)为处理可变长度输入输出序列提供了强大而灵活的框架,是深度学习序列建模的基石。
课程 P33:CS 182 第 11 讲 - 第 1 部分:序列到序列模型 🧠➡️📝

在本节课中,我们将学习如何使用递归神经网络(RNN)来解决序列到序列的转换问题。我们将从构建基本的语言模型开始,逐步引入条件语言模型,最终构建出强大的序列到序列模型,并将其应用于机器翻译、图像描述等任务。
1. 引言:从 RNN 到序列转换 🔄

上一节我们介绍了递归神经网络(RNN)及其在序列处理问题上的灵活性。本节中,我们来看看如何利用 RNN 构建序列到序列模型。
RNN 可以处理多种序列问题:
- 单个输入到序列:例如图像描述,输入是图像,输出是描述文本的序列。
- 序列到单个输出:例如活动识别,输入是视频帧序列,输出是活动标签。
- 序列到序列:例如机器翻译,输入是一种语言的文本序列,输出是另一种语言的文本序列。
在今天的课程中,我们将主要关注最后一类问题,即序列到序列的转换模型。
2. 构建基础神经语言模型 📖

在深入序列到序列模型之前,让我们首先讨论如何建立一个基本的神经语言模型。
语言模型是为表示文本的序列分配概率的模型。它们不仅能分配概率,还能生成文本。训练数据是大量自然语言句子的集合。
以下是构建语言模型的关键步骤:
2.1 文本表示
首先需要将文本表示为模型可以处理的形式。一个简单的方法是使用 独热编码。
独热向量 是一个长度等于字典大小的向量。向量中除对应单词索引的位置为 1 外,其余位置均为 0。
例如,句子 “I think therefore I am” 有 5 个单词,每个时间步将用一个高维向量表示。
更复杂的方法是使用 词嵌入,它是一个连续的向量,旨在反映单词的语义相似性。相似的词在向量空间中的距离更近。我们将在后续课程中详细讨论词嵌入。
2.2 处理句子边界
为了让模型知道何时开始和结束一个句子,我们需要引入特殊标记。
- 序列结束标记:在训练数据中所有句子的末尾添加一个特殊标记(如
<EOS>)。模型将学会在生成句子结束时输出此标记。 - 序列开始标记:在生成全新句子时,可以在第一个时间步之前添加一个额外的开始标记(如
<SOS>)。模型会基于此标记生成第一个词。
2.3 让模型完成句子
如果我们想让模型完成一个以特定片段开头的句子(例如 “I think therefore…”),方法很简单:在生成时,我们强制将前几个输入设置为该片段,而忽略模型在这几步的输出预测。之后,再让模型基于其自身的预测继续生成。

3. 构建条件语言模型 🖼️➡️🗣️
基础语言模型是无条件的。现在,我们将讨论如何构建条件语言模型,即模型的输出文本基于某些输入条件。
例如,在图像描述任务中,输入是一张图片,模型需要生成描述图片内容的文本。
以下是构建条件语言模型的方法:
3.1 模型架构
模型由两部分组成:
- 编码器:一个卷积神经网络,用于处理输入(如图片),并输出一个向量
h0。这个向量h0包含了输入的所有相关信息,并作为解码器的初始状态。 - 解码器:一个 RNN(如 LSTM),其初始隐藏状态被设置为编码器输出的
h0。解码器的工作是将h0中包含的“思想”解码成有效的文本序列。
直观理解:编码器将图片编码为“有一只可爱的小狗”这样的向量表示,解码器则负责将这个想法转化为“图片里有一只可爱的小狗”这样的英文句子。
3.2 训练数据
训练数据是成对的,例如(图片,描述文本)。整个模型(编码器+解码器)是端到端联合训练的,目标是让解码器生成正确的描述文本。
4. 序列到序列模型 🔤➡️🔠

条件语言模型的概念可以自然延伸到序列到序列的转换,例如机器翻译。
4.1 模型架构
对于将法语翻译成英语的任务:
- 编码器:一个 RNN,读取法语句子,并生成一个上下文向量(通常是其最终隐藏状态)。
- 解码器:另一个 RNN,以上下文向量作为初始状态,生成英语句子。
编码器和解码器通常是两个独立的 RNN,拥有不同的权重,但它们是端到端联合训练的。
4.2 一个实用技巧:反转输入序列
在实践中,一个常见且有效的技巧是:将输入序列(源语言)反转后再输入编码器。
原因:在翻译中,目标语言句子的开头通常与源语言句子的开头更相关。通过反转输入,源语言的开头在编码的最后时刻被处理,这使得它在时间上更接近解码器开始生成目标语句的时刻,从而缩短了依赖路径,有时能提升模型性能。
4.3 更现实的模型设计
一个更现实的序列到序列模型可能包含以下设计:
- 使用 LSTM 或 GRU 单元:以更好地捕捉长期依赖。
- 堆叠多层 RNN:通常堆叠 2 到 4 层,以增加模型的表示能力。层数通常少于卷积网络。
- 处理变长序列:输入和输出序列可以具有不同的长度。

5. 序列到序列模型的应用 🌐

序列到序列模型非常灵活,可用于多种任务,只要任务可以表述为序列对。以下是一些应用示例:
以下是序列到序列模型的一些典型应用:
- 机器翻译:将一种语言的句子翻译成另一种语言。
- 文本摘要:将长文本压缩为短摘要。
- 问答系统:输入是问题文本,输出是答案文本。
- 代码生成:输入是自然语言描述,输出是实现该描述的代码(如 Python)。
总结 📚
本节课中我们一起学习了序列到序列模型的核心思想与构建方法。
- 我们从基础的神经语言模型出发,学习了如何使用 RNN 生成文本,并引入了开始和结束标记来处理句子边界。
- 接着,我们引入了条件语言模型,通过编码器-解码器架构,使模型能够根据输入(如图片)生成条件文本。
- 最后,我们将此框架扩展到序列到序列任务,构建了用于机器翻译等任务的模型,并讨论了反转输入序列、使用堆叠 LSTM 等实用技巧。

序列到序列模型是自然语言处理领域的强大工具,为许多复杂的序列转换问题提供了统一的解决方案。

🧠 CS 182 课程笔记 - 第11讲 - 第2部分:序列到序列模型与解码策略
在本节课中,我们将要学习序列到序列模型中的一个核心环节:如何从训练好的模型中生成(解码)输出序列。我们将重点探讨为什么简单的“贪婪解码”策略可能效果不佳,并详细介绍一种更强大的近似搜索算法——波束搜索。
🔍 解码问题与贪婪解码的局限性

上一节我们介绍了序列到序列模型的基本架构。本节中我们来看看模型在实际生成句子时是如何工作的。
一个直观的方法是贪婪解码:在解码的每一步,模型都会输出一个词汇表上的概率分布(通过SoftMax得到)。最直接的做法是每一步都选择概率最高的那个词,并将其作为下一步的输入。
代码示例:贪婪解码的一步
# 假设 `output_distribution` 是当前步的SoftMax概率分布
next_word_index = np.argmax(output_distribution)
next_word = vocabulary[next_word_index]
然而,这种方法存在一个根本问题:早期的局部最优选择可能导致整体序列质量低下。例如,第一步选择了一个概率稍高的错误词汇,即使后续步骤的词汇选择概率很高,整个句子也可能语法不通或语义错误,因为模型已经“偏离了轨道”。
问题的核心在于,我们的目标不是最大化每一步的概率,而是最大化整个输出序列的联合概率。

📐 解码的数学目标:最大化序列概率
让我们从数学上理解解码的目标。在RNN解码器中,每一步输出的概率分布 P(y_t | x_1...x_T, y_0...y_{t-1}) 表示:在给定全部输入 x 和已生成的所有之前输出 y 的条件下,当前词 y_t 的概率。

我们最终想要的是最有可能的输出序列 y_1...y_T。根据概率的链式法则,整个序列的概率可以分解为每一步条件概率的乘积:
公式:序列概率分解
P(y_1...y_T | x_1...x_T) = ∏_{t=1}^{T} P(y_t | x_1...x_T, y_1...y_{t-1})
因此,解码的目标是找到使这个乘积最大化的序列。取对数后,等价于最大化对数概率之和:
max ∑_{t=1}^{T} log P(y_t | ...)

贪婪解码的缺陷在于,它每一步都最大化 P(y_t | ...),但这并不能保证最终 ∏ P(y_t | ...) 是最大的。有时,在早期选择一个概率稍低的词,可能会为后续带来概率高得多的词,从而提升整体序列的概率。
🌳 解码作为树搜索问题
从搜索的角度看,解码是一个树搜索问题。树的根节点是序列开始符,每个节点代表一个已生成的词序列,分支代表下一个可能生成的词。词典大小为 V,序列长度为 T,那么可能的路径总数是 V^T,这是一个巨大的数字。
以下是解决此类搜索问题的关键点:
- 问题规模:穷举搜索所有路径在计算上是不可行的。
- 启发式信息:每一步的SoftMax概率分布提供了很好的启发式信息,概率极低的路径几乎不可能是最优解的一部分。
- 近似策略:我们需要高效的近似算法,在可接受的计算成本内找到高质量的解。
💡 波束搜索算法

基于上述分析,我们引入波束搜索算法。它不是完全贪婪(只保留1个最优),也不是完全搜索(探索所有可能),而是一种折中的启发式搜索。

核心思想:在解码的每一步,我们只保留 k 个最有可能的部分序列假设(称为“波束宽度”)。在下一步,仅从这 k 个假设出发进行扩展,并再次从中选出新的 top k 个假设。当 k=1 时,波束搜索退化为贪婪解码。
以下是波束搜索的关键步骤说明:
- 初始化:从开始符
<SOS>开始,将其作为唯一的初始假设。 - 扩展与评分:对于当前步保留的
k个假设,分别用RNN计算下一个词的概率分布。每个假设可以扩展出V个可能的新假设(即加上一个新词)。 - 排序与剪枝:现在我们有
k * V个候选序列。计算每个候选序列的累计对数概率(即到当前步为止所有词的对数概率之和)。只保留总分数最高的k个候选序列,其余被“剪枝”掉。 - 终止判断:如果一个假设生成了序列结束符
<EOS>,则将其视为一个完整序列,记录下来并从活跃假设列表中移除。算法继续扩展其他未完成的假设。 - 循环:重复步骤2-4,直到所有活跃假设都达到最大长度或我们收集到了足够多的完整序列。
- 最终选择:从所有记录下来的完整序列中,选择分数最高的一个作为最终输出。为了公平比较不同长度的序列,通常使用长度归一化的分数:
分数 = (总对数概率) / (序列长度)。



算法总结:波束搜索通过动态维护一个固定大小的“候选集”,在探索更多可能性和控制计算成本之间取得了平衡。实践中,k 值通常设为5到10就能取得很好的效果。
✅ 课程总结

本节课中我们一起学习了序列到序列模型中的解码策略。
- 我们首先指出了贪婪解码的局限性,它可能因早期的局部最优选择而错过全局更优的序列。
- 我们从数学上明确了解码的目标是最大化整个输出序列的联合概率。
- 我们将解码问题形式化为一个树搜索问题,并指出精确搜索的计算代价过高。
- 最后,我们详细介绍了波束搜索这一高效且有效的近似解码算法,包括其核心思想、具体步骤和实现细节(如长度归一化)。

波束搜索是机器翻译、文本摘要、对话生成等序列生成任务中不可或缺的核心技术。理解其原理,有助于你更好地调优生成模型并获得更高质量的输出。
课程 P35:CS 182 第11讲 第3部分 - 序列到序列模型与注意力机制 🧠

在本节课中,我们将要学习序列到序列模型中的一个核心改进概念:注意力机制。我们将了解传统序列到序列模型的瓶颈问题,以及注意力机制如何通过允许解码器在生成每个词时“查看”编码器的不同部分来解决这个问题,从而显著提升模型处理长序列的能力。
传统序列到序列模型的瓶颈问题 🔍
上一节我们介绍了基础的序列到序列模型。本节中我们来看看该模型存在的一个关键限制。

序列到序列模型存在一个问题,它限制了模型可以处理的输入序列的最大长度。这个问题可以称为瓶颈问题。本质上,所有关于源序列的信息都必须被压缩到编码器的最后一个隐藏状态中。在解码开始时,解码器对源序列一无所知,它唯一的信息来源就是编码器放入第一个解码器隐藏状态的内容。
当输入序列很长时,这可能是一个大问题。这形成了一个瓶颈:编码器需要非常小心地将所有正确的信息塞进解码器的第一个隐藏状态,解码器才能生成正确的内容。
注意力机制的核心思想 💡
上一节我们介绍了瓶颈问题,本节中我们来看看如何通过注意力机制来缓解这个问题。
也许我们可以放松对编码器的要求,如果我们以某种方式允许解码器在解码时“窥视”输入序列的更多部分。这样可能会更好。本质上,编码器只需将输入序列的一般摘要放入隐藏状态,而解码器在生成每个词时,仍然可以引用输入序列来获取具体的细节。
例如,当输入是一个包含历史事件的段落时,编码器可以编码段落的大意和结构,但不必存储具体的年份。当解码器需要生成年份时,它可以“回头看”输入序列,取出对应的信息。这就像在解码时有一条“捷径”:如果你忘了句子的主语是什么,你可以回头看编码后的句子,把主语信息“拉”到当前解码步骤的输入中。
如果我们能做到这一点,将大大降低瓶颈的重要性,编码器的压力会变小,处理长序列也会变得容易得多。

注意力机制如何工作 ⚙️
上一节我们提出了注意力机制的想法,本节中我们来看看它在数学和流程上是如何具体实现的。
以下是注意力机制的基本工作流程:
-
编码阶段:编码器RNN的每一步都会生成一小段信息,我们称之为键。键是一个向量,直观上表示该时间步存在什么类型的信息。键是由编码器的隐藏状态通过一个学习到的函数(如线性层)生成的。
- 公式:
k_t = f_k(h_t)
- 公式:
-
解码阶段:解码器RNN的每一步也会生成一个向量,我们称之为查询。查询向量直观上表示在当前解码步骤,我们想要什么类型的信息。
- 公式:
q_l = f_q(s_l)
- 公式:
-
计算相关性:我们将当前解码步骤的查询向量
q_l与编码器所有时间步的键k_t进行比较(通常计算点积),得到一个注意力分数。这个分数表示每个编码器时间步的信息与当前解码步骤的相关性。- 公式:
e_{t,l} = k_t^T · q_l
- 公式:
-
生成注意力权重:对所有编码器时间步的注意力分数
e_{t,l}应用 Softmax 函数,将其转化为和为1的注意力权重α_{t,l}。权重最大的编码器时间步,其信息被认为与当前解码步骤最相关。- 公式:
α_{t,l} = softmax(e_{t,l})
- 公式:
-
生成上下文向量:使用注意力权重
α_{t,l}对编码器的所有隐藏状态h_t进行加权求和,得到一个上下文向量a_l。这个向量融合了编码器所有步骤的信息,但由最相关的步骤主导。- 公式:
a_l = Σ_{t} (α_{t,l} * h_t)
- 公式:
-
使用上下文向量:将生成的上下文向量
a_l提供给解码器,帮助它生成当前步骤的输出ŷ_l。上下文向量可以以不同方式整合:- 作为生成输出函数的额外输入:
ŷ_l = f(s_l, a_l) - 作为下一个解码器RNN步骤的额外输入:
s_{l+1} = RNN(s_l, [y_l, a_l]) - 作为堆叠RNN中下一层的输入。
- 作为生成输出函数的额外输入:
关键在于,键和查询函数 f_k 和 f_q 的具体含义是模型在训练过程中自动学习的,我们无需手动指定。整个网络是端到端训练的。
注意力机制的工作示例 📝

上一节我们介绍了抽象的数学公式,本节中我们通过一个具体例子来直观理解注意力机制的工作流程。
假设我们要将英文句子“A puppy is cute”翻译成中文。以下是注意力机制在解码过程中的步骤:
-
编码器处理输入序列“A”、“puppy”、“is”、“cute”,为每个词生成隐藏状态
h_1,h_2,h_3,h_4和对应的键k_1,k_2,k_3,k_4。 -
解码第一步(生成“一只”):
- 解码器初始状态生成查询
q_1。 - 计算
q_1与所有键k_1...k_4的点积,得到分数e_{1,1},e_{2,1},e_{3,1},e_{4,1}。 - 对分数进行Softmax,得到权重
α_{1,1},α_{2,1},α_{3,1},α_{4,1}。假设α_{2,1}最大(对应“puppy”)。 - 用权重对隐藏状态加权求和,得到上下文向量
a_1(主要包含h_2的信息)。 - 解码器结合自身状态
s_1和上下文向量a_1,生成输出“一只”。
- 解码器初始状态生成查询

- 解码第二步(生成“小狗”):
- 解码器新状态生成查询
q_2。 - 重新计算
q_2与所有键的点积,得到新的分数和权重。此时权重分布可能变化。 - 生成新的上下文向量
a_2。 - 解码器结合
s_2和a_2,生成输出“小狗”。
- 解码器新状态生成查询
这个过程在解码的每一步重复进行,使得模型能动态地关注输入序列的不同部分。

注意力机制的常见变体 🔄
上一节我们看了基础的注意力机制,本节中我们来看看几种常见且重要的变体。
以下是几种值得了解的注意力机制变体:
- 点积注意力:这是最简单的一种。直接使用编码器和解码器的隐藏状态作为键和查询,即
k_t = h_t,q_l = s_l。注意力分数就是h_t和s_l的点积。实现简单,但表达能力可能有限。 - 乘法注意力:这是最常用的形式之一。键和查询是隐藏状态的线性变换:
k_t = W_k * h_t,q_l = W_q * s_l。由于RNN隐藏状态本身已是非线性的产物,线性变换足以提供强大的表达能力。计算注意力分数e_{t,l} = (W_k * h_t)^T * (W_q * s_l) = h_t^T * (W_k^T W_q) * s_l,可以合并矩阵W_k^T W_q为一个矩阵W_e来学习,提高效率。 - 加性注意力:比基础注意力稍复杂。它不仅对隐藏状态
h_t应用键函数k,还应用一个独立的值函数v。在计算上下文向量时,不是对h_t加权求和,而是对转换后的值v_t = f_v(h_t)加权求和。这提供了更大的灵活性,形成了“键-值对”的直观解释:用键来匹配查询,用值来传递信息。
注意力机制的优势与总结 🏆
在本节课中,我们一起学习了注意力机制的原理、实现和变体。现在我们来总结一下为什么注意力机制如此强大。

注意力机制的核心优势在于,它为解码器的每一步与编码器的每一步建立了直接连接。
- 缓解梯度传播问题:在普通RNN中,连接两个相距
n步的时间点需要经过O(n)步,梯度需要沿这条长路径传播,容易导致梯度消失或爆炸。而通过注意力机制,这条路径的长度总是 O(1),因为上下文向量a_l是直接基于所有编码器状态计算出来的。这为梯度提供了更短、更稳定的传播路径,使得模型训练更加容易和高效。 - 突破信息瓶颈:解码器不再依赖于编码器最后一个隐藏状态所压缩的全部信息,而是可以在每个时间点动态地访问编码器序列中最相关的部分。这极大地缓解了信息瓶颈问题,使模型能够更有效地处理长序列。
- 提升模型表现:注意力权重
α_{t,l}本身具有可解释性,我们可以通过可视化这些权重来理解模型在生成每个输出词时“关注”了输入序列的哪些部分。
注意力机制是一个极其强大的概念,它不仅是改进序列到序列模型的关键,更是现代Transformer架构的基石。在下一节课中,我们将在此基础上进一步深入,探讨如何构建完全基于注意力的模型。

本节课总结:我们一起学习了序列到序列模型中的注意力机制。我们首先分析了传统模型存在的信息瓶颈问题,然后详细介绍了注意力机制如何通过让解码器动态“查看”编码器不同部分来解决这个问题。我们学习了其核心工作流程,包括键、查询、注意力分数、权重和上下文向量的计算,并通过示例和变体加深了理解。最后,我们总结了注意力机制在改善梯度流动、处理长序列和提升模型可解释性方面的巨大优势。
课程 P36:CS 182 - 第12讲 - 第1部分 - Transformer 🧠
在本节课中,我们将学习一种全新的序列处理模型——Transformer。我们将从回顾注意力机制开始,探讨如何完全摒弃循环连接,仅依靠注意力来构建强大的序列模型。课程将涵盖自注意力、位置编码、多头注意力等核心概念,并解释如何将它们组合成一个完整的Transformer架构。

从注意力到自注意力 🔄
上一节我们讨论了如何利用注意力机制增强序列到序列模型处理长期依赖的能力。注意力机制允许解码过程的每一步直接“关注”输入序列中的任意位置。
本节中,我们来看看如何完全摆脱循环连接,构建一个纯粹基于注意力的模型。
注意力机制回顾
在序列到序列模型中,编码器(绿色部分)的每一步会输出一个键(Key,例如 k1, k2, k3)。解码器(蓝色部分)的每一步会基于其当前的RNN隐藏状态输出一个查询(Query,例如 q2, q3)。
注意力计算步骤如下:
- 计算当前查询与所有编码器步骤的键之间的点积。
- 将点积结果通过 softmax 函数,得到一组权重(注意力分布)。
- 使用这些权重对编码器的隐藏状态(或对应的值 Value)进行加权求和。
这为解码的当前步骤提供了一条从编码器获取信息的快捷路径。
迈向纯注意力模型
一个自然的问题是:既然注意力原则上可以从输入序列的任何位置获取信息,我们是否还需要循环连接?
理论上,如果注意力机制不仅能访问输入序列,还能访问输出序列中已生成的部分,那么它就能完成循环神经网络(RNN)的所有功能,甚至更多。

然而,直接应用原有的注意力机制会遇到问题:
- 解码器状态不可见:在解码步骤
l=2时,我们无法访问解码器之前的状态(如s1和s0),因此无法知道之前生成了什么内容。 - 编码器缺乏时序依赖:编码器的每一步完全独立,忽略了单词之间的依赖关系。
为了解决这些问题,我们需要引入自注意力(Self-Attention)。
自注意力机制详解 🧩
自注意力是Transformer的核心。它不区分编码器和解码器,而是让序列中的每一个元素都能直接关注到序列中的所有其他元素(包括其自身)。
基本计算过程
假设我们有一个输入序列 X = [x1, x2, x3]。
-
生成表示:首先,通过一个共享权重的线性变换(或小型前馈网络)为每个输入
xt生成一个初始隐藏表示ht。- 公式:
ht = f(xt),其中f是共享的函数。
- 公式:
-
计算键、值、查询:对每个
ht,我们使用三个不同的、可学习的权重矩阵(W_K,W_V,W_Q)来生成对应的键(kt)、值(vt)和查询(qt)。- 公式:
kt = W_K * htvt = W_V * htqt = W_Q * ht
- 公式:
-
计算注意力分数:对于序列中的每一个位置
i(其查询为qi),我们计算它与所有位置j(其键为kj)的相似度(通常用点积)。- 公式:
score_ij = qi · kj
- 公式:
-
计算注意力权重:对每个位置
i的所有score_ij应用 softmax 函数,得到归一化的注意力权重α_ij。这表示位置i对位置j的关注程度。- 公式:
α_ij = softmax(score_ij)
- 公式:
-
加权求和输出:位置
i的最终输出output_i是所有位置的值vj按其对应权重α_ij的加权和。- 公式:
output_i = Σ_j (α_ij * vj)
- 公式:
通过这种方式,自注意力层整合了序列中所有位置的信息,为每个位置生成了一个新的、融合了全局上下文信息的表示。
我们可以将多个这样的自注意力层堆叠起来,构建一个深度网络,从而对序列进行越来越复杂的处理。
构建Transformer的关键组件 ⚙️
自注意力的基本概念虽然强大,但要构建一个实际可用的Transformer模型,还需要解决几个关键限制。以下是需要处理的核心问题:
1. 位置编码
问题:自注意力机制本身是排列不变的。打乱输入序列的顺序,自注意力层会产生完全相同的输出(仅顺序改变)。这对于自然语言等顺序至关重要的任务是不可接受的。
解决方案:我们需要为输入序列注入位置信息。具体做法是为序列中每个位置的输入向量添加一个位置编码向量。这个编码向量通常使用正弦和余弦函数生成,使其具有固定的模式并能处理比训练时更长的序列。
- 公式(原始Transformer使用):
PE(pos, 2i) = sin(pos / 10000^(2i/d_model))PE(pos, 2i+1) = cos(pos / 10000^(2i/d_model))- 其中
pos是位置,i是维度索引,d_model是模型维度。
2. 多头注意力
问题:单一一组键、值、查询(一个“头”)可能只能捕捉到一种类型的依赖关系(例如语法依赖),这限制了模型的表达能力。

解决方案:使用多头注意力。我们并行地使用多组不同的 W_K, W_V, W_Q 矩阵,从而让模型能够同时关注来自不同表示子空间的信息。
- 过程:将输入线性投影到
h(头数)个不同的子空间,在每个子空间独立进行自注意力计算,然后将所有头的输出拼接起来,再经过一次线性投影。 - 代码概念:
# 伪代码示意 head_outputs = [] for i in range(num_heads): K_i = linear_projection_i_K(input) V_i = linear_projection_i_V(input) Q_i = linear_projection_i_Q(input) head_i = self_attention(K_i, V_i, Q_i) head_outputs.append(head_i) multi_head_output = concat(head_outputs) final_output = linear_projection(multi_head_output)
3. 引入非线性
问题:到目前为止描述的自注意力计算本质上是线性的(除了softmax)。output_i 是值 vj 的线性组合,而 vj 本身又是 hj 的线性变换。纯粹的线性堆叠表达能力有限。
解决方案:在自注意力层之后和层与层之间,引入前馈神经网络(Feed-Forward Network, FFN)。这是一个简单的两层网络,通常包含一个非线性激活函数(如ReLU)。
- 公式:
FFN(x) = max(0, x * W1 + b1) * W2 + b2 - 在Transformer中,每个编码器和解码器层都包含一个自注意力子层和一个前馈神经网络子层。
4. 掩码注意力(用于解码)
问题:标准的自注意力在计算当前位置输出时,会“看到”序列的所有位置,包括未来的位置。这在训练语言模型或进行序列生成(解码)时是不允许的,因为模型不应该利用未来的信息来预测当前词。
解决方案:在解码器的自注意力层中使用掩码注意力。具体做法是在计算注意力分数后、应用softmax之前,将未来位置对应的分数设置为一个极大的负数(如 -1e9)。这样,经过softmax后,未来位置的权重就几乎为0。
- 代码概念:
# scores 是注意力分数矩阵, shape: [seq_len, seq_len] mask = torch.triu(torch.ones(seq_len, seq_len), diagonal=1).bool() # 生成上三角掩码(不包括对角线) scores.masked_fill_(mask, -1e9) # 将未来位置分数掩掉 attention_weights = softmax(scores, dim=-1)
总结 📝
本节课中,我们一起学习了Transformer模型的基础构建块。
- 我们从注意力机制出发,探讨了构建纯注意力模型的可能性。
- 我们深入讲解了自注意力的核心计算过程,它允许序列中的每个元素直接关注所有其他元素。
- 我们分析了自注意力的几个关键限制,并介绍了解决这些问题的核心组件:
- 位置编码:为模型注入序列的顺序信息。
- 多头注意力:让模型能够并行关注不同方面的信息,增强表达能力。
- 前馈神经网络:在注意力层之间引入非线性变换。
- 掩码注意力:确保解码器在生成时不会“偷看”未来的信息。

这些组件共同构成了Transformer编码器和解码器层的基础。在接下来的课程中,我们将看到如何将这些层堆叠起来,并加入残差连接和层归一化等技巧,最终构建出完整的Transformer模型,用于处理机器翻译等序列到序列的任务。

课程 P37:CS 182 第12讲 第2部分 - Transformer 详解 🧠

在本节课中,我们将学习如何将基本的自注意力机制构建成一个实用的序列模型。我们将探讨位置编码、多头注意力、非线性层以及掩码注意力等关键概念,这些是构成现代Transformer架构的核心组件。
位置编码:让模型感知顺序 📍
上一节我们介绍了自注意力机制。本节中我们来看看如何让模型理解序列中单词的顺序。

自注意力中的所有操作都是置换不变的。这意味着如果你打乱单词的顺序,你会得到完全相同的注意力向量,只是排列方式相同。这与循环模型非常不同,因为循环模型一次看一个单词,并记住以前见过的单词的顺序。因此,一个天真的自注意力模型看到的只是一袋无序的单词,无法理解“他用圆周率打我”和“我用圆周率打他”之间的区别。
单词在句子中的位置承载着重要的信息。为了保留这些信息,我们在模型的输入表示中添加位置编码。位置编码意味着第一个隐藏状态 h_t 是输入 x_t 和时间步 t 的函数,而不仅仅是 x_t 的函数。
绝对位置 vs. 相对位置
开发位置编码的一种简单方法是将时间索引 t 直接拼接到输入 x_t 上。但这并不是一个好主意,因为在大多数应用中,相对位置往往比绝对位置更重要。例如,在句子“我每天都遛狗”和“我遛狗”中,“我”和“狗”的绝对索引不同,但“狗”出现在“我”之后的相对位置是相似的,这才是关键信息。
我们希望位置编码能更多地关注相对位置。实现这一点的一种方法是使用频域表示。
正弦/余弦位置编码
这是原始Transformer论文中使用的方法。位置编码 P_t 是一个与输入嵌入 x_t 维度相同的向量(维度为 d)。向量中的每个元素是时间步 t 的正弦或余弦函数,其频率随维度变化。

公式:
对于位置 pos 和维度 i:
PE(pos, 2i) = sin(pos / 10000^(2i/d))PE(pos, 2i+1) = cos(pos / 10000^(2i/d))
这里的频率是 10000^(2i/d)。这意味着向量的前半部分(i 较小)频率较高,振荡较快,可以捕捉细粒度的位置信息(如奇偶性);后半部分频率较低,振荡较慢,可以捕捉粗粒度的位置信息(如句子前半部分或后半部分)。这种编码能很好地获取相对位置感。

可学习的位置编码

我们也可以将位置编码矩阵 P(形状为 [最大序列长度, d])作为模型的可学习参数。在每个时间步,我们将学习到的向量 p_t 与输入嵌入 x_t 结合。

优点:更灵活,可能通过优化找到对任务最有效的位置表示。
缺点:需要预设最大序列长度,且无法泛化到比训练时更长的序列。
如何整合位置编码

常见且有效的方法是先将输入 x_t 通过一个嵌入层(如线性层加非线性激活函数),然后将位置编码 p_t 直接加到嵌入结果上,作为第一个隐藏状态。
公式:
h_t^0 = Embedding(x_t) + p_t

多头注意力:捕捉多种关系 👥

既然我们完全依赖注意力机制,那么让模型能够同时关注序列中多种不同类型的信息就非常可取。例如,在处理一个句子时,我们可能希望同时关注主语和动词。
基本的单头注意力机制通过Softmax操作,倾向于主要关注一个时间步的信息。很难让一个注意力头同时有效地拉取主语和动词这两个在不同位置的信息。

解决方法就是使用多头注意力。我们不是为每个时间步生成一组(键、查询、值),而是生成多组。每组对应一个独立的“头”,每个头有自己的键、查询、值权重矩阵。
以下是多头注意力的计算步骤:
- 对于每个头
i,独立计算其注意力分数和权重。Q_i = h * W_Q^i,K_i = h * W_K^i,V_i = h * W_V^i- 注意力分数:
e_{t,l}^i = Q_t^i · K_l^i - 对每个头
i在时间步t上独立应用Softmax,得到注意力权重α_{t,l}^i。
- 每个头计算其输出:
head_i = Σ_l α_{t,l}^i * V_l^i。 - 将所有头的输出在特征维度上拼接起来,形成该时间步的完整注意力输出。
MultiHead(h)_t = Concat(head_1, head_2, ..., head_h) * W_O(W_O是输出投影矩阵)
这样,不同的头可以学习关注不同的信息模式(例如,一个头关注主语,另一个头关注动词),然后将这些信息组合起来,供下一层使用。在经典Transformer中,通常使用8个头。
引入非线性:进行复杂计算 🔄

到目前为止,自注意力本质上是线性的。注意力权重 α 是通过Softmax(非线性)计算得到的,但最终的注意力输出是值 V 的线性组合(V 本身是 h 的线性变换)。这意味着自注意力层擅长从其他时间步“提取”信息,但不擅长对这些信息进行复杂的非线性处理。

为了让模型能够执行更复杂的操作(例如,根据主语类型和动词组合决定输出),我们需要在过程中插入非线性变换。
位置前馈网络
标准做法是在每个自注意力层之后,交替使用一个位置前馈网络。这个网络在每个时间步上独立地应用相同的非线性函数。
结构:
- 自注意力层:在时间步之间交换信息(“内存提取”)。
- 位置前馈网络:在每个时间步独立处理信息(“实际计算”)。

这个位置前馈网络通常是一个简单的两层全连接网络,中间有一个非线性激活函数(如ReLU或GELU)。
公式:
FFN(x) = max(0, x * W_1 + b_1) * W_2 + b_2
通过这种“提取-处理-再提取”的交替结构,模型能够进行更深入和复杂的计算。


掩码注意力:用于序列生成 🎭
自注意力机制的一个问题是它无法区分过去和未来。在训练时,处理一个完整的输入序列(如翻译任务中的源语言句子)时,这没有问题。但在序列生成(如解码或语言模型预测下一个词)时,这会导致严重问题。
在生成模式下,我们一次生成一个词。在生成第 t 个词时,模型只能依赖于已经生成的 1 到 t-1 个词。如果自注意力机制可以“看到”未来的时间步(第 t+1, t+2, ... 步),就会造成信息泄漏和循环依赖,使得模型无法进行自回归生成。
解决方案是使用掩码注意力(或因果注意力)。其核心思想是:在计算时间步 t 的注意力时,只允许它关注时间步 1 到 t(包括 t 自身),禁止关注任何 t+1 之后的未来时间步。
实现方法:
在计算注意力分数矩阵 e 后,在应用Softmax之前,将一个掩码矩阵加到 e 上。这个掩码矩阵在 l > t(即查询位置早于键位置)的位置上设置为一个极大的负数(如 -1e9),在其他位置上设置为0。

公式:
masked_e_{t,l} = e_{t,l} + M_{t,l}, 其中 M_{t,l} = 0 if l <= t else -inf
然后对 masked_e_t 应用Softmax。由于 e^{-inf} = 0,未来位置的注意力权重被强制为0。
这样,模型在生成每个词时,都只能基于已生成的上下文,从而可以用于自回归解码。
总结 📝

本节课中我们一起学习了将基础自注意力机制转化为强大、实用的Transformer模型所需的四个关键修改:
- 位置编码:通过正弦/余弦函数或可学习参数,为模型注入序列顺序信息,使其能理解单词的相对位置。
- 多头注意力:使用多个独立的注意力头并行工作,使模型能够同时关注输入序列中不同方面或关系的信息。
- 非线性层(位置前馈网络):在自注意力层之间交替插入非线性变换,使模型不仅能聚合信息,还能进行复杂的计算和处理。
- 掩码注意力:在生成任务中,通过屏蔽未来信息,使自注意力模型能够用于自回归序列生成,避免信息泄漏。

将这些构建块组合起来,就构成了Transformer编码器和解码器的基础。在接下来的部分,我们将看到如何利用这些组件构建完整的、仅基于自注意力的序列到序列模型。

课程 P38:CS 182- 第12讲 - 第3部分 - Transformer模型 🧠
在本节课中,我们将把之前学到的知识整合起来,深入探讨经典的Transformer模型。我们将了解其核心架构、工作原理,以及它如何通过自注意力机制处理序列数据。

概述
Transformer是一种利用连续的自注意力和位置前馈网络来处理序列的模型。它最初被设计为序列到序列(Seq2Seq)模型,但后来也被广泛用于语言建模等任务。本节课我们将详细拆解其结构,并与传统的RNN Seq2Seq模型进行对比。
Transformer与RNN Seq2Seq模型的对比
上一节我们介绍了注意力机制,本节中我们来看看如何用Transformer构建一个完整的序列到序列模型。传统的RNN Seq2Seq模型包含一个RNN编码器和一个RNN解码器,它们可以是多层的。

为了将其转换为Transformer,我们需要将编码器和解码器替换为连续的自注意力层,并与位置前馈网络交替堆叠。
以下是Transformer编码器和解码器的核心结构对比:

- 编码器:接收输入序列
X及其位置编码P。数据首先经过一个多头自注意力层(无掩码),然后通过一个位置前馈网络(一个非线性函数)。这个过程会重复N次(例如6次)。 - 解码器:接收输出序列的位置嵌入。首先使用掩码多头自注意力(确保当前位置只能关注之前的位置),然后经过位置前馈网络。接着,解码器会进行交叉注意力操作,即利用编码器产生的所有隐藏向量。交叉注意力之后是另一个位置前馈网络。解码器的每个块也重复
N次。最后,在每个位置应用Softmax函数来生成输出。
解码过程是逐步进行的,因为使用了掩码注意力。类似于RNN,我们将上一步的预测输出作为下一步的输入。
交叉注意力机制详解

交叉注意力是Transformer解码器的关键组件,其工作原理与上节课介绍的标准注意力机制非常相似。
我们定义:
h_t^l:编码器第l层的隐藏状态(位置前馈网络的输出)。s_t^l:解码器第l层的隐藏状态。
交叉注意力的计算步骤如下:
- 查询(Query):通过对解码器状态
s_t^l应用线性变换W_Q^l得到:q_t^l = W_Q^l * s_t^l - 键(Key):通过对编码器状态
h_t^l应用线性变换W_K^l得到:k_t^l = W_K^l * h_t^l - 值(Value):通过对编码器状态
h_t^l应用线性变换W_V^l得到:v_t^l = W_V^l * h_t^l - 注意力分数与输出:计算查询与所有键的点积,应用Softmax得到注意力权重
α,然后对值进行加权求和,得到交叉注意力输出c^l。
公式表示如下:
注意力分数: e_{t,l} = q_l^T * k_t / sqrt(d_k)
注意力权重: α_{t,l} = softmax(e_{t,l})
交叉注意力输出: c^l = Σ_t (α_{t,l} * v_t)
在实际的Transformer模型中,交叉注意力也是多头的,通常与自注意力使用相同数量的头(例如8个)。
层归一化
在深入完整模型之前,我们需要了解一个重要的技术细节:层归一化。它对Transformer的稳定训练至关重要。

批归一化(Batch Norm)在序列模型中应用困难,因为序列长度可变且批次可能很小。层归一化(Layer Norm)提供了一个解决方案。
以下是批归一化与层归一化的核心区别:
- 批归一化:在一批数据的所有样本上,计算每个特征维度(共
D维)的均值和标准差(得到D维的μ和σ),然后对每个样本进行归一化。 - 层归一化:在单个数据样本的所有特征上,计算标量均值
μ和标量标准差σ,然后用这个标量对样本的所有特征进行归一化。
层归一化公式如下(对于单个样本的激活向量 a,维度为 D):
μ = (1/D) * Σ_{j=1 to D} a_j
σ = sqrt( (1/D) * Σ_{j=1 to D} (a_j - μ)^2 )
a'_j = γ_j * ( (a_j - μ) / σ ) + β_j
其中 γ 和 β 是可学习的每维缩放和偏移参数。
层归一化不依赖批次信息,因此非常适合序列模型。
完整Transformer模型架构
现在,我们可以将所有部分组合起来,形成完整的Transformer模型。以下架构基于论文《Attention Is All You Need》。

以下是编码器和解码器的详细工作流程:
编码器
- 输入嵌入与位置编码:将输入词元转换为向量并加上位置编码,得到
h_bar_t^0。 - 编码器块(重复N次):
- 多头自注意力:输入为
h_bar_t^{l-1},输出为a_t^l。 - 残差连接与层归一化(Add & Norm):
a_bar_t^l = LayerNorm( h_bar_t^{l-1} + a_t^l )。这是一个残差连接。 - 位置前馈网络:一个两层神经网络(线性层 -> ReLU -> 线性层),作用于
a_bar_t^l,输出h_t^l。 - 另一个Add & Norm:
h_bar_t^l = LayerNorm( a_bar_t^l + h_t^l )。结果传递给下一个块。
- 多头自注意力:输入为
- 编码器输出:运行完所有N个块后,得到最终的
h_bar_t^l。这些状态将用于为解码器生成键(K)和值(V)。
解码器
- 输出嵌入与位置编码:类似编码器,但用于目标序列。
- 解码器块(重复N次,与编码器块对应):
- 掩码多头自注意力:确保当前位置只能关注之前的位置。后接Add & Norm。
- 交叉注意力:查询(Q)来自上一步的解码器状态,键(K)和值(V)来自对应层的编码器输出
h_bar_t^l。后接Add & Norm。 - 位置前馈网络:结构与编码器相同,但参数独立。后接Add & Norm。
- 输出层:在最后一个解码器块之后,应用一个线性层和Softmax函数,生成每个位置上词元的概率分布。
解码过程是自回归的:生成第一个词元后,将其作为输入,再生成下一个词元,依此类推。
Transformer的优缺点

Transformer架构具有一些显著的优点和缺点。
缺点:
- 计算复杂度:自注意力的计算复杂度是序列长度
N的平方O(N^2)。但对于长序列,大部分计算实际上消耗在前馈网络部分,因此实际训练成本可能低于RNN。 - 实现复杂性:涉及位置编码、层归一化等设计,需要仔细的超参数调优。
优点:
- 远程依赖:每个输出位置通过注意力直接连接到所有输入和输出位置,路径长度仅为1,能更好地捕捉长程依赖。
- 并行化:编码器完全可并行计算,解码器在训练时也可并行(使用掩码),能充分利用GPU等硬件。
- 可扩展性:可以堆叠更多层(例如6层或更多),而深层RNN则难以训练。
- 性能卓越:在许多任务上,尤其是自然语言处理领域,Transformer的表现显著优于RNN和CNN模型。
可以说,Transformer是过去十年中序列建模最重要的进展之一。
Transformer的应用成果

Transformer在多个任务上取得了突破性成果。

- 机器翻译:在英德和英法翻译任务上,Transformer模型(包括“Base”和“Big”版本)在取得更高BLEU分数的同时,所需的训练计算量(FLOPs)却低于其他基于卷积或循环神经网络的模型(包括集成模型)。这表明Transformer效率更高,扩展性更好。
- 文本摘要:在文本摘要任务上,Transformer模型相比带有注意力的Seq2Seq RNN模型,在困惑度(Perplexity)指标上取得了显著提升,困惑度越低表示模型对序列的概率分布建模越好。
此外,Transformer作为语言模型(如GPT、BERT)更是产生了革命性影响,这将在后续课程中讨论。
总结

本节课我们一起学习了Transformer模型的核心内容。我们从对比传统的RNN Seq2Seq模型入手,详细剖析了Transformer编码器和解码器的结构,重点讲解了自注意力、交叉注意力和层归一化等关键组件。我们了解了完整Transformer的工作流程,并分析了其优缺点。最后,我们看到了Transformer在机器翻译和文本摘要等任务上取得的卓越成果。Transformer通过其强大的并行能力和对长程依赖的有效建模,已成为现代自然语言处理的基石架构。
课程 P39:CS 182 第十三讲 第一部分 - NLP 🧠
在本节课中,我们将学习如何利用深度学习处理自然语言。我们将从简单的单词表示方法开始,逐步深入到更复杂的、能够理解上下文信息的模型。核心目标是理解如何利用海量的无标签文本数据,来提升自然语言处理任务的性能。
概述:无监督预训练的力量
在之前的课程中,我们讨论了循环神经网络和序列到序列模型。本节课,我们将在此基础上,探讨如何将这些思想应用于自然语言处理领域。

自然语言处理是深度学习的一个重要应用领域。然而,设计本节课时,我必须在众多NLP主题中选择一个来重点讲解。我选择深入探讨的主题是无监督预训练。我认为,这是深度学习影响NLP最重要、最根本的方式之一,它甚至超越了NLP本身,体现了深度学习算法的基本优势。

深度学习模型通常在拥有大量数据时表现最佳。随着模型规模的增大,数据集的规模也在同步增加。在许多情况下,数据集本身对于方法的成功至关重要,其重要性不亚于模型架构和训练方法的创新。
对于NLP来说,好消息是:世界上存在海量的文本数据。理论上,NLP应该是深度学习大展身手的绝佳领域。但坏消息是:这些数据大多没有标签。例如,我们无法直接从报纸文章学习如何将英语翻译成法语。我们拥有的无标签数据(如书籍、新闻、网页文本)数量,远超有标签数据(如英法翻译对)。
因此,我们面临的核心挑战是:如何利用免费、海量的无标签文本数据,来帮助我们将深度学习方法更好地应用于NLP? 这就是本节课的主题。

我们可以用无标签数据做什么?
虽然不能直接用无标签文章学习翻译,但我们可以用它来学习良好的语言表示。例如,学习让语义相似的单词在表示空间中彼此靠近。这样,当我们基于这种表示去学习下游任务(如翻译)时,就会更容易。
我们可以想象一种表示学习的“光谱”:
- 在光谱的一端,是局部或非上下文相关的表示。例如,传统的词嵌入,每个词有一个固定的向量,不考虑它在句子中的具体用法。
- 在光谱的另一端,是全局上下文相关的表示。例如,预训练的语言模型,它会读取整个句子甚至段落,然后为句子中的每个词生成一个定制化的、考虑其上下文的表示。
在本节课中,我们将讨论这个光谱的两个极端。首先从词嵌入开始,然后花较多时间讨论预训练语言模型,以及它们如何为NLP提供更强大、更优越的表示。

从简单开始:如何表示单词?🔤
在讨论RNN和序列到序列模型时,我们见过独热编码表示法。一个词的独热向量维度等于词典大小,只有对应词索引的位置为1,其余全为0。
公式:对于一个词 w,其独热向量 v 满足 v[i] = 1 当且仅当 i 是 w 在词典中的索引。
这种表示法简单,包含了“这个词是什么”的信息,但无法体现词与词之间的关系和相似性。两个近义词的独热向量看起来和任意两个不相关的词一样不同。
我们希望获得一种更有意义的词表示,使得语义相似的词在表示空间中也彼此接近。那么,如何构建这种词表示呢?
Word2Vec:让相似的词靠近
我们追求的是类似 Word2Vec 这样的方法。它将单词嵌入到一个向量空间中,使得语义相似的词靠得很近。此外,在这个潜在空间中的向量运算也具有语义意义(例如,“国王”-“男人”+“女人”≈“女王”)。
其基本思想是:一个词的含义很大程度上由其上下文(即它附近出现的词)决定。两个词越能在相似语境中互换使用,它们就越相似。
具体来说,我们定义一个中心词和其上下文词(例如,中心词前后5个单词范围内的词)。核心问题是:我们能否从一个词的嵌入预测其邻居?
我们可以将其构建为一个预测问题:给定中心词 c,预测其上下文词 o 的概率。我们使用逻辑回归(Softmax)框架,但参数不是权重,而是每个词的向量表示。
公式:P(o|c) = softmax(u_o^T * v_c),其中 u_o 是词 o 作为上下文时的向量,v_c 是词 c 作为中心词时的向量。
训练时,我们最大化语料库中所有 (c, o) 配对的对数似然。优化参数是所有词的 u 和 v 向量。最终,一个词的表示可以取其 u 和 v 向量的平均值。
然而,直接使用Softmax计算成本高昂,因为分母需要对整个词典求和。因此,实践中常采用一种更高效的方法。
负采样:一种高效的训练技巧
为了规避对整个词典求和的问题,Word2Vec 采用了一种称为负采样的技术。它将问题转化为一个二分类问题:给定一对词 (c, o),判断 o 是否是 c 的真实上下文词。
公式:对于正样本(真实上下文对),概率为 P(D=1|c,o) = σ(u_o^T * v_c),其中 σ 是Sigmoid函数。
对于负样本(随机采样的非上下文词 w),概率为 P(D=0|c,w) = 1 - σ(u_w^T * v_c) = σ(-u_w^T * v_c)。
训练目标是最大化正样本的对数概率,同时最小化(即最大化负样本的负对数概率)随机采样的负样本的对数概率。
代码逻辑示意:
# 对于每个训练样本 (center_word, context_word)
# 1. 计算正样本得分
pos_score = sigmoid(dot(u[context_word], v[center_word]))
loss = -log(pos_score) # 最小化负对数似然
# 2. 采样 k 个负样本词
for negative_word in k_negative_samples:
neg_score = sigmoid(dot(u[negative_word], v[center_word]))
loss -= log(1 - neg_score) # 同样最小化负样本的概率
通过这种方式,模型学习将中心词的向量 v_c 拉向真实上下文词的向量 u_o,同时推离随机负样本词的向量 u_w。
训练得到的词向量可以用于下游任务,替代独热编码,因为它们包含了从海量无标签数据中学到的语义和语法关系。
总结
本节课我们一起学习了自然语言处理中利用无标签数据的核心思想——无监督预训练。
我们首先指出了NLP领域数据丰富但标签稀缺的现状,从而引出了学习通用语言表示的重要性。接着,我们从最简单的词表示出发,介绍了Word2Vec模型。该模型基于“词的上下文定义其含义”的分布式假设,通过预测上下文词来学习词向量。为了高效训练,我们详细讲解了负采样技术,它将复杂的Softmax分类问题转化为一系列二分类问题。
最终,我们获得了一种稠密的词向量表示,语义相似的词在向量空间中彼此靠近。这种表示可以作为下游监督任务(如文本分类、机器翻译)的强大特征输入,使得模型即使在较少的有标签数据上也能取得良好性能。

在下一节中,我们将沿着表示学习的“光谱”继续向右移动,探讨更强大的、能够理解完整上下文的预训练语言模型,例如BERT和GPT系列模型。
课程 P4:CS 182 - 第 2 讲,第 1 部分:机器学习基础 🧠
在本节课中,我们将学习机器学习的基本概念。这些概念是后续深度学习课程的基础。我们将从定义学习问题开始,并介绍三种主要的机器学习范式:监督学习、无监督学习和强化学习。
如何制定学习问题
机器学习可以解决许多不同的问题。接下来我们将讨论一种规范的分类方法,尽管也存在其他分类方式。
最常研究的机器学习问题是预测问题:给定一些输入 x,预测一些输出 y。例如,在经典的线性回归问题中,x 对应水平轴的位置,y 对应垂直轴的位置。同样的问题表述也适用于更复杂、更高维的输入,如上节课讨论的图像分类示例,其目标是预测图像中存在的物体标签。
监督学习 📊

上一节我们介绍了预测问题的基本概念,本节中我们来看看监督学习的具体定义。
监督学习问题之所以被称为“监督”,是因为在训练期间,模型会受到真实标签的“监督”。在监督学习中,我们假设在训练过程中获得一个数据集 D(也称为训练集)。该数据集由 (x, y) 元组组成,其中 y 是对应于 x 的真实标签。
例如,要解决图像分类问题,我们会收集狗、猫、长颈鹿和河马的照片,然后由人类仔细检查并为每张照片贴上真实标签,这就构成了我们的训练数据。我们将用它来学习如何对新图片进行分类。
监督学习的目标是学习一个关于 x 的函数 f_θ(这里的 θ 表示参数),使得 f_θ(x) 尽可能近似于真实标签 y。这可以是线性回归设置,也可以是确定照片中物体类别等更复杂的任务。
要实例化一种监督学习方法,我们必须回答几个问题:

以下是必须回答的几个关键问题:
- 如何表示函数
f_θ? 我们必须选择程序的形态以及参数θ如何进入程序。它可能像线性方程一样简单(例如y = θ_0 + θ_1 * x),也可能是多项式回归函数或神经网络。 - 如何衡量
f_θ(x)与y之间的差异? 我们需要一个衡量“接近程度”的概念。在连续值设置中,这可能是(f_θ(x) - y)^2(平方差)。在分类设置中,可能是“0-1损失”(如果f_θ(x) = y则为 1,否则为 0)。也可能是涉及概率的更复杂度量。 - 如何找到最佳的参数
θ? 我们需要设计一种算法来修改θ,使得对于训练集中的(x, y)元组,f_θ(x)尽可能接近y。这被称为优化算法,例如随机搜索、梯度下降或最小二乘法。
无监督学习 🧩
上一节我们讨论了有明确目标(标签 y)的监督学习,本节中我们来看看没有标签时如何进行学习,即无监督学习。
无监督学习从未标记的数据开始。这意味着我们没有 (x, y) 元组,只有 x。例如,我们可能只有网上的照片,我们希望以某种方式分析这些输入,对数据进行分类或聚类,从而理解 x 本身的结构,这可能对下游的预测任务有用。
无监督学习一开始可能显得抽象。我们可以将其制定为生成建模问题。你可能见过生成模型的例子,例如生成对抗网络(GAN)可以生成非常逼真但不存在的人脸图像。这些神经网络是在未标记数据上训练的,因为它们只需要学习如何构造类似于训练数据的图像,而不需要类别标签。
更正式地说,提供给模型的图像来自某个潜在的分布(例如所有人脸的连续空间)。模型的目标是学习这个分布,甚至可能生成它从未见过但依然逼真的新样本。生成模型需要获取数据的内部表示(例如编码鼻子、眼睛、发色等特征),这些表示可用于各种下游应用。生成模型的例子包括 GANs、VAEs 和自回归模型。
无监督学习的另一个重要领域是自监督表示学习。其目的不是直接生成数据,而是在没有直接监督的情况下获取有用的数据表示。这方面的一个重要例子是语言建模,例如 BERT 模型。BERT 解决的任务是:给定一个句子,遮盖其中一些词,然后预测这些词是什么。这个任务本身并不直接有用,但它迫使模型学习语言的有意义表示,这些表示随后可用于机器翻译、情感分析等下游任务。
同样的原理也应用于计算机视觉。例如,从一张图片中裁剪出两个图像块(如猫的耳朵和鼻子),然后要求模型预测这两个图像块的相对位置。做出正确预测需要模型理解图像的结构(如猫长什么样),从而学习到有用的图像表示。
强化学习 🎮

前面两节我们介绍了从静态数据中学习的范式,本节中我们来看看智能体如何通过与动态环境交互来学习,即强化学习。
强化学习涉及一个更复杂的公式,因为它不是从图像预测标签,而是关于智能体在时间序列中采取行动,并观察这些行动的长期后果。在强化学习中,智能体的目标不是采取当前“最好”的行动,而是采取从长远来看能带来最佳结果的行动序列。
在每一个时间点 t,智能体处于某种状态 s_t(类似于输入 x,但带有时间下标)。智能体根据状态输出一个动作 a_t。强化学习的目标是选择一系列动作,以最大化能获得的总奖励。
例如,一个下国际象棋的智能体有很多可能的走法,但奖励(赢或输)只在整盘棋结束时才到来。有趣的是,强化学习实际上包含或推广了常规的监督学习。你可以将监督学习制定为强化学习的一个特例:如果预测正确则获得大奖励,预测错误则获得小奖励,且只有一个时间步。然而,用强化学习方法解决监督学习问题通常更困难,因为你需要通过试错来找出正确答案。
在强化学习中,我们学习函数 f_θ(s_t) 来输出动作,以最大化奖励。奖励可以是任何东西,例如赢得棋局、机器人跑得更快或公司获得更高利润。强化学习的应用非常广泛。
以下是强化学习的一些应用示例:
- 机器人控制:例如,一个机器人学习如何行走。它的动作是发送给电机的命令,观察是传感器数据(如相机图像),奖励是任务成功的度量(如移动速度)。
- 游戏 AI:例如 AlphaGo,它通过与自己进行大量对弈来学习下围棋。
- 商业决策:例如,为电商公司选择仓库库存水平。动作是采购决策,观察是库存水平,奖励是公司利润。
- 其他领域:教育(推荐学生下一步学习内容)、推荐系统(如 YouTube 视频推荐)、医疗(为患者推荐治疗方案)等任何可以表述为顺序决策的问题。

总结

本节课中我们一起学习了机器学习的三种基本范式。
我们首先探讨了如何制定学习问题,并引入了预测的核心概念。接着,我们详细介绍了监督学习,其目标是从带有标签的数据中学习一个映射函数 f_θ(x) ≈ y。然后,我们讨论了无监督学习,它从无标签数据中学习数据的内部结构或表示,例如通过生成建模或自监督学习。最后,我们了解了强化学习,智能体通过与环境交互并获得奖励信号,来学习一系列能最大化长期回报的动作。


理解这些基础范式是深入学习后续复杂模型(如神经网络)的关键。在接下来的课程中,我们将更深入地探讨这些领域的具体算法和应用。
课程 P40:CS 182 第十三讲 第二部分 - NLP 🧠
在本节课中,我们将要学习预训练语言模型。我们将从上下文无关的词嵌入(如 Word2Vec)的局限性出发,探讨如何通过训练语言模型来获得依赖于上下文的词表示。我们将重点介绍两个重要的模型:ELMo 和 BERT,并理解它们如何为下游 NLP 任务提供更强大的语义表示。
预训练语言模型概述
上一节我们介绍了基础的词嵌入方法。本节中,我们来看看预训练语言模型。其核心目标是获得上下文表示。传统的词嵌入(如 Word2Vec)为词典中的每个单词关联一个固定的向量。然而,同一个词在不同语境下可能有不同含义,固定向量无法捕捉这种差异。
例如,在以下两个句子中:
- “我们去打棒球吧。”
- “我昨天看了一场比赛。”

“打”这个词在两句中含义不同(动词 vs. 名词),但 Word2Vec 会赋予它相同的向量表示。

因此,我们需要一种能依赖于上下文的表示方法。高级思路是:训练一个语言模型来预测给定上文后的下一个词。然后,在特定句子(如“我们去打棒球吧”)上运行该模型,并使用模型在特定词处的隐藏状态作为该词的表示。
这种隐藏状态应能代表该词在句子中的角色,因为它包含了预测后续词所需的信息。与 Word2Vec 不同,这种表示依赖于句子中的其他词(至少是上文)。
要实现这一点,需要解决两个问题:
- 如何训练最佳的语言模型?
- 如何将该语言模型用于下游任务?

ELMo:基于双向LSTM的上下文表示 🎭
首先,我们来谈谈 ELMo。ELMo 是“Embeddings from Language Models”的缩写,它是一个基于双向 LSTM 的语言模型。
核心思想与架构
基本 LSTM 语言模型的问题是:一个词(如句子中的第二个词)的表示只依赖于它之前的词,而无法利用其之后的上下文信息。这并不理想,因为一个词的完整含义通常需要整个句子来推断。
ELMo 采用了一种更简单的方法:它训练两个独立的 LSTM 语言模型。
- 前向语言模型:像常规 LSTM 一样,根据上文预测下一个词。
- 后向语言模型:将句子顺序反转,根据“下文”(即原句的后文)预测“前一个”词。
这两个模型都是多层堆叠的 LSTM。对于时间步 t,我们有以下隐藏状态:
- 前向模型:
h_{t}^{forward,1},h_{t}^{forward,2}, ... - 后向模型:
h_{t}^{backward,1},h_{t}^{backward,2}, ...
这些隐藏状态共同构成了该词的上下文表示。
如何使用 ELMo 表示
以下是使用 ELMo 表示的主要步骤:
- 获取表示:将整个目标句子分别输入前向和后向 LSTM 模型。
- 组合表示:对于句子中的每个词,组合其在两个方向、各层的隐藏状态。
- 简单版本:直接连接顶层的前向和后向隐藏状态。
ELMo_t^{simple} = [h_{t}^{forward, top}; h_{t}^{backward, top}] - 复杂/常用版本:对所有层的隐藏状态进行加权平均。公式如下:
ELMo_t^{task} = \gamma^{task} \sum_{l=0}^{L} s_l^{task} h_{t,l}
其中,h_{t,l}是第l层的双向隐藏状态组合,s_l和\gamma是可学习的权重参数,可以在下游任务中通过反向传播进行优化。
- 简单版本:直接连接顶层的前向和后向隐藏状态。
- 用于下游任务:将得到的 ELMo 表示与基础的词嵌入(或 one-hot 向量)连接起来,作为下游模型(如用于问答、文本蕴含的模型)的输入。
ELMo 总结
本节课我们一起学习了 ELMo 模型。总结其流程:
- 在大型无标注文本语料库上,分别独立训练一个前向和一个后向 LSTM 语言模型。
- 对于给定句子,使用这两个模型的隐藏状态(通常组合各层)来生成每个词的上下文相关表示。
- 将该 ELMo 表示与基础词向量拼接,作为下游特定任务模型的输入。
这比 Word2Vec 更复杂,因为需要在测试时运行整个语言模型来获取嵌入,但其提供的上下文敏感表示能显著提升各种 NLP 任务的性能。

迈向更强大的模型:BERT 的引入 🤖
虽然 ELMo 效果很好,但更强大且如今几乎普遍用于实际 NLP 应用的模型是基于 Transformer 架构的 BERT。
BERT 是“Bidirectional Encoder Representations from Transformers”的缩写。与 ELMo 使用双向 LSTM 不同,BERT 使用了 Transformer 的编码器部分,能够更高效、更深入地捕捉上下文信息。
BERT 及其后续变体(如 RoBERTa, ALBERT 等)的核心原则是掩码语言模型预训练。它通过随机遮盖输入句子中的一些词,并训练模型来预测这些被遮盖的词,从而学习双向的上下文表示。这比 ELMo 的两个单向模型更直接地整合了上下文。
在接下来的课程中,我们将深入探讨 BERT 的架构、预训练任务以及如何将其应用于各种下游任务。BERT 的出现标志着 NLP 领域进入了预训练微调的新范式,极大地推动了自然语言理解的发展。


本节课中我们一起学习了:从上下文无关词嵌入的局限性出发,引入了预训练语言模型的概念。我们详细探讨了基于双向 LSTM 的 ELMo 模型,了解了它如何通过组合前向和后向语言模型的隐藏状态来生成上下文相关的词表示,并如何将其用于下游任务。最后,我们引出了更强大的基于 Transformer 的 BERT 模型,为后续学习奠定了基础。

课程 P41:CS 182 讲座 13 第 3 部分 - NLP 中的 BERT 🧠

在本节课中,我们将要学习 BERT(Bidirectional Encoder Representations from Transformers),这是当今 NLP 任务中最常用的语言模型之一。我们将探讨 BERT 的核心思想、它与之前模型(如 ELMo)的区别、其独特的训练方法,以及如何将其应用于各种下游任务。
BERT 的基本思想
上一节我们介绍了 ELMo 等基于 LSTM 的双向语言模型。本节中我们来看看 BERT 是如何工作的。
BERT 在很高层面上使用了与 ELMO 相同的原理,但它不使用两个 LSTM 模型,而是使用一个 Transformer 模型。
如果我们想简单地用 Transformer 替换之前的 LSTM,可以尝试将 Transformer 训练成一个语言模型。在序列到序列模型中,编码器按顺序读取输入,而解码器本质上是一个条件语言模型。经典 Transformer 的解码器已经是一个条件语言模型。
要得到一个无条件的语言模型,只需使用相同的解码器,但去掉条件作用的部分(即交叉注意力)。如果我们去掉常规 Transformer 解码器上的交叉注意力,它就变成了一个基本的语言模型。

以下是其工作原理的简化描述:
- 输入句子中的所有标记及其位置编码。
- 通过 掩码自注意力 传递它们(在解码器中,这防止了未来的标记影响过去)。
- 经过一个重复 N 次的块(包含位置前馈网络)。
- 最后通过一个 softmax 层来预测下一个单词。
这是最直接的方法,用 Transformer 替换 ELMo 中的 LSTM。然而,这个模型是单向的(因为使用了掩码自注意力),而 ELMo 是双向的。原则上,可以像 ELMo 一样训练两个 Transformer(一个前向,一个后向)。
从单向到双向的挑战
我们能否简单地移除自注意力中的掩码,让 Transformer 同时携带向前和向后的信息,从而得到一个双向模型,而无需训练两个独立的模型呢?
这可能会出问题。考虑一下,如果我们要训练一个语言模型,其任务是在每个时间步预测下一个标记。完整的输出序列只是输入序列向右移动了一位。
如果我们移除自注意力中的掩码(即使用完整的自注意力,就像 Transformer 的编码器一样),会发生什么?第一步的输出实际上可能与第一步的输入相同(只是复制并输出下一个词)。这意味着 Transformer 可能不需要做很多工作来理解句子的含义,它只需学会“查找”并复制下一个词。对于长句子,除了最后一个词,模型几乎不需要理解任何内容。

因此,移除自注意力的掩蔽不是一个好主意,因为任务变得太容易了,模型可能无法学到有意义的表示。


BERT 的解决方案:掩码语言模型
BERT 通过修改训练程序来避免上述问题。它不会尝试预测序列的下一个词,而是对输入进行修改,使任务变得更困难。
BERT 的做法是随机掩码输入中的标记。对于输入中的每个标记,有 15% 的概率,它会被一个特殊的 [MASK] 标记替换。所有 [MASK] 标记的向量表示是相同的(例如,一个全零向量)。然而,输出端的目标保持不变(即预测原始的、未被掩码的词)。
这迫使 Transformer 解决一个“填空”任务。例如:
- 输入:
我 [MASK] 了 - 输出目标:
我 吃 了
直觉是,如果 Transformer 能学会填上被掩码的词,它就必须真正理解单词在上下文中的含义。这与词嵌入的直觉类似:如果你能从上下文中预测一个词,你就能学到关于其意义的东西。
BERT 本质上使用了 Transformer 的编码器部分(没有掩码的自注意力),其中 15% 的输入被掩码替换,损失函数是预测那些被掩码的原始输入。

BERT 的实际训练细节
在实际训练 BERT 时,会做一些小的调整来提升效果。

BERT 在成对的句子上进行训练。我们从大量文本语料库(如维基百科)中取句子对。训练时:
- 输入以特殊的
[CLS]标记开始。 - 对于每一对输入句子,我们随机用
[MASK]替换 15% 的标记(每个标记独立处理)。 - 我们还会随机交换两个句子的顺序(50% 的概率)。
- 在第一个标记(
[CLS])的输出位置,我们训练一个二元分类器,来预测第二句是否真的紧跟在第一句之后(即句子顺序预测任务)。 - 在所有其他标记位置,我们预测该位置原本应该是什么词(掩码语言模型任务)。
这样做的直觉是:我们希望 [CLS] 位置的表示能捕获整个句子或句子对的语义信息。对于需要词级表示的任务,我们使用每个位置的输出特征;对于需要句子级表示的任务,我们使用第一个位置([CLS])的输出。

如何使用 BERT
BERT 提供了多种使用方式,主要分为微调和特征提取两种。
1. 微调 BERT 用于下游任务
以下是常见的下游任务类型及对应的微调方法:

-
句子/句子对分类任务(如情感分析、语义等价判断、文本蕴含):
你可以直接使用 BERT 第一个位置([CLS])的输出向量。具体做法是:在一个大型未标记语料库上预训练 BERT 后,去掉其原有的输出层(语言模型损失和句子顺序预测损失),在[CLS]向量后接一个新的分类器(如一个全连接层加 softmax),然后使用新任务的数据对整个模型(包括 BERT 的权重)进行微调。 -
词级标注任务(如命名实体识别、问答):
对于需要为每个输入标记输出标签的任务,你可以使用 BERT 每个位置的输出向量。具体做法是:将预训练 BERT 在每个位置的语言模型损失替换为你任务所需的损失(例如,每个位置的交叉熵损失),然后对整个模型进行微调。
2. 从 BERT 提取特征

你也可以像使用 ELMo 一样,将 BERT 用作静态特征提取器。具体做法是:取 BERT 模型(一个包含多个 Transformer 块的大模型)中某一层或某几层的隐藏状态作为你下游模型的输入特征。
关于使用哪一层,经验表明:
- 仅使用第一层效果不佳。
- 使用最后一层效果较好。
- 将最后几层连接(concatenate)起来通常能获得最好的效果。

BERT 与其他模型的比较
BERT 在众多 NLP 任务上取得了显著提升。例如,在 GLUE 基准测试(一系列自然语言理解任务)上,BERT 的表现远超之前的模型,如 ELMo 和 GPT(一个单向 Transformer 语言模型)。
BERT 的主要优势在于其双向性,这使其能更好地理解上下文。然而,这种双向性也带来了一个限制:BERT 不擅长文本生成,因为它训练时是同时看到整个(掩码后的)句子的,而不是按顺序生成。

相比之下,像 GPT 这样的单向 Transformer 模型,虽然在下游任务上的表现可能不如 BERT,但它们非常擅长生成连贯的文本,因为它们被训练来根据前面的词预测下一个词。
总结

本节课中我们一起学习了 BERT 模型。
- BERT 是什么:它是一个基于 Transformer 编码器的双向语言模型。
- 核心训练方法:通过掩码语言模型(随机遮盖单词并预测它)和下一句预测任务进行预训练,迫使模型学习深层的上下文表示。
- 如何使用:
- 微调:在预训练模型基础上,针对特定任务(如分类、标注)调整所有参数,通常能获得最佳性能。
- 特征提取:直接使用 BERT 中间层的隐藏状态作为下游模型的输入特征。
- 关键优势:BERT 的双向上下文理解能力使其在大多数 NLP 理解任务上表现卓越。
- 重要启示:在大规模未标记文本上预训练的语言模型,其学习到的上下文相关表示,对于现代 NLP 至关重要,通常是获得先进结果的基础。

BERT 的出现标志着 NLP 从使用静态词向量(如 Word2Vec)或浅层上下文表示(如 ELMo),进入了使用深度预训练 Transformer 模型的新时代。
课程 P42:CS 182 第14课 - 模仿学习 🧠
在本节课中,我们将学习一种基于学习的控制方法——模仿学习。我们将从预测问题与控制问题的区别开始,逐步引入模仿学习的基本概念、术语、核心思想,并探讨其理论局限性与实际应用中的挑战。
从预测到控制 🔄
上一节我们介绍了深度学习方法主要聚焦于预测问题。本节中,我们来看看控制问题与预测问题的关键区别。
在预测问题中,我们通常假设数据是独立同分布的。这意味着每个数据点的标签不会影响其他数据点。例如,将第一张图片中的豹子错误分类为老虎,并不会改变第二张图片的内容。
然而,在控制问题中,情况并非如此。例如,在多风的山路上驾驶,第一步的错误选择可能会影响第二步的输入,导致后续决策更加困难。控制问题中的输入并非相互独立,且目标通常更为抽象(如“开车去杂货店”而非直接输出转向命令)。
总结来说:
- 预测问题:数据独立同分布,目标是预测正确的标签。
- 控制问题:决策影响未来输入,目标是完成高级任务。
许多现实世界中的机器学习系统部署,即使是预测系统,也可能因反馈循环而演变为控制问题。
控制问题术语 📖
为了讨论控制问题,我们需要引入一些标准术语。我们将从一个熟悉的图像分类模型开始,并逐步将其转化为控制模型。
- 观察 (Observation, O):模型的输入,例如图像。我们用
O_t表示在时间t的观察。 - 动作 (Action, A):模型的输出,例如转向命令。我们用
A_t表示在时间t的动作。 - 策略 (Policy, π):一个模型,它根据观察
O_t来输出动作A_t。我们将其表示为π_θ(A_t | O_t),其中θ是模型参数。策略可以是一个神经网络。
我们还需要区分状态 (State, S) 和观察 (Observation, O):
- 状态 (S_t):描述世界潜在真实情况的变量(如物体的精确位置、速度)。状态具有马尔可夫性质,即未来状态仅依赖于当前状态。
- 观察 (O_t):传感器获取的信息(如图像像素)。观察可能不具备马尔可夫性质,因为当前观察可能不足以完全预测未来。
在今天的模仿学习中,我们将主要处理基于观察的策略。
模仿学习:行为克隆 🚗
模仿学习是一种简单的基于学习的控制方法。其核心思想是使用监督学习工具来解决控制问题。
我们将以驾驶汽车为例:
- 观察:汽车摄像头的图像。
- 动作:方向盘的转向命令。
行为克隆是模仿学习最基本的形式。其步骤如下:
以下是行为克隆的步骤:
- 收集数据:人类专家司机驾驶汽车,记录下观察(图像)和对应的动作(转向命令)。
- 训练策略:使用这些数据训练一个监督学习模型(如神经网络),使其学会从观察
O_t预测动作A_t。 - 部署策略:将训练好的策略模型部署到汽车上,让它根据实时图像自主驾驶。
这本质上与训练一个图像分类器完全相同。
理论挑战:复合错误 ⚠️
上一节我们介绍了行为克隆的简单流程。本节中,我们来看看这种方法在理论上存在的一个严重问题。
理论上,行为克隆存在复合错误问题。原因如下:
- 训练好的策略在部署时难免会犯小错误。
- 这个小错误会导致下一个时间步的观察
O_{t+1}偏离训练数据中常见的分布。 - 面对不熟悉的观察,策略更容易犯更大的错误。
- 错误会随着时间步长累积和放大,最终可能导致灾难性后果(如驶出道路)。
这类似于我们在课程早期讨论过的序列生成模型中的错误累积问题。
实践中的表现 🤔
尽管理论上存在复合错误问题,但实践中,行为克隆有时却能表现得相当好。
例如,英伟达在2016年的研究中:
- 最初使用有限数据训练的行为克隆模型表现不佳。
- 在收集了更多数据(例如,额外3000英里的驾驶数据)并重新训练后,模型的表现得到了显著改善,能够进行合理的驾驶。
这表明,大规模、高质量的数据可以在一定程度上缓解分布偏移和复合错误问题,使简单的行为克隆方法在实际中变得可行。

总结 📝

本节课中我们一起学习了:
- 预测与控制:理解了控制问题中决策相互依赖、目标抽象的特点。
- 核心术语:掌握了状态、观察、动作和策略的定义与区别。
- 模仿学习:学习了行为克隆这一最简单的模仿学习方法,即用监督学习拟合专家数据。
- 核心挑战:认识了行为克隆的理论缺陷——复合错误,即小错误会在时间序列中累积放大。
- 实践洞察:了解到尽管有理论缺陷,但通过收集大量数据,行为克隆在实践中仍可能取得不错的效果。


在接下来的课程中,我们将探讨如何设计更鲁棒的算法来正式解决这些分布偏移和复合错误问题。
课程 P43:CS 182 第14讲 第2部分 - 模仿学习 🚗🤖
在本节课中,我们将学习如何使行为克隆在实践中有效工作。我们将探讨行为克隆失败的核心原因——分布偏移问题,并介绍几种缓解该问题的技术,包括处理非马尔可夫行为和多模态行为的方法。最后,我们会了解一个在实践中成功应用的行为克隆“技巧”。
概述:行为克隆的挑战
行为克隆是一种简单的模仿学习方法,它通过监督学习来训练策略,使其模仿专家的行为。然而,在实践中直接应用行为克隆常常会失败,因为一个被称为分布偏移的根本问题。
上一节我们介绍了行为克隆的基本概念,本节中我们来看看为什么它会失败,以及如何解决。

分布偏移问题 🔄
行为克隆的核心问题是复合误差,这可以形式化为一个分布偏移问题。
我们的策略 π_θ(a_t | o_t) 定义了在给定观测 o_t 时动作的分布。如果一切是独立同分布的,并且 o_t 实际上不影响 o_{t+1},那就没有问题。但是,因为 o_{t+1} 依赖于 a_t,如果我们的策略 π_θ 与收集数据时专家策略的动作分布不完全相同,问题就会出现。
具体来说,我们在专家观测分布 p_data(o_t) 上训练策略。但当我们实际运行自己的策略时,我们的行动会影响未来的观测,导致我们开始从策略自身的观测分布 p_{π_θ}(o_t) 中获取观测。这两个分布 p_data(o_t) 和 p_{π_θ}(o_t) 是不同的,这就是分布偏移。
与循环神经网络的类比

这个问题与我们之前在循环神经网络中看到的问题完全相同。
在RNN训练中,网络总是以真实的序列作为输入。但在测试时,它接收自己(可能不正确)的预测作为输入。一个早期的微小错误可能导致后续输入与训练时看到的任何输入都不同,从而引发更严重的错误。
行为克隆中的情况非常相似:
- 训练时:网络只看到来自专家观测分布的观测。
- 测试时:网络开始看到来自自身策略的观测,这些观测可能源于轻微不正确的动作。
这同样是一个从训练分布到测试分布的分布偏移问题。
潜在的解决方案与障碍 🧱
对于RNN,我们有一个潜在的解决方案:计划采样。在训练时,我们以一定的概率将网络自己之前的预测作为输入,而不是总是使用真实序列。
那么,我们能为模仿学习开发一个计划采样版本吗?理论上可以:从策略中采取预测的行动,观察产生的下一个观测,然后用这个观测来训练网络。

然而,这里存在一个关键障碍:对于RNN,这很容易做到,因为整个过程在计算机内模拟。但对于控制问题(如驾驶),涉及物理世界。我们只有一个记录了专家行动的视频,我们不知道如果采取了不同的行动,会产生什么观测。更正式地说,我们不知道状态转移概率 p(s_{t+1} | s_t, a_t) 或观测概率 p(o_t | s_t)。

有一些算法(如基于模型的强化学习)试图学习这些概率,但对于简单的模仿学习,我们希望保持简单,只学习从图像到动作的映射。

缓解分布偏移 🛡️
既然不能完全解决,我们能否缓解它?一个思路是:如果策略 π_θ(a_t | o_t) 非常准确,非常接近专家的真实动作分布,那么分布偏移将是最小的。在实践中,当 p_data(o_t) 非常广泛(数据集巨大)且策略泛化能力极好时,这种方法有时有效。

因此,我们需要:
- 收集大量数据。
- 训练一个非常精确的模型 π_θ。
关于收集大量数据无需多言(这就是英伟达的汽车需要行驶3000英里的原因)。那么,如何得到一个更精确的模型呢?
我们需要思考模型可能无法拟合专家数据的两个主要原因:
- 非马尔可夫行为
- 多模态行为
处理非马尔可夫行为 🧠➡️🤖

非马尔可夫行为是指:人类的行为并不只依赖于当前时刻的观测。例如,转向决策具有连续性,并且可能受到之前事件(如三分钟前被超车)的影响。一个更准确的模型应该让动作依赖于迄今为止所有的观测历史 o_1, ..., o_t。
我们可以使用什么样的神经网络架构来读取一系列图像并预测当前动作呢?
以下是一个适用于此任务的经典架构示例:
# 伪代码示意架构
观测历史 [o_1, ..., o_t] -> 卷积编码器(共享权重)-> 特征序列 -> RNN/LSTM -> 最终隐藏状态 -> 全连接层 -> 动作 a_t
我们使用一个共享权重的卷积编码器处理每一帧图像,生成一个特征向量。这些特征序列被输入到一个RNN(如LSTM)中。最后,我们根据RNN的最终隐藏状态来预测动作。这利用了RNN来模拟人类行为的时序依赖性。
处理多模态行为 🛤️
多模态行为是指:在给定相同观测的情况下,可能存在多个同样合理但不同的动作。例如,无人机面对一棵树时,向左绕行或向右绕行都是可行的,但直飞是不可行的。
如果我们使用均方误差损失来回归连续的动怍值,模型可能会将“向左”和“向右”的数据平均,输出一个“直飞”的错误动作。
以下是几种处理多模态行为的方案:
方案一:离散化与Softmax
将连续动作空间离散化为多个区间,使用Softmax分类。这能很好地捕捉多模态分布(例如,左转概率50%,右转概率50%)。但对于高维动作空间,离散化的区间数量会呈指数级增长,难以实现。
方案二:高斯混合模型
让神经网络输出一个条件高斯混合模型的参数(多个均值 μ、方差 σ 和混合权重 w)。损失函数是数据在该混合模型下的对数似然。这种方法比单一高斯更好,但在高维空间中,可能需要大量混合成分才能近似复杂分布。
方案三:潜在变量模型
引入一个潜在变量 z 作为网络的额外输入。在训练时,我们需要推断数据对应的潜在变量;在测试时,可以随机采样 z 来产生不同的动作。训练这类模型需要特定技术,如条件变分自编码器或标准化流。
方案四:自回归离散化
这是通常最有效但稍复杂的方法。核心思想是:将高维动作的每个维度依次离散化并预测。
- 首先,将动作的第一个维度离散化,用Softmax预测其分布并采样。
- 将采样得到的第一个维度值作为输入,预测第二个维度的离散分布并采样。
- 依此类推。
这类似于用RNN逐词生成句子,只不过这里的“词”是动作每个维度的离散值。这种方法易于采样,能表示非常复杂的分布,且易于训练。
请注意:关于多模态分布建模的深入方法,我们将在后续关于生成模型的课程中详细讨论。
实践技巧:英伟达的驾驶系统 🎯

最后,我们来看一个在实践中让行为克隆成功工作的特殊技巧,它来自英伟达的自动驾驶论文。
该系统并没有使用复杂的RNN或多模态预测,而是采用了一个巧妙的“数据增强”方法:
- 他们在车上安装了三个摄像头:一个朝前,一个略微向左,一个略微向右。
- 训练时:
- 正前方摄像头的图像标签是驾驶员的真实转向命令。
- 左侧摄像头的图像标签是真实转向命令加上一个小的右转修正。
- 右侧摄像头的图像标签是真实转向命令加上一个小的左转修正。
这个技巧的原理是:当车辆在测试时开始向左偏离时,它正前方摄像头看到的景象会变得更像训练时左侧摄像头看到的图像。而左侧图像的标签是“右转”,这个信号会促使车辆向右修正,从而回到道路中心。反之亦然。
这本质上是通过合成数据来模拟测试时可能犯的小错误,让模型学会如何纠正它们,从而缓解了分布偏移的影响。

总结 📝
本节课我们一起学习了行为克隆面临的分布偏移挑战及其与RNN问题的类比。我们探讨了通过提高模型精度来缓解该问题的方向,具体包括:
- 使用RNN架构来处理非马尔可夫行为。
- 使用离散化、高斯混合模型、潜在变量模型或自回归离散化来处理多模态行为。
最后,我们看到了一个成功的实践案例——英伟达的多摄像头数据增强技巧,它说明了有时需要一些针对特定领域的“技巧”才能使行为克隆有效工作。如果行为克隆在您的任务中失败,请不要气馁,因为这通常是需要克服的预期挑战。


在讲座的下一部分,我们将介绍一种更有原则的模仿学习方法。
课程 P44:CS 182 第14讲 第3部分 - 模仿学习 🧠
在本节课中,我们将要学习模仿学习中的一个核心挑战——分布转移问题,并探讨一种名为 DAgger 的算法如何通过改进数据收集方式来尝试解决这个问题。我们还将比较行为克隆与DAgger的优缺点,并了解在何种情况下应选择何种方法。
行为克隆的分布转移问题 🔄
上一节我们介绍了模仿学习的基本概念。本节中我们来看看行为克隆面临的一个主要问题。
行为克隆的问题在于:当你根据学到的策略采取行动时,可能会犯错误。这个错误会导致智能体遇到训练时从未见过的观察状态。随后,智能体可能会犯下更大的错误,这些错误会不断累积和加剧。
从根本上说,问题归结为以下事实:训练数据中的观察值分布 p_data(o_t),与实际运行策略时遇到的观察值分布 p_πθ(o_t) 是不同的。
核心公式:
- 训练数据分布:
p_data(o_t) - 策略运行分布:
p_πθ(o_t) - 问题:
p_data(o_t) ≠ p_πθ(o_t)
那么,我们能否让这两个分布相等呢?如果策略是完美的,它们自然相等。但在一般情况下,这非常困难。是否存在一种算法,即使策略不完美,也能以某种方式保证做到这一点呢?
DAgger算法:通过改进数据解决问题 🗡️
表面上看,如果策略总犯错误,似乎不可能让观察分布与真实分布匹配。关键思想是:如果你无法修复策略,那就去修复数据。
我们不是通过大幅改进策略π,而是通过智能地收集数据来让 p_data(o_t) 接近 p_πθ(o_t)。当然,你并不总能选择如何收集数据(例如,老板只给了一份固定的驾驶数据集)。但在你能影响数据收集的情况下,有一种名为 DAgger 的方法可以解决此问题。
DAgger代表 数据集聚合。它是一种迭代式的模仿学习算法,以一种特殊的方式收集数据,以避免分布转移问题。在某些情况下,DAgger效果非常好;但在无法以这种方式收集更多数据的情况下,则无法使用。
以下是DAgger算法的步骤:
- 获取初始数据集:收集由人类专家提供的观察和行动组成的数据集
D。 - 训练初始策略:使用数据集
D训练初始策略πθ。由于训练数据来自p_data,该策略容易受到分布转移的影响。 - 运行策略并收集新观察:运行学到的策略
πθ(例如控制汽车),并记录其产生的观察结果。我们称这个新数据集为D_π。这些观察中可能包含一些糟糕的状态(例如汽车即将撞上障碍物)。在现实中,这需要安全措施(如安全驾驶员)。 - 人工标注新观察:请人类专家为
D_π中的每一个新观察状态,提供在这种情况下他们应采取的正确行动(地面真值)。 - 聚合数据集:将新标注的数据集
D_π与原始数据集D合并,得到更新的数据集D ← D ∪ D_π。 - 重复迭代:使用新的数据集
D重新训练策略,然后重复步骤3-5。
你可能会问:这为什么有效?直觉是,如果你聚合足够多次,最终数据集将主要由来自 p_πθ 的数据主导,从而使 p_data 与 p_πθ 在实践中变得任意接近。实际上,通常在达到此状态之前,策略性能就已经很好了。
DAgger的优缺点与实用建议 ⚖️
DAgger解决了分布转移问题,但它也存在一些缺陷,主要集中在第2步和第3步:
- 第2步的问题:运行不成熟的策略可能不安全或不可行(例如在真实物理系统中)。
- 第3步的问题:人类专家在系统不响应其输入的情况下(例如,看着车自己开,但无法干预),可能难以给出高质量的决策标签。这是一种不自然的数据收集方式。
当然,有一些方法可以缓解这些问题,例如让人类在必要时直接接管控制。但总体而言,DAgger算法的主要缺点是数据收集和标注协议较为繁重。
相比之下,单纯的行为克隆(取一个数据集,训练,然后部署)在数据收集上更具吸引力。
总结本次讨论的要点:
- 理论上,行为克隆因分布差异大而可能失效。
- DAgger可以从原理上解决此问题,但需要昂贵的数据收集和标注。
- 有时,行为克隆配合启发式技巧和高精度模型也能有效工作。

实用建议:如果你想解决一个模仿学习问题:
- 首先尝试行为克隆,但要做好它可能效果不佳的准备。
- 如果行为克隆不起作用,则需要考虑更复杂的方法,如 DAgger 或我们下周将讨论的强化学习方法。
下节预告 🚀

在今天的讨论中,我们主要解决了非独立同分布数据的问题,但仍然假设我们能获得人类提供的地面真值行动监督。
在下节课中,我们将进入完全的强化学习设定。在这种设定下,我们不再有地面真值行动,而是会以奖励函数的形式获得一些高级目标。我们将讨论能够学习并成功实现这些目标的算法。

本节课中我们一起学习了:模仿学习中的分布转移挑战、DAgger算法如何通过迭代式数据聚合来应对这一挑战,以及在实际应用中如何根据数据收集的可行性在行为克隆和DAgger之间做出选择。
课程 P45:CS 182 - 第十五讲 - 第一部分 - 策略梯度 🎯

在本节课中,我们将学习强化学习的基本概念,特别是如何在没有专家监督数据的情况下,通过定义奖励函数来训练智能体完成复杂任务。我们将从回顾预测与控制问题的区别开始,正式定义马尔可夫决策过程,并最终推导出强化学习的核心优化目标。
从预测到控制 🔄
上一节我们讨论了基于学习的控制问题。本节中,我们来看看预测问题与控制问题的核心区别。
预测问题(如图像识别、情感分析)的输入与输出通常是独立同分布的。你对一个样本的预测不会影响下一个样本。同时,你拥有真实标签作为监督信号。

控制问题(如自动驾驶)则涉及顺序决策。你的每一个决策都会改变未来的输入,因此数据不再是独立同分布的。此外,你通常没有直接的“正确动作”作为监督,而是通过一个奖励函数来评估行为的好坏。
值得注意的是,即使是传统的预测系统,当部署到现实世界并影响数据分布时(例如交通预测影响用户路线选择),也会面临类似的反馈循环问题。本节课我们将聚焦于经典的顺序决策领域。
定义目标:奖励与回报 🏆
如果我们没有人类专家提供的“正确动作”数据,如何指导智能体学习?我们可以定义一个奖励函数。
奖励函数 R(s, a) 是一个标量函数,为每个状态(或状态-动作对)打分,表示其合意程度。例如,在驾驶任务中,车辆正确行驶的状态奖励高,发生碰撞的状态奖励低。

然而,智能体的目标不是贪婪地最大化即时奖励,而是最大化长期总回报。一个现在奖励较低但能避免未来灾难的决策,可能比一个现在奖励较高但会导致未来失败的决策更好。因此,强化学习需要进行长期推理。
马尔可夫决策过程 📐
为了形式化地描述强化学习问题,我们引入马尔可夫决策过程。一个MDP由以下核心元素定义:
- 状态集合 (S): 所有可能状态的集合。状态可以是离散的(如棋盘位置)或连续的(如车辆坐标)。我们假设状态满足马尔可夫性质,即当前状态包含了预测未来所需的所有历史信息。
- 动作集合 (A): 所有可能动作的集合。动作也可以是离散的(如左转、右转)或连续的(如方向盘转角)。
- 转移概率 (T): 定义了在状态
s_t执行动作a_t后,转移到下一个状态s_{t+1}的概率分布。即T(s_{t+1} | s_t, a_t)。 - 奖励函数 (R): 如前所述,
R(s_t, a_t)给出在状态s_t执行动作a_t后获得的即时标量奖励。
一个相关的概念是部分可观测马尔可夫决策过程,其中智能体接收的是与状态相关的观测而非状态本身。这引入了潜在的非马尔可夫性。本节课我们主要讨论完全可观测的MDP。
强化学习的形式化目标 🎯
在MDP中,智能体的行为由策略 π_θ(a | s) 决定,它是一个参数为 θ(例如神经网络权重)的函数,输入状态 s,输出动作 a 的概率分布。
策略与环境的转移概率共同作用,产生一个状态-动作序列(或称轨迹 τ = (s_1, a_1, s_2, a_2, ...))上的分布 p_θ(τ)。
强化学习的最终目标是找到能最大化期望总回报的策略参数 θ。其数学形式化如下:
目标公式:
θ* = argmax_θ E_{τ ~ p_θ(τ)} [ Σ_{t=1}^{T} R(s_t, a_t) ]
其中,期望 E 是在由策略 π_θ 和环境动力学 T 共同生成的轨迹分布 p_θ(τ) 上计算的。理解这个期望最大化的目标是理解后续所有算法的基础。
总结 📝

本节课我们一起学习了强化学习的基础设定。我们明确了预测与控制问题的区别,引入了通过奖励函数定义任务目标的思想,并正式定义了马尔可夫决策过程这一核心框架。最后,我们推导出了强化学习的根本优化目标:寻找能最大化期望总回报的策略参数。在接下来的课程中,我们将基于这个目标,开始探索求解它的具体算法。
课程 P46:CS 182 第15讲 第2部分 - 策略梯度 🧠
在本节课中,我们将学习第一个强化学习算法——策略梯度。我们将通过数学推导理解其原理,并学习如何通过采样来估计和优化策略的期望回报。


概述 📋
策略梯度是一种直接优化策略参数以最大化期望总奖励的方法。我们将从如何评估一个给定策略的期望回报开始,然后推导出计算该回报梯度的公式,最终得到一个可以通过采样和梯度上升来优化策略的实用算法。
1. 强化学习目标与评估 🔍
上一节我们介绍了强化学习的目标:最大化在由当前策略和MDP诱导的轨迹分布下的期望总奖励。其数学表达式为:
目标公式:
[
J(\theta) = \mathbb{E}{\tau \sim p(\tau)} \left[ \sum_{t} r(s_t, a_t) \right]
]
其中,(\tau) 表示一条轨迹 ((s_1, a_1, s_2, a_2, ...)),(p_{\theta}(\tau)) 是由策略 (\pi_{\theta}) 和MDP动态共同定义的轨迹分布。
首先,我们关心如何评估这个目标。由于可能的轨迹数量是指数级的,我们无法枚举求和。但我们可以通过采样来构造一个无偏估计器。
评估方法:
从策略 (\pi_{\theta}) 中采样 (N) 条轨迹 (\tau^{(i)}),计算每条轨迹的总奖励 (R(\tau^{(i)})),然后取平均值:
[
\hat{J}(\theta) = \frac{1}{N} \sum_{i=1}^{N} R(\tau^{(i)})
]
这个估计量 (\hat{J}(\theta)) 是 (J(\theta)) 的无偏估计。采样意味着在现实世界(或模拟器)中运行策略多次。

2. 策略梯度推导 🧮
我们的目标是最大化 (J(\theta)),因此需要计算其梯度 (\nabla_{\theta} J(\theta))。然后我们可以使用梯度上升法来更新策略参数 (\theta)。
我们将期望写成积分形式:
[
J(\theta) = \int \pi_{\theta}(\tau) R(\tau) d\tau
]
计算其梯度:
[
\nabla_{\theta} J(\theta) = \nabla_{\theta} \int \pi_{\theta}(\tau) R(\tau) d\tau
]
直接计算 (\nabla_{\theta} \pi_{\theta}(\tau)) 很困难。这里我们使用一个巧妙的数学技巧(对数导数技巧):
[
\nabla_{\theta} \pi_{\theta}(\tau) = \pi_{\theta}(\tau) \nabla_{\theta} \log \pi_{\theta}(\tau)
]
将其代入梯度表达式:
[
\nabla_{\theta} J(\theta) = \int \pi_{\theta}(\tau) \nabla_{\theta} \log \pi_{\theta}(\tau) R(\tau) d\tau
]
这正好是另一个期望值:
[
\nabla_{\theta} J(\theta) = \mathbb{E}{\tau \sim \pi(\tau)} \left[ \nabla_{\theta} \log \pi_{\theta}(\tau) R(\tau) \right]
]
现在,我们需要计算 (\nabla_{\theta} \log \pi_{\theta}(\tau))。一条轨迹的概率可以分解为:
[
\pi_{\theta}(\tau) = p(s_1) \prod_{t=1}^{T} \pi_{\theta}(a_t | s_t) p(s_{t+1} | s_t, a_t)
]
取对数后:
[
\log \pi_{\theta}(\tau) = \log p(s_1) + \sum_{t=1}^{T} \left[ \log \pi_{\theta}(a_t | s_t) + \log p(s_{t+1} | s_t, a_t) \right]
]
对其求梯度时,只有 (\log \pi_{\theta}(a_t | s_t)) 项依赖于参数 (\theta)。初始状态分布 (p(s_1)) 和状态转移概率 (p(s_{t+1} | s_t, a_t)) 与 (\theta) 无关,因此它们的梯度为零。

于是,梯度简化为:
[
\nabla_{\theta} \log \pi_{\theta}(\tau) = \sum_{t=1}^{T} \nabla_{\theta} \log \pi_{\theta}(a_t | s_t)
]
代入期望中,我们得到最终的策略梯度公式:
策略梯度公式:
[
\nabla_{\theta} J(\theta) = \mathbb{E}{\tau \sim \pi(\tau)} \left[ \left( \sum_{t=1}^{T} \nabla_{\theta} \log \pi_{\theta}(a_t | s_t) \right) \left( \sum_{t=1}^{T} r(s_t, a_t) \right) \right]
]
3. 策略梯度算法实现 ⚙️

现在我们已经得到了策略梯度的理论公式。在实践中,我们通过采样来估计这个期望值。
以下是实现策略梯度算法的步骤:

- 采样轨迹:使用当前策略 (\pi_{\theta}) 在环境中运行,收集 (N) 条轨迹 (\tau^{(i)})。
- 计算梯度估计:对于每条轨迹 (\tau^{(i)}),计算:
- (\sum_{t} \nabla_{\theta} \log \pi_{\theta}(a_t^{(i)} | s_t^{(i)})) (策略对数似然的梯度之和)
- (R^{(i)} = \sum_{t} r(s_t^{(i)}, a_t^{(i)})) (轨迹的总奖励)
将两者相乘。
- 平均梯度:对所有轨迹的上述乘积结果取平均,得到梯度估计 (\hat{g})。
- 策略更新:执行梯度上升更新策略参数:(\theta \leftarrow \theta + \alpha \hat{g})。
这个算法有时被称为 REINFORCE 算法。


4. 直观理解与讨论 💡

上一节我们推导了数学公式,本节我们来看看策略梯度背后的直观含义。
将策略梯度与监督学习中的最大似然估计(MLE)梯度进行比较,可以加深理解:
- 最大似然梯度:(\nabla_{\theta} J_{ML} = \mathbb{E}{\tau} \left[ \sum \nabla_{\theta} \log \pi_{\theta}(a_t | s_t) \right])
- 目标:增加在观测到的状态上采取观测到的动作的概率。
- 策略梯度:(\nabla_{\theta} J_{PG} = \mathbb{E}{\tau} \left[ \left( \sum \nabla_{\theta} \log \pi_{\theta}(a_t | s_t) \right) R(\tau) \right])
- 目标:根据轨迹的总奖励 (R(\tau)) 来缩放最大似然梯度。
- 如果一条轨迹获得高奖励(“好”轨迹),算法会显著增加该轨迹中所有动作的概率。
- 如果一条轨迹获得低(或负)奖励(“坏”轨迹),算法会降低该轨迹中动作的概率。

核心直觉:策略梯度算法实质上是试错学习的数学形式化。它让产生好结果的行动变得更可能,让产生坏结果的行动变得更不可能。即使它是一个基于梯度的优化,其最终效果与我们的学习直觉是一致的。
5. 关于部分可观测性的说明 👁️
一个重要的优点是,基础的(Vanilla)策略梯度算法不依赖于马尔可夫性质。算法中只使用了策略 (\pi_{\theta}(a_t | o_t))(其中 (o_t) 是观测值),而没有显式使用状态转移概率。
这意味着:
- 在处理部分可观测马尔可夫决策过程(POMDP)时,基础策略梯度算法无需修改。
- 你只需要将观测序列输入策略网络即可。
- 相比之下,后续我们将学习的一些策略梯度变体(如Actor-Critic方法)可能依赖于马尔可夫性质,在POMDP中需要特别处理。

总结 🎯
本节课我们一起学习了策略梯度方法:
- 我们从强化学习的核心目标——最大化期望总奖励出发。
- 通过数学推导,利用对数导数技巧,得到了策略梯度 (\nabla_{\theta} J(\theta)) 的表达式,该表达式可以表示为关于轨迹分布的期望。
- 我们发现梯度计算中不依赖于未知的环境动态(状态转移概率),只需知道策略本身,这使得算法可行。
- 我们介绍了REINFORCE算法的实现步骤:采样、计算梯度估计、平均、更新。
- 我们探讨了策略梯度的直观理解,即加权调整动作概率的试错学习。
- 我们指出了基础策略梯度算法对部分可观测环境的良好适应性。

策略梯度是强化学习中一类重要算法的基础。在接下来的课程中,我们将以此为基础,学习更高效、更稳定的改进版本。
课程 P47:CS 182 第15讲 第3部分 - 策略梯度 🎯
在本节课中,我们将学习如何让策略梯度方法在实际中真正发挥作用。我们将探讨一些关键的改进技巧,例如利用因果关系和引入基线,这些技巧对于降低梯度估计的方差至关重要。此外,我们还将了解如何使用自动微分来实现策略梯度,并讨论其在实际应用中的一些重要考量。
改进策略梯度估计器 🔧
上一节我们介绍了策略梯度的基本数学形式。本节中,我们来看看如何通过利用问题的时序结构来改进它。
我们之前讨论的策略梯度估计器没有利用任何关于问题时间结构的知识。它计算所有动作概率的对数梯度之和,乘以所有奖励的总和。这没有解释一个关键事实:未来的行为不能影响过去的奖励。
我们可以通过注意到“未来的行为不能影响过去的奖励”这一事实,来构建一个更好的估计器。其背后的直觉是,我们将避免让未来的行动影响过去时间步的奖励。
具体做法是,我们重写标准策略梯度公式。原始公式为:
∇θ J(θ) = Eτ∼πθ [ (∑t ∇θ log πθ(at|st)) * (∑t‘ r(st‘, at‘)) ]
通过分配律,我们可以将其重写为:
∇θ J(θ) = Eτ∼πθ [ ∑t (∇θ log πθ(at|st) * ∑t‘≥t r(st‘, at‘)) ]
这个新形式让我们更清楚地看到问题:时间 t 处动作概率的梯度,乘以了包括过去奖励(t‘ < t)的和。但改变时间 t 的动作概率永远不会改变过去的回报。
因此,我们可以将内部求和的下限改为当前时间步 t,只累加从当前到未来的奖励:
∇θ J(θ) = Eτ∼πθ [ ∑t (∇θ log πθ(at|st) * ∑t‘=t^T r(st‘, at‘)) ]
这个估计器同样是正确的,并且由于去除了理论上期望为零的噪声项(过去的奖励),它在有限样本下的方差更小,估计更准确。我们通常将 ∑t‘=t^T r(st‘, at‘) 记作 Q̂(i, t)。
引入基线以降低方差 📉
仅仅让好的轨迹更可能、坏的轨迹更不可能并不总是能保证发生。如果所有奖励都是很大的正数,策略梯度可能会简单地让所有轨迹都变得更可能,这不是我们想要的。
我们真正希望的是:比平均表现好的轨迹变得更可能,比平均表现差的轨迹变得更不可能。因此,直觉上,我们应该从奖励中减去一个基线(例如平均奖励)。
我们将梯度估计器修改为:
∇θ J(θ) ≈ 1/N ∑i=1^N ∑t (∇θ log πθ(at^(i)|st^(i)) * (Q̂(i, t) - b))
其中 b 通常取为平均奖励 E[Q̂]。
这个修改是允许的吗?令人惊讶的是,这个估计器在期望值上仍然是正确的(无偏的),并且通常能显著降低方差。我们可以证明,对于任何不依赖于动作的基线 b,E[∇θ log πθ(a|s) * b] = 0。
证明如下:
Eτ∼πθ [∇θ log πθ(τ) * b] = ∫ πθ(τ) ∇θ log πθ(τ) * b dτ = b * ∇θ ∫ πθ(τ) dτ = b * ∇θ 1 = 0
因为概率分布 πθ(τ) 的积分为1,其梯度为0。
因此,减去基线不会改变期望值,但通过减去一个已知期望为零的项,可以减少有限样本估计的波动,从而得到更准确的梯度方向。

策略梯度的局限性与离策略学习 🚧
关于策略梯度,需要注意的一点是,它是一种同策略算法。这意味着每次更新策略参数 θ 后,为了计算新的梯度,你必须根据新的策略 πθ 与环境交互来收集新的样本。旧策略下收集的样本不能直接复用。
这可能导致样本效率低下,因为训练神经网络通常需要大量梯度步,而每一步都需要新的交互数据。
那么,能否使用旧策略的样本来估计新策略的梯度呢?答案是肯定的,这可以通过重要性采样来实现。
重要性采样允许我们利用从一个分布(旧策略 πθ‘)中采样的数据,来估计在另一个分布(新策略 πθ)下的期望值。策略梯度的离策略估计形式如下:
∇θ J(θ) ≈ Eτ∼πθ‘ [ (∏t πθ(at|st)/πθ‘(at|st)) * ∑t (∇θ log πθ(at|st) * Q̂(t)) ]
其中,(∏t πθ(at|st)/πθ‘(at|st)) 是重要性权重。
然而,直接使用这个公式有一个严重问题:重要性权重是 T 个概率的连乘,当轨迹很长时,这个权重会指数级地趋近于0或无穷大,导致估计器方差极大而不实用。
一个实用的近似方法是忽略状态分布的变化,只对动作概率进行重要性加权。如果新旧策略足够接近,这个近似带来的误差是有界的。这引出了诸如近端策略优化(PPO)等实用算法。
使用自动微分实现策略梯度 💻
策略梯度的形式与最大似然估计非常相似,只是多了一个奖励权重项。这让我们可以方便地利用自动微分工具来实现它。
在监督学习的最大似然中,我们构建损失函数为负对数似然,然后通过反向传播求梯度。对于策略梯度,我们可以构建一个“伪损失”:
伪损失 = - 1/N ∑i ∑t [ log πθ(at^(i)|st^(i)) * Q̂(i, t) ]
然后对这个伪损失进行反向传播。自动微分库会计算其梯度,而这个梯度正好就是策略梯度的估计(前提是我们在计算图中将 Q̂ 视为常数,不依赖于 θ)。
以下是类似 TensorFlow 风格的伪代码:
# 输入: states, actions, q_values
logits = policy_network(states) # 前向传播
neg_log_likelihood = cross_entropy_loss(logits, actions) # 计算负对数似然
weighted_neg_log_likelihood = neg_log_likelihood * q_values # 用Q值加权
loss = tf.reduce_mean(weighted_neg_log_likelihood) # 计算伪损失
gradients = tf.gradients(loss, policy_network.trainable_variables) # 反向传播

实践要点与总结 📝

以下是关于策略梯度实践的一些重要考虑:
- 高方差:策略梯度的梯度估计方差通常很大。因此,相比监督学习,你需要使用更大的批大小(更多样本)来获得稳定的更新。
- 学习率调整:由于梯度噪声大,使用自适应优化器(如 Adam)调整学习率尤为重要。
- 应用范围:策略梯度不仅用于强化学习控制问题,还可用于任何需要通过随机且不可微的操作进行微分的场景,例如带有离散潜在变量的模型或某些注意力机制。



本节课中我们一起学习了:
- 如何通过利用因果关系(仅使用未来奖励)来改进策略梯度估计器,降低其方差。
- 如何通过引入基线(如减去平均奖励)来进一步稳定训练,使算法能够区分高于平均和低于平均的表现。
- 策略梯度是同策略算法,以及如何使用重要性采样进行离策略估计的近似方法。
- 如何利用自动微分,通过构建一个加权的伪损失函数来简洁地实现策略梯度。
- 策略梯度在实际应用中的一些关键特性和考量。

这些技巧是将策略梯度从理论公式转化为实用算法的关键。没有它们,策略梯度很难在复杂问题上奏效;而有了它们,策略梯度成为了解决一系列决策问题的基础工具。
强化学习课程 P48:演员-评论家与Q学习 🎭🤖
在本节课中,我们将学习强化学习中的两种重要算法:演员-评论家方法和Q学习。我们将从回顾策略梯度开始,然后探讨如何通过更有效地估计回报来改进它,最终引出演员-评论家算法。
回顾策略梯度 📈
上一节我们介绍了策略梯度方法。经典的策略梯度算法(如REINFORCE)包含三个步骤:
- 通过运行当前策略对轨迹进行采样。
- 通过计算梯度对数概率与奖励估计的乘积之和来评估策略梯度。
- 沿着策略梯度的方向更新策略。
其中,奖励估计 Q_hat 是从当前时间步 t 到轨迹结束所获得的实际奖励之和。然而,这种估计仅基于单个轨迹样本,方差较大。
改进奖励估计 🎯
本节中,我们来看看如何改进对预期回报的估计。Q_hat 是对在状态 s 采取行动 a 后预期回报的估计。在REINFORCE中,这个估计只是将单个轨迹后续的奖励简单相加。

更好的方法是估计真实的Q函数 Q(s, a),即从状态 s 执行动作 a 并遵循策略 π 所能获得的期望总回报。如果我们能获得 Q(s, a) 的良好估计,就可以用它替代 Q_hat,从而得到更准确的策略梯度。
此外,我们上次学到,在策略梯度中引入基线(baseline)可以显著降低方差。一个特别有用的基线是状态值函数 V(s),它表示从状态 s 开始并遵循策略 π 的期望回报。
以下是几个核心概念的定义:
- Q函数:
Q^π(s, a) = E[ Σ_{t'=t}^T R(s_{t'}, a_{t'}) | s_t = s, a_t = a ] - 价值函数:
V^π(s) = E_{a~π}[Q^π(s, a)] - 优势函数:
A^π(s, a) = Q^π(s, a) - V^π(s)
优势函数描述了在状态 s 下,动作 a 比该状态下的平均动作好多少。使用优势函数后,策略梯度可以表示为:
∇_θ J(θ) ≈ E[ ∇_θ log π_θ(a|s) * A^π(s, a) ]
我们对优势函数的估计越好,策略梯度的估计就越准确。
演员-评论家算法 🎬👨🏫
为了估计优势函数,我们引入第二个神经网络。现在,我们有两个网络:
- 演员(Actor):策略网络
π_θ(a|s),负责选择动作。 - 评论家(Critic):价值网络
V_φ(s),负责评估状态的好坏。
这种结构被称为演员-评论家算法。评论家网络的任务是进行策略评估,即拟合当前策略的价值函数 V^π(s)。
如何训练评论家?🔧
我们讨论两种拟合价值函数的方法:


1. 蒙特卡洛(MC)评估
这种方法直接使用从状态 s 开始到轨迹结束获得的实际总奖励 G_t 作为目标值 y,然后通过监督学习(最小化均方误差)来训练价值网络。
L(φ) = (V_φ(s_t) - y_t)^2,其中 y_t = Σ_{t'=t}^T R_{t'}
2. 时序差分(TD)与自举(Bootstrapping)
我们可以利用贝尔曼方程来获得更好的目标值。贝尔曼方程建立了相邻状态价值函数之间的联系:
Q^π(s_t, a_t) = R(s_t, a_t) + E_{s_{t+1}}[V^π(s_{t+1})]
因此,优势函数可以近似为:
A^π(s_t, a_t) ≈ R(s_t, a_t) + V^π(s_{t+1}) - V^π(s_t)
在训练评论家时,我们使用当前价值网络的预测来构建目标(即“自举”):
y_t = R_t + γ * V_φ(s_{t+1})
其中 γ 是折扣因子(通常为0.99),用于让智能体更重视近期奖励,并保证无限时域任务中价值函数的收敛性。然后同样通过最小化均方误差 (V_φ(s_t) - y_t)^2 来更新评论家。
算法流程 🔄
一个完整的(在线)演员-评论家算法步骤如下:
- 从环境中观察当前状态
s_t。 - 演员根据策略
π_θ(a|s_t)选择动作a_t。 - 执行动作
a_t,获得奖励r_t和下一个状态s_{t+1}。 - 更新评论家:计算目标值
y_t = r_t + γ * V_φ(s_{t+1}),通过梯度下降最小化(V_φ(s_t) - y_t)^2来更新参数φ。 - 更新演员:估计优势
Â_t = r_t + γ * V_φ(s_{t+1}) - V_φ(s_t),通过梯度上升更新策略参数θ:θ ← θ + α * ∇_θ log π_θ(a_t|s_t) * Â_t。

网络架构设计 🏗️
对于演员和评论家网络,有两种常见的设计方式:
- 独立网络:演员和评论家是两个完全独立的神经网络。简单稳定,但效率较低。
- 共享特征网络:演员和评论家共享底层的特征提取层,然后在顶层分成策略头和价值头。这允许特征共享,可能更高效,但训练可能更复杂。
总结 📚

本节课我们一起学习了演员-评论家算法。我们从策略梯度出发,指出了其使用单样本回报估计方差大的问题。通过引入价值函数作为基线,并定义了优势函数,我们得到了更优的策略梯度形式。为了估计优势函数,我们引入了评论家网络,并讲解了如何使用蒙特卡洛或时序差分方法来训练它。最终,我们得到了演员-评论家算法的完整框架,其中演员负责决策,评论家负责评估,两者协同工作,使得策略学习更加高效和稳定。
🧠 课程 P49:CS 182 第 16 讲 - 第二部分:Actor-Critic 与 Q-Learning
在本节课中,我们将学习如何从 Actor-Critic 方法出发,进一步构建完全基于值函数和 Q 函数的强化学习算法,最终引出经典的 Q-Learning 方法。我们将探讨如何省略显式的策略网络,直接利用值函数进行决策,并理解从动态规划到无模型 Q-Learning 的演变过程。
🤔 能否完全省略策略梯度?
上一节我们介绍了 Actor-Critic 方法,本节中我们来看看能否完全摆脱策略梯度。核心问题是:我们能否完全用值函数来进行强化学习,从而省略策略梯度?
回忆优势函数的定义:A_π(s, a) 表示在状态 s 下采取动作 a 相对于策略 π 的平均表现有多好。最优动作是优势函数的 arg max。如果我们能直接选择使优势最大化的动作,这通常比遵循当前策略 π 更好,甚至可能最优。
这意味着,我们或许可以隐式地定义一个策略:对于离散动作,我们评估所有动作的优势,然后选择优势最大的那个。这样,我们就不再需要一个独立的神经网络来表示策略,而是直接用优势函数来选择动作。
这种新策略至少和原策略一样好,通常更好。因此,我们对算法流程做了一个修改:我们仍然生成样本并拟合值函数,但不再通过策略梯度来改进策略,而是用这种贪婪的 arg max 选择来直接改进策略。

🔄 策略迭代算法基础
基于上述思想,我们可以构建策略迭代算法。其高层思想是:首先评估当前策略 π 的优势函数(通常通过计算值函数实现),然后将新策略设置为 arg max 策略,并重复此过程。

关键问题是如何计算 A_π(s, a)。我们可以使用之前讨论的 Actor-Critic 方法进行估计:

A_π(s, a) ≈ r + γ * V(s') - V(s)
我们可以使用蒙特卡洛方法或时序差分方法来用样本估计这个值。

📊 动态规划视角:价值迭代
现在,让我们暂停一下,讨论一个特殊情况:如果我们知道状态转移概率 P(s'|s, a),如何拟合 V_π?这有助于我们在已知模型的设定下推导出优雅的算法,然后再推广到无模型的设定。
假设状态和动作空间都是小而离散的,例如一个网格世界。在这种情况下,我们可以将值函数 V_π 存储在一个表中(每个状态一个值),转移概率存储在一个张量中。
值函数的贝尔曼更新方程为:
V_π(s) = E_{a∼π, s'∼P}[r(s, a) + γ * V_π(s')]
由于我们的策略 π 是 arg max 策略(确定性策略),我们可以将其代入方程。此时,期望中只有一个动作的概率为1,因此方程简化为:

V_π(s) = r(s, π(s)) + E_{s'∼P}[γ * V_π(s')]
我们可以使用这个引导更新来评估 V_π,然后将新策略设置为 arg max 策略,并重复。这个过程称为策略评估。

更高效的方法是进行价值迭代。我们注意到,arg max 策略的值实际上等于 Q 函数的最大值:
V_π(s) = max_a Q(s, a)
其中,Q(s, a) = r(s, a) + γ * E_{s'}[V(s')]。
因此,价值迭代算法直接对值函数进行备份:
V(s) ← max_a [ r(s, a) + γ * E_{s'}[V(s')] ]
这样就跳过了显式构造策略的中间步骤。

🧠 从表格到神经网络:函数逼近
当状态空间很大(例如图像)时,使用表格存储值函数变得不切实际(维度诅咒)。我们需要使用神经网络 V_φ(s) 来近似值函数,其中 φ 是网络参数。
我们可以借鉴价值迭代的思想,但使用均方误差回归来拟合神经网络。具体来说,我们计算目标值:
y = r(s, a) + γ * max_{a'} V_φ(s')
然后通过最小化 (V_φ(s) - y)^2 来更新网络参数 φ。
然而,这里存在一个大问题:在无模型设定下,我们无法准确计算 max_{a'} V_φ(s'),因为我们不知道采取不同动作会转移到什么状态 s'。

🚀 解决方案:Q-Learning 算法
为了解决上述问题,我们转向 Q-Learning。其核心思想是直接学习 Q 函数 Q(s, a),而不是值函数 V(s)。
Q-Learning 的目标是让 Q 函数满足贝尔曼最优方程:
Q(s, a) = r(s, a) + γ * max_{a'} Q(s', a')

我们可以使用样本 (s, a, r, s') 来估计这个目标,并让 Q 函数向该目标回归。
以下是拟合 Q 迭代算法的步骤:
- 收集数据:使用某种策略(如 ε-贪婪策略)与环境交互,收集状态、动作、奖励和下一个状态的数据集 D。
- 计算目标值:对于数据集中的每个样本 (s, a, r, s'),计算目标值 y = r + γ * max_{a'} Q_φ(s', a')。
- 执行回归:通过最小化均方误差 (Q_φ(s, a) - y)^2 来更新 Q 网络参数 φ。
- 重复:迭代执行步骤 1-3。
这个算法的一个关键优点是:它可以使用非策略样本,即收集数据所用的策略可以与我们要学习的策略不同。这是因为 Q 函数是以动作为条件的,我们只需要在目标值计算中“查询” Q 网络对不同动作的估值。
⚠️ 实践注意事项
虽然 Q-Learning 在理论上(当使用函数逼近时)比策略梯度缺乏一些收敛性保证,但在实践中,通过精心调整,它可以非常有效。深度 Q 网络(DQN)及其变体就是建立在 Q-Learning 基础之上的成功案例。
要使其在实践中工作良好,通常需要注意以下几点:
- 使用目标网络来稳定训练。
- 使用经验回放来打破样本间的相关性。
- 谨慎选择超参数,如学习率、折扣因子 γ 和探索率 ε。

📝 总结
本节课中,我们一起学习了从 Actor-Critic 到 Q-Learning 的演变过程。
- 我们首先探讨了省略策略梯度的可能性,提出了直接利用优势函数选择动作的贪婪方法。
- 接着,在已知模型的设定下,我们回顾了策略迭代和价值迭代这两种动态规划方法。
- 然后,面对大状态空间的挑战,我们引入了神经网络进行函数逼近。
- 最后,为了解决无模型设定下无法计算最大值的问题,我们引出了Q-Learning 算法。该算法直接学习 Q 函数,通过贝尔曼最优方程进行更新,并能使用非策略数据进行学习。

Q-Learning 为后续深度强化学习算法(如 DQN)奠定了重要基础,是价值函数方法中的核心思想。
机器学习基础教程(P5):CS 182 第二讲第二部分 🧠
在本节课中,我们将要学习监督学习的基本概念。监督学习是机器学习中最常用、最核心的形式之一,尤其在工业界的实际应用中占据主导地位。我们将从基本公式出发,理解如何从数据中学习一个预测模型,并探讨如何输出概率而非单一标签,这为后续更复杂的模型学习奠定了基础。

什么是监督学习?🎯
上一节我们介绍了机器学习的基本框架,本节中我们来看看监督学习的具体定义。监督学习是最常用的机器学习形式之一,尤其是在工业界的实际应用中。绝大多数工业界部署的机器学习系统都属于监督学习。
其核心公式是:给定一组训练数据,它由输入 x 和对应的真实输出 y 的元组 (x, y) 组成。我们的目标是学习一个带参数 θ 的函数 f_θ(x),使其预测值尽可能接近真实的 y。这个问题可以表述为从简单的线性回归到复杂的图像分类等各种任务。

监督学习涵盖了所有从带有真实标签(ground truth)的数据中进行训练和预测的模型。这是一个价值数十亿美元的产业,其基本原理虽然简单,但要构建有效工作的系统,则需要处理许多细节,这些内容将在后续课程中逐步展开。
监督学习问题示例 📋
以下是监督学习问题的一些典型例子。为了定义一个监督学习问题,你必须明确要预测什么(y),以及根据什么进行预测(x)。
以下是几个例子:
- 图像分类:根据图像预测其中物体的类别(例如,猫或狗)。
- 机器翻译:根据一个英语句子预测其对应的法语句子。
- 医疗诊断:根据X光图像预测疾病是否存在。
- 语音识别:根据一段音频话语预测其对应的文本。
总的来说,只要你能将问题表述为“根据输入 x 预测输出 y”,并且能获得带有真实标签 y 的训练数据,就可以将其构建为一个监督学习问题。从自然语言处理到语音识别再到医疗领域,许多看似不同的问题都可以纳入这个框架。

预测概率 vs. 预测离散标签 🎲
在上一节中,我们讨论了预测的具体形式。本节中我们来看看为什么预测概率通常比预测单一的离散标签更有意义,也更容易学习。

考虑一个手写数字识别的例子。面对一个书写潦草的数字,人类可能也无法立刻确定它是“4”还是“9”。如果模型被迫输出一个单一标签(如“4”),它就无法表达这种不确定性。
如果我们允许模型输出概率,情况就不同了。例如,对于同一个模糊的数字,模型可以输出:P(y=4 | x) = 0.6, P(y=9 | x) = 0.35, 其他类别概率很小。这能更准确地反映模型(以及人类)认知中的不确定性。
输出概率分布 P_θ(y | x) 比输出单一标签 y 更具灵活性,并能更好地为后续决策提供信息。从优化角度看,预测概率(一个连续值)也比预测离散标签(一个跳跃值)更平滑,使得通过微调参数 θ 来改进模型变得更容易。

条件概率与模型类型 📊
我们定义了要学习一个将输入 x 映射到输出 y 的概率分布的函数,即条件概率 P_θ(y | x)。这引出了机器学习中两种重要的方法类型。
根据概率的链式法则,联合概率 P(x, y) 可以分解为:
P(x, y) = P(x) * P(y | x)

由此可以推导出条件概率的定义:
P(y | x) = P(x, y) / P(x)
- 判别式方法:直接学习条件概率
P(y | x)。这类方法专注于区分不同的y。 - 生成式方法:学习联合概率
P(x, y)。因为掌握了x和y的联合分布,这类方法原则上可以生成新的数据x,并且可以通过公式P(y | x) = P(x, y) / P(x)间接得到条件概率。
在本课程中,我们将主要聚焦于判别式方法,即直接学习 P_θ(y | x)。
如何表示概率分布?🔢
现在我们知道要输出一个概率分布 P_θ(y | x)。接下来的问题是,如何在计算机程序中表示它?我们的程序需要输出一组数字,每个数字代表一个类别的概率。
对于一个有 N 个类别的问题(例如,10个手写数字),程序需要输出 N 个数字。这些数字必须满足两个约束:
- 每个数字都是正数(概率非负)。
- 所有数字之和为 1(概率总和为1)。
我们不能随意设计一个输出 N 个数字的程序,必须确保其输出满足这些概率公理。例如,一个简单的想法是让 P(y=狗 | x) = x^T * θ_狗,P(y=猫 | x) = x^T * θ_猫。但这通常无法保证两个输出值之和为1。
因此,我们需要一个更结构化的方法:程序内部先计算一些中间值(例如 f_狗(x), f_猫(x)),然后通过一个特殊的函数(如 Softmax)将这些中间值转换为合法的概率分布。这个函数负责完成“使值为正”和“归一化(和为1)”这两项工作。

Softmax 函数:从分数到概率 🍦
上一节我们提到需要一个函数将任意实数转换为合法的概率分布。本节中我们来看看一个特别常用且有效的选择:Softmax 函数。
首先,如何将一个实数 z 变为正数?有多种函数可以实现:
z^2(平方)|z|(绝对值)max(0, z)(ReLU函数)exp(z)(指数函数)
其中,指数函数 exp(z) 因其良好的数学性质(一对一且满射)而特别方便。它可以将整个实数域映射到正实数域,不会“浪费”表示能力。
其次,如何让一组正数 {z_i} 之和为1?答案是归一化:将每个数除以所有数的总和。即,第 i 个输出为 z_i / (∑_j z_j)。
结合这两点,Softmax 函数的定义如下:对于一个包含 N 个分数的向量 z = [z_1, z_2, ..., z_N],Softmax 输出的第 i 个元素为:
softmax(z)_i = exp(z_i) / (∑_{j=1}^{N} exp(z_j))
代码描述:
import numpy as np
def softmax(z):
exp_z = np.exp(z - np.max(z)) # 减去最大值防止数值溢出
return exp_z / np.sum(exp_z)

对于二分类问题(猫和狗),Softmax 输出为:
P(y=狗 | x) = exp(f_狗(x)) / (exp(f_狗(x)) + exp(f_猫(x)))
P(y=猫 | x) = exp(f_猫(x)) / (exp(f_狗(x)) + exp(f_猫(x)))
Softmax 函数完成了将模型原始输出(“分数”或“logits”)转换为概率分布的关键步骤。


Softmax 的直观理解与名称由来 📈

让我们更直观地理解 Softmax 的行为。假设一个二分类问题(红色和蓝色),模型学习到的参数在特征空间定义了一条决策边界。
- 在决策边界的一侧,模型更倾向于预测为红色;在另一侧,更倾向于蓝色。
- 决策边界本身是模型认为两个类别概率相等(各为50%)的点。
- 随着输入点远离决策边界,预测概率会迅速趋近于 1 或 0。
为什么叫“Softmax”?
如果我们将模型的分数 z_i 乘以一个非常大的数 C,再送入 Softmax,会发生什么?
softmax(C * z)_i ≈ 1 当 z_i 是 {z} 中最大的值时。
softmax(C * z)_i ≈ 0 当 z_i 不是最大值时。
当 C 很大时,Softmax 的输出会无限接近一个“硬”的 max 操作:将全部概率赋给分数最高的类别。而当 C 较小或分数差异不大时,它的输出是“软”的,会给所有类别分配非零的概率。因此,它被称为 Soft(软的)max(最大值)。

总结 🎉
本节课中我们一起学习了监督学习的基础知识:
- 监督学习定义:从带有标签
(x, y)的数据中学习一个映射函数f_θ(x) -> y。 - 概率化预测:输出概率分布
P_θ(y | x)比输出单一标签更具表达力和优化优势。 - 模型类型:区分了直接建模
P(y | x)的判别式方法和建模P(x, y)的生成式方法。 - 概率表示:模型输出必须满足概率的正则性和归一化约束。
- Softmax 函数:作为将模型原始分数转换为有效概率分布的核心工具,我们详细探讨了其公式、代码实现和直观含义。

理解这些基础概念是构建更复杂机器学习模型的基石。在接下来的课程中,我们将探讨如何定义“好”模型(损失函数),以及如何找到最优的参数 θ(优化算法)。

🧠 课程 P50:CS 182 第16讲 第3部分 - Actor-Critic 与 Q-Learning
在本节课中,我们将推导出一个完整的深度Q学习算法。我们将从基础的在线Q学习开始,逐步分析其存在的问题,并引入重播缓冲区和目标网络等关键技术来构建稳定高效的深度Q学习(DQN)算法。最后,我们会讨论算法的实际应用技巧。
🚀 从全拟合Q迭代到在线Q学习

上一节我们介绍了全拟合Q迭代法。本节中,我们来看看如何将其推广为在线算法。
在线Q学习方法会执行一个动作,并观察一个状态转移 (s, a, s', r)。它会在单个转移上计算一个目标值 y = r + γ * max_{a'} Q_φ'(s', a')。然后,它执行一个梯度下降步骤,以最小化估计的Q值 Q_φ(s, a) 与目标值 y 之间的误差。
这就像是针对Q值的在线版Actor-Critic。它在环境中执行一步,观察转移,计算目标值,然后执行一个SGD梯度步。这种方法有时被称为沃特金斯Q学习,它是现代深度Q学习方法的基础。
为了让该算法与深度神经网络结合时能良好工作,我们需要在几个方面进行修改。
🤔 如何选择行动?探索策略
由于Q学习是离策略算法,这意味着选择行动的策略可以与评估Q值的策略不同。为了有效探索,我们不能总是选择当前认为最好的动作(argmax策略)。
以下是两种常见的探索策略:
- ε-贪婪探索策略:设定一个较小的探索概率 ε。在概率
1-ε的情况下,选择argmax动作;在概率ε的情况下,在所有可能动作中随机选择一个。- 公式:
a = argmax_a Q(s, a) with prob (1-ε); a ~ Uniform(actions) with prob ε
- 公式:
- 玻尔兹曼探索:选择动作的概率与其Q值的指数成正比。这意味着Q值相近的动作被选中的概率也相近,而Q值很低的动作被选中的概率很低。
- 公式:
P(a|s) ∝ exp(Q(s, a))
- 公式:
对于初学者,建议从简单的ε-贪婪策略开始,因为它参数少,易于调整。

⚠️ 基础Q学习存在的问题
基础在线Q学习算法存在两个主要问题:
- Q学习不是梯度下降:在计算梯度时,我们将目标值
y视为常数,忽略了y本身也依赖于Q函数参数φ的事实。这使得算法不像标准的梯度下降那样稳定。 - 样本相关性:在线学习时,连续采样到的状态转移
(s, a, s', r)是高度相关的。这违反了SGD要求样本独立同分布的假设,可能导致模型在局部相关的样本上过度拟合,而无法学习到全局结构。

💾 解决方案一:重播缓冲区

为了解决样本相关性问题,我们可以使用重播缓冲区。其核心思想是:将智能体与环境交互得到的所有转移 (s, a, s', r) 存储在一个缓冲区(或“桶”)中。当需要更新Q网络时,不是使用最新的转移,而是从缓冲区中随机采样一小批历史转移来进行训练。
这使得训练数据更接近独立同分布,更像是在进行小批量梯度下降。同时,缓冲区通常有容量上限,当数据满时会丢弃最旧的数据。

使用重播缓冲区的Q学习流程如下:
- 使用某种策略(如ε-贪婪)收集数据,并将其添加到缓冲区。
- 从缓冲区中采样一小批转移。
- 在这小批数据上执行梯度下降步骤来更新Q函数。
- 重复步骤2和3(可以重复多次,即k>1),然后回到步骤1收集新数据。
🎯 解决方案二:目标网络

即使使用了重播缓冲区,目标值持续变化(“移动的目标”)的问题依然存在。这会导致Q函数不断追逐一个变化的目标,难以稳定收敛。

受全拟合Q迭代的启发(它在固定目标上执行多步回归,较为稳定),我们可以通过减缓目标值的变化来解决这个问题。具体方法是引入一个目标网络。
我们维护两个网络:
- 主Q网络:
Q_φ,参数为φ,用于评估Q值和选择动作,并持续更新。 - 目标Q网络:
Q_φ',参数为φ',专门用于计算目标值y。

在计算目标值时,我们使用目标网络:y = r + γ * max_{a'} Q_φ'(s', a')。目标网络的参数 φ' 并不通过梯度下降直接更新,而是定期(例如每1万步)从主网络复制过来(φ' ← φ)。这样,目标值在多次更新中保持相对稳定,将问题转化为一个更稳定的监督回归任务。
🧩 完整的深度Q学习算法(DQN)

结合重播缓冲区和目标网络,我们得到了经典的深度Q学习算法,也称为DQN。
以下是算法的核心步骤:
- 使用ε-贪婪策略选择动作
a_t,执行后观察到转移(s_t, a_t, s_{t+1}, r_t),并将其存入重播缓冲区D。 - 从缓冲区
D中均匀随机采样一小批转移(s, a, s', r)。 - 使用目标网络计算目标值:
y = r + γ * max_{a'} Q_φ'(s', a')。 - 对主网络执行一个梯度下降步骤,最小化损失:
L(φ) = (Q_φ(s, a) - y)^2。 - 每隔
N步(例如N=10000),将目标网络参数更新为当前主网络参数:φ' ← φ。



🕸️ Q函数的网络表示
要完成深度Q学习算法,我们还需要确定如何用神经网络表示Q函数。主要有两种选择:
- 输入状态和动作,输出标量Q值:
Q_φ(s, a)。这在连续动作空间中更常见。 - 输入状态,为每个可能动作输出一个Q值:
Q_φ(s)输出一个向量,每个元素对应一个动作的Q值。这对于离散动作空间非常方便,因为求最大值max_a Q_φ(s, a)只需一次前向传播和一个向量最大值操作。

注意:对于连续动作空间,求最大值 max_a Q(s, a) 本身就是一个优化问题,计算上可能不便。因此,在连续动作空间中,使用基于策略的Actor-Critic方法通常更为方便。
🔄 离策略的Actor-Critic
如果我们既想要Q学习的离策略优势,又想在连续动作空间中使用,可以将Q学习的思想与Actor-Critic结合。
其算法流程与DQN类似,但关键区别在于目标值的计算和策略的更新:
- 收集数据并存入缓冲区。
- 从缓冲区采样小批量数据。
- 计算目标值时,不使用
max操作,而是使用一个独立的目标演员网络π_θ'来选择动作:y = r + γ * Q_φ'(s', π_θ'(s'))。 - 对评论家(Q网络)执行梯度下降。
- 对演员(策略网络)使用策略梯度方法进行更新。
- 定期软更新目标评论家网络和目标演员网络。
这种方法是许多现代先进算法(如软演员评论家)的基础。

💡 Q学习的实用技巧
在实践中,深度Q学习需要精心调整才能稳定工作。以下是一些实用技巧:
- 耐心调试:Q学习的超参数调整比监督学习更耗时。性能曲线可能先上升后剧烈下降。建议先在简单环境(如网格世界)中验证实现。
- 使用大容量重播缓冲区:这有助于提高稳定性,使数据分布更接近独立同分布。
- 理解学习曲线:Q学习初期可能长时间表现不佳(随机探索阶段),一旦发现好的策略,性能会快速提升,形成“S型”学习曲线。这与监督学习不同。
- 探索调度:开始时使用较大的探索率ε,然后随着训练进程逐渐衰减(例如线性衰减),这样可以在早期充分探索,后期充分利用学到的策略。
📚 本节课总结
在本节课中,我们一起学习了:
- 如何从全拟合Q迭代推导出在线Q学习算法。
- 探索策略(如ε-贪婪)的重要性。
- 基础Q学习存在的两个核心问题:非梯度下降性和样本相关性。
- 使用重播缓冲区解决样本相关性问题。
- 使用目标网络解决目标值不稳定的问题。
- 结合两者得到了经典的深度Q学习(DQN) 算法。
- 了解了Q函数的不同网络表示方法。
- 简要了解了如何将Q学习思想扩展到连续动作空间的离策略Actor-Critic方法。
- 掌握了一些让Q学习在实践中更稳定的实用技巧。

通过本讲,你应该对深度强化学习中Q学习系列算法的原理、挑战及解决方案有了系统的认识。
课程 P51:CS 182 - 第 17 讲 - 第 1 部分 - 生成模型 🧠
在本节课中,我们将要学习生成模型。我们将从概率模型的基础概念开始,逐步深入到自回归生成模型,并探讨如何将其应用于图像等非语言数据。
概述 📋
到目前为止,在这门课程中,我们已经讨论了监督学习和强化学习。从今天开始,我们将讨论无监督学习。但在深入之前,我们需要先讨论概率模型。
我们在本课程中学习的几乎所有模型,以及其他机器学习课程中的模型,本质上都是概率模型。最简单的概率模型之一是学习随机变量 x 上分布的模型。
例如,如果你有一个二维的点集合,你估计这些点的平均值和协方差,你真正做的是在学习这些点的多元正态概率模型。我们有时把它画成椭圆,其中椭圆的轮廓表示平均值的一个或两个标准差。平均值在椭圆的中心,一个或两个标准差是线所在的地方。
我们也有条件概率模型,这些模型表示给定 x 时 y 的分布 p(y|x)。像线性回归这样的模型就属于这一类,其中 y 表示点的垂直位置,x 表示水平位置。深度神经网络分类器也属于这一类,其中 Y 表示标签的类别,x 表示图像。
到目前为止,在这门课程中,我们主要讨论了学习形式为 p(y|x) 的条件模型。今天我们要学习 p(x)。
为什么需要生成模型?🤔
我们为什么要学习图像或其他类型输入的分布?我们很快就会回到这一点。问题是我们已经看到了一些生成模型,只是没有这样描述它们。
概率模型或生成模型的简单教科书图片是 p(x)。为什么它被称为生成模型?因为它可以生成 x。你不能从给定 x 模型的 p(y|x) 生成 x。所以线性回归模型不能帮助你生成 x 坐标。但是如果你有一个二维随机变量上的多元正态分布,你可以从 p(x) 中采样生成新的、相同分布的二维随机变量。
假设我们有一个多元正态分布,我们已经了解到的那种生成模型与这个多元正态有很大的不同,但它从根本上解决了同样的问题。这就是语言模型。
语言模型也表示 p(x),其中 x 是单词序列。它们只是解决同一个问题的不同方法。语言模型对 p(x) 的表示与多元正态分布非常不同,但它在解决同样的问题。从某种意义上说,它也代表了在这种情况下句子上的概率分布。
表示概率分布意味着它可以为该空间中的每个点分配概率。语言模型的空间是句子的空间。对于每一个可能的句子,语言模型可以分配一个概率。它通过使用概率链式法则来做到这一点。
链式法则指出,任何联合分布都可以分解为条件分布的乘积。所以如果我们说 p(x),其中 x 是一个序列 (x0, x1, x2, x3, ..., x6),你可以用概率链式法则来写:
p(x) = p(x0) * p(x1|x0) * p(x2|x0, x1) * p(x3|x0, x1, x2) * ... * p(x6|x0, ..., x5)
仔细听的人会注意到,我说的和幻灯片上的有点不一致。我说你从 p(x0) 开始,但在幻灯片上,方程实际上是从 p(x1|x0) 开始的。这可能看起来有点不对劲,但它不是 x0。语言模型的 x0 是我们一开始输入的第一个令牌,第一个令牌总是相同的(开始令牌),所以它实际上不是随机变量。我们知道开始令牌是什么,这不是随机的,总是一样的。
这意味着在现实中,您可以在这些条件中的每一个中省略对 x0 的依赖,因为 x0 不是变量,它只是一个常数。这意味着你可以等价地这样写:
p(x) = p(x1) * p(x2|x1) * p(x3|x1, x2) * ... * p(x6|x1, ..., x5)

现在,更明显的是,语言模型实际上是在学习 x 上的联合分布。它不是条件分布,它不以 x0 为条件,因为 x0 不是变量。它真的是一个完整的联合分布,它是一个概率模型,它是一个生成模型,就像上面的多元正态分布一样,是一个生成模型。
只是语言模型代表了一个复杂得多的分布,在一个复杂得多的物体上。但它满足概率生成模型的所有要求:它可以为你给它的每一个句子分配一个概率,你可以用它来取样句子。您实际上可以输入开始令牌,在第一个单词上获得软最大值分布,从中采样,把它作为第二个词输入,等等。你实际上可以从这个分布中生成自然语言句子。所以它是一个成熟的概率生成模型。
生成模型的应用 🚀
我们为什么要做好这件事?我们已经了解了我们可能需要语言模型的几个原因。
例如,您可以在没有任何标签的大量数据上训练语言模型。所以你可以下载所有的维基百科并训练一个语言模型。你可以用它来获取非常适合下游自然语言任务的表示。这就是我们在谈论 ELMo 和 BERT 时学到的。您可以预先训练它,以便以后进行微调,就像我们讨论 BERT 的时候所说的。
当然,你可以用它来生成东西。所以如果你真的想完成一些句子或生成假句子,您可以使用语言模型。
所以今天讨论的主题不是如何建模语言,我们已经谈过了。主题是我们如何从语言建模到一切建模。我们能不能用我们用来构建这些语言模型的相同思想,建立其他类型物体的模型,如图像或声音?这叫做无监督学习。
这叫做无监督学习,因为提供给模型的训练数据没有用任何标签标记,不需要监督。它只需要未标记的数据。就像语言模型可以用来获取表示一样,或者以后可以微调到其他任务的模型,或者为了额外产生的东西。
我们建立在其他类型数据上的生成模型,如图像、声音等等,也可以用来表示,可用于预训练的学习,用于以后的微调。它们可以用来生成东西,就像语言模型一样。图像和其他类型数据上的生成模型不需要监督。
这就是为什么我们称之为无监督学习。

从语言到图像:自回归图像生成 🖼️
好的,所以我们可以将语言模型的思想应用于图像。假设我们有一些真实的图像,我们将用它们来训练。然后我们会得到一些新的测试图像,假设我们以前没见过这些图像。你可能想做的一项特殊任务是完成这些图像。我们也可以生成全新的图像,但也许我们能想到的特殊任务是完成一幅图像。
所以也许中间有这只狗的真实照片,底部被切掉,你想让你的模型完成底部的部分。如果你能在图像上训练一个“图像语言模型”,你可以让它填空,就像语言模型可以完成一个句子一样。
在模型的每一步之前都是一个词,现在每一步都是一个像素。所以我们将尝试在像素上训练一个语言模型。就像以前一样,我们将应用概率链式法则来分解分布:
p(x) = p(x1) * p(x2|x1) * p(x3|x1, x2) * ... * p(xn|x1, ..., xn-1)
其中下标表示我们正在查看的像素。所以 x 是像素数组,x1 是第一个像素,x2 是第二个像素,x3 是第三个像素,等等。
现在我们必须在像素上选择一个顺序来做到这一点。我们将选择一些相当任意的顺序,我们一会儿再讨论。但是模型的工作方式,一种非常简单的 RNN 模型将与语言模型完全相同。
所以我们有某种开始令牌,我们把它输入模型。然后不是产生一个词,我们产生的是一个像素。我们将像素离散化。颜色实际上是离散的,每个颜色通道的值在 0 到 255 之间,所以只有 256 个可能的值。所以我们在可能的颜色值上有一个很大的 softmax,我们一次生产一个。
在第一步,RNN 接收开始令牌,它在第一个像素的颜色上产生一个 softmax 分布。然后就像在测试时的语言模型中一样,生成的采样颜色在第二个时间步骤作为输入输入,它生成第二个像素的颜色。然后这个过程重复。
当然每张图像中都有很多像素,所以这将是一个相当长的序列。我们必须选择顺序。在这种情况下,我选择的特定顺序是扫描线顺序。所以你首先通过第一行像素 (x11, x12, x13),然后去第二行 (x21, x22, x23),然后去第三行 (x31, x32, x33)。这是一个特殊的顺序。
您可以任意挑选顺序。没有一个完美的选择,但选择有一定局部性的顺序是很好的。扫描线排序很好,因为至少水平附近的像素是顺序的,虽然在垂直方向上不是。
这基本上是 Pixel RNN 或 Pixel CNN 背后的主要思想。但我们还需要弄清楚一些细节才能把它变成一个完整的、成熟的生成模型。首先我们要决定如何排列像素。不幸的是,没有一个完美的答案,所以我们只是挑一些东西。我们得决定用什么样的模型。

当我们谈论语言建模时,我们了解了 RNNs、LSTMs、多层 LSTM、双向 LSTM、Transformer。当然,其中许多选择也可供我们选择,当我们在像素上建立这些模型时。
自回归生成模型的核心步骤 📝
以下是构建和训练自回归生成模型的主要原则:
- 分解维度: 我们把 x 分成它的维度
(x1, x2, ..., xm)。x 可能是一个图像,而x1,x2,x3可能是图像中不同的像素。 - 离散化: 我们将这些维度离散为 k 个值。在某些情况下,如图像生成,图像像素自然离散成 256 个值,因为像素只能呈现 256 种不同的颜色。
- 表示联合分布: 我们在所有像素上(或在 x 的所有维度上)表示完整的联合分布
p(x)。我们通过链式法则来实现:
每个维度都依赖于以前的所有维度。对所有以前维度的依赖由 RNN 捕获,或者被一个带着掩码的自我注意的 Transformer 捕获。然后,每个维度上的实际分布只是一个 softmax,超过它所能承受的 k 个可能值。p(x) = p(x1) * p(x2|x1) * p(x3|x1, x2) * ... * p(xm|x1, ..., xm-1) - 选择序列模型: 使用您最喜欢的序列模型来实际建模
p(x)。这可能是一个 RNN、LSTM、堆叠的 LSTM、双向 LSTM、带掩码自我注意的 Transformer。你想要什么都可以。
如何使用自回归生成模型 🛠️
假设你训练好了这个模型,你可以:

- 采样: 你可以从自回归生成模型中取样。使用祖先采样:先取样
x1,然后取样x2给定x1,然后采样x3给定x1, x2,以此类推。这与从语言模型生成句子的方式完全相同。 - 补全: 如果图像的一部分(或任何数据类型)是已知的,你想完成剩下的部分。您可以输入已知的值,然后对剩下的部分进行采样。这也是完全合法的做法。
- 寻找最可能补全: 如果你想找到最有可能的完成,你可以使用波束搜索,就像我们用波束搜索机器翻译一样。
- 表示学习: 这个想法和像 ELMo 或 BERT 这样的东西是一样的。我们可以使用序列模型的隐藏状态,作为用于下游任务的像素的表示。
具体模型:Pixel RNN 🧬
让我们走过这一类中的一种流行模型,这是特定于图像的自回归生成建模,被称为 Pixel RNN。
Pixel RNN 中的想法是:我们要生成像素,一次一个,从左到右,按扫描线顺序从上到下。所以整个图像的概率是由在所有像素上的乘积给出的。如果图像是 n x n 像素,每个像素都依赖于它的“前辈”,定义为所有正在进行的扫描线,以及当前像素左侧的所有前面的像素,在当前扫描线中。
现在,每个像素由三个颜色通道(红、绿、蓝)组成,所以我们还必须一次生成一个颜色通道。所以实际上每个像素都有一个小网络:给出前面所有像素的红色,然后给出前面所有像素和这个像素自己的红色,生成绿色,然后生成给定所有前面像素和这个像素红、绿色的蓝色。你也可以把它当成三个独立的像素,但它们是特殊的、稍微长一点的、二维空间中的自回归模型。
然后每个彩色通道都是 256 路的 softmax,所以每个颜色通道的亮度可以有 256 个可能的值。
一旦你训练好了,你可以给它,例如,图像的上半部分,它可以完成下半部分不同的随机实现。这些是 Pixel RNN 生成的实际样本。当它呈现这些图像的上半部分时,你可以看到完成度是不同的,因为它们是随机取样的。但它们也相当明智。
Pixel RNN 的一些实际考虑:它还是挺慢的。因为虽然基本配方与语言模型相同,但图像可能有大约 1000 个像素(如 32x32),每个像素有三个通道,这就增加到 3000 个“时间步”。无论是训练 Pixel RNN 还是从中取样都是相当昂贵的。
此外,逐行生成图像可能无法捕捉到图像中存在的一些空间上下文,因为位于扫描线正上方的像素在 RNN 排序中被认为是遥远的。在实践中可以使用各种各样的技巧来缓解此问题,例如“对角线双向 LSTM”,其中当前像素可以从其上方的隐藏状态接收快捷连接。
更高效的变体:Pixel CNN 🎯
Pixel CNN 的想法是让这个生成过程更快,通过不在所有像素上构建完整的 RNN,但是仅仅使用卷积来确定像素的值,基于它的邻居。
在我们建立 RNN 之前,这将读取上面所有的像素和当前像素的左边。现在我们要做的是,我们要说:这个像素的值,我们将以 256 路 softmax 产生,但它只依赖于此像素的本地邻域。这很像卷积网络的工作方式,除了我们的问题是我们没有生成整个社区。
如果我们要生成这些像素,按扫描线顺序一次一个,当前像素上方的像素、左边的像素已经生成,但是下面的像素和右边的像素还没有生成。所以我们要做的是,我们实际上将有一个掩码卷积神经网络,这将读取像素的值,但它对下面的所有东西和右边的权重都是零。所以这个卷积网络产生的值,仅依赖于当前像素左侧和上方的像素。它不会在当前像素中读取(因为它在产生它),它不会读到下面或右边的任何东西。
在训练期间,我们可以并行化这个过程,因为我们有所有可用的像素,只要你强制过滤器的权重对下面或右边像素的输入为零。训练可以很快。
然而,在生成新图像时,不能并行化生成过程。因为并行生成像素意味着你必须在图像中间生成一个像素,和左上角同时。但是中间的像素需要读取上面和左边的像素来计算它的价值,而这些还没有产生。所以生成仍然需要一次一个像素地发生。
但 Pixel CNN 并不像看起来那么受限制。因为当地社区的像素本身,取决于它们附近的值。此外,我们可以用多层卷积和更大的感受野。所以如果你有五乘五的卷积,有多层,感受野可能包括几乎整个图像。所以尽管这看起来像是一个有点限制性的体系结构,它实际上相当强大,在训练中速度要快得多。
其他架构:Pixel Transformer 🔄
我们也可以建立其他类型的模型,与我们在本课程的语言建模部分中讨论的方法相同。不仅仅是 RNNs,我们可以构建 Pixel Transformer。
我们可以从一个像素接一个像素的 LSTM,或者堆叠 LSTM,转向一个带掩码的自我注意和非线性网络重复 N 次的架构,就像在 Transformer 解码器中一样。我们可以使用 Transformer 解码器风格的架构来建立图像模型。
Transformer 有一些非常好的特性:它并不强加特定的顺序(需要位置编码),这意味着即使我们必须选择一个顺序来生成像素,所有的像素在自我注意力机制下都是同样“接近”的。自我注意力可以很容易地索引到任何像素。

然而,像素数可以很大,注意力可能会变得昂贵得令人望而却步(O(n²) 复杂度)。为了使这在计算上可行,实际用于图像 Transformer 的想法是只计算不太远的像素的注意力(局部注意力),类似于 Pixel CNN 的直觉。
条件自回归模型 🎨
如果我们想生成以另一条信息为条件的东西呢?就像我们可以训练条件语言模型一样,我们可以训练条件自回归模型。
例如,你可以有一个模型不只是生成随机的像素图像,但你可以告诉它生成一只鸟、生成汽车、产生蒸汽火车。你可以有一个条件的自回归模型来模仿学习,它在模仿学习的动作上生成分布,以观察为条件。
食谱非常类似于条件语言模型。你有一些你正在调节的东西,我们称之为上下文。这可以像对象标签一样简单,或者像模仿学习中的状态或观察一样复杂。上下文是使用某种编码器网络处理的,这可能只是一个前馈模型、一个卷积网络,也可能是另一个 RNN。这个编码器的输出被用来开始自回归生成过程的第一步。
权衡与考虑 ⚖️

自回归生成模型基本上就像语言模型,但对于其他类型的数据。更准确地说,语言模型只是一种特殊类型的自回归生成模型。
您可以用许多不同的方式表示自回归模型:可以使用 RNNs 或 LSTMs,可以使用本地上下文模型(如 Pixel CNNs,训练起来更便宜、更简单),或者可以使用更复杂的自我注意模型(如 Transformers)。

优点:
- 它们提供了具有概率的完整分布。它们真的会给每个可能的 x 分配一个概率,这些概率很容易得到。
- 概念上非常简单。如果你理解语言模型,你很可能会理解自回归生成模型。
缺点:
- 对于大的 x(如高分辨率图像),它们可能非常慢。训练和生成都极其缓慢。
- 通常需要限制图像分辨率(例如 32x32 或 64x64),而其他生成模型(如 GANs)可以生成更高分辨率的图像。
总结 📚
本节课中我们一起学习了生成模型的基础。我们从概率模型和语言模型出发,理解了自回归生成模型的核心思想:通过链式法则将联合分布分解为一系列条件分布,并利用序列模型(如 RNN、CNN、Transformer)进行建模。
我们探讨了如何将这一思想应用于图像生成,介绍了 Pixel RNN、Pixel CNN 和 Pixel Transformer 等具体模型及其权衡。我们还了解了条件自回归模型,使其能够根据特定上下文(如类别标签)生成内容。

自回归模型提供了清晰、可计算的概率分布,是理解更复杂生成模型的重要基石。在接下来的课程中,我们将继续探索其他类型的生成模型。
课程 P52:CS 182 讲座 17 - 第 2 部分 - 生成模型 🧠
在本节课中,我们将要学习一种不同的模型类别——自动编码器。自动编码器是一类广泛用于无监督学习任务的模型。我们将探讨其基本思想、不同类型以及它们如何通过特定结构来学习有意义的表示。
自动编码器概述 🎯
在上一节中,我们讨论了生成模型的基本概念。本节中,我们来看看自动编码器。自动编码器并非都是生成模型,但它们是理解现代生成模型(如变分自动编码器)的重要基础。
自动编码器的核心思想是:模型接收输入(如图像),并尝试重建相同的输出。为了使这个任务不平凡,模型内部需要引入某种结构,阻止它简单地学习恒等函数,从而迫使它学习数据的有意义表示。

自动编码器的通用框架 🔧
我们可以将许多模型(如像素RNN、图像Transformer)视为广义的自动编码器。它们都遵循一个通用模式:输入是数据(如像素),输出是重建的相同数据,中间的结构使得重建任务具有挑战性。
以下是自动编码器通用框架的要点:
- 输入 (x):原始数据,例如图像像素。
- 编码器 (Encoder):一个神经网络,将输入
x映射到隐藏状态 (h)。 - 隐藏状态 (h):数据的中间表示,是我们希望学习的有意义特征。
- 解码器 (Decoder):另一个神经网络,从隐藏状态
h重建输出x'。 - 目标:最小化重建损失
L(x, x'),使输出x'尽可能接近输入x。

为了使模型学习有意义的 h,而非恒等函数,必须在模型设计、数据处理或正则化中引入结构。

强制结构的方法 🛠️
上一节我们介绍了自动编码器的通用框架。本节中,我们来看看几种在隐藏状态 h 上强制结构的具体方法。
以下是几种常见的强制结构的方法:
- 限制维度 (Bottleneck):使隐藏状态
h的维度远小于输入x的维度。公式表示为:dim(h) << dim(x)。这迫使网络压缩信息。 - 稀疏性 (Sparsity):强制隐藏状态
h中大多数元素为零。这可以通过在损失函数中添加L1正则化项实现:Loss = L_reconstruction(x, x') + λ * ||h||_1。 - 去噪 (Denoising):用噪声破坏输入,得到
x̃,然后训练模型从x̃重建干净的x。这迫使模型学习区分信号与噪声。 - 先验匹配 (Prior Matching):强制隐藏状态
h的分布符合某个简单的先验分布(如高斯分布)。这将在后续课程中详细讨论。

瓶颈自动编码器 📦

现在,让我们深入了解第一种方法:瓶颈自动编码器。这是最经典的自动编码器之一。
其思想非常简单:无论输入维度多高,都选择一个维度低得多的隐藏状态。例如,对于一个100x100像素(10000维)的图像,将 h 限制为仅128维。由于 h 无法存储所有像素信息,模型必须学习数据的压缩表示。
一个有趣的数学事实是:如果编码器和解码器都是线性的,并使用均方误差损失,那么训练得到的瓶颈自动编码器等价于主成分分析 (PCA),其中 h 的维度就是主成分的数量。因此,非线性瓶颈自动编码器可以看作是PCA的非线性推广,用于降维。
虽然这种设计现在被认为有些过时,但在某些需要非线性降维的场景中仍然有用。
稀疏自动编码器 🌲
接下来,我们探讨第二种方法:稀疏自动编码器。这种方法背后的直觉是:我们能否用一小组“属性”来描述输入?

例如,描述一张图像时,与其列出所有像素值,不如列出它是否具有“耳朵”、“轮子”、“翅膀”等属性。对于大多数图像,大多数属性值应为零(例如,汽车没有翅膀)。这种表示是稀疏的,并且更有条理、更容易解释。
稀疏自动编码器的目标就是学习这种稀疏的隐藏表示 h。与瓶颈自动编码器不同,h 的维度可以很高(甚至超过输入维度),但通过损失函数强制其稀疏。
实现稀疏性的一种简单方法是在损失函数中添加 L1 正则化项:
总损失 = 重建损失 + λ * Σ|h_i|
L1 范数的梯度会持续将较小的 h_i 推向零,而较大的值由于对重建有用得以保留,最终导致稀疏表示。
去噪自动编码器 🧼
最后,我们来看第三种广泛使用的方法:去噪自动编码器。其核心思想与BERT的掩码语言模型类似:一个好的模型如果学会了数据的有意义结构,就应该能够“修复”被破坏的输入。

具体操作是:先对输入 x 加入噪声(如随机掩盖部分像素),得到损坏版本 x̃。然后,训练自动编码器从 x̃ 重建出原始的干净数据 x。为了完成这个任务,模型必须学会区分数据中的真实模式(信号)与随机噪声。
去噪自动编码器实现简单,且能非常有效地学习到稳健的特征表示。像BERT这样的现代模型就可以被视为一种复杂的、针对序列数据的去噪自动编码器。



总结 📝
本节课中,我们一起学习了自动编码器的基本概念和几种主要类型。
我们首先了解了自动编码器的通用框架:通过编码器-解码器结构重建输入,并依赖内部结构来学习有意义的隐藏表示。
接着,我们详细探讨了三种强制结构的方法:
- 瓶颈自动编码器:通过降低隐藏状态维度来强制压缩,实现降维。
- 稀疏自动编码器:通过
L1等正则化强制隐藏状态稀疏,旨在获得解耦的、类似属性的特征。 - 去噪自动编码器:通过破坏输入再要求重建,迫使模型学习数据的本质结构,是实践中最常用的类型之一。

每种方法都有其优缺点和适用场景。理解这些经典方法为我们后续学习更强大的现代生成模型(如变分自动编码器)奠定了坚实的基础。
课程 P53:CS 182 - 第 17 讲 - 第 3 部分 - 生成模型 🧠
在本节课中,我们将要学习生成模型,特别是自动编码器的历史背景及其在现代深度学习中的演变。接着,我们将深入探讨潜在变量模型的基本概念,为后续学习变分自编码器(VAE)和生成对抗网络(GAN)等高级模型打下基础。
自动编码器的历史背景与分层预训练 📜
上一节我们介绍了自动编码器的基本工作原理,本节中我们来看看它在深度学习发展早期的一个关键应用。
在深度学习的早期,自动编码器通过一种称为“分层预训练”的技术被广泛使用。这项技术是激发现代深度学习研究的重要原因之一。尽管如今它已不那么常用,但了解其历史背景有助于理解某些反复出现的设计思想。
分层预训练的核心思想是:如何在不进行端到端反向传播的情况下,训练非常深的神经网络。
以下是分层预训练的具体步骤:
- 从输入数据(如图像)开始,训练一个浅层的自动编码器(例如2-4层)。这个编码器会学习到一个隐藏表示(用蓝色矩形表示)。
- 使用这个浅层编码器对训练集中的所有图像进行编码,得到对应的隐藏状态。
- 将这些隐藏状态作为新的输入,在其上训练另一个浅层自动编码器。这个编码器会学习到更抽象的隐藏表示(用绿色矩形表示)。
- 重复此过程,逐层训练更多的自动编码器,每一层都学习到更高级、更抽象的表示。
- 将所有浅层编码器组合起来,就构成了一个非常深的编码网络。最后,我们可以对这个深度网络进行端到端的微调,以执行特定的下游任务(如图像分类)。
在早期,这种方法非常有效,因为它能够训练出比单纯使用端到端反向传播更深的网络。然而,随着ReLU激活函数、批量归一化、更好的超参数调优技术(如Adam优化器)的出现,我们能够更有效地直接训练深度网络。因此,分层预训练的重要性逐渐降低,如今更多地被直接端到端的训练方法所取代。
自动编码器的现状与应用场景 🔍
上一节我们回顾了自动编码器的历史角色,本节中我们来看看它在当前深度学习领域的定位。
如今,传统的自动编码器(如去噪或稀疏自动编码器)使用得不再那么广泛,因为有更强大的替代方案出现。
- 对于表征学习:变分自编码器(VAE,一种概率生成模型)和对比学习等方法,在很大程度上取代了传统的自动编码器。
- 对于生成任务:生成对抗网络(GAN)和自回归模型(如PixelRNN)在生成高质量样本方面表现更佳。传统自动编码器难以从中进行采样生成新数据。
- 对于快速、简单的表征学习:训练一个去噪自动编码器在计算上仍然非常高效,比训练PixelRNN等模型快几个数量级。因此,如果需要一个快速实现的代理方案,它仍然是一个可行的选择。
传统自动编码器的一个主要限制是难以从中采样或生成新数据,这限制了其应用范围。变分自编码器(VAE)通过引入概率框架部分解决了这个问题,使其成为当前应用最广泛的自动编码器变体。
潜在变量模型导论 🧩
上一节我们讨论了自动编码器的现状,本节中我们将开始学习生成模型的一个核心框架——潜在变量模型。这将为我们周三学习变分自编码器(VAE)奠定基础。
什么是潜在变量?
在之前的概率模型中(如 p(x) 或 p(y|x)),唯一的随机变量是观测到的 x 和 y。潜在变量模型则引入了未被观测到的随机变量 z,它代表了数据生成过程中的一些隐藏因素。
一个经典的例子是高斯混合模型。假设我们观测到的数据点形成了三个簇(如下图),但模型并不知道这些簇的存在。我们可以引入一个潜在变量 z,它是一个有三个取值的分类变量,分别代表属于红色、品红色或绿色簇。数据的分布 p(x) 就可以表示为在所有可能 z 值上,条件分布 p(x|z) 的加权和。
p(x) = Σ_{z} p(x|z) * p(z)

为何使用潜在变量模型?
假设数据 x 的分布 p(x) 非常复杂(例如,自然图像的分布),难以直接用简单的分布(如单个高斯分布)来建模。
潜在变量模型的思路是:通过组合简单的分布来建模复杂的分布。具体做法是:
- 为潜在变量
z设定一个简单的先验分布p(z),例如标准正态分布。 - 定义一个由神经网络参数化的条件分布
p_θ(x|z)。这个分布本身可以很简单(例如高斯分布),但其参数(均值和方差)是z的复杂函数。
这样,复杂的 p(x) 就被表示为:
p(x) = ∫ p_θ(x|z) * p(z) dz
我们将建模的复杂性从分布本身转移到了确定性的映射函数(神经网络)上,而这是我们所擅长的。
如何训练潜在变量模型?⚙️
我们通常使用最大似然估计来训练概率模型,即最大化训练数据的对数似然:
max_θ Σ_i log p_θ(x_i)
然而,对于潜在变量模型,p_θ(x_i) = ∫ p_θ(x_i|z) * p(z) dz 这个积分通常没有解析解,难以直接计算。
实践中,我们常使用期望对数似然作为替代目标。其核心直觉是:既然数据集中没有 z,我们就根据当前模型“猜测”每个 x_i 对应的最可能的 z 是什么,然后基于这个猜测来优化模型。这涉及到估计后验分布 p(z|x),这个过程被称为概率推断。
解决概率推断是训练潜在变量模型的主要挑战。周三我们将学习一种非常流行的方法——变分推断,它是变分自编码器(VAE)训练的核心。
深度学习中的潜在变量模型 🖼️
上一节我们介绍了潜在变量模型的抽象概念,本节中我们来看看它在深度学习中的具体实现形式。
在深度学习中,一个潜在变量生成模型通常包含以下两部分:
- 先验分布
p(z):通常设定为一个简单的分布,如多维标准正态分布。z是一个随机向量。 - 解码器(生成器):一个神经网络,它将潜在向量
z映射到数据x上的一个分布p_θ(x|z)。
生成新样本的机械过程非常简单:
- 从先验分布
p(z)中采样一个随机向量z(例如,生成一组服从标准正态分布的随机数)。 - 将
z输入解码器神经网络。 - 解码器输出定义了数据
x上的分布(例如,每个像素的RGB值),从这个分布中采样,即可得到一个新样本(如图像)。
如何表示 p_θ(x|z)?
这取决于数据的类型:
- 连续值数据(如图像像素归一化到[0,1]):通常使用对角协方差多元高斯分布。解码器为每个像素输出一个均值
μ(和可选的方差σ)。为简化,方差常被设为固定值,此时训练目标简化为均方误差(MSE)。 - 离散值数据(如8位图像像素):可以为每个像素输出一个256路的softmax分布(类似于PixelRNN),但所有像素在给定
z的条件下被假设为相互独立。
使用什么网络架构?
解码器的架构选择很重要:
- 小型图像或非图像数据:使用全连接网络(一系列线性层加激活函数)通常是足够的。
- 大型、复杂图像:更佳的选择是使用转置卷积网络。首先通过全连接层将
z向量转换为低分辨率特征图,然后通过一系列转置卷积层进行上采样,逐步提高分辨率,最终生成完整图像。
训练方法概览
训练潜在变量模型主要有三种范式,我们将在周三深入探讨:
- 变分自编码器(VAE):通过变分推断来近似后验分布
p(z|x),并最大化一个被称为证据下界(ELBO)的似然下界。 - 标准化流(Normalizing Flows):学习一个从
z到x的可逆映射,使得概率密度的计算变得容易。 - 生成对抗网络(GAN):不显式建模每个
x对应的z,而是训练一个生成器,使其生成的样本分布与真实数据分布尽可能匹配,通过一个判别器来提供训练信号。

总结 📝


本节课中我们一起学习了生成模型的重要基础。我们首先回顾了自动编码器通过分层预训练在深度学习历史上的作用,并分析了其当前的应用场景。随后,我们深入探讨了潜在变量模型的核心思想:通过引入简单的潜在变量分布和由神经网络参数化的条件分布,来建模复杂的数据分布。我们还了解了其基本表示形式和训练所面临的核心挑战(概率推断)。最后,我们概述了在深度学习中实现潜在变量模型的几种流行方法(VAE、标准化流、GAN),为接下来的课程做好了准备。

课程 P54:CS 182 第 18 课 - 第 1 部分:潜在变量模型 🧠
在本节课中,我们将继续讨论生成模型,重点介绍潜在变量模型。我们将探讨变分自编码器和另一种称为标准化流的模型。我们将学习如何通过变分近似来训练这些复杂的模型,并理解其背后的数学原理。
回顾:潜在变量模型

上一节我们介绍了生成模型。本节中,我们来看看如何用潜在变量模型表示复杂分布。
潜在变量模型的核心思想是:我们不再直接对复杂数据 x 的分布 p(x) 建模,而是引入一个更简单的潜在变量 z(例如,一个简单的单峰高斯分布)。然后,我们假设在给定 z 的条件下,x 的分布 p(x|z) 是一个简单的分布(如高斯分布),但其参数(均值和方差)是 z 的复杂函数(通常由神经网络表示)。
公式表示:
- 先验分布:
p(z),例如z ~ N(0, I) - 条件分布:
p(x|z) = N(μ(z; θ), σ(z; θ)),其中μ和σ是神经网络函数。
这样,x 的边缘分布 p(x) 由积分给出:
p(x) = ∫ p(x|z) p(z) dz

从 z 采样生成 x 很容易:先采样 z,再从 p(x|z) 采样 x。然而,训练模型需要计算或估计这个积分,这通常很困难。
变分近似与证据下界
为了训练模型,我们避免直接计算棘手的积分,转而使用期望对数似然。其直觉是:我们不知道每个数据点 x_i 对应的真实潜在变量 z,因此我们“猜测” z 的可能值,并最大化 x 和 z 的联合概率。

这个“猜测”过程称为推断,即估计给定 x 时 z 的后验分布 p(z|x)。推断本身通常也很困难。因此,我们采用变分近似:用一个更简单、易于处理的分布 q_i(z) 来近似真实后验 p(z|x_i)。

对于任何选择 q_i(z),我们可以推导出对数似然 log p(x_i) 的一个下界,称为证据下界。
推导过程:
-
引入变分分布
q_i(z),并利用 Jensen 不等式:
log p(x_i) = log ∫ p(x_i|z) p(z) dz = log E_{z~q_i(z)} [p(x_i|z)p(z) / q_i(z)]
≥ E_{z~q_i(z)} [log (p(x_i|z)p(z) / q_i(z))]
这个下界记为L_i。 -
展开
L_i:
L_i = E_{z~q_i(z)} [log p(x_i|z)] + E_{z~q_i(z)} [log p(z)] - E_{z~q_i(z)} [log q_i(z)]
其中,最后一项是q_i(z)的负熵。
核心概念:
- 证据下界:
L_i(p, q_i) = E_{z~q_i(z)} [log p(x_i|z) + log p(z) - log q_i(z)] - 最大化
L_i会同时:- 提高数据的对数似然
log p(x_i)。 - 减小
q_i(z)与真实后验p(z|x_i)之间的 KL 散度,使下界更紧。
- 提高数据的对数似然



信息论概念:熵与 KL 散度
为了深入理解证据下界,我们需要了解两个信息论概念。
熵衡量一个分布的随机性。对于分布 p(x),其微分熵定义为:
H(p) = -E_{x~p(x)} [log p(x)]
熵越高,分布越均匀、越不确定;熵为零意味着分布是确定性的。
KL 散度衡量两个分布 q(x) 和 p(x) 之间的差异:
D_KL(q || p) = E_{x~q(x)} [log (q(x) / p(x))] = -H(q) - E_{x~q(x)} [log p(x)]
KL 散度总是非负的,且当 q = p 时为零。
证据下界 L_i 与 KL 散度的关系为:
log p(x_i) = L_i(p, q_i) + D_KL(q_i(z) || p(z|x_i))
由于 KL 散度非负,L_i 是 log p(x_i) 的确切下界。最大化 L_i 会推动 log p(x_i) 上升,同时最小化 q_i(z) 与真实后验之间的 KL 散度。

训练变分模型
上一节我们建立了理论框架。本节中,我们来看看如何将其转化为可行的训练算法。

我们的目标是最大化所有数据点的证据下界之和。这涉及对模型参数 θ(生成网络 p_θ(x|z))和变分参数 φ_i(每个数据点的近似后验 q_i(z))进行优化。
传统变分推断方法:
为数据集中每个点 x_i 单独维护一组变分参数(如高斯分布的均值 μ_i 和方差 σ_i)。然后通过梯度上升同时更新 θ 和所有 {μ_i, σ_i}。
此方法的局限性:
参数数量与数据集大小成正比。对于大型数据集(数百万样本),需要维护数百万个 (μ_i, σ_i) 对,这非常低效且难以扩展。
摊余变分推断与变分自编码器

为了解决参数过多的问题,我们引入摊余变分推断。其核心思想是:不再为每个数据点单独学习变分参数,而是训练一个推断网络 q_φ(z|x),它是一个神经网络,输入数据 x,直接输出近似后验 q(z|x) 的参数(如均值和方差)。
这样,我们只需要学习推断网络的参数 φ 和生成网络的参数 θ,参数数量是固定的,与数据集大小无关。

变分自编码器正是基于此思想构建的:
- 编码器(推断网络):
q_φ(z|x),将数据x映射到潜在空间分布。 - 解码器(生成网络):
p_θ(x|z),将潜在变量z映射回数据空间。
训练时,我们最大化所有数据点的证据下界 L(θ, φ)。其梯度可以通过从 q_φ(z|x_i) 采样 z,并使用重参数化技巧来得到低方差的梯度估计,从而用随机梯度下降进行优化。
总结

本节课我们一起学习了潜在变量模型的核心思想与训练方法。
- 潜在变量模型通过引入简单潜在变量
z和条件分布p(x|z)来建模复杂数据分布p(x)。 - 变分近似通过一个简单的分布
q(z)来近似难以计算的后验分布p(z|x)。 - 证据下界为对数似然提供了一个可优化的下界,最大化它等价于同时提高数据似然和 tighten 变分近似。
- 摊余变分推断通过一个共享的推断网络
q_φ(z|x)来预测所有数据点的变分参数,极大地提升了效率,并构成了变分自编码器的基础。


在下一部分,我们将深入探讨变分自编码器的具体实现细节和训练技巧。

课程 P55:CS 182 第 18 讲 - 第 2 部分:潜在变量模型 🧠
在本节课中,我们将要学习一种名为摊销变分推理的方法,用于训练潜在变量模型。我们将探讨如何通过神经网络来分摊推理的计算负担,并详细介绍其核心算法——变分自编码器的实现。
第一部分:从正则变分推理到摊销推理 🔄
在上一部分,我们讨论了正则变分推理是训练潜在变量模型的一种可行方法。但是,如果我们需要为数据集中的每个数据点 x_i 单独存储一个变分分布 q(z_i) 的均值和方差,当数据集很大时,存储量会非常巨大。

为了解决这个问题,本节我们将讨论一种名为摊销变分推理的方法。之所以称为“摊销”,是因为我们不再为每个数据点单独表示均值和方差,而是将推理的挑战分摊到所有数据点上。具体做法是使用一个神经网络来为我们进行推理。
因此,我们实际上有两个神经网络:
- 一个生成模型
p_θ(x|z),参数为θ。 - 一个推理网络
q_φ(z|x),参数为φ,它试图预测给定输入x时潜在变量z的分布。具体来说,它会输出z的均值μ_φ(x)和方差σ_φ(x)。
这意味着我们不再为每个数据点存储单独的均值和方差,而是训练一个神经网络来输出这些参数。

第二部分:摊销变分推理的目标函数 📈
上一节我们介绍了摊销推理的基本思想,本节中我们来看看其目标函数。我们使用证据下界作为优化目标。
我们之前推导的证据下界表达式为:
ELBO = E_{q(z_i)}[log p_θ(x_i|z_i)] - KL(q(z_i) || p(z))
在摊销推理中,我们用 q_φ(z|x_i) 替换了 q(z_i)。因此,新的证据下界为:
ELBO(θ, φ) = E_{q_φ(z|x_i)}[log p_θ(x_i|z)] - KL(q_φ(z|x_i) || p(z))
训练过程如下:
- 计算证据下界关于参数
θ和φ的梯度。 - 使用梯度上升步骤更新参数。
计算关于 θ 的梯度相对简单,可以通过反向传播完成。然而,计算关于 φ 的梯度则更具挑战性,因为 φ 出现在我们计算期望的分布参数中。
第三部分:计算梯度的挑战与方法 ⚙️
上一节我们提到了计算 φ 梯度的挑战,本节中我们来看看具体的解决方法。证据下界中关于 φ 的梯度出现在两部分:
- 期望项
E_{q_φ(z|x_i)}[log p_θ(x_i|z)]。 - KL散度项
KL(q_φ(z|x_i) || p(z))。

KL散度项通常有解析解(特别是当 q_φ 和 p(z) 都是高斯分布时),其梯度容易处理。困难在于期望项的梯度。
一种计算期望项梯度的方法是使用策略梯度方法。我们可以将期望项重写为 E_{q_φ(z|x_i)}[r(x_i, z)],其中 r(x_i, z) = log p_θ(x_i|z)。这与强化学习中的目标函数形式相同,因此可以使用策略梯度定理来估计梯度:
∇_φ E_{q_φ(z|x_i)}[r(x_i, z)] ≈ (1/N) Σ_j r(x_i, z_j) ∇_φ log q_φ(z_j | x_i)
然而,这种方法的缺点是梯度估计的方差很大,可能需要大量样本才能获得准确的估计,计算成本较高。
第四部分:重参数化技巧 🎯
上一节我们介绍了策略梯度方法及其高方差问题,本节中我们来看一个更优的解决方案——重参数化技巧。
我们的目标是计算 ∇_φ E_{q_φ(z|x_i)}[log p_θ(x_i|z)]。由于 q_φ(z|x_i) 是高斯分布,我们可以将采样过程重参数化:
z = μ_φ(x_i) + ε ⊙ σ_φ(x_i)
其中,ε ~ N(0, I) 是一个标准高斯噪声,⊙ 表示逐元素相乘。
通过这种重参数化,z 成为了 φ 和确定性的噪声 ε 的确定性函数。因此,期望可以改写为:
E_{q_φ(z|x_i)}[log p_θ(x_i|z)] = E_{ε~N(0,I)}[log p_θ(x_i | μ_φ(x_i) + ε ⊙ σ_φ(x_i))]

现在,期望所基于的分布 N(0, I) 不再依赖于参数 φ。这样,我们就可以将梯度算子移入期望内部:
∇_φ E[...] = E_{ε~N(0,I)}[∇_φ log p_θ(x_i | μ_φ(x_i) + ε ⊙ σ_φ(x_i))]
我们可以通过以下步骤估计这个梯度:
- 从
N(0, I)中采样一个(或少量)噪声样本ε。 - 计算
z = μ_φ(x_i) + ε ⊙ σ_φ(x_i)。 - 计算目标函数
log p_θ(x_i | z)。 - 使用自动微分(如 TensorFlow 或 PyTorch)计算该目标函数关于
φ的梯度。
这种方法梯度估计的方差远低于策略梯度方法。
第五部分:变分自编码器的实现 🏗️
上一节我们学习了重参数化技巧的理论,本节中我们来看看如何将其组合起来,实现一个完整的模型——变分自编码器。
变分自编码器的证据下界目标函数可以写为:
L(θ, φ; x_i) = E_{ε~N(0,I)}[log p_θ(x_i | z)] - KL(q_φ(z|x_i) || p(z))
其中 z = μ_φ(x_i) + ε ⊙ σ_φ(x_i)。
以下是实现步骤:
- 编码器网络(推理网络)
q_φ(z|x):输入x_i,输出均值μ_φ(x_i)和对数方差log σ_φ^2(x_i)(通常输出对数方差以保证正值)。 - 采样:生成噪声
ε ~ N(0, I),计算z = μ_φ + ε * exp(0.5 * log σ_φ^2)。 - 解码器网络(生成网络)
p_θ(x|z):输入z,输出重构的x或其分布参数(例如,对于二值图像输出伯努利参数,对于连续值输出高斯均值)。 - 计算损失:
- 重构损失:
log p_θ(x_i | z)。 - KL散度损失:
KL(N(μ_φ, σ_φ^2) || N(0, I)),此项有解析解。
- 重构损失:
- 优化:使用自动微分计算总损失
L关于θ和φ的梯度,并用梯度上升(或Adam等优化器)更新参数。
其网络结构类似于一个自编码器,因此得名“变分自编码器”。编码器将数据映射到潜在空间,解码器从潜在空间重构数据,但中间加入了随机性(通过 ε)来模拟概率分布。
第六部分:方法对比与总结 📊
在本课程中,我们一起学习了摊销变分推理和变分自编码器。最后,我们来对比一下两种梯度估计方法:

-
策略梯度方法:
- 优点:可以处理离散和连续的潜在变量。
- 缺点:梯度估计方差高,可能需要更多样本和更小的学习率。
-
重参数化技巧:
- 优点:实现简单,梯度估计方差低,更高效稳定。
- 缺点:仅适用于连续的潜在变量(且通常要求分布易于重参数化,如高斯分布)。
在实践中,对于连续的潜在变量模型(如VAE),重参数化技巧是首选方法。它使得我们可以像训练普通神经网络一样,用反向传播高效地训练复杂的概率生成模型。

总结:本节课我们深入探讨了摊销变分推理,它通过神经网络分摊了推理成本。我们重点介绍了重参数化技巧,该技巧通过将随机采样过程转化为确定性计算,使得梯度计算变得高效且低方差,从而实现了变分自编码器这一强大且流行的生成模型。
课程 P56:CS 182 第 18 讲第 3 部分 - 变分自编码器 🧠
在本节课中,我们将学习变分自编码器的完整架构、工作原理、训练目标以及如何将其应用于条件生成任务。我们还将探讨其网络结构设计、实践中常见的问题及其解决方案。

变分自编码器架构 🏗️
变分自编码器是一种潜变量模型。它假设潜变量 z 服从高斯先验分布,并观测到变量 x(例如一张图像)。该模型包含一个用于推理的编码器 q 和一个用于生成的解码器 p。
编码器 q(z|x) 是一个神经网络,它接收输入 x,并输出潜变量 z 的均值 μ 和方差 σ²。解码器 p(x|z) 接收一个从潜变量分布中采样的 z,并输出观测变量 x 的分布参数(例如图像中每个像素的均值和方差)。
潜变量采样公式:
z = μ + ε ⊙ σ
其中 ε 是从标准正态分布中采样的噪声向量,⊙ 表示逐元素相乘。


训练完成后,变分自编码器可用于生成新图像。这与去噪自编码器或瓶颈自编码器不同,后者主要学习数据表示,但不具备良好的生成能力。

训练目标与直觉 🎯

变分自编码器的训练目标由两部分组成。第一部分类似于自编码器的重构损失,第二部分是 KL 散度正则项。
目标函数:
L(θ, φ; x) = E_{z∼q_φ(z|x)}[log p_θ(x|z)] - β * D_KL(q_φ(z|x) || p(z))
上一节我们介绍了变分自编码器的基本架构,本节中我们来看看其训练目标的直观理解。
目标的第一部分鼓励模型从潜变量 z 准确地重构出输入 x。第二部分,即 KL 散度项,惩罚了编码分布 q_φ(z|x) 与先验分布 p(z)(通常是标准正态分布)之间的差异。

这种设计带来了一个关键直觉:KL 散度项鼓励编码器产生的 z 分布与从先验 p(z) 中采样的分布相似。这样,在测试生成时,如果从先验 p(z) 中采样,解码器也会对这类 z 感到“熟悉”,从而能够生成合理的图像。
条件变分自编码器 🔄
我们还可以训练条件变分自编码器,这实际上相当简单。如果我们希望建立一个模型,在给定输入 x 的条件下,输出变量 y 的分布 p(y|x) 可能很复杂。
此时,我们的直觉是:虽然 p(y|x) 复杂,但 p(y|x, z) 在给定潜变量 z 后可能变得简单。因此,解码器变为 p_θ(y|x, z),编码器变为 q_φ(z|x, y)。所有操作都与之前相同,只是现在生成 y,且所有过程都以 x 为条件。
以下是条件变分自编码器的架构要点:
- 编码器接收 x 和 y,输出 μ 和 σ。
- 采样得到 z = μ + ε ⊙ σ。
- 解码器接收 x 和 z,生成 y。
这种架构可用于需要多模态输出的任务,例如模仿学习,或生成以类别标签等特定信息为条件的图像。
网络结构设计 🧱
现在,让我们更多地讨论用于变分自编码器的神经网络架构,特别是如何设计带有卷积层的变分自编码器。
以下是一个可能的卷积变分自编码器架构示例:
- 编码器:输入图像(例如 64x64x3)。经过若干层卷积(通常伴随步幅以降低分辨率)后,特征图被展平,并通过全连接层输出潜变量 z 的均值 μ 和方差 σ。
- 采样:从标准正态分布采样噪声 ε,利用公式 z = μ + ε ⊙ σ 得到潜变量。
- 解码器:z 首先通过全连接层提升维度,然后经过若干层转置卷积(反卷积)进行上采样,最终重建出与输入尺寸相同的图像。

关于解码器输出的说明:解码器可以输出每个像素的均值和方差,也可以只输出均值并固定方差。此外,也可以使用离散输出,如每个像素的 256 路 softmax。
一个思考:能否设计一个全卷积的变分自编码器?从机制上讲是可行的,只需让 μ、σ 和 ε 都成为卷积响应图即可。但需要注意,由于先验 p(z) 假设各维度独立,而卷积特征图的值之间通常相关,这可能导致最小化 KL 散度变得困难。
实践中的常见问题与解决方案 ⚠️
在实践中使用变分自编码器时,可能会遇到一些常见问题。
以下是两个典型问题:
- 潜变量被忽略:模型倾向于忽略 z,q_φ(z|x) 接近先验 p(z),z 不包含关于 x 的信息。这导致重建结果模糊,像是数据集的平均图像。
- 潜变量未被压缩:q_φ(z|x) 与先验 p(z) 差异很大,z 编码了过多信息。这导致重建效果很好,但从先验 p(z) 采样并解码时,会生成无意义的图像。
这两个问题可以通过训练目标中 KL 散度项的值来表征:
- 对于问题一,KL 散度通常太低。
- 对于问题二,KL 散度通常太高。
理解这些问题至关重要,因为它们的解决方案通常不同。
调整 KL 散度权重

为了使训练更有效,一个常见的技巧是在 KL 散度项前引入一个可调的超参数 β。
调整策略:
- 如果出现问题一(潜变量被忽略,重建模糊),需要降低 β,减轻对 KL 散度的惩罚,让模型更自由地利用 z。
- 如果出现问题二(潜变量未被压缩,样本质量差),需要增加 β,加强对 KL 散度的约束,使 q_φ(z|x) 更接近先验。
有时,可以动态调整 β:训练初期使用较小的 β,让模型学会使用 z;当重建质量较好后,逐渐增加 β 以改善生成样本的质量。
总结 📝

本节课中,我们一起学习了变分自编码器的完整框架。我们了解了其编码器-解码器架构、由重构损失和 KL 散度正则项构成的目标函数,以及该设计如何使模型具备生成能力。我们还探讨了条件变分自编码器的扩展、卷积网络结构的设计,并深入分析了实践中“忽略潜变量”和“潜变量未压缩”这两个关键问题及其通过 β 参数进行调节的解决方案。掌握这些概念对于有效训练和应用变分自编码器至关重要。
课程 P57:CS 182 讲座 18 - 第 4 部分 - 隐变量模型 🧠
在本节课中,我们将学习一种与变分自编码器结构相似但训练更简单的模型。我们将重点介绍规范化流模型,它使用确定性的可逆映射,从而能够精确计算数据的概率,而不仅仅是变分下界。
概述 📋
上一节我们讨论了变分自编码器及其变分下界。本节中,我们将探讨一种不同的隐变量模型——规范化流。该模型的核心思想是使用一个确定性的、可逆的映射函数 f 将潜在变量 z 转换为数据 x,从而可以直接计算数据的精确概率。
变量变化公式与核心思想 🔄
在变分自编码器中,解码器输出的是给定 z 时 x 的分布。而在规范化流中,我们使用一个确定性解码器,即 x = f(z),其中 f 是一个可逆函数。
为什么这很重要?因为现在我们可以使用变量变化公式来计算 x 的概率密度 p(x):
公式:
p(x) = p(z) * |det(df/dz)|^{-1}
其中 z = f^{-1}(x)。
这个公式的直观理解是:当通过函数 f 将 z 映射到 x 时,概率密度会随着 f 引起的体积膨胀或收缩而调整。|det(df/dz)| 项正是用来补偿这种体积变化的。
规范化流模型的目标 🎯
我们的训练目标是最大化数据集中所有样本 x 的对数似然。将变量变化公式代入,目标函数变为:
公式:
log p(x) = log p(z) - log |det(df/dz)|
通常,我们假设 p(z) 是一个简单的分布,例如标准正态分布 N(0, I)。
因此,构建规范化流模型的关键在于:设计一个可逆的神经网络 f,使得其雅可比矩阵的行列式 det(df/dz) 易于计算。
构建可逆网络层 🧱
规范化流模型由多层可逆变换堆叠而成。整个网络 f 的可逆性要求每一层都是可逆的。整个网络的对数行列式是各层对数行列式之和。
公式:
log |det(df/dz)| = Σ_i log |det(df_i/dz_i)|
因此,我们的主要任务是设计一个可逆的神经网络层。
一种简单的可逆层:仿射耦合层
以下是构建可逆层的一种简单方法,称为仿射耦合层。
首先,将输入向量 z 分成两部分:z = [z_A, z_B]。
z_A:前半部分。z_B:后半部分。
该层的变换定义如下:
x_A = z_A(直接复制前半部分)。x_B = z_B + g_θ(z_A)(后半部分加上一个由前半部分计算出的非线性变换)。
其中 g_θ 可以是任意的神经网络(如带ReLU的全连接层)。
可逆性证明:
给定输出 x = [x_A, x_B],我们可以轻松恢复输入 z:
z_A = x_A。z_B = x_B - g_θ(z_A) = x_B - g_θ(x_A)。
雅可比行列式:
该变换的雅可比矩阵是一个下三角矩阵,其对角线元素全为1。因此,其行列式 det(df/dz) = 1,这使得对数行列式项为零,计算非常简便。
然而,这种设计的表达能力有限,因为它不改变数据的体积(尺度)。
更具表达力的可逆层:Real NVP
为了增强模型的表达能力,我们可以使用 Real NVP 层。它同样对输入进行分割,但变换更为复杂:
x_A = z_A。x_B = z_B ⊙ exp(h_θ(z_A)) + g_θ(z_A)。
其中:
⊙表示逐元素相乘。h_θ和g_θ是两个神经网络。exp操作确保缩放因子为正,避免信息丢失。
可逆性:
逆变换为:
z_A = x_A。z_B = (x_B - g_θ(z_A)) ⊙ exp(-h_θ(z_A))。
雅可比行列式:
此时雅可比矩阵的对角线不再全为1,行列式变为 exp(Σ_i h_θ(z_A)_i) 的乘积。这使得模型能够学习更复杂的分布。
规范化流的优势与局限 ⚖️
优势:
- 精确似然:可以直接计算数据点的精确概率
p(x),而非变分下界。 - 概念简洁:无需处理变分推断的复杂性。
局限:
- 架构限制:必须使用特殊的可逆层,不能直接使用标准网络层(如普通ReLU层)。
- 维度约束:由于每一层都必须可逆,输入和输出的维度必须保持一致。对于高维数据(如图像),这要求潜在变量
z的维度与数据x相同,可能不如变分自编码器(可使用低维潜在空间)高效。
总结 📝
本节课我们一起学习了规范化流模型。
- 我们首先了解了其核心思想:使用可逆的确定性函数连接潜在空间与数据空间,并通过变量变化公式计算精确似然。
- 接着,我们探讨了如何通过堆叠可逆层来构建深度规范化流网络。
- 我们介绍了两种具体的可逆层设计:简单的仿射耦合层和更具表达力的Real NVP层。
- 最后,我们讨论了规范化流相对于变分自编码器的优势(精确似然)和局限(架构限制与维度约束)。


规范化流为生成模型提供了一条避开变分下界、直接优化似然的途径,是深度学习概率建模中的重要工具之一。
🎨 课程 P58:CS 182 - 第19课 - 第1部分 - 生成对抗网络 (GANs)
在本节课中,我们将学习最后一类生成模型——生成对抗网络。我们将探讨其核心思想、工作原理以及为何它能生成逼真的图像。
潜在变量模型回顾
上一节我们介绍了规范化流等生成模型。本节中,我们来看看生成对抗网络如何从另一个角度构建生成模型。

首先,让我们回顾一下潜在变量模型的基本概念。在潜在变量模型中,我们有一个潜在变量 z。这本质上是一个随机数向量,从某个先验分布中采样得到。例如,我们可以设定 z 有256个维度,每个维度都从均值为0、方差为1的正态分布中采样。
我们选择一个先验分布 p(z),然后使用一个神经网络来表示给定 z 时 x 的分布,或者像在规范化流中那样,建立一个从 z 到 x 的确定性映射。这个模型将随机向量 z 作为“随机数生成器”,并利用它们生成相应的图像。其目标是捕捉图像的真实分布。
生成过程可以概括为:从已知的 p(z) 中采样 z,然后通过神经网络生成图像 x。公式表示如下:
[
x = G(z), \quad z \sim p(z)
]

其中 G 是生成器网络。
生成对抗网络的核心思想
上一节我们回顾了传统的潜在变量模型训练方法。本节中,我们来看看生成对抗网络如何采用一种全新的训练策略。
生成对抗网络的核心思想是:与其像变分自编码器那样为每个真实图像猜测对应的潜在变量 z,不如直接训练整个模型,使其在整体分布层面上生成与真实图像相似的图像。

我们可以通过一个例子来理解“在整体分布层面上相似”的含义。假设有两组人脸图像,左边一组和右边一组。它们并非一一对应,但整体看起来风格相似。如果从两组中各取五张图像混合展示,你无法区分哪些来自左边、哪些来自右边,那么我们就说这两组图像在分布层面上是相似的。
GAN 通过设置一个“游戏”来实现这一目标。在这个游戏中,唯一获胜的方式就是生成逼真的图像。
鉴别器与生成器的对抗
上一节我们介绍了GAN的基本目标。本节中,我们来看看实现这一目标的具体机制。
GAN 包含两个网络:一个鉴别器和一个生成器。
鉴别器是一个分类器,其任务是查看图像并判断它是真实的还是生成的。它输出一个介于0到1之间的概率值,表示图像为真的概率。公式表示如下:
[
D(x) = P(\text{图像 } x \text{ 为真})
]
生成器是一个确定性函数,它将一个随机噪声向量 z 映射为图像 x。与规范化流不同,它不必是可逆的,也不要求维度匹配。其目标是生成足以“欺骗”鉴别器的图像。
为了训练鉴别器,我们需要一个包含真实图像和生成图像的数据集。真实图像来自我们的训练集,生成图像则来自当前(可能还很差劲的)生成器。
基础训练流程
以下是GAN的一个基础训练流程。请注意,这不是最终算法,但能帮助我们理解其工作原理。
- 获取真实图像数据集 D_true。
- 初始化生成器 G。
- 生成假图像数据集 D_fake:从先验分布 p(z) 中采样 z,然后通过 G(z) 得到假图像。
- 训练鉴别器 D:将 D_true 中的图像标记为“真”,D_fake 中的图像标记为“假”,进行监督学习训练。
- 使用鉴别器为生成器构造损失函数。一个简单的选择是:生成器希望其生成的图像被鉴别器判为“真”的概率最大。因此,损失函数可以是生成图像为“真”的负对数概率:
[
\mathcal{L}_G = -\log D(G(z))
]
然而,这个基础流程存在一个问题:如果只执行一次第5步,生成器只需要生成比当前假图像集稍好的图像就能“欺骗”当前这个鉴别器,但这可能仍远未达到逼真的程度。
迭代对抗训练
为了解决上述问题,GAN采用了一种迭代的对抗训练过程,让鉴别器和生成器在竞争中共同进步。
我们不会一次性训练好鉴别器,而是交替进行以下步骤:
- 固定生成器,更新鉴别器:用当前生成器生成一批假图像,与真实图像混合,训练鉴别器一步(一个梯度步),使其更好地区分真假。
- 固定鉴别器,更新生成器:根据当前鉴别器给出的“真实性评分”,更新生成器一步,使其生成更逼真的图像来“欺骗”鉴别器。
这个过程就像一场游戏:鉴别器不断学习识破假货,而生成器不断学习制造更逼真的赝品。生成器获胜的唯一途径,就是生成与真实图像在整体分布上无法区分的图像。

为何GAN能学习完整分布?
上一节我们描述了训练过程。本节中,我们深入探讨一下为何这种对抗机制能迫使生成器学习完整的真实数据分布,而不仅仅是生成几张逼真的图片。


生成器的理想目标是让鉴别器对其所有生成图像都输出概率 0.5,即鉴别器完全无法判断真假。要达到这一点,生成器必须满足两个条件:
- 生成逼真的图像:如果图像有明显瑕疵,鉴别器会轻易识破。
- 生成所有类型的逼真图像:这是关键点。假设真实数据集中一半是猫、一半是狗。如果生成器只学会了生成逼真的狗,那么当鉴别器看到猫时,它会立刻知道这来自真实数据集(因为假数据里没有猫);当看到狗时,它也会倾向于认为来自假数据集(因为假数据全是狗)。这样,鉴别器就不会在所有图像上都输出0.5。
因此,为了在这场对抗游戏中最终获胜,生成器必须学会覆盖真实数据分布中的所有模式(如猫和狗),而不仅仅是其中一部分。这就是GAN能够学习完整分布的理论原因。
注意:在实践中,GAN有时会遇到“模式崩溃”问题,即生成器只生成少数几种类型的图像,而未能覆盖全部分布。这是优化GAN的一个挑战。

GAN的效果展示
自2014年Ian Goodfellow提出以来,GAN技术已经取得了长足的进步。
- 早期GAN:可以生成MNIST手写数字、人脸等,但图像较为模糊。
- 现代GAN:如ProGAN、BigGAN等,能够生成高分辨率、细节丰富的图像,在许多情况下与真实照片难以区分。
- 条件生成:GAN还可以用于“图像到图像”的翻译任务,例如将草图转换为照片、将白天场景转换为夜晚等,展现了强大的创造力和应用潜力。
总结


在本节课中,我们一起学习了生成对抗网络的基本原理。我们了解到,GAN通过设置鉴别器和生成器之间的对抗游戏,迫使生成器学习匹配真实数据的整体分布。其核心在于迭代的对抗训练过程,以及生成器为了“欺骗”鉴别器而必须生成多样且逼真图像的内在动力。尽管存在模式崩溃等优化挑战,GAN已成为当前最强大的生成模型之一,能够创造出令人惊叹的逼真图像。
课程 P59:CS 182 第19讲 第2部分 - GANs 🧠
在本节课中,我们将深入探讨生成对抗网络的技术细节。我们将从GAN算法的基本框架开始,逐步分析其工作原理、优化目标、理论推导以及实际应用中的架构设计。
GAN算法概述
上一节我们介绍了GAN的基本概念,本节中我们来看看其算法的具体轮廓。GAN的过程可以被描述为一个经典的两人博弈,即最小最大问题。
在这个博弈中,有两个参与者:生成器 G 和判别器 D。他们试图最小化或最大化同一个目标函数,但方向相反。生成器 G 试图最小化该目标,而判别器 D 试图最大化它。这构成了他们之间的竞争关系。
目标函数本质上是判别器的分类目标,可以表示为交叉熵损失。具体公式如下:
公式:
min_G max_D V(D, G) = E_{x~p_data(x)}[log D(x)] + E_{z~p_z(z)}[log(1 - D(G(z)))]
在这个表达式中:
- 第一项
E_{x~p_data(x)}[log D(x)]是判别器对真实数据(正样本)的对数似然期望。 - 第二项
E_{z~p_z(z)}[log(1 - D(G(z)))]是判别器对生成数据(负样本)的对数似然期望。
判别器 D 的目标是最大化 V(D, G),这等价于最小化交叉熵损失。生成器 G 的目标是最小化 V(D, G),即让判别器难以区分生成的图像是假的。

优化算法与梯度计算
上一节我们定义了GAN的目标函数,本节中我们来看看如何通过优化算法来求解这个最小最大问题。这不再是传统的单目标梯度下降,而是一个寻找两人博弈纳什均衡的过程。
我们将目标函数用参数重写:
- θ 代表生成器 G 的参数。
- φ 代表判别器 D 的参数。
公式:
min_θ max_φ V(θ, φ)
算法在两者之间交替进行:
- 固定生成器 G (θ),对判别器 D (φ) 执行梯度上升步,以最大化
V。 - 固定判别器 D (φ),对生成器 G (θ) 执行梯度下降步,以最小化
V。
以下是实现细节:
使用小批量随机梯度
在实践中,我们使用小批量数据来近似梯度:
- 对于第一项期望
E_{x~p_data(x)}[log D(x)],我们使用一小批真实训练数据点。 - 对于第二项期望
E_{z~p_z(z)}[log(1 - D(G(z)))],我们采样相同数量的一批随机噪声 z,通过生成器得到生成图像,然后在这批生成图像上计算。
梯度计算
- 判别器的梯度:这直接是交叉熵损失的梯度,计算方式与标准分类器相同。
- 生成器的梯度:生成器的损失函数涉及将生成的图像输入判别器。因此,我们通过判别器反向传播到生成器。利用链式法则,生成器的梯度为
dL/dθ = (dL/dx) * (dx/dθ),其中x = G(z)。在实际编码中,只需构建一个包含生成器和判别器的计算图,然后使用自动微分软件进行常规的反向传播即可。

理论分析:GAN优化了什么?
我们了解了GAN的训练过程,但GAN最终优化的是什么目标?我们能否对这个双人博弈进行分析,并正式声明在收敛时,生成器的分布会匹配真实数据分布?
为了理论分析,我们简化问题:假设判别器能力足够强(即函数空间足够有表现力),我们可以求出给定生成器 G 时,最优判别器 D* 的闭式解。
设真实数据分布为 p_data(x),生成器分布为 p_g(x)。最优判别器 D* 为:
公式:
D*(x) = p_data(x) / (p_data(x) + p_g(x))
这个表达式被称为贝叶斯最优分类器。
现在,我们将这个最优判别器 D*(x) 代回原始的 min-max 目标函数中,从而得到一个只关于生成器 G 的目标函数 C(G)。经过代数推导(涉及KL散度),C(G) 可以表示为:
公式:
C(G) = -log(4) + KL(p_data || (p_data+p_g)/2) + KL(p_g || (p_data+p_g)/2)
其中,KL(P||Q) 是分布P和Q之间的KL散度。这个表达式实际上是 Jensen-Shannon散度 (JSD)。JSD具有以下性质:
- 当且仅当
p_data = p_g时,JSD为0。 - JSD是对称的,即
JSD(p_data || p_g) = JSD(p_g || p_data)。
结论:从理论上讲,当生成器和判别器都达到最优时(即达到纳什均衡),GAN的训练过程是在最小化真实数据分布 p_data 与生成器分布 p_g 之间的Jensen-Shannon散度。这证明了GAN确实在努力匹配真实数据分布。


实践技巧与架构设计

上一节的理论推导为我们提供了理解,本节中我们来看看实际训练GAN时的一些重要技巧和常见架构。
生成器损失函数的选择
在原始公式中,生成器的损失是 E_z[log(1 - D(G(z)))],即最小化图像被判别为“假”的概率。但在实践中,我们常使用一个修改版本:E_z[-log(D(G(z)))],即最大化图像被判别为“真”的概率。

选择后者的原因在于梯度行为更好。函数 log(1-x) 在x较小时(生成器很差时)梯度非常小,不利于学习;而函数 -log(x) 在x较小时梯度很大,能提供更强的学习信号,当生成器变好时梯度又逐渐变小,有利于稳定收敛。
GAN架构
以下是几种常见的架构:
- 全连接GAN:适用于低分辨率图像(如MNIST手写数字)。生成器和判别器均由若干全连接层构成。
- 卷积GAN (DCGAN):适用于更高分辨率的图像。生成器使用转置卷积(反卷积)进行上采样,判别器使用标准卷积层。这是现代GAN的基础架构。
- 条件GAN (cGAN):用于生成特定类别的图像。方法是将类别标签(如one-hot向量)同时连接到生成器和判别器的输入。这样,生成过程可以以类别信息为条件。
- 循环GAN (CycleGAN):用于解决无配对数据的图像到图像翻译问题(如将马转换为斑马)。它使用两个生成器(G: A->B, F: B->A)和两个判别器,并引入循环一致性损失,确保将一个域的图像转换到另一个域再转换回来时,能近似还原为原图像。
总结
本节课中我们一起学习了生成对抗网络的核心内容。
我们首先回顾了GAN作为两人最小最大博弈的基本框架。接着,我们详细讲解了其交替优化算法以及梯度的计算方法。通过理论分析,我们推导出最优判别器的形式,并证明了在均衡状态下,GAN实际上是在最小化真实分布与生成分布之间的Jensen-Shannon散度。最后,我们探讨了实践中的关键技巧(如损失函数选择)和多种GAN架构(包括全连接、卷积、条件及循环GAN)。

GAN通过生成器与判别器的对抗性训练,提供了一种强大且灵活的生成模型框架,在图像生成、风格转换等领域取得了显著成功。

机器学习基础课程 P6:损失函数 🎯

在本节课中,我们将学习如何为机器学习模型定义损失函数。损失函数用于衡量模型预测的好坏,是选择最佳模型参数的关键。
模型选择的三步法
上一节我们介绍了模型类(例如使用SoftMax输出概率的分类器)。本节中,我们来看看如何选择模型参数θ。

以下是选择模型参数的标准三步法(加上一个由计算机执行的步骤):
- 定义模型类:确定程序的表示形式及参数设置。这定义了所有可能模型的集合。
- 定义损失函数:量化模型类中特定模型的好坏,以便比较不同模型。
- 选择优化器:选择一个算法,在模型类中搜索能使损失函数最小化的模型。
- 执行优化:由计算机(如GPU)执行优化算法。
旁注:这种分解方式与神经科学家David Marr提出的认知分析层次密切相关。计算层次对应“为什么”(目标/损失函数),算法层次对应“什么”(模型表示),实现层次对应“如何”(优化算法)。这种分离有助于我们模块化地思考和设计复杂的机器学习系统。

数据生成的基本假设

在定义损失函数前,我们需要一个关于数据如何生成的基本模型。虽然现实世界生成图片和标签的过程极其复杂,但我们可以建立一个简化的概率模型来指导思考。

我们假设每张图片x是从某个分布p(x)中随机采样得到的。同时,其标签y是从一个依赖于图片的条件分布p(y|x)中采样得到的。根据概率链式法则,数据点(x, y)的联合分布为:
p(x, y) = p(x) * p(y|x)
我们进一步假设整个训练集D = {(x1, y1), (x2, y2), ..., (xn, yn)}中的样本是独立同分布的:
- 独立:每个
(xi, yi)元组的出现与其他元组无关。 - 同分布:每个元组都来自相同的联合分布
p(x, y)。
基于此,整个数据集出现的概率可以写为各样本概率的乘积:
p(D) = ∏ p(xi, yi) = ∏ [ p(xi) * p(yi|xi) ]

最大似然估计
我们模型的目标是学习一个条件分布p_θ(y|x),使其尽可能接近真实的p(y|x)。一个好的模型应该让观测到的数据看起来更可能发生。
因此,我们选择参数θ的原则是:最大化训练数据集D出现的概率,即最大化p(D)。这被称为最大似然估计。
然而,直接最大化概率乘积在数值计算上会遇到问题,因为许多小于1的概率相乘会得到一个极其接近0的数。为了解决这个问题,我们取概率的对数。对数函数是单调的,最大化对数概率等价于最大化原始概率。

log p(D) = Σ log p(xi) + Σ log p_θ(yi|xi)
由于第一项Σ log p(xi)与参数θ无关,在优化时可以视为常数。因此,我们只需最大化第二项:
θ* = argmax_θ Σ log p_θ(yi|xi)

在优化领域,习惯上我们将问题表述为最小化。因此,我们定义负对数似然作为损失函数:
L(θ) = - Σ log p_θ(yi|xi)
我们的目标变为寻找最小化该损失的θ。
常见的损失函数
负对数似然是一种通用且强大的损失函数。以下是其他一些常见的损失函数示例:

- 负对数似然 / 交叉熵:如上所述,常用于分类问题。交叉熵是衡量两个分布差异的度量,在特定条件下,用单个样本近似期望值就得到了负对数似然。
L(θ) = - Σ log p_θ(yi|xi)

-
0-1损失:用于分类问题,非常直观。如果预测错误,损失为1;如果预测正确,损失为0。它直接反映了分类准确率,但因其不连续、不可导,通常不直接用于基于梯度的优化。
L(θ) = Σ 1{ f_θ(xi) != yi } -
均方误差:常用于回归问题(预测连续值)。它最小化预测值与真实值之间的平方差。有趣的是,均方误差等价于在高斯噪声假设下的负对数似然。
L(θ) = Σ ( f_θ(xi) - yi )^2
总结

本节课中,我们一起学习了机器学习中损失函数的核心概念。我们首先介绍了模型选择的三步法框架。然后,基于数据独立同分布的假设,推导出了最大似然估计的原理,并由此引出了最常用的负对数似然(交叉熵)损失函数。最后,我们简要了解了其他类型的损失函数,如0-1损失和均方误差。理解损失函数是设计有效机器学习模型的关键一步。
课程 P60:CS 182 讲座 19 第 3 部分 - GANs 🧠
在本节课中,我们将要学习生成对抗网络的一些现代训练方法。这些方法通常比经典的简单GAN训练效果更好。
概述 📋
在讲座的最后一部分,我们将讨论一些现代的训练方法,即GANs。这些方法通常比我之前描述的经典简单GAN工作得更好。
GANs存在的问题 🤔
如果你只是按照我在讲座第二部分中描述的方式实施GAN训练,你可能会发现最简单的问题是它需要大量的超参数调整才能正常工作。

为什么对于GANs来说这是一个特别困难的场景?也许可以说明这个问题。假设x是一维的,这样我现在就可以把它们画在幻灯片上了。
假设这些蓝色圆圈代表真实数据。当然在现实中你可能有成千上万甚至数百万的数据点,而这里我只有五个。但现在只是为了可视化,假设这些橙色圆圈代表当前生成器生成的样本。
所以你可以想象p_data是一个分布,看起来像这样。和p_g(生成器的分布)现在看起来是这样的。如果我用这个p_data训练我的鉴别器,这个p_g,我的鉴别器基本上真实的概率可能是这样的。它将完美地接近真实数据的零点,它将完美地零点零接近生成的数据。它在中间的某个地方会有一些决策边界,离任何一种分布都很远。

现在请记住,生成器完全通过使用梯度来学习,通过鉴别器。那么生成器梯度实际上在生成的数据附近是什么?鉴别器在所有这些点上均匀地输出零。所以记住这是双人游戏的表达式,第二部分是生成器的目标。所有这些值,所有这些log(1 - D(x))在生成的数据附近基本相同,因为鉴别器在这一点上已经完全饱和,它输出了完美的精确度。对所有的训练数据都是完美的一个零,对于所有生成的数据完全为零。你可能需要调整了一点,以确保你不会得到NaN,但基本上它将是一个对数,这不是很有效。

改进思路 💡

那么我们怎样才能让这变得更好?我们怎样才能确保即使生成器与鉴别器相比非常糟糕,当生成的数据离p_data很远时,鉴别器仍然给我们一个有意义的梯度信号?

我们可以做的一件事是我们可以以某种方式改变我们的鉴别器。也许我们可以以某种方式改变辨别者的训练方式,因此即使它可以完美地将p_data从p_g中分类,仍然鼓励在这些分布之间产生一个更平滑的斜坡,给发电机一些梯度信号,引导它向p_data。

我们可以做的另一件事是我们基本上可以稍微改变一下分布。也许我们可以以某种方式修改p_data和p_g,所以重叠的比较多,然后辨别者就不会那么震惊了,然后我们会看到一个更强的梯度信号。所以这两个都是可行的想法。
我们可以用一些实际的方法来实例化这种直觉。


改进的GAN技术 🛠️
一些改进的GAN技术,没有特别的顺序:

- 最小二乘GAN (LSGAN):是修改GAN鉴别器的一种方法,输出一个实值数,而不是零到一之间的概率,并训练鉴别器,使其产生更平滑的斜坡。
- Wasserstein GAN (WGAN):是将鉴别器修改为Lipschitz连续的一种方法,这也鼓励它在p_data和p_g数据之间有更干净的斜率。
- 梯度惩罚 (Gradient Penalty):是提高WGAN的一种方法,因此判别器被限制为连续的,就更难了。所以它试图做与WGAN相同的事情,但更有效。
- 谱范数 (Spectral Norm):是一种真正约束判别器连续的方法。所以基本上如果你再听到Wasserstein、梯度惩罚或谱范数,他们都在试图用稍微不同的方式做同样的事情。
- 实例噪声 (Instance Noise):一个稍微不同的方法来改善GAN训练。基本上就是做幻灯片底部的事情,它试图通过增加大量噪声来改变分布,到p_data和生成的示例。

希望让它们的分布有更多的重叠。


所以这是实例噪声。

如果你想了解这些不同技术之间的一些比较,至少在理论上是这样,你可以看看这篇论文,叫做《GANs的哪些训练方法实际上是收敛的》。它分析了GANs的基本收敛。左上角的标准GANs在收敛方面非常糟糕。所以在许多简单的情况下他们可能找不到平衡,但许多改进的GAN技术可以找到平衡,或者至少接近它。
如果你想要一种技术实际使用,我的建议是用梯度惩罚或谱范数,这些都是今天很好的选择。梯度惩罚实现起来更简单一点,谱范数可以更有效一点,但实现起来有点难。它们基本上都是基于Wasserstein GAN的。

在今天的讲座中,我将主要关注Wasserstein GAN,然后简要地说明梯度惩罚和谱范数如何工作。但请记住,像最小二乘GAN、实例噪声也是可行的选择,欢迎你在文献中查找它们,以了解更多关于它们的信息。

Wasserstein GAN 的直觉 🚚
现在我将再次专注于描述Wasserstein GAN。高层直觉是Jensen-Shannon散度(经典GAN所使用的)没有任何方法来解释距离。
基本上,如果你有这种情况,你可以说这两个分布相距甚远。或者你可以有这个场景,然后说好,这两个分布非常接近。就Jensen-Shannon散度而言,这两种情况差不多,因为在这两种情况下,分布之间的重叠可以忽略不计。


所以散度和KL散度,和大多数其他纯粹使用在这两种情况下概率几乎相同,尽管很明显底部的要好得多。
高层解释了为什么在GAN中很难获得有意义的梯度,当生成器很糟糕的时候。因为一个好的梯度应该告诉你改善上面情况的方法是走向下面的情况。但是如果你的散度度量认为这两个分布几乎相同,那么当然它不会有太大的梯度。
在数学上这是真的原因是,如果你看Jensen-Shannon散度的形式,GAN目标的形式,你会看到基本上它是用一个分布下的对数概率表示的,在另一种分布下的预期。其中p_g近于零,因为所有的x p_data都不是零,那么这些表达式就不会给你任何有意义的梯度。这样基本上解释了为什么顶部的情况被认为与底部的情况非常相似,就Jensen-Shannon散度而言,尽管在底部的情况下,这些分布非常接近。
所以我们能做的是纠正这个问题,用再次训练的方法,用不同的散度度量来更好地捕捉两个分布的距离。

地球移动距离 (Earth Mover‘s Distance) 🏗️
下面是你如何考虑一个更好的指标:考虑欧几里得空间中所有概率比特之间的距离。所以想象一下这些分布,基本上是成堆的材料,成堆的泥土。你要走多远才能把泥土从一堆搬到另一堆?这有时被称为最优运输问题或地球搬运工距离。你要走多远才能把一个发行版运送到另一个发行版?
所以如果你想象一下这个,这条蓝色曲线实际上就像一堆物理材料,这个橙色的曲线是你想让材料去的地方。想象一下你必须拿起每一块蓝色的材料,把它搬到橙色的堆里。你要走多远才能做到这一点?所以你可以把这个拿过来,把这个拿过去,把这个拿过来,等等。每次你去那里,你回去再拿一些。所以你要走的总距离,这两个分布相距多远。
现在,当然啦,在现实中这些小材料是无限小的,你要无限多次旅行。但如果你把这个连续的东西离散化,取极限,当离散化为零时,这将给你对距离测量的正确直觉。所以如果他们真的靠得很近,即使它们的重叠仍然为零,你必须走更少的路来携带每一点分配,从一堆到另一堆。所以这被称为地球移动距离或Wasserstein距离。


Wasserstein距离的形式化定义 📐
正式定义Wasserstein距离有点复杂,我会尽我所能给你这个正式定义的直觉,但如果你不完全清楚,别太担心,你其实不需要明白这一点,为了再次实现Wasserstein GAN。但获得对此的直觉可能会有所帮助。
所以Wasserstein距离可以写成 W(p_data, p_g)。正式定义是 y 与 x 之间距离的期望值,其中 y 和 x 根据联合分布 γ 的最优选择进行分布。所以你找到最小化这个期望值的最佳伽马 γ。
什么是伽马 γ?伽马是 x 和 y 上的分布,其中关于 x 的边距是 p_data,相对于 y 的边距是 p_g。直觉是 x 和 y 之间的相关性,y 表示应运输哪个 x。

对我来说,试图理解这一点的最好方法是视觉。如果你想象一个图,其中一个轴是 x,另一个是 Y。你可以把 p_data 看作是 y 轴上的分布,p_g 是 x 轴上的分布。然后伽马 γ 是一个联合分布,描述了 y 轴上的哪个点,到 x 轴上的哪个点。
所以你会注意到这里,p_g 的左侧部分与伽马下 p_data 的底部部分相关,这意味着这个部分会放在这里,然后这个部分会在这里,这一块会放在这里,这一块会放在这里,以此类推。所以为了评估Wasserstein距离或推土机的距离,你会发现伽马 γ 使这些距离的期望值最小化,使每个 y 的 x 和 y 之间的差异最小化,到那个特定的 x。所以直觉上,寻找伽马 γ 就像找到移动 p_data 中所有点的最佳计划。


Kantorovich-Rubinstein 对偶性 🔄
实际上像这样直接学习伽马 γ 是非常非常困难的,因为 p_data 未知,伽马 γ(x, y) 本身可能是一些非常非常复杂的分布。
所以有一个非常酷的定理,我们可以根据Kantorovich-Rubinstein对偶性使用,这为我们提供了一个更容易处理的方法来解决Wasserstein距离。我要陈述这个结果,我不打算证明,但声明是Wasserstein距离(上面的表达式)也等于所有可能函数 f 的上确界(supremum),f 的 p_data 下的期望值减去 f 的 p_g 下的期望值。
我们不会在这里证明这一点,但这一点的证明使用了二元性的工具,类似于你可能学到的,当你研究一类凸优化中的拉格朗日对偶时。所以你知道,我不想在这里讨论这个,因为我认为这门课的预备知识不一定会提供必要的工具来做到这一点。
非常非常粗糙的直觉,基本上 f 将来自你得到的同一个地方,基本上是拉格朗日乘数。这是一种高层次的直觉。所以你对伽马 γ 有一个限制,即伽马 γ 的边缘与 p_data 和 p_g 匹配,你基本上要把它的对偶。所以我不打算证明,但你现在可以相信我的话,这是真的。
这个表达Wasserstein距离的真正吸引人的地方是,它表达了 p_g 和 p_data 下 f 的期望值的差异。就像一个普通的GAN,正则GAN是 p_data 下 log D(x) 的期望值,然后在 p_g 下 log(1 - D(x)) 的期望值。现在我们有了 p_data 下 f(x) 的期望值,和 p_g 下 -f(x) 的期望值。所以这真的很酷,这开始看起来更像是一场游戏。
现在,你们中的一些人可能已经注意到了,我没有提到任何关于上确界已经接管了这个有趣的表达,说 ||f||_L <= 1。那是你应该重新做这件事的简写,一个Lipschitz的函数。一个Lipschitz意味着 f(x) 和 f(y) 之间的差应小于或等于 x 与 y 之差。所以它基本上是Lipschitz连续的,常数为1。这相当于说函数是有界斜率,所以永远不要太陡。
为什么好?因为如果 f 太陡,那么它可以在 p_data 下任意最大化 f(x),并任意最小化 p_g 下的 f(x)。这个坡度限制基本上是一种对你能行驶多快的速度限制,当您将一个发行版的部分传送到另一个发行版时。所以如果我们回到地球搬运工的类比,把坡度限制在1就等于说你的限速是1,你从一个点到另一个点的速度有多快。

当然,很容易把它概括为,如果你是两个Lipschitz,意味着 f(x) - f(y) 小于等于 2 * ||x - y|| 或一般的 K-Lipschitz,也很好。然后你的解决方案将是Wasserstein距离乘以 K。所以如果你不是1-Lipschitz而是2-Lipschitz,那么最高的解将是 W(p_data, p_g) 乘以2。所以重点不是它是一个Lipschitz,斜坡是有界的。斜坡是有界的意味着从一个配送站到另一个配送站的速度是有限制的,这使得这些距离实际上是有意义的。如果你没有速度限制,如果你能旅行,如果你基本上可以瞬间传送,那么你就不会得到一个有意义的数量。所以你需要某种速度限制,速度限制是多少实际上并不重要,只要是好的。
那么我们如何执行速度限制?我们如何强制 f 的斜率应该以某个常数为界?

实现 Lipschitz 约束:权重裁剪 (Weight Clipping) ✂️
这基本上是实例化WGAN的困难部分。所以我有个主意,这不一定是最好的主意,但如果 f 是一个具有ReLU激活的神经网络,这是一个想法。

如果你只是绑定权重矩阵 w 的权重,例如,如果你只有一层网络,你基本上有一个线性层,然后是一个ReLU非线性。所以如果 f_θ(x) = ReLU(W_1 x + b),这不是一个很好的架构,但只是作为一个例子。你在 W 中的所有条目都在,让我们说,负零点零一和正零点零一之间。那么你的斜率可以大于 0.01 * d。
这是一个非常简单的例子。如果你有一个两层神经网络,所以它是 W_2 * ReLU(W_1 x + b_1) + b_2,你的参数是 W_1, b_1, W_2, b_2。然后可以将两个权重矩阵中的所有条目约束为在负数之间,-0.01 和 0.01 之间。然后你的斜率也将是有界的,当然比以前大了很多,因为你实际上是把这些 W 相乘在一起,但它仍然受一个常数的限制。
所以这并不能保证它是一个Lipschitz,除非你非常小心地选择界限,但它确实保证了它是一些常数的Lipschitz,这意味着斜坡将是有界的,这意味着你会得到一些Wasserstein距离的倍数。所以它确实保证了某个有限 k 的 k-Lipschitz。

这就是你们所有人需要的。现在警告,这是从最初的Wasserstein GAN论文中摘录的,我要直接读给你听,是那篇论文中一个过于诚实的段落:
“权重裁剪显然是一种可怕的方式来强制Lipschitz约束。如果裁剪参数较大,那么任何方法都可能需要很长时间才能达到极限,从而使批评家更难训练直到最优。如果剪裁很小,这很容易导致梯度消失,当层数较大或不使用批处理规范化时。我们用简单的变体进行了实验,就像把重量投射到一个稍微不同的球体上。我们坚持用重量夹,由于它的简单性和已经很好的性能。然而,我们确实留下了强制Lipschitz约束的话题,在神经网络环境中的进一步研究。我们积极鼓励有兴趣的研究人员改进这种方法。”
事实上,如今,我们会认为这种重量裁剪方法大多已经过时了。

WGAN 训练过程 🔄
有更好的方法来做到这一点,但是如果我们想通过这个Wasserstein GAN程序,它是这样工作的:

我们会像以前一样有生成器 G。我们现在会有一个“鉴别器”(在WGAN中常称为“批评家” C 或 f),而不是输出图像是真实的概率,它实际上只是输出一个实数。那个实数是不受约束的,所以你只要一堆带有ReLUs的线性层,比如说,最后没有Sigmoid。所以一个例子,一个非常简单的鉴别器的架构将只是一个两层神经网络,真的在第一层之后,第二层之后什么都没有。所以你不再输出概率了,只是输出实数。
鉴别器将使用权重裁剪。事情是这样的:随机梯度算法的每一次迭代,您将更新鉴别器 f_θ,使用 E_{x~p_data}[f(x)] - E_{z~p(z)}[f(G(z))] 的梯度。有一个小类型应该是 f(G(z))。所以这就像普通GAN中的更新一样,仅使用WGAN目标。但是你会把Theta里面的所有权重矩阵都裁剪掉,在 f 的参数内,在某个常数 c 之间,所以在 -c 和 c 之间。这将确保判别器是某个有限 k 的 k-Lipschitz。
然后更新生成器,使 E_{z~p(z)}[f(G(z))] 最大化。这就是整个训练过程。变化是判别器进入目标的方式,然后这个裁剪的步骤。

如果我们真的把它想象成一种简单的例子,我们得到的鉴别器曲线实际上看起来要好得多。这是WGAN论文中的一个例子,基本上和我以前的例子一样。所以蓝色的东西显示了真实的样本,绿色的东西显示假的生成样本,红色曲线是一个常规的鉴别器。你可以看到传统的鉴别器基本上给出了一个点的概率,
🧠 CS 182 课程第20讲第1部分:对抗性示例
在本节课中,我们将要学习深度神经网络中的泛化问题,并探讨一个关键现象:对抗性示例。我们将从理解神经网络为何会犯错开始,分析其背后的原因,并讨论这些错误在现实世界部署中的潜在风险。
🎯 深度网络是否泛化?
一个对任何考虑使用深度神经网络模型的人都至关重要的问题是:深度网络泛化了吗?你能向它们展示与训练中看到的输入不同的输入,并让它们给出正确的答案吗?
在本节课的最后,这似乎是一个非常奇怪的问题。我们讨论过,即使在最开始的时候,对深度神经网络有如此多的兴奋和热情,原因之一是它们在困难和现实的任务上取得了非常高的准确性。例如,ImageNet分类的当前错误率实际上超过了人类的性能。你可能会说这是因为人类不太擅长在非常细粒度的类之间进行分类。然而,无论如何分析,这些模型在很多基准测试任务中都表现得非常出色。
但是,当它们犯错的时候呢?它们犯了什么样的错误?这对我们来说很重要。如果我们要考虑在现实场景中部署深度神经网络,例如,如果你有一辆自动驾驶汽车,其检测行人的感知系统是一个深度神经网络,那么即使该系统的精确度是99.9%,如果在你过马路时它认不出你,那么真正重要的错误就发生了。

🤔 理解神经网络的错误
有时错误确实很有道理。这里有一张蜜蜂的照片,被误认为蜂鸟。为什么它被错误地归类为蜂鸟?你可能会想象,因为图像中有一个蜂鸟喂食器,网络可能有理由得出结论:从蜂鸟喂食器中觅食的动物是蜂鸟。那不是正确的结论,但这是一个你可以想象一个人会犯的错误。如果你不知道蜂鸟是什么,你会看到一堆动物使用蜂鸟喂食器的照片,你可能会得出结论:那些是蜂鸟。
这是一张加州大学伯克利分校应届毕业生的照片。这张图片的正确标签是“学术礼服”。这显然是一个不正确的标签。但如果你不知道加州大学伯克利分校长什么样,你现在看到背景中的尖顶,但不会那么牵强。尤其是如果你不知道学术礼服是什么样子的,你可能会想,也许那是某种宗教服装,或者类似的东西。所以,这肯定是错误的,显然不是一个消息灵通的人会犯的错误,但也不算是疯狂的错误。

我们实际上可以钻得更深一点,并试图理解为什么神经网络会做出它们所做的决定。特别是,我们可以试着把功劳归于图像的特定部分,基本上是试图理解图像的哪些部分负责神经网络进行特定的分类。换句话说,如果图像的某些部分被改变了,这会改变神经网络的决定吗?
在左边你有一张哈士奇的照片,那是一种狗。这张照片被归类为狼。当我们试图检查图像的特定部分负责这种分类时,我们实际上发现在做出这个决定时,网络实际上并没有关注狗本身的外表,而是在关注背景。在这种情况下,背景是雪。在用于训练模型的数据中,背景与狼相关。这实际上很有道理:人们拍下他们的狗在公园的草地上玩耍的照片,而人们会在狼的自然栖息地拍摄狼,哪个更有可能是雪?并不是说所有狼的照片都在雪地上,所有狗的照片都在草地上,但有很强的相关性。由于人们倾向于拍摄的照片的选择偏差,所以这不是相关性。从某种意义上说,狼比雪域气候更普遍,但这里更大的相关性可能是因为狗在这些环境中不太可能被拍照,因为这是一张经典的狗的风景如画的照片,在草地上玩耍。所以这是你数据集中的一种选择偏差。

这个例子最有效地强调的是,当你要求你的模型在训练集上达到最高的精度时,它的任务并不是找到真正的潜在功能性或因果关系。它的任务是寻找模式,以及任何对它有帮助的、能得到正确答案的模式将被利用。这不是一个错误,这实际上是你要求你的神经网络这样做的。注意背景中的雪以帮助对狼和哈士奇进行分类并不是网络的错误,这实际上是你要求它做的。这是正确的解决方案,以最大限度地提高其对该数据集的准确性。

📖 聪明汉斯的故事
这个想法也许最好用故事来说明。这是我从伊恩·古德费罗的谈话中借来的一个故事。这是一个关于自然学习系统的故事,不是机器学习系统,但我认为这说明了依赖数据集和训练测试分裂的一些危险。
这是一个关于一匹名叫聪明汉斯的马的故事。聪明的汉斯是一匹马,它的主人认为它很聪明,所以主人以为马会数数。他们会有这些公开展示,观众中的志愿者会走上前去问马:“聪明的汉斯,什么是二加一?”马会开始拍打蹄子,它会敲一、二、三,然后观众会开始鼓掌,因为马发现一加二等于三。这不是欺诈,至少不是故意的。主人没有以某种方式给马传递纸条,没有欺骗,至少不是在主人的方面。似乎马的数量,他们研究了这个,他们请来了科学家,他们试图弄清楚发生了什么。

马没有正确计数的一种情况,就是当提问者的脸被遮住的时候。所以如果有什么东西阻止马看到问问题的人的脸,他们会说:“聪明的汉斯,什么是一加二?”马会开始敲打,不停地敲打,只要允许。所以一切都进行得很顺利。马利用社交线索:它在看那个人,因为很明显,在问课程问题时,一个通情达理的人会看它的蹄子,一旦它耗尽正确的数字,会抬头并承认它得到了正确的答案。当马停下来的时候,马在训练中看到了这一点。可以这么说,在测试时间,店主不是故意欺骗任何人,更重要的是这匹马也没有试图欺骗任何人。因为马不会数数,就马而言,它因为完成了这个任务而得到了奖励。你可以通过观察另一个人的暗示来非常有效地执行。所以没有人试图欺骗任何人,每个人都在努力做好自己的工作。这是一个道德故事,当训练测试范式出错时。

尽管每个人都有最好的意图,尽管系统实际上按预期工作,我们仍然可能得不到我们想要的。我们想要一匹会数数的马,取而代之的是,我们有一匹马非常擅长阅读社交线索。用同样的方式,我们想要一个神经网络来分类哈士奇和狼,取而代之的是,我们有一个神经网络,这真的很擅长在背景中观察相关性。在某种程度上,我们只是问错了问题,每个人都尽最大努力回答了那个错误的问题。
🧩 高层次原则与分布偏移
所以这是高层次的原则,我可能会稍微强调一下这个原则,因为我真的想确保每个人都很清楚这一点。我们要谈谈很多更时髦的、在这堂课中更多的技术知识,它实际上处理了神经网络可能犯错误的特殊技术原因。但如果你从这次演讲中拿走一件事,只要记住你认为你希望网络解决的任务(比如能够区分狼和狗)与你实际上要求解决的任务之间的区别。记住你的网络会像马一样,它基本上会试图用任何可用的方法来解决问题,不用费心去推断出你到底想做什么。这对一般的计算机来说是正确的,但对于机器学习系统来说更是如此。

好的,所以你可能得不到结果的技术原因之一是分布偏移。我们已经简单地讨论过分配转移,但我只想把这一点说得很具体。麻烦的一个来源是,当测试输入可能来自不同于训练输入的分布时。这可能会以某种微妙和阴险的方式出现。如果训练数据有虚假的相关性,这通常是特别有问题的,就像以前的狗和狼的例子。但它可以在其他设置中出现。
例如,交通标志数据集。你可以想象训练一个神经网络来分类交通标志,使用此数据集。这是一个相当好的数据集,有各种各样的背景、照明条件,甚至一些运动模糊。所以您可能会认为,如果您使用这个数据集,你会得到一个很好的交通标志探测器,然后你可以把这个探测器用在你的自动驾驶汽车上,把真实的交通标志分类。


但当然每个数据集都会有某种相关性或偏差。在这种情况下,你的交通标志是中间裁剪的,它们通常是不被遮挡的。在现实中,你可能会看到一个交通标志,有点偏出,就像停车标志,可能会被一些树叶遮挡。一个人会毫不费力地弄清楚标志是什么,但你的神经网络可能会混淆。不是因为它很难,不是因为它看不到停车标志,但也许是因为它以前从来没有在停车标志前看到过树叶。树叶的存在是不分布的。
在这里区分分类问题是非常重要的。从你可能犯错的地方到分配转移,这其实不是一个难题。这个例子毫不含糊,任何通情达理的人都不会把那个停车标志误认为其他任何东西。
分布转移的一些更现实的例子,会搞乱我们的模型,在现实中医学影像。

不同的医院有不同的机器,机器的身份强烈影响你得到的图像类型,如果是X光或核磁共振之类的。这样就在图像中的特征和医院之间产生了虚假的关联。那张照片是在哪里拍摄的?不同的医院也有不同的阳性率。有些医院的病人比其他医院多。在某些情况下,如果你有一家很好的医院,你有更多更难的案子。所以也许在特定的专业领域,你可能会在一家医院得到比另一家医院更多的积极例子,以非常大的优势。那家医院也会有不同的设备。所以现在你有了一个相关性,标签正或负之间的虚假关联,以及用来拍摄图像的机器的身份。这种虚假的相关性将当然被你的模型利用。所以它将更有可能对来自更难的医院的图像进行分类。
我们在现实中使用的各种数据集,尤其是对自然图像有选择偏差。这意味着图像不仅仅是随机发生的自然图像,它们是由一个人类选择的。比如说,如果这些图片是从照片分享网站上收集的,比如Flickr。也许人类会做出某些决定,他们更有可能在某些情况下拍摄某些东西。选择偏见可能包括像中心作物这样的东西。所以如果图像中有有趣的东西,可能在中间。它可能包括对规范姿势的偏见,例如,动物更有可能从前面拍摄而不是从后面拍摄。你知道的,更多的人在前面拍马,而不是从后面拍马。所以有很多这样的偏见。
照片,人们更有可能接受反馈,这是分配转移的一个巨大来源。当我们讨论模仿学习时,我已经提到了这一点。但基本上,使用机器学习系统会导致用户改变他们的行为,这导致了分布的转移。所以当我们谈到模仿学习时,我给出的例子是一个预测交通的系统。所以如果你是谷歌地图,你有一个机器学习算法来预测交通堵塞是否会发生,你把这些信息给人类司机,他们可能会选择开车去别的地方,这可能会防止交通堵塞的发生。好的,那是件好事。但你也可以走另一条路,你可以说好,不会堵车,每个人都看到那个地区挤满了人,你会遇到交通堵塞,尽管预测不会有。所以反馈是分布转移的主要原因。反馈是当你的学习系统所做的决定影响它将看到的输入时。
当然,还有许多其他原因。一个非常经典的反馈例子是垃圾邮件分类。在这种背景下,分布转移问题已经研究了很长时间。如果你有垃圾邮件检测器,它不存在于真空中。一旦垃圾邮件发送者知道你的垃圾邮件检测器是如何工作的,他们实际上会创造更好的垃圾邮件,这将欺骗您的垃圾邮件检测器无法检测到它。
⚖️ 校准问题
另一个我想在这部分讨论的更微妙的问题是校准。校准的定义是:预测的概率反映了预测事件的实际频率。这可能看起来有点绕,我来解释一下。
我的意思是,举一个例子,让我们回到狼狗的例子。假设你的神经网络看到这个,它说这有30%的可能性是哈士奇,70%的可能性是狼。这些是您的SoftMax的输出。这些数字实际上很好地代表了某事件发生的概率。但是哪个事件?所以当我们说“预测的概率反映了预测事件的实际频率”时,我们指的是什么事件?为了回答这个问题,我们需要问一下,数据是如何生成的。
如果我们回到课程的第二讲,我们说我们的数据生成模型是一个人拍照片。所以 x 是从 x 的某个分布 p(x) 中采样的,这是人类拍摄的照片的分布。然后你得到训练数据的方式是你拍了这些照片,然后你给人类贴上标签。所以思考数据如何生成的过程的一种方法,就是一个人拍了一张照片,照片没有与之关联的标签。但后来另一个人,贴标机,也许就像一个机械术语工人,或者类似的东西看着照片给它贴上标签。这就是训练数据中的标签来自另一个人,说这是哈士奇。你可以把这个过程看作是对标签 y 的采样,从给定 x 的 y 的某个真分布 p(y|x)。p(y|x) 表示一个人在电脑屏幕上看照片的概率,会在那个标签上分配。因为标签就是这样分配的,结果很沙哑。
所以当模型说30%哈士奇,七成是狼,它真正的意思是:十有八九,有人会说这是狼。并不意味着十只狼中就有七只是这样的,或者十分之七的图像看起来像这样是狼。就像那不是那的意思。它实际上归结为生成数据的生成过程。在这种情况下,是一个人在看一张照片,并给它贴上狼或哈士奇的标签。这就是神经网络正在回答的问题:十有八九,有人会说这是狼。
特别是这种预测与生成图像的生成过程联系在一起。所以如果你在图像上有不同的分布,也许你有一个发行版,有人故意出去拍照,温暖气候下的狼和雪地上的哈士奇,那么这种相关性可能会被颠倒,但事实并非如此。所以对于这个生成过程,对于图像上的分布,标签过程,预测是一个人十有八九会对。好的,这是网络试图回答的一个问题。
现在这仍然是一个非常复杂的问题,它是否成功地回答了这个问题?我们稍后再讨论这个问题。但首先让我谈谈一个更明显的场景。假设你得到一个不分布的图像。假设你有一个区分哈士奇和狼的网络,所以这只是一个二进制分类问题。你给它看一个路标。想象一下把这个呈现给一个人,你把一个普通人从街上带走,你告诉他们,你知道你有两个按钮,一个狼按钮和一个哈士奇按钮,按下这两个按钮中的一个。这是一张路标的照片。嗯,他们打算怎么办?他们只有两个选择,他们不能告诉你“我不知道这是什么”,他们不能告诉你它不是哈士奇,他们必须从两个中挑一个。所以你可能会猜测一个合理的、没有偏见的人基本上会在他们之间随机选择。所以你会期望在这种情况下校准的预测,可能看起来五五开。
所以五五开的预测基本上等于说“我不知道是什么”,这意味着一个类和另一个类一样有可能。现在,如果我们有三等,我们没有以上的答案,就像你在多项选择题考试中做的那样,那么也许那个有百分之百的可能性。
所以我们可以问的第一个问题是:这真的会发生吗?如果你给你的神经网络一个非分布的例子,你真的会得到这五十个预测吗?通常不。通常,你不明白这一点,因为训练数据不包含分布外的例子,这就是分配外的定义。该网络从未见过被认为不自信的例子。它只看到了它应该有信心的例子,只见过哈士奇和狼。上面的或五五开的都不在它的词汇表中,以前从没这么说过。因此它不会在训练数据中,网络只需要做出自信的预测。出于这个原因,测试图像,即使它们不在分发范围内,它可能会做出自信的预测。所以它会自信地断言这是一只哈士奇,还是狼。情况并非总是如此,但很多时候都是这样。所以当你给网络一些意想不到的东西时,不分布的东西,它很可能会做出一个自信而不正确的预测,因为它只学会了在训练数据中做出自信的预测。
现在还有一个可能更慷慨的问题,我们可以问哪个是在分布预测中校准的。哈士奇的照片,它真的会产生第三点和第七点吗?它真的会反映这些事件的真实频率吗?现在这是我们认为应该发生的事情,因为这是由训练数据支持的。在现实中,这通常不会发生。这是神经网络等高容量模型的已知问题之一。他们往往得到非常高的精度,这意味着最有可能的类别通常是正确的,但它们不倾向于校准。即使对于分布图像,它们输出的概率,往往不能反映这些事件的真实频率。
我们不知道为什么这是真的,但我们可以推测这是真的的一个原因是,对于如此大容量的神经网络,对他们来说其实很容易为实际上不明确的图像分配过高的概率。基本上,他们非常非常擅长预测标签,太好了,以至于他们比他们应该有的更自信。这意味着即使是熟悉的图像,概率最高的类可能是正确的,但是分配给该类的概率可能不能反映实际频率,人类贴标者会用它来分配标签,它可能会更大。
然而,有很多技术确实在一定程度上缓解了这个问题。所以如果你上了一门关于深度学习的更高级的课,你可能会学到很多方法,可以提高深度神经网络集成的校准。顺便说一句,集成是一种我们已经学过的技术,这样问题就可以解决了。

📝 总结

在本节课中,我们一起学习了深度神经网络泛化中的核心挑战。我们探讨了神经网络可能犯错的多种原因,包括利用数据中的虚假相关性(如背景特征)、面临分布偏移(如不同医院设备导致的图像差异)、以及预测校准不佳的问题。我们通过“聪明汉斯”的故事强调了系统可能完美执行错误任务的风险。理解这些原理对于安全、可靠地部署机器学习模型至关重要。在接下来的课程中,我们将深入探讨更具体的对抗性攻击技术。
课程 P62:CS 182 第 20 讲 - 第 2 部分:对抗性示例 🎯
在本节课中,我们将要学习一种特殊现象——对抗性示例。这是一种神经网络会犯的错误,它揭示了模型在特定扰动下的脆弱性。我们将探讨什么是对抗性示例、它们为何发生、以及它们对机器学习安全性和模型泛化能力的启示。
什么是对抗性示例?🤔

上一节我们讨论了图像的自然扰动和分布偏移。本节中,我们来看看一种非自然的扰动——对抗性示例。
对抗性示例是一个特别生动的例子,说明了学习到的模型可能无法正确泛化。这是一个典型的对抗性示例:左边有一张原始照片,被分类器以 57.7% 的置信度归类为熊猫。然后,我们将一个特殊的模式乘以一个很小的幅度(例如 0.007),并将其添加到熊猫图片上。我们得到了一张新的图片,它在视觉上与原图几乎无法区分,但却被分类器以 99.3% 的置信度归类为长臂猿。

这个特殊的模式并非随机噪声,而是一个经过精心设计、专门用于欺骗模型的模式,因此被称为“对抗性”示例。这种扰动通常非常微小,以至于人类视觉难以察觉,但对于神经网络来说,它足以彻底改变分类结果。

我们为何需要关注对抗性示例?⚠️

对抗性示例不仅仅是一种理论上的好奇心,它具有重要的实际安全影响。
以下是几个关键原因:
- 直接的安全攻击:存在许多现实动机去欺骗分类器。例如,欺诈者可能希望修改交易记录以逃避信用卡欺诈检测;有人可能希望修改受版权保护的材料以上传至网站而不被识别。
- 模型泛化的启示:如果神经网络会犯这样的错误,这可能暗示了这些网络在泛化方式上存在一些根本性的问题。理解对抗性示例有助于我们构建更鲁棒、泛化能力更好的神经网络。
对抗性示例的关键事实 📝

在深入细节之前,让我们先了解一些关于对抗性示例的全貌和基本事实。
以下是关于对抗性示例的几个核心事实:
- 普遍性:对抗性示例并不局限于将特定类别(如熊猫)变为另一种类别(如长臂猿)。通过精心设计,几乎可以将任何输入转换为任何目标类别。
- 防御困难:截至当前(2021年4月),尚无已知方法能为对抗性示例提供完全可靠(防弹)的防御。虽然存在许多降低其有效性的方法,但无法彻底解决。
- 可迁移性:对抗性示例可以在不同模型间迁移。为一个网络(如AlexNet)创建的对抗性示例,通常也能欺骗另一个网络(如ResNet)。
- 物理世界有效性:对抗性示例不仅存在于数字像素层面。通过精心设计的物理扰动(如标志上的污迹或贴纸),可以使真实物体的照片被错误分类。
- 非神经网络特有:对抗性示例并非深度神经网络独有的问题。几乎所有的学习模型(包括线性模型)都容易受到对抗性攻击。
对抗性示例的成因假说 🔍
一个常见的直觉是,对抗性示例可能是模型过拟合的症状。过拟合的模型决策边界可能非常复杂且贴近数据点,导致微小的扰动就能跨越边界。

然而,证据并不支持这一假说:
- 可迁移性矛盾:如果是对过拟合,不同模型应有高方差,其对抗性示例应各不相同。但实际观察到的可迁移性表明方差很低。
- 影响低容量模型:线性模型等低容量模型同样存在对抗性示例,而过拟合通常与高容量模型相关。
- 决策边界可视化:对决策边界的可视化研究通常显示其局部是线性的,而非极度复杂和非线性。
另一种假说,即线性模型假说,认为许多模型(包括深度网络)在输入空间的大部分区域表现得局部线性。在高维空间中,即使沿着许多不相关维度进行微小的线性移动,累积起来也可能导致输出发生巨大变化。

支持线性假说的证据包括:
- 模型结构:ReLU等激活函数使网络是分段线性的。即使使用Sigmoid,模型也通常在中间区域近似线性,且易于训练。
- 外推行为:实验表明,当输入沿着某些方向远离数据流形时,模型的logits(softmax前的值)会呈线性变化,而非反映不确定性的饱和。
- 决策边界可视化:对图像空间进行二维切片可视化时,决策边界通常呈现为直线,这与线性假说一致。
对抗性示例与人类感知 👁️



对抗性示例并非机器独有。人类感知系统也存在类似现象,即视错觉。例如,一些由正方形组成的同心圆看起来像螺旋。

更有研究表明,存在能同时欺骗计算机视觉系统和时间受限的人类观察者的对抗性示例。当人类只有极短时间(如不到100毫秒)进行判断时,他们也可能被这些精心修改的图像所误导。
对抗性示例与泛化的关系 🧠

线性假说不仅解释了对抗性示例,也揭示了深度网络泛化的某些方面。当模型被训练来区分猫和狗时,它学习的是数据集中有效的模式,并沿着这些模式进行线性外推。对抗性方向可能恰恰是模型在训练数据上获得高准确率所依赖的“特征”。
因此,对抗性示例可能不完全是模型的“缺陷”,而是模型高度优化于特定任务(在训练分布上取得高精度)的副产品。这与控制理论中的观察类似:最优的控制器往往鲁棒性较差。

总结 📚
本节课中,我们一起学习了对抗性示例的核心概念。我们了解到,神经网络虽然在同分布测试集上泛化良好,但它们可能通过学习数据中的特定模式(成为“聪明的汉斯马”)来实现这一点。当输入分布发生微小变化(尤其是沿着模型敏感的线性方向)时,这些模式可能失效,导致错误的分类。

对抗性示例很可能不是过拟合的症状,而是由于模型在输入空间中的过度线性行为或简单外推所致。它们影响着从线性模型到深度网络的各种学习模型,甚至人类感知。对抗性示例可以在物理世界中构造,并且目前难以彻底防御。理解这一现象对于构建安全、可靠的机器学习系统至关重要。


在接下来的课程中,我们将探讨如何具体生成对抗性示例,以及有哪些方法可以尝试增强模型的鲁棒性。
📚 CS 182 课程笔记 - 第20讲 第3部分:对抗样本
在本节课中,我们将学习如何构建对抗样本。我们将从形式化定义开始,介绍经典的快速梯度符号法,探讨对抗样本在不同模型间的可转移性,并简要讨论如何防御此类攻击。
🎯 对抗样本的形式化定义
为了创建对抗样本,我们需要一个形式化的定义。我们不只是想随意修改图像,而是需要以某种方式限制我们的修改。
我们可以引入一个关系 R(x, x'),它描述了原始图像 x 与修改后的图像 x' 有多接近。R 有许多不同的选择,一个非常简单的选择是 x 和 x' 之间的无穷范数。
无穷范数正式定义为 x 和 x' 中差异最大的两个元素之间的差。如果 x 和 x' 是向量,我们取每个维度的差值,然后在差值的绝对值的维度上取一个最大值。直观地说,这意味着每个像素最多只能改变 ε。
因此,对抗攻击可以定义为一个约束优化问题。我们的目标是使模型 θ 的损失函数 L 最大化,同时满足 R(x, x') ≤ ε 的约束。如果 R 是无穷范数,就意味着在最大化损失的同时,每个像素最多只能改变 ε。
损失的一个例子可能是负对数似然。最大化负对数似然意味着将正确标签的可能性降至最低。当然,我们的网络训练的目标是相反的,即最小化损失。
我们也可以基于非常相似的原则,为防御对抗攻击构造一个定义。防御被表述为一个学习目标:寻找 θ 以最小化损失函数。但这里不是最小化数据集 D 中 (x, y) 元组上的损失,而是最小化在最坏扰动 x' 下的损失。
注意,这里的最大值在最小值内部。这模拟了一个场景:我们首先选择模型,然后对手可以为我们的模型选择最糟糕的图像。如果我们比较防御方程和标准的经验风险最小化问题,唯一的区别就是这个最大值。
一个警告是,虽然形式化定义对数学家很有帮助,对证明定理和设计实用的攻击算法也很有帮助,但它可能隐藏一些重要的现实世界考量。真正的攻击者不在乎我们的定义,他们只在乎攻击是否奏效。因此,简单地使网络对无穷范数扰动鲁棒,并不能让我们对真正的攻击免疫,但这仍然是一个很好的起点。
⚡ 快速梯度符号法
构建对抗样本最著名、最经典的方法之一是快速梯度符号法。这是对无穷范数关系的一种非常简单的近似。
我们的攻击目标是找到 x',在约束 R(x, x') ≤ ε 下,最大化损失 L(θ, x', y)。我们要做的是做一个一阶假设:假设局部损失函数(作为 x' 的函数)近似于 x 处的损失加上 x' - x 与损失函数关于 x 的梯度的点积。这基本上是一个一阶泰勒展开。
虽然神经网络不是线性的,一阶泰勒展开可能不精确,但我们之前看到过神经网络在局部表现出线性行为。因此,这种近似在实践中可能效果很好。

要实施这次攻击,我们用这个一阶泰勒展开近似代替损失函数。现在我们的目标是找到 x',在约束 ||x - x'||_∞ ≤ ε 下,最大化 x' - x 与损失函数关于 x 的梯度的点积。
无穷范数意味着我们可以在每个维度上独立地改变 x。最优解是将 x 的每一维沿着该维度梯度符号的方向移动 ε。这可以表示为一个方程:x' = x + ε * sign(∇_x L(θ, x, y))。
这就是为什么它被称为快速梯度符号法:我们取损失关于 x 的梯度,取其符号,然后沿着那个方向快速移动。它很快,因为计算成本仅相当于一次梯度计算。虽然这不是世界上最强的攻击,但对于不鲁棒的简单模型,它可能相当强大。
🔄 更通用的攻击方法

现在我们可以写下这次攻击的更一般的表述。我们可以不处理约束优化问题,而是将其写成一个带有拉格朗日乘数的拉格朗日函数。
这样,我们就把约束变成了一种惩罚,它会惩罚优化器偏离 x 太远。然后我们可以对任何可微关系 R 运行常规的梯度上升来优化,以获得最佳攻击。我们可以启发式地选择拉格朗日乘数,或者使用一些原则性的方法。
在文献中,这种攻击通常不是用 x' 表示,而是用扰动 δ = x' - x 来表示。优化目标变为:最大化 x + δ 处的损失,减去 λ 乘以 δ 的某种范数。如果我们使用无穷范数,就会得到类似于上一张幻灯片上的攻击。但我们可以使用其他类型的范数,甚至是一些感知损失,这实际上量化了人类区分修改的难易程度。一般来说,构建这种类型的攻击有很大的灵活性,如果做得好,它们可能很难被打败。
🌐 对抗样本的可转移性
如果我们为一种类型的模型构造一个对抗样本,它也会是不同类型模型的对抗样本吗?通常,可转移性是存在的。

研究表明,可转移性并不完美,但也绝对不是微不足道的。对抗样本似乎更容易从较强的模型转移到较弱的模型。例如,为深度神经网络训练的对抗样本,可能会成功欺骗逻辑回归或支持向量机。而在不同类型的神经网络之间,转移率也相当高。
这意味着,我们通常不需要直接访问目标神经网络的梯度来攻击它。我们可以用另一个神经网络来构建对抗样本。这就是所谓的零次黑盒攻击:我们可以训练自己的模型并攻击它,然后将生成的对抗样本部署到我们只能进行黑盒查询的目标模型上。

更进一步,如果我们对一个模型有有限的访问权限,我们可以做得更好。例如,可以使用有限差分法来估计梯度。通过查询模型对每个像素进行微小扰动后的损失,我们可以估计出梯度的方向。虽然这需要的查询次数等于像素数,但也有一些技巧可以减少所需的查询数量。或者,我们可以查询一些图像来获得标签,构建自己的小训练集,训练一个替代模型,然后攻击这个替代模型。
🛡️ 防御对抗攻击
让我们简单讨论一下防御对抗攻击。我们一开始提到,可以将防御正式定义为优化鲁棒损失。那么,我们怎样才能真正做好这件事呢?
文献中有许多不同的鲁棒方法。一个非常简单的方案叫做对抗训练。对抗训练是对小批量随机梯度下降的一个简单修改,它基本上实现了这种鲁棒损失。
它是这样工作的:对于小批量中的每个样本,我们使用快速梯度符号法生成一个对抗样本。然后,我们在损失函数上采取SGD步骤,但是用对抗样本来代替原始数据。在实践中,我们通常将对抗样本的损失和原始样本的损失结合起来优化,这往往会在一定程度上提高性能。
这个过程被称为对抗训练,它可以显著提高模型的鲁棒性。虽然它不能抵御所有的攻击,而且通常不是免费的。它会增加你对对抗性攻击的鲁棒性(通常表现为更低的欺骗率),但与原始网络相比,这通常会降低模型在测试集上的总体准确性。这很有道理,因为为了变得更鲁棒,模型通常会变得不那么精确。
📝 总结
在本节课中,我们一起学习了对抗样本的构建与防御。

- 我们介绍了快速梯度符号法,这是一种在无穷范数约束下(每个像素最多改变少量值)构建对抗样本的简单便捷方法。
- 我们描述了如何使用多次梯度下降步骤来进行更好的攻击,通过优化受到约束或惩罚的图像。
- 我们探讨了黑盒攻击,这些攻击可以在不访问模型梯度的情况下执行,例如通过构建自己的模型或集成模型进行攻击,然后将对抗样本零次转移到目标模型中,或者通过有限差分等方法估计梯度。
- 最后,我们简要讨论了像对抗训练这样的防御方法,这是一个在文献中被大量研究的话题。
课程 P64:CS 182 - 第21讲 - 第1部分 - 元学习 🧠
在本节课中,我们将要学习一种名为“元学习”的方法。元学习旨在让深度学习技术能够利用非常、非常小的数据集进行有效学习。我们将探讨其核心思想、如何将其形式化为一个监督学习问题,并介绍几种主要的元学习方法。
什么是元学习?🤔
从这门课的第一节课开始,我们就了解到,当你有大量数据时,深度学习非常有效。使用超大型模型和深度学习技术可以取得很好的效果。
但是,如果你只有一个新任务的一点点数据呢?此时应用传统的深度学习方法实际上并不那么简单。
那么我们能做什么呢?一个想法是:如果我们有来自其他任务的大量数据,这些任务与当前任务相似,也许我们可以利用它们来学习新任务。这不仅仅是学习,而是“学习如何学习”。然后,利用对如何有效学习的理解,即使从少量数据中,我们也可以更有效地学习新任务。
所以,如果这些先前的任务在结构上与新任务提出的挑战相似(例如,它们都是图像识别任务),那么也许我们可以利用这些先前的任务来了解学习过程本身。这可以让我们在这项新任务中更有效率,即使只有少量数据可用。
因此,元学习基本上是指“学习如何学习”的过程。

在实践中,元学习与多任务学习密切相关。我们在这节课中要讨论的设置是:你拥有大量先前任务的情况。也许这些先前的任务本身都只有少量的数据,但是任务数量很大。然后,你有一些在结构上相关的新任务(稍后我们将定义“结构相关”的含义)。
有很多方法来制定元学习。你可以将其表述为学习优化器、在读取先前数据的RNN中学习、学习表征等等。我们将主要关注元学习问题的一个子集,称为“少样本学习”问题。
少样本学习是指学习一项新任务时,只使用少量称为“样本”的例子。“几个样本”意味着几个例子。

为什么需要元学习?📈
深度学习效果很好,但它需要非常大的数据集。在许多情况下,我们可能只有少量的数据可用。例如,对于某些特定的医学成像诊断任务,我们可能只有一点点数据,但是我们可能有很多类似类型的数据用于其他任务(就像其他类型的图像识别任务一样)。
那么元学习对此有什么帮助呢?元学习使用大量的先验任务进行“元训练”,从而训练出一个只需几个例子就能快速学习新任务的模型。这意味着你只需要为新任务收集少量的标签数据,然后用你的元学习模型快速适应这个任务,然后解决它。
将元学习形式化为监督学习 🎯
这可能一开始看起来有点抽象和神秘,但有一个非常强大的技术可以让我们掌握元学习:简单地将元学习问题重新定义为另一个层次的监督学习问题。
这个框架展示了我们如何将少样本图像识别任务制定为一个元学习问题。少样本图像识别意味着你只能得到每个类的少量例子,然后你需要很快地使用这些少量的例子来学习识别该类的新对象。
这个特殊的插图实际上是在说明“单样本”案例。在这种情况下,我们只能得到每个类的一个示例(即非常、非常小的训练集)。你的任务是区分鸟类、蘑菇、狗等。假设你是一家制造图像分类器的公司,你的顾客会跟你说:“我想要区分鸟、蘑菇、狗、人和钢琴,但我只有一个例子,你能给我一个分类器吗?”
将元学习或少样本学习框架化为另一个监督学习任务,意味着把事情提升一个层次。我们要说的是:我们有一个“元训练集”,它本身由许多不同的数据集组成。在这种情况下,每一个都是一次分类任务。

在元训练集中,你有很多、很多任务(也许成千上万的任务)。每一个任务都是由一个小训练集(在这种情况下是一个单样本训练集,所以只有五张照片)和一个小测试集(里面有一些属于这些类的图像)决定的。
在实践中,也许这些任务会更连贯一点。我们通过创建这个数据集所做的,就是将少样本分类的问题,转化为读取一个单样本训练集并对新的测试图像进行预测的问题。
如果我们能训练一个模型来处理这个,那么在“元测试”时间,模型将面对一组新的五个图像(一套新的单样本训练器),里面包含我们从未见过的类别。所以,在元测试时间(这是客户真正想要将其适应新任务的时候),他们可能会展示一个冰淇淋的图像、一张狗的照片、一个线的图像、一个虫子的图像和一个碗的图像。也许这些类别中的一些以前见过,但通常你会假设这些是以前从未见过的新类。
因此,模型学会了快速计算出如何将不同的“五人组”分类。现在它展示了一个新的“五人小组”,他们的类别是以前从未见过的,但是任务的结构是相似的(它们都是图像)。这个想法有意义的正式假设是:我们需要元训练集从某个“任务分布”中取样。这不仅仅是图像的分布,它实际上是对任务的分配。元测试任务需要来自相同的分布。这就像将监督学习的假设,向上移动了一个层次。
如果这看起来有点抽象,别担心。我们将通过具体的模型来使其变得更明显。
元学习的抽象模型 🔄
在监督学习中,我们学习一些从输入 x 映射到输出 y 的函数 f。你的输入可能是图像,输出 y 可能是一个标签。
在元学习中,我们学习一个从训练集(称之为 D_train)和测试图像 x 映射到对应标签 y 的函数 f。所以,在我们学习从 x 到 y 的映射之前,现在我们正在学习从训练集 D_train 和 x 到 y 的映射。对于不同的任务,我们现在给这个 f 函数一个不同的 D_train。
当然,在现实中,我们可能想要分解这个函数。我们可能首先读取 D_train 并产生一些足够的统计数据或参数,然后我们可以用它来分类许多不同的 x。但在定义问题方面,我们可以很好地像这样定义它。
如果我们说的是普通的、我们在这节课中已经讨论过的算法(像梯度下降),那么 f 就是一个对 D_train 运行梯度下降的函数,收敛于它得到的参数,然后用它们来分类 x。但在元学习中,用许多其他方式表示 f 会更有帮助。

如何表示元学习函数 f?🧩

花点时间想想如何表示函数 f。如果你需要学习一个函数,输入是训练集 D_train 和测试点 x,输出是标签 y,你如何表示这个函数?

D_train 是一个训练集,这是一组图像和它们的标签。为了设置 f,我们需要回答几个问题。第一个问题是:f 实际上是如何读取训练集 D_train 的?f 在所有 D_train 中实际上是如何摄入的?
这样做有很多选择。RNNs 可以很好地工作,Transformers 也可以很好地工作,但也有其他选择。为了让事情变得简单,假设我们要使用 RNN(就像一个 LSTM)。这样我们就可以把我们的训练集 D_train 当作一个序列:(x1, y1), (x2, y2), (x3, y3), ...。我们可以按照某个顺序创建序列,一次读取一个 (x, y) 元组。然后我们可以在最后有一个小头部来读取测试图像,并对测试图像进行预测。
这是建立元学习方法的一个非常合理的方法。然后你可以训练这个小 RNN 或 LSTM。在元训练集中的每个任务中(例如,在图像中,因为所有的训练集都有五个图像,这是一个五向分类任务),你会有长度为五的序列。然后,你会有一个单独的小网络来接收 RNN 的最后一个隐藏状态和测试输入(测试图像)。

理解元学习过程 🧠

当我们向模型展示一项新任务时,它实际上学到了什么?元学习过程训练整个 LSTM 的参数,这是一种外部过程。当你给它一个新任务时(元测试后的新训练集),这里的图像将由五张图片和它们的标签组成。
以下是我们如何理解这种通用学习。标准的非元学习可以看作是采取一些损失函数和一些训练集 D_train,并恢复最佳模型参数 θ* 的过程。我们可以称之为函数 F_learn(D_train)。所以 F_learn 吸收 D_train,它产生 θ*。F_learn 的例子包括梯度下降、带动量的 Adam、SGD 等。
通用元学习可以被看作是承担多项任务(在这种情况下,n 项任务),并最小化这 n 个任务在测试集上损失函数的和。对于每一项任务 i,你有一套训练集 D_train_i 和一套测试集 D_test_i。你有 N 个这样的对。
你的学习函数 f_θ 读入 D_train_i,它产生了某种参数向量 φ_i(它不必对应于神经网络的权重,它只是一些数的向量)。φ_i 可以用来分类从 D_test_i 中拍摄的同一任务的其他图像。所以,我们从 D_test_i 取一个 x,我们使用从将 f_θ 应用于 D_train_i 中得到的 φ_i 来预测相应的 y,并最小化损失。所以,它在说:最小化测试集上的损失,在使用训练集获得 φ 之后。梯度当然会回传到 f_θ。
这就是元学习如何工作的抽象模型。幻灯片顶部的这个 RNN,非常适合这个抽象模型。为了弄清楚如何适应这个抽象模型,你得回答几个问题:什么是 f_θ?什么是 φ?
对于我们简单的 RNN 元学习器来说,f_θ 基本上是 RNN(θ 表示 RNN 的参数)。它在 D_train 中很好地阅读后产生了什么?它会产生一些 RNN 隐藏状态(即最后一个 LSTM 状态)。在你读完整个训练集后,最后一个 RNN 隐藏状态被一个小分类器网络使用,对测试点进行分类。所以我们称 φ 由 RNN 隐藏状态以及最后这个小分类器的参数组成。因为那个接收隐藏状态 h_i 和 x 并产生 y 的小网络也有一些参数,这些参数是元训练的一部分。
f_θ(D_train_i) 产生 φ_i。关于 D_train 的大部分知识就编码在最后一个 RNN 隐藏状态下,还有分类器的参数(这些参数不是由 RNN 产生的,它们是固定的,和其他东西一样受过元训练)。
从这种心理模型出发,你可以想象可以用很多其他的方法来制定元学习。你可以设计许多其他的架构,定义许多其他类型的 φ。当我在这节课剩下的时间里谈论不同的元学习算法时,请思考一下它们是如何映射到这个模板上的:想想什么是 f_θ(读取 D_train 的部分),什么是 φ(参数和数的向量,这足以对新的测试点进行分类)。对于我将讨论的每种方法,它将有不同的答案。
为了避免任何混乱,我在这里描述的一切都是以不同的方式看待 RNN。实际的方法非常简单:实际的方法基本上是处理每一个 (D_train, x, y) 元组作为训练点,将 D_train 和 x 作为 RNN 的输入,并教它产生相应的 y。就 RNN 而言,这只是一个监督学习问题。但这样看,我们也可以把它看作是一个元学习问题。
所以,如果这对你来说有点困惑,只要记住在最后,你实际这样做的机械方式是:你只需设置一个常规的序列标注问题,其中序列由 D_train 组成,然后是测试点 x,所需的输出只是与 x 一起的标签 y((x, y) 元组来自测试集)。
主要的元学习方法 📚
有哪些实际的元学习方法我们可以在实践中很好地使用?我们可以把它们分为三类。
以下是三类主要的元学习方法:
-
黑盒元学习算法:这些基本上是类似于我之前描述的 RNN 技术的方法。它们基本上是使用某种神经网络(通常是序列模型)来读取整个训练集和测试点,然后输出测试点标签。它们只是接受监督学习的训练,将元学习问题重铸为监督学习问题。它们通常在使用的特定架构上有所不同(例如,RNN、LSTM、带外部记忆的模型、Transformer 等)。训练它们的实际算法正是我到目前为止讨论的。大多数元学习的巧妙之处实际上在于数据处理。
-
非参数的元学习方法:不要让名字欺骗你,实际的元学习是参数化的,但“适应”过程不是参数化的。所以你可以把这些方法看作是学习如何做“最近邻”查询。它们解决新任务的方式是使用某种最近邻查询,但它们会在元学习阶段学习表征,这将使那些最近邻查询工作得很好。这类方法包括匹配网络和原型网络,我们将在第二部分讨论。
-
基于梯度的元学习算法:这些一开始看起来和其他的很不一样,但在许多方面,它们实际上有很多相似之处。基于梯度的元学习方法的原理是:你适应新任务的方式,只是通过梯度下降进行微调。所以,你实际上会通过在这项任务上运行梯度下降来适应新的任务。但是在元训练中,你实际上会训练你的模型,使得梯度下降适应效果很好。起初,这似乎是一个完全不同的原则。
总结 📝

在本节课中,我们一起学习了元学习的基本概念。我们了解到,元学习是“学习如何学习”的过程,旨在利用先前的任务经验来快速适应只有少量数据的新任务。我们将元学习问题形式化为一个更高层次的监督学习问题,并介绍了表示元学习函数 f 的一种方法(使用 RNN)。最后,我们概述了三种主要的元学习方法:黑盒方法、非参数方法和基于梯度的方法,为后续深入探讨具体算法奠定了基础。
课程 P65:CS 182 讲座 21 - 第 2 部分 - 元学习 🧠
在本节课中,我们将要学习元学习的两种主要方法:非参数元学习和基于梯度的元学习。我们将探讨它们的基本原理、核心公式以及各自的优缺点。
概述 📋
元学习的目标是让模型学会如何学习。在讲座的这一部分,我们将深入探讨两种实现元学习的具体方法。首先,我们将介绍非参数元学习方法,它通过比较特征空间中的嵌入来进行分类。接着,我们将转向基于梯度的元学习方法,它通过优化模型使其能够通过少量梯度步骤快速适应新任务。
非参数元学习方法 🔍
上一节我们介绍了元学习的基本概念,本节中我们来看看非参数元学习方法。这类方法的核心思想是学习一个特征嵌入函数,使得在新任务上通过简单的最近邻比较就能获得良好的分类效果。
基本思想
非参数元学习方法的基本流程如下:我们进行少样本训练。这里可以将其视为一个一次性训练集,例如有五个图像,每个对应不同的标签。通常每个类别可能有多个示例,但这里我们假设每个类别只有一个示例。
每个图像都有一个标签,例如 L1, L2, L3, L4, L5。我们不知道这些标签的具体含义,它们只是整数,在不同的任务中代表不同的东西。
然后我们有一个测试图像,其标签未知。我们需要弄清楚训练图像中哪一个与测试图像具有相同的标签。这是我们在适应过程中要解决的基本问题。
我们将使用一个神经网络来描述这些图像中的每一个。该网络将每个图像嵌入到一个特征空间中。我们称这个网络为 φ。我们还将嵌入测试图像。稍后,我们会讨论如何将测试图像嵌入到与训练图像不同的特征空间中。在训练图像中,我们实际上会切换到使用 f 和 g,其中 f 嵌入测试图像,g 嵌入训练图像。但目前,我们可以假设它们都在同一个空间里,我们称这个空间为特征空间。
接下来,我们要做的是比较 φ(x_test) 与每个训练图像的嵌入 φ(x_train)。直观上,我们要做的是找到最近的嵌入。在这个特征空间里,我们的测试图像嵌入最接近哪个训练图像的嵌入?当然,仅从像素层面进行最近邻查询可能没有意义,但我们可以元学习一个特征空间,使得这种比较变得有意义。
假设我们发现 φ(x_3) 是 φ(x_test) 的最近邻。那么我们分配标签的方式就是采用最接近图像的标签,在这个例子中就是 x_3 的标签。
为什么这能起作用?
为什么在这个特征空间中的最近邻比较实际上能给出正确答案?一般来说,如果特征空间是任意的,这不一定是一个有效的分类器。那么为什么最近邻在这样的方法中能有正确的类别呢?因为这就是元训练试图做到的。
所有这些工作的关键是能够元训练嵌入函数 φ,使得在元训练集的任务上,这种特定的最近邻查询能产生正确答案。那么希望它在你的元测试任务中也能产生正确答案。这是这种少样本元学习的基本思想。你需要确保你的元训练过程与元测试时要做的事情一致。如果在元测试时你将执行最近邻查询,那么只要你训练的网络 φ 在执行最近邻查询时是准确的,那么你在元测试时的最近邻查询就会给你正确的答案。
这意味着元训练过程可以这样形式化。这正是我之前的等式,只是之前我有 φ = f_θ,现在我只是把它代入优化中,使其成为一个无约束问题。
所以 θ* 表示嵌入函数的参数,它最小化损失。损失由 f_θ 应用于对应的训练集 D_train 得到,尽管我们将使用一种“软”最近邻方法。

损失函数公式化
如果我们使用负对数似然损失(这是我们通常用于分类的损失),这可以重写为所有任务损失之和的负数。
以下是损失函数的公式:
L(θ) = - Σ_{i=1}^{N} Σ_{j=1}^{M} log p_θ(y_j^{test} | x_j^{test}, D_train^{i})
其中:
N是元训练任务的数量。M是每个任务中测试点的数量。p_θ(y_j^{test} | x_j^{test}, D_train^{i})是在给定测试图像x_j^{test}和整个训练集D_train^{i}的条件下,测试标签y_j^{test}的概率。
你最大化每个测试点标签的对数概率,给定每个测试点的图像以及该任务的整个训练集。你把所有测试点和所有任务加起来,并实际定义这个对数概率。
定义概率 p
我们要定义这个概率量 p_nearest,即训练点 x_k^{train} 是测试点 x_j^{test} 最近邻的概率。
p_nearest 将与 exp(φ(x_k^{train})^T · φ(x_j^{test})) 成正比。它不一定是转置点积,也可以是欧几里得距离或余弦距离。关键是它是 φ(x_k^{train}) 和 φ(x_j^{test}) 相似度的度量。
因此,特征表示最接近 x_j^{test} 特征表示的训练点 x_k^{train} 将具有最大的点积,因此指数内的值最大。因为这是训练点的分布,我们通过除以所有训练点的指数之和来将其归一化。
所以你可以把它看作是应用于 φ(x_k^{train})^T · φ(x_j^{test}) 的 softmax 函数。如果这些点积非常大,那么 softmax 最终会接近硬最大值,这意味着在最近的邻居上会有一个权重为 1,其他一切都为零。但对于较小的值,它将被平滑处理。
分配标签概率
现在,你可以考虑为特定标签 y 定义概率 p(y_j^{test} = y),作为所有标记为 y 的训练点是最近邻的概率之和。
换句话说,如果你想知道标签是 L 的概率是多少,我们总结所有以 L 为标签的训练点,把它们成为最近邻的概率加起来。所以,如果标签为 L 的点(例如第三个点)最有可能是最近的邻居,这意味着它的标签是正确标签的概率也是最高的。

另一种思考方式是,对于每个标签,你求和所有带有该标签的点是最近邻的概率。然后,具有最高概率和的标签,就是你将分配给该测试点的标签。
简单回顾
我们要计算 p_nearest,即每个训练点是我们测试点最近邻的概率。然后,我们会说,标签的概率是每个标签为 y 的训练点是最近邻的概率之和。所以,在我们的软极限变成硬极限的情况下,那么它的标签也最有可能是正确的标签之一。
现在,我们为 log p_θ(y_j^{test} | x_j^{test}, D_train) 设计了一个公式,这给出了我们的训练目标。因为这里的一切都是可微的(我们用的是软最近邻),我们可以通过它反向传播梯度。我们实际上可以优化所有这些关于我们的嵌入函数 φ 的参数 θ。

所以,参数 θ 唯一影响的就是嵌入。这是非参数元学习的一个非常简单的公式。现在,实践中使用的实际方法通常更复杂一点。这是我们能设计的最简单的基本版本。

实际方法:匹配网络 🤝
现在我们将讨论一些基于这个想法的实际方法。我们要讨论的第一个是为少样本学习设计的匹配网络。
匹配网络与我在这张幻灯片上的基本原型非常相似,但是经过几个修改,这些修改与如何计算 p_nearest 概率有关。

以下是匹配网络的两个关键修改:
- 不同的嵌入函数:在匹配网络中,不是使用一个函数 φ 来计算
φ(x_k^{train})^T · φ(x_j^{test}),而是使用两个不同的函数 g 和 f。所以,不是一个函数 φ,而是有两个函数 g 和 f,这两个函数都是元训练的。因此,我们用不同的网络来嵌入x_train和x_test:g 嵌入x_train,f 嵌入x_test。 - 条件化嵌入:匹配网络的另一个区别是 g 和 f 都是有条件的。它们不仅以
x_train和x_test为条件,还以整个训练集为条件。这样做的原因是因为你可能想做类似于消歧的过程。你可能想知道你表示左边狗的方式可能会改变,取决于所有其他图像是狗还是其他动物。所以,如果整个训练集只是狗的照片(你在试图把不同品种的狗分类),编码将指示品种的属性,而不是指示是否是狗的属性。但是,如果你在嵌入一只狗的图像,而其他训练图像是猫、长颈鹿和河马,那么你可以选择对区分不同种类动物有用的特征,而不是不同品种的狗。所以这就是为什么 g 和 f 都依赖于整个训练集。
这需要我们为 g 和 f 选择一个架构,该架构可以嵌入相应的 x_train_k 和 x_test_j,同时也考虑到整个训练集。

论文中对 g 所做的特别选择是使用双向 LSTM。它的工作原理如下:你拿着你的训练集,运行一个前向 LSTM(按某种顺序),你还有另一个 LSTM,你向后运行。然后,g 的输出由前向 LSTM 的隐藏状态、后向 LSTM 的隐藏状态以及嵌入的图像本身相加形成。因为前向和后向的 LSTM 包含了关于所有其他点的信息,所以你的嵌入可以是上下文相关的。

对于 f 函数,原则上可以以同样的方式工作,但在这篇论文中,他们为它选择了一种不同的、更复杂的表示(注意力 LSTM)。高层的想法是,他们做的事情基本上和标准的非参数公式一样,但做了两个修改:分别使用不同的函数 g 和 f 嵌入训练点和测试点,并且每个嵌入函数本身也取决于整个训练集。这是最早的论文之一,真正证明了这种非参数公式可以非常有效。
实际方法:原型网络 ⭐
大多数人实际上并不使用匹配网络。一种更广泛使用的方法是所谓的原型网络。这实际上简化了公式。
原型网络可以被视为匹配网络,只需两个简单的修改:
- 为每个类构建原型:原型网络不做软最近邻比较。他们所做的是为每个类构建一个原型向量。这个想法是,
p(y | x_test)现在由原型向量 c 上的 softmax 给出。所以你仍然用嵌入函数 f 嵌入x_test,但不是用它的点积与每个g(x_train_k),而是用它的点积与 c_y,其中 c_y 只是所有属于类别y的x_train_k嵌入的平均值。因此,原型网络将嵌入所有训练点,将它们的嵌入按类别平均在一起,然后点积这些平均值。你可以把它看作是一个逻辑回归分类器,其中分类器权重是通过将训练点的嵌入平均在一起产生的。 - 简化架构:在原型网络中,第二个修改是摆脱所有复杂的架构(如双向 LSTM 或注意力机制)。g 和 f 分别只依赖于
x_train和x_test。它们不再以整个训练集为条件。这使得方法简单了许多。所以f(x_test)是一个嵌入,g(x_train)也是一个嵌入,就是这样。
这种方法实际上在许多方面更简单,并且被广泛使用。
基于梯度的元学习方法 📈

这节课的下一部分是关于基于梯度的元学习方法。这些方法一开始看起来是在非常不同的原则上运作的,但我们会看到最后,在很多方面,它们其实有很多相似之处。
动机:从预训练到元学习
建立基于梯度的元学习的动机,让我们回到我们已经学到的东西。如果你拿一个像 ImageNet 这样非常大的数据集,你在上面训练一个大的神经网络(比如 ResNet),你可以微调该网络以解决其他任务。所以你基本上可以提取网络中的特征,你可以用较少数量的数据点进行微调,以解决一些较小的任务,例如细粒度分类。
我们可以问的问题是:预训练实际上只是一种元学习吗?它似乎有一个非常相似的公式。你要用一个非常大的、多样的先验数据集得到一些东西,然后你可以用它来更有效地解决新任务。预训练为你提供更好的特征,使人们能够更有效地学习新任务。
基于梯度的元学习的想法是基本上利用这个配方,但是修改预训练阶段,以便它显式地优化以更好地进行微调。
将元学习框定为优化问题

下面是我们如何将元学习框定为一个优化问题。这是我们在第一部分中对元学习的抽象看法。f_θ(D_train) 只是一个微调算法。在 f_θ(D_train) 之前,它是一个类似于 RNN 的东西,在训练集中读取。如果我们真的让 f_θ(D_train) 像一个梯度下降步骤,那么 f_θ(D_train) 就是神经网络的参数 θ 减去学习率 α 乘以在训练集 D_train 上损失的梯度 ∇_θ L(θ, D_train)。一般情况下,它可以是多个梯度步骤。只要是固定数量的梯度步骤,这就是一个可以展开的函数,你可以评估它。它只是一个定义良好的函数,就像 RNN 是一个定义良好的函数一样,梯度下降也是一个定义良好的函数。
至关重要的是,这可以像任何其他神经网络一样训练,通过将梯度下降实现为 PyTorch 或 TensorFlow 中的计算图,然后通过梯度下降反向传播。因此,元训练过程涉及优化 θ,使得在 θ 上应用这些梯度步骤后得到的参数向量 θ',在测试集 D_test 上表现良好。
模型不可知元学习(MAML)图解
也许用图片来说明这一点会有所帮助。这种方法叫做模型不可知元学习(MAML)。它是这样工作的:
假设你有你的神经网络,它有参数 θ。它只是一个普通的神经网络。它接收一个图像并输出一个标签。如果要在单个任务上训练此网络,每一个训练步骤都将是在训练集损失上的一个梯度下降步骤。
在元学习中,我们要做的是:我们实际上会训练网络,以便在每个任务上都有一个梯度步骤,从而最小化该任务的测试损失。所以现在,每一个元训练步骤都会执行以下更新:

θ <- θ - β * Σ_i ∇_θ L(θ'_i, D_test^i)
其中:
β是元学习率。θ'_i = θ - α * ∇_θ L(θ, D_train^i)是在任务i的训练集上经过一个梯度步骤后得到的参数。L(θ'_i, D_test^i)是在任务i的测试集上,使用更新后的参数θ'_i计算的损失。
我们试图最小化的是所有任务上这些更新后参数的测试损失之和。另一种思考方式是,我们试图找到一个初始参数向量 θ,我们可以从中通过微调(少量梯度步骤)尽可能好地完成我们元训练集中的每一个可能的任务。
在高层次上,它所做的是元训练模型,使其能够非常好地进行微调。从某种意义上说,你刚刚微调的相应任务的测试损失应该很低。
MAML 与其他方法的联系

我们刚才所做的基于有意义的微调直觉,但这是如何映射到我们所学到的所有其他元学习方法的呢?
让我们回到基础:
- 监督学习:学习一个从
x映射到y的函数f。 - 监督元学习:学习一个从
(D_train, x)映射到y的函数f。
模型不可知元学习只是这个函数的一个特殊选择。这是一个选择,其中 f_MAML(D_train, x) 由 f_θ'(x) 给出,其中 θ' 是通过在 D_train 上取梯度步来获得的。但这只是一个函数。它有梯度下降的事实,对于元学习的目的来说,实际上并不那么重要。它只是一个函数,这意味着您可以用 PyTorch 或 TensorFlow 对其进行编码,你可以把它看作是另一个计算图。你的自动微分包(如 PyTorch 或 TensorFlow)实际上可以通过这个梯度下降过程反向传播。唯一的细节是,它将涉及通过神经网络计算二阶导数。
归纳偏差与优势
现在你可能会问,如果它只是另一个计算图,为什么要这样做?嗯,事实证明,这种方法有一个非常有利的归纳偏差。归纳偏差意味着这种设计非常适合解决学习问题。
为什么很好?因为它在做梯度下降。当你真正想适应的任务与你元训练的任务不完全相同时,这很重要。这个想法是,如果你看到一个与你以前看到的任务相似的任务,这当然工作得很好,但是如果你看到有点不同的任务,这仍然有效。
如果我们回想一下黑盒元学习方法,我们可以说,这些算法中的哪一部分实现了学习算法?学习算法只是向前运行 RNN。所以运行大概在里面的某个地方,它权重内的神经网络,是对如何适应新任务的理解。因此,习得的学习算法只是对应于向前运行你的 RNN。但如果我们要说这是一个习得的学习算法,我们可以开始问关于它是否收敛得很好的问题。因为你把你的 RNN 向前运行,你得到了答案,它会聚到什么程度?我们不知道,因为这将是 RNN 的结果。
与基于梯度的方法相比,它仍然是一个计算图,只是一个不同的、其中有梯度运算符的图。我们可以问同样的问题:它是否收敛?它确实收敛,因为它是梯度下降。它会聚到什么程度?它汇聚到你的新任务训练误差的局部最优点,因为它是梯度下降。如果结果不够好,你可以继续走更多的梯度步骤,因为这只是梯度下降。事实上,MAML 允许你在适应新任务时采取比元训练中更多的梯度步骤,这通常可以得到更好的解决方案。
通用性
现在我们可以问另一个关于通用性的问题。你的元学习算法能学习任何算法吗?也许适应这个特定的任务需要一些非常复杂的数学运算,你的元学习算法真的能学习复杂的数学运算吗?更准确地说,这类似于说,它能表示 (D_train, x) 的任何函数吗?
黑盒元学习方法是通用的(如果你有超大的 RNN,原理上它们可以表示任何函数)。但模型不可知元学习(MAML)或基于梯度的元学习实际上是通用的吗?因为它受到约束,必须使用梯度下降来适应新的任务。但事实证明,黑盒元学习法和梯度下降法都是通用的。这是一个有点令人惊讶的事实,但这其实是真的。本质上,这相当于说,如果你元训练一个深网的初始参数,你实际上可以“设计”那个深网,使得梯度下降更新做任何你想做的事。你的神经网络有很多层,这些图层会影响其他图层的梯度,从而可以实现复杂的更新行为。
总结与比较 🏁
本节课中我们一起学习了三类元学习方法:

- 黑盒元学习方法:在训练集中使用某种序列模型(如 RNN、Transformer)进行读取。
- 非参数元学习方法:以某种方式嵌入所有训练点,并做一些软最近邻的变体来将它们匹配到测试点(如匹配网络、原型
课程 P66:CS 182 讲座 21 第 3 部分 - 元强化学习 🧠🤖
在本节课中,我们将要学习如何将元学习的概念应用于强化学习任务,即元强化学习。我们将通过类比的方式,从一般元学习过渡到元强化学习,并介绍其核心思想、两种主要实现方法(黑盒方法与基于梯度的方法)以及实际应用示例。
概述
元强化学习旨在让智能体学会如何学习。具体来说,它通过在多个相关任务(即多个马尔可夫决策过程,MDP)上进行训练,使得智能体在面对新任务时,能够利用少量交互经验快速适应,从而显著减少深度强化学习通常所需的巨大样本量。
从一般元学习到元强化学习
上一节我们介绍了元学习的一般形式。本节中我们来看看如何将其框架迁移到强化学习领域。

一般元学习可以表述为:寻找一个学习函数 f_θ,该函数应用于训练集 D_train 后,能在测试集上最小化损失。其目标可写为:
θ* = argmin_θ L(φ, D_test),其中 φ = f_θ(D_train)。
在强化学习中,最优策略参数 θ* 是通过最大化期望累积奖励获得的:
θ* = argmax_θ E_τ~π_θ [R(τ)]。
我们可以将其视为一个函数 F_RL 应用于一个 MDP M 的结果。
元强化学习则定义如下:我们要找到一组元参数 θ,使其在多个元训练 MDP {M_i} 上的期望性能之和最大。对于每个 MDP M_i,我们最大化策略 π_φ 的期望奖励,其中适应后的参数 φ 是通过将学习算法 f_θ 应用于 M_i 得到的。形式化表示为:
θ* = argmax_θ Σ_i E_τ~π_φ_i [R(τ)],其中 φ_i = f_θ(M_i)。
元训练假设所有 MDP 来自同一个分布 p(M)。元测试时的新 MDP 也来自该分布,这保证了任务间的结构相似性,使得元学习到的经验能够泛化。
黑盒元强化学习 🎯



黑盒方法是元强化学习中最直观的一类。其主要挑战在于如何设计函数 f_θ 来处理 MDP。f_θ 需要完成两件事:
- 利用与 MDP 交互产生的数据来改进策略。
- 决定如何与环境交互以收集数据,即需要学会探索。
以下是实现黑盒元强化学习的一种典型方式:
我们使用一个循环神经网络作为策略网络。RNN 在每个时间步读取智能体与环境交互产生的转移数据 (s_t, a_t, r_t, s_{t+1}),并更新其隐藏状态 h_t。该隐藏状态与当前状态 s_t 一起用于预测下一个动作 a_t。关键点在于,RNN 的隐藏状态在多个训练回合之间不会被重置。这使得 RNN 能够记住跨回合的经验(例如奖励的位置),从而学会探索并快速适应新任务。
训练这个 RNN 策略本身就是一个强化学习过程,只不过目标是在一个很长的“元回合”(包含多个任务回合)中最大化累积奖励。通过这种方式,元强化学习问题被简化为一个更高层的标准强化学习问题。

以下是一些应用示例:
- 简单迷宫导航:智能体(如老鼠)需要找到奶酪。不同 MDP 对应奶酪在不同位置。RNN 策略通过几轮探索记住奖励位置,后续回合便能直接前往。
- 记忆控制与导航任务:早期研究(如2015年的“Memory-based Control”)和近期工作(如“RL^2”)都展示了RNN策略在迷宫等任务中通过记忆实现快速适应的能力。

一个有效的架构变体是:将所有观察到的转移 (s, a, r, s') 独立编码为嵌入向量,然后将这些嵌入相加或聚合,作为策略的输入。这类似于原型网络的思想,通常效果很好。
基于梯度的元强化学习 📈

基于梯度的元强化学习(如MAML的RL版本)与监督学习中的工作原理基本相同。

在常规强化学习中,我们收集一个任务(如向前跑)的多个回合数据,然后使用策略梯度等方法更新策略参数。

在基于梯度的元强化学习中,我们在多个不同任务(如向不同方向跑)上收集数据。元训练的目标是找到一组初始参数 θ,使得对每个任务 M_i 执行一步策略梯度更新(适应步)后,在新参数 φ_i 下该任务的性能提升最大。其优化目标与监督MAML类似,但损失函数换成了负期望奖励(即我们希望奖励最大化)。

计算上,这涉及策略梯度的二阶导数,需要更复杂的微积分,但核心理念完全一致。


实践示例:对一个四足机器人进行元训练,任务集是向不同方向奔跑。元训练后,面对一个新方向任务,机器人先用初始参数 θ 运行(产生一些探索性行为),然后根据收集到的数据计算一个策略梯度步来更新参数。通常,仅需一次梯度更新,机器人就能学会朝正确的方向奔跑。


总结



本节课中我们一起学习了元强化学习。我们首先通过类比一般元学习,定义了元强化学习的问题框架。接着,我们探讨了两种主要方法:
- 黑盒方法:使用RNN等序列模型,通过在回合间保留隐藏状态来记忆和快速适应,同时自动学习探索策略。
- 基于梯度的方法:将MAML等框架直接应用于RL,通过优化初始参数使得策略在一步梯度更新后能在新任务上快速提升性能。



元强化学习的核心优势在于,它能将深度强化学习在适应新任务时所需的巨量交互,减少到仅需几次交互,尽管元训练过程本身可能仍然需要大量数据。这使得它在机器人控制、快速适应新环境等场景中具有巨大潜力。
机器学习基础课程 P7:逻辑回归与优化 🧠


在本节课中,我们将学习如何构建一个完整的监督学习算法——逻辑回归。我们将涵盖模型定义、损失函数选择以及如何通过优化算法找到最佳参数。
模型、损失与优化 📊
我们定义了模型类别,定义了损失函数,现在需要找出如何设置参数θ,使我们的损失函数(负对数似然)最小化。
想象优化算法的一种方式是所谓的“损失景观”。你可以将损失景观视为损失函数的一个图。假设θ是二维的,水平轴代表θ的两个维度,垂直轴代表损失函数L(θ)的值。假设我们有一个单峰凸函数,看起来像一个碗。最好的θ就在碗底。
如果我们没有最好的θ设置,例如我们位于图中的某个黑点,直觉上,为了找到最好的θ,我们需要修改它,使其朝着损失函数下降最陡峭的方向移动。我们想找到损失函数减小的方向,然后朝那个方向移动θ。
一个非常通用的算法草图是:找到方向v,使得L(θ)在该方向上减小,然后朝该方向移动θ。选择新的θ为旧的θ加上一个速率α乘以v。α是一个小常数,也称为学习率或步长。

物理类比是,如果你将一颗弹珠放在这个景观中,它会向最陡峭的下降方向滚动,最终稳定在最小值处。这就是算法背后的直觉。我们想找到L(θ)减小的方向,然后朝那个方向移动。
有趣的是,最陡峭的下降方向并不总是最好的选择。我们将在后续讲座中更多地讨论其他类型的优化算法。所有这些方法的共同点是,它们都会朝着L(θ)减小的方向移动,至少是局部地。然后我们重复这个过程。如果重复足够多次,最终可能会稳定下来。
梯度下降算法 📉
让我们尝试从数学上实例化这样一个方法。如果我们有关于θ的函数L(θ),L(θ)首先向哪个方向减小?方向是什么?我们如何表示一个方向?θ是一个向量,方向也是一个向量。
在一维情况下,函数的斜率会告诉我它向哪个方向减小。如果斜率为负,函数向右减小;如果斜率为正,函数向左减小。所以从斜率可以看出该朝哪个方向移动以减少函数。
同样的直觉适用于更高维度。总的来说,你可以想象取一个高维函数,单独考虑每个维度,弄清楚斜率是正还是负。如果是正,则沿该维度向负方向移动;如果是负,则沿该维度向正方向移动。对于每个维度,沿着与斜率相反的方向移动。
斜率就是导数。所以我们要做的就是为每个维度设定方向,为我们函数L关于该维度θ的偏导数的负数。即 v₁ = -∂L/∂θ₁, v₂ = -∂L/∂θ₂。这保证会给出一个L(θ)减小的方向。

一个突击测验问题:这是L(θ)减小的唯一方向吗?答案是不一定。事实上,有整整一半的向量空间,其中的方向都会使L(θ)减小。这些偏导数给出了最速下降的方向,但如果你稍微向左或向右,仍然会减小。如果你朝相反方向走,则会增加。实际上,只要v与梯度有正点积,你就会沿着L(θ)减小的方向前进。所以这不是一个唯一的解,也不是唯一的减小方向。这是最陡峭的一个,但不一定意味着它是最好的。但这是我们要做的选择。
一个非常有用的概念是梯度。梯度是一个向量,其中该向量的每一维都是函数相对于θ相应维度的偏导数。这里v是梯度的负值。所以说,梯度是通过取偏导数并将它们堆叠成向量来形成的。我们将在后续讲座中讨论更多关于梯度相关方法的细节。
现在,这里是算法的草图:
- 计算梯度。
- 将θ设为 θ - α × 梯度。

梯度是负的v。这和上面的算法完全一样,我们只是实例化它来使用梯度。上面的算法比较通用,它可以使用任何减小的方向。梯度给了我们一个这样的方向。这就是梯度下降算法。

逻辑回归算法 🤖
在这一点上,我们实际上已经选择了我们的优化器——梯度下降。所以我们准备在一个大的GPU上运行我们的算法。
我们刚刚设计的方法叫做逻辑回归。逻辑回归不是深度学习,但我们将要讨论的深度学习中的许多概念实际上从逻辑回归中获得灵感。
总结一下逻辑回归:
- 你有一个函数 f_θ。
- 该函数输出一个n维向量,其中标签y可以有m个不同的值。
- 对于每一个可能的标签y,有一个相应的参数向量 θ_y。
- 这个向量的每个维度都由x和相应的θ_y之间的内积给出。
- 在矩阵表示法中,可以写成 f_θ(x) = xᵀΘ,其中x是列向量,xᵀ是行向量,它乘以一个矩阵Θ,其中每一列都是对应于不同标签的θ向量。
- 现在,具有特定标签y_i的概率由应用于f_θ(x)输出的softmax函数给出。
- Softmax函数的第i个输出是向量中第i个条目的指数,除以所有条目的指数之和。这确保了我们的概率都是正的,并且加起来等于1。
这就给出了逻辑回归算法。参数θ定义了分隔这些类别的线。给定类别的概率在远离该类时增加或减少。
我们找到最佳θ(学习θ)的方法是使用梯度下降。现在我们需要一点微积分来实际计算这些导数,但原则上我们拥有所需的一切,因为我们有函数,所以我们可以使用微分规则来计算这些导数。在后面的讲座中,我们将讨论自动微分,以及如何使计算这些导数的过程自动化,这样我们就不用手工计算了。
我们的损失是负对数似然。这基本上就是我们需要知道的一切,以实现逻辑回归。

二元逻辑回归的特例 ⚖️
作为一个小练习,让我介绍逻辑回归的一个特殊情况:当只有二元标签时,例如只有两类(猫和狗)。在这种情况下,会发生一个很好的简化。虽然这种简化并不总是关键,但它在其他一些地方有一定信息量和用处。
当我们只有两类(y₁和y₂)时,逻辑回归的概率可以由这个方程给出。这里我把f换成了softmax。所以 p(y₁|x) = e^(θ_y₁ᵀx) / (e^(θ_y₁ᵀx) + e^(θ_y₂ᵀx))。
同时拥有θ_y₁和θ_y₂有点冗余,因为如果你正好有两类,那么 p(y₁|x) + p(y₂|x) = 1。所以如果你知道 p(y₁|x),就可以得到 p(y₂|x)。因此,我们在这里过度参数化了这个函数,意味着我们使用的参数比实际需要的要多。
通过一些代数运算,可以说明为什么我们不需要这么多参数。让我们把这个方程的分子和分母都乘以 e^(-θ_y₁ᵀx)。这不会改变等式。现在请注意,当你有两个指数的乘积时,你可以把它写成它们指数之和的指数。
所以你可以等价地写成:p(y₁|x) = 1 / (1 + e^(-(θ_y₁ - θ_y₂)ᵀx))。
令 θ_+ = θ_y₁ - θ_y₂。在这种情况下,上面的方程简化为 1 / (1 + e^(-θ_+ᵀx))。这是一个更简单的方程。现在我们的参数减少了一半。我们实际上不需要单独的θ_y₁和θ_y₂,只需要θ_+。
一般来说,你总是可以去掉其中一个θ向量。但当有大量类别时,人们通常不会费心这么做。如果你有一千个类别,去掉其中一个向量并不能真正节省什么。但是如果你有两类,去掉其中一个参数,数量就减少了两倍。所以这是一个重要的特例。
这个函数有时被称为逻辑方程。逻辑回归实际上就得名于此。使用Softmax的多类逻辑回归实际上是后来的创新。原始的逻辑回归是二元的,它使用逻辑方程来得到其中一类的概率。逻辑方程的形状像一条S形曲线,在左边饱和到0,在右边饱和到1。它有时也被称为Sigmoid函数。

经验风险最小化 ⚠️
我想简要介绍的最后一个概念是一个特殊术语:经验风险最小化。你有时会听到人们在监督学习中这样做。
让我们回想一下0-1损失。0-1损失是:如果你预测错误,损失为1;如果正确,损失为0。你可以把风险看作是你出错的概率。回想我们的生成过程:有人拍照,这是一张从图像分布中随机抽样的图片,它有一个从标签的真实分布中采样的标签。给定那张图片,你答对或答错的可能性有多大?你出错的概率就是0-1损失的期望值。那么 f_θ(x) 出错的可能性有多大?这只是0-1损失的期望值。所以你可以称之为风险——你得到错误答案的风险。

这一切都是针对0-1损失的。但你可以把这个概念推广到其他损失上。你可以说,对于其他一些损失,风险就是该损失的期望值。它不再有“你弄错的概率”这种美好的解释,但这仍然是一个数字。
如果我们用负对数似然,我们可以说风险是在真实分布 p(x, y) 下,我们所学θ的损失的期望值。所以风险是你实际上想最小化的东西。
问题是你实际上无法计算出真正的风险,因为你不能随意地从 p(x) 中取样。你不能就这样得到无限的样本。相反,你会得到一个数据集D。所以你可以计算出所谓的经验风险。经验风险只是对真实风险的基于样本的估计,你通过将你的损失在所有样本上平均来得到。所以说,经验风险是我们实际上在监督学习时最小化的东西。但经验风险有望接近真实风险。
我们可以问:在这一点上,经验风险实际上是真实风险的一个很好的近似值吗?什么时候好?为什么好?为什么会不好?

过拟合与欠拟合 🔄
监督学习通常是经验风险最小化。这和真正的风险最小化是一样的吗?有两个设置可能会给你带来麻烦。
第一种情况是,真实风险和经验风险不一样。这是过拟合的设置。当经验风险较低,但真正的风险很高时,就会发生过拟合。这意味着你在训练集上的平均损失很低,但是你损失的真正期望值并不低。
这可能看起来有点奇怪,因为你的经验风险是对真实风险的无偏样本估计。但请记住,你实际上是在使用这些数据点来学习θ,所以θ与你的训练点耦合,你的估计器不再是无偏的。
有一个更直观的解释。假设我正在努力拟合一条线。这些点与我的训练数据相对应,所以这些点基本上是对齐的,但有一点噪声。如果我对这些点拟合一个非常高的多项式,我会得到一个类似这样的疯狂函数。这个疯狂的函数具有非常低的经验风险,因为它直接穿过点,但它的真正风险将会相当高。
如果数据集太小,或者模型太强大(意味着你的模型有太多的参数,容量过大),就会发生这种情况。它正在发生,因为我拟合了一个高次多项式,有更多的变量来拟合这些数据点。所以这就是经验风险与真实风险不同的时候。
另一个会让你陷入困境的情况是,经验风险与真实风险相匹配,但你得到了所谓的欠拟合。欠拟合是当经验风险很高,真正的风险也很高时。
例如,真正的函数可能是一种曲线,但我们选择在上面放一条线。这条线和那条曲线不太匹配。所以如果模型太弱(参数太少或者容量太小),就会发生这种情况。如果你的模型很好,但优化器配置得不好(例如用了非常糟糕的学习率或优化算法),也可能导致欠拟合。
过拟合和欠拟合是非常非常重要的概念。如果你现在还不完全清楚,别太担心,因为我们会在后面的课上更多地讨论它。但是这个材料很重要,我们会再回来的。

总结 🎯
本节课中我们一起学习了如何推导出我们的第一个监督学习算法——逻辑回归。我们讨论了如何定义一个算法,基本上需要做三个选择(电脑为我们做第四件事):
- 定义模型类:我们选择了逻辑模型,其中函数f在输入和参数中是线性的,然后对其应用softmax得到概率。
- 定义损失函数:我们选择了负对数似然。
- 选择优化器:我们使用梯度下降来优化。
- 电脑执行:实际上在一个大的GPU上运行。

在未来,我们将讨论其他优化器、其他模型类,甚至其他损失函数。但就目前而言,希望这能让你了解我们如何建立这些机器学习算法。

课程 P8:CS 182 第三讲 - 第一部分 - 误差分析 📊

在本节课中,我们将学习如何分析机器学习模型的误差。我们将探讨模型为何会犯错,并引入偏差和方差这两个核心概念来理解过拟合与欠拟合现象。通过数学分析,我们将看到总误差如何分解为偏差和方差两部分,并讨论如何在这两者之间进行权衡。
回顾:经验风险与真实风险 🔄
上一节我们介绍了如何通过梯度下降训练分类模型。本节中,我们来看看如何判断模型是否表现良好。

为了理解这一点,让我们重新审视经验风险和真实风险。我们使用0-1损失函数,其定义如下:
公式: L(fθ(x), y) = 1 if fθ(x) ≠ y else 0
这个损失函数指示预测是否正确。如果我们假设数据由一个生成过程产生,那么我们可以询问模型 fθ(x) 出错的概率。0-1损失的期望值量化了这个概率:
公式: R(fθ) = E[L(fθ(x), y)]

这个期望值就是真实风险,它衡量了模型在整个数据分布上的预期损失。然而,在实际训练中,我们无法计算整个分布,只能使用数据集 D。我们在数据集上计算的平均损失称为经验风险:
公式: R_emp(fθ) = (1/N) * Σ L(fθ(x_i), y_i)
经验风险是真实风险的一个无偏估计。但问题在于,我们通常选择参数 θ 来最小化经验风险,这并不等同于最小化真实风险。这两者之间的差异是本节课讨论的核心。
过拟合与欠拟合 🎯
经验风险并不总是真实风险的良好近似。在监督学习中,我们进行经验风险最小化,但这与真实风险最小化并不完全相同。

以下是两种常见的问题:
- 过拟合:指经验风险很低,但真实风险很高的情况。这意味着模型在训练集上表现很好,但在未见过的数据上表现很差。
- 欠拟合:指经验风险和真实风险都很高的情况。这意味着模型即使在训练集上也表现不佳。

过拟合可能发生在数据集太小,或者模型相对于数据来说过于复杂(参数太多、容量太大)时。欠拟合则可能发生在模型太简单(参数太少、容量太小),或者优化器配置不当(如学习率不佳)导致无法有效最小化损失时。
回归设定与均方误差 📉
上一节我们讨论了分类问题。本节中,我们转向回归问题,因为其数学推导更简洁。回归的目标是预测连续变量。
为了采用概率方法,我们为输出 y 选择一个概率分布。一个常见且简单的选择是正态分布(高斯分布)。如果我们将其方差固定为单位矩阵,那么该分布的对数概率就对应于预测值 fθ(x) 与真实值 y 之差的平方。
公式: log p(y | x) ∝ -1/2 * (fθ(x) - y)^2
忽略常数项,这正好对应于均方误差损失。因此,最小化均方误差损失等价于在方差固定的假设下最大化高斯分布的对数似然。
偏差-方差分解 🧩
现在,让我们更正式地理解过拟合和欠拟合。我们关心的是,算法在不同训练集上的平均表现如何。


我们定义 f_D(x) 为在数据集 D 上训练得到的模型。我们想要计算该模型预测的期望误差,这个期望是对所有可能的数据集 D 取的:
公式: E_D[ (f_D(x) - f(x))^2 ]
其中 f(x) 是真实的函数。通过引入模型在所有数据集上的平均预测 f̄(x) = E_D[f_D(x)],并进行代数运算,我们可以将期望误差分解为两个部分:
公式: E_D[ (f_D(x) - f(x))^2 ] = E_D[ (f_D(x) - f̄(x))^2 ] + (f̄(x) - f(x))^2
- 方差:
E_D[ (f_D(x) - f̄(x))^2 ]。这衡量了模型预测随着训练集的不同而产生的变化程度。高方差对应过拟合。 - 偏差平方:
(f̄(x) - f(x))^2。这衡量了模型平均预测与真实函数之间的差距。高偏差对应欠拟合。

总误差就是方差与偏差平方之和。这个分解清晰地表明:
- 过拟合时,方差主导误差。
- 欠拟合时,偏差主导误差。

偏差-方差权衡 ⚖️
认识到误差由方差和偏差组成后,我们就可以理解,要得到一个性能良好的算法,需要在偏差和方差之间进行权衡。
以下是调节权衡的思路:
- 如果方差太大(过拟合),可以尝试增加一点偏差来减少方差(例如,使用更简单的模型、正则化、获取更多数据)。
- 如果偏差太大(欠拟合),可以尝试减少一点偏差,但这可能会增加方差(例如,使用更复杂的模型、更好的特征)。

了解当前问题是高方差还是高偏差至关重要,因为针对它们的解决方法通常是相反的。用错了方法可能会使问题恶化。
总结 📝
本节课中,我们一起学习了机器学习中的误差分析。

- 我们区分了经验风险和真实风险,并指出经验风险最小化可能导致与真实风险最小化的目标不一致。
- 我们定义了过拟合(低经验风险,高真实风险)和欠拟合(高经验风险,高真实风险)现象。
- 在回归设定下,我们使用均方误差损失进行分析,并将其与高斯分布的对数概率联系起来。
- 通过数学推导,我们将模型的期望误差分解为方差和偏差平方之和。方差衡量模型对训练集的敏感性,偏差衡量模型平均预测的准确性。
- 最后,我们讨论了偏差-方差权衡,指出改进模型需要根据当前主导误差的类型(高方差或高偏差)来采取相应的策略。

理解偏差和方差是诊断和改善机器学习模型性能的基础。在接下来的课程中,我们将学习具体的技术(如正则化)来管理这种权衡。
课程 P9:CS 182 第三讲 - 第二部分 - 误差分析 📊
在本节课中,我们将要学习如何通过误差分析来权衡和调节模型的偏差与方差。我们将重点探讨一种核心方法——正则化,并理解其背后的贝叶斯视角、常见形式以及它在不同模型中的应用。
权衡偏差与方差 ⚖️

上一节我们介绍了偏差与方差的概念。本节中我们来看看如何调节它们。
一种方法是获取更多数据。获取更多数据主要解决方差问题。数据越多,过拟合的可能性就越低,方差的影响就越小。例如,如果我们只有五个数据点,并拟合一个八次多项式,很可能导致过拟合。但如果我们有五百万个数据点,拟合同样的八次多项式,结果可能会好得多。因此,拥有大量数据时,复杂的函数类可能很好地拟合真实函数。
然而,获取更多数据通常不会改善偏差。例如,一条直线无法拟合真实的曲线,无论我们提供多少数据点。
另一种方法是更改模型类。这意味着改变你的模型程序,例如使用更多参数或更复杂的结构。你可以将十二次多项式改为线性函数,这可能会减少方差,但增加偏差。但这是一个非常离散的选择。
我们能否更平滑地限制模型形式?例如,我们可能确实想要十二次多项式,但不希望它过于“疯狂”、锯齿状或尖锐。我们能否为模型复杂性构造一种连续的调节旋钮?
正则化:连续的调节旋钮 🎛️

这被称为正则化。非正式地说,正则化是我们添加到损失函数中的一项,通常是为了减少方差(尽管并非总是如此)。理解正则化的一个更简单方法是通过贝叶斯视角,它认为正则化反映了我们关于参数的先验信念。这不是唯一的解释,但有助于建立直觉。
贝叶斯视角的高级直觉 🧠
当方差很高时,通常是因为数据没有提供足够的信息来唯一确定参数。例如,如果我们只有五个数据点和一个十二次多项式,问题在于函数未被充分确定。有许多不同的十二次多项式都能很好地拟合这五个点。就经验风险而言,它们看起来都一样好。数据中的噪声甚至意味着我们可能不想要那个“最拟合”的函数。
核心问题是:数据中没有足够的信息在众多可能函数中做出选择。因此,我们需要通过损失函数向算法提供额外知识。如果我们能提供足够的信息来消除歧义,算法或许就能在众多看似同样好的模型中选择正确的那个。
假设我们有一个数据集,并画出了多个都能完美穿过数据点的函数曲线。即使你不知道真实数据是什么,仅凭观察这些曲线,你可能会觉得某些曲线(例如更平滑的)看起来更“合理”,更可能是正确的函数。这种“看起来更合理”的直觉,就对应了先验信念。
贝叶斯形式化 📐
我们可以将整个学习问题用概率形式写出:给定数据集 D,最可能的参数向量 θ 是什么?
根据条件概率定义和链式法则:
P(θ | D) ∝ P(D | θ) * P(θ)

P(D | θ):这是在给定参数θ下,观察到数据D的概率。这正是我们之前讨论的最大似然估计的目标。在独立同分布假设下,它可以分解为所有数据点概率的乘积。P(θ):这是先验概率,表示在看到任何数据之前,我们认为θ的可能性有多大。它编码了我们对模型的先验信念。
因此,我们得到了一个新的损失函数(负对数形式):
-log P(θ | D) = -log P(D | θ) - log P(θ) + 常数
第一项 -log P(D | θ) 就是我们熟悉的负对数似然损失。第二项 -log P(θ) 就是我们的正则化项。
选择先验:为何偏好小参数? 🔍
我们如何构建一个偏好“平滑”函数(对应小参数)的先验 P(θ) 呢?
考虑具有多项式特征的线性回归模型:
f_θ(x) = θ_0 + θ_1*x + θ_2*x^2 + θ_3*x^3 + ...
那些表现出“尖刺”和剧烈波动的复杂多项式,往往具有较大的系数。如果我们限制系数只能取较小的值,得到的多项式就会平滑得多。
因此,如果我们想要小系数,可以问:什么样的概率分布会给小数值分配更高的概率?一个简单而常见的选择是均值为零的正态分布(高斯分布)。
均值为零的正态分布像一个钟形曲线,它将大部分概率质量集中在接近零的小数字周围。其方差决定了“接近零”的程度。方差越小,先验对具有大系数(大尖峰)的函数的惩罚就越强。

均值为零的正态分布的对数概率为(忽略常数项):
log P(θ) ∝ - (1/(2σ^2)) * Σ_i θ_i^2

令 λ = 1/(2σ^2),我们可以将先验项等价地写为 -λ * ||θ||^2。这里 ||θ||^2 是参数向量 θ 的L2范数平方(即各分量平方和)。λ 是一个超参数,由我们选择,用于控制正则化的强度。
正则化损失函数 📉

综合以上推导,对于线性回归,我们最终的损失函数变为:
损失 = 均方误差(MSE) + λ * ||θ||^2
这与我们之前的目标函数(负对数似然)一致,只是增加了一个 λ||θ||^2 项。这个简单的添加对应于贝叶斯方法中采用均值为零的正态先验。


逻辑回归中的正则化 🧮

上一节我们介绍了逻辑回归。它也可能过拟合,产生虽然训练精度完美但决策边界不合理(高方差)的模型。
在逻辑回归中,参数 θ 的大小会影响概率预测的“尖锐”程度:
- 大的
θ值 => 尖锐的概率跳变(类似硬边界)。 - 小的
θ值 => 平滑的概率变化(更合理的决策边界)。
如果我们希望得到平滑的概率输出(即更合理的决策边界),一个自然的想法是偏好较小的 θ 值。因此,我们可以采用同样的先验:均值为零的正态分布。
对于逻辑回归(分类问题),添加正则化项后的损失函数为:
损失 = 负对数似然 + λ * ||θ||^2
这通常被称为权重衰减,因为在最小化过程中,它促使权重(参数)向零衰减、变小。
其他常见的正则化方法 📚
除了L2正则化(权重衰减),还有其他重要的正则化方法。
以下是几种常见的正则化器:
- L1正则化:使用参数的绝对值之和作为正则化项,即
λ * Σ_i |θ_i|。它对应于拉普拉斯先验。与L2正则化不同,L1正则化倾向于产生稀疏解,即它鼓励参数向量θ中的许多分量直接变为零。这在特征选择等场景中很有用。 - Dropout:一种专门用于神经网络的正则化技术。它在训练过程中随机“丢弃”(暂时忽略)网络中的一部分神经元,以防止神经元之间过度的协同适应(co-adaptation)。
- 特定模型的正则化:例如,在生成对抗网络(GANs)中,常使用梯度惩罚等专门设计的正则化器来稳定训练。
在深度学习中,权重衰减(L2)是最常见的正则化器。L1正则化也广泛使用。Dropout在特定网络架构中很流行。而GANs等模型则有自己特殊的正则化需求。

正则化的其他视角与超参数选择 🧐
正则化不仅可以从贝叶斯视角理解,还有其他重要视角:
- 数值稳定性视角:在问题欠定时(例如特征数多于数据点),L2正则化可以通过向需要求逆的矩阵对角线添加一个常数项,使其变得非奇异,从而解决数值计算问题。
- 优化视角:在某些情况下,精心选择的正则化项可以改变损失函数的景观,使其更容易优化,从而可能帮助减少欠拟合(如果欠拟合是由于优化困难造成的)。这在深度学习的一些模型(如GANs)中尤为明显。
关于正则化器,一个共同点是它们引入了超参数(如 λ)。这些超参数不能通过最小化训练损失来选择(因为训练损失可能已经很低,例如在过拟合时)。我们必须通过其他方式选择它们,例如使用验证集。
总结 📝
本节课中我们一起学习了:
- 调节偏差与方差:可以通过获取更多数据(主要降低方差)或更改模型复杂度(同时影响偏差和方差)来实现。
- 正则化的核心思想:通过向损失函数添加一个不依赖于数据的项,来引入我们对模型的先验偏好或约束,从而控制模型复杂度,主要用以降低方差。
- 贝叶斯解释:正则化项对应于模型参数的先验分布。例如,L2正则化对应于均值为零的正态先验,它表达了我们对“小参数”或“平滑函数”的偏好。
- 常见形式:
- L2正则化/权重衰减:
λ * ||θ||^2,最常见,促使参数变小。 - L1正则化:
λ * Σ_i |θ_i|,促使参数稀疏化(很多为零)。 - Dropout:神经网络特有的随机正则化方法。
- L2正则化/权重衰减:
- 多面性:正则化不仅用于防止过拟合,有时也从数值稳定或优化难易度的角度被理解和应用。
- 超参数:正则化强度(如
λ)是一个关键的超参数,需要通过验证集等技术进行选择。

正则化是机器学习中平衡模型复杂性与泛化能力、将先验知识融入学习过程的核心工具之一。


浙公网安备 33010602011771号