共计 2850 个字符,预计需要花费 8 分钟才能阅读完成。
概念澄清:架构设计差异图解
Transformer 经典结构采用 Encoder-Decoder 双栈设计,而 BERT 仅保留 Encoder 部分。关键差异点在于:
- Decoder 缺失:BERT 移除了解码器的自回归生成能力,专注于上下文编码
- 注意力流向:Transformer 的 Decoder 使用掩码注意力防止信息泄露,BERT 的 Encoder 允许全部 token 相互可见
- 输出层:Transformer 的 Decoder 输出概率分布,BERT 的 Encoder 输出上下文表征
(图示:左为 Transformer 完整结构,右为 BERT 纯 Encoder 架构)
核心差异三维度剖析
1. 预训练目标差异
- BERT 的 MLM:随机遮盖 15% 的 token,要求模型根据双向上下文预测被遮盖词
- 例如:” 机器 [MASK] 很强大 ” → 预测 ” 学习 ”
-
优势:捕获 token 间的双向关系
-
Transformer LM:标准自回归语言模型,只用上文预测下一个词
- 例如:” 机器 ” → 预测 ” 学习 ”,” 机器学习 ” → 预测 ” 很 ”
- 特点:适合文本生成但缺乏反向上下文
2. 注意力机制实现
-
BERT 的双向注意力:
# 标准的全连接注意力矩阵计算 attention_scores = torch.matmul(query, key.transpose(-1, -2)) attention_scores = attention_scores / math.sqrt(self.attention_head_size) # 无任何掩码操作(与 Transformer Decoder 的关键区别) -
Transformer Decoder 的掩码注意力:
# 添加三角掩码防止看到未来信息 mask = torch.tril(torch.ones(seq_len, seq_len)) attention_scores = attention_scores.masked_fill(mask == 0, -1e9)
3. 位置编码方案
-
Transformer 的正弦函数:
# 固定公式生成位置编码 position = torch.arange(0, max_seq_len).unsqueeze(1) div_term = torch.exp(torch.arange(0, d_model, 2) * -(math.log(10000.0) / d_model)) pe[:, 0::2] = torch.sin(position * div_term) # 偶数维 pe[:, 1::2] = torch.cos(position * div_term) # 奇数维 -
BERT 的可学习参数:
# 直接作为模型参数学习 self.position_embeddings = nn.Embedding(config.max_position_embeddings, config.hidden_size)
实战代码:BERT 文本分类改造
关键改造点示例
from transformers import BertModel, BertConfig
import torch.nn as nn
class BertForClassification(nn.Module):
def __init__(self, num_labels=2):
super().__init__()
config = BertConfig.from_pretrained('bert-base-uncased')
self.bert = BertModel(config)
# 修改 1:替换原始预训练 Head
self.classifier = nn.Sequential(nn.Linear(config.hidden_size, config.hidden_size),
nn.ReLU(),
nn.LayerNorm(config.hidden_size), # 添加层归一化提升稳定性
nn.Linear(config.hidden_size, num_labels)
)
# 修改 2:自定义学习率 warmup
self.warmup_steps = 500
def forward(self, input_ids, attention_mask):
outputs = self.bert(
input_ids=input_ids,
attention_mask=attention_mask
)
# 使用[CLS]token 作为分类特征
pooled_output = outputs.last_hidden_state[:, 0, :]
# 修改 3:动态调整学习率
if self.training:
current_step = ... # 获取当前训练步数
lr_scale = min(1.0, current_step / self.warmup_steps)
for param_group in self.optimizer.param_groups:
param_group['lr'] = param_group['initial_lr'] * lr_scale
return self.classifier(pooled_output)
生产环境关键考量
显存占用对比(测试环境:NVIDIA V100 32GB)
| 模型 | 序列长度 =128 | 序列长度 =512 |
|---|---|---|
| BERT-base | 3.2GB | 12.1GB |
| Transformer-base | 2.8GB | 10.4GB |
长文本处理方案
# 位置编码扩展方法(适用于 BERT)def extend_position_embeddings(model, new_max_len):
old_embeddings = model.bert.embeddings.position_embeddings
old_len, dim = old_embeddings.weight.shape
new_embeddings = nn.Embedding(new_max_len, dim)
# 前 old_len 维直接复制原有参数
new_embeddings.weight.data[:old_len] = old_embeddings.weight.data
# 后续位置用已有位置的平均值初始化
new_embeddings.weight.data[old_len:] = old_embeddings.weight.data.mean(dim=0)
model.bert.embeddings.position_embeddings = new_embeddings
model.bert.config.max_position_embeddings = new_max_len
常见陷阱与解决方案
- 生成任务误用
- 问题:直接用 BERT 做文本生成会导致质量低下
-
方案:采用 UniLM 架构或接入 Decoder
-
小样本微调技巧
-
分层解冻策略:
- 先只训练分类头
- 逐步解冻顶层 Transformer 层
- 最后微调全部参数
-
注意力头剪枝
- 实验发现:
- 中间层头敏感度较低
- 首尾层头对性能影响大
- 建议剪枝比例不超过 30%
延伸思考
如何设计混合架构同时利用 BERT 和 GPT 的优势?可以考虑:
- 并行训练:共享底层参数,上层分叉
- 交替注意力:部分层用双向,部分层用自回归
- 任务路由:根据输入类型动态选择处理路径
正文完
发表至: 人工智能
近一天内
