社区转角有家“老王酒馆”,吧台上挂着一块巨大的评分木板:横轴是店里的一百款鸡尾酒,纵轴是常来的六十位客人,格子里是他们给酒打的1~5星。仔细看你会发现,大部分格子是空的——客人没喝过那么多酒,评分矩阵稀疏得可怜。老王却有个天赋:他只要看到两三个格子,就能猜到某位客人会不会喜欢某款新酒。秘密在于,他相信口味是由少数几个维度组成的:甜度、烈度、果香。每位客人在这几个维度上有一个“口味坐标”,每款酒也有一个“属性坐标”,两者越接近,评分就越高。计算机要做的事,就是替老王把这张稀疏的评分表“拆开”:拆成一张用户口味表和一张酒品属性表,两张小表一乘,空掉的位置就能被估算出来。这个思路在数学上叫矩阵分解(MF),借用了SVD的分解美学时,又被称为隐因子模型。沿着老王调酒的故事,我们将一步步拆解推荐系统的这套数学之美,以及它如何演化出带偏置的MF、SVD++等经典方案。

岗位关联

矩阵分解是推荐系统工业化落地的基石算法,掌握MF/SVD及其变体,你就能独立完成评分预测与隐式反馈推荐的建模、训练、调优和评估,这是推荐算法工程师最核心的技能之一。

目录
  1. 可视化演示
  2. 矩阵分解思想
  3. SVD数学基础
  4. 基础MF模型
  5. 带偏置的MF
  6. SVD++变体
  7. 训练算法
  8. 工程实践
  9. 应用评估
  10. AI 应用场景
  11. Python 演示
  12. 常见误区
  13. 面试题

可视化演示

展示LSTM单元在时间序列上的信息流动,包括遗忘门、输入门、输出门的协同工作以及单元状态的传递过程。

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

LSTM单元 - 遗忘门阶段 遗忘门控制前一时刻单元状态的保留比例 输入门决定新信息写入单元的多少 f=σ(Wf·[ht-1,xt]+bf)

矩阵分解思想

老王发现,黑板上的评分虽然零散,却暗藏规律:喜欢“蜜桃甜心”的人,往往也会给“荔枝莫吉托”打高分;而偏爱“尼格罗尼”这种烈酒的客人,对甜酒总是嗤之以鼻。这说明口味的决定因素并不多——甜度、烈度、果香,三个维度就足以解释大半评分。用数学语言说,评分矩阵 \(R\) 的“有效自由度”远小于它表面的规模,它约等于两个低维小矩阵的乘积:一个记录用户的“口味坐标”,一个记录酒品的“属性坐标”。这正是矩阵分解(Matrix Factorization, MF)的核心思想:用低秩近似来捕捉高维稀疏数据背后的隐因子结构。

假设一共有 \(m\) 个用户、\(n\) 款酒,我们想用 \(K\) 个隐因子(如甜、烈、果香)来解释所有评分。把 \(m\times n\) 的评分矩阵 \(R\) 近似拆成两个矩阵:用户因子矩阵 \(P\)(\(m\times K\),每行是某位客人的口味向量)和物品因子矩阵 \(Q\)(\(n\times K\),每行是某款酒的属性向量)。两者相乘,就得到对整张评分表的估计。

老王把这个想法讲给常客听,大家都觉得有道理:与其把每对“人-酒”单独记忆,不如压缩成几十个公共口味坐标。不过有个现实难题——新客人只喝过一杯酒,几乎没有评分,这时的口味坐标很难猜,这就是所谓的冷启动问题。好在后续的偏置项和SVD++变体,会从“行为”里再挖出线索来。

Rm×n≈Pm×K Qn×K⊤\displaystyle R_{m\times n} \approx P_{m\times K}\, Q_{n\times K}^{\top}
\(P\) 和 \(Q\) 就像两张“口味坐标表”,相乘后补全整张评分黑板。
  1. 评分矩阵 \(R\)\(m\) 行用户乘 \(n\) 列酒品的真实评分表,大部分格子是空的。
    Rm×n,rui∈{1,2,3,4,5}R_{m\times n},\quad r_{ui}\in\{1,2,3,4,5\}
  2. 用户因子矩阵 \(P\)每一行是用户 \(u\) 在 \(K\) 个隐因子上的“口味坐标”,表示他喜欢甜还是喜欢烈。
    Pm×K,Pu,:=pu∈RKP_{m\times K},\quad P_{u,:} = p_u \in \mathbb{R}^{K}
  3. 物品因子矩阵 \(Q\)每一行是物品 \(i\) 在同样 \(K\) 个隐因子上的“属性坐标”,表示这款酒有多甜、多烈。
    Qn×K,Qi,:=qi∈RKQ_{n\times K},\quad Q_{i,:} = q_i \in \mathbb{R}^{K}
  4. 近似符号 \(\approx\)不需要完全还原每个格子,只希望在“已知评分”上误差尽量小,空位交给模型去猜测。
    min⁡∑(u,i):rui 已知(rui−qi⊤pu)2\min \sum_{(u,i): r_{ui}\text{ 已知}}(r_{ui} - q_i^{\top}p_u)^2

