想象你经营一家「风味果汁铺」,货架上摆着几百款鲜榨果汁。你对常客的口味多少有些了解:小红爱芒果,小刚怕酸,阿May只喝清爽型。可顾客有几千人、果汁有几百款,你不可能让每个人都尝遍每一款,于是手头只剩一张千疮百孔的评价表——大部分格子空着,只有零星几条打分。你真正想知道的是:如果小红尝了那款还没人打分的新品「青提茉莉」,她会喜欢吗? 聪明的做法不是挨个去问,而是从已有记录里「猜」出大家背后的口味规律。矩阵分解(Matrix Factorization)就是干这件事的数学魔法:它把那张残缺的大表格,拆解成「顾客—隐藏风味」和「果汁—隐藏风味」两张小表,外加一份「风味流行度」清单。SVD 是经典的拆解算法,ALS 则是把拆解落到工程实战的高效求解策略。掌握了它们,你就能像一位读心术调配师,从零散的打分里推断出一杯新果汁对每一位顾客的吸引力。

岗位关联

对搜索推荐算法工程师而言,SVD/ALS 是协同过滤的基石。理解它,意味着你能独立设计、评估并上线一套推荐系统;无论笔试面试还是真实项目,这套思维都是绕不开的核心考点。

目录
  1. 可视化演示
  2. 矩阵分解入门
  3. SVD数学原理
  4. SVD推荐应用
  5. ALS优化算法
  6. 模型增强技巧
  7. 评估体系构建
  8. 工程落地实践
  9. 前沿发展演进
  10. AI 应用场景
  11. Python 演示
  12. 常见误区
  13. 面试题

可视化演示

本节配有交互动效(进入视口后自动加载,约 3KB)。

矩阵分解入门

果汁铺的表哥来了,他建议你用「协同过滤」:找出跟小红口味最像的三位老顾客,他们打过高分的果汁,小红大概率也喜欢。可你翻开评分表一看,几百款果汁里每款平均只有不到 5% 的人喝过,想找「口味相似」的邻居,大家的重叠记录少得可怜。邻里之间无从比起,协同过滤在极度稀疏的表格前有些失灵。

换个思路:你不再找「人的邻居」,而是找「风味的邻居」。你猜测,顾客打高分,是因为那杯果汁恰好命中了他喜欢的几种「基础风味」——比如甜度、酸度、清爽感。如果能知道每杯果汁在这些风味上的配比,以及每位顾客对这些风味的偏好权重,你就能预测任何人对任何果汁的打分。这个思路,就是矩阵分解的雏形,学名叫「隐语义模型」:风味不是芒果、草莓这种看得见的标签,而是从数据里自动学出来的隐藏维度。

数学上,矩阵分解把评分表 R 拆成三个小矩阵的乘积:U 记录每位顾客在 k 种隐藏风味上的偏好,Σ 记录每种风味的「流行度权重」,Vᵀ 记录每款果汁在 k 种风味上的配比。看得见的大表格,被压缩成三张可理解的小表。

R≈UΣV⊤\displaystyle R \approx U \Sigma V^{\top}

1. R 评分矩阵:原始打分表,行是顾客,列是果汁,取值一般是 1-5 分,0 代表没喝过。

R∈Rm×n\displaystyle R \in \mathbb{R}^{m \times n}

2. U 用户偏好矩阵:每位顾客对 k 种隐藏风味的偏好强度,m 个顾客对应 m 行。

U∈Rm×k\displaystyle U \in \mathbb{R}^{m \times k}

3. Σ 奇异值矩阵:k×k 对角矩阵,对角线上的奇异值代表每种风味的「流行度权重」,越大的风味越能解释整体偏好。

Σ=diag⁡(σ1,σ2,…,σk)\displaystyle \Sigma = \operatorname{diag}(\sigma_1, \sigma_2, \ldots, \sigma_k)

4. Vᵀ 物品因子矩阵:每列是一种果汁在 k 种风味上的配比,转置是为了让矩阵乘法维度匹配。

V⊤∈Rk×n\displaystyle V^{\top} \in \mathbb{R}^{k \times n}

回到果汁铺:你不再纠结于「让所有顾客都喝到所有果汁」,而是专心把三张张小表算准。只要算准了,任何空格都可以用乘法填上预测值——这份「读心配方表」,就是矩阵分解送给你的见面礼。

SVD数学原理

深夜打烊后,你在后厨调试一台旧调音台。一首合唱录音被拆成几个声部音轨——人声、吉他、鼓点——每个音轨前面有一个音量推子。推子推得越高,这个声部在最终混音中的分量越重。SVD 做的事,和这台调音台如出一辙:任何一个矩阵,都能被拆成「左声部」「分量推子」「右声部」三件套。

