基于Python的虚假新闻检测多模态识别:源代码与文档说明 简介这份资源面向计算机相关专业的本科生与研究生提供一套基于Python的虚假新闻检测多模态识别完整项目源码可用于毕业设计、期末大作业或课程设计场景帮助解决多模态特征融合与虚假新闻分类的实践难题。压缩包共39个文件约353KB以16个py脚本为核心涵盖BERT微调、LightGBM与CatBoost融合建模、预测测试等流程另含4个md说明、4个txt依赖与配置、3个sh运行脚本、3个tsv数据集文件以及json配置、checkpoint权重和ipynb实验笔记结构清晰便于按模块查阅。目前已有498人学习下载代码注释详尽新手也能理解。项目经过严格调试部署后即可运行读者可据此掌握多模态虚假新闻检测的完整建模思路、模型融合技巧与调参排错方法具有较高的参考与复用价值。1. 虚假新闻检测多模态识别为什么单看文本已经不够用了一条配着现场图的突发消息文本写得有鼻子有眼图片却来自三年前的另一个事件——这种「图文错配」是当下虚假新闻最典型的形态。只做文本分类的模型遇到这种样本几乎必翻车因为文字本身没有明显破绽。基于Python的虚假新闻检测多模态识别源代码文档说明这类项目解决的正是这个问题把文本和图像两条模态的信息联合起来判断真伪。它适合已经会写Python、想从单模态NLP跨到多模态方向的工程师也适合需要快速搭一套可复现baseline的学生和研究者。核心思路不复杂文本走Transformer编码图像走CNN或ViT编码两路特征对齐融合后接分类头。难的是数据怎么配、融合放哪一层、图文不对齐时怎么兜底。下面按「先立住原理、再跑通代码、最后避坑」的顺序拆开讲。2. 多模态虚假新闻检测的技术底座从特征提取到融合分类2.1 文本与图像两条编码链路怎么选文本侧主流做法是用预训练语言模型做编码器。BERT-base是性价比最高的起点768维输出中文场景换成本地预训练的中文BERT权重即可。如果算力紧张可以用蒸馏后的轻量模型但虚假新闻检测对语义细节敏感蒸馏过头会掉点。图像侧有两个方向CNNResNet-50擅长局部纹理ViT擅长全局关系。虚假新闻里的图片篡改痕迹往往是局部的拼接边缘、光照不一致所以ResNet-50在这类任务上并不吃亏而且参数量小、训练快。我一般会先用ResNet-50跑通全流程确认融合策略有效后再换ViT对比。两条链路的输出维度要对齐。BERT输出[batch, seq_len, 768]ResNet输出[batch, 2048]。常见做法是文本侧取[CLS]向量或做平均池化得到[batch, 768]图像侧接一个线性层投影到768维这样两路特征在同一空间里才能做注意力或拼接。2.2 融合策略早期融合、晚期融合还是交叉注意力融合位置决定了模型能捕捉什么级别的关联。晚期融合最简单两路各自分类最后对logits加权平均。优点是稳、好调缺点是学不到图文之间的细粒度对应关系。早期融合在输入层就拼接对虚假新闻检测来说太粗暴因为文本和图像的原始表示差异太大。交叉注意力cross-attention是当前效果最好的方案把文本特征作为query图像特征作为key和value让每个词去「看」图像的相关区域反之亦然。这样模型能学到「文中提到的地点」和「图中建筑」是否一致。代价是显存占用高batch size要压到8或16。实操中我会用一层交叉注意力加残差连接层数多了容易过拟合尤其在小数据集上。2.3 用PyTorch搭一个可运行的双塔融合模型下面是一个最小可运行的多模态分类模型骨架文本用BERT、图像用ResNet交叉注意力做融合。import torch import torch.nn as nn from transformers import BertModel from torchvision import models class CrossAttentionFusion(nn.Module): def __init__(self, dim768, num_heads8): super().__init__() # 文本作为query图像作为key/value self.cross_attn nn.MultiheadAttention(embed_dimdim, num_headsnum_heads, batch_firstTrue) self.norm nn.LayerNorm(dim) self.dropout nn.Dropout(0.1) def forward(self, text_feat, image_feat): # text_feat: [B, L, D], image_feat: [B, 1, D] attn_out, _ self.cross_attn(querytext_feat, keyimage_feat, valueimage_feat) out self.norm(text_feat self.dropout(attn_out)) # 残差连接 return out class MultimodalFakeNewsDetector(nn.Module): def __init__(self, num_classes2, freeze_bertFalse): super().__init__() self.text_encoder BertModel.from_pretrained(bert-base-chinese) if freeze_bert: for p in self.text_encoder.parameters(): p.requires_grad False self.image_encoder models.resnet50(pretrainedTrue) self.image_encoder.fc nn.Identity() # 去掉原分类头输出2048维 self.image_proj nn.Linear(2048, 768) # 投影到文本同维度 self.fusion CrossAttentionFusion(dim768) self.classifier nn.Sequential( nn.Linear(768, 256), nn.ReLU(), nn.Dropout(0.3), nn.Linear(256, num_classes) ) def forward(self, input_ids, attention_mask, images): text_out self.text_encoder(input_idsinput_ids, attention_maskattention_mask).last_hidden_state img_feat self.image_encoder(images) # [B, 2048] img_feat self.image_proj(img_feat).unsqueeze(1) # [B, 1, 768] fused self.fusion(text_out, img_feat) # [B, L, 768] pooled fused.mean(dim1) # 平均池化 logits self.classifier(pooled) return logits逻辑说明文本编码器输出每个token的表示图像编码器输出一个全局向量并投影到768维交叉注意力让每个token根据图像信息更新自己最后平均池化接分类头。参数方面freeze_bertTrue时只训练融合层和分类头适合数据量小于1万条的场景数据充足时解冻BERT全量微调学习率要降到2e-5。num_heads8是768维的标准配置改成4会降低表达能力但省显存。Dropout(0.3)在分类头前小数据集上可以提到0.5。2.4 数据管线的三个关键处理多模态数据最难的不是模型是配对。每条样本必须同时有文本和图像缺一不可。常见做法是自定义Dataset在__getitem__里同时返回tokenized文本和归一化后的图像张量。from torch.utils.data import Dataset from PIL import Image from torchvision import transforms class FakeNewsDataset(Dataset): def __init__(self, df, tokenizer, max_len128): self.df df # 需包含 text, image_path, label 三列 self.tokenizer tokenizer self.max_len max_len self.img_transform transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) def __len__(self): return len(self.df) def __getitem__(self, idx): row self.df.iloc[idx] enc self.tokenizer(row[text], truncationTrue, paddingmax_length, max_lengthself.max_len, return_tensorspt) image Image.open(row[image_path]).convert(RGB) image self.img_transform(image) return { input_ids: enc[input_ids].squeeze(0), attention_mask: enc[attention_mask].squeeze(0), images: image, labels: torch.tensor(row[label], dtypetorch.long) }max_len128对新闻标题加正文摘要够用长文可以提到256但显存翻倍。图像统一224×224是ResNet的标准输入。convert(RGB)必须加否则遇到灰度图或RGBA图会直接报错这是血泪经验。归一化参数用的是ImageNet统计值因为ResNet预训练权重就是在这个分布上学的不匹配会掉点。3. 从零跑通训练与评估命令行、参数与日志3.1 训练脚本的核心循环与参数设置训练循环本身不复杂关键是梯度累积和混合精度。多模态模型显存吃紧batch size往往只能开到8这时用梯度累积模拟大batch。import torch from torch.cuda.amp import autocast, GradScaler from transformers import AdamW, get_linear_schedule_with_warmup def train_one_epoch(model, loader, optimizer, scheduler, scaler, device, accum_steps4): model.train() total_loss 0 optimizer.zero_grad() for step, batch in enumerate(loader): input_ids batch[input_ids].to(device) attention_mask batch[attention_mask].to(device) images batch[images].to(device) labels batch[labels].to(device) with autocast(): # 混合精度省显存 logits model(input_ids, attention_mask, images) loss nn.CrossEntropyLoss()(logits, labels) / accum_steps scaler.scale(loss).backward() if (step 1) % accum_steps 0: scaler.step(optimizer) scaler.update() scheduler.step() optimizer.zero_grad() total_loss loss.item() * accum_steps return total_loss / len(loader)accum_steps4配合实际batch size 8等效batch size 32。学习率用2e-5BERT微调标准值warmup比例0.1。混合精度用autocast加GradScaler能省30%左右显存但要注意loss必须除以累积步数否则梯度会放大。评估时用model.eval()加torch.no_grad()指标至少看准确率和F1虚假新闻检测里类别不平衡很常见F1比准确率更能反映真实水平。3.2 评估指标与阈值调整二分类默认阈值0.5但虚假新闻检测往往更怕漏判把假新闻判成真所以可以适当降低阈值提高召回。用sklearn算一组指标from sklearn.metrics import classification_report, roc_auc_score import numpy as np def evaluate(model, loader, device, threshold0.4): model.eval() all_probs, all_labels [], [] with torch.no_grad(): for batch in loader: logits model(batch[input_ids].to(device), batch[attention_mask].to(device), batch[images].to(device)) probs torch.softmax(logits, dim1)[:, 1].cpu().numpy() all_probs.extend(probs) all_labels.extend(batch[labels].numpy()) preds (np.array(all_probs) threshold).astype(int) print(classification_report(all_labels, preds, digits4)) print(AUC:, roc_auc_score(all_labels, all_probs))阈值从0.5降到0.4召回通常能提3到5个点精确率掉1到2个点。具体调到多少要看业务容忍度没有万能值。AUC不受阈值影响用来判断模型整体排序能力低于0.85说明融合策略或数据质量有问题。3.3 文档说明该写什么才有用一份能让人复现的文档说明至少包含四块环境依赖Python版本、PyTorch版本、transformers版本用requirements.txt固定、数据格式每列的字段名和样例、训练命令含所有超参的完整命令行、预期结果在标准数据集上的指标区间。我见过太多文档只写「运行train.py即可」结果别人连数据放哪都不知道。文档里还要标注哪些参数是必须改的比如数据路径哪些可以保持默认。如果代码里用了预训练权重要写清楚权重名称和获取方式不要假设别人本地就有。4. 避坑与排查多模态虚假新闻检测最容易翻车的五个地方4.1 图文不对齐导致loss不降现象训练几个epoch后loss卡在0.69附近二分类的随机水平准确率50%上下。原因数据里文本和图像不是同一条新闻的配对错了。常见于从不同来源爬取后按文件名或索引拼接。解决写一个校验脚本随机抽100条人工核对图文是否对应或者在Dataset里加断言检查image_path是否存在且能打开。更隐蔽的情况是图像路径正确但内容被替换过这种只能靠抽样肉眼查。4.2 图像编码器冻结后特征太泛现象解冻BERT但冻结ResNet时验证集F1比纯文本模型还低。原因ImageNet预训练的ResNet提取的是通用物体特征对「这张图是否被篡改」这种任务不敏感。解决至少解冻ResNet的layer4和fc层学习率设成BERT的十分之一2e-6。如果数据量超过5万条全量解冻ResNet效果更好但要注意过拟合加强数据增强随机裁剪、颜色抖动。4.3 显存溢出OOM的三种触发方式现象训练到一半报CUDA out of memory。原因一max_len设太大文本序列过长原因二batch size没配合梯度累积原因三交叉注意力的注意力矩阵是L×LL256时显存占用是L128的四倍。解决先把max_len降到128再用accum_steps补batch交叉注意力层数控制在1到2层用torch.cuda.empty_cache()在epoch之间清理缓存。如果还不行把图像分辨率从224降到160但会掉点。4.4 类别不平衡导致模型全预测多数类现象准确率看着有80%但F1只有0.3混淆矩阵显示少数类几乎全错。原因虚假新闻数据集中真新闻远多于假新闻模型学会了偷懒。解决损失函数加class weightnn.CrossEntropyLoss(weighttorch.tensor([1.0, 3.0]))权重按类别频率反比设或者用focal loss。重采样也能用但过采样容易过拟合欠采样会丢信息优先调权重。4.5 验证集指标虚高但测试集崩盘现象验证集F1 0.92换一批数据掉到0.6。原因验证集和训练集同分布但测试集来自不同时间或不同来源存在分布偏移。虚假新闻检测里这个问题特别严重因为假新闻的写法变化很快。解决划分数据时按时间切分用早期数据训练、后期数据测试或者在训练集里加入不同来源的样本做域增强。文档说明里要明确标注数据划分方式否则别人复现时指标对不上。5. 进阶技巧用对比学习提升图文一致性判别能力跑通baseline之后如果想把F1再往上推3到5个点可以加一个辅助任务图文对比学习。核心思想是让匹配的图文对在特征空间里靠近不匹配的远离。具体做法是在融合层之后加一个投影头把文本池化向量和图像向量映射到128维用InfoNCE损失训练。class ContrastiveHead(nn.Module): def __init__(self, in_dim768, proj_dim128): super().__init__() self.text_proj nn.Sequential(nn.Linear(in_dim, proj_dim), nn.ReLU(), nn.Linear(proj_dim, proj_dim)) self.image_proj nn.Sequential(nn.Linear(in_dim, proj_dim), nn.ReLU(), nn.Linear(proj_dim, proj_dim)) self.temperature nn.Parameter(torch.tensor(0.07)) def forward(self, text_vec, image_vec): t nn.functional.normalize(self.text_proj(text_vec), dim-1) i nn.functional.normalize(self.image_proj(image_vec), dim-1) logits t i.T / self.temperature # [B, B] labels torch.arange(t.size(0), devicet.device) loss_t2i nn.functional.cross_entropy(logits, labels) loss_i2t nn.functional.cross_entropy(logits.T, labels) return (loss_t2i loss_i2t) / 2总损失是分类损失加0.1倍对比损失。温度参数0.07是SimCLR的经验值可以学但初始值别乱设。对比学习对batch size敏感batch越大负样本越多效果越好所以尽量用梯度累积把等效batch推到64以上。这个辅助任务在图文错配样本多的数据集上提升最明显因为模型被迫学会判断图文是否真的对应而不是各自独立分类。验证对比学习是否有效看两个信号一是分类F1有没有涨二是图文检索的Recall1有没有提升。如果分类没涨但检索涨了说明对比损失权重太大压到0.05再试。另外注意对比学习需要每个batch里没有重复样本否则正样本对会混淆DataLoader的shuffle必须开。我自己的习惯是任何多模态方案先用纯文本跑一个baseline记住它的F1加上图像后如果提升不到2个点先别急着调模型回去查数据配对和图像质量。十次里有七次是数据问题不是模型问题。希望帮到你。本文还有配套的精品资源点击获取