BERT 微调与传统机器学习的区别和联系:
传统机器学习流程
传统机器学习处理文本分类通常包含以下步骤:
- 特征工程:手动设计特征(如 TF-IDF、词袋模型)
- 模型训练:使用分类器(如 SVM、随机森林、逻辑回归)
- 特征和模型调优:反复调整特征和超参数
BERT 微调流程
BERT 微调的典型流程:
- 预训练:使用大规模无标注数据预训练 BERT 模型
- 数据准备:将文本转换为 BERT 输入格式(tokenize、添加特殊标记)
- 模型微调:冻结大部分 BERT 层,只训练分类头(或少量 BERT 层)
- 评估与部署:在验证集上评估,保存模型
两者的主要区别
对比项 | 传统机器学习 | BERT 微调 |
---|---|---|
特征表示 | 手动设计特征(如 TF-IDF) | 自动学习上下文相关表示 |
模型复杂度 | 简单到中等(如 SVM、RF) | 非常复杂(Transformer 架构) |
数据依赖 | 需要大量标注数据 | 可以用较少数据达到好效果 |
领域适应性 | 迁移到新领域需要重新设计特征 | 可以快速适应新领域(通过微调) |
计算资源 | 通常较低 | 需要 GPU/TPU |
您代码中的 BERT 微调关键点
数据预处理:
- 使用
BertTokenizer
将文本转换为 token IDs - 添加特殊标记([CLS]、[SEP])
- 填充和截断到固定长度
- 使用
模型架构:
- 基础模型:BERT 预训练模型(bert-base-chinese)
- 分类头:在 BERT 顶部添加一个全连接层(
num_labels=6
) - 微调策略:更新整个模型的权重
训练优化:
- 使用
AdamW
优化器(带权重衰减的 Adam) - 小学习率(2e-5)避免灾难性遗忘
- 批量训练(batch_size=2)处理长序列
- 使用
优势:
- 利用预训练模型捕获的语言知识
- 自动学习文本的上下文表示
- 对训练数据量要求较低
- 迁移到新领域更容易
何时选择 BERT 微调而非传统方法?
- 当您有足够的计算资源时
- 当任务数据量有限时
- 当需要处理复杂语义理解时
- 当需要快速适应新领域时
BERT 微调入门示例,展示了如何将预训练语言模型应用于特定的分类任务。随着 Transformer 架构的普及,这种方法已经成为 NLP 任务的主流解决方案。
import torch
from torch.utils.data import Dataset, DataLoader
from transformers import BertTokenizer, BertForSequenceClassification, AdamW
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
import pandas as pd
# 1. 准备数据
data = {
'text': [
"我想预订明天的机票", "查询今天的天气", "帮我设置闹钟",
"播放周杰伦的歌曲", "今天有什么新闻", "推荐几部科幻电影"
],
'label': [0, 1, 2, 3, 4, 5] # 0:订票, 1:天气, 2:闹钟, 3:音乐, 4:新闻, 5:电影
}
df = pd.DataFrame(data)
# 2. 数据集划分
train_df, val_df = train_test_split(df, test_size=0.2, random_state=42)
# 3. 创建数据集类
class TextClassificationDataset(Dataset):
def __init__(self, texts, labels, tokenizer, max_len=128):
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 = str(self.texts[idx])
label = self.labels[idx]
encoding = self.tokenizer(
text,
add_special_tokens=True,
max_length=self.max_len,
return_token_type_ids=False,
padding='max_length',
truncation=True,
return_attention_mask=True,
return_tensors='pt'
)
return {
'input_ids': encoding['input_ids'].flatten(),
'attention_mask': encoding['attention_mask'].flatten(),
'label': torch.tensor(label, dtype=torch.long)
}
# 4. 初始化tokenizer和模型
tokenizer = BertTokenizer.from_pretrained('bert-base-chinese')
model = BertForSequenceClassification.from_pretrained(
'bert-base-chinese',
num_labels=6
)
# 5. 创建数据加载器
train_dataset = TextClassificationDataset(
train_df['text'].values,
train_df['label'].values,
tokenizer
)
val_dataset = TextClassificationDataset(
val_df['text'].values,
val_df['label'].values,
tokenizer
)
train_loader = DataLoader(train_dataset, batch_size=2, shuffle=True)
val_loader = DataLoader(val_dataset, batch_size=2)
# 6. 训练模型
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model.to(device)
optimizer = AdamW(model.parameters(), lr=2e-5)
epochs = 3
for epoch in range(epochs):
model.train()
train_loss = 0
for batch in train_loader:
input_ids = batch['input_ids'].to(device)
attention_mask = batch['attention_mask'].to(device)
labels = batch['label'].to(device)
optimizer.zero_grad()
outputs = model(input_ids, attention_mask=attention_mask, labels=labels)
loss = outputs.loss
train_loss += loss.item()
loss.backward()
optimizer.step()
avg_train_loss = train_loss / len(train_loader)
print(f"Epoch {epoch+1}/{epochs}, Loss: {avg_train_loss:.4f}")
# 7. 评估模型
model.eval()
predictions = []
true_labels = []
with torch.no_grad():
for batch in val_loader:
input_ids = batch['input_ids'].to(device)
attention_mask = batch['attention_mask'].to(device)
labels = batch['label'].to(device)
outputs = model(input_ids, attention_mask=attention_mask)
preds = torch.argmax(outputs.logits, dim=1)
predictions.extend(preds.cpu().numpy())
true_labels.extend(labels.cpu().numpy())
accuracy = accuracy_score(true_labels, predictions)
print(f"Validation Accuracy: {accuracy:.4f}")
# 8. 保存模型
model.save_pretrained('./intent_classifier')
tokenizer.save_pretrained('./intent_classifier')