BERT 模型微调与传统机器学习的对比
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 = textsself.labels = labelsself.tokenizer = tokenizerself.max_len = max_lendef __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 = 3for epoch in range(epochs):model.train()train_loss = 0for 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.losstrain_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')