中文文本分类实战:朴素贝叶斯从原理到部署全流程
1. 中文文本分类为什么我最终选了朴素贝叶斯中文文本分类这个需求几乎每个做内容相关项目的开发者都会碰到。不管是给新闻打标签、做垃圾评论过滤、还是把用户反馈自动分派到对应部门本质上都是同一件事给你一段中文文本让你判断它属于哪个类别。我最早接触这类需求的时候第一反应是上深度学习BERT 一把梭效果肯定好。但实际跑下来发现标注数据不够、训练成本高、推理速度慢一个简单的分类任务搞得像在造火箭。后来我把方案退回到最传统的朴素贝叶斯反而在多数场景下拿到了足够好的效果而且训练时间以秒计模型文件小到可以塞进任何服务里。朴素贝叶斯Naive Bayes的核心思想其实一句话就能说清楚它假设各个特征之间是相互独立的然后根据贝叶斯定理计算给定文本属于某个类别的概率取概率最大的那个类别作为预测结果。这个“特征独立”的假设在现实中几乎不可能成立——一篇文章里“足球”和“球赛”这两个词明显相关但朴素贝叶斯不管这些它假装它们没关系。有意思的是就是这个看起来很不合理的假设在文本分类任务上表现得出奇地好。原因在于分类任务并不需要精确的概率估计只需要正确的排序——只要正确类别的概率排在最高就行独立性假设带来的偏差在排序层面往往被抵消掉了。这篇文章适合谁看如果你手头有一个中文文本分类的需求数据量在几千到几十万条之间没有 GPU 集群希望快速上线一个能用的分类器那朴素贝叶斯大概率是你的最优解。如果你已经用了深度学习但想找一个轻量级的 baseline 做对比这篇文章也能帮你把 NB 的完整流程跑通。下面我会从整体设计思路开始把中文文本分类的每个环节拆开讲清楚包括分词、特征工程、模型训练、参数调优和线上部署的注意事项最后给出一份可以直接抄作业的代码方案。2. 整体方案设计与核心思路拆解2.1 为什么是朴素贝叶斯而不是其他分类器文本分类可选的算法很多逻辑回归、SVM、随机森林、梯度提升树、各种深度学习模型都能做。我在不同项目里都试过最后把朴素贝叶斯作为中文文本分类的首选方案主要基于三个维度的考量。第一是训练速度。朴素贝叶斯的训练本质上就是统计词频和计算条件概率没有迭代优化过程不需要梯度下降时间复杂度是 O(样本数 × 特征数)。我实测过一个 10 万条中文新闻标题、20 个类别的数据集用多项式朴素贝叶斯训练从分词到模型产出总共不到 30 秒。同样的数据用 SVM 大概要几分钟用 BERT 微调的话没有 GPU 基本不用想。第二是小样本下的表现。朴素贝叶斯对数据量的要求相对较低几千条标注数据就能训练出一个可用的模型。深度学习模型在小样本上很容易过拟合需要大量数据增强和正则化手段。我做过一个对比实验在 2000 条中文评论、5 个类别的数据集上朴素贝叶斯的 F1 大概在 0.82 左右而一个简单的 TextCNN 只有 0.75直到数据量加到 2 万条以上TextCNN 才反超。第三是可解释性和可维护性。朴素贝叶斯模型本质上就是一张词-类别的概率表你可以直接查看某个词对某个类别的贡献有多大。线上出问题的时候排查起来非常直接——看看是哪些词把样本拉到了错误的类别。深度学习模型在这方面就差很多你很难解释为什么模型把这条文本分错了。当然朴素贝叶斯也有明显的短板。它对特征独立性假设的依赖导致它无法捕捉词序信息“我爱你”和“你爱我”在它眼里是一样的。另外当类别不平衡严重时先验概率会带偏预测结果。这些问题都有对应的处理办法后面会详细讲。2.2 中文文本分类相比英文的特殊挑战中文文本分类和英文有一个根本性的区别中文没有天然的空格分隔词与词之间的边界是模糊的。这就导致中文文本分类的第一步——分词——成了一个独立的、有难度的环节。英文的 “I love natural language processing” 直接按空格切开就是五个 token中文的“我爱自然语言处理”则需要分词工具来判断到底是“我爱/自然/语言/处理”还是“我/爱/自然语言/处理”。分词的质量直接影响后续特征工程和模型效果。我踩过的一个坑是早期做情感分类的时候用了一个通用分词工具把“不错”分成了“不”和“错”两个词结果模型学到的信号完全反了——“不”和“错”在负面样本里出现频率高导致“不错”这个正面表达被误判为负面。后来我加了自定义词典把“不错”“挺好”“一般般”这类情感词强制合并效果立刻好转。除了分词中文还有几个特殊问题需要注意。一是繁简体混用有些数据集里同时存在“软件”和“軟件”如果不做统一处理会被当成两个不同的特征。二是全半角混用比如“”和“A”、“”和“1”需要做标准化。三是网络新词和缩写比如“yyds”“绝绝子”“u1s1”这类表达通用分词工具的词表里往往没有需要自己维护一个补充词表。2.3 朴素贝叶斯的三种变体怎么选scikit-learn 提供了三种朴素贝叶斯实现用在文本分类上主要是多项式朴素贝叶斯MultinomialNB和伯努利朴素贝叶斯BernoulliNB另外还有一个互补朴素贝叶斯ComplementNB。MultinomialNB是最常用的它以词频为特征考虑每个词在文档中出现的次数。适合文本较长、词频信息有意义的场景比如新闻分类、长评论分类。BernoulliNB以词是否出现为特征只关心 0/1不关心出现几次。适合短文本场景比如标题分类、短信分类。它的一个优势是显式地对未出现的词建模这在短文本中很重要因为短文本里“某个词没出现”本身就是一个强信号。ComplementNB是 MultinomialNB 的一个改进版本专门针对类别不平衡问题设计。它用每个类别的补集来估计参数在类别样本数量差异很大时表现更稳定。我做过一个实验在正负样本比例 1:10 的数据集上MultinomialNB 的少数类 F1 只有 0.45而 ComplementNB 能到 0.68。选择建议很简单先试 MultinomialNB如果类别不平衡严重就换 ComplementNB如果文本特别短平均不到 20 个字就试试 BernoulliNB。实际项目中我通常三个都跑一遍用交叉验证选最好的那个。3. 核心细节解析与实操要点3.1 中文分词工具选型与自定义词典分词是中文文本分类的第一个关键环节。目前主流的分词工具我基本都用过这里说一下各自的适用场景。jieba是最常见的选择优点是轻量、速度快、API 简单社区活跃遇到问题容易找到解决方案。缺点是准确率在专业领域文本上一般尤其是医学、法律、金融这些垂直领域通用词表覆盖不够。jieba 支持三种模式精确模式、全模式和搜索引擎模式。文本分类一般用精确模式因为我们需要的是准确的词边界而不是所有可能的切分。pkuseg是另一款常用的分词工具支持多领域分词可以切换新闻、网络、医药、旅游等不同领域的模型。在对应领域的文本上准确率比 jieba 高一些。缺点是速度比 jieba 慢安装包也更大。HanLP功能最全除了分词还提供命名实体识别、关键词提取、依存句法分析等功能。如果项目里不只需要分词HanLP 是一个不错的选择。但它的依赖比较多部署起来相对重一些。我的建议是如果是一般性的中文文本分类jieba 足够用重点放在自定义词典上。自定义词典的维护方法很简单把领域内的专有名词、产品名、行业术语、网络新词整理成一个文本文件每行一个词然后加载到分词器里。比如做电商评论分类就需要把“性价比”“物流速度”“客服态度”这类词加进去否则它们会被切碎丢失完整的语义信号。import jieba # 加载自定义词典 jieba.load_userdict(user_dict.txt) # 添加单个词 jieba.add_word(绝绝子) jieba.add_word(yyds) text 这个产品的性价比真的绝绝子物流也快 words jieba.lcut(text) print(words) # 输出: [这个, 产品, 的, 性价比, 真的, 绝绝子, , 物流, 也, 快]注意自定义词典的词不要加太多否则会引入大量低频特征增加模型复杂度。我的经验是控制在 500 到 2000 个词之间只加那些确实有区分度的领域词。3.2 停用词处理与文本清洗停用词是指那些在文本中频繁出现但对分类没有帮助的词比如“的”“了”“在”“是”“和”这些。去掉停用词可以减少特征维度加快训练速度有时还能提升效果。但停用词处理不是必须的需要根据具体任务来判断。我做过一个对比实验在情感分类任务上去掉停用词后 F1 提升了约 1.5 个百分点但在主题分类任务上去掉停用词后效果反而略有下降。原因是主题分类中某些停用词可能和特定主题有微弱关联去掉后丢失了这部分信息。所以我的建议是先跑一版不去停用词的 baseline再跑一版去停用词的对比一下效果再决定。文本清洗方面中文文本分类通常需要处理以下几类噪声HTML 标签和转义字符如果数据来自网页抓取需要去掉div、nbsp;这类内容。URL 和邮箱这些通常对分类没有帮助可以用正则替换掉。特殊符号和表情根据任务决定是否保留。情感分类中表情符号往往携带强信号建议保留或转换成文字描述。数字和英文可以保留也可以统一替换成占位符。如果数字的具体值不重要替换成NUM可以减少特征维度。繁简体统一用 opencc 这类工具把繁体转成简体避免同一词的不同写法被当成两个特征。import re def clean_text(text): # 去除 HTML 标签 text re.sub(r[^], , text) # 去除 URL text re.sub(rhttp[s]?://\S, , text) # 去除邮箱 text re.sub(r\S\S, , text) # 全角转半角 text .join([chr(ord(c) - 65248) if 65281 ord(c) 65374 else c for c in text]) # 去除多余空白 text re.sub(r\s, , text).strip() return text3.3 特征工程从词袋到 TF-IDF朴素贝叶斯不能直接处理原始文本需要把文本转换成数值特征向量。最基础的方法是词袋模型Bag of Words它把每个词作为一个特征特征值是词在文档中出现的次数。词袋模型实现简单但有两个问题一是高频词会主导特征向量二是没有考虑词的重要性。TF-IDF 是对词袋模型的改进。TF 是词频IDF 是逆文档频率两者相乘得到 TF-IDF 值。IDF 的计算方式是log(总文档数 / 包含该词的文档数)一个词在越少的文档中出现它的 IDF 越大说明它越有区分度。TF-IDF 能有效降低“的”“是”这类高频但无区分度词的权重提升专业术语的权重。在 scikit-learn 中CountVectorizer实现词袋模型TfidfVectorizer实现 TF-IDF。对于朴素贝叶斯我通常用CountVectorizer配合MultinomialNB因为 MultinomialNB 本身会从词频中学习每个词对类别的贡献TF-IDF 的加权反而可能干扰它的概率估计。但在数据量较大、特征维度很高时TF-IDF 配合ComplementNB或BernoulliNB效果更好。几个关键参数需要特别注意max_features限制特征数量只保留词频最高的 N 个词。对于几万条数据我通常设 10000 到 50000。设太小会丢失信息设太大会增加过拟合风险。min_df忽略出现次数少于 N 的词。通常设 2 到 5过滤掉只出现一两次的罕见词。max_df忽略出现频率超过 N% 的词。通常设 0.8 到 0.95过滤掉几乎每篇文档都有的词。ngram_range是否使用 n-gram 特征。(1, 1)只用单个词(1, 2)同时用单个词和相邻词对。中文文本分类中(1, 2)有时能提升效果因为“不”“错”和“不错”含义完全不同。但 n-gram 会显著增加特征维度需要权衡。from sklearn.feature_extraction.text import CountVectorizer, TfidfVectorizer # 词袋模型 count_vec CountVectorizer( max_features30000, min_df2, max_df0.9, ngram_range(1, 2) ) # TF-IDF tfidf_vec TfidfVectorizer( max_features30000, min_df2, max_df0.9, ngram_range(1, 2), sublinear_tfTrue # 用 1log(tf) 代替 tf缓解高频词主导问题 )提示sublinear_tfTrue这个参数我强烈建议开启。它把词频从线性尺度压缩到对数尺度避免一个词出现 100 次和出现 10 次之间的权重差距被过度放大。实测在多数中文文本分类任务上开启后 F1 能提升 1 到 2 个百分点。3.4 平滑参数 alpha 的调优朴素贝叶斯有一个关键参数alpha也叫平滑参数或拉普拉斯平滑系数。它的作用是避免某个词在某个类别中从未出现导致概率为零的情况。如果没有平滑一个在训练集中从未出现在“体育”类别中的词会让整个“体育”类别的后验概率变成零这显然不合理。alpha的取值直接影响模型的泛化能力。alpha越大平滑越强模型越保守alpha越小平滑越弱模型越依赖训练数据。默认值是 1.0但实际项目中需要调优。我的调参经验是对于文本分类任务alpha通常在 0.01 到 1.0 之间。数据量越大alpha可以设得越小数据量越小alpha应该设得越大。我通常用网格搜索在[0.001, 0.01, 0.1, 0.5, 1.0, 2.0]这几个值里选配合 5 折交叉验证。在多数中文文本分类数据集上最优值落在 0.01 到 0.1 之间。from sklearn.naive_bayes import MultinomialNB from sklearn.model_selection import GridSearchCV from sklearn.pipeline import Pipeline pipeline Pipeline([ (vec, CountVectorizer(max_features30000, min_df2, max_df0.9)), (clf, MultinomialNB()) ]) param_grid { clf__alpha: [0.001, 0.01, 0.1, 0.5, 1.0, 2.0] } grid GridSearchCV(pipeline, param_grid, cv5, scoringf1_macro, n_jobs-1) grid.fit(X_train, y_train) print(最优 alpha:, grid.best_params_) print(最优 F1:, grid.best_score_)4. 完整实操流程与核心环节实现4.1 数据准备与标注规范数据是文本分类的地基。我见过太多项目在数据质量上翻车模型怎么调都上不去最后发现是标注数据本身有问题。这里说几个数据准备阶段的关键点。类别定义要清晰互斥。每个类别必须有明确的边界不能出现“科技”和“互联网”这种高度重叠的类别。如果两个类别的区分度不高模型很难学好人工标注也会很纠结。我的做法是先定义类别然后找 50 条样本做预标注看看标注一致性如何。如果两个标注员的一致率低于 80%说明类别定义有问题需要重新调整。每个类别至少 200 条样本。这是我在多个项目里总结的经验值。低于这个数模型很难学到稳定的类别特征。如果某些类别样本太少要么补充数据要么合并到相近类别要么用数据增强手段扩充。标注规范要写成文档。不要口头说“你看着标”一定要写清楚每个类别的定义、边界案例怎么处理、遇到不确定的怎么办。这份文档在标注过程中要不断更新把新遇到的边界情况补充进去。留出验证集和测试集。训练集、验证集、测试集的比例通常是 7:1.5:1.5 或 8:1:1。验证集用于调参和模型选择测试集只在最后评估时用一次。千万不要在测试集上调参否则评估结果会虚高。4.2 从原始文本到特征矩阵的完整代码下面是一个完整的中文文本分类流程从原始文本到训练好的模型可以直接参考复现。import jieba import re import numpy as np from sklearn.feature_extraction.text import CountVectorizer from sklearn.naive_bayes import MultinomialNB, ComplementNB from sklearn.model_selection import train_test_split, cross_val_score from sklearn.metrics import classification_report, confusion_matrix from sklearn.pipeline import Pipeline # 1. 加载自定义词典 jieba.load_userdict(user_dict.txt) # 2. 停用词表 def load_stopwords(pathstopwords.txt): with open(path, r, encodingutf-8) as f: return set([line.strip() for line in f if line.strip()]) stopwords load_stopwords() # 3. 文本清洗 def clean_text(text): text re.sub(r[^], , text) text re.sub(rhttp[s]?://\S, , text) text re.sub(r\S\S, , text) text .join([chr(ord(c) - 65248) if 65281 ord(c) 65374 else c for c in text]) text re.sub(r\s, , text).strip() return text # 4. 分词 去停用词 def tokenize(text): text clean_text(text) words jieba.lcut(text) words [w for w in words if w not in stopwords and len(w) 1] return .join(words) # 5. 假设 X_raw 是原始文本列表y 是标签列表 X_processed [tokenize(t) for t in X_raw] # 6. 划分数据集 X_train, X_test, y_train, y_test train_test_split( X_processed, y, test_size0.2, random_state42, stratifyy ) # 7. 构建 Pipeline pipeline Pipeline([ (vec, CountVectorizer( max_features30000, min_df2, max_df0.9, ngram_range(1, 2) )), (clf, MultinomialNB(alpha0.1)) ]) # 8. 交叉验证选 alpha alphas [0.001, 0.01, 0.1, 0.5, 1.0] best_alpha None best_score 0 for a in alphas: pipeline.set_params(clf__alphaa) scores cross_val_score(pipeline, X_train, y_train, cv5, scoringf1_macro) mean_score scores.mean() print(falpha{a}, F1{mean_score:.4f}) if mean_score best_score: best_score mean_score best_alpha a print(f最优 alpha: {best_alpha}) # 9. 用最优参数训练最终模型 pipeline.set_params(clf__alphabest_alpha) pipeline.fit(X_train, y_train) # 10. 在测试集上评估 y_pred pipeline.predict(X_test) print(classification_report(y_test, y_pred)) print(confusion_matrix(y_test, y_pred))这段代码有几个细节值得展开说。stratifyy保证划分后训练集和测试集的类别分布一致避免某个类别在测试集中完全没有样本。len(w) 1过滤掉单字词因为中文单字词很多是虚词或噪声去掉后能减少特征维度。f1_macro作为交叉验证的指标它对每个类别平等对待适合类别不平衡的场景。4.3 模型评估与混淆矩阵分析模型训练完之后不能只看一个准确率就完事。准确率在类别不平衡时会严重误导——如果 90% 的样本都是 A 类模型把所有样本都预测成 A 类准确率也有 90%但这样的模型毫无用处。我通常看三个指标Precision查准率、Recall查全率和F1两者的调和平均。Precision 回答的是“预测为某类的样本中有多少是真的属于该类”Recall 回答的是“某类的真实样本中有多少被预测出来了”。F1 是两者的综合。对于每个类别这三个指标都要看不能只看宏平均。混淆矩阵是排查问题的利器。它能直观地告诉你哪些类别容易被混淆。比如在一个新闻分类任务中我发现“科技”和“财经”两个类别的混淆特别严重。查看混淆矩阵后发现大量“科技”类新闻被预测成了“财经”原因是这些新闻里频繁出现“融资”“估值”“上市”这类词而这些词在财经类新闻中也很常见。解决办法是在特征工程阶段加入更多科技领域的专有词或者考虑合并这两个类别。import matplotlib.pyplot as plt import seaborn as sns from sklearn.metrics import confusion_matrix cm confusion_matrix(y_test, y_pred) plt.figure(figsize(10, 8)) sns.heatmap(cm, annotTrue, fmtd, cmapBlues, xticklabelspipeline.classes_, yticklabelspipeline.classes_) plt.xlabel(预测类别) plt.ylabel(真实类别) plt.title(混淆矩阵) plt.show()4.4 模型持久化与线上部署训练好的模型需要保存下来供线上服务调用。scikit-learn 模型可以用 joblib 或 pickle 保存。我推荐 joblib因为它对包含大量 numpy 数组的对象序列化效率更高。import joblib # 保存模型 joblib.dump(pipeline, nb_text_classifier.pkl) # 加载模型 loaded_model joblib.load(nb_text_classifier.pkl) # 预测新文本 new_text 这款手机拍照效果很好续航也不错 processed tokenize(new_text) pred loaded_model.predict([processed])[0] proba loaded_model.predict_proba([processed])[0] print(f预测类别: {pred}) print(f各类别概率: {dict(zip(loaded_model.classes_, proba))})线上部署时有几个工程上的点需要注意。分词器的初始化jieba 第一次加载词典会耗时几百毫秒建议在服务启动时预热不要等到第一个请求来了才初始化。模型文件大小朴素贝叶斯模型通常只有几 MB 到几十 MB加载很快内存占用也小单机部署完全没问题。并发处理朴素贝叶斯的预测是纯计算没有 IO 等待用多线程或多进程都能获得不错的吞吐量。我实测过一个 4 核的机器用 4 个 worker 进程QPS 能到几千。如果 QPS 要求更高可以考虑把模型导出成 ONNX 格式用 ONNX Runtime 推理速度能再提升几倍。不过对于大多数中文文本分类场景原生 scikit-learn 的性能已经足够了。5. 常见问题与排查技巧实录5.1 效果不好时怎么一步步排查模型效果不达预期是最常见的问题。我通常按以下顺序排查从数据到特征到模型逐层排除。第一步检查数据质量。随机抽 50 条训练样本人工看看标注是否正确。我遇到过标注员把“苹果手机”标成“水果”类别的情况这种错误数据不清理模型永远学不好。另外检查类别分布如果某个类别只有几十条样本模型很难学好。第二步检查分词结果。把几条样本的分词结果打印出来看看有没有明显的切分错误。特别是领域专有词如果被切碎了需要加到自定义词典里。第三步检查特征矩阵。看看特征维度是多少有没有异常。如果特征维度只有几百说明 max_features 设太小或者 min_df 设太大大量有用特征被过滤掉了。如果特征维度几十万说明 ngram_range 设太大或者 min_df 设太小引入了太多噪声特征。第四步检查混淆矩阵。看看哪些类别容易被混淆分析原因。如果是类别定义本身有重叠需要重新定义类别如果是特征不足需要补充领域词如果是类别不平衡需要调整样本权重或换用 ComplementNB。第五步调参。调整 alpha、max_features、ngram_range 等参数。这一步放在最后因为如果前面几步有问题调参也救不回来。5.2 类别不平衡的处理方案类别不平衡在真实场景中非常常见。比如垃圾评论检测正常评论可能占 95%垃圾评论只有 5%。这种情况下模型会倾向于预测多数类少数类的 Recall 很低。处理类别不平衡有几种方案。换用 ComplementNB是最简单的它专门针对不平衡场景设计通常能直接提升少数类的 F1。调整样本权重在训练时给少数类更高的权重scikit-learn 的fit方法支持sample_weight参数。过采样少数类用 SMOTE 或简单的随机复制来增加少数类样本。欠采样多数类随机丢弃部分多数类样本但会损失信息数据量小时慎用。我通常先试 ComplementNB如果效果还不够再用样本权重。过采样和欠采样用得比较少因为它们可能引入过拟合或信息损失。from sklearn.naive_bayes import ComplementNB # 方案一ComplementNB clf ComplementNB(alpha0.1) # 方案二样本权重 from sklearn.utils.class_weight import compute_sample_weight sample_weights compute_sample_weight(balanced, y_train) pipeline.fit(X_train, y_train, clf__sample_weightsample_weights)5.3 新词和领域词的处理技巧中文新词层出不穷通用分词工具的词表更新往往滞后。我处理新词的经验是建立动态词表更新机制。具体做法是定期从线上预测错误的样本中提取高频未登录词人工审核后加入自定义词典重新训练模型。提取未登录词可以用新词发现算法比如基于左右邻接熵和互信息的方法。简单来说如果一个字符串片段在大量文本中频繁出现且它的左右邻接字符种类丰富那它很可能是一个新词。这个方法的实现不复杂网上有很多开源实现可以参考。另一个技巧是字符级特征。如果分词效果始终不理想可以绕过分词直接用字符作为特征。中文的字符级特征在某些任务上表现不输词级特征尤其是短文本分类。scikit-learn 的CountVectorizer设置analyzerchar或analyzerchar_wb就可以用字符级特征。字符级的优点是永远不会出现未登录词问题缺点是丢失了词边界信息特征维度会高一些。# 字符级特征 char_vec CountVectorizer( analyzerchar_wb, ngram_range(1, 3), max_features50000 )5.4 常见问题速查表问题现象可能原因排查方法解决方案准确率虚高但实际效果差类别不平衡模型偏向多数类查看混淆矩阵和各类别 F1换 ComplementNB加样本权重某些类别 F1 特别低该类别样本太少或特征不足检查样本数量和该类的关键词补充样本加领域词到自定义词典训练集效果好测试集差过拟合对比训练集和测试集指标增大 alpha减小 max_features增大 min_df分词结果不理想领域词被切碎打印分词结果人工检查维护自定义词典或改用字符级特征预测速度慢特征维度太高查看特征矩阵维度减小 max_features增大 min_df新词无法识别分词工具词表滞后检查错误样本中的未登录词动态更新自定义词典最后分享一个我踩过的坑早期做文本分类时我把停用词表设得特别大去掉了大量所谓“无意义”的词。结果发现模型效果反而下降了。后来分析发现停用词表里包含了一些在特定类别中有区分度的词比如“不”在负面评论中频率明显更高。所以停用词表不要照搬网上的通用版本要根据自己的任务定制并且一定要做对比实验验证效果。5.5 模型迭代与持续优化模型上线不是终点而是起点。线上运行过程中会不断遇到新的样本和新的问题。我通常建立一套反馈闭环线上预测结果中置信度低的样本自动进入人工审核队列人工审核后把纠正后的样本加入训练集定期比如每周或每月用新数据重新训练模型更新线上版本。这个闭环的关键是置信度阈值的设定。朴素贝叶斯的predict_proba给出各类别的概率最高概率低于某个阈值比如 0.6的样本说明模型不确定需要人工介入。阈值设太高会导致大量样本需要人工审核设太低会漏掉错误样本。我通常从 0.6 开始根据人工审核的工作量动态调整。另外定期监控线上指标也很重要。我通常监控三个指标预测分布各类别的预测比例是否和训练集一致、置信度分布低置信度样本的比例是否突然升高、人工纠正率人工审核中发现错误的比例。如果预测分布突然偏移或者纠正率突然升高说明数据分布可能发生了变化需要重新训练模型。这套流程跑下来朴素贝叶斯模型在中文文本分类任务上能稳定输出可用的效果而且维护成本远低于深度学习方案。对于大多数中小规模的中文文本分类需求这可能是性价比最高的选择。