SVD数学基础

老王师从一位数学系老先生,老先生告诉他:任何一张完整的数字表格,都能被拆成“字母积木”——三个矩阵相乘,中间那块对角矩阵上的数字从大到小排列,像一串珍珠。这就是奇异值分解(Singular Value Decomposition, SVD)。在线性代数里,SVD 是特征值分解在非方阵上的推广:它把矩阵 \(R\) 分解为左奇异向量矩阵 \(U\)、奇异值对角矩阵 \(\Sigma\) 和右奇异向量矩阵 \(V^{\top}\) 的乘积。

老先生强调:经典的 SVD 精确分解要求矩阵是“完整的”——可评分黑板有 95% 的格子空着,直接套公式根本行不通。后来推荐系统领域的 Simon Funk 受到 SVD 的启发,只分解已知评分,用迭代优化代替解析分解,这就是所谓的 FunkSVD / 隐因子模型。名字里虽然带着 SVD,但它更像“借用 SVD 精神的数值优化”:数据稀疏没关系,我们只对着已知格子使劲。

Rm×n=Um×m Σm×n Vn×n⊤\displaystyle R_{m\times n} = U_{m\times m}\, \Sigma_{m\times n}\, V_{n\times n}^{\top}
\(U\) 和 \(V\) 分别是“用户侧”和“物品侧”的正交方向,\(\Sigma\) 上的奇异值像珍珠一样按重要性从大到小排列。
  1. 左奇异向量矩阵 \(U\)\(m\times m\) 的正交矩阵,列向量是用户空间的“隐方向”,相当于把所有客人的口味模式归纳为若干标准方向。
    U⊤U=ImU^{\top}U = I_{m}
  2. 奇异值矩阵 \(\Sigma\)对角线上的 \(\sigma_1 \ge \sigma_2 \ge \cdots \ge 0\) 称为奇异值,刻画每个方向携带的信息量。
    Σ=diag(σ1,σ2,…,σmin⁡(m,n))\Sigma = \mathrm{diag}(\sigma_1, \sigma_2, \ldots, \sigma_{\min(m,n)})
  3. 右奇异向量矩阵 \(V^{\top}\)\(n\times n\) 的正交矩阵,行向量是物品空间的“隐方向”,对应酒品属性在标准方向上的投影。
    V⊤V=InV^{\top}V = I_{n}
  4. 低秩截断保留前 \(K\) 个最大的奇异值及其对应向量,就得到对原矩阵的最佳 \(K\) 秩近似,丢弃的恰是噪声和冗余。
    R≈Uk Σk Vk⊤,k≪min⁡(m,n)R \approx U_{k}\,\Sigma_{k}\,V_{k}^{\top},\quad k \ll \min(m,n)

基础MF模型

如果评选“推荐系统史上最优雅的一行公式”,老王会投给基础 MF 模型:预测用户 \(u\) 对物品 \(i\) 的评分,就是用户口味向量 \(p_u\) 与物品属性向量 \(q_i\) 的内积。想象老王手里有两本小册子:一本写着每位客人在甜、烈、果香上的权重,另一本写着每款酒在这三个维度上的得分。当客人点新酒时,老王把两页数字逐项相乘再加总,就得到一个匹配分——匹配度越高,预测评分越接近满分。

老王发现,如果只看这张“口味坐标卡”,他还能顺手解决一个伴随问题——防止模型死记硬背。正则化的思路是:在追求预测准的同时,惩罚过于夸张的向量长度。这就好比调配方时,不允许老王为了迁就某个极端客人而把“甜度权重”调到离谱的 100,最后的预测就会更稳健。训练时如何使用 \(p_u\) 和 \(q_i\) 去拟合已知评分,正是后面要讲的训练算法;而先给每个零散的评分补上“基准线”,则引出偏置项的故事。

