1. 毕设选题:为什么是Streamlit+多模态RAG电影推荐系统
先聊点实在的。
每年到毕设季,电影推荐系统几乎是计算机专业“腐朽最重灾区”的选题之一。你去知网或者毕业设计展厅扫一圈,十个推荐系统八个是协同过滤,一个是基于内容的相似度计算,还有一个是深度学习的Embedding拼接。不是说这些老思路做不出来,而是到了现在这个时间点,纯协同过滤或者纯内容推荐很难再撑起一个让人眼前一亮的答辩PPT。评委第一句话往往不是“你做了什么”,而是“你这个工作和已有的系统有什么区别”。
如果你也是冲着“大数据深度学习”方向去选电影推荐这个题目,我强烈建议你在架构上换一个角度切入:把“推荐”这件事,从传统的评分预测,转成“基于内容理解的语义检索+生成式解释”。用大白话说,就是让系统不仅能告诉你“推荐哪部电影”,还能告诉你“为什么推荐这部”。这才是我最终把题目定为“Streamlit+多模态RAG电影推荐系统”的核心动机。
这篇文章我不会给你贴一个特别完整的毕设代码仓库,因为那东西你应该自己写。我更想把从选题、数据处理、向量化、RAG检索、界面搭建到答辩演示的完整路径讲清楚,尤其是那些文档里不会写、只有自己踩过坑才会知道的细节。适合谁看?适合那些正在做推荐系统类毕设、想用较新架构做出差异化亮点、又不想把自己折腾死在深度学习训练上的同学。当然,如果你只是对多模态RAG感兴趣,想找一个能快速看到效果的应用场景,这篇也能当你的入门参考。
2. 数据准备与多模态向量化
2.1 数据集选型:第一道分水岭
电影推荐方向可用的数据集其实不少,但绝大多数人第一反应就是用某经典电影数据集。那个数据集的好处是干净、格式统一、用户评分记录多,但它有一个致命问题:信息维度太单薄。你拿到的往往只是一张评分表加一份电影基本信息表,连电影简介都写得像填空题,海报这种视觉信息更是想都不要想。
做多模态RAG,必须保证数据源里“模态够多”。我最后选的是某开源电影全集数据集,里面包含电影标题、类型、导演、演员、简介、关键词标签、海报图片路径、用户评分记录。这个数据集的单条电影信息比较丰富,而且海报是真实可下载的,这就为视觉模态提供了前提。
选数据集时有几个硬性标准,你可以直接拿去用:
- 至少有文本简介,而且简介长度不能太短,否则向量化之后区分度非常差。
- 有海报或者剧照,这样才能做图像模态。
- 有评分或者收藏记录,否则协同过滤部分没法做。
- 数据量不用太大,5000部电影以内完全够用。多模态向量化虽然不训练模型,但要调embedding接口,数据量一大,时间和钱都扛不住。
我当时实际清洗下来,大概保留了4200部电影。这个量级对毕设来说刚刚好,既能让推荐结果看起来有随机性和丰富度,又不会让向量库构建时间超过半小时。
2.2 文本向量化:不是随便调个接口就完事
文本模态覆盖的是电影的简介、类型、关键词和导演演员信息。很多人做RAG,文本向量化直接拿一个开源的embedding模型,把所有字段拼接成一大段字符串塞进去,然后发现检索结果乱七八糟。这里面的核心问题不是模型不行,而是拼接方式不对。
我建议把文本分成两个层次来做向量:
第一层是“短文本属性向量”,主要编码类型、关键词、导演、演员、年份这种结构化的标签信息。这一层向量负责的是精确匹配,比如用户说“我想看诺兰的科幻片”,它能在语义空间里快速锁定对应电影。
第二层是“长文本语义向量”,主要编码电影简介和短评。这一层向量负责的是泛化理解,比如用户说“我想看那种烧脑、时空循环、结局反转的电影”,它能在语义层面对齐到《恐怖游轮》《源代码》这一类片子上。
两个向量分开存,后面做RAG召回的时候可以分开做相似度计算,再按权重融合。千万不要偷懒把所有字段拼成一个向量,那样等于把精确匹配和语义匹配混在一个空间里,两头都做不好。
向量化的具体实现,有条件的可以直接调商用embedding服务,效果稳定;没条件的可以用开源的句子向量模型。要注意的是,中文文本和英文文本的向量化模型最好分开或者用多语模型,别拿一个纯中文模型去编码英文人名,否则效果会很离谱。
2.3 海报向量化:不是所有图都适合直接进向量库
图像模态是这个项目的亮点,也是大多数人翻车的地方。海报向量化不是简单地把图片resize成一个固定尺寸然后丢给图像模型提取特征就行。关键问题是:海报的风格差异极大,有些是真人剧照,有些是极简插画,有些是文字海报。如果用同一个全图特征去编码,可能出现“把两张颜色相近但内容毫无关系的海报判定为高度相似”的乌龙。
我的做法是先对海报做主体区域提取,把画面中心区域裁出来,再送到图像embedding模型里取特征向量。这个过程不复杂,用开源的图像特征提取模型就能完成,输出维度一般是512维或者768维。建议把特征向量做L2归一化,后面做余弦相似度时数值更稳定。
另外,海报图像建议统一resize到224x224或者256x256。分辨率不是越高越好,因为图像embedding模型的设计输入尺寸就那么大,原图分辨率再高,模型内部也只会做缩放。分辨率统一还能大幅提升批量向量化的速度。
2.4 文本向量与图像向量的空间对齐
多模态RAG和单模态RAG最大的区别在于:文本向量和图像向量本身不在同一个语义空间里,你必须有一个“对齐”的手段。
常见的对齐方式有三种:
第一种是把图像向量和文本向量拼接成一个长向量再进索引。这种方式简单,但带来的问题是两种模态的数值范围可能不一致,容易造成某一方主导。
第二种是分别建索引,查询时分别召回,最后做结果融合。这种方式灵活可控,也是我在毕设里采用的方式。
第三种是使用多模态对齐模型,比如通过对比学习训练过的双塔模型,让对应图文对的向量在空间里靠近。这种方式效果最好,但需要一定训练成本,对毕设来说性价比不高。
我采用的方式具体来说是这样:分别构建文本向量库和图像向量库,文本召回Top30,图像召回对应的向量,然后利用评分记录做加权重排,把最终Top10结果作为候选集。这其实就是RAG里“召回-重排”的经典范式,只是把召回分支从单一文本扩展到了“文本+图像”两个通道。答辩的时候把这个流程图放PPT上,说清楚“为什么要分叉召回——因为不同模态的信息互为补充”,评委一般都会点头。
向量存储我用的开源向量数据库,构建索引速度快,支持元数据过滤和相似度检索。4200条文本向量加4200条图像向量,加起来不到一万条,对嵌入式向量库来说毫无压力,跑在普通笔记本上完全没问题。你要是为了显得项目“大”,非要去上分布式向量库,那纯属给自己找麻烦,答辩被问到“数据量多少”就会很难收场。
3. 推荐引擎:向量召回+混合排序+个性化解释
3.1 RAG召回:把推荐问题翻译成检索问题
RAG在这里的使用方式,和传统的“检索增强生成问答”不太一样。在标准RAG中,用户先提问,系统检索出相关文档片段,再喂给大模型生成答案。而在电影推荐系统里,用户的“查询”不是一个明确的问题,而是一个模糊的偏好表达,比如“我想要那种适合周末一个人看、节奏慢、画面很美、能让人平静下来的电影”。
这个模糊偏好,恰恰是RAG能发挥价值的地方。
具体流程如下:
用户输入一段自然语言描述,先做文本向量化,得到查询向量。然后把这个查询向量同时丢到文本向量库和图像向量库里做相似度检索,分别取Top30。检索出来的候选电影再结合用户历史评分记录,用协同过滤的打分结果做加权融合。最后从Top10候选里,把电影的标题、简介、海报、相似度分数、协同过滤预测分数拼成一个上下文,喂给大模型,让它生成一段“推荐理由”。
这个流程最大的优势是:系统终于能对用户说人话了。
传统推荐系统只会给你一个列表:“你可能喜欢《某某电影》”。多模态RAG推荐系统能给的是:“看起来你对视觉风格独特的悬疑片有偏好,这部片子在摄影美学和叙事结构上都和你最近标记喜欢的《某某某》相近,而且和你历史评分最高的导演是同一个人。推荐你在周末的晚上,戴上耳机,从头看到尾。”
别小看这段推荐理由。对毕设答辩来说,这几乎是全场最亮眼的演示环节,因为它把“深度学习模型内部发生了什么”这种黑盒问题,变成了“系统如何理解我的意思并生成反馈”这种看得见摸得着的交互体验。
3.2 混合排序:多模态召回结果不能直接拼在一起
多通道召回的原始结果,直接拼接使用会出问题。文本通道召回来的电影和图像通道召回来的电影,相似度分数分布可能不一样。文本相似度普遍偏高,图像相似度普遍偏低,你直接把两个通道的分数放一起排序,文本通道会全面碾压图像通道,图像模态就白做了。
我当时第一次拼接时,Top10里九部都是文本召回来的,海报好看但语义不匹配的电影全被挤掉了。后来才发现要做分数归一化。
具体做法是:对每个通道召回的Top30,在通道内部做一次Min-Max归一化,把分数压缩到[0,1]区间。然后按下面这个公式算一个融合分:
最终得分 = 0.45 x 文本相似度 + 0.35 x 图像相似度 + 0.20 x 协同过滤预测分
三个系数是可以调的,我最后的实验里文本权重最高,因为电影偏好的核心表达还是语义。图像权重次之,但它起到了一个很关键的作用:当用户只说“画面好看”“色调很冷”“色调温暖”这类视觉倾向的词时,图像通道能精准命中,而文本通道基本无能为力。协同过滤占比最低,它主要起到拉高“和你口味相似的人”作用。
融合排序做好后,推荐结果的多样性明显上升。这个混合排序的部分,建议你在答辩PPT里单独放一页,画清楚通道组成和权重配比,这属于架构设计层面的加分项。
3.3 用大模型生成推荐理由:上下文拼装的艺术
RAG模式下,生成推荐理由的质量,一半取决于检索结果,另一半取决于你怎么拼上下文。
我不建议把整段电影简介原封不动地塞给大模型。你要知道,简介是面向所有人的,而推荐理由是面向当前这个用户的。你要做的事情是“信息裁剪”。
我自己用的上下文模板大概是这样的:
- 当前用户的偏好描述(用户输入的原文)
- 用户最近看过的3部电影及评分
- 候选电影的标题、类型、主演信息
- 从海报向量通道角度提炼出的视觉特征描述
- 候选电影与用户偏好的相似度值
把这些信息拼成一段结构化的文字,再让大模型生成推荐理由。你会发现,模型输出的理由明显更有针对性,因为它不是在“介绍电影”,而是在“向这个人解释为什么这部片子适合ta”。
生成式的推荐理由是这套系统的“记忆点”,但要控制大模型接口的调用次数。我当时做了缓存处理:同一个候选电影+同一个用户偏好组合,生成的推荐理由缓存一份,再遇到相同组合直接复用,大大降低了接口费用和响应时间。
4. Streamlit应用层:从原型到可答辩Demo
4.1 功能页面规划:不要一上来就堆组件
Streamlit的优势是上手快,几行代码就能跑一个界面。但也正因为上手快,很多同学会陷入“组件堆积”的误区,左一个按钮右一个下拉框,整个页面像工具箱,不像一个产品。
毕设演示的界面,我建议只保留四个核心功能模块。
第一个是偏好推荐区。用户输入一段自然语言偏好描述,点“生成推荐”,下方显示推荐电影卡片列表,每张卡片包含海报、标题、简介和推荐理由。
第二个是相似电影探索区。用户从推荐结果里点击某部电影,系统用这部电影的文本向量和图像向量去检索最相似的其它电影。这个功能相当于内容推荐的可视化验证,演示时评委很容易懂。
第三个是历史反馈区。展示用户最近的播放/评价记录,以及系统的混合排序权重分布。用Streamlit的表格组件展示协同过滤预测分、文本相似度、图像相似度三项指标,直观体现“多模态融合”。
第四个是检索过程可视化区。把RAG召回的中间过程展示出来:用户查询向量被编码后,显示文本通道召回的Top5和图像通道召回的Top5,标出各自相似度。这个模块是答辩杀手锏,因为大多数评委没在毕设里见过可交互的检索过程展示。
页面布局上不要贪多。Streamlit的侧边栏用来放参数调节控件,比如TopN取值、融合权重系数滑块。这样评委在答辩现场可以顺手拖一下滑块,看推荐结果变化,这个交互动作本身就很有说服力。
4.2 session_state与推荐历史管理
Streamlit的一个大坑是:每次交互触发,脚本都会从头到尾重新执行一遍。如果不加控制,用户输入一个偏好,页面重新刷新,推荐结果还没出来,之前的操作状态就丢了。
推荐历史、用户已选的偏好描述、向量库连接状态这些不能放在普通全局变量里,必须用st.session_state保存。
举个例子,我用session_state存了以下几个关键状态:
- 用户历史观影记录,作为协同过滤部分的输入
- 当前候选电影列表,避免每次交互都重新召回
- 大模型生成的推荐理由缓存,避免重复调用接口
- 用户切换页面标签时,保持推荐列表不丢失
这里有个很容易被忽略的点:向量数据库连接对象不能反复创建。Streamlit重刷页面时,如果你每次都重新连接向量库,加载时间会呈指数级上升。正确做法是先判断session_state里有没有已存在的连接对象,没有才去创建。这样一个简单的判断逻辑,能把页面响应时间从三秒降到零点几秒。
4.3 性能调优与演示模式
Streamlit应用本身是单机运行的,对毕设来说足够了。但你要是拿到答辩现场的电脑上跑,有几个性能问题必须提前处理。
第一个是首次加载慢。根源在于向量库索引加载和embedding模型初始化。解决办法是启动时预加载,或者做一个进度条动画,至少让现场看起来是“系统在思考”而不是“卡死了”。
第二个是海报图片请求慢。本地图片你当然可以直接读文件,但如果你偷懒用了远程图片链接,答辩现场断网,整个页面就垮了。务必把海报图片在数据准备阶段就下载到本地,Streamlit里直接用本地路径渲染。
第三个是大模型接口超时。推荐理由生成是网络请求,现场答辩时网络抖动,请求超时,结果页面报错,会非常尴尬。我给接口调用加了一个超时重试机制,三次失败后自动降级为“只展示推荐结果,不生成推荐理由”,保证核心演示不断片。
我当时是把整个项目打包成一个Docker镜像,带了一台笔记本,答辩现场直接起容器跑。虽然有点重,但确实省去了答辩现场装依赖的麻烦。如果你不想用Docker,那至少要把requirements.txt固定版本号,不要带一大串空缺版本号的依赖,否则现场环境一旦有版本冲突,你会在PPT翻页的时候听到自己的心跳声。
5. 常见问题速查与答辩避坑指南
5.1 调试阶段最容易翻车的四个坑
第一个坑:语义相似度召回的推荐结果和用户预期严重不符。比如用户说“伤感的爱情片”,系统返回了一部“动作片”,原因是动作片简介里恰好出现了“伤心”这个词。这个问题的根源是纯向量召回对否定句、转折句的理解能力有限。解决办法是让召回的文本片段更结构化,把类型标签作为硬性过滤条件,向量相似度作为软性排序条件,不能全靠向量。
第二个坑:图像向量通道把色调相近但题材完全不同的电影混在一起。比如一幅冷色调的海报,系统认为它是科幻片,其实是一部悬疑剧情片。这是图像embedding的固有局限。解决办法是给图像通道的召回结果降低权重,或者增加“海报主体文字检测”,如果海报上有大段文字,默认减少该海报的权重。
第三个坑:不同模态的向量维度不一样,融合排序时报维度错误。文本向量是768维,图像向量是512维,如果你不是用同一套模型对齐的,就不可能直接放到一个索引里。这一点在“分别建索引”方案里反正避开了,但你要是看了网上某些教程用的是“拼接向量”,很容易被这个维度不匹配问题折磨到深夜。
第四个坑:embedding接口的请求并发限制。批量向量化时,如果你一次性发太多个请求,服务端会限流。我当时给批量向量化脚本加了指数退避的重试,每批次处理200条,每次请求间隔0.5秒,耗时还算可控。
5.2 答辩评委最爱问的五个问题
“为什么选RAG而不是微调模型?”
标准回答:微调一个电影推荐模型需要大量的监督数据和计算资源,而RAG不需要重新训练,通过检索增强的方式就能实时接入最新电影数据。对电影推荐这个场景,语义检索带来的效果提升足够显著,且方案可解释性强。
“你的多模态是怎么实现的?”
这个用前面说的“分别索引、分通道召回、融合排序”来回答。关键是说清楚:不是把图像和文本丢进同一个模型,而是让它们在召回层面互相补充。
“推荐结果里有冷门电影吗?”
冷门电影这个点一定要提前准备。很多推荐系统都偏向热门的电影,因为热门电影的标签和评论多,向量化质量高。如果你被问到,要回答你的系统是怎么处理长尾数据的,我当时是加了“相似度置信度下限过滤”,相似度太低的冷门片宁可不出现在结果里。
“协同过滤和多模态内容推荐,哪个贡献更大?”
这是一个诱导性问题,不能选边站。要回答两者各自擅长什么,协同过滤擅长捕捉群体口味规律,多模态内容检索擅长捕捉个体语义偏好,系统中是相互融合的,不是互相替代。
“你的系统如果要做成产品,最大瓶颈是什么?”
这个问题考察你的思考深度。我当时的回答是:计算成本和数据更新时效性。向量化的成本、大模型生成推荐理由的成本、以及冷启动新片的向量化滞后,这三个点都是产品化需要解决的真实问题。
5.3 复盘心得
做完这个项目之后,我自己最大的体会是:毕设选题的差异,本质上不是技术难度的差异,而是“叙事空间”的差异。同是推荐系统,纯协同过滤的故事在十五页PPT里就讲完了,而多模态RAG的故事从数据清洗讲到向量召回、从融合排序讲到生成式解释,全程都有可说的地方。
你不需要真的做出一个商业级产品,你只需要在一个完整的技术链条里,把每一个环节都做出合理的决策,并且能解释清楚决策的依据。这才是毕设答辩考察的核心。
如果现在有人让我再做一个推荐系统类的毕设,我大概率还是会走多模态RAG的路子,但会在数据侧加入用户评论的细粒度情感分析,让文本模态的理解层次再往下钻一层。这个方向往下走的空间还很大,光是文本模态和图像模态的对齐方式,就够你再做一整篇论文了。