4.1 文本分类实战


文档摘要

4.1 文本分类实战 文本分类是自然语言处理中最基础也最广泛应用的下游任务之一。无论是情感分析、新闻分类、垃圾邮件检测,还是意图识别、内容审核,本质上都是文本分类问题。本章将带你从零开始,使用 HuggingFace Transformers 生态完成一个完整的文本分类项目。 4.1.1 任务定义与分类体系 文本分类的目标是将一段文本映射到预定义的类别标签上。根据类别数量,可以分为: 二分类:最简单的形式,如正面/负面情感判断。典型数据集包括 SST-2(斯坦福情感树库)、IMDB 影评数据集。 多分类:多个互斥类别,如新闻分类(体育、财经、科技、娱乐等)。经典数据集有 AG News、THUCNews 中文新闻分类数据集。

4.1 文本分类实战

文本分类是自然语言处理中最基础也最广泛应用的下游任务之一。无论是情感分析、新闻分类、垃圾邮件检测,还是意图识别、内容审核,本质上都是文本分类问题。本章将带你从零开始,使用 HuggingFace Transformers 生态完成一个完整的文本分类项目。

4.1.1 任务定义与分类体系

文本分类的目标是将一段文本映射到预定义的类别标签上。根据类别数量,可以分为:

二分类:最简单的形式,如正面/负面情感判断。典型数据集包括 SST-2(斯坦福情感树库)、IMDB 影评数据集。

多分类:多个互斥类别,如新闻分类(体育、财经、科技、娱乐等)。经典数据集有 AG News、THUCNews 中文新闻分类数据集。

多标签分类:一个样本可以同时属于多个类别,如电影类型标签(同时是动作片和科幻片)。

层次分类:类别之间存在层级关系,如先分大类(科技),再分小类(AI、硬件、互联网)。

在本节中,我们重点讲解二分类和多分类两种最常见的场景。

4.1.2 数据准备与预处理

加载数据集

HuggingFace Datasets 库提供了数千个公开数据集的统一接口。以中文情感分析为例:

from datasets import load_dataset # 加载中文情感分析数据集 dataset = load_dataset("seamew/ChnSentiCorp") # 查看数据结构 print(dataset["train"][0]) # {'text': '这个宾馆比较陈旧了...', 'label': 1} # 查看标签分布 from collections import Counter print(Counter(dataset["train"]["label"])) # Counter({1: 5600, 0: 5600}) 正负面基本均衡

对于新闻分类任务,可以使用 AG News 数据集:

# AG News: 4分类(世界、体育、商业、科技) news_dataset = load_dataset("ag_news") print(news_dataset["train"].features) # {'text': Value(dtype='string'), 'label': ClassLabel(num_classes=4, ...)}

Tokenization

Transformers 模型的核心输入是 Token ID 序列。HuggingFace 的 Tokenizer 负责将原始文本转换为模型可接受的格式:

from transformers import AutoTokenizer # 根据预训练模型自动加载对应的 Tokenizer model_name = "bert-base-chinese" tokenizer = AutoTokenizer.from_pretrained(model_name) # 单条文本处理 inputs = tokenizer("这家餐厅的菜品非常好吃", return_tensors="pt") print(inputs) # {'input_ids': tensor([[101, 1962, ...]]), # 'token_type_ids': tensor([[0, 0, ...]]), # 'attention_mask': tensor([[1, 1, ...]])}

Tokenization 的三种策略

  1. Word-based:按空格分词,如 "Transformers" → ["Transformers"],词表庞大但简单。
  2. Character-based:按字符分词,词表极小,但序列过长,丢失语义信息。
  3. Subword-based:折中方案,常用高频词整体保留,低频词拆分为子词。BERT 使用 WordPiece,GPT-2 使用 BPE,XLNet 使用 SentencePiece。

对于中文,BERT 的 WordPiece Tokenizer 以字符为最小粒度,天然适配中文:

tokens = tokenizer.tokenize("自然语言处理很有趣") # ['自', '然', '语', '言', '处', '理', '很', '有', '趣'] # 中文字符被保留为完整 token

数据预处理流水线

将 Tokenization 封装为可复用的函数,并应用到整个数据集:

def preprocess_function(examples): return tokenizer( examples["text"], padding="max_length", max_length=128, truncation=True, ) # 批量处理,利用多线程加速 tokenized_datasets = dataset.map( preprocess_function, batched=True, num_proc=4, # 并行进程数 remove_columns=dataset["train"].column_names, # 移除原始列 ) # 设置数据格式为 PyTorch Tensors tokenized_datasets = tokenized_datasets.with_format("torch")

数据增强

