ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基于协同过滤的电影推荐系统毕设实战:Django+MySQL全链路实现

基于协同过滤的电影推荐系统毕设实战:Django+MySQL全链路实现 简介本资源是一套完整的基于协同过滤算法的电影推荐系统毕业设计实现面向Python与Web开发初学者及高校计算机专业学生解决个性化推荐系统从理论到工程落地的学习与实践需求。资源包共83个文件包含28个核心Python源码如用户/物品协同过滤、爬虫、数据填充脚本、16个HTML前端模板、4个CSV格式的MovieLens数据集文件、1个含完整论文的PDF文档、1个SQLite数据库文件及Bootstrap3风格的静态资源整体压缩包仅8.41MB轻量易部署。已有10607人学习下载热度高且反馈良好。读者可直接运行Django 2.2.1 Python 3.7环境复现在线预览站点movie.colaplusice.com功能配套技术文档详尽涵盖部署步骤、算法原理说明、数据库设计与爬虫逻辑并提供可执行的populate脚本、缓存优化模块及多版本编译字节码pyc便于理解工程结构与调试排错。1. 为什么毕业设计选“基于协同过滤的电影推荐系统”——它不是炫技而是验证你能否把算法、Web框架和数据库真正串成一条能跑通的流水线如果你正在赶毕设、被导师催着交“有业务逻辑、有交互界面、有数据支撑”的系统那这个标题就是个务实的选择它不依赖复杂模型比如BERT或图神经网络却能完整覆盖数据建模→算法实现→后端服务→前端展示→数据库持久化的全链路。协同过滤本身是推荐系统里最经典、最容易解释、调试最直观的算法——用户-物品评分矩阵一画出来冷启动怎么破、稀疏性怎么填、相似度怎么算全是可触摸的问题。用 Python3.7 Django2.2.1 MySQL 组合不是为了追新Django 2.2.1 是 2019 年 LTS 版本至今在大量教育项目和中小后台中稳定服役而是因为它的 ORM 对 MySQL 支持成熟、模板渲染够快、admin 后台开箱即用能让学生把精力聚焦在“推荐逻辑怎么嵌进 Web 请求里”这个核心问题上而不是卡在异步调度、微服务拆分或容器编排里。这不是一个“高大上”的项目但它是一块试金石你能把矩阵分解写对吗能用 Django 的 QuerySet 正确拼出用户历史行为吗能处理好 MySQL 中user_id和movie_id的联合索引以支撑实时相似度查询吗——这些才是企业级开发里天天要面对的“脏活”。别小看它毕设答辩时评委问“你这个推荐结果是怎么从数据库查出来的”你能指着views.py里一行UserRating.objects.filter(useruser).select_related(movie)讲清楚执行计划比堆十个花哨的前端动画更有说服力。2. 从零搭起骨架Python3.7 环境隔离 Django2.2.1 初始化 MySQL 基础建模2.1 创建独立虚拟环境并安装指定版本组合拒绝 pip install 最新版协同过滤推荐系统对版本兼容性极其敏感。Django 2.2.1 要求 Python ≥ 3.5 且 3.8MySQL 驱动必须用mysqlclient1.4.6这是唯一官方认证支持 Django 2.2.x 的版本。直接pip install django会装最新版导致manage.py runserver报ModuleNotFoundError: No module named django.core.management.base——这是血泪经验。# 1. 确认系统已装 Python3.7Ubuntu/Debian 下 $ python3.7 --version Python 3.7.10 # 2. 创建虚拟环境关键指定 Python 解释器路径 $ python3.7 -m venv ./venv_cf $ source ./venv_cf/bin/activate # Linux/macOS # 或 venv_cf\Scripts\activate.bat Windows # 3. 升级 pip 并安装指定版本顺序不能错 (venv_cf) $ pip install --upgrade pip (venv_cf) $ pip install Django2.2.1 mysqlclient1.4.6 numpy1.16.6 scipy1.2.3 pandas0.24.2提示numpy1.16.6和scipy1.2.3是为scikit-learn0.20.3Django 2.2.1 生态中最稳定的机器学习库配套的版本。高版本 numpy 会导致scipy.spatial.distance.pdist在计算余弦相似度时返回 NaN——这个坑我在第三章会细说。2.2 Django 项目初始化与 MySQL 数据库配置避开 Django 默认 SQLiteDjango 默认用 SQLite但协同过滤需要频繁 JOIN 用户-评分-电影三张表SQLite 不支持 FULLTEXT 索引且并发写入性能差。必须切到 MySQL并手动创建数据库不要让 Django 自动建库# 1. 登录 MySQL假设 root 密码为 123456 $ mysql -u root -p Enter password: 123456 # 2. 创建专用数据库字符集必须为 utf8mb4否则电影名含 emoji 会报错 mysql CREATE DATABASE movie_recommend DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; mysql CREATE USER cf_userlocalhost IDENTIFIED BY cf_pass_2024; mysql GRANT ALL PRIVILEGES ON movie_recommend.* TO cf_userlocalhost; mysql FLUSH PRIVILEGES; mysql EXIT;然后修改settings.py中的DATABASES配置# settings.py DATABASES { default: { ENGINE: django.db.backends.mysql, NAME: movie_recommend, USER: cf_user, PASSWORD: cf_pass_2024, HOST: 127.0.0.1, # 必须写 127.0.0.1不能写 localhostMySQL socket 连接问题 PORT: 3306, OPTIONS: { charset: utf8mb4, init_command: SET sql_modeSTRICT_TRANS_TABLES, }, TEST: { CHARSET: utf8mb4, COLLATION: utf8mb4_unicode_ci, } } }参数说明HOST: 127.0.0.1是关键。若写localhostMySQL 客户端会尝试 Unix socket 连接而mysqlclient在某些 Linux 发行版如 CentOS 7下默认找不到/var/lib/mysql/mysql.sock报错error 2002 (HY000): Cant connect to local MySQL server through socket /tmp/mysql.sock。强制走 TCP/IP 就绕过此问题。init_command设置 SQL 模式避免插入空字符串时触发Data too long for column错误——电影名字段常设VARCHAR(255)但用户输入可能超长严格模式会直接拒绝而非截断。2.3 设计三张核心数据表User、Movie、Rating用 Django ORM 建模协同过滤的底层是用户-物品评分矩阵对应三张表。注意Rating表的主键必须是(user_id, movie_id)联合主键而非自增 ID——这是保证数据唯一性、加速相似度计算的前提。# models.py from django.db import models class User(models.Model): user_id models.PositiveIntegerField(uniqueTrue, db_indexTrue) # 外部数据源 ID如 MovieLens 的 user_id username models.CharField(max_length50, blankTrue) email models.EmailField(blankTrue) def __str__(self): return fUser-{self.user_id} class Movie(models.Model): movie_id models.PositiveIntegerField(uniqueTrue, db_indexTrue) title models.CharField(max_length255) genres models.CharField(max_length255, blankTrue) # 逗号分隔如 Action|Comedy year models.CharField(max_length4, blankTrue) def __str__(self): return self.title class Rating(models.Model): user models.ForeignKey(User, on_deletemodels.CASCADE, db_indexTrue) movie models.ForeignKey(Movie, on_deletemodels.CASCADE, db_indexTrue) rating models.FloatField() # 1.0 ~ 5.0 timestamp models.DateTimeField(auto_now_addTrue) class Meta: unique_together (user, movie) # 强制联合唯一等价于 (user_id, movie_id) 主键 indexes [ models.Index(fields[user, movie]), models.Index(fields[movie, user]), # 双向索引支撑用户相似度 物品相似度查询 ]执行迁移(venv_cf) $ python manage.py makemigrations (venv_cf) $ python manage.py migrate为什么db_indexTrue要加在 ForeignKey 上协同过滤的核心操作是给定一个用户 A快速查出他评过分的所有电影Rating.objects.filter(usera)再查出所有也评过分这些电影的其他用户Rating.objects.filter(movie__inmovie_ids)。没有索引时这两步都是全表扫描10 万条评分数据下响应时间 3s加索引后压测稳定在 80ms 内。这是性能分水岭。3. 实现协同过滤核心用户协同 vs 物品协同如何用纯 Python 写出可调试、可复现的推荐逻辑3.1 构建用户-物品评分矩阵用 Pandas 读取 MovieLens 数据并清洗毕业设计最常用数据集是 MovieLens 100Kml-100k.zip包含 10 万条评分结构清晰。解压后得到u.data用户ID、电影ID、评分、时间戳需转换为 Django 可批量导入的格式。# utils/matrix_builder.py import pandas as pd from django.core.management.base import BaseCommand from myapp.models import User, Movie, Rating def build_rating_matrix(): # 1. 读取 u.datatab 分隔无 header df pd.read_csv(ml-100k/u.data, sep\t, names[user_id, movie_id, rating, timestamp]) # 2. 清洗过滤掉评分不在 [1,5] 区间的异常值MovieLens 有少量 0 分 df df[(df[rating] 1.0) (df[rating] 5.0)] # 3. 去重同一用户对同一电影多次评分只保留最新按 timestamp df df.sort_values(timestamp).drop_duplicates([user_id, movie_id], keeplast) # 4. 批量创建 Django Model 实例避免逐条 save 的 N1 问题 users {uid: User(user_iduid) for uid in df[user_id].unique()} movies {mid: Movie(movie_idmid) for mid in df[movie_id].unique()} # 批量创建 User/Movie先存再关联 Rating User.objects.bulk_create(users.values(), ignore_conflictsTrue) Movie.objects.bulk_create(movies.values(), ignore_conflictsTrue) # 构建 Rating 列表 ratings [] for _, row in df.iterrows(): ratings.append( Rating( user_idrow[user_id], movie_idrow[movie_id], ratingrow[rating] ) ) # 批量插入 Rating关键ignore_conflictsTrue 防止重复插入报错 Rating.objects.bulk_create(ratings, ignore_conflictsTrue) print(f成功导入 {len(ratings)} 条评分数据)参数说明ignore_conflictsTrue是 Django 2.2.1 新增参数对应 MySQL 的INSERT IGNORE。若数据已存在如多次运行脚本不会报IntegrityError而是静默跳过——这是毕设调试阶段的后悔药。drop_duplicates(..., keeplast)保证同一用户对同一电影只保留最后一次评分符合真实场景用户改评。3.2 用户协同过滤User-Based CF计算用户相似度并生成 Top-N 推荐用户协同过滤的核心是找到与目标用户相似的 K 个用户聚合他们喜欢但目标用户没看过的电影。相似度用余弦相似度Cosine Similarity公式为$$ \text{sim}(u,v) \frac{\sum_{i \in I_{uv}} r_{ui} \cdot r_{vi}}{\sqrt{\sum_{i \in I_u} r_{ui}^2} \cdot \sqrt{\sum_{i \in I_v} r_{vi}^2}} $$其中 $I_{uv}$ 是用户 u 和 v 共同评过分的电影集合。# recommender/user_cf.py import numpy as np from scipy.spatial.distance import cosine from django.db.models import Q from myapp.models import User, Movie, Rating def get_user_similarities(target_user_id, k20): 计算与 target_user_id 最相似的 k 个用户基于共同评分电影的余弦相似度 返回: [(similar_user_id, similarity_score), ...] 按相似度降序排列 # 1. 获取 target_user 评过分的所有电影及评分 target_ratings list(Rating.objects.filter(user_idtarget_user_id).values(movie_id, rating)) if not target_ratings: return [] target_movie_ids [r[movie_id] for r in target_ratings] target_ratings_dict {r[movie_id]: r[rating] for r in target_ratings} # 2. 查找所有也评过分这些电影的其他用户排除 target_user 自身 other_users Rating.objects.filter( movie_id__intarget_movie_ids ).exclude(user_idtarget_user_id).values(user_id).distinct() similarities [] for other_user in other_users: other_user_id other_user[user_id] # 获取 other_user 对 target_movie_ids 中电影的评分 other_ratings Rating.objects.filter( user_idother_user_id, movie_id__intarget_movie_ids ).values(movie_id, rating) if len(other_ratings) 5: # 共同评分电影少于 5 个相似度不可靠跳过 continue # 构建向量按 target_movie_ids 顺序排列评分缺失则补 0 target_vec [target_ratings_dict.get(mid, 0) for mid in target_movie_ids] other_vec [next((r[rating] for r in other_ratings if r[movie_id] mid), 0) for mid in target_movie_ids] # 计算余弦相似度scipy.cosine 返回距离1 - distance 得相似度 try: dist cosine(target_vec, other_vec) sim 1 - dist if dist 1 else 0 # 防止浮点误差导致负数 except: sim 0 if sim 0.1: # 过滤低相似度用户 similarities.append((other_user_id, sim)) # 按相似度降序排列取 top-k similarities.sort(keylambda x: x[1], reverseTrue) return similarities[:k] def recommend_for_user(user_id, n10): 为 user_id 生成 top-n 推荐电影 策略加权平均预测评分 Σ(sim(u,v) * r(v,i)) / Σ|sim(u,v)| similar_users get_user_similarities(user_id, k30) if not similar_users: return [] # 获取所有相似用户评过分的电影排除 user_id 已评过的 user_rated_movies set(Rating.objects.filter(user_iduser_id).values_list(movie_id, flatTrue)) # 统计每部电影的加权评分 movie_scores {} sim_sum 0 for similar_user_id, sim_score in similar_users: # 获取该相似用户评过分的电影且目标用户没评过 rated_by_similar Rating.objects.filter( user_idsimilar_user_id ).exclude(movie_id__inuser_rated_movies).values(movie_id, rating) for item in rated_by_similar: movie_id item[movie_id] rating_val item[rating] if movie_id not in movie_scores: movie_scores[movie_id] 0 movie_scores[movie_id] sim_score * rating_val sim_sum sim_score # 归一化并排序 if sim_sum 0: return [] ranked_movies sorted( [(mid, score / sim_sum) for mid, score in movie_scores.items()], keylambda x: x[1], reverseTrue ) # 转为 Movie 对象带标题信息 movie_ids [mid for mid, _ in ranked_movies[:n]] movies Movie.objects.filter(movie_id__inmovie_ids).order_by( models.Case(*[models.When(movie_idmid, thenpos) for pos, mid in enumerate(movie_ids)]) ) return list(movies)关键细节cosine()函数要求向量非零所以用if dist 1 else 0防止nan。这是scipy1.2.3的已知 bug在scipy1.3.0中修复但 Django 2.2.1 不兼容高版本 scipy。models.Case(...)保证返回的 Movie 对象顺序与ranked_movies一致——Django ORM 默认不保序必须显式指定。3.3 物品协同过滤Item-Based CF为什么它比用户协同更适合毕业设计物品协同过滤Item-Based CF计算电影之间的相似度如《阿凡达》和《泰坦尼克号》都获高分然后为用户推荐与其历史喜好电影相似的新电影。它比用户协同更稳定电影属性变化慢用户兴趣易变、冷启动更友好新用户只要评一部电影就能推荐、且计算可离线预热——这三点让它成为毕设落地首选。# recommender/item_cf.py from collections import defaultdict from django.db.models import Count, Avg def build_item_similarity_cache(min_common_users5, threshold0.3): 离线构建电影相似度缓存表推荐在 manage.py command 中每日运行 存储格式{movie_id_a: {movie_id_b: similarity_score, ...}, ...} # 1. 找出所有被至少 min_common_users 人共同评分的电影对 # 使用 raw SQL 提升性能Django ORM 多表 JOIN 效率低 from django.db import connection with connection.cursor() as cursor: cursor.execute( SELECT r1.movie_id as m1, r2.movie_id as m2, COUNT(*) as common_users FROM myapp_rating r1 INNER JOIN myapp_rating r2 ON r1.user_id r2.user_id AND r1.movie_id r2.movie_id GROUP BY r1.movie_id, r2.movie_id HAVING COUNT(*) %s , [min_common_users]) pairs cursor.fetchall() # 2. 对每个电影对计算余弦相似度 cache defaultdict(dict) for m1, m2, _ in pairs: # 获取共同评分用户对 (user_id, r1.rating, r2.rating) ratings Rating.objects.filter( movie_id__in[m1, m2] ).values(user_id, movie_id, rating).order_by(user_id, movie_id) # 转为字典{user_id: {m1: r1, m2: r2}} user_ratings defaultdict(dict) for r in ratings: user_ratings[r[user_id]][r[movie_id]] r[rating] # 构建向量只取共同用户 vec1, vec2 [], [] for uid, scores in user_ratings.items(): if m1 in scores and m2 in scores: vec1.append(scores[m1]) vec2.append(scores[m2]) if len(vec1) 5: continue try: from scipy.spatial.distance import cosine dist cosine(vec1, vec2) sim 1 - dist if dist 1 else 0 if sim threshold: cache[m1][m2] sim cache[m2][m1] sim # 对称 except: continue return dict(cache) def recommend_by_item_history(user_id, n10): 基于用户历史评分电影推荐相似电影 # 获取用户评过分的电影按评分降序优先推荐高分电影的相似项 user_ratings Rating.objects.filter(user_iduser_id).order_by(-rating) if not user_ratings: return [] # 加载预计算的相似度缓存实际项目应存 Redis毕设可暂存内存或文件 cache build_item_similarity_cache() # 毕设可每次调用重建或存 JSON 文件 # 对每个历史电影获取其 top-5 相似电影 candidate_movies {} for ur in user_ratings[:5]: # 只取前 5 部高分电影避免长尾噪声 movie_id ur.movie_id if movie_id not in cache: continue for similar_mid, sim_score in cache[movie_id].items(): # 权重 用户对该电影的评分 × 相似度 weight ur.rating * sim_score if similar_mid not in candidate_movies: candidate_movies[similar_mid] 0 candidate_movies[similar_mid] weight # 排序并去重用户可能已评过 user_rated set(Rating.objects.filter(user_iduser_id).values_list(movie_id, flatTrue)) ranked sorted( [(mid, score) for mid, score in candidate_movies.items() if mid not in user_rated], keylambda x: x[1], reverseTrue ) movie_ids [mid for mid, _ in ranked[:n]] return list(Movie.objects.filter(movie_id__inmovie_ids))为什么 Item-Based 更适合毕设可解释性强推荐理由明确“因为你喜欢《盗梦空间》所以推荐《星际穿越》”答辩时容易讲清楚。性能可控相似度可离线计算并缓存线上请求只需查缓存 简单加权响应时间稳定 200ms。冷启动友好新用户注册后只要他评了 1 部电影立刻能推荐——而 User-Based 至少需要 5 部才能找到可靠邻居。4. 避坑Django MySQL 协同过滤的 5 个高频翻车点与血泪解决方案4.1 现象mysqlclient安装失败报Command python setup.py egg_info failed原因缺少 MySQL 开发头文件mysql_config命令未找到。Ubuntu/Debian 系统需先装libmysqlclient-devCentOS/RHEL 需装mysql-devel。解决# Ubuntu/Debian $ sudo apt-get update sudo apt-get install libmysqlclient-dev python3.7-dev # CentOS/RHEL $ sudo yum install mysql-devel python37-devel # 再重试 pip install mysqlclient1.4.64.2 现象Django admin 中新增 Rating 时user_id和movie_id下拉列表为空原因Django admin 默认对 ForeignKey 字段使用Select控件但若User和Movie表数据量 100 条页面加载极慢甚至超时Django 会自动禁用下拉显示为空。解决在admin.py中禁用raw_id_fields或改用autocomplete_fieldsDjango 2.2.1 支持# admin.py from django.contrib import admin from .models import Rating admin.register(Rating) class RatingAdmin(admin.ModelAdmin): list_display (user, movie, rating, timestamp) list_filter (user, movie) search_fields (user__username, movie__title) # 启用搜索 autocomplete_fields [user, movie] # 关键替代下拉框 # settings.py 中添加 INSTALLED_APPS [django.contrib.postgres] # autocomplete_fields 依赖 postgres 模块但 MySQL 也能用注意autocomplete_fields要求对应 Model 的search_fields已定义否则报错。4.3 现象协同过滤推荐结果全是 NaN 或 0cosine()返回nan原因scipy.spatial.distance.cosine在向量全为 0 时返回nan如两个用户只共同评了一部电影且评分相同向量差为 0。解决在计算前做向量归一化检查# 替换原 cosine 计算部分 def safe_cosine_similarity(vec1, vec2): norm1 np.linalg.norm(vec1) norm2 np.linalg.norm(vec2) if norm1 0 or norm2 0: return 0.0 return 1 - cosine(vec1, vec2)4.4 现象MySQL 报错ERROR 1071 (42000): Specified key was too long原因Django 自动生成的索引名过长如myapp_rating_user_id_movie_id_...MySQL 5.6 默认innodb_large_prefixOFF单索引长度上限 767 字节。utf8mb4字符占 4 字节VARCHAR(255)字段索引就超限。解决修改 MySQL 配置/etc/mysql/my.cnf[mysqld] innodb_file_formatBarracuda innodb_file_per_tableON innodb_large_prefixON # 并重启 MySQL $ sudo systemctl restart mysql提示修改后需对现有表执行ALTER TABLE myapp_rating ROW_FORMATDYNAMIC;。4.5 现象Rating.objects.filter(user_idxxx).select_related(movie)查询极慢EXPLAIN 显示type: ALL全表扫描原因缺少(user_id, movie_id)联合索引或索引未被正确使用。解决确认Rating模型中已定义indexes [models.Index(fields[user, movie])]手动在 MySQL 中创建索引确保生效ALTER TABLE myapp_rating ADD INDEX idx_user_movie (user_id, movie_id);用EXPLAIN SELECT * FROM myapp_rating WHERE user_id123;验证key列显示idx_user_movie。5. 让推荐系统“活”起来Django 视图层集成、AJAX 实时推荐与可验证的评估指标5.1 编写推荐视图RESTful 风格接口支持用户登录态与匿名推荐协同过滤必须区分登录用户用其历史行为和未登录用户用热门或随机推荐。Django 的request.user是核心判断依据。# views.py from django.shortcuts import render from django.http import JsonResponse from django.contrib.auth.decorators import login_required from django.views.decorators.csrf import csrf_exempt from .recommender.item_cf import recommend_by_item_history from .recommender.user_cf import recommend_for_user from .models import Movie, Rating def home(request): 首页展示热门电影 新用户引导 # 热门电影 评分人数最多 Top 10 popular_movies Movie.objects.annotate( rating_countCount(rating) ).order_by(-rating_count)[:10] context {popular_movies: popular_movies} return render(request, home.html, context) login_required def user_recommendations(request): 登录用户专属推荐页 user_id request.user.id # 注意此处 user_id 是 Django User 表的 id非 MovieLens 的 user_id # 毕设中需建立 Django User 与 MovieLens user_id 的映射表此处简化为直接用 id movies recommend_by_item_history(user_id, n12) context {movies: movies, user: request.user} return render(request, recommendations.html, context) csrf_exempt def api_recommend(request): AJAX 接口前端传 user_id后端返回 JSON 推荐列表 if request.method ! POST: return JsonResponse({error: Method not allowed}, status405) try: data json.loads(request.body) user_id data.get(user_id) if not user_id: return JsonResponse({error: Missing user_id}, status400) # 调用 Item-Based 推荐更快更稳 movies recommend_by_item_history(user_id, n8) # 序列化为 JSON 可序列化格式 result [ { movie_id: m.movie_id, title: m.title, genres: m.genres, year: m.year } for m in movies ] return JsonResponse({movies: result}) except Exception as e: return JsonResponse({error: str(e)}, status500)5.2 前端 AJAX 调用用 Fetch API 实现无刷新推荐更新在recommendations.html中用原生 JavaScript 调用后端 API避免整页刷新!-- recommendations.html -- div idrecommendation-list h3为你推荐/h3 div idloading加载中.../div div idmovie-grid classgrid/div /div script // 页面加载完成后发起推荐请求 document.addEventListener(DOMContentLoaded, function() { const userId {{ user.id }}; // Django 模板变量注入 fetch(/api/recommend/, { method: POST, headers: { Content-Type: application/json, X-CSRFToken: getCookie(csrftoken) // Django CSRF token }, body: JSON.stringify({user_id: userId}) }) .then(response response.json()) .then(data { const grid document.getElementById(movie-grid); grid.innerHTML ; if (data.movies data.movies.length 0) { data.movies.forEach(movie { const card document.createElement(div); card.className movie-card; card.innerHTML h4${movie.title} (${movie.year})/h4 p${movie.genres}/p button onclickrateMovie(${movie.movie_id})评分/button ; grid.appendChild(card); }); } else { grid.innerHTML p暂无推荐试试给几部电影打分吧/p; } }) .catch(error { console.error(推荐请求失败:, error); document.getElementById(loading).textContent 推荐加载失败请刷新页面; }); }); // CSRF token 获取函数Django 标准写法 function getCookie(name) { let cookieValue null; if (document.cookie document.cookie ! ) { const cookies document.cookie.split(;); for (let i 0; i cookies.length; i) { const cookie cookies[i].trim(); if (cookie.substring(0, name.length 1) (name )) { cookieValue decodeURIComponent(cookie.substring(name.length 1)); break; } } } return cookieValue; } /script5.3 推荐效果可验证用 MovieLens 测试集计算 Recall10 和 MAP毕设答辩最怕被问“你的推荐准不准”。必须提供量化指标。MovieLens 100K 自带u1.test和u1.base训练/测试划分我们用 Recall10前 10 名推荐中命中测试集的比例和 Mean Average PrecisionMAP来评估。# evaluation/evaluate.py import numpy as np from sklearn.metrics import recall_score from django.db.models import Q def evaluate_recommendation(model_func, test_fileml-100k/u1.test, top_n10): model_func: 接收 user_id 返回 [Movie] 列表的函数如 recommend_by_item_history # 1. 读取测试集user_id, movie_id, rating, timestamp test_df pd.read_csv(test_file, sep\t, names[user_id, movie_id, rating, timestamp]) # 2. 对每个测试用户生成推荐并计算 Recall10 recalls [] aps [] # Average Precision per user for user_id in test_df[user_id].unique(): # 获取该用户在测试集中评过分的电影ground truth true_movies set(test_df[test_df[user_id] user_id][movie_id].tolist()) if len(true_movies) 0: continue # 生成推荐 try: recommended model_func(user_id, ntop_n) pred_movies set([m.movie_id for m in recommended]) except: pred_movies set() # Recall10 |true ∩ pred| / |true| hit_count len(true_movies pred_movies) recall hit_count / len(true_movies) if len(true_movies) 0 else 0 recalls.append(recall) # AP Σ(precisionk * rel_k) / |true|rel_k1 if k-th item in pred is in true if pred_movies: ap 0.0 hits 0 for i, mid in enumerate([m.movie_id for m in recommended]): if mid in true_movies: hits 1 precision_at_k hits / (i 1) ap precision_at_k ap / len(true_movies) aps.append(ap) mean_recall np.mean(recalls) if recalls else 0 mean_ap np.mean(aps) if aps else 0 map_score mean_ap print(fRecall{top_n}: {mean_recall:.4f}) print p a hrefhttps://download.csdn.net/download/fanjialiang2401/12363909 stylecolor:#ec7500;font-size:14px; 本文还有配套的精品资源点击获取 /a img altmenu-r.4af5f7ec.gif srchttps://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif stylewidth:16px;margin-left:4px;vertical-align:text-bottom;cursor:text; /p
返回列表