Alpha内测版本警告:此为早期内部构建版本,尚不完整且可能存在错误,欢迎大家提Issue反馈问题或建议。
Skip to content

第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的‘堆栈’设计体现了深度学习的核心哲学:通过深度的层次化处理,从简单的特征中提取出复杂的模式。”


编码器堆栈:理解的艺术

窗外阳光渐强,透过木棉树叶在红砖地上投下斑驳的光影。

小小猪托着下巴问:“教授,编码器具体是做什么的?它怎么‘理解’输入序列呢?”

兔狲教授走到白板前,开始详细讲解编码器的设计。

“编码器的任务是创建输入序列的丰富表示,”他解释道,“它通过多层处理,逐步提取和整合信息,有点像我们读书时从字词到句子再到段落的理解过程。”

他在白板上列出编码器层的三个核心组件:

  1. 多头注意力:让序列中的每个位置都能关注所有位置,建立全局关系
  2. 前馈网络:对每个位置独立进行非线性变换,增加模型的表达能力
  3. Add & Norm:残差连接保持信息流动,层归一化稳定训练过程

残差连接:信息的捷径

兔狲教授用红笔重点标出了“Add”(加法)符号。

“残差连接是深度学习的一个关键创新,”他解释道,“公式其实很简单:y=x+F(x),其中 F(x) 是这一层的变换。”

小小猪歪着头思考:“所以残差连接让信息可以‘跳过’某些变换?就算这层没学好,至少还能把原始信息传过去?”

“理解得很到位,”兔狲教授赞许地点点头,“残差连接解决了深度网络的梯度消失问题,让网络可以堆叠得很深。更重要的是,它提供了一条信息高速公路——低层的特征可以直接传递到高层,不会被中间的变换完全改变。”

小海豹补充道:“这有点像大脑中的‘捷径连接’。神经科学发现,大脑也有直接连接远处区域的通路,不一定要经过所有中间处理。”

“说得对,”兔狲教授说,“正是有了残差连接,Transformer才能堆叠数十甚至数百层,而不会丢失信息或者训练困难。”

层归一化:稳定的训练

兔狲教授在白板上写下层归一化公式:

LayerNorm(x)=xμσγ+β

其中 μ,σ 是均值和标准差,γ,β 是可学习的缩放和平移参数。

“层归一化对每个样本的每个特征维度进行归一化,”他解释道,“这稳定了激活值的分布,让训练收敛得更快。”

小小猪理解了这个设计:“所以每个层输出后都归一化一下,确保输入到下一层的数据分布比较稳定?”

“正是这样,”兔狲教授说,“层归一化和残差连接配合起来,就形成了Transformer训练的‘稳定器’,让深度网络能够顺利训练。”

前馈网络:位置独立的处理

兔狲教授画出了前馈网络的结构:

输入 → 线性变换 → ReLU激活 → 线性变换 → 输出

“前馈网络对每个位置独立操作,”他解释道,“它提供非线性变换能力,增加模型的表达能力。你可以把它想象成每个位置都有自己的‘小处理器’。”

小海豹思考着:“前馈网络就像每个位置的‘微型大脑’?独立处理该位置的信息?”

“这个比喻挺形象的,”兔狲教授微笑道,“前馈网络让每个位置可以进行复杂的特征变换,而注意力机制则负责位置之间的信息交换。一个管‘内部处理’,一个管‘外部交流’。”


解码器堆栈:生成的艺术

窗外木棉花瓣随风飘落,在阳光下如红色的雪花。

小小猪好奇地问:“教授,解码器为什么比编码器复杂啊?我看它多了一个注意力层?”

兔狲教授走到白板前,开始对比编码器和解码器。

“解码器的任务确实更复杂一些,”他解释道,“它要基于编码器的理解和已经生成的部分,预测下一个元素。这个任务需要三种注意力机制协同工作。”

