十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

Django音乐推荐系统开发:协同过滤算法与毕业设计实战

Django音乐推荐系统开发:协同过滤算法与毕业设计实战 简介推荐系统是互联网产品中广泛应用的智能技术其核心目标是从海量数据中挖掘用户潜在兴趣实现个性化内容分发。在众多实现路径中协同过滤算法凭借其直观的原理和良好的效果成为经典方案它通过分析用户与物品的交互行为计算相似度并生成预测评分。在实际工程落地时选择Django这类成熟Web框架能够显著提升开发效率其自带的ORM、Admin后台和MVT架构非常适合构建包含推荐引擎的完整业务系统。音乐推荐播放器正是这一技术组合的典型应用场景它融合了Web开发、数据库设计、算法工程与前后端交互既不过度复杂又具备充足的实践深度。本文基于Python与Django技术栈从系统设计到协同过滤代码实现全面拆解如何构建一个可演示、可答辩的音乐推荐项目为毕业设计提供完整参考。 做毕业设计选方向的时候音乐推荐播放器算是Python Django技术栈里非常经典的一个选题。它不像纯粹的管理系统那样只写CRUD也不像算法研究那样需要深厚的数学底子而是恰到好处地把Web开发、数据库设计、推荐算法、前后端交互串在了一起无论是本科还是专科毕业设计这个选题的体量都比较合适。我拿到这个标题的时候第一反应是这套东西“能跑通推荐”和“能写进论文里”是两回事。单纯把协同过滤代码跑起来很简单但要让它成为一个完整的、可演示的、能在答辩时讲清楚的项目需要把算法细节、系统设计、数据流、效果优化都梳理明白。这篇文章就按我实际做过类似项目的经验从整体设计到核心代码再到常见坑位一层层拆开讲。1. 项目整体设计与思路拆解1.1 这个项目的本质推荐系统不是功能是业务逻辑很多同学拿到这个题目第一反应是“我要写一个播放器”然后重点就放在了前端播放界面、歌词滚动、播放列表这些地方。但实际上毕业设计题目里的核心关键词是“协同过滤算法”播放器只是算法的载体。导师和答辩老师真正关心的是你怎么把推荐算法落地到Web系统里怎么验证它有效而不是你的播放器UI做得有多花哨。这个项目的本质是一个以歌曲推荐为核心业务的Django Web应用。用户对歌曲产生行为收藏、播放、评分系统基于协同过滤算法挖掘用户和歌曲之间的潜在关联把用户可能喜欢的歌曲推送到前端。推荐流程应该是一个闭环用户行为 - 数据采集 - 离线计算 - 结果存储 - 在线推荐 - 用户反馈 - 更新行为数据。这里有一点特别重要推荐系统最重要的是数据闭环写代码只是其中一环。如果没有用户行为数据积累协同过滤算法就是空中楼阁。所以做这个项目第一步不是写代码而是想清楚数据怎么来、怎么存、怎么用。1.2 技术选型为什么是Django而不是Flask在Python Web框架里Django和Flask是最常用的两个选择。Flask轻量灵活适合做小接口服务Django“全家桶”自带ORM、Admin后台、Auth认证、模板引擎非常适合这种需要完整业务逻辑的项目。对于毕业设计来说Django的优势非常明显一是自带Admin后台。你可以不写一行前端代码就拥有一个管理后台来维护歌曲、歌手、专辑、用户等信息。在演示和答辩的时候直接打开Admin后台展示数据管理比对着代码讲半天要有说服力得多。二是ORM非常成熟。协同过滤算法需要频繁查询用户行为数据、计算相似度、写入推荐结果Django的ORM能让你把大部分精力放在算法逻辑上而不是手写SQL和操心连接池。三是MVT模式清晰。Model负责数据模型View负责业务逻辑Template负责页面渲染这种分层结构和毕业论文里“系统设计”章节的写作结构天然契合画架构图、写模块设计都很顺手。1.3 项目功能模块划分按照毕业设计的标准结构这个项目可以拆成两大端、五个模块用户端用户注册登录Django自带的User模型扩展支持邮箱或用户名注册登录后才有推荐功能歌曲浏览和搜索按歌曲名、歌手、专辑维度搜索歌曲播放HTML5audio标签播放支持上一首下一首、播放列表用户行为记录记录用户播放、收藏、评分行为每日推荐首页展示协同过滤算法生成的推荐歌曲列表管理端歌曲管理增删改查歌曲信息支持封面图和音频文件上传用户管理查看用户列表、行为记录封禁恶意用户数据统计简单展示歌曲播放热度、用户活跃度等实际开发的时候管理端直接复用Django Admin就够用了不需要额外开发独立的后台前端。2. 协同过滤算法详解从公式到代码2.1 基于用户的协同过滤和基于物品的协同过滤到底选哪个协同过滤分两类基于用户UserCF和基于物品ItemCF。基于用户的协同过滤找到和你兴趣相似的其他用户把这些人喜欢的歌推荐给你。逻辑是“你和用户A都收藏了周杰伦的《晴天》和《七里香》那用户A收藏的《简单爱》你大概率也喜欢”。基于物品的协同过滤找到和你喜欢的歌相似的歌推荐给你。逻辑是“收藏了《晴天》的用户往往也收藏了《七里香》所以这两首歌很相似。你收藏了《晴天》那就把《七里香》推荐给你”。音乐推荐场景下基于物品的协同过滤ItemCF是更合理的选择。原因主要有三个第一歌曲数量相对于用户数量通常更小相似度矩阵的计算和存储开销更可控。一个毕设项目可能就几千首歌但可能有上万个用户UserCF的相似度矩阵规模是用户数x用户数计算和存储压力都大得多。第二歌曲的相似度相对稳定可以离线计算在线推荐时直接查结果。而用户的兴趣会变化UserCF需要频繁更新用户相似度实时性要求高实现复杂度更高。第三ItemCF的推荐结果可解释性强。“因为你喜欢XX所以推荐YY”这种解释在答辩的时候非常好讲也符合音乐推荐的实际场景。2.2 核心公式余弦相似度和预测评分ItemCF的核心分两步计算物品相似度、根据相似度生成推荐。物品相似度计算常用余弦相似度。把每个物品表示为用户行为向量向量中的每一维是该用户对这个物品的行为值。两个物品向量的余弦值越大说明同时喜欢这两个物品的用户越多物品越相似。计算公式sim(i, j) (Σ u∈U R(u,i) * R(u,j)) / (√(Σ u∈U R(u,i)^2) * √(Σ u∈U R(u,j)^2))其中R(u,i)表示用户u对物品i的评分或行为值。在音乐推荐里通常使用隐式反馈播放一次记1分收藏记3分因为用户很少主动给歌打分隐性行为更能反映真实偏好。推荐生成对于目标用户u他对物品i的预测兴趣度公式P(u, i) Σ j∈N(u) sim(i, j) * R(u, j)其中N(u)是用户u行为过的物品集合。这个公式的含义是把用户所有听过的歌乘以它们与候选歌曲i的相似度加权求和得到用户对i的兴趣度。加权和越大说明i越该被推荐给用户u。用生活化的例子解释你听过《晴天》、《七里香》、《可爱女人》《晴天》和《简单爱》的相似度是0.8《七里香》和《简单爱》的相似度是0.6《可爱女人》和《简单爱》的相似度是0.4你给三首歌各打了2分、3分、1分那《简单爱》的预测兴趣度就是0.8x2 0.6x3 0.4x1 3.4分。2.3 相似度矩阵的稀疏问题必须做的数据平滑协同过滤最大的敌人是数据稀疏。一个刚上线的系统用户量少、行为记录更少两个物品之间共同被行为过的用户可能完全没有相似度矩阵会非常稀疏推荐效果会大打折扣。普通ItemCF的代码可能就只有下面这几行def get_item_similarity(user_item_matrix): item_sim {} for item_i in user_item_matrix.columns: for item_j in user_item_matrix.columns: if item_i item_j: continue # 计算余弦相似度 common_users user_item_matrix[item_i].dropna().index.intersection( user_item_matrix[item_j].dropna().index ) if len(common_users) 2: item_sim[(item_i, item_j)] 0 continue vec_i user_item_matrix.loc[common_users, item_i] vec_j user_item_matrix.loc[common_users, item_j] sim np.dot(vec_i, vec_j) / (np.linalg.norm(vec_i) * np.linalg.norm(vec_j)) item_sim[(item_i, item_j)] sim return item_sim但项目中我实际使用的是改进的相似度计算核心思路是引入行为加权和时间衰减同时过滤掉过于稀疏的关联。这里写一下改进后相似度计算的实现细节。首先要构建用户物品行为矩阵但不只是简单的0/1记录而是加权值。然后在计算相似度时加入两个关键的工程化处理阈值过滤和流行度惩罚。阈值过滤是设定一个最小共同行为用户数只有同时接触过两首歌的用户达到一定数量才计算相似度否则直接记0。流行度惩罚是对太热门的歌做降权因为《孤勇者》这种歌谁都听过它带来的相似性信息量很低。import math import numpy as np from collections import defaultdict # 行为加权表播放1分收藏2分评分按用户给的分值 ACTION_WEIGHT {play: 1, collect: 2, rate: 1} # 构建用户-物品行为矩阵 def build_user_item_matrix(records): records: list of (user_id, song_id, action_type, rating_value) user_items defaultdict(dict) for user_id, song_id, action_type, rating_value in records: weight ACTION_WEIGHT.get(action_type, 1) if action_type rate: weight max(1, min(5, int(rating_value))) / 2.5 # 5分制归一化 user_items[user_id][song_id] user_items[user_id].get(song_id, 0) weight return user_items # 计算物品相似度矩阵带阈值过滤和流行度惩罚 def compute_item_similarity(user_items): # 统计每个物品被多少用户行为过 item_user_count defaultdict(int) for user_id, items in user_items.items(): for song_id in items: item_user_count[song_id] 1 # 统计物品对的共现次数和两物品的加权向量模长 co_occur defaultdict(int) item_norm defaultdict(float) item_vec defaultdict(dict) for user_id, items in user_items.items(): song_ids list(items.keys()) for i in range(len(song_ids)): item_i song_ids[i] score_i items[item_i] item_vec[item_i][user_id] score_i item_norm[item_i] score_i * score_i for j in range(i 1, len(song_ids)): item_j song_ids[j] score_j items[item_j] co_occur[(item_i, item_j)] score_i * score_j co_occur[(item_j, item_i)] score_j * score_i item_sim {} for (item_i, item_j), numerator in co_occur.items(): # 共同用户过少相似度不可靠 if len(set(item_vec[item_i].keys()) set(item_vec[item_j].keys())) 3: item_sim[(item_i, item_j)] 0 continue denominator math.sqrt(item_norm[item_i]) * math.sqrt(item_norm[item_j]) if denominator 0: item_sim[(item_i, item_j)] 0 continue base_sim numerator / denominator # 流行度惩罚popularity_penalty log(1 max_count) / log(1 min_count) # 热门歌之间的相似度降权突出长尾推荐 max_cnt max(item_user_count[item_i], item_user_count[item_j]) min_cnt min(item_user_count[item_i], item_user_count[item_j]) penalty math.log(1 min_cnt) / math.log(1 max_cnt) item_sim[(item_i, item_j)] round(base_sim * penalty, 6) return item_sim这里有几个关键点在答辩时值得展开讲。行为加权是为了让收藏这类“主动行为”比播放这类“被动行为”更受重视。阈值过滤是为了控制相似度矩阵的稠密程度防止仅仅因为一两个用户的巧合行为就让完全不相关的歌“强关联”。流行度惩罚则是要解决“热门歌淹没关系”的问题让推荐有差异化。3. 系统设计与数据库实现从0到1的工程落地3.1 数据库表设计推荐系统的地基先看数据表设计。数据库是Django原生支持的SQLite也可以切换成MySQL但SQLite对毕设来说完全够用。主要表格包括用户表User复用Django内置的auth.User模型增加几个功能性的字段from django.contrib.auth.models import AbstractUser from django.db import models class User(AbstractUser): avatar models.ImageField(upload_toavatars/, nullTrue, blankTrue) nickname models.CharField(max_length50, default) preferred_genre models.CharField(max_length30, blankTrue, default) created_at models.DateTimeField(auto_now_addTrue) class Meta: db_table userpreferred_genre字段可以用在冷启动推荐里新用户没有行为数据时可以先按偏好类型推荐热门歌曲。歌曲表Songclass Song(models.Model): title models.CharField(max_length100, verbose_name歌名) artist models.ForeignKey(Artist, on_deletemodels.CASCADE, related_namesongs) album models.ForeignKey(Album, on_deletemodels.CASCADE, related_namesongs) genre models.CharField(max_length30, verbose_name流派, db_indexTrue) duration models.IntegerField(help_text时长秒, default0) audio_file models.FileField(upload_tomusic/, max_length255) cover_image models.ImageField(upload_tocovers/, nullTrue, blankTrue) lyrics models.TextField(blankTrue, default) play_count models.IntegerField(default0, verbose_name播放次数) created_at models.DateTimeField(auto_now_addTrue) class Meta: db_table song注意genre字段加了db_indexTrue等一下写冷启动推荐时会用到play_count是一个冗余设计专门服务于“热门榜”这类简单推荐规则避免每次都要去行为表里做聚合统计。行为表UserBehavior这是推荐系统最核心的表每一条用户和歌曲的交互记录都会落在这里。class UserBehavior(models.Model): ACTION_TYPES ( (play, 播放), (collect, 收藏), (rate, 评分), ) user models.ForeignKey(User, on_deletemodels.CASCADE, related_namebehaviors) song models.ForeignKey(Song, on_deletemodels.CASCADE, related_namebehaviors) action_type models.CharField(max_length10, choicesACTION_TYPES) rating models.IntegerField(nullTrue, blankTrue, help_text评分 1-5) created_at models.DateTimeField(auto_now_addTrue) class Meta: db_table user_behavior indexes [ models.Index(fields[user, action_type]), models.Index(fields[song, action_type]), ]索引设计是个容易被忽视但很重要的细节。推荐算法跑的时候最频繁的查询就是“某用户的所有行为”和“某首歌的所有行为”所以在user和song上都建索引。推荐结果表Recommendationclass Recommendation(models.Model): user models.ForeignKey(User, on_deletemodels.CASCADE, related_namerecommendations) song models.ForeignKey(Song, on_deletemodels.CASCADE) score models.FloatField(default0, verbose_name预测兴趣度) reason models.CharField(max_length255, blankTrue, default) # 推荐解释 created_at models.DateField(auto_now_addTrue) class Meta: db_table recommendation unique_together (user, song, created_at)注意unique_together约束和created_at字段用的是DateField而不是DateTimeField。这样设计的用意是推荐结果每天对每个用户生成一次如果当天已经生成过就直接复用避免每天重复计算消耗系统资源。这个设计在答辩时可以重点讲属于“工程化思维”的体现。3.2 Django项目结构划分按功能模块区分项目根目录的模块划分建议按功能走music_recommend/ ├── manage.py ├── music_recommend/ # 项目配置 │ ├── settings.py │ ├── urls.py │ └── wsgi.py ├── apps/ │ ├── users/ # 用户模块 │ ├── songs/ # 歌曲模块 │ ├── behaviors/ # 行为记录模块 │ └── recommend/ # 推荐引擎模块 ├── static/ ├── media/ # 上传文件 ├── templates/ └── scripts/ └── daily_recommend.py # 定时推荐任务把业务代码拆到apps/目录下每个子应用只负责单一职责。这里推荐单独建一个scripts/daily_recommend.py把推荐算法和Web系统解耦。这样推荐任务可以独立运行、独立调试不用经过Django的请求-响应流程。Django项目启动后在settings.py的INSTALLED_APPS里注册这几个app在根urls.py里用include把各模块的路由挂载进来就行。3.3 核心业务逻辑用户行为记录的前后端落地用户播放一首歌的时候前端页面需要把行为上报给后端。这里需要注意的是不能直接让播放事件同步阻塞播放一首歌就等接口返回体验会很差。正确做法是用异步请求发送。前端音频播放器监听play事件通过fetch把行为上报到后端// music_player.js 节选 const audio document.getElementById(audio-player); let currentSongId null; audio.addEventListener(play, function() { if (currentSongId) { fetch(/api/behavior/, { method: POST, headers: { Content-Type: application/json, X-CSRFToken: getCookie(csrftoken) }, body: JSON.stringify({ song_id: currentSongId, action_type: play }) }); } });后端接收的Viewimport json from django.http import JsonResponse from django.views.decorators.http import require_POST from django.views.decorators.csrf import csrf_exempt require_POST csrf_exempt def record_behavior(request): try: data json.loads(request.body) user request.user if not user.is_authenticated: return JsonResponse({code: 401, msg: 请先登录}, status401) song Song.objects.get(pkdata[song_id]) UserBehavior.objects.create( useruser, songsong, action_typedata[action_type], ratingdata.get(rating) ) if data[action_type] play: Song.objects.filter(pksong.pk).update(play_countmodels.F(play_count) 1) return JsonResponse({code: 200, msg: ok}) except Song.DoesNotExist: return JsonResponse({code: 404, msg: 歌曲不存在}, status404) except Exception as e: return JsonResponse({code: 500, msg: str(e)}, status500)这里有个小技巧Song.objects.update(play_countF(play_count) 1)用的是数据库原子操作比先查出来再加一再保存要稳得多线程安全。3.4 推荐引擎的离线计算定时任务怎么写推荐系统的核心计算不能在用户请求的时候实时算一遍。因为相似度矩阵的构建涉及对所有行为数据的全量扫描计算量大毫秒级别根本算不完。实际项目中采用“离线计算 在线读取”的模式。写一个独立的daily_recommend.py脚本用Django的setup机制独立运行import os, sys import django sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) os.environ.setdefault(DJANGO_SETTINGS_MODULE, music_recommend.settings) django.setup() from apps.behaviors.models import UserBehavior from apps.songs.models import Song from apps.recommend.models import Recommendation from collections import defaultdict # 1. 读取所有用户行为记录 records UserBehavior.objects.all().values_list(user_id, song_id, action_type, rating) print(f共读取 {len(records)} 条行为记录) # 2. 构建用户-物品行为矩阵复用前面写的 build_user_item_matrix user_items build_user_item_matrix(records) # 3. 计算物品相似度矩阵 item_sim compute_item_similarity(user_items) # 4. 为每个用户生成 Top N 推荐列表 from datetime import date today date.today() # 先删除今天的旧推荐避免 unique_together 冲突 Recommendation.objects.filter(created_attoday).delete() for user_id, items in user_items.items(): scores defaultdict(float) reasons defaultdict(list) for song_id_i, score_i in items.items(): for (item_i, item_j), sim in item_sim.items(): if item_i song_id_i and sim 0 and item_j not in items: scores[item_j] sim * score_i reasons[item_j].append(song_id_i) # 排序取 Top 20 top_songs sorted(scores.items(), keylambda x: x[1], reverseTrue)[:20] # 批量写入推荐结果表 rec_objs [ Recommendation( user_iduser_id, song_idsong_id, scorescore, reason,.join(reasons[song_id][:3]), created_attoday ) for song_id, score in top_songs ] Recommendation.objects.bulk_create(rec_objs) print(f推荐完成为 {len(user_items)} 个用户生成推荐)这段代码里的核心逻辑是第4步遍历用户行为过的每一首歌再遍历相似度矩阵里所有这些歌的相似歌曲排除用户已经行为过的歌剩下的歌就是候选集用相似度乘以行为权重作为打分。bulk_create批量写入很关键。如果逐条create几千个用户、每个用户20条推荐那就是几万次数据库写入脚本要跑很久用bulk_create一次写入速度提升一个量级。3.5 在线推荐接口怎么把结果返回给前端推荐结果每天全量算好之后用户访问“推荐”页面时后端只需要做一件事从Recommendation表里查当天的推荐记录再关联出歌曲信息返回。这就是“在线读取”部分查询就是主键关联查询响应速度毫秒级。from django.shortcuts import render from datetime import date from apps.recommend.models import Recommendation def recommend_view(request): user request.user if not user.is_authenticated: return render(request, songs/hot.html, {msg: 登录后查看个性化推荐}) recommend_list ( Recommendation.objects .filter(useruser, created_atdate.today()) .select_related(song__artist, song__album) .order_by(-score) ) # 如果今天还没算推荐结果展示热门歌曲作为兜底 if not recommend_list.exists(): hot_songs Song.objects.order_by(-play_count)[:20] return render(request, songs/hot.html, {songs: hot_songs}) return render(request, recommend/recommend.html, {songs: recommend_list})select_related是个性能优化避免查询每首歌时再次查歌手和专辑的关联表减少SQL次数。这种细节在毕设答辩时提一下老师会认为你确实有工程经验。4. 实操过程与核心环节实现从环境到跑通全流程4.1 环境准备与依赖清单我实际用的开发环境是Python 3.10 Django 4.2前端模板渲染用的Bootstrap 5 原生JavaScript HTML5 Audio API没有引入复杂的前端框架。项目依赖文件requirements.txtDjango4.2,5.0 Pillow10.0.0 numpy1.24.0 pandas2.0.0这里要特别提一下协同过滤的计算用pandas numpy就好不要上scikit-learn。一是演示视频里你要能讲清楚每一步在做什么黑盒库里调一个函数在论文里很难展开写二是sklearn的cosine_similarity虽然快但数据处理前后的代码还得自己写省不了多少事。自己实现公式反而能体现你对算法的理解。4.2 爬取测试数据推荐系统没有数据等于空壳推荐系统的调试必须要真实的数据分布。手工往后台填几十首歌没法验证算法因为用户之间的行为重叠不够相似度矩阵几乎是零。我当时的做法是写了一个爬虫从某音乐网站爬了大约2000首歌的歌曲名、歌手、专辑、时长、封面图同时通过网络公开的听歌记录数据集模拟生成了50个用户、大约2.5万条行为数据。这样数据量级才够算法跑起来。这里有个大坑必须提醒把爬下来的音频文件上传到自己的项目里涉及版权问题毕设如果是内部演示问题不大但如果要公开发布一定要谨慎。稳妥的做法是歌曲信息入库音频用在线链接播放或者只保留30秒试听片段。如果不想爬虫也有替代方案。可以直接用公开的音乐数据集CSV文件导入比如一些平台开放的音乐排行榜数据。配合Django的loaddata命令或者写一个import_data.py脚本解析CSV批量入库。4.3 核心页面实现推荐页、播放器、个人中心推荐页是整个项目的门面需要在视觉上让答辩老师一眼看懂“这是推荐”。页面布局上我把推荐页分三个区域每日推荐列表协同过滤算法生成的Top 20每首歌卡片显示歌名、歌手、封面、推荐理由比如“因为你喜欢《晴天》”热门榜单按播放次数排序的全局热门作为冷启动的兜底推荐相似歌曲当前播放歌曲的相似歌曲实时调用相似度接口播放器用HTML5的audio标签配合自定义的上一首/下一首、播放列表功能。!-- base.html 中播放器部分 -- audio idglobal-player controls preloadnone/audio script let playList []; let currentIndex 0; function playSong(songId, title, artist, audioUrl) { const player document.getElementById(global-player); player.src audioUrl; player.play(); // 上报行为 reportBehavior(songId, play); } function playNext() { if (playList.length 0) return; currentIndex (currentIndex 1) % playList.length; const song playList[currentIndex]; playSong(song.id, song.title, song.artist, song.audioUrl); } /script个人中心除了展示用户基本信息还有两个核心模块我的收藏列表方便用户管理收藏、我的听歌记录按时间倒序。这两个模块的数据都可以作为推荐算法的输入。4.4 论文里怎么设计实验对比协同过滤效果怎么证明很多人的毕业论文写到这里会卡住算法实现了但怎么证明有效果导师如果问“你的推荐算法比热门榜好在哪”怎么回答毕设论文里的实验部分不需要像学术论文那样严谨地做离线评测算RMSE、PrecisionK这些但至少要有一个简单的效果对比。我当时做的办法是留出实验法把用户行为数据按时间切分前80%用于生成推荐后20%用于验证。然后算两个指标覆盖度推荐列表中有多少歌确实被用户在后20%的行为中听过。覆盖度越高说明推荐命中越多。对比基准把协同过滤推荐结果和纯热门榜Top 20做对比看谁的命中率更高。我当时跑出来的数据大致是协同过滤的命中率在15%-20%左右热门榜只有6%-8%。对比结果放成柱状图放进论文里答辩的时候就能说“协同过滤比热门推荐提升了约两倍的命中率”这就非常直观。4.5 演示视频怎么录才加分按脚本走流程演示视频建议控制在8-12分钟太长答辩老师没耐心看太短核心内容展不开。录制脚本按这样走展示项目结构30秒tree命令查看目录结构快速证明代码是真实完成的展示推荐算法代码1分钟打开daily_recommend.py指到余弦相似度实现部分确认算法不是调库运行推荐脚本1分钟终端跑python scripts/daily_recommend.py看到行为记录条数和推荐生成结果启动Django服务1分钟python manage.py runserver打开浏览器演示用户登录和推荐页2分钟用测试账号登录展示推荐列表和推荐理由演示播放歌曲、收藏歌曲1分钟播放一首推荐的歌曲演示播放器的上一首/下一首功能演示Admin后台1分钟进入Admin后台展示歌曲管理和用户行为记录演示推荐更新2分钟手动往数据库加几条行为记录重新跑推荐展示推荐列表的变化这个脚本里有几个关键动作展示行为记录对推荐结果的影响这一步是最有说服力的证明推荐系统是真的在“感知”用户行为。5. 常见问题与排查技巧实录5.1 相似度矩阵内存爆炸遇到这个问题的第一反应通常是“换更大的机器”但毕设项目里没必要。几千首歌的相似度矩阵用python里的dict存完全够用。但如果你存的是完整的N x N矩阵哪怕只有5000首歌也要存2500万个浮点数内存开销就按GB算了。解决方案是我前面代码里提到的思路只保存相似度大于0的键值对同时做阈值过滤。另外可以进一步压缩只保存每首歌的Top 50相似歌曲这样矩阵规模从N^2降到N x 50存储膨胀问题直接消失。# 压缩保存每首歌只保留 TopK 相似 def keep_topk(item_sim, k50): from collections import defaultdict topk_sim defaultdict(dict) for (item_i, item_j), sim in item_sim.items(): if len(topk_sim[item_i]) k: topk_sim[item_i][item_j] sim else: min_k min(topk_sim[item_i], keytopk_sim[item_i].get) if sim topk_sim[item_i][min_k]: del topk_sim[item_i][min_k] topk_sim[item_i][item_j] sim return topk_sim5.2 推荐结果全是热门歌没有个性化如果你发现推荐给每个用户的结果差不多都是那几首热门歌说明没有做好流行度惩罚。余弦相似度天然偏向热门物品两首大热歌同时被很多用户点击过相似度虚高导致推荐结果里热门歌扎堆。解决思路就是我前面写的penalty那一行让热门歌曲之间的相似度打折。另一个辅助办法是在最终生成推荐列表时对全局播放次数超过阈值的歌做一个额外的降权。同时对冷门歌曲加一点“探索分”让推荐列表保持多样性。5.3 新用户没有行为数据推荐列表空荡荡新用户第一次登录UserBehavior表里没有这个用户的任何记录前面的推荐算法就不会给他生成任何推荐。这是协同过滤公认的冷启动问题。项目里我做了两套兜底方案第一套基于注册时填写的偏好流派。新用户注册时让用户选择喜欢的音乐风格流行、摇滚、民谣、古典、电子等登录后直接推荐该流派下播放量最高的歌曲。第二套全局热门榜。无论有没有偏好都在推荐页下面展示全站Top 20热门歌曲。这两套方案合在一起新用户就不会看到空白页面。def cold_start_recommend(user, n20): # 优先按偏好流派推荐 if user.preferred_genre: songs Song.objects.filter(genreuser.preferred_genre).order_by(-play_count)[:n] if songs.exists(): return songs # 兜底全局热门 return Song.objects.order_by(-play_count)[:n]5.4 播放记录重复刷榜单被污染测试阶段可能会发现反复播放同一首歌行为表里会积累大量重复记录。如果不对这种行为做限制某些歌的play_count会虚高热门榜和协同过滤都会被污染。我的处理方式是在记录行为时做去重# 同一用户对同一首歌的同一行为在5分钟内只记录一次 recent ( UserBehavior.objects .filter(useruser, songsong, action_typeplay, created_at__gtetimezone.now() - timedelta(minutes5)) .exists() ) if not recent: UserBehavior.objects.create(...)这个限制在演示时也很有用防止你为了演示效果反复点击播放把数据搞乱。5.5 前端播放器跨域或者音频404歌曲文件存在本地media/music/目录下如果Django没配好静态文件服务播放器会一直404。Django生产模式默认不服务用户上传的媒体文件开发模式下要在urls.py里加一行from django.conf import settings from django.conf.urls.static import static urlpatterns [...] if settings.DEBUG: urlpatterns static(settings.MEDIA_URL, document_rootsettings.MEDIA_ROOT)如果音频链接用的是外部URL且目标站点有防盗链播放器也可能加载失败。稳妥做法是下载音频到本地再上传到自己的媒体目录。5.6 答辩时最容易被追问的三个问题答辩时协过滤相关的问题集中在这几个提前准备好答案问题一为什么选择基于物品的协同过滤而不是基于用户答音乐场景下歌曲数量相对用户数量更少物品相似度计算成本更低歌曲相似度相对稳定可以离线计算推荐响应更快推荐结果可解释性强适合展示。问题二用户-物品矩阵太稀疏怎么办答一是用隐式反馈加权播放、收藏、评分都作为行为信号二是设置共同行为用户数阈值过滤不可靠的相似度三是用热门榜和偏好流派做冷启动兜底。问题三算法的时间复杂度是多少怎么优化答构建相似度的复杂度是O(N x M^2)N是用户数M是平均每个用户产生行为的物品数。通过阈值过滤和TopK截断可以把实际需要存储和计算的相似度对数量控制在O(N x K)。这里的K是每首歌保留的最近邻数量。写在最后的实操建议做这个项目期间我踩过的最大的坑不是算法写不出来而是以为算法写完就结束了。推荐系统是一个闭环系统数据和算法的关系就像燃料和发动机没有数据灌进去发动机再漂亮也跑不起来。所以建议你在动手写代码之前先把数据准备好了至少要准备出几百首歌曲信息和几千条模拟行为数据这样算法代码写一行就能测一行开发效率会高很多。给几个具体的实操建议把推荐结果从“明天才能看到”改成“跑完脚本马上能看到”一天能迭代好几轮行为数据越早上线越好因为哪怕算法逻辑一模一样真实行为数据积累得越多推荐效果就越明显。另外一个很实用的小技巧是在运行推荐脚本的终端里打印出“读取多少条行为、生成了多少条推荐、耗时多少秒”录像的时候这些数字本身就说明你的系统在真实工作。如果你正在做这个题目不用太焦虑算法效果不够惊艳。毕设考察的是完整度——从需求分析、系统设计、算法实现、工程落地到测试演示的全链路能力不是让你发顶会论文。把基础功能做扎实把推荐流程讲清楚把论文里的结构理顺就已经是一个能拿高分的项目了。本文还有配套的精品资源点击获取
返回列表