ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Python+MLP构建虚假新闻检测器:从数据清洗到模型训练全解析

Python+MLP构建虚假新闻检测器:从数据清洗到模型训练全解析 简介一套基于Python和MLP的互联网虚假新闻检测器完整源码与项目报告面向机器学习初学者、高校学生及需要完成文本分类实战的开发者适用于课程设计、毕业设计或竞赛复现。压缩包共21个文件核心内容为4个Python脚本fit.py训练、preprocess.py预处理、optimize.py超参数优化、predict.py预测、3个已保存模型、训练/测试用的csv与pkl数据文件以及停用词表txt同时包含项目报告docx、README说明和环境配置文件整体约91.14MB。目录将数据集、模型与代码分类存放结构清晰便于按模块阅读和二次开发数据预处理环节内置中文停用词表模型部分可加载已训练结果直接进行测试。平台显示已有133人学习适合希望从数据预处理、模型训练到结果预测逐步掌握MLP文本分类全流程完成虚假新闻识别实验并参考规范项目报告写法的人群。1. Python MLP 做互联网虚假新闻检测器为什么这个组合值得认真对待单看标题「Python MLP」并不唬人一个两层或三层的多层感知机在深度学习遍地走的今天显得朴素。但真把这份虚假新闻检测器源码跑通的人会有另一层感受新闻文本从字符串变向量、再变真假概率这条链路里的工程细节远比模型结构多。数据清洗怎么做、特征维度怎么定、类别不平衡怎么处理每一步都能让训练直接翻车。这个项目恰好把这些环节串完整了适合三类人做文本分类课程设计的学生、学完 Python 基础想找一个完整项目练手的入门者以及需要快速验证文本二分类可行性的从业者。下面我按源码包的常见结构从数据流、网络实现、训练命令一直讲到踩坑记录。2. 拆解源码包与数据流先弄清文本怎么变成 MLP 能吃的东西拿到一个交付型的源码包最忌讳的事是解压后直奔 train.py 点运行报错之后对着堆栈发呆。我一般先做一件事把压缩包里的文件列出来按「数据、特征、模型、训练、推理、报告」的顺序归类然后花十分钟通读一遍数据相关代码再决定要不要碰训练入口。这样做的原因是——文本分类项目里模型结构往往不是瓶颈数据从原始字符串变成数值矩阵那一段才是问题高发区。2.1 源码包的标准文件构成与阅读顺序这类项目的常见交付结构大致是这样fake_news_detector/ ├── data/ │ ├── news_train.csv # 训练集正文、标签 │ ├── news_test.csv # 测试集有时只有一份数据自己切 │ └── stopwords.txt # 停用词表中文场景必备 ├── src/ │ ├── preprocess.py # 清洗 分词 向量化 │ ├── model.py # MLP 网络定义 │ ├── train.py # 训练入口 │ └── predict.py # 推理入口 ├── output/ │ ├── mlp_model.pth # 训练好的权重 │ ├── tfidf_vectorizer.pkl # 向量器推理时必须复用 │ └── metrics.json # 验证集指标 ├── README.md └── 项目报告.pdf提示上面的目录结构是此类交付包最常见的形态不同项目会有文件名差异但「数据、特征、模型、训练、推理、报告」这几大块不会变。正确的阅读顺序是先 preprocess.py再 model.py最后 train.py。理由是preprocess 定义了模型输入维度model 定义了网络形状train 是把两者接起来的调度逻辑。如果你倒过来读很容易被训练代码里那些日志打印和断点保存带偏注意力甚至把参数改到和数据处理对不上白跑一晚上。我见到过不少人在这步上栽跟头然后花大半天排查一个其实是自己改坏的参数。2.2 数据流主线一条新闻从 CSV 行变成 MLP 输入张量这一节展示 preprocess.py 里最常见的管线实现# preprocess.py 核心流程节选 import jieba import re from sklearn.feature_extraction.text import TfidfVectorizer def clean_text(text: str) - str: # 去 HTML 标签、网址和多余空白避免噪声被当成特征 text re.sub(r[^], , text) text re.sub(rhttp\S, , text) text re.sub(r\s, , text).strip() return text def tokenize(text: str) - str: # 中文按词切分英文保持原样用空格连接以便 TF-IDF 统计 return .join(jieba.cut(text)) def build_vectorizer(train_texts, max_features5000): # ngram_range(1,2) 让特征包含单词和相邻双词 vec TfidfVectorizer(max_featuresmax_features, ngram_range(1, 2), sublinear_tfTrue) X vec.fit_transform(train_texts) # 返回稀疏矩阵 return vec, X逻辑说明clean_text 的工作是降低噪声——新闻原文里常见的超链接和 HTML 标签如果不删会被 TF-IDF 当成高频特征模型很可能拿「http」三个字符当判断真假的依据这是文本分类里最典型的伪特征。tokenize 用 jieba 分词并用空格连接是因为 TfidfVectorizer 默认按空格分隔 token 统计词频中文不预先分词一整句会被当成一个词特征完全失去意义。build_vectorizer 里两个参数很关键max_features 控制词表大小直接决定 MLP 输入层的神经元数量ngram_range(1,2) 让特征同时包含单词和相邻双词对「不实」「谣言」这类组合词有更好的识别效果。参数说明max_features 设太大比如五万MLP 输入层会急剧膨胀训练变慢且容易过拟合设太小比如五百大量有区分度的词被丢掉。5000 是这类项目里兼顾效果和训练速度的常见起点。sublinear_tfTrue 对词频做对数压缩防止个别词频过高的词主导整个特征向量当你发现模型预测结果几乎只被少数几个词决定时先检查这个参数是不是没开。2.3 向量化选型TF-IDF 与 Word2Vec 在 MLP 场景下的取舍这类项目在向量化一环通常只有两种方案TF-IDF 或 Word2Vec 平均向量。两者都行但行为差异很大我整理了一张对比表。对比项TF-IDF MLPWord2Vec 平均向量 MLP特征维度由词表大小决定通常 2000~10000固定等于词向量维度常见 100/200/300语义能力按词统计不处理近义词近义词向量接近具备一定泛化训练成本快稀疏矩阵直接喂需先训练词向量或加载预训练模型内存占用高可解释性高可以查每个词权重低平均后难以定位关键特征对 MLP 的适配输入稀疏首层需要足够容量输入稠密网络更容易收敛如果源码用的是 TF-IDF那训练脚本里一定会把词表大小 max_features 写成可配置参数。我建议先别急着换 Word2Vec在 TF-IDF 基线上跑通全流程再决定。原因很现实TF-IDF 的可解释性对虚假新闻场景特别有价值你可以把测试集里权重最高的几十个词打印出来看看模型到底依赖哪些词这在答辩或项目报告里是很实用的素材。另外换向量化方案时不仅要改数据处理部分还要同步改 model 的输入维度一个改动牵动两个文件第一次上手容易漏。3. MLP 分类器怎么搭网络结构、损失函数与训练参数这一章讲源码里的 model.py 和 train.py。MLP 处理文本分类本质上就是把「向量化的特征」映射到「真假二分类的得分」上。模型本身不复杂但结构设计与训练策略里的细节决定了它是学到语义规律还是背下了训练集。3.1 网络结构输入维度、隐藏层数与神经元数量怎么定# model.py 网络定义典型实现 import torch.nn as nn class FakeNewsMLP(nn.Module): def __init__(self, input_dim, hidden_dim256, dropout0.3): super().__init__() # 三层全连接输入层 - 256 - 128 - 1 self.fc1 nn.Linear(input_dim, hidden_dim) # 入层到第一个隐藏层 self.fc2 nn.Linear(hidden_dim, hidden_dim // 2) # 256 - 128 self.fc3 nn.Linear(hidden_dim // 2, 1) # 输出 1 个标量 logit self.relu nn.ReLU() self.dropout nn.Dropout(dropout) def forward(self, x): # x 形状: (batch_size, input_dim) x self.relu(self.fc1(x)) # 先线性变换再激活 x self.dropout(x) # 训练时随机丢弃部分神经元 x self.relu(self.fc2(x)) x self.dropout(x) return self.fc3(x) # 不做 sigmoid交给损失函数处理逻辑说明网络有三层线性层。fc1 的 input_dim 必须等于向量化后的特征维度也就是 TF-IDF 的 max_featuresfc3 输出维度是 1对应「真实新闻」的原始得分 logit后面用 BCEWithLogitsLoss 计算损失这个损失函数把 Sigmoid 和交叉熵合并数值上比分开算更稳定。Dropout 放在激活之后、下一层之前是防止过拟合的主要手段训练时随机丢弃一部分神经元让网络不能依赖单一特征路径。参数说明hidden_dim256 是这类小型文本分类任务的常见起点。数据集几千到几万条新闻时256 个神经元足够表达非线性关系继续加宽收益有限。如果数据量只有两三千条把 hidden_dim 降到 128 更稳妥超过 512 在这个场景下几乎必然过拟合除非手里有数十万条新闻。改这个参数要同步确认模型保存和加载时用了同一份配置否则会出现权重形状不匹配的报错。3.2 训练策略优化器、学习率、Batch Size 与 Epoch# train.py 训练循环核心逻辑节选 def train(model, train_loader, val_loader, epochs20, lr1e-3): from torch.nn import BCEWithLogitsLoss criterion BCEWithLogitsLoss() # 二分类交叉熵内置 sigmoid optimizer torch.optim.Adam(model.parameters(), lrlr) scheduler torch.optim.lr_scheduler.StepLR(optimizer, step_size5, gamma0.5) for epoch in range(epochs): model.train() total_loss 0.0 for batch_x, batch_y in train_loader: optimizer.zero_grad() logits model(batch_x).squeeze(1) # 去掉多余维度 loss criterion(logits, batch_y) loss.backward() optimizer.step() total_loss loss.item() val_acc evaluate(model, val_loader) # 每轮结束做验证 scheduler.step() # 每 5 轮学习率减半 print(fEpoch {epoch1}/{epochs}, loss{total_loss:.4f}, val_acc{val_acc:.4f})逻辑说明这段代码把训练循环压缩成最典型的形式——清梯度、前向、算损失、反向、更新参数。值得注意两点一是 BCEWithLogitsLoss 和模型最后不做 Sigmoid 是配套的如果 forward 里加了 sigmoid这里必须换成 BCE loss否则梯度会不稳定loss 曲线会出现奇怪的震荡二是 StepLR 让学习率每 5 轮减半帮助模型在训练后期收敛到更平滑的局部最优直观感受是 loss 曲线前期快速下降后期缓慢压低。参数说明lr1e-3 是 Adam 在这个任务上的安全默认值高于 5e-3 大概率出现 loss 震荡甚至不收敛低于 1e-4 则收敛太慢。epochs20 对这个小模型够用第 8 到 15 轮之间模型基本收敛后面的轮次更多是观察过拟合拐点。batch_size 一般设为 32 或 64纯 CPU 环境跑 MLP 没有压力可以取 64 缩短单轮时间。3.3 评估指标准确率之外还必须看召回率和 F1虚假新闻检测最常见的坑是数据集里真实新闻远多于虚假新闻比如 90% 对 10%。这时模型只要全部预测成「真实」准确率就有 90%看起来非常优秀实际什么都没学会。源码的 evaluate 函数通常会算精确率、召回率、F1但往往只打印准确率。我在读这类源码时第一件事就是确认这几项指标有没有被算出来。# 评估函数源码里通常长这样 def evaluate(model, val_loader, threshold0.5): model.eval() y_true, y_prob [], [] with torch.no_grad(): # 推理阶段不需要梯度省内存加速 for batch_x, batch_y in val_loader: logits model(batch_x).squeeze(1) probs torch.sigmoid(logits) y_prob.extend(probs.tolist()) y_true.extend(batch_y.tolist()) preds [1 if p threshold else 0 for p in y_prob] acc sum(1 for p, y in zip(preds, y_true) if p y) / len(y_true) return acc逻辑说明no_grad 上下文管理器在推理阶段关闭梯度计算能省内存并提速接近一倍是个容易忽略的小优化。threshold0.5 是默认判定阈值但把它写成入口参数的意义在于类别不平衡时调高阈值可以让模型只在非常有把握时才判虚假提高精确率但也降低召回率调低阈值则相反能多捞回一些虚假新闻但会误伤真实新闻。参数说明阈值是精确率和召回率之间的滑动杆。我建议训练完模型后在验证集上把 threshold 从 0.3 到 0.8 各扫一遍画一条精确率-召回率曲线选一个业务上可接受的平衡点。这个分析放在项目报告里比单报一个准确率值有说服力得多也是源码最值得扩展的地方之一。4. 把项目跑起来环境配置、训练命令与最小推理验证源码再好跑不起来就等于零。这一章从环境准备讲起一直讲到拿一条新新闻做推理验证。整个流程走通一遍之后你对这个项目的掌控感会完全不同——从「看别人代码」变成「能自己改参数跑实验」。4.1 环境准备Python 版本、虚拟环境与依赖清单开始之前先确认本机 Python 版本。这类项目通常基于 Python 3.8 到 3.10 编写Python 3.11 以上运行 sklearn 和 torch 一般也没问题但个别依赖可能出现兼容性警告。不确定的话先跑python --version看一眼。# 在项目根目录下创建虚拟环境并激活 python -m venv venv source venv/bin/activate # Windows 下为 venv\Scripts\activate # 安装核心依赖对应 requirements.txt 的典型内容 pip install torch --index-url https://download.pytorch.org/whl/cpu pip install scikit-learn pandas jieba numpy逻辑说明venv 是 Python 自带的虚拟环境工具不需要额外安装。激活后项目依赖和系统全局环境就隔离开了避免不同项目之间 torch 和 numpy 版本互相干扰。jieba 只在中文场景下需要如果拿到的源码用的是英文数据集可以跳过安装。参数说明CPU 版 torch 对纯 MLP 来说完全够用——这个模型参数量通常只有几十万CPU 上训练一轮也就几十秒。如果源码按 GPU 训练编写而你的机器只有 CPU训练入口一般已留了 device 参数设为 cpu 即可。新手在配置环境时最常翻车的地方是跳过虚拟环境直接装包等到装第二个项目才发现依赖冲突那时候再回头拆分环境比一开始多花数倍时间。4.2 训练基线模型入口命令与参数清单这类项目的训练入口通常支持命令行参数覆盖默认配置。我把源码里最常见的可调参数整理成一张表参数默认值含义建议调整范围--data_path./data/news_train.csv训练数据路径指向实际数据集--max_features5000TF-IDF 词表大小2000~10000--epochs20训练轮数10~50--batch_size64每批样本数32~128--lr1e-3学习率1e-4~5e-3--hidden_dim256隐藏层神经元数128~512--dropout0.3随机丢弃比例0.2~0.5--val_ratio0.2切出验证集比例0.15~0.3# 最小可复现的训练命令 python src/train.py \ --data_path ./data/news_train.csv \ --max_features 5000 \ --epochs 20 \ --batch_size 64 \ --lr 1e-3 \ --hidden_dim 256 \ --dropout 0.3 \ --val_ratio 0.2逻辑说明train.py 通常先用 pandas 读 CSV拿到正文和标签两列然后调用 preprocess 里的 clean_text 和 jieba 分词接着用 TfidfVectorizer 做 fit_transform把原始文本一次性转成稀疏数值矩阵最后按 val_ratio 切出训练集和验证集交给 MLP 训练。训练过程中每一轮的 loss 和验证集准确率会打印在终端。跑完后模型权重和 TF-IDF 向量器被保存到 output 目录。参数说明第一次跑通时只动 --data_path 和 --epochs其他保持默认。这个组合是作者调过一轮的基线先把结果复现出来确认 loss 在下降、验证集指标合理再逐个调整 hidden_dim、max_features 等参数才能判断每个改动带来的影响。一次改三个以上参数出问题很难归因。如果你改读完数据后 validator 报错大概率是路径写错或 CSV 编码不对用file命令或直接打开文件确认 UTF-8 编码。4.3 最小推理验证拿一条没见过的新闻跑预测训练完成后拿一条训练集里没有的新闻做推理验证全流程是否真的闭合# predict.py 的核心逻辑节选 def predict(news_text: str, vectorizer, model, threshold0.5): # 1. 与训练阶段完全相同的清洗和分词流程 cleaned clean_text(news_text) tokenized .join(jieba.cut(cleaned)) # 2. 用训练时保存的 vectorizer 做 transform不是 fit_transform x vectorizer.transform([tokenized]).toarray() # 3. 转成张量过模型 import torch with torch.no_grad(): logit model(torch.tensor(x, dtypetorch.float32)).item() prob 1 / (1 math.exp(-logit)) # 手动做 sigmoid 还原概率 return {fake_prob: prob, label: fake if prob threshold else real}逻辑说明这段代码最关键的一行是vectorizer.transform而不是 fit_transform。训练时已经用 fit_transform 让词表学会了语料词汇分布推理时必须用同一个向量器做转换才能保证输入特征的列顺序和训练时完全一致——这是源码里出现频率极高的一个隐蔽坑我会在避坑章节专门展开。参数说明threshold 在推理阶段直接决定最终标签。如果场景更在意别漏掉虚假新闻比如舆情监测把它调低到 0.4如果更在意别误伤真实新闻比如前台内容展示调到 0.7。另外建议每次推理前打印一条 tokenized 后的文本确认新闻内容按预期被切分这习惯能帮你快速定位是预处理出了问题还是模型判断出了问题。5. 避坑指南从数据到训练的 5 个典型翻车点把项目跑通是一回事跑出可信的结果是另一回事。这一章我从实际经验里挑出五个高频问题每个都按「现象、原因、解决」的思路讲清楚。看完这些你至少能避开这个方向上八成常见的坑。5.1 文本清洗不彻底乱码、HTML 标签和全角符号现象训练到中途 loss 居高不下验证集准确率始终在 55%~60% 之间徘徊和瞎猜差不多。原因原始新闻文本里混了大量 HTML 标签、URL 链接、全角括号和中文引号。TF-IDF 把http、www、全角空格当成独立词统计出成百上千个高频「噪声特征」真正有语义的词反而被稀释。更隐蔽的是全角空格和半角空格混用同一个词被拆成两个 token特征彻底乱掉。解决清洗函数里用正则一次性过滤[^]的 HTML 标签、http\S的链接、Unicode 全角空格和零宽字符再把全角括号、引号统一替换成半角。清洗完成后随机抽 200 条文本打印出来肉眼检查确认没有乱码再进向量化。这一步决定了后续所有环节的上限偷懒省掉后面会成倍偿还。5.2 类别不平衡模型学成了「复读机」现象训练 loss 一路快速下降但验证集上精确率和召回率严重失衡——真实新闻召回率 0.98虚假新闻召回率只有 0.11。把预测结果打印出来发现模型把所有样本都判成了「真实」。原因数据集中真实新闻占比远高于虚假新闻常见课程数据是 8:2 甚至 9:1。交叉熵损失对多数类贡献更大模型发现全判多数类就能把 loss 压得很低于是学会了偷懒。解决两种常用方案。一是对少数类做上采样把虚假新闻样本复制几份补进训练集二是给少数类更高的损失权重PyTorch 里用 BCEWithLogitsLoss 的 pos_weight 参数sklearn 的 MLP 则可以用 class_weight。我一般先试 pos_weight 设为多数类数量除以少数类数量再用验证集观察 F1 是否回升。如果两类数量差距超过 10 倍还要考虑用 SMOTE 做合成样本但这时要小心过拟合。5.3 过拟合训练集逼近满分验证集不及格现象到第 15 轮左右训练集准确率 98%验证集只有 62%两条曲线之间有明显且持续扩大的差距。原因MLP 参数量相对数据集规模太大。词表 5000 维、隐藏层 256 个神经元、三层线性层对三四千条样本做到高拟合非常容易——模型记住了训练集里的噪声和个例而不是新闻文本的泛化规律。解决优先调整三个地方。hidden_dim 降到 128 或 64dropout 从 0.3 提到 0.5epochs 减到 12 并观察验证集拐点在哪一轮出现。仍不够就把 max_features 降到 3000减少无关稀疏特征。这套组合的本质是压缩模型容量迫使网络聚焦在少数学得动的强特征上。降维之后如果验证集指标反而上升不要意外这正是过拟合的典型特征。5.4 维度不匹配模型加载时报尺寸错误现象训练跑完predict.py 一运行就报类似mat1 and mat2 shapes cannot be multiplied (64x5000 and 3000x256)的错误。原因训练时 TfidfVectorizer 的 max_features 是 5000但推理时重新创建了一个 max_features3000 的向量器做 transform或者对新文本重新 fit_transform。两种错误都让输入向量的特征维度与模型第一层权重矩阵不一致。解决训练脚本把向量器序列化保存到 output/tfidf_vectorizer.pkl推理脚本只负责加载这个 pkl不要重新创建。检查源码里向量器是否和模型权重一起保存如果没保存补上这几行import joblib joblib.dump(vectorizer, output/tfidf_vectorizer.pkl) # 推理时 # vectorizer joblib.load(output/tfidf_vectorizer.pkl)逻辑说明推理时使用的必须是训练时同一个向量器的反序列化副本词表、列顺序、idf 权重全部耦合在对象里。重新 new 一个向量器哪怕参数完全一致词表也可能因为随机因素产生差异导致特征错位。这个报错信息其实已经把原因讲得很清楚了——耐心读一下形状不匹配的具体数字就能定位是哪一层出了问题。5.5 报告与源码脱节报告里写的结果代码里复现不出来现象项目报告写着「模型在测试集上准确率 91%可以有效识别虚假新闻」但源码里没有独立的测试集文件验证用的是从训练集随机切出来的 10%。原因很多交付型项目的报告是先写结论后补实验或者验证集和训练集同源同分布导致报告指标虚高。最极端的案例是数据预处理时先做了全局清洗再划分数据集测试集信息提前泄露到了训练过程指标好看但实际部署完全不是那么回事。解决拿到压缩包后先确认报告里的准确率是在哪份数据上计算的。如果只有一份数据自己在 train.py 里把 val_ratio 调大或改为按时间切分——有 publish_time 字段就按时间排序取最后 20% 做测试没有就按新闻来源分组切分。重新评估一遍看指标还剩多少。通常补上这个验证报告的说服力反而更强因为评审一眼就能看出你理解了评估的本质。6. 让检测器脱离「玩具阶段」一个低成本但有效的跨分布验证思路这个技巧是一个习惯训练完 MLP 后不要满足于随机切分的验证集指标专门准备一组「时间窗口之外」或「来源不同」的新闻做一次对抗验证。具体做法是——如果数据带时间戳按时间排序拿最后 20% 做测试集看指标掉多少没有时间字段就把数据按来源切分留出一两个来源的全部样本不参与训练和向量化拟合只用来测试。这个习惯背后的逻辑是互联网虚假新闻检测在实际部署时面对的是「未来」的新闻而训练集里只有「过去」的新闻。随机切分会把同源文本同时塞进训练集和测试集让指标虚高。我自己做过一个典型案例随机切分验证集准确率 0.91换用时间窗口评测直接掉到 0.74——后者才更接近模型上线后的真实表现。如果你拿到的源码和报告里没有这套评估我给你一个最小可行的改法在 train.py 数据读取之后加几行# 假设数据中有 publish_time 列按时间排序后切出最后 20% 做测试 df_sorted df.sort_values(publish_time) # 按发布时间排序 split_idx int(len(df_sorted) * 0.8) train_df df_sorted.iloc[:split_idx] # 前 80% 训练 test_df df_sorted.iloc[split_idx:] # 后 20% 测试 X_train vectorizer.fit_transform(train_df[text]) X_test vectorizer.transform(test_df[text]) # 坚持用 transform不重新拟合逻辑说明这个改动的核心是让测试集分布尽可能与训练集错开模型必须靠对语言本身的判断而非对数据集的记忆来做预测。没有时间字段时按新闻来源做 groupby 后分层切分思路一致。改动后测试集指标掉得厉害是常态别慌——这是大多数文本分类项目的真实水平也恰恰是项目报告里最有价值的一节分析。我在做这类项目时逐渐养成了一个习惯拿到任何一份源码先补上跨分布验证再谈模型效果。评估方法是被忽略最多却影响判断最大的环节——模型结构可以慢慢调但评估口径错了所有后续结论都会被带偏。这个习惯帮我避过不少因为指标虚高而产生的判断失误希望你也能用上。希望这篇能帮到你。本文还有配套的精品资源点击获取
返回列表