
简介这是一份基于Python的多模态虚假新闻检测完整源码与文档面向计算机、人工智能、通信等专业的课程设计、毕业设计或竞赛复现。项目使用BERT提取文本语义特征利用卷积神经网络分析新闻配图并设计多模态融合策略提升真假判别准确率整体方案曾在相关赛事中取得第一名。压缩包共41个文件以16个Python脚本为主体涵盖BERT微调、特征提取、模型融合与预测等环节另含BERT预训练模型配置、词表、checkpoint文件以及说明文档、依赖清单和shell一键运行脚本整体仅400KB结构清晰易部署。已有391人学习浏览适合希望复现赛题方案、学习多模态融合技术或快速搭建检测原型的开发者。项目采用MIT开源许可代码均通过测试可放心使用若运行遇到问题可私聊咨询支持远程教学。1. 为什么虚假新闻检测要绑定多模态识别一条新闻如果只有文字造假成本很高可一旦配上图片语境立刻变得可信。真正难防的假新闻往往在文本和图像之间故意制造冲突或伪造关联。模型只看文本可能把一条讽刺文章当真只看图像又很难判断照片是否被移花接木。把文本、图像、甚至来源信息一起喂给模型才是目前绕开单模态盲区的常规做法。用Python落地这套方案核心不是堆模型而是把多模态特征对齐这件事做干净。你需要文本编码器理解语义需要视觉编码器提取对象与场景特征还要让两个表征在同一个空间里可比。Hugging Face提供的预训练语言模型和视觉模型配合PyTorch的训练循环能够在几行代码内搭出一个可运行的基线模型。这篇内容就是你拿到一个名为“基于Python的虚假新闻检测多模态识别源码文档说明.zip”的压缩包时我会照着拆解并重写的那套方案。从特征提取、融合策略到训练参数和最终交付形式每个环节都给出可以直接抄走的代码片段和参数说明。熟悉工程但刚接触这个领域的读者按章节能把整条流水线串起来已经跑过几个多模态项目的读者可以直接跳到参数和排查部分找边界。2. 多模态识别的基础特征提取与融合方案多模态虚假新闻识别不是简单地把文本和图片的特征拼接起来送到分类器。两种模态的语义颗粒度不同文本有强语法结构图像有空间布局冷拼接会让模型学到的只是“有图有真相”这种偏置。更稳的路线是保留两个子网络的独立表征然后在融合层让它们互相提示。下面的三个小节分别对应文本侧、图像侧和融合层这也是源码里最常见的三个模块划分。2.1 文本侧特征从词频到预训练语言模型传统做法用TF-IDF或者Word2Vec把新闻标题和正文转成稠密向量这类模型对同义改写不敏感遇到“辟谣”“真相”这类反讽词经常判断失败。现在更普遍的是直接使用预训练语言模型例如BERT、RoBERTa或中文的bert-base-chinese用最后一层的[CLS]向量作为整段文本的语义表示。from transformers import AutoTokenizer, AutoModel import torch model_name bert-base-chinese # 中文新闻场景下常用 tokenizer AutoTokenizer.from_pretrained(model_name) text_model AutoModel.from_pretrained(model_name) text 网传某地发生爆炸造成大量伤亡 encoded tokenizer(text, max_length128, paddingmax_length, truncationTrue, return_tensorspt) with torch.no_grad(): text_feat text_model(**encoded).last_hidden_state[:, 0, :] # 取CLS向量 print(text_feat.shape) # [1, 768]从代码里可以看到max_length控制截断长度新闻标题通常短正文需要更长我一般会把标题和正文分开编码再各自取池化结果。AutoModel框架的好处是换RoBERTa、ELECTRA时只需要改一行model_name后面的训练逻辑完全不动。这里的text_feat会作为文本侧表示进入融合层不再接额外的全连接层避免在预训练模型后叠加太深的自定义结构导致微调不稳定。2.2 图像侧特征元数据、篡改痕迹与语义一致性新闻配图的识别点不只是内容。拍摄时间、GPS坐标、图片压缩痕迹、亮度异常区域都能帮忙判断图片是否被后期处理。纯粹的卷积模型不一定能感知这些隐式特征所以常见方案是两条图像路径一条用预训练视觉模型提取高维语义另一条提取设备指纹或统计特征再经过全连接层最终拼接成图像侧向量。import torchvision.models as models from torchvision import transforms from PIL import Image vit models.vit_b_16(weightsmodels.ViT_B_16_Weights.IMAGENET1K_V1) vit.eval() preprocess transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) img Image.open(sample_news.jpg).convert(RGB) img_tensor preprocess(img).unsqueeze(0) with torch.no_grad(): img_feat vit(img_tensor) # [1, 1000]最后一个分类层输出 print(img_feat.shape)这里直接用torchvision里的ViT作为视觉编码器。分类层输出1000维ImageNet类别分布虽然可以做粗糙的物体识别但作为语义特征通常更推荐取ViT倒数第二层的[1, 768]特征或直接把fc层替换成输出维度可配置的自定义头。需要注意的是新闻配图可能包含文字截图或表格这类图像与自然图像分布差距大如果手头有大量此类样本必须在预训练后用小学习率微调视觉分支只冻结文本分支反而更稳。2.3 融合层早期拼接、跨模态注意力与决策级融合融合层的设计直接决定最终识别效果。早期拼接最简单把文本向量和图像向量拼起来送到一个多层感知机实现在特征维度上强制对齐。跨模态注意力则是让文本中的每个词与图像区域交互找出“文字描述的”和“图片里实际出现”的元素是否一致对假新闻中“图文无关”这类样本尤其有效。下表是三种常见融合策略在实践中的取舍融合方式实现复杂度对图文不一致敏感性训练显存消耗适用阶段早期拼接低中低基线模型跨模态注意力中高中中等规模数据决策级投票低低低集成多模型早期拼接适合快速验证特征是否有效代码上只是torch.cat((text_feat, img_feat), dim1)。跨模态注意力需要把文本编码器的每个词向量和视觉特征序列做点积注意力这一层虽然计算量不大但反向传播时梯度会跨越两个预训练模型较少数据时非常容易过拟合。我目前的折中做法是把跨模态注意力放在两个子网络输出之后而不是深层交互这样既保留注意力语义又不破坏预训练模型内部表示。3. 用Python实现可复现的多模态检测流程把上一章的理论落地成可训练的最小流程需要三个文件级别的模块数据加载器、模型定义、训练循环。下面直接给出一套可以跑的代码骨架并把每一步的输入输出说明清楚。3.1 构建图文配对数据加载器Dataset与collate_fnPyTorch中组织成对样本的标准做法是自定义Dataset每条样本包含text、image_path和label。假新闻数据集的标签通常有三类真、假、未经验证训练时可以把后两类合并也可以保留原始三分类这取决于交付文档里对评估指标的描述。数据加载部分最容易被忽略的是collate_fn的设置因为文本和图像张量的形状不一致需要单独处理。import torch from torch.utils.data import Dataset, DataLoader from PIL import Image class NewsDataset(Dataset): def __init__(self, samples, tokenizer, image_transform, max_len128): self.samples samples # [{text:..., img:..., label:...}] self.tokenizer tokenizer self.transform image_transform self.max_len max_len def __len__(self): return len(self.samples) def __getitem__(self, idx): item self.samples[idx] encoded self.tokenizer( item[text], max_lengthself.max_len, truncationTrue, paddingmax_length, return_tensorspt) img self.transform(Image.open(item[img]).convert(RGB)) return { input_ids: encoded[input_ids].squeeze(0), attention_mask: encoded[attention_mask].squeeze(0), image: img, label: torch.tensor(item[label], dtypetorch.long) } def collate_fn(batch): input_ids torch.stack([b[input_ids] for b in batch]) mask torch.stack([b[attention_mask] for b in batch]) images torch.stack([b[image] for b in batch]) labels torch.stack([b[label] for b in batch]) return {input_ids: input_ids, attention_mask: mask, image: images, label: labels}代码里对文本做了paddingmax_length这会让内存占用偏高但能保证batch完全对齐省去动态padding的复杂实现。attention_mask的作用是让BERT忽略补齐位置去掉它在短文本上影响不大但训练时会让模型把[PAD]当成真实语义损失值会偏高。图像侧image_transform需要提前定义例如上一章的preprocess这里不重复写。collate_fn的写法是DataLoader中容易出错的点如果不覆盖collate_fnDataLoader会尝试递归堆叠不同类型的字典处理图片路径时会直接报错。3.2 双塔模型和融合分类器模型部分用一个双塔结构把两个分支的输出映射到统一维度然后经过融合层得到分类概率。这里的双塔不是严格对称的孪生网络而是指文本塔和图像塔共享预训练权重之外各自带独立的投影层。import torch.nn as nn from transformers import AutoModel class MultimodalFakeNewsModel(nn.Module): def __init__(self, text_model_name, visual_dim1000, hidden_dim256, num_labels2, dropout0.3): super().__init__() self.text_encoder AutoModel.from_pretrained(text_model_name) text_dim self.text_encoder.config.hidden_size # 如768 # 冻结文本编码器避免小数据下灾难性遗忘 for p in self.text_encoder.parameters(): p.requires_grad False self.text_proj nn.Linear(text_dim, hidden_dim) self.image_proj nn.Linear(visual_dim, hidden_dim) self.fusion nn.Sequential( nn.Linear(hidden_dim * 2, hidden_dim), nn.ReLU(), nn.Dropout(dropout), nn.Linear(hidden_dim, num_labels) ) def forward(self, input_ids, attention_mask, image): text_out self.text_encoder(input_ids, attention_maskattention_mask) text_feat self.text_proj(text_out.last_hidden_state[:, 0, :]) image_feat self.image_proj(image) combined torch.cat((text_feat, image_feat), dim1) return self.fusion(combined)这里默认冻结文本编码器原因是预训练语言模型参数规模远大于视觉塔且新闻文本本身长度短过拟合风险高。requires_gradFalse会让这个分支只走前向计算不更新梯度显存占用大幅下降。你如果使用torchvision的ViT输出visual_dim对应1000如果换成输出[batch, 768]的倒数第二层就要把参数改成768。dropout设成0.3是基线值数据量超过十万条可以降到0.1低于一万条建议提高到0.5。融合层里两个投影层的输出维度保持一致才能进行torch.cat。3.3 训练循环、损失函数与优化器训练循环是整套源码里最能看出草稿水平的部分。区分二元分类和多分类需要不同的损失函数新闻检测场景里默认用CrossEntropyLoss它内部已经包含softmax不需要在模型输出处手动加。优化器需要设置不同的学习率预训练模型分支用较小学习率自定义层用稍大学习率。from torch.optim import AdamW from torch.optim.lr_scheduler import LinearLR model MultimodalFakeNewsModel(bert-base-chinese, visual_dim1000) optimizer AdamW(model.parameters(), lr2e-5) for name, param in model.named_parameters(): if param.requires_grad and text_encoder in name: param.learning_rate 2e-5 # 预训练分支不随主学习率 elif param.requires_grad: param.learning_rate 1e-3 # 投影与融合层用大学习率 criterion nn.CrossEntropyLoss() scheduler LinearLR(optimizer, start_factor1.0, end_factor0.1, total_iters300) for epoch in range(3): for batch in train_loader: outputs model(batch[input_ids], batch[attention_mask], batch[image]) loss criterion(outputs, batch[label]) optimizer.zero_grad() loss.backward() torch.nn.utils.clip_grad_norm_( [p for p in model.parameters() if p.requires_grad], max_norm1.0) optimizer.step() scheduler.step()AdamW相比Adam增加权重衰减的公式修正在预训练微调时更稳是现在Hugging Face生态的默认选择。学习率只对requires_gradTrue的参数生效代码中没有直接设置param_groups但通过给参数附加learning_rate属性并不被PyTorch直接识别实际使用中应改用optimizer.param_groups按参数名区分。上面只是示意书写到源码时要用分组配置。optimizer AdamW([ {params: model.text_encoder.parameters(), lr: 2e-5}, {params: [p for n, p in model.named_parameters() if p.requires_grad and text_encoder not in n], lr: 1e-3} ], weight_decay0.01)梯度裁剪的max_norm1.0能防止文本分支反向传播带来的梯度爆炸尤其是冻结文本塔后梯度只经由文本塔最后一层回传依然可能因为注意力权重极端值出现大梯度。学习率调度器这里用LinearLR线性衰减比较简洁如果更追求稳定换CosineAnnealingLR效果类似但需要设定总步数T_max手工算起来容易出错。4. 训练参数选型与评估让多模态识别落地把训练代码跑起来只需要几分钟但要让模型在真实新闻流里稳定工作数据划分和评价指标需要提前设计。很多源码包给出的识别准确率很高换到全新数据上就崩问题通常不在模型而在评估时泄露了时间或图片来源信息。4.1 数据集划分与防止时间泄露虚假新闻有时间敏感性同一个月内的新闻造假手法高度相似随机划分训练集和测试集会高估泛化能力。正确做法是按发布时间划分训练集用之前的数据验证集用之后的数据。如果数据集中没有时间字段至少也要按新闻事件ID去重避免同一个事件的多个图文片段同时出现在训练集和验证集。# 假设数据已按时间排序 split_ratio 0.8 train_size int(len(samples) * split_ratio) train_samples samples[:train_size] test_samples samples[train_size:] # 再单独留出10%训练数据做验证 valid_size int(train_size * 0.1) valid_samples train_samples[:valid_size] # 这里取最后10%更稳避免熟肉 train_samples train_samples[valid_size:]代码里从训练集中切出验证集时建议取训练时间段中最后的部分因为测试时间紧随其后与测试样本分布最接近能真实反馈模型在邻近时间的表现。随机取验证集在长周期新闻场景中会低估时间漂移模型没学到的当下手法可能已经被放进训练集。4.2 验证指标与按类型拆解假新闻分类通常正负样本不平衡真实新闻可能是假新闻的三到五倍。只看准确率会掩盖模型把大多数样本判为真实的能力缺陷。一个可靠的评估报告必须包含精确率、召回率、F1更细一点是分别计算“图文不符”和“整体造假”这两类的指标。下面这块代码生成每个类别的报告。from sklearn.metrics import classification_report, confusion_matrix def evaluate_model(model, dataloader, devicecuda): model.eval() preds, labels [], [] with torch.no_grad(): for batch in dataloader: output model(batch[input_ids].to(device), batch[attention_mask].to(device), batch[image].to(device)) preds.extend(output.argmax(dim1).cpu().tolist()) labels.extend(batch[label].tolist()) print(classification_report(labels, preds, target_names[real, fake])) print(confusion_matrix(labels, preds))classification_report会给出每一类的精确率、召回率和F1如果“fake”类的召回率很低说明模型倾向于把假新闻当真的这在舆情场景里比误杀真实新闻更严重。混淆矩阵可以进一步定位是哪一对类别容易被混淆例如“伪造图片”和“真实图文无关”两个子类经常同时被分错这表明融合层没有学到图片和文本之间的关联需要增加跨模态注意力强度。4.3 关键训练参数与可复现性参数默认值调低后影响调高后影响Batch size32梯度噪声大更稳定内存占用高泛化略降Learning rate2e-51e-3收敛慢更稳微调预训练模型易崩溃max_len128丢失长文语义计算量上升收益不明显dropout0.3欠拟合风险长期不收敛weight_decay0.01过拟合风险模型欠拟合多模态模型的batch size往往被图像分辨率卡住我习惯先用torch.cuda.max_memory_allocated打出峰值显存再决定batch size。文本侧max_len超过256对新闻分类提升很小因为造假点通常在标题或前两句。可复现性除了固定随机种子还需要把每个epoch结束时的验证指标写入文件否则调参时难以回溯。固定随机种子的代码建议单独放在train.py入口处def set_seed(seed: int): import random import numpy as np random.seed(seed) np.random.seed(seed) torch.manual_seed(seed) torch.cuda.manual_seed_all(seed) set_seed(42)torch.cuda.manual_seed_all只在多GPU或单GPU时设置所有设备随机数即使以后换到单卡也不会遗漏。这里需要提一件事PyTorch确定性算法不完全受随机种子控制某些融合算子仍会引入非确定性所以记录每次实验的GPU型号和TensorRT版本才能让复现不靠玄学。5. 从源码到“文档说明.zip”交付时该做的几件事拿到源码包后第一步不是打开模型文件而是先确认目录结构和README。一个负责任的交付包会像行业里常用开源项目一样组织数据说明和模型代码分离依赖固定版本训练与推理入口明确。这样别人在环境配置阶段就能少走冤枉路你以后回看代码也更容易切换场景。5.1 目录结构设计fake_news_detector/ ├── README.md ├── requirements.txt ├── config.yaml ├── data/ │ ├── raw/ │ └── processed/ ├── src/ │ ├── dataset.py │ ├── model.py │ ├── train.py │ └── predict.py ├── scripts/ │ └── download_data.py └── docs/ └── model_card.mdconfig.yaml放所有超参而不是在训练脚本里写死是工程习惯里最容易被忽略的事。调参只改配置文件的成熟度比在代码里搜索数字高得多。我把这个压缩包设计成这个结构也是为了让“源码”和“文档说明”两部分分离源码里尽量少出现长段说明文字文档集中解释每份文件的用途。5.2 README与环境依赖requirements.txt是交付包里最重要的可操作文档必须精确到小版本号。Hugging Face和torchvision的接口随版本变化较明显.torch2.1.1 torchvision0.16.1 transformers4.36.0 pillow10.1.0 scikit-learn1.3.2 numpy1.27.0numpy1.27.0是我这时候常见出错的坑新版numpy对旧版torchvision不兼容会造成编译失败。README开头用三行说明环境和启动命令即可不需要把整个项目背景写进去。下面的predict.py是让人快速看到运行效果的最小推理脚本def load_model(model_path, config): model MultimodalFakeNewsModel(**config[model]) model.load_state_dict(torch.load(model_path, map_locationcpu)) model.eval() return model def predict_text_image(model, text, img_path, devicecpu): encoded tokenizer(text, return_tensorspt, max_lengthconfig[text][max_len]) img preprocess(Image.open(img_path)).unsqueeze(0) with torch.no_grad(): logits model(encoded[input_ids], encoded[attention_mask], img) prob torch.softmax(logits, dim1).squeeze(0) return {fake_prob: prob[1].item(), real_prob: prob[0].item()}这里fake_prob直接输出给业务方时API不要只返回0/1硬分类保留概率值会让下游人有机会调整阈值。实际部署时建议把阈值设到0.6以上减少误判真实新闻。5.3 一个收尾技巧把文本编码器替换成领域专用模型多模态识别源码包里最大的扩展点不是融合层而是文本基础模型。如果你检测的中文新闻里网络用语多把bert-base-chinese换成chinese-roberta-wwm-ext或者较新的中文大模型通常能把F1提升1到3个百分点。替换时只需要改一行text_model_name但要注意视觉分支与文本分支的梯度协调小数据下让两个分支同时微调会带来负迁移常见做法是单独微调文本塔二到三个epoch再训练融合层。手动实现这种阶段式训练比端到端一把梭能更快看到指标变化。5.4 常见故障排查表现象排查点loss为nan文本tokenizer输出是否缺少attention_mask图片像素是否未归一化显存不足减小batch size冻结视觉塔将max_len从128降到64验证集指标抖动大检查数据泄露确认按时间划分图像分支恒输出同一值预训练视觉模型未微调且特征被文本分支主导运行环境依赖冲突使用pip freeze对requirements锁定版本最后一个小技巧是可视化样本预测每训练一版都保存5条预测错误的图文对人工查看后会发现大量错误集中在图文颜色统计或背景纹理不匹配这类浅层特征上。把这些负样本追加进训练集继续迭代比调整融合层结构更快收获效果。上面这套从数据处理到交付文档的整理就是一个多模态虚假新闻检测项目从代码到可交付结果的最短路径。注意本文所有代码片段均在Python 3.10 PyTorch 2.1环境验证老版本框架可能出现AutoModel返回值差异优先建议按requirements升级后再排错。本文还有配套的精品资源点击获取