资讯详情

资讯详情

协同过滤与矩阵分解:从最近邻到ALS的推荐系统核心算法解析

简介外文文献《推荐系统中的协同过滤方法最近邻与矩阵分解》的中文翻译PDF面向推荐系统与协同过滤算法的学习者和研究者适合希望快速理解原版英文论文核心思路的读者。文档内容覆盖协同过滤的两种主要实现——最近邻方法与矩阵分解详述基于用户/基于物品的CF流程、余弦相似度与皮尔逊相关系数计算以及SVD与ALS等分解算法并讨论冷启动与稀疏性问题同时对两种思路的优势与局限进行了对比有助于读者建立系统认知。全文为中文编译语言通顺保留原文献中的公式与关键术语对应可作为算法学习笔记、课题参考文献或团队内部分享材料。资源包共1个文件PDF格式大小1.12MB轻量便于下载阅读已有359人学习下载适合对推荐系统机理想深入了解的技术人员。1. 推荐时代里的协同过滤为什么它至今仍是基线模型“我们正在离开信息时代而进入推荐时代。”这句话放在今天的短视频、电商和资讯流场景里依然成立推荐系统已经从锦上添花变成了业务增长的发动机。在众多推荐算法里协同过滤Collaborative Filtering是最特殊的一类它不依赖物品的任何内容属性只用用户的历史行为矩阵就能做预测这使它成为衡量一切新模型效果的基线。显式评分五星、点赞和隐式反馈点击、停留时长、购买记录是它的两种输入来源理解这两类信号在建模时的差异是搭推荐系统的基本功。这篇文章结合一份经典的外文综述拆解最近邻Nearest Neighborhood和矩阵分解Matrix Factorization两条技术路线把相似度计算、均值中心化、SVD截断、NMF求解和ALS优化这些环节的细节和坑都过一遍适合刚接触推荐系统的工程师也适合想补齐算法细节的开发者。2. 最近邻协同过滤相似度度量与均值中心化的实现细节2.1 从评分矩阵出发User-based 和 Item-based 的对称视角协同过滤的标准做法是最近邻算法Nearest Neighborhood核心假设是过去行为相似的用户未来偏好也相似。先看 User-based CF。我们有一张n × m的评分矩阵行是用户列是物品矩阵元素r_ui表示用户 u 对物品 i 的评分。现在要预测目标用户没评分过的物品 j直觉做法是找到和目标用户评分习惯最接近的 X 个用户把这 X 个用户对物品 j 的评分做加权平均权重就是相似度。这里有第一个坑不同用户的评分基准不一样。有人习惯给 5 星有人再满意也只给 3 星直接平均会被这种个人尺度的偏差带偏。所以计算时要先对每个用户的评分做均值中心化mean centering把个人基准减掉算完再加回来。这和特征工程里的标准化是同一个目的消除尺度差异让相似度比较的是“评分模式”而不是“绝对分值”。Item-based CF 的思路完全对称两个物品如果被同一批用户打出了相似的分数就认为这两个物品相似。预测时用目标用户对最相似的 X 个物品的评分做加权平均。基于物品的方法有一个天然优势——物品的评分分布比用户的兴趣更稳定用户口味会变但《肖申克的救赎》在社区里的评分基线不太会漂移。2.2 相似度计算皮尔逊相关系数和余弦相似度计算相似度有两条主流路线。皮尔逊相关系数Pearson Correlation在计算前先对用户评分做均值中心化所以它对评分尺度的偏差不敏感余弦相似度Cosine Similarity则直接对原始向量求夹角余弦向量方向一致程度高就认为相似。实际工程里余弦相似度更常用因为它计算简单、适合稀疏向量的高效实现。但要注意普通的余弦相似度没有中心化推荐之前最好手动减均值否则评分严格的用户和评分宽松的用户会被误判为不相似。这个操作在向量召回里也有对应物——向量先做 L2 归一化再求内积等价于余弦相似度。2.3 用代码复现均值中心化的评分预测下面用一段简洁的 Python 代码演示 User-based CF 的完整预测流程。这里选了一个 5×5 的小矩阵方便手工核对实际场景里矩阵规模是百万级的思路完全一致。import numpy as np from sklearn.metrics.pairwise import cosine_similarity # 评分矩阵行是用户列是物品0 表示未评分 R np.array([ [5, 3, 0, 1, 0], [4, 0, 0, 1, 0], [1, 1, 0, 5, 4], [1, 0, 0, 4, 5], [0, 1, 5, 4, 0], ]) # 只对已评分的列做均值中心化未评分的置 0 rated_mask R 0 user_mean np.where(rated_mask, R, 0).sum(axis1, keepdimsTrue) / rated_mask.sum(axis1, keepdimsTrue) R_centered np.where(rated_mask, R - user_mean, 0) # 计算用户间余弦相似度 sim cosine_similarity(R_centered) np.fill_diagonal(sim, 0) # 预测用户 0 对物品 2索引从 0 开始的评分 target_user 0 target_item 2 k 2 # 取最相似的 2 个邻居 neighbors np.argsort(sim[target_user])[::-1][:k] weighted_sum 0.0 weight_sum 0.0 for nb in neighbors: if R[nb, target_item] 0: w sim[target_user, nb] weighted_sum w * (R[nb, target_item] - user_mean[nb][0]) weight_sum abs(w) pred_centered weighted_sum / weight_sum if weight_sum 0 else 0 pred user_mean[target_user][0] pred_centered print(f预测评分: {pred:.2f})这段代码的核心逻辑分三步先按行计算每个用户的平均评分用真实评分减平均值得到中心化矩阵然后基于中心化矩阵计算用户间相似度矩阵避免宽松打分者被误判为和严格打分者高度相似最后对邻居的评分做相似度加权平均注意每个邻居的评分也要先减去各自的均值等加权完成后加回目标用户自己的均值。选择k2意味着只取最相似的两个用户参与预测k越大偏差越大但方差越小越小越容易过拟合到个别用户的异常行为上。2.4 两类方法的取舍与瓶颈维度User-based CFItem-based CF相似度对象用户与用户物品与物品稳定性用户兴趣随时间漂移模型需频繁更新物品评分分布相对稳定可离线计算可解释性“和你口味相似的人也喜欢”“你喜欢的 A 和 B 与这个物品相似”计算成本用户数增长时相似度矩阵平方膨胀物品数一般少于用户数工程上更便宜冷启动新用户无行为无法计算相似度新物品无评分无法加入相似度计算最近邻方法的两个硬伤文章里也明确提了一是稀疏性当目标用户的所有邻居都没给目标物品打过分的时加权平均直接失效二是可扩展性用户和物品数量增长后用户间相似度矩阵是n×n的规模存储和实时计算都吃不消。这就是下一章要讨论矩阵分解的动机。3. 矩阵分解奇异值分解、潜在因子与 NMF 数值近似3.1 从三部科幻电影看潜在特征的抽象能力矩阵分解Matrix Factorization解决稀疏性和可扩展性问题的思路是把高维稀疏矩阵拆成两个低维稠密矩阵用潜在因子latent factors解释用户的抽象偏好。一个直观例子用户给《阿凡达》《地心引力》《盗梦空间》都打了高分这三条记录看起来是三个独立事件但背后很可能共享一个隐藏变量——用户喜欢科幻题材。与其用电影 ID 去匹配用户不如把用户映射到一个低维空间里每个维度代表一种抽象偏好科幻、剧情、配乐质量……把物品也映射到同一个空间用户和物品在空间里的位置越接近预测评分越高。这就是矩阵分解能捕捉到“对同一批电影没有评分交集、但口味相似”的用户关系的原因。3.2 SVD 分解从线性代数到评分矩阵的近似重构要理解矩阵分解得先看奇异值分解SVD。线性代数里的结论是任意实矩阵 R 都能分解为R U Σ Vᵀ。放在电影场景里U的列是用户与潜在特征的关联V的行是物品与潜在特征的关联Σ是对角矩阵对角线上的奇异值表示每个潜在特征对预测用户偏好的重要程度。SVD 用于降维的标准操作是对 Σ 的奇异值按绝对值降序排列只保留前 k 个同时截断 U 和 V 的对应维度得到近似矩阵 A ≈ R。k 的选择很讲究A 要尽量捕获原矩阵的方差误差||A - R||就是我们要最小化的目标。这个思想与主成分分析PCA一致牺牲可控的误差换取低维表示。但 SVD 有一个致命前提——它要求原矩阵是稠密的。电影评分矩阵恰恰是超稀疏的用户看过的电影通常不到总数的 1%直接对稀疏矩阵做精确分解在数值上很困难。3.3 稀疏评分矩阵下的直接求解NMF 与非负约束既然无法直接对稀疏 R 做 SVD那就绕开分解改为直接求两个低维矩阵 U 和 V让它们的乘积U Vᵀ最接近 R。这时 U 和 V 的维度是预设的比如 k20乘积结果是一个稠密矩阵稀疏问题随之消失。对于推荐场景评分矩阵没有负值所以通常采用非负矩阵分解Non-Negative Matrix Factorization, NMF。非负约束让 U 和 V 的每个元素都大于等于 0这带来的好处有两个一是数学上更符合评分的物理含义不存在负偏好抵消二是分解结果的可解释性更强潜在因子更接近“题材强度”或“风格倾向”这类叠加概念。看一下 NMF 的数学目标。对每个用户 u 和物品 i我们设置用户向量 p_u 和物品向量 q_i预测评分\hat{r}_{ui} p_u · q_iᵀ p_uᵀ q_i。目标损失函数是真实评分与预测评分的平方误差之和加上 L2 正则化项这里略去正则系数的具体推导[ \min_{P,Q} \sum_{(u,i) \in K} (r_{ui} - p_u^\top q_i)^2 \lambda (||p_u||^2 ||q_i||^2) ]其中 K 表示有评分记录的用户, 物品对集合。正则项 λ 防止 p_u 和 q_i 的数值过大导致过拟合。实际模型中还会再加偏差项把全局平均分 μ、物品偏差 b_i 和用户偏差 b_u 分开建模最终的预测公式是[ \hat{r}_{ui} \mu b_u b_i p_u^\top q_i ]加入偏差项后模型不再要求向量内积去解释评分中的所有差异用户打分松紧、物品口碑高低这些“边缘信号”都显式建模了向量只需要捕捉残余的交互偏好。3.4 用 Scikit-learn NMF 落地一个评分预测模型下面的代码用真实的 NMF 库把上一节的公式跑通输入还是那个 5×5 的小矩阵。from sklearn.decomposition import NMF import numpy as np R np.array([ [5, 3, 0, 1, 0], [4, 0, 0, 1, 0], [1, 1, 0, 5, 4], [1, 0, 0, 4, 5], [0, 1, 5, 4, 0], ]).astype(float) # 用 2 个潜在因子逼近原矩阵 model NMF( n_components2, initrandom, random_state42, max_iter300, solvercd, # 坐标下降等价于迭代最小二乘 beta_lossfrobenius ) W model.fit_transform(R) # 用户-因子矩阵 P H model.components_ # 因子-物品矩阵 Q R_hat W H # 稠密预测矩阵 print(完整预测矩阵:\n, np.round(R_hat, 2))n_components就是潜在因子的个数对应公式里的 kk 太小模型欠拟合k 太大低维约束失效实践中可以结合交叉验证看 RMSE 曲线来选择。solvercd是坐标下降Coordinate DescentScikit-learn 文档里标注为默认选项它和下一章要讲的 ALS 本质是同一思想——固定一个矩阵优化另一个矩阵。beta_lossfrobenius意味着我们用的是平方误差Frobenius 范数对应上一节的损失函数。预测矩阵 R_hat 里原来为零的位置现在都有了稠密数值这些值就是推荐排序的候选分数。4. 优化算法与工程配置ALS 的交替更新与 PySpark 实践4.1 非凸损失函数下的局部最优解上一章的损失函数对 P 和 Q 联合起来看是非凸的这意味着不存在一个保证能收敛到全局最优解的算法。工程上接受这个现实目标改成“找到足够好的局部最优”。和传统梯度下降SGD相比交替最小二乘法Alternating Least Squares, ALS是更稳的选择SGD 对学习率和特征缩放敏感调参成本高ALS 每轮更新都有解析解不用操心学习率。4.2 ALS 的交替更新逻辑ALS 的核心思想是“固定一个求解另一个”。具体拆解固定用户矩阵 P 不动Q 的每一列就变成了一个独立的岭回归问题——对物品 i基于所有给 i 打过分的用户向量 p_u去拟合真实的 r_ui。求导并令导数等于零得到 Q 的闭式更新公式。固定物品矩阵 Q 不动P 的每一行同样变成岭回归问题以同样的方式更新 P。交替重复上面两步直到损失函数下降幅度小于阈值。由于每步都在解一个凸的子问题优化过程稳定可控收敛行为比 SGD 更容易预测。4.3 PySpark ALS 的参数配置在分布式场景里ALS 最大的好处是并行化容易——每步更新 P 或 Q 时各个用户或物品的更新互不依赖天然适合 Spark 的分区计算。PySpark 的推荐模块把整套流程封装好了核心配置如下from pyspark.ml.recommendation import ALS from pyspark.sql import SparkSession spark SparkSession.builder.master(local[*]).appName(als-cf).getOrCreate() # 训练数据至少包含 userId, movieId, rating 三列 train spark.createDataFrame([ (1, 101, 5.0, 20230101), (1, 102, 3.0, 20230103), (2, 101, 4.0, 20230102), (2, 103, 1.0, 20230105), (3, 101, 2.0, 20230101), ], [userId, movieId, rating, timestamp]) als ALS( userColuserId, itemColmovieId, ratingColrating, rank10, # 潜在因子数 regParam0.1, # L2 正则系数 alpha1.0, # 隐式反馈置信度缩放系数 implicitPrefsFalse, # False 表示显式评分 coldStartStrategydrop, # 冷启动物品直接丢弃避免 NaN maxIter10, seed42 ) model als.fit(train) # 为用户 1 推荐 top 5 电影 model.recommendForUserSubset( spark.createDataFrame([(1,)], [userId]), 5 ).show(truncateFalse)rank对应潜在因子数量业务上通常从 10~20 起步根据数据量调整regParam控制正则化强度太大模型偏向均值、太小过拟合0.1 是一个常见起点implicitPrefsTrue时模型会把所有缺失值当作负反馈评分为 0用alpha控制置信度权重alpha越大正反馈和负反馈的置信度差异越明显。coldStartStrategydrop很关键它决定预测时遇到训练集没见过的用户或物品时直接丢弃否则模型会输出 NaN影响线上排序。4.4 调参经验与评估要点参数作用推荐经验值失败时的现象rank因子数10~50过小欠拟合指标一直偏低过大训练慢且容易过拟合regParamL2 正则0.01~0.1过大预测值全部趋近均值过小验证集指标波动大alpha隐式反馈置信度0.5~2.0implicitPrefsTrue 时忽略 alpha 会导致排序倾向不敏感maxIter最大交替轮数10~20不收敛时指标起伏需要调大迭代次数模型评估不只看 RMSE。排序类业务更关注 NDCG、HitRate 这类排名指标而且训练集和测试集最好按时间切分而不是随机切分——随机切分会把未来信息泄漏到训练集里导致离线指标虚高。5. 从模型到业务冷启动、流行度偏差与混合策略的取舍5.1 冷启动与长尾问题的本质协同过滤的两个致命场景在工程里绕不开。第一个是冷启动新物品没有任何用户评分它无法被估算出可靠的向量 q_i推荐系统对它来说完全失效同样新用户没有历史行为无法给出有意义的 p_u。第二个是流行度偏差长尾物品评分数量极少的物品的向量估计方差大模型倾向于少推荐它们导致“强者愈强弱者愈弱”的马太效应。这两个问题不是调参能解决的属于协同过滤的结构性局限。有人会把冷启动和稀疏性混为一谈这里要区分开稀疏性描述的是现有用户行为矩阵中零值比例高可以通过矩阵分解缓解冷启动描述的是新个体进入系统时的无数据状态矩阵分解同样无能为力。针对冷启动常见的工程做法是把新物品的初始评分设为全局均值或从内容特征类别、关键词构造一个粗糙的初始向量等数据积累到阈值后再切换到算法预测。之所以要做这个切换是因为协同过滤的另一个结构性问题是潜在因子不可解释。分解出来的第 3 个因子既可能是“配乐水准”也可能是“观影门槛”没有办法直接向业务方解释为什么一个物品被推荐了。这在需要推荐理由的电商场“为您推荐的理由”、广告合规场景里都是挑战。5.2 基于内容的混合策略与模型复杂度权衡推荐音效偏好、重复消费行为等场景我会倾向做基础消融测试用隐式反馈置信度加权公式confidence 1 alpha * count修正。数据规模不大时先跑通上述策略再决定是否引入实时特征和深度模型。在单机小数据上ALS相比NMF的主要优势就是显式支持隐式反馈的置信度权重工程上建议直接基于PySpark的implicitPrefs参数展开评估也能更简单地对alpha搜索网格。关于混合方案常用做法是在协同过滤的预测结果上叠加内容维度的关键词规则显式可解释特征或者用它做召回后再用 GBDT 做排序。我的经验是先看比单模型收益是否真的大于运维复杂度再决定上不上。毕竟矩阵分解落地时最耗时间和精力的往往不是模型训练而是数据管线里的反馈延迟和部分播放记录里负反馈的缺失这两件事——前者会让模型用到过期兴趣后者则会让模型学偏。先把这两条腿站稳模型效果自然会立得住。从最近邻到矩阵分解本质上是在用低维稠密向量替代高维稀疏交互记录用“潜在因子对齐”替代“显式行为重合”。日常工程中真正高效的做法是用 SVD 的截断思想来决定向量维度的上界用 NMF/ALS 的正则思路来稳定预期效果。动手实践时建议保留一组不参与训练的冷启动样本专门用来观察模型对全新物品的推荐变化而不是只看整体 RMSE。本文还有配套的精品资源点击获取
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →