一、Jupyter Notebook版本(带详细注释)

15.6-项目实战:电影评论情感分类

Pipeline

# ==================== 导入核心依赖库 ====================
# 导入PyTorch核心库,用于构建和训练神经网络
import torch
# 导入PyTorch优化器模块,用于定义模型训练的优化算法(如AdamW)
import torch.optim as optim
# 导入PyTorch神经网络模块,用于定义网络层、损失函数
import torch.nn as nn

# 从Huggingface Datasets库导入加载数据集的函数,用于获取IMDB电影评论数据集
from datasets import load_dataset
# 从Transformers库导入pipeline工具,一站式完成模型加载、文本处理、推理
from transformers import pipeline

# 导入numpy,用于数值计算(如准确率统计)
import numpy as np
# 导入matplotlib,用于绘制训练损失/准确率曲线
import matplotlib.pyplot as plt
# 导入tqdm,用于显示训练进度条,直观展示训练过程
from tqdm import *
# 导入sys,用于控制tqdm进度条的输出位置
import sys

# 导入警告处理模块,屏蔽无关警告(避免干扰输出)
import warnings
warnings.filterwarnings('ignore')  # 屏蔽所有警告
warnings.simplefilter('ignore')    # 设置简单过滤规则

# ==================== 设备配置 ====================
# 判断当前环境是否有可用的GPU(CUDA),有则用GPU,无则用CPU
# GPU能大幅提升模型训练/推理速度,是深度学习的核心硬件加速手段
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
# ==================== 快速情感分析(Pipeline) ====================
# 创建情感分析pipeline:
# - "sentiment-analysis"指定任务类型为情感分类
# - pipeline会自动加载默认模型(distilbert-base-uncased-finetuned-sst-2-english)
# - 该模型是轻量级DistilBERT,适配英文情感分类任务,速度快、效果好
classifier = pipeline("sentiment-analysis")

# 测试正向评论:输出情感标签(POSITIVE/正向)和置信度(score,越接近1越可信)
result = classifier("This is a great movie. I enjoyed it a lot!")[0]
print(result)  # 输出:{'label': 'POSITIVE', 'score': 0.999877...}

# 测试负向评论:
result = classifier("This movie is so bad, I almost fell asleep.")[0]
print(result)  # 输出:{'label': 'NEGATIVE', 'score': 0.999785...}

数据集查看

# 从Huggingface Datasets库加载IMDB电影评论数据集:
# - IMDB数据集包含5万条英文电影评论,2.5万训练集+2.5万测试集
# - 每条数据包含text(评论文本)和label(情感标签:0=负向,1=正向)
# - 首次加载会自动下载并缓存到本地(~/.cache/huggingface/datasets/),后续加载直接用缓存
from datasets import load_dataset
imdb_dataset = load_dataset('imdb')

# 查看训练集第一条数据:text是评论内容,label=0(负向情感)
print(imdb_dataset['train'][0])
# 查看训练集最后一条数据:label=1(正向情感)
print(imdb_dataset['train'][-1])

数据处理

# ==================== 自定义Dataset类(适配PyTorch数据加载) ====================
# 继承PyTorch的Dataset类,实现自定义数据集加载逻辑,适配IMDB数据集
class Dataset(torch.utils.data.Dataset):
    def __init__(self, split):
        # 初始化:加载指定拆分的数据集('train'训练集/'test'测试集)
        self.dataset = load_dataset(path='imdb', split=split)

    def __len__(self):
        # 返回数据集总长度,PyTorch DataLoader需要该方法获取批量大小
        return len(self.dataset)

    def __getitem__(self, i):
        # 根据索引i获取单条数据:返回(评论文本,情感标签)
        text = self.dataset[i]['text']  # 评论文本
        label = self.dataset[i]['label']# 情感标签(0/1)
        return text, label

# 实例化训练集和测试集:
train_dataset = Dataset('train')  # 25000条训练数据
test_dataset = Dataset('test')   # 25000条测试数据
# 打印训练集和测试集的长度,验证数据集加载是否正确
print(len(train_dataset), len(test_dataset))  # 输出:25000 25000

词元化

