news 2026/11/2 20:05:03

电影推荐系统课程设计全解析:协同过滤、评分矩阵与Flask实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
电影推荐系统课程设计全解析:协同过滤、评分矩阵与Flask实践

简介:基于Python的电影推荐系统源码,专为计算机相关专业课程设计与期末大作业场景打造,适合需要完整可运行项目进行参考、改造与答辩准备的学生。压缩包共十个文件,体积仅2.37MB,包含Python主程序、电影与评分CSV数据文件、项目配置文件及依赖数据压缩包等,各类型分工明确:py脚本承载推荐逻辑和入口流程,csv提供原始数据支撑,xml与iml面向IDE工程结构,可直接导入使用。整体经过严格调试,下载解压后即可运行,免去环境配置与数据清洗的重复工作。源码涵盖基于机器学习方法的电影推荐系统实现,附有处理后的电影数据和评分数据,便于理解协同过滤或基于内容的推荐思路,同时项目目录结构清晰,适合快速定位关键模块。该资源已有一百六十二人学习下载,对正在完成课程设计、期末大作业或希望积累推荐系统实战经验的初学者具有较高参考价值。

1. 电影推荐系统课程设计:源码能跑通不算完,答辩才见真章

每年毕业季都会有人拿着一份解压后能直接运行的 Python 电影推荐系统源码走进答辩教室,然后被一句“你的用户相似度矩阵是 N×N 还是稀疏存储”当场问住。课程设计里的电影推荐系统核心并不玄,无非是协同过滤、评分矩阵、Top-N 推荐这三件事;但它同时踩了数据处理、算法选型和工程落地三个领域,任何一个环节没想透,代码能跑,答辩照样翻车。这篇笔记围绕一份典型的基于 Python 的电影推荐系统课程设计源码展开,从 MovieLens 数据怎么读、UserCF 和 ItemCF 怎么手写、评估指标怎么算,一直写到把推荐结果用 Flask 暴露成接口,把每一步的原理、参数和血泪坑一次讲清楚。适合正在做课程设计或毕设、想快速把推荐系统跑通并验证思路的 Python 开发者。

2. 协同过滤选型先想清楚:数据形态决定你写 UserCF 还是 ItemCF

拿到一份推荐系统课程设计源码,第一眼不要盯着算法函数看,先看读数据的那几行。推荐系统所有的后续逻辑都取决于数据的形态:有多少用户、多少部电影、评分稀疏到什么程度。MovieLens 100K 数据集默认是 943 个用户、1682 部电影、10 万条评分,缺失率高达 93% 左右。这种规模的数据用传统协同过滤是绝对主流,也最适合课堂展示;但如果你把数据集换成 1M 甚至 20M,同样的代码可能跑几分钟就内存爆炸,这是选型前最先要建立的直觉。

2.1 基于用户的协同过滤:找口味相似的人,K 个邻居怎么定

基于用户的协同过滤(UserCF)逻辑非常直白:把每个用户的评分向量看成他的“口味签名”,计算签名之间的相似度,找到最像的 K 个邻居,然后用邻居对某部电影的评分来预测当前用户的评分。课程设计里最常用的是皮尔逊相关系数,它比余弦相似度多做了一步均值中心化,能抵消掉“有人打分手松、有人手紧”这种个人习惯带来的偏差。

相似度计算的核心代码通常长这样:

import numpy as np import pandas as pd def pearson_similarity(user1, user2, rating_matrix): # 只取两个用户都评过分的电影,缺失值用 notna() 判断 common = rating_matrix.loc[user1].notna() & rating_matrix.loc[user2].notna() if common.sum() < 2: # 共同评分少于 2 部,相似度视为 0 return 0.0 vec1 = rating_matrix.loc[user1, common].values.astype(float) vec2 = rating_matrix.loc[user2, common].values.astype(float) return np.corrcoef(vec1, vec2)[0, 1] # np.corrcoef 自带均值中心化