他在白板上列出解码器的三个注意力子层:

  1. 掩码多头注意力:因果自注意力,只能看到已经生成的部分
  2. 编码器-解码器注意力:交叉注意力,关注编码器的输出
  3. 前馈网络:和编码器一样的位置独立处理

掩码注意力:因果约束的智慧

兔狲教授在注意力矩阵上画了一个三角掩码。

“掩码注意力确保了自回归性质,”他解释道,“生成位置 t 的时候,只能看到位置 1,2,,t1,不能看到未来的内容。”

小小猪理解了这个设计:“这样保证了生成的顺序性?不会‘偷看’后面的答案?”

“正是这样,”兔狲教授说,“掩码注意力是序列生成的基础。正是有了它,Transformer才能用于机器翻译、文本生成、语音合成这些需要按顺序生成的任务。”

编码器-解码器注意力:对齐的艺术

兔狲教授画出了交叉注意力的示意图。

“编码器-解码器注意力实现了源语言到目标语言的对齐,”他解释道,“解码器的查询 Q 会关注编码器的键值对 (K,V)。”

小海豹补充道:“这模拟了人类翻译的过程——看着源语句子,思考怎么用目标语来表达。”

“说得对,”兔狲教授说,“这种注意力机制让模型能够动态对齐源语言和目标语言的不同部分,即使两种语言的句子长度不一样,也能处理好翻译。”


正交计算图:看见Transformer的信息流

兔狲教授打开投影仪,一幅规整的计算图出现在屏幕上。

Transformer正交计算图

“这是Transformer编码器层的正交计算图,”兔狲教授指着图说,“我们可以看到信息流动的三种路径:前向传播残差连接归一化稳定。”

小小猪仔细观察图中的信息流:“输入 X 同时进入多头注意力和第一个加法器?这就是残差连接吗?”

“是的,”兔狲教授解释道,“残差连接 X+MHA(X) 保留了原始信息。然后经过层归一化,进入前馈网络,再次进行残差连接和归一化。”

小海豹若有所思:“这个计算流程好像体现了‘变换-保持-稳定’的循环?每个子层都遵循这个模式吗?”

“观察得很仔细,”兔狲教授说,“Transformer的设计哲学可以概括为:大胆变换,小心保持,始终稳定。注意力层进行大胆的全局信息交换,残差连接小心地保持原始信息,层归一化则始终稳定训练过程。”

位置编码:序列中的位置感

兔狲教授在白板上画出了正弦余弦位置编码的公式:

PE(pos,2i)=sin(pos100002i/dmodel)PE(pos,2i+1)=cos(pos100002i/dmodel)

“位置编码为每个位置提供绝对位置信息,”他解释道,“因为注意力机制本身是位置无关的——它只看内容相似度,不看位置。”

小小猪思考着:“所以需要额外告诉模型‘这是第几个位置’?不然的话,‘我喜欢你’和‘你喜欢我’可能会被看成一样的?”

“正是这样,”兔狲教授微笑道,“位置编码让模型能够区分顺序。有趣的是,正弦余弦编码还有相对位置性质:位置 pos+k 的编码可以从位置 pos 的编码线性变换得到。”

小海豹从数学书中抬起头:“这提供了位置外推的能力?模型可以处理比训练时更长的序列?”

“理论上是这样的,”兔狲教授说,“但实践中长序列外推仍然是个挑战。现代研究正在探索更好的位置编码方法。”


思想模型:模块化系统的智慧

小海豹从书架取下一本软件工程著作,“教授,这让我想起软件工程中的‘模块化设计’原则。”

“很好的联系,”兔狲教授说,“Transformer体现了优秀系统设计的多个原则。”

他在白板上写下思想模型:

思想模型:Transformer的设计原则

  1. 模块化:每个组件(注意力、前馈、归一化)功能明确,接口清晰
  2. 层次化:通过堆叠实现从简单到复杂的特征提取
  3. 信息保持:残差连接确保信息不丢失,梯度可传播
  4. 训练稳定:层归一化、合适的初始化确保深度网络可训练
  5. 并行高效:注意力机制支持大规模并行计算

