news 2026/9/5 23:20:40

基于协同过滤的Python音乐推荐系统实现与工程落地

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于协同过滤的Python音乐推荐系统实现与工程落地

很多做 Python 毕业设计的同学,看到“推荐系统”四个字,第一反应是先去找算法公式,第二反应是担心“数学不好能不能做”。实际上,毕设里的推荐系统并没有那么神秘。它最核心的协同过滤思想,翻译成大白话就是一句话:把跟你口味相似的人喜欢的歌,推荐给你;或者把你平时听过的歌的“同类歌”,推荐给你。

这个课题能在本科毕设里持续热门,原因是它天然具备“算法有亮点、工程可落地、界面能展示、论文有素材”四个特点。你可以用 Python 独立完成后端推荐引擎,用 Django 暴露接口,再用 Vue 做前端页面。整个过程没有一项技术是超纲的,但它合成的效果,却足以撑起一份完整的毕设项目。

这篇文章会把整个系统从零到一拆开讲,重点解决三类问题:

  • 协同过滤到底是什么,在音乐场景里应该怎么建模;
  • Django 如何描述用户、歌曲和评分行为,算法如何写进业务代码;
  • Vue 前端如何把“为你推荐”变成可点击、可播放、可收藏的真实页面。

内容末尾还整理了常见异常和答辩高频问题,方便直接对照复盘。

1. 先想清楚一件事:你做的不是算法 Demo,而是一个系统

很多人做毕设容易走偏:花两周调一个 Python 脚本,打印出几个相似度数字,就以为推荐系统做完了。但答辩时老师看重的,往往不是你的相似度公式写得多细,而是你有没有把推荐这个动作完整放进业务流程里

推荐系统在真实产品中,应该具备三块联动能力:

  1. 行为采集:用户收藏了哪首歌、听了多少秒、是否评分。没有行为数据,算法就是无源之水。
  2. 算法计算:维护一张“用户-歌曲-评分”关系表,并在此基础上完成相似度计算和 TopN 排序。
  3. 前端触达:推荐结果最终要出现在页面里,以列表形式呈现,比如“猜你喜欢”“相似歌曲”。

论文里可以写“本系统基于协同过滤算法实现个性化推荐”,但工程里真正要落地的是上面这串完整链路。接下来的内容,会以这个链路为主线展开。

1.1 这个系统的技术选型为什么是 Django + Vue

先用一句话总结结论:Django 负责把“数据模型、推荐计算、API 接口”包成后端服务,Vue 负责把后端计算结果渲染成用户能看懂的界面。

这个组合在本科毕设里流行,有三个直接原因:

  • Django 自带 ORM 和 Admin 后台,建表、加数据、管理歌曲不需要额外开发一套后台页面;
  • Django REST Framework 可以快速把 Python 对象转换成 JSON 接口,刚好喂给前端;
  • Vue 单页组件写法直观,用来做“播放器 + 推荐列表 + 收藏按钮”这类交互,开发量可控。

数据库方面,如果是在本机演示,使用 Django 默认的 SQLite 即可。SQLite 单文件部署,对毕设最友好。如果导师明确要求“必须使用 MySQL”,你只需要在项目中替换DATABASES配置,然后重新执行迁移命令,模型层代码不需要大改。

2. 基础概念:协同过滤的两种核心玩法

在写代码之前,需要用“场景化”的方式把协同过滤讲明白。如果你答辩时能用自己的话复述这个东西,老师基本不会再刁难。

2.1 基于用户的协同过滤(User-Based CF)

假设用户 A 喜欢《晴天》《七里香》《夜曲》,用户 B 喜欢《晴天》《七里香》《以父之名》,系统会认为 A 和 B 的听歌口味高度相似,于是把 B 听过的《以父之名》推给 A。

这种玩法更关心“人与人之间的关系”,适合社交属性强、用户量中等的场景。缺点是:系统刚上线时,用户行为太少,很难找到相似用户,也就是常说的冷启动问题。

