🔥 别再死磕枯燥理论了!AI 时代,拿实战作品说话才是硬道理!(原创不易哈,希望可以帮到还有些许学习劲儿的同学们) 🔥
【进阶版还在创作中,耗费精力中......】
入门实践工程九:基于 BERT 的中文情感分类微调~附:安装依赖库及工程源码
简介
使用 Hugging Face Transformers 加载 bert-base-chinese,在一份内置中文评论小数据集上微调二分类模型(正向/负向情感),并支持对自定义句子进行预测,快速体验 NLP 迁移学习全流程。
工程详细介绍
核心思想: BERT 是双向 Transformer 编码器,在大规模语料上预训练后已具备通用语言理解能力;微调只需在其顶部接一个分类头,用少量标注数据即可适配下游情感分类任务,体现「预训练 + 微调」迁移学习的威力。
实现方法:
- 数据: 内置 20 条中文购物评论(10 正 10 负),二分类。
- 模型: bert-base-chinese(首次下载约 400MB)+ 顶层线性分类头(2 类)。
- 流程: Tokenizer 编码(padding/truncation 到 64)→ AdamW + 线性学习率调度微调 2 轮 → 评估准确率 → 对自定义句子预测 → 保存微调后的模型与 tokenizer。
- 输出: 每轮训练/评估准确率 + 4 句预测 + bert_sentiment/ 目录。
目录结构
09_bert_sentiment/
├── main.py # 训练 + 评估 + 预测主程序(数据集内置)
├── requirements.txt
└── bert_sentiment/ # 微调后的模型保存目录
正确安装
bash
pip install torch transformers numpy
python main.py
- 首次运行自动下载 bert-base-chinese 权重(约 400MB),需联网。
- 如需 GPU,按 PyTorch 官网选择对应 CUDA 版本安装 torch。
运行方式
bash
pip install -r requirements.txt
python main.py
说明
- 数据集已内置在
main.py中,无需额外准备。 bert-base-chinese模型权重会在首次运行时自动下载,需联网(约 400MB)。- CPU 可跑(较慢),有 GPU 更快;默认 2 个 Epoch 仅作演示。
预期结果
- 打印每个 Epoch 的训练损失与评估准确率
- 末尾对 4 句自定义评论做情感预测
- 微调模型保存到
bert_sentiment/
扩展方向
- 替换为更大的中文评论数据集(如 ChnSentiCorp)
- 改为多分类(如新闻分类、意图识别)
- 接入到 05 的智能客服 Demo 做情感路由
工程源码
python
"""
入门实践工程九:基于 BERT 的中文情感分类微调
====================================
使用 Hugging Face Transformers 加载 bert-base-chinese,
在一份内置的中文评论小数据集上微调二分类模型(正向/负向情感),
并支持对自定义句子进行预测。
数据集内置在文件中(data/train.txt, eval.txt),无需额外下载。
注意:bert-base-chinese 模型权重会在首次运行时自动下载,需要联网。
运行:
python main.py # 训练 + 评估 + 示例预测
"""
import argparse
import os
import random
import numpy as np
import torch
from torch.optim import AdamW
from torch.utils.data import Dataset, DataLoader
from transformers import BertTokenizer, BertForSequenceClassification, get_scheduler
# ---------------- 全局配置 ----------------
SEED = 42
MODEL_NAME = "bert-base-chinese"
MAX_LEN = 64
BATCH_SIZE = 16
EPOCHS = 2
LR = 2e-5
DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu")
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
# 内置小数据集:0=负向,1=正向
TRAIN_DATA = [
("这件衣服质量很好,穿着很舒服", 1),
("物流速度很快,包装也很好", 1),
("宝贝收到了,非常喜欢,好评!", 1),
("产品质量不错,性价比很高", 1),
("卖家服务态度很好,很满意", 1),
("用了一段时间,效果很好,推荐", 1),
("这个商品真的太棒了,超出预期", 1),
("物美价廉,会再来回购的", 1),
("手感很好,做工精致,满意", 1),
("宝贝很喜欢,物流也快,好评", 1),
("商品质量太差了,很失望", 0),
("物流太慢了,等了好久才收到", 0),
("做工粗糙,和描述完全不符", 0),
("客服态度很差,不推荐购买", 0),
("用了一天就坏了,质量不行", 0),
("性价比太低,不值这个价", 0),
("收到货破损了,售后也不处理", 0),
("颜色和图片差太多,很失望", 0),
("完全没法用,差评差评", 0),
("包装很差,商品都被压坏了", 0),
]
EVAL_DATA = [
("很好用,推荐购买", 1),
("性价比高,很满意", 1),
("质量太差,不建议买", 0),
("物流慢,体验不好", 0),
]
def set_seed(seed: int = 42):
random.seed(seed)
np.random.seed(seed)
torch.manual_seed(seed)
if torch.cuda.is_available():
torch.cuda.manual_seed_all(seed)
# ---------------- 数据集 ----------------
class SentimentDataset(Dataset):
def __init__(self, data, tokenizer, max_len=MAX_LEN):
self.data = data
self.tokenizer = tokenizer
self.max_len = max_len
def __len__(self):
return len(self.data)
def __getitem__(self, idx):
text, label = self.data[idx]
enc = self.tokenizer(
text,
max_length=self.max_len,
padding="max_length",
truncation=True,
return_tensors="pt",
)
return {
"input_ids": enc["input_ids"].squeeze(0),
"attention_mask": enc["attention_mask"].squeeze(0),
"label": torch.tensor(label, dtype=torch.long),
}
# ---------------- 训练 & 评估 ----------------
def train_epoch(model, loader, optimizer, scheduler):
model.train()
total_loss, total, correct = 0.0, 0, 0
for batch in loader:
input_ids = batch["input_ids"].to(DEVICE)
attention_mask = batch["attention_mask"].to(DEVICE)
labels = batch["label"].to(DEVICE)
optimizer.zero_grad()
out = model(input_ids=input_ids, attention_mask=attention_mask, labels=labels)
loss = out.loss
loss.backward()
optimizer.step()
scheduler.step()
total_loss += loss.item() * labels.size(0)
correct += (out.logits.argmax(1) == labels).sum().item()
total += labels.size(0)
return total_loss / total, correct / total
@torch.no_grad()
def eval_epoch(model, loader):
model.eval()
total, correct = 0, 0
for batch in loader:
input_ids = batch["input_ids"].to(DEVICE)
attention_mask = batch["attention_mask"].to(DEVICE)
labels = batch["label"].to(DEVICE)
out = model(input_ids=input_ids, attention_mask=attention_mask)
correct += (out.logits.argmax(1) == labels).sum().item()
total += labels.size(0)
return correct / total
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--epochs", type=int, default=EPOCHS)
args = parser.parse_args()
set_seed(SEED)
print(f"设备: {DEVICE}")
print(f"加载 Tokenizer 与模型: {MODEL_NAME} ...")
tokenizer = BertTokenizer.from_pretrained(MODEL_NAME)
model = BertForSequenceClassification.from_pretrained(
MODEL_NAME, num_labels=2
).to(DEVICE)
train_ds = SentimentDataset(TRAIN_DATA, tokenizer)
eval_ds = SentimentDataset(EVAL_DATA, tokenizer)
train_loader = DataLoader(train_ds, batch_size=BATCH_SIZE, shuffle=True)
eval_loader = DataLoader(eval_ds, batch_size=BATCH_SIZE)
optimizer = AdamW(model.parameters(), lr=LR)
num_steps = len(train_loader) * args.epochs
scheduler = get_scheduler("linear", optimizer=optimizer, num_training_steps=num_steps)
for epoch in range(1, args.epochs + 1):
loss, acc = train_epoch(model, train_loader, optimizer, scheduler)
val_acc = eval_epoch(model, eval_loader)
print(f"Epoch {epoch}: 训练损失={loss:.4f} 训练准确率={acc:.4f} 评估准确率={val_acc:.4f}")
# 示例预测
print("\n--- 自定义句子预测 ---")
predict_sentences(model, tokenizer, [
"质量很好,非常满意,会回购!",
"太差了,完全不能用,差评",
"物流挺快的,包装可以",
"做工粗糙,很失望",
])
save_path = os.path.join(BASE_DIR, "bert_sentiment")
model.save_pretrained(save_path)
tokenizer.save_pretrained(save_path)
print(f"\n微调后的模型已保存到: {save_path}")
@torch.no_grad()
def predict_sentences(model, tokenizer, sentences):
model.eval()
enc = tokenizer(
sentences, max_length=MAX_LEN, padding="max_length",
truncation=True, return_tensors="pt",
).to(DEVICE)
out = model(**enc)
preds = out.logits.argmax(1).cpu().tolist()
for s, p in zip(sentences, preds):
label = "正向" if p == 1 else "负向"
print(f" 「{s}」 -> {label}")
if __name__ == "__main__":
main()