SVD 的数学定义很干净:任意实矩阵 A(不一定是方阵)都可以分解为 A = UΣVᵀ。其中 U 和 V 是正交矩阵(列向量两两垂直),Σ 是对角矩阵,对角线上的值 σ₁ ≥ σ₂ ≥ … ≥ 0,称为奇异值。奇异值的大小,就是每个「方向」在矩阵中承载的能量大小。第一个奇异值通常独大,后面的迅速衰减。

截断 SVD 的思路由此而来:既然小奇异值对应的方向几乎不影响整体,干脆丢掉它们,只保留前 k 个最大的奇异值及其对应的左右向量,得到 A ≈ UₖΣₖVₖᵀ。这就像把调音台上 20 个音轨里的 17 个静音,你发现混音听感几乎没变——因为剩下的 3 个音轨贡献了绝大部分能量。几何上,矩阵可以看作一个线性变换:U 和 V 是旋转,Σ 是沿坐标轴拉伸,截断就是只保留最粗壮的那几根拉伸轴。

A=UΣV⊤,Σ=diag⁡(σ1,σ2,…,σn),σ1≥σ2≥⋯≥σn≥0\displaystyle A = U \Sigma V^{\top}, \qquad \Sigma = \operatorname{diag}(\sigma_1, \sigma_2, \ldots, \sigma_n), \qquad \sigma_1 \ge \sigma_2 \ge \cdots \ge \sigma_n \ge 0

1. A 任意实矩阵:m 行 n 列的原始矩阵,对应果汁铺里 m 个顾客对 n 款果汁的评分表。

A∈Rm×n\displaystyle A \in \mathbb{R}^{m \times n}

2. U 左奇异向量矩阵:m×m 正交矩阵,列向量是 A 的「左奇异向量」,相当于调音台里每个声部的波形基础。

U⊤U=I,U∈Rm×m\displaystyle U^{\top} U = I, \quad U \in \mathbb{R}^{m \times m}

3. Σ 奇异值对角矩阵:m×n 对角矩阵,对角线元素 σ₁ ≥ σ₂ ≥ … ≥ 0 是奇异值,对应每个推子的音量。

Σ=diag⁡(σ1,σ2,…,σmin⁡(m,n)),σi≥0\displaystyle \Sigma = \operatorname{diag}(\sigma_1, \sigma_2, \ldots, \sigma_{\min(m,n)}), \quad \sigma_i \ge 0

4. Vᵀ 右奇异向量矩阵:n×n 正交矩阵的转置,行向量是「右奇异向量」,刻画了原始空间中的主方向。

VV⊤=I,V⊤∈Rn×n\displaystyle V V^{\top} = I, \quad V^{\top} \in \mathbb{R}^{n \times n}

SVD 的价值在于:它给了你一张「能量排行榜」。回到果汁铺,排行榜上的大奇异值可能对应「甜度」这种被所有人关注的风味,小奇异值对应的则是只有个别人在意的冷门口感。做推荐时,你只取前几名,等于专注服务大多数人的主要口味,把细枝末节留给其他个性化手段。

SVD推荐应用

你兴冲冲地把评分表丢进 SVD 库函数,结果它报错:矩阵里有空格。SVD 是严格的数学工具,要求矩阵每一个元素都有值。有人建议:先用均值把所有空格填满再做分解。你试了一下,填进去的分数都是「全体平均分」这种毫无个性的水货——它们会污染分解结果,让学出来的风味失真。这就是传统 SVD 在推荐场景中的「稀疏性挑战」。

2006 年 Netflix 大奖赛上,参赛者 Simon Funk 想通了一个关键问题:既然最终目标只是预测打分,何必先补全再分解?他直接放弃补全,只让两个小矩阵的乘积在「已知评分的格子上」尽量接近真实值。这个改动看似微不足道,却把问题从「先补数据再分解」变成了「直接学习分解」,从此矩阵分解真正能用在百万级用户的稀疏评分数据上。

这就是 FunkSVD,也叫隐语义模型(LFM)。它不再关心奇异值矩阵 Σ,而是把 Σ 吸收进 P 和 Q 两个因子矩阵里。预测公式变成用户因子 p_u 与物品因子 q_i 的内积;训练目标是最小化所有已知评分上的预测误差,并加上正则项防止过拟合。

min⁡P,Q∑(u,i)∈K(rui−qi⊤pu)2+λ(∥pu∥2+∥qi∥2)\displaystyle \min_{P,Q} \sum_{(u,i) \in K} \left( r_{ui} - q_i^{\top} p_u \right)^2 + \lambda \left( \| p_u \|^2 + \| q_i \|^2 \right)

1. K 已知评分集合:只统计顾客真正打过分的格子,空格不参与计算。

K={(u,i)∣rui 已知}\displaystyle K = \{(u,i) \mid r_{ui} \text{ 已知}\}

2. q_iᵀ p_u 内积预测:用户因子和物品因子做点积,得到一个预测评分。