2.2 基于物品的协同过滤(Item-Based CF)

假设你在听《七里香》,系统发现听过《七里香》的人,有很大概率也收藏了《简单爱》,于是把《简单爱》推荐给你。它不关心你和别人像不像,它关心歌曲与歌曲之间被同时消费的关联性,更适合网易云音乐、QQ 音乐这类曲库相对稳定的产品。

在实际项目中,更稳的做法是同时实现这两种算法,然后在 API 层留一个参数,指定当前走哪套策略。这样不仅论文里能多写一个对比分析点,答辩时也会有更多可以讲的内容。

3. 环境准备与前置条件

下面这组环境清单,按当前主流适配情况整理。具体到某个同学的机器,版本可能略有差异,但这组配置基本可以稳定跑通。

依赖版本建议用途说明
Python3.10+推荐算法与 Django 运行环境
Django4.x LTSWeb 后端框架
djangorestframework3.14+快速生成 JSON API
django-cors-headers4.x解决前后端分离的跨域问题
Vue CLI / ViteVue 3.x前端开发工具链
Node.js18+前端脚手架运行环境
SQLiteDjango 内置本地默认数据库

如果还没装 Python,建议先确认 Python 能正常执行:

python --version pip --version

然后在虚拟环境中安装后端依赖:

mkdir music-recommend && cd music-recommend python -m venv venv source venv/bin/activate # Windows 下执行 venv\Scripts\activate pip install django djangorestframework django-cors-headers

安装完成后创建 Django 项目:

django-admin startproject config . python manage.py startapp music python manage.py startapp recommend

项目结构初步规划如下:

music-recommend/ ├── config/ # Django 主配置 │ ├── settings.py │ └── urls.py ├── music/ # 歌曲与用户行为模块 │ ├── models.py │ └── views.py ├── recommend/ # 推荐算法模块 │ ├── utils.py │ └── views.py ├── manage.py └── db.sqlite3 # 默认数据库文件

接下来把musicrecommend两个应用注册到INSTALLED_APPS中。然后在全局路由里配置 API 前缀:

# config/urls.py from django.contrib import admin from django.urls import path, include urlpatterns = [ path('admin/', admin.site.urls), path('api/music/', include('music.urls')), path('api/recommend/', include('recommend.urls')), ]

这样设计的好处是:后端业务按模块拆分,推荐算法独立成应用。以后想替换算法,只需要改业务逻辑,不需要改路由配置。

4. 后端模型设计:用数据表描述用户行为

在音乐推荐系统里,模型设计直接决定推荐算法的质量。我们以“歌手(Singer)— 歌曲(Song)— 评分(Rating)”三张核心表为例来进行搭建。

打开music/models.py,完整代码如下:

from django.db import models from django.conf import settings class Singer(models.Model): """歌手表""" name = models.CharField(max_length=100, verbose_name='歌手名称') avatar = models.URLField(blank=True, verbose_name='封面地址') description = models.TextField(blank=True, verbose_name='歌手简介') class Meta: verbose_name = '歌手' verbose_name_plural = verbose_name def __str__(self): return self.name class Song(models.Model): """歌曲表""" title = models.CharField(max_length=200, verbose_name='歌曲名称') singer = models.ForeignKey( Singer, on_delete=models.CASCADE, related_name='songs', verbose_name='歌手' ) cover = models.URLField(blank=True, verbose_name='封面地址') audio_url = models.URLField(verbose_name='音频播放地址') duration = models.IntegerField(default=0, verbose_name='时长(秒)') play_count = models.IntegerField(default=0, verbose_name='播放次数') publish_date = models.DateField(null=True, blank=True, verbose_name='发行日期') class Meta: verbose_name = '歌曲' verbose_name_plural = verbose_name ordering = ['-play_count'] def __str__(self): return f'{self.title} - {self.singer.name}' class Rating(models.Model): """用户评分表""" user = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='ratings', verbose_name='用户' ) song = models.ForeignKey( Song, on_delete=models.CASCADE, related_name='ratings', verbose_name='歌曲' ) score = models.IntegerField( default=5, verbose_name='评分(1-5)' ) created_at = models.DateTimeField(auto_now_add=True, verbose_name='评分时间') class Meta: verbose_name = '用户评分' verbose_name_plural = verbose_name unique_together = ('user', 'song') def __str__(self): return f'{self.user.username} 评 {self.song.title}:{self.score}'