r^ui=qi⊤pu=∑k=1Kqik puk\displaystyle \hat{r}_{ui} = q_{i}^{\top} p_{u} = \sum_{k=1}^{K} q_{ik}\, p_{uk}
\(p_u\) 与 \(q_i\) 的“口味匹配度”越高,预测评分越高,这就是隐因子模型最朴素的直觉。
  1. 用户隐向量 \(p_u\)用户 \(u\) 在 \(K\) 个隐因子上的偏好权重,老王为每位客人画的口味坐标卡。
    pu=(pu1,pu2,…,puK)p_u = (p_{u1}, p_{u2}, \ldots, p_{uK})
  2. 物品隐向量 \(q_i\)物品 \(i\) 在 \(K\) 个隐因子上的属性得分,老王为每款酒画的属性坐标卡。
    qi=(qi1,qi2,…,qiK)q_i = (q_{i1}, q_{i2}, \ldots, q_{iK})
  3. 点积运算逐维度相乘后求和,甜度对甜度、烈度对烈度,每个维度都贡献一部分匹配度。
    ∑k=1Kqik puk\sum_{k=1}^{K} q_{ik}\, p_{uk}
  4. 因子维度 \(K\)隐因子的个数,\(K\) 越小模型越简单,\(K\) 越大表达能力越强但也越容易过拟合。
    K≪min⁡(m,n)K \ll \min(m, n)

带偏置的MF

老王很快撞上一堵墙:常客小美给几乎所有酒都打 4 星以上,而老张打分极少超过 2 星。如果用同一张“口味坐标卡”解释,模型会误以为小美和老张口味完全相反——可他们明明都爱喝威士忌。问题出在评分里混进了“手松手紧”的个人尺度。同样,某些酒(比如招牌莫吉托)几乎人人都给高分,它天生就受欢迎。老王醒悟:评分 = 全局基准 + 用户尺度 + 物品地位 + 真实口味匹配。前三个部分,就是偏置项。

老王在模型里加入偏置后,发现原先“小美和老张”的困惑迎刃而解:小美的 \(b_u\) 是 +0.8,老张的 \(b_u\) 是 -1.1,个性差异被偏置稳稳接住,隐因子终于可以专心表达“威士忌爱好者”这个共同点。业界实践表明,从基础 MF 升级为带偏置 MF 往往能让 RMSE 下降 10% 以上,而计算量几乎可以忽略——这正应了老王的格言:先算好基准,再谈个性匹配。

r^ui=μ+bu+bi+qi⊤pu\displaystyle \hat{r}_{ui} = \mu + b_{u} + b_{i} + q_{i}^{\top} p_{u}
偏置项先解释“基准线”,剩下的口味匹配才交给隐因子,预测因此更准也更易解释。
  1. 全局均值 \(\mu\)所有已知评分的平均值,是整个预测的“水位线”,所有偏差都从它开始计算。
    μ=1∣R∣∑(u,i)∈Rrui\mu = \frac{1}{|R|}\sum_{(u,i)\in R} r_{ui}
  2. 用户偏置 \(b_u\)用户 \(u\) 的评分尺度相对均值的稳定偏移,正数代表手松,负数代表手紧。
    bu=1∣Ru∣∑i∈Ru(rui−μ)b_u = \frac{1}{|R_u|}\sum_{i\in R_u}(r_{ui} - \mu)
  3. 物品偏置 \(b_i\)物品 \(i\) 相对均值的受欢迎程度,招牌酒会得到正偏置,踩雷新品则是负偏置。
    bi=1∣Ri∣∑u∈Ri(rui−μ−bu)b_i = \frac{1}{|R_i|}\sum_{u\in R_i}(r_{ui} - \mu - b_u)
  4. 口味匹配项 \(q_i^{\top} p_u\)去掉偏置后的净口味匹配,只有它代表“这位客人是否真的喜欢这款酒”。
    qi⊤pu=∑k=1Kqik pukq_i^{\top}p_u = \sum_{k=1}^{K} q_{ik}\, p_{uk}

SVD++变体

老王又观察到一件趣事:有些客人从不打分,但他们会“点单”——点了就是线索。小美虽然没给某些文艺片打分,但她看过五六部,这个“看过”本身就透露了口味。评分矩阵里的空白,不等于没态度;浏览、点击、购买这些隐式反馈,都是客人用脚投票。SVD++ 的核心思想,正是把“用户对哪些物品产生过行为”也压缩成向量,叠加进口味画像。

在 Netflix Prize 竞赛中,SVD++ 凭借这一手“把行为变成信号”的巧妙设计,显著刷新了 RMSE 纪录。它的名字虽然带“++”,但并非对 SVD 的数学加强,而是对隐因子模型的工程进化。老王对此的点评很接地气:光看客人怎么喝酒不够,还得看他点了哪些酒——行为比语言更诚实。

