简介:这是一份面向多模态情感分析研究与毕业设计场景的模型资源包,聚焦多模态表示学习、多模态融合及情感分类等下游任务。包内集成了Multimodal-Infomax、MISA、BBFN、Hfusion等多种深度学习模型实现,并配有MELD、MUStARD、M2H2等标准数据集,覆盖文本、音频、视觉三种模态的特征提取与融合流程,可直接用于训练、对比和实验评估。资源总计185个文件,压缩包大小约285.97MB。文件类型以Python脚本(41个py)、数据集与标注文件(csv、arff、pickle、h5)、模型权重(p)、Markdown文档(md)等为主,py脚本负责模型构建与训练,csv、arff等承载多模态数据样本,p、h5保存预训练权重,md与pdf则提供说明与实验记录。整体目录结构清晰,便于按模型或任务模块检索。目前已有1492人学习下载。对于正在做多模态情感分析课设、毕设或相关课题的开发者,可直接复用其中的模型代码、数据预处理与评估脚本,节省搭建时间;也可借助附带数据集快速验证改进思路,是兼顾学习与项目落地的实用资源。
1. 多模态情感分析是什么:为什么文本不够用,视频和语音也得跟上
情感分析做到后面你会发现一个反直觉的现象:纯文本模型在影评、商品评论上已经能跑出不错的准确率,但一换到视频、直播、客服对话场景就明显掉点。原因是真实交流里情绪不只靠词,语调上扬、表情变化、停顿长短都在传递态度。只拿文本等于扔掉大半信息,这就是多模态情感分析要解决的问题——把文本、音频、视觉三种模态的特征联合起来,训练一个能综合判断情感极性和强度的模型。
这个方向现在已经是多模态领域最有落地价值的场景之一。你拿到手的这份资源包,覆盖的正是这条完整链路:数据集的预处理与特征提取、多模态表示学习与融合策略、以及实验评估的指标和对比方法。适合谁用?做毕业设计、论文复现的研究生,或者公司里要给视频内容做舆情分析、客服质检算法的工程师。它能帮你省掉最痛苦的部分——从零攒数据、对齐模态、踩融合的坑。
2. 特征提取与多模态表示学习:先解决"怎么把三种模态变成能喂给模型的向量"
2.1 标准数据集选型:MOSI、MOSEI 和它们为什么是事实标准
多模态情感分析绕不开 CMU 的 MOSI 和 MOSEI 两个数据集。MOSI 是 2199 段 YouTube 影评视频,每段都带文本转录、音频和视频帧,标注值是 [-3, 3] 的连续情感分数;MOSEI 是它的升级版,样本量到 23416 段,涉及更多说话人和主题。凡是做多模态情感分析的论文,对比实验表里几乎全是这两个,用它们你才能和已有结果对齐。某些资源包里可能还附带 DEAP 这类生理信号数据集,那偏情绪计算方向,和 MOSI/MOSEI 的任务设定不太一样,注意区分。
拿到数据后第一件事不是建模,而是确认特征是否已经对齐。MOSI 和 MOSEI 官方发布时就有两个版本:ALIGNED 和 UNALIGNED。ALIGNED 版本把文本、音频、视频按词级别强制对齐到同一个时间步,适合早期用 LSTM 拼接特征的老方案;UNALIGNED 版本保留原始时间分辨率,文本最快、音频次之、视频最慢,适合现在主流的跨模态注意力方法。刚开始跑实验,建议先用 ALIGNED 版本跑通,再做 UNALIGNED,因为后者的时序对齐本身就是研究重点,直接上手会被各种维度不匹配的报错淹没。
2.2 特征提取的落地实操:Text、Audio、Visual 三路分开处理
如果你拿到的是原始视频而不是预提取特征,第一步就得做三路提取。文本分支常见做法是用 BERT 或 GloVe;音频分支用 COVAREP 或 openSMILE 提取 74 维声学特征(包含 F0、MFCC、响度等);视频分支用 OpenFace 提取面部动作单元和姿态特征,通常 47 维或 35 维。关键点在于三个分支的采样率天然不同——文本一个词可能对应好几帧视频和好几段音频,怎么让它们能对齐,直接决定后续融合的质量。
import torch import torch.nn as nn class MultiModalFeatureExtractor(nn.Module): def __init__(self, text_dim=768, audio_dim=74, video_dim=47): super().__init__() # 文本分支:BERT输出维度是768,如果不做微调就固定用它 self.text_proj = nn.Linear(text_dim, 128) # 音频分支:COVAREP特征通常是74维 self.audio_proj = nn.Linear(audio_dim, 64) # 视频分支:OpenFace输出一般是47维左右 self.video_proj = nn.Linear(video_dim, 64) self.dropout = nn.Dropout(0.2) def forward(self, text_feat, audio_feat, video_feat): # text_feat: [batch, seq_len, 768] # audio_feat: [batch, audio_len, 74] # video_feat: [batch, video_len, 47] t = torch.relu(self.text_proj(text_feat)) a = torch.relu(self.audio_proj(audio_feat)) v = torch.relu(self.video_proj(video_feat)) return self.dropout(t), self.dropout(a), self.dropout(v)这里每个分支先独立过一层线性映射,把原始维度压缩到统一的低维空间。这一步很关键,因为原始特征里存在大量冗余——视频的 47 维里有不少是说话人 ID 造成的身份信息,和情感无关;音频的 74 维里同样有环境噪声成分。投影到 64 或 128 维后,后续融合的计算量会小很多,而且模型不容易被高维稀疏特征带偏。如果不做压缩直接拼接,你会在训练时发现 loss 要很久才降下去,尤其是 BERT 特征这种 768 维的大块头,直接拼接会让音频和视频的特征被淹没。
提取完成后,保存成.pkl或.npy文件缓存起来。训练时直接用缓存的特征而不是每次重跑 BERT 和 OpenFace,整个实验周期能缩短一个量级。
3. 多模态融合:把特征拼起来不是答案,时序对齐才是
3.1 三类融合路线:Early Fusion、Late Fusion、中间融合的取舍
多模态融合的经典划分是早融合、晚融合和中间融合。早融合就是把三路特征沿着特征维度直接拼接,然后喂给一个分类器。优点是无脑简单,缺点是完全不管三路特征的采样率差异,文本 20 个词、音频 400 帧、视频 100 帧,拼接出来的向量内部严重错位,模型学到的是"噪声对齐"。晚融合是各模态独立训练一个分类器,最后把预测分数加权平均或投票,这样做各模态互不干扰,但丢失了模态之间的交互信息——比如一个人嘴上说"很棒",表情却是嘲讽,这种矛盾信号只有跨模态交互才能捕捉到。
中间融合是目前的主流路线,核心思路是先给每个模态做局部时序编码,再通过跨模态注意力让不同模态在时间步上互相 attend。MulT 就是这么做的,它给每对模态都建一个 Transformer 编码器,文本去 attend 音频特征,音频去 attend 视频特征,然后把交互后的序列再过融合层。这套设计的理论基础是,情感信号在不同模态之间有滞后和提前的对应关系,比如语调先于表情变化,直接拼接学不到这种时间偏移规律,但跨模态注意力可以学会去关注对方模态的某个时间窗口。
3.2 融合模块的代码实现:一个能直接改的最小跨模态注意力层
import torch import torch.nn as nn import math class CrossModalAttention(nn.Module): def __init__(self, embed_dim=128, num_heads=4): super().__init__() self.embed_dim = embed_dim self.num_heads = num_heads self.head_dim = embed_dim // num_heads assert self.head_dim * num_heads == embed_dim self.q_proj = nn.Linear(embed_dim, embed_dim) self.k_proj = nn.Linear(embed_dim, embed_dim) self.v_proj = nn.Linear(embed_dim, embed_dim) self.out_proj = nn.Linear(embed_dim, embed_dim) self.scale = math.sqrt(self.head_dim) def forward(self, query, key, value): # query: [batch, t_len, embed_dim] (源模态,比如文本) # key/value: [batch, s_len, embed_dim] (目标模态,比如视频) batch, t_len, _ = query.shape _, s_len, _ = key.shape Q = self.q_proj(query).view(batch, t_len, self.num_heads, self.head_dim).transpose(1, 2) K = self.k_proj(key).view(batch, s_len, self.num_heads, self.head_dim).transpose(1, 2) V = self.v_proj(value).view(batch, s_len, self.num_heads, self.head_dim).transpose(1, 2) attn_weights = torch.matmul(Q, K.transpose(-2, -1)) / self.scale attn_weights = torch.softmax(attn_weights, dim=-1) out = torch.matmul(attn_weights, V) out = out.transpose(1, 2).contiguous().view(batch, t_len, self.embed_dim) return self.out_proj(out)这段实现了标准的缩放点积跨模态注意力。Query 来自关心源模态,Key 和 Value 来自被交互的目标模态,这样模型可以学到"文本中的这个词应该对齐视频中的哪几帧"。参数上要注意num_heads一般取 4 或 8,embed_dim建议 128;如果数据量大可以升到 256,但训练时间会显著增加。scale用1 / sqrt(head_dim)是防止 softmax 输入过大导致梯度消失,这个细节新手经常漏掉,不除的话训练前期 loss 很容易落到 0.69(log2)附近不动。
完整看一个 MulT 风格的融合流程:文本序列先过一层 self-attention 做局部编码,然后和音频、视频分别做跨模态注意力,再把交互后的三路输出拼接,过一个两层全连接网络输出到分类头。整个模块的输入是第 2 章特征提取器的输出,所以两个模块能直接对接成一条流水线。跑通基线之后再考虑加门控机制(比如动态调节各模态的贡献权重),一开始不要做太复杂的结构。
3.3 表示学习的进阶:从静态融合到动态调整
静态融合在一段视频内权重固定,但真实场景里不同片段的主导模态在变——前两秒画面抖动,语音是主要线索;中间说话人转过头去,表情信息丢失,文本和语调权重就该上来。动态融合的代表做法是基于注意力给每个时间步分配模态权重,比如 Self-MM 用标签平滑和对比学习约束单模态和融合模态的表示一致性,另外一些工作用图神经网络建模模态之间的语义交互和二阶关系。
这些进阶方案做论文对比实验时很有价值,但落地时要冷静:动态权重的收益通常只有 1~2 个点的 ACC 提升,代价是训练不稳定、hyperparameter 空间变大了很多。我的建议是先把静态融合作为基线跑稳,确认你的复现效果和原论文的差距在合理范围内,再往上加动态机制。如果基线就比论文差 3 个点以上,先不要怀疑模型,回去检查数据处理。
4. 实验评估:用指标对齐论文,用对比实验检验模块价值
4.1 MOSI 和 MOSEI 上的评估协议与三组硬指标
多模态情感分析论文的标准评估协议是:在 MOSI 和 MOSEI 上分别做 7 分类和 2 分类评估。7 分类把 [-3, 3] 的情感分数映射成 7 档(-3 强烈消极到 +3 强烈积极),指标用 Acc-7 衡量;2 分类按分数正负分成积极/消极两档(有些工作把 0 归入某一边或剔除),指标用 Acc-2 和 F1。另外还有回归指标 MAE 和 Corr——MAE 是预测分数和真实分数的绝对误差,Corr 是预测值和真实值的皮尔逊相关系数。
单独看 Acc-2 容易被骗。因为 MOSI 里积极样本占比略高,一个全部预测积极的桩模型也能到 55% 左右的准确率。所以做实验时三个指标都要报,F1 能反映少数类的召回情况,MAE 能暴露你模型是不是只会在 1 和 -1 附近打转。一个经验值:MOSI 上 Acc-2 到 82% 以上、MAE 到 0.95 以下才算得上可发表的基线水平;MOSEI 因为数据量更大,Acc-2 一般能到 85% 左右。
4.2 评估脚本:统一指标计算与结果输出
import numpy as np from sklearn.metrics import accuracy_score, f1_score, mean_absolute_error from scipy.stats import pearsonr def evaluate(preds, labels, preds_reg=None, labels_reg=None): # 7分类评估:原始分数直接作为类别标签 acc7 = accuracy_score(labels, preds) # 2分类评估:正负二分,等于0的样本可以剔除或归入积极 pos_mask = labels != 0 labels_bin = (labels[pos_mask] > 0).astype(int) preds_bin = (preds[pos_mask] > 0).astype(int) acc2 = accuracy_score(labels_bin, preds_bin) f1 = f1_score(labels_bin, preds_bin, average='binary') # 回归评估:如果用的是回归输出,需要额外预测连续分数 mae = mean_absolute_error(labels_reg, preds_reg) corr, _ = pearsonr(labels_reg, preds_reg) print(f"Acc-7: {acc7:.4f} | Acc-2: {acc2:.4f} | F1: {f1:.4f}") print(f"MAE: {mae:.4f} | Corr: {corr:.4f}") return {"acc7": acc7, "acc2": acc2, "f1": f1, "mae": mae, "corr": corr}注意 2 分类评估里的pos_mask——我把标签等于 0 的样本剔除了。不同论文对这个边界样本的处理方式不一样,有的归积极,有的归消极,有的直接丢掉,这会导致 Acc-2 差一两个点。跑对比时务必查清楚对方论文怎么处理 0 标签,否则你以为的差距可能是协议差异造成的。
4.3 消融实验怎么设计:三个必须做的对照组
一份能说服人的实验评估,至少要有三组消融:单模态基线(只用 text、只用 audio、只用 video)、成对模态组合(text+audio、text+video、audio+video)、以及完整三模态融合。单模态基线告诉你每个模态在任务里的上限在哪——如果 video 单模态 ACC 只有 40%,说明视觉特征提取质量本身有问题,后续融合效果再好也心虚。成对组合能看出哪些模态之间存在强互补关系,一般来说 text+audio 已经能到三模态的 95% 水平,video 的增量在 MOSI 上相对有限。
另一个重要对比是融合策略对比:把中间融合换成早融合(直接拼接)和晚融合(分数平均)做对照,这比对比多个 SOTA 论文更有说服力,因为所有模态特征预处理完全一致,差量完全来自融合模块本身。论文审稿人很喜欢看到这样的对比,因为它干净、能说明问题。
5. 避坑指南:多模态情感分析里最容易翻车的 5 个环节
5.1 特征不对齐导致维度爆炸
现象:训练时模型很大、loss 在 3~4 个 epoch 后开始震荡,GPU 显存占用异常。原因:文本序列长度、音频帧数、视频帧数分别走各自投影层,输出序列长度不一致时,直接拼接后注意力矩阵的形状就变成了[batch, 文本_len + 音频_len + 视频_len]的平方复杂度,显存直接爆掉。解决:在融合前对每个模态做池化(mean pooling)或设固定长度截断。我一般先统计训练集各模态序列长度的分位数,取 90 分位作为裁剪阈值,然后用 padding + mask 对齐到同一长度。不要用全局 max pooling,会丢失大量时序位置信息。
5.2 跨模态注意力训练不收敛
现象:训练早期 loss 一直停在 0.69 附近(log2,即二分类随机猜测),过了 10 个 epoch 也不下降。原因:cross-modal attention 的 softmax 输入值过大,梯度消失,常见诱因是没做 scale,或者 Q/K 的初始化方差太大。解决:确认scale = 1 / sqrt(head_dim)加上了;把nn.Linear的初始化改成正交初始化或 xavier_uniform;再不行就先把所有投影层的 bias 置零。另外一个常见问题是学习率——融合层和 BERT 微调层的合理学习率差异很大,一般 BERT 用 2e-5,融合层用 1e-3 起步,用 AdamW 分组设置参数。
5.3 训练集和验证集数据泄露
现象:验证集指标异常高,Acc-2 直接到 100%,但换到另一批数据泛化效果很差。原因:数据预处理脚本里把同一个视频的多个片段切分时,没有先按视频 ID 分组再划分 train/valid/test,导致同一个人的不同片段同时在训练集和验证集里。MOSI 的原始划分对视频 ID 做了隔离,但你自己扩展数据集或用别的数据时容易忽略。解决:数据划分前先按视频 ID 排序去重,用group_kfold或直接按 ID 哈希分割,保证同一视频的所有片段永远在同一折里。这是多模态任务最隐蔽也最常见的翻车点。
5.4 BERT 微调导致内存爆炸
现象:加入 BERT 后训练显存直接从 6G 飙升到 20G+,小卡根本跑不动。原因:BERT 的 12 层 Transformer 权重 + 梯度占了绝大部分显存,而多模态任务里文本只是其中一路,这种开销不划算。解决:第一种方案是 BERT 不微调,离线提取特征后冻结,文本分支只训练一层 MLP,显存能控制在 4G 以内;第二种方案是 BERT 后段微调,只让后 4 层参与梯度计算,前 8 层冻结。做实验对比时先用方案一跑通,确定需要微调 BERT 能带来显著提升后再上方案二。
5.5 视频帧采样率不统一导致结果不可复现
现象:同一条代码,在一台机器跑出 ACC 82%,换台机器变 79%,再跑一次变 83%。原因:视频帧提取时用 OpenCV 的VideoCapture按帧索引读,但视频的原始帧率不统一(有的 24fps 有的 30fps),采样步长单位不一致造成特征错位。解决:统一用cap.get(cv2.CAP_PROP_FPS)读取实际帧率,按时间戳采样而不是按帧序号采样;提取完成后把特征文件的时间戳信息保存下来,训练前检查各模态时长是否匹配。这是一个血泪经验:多模态数据的可复现性 80% 取决于预处理阶段的确定性,而不是模型随机种子。
6. 进阶验证:怎么判断你的融合模块真的学到了东西
融合模型最怕的是"表面融合",实际只用了文本分支,其他两个模态的投影层学会了直接输出常量。要识别这种退化,除了对比单模态基线,还有一个简单的验证手段:把音频和视频特征的顺序随机打乱,重新跑一遍预测。如果模型在打乱后得分变化很小,说明它根本没有用音频和视频的时序信息——它在用文本硬撑。
另一个实用的验证维度是回归预测的误差分布。好的多模态模型误差应该集中在零附近,且误差大小和情感极性无关。如果发现正极性样本的 MAE 明显大于负极性,说明模型对积极情绪的表达模式学习不足,这往往是训练集中消极样本过多造成的类不平衡,可以在损失函数里按极性加权。
做论文或工程交付时,额外建议在 MOSEI 上做一次交叉验证而不是只跑官方划分。MOSEI 的说话人分布不均匀,官方划分可能让一部分说话人的片段垄断了验证集,交叉验证比单一划分更可靠。代价是 MOSEI 规模大,全量训练一轮可能要大半天,可以先做 5 折中的 2 折来估计方差。
最后说一下我给自己的验收习惯:只跑出一个好数字不算完,我会强迫自己检查三件事——融合层的注意力权重是否呈现有意义的模态间对齐模式(用可视化 heatmap 查看),各模态投影层的输出方差是否显著大于零(方差为零说明退化了),以及去掉任何一个模态后 ACC 是否明显下降(下降幅度低于 0.5 个点说明这个模态没起作用)。这套检查跑完,模型才真正算数。多模态工作的价值不在最后那个 float 指标,在于融合过程是不是真的让每个模态都参与了决策。希望帮到你。
本文还有配套的精品资源,点击获取