Datewhale组队学习
为什么我们需要 Seq2Seq ?
历史背景
首先我们要知道,在Seq2Seq出现之前,我们是如何处理输入输出数据的
在 Seq2Seq 框架提出之前,深度神经网络在图像分类等问题上取得了非常好的效果。
在其擅长解决的问题中,输入和输出通常都可以表示为固定长度的向量,如果长度稍有变化,会使用填充(Padding)等操作,其目的是将序列数据转换成固定长度的格式,以便于输入到需要固定长度输入的神经网络中。
比如,我们有两个序列是输入数据:[1, 1, 4]和[1, 9, 1, 9]
但模型只能处理长度为6的序列
那就需要先对两个序列做填充:
[1, 1, 4] -> [1, 1, 4, 0, 0, 0]
[1, 9, 1, 9] -> [1, 9, 1, 9, 0, 0]
才能正确地输入模型
如果要类比的话,这种填充操作有些类似以下概念:
- 离心机配平后才能开机(除非你喜欢艺术)
- PC历史上出现过一种RDRAM内存,必须一次插满偶数个内存槽。如果你只买了一条内存条,还需要再买一条“空内存条”补到主板上,以防止信号反射。(空内存条也不便宜)
- 一把手枪弹匣有10发子弹容量,但规定必须装满才能发射,而你只有3发子弹。所以你找来了7发塑料假子弹先塞进去,再放3发真子弹,这样就能开枪了。
填充操作的缺陷
这种填充操作当然不是没有代价的。比如第一个序列[1, 1, 4],我明明想处理的是[1, 1, 4],为什么非要输入[1, 1, 4, 0, 0, 0]不可?这样一来,如果我本来就想输入[1, 1, 4, 0, 0, 0],那岂不是这两个完全不同的序列,在模型看起来却完全一致吗?
为了解决这个问题,我们还需要引入一个掩码的概念。比如对于[1, 1, 4]:
原始序列:[1, 1, 4]
填充序列:[1, 1, 4, 0, 0, 0]
掩码序列:[1, 1, 1, 0, 0, 0](把原始序列的内容标记为1,后续填充的位置标记为0)
这样就解决了[1, 1, 4]和[1, 1, 4, 0, 0, 0]的趋同问题,因为此时他们有不一样的掩码。
类比到我们前文手枪的例子:
要如何区分枪里到底是10颗真子弹,还是3颗真7颗假呢?我们可以提前告诉枪手,第1、2、3颗是真的,打完就可以停手了。这个“哪些子弹是真子弹”的信息,就是掩码
这样的操作逻辑一定程度上合理,但明显可以更合理——为什么一定要填充呢?
所以,Seq2Seq模型应运而生。
Seq2Seq 的巧妙设计
前面提到的填充操作之所以会存在,是因为模型的输入和输出表示为固定长度的向量,我们为了让数据正确进到模型,就需要对输入数据做填充和掩码的预处理。
而Seq2Seq模型,即“序列到序列”,提供了一种直接输入任意长度序列,并输出期望序列的方式。模型直接能够读取一个长度可变的向量,无需进行预处理。
这是如何实现的呢?
其实非常简单:既然传统模型只能接受固定长度向量,那我们只需要在模型中加一步将任意长度的向量都转化为一个固定长度(维度)的向量,再拿这个定长向量去进行后续操作,不就好了吗?
甜菜!
Seq2Seq 的缺点
虽然Seq2Seq的处理方式很巧妙,但是不可否认的是,它也有一些问题没有解决。
首先,Seq2Seq模型的本质,还是最终生成了一个固定长度的向量。那这个向量能表达的意思的复杂程度,就严重受限于向量的长度(或者说维度)。
如何理解这个问题?
比如你和朋友有个约定,用一种特殊的交流方式传递信息。
你们都买了一套12色水彩笔,规定不同的颜色搭配有着不同的意思。不过,受限于只有12色,所以你们只能表达出一些简单的意思,比如今天老地方见
,或者下课之后一起去打球
。
你们对这种交流方式极其满意。
班里的两个富哥发现了你们的游戏,并决定模仿。他们一人买了一桶128色水彩笔!复杂的颜色可以让他们表达出2024年12月31日,我们开车一起去xx景点跨年,晚上不通宵到6点谁也不许走
这种复杂的语义。而你和朋友看着自己的12色水彩,欲哭无泪。
另一个问题,Seq2Seq模型具有良好的模块化特性,能够与卷积神经网络(CNNs)、循环神经网络(RNNs)等神经网络架构无缝集成。这本来是好事。
但是,由于循环神经网络(RNN)的固有特性,Seq2Seq模型在处理长序列时存在短期记忆限制,难以有效捕获和传递长期依赖性。其实这口锅应该让RNN来背
为什么会出现这种问题呢?
假设你在阅读一本侦探小说,开头提到了一些重要的信息:“罪犯的外套上有一个红色的线头。”但是在 300 页后,侦探揭开真相时需要用到这个线索。如果你忘了“红色线头”这个细节,可能就无法理解故事的结局。
在机器学习中,这个“开头的信息”对应的是序列的早期数据,“故事结局的推理”对应的是模型对后续步骤的预测。如果模型忘了早期的重要信息,预测结果就可能出现错误。
而 RNN 是一个时间步接一个时间步地处理序列数据,每次都将当前输入和之前的记忆(隐藏状态)结合起来,然后输出一个新的隐藏状态。这样会出现以下几点问题:
- 信息传递过程的逐步稀释:
早期的信息在隐藏状态中一层层传递,像是一瓶颜料滴入流水,逐渐被稀释。如果序列太长,早期信息可能会被完全“冲淡”。- 梯度消失问题:
模型通过反向传播优化参数时,早期时间步的梯度(学习信号)会因为连乘关系而指数级减小,导致模型难以学会捕捉这些早期时间步的信息。- 隐藏状态容量有限:
RNN 的隐藏状态是一个固定大小的向量,比如 256 维,但长序列的信息可能需要更大的容量才能完整存储,这就导致了一种“信息拥挤”的现象。
最后,Seq2Seq模型还存在一个暴露偏差(Exposure Bias)问题。在Seq2Seq模型的训练过程中,经常采用“teacher forcing”策略,即在每个时间步提供真实的输出作为解码器的输入。然而,这种训练模式与模型在推理时的自回归生成模式存在不一致性,导致模型在测试时可能无法很好地适应其自身的错误输出。
如何理解这句话呢?这需要借助一个理解来比喻Seq2Seq模型的训练过程:
- 训练阶段(Teacher Forcing)
就像你在学习开车时,有一位教练坐在副驾驶,一直告诉你前方的道路情况(真实的目标输出),比如“前方是直路”“前方有个右转弯”,你只需要按教练的指令操作方向盘,几乎不会出错。- 推理阶段(自回归生成)
当你独自开车时,突然被蒙上了眼睛(没有真实的目标输出),只能靠自己上一秒的操作和记忆来判断接下来的路该怎么走。如果前一秒稍微偏了方向,后续可能就越走越偏,甚至撞车。
Encoder-Decoder模型是什么
与Seq2Seq模型的关系
Seq2Seq模型,指的是一种“序列到序列”的处理思路。而Encoder-Decoder模型,是实现这种思路的具体方式。
如果将Seq2Seq比作“健康的生活需要多吃水果”,那Encoder-Decoder就是:
星期 | 水果种类 | 每日摄入量 |
---|---|---|
周一 | 苹果 + 猕猴桃 | 200克 + 150克 |
周二 | 香蕉 + 蓝莓 | 150克 + 100克 |
周三 | 橙子 + 草莓 | 200克 + 150克 |
周四 | 葡萄 + 梨 | 150克 + 200克 |
周五 | 芒果 + 火龙果 | 200克 + 150克 |
周六 | 桃子 + 柚子 | 150克 + 200克 |
周日 | 哈密瓜 + 樱桃 | 200克 + 150克 |
编码器Encoder在做什么
简单来说,Encoder是一个RNN,里面包含很多堆叠起来的循环单元(如 LSTM 或 GRU)。
当我们输入一个序列,比如 “今天吃什么好” ,输入的句子会先被分解为一个序列:
- 输入序列:
[今天, 吃, 什么, 好]
在实际模型中,每个词会被转换为对应的词向量(embedding),比如:
今天
→ 向量[0.5, 0.1, 0.3, ...]
吃
→ 向量[0.8, 0.2, 0.4, ...]
什么
→ 向量[0.3, 0.7, 0.9, ...]
好
→ 向量[0.6, 0.4, 0.2, ...]
这些向量是输入到编码器的内容。
假设编码器是一个 两层 LSTM,它会一层一层地处理输入。每个时间步的处理过程如下:
时间步 1:处理“今天”
- 输入词向量:
[今天]
- 初始隐藏状态:随机初始化为
[0, 0, 0, ...]
- 输出隐藏状态:
h1 = [0.6, 0.2, 0.1, ...]
解释:编码器初步理解了“今天”的信息,并把它存储在隐藏状态
h1
中。- 输入词向量:
时间步 2:处理“吃”
- 输入词向量:
[吃]
- 隐藏状态:
h1
(包含“今天”的信息) - 输出隐藏状态:
h2 = [0.7, 0.3, 0.4, ...]
解释:编码器在隐藏状态中同时记录了“今天”和“吃”的信息。
- 输入词向量:
时间步 3:处理“什么”
- 输入词向量:
[什么]
- 隐藏状态:
h2
(包含“今天”和“吃”的信息) - 输出隐藏状态:
h3 = [0.8, 0.5, 0.6, ...]
解释:编码器进一步整合了“今天、吃、什么”的上下文信息。
- 输入词向量:
时间步 4:处理“好”
- 输入词向量:
[好]
- 隐藏状态:
h3
(包含“今天、吃、什么”的信息) - 输出隐藏状态:
h4 = [0.9, 0.7, 0.8, ...]
解释:最终隐藏状态
h4
汇总了整个句子的上下文信息。- 输入词向量:
h4,就是 “今天吃什么好” 这句话的 意思,专业一些叫 语义。
解码器Decoder又做了什么
假设我们在这里是想实现一个中英互译,输入是中文 “今天吃什么好” ,输出是英文翻译。
解码器接收上下文向量 h4
(编码器输出)并开始生成目标序列 “What should I eat today?”。
解码器的生成过程是逐步进行的,每个时间步生成一个单词。这里使用 RNN(如 LSTM 或 GRU)来逐步生成目标序列。在解码器开始生成序列之前,它的参数(权重和偏置)已经被初始化(也就是预先训练好了)。
权重(Weights):连接不同神经网络层的参数,它们决定了层与层之间信息的传递方式。决定了解码器如何将输入(上下文向量、前一步的输出)映射到当前的输出。
偏置(Biases):在神经网络层中加入的固定值,用于调节激活函数的输出。帮助解码器对模型的输出进行调整,使其能够灵活地生成更符合目标语言规律的翻译。
随后,解码器开始基于h4
这个语义来生成目标序列(翻译后的英文)
时间步 1:生成第一个单词 "What"
- 解码器接收到上下文向量
h4
(包含了整个中文句子的理解)作为输入。 - 解码器根据当前输入(
h4
)和其隐藏状态h0
(由h4初始化来的)预测第一个单词。 - 输出:
What
(解码器预测的第一个单词)。
类比:翻译官开始翻译时,首先从大意(上下文向量)出发,决定翻译的第一个单词。
- 解码器接收到上下文向量
时间步 2:生成第二个单词 "should"
- 解码器将上一步生成的单词
What
作为当前时间步的输入。 - 结合上下文向量
h4
和前一步的隐藏状态,解码器生成第二个单词。 - 输出:
should
(解码器预测的第二个单词)。
类比:翻译官根据翻译的第一个单词(
What
)和对整句话的理解(h4
),生成第二个单词。- 解码器将上一步生成的单词
时间步 3:生成第三个单词 "I"
- 解码器将上一步生成的单词
should
作为当前时间步的输入。 - 根据上下文向量
h4
和前一步的隐藏状态,解码器生成第三个单词。 - 输出:
I
(解码器预测的第三个单词)。
类比:翻译官继续根据上下文和前一步的翻译来生成下一个单词。
- 解码器将上一步生成的单词
时间步 4:生成第四个单词 "eat"
- 解码器输入前一个单词
I
。 - 结合上下文向量
h4
和前一步的隐藏状态,解码器生成第四个单词。 - 输出:
eat
(解码器预测的第四个单词)。
类比:翻译官根据已翻译的单词
What should I
以及对中文句子的理解(h4
)生成下一个单词。- 解码器输入前一个单词
时间步 5:生成第五个单词 "today?"
- 解码器输入前一个单词
eat
。 - 结合上下文向量
h4
和前一步的隐藏状态,解码器生成第五个单词。 - 输出:
today?
(解码器预测的第五个单词)。
类比:翻译官最终将最后的单词“今天”翻译成英文,并加上疑问符号“?”。
- 解码器输入前一个单词
Encoder-Decoder 的应用
我们前文提到,Encoder-Decoder是Seq2Seq的具体实现。所以,只要是能转化为序列的数据,都可以作为其应用场景。视频、音频、图像、文字,任何可以被编码成序列数据的实体都能够被Encoder-Decoder结构处理。
- 机器翻译、对话机器人、诗词生成、代码补全、文章摘要(文本 – 文本)
- 语音识别(音频 – 文本)
- 图像描述生成(图片 – 文本)
- 图片转视频(图片 - 视频)
- 给视频配音(视频 - 音频)
有太多可以想到的应用场景了!
Encoder-Decoder 的缺陷
其实也就是上面提过的,Seq2Seq模型的最经典的缺点:固定长度的向量,无法表达出复杂的信息。就像12色的水彩笔无法表达2024年12月31日,我们开车一起去xx景点跨年,晚上不通宵到6点谁也不许走
一样。
这样的操作逻辑一定程度上合理,但明显可以更合理——为什么一定要固定长度呢?
这就是Attention机制带来的翻天覆地的大变革了。
Attention机制
Attention 解决定长向量问题的方法
Attention:Seq2Seq,定长的能力是有极限的,我从短暂的人生当中学到一件事,越是固定维度,就越会发现定长向量的能力是有极限的,除非超越定长。
Seq2Seq:你到底想说什么。
Attention:所以我不做定长辣!Seq2Seq!
Attention 模型的特点是 Encoder 不再将整个输入序列编码为固定长度的“向量C” ,而是编码成一个向量(Context vector)的序列(“C1”、“C2”、“C3”)。
也就是,原先是将输入数据编码成单个变量(固定长度的向量C),而现在将输入数据编码成一整个数组!数组里面存着任意数量的定长向量,但是数组长度是可变的!
这样,在产生每一个输出的时候,都能够做到充分利用输入序列携带的信息。
Attention 的核心工作
Attention Is All You Need Ура!!!!
在传统的 Seq2Seq 模型中,编码器会将整个输入序列压缩成一个单一的 上下文向量,这个向量包含了输入序列的全部信息。解码器使用这个上下文向量生成目标序列。
然而,上下文向量在编码长序列时存在信息丢失的问题,因为它只能携带一个固定长度的摘要,随着输入长度的增加,信息会逐渐被压缩,导致模型难以捕获远距离的依赖关系。
Attention 机制的引入解决了这一问题,它允许解码器在生成每个目标单词时,都能够动态地关注输入序列的不同部分,而不是依赖于一个单一的上下文向量。
在 Seq2Seq 中加入 Attention 后,模型的工作原理发生了变化:
- 编码器的输出
编码器在每个时间步都会生成一个隐藏状态(h1, h2, h3,...
),这些隐藏状态反映了输入序列中每个位置的信息。 - 解码器的 Attention 权重
解码器生成每个目标单词时,首先会为输入序列的每个位置计算一个 注意力权重(Attention weight)。这个权重决定了当前解码器生成单词时,应该多大程度地关注输入序列中的哪个部分。
- 权重计算:解码器的当前状态与编码器每个时间步的隐藏状态进行比较,计算它们之间的相似度。这个相似度即为 Attention 权重,表示当前目标单词与输入序列各个部分的关联程度。
- 加权求和生成上下文向量
解码器根据计算得到的 Attention 权重,对编码器的所有隐藏状态进行加权求和,得到一个新的 上下文向量。这个上下文向量是动态的,依赖于目标序列当前生成的单词,因此它包含了关于输入序列的更丰富、更具体的信息。 - 生成目标单词
解码器将这个新的上下文向量与当前的隐藏状态结合,生成目标序列中的下一个单词。
这很像人类看图片的逻辑,当我们看下面这张熊猫图片的时候,我们将注意力集中在了图片的焦点上(熊猫)。
在图片上,我们的视线聚焦在熊猫及其攀爬的树干,熊猫和树干在这个区域内非常清晰,在此之外的区域则相对模糊,表示其他景物(例如熊猫背后的山林)没有被“注意力”所关注。这样的图像就能够直观地展示注意力机制是如何在众多信息中挑选出关键部分进行重点处理的。 如上面所说的,我们的视觉系统就是一种 Attention机制,将有限的注意力集中在重点信息上,从而节省资源,快速获得最有效的信息。
Attention 的3大优点
之所以要引入 Attention 机制,主要是3个原因:
- 参数少:模型复杂度跟 CNN、RNN 相比,复杂度更小,参数也更少。所以对算力的要求也就更小。
- 速度快:Attention 解决了 RNN 不能并行计算的问题。Attention机制每一步计算不依赖于上一步的计算结果,因此可以和CNN一样并行处理。
- 效果好:在 Attention 机制引入之前,有一个问题大家一直很苦恼:长距离的信息会被弱化,就好像记忆能力弱的人,记不住过去的事情是一样的。
欢迎各位大佬在评论区交流&批评指正!
Datewhale组队学习
参考链接
评论 (0)