FEATURED · 精选文章

Python机器学习文本分类器实战:从数据预处理到调参优化

发布时间 / 2026/9/14 17:37:08
来源 / 创域科博编辑部
栏目 / 资讯中心
Python机器学习文本分类器实战:从数据预处理到调参优化 简介面向具备Python基础的开发者这套基于Python的机器学习文本分类器源码以可运行的实例完整演示了从语料清洗、分词去停用词、TF-IDF特征构建到模型选择、网格搜索调参与性能评估的实用流程覆盖了文本分类任务的主要环节。压缩包共30个文件以27个Python脚本为主另有README说明、停用词表及Git配置整体仅19KB结构轻量脚本按功能拆分便于对照学习。已有272人学习浏览内容涉及线性模型、SVM、KMeans聚类、TF-IDF等常用方法并配有分词统计、三维可视化、单变量分析等辅助脚本可直接运行观察各步结果。借助这套代码读者能直观理解不同算法在文本分类中的表现节省搭建环境与代码框架的时间并可将示例迁移到自己的数据集上快速开启NLP分类实践。1. 用文件列表看懂一个Python机器学习文本分类器的骨架拿到一份名为“基于Python的机器学习文本分类器.zip”的项目压缩包打开后是一堆.py脚本和一个page-classify-master目录。别被文件数量吓到这套脚本实际覆盖了中文文本分类从预处理到模型评估的完整链路。其中preprocess.py负责清洗cut_and_cal_tfidf.py算特征binary_classify.py和scikit_learn_svm_demo.py是分类器grid_search.py做调参plot_3d_scatter.py和plot_3d_surface.py用来查看特征分布和决策边界。对想用 Python 快速落地文本分类的人这包东西比单独看 sklearn 文档有用因为你能看到工程上每个环节是怎么接起来的。2. 预处理与分词中文语料的清洗策略2.1 语料读取与格式统一在page-classify-master这类目录里语料通常按类别分文件夹存放每个文件是一篇文档。corpus.py的核心任务是把这些文件读进来整理成“文本-标签”两列方便后续处理。常见做法是import os import pandas as pd def load_corpus(root_dir): texts, labels [], [] for label in os.listdir(root_dir): label_dir os.path.join(root_dir, label) if not os.path.isdir(label_dir): continue # 跳过非目录文件 for fname in os.listdir(label_dir): path os.path.join(label_dir, fname) with open(path, encodingutf-8, errorsignore) as f: texts.append(f.read()) labels.append(label) return pd.DataFrame({text: texts, label: labels})这段代码用os.listdir遍历每个类别目录把目录名当作标签。errorsignore是为了避免个别文件里出现非法编码导致整个语料加载失败。如果项目里的数据是 gbk 编码就需要把encoding换成gbk或者先检测编码再读取。实际项目里我倾向于用chardet逐个文件检测避免“一错全错”。读取后还要做一步检查类别分布是否均衡。用df[label].value_counts()打印一下如果某个类别只有几十篇而另一类几千篇后面模型评估的准确率会失真。这时要考虑欠采样或者在模型里设置class_weight。2.2 清洗规则与停用词表预处理不是越多步越好关键看语料来源。preprocess.py里比较常见的清洗口径包括去掉 HTML 标签、压缩连续空格、去掉年份数字和无意义符号。对网页类文本抓下来的内容经常带p、a之类标签re.sub(r[^], , text)一次就能去掉。stopwords.txt的作用是过滤掉“的、了、是、在”这类高频却无类别区分度的词。注意不要直接套用网上下载的停用词表要按自己的语料调整。比如做财经新闻分类时“公司”“市场”可能每个类别都出现但分类器并不靠它们区分保留反而增加维度。常见做法是把训练集里出现频率最高的一批词抽出来人工看一遍把明显无区分度的词加进stopwords.txt。下面这段把清洗、停用词、分词合到一起import re import jieba def load_stopwords(pathstopwords.txt): with open(path, encodingutf-8) as f: return set(line.strip() for line in f) def preprocess(text, stopwords): # 去掉网页标签 text re.sub(r[^], , text) # 去掉英文和数字 text re.sub(r[a-zA-Z0-9], , text) text re.sub(r\s, , text) words jieba.lcut(text) return [w for w in words if w.strip() and w not in stopwords]load_stopwords用集合存储成员判断是 O(1)几万次匹配也不会慢。jieba.lcut返回list比jieba.cut返回生成器更适合在特征提取前一次性拿到结果。过滤时加了w.strip()防止分词结果里出现空格字符。补充一点不要过度清洗。像“机器学习”“文本分类”这种专业词汇一旦被去掉类别特征就没了。清洗规则最好先跑一版观察剩余词汇再决定保留哪些。2.3 分词与自定义词典中文文本分类的分词策略直接影响特征维度。jieba默认词典对通用领域够用但如果语料里有“自然语言处理”“BERT”这类词直接lcut可能会被切开。我一般在preprocess.py里加一句jieba.load_userdict(user_dict.txt)把业务词和专有名词写进去每行一个词。自定义词典的优先级高于默认词典分词稳定后 TF-IDF 的特征会更准。分词后的文本最终要拼回空格分隔的字符串因为 sklearn 的向量化器默认按空格分词def cut_and_join(text, stopwords, user_dictuser_dict.txt): if user_dict: jieba.load_userdict(user_dict) # 加载自定义词典 return .join(preprocess(text, stopwords))这里返回的字符串可以直接喂给TfidfVectorizer。如果后面还要用feature_extract.py做特征分析建议保留成list形态便于统计词频。任务常用方法作用去标签re.sub(r[^], , text)去掉网页标签去英文数字re.sub(r[a-zA-Z0-9], , text)去掉无区分度的字符压缩空白re.sub(r\s, , text)减少噪声分词jieba.lcut(text)切分为词列表停用词过滤w not in stopwords去掉无意义词3. TF-IDF特征提取与权重计算3.1 为什么不直接用词袋词袋模型统计每个词在文档中出现的次数问题是长文本里词频高但未必代表类别。TF-IDF 用“词频 × 逆文档频率”压制那些在太多文档里都出现的词。具体来说TF 是某个词在单篇文档中的频率IDF 是log(总文档数 / 包含该词的文档数)。某个词只在某类文本里高频IDF 就高权重会放大分类器更容易捕捉到类别差异。cut_and_cal_tfidf.py和tfidf.py干的就是这件事前者会调用预处理结果把原始语料切成词串再交给 TF-IDF 计算模块后者通常封装了向量化器的调用。3.2 TfidfVectorizer 常用参数用 sklearn 实现时特征提取可以写成下面的函数from sklearn.feature_extraction.text import TfidfVectorizer def build_tfidf(corpus, min_df2, max_df0.8, ngram_range(1, 2), max_features50000): vectorizer TfidfVectorizer( min_dfmin_df, # 忽略文档频率低于该值的词 max_dfmax_df, # 忽略文档频率高于该比例的词 ngram_rangengram_range, sublinear_tfTrue, # 用 1log(tf) 平滑词频 max_featuresmax_features, norml2 ) X vectorizer.fit_transform(corpus) return vectorizer, Xmin_df2表示词至少在 2 篇文档中出现过过滤只在单篇出现的词max_df0.8表示在 80% 以上的文档中都出现的词会被忽略这类词通常是停用词ngram_range(1,2)会把“机器学习”这种组合词纳入特征sublinear_tfTrue用1log(tf)代替原词频避免长文本里频率差异被放大max_features是最后一道维度压闸。这些参数看似简单却是文本分类效果差距的主要来源。比如max_df设得太小会丢掉“虽然常见但对某些类别有指示性”的词设太大又引入噪声需要配合网格搜索一起调。3.3 特征分析与类别特征词feature_extract.py和class_feature.py里通常会做一类事情按类别提取权重最高的词帮助判断特征是否合理。训练完向量化器后可以这样看每个类的特征词def top_features_by_class(vectorizer, X, y, top_k10): import numpy as np feature_names vectorizer.get_feature_names_out() label_to_indices {} for idx, label in enumerate(y): label_to_indices.setdefault(label, []).append(idx) for label, indices in label_to_indices.items(): # 求每个类别在特征维度上的平均权重 mean_weight X[indices].mean(axis0) sorted_indices np.argsort(mean_weight.A1)[::-1][:top_k] print(label, [feature_names[i] for i in sorted_indices])这里X[indices].mean(axis0)求出某类别所有样本在每维特征上的平均权重再取 TopK。如果输出中发现“的”“是”这类词排在最前说明停用词表没过滤干净或者max_df设置过高需要回头调整。class_feature.py如果做的是类别特征选择常见做法是结合卡方检验用SelectKBest(chi2, k10000)从 TF-IDF 矩阵里挑出与标签相关性最高的 1 万个特征。虽然TfidfVectorizer本身已经按词频过滤过但卡方选择能进一步去掉与类别独立的噪声词。要注意卡方检验要求特征非负TF-IDF 的默认输出就是非负的但使用sublinear_tf或norm时不会改变这一点可以直接应用。参数取值建议说明min_df1-5低于该文章频次的词舍去max_df0.7-0.9高于该比例的词舍去ngram_range(1,2)保留单个词和相邻双词sublinear_tfTrue用对数值平滑词频max_features30000-80000控制特征矩阵规模4. 分类器选择与训练SVM与逻辑回归4.1 从二分类到多分类binary_classify.py解决的是二分类page_classify.py面向网页多分类。sklearn 里的LinearSVC和LogisticRegression对多分类都默认采用 one-vs-rest 策略也就是“每个类别训练一个二分类器新样本逐个打分取分数最高的类别”。文本特征往往是高维稀疏矩阵样本量不会太大线性模型在这个场景下通常比树模型更稳。4.2 用 LinearSVC 训练文本分类器scikit_learn_svm_demo.py里的 SVM 示例可以直接扩展成完整训练流程from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.svm import LinearSVC from sklearn.pipeline import Pipeline from sklearn.metrics import classification_report pipeline Pipeline([ (tfidf, TfidfVectorizer(min_df2, max_df0.8, ngram_range(1, 2))), (clf, LinearSVC(C1.0, class_weightbalanced)) ]) pipeline.fit(X_train, y_train) y_pred pipeline.predict(X_test) # 输出每个类别的精确率、召回率、F1 print(classification_report(y_test, y_pred, target_namesclass_names))Pipeline把向量化和分类器绑在一起调参时不会漏掉中间步骤。LinearSVC的C是误分类惩罚系数C越小对错误分类的容忍越大决策边界越简单C越大模型越容易过拟合。文本分类里我一般从C1.0开始再用网格搜索微调。class_weightbalanced会在类别不平衡时按样本量反比给权重防止少数类全被吞掉。LinearSVC本质上是带 L2 正则的线性模型没有用到核函数。不要在文本特征上直接用SVC(kernelrbf)因为 TF-IDF 矩阵动辄几万维RBF 核会把每个样本映射到无穷维训练时间翻几十倍效果还不如线性核。4.3 逻辑回归与 SGDClassifier逻辑回归是另一个合适基线。它比LinearSVC多一个天然的predict_proba能输出置信度后续做阈值调整和业务对接都方便。训练代码几乎完全一样只需把分类器换掉from sklearn.linear_model import LogisticRegression # 替换分类器为逻辑回归保留前面的向量化环节 pipeline.set_params(clfLogisticRegression(C1.0, max_iter1000)) pipeline.fit(X_train, y_train)max_iter1000是为了避开默认max_iter100经常出现的“不收敛”警告。如果语料很大可以用SGDClassifier(losslog_loss)做在线学习但要注意它需要稳定的特征缩放在 TF-IDF 特征上要保留norml2否则梯度更新不稳定。4.4 分类指标的选择模型优点需要注意LinearSVC高维稀疏特征下训练快边界清晰无概率输出LogisticRegression可输出概率可解释性好需要调max_iterSGDClassifier支持增量训练适合大数据流对特征缩放敏感评估时不要只看 accuracy多分类文本分类里各类别样本往往不均衡。打印classification_report看每个类别的 precision、recall、F1同时计算宏平均 F1。调参时也要把评分函数从默认准确率改成f1_macro否则少数类很容易被牺牲掉。5. 网格搜索与超参数调优5.1 想清楚调什么文本分类里需要联合调的是特征提取和分类器两部分而不是只调分类器。grid_search.py里常见的套路是把TfidfVectorizer的min_df、max_df、ngram_range和LinearSVC的C放进同一个参数空间。这样能避免“调好了 C却因为max_df不合适导致效果上不去”的尴尬情况。5.2 使用 Pipeline GridSearchCV代码如下from sklearn.model_selection import GridSearchCV param_grid { tfidf__min_df: [1, 2], tfidf__max_df: [0.7, 0.8, 0.9], tfidf__ngram_range: [(1, 1), (1, 2)], clf__C: [0.1, 1.0, 10.0] } # cv5 表示五折交叉验证scoring 指定优化目标 gs GridSearchCV(pipeline, param_grid, cv5, scoringf1_macro, n_jobs-1, verbose1) gs.fit(X_train, y_train) print(gs.best_params_) print(gs.best_score_)param_grid里每个键的名字要遵循“步骤名__参数名”的规则。cv5表示五折交叉验证scoringf1_macro让网格搜索以宏平均 F1 为标准n_jobs-1表示用满所有 CPU。这个组合会训练2*3*2*3*5 180个模型如果语料到几十万篇整个搜索会非常慢。可以先用max_features20000缩小特征范围把网格大致跑通再加回去精调。如果参数空间更大可以换成RandomizedSearchCV每轮从分布里抽样。文本特征维度高网格组合爆炸很快随机搜索往往能在同样时间内找到更优的参数。但要注意对min_df、max_df这类离散参数尽量用列表而不是连续分布避免搜出无意义的值。5.3 搜索过程的常见坑网格搜索最常见的坑是交叉验证数据泄漏。如果先在整个训练集上计算 TF-IDF 再切分验证集的信息已经掺进特征统计里结果会虚高。所以必须把向量化器放进Pipeline让每一折都单独 fit。第二个坑是多分类时评分函数选错比如默认accuracy在类别不平衡下完全没有意义。第三个坑是n_jobs-1在共享内存环境下会同时复制多份特征矩阵机器内存不够时直接 Out of Memory可以先降成n_jobs2。参数影响推荐起点min_df越高噪声越少但可能丢失稀有类别词2max_df越低越能去掉公共词太高保留噪声0.8ngram_range开启双词后特征数翻倍训练变慢(1,2)C越小越正则太大过拟合1.0scoring决定搜索方向f1_macro网格搜索结束后不要直接拿best_score_当最终效果。最好再用固定随机种子的train_test_split跑一次独立的测试集得到最终指标否则容易高估模型泛化能力。6. 可视化验证与决策边界观察项目里plot_3d_scatter.py和plot_3d_surface.py这类脚本目的是为了确认特征和模型有没有把类别“分开”。TF-IDF 特征动辄几千维没法直接画图常见做法是用 PCA 或TruncatedSVD降到三维再撒点看。下面这段代码可以在小数据集上快速验证from sklearn.decomposition import TruncatedSVD import matplotlib.pyplot as plt from mpl_toolkits.mplot3d import Axes3D # 将稀疏 TF-IDF 矩阵降到 3 维便于观察 svd TruncatedSVD(n_components3, random_state42) X_reduced svd.fit_transform(X_tfidf) fig plt.figure(figsize(10, 8)) ax fig.add_subplot(111, projection3d) colors {体育: blue, 财经: green, 教育: red} ax.scatter(X_reduced[:, 0], X_reduced[:, 1], X_reduced[:, 2], c[colors[label] for label in y], s5, alpha0.6) plt.savefig(pca_text_class.png, dpi100)这里TruncatedSVD适合稀疏矩阵可以直接对 TF-IDF 矩阵降维。如果散点图里同一类别的点明显聚在一起说明特征提取有效如果不同类别完全重叠问题多半出在预处理或参数上不要急着换分类器。plot_3d_surface.py那种曲面图通常是在二维特征平面上画分类器的决策面。对文本分类我会先降维到两维再用网格生成坐标点喂给训练好的模型预测类别把预测结果画成背景颜色原始样本点叠上去。这样能直观看到边界是否过拟合边界太曲折往往是C过大或min_df太小。最后的技巧是把网格搜索结果的可视化和特征降维图放在一起看。plot_linear.py里画的是回归/分类的直线拟合虽然脚本很简单但替换成gs.cv_results_里的数据点就能画出参数与 F1 的折线图。比如查看C在[0.1, 1, 10]之间的变化趋势如果C10时训练 F1 很高、交叉验证 F1 反而下降说明模型过拟合此时应该回头调min_df或ngram_range而不是继续加大C。这套“特征降维 边界可视化 调参曲线”的验证顺序能把文本分类器的调参过程从试错变成可解释的路径。本文还有配套的精品资源点击获取
RELATED — 相关阅读

相关资讯

LATEST — 最新资讯

最新发布

TODAY — 本日精选

新闻

WEEKLY — 本周精选

新闻

MONTHLY — 本月精选

新闻