1. 项目概述:基于BERT的酒店评论情感分类系统
这个项目构建了一个能够自动判断酒店评论情感倾向的深度学习系统。核心思路是使用预训练的BERT中文模型作为基础,在其上添加分类层进行微调训练,最终实现将任意一条酒店评论分类为"好评"(1)或"差评"(0)的功能。
整套系统由四个Python文件组成完整的工作流:
- data.py:负责数据读取、预处理和加载
- model.py:构建BERT分类模型架构
- train.py:实现训练和验证流程
- main.py:整合所有模块并启动训练
2. 数据准备与处理
2.1 原始数据格式解析
原始数据存储在jiudian.txt文本文件中,每行格式为:
code复制label,review_content
其中label为0(差评)或1(好评),review_content是用户的实际评论内容。文件首行是标题行"label,review",需要跳过。
2.2 数据读取与清洗
read_txt_data()函数完成以下关键操作:
python复制def read_txt_data(path):
label = [] # 存储标签
data = [] # 存储评论文本
with open(path, "r", encoding="utf-8") as f:
for i, line in tqdm(enumerate(f)):
if i == 0: # 跳过标题行
continue
if i > 200 and i < 7500: # 抽样部分数据加速开发
continue
line = line.strip('\n') # 去除换行符
line = line.split(",", 1) # 按第一个逗号分割
label.append(line[0]) # 第一部分是标签
data.append(line[1]) # 第二部分是内容
return data, label
注意事项:实际项目中建议保留全部数据训练,这里抽样仅用于演示。抽样时注意保持正负样本比例均衡,避免引入偏差。
2.3 数据集划分与加载
使用sklearn的train_test_split划分训练集和验证集:
python复制def get_dataloader(path, batchsize=1, valSize=0.2):
x, label = read_txt_data(path)
# 分层抽样保持类别比例
train_x, val_x, train_y, val_y = train_test_split(
x, label, test_size=valSize, shuffle=True, stratify=label)
# 创建PyTorch数据集
train_set = JdDataset(train_x, train_y)
val_set = JdDataset(val_x, val_y)
# 创建数据加载器
train_loader = DataLoader(train_set, batch_size=batchsize)
val_loader = DataLoader(val_set, batch_size=batchsize)
return train_loader, val_loader
关键参数说明:
- stratify=label:确保训练/验证集的标签分布一致
- shuffle=True:打乱数据顺序
- batch_size:根据GPU内存调整,一般设为2的幂次方
3. 模型架构设计
3.1 BERT模型基础
使用HuggingFace提供的bert-base-chinese预训练模型,该模型具有:
- 12层Transformer编码器
- 768维隐藏层
- 12个注意力头
- 110M参数
3.2 自定义分类模型
python复制class myBertModel(nn.Module):
def __init__(self, bert_path, num_class, device):
super(myBertModel, self).__init__()
self.device = device
self.num_class = 2
# 加载预训练BERT
self.bert = BertModel.from_pretrained(bert_path)
self.tokenizer = BertTokenizer.from_pretrained(bert_path)
# 自定义分类头
self.out = nn.Sequential(
nn.Linear(768, num_class) # 768维输入,2分类输出
)
3.3 输入数据处理
BERT需要特定的输入格式:
python复制def build_bert_input(self, text):
Input = self.tokenizer(
text,
return_tensors='pt',
padding='max_length', # 填充到固定长度
truncation=True, # 截断超长文本
max_length=128 # 最大长度限制
)
input_ids = Input["input_ids"].to(self.device) # token ID
attention_mask = Input["attention_mask"].to(self.device) # 注意力掩码
token_type_ids = Input["token_type_ids"].to(self.device) # 段落标记
return input_ids, attention_mask, token_type_ids
经验分享:max_length设置需要权衡模型性能和计算资源。中文BERT通常设128-512,酒店评论一般128足够。
4. 训练流程实现
4.1 训练参数配置
python复制# 在main.py中配置关键参数
model_name = 'MyModel'
num_class = 2
batchSize = 4
learning_rate = 0.0001 # BERT微调常用1e-5到5e-5
loss = nn.CrossEntropyLoss()
epoch = 3
device = 'cuda' if torch.cuda.is_available() else 'cpu'
4.2 优化器选择
使用AdamW优化器配合余弦退火热重启学习率调度:
python复制optimizer = torch.optim.AdamW(
model.parameters(),
lr=learning_rate,
weight_decay=0.0001 # L2正则化
)
scheduler = torch.optim.lr_scheduler.CosineAnnealingWarmRestarts(
optimizer,
T_0=20, # 周期长度
eta_min=1e-9 # 最小学习率
)
4.3 训练循环核心逻辑
python复制for i in range(epoch):
model.train()
for batch in tqdm(train_loader):
text, labels = batch[0], batch[1].to(device)
pred = model(text)
bat_loss = loss(pred, labels)
# 反向传播
bat_loss.backward()
optimizer.step()
scheduler.step()
optimizer.zero_grad()
# 梯度裁剪防止爆炸
torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)
# 累计指标
train_loss += bat_loss.item()
train_acc += (pred.argmax(1) == labels).sum().item()
4.4 模型验证与保存
python复制if i % val_epoch == 0:
model.eval()
with torch.no_grad():
for batch in val_loader:
val_text, val_labels = batch[0], batch[1].to(device)
val_pred = model(val_text)
val_loss = loss(val_pred, val_labels)
# 计算验证准确率
val_acc += (val_pred.argmax(1) == val_labels).sum().item()
# 保存最佳模型
if val_acc > max_acc:
torch.save(model, save_path+str(epoch)+"ckpt")
max_acc = val_acc
5. 实战技巧与问题排查
5.1 常见问题解决方案
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 训练loss不下降 | 学习率过大/过小 | 调整到1e-5~5e-5范围 |
| 验证准确率波动大 | 批次太小 | 增大batch_size到16/32 |
| GPU内存不足 | 序列长度或batch太大 | 减小max_length或batch_size |
| 过拟合 | 数据量不足 | 增加数据或添加Dropout层 |
5.2 性能优化技巧
- 混合精度训练:使用torch.cuda.amp自动混合精度,可减少显存占用并加速训练
python复制from torch.cuda.amp import autocast, GradScaler
scaler = GradScaler()
with autocast():
pred = model(text)
loss = criterion(pred, labels)
scaler.scale(loss).backward()
scaler.step(optimizer)
scaler.update()
- 梯度累积:当GPU内存不足时,可通过多步梯度累积模拟大batch
python复制accum_steps = 4
for i, batch in enumerate(train_loader):
loss = model(batch).mean()
loss = loss / accum_steps # 归一化
loss.backward()
if (i+1) % accum_steps == 0:
optimizer.step()
optimizer.zero_grad()
- 早停机制:当验证集性能连续多轮不提升时停止训练
python复制patience = 3
best_acc = 0
counter = 0
for epoch in range(epochs):
val_acc = validate()
if val_acc > best_acc:
best_acc = val_acc
counter = 0
torch.save(model.state_dict(), 'best.pt')
else:
counter += 1
if counter >= patience:
break
5.3 模型部署建议
- 量化压缩:使用torch.quantization减小模型体积
python复制model = torch.quantization.quantize_dynamic(
model, {torch.nn.Linear}, dtype=torch.qint8
)
- ONNX导出:转换为通用格式便于跨平台部署
python复制torch.onnx.export(
model,
dummy_input,
"model.onnx",
input_names=["input"],
output_names=["output"],
dynamic_axes={"input": {0: "batch"}, "output": {0: "batch"}}
)
- API服务化:使用Flask构建简易推理接口
python复制from flask import Flask, request
app = Flask(__name__)
@app.route('/predict', methods=['POST'])
def predict():
text = request.json['text']
inputs = tokenizer(text, return_tensors="pt")
outputs = model(**inputs)
return {"sentiment": "positive" if outputs.argmax() else "negative"}
6. 项目扩展方向
- 多标签分类:不仅判断好坏,还可识别"服务"、"卫生"等具体方面
- 细粒度情感分析:将1-5星评级作为连续值预测
- 对抗训练:添加FGM/PGD对抗训练提升模型鲁棒性
- 模型蒸馏:用大模型训练小模型提升推理速度
- 主动学习:自动识别不确定样本进行人工标注
在实际部署这个系统时,我发现几个关键点对最终效果影响很大:首先是数据质量,需要仔细清洗标注错误的样本;其次是学习率设置,BERT微调需要非常小的学习率;最后是文本长度,合理设置max_length可以显著提升推理速度而不损失太多准确率。建议初次尝试时先用小批量数据验证整个流程,再扩展到全量数据训练。