
简介面向深度学习多模态情感分析方向的完整算法源码与说明包适合计算机、数学、电子信息等专业学生用于课程设计、期末大作业或毕业设计也适合对情感分析感兴趣的初学者开展实战演练与算法复现。资源共36个文件核心为Python源码覆盖模型结构定义、层次模块实现、参数配置、训练与测试等环节同时配有Markdown格式的多模态情感分析论文笔记和算法说明并附29张运行过程、模型结构或结果可视化截图便于对照文字理解代码实现思路。整体压缩包仅6.7MB目录结构清晰下载后可直接运行调试。目前已有153人浏览学习参考价值得到初步验证。借助源码、笔记与截图读者可获得完整的多模态情感分析项目方案掌握文本与图像等模态特征的融合处理方法梳理从数据处理、模型搭建到效果评估的完整流程为课程项目或毕业设计的深化改进提供扎实基础。1. 多模态情感分析从模态割裂走向统一建模的那一步一段客服录音里用户嘴上说着“好的谢谢你”声音却发紧视频画面里眉头还拧着。单模态模型会给出三个互相矛盾的结论——文本判正面语音判紧张人脸判不悦。多模态情感分析就是把这些信号放进同一个模型用深度学习统一建模输出一个更接近人直觉的综合判断。它不执着于把每个模态单独做到极致而是吃透模态间的互补与冲突这正是它与情感分析最本质的差别。这个方向适合做短视频内容审核、直播情绪预警、智能客服质检和人机交互体验分析的人。标题里的 LW 不是某个框架缩写而是 LightWeight——最终方案要轻量、可落地、能压进边缘设备。下文按三模态特征提取、融合策略、训练调参、翻车避坑、轻量化部署一路讲到底每一步都按可复现的工程方式拆开。2. 三模态特征提取把文本、音频、人脸变成可融合的向量融合的前提是每个模态都有稳定的数值表示而不是把原始波形、像素直接塞给模型。这一章分别讲文本、音频、人脸三个模态的常见工程做法和取舍。文本侧要处理中文语境里的反讽和转折音频侧要在手工特征和预训练特征之间选边人脸侧要解决抽帧、检测、对齐这一串前置问题。2.1 文本模态轻量预训练编码器与截断策略文本是情感分析里信息密度最高的模态显式情绪词“气死我了”和反讽表达“你可真棒”都需要上下文建模。词向量加 CNN 的做法对反讽基本无能为力常见做法是直接用中文预训练模型比如 hfl/chinese-roberta-wwm-ext 这类 BERT 家族模型。LW 场景下可以换成蒸馏版本或者 sentence-transformers 里的小型多语言模型输出维度从 768 降到 384融合层参数也随之变少。下面这段代码把一条文本编码成 768 维向量核心是 mean pooling 而不是直接取 CLS 向量。from transformers import AutoTokenizer, AutoModel import torch model_name hfl/chinese-roberta-wwm-ext # 中文预训练鲁棒性好 tokenizer AutoTokenizer.from_pretrained(model_name) model AutoModel.from_pretrained(model_name) model.eval() def text_to_vector(text: str, max_len: int 128): inputs tokenizer( text, max_lengthmax_len, paddingmax_length, truncationTrue, return_tensorspt, ) with torch.no_grad(): outputs model(**inputs) # 用 last_hidden_state 做 mean pooling比直接取 CLS 对短文本更稳 mask inputs[attention_mask].unsqueeze(-1).float() vec (outputs.last_hidden_state * mask).sum(dim1) / mask.sum(dim1) return vec.squeeze(0) # 768 维mean pooling 把整句话的语义均匀地压进一个向量不会出现 CLS 向量在某些任务上对首 token 过敏感的问题。max_len 按实际任务调整短视频弹幕和客服短句 128 够用长评论建议提到 256。这里有个容易忽略的点如果文本来自 ASR必须保留每句话的时间戳后面与音频、视频帧对齐时靠它定位。截断策略也很关键默认 truncation 会直接砍掉句子尾部长句里的转折词一旦被截掉情感极性可能反转具体现象在避坑章节里展开。2.2 音频模态eGeMAPS 低层描述符为什么仍是落地首选音频情感特征主要有两条路线。一条是 openSMILE 提取的 eGeMAPS 低层描述符包含音高、能量、抖动、闪烁等 88 维统计量提取速度快、不依赖 GPU、在端侧也能跑缺点是缺少对语义语境的理解。另一条是 wav2vec2、HuBERT 这类预训练模型的深层特征表征能力强但对算力要求高LW 项目里通常只做对比实验不直接上生产。实际落地时我会先用 eGeMAPS 跑通整套流程如果准确率不够再替换预训练特征这样每一层的调试成本都可控。openSMILE 的提取命令很直接SMILExtract -C config/eGeMAPSv02.conf \ -I sample.wav \ -O sample.csv \ -appendcsv 0 \ -csvSeparator ;-C 指定配置文件eGeMAPSv02 是官方标准配置-I 和 -O 分别指定输入音频和输出 CSV-appendcsv 0 表示每次覆盖而不是追加-csvSeparator 用分号分隔列避免和特征值里的逗号冲突。默认配置下窗口是 25ms、步长 10ms输出按帧排列。这个 25ms 窗口和视频常用的 25fps 帧率天然对齐一帧窗口正好对应一帧画面后面做多模态特征文件就会轻松很多。如果录音环境嘈杂建议在提取前先加一步 VAD 和人声增强否则音量、噪底这些统计量会被环境音带偏情感特征被污染之后融合层怎么调都救不回来。提取出的 CSV 在 Python 里读进来后先做 z-score 归一化再存成 npy。注意 openSMILE 原始输出里可能带非数值标记行读取时要按行过滤。2.3 人脸模态用 MTCNN 加轻量表情模型提取视频帧特征人脸模态的核心流程是抽帧、检测、对齐、特征提取。视频里表情变化比语音慢不需要每帧都处理常见做法是每秒钟抽 5 帧既保留表情轮廓又避免大量重复计算。每一步都要考虑失败兜底尤其在低质量视频里侧脸、遮挡、运动模糊都会让检测器失灵。下面这段代码用 MTCNN 从单帧里取出人脸区域。import cv2 from mtcnn import MTCNN detector MTCNN(min_face_size20, steps_threshold[0.6, 0.7, 0.7], scale_factor0.709) def extract_face_roi(frame, cacheNone): dets detector.detect_faces(frame) if not dets: return cache, None # 检测失败复用上一帧结果 box dets[0][box] x, y, w, h box # 扩边 10% 避免把额头/下巴裁掉 margin int(0.1 * max(w, h)) x, y max(0, x - margin), max(0, y - margin) x2, y2 min(frame.shape[1], x w margin), min(frame.shape[0], y h margin) roi frame[y:y2, x:x2] roi cv2.resize(roi, (112, 112)) # 模型输入尺寸 return roi, {box: box}这里只取置信度最高的人脸框。多说话人场景下可以按画面面积和中心距离排序挑最主要的那个人cache 参数用于连续丢帧时复用上一帧的人脸结果避免特征序列出现空洞。扩边 10% 是为了不让检测框把额头或下巴切掉这看起来是细节但对表情识别的影响很明显。112x112 是 MobileFaceNet 一类轻量表情模型的标准输入尺寸输出 embedding 通常是 128 或 512 维。另一个可用方案是 CLIP 的图像编码器提取通用视觉特征但通用特征对表情的细粒度差异不敏感做视频人物情感分析时我更倾向用专门的表情模型。2.4 三模态特征对齐时间戳统一与特征文件格式三个模态单独提完之后必须落在同一个时间轴上才能进融合层。常见做法是以视频帧时间戳为基准音频特征按 25ms hop 对应到每一帧文本按 ASR 的 start 和 end 落到帧区间人脸特征随抽帧位置落位。下面是按 clip 对齐的示例。import numpy as np def align_features(clip_start, clip_end, text_vec, audio_feat, face_emb, fps25): # audio_feat 按 25ms hop 提取face_emb 对应每一视频帧 n_frames int((clip_end - clip_start) * fps) audio_frames audio_feat[clip_start * 40:clip_end * 40] # 每秒 40 帧音频特征 face_resampled np.repeat(face_emb, max(1, n_frames // len(face_emb)), axis0)[:n_frames] return { text: text_vec, audio: audio_frames.mean(axis0), # 展平到 clip 级 face: face_resampled.mean(axis0), }对齐后的特征以 clip 为单位保存成 npy 或 h5 文件每条记录包含 text、audio、face 三个向量以及缺失标记。这里把音频和人脸都做了均值池化好处是实现简单坏处是会把瞬间的愤怒、惊慌抹平第 5 章会专门讲这个问题。特征文件命名建议带视频 ID、起始时间戳和说话人 ID后面做验证集划分时按说话人分组能避免数据泄漏。3. 融合策略选型早期拼接、晚期平均与跨模态注意力谁更稳特征提完之后融合层直接决定多模态情感分析的天花板。常见做法有三种早期把三个向量拼在一起过分类器、晚期对三个模态的预测做平均或投票、中间用注意力机制让模态之间先做交互再分类。三者没有绝对优劣但适用场景完全不同。3.1 早期拼接为什么不推荐直接 concat直接把文本 768 维、音频 88 维、人脸 512 维拼起来是最快的方案但至少有三个问题。第一向量维度和量纲差距悬殊文本那一路在梯度里占绝对主导音频和人脸几乎成了摆设。第二三个模态的关系不是简单相加文本说“不错”但音频语气讽刺这种冲突需要模态间显式交互才能建模直接拼接学不到。第三在小数据集上直接 concat 高维向量极易过拟合验证集准确率虚高上线就翻车。改进方式是先做投影对齐把三个模态映射到同一个低维空间再拼接。投影层可以用单层线性加 LayerNorm也可以用后面要讲的非对称注意力。这里给一个最小可跑的早期拼接基线作为后续实验的对照import torch.nn as nn class EarlyConcat(nn.Module): def __init__(self, dims[768, 88, 512], proj_dim256, num_classes7): super().__init__() self.proj nn.ModuleList([ nn.Sequential(nn.Linear(d, proj_dim), nn.LayerNorm(proj_dim)) for d in dims ]) self.classifier nn.Linear(proj_dim * 3, num_classes) def forward(self, text_vec, audio_vec, face_vec): x torch.cat([ self.proj[0](text_vec), self.proj[1](audio_vec), self.proj[2](face_vec) ], dim-1) return self.classifier(x)proj_dim 取 256 是为了和后面的跨模态注意力共用一套维度方便做消融对比。这个基线的作用不是拿来做最终模型而是验证融合层是否真的带来了增益——如果跨模态注意力在自建验证集上打不过这个基线说明问题出在特征侧或数据侧而不是融合结构不够高级。3.2 跨模态注意力融合的 PyTorch 实现跨模态注意力的思路是用一个模态的特征去查询其他模态的特征让文本能“看到”音频和视频里和它矛盾的信息。落实到工程上可以做成一个轻量 transformer 层query 来自主模态key 和 value 来自被查询模态。下面是按这个思路实现的单层跨模态注意力模块。import torch import torch.nn as nn import torch.nn.functional as F class CrossModalAttention(nn.Module): def __init__(self, d_model256, nhead4, dropout0.1): super().__init__() self.q_proj nn.Linear(d_model, d_model) self.k_proj nn.Linear(d_model, d_model) self.v_proj nn.Linear(d_model, d_model) self.norm nn.LayerNorm(d_model) self.dropout nn.Dropout(dropout) self.nhead nhead def forward(self, query, kv, maskNone): B, D query.shape # 投影并拆头 q self.q_proj(query).view(B, self.nhead, D // self.nhead) k self.k_proj(kv).view(B, self.nhead, D // self.nhead) v self.v_proj(kv).view(B, self.nhead, D // self.nhead) scores (q * k).sum(-1) / (D ** 0.5) # 点积注意力 if mask is not None: scores scores.masked_fill(mask, -1e9) attn F.softmax(scores, dim-1) out (attn.unsqueeze(-1) * v).sum(dim1) return self.norm(query self.dropout(out))使用时可以对每个模态各查一次其他两个模态例如 h_text_attn CrossModalAttention()(text, concat(audio, face))然后把三个注意力输出和原始投影向量在特征维度上拼接再过一层 FFN。mask 参数用来屏蔽缺失模态缺失模态对应位置的 scores 直接置为极小负值这样注意力不会把权重分给它。nhead 取 4 而不是 8是因为三模态特征维度只有 256头数太多会把每个头的维度切得太碎学不到稳定的交互模式。残差连接在这里不是可选项——去掉之后 attention 输出的分布偏移会让后面的分类层很难收敛。3.3 门控动态融合与模态缺失兜底跨模态注意力解决的是“模态间如何交互”门控融合解决的是“这个样本该信谁”。不同视频的质量差异很大有的画面全程清晰有的后半段人脸完全失焦有的音频干净有的背景音乐盖过人声。门控机制让模型根据输入特征动态分配三个模态的权重缺失模态的置信度天然会低门控会自动忽略它。下面是一个带门控的分类头实现。import torch import torch.nn as nn class GatedFusion(nn.Module): def __init__(self, d_model256, num_classes7): super().__init__() self.gate nn.Linear(d_model * 3, 3) self.classifier nn.Linear(d_model * 3, num_classes) self.temperature 1.0 # 可调控制门控分布的锐利程度 def forward(self, h_text, h_audio, h_face, maskNone): h torch.cat([h_text, h_audio, h_face], dim-1) gate_logits self.gate(h) / self.temperature if mask is not None: # mask 为 0 表示该模态缺失 gate_logits gate_logits.masked_fill(mask 0, -1e9) gate_weight torch.softmax(gate_logits, dim-1) weighted gate_weight[:, 0:1] * h_text gate_weight[:, 1:2] * h_audio gate_weight[:, 2:3] * h_face return self.classifier(torch.cat([weighted, h], dim-1))temperature 初始设为 1.0训练后期可以衰减到 0.7让门控决策更自信。注意门控和分类器共用输入 h但门控只用三个模态的投影向量做注意力打分。如果某个模态整体质量差门控权重会趋向均匀分布这时要检查特征侧而不是怪融合层——门控没有能力凭空创造信息它只能压制噪声。多模态融合论文里经常把这套叫统一处理框架落到工程上就是一套投影、交互、门控的分类头复杂度不高但每一步的细节都影响最终性能。4. 训练调参损失函数、采样策略与三个必调超参数融合模型搭好之后训练策略往往决定它能发挥几成实力。多模态情感分析的数据集天然存在两个麻烦类别不均衡和样本总量小。公开数据集如视频情感分析基准多数只有几千条特定领域的数据像 Bird1445 这类公开数据集又很难直接迁移到人脸情感场景自采数据仍是主流。在这样的数据条件下损失函数和采样策略比模型结构更能稳定带来涨点。4.1 损失函数设计Focal Loss 处理长尾情感分布中文视频弹幕和客服录音里“中性”和“开心”占比很高“厌恶”“恐惧”这类样本常年只有前者的十分之一。交叉熵损失在长尾分布下会把绝大多数梯度分配给头部类别导致尾部类别的召回率极低。Focal Loss 通过给易分类样本降权来解决这个问题实现很简洁。import torch import torch.nn as nn import torch.nn.functional as F class FocalLoss(nn.Module): def __init__(self, alphaNone, gamma2.0): super().__init__() self.alpha alpha # 类别权重一维 tensor self.gamma gamma def forward(self, logits, targets): ce F.cross_entropy(logits, targets, reductionnone) pt torch.exp(-ce) # 模型对正确类别的置信度 focal_weight (1 - pt) ** self.gamma if self.alpha is not None: alpha_w self.alpha[targets] focal_weight focal_weight * alpha_w return focal_weight.mean()gamma2.0 是默认推荐值太大容易让训练不稳定太小就退化成普通交叉熵。alpha 可以从训练集统计得到alpha_i N / (C * n_i)N 是总样本数C 是类别数n_i 是第 i 类的样本数。这里有一个血泪经验alpha 和 gamma 要一起调只调 alpha 不调 gamma 会让头部类别的准确率明显下降因为降权太凶狠了。调参时先固定 gamma2.0 把 alpha 跑三组再固定最优 alpha 把 gamma 从 1.0 到 3.0 扫一遍总共四五个实验就能定下来。4.2 类别不均衡的采样策略不换模型也能涨点除了在损失函数上做文章采样策略也是常见手段。WeightedRandomSampler 让每个 batch 里小类别的出现频率提升实现简单效果直接。下面这段代码按类别数量反比计算采样权重。from torch.utils.data import WeightedRandomSampler, DataLoader def make_sampler(labels, multiplier2.0): class_counts torch.bincount(labels, minlength7).float() weights 1.0 / (class_counts 1e-6) sample_weights weights[labels] ** multiplier return WeightedRandomSampler( weightssample_weights, num_sampleslen(labels), replacementTrue, )multiplier 控制采样权重的放大倍数取 1.0 是最温和的版本取 2.0 会明显拉高低资源类别的出现频率。replacementTrue 意味着同一个样本可能在同一个 epoch 里多次出现配合数据增强一起用效果更好否则模型会对重复样本过拟合。文本增强可以用同义词替换音频增强加随机噪声和音量扰动人脸增强做亮度、对比度和水平翻转。增强强度不要开太高音频加噪过猛会让模型把噪声当成情感特征验证集上不掉点、上线后一遇真实噪声就崩。4.3 三个必调的超参数多模态情感分析训练里下面这三个参数对结果影响最大建议每次实验都记录下来。超参数推荐范围实验方向学习率融合层 1e-3 到 3e-3预训练部分 1e-5 到 5e-5融合层和预训练部分必须分开设统一用 3e-5 会让融合层学不动Batch size16 到 32显存不够时用梯度累积到等价 batch小 batch 配合高学习率容易震荡多模态数据本身噪声大稳妥为上融合层 dropout0.1 到 0.30.1 起步过拟合明显再往上加加太多会让模态交互被抹平学习率设置是最容易踩坑的地方预训练模型部分确实要用小学习率微调但融合层是随机初始化的给它也设 1e-5 会让整个模型几个月都练不上去。常见做法是用两个参数组预训练部分挂 warmup 后线性衰减融合层用恒定小学习率或者用一个小的 warmup 阶段。warmup_ratio 取 0.1weight_decay 取 0.01fusion dropout 从 0.1 开始试。如果训练曲线显示验证 loss 一路走高而训练 loss 还在降那基本就是 dropout 不够或数据增强强度不够先改这两个再动模型结构。5. 避坑指南多模态情感分析里最容易翻车的 5 个现场这个章节按“现象、原因、解决”三条线写都属于项目里真实发生过的翻车现场希望下面的经验能帮你省掉几周的调试时间。5.1 文本截断丢掉转折词长句被砍掉“但是”之后情感反转现象验证集上模型把“电影不错但是节奏拖沓看得我快睡着了”判成正面而且相当自信。检查文本预处理后发现max_len128 的截断策略直接砍掉了句子后半段转折词和负面评价全在截断范围里。原因默认 truncationTrue 是保留头部截断尾部对长文本情感分析来说情感极性经常藏在句子后半段截断等于把关键证据提前删掉。解决把 max_len 提到 256同时改成 headtail 的截断策略保留前 64 个 token 和后 128 个 token中间部分直接丢弃。这个改动在许多长句样本上能立刻看到效果融合层的输入文本特征不再被系统性误导。如果还要处理更长的文本可以按句切分后对每句单独编码再对整段的句子向量做 mean pooling但那样计算量会成倍上涨LW 场景不推荐。5.2 音频特征把瞬时爆发抹平全段均值让“愤怒”变成“平静”现象一段视频里人物前两秒平静、后两秒爆发怒吼模型却给出“中性”的预测。检查对齐后的音频特征发现整段六秒的 eGeMAPS 特征被平均成一个向量瞬间的愤怒能量在均值里被稀释得无影无踪。原因特征对齐时为了省事对整段做 mean pooling这个操作把时间维度上的情绪变化全部压缩掉了。情感不是静态属性它是随时间波动的信号均值池化等于人为制造信息丢失。解决把 clip 切得更细比如切成 1 秒的小窗口每个窗口保留一组统计特征模型按时间步做融合让注意力机制自己决定哪一帧更重要。窗口切太小也不行0.5 秒窗口里的音高统计不够稳定抖动值会失真。短视频项目里 1 秒窗口是折中之后比较稳的选择音频特征按 25ms 帧抽取后每 40 帧一组统计量刚好对应 1 秒。5.3 人脸检测失败时硬填零向量缺失模态污染主模态输出现象视频后半段人物侧脸MTCNN 检测不到人脸特征提取返回了 512 维的零向量。融合模型直接把这堆零向量参与 attention 计算门控机制被打乱原本清晰的文本负面信号也被拖累预测结果飘忽不定。原因零向量在 attention 里会产生一个虚假的“匹配信号”模型不知道该不该信任它。门控虽然理论上能学会忽略缺失模态但零向量还是有数值梯度训练时容易被带偏。解决缺失模态不传零向量而是传一个可学习的 [MISSING] 嵌入同时把对应的 mask 置 0参与 attention 和门控计算时直接屏蔽。第 3.2 节的 CrossModalAttention 里 mask 参数就是这个用途。另外可以在检测失败时复用上一帧的人脸特征但要加一个时效上限超过 2 秒的旧特征宁可标记缺失也不要用否则人脸模态等于在胡说八道。5.4 类别不均衡导致愤怒样本全被预测成中性现象验证集整体准确率 82%看起来不错但细看混淆矩阵“愤怒”类别的召回率只有 21%几乎全被预测成了“中性”。进一步统计训练集发现中性样本占比超过 60%普通交叉熵在训练时完全被头部类别主导。原因交叉熵对每个样本同等对待头部类别的梯度累积效应会让决策边界整体偏向多数类。这个现象在情感分析里比图像分类更严重因为情感类别之间的边界本身就很模糊中性处在所有极性类别中间容易吞掉邻近类别。解决先在数据层面动手用 WeightedRandomSampler 把每个 batch 里的类别比例拉到接近均衡再加 Focal Loss 对难样本重点学习。两个方法叠加后愤怒类别的召回率通常能提升 15 到 25 个百分点代价是中性类别的精确率会有小幅度下降。如果对精确率指标有硬性要求可以在门控融合输出后加一个人为的阈值偏移但这个做法是在掩盖数据问题不推荐作为最终方案。5.5 验证集划分泄漏同一说话人同时出现在训练和验证集现象训练时验证 F1 一直稳定在 85% 以上准备上线时拿了一批新主播的录音测试F1 直接掉到 58%。回头检查数据划分发现当时按文件随机切分同一个主播的视频片段被同时分进了训练和验证集模型其实是在“认人”而不是“认情绪”。原因多模态数据里同一说话人的语气、音色、表情习惯高度一致随机切分会让验证集严重高估模型对新人的泛化能力。音频特征尤其明显音色这个跟情感无关的维度反而变成了模型的最强信号。解决按说话人 ID 或者按视频 ID 做 GroupKFold 划分保证同一个人的所有片段只在训练集或只在验证集。这个改动会让验证集指标瞬间掉几个点但它是真实泛化能力的体现。如果项目里多人同框还需要按时间窗口去重避免相邻帧的画面相似度过高造成的隐式泄漏。新手最容易在这个环节翻车因为训练曲线看起来一切正常坑全埋在下游上线阶段。6. LW 轻量化收尾把融合模型压到能部署的状态LW 不能只停留在口头上。模型在 GPU 上跑得再好压不到目标硬件上就是白做。下面从导出格式、量化精度和推理缓存三个方向给出落地路径。6.1 ONNX 导出与 INT8 量化PyTorch 模型先导出为 ONNX再交给 ONNX Runtime 做 INT8 量化是成本最低的轻量化路线。torch.onnx.export( model, (text_vec, audio_vec, face_vec), mma_model.onnx, opset_version17, input_names[text, audio, face], output_names[logits], dynamic_axes{text: {0: batch}, audio: {0: batch}, face: {0: batch}} )opset 版本建议 17 以上对 LayerNorm 和 Attention 算子的支持更完整dynamic_axes 按 batch 维度设置动态轴方便流式推理时单条请求也能跑。导出后用 onnxruntime quantization 工具做 INT8 量化量化后模型体积通常能压缩到原来的四分之一到三分之一推理时间在 CPU 上也有明显下降。INT8 会让最终准确率掉 1 到 2 个点这是在轻量化目标下能够接受的代价。6.2 流式推理缓存避免重复提取特征实时场景里每 250ms 出一次预测结果如果每次都重新提取三模态特征算力会被浪费在重复计算上。常见做法是维护一个特征缓存队列人脸特征按帧存音频特征按 100ms 窗口存文本按句存新预测只对新增时间片的特征做提取历史缓存直接复用。这个设计能让推理耗时从几百毫秒降到几十毫秒是 LW 落地里最关键的一步。6.3 用混淆矩阵决定下一次迭代方向我一般会在交付前跑一次全量测试集把混淆矩阵打印出来逐类看。有一次发现所有“惊讶”都被分到了“恐惧”后来查标注记录才发现是数据标注标准本身就混淆了这两种情绪遇到这种情况我不会去调模型结构而是先回看数据。多模态情感分析里模型结构能提供的增益远小于数据质量带来的影响把每一个误分类样例翻出来看比换更花哨的注意力机制有用得多。希望这些踩过的坑能帮你少走一段弯路。本文还有配套的精品资源点击获取