# ==================== 加载分词器(Tokenization) ====================
# 从Transformers库导入AutoTokenizer,自动匹配预训练模型的分词器
# 'bert-base-cased'是BERT基础版(大小写敏感),适配英文文本处理
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained('bert-base-cased')
# 打印分词器信息:词汇表大小、最大输入长度、特殊符号等
tokenizer
# ==================== 数据批量处理函数(collate_fn) ====================
# 定义DataLoader的批量处理函数,将原始文本转换为模型可输入的张量
def collate_fn(data):
    # 从批量数据中分离文本和标签:data是列表,每个元素是(text, label)
    sents = [i[0] for i in data]    # 提取所有评论文本
    labels = [i[1] for i in data]   # 提取所有情感标签

    # 批量编码文本:
    # - batch_text_or_text_pairs:批量文本列表
    # - truncation=True:文本长度超过max_length时自动截断(BERT最大支持512)
    # - padding='max_length':文本长度不足时用[PAD]填充到max_length
    # - max_length=500:统一文本长度为500(兼顾效率和效果)
    # - return_tensors='pt':返回PyTorch张量(而非列表)
    # - return_length=True:返回每个文本的原始长度(可选,此处未使用)
    data = tokenizer.batch_encode_plus(batch_text_or_text_pairs=sents,
                                   truncation=True,
                                   padding='max_length',
                                   max_length=500,
                                   return_tensors='pt',
                                   return_length=True)

    # 提取编码后的核心张量:
    input_ids = data['input_ids']          # 文本编码后的数字序列(核心输入)
    attention_mask = data['attention_mask']# 注意力掩码:1=有效文本,0=填充(模型忽略0的位置)
    token_type_ids = data['token_type_ids']# 分句标记:单句子任务中全为0,多句子任务区分句子
    labels = torch.LongTensor(labels)      # 将标签转换为长整型张量

    # 返回模型训练所需的4个张量
    return input_ids, attention_mask, token_type_ids, labels

# ==================== 定义数据加载器(DataLoader) ====================
# 训练集加载器:
# - dataset=train_dataset:加载自定义训练集
# - batch_size=32:每次处理32条数据(批量大小,平衡速度和显存)
# - collate_fn=collate_fn:使用自定义批量处理函数
# - shuffle=True:训练前打乱数据顺序,避免过拟合
train_loader = torch.utils.data.DataLoader(dataset=train_dataset,
                                     batch_size=32,
                                     collate_fn=collate_fn,
                                     shuffle=True)

# 测试集加载器:参数与训练集一致,用于验证模型效果
test_loader = torch.utils.data.DataLoader(dataset=test_dataset,
                                              batch_size=32,
                                              collate_fn=collate_fn,
                                              shuffle=True)

建立模型

# ==================== 加载预训练BERT模型 ====================
# 从Transformers库导入BertModel,加载预训练的BERT基础模型
from transformers import BertModel

# 加载bert-base-cased预训练模型,并移到指定设备(GPU/CPU)
# 该模型包含12层Transformer编码器,是英文文本处理的基础模型
pretrained = BertModel.from_pretrained('bert-base-cased').to(device)

# 冻结预训练模型的参数:
# - param.requires_grad_(False):参数不参与梯度更新,固定BERT的核心语义知识
# - 只训练后续新增的分类层,避免破坏预训练效果,同时减少训练参数、提升速度
for param in pretrained.parameters():
    param.requires_grad_(False)
# ==================== 定义下游任务模型(情感分类) ====================
# 基于预训练BERT构建情感分类模型:仅新增一个全连接层做二分类
class Model(torch.nn.Module):
    def __init__(self):
        super().__init__()
        # 定义全连接层:
        # - 输入维度768:BERT的[CLS] token向量维度(固定)
        # - 输出维度2:情感分类(0=负向,1=正向)
        self.fc = torch.nn.Linear(768, 2)

    def forward(self, input_ids, attention_mask, token_type_ids):
        # 前向传播:
        # - with torch.no_grad():禁用梯度计算(预训练模型已冻结,无需计算)
        # - pretrained():调用BERT模型,输入编码后的张量,输出隐藏层特征
        with torch.no_grad():
            out = pretrained(input_ids=input_ids,
                       attention_mask=attention_mask,
                       token_type_ids=token_type_ids)
        
        # 提取[CLS] token的向量作为句子语义表示:
        # - out.last_hidden_state:BERT最后一层的隐藏层输出(shape: [batch_size, seq_len, 768])
        # - [:, 0]:取每个句子的第一个token([CLS]),shape变为[batch_size, 768]
        # - self.fc():通过全连接层转换为2维输出(每个类别的得分)
        out = self.fc(out.last_hidden_state[:, 0])
        
        # softmax归一化:将得分转换为概率(两个类别概率之和为1)
        out = out.softmax(dim=1)
        return out

