Python音乐推荐系统(协同过滤算法)

项目名称:Python音乐推荐系统(协同过滤算法)

项目功能: 📖 1:用户登陆注册,2:全部音乐的展示,3:基于协同过滤算法和Svg算法的推荐音乐展示,3:根据被用户收藏喜欢的次数的热门音乐展示,4:根据数据库相关字段的音乐数据可视化展示,5:个人中心页面的展示,6:音乐的播放和动画,7:管理员后台的实现~

项目涉及技术:Python,Django,MySQL,协同过滤算法,Bootstrap,前端...

项目核心代码展示:

数据库表(项目的数据字段)

from django.contrib.auth.models import User
from django.db import models


# 用户信息
class UserProfile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    likes = models.ManyToManyField('Music', blank=True, related_name='like_users')
    dislikes = models.ManyToManyField('Music', blank=True, related_name='dislike_users')
    first_run = models.BooleanField('是否第一次运行,执行冷启动策略', default=True)
    genre_subscribe = models.TextField('流派订阅', blank=True)
    language_subscribe = models.TextField('语言订阅', blank=True)

    def __str__(self):
        return self.user.username

    class Meta:
        verbose_name = '用户信息'
        verbose_name_plural = verbose_name


# 音乐
class Music(models.Model):
    song_name = models.CharField('歌曲名称', max_length=1000)
    song_length = models.IntegerField('歌曲长度 单位为ms')
    genre_ids = models.CharField('歌曲流派', max_length=100)
    artist_name = models.CharField('歌手', max_length=1000)
    composer = models.CharField('作曲', max_length=1000)
    lyricist = models.CharField('作词', max_length=1000)
    language = models.CharField('语种', max_length=20)
    url = models.CharField('歌曲链接', max_length=1000, default="https://m701.music.126.net/20240301234259/3c4c6553837086cd21eb6013475d9d05/jdymusic/obj/wo3DlMOGwrbDjj7DisKw/27978919250/663c/4088/7b7a/0c48207cb013f953f46fe2da7dd7f803.mp3")

    def __str__(self):
        return self.song_name

    class Meta:
        verbose_name = '音乐信息'
        verbose_name_plural = verbose_name

路由(项目的页面站点)

from django.conf import settings
from django.conf.urls.static import static
from django.contrib import admin
from django.urls import path, include

from music import views

# 主路由
urlpatterns = [
    path('grappelli/', include('grappelli.urls')),  # 后台
    path('admin/', admin.site.urls),  # 后台
    path('', views.home),  # 首页
    path('recommend', views.recommend),  # 推荐
    path('get_top_liked_music', views.get_top_liked_music),  # 热门音乐
    path('music_realize', views.music_realize),  # 音乐可视化
    path('sign_in', views.sign_in),  # 登录
    path('sign_up', views.sign_up),  # 注册
    path('logout', views.user_logout),  # 退出
    path('like/<int:pk>', views.like),  # 喜欢
    path('dislike/<int:pk>', views.dislike),  # 不喜欢
    path('play', views.play),  # 播放
    path('play/<int:pk>', views.play),  # 播放
    path('user', views.user_center),  # 用户中心
    path('search', views.search),  # 搜索
]

urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

后端业务:

recommend.py

import os

import django
import pandas as pd
from django.contrib import messages
from django.http import HttpRequest
from surprise import Dataset, Reader, Prediction
from surprise import SVD

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "MusicRecommendSystem.settings")
django.setup()

from django.contrib.auth.models import User
from music.models import UserProfile, Music

current_request = None