这里需要解释几个关键设计决策:

  • Rating使用unique_together = ('user', 'song'),目的是避免用户对同一首歌重复评分。如果用户再次评分,应该走更新逻辑,而不是再插入一条记录。
  • audio_urlcover使用 URLField,前端播放器可以直接使用该地址,方便后续接入实际音频资源。
  • 每张表都定义了verbose_name,这会让 Django Admin 后台的显示更规范,同时处理较为美观。

完成模型后,执行迁移命令:

python manage.py makemigrations python manage.py migrate python manage.py createsuperuser

启动开发服务器后登录后台:http://127.0.0.1:8000/admin/,可以录入几组真实数据。推荐数据条数大致为:歌手 10 个左右,每名歌手关联 5-10 首歌,然后注册 3-5 个测试用户,分别对歌曲进行评分。这些测试数据是算法展示效果的关键基础。

5. 协同过滤推荐算法的核心实现

打开recommend/utils.py,以下代码实现的逻辑属于教学中较常用的“基于物品的协同过滤 + 基于用户的协同过滤”双策略。

from math import sqrt from collections import defaultdict from music.models import Rating, Song def get_user_item_matrix(): """将评分数据转换为 用户 -> {歌曲ID: 评分} 的字典结构""" user_item = defaultdict(dict) ratings = Rating.objects.all().select_related('user', 'song') for r in ratings: user_item[r.user_id][r.song_id] = r.score return dict(user_item) def get_item_user_matrix(user_item): """转换为 歌曲ID -> {用户ID: 评分} 的字典结构,方便计算歌曲相似度""" item_user = defaultdict(dict) for user_id, songs in user_item.items(): for song_id, score in songs.items(): item_user[song_id][user_id] = score return dict(item_user) def cosine_similarity(vec1, vec2): """基于共同评分用户的余弦相似度计算""" common = set(vec1.keys()) & set(vec2.keys()) if not common: return 0.0 dot = sum(vec1[u] * vec2[u] for u in common) norm1 = sqrt(sum([v ** 2 for v in vec1.values()])) norm2 = sqrt(sum([v ** 2 for v in vec2.values()])) if norm1 == 0 or norm2 == 0: return 0.0 return dot / (norm1 * norm2) def user_similarity_matrix(user_item): """计算用户之间的相似度矩阵""" users = list(user_item.keys()) sim_matrix = defaultdict(dict) for i in range(len(users)): for j in range(i + 1, len(users)): sim = cosine_similarity(user_item[users[i]], user_item[users[j]]) if sim > 0: sim_matrix[users[i]][users[j]] = sim sim_matrix[users[j]][users[i]] = sim return dict(sim_matrix) def recommend_by_user_based(user_id, top_n=10): """基于用户的协同过滤推荐""" user_item = get_user_item_matrix() if user_id not in user_item: return [] sims = user_similarity_matrix(user_item) target_user_songs = user_item[user_id] score_dict = defaultdict(float) # 找到与当前用户最相似的若干个用户 sorted_sims = sorted(sims.get(user_id, {}).items(), key=lambda x: x[1], reverse=True)[:5] for other_user_id, sim_value in sorted_sims: for song_id, score in user_item[other_user_id].items(): if song_id in target_user_songs: continue score_dict[song_id] += sim_value * score ranked_songs = sorted(score_dict.items(), key=lambda x: x[1], reverse=True)[:top_n] return [song_id for song_id, _ in ranked_songs] def build_item_similarity_matrix(): """预计算歌曲之间的相似度矩阵,返回 歌曲A -> 歌曲B -> 相似度""" user_item = get_user_item_matrix() item_user = get_item_user_matrix(user_item) item_ids = list(item_user.keys()) sim_matrix = defaultdict(dict) for i in range(len(item_ids)): for j in range(i + 1, len(item_ids)): sim = cosine_similarity(item_user[item_ids[i]], item_user[item_ids[j]]) if sim > 0: sim_matrix[item_ids[i]][item_ids[j]] = sim sim_matrix[item_ids[j]][item_ids[i]] = sim return dict(sim_matrix) def recommend_by_item_based(user_id, top_n=10): """基于物品的协同过滤推荐""" user_item = get_user_item_matrix() if user_id not in user_item: return [] item_sim = build_item_similarity_matrix() user_history = user_item[user_id] score_dict = defaultdict(float) # 遍历用户听过的歌,找出每首歌的相似歌曲 for listened_song_id, rating in user_history.items(): for similar_song_id, sim in item_sim.get(listened_song_id, {}).items(): if similar_song_id in user_history: continue score_dict[similar_song_id] += sim * rating ranked_songs = sorted(score_dict.items(), key=lambda x: x[1], reverse=True)[:top_n] return [song_id for song_id, _ in ranked_songs]

