一种用于不确定性感知多模态情绪识别的概率框架
Latent Distribution Decoupling
图一:全文框架结构思维导图预览
问题:多模态多标签情感识别(MMER)需从音频、文本、视频中识别共存情感,但现有方法忽略偶然不确定性(数据固有噪声),导致特征模糊和模态融合失效。方法:提出LDDU框架,通过潜在情感空间的概率建模解决:
对比解耦分布机制:将多模态数据建模为高斯分布,分离语义特征与不确定性。
不确定性感知融合:根据分布分散程度动态整合模态信息。结果:在CMU-MOSEI和M³ED数据集上达到SOTA(CMU-MOSEI的mi-F1提升4.3%)。
导言:多模态多标签情感识别(MMER)旨在识别多模态数据中同时存在的多种情感。现有研究主要侧重于改进融合策略和建模模态与标签之间的依赖关系。然而,这些研究往往忽略了任意不确定性(aleatoric uncertainty)的影响 —— 这种不确定性是多模态数据中的固有噪声,它通过在特征表示中引入歧义性来阻碍模态融合的有效性。为解决这一问题并有效建模任意不确定性,本文从潜在情感空间概率建模的新视角出发,提出了具有不确定性感知能力的潜在情感分布分解(LDDU)框架。具体而言,我们在情感空间内引入对比解耦分布机制来对多模态数据进行建模,从而实现语义特征和不确定性的提取。此外,我们设计了一种考虑不确定性分散分布特性并整合分布信息的不确定性感知多模态融合方法。实验结果表明,LDDU 在 CMU-MOSEI 和 M³ED 数据集上取得了最先进的性能,突显了不确定性建模在 MMER 中的重要性。
1 MMER 的主要研究方向
图 1:MMER 任务中任意不确定性的示意图。当在潜在情感空间中采用高斯分布建模时,由于说话风格冷淡,案例二的语义特征比案例一更加模糊,且方差更大。同时,案例一的情感强度更强,更接近全局分布的中心。
MMER 的主要研究方向在于通过有效融合多模态数据来提取情感相关特征,并对模态与标签之间的依赖关系进行建模(Zhang 等人,2021;Hazarika 等人,2020)。为了实现多模态数据的融合,一些研究(Zhang 等人,2020;Wang 等人,2024b)采用投影层来缓解模态差异(Radford 等人,2021),而另一些方法(Zhang 等人,2021;Tsai 等人,2019)则利用注意力机制。此外,多项研究(Hazarika 等人,2020;Zhang 等人,2022;Ge 等人,2023;Xu 等人,2024)将模态特征分解为公共组件和私有组件。最近,CARET(Peng 等人,2024)引入了情感空间建模,该方法在融合前先提取情感相关特征,从而实现了最先进的性能。关于模态与标签的依赖关系,许多方法(Zhang 等人,2021,2022)利用 Transformer 解码器来捕捉标签语义特征与融合后的模态序列特征之间的关系。
2 研究与实现的方法论算法实现
图2:提出的LDDU框架包含三个组成部分:(1)基于Transformer的单模态提取器,(2)基于对比学习的情绪空间分解模块,以及(3)不确定性感知融合与不确定性校准模块。
3.1 单模态特征提取器
遵循 Peng 等人(2024)的研究,我们在 CMU-MOSEI(Zadeh 等人,2018b)和 M³ED(Zhao 等人,2022)数据集上开展实验。在这两个基准数据集中,样本 X 的面部关键点视觉特征 Xᵛ通过 MTCNN 算法(Zhang 等人,2016)提取,声学特征 Xᵃ通过 Covarep(Degottex 等人,2014)提取,文本特征 Xᵗ通过 BERT(Yu 等人,2020)提取。为捕捉内容序列的依赖关系,我们采用 nᵛ、nᵃ和 nᵗ层 Transformer 作为单模态提取器,生成视觉特征 Oᵛ∈ℝˢᵛ×ᵈᵛ、音频特征 Oᵃ∈ℝˢᵃ×ᵈᵃ和文本特征 Oᵗ∈ℝˢᵗ×ᵈᵗ。每个模态特征 Oᵐ均来自其序列数据 [o₁ᵐ, …, oˢᵐᵐ],其中 m∈{v, a, t}。
3.2.1 情感空间建模
情感空间建模的主要挑战在于在统一的联合嵌入空间中建立情感表示。受 Q-Former 结构(Li 等人,2023)的启发,我们引入可训练的情感嵌入 L = [l₁, l₂, ..., l_q],其中每个 lᵢ表示一种情感,q 为标签数量。由于情感相关线索可能分布在序列数据的不同片段中,我们采用注意力机制自动为每种情感提取相关特征。由于模态相关特征 Oᵐ和 L 位于不同的特征空间,我们使用投影层计算帧特征 oⱼᵐ与标签 lᵢ之间的相似度 aᵢⱼᵐ。在获得相似度矩阵 Aᵐ = {aᵢⱼᵐ} 后,通过投影 Yᵐ提取特定于模态的标签相关特征 Zᵐ∈ℝ۹×ᵈʰ,其中 dʰ为特定于模态的标签相关特征的维度。该过程可形式化表示为:
其中 Proj 表示投影层。
为促进情感表示 L 的学习,我们将第 i 个样本的多模态特征连接为 F_dir = [Zᵛᵢ, Zᵃᵢ, Zᵗᵢ],并使用基于 MLP 的信息分类器(采用 sigmoid 激活函数)处理,生成最终预测ŷ_dirᵢ = [ŷ_dirᵢ₁, …, ŷ_dirᵢ_q]。损失函数 L_dir 定义为:
3.3.2 对比解耦分布建模
该模块由解耦分布学习(DDL)和对比学习(CL)组成。如图 3 所示,我们的架构结合了解耦表示学习(DRL)(Wang 等人,2024b;Kingma,2013),将标签相关特征建立为情感空间中的潜在概率分布。具体而言,我们将多模态情感表示 [Zᵛ, Zᵃ, Zᵗ] 建模为多元正态分布ℕ。对于每个标签相关特征 [Zᵛᵢ, Zᵃᵢ, Zᵗᵢ],我们利用编码器(本文中为 MLP)和两个全连接层获得潜在分布ℕ(μᵛᵢ, σᵛᵢ)、ℕ(μᵃᵢ, σᵃᵢ) 和ℕ(μᵗᵢ, σᵗᵢ)。其中 μᵗᵢ表示文本模态中情感标签 i 的语义特征(Tellamekala 等人,2023),σᵗᵢ反映潜在空间中的分布区域。
为确保潜在分布ℕ(μᵐᵢ, σᵐᵢ) 准确捕捉每个模态 m 中各标签的特征差异,我们采用对比学习(CL)。对比学习可将相似样本分组,增强模型区分不同类别的能力(He 等人,2020;Caron 等人,2020)。形式上,考虑到标签和模态间潜在分布的变化,我们将其划分为 3q 个潜在情感分布。对于一批 s_B 个样本 B,每个样本生成 3q 个标签相关且特定于模态的情感分布,总计 3×q×s_B 个分布。若相关样本包含对应情感,则 B⁺中的每个分布被视为正样本。对于每个正分布 e∈B⁺,我们根据标签确定其正样本集 Pₑ(B) 和负样本集 Nₑ(B)。
此外,我们从以下两个角度改进对比学习:首先,Caron 等人(2020)指出,更大的批量大小可通过在对比学习中提供更多样化的负样本增强网络能力。我们引入大小为 s_q 的队列 Q,存储最近的 s_q 个情感分布,使每个情感分布的最终正、负样本集分别为 Pₑ(B∪Q) 和 Nₑ(B∪Q)。其次,样本间的相似度计算必须同时考虑解耦分布的中心和方差。我们将分布 e 表示为:
最后,我们对每个情感分布引入 SupCon 损失(Khosla 等人,2020):
其中 Tₑ = Pₑ ∪ Nₑ,z 为情感分布间的相似度函数。为简化计算,我们对归一化的分布参数计算余弦相似度:
整个批次的最终对比损失为:
情感和情感强度 (CMU-MOSEI) 数据集
3.4 不确定性感知与校准
3.4.1 不确定性感知多模态融合
在对情感空间建模后,将潜在语义特征与分布不确定性信息整合至关重要。我们使用方差表示潜在空间中的分布不确定性,因其反映了分散程度和分布区域;同时,中心特征表示样本的语义特征(Gao 等人,2024;Tellamekala 等人,2023;Xu 等人,2024)。我们假设:当样本的任意不确定性较高时,其语义特征更模糊,潜在空间中的分布区域对情感识别更具判别性;反之,当不确定性较低时,语义特征更具判别性,分布区域更窄。因此,中心特征与方差的融合应依赖于任意不确定性的水平。
首先,我们引入信息分类器对第 i 个样本的预测ŷ_dirᵢ来量化不确定性。Kendall 和 Gal(2017)指出,任意不确定性可通过样本的预测难度衡量。具体而言,若 Zᵢ被信息分类器正确分类,而 Zⱼ被误分类且需要解耦以进一步分类,则推断 Zⱼ样本的任意不确定性更高(即信息量更少)。因此,不确定性可表示为 d (ŷ_dirᵢ, yᵢ),其中ŷ_dirᵢ为 Zᵢ的预测。
然后,我们通过融合多模态数据整合分布信息。解耦后,样本表示为潜在分布ℕ(Eᵛ,ᵃ,ᵗ, Mᵛ,ᵃ,ᵗ),其中对于每个模态 m,Eᵐ = [μ₁ᵐ, ..., μ_qᵐ],Mᵐ = [σ₁ᵐ, ..., σ_qᵐ]。由于 Eᵛ,ᵃ,ᵗ和 Mᵛ,ᵃ,ᵗ具有不同语义,我们使用门控网络实现后期融合。操作上,将 (Eᵛ, Eᵃ, Eᵗ) 和 (Mᵛ, Mᵃ, Mᵗ) 连接后通过最终分类器,获得预测ŷᵢ^μ 和ŷᵢ^σ。语义均值向量和方差根据不确定性分数动态融合:
对于批量大小为 s_B 的数据,损失函数为:
3.4.2 不确定性校准
本节中,我们施加序数约束(Moon 等人,2020)以建模不确定性与分布方差的关系。当校准良好时,不确定性分数可作为潜在分布预测正确性的代理,即校准后的不确定性表示预期估计误差(如预测情感与真实情感的偏差)。
已有研究证实:频繁被遗忘的样本更难分类,而简单样本在训练早期即可被学习(Toneva 等人,2018;Geifman 等人,2018)。因此,为表示正确性概率值,我们使用信息分类器在随机梯度下降(SGD)过程中正确预测的样本比例 rᵢ作为代理(Shamir 和 Zhang,2013;Xu 等人,2024)。在我们的方法中,方差 σᵢ = (σᵛᵢ, σᵃᵢ, σᵗᵢ) 和信息分类器的预测误差 d (ŷ_dirᵢ, yᵢ) 与情感分类的正确性概率值高度相关。因此,校准可形式化为:
其中 rk 为排序,Corr 为相关性。当样本不确定性较高时,潜在分布方差 σᵢ和预测误差 dᵢ = d (ŷ_dirᵢ, yᵢ) 往往较大,而 rᵢ较小;反之,不确定性较低时,这些特征相反。
对于批量大小为 s_B 的样本,我们计算每个样本的方差范数 S、距离向量 D 和代理向量 R:
为在 S、D 和 R 之间建立排序约束,我们基于软排序(Tellamekala 等人,2023;Bruch 等人,2019)施加序数约束。我们的方法采用双向 KL 散度评估 (S, R) 和 (D, R) 对的 softmax 分布不匹配度,因此序数校准损失 L_ocl 可计算为:
其中 P_D、P_R 和 P_S 分别表示特征 S、R 和 D 的 softmax 分布。
总体而言,LDDU 在整个训练过程中的损失为:
其中 λ、β 和 γ 为控制各正则化约束权重的超参数。
代码实现如下:
1. 环境要求
Python 3.9+
PyTorch 2.0+
CUDA 11.7+(GPU 训练推荐)
其他依赖:
pandas
,numpy
,scikit-learn
,tqdm
,pickle5
2. 依赖安装
# 克隆项目仓库(包含数据集预处理脚本和模型代码)git clone https://github.com/201983290498/lddu_mmer.gitcd lddu_mmer# 安装依赖pip install -r requirements.txt
三、数据集准备(CMU-MOSEI)
1. 数据下载
CMU-MOSEI 数据集需通过官方申请获取,下载后解压到./dataset/CMU-MOSEI
目录。
2. 数据预处理
预处理代码用于将原始视频 / 文本 / 音频特征对齐,并生成多模态特征文件。以下是核心预处理脚本:
# dataset/preprocess.pyimport osimport pickleimport numpy as npimport pandas as pdfrom collections import defaultdictdefload_raw_features(data_dir): """加载原始多模态特征(文本、视觉、音频)""" # 文本特征(BERT词嵌入,维度:seq_len×768) text_feats = np.load(os.path.join(data_dir, "text_bert.npy"), allow_pickle=True) # 视觉特征(ResNet图像特征,维度:seq_len×35) visual_feats = np.load(os.path.join(data_dir, "visual.npy"), allow_pickle=True) # 音频特征(COVAREP声学特征,维度:seq_len×74) audio_feats = np.load(os.path.join(data_dir, "audio.npy"), allow_pickle=True) # 标签(多情感标签,格式:[0,1,0,1,0,0] 对应6类情感) labels = pd.read_csv(os.path.join(data_dir, "labels.csv"))["emotion_tags"].apply(eval) return text_feats, visual_feats, audio_feats, labelsdefalign_modalities(text, visual, audio, labels, max_seq_len=50): """对齐多模态序列(截断/填充至固定长度)""" aligned_data = defaultdict(dict) for i in range(len(labels)): # 文本特征对齐 text_i = text[i][:max_seq_len] # 截断前50帧 text_pad = np.pad(text_i, ((0, max_seq_len - text_i.shape[0]), (0, 0))) # 填充至50×768 # 视觉特征对齐 visual_i = visual[i][:max_seq_len] visual_pad = np.pad(visual_i, ((0, max_seq_len - visual_i.shape[0]), (0, 0))) # 填充至50×35 # 音频特征对齐 audio_i = audio[i][:max_seq_len] audio_i[audio_i == -np.inf] = 0# 替换负无穷为0 audio_pad = np.pad(audio_i, ((0, max_seq_len - audio_i.shape[0]), (0, 0))) # 填充至50×74 aligned_data[i] = { "text": text_pad, "visual": visual_pad, "audio": audio_pad, "label": np.array(labels[i], dtype=np.float32) # 多标签向量(6维) } return aligned_dataif __name__ == "__main__": # 原始数据路径(需替换为实际路径) raw_data_dir = "./dataset/CMU-MOSEI/raw" # 对齐后数据保存路径 aligned_save_path = "./dataset/CMU-MOSEI/aligned_data.pkl" # 加载并对齐数据 text, visual, audio, labels = load_raw_features(raw_data_dir) aligned_data = align_modalities(text, visual, audio, labels) # 保存对齐后数据(训练/验证/测试集按8:1:1划分) with open(aligned_save_path, "wb") as f: pickle.dump(aligned_data, f) print(f"对齐数据已保存至 {aligned_save_path}")
四、模型实现(LDDU 核心模块)
1. 单模态特征提取器(Transformer)
# model/feature_extractor.pyimport torchimport torch.nn as nnfrom torch.nn import TransformerEncoder, TransformerEncoderLayerclassModalityTransformer(nn.Module): def__init__(self, input_dim, hidden_dim=256, nhead=4, num_layers=2): """单模态Transformer特征提取器""" super().__init__() self.input_dim = input_dim # 输入特征维度(文本768/视觉35/音频74) self.hidden_dim = hidden_dim # 隐藏层维度 self.nhead = nhead # 注意力头数 self.num_layers = num_layers # 编码层数 # 输入投影层(将原始特征映射到隐藏维度) self.projection = nn.Linear(input_dim, hidden_dim) # Transformer编码器 encoder_layer = TransformerEncoderLayer( d_model=hidden_dim, nhead=nhead, dim_feedforward=hidden_dim*4, dropout=0.1 ) self.transformer = TransformerEncoder(encoder_layer, num_layers=num_layers) # 序列池化(取序列均值作为全局特征) self.pooling = nn.AdaptiveAvgPool1d(1) defforward(self, x, mask=None): """ x: 输入特征(batch_size×seq_len×input_dim) mask: 序列掩码(batch_size×seq_len),0表示填充位置 """ # 投影到隐藏维度(batch_size×seq_len×hidden_dim) x_proj = self.projection(x) # 转换为Transformer输入格式(seq_len×batch_size×hidden_dim) x_transformer = x_proj.permute(1, 0, 2) # 编码(seq_len×batch_size×hidden_dim) x_encoded = self.transformer(x_transformer, src_key_padding_mask=mask) # 序列池化(batch_size×hidden_dim) x_pooled = self.pooling(x_encoded.permute(1, 2, 0)).squeeze(-1) return x_pooled
2. 情感空间分解模块(ESM)
# model/esm.pyimport torchimport torch.nn as nnclassEmotionSpaceModel(nn.Module): def__init__(self, hidden_dim=256, emotion_dim=6): """情感空间分解模块(建模高斯分布)""" super().__init__() self.hidden_dim = hidden_dim # 特征维度 self.emotion_dim = emotion_dim # 情感类别数(6类) # 均值(μ)和方差(σ)的线性层 self.mean_layer = nn.Linear(hidden_dim, emotion_dim) self.var_layer = nn.Linear(hidden_dim, emotion_dim) # 激活函数(方差用Softplus保证非负) self.softplus = nn.Softplus() defforward(self, x): """ x: 单模态全局特征(batch_size×hidden_dim) """ # 计算均值和方差(batch_size×emotion_dim) mean = self.mean_layer(x) var = self.softplus(self.var_layer(x)) + 1e-6# 防止方差为0 return mean, var
3. 对比学习与不确定性融合模块
# model/contrastive_fusion.pyimport torchimport torch.nn as nnimport torch.nn.functional as FclassContrastiveFusion(nn.Module): def__init__(self, hidden_dim=256, queue_size=1024, temperature=0.1): """对比学习与不确定性融合模块""" super().__init__() self.hidden_dim = hidden_dim self.queue_size = queue_size # 对比队列大小 self.temperature = temperature # 温度参数 # 对比队列(存储历史特征) self.register_buffer("queue", torch.randn(hidden_dim, queue_size)) self.queue = F.normalize(self.queue, dim=0) self.register_buffer("queue_ptr", torch.zeros(1, dtype=torch.long)) defcontrastive_loss(self, anchor, positive, queue): """计算对比损失(InfoNCE)""" # 锚点与正样本相似度 sim_pos = torch.einsum("nc,nc->n", [anchor, positive]) / self.temperature # 锚点与队列负样本相似度 sim_neg = torch.einsum("nc,ck->nk", [anchor, queue]) / self.temperature # 组合正负样本 logits = torch.cat([sim_pos.unsqueeze(1), sim_neg], dim=1) labels = torch.zeros(logits.shape[0], dtype=torch.long, device=anchor.device) return F.cross_entropy(logits, labels) defupdate_queue(self, features): """更新对比队列""" batch_size = features.shape[0] ptr = int(self.queue_ptr) if ptr + batch_size > self.queue_size: # 队列满,覆盖旧数据 self.queue[:, ptr:] = features.T[:, :self.queue_size - ptr] self.queue_ptr[0] = 0 else: self.queue[:, ptr:ptr+batch_size] = features.T self.queue_ptr[0] = ptr + batch_size defforward(self, text_feat, visual_feat, audio_feat): """多模态特征融合与对比学习""" # 归一化特征(用于对比学习) text_norm = F.normalize(text_feat, dim=1) visual_norm = F.normalize(visual_feat, dim=1) audio_norm = F.normalize(audio_feat, dim=1) # 计算对比损失(文本-视觉、文本-音频、视觉-音频) loss_vt = self.contrastive_loss(text_norm, visual_norm, self.queue) loss_ta = self.contrastive_loss(text_norm, audio_norm, self.queue) loss_va = self.contrastive_loss(visual_norm, audio_norm, self.queue) total_cl_loss = (loss_vt + loss_ta + loss_va) / 3 # 更新对比队列 self.update_queue(torch.cat([text_norm, visual_norm, audio_norm], dim=0)) # 不确定性加权融合(权重为各模态方差的倒数) # 假设各模态方差已计算(来自ESM模块) # weight = 1 / (var_text + var_visual + var_audio) # fused_feat = weight_text * text_feat + weight_visual * visual_feat + weight_audio * audio_feat # (实际实现需结合ESM输出的方差) return total_cl_loss
4. 完整 LDDU 模型
# model/lddu.pyimport torchimport torch.nn as nnfrom .feature_extractor import ModalityTransformerfrom .esm import EmotionSpaceModelfrom .contrastive_fusion import ContrastiveFusionclassLDDU(nn.Module): def__init__(self, args): """LDDU完整模型""" super().__init__() self.args = args # 单模态特征提取器(文本/视觉/音频) self.text_extractor = ModalityTransformer( input_dim=768, hidden_dim=args.hidden_dim ) self.visual_extractor = ModalityTransformer( input_dim=35, hidden_dim=args.hidden_dim ) self.audio_extractor = ModalityTransformer( input_dim=74, hidden_dim=args.hidden_dim ) # 情感空间分解模块(ESM) self.text_esm = EmotionSpaceModel( hidden_dim=args.hidden_dim, emotion_dim=args.emotion_dim ) self.visual_esm = EmotionSpaceModel( hidden_dim=args.hidden_dim, emotion_dim=args.emotion_dim ) self.audio_esm = EmotionSpaceModel( hidden_dim=args.hidden_dim, emotion_dim=args.emotion_dim ) # 对比学习与融合模块 self.contrastive_fusion = ContrastiveFusion( hidden_dim=args.hidden_dim, queue_size=args.queue_size, temperature=args.temperature ) # 最终分类头(融合各模态均值特征) self.classifier = nn.Linear(3*args.emotion_dim, args.emotion_dim) defforward(self, text, visual, audio, text_mask=None, visual_mask=None, audio_mask=None): """ text: 文本特征(batch_size×seq_len×768) visual: 视觉特征(batch_size×seq_len×35) audio: 音频特征(batch_size×seq_len×74) mask: 各模态掩码(batch_size×seq_len),0表示填充位置 """ # 单模态特征提取(batch_size×hidden_dim) text_feat = self.text_extractor(text, text_mask) visual_feat = self.visual_extractor(visual, visual_mask) audio_feat = self.audio_extractor(audio, audio_mask) # 情感空间分解(均值μ和方差σ) text_mean, text_var = self.text_esm(text_feat) visual_mean, visual_var = self.visual_esm(visual_feat) audio_mean, audio_var = self.audio_esm(audio_feat) # 对比学习损失 cl_loss = self.contrastive_fusion(text_feat, visual_feat, audio_feat) # 不确定性融合(均值拼接后分类) fused_mean = torch.cat([text_mean, visual_mean, audio_mean], dim=1) logits = self.classifier(fused_mean) return logits, cl_loss, (text_mean, text_var, visual_mean, visual_var, audio_mean, audio_var)
五、训练与评估脚本
# train.pyimport osimport argparseimport torchimport torch.nnasnnfrom torch.utils.data import DataLoaderfrom dataset.dataset import AlignedMoseiDataset # 见附录数据集类定义from model.lddu import LDDUfrom tqdm import tqdmfrom sklearn.metrics import f1_scoredef main(args): # 设备配置 device = torch.device(args.device) # 加载数据集 train_dataset = AlignedMoseiDataset(args.data_path, "train") val_dataset = AlignedMoseiDataset(args.data_path, "valid") test_dataset = AlignedMoseiDataset(args.data_path, "test") train_loader = DataLoader( train_dataset, batch_size=args.batch_size, shuffle=True, num_workers=4 ) val_loader = DataLoader( val_dataset, batch_size=args.batch_size, shuffle=False, num_workers=4 ) # 初始化模型 model = LDDU(args).to(device) optimizer = torch.optim.Adam(model.parameters(), lr=args.lr) criterion = nn.BCEWithLogitsLoss() # 多标签分类损失 # 训练循环 best_val_f1 = 0.0 for epoch in range(args.epochs): model.train() total_loss = 0.0 for batch in tqdm(train_loader): # 解析批次数据(文本/视觉/音频特征及掩码、标签) text, text_mask, visual, visual_mask, audio, audio_mask, labels = batch text = text.to(device).float() visual = visual.to(device).float() audio = audio.to(device).float() labels = labels.to(device).float() # 前向传播 logits, cl_loss, _ = model(text, visual, audio, text_mask, visual_mask, audio_mask) # 总损失 = 分类损失 + 对比损失(λ=0.3) ce_loss = criterion(logits, labels) total_loss_batch = ce_loss + args.cl_weight * cl_loss # 反向传播 optimizer.zero_grad() total_loss_batch.backward() optimizer.step() total_loss += total_loss_batch.item() # 验证 model.eval() val_preds, val_labels = [], [] with torch.no_grad(): for batch in val_loader: text, text_mask, visual, visual_mask, audio, audio_mask, labels = batch text = text.to(device).float() visual = visual.to(device).float() audio = audio.to(device).float() labels = labels.to(device).float() logits, _, _ = model(text, visual, audio, text_mask, visual_mask, audio_mask) preds = torch.sigmoid(logits) > 0.5 # 阈值0.5 val_preds.append(preds.cpu().numpy()) val_labels.append(labels.cpu().numpy()) val_preds = np.concatenate(val_preds) val_labels = np.concatenate(val_labels) val_f1 = f1_score(val_labels, val_preds, average="micro") print(f"Epoch {epoch+1}/{args.epochs} | Train Loss: {total_loss/len(train_loader):.4f} | Val F1: {val_f1:.4f}") # 保存最佳模型 if val_f1 > best_val_f1: best_val_f1 = val_f1 torch.save(model.state_dict(), os.path.join(args.output_dir, "best_model.pth")) # 测试(加载最佳模型) model.load_state_dict(torch.load(os.path.join(args.output_dir, "best_model.pth"))) model.eval() test_preds, test_labels = [], [] with torch.no_grad(): for batch in DataLoader(test_dataset, batch_size=args.batch_size, shuffle=False): text, text_mask, visual, visual_mask, audio, audio_mask, labels = batch text = text.to(device).float() visual = visual.to(device).float() audio = audio.to(device).float() labels = labels.to(device).float() logits, _, _ = model(text, visual, audio, text_mask, visual_mask, audio_mask) preds = torch.sigmoid(logits) > 0.5 test_preds.append(preds.cpu().numpy()) test_labels.append(labels.cpu().numpy()) test_preds = np.concatenate(test_preds) test_labels = np.concatenate(test_labels) test_f1 = f1_score(test_labels, test_preds, average="micro") print(f"Test Micro F1: {test_f1:.4f}")if __name__ == "__main__": parser = argparse.ArgumentParser() # 模型参数 parser.add_argument("--hidden_dim", type=int, default=256, help="隐藏层维度") parser.add_argument("--emotion_dim", type=int, default=6, help="情感类别数") parser.add_argument("--queue_size", type=int, default=1024, help="对比队列大小") parser.add_argument("--temperature", type=float, default=0.1, help="对比学习温度参数") # 训练参数 parser.add_argument("--lr", type=float, default=2e-5, help="学习率") parser.add_argument("--epochs", type=int, default=30, help="训练轮数") parser.add_argument("--batch_size", type=int, default=64, help="批次大小") parser.add_argument("--cl_weight", type=float, default=0.3, help="对比损失权重") parser.add_argument("--device", type=str, default="cuda:0", help="训练设备") parser.add_argument("--data_path", type=str, default="./dataset/CMU-MOSEI/aligned_data.pkl", help="对齐数据路径") parser.add_argument("--output_dir", type=str, default="./checkpoints", help="模型保存路径") args = parser.parse_args() # 创建输出目录 os.makedirs(args.output_dir, exist_ok=True) main(args)
六、运行步骤
数据预处理:运行
dataset/preprocess.py
,将原始 CMU-MOSEI 数据转换为对齐的多模态特征文件(aligned_data.pkl
)。
七、技术框架图
以下是 LDDU 的核心技术框架图,展示多模态特征提取、情感空间分解、对比学习解耦和不确定性融合的完整流程:
上面代码做了调整和更新:
lddu_mmer/├── dataset/ # 数据集处理│ ├── data_process.py # 数据预处理│ └── mosei_dataset.py # 数据集加载器├── models/ # 模型定义│ ├── feature_extractor.py # 单模态特征提取器│ ├── emotion_space.py # 情感空间建模│ ├── uncertainty_fusion.py # 不确定性感知融合│ └── ldd_model.py # 完整LDDU模型├── utils/ # 工具函数│ ├── metrics.py # 评估指标│ ├── losses.py # 损失函数│ └── utils.py # 辅助函数├── train.py # 训练脚本└── config.py # 配置文件
4 实验结果
图5:多模态情绪识别案例。视觉与听觉模态显示出从悲伤到愤怒的情绪转变,而文本模态则明确呈现出与愤怒相关的表达。
主要结果:在表 1 和表 2 中,我们将本方法与 CMU-MOSEI 和 M³ED 数据集上的多种基线方法进行了性能对比。不同于表 1 中多数使用 CTC(Graves 等人,2006)模块对齐未对齐数据集的基线方法,LDDU 无需依赖 CTC 模块,在未对齐数据上表现更优。LDDU 中的情感提取网络直接从序列数据中提取与标签相关的模态特异性特征,不受跨模态序列长度差异的影响。
根据表 1 和表 2 的结果,我们得出以下结论:(1)尽管在 CMU-MOSEI 数据集上召回率和精确率并非最高,但 LDDU 在 mi-F1 和准确率(Acc)等关键指标上优于所有基线方法。值得注意的是,LDDU 在对齐和未对齐数据集上均表现出均衡性能,未对齐数据的准确率提升了 3%,mi-F1 提升了 4.3%。这表明,通过对情感空间而非序列特征进行建模,LDDU 能更有效地捕捉情感相关特征。(2)LDDU 在 M³ED 数据集的所有指标上均实现显著提升,验证了模型的鲁棒性。(3)TAILOR、CARET 和 LDDU 通过特征解耦取得更优性能,突显了在 MMER 任务中考虑各模态对情感识别独特贡献的重要性。(4)尽管多模态大语言模型(MLLM)在视频理解方面表现出色,但 LDDU 的性能显著优于 MLLM。这可能是由于 MLLM 捕捉细粒度情感信息的能力有限,而轻量级模型在该场景下更具优势。
消融实验:为探究各组件的重要性,我们将 LDDU 与不同消融变体进行对比。如表 3 所示,“w/o” 表示移除对应组件,“ow” 表示仅保留特定组件。例如,“w/o ESM” 表示移除可训练特征 L,“w/o σ, μ” 表示在对比学习中仅考虑方差或均值,“Lcls w/o ˆyσ, ˆyμ” 表示最终分类不使用方差或语义中心特征。实验发现:
表1:CMU-MOSEI数据集在时序对齐与非对齐设置下的性能对比。由于基于大语言模型的方法处理原始视频片段,对齐结果不可用。最佳结果标红,次佳结果标蓝。多模态方法、多模态大语言模型与经典方法的完整对比见附录A.2节。
1)情感空间建模(ESM)的作用:在(1)中用 MLP 注意力替换 ESM、(2)中移除损失 L_dir 后,结果表明带监督信号的可训练特征 L 能从原始多模态序列中学习更具判别性的特征。
2)对比学习的影响:与(3)中不含 Lscl 的变体相比,各指标性能下降,证实对比学习在特征解耦中的关键作用。(4)优于(3),说明更大批量可增强对比学习效果。此外,(5)和(6)表明计算分布相似度时需同时考虑均值和方差。
3)不确定性校准的效果:与无校准变体相比,施加约束(8、9、10、12)后性能提升,校准过程通过对齐方差与不确定性,优化了预测结果。
4)不确定性感知融合的作用:为建模任意不确定性,融合语义特征与分布区域信息至关重要,(11)和(12)显示两者均对最终分类有贡献。
结论
我们提出了 LDDU 框架,通过潜在情感空间概率建模来捕捉 MMER 中的任意不确定性。LDDU 利用高斯分布解耦语义特征和不确定性,缓解了由情感强度变化和情感重叠引起的歧义问题。此外,不确定性感知融合模块基于分布不确定性自适应地集成多模态特征。在 CMU-MOSEI 和 M³ED 上的实验结果表明,LDDU 取得了最先进的性能。这项工作开创了概率情感空间建模,为不确定性感知的情感计算提供了有价值的见解。
数据集介绍:
CMU 多模态意见、情感和情感强度 (CMU-MOSEI) 数据集是迄今为止最大的多模态情感分析和情感识别数据集。该数据集包含来自 1000 多名在线 YouTube 使用者的 23500 多个句子视频。数据集是性别平衡的。所有句子的话语都是从各种主题和独白视频中随机选择的。视频被转录并适当地标点。该数据集可通过 CMU 多模态数据 SDK
GitHub:https://github.com/CMU-MultiComp-Lab/CMU-MultimodalSDK 下载。
下图显示了 CMU-MOSEI 中的情绪和情绪分布:
以下词云演示了视频的主题(大小表示视频数量):
下载地址2:https://hyper.ai/cn/datasets/17235