共计 2922 个字符,预计需要花费 8 分钟才能阅读完成。
为什么需要 BERT 词向量
传统 Word2Vec 或 GloVe 等方法生成的词向量存在明显局限:

- 同一个词在不同语境中总是相同的向量表示(比如 ” 苹果 ” 在水果和手机公司场景下无法区分)
- 无法有效处理一词多义现象
- 对未登录词 (OOV) 的处理能力弱
而 BERT 通过 Transformer 架构和掩码语言模型 (MLM) 预训练,能够:
- 生成上下文相关的动态词向量
- 自动学习词语在不同语境中的语义变化
- 通过 subword 分词解决大部分 OOV 问题
BERT 模型选择指南
常见的 BERT 变体及特点对比:
| 模型类型 | 参数量 | 层数 | 隐藏层维度 | 适用场景 |
|---|---|---|---|---|
| BERT-base | 110M | 12 | 768 | 大多数 NLP 任务 |
| BERT-large | 340M | 24 | 1024 | 对精度要求高的复杂任务 |
| DistilBERT | 66M | 6 | 768 | 资源受限环境 |
| ALBERT-base | 12M | 12 | 768 | 参数量敏感场景 |
实际选择建议:
- 从 BERT-base 开始实验
- 当预测延迟要求 <100ms 时考虑 DistilBERT
- 只有在大规模数据 (>1M 样本) 时才使用 BERT-large
核心实现:提取 BERT 嵌入
使用 HuggingFace Transformers 库的基本流程:
from transformers import BertModel, BertTokenizer
import torch
# 初始化模型和分词器
model_name = 'bert-base-uncased'
tokenizer = BertTokenizer.from_pretrained(model_name)
model = BertModel.from_pretrained(model_name)
# 准备输入文本
text = "Natural language processing with BERT is powerful."
inputs = tokenizer(text, return_tensors="pt")
# 获取各层隐藏状态
with torch.no_grad():
outputs = model(**inputs, output_hidden_states=True)
# 提取最后一层嵌入 (shape: [1, seq_len, 768])
last_hidden_state = outputs.last_hidden_state
# 提取所有层的嵌入 (13 层,包含嵌入层)
all_layers = outputs.hidden_states
可视化词向量
使用 PCA 降维展示词向量分布:
from sklearn.decomposition import PCA
import matplotlib.pyplot as plt
import numpy as np
# 取第一个样本的 token 嵌入 (忽略 [CLS] 和[SEP])
tokens = tokenizer.convert_ids_to_tokens(inputs['input_ids'][0])
embeddings = last_hidden_state[0][1:-1].numpy() # 去掉首尾特殊标记
# PCA 降维到 2D
pca = PCA(n_components=2)
emb_2d = pca.fit_transform(embeddings)
# 绘制结果
plt.figure(figsize=(10,6))
for i, token in enumerate(tokens[1:-1]):
plt.scatter(emb_2d[i,0], emb_2d[i,1])
plt.annotate(token, (emb_2d[i,0], emb_2d[i,1]))
plt.title('BERT Word Embeddings Visualization')
plt.show()
性能优化技巧
1. 维度裁剪
研究表明后 256 维通常包含更多语法而非语义信息。可安全裁剪到 512 维:
# 只保留前 512 维
optimized_emb = last_hidden_state[:, :, :512]
2. 层选择策略
不同层捕获不同特征:
- 底层(1- 3 层):基础语法特征
- 中层(4- 8 层):一般语义
- 高层(9-12 层):任务特定特征
对通用语义任务,推荐拼接最后 4 层:
# 拼接最后 4 层 (推荐用于大多数任务)
layers_to_concat = [all_layers[i] for i in [-1,-2,-3,-4]]
concat_emb = torch.cat(layers_to_concat, dim=-1) # [1, seq_len, 768*4]
3. 批处理与缓存
# 启用自动批处理
dataloader = DataLoader(dataset, batch_size=32, collate_fn=collate_fn)
# 缓存嵌入结果
@lru_cache(maxsize=1000)
def get_bert_embedding(text):
inputs = tokenizer(text, return_tensors="pt")
with torch.no_grad():
outputs = model(**inputs)
return outputs.last_hidden_state
常见问题与解决方案
1. 特殊标记处理
错误做法:
# 错误:直接对整个序列取平均
mean_embedding = last_hidden_state.mean(dim=1)
正确做法:
# 正确:排除 [CLS] 和[SEP]
content_embeddings = last_hidden_state[:, 1:-1, :]
mean_embedding = content_embeddings.mean(dim=1)
2. 长文本处理
BERT 最大长度限制为 512 tokens。解决方案:
- 滑动窗口法
- 关键句子选择
- 使用 Longformer 等改进模型
3. Subword 处理
对于由多个 subword 组成的词,推荐两种处理方式:
# 方法 1:取第一个 subword 的向量
def get_word_embedding(word):
tokens = tokenizer.tokenize(word)
if not tokens:
return None
inputs = tokenizer(word, return_tensors="pt")
with torch.no_grad():
outputs = model(**inputs)
return outputs.last_hidden_state[0, 1, :] # 第一个 content token
# 方法 2:平均所有 subword 的向量
embeddings = outputs.last_hidden_state[0, 1:1+len(tokens), :]
return embeddings.mean(dim=0)
进阶思考题
- 如何评估不同 BERT 层的嵌入质量?可以设计哪些定量指标?
- 对于特定领域(如医疗、法律),微调 BERT 前后词向量分布会如何变化?
- 如何将 BERT 嵌入与传统词向量 (Word2Vec) 有效结合使用?
结语
在实践中发现,合理使用 BERT 词向量能显著提升下游任务性能。最近在一个客户评论分类项目中,仅将 TF-IDF 特征替换为 BERT 嵌入就使准确率提升了 7%。关键是要根据具体场景选择合适的模型规模、层组合和降维策略。建议先用小规模数据实验不同配置,找到性价比最高的方案后再扩展到全量数据。
正文完
