入门实践工程九:基于 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()
相关推荐
樱木花道長5 分钟前
AMD Ross 深度系列 7:六组对照实验看Ross如何赋能AI完成Vivado 硬核IP配置
人工智能·fpga开发
OpenVINO 中文社区6 分钟前
英特尔 × ADG:从 Agent 落地到 3DGS 重建,GDPS 2026 空间计算实战工作坊技术复盘
人工智能·3d·空间计算
W***259210 分钟前
视频怎么单独提取声音:全设备通用操作指南与后续创作全流程
人工智能
龙孚信息12 分钟前
一座塔,与三十年的文件夹
人工智能
LV技术派12 分钟前
【成为 AI Native Builder · 01】开篇:为什么 AI 总在返工?
人工智能·程序员·ai编程
QuZhengRong17 分钟前
【AI】大模型推理中 KV Cache 原理解析
人工智能·大模型·agent·提示词·推理
论文复现现场22 分钟前
2026 低成本 AI Agent 模型怎么选?DeepSeek V4.1 Flash、Qwen3.8 Flash、GLM-5.3 Flash 对比
数据库·人工智能·qwen·ai agent·glm·deepseek
SEO_juper23 分钟前
Go 站点地图生成器:从首页爬全站,一键生成 sitemap.xml
xml·开发语言·人工智能·golang·node.js·seo·外贸独立站
长弓三石25 分钟前
Spring Boot 4.1 + AgentScope:企业级 Agent 平台的 MCP 双向集成实战
人工智能·spring boot·后端·harness·开源智能体
乌恩大侠1 小时前
从 5G 通信到厘米级感知:基于 NVIDIA Aerial、SRS 与 UL-TDOA 构建 6G 无线定位实验平台
人工智能·5g·电信大模型