'''
SVD(Singular Value Decomposition)是一种基于矩阵分解的算法,通常用于推荐系统中的评分预测。
在Surprise库中的SVD算法采用了隐式反馈数据来进行预测。
SVD算法的基本原理是将用户-物品评分矩阵分解为多个低秩矩阵的乘积,从而捕捉用户和物品的隐含特征。
SVD算法的原理步骤:
1. 隐含特征表示:SVD将用户-物品评分矩阵分解为三个矩阵的乘积,即U、S和V^T。
其中,U矩阵表示用户的隐含特征,S矩阵为奇异值矩阵,V^T矩阵表示物品的隐含特征。
2. 降维处理:奇异值矩阵S中的奇异值按降序排列,取前k个奇异值,可以实现对矩阵的降维处理。这样可以减少计算量,并聚焦于较重要的特征。
3. 重构评分矩阵:将U、S和V^T的前k列分别取出,构成降维后的矩阵。然后将它们相乘,再根据需要减去均值,即可得到重构的评分矩阵。
4. 预测评分:利用重构的评分矩阵,可以预测用户对未评分物品的评分。
预测评分的方法可以是简单的矩阵相乘,也可以加入一些调整因子如全局偏差、用户偏差和物品偏差等。
通过以上步骤,SVD算法可以在用户-物品评分矩阵的基础上,对未评分物品进行预测。
预测的评分值可以用于推荐系统中的排序和推荐过程。

需要注意的是,SVD算法在处理大规模数据时可能会面临内存和计算效率的问题。
'''


# 获取数据库中所有用户数据
def build_df():
    data = []
    for user_profile in UserProfile.objects.all():  # 获取所有的用户信息
        for like_music in user_profile.likes.all():  # 循环获取所有用户喜欢的歌曲
            data.append([user_profile.user.id, like_music.pk, 1])  # 保存数据data = [[1,1,1],]
        for dislike_music in user_profile.dislikes.all():  # 循环获取所有用户不喜欢的歌曲
            data.append([user_profile.user.id, dislike_music.pk, 0])  # 保存数据data = [[1,2,0]]

    return pd.DataFrame(data, columns=['userID', 'itemID', 'rating'])  # 存储格式


'''
userID  itemID  rating
    2       1       1
    2       4       1
    2       9       1
    2       2       0
    2       3       0
    ...     ...     ...
'''


# 根据用户的评分数据来构建预测模型,并返回一组推荐的音乐列表
def build_predictions(df: pd.DataFrame, user: User):
    userId = user.id  # 获取用户的ID
    profile = UserProfile.objects.filter(user=user)  # 查找用户的个人资料信息
    if profile.exists():
        profile_obj: UserProfile = profile.first()
    else:
        return []
    # print("profile", profile)  # <QuerySet [<UserProfile: wyucnk>]>
    # 使用Surprise库中的Reader和Dataset类来构建评分数据集
    # 指定评分范围为0到1之间的连续值
    reader = Reader(rating_scale=(0, 1))
    # 将数据加载到评分数据集中
    data = Dataset.load_from_df(df[['userID', 'itemID', 'rating']], reader)
    # 使用评分数据集构建训练集
    trainset = data.build_full_trainset()
    # 创建一个SVD算法对象
    algo = SVD()
    # 通过algo.fit(trainset)方法训练算法,将训练集数据拟合到算法中
    # 训练完毕
    algo.fit(trainset)

    # 取出当前所有有人评分过的歌曲,并去重
    subsets = df[['itemID']].drop_duplicates()
    # print('subsets', subsets)
    '''
    itemID
       1
       4
       9
       2
       3
       ...
    '''
    # 测试集
    testset = []
    # 从评分数据中提取出所有被评分过的音乐,并将其作为测试集
    for row in subsets.iterrows():
        # 测试集中的评分值被设置为0,因为我们只是想预测用户是否会喜欢这些音乐,而不关心具体的评分值。
        testset.append([userId, row[1].values[0], 0])
    # print('testset', testset)
    '''
     [[4, 1, 0], [4, 4, 0], [4, 9, 0], [4, 2, 0], [4, 3, 0], [4, 5, 0], [4, 19, 0], [4, 21, 0], [4, 63, 0], 
    '''
    # 用训练好的算法对测试集进行预测,返回一个包含预测结果的列表
    predictions = algo.test(testset, verbose=True)
    # print('predictions', predictions)
    '''
    预测结果:用户id为4的用户对物品id为1的物品的评分预测为0.8837081860340208
    r_ui表示实际评分为0,est表示预测评分为0.8837081860340208。
    details中的was_impossible为False表示该预测能够完成,不是不可能的预测。
    [Prediction(uid=4, iid=1, r_ui=0, est=0.8837081860340208, details={'was_impossible': False}), 
    '''
    result_set = []
    user_like = profile_obj.likes.all()  # 当前用户喜欢的
    user_dislike = profile_obj.dislikes.all()  # 当前用户不喜欢的
    # 遍历所有预测结果
    for item in predictions:
        prediction: Prediction = item
        # 对于预测评分高于0.99的音乐,它从数据库中获取相应的音乐对象
        if prediction.est > 0.99:
            music = Music.objects.get(pk=prediction.iid)
            # 检查是否用户已经喜欢或不喜欢该音乐,如果是,则跳过该音乐。
            if music in user_like:
                continue
            if music in user_dislike:
                continue
            result_set.append(music)
    if len(result_set) == 0:
        messages.error(current_request, '你听的歌太少了,多听点歌再来吧~')
    # print('result_set', result_set)
    '''
     [<Music: 愛我的資格>, <Music: 裂縫中的陽光 (Before Sunrise)>, <Music: PLAYING WITH FIRE>, 
    '''
    return result_set


