去年接手一个客户反馈分类项目,数据量8000条,6个类别。团队里有人建议用BERT微调,有人建议直接用GPT做零样本。最后两种方案都试了,效果差距不大但成本差了10倍。这篇文章把这个选型过程和踩坑经验整理出来。

方案演进之路

文本分类经历了四个阶段:

  • 规则方法(关键词+正则):不需要训练数据,实现简单,适合规则明确的场景;缺点是召回率低、维护成本高;
  • 传统机器学习(TF-IDF + SVM/朴素贝叶斯):需要标注数据但不多,效果稳定,可解释性好;在数据量小、类别少的场景仍有竞争力;
  • 深度学习(BERT/RoBERTa微调):需要中等量级的标注数据,准确率高,泛化能力强;目前是大多数任务的首选;
  • LLM零样本/少样本:不需要标注数据或只需几个示例,灵活度最高但成本也最高;适合快速验证或标注成本极高的场景。

BERT微调实战

数据准备阶段,先看一下类别分布。8000条数据里"产品质量"占40%、“物流"占25%、“售后"占20%、“价格"占15%,有明显的类别不平衡,后面要处理。

from transformers import AutoTokenizer, AutoModelForSequenceClassification
from transformers import TrainingArguments, Trainer
import torch

model_name = "bert-base-chinese"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForSequenceClassification.from_pretrained(model_name, num_labels=6)

def tokenize(batch):
    return tokenizer(batch["text"], padding=True, truncation=True, max_length=128)

# 数据预处理
dataset = dataset.map(tokenize, batched=True)
dataset = dataset.train_test_split(test_size=0.2)

args = TrainingArguments(
    output_dir="./results",
    num_train_epochs=3,
    per_device_train_batch_size=16,
    evaluation_strategy="epoch",
    save_strategy="epoch",
    load_best_model_at_end=True,
    metric_for_best_model="f1",
)

trainer = Trainer(model=model, args=args, train_dataset=dataset["train"],
                  eval_dataset=dataset["test"])
trainer.train()

训练3个epoch大概20分钟(RTX 4090),最终测试集F1达到0.92。

类别不平衡处理

这是最容易踩的坑。1000条数据里800条是"产品质量”,200条是其他类别,模型会倾向于预测多数类。几个解决方案:

  • 过采样:对少数类用SMOTE或简单复制,增加样本数量;
  • 欠采样:对多数类随机丢弃一部分,减少样本数量;
  • 加权损失:在loss函数里给少数类更高的权重,让模型更重视它们;
  • Focal Loss:自动降低容易分类的样本的loss权重,让模型专注学难分的样本。

实测加权损失最简单有效,Focal Loss效果最好但调参麻烦。

LLM零样本分类

没有标注数据时,用LLM做零样本分类:

from openai import OpenAI
client = OpenAI()

def classify(text, categories):
    prompt = "将以下文本分类到最合适的类别中。\n"
    prompt += f"类别:{', '.join(categories)}\n"
    prompt += f"文本:{text}\n"
    prompt += "只回答类别名称,不要解释。"
    
    resp = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": prompt}],
        temperature=0
    )
    return resp.choices[0].message.content.strip()

零样本分类的准确率通常在70-85%之间,不如微调模型(90%+),但优势是不需要标注数据、可以随时增减类别。

方法对比

方法 标注需求 准确率 推理速度 适用场景
关键词规则 60-70% 极快 简单过滤
TF-IDF+SVM 几百条 80-85% 数据少
BERT微调 几千条 90-95% 中等 主流方案
LLM零样本 0-10条 70-85% 快速验证

踩坑记录

标签噪声比你想象的严重。人工标注的一致性通常只有85-90%,同一条文本不同标注员可能给出不同标签。建议每个样本至少两个人标注,用多数投票确定最终标签。

领域迁移效果衰减明显。在通用语料上预训练的模型,迁移到特定领域时效果会打折扣。如果领域差异大,考虑用领域语料继续预训练。

推理优化很重要。BERT模型推理延迟约10-20ms/条,批量推理可以降到2-5ms/条。ONNX导出后还能再快一倍。对延迟敏感的场景一定要做推理优化。

写在最后

文本分类没有银弹。数据少用规则或LLM,数据中等用BERT微调,数据多类别复杂可以考虑多阶段方案。关键是先跑通baseline,再逐步优化。