☰
基于CNN-LSTM的多模态虚假信息检测在昇腾CANN与MindSpore上的实战
2026/10/17 10:55:17 网站建设 项目流程

多模态虚假信息检测这个方向,我在昇腾环境上折腾了两个多月,踩了不少坑,也积累了些能直接上手的经验。这篇就围绕着一个基于CNN-LSTM的文本加图像联合判定模型,完整记录我是怎么在CANN和MindSpore这套技术栈上把它跑起来的,从模型设计、数据准备,到算子适配、性能调优,再到最后的评估与部署形态,尽量把每一步背后的理由也讲清楚。

1. 多模态虚假信息检测到底在解决什么问题

1.1 为什么单一模态不够用

虚假信息检测不是新话题,传统做法基本都聚焦在文本上,靠关键词、情感倾向、传播特征、用户行为这些信号做判断。但我在实际接触业务后发现,现在很多伪造内容走的是图文组合路线:一张被篡改的新闻截图,配一段语义上挑动情绪的标题,单独看文字看不出太大毛病,单独看图片也很难判断,可组合在一起就能在短时间内造成大范围误导。

真正推动我往多模态走的是一次实际测试。我拿某个资讯平台收集到的历史谣言样本做验证,纯文本模型在训练集上的F1能到0.87,看起来不错,但上线后遇到包含新闻截图、证书照片、聊天记录截图的样本时,准确率直接掉到0.71左右。问题很明显:模型根本没有利用图像里的信息,而造假者恰好擅长在图像上做手脚。这也是我最终决定在文本分支之外增加图像分支的核心原因。

多模态检测的思路并不复杂:同时提取文本和图像的特征,在融合层把它们拼起来,再交给分类器判断真伪。难的是两种异构特征怎么对齐、怎么融合才能让信息互补而不是相互干扰。CNN-LSTM的组合恰好提供了一条相对成熟的路径:文本可以靠TextCNN或LSTM捕捉局部语义和时序依赖,图像可以靠CNN提取视觉特征,最后在高层做融合。

1.2 CNN-LSTM组合的合理性

这里先解释一下为什么标题里会同时出现CNN和LSTM。纯文本虚假信息有一个典型特征是"前面铺垫、后面煽动",也就是说,整段内容的真伪判断需要结合上下文顺序,而不只是看几个孤立的关键词。LSTM擅长建模这种序列依赖,但纯LSTM在处理长文本时容易丢失局部关键信息,比如某个核心实体名词、某个被引用的数据数值。CNN在提取局部n-gram特征方面很强,能把"某地发生""官方通报""紧急提醒"这类局部模式抓出来。两者组合,等于同时拥有了局部特征提取能力和时序语义建模能力。

图像分支我采用的是ResNet的变体结构,但没直接端到端fine-tune整个大网络,而是在中等规模的数据集上冻结了前几层,只训练后半段。原因有两个:一是训练数据量不足以让深层网络充分收敛,二是昇腾平台上的算子适配需要额外工时,如果网络太复杂,前面跑通了后面出兼容问题,排查成本很高。总体模型结构大概是:

# 概念展示模型主要组成 # 文本分支:Embedding -> TextCNN -> BiLSTM -> 全连接 # 图像分支:ResNet18(后段可训练) -> 全局池化 -> 全连接 # 融合层:拼接 -> 全连接 -> Dropout -> 分类

这个结构不是最前沿的,但它稳。某种意义上,在昇腾上做工程落地,稳定性和可复现性比模型刷点更重要。

2. 昇腾CANN与MindSpore:这套技术栈的适配逻辑

2.1 算力从哪来:昇腾硬件与CANN的定位

先说硬件。昇腾设备上的AI Core是一种专为神经网络计算设计的架构,和GPU的CUDA Core设计思路差异很大。如果直接拿PyTorch代码往上升腾上跑,基本跑不通,因为PyTorch生态默认面向CUDA,算子层面的对接完全不在一个语言体系里。CANN就是把上层AI框架和底层昇腾硬件连接起来的中间层,它提供算子库、图编译引擎、运行时管理和性能Profiling工具。

在开始项目之前,我的环境准备清单大概是这样的:

# 确认昇腾设备状态 npu-smi info # 安装CANN工具包后,设置环境变量 source /usr/local/Ascend/ascend-toolkit/set_env.sh # 安装对应版本的MindSpore pip install mindspore==2.2.0 --index-url https://ms-release.obs.cn-north-4.myhuaweicloud.com

