📝个人主页🌹:Eternity._
🌹🌹期待您的关注 🌹🌹
Joyful: Joint Modality Fusion and Graph Contrastive Learning for
Multimodal Emotion Recognition
EMNLP顶会 2023 代码复现
原文连接
文章摘要
多模态情感识别旨在识别多种模态中每个话语的情感,这在人机交互应用中越来越受到关注。当前基于图的方法未能同时描述对话中的全局上下文特征和局部多样的单模态特征。此外,随着图层数量的增加,它们很容易陷入过度平滑的情况。本文提出了一种用于多模态情感识别的联合模态融合和图对比学习方法(JOYFUL),其中多模态融合、对比学习和情感识别被联合优化。具体来说,我们首先设计了一种新的多模态融合机制,可以提供全局上下文和单模态特定特征之间的深度交互和融合。然后,我们引入了一个图对比学习框架,包括视图间和视图内对比损失,以学习更可区分的表示,适用于具有不同情绪的样本。对三个基准数据集的大量实验证明,JOYFUL相对于所有基线方法取得了最先进的性能。
本文所涉及的所有资源的获取方式:这里
模型整体框架
JOYFUL架构整体;首先提取单模态特征,然后使用多模态融合模块将它们融合起来,并将其作为基于 GCL 的框架的输入,以学习更好的情感识别表示。
特征提取模块
特征融合模块
特征融合模块一共包含两部分,一部分是上下文特征融合模块,一部分是特定区域的特征表示
上下文学习模块
通过简单的全链接网络调整了特征维度,堆叠两层transformer得到最终的融合之后的特征;模型在这里做了两次重构损失,对特征在做变换的时候做了一定的限制。
(1)特征在经过transformer之前与之后二范数损失重构:
(2)文章定义了一个全局情感状态,用最终的特征与该全局情感状态做重构损失:
个人理解:模型在进行训练的时候,是把一个向量空间的向量映射到另外一个向量空间上的向量,因为深度学习的这个向量映射的方式是不可控的,加入一定的限制之后,训练的方向会尽量的向着希望的方向进行。以上两种损失重构,就是把限制加到损失函数里面,进行限制。(1)中的重构方式是不希望特征在经过两层的transformer之后太过发散,与原来的特征求二范数加入损失函数,达到最终不会太过偏离原是数据的效果(2)定义了一个全局的情感信息,希望上下文学习的时候,不太偏离主题;
上下文主题的定义方式是:
局部特征学习
定义一个向量空间,用三个模态的向量分别与该空间的所有基向量求余弦相似度,然后使用该空间的所有基向量乘以相应的余弦相似度后累加,获得新的向量表示:
中间增加了一步归一化处理,防止某些值过大:
最后还是有重构损失的步骤:
图对比学习模块
首先是对图的定义,如图所示,图的定义很简单,一个结点表示从上一个模块得到的特征信息,紫色表示外部联系,红色是内部联系,箭头表示先后顺序
补充对比学习的理论知识:
先简单的理解正样本就是和目标比较相似但又不完全一样的样本, 负样本就是完全不一样的样本. 那么优化InfoNEC实际就是:
1.拉近正样本之间的距离
2.推远负样本之间的距离
数据增强(Data Augmentation, DA). 由于CL是一种自监督学习,并不依赖标签(label)来定义正负样本. 因此CL的正负样本往往依靠数据增强来产生, 在CL中来自同一样本的数据增强认为是正样本, 不同样本之间的数据增强是负样本,(见图 2). 那么这也就意味数据增强就会很大程度的影响对比学习的性能.
下面将是本论文数据增强的方式
图数据增强
图增强(Graph Augmentation)是指通过添加、修改或删除图中的节点或边来改善图数据的质量或增加图的多样性。这种技术通常用于图数据挖掘和图神经网络等领域,旨在提高模型的性能和鲁棒性。
特征屏蔽(FM):给定初始化的话语的表示,我们随机选择初始化表示语句的p个维度,并用0掩盖它们的元素
边扰动(EP):给定图G,我们随机删除并添加p%的边
全局拉近(GP):给定图G,我们首先计算说话人之间的相似性,并在说话人之间随机添加p%边
FM+GP、FM+EP两种方式,产生两个子图,分别用于对比学习。
图卷积对比学习
题外话:这篇文章其实本质还是在做图卷积,说一下我个人的理解,图卷积其实还是从一个向量映射的到另外一个向量,这篇文章其实就是在做图卷积的时候,增加了一种损失函数,来要求,或者说是限制了,从一个向量映射到另外一个向量的这个映射方式。因为深度学习不可控,增加了一种限制方式,让这种映射往一个想要的方向去靠近。
图增强之后变成3个图,分别进行图卷积,然后中间的图是没有进行变化的,用来和上下两个图进行图对比,简单理解就是做了损失函数,加在最后的损失函数里面。
演示效果
核心逻辑
在这里可以粘贴您的核心代码逻辑:
# start
class JOYFUL(nn.Module):
def __init__(self, args):
super(JOYFUL, self).__init__()
u_dim = 100
if args.rnn == "transformer":
g_dim = args.hidden_size
else:
g_dim = 200
h1_dim = args.hidden_size
h2_dim = args.hidden_size
hc_dim = args.hidden_size
dataset_label_dict = {
"iemocap": {"hap": 0, "sad": 1, "neu": 2, "ang": 3, "exc": 4, "fru": 5},
"iemocap_4": {"hap": 0, "sad": 1, "neu": 2, "ang": 3},
"mosei": {"Negative": 0, "Positive": 1},
"meld": {"Neutral": 0, "Surprise": 1, "Fear": 2, "Sadness": 3, "Joy": 4, "Disgust": 5, "Angry": 6}
}
dataset_speaker_dict = {
"iemocap": 2,
"iemocap_4": 2,
"mosei": 1,
"meld": 9
}
if args.dataset and args.emotion == "multilabel":
dataset_label_dict["mosei"] = {
"happiness": 0,
"sadness": 1,
"anger": 2,
"surprise": 3,
"disgust": 4,
"fear": 5,
}
tag_size = len(dataset_label_dict[args.dataset])
args.n_speakers = dataset_speaker_dict[args.dataset]
self.concat_gin_gout = args.concat_gin_gout
self.cl_loss_weight = args.cl_loss_weight
self.wp = args.wp
self.wf = args.wf
self.device = args.device
self.rnn = SeqContext(u_dim, g_dim, args)
self.gcl = GNN(g_dim, h1_dim, h2_dim, args)
if args.concat_gin_gout:
self.clf = Classifier(
g_dim + h2_dim * args.gnn_nheads, hc_dim, tag_size, args
)
else:
self.clf = Classifier(h2_dim * args.gnn_nheads, hc_dim, tag_size, args)
edge_type_to_idx = {}
for j in range(args.n_speakers):
for k in range(args.n_speakers):
edge_type_to_idx[str(j) + str(k) + "0"] = len(edge_type_to_idx)
edge_type_to_idx[str(j) + str(k) + "1"] = len(edge_type_to_idx)
self.edge_type_to_idx = edge_type_to_idx
log.debug(self.edge_type_to_idx)
def get_rep(self, data=None, whetherT=None):
node_features = self.rnn(data["text_len_tensor"], data["input_tensor"])
features, edge_index, edge_type, edge_index_lengths = batch_graphify(
node_features,
data["text_len_tensor"],
data["speaker_tensor"],
self.wp,
self.wf,
self.edge_type_to_idx,
self.device,
)
graph_out, cl_loss = self.gcl(features, edge_index, edge_type, whetherT)
return graph_out, features, cl_loss
def forward(self, data, whetherT):
graph_out, features, cl_loss = self.get_rep(data,whetherT)
if self.concat_gin_gout:
out = self.clf(
torch.cat([features, graph_out], dim=-1), data["text_len_tensor"]
)
else:
out = self.clf(graph_out, data["text_len_tensor"])
return out
def get_loss(self, data, whetherT):
graph_out, features, cl_loss = self.get_rep(data, whetherT)
if self.concat_gin_gout:
loss = self.clf.get_loss(
torch.cat([features, graph_out], dim=-1),
data["label_tensor"],
data["text_len_tensor"],
)
else:
loss = self.clf.get_loss(
graph_out, data["label_tensor"], data["text_len_tensor"]
)
return loss + self.cl_loss_weight*cl_loss
使用&部署方式
模型训练
python train.py --dataset="iemocap_4" --modalities="atv" --from_begin --epochs=50
python train.py --dataset="iemocap" --modalities="atv" --from_begin --epochs=50
模型评价
python eval.py --dataset="iemocap_4" --modalities="atv"
单模态训练模型
python eval.py --dataset="iemocap_4" --modalities="t"
python eval.py --dataset="iemocap_4" --modalities="v"
python eval.py --dataset="iemocap_4" --modalities="a"
安装下面几个包
- RGCNConv 和 TransformerConv
- PyGCL
- Sentence Embedding
终端进入目录:
执行README中的命令即可
温馨提示
1.数据集和已训练好的模型都在.md文件中有百度网盘链接,直接下载放到指定文件夹即可
2.注意,训练出来的模型是有硬件要求的,我是用cpu进行训练的,模型只能在cpu跑,如果想在gpu上跑,请进行重新训练
3.如果有朋友希望用苹果的gpu进行训练,虽然现在pytorch框架已经支持mps(mac版本的cuda可以这么理解)训练,但是很遗憾,图神经网络的包还不支持,不过不用担心,这个模型的训练量很小,我全程都是苹果笔记本完成训练的。
编程未来,从这里启航!解锁无限创意,让每一行代码都成为你通往成功的阶梯,帮助更多人欣赏与学习!
更多内容详见:这里