☰
豆瓣电影数据集驱动的混合推荐系统:用户画像与协同过滤实践
2026/10/3 7:47:17 网站建设 项目流程

简介:面向计算机专业学生与开发者的Python毕业设计资源,围绕用户画像推荐系统,采用混合推荐算法,基于豆瓣电影数据集构建了从数据处理、画像建模到推荐结果展示的完整项目。资源共13个文件,压缩包总大小144.43MB,包含可运行源码、配套设计文档与部署说明、properties配置文件、csv/dat数据文件、png界面示意图以及分卷zip压缩包,各类型文件分工明确,便于按需查阅。包内同时附带参考学习项目与原始数据,可帮助理解协同过滤、内容推荐等算法在实际场景中的融合与应用。目前已有38人学习下载。项目源码经过严格测试,可在多种环境下稳定运行,直接适用于毕业设计、课程设计或项目立项演示;具备一定开发基础后,还可基于现有框架二次开发,实现个性化榜单、相似电影推荐等扩展功能。对于部署或环境配置难题,作者提供远程指导与技术支持,降低上手门槛。

1. 用户画像推荐系统:毕设之前,先把下载包里这几样东西看清

如果你正在找推荐系统方向的 Python 毕业设计,多半见过这个名字:用户画像推荐系统(Python毕设)-混合推荐算法-豆瓣电影数据集。这份资源的核心不只是一个能跑的代码包,而是一套从“原始豆瓣评分数据 → 用户画像 → 混合推荐结果”的完整链路,算法上走的是协同过滤与内容推荐的加权融合,而不是单一算法糊弄事。它适合三类人:一是拿它做毕设底子,想省下写数据清洗和推荐主流程的时间;二是做课程设计,需要文档、代码、演示截图都齐全的参考项目;三是刚接触推荐系统的开发者,想通过一份真实数据集理解 UserCF、ItemCF、冷启动和画像构建之间的关系。我拆完这份包之后可以明确说:直接解压跑通只是第一步,真正值钱的是你会不会改它的画像权重和融合参数。

2. 算法选型与数据集摸底:为什么混合比单一模型更稳

2.1 用户画像推荐系统的主体链路

把这份资源打开后,先不要急着跑代码,而是要看清它的主体链路。目录结构里有MapleMovie.zip、DATA目录、application.properties、部署说明.md、项目说明.md和若干图片,这是一套“后端服务 + 算法脚本 + 数据文件 + 部署文档”的完整结构。算法链路梳理下来可以分成四段:原始数据入库、用户画像构建、混合推荐计算、结果输出。

举一个具体场景:现在有一个用户看过了《霸王别姬》《活着》,并且给了 9 分、8 分的高分。纯 ItemCF(基于物品的协同过滤)的思路是找到“看过这两部电影的人也喜欢什么”;纯内容推荐的思路则是“这两部电影的类型、导演、演员里有哪些共同特征,再推荐相似特征的电影”。混合策略把两路结果做加权融合,再把用户画像里的偏好权重叠加上去,这样既能保证“热门但不过度流行”的多样性,也能减少新电影完全无法曝光的问题。

这份毕设资源比较聪明的一点是,它没有用surprise库做黑匣子封装,而是把相似度计算、推荐列表生成、画像权重更新这些核心逻辑写在你能直接看到和修改的 Python 源码里。这对于答辩来说极其重要——导师问“你的推荐逻辑是怎么实现的”时,你至少能讲清每一步。

2.2 豆瓣电影数据集:字段、规模与读取方式

DATA目录里存储的是清洗过的豆瓣电影数据。常规豆瓣电影数据集会包含三张核心表,但这份资源已经做了初步脱敏和格式转换,常见字段如下。

文件/表典型字段说明
moviesmovie_id, title, genres, directors, actors, rating_avg电影基础信息与平均评分
ratingsuser_id, movie_id, rating, timestamp用户对电影的评分记录
user_profilesuser_id, preferred_genres, avg_rating, active_level预计算的用户画像中间表

代码中读取这些数据的方式一般是 pandas 直接读 CSV 或通过 SQLite 读取,路径配置写在配置文件中。需要注意的一点是:application.properties在这个项目里承担了数据路径和推荐参数的配置中心角色,第一次跑通前不要随意改动里面的连接串和输入路径,否则启动时容易报文件找不到。

