☰
基于Python的新闻标题分类毕业设计全流程解析
2026/9/28 19:08:06 网站建设 项目流程

简介:这是一份基于机器学习的新闻标题分类系统毕业设计源码与文档,出自本科大四高分项目(评审98.5分),面向计算机相关专业正在准备毕设的学生,以及需要项目练习的开发者,同样适用于课程设计和期末大作业。系统以Python为核心,完整覆盖数据预处理、特征工程、模型训练、Web端展示与结果分类等环节,能帮助读者建立端到端的项目认知。资源包共62个文件,大小约10.93MB,其中包含7个py核心代码、Jupyter Notebook分析文件、前端HTML/CSS/JS页面、SQL数据库脚本、Word与PDF文档说明,以及停用词表、敏感词表、语料数据等配套文件,结构划分清晰,便于按模块查阅和二次开发。目前平台已有147人浏览学习。除源码和文档外,还附带训练与测试语料、停用词表等辅助数据,读者可参照Notebook中的处理过程,快速复现新闻标题分类实验,或基于现有框架替换数据集、调整模型,适合作为毕业设计参考和项目实战练手材料。

1. 新闻标题分类这个毕设题:到底在做什么,凭什么拿高分

新闻标题分类是典型的短文本分类任务:输入一行标题,输出它属于哪个栏目,比如财经、体育、娱乐、科技。作为 Python 本科毕业设计,它比图像识别类题目更容易在本地跑通,比纯算法研究更好展示完整工程链路——从数据清洗、中文分词、特征工程,到模型训练、评估和结果展示,每个环节都能讲清楚,工作量饱满又不至于失控。先说一个反直觉的结论:这个题目能不能拿高分,大概率不取决于你用多新的模型,而取决于你的数据切分是否严谨、F1 是不是真实、答辩时能不能把特征工程的原理讲明白。如果你正对着选题列表发愁,或者已经拿到类似方向的源码但不知道怎么改成自己的,这篇就按一线实操的顺序把整条路拆开讲。

2. 选对技术路线:TF-IDF 加线性分类器为什么是本科毕设的最优选

2.1 短文本分类的完整流程:先看清这条链上的每一个环节

标题分类本质上是在做文本到类别的映射。整个过程可以被拆成五段:数据采集与标注、数据清洗、分词与特征提取、模型训练、评估与展示。这也是标准的机器学习应用流程,答辩时老师通常会顺着这条链问下去,所以每一步都要能讲出“为什么这么做”。

先说数据。公开可用的中文新闻类数据集中,THUCNews 的抽样子集在高校教学里用得很多,常见做法是取其中 10 类或 5 类各若干条,构成一个规模几千到两万条的平衡数据集。用它训练传统机器学习模型完全够,训练时间在普通 CPU 上也就是几秒到几分钟。这里有一个容易被忽略的点:标题和正文不一样,标题短,单条不超过几十个字,信息高度浓缩,几乎没有任何上下文可借。所以特征工程的重心不是“提取更多上下文”,而是“把离散的关键词变成模型能吃的向量,并让不同类别的关键词分布差异被放大”。

数据清洗的活儿看着不起眼,实际上最影响结果。新闻标题里常见的问题包括:全角半角混用、括号和引号残留、空格和制表符、URL 链接、无意义的“原标题:”“组图:”这类前缀。清洗规则写一条是一条,每条规则都能在后面的消融实验里体现价值,这也是论文里可以写数据的地方。

分词和向量化是这个题目的灵魂。中文不像英文天然用空格分词,所以 jieba 分词几乎是标配。分词之后要做的就是把词转成向量,TF-IDF 是这里最稳的方案,后面章节会展开。模型选型则在朴素贝叶斯、SVM、逻辑回归三者里做权衡,下面单独说。

2.2 为什么不直接上 BERT 和深度学习:数据量、可解释性与答辩风险

很多同学一上来就问“能不能直接 Fine-tune BERT”。能,但在本科毕设这个场景里我不建议把它当主方案。原因有四条,每一条都对应实际的成本。