代码里需要重点掌握三个细节:

  • 相似度计算:现在采用的方法是先找出两个向量里的共同元素,再做余弦相似度计算。这里的“向量”可以是一个用户在不同歌曲上的评分序列,也可以是一首歌在不同用户下的评分序列,它不影响代码复用。
  • 两级过滤:相似度矩阵计算完成之后,生成候选集时,会显式跳过用户已经听过的歌,这样可以避免“推荐用户已经听过的歌”这种低体验的场景。
  • 计算时机:在数据量很小的毕设场景里,接口每次临时计算相似度是可行的。但如果你演示时有几百个用户甚至更多,需要在论文里写明“在线计算成本高”,进而选择预先算好相似度矩阵、离线缓存相似度结构的方法。答辩时,老师很可能会顺着这个问题往下问。

6. 用 Django REST Framework 暴露推荐接口

推荐算法算完后,前端需要的不是一个歌曲 ID 列表,而是完整的歌曲信息,例如歌名、歌手、封面和音频地址。所以接口层负责拼接数据返回。

recommend/views.py中编写:

from rest_framework.views import APIView from rest_framework.response import Response from rest_framework.permissions import IsAuthenticated from music.models import Song from music.serializers import SongSerializer from .utils import recommend_by_item_based, recommend_by_user_based class RecommendSongView(APIView): """获取推荐歌曲列表""" permission_classes = [IsAuthenticated] def get(self, request): algorithm = request.query_params.get('algorithm', 'item') user = request.user if algorithm == 'user': song_ids = recommend_by_user_based(user.id, top_n=10) else: song_ids = recommend_by_item_based(user.id, top_n=10) # 防止数据库里还没有评分数据导致报错 songs = Song.objects.filter(id__in=song_ids) if song_ids else Song.objects.none() # 保持推荐顺序:filter(id__in=...) 不会按列表顺序返回,这里手动排序 song_map = {song.id: song for song in songs} ordered_songs = [song_map[sid] for sid in song_ids if sid in song_map] serializer = SongSerializer(ordered_songs, many=True, context={'request': request}) return Response({ 'code': 0, 'message': 'success', 'data': serializer.data })

这里需要说明一个隐藏问题:Song.objects.filter(id__in=song_ids)返回的顺序和song_ids列表的顺序是不一致的。为了让前端展示顺序和算法打分顺序保持一致,我做了一次手动映射排序。这个细节在面试或答辩中如果主动说出来,属于“实战经验型亮点”。

music/serializers.py中定义嵌套的歌曲序列化器:

