ARTICLE DETAIL

资讯详情

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

基于协同过滤的音乐推荐系统毕业设计完整开发指南

基于协同过滤的音乐推荐系统毕业设计完整开发指南 每年毕业季计算机专业的同学都在为一个问题头疼毕业设计到底做什么做管理系统太普通做算法研究又怕完不成。如果既想体现技术含量又想保证代码可控、文档好写、答辩有亮点基于协同过滤的音乐推荐系统是一个很合适的方向。这套选题的典型技术栈是Python Django Vue 协同过滤算法。它既涉及后端接口开发也涉及前端页面展示还包含推荐算法的设计与实现是一套能完整展示工程能力和算法基础的组合。本文将围绕这个项目展开从算法原理、技术选型、环境准备到核心代码实现和常见问题完整拆解一遍毕设项目的开发流程。无论你是刚确定题目还在调研还是已经动手开发但卡在某个环节这篇内容都能给你提供一条清晰可执行的路。1. 什么是基于协同过滤的音乐推荐系统1.1 推荐系统解决的痛点在音乐平台中曲库数量庞大用户不可能手动浏览所有歌曲。用户需要的是这样一个能力系统根据用户的历史行为自动猜测他可能喜欢哪些歌曲并把候选歌曲展示到用户面前。这个能力本质上是“信息过滤”。推荐系统不是搜索引擎用户没有输入明确的查询词系统要根据用户画像、历史行为、相似用户群体等数据主动把内容推到用户面前。放到毕设场景下我们需要实现的是一个小型化的推荐服务或者说一个具备核心算法逻辑的 Web 应用系统。1.2 协同过滤的基本思想协同过滤Collaborative Filtering是推荐算法中应用最广泛的思路之一。它的核心思想很朴素相似的人会有相似的喜好。假如用户 A 和用户 B 都收藏了歌曲 X、Y、Z说明这两个用户的音乐口味比较接近。那么当用户 A 收藏了一首新歌 W而用户 B 还没有听过 W 时系统可以预测用户 B 大概率也喜欢 W于是把这首 W 推荐给用户 B。这个思想往下延伸产生了两条不同的技术路线。基于用户的协同过滤User-Based CF先找与当前用户口味相似的其他用户再把这些相似用户喜欢的、但当前用户没听过的歌曲推荐出来。基于物品的协同过滤Item-Based CF先计算歌曲之间的相似度再根据用户历史上喜欢的歌曲推荐与这些歌曲相似的其他歌曲。在实际互联网产品中基于物品的协同过滤更容易落地因为歌曲数量相对稳定物品相似度可以离线计算而用户数量增长快基于用户的协同过滤需要实时计算用户相似度开销更大。毕设项目一般数据量不大两种方案都能实现但从答辩角度来讲理解两者的区别并说明为什么选择其中一种是很加分的细节。1.3 与内容推荐的边界区分和协同过滤经常一起被提起的还有基于内容的推荐Content-based Recommendation。基于内容的方法是分析物品本身的属性比如歌曲的流派、歌手、语种然后给用户推荐和他听过的歌曲属性相似的歌曲。协同过滤不关心物品本身的属性它只关心用户和物品之间的交互关系。这种“只凭行为、不看内容”的方式让协同过滤在某些场景下能带来惊喜推荐即推荐的歌曲在内容属性上和用户历史偏好并不同类但真正被相似用户验证过。对毕设来说只实现协同过滤完全够用。但如果你在论文里加入基于内容的推荐作为对比项目创新性会更强。这通常作为扩展点来写。2. 系统总体设计2.1 功能需求拆分在做系统设计之前先把需求拆开。一个音乐推荐系统至少需要以下模块。用户模块包含注册、登录、个人信息维护。管理员模块负责歌曲信息管理、用户管理、推荐结果查看。歌曲模块包含歌曲名称、歌手、专辑、封面、音频链接、歌词等字段。用户行为模块记录用户的收藏、播放、评分等行为这是推荐算法的数据来源。推荐模块基于协同过滤算法为用户生成推荐列表。前端展示模块登录后展示推荐歌曲、歌曲列表、用户收藏列表。如果是毕设建议做成“用户端 管理端”的前后端分离结构后端只提供 RESTful API前端负责页面渲染。2.2 技术栈选型后端选用 Django Django REST Framework这是 Python 生态中最成熟的 Web 开发方案之一。Django 自带 Admin 后台、ORM、认证体系能大大缩短开发周期对毕设场景非常友好。Django REST Framework 则负责 API 接口序列化与视图集封装。前端选用 Vue 3 Element Plus Axios。Vue 是目前主流的前端框架之一Element Plus 提供组件库Axios 负责发送 HTTP 请求。页面不需要写得太复杂重点是能清晰展示推荐效果。数据库使用 MySQL由于本地开发与部署便利的原因SQLite 也可以作为替代。如果追求真实项目效果建议直接用 MySQL毕竟大多数公司生产环境使用 MySQL答辩时被问到的概率比较高。推荐算法部分不依赖第三方推荐框架自己编写协同过滤核心逻辑。这是毕设项目最重要的亮点之一能把算法过程完整展示出来论文和代码讲解才有内容可写。2.3 项目目录结构推荐项目按照下面这种方式组织music-recommend/ ├── backend/ │ ├── manage.py │ ├── music_recommend/ │ │ ├── __init__.py │ │ ├── settings.py │ │ ├── urls.py │ │ ├── wsgi.py │ ├── apps/ │ │ ├── users/ │ │ ├── songs/ │ │ ├── interactions/ │ │ └── recommend/ │ ├── scripts/ │ │ └── generate_data.py │ └── requirements.txt └── frontend/ ├── node_modules/ ├── src/ │ ├── api/ │ ├── components/ │ ├── router/ │ ├── views/ │ ├── App.vue │ └── main.js ├── package.json └── vite.config.js前后端分离的好处是职责清晰前端只关心渲染与交互后端专注业务逻辑与算法实现。部署时也只需要分别启动两个服务联调过程并不复杂。3. 环境准备与项目初始化3.1 环境版本说明版本需要根据你的实际环境进行调整本文示例以常见环境为例重点演示配置思路。后端建议使用 Python 3.8 及以上版本Django 根据你的 Python 版本来选。如果你使用的是 Python 3.10 以上环境建议安装 Django 3.2 以上版本。前端部分需要 Node.js 14.18 以上版本建议使用 Node 16 或 18 的 LTS 版本。需要特别提醒的是不要盲目追求最新版本。Django 的大版本升级往往伴随着配置项调整和依赖兼容变化。尽量选择生态成熟的稳定组合比如 Django 3.2 或 4.x 搭配 Django REST Framework 3.12 以上版本。3.2 后端项目初始化打开终端依次执行下面的命令# 创建虚拟环境 python -m venv venv # 激活虚拟环境Windows 使用 venv\Scripts\activate source venv/bin/activate # 安装 Django 与 DRF pip install django pip install djangorestframework pip install django-cors-headers pip install pymysql # 创建后端项目目录 mkdir backend cd backend # 创建 Django 项目 django-admin startproject music_recommend . # 创建多个 App python manage.py startapp users python manage.py startapp songs python manage.py startapp interactions python manage.py startapp recommend创建完成后修改 settings.py把新增的 App 和第三方组件注册进去。INSTALLED_APPS [ django.contrib.admin, django.contrib.auth, django.contrib.contenttypes, django.contrib.sessions, django.contrib.messages, django.contrib.staticfiles, # 第三方 rest_framework, corsheaders, # 本地应用 apps.users, apps.songs, apps.interactions, apps.recommend, ]因为 App 放在了 apps 目录下还需要在 settings.py 中配置路径。import sys sys.path.insert(0, str(BASE_DIR / apps))同时配置跨域与数据库。如果你使用 MySQL需要安装 PyMySQL 并在项目__init__.py中引入import pymysql pymysql.install_as_MySQLdb()数据库配置如下DATABASES { default: { ENGINE: django.db.backends.mysql, NAME: music_recommend, USER: root, PASSWORD: your_password, HOST: 127.0.0.1, PORT: 3306, } }这里需要提前在 MySQL 中创建好数据库。3.3 前端项目初始化使用 Vite 创建 Vue 项目npm create vitelatest frontend -- --template vue cd frontend npm install npm install vue-router4 npm install axios npm install element-plus安装完成后启动验证npm run dev如果浏览器能正常打开 Vite 默认页面说明前端环境已经就绪。4. 数据库模型设计数据库模型是整个系统的地基。如果模型设计不合理后面的算法实现和数据清洗都会受影响。4.1 用户模型用户体系可以直接继承 Django 自带的 AbstractUser方便复用 Django 的认证能力。在apps/users/models.py中添加from django.db import models from django.contrib.auth.models import AbstractUser class User(AbstractUser): nickname models.CharField(max_length50, verbose_name昵称) avatar models.URLField(blankTrue, verbose_name头像) created_at models.DateTimeField(auto_now_addTrue, verbose_name创建时间) class Meta: db_table sys_user verbose_name 用户 verbose_name_plural verbose_name4.2 歌曲模型歌曲模型保存歌曲的基础信息。考虑到音频播放是前端的能力后端只需要保存歌曲封面的 URL 和音频文件的 URL本地开发时可以放测试链接。在apps/songs/models.py中from django.db import models class Song(models.Model): title models.CharField(max_length100, verbose_name歌曲名) artist models.CharField(max_length100, verbose_name歌手) album models.CharField(max_length100, blankTrue, verbose_name专辑) cover_url models.URLField(blankTrue, verbose_name封面链接) audio_url models.URLField(blankTrue, verbose_name音频链接) duration models.IntegerField(default0, verbose_name时长(秒)) genre models.CharField(max_length50, blankTrue, verbose_name流派) created_at models.DateTimeField(auto_now_addTrue) class Meta: db_table song verbose_name 歌曲 verbose_name_plural verbose_name def __str__(self): return self.title4.3 用户行为模型用户对歌曲的行为包括播放、收藏、评分等。推荐算法主要依赖用户和歌曲之间的交互数据因此行为表必须包含用户、歌曲和行为类型三个关键字段。在apps/interactions/models.py中from django.db import models from django.conf import settings from apps.songs.models import Song class Interaction(models.Model): TYPE_CHOICES ( (play, 播放), (collect, 收藏), (like, 点赞), ) user models.ForeignKey(settings.AUTH_USER_MODEL, on_deletemodels.CASCADE, verbose_name用户) song models.ForeignKey(Song, on_deletemodels.CASCADE, verbose_name歌曲) behavior_type models.CharField(max_length20, choicesTYPE_CHOICES, verbose_name行为类型) created_at models.DateTimeField(auto_now_addTrue) class Meta: db_table user_song_interaction verbose_name 用户行为 verbose_name_plural verbose_name unique_together (user, song, behavior_type)unique_together保证同一用户对同一首歌曲的同一行为只能记录一次防止重复收藏导致算法数据异常。4.4 数据迁移模型写完后执行数据库迁移python manage.py makemigrations python manage.py migrate如果你使用自定义用户模型务必在第一次迁移之前就完成配置否则容易遇到外键关联错误。在 settings.py 中加入AUTH_USER_MODEL users.User5. 协同过滤算法的核心实现5.1 相似度计算余弦相似度协同过滤算法中最重要的部分是相似度计算。常用的相似度计算方法有余弦相似度、皮尔逊相关系数、杰卡德相似系数等。在音乐推荐场景中用户对歌曲的行为通常用 0/1 表示未听过/听过或评分表示此时余弦相似度是最直观、最好解释的计算方式。假设有两个用户 A 和 B他们在一组歌曲上的行为向量分别是向量 a 和向量 b余弦相似度公式如下两个 n 维向量的点积除以两个向量模长的乘积得到的结果范围在 -1 到 1 之间。值越接近 1代表两个用户的口味越相似。为了便于代码组织先创建一个处理用户行为数据的工具模块。在recommend/utils.py中实现def user_song_matrix(interactions): 将用户行为数据转换为用户-歌曲矩阵。 interactions: 包含 user_id、song_id、score 的列表 返回: { user_id: { song_id: score } } matrix {} for item in interactions: uid item[user_id] sid item[song_id] score item.get(score, 1) if uid not in matrix: matrix[uid] {} matrix[uid][sid] score return matrix5.2 基于用户的协同过滤实现接下来实现核心的推荐函数。整体思路分成三步。第一步获取与目标用户最相似的 K 个用户。第二步统计这些邻居用户收藏过但目标用户没有行为的歌曲。第三步按照相似度加权排序取出 Top N 推荐歌曲。在recommend/core.py中编写import math from collections import defaultdict from .utils import user_song_matrix def cosine_similarity(vec1, vec2): 计算两个用户评分向量的余弦相似度 common_keys set(vec1.keys()) set(vec2.keys()) if not common_keys: return 0 dot_product sum(vec1[k] * vec2[k] for k in common_keys) norm1 math.sqrt(sum([v * v for v in vec1.values()])) norm2 math.sqrt(sum([v * v for v in vec2.values()])) if norm1 0 or norm2 0: return 0 return dot_product / (norm1 * norm2) def get_top_n_users(user_id, matrix, k10): 找到与目标用户最相似的 K 个用户 target_vector matrix.get(user_id, {}) if not target_vector: return [] similarity_list [] for uid, vector in matrix.items(): if uid user_id: continue sim cosine_similarity(target_vector, vector) if sim 0: similarity_list.append((uid, sim)) similarity_list.sort(keylambda x: x[1], reverseTrue) return similarity_list[:k] def recommend_by_user_based(user_id, matrix, k10, top_n20): 基于用户的协同过滤推荐 if user_id not in matrix: return [] neighbors get_top_n_users(user_id, matrix, k) target_has set(matrix[user_id].keys()) score_dict defaultdict(float) for neighbor_id, sim in neighbors: for song_id, score in matrix[neighbor_id].items(): if song_id in target_has: continue score_dict[song_id] sim * score sorted_songs sorted(score_dict.items(), keylambda x: x[1], reverseTrue) return [song_id for song_id, _ in sorted_songs[:top_n]]这段代码逻辑已经很完整。需要注意的一点是在实际项目中K 值和 Top N 值需要经过实验确定并不是越大越好。K 值太大时会把口味差异较大的用户也纳入推荐来源导致推荐结果泛化K 值太小时又可能因为邻居样本不足而缺少推荐结果。5.3 基于物品的协同过滤实现基于物品的协同过滤实现思路与用户版本对称。先计算歌曲之间的相似度再结合用户的历史行为生成推荐。在recommend/core.py中追加def item_similarity(matrix): 计算物品间相似度矩阵 item_users defaultdict(set) for uid, items in matrix.items(): for sid in items.keys(): item_users[sid].add(uid) item_sim defaultdict(dict) # 统计同时喜欢两个物品的用户数 co_occurrence defaultdict(int) for sid, users in item_users.items(): for uid in users: for other_sid in matrix[uid].keys(): if sid other_sid: continue co_occurrence[(sid, other_sid)] 1 for (sid, other_sid), count in co_occurrence.items(): if count 0: continue item_sim[sid][other_sid] count / math.sqrt( len(item_users[sid]) * len(item_users[other_sid]) ) return item_sim def recommend_by_item_based(user_id, matrix, top_n20): 基于物品的协同过滤推荐 if user_id not in matrix: return [] user_items set(matrix[user_id].keys()) item_sim item_similarity(matrix) score_dict defaultdict(float) for sid in user_items: candidate_items item_sim.get(sid, {}) for other_sid, sim in candidate_items.items(): if other_sid in user_items: continue score_dict[other_sid] sim sorted_songs sorted(score_dict.items(), keylambda x: x[1], reverseTrue) return [song_id for song_id, _ in sorted_songs[:top_n]]这里的物品相似度实现使用的是惩罚热门物品的改进版本。除以用户数量的平方根是为了降低热门歌曲被过度推荐的权重因为一首歌被很多人收藏不代表它与目标歌曲的相似度高可能只是因为它本身很火。5.4 混合推荐的简单策略毕设项目中为了展示扩展能力可以做一个简单的混合推荐函数。如果用户的历史行为数据较少基于用户的协同过滤效果会非常差此时可以退化为热门歌曲推荐。只有当用户行为数据达到一定数量时才启用协同过滤。def hybrid_recommend(user_id, matrix, k10, top_n20): 冷启动退化为热门推荐否则使用协同过滤 if user_id not in matrix or len(matrix[user_id]) 3: return get_hot_songs(matrix, top_n) return recommend_by_user_based(user_id, matrix, k, top_n) def get_hot_songs(matrix, top_n20): 按被收藏次数统计热门歌曲 song_count defaultdict(int) for uid, items in matrix.items(): for sid in items.keys(): song_count[sid] 1 sorted_songs sorted(song_count.items(), keylambda x: x[1], reverseTrue) return [sid for sid, _ in sorted_songs[:top_n]]这个策略可以作为项目中的“冷启动处理方案”来讲解在答辩中是很容易引发老师兴趣的切入点。6. Django API 接口开发6.1 用户行为记录接口推荐算法需要用户行为数据作为输入。因此先写记录播放、收藏行为的接口。在interactions/views.py中使用 DRF 的 APIViewfrom rest_framework.views import APIView from rest_framework.response import Response from rest_framework import status from rest_framework.permissions import IsAuthenticated from .models import Interaction from apps.songs.models import Song class InteractionCreateView(APIView): permission_classes [IsAuthenticated] def post(self, request): user request.user song_id request.data.get(song_id) behavior_type request.data.get(behavior_type) if not song_id or behavior_type not in [play, collect, like]: return Response({error: 参数错误}, statusstatus.HTTP_400_BAD_REQUEST) try: song Song.objects.get(idsong_id) except Song.DoesNotExist: return Response({error: 歌曲不存在}, statusstatus.HTTP_404_NOT_FOUND) obj, created Interaction.objects.get_or_create( useruser, songsong, behavior_typebehavior_type, defaults{created_at: None} ) if not created: return Response({message: 已经记录过了}) return Response({message: 记录成功}, statusstatus.HTTP_201_CREATED)6.2 推荐接口推荐接口需要从数据库中取出用户的行为数据构建矩阵然后调用协同过滤算法。在recommend/views.py中from rest_framework.views import APIView from rest_framework.response import Response from rest_framework.permissions import IsAuthenticated from apps.interactions.models import Interaction from django.db.models import Count from .core import hybrid_recommend, recommend_by_item_based from apps.songs.models import Song class RecommendView(APIView): permission_classes [IsAuthenticated] def get(self, request): user request.user # 获取该用户的所有正面行为收藏和点赞都表示感兴趣 interactions Interaction.objects.filter( useruser, behavior_type__in[collect, like] ).values(user_id, song_id) # 数据量小直接查全表构造矩阵毕设场景足够 all_interactions Interaction.objects.filter( behavior_type__in[collect, like] ).values(user_id, song_id) matrix {} for item in all_interactions: uid item[user_id] sid item[song_id] if uid not in matrix: matrix[uid] {} matrix[uid][sid] 1 recommend_ids hybrid_recommend(user.id, matrix, k10, top_n12) # 转为歌曲详情 songs Song.objects.filter(id__inrecommend_ids) # 保持推荐顺序 song_map {song.id: song for song in songs} result [] for sid in recommend_ids: song song_map.get(sid) if song: result.append({ id: song.id, title: song.title, artist: song.artist, album: song.album, cover_url: song.cover_url, audio_url: song.audio_url, }) return Response({data: result})这段代码是演示思路在实际项目中建议把“构造评分矩阵”和“调用算法”抽成独立的 service 层函数避免视图代码过于臃肿。6.3 接口路由注册在music_recommend/urls.py中统一配置from django.contrib import admin from django.urls import path, include urlpatterns [ path(admin/, admin.site.urls), path(api/users/, include(apps.users.urls)), path(api/songs/, include(apps.songs.urls)), path(api/interactions/, include(apps.interactions.urls)), path(api/recommend/, include(apps.recommend.urls)), ]在每个 App 自己的 urls.py 中配置具体路由。from django.urls import path from .views import RecommendView urlpatterns [ path(music/, RecommendView.as_view(), namerecommend_music), ]7. Vue 前端页面开发7.1 登录页面与请求封装前端部分核心页面包括登录注册页、音乐推荐页、歌曲列表页、收藏页。由于篇幅原因这里重点展示推荐页的完整实现思路。先封装 Axios。在src/api/request.js中import axios from axios import { ElMessage } from element-plus import router from ../router const request axios.create({ baseURL: http://127.0.0.1:8000/api, timeout: 10000 }) // 请求拦截器自动携带 token request.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers.Authorization Token ${token} } return config }) // 响应拦截器统一错误处理 request.interceptors.response.use( response response.data, error { if (error.response error.response.status 401) { ElMessage.error(登录状态已过期请重新登录) router.push(/login) } else { ElMessage.error(error.response?.data?.error || 请求失败) } return Promise.reject(error) } ) export default request这里使用 DRF 的 Token 认证在后端 settings.py 中需要加入REST_FRAMEWORK { DEFAULT_AUTHENTICATION_CLASSES: [ rest_framework.authentication.TokenAuthentication, rest_framework.authentication.SessionAuthentication, ], DEFAULT_PERMISSION_CLASSES: [ rest_framework.permissions.IsAuthenticated, ], }7.2 推荐页面组件推荐页面是项目的门面。用户登录后第一眼看到的就是“猜你喜欢”推荐歌曲列表。在src/views/RecommendView.vue中template div classrecommend-container h2猜你喜欢/h2 div classsong-grid div v-forsong in songs :keysong.id classsong-card img :srcsong.cover_url || defaultCover alt封面 / h3{{ song.title }}/h3 p{{ song.artist }}/p p{{ song.album }}/p div classactions el-button typeprimary sizesmall clickhandleCollect(song) 收藏 /el-button el-button sizesmall clickhandlePlay(song) 播放 /el-button /div /div /div /div /template script setup import { ref, onMounted } from vue import { ElMessage } from element-plus import request from ../api/request const songs ref([]) const defaultCover https://via.placeholder.com/150 const loadRecommend async () { try { const res await request.get(/recommend/music/) songs.value res.data } catch (e) { console.error(加载推荐失败, e) } } const handleCollect async (song) { await request.post(/interactions/create/, { song_id: song.id, behavior_type: collect }) ElMessage.success(收藏成功) } const handlePlay async (song) { await request.post(/interactions/create/, { song_id: song.id, behavior_type: play }) // 播放逻辑可根据实际音频链接实现例如 new Audio(song.audio_url) } onMounted(() { loadRecommend() }) /script style scoped .song-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 20px; } .song-card { border: 1px solid #eee; border-radius: 8px; padding: 15px; text-align: center; } .song-card img { width: 150px; height: 150px; object-fit: cover; border-radius: 8px; } /style前端的功能逻辑并不复杂重点是把 token 处理、路由守卫和 API 调用方式掌握好即可。7.3 后端 Token 登录接口在后端 users App 中使用 DRF 内置的 obtain_auth_token 快速完成登录接口# apps/users/urls.py from django.urls import path from rest_framework.authtoken.views import obtain_auth_token from .views import RegisterView urlpatterns [ path(login/, obtain_auth_token, namelogin), path(register/, RegisterView.as_view(), nameregister), ]RegisterView 的实现from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import status from .models import User from django.contrib.auth.hashers import make_password class RegisterView(APIView): def post(self, request): username request.data.get(username) password request.data.get(password) nickname request.data.get(nickname, username) if not username or not password: return Response({error: 用户名和密码不能为空}, statusstatus.HTTP_400_BAD_REQUEST) if User.objects.filter(usernameusername).exists(): return Response({error: 用户名已存在}, statusstatus.HTTP_400_BAD_REQUEST) user User.objects.create( usernameusername, passwordmake_password(password), nicknamenickname ) return Response({message: 注册成功}, statusstatus.HTTP_201_CREATED)8. 模拟数据生成与效果验证8.1 为什么需要模拟数据推荐系统是一个依赖历史数据的应用。新用户没有行为记录系统无法为他推荐个性化内容。毕设项目在演示阶段往往没有真实用户数据所以我们需要编写一个数据生成脚本模拟一批用户、歌曲和收藏行为让推荐系统跑起来。在backend/scripts/generate_data.py中import os import django import random os.environ.setdefault(DJANGO_SETTINGS_MODULE, music_recommend.settings) django.setup() from apps.users.models import User from apps.songs.models import Song from apps.interactions.models import Interaction # 关闭源码保护允许在脚本中直接创建用户 def clean_data(): Interaction.objects.all().delete() Song.objects.all().delete() User.objects.filter(is_superuserFalse).delete() def generate(): clean_data() # 创建演示用户 demo_users [] for i in range(1, 31): user, created User.objects.get_or_create( usernamefuser{i}, defaults{ nickname: f测试用户{i}, password: pbkdf2_sha256$ } ) if created: user.set_password(123456) user.save() demo_users.append(user) # 创建歌曲 song_names [ 晴天, 稻香, 夜曲, 七里香, 告白气球, 平凡之路, 后会无期, 成都, 理想, 南方姑娘, 演员, 丑八怪, 刚刚好, 一半, 绅士, 光年之外, 泡沫, 喜欢你, 我的秘密, 倒数, ] artists [周杰伦, 朴树, 赵雷, 薛之谦, 邓紫棋] songs [] for idx, name in enumerate(song_names): song Song.objects.create( titlename, artistartists[idx % len(artists)], albumf专辑{idx // 5 1}, cover_urlfhttps://via.placeholder.com/150?text{idx 1}, audio_url, durationrandom.randint(180, 320), genre流行, ) songs.append(song) # 生成用户收藏数据让不同用户之间存在偏好差异 for user in demo_users: # 每个用户收藏 8-15 首歌 selected random.sample(songs, random.randint(8, 15)) for song in selected: Interaction.objects.create( useruser, songsong, behavior_typerandom.choice([play, collect, like]) ) print(f生成完成{len(demo_users)} 个用户{len(songs)} 首歌曲) if __name__ __main__: generate()这段模拟数据脚本存在一个小问题Interaction模型中的unique_together约束要求 user、song、behavior_type 三者唯一当脚本使用random.choice时有可能对同一首歌曲生成重复行为在极端情况下触发唯一约束异常。更稳妥的写法是使用get_or_create替代create或者先收集收藏歌曲集合再批量写入。实际开发时可以使用下面的方式改进for user in demo_users: selected random.sample(songs, random.randint(8, 15)) for song in selected: behavior_type random.choice([play, collect, like]) try: Interaction.objects.create( useruser, songsong, behavior_typebehavior_type ) except Exception: # 已存在则跳过仅用于测试脚本不推荐在生产环境使用裸异常 pass在编写毕设代码时这种细节最能体现工程素养。数据生成完成后可以启动 Django shell 验证数据。8.2 推荐结果验证在终端中执行python manage.py shell输入以下代码from apps.interactions.models import Interaction from apps.recommend.core import recommend_by_user_based, user_song_matrix # 构造矩阵 interactions Interaction.objects.filter( behavior_type__in[collect, like] ).values(user_id, song_id) matrix {} for item in interactions: uid item[user_id] sid item[song_id] if uid not in matrix: matrix[uid] {} matrix[uid][sid] 1 # 给 user1 推荐 result recommend_by_user_based(1, matrix, k10, top_n5) print(推荐歌曲 ID:, result)如果数据分布合理user1 会得到一组他没有行为过的歌曲 ID。可以进一步查询这些歌曲的名称from apps.songs.models import Song for sid in result: song Song.objects.get(idsid) print(song.title, -, song.artist)这里需要解释一个现象如果所有用户都随机收藏所有歌曲用户之间没有明显偏好差异协同过滤的效果会变差。这一点在毕设文档中可以作为算法局限性讨论章节。9. 常见问题与调试思路前后端分离开发中会遇到不少问题下面整理几个高频问题。问题现象常见原因解决思路前端访问后端接口报 CORS 错误未配置跨域或配置位置不对检查 django-cors-headers 是否安装、middleware 顺序是否正确登录接口返回 401Token 认证配置错误或未携带 token检查 Authorization 请求头检查 DRF 默认认证类访问 API 总是 404URL 路径层级不一致或 App 未注册检查根 urls.py 的 include 规则确认 App 已加入 INSTALLED_APPS数据库迁移报错MySQL 版本与 Django 版本不兼容确认是否安装 pymysql检查数据库字符集推荐结果为空用户行为太少或邻居无相似用户查看协同过滤核心函数的返回条件前端组件库样式不生效Element Plus 未完整导入使用app.use(ElementPlus)全局注册9.1 跨域错误排查流程跨域是前后端分离开发中最常见的坑。在settings.py中注意下面几个点INSTALLED_APPS [ ... corsheaders, ] MIDDLEWARE [ corsheaders.middleware.CorsMiddleware, ... ] CORS_ALLOWED_ORIGINS [ http://localhost:5173, http://127.0.0.1:5173, ]CorsMiddleware 的位置很关键官方文档建议把它放在尽可能靠前的位置最好放在 CommonMiddleware 之前。9.2 推荐效果不理想的排查推荐效果不理想一般不是代码 bug而是数据问题。请依次检查下面几个方向用户之间是否存在足够的“共同行为”。如果用户之间几乎没有共同听过的歌曲相似度会很低。用户行为数据量是否足够。一个用户如果只收藏了一两首歌很难准确推荐。矩阵中歌曲 ID 是否连续有效。如果外键关联的歌曲数据缺失会导致推荐结果中出现空对象。是否做了过滤。推荐列表中不能包含用户已经收藏过的歌曲否则体验会非常差。10. 工程化建议与毕设答辩要点10.1 项目代码组织建议推荐算法代码不要写在 Django 视图函数中。虽然在小项目中一切从简可以实现但从论文写作和后期扩展角度考虑把算法代码独立成 service 模块更合理。推荐下面的文件划分方式recommend/ ├── __init__.py ├── core.py # 协同过滤核心算法 ├── utils.py # 数据处理工具 ├── views.py # API 视图 ├── urls.py # 路由 └── services.py # 连接数据库与算法模块的中间层这样的优点是核心算法不依赖 Django ORM 模型可以直接用字典和列表测试算法逻辑如果后续想换 TensorFlow 或 Surprise 库实现推荐模型只需要更换 services 层不需要改动算法模块。10.2 算法代码如何测试协同过滤算法和普通增删改查接口不太一样最好单独用单元测试验证。在recommend/tests.py中from django.test import TestCase from .core import cosine_similarity class RecommendAlgorithmTest(TestCase): def test_cosine_similarity_basic(self): vec1 {a: 1, b: 1, c: 1} vec2 {a: 1, b: 1, c: 0} sim cosine_similarity(vec1, vec2) self.assertAlmostEqual(sim, 0.816, places2) def test_cosine_similarity_no_common(self): vec1 {a: 1} vec2 {b: 1} self.assertEqual(cosine_similarity(vec1, vec2), 0)答辩时能说明白“我是如何验证算法正确性的”这句话会比单纯展示一个运行截图更有说服力。10.3 常见答辩问题整理老师大概率会围绕这几个问题提问提前准备好对应的回答为什么选择协同过滤而不是深度学习建议回答深度学习模型需要大量数据在毕设数据规模有限的情况下协同过滤算法效果稳定、可解释性强适合作为核心算法同时项目保留了扩展空间。基于用户和基于物品的协同过滤在你项目中哪个效果更好建议回答取决于数据结构。如果用户数量远大于歌曲数量物品相似度计算成本更低毕设项目中两种方案都实现了综合演示效果选择更适合数据分布的方案。冷启动怎么处理建议回答新用户没有行为数据时系统回退为热门推荐新歌曲可以通过管理员后台主动配置推荐位。这是最简单的处理方案也能体现对推荐系统常见问题的理解。推荐系统的评价指标有哪些建议回答离线阶段用准确率、召回率、F1 值、覆盖率在线阶段关注点击率、播放率。毕设中可以加一个简单的离线评测脚本把测试集和训练集分开计算 Top-N 推荐的准确率和召回率。10.4 如何扩展项目提升创新性如果学校要求论文有创新点可以在下面的方向上选择一个做扩展在算法层把基于用户和基于物品的结果做线性加权融合用实验数据说明混合策略能提升效果。在推荐策略层在播放行为基础上引入 TF-IDF 歌曲标签权重让行为分数不再只取 0/1。在展示层增加“相似歌曲推荐”“大家都在听”板块让推荐系统功能更完整。这些扩展点不需要全部实现选择一两个即可。更重要的是论文中要形成算法对比实验表用数据说明你的改进有效。11. 写作与后续学习建议如果你正在准备开题或已经进入开发阶段这里有一条完整的时间线建议。第一阶段先理解协同过滤原理和系统功能划分画出系统流程图、用例图、ER 图完成开题报告。第二阶段搭建并测试 Vue 和 Django 环境完成登录注册和歌曲管理模块。第三阶段实现推荐算法核心函数配合模拟数据验证推荐结果。第四阶段把推荐接口接入前端完成收藏、播放、推荐展示等页面。第五阶段整理测试用例、截图和答辩 PPT重点准备算法流程讲解和项目演示脚本。做毕设时最大的误区是一上来就写代码。先花两天时间把数据库表结构、接口设计和算法流程图画清楚后面写代码的速度会快很多。开发过程中如果遇到问题优先使用 Django 的错误页面、Django Debug Toolbar 等工具定位问题而不是一报错就去复制搜索引擎里不完整的老旧回答。同时养成写测试数据脚本的习惯这样每次修改算法后都可以快速验证推荐效果。最后想多说一句毕业设计是自己独立完成的第一个完整软件项目不必非得出奇出新。把协同过滤的原理讲透、把 Web 系统做完整、把工程规范做到位已经足够拿一个好成绩了。希望这篇分享能帮助你顺利完成自己的音乐推荐系统毕设项目少熬夜少踩坑答辩顺利。
返回列表