Transformer架构完全解析:从自注意力到文本分类实战

Transformer架构完全解析:从自注意力到文本分类实战

掌握自然语言处理领域的革命性架构

2017年,一篇名为《Attention Is All You Need》的论文彻底改变了自然语言处理领域的格局。这篇论文的核心思想非常简单:神经注意力机制本身就可以构建强大的序列模型,无需循环层或卷积层。

今天,我将带你深入探索Transformer架构的每一个细节,从理论基础到实际应用,并提供完整的Python实现代码。

1. 理解自注意力的本质

想象一下你在阅读技术文章时的行为:你会略读某些章节精读重点内容,这取决于你的目标和兴趣。神经注意力的思想与此惊人相似:并非所有输入信息都同等重要

1.1 注意力的两种早期形式

在深度学习中,注意力的概念早有雏形:

  • CNN中的最大汇聚:在空间区域内选择最重要的特征
  • TF-IDF规范化:根据信息量赋予词元不同权重

但自注意力更进一步:它让特征上下文感知。在嵌入空间中,每个词不再有固定位置,而是根据周围词动态调整。

1.2 自注意力机制详解

看这个例子:“The train left the station on time”。这里的"station"是什么意思?通过自注意力,我们可以计算"station"与句中每个词的相关性:

# 自注意力的NumPy风格伪代码
def self_attention(input_sequence):
    # 输入: 形状为(sequence_length, embed_dim)
    # 1. 计算成对注意力分数
    scores = np.dot(input_sequence, input_sequence.T)
    
    # 2. 应用softmax获取归一化权重
    attention_weights = softmax(scores)
    
    # 3. 使用权重对输入进行加权求和
    output = np.dot(attention_weights, input_sequence)
    
    return output

自注意力让模型能够动态调整每个词的表示,捕捉"station"在"train station"和"radio station"中的不同含义。

2. 多头注意力:注意力机制的增强版

Keras中的MultiHeadAttention层并不是简单重复三次输入,而是实现了更强大的机制:

# MultiHeadAttention的基本用法
num_heads = 4
embed_dim = 256
mha_layer = MultiHeadAttention(num_heads=num_heads, key_dim=embed_dim)
outputs = mha_layer(inputs, inputs, inputs)  # 查询、键、值

2.1 查询-键-值模型

为什么需要三个输入?这来自信息检索的隐喻:

  • 查询(Query):你想找什么
  • 键(Key):数据库中项目的标签
  • 值(Value):实际的项目内容
# 通用自注意力公式
outputs = sum(values * pairwise_scores(query, keys))

在序列分类中,查询、键、值通常是同一个序列,让每个词元都能从整个序列的上下文中获益。

2.2 多头设计原理

多头注意力的核心思想:将注意力分解到多个子空间,让模型能够学习不同类型的关注模式。

每个头都有自己的查询、键、值投影矩阵,分别学习不同的特征组合方式:

class MultiHeadAttention(layers.Layer):
    def __init__(self, num_heads, key_dim):
        super().__init__()
        self.num_heads = num_heads
        self.key_dim = key_dim
        
    def call(self, query, key, value):
        # 1. 线性投影
        q = self.linear_q(query)  # 形状: [batch, seq_len, num_heads, depth]
        k = self.linear_k(key)
        v = self.linear_v(value)
        
        # 2. 分割为多个头
        q = split_heads(q, self.num_heads)
        k = split_heads(k, self.num_heads)
        v = split_heads(v, self.num_heads)
        
        # 3. 每个头独立计算注意力
        attention_outputs = []
        for i in range(self.num_heads):
            # 计算缩放点积注意力
            scores = tf.matmul(q[i], k[i], transpose_b=True)
            weights = tf.nn.softmax(scores)
            head_output = tf.matmul(weights, v[i])
            attention_outputs.append(head_output)
        
        # 4. 合并所有头的输出
        output = combine_heads(attention_outputs)
        return output

3. Transformer编码器实现

Transformer编码器是架构的核心组件,它结合了多头注意力和前馈网络,并添加了残差连接和层规范化:

class TransformerEncoder(layers.Layer):
    def __init__(self, embed_dim, dense_dim, num_heads, **kwargs):
        super().__init__(**kwargs)
        self.embed_dim = embed_dim
        self.dense_dim = dense_dim
        self.num_heads = num_heads
        
        # 多头注意力层
        self.attention = layers.MultiHeadAttention(
            num_heads=num_heads, key_dim=embed_dim
        )
        
        # 前馈网络
        self.dense_proj = keras.Sequential([
            layers.Dense(dense_dim, activation="relu"),
            layers.Dense(embed_dim)
        ])
        
        # 层规范化
        self.layernorm_1 = layers.LayerNormalization()
        self.layernorm_2 = layers.LayerNormalization()
    
    def call(self, inputs, mask=None):
        # 自注意力
        if mask is not None:
            mask = mask[:, tf.newaxis, :]
        attention_output = self.attention(
            inputs, inputs, attention_mask=mask
        )
        
        # 第一个残差连接 + 层规范化
        proj_input = self.layernorm_1(inputs + attention_output)
        
        # 前馈网络
        proj_output = self.dense_proj(proj_input)
        
        # 第二个残差连接 + 层规范化
        return self.layernorm_2(proj_input + proj_output)