# 实例化模型并移到指定设备
model = Model().to(device)
# ==================== 自定义训练器(Trainer)类 ====================
# 封装训练和验证逻辑,简化训练流程
class Trainer:
    def __init__(self, model, train_loader, valid_loader):
        # 初始化训练/验证数据加载器
        self.train_loader = train_loader
        self.valid_loader = valid_loader
        
        # 设备和模型配置
        self.device = device
        self.model = model.to(self.device)
        
        # 定义优化器:AdamW是Adam的改进版,适合Transformer模型,带权重衰减
        self.optimizer = optim.AdamW(self.model.parameters(), lr=0.001)
        # 定义损失函数:交叉熵损失(适配分类任务)
        self.criterion = nn.CrossEntropyLoss()
        # 定义学习率调度器:指数衰减,每轮学习率乘以0.95,避免训练后期震荡
        self.scheduler = optim.lr_scheduler.ExponentialLR(self.optimizer, gamma=0.95)
        
        # 记录训练过程:保存每轮的损失和验证准确率,用于后续绘图
        self.train_losses = []
        self.val_accuracy = []
    
    def train(self, num_epochs):
        # 训练主函数:num_epochs为训练轮数
        # tqdm显示训练进度条,file=sys.stdout确保进度条输出到控制台
        for epoch in tqdm(range(num_epochs), file=sys.stdout):
            # 初始化本轮总损失
            total_loss = 0

            # 切换模型为训练模式:启用dropout、批量归一化等训练层
            self.model.train()
            
            # 遍历训练集批量数据
            for input_ids, attention_mask, token_type_ids, labels in train_loader:
                # 梯度清零:避免上一轮梯度累积
                self.optimizer.zero_grad()
                # 模型前向传播:输入张量移到指定设备,获取预测结果
                outputs = self.model(input_ids=input_ids.to(self.device), 
                                     attention_mask=attention_mask.to(self.device), 
                                     token_type_ids=token_type_ids.to(self.device)).to(self.device)
                # 计算损失:预测结果vs真实标签
                loss = self.criterion(outputs, labels.to(self.device))
                # 反向传播:计算梯度
                loss.backward()
                # 优化器更新参数:根据梯度调整模型权重
                self.optimizer.step()
                # 累加本轮损失
                total_loss += loss.item()
            
            # 更新学习率:每轮训练结束后调整学习率
            self.scheduler.step()
            # 验证模型:计算测试集准确率
            accuracy = self.validate()
            
            # 记录本轮损失和准确率
            self.train_losses.append(total_loss)
            self.val_accuracy.append(accuracy)
            
            # 打印本轮结果:tqdm.write避免覆盖进度条
            tqdm.write("Epoch: {0} Loss: {1} Acc: {2}".format(
                epoch, self.train_losses[-1], self.val_accuracy[-1]))
    
    def validate(self):
        # 验证函数:计算模型在测试集上的准确率
        # 切换模型为评估模式:禁用dropout、批量归一化等训练层
        self.model.eval()
        
        # 初始化总数和正确数
        total = 0
        correct = 0
        
        # 禁用梯度计算:验证阶段无需计算梯度,提升速度、节省显存
        with torch.no_grad():
            # 遍历测试集批量数据
            for input_ids, attention_mask, token_type_ids, labels in self.valid_loader:
                # 模型前向传播
                outputs = self.model(input_ids=input_ids.to(self.device), 
                                     attention_mask=attention_mask.to(self.device), 
                                     token_type_ids=token_type_ids.to(self.device)).to(self.device)
                # 统计总数:累加批量大小
                total += labels.size(0)
                # 统计正确数:argmax(1)取概率最大的类别,与真实标签比较
                correct += (outputs.argmax(1) == labels.to(self.device)).sum().item()
        
        # 计算准确率:正确数/总数
        accuracy = correct / total
        return accuracy

模型训练和验证

