自然语言处理 (NLP) 的快速发展已取得重大进展,其中最具变革意义的莫过于大型语言模型 (LLM) 的出现。这些模型重新定义了机器理解和人类语言生成的可能性。许多 LLM 成功的关键是编码器-解码器架构,该框架使机器翻译、文本摘要和对话式 AI 等任务取得了突破。
编码器-解码器架构的引入是为了解决序列到序列(Seq2Seq)问题,标志着处理序列数据的重大突破。
本博客的重点是处理 Seq2Seq 数据问题。
序列建模用例涉及输入、输出或两者由数据序列(例如单词或字母)组成的问题。
考虑一个非常简单的问题,即预测电影评论是正面的还是负面的。在这里,我们的输入是单词序列,输出是 0 到 1 之间的单个数字。如果我们使用传统的 DNN,那么我们通常必须使用 BOW、Word2Vec 等技术将输入文本编码为固定长度的向量。但请注意,这里单词序列没有保留,因此当我们将输入向量输入模型时,它不知道单词的顺序,因此它缺少有关输入的一个非常重要的信息。
因此,为了解决这个问题,RNN 应运而生。本质上,对于任何具有可变特征数量的输入 X = (x₀, x₁, x₂, … xₜ),在每个时间步骤中,RNN 单元都会将项目/标记xₜ作为输入并产生输出hₜ,同时将一些信息传递到下一个时间步骤。这些输出可以根据手头的问题来使用。
电影评论预测问题是一个非常基本的序列问题的示例,称为多对一预测。有不同类型的序列问题,可以使用此 RNN 架构的修改版本。
序列问题大致可以分为以下几类:
每个矩形都是一个向量,箭头代表函数(例如矩阵乘法)。输入向量为红色,输出向量为蓝色,绿色向量保存 RNN 的状态(稍后会详细介绍)。
从左到右:
(1)没有 RNN 的 Vanilla 处理模式,从固定大小的输入到固定大小的输出(例如图像分类)。
(2)序列输出(例如,图像字幕获取图像并输出一个单词句子)。
(3)序列输入(例如,情绪分析,其中给定的句子被分类为表达积极或消极情绪)。
(4)序列输入和序列输出(例如机器翻译:RNN 读取一个英文句子,然后输出一个法文句子)。
(5)同步序列输入和输出(例如,视频分类中我们希望标记视频的每一帧)。
请注意,在每种情况下都没有对长度序列的预先指定的限制,因为递归变换(绿色)是固定的,可以根据需要多次应用。
— Andrej Karpathy,《循环神经网络的不合理有效性》
序列在我们的世界中无处不在——在语言、语音、金融时间序列和基因组数据中都能找到——其中元素的顺序至关重要。与固定大小的数据不同,序列在理解、预测和生成信息方面提出了独特的挑战。传统的深度神经网络 (DNN) 在具有固定维度输入和输出的任务上表现良好,但在机器翻译等序列到序列的任务中表现不佳,因为输入和输出的长度各不相同且不一致。因此,需要专门的模型来有效处理序列数据。
研究论文:利用神经网络进行序列到序列学习
动机
编码器-解码器架构相对较新,并于 2016 年底被采用为 Google 翻译服务的核心技术。它构成了高级序列到序列模型(如注意力模型、GTP 模型、Transformers 和 BERT)的基础。因此,在转向高级机制之前,了解它非常重要。
关键组件
为了说明这个概念,我们以神经机器翻译 (NMT) 为例。在 NMT 中,输入是一串逐个处理的单词序列,输出是相应的单词序列。
任务:预测英语句子的法语翻译。
示例:
输入:英语句子:“很高兴见到你”
输出:法语翻译:“ ravi de vous rencontrer ”
使用的术语:
- 输入句子“ nice to meet you ”将被称为X或输入序列。
- 输出句子“ ravi de vous rencontrer ”被称为Y_true或目标序列,这是我们想要模型预测的基本事实。
- 模型的预测句子是Y_pred,也称为预测序列。
- 英语和法语句子中的每个单词都称为*token*。
因此,给定输入序列“很高兴见到你”,模型的目标是预测目标序列 Y_true,即“ ravi de vous rencontrer ”。
在非常高的层次上,编码器-解码器模型可以被认为是两个块,编码器和解码器通过一个向量连接,我们将其称为“上下文向量”。
Seq2Seq模型是一个基于RNN的模型,专门用于翻译、摘要等任务,以一个序列作为输入,以一个序列作为输出。
这是一个 Seq2Seq 模型,可将英文句子“I am a student.”翻译为法语“Je suis étudiant”。左侧的橙色矩形表示编码器,右侧的绿色矩形表示解码器。编码器接收输入句子(“I am a student.”)并输出上下文向量,而解码器接收上下文向量(和 token <sos>)作为输入并输出句子(“Je suis étudiant.”)。
就架构而言,它非常简单。该模型可以被认为是两个 LSTM 单元,它们之间有一些连接。这里的主要内容是我们如何处理输入和输出。我将逐一解释每个部分。
编码器部分是一个 LSTM 单元。它会随时间推移输入序列,并尝试封装所有信息并将其存储在其最终内部状态hₜ(隐藏状态) 和cₜ(单元状态)中。然后,内部状态会传递到解码器部分,解码器部分会使用该部分尝试生成目标序列。这就是我们之前提到的“上下文向量”。
编码器部分每个时间步的输出全部被丢弃
注意:上图是 LSTM/GRU 单元在时间轴上展开时的样子。即,它是单个 LSTM/GRU 单元,在每个时间戳上获取一个单词/标记。
在论文中,他们使用 LSTM 代替经典 RNN,因为它们在处理长期依赖关系方面效果更好。
给定一个输入序列:
编码器按顺序处理每个元素:
在哪里:
3. 上下文向量:在处理整个输入序列之后,最终的隐藏状态hTx 变成上下文向量c:
因此,在读取整个输入序列后,编码器将内部状态传递给解码器,这是输出序列预测的开始。
解码器块也是一个 LSTM 单元。这里要注意的主要一点是,解码器的初始状态(h₀, c₀)设置为编码器的最终状态(hₜ, cₜ)。它们充当“上下文”向量,并帮助解码器生成所需的目标序列。
现在解码器的工作方式是,它在任何时间步骤t的输出应该 是目标序列/Y_true 中的tᵗʰ单词(“ravi de vous rencontrer”)。为了解释这一点,让我们看看每个时间步骤会发生什么。
在时间步 1
在第一个时间步中馈送到解码器的输入是一个特殊符号“<START>”。这用于表示输出序列的开始。现在解码器使用此输入和内部状态(hₜ, cₜ)在第 1 个时间步中产生 输出,该输出应该是目标序列中的第一个单词/标记,即“ravi”。
在时间步 2
在时间步骤 2 中,第一个时间步骤的输出“ravi”被作为输入输入到第二个时间步骤。第二个时间步骤的输出应该是目标序列中的第二个单词,即“de”
类似地,每个时间步骤的输出都会作为下一个时间步骤的输入。这个过程一直持续到我们得到“<END>”符号,这也是一个用于标记输出序列结束的特殊符号。解码器的最终内部状态将被丢弃。
请注意,这些特殊符号不一定只是“ <START>”和“ <END>” 。它们可以是任何字符串,只要它们不存在于我们的数据语料库中,这样模型就不会将它们与任何其他单词混淆。在论文中,他们使用了符号“ <EOS>” ,方式略有不同。我稍后会对此进行更多讨论。
注意:上面提到的过程是理想解码器在测试阶段的工作方式。但在训练阶段,需要稍微不同的实现,以使其训练得更快。我在下一节中解释了这一点。
解码器生成输出序列:
使用上下文向量c:
2. 输出生成:对于输出序列中的每个时间步骤t :
和,
在哪里:
在了解细节之前,我们首先需要对数据进行矢量化。
我们拥有的原始数据是
现在我们将特殊符号“<START>”和“<END>”放在目标序列的开始和结束处
接下来,使用独热编码 (ohe) 对输入和输出数据进行矢量化。让输入和输出表示为
其中 xi 和 yi 分别表示输入序列和输出序列的 ohe 向量。它们可以表示为:
对于输入 X
‘nice’ → x1 : [1 0 0 0]
‘to’ → x2 : [0 1 0 0 ]
‘meet’ →x3 : [0 0 1 0]
‘you’ → x4 : [0 0 0 1]
对于输出 Y_true
‘<START>’ → y0_true : [1 0 0 0 0 0]
‘ravi’ → y1_true : [0 1 0 0 0 0]
‘de’ → y2_true : [0 0 1 0 0 0]
‘vous’ → y3_true : [0 0 0 1 0 0]
‘rencontrer’ → y4_true : [0 0 0 0 1 0]
‘<END>’ → y5_true : [0 0 0 0 0 1]
注意:我使用这种表示是为了更容易解释。术语“真实序列”和“目标序列”都用于指代我们希望模型学习的同一句话“ravi de vous rencontrer”。
编码器的工作方式在训练和测试阶段都相同。它逐个接受输入序列中的每个标记/单词,并将最终状态发送到解码器。其参数使用反向传播随时间更新。
与编码器部分不同,解码器在训练和测试阶段的工作方式有所不同。因此,我们将分别介绍两者。
为了训练我们的解码器模型,我们使用一种称为“教师强制”的技术,其中我们将前一个时间步的真实输出/标记(而不是预测的输出/标记)作为当前时间步的输入。
为了解释,让我们看一下训练的第一次迭代。在这里,我们将输入序列输入到编码器,编码器对其进行处理并将其最终内部状态传递给解码器。现在对于解码器部分,请参阅下图。
在继续之前,请注意,在解码器中,在任何时间步长t,输出yt_pred是使用 Softmax 激活函数生成的输出数据集中整个词汇表的概率分布。选择具有最大概率的标记作为预测词。
例如,参考上图,y1_pred = [0.02 0.12 0.36 0.1 0.3 0.1] 告诉我们,我们的模型认为输出序列中第一个标记是 '<START>' 的概率为 0.02,'ravi' 为 0.12,'de' 为 0.36,依此类推。我们将预测词视为概率最高的词。因此,这里预测的单词/标记是'de',概率为 0.36
At time-step 1
单词 '<START>' 的向量 [1 0 0 0 0 0] 被用作输入向量。现在我希望我的模型预测输出为 y1_true=[0 1 0 0 0 0] ,但是因为我的模型刚刚开始训练,它会输出一些随机的东西。让时间步 1 的预测值为 y1_pred=[0.02 0.12 0.36 0.1 0.3 0.1] ,这意味着它预测第一个标记是'de'。现在,我们是否应该在时间步 2 使用这个 y1_pred 作为输入?我们可以这样做,但在实践中,我们发现这会导致收敛速度慢、模型不稳定和技能差等问题,如果我们仔细想想,这是很合乎逻辑的。
因此,引入了教师强制来纠正这个问题。我们将前一个时间步的真实输出/标记(而不是预测输出)作为当前时间步的输入。这意味着时间步 2 的输入将是 y1_true=[0 1 0 0 0 0] 而不是 y1_pred。
现在,时间步骤 2 的输出将是某个随机向量 y2_pred。但在时间步骤 3,我们将使用输入 y2_true=[0 0 1 0 0 0] 而不是 y2_pred。同样,在每个时间步骤,我们将使用前一个时间步骤的真实输出。
最后,根据每个时间步的预测输出计算损失,并将误差随时间反向传播以更新模型的参数。使用的损失函数是目标序列/ Y_true和预测序列/ Y_pred之间的分类交叉熵损失函数,使得
解码器的最终状态被丢弃。
在实际应用中,我们没有 Y_true,只有 X。因此,我们无法使用在训练阶段所做的操作,因为我们没有目标序列/Y_true。因此,当我们测试模型时,前一个时间步的预测输出(而不是与训练阶段不同的真实输出)将作为当前时间步的输入。其余的都与训练阶段相同。
假设我们已经训练了模型,现在我们在训练它的单个句子上测试它。现在如果我们很好地训练了模型,而且也只在一个句子上训练,那么它应该表现得几乎完美,但为了解释起见,假设我们的模型没有得到很好的训练或部分训练,现在我们测试它。让我们用下面的图表来描述这个场景
At time-step 1
y1_pred = [0 0.92 0.08 0 0 0] 表示模型预测输出序列中的第一个标记/单词为“ravi”,概率为 0.92,因此现在在下一个时间步,这个预测的单词/标记将仅用作输入。
At time-step 2
第一个时间步预测的单词/标记“ravi”在此处用作输入。在这里,模型以 0.98 的概率预测输出序列中的下一个单词/标记是“de”,然后在时间步 3 中将其用作输入
并且在每个时间步骤中重复类似的过程,直到到达“<END>”标记
更好的可视化方式如下:
因此,根据我们训练的模型,测试时的预测序列是“ravi de rencontrer rencontrer”。因此,尽管模型在第 3 次预测上是错误的,但我们仍然将其作为输入输入到下一个时间步骤。模型的正确性取决于可用数据的数量以及训练程度。模型可能会预测错误的输出,但尽管如此,相同的输出只会在测试阶段输入到下一个时间步骤。
我之前没有提到的一个重要细节是,编码器和解码器都通过嵌入层处理输入序列。此步骤降低了输入词向量的维数,因为独热编码向量在实践中往往非常大。嵌入向量提供了更高效、更有意义的单词表示。对于编码器,这可以通过嵌入层如何压缩词向量维度来说明,例如,将其从 4 减少到 3。
该嵌入层可以像 Word2Vec 嵌入一样进行预训练,也可以使用模型本身进行训练。
该图演示了模型如何使用嵌入和循环层将输入序列转换为目标序列。
这种架构有两个主要缺点,都与长度有关。
此外,我们还有注意力模型和 Transformers 等模型,用于处理更为稳健和冗长的句子。
嵌入层将输入标记转换为密集的向量表示,从而允许模型学习输入序列中的单词或标记的有意义的表示。
通过使用可训练的嵌入层并探索预训练的词嵌入或上下文嵌入等技术,我们可以丰富输入表示,使模型能够更有效地捕获细微的语义和句法信息。这种增强有助于更好地理解和生成序列数据。
LSTM 是循环神经网络 (RNN) 的变体,以能够捕获序列数据中的长距离依赖关系而闻名。加深 LSTM 层可使模型学习输入和输出序列的分层表示,从而提高性能。
增加 LSTM 层的深度并结合残差连接或层规范化等技术有助于缓解梯度消失等问题并促进更深层网络的训练。这些增强功能使模型能够学习数据中更复杂的模式和依赖关系,从而更好地生成和理解序列。
事实证明,在机器翻译中反转输入序列(例如从英语到印地语或从英语到法语的转换)在某些情况下可以提高模型性能,因为它有助于捕捉长距离依赖关系并缓解梯度消失问题。
然而,其有效性可能因语言特征和数据集复杂度而异,并且可能无法在所有场景中持续提高性能。需要仔细评估和实验来确定反转输入序列是否对特定任务和数据集有益。
我们现在了解了编码器解码器的概念。现在,如果我们阅读 Ilya Sutskever 撰写的著名研究论文“使用神经网络进行序列到序列学习”,那么我们将很好地理解该论文的概念。下面我总结了论文的内容:
我们可以在编码器-解码器架构中使用 CNN、RNN 和 LSTM 来解决不同类型的问题。使用不同类型的网络组合可以帮助捕获数据输入和输出序列之间的复杂关系。以下是可以使用 CNN、RNN、LSTM、Transformer 等的不同场景或问题示例:
在编码器-解码器架构中使用不同类型的神经网络(如 CNN、RNN、LSTM 等)时,需要牢记一些限制:
以下是编码器-解码器神经网络架构的一些实际应用: