共计 3367 个字符,预计需要花费 9 分钟才能阅读完成。
背景介绍
在自然语言处理领域,传统的 RNN 和 LSTM 模型在处理长序列时存在梯度消失和并行计算困难的问题。而 Transformer 架构通过自注意力机制彻底改变了这一局面,成为现代 NLP 模型的基石。BART 作为一种基于 Transformer 的序列到序列模型,其核心正是自注意力机制。

自注意力机制允许模型在处理每个词时,直接关注到输入序列中的所有其他词,动态计算它们之间的相关性权重。这种机制相比 RNN/LSTM 具有三大优势:
- 更强的长距离依赖建模能力
- 更高的并行计算效率
- 更灵活的特征提取方式
架构图解
BART 的自注意力层遵循标准的 Transformer 架构,但作为编解码模型,它包含编码器和解码器两套注意力机制。让我们一步步拆解其数据流:
- 输入表示:每个词经过嵌入层得到维度为 d_model 的向量,加上位置编码后作为自注意力层的输入
- Q/K/ V 矩阵计算 :通过三个不同的线性变换将输入转换为查询(Query)、键(Key) 和值 (Value) 矩阵
- 缩放点积注意力 :计算 Q 和 K 的点积,除以 sqrt(d_k) 进行缩放,然后应用 softmax 得到注意力权重
- 多头注意力:将上述过程在多个子空间并行执行,最后拼接各头的输出并通过线性变换
- 残差连接和层归一化:注意力输出与原始输入相加后进行层归一化
解码器还额外包含编码器 - 解码器注意力层,其 K / V 来自编码器输出,Q 来自解码器上一层的输出。
代码实现
以下是 PyTorch 实现 BART 自注意力层的核心代码片段:
import torch
import torch.nn as nn
import torch.nn.functional as F
class BartSelfAttention(nn.Module):
def __init__(self, config):
super().__init__()
self.num_heads = config.num_attention_heads
self.head_dim = config.hidden_size // config.num_attention_heads
# Q/K/ V 投影矩阵
self.query = nn.Linear(config.hidden_size, config.hidden_size)
self.key = nn.Linear(config.hidden_size, config.hidden_size)
self.value = nn.Linear(config.hidden_size, config.hidden_size)
# 输出投影
self.dense = nn.Linear(config.hidden_size, config.hidden_size)
def forward(self, hidden_states, attention_mask=None):
batch_size, seq_length, hidden_size = hidden_states.size()
# 计算 Q /K/V [batch_size, seq_len, hidden_size]
q = self.query(hidden_states)
k = self.key(hidden_states)
v = self.value(hidden_states)
# 重塑为多头 [batch_size, num_heads, seq_len, head_dim]
q = q.view(batch_size, seq_length, self.num_heads, self.head_dim).transpose(1, 2)
k = k.view(batch_size, seq_length, self.num_heads, self.head_dim).transpose(1, 2)
v = v.view(batch_size, seq_length, self.num_heads, self.head_dim).transpose(1, 2)
# 计算注意力分数 [batch_size, num_heads, seq_len, seq_len]
scores = torch.matmul(q, k.transpose(-1, -2)) / math.sqrt(self.head_dim)
# 应用 attention mask(解码器使用)
if attention_mask is not None:
scores = scores + attention_mask
# softmax 归一化
attn_weights = nn.functional.softmax(scores, dim=-1)
# 注意力加权求和
attn_output = torch.matmul(attn_weights, v)
# 重塑回原始形状 [batch_size, seq_len, hidden_size]
attn_output = attn_output.transpose(1, 2).contiguous()
attn_output = attn_output.view(batch_size, seq_length, hidden_size)
# 最终投影
attn_output = self.dense(attn_output)
return attn_output, attn_weights
可视化实践
使用 BertViz 工具可以直观地观察 BART 的注意力模式:
- 安装 BertViz:
pip install bertviz - 加载预训练 BART 模型
- 准备输入文本
- 调用
bertviz的注意力可视化函数
示例代码:
from transformers import BartModel, BartTokenizer
from bertviz import head_view
model = BartModel.from_pretrained('facebook/bart-base')
tokenizer = BartTokenizer.from_pretrained('facebook/bart-base')
inputs = tokenizer("The cat sat on the mat", return_tensors="pt")
outputs = model(**inputs, output_attentions=True)
# 可视化最后一层的注意力
attention = outputs.attentions[-1]
head_view(attention, tokenizer.convert_ids_to_tokens(inputs['input_ids'][0]))
可视化结果将显示不同注意力头在各个位置的关注强度,有助于理解模型如何构建词与词之间的关系。
调优指南
影响 BART 自注意力机制效果的三个关键超参数:
- 注意力头数(num_attention_heads)
- 典型值:8-16
- 头数越多模型能学习到更丰富的注意力模式,但会增加计算量
-
建议:对于基础模型 (如 bart-base) 使用 12 头
-
隐藏层维度(hidden_size)
- 典型值:768-1024
- 需要是头数的整数倍
-
维度越高模型容量越大,但也更容易过拟合
-
注意力 dropout 率(attention_probs_dropout_prob)
- 典型值:0.1-0.2
- 防止注意力权重过度集中于少数位置
- 在数据量较小时可适当增大
避坑提醒
新手实现自注意力时常遇到的错误及解决方案:
- 忘记处理 attention mask
- 问题:解码时模型会看到未来信息
-
解决:正确构建三角 mask 矩阵,确保解码时只能看到当前位置及之前的信息
-
维度不匹配
- 问题:多头注意力的 reshape 操作导致维度错误
-
解决:确保 hidden_size 能整除 num_heads,检查所有 transpose 操作的维度
-
softmax 前的数值不稳定
- 问题:点积结果过大导致 softmax 溢出
- 解决:务必进行缩放(除以 sqrt(d_k)),必要时对 softmax 输入做截断
结论与思考
通过本文的解析,相信你对 BART 的自注意力机制有了更深入的理解。自注意力作为 Transformer 的核心,其灵活性和强大的特征提取能力使其在各种 NLP 任务中表现出色。
一个值得思考的问题是:在多头注意力机制中,不同的注意力头往往会学习到不同的关注模式。如何设计实验来系统性地分析这些模式差异?例如,是否可以通过聚类方法对注意力头进行分类,发现它们各自擅长的语法或语义关系识别?这或许是探索模型可解释性的一个有趣方向。