# ==================== 模型训练 ====================
# 创建Trainer实例:传入模型、训练加载器、测试加载器
trainer = Trainer(model, train_loader, test_loader)
# 开始训练:迭代30轮(epoch),轮数越多效果越好,但训练时间越长
trainer.train(num_epochs = 30)
# ==================== 绘制训练损失曲线 ====================
# 绘制每轮训练的总损失:损失越低,模型拟合效果越好
plt.plot(trainer.train_losses, label='loss')  # 绘制损失曲线
plt.legend()  # 显示图例
plt.show()    # 显示图像(损失随轮数增加逐渐下降,趋于稳定)
# ==================== 绘制验证准确率曲线 ====================
# 绘制每轮验证的准确率:准确率越高,模型泛化效果越好
plt.plot(trainer.val_accuracy, label='accuracy')  # 绘制准确率曲线
plt.legend()  # 显示图例
plt.show()    # 显示图像(准确率随轮数增加逐渐上升,最终稳定在82%左右)

直接finetune

# ==================== 使用Transformers原生Trainer微调模型 ====================
# 导入必要的库
from datasets import load_dataset
from transformers import AutoTokenizer, AutoModelForSequenceClassification, TrainingArguments, Trainer
import numpy as np
import evaluate

# 加载IMDB数据集
dataset = load_dataset("imdb")

# 加载BERT分词器(与模型配套)
tokenizer = AutoTokenizer.from_pretrained("bert-base-cased")

# 定义文本分词函数:批量处理数据集
def tokenize_function(examples):
    # 对数据集的text列进行编码,统一长度(max_length=512,BERT默认最大值)
    return tokenizer(examples["text"], padding="max_length", truncation=True)

# 对数据集进行分词处理:batched=True表示批量处理,提升速度
tokenized_datasets = dataset.map(tokenize_function, batched=True)

# 采样小数据集(加速训练,演示用):
# - 训练集取前1000条,测试集取前1000条
small_train_dataset = tokenized_datasets["train"].shuffle(seed=0).select(range(1000))
small_eval_dataset = tokenized_datasets["test"].shuffle(seed=0).select(range(1000))

# 加载适配序列分类的BERT模型:
# - num_labels=2:二分类任务(正向/负向)
# - 自动添加分类头(classifier层),无需手动定义
model = AutoModelForSequenceClassification.from_pretrained("bert-base-cased", num_labels=2)

# 加载准确率评估指标
metric = evaluate.load("accuracy")

# 定义评估函数:计算预测结果的准确率
def compute_metrics(eval_pred):
    logits, labels = eval_pred  # eval_pred包含模型输出(logits)和真实标签
    predictions = np.argmax(logits, axis=-1)  # 取logits最大值作为预测类别
    return metric.compute(predictions=predictions, references=labels)  # 计算准确率

# 设置训练参数:
training_args = TrainingArguments(
    output_dir='./results',          # 模型输出目录(保存检查点、日志等)
    num_train_epochs=10,             # 训练轮数
    evaluation_strategy="epoch")     # 每轮结束后评估一次

# 创建Transformers原生Trainer实例:封装训练/验证逻辑
trainer = Trainer(
    model=model,                     # 待训练的模型
    args=training_args,              # 训练参数
    train_dataset=small_train_dataset,  # 训练数据集
    eval_dataset=small_eval_dataset,    # 验证数据集
    compute_metrics=compute_metrics,    # 评估函数
)

# 开始训练
trainer.train()

# 保存训练好的模型到本地
model.save_pretrained('./results/imdb_model')
# ==================== 加载微调后的模型并测试 ====================
# 加载本地保存的模型
model = AutoModelForSequenceClassification.from_pretrained('./results/imdb_model')

# 创建情感分析pipeline:使用自定义微调的模型和分词器
classifier = pipeline('sentiment-analysis', model=model, tokenizer=tokenizer)

# 测试正向评论:LABEL_1对应正向(模型训练时自动分配的标签)
result = classifier('This is a great movie. I enjoyed it a lot!')
print(result)  # 输出:[{'label': 'LABEL_1', 'score': 0.999920...}]

# 测试负向评论:LABEL_0对应负向
result = classifier('This movie is so bad, I almost fell asleep.')
print(result)  # 输出:[{'label': 'LABEL_0', 'score': 0.999885...}]

二、核心知识点梳理

2.1 关键概念(小白友好版)

