入门实践工程九:基于 BERT 的中文情感分类微调~附:安装依赖库及工程源码

🔥 别再死磕枯燥理论了!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 做情感路由

工程源码

main.py

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()
相关推荐
空堂与归1 小时前
大模型幻觉:一篇文章搞懂成因、分类与四种解决方案
人工智能
CoordClaw1 小时前
主流多智能体架构为什么大多失败——它们输在结构,不在模型
人工智能·架构
一只游鱼1 小时前
PianoAgent:开源 AI 钢琴作曲 Agent,用自然语言谱写钢琴曲
人工智能
weixin_446260851 小时前
资源授权:面向部署式AI智能体的参与式治理机制设计模型
人工智能
康谋自动驾驶1 小时前
高保真+强可控:自动驾驶仿真的混合渲染方案
人工智能·机器学习·自动驾驶
JJJennie7772 小时前
ChatGPT 更新 GPT-5.6 Sol,免费用户将可无限文本聊天
人工智能·gpt·chatgpt
OceanBase数据库官方博客2 小时前
让 DRP全域数据智能流转OceanBase AI 数据库支撑央国企落地穿透式监
数据库·人工智能·oceanbase
tedcloud1232 小时前
Impeccable 部署指南:开源前端设计工具 Linux 环境搭建实践
linux·运维·服务器·前端·人工智能·开源
今天AI了吗2 小时前
Python 基础语法从入门到使用详解
开发语言·人工智能·python