入门实践工程九:基于 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 小时前
1.8k star 的开源 token 使用量监控神器— TokenTracker
人工智能·ai编程
极客 - L U2 小时前
神经网络 - 激活函数、损失函数、优化器
人工智能·深度学习·神经网络
数字融合3 小时前
透明化视频三维矿山井下照明重建技术
人工智能·python·数码相机
yi0113 小时前
LeetCode 219:存在重复元素 II——哈希表记录“最近一次出现的位置”
数据结构·人工智能·笔记·python·算法·leetcode·哈希表
xiangzhihong83 小时前
创之星花店多端业务闭环拆解
人工智能
奈落243 小时前
AI 编程从助手到 Agent:基于两份资料看哪些环节可以交出去,哪些必须自己攥住
大数据·人工智能
Joker可视化开发平台3 小时前
AI短剧接棒真人剧:开机量跌七成,普通人进场窗口在收窄
大数据·人工智能
澳鹏Appen3 小时前
澳鹏电子书 | 强化学习环境:为AI智能体打造高保真训练场
人工智能
吴佳浩3 小时前
单卡5090跑125B 大模型:Strata 把服务器级 MoE 拉进普通 PC
人工智能
Data-Miner3 小时前
AI做表格软件哪个好?专业评测:五维对比看清差距
人工智能