r^ui=qi⊤pu=∑f=1kqifpuf\displaystyle \hat{r}_{ui} = q_i^{\top} p_u = \sum_{f=1}^{k} q_{i f} p_{u f}

3. 平方误差:预测分与真实分的差取平方,放大预测不准的格子对损失的惩罚。

(rui−qi⊤pu)2\displaystyle \left( r_{ui} - q_i^{\top} p_u \right)^2

4. λ 正则项:惩罚因子向量的平方范数,防止某个向量的数值过大、把单个样本记得太死。

λ(∥pu∥2+∥qi∥2)\displaystyle \lambda \left( \| p_u \|^2 + \| q_i \|^2 \right)

回到果汁铺:FunkSVD 就像一位聪明的调配师——他不再傻傻地让所有客人把每款果汁都喝一遍,而是根据已经收到的反馈,反复微调「顾客口味档案」和「果汁配方档案」,让已知评价越来越好,顺带也就有了预测未知评价的能力。

ALS优化算法

烤饼干时你发现一个麻烦:配方和烤箱温度互相影响,没法同时调好。于是你想了个笨办法——先固定烤箱温度,调配方直到饼干好吃;再固定配方,调温度;如此反复烤几轮,饼干越来越接近完美。ALS(交替最小二乘)用的正是这个「先固定一边,调另一边」的策略。

矩阵分解的目标函数里,P 和 Q 两个未知矩阵相乘,联合起来看是一个非凸优化问题,没有一步到位的闭式解。但神奇之处在于:如果固定 Q,把 Q 当成已知常数,那么对每个用户 u 来说,P 的第 u 行 p_u 只出现在几个独立的平方项里,变成了一个标准的最小二乘问题,可以一步解出最优的 p_u。反过来固定 P,每个物品因子 q_i 同样有闭式解。交替进行,每一轮都在做「全局最优的子问题求解」。

更妙的是,每个用户的子问题只依赖他自己的评分记录,互相之间完全独立。这意味着 ALS 可以天然并行——把用户分到不同机器上同时求解,再汇总更新。Spark MLlib 的 ALS 就是靠这个特性,在分布式集群上处理上亿条评分记录。

pu=(Q⊤Q+λI)−1Q⊤ru,qi=(P⊤P+λI)−1P⊤ri\displaystyle p_u = \left( Q^{\top} Q + \lambda I \right)^{-1} Q^{\top} r_u, \qquad q_i = \left( P^{\top} P + \lambda I \right)^{-1} P^{\top} r_i

1. 固定 Q 求解 p_u:把物品因子矩阵 Q 当作已知常数,用户 u 的问题退化为线性最小二乘。

pu=(Q⊤Q+λI)−1Q⊤ru\displaystyle p_u = \left( Q^{\top} Q + \lambda I \right)^{-1} Q^{\top} r_u

2. r_u 用户评分向量:用户 u 对所有物品的打分向量,没打过的位置视为不参与。

ru=(ru1,ru2,…,run)\displaystyle r_u = (r_{u1}, r_{u2}, \ldots, r_{un})

3. QᵀQ + λI 正则化正规矩阵:Q 的协方差矩阵加上 λ 倍单位阵,保证求逆稳定、防止过拟合。

Au=Q⊤Q+λI\displaystyle A_u = Q^{\top} Q + \lambda I

4. 固定 P 求解 q_i:对称地,把物品 i 的因子向量用所有用户因子矩阵 P 和物品 i 的评分列向量 r_i 算出来。

qi=(P⊤P+λI)−1P⊤ri\displaystyle q_i = \left( P^{\top} P + \lambda I \right)^{-1} P^{\top} r_i

回到烤饼干:食谱和烤箱温度各调三五轮后,两者都稳定在最佳组合上。ALS 迭代十几轮后,P 和 Q 也趋于稳定——此时你手里既有靠谱的顾客口味档案,也有靠谱的果汁配方档案,可以放心地给任何「顾客—果汁」组合打分了。

模型增强技巧

阿May 天生爱甜,给所有果汁的打分都比别人高 0.5 分——这不是因为某款果汁特别适合她,而是她的评分尺子「偏松」。反过来,店里的招牌「杨枝甘露」因为名气大,很多人潜意识里就多给一分——这是果汁本身的「明星光环」。这些与具体口味匹配无关的系统性偏差,就是偏置项要捕捉的东西。

把偏置加进预测公式,评分预测变成四项相加:全局平均分 μ 打底,加上用户偏置 b_u(这个人整体手松还是手紧),加上物品偏置 b_i(这款果汁本身受欢迎程度),最后才是个性化因子内积 p_uᵀq_i。加了偏置后,因子向量不用再费力去解释「为什么阿May给所有果汁都高分」,可以专心刻画真正的口味差异,预测精度通常能明显提升。

