首页 / 资讯中心 / 文章详情

机器学习新闻标题分类:TF-IDF特征与Flask部署全流程解析

机器学习新闻标题分类:TF-IDF特征与Flask部署全流程解析 ★ FEATURED ARTICLE
简介一套面向人工智能、机器学习和深度学习方向的本科毕业设计资源主题是基于机器学习的新闻标题分类系统适合作为毕业设计或课程设计的完整参考。包内共63个文件涵盖Python源码7个py、前端页面16个html、13个css、8个js、数据与停用词表txt、word、sql、模型训练笔记ipynb及说明文档pdf、md、license压缩包整体约10.93MB结构清晰便于按模块查阅。已有88人学习内容包含从数据预处理、特征工程、模型训练到Web展示的完整流程附有敏感词表、停用词库、数据集划分与训练标签可帮助快速搭建新闻标题分类系统并理解机器学习项目落地细节。作者提供的是可直接运行的工程文件适合需要参考完整项目结构或进行二次开发的学生使用。1. 先说结论这是一套传统机器学习路线、不是深度学习的新闻标题分类系统拿到这份「基于机器学习的新闻标题分类系统」压缩包你先别急着找 BERT 或者 PyTorch 的痕迹——从文件清单看这是一套典型的传统机器学习落地项目文本预处理、TF-IDF 特征、分类器训练、Flask Web 展示、SQL 持久化整条链路是完整的。它不追求业界最新的深度学习精度而是把「机器学习应用流程」从头到尾走了一遍这对本科毕设、课程设计、或者刚入门机器学习想找一个能跑通全流程的参考项目来说价值比花架子大得多。你解压之后不会面对一堆没头没尾的 ipynb而是能看到数据怎么从 txt 变成模型、模型怎么被塞进 Web 接口、训练集测试集和停用词表分别放在哪儿。接下来我会按「文件结构 → 数据流 → 特征与模型 → Web 层 → 踩坑 → 评估」这条线把它拆给你看。2. 拆开 zip从文件清单反推这个系统的技术栈与数据流2.1 核心文件职责谁是入口、谁在跑数据、谁在渲染页面解压之后别急着跑main.py先把文件按职责分组能省掉很多瞎试的时间。这份压缩包里的文件不是随意堆放的它基本遵循了一个「数据处理 训练 Web 展示」的三层结构我第一遍看的时候用下面这张表把它们归了类。职责层文件说明项目入口main.py启动 Flask 服务或训练流程的入口脚本应用包app/__init__.py、view.py、tables.pyview.py管路由和请求处理tables.py管数据库表结构数据处理preprocess.ipynb、filter.py、pipeline.py预处理探索、过滤逻辑、训练/预测流程编排前端渲染templates/、static/Jinja2 模板与静态资源CSS/JS配置config.py、requirements.txt路径、参数、依赖数据资源data/下train.txt、dev.txt、test.word、test_with_label.word、vocab.txt、id2tag.txt训练集、验证集、测试集、词表、标签映射停用词哈工大停用词表.txt、中文停用词表.txt、四川大学机器智能实验室停用词库.txt、sensitive_words.txt过滤无意义词与敏感词数据库Bachelor_Graduation.sql建表与初始数据的 SQL 脚本文档taskgline02.pdf、README.md、LICENSE任务说明/报告、使用说明、协议配置残留.idea/、Bachelor_Graduation.iml、modules.xmlPyCharm 工程配置可直接删除这里有个容易被忽略的点app/__init__.py存在说明app是一个 Python 包不是单个文件因此view.py里的路由需要通过蓝图或者from app import view的方式注册到 Flask 实例上。我一般会先看main.py里怎么初始化app再决定是python main.py还是flask run。另一个容易被忽略的是test.word和test_with_label.word的区别——前者是原始测试集没有标签用于最终预测后者是带标签版本用于评估模型这在后面第 6 章做评估时会用到现在先记着。2.2 数据主链路train.txt 怎么变成能训练模型的数据这个系统的数据流可以用一句话概括原始标题文本 → 清洗过滤 → 分词去停用词 → 特征向量化 → 训练分类器 → 序列化保存 → Web 端加载模型做预测 → 结果写回 SQL。preprocess.ipynb是这条链路的第一个环节它通常承担了探索性数据分析和预处理逻辑验证的工作。你打开 notebook 会发现核心操作基本是读数据、看类别分布、做清洗、分词、构建词表。filter.py是另一个关键文件它负责在训练之前把脏数据挡在外面。结合它和sensitive_words.txt的存在我判断这里的过滤至少包含三类空标题过滤、超长标题截断、敏感词过滤。常见做法是这样# filter.py 核心逻辑示例 import re def filter_title(title: str, sensitive_words: list None, max_len: int 50) - str: # 去除首尾空白和不可见字符 title title.strip() if not title: return None # 去掉URL和用户等噪音新闻标题里常见 title re.sub(rhttps?://\S|www\.\S, , title) title re.sub(r\w, , title) # 长度截断标题过长一般是拼接噪音 if len(title) max_len: title title[:max_len] # 敏感词过滤命中直接丢弃或打码 if sensitive_words: for w in sensitive_words: if w and w in title: return None return title这段逻辑说明白点前两个正则用来去掉 URL 和 提及是中文文本清洗的默认动作长度截断是为了防止标题后面带着来源后缀或冗余信息敏感词检查直接丢弃比打码更省事因为新闻标题分类的样本本来就不缺丢掉脏数据比留着让模型学歪更划算。参数上max_len50是中文新闻标题的经验阈值头条类标题一般不超过 30 字超过 50 字大概率混入了正文片段你可以根据自己的语料调整。sensitive_words对应项目里的sensitive_words.txt加载时注意按行读并去除换行符。分词环节看vocab.txt和停用词表的组合就能推断出流程先对清洗后的标题做 jieba 分词然后过滤停用词再把剩余词映射成 id 存入vocab.txt。这里有个训练细节vocab.txt的 id 映射必须保持训练时和预测时一致所以它被单独存放在data/目录下而不是每次运行时重建。你如果自己改数据重新训练第一步应该是检查vocab.txt是否需要重建否则id2tag.txt和vocab.txt对不上预测结果会变成一串乱码。3. 特征工程与模型训练机器学习分类核心环节的拆解3.1 文本向量化新闻标题为什么适合 TF-IDF 而不是词袋新闻标题是典型短文本平均长度在 15 到 30 字之间关键词密度高、噪声词少。这种场景下 TF-IDF 是比纯词袋Bag of Words更好的选择因为 TF-IDF 会给在少数类别中出现的高频词更高的权重自动压低「的、是、了」这类无区分度词的贡献。虽然停用词表已经在前期过滤了一部分但 TF-IDF 的 IDF 分量还能做二次降权这是一个细节但很关键的点。pipeline.py里大概率用的是TfidfVectorizer而不是CountVectorizer这是正确选择。这里给出一个标准的参数配置你改了之后也按这个套路来# pipeline.py 特征工程部分 from sklearn.feature_extraction.text import TfidfVectorizer vectorizer TfidfVectorizer( max_features5000, # 保留出现最多的5000个词控制维度 ngram_range(1, 2), # 加二元词组捕捉“中国/足球”这类组合 min_df2, # 至少在2个文档中出现过过滤低频噪音 max_df0.8, # 在80%以上的文档都出现则忽略过滤无区分度词 sublinear_tfTrue # 用 1log(tf) 平滑减少高频词压制 ) X_train vectorizer.fit_transform(train_titles)参数说明是重点逐条说。max_features5000对于几万条训练样本的新闻标题场景足够维度再高容易过拟合且训练变慢你有 10 万条以上数据可以放宽到 8000 或 10000。ngram_range(1, 2)对中文短文本的收益非常明显因为「中国足球」和「中国」「足球」单拎出来含义不同二元词组能保留部分局部语义。min_df2和max_df0.8是一对组合拳前者干掉只出现一次的人名、错别字后者干掉几乎每个标题都有的泛化词。sublinear_tfTrue是一个很少被新手用但很实用的参数它把原始词频取对数避免某个词在一个标题里重复出现时对结果产生过大的影响。3.2 分类器选型朴素贝叶斯、SVM 与逻辑回归的取舍这个项目叫「基于机器学习」而非「基于深度学习」所以分类器大概率是传统三件套之一。拿新闻标题分类来说朴素贝叶斯是 baseline 首选训练极快适合短文本对 TF-IDF 特征有天然适配SVMLinearSVC在小样本下泛化能力强精度通常比朴素贝叶斯高逻辑回归训练快、可解释性强输出概率方便做阈值调整。分类器训练速度精度表现可解释性适用场景MultinomialNB极快中等高特征对数概率短文本、大规模数据、baselineLinearSVC快高通常比 NB 高 1-3 个点中权重向量可排序中等样本量、追求精度LogisticRegression快高与 SVM 接近最高概率输出需要置信度、做阈值调优我自己的习惯是先跑 MultinomialNB 拿到 baseline再切到 LinearSVC 看精度提升如果 SVC 的收益不足 1.5 个点且训练时间翻了几倍就留在 NB 按时交差。毕设场景下差两个点以内优先选逻辑回归因为写论文时能画出「特征权重高低」的图表解释性素材多。从pipeline.py的文件命名推断这个项目把「特征构建」和「模型训练」放进了同一个 pipeline说明作者是走「先固定数据清洗再调向量化参数最后比较模型」的标准流程这是对的新手最容易犯的错误就是同时改清洗逻辑和模型参数最后翻车了都不知道是哪个环节引入的。3.3 训练闭环pipeline.py 里的训练、调参与序列化训练部分的代码我按最常见的 scikit-learn 流程来还原这个项目的pipeline.py结构上应该非常接近# pipeline.py 训练主流程 import joblib from sklearn.linear_model import LogisticRegression from sklearn.pipeline import Pipeline from sklearn.metrics import classification_report # 组装 pipeline向量化 分类器一体 model Pipeline([ (tfidf, TfidfVectorizer(max_features5000, ngram_range(1, 2), min_df2, max_df0.8, sublinear_tfTrue)), (clf, LogisticRegression(C1.0, solverliblinear, max_iter1000)) ]) # 训练并保存 model.fit(train_titles, train_labels) joblib.dump(model, models/news_clf.joblib) print(classification_report(dev_labels, model.predict(dev_titles)))这段代码的逻辑是用Pipeline把向量化和分类器绑在一起训练时自动完成「特征拟合 模型拟合」预测时自动完成「特征变换 模型预测」这个封装能避免「训练时用了某种预处理、预测时忘了用」的低级错误。joblib.dump把整个 pipeline 落盘后续 Web 端加载时只需要一个joblib.load就能拿回完整的向量化器和模型。参数方面LogisticRegression用了solverliblinear这是小数据集上的经验选择它处理 L2 正则化效率高C1.0是默认值如果分类报告显示过拟合训练精度 99%、验证精度 90%把 C 降到 0.5 或 0.3如果欠拟合则把 C 调大到 1.5。max_iter1000是为了防止 liblinear 在数据量较大时迭代次数不够导致不收敛警告。4. 把模型装进 WebFlask 路由、模板渲染与预测接口4.1 app 包结构init.py、view.py、templates 之间的分工模型训练完不是终点能让人在浏览器里输入标题、拿到分类结果才算完整落地。这个项目的 Web 层由app/__init__.py、view.py和templates/、static/组成。app/__init__.py是包的初始化文件作用是把 Flask 实例创建出来、加载配置、注册路由。我读这段代码时的预期是这样# app/__init__.py from flask import Flask from . import view def create_app(): app Flask(__name__) app.config.from_object(config) # 从 config.py 加载配置 view.init_routes(app) # 注册视图路由 return appview.py则负责把「浏览器请求」翻译成「模型调用」。它内部通常加载joblib.load的模型文件并定义若干路由。这里的关键是模型加载只做一次放在模块全局位置而不是每次请求都joblib.load——后者会让 Web 接口慢一个数量级这是新手最容易踩的坑。4.2 预测接口与前端交互一次请求的完整生命周期用户在前端输入一个新闻标题点击分类按钮后发生了什么我把它拆成四个步骤第一步浏览器 POST 请求到/classify路由携带title字段。第二步view.py里对应的函数接住请求把标题字符串传给 pipeline。第三步pipeline 自动完成「清洗 TF-IDF 变换 模型预测」返回类别标签和概率。第四步函数把结果渲染到templates/result.html用户看到分类结果。对应的view.py核心函数大概是这个模样# view.py 预测路由 import joblib from flask import request, render_template _model joblib.load(models/news_clf.joblib) # 模块加载只执行一次 def classify_route(): title request.form.get(title, ).strip() if not title: return render_template(error.html, message标题不能为空) # 预测标签和置信度 label_id _model.predict([title])[0] proba _model.predict_proba([title]).max() tag id2tag[str(label_id)] # 把id还原成中文类别名 return render_template(result.html, titletitle, tagtag, probaround(proba, 4))注意_model前面的下划线这是一个约定俗成的写法告诉后来者「这个变量是模块级单例别在函数里重新赋值」。id2tag对应的就是项目里的id2tag.txt加载后同样放在模块级。predict_proba是逻辑回归优于 SVM 的地方——你能拿到置信度论文里可以写「系统对每条预测结果输出置信度」这是一个亮点。SVM 虽然也能用decision_function拿到距离但对用户不友好。4.3 数据库落位Bachelor_Graduation.sql 与 tables.py 怎么配合Bachelor_Graduation.sql不是模型训练必需品它的作用是持久化历史预测记录。tables.py里定义的应该是与 SQL 文件对应的表结构常见设计是一张「预测记录表」字段大概包括id主键、title标题原文、label预测类别、confidence置信度、created_at时间戳。-- Bachelor_Graduation.sql 核心建表语句 CREATE TABLE prediction_log ( id INT NOT NULL AUTO_INCREMENT, title VARCHAR(200) NOT NULL COMMENT 新闻标题原文, label VARCHAR(50) NOT NULL COMMENT 预测类别, confidence DECIMAL(5,4) NOT NULL COMMENT 置信度, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;utf8mb4是 MySQL 存储中文的正确字符集如果你看到 SQL 文件里写的是utf8建议改成utf8mb4。AUTO_INCREMENT自增主键是标配。tables.py在 Python 层对应这张表大概率用了pymysql或者SQLAlchemy在view.py的预测函数末尾插入一条记录即可插入失败不影响主流程用 try-except 包住。5. 避坑记录中文编码、停用词、样本不均衡与路径问题的血泪经验5.1 现象读 train.txt 打印出来全是乱码原因训练数据文件不是 UTF-8 编码。Windows 环境下用记事本另存的 txt 默认是 GBK 或 GB2312Python 的open(..., encodingutf-8)一读就乱码或者直接抛UnicodeDecodeError。解决先确认文件真实编码再统一用 UTF-8 处理。我的习惯是打开文件后先读几个字节判断或者直接写一段兼容逻辑# 读取训练数据的稳妥姿势 for enc in (utf-8, gbk, gb18030): try: with open(data/train.txt, r, encodingenc) as f: lines f.readlines() break except UnicodeDecodeError: continuegb18030是 GBK 的超集能兼容更多生僻字。跑完这段之后建议立刻把文件统一转成 UTF-8 再存回去后续所有代码都只用 UTF-8不然每次跑训练都来一次编码猜测太折腾了。5.2 现象停用词表明明在项目里分词结果却没有过滤掉「的」「了」原因停用词表加载出来了但分词和去停用词的顺序错了或者加载停用词时没处理换行符。解决先分词再逐词过滤停用词同时确保停用词表加载时去掉了\n。最常见的错误是for word in stopwords:时每个词后面都带着换行导致匹配不上。我给你个正确姿势import jieba with open(哈工大停用词表.txt, r, encodingutf-8) as f: stopwords set(line.strip() for line in f) def tokenize_with_filter(text): return [w for w in jieba.cut(text) if w.strip() and w not in stopwords]line.strip()这行是灵魂不加它整个停用词表形同虚设。另外多个停用词表可以合并成一个大set项目里放了哈工大、川大和一份通用中文停用词表我一般会三合一去重能过滤得更干净。5.3 现象训练报告显示总体准确率 95%但某个类别召回率只有 20%原因新闻标题数据类别严重不均衡。比如「体育」类样本 2 万条「科技」类只有 800 条模型倾向于把边缘样本全判成多数类换来的高准确率是虚假繁荣。解决看分类报告时别只看accuracy逐类检查recall和f1-score。处理方式按优先级排列第一用class_weightbalanced让模型自动为少数类增加权重第二对训练集做随机欠采样或过采样第三如果少数类样本太少考虑收集更多数据而非强行上模型。# 逻辑回归自动处理样本不均衡 model LogisticRegression(C1.0, solverliblinear, class_weightbalanced)class_weightbalanced一个参数就能缓解大部分不均衡问题模型会按类别频率的反比重新加权损失函数。它解决不了 1:100 的极端不均衡但新闻标题分类场景下类别比例一般不会那么悬殊先试它准没错。5.4 现象训练好的模型在 notebook 里能加载Flask 里却报路径不存在原因训练时代码用的是相对路径models/news_clf.joblib但 Flask 启动时工作目录变了相对路径指向了别的位置。这类问题玄学得很重启后时而好时而坏本质是「当前工作目录」不可控。解决所有路径统一基于项目根目录拼接别依赖工作目录。config.py里的常量用os.path.dirname(__file__)往上回溯# config.py 路径处理 import os BASE_DIR os.path.dirname(os.path.abspath(__file__)) MODEL_PATH os.path.join(BASE_DIR, models, news_clf.joblib) DATA_DIR os.path.join(BASE_DIR, data)os.path.dirname(os.path.abspath(__file__))的含义是「当前文件所在目录的绝对路径」无论你从哪里启动main.py这个路径都不会变。从那以后我写项目都强制把所有资源路径全部写在config.py里代码里一个裸的相对路径都不留。5.5 现象Bachelor_Graduation.sql 导入 MySQL 报错中文变成问号原因SQL 文件的字符集和 MySQL 数据库连接字符集不一致通常发生在「服务器端是 UTF-8文件是 GBK」或「表创建时默认字符集不是 utf8mb4」。解决导入前先统一字符集。用命令行导入时显式声明字符集比在 Navicat 里点导入更可控mysql -u root -p --default-character-setutf8mb4 -e CREATE DATABASE IF NOT EXISTS graduation DEFAULT CHARACTER SET utf8mb4; mysql -u root -p --default-character-setutf8mb4 graduation Bachelor_Graduation.sql--default-character-setutf8mb4同时管住了连接和传输的编码。如果导入后还是有乱码检查 SQL 文件开头有没有SET NAMES utf8mb4;没有就手动加一行。这类问题优先级不高但毕设答辩现场数据库恢复不出来是会扣印象分的。6. 进阶用 dev.txt 与 test_with_label.word 做一次可信的模型评估6.1 正确划分评估集别让 accuracy 骗了你项目里同时提供了dev.txt和test_with_label.word这是两份不同的评估资源。dev.txt是开发验证集你在训练时可以反复拿它调参test_with_label.word是带标签的留出测试集应该只在最终评估时使用一次用多次就相当于“测试集被污染了”。这个习惯要在毕设阶段养成因为论文里的最终精度必须以test_with_label.word为准而不是 dev 上的结果。评估脚本可以这样组织# evaluate.py 最终评估 import joblib from sklearn.metrics import classification_report, confusion_matrix model joblib.load(models/news_clf.joblib) test_titles, test_labels load_data(data/test_with_label.word) pred_labels model.predict(test_titles) # 打印逐类指标 print(classification_report(test_labels, pred_labels, target_nameslist(id2tag.values()))) # 打印混淆矩阵 print(confusion_matrix(test_labels, pred_labels))classification_report逐类输出 precision、recall、f1-score比一个孤零零的 accuracy 真实得多。confusion_matrix能直观看到哪个类之间互相混淆——比如「娱乐」和「八卦」经常混说明这两个类本身语义边界模糊可以考虑合并或者补充特征。6.2 那个让我在答辩前翻车的召回率教训我拆过不少毕设项目自己也经历过类似场景训练时看 dev 集准确率 97%心想稳了结果拿真正的 test_with_label 一测某个少数类别的召回率只有三成不到。原因就是第五章说的样本不均衡但当时没做逐类分析只盯着总准确率。从那以后我每次都强制走一遍完整评估流程先看逐类召回率再看混淆矩阵最后才汇报总体精度。这个顺序不能反。这份资源和大多数中文 NLP 学习项目一样最大的价值不是模型的绝对精度而是一个能让你摸到「数据处理 → 特征 → 模型 → 部署 → 评估」全流程的参考系。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站