from rest_framework import serializers from .models import Singer, Song, Rating class SingerSerializer(serializers.ModelSerializer): class Meta: model = Singer fields = ['id', 'name', 'avatar', 'description'] class SongSerializer(serializers.ModelSerializer): singer = SingerSerializer(read_only=True) class Meta: model = Song fields = ['id', 'title', 'singer', 'cover', 'audio_url', 'duration', 'play_count', 'publish_date']

然后在recommend/urls.py中配置路由:

from django.urls import path from .views import RecommendSongView urlpatterns = [ path('songs/', RecommendSongView.as_view(), name='recommend-songs'), ]

同时,在music/urls.py里提供歌曲列表、评分、收藏等常用接口接口。评分接口代码如下:

# music/views.py from rest_framework.views import APIView from rest_framework.response import Response from rest_framework.permissions import IsAuthenticated from django.shortcuts import get_object_or_404 from .models import Song, Rating class RateSongView(APIView): """给歌曲评分,重复评分则更新分数""" permission_classes = [IsAuthenticated] def post(self, request): song_id = request.data.get('song_id') score = request.data.get('score', 5) try: score = int(score) if score < 1 or score > 5: raise ValueError except (TypeError, ValueError): return Response({'code': 400, 'message': '评分需为 1-5 的整数'}) song = get_object_or_404(Song, id=song_id) rating, created = Rating.objects.update_or_create( user=request.user, song=song, defaults={'score': score} ) return Response({ 'code': 0, 'message': '评分成功' if created else '评分已更新', 'data': {'rating_id': rating.id, 'score': rating.score} })

上述代码用到了 Django ORM 中非常实用的一招:update_or_create。它把“有则更新、无则新建”的逻辑压缩成了一行代码,避免先filter().exists()update()的繁琐写法。

7. Vue 前端:把推荐结果变成可交互页面

前端采用 Vue 3 + Axios 开发。这里不展开脚手架创建细节,核心思路是:页面加载时向后端推荐接口发起请求,拿到data后渲染到“推荐歌曲”列表中。