2.3 选型结论:ItemCF + 内容相似度加权,冷启动靠画像兜底

选择混用 ItemCF 与内容相似度的理由可以从数据量级来判断。豆瓣电影数据集的评分记录通常有数万到数十万条,用户覆盖了高分长尾和低分大众两类偏好。如果只用 UserCF,用户数量一旦上涨,实时计算用户间相似度的开销会快速上升;而只用 ItemCF,则对冷门电影的覆盖不够。

我在实际跑这个项目时观察到,它的 ItemCF 基础推荐负责保障“召回率”,内容相似度负责“多样性”,最后叠加用户画像权重,相当于给每个用户定制了一个偏置项。这个设计识别度很高,答辩时可以重点讲:你优化的不是某一个算法,而是画像权重系数与融合比例在验证集上的表现。

3. 从原始表到用户画像:特征工程与数据清洗

3.1 数据集加载与预处理

这个环节是整个项目里最枯燥、但出错率最高的部分。你下载的代码包可能已经做好了清洗,但从毕设写作角度,你必须展示自己理解预处理过程,最好在文档中补充一部分在你机器上跑过的加载代码。这里给出一个可复现的读取与预处理模板,与项目中DATA目录的文件结构对应。

import pandas as pd import numpy as np # 假设 DATA 目录下是 movies.csv / ratings.csv / user_profiles.csv movies = pd.read_csv('DATA/movies.csv') ratings = pd.read_csv('DATA/ratings.csv') # 时间戳转成可读时间,后面做时间衰减权重时会用到 ratings['ts'] = pd.to_datetime(ratings['timestamp'], unit='s') # 去掉评分为空、电影标题为空的行 movies = movies.dropna(subset=['title']) ratings = ratings.dropna(subset=['rating']) # 过滤评分记录少于 5 条的冷启动用户,避免画像不稳定 user_cnt = ratings.groupby('user_id')['rating'].count() valid_users = user_cnt[user_cnt >= 5].index ratings = ratings[ratings['user_id'].isin(valid_users)] print(f"电影数量: {len(movies)}") print(f"有效评分记录: {len(ratings)}")

逻辑说明:时间戳转时间不是为了好看,而是为了在画像里加入“近期行为权重大于历史行为”的衰减因子。过滤掉评分数量过少的用户,可以减少用户画像中的噪声。参数说明:user_cnt >= 5是一个经验阈值,数据量大的时候可以提高到 10,如果毕设数据集较小,保留 5 条是最低限度。

这一步常见的翻车点是 pandas 版本差异导致timestamp读取为字符串,后面pd.to_datetime报错。建议在环境里锁死 pandas 版本,后面避坑章节会展开讲。

3.2 用户画像构建:评分矩阵、偏好向量与归一化

用户画像是这份资源里最有答辩价值的部分。它本质上是一个加权向量,由三部分组成:用户对类型的偏好、用户对导演/演员的偏好、用户的评分习惯(严格打分还是宽松打分)。下面这个代码块演示了核心构建逻辑,和资源中user_profiles表的计算方式一致。

def build_user_profile(user_id, ratings_df, movies_df): user_ratings = ratings_df[ratings_df['user_id'] == user_id] # 与电影表合并,拿到类型信息 merged = user_ratings.merge(movies_df, on='movie_id') # 类型偏好:每个类型的平均评分 * 该类型被观看次数权重 genre_score = {} for genres in merged['genres']: # genres 形如 "剧情|爱情|战争" for g in str(genres).split('|'): genre_score[g] = genre_score.get(g, 0) + 1 # 归一化,防止观看次数差异带来的虚高 total = sum(genre_score.values()) profile = {k: v / total for k, v in genre_score.items()} # 评分习惯:用户平均分与整体均值的偏移 user_avg = merged['rating'].mean() global_avg = ratings_df['rating'].mean() bias = user_avg - global_avg return { 'user_id': user_id, 'genre_pref': profile, 'rating_bias': bias, 'watch_count': len(merged) } # 示例:构建第一个用户的画像 profile_example = build_user_profile(1, ratings, movies) flag = '偏好占比最高的类型: ' + max(profile_example['genre_pref'], key=profile_example['genre_pref'].get) print(flag)

