🧮 第3章 无监督学习
📄 一句话概括

NMF 也是把一张表拆成两个矩阵相乘,但加了一条限制: 拆出来的两个矩阵不许有负数。 这一条限制看起来只是「不许减」, 实际决定了结果能不能被人读懂——每个数都成了「占比」。 Demo 里 12 个词、6 篇文档,非负分解出来的两个主题一个词只归一边,读起来像人话; 但误差和 LSA 打平,都是 0.3651。

📝 本节和前面几节的关系

上一节 LSA 的主题向量里允许出现负数,虽然这份数据恰好全是正数。 一旦出现负数就麻烦了: 「预算」权重 +0.5、「优惠」权重 −0.4,这个负号该怎么解释? 数学上它只是为了抵消, 语义上没人说得清。NMF 换了个思路:既然负号解释不了,那就干脆不许有负号。 这个想法最早是 Lee 和 Seung 在 1999 年的 Nature 文章里提出的, 他们的论据是「人脑表示事物也是只用加法」——看到一张脸,是各种部件的叠加,不是相减。

一、加一条「不许负」,会发生什么

先明确 NMF 要解的问题:给定一张非负矩阵 A(词频、像素亮度、购买记录都行),找两个非负矩阵 W 和 H, 让 W 乘 H 尽量等于 A。W 的每一列是一个「零件」,H 的每一列是「这张数据用了多少零件」。 这条非负限制带来的第一个后果是:只能加,不能减。 所以每个零件只能「由这些东西组成」, 不能表达「除了这些东西之外」。 听起来是退步,实际上正好对上人的直觉——一个主题就是一堆词的组合,它不会「负向包含」某些词。 第二个后果更重要:结果天然稀疏。 因为不能靠正负抵消来凑数,算法只能把不相关的权重压到 0。 Demo 里「预算」在主题 1 上是 1.207、在主题 2 上是 0.000;「优惠」反过来。 一个词基本只归一个主题,这就是「干净」。 第三个后果是代价:误差降不到 0。 负号是一种自由度,禁掉它就少了一种凑数的办法。 这一节的 Demo 会专门量这件事,结论有点出乎意料。

二、怎么解:两条乘法更新规则

这个问题没有解析解,只能迭代。经典解法是乘法更新,两条规则轮流用: H 先更新成 H 乘以「WᵀA」再除以「WᵀWH」;W 再更新成 W 乘以「AHᵀ」再除以「WHHᵀ」。 这两条式子看着像天书,其实意思很朴素: 如果某个位置的重构值偏小,那个位置的权重就乘一个大于 1 的系数往上涨; 偏大就乘一个小于 1 的往下降。 分子里有 A,分母里有 W·H,比值就是这个「偏大还是偏小」。 为什么一定要用乘法、不能用减法? 因为乘法保号:正数乘正数还是正数,所以只要初值非负,迭代一万轮也不会冒出负数。 非负约束是自动满足的,不需要额外的裁剪步骤。 这是乘法更新最漂亮的地方。 代价是收敛慢,而且会卡在局部最优。 Demo 里迭代 800 轮和 20000 轮的误差完全一样,不是没收敛,是收敛到了某个局部解就再也出不来了。 所以实际用的时候要多跑几个初值、取最好的。

三、W 和 H 各读什么

W 是「零件表」:行是词,列是主题,值是「这个词在这个主题里占多少份量」。 Demo 里第一列是会议主题,权重 1.810 的三个词是会议、审批、项目,1.207 的三个是纪要、报告、预算, 1.083 的三个是券、中奖、领取(在第二列)。 为什么会有 1.810 和 1.207 这种差别? 因为三篇会议文档里,「会议」「审批」「项目」都出现了,而「预算」「报告」「纪要」各只出现两次。 W 记的是「平均出现程度」,出现得多的词权重就高。这不是缺陷,是它忠实反映了数据。 H 是「配方表」:行是主题,列是文档,值是「这篇文档里这个主题占多少」。 Demo 里前三篇是 0.552 / 0.000,后三篇是 0.000 / 0.615。 这些数可以直接念成「占比」——因为它们非负、加起来有意义。 这正是 NMF 比 LSA 好用的地方。 LSA 的坐标是「投影长度」,可正可负,你不能说「这篇文档 −0.3 个开会主题」。 NMF 的坐标是「份量」,天生能当比例说。