为什么不直接说CUDA那套东西?因为昇腾上的算子执行流程是:算子先经过CANN的图编译阶段转换成适配AI Core的指令,再被调度到硬件上执行。也就是说,编译优化这一层比CUDA生态介入得更深,它对静态图的依赖也更强。

2.2 MindSpore的图模式与PyNative模式选型

MindSpore有两种执行模式:PyNative模式和图模式(GRAPH_MODE)。PyNative模式按行执行,调试友好,但性能差;图模式会把整个计算图编译成一个整体再执行,昇腾AI Core的利用率明显更高。我在初期用PyNative模式验证模型逻辑,正式训练切换到图模式,这个切换过程本身就踩了不少坑,后面专门讲。

迁移到这个技术栈之后,我开始重新审视一些PyTorch时代的习惯。比如在PyTorch里,动态shape很常见,不同长度的序列padding后再处理也很自然。但在图模式下,MindSpore对动态shape的支持有限,一旦某个维度无法静态推断,整个图编译就会失败。解决办法是把输入统一resize到固定尺寸,文本也做固定长度截断。这个限制看着笨,但反过来逼着我把数据pipeline做得更规范,模型性能反而更稳定了。

在多模态场景中,还要注意数据加载效率。MindSpore官方建议用MindRecord格式组织数据,它能和昇腾的数据预处理硬件配合,减少Host侧CPU拷贝的压力。我实测下来,同样的数据从TFRecord换成MindRecord后,单步训练时间缩短了大概18%。考虑到整个训练要跑几十个epoch,这个收益非常可观。

2.3 选型时我对比过的其他方案

最初我也考虑过用MindSpore加载PyTorch的预训练权重,或者干脆用ONNX中转,但最终都放弃了。理由比较实际:

方案优点问题
直接迁移PyTorch权重生态资源多层名和权重格式差异大,手动对齐太耗时
ONNX中转兼容性好昇腾ONNX算子支持不全,部分自定义层转不了
在MindSpore下重新训练完全可控需要额外训练算力
MindSpore预训练模型库开箱即用多模态模型选择少,需要二次开发

我最后选了"MindSpore下重新训练为主,预训练权重辅助初始化"的策略。文本Embedding层用随机初始化,图像分支用MindSpore版本ResNet的预训练权重初始化前几层。这样既避开了权重格式转换的麻烦,又能让图像分支有一个比较好的起点。

3. 从零搭建训练流程:数据、模型与调优

3.1 多模态数据集的构建与预处理

数据永远是最耗时间的一环。我手上这批数据来自某内容平台的举报通道和人工审核记录,总共75962条稿件,覆盖了新闻、公告、健康科普、消费维权几个常见类别。其中标注为虚假的占34.5%,真实的占65.5%。由于是内部数据,格式比较乱,预处理工作量很大。

文本侧,我做的操作包括:

  • 全角半角统一,特殊符号替换,去除不可见字符
  • 简体中文分词,这里没有用网上常见的大词表,而是结合平台语料训练了一个约20万词的中型词表
  • 过滤停用词和低频词,低频词的门槛是出现次数小于5就丢弃
  • 文本序列长度统一截断到128个token,超过的保留前128个,不足的padding到128

图像侧,大多数稿件的配图尺寸不一致,有的甚至是从PDF导出的超长截图。我没有直接resize成正方形,而是先做边缘检测裁剪,去掉大块白色背景,再统一缩放到224乘224。这一步很关键,因为新闻截图往往有大片无信息区域,直接resize会把有效信息压缩成很小的区域,模型很难学到东西。

给文本和图像打标签倒是比较简单:一条稿件只要被审核确认包含关键事实错误,就标记为虚假,跟配图是否被篡改没有直接关系。但这里有个隐藏问题:图像可以作为异常特征依据,也可以只是无关装饰。如果配图跟文字完全不相关,模型会学到"图不重要"的微弱特征,导致图像分支的梯度不稳定。所以我在预处理里又加了一个简单的相关性判断,把明显无关的图文对单独标记出来,在数据加权时给这类样本更高权重。

3.2 模型结构定稿与关键参数说明

我把最终模型写成了相对紧凑的结构,整体参数控制在2800万左右。这个体量在昇腾平台上属于中小规模网络,既能体现多模态特征融合的能力,又不会因为过于庞大导致算子编译时间过长。

