ARTICLE DETAIL

资讯详情

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

Python新闻文本分类系统源码实战:从分词到模型调优

Python新闻文本分类系统源码实战:从分词到模型调优 简介这份源码资源面向具备一定Python基础、希望入门中文短文本分类的学习者与开发者围绕新闻文本分类任务提供了一套可运行的对比实验框架帮助理解不同算法在真实语料上的表现差异。项目以搜狗实验室新闻数据集为训练与测试语料采用keras搭建深度学习模型、sklearn实现传统机器学习方法并引入word2vec预训练词向量覆盖SVM、SVMword2vec、LSTM、LSTMword2vec、MLP、KNN与朴素贝叶斯等多种方案的横向比较。资源包共9个文件以txt数据与说明文件、py源码脚本和md文档为主压缩包约1.99MB其中源码负责模型训练与工具函数封装文本文件承载训练与测试语料及标签便于直接复现实验流程。目前已有58人学习下载适合作为课程设计、毕业设计或文本分类入门实践的参考案例读者可借此掌握从数据加载、词向量加载到多模型评估的完整链路并在此基础上替换语料或扩展算法。1. 新闻文本分类系统到底在解决什么问题从一份 Python 源码说起你手上有一批新闻数据可能是爬下来的也可能是业务系统里攒的几千条到几十万条不等。老板或者导师问你能不能自动把这些新闻分到体育、财经、科技、娱乐这些类别里人工标太慢规则匹配又太脆——今天加个元宇宙明天来个固态电池关键词表永远追不上新词。这时候一套基于 Python 的新闻文本分类系统就是刚需它把分词 → 向量化 → 训练分类器 → 预测新文本这条链路固化下来你换一批数据、调几个参数就能复用。这份源码标题里的关键词是 Python、新闻文本分类系统、源码。它适合三类人一是做课程设计或毕设的学生需要一套能跑通、能改、能写进论文的完整流程二是刚转 NLP 方向的工程师想找一个比调 HuggingFace 一行代码更能看清内部机制的练手项目三是需要快速搭一个文本分类原型的开发者先跑通 baseline再决定要不要上深度学习。下面我按数据怎么进、模型怎么训、坑在哪、怎么调优的顺序把这类系统的落地路径讲清楚。2. 数据准备与中文分词把原始新闻变成模型能吃的格式2.1 新闻语料的典型结构和清洗要点一份新闻文本分类系统的输入通常是一个目录里面按类别分子目录放 txt 文件或者一个 CSV两列text和label。我见过最多的翻车场景是编码问题——Windows 下存的 GBK 文件在 Linux 上读出来全是乱码模型训练出来准确率 0.5 都不到还以为是算法不行。所以第一步永远是统一编码和去噪。清洗要处理的东西包括HTML 标签残留爬虫没清干净、全角半角混用、连续空白、URL 和邮箱、以及新闻里常见的本报讯记者某某这类对分类无用的模板句。但注意不要过度清洗——把数字全删了财经新闻的涨了 3.5%就没了把标点全去了句子边界就糊了。我的经验是去 HTML、去 URL、统一空白这三步必做数字和标点保留交给后面的分词和向量化去处理。import re import os 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 text.replace(\u3000, ) # 合并连续空白 text re.sub(r\s, , text).strip() return text 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): fpath os.path.join(label_dir, fname) # errorsignore 防止个别坏字节导致整个流程崩掉 with open(fpath, r, encodingutf-8, errorsignore) as f: raw f.read() cleaned clean_text(raw) if len(cleaned) 10: # 太短的样本直接丢多半是脏数据 continue texts.append(cleaned) labels.append(label) return texts, labels这段代码里errorsignore是个后悔药级别的参数——没有它一个坏文件就能让整个训练脚本挂掉。len(cleaned) 10这个阈值可以根据你的数据调新闻正文一般不会短于 10 个字短于这个长度的基本是导航栏或者广告残留。2.2 jieba 分词与停用词表的取舍中文和英文不一样英文按空格切就行中文得先分词。jieba 是最常用的选择安装就是pip install jieba。分词模式有三种精确模式、全模式、搜索引擎模式。新闻分类我一般用精确模式因为全模式会把北京大学切成北京大学北京大学引入大量冗余特征。停用词表要不要用要但别用网上那种几万词的通用表。新闻文本里的了是这些确实没用但不没这种否定词删了会出事——不及预期和及预期是两个意思。我的做法是先用通用停用词表跑一版看特征权重如果发现某些被删的词其实有区分度再手动加回来。import jieba # 加载停用词表一行一个词 def load_stopwords(path): with open(path, r, encodingutf-8) as f: return set(line.strip() for line in f if line.strip()) STOPWORDS load_stopwords(stopwords.txt) def tokenize(text): words jieba.lcut(text) # 精确模式 # 过滤停用词、单字、纯数字 words [w for w in words if w not in STOPWORDS and len(w) 1 and not w.isdigit()] return wordslen(w) 1过滤单字是因为中文单字歧义太大行可以是银行也可以是行走。not w.isdigit()过滤纯数字但注意3.5这种带小数点的不会被isdigit()捕获会保留下来这正好是我们要的。分词之后每条新闻就变成了一个词列表接下来要把它变成向量。3. 特征工程TF-IDF 和词袋模型怎么选、参数怎么调3.1 从词袋到 TF-IDF 的直觉理解词袋模型Bag of Words是最简单的向量化方式统计每个词在文档里出现几次组成一个高维稀疏向量。问题是的出现 50 次和涨停出现 5 次词袋模型会觉得的更重要——这显然不对。TF-IDF 就是来解决这个的TF 是词频IDF 是逆文档频率一个词在越少的文档里出现IDF 越高说明它越有区分度。公式不复杂TF-IDF TF × IDF其中 IDF log(总文档数 / 包含该词的文档数)。直观理解就是一个词在当前文章里出现得多TF 高同时在别的文章里出现得少IDF 高它就越可能是这篇文章的关键词。from sklearn.feature_extraction.text import TfidfVectorizer # 注意tokenizer 接收的是原始字符串返回词列表 vectorizer TfidfVectorizer( tokenizerlambda x: tokenize(x), # 用上面的 jieba 分词 token_patternNone, # 因为用了自定义 tokenizer关掉默认正则 max_features5000, # 只保留权重最高的 5000 个词 ngram_range(1, 2), # 考虑单字词和双字词组合 min_df2, # 至少在 2 篇文档里出现才保留 max_df0.9, # 在超过 90% 文档里出现的词丢掉 sublinear_tfTrue # 用 1log(tf) 代替原始 tf抑制高频词 ) X vectorizer.fit_transform(texts) print(X.shape) # (样本数, 特征数)max_features5000是个经验值数据量小可以调到 2000数据量大可以到 20000。ngram_range(1, 2)让模型能捕捉不 上涨这种二元组合对情感和立场分类有帮助但特征数会膨胀所以要配合max_features用。min_df2过滤只出现一次的词这些词多半是噪声或者拼写错误。sublinear_tfTrue是我强烈建议开的它把词频取对数防止某个词重复太多次主导权重。3.2 参数调优的实操顺序调参不要一上来就网格搜索先按影响从大到小逐个调。我的顺序是先定max_features看验证集准确率随特征数变化的曲线找到收益递减的拐点再调ngram_range如果 1-gram 已经不错2-gram 提升有限就别开省内存最后调min_df和max_df这两个对噪声敏感的数据集影响明显。参数作用常用范围调大后果max_features限制特征维度2000~20000太大过拟合太小欠拟合ngram_range词组合范围(1,1)~(1,3)维度爆炸训练变慢min_df过滤低频词1~5太大丢信息太小留噪声max_df过滤高频词0.8~0.95太小丢关键停用词sublinear_tf词频对数化True/False一般开 True一个容易忽略的点TfidfVectorizer的fit只能在训练集上做然后用同一个 vectorizer 去transform测试集。如果对全量数据fit_transform测试集的词表信息就泄漏到训练里了准确率会虚高上线就翻车。4. 模型训练与评估从朴素贝叶斯到线性 SVM 的选型对比4.1 为什么文本分类首选线性模型新闻文本分类有个特点特征维度高几千到几万样本量中等几千到几十万而且很多类别是线性可分的。这种情况下线性模型往往比深度学习效果不差速度还快几个数量级。我一般先跑三个 baseline朴素贝叶斯MultinomialNB、线性 SVMLinearSVC、逻辑回归LogisticRegression。朴素贝叶斯假设特征独立这个假设在文本里明显不成立词和词有关联但它速度快、对小数据友好经常作为第一版 baseline。线性 SVM 在文本分类上长期是强 baseline尤其是LinearSVC它找的是最大间隔超平面泛化能力好。逻辑回归输出概率方便做阈值调整和多分类的置信度排序。from sklearn.naive_bayes import MultinomialNB from sklearn.svm import LinearSVC from sklearn.linear_model import LogisticRegression from sklearn.model_selection import train_test_split from sklearn.metrics import classification_report # 划分训练集和测试集stratify 保证类别比例一致 X_train, X_test, y_train, y_test train_test_split( X, labels, test_size0.2, random_state42, stratifylabels ) models { NB: MultinomialNB(alpha0.1), SVM: LinearSVC(C1.0, max_iter2000), LR: LogisticRegression(C1.0, max_iter1000) } for name, clf in models.items(): clf.fit(X_train, y_train) y_pred clf.predict(X_test) print(f {name} ) print(classification_report(y_test, y_pred))MultinomialNB的alpha是平滑参数默认 1.0文本分类我一般调到 0.1 甚至 0.01因为默认值对稀疏文本平滑过头了。LinearSVC的C是正则强度C 越大越容易过拟合文本分类常用 0.5~2.0。max_iter2000是防止不收敛报警告如果数据量大还得往上加。4.2 评估指标不能只看准确率新闻分类经常遇到类别不平衡——体育新闻可能是财经新闻的 5 倍。这时候准确率会骗人全猜体育也有 60% 准确率。必须看每个类别的 precision、recall、f1-score。classification_report会输出这些重点看 macro avg 和 weighted avg 的差距差距大说明小类别被牺牲了。如果某个类别 recall 特别低先别急着换模型回去看数据是不是这个类别的样本太少是不是它的文本和其他类别高度重叠比如科技财经这种交叉领域我遇到过一次娱乐和体育分类效果差最后发现是数据里有一批体育明星参加综艺的新闻两边都像这种边界样本要么人工重新标要么单独设一个类别。from sklearn.metrics import confusion_matrix import numpy as np # 看混淆矩阵定位到底哪两类在互相误判 cm confusion_matrix(y_test, y_pred, labelsclf.classes_) print(类别顺序:, clf.classes_) print(cm) # 找出误判最多的类别对 for i in range(len(clf.classes_)): for j in range(len(clf.classes_)): if i ! j and cm[i][j] 0: print(f{clf.classes_[i]} 被误判为 {clf.classes_[j]}: {cm[i][j]} 次)混淆矩阵是排查分类问题的黑匣子。看到具体哪两类在混你就能有针对性地补数据或者加特征。比如财经和科技混可以加一些领域词典特征体育和娱乐混可能得靠实体识别来区分。5. 避坑与排查新闻文本分类系统最常见的 5 个翻车现场5.1 现象训练准确率 99%测试准确率 60%原因数据泄漏。最常见的是在划分训练测试集之前就做了 TF-IDF 的fit或者用全量数据算了词表。另一个隐蔽原因是数据里有重复样本同一条新闻既在训练集又在测试集。解决严格按train_test_split之后再fitvectorizer训练前用drop_duplicates去重如果数据是按时间采集的按时间切分而不是随机切分避免未来信息泄漏。5.2 现象模型训练报错 empty vocabulary原因分词后所有词都被停用词表或min_df过滤掉了。常见于短文本数据集或者停用词表加载错了比如把整个文件当成一个词。解决检查tokenize函数的输出打印前几条看看是不是空列表临时把min_df1、停用词表设为空跑一遍确认是哪个环节过滤太狠。5.3 现象预测新文本时结果全是同一个类别原因新文本的词汇和训练集分布差异大TF-IDF 向量几乎全零模型只能输出先验概率最高的类别。也可能是max_features太小把新文本里的关键词都过滤了。解决用vectorizer.transform后检查非零元素个数如果接近 0 说明词汇不匹配考虑用 char-level n-gram 作为补充特征或者换用词向量Word2Vec、FastText来缓解词汇表外问题。5.4 现象jieba 分词把关键实体切碎了原因jieba 默认词典不含你的领域专有名词比如固态电池被切成固态电池元宇宙被切成元宇宙。解决用jieba.add_word(固态电池)动态加词或者加载自定义词典jieba.load_userdict(dict.txt)。领域词典可以从训练数据里用 TF-IDF 高分词自动挖掘再人工筛一遍。5.5 现象模型文件保存后再加载预测结果不一致原因只保存了模型如pickle.dump(clf)没保存 vectorizer。加载后用了新的 vectorizer特征空间对不上。解决把 vectorizer 和分类器打包成一个 pipeline 一起保存或者分别保存、加载时确保用的是同一个 vectorizer 实例。import joblib from sklearn.pipeline import Pipeline # 把向量化和分类器串成 pipeline一起保存 pipeline Pipeline([ (tfidf, TfidfVectorizer(tokenizertokenize, token_patternNone, max_features5000, sublinear_tfTrue)), (clf, LinearSVC(C1.0)) ]) pipeline.fit(texts_train, y_train) joblib.dump(pipeline, news_clf.pkl) # 加载后直接预测原始文本不用手动 transform loaded joblib.load(news_clf.pkl) print(loaded.predict([央行今日宣布降准 0.5 个百分点]))用 pipeline 是省心的做法它把预处理和模型绑定避免忘了 transform或用错 vectorizer这类低级错误。joblib比pickle更适合保存含 numpy 数组的 sklearn 对象速度快、体积小。6. 进阶技巧用 char-level 特征和阈值调优把 F1 再拉一截当词级 TF-IDF 已经调到瓶颈我一般会试两个方向。第一个是 char-level n-gram中文里很多词是构词规律的电池电视电脑共享电字char-level 能捕捉这种模式对新词和错别字也更鲁棒。做法很简单把TfidfVectorizer的analyzer设成charngram_range设成(2, 4)然后和词级特征做拼接。from scipy.sparse import hstack # 词级特征 word_vec TfidfVectorizer(tokenizertokenize, token_patternNone, max_features5000, sublinear_tfTrue) X_word word_vec.fit_transform(texts_train) # 字符级特征 char_vec TfidfVectorizer(analyzerchar, ngram_range(2, 4), max_features8000, sublinear_tfTrue) X_char char_vec.fit_transform(texts_train) # 水平拼接得到更丰富的特征表示 X_combined hstack([X_word, X_char]).tocsr()拼接后特征维度到 13000 左右训练会慢一些但 F1 通常能涨 2~5 个点。注意测试集也要用同样的两个 vectorizer 分别 transform 再 hstack不能重新 fit。第二个方向是阈值调优。LinearSVC输出的是决策值不是概率但可以用CalibratedClassifierCV包一层拿到概率然后对每个类别设不同的判定阈值。比如财经类 precision 低就把阈值调高宁可漏判也不错判。这个在业务上很实用——不同类别对错误的容忍度不一样。from sklearn.calibration import CalibratedClassifierCV # 把 LinearSVC 包装成输出概率的分类器 calibrated CalibratedClassifierCV(LinearSVC(C1.0), cv3, methodsigmoid) calibrated.fit(X_combined, y_train) proba calibrated.predict_proba(X_test) # 对每个类别单独设阈值比如财经类要求 0.7 以上才判 thresholds {财经: 0.7, 体育: 0.5, 科技: 0.5, 娱乐: 0.5}这套组合拳打下来一个中等规模的新闻分类任务几万条、四五个类别F1 做到 0.85 以上是现实的。再往上走就得上预训练模型了但那是另一个量级的资源投入先用这套把 baseline 立住再决定要不要升级。我自己踩过最深的坑是数据泄漏——当时测试集准确率 0.95上线后掉到 0.6排查了两天才发现是去重没做干净。从那以后我养成了一个习惯任何文本分类任务第一步永远是drop_duplicates加打印类别分布第二步才是分词和向量化。这个顺序看起来慢其实是最快的。希望帮到你。本文还有配套的精品资源点击获取
返回列表