简介面向文本、语音、图像与视频四类输入的多模态情感分析完整项目基于Python实现源码、文档、数据集一应俱全适用毕业设计、期末大作业和课程设计。项目按照数据准备、模型构建、训练评估的流程组织代码注释清楚调试仔细新手也能快速复现。压缩包共二十个文件整体约五十七兆字节主要文件包含五个Python脚本、九个pickle特征缓存文件、三个数据集压缩包、PDF说明文档、Markdown说明和结果示例图。其中pickle文件对应MOSI、MOSEI、IEMOCAP等公开情感数据集的不同分类标签zip内为原始语料可无缝衔接训练流程目前已有96人学习下载。这份资源能够提供从多模态数据预处理、特征融合到情感分类输出的完整参考附带文档梳理了运行环境和关键代码逻辑适合作为课程设计或毕业设计的项目底座。1. 多模态情感分析系统单模态漏判的那些情绪它怎么接住做客服质检那会儿我吃过一次亏用户电话里语气已经明显不耐烦嘴上却说“行吧行吧那就这样”文本模型干净利落地判了个“中性”。后来我把语音的韵律、文本的字面语义、工单截图一起送进模型同一个样本才被翻案成“不满”。多模态情感分析系统干的就是这件事——不只看你说了什么还看你怎么说、什么表情说、在什么画面里说。这个仓库提供的是从文本、语音、图像、视频四路输入到情感标签的完整工程自带源码、文档和配套数据集适合正在做舆情分析、客服质检、人机交互的算法工程师和研究生。它解决的不是“哪个模态更准”而是“怎么让四个模态互相补位”。2. 四路输入的处理链路从原始信号到特征张量的完整分工2.1 文本分支从词向量到预训练模型的选择文本情感分析是四个分支里最成熟的一条路。早期做法是把句子里的词转成 embedding再送进 LSTM 或 TextCNN现在基本都换成了预训练模型。常见做法是直接加载中文 BERT 或其蒸馏版本取[CLS]位置的输出作为整句表示。工程上要注意两点一是截断长度客服文本通常不长max_length128够用二是标签空间二分类、三分类还是五分类直接影响最后一层num_labels的取值三分类就是负向、中性、正向。# text_branch.py 文本分支推理示例 from transformers import AutoTokenizer, AutoModelForSequenceClassification tokenizer AutoTokenizer.from_pretrained(bert-base-chinese) model AutoModelForSequenceClassification.from_pretrained( bert-base-chinese, num_labels3 ) def predict_text(text): inputs tokenizer( text, return_tensorspt, truncationTrue, max_length128 ) logits model(**inputs).logits probs logits.softmax(dim-1).tolist()[0] return probs这段代码的关键在truncationTrue和max_length128不截断的话超长文本会爆显存而且中文 BERT 对超过 512 的序列本来就不友好。num_labels3对应情感三分类如果你的数据集是正负二分类这里就要改成 2。推理返回的是概率分布不是硬标签方便后续融合模块拿到的是“置信度”而不是“结论”。2.2 语音分支梅尔频谱与轻量 CNN 的配合语音情感识别的难点在于情感信息分散在音高、语速、能量这些声学特征里单独看一帧没有意义。业界最稳的做法是先把音频切成 2 到 3 秒的片段提梅尔频谱Mel Spectrogram再送进一个轻量 CNN 或 ECAPA-TDNN。特征提取这块librosa 是绕不开的库但版本更替频繁librosa.load的默认采样率是 22050和很多开源音频数据集不一致统一重采样到 16000 是血泪经验。# audio_branch.py 语音特征提取示例 import librosa import torch def extract_mel_feature(audio_path, sr16000): # 强制重采样到 16k避免采样率不一致导致特征错位 y, _ librosa.load(audio_path, srsr) mel librosa.feature.melspectrogram( yy, srsr, n_mels64, hop_length160 ) log_mel librosa.power_to_db(mel) # 输出形状 (64, time_steps)后续CNN自己处理时序 return torch.tensor(log_mel).unsqueeze(0)n_mels64是经过验证的常用值再大对情感任务收益有限反而增加计算量。hop_length160配合sr16000意味着每帧 10 毫秒时间分辨率足够捕捉语气转折。这里有个容易踩坑的点librosa.load会自动混音到单声道但如果你自己用soundfile读音频得手动np.mean一下否则后续维度对不上。2.3 图像分支与人脸表情识别的落地选择图像情感分析最常见的载体是人脸表情识别。流程上先做人脸检测再裁剪对齐最后送分类模型。OpenCV 的 Haar Cascade 在正脸条件下能跑但角度偏一点就漏检实际项目中用 RetinaFace 或 MediaPipe 更稳。表情分类模型常用预训练的 ResNet18 或 MobileNet在 RAF-DB 这类数据集上微调。这里要提醒一个容易翻车的地方人脸检测框的抖动会直接影响表情分类结果建议对连续帧的检测框做平滑或者直接取置信度最高的一帧。# vision_branch.py 图像分支推理示例 import cv2 import numpy as np def predict_face_emotion(frame, face_detector, emotion_model): faces face_detector.detect(frame) if len(faces) 0: return np.array([0.0, 0.0, 1.0]) # 未检测到人脸时给中性兜底 x, y, w, h faces[0] face frame[y:yh, x:xw] face cv2.resize(face, (224, 224)) probs emotion_model(face) # 假设已封装好模型 return probsnp.array([0.0, 0.0, 1.0])这个兜底策略是实战里学来的客服工单配图经常没有人脸模型不能崩给一个全中性的概率分布至少不影响后续融合。注意这里用的是faces[0]只取最大人脸遇到多人场景需要按面积排序取最大否则群体情绪会把个体情绪带偏。2.4 视频分支帧采样与时序建模的组合拳视频本质上是“图像 时序”。直接逐帧跑图像模型显存扛不住而且相邻帧重复信息太多。常见做法是均匀采样 8 到 16 帧每帧过一遍 2D CNN 提特征再用 LSTM 或 Transformer 聚合时序信息。采样策略上均匀采样比随机采样稳定尤其情感表达通常集中在某一段均匀采样能覆盖到爆发点。# video_branch.py 视频帧采样示例 import cv2 def sample_frames(video_path, num_frames8): cap cv2.VideoCapture(video_path) total int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) indices np.linspace(0, total - 1, num_frames, dtypeint) frames [] for idx in indices: cap.set(cv2.CAP_PROP_POS_FRAMES, idx) ret, frame cap.read() if ret: frames.append(frame) cap.release() return framesnp.linspace比range(0, total, step)聪明的地方在于它能保证取到首尾帧避免最后一个片段的情绪高峰被漏掉。num_frames8是性价比比较高的选择16 帧能涨 1 到 2 个点但推理时间翻倍。视频分支在训练时要配合时间掩码做数据增强随机遮掉部分帧逼模型学更鲁棒的时序表示。3. 跑通最小工程源码结构、文档导读与一条推理命令3.1 先看懂目录哪个文件管哪路输入拿到源码包别急着装依赖先花十分钟把目录结构捋一遍。这套系统的工程划分比较常规四个分支各占一个模块融合层单独一个目录数据预处理和训练脚本分开文档集中在docs下multimodal_sentiment/ ├── config/ │ └── config.yaml ├── data/ │ ├── raw/ │ └── processed/ ├── models/ │ ├── text_branch.py │ ├── audio_branch.py │ ├── vision_branch.py │ ├── video_branch.py │ └── fusion.py ├── scripts/ │ ├── train.py │ ├── predict.py │ └── preprocess.py ├── docs/ │ ├── data_guide.md │ ├── model_guide.md │ └── deploy_guide.md └── requirements.txtmodels目录下的四个*_branch.py对应四条模态处理链路fusion.py是融合层这是整个源码包的核心。scripts/predict.py是入口脚本把四个分支串起来跑一条完整推理。config/config.yaml里放着所有超参数改配置不用动代码这个设计值得保留——后面做实验调参的时候你就知道好处了。3.2 从裸机到跑通推理依赖安装与最小命令安装依赖时最容易出问题的是torch和transformers的版本匹配。建议先建虚拟环境再按requirements.txt安装。装完之后别急着跑训练先跑预测脚本验证链路通不通。如果机器上有麦克风阵列或者视频采集设备但当前没有测试样本用data/raw目录下自带的样例就能完成闭环。# 创建虚拟环境并安装依赖 python -m venv venv source venv/bin/activate pip install -r requirements.txt # 跑一条最小推理输入音频文本输出情感概率 python scripts/predict.py \ --text 这个产品用起来还不错 \ --audio data/raw/sample.wav \ --video data/raw/sample.mp4predict.py内部会依次调用四个分支提取特征再交给融合层输出最终概率。--video参数是可选的原因是很多实际场景只有语音没有画面源码里做了模态缺失处理——某一模态没有输入时对应分支返回中性概率占位融合层照样能出结果。3.3 文档先读这三份少走三天的弯路docs目录下的文档不是摆设但按顺序读能省时间。第一份要读data_guide.md它说明了数据集目录怎么摆放、标签格式是什么、缺模态时怎么处理第二份是model_guide.md里面画了模型结构图和各分支输入输出的张量维度这份文档能让你快速定位“我的数据维度为什么对不上”第三份是deploy_guide.md讲的是怎么把模型导出成 ONNX、怎么在 CPU 上跑推理。三份文档加起来不到一百页但能避免大多数低级报错。4. 融合策略与训练参数别把四个模型的结果做平均4.1 三种融合架构的选型逻辑多模态融合算法这块新手最容易犯的错是把四个分支的预测概率直接平均。这个做法在模态独立性强的时候勉强能用但一旦某个模态数据质量差比如语音里全是噪声平均操作会直接把整体精度拖下水。常见的方案是早期融合Early Fusion、晚期融合Late Fusion和混合融合。融合方式实现难度适用场景主要缺陷早期融合低各模态特征维度接近量纲差异大时难收敛晚期融合低模态独立性强丢失跨模态交互信息注意力融合中需要跨模态补位对数据量要求更高早期融合是把四个分支的倒数第二层特征拼接成一个长向量再送分类层。好处是梯度能同时回传到四个分支坏处是文本的 768 维和语音的 64 维拼在一起量纲差异会让训练震荡。晚期融合则更稳四个分支各自训练、各自出概率最后学一组权重加权求和。注意力融合是这两者的折中让模型自己学每个模态的权重而不是人为固定。4.2 注意力融合的 PyTorch 实现注意力融合的核心是用一个可学习的查询向量去扫描各模态特征算出权重。这个实现不算复杂但里面有个细节容易忽略——各分支特征的维度不一致必须先投影到相同的 hidden_dim否则注意力分数计算会出问题。# fusion.py 注意力融合模块 import torch import torch.nn as nn import torch.nn.functional as F class AttentionFusion(nn.Module): def __init__(self, feat_dims, hidden_dim128, num_classes3): super().__init__() # 各模态特征先投影到同一维度否则无法计算相似度 self.projections nn.ModuleList([ nn.Linear(dim, hidden_dim) for dim in feat_dims ]) # 用单层注意力学习模态权重 self.attn nn.Linear(hidden_dim, 1) self.classifier nn.Linear(hidden_dim, num_classes) def forward(self, features): # features 是各分支提取的特征列表顺序固定为 [text, audio, image, video] proj [ self.projections[i](features[i]) for i in range(len(features)) ] proj torch.stack(proj, dim1) # (batch, num_mods, hidden_dim) scores self.attn(proj).squeeze(-1) # (batch, num_mods) weights F.softmax(scores, dim-1) # 加权求和时保留每个模态的贡献方便后期做解释性分析 fused torch.sum(weights.unsqueeze(-1) * proj, dim1) return self.classifier(fused)feat_dims参数要按实际分支输出维度填比如[768, 64, 512, 512]对应文本、语音、图像、视频分支。hidden_dim128是经验值再大容易过拟合尤其是客服数据集往往只有几万条。写训练代码时要把weights返回出来打印出来看模型是否真的在依赖语音分支——如果权重长期集中在某一个模态说明其它分支没学好优先检查那里的特征。4.3 训练参数的三个必调项学习率、batch size 与模态丢失融合模型的训练参数和单模态模型不太一样。文本分支用的是预训练模型学习率要设低一些常见做法是2e-5到5e-5而图像、语音分支如果也从预训练开始可以放宽到1e-4。用同一个学习率喂全模型预训练部分很快会被破坏这是训练翻车的主要原因之一。batch size 的设定要综合考虑显存和 batch 内模态均衡。每个 batch 里最好保证四种模态的样本数一致不能因为视频文件大就少放视频样本不然视频分支的梯度更新频率天然落后。模态丢失Modality Dropout是另一个值得加的技巧——训练时以一定概率随机把某个模态的特征向量置零强迫模型在缺失模态的情况下依然能判断部署时对缺音频、缺画面的场景会鲁棒很多。5. 数据集预处理避坑对齐、采样率与划分的五个翻车点5.1 时间戳错位语音标签和文本标签对不上现象训练损失降不下去验证集上语音分支的准确率明显低于其它分支。原因原始数据里语音是完整的 3 秒音频文本却是按句子切分的两者的时间轴起点不一致。特征提取时文本句子的第 2 秒对应语音的第 5 秒模型学到的是错位的配对。解决在预处理阶段统一对齐基准。常见做法是以文本句子的起止时间为锚点裁剪对应的音频片段做强制对齐。data_guide.md里如果写了时间戳字段先核对 sample 数据和标注是否匹配。python scripts/preprocess.py --align-mode text-anchor这个命令会把每条样本重新整理成“文本句子 对应时间段的音频 对应时间段的视频帧”输出到data/processed/下。5.2 音频采样率不一致librosa 不会替你纠错现象语音分支训练时 loss 正常下降但推理时某些样本的预测结果异常离谱。原因数据集中部分音频是 44.1kHz 采样率另一部分是 16kHz。librosa.load如果你没传sr参数它会按默认 22050 重采样导致一段 3 秒的音频在不同采样率下得到不同长度的频谱图。解决所有音频统一重采样到 16000Hz并且在预处理脚本里加一行断言检查防止后续新数据混入不一致采样率。# preprocess.py 中的检查片段 import soundfile as sf y, sr sf.read(audio_path) assert sr 16000, f采样率错误: {audio_path} 是 {sr}Hz期望 16000Hz5.3 视频帧率不统一导致采样覆盖不全现象视频分支的准确率在验证集上有几个样本特别低查下来发现是长度极短的视频。原因视频时长差异大短的只有 2 秒长的有 30 秒。均匀采样 8 帧时短视频的相邻帧几乎一样信息量不足长视频又可能漏掉情绪爆发的片段。解决按视频时长动态调整采样帧数短于 2 秒的补帧长于 10 秒的按比例多采几帧。这个逻辑可以放进sample_frames函数里。5.4 类别不平衡中性样本淹没了负面样本现象整体准确率看着有 80%但按类别看负面样本的召回率只有 30%。原因客服场景里大部分工单是中性或正面的负面样本占比可能不到 10%。模型学到的是“都预测成中性”就能拿高分。解决训练时给损失函数加类别权重或者用 Focal Loss。类别权重在config.yaml里配loss: type: focal gamma: 2.0 class_weights: [1.0, 0.8, 3.5] # 中性 正面 负面class_weights的顺序要和标签映射顺序严格一致这个顺序在data_guide.md里有定义改代码前先确认。5.5 数据划分泄漏同一视频的不同片段跨了数据集现象验证集指标高得离谱但部署到线上效果断崖式下跌。原因数据集里同一个长视频被切成多个片段预处理时按文件随机划分导致同一视频的片段同时出现在训练集和验证集模型等于记住了视频内容。解决按视频文件名分组划分一个视频的所有片段只能进同一个集合。用sklearn.model_selection.GroupShuffleSplit就能实现比手动写切分逻辑可靠得多。6. 从跑通到可信消融实验与错误 case 的检查习惯拿到这套源码后下一步不是直接上全量数据训练而是先做一轮消融实验验证每个模态的贡献度。操作上训练一个完整模型作为基准再分别去掉文本、语音、图像、视频分支重新训练。对比表能直接告诉你系统里哪个模态是“主心骨”哪个模态在拖后腿。如果去掉某个分支后效果反而更好说明那个分支的特征提取或对齐有问题值得回头查而不是继续在融合层打补丁。另一个值得养成的习惯是保存所有错误预测的原始样本不只看预测标签。拿客服场景来说把预测错的样本按“真实标签”分组逐一检查是哪路输入出了问题——语音噪声太大、人脸检测框偏了还是文本里有大量反讽。很多错误是数据标注不一致导致的比如同一段语音两个标注员一个标“愤怒”一个标“中性”这不是模型能学好的。做一轮标注一致性检查往往比换模型提升更多。多模态情感分析系统的价值不是跑出个 90% 的准确率而是当你手里只有文本或只有语音时它能靠其它模态补位。我之前在那个项目上最受益的做法是把融合层的注意力权重定期打印出来看看一旦发现权重长期锁死在文本分支立刻去查音视频分支的数据质量。这个习惯帮我至少避开了两次模型上线后的翻车。希望帮到你。本文还有配套的精品资源点击获取