2026/10/12 18:41:20

基于Python的多模态虚假新闻检测:源码解析与避坑指南

基于Python的多模态虚假新闻检测:源码解析与避坑指南 简介基于Python的多模态虚假新闻检测项目在相关赛事中斩获第一名是一套可直接运行与二次开发的完整方案适合计算机、人工智能等专业的毕设、课程设计与竞赛参考。压缩包共41个文件含16个Python脚本、5个文本说明、4个Markdown文档及模型配置文件等整体仅约400KB结构清晰便于定位代码与文档。项目融合BERT文本特征提取与卷积神经网络图像特征提取并设计多模态融合层以提升检测准确率压缩包内附模型文件、训练与预测脚本、依赖清单及说明文档可支撑从环境配置、模型微调到结果复现的全流程。所有代码均经测试成功运行文档标注MIT许可适合有一定Python与深度学习基础的读者学习进阶。已有391人学习下载可作为开展虚假新闻检测研究的起点。1. 基于Python的虚假新闻检测多模态识别源码文档说明.zip这个压缩包装的是两条证据链“基于Python的虚假新闻检测多模态识别源码文档说明.zip”这个名字本身就点明了要害它把假新闻当成一个多模态问题来处理而不是只盯标题、正文这种纯文本。单看文本AI很容易被一本正经的造谣帖骗过去但只要配图露出马脚——图里的事件、场景、水印和正文对不上判别难度立刻降下来一截。这套方案适合已经会基础 Python、想把“文本分类”升级为“图文联合判别”的工程人员也适合做舆情系统想快速塞一个可复现检测模块的团队。接下来我按这种源码包最常见的目录结构把解压、环境搭建、模型训练和部署坑逐个拆开讲让你拿到的 zip 不只是一堆 .py而是一条能复现、能改、能换自己数据的流水线。2. 多模态检测为什么能赢数据形态、三套融合方案与评估指标2.1 单模态为什么让假新闻有漏网空间虚假新闻检测的传统做法是文本分类公开数据集像 LIAR、FakeNewsNet 都是以标题、正文、说话人为主的。但文本分类有一个绕不开的硬伤短文本信息量太低模型很容易被表面词带偏。一篇“某地发生爆炸”的谣言正文可以写得非常克制没有离谱词汇单纯用 BERT 分类器看它和真新闻的分布几乎重叠。对抗样本更麻烦把“疫苗有毒”改写成“疫苗引发XX反应”词表变化一大模型置信度立刻松动。多模态检测的赢面不在某一分支更强而在“信息冗余”。一条新闻如果正文说“今天下午三点解放碑附近起火”配图却是北方城市的雪景或者图上的车辆型号、店铺招牌和文字描述对不上这就是一个强信号。模型只要学到“图文不一致→可能是假”这个潜在关联就能抓住文本分类抓不到的证据。实际业务里还有一种高频场景旧图新用图片本身是真实的但被搬到完全不相关的事件里这种情况纯文本分支完全无从下手一定要多模态。这也是这个方向在舆情监控、内容审核场景里一直没被淘汰的原因它不是玄学是两条证据链交叉验证。2.2 三套融合方案怎么选双塔拼接、跨模态注意力、知识增强多模态识别的核心不是“各跑一个模型”而是怎么把两个模态的特征在对的位置合起来。常见的做法有三套我在实际项目中按数据集规模和数据质量来决定用哪套。第一套是双塔后拼接。文本分支用一个预训练语言模型取句向量图像分支用一个 CNN 取图像特征两个向量拼在一起后过一个全连接分类头。这是最老练、最不容易翻车的方案特征提取器都可以单独用 ImageNet / BERT 预训练权重融合层只有一层训练时不容易崩。缺点是两个模态只在最后交互细粒度的图文对齐学不到。适合 1 万到 5 万条这种中等规模数据集。第二套是跨模态注意力。让文本的每个 token 去“看”图像每个区域的 patch或者让图像 patch 去 attend 文本 token。这类方案能捕捉“图文错位”这种局部证据比如配图里某个招牌文字和正文实体冲突。但代价也明显训练要从头学注意力矩阵数据量不够容易过拟合收敛速度慢对显存要求高。我一般建议数据上 5 万条以后、且已经确认纯文本基线上不去了再换这套。第三套是知识增强。在双塔基础上接入外部事实库或者 OCR 文本。比如把配图里的文字用 OCR 抽出来再和正文做实体匹配甚至查知识图谱判断时间地点人物是否冲突。这套上限最高因为它真正在做“事实核查”而不是“风格判别”但工程复杂度也最高需要维护知识库、处理 OCR 噪声、设计对齐规则。源码包里如果没带知识库文件通常走的还是第一套因为最通用。融合方案交互位置适用数据集规模训练成本适合场景双塔拼接最后一层向量1 万5 万条低快速跑通、换数据做 baseline跨模态注意力中间层逐点交互5 万条以上高图文错位类假新闻占比高的场景知识增强融合前/后接入外部知识取决于知识库最高需要可解释判断依据的业务2.3 评估指标别只用准确率F1 和 AUC 才是照妖镜虚假新闻数据天然类别不平衡。真实平台里假新闻占比通常在 5% 到 15%如果训练集和测试集也按这个比例切模型只要全预测“真”就能拿到 90% 以上准确率看起来好看实际一点用没有。所以评估至少要同时看 F1、AUC 和混淆矩阵。我更习惯用验证集 F1 做早停而不是看 loss——loss 下降不代表少数类被分对。另外要小心数据泄漏。很多虚假新闻数据集的文本和图片是从同一个来源爬的如果只是随机切分模型可能记住“哪个网站的图通常是假的”而不是学到“图文对不对得上”。正确做法是尽量按事件、按域名、按发布时间切分保证验证集里的新闻和训练集不是同源同事件。这一点源码包的文档说明里如果没强调你自己切数据时务必要补上否则后面所有调参都是自我欺骗。3. 把源码包跑起来zip 解压、环境隔离与最小推理3.1 解压 zip 并检查目录结构这种项目包解压这一步就会劝退一批人。文件名带着“基于Python”“多模态识别”等中文有时还有空格shell 裸敲 unzip 容易补全失败。先用 bash 列一下再解压ls -lh unzip 基于Python的虚假新闻检测多模态识别源码文档说明.zip -d fake_news_multimodal cd fake_news_multimodal find . -maxdepth 2 -type f | head -30解压完先别急着跑先看目录结构和文档说明。这类包常见组织方式有三种src/放模型和工具函数configs/放 YAML 参数文件data/放样本或者所有.py平铺在根目录加一个requirements.txt。find输出的前 30 个文件足够判断入口在哪里看到train.py、infer.py、predict.py这类名字基本就是入口。如果 unzip 报错或者解压出来的文件名是乱码不要硬扛后面 5.1 节有专门的处理方法。目录检查之后先花两分钟读文档说明确认它要求的 Python 版本和 GPU 环境。一般来说这类项目要求 Python 3.8 到 3.11太新的 Python 反而容易因为 torch 编译版本不匹配报错这一点很多人会忽略。3.2 创建虚拟环境并安装依赖numpy 装不上的常见原因依赖安装前我强烈建议先建一个独立虚拟环境不要直接往系统 Python 里塞包。因为 torch 和 transformers 的依赖版本很敏感和系统里已有的 numpy、scipy 冲突会带来一串连锁报错。用以下命令把环境隔离出来python -m venv venv source venv/bin/activate python -m pip install --upgrade pip pip install torch torchvision transformers scikit-learn pillow pandas这里有几个参数细节要说明。torch和torchvision的版本必须配套否则运行时会报undefined symbol之类的错transformers建议用较新的 4.x因为老版本对 BERT 的tokenizer接口不够友好pillow一定要装很多包拿PIL读图不装会在数据加载阶段才报错。如果发现 numpy 装不上大概率是 Python 版本太新或者 pip 源里没有对应 wheel先把 Python 降到 3.10 再试不要自己编译 numpy。装完依赖后跑一遍python -c import torch; print(torch.__version__)确认 torch 能正常导入。不要等训练到一半才发现 torch 是 CPU 版或者 CUDA 根本不可用这个检查越早越省事。3.3 构造最小推理副本先跑通再谈训练源码包里有完整训练代码但你第一次跑就启动训练是大忌。我的习惯是先构造一个“最小推理副本”只加载预训练权重和一张测试图、一段测试文本把前向流程跑通确认模型能吃进数据、吐出预测。先准备测试数据mkdir -p data/images data/texts data/labels touch data/images/test.jpg # 放一张真实的新闻配图 touch data/texts/test.txt # 放一段新闻正文然后写一个最小推理脚本quick_infer.py逻辑如下import torch from PIL import Image from transformers import AutoTokenizer # 假设源码包里模型封装为 FakeNewsDetector from src.model import FakeNewsDetector device torch.device(cuda if torch.cuda.is_available() else cpu) model FakeNewsDetector.from_pretrained(checkpoints/latest.pth) model.to(device).eval() text open(data/texts/test.txt, encodingutf-8).read() image Image.open(data/images/test.jpg).convert(RGB) # 输出 0/1 概率1 表示判定为虚假新闻 with torch.no_grad(): probs model.predict(text, image, devicedevice) print(prob_fake , probs.item())这里重点看三个地方。第一Image.open(...).convert(RGB)这一步不能省灰度图、RGBA 图都会在这里被统一否则后面模型的归一化会出维度错误第二device要显式传入不然 CPU 模型和 GPU 张量混用会直接报错第三如果源码包里的模型没有predict接口就临时写一个函数把文本编码、图像预处理、前向推理串起来跑通后你会发现后续训练代码里所有“模型返回了什么”的疑问都消失了。4. 核心模块拆解文本编码、图像编码与融合层实现4.1 文本分支用 BERT 系模型取句向量而不是只拿 CLS文本分支的常规做法是加载一个预训练 BERT/RoBERTa 权重把新闻标题和正文拼成一个序列然后取句向量。很多人直接取[CLS]位置的向量这在短文本上没问题但新闻正文往往几百字[CLS]向量相当于对全文做一个粗糙的池化容易丢失局部证据。更稳的做法是取所有 token 隐状态的均值池化实验里通常比 CLS 高一两个点。import torch from transformers import AutoTokenizer, AutoModel tokenizer AutoTokenizer.from_pretrained(bert-base-chinese) encoder AutoModel.from_pretrained(bert-base-chinese) def encode_text(text, max_len256): inputs tokenizer( text, max_lengthmax_len, truncationTrue, paddingmax_length, return_tensorspt, ) with torch.no_grad(): outputs encoder(**inputs) # outputs.last_hidden_state: [batch, seq_len, hidden] # 平均池化避开 pad 位 mask inputs[attention_mask].unsqueeze(-1).float() pooled (outputs.last_hidden_state * mask).sum(dim1) / mask.sum(dim1) return pooled # (batch, 768)参数层面max_len是文本截断长度新闻标题加正文一般 256 就够设太大既费显存又未必提升效果paddingmax_length会把所有样本补到同样长度方便组 batch。关键在mask那里如果不乘attention_mask[PAD]位置也会参与池化句向量会被稀释。这个细节是新手最常踩的坑。4.2 图像分支用 ResNet50 并去掉最后的分类层图像分支最省事的方案是用 torchvision 里预训练好的 ResNet50只保留特征提取部分丢掉最后的全连接分类层。这样图像输入是 224x224输出是 2048 维特征向量。之所以不重新训练 CNN 而直接用预训练权重是因为假新闻检测的数据集相对较小从头训练 CNN 很容易过拟合预训练权重至少在通用视觉特征上是可靠的。import torch import torchvision.transforms as T from torchvision.models import resnet50, ResNet50_Weights weights ResNet50_Weights.DEFAULT cnn resnet50(weightsweights) cnn.fc torch.nn.Identity() # 去掉分类头输出 2048 维特征 transform T.Compose([ T.Resize((224, 224)), T.ToTensor(), T.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]), ]) def encode_image(image): x transform(image).unsqueeze(0) # (1, 3, 224, 224) with torch.no_grad(): feat cnn(x) return feat # (1, 2048)这里有两个参数容易忽略。Resize((224, 224))是硬拉伸而不是等比缩放会导致图像变形但实际效果通常没问题要更精细就用T.Resize((256, 256))后接T.CenterCrop(224)这样保留更多中心区域信息。Normalize的均值标准差必须和预训练权重一致否则特征分布偏移虽然不一定崩但会让迁移效果打折扣。4.3 融合层为什么先降维再拼接而不是直接拼接文本特征是 768 维图像特征是 2048 维直接拼成一个 2816 维的向量给全连接层模型需要学习的参数量会变大而且两个模态的尺度差异大图像分支的梯度会把文本分支压住。常见做法是先把两边都压到同一个低维空间比如 256 维再拼接。class FakeNewsDetector(torch.nn.Module): def __init__(self, text_dim768, image_dim2048, fused_dim256, num_classes2): super().__init__() self.text_proj torch.nn.Linear(text_dim, fused_dim) self.image_proj torch.nn.Linear(image_dim, fused_dim) self.classifier torch.nn.Sequential( torch.nn.Linear(fused_dim * 2, 128), torch.nn.ReLU(), torch.nn.Dropout(0.3), torch.nn.Linear(128, num_classes), ) def forward(self, text_feat, image_feat): # text_feat: (batch, text_dim) # image_feat: (batch, image_dim) t torch.relu(self.text_proj(text_feat)) v torch.relu(self.image_proj(image_feat)) fused torch.cat([t, v], dim-1) # (batch, fused_dim * 2) logits self.classifier(fused) return logitsfused_dim256是实践中比较稳的默认值。设太小比如 64会损失信息设太大比如 1024融合层参数量变大小数据集上更容易过拟合。Dropout(0.3)放在融合后的分类头里防止模型过度依赖某一个模态如果你发现单模态表现极好、另一模态基本没用可以把这个 dropout 提高一点。前向传入的特征最好都先detach()固定住预训练模型的梯度只在融合层训练这样能节省大量显存也避免 BERT/ResNet 在小数据集上灾难性遗忘。4.4 训练脚本学习率、batch size 和早停参数怎么设训练时预训练部分和融合层的学习率应该分开。BERT 和 ResNet 用很小的学习率微调融合头可以用稍大的学习率。下面是一个常见训练循环骨架param_optimizer list(model.named_parameters()) no_decay [bias, LayerNorm.weight] optimizer_grouped_parameters [ {params: [p for n, p in param_optimizer if classifier in n], lr: 1e-3}, {params: [p for n, p in param_optimizer if classifier not in n], lr: 2e-5}, ] optimizer torch.optim.AdamW(optimizer_grouped_parameters, weight_decay0.01) for epoch in range(epochs): for step, batch in enumerate(train_loader): text_feat, image_feat, labels batch logits model(text_feat, image_feat) loss torch.nn.functional.cross_entropy(logits, labels) loss.backward() torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm1.0) optimizer.step() optimizer.zero_grad()参数说明写详细一点。lr1e-3只作用于融合分类头lr2e-5作用于预训练 encoder这是 BERT 微调的标准范围设成 1e-4 以上大概率导致灾难性遗忘。weight_decay0.01是 AdamW 的常规设置bias和LayerNorm.weight不做衰减是 Transformer 微调的惯例。clip_grad_norm_是防梯度爆炸的保险丝对多模态模型尤其重要因为两个分支的梯度尺度不同一不注意就 Nan。早停参数建议patience2到3即连续几个 epoch 验证集 F1 没有提升就停避免在训练后期浪费时间。batch size 取决于显存BERT 加 ResNet 在 16GB 卡上 batch 通常只能到 16 到 32再大就 OOM。如果 OOM优先减小图像输入尺寸到 192x192而不是调 batch。5. 部署避坑解压乱码、CUDA 假死、图像损坏的排查清单5.1 坑压缩包里中文文件名乱码文件读不到现象解压后文件名变成“鍩轰簬Python”这类乱码或者读取图片时FileNotFoundError。原因Windows 上 zip 打包时中文文件名用的是 GBK 编码而 Python 3 的zipfile默认按 UTF-8 解码导致文件系统里存的是乱码名。解决不要用 unzip 硬解改用 Python 的zipfile重新解码文件名import zipfile with zipfile.ZipFile(基于Python的虚假新闻检测多模态识别源码文档说明.zip, r) as z: for info in z.infolist(): # 尝试把 cp437 错误解码的名字转成 GBK try: new_name info.filename.encode(cp437).decode(gbk) except UnicodeDecodeError: new_name info.filename print(new_name)这段代码之前可以先z.extractall(tmp)看看实际情况如果乱码就按上面的逻辑手工改名或重打包。这类源码包如果带了资源文件、中文命名的重要配置文件这一步不做后面所有路径都是错的。5.2 坑torch.cuda.is_available() 显示 False但明明装了 GPU 版现象import torch不报错但torch.cuda.is_available()返回False代码自动退到 CPU训练速度慢到没法看。原因绝大多数时候是 PyTorch 的 CUDA 版本和显卡驱动版本不匹配或者 pip 装到了 CPU 版 torch。解决先用nvidia-smi看驱动支持的 CUDA 版本再去 PyTorch 官方索引安装对应版本。命令行里可以用python -m pip install torch torchvision --index-url https://download.pytorch.org/whl/cu121这类方式指定 CUDA 12.1。如果驱动过老需要升级驱动而不是换 torch 版本。5.3 坑图像加载报错、图片被静默跳过现象训练中途报PIL.UnidentifiedImageError或者OSError: image file is truncated再或者总是提示文件不存在。原因新闻数据集里图片来源杂有损坏的 JPEG、有伪装成图片的文本文件、有 EXIF 方向信息但形状不对的图。解决在数据读取层加异常捕获和统一方向处理而不是让训练流程直接崩溃from PIL import Image, ImageOps def load_image(path): try: img Image.open(path).convert(RGB) return ImageOps.exif_transpose(img) # 修正手机拍摄时的方向 except Exception: return NoneImageOps.exif_transpose会按照 EXIF 里的方向信息自动旋转图片这在新闻图片里很常见。返回None后训练循环要跳过该样本同时统计被跳过的数量后面单列一个日志输出不要静默丢弃太多否则你都不知道数据少了一半。5.4 坑loss 在降F1 却永远停在 0.5现象训练 loss 正常下降验证准确率有 90% 以上但 F1 始终在 0.5 附近。原因类别不平衡模型把所有样本都预测成了多数类准确率高但没有实际检测能力。解决训练时给少数类加class_weight推理时不要用固定的 0.5 阈值而是扫描验证集找最优 F1 阈值。这两个动作同时做weights torch.tensor([1.0, 5.0], devicedevice) # 假新闻类的权重加大 loss torch.nn.functional.cross_entropy(logits, labels, weightweights) # 阈值扫描 best_f1, best_thr 0.0, 0.5 for thr in torch.arange(0.3, 0.8, 0.05): preds (probs[:, 1] thr).float() f1 compute_f1(preds, labels) if f1 best_f1: best_f1, best_thr f1, thrclass_weight不一定要严格按样本比例来5:1 到 10:1 是常见尝试区间。阈值扫描要放在验证集上做不能在测试集上做否则阈值会过拟合测试集换一批数据就失效。5.5 坑文本特征和图像特征维度对不上拼接报错现象前向传播时报mat1 and mat2 shapes cannot be multiplied或者cat时维度不匹配。原因BERT 输出是 768ResNet 输出是 2048直接拼接的代码没问题但投影层的输入维度写死了 768 和 2048换了模型后没改。解决把维度写成从模型的hidden_size动态读取而不是写在配置文件里或者在模型初始化时打一行日志把实测维度打印出来print(ftext_dim{text_feat.shape[-1]}, image_dim{image_feat.shape[-1]})这个坑往往在换中文模型、换更大 CNN 时冒出来提前打印维度能省去一晚上 debug 时间。6. 换到你的数据前先做脆弱性测试、再盯注意力、最后定阈值6.1 脆弱性测试改几个字、裁一块图F1 掉了多少源码包跑通后不要直接拿它上线。先做一个“脆弱性测试”把测试集里每条新闻的标题改写几个无关紧要的字或者把配图裁剪掉三分之一看模型输出会不会大幅变化。如果模型判定结果剧烈波动说明它学到的不是“图文是否一致”这个核心信号而是记住了某些表面特征。常见做法是写一个自动化脚本对同一批样本做原版、改写版、裁剪版的预测然后对比三者的相关性。相关性越低模型越不可靠。这个验证成本很低但对判别一个多模态模型有没有真正学到东西非常有价值。6.2 用注意力分数做错误样本排查而不是只看 loss当模型在某个类别上准确率暴跌我一般先不看参数而是去翻错误样本的注意力分布。BERT 最后一层对[CLS]的注意力权重可以告诉我们模型在看哪些词配合图像分支的 Grad-CAM 热力图很容易定位“文本根本没看正文”或者“图像只看角落”这类问题。源码包里如果自带可视化工具最好如果没有可以手动抓取一批错误样本的注意力分数看模型是否把注意力集中在与事实无关的位置。这一步不能自动化解决但血泪经验告诉我排查多模态模型问题可视化永远比调超参数快。我以前部署这类模型时就吃过数据静默丢失的亏换了一批带损坏图片的数据F1 掉了 8 个点我盯着训练日志看了整整两天最后才发现大批图片在加载阶段被跳过日志里却没有统计。现在我的习惯很固定每换一批数据先把样本总数、可读图片数、标签分布三行打印出来再谈调参。希望你拿到这套源码包时能少走这一步弯路。希望帮到你。本文还有配套的精品资源点击获取