逻辑说明:rating_bias这个偏置项非常关键。它代表“这个人是不是给分很手松”,如果某用户平均分比全局平均分高 0.8,那他打了 8 分的电影,在别人那里可能只有 7 分。推荐系统在融合时会用这个 bias 做评分修正。参数说明:类型分割符|是豆瓣数据的常见格式,如果你替换成 MovieLens 数据集,分割符要变成|、,或者空格中的某一种,写代码时先打印前 5 行确认格式。

这是整个项目里我建议你最仔细读的一部分,因为答辩时导师提问的概率极高,几乎一定会问:“用户画像向量是怎么构建的?你的归一化方式为什么选了这个?”

3.3 画像验证:可视化与分布检查

画像构建完需要验证,不能直接拿去算推荐。最简单粗浅的验证方式是把不同用户的画像向量打印出来,看是否有区分度。如果发现所有用户的偏好类型都集中在同一两个类型上,大概率是数据稀疏或者归一化出问题了。

import matplotlib.pyplot as plt genre_series = pd.Series(profile_example['genre_pref']) genre_series.sort_values(ascending=False).plot(kind='bar', figsize=(10, 4)) plt.title('User Preference Distribution') plt.tight_layout() plt.savefig('output/user_profile_distribution.png')

逻辑说明:保存到output目录而不是直接plt.show(),是因为毕设报告里需要插图。图片用中文标签时,记得在代码里加plt.rcParams['font.sans-serif'] = ['SimHei'],否则会变成方块字。参数说明:图尺寸figsize=(10, 4)适合放一份 A4 报告的一行位置,不用来回缩放。

这个可视化结果可以直接放进毕设第三章作为用户画像章节的配图。很多学生忽略这部分,答辩时被问到“你怎么验证画像的正确性”就卡住了,有这张图,至少说明你做了分布合理性检查。

4. 混合推荐引擎:从相似度计算到结果融合

4.1 协同过滤:ItemCF 实现与参数选择

ItemCF 的实现思路不是把所有电影两两算一遍相似度,而是先通过用户行为,把同时被同一个人看过的电影建立共现矩阵,再用余弦相似度或皮尔逊相关系数计算相似度。这份资源里用的是基于评分矩阵的修正余弦相似度,下面是精简后可复现的核心代码逻辑。

from sklearn.metrics.pairwise import cosine_similarity import scipy.sparse as sp # 构建 用户-电影 评分矩阵,缺失位置填 0 pivot = ratings.pivot(index='user_id', columns='movie_id', values='rating').fillna(0) # 转成稀疏矩阵,节省内存 sparse_matrix = sp.csr_matrix(pivot.values) # 按电影方向计算相似度(每部电影是一个向量) item_sim = cosine_similarity(sparse_matrix.T) # 转成 DataFrame,便于取某部电影的相似列表 item_sim_df = pd.DataFrame(item_sim, index=pivot.columns, columns=pivot.columns)

逻辑说明:用fillna(0)在评分矩阵上其实有争议。用户没看过某部电影不是“评分为 0”,而是“没有行为”,但在 ItemCF 的常规实现里,矩阵分解和余弦相似度计算先填 0 是工程惯例,后续用权重修正来弥补。参数说明:cosine_similarity(sparse_matrix.T)中.T是转置,因为当前矩阵行是用户、列是电影,而 ItemCF 需要把电影作为向量的维度,所以必须转置成“电影 × 用户”的形态再去算相似度。

在这里有一个常见的数值坑:如果用户数量大(比如超过 1 万),稠密矩阵做.fillna(0)后内存直接爆炸。解决办法是用coo_matrix或直接不填 0、只以评分记录构建稀疏矩阵。这个我在后期测数据时踩过,后面避坑章节详细写。

4.2 内容推荐:基于类型和标签的相似度

内容推荐这一路不使用评分行为,而是用电影自身的属性计算相似度。读取电影的类型、导演、演员信息,做 one-hot 编码或者 TF 向量,再用余弦相似度计算电影间的内容相似度。

