基于MLP的虚假新闻检测:从TF-IDF特征到Keras模型实现

发布时间:2026/9/15 2:19:08
基于MLP的虚假新闻检测:从TF-IDF特征到Keras模型实现 简介基于Python多层感知器MLP打造的虚假新闻检测器是一份完整的课程设计资源。项目面向机器学习与自然语言处理入门者系统演示了从新闻文本清洗、分词、TF-IDF向量化到MLP模型构建、训练、评估与调优的完整流程能帮助读者直观理解神经网络在文本分类任务中的实际应用。资源包共19个文件大小约66.23MB包含Python训练脚本、模型权重文件、数据集文件、项目配置文件、实验报告文档及说明文档等压缩包内还附有训练好的模型和预测结果文本可直接运行脚本复现实验并结合报告深入理解每个步骤。目前已有227人学习借鉴内容覆盖数据预处理、特征工程、模型评估与扩展思路既适合作为课程设计与毕业设计的参考资料也适合新手作为第一个完整的深度学习小项目。1. 用 MLP 做虚假新闻检测其实是在解决一个文本信任问题刷资讯类应用时标题耸动、正文经不起推敲的“新闻”层出不穷。人工逐条核验成本太高基于规则的敏感词拦截又容易误伤正常内容。把这个问题归类到文本分类里就是一个标准的二分类任务给定一条新闻文本判断它是可信新闻还是虚假新闻。本文讨论的基于 Python MLP 实现的方案核心思路是先用 TF-IDF 把新闻文本转成固定长度的数值向量再交给一个两到三层的多层感知机做分类。这个方案不依赖大规模算力单机 CPU 就能完成训练和推理适合做原型验证、内容审核前置过滤也适合作为更复杂模型的基线对照。接下来我会给出从数据清洗、特征工程到模型训练、调参和验证的完整路径。2. 虚假新闻检测器的数据准备与 TF-IDF 特征工程2.1 标注数据从哪来先跑通流程再考虑规模网上公开的虚假新闻数据集大体分两类一类是英文的 FakeNewsNet、LIAR 等字段通常包含标题、正文和真实性标签另一类是中文的微博谣言数据集结构类似。实践中真正的问题往往是数据量不够、标签偏斜而不是算法选型。我一般建议先找一份规模适中的二分类文本数据跑通全流程再把模型迁移到自己的业务数据上。如果手头连一份像样的标注数据都没有可以先用一小批人工标注的新闻标题加正文构造最小可用集合先把特征工程和训练流程验证起来。下面用一个内联构造的小样本集合演示流程字段包括title、text和label其中 label 为 1 表示虚假新闻0 表示可信新闻。2.2 文本清洗与字段拼接规范化是特征工程的第一步原始新闻文本里有大量干扰信息HTML 标签、URL、多余空白、特殊符号以及中英文混排时的格式错乱。清洗逻辑不宜过度去掉非文本元素即可不要连标点都删光因为后续 TF-IDF 会自行处理词频统计。除了清洗还需要把标题和正文拼成一个字段标题的词往往比正文更具判别力直接拼在一起可以避免单独处理标题带来的额外复杂度。import pandas as pd import re df pd.DataFrame({ title: [ 紧急某地发生严重事故, 专家称睡眠不足影响健康, 震惊这种食物吃了致癌, ], text: [ 据现场目击者称事故发生在今天凌晨……, 研究数据显示长期睡眠不足与多种疾病相关……, 网传消息称该食物含有致癌物质专家已辟谣……, ], label: [1, 0, 1], }) # 字符串类型归一化避免 mixed type 警告 df[label] df[label].astype(int) def clean_text(s: str) - str: if not isinstance(s, str): return s re.sub(r[^], , s) # 去 HTML 标签 s re.sub(rhttp\S, , s) # 去 URL s re.sub(r\s, , s).strip() # 压缩空白 return s df[title] df[title].map(clean_text) df[text] df[text].map(clean_text) df[content] df[title] 。 df[text]这段代码里有几个值得注意的参数和写法.astype(int)是 Python 类型转换的一个典型应用业界经常在拿到字符串形式的标签后忘记做这一步导致后续 sklearn 报标签类型错误re.sub(r[^], , s)对 HTML 实体并不生效如果需要处理amp;这类实体要再补一层html.unescape。清洗完成后content字段就是后续特征提取的输入。2.3 用 TF-IDF 把新闻文本变成 MLP 能处理的固定长度向量MLP 接收的是定长数值输入不能直接吃字符串。常见做法是使用 TF-IDF 向量化它可以同时完成分词、词频统计和逆文档频率加权三个工作。参数配置直接影响向量维度与稀疏程度需要结合语料规模调整。from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.model_selection import train_test_split X_raw df[content].tolist() y df[label].to_numpy() X_train_raw, X_val_raw, y_train, y_val train_test_split( X_raw, y, test_size0.2, random_state42, stratifyy ) vectorizer TfidfVectorizer( max_features512, # 只保留词频最高的 512 维特征 ngram_range(1, 2), # 一元词 二元词组 min_df1, # 文档频率下限数据量大时可设为 2~3 sublinear_tfTrue, # 用 1log(tf) 平滑词频 ) X_train vectorizer.fit_transform(X_train_raw) X_val vectorizer.transform(X_val_raw)关于参数说明max_features512控制了特征维度MLP 的输入层宽度就由它决定。维度不是越大越好词表膨胀到几万维时MLP 第一层的参数量会跟着爆炸在小数据集上很容易过拟合。ngram_range(1, 2)让模型能看到“不实消息”这类二元词组比只统计单个词更能捕捉否定和修饰关系。sublinear_tfTrue是一个容易被忽略的参数它把原始词频取对数弱化高频词对特征的支配地位。需要强调的是fit_transform只能用在训练集上验证集和测试集一律用transform确保验证阶段看到的特征空间与训练阶段完全一致。中文文本如果分词效果不理想可以在向量化之前先经 jieba 分词英文数据集则不需要。3. 用 Keras 搭建用于真假新闻分类的 MLP 模型3.1 为什么选 MLP 而不是直接上 Transformer面对虚假新闻检测任务很多人的第一反应是微调 BERT 或直接调用大模型 API。但在实际工程中MLP 在这个场景里依然有明确的适用位置。首先标注数据在大多数场景下只有几千到几万条深度学习模型在这种规模下很难发挥优势MLP 反而因为参数少、归纳偏置简单而更稳其次MLP 推理速度极快CPU 下单条样本的预测延迟在毫秒级适合做内容审核链路里的第一道粗筛把置信度低的样本再转给重模型最后MLP 的权重可以直接导出为矩阵文件部署形态非常灵活甚至不需要依赖 TensorFlow Serving 这类重型组件。它的短板在于无法捕捉词序信息但 TF-IDF 引入的二元词组部分弥补了这个缺陷。3.2 模型结构从输入层到输出层的参数设计这里给出一个可直接运行的模型定义。输入维度与vectorizer.max_features保持一致隐藏层用两个全连接层中间插入 Dropout 做正则化输出层用单神经元加 sigmoid 激活函数。import tensorflow as tf from tensorflow.keras import Sequential from tensorflow.keras.layers import Dense, Dropout model Sequential([ Dense(256, activationrelu, input_shape(512,)), Dropout(0.3), Dense(128, activationrelu), Dropout(0.3), Dense(1, activationsigmoid), ]) model.summary()模型参数的选取逻辑第一层 256 个神经元对应 512 维输入实际上是在做一次降维把稀疏的 TF-IDF 特征压缩成稠密表示。relu激活函数计算开销小且能缓解梯度消失但要注意 ReLU 会让部分神经元死亡如果训练过程中发现大量神经元输出恒为 0可以把学习率调低。Dropout 层的位置放在激活函数之后、下一层之前0.3表示每次训练迭代随机丢弃 30% 的神经元连接。输出层的 sigmoid 将输出值压缩到 01 之间作为模型预测为虚假新闻的概率。3.3 编译配置损失函数、优化器与评估指标编译阶段需要明确三件事用什么损失函数、用什么优化器、用什么指标观察训练过程。二分类问题对应binary_crossentropy损失优化器选择 Adam它对学习率的敏感度低于 SGD新手不容易把训练跑飞评估指标在准确率之外一定要加上精确率和召回率因为虚假新闻数据集的标签往往不平衡准确率会产生很强的误导性。model.compile( optimizertf.keras.optimizers.Adam(learning_rate1e-3), lossbinary_crossentropy, metrics[ accuracy, tf.keras.metrics.Precision(nameprecision), tf.keras.metrics.Recall(namerecall), ], )这里的learning_rate1e-3是 Adam 的常用起点值。如果训练日志里 loss 在前几个 epoch 内不降反升优先把学习率降到3e-4或1e-4如果损失下降特别慢可以适当调大到3e-3但要注意观察验证集指标是否同步改善。精确率和召回率作为编译指标的意义在于它们在训练过程中就能直观反映模型是否偏向多数类而不是等训练结束后才通过classification_report发现模型失效。4. 训练 MLP 模型时容易忽视的参数与过拟合控制4.1 BatchSize、Epochs 与学习率的联动关系训练参数不是孤立存在的batch_size影响梯度估计的稳定性epochs影响训练时长学习率影响每一步更新的步长。伪新闻检测场景下数据量通常不大batch_size设为 32 或 64 即可过大的 batch 会显著增加单次迭代的内存占用且在小数据集上容易收敛到平坦的局部最优点。下面给出一个带早停和模型保存的训练配置这也是我在实际项目中默认使用的模板。from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint callbacks [ EarlyStopping( monitorval_loss, patience5, restore_best_weightsTrue, ), ModelCheckpoint( fakenews_mlp.keras, monitorval_loss, save_best_onlyTrue, ), ] history model.fit( X_train.toarray(), y_train, validation_data(X_val.toarray(), y_val), epochs50, batch_size32, callbackscallbacks, verbose1, )这段代码中有两个细节值得说明。EarlyStopping的patience5表示验证集损失连续 5 个 epoch 没有改善就停止训练restore_best_weightsTrue会在停止时自动回滚到验证集表现最好的权重避免保存训练后期已经过拟合的参数。ModelCheckpoint只保存验证集 loss 最低的模型文件这样磁盘上永远只有一个最优版本。训练时要把 TF-IDF 的稀疏矩阵转成稠密数组因为 Keras 的 Dense 层不接受稀疏输入这一步会消耗一定内存特征维度控制在 5122048 之间时基本没有压力。4.2 处理类别不平衡class_weight 的正确用法虚假新闻标注数据的典型问题是负样本远多于正样本。如果直接训练模型会倾向于把所有样本预测为多数类因为这样就能获得很高的准确率。此时 precision 和 recall 两个指标会暴露问题精确率可能很高但召回率极低模型实际上漏掉了大部分假新闻。解决办法是在训练时给少数类更高的权重让模型对少数类的误分类付出更大代价。import numpy as np from sklearn.utils.class_weight import compute_class_weight classes np.array([0, 1]) weights compute_class_weight( class_weightbalanced, classesclasses, yy_train, ) class_weight {0: weights[0], 1: weights[1]} print(class_weight)compute_class_weight会按照样本数量的反比自动计算权重。例如真实新闻 800 条、虚假新闻 200 条时最终得到的权重大约是{0: 0.625, 1: 2.5}意味着模型每把一个虚假新闻样本判错产生的损失相当于错判 2.5 个真实新闻。这个参数在model.fit里通过class_weightclass_weight传入即可。注意不要在验证集上使用同样权重验证集的评估必须保持原始分布才能真实反映上线后的表现。4.3 从训练曲线判断过拟合的临界点训练过程中真正要盯的是训练 loss 和验证 loss 的差值。训练 loss 持续下降、验证 loss 先降后升这就是过拟合的经典信号且转折点通常出现在训练后期。应对手段按优先级排序先看早停是否已经触发如果没有就加大patience并不解决问题正确的做法是增加 Dropout 比例、减小网络宽度或降低特征维度。Dropout 从 0.3 提到 0.5 常常能带来明显改善但代价是训练收敛变慢。另一个常见手段是正则化权重衰减Keras 中可以通过Dense(..., kernel_regularizertf.keras.regularizers.l2(1e-4))实现它对 TF-IDF 这种高维稀疏输入的效果不如 Dropout 明显通常只用其中一种即可。4.4 训练失败的排查顺序先看数据再看代码模型训练不走运时排查顺序比排查方法更重要。第一步看 loss 是否下降如果 loss 完全不动多半是学习率过大或梯度爆炸把学习率降到1e-4试试。第二步看预测分布如果所有样本的输出都集中在 0.5 附近说明特征与标签之间缺乏有效关联这时检查 TF-IDF 的min_df是否设得过高把大量有效词都滤掉了。第三步看数据泄漏检查train_test_split之前有没有对全量数据做过fit_transform这是个高频错误特征是整个数据集共同拟合出来的验证集的指标会虚高上线后立刻被打回原形。5. 验证检测器效果混淆矩阵、阈值校准与对抗改写自检5.1 混淆矩阵才是虚假新闻检测器的及格线准确率在类别不平衡场景下没有参考价值真正要看的是混淆矩阵的四象限真正例、假正例、真负例、假负例。假负例意味着把假新闻放过去了这是内容审核场景最不能接受的失误假正例则是误伤真实新闻同样会损害用户体验。用 sklearn 可以快速输出这两个维度的量化结果。from sklearn.metrics import confusion_matrix, classification_report y_proba model.predict(X_val.toarray()).ravel() y_pred (y_proba 0.5).astype(int) print(confusion_matrix(y_val, y_pred)) print(classification_report(y_val, y_pred, target_names[real, fake]))y_proba取的是模型输出的连续概率值(y_proba 0.5).astype(int)是默认的二值化方式。classification_report 会输出 precision、recall、f1-score 和 support其中 f1-score 是精确率和召回率的调和平均在类别不平衡场景下f1-score 比准确率更能代表模型真实水平。5.2 按业务场景校准决策阈值宁可错杀还是宁缺毋滥默认的 0.5 阈值只适用于正负样本分布均衡的场景。在实际业务里检测器的阈值应当根据成本结构来确定如果漏放一条假新闻造成的舆论风险高于误伤一条真实新闻就应当降低阈值让更多低置信度样本被拦截如果误伤真实新闻会导致用户投诉和业务流失就应当提高阈值。阈值校准可以基于验证集精确率和召回率的变动趋势来操作。from sklearn.metrics import precision_recall_curve precision, recall, thresholds precision_recall_curve(y_val, y_proba) f1_scores 2 * precision * recall / (precision recall 1e-9) best_idx np.argmax(f1_scores[:-1]) best_threshold thresholds[best_idx] print(fbest threshold: {best_threshold:.3f}) print(fprecision: {precision[best_idx]:.3f}, recall: {recall[best_idx]:.3f})这里用precision_recall_curve计算出每个可能阈值下的精确率和召回率再找出 F1 最高的点作为参考阈值。注意f1_scores计算时加了1e-9防止除零且thresholds的长度比precision少一个所以索引要截取到[:-1]。实际部署时我会把这个校准过程做成一个配置文件把阈值、召回偏好和业务挂钩而不是让模型工程师拍脑袋写死一个值。5.3 对抗改写自检换一种说法模型还认得出来吗最后一个值得养成的习惯是编写一个轻量的对抗改写自检。上述“shock! 紧急震惊”等典型虚假新闻套话前缀拼接到真实新闻上观察模型预测概率是否发生明显偏移。如果模型对此类改写不敏感说明它真正学到了内容语义特征如果概率飙升到虚假区间说明模型实际是在按标题句式做判断而不是按内容真实性做判断这类模型上线后很容易被绕过。test_texts [ 专家称睡眠不足影响健康, 震惊专家紧急警告睡眠不足竟然会导致这种后果, ] for text in test_texts: vec vectorizer.transform([text]) proba model.predict(vec.toarray(), verbose0).ravel()[0] print(f{proba:.3f} - {text})如果第二条改写文本的预测概率显著高于第一条就要回到数据层面检查训练集里的正样本是否大多集中带有此类夸张句式考虑补充句式风格更多样化的样本或是在数据清洗阶段保留标题特征的同时避免模型过度依赖单个高频词。这种自检脚本体积不大但对提升检测器的鲁棒性非常有帮助也应当纳入每次模型迭代的回归测试流程中。本文还有配套的精品资源点击获取