一、Transformer总览:从RNN到Transformer的演进、核心思想、五组件概览
说实话,我第一次接触Transformer时,心里是有点抵触的。
2017年那会儿,我还在用LSTM做机器翻译。每次训练都要等好久,梯度消失、梯度爆炸轮着来。后来Google那篇"Attention is All You Need"出来,我第一反应是:又一篇标题党吧?
结果看完论文,我沉默了。
嗯,这玩意儿确实不一样。今天咱们就来聊聊,Transformer到底是怎么从RNN的泥潭里爬出来的,它的核心思想是什么,以及那五个组件到底长啥样。
1.1 从RNN到Transformer:我们到底在解决什么问题?
先说说RNN的问题。你想想看,RNN处理序列数据时,是一个词一个词往后传的。就像你在读一句话,必须一个字一个字地看,不能跳着读。
这带来两个麻烦:
- 慢——没法并行计算。每个时间步都得等前一个算完。
- 记不住——长距离依赖搞不定。句子一长,前面的信息就丢了。
我在做文本生成项目时遇到过这种情况:模型生成到后半段,完全忘了开头说了什么。你让它写一篇200字的短文,它写到第150字就开始胡言乱语。
LSTM和GRU虽然加了门控机制,但说白了只是"缓解",不是"解决"。就像给一个漏水的桶贴创可贴——能撑一会儿,但终究不是办法。
那Transformer是怎么做的?
它直接把整个序列扔进模型,让每个词都能"看到"所有其他词。不需要一步步传递,一步到位。
核心转变:从"逐步传递"变成"全局交互"。RNN是串行的,Transformer是并行的。就这么简单。
1.2 核心思想:Attention Is All You Need
Transformer的核心思想,说白了就一句话:用注意力机制替代循环结构。
什么叫注意力机制?我给你举个我自己的例子。
有一次我在看一篇技术文档,里面提到"它支持分布式训练"。这个"它"指的是什么?我得回头看前文——哦,是"PyTorch"。这个过程就是注意力:你关注当前词时,会去"看"其他相关词来理解它。
Transformer里的Self-Attention(自注意力)做的就是这件事:
- 每个词都跟所有其他词计算相关性
- 相关性高的词,贡献更大
- 最终每个词都融合了全局信息
你可能会问:那位置信息怎么办?RNN天然有顺序,Transformer没有啊。
嗯,这里要注意。Transformer用了位置编码(Positional Encoding)来告诉模型每个词在句子中的位置。就像给每个词发了一个"座位号",模型就知道谁在前谁在后了。
我的经验:位置编码的设计其实挺讲究的。我早期试过直接用整数编码,效果很差。后来用了正弦余弦函数,模型才真正"理解"了位置关系。这个细节后面会细讲。
1.3 五组件概览:一张图看懂Transformer
Transformer整体结构像是一个"编码器-解码器"的沙漏。但咱们先不纠结整体,先看它的五个核心组件:
| 组件 | 作用 | 一句话理解 |
|---|---|---|
| 1. 自注意力机制 | 计算词与词之间的关联度 | 让每个词"看到"所有词 |
| 2. 多头注意力 | 从不同角度做注意力 | 多个"视角"同时看 |
| 3. 前馈神经网络 | 对每个位置做非线性变换 | 给每个词"思考"的空间 |
| 4. 层归一化 | 稳定训练过程 | 防止数值爆炸 |
| 5. 残差连接 | 缓解梯度消失 | 让信息"抄近道" |
这五个组件,每个都值得单独花一章来讲。但今天咱们先有个整体印象。
1.4 自注意力机制:最核心的那个
自注意力是Transformer的灵魂。没有它,其他组件都是摆设。
它的计算过程其实不复杂:
- 每个词生成三个向量:Query(查询)、Key(键)、Value(值)
- 用Query跟所有Key做点积,得到注意力分数
- 分数经过Softmax归一化
- 用归一化后的分数加权求和所有Value
写成代码就是:
import torch
import torch.nn.functional as F
def scaled_dot_product_attention(Q, K, V):
# Q, K, V: [batch_size, seq_len, d_k]
d_k = Q.size(-1)
scores = torch.matmul(Q, K.transpose(-2, -1)) / (d_k ** 0.5)
attn_weights = F.softmax(scores, dim=-1)
output = torch.matmul(attn_weights, V)
return output, attn_weights
你看,就这么几行。但效果惊人。
避坑指南:我曾经在实现时忘了除以√d_k,结果训练直接炸了。这个缩放因子不是随便加的——当d_k很大时,点积结果会很大,Softmax会进入梯度饱和区。除以√d_k就是让方差保持为1。
1.5 多头注意力:一个不够?来八个
单头注意力只能从一个角度捕捉关系。但一个词可能有多种含义——比如"苹果"可以是水果,也可以是公司。
多头注意力就是让模型同时从多个"视角"看问题:
- 每个头独立做自注意力
- 头与头之间参数不共享
- 最后把所有头的结果拼起来
我习惯用8个头。为什么是8?其实没有特别的原因,论文里用8,大家就都跟着用8。你试试4个头或16个头,效果也差不多。
1.6 前馈神经网络:给每个词一点"思考"空间
注意力机制做完后,每个词都融合了全局信息。但光有"看"还不够,还得"想"。
前馈神经网络(FFN)就是干这个的。它对每个位置独立做两次线性变换,中间加一个ReLU激活:
class FeedForward(nn.Module):
def __init__(self, d_model, d_ff):
super().__init__()
self.w1 = nn.Linear(d_model, d_ff)
self.w2 = nn.Linear(d_ff, d_model)
def forward(self, x):
return self.w2(F.relu(self.w1(x)))
d_ff通常是d_model的4倍。比如d_model=512,d_ff=2048。这个比例是经验值,我试过3倍和5倍,4倍确实最稳。
1.7 层归一化与残差连接:训练的"稳定器"
这两个组件放在一起说,因为它们经常成对出现。
残差连接:把输入直接加到输出上。这样梯度可以"抄近道"传到前面,防止深层网络梯度消失。
层归一化:对每个样本的所有特征做归一化。跟BatchNorm不同,LayerNorm不依赖batch size,更适合序列模型。
我早期做Transformer训练时,不加LayerNorm,loss曲线像过山车。加上之后,稳得像老狗。
总结一下:Transformer用自注意力替代了RNN的循环结构,用位置编码保留了顺序信息,用多头注意力扩展了"视野",用FFN增加了表达能力,用残差连接和层归一化保证了训练稳定。五个组件,缺一不可。
1.8 下一章预告
这一章咱们把Transformer的来龙去脉和五个组件过了一遍。下一章,我会手把手带你实现自注意力机制——从数学公式到可运行的PyTorch代码,一个细节都不放过。
到时候你会看到,自注意力其实没那么神秘。它就是一个加权求和,只不过权重是动态算出来的。
嗯,咱们下章见。