微博作为国内最大的社交媒体平台之一,每天产生海量的用户生成内容。这些短文本中蕴含着丰富的情绪信息,对于舆情监控、产品反馈分析等场景具有重要价值。传统的情感分析方法通常只能区分正向、负向和中立三种情绪,而实际业务往往需要更细粒度的分类。
chinese-roberta-wwm-ext之所以成为我们的首选模型,是因为它在中文处理上的几个独特优势。首先,Whole Word Masking(全词掩码)技术让模型能更好地理解中文词语的完整语义。比如遇到"苹果"这个词时,模型不会像传统BERT那样可能只掩码"苹"或"果"单个字,而是会把整个词作为一个单元处理,这更符合中文的语言特性。
我在实际项目中对比过多个预训练模型的效果。当处理微博这类包含大量网络用语和缩略语的文本时,chinese-roberta-wwm-ext在语义理解上的优势尤为明显。比如面对"yyds"、"绝绝子"这类网络流行语时,基于字符级别的模型可能会完全无法理解,而wwm架构能更好地捕捉这类新兴表达的整体含义。
SMP2020微博情绪分类评测数据集是目前中文领域最权威的细粒度情绪标注数据集之一。下载后的数据包含三个部分:usual_train.txt(训练集)、usual_test_labeled.txt(测试集)和virus_eval_labeled.txt(验证集)。每个文件都是JSON格式,包含content(微博内容)和label(情绪标签)两个关键字段。
我建议在数据加载阶段就做好异常处理。微博数据常常包含特殊字符、表情符号甚至HTML标签。以下是我优化后的数据加载代码:
python复制def load_dataset(file_path):
try:
with open(file_path, 'r', encoding='utf-8') as f:
data = json.load(f)
# 数据清洗
cleaned_data = []
for item in data:
content = item['content'].strip()
content = re.sub(r'<[^>]+>', '', content) # 去除HTML标签
content = re.sub(r'\s+', ' ', content) # 合并多余空格
if content and 'label' in item:
cleaned_data.append({
'content': content,
'label': item['label']
})
return cleaned_data
except Exception as e:
print(f"加载数据集出错: {str(e)}")
return None
使用PyTorch的DataLoader可以大幅提升训练效率。这里我分享一个实战技巧:通过预计算和缓存来避免重复分词。微博文本长度普遍较短,我们可以统一截断到128个token,这样既能保证覆盖大多数内容,又能节省计算资源。
python复制class WeiboDataset(Dataset):
def __init__(self, data, tokenizer, max_length=128):
self.data = data
self.tokenizer = tokenizer
self.max_length = max_length
self.cache = {} # 缓存分词结果
def __len__(self):
return len(self.data)
def __getitem__(self, idx):
if idx in self.cache:
return self.cache[idx]
item = self.data[idx]
encoding = self.tokenizer(
item['content'],
max_length=self.max_length,
padding='max_length',
truncation=True,
return_tensors='pt'
)
# 将张量从形状[1, seq_len]转为[seq_len]
input_ids = encoding['input_ids'].squeeze(0)
attention_mask = encoding['attention_mask'].squeeze(0)
# 缓存结果
self.cache[idx] = (input_ids, attention_mask, torch.tensor(item['label']))
return self.cache[idx]
微调预训练模型时,学习率的选择至关重要。经过多次实验,我发现分层设置学习率效果最好:底层参数使用较小的学习率(1e-5),顶层分类层使用较大的学习率(5e-4)。这是因为底层已经学习到通用的语言特征,只需要微调;而顶层需要快速适应新任务。
python复制from transformers import AdamW
# 分组设置学习率
no_decay = ['bias', 'LayerNorm.weight']
optimizer_grouped_parameters = [
{
'params': [p for n, p in model.named_parameters()
if not any(nd in n for nd in no_decay) and 'classifier' not in n],
'lr': 1e-5,
'weight_decay': 0.01
},
{
'params': [p for n, p in model.named_parameters()
if any(nd in n for nd in no_decay) and 'classifier' not in n],
'lr': 1e-5,
'weight_decay': 0.0
},
{
'params': [p for n, p in model.named_parameters() if 'classifier' in n],
'lr': 5e-4,
'weight_decay': 0.0
}
]
optimizer = AdamW(optimizer_grouped_parameters)
微博文本常常包含拼写错误、方言和网络用语,为了提高模型对这些噪声的鲁棒性,我引入了FGM(Fast Gradient Method)对抗训练。这种方法通过在梯度方向上添加扰动,让模型学习更稳健的特征表示。
python复制class FGM():
def __init__(self, model):
self.model = model
self.backup = {}
def attack(self, epsilon=0.5, emb_name='word_embeddings'):
for name, param in self.model.named_parameters():
if param.requires_grad and emb_name in name:
self.backup[name] = param.data.clone()
norm = torch.norm(param.grad)
if norm != 0:
r_at = epsilon * param.grad / norm
param.data.add_(r_at)
def restore(self, emb_name='word_embeddings'):
for name, param in self.model.named_parameters():
if param.requires_grad and emb_name in name:
assert name in self.backup
param.data = self.backup[name]
self.backup = {}
在训练循环中使用FGM:
python复制fgm = FGM(model)
for batch in train_loader:
# 正常训练
outputs = model(**batch)
loss = outputs.loss
loss.backward()
# 对抗训练
fgm.attack()
outputs_adv = model(**batch)
loss_adv = outputs_adv.loss
loss_adv.backward()
fgm.restore()
optimizer.step()
optimizer.zero_grad()
对于多分类问题,特别是各类别不平衡时(微博数据中"neutral"类别通常占比较大),仅看准确率是不够的。我建议计算每个类别的精确率、召回率和F1分数,并使用宏平均(macro-average)来综合评估模型性能。
python复制from sklearn.metrics import classification_report
def evaluate(model, dataloader, device):
model.eval()
predictions = []
true_labels = []
with torch.no_grad():
for batch in dataloader:
inputs = {k:v.to(device) for k,v in batch.items()}
outputs = model(**inputs)
logits = outputs.logits
preds = torch.argmax(logits, dim=1)
predictions.extend(preds.cpu().numpy())
true_labels.extend(inputs['labels'].cpu().numpy())
print(classification_report(
true_labels,
predictions,
target_names=label_dict.keys(),
digits=4
))
训练好的模型最终需要部署为可用的服务。FastAPI是一个高性能的Python web框架,非常适合部署机器学习模型。以下是一个完整的API实现:
python复制from fastapi import FastAPI
from pydantic import BaseModel
import torch
from transformers import AutoTokenizer, AutoModelForSequenceClassification
app = FastAPI()
class RequestData(BaseModel):
text: str
# 加载模型
model = AutoModelForSequenceClassification.from_pretrained("./model")
tokenizer = AutoTokenizer.from_pretrained("./model")
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model.to(device)
@app.post("/predict")
async def predict(data: RequestData):
inputs = tokenizer(
data.text,
return_tensors="pt",
padding=True,
truncation=True,
max_length=128
).to(device)
with torch.no_grad():
outputs = model(**inputs)
probs = torch.nn.functional.softmax(outputs.logits, dim=-1)
pred = torch.argmax(probs).item()
return {
"emotion": label_dict[pred],
"confidence": probs[0][pred].item(),
"probabilities": {k: v.item() for k, v in zip(label_dict.values(), probs[0])}
}
启动服务后,可以通过发送POST请求到/predict端点获取预测结果。这种部署方式不仅支持单条预测,稍加改造就能支持批量预测,满足不同业务场景的需求。