r^ui=μ+bu+bi+qi⊤(pu+∣N(u)∣−12∑j∈N(u)yj)\displaystyle \hat{r}_{ui} = \mu + b_{u} + b_{i} + q_{i}^{\top}\left(p_{u} + |N(u)|^{-\frac{1}{2}} \sum_{j\in N(u)} y_{j}\right)
行为集合 \(N(u)\) 里每个物品都贡献一个“隐式口味向量”,聚合后为用户的显式口味画像补上行为佐证。
  1. 行为集合 \(N(u)\)用户 \(u\) 产生过浏览、点击、购买等行为的物品集合,这些行为没有评分值,但暗示偏好。
    N(u)={j∣用户u 对物品j 有过行为}N(u) = \{ j \mid \text{用户} u \text{ 对物品} j \text{ 有过行为} \}
  2. 物品隐式向量 \(y_j\)每个物品 \(j\) 的隐式反馈向量,把“被点过”这件事编码为一个口味信号。
    yj∈RKy_j \in \mathbb{R}^{K}
  3. 聚合平均对集合中所有 \(y_j\) 求和,再除以 \(|N(u)|^{1/2}\),相当于融合出“行为画像”,同时防止行为多的用户向量被放大失控。
    ∣N(u)∣−12∑j∈N(u)yj|N(u)|^{-\frac{1}{2}} \sum_{j\in N(u)} y_{j}
  4. 增强用户画像\(p_u\) 代表显式口味,行为项代表隐式佐证,两者相加后再与 \(q_i\) 做内积。
    pu+∣N(u)∣−12∑j∈N(u)yjp_u + |N(u)|^{-\frac12}\sum_{j\in N(u)} y_j

训练算法

有了模型,接下来就是“怎么调配方”。老王尝一口预测酒,如果和客人期望差太多,就微调配方再试。在数学上,这个过程是优化一个损失函数:既要让预测接近真实评分,又不能让参数太大导致过拟合。损失由两项构成:已知评分上的平方误差,以及正则化项。

优化损失的方法有两条主流路线。第一条是随机梯度下降(SGD):每看到一个训练样本,就按当前误差的方向挪一小步,像调酒师凭口感反复微调。

第二条路线是交替最小二乘(ALS):固定物品矩阵 \(Q\) 时,关于用户向量的优化变成了一个标准最小二乘问题,可以直接求得闭式解;随后固定用户矩阵 \(P\) 再求物品矩阵,如此交替往复。

老王在小本子上对比两种路线:SGD 灵活,能随时加入偏置、时间衰减等特征;ALS 适合并行,每次固定一侧矩阵后各用户之间完全独立,可以开多个伙计同时算。训练时他还留了一手:每迭代几轮就在验证集上测一次 RMSE,一旦发现验证误差回升就提前“收手”,防止模型把已知评分背得滚瓜烂熟、新酒却推不准。

L=∑(u,i)∈Rtrain(rui−r^ui)2+λ(∥pu∥2+∥qi∥2)\displaystyle L = \sum_{(u,i)\in R_{train}} \left(r_{ui} - \hat{r}_{ui}\right)^{2} + \lambda\left(\|p_{u}\|^{2} + \|q_{i}\|^{2}\right)
ALS 每个交替步都在“解方程”,天然适合并行化,是隐式反馈场景的工业标配。
  1. 物品矩阵 \(Q\)把所有物品向量按行堆叠成的矩阵,固定不变时问题化为线性回归。
    Q=[q1,q2,…,qn]⊤Q = [q_1, q_2, \ldots, q_n]^{\top}
  2. 评分向量 \(r(u)\)用户 \(u\) 对所有物品的评分向量,缺失位置补 0(仅用于该闭式解,配合加权策略避免偏差)。
    r(u)=(ru1,ru2,…,run)r(u) = (r_{u1}, r_{u2}, \ldots, r_{un})
  3. 闭式解形如 \((Q^{\top}Q + \lambda I)^{-1}Q^{\top}\) 的操作本质是解一个带正则的线性方程组。
    pu=(Q⊤Q+λI)−1Q⊤r(u)p_u = (Q^{\top}Q + \lambda I)^{-1} Q^{\top} r(u)

工程实践