概念 定义 小白解释
情感分类 对文本的情感倾向进行二分类(正向/负向) 判断电影评论是好评还是差评
Huggingface Pipeline 封装好的一站式工具,自动完成“文本处理→模型推理→结果输出” 一行代码实现情感分析,不用手动写复杂的模型加载/推理逻辑
IMDB数据集 5万条英文电影评论数据集(2.5万训练+2.5万测试),label=0负向/1正向 专门用于情感分类的经典数据集,相当于“深度学习的练习题”
词元化(Tokenization) 将文本转换为模型可识别的数字序列 把“Great movie”变成[101, 1939, 3185, 102],模型能看懂数字
Attention Mask 标记有效文本(1)和填充(0),模型忽略填充位置 告诉模型“这些0是补的,不用计算”,避免干扰结果
[CLS] Token BERT输入开头的特殊标记,其向量代表整个句子的语义 用一个向量概括整段评论的含义,输入到分类层判断情感
预训练模型微调(Finetune) 在预训练BERT基础上,训练下游任务的分类层 站在巨人的肩膀上,不用从零训练模型,只改最后一层
冻结(Freeze) 固定预训练模型参数,只训练新增的分类层 不让BERT的核心语义知识被破坏,训练更快、效果更好
学习率调度器 训练过程中动态调整学习率,避免后期震荡 训练前期大步走,后期小步走,更容易找到最优解
Transformers Trainer 封装好的训练器,自动完成训练、验证、保存 不用手动写训练循环,一行代码启动训练,适合快速开发

2.2 核心库与函数用法表

库/函数 作用 关键参数/方法 适用场景
transformers.pipeline 快速构建推理管道 task="sentiment-analysis" 快速验证模型效果,原型开发
datasets.load_dataset 加载开源数据集 name="imdb" 获取标准化的训练/测试数据
AutoTokenizer 自动匹配模型的分词器 from_pretrained("bert-base-cased") 文本→数字编码,保证与模型兼容
tokenizer.batch_encode_plus 批量编码文本 truncation=True, padding="max_length" 统一文本长度,适配批量训练
BertModel 加载BERT基础模型 from_pretrained("bert-base-cased").to(device) 提取文本的语义向量
AutoModelForSequenceClassification 加载带分类头的BERT num_labels=2 直接用于分类任务,无需手动定义分类层
TrainingArguments 配置训练参数 output_dir, num_train_epochs, evaluation_strategy 定义训练的轮数、输出路径、评估策略
Trainer(Transformers) 封装训练逻辑 model, args, train_dataset, eval_dataset 自动化训练,支持断点续训、日志记录
evaluate.load("accuracy") 加载评估指标 compute(predictions, references) 计算模型准确率,评估效果

2.3 自定义Trainer类核心逻辑

方法 核心步骤 作用
__init__ 初始化优化器(AdamW)、损失函数(CrossEntropyLoss)、学习率调度器 配置训练所需的核心组件
train 遍历轮数→批量训练→损失计算→反向传播→学习率更新→验证准确率 实现完整的训练流程
validate 禁用梯度→批量推理→统计正确数→计算准确率 评估模型泛化能力,避免过拟合

三、PyCharm版本代码(可直接运行)

# ==================== 环境配置与库导入 ====================
import torch
import torch.optim as optim
import torch.nn as nn
from datasets import load_dataset
from transformers import pipeline, AutoTokenizer, BertModel, AutoModelForSequenceClassification, TrainingArguments, Trainer
import numpy as np
import matplotlib.pyplot as plt
from tqdm import *
import sys
import warnings
import evaluate

# 屏蔽警告
warnings.filterwarnings('ignore')
warnings.simplefilter('ignore')

# 设备配置:优先使用GPU
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(f"使用设备:{device}")

# ==================== 1. 快速情感分析(Pipeline) ====================
print("\n=== 步骤1:快速情感分析(Pipeline) ===")
classifier = pipeline("sentiment-analysis")
# 测试正向评论
result_pos = classifier("This is a great movie. I enjoyed it a lot!")[0]
print("正向评论结果:", result_pos)
# 测试负向评论
result_neg = classifier("This movie is so bad, I almost fell asleep.")[0]
print("负向评论结果:", result_neg)

# ==================== 2. 加载并查看IMDB数据集 ====================
print("\n=== 步骤2:加载IMDB数据集 ===")
imdb_dataset = load_dataset('imdb')
print("训练集第一条数据:", imdb_dataset['train'][0]['text'][:100], "...")  # 截断显示
print("训练集第一条标签:", imdb_dataset['train'][0]['label'])