这里有两个容易被忽略的点。第一个是common.sum() < 2这个阈值,如果两个人只共同看过一部片子,皮尔逊系数算出来是 1.0,但这完全不能说明两个人口味一致,只能说明他们恰好都看过同一部热门电影;阈值设成 2 或 3 能让相似度分布更可信。第二个是判断缺失值必须用notna(),不能用!= 0。很多初版源码把评分矩阵中缺失的位置填成了 0,再用!= 0去筛共同评分,结果两个都不爱打高分、经常留下“未评分”的用户,在计算时反而因为共同的 0 被判定为相似——这是典型的“把没看过当成打了 0 分”的错误。

拿到相似度之后,预测评分就是加权和:

def predict_rating(user_id, movie_id, rating_matrix, similarity_matrix, k=20): sim = similarity_matrix[user_id].drop(user_id, errors='ignore') sim = sim.sort_values(ascending=False).head(k) weighted_sum, sim_sum = 0.0, 0.0 for neighbor, weight in sim.items(): r = rating_matrix.loc[neighbor, movie_id] if not np.isnan(r): # 只累加真正评过分且相似度不为 0 的邻居 weighted_sum += weight * r sim_sum += weight return weighted_sum / sim_sum if sim_sum > 0 else float('nan')

参数 k 的取值是这门课里最值得做实验的地方。MovieLens 100K 上常见的经验区间是 10 到 50,k=20 算是稳妥起步。k 太小,预测会被一两个口味极端的邻居带偏;k 太大,把相似度很低的用户也拉进来,预测值会趋于全局平均分,失去个性。我一般会让学生把 k 设计成命令行参数,跑一组误差曲线给答辩老师看,那种“我用 20 个邻居效果最好”的结论比空口说强得多。

2.2 基于物品的协同过滤:电影场景里更平顺的选择

ItemCF 的思路和 UserCF 相反,它先算电影之间的相似度,然后做这样的推荐:“你给《盗梦空间》打了高分,那么和它最像的《星际穿越》也值得看”。在电影推荐场景里 ItemCF 通常比 UserCF 更可靠,原因很朴素:一部电影被几千个用户评过分,统计上比一个用户只看过几十部电影要扎实;且电影相似度矩阵可以在离线阶段一次性算好,在线推荐时只查表,不用实时遍历全体用户。

ItemCF 的标准做法是调整余弦相似度(adjusted cosine),核心区别在于先减去每个用户的平均评分:

def adjusted_cosine(movie_i, movie_j, rating_matrix): # 找出同时给这两部电影评过分的用户 common = rating_matrix[movie_i].notna() & rating_matrix[movie_j].notna() if common.sum() < 2: return 0.0 common_df = rating_matrix.loc[common, [movie_i, movie_j]] # 对共同评分用户按行取均值,实现均值中心化 user_avg = common_df.mean(axis=1, skipna=True) vec_i = common_df[movie_i] - user_avg vec_j = common_df[movie_j] - user_avg num = (vec_i * vec_j).sum() den = np.sqrt((vec_i ** 2).sum() * (vec_j ** 2).sum()) return num / den if den != 0 else 0.0

这段代码里中心化那一步是关键。如果不减掉用户均值,一个平时只打 4 到 5 星的“慷慨用户”和一个只打 1 到 2 星的“严苛用户”,即使口味完全一致,余弦相似度也会被拉得很低;减掉均值之后,大家就都在“相对自己的平均分高多少”这个尺度上比较了。另外我特别强调一下:这里只用了共同评分的用户子集来算点积和模长,没有把整列向量拖进来。很多课程设计源码图省事,用fillna(0)把全向量直接丢给np.dot,结果就是那批“都没看过这部片”的用户也贡献了相似度分数,数学意义完全变了。

2.3 内容推荐与混合策略:什么时候别硬上深度学习