# 获取用户流派推荐
def build_genre_predictions(user: User):
    predictions = []
    profile = UserProfile.objects.filter(user=user)  # 用户信息
    if profile.exists():
        profile_obj: UserProfile = profile.first()
    else:
        return predictions

    genre_subscribe = profile_obj.genre_subscribe.split(',')  # 获取用户订阅的流派
    user_like = profile_obj.likes.all()  # 获取用户喜欢的音乐
    user_dislike = profile_obj.dislikes.all()  # 获取用户不喜欢的音乐

    # 查找遍历用户喜欢流派的所有音乐
    for music in Music.objects.filter(genre_ids__in=genre_subscribe):
        if music in user_like:
            continue
        if music in user_dislike:
            continue
        predictions.append(music)

    return predictions


# 构建语言推荐
def build_language_predictions(user: User):
    predictions = []
    profile = UserProfile.objects.filter(user=user)
    if profile.exists():
        profile_obj: UserProfile = profile.first()
    else:
        return predictions

    language_subscribe = profile_obj.language_subscribe.split(',')  # 获取用户喜欢的语言
    user_like = profile_obj.likes.all()
    user_dislike = profile_obj.dislikes.all()

    for music in Music.objects.filter(language__in=language_subscribe):
        if music in user_like:
            continue
        if music in user_dislike:
            continue
        predictions.append(music)

    return predictions


# 构建推荐
def build_recommend(request: HttpRequest, user: User):
    global current_request
    current_request = request
    predictions = []
    predictions.extend(build_predictions(build_df(), user))  # 算法预测
    if not predictions:
        predictions.extend(build_genre_predictions(user))  # 流派推荐
        predictions.extend(build_language_predictions(user))  # 语言推荐
    return predictions


if __name__ == '__main__':
    # print(build_df())  # 获取用户数据
    print(build_predictions(build_df(), User.objects.get(pk=4)))  # 算法推荐
    print(build_genre_predictions(User.objects.get(pk=4)))  # 流派推荐
    print(build_language_predictions(User.objects.get(pk=4)))  # 语言推荐

view.py

from django.contrib import messages
from django.contrib.auth import authenticate, login, logout
from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User
from django.core.paginator import Paginator
from django.http import HttpResponseRedirect
from django.shortcuts import render, get_object_or_404
from django.db.models import Count
from .decorators import cold_boot
from .models import Music, UserProfile
from .recommend import build_recommend
from .subscribe import build_genre_ids, build_languages
from django.db.models import Count, F, Value
from django.db.models.functions import Coalesce
from collections import defaultdict
# 当前播放
current_play = None
# 当前推荐
current_recommend = []
# 首页
def home(request):
    return all(request)

