协同过滤与矩阵分解:从最近邻到ALS的推荐系统核心算法解析
2026/9/19 15:52:24 网站建设 项目流程

简介:外文文献《推荐系统中的协同过滤方法:最近邻与矩阵分解》的中文翻译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(axis=1, keepdims=True) / rated_mask.sum(axis=1, keepdims=True) 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}")

这段代码的核心逻辑分三步:先按行计算每个用户的平均评分,用真实评分减平均值得到中心化矩阵;然后基于中心化矩阵计算用户间相似度矩阵,避免宽松打分者被误判为和严格打分者高度相似;最后对邻居的评分做相似度加权平均,注意每个邻居的评分也要先减去各自的均值,等加权完成后加回目标用户自己的均值。选择k=2意味着只取最相似的两个用户参与预测,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 的维度是预设的(比如 k=20),乘积结果是一个稠密矩阵,稀疏问题随之消失。

对于推荐场景,评分矩阵没有负值,所以通常采用非负矩阵分解(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_components=2, init='random', random_state=42, max_iter=300, solver='cd', # 坐标下降,等价于迭代最小二乘 beta_loss='frobenius' ) W = model.fit_transform(R) # 用户-因子矩阵 P H = model.components_ # 因子-物品矩阵 Q R_hat = W @ H # 稠密预测矩阵 print("完整预测矩阵:\n", np.round(R_hat, 2))

n_components就是潜在因子的个数,对应公式里的 k,k 太小模型欠拟合,k 太大低维约束失效,实践中可以结合交叉验证看 RMSE 曲线来选择。solver='cd'是坐标下降(Coordinate Descent),Scikit-learn 文档里标注为默认选项,它和下一章要讲的 ALS 本质是同一思想——固定一个矩阵,优化另一个矩阵。beta_loss='frobenius'意味着我们用的是平方误差(Frobenius 范数),对应上一节的损失函数。预测矩阵 R_hat 里原来为零的位置现在都有了稠密数值,这些值就是推荐排序的候选分数。

4. 优化算法与工程配置:ALS 的交替更新与 PySpark 实践

4.1 非凸损失函数下的局部最优解

上一章的损失函数对 P 和 Q 联合起来看是非凸的,这意味着不存在一个保证能收敛到全局最优解的算法。工程上接受这个现实,目标改成“找到足够好的局部最优”。和传统梯度下降(SGD)相比,交替最小二乘法(Alternating Least Squares, ALS)是更稳的选择:SGD 对学习率和特征缩放敏感,调参成本高;ALS 每轮更新都有解析解,不用操心学习率。

4.2 ALS 的交替更新逻辑

ALS 的核心思想是“固定一个,求解另一个”。具体拆解:

  1. 固定用户矩阵 P 不动,Q 的每一列就变成了一个独立的岭回归问题——对物品 i,基于所有给 i 打过分的用户向量 p_u,去拟合真实的 r_ui。求导并令导数等于零,得到 Q 的闭式更新公式。
  2. 固定物品矩阵 Q 不动,P 的每一行同样变成岭回归问题,以同样的方式更新 P。
  3. 交替重复上面两步,直到损失函数下降幅度小于阈值。

由于每步都在解一个凸的子问题,优化过程稳定可控,收敛行为比 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( userCol="userId", itemCol="movieId", ratingCol="rating", rank=10, # 潜在因子数 regParam=0.1, # L2 正则系数 alpha=1.0, # 隐式反馈置信度缩放系数 implicitPrefs=False, # False 表示显式评分 coldStartStrategy="drop", # 冷启动物品直接丢弃,避免 NaN maxIter=10, seed=42 ) model = als.fit(train) # 为用户 1 推荐 top 5 电影 model.recommendForUserSubset( spark.createDataFrame([(1,)], ["userId"]), 5 ).show(truncate=False)

rank对应潜在因子数量,业务上通常从 10~20 起步,根据数据量调整;regParam控制正则化强度,太大模型偏向均值、太小过拟合,0.1 是一个常见起点;implicitPrefs=True时模型会把所有缺失值当作负反馈(评分为 0),用alpha控制置信度权重,alpha越大正反馈和负反馈的置信度差异越明显。coldStartStrategy="drop"很关键,它决定预测时遇到训练集没见过的用户或物品时直接丢弃,否则模型会输出 NaN,影响线上排序。

4.4 调参经验与评估要点

参数作用推荐经验值失败时的现象
rank因子数10~50过小欠拟合,指标一直偏低;过大训练慢,且容易过拟合
regParamL2 正则0.01~0.1过大预测值全部趋近均值,过小验证集指标波动大
alpha隐式反馈置信度0.5~2.0implicitPrefs=True 时忽略 alpha 会导致排序倾向不敏感
maxIter最大交替轮数10~20不收敛时指标起伏,需要调大迭代次数

模型评估不只看 RMSE。排序类业务更关注 NDCG、HitRate 这类排名指标,而且训练集和测试集最好按时间切分而不是随机切分——随机切分会把未来信息泄漏到训练集里,导致离线指标虚高。

5. 从模型到业务:冷启动、流行度偏差与混合策略的取舍

5.1 冷启动与长尾问题的本质

协同过滤的两个致命场景在工程里绕不开。第一个是冷启动:新物品没有任何用户评分,它无法被估算出可靠的向量 q_i,推荐系统对它来说完全失效;同样,新用户没有历史行为,无法给出有意义的 p_u。第二个是流行度偏差:长尾物品(评分数量极少的物品)的向量估计方差大,模型倾向于少推荐它们,导致“强者愈强,弱者愈弱”的马太效应。这两个问题不是调参能解决的,属于协同过滤的结构性局限。

有人会把冷启动和稀疏性混为一谈,这里要区分开:稀疏性描述的是现有用户行为矩阵中零值比例高,可以通过矩阵分解缓解;冷启动描述的是新个体进入系统时的无数据状态,矩阵分解同样无能为力。针对冷启动,常见的工程做法是把新物品的初始评分设为全局均值,或从内容特征(类别、关键词)构造一个粗糙的初始向量,等数据积累到阈值后再切换到算法预测。

之所以要做这个切换,是因为协同过滤的另一个结构性问题是潜在因子不可解释。分解出来的第 3 个因子,既可能是“配乐水准”,也可能是“观影门槛”,没有办法直接向业务方解释为什么一个物品被推荐了。这在需要推荐理由的电商场(“为您推荐的理由”)、广告合规场景里都是挑战。

5.2 基于内容的混合策略与模型复杂度权衡

推荐音效偏好、重复消费行为等场景,我会倾向做基础消融测试,用隐式反馈置信度加权公式confidence = 1 + alpha * count修正。数据规模不大时先跑通上述策略,再决定是否引入实时特征和深度模型。在单机小数据上,ALS相比NMF的主要优势就是显式支持隐式反馈的置信度权重,工程上建议直接基于PySparkimplicitPrefs参数展开评估,也能更简单地对alpha搜索网格。

关于混合方案,常用做法是在协同过滤的预测结果上叠加内容维度的关键词规则(显式可解释特征),或者用它做召回后再用 GBDT 做排序。我的经验是:先看比单模型收益是否真的大于运维复杂度,再决定上不上。毕竟矩阵分解落地时,最耗时间和精力的往往不是模型训练,而是数据管线里的反馈延迟和部分播放记录里负反馈的缺失这两件事——前者会让模型用到过期兴趣,后者则会让模型学偏。先把这两条腿站稳,模型效果自然会立得住。

从最近邻到矩阵分解,本质上是在用低维稠密向量替代高维稀疏交互记录,用“潜在因子对齐”替代“显式行为重合”。日常工程中真正高效的做法,是用 SVD 的截断思想来决定向量维度的上界,用 NMF/ALS 的正则思路来稳定预期效果。动手实践时建议保留一组不参与训练的冷启动样本,专门用来观察模型对全新物品的推荐变化,而不是只看整体 RMSE。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询