除了协同过滤,课程设计里也常见基于内容的推荐:把电影的类别(Genres)、年份、导演甚至标签拆成特征向量,再计算你喜欢的电影和新电影之间的相似度。这种做法在冷启动阶段特别有价值——一个新用户没有任何评分,协同过滤给不出任何推荐,内容推荐却可以用“你刚才选了科幻”来兜底。

混合策略最常见的做法是线性加权,比如score = 0.7 * 协同过滤预测分 + 0.3 * 内容相似度分,权重就是课程设计里可以大谈特谈的“调参点”。至于为什么不在课程设计里硬上深度学习,我的理解很简单:10 万条评分对一个神经网络来说太少,过拟合会让离线指标虚高,但实际推荐结果却可能不如一个 100 行的 ItemCF。更关键的是深度模型是个黑匣子,答辩问“为什么推荐这部片子”时,你很难像协同过滤那样指着邻居说“因为你喜欢 A 的人里有 80% 也喜欢 B”。可解释性这东西,在课程设计里比你想的值钱得多。

3. 数据加载与评分矩阵构建:把 10 万条评分变成能算相似度的矩阵

绝大多数课程设计源码的报错都出在数据加载和矩阵构建这两步,而不是算法本身。zip 包解压后,你会发现 data 目录里躺着ratings.csv、movies.csv,有的还带u.data老式格式。不要一上来就pd.read_csv,先花一分钟确认列名、分隔符和编码,能省掉后面一整晚的排错。下面所有操作都以 MovieLens 100K 的ratings.csv为例,字段只有 userId、movieId、rating、timestamp 四个。

3.1 字段与加载:ratings.csv 与 movies.csv 里藏着哪些坑

import pandas as pd ratings = pd.read_csv("ratings.csv", encoding="utf-8") movies = pd.read_csv("movies.csv", encoding="utf-8") # 观察数据形态和缺失值 print(ratings.head()) print(ratings.isna().sum()) print(movies.head())

这段代码逻辑很简单,但有两个容易踩坑的点:一是 MovieLens 100K 的老格式u.data通常用sep='\t'分隔且没有表头,和ratings.csv的逗号分隔完全不同,加载前要看清后缀;二是movies.csv的标题字段里年份是放在括号里的,比如 "Toy Story (1995)",如果你后面做内容推荐按年份过滤,一定要先把年份单独提取出来,否则字符串匹配会出各种幺蛾子。下面给出一个带年份提取的加载方式:

movies["year"] = movies["title"].str.extract(r"\((\d{4})\)") movies["clean_title"] = movies["title"].str.replace(r"\s*\(\d{4}\)", "", regex=True)

用正则把年份拆出来,同时保留干净标题。extract返回的是带一列的正则捕获组,replace里的\s*\(\d{4}\)会吃掉标题末尾的年份和它前面可能存在的空格,注意regex=True必须显式声明,这是 pandas 2.0 之后的要求,省略会直接抛 FutureWarning。

3.2 构建用户—物品评分矩阵:pivot_table 之后思考内存

协同过滤需要一个二维矩阵,行是用户,列是电影,值是评分。最直接的方式是pivot_table:

rating_matrix = ratings.pivot_table( index="userId", columns="movieId", values="rating" ) print(rating_matrix.shape) # (943, 1682) print(rating_matrix.isna().mean().mean()) # 约 0.93,即 93% 缺失

pivot_table会把没有评分的单元格自动填成 NaN,这正是我们要的“缺失”语义。但注意它生成的 DataFrame 是稠密二维结构,943×1682 在内存里也就十几兆,感觉不到压力;一旦换成 MovieLens 1M(6040 用户 × 3706 电影),稠密数组就接近 2200 万个元素,float64 下约 180MB,机器已经会开始卡顿。课程设计如果被要求跑大规模数据,常见的做法是转成scipy.sparse.csr_matrix:

from scipy.sparse import csr_matrix user_ids = rating_matrix.index movie_ids = rating_matrix.columns sparse_matrix = csr_matrix(rating_matrix.fillna(0).values) # 用 fillna(0) 只是为了构造稀疏矩阵,后续计算时仍要区分“未评分”和“评分为 0”

