第13章 Decoder
本章目标:
理解 Decoder 的 Mask 机制,为什么不能偷看未来,以及 Causal Attention 的实现。
13.1 为什么 Decoder 不能偷看未来?
假设我们在训练一个翻译模型:
输入(英文): I love AI
目标(中文): 我 爱 AI
训练时,Decoder 的输入是目标序列(右移一位):
Decoder 输入: [START] 我 爱
Decoder 输出: 我 爱 AI
如果 Decoder 可以看到未来的 Token:
预测"我"时,看到了"爱"和"AI" → 作弊!
这样训练出来的模型,推理时(没有未来 Token)会完全失效。
所以必须用 Mask 遮住未来的 Token。
13.2 Causal Mask(因果掩码)
Causal Mask 是一个上三角矩阵,用于遮住未来的 Token:
序列长度 = 4
Mask:
t=1 t=2 t=3 t=4
t=1 [ 0 -∞ -∞ -∞ ]
t=2 [ 0 0 -∞ -∞ ]
t=3 [ 0 0 0 -∞ ]
t=4 [ 0 0 0 0 ]
0:可以看到(不遮住)-∞:不能看到(遮住,Softmax 后变为 0)
13.3 Causal Mask 的效果
以序列 [我, 爱, AI] 为例:
没有 Mask(BERT 风格):
我 爱 AI
我 [0.5 0.3 0.2] ← 我可以看到"爱"和"AI"
爱 [0.2 0.6 0.2] ← 爱可以看到"我"和"AI"
AI [0.3 0.3 0.4]
有 Causal Mask(GPT 风格):
我 爱 AI
我 [1.0 0.0 0.0] ← 我只能看到自己
爱 [0.4 0.6 0.0] ← 爱只能看到"我"和自己
AI [0.3 0.3 0.4] ← AI可以看到所有
13.3.1 为什么 Mask 值是 -∞ 而不是 0?
这是初学者最常问的问题。答案和 Softmax 的数学性质有关。
如果把未来位置的 Score 设为 0:
Score: [3, 0, 0]
Softmax: [0.91, 0.045, 0.045] ← 未来位置仍然分到了4.5%的权重!
因为 e0=1e^0 = 1e0=1,所以 Score=0 在 Softmax 中不代表概率为零,它仍然会分到权重。模型还是"偷看"了未来。
如果把未来位置的 Score 设为 -∞:
Score: [3, -∞, -∞]
Softmax: [1.0, 0, 0] ← 未来位置权重严格为零
因为 e−∞=0e^{-\infty} = 0e−∞=0,Softmax 后这些位置的概率精确为零。
13.3.2 为什么用加法而不是乘法?
另一个常见疑问:为什么不直接把 Score 乘以 0?
❌ 乘法方案:Score × Mask = [3, 0, 0]
→ Softmax 后:[0.91, 0.045, 0.045] ← 没有真正遮住!
✅ 加法方案:Score + Mask = [3, -∞, -∞]
→ Softmax 后:[1.0, 0, 0] ← 完全遮住
加法的优势:
- 允许看的位置加 0(不改变原始 Score)
- 需要遮住的位置加 -∞(Softmax 后精确为 0)
- 不会像乘法那样遇到"负数×(-∞)=+∞"的数学问题
💡 实际代码中,-∞ 通常用
-1e9或torch.finfo(dtype).min来近似,保证 Softmax 后的值足够接近 0。
13.3.3 Mask 没有删除 Token
一个重要认识:Mask 不会删除任何 Token、Embedding 或 Value。它只修改 Attention Score:
Token、Q、K、V → 全部正常计算,一个不少
Score = QK^T → 正常算出完整的 [seq, seq] 矩阵
Score + Mask → 只在这一步把未来位置变成 -∞
Softmax → 未来位置的权重自然变成 0
Weight × V → 未来位置对输出的贡献为 0
所以 Mask 的本质是:让未来 Token 在 Attention 权重竞争中失去资格,而不是物理删除它们。
13.4 Causal Mask 的实现
import tensorflow as tf
def create_causal_mask(seq_len):
"""
创建因果掩码(上三角矩阵)
返回: [1, 1, seq_len, seq_len],1表示遮住,0表示不遮住
"""
mask = 1 - tf.linalg.band_part(tf.ones((seq_len, seq_len)), -1, 0)
return mask[tf.newaxis, tf.newaxis, :, :] # [1, 1, seq, seq]
# 测试
mask = create_causal_mask(4)
print(mask[0, 0])
# [[0. 1. 1. 1.]
# [0. 0. 1. 1.]
# [0. 0. 0. 1.]
# [0. 0. 0. 0.]]
# 在 Attention 中使用
def masked_attention(Q, K, V, mask=None):
d_k = tf.cast(tf.shape(K)[-1], tf.float32)
scores = tf.matmul(Q, K, transpose_b=True) / tf.math.sqrt(d_k)
if mask is not None:
scores += (mask * -1e9) # 遮住的位置加 -∞
weights = tf.nn.softmax(scores, axis=-1)
return tf.matmul(weights, V)
13.5 Decoder 的完整结构
原始 Transformer 的 Decoder Block 有三个子层:


2025

被折叠的 条评论
为什么被折叠?