@cold_boot
def all(request):
    page_number = request.GET.get('page', 1)
    queryset = Music.objects.all()
    paginator = Paginator(queryset, 10)  # 分页
    musics = paginator.page(page_number)
    context = {
        'musics': musics,
        'user_likes': [],
        'user_dislikes': []
    }
    # 如果登录的首页
    if request.user.is_authenticated:
        user_profile = UserProfile.objects.filter(user=request.user)
        if user_profile.exists():
            user_profile = user_profile.first()  # 用户信息
            context['user_likes'] = user_profile.likes.all()  # 获取用户喜欢或不喜欢的数据
            context['user_dislikes'] = user_profile.dislikes.all()
    return render(request, 'list.html', context)

# 注册
def sign_up(request):
    if request.method == 'POST':
        username = request.POST.get('username')
        password = request.POST.get('password')
        if User.objects.filter(username=username).exists():
            messages.add_message(request, messages.ERROR, '该用户已存在!')
        else:
            user_obj = User.objects.create_user(username=username, password=password)
            UserProfile.objects.create(user=user_obj)
            messages.add_message(request, messages.SUCCESS, '注册成功!')
            return HttpResponseRedirect('/sign_in')
    return render(request, 'sign_up.html')

# 登录
def sign_in(request):
    if request.method == 'POST':
        username = request.POST.get('username')
        password = request.POST.get('password')
        user = authenticate(request, username=username, password=password)
        if user is not None:
            login(request, user=user)
            messages.success(request, '登录成功')
            return HttpResponseRedirect('/')
        else:
            messages.add_message(request, messages.ERROR, '用户名或密码错误!')
            return HttpResponseRedirect('/')
    else:
        return render(request, 'sign_in.html')
# 退出登录
@login_required(login_url='/sign_in')
def user_logout(request):
    logout(request)
    messages.info(request, '退出登录')
    return HttpResponseRedirect('/')

@login_required(login_url='/sign_in')
@cold_boot
def recommend(request):
    page_number = request.GET.get('page', 1)

    # -------------------- 推荐 --------------------------
    recommend_set = build_recommend(request, request.user)  # 获取推荐的数据
    # -------------------- 推荐 --------------------------

    paginator = Paginator(recommend_set, 10)  # 分页
    musics = paginator.page(page_number)  # 推荐的音乐
    context = {
        'musics': musics,
        'user_likes': [],
        'user_dislikes': []
    }
    user_profile = UserProfile.objects.filter(user=request.user)
    if user_profile.exists():
        user_profile = user_profile.first()
        context['user_likes'] = user_profile.likes.all()
        context['user_dislikes'] = user_profile.dislikes.all()
    return render(request, 'list.html', context)

# 用户添加喜欢
@login_required(login_url='/sign_in')
def like(request, pk: int):
    user_obj = UserProfile.objects.get(user=request.user)
    music_obj = get_object_or_404(Music.objects.all(), pk=pk)  # 通过id查找歌曲信息
    user_obj.likes.add(music_obj)  # 添加喜欢
    user_obj.dislikes.remove(music_obj)  # 删除不喜欢
    messages.add_message(request, messages.INFO, '已经添加到我喜欢')
    redirect_url = request.GET.get('from', '/')
    if 'action' in request.GET:
        redirect_url += f'&action={request.GET["action"]}'
    return HttpResponseRedirect(redirect_url)

# 用户添加不喜欢
@login_required(login_url='/sign_in')
def dislike(request, pk: int):
    user_obj = UserProfile.objects.get(user=request.user)
    music_obj = get_object_or_404(Music.objects.all(), pk=pk)  # 通过id查找歌曲信息
    user_obj.dislikes.add(music_obj)  # 添加到不喜欢
    user_obj.likes.remove(music_obj)  # 删除喜欢
    messages.add_message(request, messages.INFO, '已经添加到我不喜欢')
    redirect_url = request.GET.get('from', '/')
    if 'action' in request.GET:
        redirect_url += f'&action={request.GET["action"]}'
    return HttpResponseRedirect(redirect_url)