# ==================== 3. 自定义Dataset与DataLoader ====================
print("\n=== 步骤3:数据加载器配置 ===")
# 自定义Dataset类
class Dataset(torch.utils.data.Dataset):
    def __init__(self, split):
        self.dataset = load_dataset(path='imdb', split=split)
    def __len__(self):
        return len(self.dataset)
    def __getitem__(self, i):
        return self.dataset[i]['text'], self.dataset[i]['label']

# 实例化数据集
train_dataset = Dataset('train')
test_dataset = Dataset('test')
print(f"训练集长度:{len(train_dataset)},测试集长度:{len(test_dataset)}")

# 加载分词器
tokenizer = AutoTokenizer.from_pretrained('bert-base-cased')

# 批量处理函数
def collate_fn(data):
    sents = [i[0] for i in data]
    labels = [i[1] for i in data]
    data = tokenizer.batch_encode_plus(batch_text_or_text_pairs=sents,
                                   truncation=True,
                                   padding='max_length',
                                   max_length=500,
                                   return_tensors='pt',
                                   return_length=True)
    input_ids = data['input_ids']
    attention_mask = data['attention_mask']
    token_type_ids = data['token_type_ids']
    labels = torch.LongTensor(labels)
    return input_ids, attention_mask, token_type_ids, labels

# 定义DataLoader(缩小batch_size,适配低配设备)
train_loader = torch.utils.data.DataLoader(dataset=train_dataset,
                                     batch_size=8,  # 原32,改为8减少显存占用
                                     collate_fn=collate_fn,
                                     shuffle=True)
test_loader = torch.utils.data.DataLoader(dataset=test_dataset,
                                              batch_size=8,
                                              collate_fn=collate_fn,
                                              shuffle=True)

# ==================== 4. 构建模型 ====================
print("\n=== 步骤4:构建情感分类模型 ===")
# 加载预训练BERT并冻结参数
pretrained = BertModel.from_pretrained('bert-base-cased').to(device)
for param in pretrained.parameters():
    param.requires_grad_(False)

# 定义下游任务模型
class Model(torch.nn.Module):
    def __init__(self):
        super().__init__()
        self.fc = torch.nn.Linear(768, 2)
    def forward(self, input_ids, attention_mask, token_type_ids):
        with torch.no_grad():
            out = pretrained(input_ids=input_ids,
                       attention_mask=attention_mask,
                       token_type_ids=token_type_ids)
        out = self.fc(out.last_hidden_state[:, 0])
        out = out.softmax(dim=1)
        return out

model = Model().to(device)
print("模型构建完成")

# ==================== 5. 自定义Trainer类 ====================
class Trainer:
    def __init__(self, model, train_loader, valid_loader):
        self.train_loader = train_loader
        self.valid_loader = valid_loader
        self.device = device
        self.model = model.to(self.device)
        self.optimizer = optim.AdamW(self.model.parameters(), lr=0.001)
        self.criterion = nn.CrossEntropyLoss()
        self.scheduler = optim.lr_scheduler.ExponentialLR(self.optimizer, gamma=0.95)
        self.train_losses = []
        self.val_accuracy = []
    
    def train(self, num_epochs):
        for epoch in tqdm(range(num_epochs), file=sys.stdout):
            total_loss = 0
            self.model.train()
            # 仅训练前100个批次(加速演示)
            for i, (input_ids, attention_mask, token_type_ids, labels) in enumerate(train_loader):
                if i >= 100:  # 限制批次数量,快速演示
                    break
                self.optimizer.zero_grad()
                outputs = self.model(input_ids=input_ids.to(self.device), 
                                     attention_mask=attention_mask.to(self.device), 
                                     token_type_ids=token_type_ids.to(self.device)).to(self.device)
                loss = self.criterion(outputs, labels.to(self.device))
                loss.backward()
                self.optimizer.step()
                total_loss += loss.item()
            self.scheduler.step()
            # 仅验证前50个批次(加速演示)
            accuracy = self.validate(limit_batch=50)
            self.train_losses.append(total_loss)
            self.val_accuracy.append(accuracy)
            tqdm.write(f"Epoch: {epoch} Loss: {total_loss:.2f} Acc: {accuracy:.4f}")
    
    def validate(self, limit_batch=None):
        self.model.eval()
        total = 0
        correct = 0
        with torch.no_grad():
            for i, (input_ids, attention_mask, token_type_ids, labels) in enumerate(self.valid_loader):
                if limit_batch and i >= limit_batch:
                    break
                outputs = self.model(input_ids=input_ids.to(self.device), 
                                     attention_mask=attention_mask.to(self.device), 
                                     token_type_ids=token_type_ids.to(self.device)).to(self.device)
                total += labels.size(0)
                correct += (outputs.argmax(1) == labels.to(self.device)).sum().item()
        accuracy = correct / total if total > 0 else 0
        return accuracy