<template> <div class="container"> <h2>猜你喜欢</h2> <div class="toolbar"> <button @click="loadRecommend('item')" :class="{ active: algorithm === 'item' }">相似歌曲推荐</button> <button @click="loadRecommend('user')" :class="{ active: algorithm === 'user' }">相似用户推荐</button> </div> <div v-if="loading" class="loading">推荐加载中...</div> <div v-else class="song-list"> <div class="song-card" v-for="song in songs" :key="song.id"> <img :src="song.cover || defaultCover" alt="封面" class="cover" /> <div class="info"> <div class="title">{{ song.title }}</div> <div class="singer">{{ song.singer.name }}</div> <audio :src="song.audio_url" controls></audio> </div> </div> </div> <p v-if="!loading && songs.length === 0" class="empty"> 暂无推荐结果,请先给几首歌曲打分 </p> </div> </template> <script> import axios from 'axios'; export default { name: 'RecommendList', data() { return { songs: [], loading: false, algorithm: 'item', defaultCover: 'https://via.placeholder.com/100', }; }, methods: { async loadRecommend(algorithm) { this.algorithm = algorithm; this.loading = true; try { const response = await axios.get('/api/recommend/songs/', { params: { algorithm }, headers: { Authorization: `Bearer ${localStorage.getItem('token')}` }, }); this.songs = response.data.data; } catch (error) { console.error('加载推荐失败', error); this.songs = []; } finally { this.loading = false; } }, }, mounted() { this.loadRecommend('item'); }, }; </script> <style scoped> .container { max-width: 900px; margin: 0 auto; padding: 20px; } .toolbar { margin-bottom: 20px; } .toolbar button { margin-right: 10px; padding: 8px 16px; border: 1px solid #ddd; border-radius: 4px; background: #fff; cursor: pointer; } .toolbar button.active { background: #409eff; color: #fff; border-color: #409eff; } .song-card { display: flex; align-items: center; background: #f9f9f9; border-radius: 8px; padding: 12px; margin-bottom: 12px; } .cover { width: 80px; height: 80px; border-radius: 8px; margin-right: 16px; object-fit: cover; } .info { flex: 1; } .title { font-size: 18px; font-weight: bold; } .singer { color: #888; margin: 4px 0 8px; } audio { width: 100%; } .empty { text-align: center; color: #999; padding: 40px 0; } </style>

这个组件里隐藏了一个工程化问题:开发环境里 Vue 页面通过 Axios 请求 Django 后端,涉及“跨域”问题。为了顺利联调,需要在 Django 的settings.py中增加:

INSTALLED_APPS = [ # ... 'corsheaders', ] MIDDLEWARE = [ 'corsheaders.middleware.CorsMiddleware', # ... ] CORS_ALLOW_ALL_ORIGINS = True

生产环境不允许CORS_ALLOW_ALL_ORIGINS = True,这种做法只适合本地开发验证。更稳妥的做法是配置具体的前端域名白名单,例如:

CORS_ALLOWED_ORIGINS = [ 'http://localhost:5173', 'http://127.0.0.1:5173', ]

8. 运行验证与观察推荐效果

完成以上步骤后,按顺序做一轮全链路验证。

8.1 准备测试数据

通过 Django Admin 或命令行录入测试数据,这里给出三条模拟评分数据。用 shell 代码添加测试用户并评分:

python manage.py shell
from django.contrib.auth.models import User from music.models import Singer, Song, Rating singer = Singer.objects.create(name='某知名歌手') song1 = Song.objects.create(title='歌曲A', singer=singer, audio_url='https://example.com/a.mp3') song2 = Song.objects.create(title='歌曲B', singer=singer, audio_url='https://example.com/b.mp3') song3 = Song.objects.create(title='歌曲C', singer=singer, audio_url='https://example.com/c.mp3') u1, _ = User.objects.get_or_create(username='u1', password='pbkdf2_sha256$') u2, _ = User.objects.get_or_create(username='u2', password='pbkdf2_sha256$') Rating.objects.create(user=u1, song=song1, score=5) Rating.objects.create(user=u1, song=song2, score=4) Rating.objects.create(user=u2, song=song1, score=5) Rating.objects.create(user=u2, song=song3, score=3)

执行完成后退出 shell。

注意:上面的password只是占位,在实际登录验证时,需要对这些测试用户设置真实密码:

u1.set_password('123456') u1.save() u2.set_password('123456') u2.save()

8.2 通过接口验证推荐结果

启动 Django:

python manage.py runserver

使用 Token 或 JWT 获取用户身份后,请求:

curl -H "Authorization: Bearer <token>" \ "http://127.0.0.1:8000/api/recommend/songs/?algorithm=item"

预期返回的 JSON 结构如下:

{ "code": 0, "message": "success", "data": [ { "id": 3, "title": "歌曲C", "singer": { "id": 1, "name": "某知名歌手" }, "audio_url": "https://example.com/c.mp3", "play_count": 0 } ] }

8.3 怎么判断推荐是否正常

不要只看“有没有返回数据”,要检查以下三个维度:

  • 推荐列表里是否排除了用户已评分的歌曲;
  • 用户 A 和用户 B 的历史行为不同,调换用户身份请求接口时,推荐结果是否存在差异;
  • 把测试用户历史行为修改为完全离群数据后,推荐结果是否随之改变。

如果两组不同用户的推荐结果完全相同,大概率是评分矩阵没有按用户区分,需要回查get_user_item_matrix()r.user_id取到的是不是同一个用户。

9. 常见问题与排查思路

做这类系统的过程中,大家容易遇到的问题,我做了一个总结式的清单。它本身也是论文“系统测试与问题分析”章节的直接素材。

问题现象可能原因排查方式解决方案
推荐接口报 401前端请求头没有携带 Token打开浏览器 Network 面板查看 Authorization 字段在 Axios 拦截器中统一注入 Token
推荐结果为空当前用户没有任何评分行为调接口前检查数据库中 Rating 表无行为数据时返回热门歌曲兜底,而不是返回空列表
推荐列表顺序与打分不一致filter(id__in=...)不保证顺序打印song_ids列表与接口返回列表对比建立id -> Song映射后手动排序
用户相似度全是 0两个用户没有共同评分过的歌曲检查余弦相似度函数中共同元素集合是否为空补充测试用户之间的共同评分记录
接口请求跨域前后端端口不同查看浏览器 Console 的 CORS 错误配置django-cors-headers并在白名单里添加前端地址
item 推荐与 user 推荐结果相同用户相似度矩阵或物品相似度矩阵存在退化打印矩阵查看相似度较大值是否集中在少数歌曲上增加差异化数据;检查是否误传入同一张矩阵
用户重复评分的记录出现多条没有为user + song建唯一约束查看 Django Admin 的 Rating 记录在 Meta 中添加unique_together,使用update_or_create保证幂等
新用户登录后没有推荐冷启动问题检查该用户是否产生过行为记录冷启动阶段用“热门歌曲榜”做替代推荐方案
音频地址无法播放在线音频资源本身跨域或失效用浏览器直接打开audio_url测试替换为可公开访问的音频直链,或在前端配置代理
算法返回极慢在线计算所有相似度,未做缓存统计接口耗时时长离线预计算相似度矩阵并缓存到文件或数据库

9.1 冷启动怎么在代码头解决

“冷启动”是答辩时老师最常问的问题之一。实际产品中面对新用户没有行为数据,推荐系统无法建模。毕设 Demo 可以采用常见工程解法:

  • 新用户注册后,默认展示全站播放量最高的 Top 10 歌曲,作为热门推荐;
  • 只有在用户产生足够评分之后,系统才启用协同过滤推荐;
  • 在接口层的判断逻辑为:先检查用户是否存在评分记录,不存在则走热门兜底逻辑。这样不存在“推荐结果为空”的问题,可以直接跨过数据不足带来的体验尴尬。

热门兜底的推荐接口可简单实现为:

from music.models import Song from music.serializers import SongSerializer def recommend_hot_songs(top_n=10): """冷启动阶段:没有行为数据时返回全站热门""" songs = Song.objects.order_by('-play_count')[:top_n] return songs

9.2 “评分预测”和“TopN 推荐”有什么区别

很多教材会把协同过滤分成两个问题方向:

  • 评分预测:目标是预测用户会给某首歌打几分,常用评估指标是 RMSE、MAE;
  • TopN 推荐:目标是生成一个用户可能喜欢的歌曲列表,常用评估指标是准确率、召回率。

本系统采用的方式是 TopN 推荐,因此返回结果的接口设计,用列表形式呈现推荐候选歌曲。很多同学在论文里把这两类问题混着写,导致“问题定义不清”直接被导师打回。建议在第 4 章就明确写下这一段:本系统完成的是 TopN 推荐,因此评估指标不采用 RMSE,而是重点分析推荐结果是否贴合用户兴趣。

10. 最佳实践与工程建议

如果你的目标不只是一份“能跑过的代码”,而是希望导师觉得你具备工程能力和安全规范,可以把下面的建议内化进代码与文档中。

10.1 算法层:相似度矩阵“离线计算,在线读取”

项目数据量较小时,接口内实时计算相似度完全没问题。但如果是答辩现场演示,点击一次推荐按钮要卡住三四秒,体验会很糟糕。更规范的做法是:

  • 写一个manage.pycommand 或定时任务;每隔一段时间预计算一次相似度矩阵;
  • 把矩阵结果缓存进数据库或内存,例如使用 JSON 字段或 Redis;
  • 用户请求时,直接读取缓存好的相似度矩阵;只对当前用户执行打分求和 + TopN 排序。这样可大幅优化响应时间。

在论文系统设计部分,你可以给出这样的说明:“系统采用离线计算、在线推荐的服务架构”。老师听了会觉得你有生产环境意识。

10.2 冷启动与数据稀疏问题要写进论文

更完整的协同过滤毕设,都需要回应“数据稀疏”的经典质疑。你的论文里最好有一小节专门说明:

  • 当用户-歌曲评分矩阵非常稀疏时,基于共同评分的余弦相似度可能不准确;
  • 当前系统使用了“热门歌曲兜底”策略,保证数据不足时也能返回可用推荐;
  • 未来改进方向可以加入基于内容特征的 Bandit 策略或 Embedding 化召回。

10.3 数据与代码安全

既然是系统开发,建议遵循与实际工业一致的部署意识:

  • 使用 Djangocreatesuperuser录入的测试密码,不要在代码中明文保存管理员口令。
  • 生成接口返回歌曲详情时,只返回与前端展示有关的字段;涉及内部状态字段的处理建议单独设计。
  • 批量写测试数据时先备份好原始数据,再执行 Django 的 shell 脚本。
  • 推荐接口需要登录态验证,不推荐将接口完全公开。

10.4 前后端分离配置建议

如果你的前端使用的是 Vite 开发服务器,Proxy 方式比跨域更自然。在vite.config.js中增加:

export default { server: { proxy: { '/api': { target: 'http://127.0.0.1:8000', changeOrigin: true, }, }, }, };

配置好代理之后,前端代码里的请求地址可以统一写成相对路径/api/xxx,不需要在多个组件里硬编码完整域名。

10.5 包管理和版本锁定

毕设提交时,建议统一锁定版本,避免换一台电脑跑不起来。可使用:

pip freeze > requirements.txt

提交的代码包里保留requirements.txt,并附上版本说明。

11. 总结与后续学习方向

至此,你已经有能力完整地搭建一个前后端分离的音乐推荐系统:

  • 使用 Django 描述了歌手、歌曲和用户评分三类核心数据;
  • 基于用户评分矩阵,实现了基于物品与基于用户两种协同过滤算法;
  • 通过 Django REST Framework 把推荐结果转换成 JSON 接口;
  • 用 Vue 制作了一个“猜你喜欢”页面,并能切换两套算法查看效果;
  • 梳理了冷启动、相似度计算、接口顺序等问题,并给出排查方法。

如果这个项目用作毕业设计,接下来的优化方向我建议按你论文的定位来:

  • 从算法对比角度切入的同学,可以增加评价指标,比如把推荐集合与用户真实喜好进行比对;
  • 从系统实现角度切入的同学,可以把评分矩阵的构造做成动态采集,并把播放行为变成可量化的隐式反馈;
  • 对前端展示有追求的同学,可以在列表里加入收藏、歌词滚动、播放历史,形成更完整的用户闭环。

整理代码和文档时,建议专门把“推荐算法如何从模型设计一路走到前端展示”这条主线写清楚。毕设答辩最看重的是主线清晰,所有模块最好都能像链条一样衔接自然。代码跑通后,再回头给自己的软件配上一份可复现的操作说明,这份工作对未来的项目复盘和改进都会有明显帮助。

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

基于Jetson Nano与STM32的AI视觉机械臂:从YOLOv8部署到串口控制实战

简介&#xff1a;本资源是一套面向嵌入式AI开发者的端侧智能控制完整实践方案&#xff0c;聚焦Jetson Nano与STM32协同实现垃圾分类模型部署及舵机闭环控制&#xff0c;适用于具备C语言基础与嵌入式开发经验的进阶学习者。资源共110个文件&#xff0c;涵盖38个C源码&#xff08…

作者头像 李华
网站建设 2026/9/5 23:16:06

洗烘一体机真的省水省电?从冷水洗原理到实测方法全解析

把洗衣从“下班后的持久战”里解放出来&#xff0c;是这两年很多家庭换家电时最真实的需求&#xff1a;要洗得干净、不伤衣服&#xff0c;最好不用长时间晾晒&#xff1b;偶尔碰上连阴雨&#xff0c;又希望烘干能真正救急。看到“小天鹅 TD12VE10PRO 洗烘一体机”这类产品被放到…

作者头像 李华