ARTICLE DETAIL

资讯详情

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

基于MLP与TF-IDF的虚假新闻检测:从特征工程到分类器调优实战

基于MLP与TF-IDF的虚假新闻检测:从特征工程到分类器调优实战 简介这是一份基于Python与多层感知机MLP构建的互联网虚假新闻检测项目包含完整源码与配套项目报告。项目使用scikit-learn中的MLP分类器配合jieba分词和停用词过滤覆盖中文文本预处理、特征表示、模型训练与预测的完整流程既适合自然语言处理方向的课程设计、毕业设计也便于初学者系统理解MLP在文本分类中的实际应用。压缩包内共有21个文件以Python源码、模型权重、预处理后的pkl数据及csv原始数据集为主同时附有项目报告、停用词表、说明文档等整体约91.14MB。资源内部按训练、预处理、优化、预测等模块组织目录结构清晰便于定位与二次开发。目前已吸引134人学习浏览可直接复现从数据预处理到最终预测的完整实验项目报告对实验方案、调参过程和结果分析也有详细阐述可作为虚假新闻检测课题的成体系参考方案。1. 基于Python和MLP的互联网虚假新闻检测器不调BERT也能在30分钟内跑通一个可用的分类器看到「源码项目报告」这个组合基本可以判断这是一个面向课程设计、毕业设计或入门NLP实践的小型项目。它的核心任务很明确把一篇新闻文本输入模型模型输出它是真实新闻还是虚假新闻。技术选型是MLP多层感知机没有上BERT、RoBERTa这类预训练大模型。这个选择在工程上其实很务实——MLP训练快、显存要求低、代码量小在一台普通笔记本上就能完成训练和推理。如果你正在找一个人工智能方向的实战项目或者想快速理解文本分类的完整链路这个项目会是一个很好的起点。它的价值不在于刷榜而在于让你在短时间内跑通「数据清洗 → 特征工程 → 模型训练 → 评估报告」全流程。接下来我会按这个项目最常见的实现路径把每一步拆开讲清楚包括参数怎么调、代码怎么写、哪些坑必须躲开。2. 先搞清楚地基MLP为什么适合做虚假新闻检测数据怎么准备2.1 MLP与BP-ANN的关系它不是黑匣子只是把特征映射到标签MLPMulti-Layer Perceptron是神经网络最经典的形态结构上就是输入层、若干隐藏层、输出层的全连接堆叠。很多人看到「MLP和BP-ANN是什么关系」这个问题会懵其实两者本质是同一件事的两种称呼——BP-ANN反向传播人工神经网络强调的是训练算法即用误差反向传播来更新权重MLP强调的是网络结构即多层感知机的层叠方式。一个MLP默认就是用BP算法来训练的所以这两个词在绝大多数场景下可以互换使用。虚假新闻检测对MLP来说是一个标准的文本分类任务。文本本身不能被模型直接读取需要先转换成数值向量。MLP做的就是学习从向量到类别的映射关系。它的局限性也很清楚MLP没有序列建模能力不会像LSTM那样感知词与词的前后顺序也不像Transformer那样计算全局注意力。所以在做这个项目时决定效果上限的不是MLP本身而是你喂给它的特征好不好。这就是为什么后续的特征工程TF-IDF或词向量比模型结构更值得花时间。MLP的优势在于它的容错性和易用性。网络结构设计只需要决定两个关键变量隐藏层神经元数量和层数。神经元数量太少模型欠拟合训练集上的准确率就上不去神经元数量太多模型过拟合测试集表现会明显下滑。对于新闻文本这种高维稀疏输入一个经验做法是隐藏层用(256, 128)或者(512, 256)这样的递减结构——第一层大一点接收特征后续层逐步压缩抽象。隐藏层激活函数直接用ReLU输出层用softmax做二分类概率输出。2.2 数据集与预处理从原始文本到干净的训练样本这个项目的训练数据来源常见选择是Kaggle的Fake News数据集或国内的新闻分类数据集。如果你是自己收集数据一个重要的原则是类别平衡——虚假新闻和真实新闻的比例不应该偏差太大。二分类任务中如果类别比例到了9:1模型会倾向于全部预测为多数类准确率看着很高但实际没有任何应用价值。遇到这种场景一个简单的处理办法是欠采样或对少数类做加权比如在class_weight参数里给少数类设高权重。数据清洗的第一步是去噪。新闻文本里常见的噪声包括HTML标签p、div等爬虫采集时常混入URL链接http://...特殊符号和数字对MLP来说这些都可能变成干扰特征重复空白、换行符清洗阶段的代码看起来简单但正则表达式写不好就会误伤正文内容。我一般的做法是分段处理先处理掉最明显的标签和链接再做标点和数字的处理。清洗完成后需要进行分词。英文文本和中文文本的差异在这一步会体现得很明显英文直接按空格切分中文则需要用jieba分词。这两个选择的产出形式是一致的——一个以空格分隔的词语序列这个序列将直接用于后续的TF-IDF向量化。import re import jieba def clean_text(text: str) - str: 清洗原始新闻文本 1. 去掉HTML标签 2. 去掉URL 3. 丢弃非中英文字符保留中文、英文、数字和基本标点 if not isinstance(text, str): return text re.sub(r[^], , text) # 去HTML标签 text re.sub(rhttp\S|https\S, , text) # 去URL text re.sub(r[^\u4e00-\u9fa5a-zA-Z0-9\s], , text) # 去特殊符号 text re.sub(r\s, , text).strip() # 合并空白 return text def tokenize(text: str, use_jieba: bool True) - list: 对清洗后的文本做分词。 中文场景使用jieba英文场景直接split。 if use_jieba: return [w for w in jieba.cut(text) if w.strip()] return text.split() text 美国科学家宣布研发出新型疫苗效果显著。 cleaned clean_text(text) tokens tokenize(cleaned, use_jiebaTrue) print(tokens) # [美国, 科学家, 宣布, 研发, 新型, 疫苗, 效果, 显著]清洗和分词的逻辑需要分别讲清楚。clean_text中的正则[^\u4e00-\u9fa5a-zA-Z0-9\s]匹配的是所有非中文、英文、数字、空格的字符这一步会把标点符号、表情符号、货币符号等全部剔除。这个设计的取舍在于TF-IDF向量化阶段不需要标点作为特征保留标点只会增加特征维度且引入噪声。tokenize函数的use_jieba参数是为了应对中英文混合场景如果你的数据是英文新闻关闭这个参数可以避免不必要的分词延迟。2.3 TF-IDF特征构建MLP吃的是特征矩阵不是文本把文本变成向量这是整个项目最关键的环节。常见的选择有TF-IDF、Word2Vec、以及从MLP角度看的词袋模型Bag of Words。对于虚假新闻检测来说TF-IDF是性价比最高的方案原因是它考虑了一个词在多少篇文档中出现过能过滤掉「的」「了」「是」这类高频但对分类没有区分度的停用词。Word2Vec虽然能捕获词之间的语义关系但需要海量语料预训练而且生成的平均词向量会让高频词主导语义用在短文本上效果不如TF-IDF稳定。from sklearn.feature_extraction.text import TfidfVectorizer vectorizer TfidfVectorizer( max_features10000, # 只保留词频最高的1万个词控制维度 ngram_range(1, 2), # 考虑单个词和双词组合 stop_wordsNone, # 中文停用词表需要自行传入 min_df3 # 至少在3篇文档中出现过的词才保留 ) X_train_tfidf vectorizer.fit_transform(corpus_train) X_test_tfidf vectorizer.transform(corpus_test)这里的max_features参数决定了特征矩阵的维度MLP在输入层会对应这个维度的神经元数量。10000是个折中值——太少会丢失长尾信息太多会拖慢训练速度且让模型更容易过拟合。ngram_range(1, 2)表示同时使用单个词和相邻双词作为特征双词组合能捕获一些短语信息比如「假消息」「紧急通知」这类连续搭配在单字词特征下会被拆散。min_df3是一个去低频词的操作只在1-2篇文档中出现过的词通常是拼写错误或个人用语习惯对泛化没有帮助去掉还能降低过拟合风险。需要注意的是fit_transform在训练集上执行transform在测试集上执行绝对不能对测试集单独fit否则特征空间的分布就会不一致。3. 从零写MLP检测器这里有一条可以直接跑通的完整源码路径3.1 模型结构定义PyTorch实现两层MLP分类器在框架选择上这个项目可以用scikit-learn自带的MLPClassifier也可以手动用PyTorch或TensorFlow构建。两者的差别在于可控性和学习效果。用MLPClassifier三行代码就能完成训练适合只想快速出结果的情况但如果你需要写进项目报告用PyTorch实现能让整个模型结构、前向传播、损失计算都展示得更清晰。我在这篇笔记里给出PyTorch的实现方式因为它在调参时更直观——你想改哪个参数就知道去改代码的哪个位置。import torch import torch.nn as nn import torch.optim as optim from torch.utils.data import DataLoader, TensorDataset class FakeNewsMLP(nn.Module): def __init__(self, input_dim: int, hidden_dims: list [256, 128], dropout: float 0.3): super(FakeNewsMLP, self).__init__() layers [] prev_dim input_dim for hidden_dim in hidden_dims: layers.append(nn.Linear(prev_dim, hidden_dim)) layers.append(nn.ReLU()) layers.append(nn.Dropout(dropout)) prev_dim hidden_dim layers.append(nn.Linear(prev_dim, 2)) # 二分类输出 self.net nn.Sequential(*layers) def forward(self, x): return self.net(x) # 假设X_train_tfidf是稀疏矩阵需要转换为密集张量 X_train_dense torch.tensor(X_train_tfidf.toarray(), dtypetorch.float32) y_train_tensor torch.tensor(y_train, dtypetorch.long) dataset TensorDataset(X_train_dense, y_train_tensor) dataloader DataLoader(dataset, batch_size64, shuffleTrue)模型结构中最关键的设计是Dropout层。TF-IDF特征矩阵是高维稀疏的维度可能达到上万而样本数量可能只有几千条这意味着模型容量远大于数据信息量极易过拟合。dropout0.3的含义是每次前向传播时随机丢弃30%的神经元输出这相当于在训练时同时训练了多个不同的子网络推理时再平均它们的输出能显著提升泛化性能。隐藏层[256, 128]的选择遵循递减原则——输入维度大第一层往256压缩第二层再往128压缩最后输出到2个类别。batch_size64是一个常用值太大容易显存溢出且梯度下降不够随机太小则训练不稳定。3.2 训练循环与评估指标你不能只看准确率训练循环的核心内容就只有三件事前向计算损失、反向传播梯度、更新权重。但评估指标的选择却大有讲究。在虚假新闻检测这个任务上准确率Accuracy是最有欺骗性的指标——如果数据集中90%是真实新闻你什么都不做全部预测为真实新闻准确率就是90%。所以至少要同时看精确率Precision、召回率Recall和F1-Score。精确率关注的是「你预测的虚假新闻中有多少是真的虚假新闻」这决定了用户被误导的程度召回率关注的是「真正的虚假新闻有多少被你抓出来了」这决定了检测系统的覆盖面。两者在工程上存在天然的矛盾——阈值调高精确率上升但召回率下降阈值调低则反过来。F1-Score是两者的调和平均值用于找一个平衡点。from sklearn.metrics import classification_report, confusion_matrix criterion nn.CrossEntropyLoss() optimizer optim.Adam(model.parameters(), lr1e-3) epochs 20 for epoch in range(epochs): model.train() total_loss 0.0 for X_batch, y_batch in dataloader: optimizer.zero_grad() outputs model(X_batch) loss criterion(outputs, y_batch) loss.backward() optimizer.step() total_loss loss.item() print(fEpoch {epoch1}/{epochs}, Loss: {total_loss/len(dataloader):.4f}) # 评估阶段 model.eval() with torch.no_grad(): y_pred_logits model(X_test_tfidf_dense) y_pred torch.argmax(y_pred_logits, dim1).numpy() print(classification_report(y_test, y_pred, target_names[real, fake]))训练阶段和评估阶段的一个重要区别是model.train()和model.eval()的切换。train()模式下Dropout层生效会随机丢弃神经元eval()模式下Dropout层被关闭所有神经元都参与计算。很多人第一次跑代码时会忘记切换模式导致评估时模型表现不稳定——这次推理的结果和下次不一样。这里用CrossEntropyLoss作为损失函数它内部集成了softmax计算所以输出层不需要额外加nn.Softmax。optimizer.Adam的学习率设为1e-3是经验值过大1e-2以上会导致损失值震荡过小1e-5以下则收敛极慢。3.3 训练曲线解读Loss下降但准确率不动是怎么回事训练过程中你可能会看到这样几种「翻车」现象。第一种是损失值持续下降但验证集准确率没有提升这通常意味着模型在过拟合——它记住了训练集中的噪声和细节但没有学到可泛化的规律。解决方法优先考虑增大dropout值到0.5或者减小隐藏层神经元数量。第二种是损失值在某个数值附近震荡不升不降这大概率是学习率设置不合适把lr从1e-3降到1e-4再试。第三种是损失值直接变成NaN说明梯度爆炸检查一下输入数据是否包含无穷大或异常值TF-IDF矩阵一般不会有这个问题但如果自己做了归一化就可能踩到。4. 把弱分类器变成可用的检测系统特征优化与阈值调参4.1 特征层面的三次优化尝试从词频到卡方检验TF-IDF向量化完成了文本到数值的转换但原始特征矩阵对上万维的特征维度对MLP来说仍然是个负担。优化特征的目标不是减少信息量而是滤除噪声维度。我在这里按性价比从高到低列出三种优化手段实际项目里不需要全部做做前两步就足够看到明显的指标提升。第一步是卡方检验Chi-Square特征选择。它的工作原理是计算每个词与类别标签之间的独立性卡方值越大说明该词与类别的关联越强。sklearn的SelectKBest配合chi2可以保留最相关的K个特征这里K可以设2000到5000之间。要注意的是卡方检验只适用于非负特征矩阵TF-IDF恰好满足这个条件。第二步是调整ngram_range。单从词unigram可能忽略一些关键短语比如「官方辟谣」「紧急通知」这种双词组合。但盲目增加到trigram会让特征维度爆炸式增长而且会引入大量低频组合反而稀释有效特征。一个比较稳的做法是先用ngram_range(1, 1)跑一个基线再加到(1, 2)对比效果。如果F1提升在1个点以上就保留否则改回去。第三步是类别特征加权。虚假新闻的语言风格往往包含情绪强烈的词比如「震惊」「必看」「速转」。可以用TfidfVectorizer的sublinear_tfTrue参数对词频做对数缩放防止某个词在一篇长文中出现次数过多导致权重虚高。4.2 阈值移动默认的0.5分类边界可能不是最优解softmax输出的两个概率值虚假新闻概率和真实新闻概率之和为1模型默认取概率较大的一方作为预测结果这等价于把分类阈值设在0.5。但实际场景中把一条虚假新闻判成真实新闻漏报和把一条真实新闻判成虚假新闻误报的代价是完全不同的。漏报意味着用户可能被谣言误导后果更严重误报则只会让平台多一次人工复核。这种情况下可以把预测阈值从0.5向下调低到0.4甚至0.3。这意味着只要模型认为一条新闻有30%以上的概率是虚假的就触发人工审核。代价是误报增多但整体召回率会明显上升。这种调优方法在工程上叫做「阈值移动」不需要重新训练模型只改变推理时的判断逻辑。def predict_with_threshold(logits: torch.Tensor, threshold: float 0.4) - list: 将阈值从默认0.5下调至0.4提升对虚假新闻的召回率。 返回值为1表示判定为虚假新闻需要审核。 prob_fake torch.softmax(logits, dim1)[:, 1] # 取类别1虚假新闻的概率 return (prob_fake threshold).long().numpy() # 使用示例 y_pred_adjusted predict_with_threshold(y_pred_logits, threshold0.4) print(confusion_matrix(y_test, y_pred_adjusted))prob_fake取的是softmax输出中索引为1的那一列这要求训练时把「虚假新闻」类别的标签编码为1真实新闻编码为0。threshold0.4并非固定值你可以用0.35、0.45分别跑一遍评估看哪个阈值下F1分数最高这就是一份很好的调参记录材料项目报告里可以画一条「阈值-F1」曲线来展示调优过程。4.3 标签不平衡的补救手段class_weight和过采样如果数据集本身类别比例不均衡比如虚假新闻只占20%模型会倾向把大部分样本预测为多数类。处理这个问题的两个常见办法是类别加权和过采样。前者在损失函数层面给少数类的损失乘以一个系数让模型对少数类的错分更敏感后者在数据层面用SMOTE等方法复制少数类样本让数据分布趋于均衡。这个项目的报告部分如果写到这里加分效果会比单纯堆模型结构好很多。5. 避坑指南这个项目最常见的5个踩坑记录5.1 训练集和测试集的特征空间不一致现象模型在训练集上准确率达到98%测试集上只有60%多。原因对训练集和测试集分别调用了fit_transform导致测试集的特征矩阵与训练集的特征维度不匹配向量映射关系错乱。解决训练集只用一次fit_transform测试集只调用transform。这背后是一个很关键的原则——测试集在模型训练完成之前必须保持完全不可见。5.2 TF-IDF矩阵转稠密后内存溢出现象代码运行到.toarray()时报MemoryError笔记本风扇狂转然后程序崩溃。原因10000维特征矩阵乘上几万条样本稠密矩阵需要占用10000 * 样本数 * 4字节的内存。5万条样本就是2GB加上训练过程中其他变量的占用内存撑不住。解决控制max_features到5000以内或者用batch_size分批转换不要一次性把全量数据转成稠密矩阵。5.3 中文数据没加停用词表现象混淆矩阵显示模型把大量文本预测为同一类别精确率很低。原因中文的「的」「了」「在」「是」等虚词词频极高在TF-IDF中没有被过滤抢占了大量特征权重。英文的stop_words参数默认可用英文停用词但对中文不生效。解决准备一个中文停用词表通常是几百行的txt文件传入TfidfVectorizer的stop_words参数。这个表可以在常见的NLP开源仓库里找到也可以自己按词频统计后人工筛选。5.4 模型训练时开启Dropout评估时忘记关闭现象模型预测结果不稳定同一篇新闻每次运行得到的分类结果不一样。原因model.eval()没有调用Dropout层在推理时仍然随机丢弃神经元模型输出带有随机性。解决评估前务必调用model.eval()如果想确认这一点可以在评估代码前后加一行打印当前模型状态。5.5 二分类标签编码顺序搞反现象精确率、召回率、F1分数的数值看起来正常但混淆矩阵显示真实新闻被大量识别为虚假新闻类名和预测结果对不上。原因classification_report默认按标签升序排列0对应的是real还是fake取决于你训练时的编码方式。如果标签编码为「真实1虚假0」而target_names参数传入的顺序是[real, fake]报告就会错位。解决训练前确定编码规则推荐真实0虚假1在classification_report中的target_names按数字升序传入。6. 再往前走一步从MLP到泛化能力更好的检测模型以及如何验证你的改进有效MLP这个方案做到这里已经是一个完整的闭环了——数据清洗、特征提取、模型训练、评估报告四件套齐活。如果要在这个基础上让项目报告更有含金量有三个方向可以延伸。第一个方向是特征层级的升级把TF-IDF替换成预训练的词向量比如百度的paddle-embedding或腾讯的词向量喂给MLP。这会让模型的输入从稀疏的高维向量变成稠密的低维向量语义信息的利用率显著提升同时还能降低输入维度、加快训练速度。需要注意的是用词向量时一个文档需要聚合成一个向量常见做法是对所有词的词向量取平均或加权平均这会丢失部分语序信息但比TF-IDF更能捕获近义词关系。第二个方向是模型结构的升级把MLP从全连接层堆叠改为在输入端接入一个Embedding层。这样做的好处是模型可以直接接收整数索引序列不需要预先做向量化相当于把特征工程和模型训练合并成一个端到端的过程。代价是Embedding矩阵的维度需要根据词表大小设定训练时间会比固定特征向量长一些。这个改进方向可以放在报告里的「未来工作」部分表明你已经思考过模型的演进路径。第三个方向是评估体系的补全。当前只做了离线数据集上的准确率和F1评估更严谨的做法是引入时间维度——用前三个月的数据训练用后一个月的数据测试检验模型在时间变化下的泛化能力。虚假新闻的用词和话题会随着热点事件快速变化上个月训练的模型下个月可能就已经失效。这个「时间切分验证」的操作在报告里写出来能直观体现你对真实业务场景的理解比单纯堆参数调优更有说服力。我自己做这类项目有一个习惯每次调参只改一个变量记录变化前后的评估指标写进实验表格。你会发现很多看起来「经验正确」的调参操作在这份数据上就是不work——比如把ngram_range从(1,2)改成(1,3)未必提升F1反而引入大量低频组合特征把隐藏层从[256,128]扩成[512,256]也未必提升精度可能只是让模型更快过拟合。血泪经验就是调参前先定好评估指标调参后至少跑三次取均值排除随机初始化带来的噪声。这样整个项目的完成度会从一个「跑通的demo」变成一份「有实验支撑的技术报告」。希望这些路径和坑位对你有帮助动手跑一遍比看十遍笔记有用得多。本文还有配套的精品资源点击获取
返回列表