酒馆越开越大,黑板变成了数据库,老王必须把“调配方”做成流水线。第一个工程决策是存储:评分矩阵 99% 的格子是空的,绝不能存成 \(m\times n\) 的大稠密表,而要用稀疏三元组 \((u, i, r)\) 存储——只记谁给哪款酒打了多少分。第二个决策是计算:ALS 每一步中,固定 \(Q\) 后每个用户的 \(p_u\) 可以独立计算,把用户分片丢给多个计算节点并行跑,天亮前就能更新完整个模型。这两个技巧,让矩阵分解从学术玩具变成了可上线的工业系统。

老王还总结了两条工程经验:一是超参 \(K\) 和 \(\lambda\) 要用小验证集做网格搜索,\(K\) 从 20 试到 200,\(\lambda\) 从 0.01 试到 1;二是模型要周期性重训练,新酒和新客人不断加入,昨天的最佳配方今天可能就过时了。部署时,把训练好的 \(P\) 和 \(Q\) 放进内存,线上预测就是查表加一次点积——毫秒级响应,完全扛得住高并发。

∣Θ∣=(m+n)K+m+n+1\displaystyle |\Theta| = (m+n)K + m + n + 1
模型总参数数由用户数、物品数和隐因子数共同决定,这直接决定了内存与机器数量规划。
  1. 因子矩阵参数 \((m+n)K\)\(P\) 有 \(mK\) 个参数,\(Q\) 有 \(nK\) 个参数,两者共同承载“口味匹配”的表达。
    (m+n)K(m+n)K
  2. 偏置项参数 \(m+n\)每个用户一个 \(b_u\),每个物品一个 \(b_i\)。
    m+nm + n
  3. \(\mu\) 参数全局均值只有一个标量。
    +1+1
  4. 规模直觉1000 万用户、100 万物品、\(K=50\) 时,参数约 5.5 亿个,需要分布式存储与并行更新。
    (107+106)×50+107+106+1≈5.5×108(10^7+10^6)\times 50 + 10^7 + 10^6 + 1 \approx 5.5\times 10^8

应用评估

配方调完,怎么知道好不好?老王的方法很朴素:请一批老客户试新品,记录他们的真实反馈,和自己预测的分值对比。推荐系统离线评估的黄金指标之一就是 RMSE(均方根误差——预测的“偏航距离”)。它把每个测试样本的误差平方后取平均再开方,大误差会被放大,因此能敏锐暴露极端误判。

除了评分预测,实际业务还常做 TopN 推荐:给用户推荐一列酒,看推荐的酒里有多少被真正点击或购买,这就要用到召回率、精确率等指标,同时还要关注覆盖率——推荐列表不能总盯着那几款热门酒,否则就失去了“千人千面”的意义。老王的最终心得是:离线 RMSE 只是温度计,线上 A/B 测试才是试金石,两者一起看,才能把顾客伺候舒坦。

RMSE=1n∑t=1n(rt−r^t)2\displaystyle RMSE = \sqrt{\frac{1}{n}\sum_{t=1}^{n}\left(r_{t} - \hat{r}_{t}\right)^{2}}
RMSE 是推荐“偏航距离”的量尺:距离越小,预测越贴合真实口味。
  1. 测试样本数 \(n\)评估集里所有参与计算的评分个数,样本越多结论越可信。
    n=∣Rtest∣n = |R_{test}|
  2. 真实评分 \(r_t\)测试集中第 \(t\) 个样本的用户真实打分。
    rtr_t
  3. 预测评分 \(\hat{r}_t\)模型给出的估计值,两者的差就是误差。
    r^t\hat{r}_t
  4. 平方与开根误差先平方再平均再开方,让偏离 2 分的错误比偏离 1 分的错误受到四倍判罚。
    1n∑t=1n(rt−r^t)2\sqrt{\frac{1}{n}\sum_{t=1}^{n}(r_t - \hat{r}_t)^2}

AI 应用场景

视频平台的评分预测

阶段:离线召回与个性化排序

解决的问题:传统协同过滤面对稀疏矩阵时几乎失效,新影片没有评分就永远无法被推荐。矩阵分解用 \(K\) 个隐因子把用户与影片投射到同一空间,稀疏矩阵的空白格被“算”出来,新影片只要有历史行为(如预告片点击)就能进入推荐候选。

具体案例:经典影片推荐平台上,算法团队用带偏置 MF 预测用户对未看影片的打分,以预测分数作为候选集排序依据。落地后离线 RMSE 下降约 12%,线上完播率提升 8%,工程师还能用隐向量做“相似影片”召回,一举两得。

电商平台的隐式反馈推荐

阶段:召回与粗排