文本分支我用的是嵌入层加TextCNN加BiLSTM再加全连接的结构。嵌入维度128,TextCNN有三组卷积核,窗口大小分别是1、2、3,每组64个卷积核。卷积输出接最大池化,然后把不同窗口的结果拼接起来,送进一个隐藏状态维度128的双向LSTM。BiLSTM的输出取最后一步的隐藏状态,经过一个256维的全连接层,得到128维的文本语义向量。

图像分支用的是ResNet18的变体,把第一个卷积核的感受野从7乘7改成了5乘5,更适配截图类图像上的局部文字纹理。后三层可训练,前两层的参数固定不变。全局池化之后接一个128维的输出,得到128维的图像语义向量。

融合层做的事情很简单:文本向量和图像向量拼接成256维,经过一个128维的隐藏层,再经过一个sigmoid输出层。只不过我在融合之前把两个分支的输出各自过了一个LayerNorm。理由是两者的特征分布差异很大,文本特征是稀疏语义型,图像特征是密集视觉型,不归一化直接拼接,梯度容易被某一侧主导。

# 核心结构示意(精简版) class MultimodalModel(nn.Cell): def __init__(self): super().__init__() self.text_branch = TextBranch() self.image_branch = ImageBranch() self.fusion = Dense(256, 128) self.classifier = Dense(128, 1) self.loss_fn = BCELoss() def construct(self, text, image): tv = self.text_branch(text) iv = self.image_branch(image) f = self.fusion(Concat((tv, iv))) out = self.classifier(f) return out

最关键的一个设计决策是:图像分支的损失权重稍微高于文本分支。我在实验中发现,单纯把两个分支的特征拼起来训练,模型会自动偏向文本信息,图像分支学到的大部分是背景颜色、构图这类浅层特征。为了强制模型重视图像中的篡改痕迹,我在总损失中对图像分支的梯度做了额外缩放,具体做法是融合层之前图像分支的梯度乘以1.2倍。就这一个小改动,让图像分支从纯装饰性变成了有效判断依据。

3.3 训练过程中的损失函数与评估指标

损失函数用的是加权二分类交叉熵。由于数据集中虚假样本只占34.5%,正负样本比例接近1比2,我按负类权重0.8、正类权重1.2来调整,避免模型把所有样本都预测成真实类。这个权重的设置不是拍脑袋,我对几组候选值做了一轮小规模实验,发现权重比在1.2比0.8时P/R比较均衡,再高就容易把大量真实样本误判成虚假的。

评估指标不能只看准确率。准确率在这种类别不均衡的数据集上容易虚高,哪怕模型什么都不学,只要全部预测为真实就能拿到65.5%的准确率。所以我用了宏平均F1、召回率、AUC和PRAUC四个指标。宏平均F1是综合判断,AUC用来观察排序能力,PRAUC则对正样本变化更敏感,在虚假信息检测这种"宁可多抓不可漏放"的场景里尤其重要。

训练过程中的其他参数我也简单列一下:

  • 优化器:AdamW,初始学习率3e-4
  • 学习率策略:余弦退火,最低衰减到1e-5
  • 批大小:32,昇腾单卡
  • 训练轮数:35轮
  • 混合精度:开启AMP,损失缩放初始值1024
  • 权重衰减:1e-5

早期我试过大批量64,但发现图像分支在BatchNorm层上开始不稳定,loss震荡明显。降到32后好了很多,这也间接说明多模态模型对BatchNorm的参数敏感,批大小不能照搬单模态任务的经验。

4. 昇腾环境下的实际踩坑记录

4.1 算子兼容性引发的推理崩溃

我遇到的问题主要集中在图模式编译阶段,最典型的是一次非常诡异的崩溃。模型训练到第17轮的时候,突然在验证阶段报错,错误信息指向一个我完全没有用到的算子。后来花了整整两天才定位出原因:MindSpore图模式下会自动进行算子融合,某个中间环节把我自定义的卷积结构重写成了另一个底层算子,而这个算子在当前CANN版本上的实现有缺陷,导致验证阶段数值溢出。

排查链路大概是这样的:

  1. 先确认是不是训练代码写错了,逐行检查数据加载、标签对齐,没有发现问题
  2. 把验证阶段改成PyNative模式,模型正常,说明问题出在图模式的算子编译
  3. 使用mindspore profiler工具导出算子级耗时和精度信息,对比正常轮次和异常轮次
  4. 定位到问题算子后,在模型定义处用算子级黑名单接口强制禁用该算子融合
  5. 重新编译图,验证阶段恢复稳定

