以下内容将详细解析 BERT 的双向 Transformer 架构及其核心原理,并展示如何在 Hugging Face Transformers 框架中使用 BERT 进行文本分析与处理的示例代码。我们将首先阐述 BERT 的原理、预训练任务、在下游任务中的微调流程,然后给出一个可运行的文本分类(情感分析)示例,帮助你完整理解并上手使用。
一、BERT 的核心思想与双向 Transformer 架构
1.1 背景与动机
在 BERT(Bidirectional Encoder Representations from Transformers)出现之前,许多自然语言处理(NLP)任务虽然也在使用预训练(如 word2vec、GloVe 等静态词向量),但这些向量对同一个词在不同上下文中的含义并无区别,也难以捕捉深层的句子级别语义。此外,传统的语言模型(如 GPT)多是单向或自回归方式,不能充分利用序列左右两侧的上下文信息。
BERT 由 Google AI 研究团队于 2018 年提出,它在大规模无监督语料上进行双向 Transformer 预训练,能学习到更加丰富的上下文和语义关系,从而在各种下游任务(如文本分类、情感分析、问答系统、命名实体识别等)上取得显著性能提升。
1.2 双向 Transformer 的基本结构
BERT 的骨干网络是 Transformer Encoder 堆叠多层而成。Transformer 最早由 Vaswani 等人在论文 “Attention Is All You Need” 中提出,用多头自注意力机制替代了传统的 RNN/CNN,实现了更高的并行度和更强的上下文建模能力。
- Encoder 模块:由多层(典型的 BERT-base 有 12 层)“多头自注意力 + 前馈网络 + 残差连接 + LayerNorm” 组成。
- 双向:在自注意力计算中,每个词可与序列中任意位置(包括左侧和右侧)的词进行交互,从而获取全局上下文信息。
在常见的语言模型(如 LSTM-based LM、GPT 等)中,要么是从左到右,要么是从右到左,而 BERT 则通过Mask策略,实现了对序列中所有词的双向建模。
1.3 BERT 的预训练任务
BERT 在大规模语料(如英文 Wikipedia、BookCorpus 等)上进行预训练,主要包括两个关键任务:
-
Masked Language Model (MLM)
- 随机将输入句子中 15% 的词替换成
[MASK]
,让模型去预测原词是什么。 - 这样的训练方式使模型充分学习上下文,因为要从句子中其他未 Mask 的词中猜测被 Mask 的词。
- 随机将输入句子中 15% 的词替换成
-
Next Sentence Prediction (NSP)
- 随机选取句子对 (A, B),其中 50% 的情况 B 是紧随 A 的下一句,另 50% 的情况 B 与 A 毫不相关。
- 让模型预测这两个句子是否在原文中相邻,从而学习句子间关系。
注意:后续的一些 BERT 变体(如 RoBERTa)去掉了 NSP 任务,仅用 MLM + 更大数据进行训练,也能取得优异效果。
1.4 BERT 在下游任务中的应用
预训练完成后,BERT 在下游任务中通常使用**微调(fine-tuning)**方式:
- 在输入层加入任务所需的头部结构(如分类任务加上一个全连接层输出类别),或使用现成的 “AutoModelForSequenceClassification”。
- 使用少量标注数据进行反向传播训练,让整个 BERT 网络(或其一部分)根据具体任务进行调整。
- 得到端到端的任务模型,显著提升各种 NLP 任务的准确性和效率。
二、使用 Hugging Face Transformers 进行 BERT 微调示例
下面演示一个 文本情感分析(positive/negative) 的简化流程。核心步骤包括:
- 安装并导入所需库
- 准备数据(模拟小规模情感数据集)
- 使用 BertTokenizer 对文本进行子词分词
- 定义 DataLoader 与 BertForSequenceClassification 模型
- 进行训练与测试
注意:真实应用中应使用大型数据集,并配置合适的参数、GPU 环境,以获得更好效果。
2.1 安装与导入
pip install transformers
pip install torch # 若未安装 PyTorch
import torch
from transformers import BertTokenizer, BertForSequenceClassification
from torch.utils.data import Dataset, DataLoader
import torch.nn as nn
import torch.optim as optim
2.2 准备示例数据
我们模拟一小份文本与情感标签(正面/负面)。真实项目需更大规模、更多样的文本。
fake_texts = [
"I love this product, it works great!",
"Absolutely terrible experience, won't buy again.",
"The movie was fantastic and I enjoyed every moment.",
"Horrible service, really disappointed with the support team.",
"This camera has excellent image quality, highly recommended!",
"The battery life is too short, not satisfied at all.",
]
fake_labels = [
1, # positive
0, # negative
1, # positive
0, # negative
1, # positive
0 # negative
]
2.3 数据集与 DataLoader
我们定义一个自定义 Dataset
,在 __getitem__
中执行 BertTokenizer,将文本转换为模型可读的张量形式(input_ids、attention_mask 等)。
class SentimentDataset(Dataset):
def __init__(self, texts, labels, tokenizer, max_len=32):
self.texts = texts
self.labels = labels
self.tokenizer = tokenizer
self.max_len = max_len
def __len__(self):
return len(self.texts)
def __getitem__(self, idx):
text = self.texts[idx]
label = self.labels[idx]
# 使用BertTokenizer将文本编码
encoding = self.tokenizer(
text,
add_special_tokens=True,
max_length=self.max_len,
padding='max_length',
truncation=True,
return_tensors='pt'
)
# encoding输出是字典,包含 input_ids, attention_mask, token_type_ids (对于BERT)
item = {
'input_ids': encoding['input_ids'].squeeze(0),
'attention_mask': encoding['attention_mask'].squeeze(0),
'labels': torch.tensor(label, dtype=torch.long)
}
return item
tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')
dataset = SentimentDataset(fake_texts, fake_labels, tokenizer, max_len=32)
dataloader = DataLoader(dataset, batch_size=2, shuffle=True)
此时 dataloader
可迭代得到批量化的数据张量,形如:
input_ids
:子词 token 的索引attention_mask
:指示哪些 token 为实际内容,哪些为填充[PAD]
labels
:情感标签(0 或 1)
2.4 定义模型与训练流程
我们使用 BertForSequenceClassification
,它在 BERT 上添加了一个简单的线性分类头,适合直接做文本分类任务。
model = BertForSequenceClassification.from_pretrained('bert-base-uncased', num_labels=2)
# 将模型移至 GPU(若可用)
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model.to(device)
# 定义优化器、损失函数
optimizer = optim.AdamW(model.parameters(), lr=2e-5)
criterion = nn.CrossEntropyLoss()
2.4.1 训练循环示例
epochs = 3
model.train()
for epoch in range(epochs):
total_loss = 0
for batch in dataloader:
input_ids = batch['input_ids'].to(device)
attention_mask = batch['attention_mask'].to(device)
labels = batch['labels'].to(device)
optimizer.zero_grad()
# BertForSequenceClassification 直接返回 (loss, logits) 等
outputs = model(input_ids, attention_mask=attention_mask, labels=labels)
loss = outputs.loss
logits = outputs.logits
loss.backward()
optimizer.step()
total_loss += loss.item()
avg_loss = total_loss / len(dataloader)
print(f"Epoch: {epoch+1}, Loss: {avg_loss:.4f}")
在 BertForSequenceClassification 中,若传入 labels
参数,会自动计算交叉熵损失,也可自行使用输出的 logits + 自定义的 loss 函数。
2.5 推理和评估
训练完成后,可以进行推理,计算预测结果并评估准确率等。此示例只做简单展示。
model.eval()
test_texts = [
"The laptop is amazing, battery lasts long!",
"Totally hate the new update, it's buggy."
]
test_labels = [1, 0] # 期望:正面 / 负面
encoding = tokenizer(
test_texts,
add_special_tokens=True,
max_length=32,
padding='max_length',
truncation=True,
return_tensors='pt'
).to(device)
with torch.no_grad():
outputs = model(encoding['input_ids'], attention_mask=encoding['attention_mask'])
logits = outputs.logits
preds = torch.argmax(logits, dim=1).cpu().numpy()
print("Predictions:", preds)
print("True labels:", test_labels)
若训练充分并有更多数据,preds
就能较为准确地与 test_labels
匹配。
三、总结与扩展
-
BERT 的双向 Transformer 架构
- 通过多头自注意力同时关注上下文任意位置,有效捕捉深层语义和依存关系。
- 在大规模语料库上预训练(MLM + NSP),形成对语言的强大理解能力。
-
显著提升下游任务表现
- 文本分类、情感分析、问答系统、序列标注、文本生成等诸多任务都能通过对 BERT 的微调受益。
-
Hugging Face Transformers 库简化了使用
- 提供了 BertTokenizer 来做子词分词(WordPiece)
- BertForSequenceClassification 等封装好的模型结构,便于快速上手
- 还支持其他 Transformer 变体(RoBERTa、GPT-2、DistilBERT、ALBERT 等)
-
实践建议
- 大部分情况下,默认超参数(如学习率 2e-5)即可得到不错效果;若需调参,可尝试调整 batch_size、epochs、学习率等。
- 尤其在资源充足时,可使用 GPU/TPU 加速预训练或微调。
- 如果下游数据集有限,使用 domain-specific BERT(如 BioBERT、FinBERT)或 数据增强 也是可行路径。
综上,BERT 通过其双向Transformer 架构和大规模预训练,极大地增强了对语言的理解能力,成为现代 NLP 的重要基石,大幅提升文本分类、情感分析、问答系统等多种下游任务的效果和效率,为高效文本分析与处理提供了强大的工具。通过上文示例,你可以迅速搭建一个 BERT 微调流程,并在真实场景中进一步扩展、优化。
【哈佛博后带小白玩转机器学习】 哔哩哔哩_bilibili
总课时超400+,时长75+小时