简介:这是一份基于深度学习的虚假新闻检测技术研究项目的完整源码包,主要面向本科毕业设计、课程设计以及希望实践文本分类的Python开发者。项目覆盖数据清洗、特征处理、RNN模型构建与训练、前端结果展示的完整链路,有助于理解虚假新闻自动识别系统的工程实现。压缩包为zip格式,共113个文件,包含Python核心训练与预测脚本、TypeScript与JSX编写的前端页面、JSON/LESS工程配置、训练好的HDF5模型权重、CSV训练与测试数据集以及环境依赖说明,整体约49.65MB。当前已有270人学习/下载。所有源码经过严格测试、开箱即用,模型文件与数据配套齐全,可直接加载预训练权重验证效果,也可自行重新训练以对比不同参数。目录按功能模块组织清晰,便于快速定位关键代码,适合直接用于答辩演示、二次开发或在此基础上扩展多算法对比实验。
1. 当毕业设计遇上虚假新闻检测:这个题目真正值钱的部分不在模型代码
网上一搜“基于深度学习的虚假新闻检测”,能翻出一堆项目源码,Python写的、PyTorch和TensorFlow的都有。但多数应届生第一次把源码跑通、看到训练集准确率95%时,心里其实没底——因为真正决定这个毕业设计能不能讲明白的,不是那个训练脚本,而是数据怎么洗、标签怎么定义、指标怎么报。虚假新闻检测本质上是一个文本二分类问题:把新闻按内容分成真实和虚假两类。难点不在“深度学习”四个字,而在新闻文本的脏数据、类别不均衡、以及“看着像真的其实是假的”这类语义陷阱。这篇文章把技术选型、数据预处理、模型训练、评估到最后的演示界面整条链路拆开讲,适合两类人——一类是拿这个题目做毕设、需要把源码改造成自己东西的学生;另一类是刚入门NLP、想用文本分类练手、又不想只跑MNIST的开发者。
2. 技术选型先于写代码:LSTM、CNN还是BERT,怎么选才省时间
2.1 先看清问题:虚假新闻检测是文本二分类,不是“新闻识别”
很多人一看“虚假新闻检测”这个名字,以为要先去搞懂新闻学、传播学,甚至要做舆情分析,方向一下就偏了。落地到深度学习项目里,这件事被简化成一个非常标准的监督学习任务:给定一段新闻正文,模型输出一个标签,0代表真实,1代表虚假。
这个简化是毕业设计能推进的前提。你不需要判断新闻的立场、不需要识别标题党、也不需要给出证据链——只要在给定的数据集上,把标签预测得足够准,项目就成立了。所以第一件事是打开源码包里的数据文件,确认标签字段是二分类还是三分类(有的数据集有mixed/混合类),这决定了模型最后一层是输出2个类别还是3个类别。我一般建议直接用二分类的数据集,理由很简单:三分类的mixed类在标注时就很有争议,模型学起来不稳定,答辩时也容易被追问“你如何定义mixed”,说不清楚。
2.2 三类模型各管一摊:CNN、LSTM与BERT的边界在哪里
深度学习做文本分类,主流就三条路:TextCNN、LSTM/BiLSTM、BERT。它们的差异不在“谁更高级”,而在“你手里有什么资源、要什么效果”。
TextCNN用卷积核在文本上滑动,本质是抓局部n-gram特征。它的优势是训练快、参数少,在短文本上表现不差,但新闻正文动辄几百上千词,跨句子的逻辑关系它抓不住。虚假新闻里最常见的特征恰恰是“前文说A,后文说B”的自相矛盾,这种长距离依赖是CNN的短板。
LSTM把文本当作带顺序的时间序列,通过门控机制记住前面的内容。双向LSTM(BiLSTM)再叠加一层反向扫描,让每个位置同时看到上文和下文,对跨句子的语义连贯性敏感很多。缺点是训练比CNN慢,但对毕业设计的数据量来说,这个差距可以忽略。
BERT走的是预训练加微调的路子,在超大规模语料上学过通用语义,再拿新闻数据微调。效果通常是三者里最好的,尤其在小数据集上优势明显,但显存开销和训练时间也是前两者的好几倍。CPU上跑BERT基本不现实,没有GPU的机器慎选。
我的建议很简单:CNN别作为主模型,LSTM作为主模型,BERT作为对比实验的上限参考。这个搭配既控制了工作量,又保证了论文里有“不同模型效果对比”的章节。
2.3 拿到源码.zip之后的第一件事:不看模型,先看数据
从网上下载的源码包,第一反应是打开train.py看网络结构,这是最容易犯的错误。正确的顺序是:先看README,再看数据,最后看模型。
源码包里通常有这么几个部分:data目录放数据集,models目录放网络定义,utils目录放预处理工具,train.py是训练入口,predict.py是推理入口。你需要确认三件事:数据是什么格式(CSV还是JSON)、标签字段叫什么、数据是否已经切分好训练集和测试集。很多源码包把切分逻辑写在train.py里,每次都随机切分,这会导致一个隐蔽问题——你改了代码里的某个参数后,训练集和测试集的分布跟着变了,实验结果对不上。
拿到CSV数据后,第一件事是用pandas看一下标签分布。虚假新闻数据集普遍不均衡,假新闻占比通常在25%到40%之间。这个数字直接决定了后面该用什么评估指标,也决定了你论文里“数据预处理”这一章有没有东西可写。
2.4 选型对比表与推荐路线
| 模型 | 特征捕捉能力 | 训练成本 | 效果上限 | 毕业设计适配度 |
|---|---|---|---|---|
| TextCNN | 局部n-gram,短文本友好 | 极低 | 中 | 适合做对比实验的“弱基线” |
| BiLSTM | 长距离依赖,语义连贯 | 低 | 中高 | 首选主模型,可控可解释 |
| BERT微调 | 通用语义+上下文建模 | 高(需GPU) | 高 | 适合做“强基线”,展示上限 |
推荐的完整路线是三步走:先用TF-IDF加逻辑回归跑出一个传统机器学习基线,通常F1在0.82到0.88之间;再用BiLSTM作为深度学习主模型,目标是把F1推到0.90上下;最后用BERT微调跑两个epoch,作为实验上限。三个结果摆在论文里,既有对比实验又有消融分析,“技术研究”这四个字就立住了。
3. 把源码拆开跑通:环境、数据预处理与BiLSTM训练的最小实现
3.1 环境准备:CPU也能跑,但显存决定你的上限
不管源码包里写的是PyTorch还是TensorFlow,第一步永远是建一个干净的虚拟环境。我习惯用conda,避免把系统Python搞乱。
conda create -n fake_news python=3.9 conda activate fake_news pip install torch pandas numpy scikit-learn jieba transformers每一条都有用:torch是深度学习框架,pandas负责读数据,scikit-learn提供切分和评估指标,jieba是中文分词用,英文数据集可装可不装,transformers是后面跑BERT时才用到。装完之后建议顺手跑一句python -c "import torch; print(torch.__version__)"确认安装成功。
CPU能不能跑?能。BiLSTM在几千条新闻数据上,十几分钟一个epoch,完全能接受。但如果后面要跑BERT,CPU上一个epoch可能就要两三个小时,不现实。所以环境这一步就要想好,你的机器有没有NVIDIA显卡、显存多大。没有GPU就老老实实以BiLSTM为主模型,BERT对比实验可以只跑部分数据做定性观察,不用跑满。
3.2 数据预处理:先把文本洗干净再进模型
这一步是源码包里最容易被忽略、实际最影响结果的部分。拿到的原始CSV通常长这样:id、title、author、text、label。text字段里全是HTML标签、超链接、多余空格,直接喂给模型就是灾难。
import pandas as pd import re df = pd.read_csv("data/fake_news.csv") print(df["label"].value_counts()) # 先看标签分布,确认是不是二分类 def clean_text(text): text = re.sub(r"<.*?>", "", text) # 去HTML标签 text = re.sub(r"http\S+", "", text) # 去超链接 text = re.sub(r"[^a-zA-Z\s]", "", text.lower()) # 只留字母与空格,统一小写 return text.strip() df["clean_text"] = df["text"].fillna("").apply(clean_text) # 看清洗后的文本长度分布,决定max_len df["word_count"] = df["clean_text"].apply(lambda x: len(x.split())) print(df["word_count"].describe()) from sklearn.model_selection import train_test_split train_df, test_df = train_test_split( df, test_size=0.2, random_state=42, stratify=df["label"] )逻辑说明:清洗函数里三步操作分别处理三种脏数据——HTML标签、超链接、标点和大小写。fillna("")处理空值,避免某条新闻text字段为空导致程序崩溃。
参数说明:test_size=0.2表示20%的数据留作测试集,这个比例是文本分类项目的常见默认值,数据量太小就改成0.15。stratify=df["label"]是这里最关键的一个参数,它保证切分后训练集和测试集里的真假新闻比例和原始数据一致,避免出现测试集全是真的、假的只出现在训练集这种低级错误。random_state=42固定随机种子,保证每次跑出来的切分结果一致。
3.3 核心训练脚本:BiLSTM分类模型的搭建与训练
数据处理完之后,就到了源码包里最核心的训练脚本。BiLSTM的模型结构不复杂,核心就是Embedding层加双向LSTM层加全连接层。
import torch import torch.nn as nn from torch.utils.data import Dataset, DataLoader class FakeNewsDataset(Dataset): def __init__(self, texts, labels, vocab, max_len=200): self.data = texts self.labels = labels self.vocab = vocab self.max_len = max_len def __len__(self): return len(self.data) def __getitem__(self, idx): words = self.data[idx].split() ids = [self.vocab.get(w, 1) for w in words[:self.max_len]] ids = ids + [0] * (self.max_len - len(ids)) # 不足max_len补0 return torch.tensor(ids), torch.tensor(self.labels[idx]) class BiLSTM(nn.Module): def __init__(self, vocab_size, embedding_dim=100, hidden_dim=128, num_layers=2, num_classes=2, dropout=0.5): super().__init__() self.embedding = nn.Embedding(vocab_size, embedding_dim, padding_idx=0) self.lstm = nn.LSTM( embedding_dim, hidden_dim, num_layers, batch_first=True, bidirectional=True, dropout=dropout ) self.fc = nn.Linear(hidden_dim * 2, num_classes) def forward(self, x): emb = self.embedding(x) out, (h_n, c_n) = self.lstm(emb) # 取最后一个时间步的输出,双向LSTM拼接两个方向 out = out[:, -1, :] return self.fc(out)逻辑说明:FakeNewsDataset的作用是把一段文本转成固定长度的数字序列。vocab.get(w, 1)里,0是padding符号,1是未知词,词表里没有的词统一映射成1,这样模型遇到生词不会崩。ids[:self.max_len]截断超长文本,再用0补齐短文本,保证一个batch里的所有样本长度一致,才能拼成矩阵送入模型。
参数说明:embedding_dim=100是词向量维度,100对新闻文本够用,追求效果可以调到200或300,但训练时间会变长。hidden_dim=128是LSTM隐藏层节点数,太小欠拟合、太大过拟合且显存压力大。num_layers=2是LSTM层数,两层是文本分类的常用配置,超过两层收益很小。bidirectional=True开启双向,这也是BiLSTM里那个B的由来。dropout=0.5在两层LSTM之间生效,防止过拟合,但注意最后一层全连接前没有dropout,效果上差别不大。
训练循环不复杂,关键是优化器和损失函数的选择:
from torch.optim import Adam model = BiLSTM(vocab_size=len(vocab)+2) loss_fn = nn.CrossEntropyLoss() optimizer = Adam(model.parameters(), lr=1e-3) for epoch in range(10): model.train() total_loss = 0 for batch_ids, batch_labels in train_loader: optimizer.zero_grad() logits = model(batch_ids) loss = loss_fn(logits, batch_labels) loss.backward() optimizer.step() total_loss += loss.item() print(f"epoch {epoch+1}, loss: {total_loss/len(train_loader):.4f}")逻辑说明:每次迭代先把梯度清零,前向传播得到logits,计算交叉熵损失,反向传播更新权重。这里用的是Adam优化器,它对学习率不敏感,是深度学习文本分类的默认选择,比SGD省心很多。
参数说明:lr=1e-3是Adam的常用初始学习率,太大loss会震荡,太小收敛太慢。epoch=10对几千条训练数据通常够用,配合早停策略(后面会讲)可以防止过拟合。CrossEntropyLoss在PyTorch里已经内置了softmax,所以模型最后一层直接输出原始logits就好,不要在fc层后再手动加softmax,否则梯度会出问题。
3.4 训练参数别照抄:max_len、batch_size、早停的设置思路
源码包里的参数是作者在他的数据和显卡上试出来的,直接照抄往往翻车。max_len尤其明显——有人用100,有人用500,差别来自数据集的文本长度分布。
我之前统计过两个公开的英文新闻数据集,文本词数均值在200到300之间,但长尾能到1000词以上。max_len设置太短,长新闻的信息全被截掉;设置太长,短新闻需要疯狂补齐,计算浪费一半以上。正确做法是看3.2里打印出来的describe()结果,取75%分位数作为max_len,比如75%分位是240,那就设250,保证覆盖大多数样本。
batch_size取决于显存大小。显存8GB以下,batch_size设16到32比较稳;显存充足可以试64,训练更快但不一定更准。CPU训练时batch_size设小一点,比如16,减少单次计算量。
早停我一般放在训练函数里,逻辑很简单:每个epoch结束在验证集上算一次F1,连续3个epoch F1没有提升就停止训练,并恢复到历史最优的模型参数。这个策略不复杂,但能帮你省下大量调参时间,不用死磕“到底要跑几个epoch”。
4. 评估和出结果:用F1和混淆矩阵说话,别只报准确率
4.1 评估指标:为什么准确率在虚假新闻场景里会骗人
很多源码包的README里只写准确率,这是最误导人的地方。假设测试集里30%是假新闻、70%是真新闻,模型什么都不学,把所有样本都判为真实,准确率也有70%。这个70%看着还行,但假新闻一条都没检测出来,项目在实战层面毫无意义。
所以虚假新闻检测的核心评估指标是F1分数,它是精确率和召回率的调和平均。精确率回答“模型说是假新闻的,有多少真说对了”;召回率回答“真正的假新闻里,模型抓到了多少”。单独看任何一个都可能失真——宁可错杀一万只为抓到一个,召回率极高但精确率极低,在实际场景里同样不可用。F1同时惩罚这两端的偏科。
| 指标 | 计算方式 | 回答的问题 | 何时重点关注 |
|---|---|---|---|
| 准确率 | 全部预测对的样本 / 总样本 | 整体判断对了几成 | 类别均衡时 |
| 精确率 | 预测为假且确实为假 / 预测为假的总数 | 报出来的假新闻有多可信 | 误报成本高时 |
| 召回率 | 预测为假且确实为假 / 实际为假的总数 | 假新闻漏掉了多少 | 漏报成本高时 |
| F1 | 精确率和召回率的调和平均 | 两者兼顾的综合成绩 | 类别不均衡时 |
毕业设计场景下,三者都要报,但答辩时重点讲F1和召回率。
4.2 用classification_report一次性输出全套指标
源码包里一般有一段评估代码,但很多写得不全,只算准确率。建议改成下面这样,一次输出全部指标:
model.eval() all_preds, all_labels = [], [] with torch.no_grad(): for batch_ids, batch_labels in test_loader: logits = model(batch_ids) preds = torch.argmax(logits, dim=1) all_preds.extend(preds.cpu().numpy()) all_labels.extend(batch_labels.cpu().numpy()) from sklearn.metrics import classification_report, confusion_matrix print(classification_report(all_labels, all_preds, target_names=["real", "fake"])) print("混淆矩阵:") print(confusion_matrix(all_labels, all_preds))逻辑说明:torch.no_grad()告诉PyTorch不需要计算梯度,推理阶段显存占用更小、速度更快。torch.argmax(logits, dim=1)取每个样本概率最大的类别下标,0对应真实新闻,1对应假新闻。classification_report一次输出精确率、召回率、F1和各类别的样本数,比手动计算省事得多。
confusion_matrix输出的2x2矩阵里,左上角是真实新闻被正确预测的数量,右下角是假新闻被正确预测的数量,右上角是真实新闻被误判成假新闻的数量,左下角是假新闻被漏判的数量。答辩时被问到“模型在哪种情况下会出错”,直接指着混淆矩阵说“左下角漏判了X条假新闻,这些样本有个共同特征是……”,这比空谈模型结构有说服力得多。
4.3 把结果做成“研究”的样子:对比实验与消融实验
毕业设计的题目里带着“技术研究”四个字,就意味着不能只交一个能跑的模型,得有实验对比。最省时间也最有效的做法是两组实验。
对比实验是上面的三步走路线:逻辑回归基线、BiLSTM主模型、BERT微调强基线,把三者的准确率、精确率、召回率、F1和训练时间放一张表里。这张表能回答“深度学习比传统方法好在哪”和“更强的模型能带来多少提升”两个问题。
消融实验的做法是拆掉模型的一部分,看效果掉多少。比如从BiLSTM去掉双向、只用单向LSTM;去掉预训练词向量、用随机初始化Embedding;减少LSTM层数到1层。每一项改动对应一个F1的下降幅度,这个“下降”就是你要在论文里分析的贡献点。
这两组实验做完,你论文的实验章节基本就填满了。表格里加一列“训练时间”,顺便能看出你在训练成本和效果之间的取舍,这正是一个工程型毕设该有的思考维度。
5. 避坑清单:源码跑不通、结果复现不了的五个常见问题
5.1 训练集准确率99%,测试集F1只有0.6:数据泄漏的典型表现
现象:训练时loss降得飞快,准确率逼近100%,测试集上却一塌糊涂,F1徘徊在0.6。
原因:这是典型的特征泄漏。常见来源有两个方向——文本级别上,源码自带的预处理把同一个文本同时写进了训练集和测试集;标签级别上,某个信息量很大的字段(比如作者名或来源域名)和标签高度相关,模型学会了“看到某个作者就预测为假”,根本没学新闻内容。
解决:用3.2里的train_test_split重新切分数据,保证没有重复样本跨集合。另外,把text之外的元数据字段全部丢掉,只保留正文文本和标签。如果换了数据之后模型效果骤降,不要慌,这才说明模型在学真东西。
5.2 每次训练结果都不一样,F1在0.80到0.86之间跳动:随机种子没固定
现象:同一个源码包、同一条命令,每次运行的F1结果都不一样,波动幅度能到5个点。
原因:PyTorch的模型参数初始化是随机的,DataLoader的数据顺序是随机的,如果数据切分也用了随机数,那每次跑的实验环境都不一样。这类复现问题在论文阶段最致命——你无法向老师解释为什么今天跑出来的F1比论文里写的低了4个点。
解决:在训练脚本最前面固定所有随机源,我一般会写一个set_seed函数,固定Python、NumPy、PyTorch三个库的随机种子,并在数据切分时固定random_state。这样做完,同一份代码在不同时间跑的结果波动能控制在1个点以内。
def set_seed(seed=42): import random, numpy as np random.seed(seed) np.random.seed(seed) torch.manual_seed(seed) torch.cuda.manual_seed_all(seed) torch.backends.cudnn.deterministic = True5.3 一跑BERT显存就爆:max_len和batch_size没按源码头尾来
现象:BERT微调脚本一启动就直接报CUDA out of memory,或者训练几轮之后OOM。
原因:BERT内部是12层Transformer,显存占用比BiLSTM高一个数量级。源码包里如果默认max_len=512、batch_size=32,8GB显存的显卡基本扛不住。
解决:把max_len降到128或64,batch_size降到8或4,先跑通再考虑效果。BERT对句子长度的实际有效利用没有LSTM那么依赖,短一点损失不大。
5.4 中文数据集直接用英文模型跑,效果差到怀疑人生
现象:换用中文新闻数据后,同样的训练脚本,F1比英文数据低了十几个点,甚至根本不收敛。
原因:英文按空格分词,中文没有空格,一个“特朗普宣布”在英文脚本里会被当成一个词查到未知词表,变成1,所有词都是未知词,模型自然什么都学不到。
解决:中文数据必须分词。先用jieba分词,再用空格把词连起来,后续流程才能复用英文数据的代码。代码很简单:df["clean_text"] = df["clean_text"].apply(lambda x: " ".join(jieba.cut(x)))。中文的词表构建逻辑和英文完全一致,只是分词这一步不能省。
5.5 保存加载模型后,预测结果全是真实新闻:标签映射错位
现象:训练时F1正常,用predict.py加载保存的模型后,输入什么文本都输出“真实”。
原因:保存模型时只存了model.state_dict(),没保存词表vocab。加载时重新构建的词表顺序和训练时不一致,文本转出来的词ID全部错位,模型等于在接收一堆乱码。另一种情况是加载模型时忘了设置model.eval(),模型处于训练模式,Dropout还在生效,输出被随机噪声干扰。
解决:保存模型时把vocab一起存下来,用torch.save({"state_dict": model.state_dict(), "vocab": vocab}, "model.pth")。加载后再手动调用model.eval(),这两步能做到,推理结果基本就能和测试集表现对齐了。
6. 进阶玩法:把检测模型套一个Web演示壳
训练完模型,不要只停留在终端里打几行预测结果。给模型套一个Streamlit网页界面,输入一段新闻文本直接出检测结果,这个演示在答辩现场非常加分。
import streamlit as st import torch st.title("虚假新闻检测演示") text = st.text_area("粘贴新闻正文", height=200) model, vocab, max_len, clean_text_fn = load_model_and_vocab("model.pth") if st.button("开始检测"): cleaned = clean_text_fn(text) words = cleaned.split()[:max_len] ids = [vocab.get(w, 1) for w in words] ids = ids + [0] * (max_len - len(ids)) input_tensor = torch.tensor([ids]) with torch.no_grad(): logits = model(input_tensor) probs = torch.softmax(logits, dim=1) fake_prob = probs[0, 1].item() if fake_prob > 0.5: st.error(f"判定为虚假新闻,置信度 {fake_prob:.2%}") else: st.success(f"判定为真实新闻,置信度 {1 - fake_prob:.2%}")逻辑说明:load_model_and_vocab把上一章的模型和词表加载回来,clean_text_fn复用3.2里的清洗函数,保证网页输入的文本和训练时走一样的预处理逻辑。torch.softmax把logits转成概率,取下标1的概率作为虚假新闻置信度。
这个小界面还能加两个提升效果的小改动:一是用st.bar_chart把两个类别的概率画成条形图,直观展示模型的判断信心;二是在页面底部放几条真实和虚假的示例文本,加一个“填入示例”按钮,让评委不用自己准备素材就能快速体验。
我自己的习惯是,跑通这类项目后会把“种子固定、指标报告、模型保存”这三段代码固定成模板,每次新项目直接复用。毕业设计里走通这么一遍,后面再做其他文本分类任务,基本就是换数据、换参数的事。模型训练这种活,运气成分是存在的,但固定随机种子、把预处理和评估流程标准化之后,剩下能波动的空间就很小了。希望帮到你。
本文还有配套的精品资源,点击获取