这个过程让我对MindSpore的图编译机制有了更直观的理解。昇腾平台上的算子融合是为了提升效率,但它会隐藏掉很多中间细节,一旦底层实现有缺陷,开发者看到的错误信息往往非常不直观。遇到这种情况,先切换到PyNative模式确认代码逻辑,再用Profiling工具定位具体算子,是最有效的排查思路。

4.2 显存分配与batch size的博弈

昇腾在显存管理上和CUDA设备不完全一样。刚开始我在PyNative模式下甚至尝试了batch size 64,结果一个epoch跑到一半就ReportError,提示内存不足。切换到图模式后,显存分配发生在编译阶段,不运行到那个位置根本不知道会不会爆,非常憋屈。

我的实际解法是先用小batch size跑通完整训练流程,然后逐步增大batch size测试峰值显存。每次调整后重新编译图,观察npu-smi输出的显存占用。最终稳定在batch size 32,单卡显存占用约9.6GB,还剩接近两成余量。这里的经验是:昇腾平台上不要用PyTorch时代的"按整数倍扩大batch size"思路,因为不同的shape会触发不同的算子优化策略,有时候batch size从32翻倍到64,显存占用可能不是翻倍而是跳变,一定要实测。

4.3 混合精度训练的收益与代价

手动做了像深度学习这么久,混合精度带来的收益和代价我都很清楚。昇腾平台对FP16的支持比普通GPU更激进,但多模态模型中文本分支的Embedding层和LSTM这类结构在FP16下特别容易溢出。文本Embedding参数经常出现很小的数值,一旦转成FP16,精度损失会被后端的Softmax或LayerNorm放大,造成训练loss飘忽不定。

为了规避这个问题,我最初直接把整个网络设置成FP32精度,训练速度自然就慢了。后来在MindSpore的amp接口里做了策略调整:只让图像分支的卷积层使用FP16,文本分支和融合层保留FP32。这样既利用了昇腾AI Core对FP16卷积的高吞吐,又保住了文本语义特征的精度。实测训练速度提升了约43%,最终F1只下降了0.4个百分点,基本可以忽略。

不过这里有一个隐藏问题:手动设置amp范围后,LossScale需要监控得更频繁。我把LossScale的更新策略改成了动态调整,初始化1024,之后每10步检查一次溢出标志,如果溢出频率超过5%,就降低LossScale一半。这个机制保证了整个训练过程不会因为某一层数值异常而全盘崩溃。

5. 实验结果与分析:我们的模型到底能抓出多少谣言

5.1 与基线方案的量化对比

我一直坚持:模型好不好,必须放到同一批数据、同一个评估流程里对比。我做了三组对照实验。

第一组是纯文本模型,结构就是TextCNN加BiLSTM,不输入图像。第二组是纯图像模型,直接用ResNet18做一个二分类。第三组是我们最终的多模态模型。

模型宏F1召回率准确率AUC
纯文本模型0.8650.8420.8830.931
纯图像模型0.7030.6120.7550.802
多模态模型0.9040.8910.9180.964

从数据上看,多模态模型相比纯文本模型,宏F1提升了将近四个百分点,召回率提升了约五个点。这意味着在同样的测试集上,原来会被漏掉的虚假样本,现在能多抓回来5%左右,这个增量在内容安全场景里已经很有实际意义了。

一个值得注意的发现是:纯图像模型虽然整体表现差,但在某些特定类别的样本上,它的输出置信度比多模态模型还要高。我去翻了这些样本,发现它们几乎都是图片本身包含明显篡改区域的情况,比如液化过的人脸、拼接处的光线不一致、复制粘贴残留的边线。这说明视觉篡改线索在某些场景中确实比文本线索更具决定性。

5.2 错误样本分析

随便调参刷分不能说明问题,真的能上线才是硬道理。我专门挑出50个被多模态模型判错的样本做人工分析,大致能分成四类:

第一类是"文本正确、图像错误"。这类稿件的文字描述没问题,但配图被PS过,或者虽然是真的但是与文本描述的时间地点对不上。模型把图像中的异常识别出来了,但文本信息太强,融合时拉不回来。