# 播放歌曲
def play(request, pk: int = 0):
    global current_play
    if pk > 0:  # 存在id
        music_obj = Music.objects.filter(pk=pk)
        if music_obj.exists():
            current_play = music_obj.first()
    if current_play is None:
        messages.error(request, '当前没有正在播放的音乐')
        return HttpResponseRedirect('/')
    return render(request, 'play.html', context={
        'music': current_play
    })

# 用户信息
@login_required(login_url='/sign_in')
def user_center(request):
    user_profile = UserProfile.objects.filter(user=request.user)
    if user_profile.exists():
        profile_obj: UserProfile = user_profile.first()
    else:
        messages.error(request, '找不到用户资料,请重新登录')
        logout(request)
        return HttpResponseRedirect('/')
    # 添加个人信息
    if request.method == 'POST':
        genres = request.POST.getlist('genres', '')
        languages = request.POST.getlist('languages', '')
        profile_obj.first_run = False
        if len(genres) > 0:
            profile_obj.genre_subscribe = ','.join(genres)
            profile_obj.save()
            messages.success(request, '修改流派订阅成功!')
        elif not profile_obj.first_run:
            profile_obj.genre_subscribe = ''
            profile_obj.save()
            messages.success(request, '修改流派订阅成功!')
        if len(languages) > 0:
            profile_obj.language_subscribe = ','.join(languages)
            profile_obj.save()
            messages.success(request, '修改语言订阅成功!')
        elif not profile_obj.first_run:
            profile_obj.language_subscribe = ''
            profile_obj.save()
            messages.success(request, '修改语言订阅成功!')
    context = {
        'user_likes': profile_obj.likes.all(),
        'user_dislikes': profile_obj.dislikes.all(),
        'genres': build_genre_ids(),
        'languages': build_languages(),
        'genre_subscribe': profile_obj.genre_subscribe.split(','),
        'language_subscribe': []
    }
    # 去除空字符
    for lang in profile_obj.language_subscribe.split(','):
        lang = lang.strip()
        context['language_subscribe'].append(lang)
    return render(request, 'user.html', context=context)

# 搜索歌曲
def search(request):
    if 'keyword' not in request.GET:
        messages.error(request, '请输入搜索关键词')
        return HttpResponseRedirect('/')
    keyword = request.GET.get('keyword')
    action = request.GET.get('action')
    # 两种方式搜索
    musics = []
    if action == 'song_name':
        musics = Music.objects.filter(song_name__contains=keyword)
    if action == 'artist_name':
        musics = Music.objects.filter(artist_name__contains=keyword)
    messages.info(request, f'搜索关键词:{keyword},找到 {len(musics)} 首音乐')
    context = {
        'musics': musics,
        'user_likes': [],
        'user_dislikes': []
    }
    if request.user.is_authenticated:
        user_profile = UserProfile.objects.filter(user=request.user)
        if user_profile.exists():
            user_profile = user_profile.first()
            context['user_likes'] = user_profile.likes.all()
            context['user_dislikes'] = user_profile.dislikes.all()
    return render(request, 'list.html', context)

def get_top_liked_music(request):
    def get_top_liked_musics():
        # 获取所有被喜欢的歌曲及其喜欢数量
        top_liked_musics = Music.objects.filter(
            like_users__isnull=False  # 确保只考虑有用户喜欢的歌曲
        ).annotate(
            like_count=Count('like_users')  # 计算每首歌曲的喜欢数量
        ).order_by('-like_count')  # 按喜欢数量降序排序

        return top_liked_musics[:50]
    # 使用示例
    top_liked_musics = get_top_liked_musics()
    for music in top_liked_musics:
        print(f"歌曲名称: {music.song_name}")
        print(f"被标记为喜欢的用户数量: {music.like_count}")
        # 如果需要,您可以继续打印其他字段
        # print(f"歌曲长度: {music.song_length} ms")
        # ...(其他字段)
        print("---")
    context = {'top_liked_musics': top_liked_musics}
    return render(request,'get_top_liked_music.html',context)

