「MIT 6.S191 (2025)- 2」深度序列模型:RNN,Transformers与注意力机制
讲座介绍
引言
本篇讲稿内容源自关于“深度序列建模”的第二场讲座。该讲座旨在系统性地介绍处理时序或顺序数据的基本概念与核心技术。随着音频、文本、金融时间序列以及生物序列等数据的日益增多,理解如何有效建模这些数据变得至关重要,尤其是考虑到大型语言模型(LLM)等前沿技术的兴起,序列建模已成为其关键基础。
讲座首先从基础的前馈神经网络出发,探讨其在处理序列数据时的局限性,进而引出循环神经网络(RNN)的核心思想——利用内部状态(Hidden State)来传递和记忆历史信息。内容涵盖了RNN的基本结构、递归关系、权重共享特性,以及其训练方法(时间反向传播 BPTT)和面临的挑战,如梯度消失与爆炸问题,并简要提及了LSTM等改进结构。此外,讲座还讨论了将文本等非数值序列数据转化为模型可处理的数值表示的关键技术——词嵌入(Embeddings)。
在讨论了RNN的局限性(如信息瓶颈和并行计算困难)后,讲座将重点转向更为现代和强大的注意力机制(Attention Mechanism)。通过类比信息检索,详细解释了Query、Key、Value的概念及其在自注意力(Self-Attention)中的运作方式,最终介绍了完全基于注意力机制构建的Transformer架构。该架构不仅克服了RNN的部分缺点,更因其强大的并行处理
讲座框架与内容纲要
深度序列建模 (Lecture 2 - 6S191)
├── 引言与动机
│ ├── 讲座主题:深度序列建模及其重要性
│ ├── 与大型语言模型 (LLM) 的关联
│ └── 目标:为后续LLM讲座奠定基础
├── 序列建模基础
│ ├── 动机示例:预测球的运动轨迹
│ ├── 序列数据的普遍性(音频、文本、医学、金融、生物等)
│ └── 序列建模的任务类型
│ ├── 多对一 (如情感分类)
│ ├── 一对多 (如图像描述)
│ └── 多对多 (如机器翻译, 下一词预测)
├── 循环神经网络 (RNNs)
│ ├── 从前馈网络到RNN的演进
│ │ ├── 前馈网络的局限性 (无法处理时序依赖)
│ │ └── 引入核心思想:通过内部状态传递历史信息
│ ├── RNN定义与核心概念
│ │ ├── 内部状态 (Hidden State, H(t)) 与记忆
│ │ ├── 递归关系 (Recurrence Relation)
│ │ └── 权重共享 (Weight Sharing across time steps)
│ ├── RNN的可视化 (折叠与按时间展开)
│ ├── RNN的训练
│ │ ├── 损失函数 (各时间步损失求和)
│ │ ├── 时间反向传播 (BPTT - Backpropagation Through Time)
│ │ └── 训练挑战:梯度消失与梯度爆炸
│ ├── RNN的改进:LSTM (长短期记忆网络) 简介
│ ├── RNN应用示例:音乐生成
│ └── 问答环节
│ └── 关于隐藏层内部及层间连接
├── 处理序列数据的挑战与方法
│ ├── 序列数据的特性:变长性、长/短距离依赖、顺序重要性
│ └── 词表示方法:嵌入 (Embeddings)
│ ├── 动机:神经网络需要数值输入
│ ├── 方法:词汇表 -> 索引 -> 向量 (One-hot / Learned Embeddings)
│ └── 核心概念:通过索引查找数值表示
├── 注意力机制 (Attention Mechanism) 与 Transformer
│ ├── RNN的局限性回顾
│ │ ├── 信息瓶颈 (固定大小的隐藏状态)
│ │ ├── 难以并行化 (顺序处理依赖)
│ │ └── 长期记忆能力受限
│ ├── 寻求新方法:摆脱顺序处理
│ │ ├── 朴素尝试 (如拼接输入) 及其问题
│ │ └── 核心需求:动态关注输入的相关部分
│ ├── 注意力机制的核心思想
│ │ ├── 类比:人类视觉、信息检索 (搜索)
│ │ └── Query, Key, Value (QKV) 概念解释
│ ├── 自注意力 (Self-Attention)
│ │ └── Q, K, V 均来自同一输入序列,捕捉内部依赖
│ ├── 注意力机制实现步骤
│ │ ├── 位置编码 (Positional Encoding)
│ │ ├── 通过线性变换生成 Q, K, V
│ │ ├── 计算注意力权重 (相似度 + Softmax)
│ │ └── 计算输出 (权重 * Value)
│ ├── Transformer 架构
│ │ ├── 基于注意力机制,抛弃循环结构
│ │ ├── 核心:(多头)自注意力模块
│ │ ├── 优点:并行计算、擅长捕捉长距离依赖
│ │ └── 作为现代LLM的基础
│ └── 注意力机制的应用扩展 (NLP, 生物序列, 视觉Transformer)
└── 总结与后续安排
├── 讲座内容回顾 (RNN, Attention, Transformer)
├── 预告后续LLM讲座与实验
└── 课程安排 (Office Hours, 软件实验, 招待会)
**能力和捕捉长距离依赖的能力,成为了当前许多先进模型(包括大型语言模型)的基础。
讲座实录:深度序列建模 (Deep Sequence Modeling)
1. 引言与动机
大家好,非常高兴大家能来到这里。我叫Ava,也是这门课程的讲师。这是6S191深度序列建模课程的第二讲。正如Alexander在他讲座结束时提到的,深度序列建模是一个非常非常强大的概念,尤其在近期,由于大型语言模型(Large Language Models)的出现,它获得了大量的关注和热情。实际上,令人兴奋的是,在今年的课程中,我们将在周四和周五安排两到两个半的客座讲座,专门讨论大型语言模型。我们开设这节课的目的是真正让大家掌握序列建模的基础知识,这样当我们在后续讲座中深入探讨这些前沿新领域话题时,你们能从基本原理和基础上真正理解它们。
好的,基于此,在第一讲中,Alexander介绍了神经网络的最基础知识、要素,我们如何使用反向传播训练它们,以及定义一个前馈模型意味着什么。因此,在接下来的这次讲座中,我们将把注意力转向如何将这些神经网络应用于涉及序列处理或数据序列建模的问题。我们将尝试逐步、并行地讲解这个问题设定和模型,建立我们关于如何从上一讲结束的地方开始,一步步构建这些网络的直觉。
2. 序列建模基础
为了首先设定好场景,我总是喜欢用一个非常非常直观、超级简单的例子来激发大家对序列建模和序列数据的概念。假设我们有这样一个二维空间中球的图像,我们的目标是预测这个球接下来会运动到哪里。
如果我没有给你任何关于球的历史信息,它在这个二维空间中的运动轨迹,那么你对球下一个位置的任何猜测都将是随机的。
但相反,如果除了球当前的位置,我还给了你一些关于它历史的信息,比如它之前的位置,那么你的问题就变得容易多了。实际上,任务就简化为:给定球过去的这段历史,预测它接下来会去哪里。我想我们都能同意,在这个例子中,球看起来是从左向右移动的。
这就是我们思考序列建模时所指的概念。除了这个简单的例子,正如我们将在整个课程中看到的,序列数据和序列建模实际上无处不在。例如:
- 我说话的声音音频可以被分割成一系列声波,有效地分块并按序列处理。
- 我们可以对词语、文本和自然语言做类似的事情,将文本分块或分割成单个字符、单个词语的序列,并以这种方式处理。
- 除此之外,还有许多其他情况,序列处理变得显而易见:从医学信号如心电图(ECG),到金融市场中的股票价格,到生物序列如核酸或蛋白质序列,再到天气、运动、视频等等。
因此,这种序列建模的范式确实为深度学习在现实世界中的应用和实际用例释放了巨大的潜力。
那么,当我们开始思考序列建模时,可以考虑哪些具体的 问题设定 或 任务 呢?在第一讲中,我们学习了非常基础的预测问题,比如一个分类问题:“我能否通过这门课?” 是或否,二元分类。现在,不同于从单个输入到一个单一输出(如类别标签),通过序列建模,我们希望处理一个 输入序列,例如一个句子中的文本、词语,并在最后产生一个输出,这个输出可能同样是一个分类标签,比如“一个句子是否带有积极或消极的情感或感觉?”。它也可能是一个 序列输出,比如输入一张图片,然后生成描述该图片的文本。类似地,我们可以考虑 多对多 的生成或预测任务,比如我们有英语序列,想把它翻译成另一种语言的序列。这些就是当我们拥有处理序列数据的能力时,可以开始思考的基本问题定义或问题设置。
3. 循环神经网络 (Recurrent Neural Networks - RNNs)
好了,有了这些背景,问题就变成了:我们如何实际构建一个神经网络模型来处理和应对这种独特的序列数据类型呢?今天,我们将花大约一半的讲座时间来讨论这个领域的历史开端,即一种新型的神经网络架构,它开启了我们真正模拟序列的能力。然后在下半部分,我们将讨论一种强大的机制,这种机制正被用于当今我们随处可见的最先进的序列模型中。在这两种情况下,我的目标都是真正传达我们如何实际做到这一点的基本原理。因此,我们将从Alexander介绍的感知机开始,一步步地建立对这些模型的理解。
好的,让我们就这么做。让我们回到在第一讲中学到的感知机。我们看到了像这样的图表,我们有一组输入 X1 到 Xm,这些是数字,它们以数值形式表示。在感知机中,我们所做的是将这些输入乘以相应的权重值,将它们全部加起来得到状态 Z,然后将这个 Z 通过一个非线性函数,也称为激活函数,这就得到了我们的预测输出 Y_hat。虽然这里可以有多个输入,但你可以将这些输入看作是一个序列中的单个切片或单个时间步。
我们还看到,我们能够将这些单独的感知机堆叠在一起,构成神经网络的一层。在这里,我们还可以扩展到从一组输入映射到一组多个输出(紫色部分)。但即便如此,在这个例子中,我们仍然是在处理一个静态的、单一的时间步。没有序列的概念,也没有时间的概念。这些输入都只是在一个时间点切片上的单个输入。
我们可以进一步简化我们的图示。我在这里所做的只是将隐藏层的视觉效果折叠成这个抽象的绿色方框。我们仍然有相同的操作,将一个输入映射到一个输出,即长度为 M 的向量映射到长度为 N 的输出向量。
现在我们能做些什么来尝试将这样的网络应用于时间序列数据或序列数据呢?也许,让我们从一个可能有点朴素的第一步开始。让我们采用这个相同的模型——我在这里只是旋转了它,所以它是从下到上,输入到输出——如果我们多次重复这个过程呢?我们在某个时间步 T(这里是 t0)有我们的输入向量,将其输入到我们的神经网络中,生成一个输出预测。也许我们在多个时间步上都这样做,因为在序列数据中,我们不只有一个时间步,我们有多个单独的时间步,从 t0 到 T1、T2 等等。
我们可以孤立地做这件事:取这些单独时间步的输入,将它们传入模型,并生成一个输出。这里同样是一个简单的函数,我们将由神经网络定义的这个函数应用于时间步 T 的输入,以生成一个预测 y_hat(T)。
但这里的问题是什么?我们知道,在特定时间步的输出向量 y_hat 仅仅是该时间步输入的函数。如果这本质上是序列数据,它之所以是序列很可能是有原因的,因为序列中时间步之间存在某种内在的依赖关系。而以这种方式,我们在预测后续时间步时完全忽略了先前时间步的输入。它们都被孤立地处理。要预测比如时间点 2 的输出,我们很可能可以从时间步 0 和时间步 1 的信息中获益良多。
我们如何规避这个问题?我们怎么可能将这些时间步关联起来呢?从根本上说,我们想要做的是将网络在这些独立时间步的内部计算与来自先前观察到的时间步的计算历史关联起来。我们希望将这些信息随着时间的推移向前传递,在序列处理过程中保持这些信息。
也许我们可以试试看。如果我们能够定义一种方式,将先前时间步神经元的内部状态与后续时间步的计算联系起来呢?也许我们可以让这个更具体些。我们可以定义一个变量,称之为内部状态 H(t),由网络中的神经元维护,并将这个状态在时间步之间传递。我们的目标是,也许要赋予这个内部状态某种“记忆”的概念,即网络先前所做计算的某种记录。
更具体地说,这意味着现在我们在特定时间步 T 的预测 Y_hat,不仅应该依赖于该时间步的输入,还应该依赖于从前一个时间步传递过来的这个状态,即过去的历史和当前的输入。所以这里真正强大的是,我们现在开始更正式地定义这些输出预测之间的关系,这种关系不仅取决于观察到的输入,还取决于过去的这个“记忆”。正如你可以看到并希望开始获得直觉的那样,过去的记忆依赖于先前的输入。所以我们有了某种“循环”(recurrence)的概念,某种在网络进行这些计算时随着时间重复和维持的东西。
因为我们产生的这个输出不仅是当前输入的函数,也是过去状态的函数,我们可以通过所谓的“递归关系”(recurrence relation)来描述这类网络。我们可以用几种不同的方式来可视化它。在这里的右侧,我展示了这些时间步按时间切片展开的样子,我们通过这个变量 H 将这些连接可视化了。但我们也可以在左侧通过这个循环、这个回路来表示它,这告诉我们状态维持着网络内部计算的这种循环日志的概念,这个日志在每个时间步都会更新。
这就是具有循环感的时间序列模型的概念,这确实是所有序列建模中一个非常非常基础的概念,即我们所说的“循环神经网络”(Recurrent Neural Networks, RNNs)。希望这个例子以及从感知机开始的这种构建过程,能让你直观地理解我们如何开始使用这些称为循环神经网络的时间序列模型来建模序列数据。
让我们继续,让我们从这个基础上继续构建,现在开始更正式地了解这些循环神经网络(RNNs)内部的操作。正如我们开始看到的,关键思想是我们有这个内部状态 H(t),并且该状态将在处理序列的每个时间步进行更新。我们可以通过定义我们所谓的“递归关系”来做到这一点。这是一种逐个时间步处理序列的方法。我们希望我们的内部状态 H 不仅是输入的函数,也是先前状态的函数,这样就有某种记忆被传递下去。
为了形式化这一点,单元状态 H(t) 由一个由一组权重 W 参数化的函数定义,该函数不仅依赖于该时间步的输入,也依赖于前一个时间步的先前状态。我们可以递归地在每个时间步应用相同的函数,更新权重,从而在序列处理过程中得到我们单元状态的循环更新。
另一种建立对RNN直觉的方法是,可以从伪代码的角度来思考。这里,我们将通过一个Python伪代码示例来帮助建立这种直觉。假设我们想从头开始构建和传达RNN。我们将定义一个RNN,my_rnn
,并用一个设置为零的隐藏状态来初始化它。现在我们的输入是这个句子片段:“I love recurrent neural”。我们的任务是使用这个句子中的单个词来预测序列中接下来出现的下一个词。
我们要做的是遍历这个句子中的每个词。我们使用我们的RNN接收那个词,接收上一个隐藏状态,预测某个输出,并更新隐藏状态。然后迭代地进行这个过程。通过这个迭代过程,最终我们可以在处理完这些输入后,生成对下一个词的预测。这就是状态更新和输出的基本概念,它是RNN的核心。
再次从下往上梳理一遍:给定我们的输入向量,我们定义一个函数来更新隐藏状态。现在我们只是引入了更多的形式化,使这些权重定义和函数更加明确。但这再次说明,隐藏状态的更新是该时间步输入和前一时间步先前隐藏状态的函数,它更新了我们的隐藏状态。现在我们可以通过获取那个隐藏状态并对其应用加权变换来实际生成一个预测,一个输出向量。这就是RNN如何更新其内部记忆(隐藏状态)以及如何在每个单独时间步产生预测(输出)的核心。
通过将RNN看作这种循环的、循环的视觉效果,我提到我们也可以通过将这些单独的时间片分开并在时间轴上展开它们来表示它。如果我们这样做,我认为直觉可能会变得更加清晰。再次从第一个时间步开始,我们可以一步步展开,T1, T2,一直到 Tt,你可以看到这个内部状态在时间步之间传递。我们生成预测。
我们也可以将形式化的数学带回到这个图表中,现在你可以看到:
- 我们有一个权重矩阵,将输入转换到隐藏状态的计算中 (W_xh)。
- 我们有一个权重矩阵,定义了隐藏状态如何更新自身 (W_hh)。
- 最后,我们有一个权重矩阵,将隐藏状态转换成输出预测 (W_hy)。
重要的是,这些是在每个独立时间步使用的 相同 的权重矩阵。这个、这个和这个权重矩阵在序列的每个时间步都被重用。
现在这告诉了我们如何实际进行这些更新,如何预测输出。但是,要实际训练和学习这个网络,我们需要知道如何定义一个损失(Loss)。正如Alexander介绍的,为了学习或训练一个神经网络,你需要定义某种函数,它告诉你预测实际上离你所寻求的期望行为有多近。对于RNNs,这个概念,完全相同的概念仍然适用。唯一的区别是,我们可以在序列中的每个切片、每个独立的时间步计算损失,并通过对序列中所有时间步的损失求和来得到总损失。
总的来说,这定义了我们所谓的“前向路径”(forward path),即我们如何逐个时间步进行预测,以及如何使用这些预测来计算损失。
我们可以通过一个例子来演示如何自己从头开始,使用像TensorFlow这样的库来实现一个RNN。我们尝试将RNN定义为一个层,将权重矩阵初始化为我们RNN类的属性。并且,重要的是,在这个 call
函数中,我们定义了我在前一张幻灯片中介绍的那个精确的前向操作,即前向传播。正如你所看到的,它包含这两个关键行:
- 第一行是根据输入和先前的隐藏状态更新隐藏状态。
- 第二行是预测输出,它是该时间步隐藏状态的一个变换。
- 我们返回这两个值:预测和那个隐藏状态值。
正如你将看到的,这是一个很好的方式,从我们之前展示的伪代码直觉,过渡到思考代码,再到现在如何可以自己从头定义一个RNN类。
再往前一步,你可以从这个直觉出发,现在学习和理解如何通过在常见的机器学习框架(如TensorFlow和PyTorch)中已经实现的层和模块来操作RNN。在本课程的第一个实验“使用RNN进行序列建模”中,你将获得在任一库中使用这些函数和类的实践经验。
好的,希望现在这能让你直观地理解我们是如何从静态输入、静态输出的第一个例子,构建到当我们开始能够处理序列数据时可以应对的各种任务和新问题类型。无论是接收一个序列产生一个类别标签,还是能够做像下一词预测或下一字符预测这样的事情来实际生成和产生一个序列作为输出。
在后一个例子中,稍微预测一下,这不仅是你们将在我们的软件实验中亲身体验的内容,而且实际上,这种多对多序列建模的概念正是语言模型实际工作方式的支柱。你们今天已经开始理解了其中的一些直觉,并在此基础上继续前进。
问: 隐藏层中的每个节点都与第二个隐藏层中的节点相连吗?RNN是这样看的吗?
答: 问题是关于隐藏层之间的连接是如何定义的。重要的一点是,这些单独的绿色块中的一个(它可以包含多个层,不一定只有一个,可以是很多个)你可以把它看作一个单元(unit)。这个单元内部包含一定数量的层。但重要的是,这个操作基本上是应用于该序列中所有独立时间步的,使用该单元中相同的层和权重集。你在每个步骤计算损失,然后,正如我们将看到的,当你实际更新权重时,是通过获取所有独立时间步的损失,然后在处理完所有时间步之后再进行更新。
问: (补充提问,声音较轻,大意是关于层内连接)
答: 就像在前馈网络中,你可以有连接层与层的权重一样,在一个单元内有多个层的RNN中,也存在定义这些连接的权重。
4. 处理序列数据的挑战与方法
好的,现在让我们思考如何在现实世界中实际操作并应用这种序列建模的概念。序列是丰富的,序列之所以有趣有几个原因。
- 一是因为它们具有极大的可变性。不像图像,你试图让图像有固定的高度和宽度,并且数据集中所有图像的高度和宽度都是固定的。在像语言这样的东西中,序列可以很短,可以很长,也可以介于两者之间。所以我们需要能够处理这种可变性。
- 另一个丰富性和细微差别是,对于依赖时间的事物,你可能遇到存在非常短程交互或依赖的情况,也可能在序列中遇到一开始的某些内容决定了最后某个内容的含义或值的情况。所以这种跨度或长期依赖的概念很关键。
- 从根本上说,存在顺序的概念,我们的模型需要能够很好地表示和推理这种顺序,通过我们采取的方法来实现。
我们将使用这些标准来激发RNN如何能很好地完成这些任务,但也要看到RNN在满足这些序列模型需要满足的现实操作标准方面存在的一些缺点。
为此,我们将通过一个非常非常具体的例子来讲解,这也许现在已经成为了典型的序列建模问题,那就是“预测下一个词”:给定一个词序列,预测接下来出现的词。需要强调的是,这是一项非常重要的任务,因为它不仅优美、简单、直观,而且事实证明,它对于构建我们今天看到的那些非常非常强大的语言模型极其有效——它们正是通过能够预测下一个词这个任务来训练的。
假设我们有这个例句:“This morning I took my cat for a walk”。我们的任务是,给定这组词,我们希望能够预测下一个词。假设我们想构建一个像RNN这样的序列模型来完成这个任务。我们的第一步是什么?(听众回答)把词分成块。好的,假设我们现在已经把句子分成了块或词。我们如何才能真正构建一个神经网络模型来做到这一点呢?(听众回答)将它们向量化。
完全正确。在我们把文本分解成词之后,核心的考虑因素是我们需要一种方法来实际地将它表示给模型。因为请记住,所有的神经网络都只是函数执行器或函数逼近器,它们操作的是数字——向量、矩阵以及表示数字的方式。
所以,如果我们想要一个模型接收一个词输入并预测下一个词输出,这正是我们需要做的。我们不能直接把词语传进去,我们需要一种实际的方法将这些词语表示为数字,以数值表示,以便能够使用神经网络来操作它们。这就是将输入向量化或为神经网络编码语言的概念。这在语言建模以及神经网络和机器学习建模中通常是一个非常非常核心的概念。
我们现在要介绍的解决方案是“嵌入”(embedding)的概念,这是一种将可以以某种形式(如词语)存在的输入,首先转换成一个索引,然后该索引可以映射到一个固定大小的向量。
为了逐步分解这个过程,我们具体该怎么做呢?假设我们有我们的词汇库(vocabulary),即我们可能在所有可能遇到的句子中看到的所有可能词语的集合。我们称之为一个词汇表,一个覆盖了我们可能遇到的所有可能词语的语料库。这个词汇表必须有一个固定的大小。然后我们可以做的是,将这个词汇表中的单个词语映射到一个数字,一个索引。比如,“a”映射到1,“cat”映射到2,以此类推。
然后,现在这些索引为我们提供了一种方式,将那个索引、那个位置,转换成一个向量,并根据该索引查找代表那个词的向量。我将向你展示这具体意味着什么。最后一步是进行这个嵌入操作,即将一个索引映射到一个固定大小的向量。
一种方法是我们可以使用所谓的“独热”(one-hot)或二元编码/嵌入。我在这里所做的是,我们定义了一个固定大小的稀疏向量,它只包含0和一个对应于该词索引位置的1。因此,基于那个索引值,我可以有效地编码词语的身份,并根据该索引查找和回溯出词语是什么。
另一种我可以做的事情是,实际使用一个神经网络层来学习这些词在某个固定长度、较低维度空间中的嵌入。这非常相似,我们只是完成了将那个索引映射到一个编码的操作,使得相似的词最终会落在这个嵌入空间的相似区域。但我们仍然能够使用那个索引来检索我们词语的向量表示。
我认为一个很好的思考这些向量化或嵌入操作的方式是,把它们看作是通过索引来查找这些词语的固定数值表示。这是一个非常非常重要的概念。
现在我们可以为我们的序列做这件事了。我们可以将我们的词语转换成这种向量表示。现在来思考,为什么序列建模困难且复杂?我们可以看一些例子,看看这在现实中是如何体现的。
我们可能会遇到因可变序列长度而产生的复杂性。也许我们有短序列、中等长度序列、长序列。在所有情况下,我们都希望我们的神经网络模型能够持续地跟踪这些依赖关系,以便在最后仍然能够很好地预测下一个词。
这也与能否很好地跟踪和存储序列中的这些长期依赖关系有关。在很多情况下,我们可能需要句子最开头的信息来预测句子最末尾的下一个词。这很重要,因为根据我们如何排列词语,序列的概念可以在我们的预测任务中传达非常不同的语义含义。
所以希望这个语言建模和下一词预测的例子能让你感受到,为什么作为一个神经网络深度学习任务,序列建模可以非常丰富和复杂。
(接续 RNN 部分,训练挑战)
好的,这给了我们一个在现实世界中具体的方法,以及对为什么序列建模丰富且具有挑战性的一些理解。然而,要实际训练像RNN这样的序列模型,我们需要一些特殊的考虑。我们仍然会使用Alexander介绍的那个基本算法——反向传播,但现在我们需要引入一些东西来处理时间依赖性。
就像我们这节课一直在做的那样,让我们从我们开始的基本原理出发。让我们回到我们如何训练前馈模型。我们首先接收输入,通过网络进行一次前向传播,从输入到输出。为了实际训练模型、计算损失和反向传播梯度,我们通过反向传播算法向后进行:通过计算损失相对于模型中每个参数和每个权重的导数。然后我们调整并朝着最小化损失的方向移动这些参数,即我们迈出一步。
在RNN中,我们看到了损失是如何逐个时间步计算的预览。现在,当我们要训练RNN时,我们不仅需要考虑单个损失,还需要考虑所有这些独立时间步的聚合损失。这意味着,我们不是通过单个前馈网络反向传播这些损失值,而是需要跨越这些独立的时间步反向传播误差,这样我们就可以将后期时间步的误差一直传递回开始。
执行此操作的算法被称为“时间反向传播”(Backpropagation Through Time, BPTT),使得误差从序列的最后一直流向最开始。
这在实践中意味着,你可能会有一连串非常重复的计算,比如一个权重矩阵多次重复地与自身相乘,以及网络中激活函数导数的重复使用。这可能会带来一些非常实际的挑战,出于时间考虑,我们不会深入探讨,但需要记住的重要一点是,这些标准的RNN可能有点难以稳定地训练。因为你可能会得到许多大于1的值,将它们相乘,然后梯度就爆炸了。相反,你也可能有许多非常小的值,将它们相乘,然后梯度就消失了,降到非常接近零。
这实际上具有实际影响,因为它意味着,如果我们关心的序列中的长期依赖关系很难被追踪。如果我们的梯度不稳定,要么爆炸要么缩小到几乎为零,我们就无法有效地将后期时间步的梯度传递到早期时间步,从而促使我们的模型保留那些信息。
因此,在文献中和序列建模社区中,已经进行了大量的积极研究,以提出基于RNN的改进架构来尝试解决这个问题。真正的核心概念是,它们为RNN单元本身增加了一些复杂性,有效地添加了额外的功能来尝试选择性地控制传递给隐藏状态更新的信息量。一种非常非常突出的方法是称为LSTM(Long Short-Term Memory,长短期记忆)网络。它在相当一段时间前提出的,但对于此后进行的许多序列建模工作来说一直是非常基础的。
(接续 RNN 部分,应用示例)
为了让大家快速了解一些你们今天将实际动手操作的RNN应用,我想强调这个音乐生成的具体例子。这是一个非常自然地适用于序列建模和循环神经网络架构的问题。因为我们要做的是,假设我们想尝试预测并生成一段新的音乐。一种方法是,我们可以获取一段音乐中的单个音符,然后,与我们看到的预测下一个词的任务非常相似,构建一个模型,给定过去的音符历史,学习预测序列中最可能的下一个音符。
这正是你们今天将在我们的软件实验中要做的事情,你们将能够训练一个RNN模型来生成以前从未存在过的全新音乐。事实上,你们不是唯一尝试过这个的人。这是几年前一个初创公司的例子,他们试图进行音乐生成。他们用古典音乐训练了一个神经网络模型,并测试它来完成作曲家弗朗茨·舒伯特(Franz Schubert)著名的《未完成交响曲》。他们给模型交响曲的前两个乐章,任务是让模型生成对应于第三乐章的音乐。让我们看看能否播放一下。
[播放音乐]
效果相当不错。也许观众中有一些熟悉这部作品的古典音乐爱好者。但我总是很欣赏它,因为它触及了我们正在讨论的关于这些序列模型能力的一些主题。
5. 注意力机制 (Attention Mechanism) 与 Transformer
到目前为止,我们只讨论了这种称为循环神经网络的序列建模架构。我想花点时间来欣赏一下,我们能够理解序列建模的基本原理,并使用RNN构建出达到某些能力,这是非常了不起的。但是,就像任何技术和任何方法一样,RNN也有一些核心局限性。事实上,这些局限性也推动了序列建模中新架构和方法的发展,以及性能更好、解决了部分局限性的改进版RNN。
在思考RNN时,有几点重要的事情需要记住:
- 核心概念,正如我们讨论的,是状态 H(t) 的概念。记住,我们讨论的所有这些神经网络中的概念都操作在数字的向量和矩阵上。同样,RNN的状态是一个固定长度的向量。能够封装到一个固定大小的东西中的信息量是有限的。因此,这带来了我们认为是RNN状态所能容纳信息量的瓶颈。
- 此外,因为RNN是逐个时间步处理信息的,这使得它们很难并行化,即难以同时处理事情。我们有这种固有的序列依赖性。
- 最后,与这两点都相关的是,状态的编码瓶颈可能会限制某些这类循环架构的长期记忆能力。
现在思考我们如何尝试克服这个问题。让我们回到序列建模的基本目标:接收一个输入序列,使用神经网络计算代表这些输入的一些特征或状态,然后能够根据该序列生成预测。
对于RNN,我们说我们将逐个时间步处理,使用循环。但我们也看到,这种按时间步处理序列的固有概念,对RNN的能力最终施加了一些真正的限制。
理想情况下,我们希望能够非常高效地并行处理我们的序列,也许这样我们就可以泛化到长序列并高效地做到这一点,并且还具有我们期望的能够有效跟踪序列中重要依赖关系的属性。
因此,几年前提出的一个问题是:我们是否可以尝试解决序列建模问题,而无需逐个时间步处理数据?也许我们可以消除对循环的需求?也许我们可以通过把所有东西都压缩在一起来做到这一点?忽略这些单独时间步的概念,假设我们将所有输入连接成一个向量,然后将其输入到像前馈模型这样的东西中,并在最后生成一个输出,并希望它是有意义的。
如果我们以这种朴素的、第一种方法来做,是的,我们消除了对循环的需求,所以我们不必一步步处理数据,这很好。但这似乎不太可扩展,因为现在假设我们只是试图使用一个密集网络,那将不会非常高效。而且,我们破坏了所有关于顺序的信息。我们将输入压缩成一个连接起来的向量,这样做就破坏了任何记住先前或稍后出现的内容并将它们相互关联起来的希望。
这激发了一种不同的思考序列建模的方式,即尝试获取序列数据的表示,并定义一种机制,该机制能够自行挑选和查看该信息中相对于其他部分而言重要的部分。
换句话说,我们能否定义一种方法,使得给定一个序列,能够识别并“关注”(attend to)该序列的重要部分?并且,更进一步地,对序列中相互关联的依赖关系进行建模?
这就是一个非常强大的机制——“注意力”(Attention)的核心思想。在2017年,一篇名为《Attention Is All You Need》的论文被提出,介绍了这种机制。所以,如果你听说过像ChatGPT或GPT这样的模型,那个缩写中的 T 代表 Transformer。而 Transformer 是一种神经网络架构,它不仅可以应用于语言数据,还可以应用于其他类型的序列数据。Transformer 的基础机制,使其与众不同之处,就是这种注意力的操作。
因此,我们将在本讲座中讨论那个注意力机制,在后续的讲座中,你将学到更多关于这些 Transformer 如何作为语言模型以及在其他应用中实际应用的知识。我们将真正一步步地分解注意力的核心概念。
好的,让我们开始。
“注意力”本身就是一个信息量很大的词。它意味着我们人类拥有这种固有的能力,去思考一个输入,并自动放大和挑选出那些显著的、重要的特征。让我们从一个图像开始建立我们的直觉。我们如何找出这张图片中什么是重要的?
一种朴素的方法是,我们可以逐个像素地,从左到右,来回扫描,试图计算这些单个像素有多重要的某个值。但显然我们的大脑不是这样运作的。我们能够自动地看这个,并挑选出、关注到重要的部分。
这个问题的第一个部分是能够识别输入中哪些部分是重要的。然后最终,我们想利用这种识别来提取输入中与这些高注意力值相对应的特征、组成部分。
更具体地思考一下,识别要关注的部分这个概念,实际上与“搜索”(search)非常相似。当我们做像搜索这样的事情时,我们提出一个问题,并试图寻找答案。假设你来上这门课,你带着问题:“我如何能更多地了解神经网络、深度学习和人工智能?” 除了来上这门课,你可能做的另一件事是去互联网,找到互联网上所有可用的视频和材料,并尝试进行搜索,找到符合你查询(query)的内容。
假设你去一个巨大的视频数据库,比如YouTube,然后你输入你的查询,你的请求:“深度学习”。这是你的搜索主题。现在,搜索所做的是——假设我们遍历这个数据库中的每一个视频,我们提取一些信息性的精华,一个“密钥”(key),它代表了该视频的核心元素,该视频的描述符。现在我们有了我们的查询和一组密钥。
我们的任务是,好吧,要实际进行搜索,我该怎么做?我想看看我的查询(我搜索的内容)和数据库中的那些密钥(关键指标)之间的匹配程度有多接近。我想看看它们有多相似。
所以我将一步步地做这件事:问我的查询与这些密钥有多相似?
- 第一个例子,一个关于优雅海龟的精美视频——不相似。
- 一个来自我们过去关于深度学习讲座的视频——是的,相似。
- 一个与科比·布莱恩特的后仰跳投相关的密钥——不相似。
现在我们已经识别出了重要的密钥,我们想要关注这个。我们的最后一个任务是实际提取与我们找到的这个相似匹配相关联的“价值”(value)。我们想要提取我们想要关注的特征——视频本身。我们将此称为价值。因为我们的搜索是用一个好的注意力机制实现的,我们为你和你的查询找到了最好的深度学习课程。
这个概念确实是注意力背后的核心直觉,这与注意力操作在像Transformer这样的神经网络中如何工作非常非常相关。
现在让我们回到我们的序列建模问题,我们有一系列词语,我们想预测下一个词。对于这个句子,如果我们一步步分解:
首先,记住我们不想按时间步处理信息。我们已经打破了对循环的需求。我们将一次性输入所有数据。但我们仍然需要一种方法来编码关于顺序的某种概念。所以我们将要做的是,放入一个称为“位置编码”(positional embedding)的嵌入,它有效地为我们提供了一种封装序列中这些元素相对位置的方法。我们不会深入讨论位置编码的细节,但你可以把它看作是一种为我们提供序列中位置某种表示的编码。
现在我们获取那些具有位置意识的编码,并进行搜索操作。我们将该搜索操作付诸实践,以提取我们称之为“查询”(Query)、“密钥”(Key)和“价值”(Value)的三组矩阵,就像我之前介绍的那样。我们这样做的方法是:获取位置嵌入,并将其视为以位置感知的方式表示我们的序列,然后我们使用一个神经网络层来产生这些单独的矩阵:查询、密钥和价值。
需要记住的重要一点是,在这个自注意力(self-attention)机制中,是 相同 的位置嵌入被重复使用,但是是 不同 的神经网络层(权重)产生了查询、密钥和价值矩阵各自不同的值。这意味着它们可以有效地捕捉不同的信息,正如我们将看到的。
再次,我们搜索操作的下一步是找出查询与密钥的相似度。这正是我们接下来要用注意力机制做的事情。要做到这一点,记住这些是数值向量或矩阵,所以我们需要一种数学方法来计算两组数值特征之间的相似度。
假设我们有两个向量:我们的查询向量和我们的密钥向量。在数学上,使用线性代数,我们可以使用“点积”(dot product)来衡量这些向量在空间中的相似度。它告诉我们它们彼此有多接近。我们可以对其进行缩放。这给了我们一个非常具体的相似性度量,捕捉了查询和密钥之间的相似性。
同样的原理现在也可以应用于矩阵:点积和缩放,这给了我们一个相似性度量。
现在让我们思考一下这个相似度计算实际上意味着什么。记住,我们试图理解输入的这些组成部分是如何相互关联的,句子的哪些部分对彼此是重要的,以及对于传达整个句子的语义含义是重要的。
所以如果我们有这个例子:“He tossed the tennis ball to serve”。假设我们已经计算了我们的查询和密钥矩阵,应用了缩放。然后我们可以应用一个称为Softmax的函数,基本上将这些值压缩到0和1之间。现在这给了我们一个矩阵,它给出了序列中各个组成部分之间相互关联程度的相对权重。
直观上,你可以认为更相似的事物具有更高的注意力权重,更相关的事物具有更高的注意力权重,而不太相关的事物具有较低的注意力权重。所以在这里,在这个例子中,“tossed”和“ball”得分很高,“tennis”和“ball”得分很高,等等。这给了我们注意力权重或注意力矩阵。
最后一步是,现在使用那个相对权重来实际提取出我们关心的那些重要特征。我们在这里做的是,取我们的价值矩阵,乘以我们的注意力权重。这给了我们一个在该输入空间上的特征集输出,该输出反映了序列中相互关联的相对元素。
这确实是注意力如何工作的核心。这对我来说真的非常非常优美和引人注目,因为这种机制为我们提供了一种非常自然的方式来提取和关注输入中彼此相对非常重要的特征。
在架构上,我们现在如何将其构建成类似Transformer的东西呢?(稍等一下)我们可以再次通过获取输入,计算这些位置编码。我们定义这些神经网络层来计算查询、密钥和价值。然后我们可以计算查询和密钥之间的相对权重,这是一个表示点积的矩阵乘法,加上缩放和Softmax。然后使用价值矩阵来提取具有高注意力分数的特征。
这些就是现在定义了这些“注意力头”(attention heads)的核心操作,它们确实是像Transformer这样的架构的核心组成部分。
正如我们已经提到并意识到的,这确实是... 注意力是Transformer的基础构建模块。而且,我想有些问题也提到了,对吧,一个Transformer架构不必只由一个注意力块定义。你实际上可以将多个注意力头堆叠在一起,现在基本上可以增加你网络的容量,并提取出不同集合的特征和更复杂的特征集。
再次,在这个非常直观的例子中,也许你有一个包含三个注意力头的网络。如果你进去检查——再次,这是一个直观的例子——假设你去检查这些注意力头各自的值,也许你可以获得一些关于网络关注输入的哪些不同特征或部分的可解释性。
那么,有哪些实际应用案例,注意力在近年来真正推动和改变了什么呢?
不仅仅是在语言处理领域,Transformer和自注意力才真正带来了巨大的进步。虽然情况确实如此,但注意力/Transformer背后的机制和架构具有很强的通用性。所以正如你将看到的:
- 自然语言是Transformer真正取得巨大成功的领域之一,你们不仅将在讲座中,而且在一个全新的关于大型语言模型(LLM)的软件实验中获得实践经验。
- 我们也将在我们的一个客座讲座中看到一些关于注意力和序列建模如何扩展到生物序列的内容。
- 事实上,在一些可能看起来不是序列的东西,比如图像或计算机视觉中,有一类称为Vision Transformers的模型,现在在处理图像数据方面也变得非常非常强大。
6. 总结与后续安排
总结一下,并结束今天的讲座。希望大家已经体会到序列建模作为一系列我们可以考虑的问题和事物是多么丰富。
- 我们看到了RNNs是如何工作的。
- 我们看到了如何通过循环的概念来建立对RNNs的直觉。
- 我们看到了它们如何通过反向传播进行训练。
- 你们将有机会亲身体验为音乐生成构建RNNs。
- 最后,我们以讨论自注意力和Transformer作为一种无需按时间步、循环地处理即可建模序列的方法结束。
请继续关注更多关于LLM的内容,包括实践操作和更多讲座。
好了,以上就是今天讲座的部分。现在我们可以利用剩余的时间进行开放的答疑(Office Hours)和讨论,关于你们可能有的任何遗留问题或与讨论相关的评论。
我们还想提醒大家注意软件实验,它们现在已经在课程网站上链接的GitHub上可用。完成软件实验的说明都在那里。我们提供了TensorFlow和PyTorch两种选项,所以希望你们能有一个有趣的机会去完成它们并进行操作。
最后,我想我们慷慨的招待会主持人John好像离开了... 但紧接着之后,将在街对面的 One Kendall Square 举办一个现场招待会,以启动本课程。届时将提供食物,并特别感谢 John Werner 和 Link Ventures... 哦,他还在,在后面上面。谢谢你,John,慷慨地主持。