from sklearn.feature_extraction.text import TfidfVectorizer # 把类型 + 导演 + 演员拼成一个“词袋”文本 movies['content_features'] = ( movies['genres'].fillna('') + ' ' + movies['directors'].fillna('') + ' ' + movies['actors'].fillna('') ) # TF-IDF 向量化,语法分词,忽略英文停用词 vectorizer = TfidfVectorizer(token_pattern=r'[^|]+') content_vec = vectorizer.fit_transform(movies['content_features']) # 内容相似度矩阵 content_sim = cosine_similarity(content_vec) content_sim_df = pd.DataFrame(content_sim, index=movies['movie_id'], columns=movies['movie_id'])

逻辑说明:把导演、演员直接拼进文本里做 TF-IDF,是一种快速有效的“内容画像”做法。token_pattern=r'[^|]+'是关键参数,意思是按竖线符号|切分,因为你读到的导字段是“凯文·史派西|凯特·布兰切特”这种格式,默认的英文分词器会把外国人名拆碎。参数说明:min_df和max_df在这类数据集里有必要设置,比如min_df=2可以去掉只出现一次的导演名,避免冷门人物干扰相似度计算。

内容推荐的目的是兜住协同过滤的盲区。比如一部刚上线、还没有多少人评分的电影,ItemCF 算不出它的相似物品,但内容特征和《星际穿越》相似,就仍然能进入推荐候选集。

4.3 加权融合与 Top-N 截断

两步相似度计算完成后,进入最核心的部分——融合。资源中的做法是加权相加,然后按用户画像偏置修正,最后截断 Top-N。

def hybrid_recommend(user_id, top_n=20, alpha=0.6, beta=0.4, profile_weight=0.05): # 获取该用户已看过的电影 watched = set(ratings[ratings['user_id'] == user_id]['movie_id']) # 候选分数累加容器 score = {} # 已看过的电影作为种子,在 ItemCF 和内容相似度中取相似物品 for movie_id in watched: # ItemCF 贡献 sim_items = item_sim_df[movie_id].sort_values(ascending=False).iloc[1:20] for candidate, sim in sim_items.items(): score[candidate] = score.get(candidate, 0) + alpha * sim * beta # 内容相似度贡献 content_items = content_sim_df[movie_id].sort_values(ascending=False).iloc[1:20] for candidate, sim in content_items.items(): score[candidate] = score.get(candidate, 0) + (1 - alpha) * sim * beta # 过滤已看电影 for movie in watched: score.pop(movie, None) # 按分数排序取 Top-N top_recs = sorted(score.items(), key=lambda x: x[1], reverse=True)[:top_n] # 叠加用户画像偏置:如果候选电影的类型命中用户偏好类型,加权 profile = build_user_profile(user_id, ratings, movies) rec_with_bias = [] for movie_id, base_score in top_recs: movie_genres = movies[movies['movie_id'] == movie_id]['genres'].values hit_genres = sum(1 for g in str(movie_genres[0]).split('|') if g in profile['genre_pref']) final_score = base_score * (1 + profile_weight * hit_genres) rec_with_bias.append((movie_id, final_score)) return rec_with_bias # 调用示例 recs = hybrid_recommend(user_id=1, top_n=10, alpha=0.6, beta=0.4, profile_weight=0.05) print(recs[:3])

逻辑说明:这里alpha是混合比例,alpha=0.6表示 ItemCF 贡献 60%、内容推荐贡献 40%。beta是整体放缩系数,它不会改变排序,但会影响后续如果再做评分预测时的量级。参数说明:profile_weight=0.05是画像偏置项的调节幅度,这个值如果设置太大(比如 0.3),会过度放大用户偏好类型,导致推荐结果单一化,全是同一类电影;调到 0.02~0.08 之间比较稳。

这段代码的含义可以延伸成答辩时的一句话:“用户画像不是独立的推荐路,而是叠加在混合结果之上的偏置修正层。”这句话比“我用了一个混合算法”要有说服力得多。

5. 常见问题与排查:环境、编码、冷启动与内存占用

5.1 Python 环境配置:版本与依赖锁死