# ==================== 6. 模型训练(简化版) ====================
print("\n=== 步骤5:模型训练(简化版,仅3轮) ===")
trainer = Trainer(model, train_loader, test_loader)
trainer.train(num_epochs = 3)  # 仅训练3轮,快速演示

# ==================== 7. 绘制训练曲线 ====================
print("\n=== 步骤6:绘制训练曲线 ===")
# 绘制损失曲线
plt.plot(trainer.train_losses, label='loss')
plt.title("Training Loss")
plt.legend()
plt.show()

# 绘制准确率曲线
plt.plot(trainer.val_accuracy, label='accuracy')
plt.title("Validation Accuracy")
plt.legend()
plt.show()

# ==================== 8. 使用Transformers Trainer微调(简化版) ====================
print("\n=== 步骤7:Transformers原生Trainer微调 ===")
# 加载数据集并分词
dataset = load_dataset("imdb")
tokenizer = AutoTokenizer.from_pretrained("bert-base-cased")
def tokenize_function(examples):
    return tokenizer(examples["text"], padding="max_length", truncation=True, max_length=128)  # 缩短长度加速

tokenized_datasets = dataset.map(tokenize_function, batched=True)
# 采样极小数据集(加速演示)
small_train_dataset = tokenized_datasets["train"].shuffle(seed=0).select(range(100))
small_eval_dataset = tokenized_datasets["test"].shuffle(seed=0).select(range(100))

# 加载模型
model = AutoModelForSequenceClassification.from_pretrained("bert-base-cased", num_labels=2)

# 定义评估函数
metric = evaluate.load("accuracy")
def compute_metrics(eval_pred):
    logits, labels = eval_pred
    predictions = np.argmax(logits, axis=-1)
    return metric.compute(predictions=predictions, references=labels)

# 训练参数
training_args = TrainingArguments(
    output_dir='./results',
    num_train_epochs=2,  # 仅2轮
    evaluation_strategy="epoch",
    per_device_train_batch_size=4,  # 小批量
    per_device_eval_batch_size=4)

# 训练模型
trainer = Trainer(
    model=model,
    args=training_args,
    train_dataset=small_train_dataset,
    eval_dataset=small_eval_dataset,
    compute_metrics=compute_metrics,
)
trainer.train()

# 保存并加载模型测试
model.save_pretrained('./results/imdb_model')
model = AutoModelForSequenceClassification.from_pretrained('./results/imdb_model')
classifier = pipeline('sentiment-analysis', model=model, tokenizer=tokenizer)

# 测试模型
print("\n=== 步骤8:测试微调后的模型 ===")
print("正向评论测试:", classifier('This is a great movie!'))
print("负向评论测试:", classifier('This movie is terrible!'))

print("\n=== 所有步骤执行完成 ===")

总结

  1. 核心流程:电影评论情感分类的核心是“文本编码→BERT提取语义→分类层输出情感”,分为快速推理(Pipeline)和自定义训练(冻结预训练模型+训练分类层)两种方式;
  2. 关键工具:Huggingface生态的三大核心工具(Datasets加载数据、Transformers加载模型/分词器、Trainer封装训练)能大幅简化开发流程,是NLP实战的首选;
  3. 优化技巧:冻结预训练模型参数可减少训练成本,调整批量大小/文本长度可适配不同硬件,学习率调度器能提升训练稳定性;
  4. 新手避坑
    • 确保模型和分词器版本匹配(如bert-base-cased的模型配同名分词器);
    • GPU显存不足时,减小batch_size或文本长度;
    • 训练轮数过多会过拟合,需结合验证准确率停止训练。

避坑提示

  1. 安装依赖:pip install torch transformers datasets evaluate tqdm matplotlib
  2. 模型下载慢:手动下载模型文件放到~/.cache/huggingface/hub/
  3. GPU显存不足:将batch_size改为4/2,max_length改为128;
  4. 中文情感分类:替换模型为bert-base-chinese,数据集改为中文评论数据集(如ChnSentiCorp);
  5. 训练时间长:优先用Pipeline快速验证,再用小数据集微调。
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