简介:这是一份完整的基于Python的多模态虚假新闻检测项目源码及配套文档,适合计算机、人工智能等相关专业的在校学生、教师或企业开发者用于课程设计、毕业设计或项目初期验证。项目中融合了文本与图像等多模态特征进行虚假新闻识别,代码结构清晰,包含基于BERT的模型实现、特征融合与调参脚本等模块。压缩包共39个文件,以16个Python脚本为主,辅以Markdown/TXT说明文档、Shell运行脚本、JSON配置及TSV数据文件,整体大小约350KB,便于快速浏览与部署,目录结构也比较清晰。目前已有296人次学习下载。读者可获得完整的模型代码、训练与预测脚本、环境依赖清单(packages.txt)以及项目说明文档,既能直接复现多模态识别流程,也可基于现有代码进行改进,用于毕设或课题研究。
1. 基于 Python 的虚假新闻检测多模态识别,到底在解决什么问题
2024 年之后,纯文本的虚假新闻检测已经不太够用了。大量伪造内容开始走“真假拼接”的路线:新闻正文是从正规媒体上扒下来的,配图却是用生成模型伪造的,或者反过来,配图是真实的,但文字描述被恶意篡改。这类样本如果只做文本分类,特征非常接近真实新闻,检测模型在这种场景下集体翻车。基于 Python 的虚假新闻检测多模态识别,就是把新闻正文文本和配图图像放在同一个模型里做联合判断,通过文本与图像之间的语义一致性、来源可信度、编辑痕迹等互补信号,把单模态模型漏掉的那部分假新闻重新捞回来。这个方案适合已经在做文本分类、想往多模态方向扩展的从业者,也适合需要给内容审核系统加一道视觉校验的团队。它解决的不是“识别所有假新闻”,而是“识别单靠文本看不出来的假新闻”,这套思路落地后,能将验证集 Macro-F1 从 0.83 附近拉到 0.91 左右。
2. 搭建多模态识别框架:文本分支、图像分支和融合层
多模态识别的框架搭建,核心是三个分支的选择与配合:文本特征提取、图像特征提取、以及把两边特征压到同一尺度再融合的分类层。很多人第一次做多模态时,习惯把文本和图像的特征直接拼起来丢给分类器,结果模型收敛慢、准确率上不去。问题不在数据量,而在特征维度不匹配——5000 维的 TF-IDF 文本向量和 2048 维的图像特征拼接后,梯度更新会被高维分支主导。常见的做法是先做投影对齐再融合,这也是这个项目源码里最核心的套路。
2.1 环境选型:Python 3.10、PyTorch 2.x 和 Transformers 库的搭配
搭建环境阶段有一个先决条件:Python 版本不能太低。PyTorch 2.x 在 3.8 以下版本上会出现算子兼容问题,而 Transformers 库的新版特性也需要 Python 3.9 以上。推荐直接用 Python 3.10,这个版本在虚拟环境和依赖管理上踩的坑最少。
conda create -n fake_news python=3.10 conda activate fake_news pip install torch torchvision transformers scikit-learn pandas pillow逻辑说明:torch和torchvision负责图像分支的模型加载与训练,transformers用来调用预训练视觉模型和文本编码器,scikit-learn提供 TF-IDF 向量化和评估指标。装完以后用python -c "import torch; print(torch.__version__)"验证安装结果,这个习惯能省掉不少环境问题的排查时间。
参数说明:如果机器没有 NVIDIA GPU,torch会默认安装 CPU 版本,性能会差一些但不是不能用。四核 CPU 的笔记本跑一轮推理大约 8 秒,优化后可以压到 2 秒以内,这个后面在推理部分单独展开。GPU 显存低于 6G 时,建议把图像编码器的输入尺寸从 224 降到 168,能少占约一半显存。
2.2 文本分支:TF-IDF 和词向量怎么选
文本特征在虚假新闻检测里的作用,比大多数人想的要大。标题党是第一个特征,正文和图片的语义错位是第二个特征。起始阶段不用直接上 BERT,成本太高,先用 TF-IDF 拿到基准线。
from sklearn.feature_extraction.text import TfidfVectorizer vectorizer = TfidfVectorizer(ngram_range=(1, 2), stop_words='english', max_features=5000) X_train_tfidf = vectorizer.fit_transform(df['clean_text']) X_test_tfidf = vectorizer.transform(df_test['clean_text'])逻辑说明:ngram_range=(1, 2)让特征包含单个词和连续双词,能抓到“震惊”“绝对真相”这类典型的标题党短语;max_features=5000控制维度,避免在新闻数据集上把稀疏矩阵撑得过大。fit_transform只用在训练集上,测试集只能用transform,否则会把测试集的词汇混进词典,造成特征泄漏,这在虚假新闻检测里是常见的评测虚高来源。
参数说明:stop_words='english'对中文文本无效,中文场景需要用jieba先分词,再把分词结果传给 TF-IDF。检测结果里如果出现“不实信息”和“不实”被拆成两个特征的情况,多半是分词粒度没调对,需要看analyzer参数是否生效。
2.3 视觉分支:用预训练 CNN 提取新闻配图特征
视觉分支负责从配图中抽取用于判断任务的特征,不负责理解图片内容。用 ImageNet 预训练模型来初始化,然后在新闻数据集上微调。
from transformers import AutoImageProcessor, AutoModelForImageClassification image_processor = AutoImageProcessor.from_pretrained('google/vit-base-patch16-224') image_model = AutoModelForImageClassification.from_pretrained( 'google/vit-base-patch16-224', num_labels=128, ignore_mismatched_sizes=True )逻辑说明:ignore_mismatched_sizes=True是必填项。因为原模型的分类头是 1000 类(ImageNet),改成 128 维特征后会维度不匹配,不加这个参数会直接报错。改动分类头维度是为了让视觉特征在融合层之前降维,避免进入融合层时特征维度过大。
参数说明:可以选用resnet50替代vit,效果相似但 CPU 推理更快。视觉特征被组织成一个 128 维的特征向量,经过 L2 归一化后再进入融合层,用于消除亮度、对比度差异带来的特征偏移。
2.4 多模态融合层:为什么用 128 维而不是直接拼接特征
融合层是多模态识别成败的关键之一。简单粗暴地把 2048 维图像特征和 5000 维文本特征拼接起来,后续的分类器在处理这种不平衡维度时,权重更新会严重偏向高维一侧,导致训练速度下降,最终准确率也会受影响。常见的做法是用一个低维对齐层,把两边都压到同一个尺度再融合。
class MultimodalFusion(nn.Module): def __init__(self, text_dim=5000, image_dim=128, fusion_dim=128, num_classes=2): super().__init__() self.text_proj = nn.Linear(text_dim, fusion_dim) self.image_proj = nn.Linear(image_dim, fusion_dim) self.classifier = nn.Sequential( nn.Linear(fusion_dim * 2, 64), nn.ReLU(), nn.Dropout(0.3), nn.Linear(64, num_classes) ) def forward(self, text_vec, image_vec): text_h = self.text_proj(text_vec) image_h = self.image_proj(image_vec) fusion = torch.cat([text_h, image_h], dim=-1) return self.classifier(fusion)参数说明:text_dim=5000对应 TF-IDF 的max_features,如果改了分词配置,这里要同步改。image_dim=128对应 VIT 特征维度,如果换成 ResNet 的 2048 维,这里要同步改。fusion_dim=128是融合向量的维度,不是越大越好,超过 256 之后在小数据集上会开始过拟合。Dropout(0.3)在全连接层之间做正则化,数据量低于 5 万条时,0.3 比 0.5 更稳。
融合后的特征向量长度固定为fusion_dim * 2 = 256,接一个两层 MLP 输出二分类概率。文本特征在这个阶段会被完全压缩到 128 维,这个降维过程对噪声也有抑制作用。
2.5 双编码器训练:把训练过程分成冻结、微调、联合三个阶段
多模态识别的训练和普通分类任务不一样,不能一开始就把所有参数都放开。常见做法是分三个阶段,每个阶段负责不同层次的学习:
阶段一:冻结图像编码器,只训练融合层。让融合层先适应两种特征的统计分布,这个阶段跑 5-10 个 epoch。如果一开始就放开图像编码器,预训练权重会被少量标注数据带偏。
阶段二:解冻图像编码器,用较低的学习率(比如 2e-5)微调。这时候融合层已经稳定,反向传播的梯度在特征空间中会按预期方向流动,训练过程才可控。
阶段三:联合微调文本向量器。TF-IDF 本身不参与梯度更新,所以文本向量器不涉及反向传播。如果在文本侧选择 BERT 方案,本阶段才需要解冻文本编码器。
训练一个 epoch 的参考代码如下:
for batch in train_loader: text_ids, images, labels = batch text_vec = vectorizer.transform(text_ids) # 转成 TF-IDF 向量 text_vec = torch.tensor(text_vec.toarray(), device=device) image_vec = image_model(images).logits image_vec = torch.nn.functional.normalize(image_vec, p=2, dim=-1) logits = fusion_model(text_vec, image_vec) loss = criterion(logits, labels) optimizer.zero_grad() loss.backward() optimizer.step()逻辑说明:vectorizer.transform返回的是稀疏矩阵,必须先.toarray()转成稠密张量才能输入nn.Linear。image_model(images).logits拿的是分类头的输出向量,在这里它被当作图像特征使用,而不是直接输出类别概率。normalize这一步是让图像特征在进入融合层前保持单位长度,防止某些图片特征模长过大主导融合结果。
参数说明:optimizer用 AdamW,冻结阶段学习率 1e-3,解冻后整体降到 2e-5。同一份代码里同时存在两个学习率,所以优化器要分成两个参数组。
3. 构建多模态数据集:把新闻文本与配图对齐,排查数据层面的坑
虚假新闻检测的数据集是整个项目里最麻烦的环节。公开的新闻数据集大多是纯文本,多模态的数据集需要同时有新闻正文和配图,还要有真实/虚假标签,能直接下载的并不多。这个项目的数据通常以 CSV 加图片文件夹的形态存在,拿到数据后要先做一次质量检查,而不是直接扔进模型。
3.1 数据质量检查:先算出文本和图像样本的对齐率
多模态数据最容易出问题的点在于对齐。配图和文本对不上,多模态模型比单模态模型更脆弱。因为模型会尝试学习“文本描述 A 对应图片 B”的关系,一旦对应关系是错的,它会用这些错误的对应关系学到一套自洽的假规律,在两个模态的信息本来就矛盾的真实样本上反而会失效。
import pandas as pd from pathlib import Path df = pd.read_csv('news_data.csv') img_root = Path('images') df['has_img'] = df['image_id'].apply(lambda x: (img_root / x).exists()) print(df.groupby('label')[['has_img', 'text_len']].mean()) # 检查文本与图片是否大致对齐(按字符长度分布) df['text_len'] = df['text'].str.len() mismatch = df[(df['text_len'] < 50) & (df['has_img'] == True)] print(f'短文本+有图样本数: {len(mismatch)}')逻辑说明:has_img列统计本地图片是否存在,text_len按字符数粗筛文本是否过短。短文本加有图不算错误,但如果占总量的比例超过 10%,说明数据标注时可能把纯图片新闻也算进去了,这种样本在训练时会把文本分支的信号冲淡。
参数说明:text_len < 50这个阈值是针对英语新闻设置的,中文新闻 50 个字以下很难承载完整信息,可以调整到 30。如果数据集里全是视频封面帧,images/下的文件命名要和image_id完全一致,注意 Windows 系统里jpg和JPG会被视为不同文件。
3.2 把 CSV 文本切分成带标签的 PyTorch Dataset
from torch.utils.data import Dataset class NewsDataset(Dataset): def __init__(self, df, vectorizer, img_dir, processor): self.df = df.reset_index(drop=True) self.vectorizer = vectorizer self.img_dir = img_dir self.processor = processor def __len__(self): return len(self.df) def __getitem__(self, idx): row = self.df.iloc[idx] text_vec = self.vectorizer.transform([row['clean_text']]) text_vec = torch.tensor(text_vec.toarray(), dtype=torch.float32).squeeze(0) img_path = self.img_dir / row['image_id'] image = Image.open(img_path).convert('RGB') pixels = self.processor(image, return_tensors='pt')['pixel_values'].squeeze(0) label = torch.tensor(row['label'], dtype=torch.long) return {'text_vec': text_vec, 'pixels': pixels, 'label': label}逻辑说明:__getitem__返回一个字典而不是元组,后面写训练循环时语义更清晰。每次取数据时都打开图片并做预处理,数据量大时可以调整num_workers来并行加载。
参数说明:processor是 2.3 节里定义的AutoImageProcessor,return_tensors='pt'控制输出格式。.squeeze(0)用来去掉 batch 维度,因为单条数据必须是[C, H, W]形状,而不是[1, C, H, W],否则模型会报 batch 维度不匹配的错误。vectorizer.transform每次返回稀疏矩阵,转成稠密toarray()会导致内存占用偏高,2 万条数据时还好,20 万条时就要考虑在内存里换一种表示方式。图片文件缺失时,Image.open会抛FileNotFoundError,最稳妥的做法是直接丢弃缺失样本。
3.3 标签分布检查与类别不平衡处理
虚假新闻数据集有个常见问题:伪造新闻占比通常在 30% 到 40% 之间。如果训练时不做平衡处理,模型会把所有样本都预测为真实新闻,准确率依然很高,但实际毫无用处。
label_dist = df['label'].value_counts(normalize=True) if label_dist.min() < 0.3: print('警告: 类别不平衡,精度指标不可靠,改用 Macro-F1') from sklearn.metrics import classification_report print(classification_report(y_true, y_pred, target_names=['real', 'fake']))逻辑说明:classification_report会把 precision、recall、F1 分标签输出。做多模态识别的项目里,只看 accuracy 不看 F1 几乎一定会翻车。在低召回率场景里,真实新闻的准确率很高,但假新闻几乎全漏,这个报告里会看得一清二楚。
参数说明:如果确实存在不平衡问题,最直接的解法是给CrossEntropyLoss设置weight参数。用torch.tensor([1.0, 2.0])这样的比例,具体数值按训练集统计结果来填。更激进的做法是重采样,但重采样配合多模态模型容易让文本和图像的组合分布失真,所以一般先调 loss 权重,不急着重采样。
4. 多模态模型微调:学习率、批大小和早停策略,以及验证集对比结果
模型架构确定之后,进入到最消耗时间的训练环节。多模态识别落地时,新手和熟手的差距往往体现在三个参数的设置上:学习率、batch size 和早停策略。这三个参数单独调都不难,但它们相互影响,调参时要看组合效果。
4.1 学习率先预热再衰减:多模态模型最容易在第一个 epoch 就发散
多模态模型里不同分支的收敛速度不一样,文本分支通常比图像分支收敛快很多。一个过大的全局学习率会把图像分支的预训练权重破坏掉。常见做法是加一个 warmup 阶段,头 3 个 epoch 让学习率从 0 线性升到目标值,后面再用余弦退火降下来。
from torch.optim.lr_scheduler import LinearLR, CosineAnnealingLR, SequentialLR warmup = LinearLR(optimizer, start_factor=0.1, end_factor=1.0, total_iters=3) anneal = CosineAnnealingLR(optimizer, T_max=12, eta_min=1e-6) scheduler = SequentialLR(optimizer, schedulers=[warmup, anneal], milestones=[3])逻辑说明:start_factor=0.1是让第一个 epoch 的学习率只有目标值的 10%,到第 3 个 epoch 结束时升到目标值。CosineAnnealingLR的T_max=12指从第 3 个 epoch 之后再过 12 个 epoch 降到最低点。这两个组合起来,能把训练过程中的 loss 震荡压下去很多。
参数说明:milestones=[3]表示第 3 个 epoch 结束后切到第二个调度器。如果训练轮数只有 6,那T_max也要跟着改小,否则余弦下降还没走完就提前结束了。
4.2 batch size 和显卡显存限制下的最小配置
多模态模型显存占用的大头是图像编码器。VIT Base 的输入是 224x224 的三通道图像,单张大约占用 1.2 GB 显存(含梯度),batch size 4 时大约 5 GB 上下,8GB 显存勉强能跑,但几乎没法同时开大 batch 和做梯度累积。
train_loader = DataLoader( train_dataset, batch_size=8, shuffle=True, num_workers=4, pin_memory=True )逻辑说明:如果 8G 显存会 OOM(内存溢出),就把batch_size调成 4,同时打开梯度累积,用两步累积来等效一个 batch size 8 的更新。num_workers=4让数据加载并行,但如果__getitem__里做了大量预处理,瓶颈仍在 CPU 上,这时候pin_memory=True反而会让显存占用略微上升。
参数说明:shuffle=True只用于训练集,验证集和测试集必须设成False,否则评估结果不稳定。如果 CPU 核数不足,num_workers=4有时反而更慢。一个常见判断方法是看训练日志里DataLoader的等待时间占比,如果 GPU 利用率常驻低于 50%,优先检查是加载慢还是模型小。
4.3 早停策略和保存最优模型:验证集 Macro-F1 连续没提升就停下
多模态模型迭代次数不宜太多,通常 15-20 个 epoch 就到了平台期。很多项目就是在这个阶段过度训练,验证集 F1 在 0.87 附近徘徊,训练集却已经冲到 0.95,过拟合的特征就开始出现了。早停的代码单独抽出来写,不依赖第三方库。
best_f1 = 0.0 patience = 4 bad_epochs = 0 for epoch in range(max_epochs): train_loss = train_one_epoch(...) val_f1 = evaluate(val_loader, ...) if val_f1 > best_f1: best_f1 = val_f1 bad_epochs = 0 torch.save({ 'fusion': fusion_model.state_dict(), 'image_encoder': image_model.state_dict(), 'optimizer': optimizer.state_dict(), }, 'best_model.pt') print(f'epoch {epoch}: save, F1={val_f1:.4f}') else: bad_epochs += 1 if bad_epochs >= patience: print('early stop') break逻辑说明:patience=4表示连续 4 个 epoch 的验证集 F1 没有刷新纪录,就停止训练。best_model.pt不是整个模型对象,而是各分支的state_dict。存optimizer.state_dict是为了支持后续从断点恢复训练,虽然初版用不到,但能省掉一个后悔药。
参数说明:max_epochs设 20,理论上在这个范围内应该能触发早停。如果到第 20 个 epoch 还没触发,说明验证集波动很大,要检查是不是数据切分时没有stratify,或者 batch size 太小导致梯度不稳定。
4.4 对照实验:单文本、单图像、多模态效果分别如何
多模态识别项目必须有一个对照实验,否则无法回答“多模态到底比单模态强在哪”这个问题。建立一个三列对比基准:文本单独跑一个 LR(逻辑回归),图像单独跑一个微调后的分类头,多模态跑 2.4 节的融合模型。下面是一个简化的记录表:
| 模型 | 验证集 Macro-F1 | 说明 |
|---|---|---|
| 文本 TF-IDF + LR | 0.83 | 标题党检测的文本基线 |
| 图像 VIT 特征 + LR | 0.88 | 仅靠配图特征的基线的补充 |
| 多模态融合 | 0.91 | 文本+图像+融合层 |
逻辑说明:表格里的数字是典型值。在真实项目中,单文本和单图像的差距通常很大,取决于数据集的文本质量。如果单图像已经到 0.91,而加上文本只有 0.90,说明多模态融合层的设计有问题。首先要怀疑文本分支存在过拟合,检查训练时的文本 loss 是否下降异常快。
参数说明:调参没有跑过对照组之前,不能说某个改动起作用了。常见做法是每改一次超参,就把三列指标记录下来,比较后再决定下一步。这也是判断多模态融合是否有效的最直接方式:如果融合模型的 F1 不高于两个单模态中的最优值,说明模型结构或训练策略有问题。
5. 多模态识别结果验证与诊断:把这套真假分类器落到本地推理
模型训练完只是第一步。虚假新闻检测在实际使用中最关心的不是整体准确率,而是“它判断错了会怎样”。需要单独做的验证和诊断包括评估指标的分标签查看、推理时间统计,以及把模型包装成一个简单的命令行工具。
5.1 从保存的检查点恢复模型,在测试集上输出分标签指标
使用best_model.pt做推理前,必须做一步:model.eval()。这个操作会关闭 Dropout 和 BatchNorm 的训练行为,否则推理结果每次都不一样,排查时容易让人误以为模型不稳定,实际上是从没切换过推理模式。
def load_model(checkpoint_path): fusion_model = MultimodalFusion(...) image_model = AutoModelForImageClassification.from_pretrained( 'google/vit-base-patch16-224', num_labels=128, ignore_mismatched_sizes=True ) state = torch.load(checkpoint_path, map_location='cpu') fusion_model.load_state_dict(state['fusion']) image_model.load_state_dict(state['image_encoder']) fusion_model.eval() image_model.eval() return fusion_model, image_model逻辑说明:map_location='cpu'保证在没有 GPU 的机器上也能加载检查点。load_state_dict必须和保存时的键保持一致,如果保存的是{'fusion': ...},加载时也要用state['fusion']。如果遇到尺寸不匹配的报错,通常是因为重建模型时的num_labels和保存时不一致。
参数说明:torch.load在 PyTorch 2.0 以上的版本里,对weights_only参数有默认值的变更。推荐显式写torch.load(checkpoint_path, map_location='cpu', weights_only=True),避免加载时执行任意 Python 对象反序列化,尤其在处理来源不明的权重文件时更稳妥。
5.2 推理时间统计:头一次跑通时,单条新闻耗时多少可以接受
虚假新闻检测在离线批处理场景中,单条推理耗时达到几百毫秒到一两秒都算正常。但如果要在 web 服务里被高频调用,就要压缩预处理和数据加载的时间。下面给一个统计多次推理耗时的模板:
import time def predict(news_text, img_path, vectorizer, processor, fusion_model, image_model): text_vec = vectorizer.transform([news_text]).toarray() text_vec = torch.tensor(text_vec, dtype=torch.float32) image = Image.open(img_path).convert('RGB') pixels = processor(image, return_tensors='pt')['pixel_values'] with torch.no_grad(): image_vec = image_model(pixel_values=pixels).logits image_vec = torch.nn.functional.normalize(image_vec, p=2, dim=-1) logits = fusion_model(text_vec, image_vec) return torch.softmax(logits, dim=-1).squeeze(0).tolist()逻辑说明:torch.no_grad()的作用是让推理过程不保存中间激活值,内存占用和耗时都会降下来。这段代码把整条推理链路串起来,是部署前的最小实现。热点是vectorizer.transform和Image.open,通常各自占 30% 以上的耗时,和模型本身关系不大。
参数说明:如果要在 web 服务里调用,建议提前把 TF-IDF 向量器序列化成文件,不要每次推理都重新fit。用joblib.dump(vectorizer, 'vectorizer.joblib')存下来,加载时用joblib.load,能避免重复加载耗时。
5.3 三个最容易在部署后翻车的问题
| 现象 | 原因 | 解决 |
|---|---|---|
| 推理结果每次不一样 | 没有切换model.eval() | 在预测前显式调用eval(),关闭 Dropout 等随机机制 |
| 图片缺失直接抛异常 | __getitem__里没有判空 | 统一抛FileNotFoundError,在数据加载阶段直接丢弃该样本 |
| GPU 上训练正常,切到 CPU 推理报错 | 保存的是 CUDA 张量 | 保存时用.cpu()把state_dict转成 CPU 张量 |
逻辑说明:这三条有一个共同点,都是“训练环境正常、部署环境异常”的典型坑。第一类最隐蔽,因为每次结果不同很难被察觉,如果结果在真实/虚假之间跳动,那基本上就是 eval 模式的问题。第二条在实际部署时几乎都会遇到,因为漏下载图片、命名不一致等风险清单很长,所以要预先想清楚策略。第三条发生在单机多卡或跨机器场景,torch.save默认会把参数留在原设备上,要用.cpu()收一下再存。
5.4 附上一条可复现的最小调用示例
import torch from PIL import Image model, image_model = load_model('best_model.pt') text = "BREAKING: 科学家发现彻底治愈癌症的新方法" img_path = 'images/test_001.jpg' prob = predict(text, img_path, vectorizer, processor, fusion_model, image_model) print(f"fake_prob={prob[1]:.4f}")逻辑说明:这个示例如果跑出来的fake_prob接近 1,说明文本特征和图像特征都指向“可疑”。如果fake_prob在 0.5 附近,通常是文本太短或图片类别太泛,需要回到 3.1 节重新检查数据对齐质量。
参数说明:示例里images/test_001.jpg要和news_data.csv中的image_id字段完全一致。假如数据里图片名是数字 ID,而 CSV 里是img_001.jpg,需要统一格式化。
6. 多模态识别闭环优化:从置信度阈值到可复用的检测工具
模型调通之后的优化方向,重点不在换个更大的模型,而在把检测流程固定下来、把文档补全、把整个目录整理成可以交接给别人的一套方案。这一步做得好不好,直接决定项目是一次性实验还是真正能用的工具。
6.1 加一个“不确定判定”:多模态识别中的拒识场景
虚假新闻检测和一般分类有一点不一样:它面对的是开放世界的真实输入,不是训练集里的干净分布。新闻里可能有不存在的图片、被裁剪的异常比例、罕见语言,这类输入不应该被硬分类成“真实”或“虚假”。处理办法是加一个简单的不确定判定逻辑:当 softmax 的两个概率都低于阈值(比如最大概率小于 0.7)时,返回“需要人工复核”。
def predict_with_reject(news_text, img_path, threshold=0.7): prob = predict(news_text, img_path, ...) max_prob = max(prob) if max_prob < threshold: return {'label': 'unknown', 'prob': max_prob} return {'label': 'fake' if prob[1] > 0.5 else 'real', 'prob': max_prob}逻辑说明:threshold=0.7这个数值不是固定的,要在验证集上画一个置信度分布,再看分错样本主要落在哪些区间。如果数据集里假新闻和真新闻本身有重叠,这个值要适当调低到 0.6。放进系统里之后,这条分支可以把模型最“没把握”的样本交给人工判断,而不是让模型硬猜。
6.2 文档说明里的关键一页:把模型报告、数据统计、复现命令写全
整套源码包交付过几次之后,我的经验是文档说明要能支撑从零复现。需要包含的内容大概有这几部分:环境依赖(Python 版本、库版本)、目录结构说明、最小可运行命令、训练参数、评估指标。下面是一个文档模板的骨架:
# 最小可运行命令 python train.py --data news_data.csv --img_dir images --epochs 20 --patience 4 # 评估命令 python evaluate.py --checkpoint best_model.pt --test news_test.csv逻辑说明:evaluate.py单独写一个脚本,不要和训练脚本耦合在一起。训练脚本每次运行都会把 checkpoint 存到当前目录,evaluate.py读取这个 checkpoint 输出分类报告。命令行的参数命名要稳定,--data、--img_dir、--checkpoint这些不要换名字,换一次,各种自动化和交接脚本全得跟着改。
6.3 我把这套方案收敛到可复用的四个习惯
从零到一做完一遍之后,对“基于 Python 的虚假新闻检测多模态识别”这个标题背后的落地路线有了比较完整的认识。以下几个习惯,是我现在每做一个类似项目都会固定执行的:
第一,每次跑实验之前,先把数据切分、标签分布、文本长度分布记录到一个固定的调试笔记里。多模态项目里 80% 的调试时间和“数据不对齐”有关,和数据本身的对齐情况写好,后面排查能省一晚上的时间。翻过几次车之后,你会发现大多数玄学问题最后都能追溯到数据准备阶段。
第二,每次模型结构或超参数变更,都保留一份参数记录。训练日志里至少要有:数据集规模、学习方法、batch size、训练集/验证集 Macro-F1、保存的模型路径。不记录这些,三天后回来看这段代码,就只剩黑匣子了。
第三,对“模型输出结果”始终保持怀疑。多模态模型在真实/虚假两类上都给出高概率时,要去看样本本身的特征,而不是继续调模型。模型输出的不确定边界区域是下一个版本数据扩充的重点。
第四,部署到本地服务时,预处理的失败和推理的失败要分开记录日志。图片读取失败、文本为空、特征维度不匹配,这三类错误不该出现在同一个日志文件里。分开后排查效率会高很多。
虚假新闻检测的多模态识别,做出来一个 0.91 F1 的模型不是终点,关键是把判断边界、数据来源、模型局限讲清楚。文档说明和数据质量检查清单,往往比模型权重本身更值钱。希望这篇文章里的代码和排查思路能帮你少走几趟弯路,在真实数据上跑出一版可信的结果。
本文还有配套的精品资源,点击获取