注意:fillna(0)只是配合稀疏矩阵的存储结构,不代表“没看过 = 0 分”。转换成csr_matrix后,相似度计算里取某一行或某一列会快很多;但手写循环时千万不要用sparse[user, :]这样的行切片去遍历,csr按行切片会触发大量拷贝,性能反而比稠密矩阵更差。

3.3 切分训练集/测试集:按时间切,别按随机切

推荐系统的评估有个特殊约束:不能用未来的数据去预测过去。很多课程设计源码直接train_test_split(ratings, test_size=0.2, random_state=42),这在分类任务里没问题,在推荐里却相当于“让模型看了明天的评分再去预测昨天”,会造成数据泄露。正确做法是按时间戳切分,用每个用户最后 20% 的评分作为测试,前面的 80% 作为训练:

ratings["timestamp"] = pd.to_numeric(ratings["timestamp"]) ratings_sorted = ratings.sort_values(["userId", "timestamp"]) train_list, test_list = [], [] for uid, group in ratings_sorted.groupby("userId"): split_idx = int(len(group) * 0.8) train_list.append(group.iloc[:split_idx]) test_list.append(group.iloc[split_idx:]) train = pd.concat(train_list, ignore_index=True) test = pd.concat(test_list, ignore_index=True)

按用户分组、组内按时间排序、再按比例切,这是既能防止时间穿越又能保持每个人数据完整性的常见做法。但这里有个边界要处理:如果一个用户只有 1 条评分,int(1 * 0.8)得到 0,训练集为空。实际处理时我一般会设一个最低门槛,比如用户评分数大于等于 5 才参与切分,否则整组进训练集,让评估指标不至于因为极小样本抖动得离谱。这类边界条件在答辩时提一句,老师会认为你想过数据分布问题。

4. 手写 UserCF 与 ItemCF:从相似度矩阵到 Top-N 推荐

到这一步,数据已经变成评分矩阵和训练集/测试集,剩下的是把所有模块拼成完整流程。很多课程设计源码喜欢把推荐写成一个大函数,但我建议按“建相似度矩阵 → 预测评分 → 生成 Top-N → 评估”四段拆开,每一段都可独立测试。下面把 UserCF 和 ItemCF 的完整路径各走一遍。

4.1 UserCF 完整流程:相似度矩阵、预测、Top-N 一步不缺

构建用户相似度矩阵时,最朴素的做法是双层循环对每个用户对调一次pearson_similarity,这种 O(N²) 的代价对于 943 个用户尚可忍受,但别把相似度矩阵全量存成DataFrame后丢进内存不管。课程设计里更常见的做法是只保存上三角,或直接存成一个稀疏的字典结构。下面是配合 2.1 节相似度函数的完整训练与推荐流程:

def build_user_similarity(rating_matrix, threshold=2): users = rating_matrix.index sim_dict = {} for i, u in enumerate(users): for v in users[i+1:]: # 只看上三角,对称矩阵减半计算 s = pearson_similarity(u, v, rating_matrix) if s > 0: # 负相似度在 Top-N 里没有意义,直接滤掉 sim_dict[(u, v)] = s sim_dict[(v, u)] = s return sim_dict def recommend_top_n(user_id, rating_matrix, sim_dict, k=20, n=10, exclude_seen=True): scores = {} for movie in rating_matrix.columns: if exclude_seen and not pd.isna(rating_matrix.loc[user_id, movie]): continue scores[movie] = predict_rating(user_id, movie, rating_matrix, sim_dict, k) rec = pd.Series(scores).dropna().sort_values(ascending=False).head(n) return rec