当训练数据不足时,可以通过数据增强提升模型泛化能力:

# 使用 nlpaug 库进行数据增强 # 同义词替换:将部分词替换为同义词 # 回译增强:中文→英文→中文,生成语义相近的改写 # 随机删除:随机删除部分词 # 随机交换:交换相邻词的位置

4.1.3 模型构建与微调

方案一:AutoModelForSequenceClassification

最简单直接的方式,使用 HuggingFace 的高层 API:

from transformers import AutoModelForSequenceClassification # 自动加载预训练模型并添加分类头 model = AutoModelForSequenceClassification.from_pretrained( "bert-base-chinese", num_labels=2, # 二分类 id2label={0: "负面", 1: "正面"}, label2id={"负面": 0, "正面": 1}, ) print(model) # BertForSequenceClassification( # (bert): BertModel(...) # (dropout): Dropout(p=0.1) # (classifier): Linear(in_features=768, out_features=2) # )

模型的分类头在 [CLS] token 的表示之上堆叠一个线性层 + Softmax,将 BERT 输出的 768 维向量映射到类别数维度。

方案二:Trainer API

HuggingFace 的 Trainer 类封装了训练循环、评估、日志等全部逻辑:

from transformers import TrainingArguments, Trainer from datasets import load_metric # 训练参数配置 training_args = TrainingArguments( output_dir="./sentiment_model", num_train_epochs=3, per_device_train_batch_size=32, per_device_eval_batch_size=64, learning_rate=2e-5, weight_decay=0.01, warmup_ratio=0.1, eval_strategy="epoch", # 每个 epoch 评估一次 save_strategy="epoch", load_best_model_at_end=True, metric_for_best_model="f1", logging_dir="./logs", logging_steps=100, fp16=True, # 混合精度训练,加速 2-3 倍 ) # 定义评估指标 def compute_metrics(eval_pred): import numpy as np logits, labels = eval_pred predictions = np.argmax(logits, axis=1) # 使用 sklearn 计算多种指标 from sklearn.metrics import precision_score, recall_score, f1_score, accuracy_score return { "accuracy": accuracy_score(labels, predictions), "precision": precision_score(labels, predictions, average="binary"), "recall": recall_score(labels, predictions, average="binary"), "f1": f1_score(labels, predictions, average="binary"), } # 构建 Trainer trainer = Trainer( model=model, args=training_args, train_dataset=tokenized_datasets["train"], eval_dataset=tokenized_datasets["validation"], tokenizer=tokenizer, compute_metrics=compute_metrics, ) # 开始训练 trainer.train() # 保存模型 trainer.save_model("./sentiment_model/best") tokenizer.save_pretrained("./sentiment_model/best")

方案三:自定义训练循环

当需要更灵活的控制时,可以编写原生 PyTorch 训练循环:

import torch from torch.optim import AdamW from torch.utils.data import DataLoader # 数据加载器 train_loader = DataLoader( tokenized_datasets["train"], shuffle=True, batch_size=32, collate_fn=None, # 已经 pad 过 ) # 优化器(Transformers 推荐使用 AdamW) optimizer = AdamW(model.parameters(), lr=2e-5, weight_decay=0.01) # 学习率调度器 from transformers import get_linear_schedule_with_warmup total_steps = len(train_loader) * 3 # 3 个 epoch scheduler = get_linear_schedule_with_warmup( optimizer, num_warmup_steps=int(0.1 * total_steps), num_training_steps=total_steps, ) # 训练循环 device = torch.device("cuda" if torch.cuda.is_available() else "cpu") model.to(device) for epoch in range(3): model.train() total_loss = 0 for batch in train_loader: optimizer.zero_grad() inputs = { "input_ids": batch["input_ids"].to(device), "attention_mask": batch["attention_mask"].to(device), "token_type_ids": batch["token_type_ids"].to(device), "labels": batch["label"].to(device), } outputs = model(**inputs) loss = outputs.loss loss.backward() torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0) optimizer.step() scheduler.step() total_loss += loss.item() avg_loss = total_loss / len(train_loader) print(f"Epoch {epoch+1}: Loss = {avg_loss:.4f}")

4.1.4 模型评估与调优

评估指标选择

文本分类的评估不仅仅是准确率,需要根据任务特点选择合适指标:

  • 准确率(Accuracy):类别均衡时有效
  • 精确率(Precision):关注预测为正类的准确性,适用于误报代价高的场景(如垃圾邮件检测)
  • 召回率(Recall):关注正类被正确识别的比例,适用于漏报代价高的场景(如医疗诊断)
  • F1 值:精确率和召回率的调和平均数,是不平衡数据集的黄金标准
  • AUC-ROC:模型排序能力的综合评估