3.1 为什么使用LayerNormalization而不是BatchNormalization?

处理序列数据时,LayerNormalization比BatchNormalization更合适:

# LayerNormalization:对每个样本独立规范化
def layernorm_naive(x):
    mean = np.mean(x, axis=-1, keepdims=True)
    std = np.std(x, axis=-1, keepdims=True)
    return (x - mean) / (std + 1e-5)

# BatchNormalization:跨批次规范化(不适合序列数据)
def batchnorm_naive(x, training=True):
    if training:
        mean = np.mean(x, axis=0)  # 跨批次
        std = np.std(x, axis=0)
        return (x - mean) / (std + 1e-5)

LayerNormalization在每个序列内部规范化,更适合处理长度不一的序列数据。

4. 缺失的一环:位置编码

现在揭晓一个关键问题:基础的Transformer编码器并不考虑词序!它本质上处理的是词袋,而不是序列。

解决方案:位置编码

4.1 位置嵌入实现

class PositionEmbedding(layers.Layer):
    def __init__(self, sequence_length, input_dim, output_dim, **kwargs):
        super().__init__(**kwargs)
        # 词嵌入
        self.token_embeddings = layers.Embedding(
            input_dim=input_dim, output_dim=output_dim
        )
        # 位置嵌入
        self.position_embeddings = layers.Embedding(
            input_dim=sequence_length, output_dim=output_dim
        )
        self.sequence_length = sequence_length
        self.input_dim = input_dim
        self.output_dim = output_dim
    
    def call(self, inputs):
        length = tf.shape(inputs)[-1]
        positions = tf.range(start=0, limit=length, delta=1)
        
        # 获取词嵌入和位置嵌入
        embedded_tokens = self.token_embeddings(inputs)
        embedded_positions = self.position_embeddings(positions)
        
        # 相加得到最终嵌入
        return embedded_tokens + embedded_positions

5. 完整Transformer文本分类模型

def create_transformer_model_with_position():
    """创建带位置嵌入的Transformer文本分类模型"""
    vocab_size = 20000
    sequence_length = 600
    embed_dim = 256
    num_heads = 2
    dense_dim = 32
    
    # 输入层
    inputs = keras.Input(shape=(None,), dtype="int64")
    
    # 位置嵌入
    x = PositionEmbedding(sequence_length, vocab_size, embed_dim)(inputs)
    
    # Transformer编码器
    x = TransformerEncoder(embed_dim, dense_dim, num_heads)(x)
    
    # 全局池化和分类
    x = layers.GlobalMaxPooling1D()(x)
    x = layers.Dropout(0.5)(x)
    outputs = layers.Dense(1, activation="sigmoid")(x)
    
    # 构建模型
    model = keras.Model(inputs, outputs)
    model.compile(
        optimizer="rmsprop",
        loss="binary_crossentropy",
        metrics=["accuracy"]
    )
    return model

6. 关键发现:何时使用Transformer vs 词袋模型

基于大量实验,我们发现一个简单的经验法则:

训练样本数 ÷ 平均样本词数 = 选择依据

  • 如果比例 < 1500:使用词袋模型(训练快,效果好)
  • 如果比例 > 1500:使用Transformer模型(需要更多数据但性能更强)

6.1 示例分析

以IMDB数据集为例:

  • 训练样本:20,000
  • 平均词数:233
  • 比例:20,000 ÷ 233 ≈ 86 < 1500

结论:应该使用词袋模型(在实践中确实表现更好)

7. 实战代码:完整训练流程

def main():
    """完整训练流程"""
    # 1. 加载IMDB数据
    (x_train, y_train), (x_test, y_test) = keras.datasets.imdb.load_data(
        num_words=20000
    )
    
    # 2. 填充序列
    x_train = keras.preprocessing.sequence.pad_sequences(x_train, maxlen=600)
    x_test = keras.preprocessing.sequence.pad_sequences(x_test, maxlen=600)
    
    # 3. 创建并训练模型
    model = create_transformer_model_with_position()
    
    # 4. 训练配置
    callbacks = [
        keras.callbacks.ModelCheckpoint(
            "transformer_encoder.keras",
            save_best_only=True
        )
    ]
    
    # 5. 训练
    history = model.fit(
        x_train, y_train,
        batch_size=32,
        epochs=10,
        validation_split=0.2,
        callbacks=callbacks
    )
    
    # 6. 评估
    test_loss, test_acc = model.evaluate(x_test, y_test)
    print(f"测试准确率: {test_acc:.3f}")
    
    return model, history

总结

Transformer架构的核心创新点:

  1. 自注意力机制:动态的上下文感知表示
  2. 多头注意力:多角度捕捉复杂模式
  3. 位置编码:重新注入序列顺序信息
  4. 层规范化+残差连接:稳定训练深度网络

在实际应用中,记住我们的经验法则:根据数据特性选择模型,而不是盲目追求最新技术。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

钢铁男儿

赛博功德充值,BUG退散

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值