这里有两个参数值得展开:threshold=2是最小共同评分电影数,调大它会让相似度更可信但矩阵更稀疏;exclude_seen=True表示推荐列表只包含用户没看过的电影,这是课程设计里最容易忘记的一步——不排除已看过的电影,推荐的 Top-N 里会混进用户早就打过分的老片,显得没有意义。注意我在recommend_top_n里对每个候选电影都调了一次predict_rating,这意味着它会扫描全部 1682 部电影,一次推荐的复杂度是 O(N×K),N 是电影数、K 是邻居数;这种粗暴方式在 100K 数据集上跑一次约几秒,能接受,但如果换成 1M 数据集就要用物品相似度的索引来加速,这就是 ItemCF 的优势。

4.2 ItemCF 完整流程:离线算好物品相似度,在线只查表

ItemCF 的构建和 UserCF 对称,同样先算相似度矩阵。不过物品对约 1682×1682,比用户对 943×943 大近三倍;更关键的是很多电影之间共同评分用户极少,大部分物品对的相似度是 0。因此课程设计里实现 ItemCF 时,直接用一个全量稠密矩阵存 1682×1682 的浮点数,大约 22MB,尚可接受;但再往上到 1M 数据集就必须用稀疏格式。

def build_item_similarity(rating_matrix, threshold=2): movie_ids = rating_matrix.columns sim_dict = {} for i, m1 in enumerate(movie_ids): for m2 in movie_ids[i+1:]: s = adjusted_cosine(m1, m2, rating_matrix) if s > 0: sim_dict[(m1, m2)] = s sim_dict[(m2, m1)] = s return sim_dict def item_based_predict(user_id, movie_id, rating_matrix, sim_dict, k=20): # 找到用户评分过的电影,用它们的评分和目标电影相似度做加权 user_rated = rating_matrix.loc[user_id].dropna() sim_sum, weighted_sum = 0.0, 0.0 for rated_movie, rating in user_rated.items(): if rated_movie == movie_id: continue s = sim_dict.get((movie_id, rated_movie), 0.0) if s > 0: weighted_sum += s * rating sim_sum += s return weighted_sum / sim_sum if sim_sum > 0 else float('nan')

ItemCF 里一个常见的优化是“相似度归一化”。比如《玩具总动员》和《狮子王》在数据里同时被很多人看过,相似度天然比某些冷门电影对高;直接把原始相似度当权重,会让热门电影霸榜。简单做法是对每个电影的相似度向量做最大值归一化:sim[i][j] = sim[i][j] / max(sim[i]),把所有相似度压缩到 0~1,这样每条推荐理由都是“跟你刚看过的 X 最相似的是什么”,而不是“跟全站最热的电影相似”。我在实际项目里会把归一化开关做进函数参数,默认开启,答辩时可以演示开/关两种推荐的差别。

4.3 评估指标:RMSE 和 Precision@N 要分开看

课程设计的评分预测能力用 RMSE(均方根误差)衡量,推荐列表质量用 Precision@N 衡量。RMSE 只关心数值预测准不准,Precision@N 关心排出来的前 N 个东西里用户真正会喜欢的比例。很多源码只打印 RMSE,答辩时被问“你这个推荐列表到底准不准”就答不上来。

def evaluate_rmse(predict_func, test_ratings): errors = [] for row in test_ratings.itertuples(): pred = predict_func(int(row.userId), int(row.movieId)) if pred is not None and not np.isnan(pred): errors.append((pred - row.rating) ** 2) return np.sqrt(np.mean(errors)) if errors else float('nan')

计算 Precision@N 时,通常先把测试集的评分按阈值(比如 ≥4)二值化,把评分≥4 的电影视为用户“真正喜欢的”,然后在你推荐的前 N 部电影里数命中的比例:

def precision_at_n(rec_list, test_ratings, n=10, threshold=4.0): rec_list = rec_list.head(n) if len(rec_list) == 0: return 0.0 liked = set(test_ratings.loc[ test_ratings["rating"] >= threshold, "movieId" ]) hits = sum(1 for movie_id in rec_list.index if movie_id in liked) return hits / len(rec_list)

