ARTICLE DETAIL

资讯详情

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

协同过滤音乐推荐系统实战:从评分矩阵到SVD与冷启动

协同过滤音乐推荐系统实战:从评分矩阵到SVD与冷启动 简介基于协同过滤算法的音乐推荐系统研究与实现毕业论文属于西南财经大学学士学位论文面向计算机、大数据、人工智能等相关专业学生、研究生及推荐算法研究者。论文系统阐述协同过滤推荐算法的核心原理包括用户-用户协同过滤、物品-物品协同过滤以及基于矩阵分解的模型协同过滤并完整设计音乐推荐系统覆盖数据预处理、特征提取与表示、推荐算法模型构建、系统搭建与实验评估等环节。资源为单个docx文档大小37KB目前已有六百四十二人学习下载。读者通过该论文可掌握协同过滤算法的实现细节与效果评估方法理解冷启动、数据稀疏性等实际问题的处理思路同时获得一篇结构完整、可直接参考的毕业论文范本有助于学术研究和毕业设计撰写。1. 从毕业论文到协同过滤音乐推荐引擎可复现的入门问题当你拿到一份名为《基于协同过滤算法的音乐推荐系统的研究与实现》的毕业论文资源包时最容易犯的错是把它从头读到尾然后感叹“原理都懂代码在哪”。其实这份毕业设计已经把推荐系统的主干写清楚了用户-用户协同过滤、物品-物品协同过滤、基于 SVD 的模型化方法以及从数据预处理到实验评估的完整链路。对正在做推荐系统方向毕业设计、或者想在一个具体领域落地推荐算法的工程师来说这份文档的真正价值在于能拆成可运行的工程实践。下面我会沿着论文的研究顺序把相似度计算、用户歌曲评分矩阵构建、离线训练和冷启动处理整理成带参数的实现方案并标注哪些参数会直接影响实验结果供你直接改。2. 协同过滤的两条主路径用户-用户与物品-物品的工程取舍2.1 用户-用户协同过滤相似用户与加权评分用户-用户协同过滤的核心假设是“兴趣相似的人会喜欢相同音乐”。实现时先构造用户-物品评分矩阵再计算目标用户与其它用户的相似度选出 Top-K 近邻用近邻对候选歌曲的评分做加权汇总。论文里提到的皮尔逊相关系数比余弦相似度更适合评分场景因为它先减去了用户自身的平均分能消除用户打分尺度不同带来的偏移。预测评分时我习惯先计算每个近邻的相似度sim再用该近邻对歌曲的评分r做加权平均。如果用户存在明显的评分偏差则使用“用户平均分 加权偏差”的形式import numpy as np def user_based_predict(ratings, target_user, target_item, top_k20, min_common5): # ratings: pandas DataFrameindex 是用户IDcolumns 是歌曲ID值为 1-5 分 if target_item not in ratings.columns: return ratings.loc[target_user].mean() target_vec ratings.loc[target_user] candidates ratings[ratings[target_item].notna()].drop(indextarget_user, errorsignore) scored [] for uid, row in candidates.iterrows(): common_mask target_vec.notna() row.notna() # 公共评分项少于 min_common 时不参与计算防止偶然相似 if common_mask.sum() min_common: continue common_vec target_vec[common_mask].astype(float) other_vec row[common_mask].astype(float) sim np.corrcoef(common_vec, other_vec)[0, 1] if not np.isnan(sim): scored.append((sim, row[target_item])) if not scored: return ratings.loc[target_user].mean() scored.sort(keylambda x: x[0], reverseTrue) neighbors scored[:top_k] # 加权求和相似度越高对预测结果的影响越大 return sum(s * r for s, r in neighbors) / sum(s for s, r in neighbors)这段代码用皮尔逊相关系数作为相似度权重是sim。参数min_common5表示两个用户至少要有 5 首共同听过的歌才计算相似度否则计算出的系数没有统计意义。top_k20控制近邻数量K 太小容易过拟合K 太大则会把相关性很低的用户也拉进来。代码里没有对原始评分做归一化实际使用时可以先用row - row.mean()计算偏差再累加效果往往更好。2.2 物品-物品协同过滤音乐场景下的稳定基座物品-物品协同过滤的思路是一首歌和用户听过的歌越相似越应该被推荐。它不需要实时计算用户之间的相似度而是离线构建歌曲相似度矩阵在线只做矩阵-向量乘法响应速度更快。音乐平台里歌曲数量通常远小于用户数量因此歌曲相似度矩阵的更新成本低于用户相似度矩阵这也是很多生产环境默认使用 ItemCF 的原因。推荐时先取出用户评分较高的歌曲再找到与这些歌曲相似的其它歌曲按相似度加权汇总pred(u, i) sum(sim(i, j) * rating(u, j)) / sum(sim(i, j))。这里sim(i, j)可以使用余弦相似度也可以使用皮尔逊相关系数。实际工程中我常用sklearn.metrics.pairwise.cosine_similarity对标准化后的评分矩阵直接计算相似度矩阵一天更新一次即可。维度UserCFItemCF基于模型的 SVD计算复杂度在线计算用户近邻用户量上升后耗时明显离线计算物品相似度在线查询快离线训练较慢在线推理只做矩阵内积推荐解释性解释为“和你兴趣相同的人也在听”解释为“因为你听过 A所以推荐相似的 B”解释维度不直观要靠因子向量近似新物品冷启动新物品没有评分无法被推荐同左但可借助物品元数据扩展新物品需要重新训练或补向量在音乐场景中的稳定性用户口味变化快近邻列表波动大歌曲相似关系相对稳定需要频繁重训但效果上限更高从算法与数据结构的角度看ItemCF 更适合音乐平台还有一个原因歌曲相似度矩阵可以按列分块缓存每天增量更新热门歌曲子集即可不需要像 UserCF 那样实时计算用户近邻。这也是论文里提到系统需要支持大数据量处理时工程上优先选 ItemCF 的原因。2.3 基于模型的协同过滤SVD 把稀疏矩阵压成低秩表达前面两种方法都依赖显式评分矩阵但实际音乐数据中 99% 以上的格子都是空值。基于模型的协同过滤不直接比较用户或物品向量而是把“用户-歌曲评分矩阵 R”近似分解成U * S * V^T用低维向量表示用户和歌曲。这样的话两个从未共同出现过的用户和歌曲也能通过隐因子向量产生关联。论文里提到的奇异值分解在实现时通常先对评分矩阵做均值填充或使用 Funk-SVD只对有评分的项计算损失。SVD 的潜在因子维度n_factors对应隐语义的容量。这个容量过大容易记住训练集的噪声过小则不足以表达用户偏好。下一章会先解决评分矩阵怎么来因为不管哪种 CF脏数据都会直接影响相似度和分解结果。3. 数据预处理与评分矩阵构建推荐系统地基怎么打3.1 从播放日志到评分数据清洗与字段选择论文在第三章提到分布式架构和大数据技术但落到这份毕业设计的复现时我更建议先用单机 DataFrame 把数据组织好再考虑换 Spark。数据量在百万级以内pandas 完全够用只有超过千万条收听日志才需要按用户 ID 哈希分片。最直接的原始记录是user_id, song_id, play_count, listen_time四列其中user_id和song_id是外键play_count是播放次数。播放次数本质上是隐式反馈需要把它转换成显式评分。常见做法是分位数分档或者用播放时长做加权。我一般先做三步清洗去掉播放次数小于等于 0 的脏记录按user_id song_id去重再过滤掉只有极少数用户听过的冷门歌曲。否则评分矩阵会极度稀疏后面无论用 UserCF 还是 SVD结果都是热门歌刷屏。import pandas as pd df pd.read_csv(music_log.csv, parse_dates[listen_time]) # 1. 去脏数据播放次数必须大于0且同一用户对同一首歌只保留一条记录 df df[df[play_count] 0].drop_duplicates([user_id, song_id]) # 2. 过滤低支持度歌曲被听人数少于10的歌评分信息太少留着只会增加噪声 song_support df.groupby(song_id)[user_id].count() df df[df[song_id].isin(song_support[song_support 10].index)] # 3. 播放次数转1-5分用分位数切成五个等级让评分分布更均匀 df[rating] pd.qcut(df[play_count], 5, labels[1, 2, 3, 4, 5]) # 4. 透视生成用户-歌曲评分矩阵 matrix df.pivot_table(indexuser_id, columnssong_id, valuesrating) sparsity 1 - matrix.notna().sum().sum() / matrix.size print(f评分矩阵维度: {matrix.shape[0]} 用户 x {matrix.shape[1]} 歌曲稀疏度: {sparsity:.4f})这段代码中drop_duplicates保证同一个用户对同一首歌只保留一条记录避免后续加权时重复计数groupby(song_id)[user_id].count()计算每首歌的听众人数小于 10 的直接丢弃。pd.qcut用分位数把播放次数切成 1 到 5 分比直接线性映射更稳因为播放量长尾分布严重线性映射会把大部分歌都压在低分段。矩阵透视后打印的稀疏度通常在 0.98 以上这并不意外关键是后面算法需要能容忍这种稀疏度。3.2 稀疏度评估与降稀疏策略如果稀疏度高于 0.99先不要急着上模型可以按顺序做减法。歌曲支持度阈值是最直接的手段把阈值从 10 提到 50矩阵会变瘦但密度会提高。用户维度同理过滤掉听歌少于 20 首的用户。另一个做法是引入“隐式反馈置信度”把播放次数直接作为 confidence 参与矩阵分解这比硬转成 1-5 分更能保留用户真实兴趣强度。预处理步骤规则示例解决什么问题字段去重user_id song_id 去重重复点击导致评分放大歌曲支持度过滤听歌人数 10长尾噪声和矩阵稀疏用户活跃度过滤听歌数 20僵尸账号与低质量行为播放量转评分qcut 5 档隐式反馈转显式评分评分矩阵压缩只保留活跃用户和歌曲降低训练成本和内存占用3.3 特征提取与表示给协同过滤准备额外信号论文把特征提取单独列成一个小节说明只有评分矩阵还不够。音乐推荐系统里常见的特征有两类一类是音乐内容特征比如歌曲的流派、歌手、音频的 MFCC 特征另一类是用户特征比如年龄段、常听语种。协同过滤本身可以用评分矩阵完成推荐但新歌和新用户没有历史行为这时内容特征就能发挥作用。特征表示上我常把song_id映射成索引再额外构造一个song_meta表包含歌手的 one-hot 编码和流派标签。SVD 训练时先只使用评分矩阵冷启动阶段再用内容相似度做候选集扩展。这个思路在论文的实验讨论里被隐式提到单纯依赖协同过滤会遇到冷启动利用内容过滤辅助能明显提升推荐新颖度。4. 推荐引擎实现与评估从 SVD 到 PrecisionK 的落地细节4.1 用 Surprise 实现 SVD 推荐模型上一章得到的评分矩阵是 pandas 的宽表放进训练代码前要转回长表。推荐系统常用的surprise库直接支持这种格式内部会自己处理用户、物品索引。SVD 在这里的对应实现是 Funk-SVD只拟合已有的评分把缺失值当作未观测项训练目标是最小化 RMSE。先用pip install scikit-surprise装好依赖再跑下面的代码。from surprise import Dataset, Reader, SVD from surprise.model_selection import cross_validate # 长表只需要 user_id, song_id, rating 三列 reader Reader(rating_scale(1, 5)) data Dataset.load_from_df(df[[user_id, song_id, rating]], reader) # n_factors: 隐因子数量n_epochs: 遍历训练集次数 # lr_all: 全局学习率reg_all: 正则化系数防止过拟合 algo SVD(n_factors80, n_epochs30, lr_all0.005, reg_all0.02) result cross_validate(algo, data, measures[RMSE, MAE], cv5, verboseTrue) print(fRMSE{result[test_rmse].mean():.4f} MAE{result[test_mae].mean():.4f})这里n_factors80是隐语义向量维度论文中的矩阵分解场景一般取 50 到 100 之间。n_epochs30表示完整遍历训练集 30 轮轮数太少欠拟合太多则训练时间变长且容易过拟合。lr_all0.005是梯度下降步长太大会导致 loss 抖动太小收敛变慢。reg_all0.02是正则化系数评分矩阵越稀疏这个值越要调大一些。cross_validate的cv5会让每个样本都被当作测试集一次结果比单次划分更可信。推荐算法的高层思想并不复杂真正的复杂度在算法与数据结构评分矩阵用什么稀疏矩阵存储相似度矩阵怎么分块决定了训练时间。surprise 底层用 CSR 稀疏矩阵保存交互数据所以不需要手动压缩直接喂长表即可。4.2 评估指标RMSE、PrecisionK 与 Coverage音乐推荐场景不能只看 RMSE。RMSE 衡量评分预测的误差但它无法回答“用户是否真的喜欢推荐列表”。因此论文的实验部分把准确率和召回率也放了进来。推荐列表里常用 PrecisionK取前 K 个物品看其中有多少是用户实际听过的。def precision_at_k(recommended, relevant, k10): # recommended: 模型给用户生成的推荐列表 # relevant: 用户在测试集里真实消费过的歌曲集合 rec set(recommended[:k]) rel set(relevant) if not rec: return 0.0 return len(rec rel) / k这个函数的意图是推荐列表长度固定为 K命中越多越好。使用时要确保recommended已经从推荐引擎的排序结果中截取前 K 项relevant是测试期内用户实际播放过的歌曲。若用户本身就只听过 3 首歌K 大于 3 时 PrecisionK 天然偏低因此分组统计时最好按用户消费数量分层。指标计算方式在音乐推荐中的关注点RMSE预测评分与真实评分均方根误差评分预测是否贴近用户真实打分MAE预测评分与真实评分绝对误差均值误差波动较 RMSE 小PrecisionK前 K 个推荐中命中数 / K推荐列表直接命中率RecallK前 K 个推荐中命中数 / 用户真实听歌数覆盖率与发现能力Coverage推荐过的歌曲数 / 歌曲总数推荐结果是否只集中在头部热歌4.3 UserCF 还是 ItemCF实验对比怎么下结论在实验环节论文建议对比不同相似度函数和不同 K 值。实际操作中我先把 ItemCF 的歌曲相似度矩阵离线算好再分别跑 Top-20 近邻的推荐UserCF 则用上一章的函数或surprise的KNNBasic(user_basedTrue)同步对比。重点关注两类指标RMSE 反映评分精度PrecisionK 和 Coverage 反映推荐列表质量。调参顺序我先固定top_k20比较余弦相似度和皮尔逊相关系数。皮尔逊通常 RMSE 更低但覆盖率可能下降因为相似度集中在少数活跃用户。随后把top_k从 10 扫到 60观察 RMSE 先降后升的位置那个拐点就是当前数据集上的最优近邻数量。SVD 则先调n_factors固定其它参数后画出校验集 RMSE 随因子数变化的曲线选下降变平缓的点而不是一味追求训练集上的最小值。5. 冷启动、稀疏矩阵和混合推荐的验证技巧5.1 冷启动缓解给新用户一条可解释的默认路径新用户没有任何历史评分UserCF 和 SVD 都无能为力。论文在国内外研究现状部分提到内容过滤与协同过滤结合是常用解法。具体做法是新用户首次进入时先用注册时选择的歌手或语种生成内容候选再叠加热门榜兜底。当用户产生 5 条以上播放记录后再切到 ItemCF。5.2 用时间切分代替随机划分离线验证时我强烈建议不要用随机划分。随机划分会把用户后半个月的行为泄露到训练集里导致 RMSE 虚低。把每个用户的播放记录按时间排序前 80% 作训练后 20% 作测试这样更接近线上场景。实现上也很直接df df.sort_values([user_id, listen_time]) train_list, test_list [], [] for uid, group in df.groupby(user_id): split_idx int(len(group) * 0.8) train_list.append(group.iloc[:split_idx]) test_list.append(group.iloc[split_idx:]) train_df pd.concat(train_list) test_df pd.concat(test_list)这里按用户分组后取前 80%保留时间顺序能有效模拟“用过去预测未来”。参数0.8可以根据数据量调整如果用户平均听歌数量小于 20就要适当提高训练比例否则测试集音乐太少PrecisionK 的方差很大。5.3 用覆盖率监控推荐是否退化最后一个小技巧是每次实验都额外计算覆盖率。Coverage 等于推荐列表去重后的歌曲数除以歌曲总数它不关心推荐得准不准只关心推荐是否都集中在头部。如果某次调参后 RMSE 降低但覆盖率从 30% 掉到 10%说明模型只是在迎合热门歌用户的个性化需求反而被牺牲了。我在并行对比 ItemCF 和 SVD 时会同时输出这一项把它作为调参时的重要参考指标。当你把时间切分、内容特征和覆盖率同时加进评估流程这套基于协同过滤的音乐推荐系统才算从毕业论文变成了可落地的推荐基线。本文还有配套的精品资源点击获取
返回列表