r^ui=μ+bu+bi+pu⊤qi\displaystyle \hat{r}_{ui} = \mu + b_u + b_i + p_u^{\top} q_i

1. μ 全局平均分:所有已知评分的平均值,是整个评分体系的「原点」。

μ=1∣K∣∑(u,i)∈Krui\displaystyle \mu = \frac{1}{|K|} \sum_{(u,i) \in K} r_{ui}

2. b_u 用户偏置:用户 u 的平均打分习惯与全局均值的差,代表这个人是「手松」还是「手紧」。

bu=1∣Iu∣∑i∈Iu(rui−μ)\displaystyle b_u = \frac{1}{|I_u|} \sum_{i \in I_u} (r_{ui} - \mu)

3. b_i 物品偏置:物品 i 收到的平均打分与全局均值的差,代表它的「人气体质」。

bi=1∣Ui∣∑u∈Ui(rui−μ−bu)\displaystyle b_i = \frac{1}{|U_i|} \sum_{u \in U_i} (r_{ui} - \mu - b_u)

4. p_uᵀq_i 因子交互:用户在隐藏风味上的偏好与果汁在相同风味上的配比做内积,是真正的个性化部分。

偏置项之外,还有三个常用增强:一是隐式反馈——顾客没说好不好喝,但把杯子喝空了(播放完、点开、加购),这些行为信号可以用置信度加权的 ALS-WR 来建模;二是时间衰减——三个月前的口味权重应该低于最近的口味,给旧评分乘以衰减系数;三是冷启动——新顾客没有任何记录,可以用他选的「口味标签」或内容特征来初始化因子向量。这些技巧组合使用,才能把矩阵分解从「教室演示」推向「真实战场」。

评估体系构建

店里的新品上架一周,你怎么判断它成不成功?最直接的办法是看顾客实际打分和你的预测差多少——差的平均值越小,说明你的「读心术」越准。这就是预测精度类指标:RMSE 和 MAE。

RMSE 先求每个格子「预测与真实」的差的平方,再取平均后开根号;MAE 则是把差的绝对值直接平均。区别在于:平方运算会让 RMSE 对「差得离谱」的预测更敏感——一个差 3 分的格子,对 RMSE 的贡献是 9,对 MAE 的贡献只有 3。如果你的系统偶尔会给出特别离谱的推荐,RMSE 更能帮你发现这个问题。

RMSE=1n∑(u,i)∈T(rui−r^ui)2,MAE=1n∑(u,i)∈T∣rui−r^ui∣\displaystyle \mathrm{RMSE} = \sqrt{\frac{1}{n} \sum_{(u,i) \in T} \left( r_{ui} - \hat{r}_{ui} \right)^2}, \qquad \mathrm{MAE} = \frac{1}{n} \sum_{(u,i) \in T} \left| r_{ui} - \hat{r}_{ui} \right|

1. T 测试集:训练时留出的一批真实评分,专门用来检验模型没见过的新数据。

T={(u,i)∣(u,i)∉Ktrain}\displaystyle T = \{(u,i) \mid (u,i) \notin K_{\text{train}}\}

2. r_ui 真实评分:顾客实际打出的分数,是检验的「标准答案」。

rui∈{1,2,3,4,5}\displaystyle r_{ui} \in \{1, 2, 3, 4, 5\}

3. r̂_ui 预测评分:矩阵分解模型计算出的预测值。

r^ui=μ+bu+bi+pu⊤qi\displaystyle \hat{r}_{ui} = \mu + b_u + b_i + p_u^{\top} q_i

4. 平方与开根号:RMSE 对差值先平方再平均再开根,放大了大误差的惩罚;MAE 则直接取绝对值平均。

RMSE=1n∑(rui−r^ui)2,MAE=1n∑∣rui−r^ui∣\displaystyle \mathrm{RMSE} = \sqrt{\frac{1}{n} \sum (r_{ui} - \hat{r}_{ui})^2}, \quad \mathrm{MAE} = \frac{1}{n} \sum |r_{ui} - \hat{r}_{ui}|

但只看 RMSE 远远不够。一个只推荐 10 款热门果汁的系统,预测误差可能很小,可它牺牲了覆盖率——大量冷门果汁永远得不到曝光;也牺牲了多样性——顾客看到的推荐列表千篇一律。所以上线前还要看覆盖率、多样性指标;上线后做在线评测,把用户分成实验组和对照组,用 A/B 测试看真实点击率、转化率和留存;最终对齐业务指标(GMV、会员时长)。回到果汁铺:客人说「挺好喝」但每次只点同一款,也不算成功的推荐——你得确保他愿意不断尝试你推荐的新风味。

工程落地实践

