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架构的核心创新点:
- 自注意力机制:动态的上下文感知表示
- 多头注意力:多角度捕捉复杂模式
- 位置编码:重新注入序列顺序信息
- 层规范化+残差连接:稳定训练深度网络
在实际应用中,记住我们的经验法则:根据数据特性选择模型,而不是盲目追求最新技术。
619

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



