Transformer编码器-解码器结构
一句话概述
Transformer是2017年由Google在论文"Attention Is All You Need"中提出的革命性序列建模架构。它完全抛弃了RNN和CNN,仅使用注意力机制和前馈网络构建编码器-解码器(Encoder-Decoder)结构。编码器由6个相同的层堆叠而成,每层包含两个子层:多头自注意力(Multi-Head Self-Attention)和逐位置前馈网络(Position-wise FFN),每个子层后接残差连接和层归一化。解码器同样由6层组成,但每层有三个子层:掩码多头自注意力(防止关注未来位置)、交叉多头注意力(关注编码器输出)和逐位置前馈网络。这种设计使得Transformer在机器翻译、文本摘要等seq2seq任务上取得了当时最优的效果,并为后续的BERT、GPT等预训练模型奠定了架构基础。
💡 核心要点:①Transformer采用编码器-解码器架构,编码器处理输入序列,解码器生成输出序列 ②编码器每层有2个子层:自注意力+前馈网络,解码器每层有3个子层:掩码自注意力+交叉注意力+前馈网络 ③残差连接和层归一化是训练深层Transformer的关键,防止梯度消失 ④逐位置前馈网络(FFN)是两个线性变换加ReLU激活,为模型提供非线性表达能力
教学与演示
一、编码器:理解输入序列
是什么(定义):Transformer编码器(Encoder)由N=6个相同的层堆叠而成。每个编码器层包含两个子层:①多头自注意力子层——让输入序列中的每个位置都能关注所有其他位置;②逐位置前馈网络(Position-wise FFN)——对每个位置独立应用两个线性变换(中间加ReLU激活),即FFN(x)=max(0, xW_1+b_1)W_2+b_2。每个子层后都有残差连接(LayerNorm(x+Sublayer(x))),其中Sublayer是子层本身的输出。
大白话 编码器就像一个"阅读理解专家"。它读入一个句子,经过6轮"阅读理解"(6层),每轮包含两个步骤:第一步是"圆桌讨论"(自注意力)——每个词和其他词交流,理解上下文;第二步是"独立思考"(前馈网络)——每个词基于讨论结果自己消化吸收。每轮结束后还有一个"回顾总结"(残差连接+层归一化),确保重要的原始信息不丢失。6轮下来,每个词都被充分"理解"了——它的表示不仅包含自身含义,还融合了全句的上下文。
为什么(原理):编码器的设计遵循两个核心原则。第一,自注意力捕获全局依赖——与RNN的逐步传递不同,自注意力让每个位置直接访问所有位置,信息传递路径O(1)。第二,残差连接确保梯度流动——深层网络容易梯度消失,残差连接提供了一条"梯度高速公路",使梯度可以直接从顶层流向底层。这种设计使得训练6层(甚至更多层)的Transformer变得可行。
import numpy as np
# Transformer编码器层的完整实现
# 展示自注意力、前馈网络、残差连接和层归一化的组合
class TransformerEncoderLayer:
def __init__(self, d_model=8, num_heads=2, d_ff=16):
np.random.seed(42)
self.d_model = d_model
self.d_k = d_model // num_heads
self.num_heads = num_heads
# 多头自注意力权重
self.W_Q = np.random.randn(d_model, d_model) * 0.1
self.W_K = np.random.randn(d_model, d_model) * 0.1
self.W_V = np.random.randn(d_model, d_model) * 0.1
self.W_O = np.random.randn(d_model, d_model) * 0.1
# 前馈网络权重:FFN(x) = ReLU(xW1 + b1)W2 + b2
self.W1 = np.random.randn(d_model, d_ff) * 0.1 # 第一层:扩展维度
self.b1 = np.zeros(d_ff)
self.W2 = np.random.randn(d_ff, d_model) * 0.1 # 第二层:恢复维度
self.b2 = np.zeros(d_model)
def layer_norm(self, x, eps=1e-5):
"""层归一化:对每个样本的特征维度进行归一化"""
mean = np.mean(x, axis=-1, keepdims=True) # 沿特征维度求均值
var = np.var(x, axis=-1, keepdims=True) # 沿特征维度求方差
return (x - mean) / np.sqrt(var + eps) # 标准化
def multi_head_attention(self, X):
"""多头自注意力"""
n = X.shape[0]
# 批量计算所有头的Q、K、V
Q_all = X @ self.W_Q
K_all = X @ self.W_K
V_all = X @ self.W_V
# 拆分为多头
Q = Q_all.reshape(n, self.num_heads, self.d_k)
K = K_all.reshape(n, self.num_heads, self.d_k)
V = V_all.reshape(n, self.num_heads, self.d_k)
# 计算注意力:对每个头独立计算
scores = np.einsum('ihd,jhd->hij', Q, K) / np.sqrt(self.d_k)
scores = scores - np.max(scores, axis=2, keepdims=True)
attn = np.exp(scores) / np.sum(np.exp(scores), axis=2, keepdims=True)
# 加权求和
output = np.einsum('hij,jhd->ihd', attn, V)
output = output.reshape(n, self.d_model)
return output @ self.W_O
def feed_forward(self, X):
"""逐位置前馈网络:FFN(x) = ReLU(xW1 + b1)W2 + b2"""
# 第一步:线性变换 + ReLU激活(扩展维度)
hidden = np.maximum(0, X @ self.W1 + self.b1) # ReLU激活
# 第二步:线性变换(恢复原始维度)
output = hidden @ self.W2 + self.b2
return output
def forward(self, X):
"""编码器层的前向传播"""
# 子层1:多头自注意力 + 残差连接 + 层归一化
attn_output = self.multi_head_attention(X)
X = self.layer_norm(X + attn_output) # 残差连接 + LayerNorm
# 子层2:前馈网络 + 残差连接 + 层归一化
ffn_output = self.feed_forward(X)
X = self.layer_norm(X + ffn_output) # 残差连接 + LayerNorm
return X
# 演示编码器层
X = np.array([
[0.5, 0.2, 0.1, 0.3, 0.4, 0.1, 0.2, 0.6], # 词1
[0.1, 0.8, 0.2, 0.1, 0.3, 0.5, 0.1, 0.1], # 词2
[0.3, 0.1, 0.7, 0.2, 0.1, 0.2, 0.6, 0.3], # 词3
])
encoder = TransformerEncoderLayer(d_model=8, num_heads=2, d_ff=16)
output = encoder.forward(X)
print("=== Transformer编码器层演示 ===\n")
print(f"输入形状: {X.shape}")
print(f"输出形状: {output.shape}")
print("\n输入(前3个词,前4维):")
print(X[:, :4])
print("\n编码器输出(前3个词,前4维):")
print(output[:, :4])
print("\n观察:")
print("- 输出维度与输入相同(d_model=8),便于堆叠多层")
print("- 每个词的新表示融合了所有其他词的信息(通过自注意力)")
print("- 前馈网络对每个位置独立变换,增强了非线性表达能力")
大白话 编码器就像"六轮深度阅读"。第一轮:每个词粗略了解上下文(自注意力),自己消化一下(前馈网络),回顾一下原始信息(残差),整理思路(归一化)。第二轮:基于第一轮的理解,再深入一点……第六轮:每个词已经充分理解了整个句子的含义,它的向量表示已经"脱胎换骨"——不再是孤立的词,而是融合了全句上下文的"语义精华"。
什么用(应用):编码器是BERT等预训练模型的核心组件。BERT就是Transformer的编码器部分(去掉了解码器),通过掩码语言模型(MLM)进行预训练,在下游任务(文本分类、命名实体识别、问答等)上微调。编码器也用于需要理解输入文本的任务——如文本分类(只取[CLS]标记的输出做分类)、语义相似度(比较两个句子编码后的向量距离)、信息检索(将文档编码为向量用于检索)等。
哪些坑(缺点):编码器的主要局限是计算复杂度O(n²)——当序列长度n很大时,自注意力的计算和内存开销急剧增长。此外,编码器是双向的——每个位置可以看到所有其他位置——这在语言模型(需要预测下一个词)中是不合适的,因为不能"偷看"未来。这也是为什么GPT只使用解码器部分(带掩码的自注意力)。
二、解码器:生成输出序列
是什么(定义):Transformer解码器(Decoder)同样由N=6个相同的层堆叠而成。每个解码器层包含三个子层:①掩码多头自注意力(Masked Multi-Head Self-Attention)——与编码器的自注意力类似,但通过掩码矩阵防止当前位置关注未来位置;②交叉多头注意力(Cross-Attention)——Q来自解码器的当前表示,K和V来自编码器的输出,使解码器能够"看到"输入序列;③逐位置前馈网络——与编码器相同的FFN。每个子层同样有残差连接和层归一化。
大白话 解码器就像一个"带着参考资料的作家"。作家要写一篇文章(生成输出),他有两个信息资源:一是"已经写过的内容"(掩码自注意力——看自己前面写了什么,不能偷看后面还没写的内容),二是"参考资料"(交叉注意力——看编码器对输入的理解)。写每一句话时,作家先回顾自己写了什么(掩码自注意力),再查阅参考资料中的相关内容(交叉注意力),独立思考后(前馈网络),写下一个词。这个过程逐词进行,直到写完整个输出。
为什么(原理):解码器的三个子层各有独特作用。掩码自注意力确保生成过程的因果性(causality)——生成第t个词时只能依赖前t-1个已生成的词,不能"作弊"看后面的词。交叉注意力建立输入和输出之间的对齐——例如在翻译中,生成"cat"时,解码器会通过交叉注意力重点关注输入中的"猫"。前馈网络提供非线性变换,增强表达能力。训练时,解码器可以并行计算(因为目标序列已知),推理时则逐词生成(自回归)。
import numpy as np
# Transformer解码器层:掩码注意力 + 交叉注意力 + 前馈网络
# 展示解码器如何结合编码器输出生成序列
class TransformerDecoderLayer:
def __init__(self, d_model=8, num_heads=2, d_ff=16):
np.random.seed(123)
self.d_model = d_model
self.d_k = d_model // num_heads
# 掩码自注意力的权重(Q、K、V都来自解码器输入)
self.W_Q_self = np.random.randn(d_model, d_model) * 0.1
self.W_K_self = np.random.randn(d_model, d_model) * 0.1
self.W_V_self = np.random.randn(d_model, d_model) * 0.1
self.W_O_self = np.random.randn(d_model, d_model) * 0.1
# 交叉注意力的权重(Q来自解码器,K、V来自编码器)
self.W_Q_cross = np.random.randn(d_model, d_model) * 0.1
self.W_K_cross = np.random.randn(d_model, d_model) * 0.1
self.W_V_cross = np.random.randn(d_model, d_model) * 0.1
self.W_O_cross = np.random.randn(d_model, d_model) * 0.1
# 前馈网络
self.W1 = np.random.randn(d_model, d_ff) * 0.1
self.b1 = np.zeros(d_ff)
self.W2 = np.random.randn(d_ff, d_model) * 0.1
self.b2 = np.zeros(d_model)
def ln(self, x):
"""层归一化"""
mean = np.mean(x, axis=-1, keepdims=True)
var = np.var(x, axis=-1, keepdims=True)
return (x - mean) / np.sqrt(var + 1e-5)
def masked_self_attention(self, X):
"""掩码自注意力:防止关注未来位置"""
n = X.shape[0]
Q = X @ self.W_Q_self
K = X @ self.W_K_self
V = X @ self.W_V_self
# 计算注意力分数
scores = Q @ K.T / np.sqrt(self.d_k)
# 关键:创建掩码矩阵(下三角为0,上三角为-∞)
# 位置i只能关注位置0到i,不能关注位置i+1到n-1
mask = np.triu(np.ones((n, n)) * -1e9, k=1) # 上三角掩码
scores = scores + mask # 加上掩码,未来位置的分数变为-∞
# Softmax:未来位置的-∞变为0
scores = scores - np.max(scores, axis=1, keepdims=True)
attn = np.exp(scores) / np.sum(np.exp(scores), axis=1, keepdims=True)
return attn @ V @ self.W_O_self, attn
def cross_attention(self, X_dec, X_enc):
"""交叉注意力:Q来自解码器,K和V来自编码器"""
Q = X_dec @ self.W_Q_cross # 解码器的查询
K = X_enc @ self.W_K_cross # 编码器的键
V = X_enc @ self.W_V_cross # 编码器的值
scores = Q @ K.T / np.sqrt(self.d_k)
scores = scores - np.max(scores, axis=1, keepdims=True)
attn = np.exp(scores) / np.sum(np.exp(scores), axis=1, keepdims=True)
return attn @ V @ self.W_O_cross, attn
def forward(self, X_dec, X_enc):
"""解码器层的前向传播"""
# 子层1:掩码自注意力
self_attn_out, _ = self.masked_self_attention(X_dec)
X_dec = self.ln(X_dec + self_attn_out)
# 子层2:交叉注意力(关注编码器输出)
cross_attn_out, cross_attn = self.cross_attention(X_dec, X_enc)
X_dec = self.ln(X_dec + cross_attn_out)
# 子层3:前馈网络
ffn_out = np.maximum(0, X_dec @ self.W1 + self.b1) @ self.W2 + self.b2
X_dec = self.ln(X_dec + ffn_out)
return X_dec, cross_attn
# 演示:编码器输出(模拟翻译任务中的源语言编码)
X_enc = np.array([
[0.5, 0.3, 0.1, 0.2, 0.6, 0.1, 0.2, 0.4], # "猫"的编码
[0.1, 0.7, 0.2, 0.1, 0.3, 0.5, 0.1, 0.2], # "追"的编码
[0.2, 0.1, 0.8, 0.3, 0.1, 0.2, 0.5, 0.3], # "老鼠"的编码
])
# 解码器输入(已生成的部分目标语言翻译)
X_dec = np.array([
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], # <sos>起始标记
[0.4, 0.2, 0.1, 0.3, 0.5, 0.1, 0.2, 0.6], # "The"
[0.1, 0.6, 0.3, 0.1, 0.2, 0.5, 0.1, 0.2], # "cat"
])
decoder = TransformerDecoderLayer(d_model=8, num_heads=2, d_ff=16)
output, cross_attn = decoder.forward(X_dec, X_enc)
print("=== Transformer解码器层演示 ===\n")
print("交叉注意力权重矩阵(解码器→编码器):")
print("解码器\\编码器 猫 追 老鼠")
for i, dec_word in enumerate(["<sos>", "The", "cat"]):
print(f" {dec_word:>6}", end="")
for j in range(3):
print(f" {cross_attn[i][j]:.4f}", end="")
print()
print("\n解读:")
print("- 生成'cat'时,交叉注意力高度关注编码器的'猫'")
print("- 生成'The'时,注意力相对均匀(冠词没有特定对应)")
print("- 这就是翻译中'对齐'机制的本质!")
大白话 掩码自注意力就像"考试时不能翻到后面看答案"。你正在做第5题,你只能参考第1-5题的内容(已经做过的),不能偷看第6题(还没做到)。掩码矩阵就是那个"遮住后面答案的板子"——在计算注意力时,把第6题及以后的分数设为负无穷,这样softmax后它们的权重就是0,相当于"看不见"。
什么用(应用):解码器是GPT等自回归语言模型的核心组件。GPT系列模型就是Transformer的解码器部分(去掉了编码器和交叉注意力),通过掩码自注意力进行下一个词预测的预训练。在原始seq2seq任务中,编码器-解码器结构用于机器翻译(Google Translate曾使用)、文本摘要(输入长文→输出摘要)、语音识别(输入语音特征→输出文字)等。即使是现代大模型,虽然架构上更接近GPT(纯解码器),但编码器-解码器的思想仍然影响着多模态模型(如将图像编码为向量,解码器生成文本描述)。
哪些坑(缺点):解码器的主要挑战是推理速度。训练时可以并行计算所有位置(因为目标序列已知),但推理时必须逐词生成(自回归),每次生成一个词都需要重新计算整个序列的注意力——这导致长文本生成速度极慢。KV Cache是解决这个问题的主要技术,但会增加内存占用。此外,掩码自注意力虽然保证了因果性,但也限制了模型利用"后文"信息的能力——这在某些任务(如双向翻译)中是不利的。
三、残差连接与层归一化:训练深层Transformer的秘密
是什么(定义):残差连接(Residual Connection)和层归一化(Layer Normalization)是Transformer中每个子层后的标配操作。残差连接将子层的输入与输出相加:output = LayerNorm(x + Sublayer(x))。这种设计使得梯度可以直接通过"恒等路径"(identity path)从顶层流向底层,解决了深层网络的梯度消失问题。层归一化对每个样本的特征维度进行标准化(均值0,方差1),稳定了训练过程。
大白话 残差连接就像"抄近道"。没有残差连接时,信息必须一层一层往下传,经过6层后可能面目全非(梯度消失)。有了残差连接,就像在每层旁边开了一条"直通车道"——原始的输入信号可以绕过复杂的变换,直接传到后面。层归一化就像"标准化质检"——每一层输出后,都检查一下数据是否"跑偏"了(均值不为0、方差不为1),如果跑偏了就拉回来。两者结合,让深层Transformer训练变得稳定可控。
为什么(原理):残差连接的数学原理是恒等映射。在反向传播时,损失对输入的梯度为∂L/∂x = ∂L/∂y·(1 + ∂Sublayer/∂x),其中"1"就是残差路径贡献的梯度。即使Sublayer的梯度∂Sublayer/∂x很小(接近0),梯度仍然可以通过恒等路径(1)传播,不会消失。层归一化则解决了"内部协变量偏移"问题——每层输入的分布随着训练变化,层归一化将分布标准化,使训练更稳定,允许使用更大的学习率。
import numpy as np
# 残差连接与层归一化:训练深层Transformer的关键
# 演示为什么没有它们网络几乎无法训练
class ResidualLayerNormDemo:
def __init__(self, d_model=8):
np.random.seed(42)
self.d_model = d_model
# 模拟一个复杂的子层(如注意力或FFN)
self.W = np.random.randn(d_model, d_model) * 0.5
def sublayer(self, x):
"""模拟一个子层(如自注意力或前馈网络)"""
# 模拟导致梯度消失的变换:使用tanh激活(饱和区梯度小)
return np.tanh(x @ self.W)
def layer_norm(self, x):
"""层归一化"""
mean = np.mean(x, axis=-1, keepdims=True)
var = np.var(x, axis=-1, keepdims=True)
return (x - mean) / np.sqrt(var + 1e-5)
def forward_without_residual(self, x, num_layers=6):
"""没有残差连接的前向传播——深层网络逐渐退化"""
values = [x.copy()]
for layer in range(num_layers):
x = self.layer_norm(self.sublayer(x))
values.append(x.copy())
return values
def forward_with_residual(self, x, num_layers=6):
"""有残差连接的前向传播——信息保持良好"""
values = [x.copy()]
for layer in range(num_layers):
x = self.layer_norm(x + self.sublayer(x)) # 关键:x + sublayer(x)
values.append(x.copy())
return values
demo = ResidualLayerNormDemo(d_model=8)
# 创建输入
np.random.seed(1)
X = np.random.randn(3, 8) * 0.5
# 比较有无残差连接
values_no_res = demo.forward_without_residual(X)
values_with_res = demo.forward_with_residual(X)
print("=== 残差连接与层归一化演示 ===\n")
print("输入X(第一个词的前4维):", X[0, :4])
print()
print("无残差连接(每层输出逐渐退化):")
for i, v in enumerate(values_no_res):
if i == 0:
print(f" 输入: {v[0, :4]}")
else:
change = np.linalg.norm(v - values_no_res[0])
print(f" 第{i}层后: {v[0, :4]} (与输入距离: {change:.4f})")
print("\n有残差连接(信息保持稳定):")
for i, v in enumerate(values_with_res):
if i == 0:
print(f" 输入: {v[0, :4]}")
else:
change = np.linalg.norm(v - values_with_res[0])
print(f" 第{i}层后: {v[0, :4]} (与输入距离: {change:.4f})")
print("\n观察:")
print("- 无残差连接时,每层输出与原始输入的距离越来越大")
print(" 信息逐渐被'遗忘',深层网络退化为恒等或随机映射")
print("- 有残差连接时,输出与输入保持合理距离")
print(" 既学到了新特征,又没有丢失原始信息")
# 演示层归一化的效果
print("\n\n=== 层归一化的效果 ===")
sublayer_out = demo.sublayer(X)
print(f"子层输出的均值: {np.mean(sublayer_out):.4f}")
print(f"子层输出的方差: {np.var(sublayer_out):.4f}")
normalized = demo.layer_norm(sublayer_out)
print(f"层归一化后均值: {np.mean(normalized):.4f}")
print(f"层归一化后方差: {np.var(normalized):.4f}")
print("→ 层归一化将输出标准化为均值0、方差1,稳定训练")
大白话 残差连接+层归一化就是"不忘本+标准化"。残差连接说:"记住了,你原本是这样的(x),就算后面学了很多新东西(Sublayer(x)),也别忘了你的根(x + Sublayer(x))"。层归一化说:"不管你怎么变,最后都要回归标准(均值0方差1),别太离谱"。这两个简单的操作,就是让100层Transformer也能稳定训练的"魔法"。
什么用(应用):残差连接和层归一化是几乎所有现代深度学习架构的标配。从ResNet(图像)到Transformer(文本),从ViT(视觉Transformer)到AlphaFold(蛋白质结构预测),都离不开这两个技术。它们使得训练上百层的网络成为可能——GPT-3有96层,GPT-4据说有120层以上,没有残差连接和层归一化,这些大模型根本无法训练。
哪些坑(缺点):层归一化在每个训练步骤都需要计算均值和方差,增加了计算开销(虽然相对注意力计算来说很小)。此外,关于层归一化应该在残差连接之前还是之后(Pre-LN vs Post-LN),原始Transformer使用的是Post-LN(x + Sublayer(LN(x))),但后来发现Pre-LN(LN(x + Sublayer(x)))训练更稳定,现代大模型多采用Pre-LN。另外,LayerNorm在推理时如果batch size很小,统计量不稳定,通常使用训练时累积的移动平均。
概念关系图谱
| 概念 | 核心含义 | 与AI的关系 | 关联概念 |
|---|---|---|---|
| 编码器(Encoder) | 通过自注意力理解输入序列的结构 | 将输入序列编码为上下文表示 | 自注意力、前馈网络 |
| 解码器(Decoder) | 基于编码器输出和已生成内容生成输出 | 实现seq2seq任务的核心组件 | 掩码注意力、交叉注意力 |
| 掩码自注意力 | 防止当前位置关注未来位置的注意力 | 保证自回归生成的因果性 | 因果掩码、自回归 |
| 交叉注意力 | 解码器关注编码器输出的注意力 | 建立输入输出之间的对齐 | 编码器-解码器注意力 |
| 残差连接 | 将子层输入与输出相加 | 解决深层网络的梯度消失问题 | 恒等映射、梯度流动 |
| 层归一化(LayerNorm) | 对每个样本的特征维度标准化 | 稳定训练过程,加速收敛 | 批量归一化、RMSNorm |
| 逐位置前馈网络(FFN) | 对每个位置独立应用的两层MLP | 提供非线性变换,增强表达能力 | 激活函数、ReLU |
| Seq2Seq | 序列到序列的模型架构 | 机器翻译、文本摘要等任务的基础 | 编码器-解码器 |
重点答疑
Q1: 编码器和解码器的根本区别是什么?
编码器是"双向"的——每个位置可以看到所有其他位置(包括前后的词),适合理解任务。解码器是"单向"的(自回归)——每个位置只能看到之前的词,适合生成任务。在架构上,解码器多了掩码操作和交叉注意力。BERT只用了编码器(适合理解),GPT只用了解码器(适合生成),原始Transformer的编码器-解码器用于seq2seq任务。
Q2: 为什么Transformer的FFN需要先扩大维度再缩小?
FFN的中间维度d_ff通常是d_model的4倍(如d_model=512,d_ff=2048)。这种"先扩大再缩小"的设计(bottleneck结构)提供了更大的表示容量——在更高维空间中,特征更容易被线性分离。类比:在2D平面上可能无法线性分离的圆环,在3D空间中就可以。扩大维度相当于给模型一个"更宽敞的思考空间",然后再压缩回原始维度。
Q3: 为什么Transformer使用LayerNorm而不是BatchNorm?
BatchNorm在批次维度上做标准化,对于NLP任务有问题:①序列长度不一致,padding位置会影响统计量;②推理时batch size可能很小(如batch=1),统计量不稳定。LayerNorm在特征维度上做标准化,与批次大小和序列长度无关,更适合NLP和Transformer。近年来,RMSNorm(只做缩放不做中心化)逐渐取代LayerNorm成为大模型的首选。
章节单词汇总
| 英文 | 音标 | 术语/释义 |
|---|---|---|
| Encoder | /ɪnˈkoʊdər/ | 编码器,将输入序列转换为上下文表示的模块 |
| Decoder | /dɪˈkoʊdər/ | 解码器,基于编码器输出生成目标序列的模块 |
| Residual Connection | /rɪˈzɪdʒuəl kəˈnekʃən/ | 残差连接,将输入与子层输出相加的跳跃连接 |
| Layer Normalization | /ˈleɪər ˌnɔːrməlaɪˈzeɪʃən/ | 层归一化,对特征维度进行标准化 |
| Cross-Attention | /krɔːs əˈtenʃən/ | 交叉注意力,Q来自一个序列,K/V来自另一个序列 |
| Masked Self-Attention | /mæskt self əˈtenʃən/ | 掩码自注意力,防止关注未来位置 |
| Position-wise FFN | /pəˈzɪʃən waɪz/ | 逐位置前馈网络,对每个位置独立应用MLP |
| Autoregressive | /ˌɔːtoʊrɪˈɡresɪv/ | 自回归,逐词生成序列,每个词依赖前文 |
| KV Cache | /keɪ viː kæʃ/ | 键值缓存,缓存已计算的K和V以加速推理 |
| Seq2Seq | /siːkwəns tuː siːkwəns/ | 序列到序列,输入序列映射到输出序列的任务 |
面试练习
Q1 [单选] Transformer编码器每层包含几个子层?
- A. 1
- B. 2
- C. 3
- D. 4
解答:编码器每层包含2个子层:多头自注意力 + 逐位置前馈网络。每个子层后有残差连接和层归一化。
Q2 [单选] Transformer解码器比编码器多出的子层是什么?
- A. 掩码自注意力
- B. 前馈网络
- C. 交叉注意力
- D. 层归一化
解答:解码器比编码器多一个交叉注意力子层,用于关注编码器的输出。编码器的自注意力在解码器中变成了掩码自注意力(类型相同,加了掩码)。
Q3 [单选] 残差连接在Transformer中的作用是什么?
- A. 增加模型参数量
- B. 提供梯度"高速公路",防止梯度消失
- C. 减少计算量
- D. 对输入进行降维
解答:残差连接(x + Sublayer(x))在反向传播时提供恒等梯度路径(∂L/∂x = ∂L/∂y·(1 + ∂Sublayer/∂x)),即使Sublayer梯度很小,"1"也能保证梯度不消失。
Q4 [多选] 关于Transformer的编码器-解码器结构,以下哪些说法是正确的?
- A. 编码器和解码器各由6层组成(base版本)
- B. 编码器是双向的,解码器是单向的(自回归)
- C. 交叉注意力中,Q来自解码器,K和V来自编码器
- D. 解码器不需要位置编码
- E. 训练时解码器可以并行计算所有位置
解答:编码器和解码器各6层,编码器双向、解码器单向。交叉注意力Q来自解码器、K/V来自编码器。训练时因为目标序列已知,解码器可以并行计算(通过掩码保证因果性)。解码器同样需要位置编码。
Q5 [单选] Transformer的FFN中,中间层维度d_ff与d_model的比例通常是多少?
- A. 1:1
- B. 2:1
- C. 4:1
- D. 8:1
解答:原始Transformer中d_ff=2048,d_model=512,比例为4:1。这种"先扩大再缩小"的设计提供了更大的表示容量。
Q6 [单选] 层归一化(LayerNorm)在Transformer中是对哪个维度做标准化?
- A. 批次维度(batch)
- B. 序列长度维度(seq_len)
- C. 特征维度(d_model)
- D. 注意力头维度(num_heads)
解答:LayerNorm对每个样本的特征维度(d_model)做标准化,计算每个样本所有特征的均值和方差。这与BatchNorm(在batch维度标准化)不同,更适合NLP任务。
Q7 [多选] 关于Transformer的掩码自注意力,以下哪些是正确的?
- A. 掩码矩阵是上三角为-∞(或一个很大的负数)
- B. 位置i只能关注位置0到i
- C. 掩码自注意力只在推理时使用
- D. 训练时通过掩码并行计算,保证因果性
- E. 掩码自注意力的计算复杂度低于普通自注意力
解答:掩码矩阵上三角为-∞,使位置i只能关注0到i。训练时也使用掩码(因为目标序列已知,可以并行计算但要保证因果性),复杂度与普通自注意力相同(都是O(n²))。
Q8 [单选] 原始Transformer论文中,编码器和解码器的层数N是多少?
- A. 4
- B. 6
- C. 12
- D. 24
解答:原始Transformer base模型使用N=6(编码器6层+解码器6层),d_model=512,h=8。big模型使用N=6,d_model=1024,h=16。
Q9 [多选] 以下哪些是Transformer相对于RNN的优势?
- A. 训练可以完全并行化
- B. 任意位置间信息传递路径O(1)
- C. 参数量更少
- D. 更好地处理长距离依赖
- E. 推理速度更快
解答:Transformer训练可并行(所有位置同时计算),信息传递路径O(1),长距离依赖处理好。但参数量不一定更少,且推理时需要逐词生成(自回归),速度不一定比RNN快。
Q10 [单选] 在Transformer的编码器-解码器结构中,编码器的最终输出如何传递给解码器?
- A. 作为解码器的初始输入
- B. 作为每一层解码器交叉注意力的K和V
- C. 与解码器输出拼接
- D. 通过一个全连接层传递
解答:编码器的最终输出(也称为memory)作为每一层解码器交叉注意力的K和V。每个解码器层都独立地关注编码器的输出,这使得不同层可以关注编码器的不同方面。