恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
Python文本分类系统实战:从数据清洗到模型上线
首页
资讯中心
/
Python文本分类系统实战:从数据清洗到模型上线
Python文本分类系统实战:从数据清洗到模型上线
发布时间:2026/9/15 3:49:58
简介基于Python的文本分类系统源码包面向机器学习初学者和自然语言处理入门者可用于课程设计、毕业设计或快速搭建文本分类基线。项目依托K近邻、朴素贝叶斯、支持向量机、逻辑回归、决策树与随机森林六种经典算法完整覆盖文本预处理的去空格、转小写、分词、词性标注和词形还原并利用TF-IDF完成特征提取对多个模型进行训练与准确率对比还能将中间数据保存为便于后续处理的表格格式。压缩包共8个文件以Python脚本、文本样本、处理后的数据文件及说明文档为主整体仅3.7MB结构清晰、运行门槛低。已有69人学习下载适合希望系统掌握传统机器学习文本分类实操流程的读者借助源码可以直观看到从原始文本到模型评估的每一处实现细节并对比不同分类器的效果差异为后续调优或扩展提供基础。1. 一套能落地的 Python 文本分类系统代码只是最后一步下载一个基于 Python 的文本分类系统源码包很快但真正让它在你自己的数据上产出可用结果靠的不是某段神奇代码而是数据清洗、特征工程、模型训练、评估调参和上线验证这条完整链路。文本分类是自然语言处理里最常被问到的落地场景客服工单自动分派、新闻或评论归类、垃圾内容过滤本质都是给一段文本打上预定义标签。这套源码包通常已经带好了脚本、示例数据和模型文件但换到真实语料时训练集质量、分词效果、类别分布和特征维度才是决定系统好坏的地方。下面按工程视角拆这条链路覆盖从原始文本到特征向量、从模型训练到评估调参、从模型导出到接口验证的每一步适合正在搭建第一个分类模型、或者拿到了源码包但不知道怎么改造和排错的开发者。2. 文本分类的数据链路清洗、分词与特征向量化大多数文本分类源码包在自带数据集上效果不错换到用户自己的语料就崩原因基本不在模型而在数据预处理。真实文本里有 HTML 标签、URL、全角符号、乱码编码和大量噪音词这些都会直接进入特征空间。所以这一章先讲数据链路的三段式处理文本清理、中文分词、特征向量化。这三步做扎实后面模型训练才有意义。2.1 先处理数据再谈模型文本清理的三个必做动作第一件必须做的是统一编码。用open()读取语料时指定utf-8遇到坏字节用errorsignore兜底避免一个乱码字符让整个进程抛异常。第二件是去除与分类无关的结构噪音包括 HTML 标签、URL、邮箱地址。第三件是归一化空白和全角字符中文文本里经常混入全角空格和特殊空白字符不处理会分裂同一个词。import re def clean_text(raw: str) - str: if not isinstance(raw, str): return raw raw.replace(\u3000, ) # 全角空格归一为半角 raw re.sub(r[^], , raw) # 去掉 HTML 标签 raw re.sub(rhttps?://\S, , raw) # 去掉 URL raw re.sub(r[^\u4e00-\u9fa5a-zA-Z0-9], , raw) # 只保留中英文与数字 raw re.sub(r\s, , raw).strip() # 压缩连续空白 return raw这段函数的处理顺序是有讲究的先去标签再去 URL因为 URL 里可能包含这类字符最后才做字符白名单过滤避免正则把已经清理过的文本再次破坏。第四行正则会把所有标点符号替换成空格对大部分中文分类任务影响不大但如果你的类别依赖表情符号或特定标点比如判断评论情绪时!!!是信息量这一行就要去掉。清洗函数写好后对全量语料跑一遍并保存成新文件训练和预测阶段必须使用同一个清洗函数这是源码包改造里最容易漏的一步。2.2 中文分词jieba 的默认行为与两个工程参数中文不像英文按空格切词分类系统里几乎逃不开分词环节。常见做法是用 jieba开源、轻量、对工业场景够用。但默认设置有两个坑一是会打印大量 INFO 日志训练时刷屏用setLogLevel(20)关掉二是默认开了 HMM 新词发现对同一段文本每次切分可能出现细微差异在需要结果可复现的测试环境里显式传HMMFalse更稳妥。import jieba jieba.setLogLevel(20) # 关闭 INFO 日志避免训练时刷屏 def tokenize(text: str) - list[str]: # lcut 直接返回 listcut 返回生成器 return [w for w in jieba.lcut(clean_text(text), HMMFalse) if w.strip()]领域词库是这个环节最值得投入的优化点。电商数据的退货包运费、IT 工单里的蓝屏代码让通用词典去切通常会被拆碎。jieba.load_userdict(domain_words.txt)可以从外部文件加载领域词每行格式为词 词频 词性词频越大越倾向被识别为独立词。另一个常用参数是停用词表把的、了、吗、呢这类无区分度的词过滤掉减少特征维度STOP set(line.strip() for line in open(stopwords.txt, encodingutf-8)) def tokenize_with_stop(text: str) - list[str]: return [w for w in tokenize(text) if w not in STOP and len(w) 1]注意len(w) 1会过滤掉单个汉字这对大部分场景是对的因为单字通常是噪音但如果你的语料里有退换这类单字强信号词需要单独评估。停用词表不要直接抄网上的通用版本收集自己训练集里Counter频率前 100 的词人工扫一遍把和分类任务无关的高频词加进去比任何现成表都有效。2.3 特征向量化TfidfVectorizer 的工程参数拿到分词结果后需要把变长文本转成模型能吃的定长向量。虽然现在有很多预训练 embedding 方案但 TF-IDF 仍然是文本分类性价比最高的起点训练快、可解释、在小数据集上经常比深度模型更可靠。scikit-learn 的TfidfVectorizer封装了词频统计、逆文档频率计算和 L2 归一化工程上只需要关注几个参数。from sklearn.feature_extraction.text import TfidfVectorizer vectorizer TfidfVectorizer( tokenizertokenize_with_stop, ngram_range(1, 2), min_df2, max_df0.8, max_features20000, sublinear_tfTrue, ) X vectorizer.fit_transform(corpus_texts) # corpus_texts 是清洗后的原始文本列表 print(X.shape) # (样本数, 特征数)参数选择直接决定特征空间的规模和有效性几个核心参数的推荐值如下表参数作用推荐设置调参方向ngram_range是否纳入相邻词组合(1, 2)数据量小且领域词固定时尝试(1, 2)数据量大再退回(1, 1)min_df低于该文档频率的词被丢弃2或3语料大时提到5有效降噪max_df高于该比例的词被丢弃0.8或0.9去除的、我这类全语料高频词max_features保留的最大特征数20000配合min_df使用过大会拖慢训练sublinear_tf词频取对数压缩True对长文本效果明显保持开启这里有个容易踩的性能坑TfidfVectorizer(tokenizercallable)时sklearn 会对每个样本调用一次 Python 函数分词成了全流程最慢的环节。语料几十万条时建议先离线把清洗和分词结果写入joblib或 parquet 文件再用tokenizerstr.split读取预切分好的词列表训练速度能提升一个数量级。3. 用 scikit-learn 构建文本分类核心模型选择与训练实现特征向量就绪后进入模型环节。源码包里常见的模型文件有.pkl、.joblib、onnx等格式但拿来主义通常行不通因为默认模型是在别人的语料上训练的。正确做法是理解模型选型逻辑用自己的数据重新训练。这一章从任务类型、模型对比、数据划分和 Pipeline 四个层面讲清楚训练实现。3.1 先认清任务类型多分类、多标签还是层次分类动手前先看一眼自己数据的标签结构。每一条样本只对应一个类别的叫多分类比如新闻归类一条样本可以同时命中多个类别的叫多标签比如一篇技术文章同时属于Python和数据库。这两个任务在输出层有本质区别多分类用 softmax多标签用 sigmoid。源码包里如果只写了predict()而没有predict_proba()大概率只支持多分类。另一个容易混淆的是层次分类比如先分技术/非技术再在技术下细分语言方向。层次分类可以用一个多分类器硬做但类别数量多且样本不均衡时常见做法是先训一级分类器、再对每个一级类别训练二级分类器。判断依据很简单把标签展开成集合看看有没有一个样本同时出现两个互斥类别的标签如果有先按多标签处理。标签本身直接存字符串sklearn 的模型和评估函数都支持字符串标签省去了LabelEncoder再解码的一层麻烦。但要注意把完整标签列表存下来因为predict()返回的是 numpy 数组上线时需要有标签表才能把序号映射回可读类别。3.2 三个性价比最高的模型在动手跑深度学习之前先用经典模型把基线打出来。对大部分中小规模文本分类任务下面三个模型足够覆盖从快速验证到线上服务的大部分需求模型核心特点适合场景注意事项逻辑回归 LogisticRegression有概率输出可解释性好通用文本分类默认首选特征维度高时记得调C线性 SVM LinearSVC决策边界更硬小样本表现好类别边界清晰的短文本没有predict_proba缺置信度多项式朴素贝叶斯 MultinomialNB训练极快适合高维稀疏特征短文本、情感极性判断特征分布假设强长文本易失真深度模型在文本分类里的优势需要足够数据支撑。几千条样本的中文分类任务预训练模型不一定打得过调好参的逻辑回归还会把训练和推理成本拉高一个量级。如果确实想试更强方案fastText 是经典模型和深度模型之间一个折中训练快效果通常比朴素贝叶斯好。但不管选哪个第一版系统的核心目标是把链路跑通而不是追求精度小数点这在工程决策上很重要。3.3 数据划分分层抽样保证小类别不丢失训练集和测试集的划分方式直接决定评估结果可信度。如果原始数据里 90% 是正常类、10% 是异常类用默认的随机划分测试集里异常类可能只有几十条评估指标的波动会非常大。stratify参数可以按原始标签比例做分层抽样保证训练集和测试集的类别分布一致。from sklearn.model_selection import train_test_split X_train, X_test, y_train, y_test train_test_split( texts, # 原始文本列表 labels, # 对应标签列表 test_size0.2, # 20% 留作测试 random_state42, # 固定随机种子保证结果可复现 stratifylabels, # 按类别比例分层抽样 ) print(训练集类别分布:, {c: sum(y_train c) for c in set(labels)})random_state固定下来之后每次跑出来的结果才可比较调参时不会因为随机波动误判参数好坏。分层抽样对多标签任务不适用这种场景退化为train_test_split默认随机划分同时人工检查每个标签在测试集里是否仍有足够样本。划分完成后训练集和测试集之间避免任何信息泄漏比如全量文本先做了fit_transform再做划分这种顺序错误的源码在不少开源包里能见到特征统计会把测试集信息带进训练过程造成虚高的评估分数。3.4 用 Pipeline 把向量化和模型串成一体在源码改造里最常见的结构问题是没有把预处理、向量化、模型串起来导致训练时先fit_transform特征、再训模型预测时又单独对输入跑一遍transform漏掉任何一个环节线上就会报特征数量不匹配。scikit-learn 的Pipeline就是为这件事设计的from sklearn.pipeline import Pipeline from sklearn.linear_model import LogisticRegression pipeline Pipeline([ (vect, TfidfVectorizer( tokenizertokenize_with_stop, ngram_range(1, 2), min_df2, max_df0.8, sublinear_tfTrue, )), (clf, LogisticRegression(max_iter1000, C1.0)), ]) pipeline.fit(X_train, y_train) # 内部自动完成 fit_transform fit y_pred pipeline.predict(X_test) # 内部自动完成 transform predictPipeline 的价值在于把训练阶段拟合的向量化器状态和模型绑定成一个对象。以后换参数、换模型、做交叉验证都只需要操作pipeline这一个变量。TfidfVectorizer在训练时学会了每个词的 IDF 值预测时如果新文本里出现训练集没有的词transform会自动忽略这个兼容逻辑由 Pipeline 保证手工分步实现时容易写错。4. 文本分类的评估调参指标、参数与过拟合排查模型训练完只是第一步源码包能不能用于生产取决于评估和调参是否系统。这一章讲清楚三个环节分类报告怎么看、三个必调参数怎么设、样本不均衡怎么处理。跳过这一章直接调参容易陷入盲目试参数的死循环。4.1 不看准确率先看分类报告准确率在类别不均衡时是欺骗性最强的指标。90% 样本是A 类的数据集全预测成A 类就有 90% 准确率但这个模型没有任何使用价值。正确的打开方式是打印分类报告from sklearn.metrics import classification_report, confusion_matrix y_pred pipeline.predict(X_test) print(classification_report(y_test, y_pred, digits3))输出里每一行会给出该类别的精确率、召回率和 F1 值。精确率回答预测成这个类别的样本里有多少是对的召回率回答这个类别的真实样本里有多少被找回来了。对工单分派这种场景漏掉一个投诉工单比误分一个普通咨询代价高这时优先看召回率对内容过滤场景误杀正常内容代价更高优先看精确率。classification_report最后两行给出 macro avg 和 weighted avgmacro 是每个类别的 F1 直接平均不受样本量影响类别不均衡时应以它为主要参考。再配合混淆矩阵定位具体错误import numpy as np cm confusion_matrix(y_test, y_pred) # 找出错误最严重的类别对优先判断是特征问题还是标签噪声混淆矩阵能直接看出哪些类别经常互相混淆。比如投诉和咨询大量互混通常是训练语料里这两类标注边界模糊如果真的分不开考虑把这两个类合并或者引入新的特征字段。4.2 三个必调的参数ngram、特征规模和正则强度文本分类调参不需要玄学线性模型下影响最大的就三个维度特征窗口、特征规模、正则强度。把它们放进网格搜索一次跑出结果from sklearn.model_selection import GridSearchCV param_grid { vect__ngram_range: [(1, 1), (1, 2)], # 是否引入相邻词组合 vect__max_features: [10000, 20000], # 特征数量上限 clf__C: [0.1, 1.0, 10.0], # 逻辑回归正则强度 } grid GridSearchCV( pipeline, param_grid, cv5, # 5 折交叉验证 scoringf1_macro, # 不均衡数据用 macro F1 n_jobs-1, # 并行跑内存够才开 ) grid.fit(X_train, y_train) print(best params:, grid.best_params_)参数名里的vect__和clf__前缀对应 Pipeline 里定义的环节名称网格搜索靠这个前缀定位参数属于哪个环节。C是正则强度的倒数C越小正则越强、模型越保守特征维度高、噪音大的场景C0.1通常比默认的1.0效果更好。ngram_range从(1,1)提到(1,2)能捕获不_满意这类局部词序信息中文场景经常带来 2 到 3 个点的提升但特征量会成倍上涨所以和max_features一起调。网格搜索的维度不要一开始就铺满。先固定ngram_range调C再放开ngram_range最后压max_features比一次跑几十组组合更节省时间。如果语料超过十万条GridSearchCV的 5 折交叉会训练 5 倍的模型跑不动可以把cv降到 3或者改用RandomizedSearchCV随机采样参数组合。4.3 样本不均衡class_weight 与阈值移动多数真实语料是不均衡的投诉工单占 2%剩余 98% 是普通咨询。predict默认按概率最大值判类别少数类几乎永远被淹没。第一招是用class_weightpipeline.set_params(clf__class_weightbalanced) # 按类别频率自动加权 pipeline.fit(X_train, y_train)class_weightbalanced会让少数类的错分代价自动放大代价是多数类的误报率可能上升这是可以接受的交换。第二招是阈值移动不直接用argmax而是对少数类单独设定一个置信度门槛低于门槛就判为多数类。这个门槛用验证集扫描得到而不是拍脑袋定。proba pipeline.predict_proba(X_test) rare_idx pipeline.classes_.tolist().index(投诉) for thr in np.arange(0.3, 0.8, 0.05): y_conv (proba[:, rare_idx] thr).astype(int) # 在这里计算召回率和误报率画曲线选拐点这里不推荐一上来就用 SMOTE 之类的过采样。文本特征空间是稀疏高维的人工合成样本容易产生噪音优先把标注数据补一补或者用加权方式先看基线过采样放在最后对比验证时再决定。5. 把文本分类系统跑起来模型导出、接口与回归验证训练和调参告一段落后源码包的价值最后体现在模型能不能被其他服务调用。这一章处理三个具体问题如何把模型和配套状态完整持久化如何暴露一个最小可用的预测接口以及上线前如何用回归用例验证模型行为没有漂移。5.1 用 joblib 一次性保存模型、向量器和标签表Pipeline 已经把所有状态内聚到一个对象里保存时连标签表一起打包是工程上更稳的做法import joblib pack { pipeline: pipeline, # 完整的 向量化 模型 classes: list(pipeline.classes_), # 类别顺序预测时映射用 feat_names: vectorizer.get_feature_names_out(), # 排查特征时用 } joblib.dump(pack, text_clf.joblib, compress3) print(saved, size:, round(__import__(os).path.getsize(text_clf.joblib)/1024/1024, 2), MB)compress3会在序列化时压缩模型文件通常能缩小一半以上。加载侧要记得做一次保存后重载验证确认joblib.load出来的对象预测结果和内存里的 Pipeline 完全一致loaded joblib.load(text_clf.joblib)[pipeline] assert (loaded.predict(X_test[:10]) pipeline.predict(X_test[:10])).all()注意 Pipeline 在fit之后才会生成classes_属性保存前确认这个属性存在。跨 Python 版本加载 joblib 文件偶尔会报兼容性错误生产环境建议用 Docker 固定 Python 和 scikit-learn 版本不要随手升级环境。5.2 用 Flask 暴露一个最小预测接口模型落盘后常见的做法是用一个轻量 HTTP 服务包一层让业务系统通过 JSON 调用。Flask 足够处理这种标注场景from flask import Flask, request, jsonify import joblib app Flask(__name__) pack joblib.load(text_clf.joblib) pipe, classes pack[pipeline], pack[classes] app.post(/predict) # 只接受 POST避免 GET 缓存干扰 def predict(): body request.get_json(forceTrue) text body.get(text, ) if not text.strip(): return jsonify({error: empty text}), 400 proba pipe.predict_proba([text])[0] # probs 顺序与 classes_ 一致 idx int(proba.argmax()) return jsonify({ label: classes[idx], confidence: round(float(proba[idx]), 4), }) if __name__ __main__: app.run(host127.0.0.1, port8000)启动后在另一个终端可以直接验证curl -X POST http://127.0.0.1:8000/predict \ -H Content-Type: application/json \ -d {text: 打印机连接不上公司网络}接口里最容易踩的坑是类别顺序。predict_proba返回的每一列顺序与classes_严格一致保存时把classes一起存下来、返回时用它做映射就不会出现标签张冠李戴的问题。并发量上来之后Flask 内置服务器撑不住常见做法是换 gunicorn 起多 worker但要注意每个 worker 都会加载一份模型副本内存按 worker 数线性增长机器内存小就把 worker 数压到 2。5.3 上线前跑一遍回归用例模型上线的最后一道关卡是回归验证。准备一组覆盖每个类别、包含边界情况的黄金样本在每次模型更新或环境变更后跑一遍确认预测结果和预期一致GOLDEN [ (打印机无法连接公司网络, IT 工单), (退款什么时候到账, 财务), (今天天气怎么样, 其他), ] def predict_one(text: str) - str: proba pipe.predict_proba([text])[0] return classes[int(proba.argmax())] for text, expect in GOLDEN: got predict_one(text) assert got expect, f{text}: got {got}, expect {expect} print(all golden checks passed)回归用例的价值体现在你更新了分词词典、升级了 scikit-learn 版本或者改了清洗正则之后。任何一个环节的行为变化都会在这个用例集上暴露出来定位问题的成本远低于线上出了事故再查。如果预训练 embedding 或分词工具升级先用这批用例跑一遍旧模型再替换新模型能省掉大部分线上问题排查时间。本文还有配套的精品资源点击获取