深度学习虚假新闻检测实战:基于LSTM的文本分类模型构建与调参
发布时间:2026/9/28 15:11:10来源:尧图网络
简介面向计算机相关专业学生的深度学习毕业设计源码聚焦虚假新闻检测技术研究适合用于课程设计、论文实验或项目实践。项目以Python为后端核心同时配有前端展示界面整体涵盖文本数据清洗、特征构建、循环神经网络RNN模型训练与评估以及检测结果的可视化展示模型权重文件也一并内置便于直接复现实验。压缩包共113个文件大小约49.65MB主要包含Python训练与推理脚本、React前端组件、JSON/TS配置文件、CSV标注数据集以及HDF5模型权重等类型目录结构清晰可按数据处理、模型层、前端展示和配置文档等模块快速查阅免去自己整理环境的琐碎步骤。所有源码均经过严格测试可直接运行适合在此基础上进行二次开发也可作为理解深度学习文本分类完整流程的参考范式。目前已有270人学习下载对需要快速搭建完整毕业设计项目的同学具有较高的参考价值。1. 基于深度学习的虚假新闻检测是什么一个毕业设计项目到底在解决什么问题临近毕业季很多人拿到这样一个题目就懵了Python毕业设计基于深度学习的虚假新闻检测。听起来高大上说穿了就是一个文本二分类问题——输入一条新闻正文输出它是真实还是虚假。这类项目能落地是因为它把NLP里的文本清洗、分词、模型训练、指标评估和结果解释完整串了一遍导师想看到的是你具备独立解决一个实际问题的能力而不是只会调用现成的接口。这个方向适合两类人一是想做点有故事性的毕设、不想停留在“图书管理系评统”的同学二是对深度学习入门者想借一个具体场景把LSTM或BERT跑通的实践派。源码包里通常包含数据预处理脚本、模型定义、训练与评估代码以及一份能跑通的README。这篇文章不卖源码而是帮你把背后的数据集怎么选、模型怎么搭、参数怎么调、坑在哪里一次说清楚。2. 搭建虚假新闻检测的最小系统数据集、预处理与一行命令跑通训练2.1 选数据集为什么绕不开LIAR和FakeNewsNet做虚假新闻检测第一步不是写模型而是找一份能洗出训练集的数据。公开常用的有三类LIAR是短文本格式每条声明带六种标注实际使用时要自己压缩成二分类FakeNewsNet包含新闻正文和社交上下文结构完整但部分抓取链接已经失效Kaggle上流传最广的fake news detection数据集直接给你CSV文件每行只有text和label两列。毕业设计追求可控、可复现我建议从这份静态CSV起步因为它不需要联网采集样本量也足够训练一个LSTM。如果课题要求做中文虚假新闻常见做法是从公开谣言数据集或官方辟谣平台采集。但自建数据要慎重谣言类别通常极不均衡而且新闻有时效性三年前的“事实”放到今天可能反转。更关键的是你得在论文里写清楚数据来源、采集窗口和标注规则否则答辩时一句“数据哪来的”就能让你卡壳。第一次做别碰需要实时爬取的大工程先把一个小而干净的数据集用透。2.2 数据预处理把文本变成模型能吃的序列深度学习模型不认识字符串我们得做四步清洗、分词、建词表、转索引。先看一个英文场景的清洗函数中文场景我在注释里给出替代方案。import re import pandas as pd from sklearn.model_selection import train_test_split def clean_text(text: str) - str: 基础清洗去HTML标签、URL、非字母字符统一转小写。 # 去HTML标签适用于从网页抓取的正文 text re.sub(r[^], , text) # 去URL新闻里常带外链对分类是噪声 text re.sub(rhttp[s]?://\S, , text) # 只保留字母和空格英文场景数字也可以去掉 text re.sub(r[^a-zA-Z\s], , text) return text.lower().strip() # 读取数据假设 data.csv 有两列 text, label df pd.read_csv(data.csv) df[text] df[text].astype(str).apply(clean_text) df df.dropna(subset[text]) # 分层划分训练/验证集固定随机种子保证结果可复现 train_df, val_df train_test_split(df, test_size0.2, random_state42, stratifydf[label]) print(train_df.shape, val_df.shape)逻辑说明这个清洗函数把对分类没帮助的网页“杂质”删掉。我习惯保守处理只删明显噪声不做词干还原因为LSTM这类模型能自己学到词形变化提前砍词反而丢掉信息。参数说明random_state42 是复现结果的钥匙stratifydf[label] 保证训练集和验证集正负比例一致否则验证集会偏向某一类评估指标失真。接下来是分词与建词表。import nltk from collections import Counter # 第一次使用需要下载 punkt # nltk.download(punkt) def tokenize(text: str) - list: 英文用 nltk 分词如果是中文替换成 jieba.lcut(text)。 return nltk.word_tokenize(text) # 统计词频保留出现次数不少于2次的词 counter Counter() for text in train_df[text]: counter.update(tokenize(text)) vocab [pad, unk] [word for word, cnt in counter.most_common(10000) if cnt 1] word2idx {word: idx for idx, word in enumerate(vocab)} def encode(text: str, max_len: int 200): tokens tokenize(text)[:max_len] ids [word2idx.get(w, word2idx[unk]) for w in tokens] ids ids [word2idx[pad]] * (max_len - len(ids)) # 右侧补齐到定长 return ids逻辑说明限制词表大小为10000并去掉低频词能减少模型参数、挡住大部分拼写噪声。max_len设200因为新闻正文的关键信息通常集中在前几句。padding统一长度才能拼成batch送进GPU。参数说明 的索引固定为0后面模型里Embedding的padding_idx要指向它否则pad位置也会参与反向传播 索引给1词表外的词统一映射到它。2.3 第一版可跑通的最小命令与代码有了编码函数接下来写一个最简Dataset把DataLoader跑通。import torch from torch.utils.data import Dataset, DataLoader class FakeNewsDataset(Dataset): def __init__(self, df, max_len200): self.ids_list [encode(text, max_len) for text in df[text]] self.labels df[label].astype(int).values def __len__(self): return len(self.labels) def __getitem__(self, idx): return torch.tensor(self.ids_list[idx], dtypetorch.long), torch.tensor(self.labels[idx], dtypetorch.float32) train_dataset FakeNewsDataset(train_df) val_dataset FakeNewsDataset(val_df) train_loader DataLoader(train_dataset, batch_size64, shuffleTrue) val_loader DataLoader(val_dataset, batch_size128, shuffleFalse)逻辑说明这个Dataset类把每条样本转换成定长索引张量label用float32是为了后面配合BCEWithLogitsLoss做二分类。参数说明batch_size64是经验值显存小就改成32shuffleTrue只应用在训练集验证集保持固定顺序方便复现指标。到这里一个能跑通的数据流就搭好了。很多人卡在装环境、跑不起来的阶段其实只要按照Python教程安装好3.8以上的环境把上述代码按顺序拼起来一个最小可训练系统就完成了。下一步要选模型先让LSTM跑起来再考虑要不要上BERT。3. 模型选型与结构CNN、LSTM还是BERT预训练毕业设计怎么权衡3.1 文本分类模型的演进从词向量到注意力虚假新闻检测本质是文本分类。早期方案是TF-IDF加SVM把词频矩阵喂给线性模型后来word2vec让词有了语义向量模型可以学习局部词语搭配但新闻长文本更依赖上下文逻辑于是LSTM登场它通过门控结构保留前面信息与此同时CNN也被证明能抽取局部n-gram模式配合池化层捕获关键词。2018年后Transformer预训练模型出现BERT把分词、词义消歧和上下文建模一次解决成为文本分类新基线。对毕业设计来说你不需要追最前沿。模型复杂度大致是LSTM CNN BERT。答辩老师最常问“你为什么选这个模型”。我的建议是用LSTM做主线拿BERT做对比实验。理由有三LSTM代码量小、不易翻车超参数直观遇到问题好调自带时序结构解释“模型关注了哪些词”时更顺手。BERT效果好但显存需求和训练时间摆在那里不是所有电脑都扛得住。3.2 用PyTorch实现一个LSTM分类器核心代码与参数下面这段模型定义是我在毕设项目里常用的版本每个参数的作用都标在注释里。import torch.nn as nn class LSTMClassifier(nn.Module): def __init__(self, vocab_size, embedding_dim128, hidden_size256, num_layers2, num_classes1, dropout0.5, pad_idx0): super().__init__() self.embedding nn.Embedding(vocab_size, embedding_dim, padding_idxpad_idx) self.lstm nn.LSTM(embedding_dim, hidden_size, num_layers, batch_firstTrue, bidirectionalTrue, dropoutdropout) # 双向LSTM最后输出的特征维度是 hidden_size * 2 self.classifier nn.Sequential( nn.Dropout(dropout), nn.Linear(hidden_size * 2, hidden_size), nn.ReLU(), nn.Dropout(dropout), nn.Linear(hidden_size, num_classes) ) def forward(self, x): # x: (batch, seq_len) emb self.embedding(x) # (batch, seq_len, embedding_dim) lstm_out, (h_n, c_n) self.lstm(emb) # lstm_out: (batch, seq_len, 2*hidden_size) # 取双向最后一层拼接向量前向h_n[-2]反向h_n[-1] h_cat torch.cat((h_n[-2], h_n[-1]), dim1) # (batch, 2*hidden_size) return self.classifier(h_cat)逻辑说明这里没有把所有时间步的输出都接全连接而是只取最后一步的隐藏状态。双向LSTM的h_n第一维是层数乘以2取h_n[-2]和h_n[-1]分别对应当前最后一层的前向和反向输出这样整段新闻的信息被压缩成一个向量再经过两个线性层映射到得分。参数说明embedding_dim取100到300之间128是稳当的中间值hidden_size越大容量越强但会加重显存负担和过拟合256对毕设足够num_layers2能让模型捕捉更抽象的特征再深在小数据集上很难收敛dropout0.5用来抑制过拟合后面训练时若F1稳定可调到0.3。3.3 引入预训练模型什么时候值得上BERT当LSTM在验证集上F1一直卡在0.75附近而答辩老师期望看到“深度学习最新进展”时可以引入预训练模型。常见做法是用transformers库加载一个小号的BERT变体比如DistilBERT参数量只有BERT一半效果损失很少。from transformers import AutoTokenizer, AutoModelForSequenceClassification # 英文场景常见 distilbert-base-uncased中文场景常见 bert-base-chinese model_name distilbert-base-uncased tokenizer AutoTokenizer.from_pretrained(model_name) model AutoModelForSequenceClassification.from_pretrained(model_name, num_labels2)逻辑说明预训练模型自带tokenizer它会自己处理分词、特殊符号和padding。所以前面2.2节的清洗逻辑到这里只需去除URL和HTML不能再用nltk切词否则会破坏BERT的WordPiece。参数说明num_labels2表示二分类模型输出logits后面配合CrossEntropyLoss使用。如果你的电脑只有4GB显存把max_len调短到128batch_size设为16再打开梯度累积。4. 训练与调参让虚假新闻检测模型验证集F1超过90%的五个参数4.1 训练脚本的整体结构数据加载、优化器、学习率策略训练脚本的骨架不长但每一步都有讲究。先贴一段可以直接扩展的版本再逐段解释。import torch import torch.nn as nn from torch.optim import AdamW device torch.device(cuda if torch.cuda.is_available() else cpu) model LSTMClassifier(len(vocab)).to(device) criterion nn.BCEWithLogitsLoss() optimizer AdamW(model.parameters(), lr1e-3, weight_decay1e-5) scheduler torch.optim.lr_scheduler.StepLR(optimizer, step_size2, gamma0.5) best_f1 0.0 for epoch in range(10): model.train() for batch in train_loader: inputs, labels batch inputs, labels inputs.to(device), labels.to(device) logits model(inputs).squeeze(1) loss criterion(logits, labels) optimizer.zero_grad() loss.backward() torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm1.0) optimizer.step() val_f1, _, _, _ evaluate(model, val_loader, device) # 函数见4.3 scheduler.step() print(fepoch {epoch}, val_f1{val_f1:.4f}) if val_f1 best_f1: best_f1 val_f1 torch.save(model.state_dict(), best_model.pt)逻辑说明用AdamW替代Adam配合weight_decay做正则clip_grad_norm把梯度模长限制在1.0防止LSTM梯度爆炸。每个epoch后做一次评估以验证F1为准保存最优权重而不是最后epoch的权重。参数说明StepLR每2个epoch把学习率减半这是最简单的学习率衰减策略。如果换成BERT学习率要降到2e-5左右weight_decay调成0.01否则损失会震荡。4.2 必调的五个超参数及其作用参数典型值设得过大设得过小learning_rateLSTM用1e-3BERT用2e-5损失震荡训练翻车收敛极慢原地踏步batch_size32~64显存溢出泛化变差训练慢梯度不稳定max_len200短文本可128内存浪费引入尾部噪声截掉关键信息F1骤降hidden_size128~256参数量大过拟合表达力不足欠拟合num_layers1~2梯度消失训练不稳无法建模复杂时序依赖这五个参数是“玄学”重灾区。我通常先把learning_rate钉在1e-3batch_size设为64max_len设为200hidden_size设为256跑10个epoch观察loss曲线。如果loss下降后稳定不再动才去调整学习率和层数。不要一上来就做全参数网格搜索毕设时间经不起折腾先用手感调通一个模型再考虑优化。4.3 评估指标准确率不够要看F1和混淆矩阵虚假新闻数据集里真实新闻往往占多数模型全输出“真实”也能到八成准确率。只看准确率会被严重误导所以要写一个evaluate函数返回precision、recall、F1和confusion matrix。from sklearn.metrics import precision_recall_fscore_support, confusion_matrix def evaluate(model, loader, device, threshold0.5): model.eval() all_preds, all_labels [], [] with torch.no_grad(): for inputs, labels in loader: inputs inputs.to(device) logits model(inputs).squeeze(1) preds (torch.sigmoid(logits) threshold).int() all_preds.extend(preds.cpu().numpy()) all_labels.extend(labels.cpu().numpy()) precision, recall, f1, _ precision_recall_fscore_support( all_labels, all_preds, averagebinary, zero_division0) cm confusion_matrix(all_labels, all_preds) return f1, precision, recall, cm # 使用示例 f1, precision, recall, cm evaluate(model, val_loader, device) print(F1:, f1, Precision:, precision, Recall:, recall) print(混淆矩阵:\n, cm)逻辑说明用sigmoid把logits转成概率再和阈值比较得到预测类别。precision表示模型判定为“假新闻”的样本里真实假新闻的占比recall表示真正的假新闻被找出来的比例F1是两者的调和平均。对虚假新闻检测漏放一条假新闻的代价通常高于误伤一条真新闻因此recall和F1比准确率更能说明问题。参数说明threshold默认0.5。如果recall偏低可以把threshold调到0.4或0.3牺牲一点precision换取更多假新闻被召回。调整阈值后需要在论文里注明否则演示时结果对不上。5. 避坑与常见问题虚假新闻检测项目的五个典型踩坑记录5.1 数据集样本不均衡模型全预测“真实新闻”却得到高准确率现象训练完打印准确率有92%但看混淆矩阵发现假新闻一个都没找出来模型把所有样本都当成了真实新闻。原因公开数据集中真实新闻占了接近九成。模型发现全预测正类就能把loss压得很低它根本没有学会区分模式。解决先用分层抽样前面代码里的stratify再给损失函数加类别权重。PyTorch的BCEWithLogitsLoss支持pos_weight用于放大少数类样本的梯度。# 假新闻样本数 / 真新闻样本数用来折算类别权重 pos_weight torch.tensor([len(train_df[train_df[label]0]) / len(train_df[train_df[label]1])]).to(device) criterion nn.BCEWithLogitsLoss(pos_weightpos_weight)逻辑说明pos_weight让模型在遇到假新闻样本时产出更大的梯度强迫它关注少数类。这个方法比修改损失函数简单也不需要动模型结构。注意weight是张量要放到device上。5.2 中文数据集与分词差异按空格切词损失语义现象拿到中文新闻数据直接按空格split结果词表里出现大量单字模型F1一直卡在0.70附近。原因中文不像英文天然有空格分词。“不/是”这类否定词被拆开后整句语义可能反转模型学到的是错误规律。解决中文场景改用jieba分词最好加载专业词典。预处理代码里把tokenize函数替换成jieba.lcut即可。import jieba def tokenize_zh(text: str) - list: # 关闭HMM新词发现减少噪声专业领域可加入自定义词典 jieba.initialize() return [w for w in jieba.lcut(text) if w.strip()]逻辑说明jieba是基于统计词频的中文分词器对新闻领域通常够用。如果你后续使用BERT中文模型则不需要jieba直接用bert的tokenizer拿到字粒度token它本身就对中文友好。5.3 预训练模型加载慢或显存不足瓶颈不在模型本身现象加载best_model.pt时提示显存不够或者训练时batch_size只能设4一个epoch要跑半小时。原因大部分毕设电脑只有8GB内存或4GB显存。bert-base有110M参数前向一次就要占用数百MB如果max_len还开到512显存直接爆。解决换成DistilBERT或ALBERT这类轻量模型max_len降到128batch_size设为16再用梯度累积模拟大batch。# 梯度累积每4个小batch更新一次参数 accumulation_steps 4 for step, (inputs, labels) in enumerate(train_loader): outputs model(inputs) loss criterion(outputs.squeeze(1), labels) / accumulation_steps loss.backward() if (step 1) % accumulation_steps 0: optimizer.step() optimizer.zero_grad()逻辑说明梯度累积把一次参数更新拆成多步等价于变相增大batch_size但显存峰值不变。注意loss要除以累积步数否则梯度会被放大。5.4 同源数据集泄漏验证集F1虚高新数据立刻拉垮现象验证集上F1达到0.98换一批新采集的数据去测试直接跌到0.65。原因数据划分前没有去重同一条新闻的多个改写版本同时出现在训练集和验证集里。模型其实记住了重复文本没有学到泛化规律。解决划分前按正文去重最好再按发布时间或ID序列做时间切分。ID不能作为去重依据不同ID可能对应同一条原文。# 按正文去重确保同一内容只出现在一个集合 df df.drop_duplicates(subsettext, keepfirst)逻辑说明虚假新闻数据集中“转载”、“增删改写”现象很常见。去重后划分结果才真正反映模型泛化能力。在论文里写明“去重后样本量减少的占比”也是诚实评估的一部分。5.5 答辩时代码突然跑不通环境和随机种子没有锁死现象前一天还能训练的代码第二天打开报ImportError或NumPy版本冲突答辩演示再跑一次指标也和论文对不上。原因Python依赖没有固定版本某个库升级后行为发生变化随机种子没设模型每次初始化不同结果自然漂移。解决用requirements.txt锁版本训练和推理拆成两个脚本训练脚本开头固定所有随机源。import random, numpy as np def set_seed(seed42): random.seed(seed) np.random.seed(seed) torch.manual_seed(seed) torch.cuda.manual_seed_all(seed) torch.backends.cudnn.deterministic True set_seed(42)逻辑说明cudnn.deterministicTrue会让卷积和RNN采用确定性算法速度略慢但结果可复现。对答辩来说可复现比速度重要。requirements.txt把精确版本记录下来换电脑时用pip install -r requirements.txt就能还原环境。6. 让毕业设计拿得出手交互验证脚本与三个答辩加分习惯6.1 做一个可交互的预测脚本让答辩现场演示不翻车训练好的模型不要只停留在训练脚本里封装成一个最小命令行工具答辩时输入一条新闻就能看到预测结果比现场跑训练脚本稳得多。import torch from model import LSTMClassifier # 你的模型文件 def predict(text: str, model, word2idx, device, max_len200): ids encode(text, max_len) x torch.tensor([ids], dtypetorch.long).to(device) logits model(x).squeeze(0) prob torch.sigmoid(logits).item() label 1 if prob 0.5 else 0 return label, prob if __name__ __main__: device torch.device(cuda if torch.cuda.is_available() else cpu) model LSTMClassifier(len(word2idx)).to(device) model.load_state_dict(torch.load(best_model.pt, map_locationdevice)) model.eval() while True: text input(输入新闻文本输入quit退出) if text quit: break label, prob predict(text, model, word2idx, device) print(f预测类别{虚假 if label 1 else 真实}置信度{prob:.4f})逻辑说明这个脚本把预测流程抽出来加载一次权重然后循环接收输入。答辩时可以先准备几条典型样本现场输入立刻出结果。阈值保持0.5演示时如果想减少误报可以临时改成0.6但记得跟论文里的评估阈值保持一致。6.2 三个答辩加分习惯第一训练日志要留痕。每轮epoch把loss和F1追加到CSV里答辩时展示loss下降曲线和F1提升曲线比口头说“效果不错”有力得多。第二做一组LSTM和BERT的对比实验用表格列出F1、precision、recall和训练时长说明你为什选择平衡方案。第三源码包里包含README写明环境版本、运行命令和数据集结构。很多老师的默认印象是“项目源码开箱不能跑”你能让他在10分钟内复现就已经赢了一半。我第一次做这个方向时也翻过车——把验证集准确率当卖点结果答辩时老师现场扔出一条新样本模型预测错了场面一度尴尬。后来我把评估指标换成F1并把交互脚本加进演示原则才把故事讲圆。希望你在动手前就知道这一点模型能复现比模型跑出高分更值钱。希望帮到你。本文还有配套的精品资源点击获取
网站建设高端定制企业官网