资讯动态

基于Jupyter+Python的多模态情感分析期末项目实战指南

发布时间:2026/10/2 14:01:45 来源:尧图企业网站定制
简介这是一份基于Jupyter与Python实现的多模态情感分析模型项目源码源自期末大作业面向计算机、人工智能、自动化等相关专业的学生、教师或从业者可作为课程设计、毕业设计或自学进阶的参考案例。项目代码经过完整调试与测试确保可正常运行作者答辩评审分达到98分整体实现具有较强的实用性与学习借鉴价值。压缩包内共2000个文件大小202.69MB其中包含1998个txt文本文件多为数据样本、中间结果或预测输出、1个main.py主程序以及1个README.md说明文档文件构成清晰便于快速定位主代码与说明资料也方便在此基础上修改调整。目前已有187人学习下载适合需要完成类似多模态项目或作业的读者。通过该资源可获得完整的模型实现思路、数据处理方法及报告文档参考既能帮助入门者理解多模态情感分析的基本流程也能支撑进阶者在此基础上扩展功能、适配自身需求。1. 多模态情感分析期末项目这个标题到底在做什么期末大作业选题撞车率最高的是那种只输入一段文本就判断情绪的模型——因为大家手里的开源代码一抓一大把。真正能拉开差距的是基于Jupyterpython把文本、音频、视频三路信号一起喂给模型的多模态情感分析它既有源码和训练好的模型可以交付又有报告文档能解释清楚每一步设计答辩时还讲得出创新点而不是一句“我调了别人的包”带过。这个标题背后其实是一套完整工程数据准备、特征提取、模型构建、训练评估、对比实验到报告整理。按这套流程走下来你能得到一个能跑、能讲、能演示的项目而不是一个跑完就删的实验脚本。适合期末要交作业的本科生和研究生也适合想入门多模态但不想一上来啃论文的从业者。下面所有代码我都按“在 Jupyter Notebook 里能直接跑”的标准来写依赖只有 pytorch、transformers、opensmile 这几个常见库。2. 先立框架模态、融合策略与模型选型多模态情感分析不是把三个模型的结果拼在一起投票那么简单。先想清楚任务输入是一段人说话的短视频输出是说话人的情绪极性常见设定是三分类积极 / 中性 / 消极或者二分类积极 / 消极。文本告诉你“说了什么”音频告诉你“语调是上扬还是低沉”视觉告诉你“表情是笑还是皱眉”。三路信号各有短板文本表达不了反讽音频听不出语义内容视觉在侧脸、光线差时基本失效。模型要学的不是哪一路最准而是三路信号之间的互补关系。2.1 三路信号和一个标签任务边界先定清楚期末作业最常见的翻车点是任务边界没定清楚就开写代码。你到底是判断“整段视频一个情绪”还是“视频里每一句话一个情绪”如果一个人先抱怨后高兴整段打一个标签会把模型逼疯。我一般建议按“句子级”做把视频切成多个 utterance每个 utterance 对应一句文本、一段音频波形、一帧或几帧画面打一个独立标签。这样样本量还能翻几倍——自建数据录 10 段视频每段 5~8 句就能得到 50~80 个训练样本勉强够期末作业演示。对应的标注文件是一张 CSV至少四列video_id、start_time、end_time、text、label。后面所有特征工程都挂在这张表上。start_time 和 end_time 的单位是秒精确到小数点后两位即可——因为后续对齐音频和抽帧都要用这两个字段切边界。如果你用公开数据集这一步已经被作者做好你要做的只是读进来理解字段含义如果是自建数据建议先人工转录文本再逐句打标签转录花的时间通常比想象中多一倍留足余量。2.2 两种落地选型特征工程派与端到端派给定三路原始信号有两条实现路线。端到端派文本分支微调 BERT音频分支用 wav2vec2 或 HuBERT视频分支用 VideoMAE 这类预训练视频模型最后把三个 encoder 的输出拼起来训练。效果上限高但显存需求也高低显存环境基本跑不动而且期末报告很难解释清楚“这三个大模型内部到底学了什么”。特征工程派文本用预训练 BERT 取句向量后冻结音频用 opensmile 提取 88 维统计特征视觉用预训练 ResNet 的 pooling 层输出或 OpenFace 的面部动作单元强度。所有特征在训练前一次性抽好存成 npy 文件训练阶段只有两个全连接投影层和一个分类头纯 CPU 跑几分钟就收敛。期末作业我强烈推荐特征工程派理由有三可控、可解释、可复现——报告里每一步都能讲清楚输入输出维度和参数含义。选型文本分支音频分支视觉分支显存需求报告好写程度答辩风险端到端派BERT 微调wav2vec2VideoMAE高低黑匣子容易被追问细节特征工程派BERT 冻结opensmileResNet 冻结极低高每步可解释容易讲透2.3 融合时机怎么选早融合、晚融合与中间融合融合时机是这类项目的核心设计决策。早融合在特征抽取之前就把三路原始信号拼在一起实操时很难实现因为文本、波形、图像的采样率和维度完全不是一个量纲强行拼接会让模型学到虚假的相关性。晚融合是各训练各的模型最后对三个预测概率做平均或投票实现最简单但模型学不到三路信号之间的交互相当于放弃了多模态最大的价值。中间融合是期末作业的最优解每一路信号先各自编码成固定维度的向量在特征层拼接再接分类头。下面这段是模型层的骨架代码后续第 4 章会补全整个训练循环import torch import torch.nn as nn # 中间融合骨架先分别投影再拼接最后分类 class EarlyFusionDemo(nn.Module): def __init__(self, text_dim768, audio_dim88, video_dim512, hidden128, num_classes3): super().__init__() # 音频和视觉特征维度低先各自映射到与分类器匹配的维度 self.audio_proj nn.Linear(audio_dim, hidden) self.video_proj nn.Linear(video_dim, hidden) self.classifier nn.Sequential( nn.Linear(text_dim 2 * hidden, 256), nn.ReLU(), nn.Dropout(0.3), nn.Linear(256, num_classes) ) def forward(self, text_feat, audio_feat, video_feat): a torch.relu(self.audio_proj(audio_feat)) v torch.relu(self.video_proj(video_feat)) h torch.cat([text_feat, a, v], dim-1) # 拼接后过分类头 return self.classifier(h)注意这里为什么把音频和视频分别投影到 128 维而不是直接把 88 维和 512 维原始特征拼接如果直接拼接768 维文本特征会在数值上淹没 88 维音频特征分类头实际只看到了文本。先让每路信号过一层 Linear 把维度拉齐等于给了分类头一个信号告诉它三路是平等的。这两个投影层的参数就是模型要学的融合权重。比手动调权重靠谱得多。3. 在 Jupyter 里准备数据构建对齐后的多模态样本特征工程派的第一步是把视频拆成三路信号并让它们在时间上对齐。这一步做得越扎实后面训练越省心。我先说数据来源再说怎么从视频文件切出文本、音频波形和图像帧最后讲对齐的边界条件。整个过程我习惯用一个 Jupyter Notebook 按顺序跑每个 cell 只做一件事因为中间需要反复查看中间产物。3.1 数据集两条路公开基准与自建小样本公开数据集方面多模态情感分析最经典的是 CMU-MOSI 和 CMU-MOSEI中文场景有 CH-SIMS都是学术基准网上能搜到官方介绍和下载方式。这些数据集把句子级对齐、标签都做好了适合直接用来跑通全流程。但如果你是在国内网络环境下载这些数据集经常因为网盘或外部链接的问题卡住那就走第二条路自建。自建很直接找 10~15 段带人脸的短视频片段每段控制在 10 秒左右说话清晰、表情可见、背景噪音小。用 OpenCV 或 moviepy 把视频切成按句对齐的片段人工转录文本并打标签。自建样本量小报告里一定要诚实写“受限于标注成本实验使用小样本验证可行性”然后用留出法评估不要谎称大数据量。另外一个小建议在项目目录下启动 Jupyter 时先执行cd 项目路径再运行jupyter notebook这样新建的 ipynb 默认就落在当前目录避免出现“jupyter 怎么创建在别的文件夹”这种卡住半小时的问题。3.2 预处理流水线从一段视频到三路原始信号拿到视频文件后第一件事是分离音轨和画面。我用 moviepy 完成因为它读写 mp4 和 wav 都很稳定不会在期末前夜突然抛底层解码错误。下面是拆分函数import os from moviepy.editor import VideoFileClip VIDEO_DIR data/videos # 原始视频目录 RAW_DIR data/raw # 分离产物目录 os.makedirs(RAW_DIR, exist_okTrue) def split_modalities(name): video_path os.path.join(VIDEO_DIR, f{name}.mp4) clip VideoFileClip(video_path) # 分离音轨输出单声道 wav后面 opensmile 直接读 clip.audio.write_audiofile( os.path.join(RAW_DIR, f{name}.wav), fps16000, # 16k 采样率语音特征提取的默认标准 verboseFalse, loggerNone ) # 输出无声视频专门给视觉分支抽帧用 clip.write_videofile( os.path.join(RAW_DIR, f{name}_silent.mp4), audioFalse, verboseFalse, loggerNone ) clip.close() print(f[OK] {name}: 音频与无声视频已分离) split_modalities(sample_01)16k 采样率是 opensmile 和大多数语音特征提取工具默认支持的采样率如果你保留原始 44.1k 采样率波形数据更大但特征几乎不变纯属浪费磁盘和内存。write_videofile 里audioFalse是为了避免二次压缩音轨因为我们要的不是带声音的视频而是纯画面。verboseFalse 和 loggerNone 是 moviepy 2.x 版本里抑制日志的方式不写的话每个文件会刷一屏进度条在 Notebook 里很难看。3.3 按句切对齐不要对着整段视频抽特征大多数新手最容易在这里翻车直接对整段 10 秒音频抽一组统计特征对整段视频的所有帧求平均再配上一句文本。这种“一个样本 整段视频”的做法相当于让模型忽略情绪在时间轴上的变化。正确做法是以 CSV 里的 start_time 和 end_time 为基准把音频裁出对应时间窗把视频帧抽出对应时间窗内的画面然后这三块才组成一个样本。import numpy as np import librosa def load_segment_audio(wav_path, start, end, sr16000): # 用 librosa 读取整段波形再按时间索引切片 y, _ librosa.load(wav_path, srsr) s int(start * sr) e int(end * sr) return y[s:e] # 返回的是 numpy 数组可直接喂给特征提取 import cv2 def extract_frames(video_path, start, end, fps2): # 每隔 0.5 秒抽一帧控制每句话的视觉样本量 cap cv2.VideoCapture(video_path) frames [] t start while t end: cap.set(cv2.CAP_PROP_POS_MSEC, t * 1000) ok, frame cap.read() if ok: frames.append(cv2.resize(frame, (224, 224))) t 1.0 / fps cap.release() return framesfps2 是我试过最稳妥的默认值。一段 3 秒的话抽 6 帧画面既能捕捉到表情变化的关键姿态又不会让视觉分支的计算量爆炸。如果调成 10fps一句话就要抽 30 帧每帧都要过一遍 ResNetCPU 环境下训练前期的特征提取会慢到让你怀疑人生。抽出来的帧尺寸统一 resize 到 224×224这是 ResNet 预训练模型的标准输入尺寸不 resize 会在跑模型时报尺寸不匹配的错。4. 动手训练用 BERT 分类头搭建融合模型数据准备好后项目最核心的部分就是三个分支的特征抽取和融合模型的训练。这一章的代码可以直接复制进 Jupyter Notebook按 cell 顺序执行。我会把关键参数和它们为什么取这个值讲清楚。如果你的机器没有 GPU完全不用慌——这一整套设计就是奔着“低显存也能跑”去的。4.1 文本分支冻结 BERT 取句向量BERT 本质是 Transformer 的 Encoder 堆叠期末作业不需要深挖内部的多头注意力机制只需要知道一件事把一句话送进去拿回一个固定维度的向量这个向量浓缩了整句话的语义。我们用bert-base-chinese做中文文本编码取pooler_output作为句子向量维度是 768。from transformers import BertTokenizer, BertModel tokenizer BertTokenizer.from_pretrained(bert-base-chinese) bert BertModel.from_pretrained(bert-base-chinese) bert.eval() # 冻结模式只做前向不更新梯度 def encode_text(texts, max_len64): # padding 到同一长度truncation 截断超长句子 inputs tokenizer( texts, paddingTrue, truncationTrue, max_lengthmax_len, return_tensorspt ) with torch.no_grad(): outputs bert(**inputs) return outputs.pooler_output # 形状 [batch, 768]max_len64 对短视频里的一句话完全够用。中文一句话平均十几个字即使加上所有 special token 也不会超过 64。如果你设置 max_len512BERT 的 attention 计算量会以平方级别上升CPU 下编码 80 句话可能要多等十分钟而语义效果几乎没差别。这里最关键的是model.eval()和torch.no_grad()我们只把 BERT 当特征提取器用不做微调这样既省显存又能避免小数据集上微调导致过拟合。4.2 音频分支opensmile 提取统计特征音频分支我默认用 opensmile 的 eGeMAPPs 特征集。eGeMAPPs 是语音情感识别社区广泛使用的标准特征集包含音高、响度、频谱斜率等 88 维统计量文件小、提取快、可解释性强。安装命令是pip install opensmile这一步通常不会有坑。import opensmile smile opensmile.Smile( feature_setopensmile.FeatureSet.eGeMAPPs, feature_levelopensmile.FeatureLevel.Functionals, ) def encode_audio(audio_array, sr16000): # 从内存数组直接提取特征返回 88 维向量 feat smile.process_signal(audio_array, sampling_ratesr) return feat.values.flatten().astype(np.float32)注意FeatureLevel.Functionals是什么意思它对整段音频按帧先算底层描述符再对这些帧做统计聚合均值、方差、峰值等最终每条音频只出一个 88 维向量。如果选LowLevelDescriptors每一帧都会输出一组特征维度会变成 (帧数, 88)后面还要自己做时序聚合多一步麻烦。期末报告里建议写清楚“eGeMAPPs 包含 88 维与情绪强相关的语音韵律特征选取 Functionals 级别以匹配句子级标签。”4.3 视觉分支预训练 ResNet 抽帧后做均值池化视觉分支的思路和文本完全一致加载一个预训练好的 ResNet18去掉最后的全连接分类层把每帧图片变成 512 维特征再把一句话内的所有帧特征求平均。平均池化这一步是为了把“多个帧的特征”压缩成“一个句子的视觉特征”这样三个分支的输出维度就对齐了。import torchvision.models as models resnet models.resnet18(pretrainedTrue) # 去掉最后的全连接层只保留卷积特征部分 resnet torch.nn.Sequential(*list(resnet.children())[:-1]) resnet.eval() def encode_frames(frames): # frames: list of (224, 224, 3) 的 BGR 数组 tensors [torch.from_numpy(f).permute(2, 0, 1).float() / 255.0 for f in frames] batch torch.stack(tensors) # [帧数, 3, 224, 224] with torch.no_grad(): feats resnet(batch) # [帧数, 512, 1, 1] feats feats.squeeze(-1).squeeze(-1) return feats.mean(dim0).numpy() # 平均池化得到 [512]pretrainedTrue会从 torchvision 缓存目录加载在 ImageNet 上训练好的权重第一次运行需要联网下载约 45MB。如果你不想等下载也可以用weightsResNet18_Weights.DEFAULT这种新写法效果一样。帧特征平均池化会丢失时序信息比如“先皱眉后笑”但对期末作业来说这种信息损失可以接受换来的是极低的计算量和稳定的特征维度。如果你后面想进阶可以换成 LSTM 或 Transformer 编码帧序列那就是另一个量级的工程了。4.4 训练循环与早停小数据别硬练三个分支的特征都抽好后把所有特征拼成三个 numpy 矩阵存到本地训练阶段只加载这三个矩阵。这样做的收益非常大训练循环里完全没有 BERT、ResNet、opensmile 的推理过程每个 epoch 只是矩阵乘法和全连接层的运算CPU 上跑 100 个 epoch 也就几分钟。class MultimodalSentimentModel(nn.Module): def __init__(self, text_dim768, audio_dim88, video_dim512, hidden128, num_classes3): super().__init__() self.audio_proj nn.Linear(audio_dim, hidden) self.video_proj nn.Linear(video_dim, hidden) self.classifier nn.Sequential( nn.Linear(text_dim 2 * hidden, 256), nn.ReLU(), nn.Dropout(0.3), nn.Linear(256, num_classes), ) def forward(self, text, audio, video): a torch.relu(self.audio_proj(audio)) v torch.relu(self.video_proj(video)) h torch.cat([text, a, v], dim-1) return self.classifier(h) # 训练循环AdamW 交叉熵 早停 model MultimodalSentimentModel() optimizer torch.optim.AdamW(model.parameters(), lr2e-3) loss_fn nn.CrossEntropyLoss() best_acc 0.0 patience 0 for epoch in range(80): model.train() # train_loader 每个 batch 返回 (text, audio, video, label) for text, audio, video, label in train_loader: out model(text, audio, video) loss loss_fn(out, label) optimizer.zero_grad() loss.backward() optimizer.step() # 验证集早停连续 5 轮不提升就停 val_acc evaluate(model, valid_loader) if val_acc best_acc: best_acc val_acc patience 0 torch.save(model.state_dict(), msa_model.pt) else: patience 1 if patience 5: print(f[早停] epoch{epoch}, best_acc{best_acc:.4f}) break这里 lr2e-3 比微调 BERT 常用的 2e-5 要大很多因为整个模型只有三层全连接参数量不到 20 万小网络用大一点的学习率收敛更快。dropout0.3 是防止全连接层在小样本上过拟合如果你自建数据只有 60 条可以把 dropout 提到 0.5。早停的 patience 设 5在这个量级的数据上够用了设太大反而会在过拟合区多跑很多轮。训练结束后msa_model.pt就是标题里说的“模型”文件答辩现场可以直接加载它做推理演示。4.5 在 Jupyter 里可视化训练曲线训练过程每轮记录 loss 和验证集准确率最后画两条曲线这是报告里必备的图。用 matplotlib 画就行不要用太花哨的可视化库期末报告讲究的是信息清晰。import matplotlib.pyplot as plt def plot_training_curve(loss_history, acc_history): fig, ax1 plt.subplots(figsize(8, 4)) ax2 ax1.twinx() ax1.plot(loss_history, labeltrain_loss, color#d62728) ax2.plot(acc_history, labelval_acc, color#1f77b4) ax1.set_xlabel(epoch) ax1.set_ylabel(loss) ax2.set_ylabel(accuracy) fig.legend(locupper right) plt.show()这张图放进报告后旁边一定要配一段文字说明“训练损失在前 20 轮快速下降验证集准确率在 30 轮后趋于平稳早停机制在第 40 轮触发说明模型在小样本条件下收敛稳定”。这种解读比图本身更重要答辩老师大概率会追问“为什么选这个早停轮数”。5. 期末项目避坑与排查5 个让我翻过车的问题这 5 个坑是我自己跑多模态情感分析时真实踩过的每一个都浪费过至少半天时间。按“现象 → 原因 → 解决”写清楚你现在看到了就不用再付这个时间成本。5.1 音频与视频不同步画面在笑、语音在哭现象模型训练时 loss 死活降不下去收敛后准确率也只有 40% 出头。排查时随机抽了几个样本发现文本是“今天真高兴”音频特征却是低沉的画面里也是面无表情甚至皱眉。原因时间戳对齐不正确。要么是 CSV 里的 start_time 和 end_time 标错了要么是抽帧时用了视频容器的时间轴而音频切片用了另一套时间基准。自建数据时人工转录最容易在这里出错——手动记的秒数和实际视频时间偏差 0.3 秒情绪边界就全错了。解决在预处理阶段加一个“对齐检查”代码块随机抽 5 个样本把音频波形、文本字幕、抽帧时间点三件事打印出来人工核对。写一个简单函数把 start_time 和 end_time 对应的音频波形用 matplotlib 画出来同时在图里标出文本内容一眼就能看出边界是否合理。5.2 BERT 下载失败导致整个 Notebook 卡死现象执行BertModel.from_pretrained(bert-base-chinese)时长时间无响应最后报连接超时错误后续 cell 也全废了。原因Hugging Face 模型文件在国内访问不稳定直接下载经常超时而 transformers 库默认会连官方站点。这是网络环境问题不是代码问题。解决设置环境变量指向镜像站点然后重新执行export HF_ENDPOINThttps://hf-mirror.com或者在代码开头加import os os.environ[HF_ENDPOINT] https://hf-mirror.com设置后再执行from_pretrained就能正常下载了。如果镜像也不可用还有一个保底方案找一台网络正常的机器提前把bert-base-chinese下载到本地文件夹然后把from_pretrained(本地目录/bert-base-chinese)直接加载。别问为什么我连这个都知道都是眼泪。5.3 opensmile 特征维度对不上明明是 88 维怎么变成了 25 维现象.values.flatten()得到的数组长度不是 88有时是 25有时是 2268代码在拼接特征时报维度不匹配错误。原因opensmile 不同版本对FeatureSet的命名和默认配置有变化。老版本里eGeMAPPs是 88 维新版本里如果你写成小写或拼错成egemaps它会静默选择另一套特征集。另外feature_level选错也会导致维度剧变。解决不要靠记忆跑一行代码看实际输出feat smile.process_signal(audio_array, sampling_rate16000) print(feat.columns.tolist()) # 直接打印特征名列表 print(feat.shape) # 确认最终维度把这个形状断言写进代码assert feat.shape[-1] 88, feGeMAPPs 维度异常: {feat.shape}只要特征维度不对直接中断提示而不是带病跑到训练阶段才报错。5.4 CPU 训练慢到怀疑人生一个 epoch 跑了二十分钟现象训练循环里每个 epoch 都特别慢甚至执行完第一个 epoch 就想关机。CPU 风扇狂转但进度条纹丝不动。原因最典型的是把特征提取写进了训练循环。比如每个 batch 都在加载 npy 之前又重新调了一次 opensmile或者漏写了torch.no_grad()导致 BERT 和 ResNet 在训练时参与反向传播计算。前者是重复计算后者是让几百倍于全连接层的计算量白跑。解决铁律只有一条——特征只提取一次存成 npy 后训练代码只读 npy。训练前用del bert, resnet释放大模型内存再调用gc.collect()。另外确认训练循环里没有任何隐含的模型前向调用打印一次 epoch 耗时如果超过 10 秒就要检查是不是漏掉了什么重复计算。5.5 数据集不均衡模型学会无脑输出多数类现象准确率看着有 70%但看混淆矩阵发现所有样本都被预测成了“积极”这一类“消极”的精确率和召回率全是 0。这不是模型学会了是模型在偷懒。原因自建数据时如果采集场景单一比如全挑的朋友圈正能量视频标签分布会严重倾斜。另一方面交叉熵损失对多数类和少数类是等权看待的模型只要全输出多数类就能把 loss 压到很低。解决在损失函数里给少数类加权。先统计每类样本数再计算权重import torch.nn.functional as F class_counts [32, 15, 8] # 按实际标签统计 weights torch.tensor([ len(train_labels) / c for c in class_counts ], dtypetorch.float32) loss_fn nn.CrossEntropyLoss(weightweights)权重计算原则是“样本越少的类权重越大”让模型犯少数类错误的代价变高。加了权重后如果发现少数类也不被识别再检查一下是不是样本总共不到 10 条——数据量太小的时候任何技巧都有限老老实实在报告里写“未来工作扩充数据规模”就行。6. 从能跑到能答辩验证方法与报告组织技巧模型能跑不代表答辩能过。期末项目被问倒的高频点集中在“你怎么证明多模态比单模态好”和“你凭什么说这个模型有效”这两类问题上。最后一章把这几件事讲清楚你的项目就能从“能跑”升级成“能讲”。6.1 别只用准确率多类别指标与混淆矩阵三分类任务只用准确率非常吃亏因为随机猜测也有 33%。报告里至少放一张包含 Precision、Recall、F1 的表格每个类别一行再加一个整体准确率。计算方式直接用 sklearn 一行搞定from sklearn.metrics import classification_report, confusion_matrix print(classification_report(y_true, y_pred, target_names[消极, 中性, 积极])) print(confusion_matrix(y_true, y_pred))报告里写这个表格时要刻意做一件事挑一个模型预测错的样本具体分析。比如“样本 #12 文本为‘没事’音频平缓但画面中嘴角有轻微上扬模型判为积极真实标签是中性”这段失败案例分析比任何指标都能说明你对任务的理解深度。6.2 报告按“问题→数据→特征→模型→实验”组织报告文档的组织顺序很重要我习惯用的结构是研究问题与背景为什么要做多模态而不是单模态、数据集与标注说明数据来源、样本量、标签分布、特征工程三个分支分别怎么提取、为什么选这些特征、模型结构融合时机、维度变化然后是实验对比——这是整份报告的题眼。对比实验至少要有一组单模态只用文本 / 只用音频 / 只用视频各自跑一次准确率再和完整多模态模型比较。这张表会给导师一个最强的信号多模态融合不是摆设是确实提升了表现。用我前面代码里的MultimodalSentimentModel把 audio 和 video 分支置零就能跑单文本版本实现成本极低。早融合和晚融合的对比也可以加在进阶实验里如果篇幅不够至少把三模态 vs 单模态的对比做了。6.3 答辩演示的两个小技巧第一固定随机种子。代码开头加import torch torch.manual_seed(42) np.random.seed(42)不固定种子的话答辩现场抽查推理时模型每次运行结果可能波动万一抽到一个分类错误的样本场面会非常尴尬。第二准备一个“演示 cell”加载训练好的模型权重对一条新输入直接出预测结果并且打印三路特征的实际值让评委看到模型确实在综合三路信息做判断而不是一个空壳model MultimodalSentimentModel() model.load_state_dict(torch.load(msa_model.pt, map_locationcpu)) model.eval() text_feat encode_text([今天项目终于跑通了]) audio_feat encode_audio(load_audio(demo.wav)) video_feat encode_frames(extract_frames(demo.mp4)) prob torch.softmax(model(text_feat, audio_feat, video_feat), dim-1) print(积极: {:.2f} 中性: {:.2f} 消极: {:.2f}.format(*prob.tolist()))我自己的习惯是答辩前一周把所有代码重跑一遍从预处理到最后推理不做任何改动答辩当天再跑一遍确保每个 cell 的缓存都被清过。这个方法帮我避过两次现场翻车——一次是模型权重文件被同步工具改坏一次是特征缓存过期导致维度对不上。希望这些流程和踩坑记录能帮到你省下的时间拿去做实验对比和把报告写厚才是期末大作业真正的加分项。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