“这五种原则,”兔狲教授解释,“不仅是Transformer成功的秘诀,也是优秀系统设计的通用智慧。”

小小猪思考着:“所以Transformer不仅是AI模型,也是系统设计的范例?它的思想可以应用到其他领域?”

“正是,”兔狲教授回答,“Transformer的模块化、层次化、残差连接等思想,已经影响了计算机体系结构、编译器设计、软件工程等多个领域。”

注意力就是全部?重新思考

兔狲教授在白板上写下论文标题“Attention Is All You Need”,然后在旁边画问号。

“这个标题是修辞性的,”他说,“实际上Transformer需要很多:位置编码、前馈网络、残差连接、层归一化、合适的初始化、大量的数据、强大的算力……”

小海豹补充道:“但标题抓住了本质:注意力机制是核心创新。其他组件是使注意力机制能够有效工作的‘基础设施’。”

“是的,”兔狲教授说,“Transformer的启示是:核心创新需要配套基础设施。伟大的想法需要精心设计的环境才能发挥威力。”


关键要点

兔狲教授的总结:编码-解码堆栈的智慧

  1. 系统设计哲学:Transformer不是单一算法,而是组件的有组织堆叠,体现“整体大于部分之和”的系统思维
  2. 编码器的理解之路:通过多层注意力与前馈网络的交替,逐步提取输入序列的层次化表示,实现从局部特征到全局语义的理解跃迁
  3. 解码器的生成之道:结合掩码自注意力(因果约束)、编码器-解码器注意力(源-目标对齐)、前馈网络(位置处理),实现自回归序列生成
  4. 训练稳定化设计:残差连接保持信息流与梯度流,层归一化稳定激活分布,二者配合使深度堆叠成为可能
  5. 模块化通用架构:Transformer展示了模块化、层次化、标准化的设计原则,这些原则超越AI领域,成为复杂系统设计的通用智慧

代码实践:完整Transformer的Python实现

"让我们用Python代码实现完整的Transformer,"兔狲教授说,"从基础组件到完整架构,最后在简单任务上演示。"

Transformer基础组件实现

python
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编码器层实现

python
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实现(简化版)

python
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是深度学习系统设计的典范——它将简单的组件(注意力、前馈网络、归一化)通过精心设计的结构(残差连接、层堆叠、位置编码)组织成强大的系统。最重要的不是某个组件的创新,而是整体架构的智慧。在这个架构中,我们看到了模块化、层次化、标准化、并行化等普适设计原则的完美体现。它提醒我们:真正的突破往往不是单一技术的跃进,而是系统思维的胜利。"


兔狲教授的思考题

实践探索(适合小小猪)

  1. Transformer变体实现:实现不同的Transformer变体(如Performer、Linformer、Reformer)。比较它们的计算效率与模型效果?
  2. 位置编码实验:实现不同的位置编码方法(可学习位置编码、相对位置编码、旋转位置编码)。比较它们对模型性能的影响?
  3. 模型压缩实验:对一个训练好的Transformer进行剪枝、量化、蒸馏。如何压缩模型同时保持性能?

历史探究(适合小海豹)

  1. Transformer的前身与演进:研究Transformer之前的主流序列模型(RNN、LSTM、GRU、CNN-seq)。Transformer如何吸收它们的优点?
  2. Transformer的跨领域迁移:调查Transformer如何从NLP迁移到CV(Vision Transformer)、语音、生物信息等领域。这种迁移说明了什么?
  3. 开源生态的影响:研究Hugging Face等开源社区如何加速Transformer的普及和应用。开源对AI发展有什么影响?

综合思考

  1. 哲学反思:Transformer的“注意力就是全部”是否暗示了某种认识论——理解就是建立关系?这与传统的“表示-推理”范式有何不同?
  2. 伦理挑战:Transformer模型需要大量数据和算力,这可能加剧资源不平等。如何使Transformer技术更加普惠?
  3. 创造练习:设计一个“自解释Transformer”,让模型能够解释自己的注意力模式和决策过程。你会如何设计?
  4. 极限挑战:证明Transformer是图灵完备的(理论上可以模拟任何图灵机)。需要什么条件?这说明了Transformer的什么能力?