第一是数据量。标题分类数据集通常在几千到两万条。BERT 这类预训练模型在这个量级上微调很容易过拟合,需要做更精细的学习率设置和早停,一不注意验证集分数好看,测试集立刻打回原形。第二是可解释性。TF-IDF 的每一维特征对应一个词,逻辑回归或线性 SVM 的权重可以直接反查:哪些词把一条标题推向“体育”类,哪些词推向“财经”类。这件事在论文里可以写一节,答辩时也可以现场演示,而深度学习在这块基本是黑匣子。第三是环境成本。本科毕设的机器不一定有 GPU,传统机器学习在 CPU 上几分钟跑完全部实验,BERT 微调没有 NVIDIA 显卡会很痛苦,时间成本也高。第四是文档写作。机器学习经典算法的数学推导,比如朴素贝叶斯的条件独立假设、SVM 的间隔最大化,参考资料多,本科生写起来有底气,不容易被答辩组深挖到答不上来。

这不是说深度学习不能用。如果数据量大、机器有 GPU,完全可以把它作为论文里的“进阶对比实验”出现,在展望里提一句“引入预训练模型可进一步提升分类精度”,但主链路老老实实走 TF-IDF 加经典分类器。我带的这类题目基本都是这个思路,既能保证跑通,又能保证有东西可写。

2.3 朴素贝叶斯、SVM、逻辑回归的选型对比:三个模型的适用边界

三个经典模型各有脾气,选哪个不是拍脑袋,而是看特征空间的样子。文本经过 TF-IDF 转出来之后,是一个高维稀疏矩阵:维度经常上到几万,但每条标题非零元素很少。这个特点直接决定了模型选择。

朴素贝叶斯(MultinomialNB)训练极快,对小样本和离散特征都友好,很适合做基线模型。但它的条件独立假设在文本上其实站不住脚,因为词与词之间明显有共现关系,比如“房价”“调控”总一起出现。假设归假设,它在分类任务上依然能打,做个 baseline 完全合格。

LinearSVC 是我在这个场景下的默认主力。SVM 对高维稀疏数据有天然优势,线性核在文本分类上比 RBF 核更稳,原因是标题分类决策边界在特征空间里基本是线性的,引入核变换只会增加过拟合风险。LinearSVC 本身支持多分类,训练开销可控,调参压力小。注意用的是一对多(one-vs-rest)策略,本身就符合新闻栏目的多分类设定。

逻辑回归(LogisticRegression)是另一个好选择。它的优势在于有概率输出,predict_proba 可以直接拿来展示每条预测的置信度,界面演示时非常直观。权重也可以解释为“这个词把标题往哪个方向推”。LR 的 L2 正则化参数 C 值得调,后面调参部分会讲。

模型适用场景优势主要风险
MultinomialNB基线对比训练快、参数少独立性假设与文本特征不符
LinearSVC主模型高维稀疏特征友好、多分类支持好无概率输出,需额外转换
LogisticRegression主模型/演示有概率输出、权重可解释特征维度高时训练略慢

所以我的建议是:MultinomialNB 做基线,LinearSVC 或 LogisticRegression 二选一做主模型。这样论文里天然多了一组对比实验,结构也完整。

3. 把数据处理和特征工程做扎实:决定分类系统上限的工序

3.1 数据集的组织与切分:类别均衡、分层抽样、随机种子

动手写模型之前,先把数据目录组织好。我一般会用这种结构:一个 data 目录放原始 CSV,一个 preprocess.py 放清洗和分词函数,一个 train.py 放训练脚本,结果输出到 results 目录。如果你还在装环境阶段,先花半天把 VSCode 的 Python 环境配好,装好 jieba、pandas、scikit-learn、matplotlib 这几个包再往下走。

读取和切分的代码是整个项目的地基,写的时候有几个参数直接影响后续所有实验的可信度。

import pandas as pd from sklearn.model_selection import train_test_split # 假设原始文件有两列:label 是栏目类别,title 是新闻标题 df = pd.read_csv("data/train.csv", encoding="utf-8") print(df.shape) print(df["label"].value_counts()) # 按类别分层切分,保证训练集和测试集的类别比例一致 X = df["title"].tolist() y = df["label"].tolist() X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42, stratify=y )