解决的问题:电商场景里“不买”不等于“不喜欢”,显式评分极度稀缺,而点击、加购、收藏等隐式反馈源源不断。SVD++ 的思想把“用户对哪些商品发生过行为”编码成隐式向量,叠加口味画像,冷门长尾商品也能获得合理曝光。

具体案例:某电商平台在用户行为日志上训练带置信权重的 ALS 模型,把点击次数映射为置信度 \(c_{ui}=1+\alpha r_{ui}\),用隐式反馈矩阵分解生成 TopN 推荐。上线后推荐位点击率提升 15%,且长尾商品曝光占比从 10% 提升到 26%。

内容平台的冷启动辅助

阶段:新用户冷启动

解决的问题:新用户只有一两次交互,传统矩阵分解难以产出可靠画像。带偏置 MF 提供了一种“用行为说话”的路径:全局均值给出保守基线,用户偏置和物品偏置在新数据不足时充当平滑先验,SVD++ 的行为向量让“看过三条新闻”就能勾勒出口味轮廓。

具体案例:资讯 App 对注册首日只有 3 次点击的新用户,用 SVD++ 的行为聚合项生成临时画像,结合热门偏置兜底,使新用户次日留存提升 5 个百分点;等行为积累到 20 次以上时,再平滑切换到完整 MF 模型。

Python 演示

python67 行
import mathimport random  def rmse(preds, truths):    return math.sqrt(sum((p - t) ** 2 for p, t in zip(preds, truths)) / len(preds))  class MatrixFactorizer:    def __init__(self, n_factors=4, n_epochs=40, lr=0.02, reg=0.1):        self.n_factors = n_factors        self.n_epochs = n_epochs        self.lr = lr        self.reg = reg        self.mu = 0.0        self.user_vec = {}        self.item_vec = {}        self.user_bias = {}        self.item_bias = {}     def _vec(self, key, store):        if key not in store:            store[key] = [random.uniform(-0.1, 0.1) for _ in range(self.n_factors)]        return store[key]     def predict(self, u, i):        p = self._vec(u, self.user_vec)        q = self._vec(i, self.item_vec)        self.user_bias.setdefault(u, 0.0)        self.item_bias.setdefault(i, 0.0)        score = self.mu + self.user_bias[u] + self.item_bias[i]        score += sum(p[k] * q[k] for k in range(self.n_factors))        return max(1.0, min(5.0, score))     def fit(self, data):        self.mu = sum(r for _, _, r in data) / len(data)        for _ in range(self.n_epochs):            random.shuffle(data)            preds = []            for u, i, r in data:                p = self._vec(u, self.user_vec)                q = self._vec(i, self.item_vec)                err = self.predict(u, i) - r                self.user_bias[u] -= self.lr * err                self.item_bias[i] -= self.lr * err                for k in range(self.n_factors):                    old_p, old_q = p[k], q[k]                    p[k] = old_p - self.lr * (err * old_q + self.reg * old_p)                    q[k] = old_q - self.lr * (err * old_p + self.reg * old_q)                preds.append(self.predict(u, i))            print("epoch", _, "RMSE", rmse(preds, [r for _, _, r in data]))  def build_data():    raw = [        [5, 3, 0, 1, 4, 0],        [2, 0, 4, 0, 3, 2],        [0, 4, 2, 3, 0, 5],        [4, 2, 0, 5, 1, 0],    ]    return [(u, i, r) for u, row in enumerate(raw) for i, r in enumerate(row) if r > 0]  if __name__ == "__main__":    mf = MatrixFactorizer()    mf.fit(build_data())    print("user 0 -> item 3 预测:", round(mf.predict(0, 3), 2))
目的:用随机梯度下降训练一个带偏置项和正则化的矩阵分解模型,在 4 用户 × 6 物品的评分数据上演示如何拟合已知评分并预测缺失格。
  1. self.mu = sum(r for _, _, r in data) / len(data)用全体已知评分的均值初始化全局基准线 \(\mu\)。
  2. err = self.predict(u, i) - r算出当前预测与真实评分的偏差,这是所有参数更新的“方向标”。
  3. self.user_bias[u] -= self.lr * err用户偏置沿负误差方向微调,手松手紧被逐步校准。
  4. self.item_bias[i] -= self.lr * err物品偏置同理,热门酒和雷酒的地位被自动学到。
  5. old_p, old_q = p[k], q[k]先保存旧值再更新,保证 \(p_u\) 和 \(q_i\) 都使用更新前的参数,符合 SGD 的正确语义。
  6. p[k] = old_p - self.lr * (err * old_q + self.reg * old_p)对用户因子执行带正则的梯度下降。
  7. q[k] = old_q - self.lr * (err * old_p + self.reg * old_q)对物品因子执行对称更新。
  8. random.shuffle(data)每个 epoch 打乱样本顺序,避免样本顺序带来的偏置。
  9. max(1.0, min(5.0, score))把预测分数裁剪到 1~5 星区间,保证输出符合评分含义。

