CMU-11-785-深度学习导论笔记-全-

CMU 11-785 深度学习导论笔记(全)

1:课程导览与安排 📋

在本节课中,我们将学习卡内基梅隆大学《深度学习导论》课程(2024年秋季)的整体安排、课程目标、评估方式以及学习准备。我们将了解神经网络的基本概念、课程结构、作业与项目要求,以及如何有效地参与课程学习。


什么是神经网络?🧠

神经网络是人工智能中的一种方法,它教会计算机以受人类大脑启发的方式处理数据。近年来,它已成为各种模式识别、预测和分析问题的主要研究方向之一。神经网络在许多问题上确立了最先进的技术水平,并且常常大幅超越之前的基准。

上一节我们介绍了神经网络的基本定义,本节中我们来看看神经网络带来的一些突破性应用。

以下是神经网络取得突破的一些领域:

  • 语音助手:例如 Siri、Alexa、Google Assistant。
  • 视觉与感知:例如人脸检测、人脸识别、物体检测、语义分割。
  • 自动驾驶汽车
  • 音乐生成
  • 图像生成(如DALL-E) 以及 ChatGPT


谁适合学习本课程?🎯

本课程专为任何希望学习深度学习并愿意每周投入12至20小时(取决于已有知识基础)的人设计。我们希望学生愿意持续在Piazza论坛上提供反馈并进行互动,因为这是本课程的主要沟通方式。同时,我们希望学生乐于接受挑战,并为未来的AI研究做好准备。

为了学习本课程,你需要具备以下基础知识:

  • 编程:需要掌握Python,本课程将使用PyTorch。至少应学过编程基础。
  • 数学:需要了解微积分和线性代数。如果具备向量微积分和软件工程背景会更有帮助,但最重要的是掌握Python、微积分和线性代数。

课程目标与内容概览 🗺️

从宏观角度看,本课程旨在让你理解神经网络,并理解那些能够完成前述任务(如语音识别、图像生成等)的模型。我们希望你能无畏地为各种任务设计和训练这些网络。

更具体地看,本课程将涵盖以下概念:

  • 神经网络发展的简要历史视角。
  • 神经网络的类型及其核心思想。
  • 学习是如何发生的。
  • 神经网络的架构与应用。

在实践层面,我们希望你能熟悉训练过程,能够实现各种不同的架构,并了解某些问题的最先进解决方案。总体目标是为你未来的研究领域工作打下基础。

以下是本课程将涵盖的主题列表:

  • 基础网络形式:包括多层感知机、卷积神经网络、循环神经网络、玻尔兹曼机。
  • 高级形式:包括生成模型、对抗模型、图神经网络以及Transformer。
  • 应用领域:计算机视觉(图像识别)、文本处理(语言建模与生成)、机器翻译(序列到序列建模)、分布建模与数据生成,以及语音识别。

课程网页上列出了参考书目,并且在每节课的页面中也会提供额外的阅读材料(文章)。


课程安排与后勤 📅

教师与助教

本课程的主讲教师是Bhiksha Raj教授,他将负责所有讲座。课程助教名单及其联系方式公布在课程网站上,他们来自匹兹堡和基加利校区,提供线上和线下的办公时间。

讲座与讨论

课程讲座将进行直播以供远程学生观看,同时也会被录制并上传到课程网站。观看讲座非常重要,因为测验内容与讲座相关,并且我们会记录出勤情况。

课程讨论将在Piazza论坛上进行,请务必关注最新动态。计算基础设施方面,我们将为所有学生提供Amazon代金券,并协助使用Google Cloud。

出勤与参与

出勤将计入成绩(占1%),因为我们注意到出勤的学生通常表现更好。我们主要通过课堂实时投票来追踪出勤。教授会在讲座中发起投票,你需要在Piazza或Zoom(如果教授远程授课)上在规定时间内(通常30秒)作答。答案对错不重要,重要的是参与。对于时区不便的学生,可以通过观看录制讲座来获得出勤分,但必须在讲座后一周内完整观看视频。

课程后半部分的日程可能会根据教授的行程和客座讲座安排略有调整。


作业、测验、项目与评分 📝

测验

每周会有一次测验,问题涉及当周讲座幻灯片中涵盖的主题。测验通常在周五晚上发布,周日晚上截止。本学期共有14次测验,我们将取其中最好的12次成绩计入最终评分。每次测验包含10道选择题,你有3次尝试机会以取得最佳成绩。

请注意:测验问题可能来自幻灯片上提及但教授未在课中讲到的内容,也可能来自教授在课中提到但未写在幻灯片上的内容。有时还会涉及需要阅读指定论文才能回答的问题。

作业

本学期有4次计分作业(作业1至4),以及一次不计分的预备练习(第0次辅导课)。每次作业分为两部分:

  1. 第一部分:自动评分的编程问题,使用自定义的神经网络工具包(如my_torch)从头开始实现。这部分考察你独立编写神经网络代码的能力。提交时需要特别注意软件包版本,否则自动评分器可能给出零分。提前提交可获得加分。
  2. 第二部分:在真实数据集上解决复杂问题的开放性问题,以竞赛形式在Kaggle上进行评分。评分基于你在排行榜上的表现,采用线性插值法计算得分。有早期提交截止日期(占10分,旨在督促起步)和准时提交截止日期(占90分)。整个学期共有10天的延迟提交额度(总计,非每次作业)。

项目(仅限11-785和11-685课程)

项目旨在锻炼你理解和实现超越作业范围的新想法的能力。项目范围很广,你可以:

  • 实现并评估最新论文中的前沿思想。
  • 尝试解决一个问题,如果做得好,可能导向发表。
  • 尝试全新的想法,例如新的学习算法、技术或模型。

项目以小组形式进行(11-785课程为3-4人,11-685课程为2人)。要求包括提交项目提案、中期报告、最终报告以及一个5分钟的视频演示。每个小组都会分配一名助教作为导师。视频发布后,你需要回答其他同学在Piazza上提出的问题。

评分构成

  • 每周测验:占总成绩的 24%
  • 作业:占总成绩的 50%
  • 团队项目:占总成绩的 25%(11-485课程无此项,评分比例会相应调整)。
  • 出勤:占总成绩的 1%


学习准备、团队合作与指导 🤝

准备工作

本课程实践性很强,涉及大量编码和实验,需要处理大型数据集。主要使用Python和PyTorch。如果你觉得起点较低,强烈建议完成第0次辅导课的练习。

课程沟通

所有课程沟通都在Piazza上进行,包括课堂投票、问答、公告、团队协作以及教授的笔记。请务必下载该应用。

学习小组

每位学生都必须加入一个学习小组,并会分配一名助教导师。我们建议每组3-4人。在学习小组中,你们可以讨论作业、论文、课堂内容和测验。小组成员也可以同时是项目团队成员。

学术诚信

合作学习很重要,但必须遵守规则:

  • 测验:必须独立完成。可以讨论问题,但答题时必须独自进行。
  • 作业:必须独立解决。可以讨论代码和调试,但最终提交的每一行代码都必须是自己的成果。
  • 严禁抄袭。你是来学习深度学习的,请挑战自己,不要降低标准。

寻求帮助

如果你的测验或作业成绩突然大幅下滑,你的助教导师可能会主动联系你。如果你遇到困难、感到落后或课业压力过大,请主动联系你的导师、任何助教或教师。课程还提供了关于如何应对困难的特定辅导视频。


课程挑战与成功秘诀 💪

本课程难度很高,需要每周投入大量时间。但如果你能跟上进度,它会充满乐趣。课程采用掌握性评估:

  • 测验旨在检验你对讲座概念的理解。
  • 作业教你实现和优化复杂的神经网络。
  • 项目让你接触解决真实世界问题的过程。

任何在本课程中获得A的学生,从技术上讲都已准备好从事深度学习相关的工作。

再次强调,如果你对课程准备程度有疑虑,请务必完成第0次辅导课。作业1的第一部分包含了对后续课程有帮助的内容,争取拿到满分,它也是最容易的作业。

如有任何问题,请在Piazza上发帖,我们会尽力在5分钟内回复。

感谢你选修本课程,期待在讲座和办公时间见到你!


本节课总结:我们一起学习了《深度学习导论》课程的总体安排,包括神经网络简介、课程目标、适合人群、课程内容结构、详细的评分与考核方式(测验、作业、项目),以及成功完成课程所需的学习准备、团队合作规范和可用的支持资源。记住,积极参与、独立完成作业并善用沟通渠道是成功的关键。

2:深度学习导论与神经网络基础 🧠

在本节课中,我们将学习深度学习与神经网络的基本概念。我们将从历史背景出发,探讨连接主义思想,了解早期模型及其局限性,并最终介绍现代神经网络的基本原理和强大能力。

概述:为什么是神经网络?

近年来,神经网络已应用于几乎所有能想到的问题,并在许多领域达到了最先进的水平。从2016年左右开始,基于神经网络的AI无处不在,这正是我们学习它的原因。

从大脑到机器:连接主义思想

上一节我们看到了神经网络的广泛应用,本节中我们来看看其思想根源——连接主义。

人类大脑由约800亿个神经元和约100万亿个连接构成。大脑的处理能力完全取决于这些神经元之间的连接方式,而非单个神经元本身。这就是连接主义的核心思想:知识存储在连接中。

这与传统的冯·诺依曼计算机架构形成鲜明对比:

  • 冯·诺依曼架构:处理器和内存分离。程序存储在内存中,要执行不同任务,只需更改程序。
  • 连接主义架构:机器结构本身就是程序。要执行不同操作,必须改变机器(网络)本身的连接方式。

因此,当前的神经网络模型都是连接主义机器,它们模拟了大脑的结构。

早期神经元模型及其演进

理解了连接主义思想后,我们来看看人们如何尝试用计算模型来模拟神经元。

麦卡洛克-皮茨模型 (1943)

这是第一个神经元的计算模型。该模型将神经元视为布尔阈值单元:

  • 接收来自其他神经元的兴奋性或抑制性输入。
  • 如果总输入超过阈值,则“激发”(输出1),否则不激发(输出0)。

该模型表明,仅使用这种简单机制,就可以构建任何布尔逻辑门,从而将大脑视为一个巨大的布尔机器。但它没有提供学习规则。

赫布学习规则 (1949)

唐纳德·赫布提出了一个著名的学习规则:“一起激发的神经元,连接在一起。”用公式可表示为:
W_xy = W_xy + η * x * y
其中,xy是神经元的激活状态(0或1),η是学习率。

局限性:权重只增不减,最终会导致所有连接变得非常强,网络变得无用,因此该规则本质上是不稳定的。

罗森布拉特感知机 (1958)

弗兰克·罗森布拉特提出了“感知机”模型,这是一个更完整的模型。单个感知机单元的结构如下:

  1. 计算输入的加权和:z = w1*x1 + w2*x2 + ... + bb是偏置,即阈值项的负值)。
  2. 通过一个激活函数(如阶跃函数)输出:如果 z > 0,输出1;否则输出0。

罗森布拉特的关键贡献是引入了期望响应的概念,并提出了一个可证明收敛的学习算法(适用于线性可分问题)。单个感知机可以计算AND、OR、NOT等布尔函数。

局限性:单个感知机无法计算异或(XOR) 函数,因为XOR是线性不可分的。

现代神经网络:多层感知机

既然单个感知机能力有限,本节我们来看看如何通过组合它们来构建更强大的模型。

通过将多个感知机连接成层,就形成了多层感知机。在这种网络中:

  • 隐藏神经元:中间层的神经元,其输出模式并非我们直接关心的。
  • 输出神经元:最终产生我们感兴趣结果的神经元。

一个关键见解是:单个感知机定义一个线性决策边界(一条直线或超平面)。通过组合多个感知机的输出,可以形成复杂的、非线性的决策边界。

以下是多层感知机(MLP)的强大能力:

  1. 通用布尔函数:给定任何布尔函数,都可以构造一个MLP来计算它。
  2. 通用分类器:MLP可以近似任何复杂的决策边界,对输入空间进行划分。例如,要识别数字“2”,MLP可以学习在784维像素空间中划出代表“2”的区域。
  3. 通用函数逼近器:通过使用适当的激活函数(不仅仅是阶跃函数)和足够多的隐藏单元,MLP可以以任意精度逼近任何连续函数。这意味着神经网络本质上是一个函数,它将输入映射到输出。

因此,无论是语音识别(音频→文本)、图像描述(图像→文字)还是游戏(状态→动作),都可以被看作是一个由神经网络实现的复杂函数。

总结与展望

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

  • 神经网络源于对大脑和认知的连接主义建模。
  • 早期模型(麦卡洛克-皮茨、赫布、感知机)奠定了基础,但各有局限。
  • 现代多层感知机(MLP) 通过组合简单单元,成为了通用函数逼近器,能够进行分类、回归和模拟复杂输入输出关系。

在下一节课中,我们将更深入地探讨神经网络如何计算这些函数,理解“深度”的含义,并讨论神经网络的局限性。

4:如何训练神经网络 🧠

在本节课中,我们将学习如何训练一个神经网络。我们将描述学习问题的本质,介绍感知器规则,并引入经验风险最小化的概念来训练神经网络。


神经网络学习问题

在过去的几节课中,我们介绍了什么是神经网络。它们是这些简单单元的层级组合,这些单元共同协作,能够完成许多复杂的任务。

我们了解到,这些网络实际上可以建模任何布尔函数,这意味着如果你给我一个布尔函数,我可以为其设计一个网络。它们可以以任意精度建模任何分类边界,并且也能建模任何连续值函数。

然而,要使网络能够执行这些操作,它必须具备足够的架构:足够深的网络,其中每一层都足够宽且连接充分。

核心问题:参数学习

给定所有这些,我们发现,在各种人工智能任务中,神经网络是一个神奇的盒子,它接收输入并产生输出。这个盒子本身就是一个函数。

一旦我们意识到这个盒子是一个函数,一些问题就出现了。我们谈论的是数学对象,而当我观察这个AI任务时,输入的是游戏状态,输出的也是游戏状态,这些都不是数学对象。那么,我们如何将它们转换成函数可以实际操作的形式呢?

我们有几个问题需要回答:

  1. 如何表示输入和输出?输入和输出必须是数学对象,必须是数字或向量。
  2. 如何组合网络来计算所需的函数?

今天,我们将专注于第二个问题:如何组合执行特定函数的网络。


网络结构与参数

我们定义网络为非常简单的单元组成的网络。最基本的单元是感知器。

感知器接收一组输入,每个输入对应一个权重。它计算输入的加权和,并与一个阈值进行比较。如果超过阈值,它就“激活”。这是感知器最简单的版本。

更进一步,我们可以将感知器视为计算输入的仿射函数(即输入的加权和加上一个偏置),然后应用某种激活函数。最简单的激活函数就是阈值函数。

一旦我们以这种方式分解感知器的操作,激活函数本身可以是任何函数。因此,感知器也可以这样重画:偏置可以表示为一个固定为1的额外输入,该输入的权重就是偏置。

激活函数可以是任何函数:阈值函数、Sigmoid函数或其他类型的函数。这是基本的单元。

但我们实际计算的函数是由这些单元组成的网络。我们假设网络是一个前馈网络,这意味着在处理任何输入时,信息从输入单向流向输出,没有循环。

当我们决定网络结构后,设计网络时需要回答的问题包括架构本身:有多少层,每层有多宽等等。目前,由于我们讨论的是函数逼近,我们将假设我们拥有的任何网络都具有能够建模我们试图建模的特定函数的架构。

现在,网络是一个由这些基本单元组成的层级结构。每个单元都有一些权重和一个偏置。因此,整个网络是一个数学结构,它接收一些输入,最终计算出一个输出。它拥有参数,即网络中神经元的权重和偏置。

因此,你可以用函数形式表示整个网络:F(x; W)。分号后的任何内容表示参数,分号前的内容表示输入。所以这是一个参数化函数,参数是W。

整个网络只是一个参数化函数。因此,当我们谈论学习一个网络时,我们实际上是在谈论学习参数W,因为我们假设网络的架构是给定的。

问题是,我们如何设置这些参数W,以使网络执行你想要它做的任何事情。


从函数逼近到经验风险

我们已经看到,多层感知器可以表示任何函数。这意味着无论你给我什么函数,我总能构造一个MLP来计算这个函数。由于我们特别假设网络架构是给定的,这意味着我们总能选择一组参数W,使网络计算这个函数。

那么,给定一个网络和一个它必须计算的函数,我如何设置网络的参数W,使网络精确地计算这个函数而不是其他东西?

对于非常简单的函数,你可以直接手工设计网络,手动设置参数。但这只适用于最简单的函数。当函数变得更复杂时,你将无法手动设计网络参数。

那么,我们如何为更通用的函数设计网络?如何设置参数值?

可视化学习问题

我们可以这样可视化问题:假设我们有一个目标函数 G(x)。网络本身是一个参数化函数 F(x; W)。当我们谈论训练网络时,我们问自己的问题是:如何设置这些参数W,使得网络计算的函数与目标函数之间的差距最小化?换句话说,如何设置W使得两者之间的“面积”最小化?如果这个面积变为0,那么网络就在计算正确的函数。

为了能够指定这一点,我需要一种量化这个面积的方法。为了量化面积,我首先需要定义一个误差函数,用于量化网络当前计算的结果与你希望它计算的结果之间的差距。

这个阴影面积将是整个输入空间上误差的积分。我们的问题是确定最小化这个误差的W。

实际问题:未知的目标函数

这里可能有什么问题?问题在于,要使这种方法有效,G(x) 必须在所有地方都被指定。而在实践中,我们并不知道 G(x) 是什么。那么我们该怎么办?

我们将对函数进行采样。我们不会知道所有地方的 G(x),而是会收集一堆 x,并在每个 x 处获取对应的 G(x) 值。因此,我们有一堆输入-输出对。

我们只剩下了一组输入-输出对。从这组输入-输出对中,我们将尝试学习这个函数。

回到我们最初的图示,我们现在拥有的是一堆经验性的输入 x 和对应的 G(x) 值。现在,你的网络将在这些输入处计算其他值。因此,我们不是试图最小化整个阴影面积,而是最小化这些红线(误差)在我们获得的样本集合上的平均误差。

收集样本集很容易。你基本上会为你的数据收集输入-输出对,如图像及其标题、语音及其转录。因此,采样函数很容易,我们可以计算这个经验误差估计。

然后,我们可以尝试估计最小化这个经验误差的参数W。

经验误差与真实误差

最小化经验误差是否必然也会最小化网络与你想要的函数之间的实际误差?这是一个我们做出的假设,但并不能保证。

因此,我们的问题归结为:给定一组训练样本(基本上是 G(x) 的样本),我们计算经验误差(即网络输出与训练集合上实际函数输出之间的平均误差)。我们将尝试找到使这个平均误差最小化的网络参数。

我们希望,如果我们做得正确,网络学习到的函数将处处成立。但这只是一个希望,没有保证。


故事梗概

到目前为止,学习神经网络就是确定网络参数(权重和偏置)的问题。网络必须具备足够的能力(即足够的宽度和深度),我们才有机会学习到能够捕捉目标函数的网络。我们假设它具有足够的能力。

因此,学习问题仅仅是确定其参数,以使误差最小化。

理想情况下,你希望优化网络以在所有地方表示所需的函数。但这需要知道所有地方的函数。因此,在实践中,我们做的是抽取输入-输出训练实例。这些训练实例是你的函数的样本。

我们将估计网络参数以拟合这些训练实例上的输入-输出关系。我们希望,当我们这样做时,得到的网络能处处拟合函数 G(x)


从分类问题开始

让我们从一个简单的任务开始:尝试学习一个分类器,这比学习回归问题更简单。这是最早使用MLP解决的问题之一,因此我们将考虑它,特别是考虑二分类问题,但这可以推广到多分类。

在二分类中,网络的期望输出是什么?是0或1。只有两个类别:0类和1类。那么误差是什么?如果网络的输出是正确的类别,则没有误差。如果网络的输出是错误的类别,则存在误差。

因此,如果你的目标函数是由虚线所示的函数,网络的输出只能是0或1。对于错误的W值,网络函数(以绿色显示)将在某些位置计算错误的输出。

因此,这里的总误差将是网络输出与期望输出不匹配的训练实例的数量。误差是网络输出与期望输出不匹配的训练实例的计数。

当我们想要学习分类器时,我们真正谈论的是学习这些W,使得这些误差的计数最小化。


感知器学习规则

让我们从考虑Rosenblatt最初定义的多层感知器开始。在Rosenblatt的原始模型中,基本单元具有阈值激活。每个感知器计算输入的仿射函数,并将其与0进行比较。如果大于0,则输出为1;如果小于0,则输出为0。

我们现在的问题是用一些训练数据(输入和输出的集合)来训练这个由这些基本单元组成的网络。

最简单的多层感知器模型是什么?最简单的模型就是单个感知器。如果我们只有一个感知器,我们知道它是如何操作的。单个感知器将输入的加权和与阈值进行比较。如果输入大于阈值,则输出为1,否则为0。

因此,输出为1和输出为0之间的边界是输入的加权和恰好等于阈值的地方。这个边界是一个超平面。该函数是一个阶跃函数。

这就是我们想要学习的函数,如果我们只是学习最简单的分类器网络(只有一个单元)。但我们不会被给予整个函数。当我们谈论学习这个函数时,我们真正谈论的是学习权重和阈值。你只会得到一堆训练样本:对于某些输入,输出是0;对于其他输入,输出是1。你只得到那些彩色点(红点和蓝点)。从这些点中,你必须学习整个阶跃函数。

问题本质

这意味着什么?我们想要学习将正例与负例分开的超平面。换句话说,我们想要学习这个边界的方程。而这个边界的方程完全由权重和阈值指定。

让我们看看如何解决这个问题。但在我们这样做之前,这个边界具有 Σ_i w_i x_i + b = 0 的结构,其中 b = -t。这是一种仿射函数。它将是一个超平面,但这个超平面会经过原点吗?

为了简化我们的解释,使用线性函数总是更方便。所以,这是你的仿射函数。我可以这样做:我可以通过添加一个值为1的单一值来增广输入。现在,该输入的对应权重成为偏置。如果我将增广后的输入视为新的输入,这个函数突然变成了线性的。我是如何将仿射函数转换为线性函数的?

转换为线性问题

假设这是x1,这是x2。然后我添加了一个x3,其值始终为1。这意味着我现在完全在x3=1的平面上工作。在这个平面上,我的边界不经过原点。但是,当我观察增广后的输入时,我可以画一个经过原点的超平面,该超平面在这个边界处切割这个平面。

因此,虽然我的超平面与x3=1平面的交线是一条不经过原点的线,但我在这个增广空间中得出的实际决策边界确实经过原点。这样我就将仿射问题转换为了线性问题。现在我可以处理线性问题了。

线性分类器的几何

现在,我们谈论线性函数。我们想要找到一组权重 w_i,使得 Σ_i w_i x_i = 0 表示一个能清晰分隔正例和负例的超平面方程。

我可以稍微不同地写它。Σ_i w_i x_i 可以写成向量形式:w^T x = 0。这就是我的边界。

如果两个向量的内积为0,它们之间的关系是什么?它们是正交的。所以,对于任何W,这表示所有与W正交的X的集合。这是一个经过原点的超平面,包含所有与W正交的向量。

如果我在平面的这一侧(与W相同的一侧)有一个向量,w^T x 的符号将是正的。如果我在另一侧有一个向量 x'w^T x' 将是负的。

因此,我可以简单地运行一个测试:如果 w^T x > 0,则表示空间的这一部分,即类别1;如果 w^T x < 0,则表示类别0。

感知器学习算法的基础

假设我有一个正例实例 x。我可以画出各种将 x 保持在正侧的决策边界。哪个决策边界使 x 离它最远?与它成90度角的那一个。换句话说,对于任何给定的 x,如果 x 是一个正例实例,最优的权重向量是什么?是 x 本身。如果 x 属于负类,最优的权重向量是 -x

这是感知器学习规则所依据的基本原则。算法如下:给定一堆点,我想找到能清晰分隔红点和蓝点的决策边界。我假设红点和蓝点可以通过一个超平面线性分离。我想找到W,使得对于所有红点 w^T x > 0,对于所有蓝点 w^T x < 0

有一个闭式解,但最初由Rosenblatt提出的流行解决方案是一种在线算法,即著名的感知器算法。你任意初始化W,然后持续增量修正它,直到分类误差最小化。

我们知道,对于任何正例实例 x,最优权重向量是 x 本身(可能有一些缩放)。对于任何负例实例,最优向量是 -x

因此,你从某个W开始,开始分类你所有的训练样本。如果你遇到一个被错误分类的正例实例,那么你将权重向量拖向该正例实例的最优权重向量(即向 x 方向拖动),通过简单地将 x 加到 w 上来实现。如果你得到任何被错误分类的负例实例,那么你将权重向量拖向该 x 的最优权重向量(即向 -x 方向拖动),通过从 w 中减去 x 来实现。

算法流程与局限性

感知器学习算法如下:给定一些训练样本集合,初始化W,然后循环遍历训练实例。

只要数据可以被线性边界分离,该算法保证在有限步数内收敛,不会循环。但是,如果类别不是线性可分的,例如,如果红点在蓝侧或蓝点在红侧,算法将永远不会收敛。总是会有一个被错误分类的实例,你将一直追逐自己的尾巴。

所以,当我们只有一个感知器的非常简单的问题时,情况就是这样。即使我假设数据是线性可分的,我也必须进行一些搜索。


扩展到多层网络与组合爆炸

现在,让我们看一些更复杂的情况,比如这个双五边形问题。在双五边形问题中,我知道使用左边的架构,我可以构造一个能精确分类这个双五边形边界的网络。较低层的每个感知器将捕获一个五边形的一条边界。下一层中,一个感知器将捕获一个五边形,另一个将捕获第二个五边形。然后将两者相加,从而得到所需的决策边界。

但是,当你尝试训练这个网络时,你只会得到这些点。你必须学习这个边界。现在,我能应用我们刚刚看到的感知器规则来学习这些边界吗?

为了使这一点更明显,让我们假设某个“先知”告诉了我该层中其他9个感知器的权重。所以我只剩下学习第10个感知器(图中圈出的那个)的权重的问题。训练数据当然是这些红点和蓝点。我能从这些数据中学习那个感知器吗?整个网络是给定的,但“先知”恰好没有给我一个感知器。

我能从这些数据中学习这个感知器吗?感知器学习的要求是数据必须是线性可分的。这里的类别是线性可分的吗?其他感知器的权重定义了特定的边界,然后它可能变成线性的吗?不一定。

如果你想学习这个感知器,你将不得不重新标记一些蓝点,使得两个类别现在变得线性可分。然后,只有到那时,你才能学习感知器。没有其他方法可以学习感知器。

我需要重新标记哪些实例?你怎么知道?记住,如果你错误标记一个实例,整个网络就会出错。你怎么知道?

因此,如果你想真正学习感知器规则,你将不得不开始重新标记一些实例。然后学习边界,正确的解决方案是你不知道要重新标记哪些实例。因此,你将不得不以各种可能的方式重新标记数据实例,并尝试学习决策边界,直到找到一个能为你提供整个训练数据完美分类的重新标记方式。

这意味着,对于单个神经元,我们必须尝试重新标记蓝点的每一种可能方式,使得我们可以学习一条将所有红点保持在一侧的线。而这是在我给了你整个网络的情况下。如果我一开始什么神经元都不知道,只给了你正确的架构,你将不得不为你网络中的每个神经元,以各种可能的方式重新标记数据,这样当你学习每个神经元的边界时,网络的最终输出是完美的。

这是一个组合搜索问题,你无法在合理的时间内完成。它不会奏效。

因此,即使对于这个非常简单的双五边形问题,我们也必须知道每个训练实例中每个神经元的期望输出(基本上是如何标记它们)。我们必须这样做,以便当你将它们全部组合时,整体的决策边界是双五边形。这是一个组合优化问题。如果你在重新标记时犯了一个错误,你的整个分类器就会变成垃圾。

问题的核心

这就是问题所在:使用感知器规则训练这个网络是一个组合优化问题。我们不知道每个训练实例中每个神经元的输出。我们需要重新标记它们并在空间中搜索,因此我们还必须确定每个训练实例中每个神经元的正确输出,这至少是输入大小的指数级。

因此,这不是一个可行的解决方案。这实际上使多层感知机的研究停滞了很长时间。人们意识到,早期的原始感知器和MLP基于简单的线性分类器和阈值激活。很快人们就意识到,学习单个感知器的问题可能是可处理的,但如果你想学习整个网络,你就会遇到这个无法解决的组合优化问题。

因此,人们提出了各种贪婪解决方案,如Adaline、Madaline等。这些方法持续了将近三十年,直到Paul Werbos(在MIT)发现了一种替代方法(在70年代初),但直到15年后,Jeff Hinton才认识到Werbos论文的价值。


解决方案:可微性与代理损失

那么解决方案是什么?首先,到目前为止的故事是:学习网络就是学习权重和偏置以计算目标函数的问题,假设网络具有足够的能力。在实践中,我们通过使网络匹配从目标函数中抽取的训练实例的输入-输出关系来学习网络。

线性决策边界可以由单个感知器学习。如果类别是线性可分的,可以在线性时间内学习。对于非线性决策边界,我们需要感知器网络,但使用阈值函数激活训练MLP将需要知道每个训练实例中每个感知器的输入-输出关系。这些必须作为训练的一部分来确定。对于阈值激活,这是一个NP问题,具有NP复杂度。

因此,认识到训练整个MLP是一个组合优化问题,使神经网络的发展停滞了十多年,直到我们有了一个小小的洞见。

阈值激活的问题

那个洞见是什么?问题在于,我们真正试图计算的是误差:网络正确的频率是多少?错误的频率是多少?你有一堆训练输入,对于这些输入,输出必须是0或1。然后,在每个输入处,你可以计算网络的输出。网络的输出可能是正确的或错误的,我们计算它错误分类的次数,然后设置参数。

但是,假设我有这个训练数据 x1x5,虚线显然是正确的决策边界。假设我选择构建我的网络,使其学习绿色函数。绿色函数在某个点从0变为1,它犯了两个错误。现在,如果我将这个阈值(从0变为1的点)向左移动一点,错误的数量会改变吗?如果我向右移动一点,错误的数量会改变吗?我必须移动很多,它必须跨越一个训练点。如果我移动一点点,错误的数量不会改变。这就是实际问题。

同样,如果我试图在多维空间中学习这种感知器,我可能从一个初始边界开始。它会错误分类一些实例。如果我稍微旋转边界,在边界实际跨越并分类一个训练点之前,错误总数不会改变。因此,我的参数的微小变化不会导致误差的任何变化。这就是为什么我们真的无法使用增量步骤来优化。没有迹象表明我应该朝这个方向还是那个方向旋转权重,因为朝这个方向或那个方向

5:神经网络优化与梯度下降 🧠

在本节课中,我们将学习如何训练神经网络,这本质上是一个函数优化问题。我们将从基础的导数概念出发,逐步理解梯度下降算法,并了解如何将其应用于最小化神经网络的损失函数。


1. 函数最小化与导数回顾

上一节我们介绍了神经网络训练的核心是经验风险最小化。本节中,我们来看看如何通过优化方法找到使损失函数最小的参数。

1.1 导数的本质

给定一个函数 y = f(x),在任意点 x 处,如果我们对 x 施加一个微小的扰动 Δx,会导致 y 产生一个微小的变化 Δy。导数 f'(x) 被定义为连接 ΔxΔy 的乘性因子。

公式
Δy ≈ f'(x) * Δx

xy 都是标量时,我们常写作 dy/dx。导数 f'(x) 的符号表明了函数的变化趋势:

  • 如果 f'(x) > 0,函数在 x 处递增。
  • 如果 f'(x) < 0,函数在 x 处递减。
  • 在函数从递增转为递减(局部极大值)或从递减转为递增(局部极小值)的转折点,导数为零。

1.2 多变量函数的导数:梯度

当输入 x 是一个向量时,函数 y = f(x) 的导数定义依然不变:ΔyΔx 的线性函数。

公式
Δy = ∇f(x)^T * Δx

其中,∇f(x) 称为函数在 x 处的梯度。它是一个向量,其每个分量是函数 f 对相应输入分量的偏导数。

梯度的几何意义
梯度指向函数值增长最快的方向。其大小表示增长率。因此,负梯度 -∇f(x) 指向函数值下降最快的方向。

梯度的另一个性质:梯度始终垂直于函数的等高线(即函数值相同的点构成的集合)。

1.3 海森矩阵与二阶信息

对于多变量函数,海森矩阵 H 描述了函数的曲率。其第 (i, j) 个元素是二阶偏导数 ∂²f / (∂x_i ∂x_j)

海森矩阵的特征值与优化

  • 在局部极小值点,海森矩阵的所有特征值均为
  • 在局部极大值点,所有特征值均为
  • 如果特征值有正有负,则该点是一个鞍点

2. 梯度下降算法 📉

对于复杂的函数,我们无法直接求解 ∇f(x) = 0 来找到最小值。梯度下降提供了一种迭代逼近的方法。

2.1 算法核心思想

我们从参数的初始猜测 x₀ 开始,反复沿负梯度方向(即函数下降最快的方向)移动一小步,直到函数值不再显著变化。

更新公式
x_{k+1} = x_k - η * ∇f(x_k)

其中:

  • x_k 是第 k 次迭代的参数值。
  • η学习率,控制步长大小。
  • ∇f(x_k) 是函数在 x_k 处的梯度。

2.2 算法行为与挑战

梯度下降的行为取决于函数形状:

  • 对于凸函数(如碗状),梯度下降能稳定收敛到全局最小值。
  • 对于非凸函数,算法可能陷入局部最小值、鞍点或平坦区域。

停止准则:通常不直接检查梯度是否为零,而是监控函数值 f(x) 的变化。当连续迭代中函数值下降非常微小时,即可停止。


3. 应用于神经网络训练 🔧

现在,我们将梯度下降的思想应用于神经网络的训练。

3.1 问题重述

给定训练集 {(x_i, d_i)},我们定义损失函数 L(W),它是网络输出 f(x_i; W) 与期望输出 d_i 之间差异的平均值。目标是找到参数 W 以最小化 L(W)

训练过程

  1. 初始化网络参数 W
  2. 重复以下步骤直至收敛:
    a. 计算当前参数下,损失函数关于所有参数的梯度 ∇L(W)
    b. 沿负梯度方向更新参数:W := W - η * ∇L(W)

直观理解:梯度下降检查每个参数 w_i,判断“增加这个参数会使损失增大还是减小”。根据结果,相应地增加或减少该参数。

3.2 网络组件与数据表示

为了具体计算损失和梯度,我们需要定义网络的各个部分。

神经元结构:典型的神经元先计算输入的线性加权和(仿射变换),再通过一个非线性激活函数
z = w^T * a + b
y = g(z)
常见激活函数包括 Sigmoid、Tanh、ReLU 及其平滑版本 Softplus。

向量激活函数:有些激活函数同时处理整个层的神经元输出。最典型的是 Softmax,它将一组实数 z_i 转换为一个概率分布 y_i
y_i = e^{z_i} / Σ_j e^{z_j}
Softmax 的输出所有分量和为1,且每个分量非负,常用于多分类网络的输出层。

输入与输出表示

  • 输入 x:必须是数值向量(如图像像素、语音特征、文本嵌入)。
  • 输出 y 与目标 d
    • 回归任务dy 是实值标量或向量。损失函数常使用均方误差。
    • 二分类任务:网络输出一个标量 y(如使用 Sigmoid 表示正类概率),目标 d 为 0 或 1。
    • 多分类任务:网络输出一个向量 y(使用 Softmax 表示各类概率),目标 d 使用 独热编码(一个分量是1,其余为0)。

3.3 损失函数的选择

损失函数必须是可微的,这样才能计算梯度。不同任务对应不同的损失函数。

回归任务(L2损失)
L = 1/2 * ||y - d||²
引入 1/2 是为了使损失对 y 的导数形式更简洁:∂L/∂y = y - d

分类任务(交叉熵损失)

  • 二分类L = -[d * log(y) + (1-d) * log(1-y)]
  • 多分类L = -log(y_c),其中 c 是目标类别索引。

为什么使用交叉熵而非均方误差?
交叉熵损失在概率预测中具有更好的性质。例如,当网络对错误类别给出100%置信度时,交叉熵损失会趋于无穷大,迫使网络快速修正错误。而均方误差对此惩罚不足。

一个重要性质:对于使用 Softmax 输出层和交叉熵损失的多分类网络,损失 L 关于 Softmax 层输入 z 的梯度恰好是 y - d。这与回归问题中 L2 损失的梯度形式一致,极大地简化了反向传播的计算。


总结

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

  1. 优化基础:回顾了导数、梯度、海森矩阵的概念及其在寻找函数极值点中的作用。
  2. 梯度下降:掌握了这一核心迭代优化算法,它通过沿负梯度方向更新参数来最小化函数。
  3. 神经网络训练框架:将梯度下降应用于神经网络,明确了训练即最小化损失函数的过程。
  4. 关键组件:了解了神经元结构、激活函数(特别是 Softmax)、数据表示(独热编码)以及针对回归和分类任务的不同损失函数(L2损失和交叉熵损失)。

下一节课,我们将深入探讨如何高效地计算神经网络中复杂的梯度——即反向传播算法

6:神经网络训练与反向传播 🧠

在本节课中,我们将学习如何训练神经网络。核心是通过经验风险最小化来最小化损失函数,并使用梯度下降算法来更新网络参数。为了实现梯度下降,我们需要计算损失函数相对于每个网络参数的梯度,而反向传播正是高效计算这些梯度的关键算法。


概述:训练神经网络的框架

训练神经网络的目标是找到一组参数(权重和偏置),使得网络在训练数据上的预测误差最小。我们通过最小化一个称为损失函数的量来实现这一点。损失函数衡量了网络输出与期望输出之间的差异。

具体来说,给定一个包含输入-输出对 (X, Y) 的训练集,我们定义损失函数 L(W) 为所有训练实例上差异的平均值。这里的差异 D 衡量了网络实际输出与期望输出之间的距离。损失 L 是网络参数 W 的函数。

我们的目标是找到使 L(W) 最小的 W。这通过梯度下降完成:我们初始化参数,然后反复沿损失函数梯度的反方向更新参数。

梯度下降更新公式:
W_new = W_old - η * ∇L(W_old)

其中 η 是学习率,∇L(W) 是损失函数关于参数 W 的梯度。

为了执行梯度下降,我们必须计算梯度 ∇L(W)。由于损失是单个训练实例差异的平均值,因此梯度也是这些单个差异梯度的平均值。所以,核心问题归结为:如何计算单个训练实例的差异相对于任意网络参数 W_ij 的梯度? 这就是本节课和下一节课的重点。


预备知识:微积分与链式法则回顾

在深入反向传播之前,我们先回顾一些基本的微分规则,并用一种直观的“影响图”来表示。

基本导数与影响图

对于一个函数 y = f(x),导数 dy/dx 描述了 x 的微小变化 Δx 会导致 y 产生多大的变化 Δy。对于足够小的 Δx,有:
Δy ≈ (dy/dx) * Δx

我们可以用“影响图”来表示:变量 x 通过一条边影响变量 y,这条边的“权重”就是导数 dy/dx

标量函数图示:
x --(dy/dx)--> y

多变量函数与偏导数

对于多变量函数 y = f(x1, x2, ..., xn),每个输入变量 xi 都通过一条边影响 y,边的权重是偏导数 ∂y/∂xiy 的总变化是所有输入变化的加权和:
Δy ≈ Σ_i (∂y/∂xi) * Δxi

多变量影响图:
x1 --(∂y/∂x1)--> y
x2 --(∂y/∂x2)--> y
...
xn --(∂y/∂xn)--> y

链式法则

对于复合函数 y = f(g(x)),存在中间变量 g。影响图变为:
x --(dg/dx)--> g --(dy/dg)--> y

根据图示,xy 的总体影响是沿着路径权重的乘积:
Δy ≈ (dy/dg) * (dg/dx) * Δx
因此,导数 dy/dx = (dy/dg) * (dg/dx)。这就是链式法则

分布式链式法则

当多个中间变量都依赖于同一个变量,并共同影响最终输出时,需要使用分布式链式法则。例如,y = f(g1(x), g2(x), ..., gn(x))

影响图如下:
x --(dg1/dx)--> g1 --(∂y/∂g1)--> y
x --(dg2/dx)--> g2 --(∂y/∂g2)--> y
...
x --(dgn/dx)--> gn --(∂y/∂gn)--> y

xy 的总体影响是所有可能路径贡献的总和:
dy/dx = Σ_i (∂y/∂gi) * (dgi/dx)


前向传播:计算网络所有中间值

为了计算梯度,我们首先需要知道网络在给定输入和当前参数下的所有中间激活值。这个过程称为前向传播

考虑一个具有 L 层的神经网络。我们使用以下符号:

  • y^(0):网络输入 x
  • 对于第 l 层 (l=1...L):
    • z^(l):该层神经元的仿射(加权和)值向量。z_j^(l) 表示第 l 层第 j 个神经元的仿射值。
    • W^(l):连接第 l-1 层到第 l 层的权重矩阵。
    • b^(l):第 l 层的偏置向量。
    • y^(l):第 l 层的输出(激活值)向量。y_j^(l) = f(z_j^(l)),其中 f 是激活函数。

前向传播的伪代码如下:

  1. 设置输入:y^(0) = x
  2. 对于每一层 l = 1L
    a. 计算仿射值:z^(l) = W^(l) * y^(l-1) + b^(l)
    b. 应用激活函数:y^(l) = f^(l)(z^(l))
  3. 网络最终输出为 y^(L)

通过前向传播,我们得到了网络中每一个 zy 的值,这些值在后续计算梯度时是必需的。


反向传播:计算梯度

现在我们有了所有中间值,可以计算损失相对于每个参数的梯度了。我们从网络的输出层开始,逆向计算到输入层,因此这个过程被称为反向传播

我们的目标是计算对于单个训练实例的差异 D 相对于任意参数 W_ij^(l)b_j^(l) 的梯度。

核心思想:逆向链式法则

我们从输出层开始。首先计算差异 D 相对于网络最终输出 y^(L) 的梯度 ∂D/∂y^(L)。这个梯度取决于我们具体使用的差异函数(如均方误差、交叉熵等),是已知的。

然后,我们一步步向后(反向)计算:

  1. 计算 ∂D/∂z^(L):利用链式法则,∂D/∂z^(L) = (∂D/∂y^(L)) * (∂y^(L)/∂z^(L))。其中 ∂y^(L)/∂z^(L) 就是激活函数 f 在点 z^(L) 处的导数 f'(z^(L))
  2. 计算参数梯度:
    • 对于输出层权重:∂D/∂W^(L) = (∂D/∂z^(L)) * (∂z^(L)/∂W^(L))。由于 z^(L) = W^(L)*y^(L-1) + b^(L),所以 ∂z^(L)/∂W^(L) 就是前一层的输出 y^(L-1)。因此,∂D/∂W^(L) = (∂D/∂z^(L)) * (y^(L-1))^T(注意维度匹配)。
    • 对于输出层偏置:∂D/∂b^(L) = ∂D/∂z^(L),因为 ∂z^(L)/∂b^(L) 是单位矩阵。
  3. 计算对前一层的“误差信号”:为了继续反向传播,我们需要知道 D 对前一层的输出 y^(L-1) 的梯度。∂D/∂y^(L-1) = (W^(L))^T * (∂D/∂z^(L))。这可以理解为当前层的梯度 ∂D/∂z^(L) 通过权重矩阵 W^(L) 反向传播到了前一层的输出。
  4. 重复步骤:现在我们将 y^(L-1) 视为新的“输出”,重复步骤1-3,计算 ∂D/∂z^(L-1)∂D/∂W^(L-1)∂D/∂b^(L-1) 以及 ∂D/∂y^(L-2),依此类推,直到传播到第一层。

向量化形式的反向传播

在实际实现中,我们使用向量和矩阵运算,效率更高。对于第 l 层,反向传播的向量化计算如下:

假设我们已经计算出了 ∂D/∂z^(l)(记作 δ^(l))。

  1. 计算权重梯度:∂D/∂W^(l) = δ^(l) * (y^(l-1))^T
  2. 计算偏置梯度:∂D/∂b^(l) = δ^(l)
  3. 计算对前一层的误差:∂D/∂y^(l-1) = (W^(l))^T * δ^(l)
  4. 计算前一层的 δδ^(l-1) = (∂D/∂y^(l-1)) ⊙ f'(z^(l-1)),其中 表示逐元素乘法。

反向传播的伪代码如下(向量形式):

  1. 前向传播,保存所有 z^(l)y^(l)
  2. 计算输出层误差:δ^(L) = (∂D/∂y^(L)) ⊙ f'(z^(L))
  3. 对于层 l = L2(反向):
    a. 计算权重梯度:∇W^(l) = δ^(l) * (y^(l-1))^T
    b. 计算偏置梯度:∇b^(l) = δ^(l)
    c. 向上一层传播误差:δ^(l-1) = (W^(l))^T * δ^(l) ⊙ f'(z^(l-1))
  4. 最终也可以计算第一层的参数梯度。

注意,对于整个训练集的损失 L,其梯度是每个训练实例计算出的梯度 ∇D 的平均值。因此,在实际训练中,我们通常在一个批次(batch)的数据上计算平均梯度,然后用它来更新参数。


特殊情况与注意事项

反向传播算法依赖于一些假设,当这些假设不成立时,需要特殊处理。

1. 向量激活函数(如Softmax)

对于标准的逐元素标量激活函数(如Sigmoid、ReLU),∂y/∂z 是一个对角矩阵。但对于像Softmax这样的向量激活函数,每个输出 y_i 都依赖于所有输入 z_j。因此,其雅可比矩阵 ∂y/∂z 是一个满矩阵,而不是对角阵。

在反向传播中,计算 δ = (∂D/∂y) * (∂y/∂z) 时,就需要进行矩阵乘法,而不是简单的逐元素乘法。对于Softmax与交叉熵损失结合这种常见情况,可以推导出简洁的梯度形式。

2. 不可微的激活函数(如ReLU, Max)

  • ReLU:在 z=0 处不可微,因为左导数为0,右导数为1。在实践中,我们通常约定在 z=0 处的导数(次梯度)为一个值,例如0或1。常用约定是:f'(z) = 1 if z > 0 else 0
  • Max函数:例如 y = max(z1, z2, ..., zn)。其导数对于最大值对应的输入为1,对于其他输入为0。如果最大值不唯一(平局),则需要定义次梯度。

对于这些情况,只要函数是连续的(或分段连续),我们仍然可以使用一个定义的(次)梯度来进行反向传播,这在实践中通常是有效的。


总结

本节课我们一起学习了神经网络训练的核心机制:

  1. 训练目标:通过最小化损失函数(训练集上的平均差异)来优化网络参数。
  2. 优化方法:使用梯度下降法迭代更新参数。
  3. 梯度计算:通过反向传播算法高效计算损失函数相对于所有参数的梯度。反向传播本质上是微积分中链式法则在计算图上的高效应用。
  4. 计算过程
    • 前向传播:计算网络在给定输入下的所有中间输出。
    • 反向传播:从输出层开始,逆向计算梯度,并利用这些梯度更新每一层的权重和偏置。
  5. 实现形式:使用向量和矩阵运算可以极大地简化并加速前向和反向传播的过程。

反向传播是训练深度神经网络的基石,理解其原理对于掌握深度学习至关重要。在接下来的课程中,我们将探讨与训练相关的其他重要主题,如优化器、初始化策略和正则化。

7:神经网络训练与优化 🧠

在本节课中,我们将要学习神经网络训练的核心过程——梯度下降法,并深入探讨其在实际应用中的收敛性、挑战以及一些高效的优化算法。我们将从回顾反向传播开始,分析梯度下降可能遇到的问题,并介绍如何通过调整学习率和使用更先进的优化器来改善训练效果。


神经网络训练回顾

上一节我们介绍了如何使用反向传播计算梯度。本节中,我们来看看梯度下降法如何利用这些梯度来更新网络权重,并开始探讨其有效性。

神经网络是通用函数逼近器,只要架构合适,它们可以模拟任何复杂函数。我们必须通过训练来学习权重,使网络能够逼近目标函数。训练的目标是最小化在训练集上定义的损失函数。

然而,最小化训练集上的损失,是否保证网络学到了正确的函数?我们真正希望最小化的是模型在整个数据分布上的期望误差,而我们实际做的只是在一小部分训练样本上最小化经验风险。我们使用梯度下降法进行这种最小化,而梯度则通过反向传播计算。

以下是训练的基本步骤:

  1. 给定一组训练点,定义在所有训练点上的损失函数,即训练集上的平均误差。
  2. 初始化所有网络参数。
  3. 迭代计算损失函数相对于所有网络参数的梯度。
  4. 沿着梯度的反方向更新所有网络参数。

反向传播用于计算关键的梯度项:损失相对于网络参数的导数。


反向传播规则与向量化更新

在上一节中,我们推导了反向传播的规则。本节中,我们通过向量化的形式来回顾这些更新规则,以便更清晰地理解信息在网络中的流动。

在网络中,每一层的计算如下:

  • 线性变换Z_k = W_k * Y_{k-1} + b_k
    • 其中 Y_{k-1} 是前一层的输出向量,W_k 是权重矩阵,b_k 是偏置向量,Z_k 是当前层的净输入。
  • 激活函数Y_k = f(Z_k)
    • 其中 f 是非线性激活函数。

通过这两个规则,我们可以从输入层逐步前向传播到输出层。最终,网络的输出会与期望输出进行比较,计算损失。每一个 Y_k 最终都会影响损失值。

在反向传播中,我们计算损失相对于各层参数的梯度。核心是利用链式法则:

  • 损失 L 相对于 Z_k 的梯度:∂L/∂Z_k = (∂L/∂Y_k) * (∂Y_k/∂Z_k)
    • 这里 ∂Y_k/∂Z_k 是激活函数的导数构成的雅可比矩阵。
  • 损失 L 相对于 Y_{k-1} 的梯度:∂L/∂Y_{k-1} = (∂L/∂Z_k) * W_k^T
  • 损失 L 相对于参数 W_kb_k 的梯度:
    • ∂L/∂W_k = Y_{k-1}^T * (∂L/∂Z_k)
    • ∂L/∂b_k = ∂L/∂Z_k

通过这种方式,我们可以从输出层开始,逐层反向迭代,计算出所有参数的梯度。


梯度下降的局限性与挑战

我们已经知道如何使用反向传播计算梯度并进行参数更新。本节中,我们来看看梯度下降法本身是否存在局限,以及它是否总能找到最优解。

首先,反向传播这个术语有时被滥用来指代整个梯度下降算法,但它严格来说只是用于计算梯度的一部分。真正的问题是:梯度下降总能找到损失函数的正确最小值吗?

在分类问题中,我们最小化的损失函数(如交叉熵)只是分类错误率的一个可微代理。最小化这个代理损失,并不保证最小化真正的分类错误率。

考虑一个简单的线性可分二分类例子。如果使用感知机学习规则,它能找到一个完美的决策边界。但如果使用梯度下降最小化一个可微损失函数(如逻辑损失),当我们在正确类别一侧很远的地方加入一个“干扰”样本时,梯度下降可能不会为了完美分类这一个样本而大幅改变决策边界,从而容忍一个错误。

  • 感知机规则:具有低偏差(如果解存在,它就能找到),但高方差(对单个数据点非常敏感,解可能剧烈变化)。
  • 梯度下降:具有较高的偏差(可能无法找到完美解),但低方差(对噪声和异常值不敏感,解更稳定)。

因此,使用反向传播梯度训练的神经网络分类器,通常比训练数据上的最优分类器具有更低的方差。我们更偏好这种一致性而非对训练集的完美拟合。

另一个挑战是损失函数的形态。损失曲面可能非常复杂,存在许多局部最小值和鞍点。梯度下降可能会陷入某个局部最小值或鞍点而无法到达全局最小。

  • 鞍点:在某些方向上是极小值,在另一些方向上是极大值,梯度为零。
  • 对于大型网络:理论表明,许多局部最小值在损失值上彼此接近,并且与全局最小值相差不大,陷入一个局部最小值可能并非灾难。

收敛性分析:从凸函数到神经网络

梯度下降法一定会收敛吗?如果收敛,速度如何?由于神经网络是复杂的非线性函数,直接分析其训练动态非常困难。因此,我们通常先分析更简单的凸函数,以期获得一些洞见。

一个凸函数的定义是:连接函数图像上任意两点的线段都位于函数图像上方。凸函数具有良好的性质,例如只有一个全局最小值。

我们首先分析最简单的凸函数:二次函数。一个标量二次函数 f(x) = 1/2 * a * x^2 + b * x + ca > 0)是一个开口向上的抛物线,有唯一最小值。

梯度下降的更新规则是:x_{k+1} = x_k - η * f'(x_k),其中 η 是学习率。

  • 最优学习率:当 η = 1 / f''(x_k) = 1/a 时,梯度下降一步就能到达最小值。
  • 学习率过小η < 1/a,会收敛,但需要更多步数。
  • 学习率过大η > 2/a,可能导致迭代发散。
  • 学习率在临界值η = 2/a,会在最小值两侧无限振荡。

对于非二次函数,我们可以在当前点 x_k 处用泰勒级数进行二次近似:f(x) ≈ f(x_k) + f'(x_k)(x - x_k) + 1/2 f''(x_k)(x - x_k)^2。此时,局部最优步长就是 1 / f''(x_k)。因此,选择合适的学习率至关重要。


多维情况与条件数问题

上一节我们分析了标量函数的收敛性。本节中,我们将其推广到多维情况,这会引入新的挑战——不同方向可能具有不同的最优学习率。

考虑一个轴对齐的二元二次函数:f(x1, x2) = 1/2 * a11 * x1^2 + 1/2 * a22 * x2^2。它的等高线是椭圆。

  • 沿 x1 方向的最优步长是 1 / a11
  • 沿 x2 方向的最优步长是 1 / a22

在标准梯度下降中,我们对所有参数使用统一的学习率 η。这会导致问题:

  • 如果 η 接近 1/a11x1方向最优),但对 x2 方向来说可能太大(如果 a22 很小)或太小(如果 a22 很大)。
  • 结果可能是:一个方向收敛很快,另一个方向收敛极慢甚至发散。

为了保证所有方向都收敛,学习率 η 必须小于最陡峭方向(对应最大 a,即最小最优步长)最优步长的两倍。但这意味着在平坦方向上的学习会非常缓慢。

条件数(最大特征值与最小特征值之比)衡量了曲率在不同方向上的差异程度。条件数越大(椭圆越扁),标准梯度下降的收敛就越慢。为了快速收敛,我们希望条件数接近1(等高线接近圆形)。

对于非二次函数,海森矩阵(二阶导数矩阵)的特征值决定了不同方向的曲率。同样,统一学习率会受到条件数的制约。


学习率调度与进阶优化算法

面对收敛速度慢或可能陷入不良局部最小值的问题,我们有哪些策略?本节介绍两种核心思路:动态调整学习率,以及为不同参数维度使用不同的学习率。

1. 学习率衰减
与其使用固定学习率,不如在训练过程中动态减小它。

  • 思路:初期使用较大的学习率,有助于快速前进并可能跳出尖锐的局部最小值或鞍点;后期减小学习率,有助于稳定地收敛到宽阔的谷底。
  • 方式:可以线性衰减、指数衰减、按步衰减等。

2. 为不同维度适配不同学习率
这是解决条件数问题的根本方法。目标是让每个参数都有自己的“学习步调”。以下是两个经典算法:

  • RProp(弹性反向传播)
    RProp 完全摒弃了梯度的大小,只利用其符号,并为每个权重维护一个独立的步长值。

    • 核心规则
      1. 如果本次梯度的符号与上一次相同,说明方向正确,则增大该权重的步长(乘以一个因子 α > 1)。
      2. 如果本次梯度的符号与上一次相反,说明可能越过了极值点,则减小该权重的步长(乘以一个因子 0 < β < 1),并反转更新方向(或退回上一步)。
    • 优点:非常简单高效,对损失函数形状假设少,常能收敛到宽阔的极小值点。
  • QuickProp(快速传播)
    QuickProp 尝试近似每个参数的二阶导数(海森矩阵的对角线),从而使用类牛顿法的更新。

    • 思路:通过比较连续两次迭代的梯度和参数变化,来估计该维度上的曲率,并据此计算一个自适应的步长。
    • 优点:收敛速度通常快于标准梯度下降。

这些方法的核心思想是解耦不同维度的更新,根据每个参数自身的梯度行为来调整其步长。


动量法及其变体

除了RProp和QuickProp,另一类非常重要的优化算法是动量法。它通过引入“速度”的概念,来平滑梯度更新并加速收敛。

动量法(Momentum)
动量法模拟了物理中的动量概念。它维护一个速度向量 v,该向量是过去梯度的指数加权移动平均。

  • 更新规则
    v_t = γ * v_{t-1} + η * ∇J(θ_t)
    θ_{t+1} = θ_t - v_t
    • γ 是动量系数(通常设为0.9),控制历史梯度的影响。
  • 作用
    • 在梯度方向持续一致的维度上,速度会累积,更新加快。
    • 在梯度方向频繁改变的维度上,梯度会相互抵消,速度减小,更新变慢。
    • 这相当于在各个维度上产生了自适应的学习率,有助于缓解条件数问题,并帮助穿过狭窄的鞍点区域。

Nesterov 加速梯度(NAG)
NAG 是动量法的一个改进版本,它具有“前瞻性”。

  • 更新规则
    v_t = γ * v_{t-1} + η * ∇J(θ_t - γ * v_{t-1})
    θ_{t+1} = θ_t - v_t
  • 区别:NAG 在计算当前梯度时,不是基于当前位置 θ_t,而是基于“如果按照现有动量再前进一步”的预估位置 θ_t - γ * v_{t-1}。这使得更新更具前瞻性,能对梯度变化做出更灵敏的响应,通常比标准动量法收敛更快。

动量法和NAG是深度学习中最常用且有效的优化器之一,我们将在讨论随机梯度下降时进一步探讨。


总结

本节课中,我们一起深入探讨了神经网络训练的核心——梯度下降优化法。

我们首先回顾了反向传播如何计算梯度。然后分析了梯度下降的局限性:它最小化的是真实目标的一个可微代理,因此可能错过某些理论上的最优解,但这往往能带来更稳定、方差更低的模型,这有时是有益的。

接着,我们研究了收敛性问题。从简单的凸二次函数分析中,我们发现学习率的选择至关重要,存在一个最优值。在多维情况下,不同参数方向可能有不同的最优学习率,而标准梯度下降的统一学习率会受制于最坏方向的条件数,导致收敛缓慢。

为了克服这些挑战,我们介绍了多种策略:

  1. 学习率衰减:在训练中动态降低学习率,以平衡探索与收敛。
  2. 维度解耦的优化器
    • RProp:仅使用梯度符号,为每个权重独立调整步长,简单而强大。
    • QuickProp:近似二阶信息,实现更快的收敛。
  3. 动量法:通过累积历史梯度来平滑更新,在一致方向上加速,在振荡方向上减速,有效缓解条件数问题。其变体 Nesterov 加速梯度 通过前瞻性计算进一步提升了性能。

理解这些优化算法的原理和权衡,对于有效训练深度神经网络至关重要。在接下来的课程中,我们将把这些知识应用到更实际的随机梯度下降环境中。

8:训练神经网络(续)📚

在本节课中,我们将继续学习如何训练神经网络。我们将深入探讨增量更新、随机梯度下降、小批量训练以及更高级的优化算法,如动量法和Adam。这些技术对于高效、稳定地训练深度神经网络至关重要。


从批量更新到增量更新 🔄

上一节我们介绍了通过梯度下降最小化损失函数来训练神经网络。这种方法通常需要处理整个训练集来计算平均梯度,然后才更新一次参数,这被称为批量更新

然而,当训练集非常庞大(例如数十亿个样本)时,每次迭代都处理所有数据在计算上是不可行的。此外,批量更新可能收敛缓慢。

本节中我们来看看一种更实用的方法:增量更新。其核心思想是,我们不等待处理完所有数据,而是每看到一个训练样本(或一小批样本)就立即更新一次网络参数。

以下是增量更新的基本步骤:

  1. 随机打乱训练数据的顺序。
  2. 依次遍历每个训练样本(或小批量)。
  3. 对于每个样本,计算其损失相对于网络参数的梯度。
  4. 立即使用这个梯度(乘以学习率)来更新网络参数。
  5. 重复此过程多次(多个轮次)。

这种方法计算效率更高,并且通常能更快地减少训练初期的损失。


随机梯度下降(SGD)🎲

当增量更新的批量大小为1时,我们称之为随机梯度下降。之所以称为“随机”,是因为我们以随机顺序呈现训练样本。

SGD的更新公式可以表示为:
θ = θ - η * ∇J(θ; x_i, y_i)
其中:

  • θ 代表网络参数。
  • η 是学习率。
  • ∇J(θ; x_i, y_i) 是单个训练样本 (x_i, y_i) 的损失梯度。

关键点

  • 随机性:必须随机化样本顺序,否则可能导致循环振荡,无法收敛。
  • 学习率衰减:为了确保最终收敛,学习率必须随着迭代进行而衰减。一个常见的要求是学习率序列 {η_t} 满足:∑ η_t = ∞∑ η_t^2 < ∞。例如,η_t = 1/t 就满足这个条件。
  • 方差大:由于每次更新只基于一个样本,梯度估计的方差很大。这导致损失曲线波动剧烈,但有时能帮助模型跳出局部最优解。

小批量梯度下降 ⚖️

SGD虽然计算快,但梯度估计的高方差可能导致收敛不稳定,甚至收敛到较差的解。一个自然的折中方案是使用小批量梯度下降

在小批量梯度下降中,我们不是处理单个样本,也不是处理整个数据集,而是每次处理一小批(例如32、64、128个)随机样本。

其更新公式为:
θ = θ - η * (1/B) * ∑ ∇J(θ; x_i, y_i), 其中求和针对当前小批量中的B个样本。

优点

  1. 降低方差:相对于SGD,基于一批样本的梯度估计方差更小,更新方向更稳定。
  2. 硬件友好:现代GPU等硬件可以高效并行处理小批量数据,计算速度远快于逐个处理样本。
  3. 收敛更优:通常能比SGD收敛到更好的解,同时比批量更新更快。

在实践中,小批量大小通常设置为你的硬件(如GPU内存)所能支持的最大值,以在降低方差和利用并行计算之间取得最佳平衡。


高级优化算法:动量法与Adam 🚀

无论是SGD还是小批量梯度下降,在复杂损失曲面上都可能遇到问题,例如在峡谷状区域振荡或在平坦区域缓慢前进。高级优化算法旨在解决这些问题。

动量法(Momentum)

动量法的灵感来自于物理学中的动量。它引入了一个速度变量 v,用于累积过去的梯度信息。更新规则如下:
v_t = β * v_{t-1} + (1 - β) * ∇J(θ_t)
θ_{t+1} = θ_t - η * v_t
其中 β 是动量系数(通常为0.9),控制着历史梯度的影响程度。

作用

  • 在梯度方向持续一致的维度上,更新速度会加快。
  • 在梯度方向频繁改变的维度上,更新会因正负抵消而减缓。
  • 这有助于加速收敛并减少振荡。

RMSProp

RMSProp 自适应地调整每个参数的学习率。它为每个参数维护一个梯度平方的指数移动平均值,并以此调整该参数的学习步长。
E[g^2]_t = γ * E[g^2]_{t-1} + (1 - γ) * (∇J(θ_t))^2
θ_{t+1} = θ_t - η / (√(E[g^2]_t + ε)) * ∇J(θ_t)
其中 γ 是衰减率,ε 是一个很小的数(如1e-8)防止除零。

作用

  • 在梯度大的参数方向,降低学习率,避免震荡。
  • 在梯度小的参数方向,提高学习率,加速更新。
  • 解决了不同参数尺度差异大的问题。

Adam(自适应矩估计)

Adam 结合了动量法和RMSProp的思想,可以说是当前最流行、默认的优化器之一。它同时计算梯度的一阶矩(均值,类似动量)和二阶矩(未中心化的方差,类似RMSProp)。
m_t = β1 * m_{t-1} + (1 - β1) * g_t
v_t = β2 * v_{t-1} + (1 - β2) * g_t^2
m̂_t = m_t / (1 - β1^t)
v̂_t = v_t / (1 - β2^t)
θ_{t+1} = θ_t - η * m̂_t / (√(v̂_t) + ε)

作用

  • 兼具两者优点:像动量法一样加速在稳定方向的前进,像RMSProp一样自适应调整每个参数的学习率。
  • 偏差校正:公式中的 m̂_tv̂_t 是对初始零值估计的偏差进行校正,使得初期更新更准确。
  • 通常表现优异:在多种任务上都能实现快速且稳定的收敛。

总结 📝

本节课中我们一起学习了如何更高效地训练神经网络。

  1. 从批量到增量:我们了解到,为了处理大数据集,需要从批量更新转向增量更新。
  2. SGD与小批量随机梯度下降是增量更新的极端形式(批量大小为1),它计算快但方差高。小批量梯度下降是更实用的选择,在计算效率和稳定性之间取得了平衡。
  3. 优化算法的演进:基础的梯度下降存在收敛问题。动量法通过累积历史梯度来加速收敛并抑制振荡。RMSProp通过自适应调整每个参数的学习率来应对不同尺度的梯度。Adam综合了动量法和RMSProp的优点,成为目前广泛使用的强大优化器。

理解这些优化算法的原理,将帮助你在实际项目中根据具体问题选择合适的训练策略,从而更有效地训练你的深度学习模型。

9:神经网络训练技巧 🧠

在本节课中,我们将学习神经网络训练的最后一部分内容,包括损失函数的选择、批归一化、正则化以及Dropout等关键技术。这些技巧对于提升模型性能、加速收敛和防止过拟合至关重要。


损失函数与收敛性 🔍

上一节我们讨论了梯度下降和优化算法。本节中,我们来看看损失函数的选择如何影响网络的收敛。

我们通过最小化损失函数来训练网络。损失函数是网络参数的函数,它计算一批训练样本上的平均差异。我们真正希望最小化的是在整个数据分布上的期望差异。

我们通过梯度下降进行最小化,即沿着梯度的反方向迭代更新参数。理想情况下,损失函数的形状应能引导算法找到最优解。

以下是几种损失函数形状的对比:

  • 左侧函数:形状崎岖不平,不利于优化。
  • 中间函数:在远离最小值时梯度平缓,接近最小值时梯度陡峭,容易在最优解附近震荡。
  • 右侧函数:远离最小值时梯度大,更新步长大;接近最小值时梯度变小,更新步长变谨慎。这是我们期望的二次型(碗状)形状。

我们主要使用两种损失函数:

  • L2损失(均方误差):用于回归任务,预测实数值输出。公式为:L = (y_pred - y_true)^2
  • KL散度(交叉熵损失):用于分类任务,输出是概率。公式涉及对数概率。

一个关键问题是:在分类任务中,为什么使用KL散度而不是形状更好的L2损失?

虽然从网络最终输出(概率)的角度看,L2损失形状更优,但从网络倒数第二层线性输出 Z 的角度看,情况正好相反。KL散度作为 Z 的函数呈现出良好的凸性(碗状),而L2损失作为 Z 的函数则形状不佳。由于 Z 是权重和上一层输出的线性组合(Z = W * y_prev),损失函数关于 Z 的凸性意味着关于权重 W 也是凸的,这更有利于优化。因此,在分类任务中我们使用KL散度。

此外,无论使用L2还是KL损失,损失关于最终线性项 Z 的导数都是预测误差 (y_pred - y_true),这也是误差反向传播名称的由来。


批归一化(Batch Normalization)⚖️

我们使用小批量随机梯度下降来加速训练,其基本假设是每个小批量都能代表整体数据分布。但由于神经网络的非线性特性,即使输入相似,经过几层网络后,不同批次的激活值分布也可能产生很大差异(协变量偏移)。这导致在一个小批量上的优化不能很好地反映整体情况。

批归一化的核心思想是:在每一层的激活函数之前,对小批量数据的分布进行标准化,使其具有零均值和单位方差,然后通过可学习的参数进行缩放和偏移,将其调整到网络该层应有的分布。

具体操作如下:

  1. 计算小批量数据的均值 μ 和方差 σ^2
  2. 对数据进行标准化:u = (z - μ) / sqrt(σ^2 + ε),其中 ε 是为防止除零的小常数。
  3. 对标准化后的数据进行缩放和偏移:z_hat = γ * u + β。其中 γβ 是可学习的参数。

由于均值 μ 和方差 σ^2 依赖于整个小批量的数据,反向传播的计算会变得复杂。我们需要计算损失关于每个原始输入 z_i 的梯度,这涉及到 z_i 通过 μσ^2 对所有标准化输出 u_j 的影响。

批归一化能带来诸多好处:

  • 允许使用更大的学习率。
  • 减少对参数初始化的依赖。
  • 在一定程度上起到正则化的效果。

重要注意事项

  • 批归一化依赖于小批量内的数据多样性。如果一个小批量内所有实例相同或极其相似,梯度可能会消失,阻碍训练。
  • 在推理阶段,我们不再有小批量。通常使用训练阶段所有小批量统计的移动平均值作为推理时的 μσ^2

正则化与权重衰减 🛡️

神经网络是通用函数逼近器,有能力完美拟合训练数据,导致过拟合。例如,一个复杂的网络可能学会一个在训练点上完全正确,但在其他区域极不合理的函数。

过拟合通常由具有过大权重的神经元导致,它们会产生非常陡峭的激活。为了防止过拟合,我们希望对模型的复杂度进行约束。

一种常见的方法是在损失函数中添加一个正则化项,惩罚大的权重。这被称为 L2正则化权重衰减

修改后的损失函数为:L_total = L_data + (λ/2) * ||W||^2
其中 L_data 是原始数据损失,λ 是控制正则化强度的超参数。

这轻微地改变了权重更新规则。新的梯度包含两部分:原始的反向传播梯度加上 λ * W。因此,权重更新公式变为:
W_new = W - η * (∇L_data + λW) = (1 - ηλ)W - η * ∇L_data
可以看到,在每次更新前,权重会先乘以一个小于1的因子 (1 - ηλ),从而实现“衰减”,然后再用梯度进行修正。

此外,网络深度本身也是一种隐式的正则化。对于相同数量的参数,更深的网络往往比更宽的网络学习到更平滑、泛化更好的函数。


Dropout:随机失活 🎲

在深度学习流行之前,集成学习(如Bagging)被证明能提升模型性能。其思想是:从训练数据中随机抽取多个子集,分别训练不同的模型,然后对它们的预测进行投票。

Dropout可以看作是在单个神经网络中实现Bagging的一种高效近似。在训练过程中,对于每个输入样本,我们以概率 p(例如0.5)随机“丢弃”(即暂时移除)网络中的每个神经元。这意味着:

  • 每个输入样本看到的是不同的、更薄的子网络。
  • 每次迭代时,被丢弃的神经元集合都可能不同。

Dropout为何有效?

  1. 集成学习的视角:一个具有 N 个神经元的网络,通过Dropout可以产生 2^N 个可能的子网络。训练过程相当于同时在训练所有这些共享参数的子网络,并在推理时对它们的输出进行平均。
  2. 防止协同适应:没有Dropout时,某些神经元可能变得冗余,或者层与层之间可能形成固定的依赖路径。Dropout迫使每个神经元不能过分依赖于少数其他神经元,必须学习更鲁棒的特征。

实现细节

  • 训练时,对每个神经元应用一个伯努利掩码(0或1)。
  • 反向传播时,只通过未被丢弃的神经元传递梯度。
  • 推理时,我们需要近似所有可能子网络的平均输出。一个简单而有效的做法是:在训练时,将每个神经元的输出乘以 (1-p)(即保留概率);在推理时,则直接使用完整的网络。另一种等价做法是:训练时不做调整,推理时将每一层的权重乘以 (1-p)

Dropout是一种非常强大的正则化技术,但它可能与批归一化产生冲突,因此在实际中需要谨慎搭配使用。


其他训练技巧与总结 📝

除了上述主要技术,还有一些其他有用的训练启发式方法:

  • 早停(Early Stopping):在训练过程中,定期在验证集上评估模型性能。当验证集性能不再提升甚至开始下降时,停止训练,以防止过拟合。
  • 梯度裁剪(Gradient Clipping):当损失函数的梯度变得非常大时,单个批次或样本可能导致参数更新剧烈,破坏训练稳定性。梯度裁剪通过限制梯度向量的范数来解决这个问题。
  • 数据增强(Data Augmentation):通过对训练数据应用随机变换(如旋转、裁剪、颜色抖动等)来人工增加数据量和多样性,这是提升模型泛化能力的有效手段。

本节课总结
在本节课中,我们一起学习了神经网络训练的最后一系列关键技巧。我们探讨了损失函数(特别是KL散度)对优化过程凸性的影响;深入分析了批归一化如何通过标准化层间输出来稳定和加速训练;介绍了通过权重衰减进行正则化来防止过拟合;并详细解释了Dropout这一强大的随机正则化方法及其集成学习的本质。掌握这些技巧对于成功训练深度神经网络至关重要。下一节课,我们将开始学习卷积神经网络。

10:卷积神经网络(CNN)基础 🧠

在本节课中,我们将要学习如何构建一种能够识别模式(如唤醒词或图像中的花朵)的神经网络,并且这种识别不受模式在输入中具体位置的影响。我们将从简单的多层感知机(MLP)扫描方法开始,逐步推导出卷积神经网络(CNN)的核心思想。


概述:从位置敏感到位置不变

我们之前看到,多层感知机(MLP)是通用的函数逼近器,可以建模分类器和回归器。然而,传统的MLP对输入模式的位置非常敏感。例如,一个训练用于识别位于录音开头的单词“welcome”的MLP,可能无法识别位于录音中部的同一个单词,因为输入向量中激活的组件完全不同。

本节中,我们将探讨如何通过“扫描”输入并共享参数,来构建对模式位置不敏感的网络,这正是卷积神经网络的基础。


1. 问题:为什么需要位置不变性?

考虑两个任务:

  1. 语音唤醒词检测:判断一段录音中是否包含“welcome”这个词。
  2. 图像花朵检测:判断一张图片中是否包含一朵花。

如果使用一个大的MLP来处理整个输入(整段录音或整张图片),网络会学习到模式出现的精确位置。如果模式出现在训练时未见过的位置,网络很可能无法识别它,因为新的输入位于一个完全不同的子空间。

核心需求:我们需要的网络应该只关心模式是否存在,而不关心它具体出现在哪里。


2. 解决方案:扫描与共享参数

一个直观的解决方案是:我们不再用一个大网络处理整个输入,而是用一个较小的、能识别目标模式(如单词或花朵)的MLP去扫描输入。

以下是具体步骤:

  1. 定义一个能覆盖目标模式大小(例如,单词长度或花朵尺寸)的MLP。
  2. 将这个MLP像滑动窗口一样,在输入(时间轴或图像空间)上逐步移动。
  3. 在每个窗口位置,MLP都会输出一个值(例如,该位置存在目标的概率)。
  4. 最后,我们收集所有窗口位置的输出,并通过一个聚合操作(如取最大值 max 或使用一个小的MLP)来得到最终判断。

关键洞察

  • 这个“扫描”过程等价于构建一个巨大的、共享参数的MLP
  • 所有扫描窗口使用的子MLP都是完全相同的,即它们共享同一套权重参数。
  • 这种参数共享强制网络学习位置不变的特征

公式/代码描述
假设扫描窗口函数为 MLP_window,输入为 X,步长为 stride,则扫描过程可表示为:

outputs = []
for i in range(0, len(X) - window_size + 1, stride):
    window = X[i:i+window_size]
    outputs.append(MLP_window(window))
final_output = aggregate(outputs) # 例如 max(outputs)

3. 计算重排:从扫描MLP到特征图

上一节我们介绍了通过扫描MLP实现位置不变性的概念。本节中我们来看看如何重新组织计算流程,这能更清晰地揭示CNN的结构。

我们可以改变计算顺序,而不影响最终结果:

  • 原始顺序:在每个位置,取出一个输入窗口,然后让这个窗口数据逐层通过整个MLP。
  • 等价顺序:让第一层的所有神经元先独立工作,各自扫描整个输入,生成一张“特征图”(feature map)。这张图上的每个点对应输入中某个位置,该神经元对该位置的响应。

然后,第二层的神经元不再直接看原始输入,而是联合扫描第一层所有神经元生成的特征图。以此类推。

优势

  • 计算可视化:每一层都在生成对输入的一种新“解读”或特征图。
  • 为参数分布奠定基础:这种视角让我们可以更容易地将识别大模式的责任分布到多个网络层中。

4. 参数分布:构建层次化特征

让第一层神经元直接识别整个花朵(或单词)可能负担过重且不灵活。更好的方法是构建一个层次化的特征检测系统。

分布式检测流程

  1. 第一层:神经元只检测非常小的、基础的局部模式(例如,图像中的边角、纹理;语音中的音素片段)。它们扫描输入,生成多个基础特征图。
  2. 第二层:神经元接收来自第一层特征图的一个小窗口(例如2x2区域)。通过组合这些基础特征(如几个边角),它可以检测更复杂的模式(如一个花瓣的轮廓)。
  3. 更高层:重复此过程。每一层都通过组合下一层的特征,来检测更大、更复杂的模式(例如,由花瓣组成的花朵)。

核心概念

  • 感受野:一个神经元“看到”的原始输入区域的大小。随着层数加深,神经元的感受野会变得越来越大。
  • 权重共享:在每一层内,用于扫描的神经元(滤波器)在不同位置使用的是相同的权重。这极大地减少了参数量。

公式/代码描述
对于第 l 层的卷积操作(简化):
output_map[l][x] = activation( sum( weight[l] * output_map[l-1][x:x+k] ) + bias[l] )
其中 k 是滤波器大小,weight[l] 在该层所有位置共享。


5. 优势:为何使用这种分布式结构?

分布参数不仅是一种不同的组织方式,它带来了实实在在的好处:

  1. 层次化与泛化性:强制网络学习从简单到复杂的层次化特征表示,这与许多自然信号(如图像、语音)的结构相符,通常能带来更好的模型泛化能力。
  2. 参数效率:通过权重共享,参数量大幅减少。例如,一个直接处理8维输入窗口的层需要约 8*D*N1 个参数(D为输入维度,N1为神经元数)。而将其分布到两个各看2维输入的层,仅需约 2*D*N1 + 2*N1*N2 个参数,当输入维度D较大时,节省非常显著。
  3. 计算效率:由于参数共享和滑动窗口的重叠,中间计算结果可以被大量重用,减少了总体计算量。
  4. 训练便利性:我们仍然只需要图像级别的标签(“有花”/“无花”),而不需要标注花朵的精确位置。通过反向传播和共享权重的梯度聚合,网络能自动学习在特征图中定位模式。


6. 术语与扩展概念

最后,我们来统一一下术语,并介绍两个关键概念:

  • 滤波器:每一层中用于扫描的共享权重组,负责检测一种特定的模式。
  • 特征图:一个滤波器在整个输入上扫描后产生的输出矩阵。
  • 展平:在卷积层堆叠结束后,将最终的特征图拉平成一个长向量,以便输入给传统的全连接层或Softmax层进行分类。

过渡到下一步:上述结构已经具备了CNN的雏形。但为了获得更强的鲁棒性,我们还需要引入以下操作:

  • 步长:滑动窗口移动的间隔。步长大于1可以降采样,减少计算量和特征图尺寸。
  • 池化:通常跟在卷积层之后。在一个小区域(如2x2)内进行下采样,常用最大池化(Max Pooling),即取该区域内的最大值。这能提供一定程度的平移不变性(即使目标轻微移动,仍能被捕获),并进一步减少数据维度。

公式/代码描述
最大池化(2x2,步长2):
pooled_output[x, y] = max(input[2x:2x+2, 2y:2y+2])


总结

本节课中我们一起学习了卷积神经网络的核心思想及其演变过程:

  1. 我们从位置敏感性问题出发,提出了用MLP扫描输入并聚合结果的解决方案。
  2. 我们发现这等价于一个共享参数的大型网络。
  3. 通过重排计算顺序,我们得到了“特征图”的概念,每一层都在生成输入的一种新表示。
  4. 将识别大模式的任务分布到多个网络层,形成了层次化的特征检测结构,这大大提升了参数和计算效率,并鼓励学习更泛化的特征。
  5. 我们介绍了感受野滤波器步长池化等关键术语与概念。

本质上,卷积神经网络是一个精心设计的、参数共享的MLP,它通过扫描和层次化处理,高效地实现了对平移不变模式的学习。在接下来的课程中,我们将深入探讨CNN的具体架构和训练细节。

11:卷积神经网络(CNN)📚

在本节课中,我们将学习卷积神经网络(CNN)的起源、核心概念和基本结构。我们将从生物视觉系统的工作原理出发,了解CNN如何受此启发而构建,并最终掌握其核心组件——卷积层和池化层的工作原理。


概述:从猫的视觉到人工神经网络 🐱➡️🧠

卷积神经网络并非凭空产生,其设计灵感源于对哺乳动物(特别是猫)视觉系统的科学研究。本节我们将回顾这段历史,理解视觉信息是如何在大脑中被分层处理的。

1959年,Hubel和Wiesel通过研究猫的视皮层(相当于人类的V1皮层),首次揭示了视觉处理的神经基础。他们发现,视皮层中的单个神经元并不响应视网膜上的所有光点,而只对特定小区域内的特定方向的光线模式(如竖线、横线)产生反应。每个神经元都有一个“感受野”。

他们进一步发现,视皮层中存在两级处理:简单细胞(S细胞) 负责检测特定方向的线性光模式;复杂细胞(C细胞) 则接收多个S细胞的输入,通过类似“最大响应”的机制来清理S细胞的输出,使其对噪声和微小位置变化更加鲁棒。这种S-C细胞层级结构,为后续的复杂模式识别奠定了基础。


从生物模型到计算模型:认知机与神经认知机 🧩

上一节我们介绍了生物视觉的基础。本节中我们来看看研究人员如何将这些原理转化为计算模型。

1980年,福岛邦彦(Kunihiko Fukushima)提出了神经认知机(Neocognitron),这是第一个受Hubel和Wiesel模型启发的计算视觉模型。该模型由多个块(Block) 堆叠而成,每个块内部包含两层:

  • S层:模拟简单细胞,使用可学习的参数来检测特定模式。
  • C层:模拟复杂细胞,执行固定的最大池化操作,对S层的输出进行下采样和鲁棒化处理。

神经认知机的关键创新在于权重共享:在同一个S层平面内,所有神经元使用相同的参数。这意味着无论模式出现在图像的哪个位置,只要该平面负责检测该模式,总会有某个神经元被激活,从而实现了位置不变性。这个模型通过无监督的赫布学习进行训练,能够自动从图像中学习到有意义的语义概念(如数字)。


引入监督学习:LeNet与CNN的诞生 🖼️➡️🔢

神经认知机展示了无监督学习视觉概念的潜力。然而,对于许多实际任务(如分类),我们需要明确的监督信号。本节我们来看看如何将监督学习引入这个框架。

答案很简单:在神经认知机的末端添加一个全连接层(如Softmax分类器),并使用带标签的数据进行训练。通过反向传播算法,整个网络(包括前面的S层参数)都可以被有监督地训练。这就是卷积神经网络(CNN) 的雏形。

Yann LeCun在20世纪80年代末至90年代初的工作(尤其是LeNet网络)是这一方向的里程碑。他成功地将CNN应用于美国邮政服务的手写数字识别(MNIST数据集),取得了巨大成功。在他的模型中:

  • S层被实现为卷积层,通过可学习的滤波器扫描输入。
  • C层被实现为池化层(通常是最大池化),执行下采样。

以下是卷积操作的核心公式。对于一个输入图像(或特征图)X和一个滤波器(核)W,在位置(i, j)的输出(在加偏置b和应用激活函数σ之前)为:

Z[i, j] = ∑_m ∑_n X[i+m, j+n] * W[m, n] + b

然后应用激活函数得到特征图:A[i, j] = σ(Z[i, j])

在代码中,这通常通过高效的张量操作实现。一个简化的概念性伪代码如下:

# 假设 input 形状为 (C_in, H_in, W_in), filter 形状为 (C_out, C_in, K, K)
output = zeros(C_out, H_out, W_out)
for c_out in range(C_out): # 对于每个输出通道(每个滤波器)
    for i in range(H_out):
        for j in range(W_out):
            # 提取输入的感受野区域
            receptive_field = input[:, i:i+K, j:j+K] # 形状 (C_in, K, K)
            # 计算点积并求和(加上偏置)
            output[c_out, i, j] = sum(receptive_field * filter[c_out, :, :, :]) + bias[c_out]
# 随后对 output 应用激活函数,如 ReLU


CNN的核心组件详解 ⚙️

我们已经了解了CNN的历史和整体框架。现在,让我们深入探讨其两个核心组件的细节:卷积层和池化层。

卷积层(Convolutional Layer)

卷积层是CNN中可学习的部分,负责提取特征。

以下是卷积层的关键特性:

  • 滤波器(核):每个滤波器是一个小的权重矩阵(如3x3, 5x5),用于检测一种特定的局部模式(如边缘、纹理)。
  • 通道:输入可以有多个通道(如RGB图像的3个通道)。每个滤波器也会具有相应的深度,同时处理所有输入通道的信息。
  • 输出特征图:每个滤波器扫描整个输入,生成一张输出特征图。输出特征图的数量等于滤波器的数量。
  • 步长(Stride):滤波器每次移动的像素数。步长为1是常见选择,步长大于1会减少输出尺寸。
  • 填充(Padding):在输入周围添加零值像素,通常用于控制输出特征图的大小(例如,保持与输入尺寸相同)。

池化层(Pooling Layer)

池化层通常跟在卷积层之后,用于降低特征图的空间尺寸,增强特征的鲁棒性。

以下是池化层的关键特性:

  • 操作:最常见的是最大池化(Max Pooling),它在一个小窗口(如2x2)内取最大值。也有平均池化等其他形式。
  • 作用
    1. 下采样:减少数据量和计算复杂度。
    2. 平移不变性:使特征对微小的位置变化不敏感。
    3. 扩大感受野:使后续层能够基于更广阔的区域进行判断。
  • 实现细节:在反向传播时,需要记录最大值所在的位置,以便将梯度正确地传回。


特征图尺寸变换:下采样与上采样 🔽🔼

在网络的前向传播过程中,特征图的尺寸会发生变化。理解这些操作对设计网络结构至关重要。

下采样(Downsampling)

下采样用于减少特征图的空间尺寸。

以下是实现下采样的主要方式:

  • 池化操作:最大池化或平均池化在操作时,如果步长大于1,会直接实现下采样。
  • 带步长的卷积:卷积层也可以设置步长大于1,在提取特征的同时进行下采样。
  • 简单丢弃:理论上可以直接每隔S个像素保留一个,但实践中通常与卷积或池化合并。

上采样(Upsampling)

上采样用于增加特征图的空间尺寸,常见于图像分割、生成等任务。

以下是上采样的常见方法:

  • 最近邻插值:复制相邻像素的值。
  • 双线性/双三次插值:基于周围像素进行加权平均。
  • 转置卷积(Transposed Convolution):一种可学习的上采样方法,通过学习的滤波器来“填充”扩大的区域。
  • 反池化(Unpooling):与最大池化对应,将值放回池化前记录的最大值位置,其他位置填零。

一个典型的上采样(如最近邻插值,因子为2)的简单伪代码示例如下:

def upsample_nearest(input, scale_factor=2):
    H, W = input.shape
    new_H, new_W = H * scale_factor, W * scale_factor
    output = np.zeros((new_H, new_W))
    for i in range(new_H):
        for j in range(new_W):
            # 找到在原始输入中对应的位置
            src_i = i // scale_factor
            src_j = j // scale_factor
            output[i, j] = input[src_i, src_j]
    return output

重要关系:为了在多次下采样后不丢失过多信息,网络通常会增加通道数。这样,尽管空间尺寸(高度、宽度)减小,但信息容量(通过更多的特征图)得以保持。当需要上采样恢复细节时,这些丰富的通道信息可以提供必要的上下文。


构建一个典型的CNN分类网络 🏗️

现在,让我们把所有这些组件组合起来,看一个用于图像分类的典型CNN结构示例。

一个经典的顺序可能是:

  1. 输入:RGB图像(3通道)。
  2. 卷积块1
    • 卷积层(如:32个3x3滤波器,填充1,步长1) + ReLU激活。
    • 池化层(如:2x2最大池化,步长2)。
  3. 卷积块2
    • 卷积层(如:64个3x3滤波器,填充1,步长1) + ReLU激活。
    • 池化层(如:2x2最大池化,步长2)。
  4. 卷积块N:可以重复更多次,每次通常增加滤波器数量。
  5. 展平层:将最后的二维特征图转换为一维向量。
  6. 全连接层:一个或多个传统的神经网络层,用于最终分类。
  7. 输出层:Softmax层,输出每个类别的概率。

在这个结构中,随着网络加深,特征图的空间尺寸逐渐减小(由于池化),但通道数逐渐增加。最后的全连接层整合所有高级特征,做出决策。


总结 📝

本节课中我们一起学习了卷积神经网络(CNN)的完整故事:

  1. 生物起源:CNN的设计灵感来源于Hubel和Wiesel对猫视觉皮层的研究,特别是简单细胞(S细胞)和复杂细胞(C细胞)的分层处理机制。
  2. 模型演化:福岛邦彦的神经认知机首次将这些原理计算化,通过无监督学习实现模式识别。LeCun等人通过引入监督学习(在末端添加分类器),创造了现代CNN的雏形。
  3. 核心组件
    • 卷积层:使用可学习的滤波器扫描输入,提取局部特征,通过权重共享实现平移不变性。
    • 池化层(尤其是最大池化):对特征图进行下采样,增强鲁棒性,扩大感受野。
  4. 尺寸管理:通过下采样(池化、带步长卷积)减少计算量,通过增加通道数来补偿信息损失;上采样操作则用于需要恢复空间分辨率的任务。
  5. 网络结构:典型的CNN由交替的卷积层和池化层堆叠而成,最后连接全连接层进行分类。

CNN成功地将空间结构的理解引入了神经网络,使其成为处理图像、视频甚至某些序列数据的强大工具。在接下来的课程中,我们将深入探讨如何训练这些网络,即CNN中的反向传播算法。

12:卷积神经网络(CNN)的训练与反向传播 🧠

在本节课中,我们将学习如何训练卷积神经网络(CNN),重点在于理解其独特的反向传播过程。我们将回顾CNN的基本结构,并详细探讨如何计算卷积层和池化层的梯度。


概述

卷积神经网络通过扫描输入并组合结果来执行模式分类任务,这等效于使用共享参数的神经元进行分层扫描。网络结构包含卷积层、池化层以及最终的多层感知机(MLP)。训练CNN的目标是学习这些层的参数(即滤波器权重和MLP参数),以最小化预测输出与真实标签之间的损失。


CNN结构回顾

上一节我们介绍了CNN的基本组件。本节中,我们来看看其具体结构。

一个典型的CNN由以下部分组成:

  • 卷积层:包含两个阶段。首先,使用滤波器在所有输入通道上进行卷积,生成仿射图。然后,对仿射图逐点应用激活函数,生成激活图。
  • 池化层:间歇性地跟随卷积层,通过对局部区域(如最大值或平均值)进行下采样来减少特征图尺寸并增加一定程度的平移不变性。
  • 重采样层:用于按比例增大或减小特征图尺寸。下采样通过删除行和列实现,上采样通过插入零行和零列实现。
  • MLP分类器:将最后一个卷积/池化层输出的扁平化向量作为输入,进行最终分类。

在训练时,所有卷积层的滤波器和MLP的权重都需要通过反向传播和梯度下降来学习。


反向传播原理

训练神经网络,包括CNN,核心在于通过反向传播计算损失函数相对于所有网络参数的梯度。

对于单个训练样本,其流程如下:

  1. 前向传播:输入通过网络,计算得到输出。
  2. 计算损失:计算网络输出与真实标签之间的差异(损失)。
  3. 反向传播:从输出层开始,向后逐层计算损失相对于每一层输入和参数的梯度。

总体损失是训练集上所有样本损失的平均值。因此,任何参数的梯度也是所有样本对该参数梯度的平均值。

对于CNN,反向传播在MLP部分是常规操作。挑战在于如何通过卷积层和池化层进行反向传播。


卷积层的反向传播

现在我们已经理解了反向传播的整体目标,本节中我们来看看卷积层具体的梯度计算规则。

假设我们已经通过反向传播得到了损失 L 相对于第 l 层输出激活图 Y^l 的梯度 dL/dY^l。我们需要计算:

  1. 损失相对于第 l 层仿射图 Z^l 的梯度 dL/dZ^l
  2. 损失相对于第 l 层滤波器权重 W^l 的梯度 dL/dW^l
  3. 损失相对于前一层(第 l-1 层)输出 Y^{l-1} 的梯度 dL/dY^{l-1}

1. 从激活梯度到仿射图梯度

由于 Y^l = f(Z^l),其中 f 是逐点应用的激活函数(如ReLU),因此梯度计算很简单:

公式
dL/dZ^l = dL/dY^l * f'(Z^l)

这里 * 表示逐元素相乘。

2. 计算对前一层输入的梯度

这是卷积层反向传播的核心。在前向传播中,每个输出仿射图元素 Z^l(x, y) 都是由前一层的一片区域与滤波器进行卷积(点积)得到的。因此,在反向传播时,损失相对于前一层某个输入 Y^{l-1}(a, b) 的梯度,需要累加所有受到该输入影响的输出元素 Z^l 的贡献。

计算规则可以表述为一次卷积操作:

  • 将第 l 层的滤波器在空间维度上进行翻转(上下翻转,左右翻转)。
  • 用这个翻转后的滤波器,对第 l 层的梯度图 dL/dZ^l 进行卷积(需进行适当的零填充,以使输出尺寸与 Y^{l-1} 匹配)。
  • 由于每个输入通道被所有输出通道的滤波器使用,因此需要对所有输出通道的上述卷积结果进行求和,才能得到对单个输入通道的最终梯度。

核心概念:对输入 Y^{l-1} 的梯度计算,等价于用翻转后的滤波器对 dL/dZ^l 进行卷积。

3. 计算对滤波器权重的梯度

类似地,损失相对于滤波器某个权重 W^l(i, j) 的梯度,需要累加所有使用该权重的输出位置 Z^l(x, y) 的贡献。

计算规则也可以表述为一次卷积操作:

  • 将第 l-1 层的输入激活图 Y^{l-1} 作为“输入”。
  • 将第 l 层的梯度图 dL/dZ^l 作为“滤波器”。
  • 对这两者进行卷积(无需翻转),得到的结果就是损失相对于整个滤波器 W^l 的梯度。

核心概念:对滤波器权重 W^l 的梯度计算,等价于用 Y^{l-1}dL/dZ^l 进行卷积。


池化层的反向传播

理解了卷积层的反向传播后,本节中我们来看看池化层的梯度如何计算。池化层没有可学习的参数,其作用是将梯度从输出分配回输入。

最大池化(Max Pooling)

在前向传播中,最大池化从每个局部窗口中选择最大值作为输出。在反向传播时:

  • 梯度只会传递给前向传播中被选为最大值的那一个输入位置。
  • 其他位置的梯度为零。
  • 如果一个输入元素在多个重叠的池化窗口中都曾是最大值,那么它会接收到来自所有这些窗口的梯度之和。

伪代码逻辑

# 前向传播时记录每个输出位置对应的最大值的索引 (max_index)
# backward_pass:
dY_prev[...] = 0 # 初始化输入梯度为0
for each output position (x, y):
    dY_prev[max_index[x, y]] += dL/dY_pool[x, y] # 将梯度累加到原最大值位置

平均池化(Average Pooling)

在前向传播中,平均池化计算每个局部窗口的平均值作为输出。在反向传播时:

  • 梯度被均匀地分配回该窗口内的所有输入位置。
  • 每个输入位置接收到的梯度是输出梯度除以窗口大小(例如,2x2窗口则除以4)。

伪代码逻辑

# backward_pass:
dY_prev[...] = 0 # 初始化输入梯度为0
pool_size = k * k # 例如 k=2
for each output position (x, y):
    for each element in the corresponding input window (i, j):
        dY_prev[i, j] += (1 / pool_size) * dL/dY_pool[x, y] # 均匀分配梯度

总结

本节课中我们一起学习了卷积神经网络(CNN)训练的核心——反向传播。我们回顾了CNN的组成结构,并深入探讨了如何计算卷积层和池化层的梯度。

关键要点如下:

  • 卷积层反向传播:可以巧妙地用卷积运算来实现。对输入的梯度计算涉及与翻转滤波器的卷积,而对滤波器权重的梯度计算则涉及与输入图的卷积。
  • 池化层反向传播:根据池化类型分配梯度。最大池化将梯度路由回最大值所在位置;平均池化将梯度均匀分配回池化窗口内的所有位置。
  • 训练流程:通过前向传播计算输出和损失,然后通过上述规则反向传播梯度,最终使用梯度下降法更新所有参数(卷积滤波器和MLP权重)。

通过掌握这些规则,我们便能够从零开始理解并实现CNN的训练过程。下一节课我们将继续探讨重采样(上/下采样)操作的反向传播。

13:卷积神经网络(CNN)进阶与架构演变 🧠

在本节课中,我们将深入学习卷积神经网络(CNN)的剩余核心概念,包括完整的反向传播规则、处理上/下采样的方法、如何引入其他变换不变性,以及一些经典的CNN架构演变。我们将确保内容简单直白,让初学者能够跟上。


1. CNN反向传播快速回顾 🔄

上一节我们介绍了CNN的基本结构和前向传播。本节中,我们来看看如何通过反向传播来训练CNN。

对于每个训练数据:

  1. 将数据实例通过模型前向传播,得到输出。
  2. 计算模型输出与期望输出之间的差异(损失)。
  3. 使用标准的反向传播规则,将梯度从输出层向后传播,直到CNN之后的第一层MLP。
  4. 将该MLP输入处的梯度“折叠”回来,得到CNN最后一层所有通道的梯度。
  5. 从此处开始,使用我们上一堂课见过的反向传播规则,将导数向后传播。这些规则必须考虑卷积层中的共享计算和池化层的特殊计算。

在每个层,我们需要计算两种导数:

  • 对于卷积层:给定输出激活图 y^L 的导数,我们需要计算其对应的仿射图 Z^L 的导数,然后再计算对前一层输出 y^(L-1) 和滤波器权重 W^L 的导数。
  • 对于池化层:给定输出 y^L 的导数,我们需要计算对输入 y^(L-1) 的导数。

2. 卷积层的反向传播规则 🧮

首先,我们来看如何从激活图的导数得到仿射图的导数。这很简单,因为激活是通过逐点应用激活函数 f 得到的。

公式
y^L[m, x, y] = f(Z^L[m, x, y])

因此,根据链式法则,对仿射项 Z^L[m, x, y] 的导数就是对激活 y^L[m, x, y] 的导数乘以激活函数在该 Z 值处的导数。

代码概念

# dL_dZ 是损失L对仿射图Z的导数
# dL_dY 是损失L对激活图Y的导数
# f_prime 是激活函数的导数
dL_dZ = dL_dY * f_prime(Z)

接下来,我们需要计算对输入 y^(L-1) 和滤波器 W^L 的导数。

  • 对输入 y^(L-1) 的导数:将第 m 个输入通道对应的所有滤波器的第 m 个通道进行转置(即左右、上下翻转),然后与零填充后的输出仿射图导数进行卷积。
  • 对滤波器 W^L 的导数:为了计算第 n 个滤波器的导数,我们将第 n 个输出仿射通道的导数图与所有输入通道进行卷积。

3. 池化层的反向传播规则 📉

我们考虑两种池化:最大池化和平均池化。

  • 最大池化:在前向传播中,我们取一个窗口内的最大值。在反向传播时,只有那个最大值元素对输出有贡献。因此,梯度只被复制回前向传播中被选中的那个最大值位置,窗口内其他位置的梯度为零。
  • 平均池化:在前向传播中,我们取窗口内所有值的平均值。在反向传播时,梯度被均匀地分配回窗口内的所有元素。如果窗口大小为4,那么每个位置将得到输出梯度四分之一的贡献。需要注意的是,由于窗口可能重叠,同一个输入位置可能从多个窗口接收梯度,因此需要进行累加(+=)。

平均池化的反向传播操作可以看作是一种卷积操作。


4. 处理上采样与下采样 🔼🔽

上采样和下采样层会改变特征图的大小。我们之前提到,上采样后通常要接一个卷积,下采样前通常有卷积或池化。

下采样的反向传播

  • 前向传播:从一个大图中,每隔一定步长(stride)取样,忽略其他位置(可视为置零)。
  • 反向传播:我们需要一个与输入同样大小的导数图。首先将其全部置零,然后将输出导数图的值复制到输入图中对应被取样的位置。这本质上是一个上采样操作

上采样的反向传播

  • 前向传播:通过在特征图中插入零值行和列来增大尺寸。
  • 反向传播:只有那些来自原始输入的值(非插入的零)才有梯度。因此,我们丢弃那些对应于插入零位置的梯度,只保留原始位置的梯度。这本质上是一个下采样操作

重要提示:虽然数学上互为逆操作,但由于边界处理方式不同,在代码实现中不能简单地用上采样代码去实现下采样的反向传播,反之亦然。


5. 处理步长大于1的卷积和池化 🏃‍♂️

直接为步长(stride)大于1的卷积推导反向传播规则会很复杂。一个更简单的方法是将其分解

  • 卷积 stride > 1:可以视为一个 stride=1的卷积,后面接一个 下采样层。然后分别对这两个简单操作进行反向传播。
  • 卷积 fractional stride (stride < 1):可以视为一个 上采样层,后面接一个 stride=1的卷积
  • 池化 stride > 1:同样可以分解为 stride=1的池化,后面接一个 下采样层。对于最大池化,由于其本身会记录最大值位置,实现起来仍然简单;对于平均池化,分解则使逻辑更清晰。

从代码角度思考通常更简单:只需反转前向传播循环中的操作顺序和步进逻辑即可。


6. 超越平移不变性:其他变换 ✨

CNN天然具有平移不变性。但我们可能还希望它对旋转、缩放等变换具有不变性。理论上,可以通过对每个滤波器枚举所有希望不变的变换(如旋转45度、翻转),生成相应的变换后滤波器,然后用它们一起扫描输入。

问题

  1. 这会导致输出通道数急剧增加(原始滤波器数 × 变换数)。
  2. 反向传播时,所有变换后滤波器的梯度必须经过相应的逆变换后,再聚合到原始滤波器上。
  3. 计算量巨大。

因此,在实践中,我们很少显式构建这种变换不变的CNN。更常用的方法是数据增强:在训练时,对输入数据随机进行旋转、缩放、裁剪等变换,从而让网络自己从数据中学习到这些不变性。


7. 目标定位与检测 📍

基础的CNN只能判断图像中是否有某类物体。如果我们还想知道物体的位置,该怎么做?

方法:在CNN的末端,除了用于分类的全连接层,我们并行地添加一个回归层

  • 输入:CNN提取的最终特征(展平后)。
  • 输出:一个边界框的坐标,例如 (x1, y1, x2, y2, x3, y3, x4, y4) 代表框的四个角点。
  • 训练:需要带有边界框标注的数据。总损失是分类损失和边界框回归损失的加权和。
  • 应用:不仅可以定位物体,还可以用于姿态估计(预测人体关节位置)。

8. 经典架构:深度可分离卷积 🧩

标准的卷积操作计算成本高。为了提升效率,提出了深度可分离卷积,它分为两步:

  1. 深度卷积:使用一个滤波器(其通道数与输入相同),分别与每个输入通道进行卷积,产生与输入通道数相等的输出通道。这一步是通道分离的。
  2. 逐点卷积:使用多个 1x1 的卷积核,对上一步得到的多通道特征图进行组合,得到最终输出通道。这一步负责通道混合

优势

  • 大幅减少参数和计算量。假设输入有 C_in 个通道,输出需要 C_out 个通道。
    • 标准卷积:需要 C_outC_in 通道的滤波器,计算复杂度约为 C_in * C_out * K^2(K为核大小)。
    • 深度可分离卷积:计算复杂度约为 C_in * K^2 + C_in * C_out
  • 在移动端和资源受限场景中非常有用。

权衡:由于减少了参数和计算,在数据充足的情况下,其表示能力可能略低于标准卷积。


9. CNN学到了什么? 🎨

通过可视化CNN的滤波器,我们可以洞察其工作原理:

  • 底层神经元:学习到类似Gabor滤波器的简单边缘、纹理模式,与哺乳动物视觉皮层V1区的神经元响应惊人地相似。
  • 高层神经元:响应越来越复杂的模式。当输入是人脸时,高层神经元可能对应眼睛、鼻子等部件;当输入是车辆时,则对应车轮、车灯等。最高层的神经元可能响应整个物体(如一张完整的脸、一辆完整的车)。
  • 这证明了CNN确实在学习一种层次化的、由简到繁的特征表示。

10. 训练技巧与成功故事 🏆

训练深度CNN需要一些技巧:

  • 优化器:使用Adam、带动量的SGD等高级优化算法。
  • 正则化:使用批归一化、Dropout等防止过拟合。
  • 数据增强:如前所述,通过对训练图像进行随机变换来扩充数据集,提升模型泛化能力。

CNN发展史上的里程碑

  1. LeNet-5 (1998):早期成功的CNN,用于手写数字识别。
  2. AlexNet (2012):在ImageNet大赛上取得突破性成功,将Top-5错误率从25%以上降至16.4%,开启了深度学习新时代。它使用了ReLU激活函数和多GPU训练。
  3. VGGNet (2014):通过堆叠多个3x3的小卷积核来构建深度网络,结构简洁有效。
  4. GoogLeNet / Inception (2014):提出了Inception模块,在增加网络深度的同时巧妙控制计算量。
  5. ResNet (2015):引入了残差连接,解决了极深网络中的梯度消失/爆炸问题,使得训练数百甚至上千层的网络成为可能,并将ImageNet错误率降至3.57%。
  6. DenseNet等:后续出现了更多高效架构。

如今,CNN及其变体已广泛应用于计算机视觉、语音识别、自然语言处理等诸多领域。


总结 📚

本节课中,我们一起深入学习了卷积神经网络(CNN)的多个进阶主题:

  1. 我们回顾并完善了CNN中卷积层和池化层的反向传播规则。
  2. 我们学习了如何处理上采样、下采样以及步长大于1的卷积,理解了将其分解为基本操作的思想。
  3. 我们探讨了超越平移不变性的其他变换不变性,并理解了数据增强是更实用的方法。
  4. 我们了解了如何使用CNN进行目标定位,即通过添加回归层来预测边界框。
  5. 我们认识了深度可分离卷积这一高效架构,它通过分离空间滤波和通道组合来大幅降低计算成本。
  6. 我们通过可视化看到了CNN从简单边缘到复杂物体的层次化学习过程。
  7. 最后,我们回顾了CNN发展史上的关键架构和成功故事,从LeNet到ResNet,见证了其强大的能力和演变历程。

CNN作为深度学习的基础模型之一,其核心思想——共享权重的局部连接和层次化特征提取——将继续影响着众多人工智能应用的发展。

14:循环神经网络(RNN)📈

在本节课中,我们将学习如何利用神经网络来处理和分析序列数据。我们将从有限响应系统的问题出发,逐步引入能够记忆“无限过去”的循环神经网络(RNN),并探讨其基本结构、工作原理以及训练方法。


序列建模的必要性

在许多实际应用中,我们需要考虑一系列输入来产生一个或多个输出。例如:

  • 语音识别:需要分析整段语音录音才能判断说话内容。
  • 情感分析:需要理解整个句子的上下文才能判断其情感倾向。
  • 机器翻译:需要读完整个英文句子才能输出对应的法语句子。
  • 股票市场预测:需要分析过去一段时间内的股价序列,才能对未来做出投资决策。

这些都属于分类和预测问题,其共同点是需要考虑一个输入向量序列来产生输出。显然,我们可以用神经网络来完成这些任务。


从有限响应到无限响应

上一节我们介绍了卷积神经网络(CNN)在处理序列时的应用。本节我们来看看它的局限性。

卷积神经网络(CNN)的局限性

对于时间序列问题,一个直观的想法是使用一维卷积神经网络(1D-CNN)。网络会查看当前输入以及过去几天的输入(一个固定大小的窗口),然后做出预测。

然而,这种模型是一个有限响应系统。发生在时间 t 的事件只会影响未来 n 天内的输出(n 是网络查看的窗口大小)。一旦窗口滑过该事件,其影响就消失了。

问题:市场趋势往往包含更长的模式(如周模式、月模式、年模式)。为了考虑所有这些长期历史趋势,窗口大小必须不断增加。这会导致模型参数数量急剧增长,且难以捕捉真正长期的依赖关系。

我们真正需要的是一个无限响应系统,能够考虑“无限过去”的信息来做预测。


引入循环:从NARX网络到简单循环网络

为了构建无限响应系统,最直接的想法是将过去的输出反馈给输入。

NARX网络(非线性自回归外生输入网络)

在这种模型中,任何时刻 t 的输出 y(t) 不仅是当前输入 x(t) 的函数,也是前一个时刻输出 y(t-1) 的函数。

公式
y(t) = f( x(t), y(t-1) )

特点

  • 一个在时间 0 的输入会影响所有未来的输出,实现了无限响应。
  • 然而,记忆完全存储在外部的前一个输出值中,而不是在网络内部。

乔丹网络(Jordan Network)

乔丹网络在NARX网络的基础上引入了明确的记忆单元。记忆单元 m(t) 维护一个过去输出的运行平均值,并将其作为额外输入馈送到网络中。

公式(记忆更新):
m(t) = μ * m(t-1) + y(t-1)

特点

  • 记忆单元的结构是固定的(运行平均),没有可学习的参数
  • 在反向传播时,误差不会通过这个记忆单元传播回去。

埃尔曼网络(Elman Network / 简单循环网络)

埃尔曼网络提出了上下文状态的概念,直接将隐藏层的状态克隆并反馈到下一时间步的输入中。

特点

  • 记忆被明确地置于网络内部的隐藏状态中
  • 然而,在训练时,克隆操作阻止了梯度通过时间反向传播。这意味着当前时刻的误差不会用于更新更早时刻的参数,因此它被称为部分循环网络

完全循环神经网络(RNN)

为了解决梯度无法穿越时间的问题,我们引入了状态空间模型,即现代意义上的完全循环神经网络(RNN)。

RNN的核心思想

RNN将记忆直接嵌入到网络的隐藏状态 h(t) 中。隐藏状态递归地基于当前输入和前一时刻的隐藏状态计算得出,它总结了关于整个过去的信息。

前向计算公式

  1. 计算当前隐藏状态:h(t) = f( W_x * x(t) + W_h * h(t-1) + b_h )
  2. 计算当前输出:y(t) = g( W_y * h(t) + b_y )

其中:

  • fg 是激活函数(如 tanh, softmax)。
  • W_x, W_h, W_y 是权重矩阵。
  • b_h, b_y 是偏置项。
  • 初始隐藏状态 h(-1) 可以设置为零向量或作为可学习参数。

特点

  • 完全循环:前向传播时,信息通过隐藏状态在时间上流动;反向传播时,梯度也可以沿着时间反向传播,从而允许当前误差更新很久以前的参数。
  • 隐藏状态 h(t) 是网络的“记忆”。

RRN的展开视图

为了便于理解,我们可以将RNN在时间维度上“展开”,形成一个由多个相同副本组成的深层网络,每个副本对应一个时间步,并且所有副本共享相同的参数W_x, W_h, W_y)。


训练RNN:通过时间反向传播(BPTT)

训练RNN的目标是:给定一个输入序列 X = [x(0), x(1), ..., x(T)] 和对应的目标输出序列 Y_target = [y_target(0), y_target(1), ..., y_target(T)],调整网络参数以最小化损失函数 L,该损失衡量的是整个输出序列 Y 与目标序列 Y_target 之间的差异。

BPTT步骤

  1. 前向传播:将整个输入序列按时间步输入网络,计算每个时间步的隐藏状态 h(t) 和输出 y(t),并计算最终损失 L
  2. 反向传播:从最后一个时间步 T 开始,反向计算损失 L 对每个时间步输出 y(t)、隐藏状态 h(t) 以及所有参数(W_y, W_h, W_x)的梯度。
    • 关键点:由于参数在时间上共享,每个参数的梯度是其在所有时间步上贡献的梯度之和。例如,W_h 的梯度是损失对每个时间步的 z_h(t)h(t) 的输入)的梯度,乘以 h(t-1),然后对所有时间步 t 求和。
  3. 参数更新:使用聚合后的梯度(如SGD)更新网络参数。

核心挑战:损失 L 必须是关于整个输出序列的可微函数。对于序列到序列的任务(如机器翻译),直接定义这样的损失可能比较复杂,我们将在后续课程中看到如何解决(例如使用连接主义时间分类(CTC)或注意力机制)。


双向循环神经网络(Bi-RNN)

在有些任务中,我们不仅能看到过去的信息,还能看到未来的信息。例如,在词性标注中,判断一个词是名词还是动词,既依赖于它前面的词,也依赖于它后面的词。

Bi-RNN的结构

Bi-RNN在每一层包含两个独立的RNN:

  • 前向RNN:按时间顺序(从开始到结束)处理序列。
  • 后向RNN:按逆序时间(从结束到开始)处理序列。

在每一时间步 t,该层的输出是前向RNN的隐藏状态 h_f(t) 和后向RNN的隐藏状态 h_b(t) 的拼接(或求和等其他操作)。

公式(对于某一层):
h(t) = [ h_f(t); h_b(t) ]
其中 h_f(t) = RNN_forward(x(t), h_f(t-1))
h_b(t) = RNN_backward(x(t), h_b(t+1))

Bi-RNN的训练

训练Bi-RNN同样使用BPTT:

  1. 前向传播:分别独立运行前向和后向RNN。
  2. 反向传播:
    • 将损失对层输出 h(t) 的梯度拆分为对 h_f(t)h_b(t) 的两部分。
    • 分别对前向RNN执行从后向前的BPTT。
    • 分别对后向RNN执行从前向后的BPTT(因为其前向计算是逆序的)。
    • 合并来自两个方向的梯度以更新共享的输入层参数。

应用限制:Bi-RNN要求整个输入序列在预测时是可用的,因此不适用于严格的在线流式预测(如实时股票交易),但非常适用于有完整上下文的任务(如文本翻译、文档分类)。


总结 🎯

本节课我们一起学习了循环神经网络(RNN)的核心知识:

  1. 动机:为了对序列数据进行建模,需要能够记忆长期历史信息的无限响应系统。
  2. 演进:从有限响应的CNN,到反馈输出的NARX网络,再到引入内部记忆的乔丹网络和埃尔曼网络,最终发展到完全循环的RNN。
  3. RNN核心:通过递归更新的隐藏状态 h(t) 来承载记忆,其计算公式为 h(t) = f(W_x x(t) + W_h h(t-1) + b)
  4. 训练方法:通过时间反向传播(BPTT),将循环网络在时间上展开,视为一个共享参数的深层网络进行梯度计算和更新。
  5. 双向扩展:为了利用未来上下文信息,引入了双向RNN(Bi-RNN),它同时从前向后和从后向前处理序列,并将结果结合。

RNN为语音识别、机器翻译、时间序列预测等众多序列建模任务提供了强大的基础框架。在接下来的课程中,我们将探讨RNN在实际应用中面临的挑战(如梯度消失/爆炸)及其更先进的变体,如长短期记忆网络(LSTM)和门控循环单元(GRU)。

15:稳定性分析与LSTM 🧠

在本节课中,我们将学习循环神经网络(RNN)的稳定性问题,并探讨长短期记忆网络(LSTM)如何解决这些问题。我们将从分析RNN的长期记忆能力开始,理解其局限性,然后深入探讨LSTM的结构和工作原理。


概述:RNN的威力与挑战

上一节我们介绍了循环神经网络(RNN)的基本概念。本节中,我们来看看RNN在处理特定问题时的强大能力,以及其内在的稳定性挑战。

RNN在处理具有长期依赖关系的时间序列数据时非常有效。但更重要的是,它们甚至能简化一些对传统多层感知机(MLP)来说非常困难的问题。

示例:二进制加法
假设我们需要一个网络来对两个n位二进制数进行加法运算。

  • 如果使用MLP,网络规模需要指数级增长(O(2^(2n))),并且需要看到所有可能的输入组合(2^(2n)个)才能学习。
  • 如果使用RNN,我们可以逐位处理。网络只需要处理当前位的两个输入和一个进位,总共只有8种可能的输入组合。因此,网络规模很小,只需要8个训练实例,并且训练好的网络可以处理任意长度的数字。

示例:奇偶校验问题
判断一个n位二进制输入中1的个数是奇数还是偶数。

  • MLP方案:同样需要指数级规模和训练数据。
  • RNN方案:网络只需要记住前一步的输出和当前输入,只有4种组合。网络规模小,只需4个训练实例,并且能泛化到更长的序列。

由此可见,只要找到问题中的循环结构,RNN就能极大地减少所需的计算量和训练数据。

然而,RNN在记忆方面存在根本性问题。接下来,我们将分析其稳定性。


RNN的稳定性分析 🔬

上一节我们看到了RNN的威力,本节中我们来看看其记忆行为的稳定性。我们将分析隐藏状态如何随时间演变。

线性系统的稳定性

为了简化分析,我们首先考虑一个使用恒等激活函数的线性RNN单元。其隐藏状态更新公式为:
h_t = W_h * h_{t-1} + W_x * x_t

假设在时间0有一个输入x_0,之后没有输入。那么时间t的响应为:
h_t = (W_h)^t * c * x_0

这里的关键是权重矩阵W_h的幂次(W_h)^t。通过对W_h进行特征值分解(W_h = U * Λ * U^{-1}),我们可以分析长期行为。对于大的t值,隐藏状态向量的长度将趋近于最大特征值的t次方:
||h_t|| ≈ |λ_max|^t

由此我们可以得出结论:

  • 如果最大特征值 |λ_max| > 1:响应会爆炸式增长(Explode)。
  • 如果最大特征值 |λ_max| < 1:响应会迅速衰减到零(Vanish)。
  • 只有特征值恰好为1时,信息才能稳定保留。

复数特征值会导致振荡,但总体趋势(爆炸或消失)仍由模长决定。

非线性激活函数的影响

在实际的RNN中,我们使用非线性激活函数(如Sigmoid、Tanh、ReLU)。分析表明:

  • Sigmoid:隐藏状态会迅速饱和到一个固定值,该值仅取决于偏置项,而与初始输入无关。记忆很快丢失。
  • Tanh:比Sigmoid稍好,饱和速度较慢,但长期响应仍然主要取决于权重和偏置,而非输入本身。
  • ReLU:在RNN中效果很差,会导致输出要么爆炸要么消失。

核心结论:在标准RNN中,网络能记住输入信息的时间长短,以及记住什么内容,主要取决于循环权重矩阵的特征值和激活函数的类型,而不是输入数据本身。这对于需要根据输入内容决定记忆时长的任务来说是不理想的。


深度网络中的梯度问题 📉

上一节我们分析了前向传播中的记忆问题,本节中我们来看看反向传播中的梯度问题。这个问题不仅存在于RNN,也存在于任何深度神经网络中。

在反向传播过程中,损失函数相对于网络早期层参数的梯度,需要通过链式法则,连续乘以一系列雅可比矩阵(激活函数的导数)和权重矩阵的转置。

每个乘法步骤都可能改变梯度的大小:

  • 激活函数的雅可比矩阵:通常是对角矩阵,其对角线元素是激活函数的导数(对于Sigmoid ≤0.25,Tanh ≤1,ReLU为0或1)。这通常会缩小梯度。
  • 权重矩阵:具有不同的奇异值。梯度向量乘以权重矩阵转置后,在奇异值>1的方向上会放大,在奇异值<1的方向上会缩小

随着反向传播的深入,梯度在大多数方向上会指数级衰减(梯度消失),在极少数方向上可能急剧增大(梯度爆炸)。这导致:

  1. 早期层的参数几乎得不到有效的梯度更新(梯度消失)。
  2. 训练变得不稳定(梯度爆炸)。
  3. 在RNN中,这意味着远处时间步发生的事件,其误差很难影响早期时间步的参数更新,从而无法学习长期依赖关系。

长短期记忆网络(LSTM) 🏗️

前面我们指出了RNN在记忆和梯度方面的核心问题。本节中,我们来看看如何通过改进网络结构来解决这些问题,这就是长短期记忆网络(LSTM)。

LSTM的核心思想是引入一个记忆单元(Cell State),它像一条传送带,贯穿整个时间序列。信息在这条传送带上可以保持不变地流动,从而避免因权重连乘导致的梯度消失或爆炸。对记忆的修改(写入或擦除)由基于输入内容学习的“门”来控制。

LSTM单元结构

一个LSTM单元在时间步t的运算涉及以下部分:

1. 遗忘门(Forget Gate)
决定要从记忆单元中丢弃哪些信息。它查看当前输入x_t和上一时刻隐藏状态h_{t-1},并输出一个0到1之间的向量,作用于上一时刻的记忆C_{t-1}
f_t = σ(W_f · [h_{t-1}, x_t] + b_f)

2. 输入门(Input Gate)与候选记忆

  • 输入门:决定哪些新信息将被存入记忆单元。
    i_t = σ(W_i · [h_{t-1}, x_t] + b_i)
  • 候选记忆:根据当前输入生成可能的新记忆内容。
    \tilde{C}_t = tanh(W_C · [h_{t-1}, x_t] + b_C)

3. 更新记忆单元
结合遗忘门的决定和输入门的决定,来更新记忆单元:
C_t = f_t ⊙ C_{t-1} + i_t ⊙ \tilde{C}_t
其中表示逐元素相乘。这是一个乘法遗忘加法更新的组合。

4. 输出门(Output Gate)与隐藏状态

  • 输出门:决定记忆单元的哪些部分将输出为隐藏状态。
    o_t = σ(W_o · [h_{t-1}, x_t] + b_o)
  • 隐藏状态:将记忆单元通过tanh激活函数缩放后,由输出门过滤得到。
    h_t = o_t ⊙ tanh(C_t)

LSTM如何解决问题

  • 解决梯度消失:记忆单元C_t的更新包含一条从C_{t-1}C_t直接加法路径f_t ⊙ C_{t-1}项)。在反向传播时,梯度可以沿着这条路径无衰减地流动(当f_t接近1时),这构成了“常数误差传送带”。
  • 控制记忆时长:记忆的保留(遗忘门f_t)和更新(输入门i_t依赖于当前输入x_t和上下文h_{t-1},而不是固定的网络参数。这意味着网络可以学习基于输入内容来决定记忆的时长。
  • 缓解梯度爆炸:虽然LSTM改善了梯度消失,但梯度爆炸可能仍然存在,通常可以通过梯度裁剪等技术来处理。

简化变体:门控循环单元(GRU)

GRU是LSTM一种流行的简化变体。它将遗忘门和输入门合并为一个“更新门”,同时将记忆单元和隐藏状态合并。其参数更少,计算效率更高,且在许多任务上表现与LSTM相当。
z_t = σ(W_z · [h_{t-1}, x_t] + b_z) # 更新门
r_t = σ(W_r · [h_{t-1}, x_t] + b_r) # 重置门
\tilde{h}_t = tanh(W · [r_t ⊙ h_{t-1}, x_t] + b)
h_t = (1 - z_t) ⊙ h_{t-1} + z_t ⊙ \tilde{h}_t


总结 🎯

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

  1. RNN的威力与稳定性问题:RNN能高效处理序列和结构化问题,但其隐藏状态容易因权重矩阵特征值而爆炸或消失,记忆能力受限于网络参数而非输入内容。
  2. 深度网络的梯度问题:深度网络(包括深层RNN)在训练时普遍面临梯度消失或爆炸问题,这使得学习长期依赖关系变得困难。
  3. LSTM的解决方案:LSTM通过引入由输入控制的“门”机制和一个“记忆单元”来解决问题。记忆单元提供了稳定的梯度流路径,而门机制让网络能基于输入决定信息的保留与遗忘,从而实现了可控的长短期记忆。
  4. GRU简介:作为LSTM的简化版本,GRU合并了部分结构,在保持性能的同时提高了计算效率。

LSTM及其变体是处理长序列依赖任务(如机器翻译、语音识别、文档生成)的基石性技术。在接下来的课程中,我们将探讨如何利用这些网络进行序列生成等更高级的任务。

16:循环神经网络(二)与序列对齐 🧠

在本节课中,我们将继续学习循环神经网络,重点探讨如何处理输入与输出序列不同步的任务,例如语音识别。我们将学习如何定义序列间的差异(损失),以及如何在没有明确对齐信息的情况下训练模型。


概述:序列建模的挑战

上一节我们介绍了循环神经网络的基本结构及其在时间序列分析中的应用。本节中,我们来看看一个更复杂的场景:输入与输出序列在时间上并不同步,但顺序相关。例如,在语音识别中,一段音频(长序列)对应一个音素序列(短序列),我们需要将两者对齐。

处理这类任务的核心挑战在于如何定义模型输出序列与目标序列之间的可微损失函数,以便进行梯度下降训练。


不同类型的序列任务

以下是使用循环神经网络可以处理的不同类型的序列任务图示:

  • 一对一(传统MLP):单个输入产生单个输出。也可独立处理时间序列中的每个点,但忽略了时间依赖性。
  • 多对多(同步):每个输入时间步都对应一个输出。适用于词性标注或每日股价预测。
  • 多对一:分析整个输入序列后,产生单个输出。适用于情感分析或句子分类。
  • 多对多(异步):输入序列与输出序列顺序相关但不同步。输出序列更短,且输出时刻不确定。这是本节课的重点,常见于语音识别。
  • 一对多:单个输入产生整个输出序列。适用于图像描述生成。
  • 多对多(编码器-解码器):分析整个输入序列后,生成整个输出序列。适用于机器翻译。

使用传统MLP建模序列

即使使用传统的前馈神经网络(MLP),也可以处理时间序列任务。

方法:将序列中的每个输入向量独立地输入MLP,产生对应的输出。这意味着时间步 t 的分析与其他时间步无关。

训练:损失函数定义为整个输出序列与目标序列之间的差异。通常假设输出与目标在时间上一一对应,并且总序列损失可以分解为各个时间步损失之和。

公式
总损失 L 定义为各时间步损失 L_t 的和:
L = Σ_t L_t(y_t, target_t)
其中,y_t 是模型在时间 t 的输出,target_t 是对应的目标。

这种方法的缺点是忽略了时间序列中的上下文依赖关系。


同步多对多循环网络

这是标准的循环神经网络,每个输入时间步都产生一个输出,且输出与输入同步。

训练:给定输入序列和对应的目标输出序列,计算序列间的损失。同样,通常假设一一对应,并将序列损失分解为各时间步损失之和。

公式
L = Σ_t L_t(h_t, target_t)
其中,h_t 是RNN在时间 t 的隐藏状态(用于计算输出)。

这使得梯度计算变得直接,可以通过时间反向传播(BPTT)来训练网络。


多对一循环网络

在这种模型中,我们分析整个输入序列,只在序列末尾产生一个输出(例如,情感分析中的正面/负面标签)。

训练:在训练时,网络在每个时间步实际上都会产生一个输出,但我们只关心最后一个时间步的输出。损失函数基于最终输出与目标之间的差异计算。

然而,我们也可以利用中间时间步的输出。例如,在语音识别中,如果当前帧代表音素“A”,那么之前的几帧很可能也代表“A”。因此,一个通用的方法是计算所有时间步输出的加权损失和

公式
L = Σ_t w_t * L_t(h_t, target_extension_t)
权重 w_t 取决于具体任务。对于情感分析,w_t 在最终时间步之前可能为0;对于某些语音任务,权重可能均匀分布。

这实际上将多对一任务转换成了加权同步多对多任务。


异步多对多循环网络:问题定义

现在,我们进入本节课的核心:处理输入与输出顺序相关但不同步的任务(如图中第四种类型)。以语音识别为例:

  • 输入:长度为 N 的音频帧序列(例如,100帧)。
  • 输出:长度为 K 的音素序列(例如,音素 [B, E, T]K=3)。
  • 挑战:我们不知道每个音素对应输入序列中的哪一段(即对齐信息未知)。此外,N 通常大于 K

目标:在给定输入序列 X 的情况下,找到最可能的输出符号序列 S
S* = argmax_S P(S | X)
其中,S 的长度未知,但小于或等于 N


一种简单的解码方法及其缺陷

一个朴素的方法是:在测试时,让网络在每个时间步都输出一个音素概率分布,然后简单地选取每个时间步最可能的音素,形成序列。

缺陷

  1. 生成的序列是时间同步的(长度为 N),而我们想要的是更短的、压缩后的序列。
  2. 无法处理重复音素。例如,输出 [A, A, B] 压缩后会变成 [A, B],丢失了“AA”和“A”的区别。
  3. 生成的序列可能不符合语言约束(如构成有效的单词)。

因此,我们需要更智能的方法来从时间同步的网络输出中,解码出正确的、压缩后的符号序列。


对齐:连接压缩序列与输入

对齐是指将较短的输出符号序列映射到较长的输入序列上的过程。

关键概念

  • 压缩序列:目标输出序列,如 [B, E, T]
  • 扩展序列:通过对压缩序列中的符号进行重复,生成一个与输入等长(N)的序列。例如,[B, B, E, E, E, T, T][B, E, T] 的一种可能扩展。
  • 对齐即扩展:一个对齐方案本质上就是一个特定的扩展序列,它指定了每个输入时间步“应该”对应哪个输出符号(或空白)。

同一个压缩序列可以对应许多种可能的扩展/对齐方式。


基于对齐的训练(已知对齐)

如果我们在训练数据中已知每个目标符号在输入序列中的确切位置(即对齐信息),那么训练将变得简单。

方法

  1. 根据对齐信息,将压缩序列 [B, E, T] 扩展为与输入等长的目标序列(例如,在对应位置填上B、E、T,其余位置可视为空白或重复前一符号)。
  2. 现在,问题转化为了同步多对多任务。我们可以使用标准的序列损失,即各时间步损失之和。
  3. 如果使用交叉熵损失,那么总损失就是网络在各个时间步对目标符号赋予概率的负对数之和

公式
L_aligned = - Σ_t log( P(y_t = target_t | X) )
其中 target_t 是扩展后的目标序列在第 t 步的符号。


训练中的核心难题:对齐未知

然而,在现实任务(如语音识别)中,我们通常只有输入音频对应的音素转录文本,而没有音素与音频帧之间的精确对齐信息。

问题:如何在没有对齐信息的情况下计算损失并训练网络?

解决方案思路

  1. 猜测对齐并迭代优化:先初始化一个对齐(例如随机或启发式),训练模型,然后用训练好的模型重新估计更优的对齐,反复迭代。
  2. 考虑所有可能对齐:不只选择“最好”的对齐,而是在训练时考虑所有可能对齐的加权贡献。这就是下一节课要讲的CTC损失

本节课我们重点讲解第一种方法。


动态规划寻找最优对齐

假设我们有一个训练好的模型,对于输入 X,它能输出每个时间步 t 上所有可能符号 s 的概率 P(s | X, t)。现在我们有一个压缩目标序列 [B, E, T],如何找到最可能与之对应的扩展序列(即最优对齐)?

步骤

  1. 构建概率表格:创建一个表格,行是目标序列的符号(每个符号重复其出现的次数,例如 [B, E, T] 就是三行),列是输入时间步 (0...N-1)。单元格 (i, t) 的值是模型在时间 t 对应该行符号的概率。
  2. 定义路径约束:一条合法的对齐路径必须从表格左上角开始,右下角结束。在每一步,路径只能向右向右下移动一格。这保证了路径访问的符号顺序与目标序列 [B, E, T] 一致。
  3. 路径概率:一条路径的概率是其经过的所有单元格概率的乘积。
  4. 目标:找到所有合法路径中概率最大的那条。

由于路径数量是指数级的,我们使用动态规划(类似Viterbi算法)来高效求解。

动态规划算法

  • score(i, t):表示到达表格第 i 行、第 t 列单元格的最大路径概率。
  • 递推关系score(i, t) = P(s_i | X, t) * max( score(i, t-1), score(i-1, t-1) )
    即,当前单元格的最佳路径,要么来自同一行的左边单元格(重复当前符号),要么来自上一行的左上单元格(切换到下一个符号)。
  • 初始化score(0, 0) = P(s_0 | X, 0),其他边界条件设为0或极小值。
  • 回溯:从终点 (K-1, N-1) 开始,根据存储的父节点指针回溯,即可得到最优对齐路径。

该算法的时间复杂度约为 O(K^2 * N),非常高效。


迭代训练流程

结合以上内容,我们可以形成一个完整的迭代训练方案:

  1. 初始化:为训练数据中的每个(输入,压缩目标序列)对,随机或简单启发式地初始化一个对齐。
  2. 训练模型:使用当前对齐(将压缩序列扩展为同步目标序列),以同步多对多方式训练RNN模型。
  3. 重新对齐:使用当前训练好的模型,对每个训练样本,运行动态规划算法,找到当前模型下最可能的对齐。
  4. 判断收敛:如果对齐不再变化或模型性能满足要求,则停止;否则,返回第2步。

性质:每一步(固定模型优化对齐,或固定对齐优化模型)都会增加训练数据在当前模型下的对数似然概率(或降低损失),因此整个迭代过程是收敛的。

注意:该方法严重依赖于初始对齐的猜测,可能陷入局部最优。


总结

本节课中,我们一起学习了如何处理输入输出序列异步的循环神经网络任务:

  1. 我们明确了序列建模的不同类型及其对应架构。
  2. 我们深入探讨了异步多对多任务的核心挑战:序列对齐问题。
  3. 我们学习了如何在已知对齐的情况下进行训练(转化为同步问题)。
  4. 针对对齐未知这一现实难题,我们介绍了一种迭代优化的方法:
    • 核心是利用动态规划(Viterbi算法)在给定模型和目标序列下寻找最优对齐
    • 通过交替进行模型训练对齐更新,逐步提升模型性能。
  5. 我们指出了这种方法的局限性(依赖初始值,可能局部最优),并预告了下节课更强大的解决方案——CTC损失函数,它将考虑所有可能对齐的贡献。

通过本课的学习,你应该对序列对齐的概念、重要性以及一种基础的解决方案有了清晰的理解,为后续学习更高级的序列模型训练技术打下了基础。

17:连接主义时序分类(CTC)🚀

在本节课中,我们将学习一种重要的序列到序列建模方法——连接主义时序分类。我们将探讨如何在没有输入与输出对齐信息的情况下训练模型,并理解其核心算法:前向-后向算法。


概述

序列到序列模型接收一个输入序列(如语音信号),并输出一个长度可能不同的序列(如文本)。在语音识别等任务中,输入与输出之间存在顺序同步性,但没有严格的时间同步性。本节课的核心问题是:当训练数据只提供输入和输出序列,而没有它们之间的对齐关系时,我们如何训练模型?

我们将介绍两种方法:迭代对齐估计法,以及更优雅、更常用的CTC方法——它通过考虑所有可能的对齐来计算期望损失。


序列到序列建模回顾

上一节我们介绍了循环神经网络。本节中,我们来看看一种特殊的序列到序列模型。

在序列到序列建模中,输入序列 X1, X2, ..., XN 经过模型,输出另一个序列 Y1, Y2, ..., YM。例如:

  • 语音识别:语音信号输入,单词序列输出。
  • 机器翻译:一种语言的单词序列输入,另一种语言的单词序列输出。
  • 对话系统:用户语句输入,系统回复输出。

在这些任务中,输入和输出序列的长度通常不同,且输入与输出之间可能没有严格的时间对应关系。

我们之前见过一个具有顺序同步性但非时间同步性的例子:语音识别。输入的每一段大致对应输出的一个符号,顺序一致,但时间点并不精确对应。


训练中的对齐问题

如果我们有训练数据,并且知道输入序列中每个时间点对应输出序列中的哪个符号(即对齐信息),那么训练就很简单。我们可以计算每个时间点的输出概率与目标符号的交叉熵损失,然后求和。

损失函数公式
总损失 = Σ_t ( -log( P(目标符号_t | 输入) ) )

其导数计算也很直接:对于每个时间点t,损失对网络输出概率向量的导数,仅在目标符号对应的维度上为 -1 / P(目标符号),其他维度为0。

核心问题:在大多数情况下,训练数据不提供这种精确的对齐信息。我们只有输入序列和对应的输出符号序列(例如,音频和文本“BEE”),但不知道“B”何时开始,“E”何时结束。


解决方案一:迭代对齐估计

一种直观的解决方法是猜测对齐,然后迭代优化。

以下是具体步骤:

  1. 初始化对齐:为训练数据假设一个初始对齐(例如,均匀分布)。
  2. 训练模型:使用当前对齐信息,像有对齐数据一样训练模型。
  3. 重新对齐:使用训练好的模型,为输入数据找出新的、更可能的最优对齐路径。
  4. 迭代:用新的对齐替换旧的对齐,回到步骤2,重复此过程。

然而,这种方法有一个明显缺点:结果严重依赖于初始对齐的猜测,容易陷入局部最优解。


解决方案二:CTC——考虑所有可能对齐

为了克服对初始值的依赖,CTC方法采用了一种更全局的视角:不固定单一对齐,而是考虑所有可能的对齐,并计算期望损失

构建对齐图

首先,给定输入和输出序列(如“BEE”),我们可以根据网络在每个时间点输出的概率,构建一个对齐图(或称为篱笆网络)。图的每一行代表输出序列的一个符号(包括一个特殊的“空白”符号,稍后解释),每一列代表一个输入时间步。

图中的每条从左到右的路径(每次向右或向右下移动)都代表一种可能的对齐方式。每条路径的概率是路径上各节点概率的乘积。

计算期望损失

我们不只使用概率最高的那条路径,而是考虑所有路径。我们希望最小化的损失函数是所有可能对齐路径的期望损失

期望损失公式
L = Σ_{所有路径A} [ P(A) * (-Σ_t log( P(符号_t | A, 输入) ) ) ]

根据期望的线性性质,这可以转化为:
L = -Σ_t Σ_{所有符号s} [ γ_t(s) * log( y_t(s) ) ]

其中:

  • y_t(s) 是网络在时间t预测符号s的概率。
  • γ_t(s) 是关键:它表示在给定输入和整个输出序列的条件下,所有对齐路径在时间t经过符号s节点的概率之和,即该节点的后验概率。

前向-后向算法计算 γ

那么,如何高效计算每个节点(时间t,符号s)的 γ_t(s) 呢?这正是CTC的核心——前向-后向算法

我们将节点的概率分解为两部分:

  1. 前向概率 α_t(s):从开始到时间t、符号s节点的所有路径的概率之和。
  2. 后向概率 β_t(s):从时间t、符号s节点到结束的所有路径的概率之和。

那么,经过该节点的总概率就是 α_t(s) * β_t(s)。对所有节点进行归一化(除以所有节点的αβ乘积之和),就得到了我们需要的后验概率 γ_t(s)

前向算法(动态规划)

  • 初始化:α_0(起始节点) = 该节点概率。
  • 递推:α_t(s) = Σ_{父节点p} [ α_{t-1}(p) * y_t(s) ]
  • 从左上计算到右下,最终得到结束节点的α,即整个输出序列的概率。

后向算法(动态规划)

  • 初始化:β_T(结束节点) = 该节点概率。
  • 递推:β_t(s) = y_t(s) * Σ_{子节点c} [ β_{t+1}(c) ]
  • 从右下计算到左上。

有了α和β,γ_t(s) = (α_t(s) * β_t(s)) / (Σ_{所有节点} α * β)


损失梯度计算

我们的损失是 L = -Σ_t Σ_s γ_t(s) log( y_t(s) )。我们需要计算损失L对网络输出 y_t(s) 的梯度,以便反向传播。

y_t(s) 求导,公式包含两项。但可以证明,在最大似然估计的框架下,可以忽略其中复杂的一项,得到一个非常简洁的梯度公式:

梯度公式
∂L / ∂y_t(k) = - (γ_t(k) / y_t(k))

其中,γ_t(k) 是符号k在时间t的后验概率之和(如果k在输出序列中出现多次,则需累加多个节点的γ)。

这意味着,对于每个时间步t,我们只需将计算得到的γ_t向量除以对应的网络输出概率向量y_t,取负号,就得到了损失对网络输出的梯度。这个梯度可以轻松地反向传播回去训练网络。


处理重复字符:引入空白符(Blank)

上述讨论隐含了一个问题:如果输出序列有重复字符(如“BEETLE”中的‘E’),如何区分一个长音“E”和两个连续的短音“E”?

CTC的巧妙设计是引入一个特殊的空白符(通常用“-”表示)。它被视为一个“无声”的符号。

  • 在最终输出中,所有空白符会被移除。
  • 连续相同的字符之间必须有空白符隔开,否则它们会被合并成一个。
  • 不同字符之间可以有或没有空白符。

例如,对齐路径 “B - E E - T - L E” 在移除空白符和合并连续相同字符后,会变成 “B E T L E”。

引入空白符后,对齐图的构建规则需要调整,但前向-后向算法的核心思想不变,只是每个节点的父节点/子节点数量可能变为2个或3个。


解码:寻找最优输出序列

训练完成后,在推理(解码)阶段,我们的目标不再是找最优对齐路径,而是找最优的输出符号序列

贪婪解码:每个时间步选择概率最高的符号,然后移除空白符、合并重复字符。这种方法简单快速,但可能是次优的,因为它没有考虑不同对齐路径可以对应同一个输出序列。

改进解码:我们需要找到那个所有可能对齐路径概率之和最大的输出序列。这可以通过束搜索来近似实现:

  1. 在每一步,保留概率最高的若干条候选序列(beam)。
  2. 对这些候选序列进行扩展(添加新符号或空白符)。
  3. 重复直到序列结束,选择最终概率最高的序列。

束搜索在计算复杂度和效果之间取得了良好平衡。


总结

本节课我们一起学习了连接主义时序分类(CTC),这是一种用于训练输入输出有序但非严格时间对齐的序列到序列模型的强大方法。

核心要点

  1. 问题:训练序列模型时缺乏输入与输出的对齐信息。
  2. 思路:不猜测单一对齐,而是计算所有可能对齐的期望损失。
  3. 核心算法前向-后向算法,用于高效计算每个时间步、每个符号的后验概率 γ_t(s)
  4. 梯度计算:损失梯度有一个简洁形式:-γ_t(s) / y_t(s)
  5. 关键技巧:引入空白符来处理输出序列中的重复字符。
  6. 解码:使用束搜索来寻找整体概率最高的输出序列,而非单步最优。

CTC是语音识别、手写识别等任务中的基石性技术,它优雅地解决了序列学习中一个根本性的对齐难题。

18:序列到序列网络与语言模型 🧠

在本节课中,我们将学习序列到序列网络,特别是处理输入与输出序列之间没有顺序对应关系的问题。我们还将深入了解语言模型,以及如何使用循环神经网络来建模和生成语言。


概述

我们将从语言模型的基础概念开始,探讨如何将单词表示为向量,并介绍词嵌入的概念。接着,我们会深入讲解序列到序列模型的结构,包括编码器和解码器,并解释如何使用束搜索来生成最优的输出序列。最后,我们会讨论模型的训练方法及其在机器翻译、图像描述等任务中的应用。


语言模型基础

上一节我们介绍了序列到序列问题的两种类型。本节中,我们来看看什么是语言模型。语言模型的核心是建模语言中符号序列的概率分布。换句话说,它告诉我们哪些单词序列更可能出现,哪些不太可能出现。

一个语言模型通常通过预测给定历史单词后的下一个单词来工作。如果我们能完美地预测下一个单词的概率,那么我们也就掌握了整个句子序列的概率分布。

单词的数学表示

为了将单词输入模型,我们需要将其转换为数学形式。最直接的方法是使用独热编码。

  • 独热向量:对于一个包含N个单词的词汇表,每个单词被表示为一个N维向量。该向量中,仅在对应单词在词汇表中索引的位置为1,其余位置均为0。
    • 公式:如果词汇表为 [“artwork”, “Erin”, ...],则 “artwork” 可表示为 [0, 1, 0, 0, ...]

然而,独热编码存在维度高且稀疏的问题。为了解决这个问题,我们引入词嵌入。

  • 词嵌入:通过一个投影矩阵 P 将高维的独热向量映射到一个低维的稠密向量空间。这个稠密向量就是词嵌入。
    • 公式:embedding = P * one_hot_vector
    • 这个投影矩阵 P 可以通过模型学习得到,使得语义相近的单词在嵌入空间中的距离也更近。

循环神经网络语言模型

了解了词嵌入后,我们来看看如何使用循环神经网络构建语言模型。与仅基于固定窗口历史预测的模型不同,RNN能够利用整个历史序列的信息。

在RNN语言模型中,在任意时刻 t,模型计算的是给定之前所有单词 (w0, w1, ..., w_{t-1}) 后,下一个单词 w_t 的概率分布。

我们可以用训练好的语言模型来生成文本。生成过程是自回归的:给定一个起始标记(如<SOS>),模型预测第一个单词的概率分布,从中采样一个单词,然后将这个单词作为输入反馈给模型,用于预测下一个单词,如此循环,直到生成结束标记(如<EOS>)。

以下是生成文本的关键步骤:

  1. 初始化输入为起始标记 <SOS>
  2. 将当前输入和隐藏状态输入RNN,得到下一个单词的概率分布和新的隐藏状态。
  3. 从概率分布中采样一个单词作为输出。
  4. 将采样的单词作为下一步的输入。
  5. 重复步骤2-4,直到输出结束标记 <EOS>

序列到序列模型

现在,我们回到核心主题:处理没有顺序对应关系的序列到序列任务,例如机器翻译。解决这类问题的标准架构是编码器-解码器模型。

模型结构

该模型分为两部分:

  1. 编码器:一个RNN,用于处理整个输入序列 (x1, x2, ..., xM)。在读取完所有输入后,编码器最终隐藏状态 h_M 旨在捕获输入序列的全部语义信息。
  2. 解码器:另一个RNN,作为一个条件语言模型,负责生成输出序列 (y1, y2, ..., yT)。解码器的初始隐藏状态被设置为编码器的最终隐藏状态 h_M,其第一个输入是起始标记 <SOS>

解码器在每一步 t 的工作是:基于编码器信息(通过初始隐藏状态传递)以及已生成的前 t-1 个输出单词 (y1, ..., y_{t-1}),来预测第 t 个输出单词 y_t 的概率分布。

序列概率

对于一个给定的输出序列 Y = (y1, y2, ..., yT),其条件概率可以分解为:
P(Y | X) = P(y1 | X) * P(y2 | X, y1) * ... * P(yT | X, y1, y2, ..., y_{T-1})
这正是解码器每一步计算的条件概率的乘积。


解码与束搜索

在生成输出时,我们的目标是找到使 P(Y | X) 最大的序列 Y。贪婪解码(每一步只选择概率最高的单词)通常不是最优解,因为当前最优选择可能导致后续整体概率降低。

理论上,我们需要搜索所有可能的输出序列,但这在计算上是不可行的。因此,我们采用束搜索作为一种高效的近似方法。

以下是束搜索(束宽 k=2)的基本步骤:

  1. 在解码第一步,保留概率最高的 k 个候选单词。
  2. 对于这 k 个候选,分别展开下一步,得到 k * V 个可能的二元序列(V是词汇表大小)。
  3. 计算这些二元序列的累积概率(第一步概率 * 第二步条件概率),并从中保留总体概率最高的 k 个序列。
  4. 重复此过程,每一步都基于累积概率保留 top k 个候选序列。
  5. 当一个候选序列生成了 <EOS> 标记时,它被视为一个完整的假设,不再继续扩展。
  6. 当达到最大生成长度或足够数量的完整假设时,选择累积概率最高的完整序列作为最终输出。

束搜索在探索更多可能性和计算效率之间取得了平衡。


模型训练:教师强制

训练编码器-解码器模型面临一个挑战:在训练初期,解码器自身生成的质量很差,如果将其输出反馈回去,会导致错误累积,难以与真实目标序列计算有效的损失。

为了解决这个问题,我们使用教师强制技术。在训练时,我们不将解码器上一步的预测输出作为下一步的输入,而是将真实的目标序列中的单词作为输入。这样,无论模型当前预测能力如何,每一步的输入都是正确的,从而可以稳定地计算预测分布与真实下一个单词之间的损失(如交叉熵),并进行反向传播。

虽然这是一种“作弊”,但它对于模型的稳定训练至关重要。


应用与总结

本节课我们一起学习了序列到序列模型的核心框架。编码器-解码器结构非常灵活,其编码器可以处理各种输入,解码器可以生成各种序列,因此应用广泛:

  • 机器翻译:将一种语言的句子(输入序列)翻译成另一种语言的句子(输出序列)。
  • 语音识别:将语音信号(输入序列)转换为文字(输出序列)。
  • 文本摘要:将长文章(输入序列)压缩为简短摘要(输出序列)。
  • 图像描述:将图像(通过CNN编码为特征向量作为输入)用文字描述出来(输出序列)。

然而,基本的编码器-解码器模型有一个局限性:编码器需要将整个输入序列的信息压缩到一个固定维度的最终隐藏状态中,这在处理长序列时会造成信息瓶颈,导致信息丢失或稀释。在下一节课中,我们将探讨如何通过注意力机制来解决这个问题。

19:序列到序列模型进阶 🧠

概述

在本节课中,我们将学习序列到序列模型的进阶内容,特别是注意力机制及其核心变体。我们将探讨简单编码器-解码器模型的局限性,并深入理解如何通过注意力机制来解决信息稀释和上下文依赖问题,最终引出“自注意力”和Transformer架构的基本思想。


简单序列到序列模型的局限性

上一节我们介绍了基础的序列到序列模型。本节中,我们来看看该模型存在的主要问题。

该模型包含一个编码器和一个解码器。编码器处理输入序列并生成一个最终的隐藏状态向量,该向量被传递给解码器作为其初始状态,以生成输出序列。

这个简单框架存在两个核心问题:

  1. 解码器侧的信息稀释:解码器的隐藏状态会随时间推移而递归更新。当生成长序列输出时,初始的编码器隐藏状态信息会逐渐被“稀释”或遗忘,导致后续生成的词与原始输入的相关性减弱。
  2. 编码器侧的信息过载:整个输入序列的信息被压缩到一个单一的向量中。对于长序列,较早的输入信息可能会在递归过程中被遗忘,导致该向量存在“近期偏置”,难以有效保留所有细节。

注意力机制的引入

为了解决上述问题,我们引入注意力机制。其核心思想是:解码器在生成每一个输出词时,都应能够直接“查看”编码器处理输入序列时产生的所有隐藏状态,而非仅仅依赖最后一个状态。

以下是注意力机制的工作流程:

  1. 计算注意力权重:在解码器的每个时间步 t,我们计算一个权重向量。权重 α_ti 表示在生成第 t 个输出词时,应给予第 i 个输入隐藏状态 h_i 的关注程度。
  2. 生成上下文向量:使用这些权重,计算编码器所有隐藏状态的加权和,得到一个上下文向量 c_t。公式如下:
    c_t = Σ_i (α_ti * h_i)
  3. 结合上下文生成输出:将当前时间步的上下文向量 c_t 与解码器上一时间步的输出(或目标词)一起,输入到解码器网络中,以生成当前时间步的输出概率分布。

关键点:注意力权重是动态计算的,是解码器上一时间步的隐藏状态 s_{t-1} 和编码器各个隐藏状态 h_i 的函数。一个常见的计算方式是使用加性注意力或点积注意力:
e_{ti} = a(s_{t-1}, h_i)
然后通过softmax函数将 e_{ti} 归一化为概率分布,得到 α_ti
α_ti = softmax(e_{ti})

这使得模型能够学会在生成不同输出词时,灵活地将注意力聚焦于输入序列的不同部分。


键值对注意力与多头注意力

基础的注意力机制可以进一步优化。

键值对注意力:将编码器的每个隐藏状态 h_i 拆分为一个和一个

  • :用于计算注意力权重,可以理解为内容的“粗糙”索引或类别信息。
  • :用于生成上下文向量,包含需要传递给解码器的“精细”内容信息。
  • 查询:解码器状态 s_{t-1} 经过变换后称为查询,用于与所有键进行比较以计算权重。

其工作流程更新为:

  1. 使用查询和所有计算原始注意力分数 e_{ti}
  2. e_{ti} 归一化为权重 α_ti
  3. 使用权重 α_{ti}进行加权求和,得到上下文向量 c_t

多头注意力:单一的注意力机制可能只关注一种类型的关系(例如语法或语义)。多头注意力并行运行多个独立的键值对注意力“头”。每个头学习在不同的表示子空间中关注输入的不同方面。最后,将所有头的输出拼接或组合起来,形成最终的上下文表示。这极大地增强了模型的表示能力。


训练与解码策略

训练(教师强制):在训练时,我们已知目标输出序列。为了避免在模型训练初期因输出质量差而无法对齐,我们采用“教师强制”策略:在解码器的每个时间步,我们将真实的目标词(而非模型上一时间步的预测词)作为输入喂给解码器。这确保了训练过程的稳定性,并允许我们计算每个时间步预测分布与真实词之间的交叉熵损失,从而进行有效的反向传播。

解码(束搜索):在推理(生成)时,我们的目标是找到整体概率最高的输出序列。由于输出是自回归的(每个词影响后续词),穷举所有序列不可行。因此,我们使用束搜索

  1. 在第一个时间步,保留概率最高的 K 个候选词(K 为束宽)。
  2. 在后续每个时间步,对每个候选序列进行扩展,考虑所有可能的下一词,但只保留所有扩展路径中总概率最高的 K 个序列。
  3. 重复此过程,直到生成结束符或达到最大长度,最终选择概率最高的序列作为输出。

从注意力到自注意力与Transformer

注意力机制的成功引出了一个更深层的问题:既然解码器可以通过注意力直接访问编码器的所有状态,那么编码器本身是否还需要递归结构来聚合信息?

自注意力:答案是可以移除递归。我们可以在编码器内部使用自注意力机制。对于输入序列中的每个位置,我们将其表示(如词嵌入)转换为查询、键、值。然后,该位置的查询会与序列中所有位置(包括自身)的键进行计算,得到注意力权重,再对值进行加权求和,从而更新该位置的表示。这样,每个词的表示都融入了整个序列的上下文信息。

位置编码:自注意力本身不考虑词序。为了注入顺序信息,我们在输入词嵌入上添加位置编码。位置编码是一个与词嵌入维度相同的向量,其值根据位置通过特定函数(如正弦和余弦函数)生成,使得模型能够感知词与词之间的相对或绝对位置关系。

掩码自注意力:在解码器中,生成过程是顺序的,当前词不应“看到”未来的词。因此,在解码器的自注意力层中,我们会使用掩码,确保在计算位置 i 的注意力时,只允许其关注位置 1i 的输入,而将未来位置屏蔽。

Transformer架构:结合了多头自注意力、位置编码、前馈神经网络和残差连接等组件,完全摒弃了递归,构建了强大的编码器-解码器架构,即著名的Transformer模型。其编码器由多层多头自注意力和前馈网络堆叠而成,解码器则由掩码多头自注意力和编码器-解码器注意力层堆叠而成。


总结

本节课中我们一起学习了序列到序列模型的进阶知识。我们从简单编码器-解码器模型的局限性出发,深入探讨了注意力机制如何通过动态计算权重、聚焦相关输入部分来解决信息瓶颈问题。我们进一步了解了键值对注意力、多头注意力等增强机制。接着,我们探讨了使用教师强制进行训练和使用束搜索进行解码的策略。最后,我们看到了如何将注意力机制应用于编码器自身,形成自注意力,并由此引出了完全基于注意力、摒弃递归的Transformer架构的核心思想,这为后续学习大语言模型奠定了重要基础。

20:Transformer与神经架构 🧠

在本节课中,我们将学习Transformer架构的基本原理及其在自然语言处理、计算机视觉等领域的应用。Transformer是现代深度学习中最核心的架构之一,理解其设计思想对于掌握当前最先进的模型至关重要。


1. 为什么需要学习Transformer?🤔

几乎所有当今深度学习的顶尖系统都基于Transformer架构。例如,GPT和GPT-4等大型语言模型、文本到图像或文本到视频生成模型,以及蛋白质结构预测模型AlphaFold都使用了Transformer。Transformer具有几个关键优势:

  • 灵活性与通用性:能够处理文本、图像、音频等多种模态的数据。
  • 良好的扩展性:随着模型参数和数据量的增加,会展现出涌现能力上下文学习能力。
  • 参数高效训练:可以通过仅微调预训练模型的一小部分参数来适应下游任务,在训练和推理上都非常高效。

2. Transformer基础架构 🏗️

上一讲我们介绍了注意力机制,本节中我们来看看如何将其构建成完整的Transformer模型。Transformer的核心是多头注意力机制。

2.1 架构概览

一个标准的Transformer块通常包含以下组件:

  • 词元化与嵌入层:将原始输入(如文本)转换为连续的向量表示。
  • 位置编码:为输入序列注入位置信息,因为自注意力本身是排列不变的。
  • 多头自注意力层:模型的核心,用于捕捉序列内部的依赖关系。
  • 前馈网络层:一个简单的多层感知机,用于处理注意力层的输出。
  • 残差连接与层归一化:用于稳定深度网络的训练。
  • 输出投影层:根据任务(如分类、生成)产生最终输出。

2.2 词元化与嵌入

Transformer无法直接处理原始文本字符串。首先需要进行词元化,将句子分割成子词或词元,并为每个词元分配一个索引。

接着,通过嵌入层将这些离散的索引转换为连续的向量。嵌入层本质上是一个查找表或一个线性层。假设词元索引为 i,词汇表大小为 V,嵌入维度为 D,那么嵌入操作可以表示为:

嵌入向量 = 嵌入矩阵[i, :]

其中,嵌入矩阵 是一个可学习的参数矩阵,形状为 (V, D)

2.3 多头自注意力

这是Transformer最核心的部分。自注意力机制允许序列中的每个位置关注序列中的所有其他位置。

计算过程如下:

  1. 对输入 X(形状 L×D,L为序列长度)进行三次不同的线性变换,得到查询Q、键K、值V矩阵:
    Q = XW_Q, K = XW_K, V = XW_V
  2. 计算注意力权重:注意力权重 = softmax(QK^T / sqrt(d_k)),其中 d_kK 的维度,缩放是为了稳定梯度。
  3. 计算加权和输出:输出 = 注意力权重 * V

多头注意力是将 D 维的输入特征分割成 h 个头,在每个头上独立进行上述的自注意力计算,最后将结果拼接起来。这样可以让模型同时关注来自不同表示子空间的信息。

注意力掩码:在训练解码器或进行因果预测时,需要防止当前位置关注到未来的位置。这时会使用因果掩码。而编码器通常使用双向掩码,允许每个位置关注序列中的所有位置。

2.4 位置编码

自注意力机制本身不考虑输入的顺序。为了引入序列的顺序信息,需要添加位置编码。

绝对位置编码:最原始的方法是使用正弦和余弦函数生成固定的位置向量,然后加到输入嵌入上。公式如下:
PE(pos, 2i) = sin(pos / 10000^(2i/D))
PE(pos, 2i+1) = cos(pos / 10000^(2i/D))
其中 pos 是位置,i 是维度索引。

相对位置编码与旋转位置编码:为了更好泛化到更长的序列,现代模型(如LLaMA)常使用相对位置编码或旋转位置编码。它们不是将位置信息加到输入上,而是直接修改注意力权重计算过程,使其能感知词元间的相对距离。

2.5 前馈网络、残差连接与归一化

  • 前馈网络:通常是一个简单的两层MLP,中间有一个激活函数(如ReLU或GELU)。公式为:FFN(x) = max(0, xW_1 + b_1)W_2 + b_2。研究表明,前馈网络存储了大量的世界知识。
  • 残差连接与层归一化:每个子层(自注意力、前馈网络)周围都应用了残差连接,然后是层归一化。这极大地促进了深度网络的训练。有两种主要配置:
    • 后归一化输出 = LayerNorm(x + 子层(x))。更难训练,但性能可能更好。
    • 前归一化输出 = x + 子层(LayerNorm(x))。训练更稳定,是现代架构的默认选择。

3. Transformer在自然语言处理中的应用 📚

在NLP中,Transformer主要有三种架构变体,适用于不同任务。

3.1 三种主要架构

以下是三种主要的Transformer架构及其典型应用:

  • 编码器-解码器架构:如T5模型。编码器处理输入序列,解码器基于编码器的输出和已生成的部分自回归地生成目标序列。适用于机器翻译、摘要等序列到序列任务。
  • 仅编码器架构:如BERT模型。仅使用编码器部分,具有双向注意力,能充分理解整个输入上下文。适用于文本分类、命名实体识别等理解类任务。通常使用掩码语言建模进行预训练。
  • 仅解码器架构:如GPT系列模型。仅使用解码器部分,采用因果注意力掩码,用于自回归生成。预训练目标是简单的下一个词元预测。当模型规模足够大时,会展现出强大的零样本和上下文学习能力。

3.2 高效的注意力机制

标准自注意力的计算复杂度是序列长度的平方级 O(L²),这对于长序列是个瓶颈。研究者提出了多种高效注意力方法,如线性注意力,它通过将softmax操作近似为核函数,并利用矩阵乘法的结合律,将复杂度降低到 O(L)


4. Transformer在计算机视觉中的应用 👁️

Transformer同样革新了计算机视觉领域。

4.1 视觉Transformer

ViT是将Transformer直接应用于图像分类的开创性工作。其关键是将图像分割成固定大小的图像块,并将每个图像块线性投影为一个向量(称为“词元”)。然后,这些图像块词元像NLP中的词元一样输入到标准的Transformer编码器中。

与CNN的对比:CNN具有归纳偏置(局部性、平移等变性),使其在数据较少时也能有效学习。而ViT缺乏这种偏置,必须从海量数据中学习这些关系。因此,ViT在超大规模数据集(如JFT-300M)上预训练后,才能超越CNN的性能。

4.2 高效的视觉Transformer变体

为了引入局部性偏置并提升效率,出现了许多ViT变体:

  • Swin Transformer:引入窗口注意力移位窗口机制,在非重叠的局部窗口内计算自注意力,并通过移位操作实现窗口间信息交互。
  • ConvNeXt:采用类似Transformer的宏观架构,但用大核深度卷积代替自注意力层。
  • MetaFormer:研究发现,Transformer的成功很大程度上归功于其通用的“池化器+令牌混合器”的元架构,而具体的注意力机制可以被更简单的操作(如恒等映射、随机混合)替代,并在某些任务上取得相近效果。

4.3 连接:自注意力与卷积

自注意力与卷积并非完全无关。研究表明,ViT的浅层确实会学习到类似卷积的局部注意力模式。理论上,一个具有动态权重(权重由输入决定)和全局感受野的卷积层可以近似自注意力。反之,通过限制自注意力的感受野,也可以使其表现得像卷积。

4.4 CV中的不同架构范式

与NLP类似,CV中也存在不同的架构范式:

  • 掩码自编码器:使用编码器-解码器架构,随机掩码图像块并训练模型重建,用于视觉表征学习。
  • 编码器-解码器:用于图像分割(如Segment Anything)或目标检测,编码器提取特征,解码器生成掩码或边界框。
  • 仅解码器:用于图像生成,如MaskGIT,以掩码图像块预测的方式进行训练,并以迭代去掩码的方式进行图像生成。

5. 总结 🎯

本节课我们一起深入学习了Transformer架构。我们从其核心组件——多头自注意力机制出发,详细探讨了位置编码、前馈网络、残差连接等关键部分。我们看到了Transformer在自然语言处理中的三种主要架构(编码器-解码器、仅编码器、仅解码器)及其对应的任务,并了解了高效的注意力变体。接着,我们将视野扩展到计算机视觉,学习了视觉Transformer如何将图像处理为词元序列,以及各种为了引入局部性和提升效率而设计的变体模型。最后,我们探讨了自注意力与卷积之间的联系,以及Transformer在CV中不同的架构范式。Transformer以其灵活性和强大的扩展性,已成为连接多模态人工智能的通用骨干网络。

21:大语言模型与复合AI系统 🧠

在本节课中,我们将学习大语言模型如何从深度学习基础发展而来,以及如何利用它们构建更复杂、更可靠的复合AI系统。我们将从预训练和微调的基础概念开始,逐步深入到如何将这些模型作为模块化组件来构建可控制、可调试的应用程序。


📚 概述:从深度学习到大语言模型

大语言模型是一类深度神经网络,它们彻底改变了我们构建AI助手和原型的方式。这一切主要归功于Transformer架构和自回归解码。然而,仅仅拥有这些强大的基础模型还不够。为了构建真正有用且安全的用户界面系统,我们需要进行预训练、后训练(如指令微调和人类反馈强化学习),并将这些模型作为模块化组件整合到更大的复合AI系统中。


🔄 自回归解码:文本生成的基础

上一节我们介绍了Transformer作为强大的架构基础。本节中,我们来看看如何利用它进行文本生成,其核心是自回归解码

其思想非常简单:我们有一个能够编码文本并将其投影到词汇空间的架构。我们可以以一种贪婪的方式进行自回归解码。

以下是其核心流程的伪代码描述:

while not sequence_finished:
    # 1. 将当前输入文本分词
    tokens = tokenize(input_text)
    # 2. 输入Transformer,进行前向传播,得到输出概率
    output_probs = transformer.forward(tokens)
    # 3. 根据输出概率采样下一个词元(例如,选择最高概率的词元或加入随机性)
    next_token = sample(output_probs)
    # 4. 将新采样的词元追加到输入中,重复此过程
    input_text.append(next_token)

通过这个过程,我们能够生成长文本序列。这个简单的机制已经非常强大,可以捕捉许多任务,因为用自然语言描述输入输出本身就是一种非常通用的函数。


🏗️ 编码器与解码器:架构选择

我将在本讲座中主要关注仅解码器架构,因为这是过去几年用于生成的模型的主流。但编码器仍然存在,并且在某些意义上,如果按部署数量计算,它们可能应用更广泛,因为它们是搜索、信息检索等需要实际编码的任务的支柱。

编码器可以用来表示文档,为每个词赋予重要性权重,并构建支持神经搜索的表示。你可以直接使用它们提供的上下文化嵌入在高维空间中进行操作,或者将它们用作评分函数。

以下是使用编码器进行检索的几种方式权衡:

  • 双编码器:分别编码查询和文档,可预先计算文档表示,扩展性好。
  • 交叉编码器:联合编码查询和文档对,能利用注意力机制,精度更高,但计算成本也高。

这只是为了确保编码器没有被遗忘,本讲座的其余部分将完全专注于解码器。


🌐 预训练:赋予模型广泛的知识

上一节我们了解了如何生成文本。本节中,我们来看看如何训练一个Transformer,使其真正擅长此道。目标是通过在海量网络数据上训练,赋予语言模型广泛的知识

我们基本上会尽可能爬取整个网络,过滤掉不想要的内容,然后进行积极的清洗。覆盖更多样化的数据源通常效果更好。

例如,早期开源数据集The Pile就包含了学术论文、通用网络爬取数据、问答社区、代码、维基百科等多种来源。

预训练任务:下一个词预测

拥有了所有这些数据后,我们能做什么呢?有趣的是,非常传统的语言建模任务,或者说简单的下一个词预测,就能带我们走得很远。

我们将使用标准的交叉熵分类损失,教导Transformer根据前文预测下一个词。这就是我们在过去大约八年深度学习中所遵循的范式:对于许多任务,你并非从头开始,而是先在一些广泛、可扩展的数据上预训练一个基础模型,然后再通过微调来适应你的特定任务。

为什么预训练有效?

一个有趣的问题是:为什么在广泛数据上进行预训练会有帮助?有两种可能的假设:

  1. 它可能有助于在微调时梯度流动得更好。
  2. 梯度下降可能倾向于停留在初始化点附近。因此,当我们花费很长时间得到一个很好的初始化后,即使少量的微调也能学习任务,并且由于接近这个通用的预训练参数,泛化能力也很好。

数据过滤与清洗

数据过滤和清洗是工程中的关键因素,但具体方法通常是保密的。一些启发式方法包括:

  • 基于困惑度过滤:如果模型对某个页面的困惑度过高(难以预测其中的词元),则可能只是随机噪声,应丢弃。
  • 上采样高质量数据:例如,人为提升维基百科等高质量数据在训练批次中的比例。
  • 自动加权方法:这是一个新兴的研究领域,旨在通过学习来确定数据子集的权重。

这主要是大量的工程和试错,理论正在努力追赶和推进。


🧩 预训练学到了什么?

我们进行了所有这些预训练,但它在试图做什么?实际上又实现了什么?在进入后训练等步骤之前,我们需要了解这一点。

预训练主要带来两方面的收获:

  1. 强大的语言表示:帮助模型理解词语关联、基本语法,并生成类似人类语言的序列。
  2. 适应下游任务的基础:为情感分析、毒性检测、报告生成、翻译、信息检索等下游任务提供了良好的起点,这些任务都受益于良好的语言表示。

仅仅通过大规模的下一个词预测,模型就能学习到事实(如斯坦福大学的位置)、句法、共指消解,甚至一些看似需要推理的任务模式。这表明预训练编码的知识远不止简单的词语预测。


📈 缩放定律与涌现能力

在上一讲中,你们已经了解了缩放定律。对于仅解码器模型,研究发现性能提升是高度可预测的:通常,模型参数量越多、训练数据越多,效果越好。

这可以描述为一个经验性的缩放定律,帮助我们在大规模训练模型时做出明智的决策,例如,可以在小规模上调整超参数,然后可预测地扩展到更大规模。

一个关键的启示是:在训练大语言模型时,你有一个固定的预训练计算预算。你需要在增加模型参数量增加训练数据量之间做出权衡。模型越小,长期推理效率越高,但为了达到特定质量水平所需的数据量增长是非线性的。近年来,趋势是训练参数量不一定更大、但在多得多数据上训练的模型。

有趣的是,缩放不仅平滑地降低测试损失,还会带来一些涌现能力

上下文学习

对于足够大的模型,你可以通过提供几个任务示例(即“上下文”),让模型在不进行梯度更新的情况下学习并遵循新模式。这曾是一个惊人的观察,虽然它本身并未直接带来任务性能的飞跃,但模型识别和生成这类序列的能力是后续赋予其更多能力的关键。

思维链推理

对于许多组合性问题,直接生成答案可能很困难。思维链提示要求模型在生成最终答案前,先输出推理步骤。研究表明,当模型规模足够大时,这种技巧可以显著提高复杂任务(如数学问题)的解答质量。

关于这些现象何时开始出现,并没有绝对的规则。早期认为需要超过1000亿参数,但现在,即使在参数量小得多的模型上(如果它们在更多数据上训练并经过后训练),也能观察到这些增益。


🎯 后训练:从知识模型到助手

预训练给了我们一个知识渊博但“原始”的模型。它擅长生成类似其训练数据的文本,但并不一定知道它应该回答问题或遵循指令。这引出了后训练(也称为对齐或人类反馈强化学习)的目标。

后训练的目标是:利用模型已掌握的大量知识,教导它成为一个面向用户的助手,其职责是以简洁、准确、有用、安全的方式回答问题。

指令微调

最简单的方法是指令微调,本质上是大规模多任务学习。我们收集来自各种任务(事实问答、数学、代码、情感分析、报告撰写等)的大量问答对数据集,然后在预训练模型上使用相同的交叉熵损失进行微调,让模型学会根据输入问题生成答案。

这是一个巨大的进步,使得模型从“酷炫的玩具”变成了半可靠的工具。但其局限性也很明显:它依赖于我们能收集到多少样化的高质量数据,可扩展性远不如预训练,并且可能鼓励模型在不知道答案时也显得知识渊博(即幻觉)。

人类反馈强化学习

要进一步提升,我们需要模型能够试错并从反馈中学习,这就是人类反馈强化学习的范式。OpenAI等公司广泛使用此方法。

其流程通常包括以下步骤:

  1. 收集示范数据:让人类标注员根据用户问题撰写高质量答案。
  2. 监督微调:用这些数据对预训练模型进行微调,得到初始模型。
  3. 训练奖励模型:让初始模型生成多个答案,由人类标注员进行排序或比较。用这些数据训练一个奖励模型,使其能够评估答案的好坏。
  4. 强化学习优化:使用PPO等强化学习算法,以奖励模型为指导,优化初始模型,使其生成能获得高奖励的答案。

这个过程是使模型行为更符合人类期望的关键,但也非常复杂,并且奖励模型的质量至关重要。


🤖 复合AI系统:超越单一模型

尽管后训练大大提升了模型能力,但大语言模型作为单一、整体的系统,仍难以达到在生产环境中部署可靠AI系统的标准。它们可能产生流畅但错误的答案(幻觉),且难以控制和调试。

为了解决这个问题,AI研究者越来越多地构建复合AI系统。这意味着构建模块化的程序,将预训练和后训练的语言模型作为专用组件整合到更大的架构中。

检索增强生成

一个典型的例子是检索增强生成。其架构不是让语言模型直接“猜测”答案,而是将其分解为步骤:

  1. 用户问题首先由检索器(通常也是一个Transformer编码器)处理,从知识库中检索相关文档。
  2. 检索到的文档与原始问题一起输入给生成器(大语言模型)。
  3. 生成器被指示基于这些文档来回答问题。

这样做的好处包括:

  • 可解释性:可以追溯答案所依据的文档。
  • 效率:可以将事实记忆卸载到检索系统,从而可能使用更小的、专注于推理的生成模型。
  • 可控性:可以检查和改进检索、生成等各个模块。

多跳推理系统

对于更复杂的问题(例如,“大卫·格雷戈里在17世纪继承的城堡有多少层?”),可能需要多步推理。多跳RAG系统通过引入循环来模拟这一过程:

  1. 生成一个用于查找初始实体(如“大卫·格雷戈里”)的查询。
  2. 根据检索结果,生成新的查询以查找更多信息(如“他继承的城堡名称”)。
  3. 再次检索,最终基于所有信息生成答案。

通过将语言模型用作查询生成器、摘要器、答案生成器等不同角色,我们可以构建出能处理复杂组合性问题的系统,并且可以独立改进每个模块。

基于代码执行的系统

另一个方向是让系统生成代码并执行,以获得精确的反馈。例如,在代码生成任务中,系统可以:

  1. 生成多个代码解决方案。
  2. 在公共测试用例上运行这些代码。
  3. 根据测试结果对解决方案进行排名或改进。
  4. 循环此过程以获得更好的解决方案。

这为系统提供了基于实际执行的 grounding( grounding)。


⚙️ 挑战与未来方向:可编程的AI系统

复合AI系统在原理上是模块化的,这很有吸引力。但当前的实现方式存在一个重大问题:它们通常通过冗长、手工编写的提示词来连接各个模块。这导致了强耦合,将系统架构与特定模型、提示词格式等偶然选择绑定在一起,使得系统难以移植、维护和泛化。

其根本原因在于,当我们编写提示词时,我们同时编码了:

  1. 函数签名:输入输出行为。
  2. 计算逻辑:推理步骤、示例。
  3. 输出适配器:如何从模型输出的文本中解析出结构化的结果。
  4. 目标约束:如“不要幻觉”、“使用计算器”。
  5. 优化过程:如何通过试错来改进提示词本身。

为了解决这个问题,一个前沿方向是让复合AI系统像真正的计算机程序一样可编程。这涉及到创建新的编程抽象,允许我们声明式地定义处理自然语言输入输出的模块,并自动优化这些模块的提示词或内部参数,以最大化整个系统的目标。

例如,像DSPy这样的框架允许开发者用高级语法定义模块(如“ChainOfThought”),并声明其输入输出类型。然后,系统可以自动搜索最佳提示词或进行微调,使所有模块协同工作以优化最终指标。这种方法将我们从繁琐的手工提示工程中解放出来,转向更通用、可移植的系统构建和优化算法。


🎓 总结

本节课中,我们一起学习了构建现代大语言模型应用的全景:

  1. 基础:Transformer和自回归解码提供了强大的文本生成能力。
  2. 预训练:在海量网络数据上进行下一个词预测,赋予模型广泛的语言知识和世界知识,缩放定律指导我们有效利用计算资源。
  3. 后训练:通过指令微调和人类反馈强化学习,将知识模型转化为能遵循指令、安全有用的助手。
  4. 复合AI系统:将大语言模型作为模块化组件,构建检索增强生成、多跳推理等更可靠、可解释、可控制的系统。
  5. 未来方向:通过可编程的抽象和自动优化,解决当前系统脆弱、难以移植的问题,实现更稳健、通用的AI系统构建。

大语言模型是深度学习力量的杰出体现,但将它们转化为可靠的现实世界应用,需要我们以系统化的思维,将其作为更宏大工程中的一部分来设计和优化。

22:神经网络内部机制解析 🧠

在本节课中,我们将深入探讨神经网络的内部工作原理。我们将了解网络如何学习、数据在网络中如何流动,以及网络的不同部分(如特征提取层和分类层)各自扮演什么角色。通过分析线性分类器、逻辑回归和自编码器等概念,我们将揭示神经网络作为统计估计器的本质,以及它如何学习数据的底层流形结构。


神经网络概览:功能与训练

上一节我们回顾了神经网络作为通用逼近器的强大能力。本节中,我们来看看网络在训练过程中究竟在做什么。

一个标准的神经网络接收输入,经过一系列变换,最终产生输出(如分类结果)。整个网络可以看作由两部分组成:

  1. 特征提取部分:输入层到倒数第二层之间的所有层。它们将原始输入数据转换为新的特征表示。
  2. 分类部分:通常是最后一层(如 softmax 层)。它基于提取出的特征进行最终决策。

网络的目标是学习一个函数,能够根据输入数据 X 预测输出 Y。在分类任务中,这通常意味着学习能够区分不同类别的决策边界。


从线性分类到概率建模

当数据可以被一个清晰的线性边界完美分开时,事情很简单。但现实中的数据往往是混杂的,同一个输入值 X 可能对应不同的类别 Y

例如,在某个特征值 x 处,90% 的样本属于类别1(红色),10%属于类别0(蓝色)。一个理想的分类器不应该武断地输出“红色”,而应该输出一个能反映这种不确定性的值,即类别1的概率 P(Y=1 | X=x) = 0.9

这引出了我们的核心观点:神经网络的输出层(如使用 sigmoidsoftmax 激活函数)实际上是在建模给定输入 X 后,输出类别 Y 的后验概率 P(Y | X)

对于二分类问题,sigmoid 函数完美地扮演了这个角色:
P(Y=1 | X) = 1 / (1 + exp(-(w0 + w1*X)))
这个函数是平滑、可微的,其输出值在0到1之间,可以解释为概率。


最大似然估计与交叉熵损失

那么,网络是如何学习到这个概率模型的呢?答案是通过最大似然估计

给定一组训练数据 (X_i, Y_i),我们希望找到网络参数 θ,使得观察到这组数据的概率(即似然)最大。这等价于最大化所有训练样本的似然乘积,或更常见地,最大化其对数似然和。

当我们推导用于二分类的 sigmoid 输出层的对数似然时,会发现一个熟悉的形式:
最大化 ∑ log(P(Y_i | X_i)) 等价于 最小化 -∑ [Y_i * log(Ŷ_i) + (1-Y_i) * log(1-Ŷ_i)]
这正是二元交叉熵损失函数

因此,使用交叉熵损失训练一个分类神经网络,本质上就是在执行最大似然估计,以学习后验概率 P(Y | X)。这为神经网络的训练提供了一个坚实的统计学基础。


特征提取:创造线性可分空间

现在,我们来看看特征提取部分的作用。如果最后一层是一个线性分类器(sigmoid 本质上也是线性决策边界),那么它要能有效工作,前提是输入给它的特征必须是(近似)线性可分的

这正是网络中下层网络的神奇之处。它们学习一种变换,将原始输入空间中复杂交织、非线性可分的数据,映射到一个新的特征空间。在这个新空间中,不同类别的数据变得尽可能线性可分。

以下是这一过程的直观理解:

  • 目标:将原始数据“挪动”到一个易于用线性平面分开的布局。
  • 方式:通过每一层的线性变换(权重矩阵)和非线性激活函数,逐步扭曲和变换数据流形。
  • 结果:到达倒数第二层时,数据点在新特征空间中的分布变得几乎线性可分,以便最后的线性分类器能够轻松处理。

如果网络容量(宽度、深度)足够,它可以学习到使数据完美线性可分的变换。如果容量不足,它也会尽力使数据达到“最可能”线性可分的状态。


窥视第一层:作为相关滤波器的神经元

为了理解特征是如何被提取的,让我们聚焦于网络的第一层。每个神经元计算输入 X 和其权重向量 W 的点积,然后通过一个激活函数(如 ReLU, sigmoid)。

在高维空间中,如果我们将输入向量和权重向量都归一化到相近的长度,那么它们的点积主要取决于两者之间的夹角余弦值:
W·X ≈ ||W|| * ||X|| * cos(θ)
cos(θ) 超过某个阈值时,神经元被激活(“放电”)。

这意味着什么?第一层的每个神经元,其权重向量 W 就像一个“模板”或“特征探测器”。它会在输入数据 X 与这个模板足够相似(即夹角足够小)时被激活。

例如,一个用于数字识别的网络,其第一层的某些神经元可能学会对应“垂直线段”、“左上圆弧”或“右下角点”等基本图案。后续的层则将这些基本特征组合成更复杂的模式(如完整的数字“8”或“2”)。


自编码器:学习数据的内在流形

如果我们改变网络的目标,不是预测类别,而是重建输入本身,我们就得到了自编码器

一个基本的自编码器由两部分组成:

  1. 编码器:将高维输入 X 压缩成一个低维的“编码” Z(瓶颈层)。
  2. 解码器:根据编码 Z 试图重建出原始输入

以下是自编码器的关键见解:

情况一:线性激活函数
如果编码器和解码器都使用线性激活函数,并且我们使用均方误差(MSE)作为重建损失,那么训练好的自编码器实际上是在执行主成分分析

  • 编码器 W_enc 将数据投影到主成分子空间。
  • 解码器 W_dec 从该子空间重建数据。
  • 无论输入是什么,输出都必然位于由主成分张成的线性子空间上。

情况二:非线性激活函数
当引入非线性激活函数(如 ReLU, tanh)后,自编码器能够学习非线性主成分分析

  • 解码器学会将一个低维的编码空间 Z 扭曲成一个复杂的、高维的数据流形
  • 这个流形捕捉了训练数据分布的本质结构。例如,对于螺旋形分布的数据,解码器可能学会将一条直线(编码 Z)映射回一个螺旋。

自编码器的一个重要特性是:训练好的解码器,无论你输入什么编码 Z,其输出都必然位于它所学到的数据流形之上。这使得它成为一个强大的生成模型,可以产生与训练数据类似的新样本(例如,生成像训练集中乐器声音的音频,或像训练集中数字的手写图像)。


应用示例:基于字典的音频分离

自编码器学习数据流形的能力可以用于解决像音频源分离这样的问题。

假设我们想从混合音频中分离出吉他和鼓的声音:

  1. 分别用纯吉他音频和纯鼓音频训练两个自编码器。
  2. 训练完成后,它们的解码器部分就分别成为了“吉他声音字典”和“鼓声音字典”。每个解码器只能生成各自乐器特有的声音。
  3. 当面对一个混合音频(吉他+鼓)时,我们将其输入这两个冻结的解码器网络,并通过反向传播只优化输入给这两个解码器的编码向量 Z_guitarZ_drum
  4. 优化的目标是使两个解码器的输出之和尽可能接近混合音频。
  5. 优化完成后,解码器_吉他(Z_guitar) 的输出就是分离出的吉他音轨,解码器_鼓(Z_drum) 的输出就是分离出的鼓音轨。

这种方法利用了每个解码器只能生成特定流形上数据这一约束,从而迫使模型在解释混合信号时找到正确的成分组合。


总结

本节课中我们一起深入探索了神经网络的内部机制:

  1. 本质是概率模型:分类神经网络的输出层旨在建模后验概率 P(Y | X),使用交叉熵损失训练等价于进行最大似然估计。
  2. 特征提取即空间变换:网络中层的作用是将原始数据非线性地变换到一个新的特征空间,使得不同类别在该空间中变得(近似)线性可分,从而方便最后一层的线性分类器工作。
  3. 底层神经元是特征探测器:第一层的神经元充当相关滤波器,其权重向量代表了它要检测的输入中的基本模式或特征。
  4. 自编码器揭示数据流形:通过以重建为目标进行训练,自编码器能够学习数据分布的底层流形结构。线性版本对应PCA,非线性版本能发现复杂的流形,其解码器可作为特定数据类型的生成器。
  5. 流形学习具有实用价值:学习到的数据流形可以用于各种任务,如数据生成、去噪、以及我们演示的基于字典的音频源分离。

通过理解这些内部原理,我们不仅能更好地理解神经网络的行为,还能更有目的地设计和使用它们来解决复杂问题。

23:生成模型与变分自编码器(VAE)📊

在本节课中,我们将要学习一个全新的问题类别:生成模型。我们将探讨如何让神经网络从一组数据(如人脸图片)中学习,并生成新的、类似的数据。我们将从简单的自编码器开始,逐步引入约束和概率模型,最终构建出强大的变分自编码器(VAE)。


概述:从分类到生成

到目前为止,我们看到的神经网络主要用于执行分类和回归任务。给定一堆数据,网络学习如何预测数据的类别或某个连续值。

现在,我们面临一个全新的问题类别:给定一个大型图像集合(例如人脸),你必须学会如何从中生成新的肖像。这是一个生成问题。其核心思想是,所有可能的图像都存在于一个高维空间中(例如,百万像素的图像是百万维空间中的一个点)。所有可能的人脸在这个空间中有一个分布。我们的目标是:如何从这个分布中抽取一个样本,使其看起来像一张人脸。

同样,如果给你一个大型风景图像集合,你如何训练一个网络来生成新的风景?这也是同一个问题。


挑战:高维空间中的未知分布

这是一个非常具有挑战性的问题。在百万维空间中,我们完全不知道人脸分布的具体数学形式。即使在三维或二维空间中,除了高斯分布、均匀分布等少数几种,我们也很难描述一个复杂的分布。

问题的关键在于,并非百万维空间中的每一个点都是一张有意义的图像。有意义的图像(如人脸、风景)具有结构。结构意味着限制——数据点不能取所有可能的值,只能取某个子集的值。这相当于消除了自由度,或者说降低了维度。

因此,我们的假设是:数据(如所有人脸实例)分布在一个高维空间中的非线性流形上或附近。如果我们想从这个类别中生成一个新实例,首先需要描述这个流形,然后从中选取一个点。


自编码器的初步尝试

我们之前学过的自编码器可以帮助我们学习数据的底层流形结构。它通过非线性主成分分析来捕获这个流形。自编码器的解码器部分可以从流形上生成数据。

例如,我们可以训练一个关于人脸的自编码器。在瓶颈处,我们希望捕获人脸所在子空间(流形)的维度。之后,任何时候向解码器输入任何内容,它都会在这个“人脸流形”上生成数据,希望这看起来像一张脸。

但这里存在几个问题:

  1. 我们不知道流形的真实维度。
  2. 即使知道了维度,我们也无法控制潜在变量 Z 如何映射到流形上。解码器可能以任意方式扭曲这个映射。
  3. 自编码器只能生成位于其学习到的流形上的数据,无法生成流形之外的数据,而真实数据可能存在流形之外的微小变化。

引入约束:对潜在变量施加分布

为了解决控制问题,我们希望对潜在变量 Z 施加一个分布约束。最简单的选择是标准高斯分布(均值为0,方差为1的单位高斯分布)。选择高斯分布是因为在给定方差的情况下,它是做出最少假设的分布(最大熵分布)。

如果我们能训练自编码器,使得 Z 始终服从标准高斯分布 N(0, I),那么要生成新数据时,我们只需从标准高斯分布中采样一个点,然后通过解码器,它就有很高的可能性生成正确范围内的数据(如一张人脸)。

核心问题:如何训练一个自编码器,使其隐藏表示 Z 服从特定的分布?

我们将使用最大似然原则。对于任何编码器,我们希望设置其参数,使得编码器输出的 Z 值用标准高斯分布计算得到的概率最大化(或负对数概率最小化)。这等价于最小化编码器生成的 Z 的分布与标准高斯分布之间的 KL 散度。

对于标准高斯分布,一个向量 Z 的负对数概率简单地正比于其平方范数 ||z||²

因此,我们训练的整体模型将同时做两件事:

  1. 最小化输入 x 与重构输出 之间的误差。
  2. 最小化从编码器出来的 Z 向量的平方范数。

公式表示
总损失 = 重构损失 + λ * ||z||²
其中 λ 是一个权衡参数。


处理流形外的变化:加入噪声

简单的模型仍然不能充分捕获数据中的所有变化。真实数据并不完全位于流形上,而是在其附近。因此,我们假设实际数据是通过在解码器输出上添加噪声得到的。

我们假设噪声是零均值、各分量独立且无结构的。给定方差,信息量最少的分布就是高斯分布。因此,我们假设噪声是协方差矩阵为对角阵的高斯分布(各维度独立)。

这意味着,给定一个 Z,它生成一个 ,然后加上一些噪声 ε 来产生最终的 x。因此,x 不再是 Z 的确定性结果,而是一个分布。给定 Zx 的概率分布是以解码器输出 D(z) 为均值、以某个协方差矩阵 C 为方差的高斯分布。

生成过程

  1. 从标准高斯分布中采样一个 K 维向量 z
  2. z 通过解码器 D,得到
  3. 从高斯分布 N(0, C) 中采样噪声 ε
  4. 将噪声加到解码器输出上:x = x̂ + ε

这就构成了数据的生成模型


变分自编码器(VAE)的完整框架

为了训练这个生成模型,我们需要知道每个训练数据点 x 对应的 z。但我们没有。这就是编码器的作用:编码器的任务是估计产生每个 xz

然而,由于噪声的存在,对于同一个观测到的 x,可能有多个不同的 z(通过添加不同的噪声)都能生成它。因此,z 并不是唯一的,它有一个分布。

所以,编码器的工作不再是输出一个确定的 z,而是估计 z 的后验概率分布 P(z|x)。我们称编码器计算出的分布为 Q(z|x)

我们希望 Q(z|x) 尽可能接近真实的 P(z|x)。同时,我们有一个全局约束:所有 x 对应的 P(z|x) 的平均(混合)应该是一个标准高斯分布。因此,我们也要让 Q(z|x) 的平均接近标准高斯。

通过一番推导(使用变分推断),我们可以得到 VAE 的优化目标(损失函数),它包含两部分:

  1. 重构损失:使解码器输出尽可能接近原始输入 x
  2. KL 散度损失:使编码器输出的分布 Q(z|x) 尽可能接近标准高斯分布 N(0, I)

VAE 损失函数(简化形式)
L(θ, φ) = 重构损失(x, D(z)) + β * KL( Q(z|x; θ) || N(0, I) )
其中 z 是从 Q(z|x; θ) 中采样得到的,θ 是编码器参数,φ 是解码器参数,β 是超参数。

为了使采样过程可反向传播(允许梯度通过),我们使用重参数化技巧
z = μ + σ ⊙ ε
其中 ε 采样自标准高斯分布 N(0, I)μσ 是编码器根据输入 x 输出的均值和标准差。这样,z 的随机性来自于 ε,而 μσ 是确定性的,可以进行求导。


VAE 的训练与生成

训练过程

  1. 输入 x
  2. 编码器输出分布参数 μ(x)σ(x)
  3. 使用重参数化技巧采样 zz = μ + σ ⊙ ε, ε ~ N(0, I)
  4. 解码器将 z 映射为
  5. 计算损失(重构损失 + KL 损失)。
  6. 通过反向传播更新编码器和解码器的参数。

生成过程(训练完成后):

  1. 丢弃编码器。
  2. 从标准高斯分布 N(0, I) 中采样一个随机向量 z
  3. z 输入解码器。
  4. 解码器输出 ,这就是生成的新数据样本。

VAE 的编码器仅在训练时需要,用于帮助学习一个结构良好的潜在空间。一旦训练完成,解码器本身就是一个生成模型。


VAE 的能力与局限

VAE 是一个强大的生成模型,它学习数据的压缩潜在表示,并可以从中生成新样本。潜在空间 Z 具有连续性,例如,对两个不同人脸对应的 z 进行插值,再通过解码器,可以得到两张人脸之间平滑过渡的图像。

然而,VAE 生成的图像有时看起来模糊。主要原因有两个:

  1. 近似误差:由于解码器 D 是非线性的,我们无法精确计算真实后验 P(z|x),只能用编码器 Q(z|x) 来近似,这引入了误差。
  2. 分布转换的难度:模型试图一步将简单的标准高斯分布映射到复杂的数据分布(如人脸分布),这个转换可能过于困难。

这些局限性引出了更先进的模型,如标准化流模型(使用可逆解码器来精确计算概率)和扩散模型(通过多步微小转换来渐进地将噪声分布转化为数据分布)。


总结

在本节课中,我们一起学习了生成模型的核心思想以及变分自编码器的原理。我们从简单的自编码器出发,逐步引入了对潜在变量的分布约束、对数据流形外变化的噪声建模,最终通过变分推断得到了 VAE 的优化目标。

关键点回顾

  • 生成问题的目标是从数据分布中采样新样本。
  • 自编码器可以学习数据流形,但缺乏对潜在空间的控制和生成能力。
  • VAE 通过强制潜在变量 z 服从先验分布(如标准高斯),并利用编码器近似后验分布 Q(z|x),构建了一个概率生成模型。
  • 重参数化技巧使得从分布中采样这一过程可以进行梯度下降训练。
  • VAE 的损失函数包含重构损失和潜在分布与先验分布之间的 KL 散度损失。
  • 训练完成后,VAE 的解码器可作为生成器使用。
  • VAE 是连接自编码器与更复杂生成模型(如扩散模型)的重要桥梁。

VAE 为理解深度生成模型奠定了坚实的基础,并展示了如何将概率思想与神经网络相结合。

24:扩散模型 🧠

在本节课中,我们将要学习生成式模型的一个重要分支——扩散模型。我们将从变分自编码器的基础出发,理解扩散模型如何通过堆叠多个VAE来构建,并深入探讨其前向与反向过程、训练与采样的原理。课程还将从随机微分方程和分数匹配的视角解读扩散模型,并介绍加速采样技术DDIM以及条件扩散模型。最后,我们会概览扩散模型的一些重要应用。


概述:生成式模型 vs. 判别式模型

上一节我们回顾了机器学习中的基本概念,本节中我们来看看生成式模型与判别式模型的区别。

在判别式模型中,我们学习的是条件分布 P(Y|X)。这反映在数据分布上,是在不同数据簇之间学习一个决策边界。

在生成式模型中,我们学习的是后验分布 P(X|Y)。通过学习数据的概率分布(而不仅仅是决策边界),我们可以利用贝叶斯规则得到条件概率。生成式模型的核心是学习数据分布本身,而不仅仅是在数据之上进行分类。

生成式模型的一个显著优势是,在训练完成后,我们可以从学习到的数据分布中直接采样,生成新的数据样本(如图像、音频等)。

以下是几种常见的生成式模型:

  • 生成对抗网络:包含一个生成器和一个判别器,二者在训练中相互对抗以隐式地学习数据分布。
  • 变分自编码器:包含一个编码器和一个解码器。编码器负责将原始数据压缩为隐变量,解码器负责从隐变量生成新数据。
  • 扩散模型:本讲的重点。我们将看到,扩散模型本质上是VAE的堆叠,并且与基于流的生成模型有联系。

生成式模型是一个快速发展的领域。从2013-2014年GAN和VAE刚出现时只能生成模糊图像,到2024年已经能用扩散模型生成高分辨率的长视频序列。


扩散模型基础:堆叠的VAE 🧱

上一节我们介绍了生成式模型的概览,本节中我们来看看扩散模型的核心思想——它本质上是堆叠的变分自编码器。

变分自编码器的回顾与局限

变分自编码器是基于似然的生成模型,它直接估计数据的似然函数来优化模型。VAE包含一个编码器(推断模型,近似后验分布)和一个解码器(生成模型,从隐变量生成数据)。它们通过最大化证据下界来联合训练,该目标包含重构损失和编码器后验与先验分布之间的KL散度。

然而,朴素VAE生成的图像通常比较模糊。一个主要原因是:解码器必须一步之内将一个标准高斯分布转换成一个非常复杂的目标分布(通常是多模态的)。这个巨大的转换差距导致模型倾向于捕捉数据的“平均模式”,从而产生模糊结果。

解决方案:分层VAE(堆叠VAE)

解决上述问题的一个直接思路是:将困难的一步转换分解为多个简单的步骤。这就是分层VAE堆叠VAE的思想。

我们引入多个中间隐变量 z₁, z₂, ..., z_T。解码过程从最深的隐变量 z_T(一个简单的高斯噪声)开始,通过一系列解码器逐步将其转换为 z_{T-1}, z_{T-2}, ...,最终得到原始数据 x。每个解码器只负责移除一部分噪声,为下一步提供一个更好的起点。从分布角度看,这是一个从单峰高斯分布逐步向复杂多模态目标分布转变的过程。

连接扩散模型

这个过程已经非常类似于扩散模型中的反向去噪过程。事实上,扩散模型正是堆叠VAE的一个特例。

在扩散模型中:

  • 反向去噪过程:对应堆叠VAE中的解码器链。每个步骤学习一个解码器,用于预测并移除当前步骤中添加的高斯噪声的均值(和方差)。
  • 前向扩散过程:对应堆叠VAE中的编码器链。但为了避免VAE中可能出现的“后验坍塌”问题(即当解码器足够强时,编码器学不到有用信息),扩散模型使用了一个固定的推理编码器——一个固定的马尔可夫链,其高斯转移参数是预先设定的。

因此,扩散模型可以看作是一个具有固定编码器链和可训练解码器链的分层VAE。


去噪扩散概率模型详解 🔄

上一节我们建立了扩散模型与VAE的联系,本节中我们来详细看看其前向和反向过程的具体数学形式。

前向扩散过程

前向过程是一个固定的过程,逐步向数据 x₀ 添加高斯噪声。这是一个马尔可夫链,每一步的转移由噪声调度表 β_t 控制:
q(x_t | x_{t-1}) = N(x_t; √(1-β_t) x_{t-1}, β_t I)

由于每一步都是高斯分布,我们可以直接计算在给定 x₀ 时,任意时刻 tx_t 的分布:
q(x_t | x_0) = N(x_t; √(ᾱ_t) x_0, (1-ᾱ_t) I)
其中 α_t = 1 - β_t, ᾱ_t = Π_{s=1}^{t} α_s

这意味着我们可以直接从 x₀ 采样得到 x_t
x_t = √(ᾱ_t) x_0 + √(1-ᾱ_t) ε, 其中 ε ~ N(0, I)

反向去噪过程

反向过程的目标是从高斯噪声 x_T ~ N(0, I) 开始,逐步去噪以生成数据。我们需要学习反向转移分布 p_θ(x_{t-1} | x_t)

β_t 足够小时,这个反向转移也可以用一个高斯分布来近似。因此,我们用一个可训练的网络(如U-Net)来预测该高斯分布的参数:
p_θ(x_{t-1} | x_t) = N(x_{t-1}; μ_θ(x_t, t), Σ_θ(x_t, t))

训练目标

训练扩散模型类似于优化VAE的证据下界。经过推导,核心的优化项是每一步反向预测分布与真实后验分布之间的KL散度。真实后验 q(x_{t-1} | x_t, x_0) 可以根据前向过程公式计算出来。

最终,训练目标可以简化为一个非常直观的形式:最小化实际添加到数据中的噪声 ε 与神经网络预测的噪声 ε_θ 之间的均方误差。
L_simple = E_{t, x_0, ε} [ || ε - ε_θ(√(ᾱ_t) x_0 + √(1-ᾱ_t) ε, t) ||^2 ]

训练与采样流程总结

训练

  1. 从数据集中采样干净数据 x₀
  2. 随机采样时间步 t ~ Uniform({1, ..., T})
  3. 采样噪声 ε ~ N(0, I)
  4. 计算带噪数据 x_t = √(ᾱ_t) x_0 + √(1-ᾱ_t) ε
  5. x_tt 输入去噪网络,预测噪声 ε_θ
  6. 计算预测噪声与真实噪声 ε 的均方误差并反向传播。

采样(生成)

  1. 从标准高斯分布采样 x_T
  2. 对于 t = T, T-1, ..., 1
    • 用网络预测 x_t 对应的噪声 ε_θ
    • 根据公式计算 x_{t-1} = (1/√(α_t)) (x_t - (β_t/√(1-ᾱ_t)) ε_θ) + σ_t z,其中 z ~ N(0, I)
  3. 最终得到生成的干净数据 x₀

随机微分方程与分数匹配视角 📈

上一节我们从VAE和ELBO的角度理解了扩散模型,本节中我们从连续时间的视角,通过随机微分方程来统一地看待它。

随机微分方程简介

  • 常微分方程:描述状态 x 随时间 t 的确定性变化,dx/dt = f(x, t)
  • 随机微分方程:在ODE基础上加入一个随机噪声项,dx = f(x, t)dt + g(x, t)dw。其中 f(x, t) 称为漂移系数,g(x, t) 称为扩散系数,dw 是维纳过程(布朗运动)。SDE描述的是状态 x 随时间演变的概率分布。

分数匹配

对于任何概率密度函数 p_θ(x),其对数梯度 ∇_x log p_θ(x) 称为分数函数。直接建模似然函数需要处理难以计算的归一化常数,而分数匹配则通过匹配数据分布的分数函数来避免这个问题。

前向过程即SDE

扩散模型的前向过程可以看作是一个SDE:
dx = - (β(t)/2) x dt + √(β(t)) dw

  • 漂移项 - (β(t)/2) x dt:将数据拉向原点(0均值),相当于将多模态分布拉向单峰高斯。
  • 扩散项 √(β(t)) dw:注入随机高斯噪声,使路径变得随机。

反向过程即逆向SDE

每个前向SDE都有一个对应的逆向SDE,其形式为:
dx = [- (β(t)/2) x - β(t) ∇_{x_t} log q(x_t)] dt + √(β(t)) dŵ
其中多出了一项 β(t) ∇_{x_t} log q(x_t),即分数函数。逆向SDE描述的是从噪声分布向数据分布演化的过程。

训练即分数匹配

我们的去噪网络 ε_θ(x_t, t) 实际上是在估计分数函数:
∇_{x_t} log q(x_t) ≈ - ε_θ(x_t, t) / √(1-ᾱ_t)
因此,之前简化的训练目标 ||ε - ε_θ||^2,本质上是在进行(加权的)分数匹配。不同的加权方式对应了文献中扩散模型的不同变体,在生成样本的感知质量和最大似然之间进行权衡。


加速采样:去噪扩散隐式模型 ⚡

上一节我们介绍了扩散模型的基本采样流程,本节中我们来看看如何加速这个通常需要上千步的缓慢过程。

动机

标准扩散模型(DDPM)采样需要从 T1 逐步迭代,当 T=1000 时非常缓慢。我们能否用更少的步数进行生成?

DDIM:非马尔可夫前向过程

DDIM的核心思想是重新定义前向过程,使其成为一个非马尔可夫过程。即 x_t 不仅依赖于 x_{t-1},还依赖于 x_0。这允许我们推导出一个确定性的反向过程。

在DDIM中,反向采样步骤为:

  1. 用网络根据当前 x_t 预测噪声,并估计出 x_0
  2. 根据估计的 x_0 和当前 x_t,直接计算 x_{t-1}

优势:子序列采样

由于DDIM的反向过程不依赖于严格的马尔可夫链,我们可以对时间步进行子序列采样。例如,我们可以只用 [T, T-s, T-2s, ..., 1] 这些时间步来进行生成,从而大幅减少采样步数(例如从1000步减少到50步或更少),同时生成质量下降不多。

DDIM开启了扩散模型加速采样研究的大门,目前已有许多技术可以实现用极少的步数(如10步)获得高质量的生成结果。


条件扩散模型 🎯

上一节我们学习了生成无条件样本的模型,本节中我们来看看如何控制生成过程,使其符合我们的预期。

为何需要条件生成?

无条件扩散模型学习的是 p(x)。条件扩散模型则学习 p(x|y),其中 y 可以是类别标签、文本描述、语义掩码、边界框等任何条件信息。这使得生成过程变得可控。

基于分类器的引导

一种早期方法是在扩散过程中加入一个额外的分类器 p(y|x_t) 的梯度来引导生成。这需要预先训练一个分类器,其性能会限制条件生成的质量,并且增加了训练负担。

无分类器引导

目前最流行的方法是无分类器引导。其核心思想是在训练时,以一定概率 p 随机将条件 y 替换为一个空条件(如空字符串)。这样,同一个网络同时学会了条件生成和无条件生成。

在采样时,引导后的分数函数为:
∇ log p(x|y) ≈ ∇ log p(x) + γ * (∇ log p(x|y) - ∇ log p(x))
其中 γ 是引导尺度。当 γ=0 时为无条件生成,γ>1 时会增强条件的影响,通常能生成更符合条件且质量更高的样本,但多样性可能降低。这种方法无需额外分类器,简单有效。


扩散模型的应用实例 🚀

上一节我们学习了条件生成技术,本节中我们快速浏览一下扩散模型发展历程中的一些关键应用。

  • DDPM:扩散模型的开山之作之一,使用简单的U-Net结构和 ||ε - ε_θ||^2 损失,在图像生成上取得了显著效果,证明了扩散模型的潜力。
  • Latent Diffusion Models:在预训练好的VAE的隐空间中进行扩散,而非原始高维像素空间。这大大降低了计算成本,是Stable Diffusion等成功模型的基础。它们通常结合强大的文本编码器(如CLIP、T5)来实现高质量的文生图。
  • DiT:用Transformer架构取代了传统的U-Net卷积架构,展示了扩散模型主干网络的另一种可能性。
  • AR + Diffusion:将自回归模型与扩散损失结合。例如,在图像生成中,不是按顺序预测离散token,而是以随机顺序预测token集合,并用扩散损失进行优化,取得了当前最好的生成效果之一。

总结 📚

本节课中我们一起学习了扩散模型。我们从变分自编码器出发,理解了扩散模型作为堆叠VAE的本质。我们详细推导了其前向扩散和反向去噪过程,以及简化的训练目标。接着,我们从随机微分方程和分数匹配的视角,获得了对扩散模型更统一的理解。为了克服采样速度慢的缺点,我们介绍了加速技术DDIM。为了实现可控生成,我们探讨了条件扩散模型,特别是无分类器引导这一重要技术。最后,我们回顾了扩散模型从DDPM到Stable Diffusion等重要应用的发展脉络。扩散模型是当前生成式AI领域的核心支柱之一,理解其原理对于深入该领域至关重要。

25:霍普菲尔德网络、自联想器与玻尔兹曼机 🧠

在本节课中,我们将学习一种特殊的神经网络结构——循环网络,特别是霍普菲尔德网络和玻尔兹曼机。这些模型是今年诺贝尔物理学奖获奖工作的基础,它们将物理学中的概念(如自旋玻璃和能量)引入机器学习,实现了内容寻址记忆和概率建模。


从循环网络到霍普菲尔德网络

上一节我们介绍了前馈网络,数据从一端流入,经过处理从另一端流出。本节中我们来看看如果我们将神经元的输出反馈给自身或前一层,会发生什么。

考虑一个简单的循环网络,其中每个神经元的输出都连接到所有其他神经元(包括自身)。这种结构可以展开成一个无限深的网络,其权重在每一层重复。网络的处理过程会持续进行,直到状态不再变化,即达到收敛。

以下是这种网络的一个关键特性分析:

  • 每个神经元接收来自所有其他神经元输出的加权和,加上一个偏置项,我们称之为该神经元的“场”。
  • 神经元通过一个阈值激活函数(输出+1或-1)来响应这个场。
  • 神经元是否翻转(改变输出)取决于其当前输出与场的符号是否对齐。

具体规则如下:

  • 如果神经元当前输出为 y,其场为 F
  • y * F < 0 时,神经元翻转,输出变为 -y
  • y * F >= 0 时,神经元保持当前状态。

当一个神经元翻转时,它会改变其他神经元的场,可能引发连锁反应。这引出了一个问题:这个过程会永远持续下去吗?


能量函数与收敛性

为了分析网络的动态,我们引入一个称为“能量”的量 E。对于具有对称连接权重(即 W_ij = W_ji)且无自连接(W_ii = 0)的网络,能量定义为:

E = -1/2 * ∑_i ∑_j W_ij * y_i * y_j - ∑_i b_i * y_i

其中 y_i 是神经元 i 的状态(+1或-1),b_i 是偏置。

关键结论是:网络的每一次状态演化(神经元翻转)都会保证能量 E 降低或保持不变。由于能量存在下界(权值和偏置是有限的),因此任何演化序列都必须在有限步内收敛到一个局部能量极小点。

这使得网络的行为类似于物理中的自旋玻璃系统,其“势能”会不断降低直至稳定。


霍普菲尔德网络作为内容寻址记忆

霍普菲尔德网络是一个具有对称连接的循环二值网络。其核心特性使其成为优秀的内容寻址记忆(或联想记忆)。

  • 内容寻址:与传统计算机通过地址检索内存不同,内容寻址记忆通过提供部分或含噪声的内容来触发并恢复完整模式。
  • 模式恢复:网络可以从一个受损、不完整或嘈杂的初始状态开始演化,最终稳定在之前存储的某个完整模式上。
  • 功能:这实现了去噪模式补全

例如,存储了“米老鼠”和“兔八哥”图像的网络,即使输入是带噪声的或下半部分被擦除的图像,也能演化恢复出清晰的原始图像。


网络的训练与记忆容量

如何让网络记住我们想要的特定模式呢?

我们希望网络的能量在目标模式处较低,而在其他模式处较高。这可以通过修改权重 W 来实现。一种简单的赫布学习规则是,对于每个要存储的模式向量 y,按如下方式更新权重:

W := W + η * (y * y^T)

这里 η 是学习率,y * y^T 是模式向量的外积。这实质上是将目标模式“雕刻”为能量景观中的低谷。

然而,一个拥有 N 个神经元的经典霍普菲尔德网络,可靠存储的随机模式数量上限约为 0.14N。超过这个限度,会出现伪吸引子,导致记忆混淆。


玻尔兹曼机:概率化扩展

为了突破容量限制并引入概率解释,我们将霍普菲尔德网络扩展为玻尔兹曼机。

  • 概率化状态:神经元的状态翻转不再是确定性的,而是概率性的。神经元 i 取值为+1的概率由Sigmoid函数给出:
    P(y_i = +1) = σ(∑_j W_ij * y_j + b_i) = 1 / (1 + exp(- (∑_j W_ij * y_j + b_i)))
  • 玻尔兹曼分布:整个网络状态 y 的概率服从玻尔兹曼分布:
    P(y) ∝ exp(-E(y))
    其中 E(y) 就是之前定义的能量函数。低能量状态出现概率更高。
  • 隐神经元:引入额外的“隐”神经元。它们不与外部直接相连,但可以大幅增加网络的表示能力和记忆容量。我们只关心“显”神经元的输出。

玻尔兹曼机的训练:对比散度

训练玻尔兹曼机的目标是最大化训练数据(显神经元状态)的似然概率。由于涉及难以计算的正则化项(所有可能状态的求和),直接计算梯度很困难。

对比散度是一种有效的近似训练算法,其核心步骤如下:

  1. 正相:将训练数据的显神经元状态“钳制”住,让隐神经元根据概率规则自由演化若干步,采样得到一个“重建”的完整状态(包含显和隐神经元)。
  2. 负相:释放所有钳制,让整个网络(显和隐神经元)自由演化若干步,采样得到一个“自由运行”状态。
  3. 权重更新:根据正相和负相采样结果的外积差来更新权重。
    ΔW_ij ∝ <y_i * y_j>_data - <y_i * y_j>_model
    这相当于降低数据状态的能量,同时提高模型自由运行状态的能量。

应用与变体

玻尔兹曼机及其变体有广泛的应用:

  • 模式补全与去噪:与霍普菲尔德网络类似。
  • 分类:将类别标签也作为显神经元的一部分。训练时,输入特征和标签都被钳制。预测时,只钳制输入特征,让标签神经元自由演化,其稳定状态即为预测结果。
  • 受限玻尔兹曼机:这是一种简化结构,显神经元与隐神经元之间有连接,但显神经元内部和隐神经元内部没有连接。这大大简化了训练,是构建深度信念网络的基础模块。
  • 深度玻尔兹曼机:由多层受限玻尔兹曼机堆叠而成,具有更强的特征学习能力。

总结

本节课我们一起学习了霍普菲尔德网络和玻尔兹曼机。

  • 我们从循环网络的基本概念出发,引入了能量函数来分析其动态收敛性。
  • 霍普菲尔德网络利用能量最小化原理,实现了强大的内容寻址记忆功能。
  • 玻尔兹曼机通过引入概率翻转和隐神经元,将确定性模型扩展为概率生成模型,极大地提高了容量和灵活性。
  • 这些模型深刻地连接了物理学(统计力学)和机器学习,其思想至今仍在深度学习领域产生回响。

这些工作因其基础性贡献获得了诺贝尔物理学奖的认可,展示了跨学科研究的巨大威力。

26:图神经网络 🕸️

在本节课中,我们将要学习图神经网络。图神经网络是一种专门用于处理非欧几里得数据(如图结构数据)的深度学习模型。我们将从图的基本概念开始,逐步深入到图卷积网络和图注意力网络的核心原理。


概述

到目前为止,我们学习了多种深度学习模型。多层感知机作为通用函数逼近器,可以通过反向传播和梯度下降的变体进行训练。卷积神经网络则专门处理图像和空间数据,利用卷积层捕捉空间模式并通过池化降低维度。序列到序列模型(如RNN、LSTM和Transformer)则擅长处理文本或时间序列等有序数据。

然而,现实世界中还存在大量非欧几里得数据,例如社交网络、分子结构、知识图谱、3D网格或点云。这些数据的特点是节点间的“距离”概念不直接,邻域结构不规则且节点连接数可变。传统的CNN和MLP模型难以直接处理这类数据,因此我们需要图神经网络。


什么是图?

图由两个基本元素组成:顶点(或节点)的集合 V 和边(或连接)的集合 E。图可以表示为 G = (V, E)

以下是图的主要类型:

  • 无向图:边没有方向,表示双向关系。
  • 有向图:边有方向,表示单向关系。

为了进行计算,我们通常使用矩阵来表示图。最常用的是邻接矩阵 A,它是一个 n x n 的矩阵(n为节点数)。矩阵中的元素 A_ij 表示从节点 i 到节点 j 是否存在边(通常1表示存在,0表示不存在)。在使用前,需要明确约定矩阵的读取方式(例如,行代表源节点,列代表目标节点)。


图上的学习任务

在图数据上,我们可以执行三种主要类型的任务:

  • 节点级任务:例如节点分类或回归。给定一个节点及其与其他节点的关系,预测该节点的属性(例如,在社交网络中预测用户是否吸烟)。
  • 边级任务:例如链接预测。预测两个节点之间是否存在边,这在推荐系统中非常有用。
  • 图级任务:例如图分类或图匹配。对整个图的属性进行预测(例如,判断一个分子图是否具有某种毒性)。

本节课我们将重点讨论节点级分类任务。


从多层感知机到图卷积

上一节我们介绍了图的基本概念和学习任务,本节中我们来看看如何将熟悉的模型应用于图数据。

最直接的想法是使用多层感知机。如果我们已经有了节点的特征向量,可以直接将其输入MLP进行分类。然而,这种方法完全忽略了节点之间的连接关系。

如果信息存储在边上,而非节点上呢?我们可以从目标节点的所有邻边中聚合信息(例如求和或取平均),将聚合后的边信息转化为一个新的“节点特征”,再输入MLP。

上述方法的核心思想是:为了更新一个节点的信息,我们需要聚合其邻居的信息。这引出了图卷积的概念。

在CNN中,卷积核在规则的网格上滑动,聚合局部像素信息。在图卷积中,我们进行一个类似但更通用的三阶段过程:

  1. 消息传递:每个节点将其特征信息发送给它的邻居节点。
  2. 消息聚合:目标节点收集来自所有邻居的消息。
  3. 节点更新:目标节点结合自身上一轮的特征和聚合的邻居信息,更新自己的特征。

更形式化地,对于节点 v 在第 k+1 层的嵌入 h_v^(k+1),我们可以用以下公式描述均值聚合策略:
h_v^(k+1) = σ( W_k * CONCAT( h_v^(k), MEAN( {h_u^(k) for u in N(v)} ) ) )
其中,N(v) 是节点 v 的邻居集合,W_k 是可训练权重矩阵,σ 是非线性激活函数。

通过堆叠多个这样的层,我们可以构建一个深度图卷积网络,最终得到可用于分类等任务的节点嵌入 z_v


训练图神经网络

训练图神经网络与训练其他神经网络类似。我们定义一个损失函数,例如用于节点分类的交叉熵损失:
L = - Σ [ y_v * log(ŷ_v) ]
其中 y_v 是节点 v 的真实标签,ŷ_v 是将最终节点嵌入 z_v 通过一个分类器(如MLP)后得到的预测概率。

模型中的可训练参数(如图卷积层中的权重 W_k 和分类器权重)通过反向传播计算梯度,并使用随机梯度下降或其变体进行优化。


图注意力网络

上一节我们介绍了基础的图卷积网络,它平等地对待所有邻居。本节中我们来看看如何让模型学习邻居的重要性差异,即图注意力网络。

在基础的GCN或GraphSAGE中,聚合时对每个邻居赋予相同的权重(如1/|N(v)|)。但在现实中,不同邻居对目标节点的重要性可能不同。图注意力网络引入了注意力机制,为每个邻居分配一个可学习的权重 α_vu

计算注意力权重的过程如下:

  1. 计算节点对 (v, u) 的注意力系数 e_vue_vu = a( W * h_v, W * h_u ),其中 a 是一个可学习的函数(如一个单层神经网络),W 是共享的权重矩阵。
  2. 使用softmax函数对节点 v 的所有邻居的注意力系数进行归一化,得到最终的注意力权重:α_vu = softmax_u(e_vu) = exp(e_vu) / Σ_{k∈N(v)} exp(e_vk)
  3. 使用加权和聚合邻居信息:h_v‘ = σ( Σ_{u∈N(v)} α_vu * W * h_u )

为了稳定学习过程并捕捉不同类型的邻居重要性,我们可以使用多头注意力。即使用多个独立的注意力函数(“头”)并行计算,然后将它们的输出连接或求平均:
h_v‘ = CONCAT( head_1, head_2, ..., head_h )h_v‘ = MEAN( head_1, head_2, ..., head_h )

注意力权重 α_vu 不是额外的独立参数,而是通过可学习的函数 a 和共享权重 W 从节点特征中计算得出的,因此可以在端到端的训练过程中与网络其他部分一同优化。


总结与展望

本节课中我们一起学习了图神经网络的基础知识。我们首先了解了为什么需要GNN来处理非欧几里得数据。然后,我们学习了图的基本表示和任务类型。接着,我们从MLP出发,逐步推导出图卷积网络的核心思想——通过聚合邻居信息来更新节点表示。我们还探讨了如何训练GNN。最后,我们介绍了更先进的图注意力网络,它通过可学习的注意力机制区分邻居的重要性。

图神经网络是处理社交网络、推荐系统、药物发现、知识图谱等复杂关系数据的强大工具。通过消息传递、聚合和更新的框架,GNN能够有效地利用图的结构信息。像Dropout、特征增强等标准深度学习技巧也可以应用于GNN的训练。此外,图数据本身也可以通过添加虚拟节点/边或子图采样等方式进行增强,以提升模型性能。


本教程内容主要基于斯坦福大学、密歇根大学及卡内基梅隆大学的相关课程讲义,以及原始研究论文(如Graph Convolutional Networks, GraphSAGE, Graph Attention Networks)。

1:神经网络导论 🧠

在本节课中,我们将学习神经网络的基本概念、其历史渊源以及它们为何能成为现代人工智能的核心。我们将从认知科学和大脑模型出发,逐步理解人工神经网络的工作原理和强大能力。

概述:从大脑到机器

神经网络如今已主导了人工智能领域,成功应用于各种模式识别、预测和分析问题。它们最初是作为大脑或更广义的认知的计算模型而诞生的。最早的认知模型是联想主义,而更近期的模型则是连接主义,即神经元通过连接构成网络,大脑的工作机制就编码在这些连接之中。当前的神经网络模型正是连接主义机器。

1. 历史背景:连接主义与早期模型

上一节我们提到了神经网络的思想根源。本节中,我们来看看其具体的历史发展脉络。

1.1 联想主义与连接主义

人类大脑的运作机制一直是科学探索的焦点。柏拉图最早提出了“联想主义”模型,认为大脑通过形成关联来运作,就像巴甫洛夫的狗将铃声与食物关联起来一样。然而,联想主义并未解释这些关联存储于何处以及如何存储。

随着显微镜的发展,科学家发现大脑由大量相互连接的细胞(神经元)构成。哲学家亚历山大·贝恩在1873年首次提出,信息存储在大脑的连接之中,这标志着“连接主义”思想的诞生。他假设大脑是一个由简单处理单元(神经元)组成的网络,所有知识都存储在这些单元之间的连接里。

这与现代计算机的冯·诺依曼架构(处理器与内存分离)有根本区别。在连接主义机器中,程序就存在于连接之中,计算机本身就是程序。

1.2 第一个神经元数学模型

为了用数学描述大脑单元,神经生理学家沃伦·麦卡洛克和逻辑学家沃尔特·皮茨在1943年提出了第一个神经元数学模型。该模型将神经元视为一个布尔阈值单元:

  • 神经元通过兴奋性突触抑制性突触接收来自其他神经元的信号。
  • 如果总的兴奋性信号超过某个阈值,且没有抑制性信号,则神经元“放电”(输出1),否则不放电(输出0)。

这个简单的模型可以组合实现基本的逻辑门(如AND、OR),从而执行命题逻辑,为将大脑视为计算设备提供了理论基础。

1.3 学习规则的出现

模型需要学习机制。唐纳德·赫布在1949年提出了著名的赫布学习规则,其核心思想是“一起放电的神经元会连接在一起”。用数学公式表示为:
Δw = x * y
其中,xy是相连神经元的激活值,w是它们之间的连接权重。当两个神经元同时激活时,它们之间的连接会增强。

然而,赫布规则存在一个根本问题:权重只会增加,永不减少,这会导致网络最终变得不稳定。

2. 感知机:第一个可学习的神经网络模型

上一节我们看到了神经元模型和学习规则的雏形。本节中我们来看看第一个具有实用学习算法的神经网络模型——感知机。

2.1 罗森布拉特感知机

弗兰克·罗森布拉特在1958年提出了感知机模型,它是对麦卡洛克-皮茨神经元的扩展,并引入了有监督学习规则。

  • 模型:一个感知机单元接收多个实值输入 x1, x2, ..., xn,每个输入对应一个权重 w1, w2, ..., wn。它计算加权和,并加上一个偏置 b,然后通过一个阈值激活函数(如阶跃函数)产生输出。
    输出 = 阶跃函数( Σ(wi * xi) + b )
  • 学习规则:与赫布规则不同,感知机学习规则引入了目标值。权重根据输出误差和输入值进行更新:
    Δwi = 学习率 * (目标输出 - 实际输出) * xi
    这个规则被证明可以完美分类线性可分的数据。

2.2 单层感知机的局限性

尽管感知机很强大,但单个感知机(单层网络)的能力有限。最著名的例子是它无法表示“异或”(XOR)这样的布尔函数。这是因为单个感知机在输入空间中只能画出一条直线(或超平面)作为决策边界,而XOR问题需要两条线才能分开。

3. 多层感知机:走向通用性

单层感知机的局限性曾使神经网络研究陷入低谷。解决之道在于将多个感知机连接成网络。

3.1 网络化带来强大能力

通过将感知机排列成层(输入层、隐藏层、输出层),就构成了多层感知机。隐藏层的输出不作为最终输出,但其计算对最终结果至关重要。

  • 计算任意布尔函数:通过组合多个感知机,MLP可以表示任何布尔函数。例如,用三个感知机可以构建一个实现XOR功能的网络。
  • 通用分类器:对于实值输入,单个感知机的决策边界是一个超平面。通过组合多个超平面,MLP可以逼近任意复杂的决策边界,从而对任何形状的数据区域进行分类。这意味着MLP是通用分类器

以下是构建一个能识别“五边形”内区域的MLP的步骤:

  1. 用五个感知机分别定义五边形的五条边,每个感知机输出“1”表示点在边的某一侧。
  2. 将这五个感知机的输出连接到一个最终的感知机(执行AND操作),仅当所有五个输入都为1(即点在五边形内)时,最终输出才为1。

3.2 通用函数逼近器

MLP的能力不仅限于分类。通过使用合适的激活函数(如Sigmoid、ReLU)替代简单的阶跃函数,MLP还可以逼近任何连续函数。

  • 思路:可以将复杂的连续函数看作由许多窄矩形拼接而成。每个矩形可以由一个小的子网络(如两个感知机)来生成,该子网络在特定输入区间内输出一个常数值。通过将许多这样的子网络按不同高度缩放并相加,就可以以任意精度逼近原函数。

因此,MLP也是通用函数逼近器。理论上,给定足够多的神经元和层,它可以表示任何从输入到输出的映射关系。

总结

本节课我们一起学习了神经网络的基础知识:

  1. 历史与理念:神经网络源于对大脑(连接主义)和认知(联想主义)的建模尝试。
  2. 核心单元:从麦卡洛克-皮茨神经元模型到罗森布拉特感知机,我们有了可计算、可学习的基元。
  3. 关键突破:单层感知机能力有限,但通过堆叠成多层感知机,网络获得了强大的表达能力。
  4. 通用性:MLP可以作为通用分类器逼近任何决策边界,也可以作为通用函数逼近器模拟任何连续函数。这解释了为什么神经网络能够成为解决众多AI任务的强大工具。

在下一节课中,我们将更深入地探讨MLP的架构,理解“深度”的含义,并研究其表示能力与网络深度、宽度的关系。

2:课程介绍 🧠

在本节课中,我们将对CMU 2025年春季的《深度学习导论》课程进行全面的介绍。我们将了解课程背景、教学团队、课程结构、评分方式以及学术诚信等重要信息。


🌍 神经网络的崛起

神经网络已经从理论概念转变为实用工具,正在塑造我们生活和互动的众多领域。

例如,像ChatGPT这样的模型正在为问题解决和上下文理解设定新的标准。

在视觉应用领域,DALL-E等模型正在突破机器生成照片级真实感或高度想象力视觉内容的边界。

科学领域也取得了突破,例如AlphaFold以前所未有的准确性预测蛋白质结构,对医学和生物化学等领域产生了积极影响。

本课程将为你提供参与这些进步所需的技能和理解,无论是构建、应用还是批判性地分析它们。


🗣️ 从语音助手到对话式AI

早期的系统如Siri和Alexa为道路铺平了基础,现在我们有了像ChatGPT-4、Bard和Claude AI这样的对话式AI。这些系统不仅仅是回应,它们正在与我们进行有意义、细致的互动,成为技术问题解决的合作者。


👁️ 计算机视觉的进步

在计算机视觉领域,我们现在有了Stable Diffusion,它使我们能够以几年前无法实现的方式创建和编辑图像。

我们还有NeRF(神经辐射场),它仅凭少量照片就能创建新的3D建模可能性,构建空间和物体。

自动驾驶汽车也在持续展示神经网络的力量和局限性。Waymo在一些城市创建了自动驾驶出租车,而特斯拉的全自动驾驶汽车也代表了实现完全自主导航的潜力和持续挑战。


👨‍🏫 教学团队

本课程的主讲教师是Bhiksha Raj教授。教授设有开放的办公时间,欢迎你随时拜访。

本课程有大约20名助教。如果你想联系我们,我们的姓名和邮箱都列在课程网页上。助教来自匹兹堡、基加利和硅谷。我们提供远程和线下的办公时间,这些信息也都列在网页上。


📚 课程先修要求

本课程是为已经掌握Python编程的人设计的。

我们强烈建议你具备微积分和线性代数知识。我们也推荐向量微积分和软件工程知识,但这些不像Python那样是优先要求。


🧩 通用课程主题

课程涵盖的入门级神经网络结构包括:

  • 多层感知机
  • 卷积神经网络
  • 循环神经网络
  • 玻尔兹曼机

除此之外,教授还将讲授以下主题:

  • 生成模型
  • 对抗模型
  • 图神经网络
  • 变换器

这些结构的实际应用重点领域包括:

  • 计算机视觉
  • 文本处理与机器翻译
  • 分布建模与数据生成
  • 语音识别

💻 课程实践重点

本课程非常注重实现,包含大量编码。我们使用Python和PyTorch。虽然你可以使用其他语言和工具包,但如果你需要向助教寻求帮助,则需要使用课程指定的语言和工具包。


⚠️ 课程的挑战

这是一门难度很高的课程,工作量很大。我们期望你投入大量时间学习和努力,以更好地理解课程材料。

如果你以A的成绩完成这门课,你很可能已经准备好进入深度学习行业工作。

我们通过测验来检验你对讲座主题的理解,通过作业来教你如何实现和优化这些模型。如果你是研究生级别的学生,我们还有项目让你接触现实世界的问题。


🎥 讲座

课程讲座在线下进行,同时通过Zoom直播。讲座会被录制并上传到YouTube和MediaTech平台。

观看讲座非常重要,因为除了讲解主题外,你的测验也基于这些幻灯片。我们还会监控你的出勤情况,稍后会详细说明。


📝 测验

你总共会有14次测验。每周五发布一次,每周日截止。在14次测验中,我们会取你成绩最好的12次。每次测验有10个问题,我们会取你三次尝试中最好的一次成绩。


🧪 习题课、黑客松和训练营

我们还有由助教带领的习题课、黑客松和训练营。我们强烈建议你参加所有这些活动。

  • 习题课:每周五在教室举行,共14次。它们是动手实验课,涵盖具体的实现细节,你可以跟着互动学习。
  • 黑客松:每周六举行,地点待定,在匹兹堡和基加利线下进行。匹兹堡校区会提供披萨。
  • 训练营:每次作业发布后都会有一个训练营,在作业发布后的那个周六举行。它们是对作业的手把手指导。针对11685课程的学生,还有一次针对作业5的训练营。

📄 作业

作业包括四个主要作业、加分作业以及一个自动微分加分作业。

主要作业分为两部分:

  • 第一部分:自动评分,你需要将代码上传到AutoLab。
  • 第二部分:Kaggle竞赛。

此外还有作业加分和自动微分加分。这些加分旨在给你机会弥补在其他部分丢失的分数,它们不会直接加到总成绩曲线上。

课程还提供学习小组。


👥 学习小组

你必须加入一个学习小组,每个小组会被分配一名助教导师。

我们建议你的小组由3到4人组成。如果你愿意,也可以有更多人。

如果你没有学习小组或找不到小组,或者你的小组解散了,请告诉我们,我们会将你分配到一个新小组。


🎯 课程项目

如果你是11785课程的学生,你需要完成一个原创项目。

11685课程的学生将完成一个我们指定的项目,称为作业5。

如果你是11485课程的学生,则无需担心这部分内容,请不要报名参加项目。

项目旨在锻炼你理解和实现超出作业范围内容的能力。范围很广,你可以做一个完全原创的想法和全新的模型设计,也可以实现一些已被验证的模型并尝试改进它。


🏃 如何取得优异成绩

为了在这门课上取得好成绩,我们建议你参加每一次讲座。出勤率会计入分数,这可能决定你是得A还是B。我们将通过每次讲座期间发布的投票来跟踪你的出勤情况。

我们还建议你观看我们在YouTube上发布的每一个“习题课0”视频。除了编码帮助,我们还提供了计算基础设施的信息。我们要求你在办公时间就这些主题提问之前,先参考这些视频。

作业1的第一部分也非常有价值,因为作业1第一部分的所有内容都将在后续课程中使用。因此,在这部分拿到100分至关重要。


💬 课程沟通

所有课程沟通都通过Piazza进行。请习惯使用Piazza,通过它接收通知并每天阅读,因为投票将通过它发布,教授和助教将通过它与你们沟通,公告也会在那里发布,你私下或公开提出的问题以及同学的回答也会在那里。


📖 额外阅读材料

如果你想在教授课堂讲授内容之外更深入地探讨主题,可以在课程网站上找到额外的阅读材料。


🤝 学习小组的价值

学习小组尤其有价值,因为你们可以一起讨论作业问题和解决方案、论文以及一般的课堂内容和测验。我们强烈鼓励你们每周聚一次,你甚至可以根据你的学习小组组建项目小组。

每个学习小组都有一名导师。导师的存在是为了在你落后或遇到困难时随时联系。如果你在课程之外遇到问题,或者需要找到联系教授的方式,你的助教导师都会以各种方式支持你。

我们致力于确保课程体验是积极的,通过让助教直接与你联系,我们希望这能帮助你在课程中找到立足点,取得好成绩。


🧑‍🏫 项目小组导师

在11785课程部分,每个项目小组都会被分配一名项目导师。这不适用于11685或11485课程,只有785课程的学生会有项目小组导师。

这些导师会与小组保持联系。除了助教导师,你的小组还可以寻求一位外部导师,如果你希望为你的项目获得额外指导的话。


📊 成绩构成

你的分数基于四种类型的考核进行评估:出勤、每周测验、作业和团队项目。

  • 出勤:必须线下参加或通过Zoom参加。我们将通过课堂投票获取你的出勤数据。课程开始时会有一次初始出勤投票,然后教授每节课会有三到四次投票,你需要在一分钟内回答基于他刚刚讲授内容的问题。如果你在硅谷或时区导致你无法在合理时间参加讲座,我们理解。我们希望你能通过MediaTech观看讲座。MediaTech允许我们跟踪谁观看了讲座以及观看了多久,这样我们可以确定你以这种方式参加了。如果你在YouTube上观看,则不计入出勤。请在讲座发布后一周内观看。
  • 测验:每周测验基于教授的幻灯片。通常,教授不会讲解幻灯片上的所有信息,因此你的任务是仔细阅读幻灯片来回答测验问题。偶尔,测验中也会包含基于需要你阅读的出版物的题目,但大多数情况下,测验都基于课堂幻灯片。
  • 作业:每个作业的第一部分使用PyTorch,本课程为你提供了自己的工具包。第一部分作业是自动评分的,你需要将它们上传到AutoLab。如果你的成绩在AutoLab上显示为0,这意味着你得了零分,你需要重新提交,修复你遇到的错误,以获得你在本地可能看到的分数,否则我们不会知道你实际上应该得到更好的成绩。你必须在AutoLab上修复它。对于作业第二部分,如果你提前提交作业第一部分,你会获得加分。这个加分是在你第一部分成绩之上的额外加分。相比之下,第二部分的提前提交是第二部分成绩的一部分,价值10分。你不需要为这次提前提交获得任何类型的分数,你只需要提交即可。第二部分剩余的90%成绩由相对于截止分数的最终分数决定。你的截止分数是在截止点之间线性插值得出的。这只是高、中、低截止点的一个示例,每个作业会有所不同。如果你是Kaggle竞赛中得分最高的学生,你将获得额外的10分加分。因此,总的来说,如果你按时提交并获得最高分,你总共可以获得110分。


🗓️ 项目与截止日期

请尽快寻找你的项目团队成员。这是针对研究生级别课程的。对于11785项目,最多4人;对于11685的作业5项目,最多3人。

你需要提交一份提案、一份中期报告、一份最终报告以及一个5分钟长的视频演示。你的最终报告不仅会由助教和教授评审,还会由一组与CMU有关联且在业界工作的外部评审员评审。你的视频项目演示将由外部评审员、助教、教授以及班上所有其他学生观看。11485、685和785的学生都将观看你的视频演示来为你的最终报告演示评分。

评分构成如下:

  • 每周测验:14次,其中2次不计入最终成绩。
  • 四个作业:每个作业分为两部分,AutoLab第一部分和Kaggle第二部分。
  • 团队项目:如果你不是11485的学生,你的中期报告和演示以及最终报告是你成绩的一部分。
  • 出勤:占1分。

请注意,作业第一部分的截止日期与第二部分是分开的。作业第二部分有多个截止日期:提前提交截止日期、按时提交截止日期和延迟宽限截止日期。“宽限”指的是教授给所有学生的10个“宽限日”,你可以一次性用完,也可以在整个学期内分配使用。它旨在允许你延迟提交,但会有10%的罚分。对于每个延迟提交的作业,你都会受到10%的罚分。总共你有10个宽限日,我们会接受提交直到你用尽宽限日。你可以联系你的学习小组导师来提交延迟的作业。


⚖️ 常识与诚信问题

请随时在Piazza上发布任何问题,我们力求立即回复。话虽如此,我们希望你的代码相关问题有一定的结构,所以给你的帖子起一个好标题。重新阅读你的问题和描述,确保清晰明了。不要只是粘贴整个文件或添加一个笔记本,这是不可接受的。我们希望你能解释你尝试了什么,已经得到了什么结果。错误和扩展的堆栈跟踪应该被呈现出来,我们想知道你已经尝试修复了什么。否则,这只是额外的工作,也不是寻求帮助的有效方式。

最后,关于个人和学术诚信。你是CMU的学生,来这里是为了学习深度学习,而不是学习如何假装学习。因此,我们期望你不要侮辱自己,不要降低自己的诚信。请诚实地对待你的工作。如果有问题,随时联系。

在你的学习小组中,我们允许你们一起工作,并期望你们分担工作量,特别是在作业二的探索阶段。然而,你的最终提交必须是自己的。你们不能只是互相复制粘贴,你必须实际完成自己的工作,尽管你们可以互相帮助调试、讨论和一起解决问题。

抄袭代码是违反学术诚信的行为。


🎉 总结

本节课中,我们一起学习了CMU《深度学习导论》课程的整体框架。我们了解了神经网络的重要性、课程的教学团队与先修要求、涵盖的核心主题与结构、以及取得好成绩的关键——包括积极参与讲座、完成作业和测验、利用学习小组和导师资源。我们还明确了课程的评分构成、项目要求以及至关重要的学术诚信准则。希望本介绍能帮助你为这门富有挑战性但又收获颇丰的课程做好准备。

3:神经网络作为通用逼近器 🧠

在本节课中,我们将深入探讨神经网络能够表示什么,以及其表示能力在深度、宽度和激活函数方面的限制。我们将从布尔函数、分类器到实值函数,逐步理解神经网络的通用逼近能力。


🧩 神经网络基础回顾

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

神经网络是能够接收输入并产生输出的函数。其基本形式模仿了人脑的网络结构,由称为神经元的简单计算单元组成。人工神经网络中的基本单元是感知机。

感知机的数学表示如下:它计算输入的加权和,如果该和超过阈值,则输出1;否则输出0。公式表示为:

输出 = 1,如果 Σ(wi * xi) > 阈值;否则为 0

另一种等效表示是计算输入的仿射组合(加权和加上偏置项),然后通过一个阈值激活函数。公式为:

z = Σ(wi * xi) + b
输出 = 1,如果 z > 0;否则为 0

我们可以用其他激活函数(如Sigmoid、Tanh、ReLU)替换这个阈值函数,但在本讲中,我们主要使用基本的阈值激活感知机。


🔗 多层感知机与深度定义

单个感知机能力有限,因此我们通常使用多层感知机。要理解“深度”,首先需要了解有向无环图。

在有向无环图中,深度定义为从源节点(输入)到汇节点(输出)的最长路径长度。在神经网络中,深度就是从任何输入到任何输出的最长路径上的神经元数量。

层是相对于输入具有相同深度的神经元集合。深度大于2的网络被称为深度网络。


🧮 作为通用布尔函数的多层感知机

上一节我们提到感知机可以构成布尔函数。本节中我们来看看其通用性及深度的重要性。

单个感知机非常强大,可以计算需要指数级数量布尔门才能完成的函数(例如“多数表决”门)。然而,单个感知机无法计算异或函数。

通过组合感知机(即构建多层感知机),我们可以构成任何布尔电路,这意味着多层感知机是通用布尔函数——对于任意布尔函数,都存在一个MLP可以计算它。

一个关键问题是:需要多少层?任何布尔函数都可以写成析取范式。基于此,仅含一个隐藏层的MLP就足以表示任何布尔函数,是通用布尔函数。

但是,对于最坏情况(如棋盘格函数),单隐藏层网络可能需要指数级数量的感知机(约 2^(n-1) 个)。

如果允许网络更深,例如将异或函数视为输入的级联,我们可以用线性数量(约 3(n-1) 或 2(n-1) 个)的感知机构建网络。深度可以将网络规模从输入规模的指数级降低到线性级。

核心结论:MLP是通用布尔函数。单隐藏层MLP即可实现,但可能需指数级宽度。更深的网络可以用少得多的神经元(线性级)表示相同函数。


🎯 作为通用分类器的多层感知机

接下来,我们看看神经网络如何处理实值输入并构成分类器。

单个感知机构成一个阶跃函数,其决策边界是一个超平面,因此它是一个线性分类器。但单个感知机无法表示异或所需的复杂边界。

通过组合多个感知机,我们可以构建复杂的决策边界,例如多边形区域。基本思想是为边界多边形的每条边设置一个感知机,然后对它们的输出求和并应用阈值,从而得到多边形内部的区域。

我们可以将这种方法推广到任意复杂的决策边界,方法是用许多小的多边形(多面体)来近似该区域,每个多边形对应一个子网络,然后对这些子网络的输出进行“或”运算。

即使是单隐藏层MLP,也可以通过使用无限多的神经元来近似任何分类边界,因此MLP是通用分类器。

然而,深度再次显示出优势。对于复杂的决策边界(如交叉网格),浅层网络可能需要数百个神经元,而深层网络通过将问题结构化为多层异或计算,可以用少得多的神经元实现。

核心结论:MLP是通用分类器。单隐藏层MLP即可实现,但可能需指数级宽度。更深的网络可以用指数级更少的神经元表示相同函数。


📈 作为通用逼近器的多层感知机

最后,我们探讨神经网络如何逼近实值函数。

对于标量输入,一个三单元的MLP可以配置为在两个阈值之间输出1,否则输出0,从而构成一个“窗函数”。通过组合许多这样的窗函数,可以逐步逼近任何连续标量函数。

对于多维输入,思路类似。一个具有许多感知机的单隐藏层网络,对其输出求和,可以产生类似“圆柱体”的函数(在某个圆形区域内输出较高,外部输出较低)。通过在不同位置放置许多这样的“圆柱体”,并缩放其高度以匹配目标函数在该区域的值,我们可以求和这些缩放后的输出来逼近任何连续多元函数。逼近误差可以任意小。

因此,具有单隐藏层的MLP是通用函数逼近器,但同样可能需要无限宽度。更深的网络可以用更少的神经元达到相同的逼近精度。


⚖️ 深度、宽度与激活函数的权衡

我们讨论了深度的重要性,但网络的每一层也必须足够“宽”,以将相关信息传递到后续层。

如果第一层太窄(例如,用8个阈值神经元试图捕获16条线构成的交叉网格边界),由于阈值激活函数只提供“在边界哪一侧”的二元信息,而不提供“距离边界多远”的信息,后续层将无法恢复丢失的几何细节,从而无法构成完整的边界。

解决方案是使用具有梯度的激活函数(如Sigmoid、ReLU、Leaky ReLU)。这些函数能提供关于输入距离边界远近的信息,使得后续层即使在前层较窄时,也有可能利用这些梯度信息恢复更复杂的模式。但这并非绝对保证。

核心结论

  1. MLP是通用布尔函数、通用分类器和通用函数逼近器。
  2. 单层MLP可以逼近任何函数,但可能需要指数级甚至无限宽度。
  3. 更深的MLP可以用少得多的神经元达到相同的精度。
  4. 具有梯度的激活函数能使网络更具表现力,有助于信息在层间传递。

🎓 本节课总结

本节课我们一起学习了:

  • 神经网络作为通用布尔函数的表示能力及深度的重要性。
  • 神经网络作为通用分类器,如何构成复杂决策边界。
  • 神经网络作为通用函数逼近器,如何近似连续实值函数。
  • 深度、宽度和激活函数梯度之间的权衡:深度可以大幅减少所需神经元数量,但每层仍需足够宽度或使用梯度激活函数来传递有效信息。

我们了解到MLP可以模拟任何函数,但如何让它们模拟某个特定的期望函数呢?这就是训练MLP的任务,我们将在下一节课开始学习。

4:训练(第一部分) 🧠

在本节课中,我们将要学习如何训练神经网络。我们将从最简单的感知器学习规则开始,探讨其在多层感知器(MLP)中遇到的困难,并最终引出通过经验风险最小化来学习网络参数的核心思想。


神经网络作为函数

上一节我们介绍了神经网络是通用函数逼近器。这意味着,给定一个函数,总存在一个神经网络可以以任意精度逼近它。然而,这并不意味着我们知道如何构建这个网络。

神经网络本质上是数学对象。它们接收一组数字作为输入,经过内部一系列数字运算,最终输出另一组数字。因此,要让神经网络处理像语音、图像或游戏状态这样的任务,我们首先需要将这些输入和输出转换为数字形式。本节课我们暂不讨论这个表示问题,而是假设输入和输出已经是数字形式,并专注于如何构建能执行所需函数的网络。


学习网络参数

一个神经网络可以表示为:
y = f(x; W)
其中,x 是输入,W 代表网络中所有感知器的权重和偏置参数集合,y 是输出。学习神经网络,实质上就是确定参数 W 的值,使得网络能够计算我们想要的特定函数。

理想情况下,我们希望网络在所有可能的输入上都精确计算目标函数 g(x)。这需要最小化网络输出 f(x; W) 与目标 g(x) 之间的误差在整个输入空间上的积分。然而,在现实中,我们并不知道完整的函数 g(x)


从样本中学习

我们无法获知完整的函数 g(x),但获取其样本却相对容易。例如,我们可以收集一批带有标签的图像或带有转录文本的语音片段。这些输入-输出对构成了我们的训练样本

因此,我们的策略转变为:利用这些训练样本来估计网络参数,使得网络在这些样本点上的输出尽可能接近目标值。我们计算所有训练样本上的平均误差(称为经验误差),并尝试最小化它。我们希望,通过这种方式学到的网络,在未见过的数据点上也能表现良好,但这并非绝对保证。

以下是关于学习网络的基本假设:

  • 网络架构必须具有足够的容量(即足够的参数)来近似目标函数。
  • 网络是一个参数化函数,其参数是所有权重和偏置。
  • 参数必须通过学习来最佳地近似目标函数。
  • 仅凭少量训练样本无法完美地学习到函数。

感知器学习规则

让我们从一个最简单的网络开始:单个感知器(带阈值激活函数)。这对应于一个线性分类器。给定一组线性可分的训练样本(红点和蓝点),学习感知器意味着找到一个超平面(由权重 W 定义),使得所有红点位于超平面的一侧(输出为1),所有蓝点位于另一侧(输出为0)。

罗森布拉特提出了一个简单的在线学习算法:

  1. 随机初始化权重 W
  2. 遍历所有训练样本。
  3. 对于每个样本 x
    • 如果分类正确,则不更新权重。
    • 如果分类错误(例如,一个正类样本被分类为负类),则将 W 更新为 W + x(因为对于正类样本,最优的 W 方向就是 x 本身)。
    • 如果是一个负类样本被错误分类,则将 W 更新为 W - x
  4. 重复步骤2-3,直到所有样本被正确分类或达到最大迭代次数。

该算法在线性可分的数据上保证能在有限步内收敛到一个解。然而,如果数据不是线性可分的,算法将永远不会停止。


多层感知器(MLP)的学习困境

对于更复杂的非线性决策边界(例如“双五边形”边界),我们需要一个多层感知器网络。虽然我们知道存在一个具有特定架构的网络可以精确计算这个边界,但使用感知器学习规则来训练整个网络会面临巨大挑战。

问题在于,要训练网络中间层的某个感知器,我们需要知道对于每个训练样本,该感知器的“正确”输出应该是什么(即,它应该将样本标记为“正”还是“负”)。然而,我们只有整个网络最终输出的标签(例如,样本属于“双五边形内部”还是“外部”)。

为了确定中间层每个感知器对每个样本的“正确”输出,我们可能需要尝试所有可能的组合。对于一个有 N 个训练样本的网络,这相当于 2^N 种可能的标记方式,这是一个组合优化问题,计算复杂度是指数级的,在实践中不可行。

此外,阈值激活函数几乎处处导数为零,在边界点导数甚至不存在(无穷大)。这意味着,轻微调整权重可能完全不会改变分类误差,因此我们无法获得关于调整方向是否正确的信息。


解决方案:可微分的激活函数与损失函数

为了克服上述困难,我们需要做出两项关键改变:

  1. 使用可微分的激活函数:用平滑的、几乎处处可导的函数(如Sigmoid、ReLU)替换阈值函数。这样,权重的微小变化会导致输出的连续变化,从而我们可以计算输出相对于权重的梯度。
  2. 定义可微分的损失函数:不再直接最小化分类错误计数(它是离散的、不可导的),而是定义一个连续可导的损失函数(或代价函数),它衡量网络输出与目标输出之间的“差异”(称为散度)。常用的散度包括均方误差(用于回归)和交叉熵(用于分类)。

通过最小化在所有训练样本上平均的损失(即经验风险),我们利用梯度信息来指导参数更新,从而避开组合爆炸问题。这个过程称为经验风险最小化,它是一个标准的函数优化问题。

虽然最小化这个代理损失函数不一定能直接最小化我们最终关心的分类错误率,但它为我们提供了一个可操作的、基于梯度的优化路径。


总结

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

  • 学习神经网络就是确定其参数 W,以逼近目标函数。
  • 我们通过拟合训练样本来学习,最小化经验风险(即平均损失)。
  • 对于单层感知器,感知器学习规则可以在线性可分数据上有效工作。
  • 对于多层感知器,使用阈值激活函数会导致组合优化的难题,因为需要确定中间层的标签。
  • 解决方案是采用可微分的激活函数(如Sigmoid、ReLU)和可微分的损失函数
  • 这使我们能够通过梯度下降等优化方法,以经验风险最小化的框架来训练整个网络,从而避免了组合爆炸问题。

下一讲,我们将深入探讨如何具体计算这些梯度,即著名的反向传播算法。

5:训练神经网络(第二部分)🚀

在本节课中,我们将学习如何训练神经网络来逼近目标函数。我们将深入探讨函数优化的核心概念,特别是梯度下降法,并定义训练神经网络所需的所有关键组成部分:网络结构、输入输出表示以及损失函数。


函数优化与导数回顾 🔍

上一节我们介绍了神经网络的训练目标是最小化损失函数。本节中我们来看看如何通过优化方法找到使损失最小的网络参数。

假设我们有一个函数 $ y = f(x) $,我们想找到使 $ f(x) $ 最小的 $ x $ 值。在单变量情况下,我们知道在最小值点,函数的导数为零,且二阶导数为正。

导数的定义是:对于一个连续函数,在任意点 $ x $,一个微小的增量 $ \Delta x $ 会导致函数值产生增量 $ \Delta y $。导数 $ \alpha $ 是连接两者的乘数:

\[\Delta y = \alpha \cdot \Delta x \]

这表示函数在局部是线性的。

多变量情况下的导数

当 $ x $ 是一个向量 $ \mathbf{x} = [x_1, x_2, ..., x_n]^T $,而 $ y $ 是一个标量时,情况变得复杂。此时,增量关系变为:

\[\Delta y = \boldsymbol{\alpha} \cdot \Delta \mathbf{x} \]

其中 $ \Delta \mathbf{x} $ 是一个向量增量,$ \boldsymbol{\alpha} $ 必须是一个行向量,其每个元素是 $ y $ 对 $ x $ 各分量的偏导数。这个行向量 $ \boldsymbol{\alpha} $ 被称为函数 $ y $ 对 $ \mathbf{x} $ 的导数

我们通常更常用的是梯度(Gradient)。梯度 $ \mathbf{g} $ 是导数的转置,因此它是一个列向量,与 $ \mathbf{x} $ 生活在同一空间:

\[\mathbf{g} = \nabla_{\mathbf{x}} y = \boldsymbol{\alpha}^T \]

因此,增量关系也可以写成梯度的形式:

\[\Delta y = \mathbf{g}^T \Delta \mathbf{x} \]

这实际上是梯度向量 $ \mathbf{g} $ 与步进向量 $ \Delta \mathbf{x} $ 的内积。

梯度的几何意义

梯度方向是函数值上升最快的方向。反之,负梯度方向就是函数值下降最快的方向。这是因为内积 $ \mathbf{g}^T \Delta \mathbf{x} = |\mathbf{g}| |\Delta \mathbf{x}| \cos\theta $,当 $ \Delta \mathbf{x} $ 与 $ \mathbf{g} $ 方向相同时($ \theta=0 $),内积最大,函数增加最多。

为了找到函数的最小值,我们应该沿着负梯度方向移动。


梯度下降算法 📉

基于以上原理,我们可以设计一个迭代算法来寻找函数最小值,这就是梯度下降法

算法从参数的一个初始猜测值 $ \mathbf{x}_0 $ 开始。在每一步 $ k $,我们计算当前点 $ \mathbf{x}_k $ 的梯度 $ \mathbf{g}_k $,然后沿着负梯度方向更新参数:

\[\mathbf{x}_{k+1} = \mathbf{x}_k - \eta \cdot \mathbf{g}_k \]

其中 $ \eta $ 是一个正数,称为学习率(Learning Rate),它控制着每一步更新的幅度。

学习率的选择至关重要:

  • 学习率太大:可能导致更新步伐过大,在最小值附近震荡甚至发散。
  • 学习率太小:收敛速度会非常慢。

我们重复这个过程,直到梯度接近零(到达临界点),或者连续迭代中函数值的变化非常小。


定义神经网络的训练问题 🧠

现在,我们将梯度下降的思想应用到神经网络的训练中。我们的目标是:给定一组训练样本(输入-输出对),调整网络参数 $ \mathbf{W} $,以最小化损失函数 $ L(\mathbf{W}) $。

为了实现这一点,我们必须明确定义三个核心要素:

1. 网络函数 $ F $

我们使用多层感知机(MLP)作为网络结构。它是一个前馈网络,由输入层、若干隐藏层和输出层组成。每一层的神经元接收前一层输出的加权和(加上偏置),然后通过一个激活函数产生输出。

单个感知机的计算如下:

\[z = \mathbf{w}^T \mathbf{x} + b \]

\[y = f(z) \]

其中 $ f(\cdot) $ 是激活函数。

常见的激活函数包括:

  • Sigmoid: $ f(z) = \frac{1}{1 + e^{-z}} $,输出在 (0,1) 之间。
  • Tanh: $ f(z) = \tanh(z) $,输出在 (-1,1) 之间。
  • ReLU: $ f(z) = \max(0, z) $。
  • Softmax (用于多分类输出层): 这是一个向量激活函数。对于输入向量 $ \mathbf{z} $,其第 $ i $ 个输出为:

    \[y_i = \frac{e^{z_i}}{\sum_{j} e^{z_j}} \]

    它保证所有输出之和为 1,可以解释为概率分布。

2. 输入与输出的表示

  • 输入 $ \mathbf{x} $: 必须是实数向量。例如,图像是像素值向量,语音是特征向量,文本需要被编码为词向量等。
  • 期望输出 $ \mathbf{d} $: 根据任务类型不同,表示方式也不同。
    • 回归任务: $ \mathbf{d} $ 是任意实数值向量。网络输出层通常不使用非线性激活函数。
    • 二分类任务: $ d $ 是 0 或 1。网络输出层使用一个 Sigmoid 神经元,其输出可以解释为属于正类的概率 $ P(class=1|\mathbf{x}) $。
    • 多分类任务(K类): $ \mathbf{d} $ 是一个 one-hot 向量,长度为 K。只有对应真实类别的那个位置是 1,其余全为 0。网络输出层使用 Softmax 激活函数,产生一个 K 维的概率分布。

3. 损失函数(散度)

损失函数衡量网络输出 $ \mathbf{y} $ 与期望输出 $ \mathbf{d} $ 之间的差异。它必须是可微的。

以下是不同任务对应的常见损失函数:

  • 回归任务(L2损失/均方误差):

    \[D(\mathbf{y}, \mathbf{d}) = \frac{1}{2} \|\mathbf{y} - \mathbf{d}\|^2 \]

    其关于网络输出 $ \mathbf{y} $ 的导数为 $ \mathbf{y} - \mathbf{d} $。

  • 二分类任务(交叉熵损失):

    \[D(y, d) = -[d \cdot \log(y) + (1-d) \cdot \log(1-y)] \]

    其中 $ y $ 是 Sigmoid 的输出。当网络完全预测错误(如 $ d=1, y\to 0 $)时,损失会趋于无穷大,给予了严重惩罚。

  • 多分类任务(交叉熵损失):

    \[D(\mathbf{y}, \mathbf{d}) = -\sum_{i=1}^{K} d_i \log(y_i) = -\log(y_c) \]

    其中 $ c $ 是真实类别的索引。因为 $ \mathbf{d} $ 是 one-hot 向量,所以求和后只剩下真实类别对应的项 $ -\log(y_c) $。这促使网络提高对真实类别的预测概率。

一个重要的性质是,对于使用 Softmax 输出层和交叉熵损失的多分类网络,以及对于使用线性输出层和 L2 损失的回归网络,损失函数关于最后一层**线性加权和 $ \mathbf{z} \(** 的导数都具有一个非常简洁的形式:\) \mathbf{y} - \mathbf{d} $(即误差本身)。这大大简化了反向传播的计算。


总结 🎯

本节课中我们一起学习了神经网络训练的核心优化方法。

  1. 我们回顾了导数和梯度的概念,理解了梯度方向是函数上升最快的方向。
  2. 我们介绍了梯度下降算法,它通过不断沿负梯度方向更新参数来寻找函数最小值。
  3. 我们将此算法应用于神经网络训练,并明确了训练所需的三个定义:
    • 网络结构: 多层感知机(MLP)及其激活函数。
    • 数据表示: 输入需为实数向量;输出根据任务(回归、二分类、多分类)采用不同的编码(实数值、0/1、one-hot向量)。
    • 损失函数: 根据任务选择(如L2损失、交叉熵损失),用于衡量预测与目标的差距。

至此,我们已经建立了训练神经网络的理论框架。接下来的课程将深入探讨如何高效地计算这些梯度(即反向传播算法),并处理训练中的实际挑战。

6:训练神经网络(第三部分) 🧠

在本节课中,我们将继续学习如何训练神经网络。我们将深入探讨如何计算损失函数相对于网络参数的梯度,这是梯度下降算法的核心。我们将学习一种名为反向传播的高效算法来完成这一计算。


概述

在之前的课程中,我们了解到,训练神经网络的目标是找到一组权重和偏置,使得网络在训练数据上的平均损失最小。我们使用梯度下降法来迭代更新这些参数。为了实现这一点,我们需要计算损失函数相对于每个参数的梯度。本节课,我们将学习如何通过反向传播算法来高效地计算这些梯度。


梯度下降回顾

首先,让我们回顾一下梯度下降的基本原理。给定一个由输入-输出对组成的训练集,我们为每个训练实例定义一个损失,它衡量了网络实际输出与期望输出之间的差异。整个训练集上的总损失是所有单个实例损失的平均值。

我们的目标是找到一组网络参数(权重 W 和偏置 b),使得总损失最小化。梯度下降的更新规则是:

W ← W - η ∇L(W)

其中,η 是学习率,∇L(W) 是损失函数相对于所有参数的梯度向量。

为了计算这个梯度,我们需要计算每个训练实例的损失相对于每个参数的偏导数,然后取平均值。


计算图与链式法则

为了理解反向传播,我们首先需要理解计算图链式法则的概念。

标量情况

考虑一个简单的函数:y = f(x)。如果我们对 x 做一个小的改变 Δx,那么 y 的相应改变 Δy 近似为:

Δy ≈ (dy/dx) Δx

我们可以用一个图来表示这种关系:x → y,在边上标注导数 dy/dx

现在,考虑一个嵌套函数:y = f(g(x))。我们可以将其表示为:x → g → y。根据链式法则,y 相对于 x 的导数是:

dy/dx = (dy/dg) * (dg/dx)

在计算图中,这相当于沿着从 xy 的路径,将路径上所有边的导数相乘。

多路径情况

如果 y 依赖于多个中间变量 z1, z2, ...,而每个 z 又都依赖于 x(例如 y = f(z1, z2), z1 = g1(x), z2 = g2(x)),那么 y 相对于 x 的导数需要对所有从 xy 的路径求和:

dy/dx = Σ_i (∂y/∂z_i) * (dz_i/dx)

这个“沿所有路径求和”的规则是反向传播的核心。


神经网络中的前向传播

在计算梯度之前,我们需要进行前向传播:将输入数据通过网络,计算所有神经元的中间值和最终输出。

考虑一个具有 L 层的网络。我们用上标 (l) 表示层索引,下标表示神经元索引。

  • y_i^{(l)}:第 l 层第 i 个神经元的输出。
  • z_j^{(l)}:第 l 层第 j 个神经元的加权输入(仿射值)。
  • W_{ij}^{(l)}:从第 l-1 层的第 i 个神经元到第 l 层的第 j 个神经元的连接权重。
  • b_j^{(l)}:第 l 层第 j 个神经元的偏置。

前向传播的步骤如下(为简化,我们将输入层记为第 0 层,y^{(0)} = x):

  1. 对于每一层 l = 1 to L
    • 计算仿射值:z^{(l)} = W^{(l)} y^{(l-1)} + b^{(l)}
    • 应用激活函数:y^{(l)} = f{(l)}(z)

最终,y^{(L)} 就是网络的输出。在此过程中,我们需要保存所有中间值 z^{(l)}y^{(l)},因为它们在后向传播中会被用到。


反向传播:计算梯度

现在,假设我们已经完成了前向传播,并计算出了网络输出 y^{(L)} 和损失值 L。我们的目标是计算损失 L 相对于所有权重 W^{(l)} 和偏置 b^{(l)} 的梯度。

反向传播从网络的输出层开始,向后逐层计算梯度。其核心思想是链式法则

关键步骤

我们定义一些有用的中间梯度:

  • δ^{(l)} = ∂L / ∂z^{(l)}:损失相对于第 l 层仿射值的梯度。

反向传播算法如下:

  1. 输出层梯度:首先计算损失相对于网络输出的梯度 ∂L / ∂y^{(L)}。这取决于我们选择的损失函数(如均方误差、交叉熵等)。
  2. 反向迭代每一层:对于 l = L down to 1
    a. 计算 δ^{(l)}δ^{(l)} = (∂L / ∂z^{(l)}) = (∂L / ∂y^{(l)}) ⊙ f'^{(l)}(z^{(l)})
    * 这里 表示逐元素乘法。f'^{(l)} 是第 l 层激活函数的导数。
    * 对于输出层,∂L / ∂y^{(L)} 已在第一步计算。
    * 对于隐藏层,∂L / ∂y^{(l)} 需要从后一层传递过来。
    b. 计算权重梯度∂L / ∂W^{(l)} = δ^{(l)} (y^{(l-1)})^T
    * 这给出了一个矩阵,其 (i, j) 元素是 ∂L / ∂W_{ij}^{(l)}
    c. 计算偏置梯度∂L / ∂b^{(l)} = δ^{(l)}
    * 偏置的梯度就是 δ^{(l)} 本身。
    d. 计算前一层输出梯度(为下一层迭代做准备):∂L / ∂y^{(l-1)} = (W^{(l)})^T δ^{(l)}

为什么是“反向”传播?

注意步骤 2d。为了计算第 l-1 层的梯度 ∂L / ∂y^{(l-1)},我们需要第 l 层的梯度 δ^{(l)}。这就是为什么我们必须从输出层开始,向后计算。每一层的梯度计算都依赖于其后一层的梯度结果。


向量化与矩阵表示

在实际实现中,我们使用矩阵和向量运算,这比逐元素操作要高效得多。这正是我们之前用矩阵形式书写前向传播的原因。反向传播的公式也完全可以用矩阵运算表示,如上节所示。

这种向量化表示不仅使代码更简洁,还能利用高度优化的线性代数库(如 BLAS、CUDA)来加速计算,这对于训练大型神经网络至关重要。


特殊激活函数的处理

反向传播需要计算激活函数的导数 f'(z)。对于常见的激活函数:

  • Sigmoid: f'(z) = f(z)(1 - f(z))
  • Tanh: f'(z) = 1 - (f(z))^2
  • ReLU: f'(z) = 1 if z > 0 else 0 (在 z=0 处通常定义为 0 或 1,这是一个次梯度)
  • Softmax:这是一个向量激活函数。它的导数是一个雅可比矩阵(Jacobian),而不仅仅是一个对角矩阵。对于 Softmax 输出层配合交叉熵损失,会有一种简化的、非常高效的特殊梯度形式,我们将在后续课程中看到。

对于像 Max 这样的函数,其导数在最大值输入处为 1,在其他输入处为 0。


训练流程总结

将前向传播和反向传播结合起来,整个神经网络的训练流程如下:

  1. 初始化:随机初始化所有权重和偏置。
  2. 循环(对于多个迭代周期或直到收敛):
    a. 前向传播:对于一个批次(Batch)的训练数据,计算网络输出和损失。
    b. 反向传播:计算损失相对于所有参数的梯度。
    c. 参数更新:使用梯度下降(或其变体,如 Adam)更新权重和偏置:W ← W - η * ∂L/∂Wb ← b - η * ∂L/∂b

总结

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

  • 我们回顾了梯度下降法,它需要损失函数相对于参数的梯度。
  • 我们引入了计算图和链式法则的概念,作为理解梯度流动的基础。
  • 我们详细剖析了前向传播(计算网络输出和中间值)和反向传播(从输出层到输入层,利用链式法则高效计算梯度)的过程。
  • 我们了解了如何用向量化和矩阵形式表示这些计算,以适应实际应用。
  • 最后,我们勾勒出了将反向传播嵌入梯度下降框架的完整训练流程。

反向传播是深度学习得以成功的基石之一。理解它如何工作,将帮助你更好地调试模型、设计新的架构,并深入理解神经网络的内部机制。在接下来的课程中,我们将探讨训练中的其他重要主题,如泛化能力、过拟合和更先进的优化技术。

7:训练(第四部分)🚀

在本节课中,我们将继续探讨神经网络的训练。我们将分析梯度下降的收敛性,理解为何它可能无法找到最优解,并探讨一些改进训练过程的算法。

回顾:神经网络训练基础

上一节我们介绍了神经网络训练的核心概念。本节中,我们来看看训练过程中的收敛性问题。

神经网络是通用近似器,只要架构合适,它们可以模拟任何函数。我们通过指定架构、权重和偏置来训练网络,目标是最小化训练集上的总损失

我们通过经验风险最小化来实现这一点。我们使用梯度下降的变体,并通过反向传播计算网络参数相对于误差的梯度。

以下是训练算法的核心步骤:

  1. 初始化所有网络参数。
  2. 在每次迭代中,遍历所有训练实例:
    • 前向传播中,将训练实例通过网络。
    • 反向传播中,计算梯度。
  3. 计算所有训练实例的平均梯度。
  4. 沿梯度的负方向调整所有参数。

梯度下降与分类误差

我们训练网络的最终目标通常是最小化分类误差。然而,分类误差是权重的一个不可微函数。因此,我们使用一个平滑、可微的损失函数(如交叉熵)作为代理。

关键问题:最小化这个可微的损失函数,是否总能最小化分类误差?

答案:不一定。以下是一个例子:

  • 假设数据是线性可分的。
  • 感知器学习规则(直接最小化分类误差)总能找到分离超平面。
  • 基于梯度下降的方法(最小化代理损失函数)可能找不到分离解,特别是当存在少数“干扰”数据点时。

为什么这可能是一件好事?

  • 感知器规则对异常值非常敏感,添加单个数据点可能导致解发生剧烈变化。它过拟合训练数据,具有低偏差但高方差
  • 基于反向传播的方法对少量数据点不那么敏感,它更倾向于一致性而非完美拟合。因此,它是一个低方差估计器,但可能以引入一些偏差为代价。

结论:反向传播训练出的神经网络分类器,通常比训练数据的最优分类器具有更低的方差。

损失函数的复杂性

之前的讨论假设损失函数具有单一的、可找到的全局最优解。但实际上,损失函数作为网络参数的函数可能非常复杂。

对于大型网络,损失函数可能包含:

  • 许多鞍点:在某些方向是最小值,在其他方向是最大值。
  • 许多局部最小值:梯度下降可能陷入这些小坑中。

梯度下降(通过反向传播计算梯度)可能会找到这些不理想的点,而非全局最小值。

分析收敛性:凸函数与二次函数

分析多层感知机的损失函数非常困难。因此,我们借助凸优化的理论来分析,这就像“在路灯下找钥匙”——我们分析我们能分析的部分。

凸函数是指,连接函数上任意两点的线段都位于函数图像上方或之上的函数。最简单的凸函数之一是二次函数

我们选择分析二次函数,是因为在凸函数中,它们能以最清晰的方式引导我们到达最优点。

对于一个单变量二次损失函数:
E(w) = (1/2) * a * w^2 + b * w + c (其中 a > 0)

其最优解有闭式解:w* = -b / a

使用梯度下降时,更新规则为:w_{k+1} = w_k - η * E'(w_k)

通过泰勒级数展开分析可知,存在一个最优步长(学习率) η_opt = 1 / a(即二阶导数的倒数)。使用该步长,可以一步到达最优点。

步长的影响

  • 如果 η < η_opt:收敛缓慢。
  • 如果 η_opt < η < 2 * η_opt:仍然收敛,但会在最优点附近振荡。
  • 如果 η > 2 * η_opt发散

多变量情况与不同方向的步长问题

对于多变量二次函数(例如 E(w1, w2) = (1/2)*a1*w1^2 + (1/2)*a2*w2^2 + ...),其图像像一个椭圆碗。不同方向(参数)的曲率(二阶导数 a1, a2)不同。

每个方向都有自己的最优步长:η_opt1 = 1/a1, η_opt2 = 1/a2

问题:在标准梯度下降中,我们对所有参数使用相同的学习率 η

后果

  • 如果 η 对于某个方向太大(> 2 * η_opt_i),该方向可能发散。
  • 为了保证所有方向都不发散,η 必须小于 2 * min(η_opt_i),即最小最优步长的两倍。
  • 如果不同方向的最优步长差异很大(即条件数很大),那么为了稳定性,我们被迫使用一个非常小的 η,这会导致在其他方向上的收敛极其缓慢

学习率调度与逃离局部最优

固定的、过小的学习率会导致收敛缓慢。但固定的、过大的学习率又会导致发散。然而,发散并不总是坏事

在复杂的非凸损失函数中,我们可能希望初始时使用较大的学习率,以便有能量跳出较差的局部最小值或鞍点。随后,我们再逐渐减小学习率,以便最终稳定在一个(希望是更好的)局部最小值中。

因此,常见的实践是使用学习率调度

  • 衰减策略:学习率随迭代次数衰减(如按倒数、平方倒数或指数衰减)。
  • 分段常数衰减:先以固定学习率训练直到损失平台期,然后将学习率乘以一个因子(如0.1)降低,继续训练,重复此过程。

改进算法:弹性传播 (RProp)

上述问题的根源在于对所有维度使用固定的学习率。弹性传播 (RProp) 算法通过为每个参数独立地自适应调整步长,释放了这一限制。

RProp 核心思想

  • 为每个权重维护一个独立的步长。
  • 观察本次梯度与上次梯度的符号
    • 如果符号相同(方向一致),说明可以加速,增大该权重的步长。
    • 如果符号相反(方向改变),说明可能 overshoot 了,减小该权重的步长并回退到上一步的值。
  • 然后使用调整后的新步长继续更新。

以下是其简化流程:

  1. 计算当前梯度。
  2. 对于每个参数,比较当前梯度符号与上一次梯度符号。
  3. 如果符号相同,则步长 Δ = Δ * α (α > 1,例如 1.2)。
  4. 如果符号相反,则步长 Δ = Δ * β (0 < β < 1,例如 0.5),并且取消上一步的更新(回退)。
  5. 无论符号如何,参数更新为:w_{new} = w_{old} - sign(当前梯度) * Δ

RProp 是一个简单高效的一阶算法,对损失函数形状假设最少,在实践中通常比朴素梯度下降更快。

动量方法简介

另一种思路是动量方法。它通过维护一个过去梯度的移动平均(称为动量项)来更新参数。

核心思想

  • 在收敛顺利的方向,梯度符号一致,移动平均会累积增大,从而加速收敛。
  • 在振荡的方向,正负梯度会相互抵消,移动平均变小,从而等效于减小了该方向的步长,稳定了更新。

参数更新规则类似于:v_{k+1} = γ * v_k + η * ∇E(w_k)w_{k+1} = w_k - v_{k+1}
其中 v 是动量项,γ 是动量系数(通常接近 0.9)。

动量方法能有效缓解振荡,加速训练,我们将在后续课程中详细讨论。

总结

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

  1. 梯度下降的局限性:最小化代理损失函数不一定最小化分类误差,但这能带来低方差的益处。
  2. 损失函数的复杂性:存在鞍点和局部最小值,影响收敛。
  3. 收敛性分析:通过二次函数模型,我们理解了学习率对收敛速度和稳定性的关键影响,以及不同方向需要不同步长的问题。
  4. 学习率调度:使用衰减的学习率策略可以在早期逃离局部最优,后期稳定收敛。
  5. 改进算法:介绍了 RProp 算法,它通过为每个参数自适应调整步长来加速训练。
  6. 动量方法:简要介绍了通过梯度移动平均来稳定更新、加速收敛的思想。

这些知识为我们理解更高级的优化器(如 Adam)奠定了基础,并提供了调试和改善神经网络训练过程的实用见解。

8:训练(第五部分)🚀

概述

在本节课中,我们将继续探讨深度神经网络的训练。我们将学习一种更高效的参数更新方法——增量更新,并深入了解其背后的原理和实现细节。接着,我们将重新审视优化算法,特别是动量方法及其改进版本,以理解它们如何帮助网络更快、更稳定地收敛。


增量更新

上一节我们介绍了使用梯度下降来最小化整个训练集上的总误差。本节中,我们来看看一种更高效的替代方案:增量更新。

在传统的批量更新中,我们需要处理完所有训练样本后才能进行一次参数调整。这就像试图用数千只手臂同时按压一块粘土,将其塑造成一个娃娃,这在计算上是低效的。

一个更实际的方法是像手工塑造粘土一样,一次只处理一个训练样本。以下是其工作原理:

  • 逐点调整:我们选择一个训练点,根据该点的误差梯度对网络参数进行微小的调整。
  • 全局影响:虽然每次调整只针对一个点,但由于网络参数是共享的,这个调整会轻微地影响整个函数在所有点的输出。
  • 多次遍历:我们按随机顺序遍历所有训练样本,每个样本都触发一次参数更新。对整个数据集的一次完整遍历称为一个周期。在每个周期内,如果有 T 个样本,就会进行 T 次参数更新。
  • 防止循环:为了避免参数更新在不同样本组之间来回振荡(例如,先调整左侧样本导致右侧出错,再调整右侧样本又导致左侧出错),必须在每个周期随机打乱训练数据的顺序

这种每次只基于一个样本进行更新的方法,被称为随机梯度下降


为什么SGD有效?数学视角

为了理解SGD为何有效以及其局限性,我们需要从数学上审视我们的目标。

我们真正想要最小化的目标是模型在整个输入空间上的期望误差。由于无法计算整个空间,我们使用训练集上的平均误差(经验风险)作为近似。这个经验风险是期望误差的一个无偏估计,这意味着从统计上看,最小化经验风险有助于最小化真实的期望误差。

然而,SGD每次只使用一个样本的误差梯度来更新参数。虽然单个样本的梯度也是真实梯度的无偏估计,但其方差非常大。方差大的原因是,不同的训练样本可能给出完全相反的方向建议(例如,一个样本说“函数值应该提高”,另一个样本说“函数值应该降低”)。这会导致参数更新路径非常不稳定,收敛到的解可能质量较差,且不同训练运行间的结果差异很大。

为了降低方差,一个自然的想法是:为什么不使用一小批样本的平均梯度呢?


小批量梯度下降

小批量梯度下降是批量更新和SGD之间的一个折中方案。它结合了两者的优点:

  • 工作原理:每次更新时,随机选取一小批(例如32、64、128个)训练样本,计算这批样本上的平均损失梯度,并用这个平均梯度来更新参数。
  • 方差降低:平均梯度 g_batch 的方差是单个样本梯度方差 Var(g)1/B 倍(B 为批量大小)。公式表示为:
    Var(g_batch) ≈ Var(g) / B
    这意味着批量越大,更新方向越稳定,方差越小。
  • 效率与稳定性的平衡:批量大小 B 存在一个“甜点”。B 太小(如SGD),方差大,不稳定;B 太大(接近全批量),每次更新计算成本高,且可能陷入尖锐的局部极小点。实践中,通常选择硬件内存能支持的最大批量大小。

小批量方法通常能获得接近批量更新的低方差和稳定性,同时保持SGD的更新速度和逃离不良局部极小点的能力。


优化算法进阶:平滑更新

在使用SGD或小批量方法时,损失曲面在不同方向上的曲率可能差异很大,导致优化路径像在峡谷中震荡前进,效率低下。上一节我们提到,可以通过考虑梯度的历史信息来平滑更新方向。本节我们将深入探讨这类方法。

动量法

动量法的核心思想是:对历史梯度取指数加权移动平均,用这个平均方向来更新参数。这有助于在梯度方向持续一致的维度上加速,而在梯度方向频繁变化的维度上阻尼振荡。

其更新规则如下:
v_t = μ * v_{t-1} - η * g_t
θ_t = θ_{t-1} + v_t
其中:

  • v_t 是当前的速度(动量)。
  • μ 是动量系数(通常设为0.9),控制历史梯度的影响。
  • η 是学习率。
  • g_t 是当前小批量的梯度。
  • θ_t 是模型参数。

这就像给优化过程增加了一个“惯性”,使其能够平滑地穿过梯度噪声较大或曲率不佳的区域。

Nesterov 加速梯度

NAG是动量法的一个改进版本。它与普通动量法的区别在于计算梯度的位置

  1. 普通动量法:先计算当前参数 θ_{t-1} 处的梯度 g_t,然后结合动量进行更新。
  2. NAG:先根据历史动量“向前看”一步,得到一个临时参数 θ_{temp} = θ_{t-1} + μ * v_{t-1},然后计算这个“未来”位置 θ_{temp} 处的梯度 g_{temp},最后用这个梯度来修正更新方向。

其更新规则为:
v_t = μ * v_{t-1} - η * ∇J(θ_{t-1} + μ * v_{t-1})
θ_t = θ_{t-1} + v_t
直观上,NAG 能对梯度变化做出更灵敏的反应,从而在凸优化问题上具有更好的理论收敛性。

RMSProp

动量法主要平滑了梯度(一阶矩)的方向。RMSProp 则关注于自适应地调整每个参数的学习率。其思想是:对于梯度幅度大的参数,降低其学习率;对于梯度幅度小的参数,提高其学习率。这通过维护一个梯度平方的指数加权移动平均来实现。

更新规则如下:
E[g^2]_t = γ * E[g^2]_{t-1} + (1 - γ) * g_t^2
θ_t = θ_{t-1} - (η / √(E[g^2]_t + ε)) * g_t
其中:

  • E[g^2]_t 是梯度平方的移动平均。
  • γ 是衰减率(通常为0.9)。
  • ε 是一个很小的数(如1e-8),防止除以零。
  • 学习率 η 被每个参数的历史梯度幅度所缩放。

Adam:动量 + RMSProp

Adam 算法结合了动量法和 RMSProp 的优点,它同时维护梯度的一阶矩估计(均值)二阶矩估计(未中心化的方差),并对其进行偏差校正,然后用于参数更新。

以下是 Adam 的核心步骤:

  1. 计算梯度的一阶矩(均值)和二阶矩(平方)的指数移动平均:
    m_t = β1 * m_{t-1} + (1 - β1) * g_t
    v_t = β2 * v_{t-1} + (1 - β2) * g_t^2
  2. 对一阶和二阶矩估计进行偏差校正(因为初始时刻它们被初始化为0):
    m̂_t = m_t / (1 - β1^t)
    v̂_t = v_t / (1 - β2^t)
  3. 使用校正后的矩估计更新参数:
    θ_t = θ_{t-1} - η * m̂_t / (√(v̂_t) + ε)

Adam 因其对超参数相对不敏感、通常能取得良好效果而成为深度学习中最流行的优化器之一。典型的超参数值为:η=0.001, β1=0.9, β2=0.999, ε=1e-8


总结

本节课中我们一起学习了深度神经网络训练中的两个核心进阶主题:

  1. 增量更新策略:我们从低效的批量更新,过渡到高效的随机梯度下降,并最终确立了小批量梯度下降作为实践中的标准。我们理解了其背后通过降低梯度估计方差来平衡效率与稳定性的原理。
  2. 自适应优化算法:为了应对损失曲面的复杂地形和SGD的不稳定性,我们学习了一系列平滑和自适应更新技术:
    • 动量法:通过累积历史梯度来加速稳定方向并抑制振荡。
    • Nesterov 加速梯度:动量法的改进,通过“向前看”来获得更精确的更新方向。
    • RMSProp:自适应地调整每个参数的学习率,根据历史梯度幅度进行缩放。
    • Adam:综合了动量法和 RMSProp 的优势,同时利用梯度的一阶和二阶矩信息进行自适应更新,是目前最广泛使用的优化器。

这些技术共同构成了现代深度学习训练工具箱的基础,使我们能够更快速、更稳定地训练出性能强大的神经网络模型。

8:神经网络训练(第六部分)📚

在本节课中,我们将学习神经网络训练的最后一部分内容,涵盖泛化问题、训练技巧、损失函数、批归一化、正则化以及Dropout等核心概念。


概述 📋

到目前为止,我们已经学习了如何通过最小化损失函数来训练神经网络。损失函数是训练集上平均差异的近似值,我们使用梯度下降法来最小化它。我们可以使用批量更新或增量更新算法(如SGD或小批量梯度下降)来加速收敛。动量算法通过考虑梯度的长期趋势来平滑增量更新方法中的变化,从而实现更快更好的收敛。

接下来,我们将讨论一些泛化问题和训练技巧,包括损失函数、归一化、Dropout等。


损失函数的选择与影响 📉

上一节我们介绍了梯度下降和动量算法,本节中我们来看看损失函数的选择如何影响训练。

损失函数是整个训练点上平均差异的函数。梯度下降的收敛位置取决于损失函数,而损失函数又取决于差异函数。理想情况下,差异函数应具有能产生显著梯度的形状,并在正确方向上引导梯度下降。这意味着差异函数应该是平滑的,并且没有许多不良的局部最优解。

例如,如果一个差异函数在远离最优解时斜率很小,而在接近最优解时斜率很大,那么当远离最优解时收敛会很慢,而接近最优解时则会因为步长过大而反弹出去。最好的差异函数类型是在远离最优解时陡峭,在接近最优解时平缓(但不要太浅,最好是二次型)。

以下是几种流行的差异函数选择:

  • L2差异(用于回归):如果你试图预测实数值,L2差异非常流行。对于标量预测,它是误差平方的一半;对于向量预测,它是各分量平方差之和的一半。公式为:L2 = 0.5 * (y_pred - y_true)^2(标量)或 L2 = 0.5 * Σ(y_pred_i - y_true_i)^2(向量)。
  • KL差异(用于分类):如果你执行分类任务,通常使用KL差异。对于二分类(单个输出),公式为:KL = -[d * log(y) + (1-d) * log(1-y)],其中 d 是期望输出(0或1),y 是实际输出。对于多类分类(输出为向量,期望是one-hot向量),公式为:KL = Σ_i d_i * log(d_i / y_i)

即使在分类任务中,也有可能使用L2差异。那么为什么我们在执行分类时使用KL差异而不是L2差异呢?这是因为当我们观察损失作为网络权重(例如,逻辑回归前的线性项Z)的函数时,KL损失是一个漂亮的凸函数,而L2损失则呈现出非凸的“花朵”形状。L2差异相对于权重不是凸的,尽管两者都有唯一的全局最小值。因此,对于分类任务,KL差异是更好的选择。

另一个要点是,在回归网络(使用L2损失和最终的线性层)或分类网络(使用softmax和交叉熵)这两种最流行的网络中,损失相对于最终线性项(Z)的梯度都恰好是期望输出和网络输出之间的简单误差。这就是反向传播通常被称为误差反向传播的原因,因为我们反向传播的正是这个误差。


批归一化(Batch Normalization)⚙️

我们通常使用小批量进行训练。当我们使用小批量更新模型参数时,其背后的假设是每个小批量的总体分布与整体数据的分布相似。然而在现实中,特别是在神经网络内部数据发生偏移的某些奇怪位置,它们可能并不相似,甚至可能彼此相距甚远。

我们希望将所有小批量移动到空间的同一区域。实现方法之一是首先将每个小批量的数据移动到原点(通过减去该小批量的均值),然后进一步标准化(通过除以每个分量的标准差)。这样,每个小批量都被中心化并归一化了方差。这个将每个小批量归一化并移动到公共位置的过程称为批归一化(实际上是“小批量归一化”)。

我们通常将其应用于任何位置,特别是在进入激活函数之前的线性项(Z)上,并且对网络中的每个神经元独立进行。

批归一化的操作 🛠️

在一个典型的神经元中,我们首先计算线性项 Z = sum(inputs) + bias,然后将其通过一个激活函数。批归一化发生在线性项计算之后、进入激活函数之前。它将线性项 Z 转换为 Z_hat

具体操作如下:

  1. 计算整个小批量 Z 的均值 μ_B 和方差 σ_B^2
  2. 对每个 Z 进行归一化:U = (Z - μ_B) / sqrt(σ_B^2 + ε),其中 ε 是一个防止除零的小常数。
  3. 随后,对每个归一化的值进行缩放和移位:Z_hat = γ * U + β。其中 γ(缩放因子)和 β(移位项)是可学习的参数,它们将数据移动到新的标准位置。

这个过程可以写作:Z -> (归一化) -> U -> (缩放和移位) -> Z_hat -> 激活函数

批归一化的反向传播 🔄

在常规训练中,小批量损失是各个训练实例差异的平均值,损失相对于任何参数的导数就是各个实例导数相对于该参数的平均值。

但在批归一化中,情况变得复杂。任何神经元的输出(正在进行批归一化的地方)不仅取决于该小批量的那个实例,还取决于整个小批量的均值和方差。因此,每个实例的差异不仅取决于单个输入 x,还取决于整个小批量。这意味着我们不能使用简单的规则来计算差异的导数。

为了计算损失相对于任何原始 Z_i 的导数,我们必须考虑所有归一化后的值 U_j,因为每个 Z_i 都通过影响均值和方差来影响每一个 U_j。因此,我们需要使用分布式链式法则:∂Loss/∂Z_i = Σ_j (∂Loss/∂U_j) * (∂U_j/∂Z_i)

通过仔细推导依赖图(每个 Z 影响均值 μ,所有 Zμ 影响方差 σ^2,每个 U 受其自身 Zμσ^2 影响),我们可以得到 ∂U_j/∂Z_i 的公式。当 j = i 时,有三项贡献(直接路径、通过 μ 的路径、通过 σ^2 的路径)。当 j ≠ i 时,有两项贡献(通过 μ 的路径、通过 σ^2 的路径)。将这些公式代入,就能得到损失相对于 Z_i 的导数,尽管公式看起来复杂,但借助依赖图很容易推导。

一个重要的启示是:如果小批量中的所有实例几乎相同,那么损失相对于 Z 的导数将变为零,反向传播会在此停止。因此,为了让批归一化有效工作,小批量中需要具有多样性。

推理阶段的批归一化 🤔

在训练时,我们使用每个小批量的统计量(均值和方差)。但在推理时,我们通常处理单个实例,没有小批量来计算 μσ^2。解决方法是在模型完全训练后,将训练数据全部通过模型,计算每个小批量的均值和方差,然后对它们取平均(更实际的做法是使用运行平均值)。在推理时,就使用这些全局平均统计量来进行归一化。

批归一化通常能提高收敛速度和网络性能。有经验证据表明,批归一化可以消除对Dropout的需求(我们稍后讨论)。为了从批归一化中获得最大收益,通常需要增加学习率,并且学习率衰减可以更快,因为数据通常保持在激活函数的高梯度区域。


正则化与泛化 🛡️

我们试图从少量训练实例中学习一个函数,并希望模型在拟合这些训练实例后,也能很好地拟合整个函数。但问题是,没有什么能阻止我们的模型仅仅过拟合训练数据,产生非常曲折、不光滑的决策边界。

为什么会发生这种情况?因为网络中的每个感知器(例如使用Sigmoid激活函数)都能够通过调整其权重参数 w 变得非常陡峭,类似于阶跃函数。当权重 w 的幅度很大时,Sigmoid函数会变得非常陡峭,从而允许网络学习到这种不期望的、不平滑的函数。

为了防止这种情况,我们需要约束权重 w 不要变得太大。这样,单个感知器就不会变得那么陡峭,最终会得到平滑的曲线。

权重衰减(L2正则化)⚖️

在常规训练中,我们有一个关于所有网络参数的损失函数,即整个训练数据的平均差异。我们最小化这个损失函数。

现在,除了常规损失,我们添加第二项:权重的平方范数的一半。这个增强的损失函数是:L_total = L_data + λ * 0.5 * ||W||^2,其中 λ 是一个超参数,控制我们对于保持权重较小的重视程度。

当我们最小化这个总损失时,自然也会最小化权重的平方范数,因为它现在是损失的一部分。

在批量训练、随机梯度下降或小批量更新的情况下,损失相对于权重的梯度都变成了原始差异梯度加上 λ 乘以权重矩阵本身。因此,更新规则变为:W_new = W_old - η * (∇L_data + λ * W_old)。这可以重新排列为:W_new = (1 - ηλ) * W_old - η * ∇L_data

这被称为权重衰减。它所做的是在每次更新前,将当前权重乘以一个衰减因子 (1 - ηλ),然后再进行梯度修正。这实质上是一种正则化,确保权重保持较小。

网络深度作为正则化 🏗️

平滑性也可以通过网络结构来施加。网络通常有很多层,每一层都在转换输入空间。如果从平坦的表面开始,每一层都会根据其权重矩阵重塑这个表面。事实证明,如果你从一个非常崎岖的表面开始,每经过一层,整体表面会变得不那么陡峭、不那么锯齿状。因此,对于给定数量的参数,更深的网络比浅层网络施加了更多的平滑性(正则化),因为每一层本身都会抑制网络中的一些锯齿状。

实验表明,在神经元总数相同的情况下,更深的网络(更多层,每层神经元更少)往往能学习到更平滑、更泛化的决策边界。另一种获得更好泛化的方法是使用更多的训练数据。


Dropout:随机正则化 🎲

除了L2正则化,另一种非常有影响力的方法是Dropout。在介绍Dropout之前,需要了解Bagging的概念。Bagging是一种集成学习技术,它从训练数据中随机采样不同的子集,训练多个不同的分类器,然后对所有分类器的预测进行投票。理论表明,这种分类器集合比训练单个分类器能获得更好的泛化能力,因为它覆盖了更多的空间模式。

Dropout的思想类似于在神经网络内部进行Bagging。在训练期间,对于每个输入和每次迭代,以概率 (1 - α) 随机“关闭”(将输出置零)每个神经元。因此,对于任何特定的数据实例,实际使用的网络可能是原始网络的一个随机子集。每个训练实例都可能看到不同的网络子集。前向传播在这个子网络上进行,反向传播也在这个子网络上进行,并且只在该子网络中被激活的参数上累积梯度。

Dropout的工作原理与解释 💡

Dropout效果很好的原因有两种解释:

  1. 集成学习视角:一个具有 n 个神经元的网络,通过随机开关可以产生 2^n 个可能的子网络。Dropout相当于从这 2^n 个可能的网络中采样,并同时有效地训练所有这些网络。在推理时,你将对所有这些网络的输出进行集成平均。
  2. 防止协同适应视角:在没有Dropout的情况下,网络可能学习到一种“偷懒”的路径,例如某一层只是简单地复制前一层的值,这相当于浪费了一层。使用Dropout后,由于神经元会随机被关闭,这种简单的复制路径无法一直工作,神经元被迫从侧边获取信息,从而学习到更稠密、更鲁棒的权重矩阵。

Dropout的实现 🖥️

在训练阶段的前向传播中,对每个神经元运行一个伯努利随机数生成器(以概率 α 输出1,否则为0)来生成一个掩码(mask)。神经元的输出乘以这个掩码。在反向传播中,存储这个掩码,并将每个神经元的梯度乘以相同的掩码。

在推理阶段,理论上需要对所有 2^n 个子网络的输出取期望值,但这在计算上是不可行的。我们使用一个近似:整个网络的期望值近似等于每个神经元激活值的期望值所构成的网络。对于每个神经元,其输出是激活值乘以一个伯努利随机变量(以概率 α 为1)。因此,其期望值就是激活值乘以 α

因此,在推理时,我们只需将每个神经元的激活值乘以 α。或者,我们可以将 α 因子乘入下一层的权重中,存储缩放后的权重,这样在推理时就可以直接使用常规的前向传播,而无需额外操作。另一种等效的工程实现是在训练时,只对那些未被丢弃的神经元的激活值乘以 1/α,然后在推理时直接使用常规激活值。

Dropout通常能显著提高模型的泛化性能,防止过拟合。已经出现了许多Dropout的变体,如Zoneout(随机选择的单元保持不变)、DropConnect(丢弃连接边而不是神经元)、Shakeout(随机缩放、添加噪声)等。

需要注意的是,将Dropout与批归一化结合使用通常效果不佳,因为Dropout会增加方差,而批归一化会减少方差,它们可能会相互抵消。


其他训练技巧与启发式方法 🧰

除了上述核心方法,还有一些其他常用的训练技巧:

  • 早停(Early Stopping):在训练集上持续训练时,训练损失可能会不断下降,但在一个单独保留的验证集上,损失或分类性能在某个点之后开始上升或饱和,这意味着模型开始过拟合。因此,我们会在训练过程中监控验证集性能,一旦性能开始恶化就停止训练,并使用此时的最佳模型。
  • 梯度裁剪(Gradient Clipping):尽管我们尽力避免,但损失函数的梯度有时会变得非常大(例如在非常陡峭的点)。如果使用这个巨大的梯度更新模型,会导致更新步长过大,训练不稳定。梯度裁剪是一种常见技术,当梯度的范数超过某个阈值(例如5)时,就将其裁剪到该阈值。
  • 数据增强(Data Augmentation):通过人工创建合成数据来扩充训练集。例如,在图像分类任务中,可以对原始图像进行旋转、翻转、缩放、添加噪声等变换,同时保持其标签不变。这能极大地帮助模型学习更鲁棒的特征。
  • 输入归一化(Input Normalization):在训练开始前,将整个训练数据归一化为零均值和单位方差。这相当于在输入层进行批归一化。
  • 参数初始化技巧:如Xavier初始化、He初始化等,这些方法旨在使网络在训练初期保持稳定的梯度流。
  • 确保神经元多样性:在训练开始时看起来相同的神经元,往往会保持相同。因此,我们需要确保在训练初期神经元是多样化的,以促进学习不同的特征。

总结 🎯

在本节课中,我们一起学习了神经网络训练的最后一系列主题:

  1. 损失函数:L2损失适用于回归任务,KL(交叉熵)损失适用于分类任务。选择正确的损失函数对于获得凸的优化景观和有效的梯度传播至关重要。
  2. 批归一化:通过归一化每个小批量的激活值来减少内部协变量偏移,加速训练并提高性能。它需要在训练和推理阶段进行不同的处理。
  3. 正则化:为了防止过拟合,我们使用如L2正则化(权重衰减)等技术来约束模型复杂度,鼓励较小的权重,从而获得更平滑的决策函数。
  4. 网络深度:更深层的网络架构本身可以提供一种隐式的正则化效果,促进更好的泛化。
  5. Dropout:一种随机正则化技术,通过在训练期间随机丢弃神经元,强制网络学习更鲁棒的特征,并可以解释为训练大量子网络的集成。
  6. 其他启发式方法:包括早停、梯度裁剪、数据增强等,这些都是成功训练现代深度神经网络的重要组成部分。

训练神经网络既是一门科学,也是一门艺术。科学知识为我们提供了基础和工具,但真正的技能、直觉和实践经验才是达到完美效果的关键。希望本课程为你提供了坚实的理论基础,助你在实践中不断探索和精进。

9:卷积神经网络(CNNs)第一部分 🧠

在本节课中,我们将要学习卷积神经网络(CNNs)的基本概念。我们将探讨为什么传统的多层感知机(MLPs)在处理图像或语音等数据时存在局限性,以及如何通过“扫描”和“参数共享”的思想来构建对位置变化不敏感、更高效的网络模型。我们将从直观的扫描概念入手,逐步推导出卷积操作,并理解其如何通过分层和参数共享来大幅减少模型参数和计算量。


从问题出发:位置不变的模式识别

到目前为止,我们已经看到多层感知机是通用的函数逼近器。它们可以建模布尔函数、分类器、回归器,并且可以通过梯度下降的变体进行训练。

然而,我们见过的所有模型都是这种类型:一层神经元连接到下一层神经元,再连接到更下一层。

现在有一个新问题。我给你一段语音录音,长度固定为半秒或一秒。我的问题是:这段信号中是否包含单词“welcome”?

一个显而易见的方法是将其转换为频谱图表示,就像你在作业一中做的那样。然后,直接在上面应用一个多层感知机。它可以读取整个频谱图,并应该能够告诉你单词“welcome”是否存在。

但是,假设我有两段录音。第一段录音中,单词“welcome”出现在前半部分。第二段录音中,同一个单词出现在后半部分。同一个网络会对这两个输入都产生“激活”吗?为什么不会?

答案是:不会。因为整个输入可以被看作一个向量。在第一种情况下,向量的一部分有数值(代表单词),其余部分是零。在第二种情况下,数值出现在向量的另一部分。这是两个完全不同的向量,它们位于不同的子空间中。在第一种情况下激活的网络,在第二种情况下不会激活。

我们希望有一个简单的网络,无论单词“welcome”出现在什么位置,只要它存在,网络就会激活;如果不存在,则不会激活。

图像检测也存在同样的问题。我们可以构建一个检测花朵的图像检测器。但是,一个训练用于在图片中检测花朵的MLP,如果它学会了在左上角检测花朵,那么当花朵出现在右下角时,它不一定能检测到,因为这两个数据点位于完全不同的子空间中。

我们需要一个网络,无论目标物体的精确位置在哪里,它都能激活。

在许多问题中,模式的位置并不重要,重要的是模式是否存在。而传统的MLPs对模式的位置是敏感的。即使将整个模式仅仅移动一个位置,也意味着它位于不同的子空间,网络就不会激活。我们想要一个对平移具有不变性的网络。


解决方案:扫描与参数共享

面对这个问题,你能想到的最简单的解决方案是什么?

一个简单的方案是:将数据增强到训练集中。我可以有花朵在左上角的图片,然后将其移动一个像素、两个像素……将花朵所有可能的位置变体都放入训练数据。但这个方法的问题是什么?数据量会急剧增加,并且能够学习所有这些变体的网络复杂度将非常巨大,你需要一个非常大的网络。

另一个解决方案是扫描。我可以构建一个网络,专门寻找大小与单词“welcome”大致匹配的模式。

然后,我在输入的初始位置运行它,接着进行扫描。在每个位置,它都会寻找单词“welcome”。这个网络会输出一个值,告诉你它是否在这些位置中的任何一个看到了“welcome”。

但我想要知道的是整个录音中是否有“welcome”。如何结合这些输出来判断呢?

一个简单的解决方案是使用一个 max 层。如果它在任何一个位置找到了“welcome”,那么至少有一个输出会很大,max 操作就会给出一个大的值。否则,max 值就会很小。或者,我可以用 softmax 替换 max,或者用一个MLP层。但基本思想是:我们实际上是在扫描输入。

这个过程的伪代码是什么样的?很简单:从左到右扫描,在每个位置,提取一个大致与“welcome”宽度匹配的输入片段,将其输入一个MLP。在每个位置得到一个输出,然后将所有这些输出传递给一个 softmax

仔细观察,这看起来就像是一个巨大的网络。这个网络的特殊之处在于:它的下层是由许多完全相同的子网络构成的。最终层可以看作是一个大的MLP。

在二维图像中也是如此。我可以有一个花朵检测器,检测一个小矩形区域内的花朵。我扫描输入图像,最终,当我扫描完输入的每个位置后,我将所有输出集合通过一个 maxsoftmax 或一个完整的MLP。这仍然解决了同样的问题:告诉我图像中是否有花朵。

这里的关键组成部分是,在较低层有许多子网络,它们都是相同的。这是唯一的区别。因此,这个网络现在对平移具有不变性。


重新审视网络结构:垂直表示与计算重排

为了更清晰地表示,我将改变图示方式。传统的图示中,同一层内的神经元似乎存在时间顺序,这是不正确的。我将把它们垂直重画,以表明整个计算是同时发生的。

这是一个块,箭头表示一整组神经元完全连接到下一层的另一组神经元,所有计算都在一个“列”中同时发生。我将使用这种表示方式。

现在,让我们回到如何训练这个网络。这个网络正在解决一个问题:判断图片中是否有花朵,或录音中是否有“welcome”。之前的解决方案假设我们需要先训练一个花朵检测器(使用花朵大小框的正负样本数据),然后组合输出。但这真的必要吗?

并不必要。因为我们认识到,整个东西就是一个巨大的网络。既然它是一个巨大的网络,我就可以一次性训练它。我只需要提供包含花朵(或单词)的图片(或录音)和不包含的数据集。我不会告诉模型花朵的确切位置。它必须通过梯度下降自己学会底层的检测器。

关键区别在于,这是一个参数共享网络。所有下层子网络都有相同的参数集。同样,所有其他子网络也有相同的参数。因此,我们必须修改训练范式,以考虑参数相同这一事实。

这意味着,当我训练网络并执行反向传播时,如果我更新了某个子网络实例的参数,这个更新必须反映到所有其他副本上。每一个副本都必须复制对最低层子网络所做的任何更新。这样才能确保所有这些子网络是相同的。


参数共享的反向传播

为了理解参数共享如何工作,让我们考虑一个通用概念。你可以完全任意地决定,网络中的这个连接和那个连接将具有相同的值。这和我们刚才看到的特定结构无关。

假设你决定在这个网络中,这个连接和这个连接具有相同的值。这意味着存在一个公共值 W_s,它同时应用于 W_ijkW_lmn

那么,在训练时,任何时候我想要更新这个参数,另一个参数也必须被更新相同的量。更一般地说,这个公共值必须被更新,并且必须分配到所有复制该值的元素上。

现在,如果我画出影响图。这个公共值 W_s 影响 W_ijk,也影响 W_lmn,而两者都会影响损失函数。我们试图估计这个公共值 W_s,这意味着我们需要损失函数关于 W_s 的导数。

有两条路径到达 W_s。导数的公式是:沿着一条路径,损失函数关于 W_ijk 的导数,乘以 W_ijk 关于 W_s 的导数,再加上损失函数关于另一个权重的导数,乘以那个权重关于 W_s 的导数。

W_ijk 关于 W_s 的导数是什么?它就是1,因为 W_ijk 就是 W_s 的副本。同样,另一个权重关于 W_s 的导数也是1。

因此,损失函数关于这个公共共享值 W_s 的导数,简单地等于损失函数关于各个共享该值的独立参数的导数之和

所以,在训练这个具有相同子网络的模型时,我真正需要做的就是向模型提供数据(正例和反例)。计算损失,然后,如果我想更新扫描网络中这个“红色”边的权重,意味着我也必须更新所有其他“红色”边(因为它们是同一个网络)。我会计算损失函数关于每一个“红色”边的导数,然后将它们求和。这个和就是我将用来更新这个红色边关联的权重值的导数。

对于任何一组共享参数,都是如此。


分布式扫描与卷积的引入

到目前为止,我们介绍了位置不变模式分类可以通过扫描输入来实现。一维扫描用于声音,二维扫描用于图像。扫描等价于构建一个具有相同子网络的大型网络。当在这些网络中学习时,必须修改反向传播规则,以聚合所有具有相同共享值的元素的导数。

现在,让我们更仔细地看看整个扫描是如何工作的。

这是我的网络,我使用垂直表示法来强调整个计算是同时完成的。如果这个网络分析图像的这一部分(记住,它是在扫描),第一层将生成输出(四个灰色圆圈),然后这四个输出被馈送到第二层,第二层的每个神经元生成其输出(两个绿色圆圈),接着第二层神经元馈送到第三层,生成最终输出。

这个计算在第一个位置完成,然后在第二个位置、第三个位置……依此类推。最终,所有最终层的输出被组合起来(通过 maxsoftmax 或 MLP),为整个输入做出决策。

现在,假设在计算时,第一层很着急,它不愿意等待第二层。它在后续层执行操作之前,就计算了整个输入的输出。然后,第二层才开始工作。第二层(以及网络的其余部分)接收第一层在第一个窗口的输出,并生成其输出。现在,顶部第一个位置的输出会与之前整个网络分析完该窗口再前进的情况不同吗?

不会不同。因为无论第一层是否等待,第二层操作的都是相同的四个值。输出不会因为计算顺序的改变而改变。

第二层也可以做同样的事情。第二层可以去扫描。它扫描的是什么?是输入还是第一层的输出?如果我将第二层的输出传递给第三层,那个输出会不同吗?仍然相同。

我们所做的只是重新排序了扫描,最终输出应该仍然相同。从代码的角度看更明显。

在伪代码中,我们最初是:从左到右扫描,提取一个片段,用MLP分析它,生成输出,当所有片段都被分析后,通过最终的 softmax

但在每个片段内部,这个MLP片段做了什么?在那个蓝色补丁的位置,你经历了MLP的所有层,计算一个仿射项并通过激活函数。

如果我们观察所有有趣的事情发生的地方,会发现有一个 if 子句。这是因为只有第一层在查看一个窗口,而第二层只是查看前一层的单个输出列。但所有有趣的计算都发生在最内层的循环中。

因此,两个最外层循环(一个遍历时间/位置,一个遍历层)是可以互换的。如果我翻转这两个循环的顺序,不会改变最终输出,因为所有计算都在最内层循环中。

这基本上就是我们刚才所做的:我们只是改变了循环索引的顺序。


从整体扫描到逐层扫描

当我们以这种方式重新排序计算时,发生了什么?我们让第一层的每个神经元先去扫描整个输入,产生一个“映射图”(map)。这个映射图上的每个点,对应第一层神经元在输入的那个位置分析时产生的输出值。

如果整个网络想判断右下角是否有花朵,它需要查看第一层神经元在分析那个区域时产生的值。因为我将像素排列成与输入本身相同的形状,所以我只需要拾取该图像位置对应的激活值,并将它们传递给网络的其余部分。

然后,第二层神经元也可以继续以同样的方式扫描。第二层神经元现在查看所有第一层神经元产生的映射图。为了分析输入中的任何特定位置,第二层神经元从每个第一层神经元的映射图中读取一个值。然后,第二层神经元可以扫描整个输入,产生它们自己的映射图。

接下来,如果我想知道某个位置是否有花朵,下一层只需读取第二层神经元在分析该位置时产生的值。第三层神经元也可以做同样的事情:扫描第二层神经元的输出并产生自己的映射图,然后通过一个 softmax

我们重新排序了计算,但最终输出仍然相同。


分布式模式检测与层次化表示

在非分布式扫描中,整个责任(寻找花朵大小的模式)都强加在第一层神经元上。第一层神经元检测的特征是整个花朵大小,这非常复杂。

当我们以分布式方式进行时,我们说:让第一层神经元寻找更小的东西,比如花瓣大小、萼片大小、花蕊大小。然后第二层神经元在位置上聚合这些信息,以做出决策,从而得到更大的模式。

这样,你将模式检测分布到了多个层

现在,第二层神经元当然可以继续扫描。它们必须扫描所有第一层神经元产生的映射图,因为每个第一层神经元都在寻找不同的子特征。然后它生成输出。如果下一层神经元想判断左上角是否有花,它只需要查看第二层神经元在分析第一层映射图的这个区域时产生的元素。

这仍然是扫描,仍然是参数共享网络,只是有了更多的参数共享。

例如,第二层神经元查看第一层神经元映射图中的这9个元素。这9个元素中的每一个都是由第一层神经元扫描计算出来的。所以这就像有9个相同的神经元副本,每个都在查看图像的不同区域。因此,第二层神经元实际上读取了这27个值。

虽然第二层神经元有27个输入,但只有三组独特的参数集,因为所有“黑色”神经元(的权重)是相同的,所有“红色”神经元是相同的,所有“蓝色”神经元是相同的。所以整体网络仍然是一个参数共享网络,只是共享不仅发生在分析输入不同区域的子网络之间,而且在每个窗口内部也有额外的共享。

我可以将这个过程扩展到三层。第一层神经元查看输入中更小的区域并扫描。第二层神经元现在查看第一层神经元输出的小窗口。第三层神经元查看第二层神经元映射图的小窗口。这样,我将模式分布到了更多层。

分布式的一个关键点是:第一层神经元的输出必须排列成与输入相同的形状。只有这样,我才能查看一个“窗口”。同样,第二层神经元的输出也必须排列成相同的形状。因此,这种排列现在变得很重要。

从伪代码的角度看,分布式扫描实际上变得更简单。它只是说:在每一层的每个位置,从所有前一层神经元的映射图中提取一个小窗口,然后用这些值计算一个仿射项并通过激活函数。

这个扫描映射图、使用权重窗口的操作,就叫做卷积。因此,这整个网络被称为卷积神经网络

在向量表示中,你提取一个来自每个映射图的小矩形段(形成一个二维立方体,即张量)。神经元计算的是这16个元素的加权和(即与一个2x2x4的权重集进行逐元素相乘,加上偏置,再通过激活函数)。

对于一维情况(如分析语音中的“welcome”词),原理相同。第一层神经元查看输入的一个小窗口。第二层神经元查看第一层神经元输出的一个窗口,这相当于查看了输入中一个更大的窗口。随着层数增加,每一层通过查看前一层输出的窗口,看到的原始输入区域越来越大。最终层将看到整个输入的一个很宽的区域。

这种结构在一维中通常被称为一维卷积网络,更传统的名称是时延神经网络


为什么需要分布式?优势所在

为什么我们要费心进行分布式处理?有多个原因:

  1. 强制产生具有局部模式的层次化表示,这更具泛化性。
  2. 需要更少的计算
  3. 需要少得多的参数

让我们逐一来看。

层次化表示与局部模式:回想之前我们想为“双五边形”构建分类器时,我们并没有让每个神经元都尝试计算一个双五边形。第一层神经元计算单个边,第二层神经元计算单个五边形,最终层组合五边形。我们将结构分布到许多层。为什么这样做?因为这给了你更强的表达能力。如果你想用更少的层来做,你将需要数量巨大的神经元。通过这种分布式方式,每一层查看来自前一层的简单模式,你实际上简化了整个问题,并使网络更简单。因此,分布式在学习和建模的网络参数效率方面是一件好事。

更少的计算和参数:让我们通过一个例子来看看。

假设我们有一个语音信号,变成了一系列频谱向量。每个黑色竖条代表一个D维向量。如果我想分析一个宽度为8个向量的输入窗口。

  • 非分布式扫描:第一层查看8个输入,第二层查看第一层的输出,第三层查看第二层的输出。如果第一层有N1个神经元,第二层有N2个,第三层有N3个,参数数量为:第一层 8 * D * N1,第二层 N1 * N2,第三层 N2 * N3。总参数量大。
  • 分布式扫描:不让一个神经元看8个输入,而是让第一层用大小为2的窗口扫描输入。然后第二层查看4个这样的输出。网络仍然一次查看8个输入,但这8个输入的窗口现在分布在了两层上。第一层参数:2 * D * N1。第二层参数:4 * N1 * N2(但注意,这里的4个“块”是相同的,所以是参数共享的)。第三层参数:N2 * N3。与非分布式相比,第一层的参数从 8 * D * N1 减少到了 2 * D * N1,有了显著的压缩。

此外,分布式还节省计算。当你在第一个窗口(8个输入)执行分析后,如果步进(stride)2个向量到下一个位置,你不需要重新计算整个网络。在非分布式扫描中,很多计算需要重做。而在分布式扫描中,由于较低层的块在相邻位置被重用,你可以复用之前位置已经计算过的许多值(例如,第一层的三个输出值在下一个位置是相同的,可以直接复用)。随着你分布到更多层,你可以在更深层也重用计算,从而大幅减少总计算量。

对于图像,节省更为显著。对于一个只有7个神经元的简单模型,如果不分布参数,会有1034个权重;分布后,只有194个权重,几乎减少了一个数量级。对于更大的模型,减少量可能达到几个数量级(例如从1亿减少到100万)。


卷积神经网络术语与结构

最后,我们来介绍一些术语和整体结构。

整个操作可以重新绘制为整个图像的映射图。每个神经元基本上是在扫描输入并绘制其检测图。第一层查看输入的小区域(例如检测花瓣、萼片)。第二层查看第一层输出的区域,将这些花瓣组合成花朵的更大子结构。第三层查看第二层的输出区域,获得花朵大小的模式。最后的感知器查看所有这些,以决定是否有花。

这种扫描在每个位置使用一组权重完成。这组

10:卷积神经网络(CNN)第二部分 🧠

在本节课中,我们将学习卷积神经网络(CNN)的起源、其背后的生物学灵感,以及其核心组件的详细工作原理。我们将从哺乳动物视觉系统的研究开始,逐步理解CNN如何从这些生物学发现中演变而来,并最终成为一个强大的工程模型。


从猫的视觉到计算模型 🐱

上一节我们介绍了CNN作为“使用MLP进行扫描”的概念。然而,CNN并非起源于此,而是源于对人类和动物视觉感知机制的研究。

核心问题是:动物如何看见东西?从眼睛接收到的信号到最终识别物体,大脑经历了怎样的神经过程?长期以来,研究主要基于行为学,例如我们如何将不连续的片段组织成完整的物体(格式塔现象)。

1959年,Hubel和Wiesel进行了一项开创性研究。他们通过研究猫的初级视皮层(V1区)神经元的感受野,发现了视觉处理的关键机制。

  • 感受野:指视网膜上能引起特定神经元反应的区域。
  • 研究发现:他们发现神经元对特定朝向的光缝反应最强烈。不同神经元偏好不同的朝向(如水平、垂直、倾斜)。
  • 简单细胞与复杂细胞:他们进一步区分了简单细胞复杂细胞
    • 简单细胞:直接响应视网膜上特定朝向的图案。
    • 复杂细胞:接收来自多个具有相似朝向的简单细胞的输入,其响应更具位置不变性,对噪声更鲁棒。

这项研究表明,视觉处理是分层的:从简单的边缘检测开始,通过层层组合,形成越来越复杂的图案感知。


从生物学到工程:认知机与Neocognitron 🧮

Hubel和Wiesel的模型存在局限性,例如难以解释位置不变性(例如著名的“詹妮弗·安妮斯顿神经元”或“祖母细胞”,它们无论目标出现在视野何处都会响应)。

1980年代,福岛邦彦提出了Neocognitron计算模型来模拟这一视觉层次结构。

  • 模块化结构:模型由多个层级化的模块组成,每个模块包含S细胞层C细胞层
    • S细胞:对应简单细胞,负责从上一层检测模式。同一S平面内的所有神经元具有相同的权重(即相同的响应特性),但各自关注输入的不同区域。只有S细胞的权重是可学习的。
    • C细胞:对应复杂细胞,负责“清理”S细胞的响应,通常通过对局部S细胞响应取最大值等操作来实现,从而引入一定的位置不变性。C细胞的操是固定的,无需学习。
  • 无监督学习:福岛邦彦使用赫布学习规则来训练模型。当输入呈现大量模式(如手写数字)后,模型最深层的S细胞会自发地对不同的高级概念(如不同的数字)产生响应。

Neocognitron展示了如何通过无监督学习,从生物视觉机制中衍生出一个能识别语义概念的计算模型。


监督学习的引入:LeNet与CNN的诞生 🤖

Neocognitron是无监督的。Yann LeCun等人为其引入了监督学习,从而诞生了现代卷积神经网络(CNN)。

LeCun对Neocognitron做了几项关键修改,使其更易于计算和训练:

  1. 参数共享与扫描:不再使用多个具有相同权重的S细胞副本,而是使用一个神经元(滤波器)扫描整个输入,这完全等价,但计算上更高效。
  2. 感受野形状:将椭圆形的感受野改为更易处理的正方形或矩形
  3. 下采样:C细胞层的操作(如取最大值)通常会缩小特征图的尺寸,这被明确为下采样操作。
  4. 激活函数:用更简单的Sigmoid等激活函数替换了复杂的响应函数。
  5. 监督信号:在网络的最后添加了一个全连接层和Softmax分类器,并使用反向传播算法和带标签的数据来训练整个网络。

这就是著名的LeNet,最初用于邮政编码的手写数字识别(MNIST数据集),并取得了巨大成功。


CNN核心组件详解 ⚙️

至此,我们得到了一个标准的CNN架构。它主要由两种类型的层交替堆叠而成:卷积层池化层

卷积层

卷积层对应Neocognitron中的S细胞层,是网络中进行特征提取的核心可学习部分。

  • 核心操作:使用一组可学习的滤波器(或称为)扫描输入。
  • 输入与输出:输入通常是一个三维张量(高度 × 宽度 × 通道数)。每个滤波器也是一个三维张量(滤波器高度 × 滤波器宽度 × 输入通道数)。每个滤波器扫描整个输入后,会生成一个二维的特征图(或称激活图)。多个滤波器则产生多个特征图,堆叠起来成为新的三维输出。
  • 计算公式:对于输出特征图 (i, j) 位置的值,其计算如下:
    output[i, j] = activation( bias + ∑_m ∑_x ∑_y weight[m, x, y] * input[m, i+x, j+y] )
    其中,m 遍历所有输入通道,(x, y) 遍历滤波器窗口,activation 是非线性激活函数(如ReLU)。
  • 参数共享:同一个滤波器在扫描输入的不同位置时,使用的是同一组权重。这是CNN减少参数数量、引入平移等变性的关键。
  • 填充与步幅
    • 填充:在输入边缘添加零值,可以控制输出特征图的大小(通常用于保持尺寸不变)。
    • 步幅:滤波器每次移动的像素数。步幅为1是常见选择。步幅大于1的卷积等价于步幅为1的卷积后接一个下采样操作

池化层

池化层对应Neocognitron中的C细胞层,其主要作用是引入一定程度的平移不变性并降低特征图的空间尺寸,从而减少计算量和参数。

  • 核心操作:对特征图上每个局部区域进行汇总统计。
  • 常见类型
    • 最大池化:取区域内的最大值。output = max(window)
    • 平均池化:取区域内的平均值。output = mean(window)
  • 操作细节:池化操作通常分两步:1) 找到区域内最大值(或计算平均值)的位置;2) 传递该值。记录位置信息对于后续的反向传播至关重要。
  • 步幅:池化层通常使用大于1的步幅,直接实现下采样。

上采样与下采样

这是调整特征图空间尺寸的独立操作。

  • 下采样:最简单的方法是丢弃每隔S-1行和列的数据。这通常与卷积或池化层合并,表现为其步幅大于1。
  • 上采样:最常见的方法是插入零值。在行和列之间插入S-1行/列的零。上采样通常后接卷积层,让卷积操作来填充这些零值区域的信息。上采样后接池化层通常没有意义,因为零值会干扰池化操作。分数步幅的卷积等价于上采样后接标准卷积。


一个典型的CNN分类流程 🖼️➡️🔢

让我们看一个用于图像分类的典型CNN流程:

  1. 输入:一张RGB图像(3个通道:红、绿、蓝)。
  2. 卷积块1
    • 使用 K1 个大小为 L1 x L1 的滤波器进行卷积。
    • 参数数量:K1 * (3 * L1^2 + 1) (每个滤波器有 3*L1^2 个权重和1个偏置)。
    • 经过激活函数(如ReLU),得到 K1 个特征图。
  3. 池化层1
    • 对每个特征图进行(例如)2x2最大池化,步幅为2。
    • 特征图尺寸减半,通道数仍为 K1
  4. 卷积块2
    • 使用 K2 个大小为 L2 x L2 的滤波器进行卷积。注意,现在每个滤波器的深度必须与输入通道数 K1 匹配。
    • 参数数量:K2 * (K1 * L2^2 + 1)
    • 激活后得到 K2 个特征图。
  5. 重复:可以重复多个“卷积-池化”块。随着网络加深,特征图空间尺寸越来越小,但通道数(即滤波器数量)通常会增加,以保留信息。
  6. 展平与全连接:将最后的特征图展平成一个长向量,输入到一个或多个全连接层中。
  7. 输出层:最后一个全连接层输出每个类别的得分,通常通过Softmax函数转换为概率。

关键设计考量:为了避免信息丢失,当下采样(池化)减小空间尺寸时,通常需要增加通道数。例如,下采样使尺寸减半(面积变为1/4),那么将通道数增加4倍可以大致保持总信息容量。


总结 📚

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

  • CNN的生物学起源,源于Hubel和Wiesel对猫视觉皮层的研究,以及简单细胞、复杂细胞的概念。
  • Neocognitron如何将生物模型转化为一个分层的、无监督的计算模型,并通过S细胞和C细胞的交替堆叠来学习复杂模式。
  • Yann LeCun如何通过引入监督学习参数共享扫描反向传播,将Neocognitron演进为现代CNN
  • CNN的核心组件卷积层(可学习的特征提取器)、池化层(引入不变性和下采样)以及上/下采样操作(显式控制特征图尺寸)。
  • 一个典型的CNN图像分类架构流程,以及其中滤波器数量、尺寸、步幅等参数的设计思路。

理解CNN的这些基础概念和设计原理,是后续深入学习其训练方法、现代变种(如ResNet, Transformer)以及在各种任务中应用的关键。下一节课,我们将探讨如何训练这些CNN网络。

11:卷积神经网络(CNN)第三部分 🧠

在本节课中,我们将继续学习卷积神经网络,重点探讨如何通过反向传播算法来训练CNN。我们将学习如何计算卷积层和池化层中参数的梯度,这是优化网络权重、使其能够从数据中学习的关键步骤。

回顾:CNN架构

上一节我们介绍了CNN的基本架构。CNN由卷积层、可选的池化层以及一个或多个全连接层(MLP)组成。

在卷积层中,主要进行两个操作:

  1. 仿射图计算:通过将前一层输出的特征图与一个滤波器进行卷积运算得到。
    • 公式:Z[l][m] = Conv2D(Y[l-1], W[l][m]) + b[l][m]
  2. 激活图计算:对仿射图的每个元素应用非线性激活函数(如ReLU)。
    • 公式:Y[l][m] = f(Z[l][m])

每个滤波器会产生一个输出特征图。滤波器的通道数等于输入特征图的通道数,而滤波器的数量等于输出特征图的数量

反向传播概述

训练CNN的目标是最小化损失函数,这通常通过梯度下降法实现。我们需要计算损失函数相对于网络中所有参数(主要是滤波器权重)的梯度。

反向传播的过程从网络的输出层开始,逐层向后计算梯度。对于CNN末端的全连接层,我们可以直接使用标准MLP的反向传播规则。因此,我们的核心任务是学会如何将梯度反向传播通过卷积层池化层

具体来说,我们需要解决以下问题:

  • 对于卷积层:已知损失相对于输出激活图 Y[l] 的梯度,如何计算损失相对于:
    1. 输入仿射图 Z[l] 的梯度?
    2. 前一层输入特征图 Y[l-1] 的梯度?
    3. 滤波器权重 W[l] 的梯度?
  • 对于池化层:已知损失相对于池化层输出的梯度,如何计算损失相对于池化层输入的梯度?

接下来,我们将逐一解决这些问题。

卷积层的反向传播

步骤1:从激活图梯度到仿射图梯度

首先,我们已知损失 L 相对于第 l 层第 m 个通道激活图 Y[l][m] 的梯度 ∂L/∂Y[l][m]

根据前向传播公式 Y[l][m] = f(Z[l][m]),这是一个逐元素的非线性变换。因此,反向传播时,Z[l][m] 的梯度可以通过链式法则计算:

∂L/∂Z[l][m] = (∂L/∂Y[l][m]) ⊙ f'(Z[l][m])

其中 表示逐元素乘法,f' 是激活函数的导数。这是一个简单的逐元素操作。

步骤2:从仿射图梯度到输入特征图梯度

现在,我们有了损失相对于仿射图 Z[l] 的梯度 ∂L/∂Z[l]。我们需要计算损失相对于前一层输入特征图 Y[l-1] 的梯度 ∂L/∂Y[l-1]

在前向传播中,每个输出特征图 Z[l][n] 都是由所有输入特征图 Y[l-1][:] 与对应的滤波器通道卷积后求和得到的。因此,每个输入特征图的元素会影响所有输出特征图的多个元素。

计算 ∂L/∂Y[l-1][m](第 m 个输入通道的梯度)需要对所有输出通道 n 和所有空间位置进行求和

∂L/∂Y[l-1][m] = Σ_n (∂L/∂Z[l][n] * ∂Z[l][n]/∂Y[l-1][m])

其中,∂Z[l][n]/∂Y[l-1][m] 实际上就是第 n 个滤波器的第 m 个通道(记作 W[l][n][m])。

关键发现:上述求和运算等价于一个卷积操作。具体来说,为了计算 ∂L/∂Y[l-1][m],我们需要:

  1. 对于每个输出通道 n,取出对应的滤波器 W[l][n][m]
  2. 将该滤波器在水平和垂直方向上进行翻转(旋转180度)。
  3. 将翻转后的滤波器与 ∂L/∂Z[l][n] 进行卷积(通常需要对 ∂L/∂Z[l][n] 进行零填充,以确保输出尺寸与 Y[l-1][m] 相同)。
  4. 将所有输出通道 n 的卷积结果相加。

用公式简要表示为:
∂L/∂Y[l-1] = Conv2D( zero_pad(∂L/∂Z[l]), flip(W[l]) )

步骤3:计算滤波器权重的梯度

最后,我们需要计算损失相对于滤波器权重 W[l][n][m] 的梯度 ∂L/∂W[l][n][m]

在前向传播中,特定的滤波器权重 W[l][n][m][i,j](位于第 i 行,第 j 列)会与输入特征图 Y[l-1][m] 中相应位置的窗口进行逐元素相乘,并贡献给输出特征图 Z[l][n] 的每一个元素。

因此,∂L/∂W[l][n][m][i,j] 需要对输出特征图 Z[l][n] 的所有空间位置 (x,y) 求和:

∂L/∂W[l][n][m][i,j] = Σ_x Σ_y (∂L/∂Z[l][n][x,y] * ∂Z[l][n][x,y]/∂W[l][n][m][i,j])

其中,∂Z[l][n][x,y]/∂W[l][n][m][i,j] = Y[l-1][m][x+i, y+j]

关键发现:这个求和运算同样等价于一个卷积操作!为了计算整个滤波器 W[l][n][m] 的梯度,我们可以:

∂L/∂W[l][n][m] = Conv2D( Y[l-1][m], ∂L/∂Z[l][n] )

也就是说,直接将第 m 个输入特征图 Y[l-1][m] 与第 n 个输出仿射图的梯度 ∂L/∂Z[l][n] 进行卷积,得到的结果就是滤波器 W[l][n][m] 的梯度。

池化层的反向传播

池化层(如最大池化或平均池化)没有需要学习的参数,但我们需要将梯度反向传播通过它。

最大池化 (Max Pooling)

在前向传播中,最大池化从每个局部窗口中选择最大值作为输出,并记录最大值的位置。

在反向传播中,梯度传递非常简单:

  • 对于每个池化窗口,只有前向传播中被选为最大值的那个输入元素接收到了梯度。
  • 因此,我们将输出位置的梯度 ∂L/∂Y_pool 直接“分配”回前向传播中记录的那个最大值输入位置。
  • 如果一个输入元素是多个不同池化窗口的最大值(例如,当步长小于池化核大小时),那么它接收到的梯度是来自所有相关窗口梯度的累加和。

伪代码示意:

# 前向传播记录最大值位置
max_positions = argmax_over_windows(input)
# 反向传播
dinput = zeros_like(input)
for each output_position (x,y):
    dinput[ max_positions[x,y] ] += doutput[x,y]

平均池化 (Average Pooling)

在前向传播中,平均池化计算每个局部窗口内所有元素的平均值作为输出。

在反向传播中,梯度被均匀地分配回该窗口内的所有输入元素,因为每个元素对输出的贡献是相等的。

对于一个池化核大小为 k x k 的平均池化,每个输入元素接收到的梯度是输出梯度除以 k^2,然后累加到所有它贡献过的窗口位置上。

伪代码示意:

# 反向传播
dinput = zeros_like(input)
for each output_position (x,y):
    window = get_input_window(x, y)
    dinput[window] += doutput[x,y] / (k*k)

这也可以看作是用一个元素值全为 1/(k*k) 的核与 doutput 进行卷积(并进行适当的填充和累加)。

实现技巧:利用前向传播代码

一个优雅的实现技巧是,反向传播的代码结构可以与前向传播镜像对称。核心规则只有两个:

  1. 对于非线性激活dZ = dY * f'(Z)
  2. 对于线性卷积(或全连接):若前向为 Z = W * Y,则反向时:
    • dY += W^T * dZ (计算输入梯度)
    • dW += Y * dZ (计算权重梯度)

在实现时,可以遍历与前向传播相同的循环,但顺序相反,并应用上述规则。这样,你甚至不需要显式地推导和记忆复杂的翻转卷积公式,代码会自动处理正确的索引映射。

总结

本节课中,我们一起学习了卷积神经网络(CNN)中核心的反向传播过程。

我们首先回顾了CNN的架构,明确了需要计算梯度的部分。然后,我们深入探讨了:

  • 如何将梯度从卷积层的输出激活图反向传播到其输入仿射图。
  • 如何进一步将梯度传播到前一层的输入特征图,这涉及到一个与翻转滤波器卷积的操作。
  • 如何计算滤波器权重本身的梯度,这涉及到一个输入特征图与输出梯度图卷积的操作。
  • 对于池化层,我们学习了最大池化和平均池化简单的梯度分配规则:最大池化将梯度传回最大值位置,而平均池化将梯度均匀传回窗口内所有位置。

理解这些梯度计算原理,是使用PyTorch、TensorFlow等深度学习框架有效构建和调试CNN模型的基础。这些框架的自动微分功能封装了所有这些复杂的计算,但了解其背后的机制能让我们成为更强大的实践者。

在下一节课中,我们将探讨CNN中的其他技术,如上/下采样(重采样)以及更现代的架构变体。

12:卷积神经网络(CNNs)第四部分 🧠

在本节课中,我们将完成关于卷积神经网络(CNNs)中反向传播的讨论,并探讨一些高级主题,如变换不变性、目标定位、深度可分离卷积以及CNN的历史与发展。我们将确保内容简单明了,适合初学者理解。


📚 概述

上一节我们介绍了如何通过卷积层和池化层进行反向传播。本节中,我们将首先快速回顾这些内容,然后深入探讨上采样和下采样层的反向传播规则。接着,我们将研究如何使CNN对旋转、缩放等变换具有不变性,以及如何利用CNN进行目标定位。最后,我们将了解深度可分离卷积等模型变体,并回顾CNN的发展历程。


🔙 反向传播回顾

在标准训练中,我们使用梯度下降法来优化模型参数,这需要计算损失函数对每个参数的梯度。对于每个训练样本,首先进行前向传播,计算网络输出与期望输出之间的差异(损失)。然后进行反向传播,将损失梯度从输出层逐层传回输入层。

对于卷积神经网络,我们需要处理两种特殊层:卷积层和池化层。

卷积层的反向传播

卷积层涉及参数共享。给定输出激活图的梯度,我们需要计算其对仿射项(卷积结果)的梯度,进而计算其对滤波器权重和上一层输入通道的梯度。

  • 从激活到仿射项:激活图 y 是通过对仿射项 z 逐元素应用激活函数 f 得到的,即 y = f(z)。根据链式法则,损失 Lz 的梯度为:
    dL/dz = dL/dy * f'(z)
    这是一个逐元素的乘法操作。

  • 从仿射项到滤波器和输入:这是更复杂的部分。假设我们有第 m 个输入通道的梯度,我们需要计算损失对该通道的梯度。这涉及到使用第 m 个滤波器通道(因为只有它与第 m 个输入通道交互)与仿射项的梯度图进行卷积操作。具体来说,计算输入通道梯度时,需要将滤波器通道翻转(转置其所有维度),然后与零填充后的梯度图进行卷积。因此,这个操作常被称为转置卷积

    计算第 n 个滤波器的梯度时,我们选取第 n 个输出通道的梯度图(因为该滤波器只计算这个输出),然后将其与所有输入通道进行卷积,从而得到该滤波器所有权重通道的梯度。

池化层的反向传播

池化层(如下采样)减少了特征图尺寸。

  • 最大池化:在前向传播中,每个池化窗口只保留最大值。在反向传播时,梯度只传递给前向传播中被选为最大值的那个输入位置,其他位置的梯度为零。
  • 平均池化:在前向传播中,输出是窗口内所有输入的平均值。在反向传播时,输出位置的梯度被均匀地分配回窗口内的所有输入位置。平均池化也可以看作是一种特殊的卷积操作(使用元素值均为 1/k² 的滤波器,其中 k 是窗口大小),因此其反向传播也遵循类似的卷积规则。

⬆️⬇️ 上采样与下采样层的反向传播

上采样和下采样层可以改变特征图的大小。理解它们的反向传播规则对于处理步长大于1的卷积或分数步长卷积至关重要。

下采样层的反向传播

下采样(如每隔一行一列取一个值)会丢弃一些输入元素。在前向传播中,被丢弃的元素对输出没有影响。

  • 梯度大小:反向传播时,损失对层输入的梯度图大小必须与原始输入大小相同。
  • 梯度计算:对于被保留下来的输入元素,其梯度直接等于输出对应位置的梯度(因为值是被直接复制的)。对于被丢弃(置零)的输入元素,其梯度为零,因为它们不影响输出。
  • 操作实质:从操作上看,下采样层的反向传播就像是上采样——在输出梯度元素之间插入零行和零列,以恢复到输入大小。

上采样层的反向传播

上采样(如插入零行零列)会增大特征图尺寸。插入的零值不是输入的函数。

  • 梯度大小:损失对层输入的梯度图大小与原始输入大小相同。
  • 梯度计算:输出中由输入复制而来的位置,其梯度被直接复制回输入的对应位置。输出中由插入的零值构成的位置,其梯度对输入没有贡献,因此被忽略。
  • 操作实质:上采样层的反向传播就像是下采样——从输出梯度中有选择地取出元素放回输入对应位置。

🎯 简化实现的关键洞见

  • 步长大于1的卷积:可以将其视为“标准步长为1的卷积”后接一个“下采样层”。这样,反向传播时先通过简单的下采样规则,再通过标准的卷积反向传播规则,比直接推导带步长的卷积反向传播公式更简单、不易出错。
  • 分数步长卷积(如步长0.5):可以将其视为“上采样层”后接一个“标准步长为1的卷积”。同样,这简化了反向传播的实现。
  • 带步长的池化:可以视为“标准池化”后接“下采样”。分别处理这两个操作的反向传播更为简便。

重要提示:虽然概念上下采样的反向传播类似上采样,但由于边界效应(例如输入尺寸可能无法被下采样因子整除),不能直接使用同一个上采样层代码来实现反向传播,需要额外注意边界处理。


🔄 超越平移不变性:其他变换

标准CNN通过在不同位置共享滤波器权重来实现平移不变性。如果我们还希望网络对旋转、缩放等变换具有不变性,理论上可以这样做:

  • 方法:对于每个基础滤波器,显式地创建其经过各种所需变换(如旋转45度、60度,缩放1.2倍等)后的多个副本。每个副本作为一个独立的通道,扫描输入。
  • 结果:这样,无论目标图案如何变换,总有一个变换后的滤波器能与之匹配。
  • 局限性:这种方法不可扩展。需要枚举所有可能的离散变换,导致参数数量和计算量急剧增加,且无法泛化到未枚举的变换。

实际做法:我们通常使用数据增强。在训练时,对输入数据随机施加各种变换(旋转、平移、缩放、翻转等),然后训练一个更大的标准CNN模型。模型通过接触大量变换后的样本,学习到对这些变换的鲁棒性,这是一种更可行且有效的方法。


📍 目标检测与定位

标准的分类CNN可以判断“图片中是否有花”,但无法指出“花在哪里”。位置信息其实存在于最后一个全连接层之前的扁平化特征向量中。

  • 定位原理:这个特征向量编码了输入图像的空间信息。我们可以在此向量后接一个额外的子网络(例如另一个小型MLP),来预测目标的位置,通常表示为一个边界框的坐标 (x, y, width, height)
  • 训练:这需要多任务学习。训练数据不仅要有类别标签,还要有边界框标注。损失函数是分类损失(如交叉熵)和定位损失(如边界框坐标的L2损失或IoU损失)的加权和。
  • 应用:此原理也可用于人体姿态估计,即预测关节点的坐标,然后连接成骨骼图。

🧩 模型变体:深度可分离卷积

为了进一步提升参数效率和计算效率,人们提出了深度可分离卷积。它将标准卷积分解为两个步骤:

  1. 深度卷积:每个输入通道使用一个独立的二维滤波器进行卷积,产生与输入通道数相同的中间特征图。滤波器权重在此步骤中所有输出通道间共享
  2. 逐点卷积:使用 1x1 的卷积核,对上述中间特征图进行线性组合,以产生最终数量的输出通道。此步骤的权重决定了不同输出通道的特性

优势

  • 参数更少:标准卷积参数约为 N * M * K²,而深度可分离卷积约为 M * K² + N * MN:输出通道数,M:输入通道数,K:滤波器尺寸)。
  • 计算量更小:大幅减少了乘加操作次数。

核心思想:将空间特征的学习(深度卷积)和通道组合的学习(逐点卷积)解耦,是参数共享思想的进一步延伸。


🏛️ CNN简史与影响

  • 开端(1989):Yann LeCun 提出的 LeNet-5 首次成功将CNN应用于手写数字识别,奠定了基础结构(卷积、池化、全连接)。
  • 复兴(2012):AlexNet 在 ImageNet 大赛上取得突破性胜利,将Top-5错误率从约25%大幅降至15.5%,震惊学界。它引入了 ReLU、Dropout、数据增强等关键技术,并证明了在大规模数据上训练深层CNN的可行性。
  • 快速发展:随后涌现了 VGGNet(探索深度)、GoogLeNet(Inception 模块)、ResNet(残差连接解决深层网络梯度消失/爆炸问题,允许训练数百甚至上千层网络)、DenseNet(密集连接)等一系列重要工作,错误率持续快速下降。
  • 深远影响
    • CNN 不仅是强大的视觉工具,其学到的特征表示具有语义信息,可用于图像检索等任务。
    • CNN 的结构与哺乳动物视觉皮层处理信息的方式(从简单边缘到复杂物体)有惊人的相似性。
    • CNN 的成功复兴了深度学习,其设计原则(局部连接、权重共享、层次化特征提取)深刻影响了后续其他架构(如用于序列处理的RNN、Transformer)。

📝 总结

本节课中,我们一起学习了卷积神经网络(CNNs)的收尾内容:

  1. 完成了反向传播:详细分析了上采样和下采样层的梯度传播规则,并强调了通过分解操作(如将带步长卷积视为卷积+下采样)来简化实现的重要性。
  2. 探讨了变换不变性:理解了理论上实现旋转/缩放不变性的方法及其局限性,以及实践中采用数据增强的可行性。
  3. 扩展了CNN应用:学习了如何通过多任务学习,使CNN不仅能分类,还能进行目标定位和姿态估计。
  4. 认识了高效模型变体:了解了深度可分离卷积如何通过解耦空间和通道维度的学习来大幅提升效率。
  5. 回顾了CNN历程:从 LeNet 到 AlexNet 再到 ResNet,看到了CNN如何推动深度学习革命,并理解了其核心思想为何如此强大和持久。

CNN 是深度学习的基石之一,其思想已广泛应用于图像、语音、文本等多个领域。从下一讲开始,我们将进入循环神经网络(RNNs)的世界。

13:循环神经网络(RNNs)第一部分 🧠

在本节课中,我们将要学习循环神经网络(RNNs)的基本概念。我们将探讨为什么在处理序列数据(如语音、文本或时间序列)时,传统的多层感知机(MLPs)和卷积神经网络(CNNs)存在局限性,并介绍如何通过引入“记忆”或“状态”来构建能够分析整个输入序列的模型。


概述:从静态输入到序列分析

我们之前已经学习了多层感知机(MLPs)如何分析静态输入,以及卷积神经网络(CNNs)如何扫描模式。然而,许多现实世界的问题需要我们考虑一系列输入来产生输出,而这个输出本身也可能是一个序列。

例如,在语音识别中,我们需要分析整个频谱向量序列来决定是否包含某个词。在情感分析中,我们需要阅读整个句子来判断其情感倾向。在股票市场预测中,我们需要分析过去多天的指数模式来决定今天是否投资。

这些问题都属于分类和预测问题,其特点是考虑一个输入向量序列,并产生一个或多个输出。这本质上是一个函数计算问题,而函数可以用神经网络来建模。


有限记忆与无限记忆

上一节我们介绍了处理静态模式的基本网络。本节中,我们来看看如何处理随时间变化的序列模式。

一种直观的方法是使用时间延迟神经网络(TDNN),它本质上是一个卷积神经网络(CNN)。这种网络在每一天,都会查看当前输入以及过去几天的输入,然后基于这个模式做出决定。

公式表示:
输出(t) = 函数(输入(t), 输入(t-1), ..., 输入(t-N))

然而,这是一个有限响应系统。发生在某一天的事件只会影响未来有限时间(N天)内的输出。对于需要捕捉长期趋势(如周趋势、月趋势、季节性趋势)的任务(如股票市场),这显然不够。

我们想要的是一个无限响应系统,即今天发生的事情应该能够永远影响未来的输出。但简单地增加输入窗口(例如,回顾过去10年的数据)会导致参数数量爆炸式增长,这是不现实的。


引入递归:从输出反馈到状态记忆

为了获得无限记忆而不需要无限参数,我们需要引入递归的概念。

非线性自回归外生模型(NARX)

一个简单的想法是:让当前时刻 t 的输出不仅依赖于当前输入 X(t),还依赖于上一个时刻的输出 Y(t-1)

公式表示:
Y(t) = 函数( X(t), Y(t-1) )

这被称为NARX网络。它是一个无限响应系统,因为 t=0 时刻的输入会影响 Y(0),而 Y(0) 又会影响 Y(1),如此循环下去,影响会持续传播。

在这种模型中,关于过去的所有“记忆”都存储在输出序列中,网络内部本身并没有一个变量来记录它已经处理了多长的序列。

早期递归网络:乔丹网络与埃尔曼网络

为了将“记忆”内化到网络本身,研究人员引入了显式的记忆变量。

  • 乔丹网络:引入一个记忆单元 μ,它简单地存储所有过去输出的运行平均值。

    • 公式表示μ(t) = α * μ(t-1) + β * Y(t-1)
    • 隐藏状态 H(t) 依赖于当前输入 X(t) 和记忆 μ(t)
    • 局限性:在训练时,误差导数在记忆单元处停止反向传播,无法追踪远期事件的影响。记忆本质上是输出的副产品。
  • 埃尔曼网络:不再使用输出,而是将上一时刻的隐藏状态 H(t-1) 复制到一个“上下文单元”,并作为当前时刻的额外输入。

    • 公式表示H(t) = 函数( X(t), H(t-1) )
    • 局限性:虽然隐藏状态看起来携带了信息,但在训练时,上下文单元是被“克隆”的,误差导数同样不会通过它反向传播到更早的时间步。因此,它被称为部分递归网络

这两种早期模型的共同问题是:网络内部没有真正能够学习并传播长期依赖关系的记忆机制。


真正的解决方案:状态空间模型(循环神经网络)

真正的突破来自于状态空间模型,也就是我们现在所说的循环神经网络(RNN)

其核心思想是:网络拥有一个隐藏状态 H(t),这个状态是网络的“记忆”。在每一时刻 t

  1. 新的隐藏状态 H(t) 由当前输入 X(t) 和上一时刻的隐藏状态 H(t-1) 共同决定。
  2. 输出 Y(t) 则由当前的隐藏状态 H(t) 决定。

核心公式:

H(t) = 激活函数( W_ih * X(t) + W_hh * H(t-1) + b_h )
Y(t) = 输出函数( W_ho * H(t) + b_o )

其中:

  • W_ih:连接输入到隐藏层的权重(当前权重)。
  • W_hh:连接上一时刻隐藏状态到当前时刻隐藏层的权重(循环权重)。
  • W_ho:连接隐藏层到输出的权重。

现在,网络内部的状态 H(t) 明确地总结了所有过去输入的信息。这是一个完全递归网络,因为误差导数可以通过 W_hh 这条路径,从当前时刻一直反向传播到序列的起始点,从而能够学习长期的依赖关系。


训练循环神经网络:随时间反向传播(BPTT)

训练RNN在概念上并不神秘。将RNN按时间步展开后,它就变成了一个非常深的、但权重共享的前馈神经网络。

关键点:

  • 输入:一个序列 [X(1), X(2), ..., X(T)]
  • 目标输出:一个对应的序列 [Y_target(1), Y_target(2), ..., Y_target(T)]
  • 网络输出:网络会产生一个序列 [Y(1), Y(2), ..., Y(T)]
  • 损失函数:我们需要最小化网络输出序列和目标序列之间的差异(损失)。这个损失通常是各时间步损失的总和,但本质上衡量的是两个序列的整体差异。

训练算法称为随时间反向传播(Backpropagation Through Time, BPTT),其步骤可概括为:

  1. 前向传播:将整个输入序列按时间步输入网络,计算所有时刻的隐藏状态和输出。
  2. 计算损失:计算网络输出序列与目标序列之间的总损失。
  3. 反向传播
    • 从最后一个时间步 T 开始,计算损失对输出 Y(T) 的梯度。
    • 将这个梯度通过网络反向传播,计算对权重 W_hoW_hhW_ih 以及各时刻隐藏状态的梯度。
    • 关键:由于权重是共享的(例如,连接 H(1)->H(2)W_hh 和连接 H(2)->H(3)W_hh 是同一个矩阵),所以在反向传播时,来自不同时间步对同一权重的梯度必须累加(求和)
  4. 参数更新:使用累积的梯度更新所有权重参数。

BPTT使得网络能够学习到“某个遥远过去的事件如何影响当前输出”,从而捕捉长期依赖。


双向循环神经网络(Bi-RNN)

到目前为止,我们介绍的RNN都是单向的,即 H(t) 只依赖于过去 (X(1)...X(t-1)) 和当前 X(t) 的信息。这对于股票预测等任务很合适,因为无法预知未来。

但在许多任务中,整个输入序列是已知的(如机器翻译、词性标注),当前时刻的输出可能同时依赖于其上下文(左边和右边的信息)。例如,判断“red”是形容词还是名词,需要看它后面跟着的是“car”还是“Carpenter”。

双向RNN 通过组合两个独立的RNN来解决这个问题:

  1. 前向RNN:从左到右处理序列,计算前向隐藏状态 H_forward(t),它编码了到时刻 t 为止的过去信息。
  2. 后向RNN:从右到左处理序列,计算后向隐藏状态 H_backward(t),它编码了从时刻 t 到序列结束的未来信息。

在每一时刻 t,最终的输出或特征表示是前向和后向隐藏状态的拼接或组合。

公式表示(简化):

H_forward(t) = RNN_forward( X(t), H_forward(t-1) )
H_backward(t) = RNN_backward( X(t), H_backward(t+1) )
Output_representation(t) = [ H_forward(t); H_backward(t) ]

训练:训练双向RNN需要对前向和后向网络分别进行BPTT。在反向传播时,损失函数的梯度会被拆分,分别流向两个方向独立的网络路径。


总结

本节课中我们一起学习了循环神经网络的基础知识:

  1. 动机:处理序列数据需要能够考虑历史信息的模型。
  2. 演进:从有限记忆的TDNN,到通过输出反馈实现无限记忆的NARX,再到将记忆内化为网络状态的RNN。
  3. 核心:RNN通过隐藏状态 H(t) 来记忆和总结历史信息,其更新依赖于当前输入和前一状态。
  4. 训练:使用随时间反向传播(BPTT)算法,通过展开网络并累加共享权重的梯度来训练。
  5. 扩展:对于需要利用整个上下文的任务,可以使用双向RNN,它同时从前向和后向处理序列。

在接下来的课程中,我们将探讨RNN在实际应用中的表现、可能遇到的问题(如梯度消失/爆炸),以及如何将它们用于时间序列预测、分类和生成等更复杂的任务。

14:循环神经网络 (RNNs) 第二部分 🧠

在本节课中,我们将继续探讨循环神经网络。我们将了解为什么简单的RNN在记忆长期信息方面存在困难,以及如何通过更先进的架构(如长短期记忆网络)来解决这些问题。


概述

上一节我们介绍了循环神经网络的基本概念,以及它们如何通过循环结构处理序列数据。本节中,我们将深入探讨RNN在长期记忆方面面临的挑战,并介绍一种强大的解决方案——长短期记忆网络。


RNN的局限性:记忆与梯度问题

我们之前看到,循环结构理论上可以记住来自“时间起点”的信息。但在实践中,简单的RNN在记忆方面表现不佳。

线性系统的行为分析

为了理解问题所在,我们先分析一个简化的线性RNN。假设我们有一个没有非线性激活函数的循环层,其操作可以表示为:

公式:
h_t = W_h * h_{t-1} + W_x * x_t

其中,h_t 是时间步 t 的隐藏状态,W_h 是循环权重矩阵,W_x 是输入权重矩阵,x_t 是时间步 t 的输入。

如果我们只考虑在时间 0 有一个输入 x_0,而后续输入均为 0,那么经过 t 个时间步后,隐藏状态变为:

公式:
h_t = (W_h)^t * W_x * x_0

这个公式揭示了关键问题:隐藏状态 h_t 的行为完全取决于循环权重矩阵 W_ht 次幂。

特征值的影响

通过对权重矩阵 W_h 进行特征值分解,我们可以发现:

  • 如果 W_h 的最大特征值(按模计算)大于1(W_h)^t 会随着 t 增大而爆炸式增长,导致隐藏状态值变得极大(爆炸)。
  • 如果最大特征值小于1(W_h)^t 会随着 t 增大而趋近于0,导致隐藏状态值消失(梯度消失)。

这意味着,在简单的线性RNN中,网络要么很快忘记过去的输入,要么状态值变得不稳定,无法有效保留长期信息。

加入非线性激活函数

在实际的RNN中,我们会使用非线性激活函数(如 tanhsigmoid)。分析表明:

  • sigmoid 激活函数:容易饱和,网络在几个时间步后就会忘记输入,最终状态仅取决于权重和偏置。
  • tanh 激活函数:比 sigmoid 稍好,能记住信息更久一些,但最终仍然会忘记。
  • ReLU 激活函数:可能导致输出爆炸或消失。

结论是:无论使用哪种激活函数,简单RNN都难以维持长期记忆。记忆的持续时间取决于循环权重矩阵和激活函数,而不是输入数据本身,这与许多任务(如代码解析中需要配对括号)的需求不符。


深度网络中的梯度问题

RNN在时间上展开后,本质上是一个非常深的网络。训练深度网络时,反向传播梯度会遇到不稳定问题。

梯度消失与爆炸

在反向传播过程中,误差梯度需要从输出层逐层传递回输入层。每一步都涉及与权重矩阵和激活函数雅可比矩阵的乘法。

  • 激活函数雅可比矩阵:通常是对角矩阵,其对角线元素是激活函数的导数。对于 sigmoidtanh 等函数,其导数值通常小于或等于1,这会导致梯度收缩
  • 权重矩阵:通过奇异值分解分析,乘以权重矩阵会使梯度在某些方向扩张(奇异值>1),在另一些方向收缩(奇异值<1)。

由于各层的收缩方向通常不对齐,经过多层反向传播后,大多数参数的梯度会变得非常小(消失),而少数方向的梯度可能变得非常大(爆炸)。这使得网络深层参数难以得到有效更新。

实验表明,即使是改进的激活函数如 ELU,梯度在反向传播约10层后也基本会消失。


解决方案:长短期记忆网络 (LSTM)

为了解决简单RNN的长期记忆和梯度问题,我们引入了长短期记忆网络。其核心思想是:让记忆的更新由输入数据本身触发和控制,而不是固定的网络参数

LSTM 的核心概念

LSTM 引入了一个称为“细胞状态”的恒定误差传送带。信息在这个传送带上流动,只受到一些“门”结构的轻微调节。这些门由输入数据和当前上下文决定,从而实现了长期记忆。

以下是LSTM单元的关键组件图示:

输入 (x_t)          上一时刻隐藏状态 (h_{t-1})
      |                    |
      ---------------|--------------
                    \|/
                 [LSTM 单元]
                    /|\
      ---------------|--------------
      |                    |
当前隐藏状态 (h_t)       细胞状态 (C_t)

一个LSTM单元内部主要包含以下部分:

  1. 细胞状态:记忆的“主线”,信息在其中相对不变地流动。
  2. 遗忘门:一个 sigmoid 层,决定从细胞状态中丢弃哪些信息。它查看 h_{t-1}x_t,并为细胞状态 C_{t-1} 中的每个元素输出一个0到1之间的数(1表示“完全保留”,0表示“完全遗忘”)。
    • 公式f_t = σ(W_f · [h_{t-1}, x_t] + b_f)
  3. 输入门:一个 sigmoid 层,决定哪些新信息将被存储到细胞状态中。
    • 公式i_t = σ(W_i · [h_{t-1}, x_t] + b_i)
  4. 候选值:一个 tanh 层,创建一个新的候选值向量,可能会被添加到细胞状态中。
    • 公式\tilde{C}_t = tanh(W_C · [h_{t-1}, x_t] + b_C)
  5. 更新细胞状态:将旧状态 C_{t-1} 更新为新状态 C_t。首先将旧状态乘以遗忘门 f_t(丢弃部分信息),然后加上输入门 i_t 和候选值 \tilde{C}_t 的乘积(添加新信息)。
    • 公式C_t = f_t * C_{t-1} + i_t * \tilde{C}_t
  6. 输出门:一个 sigmoid 层,基于细胞状态决定输出什么。
    • 公式o_t = σ(W_o · [h_{t-1}, x_t] + b_o)
  7. 最终隐藏状态输出:将细胞状态 C_t 通过 tanh 函数(将值缩放到-1到1之间),然后乘以输出门 o_t,得到当前时刻的隐藏状态 h_t,并作为输出。
    • 公式h_t = o_t * tanh(C_t)

LSTM 的优势

  • 解决长期依赖:通过精心设计的门控机制,LSTM可以选择性地记住或忘记信息,从而有效捕捉长期依赖关系。
  • 缓解梯度问题:细胞状态上的线性循环连接(C_t = f_t * C_{t-1} + ...)使得梯度可以更稳定地流动,缓解了梯度消失问题。

简化版:门控循环单元

由于LSTM结构相对复杂,后来出现了其简化变体——门控循环单元。GRU将遗忘门和输入门合并为一个“更新门”,同时将细胞状态和隐藏状态合并,使结构更加简洁,但在许多任务上仍能取得与LSTM相当的性能。


训练 LSTM

训练LSTM与训练标准RNN类似,都使用基于时间的反向传播算法。虽然LSTM单元内部计算复杂,但现代深度学习框架(如PyTorch、TensorFlow)可以自动计算梯度,我们只需定义前向传播过程即可。

关键点在于,将LSTM的前向传播过程分解为一系列线性运算后接激活函数的基本操作。这样,反向传播就可以通过框架的自动微分功能轻松实现,无需手动推导复杂的梯度公式。


总结

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

  1. 简单RNN的局限性:分析了其在长期记忆方面的不足,以及训练时面临的梯度消失/爆炸问题。
  2. LSTM的原理:介绍了长短期记忆网络如何通过细胞状态和门控机制(遗忘门、输入门、输出门)来解决长期依赖问题。
  3. LSTM的优势:LSTM能够选择性地保留和遗忘信息,使梯度流动更稳定,非常适合需要长程记忆的任务,如机器翻译、文本生成、代码解析等。
  4. 训练与应用:LSTM可以通过BPTT进行训练,并且可以像标准RNN一样堆叠成多层或构建双向网络。

通过引入LSTM,我们获得了处理长序列数据的强大工具,为后续学习更复杂的序列模型奠定了基础。

15:循环神经网络(RNN)第三部分 🧠

在本节课中,我们将学习如何将循环神经网络应用于输出与输入异步的问题,例如语音识别。我们将重点探讨当不知道输出符号在输入序列中的确切位置(即“对齐”信息)时,如何训练模型。


概述:从同步到异步输出

上一节我们介绍了门控机制(如LSTM、GRU)如何解决RNN的稳定性问题。本节中,我们来看看当输出序列与输入序列在时间上不同步时带来的挑战。

在诸如股票预测或词性标注等任务中,每个输入都对应一个输出,这是时间同步的。然而,在语音识别或手写识别中,输入是一长串信号(如音频帧),而输出是较短的符号序列(如单词),我们并不知道每个输出符号应该在输入的哪个时刻产生。这就是顺序同步但时间不同步的问题。


将问题转化为时间同步格式

一个直接的思路是尝试将异步输出问题转化为我们熟悉的时间同步问题,以便于训练。

通过重复符号进行扩展

假设我们有一个输入序列 X0...XT 和一个目标符号序列(例如单词 “but”)。虽然我们不知道 BUT 这三个音素在输入时间轴上的确切位置,但我们可以假设:在属于某个音素的时间段内,网络应该持续输出该音素

这意味着,我们可以将短的目标序列(如 B, U, T扩展成一个与输入等长的时间同步序列,方法是在未知的对应时间段内重复每个符号。例如,一个可能的扩展是 B, B, B, U, U, T, T

核心转换

  • 压缩序列:顺序同步的短序列(如 “but”)。
  • 扩展序列:通过重复符号得到的、与输入等长的时间同步序列。
  • 对齐:指的就是一个具体的扩展序列,它定义了每个输入时刻“应该”输出哪个符号。

通过这种转换,我们就可以为每个时间步定义一个目标输出,从而将序列级的损失函数转化为各时间步损失之和,便于通过反向传播训练。


挑战:对齐信息未知

然而,在实际训练数据中,我们只有输入序列和压缩后的目标序列(如文本转录),并没有对齐信息。我们不知道 BUT 具体应对应到哪些输入帧。

以下是解决此问题的两种思路:

  1. 猜测一个对齐:使用启发式方法初始化一个对齐,然后迭代优化。
  2. 考虑所有可能对齐:在计算损失时,不依赖单一对齐,而是考虑所有可能对齐的总体效应。

本节课我们主要探讨第一种方法。


迭代对齐与训练

我们可以通过一个迭代过程来同时学习模型参数和对齐。

算法步骤

以下是该过程的核心步骤:

  1. 初始化对齐:对于每个训练样本,使用简单启发式方法(如均匀拉伸)生成一个初始的时间同步对齐序列。
  2. 训练模型:使用当前对齐序列作为每个时间步的目标,以时间同步的方式训练RNN。损失函数是各时间步交叉熵损失之和。
    • 对于目标类别 c,在时间步 t 的损失为:L_t = -log(y_t^c),其中 y_t^c 是网络对类别 c 预测的概率。
    • 该损失对网络输出 y_t 的梯度是一个向量,仅在目标类别 c 对应的位置为 -1 / y_t^c,其余位置为0。
  3. 重新估计对齐:使用当前训练好的模型,为每个输入寻找最可能的对齐序列(即最可能的扩展序列)。
  4. 迭代:用新估计的对齐替换旧的对齐,回到步骤2,重复此过程直至收敛。

这个过程的核心在于第3步:如何利用当前模型找到最可能的一个对齐? 这需要一个高效的搜索算法。


维特比算法:寻找最优对齐

我们需要在所有可能的扩展序列(即所有有效对齐) 中,找到概率最高的那一个。这可以建模为一个在概率网格中寻找最优路径的问题。

构建概率网格

  1. 对于给定的输入序列和压缩目标序列(如 “B, E, F, E”),运行当前RNN,得到每个时间步 t 上所有符号的概率分布。
  2. 构建一个网格,其对应目标序列的每个符号(按顺序,重复的符号占多行),对应输入时间步。
  3. 网格节点 (t, s) 的分数即为模型在时间 t 预测符号 s 的概率 y_t^s

路径与约束

一条从网格左上角到右下角的路径代表一种可能的对齐。路径需遵循规则:在每一步,只能向右(延长当前音素)或向右下(转移到下一个音素)移动。这保证了路径产生的符号序列在压缩后能得到原始目标序列。

路径的概率是其经过所有节点的概率的乘积。我们的目标是找到概率最大的路径。

动态规划求解

直接枚举所有路径是指数级的。我们可以使用维特比算法,这是一种动态规划算法,用于高效寻找最优路径。

算法核心思想

  • 到达当前节点 (t, s) 的最优路径,必然是通过其前驱节点 (t-1, s)(来自同一符号)或 (t-1, s-1)(来自上一个符号)的最优路径扩展而来。
  • 因此,我们只需为每个节点保存两个信息:
    1. 到达该节点的最高分数
    2. 得到该分数的前驱节点(用于回溯得到完整路径)。
  • 从左到右、从上到下遍历网格,递推计算每个节点的这两个信息。
  • 最终,右下角节点的分数即为最优路径分数,通过回溯其前驱节点即可得到完整对齐序列。

递推公式(取对数后变为加法,更稳定)
score(t, s) = y_t^s * max( score(t-1, s), score(t-1, s-1) )


方法总结与局限

本节课我们一起学习了一种处理输出异步问题的经典方法:

  1. 核心思路:通过将短目标序列扩展为与输入等长的序列,把异步问题转化为时间同步问题。
  2. 训练挑战:真实数据缺少扩展序列(对齐)信息。
  3. 解决方案:采用迭代对齐训练
    • 初始化对齐(如均匀对齐)。
    • 训练RNN模型。
    • 用训练好的模型和维特比算法重新估计最优对齐。
    • 迭代上述步骤。
  4. 潜在问题:这种方法严重依赖于初始对齐的质量,容易陷入局部最优。如果初始模型很差,可能导致学习失败。

展望

下一节课,我们将探讨一种更优雅、不需要显式迭代对齐的解决方案——连接主义时间分类。它将所有可能对齐的贡献考虑在内,直接优化目标序列的整体概率,从而避免了初始对齐偏差和局部最优问题,是处理此类异步序列任务的强大工具。

16:序列到序列模型 🧠

在本节课中,我们将学习如何使用循环神经网络将一种序列转换为另一种序列。我们将重点关注输出与输入顺序对齐但时间不同步的问题,例如语音识别。我们将探讨如何训练此类模型,特别是当训练数据没有提供精确的时间对齐信息时。


概述

序列到序列转换是深度学习的核心任务之一。输入是一个序列(如音频帧),输出是另一个序列(如单词)。这两个序列的长度可能不同,并且没有直接的、时间同步的对应关系。本节课,我们将深入探讨一种专门处理此类问题的模型:连接时序分类模型。


序列到序列问题分类

我们可以将序列到序列转换问题大致分为两类:

  1. 顺序对齐但时间不同步:输出的顺序与输入的顺序大致对应,但每个输出符号的持续时间可能不同。例如,在语音识别中,说出的单词顺序与识别的文本顺序相同,但每个音素或单词的时长不同。
  2. 无顺序对应关系:输入和输出序列之间没有直接的顺序对应关系。例如,在机器翻译中,一种语言的句子结构可能与另一种语言完全不同。

本节课我们主要讨论第一类问题。


训练对齐已知的模型

首先,我们考虑一种理想情况:在训练时,我们不仅知道输入序列 X1, X2, ..., XN 和输出符号序列 Y1, Y2, ..., YM,还知道每个输出符号 Yk 具体对应输入序列中的哪一段时间。

在这种情况下,训练变得相对简单。我们可以通过重复每个输出符号,使其覆盖它所对应的输入时间段,从而将问题转化为一个时间同步的分类问题。

网络的损失函数(散度)是每个时间步上,网络分配给目标符号的负对数似然之和:

公式: D = - Σ_t log( P(Y_target(t) | X) )

其中,P(Y_target(t) | X) 是网络在时间步 t 分配给目标符号的概率。

这个损失函数关于网络输出的导数也很容易计算:对于目标符号,导数是 -1 / P(Y_target);对于其他符号,导数为0。


训练对齐未知的模型(迭代对齐)

然而在现实中,我们通常只有输入和输出序列,而不知道精确的时间对齐信息。如何训练模型呢?

一种方法是迭代对齐,步骤如下:

  1. 初始化对齐:为训练数据提供一个初始的(可能是粗糙的)时间对齐。
  2. 训练模型:使用这个对齐,像上一节那样训练一个初始模型。
  3. 重新估计对齐:使用训练好的模型和维特比算法,为每个训练样本找出新的、最可能的时间对齐路径。
  4. 迭代优化:用新的对齐更新模型,再用更新后的模型重新估计对齐,如此反复,直到收敛。

这种方法虽然有效,但严重依赖于初始对齐的质量。如果初始对齐很差,模型可能会陷入较差的局部最优解。


训练对齐未知的模型(考虑所有对齐)

为了避免对单一对齐的依赖,我们可以采用一种更优雅的方法:考虑所有可能的时间对齐,并优化所有对齐上的期望损失

以下是具体思路:

  1. 构建概率表:将输入序列通过网络,得到一个概率表,其中包含了每个时间步上每个可能符号的概率。
  2. 提取目标行:根据输出符号序列(例如 “B E E”),从概率表中提取对应符号的行,构建一个新的表格。这个表格的每一行代表一个输出符号在不同时间步的概率。
  3. 定义对齐图:这个新表格可以看作一个图。图中的每个节点 (t, r) 表示在时间 t 输出第 r 个目标符号。从左上角到右下角的任何一条路径都代表一种可能的时间对齐方式。
  4. 计算期望损失:我们不只取最可能的路径,而是计算所有可能路径的损失,并按其概率加权平均。这等价于计算每个时间步上,每个节点损失的期望值。

为了高效计算这个期望损失,我们需要两个关键算法:前向算法后向算法


前向算法(Forward Algorithm)

前向算法用于计算从图起点到达某个节点 (t, r) 的所有路径的概率总和,记为 α(t, r)

计算规则(递归)
α(t, r) = [α(t-1, r) + α(t-1, r-1)] * y(t, r)
其中 y(t, r) 是节点 (t, r) 本身的概率(即网络在时间 t 输出第 r 个目标符号的概率)。

我们从左向右计算,初始化 α(0, 0) = 1(或根据实际情况初始化第一列)。最终,右下角节点的 α 值就是整个输出序列对应输入的总概率。


后向算法(Backward Algorithm)

后向算法用于计算从某个节点 (t, r) 到图终点的所有路径的概率总和,记为 β(t, r)。我们通常先计算包含当前节点概率的 β_hat(t, r)

计算规则(递归)
β_hat(t, r) = y(t, r) * [β_hat(t+1, r) + β_hat(t+1, r+1)]
然后,β(t, r) = β_hat(t, r) / y(t, r)

我们从右向左计算,初始化最后一列的 β_hat 值。最终,我们可以得到每个节点的 β 值。


计算后验概率与损失

有了 αβ,我们可以计算对于给定的输入和输出序列,在时间 t 对齐到第 r 个符号的后验概率 γ(t, r)

公式: γ(t, r) = α(t, r) * β(t, r) / Z
其中 Z 是一个归一化因子,确保每一列(同一时间步)的 γ 之和为1。Z 通常就是该列所有 α*β 的乘积之和。

最终,我们的损失函数(所有对齐的期望负对数似然)可以简洁地表示为:

公式: L = - Σ_t Σ_r γ(t, r) * log( y(t, r) )

这个损失函数关于网络输出 y(t, r) 的导数近似为 -γ(t, r) / y(t, r)。这个近似忽略了 γy 的依赖,但在实践中效果很好,并且使得反向传播可以顺利进行。


处理重复符号:空白符号(Blank)

上述方法还有一个问题:如何区分输出中的重复字符?例如,路径 “R R E E D D” 压缩后是 “RED” 还是 “REED”?

解决方案是引入一个特殊的空白符号(blank),通常用 “-” 表示。这个符号是“不可见”的,在最终输出时会被移除。

规则如下

  • 网络在每个时间步也会输出空白符号的概率。
  • 在构建对齐图时,我们在输出符号序列的每个符号之间以及首尾插入可选的空白符号。
  • 关键规则:如果两个相邻的真实符号相同(如 “DD”),则它们之间必须有一个空白符号,否则在压缩时两个 “D” 会合并成一个。如果两个符号不同,则空白符号是可选的。

引入空白符号后,对齐图会变得更大(包含了空白节点),但前向-后向算法和损失计算的过程完全不变,只是图的连接规则根据上述规则进行了调整。


推理:如何从模型得到输出序列

训练好模型后,我们如何进行推理(解码)?即给定一个输入序列,如何得到最可能的输出符号序列?

  1. 贪婪解码:最简单的方法是每个时间步都选择概率最高的符号(包括空白),然后将得到的序列进行压缩(合并重复字符并移除空白)。这种方法速度快,对于CTC模型通常效果不错。
  2. 束搜索解码:为了找到全局最优的序列,我们需要考虑所有可能的输出序列。直接计算所有序列的概率是指数级的,不可行。因此,我们使用束搜索
    • 在束搜索中,我们不是展开所有可能的序列,而是在每个时间步只保留概率最高的 K 个候选序列(K 是束宽)。
    • 我们扩展这些候选序列,再次评估并保留最好的 K 个,直到处理完整个输入序列。
    • 最后,从最终的 K 个候选序列中选出总概率最高的一个(注意:不同对齐可能对应同一输出序列,需要将其概率相加)。

束搜索在计算效率和结果质量之间取得了很好的平衡。


总结

本节课我们一起学习了连接时序分类模型,这是一种用于处理顺序对齐但时间不同步的序列到序列转换问题的强大工具。

我们重点掌握了:

  • 当训练数据没有对齐信息时,通过考虑所有可能对齐的期望损失来训练模型。
  • 使用前向-后向算法高效计算用于损失函数的后验概率 γ(t, r)
  • 引入空白符号来解决输出中重复字符的歧义问题。
  • 模型推理时,可以使用贪婪解码或更精确的束搜索来获得输出序列。

CTC模型在语音识别、手写识别等任务中有着广泛的应用。理解其核心思想,对于掌握更复杂的序列到序列模型(如下节课将讲的编码器-解码器架构)至关重要。

17:循环网络 - 语言建模与序列到序列模型 🧠➡️🗣️

在本节课中,我们将学习如何使用循环神经网络来建模语言,并构建一个基础的序列到序列模型。我们将从理解语言建模的核心概念开始,逐步深入到如何将这种模型应用于机器翻译、对话生成等任务。


语言建模 📚

上一节我们介绍了循环神经网络处理序列数据的能力。本节中,我们来看看如何利用这种能力来建模语言。

语言建模的核心目标是对语言中所有可能的符号序列(如单词序列)的概率分布进行建模。一个语言模型可以计算给定符号序列的概率,也可以从这个分布中生成新的序列。

然而,直接对整个序列的概率分布 P(w1, w2, ..., wN) 进行建模非常困难,因为可能的序列数量是无限的。为了解决这个问题,我们使用贝叶斯规则将其分解:

公式:
P(w1, w2, ..., wN) = P(w1) * P(w2|w1) * P(w3|w1, w2) * ... * P(wN|w1, ..., wN-1)

这样,建模整个序列的问题就转化为了一个更简单的问题:在给定所有历史词的情况下,预测下一个词的概率,即 P(下一个词 | 历史词)。这正是循环神经网络或卷积神经网络(有限历史窗口)可以处理的任务。

以下是语言建模的两个主要应用:

  • 计算序列概率:通过将序列输入模型,并逐词读取模型预测的“下一个词”的概率,然后将这些概率相乘,即可得到整个序列的概率。
  • 生成新序列:从起始标记开始,模型每一步都根据当前历史预测下一个词的概率分布,然后从这个分布中采样得到下一个词,并将其反馈给模型,重复此过程直到生成结束标记。

为了使模型知道句子的开始和结束,我们需要在序列中添加特殊的标记:

  • <SOS> (Start of Sequence):表示序列开始。
  • <EOS> (End of Sequence):表示序列结束。

词嵌入与表示 🔤➡️🔢

在将词输入神经网络之前,我们需要将其转换为数值表示。最直接的方法是独热编码,即每个词用一个很长的向量表示,其中只有对应词索引的位置为1,其余为0。

代码示例(独热编码概念):

# 假设词汇表为 [“cat”, “dog”, “fish”]
“cat” -> [1, 0, 0]
“dog” -> [0, 1, 0]
“fish” -> [0, 0, 1]

然而,独热编码有两个主要缺点:

  1. 维度灾难:词汇表很大时,向量维度极高,非常稀疏且低效。
  2. 缺乏语义:任意两个不同词向量的距离都相同,无法体现词之间的语义关系(如“国王”与“王后”的相似性)。

为了解决这些问题,我们引入词嵌入。其核心思想是通过一个可学习的线性层(一个矩阵),将高维的独热向量投影到一个低维的连续空间中。

公式:
embedding = W * one_hot_vector

这个投影矩阵 W 可以在训练语言模型(如下一个词预测任务)的过程中一同学习。神奇的是,在这个学习到的低维空间中,语义相近的词(如“国王”和“王后”)会彼此靠近,并且词向量之间的算术运算可以捕捉语义关系(如 king - man + woman ≈ queen)。


序列到序列模型 🔄

上一节我们介绍了如何用循环网络建模单一语言。本节中我们来看看如何将其扩展,用于处理输入序列和输出序列可能长度不同、且没有直接对齐关系的任务,例如机器翻译。

我们需要的模型结构是:先完整读取输入序列,然后基于对输入的理解生成输出序列。这引出了编码器-解码器架构。

以下是该模型的工作流程:

  1. 编码器:一个循环神经网络,逐词读取输入序列 X,直到遇到 <EOS>。编码器最终隐藏状态(图中红框)旨在概括整个输入序列的信息
  2. 解码器:另一个循环神经网络,它是一个条件语言模型。它的初始隐藏状态是编码器的最终状态。解码器以 <SOS> 作为第一个输入,开始生成输出序列。
  3. 生成过程:解码器每一步都基于当前隐藏状态和上一步生成的词,计算下一个词的概率分布。从这个分布中采样(或选择)一个词,作为当前输出,并反馈给解码器作为下一步的输入,重复此过程直到生成 <EOS>

重要:在训练时,为了稳定学习过程,我们采用“教师强制”策略。即,在解码器的每一步,我们使用上一步模型自己的输出,而是直接使用真实目标序列中对应的上一个词作为输入。这确保了训练初期模型也能接收到正确的信号。


解码策略:从贪婪到束搜索 🧭

模型每一步都会输出一个概率分布,我们如何根据这个分布决定最终的输出序列呢?有以下几种策略:

以下是几种常见的解码策略:

  • 贪婪解码:每一步都选择概率最高的词。这种方法简单高效,但可能因为早期的局部最优选择而导致最终序列整体概率并非最高。
  • 随机采样:每一步根据概率分布随机采样下一个词。这种方法能产生更多样化的输出,有时比贪婪解码的结果更流畅、自然,但不保证输出是最优的。
  • 束搜索:一种折中的启发式方法。它维护一个大小为 k(束宽)的候选序列集合。在每一步,对每个候选序列扩展所有可能的下一词,但只保留总体概率(序列所有词概率的乘积)最高的 k 个新序列。当有候选序列生成 <EOS> 时,可将其作为输出或继续搜索。

束搜索通常比贪婪解码效果更好,但它倾向于生成更短的序列(因为更长的序列概率乘积会累积更多小于1的因子),因此在实际中常需要长度归一化等启发式方法进行调整。


模型训练与应用 🏋️

序列到序列模型的训练目标是最小化模型预测的分布与真实目标词分布之间的差异(如交叉熵损失)。由于采用了“教师强制”,训练过程是高效且稳定的。

这种简单的编码器-解码器架构非常强大,可以应用于多种任务:

以下是序列到序列模型的一些应用场景:

  • 机器翻译:编码器读源语言句子,解码器生成目标语言句子。
  • 文本摘要:编码器读长文章,解码器生成简短摘要。
  • 对话系统:编码器读用户问题,解码器生成系统回复。
  • 图像描述:编码器替换为CNN处理图像,解码器生成描述图像的文本。

总结 ✨

本节课中我们一起学习了循环神经网络在语言建模和序列到序列任务中的应用。我们从统计语言建模的基本概念出发,理解了如何通过下一个词预测来建模整个语言。接着,我们探讨了词嵌入的重要性,它能将离散的符号转化为富含语义的连续向量。

然后,我们重点介绍了编码器-解码器架构,这是处理非对齐序列转换任务(如机器翻译)的核心框架。编码器负责压缩输入信息,解码器作为条件语言模型负责生成输出。我们还讨论了不同的解码策略,包括贪婪解码、随机采样和更优的束搜索。

最后,我们看到了这种简单而强大的模型在机器翻译、图像描述等领域的成功应用。然而,这种基础模型有一个关键限制:编码器需要将整个输入序列的信息压缩到一个固定长度的向量中,这对于长序列来说是一个信息瓶颈。在下一节课中,我们将学习如何通过“注意力机制”来突破这一限制。

19:循环网络 - 语言建模与序列到序列模型 🧠➡️📝

在本节课中,我们将学习如何使用循环神经网络(RNN)来建模语言,并构建一个基础的序列到序列(Seq2Seq)模型。我们将从统计语言建模的基本概念开始,逐步深入到如何构建一个能够将一种序列(如英语句子)转换为另一种序列(如德语句子)的模型。


概述:什么是语言建模?

语言建模的核心目标是对语言中所有可能的符号序列(如单词序列)的概率分布进行建模。简单来说,一个好的语言模型应该能够判断一个句子(如“I ate an apple”)在真实语言中出现的可能性是高还是低,同时也能生成听起来自然的新句子。

然而,直接对整个无限可能的句子空间建模是极其困难的。因此,我们将其分解为一个更易处理的问题:预测序列中的下一个符号。给定到目前为止的所有单词,模型需要预测下一个最可能出现的单词是什么。


从有限历史到循环网络

上一节我们介绍了语言建模的目标。本节中,我们来看看如何用神经网络来实现它。

最初,我们可以尝试使用一个有限历史模型,例如一个时间延迟神经网络(TDNN)。它假设下一个单词的概率仅取决于前N个单词。

公式表示:
P(W_n | W_0, W_1, ..., W_{n-1}) ≈ f(W_{n-N}, ..., W_{n-1})

这里,f 可以是一个神经网络。单词通常首先被表示为独热编码(one-hot)向量。例如,在一个包含5个单词的词汇表中,“apple”可能被表示为 [0, 0, 1, 0, 0]

然而,独热编码存在两个主要问题:

  1. 维度灾难:词汇表越大,向量维度越高,且表示极其稀疏。
  2. 缺乏语义关系:任意两个不同单词的独热向量之间的距离是恒定的(例如√2),无法体现“国王”和“王后”之间的语义关联。

为了解决这些问题,我们引入词嵌入(Word Embedding)


词嵌入:从稀疏到稠密

为了解决独热编码的问题,我们通过一个可学习的投影矩阵(线性层) 将高维的独热向量映射到一个低维的稠密向量空间。

代码描述(概念):

# 假设 vocab_size=10000, embedding_dim=300
embedding_layer = nn.Linear(vocab_size, embedding_dim, bias=False)
# 输入是独热向量,输出是300维的词嵌入
word_embedding = embedding_layer(one_hot_vector)

这个投影矩阵是在训练语言模型(即执行“下一个词预测”任务)的过程中同时学习到的。神奇的是,在这个学习到的低维空间中,语义相似的单词会彼此靠近,并且还能捕捉到诸如“国王 - 男人 + 女人 ≈ 王后”这样的向量关系。


循环神经网络作为语言模型

有限历史模型仍然受限于窗口大小。为了考虑整个历史,我们自然需要使用循环神经网络(RNN)

一个RNN语言模型在每个时间步 t 接收当前的单词输入(或其嵌入)和之前的隐藏状态,然后输出一个在所有可能单词上的概率分布,预测下一个单词。

训练:我们可以轻松地从大量文本中创建训练数据。给定一个句子“Betty Botter bought butter”,我们可以生成训练样本:

  • 输入 [<SOS>, Betty],目标输出 Botter
  • 输入 [<SOS>, Betty, Botter],目标输出 bought
  • 以此类推...

其中 <SOS> 是代表句子开始的特殊符号。

两个核心应用

  1. 计算序列概率:要计算整个句子“W1, W2, W3, W4”的概率,只需让RNN依次处理该序列,并在每个时间步 t 记录模型分配给实际下一个词 W_{t+1} 的概率值,最后将所有概率相乘。
    P(句子) = P(W1) * P(W2|W1) * P(W3|W1,W2) * P(W4|W1,W2,W3)
  2. 生成新序列:从 <SOS> 开始,将模型预测的概率分布作为下一个词的抽样依据,抽出的词作为下一步的输入,重复此过程直到抽到 <EOS>(句子结束符)。

序列到序列模型:编码器-解码器架构

上一节我们学习了如何用RNN建模单一语言序列。本节中,我们将其扩展,解决更通用的序列到序列转换问题,例如机器翻译、对话生成。

在这种任务中,输入和输出序列之间没有直接的、逐词的对应关系(例如“I ate an apple” -> “Ich aß einen Apfel”)。我们需要一个能先理解整个输入,再生成整个输出的模型。

这就是编码器-解码器(Encoder-Decoder)架构,也称为Seq2Seq模型。

以下是该模型的工作流程:

  1. 编码器(Encoder):一个RNN(如LSTM)逐词读取输入序列,直到遇到 <EOS>。最终时刻的隐藏状态(图中红框)旨在编码整个输入句子的语义信息
  2. 解码器(Decoder):另一个RNN负责生成输出序列。它从编码器的最终隐藏状态开始初始化,并以 <SOS> 作为第一个输入。
  3. 解码步骤:在每一步,解码器基于其当前隐藏状态和上一步生成的单词,计算下一个词的概率分布。从这个分布中采样(或选择)一个词,并将其作为下一步的输入,循环直至生成 <EOS>

关键点:解码器本质上是一个条件语言模型。它与普通语言模型的区别在于,它的概率预测以编码器提供的输入序列表示(红框向量)为条件。
P(Y1, Y2, ... | X) = Π P(Y_t | X, Y1, ..., Y_{t-1})


解码策略:如何选择生成的词

在生成输出序列的每一步,我们都有一个概率分布。如何选择下一个词呢?有以下几种策略:

以下是几种常见的解码策略:

  • 贪婪解码(Greedy Decoding):每一步都选择概率最高的词。这种方法简单高效,但可能导致“局部最优”而非“全局最优”的序列,因为早期的高概率选择可能将后续引向低概率区域。
  • 随机采样(Random Sampling):根据模型输出的概率分布随机抽取下一个词。这能增加生成的多样性,可能产生更流畅、更有创意的文本,但不保证输出是最可能的序列。
  • 束搜索(Beam Search):一种折中的方法。它维护一个大小为 k(束宽)的候选序列列表。在每一步,它扩展所有候选序列,但只保留总体概率(从序列开始到当前步的概率乘积)最高的 k 个。当有候选序列生成 <EOS> 时,它可能被输出。束搜索比贪婪解码更可能找到高概率序列,但计算量更大。

模型训练与总结

训练方法:Seq2Seq模型的训练采用一种“教师强制(Teacher Forcing)”策略。即在训练解码器时,我们不使用它上一步预测的词作为输入,而是直接使用真实目标序列中对应的上一个词作为输入。这确保了训练过程的稳定性,即使模型早期预测不准,也能接收到正确的学习信号。

损失计算:在每个解码时间步,我们将模型输出的概率分布与真实的下一个词(独热编码)计算交叉熵损失,然后将所有时间步的损失求和或平均,通过反向传播同时训练编码器和解码器。

本节课总结
在本节课中,我们一起学习了:

  1. 统计语言建模的本质是对符号序列的概率分布进行建模,通常通过“下一个词预测”任务来实现。
  2. 词嵌入是将离散符号转化为稠密、富含语义的向量表示的关键技术。
  3. RNN是建模序列数据的强大工具,既能计算序列概率,也能生成新序列。
  4. 编码器-解码器架构是解决序列到序列转换问题的核心框架,其中编码器压缩输入信息,解码器作为条件语言模型生成输出。
  5. 生成输出时,可以采用贪婪解码、随机采样或束搜索等不同策略,各有利弊。

然而,基础的Seq2Seq模型有一个显著缺陷:编码器需要将整个输入序列的信息压缩到一个固定维度的向量(红框)中,这对于长序列来说是一个信息瓶颈。在下一节课中,我们将学习注意力机制(Attention Mechanism),它通过允许解码器在生成每个词时动态地回顾编码器的全部隐藏状态,从而优雅地解决了这个问题。

20:序列到序列模型:注意力模型 🧠

📖 概述

在本节课中,我们将学习序列到序列模型,特别是注意力模型。我们将探讨如何将输入序列转换为输出序列,即使输入和输出之间没有直接的对应关系或顺序。我们将从基础的编码器-解码器架构开始,逐步引入注意力机制,并最终讨论自注意力与Transformer模型。


🔄 从基础序列到序列模型开始

上一节我们介绍了序列到序列模型的基本概念。这种模型旨在将一个输入序列转换为另一个输出序列,而输入和输出之间不一定存在一一对应或顺序关系。例如,在英语到德语的翻译中,单词“I”可能对应“Ich”,而单词“ate”可能对应两个不同的德语单词。因此,我们需要一种更灵活的模型来处理这种复杂的映射关系。

我们之前看到的模型是延迟序列到序列模型。整个输入首先由网络的前半部分(编码器)处理,计算出一个最终的隐藏表示,该表示捕获了输入的本质。然后,这个表示由网络的后半部分(解码器)转换以生成输出。

在朴素实现中,我们遇到了一个问题:在解码过程中,通过采样生成的任何单词都不会影响后续的输出。为了解决这个问题,我们将生成的单词反馈回解码器,从而形成了最终的模型架构。

基础翻译模型架构

以下是我们的简单翻译模型:

  • 输入序列:输入到一个循环结构中。
  • 编码器:循环结构由一个显式的序列结束标记终止,此时我们得到一个捕获所有输入信息的隐藏表示。
  • 解码器:第二个RNN使用编码器在最终时间步的隐藏激活,以自回归的方式生成输出。它使用该隐藏状态生成第一个单词,然后将该单词反馈回去生成第二个单词,依此类推,直到最终生成序列结束标记。

我们确定了模型的两个不同部分:处理输入的部分称为编码器,它把输入编码成一些隐藏表示;生成输出的部分称为解码器,它将这些隐藏表示解码为输出。

这个模型可以训练,并且效果不错。然而,它存在一个问题。


⚠️ 基础模型的问题与改进思路

基础模型的问题是,所有关于输入的信息都存储在解码器初始接收的这一个隐藏向量中。这个隐藏向量随后会沿着解码器的循环结构传递下去。

这里存在两个问题:

  1. 编码器侧的信息稀释:随着输入序列变长,隐藏表示(尤其是简单RNN)会存在“近期偏向”,即更倾向于表示最近的输入,而较早的输入信息可能会被稀释甚至遗忘。
  2. 解码器侧的信息稀释:当这个初始隐藏表示沿着解码器循环传递时,每一步解码器都会对其进行处理并融入新生成的单词信息。因此,当解码到序列后部时,初始隐藏向量中的信息可能已被大量遗忘,解码器主要是在响应之前已生成的单词。

让我们逐一解决这些问题。首先考虑第二个问题:初始隐藏向量在解码过程中被稀释。

解决解码器侧的信息稀释

一个简单的解决方案是:在解码器的每一步,都直接将编码器的最终隐藏表示作为输入的一部分,而不仅仅是第一步。这样,解码器在生成每一个单词时,都能直接访问到编码器的总结信息,从而避免了信息在解码循环中被逐步稀释。

然而,我们仍然面临第一个问题:编码器将所有输入信息压缩到一个静态向量中,远程的、早期的输入信息可能完全丢失。


🎯 引入注意力机制

实际上,编码器在每个时间步都会产生一个隐藏表示。例如,处理单词“I”时产生一个表示,处理“ate”时在“I”的上下文中产生一个表示,依此类推。每个隐藏框都承载着信息。

此外,观察输出可以发现,输出中的不同单词与输入中的不同单词相关。例如,生成“apple”主要依赖于输入中的单词“apple”,而对其他输入的依赖很小。如果我们将所有信息塞进一个最终的隐藏向量,这种对应关系就丢失了。

加权上下文向量

我们如何解决这个问题呢?我们不再仅仅依赖最终的隐藏向量,而是考虑所有隐藏表示的某种加权组合或平均值。这意味着每个隐藏表示都不会被稀释,它们都被平等地呈现给解码器。

但这仍然缺失了一个关键点:单词“apple”与输入中的“apple”更相关,而不是与单词“an”相关。如果只是简单地将所有隐藏表示平均在一起,这种对应关系就丢失了。

因此,我们真正需要的是更高级的东西:为每个输出单词计算一个不同的加权和

这个在每一步输入到解码器的加权和,我们现在称之为上下文向量。上下文向量是所有编码器隐藏表示的加权和。

这里的核心思想是:用于组合这些隐藏表示的权重集,对于每个输出单词都是不同的。例如,当生成第一个单词“Ich”时,使用权重集 W0 进行组合;当生成“habe”时,使用不同的权重集 W1;生成“einen”时,又使用另一组权重。

如果权重能够以某种方式被计算出来,使得模型学会关注输入中正确的部分,那么这将非常有效。例如,在生成“Apfel”时,我们希望模型关注输入中的“apple”,这意味着“apple”对应的权重应该很高,而其他权重应该很低。生成“gegessen”时,则应关注“ate”。

这意味着权重必须是动态计算的,因为每个输出都需要输入的不同加权组合。因此,权重必须是解码器状态的函数。如果模型训练得当,我们期望它能自动突出输入的相关部分。


⚖️ 如何计算注意力权重?

我们已经确定权重必须动态计算。那么,计算权重时有哪些变量可用呢?在生成某个输出单词时(例如生成“einen”之后),解码器可用的信息是它最新的隐藏状态 S_t。因此,权重必须是 S_t 的函数。

同时,权重也必须依赖于编码器的各个隐藏表示 H_i,因为权重 H_i(例如对应“apple”的 H_3)必须同时依赖于解码器状态 S_t 和 H_i 本身。

因此,在输出时间步 t,对于输入 i 的权重,是 S_{t-1} 和 H_i 的函数。

权重的约束条件

权重可以是任意的吗?不能。考虑所有编码器隐藏状态 H_i 存在于某个向量空间区域。如果我们计算它们的加权和作为上下文向量 C,并且希望 C 能合理地代表这些 H_i,那么理想情况下,C 应该位于这些 H_i 所张成的凸包内部。如果某个权重是100,而其他权重是0,C 可能会远远超出这个区域。

如何确保上下文向量保持在这个区域内呢?让权重为正且和为1。这样,加权和(即上下文向量)就保证位于这些 H_i 的凸包内部。因此,权重实际上构成了一个概率分布。

从原始分数到归一化权重

我们如何得到一组和为1的正权重呢?我们可以通过一个两步骤的过程:

  1. 首先,使用一个任意选择的函数 a,结合解码器状态 S 和编码器隐藏状态 H,为每个输入计算一个原始分数(或称为“能量值”)。
  2. 然后,将这些原始分数通过一个 Softmax 函数,转换成一个概率分布(即所有权重为正且和为1)。

这个函数 a 可以是简单的内积(如果 S 和 H 维度相同),或者通过一个可学习的矩阵进行变换以使维度匹配(如果 S 和 H 维度不同)。更复杂的函数如小型神经网络也可以,但实践发现简单的矩阵变换效果很好。


👀 注意力框架与工作流程

为什么称之为“注意力”框架?因为这些权重通过在某些词上赋予高权重、在其他词上赋予低权重,来决定模型在生成当前输出时应该“关注”输入的哪些部分。因此,权重被称为注意力权重

在注意力框架下,我们在每个输出时间步计算一个不同的上下文向量。上下文不是简单地选择权重最高的输入向量,而是所有输入向量的加权组合。对任何输入词的注意力权重,都是该词的编码器隐藏表示与最新解码器状态的函数。

带注意力的解码过程

让我们以一个具体例子(“I ate an apple”)来看带注意力的解码过程:

  1. 编码器:处理整个输入序列,直到序列结束标记,为每个输入词(包括结束标记)计算一个隐藏状态 H_i。
  2. 解码器初始化:解码器有自己的初始隐藏状态 S_0(例如可以设为0或通过其他可学习机制设置)。S_0 很重要,因为它将用于计算第一个输出词的注意力权重。
  3. 第一步解码
    • 使用 S_0 和所有编码器隐藏状态 H_i 计算第一组注意力权重(W0)。
    • 用 W0 对 H_i 加权求和,得到第一个上下文向量 C_0。
    • 解码器的初始输入通常是一个“序列开始”标记。
    • 将 C_0 和“序列开始”标记一起输入解码器,解码器计算第一个输出词的概率分布(例如“Ich”),并从中采样得到第一个词 y_0。
    • 解码器更新其隐藏状态到 S_1。
  4. 后续解码步骤
    • 在时间步 t,使用最新的解码器状态 S_{t-1} 和所有 H_i 计算新的注意力权重。
    • 用新权重计算新的上下文向量 C_t。
    • 将 C_t 和上一步生成的词 y_{t-1} 一起输入解码器。
    • 解码器输出当前词的概率分布,采样得到 y_t,并更新隐藏状态到 S_t。
  5. 终止:重复此过程,直到解码器生成“序列结束”标记。

🔑 查询、键与值(Query, Key, Value)

在更精细的注意力实现中,我们通常不会直接使用原始的隐藏状态 H。考虑生成“einen”时,我们需要判断“an apple”中的“apple”是食物。这涉及两个层面的信息:一是类别信息(这是食物),二是具体信息(这是苹果)。

为了做这种区分,我们喜欢将 H 投影到两个不同的子空间:

  • :一个更粗略的投影,用于获取类似类别的信息(例如“这是食物”)。键用于计算注意力权重。
  • :一个更详细的变换,用于获取具体信息(例如“这是苹果”)。值用于在计算上下文向量时进行加权求和。

因此,我们使用加权和的值,而不是原始的 H。同时,在解码器侧,我们通常也将解码器状态 S 投影到一个与键维度相同的向量,称为查询

在特殊情况下,如果查询、键、值和 H 都相同,那就是我们之前用于说明的简单形式。但在实际实现中,通常会从隐藏表示中派生出独立的键和值。

计算过程

  1. 从编码器隐藏状态 H_i 通过可学习矩阵 W^K 和 W^V 分别计算键 K_i 和值 V_i。
  2. 从解码器状态 S_{t-1} 通过可学习矩阵 W^Q 计算查询 Q_t。
  3. 使用查询 Q_t 和所有键 K_i 计算相似度分数(例如点积),然后通过 Softmax 得到注意力权重。
  4. 使用注意力权重对所有的值 V_i 进行加权求和,得到上下文向量 C_t。

🎯 训练:教师强制(Teacher Forcing)

这个模型的训练目标是什么?对于机器翻译,我们希望找到给定英语句子下最可能的德语句子。输出序列的概率是解码器分配给序列中每个词的概率的乘积。

然而,直接找到最可能的序列需要在所有可能的序列上进行评估,这是不可行的。因此,我们使用束搜索作为启发式方法,在每一步只保留概率最高的 K 个候选序列。

训练挑战与教师强制

在标准神经网络训练中,我们进行前向传播,将输出与目标比较,计算损失并反向传播。但对于序列到序列模型,在训练初期,模型性能很差,无论输入什么,都可能生成乱码。如何将这种乱码输出与目标序列“Ich habe einen Apfel gegessen”进行比较呢?没有明显的对应关系。

为了解决这个问题,我们在训练时引导解码器。具体方法是:在解码器的每一步,我们不是将解码器自己上一步生成的词(可能是错误的)作为输入,而是将真实的目标词作为输入。这被称为教师强制

这就像学骑自行车时,有人扶着车帮你保持平衡。没有这种引导,模型在初期很难学会生成有意义的序列。

教师强制比率与课程学习

但是,如果训练时一直使用真实目标词(即一直扶着自行车),模型在推理时(需要自己生成输入)可能会表现不佳,因为它从未练习过在之前可能出错的条件下继续生成。

因此,我们需要引入课程学习的思想。随着模型训练得越来越好,我们逐渐降低使用真实目标词(教师强制)的频率,而是以一定概率使用模型自己上一步生成的词作为输入。这个概率就是教师强制比率。在训练初期,该比率较高(更多引导);随着训练进行,该比率逐渐降低(更多让模型自己尝试)。

当使用模型自己生成的词时,采样过程是不可微分的。为了在训练中也能反向传播,我们可以使用Gumbel-Softmax重参数化技巧,它提供了一种可微分的近似采样方法。


👁️ 注意力权重的可视化

我们如何知道注意力机制是否有效?一个很好的方法是可视化注意力权重。例如,在机器翻译任务中,我们可以绘制一个对齐图,横轴是输入词,纵轴是输出词,每个单元格的亮度表示生成某个输出词时对某个输入词的注意力权重。

在早期的注意力论文中,可以清晰地看到,当翻译英语“European Economic Area”为法语“zone économique européenne”时,注意力权重的路径会反转,以匹配法语中形容词后置的语序。这种可视化是调试模型和理解其行为的强大工具。

注意力模型也被成功应用于图像描述生成等任务,其中编码器是卷积神经网络,解码器是RNN。可视化显示,当生成“girl”时,模型关注图像中女孩的区域;生成“trees”时,则关注背景中的树木区域。


🧠 从注意力到自注意力与Transformer

我们有了基于注意力的编码器-解码器模型。但这里有一个问题:如果解码器可以通过注意力单独关注编码器的每一个隐藏状态,那么我们是否还需要编码器中的循环结构?循环的目的是将信息向前推送并累积。但如果编码器要对每个输入步骤单独关注,我们还需要这种推送吗?

不一定需要。但编码器中的循环有一个重要作用:为每个单词提供上下文信息。例如,单词“apple”的含义(是水果还是公司?)需要根据其相邻单词(如“ate”)来判断。如果不使用循环,我们如何让单词的表示包含其上下文信息呢?

答案就是使用注意力机制本身。我们可以让输入序列中的每个词,通过注意力机制,去关注序列中的所有其他词(包括自己),从而更新自己的表示。这被称为自注意力

自注意力块的工作方式

  1. 对于输入序列中的每个词,首先通过一个嵌入层得到初始表示。
  2. 从每个词的表示中,计算出查询、键和值(通过不同的可学习投影矩阵)。
  3. 为了更新词“I”的表示,我们使用“I”的查询与所有词的键(包括“I”自己的键)计算注意力权重。
  4. 然后,用这些注意力权重对所有词的值进行加权求和,得到“I”更新后的表示。
  5. 对序列中的每个词并行地重复此过程。

这个过程允许每个词直接访问序列中任何位置的词的信息,不受距离限制,从而更好地捕获长距离依赖。

多头注意力

我们可以将上述过程重复多次,每次使用不同的、独立的查询、键、值投影矩阵。每一次称为一个注意力头。每个头可能会学习关注不同方面的信息(例如语法、语义、指代等)。然后将所有头输出的更新表示拼接起来,再通过一个前馈神经网络(通常是带有一个隐藏层的MLP)进行混合和变换。这就是多头注意力

由自注意力层和前馈层组成的模块,可以堆叠多次,构成一个强大的序列处理器。


📍 位置编码(Positional Encoding)

自注意力有一个明显的缺陷:它本身是排列等变的。也就是说,打乱输入序列的顺序,输出序列的表示也会以同样的方式被打乱,但模型无法感知原始的单词顺序。然而在语言中,顺序至关重要。

如何将位置信息注入到模型中呢?我们需要一种方法,使得在计算两个词的注意力时,能考虑到它们之间的距离。我们希望距离较远的词,获得的注意力权重通常应该更低(尽管不是强制为零)。

一种巧妙的方法是位置编码。我们将一个依赖于位置的向量 P_t 添加到每个词的初始嵌入向量中。这个位置向量需要设计成具有以下性质:两个位置编码向量的内积 P_i · P_j 仅依赖于它们的位置差 |i - j|。

正弦位置编码

一个经典的解决方案是使用正弦和余弦函数来构造位置编码。对于位置 pos 和维度 i

  • 偶数维:PE(pos, 2i) = sin(pos / 10000^(2i/d_model))
  • 奇数维:PE(pos, 2i+1) = cos(pos / 10000^(2i/d_model))

这种编码方式使得模型能够轻松地学习到相对位置关系。通过将位置编码与词嵌入相加,自注意力机制在计算相似度时,就会自然地包含位置信息,从而让模型知道“an”紧挨着“apple”与远离“apple”是不同的。


🎭 编码器与解码器中的自注意力

现在,我们可以用一系列的多头自注意力块和前馈层来构建编码器,完全取代循环网络。

对于解码器,我们也可以做类似的事情,但有一个关键区别:在生成某个输出词时,解码器不能“看到”未来的词(即尚未生成的词)。因此,解码器中使用的是掩码多头自注意力。具体做法是,在计算注意力权重时,将当前时间步之后的所有位置的权重设置为负无穷(在Softmax前),这样经过Softmax后,这些位置的注意力权重就变成了0。这确保了自回归属性。

解码器通常由掩码自注意力层、编码器-解码器注意力层(即标准的注意力,查询来自解码器,键和值来自编码器)以及前馈层组成。


🤖 Transformer:注意力即一切

将基于自注意力的编码器和解码器堆叠起来,就构成了Transformer模型(“Attention Is All You Need”论文)。它完全摒弃了循环,实现了高度的并行化,训练速度极大提升。

Transformer催生了现代大语言模型的两大主流架构:

  • 仅解码器模型:如GPT系列。它们只使用Transformer的解码器部分(带掩码的自注意力),通过自回归方式生成文本,在大量文本上预训练后,在众多任务上表现出色。

21:序列到序列模型与注意力机制 🧠

在本节课中,我们将要学习序列到序列模型的核心架构,特别是如何通过注意力机制来解决传统编码器-解码器模型在处理长序列时信息丢失的问题。我们将从基础的延迟序列到序列模型出发,逐步引入注意力框架,并最终理解其如何演变为强大的Transformer模型。

概述

上一节我们介绍了基础的序列到序列模型,它通过一个编码器处理输入,一个解码器生成输出。本节中我们来看看这种基础模型在处理长序列时面临的核心挑战,以及注意力机制如何巧妙地解决这些问题。

基础序列到序列模型及其问题

我们之前看到的模型是一种延迟序列到序列模型。整个输入序列首先被网络的前半部分(编码器)处理,计算出一个最终的隐藏表示,用以捕捉输入的本质。随后,网络的第二部分(解码器)将这个表示转换为输出序列。

这个简单的循环神经网络实现有一个问题:在解码过程中,通过采样生成的任何一个词都不会直接影响后续的输出。因此,我们实际上让生成的词反馈回去,从而得到了我们最终的模型。

以下是我们的简单翻译模型结构:

  • 输入序列送入一个循环结构。
  • 循环结构以一个显式的序列结束标记终止,此时你得到一个捕捉了所有输入信息的隐藏表示。
  • 然后,第二个RNN使用这个最终时刻的隐藏激活,以自回归的方式生成输出。用它生成第一个词,该词接着用于生成第二个词,依此类推,直到最终生成序列结束标记。

我们还将模型划分为两个不同的组件:处理输入的部分称为编码器,它将输入编码为一些隐藏表示;生成输出的部分称为解码器,它将编码器计算的隐藏表示解构为输出。

这个模型存在一个问题。所有关于输入的信息都存储在这个单一的隐藏向量(红框内)中。然后,这个隐藏向量被传递给解码器循环网络,并一直传递到最后。这里有两个问题:

  1. 编码器侧:随着输入越来越长,隐藏表示(如我们所知)总是存在“近期偏向性”。它们会更倾向于表示最近的输入,而淡化甚至遗忘遥远过去的信息。这意味着,当关于“I”的信息到达这里时,该信息可能已经被稀释甚至遗忘了。如果输入很长,这将是一个问题。
  2. 解码器侧:这个隐藏表示在解码器侧的循环中传递时,每次传递解码器都会对其进行一些处理,因此信息被转换并有所减弱。因为解码器不断从这些生成的词中收集额外的输入,所以当你到达输出序列末尾时,红框中的内容在很大程度上被遗忘了,你实际上只是在响应解码器侧之前生成的内容。

注意力机制的引入

让我们逐一解决这些问题。首先考虑第二个问题:隐藏表示在网络传递过程中被稀释。如何解决?

一个简单的解决方案是:在解码器的每一步,不仅将上一步生成的词作为输入,还将编码器的最终隐藏表示(红框)也作为输入。这意味着,解码器侧的“近期偏向性”被消除了。这是我们想要做的。

但你仍然有另一个问题:在输入侧,信息在不断传入时被稀释。你将所有关于输入的信息压缩到一个单一的向量中。这个向量以某种静态方式打包了一切,而关于遥远过去(即开头)的信息可能完全丢失了。这个向量信息过载。

实际上,编码器每一步的隐藏表示都承载着信息。例如,第一个隐藏框承载关于单词“I”的信息,第二个在“I”的上下文中承载关于单词“ate”的信息,第三个在“I ate”的上下文中承载关于单词“an”的信息,等等。每一个框都代表着信息。

此外,如果你观察输出,会发现输出中的不同词与输入中的不同词相关。例如,生成“Apfel”很大程度上依赖于输入中的“apple”,而对输入的其他部分依赖很小。然而,如果我将所有信息塞进那个单一的红框,这个对应关系就丢失了。

如何解决这个问题?首先考虑输入侧信息不断被稀释的事实。

我可以这样做:我不只读取最终的红框,而是考虑所有这些隐藏表示的某种加权和或平均值。这意味着它们都没有被削弱,每一个都平等地呈现给解码器。

但这仍然遗漏了一个关键点:当生成“Apfel”时,它更多地与“apple”相关,而不是与单词“an”相关。仅仅将它们全部平均在一起,这种对应关系就丢失了。

因此,我们真正需要的是稍微更高级的东西。像这样:我们为每一个输出词,计算这些隐藏表示的不同加权和。

在这里,我们在每个时间步输入的内容现在被称为上下文向量。上下文是所有输入隐藏表示的加权和。这里的核心思想是,对于每个输出词,组合这些隐藏表示的权重集合是不同的。

例如,当生成第一个词“Ich”时,它们以某种权重W0组合。当生成“habe”时,它们使用另一组权重W1组合。对于“einen”,又有一组不同的权重。因此,对于每一个输出,你都会查看隐藏表示的不同加权和来计算上下文,然后用它来生成输出。

如果权重能够以某种方式被神奇地计算或推导出来,使得模型学会关注正确的事物,那么这将起作用。例如,在生成“Apfel”时,你希望关注输入侧的哪个部分?是“apple”。这对权重意味着什么?对应于“apple”的权重必须高,而其他权重必须低。另一方面,如果生成“gegessen”,我必须关注什么?是“ate”。这意味着你希望“ate”的权重高,其他权重低。

因此,权重必须是动态计算的。因为在每个输出时刻,你都需要输入的不同加权组合。所以,权重必须作为解码器状态的函数动态计算。如果模型训练良好,我们期望这将自动突出输入的相关部分。

注意力权重的计算

那么,这些权重是如何计算的呢?首先,我们已经确定权重必须是动态计算的。

让我们看看在计算权重时,我们有哪些可用的变量。当我们计算权重时,依赖于训练这个神奇的过程来找出如何使用我们提出的程序正确计算权重。

在生成“einen”之后的词时,解码器可用的信息是什么?解码器唯一可用的信息是最新的隐藏状态S2。因此,这些权重必须是S2的函数。同时,权重也必须是这些编码器隐藏状态H的函数。例如,计算对应于“apple”的权重时,它必须依赖于S2,也必须依赖于H3,因为H3是从编码器侧的“apple”派生出来的。

这意味着,在每个输出时刻T,对应于输入的权重是S_{T-1}和所有H的函数。

但还有一个要求:权重可以是任意的吗?不能。因为权重代表一个分布,它们必须都是正数,并且和为1。如果你使权重和为1,那么你就能保证加权和位于所有H的外壳(凸包)内。这样,它们对于相关部分会很高,对于其他部分则很低。

如何使一堆权重既是正数又和为1?我可以使用softmax函数。因此,我将通过一个两步过程计算权重:首先,使用我选择的任意函数(该函数结合了解码器侧的S和编码器侧的H)计算一些原始权重。然后,我将它们通过一个softmax层,将它们转换为一个概率分布。

注意力框架

为什么我称之为注意力框架?这些权重在做什么?一旦这些权重成为一个分布,基本上,对于任何词,权重高意味着“关注”那个词,权重低意味着“较少关注”那些词。因此,你可以将权重看作是一种机制,用于确定应该关注输入的哪些部分。这就是注意力形式主义,权重被称为注意力权重

在注意力框架中,我们在每个输出时间步计算一个不同的上下文向量。上下文不是选择具有最高权重的输入向量,而是选择所有输入的加权组合。对任何输入词的注意力权重是该词的编码器隐藏表示和最新解码器状态的函数。

注意力函数E是什么?有各种可能性。H是隐藏状态向量,S是另一个隐藏状态向量。如果两个向量长度相同,那么我可以直接计算两者之间的内积,得到一个标量。如果我有两个长度不同的向量,我可以计算矩阵内积,在两者之间插入一个矩阵以确保尺寸匹配。

如果S是一个m×1向量,H是一个n×1向量,我能直接计算它们的内积吗?不能,因为它们尺寸不同。如何将H转换为与S相同的大小?我可以将H乘以一个矩阵。如果H是n×1向量,W是m×n矩阵,那么S^T(1×m)乘以W(m×n)再乘以H(n×1),整个结果将变成一个标量。中间的矩阵是一个可学习的参数,它使两边尺寸匹配。

你可以有更复杂的函数,比如通过一个MLP。但我们发现简单的矩阵内积效果最好。所以,我们通常使用矩阵内积。

带注意力的解码过程

让我们以一个典型过程为例,假设使用矩阵内积作为注意力函数。

  1. 输入“I ate an apple”通过编码器,编码器计算所有这些隐藏状态,直到序列结束标记被处理。
  2. 在解码器侧,解码器现在独立于编码器,因此解码器的隐藏状态必须有自己的初始值。最简单的设置是将其设为零。
  3. 这个初始状态S_{-1}很重要,因为它实际上用于计算解码器侧第一个词的所有输入的权重。
  4. 一旦有了S_{-1},你就可以计算所有输入词对应的权重W0。
  5. 然后,你计算第一个上下文向量,作为所有输入词隐藏表示的加权和。
  6. 解码器侧的初始输入将是一个开始序列标记,表示我们开始生成。
  7. 以这两个(上下文和开始标记)作为输入,解码器现在计算词的概率分布。
  8. 然后,你可以从这个概率分布中抽取下一个词,比如“Ich”。
  9. 现在,这个词被反馈回去用于下一个时间实例。但然后我需要再次计算输入的注意力权重。为了计算注意力权重,我现在将使用S0。
  10. 使用S0,我得到所有输入的注意力权重,计算一个新的上下文向量作为输入隐藏表示的加权和。
  11. 新的上下文向量输入解码器。在下一个时间步,我再次计算一个概率分布,从中可以采样一个词。那个词被反馈回去。
  12. 现在,S1是我可用的最新解码器状态。因此,S1将用于计算输入的注意力权重。
  13. 然后,使用S1为输入计算的注意力权重将用于计算上下文向量C2,依此类推。
  14. 你不断重复这个过程,直到最终生成一个序列结束标记。

键值对注意力

我们可以对此做一些小的修改。考虑一下,当我在“Ich habe einen”之后,想要决定下一个词必须是什么时,我必须计算注意力权重。当我在“einen”时,“我吃了一个苹果”这个事实与“它是一种可以吃的食物”这个事实一样重要吗?什么真正决定了注意力权重?我应该关注一般的食物,还是应该具体关注苹果?

为了弄清楚我要关注什么,我需要“类别”信息,知道它是一种食物。但一旦我弄清楚应该给予它多少关注,仅知道“这是一种食物”是否足够?还是我应该考虑它实际上是“一个苹果”这个事实?这是两个不同层次的信息。

为了区分这一点,我们喜欢将H投影到两个不同的子空间。一个更粗略的投影将其转换为“食物”级别,这就是我们所说的。一个更详细的转换说明“这是一个苹果”,这就是我们所说的。因此,我们不是直接使用原始的H,而是喜欢将其投影到更简单的东西上。

我们使用一个投影矩阵从H派生出称为“键”的东西。但一旦你计算出注意力权重,为了知道你要翻译的到底是什么,你需要知道它具体携带什么信息,所以你从H计算出称为“值”的东西。

因此,键实际上是用来计算注意力权重的,但一旦你计算出注意力权重,你要平均的项是值。这是有道理的。我们使用值的加权和。在查询侧也有一个相应的矩阵(通常不需要两个独立的矩阵),以确保查询降到与键相同的大小。

在K、V和H都相同的特殊情况下,这就是我目前用来举例的简单例子。那时我们不进行特定的投影。但请记住,当你实际实现这个机制时,你通常从这些隐藏表示中派生出独立的键和值。

训练与教师强制

整个过程的实际目标是什么?如果我像以前一样执行机器翻译,我想为这个特定的英语句子推导出最可能的德语句子。现在,输出上任何特定句子的概率是多少?这简单地是解码器分配给输出中每个词的概率的乘积。

像上一节课一样,天真地计算这个概率是具有挑战性的。由于自回归,每个词都被反馈回去,你必须整体考虑每个句子,计算其概率。这意味着找到最可能的句子需要你评估宇宙中每个句子的这个概率,并选择概率最高的那个。这是不可行的。我们如何解决?我们使用束搜索

那么,我们如何训练网络?在神经网络的标准训练中,你执行推理,它生成一个输出。然后你将输出与目标输出进行比较,计算差异,并将其反馈回去。

如果你从这个角度看待这个网络,那么这个网络确实具有这样的结构。如果你使用传统的神经网络训练机制,对于每个训练实例,你只需传入输入,它会生成一些输出。然后你可以将输出与你真正想要的目标进行比较,计算差异并将其反馈回去。但这不是我们在这里所做的。我们将把它像语言模型一样对待。

你希望输入传入后,它生成一系列概率分布。你想计算与目标分布的差异,反向传播,并用它来更新解码器和编码器,以及注意力框架的任何参数(如果它有参数的话)。

挑战在于:如果我使用这个简单的机制,我传入一些输入,生成一些输出,然后尝试将输出与目标输出进行比较,在训练的初始阶段,模型将非常糟糕,无论你输入什么,它都会生成垃圾。如何将其与“Ich habe einen Apfel gegessen”进行比较?没有对应关系,没有一对一的对应关系,甚至没有明显的匹配机制来计算差异。

因此,我们将在训练期间引导解码器。我们以实际允许我们计算网络实际输出与期望输出之间一对一对应关系的方式来引导解码器,方法是在解码器侧输入实际的输出。

这意味着在解码器侧,这些输入的词不是从输出中抽取并反馈回去的,而是给出实际的输出。这意味着在这一点上,解码器实际上已经看到了“Ich habe einen”,并且知道下一个词必须是“Apfel”。然后,它可以将输出概率分布与单词“Apfel”进行比较,然后计算差异,这可以用来纠正网络。

所以,你通过提供这个额外的引导(即实际输出本身)来帮助网络。在某种意义上,这是“作弊”,因为你提供了很多在推理期间无法获得的引导。但没有它,我们实际上无法训练模型。这就是我们所说的教师强制

从传统训练的角度来看,它在前向传递过程中作弊,因为真实标签与输入一起被输入。我们称之为教师强制,就像在教室里老师引导你完成过程一样。

我们也可以将其视为学习骑自行车。你第一次骑自行车时,是直接跳上去开始蹬吗?如果你那样做,会发生什么?你会摔倒。所以你做了什么?用了训练轮,或者让你的朋友或父亲扶着自行车帮你蹬。但如果你父亲总是扶着自行车让你蹬,你还能学会骑自行车吗?所以他每隔一段时间就会放手,当你开始摔倒时,他会再次抓住自行车。

我们想做一些类似的事情。这种引导就像你的父亲或朋友在你蹬车时扶着自行车。但每隔一段时间,你希望他们放手。那么他们什么时候开始放手?当他们确信你可以保持平衡一小会儿时。所以,相应的等价做法是,每隔一段时间,我们不给解码器实际的输出作为输入,而是从输出分布中抽取一个样本并输入那个。那可能是错误的,但尽管如此,这就像定期放开自行车一样。最终,模型实际上也会学会这一点。

那么,你多久会这样做一次?在早期阶段,模型状态很差,你一直引导它。随着它变得更好,你开始每隔一段时间放手,并进行纠正。你实际放手的频率,或者说你引导系统的频率,就是你的教师强制比率——你提供实际目标与抽取样本的频率之比。

这里有一个问题:当你从分布中抽取样本并反馈回去时,这个过程是不可微的。为了使其可微,你需要一些额外的技巧。这就是著名的Gumbel噪声技巧。事实证明,从分布中抽取是不可微的,但如果你使用带有对数概率作为参数的Gumbel分布,然后只选取峰值,这在统计意义上与从原始分布中抽样相同。一旦你将其转换为argmax,argmax可以转换为softmax,而softmax是可微的。这是当你想要在训练中引入抽样时经常使用的标准重参数化技巧。

注意力机制的可视化与扩展

我们如何知道这整个方案是否有效?如果我们检查注意力权重,我们可以判断。例如,我在进行机器翻译。如果我看注意力权重,我可以观察它是否确实关注了输入的正确区域。事实上,当你编码这个时,如果遇到问题,你总是想可视化你的注意力权重,以确保它们看起来没问题。如果它们看起来不对,那么无论模型生成什么,都说明你的模型有问题。

让我们看看这个东西是否真的学会了。以下是一些来自早期论文的例子(例如Bahdanau等人的论文),用于机器翻译。当生成法语句子的第一个词“L'”时,你最想关注什么?它最关注的是“agreement”。确实,它关注的是“agreement”。然后“accord”对应于“on”,它关注的是“on”。然后“sur”对应于“the”,它关注的是“the”。现在有一些有趣的事情:英语和法语之间的词序会发生变化。在英语中,你会说“European Economic Area”,而在法语中,你改变词序说“zone économique européenne”。所以,如果你看它在生成单词“zone”时关注什么,它关注的是“area”,但那不在对角线上。因此,在“European economic area”这一小部分,注意力模式反转了方向,沿着负对角线,正如它应该的那样。然后随后,它继续沿着对角线前进。这非常漂亮。

那么,我们如何训练网络?我们已经看到了训练网络如何执行推理,但训练是略有不同的事情。在神经网络的标准训练中,你执行推理,它生成一个输出。然后你将输出与目标输出进行比较,计算差异,并将其反馈回去。

自注意力与Transformer

如果我的解码器要单独关注这些编码器隐藏状态中的每一个,那么循环的意义是什么?我真的需要这个循环吗?循环的目的是不断向前推送信息,使其在传递过程中不断累积。但编码器将单独关注每个输入步骤。我们是否需要通过循环向前推送信息?从表面上看,这似乎不是必需的,所以我可以去掉这个循环。

但这有一个问题。问题在于,虽然你显然不需要在H4中存储关于单词“ate”的信息,但这些H实际上是什么?这些H是单词的表示。单词的表示取决于它的含义。单词“apple”是水果还是公司?在这个上下文中,它是什么?线索是“ate”,所以你必须考虑单词“ate”来决定隐藏表示必须是什么。有什么机制可以让你在不使用循环的情况下查看相邻单词来计算更新的表示?我可以使用注意力本身。

因此,使用注意力框架本身在嵌入中引入上下文特异性,这样序列到序列模型的编码器可以在没有循环的情况下组成。这被称为自注意力。Transformer使用的就是这种机制。

这是如何工作的?首先,对于输入序列中的每个单词,你可以计算初始隐藏表示(例如,使用一个MLP层)。然后,从每个单词中,你计算一个查询、一个键和一个值。为了更新单词“I”的表示,我将使用“

22:Transformer 与新型架构

📖 概述

在本节课中,我们将深入探讨 Transformer 架构,这是当今强大 AI 系统(如 ChatGPT)背后的核心构建模块。我们将从原始的 Transformer 架构开始,逐一剖析其每一层的工作原理,然后讨论针对不同组件的改进。我们还将深入研究注意力机制的变体,包括更高效的版本,并探索 Transformer 如何适配音频和图像处理。最后,我们将介绍参数高效微调技术,并讨论模型的缩放定律。


🤔 为什么学习 Transformer?

Transformer 之所以重要,是因为当前几乎所有先进的 AI 系统都基于它。它具备几个关键特性:

  • 架构灵活性:Transformer 最初为自然语言处理(NLP)设计,但能轻松泛化到不同模态(如视觉、音频、机器人),只需对架构进行最小改动。
  • 可扩展性:Transformer 的性能随着数据和参数的增加而提升,这种提升是可预测的。在规模足够大时,Transformer 会展现出无需显式训练即可解决任务的“涌现能力”。
  • 高效微调:可以通过如 LoRA 等技术,高效地对预训练的 Transformer 进行下游任务微调。

🏗️ 原始 Transformer 架构解析

上图展示了2017年提出的用于机器翻译的原始 Transformer 架构。它基于多头注意力机制,并包含嵌入层、位置编码、前馈层和归一化层等核心组件。该图展示了编码器-解码器架构,但 Transformer 也可以仅使用编码器(左半部分)或仅使用解码器(右半部分),具体取决于任务。

接下来,我们将以文本模态为例,拆解 Transformer 架构的每一层。

1. 文本的数值化表示

首先需要将文本转换为 Transformer 可以处理的数值表示。前两个组件可能因模态而异,但其余组件是 Transformer 块的核心。

分词

分词是将文本拆分为更小的单元(词元)并转换为整数的过程。这些单元可以是完整单词、子词或字符。现代模型大多使用子词分词,因为词级分词可能因拼写错误、词形变化或训练数据中未出现该词而引入问题。子词分词通过允许模型表示未见过的词来处理这些情况。

词嵌入

使用分词器将词转换为整数后,我们通过嵌入层将这些离散的词元索引转换为连续的向量表示。该层就像一个查找表,每个词元都映射到一个唯一的向量表示。这些向量在训练过程中学习,以捕获词之间的关系。我们这样做是因为词元索引本身没有内在含义,词之间也没有直接关系。通过嵌入,我们创建了一个更有意义的表示,其中语义相似的词在嵌入空间中的向量表示也更接近。

我们也可以将嵌入层视为一个线性层:将索引转换为独热向量表示,然后将其乘以嵌入权重矩阵。


⚙️ 理解 Transformer 块

现在我们来讨论 Transformer 块中最有趣的部分:注意力机制及其变体。

自注意力机制

在上一讲中,你们在序列到序列模型的背景下学习了自注意力。简单来说,注意力机制允许模型确定输入序列中哪些部分与给定词元最相关。它通过计算注意力分数来实现,这些分数告诉我们应在每个词元上放置多少“焦点”。

自注意力的关键组件是查询、键和值向量:

  • 查询向量:每个词元都有自己的查询向量,代表该词元在序列中“寻找”什么。例如,该词元是动词还是名词?
  • 键向量:作为词元内容的标识符,它基本上回答了查询向量提出的问题。
  • 值向量:包含将根据计算的注意力分数进行聚合的实际内容。

注意力机制将一个词元的查询与所有其他词元的键进行比较,以衡量它们之间的相似性。这是通过查询和键的点积,然后进行 Softmax 操作得到0到1之间的分数来实现的。最终输出是值向量的加权和,其中相关性更高的词元对最终表示的贡献更大。这个输出将与输入相加,产生一个更精细的表示。例如,在注意力操作之前,“station”这个词可能只表示一个车站;而在注意力操作之后,其嵌入将携带更精确的含义,表明它是“火车站”而不是其他车站。

在自注意力中,查询、键和值都来自输入嵌入 X,但分别乘以不同的权重矩阵(W_Q, W_K, W_V),这些矩阵在训练中学习。

多头注意力

我们讨论的自注意力有时被称为单头注意力。在多头注意力中,我们将输入表示(嵌入)分割成更小的维度或子空间,每个子空间对应一个“头”。每个头学习序列的不同方面,以捕获更多样化的信息。例如,一个头可能专注于动词,另一个头可能只专注于名词。在实践中,这些学习到的特征不一定与语法类别绑定。处理完后,所有头的输出被拼接并组合,以从不同角度丰富嵌入表示。

因果掩码与交叉注意力

Transformer 中的自注意力默认处理所有词元,这意味着每个词元都可以关注序列中的所有其他词元(双向)。这对于文本编码是有益的,但对于自回归解码(如语言模型),未来的词元不应影响当前的预测。因此,在解码器中,我们需要强制实施因果性,即每个词元只能关注过去和当前的词元,而不能关注未来的词元。这是通过使用三角掩码矩阵来实现的,该掩码将未来位置设置为0,防止模型关注它们。

解码器中还有第二个多头注意力层,其查询来自解码器,而键和值取自编码器,这被称为交叉注意力。在交叉注意力中,由于编码器的完整表示已经存在,因此不需要进行掩码。


📍 位置编码

我们需要在自注意力中添加位置信息,因为注意力本身是排列不变的。这意味着,如果我们改变序列的顺序,注意力分数将保持不变,但这会导致问题,因为序列的顺序可以完全改变含义。

为了解决这个问题,我们为每个词元添加位置信息,以捕获绝对或相对距离。原始 Transformer 使用的是绝对位置编码,序列中的每个位置都被分配一个唯一的向量。这些向量使用不同频率的正弦和余弦函数组合生成,如公式所示。这种周期性特性使得即使对于长序列,也能保持唯一的位置编码。

在公式中,对于嵌入维度索引 i,如果 i 是偶数,我们使用正弦函数;如果 i 是奇数,我们使用余弦函数。其中 t 是词元在序列中的位置,d_model 是嵌入维度,分母中的 10000 是一个超参数,可以根据任务调整。

添加位置信息后,不同顺序的相同词元序列将产生不同的注意力分数。


🧠 前馈层

前馈层本质上是一个多层感知机。通常,它会将输入维度扩展到原来的4倍,在中间应用一个激活函数,然后再降采样回输入维度。Transformer 的大部分参数都存在于这个前馈层中,它存储了从训练数据中学到的知识。一些研究发现,大语言模型中上下文学习和涌现能力实际上可能源于这一层。


➕ 残差连接与层归一化

在多头注意力和前馈层之间,存在跳跃连接和归一化层。跳跃连接是残差块,层归一化是针对每个样本沿维度进行的归一化,用于稳定训练。

归一化层在 Transformer 块中的放置方式有两种:

  1. 后置归一化:在残差连接之后应用归一化。
  2. 前置归一化:在多头注意力和前馈层之前应用归一化。

放置位置对训练稳定性和性能有重大影响。前置归一化通常更稳定,使训练更容易,它通过在处理输入前对其进行归一化来防止极端激活,确保层接收行为良好的输入,从而获得更好的收敛性。而后置归一化可能更难训练,但有时在下游任务中能产生更好的结果。另一种理解是,前置归一化使网络行为更接近恒等映射,信息在网络中流动更顺畅。


📤 输出层

最后一层是输出层,它只是一个投影层,用于将输出表示投影到任务词汇表的大小。


🔄 整合所有组件

现在让我们把所有组件整合起来。处理流程始于分词,然后是嵌入,接着添加位置编码以注入位置信息。之后,输入被传递给多头注意力层,以帮助词元之间交互。接着是前馈层,用于细化表示。层归一化和残差连接被添加到网络中以提高稳定性。最后一层是输出投影层。


🆕 Transformer 架构的变体与改进

上一节我们介绍了原始 Transformer 的各个组件,本节中我们来看看针对这些组件的神经结构改进。首先讨论不同类型的 Transformer 架构,然后谈谈相对位置编码,最后介绍注意力机制的高效变体。

Transformer 架构的三种类型

  1. 编码器-解码器:通常用于条件生成任务,如机器翻译或摘要生成。编码器可以完全编码输入(无掩码),然后解码器根据给定输入生成相应的输出。解码器通常是因果解码器。
  2. 仅解码器:只包含解码器部分,没有编码器。用于概率分布建模和生成,是自回归的,逐个词元生成。GPT 系列就是例子。
  3. 仅编码器:只包含编码器部分,注意力不是因果的,输入对注意力完全可见。适用于理解任务,通常用于分类或表示学习。BERT 就是例子。

相对位置编码

我们之前学习了绝对位置编码。然而,这种方法无法很好地泛化到训练中未见过的更长序列。一种提出的替代方案是使用词元之间的相对距离,并将其直接添加到注意力计算中,而不是使用固定的嵌入并添加到词元嵌入中。

以下是两种相对位置编码的例子:

  • ALiBi(线性偏置注意力):在 Softmax 操作之前,向注意力矩阵添加一个与词元间相对距离成比例的偏置。这个偏置不是可学习的参数,而是一个超参数,不同注意力头可以有不同的值,以同时关注局部和长程依赖。
  • RoPE(旋转位置编码):使用旋转矩阵,根据词元在序列中的位置旋转每个键和查询向量。它通过将词元嵌入在2D空间中旋转来纳入位置信息。RoPE 允许在训练后扩展序列长度,在推理时可以使用更长的序列。

高效注意力机制

注意力机制的一个问题是其计算复杂度是序列长度的二次方。有多次尝试来降低这种操作的复杂度,以下是五个例子:

  1. 线性注意力:用线性操作近似相似性度量,通过使用核技巧重新排列计算顺序,将复杂度从关于序列长度的二次方降低为关于嵌入维度的二次方。
  2. Flash Attention:一种硬件感知的优化,通过将注意力计算重构为分块(tiling)处理,以最小化内存访问。它不改变注意力计算本身,而是更有效地利用 GPU 内存层次结构(如 SRAM 和 HBM),减少读写操作,从而加快计算速度。
  3. 多查询注意力 & 分组查询注意力:通过让多个查询共享单一的键值对(多查询)或一组查询共享一组键值对(分组查询),来减少推理过程中需要缓存和计算的键值对数量,从而节省内存,但性能可能略有下降。
  4. 多潜在注意力:通过一个投影矩阵压缩键和值的维度(而不是数量),只缓存压缩后的潜在向量。令人惊讶的是,其性能有时优于标准注意力,可能因为投影去除了向量中的噪声。

为了理解后三种技术,需要先了解 KV 缓存。对于解码器 Transformer 模型,解码是自回归的,每一步都在重新计算之前词元的注意力。KV 缓存的思路是缓存之前词元的键和值,只计算当前新词元的注意力,从而大幅提高推理效率。


🖼️🎵 Transformer 在多模态中的应用

Transformer 已被广泛应用于视觉和音频任务。接下来我们将学习如何使 Transformer 适配这些模态。

视觉 Transformer

将 Transformer 应用于图像的一种早期架构是 Vision Transformer。其思路是将图像分割成小块(patch),将这些块视为词元,而 Transformer 架构保持不变。这类似于 CNN 中使用固定大小的卷积核处理图像局部区域。在 ViT 中,图像被分割成基于所选块大小的小块,然后这些块被展平并通过一个全连接层(或使用卷积层)进行线性投影。由于分割图像块会丢失空间信息,因此需要为每个块添加一个可学习或固定的位置嵌入。一旦图像被转换为这些小块词元,剩余的架构就与原始 Transformer 相同。ViT 通常是一个仅编码器模型,用于分类任务。

与 CNN 相比,CNN 具有归纳偏置(如局部性、平移等变性),使其能高效处理图像。而 Vision Transformer 没有内置的归纳偏置,必须从头从数据中学习这些模式,这使得它非常灵活,但也需要大量数据才能有效学习。

音频 Transformer

Transformer 在音频领域的早期应用之一是语音识别。与 Vision Transformer 类似,主要改动在输入层,其余架构保持不变。例如,可以在 Transformer 编码器之前加入卷积层,以捕获声谱图的局部结构,并通过在时间轴上步进来解决长度不匹配问题。另一种方法是 Audio Spectrogram Transformer,它将音频转换为声谱图,并将其视为图像进行处理。还有 Conformer 模型,它结合了 Transformer 的全局依赖建模能力和 CNN 的局部特征捕获能力,在语音识别任务中表现出色。

分词器的作用

多模态方法的一个关键思想是将图像或音频等连续模态转换为离散的词元表示,然后像处理文本一样使用 Transformer 架构对这些词元进行建模。分词器可以针对不同目标进行训练,例如从图像中提取语义或空间信息,从音频中提取声学信息。


🎛️ 参数高效微调

参数高效微调的核心思想是,不微调预训练 Transformer 的所有参数,而只调整一小部分参数。以下是四种技术:

  1. 前缀微调:在输入序列的嵌入前添加可训练的前缀嵌入向量,通常将这些参数添加到每一 Transformer 层的键和值权重矩阵中。每个任务有不同的前缀。
  2. 提示微调:前缀微调的简化版本,只将提示嵌入添加到输入序列中,并且只微调这些嵌入,模型其余参数冻结。
  3. 适配器:在预训练模型中插入新的小型前馈网络层(适配器),通常放在前馈层之后,并仅训练这些适配器用于下游任务。这样可以不破坏预训练模型的知识。
  4. LoRA:向原始权重矩阵添加低秩矩阵(适配器),仅训练这些低秩适配器。训练后,将这些适配器加到原始权重上。这些低秩适配器的维度通常很小(如8或16),能显著减少可训练参数量。

本质上,所有参数高效微调方法都做同样的事情:以最少的参数量修改前向传播的特征。它们通常能达到接近全参数微调的性能。


📈 缩放定律与涌现能力

Transformer 的魔力在于其性能随着数据、模型规模和计算资源的增加而可预测地提升。OpenAI 的《Scaling Laws》论文描述了这种缩放关系。缩放定律可用于通过在小规模实验上运行多个实验并拟合回归模型,来预测大规模训练的最佳超参数(如批大小、学习率),从而避免浪费计算资源去尝试大规模训练的不同配置。

最令人兴奋的能力之一是 Transformer 和大语言模型的涌现能力,即上下文学习。

  • 零样本学习:只提供任务描述,模型必须找出正确答案。
  • 少样本学习:除了任务描述,还提供少量任务示例,模型应从这些示例中学习。

当模型规模达到某个临界点时,其在各种任务上的性能会急剧提升,展现出这种能力。


✅ 总结

本节课中,我们一起深入学习了 Transformer 架构。我们从其核心组件(如注意力机制、位置编码、前馈网络)开始,理解了它是如何处理序列数据的。接着,我们探讨了 Transformer 的多种变体(编码器-解码器、仅编码器、仅解码器)以及如何将其成功应用于视觉和音频等多模态任务。我们还了解了提高注意力计算效率的技术(如 Flash Attention)和仅需微调少量参数的适配方法(如 LoRA)。最后,我们讨论了 Transformer 模型遵循的缩放定律及其带来的神奇涌现能力。Transformer 的灵活性、可扩展性和强大性能,使其成为当今人工智能领域的基石架构。

23:大语言模型与复合AI系统 🧠

在本节课中,我们将学习大语言模型(LLMs)的核心概念、它们如何通过预训练和对齐获得能力,以及如何利用它们构建更可靠、更可控的复合AI系统。我们将从基础的语言模型和Transformer架构开始,逐步深入到预训练、后训练(对齐)、强化学习,并最终探讨如何将这些模型组合成强大的软件系统。


语言模型与Transformer架构回顾

上一节我们介绍了课程的整体框架。本节中,我们来看看构建大语言模型的基础:神经语言模型和Transformer架构。

神经语言模型,特别是基于Transformer的模型,是当前许多AI进展的起点。Transformer架构,尤其是仅解码器(Decoder-only)的变体(如GPT系列),因其强大的序列建模能力而成为主流。

自回归解码是这一切的核心。其过程可以概括为以下步骤:

  1. 分词:将输入文本(提示)分割成词元(tokens)。
  2. 前向传播:将词元序列输入Transformer,计算每一层中每个词元的注意力键值。
  3. 预测下一个词元:基于模型输出的最终嵌入,投影到整个词表的概率分布上。
  4. 采样:根据概率分布采样出下一个词元。
  5. 循环:将新采样的词元追加到序列末尾,重复步骤2-4,逐步生成完整的文本。

这个过程非常通用,几乎任何可以用自然语言描述的任务都可以纳入这个框架。它使学习过程变得易于推理和实现。

编码器(Encoders)的补充说明:虽然本节课主要关注解码器模型,但编码器模型(如BERT)在需要高质量文本表示的任务中仍然至关重要,例如信息检索和搜索。它们擅长将文档映射为密集向量,从而构建可扩展的搜索索引。


预训练:赋予模型广泛的知识

仅仅拥有Transformer架构是不够的。为了让模型变得“大”且“智能”,第一步是进行大规模预训练。预训练的目标是让一个初始时一无所知的Transformer,通过海量文本数据,获得关于语言、世界乃至初步推理能力的广泛知识。

预训练数据通常来自网络爬取、书籍、代码库(如GitHub)、学术论文(如PubMed)、法律文本等多种来源。关键在于既要数据量足够大,又要通过精心筛选确保数据质量,去除低质量或垃圾内容。

预训练的核心任务是语言建模,即让模型根据上文预测下一个词元。这是一个自监督学习任务,使用交叉熵损失函数。公式可以表示为:

损失函数L = -log P(w_t | w_1, w_2, ..., w_{t-1})

其中,w_t 是目标词元,w_1w_{t-1} 是上文。

通过在海量数据上完成这个看似简单的任务,模型能够隐式地学习到语法、事实、常识、推理模式,甚至代码结构和数学规律。这造就了所谓的“基础模型”,一个易于适应各种下游任务的通用“神器”。

为什么预训练有效? 有两个主要假设:一是它通过大量计算“暴力”地找到了一个有利于梯度流动的良好参数初始化点;二是它使模型初始就具备强大的泛化能力,因为其训练目标(预测任何上下文的下一个词)本身就极其通用。

缩放定律:模型性能(如困惑度)随着训练计算量、数据量和参数规模的增加而可预测地提升。然而,这种提升通常遵循“收益递减”规律,即需要指数级增加计算资源才能获得线性的性能提升。这凸显了单纯依赖扩大预训练规模的局限性。


后训练与对齐:从“玩具”到“助手”

预训练得到的模型虽然知识渊博,但通常并不“好用”。它只是一个强大的“下一个词预测器”,可能生成无关内容、重复语句或不安全的文本。为了让它成为遵循指令、有帮助且安全的“助手”,我们需要进行后训练对齐

后训练的核心是大规模多任务学习。我们收集大量涵盖各种任务的指令-输出对数据(例如,翻译、总结、问答、代码生成),并在此数据上继续训练模型。这教会模型理解并执行人类的指令。

然而,仅仅通过监督学习让模型模仿标准答案存在局限:对于复杂任务(如数学或编程),模型可能只是学会了“捏造”一个看起来合理的答案,而非真正学会解决问题的步骤。这引出了强化学习的应用。

基于人类反馈的强化学习(RLHF) 是让模型行为符合人类偏好的关键方法。其典型流程分为三步:

  1. 监督微调:收集人类标注的优质指令-回复对,训练模型模仿这些行为。
  2. 奖励模型训练:让模型生成多个回复,由人类标注员对这些回复进行排序。基于这些排序数据训练一个“奖励模型”,使其能够判断回复质量的优劣。
  3. 强化学习优化:使用PPO等策略梯度算法,以奖励模型给出的分数为优化目标,进一步调整语言模型的参数,使其生成更受人类偏好的回复。

基于可验证奖励的强化学习:对于数学、编程等答案可明确验证的任务,我们可以绕过奖励模型,直接使用任务本身的成功与否(如代码通过测试、数学答案正确)作为强化学习的奖励信号。这种方法近年来取得了巨大进展,显著提升了模型在复杂推理任务上的能力。

成功RL探索的前提:要使强化学习有效,基础模型本身需要具备一定的“认知”或“推理”行为,例如能够进行思维链推理、自我检查或回溯。这样,模型才能在探索中偶然获得成功,从而让强化信号有迹可循。


复合AI系统:构建可靠、可控的应用

拥有了预训练和对齐后的大语言模型,我们就能构建用户可用的系统了吗?答案是:可以,但直接使用单体模型构建可靠系统非常困难。模型可能会产生看似合理实则错误的“幻觉”,且其黑箱特性使得调试、控制和迭代变得极具挑战性。

因此,研究者和工程师们开始构建复合AI系统。这类系统将大语言模型作为核心组件,而非最终产品,并将其与其他模块(可能是其他模型或传统软件)组合起来,形成模块化的软件架构。

一个经典例子是检索增强生成(RAG)

  1. 用户提出一个问题。
  2. 检索器(可能是一个编码器模型)从可信的知识库中查找相关文档。
  3. 大语言模型(解码器)基于检索到的文档生成答案,并可引用来源。

复合AI系统的优势包括:

  • 透明度:可以检查是检索器还是生成器出了问题。
  • 可控性:可以针对不同模块进行优化和调整。
  • 效率:语言模型无需记忆所有知识,可以更专注于推理和合成。
  • 准确性:通过引用来源,增加了答案的可信度。

更复杂的复合系统可以实现深度研究(让模型与搜索引擎等多轮交互)、编码代理(集成代码执行、测试和调试循环)等高级功能。

然而,当前构建复合AI系统面临一个主要挑战:提示工程过于复杂。开发者往往需要编写冗长、精细的提示文本来连接不同模块、定义行为约束和处理边界情况。这导致系统变得脆弱、难以维护,且与特定模型版本强耦合。

未来的方向是将复合AI系统视为程序。我们需要更高层次的抽象,将系统架构(模块、数据流)与具体的提示实现、模型调优分离开来。通过声明式的编程接口来定义系统行为,并利用机器学习算法(如自动提示优化、强化学习)自动寻找最优的模块实现方式(如最佳提示词或模型微调),从而构建出既强大又可迭代的AI软件。


总结

本节课中,我们一起学习了构建和应用大语言模型的完整路径:

  1. 基础:Transformer架构,特别是仅解码器模型,通过自回归解码生成文本。
  2. 预训练:在海量多样化文本数据上进行语言建模,赋予模型广泛的知识和基础能力,其性能遵循缩放定律。
  3. 后训练与对齐:通过监督微调和强化学习(包括RLHF和基于可验证奖励的RL),将模型从“下一个词预测器”转变为有用、可靠、符合人类偏好的“助手”。
  4. 复合AI系统:将大语言模型作为模块化软件中的组件,与其他模块组合,以构建更透明、可控、可靠的应用。未来的趋势是将其视为可通过编程和自动优化来管理的系统。

大语言模型是强大的基础,但将它们转化为真正可靠的AI系统,需要我们以软件工程的思维,构建精心设计的复合架构。

24:神经网络内部机制解析 🧠

概述

在本节课中,我们将深入探讨神经网络在训练过程中究竟学到了什么。我们将从简单的线性分类器开始,逐步理解神经网络如何将复杂数据转换为线性可分的特征,并最终学习到数据的统计分布。我们还将介绍自编码器的概念,并了解它如何学习数据的主流形。


神经网络的学习问题

我们首先回顾神经网络的基本学习问题。给定一组输入-输出对作为训练数据,我们的目标是学习一个函数,使其能够根据输入预测正确的输出。

在理想情况下,数据可能是清晰可分的。但在现实中,数据往往存在噪声和重叠。例如,在一个双五边形分类问题中,我们期望看到一些蓝色点出现在红色区域内,反之亦然。神经网络必须从这些不完美的数据中学习边界。

为了深入理解,让我们从一个非常简单的例子开始。


一维线性分类器与逻辑函数

考虑一个一维输入空间和两个类别(红色和蓝色)。一维的线性分类器就是一个阈值,它将空间的一侧分配给一个类别,另一侧分配给另一个类别。

然而,当数据点无法被一个清晰的阈值完美分开时,我们该怎么办?例如,在同一个x值处,我们可能有90个红色实例和10个蓝色实例。我们希望函数输出什么?

一个更合理的输出不是简单的类别标签,而是该类别的后验概率。在这个例子中,输出0.9(表示90%的概率是红色)比单纯输出“红色”提供了更多信息。这告诉我们,对于给定的输入x,类别1(红色)出现的概率。

当我们考虑输入值附近的一个小窗口,并计算该窗口内y值的平均值时,这个后验概率会平滑地变化。这个平滑变化的函数形状看起来非常熟悉。

公式
P(y=1|x) = 1 / (1 + e^{-(w_0 + w_1 x)})

这正是逻辑函数(Sigmoid)。当x为很大的负值时,函数输出接近0;当x为很大的正值时,函数输出接近1。它平滑地从0过渡到1,完美地建模了给定输入x时,类别1的后验概率。

因此,一个使用逻辑激活函数的感知器,实际上是在建模目标类别的后验概率。


扩展到多维:线性分类器的本质

将问题扩展到二维。一个线性分类器试图用一条直线(或高维空间中的超平面)来分隔两个类别。即使数据不是完美线性可分,逻辑回归输出的概率值也会形成一个平滑的曲面,从一侧的0过渡到另一侧的1。

这个曲面的决策边界(即输出概率为0.5的地方)在哪里?

公式
w_0 + w_1 x_1 + w_2 x_2 = 0 时,输出为0.5。

这是一个直线方程。因此,尽管概率函数本身是弯曲的,但类别之间的决策边界仍然是一条直线。这就是为什么逻辑回归被称为线性分类器。


最大似然训练与交叉熵损失

我们如何训练这个模型?给定训练数据 (x_i, y_i),我们使用最大似然估计。我们的目标是找到模型参数(权重),使得观察到当前训练数据的概率最大。

假设数据点独立,联合概率是每个数据点概率的乘积。应用贝叶斯规则并取对数后,我们最大化对数似然函数。

公式
最大化 Σ_i log P(y_i | x_i)

这等价于最小化负对数似然。如果我们仔细观察这个表达式,会发现它正是KL散度交叉熵损失

因此,当我们使用反向传播和交叉熵损失来训练逻辑函数时,实际上是在执行最大似然训练,以最佳地估计给定输入x时类别y的后验概率。


深度神经网络:特征提取与分类

现在,让我们考虑更复杂的网络,例如用于双五边形分类的网络。一个足够强大的网络可以完美分类。

我们可以将网络分为两个部分:

  1. 特征提取部分:从输入层到倒数第二层的所有层。
  2. 分类部分:最后一层(通常是逻辑回归或Softmax)。

如果网络能够完美分类,这意味着在倒数第二层,数据特征已经变得线性可分。然后,最后一层的线性分类器可以轻松地划出决策边界。

因此,特征提取部分的作用是将原始输入数据转换(或“扭曲”)到一个新的特征空间,在这个新空间中,不同类别的数据变得线性可分。最后一层则在这个线性可分的特征空间上进行分类。

即使网络结构不足以完美分离数据,或者数据本身不可分,特征提取部分也会尽力将数据转换,使得最后一层分类器能在其能力范围内达到最佳性能(即最小化错误)。整个网络仍然在努力估计后验概率 P(y|x)

总结一下:分类神经网络是一个统计模型,它学习计算给定输入下各个类别的后验概率。训练网络以最小化交叉熵损失,等同于对该模型进行最大似然训练。


网络内部的逐层变换:流形假设

那么,网络中间的层具体在做什么呢?一个合理的假设是流形假设

以二维圆形决策边界为例。我们训练一个网络,它有一个包含3个神经元的隐藏层(使用tanh激活函数),最后接一个逻辑输出层。

初始时,数据是二维的。第一层的权重矩阵(3x2)将数据投影到一个三维空间中的一个二维平面上。然后,tanh激活函数对这个平面进行非线性扭曲,使其变成一个弯曲的曲面。后续的层(权重矩阵)可以看作是在对这个曲面进行投影

网络学习的目标是:通过调整各层的权重,扭曲并投影这个曲面,使得最终在投影后的特征空间里,属于不同类别的点能够被一条直线(或一个超平面)分开。

可视化训练过程显示,随着训练进行,数据在每一层之后都变得更加线性可分。更深层的网络可以将类别分得更开。有时,即使在中层网络特征已经线性可分后,更深层的网络还会继续工作,以增大类别间的“间隔”。


单个神经元的学习:模板匹配

现在,让我们深入到最基本的单元:一个使用阈值激活的感知器。

感知器计算输入向量 x 和权重向量 w 的点积(内积),并与阈值比较。在高维空间中,如果我们将向量长度归一化,点积主要反映的是两个向量之间的夹角余弦(相似度)

公式
输出 = 1,如果 w·x > 阈值

这等价于说:如果输入 x 与权重向量 w 的夹角小于某个值,则神经元激活。因此,权重向量 w 可以被视为该神经元要检测的模板典型模式

神经元的作用是计算输入与自身模板的相关性。如果相关性超过阈值,它就“激活”。

例如,要识别数字“2”,我们可以设置一个看起来像“2”的模板作为权重。输入图像与这个模板计算相关性,相关性高则判定为“2”。

在多层网络中,第一层的神经元学习成为基本的特征检测器(如检测水平线、垂直线)。后续层的神经元则检测这些基本特征的组合(如构成数字“1”或“2”的模式)。


自编码器:学习数据的主流形

如果我们截取一个训练好的分类网络的第一层,用其输出乘以权重的转置,会发生什么?

我们会重建输入,但重建的将是那些被神经元检测到的、与分类相关的特征。为了更完整地重建原始输入,我们可以设计一个专门的网络结构——自编码器

自编码器由两部分组成:

  1. 编码器:将输入压缩成一个低维的“编码”(潜在表示)。
  2. 解码器:从这个编码试图重建原始输入。

网络通过最小化输入与重建输出之间的误差来训练。

线性自编码器
假设编码器和解码器都是线性的,并且隐藏层只有一个神经元。那么,网络学习的是什么呢?

公式
重建输出 = w^T (w x) = (w^T w) x

这实际上是在学习将数据投影到向量 w 所张成的一维直线上,并最小化投影误差。这正是主成分分析(PCA)——寻找数据中方差最大的方向。无论输入是什么,输出都只能落在这条直线上。

非线性自编码器
当我们在解码器中引入非线性激活函数时,解码器可以将低维编码映射回一个弯曲的流形,而不仅仅是一个线性子空间。

这样的自编码器学习的是数据的非线性主流行形。解码器学会了如何根据编码(在流形上的位置)来生成数据。一个关键特性是:无论你向训练好的解码器输入什么,它都只能生成位于这个学习到的流形上的数据

例如,在螺旋形数据上训练的自编码器,其解码器只能生成螺旋线上的点。在数字图像上训练的自编码器,其解码器只能生成像数字的图像。在音乐数据上训练的自编码器解码器,只能生成类似训练音乐的声音。


应用示例:基于字典的信号分离

自编码器的这种“流形生成”特性可以用于信号分离。假设我们想从混合音频中分离吉他和鼓声。

  1. 分别用吉他音频和鼓声音频训练两个自编码器,得到吉他解码器和鼓声解码器。
  2. 吉他解码器只能生成吉他般的声音,鼓声解码器只能生成鼓般的声音。
  3. 给定一个混合信号,我们使用反向传播来优化:找到分别输入到两个解码器的编码,使得两个解码器的输出之和尽可能接近混合信号。
  4. 优化后,吉他解码器的单独输出就是分离出的吉他声,鼓声解码器的单独输出就是分离出的鼓声。

这种方法利用了每个解码器只能生成特定流形上数据的特点,从而实现了源分离。


总结

本节课我们一起深入探索了神经网络的内部表示:

  1. 分类网络学习预测类别的后验概率 P(y|x)。网络的末端是一个线性分类器,而此前的所有层共同构成一个特征提取器,致力于将数据转换为线性可分的特征。
  2. 逐层处理的过程可以理解为将数据逐渐变换到更易于线性分离的空间,这符合流形假设。
  3. 单个神经元学习的是输入数据的模板,通过计算输入与模板的相关性来决定是否激活。
  4. 自编码器提供了一种无监督学习方式,用于捕捉数据的主流行形。编码器进行降维或特征提取,解码器则学习从低维表示重建数据,且只能生成在学习到的流形上的数据。
  5. 这种对数据流形的理解是构建生成模型的基础,我们将在后续课程中继续探讨。

通过本讲,我们认识到神经网络不仅是强大的函数逼近器,更是有坚实统计基础的、能够学习数据底层结构与分布的模型。

25:变分自编码器 (VAE) 🧠

概述

在本节课中,我们将学习变分自编码器。这是一种强大的生成模型,它不仅能学习数据的压缩表示,还能学习数据的潜在分布,从而能够生成新的、与训练数据相似的数据样本。我们将从标准自编码器的局限性开始,逐步推导出VAE的原理和训练方法。


从自编码器到数据生成

上一节我们介绍了自编码器如何学习数据的低维流形。本节中我们来看看如何利用自编码器来生成新数据。

深度神经网络可以作为分类器或预测器。我们即将处理一个新问题:如何生成数据。例如,给定大量人脸图像,我们能否训练一个网络来生成新的人像?这本质上是一个表征数据分布并从该分布中采样的问题。

数据可以被认为大致位于某种流形上。这个假设是成立的,因为只有噪声才会均匀地占据所有空间。一旦我们对数据施加结构,数据就会受到限制。因此,数百万像素的图像并非占据所有可能的高维空间,而是位于一个高度结构化的子空间中。

我们的假设是:数据分布在这个高维空间中的一个弯曲或非线性的流形上。自编码器的解码器部分就学会了捕获这个数据流形。如果自编码器经过适当训练,其解码器在接收到任意输入时,只能生成位于该流形上的数据。

然而,这里存在一个问题。即使解码器学会了在训练数据区域表征流形,但在训练数据未覆盖的区域,其行为可能变得不可预测。解码器可能会偏离预期的路径。


约束潜在空间

为了解决上述问题,我们需要确保输入解码器的潜在变量 Z 是“典型”的,即它们来自该数据类别的自然分布。但我们并不知道这个分布是什么。

一个解决方案是:在训练自编码器时,显式地约束 Z 的分布。一个典型的选择是标准高斯分布(均值为0,方差为1)。我们选择它是因为在给定方差下,高斯分布是信息量最少(熵最高)的分布,这意味着我们没有强加不必要的假设。

如果我们能约束模型,使 Z 值来自标准高斯分布,那么要生成新数据,我们只需从标准高斯分布中采样一个 Z,然后将其输入解码器。理论上,输出应该类似于训练数据。

那么,如何训练这个带有约束的模型呢?模型结构如下:

  • 编码器 E (参数 θ):输入数据 X,输出潜在变量 Z
  • 解码器 D (参数 φ):输入 Z,输出重建数据 X_hat

训练目标是:

  1. 最小化输入 X 与重建输出 X_hat 之间的误差。
  2. 同时,最小化 Z 的分布与标准高斯分布之间的差异。

这种差异可以用KL散度来衡量。对于标准高斯分布,最小化负对数似然等价于最小化 Z平方范数 ||Z||^2

因此,整体的训练策略是寻找参数 θφ,以最小化以下损失函数:
L = ||X - D(Z)||^2 + λ * ||Z||^2
其中 λ 是一个权衡两项重要性的超参数。


处理流形外的变化

然而,上述模型并未捕获数据的全部变化。解码器只能生成位于低维流形上的数据,但真实数据可能围绕这个流形存在变化。此外,我们可能无法准确猜中流形的真实维度。

为了解释这种变化,我们扩展模型:假设观测到的数据 X 是通过向解码器的输出 X_hat 添加噪声 ε 而获得的。即:
X = D(Z) + ε
通常假设噪声 ε各向同性的高斯噪声(均值为0,协方差矩阵为对角阵)。这是合理的,因为任何有结构的噪声理论上都可以被解码器学习。

这个新模型意味着:对于任何输入 Z,由于噪声 ε 的存在,最终输出 X 可能不同。因此,解码器现在是一个生成模型,它描述了数据产生的过程:从高斯分布中采样 Z,通过解码器得到流形上的点,再加入高斯噪声得到最终数据。


变分自编码器的核心思想

现在面临一个训练难题:要训练解码器参数 φ,我们需要 (Z, X) 对。但 Z隐变量,我们无法直接观测。

我们可以用编码器来估计 Z。但由于噪声 ε 的存在,对于任何一个给定的 X,可能有无数个 Z 值(通过添加不同的噪声)都能生成它。这些 Z 值构成一个分布

因此,我们的编码器不应该输出一个确定的 Z 值,而应该输出一个分布 Q(Z|X),这个分布描述了哪些 Z 值最有可能生成了当前的 X。我们希望 Q(Z|X) 尽可能接近真实的后验分布 P(Z|X)

这就是变分自编码器的核心:编码器学习一个近似后验分布 Q(Z|X),解码器则尝试将从这个分布中采样的 Z 转换回 X


VAE的模型设定与训练

我们如何表示这个分布 Q(Z|X)?根据之前关于各向同性高斯分布的讨论,我们可以合理地假设 Q(Z|X) 是一个高斯分布。这样,编码器只需要为每个输入 X 输出该高斯分布的均值 μ方差 σ^2(通常假设为对角阵以简化计算)。

以下是VAE的训练过程概览:

  1. 前向传播:对于输入 X,编码器输出 μ(X)σ^2(X)
  2. 采样:从分布 N(μ, σ^2) 中采样一个 Z。为了能够反向传播,我们使用重参数化技巧
    Z = μ + σ ⊙ ε,其中 ε ~ N(0, I)
    这样,随机性被转移到 ε,而 μσ 是确定性的、可微的。
  3. 重建:将采样的 Z 输入解码器,得到重建输出 X_hat
  4. 计算损失:VAE的损失函数由两部分组成:
    • 重建损失:衡量 X_hat 与原始 X 的差异。假设观测噪声是高斯分布,这通常等价于均方误差 ||X - X_hat||^2
    • KL散度损失:衡量编码器输出的分布 Q(Z|X) 与先验分布 P(Z)(标准高斯分布)之间的差异。对于高斯分布,其KL散度有闭合形式:
      KL = -0.5 * Σ (1 + log(σ_i^2) - μ_i^2 - σ_i^2)
      其中求和是对 Z 的所有维度进行。
  5. 反向传播与优化:总损失是重建损失与KL散度损失的加权和。通过反向传播同时更新编码器 (θ) 和解码器 (φ) 的参数。

训练完成后,我们可以丢弃编码器。解码器就是我们需要的生成模型。要生成新样本,只需从标准高斯分布 N(0, I) 中采样一个 Z,然后输入解码器即可。


VAE的能力与局限性

VAE是一个强大的生成模型,其潜在空间 Z 能够捕获数据的内在结构。例如,在两个人脸对应的 Z 之间进行线性插值,解码后可以得到中间表情的人脸图像,这证明了潜在空间的连续性。

然而,VAE有两个主要的局限性:

  1. 近似后验的偏差:我们使用简单的高斯分布 Q(Z|X) 来近似可能非常复杂的真实后验 P(Z|X),这必然会引入偏差。一种改进思路是使用归一化流来构建更灵活、可逆的变换。
  2. 生成样本模糊:VAE生成的图像常常看起来比较模糊。这是因为模型假设重建误差(噪声)是各向同性、不相关的高斯噪声。但实际上,图像中缺失的细节(如清晰边缘)具有高度结构性,这与模型的假设不符。这表明解码器未能完全解耦所有数据变化。

为什么解码器工作得如此“困难”?因为它需要将一个简单的标准高斯分布映射到极其复杂的数据分布。如果先验分布 P(Z) 本身就更接近目标分布,那么解码器的任务就会轻松很多。这种“用一系列简单的步骤逐渐逼近复杂分布”的思想,引出了著名的扩散模型


总结

本节课中我们一起学习了变分自编码器。我们从标准自编码器生成数据时遇到的问题出发,引入了对潜在变量的分布约束。通过假设潜在变量服从高斯分布,并使用编码器来近似其后验分布,我们得到了VAE的框架。我们详细探讨了其损失函数(重建损失和KL散度损失)、训练中的重参数化技巧,以及它作为生成模型的工作原理。最后,我们也分析了VAE的优缺点,并指出了其与更先进模型(如归一化流和扩散模型)的联系。VAE是连接自编码器与复杂生成模型的重要桥梁。

26: 扩散模型 🌀

概述

在本节课中,我们将要学习扩散模型。这是一种通过逐步添加和去除高斯噪声来学习数据分布并生成数据的生成式模型。我们将从基础概念开始,逐步深入到其数学原理、训练方法、加速技术以及条件生成等高级主题。


1. 扩散模型基础 🧱

上一节我们回顾了生成式模型与判别式模型的基本区别。本节中,我们来看看扩散模型如何作为一种特殊的生成式模型工作。

扩散模型本质上是一系列变分自编码器的堆叠。它包含两个核心过程:前向扩散过程反向去噪过程

  • 前向扩散过程:这是一个固定的过程,它通过一系列固定的高斯转换,逐步向原始数据 x0 添加噪声,最终得到纯高斯噪声 xT。这个过程可以看作是VAE编码器部分的堆叠,但其参数是预先设定而非学习的。
  • 反向去噪过程:这是一个可学习的过程,目标是从纯高斯噪声 xT 开始,通过一系列可学习的“解码器”逐步去除噪声,最终恢复出原始数据分布 x0。这个过程可以看作是VAE解码器部分的堆叠。

公式:前向过程中,任意时刻 t 的数据 xt 可以由初始数据 x0 通过以下闭式解得到:
xt = sqrt(α_t_hat) * x0 + sqrt(1 - α_t_hat) * ε,其中 ε ~ N(0, I)α_t_hat 是由噪声调度 β_t 决定的累积乘积。


2. 训练与采样 🎯

理解了扩散模型的基本框架后,本节我们来看看如何训练它以及如何用它来生成新样本。

训练的核心目标是让神经网络学会预测在前向过程中添加到数据里的噪声。尽管推导过程涉及变分下界,但最终目标可以简化为一个简单的去噪任务。

以下是训练一个扩散模型的基本步骤:

  1. 从数据集中采样一个干净样本 x0
  2. 随机选择一个时间步 t(1 到 T 之间)。
  3. 从标准高斯分布采样一个噪声 ε
  4. 利用前向过程公式,计算加噪后的样本 xt
  5. xt 和时间步 t 输入神经网络,让网络预测所添加的噪声 ε_θ(xt, t)
  6. 最小化预测噪声 ε_θ(xt, t) 与真实噪声 ε 之间的均方误差(MSE)。

代码:训练目标可以简化为:
L_simple = E_{t, x0, ε}[|| ε - ε_θ(xt, t) ||^2]

采样(生成)过程则是训练的逆过程:

  1. 从标准高斯分布采样一个随机噪声 xT
  2. t = T 开始,逐步迭代到 t = 1
    • 用训练好的网络预测当前 xt 中的噪声 ε_θ(xt, t)
    • 根据预测的噪声和噪声调度参数,计算出去除部分噪声后的前一步状态 x_{t-1}
  3. 最终得到生成的样本 x0

3. 随机微分方程与得分匹配视角 📈

扩散模型的理论基础可以统一在随机微分方程的框架下。本节我们将从这个更一般的视角来理解扩散模型。

  • 前向SDE:将离散的前向扩散过程视为连续时间下的随机微分方程。它描述了数据分布如何随时间“漂移”并注入噪声,最终演变为简单的高斯分布。
    dx = f(x, t)dt + g(t)dw,其中 dw 是维纳过程(高斯噪声)。
  • 反向SDE:每个前向SDE都有一个对应的反向SDE。反向SDE描述了如何从高斯噪声出发,通过一个包含“得分函数”的漂移项,逐步演化回数据分布。
  • 得分函数:定义为数据对数概率密度的梯度,s(x) = ∇_x log p(x)。在扩散模型中,我们需要估计每个噪声数据 xt 的得分函数。
  • 得分匹配:训练神经网络 s_θ(xt, t) 来近似真实的得分函数。有趣的是,通过推导可以发现,预测噪声的目标与得分匹配的目标在数学上是等价的。

这个视角将扩散模型与另一类生成模型(得分匹配模型)联系起来,提供了更统一的理论解释。


4. 加速采样:DDIM 🚀

标准扩散模型(DDPM)的一个主要缺点是采样速度慢,需要迭代很多步(如1000步)。本节我们介绍一种名为DDIM的加速采样技术。

DDIM的核心思想是构造一个非马尔可夫的前向过程,使得在反向生成时,可以跳过一些中间步骤,用更少的迭代步数生成样本。

它与DDPM的关键区别在于采样方式:

  • DDPM:严格遵循马尔可夫链,必须一步步地从 x_t 预测 x_{t-1}
  • DDIM:在每一步,它首先利用当前 x_t 和预测的噪声 ε_θ 来估计初始数据 x0 的一个近似值 x0_hat。然后,利用这个 x0_hat 和更早时间步的噪声,直接计算 x_{t-1}

这种方法允许我们使用一个远小于训练步数 T 的子序列来进行采样(例如,只使用50或100步),从而大幅提升生成速度,且通常能保持不错的生成质量。


5. 条件扩散模型 🎨

到目前为止,我们讨论的都是无条件生成模型。本节我们看看如何引导扩散模型根据特定条件(如类别标签、文本描述)生成内容。

条件扩散模型学习的是条件分布 p(x|y),其中 y 是条件信息。训练时,模型除了接收噪声数据 xt 和时间步 t,还会接收条件 y 的编码。

一种重要的技术是无分类器引导。它的训练和推理策略如下:

  1. 训练:以一定概率(如10%)随机丢弃条件 y。这样,模型同时学会了有条件生成 (ε_θ(xt, t, y))无条件生成 (ε_θ(xt, t, ∅))
  2. 推理:通过引导尺度 γ 对有条件预测和无条件预测进行插值,得到最终的噪声预测:
    ε_guided = ε_θ(xt, t, ∅) + γ * (ε_θ(xt, t, y) - ε_θ(xt, t, ∅))
    增大 γ 可以增强模型对条件的遵循程度,但可能降低样本多样性。

这种方法避免了训练一个独立的分类器,更稳定且易于实现,被Stable Diffusion等现代模型广泛采用。


6. 近期进展与应用掠影 🚀

扩散模型领域发展迅速。本节我们将简要了解一些重要的架构和应用进展。

  • 潜在扩散模型:不在高维像素空间直接操作,而是先使用一个预训练的VAE将图像压缩到低维潜在空间,然后在潜在空间训练扩散模型。这大大降低了计算成本,是Stable Diffusion等模型的核心。
  • DiT:将Transformer架构作为扩散模型的主干网络,取代了常用的U-Net,展示了出色的缩放能力。
  • MAGE:将扩散模型的训练目标(去噪)与自回归建模结合,在图像生成任务上取得了优异效果。

这些进展表明,扩散模型不仅是强大的生成工具,其思想(如逐步去噪)也可以灵活地与其他深度学习范式结合。


总结

本节课中我们一起学习了扩散模型。我们从VAE堆叠的视角理解了其基本框架,学习了通过预测噪声进行训练和采样的过程。随后,我们从随机微分方程和得分匹配的角度获得了更统一的理论认识。为了克服采样慢的缺点,我们探讨了DDIM加速技术。最后,我们学习了如何通过无分类器引导实现条件生成,并概览了潜在扩散、DiT等前沿进展。扩散模型因其强大的生成能力和理论美感,已成为当前生成式AI领域的基石之一。

27:生成对抗网络 (GANs) 🎨🤖

在本节课中,我们将学习生成对抗网络(GANs)的核心概念。这是一种通过让两个神经网络相互对抗来生成逼真数据的强大技术。我们将从基本定义开始,逐步深入到其工作原理、训练过程以及面临的挑战。


概述:什么是生成对抗网络?

想象一下,你是一名正在学习绘画的艺术生。起初,你的作品粗糙且充满错误,但身边有一位严厉的艺术评论家不断指出你的不足。通过反复的试错,你不断精进技艺,直到你的画作逼真到连评论家都无法将其与真正的杰作区分开来。

这个过程就是生成对抗网络(GAN)的核心思想。GAN由两个神经网络组成:生成器试图创造逼真的内容(如图像、语音或视频),而判别器则扮演着严厉评论家的角色,拒绝任何看起来不真实的东西。


判别式模型 vs. 生成式模型

在深入GAN之前,我们需要理解两类基础模型。

  • 判别式模型:学习如何区分。给定输入,它们确定其类别。它们计算的是条件概率 P(Y|X)
    • 特点:学习类别间的决策边界。
    • 例子:逻辑回归、支持向量机(SVM)。
  • 生成式模型:学习如何生成。它们产生类似于训练数据的新实例。它们计算并从中采样的是联合概率 P(X, Y)
    • 特点:学习数据的实际概率分布。
    • 例子:朴素贝叶斯、高斯混合模型。

生成式模型是更困难的问题,因为它需要对数据分布有更深入的理解,而不仅仅是区分它们。


显式模型 vs. 隐式模型

生成式模型内部也有不同的建模方式。

  • 显式模型:显式地定义并计算样本的概率分布 P(X)
  • 隐式模型:不直接计算概率分布,只允许你从分布中抽取样本

上一节我们介绍了模型的基本分类,本节中我们来看看生成新数据时面临的核心问题。


核心问题:如何生成新数据?

假设我们有一个包含大量《辛普森一家》角色面部图像的数据集。我们的目标是训练一个能够生成全新肖像的网络。本质上,我们希望能够从面部图像(或任何数据)的分布中进行采样。

这面临两个根本性挑战:

  1. 高维空间:图像数据存在于极高维的空间中(例如百万像素),直接刻画整个分布极其困难。
  2. 评估困难:即使我们建立了一个模型,如何自动判断生成的结果是否像一张“脸”?手动检查不具可扩展性。

解决方案是自动化评估过程。我们可以用一个分类器(即判别器)来替代人类评估者,其任务是判断输入是“真实面部”还是“生成面部”。这个分类器的损失函数,我们称之为 “D_loss”(看起来像脸吗?)。


GAN的构成与工作原理

现在,让我们正式定义GAN及其组件。

GAN 代表 生成对抗网络

  • 生成式:能够生成与训练数据相似的数据。
  • 对抗式:由两个相互竞争、试图击败对方的网络组成。
  • 网络:使用神经网络实现。

GAN于2014年被提出,其目标是建模分布 P(X) 以便从中生成样本。它通过让一对模型扮演对抗角色来训练:一个生成,一个判断。

以下是GAN的完整框架图:

  • 生成器:接收一个随机噪声向量 z(来自先验分布 P(z)),并将其映射到数据空间,生成假数据 G(z)。生成器的目标是让输出分布 P_G(x) 尽可能匹配真实数据分布 P_data(x)
  • 判别器:接收数据(可以是真实数据 x 或生成数据 G(z)),并输出一个标量,表示输入是真实数据的概率。其任务是区分真实数据与生成数据。

如果存在一个完美的判别器,那么生成的数据将无法与真实数据区分。


如何训练GAN?

我们意识到需要同时训练生成器和判别器。

  • 判别器被训练以区分生成器产生的假脸和真实的脸。
  • 生成器被训练以“欺骗”判别器,使其认为生成的数据是真实的。

以下是训练目标:

对于判别器 (D)

  • 当看到真实数据时,希望输出接近1。目标:最大化 log(D(x))
  • 当看到生成数据时,希望输出接近0。目标:最大化 log(1 - D(G(z)))

对于生成器 (G)

  • 希望判别器对生成数据的判断出错(即认为它是真实的)。目标:最小化 log(1 - D(G(z)))(等价于让 D(G(z)) 接近1)。

整体损失函数(极小极大博弈)
将两者结合,GAN的训练可以被表述为一个极小极大优化问题:

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)))]

训练过程简述

  1. 初始化:生成器产生随机噪声(无意义数据),判别器从零开始学习。
  2. 判别器学习:判别器学习区分初始的假数据和真实数据。
  3. 生成器优化:生成器利用判别器提供的“反馈”(梯度)更新自身,试图生成更能欺骗判别器的数据。
  4. 迭代对抗:两者不断交替优化,判别器努力变得更强以识破骗局,生成器则努力生成更逼真的数据以通过检查。

理想判别器与训练动态

那么,一个理想的判别器是怎样的?

最优判别器 D*(x) 对于任意样本 x,会估计它来自真实分布而非生成分布的概率。根据贝叶斯规则,可以推导出:

D*(x) = P_data(x) / [P_data(x) + P_G(x)]

训练动态可视化

  1. 初始:判别器学习一个决策边界,分离差的生成数据和真实数据。
  2. 生成器更新:生成器调整其输出分布,使其更靠近真实数据分布。
  3. 判别器再更新:判别器重新学习新的决策边界。
  4. 收敛:理想情况下,经过多次迭代,生成分布 P_G(x) 与真实分布 P_data(x) 完全重合。此时,对于任何输入 x,判别器都只能给出 D(x) = 0.5(即无法判断),训练达到均衡。

这种训练过程实际上是在最小化真实分布与生成分布之间的 Jensen-Shannon散度


训练挑战与稳定化技巧

GAN的训练因其对抗性本质而充满挑战,容易出现不稳定、模式崩溃等问题。以下是研究人员提出的一些稳定化技巧的核心思想:

1. 优化问题

  • 原因:生成器和判别器的同步更新是一个动态博弈,不一定收敛。
  • 例子:就像“石头剪刀布”游戏,存在纳什均衡点(各出1/3),但梯度下降可能导致策略循环震荡,而非收敛。

2. 稳定化技巧概览
研究人员从不同角度提出了改进方案:

基础与稳定性修复

  • 梯度惩罚:约束判别器函数的梯度范数,使其更平滑,提供更有意义的梯度信号。
  • 实例噪声:向真实和生成数据添加噪声,平滑判别器的学习空间。
  • 最小二乘GAN:使用L2损失替代二元交叉熵损失,缓解梯度消失,训练更稳定。

散度替代

  • Wasserstein GAN:使用Wasserstein距离(推土机距离)替代Jensen-Shannon散度作为衡量标准。即使两个分布没有重叠,它也能提供平滑的梯度。其核心是要求判别器(在WGAN中称为“评论家”)是一个1-Lipschitz函数。
    • 实现挑战:如何有效实施Lipschitz约束。
    • 解决方案
      • 权重裁剪:原始方法,简单但可能导致优化问题。
      • 梯度惩罚:更有效的方法,直接对评论家相对于输入梯度的范数进行正则化,使其接近1。
      • 谱归一化:通过约束神经网络每一层权重矩阵的谱范数来保证整体函数的Lipschitz性质。

前瞻性优化

  • 展开GAN:生成器在更新时,会考虑判别器在未来几步内可能做出的反应,从而做出更不“贪婪”、更稳定的更新,有助于缓解模式崩溃。

要点总结与未来方向

本节课我们一起学习了生成对抗网络(GANs)的核心原理。

关键要点

  1. GAN通过生成器判别器的对抗博弈来学习数据分布。
  2. 训练目标是极小极大博弈,旨在让生成分布匹配真实分布。
  3. 原始GAN训练不稳定,易出现模式崩溃等问题。
  4. 一系列技术通过正则化平滑损失曲面替换散度度量(如Wasserstein距离)和约束网络复杂度来稳定训练。

当前趋势与挑战

  • 研究致力于设计更好的正则化方法损失函数
  • 一些方法(如展开GAN)理论效果好但计算成本高
  • 基于采样的方法在高维空间中可能效率低下
  • 基于约束的方法可能产生次优解或伪影

未来方向
一个核心的开放问题是:如何设计神经网络架构,使其能自动学习一个更有意义的、可指导稳定训练的损失函数? 这是推动GAN向前发展的关键。


本教程根据CMU《深度学习导论》课程Lecture 24内容整理翻译而成,旨在提炼核心概念,供初学者学习参考。

28:图神经网络 🧠

概述

在本节课中,我们将学习图神经网络。我们将探讨如何将深度学习技术应用于定义在不规则图结构上的数据,理解其核心概念、挑战以及解决方案。


从网格数据到图数据

在之前的课程中,我们主要处理的是定义在规则网格上的数据,例如时间序列或图像。这类数据具有自然的顺序。

然而,许多现实世界的数据,如社交网络、传感器网络、生物系统等,是定义在不规则的图结构上的。这类数据没有固定的顺序,这给传统的深度学习方法带来了挑战。


图数据的核心挑战

处理图数据主要面临两大挑战:

  1. 缺乏固定顺序:对于一个图,没有像“左上角像素”或“t=0时刻”这样直观的排序。不同的节点排序方式不应影响模型的计算结果。
  2. 如何融入图结构信息:模型需要一种机制来利用节点之间的连接关系(边),而不仅仅是节点自身的特征。

排列不变性

为了应对第一个挑战,我们对图数据进行的任何操作都必须是排列不变的。这意味着,无论我们如何给图中的节点编号,操作产生的数值结果(或经过相应排列后的结果)应该相同。

公式:设有一个排列矩阵 P(由单位矩阵行置换得到,满足 P^T = P^{-1})。如果我们的操作 f 是排列不变的,那么对于图信号 x 和其排列版本 x' = Px,以及相应的邻接矩阵 AA' = PAP^T,应有 f(A', x')f(A, x) 的排列版本。


图信号处理基础

为了给图神经网络打下基础,我们先了解下图信号处理的一些核心概念。它试图将离散信号处理中的工具(如卷积、傅里叶变换)推广到图数据上。

  • 图信号:定义在图节点上的一组数值,可以表示电压、社交网络中的好友数量等。它通常表示为一个向量 x
  • 邻接矩阵 (A):表示图中节点连接关系的矩阵。在GSP中,通常约定 A[i, j] = 1 表示存在一条从节点 j 指向节点 i 的边。
  • 图移位:这是GSP中的一个基本操作,类比于时间序列中的“延迟”。通过将邻接矩阵 A 乘以信号向量 x,即计算 Ax,信号值会沿着图的边传播到邻居节点。可以证明,图移位操作是排列不变的。

图卷积与图傅里叶变换

  • 图卷积:在GSP中,一个线性移位不变的图滤波器被定义为邻接矩阵 A 的一个多项式。
    公式y = h(A)x = (∑_{k=0}^{K} h_k A^k) x
    其中 h_k 是滤波器系数,x 是输入图信号,y 是输出图信号。这个操作也是排列不变的。
  • 图傅里叶变换 (GFT):通过对邻接矩阵 A 进行特征分解(A = VΛV^{-1}),其特征向量矩阵 V^{-1} 的转置构成了GFT的基。GFT将图信号从“节点域”变换到“谱域”(频率域)。
    公式x_hat = V^{-1} x (GFT)
    公式x = V x_hat (逆GFT)
    GFT同样是排列不变的操作。

图神经网络的任务

上一节我们介绍了处理图数据的理论基础,本节中我们来看看图神经网络具体能解决哪两类主要任务:

  1. 节点分类:目标是预测图中每个节点的类别标签(例如,在论文引用网络中预测每篇论文的研究领域)。由于需要为每个节点输出结果,通常不进行池化操作。
  2. 图分类:目标是预测整个图的类别标签(例如,判断一个分子结构是否具有致突变性)。每个数据样本是一个独立的图。这类任务可以进行图池化。

为什么需要图结构?
一个自然的疑问是:对于节点分类,为什么不直接使用多层感知机处理每个节点的特征?关键在于,图结构充当了“答题卡”的角色。即使只有很少的节点有标签(低标签率),模型也能利用节点之间的连接关系(同质性假设:相连的节点往往类别相似)来提升分类性能。研究表明,图的质量(是否真实反映节点关系)对最终准确率有巨大影响。


图卷积神经网络

现在,我们来看看如何将CNN的思想迁移到图数据上,构建图卷积神经网络。

GCN的核心结构类似于MLP,但将全连接层替换为图卷积层。一个典型的图卷积层操作可以分解为两步:

  1. 聚合:对于目标节点,收集其所有邻居节点的特征信息。
  2. 组合:将聚合得到的信息与目标节点自身的特征相结合,生成新的节点特征。

代码/公式示例:一个常见的图卷积层操作可以向量化地表示为:
X^{(l+1)} = σ( D^{-1/2} A D^{-1/2} X^{(l)} W^{(l)} )
其中:

  • X^{(l)} 是第 l 层的节点特征矩阵。
  • A 是带自环的邻接矩阵 (A = A + I)。
  • DA 的度矩阵(对角矩阵)。
  • W^{(l)} 是可学习的权重矩阵。
  • σ 是非线性激活函数。

这里的 D^{-1/2} A D^{-1/2} 是对邻接矩阵的归一化,用于稳定训练。这个操作可以理解为在每个节点上应用一个共享权重的MLP,其“偏置”来自邻居节点的聚合信息。

图注意力网络

标准的GCN在聚合时给所有邻居分配固定的权重。图注意力网络引入了注意力机制,让模型学习为不同的邻居分配合适的权重。

核心思想:计算目标节点 i 与其邻居节点 j 之间的注意力系数 α_{ij}
α_{ij} = softmax_j ( LeakyReLU( a^T [W x_i || W x_j] ) )
其中 W 是共享的权重矩阵,a 是注意力向量,|| 表示拼接。然后使用学习到的 α_{ij} 进行加权聚合。

从节点特征到图特征(图池化)

对于图分类任务,我们需要将所有节点的特征汇总成一个代表整个图的特征向量,并且这个汇总操作必须是排列不变的。

以下是几种简单的全局池化方法:

  • 求和池化z_G = sum( x_i ),对所有节点特征求和。
  • 平均池化z_G = mean( x_i ),对所有节点特征求平均。
  • 最大池化z_G = max( x_i ),对所有节点特征逐元素取最大值。

更复杂的池化方法(如Top-K Pooling)则学习有选择地丢弃一些节点,并生成一个节点数更少的新图,类似于CNN中的空间下采样。


研究案例:为图像构建更好的图表示

最后,我们通过一个前沿研究案例,看看如何为传统任务(如图像分类)设计更有效的图表示,以提升GNN的性能。

动机:通常将图像表示为网格图(每个像素是一个节点,连接相邻像素)。但我们可以尝试构建更能捕捉图像语义结构的图。

方法

  1. 行/列相关图:将图像的每一行/列视为一个信号,计算行与行、列与列之间的相关性,通过聚类判断是否存在“边”,从而分别构建行关系图和列关系图(均为28x28的邻接矩阵,对应MNIST的28行/列)。
  2. 克罗内克积图:为了得到一个能同时捕捉行和列关系的图(784x784,对应所有像素),我们对行图和列图进行克罗内克积操作,生成最终的图结构。
  3. 增强节点特征:不仅使用原始像素值作为节点特征,还加入梯度、均值、方差等更多描述性特征。

结果:实验表明,使用克罗内克积生成的图结构,并结合更丰富的节点特征,能显著提升GCN和图注意力网络在MNIST图像分类任务上的准确率。这验证了“更好的图结构(答题卡)和更好的特征”对提升GNN性能至关重要。


总结

本节课中我们一起学习了图神经网络的核心知识。我们从图数据特有的挑战(排列不变性)出发,了解了图信号处理的基础工具(图移位、卷积、傅里叶变换)。接着,我们深入探讨了图卷积神经网络的工作原理,包括其聚合-组合的范式、具体的GCN和GAT实现,以及用于图分类的池化方法。最后,通过一个研究案例,我们看到了如何为特定任务设计更优的图表示,从而释放GNN的潜力。图神经网络是将深度学习威力应用于社交网络、化学分子、推荐系统等复杂关系数据的强大工具。

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