# 评估测试集 test_results = trainer.evaluate(tokenized_datasets["test"]) print(test_results) # {'eval_accuracy': 0.952, 'eval_precision': 0.948, # 'eval_recall': 0.957, 'eval_f1': 0.952, ...}

错误分析

模型评估不应止步于数字,还需要深入分析错误样本:

# 获取预测结果 predictions = trainer.predict(tokenized_datasets["test"]) preds = predictions.predictions.argmax(axis=1) # 找出错误样本 import numpy as np errors = [] for i, (pred, label) in enumerate(zip(preds, tokenized_datasets["test"]["label"])): if pred != label: errors.append({ "text": dataset["test"]["text"][i], "true_label": label, "pred_label": pred, }) # 分析错误模式 for err in errors[:5]: print(f"文本: {err['text'][:50]}...") print(f"真实标签: {err['true_label']}, 预测标签: {err['pred_label']}\n")

超参数调优

HuggingFace 与 Optuna 集成,支持自动超参数搜索:

from transformers import Trainer from optuna.integration import TransformersObjective def hp_space(trial): return { "learning_rate": trial.suggest_float("learning_rate", 1e-5, 5e-5, log=True), "num_train_epochs": trial.suggest_int("num_train_epochs", 2, 5), "per_device_train_batch_size": trial.suggest_categorical( "per_device_train_batch_size", [16, 32, 64] ), "weight_decay": trial.suggest_float("weight_decay", 0.0, 0.1), } # 执行超参数搜索 trainer.hyperparameter_search( direction="maximize", hp_space=hp_space, n_trials=20, backend="optuna", )

4.1.5 模型部署

推理脚本

训练完成后,可以用简单的脚本进行推理:

from transformers import pipeline # 一行代码加载模型进行推理 classifier = pipeline( "sentiment-analysis", model="./sentiment_model/best", tokenizer="./sentiment_model/best", device=0, # GPU 推理 ) # 批量预测 results = classifier([ "这部电影太好看了,强烈推荐!", "服务态度很差,再也不来了。", "产品质量一般,价格倒是不贵。", ]) for result in results: print(f"{result['label']}: {result['score']:.4f}") # 正面: 0.9823 # 负面: 0.9951 # 负面: 0.6234

使用 ONNX Runtime 加速

在生产环境中,使用 ONNX 格式可以显著提升推理速度:

from optimum.onnxruntime import ORTModelForSequenceClassification from transformers import pipeline # 导出为 ONNX 格式 ort_model = ORTModelForSequenceClassification.from_pretrained( "./sentiment_model/best", export=True, ) # 使用 ONNX Runtime 推理(速度提升 2-4 倍) classifier = pipeline( "text-classification", model=ort_model, tokenizer="./sentiment_model/best", ) # 性能对比 import time texts = ["这是一段测试文本"] * 100 # PyTorch 推理 start = time.time() classifier_pt(texts) print(f"PyTorch: {time.time()-start:.2f}s") # ONNX Runtime 推理 start = time.time() classifier_ort(texts) print(f"ONNX: {time.time()-start:.2f}s")

4.1.6 进阶技巧

对抗训练增强鲁棒性

通过在 embedding 层添加对抗扰动,提升模型对输入扰动的抵抗能力:

# HuggingFace Trainer 内置对抗训练支持 training_args = TrainingArguments( adversarial_lr=1e-4, # 对抗学习率 adversarial_epsilon=1e-6, # 扰动范围 )

R-Drop 正则化

同一样本进行两次前向传播(使用不同的 dropout mask),最小化两个输出分布的 KL 散度,有效防止过拟合:

# R-Drop 简要实现思路 # 在模型 forward 时执行两次前向传播 # loss = CE_loss_1 + CE_loss_2 + alpha * KL(p1 || p2) + alpha * KL(p2 || p1)

LoRA 微调(低资源场景)

当 GPU 显存不足时,使用 LoRA 只微调少量参数,显存占用降低 60% 以上:

from peft import LoraConfig, get_peft_model, TaskType lora_config = LoraConfig( task_type=TaskType.SEQ_CLS, r=8, # LoRA 秩 lora_alpha=32, lora_dropout=0.1, target_modules=["query", "value"], ) model = get_peft_model(model, lora_config) model.print_trainable_parameters() # trainable params: 294,912 || all params: 102,267,136 || 0.29%

通过以上完整流程,你可以用 HuggingFace Transformers 在几小时内完成一个高质量的文本分类系统,从数据准备到模型训练再到生产部署,形成闭环。


发布者: 作者: 转发
评论区 (0)
U