这段代码里最关键的是三个参数。test_size=0.2 表示拿 20% 的数据做测试集,这个比例是常见默认值,数据量只有几千条时可以适当减到 0.15,避免测试集太小导致评估波动大。random_state=42 是随机种子,固定它才能保证每次运行切分结果一致,答辩时你论文里的数字才能随时被复现。stratify=y 是分层抽样,保证切分后训练集和测试集各自的类别比例和原始数据一致,这一步在类别不平衡时尤其重要,不做的话很可能某个小类在测试集里只有几条,算出来的 F1 波动极大。

读入之后先打印类别分布,这是判断数据集是否平衡的第一步。如果发现某一类占比特别高,比如娱乐类占 60%,后面所有评估都要以 macro-F1 为准,不能盯着 accuracy。另外提醒一句,CSV 文件统一用 UTF-8 编码保存,Windows 上用记事本另存时容易默认存成 GBK,后面读取会乱码或直接报错。

3.2 jieba 分词与自定义词典:三个直接影响精度的参数

分词这一步基本没有替代方案,jieba 是中文分词的事实标准。它有精确模式、全模式和搜索引擎模式,默认的精确模式最适合短文本分类。全模式会把句子切成所有可能的词,产生大量冗余,搜索引擎模式更偏向长句子的召回,用在标题上都不合适。

import jieba # 精确模式分词,默认即精确模式 words = jieba.lcut("恒大客场击败国安,提前锁定小组第一") print(words) # 输出类似:['恒大', '客场', '击败', '国安', '提前', '锁定', '小组第一'] # 加载自定义词典,解决新闻专有名词被切碎的问题 jieba.load_userdict("data/userdict.txt") # 对单个词的切分做微调,建议写作时记录在预处理脚本里 jieba.suggest_freq("小组第一", True)

自定义词典的格式是三列:词、词频、词性,每行一个词,例如“梅西 1000 nz”“英雄联盟 500 n”。词频是参考值,不要求精确,给个大概值即可。我做过的一个体育类标题数据集,默认分词会把“梅西”切成一长串莫名其妙的音节,加载了 200 多个球员和球队名之后,分类效果立刻提升。

分词之后必须接停用词过滤。常见做法是准备一个停用词表,把“的、了、是、在、和”这类虚词以及标点符号都过滤掉。注意停用词表不要做得太长,否则会误伤有分类价值的词。比如标题里出现“为什么”,这个词看起来像套话,但在科技类标题里它可能反复出现,反而是一个弱特征,过滤前最好先用下面的代码统计一下词频分布。

def tokenize(title: str) -> list: words = jieba.lcut(title.strip()) # 过滤停用词、纯符号和单字词 return [w for w in words if w not in STOPWORDS and len(w) > 1 and w.strip()]

这里有三个直接决定效果的参数:stopwords 集合、最小词长阈值 len(w) > 1、是否保留单字。最小词长阈值过滤掉“的、了、是”之外的碎片,但像“房”“股”这类在财经标题里高频出现的单字,如果一刀切过滤会有损失,所以阈值本身值得做一个对比实验写进论文。

3.3 TF-IDF 向量化的参数语义:ngram、min_df、max_df 到底在管什么

TF-IDF 把分词结果转成向量,这一小节是整个系统特征工程的核心。落在 sklearn 的 TfidfVectorizer 上,关键是理解它的参数,而不是只抄代码。

TF 是词频,表示词在标题里出现的次数。IDF 是逆文档频率,衡量这个词在整个语料里的稀缺程度。“恒大”如果是体育类标题的高频词,但全语料里并不常见,它的 IDF 就高,分类价值就大。而“一个”这种所有类别都在用的词,IDF 接近 0,基本是废特征。

