Python文本分类实战:从txt清洗到TF-IDF与六模型对比
发布时间:2026/10/1 18:07:50来源:尧图网络
简介基于Python实现的文本分类系统源码包适合计算机相关专业学生、机器学习初学者以及需要完成课程设计、毕业设计或算法对比实验的开发者。项目完整演示了从文本预处理、TFIDF特征提取到多模型训练评估的闭环流程内置KNN、朴素贝叶斯、支持向量机、逻辑回归、决策树和随机森林六种分类算法自动计算并输出各算法准确率便于横向比较不同模型的泛化能力。资源包共8个文件包含2个Python源码文件、3个txt原始文本文件、2个csv格式化数据集以及1个Markdown说明文档整体压缩包约3.7MB依赖库均为pandas、numpy、nltk、sklearn等常见工具结构简洁、上手门槛低。已有69人学习/下载。通过源码可以熟悉去除空格、转小写、分词、词性标注、词形还原等预处理细节也可以直接复用TFIDF建模、模型训练与准确率评估代码快速复现并扩展完整文本分类实验。1. 文本分类系统源码包从 txt 到 CSV 再到六模型对比的一次完整落地做文本分类的课程设计、毕业设计或者第一次拿 sklearn 做 NLP 练手的人通常会卡在同一个地方手里有文本、有标签但不知道怎么把原始 txt 串成一条能跑通机器学习流程的链路。这套基于 Python 的文本分类系统源码解决的正是这个问题——它把项目拆成了 format.py 和 classification.py 两个脚本前者把零散的文本数据格式化成 CSV后者用 KNN、朴素贝叶斯、SVM、逻辑回归、决策树、随机森林六个模型做训练和准确率对比。对计算机专业做课设的人来说它是个可以直接抄作业的完整工程对想搞懂 sklearn 文本分类细节的人来说它的预处理链路去空格、小写化、分词、词性标注、词形还原和 TF-IDF 特征提取也是有参考价值的范本。接下来我把每个文件的用途、关键代码逻辑和实际踩过的坑拆开讲。2. 先把数据整明白format.py 如何把 txt 规整成 CSV任何文本分类项目的第一步都不是建模而是确认输入数据长什么样。这套源码里放着 texts_prelabel.txt、trains.txt、tests.txt 和 formats.py说明原始数据是分散的文本文件需要先统一格式才能喂给 pandas 和 sklearn。2.1 为什么非要转成 CSV 不可pandas 读 txt 不是不行但原始文本往往带有多余空格、换行符、注释行而且标签和文本内容通常混在一起。直接 pd.read_csv 去读一个预标注的 txt会遇到分隔符不统一、列数不匹配、大小写混乱等问题。format.py 存在的意义就是把这些脏数据洗成两张标准表trains_deal.csv 和 tests_deal.csv。这两张表结构一致至少包含 text 和 label 两列后续 classification.py 只认这个接口不用再关心原始数据长什么样。我在处理这套源码的时候默认的 CSV 结构是这样的import pandas as pd # 读取原始txtheaderNone因为原始文件没有列名 raw pd.read_csv(texts_prelabel.txt, sep\t, headerNone, names[label, text]) # 去掉首尾空格、把标签转为字符串类型 raw[text] raw[text].str.strip() raw[label] raw[label].astype(str).str.strip() # 剔除空文本 raw raw[raw[text].str.len() 0] # 格式化并保存 raw.to_csv(trains_deal.csv, indexFalse, encodingutf-8) raw.to_csv(tests_deal.csv, indexFalse, encodingutf-8)这段代码的逻辑是先把原始文件按 Tab 分隔符拆成 label 和 text 两列然后清洗两列的空白字符最后去掉空文本行。sep\t这个参数要看原始文件实际情况如果原始文件是逗号分隔就要改成sep,encodingutf-8在 Windows 下如果遇到 Excel 打开乱码可以换成utf-8-sig。清洗文本的空行这一步很重要空文本进模型会让 TfidfVectorizer 报空词汇表的错误。2.2 训练集和测试集的正规划分源码里同时存在 txts_prelabel.txt、trains.txt、tests.txt 三个文件说明原始数据本身可能已经划分好了训练和测试。最稳妥的做法是先把全部数据格式化再做分层划分但如果是课程设计提交通常要求训练集、测试集分开处理。我一般会这样处理from sklearn.model_selection import train_test_split dealed pd.read_csv(trains_deal.csv) train, test train_test_split(dealed, test_size0.2, random_state42, stratifydealed[label]) train.to_csv(trains_deal.csv, indexFalse) test.to_csv(tests_deal.csv, indexFalse)stratifydealed[label]是分层抽样参数保证划分后训练集和测试集的类别比例与原数据一致。如果原始数据类别不平衡少了这个参数会让某些类在测试集里缺失准确率算出来就是虚的。random_state42固定随机种子确保每次跑出来的划分结果一致方便对比不同模型的效果。3. 文本预处理与 TF-IDF 特征提取这套源码的核心引擎CSV 只是把数据装进了统一容器真正决定分类效果的是文本预处理和特征提取。这套源码的预处理链路包含去除空格、转换为小写、分词、词性标注和词形还原这个组合在英文语料上非常标准。3.1 词性标注与词形还原的配合逻辑分词之后直接做词形还原会有一个经典问题lemmatizer 默认把词当名词处理导致 running 还原成 running 而不是 run。要解决这个问题必须先做词性标注POS tagging把动词、名词、形容词区分开再传给 lemmatizer。源码里的预处理函数我补全后大概是这样import nltk from nltk.corpus import stopwords from nltk.stem import WordNetLemmatizer from nltk.corpus import wordnet lemmatizer WordNetLemmatizer() # 词性标注的tag需要映射成wordnet认识的格式 def get_wordnet_pos(tag): if tag.startswith(J): return wordnet.ADJ elif tag.startswith(V): return wordnet.VERB elif tag.startswith(N): return wordnet.NOUN elif tag.startswith(R): return wordnet.ADV else: return wordnet.NOUN def preprocess_text(text): # 去除多余空格 text .join(text.split()) # 转小写 text text.lower() # 分词 tokens nltk.word_tokenize(text) # 去停用词和纯标点 tokens [tok for tok in tokens if tok not in stopwords.words(english) and tok.isalpha()] # 词性标注 tagged nltk.pos_tag(tokens) # 按词性做词形还原 lemmatized [lemmatizer.lemmatize(word, get_wordnet_pos(pos)) for word, pos in tagged] return .join(lemmatized)这段代码的关键在最后两步nltk.pos_tag给每个词标上 NN、VB、JJ 这类标签然后get_wordnet_pos把 nltk 的标签映射成 wordnet 能识别的类别。如果不做这个映射lemmatize默认的 pos 参数是名词动词变化就还原不干净。tok.isalpha()会过滤掉数字和纯标点这一条对英文语料有效但如果是中文语料就要换 jieba 分词。3.2 TF-IDF 特征提取的参数选择预处理完的文本还不能直接进模型需要把字符串变成数值向量。这套源码用的是 TF-IDF比简单的词频统计多了一个逆文档频率的权重让在少数文档中出现的词获得更高权重从而削弱 the、and 这类高频无意义词的干扰。TfidfVectorizer 的常用参数配置如下from sklearn.feature_extraction.text import TfidfVectorizer vectorizer TfidfVectorizer( max_features5000, # 只保留出现最多的5000个词 ngram_range(1, 2), # 考虑单词和相邻双词组合 sublinear_tfTrue, # 用 1log(tf) 平滑词频 min_df2 # 至少在2篇文档中出现过 ) X_train vectorizer.fit_transform(train_texts) X_test vectorizer.transform(test_texts)max_features5000是控制维度上限的关键参数默认情况下 TfidfVectorizer 会把所有不重复的词都算进来几万维的稀疏矩阵会拖慢 KNN 和决策树的训练速度。ngram_range(1, 2)把 not good 这种双词组合也作为特征对情感分类这类任务帮助很大但维度也会相应膨胀所以要用 max_features 做截断。sublinear_tfTrue用对数缩放词频防止长文档的某个词频数字过分压制其他词。这里有一个必须强调的铁律fit_transform只能用在训练集上测试集必须用训练好的 vectorizer 做transform不能重新 fit。否则测试集的特征空间会与训练集不一致模型预测时特征维度对不上准确率也会虚高。4. 六个机器学习模型的训练与评估classification.py 的完整链路数据准备好了特征提取器也跑通了接下来进入 classification.py 的核心环节。这个脚本把六个模型全部训练一遍在相同训练集/测试集切分下计算准确率最后输出一个横向对比结果。4.1 六个模型的代码骨架与选型理由import pandas as pd from sklearn.model_selection import train_test_split, cross_val_score from sklearn.neighbors import KNeighborsClassifier from sklearn.naive_bayes import MultinomialNB from sklearn.svm import SVC from sklearn.linear_model import LogisticRegression from sklearn.tree import DecisionTreeClassifier from sklearn.ensemble import RandomForestClassifier from sklearn.metrics import accuracy_score, classification_report # 读取格式化后的数据 train pd.read_csv(trains_deal.csv) test pd.read_csv(tests_deal.csv) # 预处理文本复用上面的preprocess_text函数 train[text] train[text].apply(preprocess_text) test[text] test[text].apply(preprocess_text) # 特征提取 vectorizer TfidfVectorizer(max_features5000, ngram_range(1, 2), sublinear_tfTrue) X_train vectorizer.fit_transform(train[text]) X_test vectorizer.transform(test[text]) y_train train[label] y_test test[label] # 定义六个模型统一用默认参数 models { KNN: KNeighborsClassifier(n_neighbors5), Naive Bayes: MultinomialNB(), SVM: SVC(kernellinear, C1.0), Logistic Regression: LogisticRegression(max_iter1000), Decision Tree: DecisionTreeClassifier(random_state42), Random Forest: RandomForestClassifier(n_estimators100, random_state42) } # 训练并评估 for name, model in models.items(): model.fit(X_train, y_train) y_pred model.predict(X_test) acc accuracy_score(y_test, y_pred) print(f{name}: {acc:.4f}) print(classification_report(y_test, y_pred, zero_division0))这段代码里我对六个模型做了统一封装每个模型创建时带了关键的初始化参数。KNN 的n_neighbors5是最常用的默认值KNN 在 TF-IDF 稀疏高维特征上表现通常一般因为欧氏距离在高维空间区分度会退化SVM 选了kernellinear而不是默认的 RBF因为线性核在文本分类这种高维稀疏场景下效果通常更好RBF 核反而容易过拟合LogisticRegression 的max_iter1000是为了防止默认的 100 次迭代在文本特征上不收敛报警告。zero_division0这个参数很多新手会忽略当某个类别在测试集中没有出现时classification_report 会报除零错误加上它才能让程序跑完。4.2 模型表现的预期判断在文本分类任务上六个模型的典型表现排序是有规律的SVM 和逻辑回归通常稳居前二朴素贝叶斯紧随其后随机森林视数据量而定KNN 和决策树一般垫底。原因不复杂文本经过 TF-IDF 后是成千上万维的稀疏向量SVM 和逻辑回归本身就擅长高维线性分类朴素贝叶斯对词频独立性假设在文本上基本成立决策树在高维稀疏数据上容易过拟合而且划分特征时的信息增益计算会偏向取值多的词。我在实际跑这套源码时还喜欢加一个交叉验证来替代单次切分避免因为 random_state 不同导致某次切分对某个模型特别友好from sklearn.model_selection import cross_val_score for name, model in models.items(): scores cross_val_score(model, X_train, y_train, cv5, scoringaccuracy) print(f{name} CV: {scores.mean():.4f} (/- {scores.std():.4f}))cv5表示五折交叉验证把训练数据切成五份轮流拿四份训练、一份验证最终准确率取五次均值。这样比单次 train_test_split 更稳能看出哪个模型是泛化好、哪个只是碰运气。下表是我在类似规模的文本数据上跑出来的典型准确率区间供你对照自己的结果判断模型有没有跑歪模型典型准确率区间训练速度备注SVM (linear)0.85~0.92中文本分类首选Logistic Regression0.84~0.91快与SVM结果接近Multinomial Naive Bayes0.80~0.88极快适合词频类特征Random Forest0.78~0.86慢随n_estimators增加变慢KNN0.60~0.75极慢高维稀疏数据不推荐Decision Tree0.60~0.70快容易过拟合如果某个模型的准确率明显低于这个区间优先检查特征提取是否用了fit_transform在测试集上重新拟合或者预处理阶段是不是把标签也一起清洗了。5. 避坑指南文本分类源码复现的四个常见问题这个源码包整体结构不复杂但我在帮别人排查问题时发现翻车往往不在建模本身而在数据准备和特征提取的细节上。以下四条是我见过最多、也最值得写进笔记的坑。5.1 测试集特征泄漏准确率虚高的头号原因现象模型准确率高达 0.95 以上换一组数据立刻暴跌到 0.6或者训练时直接报 feature mismatch 错误。原因在划分训练集和测试集之前就把全部数据拿去做fit_transform。这样 TfidfVectorizer 的词汇表里包含了测试集的词频信息相当于模型提前看到了测试集的内容。或者反过来对测试集单独调用了fit_transform导致测试集特征矩阵的列数与训练集不一致。解决严格遵守训练集 fit_transform、测试集仅 transform的规则。同一个 vectorizer 实例训练阶段用vectorizer.fit_transform(X_train)测试阶段用vectorizer.transform(X_test)禁止对测试集调用 fit 系列方法。如果不小心对测试集做了 fit把这个 vectorizer 重新 new 一个实例再跑一遍。5.2 nltk 数据包缺失word_tokenize 直接抛异常现象nltk.word_tokenize报错提示 LookupError 找不到 punkt 资源连续点重试也无济于事。pos_tag和WordNetLemmatizer也分别需要 averaged_perceptron_tagger 和 wordnet 数据包。原因nltk 的分词、词性标注、词形还原依赖外部语料数据这些数据默认存放在用户目录的 nltk_data 文件夹下pip 安装 nltk 时并不会自动下载。解决在代码开头显式下载这三个包或者离线下载后放到项目目录里import nltk nltk.download(punkt) nltk.download(averaged_perceptron_tagger) nltk.download(wordnet)如果网络环境受限可以在另一台机器上下载 nltk_data 目录整个拷贝到当前机器的~/nltk_data下。我习惯在preprocess_text函数被调用之前先确认这些资源存在否则跑一半才报错前面所有预处理时间都白费。5.3 中文语料直接套源码乱码和空词汇表现象把英文语料换成中文tok.isalpha()过滤后文本全空TfidfVectorizer 报 empty vocabulary或者 CSV 打开是乱码。原因这套源码的预处理链路是典型的英文 NLP 流程nltk 的 word_tokenize 对中文直接按空格切分而中文句子没有空格isalpha()对中文字符的保留逻辑也与英文不同。CSV 读取时如果用了默认编码Windows 下会与文件实际编码不符。解决中文语料要换 jieba 分词去掉 nltk 相关的词形还原部分只保留去空格、小写化中文不涉及、分词和去停用词。CSV 读写统一用encodingutf-8-sig。举例来说把预处理函数改成import jieba def preprocess_chinese(text): text .join(text.split()) tokens [tok for tok in jieba.lcut(text) if tok.strip() and tok not in chinese_stopwords] return .join(tokens)这个改动意味着词性标注和词形还原两个环节在中文场景下直接失效属于源码的适用范围边界拿到手先看清楚语料语言再决定怎么改。5.4 KNN 训练慢到怀疑人生现象其他模型几秒钟跑完KNN 跑了十分钟还没出结果或者直接内存溢出。原因KNN 是惰性学习算法训练阶段只是把样本存起来真正的计算发生在 predict 阶段——对每个测试样本都要计算与所有训练样本的距离。TF-IDF 特征动辄五千维3000 条训练样本预测 1000 条测试数据就需要做 300 万次高维距离计算。解决如果只是为了交作业可以保留 KNN 但减小数据规模如果想认真优化把max_features降到 2000 以下或者用NearestNeighbors配合 KDTree 加速。项目里跑 KNN 时把所有模型训练放在一个脚本里KNN 放在最后执行这样前面的输出不会因为 KNN 卡住而丢失。我用过一次 5000 维特征、8000 条样本跑 KNN等了将近二十分钟从那以后我默认在 KNN 前先看一眼数据规模。6. 把准确率再往上拉网格搜索调参与模型持久化六个模型的默认参数对比只是基线真要拿高分或者应对验收时的提问还需要做两件事调参和保存模型。先看调参。SVM 的C值、TfidfVectorizer 的ngram_range和max_features是文本分类最值得调的三个参数。用 GridSearchCV 可以一次性搜出最优组合from sklearn.model_selection import GridSearchCV from sklearn.pipeline import Pipeline pipeline Pipeline([ (tfidf, TfidfVectorizer()), (svm, SVC()) ]) param_grid { tfidf__max_features: [3000, 5000, 8000], tfidf__ngram_range: [(1, 1), (1, 2)], tfidf__sublinear_tf: [True, False], svm__C: [0.1, 1, 10] } grid GridSearchCV(pipeline, param_grid, cv3, scoringaccuracy, n_jobs-1) grid.fit(train[text], train[label]) print(grid.best_params_) print(grid.best_score_)这里用 Pipeline 把特征提取和分类器串起来好处是交叉验证时每次划分都不会发生特征泄漏。n_jobs-1让所有 CPU 核心并行搜索3 折交叉验证乘以 3×2×2×336 组参数并行起来也就几分钟的事。GridSearchCV 跑完后直接用grid.best_estimator_就是调好参的完整模型。然后是模型持久化。课程设计答辩时演示环境不一定装了全部依赖把训练好的模型存下来现场直接预测新文本会省掉很多麻烦import joblib # 保存完整pipeline joblib.dump(grid.best_estimator_, text_classifier.pkl) # 加载模型并预测新文本 loaded_model joblib.load(text_classifier.pkl) new_texts [ This course was amazing and well organized., The delivery was late and the quality was poor. ] predictions loaded_model.predict(new_texts) print(predictions)joblib是 sklearn 官方推荐的持久化工具比 pickle 更适合保存包含大量 numpy 数组的模型对象。保存的对象里已经包含 TfidfVectorizer 的词汇表所以新文本进来直接走完整的预处理和向量化流程不需要重新 fit 特征提取器。预测结果是一个类别标签数组直接对应原始数据里 label 列的值。最后说一个我自己的习惯每次跑完模型对比我都会把六个模型的准确率、训练时长、预测时长记录下来连同调参后的最优参数一起写进 README。这样答辩时被问到为什么选这个模型和参数是怎么定的直接拿出来就是一页数据支撑。这套源码我复现过不止一次每次都是在数据格式化和特征泄漏这两个环节翻车把这两关把住后面就是水到渠成的事。希望帮到你。本文还有配套的精品资源点击获取
网站建设高端定制企业官网