果汁铺开到第十家分店后,你发现每家店手写配方、靠店长个人经验去猜顾客口味,效率太低。你需要一套标准化工序:中央厨房统一训练「顾客口味画像」和「果汁配方表」,所有分店共享;每家分店只需根据顾客 ID 查出画像,与配方表做快速内积打分,就能在毫秒级给出推荐。

这就是矩阵分解在工业界的标准落地方式:离线训练、线上召回。离线阶段,用 Spark MLlib 的 ALS 在分布式集群上把用户-物品评分矩阵分解成两个因子矩阵,存到线上存储(如 Redis 或向量数据库);线上阶段,用户请求进来后,取出他的用户因子向量,与候选物品因子向量批量做内积,按分数取 Top-N 作为召回结果,再交给精排模型细化排序。整个链路中,内积计算的开销极低,这也是矩阵分解至今仍是工业召回主力之一的原因。

score⁡(u,i)=pu⊤qi=∑f=1kpufqif\displaystyle \operatorname{score}(u,i) = p_u^{\top} q_i = \sum_{f=1}^{k} p_{u f} q_{i f}

1. p_u 用户因子向量:离线训练得到的用户口味画像,线上直接从存储中查表取出。

pu∈Rk\displaystyle p_u \in \mathbb{R}^{k}

2. q_i 物品因子向量:离线训练得到的物品配方向量,同样预先算好。

qi∈Rk\displaystyle q_i \in \mathbb{R}^{k}

3. 逐位相乘再求和:内积运算本身非常轻量,一次打分只涉及 k 次乘法和加法。

∑f=1kpufqif\displaystyle \sum_{f=1}^{k} p_{u f} q_{i f}

工程上的关键经验:一是超参调优,因子维度 k 一般取 10-200,正则系数 λ 用交叉验证选择,迭代次数看验证集误差是否收敛;二是工具链选择,中小规模用 scikit-learn 或自定义 NumPy 实现,海量数据用 Spark ALS,追求极致效果用 TensorFlow/PyTorch 自定义损失函数;三是业界案例,Netflix Prize 的冠军方案以矩阵分解为骨架,Spotify 用加权矩阵分解处理播放行为,淘宝用分解结果做召回并叠加实时行为。标准化之后,「读心调配」从个人手艺变成了可复制的工业流水线。

前沿发展演进

如今的果汁铺引进了一台「智能调饮机」:它不再手动设置甜酸配比,而是通过深度学习自动学习风味组合。矩阵分解的思想没变——从交互数据中提炼用户和物品的隐藏表示——但「表示」的生成方式正在进化。

深度学习的第一个方向,是用神经网络替代内积。NCF(神经协同过滤)把用户向量和物品向量喂给多层感知机,让网络学习非线性交互——比如「喜欢酸的人其实也喜欢微苦」,这种模式是简单内积很难捕捉的。第二个方向是图神经网络:把用户和物品看作二部图的节点,评分为边,用 GCN 在图上做邻居聚合,让向量表示包含更高阶的关系——相当于不只从顾客的直接打分学习,还参考「他朋友的打分」「同类果汁的粉丝群体」等信息。

r^ui=σ(h⊤(pu⊙qi))\displaystyle \hat{r}_{ui} = \sigma \left( h^{\top} (p_u \odot q_i) \right)

1. p_u ⊙ q_i 逐元素乘:用户因子与物品因子对应位置相乘,保留「同风味相撞」的信息。

(pu⊙qi)f=puf⋅qif\displaystyle (p_u \odot q_i)_f = p_{u f} \cdot q_{i f}

2. h 输出层权重:一个可学习的向量,对每个因子的交互做加权求和。

h∈Rk\displaystyle h \in \mathbb{R}^{k}

3. σ sigmoid 函数:把加权和压到 0-1 区间,作为预测打分或点击率的概率。

σ(x)=11+e−x\displaystyle \sigma(x) = \frac{1}{1 + e^{-x}}

至于大模型时代,LLM 的语义理解能力被用来做冷启动——新用户注册时,根据他填写的兴趣标签让大模型生成初始用户画像向量,再与 MF 因子结合。这套组合拳看似花哨,但骨子里的逻辑依然是:从零散反馈中发现隐藏风味。矩阵分解作为高效、可解释、易上线的基线模型,不仅没有过时,反而成为验证所有新方法的基准线。调饮机再智能,调的还是那几味——甜、酸、清爽,只是表达得更细腻了。

AI 应用场景

电商商品推荐(淘宝 / 亚马逊)

阶段:召回 / 粗排

解决的问题:用户行为大多是点击、加购、下单等隐式信号,没有显式的 1-5 星评分;且用户-商品矩阵极其稀疏,传统协同过滤难以找到可靠邻居。

具体案例:电商平台把「用户-商品交互矩阵」交给 ALS 分解,得到用户向量和商品向量。用户请求时,将他最近的交互商品向量折叠成用户向量,与千万级商品向量做内积召回 Top-N 候选,再交给精排模型。隐式反馈场景下,对点击、加购、下单赋予不同置信度权重(ALS-WR),大幅提升召回准确率。

