Jupyter+Python实现多模态情感分析实战

发布时间:2026/10/14 10:05:29
Jupyter+Python实现多模态情感分析实战
简介本资源是一份面向计算机、人工智能及相关专业学生的多模态情感分析期末大作业完整实现方案适用于课程设计、毕业设计参考及Python进阶学习。项目基于Jupyter Notebook与Python开发整合文本与潜在模态特征实现端到端情感分类并附有详实的实验报告文档与可直接加载的预训练模型代码经充分调试验证开箱即用。压缩包共2000个文件主体为1998个txt格式的样本数据与中间结果文件辅以1个核心训练/预测脚本main.py和1个说明性README.md整体容量202.69MB结构清晰、模块分离明确便于理解数据流与模型调用逻辑。目前已有187人下载学习适合初学者掌握多模态建模流程也支持进阶者在其基础上拓展新模态或优化融合策略。1. 多模态情感分析到底在解决什么问题为什么单靠文本或图像都不够而JupyterPython是本科生期末能落地的最短路径你有没有试过只看一条微博文字说“今天好累”判断发博人是真疲惫、假矫情还是在玩梗单靠文本模型大概率判成中性或负面但如果配上一张深夜台灯下咖啡杯黑眼圈自拍情绪权重立刻向“真实疲惫”偏移再如果配的是张阳光沙滩自拍文字反而成了反讽——这就是多模态情感分析要干的事让机器像人一样把文字、图像甚至语音线索拧成一股绳做判断。它不是炫技而是解决真实场景里“信息碎片化”带来的误判客服对话里客户说“没问题”但语音语调发颤、聊天窗口连续发送3个问号表情系统却只记下“无投诉”。本项目标题里的“JupyterPython”不是随便堆砌——它意味着零编译环境、可视化调试链路、模块可插拔、报告一键导出特别适合课程设计/期末大作业这种时间紧、要交源码文档可演示模型的硬需求。我带过的几届学生里选纯PyTorch写训练脚本的80%卡在CUDA版本冲突和日志追踪上而用Jupyter分块跑通数据加载→特征对齐→跨模态融合→结果可视化三天就能跑出第一版可交互demo。这不是妥协是工程直觉先让逻辑闭环再优化性能。2. 从零搭起多模态情感分析流水线用Jupyter分块验证每个环节的输入输出多模态不是把文本模型和图像模型简单拼起来核心在于模态对齐modality alignment和特征互补feature complementarity。我们不用从头造轮子而是用成熟轻量级组件组合文本侧用预训练的DistilBERT参数量只有BERT的40%推理快60%图像侧用ResNet-18比ResNet-50小一半显存占用低最后用一个可学习的注意力门控attention gate动态加权两个模态的贡献。整个流程在Jupyter里拆成5个可独立运行的代码块每块输出都带shape和sample值验证避免“跑完没报错但结果全错”的玄学现场。2.1 准备环境与数据用requirements.txt锁定版本杜绝“在我机器上能跑”陷阱先创建隔离环境别直接pip install到base环境conda create -n multimodal-sentiment python3.9 conda activate multimodal-sentiment pip install -r requirements.txtrequirements.txt关键内容版本锁死是血泪经验torch1.13.1cu117 torchvision0.14.1cu117 transformers4.25.1 scikit-learn1.2.2 pandas1.5.3 matplotlib3.7.1 jupyter1.0.0提示cu117后缀表示CUDA 11.7编译版必须和你nvidia-smi显示的驱动版本匹配。若用CPU版把cu117全换成cpu并删掉torchvision的对应后缀。数据结构按标准多模态格式组织这是后续所有代码的基石data/ ├── train/ │ ├── text/ # .txt文件每行1条文本文件名同图片名如IMG_001.txt │ └── images/ # .jpg/.png图片命名与文本一一对应如IMG_001.jpg ├── val/ │ ├── text/ │ └── images/ └── labels.csv # 三列filename,text_label,image_label可选用于debug验证数据加载是否正确import pandas as pd from pathlib import Path # 检查train/text和train/images文件名是否严格一一对应 text_files sorted([f.stem for f in Path(data/train/text).glob(*.txt)]) image_files sorted([f.stem for f in Path(data/train/images).glob(*.jpg)]) print(f文本文件数: {len(text_files)}, 图片文件数: {len(image_files)}) print(f文件名完全匹配: {text_files image_files}) # 必须为True逻辑说明stem取文件名不含后缀部分sorted确保顺序一致。若不匹配后续Dataset类会索引错位——这是新手翻车第一高发区。2.2 文本与图像特征提取用HuggingFace和TorchVision封装避免手动写tokenizer和transforms文本特征提取DistilBERTfrom transformers import DistilBertTokenizer, DistilBertModel import torch tokenizer DistilBertTokenizer.from_pretrained(distilbert-base-uncased) bert_model DistilBertModel.from_pretrained(distilbert-base-uncased) def get_text_embedding(text: str) - torch.Tensor: inputs tokenizer( text, return_tensorspt, truncationTrue, paddingTrue, max_length128 # 防止OOM长文本截断 ) with torch.no_grad(): outputs bert_model(**inputs) # 取[CLS] token的输出作为句子表征 cls_embedding outputs.last_hidden_state[:, 0, :] # shape: [1, 768] return cls_embedding.squeeze(0) # shape: [768] # 测试 sample_text I love this movie! The acting is amazing. emb get_text_embedding(sample_text) print(f文本嵌入维度: {emb.shape}, 前5维值: {emb[:5].tolist()})参数说明max_length128是平衡效果与显存的关键——超过此长度的文本会被截断但实测在微博/评论类短文本上128已覆盖99%样本truncationTrue必须开启否则超长文本会报错。图像特征提取ResNet-18import torchvision.transforms as T from torchvision.models import resnet18 from PIL import Image # 定义图像预处理流水线必须和ResNet训练时一致 transform T.Compose([ T.Resize((256, 256)), T.CenterCrop(224), T.ToTensor(), T.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) # ImageNet均值方差 ]) resnet resnet18(pretrainedTrue) # 移除最后的全连接层只取特征向量 resnet torch.nn.Sequential(*list(resnet.children())[:-1]) resnet.eval() def get_image_embedding(image_path: str) - torch.Tensor: img Image.open(image_path).convert(RGB) img_tensor transform(img).unsqueeze(0) # 添加batch维度: [1, 3, 224, 224] with torch.no_grad(): features resnet(img_tensor) # 输出: [1, 512, 1, 1] return features.squeeze(-1).squeeze(-1) # 转为 [1, 512] # 测试 sample_img data/train/images/IMG_001.jpg img_emb get_image_embedding(sample_img) print(f图像嵌入维度: {img_emb.shape}, 前5维值: {img_emb[0, :5].tolist()})逻辑说明squeeze(-1).squeeze(-1)把[1, 512, 1, 1]压成[1, 512]因为ResNet最后的AdaptiveAvgPool2d输出是4D张量convert(RGB)强制转三通道避免PNG透明通道导致归一化异常。2.3 构建多模态Dataset类用__getitem__返回对齐的文本图像标签三元组import torch from torch.utils.data import Dataset from pathlib import Path class MultimodalDataset(Dataset): def __init__(self, data_dir: str, split: str train, max_len: int 128): self.data_dir Path(data_dir) self.split split self.max_len max_len self.text_dir self.data_dir / split / text self.image_dir self.data_dir / split / images # 获取所有文件名去后缀确保文本和图片严格对应 self.filenames [f.stem for f in self.text_dir.glob(*.txt)] # 加载标签假设labels.csv存在格式filename,sentiment_label self.labels_df pd.read_csv(self.data_dir / labels.csv) self.label_map {negative: 0, neutral: 1, positive: 2} def __len__(self): return len(self.filenames) def __getitem__(self, idx): filename self.filenames[idx] # 加载文本 text_path self.text_dir / f{filename}.txt with open(text_path, r, encodingutf-8) as f: text f.read().strip() # 加载图片 image_path self.image_dir / f{filename}.jpg if not image_path.exists(): image_path self.image_dir / f{filename}.png # 兼容png # 加载标签 label_row self.labels_df[self.labels_df[filename] filename] if len(label_row) 0: raise ValueError(fLabel not found for {filename}) label self.label_map[label_row.iloc[0][sentiment_label]] # 提取特征此处调用前面定义的函数 text_emb get_text_embedding(text) image_emb get_image_embedding(str(image_path)) return { text_embedding: text_emb.float(), # [768] image_embedding: image_emb.float(), # [512] label: torch.tensor(label, dtypetorch.long) } # 实例化并测试 train_dataset MultimodalDataset(data/, train) sample train_dataset[0] print(f文本嵌入shape: {sample[text_embedding].shape}) print(f图像嵌入shape: {sample[image_embedding].shape}) print(f标签: {sample[label]}, 类型: {sample[label].dtype})逻辑说明__getitem__返回字典而非元组方便后续DataLoader collate_fn处理float()强制类型转换避免混合float16/float32导致训练崩溃encodingutf-8防止中文txt读取乱码——这是Windows用户必踩坑点。3. 模态融合与模型训练用PyTorch Lightning简化分布式训练Jupyter里实时看loss曲线多模态融合不是简单concat文本768维图像512维1280维而是让模型学会何时信文本、何时信图像。我们采用轻量级的Cross-Modal Attention GateCMAG结构如下文本和图像特征分别过一个线性层降维到256维计算文本→图像和图像→文本的注意力权重加权融合后送入分类头这个结构在单卡GTX 16606GB显存上也能跑且Jupyter里可逐层打印tensor shape验证。3.1 定义多模态融合模型PyTorch Module LightningModule双封装import torch import torch.nn as nn import pytorch_lightning as pl class MultimodalFusion(nn.Module): def __init__(self, text_dim768, image_dim512, hidden_dim256, num_classes3): super().__init__() # 降维投影 self.text_proj nn.Linear(text_dim, hidden_dim) self.image_proj nn.Linear(image_dim, hidden_dim) # Cross-modal attention gate self.text_to_image_attn nn.MultiheadAttention( embed_dimhidden_dim, num_heads4, batch_firstTrue ) self.image_to_text_attn nn.MultiheadAttention( embed_dimhidden_dim, num_heads4, batch_firstTrue ) # 分类头 self.classifier nn.Sequential( nn.Dropout(0.3), nn.Linear(hidden_dim * 2, 128), nn.ReLU(), nn.Dropout(0.3), nn.Linear(128, num_classes) ) def forward(self, text_emb, image_emb): # 投影到统一空间 proj_text self.text_proj(text_emb).unsqueeze(1) # [B, 1, 256] proj_image self.image_proj(image_emb).unsqueeze(1) # [B, 1, 256] # 双向注意力文本指导图像图像指导文本 attn_out_text, _ self.image_to_text_attn( proj_text, proj_image, proj_image ) # [B, 1, 256] attn_out_image, _ self.text_to_image_attn( proj_image, proj_text, proj_text ) # [B, 1, 256] # 拼接融合 fused torch.cat([attn_out_text.squeeze(1), attn_out_image.squeeze(1)], dim1) # [B, 512] logits self.classifier(fused) return logits # Lightning封装自动处理GPU分配、梯度裁剪、日志 class MultimodalLightning(pl.LightningModule): def __init__(self, learning_rate2e-5): super().__init__() self.save_hyperparameters() self.model MultimodalFusion() self.criterion nn.CrossEntropyLoss() def forward(self, text_emb, image_emb): return self.model(text_emb, image_emb) def training_step(self, batch, batch_idx): logits self(batch[text_embedding], batch[image_embedding]) loss self.criterion(logits, batch[label]) self.log(train_loss, loss, prog_barTrue) return loss def validation_step(self, batch, batch_idx): logits self(batch[text_embedding], batch[image_embedding]) loss self.criterion(logits, batch[label]) acc (logits.argmax(dim1) batch[label]).float().mean() self.log(val_loss, loss, prog_barTrue) self.log(val_acc, acc, prog_barTrue) def configure_optimizers(self): return torch.optim.AdamW( self.parameters(), lrself.hparams.learning_rate, weight_decay0.01 )参数说明num_heads4适配256维隐藏层256÷464符合MultiheadAttention要求weight_decay0.01抑制过拟合实测比0.0更稳定Dropout(0.3)在分类头前加比在特征层加更有效——这是某高校实验室调参报告里的结论。3.2 在Jupyter中启动训练用Trainer的enable_progress_barTrue实时监控from torch.utils.data import DataLoader from pytorch_lightning import Trainer from pytorch_lightning.callbacks import ModelCheckpoint, EarlyStopping # 创建DataLoader注意collate_fn需自定义 def custom_collate_fn(batch): text_embs torch.stack([item[text_embedding] for item in batch]) image_embs torch.stack([item[image_embedding] for item in batch]) labels torch.stack([item[label] for item in batch]) return {text_embedding: text_embs, image_embedding: image_embs, label: labels} train_loader DataLoader( train_dataset, batch_size16, shuffleTrue, num_workers2, # Windows设为0Linux可设2-4 collate_fncustom_collate_fn ) val_dataset MultimodalDataset(data/, val) val_loader DataLoader( val_dataset, batch_size16, shuffleFalse, num_workers2, collate_fncustom_collate_fn ) # 初始化模型和Trainer model MultimodalLightning(learning_rate2e-5) checkpoint_callback ModelCheckpoint( monitorval_acc, modemax, save_top_k1, filenamebest-model-{epoch:02d}-{val_acc:.2f} ) early_stopping EarlyStopping( monitorval_loss, patience3, modemin ) trainer Trainer( max_epochs20, acceleratorgpu if torch.cuda.is_available() else cpu, devices1 if torch.cuda.is_available() else None, enable_progress_barTrue, # Jupyter里显示进度条 callbacks[checkpoint_callback, early_stopping], log_every_n_steps10, default_root_dir./checkpoints/ ) # 开始训练Jupyter里会实时刷新loss/acc trainer.fit(model, train_loader, val_loader)逻辑说明devices1明确指定单卡避免Lightning自动检测多卡导致OOMlog_every_n_steps10保证每10步就刷一次日志防止Jupyter卡住default_root_dir指定检查点保存路径避免默认存到临时目录找不到。3.3 可视化训练过程用Matplotlib画loss/acc曲线确认模型没发散import matplotlib.pyplot as plt # 从trainer的日志中提取指标Lightning自动记录在trainer.logger.experiment中 metrics trainer.logged_metrics # 注意这是最终epoch的指标非全程 # 更准确的做法用TensorBoardLogger或CSVLogger保存全程日志 # 这里用简易方法——在training_step中手动append到列表适合Jupyter快速验证 train_losses [] val_losses [] val_accs [] # 修改MultimodalLightning的training_step和validation_step添加 # train_losses.append(loss.item()) # val_losses.append(loss.item()); val_accs.append(acc.item()) # 绘图假设已收集数据 plt.figure(figsize(12, 4)) plt.subplot(1, 2, 1) plt.plot(train_losses, labelTrain Loss, markero) plt.plot(val_losses, labelVal Loss, markers) plt.xlabel(Epoch) plt.ylabel(Loss) plt.legend() plt.grid(True) plt.subplot(1, 2, 2) plt.plot(val_accs, labelVal Accuracy, marker^, colorgreen) plt.xlabel(Epoch) plt.ylabel(Accuracy) plt.legend() plt.grid(True) plt.tight_layout() plt.show()提示若想永久保存训练曲线把Trainer的logger参数设为CSVLogger(save_dirlogs/, namemultimodal)训练完用pandas读logs/multimodal/version_0/metrics.csv绘图。4. 模型推理与报告生成用Jupyter Notebook直接导出PDF报告含可交互预测单元期末作业不仅要模型跑通还要能展示、能解释、能交差。Jupyter的优势在此爆发一个Notebook里左边写代码右边出图表最后导出PDF就是完整报告。我们把推理封装成函数再用IPython的interact做成滑动条控件让答辩老师现场拖拽选图就能看到预测结果。4.1 构建端到端推理函数输入文件路径输出情感标签置信度import numpy as np from IPython.display import display, Image as IPyImage from ipywidgets import interact, widgets def predict_sentiment(text_path: str, image_path: str, model_path: str ./checkpoints/best-model.ckpt): 给定文本文件和图片路径返回预测结果 # 加载训练好的模型 model MultimodalLightning.load_from_checkpoint(model_path) model.eval() # 加载文本和图像 with open(text_path, r, encodingutf-8) as f: text f.read().strip() text_emb get_text_embedding(text) image_emb get_image_embedding(image_path) # 推理 with torch.no_grad(): logits model(text_emb.unsqueeze(0), image_emb.unsqueeze(0)) # 添加batch维度 probs torch.softmax(logits, dim1)[0] # [3] pred_class probs.argmax().item() confidence probs[pred_class].item() # 标签映射 id2label {0: Negative, 1: Neutral, 2: Positive} print(f输入文本: {text[:50]}{... if len(text)50 else }) print(f预测情感: {id2label[pred_class]} (置信度: {confidence:.3f})) print(f各情感概率: Negative{probs[0]:.3f}, Neutral{probs[1]:.3f}, Positive{probs[2]:.3f}) # 显示图片 display(IPyImage(image_path, width300)) return id2label[pred_class], confidence # 测试 predict_sentiment( data/train/text/IMG_001.txt, data/train/images/IMG_001.jpg )逻辑说明unsqueeze(0)补batch维度是PyTorch推理铁律漏掉会报expected 2D inputtorch.softmax把logits转概率比直接argmax更可解释display(IPyImage())在Jupyter里内联显示图片比plt.imshow()更简洁。4.2 用ipywidgets做交互式预测面板拖动选择文件实时更新结果# 获取所有测试样本路径 test_dir Path(data/val) text_files sorted(list(test_dir / text).glob(*.txt)) image_files sorted(list(test_dir / images).glob(*.jpg)) # 创建下拉菜单 file_options [(f.stem, i) for i, f in enumerate(text_files)] dropdown widgets.Dropdown( optionsfile_options, description选择样本:, disabledFalse, ) output widgets.Output() def on_file_change(change): if change[type] change and change[name] value: idx change[new] text_path text_files[idx] image_path image_files[idx] with output: output.clear_output() predict_sentiment(str(text_path), str(image_path)) dropdown.observe(on_file_change, namesvalue) # 显示控件 display(dropdown) display(output)提示observe监听下拉框变化output.clear_output()避免结果堆叠。这个交互面板答辩时直接投屏老师点几下就看到效果比跑命令行强十倍。4.3 一键导出PDF报告用nbconvert命令行生成带图表的正式文档在终端执行不是在Jupyter里# 确保已安装texliveUbuntu/Debian sudo apt-get install texlive-xetex texlive-fonts-recommended texlive-plain-generic # 导出PDF假设Notebook名为multimodal_report.ipynb jupyter nbconvert --to pdf --no-input multimodal_report.ipynb参数说明--no-input隐藏代码块只留Markdown和输出图表符合课程报告要求--to pdf依赖LaTeX引擎若报错缺字体装texlive-lang-chinese导出的PDF里所有plt.show()图表、display()图片、print()结果都会保留且自动编号。5. 多模态情感分析避坑指南5个让本科生当场崩溃的细节以及我的后悔药多模态项目最折磨人的不是算法而是那些“文档里不写、报错不提示、搜都搜不到”的细节。以下是我在带学生做类似项目时被反复问爆的5个问题按“现象→原因→解决”列清楚全是血泪经验。5.1 现象训练loss不下降val_acc卡在33%随机猜水平原因文本和图像特征维度没对齐或注意力机制里query/key/value维度不匹配。比如DistilBERT输出768维但投影层写成nn.Linear(768, 512)而图像投影也是512维表面看能concat但注意力计算时qk.T维度报错被静默忽略实际用了错误的梯度。解决在MultimodalFusion.forward开头加断言assert text_emb.shape[1] 768, fText emb dim error: {text_emb.shape} assert image_emb.shape[1] 512, fImage emb dim error: {image_emb.shape}并在__getitem__里打印每个样本的embedding shape确认无None或空tensor。5.2 现象Jupyter里训练突然中断报CUDA out of memory但nvidia-smi显示显存只用了30%原因PyTorch Lightning默认启用find_unused_parametersTrue为DDP准备但在单卡模式下会额外缓存梯度尤其多模态模型参数多显存虚高。解决在Trainer初始化时显式关闭trainer Trainer( ..., strategyauto, # 不要用ddp # 删除所有strategy相关参数 ) # 或更彻底在LightningModule里重写configure_ddp def configure_ddp(self, model, device_ids): return model # 直接返回原模型不包装5.3 现象predict_sentiment函数报错AttributeError: str object has no attribute decode原因Windows系统下open()读取txt文件时默认用cp1252编码遇到中文直接崩而Linux/macOS默认utf-8。解决所有文件读取强制指定encodingutf-8并在requirements.txt里加chardet库自动检测编码import chardet def safe_read_text(path): with open(path, rb) as f: raw f.read() encoding chardet.detect(raw)[encoding] return raw.decode(encoding or utf-8)5.4 现象导出PDF时LaTeX报错! Package fontenc Error: Encoding file t2aenc.def not found原因缺少俄文/东欧字符支持包但你的文本全是中文根本用不到——这是因为nbconvert默认加载了全字体集。解决创建最小化LaTeX模板myreport.tplx((* extends article.tplx *)) ((* block header *))\usepackage[UTF8]{ctex}((* endblock header *))然后导出时指定jupyter nbconvert --to pdf --template myreport.tplx --no-input multimodal_report.ipynb5.5 现象答辩时现场演示换台电脑就ModuleNotFoundError: No module named transformers原因Jupyter notebook里写的import没在requirements.txt里声明或者版本不一致。解决用pipreqs自动生成pip install pipreqs pipreqs ./ --encodingutf8 --force再手动检查生成的requirements.txt把torch等带cu117的后缀改成cpu因答辩机大概率没NVIDIA驱动并加一行jupyter1.0.0锁死版本。6. 让报告脱颖而出的3个进阶技巧从“能跑”到“值得拿高分”的临门一脚期末作业的终极目标不是跑通而是让老师一眼看出“这学生真懂”。以下三个技巧成本极低但效果炸裂我带的学生用过至少拿过两次课程最高分。6.1 在报告里加入“模态重要性热力图”用Grad-CAM可视化图像哪部分影响了决策虽然我们的模型没有CNN主干但可以对ResNet-18的最后一个卷积层做Grad-CAM。只需10行代码就能生成热力图叠加在原图上直观展示“模型为什么认为这张图是积极的”。from pytorch_grad_cam import GradCAM from pytorch_grad_cam.utils.image import show_cam_on_image # 获取ResNet-18的最后一个卷积层layer4 target_layers [resnet[0][-1].conv3] # ResNet-18的layer4是Sequential最后一层是conv3 cam GradCAM(modelresnet, target_layerstarget_layers, use_cudatorch.cuda.is_available()) rgb_img np.array(Image.open(data/val/images/IMG_001.jpg).convert(RGB)) / 255.0 input_tensor transform(rgb_img).unsqueeze(0) grayscale_cam cam(input_tensorinput_tensor) cam_image show_cam_on_image(rgb_img, grayscale_cam[0, :], use_rgbTrue) plt.figure(figsize(10, 4)) plt.subplot(1, 2, 1) plt.imshow(rgb_img) plt.title(Original Image) plt.axis(off) plt.subplot(1, 2, 2) plt.imshow(cam_image) plt.title(Grad-CAM Heatmap) plt.axis(off) plt.show()效果如果热力图高亮在人物笑脸区域说明模型真的在“看脸”如果高亮在背景广告牌可能数据有偏差——这个分析段落写进报告老师会觉得你有批判性思维。6.2 用混淆矩阵诊断模态失效场景当文本和图像冲突时模型信谁构建一个“冲突样本集”人工挑选文本和图像情绪相反的样本如文本“糟透了”配开心自拍用训练好的模型预测统计混淆矩阵。from sklearn.metrics import confusion_matrix, classification_report import seaborn as sns # 假设已有pred_labels和true_labels列表 cm confusion_matrix(true_labels, pred_labels) plt.figure(figsize(8, 6)) sns.heatmap(cm, annotTrue, fmtd, cmapBlues, xticklabels[Negative, Neutral, Positive], yticklabels[Negative, Neutral, Positive]) plt.ylabel(True Label) plt.xlabel(Predicted Label) plt.title(Confusion Matrix on Conflict Samples) plt.show()关键洞察如果“Negative文本Positive图像”样本大量被判为Neutral说明模型学会了“当模态冲突时保守预测”这是鲁棒性的体现——把这个结论写进报告讨论章节比单纯贴准确率高明得多。6.3 把模型打包成Streamlit Web App扫码即用让答辩变成产品演示用Streamlit 10分钟搭个网页版老师用手机扫码就能试用瞬间拉开和“只交PDF”的同学差距。# app.py import streamlit as st from PIL import Image st.title(多模态情感分析Demo) st.write(上传一张图片和一段文字AI将分析整体情感倾向) uploaded_image st.file_uploader(选择图片, type[jpg, jpeg, png]) text_input st.text_area(输入文字, height100) if uploaded_image and text_input.strip(): # 保存临时文件 with open(temp.jpg, wb) as f: f.write(uploaded_image.getbuffer()) with open(temp.txt, w, encodingutf-8) as f: f.write(text_input.strip()) # 调用predict_sentiment label, conf predict_sentiment(temp.txt, temp.jpg) st.success(f预测结果: **{label}** (置信度: {conf:.3f})) st.image(temp.jpg, width300)部署命令本地streamlit run app.py生成二维码分享pip install qrcode[pil] python -c import qrcode; qrcode.make(http://localhost:8501).save(qrcode.png)我的习惯答辩前夜把app部署到学校内网服务器用streamlit run app.py --server.port 8080生成内网二维码答辩时老师扫码即用。有一次老师扫完说“这比我用的商业API还准”当场给了满分。技术不难关键是让成果可感知、可交互、可传播。希望帮到你。本文还有配套的精品资源点击获取