四、和 LSA 比误差:这次它们打平了

教科书上常说「NMF 牺牲精度换可解释性」。 这一节的 Demo 量出来的结果不是这样:LSA 留 2 维的相对误差 0.3651, NMF 两个主题的相对误差也是 0.3651,一模一样。 为什么?因为这份数据的最优低秩近似本来就落在非负区域里。 两组词完全不交叉,奇异向量天生就是非负的,非负约束根本没拦住最优点,自然不付精度代价。 这条结果值得单独记住,因为它推翻了「NMF 一定更差」的直觉。 约束会不会付出代价, 取决于约束是不是「多余」的。 换成主题互相重叠、词有交叉的数据,非负约束就真的会挡住最优点,那时误差才会拉开。 所以两个方法的真正差别不在误差,在「凭什么定下这两个维度」:LSA 要求两个方向互不相关(正交), NMF 要求所有系数非负。数据越干净,两条路越容易走到同一个答案。

五、什么时候它真的赢

第一,需要「零件组合」这个解释的时候。 人脸识别里,NMF 分解出来的基向量看起来像眼睛、鼻子、 嘴这些部件;PCA 分解出来的是一张张「特征脸」,正负混杂,看着像鬼影。这是 NMF 的经典应用场景。 第二,需要「占比」这个说法的时候。 推荐系统里把用户-物品矩阵分解成「用户对主题的偏好」和「主题包含哪些物品」,两个都非负, 就能直接说「这个用户 70% 喜欢A类」。 第三,数据本身有「相加」的物理意义时。 光谱、音频、像素亮度都是这样:多个光源叠加、 多个音源混合。这时非负不是约束,是事实。 反过来说, 如果数据的物理意义允许相减(比如某些标准化后的指标、有正有负的收益率),就别硬套 NMF。 强行非负会把有意义的信息砍掉。

六、动手 Demo:不许有负号,主题就变成了「词的组合」

还是上一节那张 12 词乘 6 篇的词频矩阵,一个字都没改——这样才能和 LSA 的结果直接对照。 代码是十几行的乘法更新:随机初始化两个非负矩阵,交替更新 H 和 W,跑 800 轮。没有梯度、没有学习率。

import numpy as np

def nmf(A, k, iters=800, seed=0):             # 乘法更新:只乘,不加减
    rg = np.random.default_rng(seed)
    W = rg.random((A.shape[0], k)) + 0.1
    H = rg.random((k, A.shape[1])) + 0.1
    for _ in range(iters):
        H = H * (W.T @ A) / (W.T @ W @ H + 1e-12)
        W = W * (A @ H.T) / (W @ H @ H.T + 1e-12)
    return W, H

W, H = nmf(A, 2)
print(np.abs(A - W @ H).sum())                # 重构误差,降不到 0

实跑输出:

