第15章:编码-解码堆栈(Transformer)
兔狲教授的亲切开场
不知不觉,我们已经走过了五章的旅程——从最简单的神经元开始,经历了反向传播的学习、LSTM的记忆、注意力的聚焦,终于来到了现代AI的核心:Transformer编码-解码堆栈。今天,我们要回答一个关键问题:如何将简单的组件组织成强大的系统? 当注意力层一层层堆叠起来,当前馈网络加入其中,当残差连接贯穿始终,会涌现出怎样的智慧?别急,我们慢慢来,一起探索编码-解码堆栈的奥秘。
核心议题:从组件到系统
小小猪盯着屏幕上复杂的架构图,眉头微皱:“教授,我们学了注意力机制、前馈网络、归一化……这些组件都挺有意思的,但它们怎么组合成一个完整的Transformer呢?就像我有一堆乐高积木,但不知道该怎么拼成城堡。”
中山大学康乐园的春日清晨,晨光透过玻璃窗洒进黑石屋书房,在红砖地上投下温暖的光斑。窗外,木棉花开得正盛,鲜红的花朵在晨风中轻轻摇曳。书房里,功夫茶具上飘着淡淡的热气,墙上的挂钟滴答作响,像是在为学习的最后冲刺计时。
窗边的小海豹抬起头,推了推眼镜:“这其实是个系统设计问题。历史上,很多复杂系统都是由简单组件通过特定结构组织而成的。Transformer的突破,很大程度上在于它的模块化设计和层次化堆叠。”
兔狲教授轻轻放下茶杯,微笑道:“你们提出了一个很好的问题。单个注意力层就像一只强大的‘眼睛’,但真正的智慧需要组织。今天,我们就来探索一下,如何把这些组件组织成一个完整的Transformer系统。”
Transformer的诞生:注意力就是全部
小小猪走到白板前,随手画出了注意力、前馈网络、归一化的示意图。
“教授,我记得2017年那篇著名的论文《Attention Is All You Need》,标题说‘注意力就是全部所需’。可是Transformer里不只有注意力啊?还有前馈网络、归一化这些呢。”
小海豹放下手中的书,温和地补充道:“这个标题其实是修辞性的。论文的实际贡献是展示了基于注意力的编码器-解码器架构可以超越当时的RNN和CNN模型。关键创新在于完全依赖注意力机制来处理序列,不再需要循环或卷积结构。”
兔狲教授点点头:“说得对。Transformer的核心思想是:用注意力机制完全取代循环和卷积。但这可不是简单的一句话,它需要精心设计整个系统架构。”
他在白板上画出Transformer的整体架构:
编码器堆栈(N×):
输入 → 位置编码 → [多头注意力 → Add & Norm → 前馈网络 → Add & Norm] × N → 输出
解码器堆栈(N×):
输入 → 位置编码 → [掩码多头注意力 → Add & Norm → 编码器-解码器注意力 → Add & Norm → 前馈网络 → Add & Norm] × N → 输出“看看这个架构,”兔狲教授指着白板说,“Transformer不是一个单一的算法,而是组件的有组织堆叠。每个组件都有明确的功能,通过特定的方式连接在一起。”
小小猪凑近仔细观察架构图:“编码器和解码器都是‘堆栈’?就像叠罗汉一样,一层叠一层?”
“正是这样,”兔狲教授微笑道,“Transformer的‘堆栈’设计体现了深度学习的核心哲学:通过深度的层次化处理,从简单的特征中提取出复杂的模式。”
编码器堆栈:理解的艺术
窗外阳光渐强,透过木棉树叶在红砖地上投下斑驳的光影。
小小猪托着下巴问:“教授,编码器具体是做什么的?它怎么‘理解’输入序列呢?”
兔狲教授走到白板前,开始详细讲解编码器的设计。
“编码器的任务是创建输入序列的丰富表示,”他解释道,“它通过多层处理,逐步提取和整合信息,有点像我们读书时从字词到句子再到段落的理解过程。”
他在白板上列出编码器层的三个核心组件:
- 多头注意力:让序列中的每个位置都能关注所有位置,建立全局关系
- 前馈网络:对每个位置独立进行非线性变换,增加模型的表达能力
- Add & Norm:残差连接保持信息流动,层归一化稳定训练过程
残差连接:信息的捷径
兔狲教授用红笔重点标出了“Add”(加法)符号。
“残差连接是深度学习的一个关键创新,”他解释道,“公式其实很简单:
小小猪歪着头思考:“所以残差连接让信息可以‘跳过’某些变换?就算这层没学好,至少还能把原始信息传过去?”
“理解得很到位,”兔狲教授赞许地点点头,“残差连接解决了深度网络的梯度消失问题,让网络可以堆叠得很深。更重要的是,它提供了一条信息高速公路——低层的特征可以直接传递到高层,不会被中间的变换完全改变。”
小海豹补充道:“这有点像大脑中的‘捷径连接’。神经科学发现,大脑也有直接连接远处区域的通路,不一定要经过所有中间处理。”
“说得对,”兔狲教授说,“正是有了残差连接,Transformer才能堆叠数十甚至数百层,而不会丢失信息或者训练困难。”
层归一化:稳定的训练
兔狲教授在白板上写下层归一化公式:
其中
“层归一化对每个样本的每个特征维度进行归一化,”他解释道,“这稳定了激活值的分布,让训练收敛得更快。”
小小猪理解了这个设计:“所以每个层输出后都归一化一下,确保输入到下一层的数据分布比较稳定?”
“正是这样,”兔狲教授说,“层归一化和残差连接配合起来,就形成了Transformer训练的‘稳定器’,让深度网络能够顺利训练。”
前馈网络:位置独立的处理
兔狲教授画出了前馈网络的结构:
输入 → 线性变换 → ReLU激活 → 线性变换 → 输出“前馈网络对每个位置独立操作,”他解释道,“它提供非线性变换能力,增加模型的表达能力。你可以把它想象成每个位置都有自己的‘小处理器’。”
小海豹思考着:“前馈网络就像每个位置的‘微型大脑’?独立处理该位置的信息?”
“这个比喻挺形象的,”兔狲教授微笑道,“前馈网络让每个位置可以进行复杂的特征变换,而注意力机制则负责位置之间的信息交换。一个管‘内部处理’,一个管‘外部交流’。”
解码器堆栈:生成的艺术
窗外木棉花瓣随风飘落,在阳光下如红色的雪花。
小小猪好奇地问:“教授,解码器为什么比编码器复杂啊?我看它多了一个注意力层?”
兔狲教授走到白板前,开始对比编码器和解码器。
“解码器的任务确实更复杂一些,”他解释道,“它要基于编码器的理解和已经生成的部分,预测下一个元素。这个任务需要三种注意力机制协同工作。”
他在白板上列出解码器的三个注意力子层:
- 掩码多头注意力:因果自注意力,只能看到已经生成的部分
- 编码器-解码器注意力:交叉注意力,关注编码器的输出
- 前馈网络:和编码器一样的位置独立处理
掩码注意力:因果约束的智慧
兔狲教授在注意力矩阵上画了一个三角掩码。
“掩码注意力确保了自回归性质,”他解释道,“生成位置
小小猪理解了这个设计:“这样保证了生成的顺序性?不会‘偷看’后面的答案?”
“正是这样,”兔狲教授说,“掩码注意力是序列生成的基础。正是有了它,Transformer才能用于机器翻译、文本生成、语音合成这些需要按顺序生成的任务。”
编码器-解码器注意力:对齐的艺术
兔狲教授画出了交叉注意力的示意图。
“编码器-解码器注意力实现了源语言到目标语言的对齐,”他解释道,“解码器的查询
小海豹补充道:“这模拟了人类翻译的过程——看着源语句子,思考怎么用目标语来表达。”
“说得对,”兔狲教授说,“这种注意力机制让模型能够动态对齐源语言和目标语言的不同部分,即使两种语言的句子长度不一样,也能处理好翻译。”
正交计算图:看见Transformer的信息流
兔狲教授打开投影仪,一幅规整的计算图出现在屏幕上。
“这是Transformer编码器层的正交计算图,”兔狲教授指着图说,“我们可以看到信息流动的三种路径:前向传播、残差连接、归一化稳定。”
小小猪仔细观察图中的信息流:“输入
“是的,”兔狲教授解释道,“残差连接
小海豹若有所思:“这个计算流程好像体现了‘变换-保持-稳定’的循环?每个子层都遵循这个模式吗?”
“观察得很仔细,”兔狲教授说,“Transformer的设计哲学可以概括为:大胆变换,小心保持,始终稳定。注意力层进行大胆的全局信息交换,残差连接小心地保持原始信息,层归一化则始终稳定训练过程。”
位置编码:序列中的位置感
兔狲教授在白板上画出了正弦余弦位置编码的公式:
“位置编码为每个位置提供绝对位置信息,”他解释道,“因为注意力机制本身是位置无关的——它只看内容相似度,不看位置。”
小小猪思考着:“所以需要额外告诉模型‘这是第几个位置’?不然的话,‘我喜欢你’和‘你喜欢我’可能会被看成一样的?”
“正是这样,”兔狲教授微笑道,“位置编码让模型能够区分顺序。有趣的是,正弦余弦编码还有相对位置性质:位置
小海豹从数学书中抬起头:“这提供了位置外推的能力?模型可以处理比训练时更长的序列?”
“理论上是这样的,”兔狲教授说,“但实践中长序列外推仍然是个挑战。现代研究正在探索更好的位置编码方法。”
思想模型:模块化系统的智慧
小海豹从书架取下一本软件工程著作,“教授,这让我想起软件工程中的‘模块化设计’原则。”
“很好的联系,”兔狲教授说,“Transformer体现了优秀系统设计的多个原则。”
他在白板上写下思想模型:
思想模型:Transformer的设计原则
- 模块化:每个组件(注意力、前馈、归一化)功能明确,接口清晰
- 层次化:通过堆叠实现从简单到复杂的特征提取
- 信息保持:残差连接确保信息不丢失,梯度可传播
- 训练稳定:层归一化、合适的初始化确保深度网络可训练
- 并行高效:注意力机制支持大规模并行计算
“这五种原则,”兔狲教授解释,“不仅是Transformer成功的秘诀,也是优秀系统设计的通用智慧。”
小小猪思考着:“所以Transformer不仅是AI模型,也是系统设计的范例?它的思想可以应用到其他领域?”
“正是,”兔狲教授回答,“Transformer的模块化、层次化、残差连接等思想,已经影响了计算机体系结构、编译器设计、软件工程等多个领域。”
注意力就是全部?重新思考
兔狲教授在白板上写下论文标题“Attention Is All You Need”,然后在旁边画问号。
“这个标题是修辞性的,”他说,“实际上Transformer需要很多:位置编码、前馈网络、残差连接、层归一化、合适的初始化、大量的数据、强大的算力……”
小海豹补充道:“但标题抓住了本质:注意力机制是核心创新。其他组件是使注意力机制能够有效工作的‘基础设施’。”
“是的,”兔狲教授说,“Transformer的启示是:核心创新需要配套基础设施。伟大的想法需要精心设计的环境才能发挥威力。”
关键要点
兔狲教授的总结:编码-解码堆栈的智慧
- 系统设计哲学:Transformer不是单一算法,而是组件的有组织堆叠,体现“整体大于部分之和”的系统思维
- 编码器的理解之路:通过多层注意力与前馈网络的交替,逐步提取输入序列的层次化表示,实现从局部特征到全局语义的理解跃迁
- 解码器的生成之道:结合掩码自注意力(因果约束)、编码器-解码器注意力(源-目标对齐)、前馈网络(位置处理),实现自回归序列生成
- 训练稳定化设计:残差连接保持信息流与梯度流,层归一化稳定激活分布,二者配合使深度堆叠成为可能
- 模块化通用架构:Transformer展示了模块化、层次化、标准化的设计原则,这些原则超越AI领域,成为复杂系统设计的通用智慧
代码实践:完整Transformer的Python实现
"让我们用Python代码实现完整的Transformer,"兔狲教授说,"从基础组件到完整架构,最后在简单任务上演示。"
Transformer基础组件实现
import numpy as np
import matplotlib.pyplot as plt
class LayerNormalization:
"""层归一化实现"""
def __init__(self, d_model, eps=1e-6):
"""初始化层归一化
参数:
d_model: 特征维度
eps: 数值稳定性常数
"""
self.gamma = np.ones((1, d_model)) # 缩放参数
self.beta = np.zeros((1, d_model)) # 平移参数
self.eps = eps
def forward(self, x):
"""前向传播
参数:
x: 输入 (batch_size, seq_len, d_model)
返回:
归一化后的输出
"""
# 计算均值和方差(沿最后一个维度)
mean = np.mean(x, axis=-1, keepdims=True)
variance = np.var(x, axis=-1, keepdims=True)
# 归一化
x_normalized = (x - mean) / np.sqrt(variance + self.eps)
# 缩放和平移
output = self.gamma * x_normalized + self.beta
return output
class FeedForwardNetwork:
"""前馈网络(两层线性变换 + 激活函数)"""
def __init__(self, d_model, d_ff):
"""初始化前馈网络
参数:
d_model: 输入输出维度
d_ff: 中间层维度(通常为4*d_model)
"""
self.W1 = np.random.randn(d_model, d_ff) * 0.01
self.b1 = np.zeros((1, d_ff))
self.W2 = np.random.randn(d_ff, d_model) * 0.01
self.b2 = np.zeros((1, d_model))
def relu(self, x):
"""ReLU激活函数"""
return np.maximum(0, x)
def forward(self, x):
"""前向传播"""
# 第一层线性变换 + ReLU
h = np.matmul(x, self.W1) + self.b1
h = self.relu(h)
# 第二层线性变换
output = np.matmul(h, self.W2) + self.b2
return output
class MultiHeadAttention:
"""多头注意力(简化版,基于第14章实现)"""
def __init__(self, d_model, num_heads):
"""初始化多头注意力"""
assert d_model % num_heads == 0, "d_model必须能被num_heads整除"
self.d_model = d_model
self.num_heads = num_heads
self.depth = d_model // num_heads
# 权重矩阵
self.W_q = np.random.randn(d_model, d_model) * 0.01
self.W_k = np.random.randn(d_model, d_model) * 0.01
self.W_v = np.random.randn(d_model, d_model) * 0.01
self.W_o = np.random.randn(d_model, d_model) * 0.01
def scaled_dot_product_attention(self, Q, K, V, mask=None):
"""缩放点积注意力"""
d_k = Q.shape[-1]
# 计算注意力分数
scores = np.matmul(Q, K.swapaxes(-1, -2)) # 点积
scores = scores / np.sqrt(d_k) # 缩放
# 应用掩码(如果有)
if mask is not None:
scores = scores + (mask * -1e9)
# softmax得到注意力权重
attention_weights = self.softmax(scores, axis=-1)
# 加权值向量
output = np.matmul(attention_weights, V)
return output, attention_weights
def softmax(self, x, axis=-1):
"""稳定的softmax实现"""
x_exp = np.exp(x - np.max(x, axis=axis, keepdims=True))
return x_exp / np.sum(x_exp, axis=axis, keepdims=True)
def split_heads(self, x, batch_size):
"""分割多头"""
# 重塑形状: (batch_size, seq_len, d_model) -> (batch_size, seq_len, num_heads, depth)
x = x.reshape(batch_size, -1, self.num_heads, self.depth)
# 转置: (batch_size, num_heads, seq_len, depth)
return x.transpose(0, 2, 1, 3)
def combine_heads(self, x, batch_size):
"""合并多头"""
# 转置回来: (batch_size, seq_len, num_heads, depth)
x = x.transpose(0, 2, 1, 3)
# 重塑: (batch_size, seq_len, d_model)
return x.reshape(batch_size, -1, self.d_model)
def forward(self, Q, K, V, mask=None):
"""前向传播"""
batch_size = Q.shape[0]
# 线性变换
Q = np.matmul(Q, self.W_q)
K = np.matmul(K, self.W_k)
V = np.matmul(V, self.W_v)
# 分割多头
Q = self.split_heads(Q, batch_size)
K = self.split_heads(K, batch_size)
V = self.split_heads(V, batch_size)
# 缩放点积注意力
scaled_attention, attention_weights = self.scaled_dot_product_attention(Q, K, V, mask)
# 合并多头
scaled_attention = self.combine_heads(scaled_attention, batch_size)
# 输出线性变换
output = np.matmul(scaled_attention, self.W_o)
return output, attention_weights
class PositionalEncoding:
"""位置编码(正弦余弦)"""
def __init__(self, d_model, max_seq_len=5000):
"""初始化位置编码"""
self.d_model = d_model
self.max_seq_len = max_seq_len
# 预计算位置编码矩阵
self.pe = self.create_positional_encoding(max_seq_len, d_model)
def create_positional_encoding(self, max_seq_len, d_model):
"""创建位置编码矩阵"""
pe = np.zeros((max_seq_len, d_model))
for pos in range(max_seq_len):
for i in range(0, d_model, 2):
pe[pos, i] = np.sin(pos / (10000 ** (2 * i / d_model)))
if i + 1 < d_model:
pe[pos, i + 1] = np.cos(pos / (10000 ** (2 * i / d_model)))
return pe
def forward(self, x):
"""添加位置编码到输入"""
seq_len = x.shape[1]
return x + self.pe[:seq_len]
# 基础组件演示
print("Transformer基础组件演示:")
print("=" * 60)
# 测试数据
batch_size = 2
seq_len = 10
d_model = 64
x_test = np.random.randn(batch_size, seq_len, d_model)
print(f"测试数据形状: {x_test.shape}")
# 测试层归一化
print("\n1. 层归一化测试:")
layer_norm = LayerNormalization(d_model=d_model)
x_norm = layer_norm.forward(x_test)
print(f" 输入范围: [{x_test.min():.3f}, {x_test.max():.3f}]")
print(f" 归一化后范围: [{x_norm.min():.3f}, {x_norm.max():.3f}]")
print(f" 归一化后均值: {x_norm.mean():.6f} (接近0)")
print(f" 归一化后方差: {x_norm.var():.6f} (接近1)")
# 测试前馈网络
print("\n2. 前馈网络测试:")
d_ff = 4 * d_model # 典型设置
ffn = FeedForwardNetwork(d_model=d_model, d_ff=d_ff)
x_ffn = ffn.forward(x_test)
print(f" 前馈网络输出形状: {x_ffn.shape}")
print(f" 参数数量: {ffn.W1.size + ffn.b1.size + ffn.W2.size + ffn.b2.size}")
# 测试多头注意力
print("\n3. 多头注意力测试:")
num_heads = 8
mha = MultiHeadAttention(d_model=d_model, num_heads=num_heads)
# 自注意力测试
output_mha, attn_weights = mha.forward(x_test, x_test, x_test)
print(f" 多头注意力输出形状: {output_mha.shape}")
print(f" 注意力权重形状: {attn_weights.shape}")
# 测试位置编码
print("\n4. 位置编码测试:")
pos_enc = PositionalEncoding(d_model=d_model, max_seq_len=100)
x_with_pos = pos_enc.forward(x_test)
print(f" 添加位置编码后形状: {x_with_pos.shape}")
# 位置编码可视化
plt.figure(figsize=(12, 6))
plt.imshow(pos_enc.pe[:50].T, cmap='RdBu', aspect='auto')
plt.colorbar(label='位置编码值')
plt.xlabel('位置索引')
plt.ylabel('维度')
plt.title('正弦余弦位置编码(前50个位置)')
plt.savefig('/tmp/positional_encoding_visualization.png', dpi=150, bbox_inches='tight')
plt.close()
print(f" 位置编码可视化已保存到 /tmp/positional_encoding_visualization.png")Transformer编码器层实现
class TransformerEncoderLayer:
"""Transformer编码器层"""
def __init__(self, d_model, num_heads, d_ff):
"""初始化编码器层"""
# 子层1: 多头注意力 + 残差连接 + 层归一化
self.multi_head_attention = MultiHeadAttention(d_model, num_heads)
self.norm1 = LayerNormalization(d_model)
# 子层2: 前馈网络 + 残差连接 + 层归一化
self.feed_forward = FeedForwardNetwork(d_model, d_ff)
self.norm2 = LayerNormalization(d_model)
def forward(self, x, mask=None):
"""前向传播
参数:
x: 输入 (batch_size, seq_len, d_model)
mask: 注意力掩码(可选)
返回:
编码器层输出
"""
# 子层1: 多头注意力 + 残差连接 + 层归一化
attn_output, attn_weights = self.multi_head_attention.forward(x, x, x, mask)
# 残差连接 + 层归一化
x = self.norm1.forward(x + attn_output)
# 子层2: 前馈网络 + 残差连接 + 层归一化
ff_output = self.feed_forward.forward(x)
x = self.norm2.forward(x + ff_output)
return x, attn_weights
class TransformerEncoder:
"""Transformer编码器(堆叠多个编码器层)"""
def __init__(self, num_layers, d_model, num_heads, d_ff):
"""初始化编码器"""
self.layers = []
for _ in range(num_layers):
layer = TransformerEncoderLayer(d_model, num_heads, d_ff)
self.layers.append(layer)
self.num_layers = num_layers
def forward(self, x, mask=None):
"""前向传播"""
all_attention_weights = []
for layer in self.layers:
x, attn_weights = layer.forward(x, mask)
all_attention_weights.append(attn_weights)
return x, all_attention_weights
# 编码器演示
print("\nTransformer编码器演示:")
print("=" * 60)
# 创建编码器
num_layers = 3
d_model = 64
num_heads = 8
d_ff = 4 * d_model
encoder = TransformerEncoder(num_layers=num_layers, d_model=d_model,
num_heads=num_heads, d_ff=d_ff)
print(f"编码器配置:")
print(f" 层数: {num_layers}")
print(f" 模型维度: {d_model}")
print(f" 注意力头数: {num_heads}")
print(f" 前馈网络维度: {d_ff}")
# 前向传播测试
encoder_output, all_attn_weights = encoder.forward(x_test)
print(f"\n编码器输出形状: {encoder_output.shape}")
print(f"注意力权重数量(每层): {len(all_attn_weights)}")
print(f"每层注意力权重形状: {all_attn_weights[0].shape}")
# 可视化不同层的注意力模式
def visualize_encoder_attention(all_attn_weights, sample_idx=0, head_idx=0):
"""可视化编码器不同层的注意力模式"""
num_layers = len(all_attn_weights)
fig, axes = plt.subplots(1, num_layers, figsize=(5*num_layers, 5))
for layer_idx in range(num_layers):
ax = axes[layer_idx] if num_layers > 1 else axes
# 获取该层的注意力权重
layer_weights = all_attn_weights[layer_idx][sample_idx, head_idx]
im = ax.imshow(layer_weights, cmap='viridis', aspect='auto')
ax.set_xlabel('键位置')
ax.set_ylabel('查询位置')
ax.set_title(f'层 {layer_idx+1}, 头 {head_idx+1}')
# 添加颜色条
plt.colorbar(im, ax=ax, fraction=0.046, pad=0.04)
plt.suptitle('Transformer编码器不同层的注意力模式', fontsize=14)
plt.tight_layout()
plt.savefig('/tmp/encoder_attention_patterns.png', dpi=150, bbox_inches='tight')
plt.close()
print(f"编码器注意力模式可视化已保存到 /tmp/encoder_attention_patterns.png")
# 运行可视化
visualize_encoder_attention(all_attn_weights, sample_idx=0, head_idx=0)
# 分析不同层的信息变化
print("\n编码器不同层的信息变化分析:")
print("=" * 60)
# 计算层间输出的差异
layer_outputs = []
# 模拟逐层处理(为了演示)
current_x = x_test.copy()
for layer_idx, layer in enumerate(encoder.layers):
current_x, _ = layer.forward(current_x)
layer_outputs.append(current_x.copy())
# 计算与输入的差异
diff_norm = np.linalg.norm(current_x - x_test)
print(f" 层 {layer_idx+1}: 输出与输入差异 = {diff_norm:.4f}")
# 计算层间变化的模式
print(f"\n层间变化模式:")
for i in range(num_layers-1):
layer_diff = np.linalg.norm(layer_outputs[i+1] - layer_outputs[i])
print(f" 层 {i+1} → 层 {i+2}: 变化 = {layer_diff:.4f}")完整Transformer实现(简化版)
class Transformer:
"""完整的Transformer模型(简化版,只包含编码器)"""
def __init__(self, vocab_size, d_model, num_heads, d_ff, num_layers, max_seq_len):
"""初始化Transformer"""
self.vocab_size = vocab_size
self.d_model = d_model
# 词嵌入层
self.embedding = np.random.randn(vocab_size, d_model) * 0.01
# 位置编码
self.positional_encoding = PositionalEncoding(d_model, max_seq_len)
# 编码器
self.encoder = TransformerEncoder(num_layers, d_model, num_heads, d_ff)
# 输出层(用于分类等任务)
self.output_layer = np.random.randn(d_model, vocab_size) * 0.01
self.output_bias = np.zeros((1, vocab_size))
def forward(self, input_ids, attention_mask=None):
"""前向传播"""
batch_size, seq_len = input_ids.shape
# 词嵌入
embedded = np.zeros((batch_size, seq_len, self.d_model))
for b in range(batch_size):
for t in range(seq_len):
token_id = input_ids[b, t]
embedded[b, t] = self.embedding[token_id]
# 添加位置编码
embedded = self.positional_encoding.forward(embedded)
# 编码器处理
encoded, all_attn_weights = self.encoder.forward(embedded, attention_mask)
# 输出层(这里使用平均池化后分类)
pooled = np.mean(encoded, axis=1) # (batch_size, d_model)
logits = np.matmul(pooled, self.output_layer) + self.output_bias
return logits, encoded, all_attn_weights
# 完整Transformer演示
print("\n完整Transformer模型演示:")
print("=" * 60)
# 创建Transformer模型
vocab_size = 1000
d_model = 128
num_heads = 8
d_ff = 4 * d_model
num_layers = 4
max_seq_len = 50
transformer = Transformer(
vocab_size=vocab_size,
d_model=d_model,
num_heads=num_heads,
d_ff=d_ff,
num_layers=num_layers,
max_seq_len=max_seq_len
)
print(f"Transformer配置:")
print(f" 词汇表大小: {vocab_size}")
print(f" 模型维度: {d_model}")
print(f" 注意力头数: {num_heads}")
print(f" 前馈网络维度: {d_ff}")
print(f" 编码器层数: {num_layers}")
print(f" 最大序列长度: {max_seq_len}")
# 估计参数数量
embedding_params = vocab_size * d_model
attention_params = 4 * d_model * d_model * num_layers # W_q, W_k, W_v, W_o 每层
ffn_params = 2 * (d_model * d_ff + d_ff * d_model) * num_layers # W1, W2 每层
output_params = d_model * vocab_size
total_params = embedding_params + attention_params + ffn_params + output_params
print(f"\n参数数量估计:")
print(f" 词嵌入: {embedding_params:,}")
print(f" 注意力层: {attention_params:,}")
print(f" 前馈网络: {ffn_params:,}")
print(f" 输出层: {output_params:,}")
print(f" 总计: {total_params:,}")
# 模拟输入
batch_size = 3
seq_len = 15
# 生成随机token IDs(模拟文本)
input_ids = np.random.randint(0, vocab_size-10, (batch_size, seq_len))
print(f"\n输入数据:")
print(f" input_ids形状: {input_ids.shape}")
print(f" 示例输入(第一个样本): {input_ids[0]}")
# 前向传播
logits, encoded, all_attn_weights = transformer.forward(input_ids)
print(f"\n输出结果:")
print(f" logits形状: {logits.shape}")
print(f" 编码输出形状: {encoded.shape}")
print(f" 注意力权重数量: {len(all_attn_weights)}")
# 任务演示:简单的序列分类
print(f"\nTransformer在简单任务上的演示:")
print("=" * 60)
# 创建一个简单的模式识别任务
def create_pattern_task(num_samples, seq_len, vocab_size):
"""创建模式识别任务数据"""
X = np.zeros((num_samples, seq_len), dtype=int)
y = np.zeros((num_samples,), dtype=int)
for i in range(num_samples):
# 随机生成序列
sequence = np.random.randint(0, vocab_size-5, seq_len)
# 任务:如果序列包含特定模式(如连续三个递增数字),则分类为1
has_pattern = 0
for j in range(seq_len - 2):
if (sequence[j] + 1 == sequence[j+1] and
sequence[j+1] + 1 == sequence[j+2]):
has_pattern = 1
break
X[i] = sequence
y[i] = has_pattern
return X, y
# 生成数据
num_samples = 100
seq_len = 10
vocab_size = 50
X_task, y_task = create_pattern_task(num_samples, seq_len, vocab_size)
print(f"任务数据: {num_samples}个样本,序列长度{seq_len},词汇表大小{vocab_size}")
print(f"正例比例: {np.mean(y_task):.1%}")
# 创建适合任务的Transformer
task_transformer = Transformer(
vocab_size=vocab_size,
d_model=32, # 较小维度
num_heads=4,
d_ff=128,
num_layers=2,
max_seq_len=seq_len
)
# 训练演示(简化版)
def train_transformer_demo(model, X, y, epochs=20, lr=0.01):
"""训练Transformer演示(简化版)"""
losses = []
accuracies = []
for epoch in range(epochs):
batch_losses = []
batch_accs = []
# 小批量训练(简化,实际应随机打乱)
for i in range(0, len(X), 10):
batch_X = X[i:i+10]
batch_y = y[i:i+10]
# 前向传播
logits, _, _ = model.forward(batch_X)
# 计算损失(交叉熵)
# 将logits转换为概率
exp_logits = np.exp(logits - np.max(logits, axis=1, keepdims=True))
probs = exp_logits / np.sum(exp_logits, axis=1, keepdims=True)
# 二元交叉熵损失
loss = -np.mean(batch_y * np.log(probs[:, 1] + 1e-8) +
(1 - batch_y) * np.log(probs[:, 0] + 1e-8))
# 计算准确率
predictions = np.argmax(logits, axis=1)
accuracy = np.mean(predictions == batch_y)
batch_losses.append(loss)
batch_accs.append(accuracy)
epoch_loss = np.mean(batch_losses)
epoch_acc = np.mean(batch_accs)
losses.append(epoch_loss)
accuracies.append(epoch_acc)
if epoch % 5 == 0:
print(f" 轮数 {epoch}: 损失={epoch_loss:.4f}, 准确率={epoch_acc:.2%}")
return losses, accuracies
print(f"\n开始训练演示(简化版)...")
losses, accuracies = train_transformer_demo(task_transformer, X_task, y_task, epochs=30)
# 可视化训练过程
plt.figure(figsize=(12, 5))
plt.subplot(1, 2, 1)
plt.plot(losses, 'b-', linewidth=2)
plt.xlabel('训练轮数')
plt.ylabel('交叉熵损失')
plt.title('Transformer训练损失曲线')
plt.grid(True, alpha=0.3)
plt.subplot(1, 2, 2)
plt.plot(accuracies, 'g-', linewidth=2)
plt.xlabel('训练轮数')
plt.ylabel('准确率')
plt.title('Transformer训练准确率曲线')
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('/tmp/transformer_training_demo.png', dpi=150, bbox_inches='tight')
plt.close()
print(f"\n训练演示完成!")
print(f" 最终损失: {losses[-1]:.4f}")
print(f" 最终准确率: {accuracies[-1]:.2%}")
print(f" 训练曲线已保存到 /tmp/transformer_training_demo.png")
# 测试模型在未见数据上的表现
X_test, y_test = create_pattern_task(50, seq_len, vocab_size)
logits_test, _, _ = task_transformer.forward(X_test)
predictions_test = np.argmax(logits_test, axis=1)
test_accuracy = np.mean(predictions_test == y_test)
print(f"\n测试集表现:")
print(f" 测试样本数: {len(X_test)}")
print(f" 测试准确率: {test_accuracy:.2%}")"记住,"兔狲教授总结道,"Transformer是深度学习系统设计的典范——它将简单的组件(注意力、前馈网络、归一化)通过精心设计的结构(残差连接、层堆叠、位置编码)组织成强大的系统。最重要的不是某个组件的创新,而是整体架构的智慧。在这个架构中,我们看到了模块化、层次化、标准化、并行化等普适设计原则的完美体现。它提醒我们:真正的突破往往不是单一技术的跃进,而是系统思维的胜利。"
兔狲教授的思考题
实践探索(适合小小猪)
- Transformer变体实现:实现不同的Transformer变体(如Performer、Linformer、Reformer)。比较它们的计算效率与模型效果?
- 位置编码实验:实现不同的位置编码方法(可学习位置编码、相对位置编码、旋转位置编码)。比较它们对模型性能的影响?
- 模型压缩实验:对一个训练好的Transformer进行剪枝、量化、蒸馏。如何压缩模型同时保持性能?
历史探究(适合小海豹)
- Transformer的前身与演进:研究Transformer之前的主流序列模型(RNN、LSTM、GRU、CNN-seq)。Transformer如何吸收它们的优点?
- Transformer的跨领域迁移:调查Transformer如何从NLP迁移到CV(Vision Transformer)、语音、生物信息等领域。这种迁移说明了什么?
- 开源生态的影响:研究Hugging Face等开源社区如何加速Transformer的普及和应用。开源对AI发展有什么影响?
综合思考
- 哲学反思:Transformer的“注意力就是全部”是否暗示了某种认识论——理解就是建立关系?这与传统的“表示-推理”范式有何不同?
- 伦理挑战:Transformer模型需要大量数据和算力,这可能加剧资源不平等。如何使Transformer技术更加普惠?
- 创造练习:设计一个“自解释Transformer”,让模型能够解释自己的注意力模式和决策过程。你会如何设计?
- 极限挑战:证明Transformer是图灵完备的(理论上可以模拟任何图灵机)。需要什么条件?这说明了Transformer的什么能力?
第三部分总结:神经网络的涌现
茶香在黑石屋中弥漫,春日阳光温暖而宁静。
"我们用了六章时间,走完了神经网络的完整旅程,"兔狲教授说,"从最简单的神经元,到最复杂的Transformer。让我们回顾一下这段旅程。"
小小猪翻开笔记本:"我们从第10章的神经元开始——最简单的感知单元,学习加权求和与激活函数。"
小海豹补充道:"第11章探索了反向传播——错误如何成为进步的阶梯,梯度下降如何指引学习方向。"
"第12章引入了时间维度,"小小猪继续说,"LSTM的记忆链条,通过门控机制实现选择性记忆。"
小海豹翻动着笔记:"第13章比较了两种记忆哲学——LSTM的渐进式记忆与注意力的直接访问。遗忘与因果的较量。"
"第14章深入探索了注意力机制,"小小猪说,"在这个嘈杂的世界里,该看哪?缩放点积、多头分工、位置编码……"
兔狲教授微笑:"最后,第15章我们将所有组件组织起来——Transformer编码-解码堆栈。从组件到系统,从简单到复杂。"
"这六章展示了智能的涌现过程,"兔狲教授总结,"从简单的计算单元,通过连接、学习、记忆、注意力、组织,最终涌现出强大的智能系统。这个过程的核心启示是:"
第三部分核心启示
- 简单产生复杂:复杂的智能行为可以从简单的计算单元(神经元)通过连接和组织涌现
- 学习源于错误:反向传播将错误转化为进步的阶梯,体现了试错学习的基本原理
- 记忆需要选择:LSTM的门控机制展示了选择性记忆的智慧——记住重要的,忘记无关的
- 注意力就是选择:注意力机制实现了信息选择的数学形式,是智能的"眼睛"
- 组织创造能力:Transformer展示了如何通过模块化、层次化组织简单组件创造强大系统
- 深度需要稳定:残差连接、层归一化等技巧使深度网络可训练,体现了工程智慧
"最重要的是,"兔狲教授说,"神经网络的旅程告诉我们:智能不是神秘的黑箱,而是可理解、可构建、可改进的复杂系统。从数学公式到代码实现,从理论原理到实际应用,每一步都是人类智慧的体现。"
下一步预告:通往推理王国
"但神经网络真的是在'推理'吗?"小小猪问,"还是只是在模仿统计模式?"
"这正是第四部分要探索的问题,"兔狲教授解释,"当我们完成了神经网络的技术之旅,需要回到根本问题:什么是真正的推理?"
小海豹期待地说:"第四部分:通往推理王国。我们将探讨LLM的迷思、推理科学的工具箱,以及致20岁后的你。"
兔狲教授微笑:"我们慢慢来,下一部分见。"
小小猪的笔记:我实现了一个完整的Transformer!虽然简化,但包含了所有核心组件。最震撼的是看到不同层的注意力模式确实不同——低层关注局部,高层关注全局。Transformer真的像是一个层次化的理解系统。
小海豹的笔记:研究了Transformer的历史和影响,震撼于它的跨领域迁移能力。从NLP到CV,从语音到蛋白质结构预测,Transformer展现了一的架构潜力。最深刻的是它的设计哲学:模块化、层次化、标准化——这些原则超越了AI领域。
兔狲教授的结语:第三部分的旅程教给我们关于智能构建的深刻一课:复杂源于简单,能力源于组织,智能源于设计。从神经元到Transformer,我们看到了数学、工程、认知科学的完美融合。最重要的是,它提醒我们:技术工具本身没有智能,智能在于我们如何使用这些工具来理解世界、解决问题、创造价值。在这条路上,理解比使用更重要,思考比记忆更有价值。我们慢慢来,理解了最重要。