现象:按部署说明.md安装依赖后直接运行,报ModuleNotFoundError: No module named 'sklearn',或者pandas版本不兼容导致fillna行为异常。

原因:部署说明.md里的依赖清单往往只列了包名,没有锁版本。scikit-learn在 1.2 之后部分 API 有变化,pandas 在 2.x 里对timestamp的默认解析行为也和 1.x 不同。环境变量里有两个 Python 解释器时,pip 装到了 A 环境,而 IDE 用的是 B 环境,也会出现模块丢失。

解决:创建一个干净的虚拟环境,并手动锁版本,不要直接pip install -r requirements.txt一把梭。

python -m venv .venv source .venv/bin/activate # Windows 用 .venv\Scripts\activate pip install pandas==1.5.3 numpy==1.24.3 scikit-learn==1.3.2 matplotlib==3.7.5 python main.py

逻辑说明:我一般会在每个毕设项目里新建虚拟环境,原因只有一个——有些包是全局环境里被其他项目升级后破坏的。参数说明:pandas==1.5.3不是拍脑门选的,它和numpy==1.24.3以及scikit-learn==1.3.2的编译依赖是已知兼容组合,能避开大多数 c 扩展报错。

5.2 中文乱码与 CSV 编码问题

现象:movies.csv中的中文标题读出为乱码,或者可视化图里中文字体显示成方块。

原因:豆瓣数据集大多以 UTF-8 编码存储,但部分二次加工后的文件可能是 GBK 或 GB2312 编码。Windows 默认的read_csv编码参数是utf-8,碰到 GBK 就崩。Matplotlib 则是因为中文字体不在默认字体列表里。

解决:读取时先判断编码;绘图时指定中文字体。

# 崩溃时尝试指定 GBK 读取 try: movies = pd.read_csv('DATA/movies.csv', encoding='utf-8') except UnicodeDecodeError: movies = pd.read_csv('DATA/movies.csv', encoding='gbk')

逻辑说明:用 try-except 做编码回退是工程上最省事的做法,不用每次打开文件先猜编码。参数说明:少数文件声明encoding='utf-8'但仍乱码,说明文件开头有 BOM,改成utf-8-sig即可。

5.3 冷启动:新用户没有任何评分记录怎么办

现象:给一个新注册的用户跑推荐,返回结果是空列表,或者只返回热门电影榜。

原因:hybrid_recommend函数里取种子电影用的是watched集合,新用户没有评分历史就取不到种子,不可能算出相似物品。

解决:在融合排序前做一次判断,如果用户种子不足,直接降级到“全局热度优先 + 高分策略”。

if len(watched) == 0: hot_movies = movies.sort_values('rating_avg', ascending=False) return [(mid, 1.0) for mid in hot_movies['movie_id'].iloc[:top_n]]

逻辑说明:这个兜底逻辑建议写进代码里,因为答辩时老师大概率会问“你的系统怎么处理冷启动”,而一个if分支就能讲清楚。参数说明:top_n这里可以设成 10,但注意热度榜会重复出现在所有用户的结果里,个性化价值低,所以只适合冷启动场景。

5.4 内存占用与迭代速度异常

现象:评分矩阵用户数过万后,进程直接内存溢出,或者相似度计算跑了十几分钟没结束。

原因:评分矩阵用pivot后填充 0 的稠密矩阵,行数是用户数、列数是电影数,一万用户乘一万部电影就是 1 亿个格子,dtype 默认 float64,内存瞬间接近 800 MB,相似度矩阵计算时再乘一个平方级,直接扛不住。

解决:只用稀疏矩阵,并限制参与计算的电影范围。

from scipy.sparse import csr_matrix # 只取出现过评分的用户和电影组成索引映射 rating_index = ratings.copy() rating_index['user_idx'] = rating_index['user_id'].astype('category').cat.codes rating_index['movie_idx'] = rating_index['movie_id'].astype('category').cat.codes # 构建 coo_matrix,再转 CSR 参与计算 from scipy.sparse import coo_matrix sparse_ratings = coo_matrix( (rating_index['rating'], (rating_index['user_idx'], rating_index['movie_idx'])) ).tocsr()