这里有个必须说明的约定:用“测试集里评分≥4”当作真实喜欢的金标准,本身就是个近似。因为用户没看过的电影不代表不喜欢,只看过且打高分的有限几部,所以 Precision@N 在离线评估里往往偏低。课程设计写到这一步,你能解释清楚这个近似,就已经超过大部分只会跑代码的同学了。

5. 避坑清单:课程设计里最常见的 5 个翻车现场

写推荐系统课程设计,代码跑通不是终点,跑出来的结果合理才是。这一章把自己踩过和帮别人改过的常见问题整理成五条记录,每一条都按“现象 → 原因 → 解决”展开,值得投影到答辩 PPT 上。

5.1 预测分数全一样,推荐列表像复制粘贴

预测分数全一样,推荐列表像复制粘贴的现象,通常是评分矩阵里的缺失值被填成了 0,导致皮尔逊或余弦相似度把所有用户都“拉平”了。两个用户没看过同一堆电影,却因为共同的 0 被算成相似;预测时又把这些 0 当作低分加权,最后每个人的预测分都收敛到同一个值。

解决:把填充 0 改成保留 NaN,所有相似度函数和预测函数里都用notna()判断有效评分。如果为了构造稀疏矩阵必须填充 0,那就在填充前先用.notna()把有效评分掩码单独存好,后续所有计算都带上掩码。我见过一份源码在pivot_table之后加了句df.fillna(0),整整改了一晚上才找到这一行。

5.2 数据量一大,内存直接爆炸

现象很直白:跑 100K 很流畅,换成 1M 开始卡死。原因通常有两个,一是pivot_table生成的稠密 DataFrame 太大,二是相似度矩阵抱着DataFrame不放。UserCF 的 6040×6040 矩阵存 float64 约 292MB,ItemCF 的 3706×3706 也有 110MB,看起来不大,但如果程序里同时保留了评分矩阵、相似度矩阵、多个临时副本,很容易翻倍到 GB 级。

解决:评分矩阵用scipy.sparse.csr_matrix存;相似度矩阵用字典只存非零相似度,或者用scipy.sparse.lil_matrix构建后再转csr。另外注意 pandas 的groupby会保留副本,用完之后del train_list, test_list及时释放。调内存这件事有时候真讲玄学,但大多数情况下是没释放临时对象导致的。

5.3 皮尔逊相似度大面积出现 NaN

这两个用户所有共同评分完全相同,比如都只给同一部电影打了 5 分,皮尔逊公式的分母为 0,np.corrcoef返回 NaN。NaN 一旦进入加权和,整个预测结果都会被污染,程序还不一定报错,最后推荐列表里全是空值。

解决:在相似度函数里,分母接近 0 时直接返回 0.0;更稳妥的做法是在公共评分判断上增加common.sum() < 2就跳过。也可以把np.corrcoef替换成手动实现,并加一个极小值epsilon=1e-8。写完后跑一个小测试:随便抽 100 个用户对,打印相似度分布,看到 NaN 就说明你的过滤条件没兜住。

5.4 随机切分训练测试集,评估结果好得离谱

如果只用train_test_split随机打乱,用户在过去看的电影会出现在测试集,而模型在训练集里已经见过同一用户的评分习惯,预测误差当然虚低。结果就是你看着 RMSE 0.85 沾沾自喜,答辩时老师一句“你这切分有数据泄露”就哑口无言,没有后悔药可吃。

解决:按 3.3 节的方式按时间戳分组切分。如果数据集没有时间戳,也至少要按 userId 分组后随机选一部电影进测试集,并且同一个 userId 不能同时出现在训练和测试里。这是推荐系统评估里最底线的一条原则,也是答辩老师最爱问的点。

5.5 读入 CSV 时中文电影名乱码

如果使用老式的u.item文件,它的编码通常不是 UTF-8,读进来全是乱码,直接导致内容推荐匹配失败。最初的坑在于解码用的编码选错,utf-8直接报UnicodeDecodeError,换成gbk也报错。