第二类是"图像正确、文本误导"。图像是新闻原图,但文字在关键数字上做了篡改,比如把"死亡率0.1%"改成"死亡率10%"。文本分支在语义层面没抓到这种细微的数值错误,原因是数字特征在分词后容易被当成普通token,缺乏数值逻辑校验。

第三类是"图文都无异常,但逻辑关系可疑"。有些虚假信息使用的图片是真实的,文字也全部来自真实报道,但拼凑在一起描述的是一个从未发生过的事件。这类样本连我们人工审核都要花大量时间核对原文,模型判错完全可以理解。

第四类是极端对抗样本。有些造假者已经开始针对检测模型做规避,比如轻微改变图片色调以绕过篡改检测,或者在文本中穿插大量无关形容来稀释关键词密度。这类样本在测试集中占6%左右,判错率接近50%。

这些错误案例给我最大的启发是:多模态模型并没有想象中那么鲁棒。它确实能弥补单一模态的盲区,但也引入了新的融合盲区。后续如果要继续提升,关键不在换更花哨的网络结构,而在于引入跨模态一致性校验,比如让模型独立判断"图文是否匹配",再与真假分类结果做交叉约束。

5.3 在真实业务场景中的部署形态

训练完成之后的部署阶段也有不少值得记录的点。由于我们的业务方需要的是"快速初筛辅助人工审核",所以并没有把模型做成一个独立的线上服务,而是嵌入到已有的审核流中作为一种前置过滤。全量稿件先进多模态模型,只有在模型置信度低于0.6的区间才转人工。这条阈值是跟审核团队一起定的:他们可以接受的漏判率为2%以内,以此为基准把阈值调到了0.55到0.65之间。

昇腾平台提供的推理接口可以无缝加载训练好的MindSpore模型,我做了如下部署优化:

  • 把模型编译成静态图离线序列化文件,加载时间从4.2秒缩短到0.6秒
  • 开启batch推理,单次同时处理8条图文对,吞吐量达到每秒钟约120条
  • 输出层只用置信度分数,不返回中间特征,减少结构数据的序列化开销
  • 部署侧做并发控制,防止突发流量把推理服务打挂

实际压测结果是:在单张昇腾310推理卡上,整个服务稳定在每秒80到120条的处理速度,平均单条延迟35毫秒左右。考虑到这是对全平台稿件实时打标,这个性能已经够用。唯一需要注意的是,静态图序列化后模型的输入shape被锁死了,文本长度和图像尺寸都不能再改,所以线上预处理逻辑必须和训练时保持完全一致。

6. 从这次实战中提炼的几个能复用的经验

最后再说几个这次项目里被验证过的小技巧,都是文档里不会写的那种。

第一个是数据预处理的顺序会影响训练稳定性。之前我先做图像resize再检测异常区域,结果大量关键边缘信息被压缩模糊掉。后来换成先做边缘检测,再根据检测结果做内容裁剪,最后统一resize,图像分支的AUC单独涨了四个点。顺序问题看似不起眼,实际影响很大。

第二个是保存模型权重一定要配合记录优化器状态。当时为了省存储只保存了模型结构加参数,恢复训练时优化器状态归零,导致前三个epoch的loss明显异常,相当于白白浪费了两天。如果是在昇腾平台做长周期训练,建议每两个epoch自动保存一次checkpoint,并且把优化器的state_dict一起存下来。

第三个是梯度缩放范围不要想当然。在FP16训练里,初始参数越小越容易被下溢吞掉,我一开始用固定LossScale=1024,结果文本分支梯度逐步消失,模型只靠图像分支在学。改成动态LossScale之后,问题立刻消失。很多时候模型性能上不去,不是结构问题,而是数值稳定性问题。

第四个是学会利用MindSpore的Profiling工具看待优化瓶颈。比如AI Core利用率达不到90%以上,我会优先检查数据pipeline:是不是CPU预处理太慢,是不是Graph输入在Host和Device之间反复拷贝,是不是存在过多小算子调度。先把瓶颈定位到宏观环节,再去做细节优化,效率比盲目调参高太多了。

做个简单总结的话,昇腾CANN加MindSpore这套技术栈确实有一条不低的学习门槛,但从算子优化、编译期检查到部署支持,它解决的是端到端的问题。文本加图像的多模态虚假信息检测,在这套平台上跑出来的效果不比我在其他主流框架上做的差,而且部署更省心。如果你也在考虑在昇腾设备上做多模态任务,建议先从小模型跑通图模式,再逐步增加分支复杂度,这样排错压力会小很多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询