from sklearn.feature_extraction.text import TfidfVectorizer vectorizer = TfidfVectorizer( tokenizer=tokenize, # 使用上面定义的 jieba 分词函数 ngram_range=(1, 2), # 保留单词和相邻双词 min_df=2, # 至少在 2 条标题中出现过才保留 max_df=0.8, # 出现在 80% 以上标题中的词视为噪声 sublinear_tf=True, # 用 1 + log(tf) 平滑词频 norm="l2", # 默认值,按向量长度归一化 )

几个参数逐个说。ngram_range=(1, 2) 同时保留“房价”和“房价 调控”这种相邻词组合,可以捕捉词组语境。要不要升到 (1, 3)?标题太短,三元组过于稀疏,而且会让特征维度爆炸,不建议。min_df=2 过滤掉只出现一次的词,这种词基本是噪声,留着只会让模型记住单条样本的偶然信息,是防止过拟合最有效的后手。max_df=0.8 过滤掉“揭秘”“现场”这类新闻标题套话,它们看似高频,其实在各类里均匀分布,IDF 很低。sublinear_tf=True 用对数平滑原始词频,避免长标题因为某个词重复出现而拿到过高权重。

参数设置不是死的,答辩的时候你只要能说出“min_df 从 1 调到 2,宏 F1 从多少涨到多少”,这就是一个有说服力的实验。所以这部分我建议你后期至少跑一组 min_df 和 ngram_range 的对照,把数字留在论文里。

4. 训练与评估:用 sklearn 跑通最小可复现的标题分类器

4.1 最小训练脚本:用 Pipeline 串起向量化和分类器

进入训练环节之前先统一认识:训练时避免数据泄露是第一优先级。数据泄露的意思是测试集的信息被模型看见了,最常见的情形就是先对整个数据集做 TfidfVectorizer 的 fit,再切分训练集测试集。这样词表和 IDF 统计都来自全量数据,测试集的分布信息提前混进训练过程,验证分数虚高,换新数据立刻露馅。

解决办法就是用一个 Pipeline 把向量化和模型串起来。Pipeline 的规则是:fit 时训练集逐级传递,测试集只走 transform,永远不参与 fit。

import pandas as pd from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.svm import LinearSVC from sklearn.pipeline import Pipeline from sklearn.model_selection import train_test_split from sklearn.metrics import classification_report from preprocess import load_data, tokenize df = pd.read_csv("data/train.csv", encoding="utf-8") X_train, X_test, y_train, y_test = train_test_split( df["title"], df["label"], test_size=0.2, random_state=42, stratify=df["label"] ) pipeline = Pipeline([ ("tfidf", TfidfVectorizer( tokenizer=tokenize, ngram_range=(1, 2), min_df=2, max_df=0.8, sublinear_tf=True )), ("clf", LinearSVC(C=1.0, class_weight="balanced", random_state=42)), ]) pipeline.fit(X_train, y_train) print(classification_report(y_test, pipeline.predict(X_test), digits=4))

这里有两个参数值得展开。LinearSVC 的 C 表示正则化强度的倒数,C 越小对误分类的惩罚越轻,模型越平滑;C 越大模型越努力拟合训练集,容易过拟合。class_weight="balanced" 会根据类别的样本数自动调整权重,样本少的那一类在损失函数里拿到更高的权重,这在类别不平衡时比不做处理明显更稳。random_state=42 固定住模型内部的随机性,不然每次训练结果有细微差别,论文里没法复现。

跑完之后看 classification_report。如果宏平均 F1 能到 85% 以上,说明流程基本没大问题。如果只有 60% 上下,先回去检查分词和清洗,而不是急着换模型。

4.2 评估指标的读法:accuracy 虚高、macro-F1 与混淆矩阵

标题分类系统最容易让新手产生错觉的就是准确率。如果数据里体育类占 60%,一个把所有标题都预测成体育类的傻子模型,准确率也有 60%。所以在论文里不要只报 accuracy,分类报告里每一栏都有意义。

precision 是“预测为该类的样本中,有多少真预测对了”,recall 是“真实的该类样本中,有多少被找回来了”。F1 是两者的调和平均。对类别不平衡的数据,要看 macro-F1 和 weighted-F1:macro-F1 是每个类的 F1 取算术平均,等权看待所有类别;weighted-F1 按每个类的样本占比加权,更贴近整体表现。答辩时你主动说出这两个指标的区别,比被问到时支支吾吾强得多。

再看混淆矩阵,它可以告诉你哪些类别互相打架。画它只需要几行代码:

import matplotlib.pyplot as plt import seaborn as sns from sklearn.metrics import confusion_matrix plt.rcParams["font.sans-serif"] = ["SimHei"] plt.rcParams["axes.unicode_minus"] = False y_pred = pipeline.predict(X_test) cm = confusion_matrix(y_test, y_pred, labels=pipeline.classes_) sns.heatmap(cm, annot=True, fmt="d", xticklabels=pipeline.classes_, yticklabels=pipeline.classes_) plt.xlabel("Predicted") plt.ylabel("True") plt.savefig("results/confusion_matrix.png", dpi=300, bbox_inches="tight")

读混淆矩阵有一个固定姿势:先看对角线,对角线数值占总样本比例越高越好;再看最大的非对角线格子,那就是模型最容易搞混的类别对。以我的经验,娱乐和体育最容易互相混,因为两边都频繁出现人名;财经和房产也容易混,因为关键词有大量重叠。这个发现本身就可以写进论文的误差分析一节。

4.3 交叉验证与网格调参:GridSearchCV 的实用参数组合

参数怎么调,决定你论文里那张实验结果表有没有说服力。网格搜索配合交叉验证是本科阶段最稳妥的调参方式。注意一个原则:网格搜索只应在训练集上做,测试集必须留到最后,否则你就是在用测试集调参,评估结果必然虚高。

from sklearn.model_selection import GridSearchCV param_grid = { "tfidf__ngram_range": [(1, 1), (1, 2)], "tfidf__min_df": [1, 2, 3], "clf__C": [0.1, 1.0, 10], } grid = GridSearchCV( pipeline, param_grid, cv=5, scoring="f1_macro", n_jobs=-1, verbose=1 ) grid.fit(X_train, y_train) print("best params:", grid.best_params_) print("best cv score:", grid.best_score_)

这里的参数空间是 2×3×3 共 18 个组合,五折交叉验证意味着要做 90 次训练。标题分类数据量不大,这个规模在 CPU 上完全能接受。scoring="f1_macro" 比默认的 accuracy 更合理,原因上面已经说过。n_jobs=-1 表示用满所有 CPU 核心,verbose=1 会打印进度,跑的时候能知道还剩多少。

网格跑完后,用 grid.best_estimator_ 在真正的测试集上重新评估一次,这个分数才是论文里该写的。一个容易漏的步骤是:调参过程中如果发现 best_params 里 ngram_range 是 (1, 2) 而 min_df 是 2,就把这组参数写死进 train.py,避免之后不小心改参数重跑,得到和论文不符的数字。

5. 毕设避坑指南:从数据泄露到答辩翻车的六个真实教训

5.1 数据泄露:验证集分数虚高,一上测试集就打回原形

现象:交叉验证分数很高,模型换一批数据测试时效果骤降,或者测试集上重跑的结果和论文里记录的数字对不上。原因:写代码时先对整个语料调用了 fit_transform,然后才做 train_test_split,词的 IDF 和词表信息把测试集泄露进了训练过程。解决:把向量化和模型全装进 Pipeline,只在训练集上 fit,测试集只走 transform。一句话原则:凡是需要看全局统计量的操作,都必须先在训练集内部完成。

5.2 类别不平衡导致 accuracy 虚高:85% 的准确率掩盖了一类全错

现象:训练后打印准确率 85%,一看分类报告,占比最大的类 F1 有 95%,有个小类召回率只有 20%。原因:多数类样本占比过高,模型只要把多数类猜对就有高准确率,小类的损失被淹没。解决:评估时以 macro-F1 为准,模型里加 class_weight="balanced",数据层面可以对少数类做简单的复制增强或欠采样,但绝不能动测试集。这条是我们实际调过的数据里最常见的现象,也是最容易拿高分的一个点,因为很多人的论文根本不提类别不平衡。

5.3 专有名词被切碎:“英雄联盟”变成“英雄/联盟”,特征彻底失效

现象:某个类别的标题里反复出现游戏名、球队名或者人名,但分类效果一直上不去,查看特征词时发现这些词根本不在重要的特征列表里。原因:jieba 默认词典以通用词为主,新闻专有名词覆盖率不足,“英雄联盟”被切成通用词“英雄”和“联盟”,在财经类里也可能出现“联盟”,于是特征被稀释。解决:收集高频专有名词做成自定义词典加载,格式是“词 词频 词性”三列;也可以用 jieba.suggest_freq 对单个词做切分优先级标记。建议从预测错误的样本里批量提取专有名词,迭代两轮就能覆盖大部分问题。

5.4 中文编码与绘图乱码:Windows 上读数据就翻车

现象:read_csv 报 UnicodeDecodeError,或者程序跑通但混淆矩阵图里中文全变方块。原因:数据文件是 GBK 编码保存的,pandas 默认按 UTF-8 解码失败;matplotlib 默认字体里没有中文字体映射。解决:读取时显式指定 encoding="utf-8",从 Windows 记事本复制出来的文件如果报错,优先用 encoding="gbk" 重读一遍确认原始编码;绘图前设置 rcParams 的 font.sans-serif 为 SimHei,并关闭负号转义。这类问题不解决,你会在环境配置上浪费一整天,别问我怎么知道的。

5.5 论文里的数字和代码结果对不上:答辩现场被一句追问卡住

现象:论文里写的准确率是 93%,现场演示或者老师要求重跑,实际结果只有 88%。原因:实验时无意中用了包含测试集的参数搜索,或者数据文件在论文写作之后被替换过,又或者代码里没有固定随机种子。解决:答辩前重跑一次完整脚本,把 classification_report 和混淆矩阵另存到 results 目录下的文本文件,论文里的全部数字以这次输出为准。模型代码里所有有随机性的地方都固定 random_state,并在论文的实验设置一节写清楚数据量、切分比例、随机种子这三个信息。

5.6 把训练好的模型直接丢进界面:预测代码与训练代码不一致

现象:训练时用 Pipeline 调好的流程,预测时手写了一段向量化代码,结果预测结果乱成一团。原因:预测阶段的预处理和训练阶段不一致,最常见的是停用词表不同、分词函数不同,或者向量化参数被改掉。解决:预测函数直接复用训练好的 Pipeline 对象,用 pickle 或 joblib 把 pipeline 保存下来,预测时加载同一个对象调用 predict。保存模型的代码就在训练脚本末尾加一行 joblib.dump(pipeline, "results/model.pkl"),预测脚本里 joblib.load 回来直接用,不要重新搭流程。

6. 让项目从“能跑”变成“高分”:文档组织、演示设计与进阶方向

这个题目拿高分的差距不在模型,在完整度。文档上,建议按“绪论、相关技术、需求分析、系统设计、系统实现、系统测试、总结”七章组织,其中系统设计必须画系统流程图和模块结构图,系统测试放分类报告和混淆矩阵两张图,加上一张“不同模型对比”的结果表。源码目录建议包含 data、preprocess.py、train.py、predict.py、requirements.txt、README.md,训练好的模型输出到 models 目录,实验报告输出到 results 目录。README 里写清楚运行环境、依赖安装命令和数据格式,这一个细节就能在查重和答辩印象分上拉开差距。

演示设计上,最低成本的做法是写一个 predict.py,接收一条命令行输入的标题,输出类别和置信度。如果用的是逻辑回归,predict_proba 可以直接给出概率;如果用的 LinearSVC,可以用 decision_function 的值做一次 softmax 转换,同样能得到一个 0 到 1 的置信度。

进阶方向上,如果时间和资源允许,可以补两组对比实验:一组是 Word2Vec 训练词向量后对标题词向量取平均再接分类器,另一组是微调一个轻量预训练模型。这两组实验的意义是证明你理解“传统特征工程”和“表示学习”两条路线的差异,而不是说明深度学习一定更好。数据集小的时候,预训练模型很容易过拟合,反而跑不过调好的 TF-IDF。我的习惯是先把自己调的 TF-IDF 链路讲透,再在展望里给深度学习留位置。被答辩老师追问过“Word2Vec 和 TF-IDF 的本质区别”之后,我就发现这个题的高分逻辑从来不是模型越新越好,而是每一步都有据可查、每个数字都能复现。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询