常见误区

误区SVD++ 名字里带着 SVD,听起来就是经典奇异值分解的加强版,那一定是先把整个评分矩阵补齐再做分解吧?

正解不是的。经典 SVD 要求矩阵稠密,而推荐场景的评分矩阵超过 95% 是缺失的。SVD++ 属于隐因子模型(沿袭 FunkSVD 的思路),它借用“低秩近似”的数学美学,但实际是用梯度下降拟合已知评分,并在模型中加入用户历史行为项。名字里的 SVD 更多是历史渊源,而不是算法流程本身。

为何常见:初学者看到“SVD++”最容易望文生义,以为它直接调用线性代数里的 SVD 即可,忽略了推荐系统领域那场著名的 Netflix Prize 竞赛与 FunkSVD 的演进故事。

误区评分矩阵这么稀疏,先把缺失的格子填成 0 或者全局均值,矩阵就完整了,再直接做 SVD 不就行了吗?

正解填 0 等于假设“没喝过 = 讨厌”,这显然荒谬;填均值则是制造大量虚假证据,隐因子会被这些伪造的评分带偏。正确做法是只让已知评分参与损失计算,缺失值不进入训练,但可以在推理阶段被预测。这样模型才学会“未知”不等于“负向”。

为何常见:线性代数教材里的 SVD 默认矩阵是完整的,“补全再分解”是最自然的第一反应,却忽略了推荐系统数据缺失背后的语义。

误区评分预测不就是两个向量做内积吗?加上 \(\mu\)、\(b_u\)、\(b_i\) 似乎只是多几个参数,效果应该差别不大。

正解评分数据中很大一部分方差来自“评分尺度”和“物品热度”而非口味匹配。一个手松的顾客和一个手紧的顾客,如果不加偏置,他们的向量差异会被误认为“口味不同”,从而污染所有预测。加入偏置后,隐因子只需表达净口味匹配,RMSE 常有 10% 以上的下降,而参数成本几乎为零。

为何常见:矩阵分解的数学形式优美抢眼,均值项和偏置项显得“太简单”,容易被初学者忽略其统计上的重要性。

误区反正都是解同一个矩阵分解问题,SGD 和 ALS 只是优化器的区别,哪个顺手用哪个即可。

正解二者适用场景差别很大。显式评分稀疏时 SGD 灵活,能轻松扩展偏置、时间项等特征,适合在线增量;隐式反馈场景数据密集,SGD 每轮要扫过全部“用户-物品”对,而 ALS 固定一侧矩阵后各用户独立求解,天然可并行,且配合置信权重在隐式反馈上表现更稳定,是工业标配。

为何常见:在教材的小数据集上跑一遍,两者 RMSE 差不多,容易让人误以为实战中也没有区别。

误区\(K=20\) 效果不错,那把 \(K\) 加到 500 模型肯定更强,毕竟更高维意味着更强表达能力。

正解\(K\) 是模型容量的旋钮。超过某个阈值后,模型开始“背诵”已知评分,对未观测评分的预测变得不稳定,同时存储、计算和过拟合风险同步上升。实践中显式评分场景 \(K\) 常取 20~100,隐式反馈场景可放宽到 10~200,最终应依据验证集 RMSE 决定。

为何常见:刚接触机器学习的人习惯把“参数更多”和“效果更好”划等号,忽略了稀疏矩阵上高维参数更容易过拟合这一事实。

面试题

Q1. 矩阵分解MF把用户-物品评分矩阵分解成哪两个低维矩阵的乘积?

单选题 · easy

  • A.用户因子矩阵和物品因子矩阵← 正确答案
  • B.左奇异矩阵和右奇异矩阵
  • C.协方差矩阵和均值矩阵
  • D.相似度矩阵和邻接矩阵

答案:A。MF 的核心是把评分矩阵 \(R\) 近似拆成用户因子矩阵 \(P\) 和物品因子矩阵 \(Q\) 的乘积,维度均为 \(K\) 个隐因子。B 是经典 SVD 的分解因子,C 和 D 与 MF 无关。

Q2. 经典SVD直接分解评分矩阵时要求矩阵满足什么条件?