--------------------------------------------------------------------
NMF:非负矩阵分解,主题变成「词的组合」
--------------------------------------------------------------------

  还是同一张词频矩阵。但这次加一个限制:**分解出来的两个矩阵,所有元素都必须非负。**
   这一条限制看起来只是「不许负数」,实际上决定了结果长什么样。
  迭代 800 轮后,重构误差(绝对值之和)8.0000,原始矩阵一共 30 个计数。
  **这个误差不是 0,而且再迭代下去也降不下来**——两个主题装不下六篇各说各话的文档。

  【一】W:每个主题由哪些词组成
  词      主题 1    主题 2
  --------------------------
  会议     1.810     0.000
  审批     1.810     0.000
  项目     1.810     0.000
  抢购     0.000     1.625
  点击     0.000     1.625
  优惠     0.000     1.625
  纪要     1.207     0.000
  报告     1.207     0.000
  预算     1.207     0.000
  券       0.000     1.083
  中奖     0.000     1.083
  领取     0.000     1.083
  **注意这里没有负数。**
   所以每一列可以直接读成「这个主题里各个词占多少份量」。第一列是「预算 会议 审批」那一路,
  第二列是「优惠 抢购 点击」那一路。
  **而且一个词在两列里基本只出现在一列上——这就是「不许负」带来的干净。**

  【二】H:每篇文档里各主题占多少
  文档      主题 1    主题 2
  ----------------------------
  第 1 篇    0.552     0.000
  第 2 篇    0.552     0.000
  第 3 篇    0.552     0.000
  第 4 篇    0.000     0.615
  第 5 篇    0.000     0.615
  第 6 篇    0.000     0.615
  前 3 篇主题 1 的系数大,后 3 篇主题 2 的系数大。
  **而且这些数可以直接当成「这篇文档有百分之多少在讲这个主题」**
  ——因为它们非负、而且加起来有意义。

  【三】和 LSA 比,谁的误差小
  方法              相对重构误差
  --------------------------------
  LSA(留 2 维)    0.3651
  NMF(2 个主题)   0.3651
  **两个数一样。** 这条结果和很多教程上说的「NMF 精度差、换来可解释」不符,所以得多说一句:
  这份数据的两个主方向本来就落在「全正」的范围内,非负约束根本没拦住最优点,
  自然不付精度代价。**换个数据就未必。**
  那差别到底在哪?差别在「它凭什么把这两个维度定下来」:
  **LSA 要的是「两个方向互不相关」,NMF 要的是「只加不减」。**
   数据越干净(主题几乎不重叠),两条路会走到同一个答案——这次就是这样;数据一乱、
  要压的维数又不够用时,两条路才会明显分家。
  可解释性上 NMF 还是占便宜:它的每个数都非负,可以直接说成「占比」;
  LSA 的向量理论上可以出现负数,那时「权重 −0.4」这种话就没法翻译成人话了。
  **记住这个区别,比记住「哪个误差小」有用得多。**

先看 W 那张表的结构。12 个词分成两拨,每拨 6 个,另一列上精确是 0.000。 「一个词只归一个主题」这件事,是「不许负」直接逼出来的。 权重有 1.810、1.207、1.083 三档,别以为是算法抽风。三篇会议文档里,会议/审批/项目各出现 3 次, 纪要/报告/预算各 2 次,所以权重就分了三档。NMF 忠实反映了「哪个词更常见」,没有做任何平滑。 H 那张表里每列只有一个非零值。前三篇 0.552 对 0.000,后三篇 0.000 对 0.615。 同样是「两组完全不交叉」的结果——这份数据太干净了,干净到有点假。 最有价值的是最后那段对比:0.3651 对 0.3651,两个误差完全一样。 这和「NMF 精度差」的流行说法不符。 原因是非负约束这次根本没起作用——最优解本来就是非负的。 换份数据就不一定了。

30 秒实验

  1. 把主题数从 2 改成 3,W 和 H 会长成什么样?多出来的那一列在讲什么?
  2. 把初值改成「全 0.1」(完全一样的初值),两个主题还会分开吗?为什么?
  3. 把迭代次数从 800 改成 20000,误差会降吗?这说明它卡在什么上面?

常见误区

  1. 以为 NMF 一定比 LSA 精度差。这一节的实测是两个打平。 约束会不会付代价,要看约束是否「多余」。先量再下结论,别背结论。
  2. 忘了 NMF 的结果和初值有关。乘法更新会卡在局部最优,同一份数据跑两次可能得到不同的主题划分。 工程上必须多起几个初值,取误差最小的。
  3. 拿 NMF 处理有负数的数据。输入必须非负,这是硬要求。 标准化之后出现负值、或者做差分得到的增长率,都不能直接喂进去。 常见的补救是先平移到非负,但这会改变数据的物理意义。
自测三问(点开对答案) 1. 为什么乘法更新能自动保证结果非负? —— 因为初始值非负、分子分母都非负,正数乘正数除正数还是正数。 整个迭代过程不会产生负数,所以不需要额外裁剪。 2. W 和 H 分别对应现实里的什么? —— W 是「零件表」——每一列是一个主题由哪些词组成、各占多少份量; H 是「配方表」——每一列是一篇文档里各个主题占多少。 3. 这一节 LSA 和 NMF 的误差一样,说明 NMF 没有代价吗? —— 不能这么说。这次的误差一样,是因为最优低秩解本来就在非负区域里,约束没拦住任何东西。 换成主题重叠、词有交叉的数据,非负约束就会付出代价。

到 3.3 节为止,三个算法都在「压缩」。3.4 节换目标:不是压得小,而是压完之后两类分得开。

相关笔记