第三部分总结:神经网络的涌现

茶香在黑石屋中弥漫,春日阳光温暖而宁静。

"我们用了六章时间,走完了神经网络的完整旅程,"兔狲教授说,"从最简单的神经元,到最复杂的Transformer。让我们回顾一下这段旅程。"

小小猪翻开笔记本:"我们从第10章的神经元开始——最简单的感知单元,学习加权求和与激活函数。"

小海豹补充道:"第11章探索了反向传播——错误如何成为进步的阶梯,梯度下降如何指引学习方向。"

"第12章引入了时间维度,"小小猪继续说,"LSTM的记忆链条,通过门控机制实现选择性记忆。"

小海豹翻动着笔记:"第13章比较了两种记忆哲学——LSTM的渐进式记忆与注意力的直接访问。遗忘与因果的较量。"

"第14章深入探索了注意力机制,"小小猪说,"在这个嘈杂的世界里,该看哪?缩放点积、多头分工、位置编码……"

兔狲教授微笑:"最后,第15章我们将所有组件组织起来——Transformer编码-解码堆栈。从组件到系统,从简单到复杂。"

"这六章展示了智能的涌现过程,"兔狲教授总结,"从简单的计算单元,通过连接、学习、记忆、注意力、组织,最终涌现出强大的智能系统。这个过程的核心启示是:"

第三部分核心启示

  1. 简单产生复杂:复杂的智能行为可以从简单的计算单元(神经元)通过连接和组织涌现
  2. 学习源于错误:反向传播将错误转化为进步的阶梯,体现了试错学习的基本原理
  3. 记忆需要选择:LSTM的门控机制展示了选择性记忆的智慧——记住重要的,忘记无关的
  4. 注意力就是选择:注意力机制实现了信息选择的数学形式,是智能的"眼睛"
  5. 组织创造能力:Transformer展示了如何通过模块化、层次化组织简单组件创造强大系统
  6. 深度需要稳定:残差连接、层归一化等技巧使深度网络可训练,体现了工程智慧

"最重要的是,"兔狲教授说,"神经网络的旅程告诉我们:智能不是神秘的黑箱,而是可理解、可构建、可改进的复杂系统。从数学公式到代码实现,从理论原理到实际应用,每一步都是人类智慧的体现。"


下一步预告:通往推理王国

"但神经网络真的是在'推理'吗?"小小猪问,"还是只是在模仿统计模式?"

"这正是第四部分要探索的问题,"兔狲教授解释,"当我们完成了神经网络的技术之旅,需要回到根本问题:什么是真正的推理?"

小海豹期待地说:"第四部分:通往推理王国。我们将探讨LLM的迷思、推理科学的工具箱,以及致20岁后的你。"

兔狲教授微笑:"我们慢慢来,下一部分见。"


小小猪的笔记:我实现了一个完整的Transformer!虽然简化,但包含了所有核心组件。最震撼的是看到不同层的注意力模式确实不同——低层关注局部,高层关注全局。Transformer真的像是一个层次化的理解系统。

小海豹的笔记:研究了Transformer的历史和影响,震撼于它的跨领域迁移能力。从NLP到CV,从语音到蛋白质结构预测,Transformer展现了一的架构潜力。最深刻的是它的设计哲学:模块化、层次化、标准化——这些原则超越了AI领域。

兔狲教授的结语:第三部分的旅程教给我们关于智能构建的深刻一课:复杂源于简单,能力源于组织,智能源于设计。从神经元到Transformer,我们看到了数学、工程、认知科学的完美融合。最重要的是,它提醒我们:技术工具本身没有智能,智能在于我们如何使用这些工具来理解世界、解决问题、创造价值。在这条路上,理解比使用更重要,思考比记忆更有价值。我们慢慢来,理解了最重要。