视频流媒体推荐(Netflix)

阶段:个性化排序 / 评分预测

解决的问题:评分矩阵补全会引入噪声,传统 SVD 无法直接处理稀疏数据,需要一种能在原始稀疏矩阵上直接学习的方法。

具体案例:Netflix Prize 竞赛中,Simon Funk 发布的 FunkSVD 在稀疏评分矩阵上直接学习用户与电影因子,一举将 RMSE 降低近 8%,成为竞赛夺冠方案的核心组件。此后 Netflix 将矩阵分解融入推荐系统主链路,结合偏置项和隐式反馈特征,支撑了首页 80% 以上的个性化影片选择。

音乐流媒体推荐(Spotify)

阶段:召回 / 探索与利用平衡

解决的问题:播放次数不等于喜欢程度——单曲循环可能因为曲目较短或用户在做家务;同时每周有大量新歌上架,冷启动严重。

具体案例:Spotify 使用加权矩阵分解(ALS-WR)从播放行为中生成用户与歌曲嵌入向量,播放时长越长、置信度越高;新的冷门歌曲则通过音频内容特征(声学向量)映射到因子空间,保证新歌也能被「刚听过类似曲风」的用户召回。

Python 演示

python59 行
import numpy as np  class ALS:    """交替最小二乘矩阵分解,用于协同过滤评分预测"""     def __init__(self, n_factors=5, reg=0.1, n_iter=10, seed=42):        self.n_factors = n_factors        self.reg = reg        self.n_iter = n_iter        self.seed = seed     def fit(self, R):        rng = np.random.RandomState(self.seed)        n_users, n_items = R.shape        self.P = rng.rand(n_users, self.n_factors) * 0.1        self.Q = rng.rand(n_items, self.n_factors) * 0.1        for _ in range(self.n_iter):            # 固定 Q 更新 P            for u in range(n_users):                items = np.where(R[u, :] > 0)[0]                if len(items) == 0:                    continue                Q_sub = self.Q[items, :]                A = Q_sub.T @ Q_sub + self.reg * np.eye(self.n_factors)                b = Q_sub.T @ R[u, items]                self.P[u, :] = np.linalg.solve(A, b)            # 固定 P 更新 Q            for i in range(n_items):                users = np.where(R[:, i] > 0)[0]                if len(users) == 0:                    continue                P_sub = self.P[users, :]                A = P_sub.T @ P_sub + self.reg * np.eye(self.n_factors)                b = P_sub.T @ R[users, i]                self.Q[i, :] = np.linalg.solve(A, b)        return self     def predict(self, u, i):        return float(self.P[u] @ self.Q[i])     def rmse(self, R):        rows, cols = np.nonzero(R)        preds = np.sum(self.P[rows] * self.Q[cols], axis=1)        return float(np.sqrt(np.mean((R[rows, cols] - preds) ** 2)))  if __name__ == "__main__":    R = np.array([        [5, 3, 0, 1, 4, 0],        [4, 0, 0, 1, 4, 2],        [1, 1, 0, 5, 0, 4],        [0, 2, 4, 0, 2, 5],        [2, 0, 3, 4, 0, 1],    ], dtype=float)    model = ALS(n_factors=2, reg=0.1, n_iter=15)    model.fit(R)    print("训练集 RMSE:", round(model.rmse(R), 4))    print("预测用户1对第3款果汁的评分:", round(model.predict(0, 2), 2))
目的:用纯 NumPy 实现交替最小二乘,演示「固定 Q 更新 P、固定 P 更新 Q」的核心循环,在稀疏评分矩阵上完成因子学习与评分预测。
  1. self.P = rng.rand(n_users, self.n_factors) * 0.1用小随机数初始化用户因子矩阵,避免全零导致的对称困境和过大初值
  2. items = np.where(R[u, :] > 0)[0]取出用户 u 打过分的所有物品下标,0 表示缺失评分,不参与训练
  3. A = Q_sub.T @ Q_sub + self.reg * np.eye(self.n_factors)构造正规方程系数矩阵,正则项加在对角线上保证可逆
  4. b = Q_sub.T @ R[u, items]构造闭式解右侧向量,本质是「已知因子与评分的相关度」
  5. self.P[u, :] = np.linalg.solve(A, b)直接求解线性方程组,一步得到用户 u 的最优因子,这就是最小二乘闭式解
  6. preds = np.sum(self.P[rows] * self.Q[cols], axis=1)用向量化点积批量预测所有已知评分,避免显式 for 循环
  7. model.fit(R)训练入口:15 轮交替更新后,P 与 Q 逐步逼近能使评分误差最小化的因子矩阵

常见误区

