简介基于Jupyter与Python的多模态情感分析模型实现完整附带源码、项目文档与训练好的模型专门面向毕业设计、课程设计或项目开发场景适合计算机相关专业学生和初阶开发者参考。方案覆盖从数据预处理、特征提取到多模态融合与情感分类的完整链路可帮助快速理解文本与图像信息如何联合建模。压缩包共2000个文件大小约202.13MB主体为1个Python主程序、2个Markdown说明文档及1997个txt文件。其中Markdown文档对应项目说明与实验报告txt文件多用于存放测试样本、中间结果或预测输出便于配合代码逐段核验。资源已经过严格测试直接运行即可复现结果也可在此基础上替换数据集、调整参数继续延伸。目前已有523人学习下载适合作为毕业设计基线、课程作业的完整示例或用于快速搭建多模态情感分析实验并验证思路。1. 多模态情感分析从跑通到复现这门课设/毕设到底在做什么做情感分析的人都知道单看文本已经很难覆盖真实场景里的情绪表达一个咬牙切齿的表情包加一句“你真好”模型如果只读文字会直接判成正面。这个基于 Jupyter python 实现的多模态情感分析项目走的就是「文本 视觉」两条输入线同时建模的路线最终输出情感类别完整度足够支撑毕业设计、课程设计甚至项目开发的二次扩展。它自带源码、项目文档和训练好的模型拿到手第一件事不是读论文而是把 main.py 跑起来、用 test_with_predict.txt 复现一遍预测结果。适合那些已经会用 python 基础语法、但没完整走过多模态流程的人——你缺的不是情感分析理论而是一份可以直接下手改的工程骨架。2. 项目结构与 Jupyter 环境先拆清单再配环境少走一半弯路2.1 项目文件逐个解读main.py、REPORT.md、数字 txt 各自承担什么角色拿到压缩包后先别急着打开 IDE对照文件清单把角色分清楚。README.md 是使用入口REPORT.md 是项目说明文档这两个文件优先读里面包含运行顺序和依赖版本说明。main.py 是主程序从数据读取、预处理、模型加载到预测输出都在这一个文件里串起来适合作为理解整个 pipeline 的线索。test_with_predict.txt 是测试输入文件每一行是一条待预测的样本main.py 运行后会为这些样本生成预测标签。剩下那几个四位数字命名的 txt比如 4226.txt、2902.txt、4094.txt、4987.txt、4723.txt、4486.txt、4094.txt 这类文件是项目自带的数据样本或演示语料通常按编号区分不同来源或不同情感倾向的内容。它们在本项目里的直接作用是让模型在训练/验证阶段有足够的文本素材同时你也能在熟悉代码时手动打开看看原始文本长什么样理解预处理为什么要做清洗和分词。整个项目最值得参考的不是某个高深算法而是「多模态特征怎么在代码层面汇合、模型预测怎么写回文件」这条完整链路。2.2 Python 环境和 Jupyter 配置版本、依赖安装与 token 问题这个项目的载体是 Jupyter python所以环境配置可以按「能用 Jupyter 跑通 python 脚本」为标准。我用的是 python 3.8 到 3.10 这个区间太老的版本容易出现依赖兼容问题太新的版本偶尔会遇到某些库没跟上。依赖方面常见的做法是先建一个虚拟环境再逐个安装避免和系统 python 打架conda create -n multimodal python3.9 -y conda activate multimodal pip install jupyter notebook pip install numpy pandas scikit-learn tensorflow pillow参数说明conda create 指定了虚拟环境名 multimodal 和 python 3.9防止把依赖装进全局环境numpy、pandas 负责数据处理scikit-learn 提供 TF-IDF 向量化和评估指标tensorflow 用于构建神经网络pillow 是图像读取的基础库。装完后在终端输入 jupyter notebook 就能拉起网页版编辑界面。如果你在浏览器里碰到「password or token」输入框那不是项目问题是 Jupyter 的安全机制。解决方式很简单在启动 Jupyter 的终端窗口里找到那条带 token 的 URL复制完整链接粘贴到浏览器即可。另一个高频问题是 Notebook 保存路径——默认存在用户目录下的 jupyter 文件夹里想看当前路径就在 notebook 里执行 pwd 命令。如果你习惯用 VSCode也可以直接装 Jupyter 插件接入同一个环境省掉浏览器切换。2.3 首次运行 main.py把基线跑通比看懂每一行代码更优先第一次接触这个项目我强烈建议先跑通再细读代码。在终端里切到项目根目录执行 python main.py观察输出节奏。正常情况你会看到数据读取信息、预处理日志和每一行的预测标签。如果没装依赖直接跑大概率会看到 ModuleNotFoundError这时候缺什么补什么就好。跑通基线之后再打开 main.py 按顺序读数据读取 → 特征提取 → 模型加载 → 预测输出你会发现整个流程是直线结构没有花哨的调度。提示不要把第一次运行当成测试它是一个「环境是否完整」的探针。跑通了后面所有改动才有意义。3. 数据预处理与多模态特征提取文本和视觉两条线同时并进3.1 文本线中文清洗、分词与 TF-IDF / 向量化项目里的 TXT 样本基本是中文内容所以文本处理线要做的第一件事是清洗。常见做法是用正则把数字、英文、特殊符号统一处理掉再按中文语境分词。分词这一步推荐用 jieba它是国内用得最多的轻量级分词工具效果稳定文档多遇到问题也好查。import re import jieba from sklearn.feature_extraction.text import TfidfVectorizer def clean_text(text): text re.sub(r[0-9a-zA-Z], , text) text re.sub(r[\s\-\/\[\]【】()。、~#%…*], , text) return text def cut_text(text): return .join(jieba.lcut(text)) raw_lines [line.strip() for line in open(test_with_predict.txt, r, encodingutf-8)] cleaned [cut_text(clean_text(line)) for line in raw_lines] print(cleaned[:3])逻辑说明clean_text 先把数字英文剔掉再用正则把常见标点全清理掉避免噪声进入向量化cut_text 用 jieba 分词后用空格拼接这是为了让 TfidfVectorizer 能正确识别词边界。打开 txt 看原始文本你会发现很多样本里标点混杂、表情符号残留清洗前后特征质量差别很明显。vectorizer TfidfVectorizer(max_features3000) text_feats vectorizer.fit_transform(cleaned).toarray() print(text_feats.shape)参数说明max_features3000 的意思是只保留 TF-IDF 权重最高的 3000 个特征这个值是根据样本量折中选的太小丢信息太大容易引入稀疏噪声。toarray() 把稀疏矩阵转成稠密数组方便后续和视觉特征拼接到一起。这里用的是 TF-IDF如果你的数据量够大、算力允许也可以换成预训练词向量但对这个项目规模来说 TF-IDF 已经够用而且更好解释。3.2 视觉线从路径读取到预训练特征抽取多模态里的“视觉”部分是这个项目区别于纯文本情感分析的真正价值点。常见做法是用 ImageDataGenerator 管理图片路径和标签再用预训练模型抽特征——VGG16 或 ResNet50 都行本场景下 VGG16 结构简单特征维度适中更适合课程设计级别理解和复现。from tensorflow.keras.applications import VGG16 from tensorflow.keras.preprocessing.image import img_to_array, load_img import numpy as np model VGG16(weightsimagenet, include_topFalse, input_shape(224, 224, 3)) def extract_img_feature(img_path): img load_img(img_path, target_size(224, 224)) arr img_to_array(img) / 255.0 arr np.expand_dims(arr, axis0) feat model.predict(arr, verbose0) return feat.flatten()逻辑说明VGG16 加载了 ImageNet 预训练权重include_topFalse 表示去掉最上层的分类器只保留卷积特征部分——这一步的本质是把一张图变成一串固定长度的语义特征向量不需要重新训练权重直接借力预训练模型。这个技巧在迁移学习里属于基础操作也是多模态项目最常用的视觉特征提取方式。如果你没有完整图片数据集也可以把已经算好的特征存成 npy 文件直接加载避免每次重复计算。注意图片统一 resize 到 224×224 是预训练模型的硬性要求改尺寸会直接影响特征质量。3.3 特征对齐与拼接融合前最后一个细节文本特征和视觉特征各自搞定后最难的一步是拼接。两边维度不一样直接拼会导致某一方在数量级上压过另一方模型训练时注意力全跑偏。我一般会做两个处理先分别做标准化再按同一维度拼接。from sklearn.preprocessing import StandardScaler scaler_t StandardScaler().fit_transform(text_feats) scaler_v StandardScaler().fit_transform(img_feats) fusion_feats np.hstack([scaler_t, scaler_v]) print(fusion_feats.shape)参数说明StandardScaler 把每列均值变 0、方差变 1消除量纲差异np.hstack 按列方向把文本特征和视觉特征拼成一个大矩阵行数必须一致列数 3000 VGG16 特征数。这一步做完就得到了真正的“多模态融合特征”。很多项目翻车就在这里——文本特征和视觉特征直接 raw 拼接训练出的模型在验证集上表现极不稳定。4. 模型构建与训练评估从基线 MLP 到融合层调整4.1 融合层输入设计与模型结构隐藏层、dropout 和输出层怎么设特征拼接完成之后进入分类模型构建。这个项目规模不大不需要上 Transformer 或大模型一个结构清晰的多层感知机就足够而且在答辩时更容易讲清楚每一层的作用。输入维度由 fusion_feats 的列数决定这里用 Input 层显式声明避免硬编码数字。from tensorflow.keras.models import Model from tensorflow.keras.layers import Input, Dense, Dropout from tensorflow.keras.optimizers import Adam n_input fusion_feats.shape[1] n_classes 3 # 按项目实际类别数调整 inputs Input(shape(n_input,)) x Dense(256, activationrelu)(inputs) x Dropout(0.5)(x) x Dense(64, activationrelu)(x) outputs Dense(n_classes, activationsoftmax)(x) model Model(inputsinputs, outputsoutputs) model.compile(losscategorical_crossentropy, optimizerAdam(learning_rate1e-3), metrics[accuracy]) model.summary()参数说明第一层 256 个神经元把融合特征从高维压下来relu 激活避免梯度消失Dropout 0.5 是经验值课程设计样本量通常不大不加 dropout 很容易过拟合输出层 softmax 输出三个类别的概率分布。Adam 学习率设 1e-3这个值在中小型项目上收敛稳定不必一开始就上学习率调度。n_classes 是全局变量改成项目实际标签数即可大部分多模态情感分析数据是 3 分类积极、消极、中立或 5 分类。4.2 训练循环与回调早停和验证集是防止过拟合的底线训练阶段最怕两件事收敛太慢和过拟合。慢的问题可以用 batch size 调节过拟合的问题靠早停兜底。我一般会把验证集比例固定在 0.2并加上 EarlyStopping让模型在验证指标连续若干轮不提升时自动停下来。from tensorflow.keras.callbacks import EarlyStopping from sklearn.model_selection import train_test_split X_train, X_val, y_train, y_val train_test_split( fusion_feats, labels, test_size0.2, random_state42 ) early_stop EarlyStopping( monitorval_loss, patience5, restore_best_weightsTrue ) history model.fit( X_train, y_train, validation_data(X_val, y_val), epochs50, batch_size16, callbacks[early_stop], verbose1 )逻辑说明train_test_split 在特征和标签上做同样划分random_state 固定保证每次实验可比EarlyStopping 监控验证集损失连续 5 轮不下降就停同时自动恢复训练过程中出现过的最优权重。batch_size16 适用于百级到千级的样本量调到 32 会使单轮变快但梯度震荡幅度变大。epochs 设 50 只是上限加了早停后实际迭代次数通常在 15-30 轮之间。训练完成后记得保存模型这一步经常被忽略。调试阶段可以反复跑但答辩或演示前一定要落盘model.save(multimodal_sentiment_model.h5) print(模型已保存)4.3 评估与解读准确率之外的 F1 与混淆矩阵模型训练结束后光看 accuracy 会掩盖很多问题。多模态情感分析数据集的标签分布很少完全均衡积极样本多、消极样本少是常态准确率虚高而少数类完全没学会是常见事故。所以评估阶段我会同时输出 F1 和混淆矩阵。from sklearn.metrics import classification_report, confusion_matrix y_pred model.predict(X_val) y_pred_cls np.argmax(y_pred, axis1) print(classification_report(np.argmax(y_val, axis1), y_pred_cls)) print(confusion_matrix(np.argmax(y_val, axis1), y_pred_cls))参数说明predict 返回每个类别的概率argmax 取最大概率对应的索引作为预测类别classification_report 一次性输出每类的精确率、召回率、F1confusion_matrix 可以直观看到哪两个类别互相混淆最严重。如果看到 F1 矩阵里「消极」类别只有 0.3 左右而「积极」到了 0.9那基本可以断定是标签不均衡加过拟合的双重作用这时候回过头调整类别权重比换模型更有效。5. 多模态情感分析的常见坑与排查五条能直接照抄的排错经验5.1 Jupyter 内核频繁崩溃内存被数据喂爆了现象在 Jupyter 里跑预处理时Cell 执行到一半内核直接死掉重启后前面变量全部丢失只能从头再来。原因TF-IDF 特征和预训练图片特征都是稠密矩阵如果样本量几千条、特征维度几千到几万内存占用会指数级膨胀。Jupyter 每个 Notebook 共享一个内核崩溃之前没有任何预兆保存不及时就是白算。解决把特征提取和模型训练拆成两个 Notebook 或在 main.py 里分阶段运行每阶段结束及时释放大变量。也可以在处理完特征后执行 del fusion_feats, text_feats 加 gc.collect()把不需要的中间矩阵清掉。我自己习惯把特征矩阵直接存成 npy训练时从磁盘加载而不是每次重启内核都重新算。5.2 中文文本乱码和分词错乱编码问题在预处理阶段就埋下了现象打开 txt 正常但打印预处理结果时出现一堆 符号或者 jieba 分词把整句当一个词完全不对。原因文件编码不是 UTF-8但代码里 open() 强制指定了 encodingutf-8读进来就是乱码分词错乱则通常是清洗时误把中文字符和特殊符号一起删掉了或者标点没有被正确隔开导致整串连在一起。解决先检查文件编码处理文本时用 python 的 chardet 库探测或直接在 open() 里指定 encodingutf-8, errorsignore 减少非致命错误清洗正则里把 [\u4e00-\u9fa5] 保留只删数字英文标点。血泪经验是预处理结果打印出来看一眼再往下走别等到向量化之后才发现全是空序列。5.3 准确率很高但验证不稳定标签分布不均衡是黑匣子现象训练集准确率 0.96验证集只有 0.68而且每次跑结果不一样看起来像个黑匣子。原因训练验证的随机划分导致少数类样本在验证集里时多时少模型恰好没学会少数类时验证集一波动分数就暴跌。这不是模型结构问题而是评估策略问题。解决train_test_split 固定 random_state42保证每次划分一致如果类别差距超过 2:1在 fit 里加 class_weightbalanced让少数类获得更高的损失权重这是多数分类场景下的标准做法。5.4 模型过拟合训练轮次太多Memory 被背下来了现象训练 loss 一路降到 0.01验证 loss 反而在第 10 轮后持续上升典型的高方差表现。原因样本量小、模型容量大网络把训练样本的个性特征当成了普遍规律。Dropout 设了 0.5 还不够解决所有问题更多是训练轮次没设上限直接跑满 50 轮。解决EarlyStopping 是标配但还要配合 reduce_lr_on_plateau 在 loss 持平时自动降低学习率让模型在后期可以更精细地拟合而不过冲。另一个实际有效的方案是数据增强文本上做同义词替换图像上做翻转和色彩抖动项目演示效果能得到肉眼可见的提升。5.5 test_with_predict.txt 逐行预测时形状对不上现象main.py 跑到 model.predict 时抛异常报形状不一致或者预测结果全是同一个类别。原因测试样本经过预处理后特征维度与训练时不匹配。最典型的是 TF-IDF 词表不一致——测试时重新 fit_transform 生成了新词表而不是沿用训练时的 vectorizer另一个常见问题是文本清洗函数没作用到测试行。预测输出全同类别通常是融合特征在测试时标准化参数没保存导致特征分布漂移。解决在训练阶段把 vectorizer 和 scaler 分别保存成 joblib 文件或 pkl预测阶段用 load 加载后再 transform绝对不要在预测阶段重新 fit。确保 main.py 里读取 test_with_predict.txt 后调用的是同一个预处理函数不要复制粘贴后改了一行变量名就漏掉清洗步骤。6. 进阶验证技巧用交叉验证和错例分析把答辩做扎实6.1 五折交叉验证比单次划分更能说服评委单次划分出来的验证分数多少带点运气成分课程设计和毕业设计答辩时评委最常问的一句话就是“你的模型稳定性怎么证明”。用 K 折交叉验证可以得到一组分数的均值和方差这是最容易被接受的稳定性说明。from sklearn.model_selection import StratifiedKFold import numpy as np skf StratifiedKFold(n_splits5, shuffleTrue, random_state42) fold_scores [] for fold, (train_idx, val_idx) in enumerate(skf.split(fusion_feats, labels_raw)): X_tr, X_va fusion_feats[train_idx], fusion_feats[val_idx] y_tr, y_va labels[train_idx], labels[val_idx] model build_model() # 复用第 4 章的模型构建函数 early_stop EarlyStopping(monitorval_loss, patience5, restore_best_weightsTrue) model.fit(X_tr, y_tr, validation_data(X_va, y_va), epochs50, batch_size16, callbacks[early_stop], verbose0) fold_acc model.evaluate(X_va, y_va, verbose0)[1] fold_scores.append(fold_acc) print(f五折平均准确率: {np.mean(fold_scores):.4f} ± {np.std(fold_scores):.4f})逻辑说明StratifiedKFold 按标签比例划分保证每折的类别分布和全量一致每一折都重新构建模型、重新训练避免数据泄漏。最终输出一个「均值 ± 标准差」的分数比某个单次划分的准确率可信得多。如果模型每次训练结果波动剧烈先不要怪模型回头检查特征拼接和标准化是否在每一折内部正确进行。6.2 错例画像与阈值调整最后一个能救命的具体技巧交叉验证出分数之后还有一个高阶技巧值得做把预测错误的样本拉出来生成一张「错例表格」观察模型到底在输给什么。这一步对课程设计和结题汇报非常有说服力因为你能指出具体的失败场景而不是只说“模型准确率 0.8”。wrong_idx np.where(y_pred_cls ! y_true_cls)[0] for idx in wrong_idx[:10]: print(f真实: {classes[y_true_cls[idx]]} - 预测: {classes[y_pred_cls[idx]]}) print(original_text[idx]) print(---)参数说明classes 是类别索引到中文标签的映射original_text 是预处理前的原始文本用来还原错例的真实语境。如果错例集中在“消极被识别为中立”且这些样本文本大多很短说明短文本信息量不足视觉特征的权重应该提高如果错例在某个固定主题反复出现可能属于领域词汇没被 TF-IDF 词表覆盖可以手动扩充词典。最后再分享一个阈值微调的习惯做法不做 argmax 硬分类而是看预测概率的分布把置信度低于 0.6 的样本单独挑出来归为“不确定类”。在很多应用场景里模型说“我看不准”比硬给一个错误答案更有价值。从那以后我每次做情感分析项目最后都会强制走一遍交叉验证和错例抽样这能提前暴露大多数训练阶段看不见的问题希望帮到你。本文还有配套的精品资源点击获取