逻辑说明:astype('category').cat.codes会把原始 user_id 从非密集整数映射成紧凑索引,内存占用大幅下降。参数说明:如果数据量还是太大,可以在这一层继续过滤掉观看人数极少的电影(比如只被 1 个人看过的),因为它们的相似度参考价值太弱。

5.5 项目结构理解:application.properties 与部署说明在说什么

现象:很多学生解压后看到application.properties和MapleMovie打包文件,误以为这是一个 Java 项目,或直接忽略这些文件。

原因:这是一个前后端分离的完整作品,推荐算法核心在 Python 侧,而接口服务与打包部署部分涉及 Spring Boot 配置。application.properties是服务端配置文件,MapleMovie.z01/z02/z03是压缩分卷。

解决:毕设文档里建议把项目架构画成“Python 算法模块 + 服务端接口模块”,并在部署说明里写清楚两部分各自如何启动。这里不做技术展开,但你需要理解:答辩时老师更关心推荐算法你的贡献,而不是服务部署细节,所以文档中算法部分要着墨更多。

6. 验证召回效果与二次开发:评估指标、可视化与改造方向

6.1 离线评估:召回率与精确率的简易实现

推荐结果不能只看“长得合理”,要给出数值。毕设里最常用的是离线分割评估,把每个用户的评分记录按时间切出最后 20% 作为测试集,前 80% 作为训练集,然后算召回率与精确率。这个代码可以直接复用到项目的evaluate.py里。

def evaluate_recall_precision(test_ratings, rec_func, k=10): hit = 0 total_test_items = 0 for uid, group in test_ratings.groupby('user_id'): actual = set(group['movie_id']) predicted = [mid for mid, _ in rec_func(uid, top_n=k)] hit += len(actual & set(predicted)) total_test_items += len(actual) recall = hit / total_test_items if total_test_items else 0 precision = hit / (len(test_ratings['user_id'].unique()) * k) return recall, precision recall, precision = evaluate_recall_precision(test_ratings, hybrid_recommend, k=10) print(f"Recall@{10}: {recall:.4f}, Precision@{10}: {precision:.4f}")

逻辑说明:actual & set(predicted)是求交集,代表真实观看和推荐的重复部分。参数说明:k=10是评估片段最常用的值,你也可以同时跑k=5和k=20形成对比表格,放在毕设实验章节会让数据更有层次感。

6.2 把推荐结果可视化:直观汇报毕设成果

推荐结果列表不好给导师看,但可视化推荐理由就直观很多。

import matplotlib.pyplot as plt def visualize_recs(user_id, rec_list): titles = [] for movie_id, score in rec_list[:5]: title = movies[movies['movie_id'] == movie_id]['title'].values titles.append(f"({movie_id}) {title[0] if len(title) else '未知'}") plt.figure(figsize=(8, 4)) y = range(len(titles)) plt.barh(y, [s for _, s in rec_list[:5]]) plt.yticks(list(y), titles) plt.title(f"User {user_id} Top-5 Recommendations") plt.tight_layout() plt.savefig('output/rec_result_user_{}.png'.format(user_id))

逻辑说明:直接在推荐列表上做水平条形图,分数差异可以直观看出个性化强度。如果所有分数几乎一样,说明融合权重没有拉开差距。参数说明:保存 PNG 到output目录而不是弹窗显示,这有利于在论文中插入多组实验结果。

6.3 可扩展的改造方向

只做离线评估还不够,有几个方向可以低成本扩展给毕设加分。一是把评分预测改成“隐式反馈”预测,把“看过/没看过”作为标签,用矩阵分解替代传统相似度;二是引入时间衰减因子,用户半年前和一周前的同样是 5 分,但后者权重更高;三是在融合层换成加权排名融合,而不是分数相加。

我拆这份资源的时候,感受最深的是劝身边朋友不要直接解压运行然后改个标题就交差。从那以后我每次拿到这种毕设包,都会强制自己先读一遍项目说明.md,把application.properties和DATA目录里的字段对齐,再动手改算法参数。这样做的好处是答辩时你不怵提问,也知道哪里改了解能起什么作用。希望这份拆解能帮你在复现时少走几步弯路,真正把它变成你自己的作品。祝顺利。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询