共计 4941 个字符,预计需要花费 13 分钟才能阅读完成。
背景与痛点
情感分析是 NLP 领域的核心任务之一,传统方法如 LSTM、TextCNN 虽然在特定场景下表现尚可,但在语义理解上存在明显局限性。这些模型通常需要大量标注数据,且对上下文信息的捕捉能力有限。例如,LSTM 虽然能处理序列数据,但在长距离依赖关系上表现不佳;TextCNN 则受限于固定窗口大小的卷积核,难以捕捉全局语义。

BERT(Bidirectional Encoder Representations from Transformers)作为一种预训练语言模型,通过双向 Transformer 结构和大规模无监督预训练,能够更好地理解上下文语义。这使得 BERT 在情感分析任务中表现出色,尤其是对复杂语句和隐含情感的理解。
技术方案
使用 HuggingFace Transformers 库加载中文 BERT 模型
HuggingFace 的 Transformers 库提供了丰富的预训练模型和便捷的接口,我们可以直接加载中文 BERT 模型(如bert-base-chinese)进行微调。
from transformers import BertTokenizer, BertForSequenceClassification
tokenizer = BertTokenizer.from_pretrained('bert-base-chinese')
model = BertForSequenceClassification.from_pretrained('bert-base-chinese', num_labels=2)
数据预处理与 Dataset 构建
情感分析任务通常需要标注好的文本数据,例如中文情感分类数据集 ChnSentiCorp。我们需要将原始文本转换为 BERT 可接受的输入格式,包括 tokenization、添加特殊标记(如[CLS]、[SEP])以及生成 attention mask。
from torch.utils.data import Dataset
class SentimentDataset(Dataset):
def __init__(self, texts, labels, tokenizer, max_len):
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.encode_plus(
text,
add_special_tokens=True,
max_length=self.max_len,
truncation=True,
padding='max_length',
return_attention_mask=True,
return_tensors='pt'
)
return {'input_ids': encoding['input_ids'].flatten(),
'attention_mask': encoding['attention_mask'].flatten(),
'labels': torch.tensor(label, dtype=torch.long)
}
模型微调策略
微调 BERT 时,学习率设置和损失函数选择是关键。由于 BERT 的参数量较大,通常需要较小的学习率(如 2e-5)以避免过拟合。交叉熵损失函数(CrossEntropyLoss)适用于多分类任务。
from transformers import AdamW
optimizer = AdamW(model.parameters(), lr=2e-5)
criterion = torch.nn.CrossEntropyLoss()
代码实现
数据加载与 tokenization 处理
以下代码展示了如何加载数据集并进行 tokenization 处理:
import pandas as pd
from sklearn.model_selection import train_test_split
data = pd.read_csv('ChnSentiCorp.csv')
train_texts, val_texts, train_labels, val_labels = train_test_split(data['text'], data['label'], test_size=0.2, random_state=42
)
train_dataset = SentimentDataset(train_texts, train_labels, tokenizer, max_len=128)
val_dataset = SentimentDataset(val_texts, val_labels, tokenizer, max_len=128)
模型定义与训练循环
训练循环包括前向传播、损失计算和反向传播:
from torch.utils.data import DataLoader
train_loader = DataLoader(train_dataset, batch_size=16, shuffle=True)
val_loader = DataLoader(val_dataset, batch_size=16)
for epoch in range(3):
model.train()
for batch in train_loader:
optimizer.zero_grad()
input_ids = batch['input_ids'].to(device)
attention_mask = batch['attention_mask'].to(device)
labels = batch['labels'].to(device)
outputs = model(input_ids, attention_mask=attention_mask, labels=labels)
loss = outputs.loss
loss.backward()
optimizer.step()
评估指标计算
评估模型性能时,常用的指标包括准确率和 F1 值:
from sklearn.metrics import accuracy_score, f1_score
model.eval()
predictions = []
true_labels = []
for batch in val_loader:
input_ids = batch['input_ids'].to(device)
attention_mask = batch['attention_mask'].to(device)
labels = batch['labels'].to(device)
with torch.no_grad():
outputs = model(input_ids, attention_mask=attention_mask)
logits = outputs.logits
preds = torch.argmax(logits, dim=1)
predictions.extend(preds.cpu().numpy())
true_labels.extend(labels.cpu().numpy())
accuracy = accuracy_score(true_labels, predictions)
f1 = f1_score(true_labels, predictions, average='weighted')
print(f'Accuracy: {accuracy}, F1: {f1}')
性能优化
batch size 选择
batch size 的选择需要在内存和训练效率之间权衡。较大的 batch size 可以提高训练速度,但会占用更多 GPU 内存。对于 BERT 模型,通常选择 16 或 32。
混合精度训练
混合精度训练(Mixed Precision Training)可以显著减少内存占用并加速训练。PyTorch 中可以通过 torch.cuda.amp 模块实现:
from torch.cuda.amp import GradScaler, autocast
scaler = GradScaler()
for batch in train_loader:
optimizer.zero_grad()
with autocast():
outputs = model(input_ids, attention_mask=attention_mask, labels=labels)
loss = outputs.loss
scaler.scale(loss).backward()
scaler.step(optimizer)
scaler.update()
生产部署
模型导出为 ONNX 格式
将训练好的模型导出为 ONNX 格式,便于跨平台部署:
torch.onnx.export(
model,
(input_ids, attention_mask),
'bert_sentiment.onnx',
input_names=['input_ids', 'attention_mask'],
output_names=['logits'],
dynamic_axes={'input_ids': {0: 'batch_size'},
'attention_mask': {0: 'batch_size'},
'logits': {0: 'batch_size'}
}
)
使用 FastAPI 构建推理服务
FastAPI 是一个高性能的 Web 框架,适合构建模型推理服务:
from fastapi import FastAPI
import torch
from transformers import BertTokenizer
app = FastAPI()
tokenizer = BertTokenizer.from_pretrained('bert-base-chinese')
model = torch.jit.load('bert_sentiment.pt')
@app.post('/predict')
async def predict(text: str):
encoding = tokenizer(text, return_tensors='pt', padding=True, truncation=True, max_length=128)
with torch.no_grad():
outputs = model(**encoding)
logits = outputs.logits
pred = torch.argmax(logits, dim=1).item()
return {'sentiment': 'positive' if pred == 1 else 'negative'}
处理高并发请求的优化方案
为了提高服务的并发能力,可以采用以下优化方案:
- 使用异步 IO(如 FastAPI 的
async/await) - 启用模型的多线程推理
- 使用负载均衡器(如 Nginx)分发请求
避坑指南
中文文本的特殊处理
中文文本需要进行分词处理,但 BERT 的 tokenizer 已经内置了分词功能,因此无需额外分词。不过,需要注意停用词的处理,某些停用词可能对情感分析有影响。
类别不平衡问题的解决方案
如果数据集中正负样本比例失衡,可以采用以下方法:
- 过采样少数类别或欠采样多数类别
- 使用类别权重(class weights)调整损失函数
- 采用 Focal Loss 等改进的损失函数
GPU 内存不足时的应对策略
如果 GPU 内存不足,可以尝试以下方法:
- 减小 batch size
- 使用梯度累积(gradient accumulation)
- 启用混合精度训练
- 使用模型并行或数据并行
结尾
BERT 在情感分析任务中表现出色,但仍有一些挑战需要解决。例如,如何改进模型处理讽刺语句的能力?讽刺语句的情感往往与字面意思相反,这对模型的语义理解能力提出了更高要求。未来可以尝试以下方向:
- 引入更多的上下文信息
- 结合知识图谱增强语义理解
- 使用更大规模的预训练模型(如 RoBERTa、ALBERT)
希望本文能帮助你快速上手 BERT 情感分析任务,并在实际项目中取得好效果!
