ARTICLE DETAIL

资讯详情

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

基于Python的音乐推荐系统:从协同过滤到冷启动实战

基于Python的音乐推荐系统:从协同过滤到冷启动实战 简介一份面向专科与本科毕业生的原创论文资源围绕基于Python的音乐推荐系统完整展开适合需要完成数据挖掘、爬虫或推荐系统方向课题的学生参考。论文从研究背景、意义到音乐数据获取与处理、特征提取、协同过滤与决策树算法设计再到实验与结果分析结构完整覆盖系统实现的关键环节。资源为1个docx文档体积仅30KB文本内容清晰便于直接阅读、修改和格式调整压缩包内仅此一份文件结构一目了然。目前已有七百八十三人学习下载。通过学习这份资料可以了解如何用Requests和BeautifulSoup爬取音乐数据借助pandas完成清洗与特征工程并掌握使用Scikit-learn等框架构建推荐模型的基本思路对于毕业论文选题、框架搭建和算法选型具有较强的参考价值也能为后续系统扩展与优化提供起点。1. 基于python的音乐推荐系统先把行为数据变成可计算的矩阵做音乐推荐系统很多人一上来就规划深度学习模型真正耗时间的往往是数据清洗和相似度计算。常见可靠做法是把用户行为播放次数、收藏、跳过归一化成评分矩阵用协同过滤先把链路跑通。基于 python 的音乐推荐系统设计与实现核心不在模型多新而在从日志到推荐结果的每一步都可解释、可复现。下面按选型、实现、评估、冷启动的顺序推进先定算法再写能直接运行的 pandas、numpy 代码用准确率、召回率和覆盖率做离线评估最后补一个解决新用户冷启动的混合兜底方案。适合正在做毕设或课设的同学也适合想快速验证推荐逻辑的工程师。2. 音乐推荐系统的算法选型为什么协同过滤和 ItemCF 是默认起点2.1 用户-物品矩阵把播放、收藏、跳过归一成评分先明确输入长什么样。音乐平台能拿到的原始行为一般是日志表字段大致如下字段类型说明user_idstring用户标识song_idstring歌曲标识play_countint累计播放次数is_favint是否收藏0/1skip_ratiofloat跳过比例越大越不喜欢tsdatetime行为发生时间数据来源可以是平台导出的行为日志、公开数据集也可以是自己用爬虫抓取的歌单页行为。落库后第一件事是构建可计算的评分。播放次数是长尾分布1000 次和 10 次的差距远大于真实偏好差距所以要做 log 压缩import pandas as pd import numpy as np df pd.read_csv(user_song_behavior.csv) df[ts] pd.to_datetime(df[ts]) # 播放次数做 log1p 压缩避免热门歌曲把相似度带偏 df[rating] np.log1p(df[play_count]) # 收藏是强正向信号直接加权 df[rating] df[rating] df[is_fav] * 1.5 # 跳过比例高的样本降权 df.loc[df[skip_ratio] 0.6, rating] * 0.5 df df[df[rating] 0]逻辑说明np.log1p 在 x 接近 0 时输出接近 x在 x 很大时输出缓慢增长把播放次数压到差异合理、可解释的范围收藏加权 1.5 是经验值想突出主动行为可以提高到 2.0skip_ratio 超过 0.6 说明这首歌一多半被跳过评分砍半是保守处理。最终 rating 为 0 的样本对余弦相似度没有贡献提前删掉能减少矩阵体积。接下来把 DataFrame 映射成稀疏矩阵。用户和歌曲各自编号用 scipy 的 csr 结构存from scipy.sparse import csr_matrix user_ids {u: i for i, u in enumerate(df[user_id].unique())} song_ids {s: i for i, s in enumerate(df[song_id].unique())} row df[user_id].map(user_ids).values col df[song_id].map(song_ids).values data df[rating].values user_item csr_matrix((data, (row, col)), shape(len(user_ids), len(song_ids))) item_user user_item.T.tocsr()参数说明csr_matrix 的三个数组分别是行索引、列索引、数值shape 用去重后的用户数和歌曲数。user_item 是用户 × 歌曲做物品相似度时要转成 item_user。百万级数据下 csr 的优势是转置和行切片快、内存省如果只是课设规模普通 numpy 二维数组也能跑但后面算物品相似度时转置会多一次完整拷贝。2.2 UserCF 与 ItemCF 的取舍音乐场景怎么选协同过滤里有两派UserCF 找口味相似的用户把相似用户听过的歌推给我ItemCF 找和这首歌相似的歌从我听过的歌向外扩展。两者本质都是最近邻区别只在相似度作用在哪个维度。选型先看下表维度UserCFItemCF相似对象用户-用户物品-物品离线计算量随用户量增长快歌曲量稳定适合预计算在线耗时每次要算用户近邻直接查物品相似表可解释性难以直接说明因为喜欢 A 所以推荐 B新物品冷启动无交互则无法推荐无交互则无法参与相似度音乐场景我一般选 ItemCF理由有三个。用户的听歌口味相对稳定歌曲之间的相似度不会每天剧烈变化可以离线算好写入内存或 Redis在线只做查表累加推荐理由容易展示因为你收藏了某歌手的歌所以推荐同类比你有 5 个相似用户也在听直观得多用户量通常比歌曲量大一个数量级UserCF 每个请求都要做一次用户近邻计算压测时 QPS 很难上去。如果数据来自爬虫抓的歌单歌曲去重后数量通常只有几千到几万ItemCF 的相似度矩阵完全放得进内存。2.3 相似度度量怎么选余弦、皮尔逊、杰卡德对照相似度是协同过滤的灵魂参数。音乐行为矩阵以 0 居多稀疏度常在 99% 以上选度量要看两点是否忽略未交互的 0是否对流行度敏感。常用的几种对比如下度量计算要点什么时候用余弦相似度向量夹角0 值不参与方向判断默认首选评分带权重时合适皮尔逊相关系数先减用户均值的余弦评分体系完整、尺度差异大时杰卡德相似度交集除以并集只看 0/1只有收藏/未收藏两种状态时IUF 加权余弦对热门物品做 log 逆频率惩罚要压热门、提升长尾时IUF 的思路和 TF-IDF 里的 IDF 一致一首歌被越多人听过它能提供的区分度越低权重就该越小。常见做法是把它乘到评分矩阵上再算余弦def add_iuf(user_item, n_users): # 每个物品被多少个用户听过注意是用户数而不是播放量 item_cnt np.asarray((user_item 0).sum(axis0)).ravel() iuf np.log((n_users 1) / (item_cnt 1)) # multiply 只影响非零位置稀疏结构不变 return user_item.multiply(iuf)参数说明item_cnt 统计的是有交互的用户数用播放量会被刷榜行为干扰分子分母各加 1 是平滑项防止出现 log 0 的负无穷。multiply 是逐元素乘法0 乘任何数仍是 0所以稀疏矩阵的存储路径不会被破坏。加了 IUF 之后热门歌曲的评分权重被压低长尾歌曲才有机会进推荐列表覆盖率通常能提升 5 到 10 个百分点。3. 用 python 实现音乐推荐核心模块相似度计算与 Top-N 推荐3.1 数据清洗去重、过滤低活跃用户和无人问津的歌曲把上一章的矩阵构造代码存成 recommend.py在 PyCharm 里配好 python 环境就能直接跑。但原始数据一般先要清洗日志经常有重复上报、短活跃用户和只被听过一两次的歌曲。先做一轮去重和过滤df df.sort_values(ts).drop_duplicates([user_id, song_id], keeplast) # 过滤低活跃用户行为少于 10 条的删掉相似度没有统计意义 active df.groupby(user_id)[song_id].count() df df[df[user_id].isin(active[active 10].index)] # 过滤冷门歌被少于 5 个用户听过不参与相似度计算 popular df.groupby(song_id)[user_id].count() df df[df[song_id].isin(popular[popular 5].index)]说明10 和 5 是起始阈值跑完评估再看要不要调。用户阈值设太低相似度全是噪声设太高有推荐结果的用户变少。歌曲阈值同理冷门歌既没有统计意义还会拉大稀疏矩阵的维度。过滤完要重新执行 2.1 的 id 映射否则 shape 里带着被删掉歌曲的空洞索引。清洗完建议顺手做个分布图用 pandas 的 value_counts 统计播放次数分布用 matplotlib 画出来阈值设在哪一档一眼就能看出来。这一步很多人跳过实际是过滤阈值最直接的依据属于数据分析与可视化里最朴素但最管用的用法。3.2 用 numpy 实现物品余弦相似度顺便压一遍热门相似度计算是离线最重的一步。直接写双层 for 循环算 N×N 个物品的余弦物品数 5000 时就要 2500 万次两两计算跑完项目也凉了。正确做法是用矩阵乘法一次算完def cosine_item_sim(item_user): # item_user物品 × 用户 的 csr 矩阵 norm np.sqrt(np.asarray(item_user.power(2).sum(axis1))).ravel() # 矩阵乘法一次性得到所有物品两两点积转稠密方便按行切片 sim (item_user item_user.T).toarray() sim sim / (norm[:, None] * norm[None, :] 1e-9) np.fill_diagonal(sim, 0) # 自己与自己的相似度置 0推荐时不推自己 return sim逻辑说明item_user item_user.T 一次矩阵乘法算出全部两两点积比 for 循环快几个数量级norm[:, None] 把一维模长转成列向量norm[None, :] 转成行向量广播相乘得到模长乘积矩阵1e-9 防止零向量除零。sim 是稠密矩阵物品数 1 万以内内存无压力超过 5 万就建议只保留每行 TopK或者按物品分片计算再用多进程并行归并。3.3 基于相似物品的 Top-N 推荐函数相似度表算完推荐就是查表累加。用户历史里每首歌找它相似度最高的 K 个邻居把用户对历史歌曲的评分 × 邻居相似度累加成候选歌曲得分最后取得分最高的 N 首返回def recommend(user_item, item_sim, user_id, K20, N10): user_vec user_item[user_id] # 该用户的 csr 稀疏行 liked user_vec.indices # 用户听过的歌曲索引 scores {} for i in liked: # 取第 i 首歌最相似的 K 个邻居逆序取前 K k_neighbors np.argsort(item_sim[i])[::-1][:K] for j in k_neighbors: if item_sim[i, j] 0: continue if j in liked: continue # 已听过的不再推荐 scores[j] scores.get(j, 0) item_sim[i, j] * user_vec[0, i] top sorted(scores.items(), keylambda x: x[1], reverseTrue)[:N] return top参数说明K 是邻居半径K 太小候选集窄、容易反复推同一批歌K 太大把弱关联也拉进来N 是最终列表长度前端一般取 10 到 20。得分累加相当于把用户的历史偏好沿相似图传播一层。j in liked 是 Python 的成员判断在线场景下可以换成 set 把判断降到 O(1)。返回的 top 是 (song_id, score) 列表前端渲染前再映射回歌曲名和封面。调参时优先动下表里的三个位置参数含义建议值K每首历史歌曲取多少个相似邻居1030N最终推荐列表长度1020min_sim参与得分的相似度下限00.05提示item_sim 如果提前按每行 TopK 截断保存recommend() 里就不必每次对全行 argsort在线请求量大时能省下大量 CPU 和内存。4. 音乐推荐系统的离线评估与参数调优准确率、召回率、覆盖率4.1 按时间划分训练集和测试集别用随机切分推荐系统评估最忌讳随机打乱用户行为有时序拿未来行为做训练等于提前交卷。正确切分是每个用户按时间线切开前 80% 行为进训练集后 20% 进测试集train_list, test_list [], [] for uid, group in df.groupby(user_id): group group.sort_values(ts) cut int(len(group) * 0.8) train_list.append(group.iloc[:cut]) test_list.append(group.iloc[cut:]) train pd.concat(train_list) test pd.concat(test_list)说明80/20 是经验切分行为特别短的用户可以放宽到保留最后 2 条进测试。切分一定要在 sort_values(ts) 之后做groupby 出来的分组顺序不保证有序。训练集和测试集的歌曲集合会有差异评估前取交集测试里出现训练集没有的新歌属于冷启动样本单独记录不要混进主指标。4.2 用 python 计算 precisionN、recallN 和覆盖率离线评估三个指标记牢精确率看推荐的歌用户真的听了多少召回率看用户测试期听的歌被推荐出来多少覆盖率看推荐结果有没有被头部歌曲垄断。计算函数如下def evaluate(user_item, test_dict, item_sim, N10): hit total_rec total_test 0 rec_songs set() for uid, test_songs in test_dict.items(): recs [sid for sid, _ in recommend(user_item, item_sim, uid, NN)] rec_set, real_set set(recs), set(test_songs) hit len(rec_set real_set) total_rec N total_test len(real_set) rec_songs.update(rec_set) precision hit / total_rec recall hit / total_test coverage len(rec_songs) / user_item.shape[1] return precision, recall, coverage说明test_dict 是 user_id 到测试期真实听过歌曲集合的字典构造方式是把 test DataFrame groupby 再转 set。total_rec 用 N 而不是实际返回条数防止用户行为太少时指标被虚高。覆盖率分母是 user_item 的歌曲维度分子是推荐出现过的歌曲去重数覆盖率低于 5% 基本可以判定推荐被热门歌垄断。举个例子跑完一轮 K20、N10 的评估得到 precision0.12、recall0.09、coverage0.18。意思是推荐列表里 100 首有 12 首被用户真实听过测试期的行为有 9% 被命中推荐覆盖了曲库 18% 的歌。这三个数在不同数据集之间没有绝对可比性你要看的是调参前后的相对变化IUF 打开后 coverage 从 0.11 涨到 0.18recall 只掉了 0.01这组参数就值得保留。4.3 K 值和 N 值怎么调先粗扫再细调参数调优没有一步到位常见做法是先固定 N10把 K 按 5、10、20、30、50 扫一遍画 recall 和 precision 的曲线看 recall 增速变缓的位置再固定最优 K 扫 N。推荐一个手动记录表参数作用扫描范围主要观察K邻居数相似度扩展半径5 / 10 / 20 / 30 / 50recall 与 precision 的平衡N推荐数列表长度5 / 10 / 20覆盖率随 N 上升曲线IUF 开关热门惩罚on / off覆盖率与长尾占比扫参时按用户活跃度分桶看指标行为大于 50 条的用户和少于 10 条的用户分开统计。ItemCF 对低频用户的 recall 天然低如果分桶后差距过大说明要上第 5 章的冷启动兜底策略。5. 冷启动兜底用热度-内容混合策略补上推荐盲区5.1 冷启动为什么是 ItemCF 的结构性弱点ItemCF 的一切都建立在交互矩阵上。新用户没有历史recommend() 里的 user_vec 是空向量循环直接不执行返回空列表新歌没有任何人听过item_sim 对应行全是 0。这不是参数问题是结构问题。常见做法是加一层混合策略ItemCF 分数、内容标签相似度、热度兜底三者加权排序。5.2 一个可直接改权重的混合排序函数def hybrid_score(itemcf_scores, content_sim, hot_scores, alpha0.6, beta0.3): alpha: ItemCF 权重 beta: 内容相似度权重 剩余 1-alpha-beta 分给热度兜底 final {} for sid, s in itemcf_scores.items(): final[sid] (alpha * s beta * content_sim.get(sid, 0) (1 - alpha - beta) * hot_scores.get(sid, 0)) return sorted(final.items(), keylambda x: x[1], reverseTrue)[:10]参数说明alpha 和 beta 之和必须小于 1剩余部分给热度兜底保证 ItemCF 完全失效时列表不为空。content_sim 用歌曲的歌手、流派标签算杰卡德相似度新歌没有交互也有标签这是它补 ItemCF 死角的根本原因。hot_scores 建议用 log1p(总播放量) 归一化到 [0,1]否则头部歌曲的绝对数值会把其他两个信号压没。验证时构造一个只有 2 条行为的模拟新用户分别跑 recommend() 和 hybrid_score()前者大概率返回空列表后者能给出带标签相似和热度排序的结果。冷启动用户的指标单独统计和主指标分开汇报这样评审时能说清楚每个数字的含义。本文还有配套的精品资源点击获取
返回列表