单选题 · easy

  • A.评分矩阵必须稠密完整← 正确答案
  • B.评分矩阵可以高度稀疏
  • C.评分矩阵必须是方阵
  • D.评分矩阵必须是对称方阵

答案:A。经典 SVD 要求矩阵是稠密的,而推荐场景的评分矩阵超过 95% 缺失,直接套用会失败。推荐系统中的 FunkSVD 因此只分解已知评分,衍生出隐因子模型。

Q3. 在带偏置的MF公式中,\(b_u\) 代表什么含义?

单选题 · easy

  • A.某件商品受欢迎的程度
  • B.所有评分的平均水平
  • C.该用户常打分的偏移量← 正确答案
  • D.用户与物品的相似程度

答案:C。\(b_u\) 是用户偏置,刻画用户“手松还是手紧”的稳定尺度;物品偏置 \(b_i\) 刻画商品受欢迎程度;全局均值 \(\mu\) 才是评分的平均水平。

Q4. 下列哪些信号属于SVD++中纳入的隐式反馈?

多选题 · mid

  • A.用户历史浏览过的电影列表← 正确答案
  • B.用户是否点击过某件商品← 正确答案
  • C.影迷打出的显式星级评分
  • D.用户注册时填写的年龄段

答案:AB。隐式反馈是“用行为投票”,浏览与点击都是典型代表;显式星级评分属于显式反馈;注册年龄段是用户画像属性而非反馈信号。

Q5. ALS交替最小化在固定物品矩阵Q后,如何求得用户矩阵P?

单选题 · mid

  • A.解一个线性方程组获得闭式解← 正确答案
  • B.通过随机梯度下降逐步逼近
  • C.对用户评分行做均值填充
  • D.用余弦相似度重建用户向量

答案:A。固定 \(Q\) 后,用户向量优化是标准最小二乘问题,可直接解 \(p_u=(Q^{\top}Q+\lambda I)^{-1}Q^{\top}r(u)\) 得到闭式解,这也是 ALS 适合并行计算的原因。

Q6. 关于SGD与ALS的对比,下列说法正确的是?

多选题 · mid

  • A.SGD适合流式在线的增量训练← 正确答案
  • B.ALS能并行求解固定矩阵一侧← 正确答案
  • C.隐式反馈场景ALS通常更高效← 正确答案
  • D.SGD必须载入全部数据训练

答案:ABC。SGD 逐个样本更新,天然支持流式与在线学习;ALS 每一次矩阵求解按用户或物品独立分解,便于并行;隐式反馈数据密集,ALS 闭式解+并行优势明显。SGD 并不要求全量数据载入内存。

Q7. RMSE指标的正确计算方式是?

单选题 · mid

  • A.绝对误差的平均值再开方
  • B.误差平方均值再开根号← 正确答案
  • C.预测值之和与真实值之比
  • D.误差高于阈值的样本占比

答案:B。RMSE 先对所有样本的误差平方求平均,再开根号,因此对大误差的惩罚更重,适合甄别极端误判。

Q8. 评分矩阵稀疏时,以下哪种做法最合理?

单选题 · mid

  • A.用零值填充后做SVD分解
  • B.用均值填充后做SVD分解
  • C.只用已知评分训练隐因子模型← 正确答案
  • D.用随机值填充后做SVD分解

答案:C。填充 0/均值/随机值都会引入虚假证据,污染隐因子。正确做法是只对已知评分计算损失,缺失值不参与训练但可用于预测。

Q9. SVD++相比基础MF,新增的向量 \(y_j\) 捕捉的是什么?

单选题 · mid

  • A.物品在显式评分中的偏置量
  • B.用户历史行为的隐式表征← 正确答案
  • C.物品与用户的余弦相似度
  • D.特征值分解中的奇异值

答案:B。\(y_j\) 是物品 \(j\) 的隐式反馈向量,把“用户对该物品产生过行为”编码为口味信号,聚合后增强用户画像,是 SVD++ 的关键创新。

Q10. 工程上训练MF模型时,下列哪些措施能有效缓解过拟合?

多选题 · hard

  • A.增大隐因子的个数设置
  • B.增大正则化惩罚系数λ← 正确答案
  • C.在验证集上使用早停法← 正确答案
  • D.只用训练集RMSE衡量效果

答案:BC。增大正则系数 \(\lambda\) 能限制参数幅度,早停法在验证误差回升前终止训练,两者都是经典防过拟合手段。增大 \(K\) 反而增加过拟合风险;只用训练集 RMSE 无法察觉过拟合。