解决:拿到的老式数据集先看第一行,常见做法是encoding='latin-1'或encoding='iso-8859-1';movies.csv这类新格式一般是 UTF-8。另外,pandas 2.x 里read_csv对编码错误比较严格,可以在加载之前用二进制方式打开文件看一眼前几行的字节,再决定编码,不要一个个试到心态崩。顺带一提,如果要在 Windows 终端里打印中文电影名,可能还要把sys.stdout.reconfigure(encoding='utf-8')加上,否则控制台先花掉。

6. 把推荐系统接到 Flask 上:用一百行代码换来答辩加分项

最后这个技巧不复杂,却经常能让课程设计的完成度上一个台阶:把离线推荐逻辑包成一个 Flask 接口,前端拿 JSON 就能渲染。很多课程设计的对接方会要求“纯后端即可”,那你返回 JSON 就比在终端里打印推荐列表更有说服力。

6.1 Flask 暴露 /recommend 接口

from flask import Flask, jsonify, request app = Flask(__name__) @app.route("/recommend", methods=["GET"]) def recommend(): user_id = request.args.get("user_id", default=1, type=int) top_n = request.args.get("top_n", default=10, type=int) rec = recommend_top_n(user_id, item_matrix, sim_dict, n=top_n) return jsonify({"user_id": user_id, "recommendations": rec.index.tolist()})

注意type=int让 Flask 自动做类型转换,避免前端传?user_id=abc时让函数崩溃。把rating_matrix和sim_dict设计成模块级全局变量,在if __name__ == "__main__"之前加载一次,避免每次请求都重新构建相似度矩阵。

6.2 给推荐结果加一句“人话”

可解释性是加分项。基于 ItemCF,推荐《星际穿越》可以说“因为你给《盗梦空间》打了高分”,代码里只需要保存相似度最高的那部已看电影 ID:

def explain_rec(movie_id, user_id, sim_dict, rating_matrix): seen = rating_matrix.loc[user_id].dropna() if seen.empty: return "新用户暂无评分,先推荐热门高分电影" best_movie, best_sim = None, 0.0 for m, rating in seen.items(): if m == movie_id: continue s = sim_dict.get((movie_id, m), 0.0) if s > best_sim: best_sim, best_movie = s, m return movies.loc[best_movie, "clean_title"] if best_movie is not None else ""

6.3 验证:把推荐列表和评分预测分开看

最后给一个验证习惯:不要只跑一遍评估指标就结束。把预测分最高的 20 部电影随机挑 5 部打印出来人工核对,如果平时看科幻片见多,推荐列表里却全是爱情片,多半是数据或相似度计算出了问题;合理的列表至少有一半和直觉一致。我习惯在推荐列表里顺手把 explain 的结果也返回,这样前端展示时每条推荐都带着理由,答辩时演示效果会好很多。希望帮到你。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/11/1 21:33:15

交换机和路由器基本配置:VLAN、NAT、ACL与排错实战

简介&#xff1a;这是一份面向网络技术初学者与高校计算机网络课程实验的PDF文档&#xff0c;聚焦交换机和路由器的基础配置。内容从实验目的出发&#xff0c;系统讲解带外管理&#xff08;Console口与超级终端&#xff09;与带内管理&#xff08;Telnet、Web、TFTP、SNMP&…

作者头像 李华
网站建设 2026/10/29 18:47:59

Umi-OCR 离线OCR实测:500页扫描书一晚上变成可搜索文档

Umi-OCR 离线OCR实测&#xff1a;500页扫描书一晚上变成可搜索文档 【免费下载链接】Umi-OCR OCR software, free and offline. 开源、免费的离线OCR软件。支持截屏/批量导入图片&#xff0c;PDF文档识别&#xff0c;排除水印/页眉页脚&#xff0c;扫描/生成二维码。内置多国语…

作者头像 李华