误区我只要把用户-物品评分矩阵丢给 SVD 库函数,就能直接得到用户向量和物品向量开始做推荐。

正解标准 SVD 要求矩阵完整无缺失。评分矩阵通常九成以上是空格,必须先补全(如均值填充)再分解,但补全会引入大量噪声,让学出来的因子失真。这正是 Simon Funk 发明 FunkSVD、以及 ALS 直接在稀疏矩阵上迭代求解的原因——它们跳过了补全步骤,只在已知评分上优化。

为何常见:很多教程直接展示 A=UΣVᵀ 的分解图示,省略了「矩阵必须完整」这个前提;而工业库(如 Spark ALS)屏蔽了稀疏性处理细节,容易让人误以为 SVD 天生就能吃稀疏数据。

误区因子越多,能捕捉的偏好细节越丰富,模型效果一定更好。直接把 k 设成 1000,是不是能把所有人的口味都记住?

正解k 越大,模型容量越大,但评分矩阵提供的监督信号有限。k 过大会把个别评分的噪声当成规律,表现为训练误差持续下降而验证误差上升——这就是过拟合。一般用交叉验证选择 k,常见范围在 10 到 200 之间,具体取决于数据规模和稀疏程度。

为何常见:直觉上「细节越丰富越好」很符合常理,但机器学习中模型容量必须与数据量匹配。初学者容易只盯着训练集表现,忽略泛化能力。

误区离线 RMSE 从 1.2 降到 0.9,说明我的推荐系统一定变好了,可以放心上线。

正解RMSE 只衡量评分预测的绝对误差,它可能通过「把所有预测都收敛到热门物品的平均分」来刷低,却导致推荐列表高度集中在少数爆款上——覆盖率低、多样性差,用户看到的推荐千篇一律。评估必须综合覆盖率、多样性、新颖度,并通过线上 A/B 实验验证真实业务指标。

为何常见:离线指标可复现、易对比,大家容易把「指标好」等同于「业务好」,忽略了离线指标只是线上效果的间接代理。

误区SVD 是线性代数里的数学分解,ALS 是推荐系统里的另一种算法,两者没有关系。

正解ALS 是求解矩阵分解目标函数的一种优化算法。传统 SVD 是矩阵分解的一种数学形式,但它要求矩阵完整;FunkSVD 把目标改成最小化已知评分误差后,可以用 SGD 或 ALS 求解。Spark MLlib 的 ALS 正是在最小化加权平方误差的意义上求解矩阵分解参数,所以 ALS 是「矩阵分解家族」的成员,而非并列的另一条路线。

为何常见:工具库只暴露 ALS 接口,不解释它来自矩阵分解;教科书只讲 SVD 的线性代数定义。两个知识来源在脑海中没有打通,就形成了「两套算法」的错觉。

误区矩阵分解训练好之后,新注册的用户也能立刻得到精准推荐——毕竟模型已经学会了所有用户的偏好。

正解矩阵分解只能为「有评分记录」的用户和物品生成因子向量。新用户没有任何交互,P 矩阵里根本没有他的行,内积打分无从算起。实际工程中需要组合拳:用内容特征(口味标签、画像属性)初始化因子向量、用热门推荐兜底、或先让用户做几个偏好选择,等积累少量行为后再切回矩阵分解。

为何常见:演示数据里所有用户都有评分,教程也很少展示「新用户没有因子向量」的边界情况,造成「模型训练好就万事大吉」的错觉。

面试题

Q1. 在奇异值分解 A = UΣVᵀ 中,Σ 对角线上的元素 σ₁, σ₂, …… 被称为?

单选题 · easy

  • A.特征向量
  • B.奇异值← 正确答案
  • C.特征根
  • D.主成分

答案:B。奇异值分解中 Σ 的对角线元素就叫奇异值,对应矩阵在该方向上的「拉伸倍数」,可以理解为果汁铺里每种风味的流行度权重。特征向量是 U 或 V 的列向量;特征根是方阵特征值;主成分是 PCA 的说法。B 正确。

Q2. 传统 SVD 无法直接用于用户-物品评分矩阵分解推荐的原因是?

单选题 · mid

  • A.矩阵必须完整无缺失← 正确答案
  • B.奇异值无法被计算
  • C.矩阵必须低秩稠密
  • D.用户数量远远不足

答案:A。传统 SVD 要求矩阵中每个元素都有定义,而评分矩阵通常九成以上是空格。必须先补全(如均值填充)才能分解,但补全会引入噪声。FunkSVD 等改进方案直接在原始稀疏矩阵上学习因子,绕开了这个限制。A 正确。

Q3. FunkSVD 与传统 SVD 在推荐场景中的最大区别是?

单选题 · mid

  • A.直接学习用户物品因子← 正确答案
  • B.先补全矩阵再分解
  • C.保留更多奇异值
  • D.不依赖梯度下降

