ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Python话题文本分类实战:从TF-IDF到深度学习模型全流程解析

Python话题文本分类实战:从TF-IDF到深度学习模型全流程解析 简介文本分类是自然语言处理中最基础也最实用的任务之一广泛应用于舆情监控、工单自动分派和内容推荐等场景。其核心原理是将非结构化的文本转换为机器可理解的特征表示再通过机器学习或深度学习模型进行类别预测。传统方法如TF-IDF结合SVM在数据量有限时往往能取得稳定且高效的效果而Word2Vec、TextCNN等深度模型则能捕捉更丰富的语义信息。本文以新闻话题分类为切入点系统讲解数据清洗、分词、特征提取、模型训练与评估的完整流程并分享类别不均衡、过拟合等实际工程问题的排查经验帮助开发者快速构建可落地的文本分类系统。 做话题文本分类这个需求我今年被问到的次数比过去三年加起来都多。说白了就是给定一段文本让程序自动判断它属于哪个话题比如体育、科技、娱乐、财经这种。用 Python 做自然语言处理NLP是绕不开的路径因为 Python 在文本处理、机器学习、深度学习这个生态里实在太全了。这篇文章会把“话题文本分类”这个项目从数据准备、文本预处理、特征工程到模型训练、评估和调优完整走一遍里面包含可直接运行的代码片段和我踩过的坑。不管你是刚入门 NLP 的学生还是工作中突然接到文本分类任务的工程师这篇文章都能给你一条清晰可落地的路线。我特别想强调一点很多人一上来就想着“我要用 BERT 微调”结果数据没洗干净、标签不均衡、评估指标没想清楚最后模型效果一塌糊涂还以为是模型不够强。实际上话题文本分类是一个典型的“8 成时间花在数据上2 成时间花在模型上”的任务。这篇博文会帮你把关键环节全部打通而且我会说明白每一步为什么要这么做不是简单贴一段代码就完事。1. 项目背景与整体思路1.1 话题文本分类到底在解决什么问题话题文本分类是自然语言处理中最经典、最实用的任务之一。它要做的事情可以概括成一句话给定一条文本预测它所属的预定义类别。比如新闻稿可以分成政治、经济、体育、娱乐用户投诉可以分成产品质量、物流、售后社交平台的帖子可以分成美食、旅行、数码。这个任务看起来简单但它几乎是所有 NLP 上层应用的基座垃圾邮件过滤、舆情监控、工单自动分派本质都是话题分类的变体。我在实际项目里发现很多刚接触这个领域的人会把“话题分类”和“情感分类”混淆。情感分类是判断正负面话题分类是判断内容属于什么领域。比如“这个手机续航太差但是拍照效果不错”这句话做情感分类需要输出整体倾向做话题分类则应该输出“数码”。这也是为什么数据标注的阶段要特别小心标签定义必须清晰。从技术角度看这个话题文本分类项目覆盖了 NLP 的标准流水线获取数据、文本清洗、分词、去停用词、特征提取、模型选择、训练评估、上线预测。每一个环节都有对应的 Python 库和工具只要按顺序走通就能应付绝大多数业务需求。我选择用这个话题来展开也是因为它最能体现 NLP 的通用方法论而不是某个特定场景的特殊技巧。1.2 技术选型为什么从传统机器学习起步再引入深度模型关于技术选型我先说一个反直觉的结论对于话题文本分类传统机器学习方法TF-IDF 朴素贝叶斯 / SVM在很多场景下的表现反而比复杂的深度学习模型更稳尤其是在数据量不大时。我有一次做新闻分类训练集只有两万条BERT 微调后的准确率比 TF-IDF SVM 只高了两个百分点但训练时间慢了几十倍推理速度也差了一个数量级。所以如果不是万不得已我都建议先用传统方法建立一个基线模型这个基线可以作为后续所有优化的参照物。深度模型Word2Vec LSTM、Transformer 微调的优势在于能够捕捉文本的语义信息尤其是同义词、上下文相关的表达。比如“苹果”在“水果价格”和“手机发布”两个话题里含义完全不同TF-IDF 这种词袋模型无法区分这一点但词向量和预训练模型可以。不过深度模型需要的数据量和调参成本都不小而且在小数据集上容易过拟合导致泛化能力很差。我的整体思路是分三步走第一步用 TF-IDF 朴素贝叶斯 / SVM 快速做出一个可用版本跑通整个流程第二步用 Word2Vec 或预训练词向量把文本转成序列再搭一个简单的循环神经网络RNN或者卷积神经网络CNN模型第三步如果数据量足够大比如十万条以上再考虑用 BERT 这类预训练语言模型做微调。这样做的好处是每一步都有明确的评估指标你能够直观看到每一层改进到底带来了多少收益而不是一头扎进深度模型最后出了问题都不知道是数据的问题还是模型的问题。2. 数据准备与预处理2.1 数据来源与标注说明做文本分类第一步必然是拿数据。公开数据集里我常用的是 THUCNews清华大学新闻分类数据集和今日头条新闻分类数据集后者在 GitHub 上就能找到包含 15 个类别比如民生、文化、娱乐、体育、财经、房产、汽车、教育、科技、军事、旅游、国际、证券、健康、三农。我一般会从中抽几个类别来做 demo比如体育、科技、娱乐、财经这样类别之间区分度够大便于验证流程。如果你做的是业务私有数据那就得先和业务方确认好话题标签体系。这里有个很重要的经验标签体系一定要互斥且完备。互斥是一条文本只能对应一个标签完备是所有文本都能落到某个标签。如果业务方说“这个话题既可以算科技也可以算数码”你就要在标注阶段强制指定优先级规则否则模型训练时标签之间互相干扰分类器会懵掉。数据量方面我的建议是每个类别至少 2000 条以上四个类别就是 8000 条这样训练出的模型才勉强可用。如果你只有几百条先别急着上模型优先去扩充数据或者用预训练模型做 few-shot不要指望一个简单分类器在多类少量数据上有好效果。另外拿到数据后一定要做标签分布统计如果某个类别占到 80%那这个分类任务就要先想办法处理样本不均衡否则准确率再高也没意义。2.2 文本清洗、分词与去停用词实战文本清洗这个话题看起来简单但很多翻车现场都发生在这里。我接到过一份数据里面全是网页抓下来的新闻标题和正文夹杂着 HTML 标签、乱码字符、全角半角不统一、一堆换行符。如果这些不清理干净后面分词和向量化的效果都会大打折扣。清洗的原则是保留对话题判断有用的信息去掉噪声。对于中文文本一般需要做这几件事去除 HTML 标签、去除 URL、去除 emoji 和特殊符号、统一全角半角、去除多余空白。清洗完之后就是分词。中文不像英文天然按空格分开必须用分词工具。我用过 jieba、pkuseg、HanLP做新闻分类的话 jieba 完全够用速度快、词典可扩展。pkuseg 在新闻领域的分词准确率会高一些但速度慢。下面是一段典型的分词代码import jieba import re def clean_text(text): # 去除HTML标签 text re.sub(r[^], , text) # 去除URL text re.sub(rhttp\S|www\.\S, , text) # 去除特殊符号只保留中文、英文、数字 text re.sub(r[^\u4e00-\u9fa5a-zA-Z0-9], , text) # 统一空白 text re.sub(r\s, , text).strip() return text def cut_words(text): return list(jieba.cut(text)) text 苹果今天发布了新款手机搭载了更强的芯片价格也涨了不少。 print(cut_words(clean_text(text))) # 输出[苹果, 今天, 发布, 了, 新款, 手机, 搭载, 了, 更强, 的, 芯片, 价格, 也, 涨, 了, 不少]你可能会问为什么要做分词而不是直接把字作为特征因为中文的词承载了更多的语义信息比如“手机”“芯片”拆成单字后含义就模糊了。分词之后通常还要去停用词。停用词就是“了”“的”“也”“在”这类出现频率极高但对话题判断没什么帮助的虚词。我常用哈工大停用词表同时也会针对具体业务往停用词表里追加自定义词比如“记者”“报道”“近日”这种在新闻里到处出现但对分类没什么区分的词。这里有一个容易忽略的细节分词和去停用词是会直接改变特征空间的所以必须把清洗、分词、去停用词整合成一个统一的预处理函数然后应用到所有数据上包括训练集、验证集和测试集。千万不要训练集一套预处理测试集另一套预处理那样会造成特征分布不一致模型评估结果就是虚高的。2.3 数据集划分与标签处理数据准备好后需要把数据集切分成训练集、验证集和测试集。我的习惯是 8:1:1 的比例训练集用来学参数验证集用来调参和早停测试集用来最终评估。这里要注意切分数据之前一定要先打乱顺序否则新闻数据按时间排列的话前面的类别都是老的模型很容易学到时间分布而不是话题分布。用 scikit-learn 的 train_test_split 可以轻松完成如果类别不均衡还可以设置 stratify 参数保证切分后每个类别比例与原数据集一致。标签处理方面模型不认字符串需要把话题名映射成整数。比如 {‘体育’: 0, ‘科技’: 1, ‘娱乐’: 2, ‘财经’: 3}然后转换为一维数组。对于传统模型标签直接用整数即可对于深度学习模型通常还需要把标签转成 one-hot 编码或者在损失函数里设置稀疏交叉熵直接用整数标签。我一般用后者因为不用显式转换代码更简洁。预处理做完后我强烈建议做一个“数据抽查”环节随机打印几十条处理后的文本人工看看分词结果是否合理有没有把关键信息弄丢。这一步虽然费眼睛但能避免很多隐藏在数据里的问题比如某个领域词被错误分词拆开或者停用词表误删了“不”这种否定副词——在分类任务里“不”对语义影响很大但在话题分类里它可能没那么重要这个判断只能靠人工抽查确认。3. 特征工程与模型训练3.1 TF-IDF特征提取与贝叶斯/SVM基线模型特征工程是传统机器学习方法的核心。TF-IDF 是我第一个要推荐的特征表示方法。它把每篇文档表示成一个高维稀疏向量向量的每一维对应一个词值由该词在文档中的词频TF和在整个语料中的逆文档频率IDF共同决定。通俗解释就是如果一个词在当前文档中出现频率高但它在其他文档中出现得很少那这个词对区分这篇文章的话题就很有价值比如“股价”频繁出现在财经文章里但很少出现在娱乐文章里它的 TF-IDF 值就会很高。在 Python 里用 scikit-learn 的 TfidfVectorizer 可以一步到位。它内部已经做了分词前的处理但我通常会把 jieba 分词的结果以空格拼接后传入这样可控性更强。下面是一段典型的特征提取和模型训练代码from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.naive_bayes import MultinomialNB from sklearn.svm import LinearSVC from sklearn.pipeline import Pipeline from sklearn.metrics import classification_report # X_train 是分词并预处理后的文本列表y_train 是标签整数列表 tfidf TfidfVectorizer(max_features50000, ngram_range(1, 2)) model Pipeline([ (tfidf, tfidf), (clf, LinearSVC()), ]) model.fit(X_train, y_train) y_pred model.predict(X_test) print(classification_report(y_test, y_pred, target_namestarget_names))这里有两个重要参数max_features 和 ngram_range。max_features50000 表示只保留在所有文档中出现次数最多的 50000 个词作为特征可以显著降低维度防止模型过拟合。ngram_range(1, 2) 表示同时考虑单个词和前后两个词的组合比如“黄金”和“黄金价格”“黄金价格”这个二元词组比单独“黄金”更具话题指向性在新闻分类里能提升一到两个百分点的准确率。不过 ngram 范围也不要设得太高三元以上特征稀疏化严重训练速度会明显下降。分类器的选择上LinearSVC线性支持向量机在文本分类里是默认的“稳赢”选手。它基于线性核在高维稀疏特征下表现非常好训练速度也快。朴素贝叶斯 MultinomialNB 是另一个经典选择适合做概率基线但整体精度通常略低于 LinearSVC。我在实际项目中有一半以上的场景是直接用 TF-IDF LinearSVC 交付的不是因为深度学习不行而是传统方法足够稳定、可解释性强出了问题也容易跟踪。3.2 Word2Vec与文本向量化TF-IDF 是词袋模型忽略了词的顺序和上下文。如果你想让模型理解“狗咬人”和“人咬狗”的区别就需要更复杂的表示方法。Word2Vec 是 2013 年提出的词向量模型它的核心思想是用神经网络把每个词训练成一个低维稠密向量向量之间的空间距离能够反映词的语义相似度比如“足球”和“篮球”的向量距离比“足球”和“芯片”近得多。Word2Vec 有两种训练方式CBOW 和 Skip-gram。CBOW 根据上下文词预测中心词训练速度快Skip-gram 根据中心词预测上下文词在语料较少时的准确度更高。在 Python 里用 gensim 训练很简单from gensim.models import Word2Vec # sentences 是分词后的文本列表每一项是一个list sentences [[苹果, 发布, 新款, 手机], [股票, 市场, 大涨]] model Word2Vec(sentences, vector_size100, window5, min_count2, sg1, epochs10)训练完成后我们需要把一篇文本转换成一个向量常用的方法有平均词向量把文本中所有词的向量求平均和加权词向量按 TF-IDF 权重加总再平均。平均词向量简单实用但它把所有词一视同仁会稀释关键词的贡献。加权词向量效果更好但计算量稍大。我通常会先算平均词向量把它和 TF-IDF 特征一起输入到模型里对比效果而不是一上来就搞复杂方案。这里有个非常重要的经验如果你用自己的数据训练 Word2Vec语料量一定要够大否则学出来的词向量质量很差。我有一次用 5000 条新闻训练 Word2Vec得到的词向量和随机初始化没什么区别下游模型效果还不如 TF-IDF。所以当数据量不够时建议使用别人在大规模语料上预训练好的词向量比如腾讯开源的 Tencent AI Lab Embedding它有 800 万中文词向量效果比自己训练好得多。3.3 基于深度学习的分类模型搭建用深度学习做话题文本分类我推荐从最简单的 TextCNN 入手。TextCNN 是 2014 年提出的模型把文本看作一维的序列用多个不同尺寸的卷积核去提取 n-gram 级别的特征然后通过池化层得到固定长度的向量最后接全连接层和 softmax 输出分类概率。它结构简单、训练快、在小数据集上不容易过拟合非常适合作为文本分类的深度基线。在我的实践中一个基于 PyTorch 的 TextCNN 模型可以在几千条数据上训练得比较稳定准确率比传统方法高出 35 个百分点尤其是类别间的区分比较模糊时比如“科技”和“财经”里都会出现“公司”“投资”这类词。但再往上提升就困难了需要换更强的预训练模型。在写代码之前需要把文本转成词表索引序列。我先用 tokenizer 将分词后的文本映射成整数序列然后做 padding填充或 truncation截断到固定长度比如 max_len100。这里 pad 的填充值统一用 0对应的词一般是“ ”。接下来就是模型的嵌入层通常使用预训练词向量初始化也可以从随机初始化开始。LSTM 或者 TextCNN 都可以作为特征提取器我以 TextCNN 为例代码框架如下import torch import torch.nn as nn import torch.nn.functional as F class TextCNN(nn.Module): def __init__(self, vocab_size, embed_dim, num_class, num_filters128): super(TextCNN, self).__init__() self.embedding nn.Embedding(vocab_size, embed_dim, padding_idx0) self.convs nn.ModuleList([ nn.Conv2d(1, num_filters, (k, embed_dim)) for k in (2, 3, 4) ]) self.fc nn.Linear(num_filters * 3, num_class) def forward(self, x): # x shape: [batch, seq_len] x self.embedding(x) # [batch, seq_len, embed_dim] x x.unsqueeze(1) # [batch, 1, seq_len, embed_dim] pooled [] for conv in self.convs: out conv(x).squeeze(3) # [batch, num_filters, seq_len - k 1] out F.relu(out) out F.max_pool1d(out, out.size(2)).squeeze(2) # [batch, num_filters] pooled.append(out) x torch.cat(pooled, dim1) # [batch, num_filters * 3] return self.fc(x)训练循环则使用交叉熵损失函数和 Adam 优化器配合早停机制。在验证集 loss 连续三轮不下降时把模型保存下来然后加载最优模型在测试集上评估。这个框架可以作为你后续扩展到 LSTM、GRU、Bert 的通用模板改动量并不大。这里我想强调一下 embedding 层初始化。如果使用预训练词向量你应该在模型初始化后把 embedding 的权重替换成预训练矩阵并设置 trainableFalse 或者用较小的学习率微调。否则预训练向量会被随机初始化覆盖等于白加载。我踩过这个坑训了半天发现效果和随机初始化没区别最后检查才发现忘了设置 requires_gradFalse。4. 核心环节实现从训练到评估的完整流程4.1 完整代码框架与关键步骤说明现在我们把所有环节串成一个完整流程。为了让你能直接跑起来我给出一个精简但闭环的示例流程包含数据加载、预处理、模型训练和评估。这里以下载好的 CSV 文件为例假设包含两列content 和 label。第一步读取数据并预览标签分布import pandas as pd df pd.read_csv(news_dataset.csv) print(df[label].value_counts())第二步定义预处理函数并应用到整个数据集import jieba def preprocess(text): text clean_text(text) words cut_words(text) # 去停用词 stopwords set() with open(stopwords.txt, r, encodingutf-8) as f: for line in f: stopwords.add(line.strip()) words [w for w in words if w not in stopwords and len(w) 1] return .join(words) df[processed] df[content].apply(preprocess)第三步切分数据集并建立标签映射from sklearn.model_selection import train_test_split X df[processed].tolist() y df[label].astype(category).cat.codes.tolist() target_names df[label].astype(category).cat.categories.tolist() X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42, stratifyy )这里我先按 8:2 切分没有单列验证集。如果是做深度模型建议再从训练集里切 10% 作为验证集这样训练循环里的早停才有数据依据。传统机器学习模型通常不需要早停交叉验证或者直接在测试集上调参也可以但严格一点的做法是在训练集中留出验证集把测试集留到最后。第四步训练 TF-IDF LinearSVC 基线模型并打印测试结果这部分代码在前文已经给出。拿到基线准确率后你再决定是否继续做深度模型。如果基线准确率已经达到业务要求比如 0.93那就可以直接部署了如果离要求还差一截就继续上 TextCNN 或预训练模型。第五步是深度模型的完整训练流程。数据需要先从文本转为序列这里我补充一段构造数据集的代码from torch.utils.data import Dataset, DataLoader class TextDataset(Dataset): def __init__(self, texts, labels, vocab, max_len100): self.texts texts self.labels labels self.vocab vocab self.max_len max_len def __len__(self): return len(self.texts) def __getitem__(self, idx): tokens self.texts[idx].split() ids [self.vocab.get(w, 1) for w in tokens] # 1表示UNK ids ids[:self.max_len] [0] * max(0, self.max_len - len(ids)) return torch.tensor(ids, dtypetorch.long), torch.tensor(self.labels[idx], dtypetorch.long)再构建词表from collections import Counter counter Counter() for text in X_train: counter.update(text.split()) # 保留出现次数大于等于2的词0PAD, 1UNK vocab {word: idx 2 for idx, (word, count) in enumerate(counter.items()) if count 1} vocab_size len(vocab) 2这一段的细节在于 PAD 和 UNK 的编号约定。我习惯让 0 表示 PAD1 表示 UNK所以真实的词从 2 开始编号。padding_idx0 可以确保 padding 位置不参与训练更新这是一个容易忽略但很关键的细节。最后就是训练循环这里省略重复代码。有一点想提醒你训练时一定要把模型设置为 model.train()评估时设置为 model.eval()并包裹torch.no_grad()。不要小看这些细节我见过至少两个人因为忘了 eval() 模式导致 BatchNorm 和 Dropout 在推理时仍然生效测试结果严重偏低。4.2 模型评估指标与结果解读分类评估指标最常用的是准确率Accuracy但单纯看准确率会掩盖很多问题。比如样本不均衡时若 90% 都是“科技”类盲猜“科技”就能有 90% 准确率但模型对“娱乐”类完全无效。所以对于话题文本分类我至少要看三组指标每个类别的精确率Precision、召回率Recall和 F1 值再加一个宏平均 F1Macro F1。用一个例子来说明假设“财经”类别有 100 条测试文本模型将其中 80 条正确分类为“财经”同时把另外 20 条“科技”文本也误判成“财经”。那么财经类别的精确率是 80/(8020)0.8召回率是 80/1000.8F1 也是 0.8。如果模型把 100 条财经全部认对了但还误判了 100 条科技为财经那么精确率就是 100/(100100)0.5召回率是 1.0F1 是 0.667。很明显只有同时看精确率和召回率才能知道模型是“爱乱猜”还是“太保守”。在代码层面直接用 sklearn 的 classification_report 就能输出所有类别指标。我建议在训练完后除了打印指标还要把错误分类的样本拿出来看几条。这个“错误分析”步骤是提升模型最重要的依据。比如我发现模型经常把“苹果发布新手机”归到“财经”而不是“科技”那就说明训练数据里这种带品牌名的样本标注不一致或者“发布”这个词在两个类别中都很常见需要补充更多特征词。另外混淆矩阵也是必须看的。典型的话题分类混淆矩阵可以帮你发现哪些类别容易被混淆。我做过一个四类新闻分类结果显示“财经”和“科技”之间互相误判率最高因为两个类别都高频出现“公司”“投资”“市场”等词。这时可以考虑增加“科技”类别独有的词权重或者在预处理阶段做类别专用词表增强。5. 常见问题与排查技巧实录5.1 数据与预处理阶段易踩的坑第一坑标签里面有脏数据。我有次做项目发现训练集里一个类别叫“科技 ”后面带个空格另一个叫“科技”pandas 统计时是两个标签导致模型训练时把同一个话题拆成两类准确率一直上不去。解决方式是做数据清洗时对标签列也要 strip、去重、统一大小写。第二坑编码问题。文件中文字符串经常出现 UnicodeDecodeError。读取数据时建议强制指定encodingutf-8如果报错尝试gbk或gb18030。我一般会写一个健壮的读取函数先尝试 utf-8再尝试 gb18030最后尝试 latin-1避免手动反复改编码。第三坑分词词典缺失领域词。比如“自然语言处理”这个专有名词jieba 默认会切成“自然语言”和“处理”这倒不影响分类但如果你做的是医疗领域很多药物名和疾病名会被误切这时候就需要向 jieba 添加自定义词jieba.add_word(腰间盘突出)。在分类任务里领域词典的维护是很重要的一环。第四坑停用词表误删了关键词。通用停用词表里可能包含“不”“没”“是”这类词在情感分类里绝对不能删但话题分类里影响较小。所以我建议针对具体任务主动检查停用词表不要无脑套用。另外数字和英文字母的处理要慎重比如“5G”是话题分类里的强特征词如果清洗时把所有数字都去掉就丢失了重要信息。5.2 模型训练阶段的坑第一个常见问题是“训练 loss 不下降”。遇到这种情况先别怀疑模型结构按顺序排查数据标签是否错乱、文本序列是否全为 PAD、学习率是否太大、embedding 层是否初始化异常。我在一次实验里发现因为忘了把文本转换成序列时过滤空文本导致一批样本全是空的模型学不到任何信息。空文本处理策略很简单如果分词后为空直接用“未知”标记或者丢弃该样本但丢弃前要确认它不是某个罕见话题的唯一样本。第二个问题是“过拟合严重”。表现是训练集准确率接近 100%测试集准确率差很远。常见原因有训练数据太少、模型容量太大、正则化不够。解决思路包括增加 Dropout、加 L2 正则、减小模型维度、做数据增强如同义词替换、回译。对 TextCNN 来说Dropout 设置在 0.30.5 之间通常比较合适太小没效果太大模型欠拟合。第三个问题是“类别不均衡”导致小类别 F1 特别低。解决方式有几种对多数类降采样、对少数类过采样、在损失函数里给少数类更高权重。我在新闻分类里试过把财经类权重调成 1.5科技类调成 1.0效果立竿见影。PyTorch 的 CrossEntropyLoss 可以直接传 weight 参数示例class_weights torch.tensor([1.0, 1.5, 1.0, 2.0]).to(device) criterion nn.CrossEntropyLoss(weightclass_weights)但权重也不能太大否则模型会过度偏向少数类把多数类全判错。比较稳妥的做法是网格搜索一组权重比如 [1.0, 1.5, 2.0, 3.0]。第四个问题是“训练速度慢”。如果数据规模较大建议先用较小版本的模型跑通再逐步扩大。深度学习模型训练的 batch size 最好用 32 或 64太大容易 OOM太小训练不稳定。还可以使用 GPU 加速PyTorch 中只需把模型和数据都.to(cuda)即可。如果你没有 GPU不建议直接训练 BERT先用传统方法或者小模型即可。5.3 优化方向与个人建议当你的基线模型已经稳定工作如何再提升一两个百分点我一般会按照下面的优先级来尝试。第一优先级是数据质量。检查是否存在错误标注、标签不确定的样本往往修正几十条数据的收益比换任何模型都大。可以利用模型预测结果反推找出置信度低或预测错误的样本人工复查这些样本的标注是否合理。第二优先级是特征增强。对于传统模型可以把 TF-IDF 的 ngram 从 (1,2) 改为 (1,3)或者加入词性特征、文本长度特征。对于深度模型可以尝试使用预训练词向量替代随机初始化或者用哈工大 BERT-wwm 这样更强的中文预训练模型。第三优先级是模型集成。我在实际项目中经常用“投票集成”同时训练 LinearSVC、TextCNN 和 BERT 三个模型最后投票决定类别。集成模型通常比单个模型高 12 个百分点但推理成本也会增加是否值得要结合业务考虑。还有一个很容易忽略的点业务上线阶段的预测逻辑和训练时要保持完全一致。你训练时做了清洗、分词、去停用词那线上接口收到的文本也必须走同样的预处理函数。我见过有人训练时用 jieba上线时因为性能压力换了一个更快的分词库结果特征分布变了效果惨降。所以建议把预处理函数固化成单独模块训练和推理共用一个函数。另外如果只是做学术实验可以不用太关注推理速度。但如果是面向用户的系统比如自动工单分类模型响应时间必须控制在几百毫秒以内。这时 BERT 这类大模型可能就不合适可以考虑用蒸馏版模型如 TinyBERT或者把文本长度截短比如只用标题做分类牺牲一点准确率换取速度。最后分享一点个人体会做了这么多年 NLP 项目我最大的感觉是话题文本分类的难点从来不在“选哪个模型”而在“有没有把数据吃透”。我见过有人用一套很复杂的预训练模型效果却不如一个认真清洗数据后的朴素贝叶斯。所以如果你是新手千万别急着上 BERT先老老实实走一遍 TF-IDF SVM 的流程把每一步的数据都搞清楚再逐步升级模型。这样即使以后遇到更复杂的 NLP 任务你也能快速定位问题出在哪个环节。最后分享一个小技巧在模型评估时不只输出准确率把每个类别的 Top 错误样本打印出来人工扫一眼。你会惊讶地发现很多“模型犯的错”其实是标注员犯的错。把这些错误修正后重新训练模型的提升往往比你换更厉害的模型还要明显。希望这篇实战记录能帮你少走一些我走过的弯路祝你在 NLP 话题分类的路上一路顺风。本文还有配套的精品资源点击获取
返回列表