
简介这份资源面向深度学习与虚假新闻检测方向的学习者和研究者提供一套基于PyTorch框架的多模态检测系统实现。系统以BERT预训练模型提取文本深层语义特征以ResNet卷积神经网络提取图像特征并引入对比学习技术增强真实与虚假新闻的判别能力支持在微博谣言数据集上完成训练与评估适合作为课程设计、毕业设计或相关课题的参考方案。压缩包共21个文件约1.48MB以12个Python脚本为核心涵盖模型定义、数据处理、训练与配置等模块另含4个txt说明、3个csv数据文件及docx、md文档便于快速理解项目结构与运行流程。目前已有96人学习下载。通过该资源读者可掌握多模态特征融合的完整实现思路理解BERT与ResNet的协同建模方式并借助对比学习提升检测精度为社交媒体虚假信息识别提供可复用的工程范例。1. 拆开这个多模态虚假新闻检测包BERTResNet 到底怎么配合干活微博上的假新闻有个特点配图往往比文字更有迷惑性。一张移花接木的现场图配上一段情绪拉满的文字单看文本分类器很容易被骗过去。这个资源包解决的就是这个问题用 BERT 抽文本特征、ResNet 抽图像特征再通过对比学习把两个模态拉到同一个语义空间里做真假判断。技术栈是 PyTorch数据集走的是微博谣言数据集训练和评估脚本都封装好了。适合谁用如果你正在做多模态相关的课程设计、毕设或者想找一个能跑通的跨模态融合 baseline这个包的门槛不算高——只要你会配 PyTorch 环境、能读懂 Dataset 和 DataLoader 的基本写法剩下的就是调参和看指标。但如果你指望开箱即用、不碰任何配置就能出论文级结果那得先做好踩坑的心理准备多模态的坑比单模态多得多。2. 环境搭建与数据准备从零把训练管线跑起来2.1 PyTorch 环境与依赖版本选择这个项目对 PyTorch 版本不算特别挑剔但有几个依赖的版本组合会直接影响能不能跑通。我一般会先确认 CUDA 版本再倒推 PyTorch 的安装命令。如果你用的是 CUDA 12.1 及以上直接走官方推荐命令就行如果是 11.8注意 torchvision 的版本要对应上否则 ResNet 的预训练权重加载会报 key 不匹配。# 创建独立环境避免和已有项目冲突 conda create -n fake_news python3.9 -y conda activate fake_news # CUDA 11.8 对应的 PyTorch 安装常见做法 pip install torch2.0.1 torchvision0.15.2 --index-url https://download.pytorch.org/whl/cu118 # 文本侧依赖 pip install transformers4.30.2 pip install scikit-learn pandas numpy tqdm tensorboard这里有几个参数值得说清楚。transformers版本不要低于 4.28因为 BERT 的AutoModel接口在旧版本里对attention_mask的处理有差异会导致对比学习阶段 loss 异常。torchvision必须和torch主版本号对齐0.15.x 配 2.0.x 是验证过的组合。装完之后跑一句python -c import torch; print(torch.cuda.is_available())返回 True 才算环境就绪。提示如果你在 WSL 里配环境CUDA 驱动走 Windows 侧就行WSL 内部只需要装 CUDA Toolkit 的运行时不用重复装驱动。2.2 微博谣言数据集的目录结构与加载逻辑这个包默认的数据组织方式是「文本文件 图片文件夹 标签 CSV」三件套。常见做法是把数据根目录设成data/weibo_rumor/下面分texts/、images/、labels.csv。labels.csv 至少要有三列text_id、image_id、labellabel 用 0/1 表示真/假。import os import pandas as pd from torch.utils.data import Dataset from PIL import Image class WeiboRumorDataset(Dataset): def __init__(self, root_dir, label_file, tokenizer, transform, max_len128): self.root root_dir self.df pd.read_csv(os.path.join(root_dir, label_file)) self.tokenizer tokenizer self.transform transform self.max_len max_len def __len__(self): return len(self.df) def __getitem__(self, idx): row self.df.iloc[idx] # 文本侧BERT tokenizer 输出 input_ids 和 attention_mask text_path os.path.join(self.root, texts, f{row[text_id]}.txt) with open(text_path, r, encodingutf-8) as f: text f.read().strip() enc self.tokenizer(text, max_lengthself.max_len, paddingmax_length, truncationTrue, return_tensorspt) # 图像侧ResNet 要求 224x224 输入 img_path os.path.join(self.root, images, f{row[image_id]}.jpg) image Image.open(img_path).convert(RGB) image self.transform(image) return { input_ids: enc[input_ids].squeeze(0), attention_mask: enc[attention_mask].squeeze(0), image: image, label: row[label] }逻辑说明max_len128是微博文本的合理截断长度微博正文普遍短128 个 token 覆盖 95% 以上的样本。paddingmax_length保证 batch 内张量对齐不然 DataLoader 的 collate 会报错。图像统一转 RGB 是因为数据里可能混有灰度图或 PNG 带 alpha 通道的情况不转的话 ResNet 第一层卷积会直接崩。参数怎么改如果你的显存吃紧把max_len降到 64batch_size 设成 16基本能在 8G 显存上跑起来。图像 transform 里记得加Resize(256)再CenterCrop(224)这是 ResNet 预训练时的标准预处理直接 Resize 到 224 会损失边缘信息。3. 双塔特征提取与对比学习融合模型结构拆解3.1 BERT 文本编码器与 ResNet 图像编码器的接法模型主体是双塔结构文本塔用bert-base-chinese图像塔用resnet50(pretrainedTrue)。关键改动在两处——BERT 的输出取pooler_output768 维ResNet 去掉最后的全连接层取全局平均池化后的 2048 维特征。两个维度不一致所以中间要加投影头把维度对齐到同一个空间。import torch import torch.nn as nn from transformers import BertModel from torchvision import models class DualEncoder(nn.Module): def __init__(self, proj_dim256): super().__init__() # 文本塔加载中文 BERT self.text_encoder BertModel.from_pretrained(bert-base-chinese) # 图像塔ResNet50 去掉 fc 层 resnet models.resnet50(pretrainedTrue) self.image_encoder nn.Sequential(*list(resnet.children())[:-1]) # 投影头把两个模态映射到同一维度 self.text_proj nn.Sequential( nn.Linear(768, 512), nn.ReLU(), nn.Linear(512, proj_dim) ) self.image_proj nn.Sequential( nn.Linear(2048, 512), nn.ReLU(), nn.Linear(512, proj_dim) ) def forward(self, input_ids, attention_mask, image): text_out self.text_encoder(input_idsinput_ids, attention_maskattention_mask) text_feat text_out.pooler_output # (B, 768) img_feat self.image_encoder(image) # (B, 2048, 1, 1) img_feat img_feat.flatten(1) # (B, 2048) text_emb self.text_proj(text_feat) image_emb self.image_proj(img_feat) # L2 归一化方便后续算余弦相似度 text_emb nn.functional.normalize(text_emb, dim-1) image_emb nn.functional.normalize(image_emb, dim-1) return text_emb, image_emb为什么用pooler_output而不是last_hidden_state的均值pooler_output经过一层 tanh 变换对分类任务更友好而且维度固定省去手动池化的麻烦。ResNet 那边用flatten(1)把 (B, 2048, 1, 1) 压成 (B, 2048)比用view更安全不会因为 batch 维度顺序问题翻车。投影维度proj_dim256是个经验值。太小会丢信息太大对比学习收敛慢。如果你数据量超过 5 万条可以提到 512数据量小就保持 256防止过拟合。3.2 对比学习损失函数与训练循环对比学习的核心思路是同一个样本的文本和图像特征应该靠近不同样本的特征应该远离。这里用的是 InfoNCE 损失的变体温度系数设 0.07。class ContrastiveLoss(nn.Module): def __init__(self, temperature0.07): super().__init__() self.temp temperature def forward(self, text_emb, image_emb): # 计算相似度矩阵 (B, B) logits text_emb image_emb.T / self.temp labels torch.arange(logits.size(0), devicelogits.device) # 对称损失文本找图像 图像找文本 loss_t2i nn.functional.cross_entropy(logits, labels) loss_i2t nn.functional.cross_entropy(logits.T, labels) return (loss_t2i loss_i2t) / 2训练循环里有个细节容易被忽略对比学习阶段和分类微调阶段最好分开。先冻结 BERT 和 ResNet 的主干只训投影头 5 个 epoch让两个模态先对齐然后解冻全部参数接一个分类头做端到端微调。这样做比一上来就端到端训练稳定得多loss 不会在前期剧烈震荡。# 阶段一只训投影头 for param in model.text_encoder.parameters(): param.requires_grad False for param in model.image_encoder.parameters(): param.requires_grad False optimizer torch.optim.AdamW( list(model.text_proj.parameters()) list(model.image_proj.parameters()), lr1e-3, weight_decay1e-4 ) # 阶段二解冻全部小学习率微调 for param in model.parameters(): param.requires_grad True optimizer torch.optim.AdamW(model.parameters(), lr2e-5, weight_decay1e-4)学习率从 1e-3 降到 2e-5 是 BERT 微调的标准操作主干网络经不起大学习率的折腾。weight_decay1e-4就是常说的 L2 正则化在 PyTorch 里通过 AdamW 的 weight_decay 参数实现比手动加 L2 惩罚项更干净。4. 训练与评估实操指标怎么看、参数怎么调4.1 训练脚本的关键参数与日志监控训练脚本一般会暴露这几个参数batch_size、lr、epochs、temperature、proj_dim。我习惯用 argparse 管理方便做消融实验。import argparse parser argparse.ArgumentParser() parser.add_argument(--batch_size, typeint, default32) parser.add_argument(--lr, typefloat, default2e-5) parser.add_argument(--epochs, typeint, default10) parser.add_argument(--temperature, typefloat, default0.07) parser.add_argument(--proj_dim, typeint, default256) parser.add_argument(--data_root, typestr, defaultdata/weibo_rumor) args parser.parse_args()batch_size 设 32 是个平衡点。对比学习依赖 batch 内的负样本batch 太小负样本不够对比效果打折扣batch 太大显存扛不住。如果你只有一张 8G 卡降到 16同时把 temperature 提到 0.1补偿负样本减少带来的影响。日志方面除了 loss重点盯三个指标文本-图像相似度的均值应该随训练上升、分类准确率、F1。如果相似度均值一直不涨说明投影头没学好检查一下归一化是不是漏了。4.2 评估指标与混淆矩阵的读法虚假新闻检测不能只看准确率。微博谣言数据集通常正负样本不均衡假新闻占比可能只有 30% 左右。这时候准确率会被多数类带偏F1 和 AUC 才是靠谱指标。from sklearn.metrics import classification_report, confusion_matrix def evaluate(model, dataloader, device): model.eval() all_preds, all_labels [], [] with torch.no_grad(): for batch in dataloader: input_ids batch[input_ids].to(device) attention_mask batch[attention_mask].to(device) images batch[image].to(device) labels batch[label].to(device) text_emb, image_emb model(input_ids, attention_mask, images) # 融合方式拼接后过分类头 fused torch.cat([text_emb, image_emb], dim-1) logits model.classifier(fused) preds logits.argmax(dim-1) all_preds.extend(preds.cpu().numpy()) all_labels.extend(labels.cpu().numpy()) print(classification_report(all_labels, all_preds, digits4)) print(confusion_matrix(all_labels, all_preds))classification_report里重点看假新闻类label1的 recall。如果 recall 低说明模型漏检多假新闻被放过了这在实际场景里比误判更危险。混淆矩阵里如果 FP 高、FN 低可以适当调低分类阈值反过来就调高。注意评估时一定要用model.eval()切到推理模式否则 BatchNorm 和 Dropout 还在训练状态指标会飘。5. 避坑与排查多模态训练里那些血泪经验5.1 常见问题排查现象一loss 变成 NaN。原因通常是对比学习阶段温度系数太小相似度矩阵除以 0.07 后数值爆炸。解决把 temperature 提到 0.1 或 0.2或者在计算 logits 前对 embedding 做 clamp。现象二ResNet 预训练权重加载报错。报错信息里出现unexpected key或missing key多半是 torchvision 版本和权重文件不匹配。解决确认 torchvision 版本或者用pretrainedFalse先跑通流程再换回预训练。现象三显存溢出但 batch_size 已经很小。检查是不是在__getitem__里做了图像增强的多次复制或者 BERT 的max_len设太大。解决把max_len降到 64图像 transform 里去掉 RandomRotation 这类耗显存的操作。现象四训练集 loss 降但验证集不降。典型过拟合。解决加大 weight_decay 到 1e-3或者在投影头里加 Dropout(0.3)再不行就减少 proj_dim。现象五文本和图像特征相似度始终在 0.5 附近。说明两个模态根本没对齐。解决检查投影头后面有没有做 L2 归一化以及对比学习的 labels 是不是torch.arange(batch_size)这个最容易写错。5.2 数据层面的隐蔽坑微博谣言数据集的图片里有一部分是截图或长图直接 Resize 到 224 会糊成一片。常见做法是先做短边 Resize 到 256再 CenterCrop 224保留主体区域。另外文本里如果有大量 和话题标签建议在预处理阶段去掉这些 token 对真假判断没有信息量反而干扰 BERT 的注意力。还有一个容易翻车的地方训练集和验证集的划分要按事件划分不能随机划分。同一个事件的文本和图片如果同时出现在训练集和验证集里指标会虚高。这个包默认是按随机划分的如果你要发论文记得改成按事件 ID 分组划分。6. 进阶技巧把对比学习权重用出额外价值跑通基础流程之后这个包最值钱的部分其实是对比学习训出来的双塔编码器。它不只能做真假分类还能直接拿来做跨模态检索——用文本搜图片或者用图片搜相关报道。具体做法是把训练好的text_proj和image_proj单独抽出来对所有样本的文本和图像分别编码存成一个特征库然后用余弦相似度做近邻搜索。# 构建跨模态检索特征库 def build_retrieval_index(model, dataloader, device): model.eval() text_feats, image_feats, ids [], [], [] with torch.no_grad(): for batch in dataloader: t_emb, i_emb model( batch[input_ids].to(device), batch[attention_mask].to(device), batch[image].to(device) ) text_feats.append(t_emb.cpu()) image_feats.append(i_emb.cpu()) ids.extend(batch[id]) text_feats torch.cat(text_feats, dim0) image_feats torch.cat(image_feats, dim0) # 保存成 npy后续用 faiss 或 sklearn 做近邻检索 torch.save({text: text_feats, image: image_feats, ids: ids}, retrieval_index.pt) return text_feats, image_feats这个特征库的用法很灵活。比如你拿到一条新微博想找历史上有没有相似谣言就用文本特征去检索如果只有一张图想找相关报道就用图像特征去搜。相似度阈值我一般设 0.75低于这个值的结果噪声太大不如不返回。验证对比学习有没有真正学到东西有个简单办法把同一事件的文本和图像特征算相似度再随机抽不同事件的文本图像对算相似度如果前者显著高于后者差距在 0.2 以上说明对齐是有效的。如果差距很小回去检查投影头的归一化和温度系数。从那以后我每次训完对比学习模型都会强制走一遍跨模态检索验证不跑这一步就不敢说模型真的把两个模态对齐了。希望帮到你。本文还有配套的精品资源点击获取