简介:面向毕业设计、期末大作业与课程设计场景的多模态情感分析系统,基于Python开发,支持文本、语音、图像、视频四种输入形式。项目采用多模态融合思路,覆盖数据预处理、特征提取、模型训练与结果可视化等环节,代码结构清晰、注释完整,并附带项目文档与数据集,适合机器学习初学者参考学习,也可作为完整课设方案直接使用。压缩包共20个文件,以pickle数据文件、py源码、zip数据集压缩包、pdf文档和md说明为主,整体大小约56.91MB,目录涵盖imi/mosi/mosei等公开数据集及对应处理代码,便于按模块理解数据装配与模型构建流程。目前已有96人浏览学习该资源,适合需要快速搭建多模态分析项目、完成课程报告或答辩演示的开发者。资源经调试可正常运行,文档中另含流程图与结果示例,可减少上手阻力,帮助使用者快速掌握从数据到情感判别结果的完整链路。
1. 多模态情感分析系统:为什么只靠文本通道在真实场景总是不够用
今年上半年我接过一个客服质检需求:工单文本只有三四十个词,看不出情绪,但配合录音能听出客户已经在上火。把文本、语音两路特征合并后,愤怒识别的召回率从四五成提到了六成出头——这就是多模态情感分析系统的价值:把文本、语音、图像、视频四类输入统一处理,汇成一个可解释的情感判断,而不是每个模态各给一个孤立分数。这类项目通常以源码+文档+数据集的形式打包,拿到手能直接复现多模态融合、替换自己的数据重训。适合做毕设的学生,也适合做短文本舆情、客服质检、影视分析的工程师。下面按特征提取、核心代码、训练参数、避坑细节的顺序拆开。
2. 四路特征怎么提:文本、语音、图像、视频各自的家底
多模态情感分析的难点从来不在“模型有多深”,而在四个模态的数据形态完全不一样。文本是离散的 token 序列,语音是波形采样点,图像是像素矩阵,视频是带时间轴的一组图像。这一章先把四路特征从原材料里抽出来的方案讲清楚,因为后面所有融合和参数调节都建立在这步之上。
2.1 文本情感特征:CLS 向量与词典特征拼接
常见做法是直接用预训练语言模型拿句向量。以 RoBERTa 为例,输入“这个方案看着还行”,取最后一层 [CLS] 位置的输出作为整句表示,维度是 768。单独用 BERT 类模型已经能覆盖大部分文本情感信息,但在多模态系统里我一般不会只靠它:预训练模型对否定、反讽的捕捉并不总是可靠,所以再拼接一组传统词典特征。
词典特征指统计情感词个数、否定词个数、程度副词的加权和,以及感叹号数量这类显式信号。维度很小,一般 10 到 20 维,但它能补充预训练模型在小样本场景下的盲区。两者拼接后,文本通道变成一个[768 + k]维向量,k 取 12 左右就够。
特征提取时几个参数比较关键:max_len 设 128,过短会截掉转折信息,过长在 batch 里浪费显存;嵌入层和编码层保持预训练权重不变,只在后面挂一个 128 维的投影层做跨模态对齐。这里不建议用 LSTM 重训,多模态系统的数据量通常喂不饱一个纯文本语言模型。
2.2 语音特征:eGeMAPS 低维特征和 mel 谱图怎么选
语音通道我见过两种主流做法,适用边界很清楚。第一种是用 openSMILE 提取 eGeMAPS 的 88 维低层描述符,包含斜率、响度、能量相关统计量。优点是参数少、可解释、计算轻,适合总样本量只有几千条的小数据集。缺点是它把时间结构压平了,语气里的先后变化保留得很少。第二种是把波形转成 mel 谱图,再用一层小 CNN 或直接把谱图序列过 Transformer。mel 谱图保留了时频结构,对“从平静到爆发”这类过程性情绪更敏感。如果项目里语音情绪种类多、上下文长,我优先做 mel 谱图方案。
mel 谱图的参数我的起点是 16kHz 采样率、25ms 窗长、10ms 帧移、80 个 mel bins。时长两秒的语音会得到约 199 帧的谱图序列,这个长度直接进 Transformer 有些浪费,可以先在时间维上用 1D 卷积做 4 倍降采样,再进后面的时序模块。无论走哪条路,最后都把语音特征投影到和文本相同的表示维度,比如 256 维,方便后续融合。
2.3 图像特征:人脸表情概率分布比 CNN 特征更好接
这里的图像主要指包含人脸的静帧。直接拿一张图过 ResNet50,取出最后一个池化层的 2048 维特征,不是不可以,但问题很明显:这个特征主要描述“图片里有什么”,而不是“人是什么情绪”。多模态系统要的是与情感语义对齐的表示,所以我更推荐先跑一个人脸检测和表情识别,输出 7 类概率分布:生气、厌恶、害怕、开心、难过、惊讶、中性。
概率分布有三个好处:维度固定为 7,和文本、语音的投影层做向量拼接时几乎不需要额外变换;每个值天然在 0 到 1 之间,不会像深层卷积特征那样动辄上百的尺度,导致后续融合时某一维把其他模态压死;结果可解释,测试出错时能直接看到“原来是开心被识别成了惊讶”。如果不想引入额外人脸模型,也可以用 CLIP 或 ViT 的 [CLS] 向量接投影层,但样本量小的时候大概率不如表情概率分布可靠。
2.4 视频通道:视频人物情感分析常踩的坑是整段塞进模型
视频人物情感分析容易犯的错是把整个视频逐帧过 3D CNN,这在离线实验里可行,但显存和训练时间都难以接受,而且短视频情感往往只体现在关键几帧的表情变化上。我一般先把视频按 2fps 抽帧,再通过人脸检测把最大人脸裁剪成 224×224,固定取 16 帧组成一个片段;如果视频不足 16 帧,做首尾复制或插值补齐。
16 是这个系统常用的折中值:太短会丢掉表情渐变,太长会让模型学到的多是人脸的轻微抖动而不是情绪。抽完帧后,每一帧独立送进表情识别模型得到 7 类概率,这样一个短视频在图像通道就变成 16×7 的序列,再在后面过一个轻量级 Transformer 或直接做平均池化变成 7 维。注意“平均池化”不是万能解,它会把最强烈的几帧表情稀释掉,如果你发现预测结果总比人工标注温和,就把平均池化换成交权平均,权重来自一个可学习的注意力层。
2.5 模态对齐:时间戳不统一,后面全是空谈
四路特征各自提取完之后,必须回到同一个时间参考系。常见坑是文本和语音来自不同记录系统,文本记了“开始时间 10:05:03”,语音文件却是从 10:05:00 开始录的,差出来的 3 秒会让模型把上一句话的情绪配给当前音频。我在系统里统一用毫秒做时间戳,并且用一个样本清单把每个模态的时间区间都显式写出来。样本清单至少包含这些列:
| 字段 | 示例 | 说明 |
|---|---|---|
| sample_id | 0001 | 全局唯一样本号 |
| text_start_ms / text_end_ms | 15000 / 20000 | 文本片段起止时间 |
| audio_start_ms / audio_end_ms | 15000 / 20000 | 语音切片起止时间 |
| face_conf | 0.92 | 人脸检测置信度 |
| frame_indices | 0,1,2,…,15 | 抽帧序号,对应视频时间点 |
判断对齐是否正确,我会在训练前写一个小的核查脚本:随机抽 20 条样本,把文本内容、语音波形、16 帧缩略图打在一个画面上人工扫一遍。这一步不能省,因为多模态系统最常见的预测翻车,根源不在模型,而在喂进模型的四份数据本来就不是同一件事。如果你拿到手的数据集已经在清单里给了 start_ms 和 end_ms,也别默认它正确,抽查确认后再开始调参。
3. 把源码拆开:目录、数据流与融合模型怎么搭
拿到“源码+文档+数据集”的项目,第一反应不要去看模型文件,先看目录。一个规范的工程会按配置、数据、特征、模型、训练、评估这几块来组织。下面以我习惯维护的多模态情感分析系统结构为例,把每个目录为什么存在讲明白,你再对照手头的源码就很容易定位问题。
| 目录 | 职责 | 关键文件 |
|---|---|---|
| config/ | 全部超参,包括各模态学习率、抽帧数、特征维度 | train.yaml, data.yaml |
| data/ | 样本清单读取、数据增强、按时间戳对齐 | loader.py, augment.py |
| features/ | 四路特征离线抽取和缓存 | text_extractor.py, audio_extractor.py, video_extractor.py |
| models/ | 投影层、融合模块、任务头 | fusion.py, heads.py |
| train.py | 训练循环,跨模态 batch 构造 | train.py |
| evaluate.py | 消融实验和指标计算 | evaluate.py, metrics.py |
没有 config 目录、参数直接硬编码的源码,我建议你顺手补一个。因为多模态实验跑一次很贵,参数散落在代码里会让实验复现变成玄学。
3.1 先跑离线特征缓存,不要在训练时重复抽特征
多模态系统最消耗资源的步骤不是反向传播,而是四个模态的原始数据同时涌进显存。我在第 2 章提到的特征提取,在实际工程里一定是离线完成并缓存到磁盘的。下面这段代码就是特征缓存模块的核心逻辑,它在训练前把所有样本的四路特征算好,存成 npz 文件。extract_text_feat、extract_audio_feat、extract_face_prob 这三个函数分别封装了 2.1 到 2.4 的抽取细节,这里只展示缓存流程。
# features/cache_all.py import json, numpy as np from pathlib import Path def cache_all_features(manifest_path, out_dir, frame_num=16): out_dir = Path(out_dir) out_dir.mkdir(parents=True, exist_ok=True) samples = json.loads(Path(manifest_path).read_text()) for item in samples: # 每条样本四个模态的特征,统一转成 float32 text_feat = extract_text_feat(item["text"], max_len=128) # (780,) audio_feat = extract_audio_feat(item["audio_path"], sr=16000) # (256,) face_feat = extract_face_prob(item["video_path"], frame_num) # (16, 7) video_feat = temporal_aggregate(face_feat, method="attn") # (7,) np.savez( out_dir / f"{item['sample_id']}.npz", text=text_feat.astype("float32"), audio=audio_feat.astype("float32"), image=face_feat.astype("float32"), video=video_feat.astype("float32"), label=np.array(item["label"], dtype="int64"), )逻辑说明:代码按样本清单逐条读取文本、语音、视频路径,分别调用对应抽取函数。注意 text_feat 这里 780 维 = 768 维 CLS + 12 维词典特征;image 保留的是 16 帧的逐帧表情概率,而 video 是已经聚合过的 7 维特征,这样设计是为了同时支持后面两种融合方式。
参数说明:frame_num 固定为 16,对应第 2 章说的抽帧策略;音频统一重采样到 16kHz,采样率不一致会导致 mel 谱图的频率轴失真。所有特征存成 float32 而不是 float64,多模态样本动辄几万条,float64 会让缓存文件增大一倍,精度收益几乎为零。
3.2 融合模型:特征投影、跨模态注意力与任务头
单模态特征并行提取完之后,需要进入融合网络。我采用早期与中间融合结合的结构:早期融合指四路特征投影后直接拼接,中间融合指在拼接之后加一层跨模态注意力,让文本向量去查询语音和图像特征里与当前情绪最相关的部分。下面给出 models/fusion.py 的骨架。
# models/fusion.py import torch import torch.nn as nn class CrossModalFusion(nn.Module): def __init__(self, dims, proj_dim=256, num_heads=4, dropout=0.2): super().__init__() self.text_proj = nn.Sequential( nn.Linear(dims["text"], proj_dim), nn.LayerNorm(proj_dim)) self.audio_proj = nn.Sequential( nn.Linear(dims["audio"], proj_dim), nn.LayerNorm(proj_dim)) self.image_proj = nn.Sequential( nn.Linear(dims["image"], proj_dim), nn.LayerNorm(proj_dim)) self.video_proj = nn.Sequential( nn.Linear(dims["video"], proj_dim), nn.LayerNorm(proj_dim)) self.cross_attn = nn.MultiheadAttention( embed_dim=proj_dim, num_heads=num_heads, dropout=dropout, batch_first=True) self.classifier = nn.Sequential( nn.Linear(proj_dim * 2, 128), nn.ReLU(), nn.Dropout(dropout), nn.Linear(128, 3)) self.dropout = nn.Dropout(dropout) def forward(self, text, audio, image, video): t = self.text_proj(text).unsqueeze(1) # (B, 1, 256) a = self.audio_proj(audio).unsqueeze(1) # (B, 1, 256) i = self.image_proj(image) # (B, 16, 256) 逐帧特征 v = self.video_proj(video).unsqueeze(1) # (B, 1, 256) context = torch.cat([a, i, v], dim=1) # (B, 18, 256) attn_out, _ = self.cross_attn(t, context, context) # 文本作为 query attn_out = attn_out.squeeze(1) # (B, 256) fused = torch.cat([t.squeeze(1), attn_out], dim=-1) logits = self.classifier(self.dropout(fused)) return logits逻辑说明:forward 里文本向量作为查询向量,语音、逐帧图像、聚合视频一起作为键值序列,跨模态注意力会让文本去“挑选”其他模态里最值得参考的时间步。例如客户说了“没事”,语音通道已经不耐烦,图像通道某帧嘴角下撇,注意力会把这些帧的权重抬高。最后分类头输入是文本投影和注意力输出的拼接,因为文本通常贡献最大,所以要保留一条直连路径,不能被其他模态的噪声稀释。
参数说明:proj_dim 取 256,四个模态全部投影到同一维度,避免某一模态原始维度过大主导融合;num_heads 取 4 对短视频足够,取 8 在小样本上容易过拟合;dropout 在 0.2 到 0.3 之间调。这里的分类输出为 3 类(负向/中立/正向),如果你的标注是 valence、arousal、dominance 三维连续值,只需要把最后的 nn.Linear(128, 3) 换成输出 3 个回归值的头即可。
3.3 训练入口:一个 batch 里四路数据怎么拼装
特征缓存好以后,训练时不再读原始音频和视频,只读取 npz 缓存,数据装载速度会快接近一个量级。训练入口需要做成能同时拿到 text、audio、image、video 四个 key 的 dataloader,模型 forward 一次消费一条样本的四路特征。
# train.py 中的 Dataset 定义 from torch.utils.data import Dataset, DataLoader from pathlib import Path import numpy as np, torch class MultimodalDataset(Dataset): def __init__(self, cache_dir, labels): self.samples = sorted(Path(cache_dir).glob("*.npz")) self.labels = labels def __len__(self): return len(self.samples) def __getitem__(self, idx): data = np.load(self.samples[idx]) return { "text": torch.from_numpy(data["text"]), "audio": torch.from_numpy(data["audio"]), "image": torch.from_numpy(data["image"]), "video": torch.from_numpy(data["video"]), "label": torch.tensor(self.labels[idx], dtype=torch.long), }逻辑说明:Dataset 直接读取缓存目录里的 npz 文件,避免了在训练循环里调 openSMILE 或人脸模型,这也是训练速度的关键。数据增强在这个方案里放在缓存之前做:文本同义词替换在语料阶段完成,语音加噪在抽特征时完成,训练时不再做在线增强。因为多模态的增强组合空间太大,在线增强会让实验变量失控。
参数说明:batch size 我一般从 16 起步,特征缓存模式下显存占用主要来自投影层和注意力,显存不够时优先减小 batch 而不是减抽帧数。DataLoader 的 num_workers 设成 CPU 核心数的一半,设太多会在每个 epoch 启动阶段反复 fork 进程,卡顿反而更明显。
4. 训练参数与收敛技巧:十几个设定把模型从玄学拉回工程
多模态系统训不出来,大多数时候不是架构写错了,而是参数没有按模态特性去配。我的血泪经验是:把四个模态塞进同一个网络、用同一套学习率和同一套增强策略,是最常见的翻车原因。下面按优先级把参数设定捋一遍。
4.1 学习率按模态分开:文本 1e-5,视觉 1e-4 起步
预训练文本模型内部的特征已经很成熟,学习率过大会把 BERT 这类模型学到的语义破坏掉。常见做法是文本分支用 1e-5 到 2e-5,语音和图像分支用 1e-4,融合层也用 1e-4。同一个优化器里按参数组区分即可:
# train.py 中优化器配置 optimizer = torch.optim.AdamW([ {"params": text_model.parameters(), "lr": 1e-5}, {"params": audio_model.parameters(), "lr": 1e-4}, {"params": image_model.parameters(), "lr": 1e-4}, {"params": fusion_model.parameters(), "lr": 1e-4}, ], weight_decay=0.01)逻辑说明:这样配置的理由是文本分支如果学习率过高,两三轮之后文本特征分布就会漂移,而漂移的后果往往不是立刻变差,而是整个融合模型不收敛。视觉分支本来就是在小数据集上微调,用 1e-4 比较合适。weight_decay 设为 0.01 对融合层足够,但要注意不要对 LayerNorm 的 scale 参数做 weight decay,实现时把 norm 层参数排除在外。
参数说明:warmup 比例设 0.1,总共 10 个 epoch 的话,前 1 个 epoch 学习率从 0 线性爬到目标值。文本分支的 1e-5 在 warmup 阶段可以不参与,因为预训练权重已经足够好。不推荐 one-cycle 这类复杂调度,多模态实验一次要跑很久,调度器越简单越容易复现。
4.2 融合前的归一化:LayerNorm 的位置比结构更重要
很多翻车现场是原始特征直接 concat,结果某个模态的特征值范围是 -10 到 10,另一个是 0 到 1,分类器实际只看到了值域大的那一路。我在第 3 章的融合代码里已经做了投影加 LayerNorm,这里再强调它的位置:先 Linear 投影,再 LayerNorm,再进注意力。BN 在融合层里不如 LayerNorm 好用,因为多模态 batch 内部方差受单条样本影响大,BN 的统计量波动大,小 batch 下更明显。
dropout 设在两个位置:投影层输出之后和分类头进入前。0.2 起步,如果训练集只有几千条,直接调到 0.3。加 dropout 不是因为玄学,而是多模态特征之间存在冗余,dropout 能让模型不依赖某一个通道;比如只靠文本就能分对的样本,没有 dropout 时模型会偷懒忽略语音信号。
4.3 损失函数:离散情感用 Focal Loss,连续维度用 MSE
情感标注有两种主流格式,对应两套损失函数选型。如果数据是情绪类别(生气、开心、难过),直接用交叉熵的问题是负样本往往多于正样本,愤怒只占工单总量的 10%,模型会倾向于全部预测中性。Focal Loss 会压低易分类样本的权重,让模型被迫关注那些不好分的愤怒样本。
| 标注形式 | 常用损失 | 附加约束 |
|---|---|---|
| 离散类别(3/5/7类) | Focal Loss,gamma=2,alpha 按类别频率设置 | 评估用 macro-F1 |
| valence-arousal 连续值 | MSE 或 Smooth L1 | 希望相关度高时用 CCC 损失 |
| dominance 等有序值 | 排序损失或回归 | 输出层做 sigmoid 限制范围 |
gamma 取 2 是基线,alpha 设成“1 / 类别出现频率”再归一化。连续值场景我一般用 MSE,但要注意单一 MSE 对整体系统性偏差不敏感,例如模型所有输出都比标注低 0.2,MSE 不会立刻惩罚它。这时可以叠加一个中心损失,对 batch 内预测均值和标注均值做约束,能把整体值域拉正。
4.4 数据增强:不是越多越好,文本、语音、图像分开做
多模态系统在线做增强的项目我基本没见跑赢过,因为四个模态的增强只要有一个影响时间轴,对齐就崩了。更常见的是在特征抽取前离线增强:文本用同义词替换或回译,语音加 10 到 20dB SNR 的空调噪声,图像做水平翻转和轻微旋转,视频在抽帧时随机丢弃一帧再补齐。目标是让同一个情感标签下的分布变宽,而不是造出时间对不上的样本。每一路增强后的样本,ID 要加后缀,防止和原样本混淆。
增强的另一个用途是解决数据集太小的问题。如果标注样本只有一千条,先把文本做词级替换、语音做速度扰动,能各扩出 2 到 3 倍再抽特征;如果增强后验证集效果没有变化,说明增强方向本身有问题,优先换增强策略而不是继续加大倍数。
5. 避坑手册:多模态训练中我踩过的五个典型坑
这一部分写给已经能把代码跑起来、但结果不太对劲的读者。多模态系统的失败模式比单模态多得多,我按踩坑频率排序,把现象、原因和解决方案都列清楚。
5.1 时间戳没对齐:模型把“骂人”语音配给了上一条文本
现象:训练曲线在某个 epoch 后突然往下掉,或者融合后效果还不如单摸态文本。
原因:数据集的 JSON 清单里,文本标记的是聊天时间,语音文件开始时间是录音时间,两者相差数秒;如果样本构造只是按数组顺序 zip,模型学到的是错误配对。
解决:回到第 2 章的样本清单,把每个模态的起止毫秒数核对一遍;写一个 20 条样本的人工抽查,确认文本和语音指的是同一段内容;对语音切片做 VAD 检测,用有效语音起始点重新计算时间戳。这个坑我踩过三次,每次都消耗一整天,后来我把“抽查对齐”写进了训练前的固定步骤。
5.2 加了图像通道后效果反而不如单摸态文本
现象:A/B 测试时,文本单通道 macro-F1 0.72,加图像后降到 0.68。
原因:图像通道输出的表情概率分布和文本情感不一定同义,“中性”表情很多时候掩盖了真实情绪;另外如果投影层维度设置不当,图像分支的梯度会干扰文本分支的预训练表示。
解决:先把图像分支的梯度断开,单独训练融合层和分类头,验证融合结构本身能利用特征;再开启图像分支微调,学习率降至视觉分支的一半;最后把跨模态注意力权重保存下来,取 top-3 的帧号,和人工标注对应,确认模型确实参考了人脸表情,而不是把它当噪声。如果注意力权重集中在错误帧,问题大概率在特征抽取,不在融合。
5.3 训练 loss 在下降,验证集指标却纹丝不动
现象:训练约 3 个 epoch 之后 loss 一路走低,验证集准确率一直停在多数类比例附近,比如 0.73。
原因:数据集情绪标签本身就极度不均衡,模型学会了全预测多数类,因为这样交叉熵最小。验证集准确率看似不低,但对少类完全没有区分能力。
解决:训练前先打印每类样本数,确认类别分布;损失函数切换成第 4 章的 Focal Loss,并在评估指标里同时看 macro-F1。对多数类做下采样要谨慎,多模态样本本来就难标,丢掉数据会让其他模态的信息更稀疏。如果少类总共不到 50 条,先做增强扩样本,而不是靠损失函数硬撑。
5.4 视频特征梯度爆炸:位置编码和采样帧数不一致
现象:loss 在 step 200 附近直接变 NaN,回滚检查点后问题仍反复出现。
原因:视频分支把不同时长的视频全塞进 Transformer,序列长度从 8 到 64 帧不等,位置编码第一次见到超出训练区间的长度时,输出会异常放大,梯度随之爆炸。
解决:固定抽帧数为 16,不足时补齐;位置编码只初始化到 16;把视频分支和主训练阶段分开做 2 个 epoch 的预热,等注意力模块的参数平稳后再联合训练。如果已经写了动态长度版本,趁早改掉,后面时间成本更大。
5.5 显存 OOM:在训练时重复抽特征,而不是读缓存
现象:batch size 4 都跑不起来,显存监控显示内存是在数据加载阶段涨上去的,不是反向传播阶段。
原因:训练循环里直接调用人脸模型和 openSMILE 抽取特征,这些模型的参数量和中间张量完全不小于融合网络,四路同时计算必然爆显存。
解决:严格按第 3 章的流程度做:离线缓存 npz,训练时只读缓存。如果已经缓存但还是 OOM,把 batch size 降到 8,并把 image 特征从 16 帧降采样到 8 帧做对比实验。不值得为了帧数把 batch 压到 2,梯度估计的噪声会大到训练根本无法收敛。
6. 验证这一步别省:用消融实验判断多模态到底值不值得做
建模阶段结束后,最要紧的是回答“多加一路到底有没有用”。消融实验是最好的验证方法。我在工程上把评估脚本做成可开关模态的形式,每一个实验只改变一个开关,其余配置完全相同。
6.1 消融实验脚本应该怎么写
# evaluate.py 消融实验入口 import itertools, argparse def run_ablation(model, loader, modality_flags): result = {} for flags in itertools.product([True, False], repeat=4): if not any(flags): # 全关没有意义 continue name = "_".join(m for m, on in zip(["text", "audio", "image", "video"], flags) if on) output = model(loader, use_text=flags[0], use_audio=flags[1], use_image=flags[2], use_video=flags[3]) result[name] = compute_macro_f1(output) return result逻辑说明:这段代码把四个开关循环展开,得到 15 种模态组合的 macro-F1。认真看这个表,你会得到两个结论:第一,单摸态文本分数是多少,这是所有融合效果的天花板参考;第二,加进去的那一路如果只让 F1 提升不到 0.5 个点,说明该模态当前的特征质量不高,问题在特征提取而不是融合模型。
参数说明:评估时关掉 dropout 和所有随机增强;同一个实验跑两次取平均值,避免随机种子掩盖真实差异。测试集在 200 到 500 条之间时,消融实验的结论比较可靠;如果只有几十条,就不建议把结论当成通用规律。
6.2 错例反推:哪一路置信度高但结果是错的
除了消融实验,我还会做错例不确定性分析:把模型预测错误的样本单独打出来,按模态标注出“哪一路置信度高但结果错”。比如一段视频里图像通道给出“开心”概率 0.9,但标注是“难过”,说明表情识别模型的标注与数据集不一致,这个发现通常比换模型更有效。最近多模态大模型在图文理解上很火,但我仍然建议情感分析先用这种轻量级融合系统,因为情感标注粒度往往比大模型的通用语义更细,消融实验能给每一路能力一个明确交代。做多模态情感分析这几年,我最大的教训是不要迷信融合结构,先怀疑数据、再怀疑特征、最后才怀疑模型。希望这份从特征到参数再到验证的完整路径帮到你,少走我踩过的那些弯。
本文还有配套的精品资源,点击获取