答案:A。FunkSVD 不再要求补全矩阵,而是直接最小化已知评分上的预测误差,学习用户因子 p_u 和物品因子 q_i。B 是传统 SVD 的做法;C 恰恰相反,SVD 要截断而不是保留更多;D 错误,FunkSVD 通常用 SGD 或 ALS 求解。A 正确。

Q4. ALS 算法固定物品因子矩阵 Q 后,用户因子 p_u 的最优解公式是?

单选题 · extreme

  • A.(QᵀQ + λI)⁻¹Qᵀr_u← 正确答案
  • B.(PᵀP + λI)⁻¹Pᵀr_i
  • C.Q(QᵀQ + λI)⁻¹r_u
  • D.P(PᵀP + λI)⁻¹r_i

答案:A。固定 Q 时,用户 u 的损失为 ||r_u - Qp_u||² + λ||p_u||²,对 p_u 求导并令其为零,得到闭式解 p_u = (QᵀQ + λI)⁻¹Qᵀr_u。A 正确。B 和 D 是固定 P 更新物品因子的形式;C 的矩阵维度不匹配,形式错误。这正是 ALS 效率高的关键。

Q5. 在加入偏置项的矩阵分解模型中,评分预测公式是?

单选题 · mid

  • A.r̂ui = μ + b_u + b_i + p_uᵀq_i← 正确答案
  • B.r̂ui = μ + b_u + b_i + p_uᵀq_u
  • C.r̂ui = μ + b_i + b_u + p_uᵀp_i
  • D.r̂ui = μ + b_u + b_i + q_uᵀq_i

答案:A。全局均值 μ 描述整体基准,b_u 是用户偏置(某人打分手松手紧),b_i 是物品偏置(果汁本身人气),最后是用户因子与物品因子内积刻画个性化口味。B、D 错误使用了 q_u、p_i 等错位因子;C 用物品因子与用户因子相乘,维度与语义都不对。A 正确。

Q6. 下列哪些属于推荐系统离线评估指标?

单选题 · hard

  • A.RMSE 误差率← 正确答案
  • B.覆盖率指标← 正确答案
  • C.用户活跃度
  • D.多样性指标← 正确答案

答案:ABD。离线评估从预测精度(RMSE/MAE)、覆盖率(推荐结果是否覆盖足够多物品)、多样性(推荐列表是否同质化)等角度衡量模型。C 用户活跃度属于业务运营指标,反映用户使用频率,不属于推荐模型的评估指标。A、B、D 正确。

Q7. 矩阵分解中潜在因子维度 k 对模型的影响是?

单选题 · easy

  • A.越大越容易过拟合← 正确答案
  • B.越小拟合能力越强
  • C.与模型效果没有关系
  • D.越大运行速度越快

答案:A。k 越大,模型可表达的信息越多,但评分矩阵本身信息有限,k 过大会把噪声也学进去,表现为训练误差低、验证误差高(过拟合)。k 过小则欠拟合。需要通过交叉验证选择合适维度。A 正确。

Q8. 与 SGD 相比,ALS 求解矩阵分解的优势有哪些?

多选题 · hard

  • A.可并行化独立子问题← 正确答案
  • B.无需调节任何超参数
  • C.每次迭代全局最优子解← 正确答案
  • D.完全避免过拟合风险

答案:AC。ALS 固定一方后,每个用户/物品子问题都是凸的最小二乘问题,闭式解是子问题的全局最优解,且各子问题相互独立、天然可并行——这正是 Spark ALS 的并行基础。但 ALS 仍需调节 λ、k 等超参数,也会过拟合,B 和 D 错误。A、C 正确。

Q9. 隐式反馈场景(如播放次数、点击次数)常用哪种矩阵分解变体?

单选题 · mid

  • A.加权正则变体← 正确答案
  • B.传统 SVD 变体
  • C.FunkSVD 算法
  • D.主成分分析法

答案:A。隐式反馈没有显式评分,只有行为强度(播放次数多 = 更喜欢)。加权正则变体 ALS-WR 对高置信度样本赋予更大权重,是处理隐式反馈的经典方法。传统 SVD 无法处理稀疏矩阵也不建模置信度;FunkSVD 面向显式评分;主成分分析不属于协同过滤方法。A 正确。

Q10. 面对新用户冷启动,以下哪些策略可行?

多选题 · easy

  • A.内容特征补充← 正确答案
  • B.热门物品推荐← 正确答案
  • C.用户主动反馈← 正确答案
  • D.放弃所有新用户

答案:ABC。冷启动时没有历史评分,可以用物品内容特征(如口味标签)推断偏好,或先推热门物品保证基础体验,也可让用户主动选择感兴趣的类型收集反馈。D 显然不可行——放弃新用户等于放弃增长。A、B、C 正确。