# 音乐可视化分析
def music_realize(request):
    # 使用 defaultdict 来统计每个流派下每种语种的音乐数量
    genre_language_counts = defaultdict(lambda: defaultdict(int))

    # 用于存储所有唯一的流派和语种
    all_genres = set()
    all_languages = set()

    # 查询所有音乐
    for music in Music.objects.all():
        genre = music.genre_ids
        language = music.language

        # 更新统计
        genre_language_counts[genre][language] += 1

        # 添加到唯一集合中
        all_genres.add(genre)
        all_languages.add(language)

    # 将 defaultdict 转换为列表格式
    result_list = []
    for genre, languages in genre_language_counts.items():
        sublist = [languages.get(lang, 0) for lang in sorted(all_languages)]  # 使用 sorted 确保顺序一致
        result_list.append(sublist)

    # 将集合转换为列表
    genres_list = sorted(list(all_genres))
    languages_list = sorted(list(all_languages))
    print(languages_list)
    print(genres_list)
    print(result_list)
    ##############################

    # 使用 values 和 annotate 方法进行分组和计数
    language_counts = Music.objects.values('language').annotate(count=Count('id'))

    # 将查询结果转换为字典列表
    result_language = [{'name': item['language'], 'value': item['count']} for item in language_counts]

    for item in result_language:
        print(item)
    ################################
    # 使用 values 和 annotate 方法进行分组和计数
    genre_counts = Music.objects.values('genre_ids').annotate(count=Count('id'))

    # 将查询结果转换为字典列表
    result_genre = [{'name': item['genre_ids'], 'value': item['count']} for item in genre_counts]

    for item in result_genre:
        print(item)

    context = {'genres_list': genres_list, 'languages_list':languages_list, 'result_list':result_list,'result_genre':result_genre, 'result_language':result_language}
    return render(request, 'music_realize.html',context)

项目部分截图:

 

  

 最后:项目可以私信我,项目简单,适合学习参考和二次修改开发~

基于python音乐推荐系统设计与实现源码+文档说明,个人经导师指导并认可通过的高分设计项目,评审分98分,项目中的源码都是经过本地编译过可运行的,都经过严格调试,确保可以运行!主要针对计算机相关专业的正在做大作业、毕业设计的学生和需要项目实战练习的学习者,资源项目的难度比较适中,内容都是经过助教老师审定过的能够满足学习、使用需求,如果有需要的话可以放心下载使用。 基于python音乐推荐系统设计与实现源码+文档说明基于python音乐推荐系统设计与实现源码+文档说明基于python音乐推荐系统设计与实现源码+文档说明基于python音乐推荐系统设计与实现源码+文档说明基于python音乐推荐系统设计与实现源码+文档说明基于python音乐推荐系统设计与实现源码+文档说明基于python音乐推荐系统设计与实现源码+文档说明基于python音乐推荐系统设计与实现源码+文档说明基于python音乐推荐系统设计与实现源码+文档说明基于python音乐推荐系统设计与实现源码+文档说明基于python音乐推荐系统设计与实现源码+文档说明基于python音乐推荐系统设计与实现源码+文档说明基于python音乐推荐系统设计与实现源码+文档说明基于python音乐推荐系统设计与实现源码+文档说明基于python音乐推荐系统设计与实现源码+文档说明基于python音乐推荐系统设计与实现源码+文档说明基于python音乐推荐系统设计与实现源码+文档说明基于python音乐推荐系统设计与实现源码+文档说明基于python音乐推荐系统设计与实现源码+文档说明基于python音乐推荐系统设计与实现源码+文档说明基于python音乐推荐系统设计与实现源码+文档说明基于python音乐推荐系统设计与实现源码+文档说明基于python音乐推荐系统设计与实现
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小熊Coding

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值