☰
深度学习虚假新闻检测实战:基于LSTM的文本分类模型构建与调参
2026/9/28 15:11:04 网站建设 项目流程

简介:面向计算机相关专业学生的深度学习毕业设计源码,聚焦虚假新闻检测技术研究,适合用于课程设计、论文实验或项目实践。项目以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(r'http[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_size=0.2, random_state=42, stratify=df['label']) print(train_df.shape, val_df.shape)

逻辑说明:这个清洗函数把对分类没帮助的网页“杂质”删掉。我习惯保守处理,只删明显噪声,不做词干还原,因为LSTM这类模型能自己学到词形变化,提前砍词反而丢掉信息。参数说明:random_state=42 是复现结果的钥匙;stratify=df['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_len=200): 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], dtype=torch.long), torch.tensor(self.labels[idx], dtype=torch.float32) train_dataset = FakeNewsDataset(train_df) val_dataset = FakeNewsDataset(val_df) train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True) val_loader = DataLoader(val_dataset, batch_size=128, shuffle=False)

逻辑说明:这个Dataset类把每条样本转换成定长索引张量,label用float32,是为了后面配合BCEWithLogitsLoss做二分类。参数说明:batch_size=64是经验值,显存小就改成32;shuffle=True只应用在训练集,验证集保持固定顺序,方便复现指标。

到这里,一个能跑通的数据流就搭好了。很多人卡在装环境、跑不起来的阶段,其实只要按照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_dim=128, hidden_size=256, num_layers=2, num_classes=1, dropout=0.5, pad_idx=0): super().__init__() self.embedding = nn.Embedding(vocab_size, embedding_dim, padding_idx=pad_idx) self.lstm = nn.LSTM(embedding_dim, hidden_size, num_layers, batch_first=True, bidirectional=True, dropout=dropout) # 双向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]), dim=1) # (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_layers=2能让模型捕捉更抽象的特征,再深在小数据集上很难收敛;dropout=0.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_labels=2)

逻辑说明:预训练模型自带tokenizer,它会自己处理分词、特殊符号和padding。所以前面2.2节的清洗逻辑到这里只需去除URL和HTML,不能再用nltk切词,否则会破坏BERT的WordPiece。参数说明:num_labels=2表示二分类,模型输出logits,后面配合CrossEntropyLoss使用。如果你的电脑只有4GB显存,把max_len调短到128,batch_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(), lr=1e-3, weight_decay=1e-5) scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=2, gamma=0.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_norm=1.0) optimizer.step() val_f1, _, _, _ = evaluate(model, val_loader, device) # 函数见4.3 scheduler.step() print(f"epoch {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-3,BERT用2e-5损失震荡,训练翻车收敛极慢,原地踏步
batch_size32~64显存溢出,泛化变差训练慢,梯度不稳定
max_len200(短文本可128)内存浪费,引入尾部噪声截掉关键信息,F1骤降
hidden_size128~256参数量大,过拟合表达力不足,欠拟合
num_layers1~2梯度消失,训练不稳无法建模复杂时序依赖

这五个参数是“玄学”重灾区。我通常先把learning_rate钉在1e-3,batch_size设为64,max_len设为200,hidden_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, threshold=0.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, average='binary', zero_division=0) 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_weight=pos_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降到128,batch_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(subset='text', keep='first')

逻辑说明:虚假新闻数据集中“转载”、“增删改写”现象很常见。去重后,划分结果才真正反映模型泛化能力。在论文里写明“去重后样本量减少的占比”,也是诚实评估的一部分。

5.5 答辩时代码突然跑不通:环境和随机种子没有锁死

现象:前一天还能训练的代码,第二天打开报ImportError或NumPy版本冲突;答辩演示再跑一次,指标也和论文对不上。

原因:Python依赖没有固定版本,某个库升级后行为发生变化;随机种子没设,模型每次初始化不同,结果自然漂移。

解决:用requirements.txt锁版本,训练和推理拆成两个脚本,训练脚本开头固定所有随机源。

import random, numpy as np def set_seed(seed=42): 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.deterministic=True会让卷积和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_len=200): ids = encode(text, max_len) x = torch.tensor([ids], dtype=torch.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_location=device)) 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,并把交互脚本加进演示原则,才把故事讲圆。希望你在动手前就知道这一点:模型能复现,比模型跑出高分更值钱。希望帮到你。

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

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

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

立即咨询