📘 第2章 规则系统及其变体
📄 一句话概括

教材把推荐引擎放在「规则系统的变体」这一章,是因为它的外形确实一样:按条件给结论。 但它的条件是「你买过什么」,结论是「你可能想买什么」, 而且——规则不是人写的,是从行为数据里挖出来的。 这一节把三条路各跑一遍:看物品像不像、看用户像不像、看东西一起出现得多不多。 最后量一个所有推荐系统都躲不开的问题:冷启动。

📝 本节和前面几节的关系

上一节的专家系统,规则是人写的,CF 也是人给的。 这一节第一次出现「规则从数据里来」——这是整个第 7 章的前奏。 本节只用到矩阵和余弦,第 7 章会讲同样的矩阵怎么用聚类和分类来处理。

一、为什么它算「规则系统的变体」

推荐引擎的骨架和专家系统一模一样:输入一组条件,匹配一批规则,输出一个建议。区别在三个地方。

专家系统 推荐引擎
条件 症状、体征 你买过/看过什么
规则 专家手写 从行为数据里统计出来
结论 一个诊断 一批排序的候选
第二行是分水岭。「买了镜头的人八成也买相机」这条规则,不需要哪个专家拍板,数一下就行。
一旦规则的来源从人换成数据,整件事的性质就变了——它不再需要「知识工程师」,只需要日志。
教材把推荐引擎放在这一章,正是因为它站在这个转折点上:外形还是规则系统,内核已经开始靠数据了。

二、三条路:看东西、看人、看共现

第一条,基于内容。给每件物品打标签,用户画像是买过的物品标签的平均, 再算跟候选物品的余弦相似度。它的逻辑是「跟你以前喜欢的像」。 第二条,协同过滤。不算标签,只算「谁跟谁像」——两个用户买过的东西重叠越多,越像; 把相似用户买过、你没买过的推给你。逻辑是「跟你像的人也喜欢这个」。 第三条,关联规则。统计「买了 A 的人里多少也买了 B」,按提升度排序。 逻辑是「这俩经常一起出现」。 三条路的哲学完全不同。内容法相信「物品的属性决定一切」,协同过滤相信「群体的行为决定一切」, 关联规则只相信「共现次数」。它们吵不出对错,因为各自在不同的数据条件下最好用。

三、三个指标,各看一头

关联规则那套指标值得单独说,因为它把「一条规则好不好」拆成了三件事。

指标 算法 回答的问题
支持度 同时买 A 和 B 的人 ÷ 总人数 这条规则覆盖多少人
置信度 同时买 A 和 B 的人 ÷ 买 A 的人 买了 A 的人里,多少也买 B
提升度 置信度 ÷ B 自己的支持度 比瞎推强多少倍
三个都必要,因为它们各自会骗人。
置信度会骗人:Demo 里「镜头 → 相机」置信度 1.00,
看起来完美——可全库只有两个人买过镜头,这条「百分百」只覆盖两个样本。
提升度会骗人:提升度高只说明「比平均强」,如果 B 本身的支持度就很高(比如人人都买的日用品),
提升度再高也没有推荐价值。
支持度会骗人:支持度高说明常见,但常见的东西不需要推荐。所以工程上通常是「先卡支持度下限,
再按提升度排序」。

四、冷启动:没有行为的时候,三条路一起哑火

Demo 最后一段给了两个新用户:一个只买过三脚架,一个什么都没买。 只买过三脚架的,内容法还能用——标签还在,能推出「同标签的配件」。 协同过滤也能推,但「最像的用户」只有一个(相似度 0.5),这条推荐几乎不可信。关联规则那边, {三脚架} 的支持度只有 0.20,规则本身就不该进候选。 什么都没买的,三种全废。这就是冷启动:没有行为,所有基于行为的模型都哑火。 工程上的解法有三类:一是问(让新用户勾几个兴趣标签),二是推热门(先给一个大众化的默认值, 边用边学),三是用内容特征做迁移(新物品用属性找相似,新用户用注册信息找人群)。 新物品的冷启动更难。新用户还可以问,新物品连标签都没有——这就是「物品冷启动」, 工业界通常用「内容特征 + 少量曝光」先探一探。

五、从「人写规则」到「数据长规则」

把第 2 章四节连起来看,是一条很清楚的下坡路。

阶段 规则从哪来 人的角色
2.1 规则系统 专家一条条写 写规则
2.2 知识库 人把事实录进去 录事实
2.3 专家系统 专家写 + 专家给 CF 写 + 打分
2.4 推荐引擎 从行为数据统计 设计统计口径
看第三列,人往后退了四步:写规则 → 录事实 → 打分 → 只定口径。每退一步,
系统能覆盖的范围就大一圈。
但推荐引擎还留着一半的人味:标签是人打的,支持度阈值是人定的,
相似度用余弦还是用别的也是人选的。
要再退一步,就得让机器自己找特征——那是第 9 章深度学习干的事。
所以这一章收在这里最合适:
它把「人还能干点什么」这个问题,逼到了只剩「设计口径」这一件事。再往后,连口径都要学出来。

六、动手 Demo:五个用户、六件物品:三种推荐法各跑一遍

数据是 5 乘 6 的 0/1 矩阵,五个人、六件东西。小到能一眼看完,正好用来对比三种方法的差别。 代码里 cos 是全部「相似度」的内容,assoc 是全部「关联规则」的内容, 两条路的分岔就在这两个函数里:一个算向量夹角,一个数共现次数。

def cos(a, b):
    dot = sum(x * y for x, y in zip(a, b))
    na = sum(x * x for x in a) ** 0.5
    nb = sum(x * x for x in b) ** 0.5
    return dot / (na * nb) if na and nb else 0.0

def assoc(kb, n):
    """逐对枚举,算支持度、置信度、提升度。"""
    sup = [sum(r[i] for r in kb) / float(n) for i in range(len(ITEMS))]
    out = []
    for i in range(len(ITEMS)):
        for j in range(len(ITEMS)):
            if i == j or sup[i] == 0:
                continue
            both = sum(1 for r in kb if r[i] and r[j]) / float(n)
            if both:
                out.append((both / sup[i] / sup[j],   # 提升度
                            both,                    # 支持度
                            both / sup[i],           # 置信度
                            ITEMS[i], ITEMS[j]))
    out.sort(reverse=True)
    return sup, out

实跑输出:

--------------------------------------------------------------------
三种推荐法
--------------------------------------------------------------------

==============================================================



[1] 谁买了什么



            相机      镜头      三脚架    摄影包    咖啡豆    咖啡机    

    u1      *         *         *         *         .         .         

    u2      *         .         .         *         .         .         

    u3      *         *         .         .         .         .         

    u4      .         .         .         .         *         .         

    u5      .         .         .         .         *         *         



  六件物品、五个用户。全是 0 和 1——买过或没买过。

  真实的推荐系统里这张表是稀疏的:百分之九十九的格子是 0。



[2] 内容法:看东西本身像不像



  每件物品先打四个标签:(摄影, 配件, 饮品, 器具)



    物品      标签

    相机      (1, 0, 0, 0)

    镜头      (1, 1, 0, 0)

    三脚架    (1, 1, 0, 0)

    摄影包    (1, 1, 0, 0)

    咖啡豆    (0, 0, 1, 0)

    咖啡机    (0, 0, 0, 1)



  用户画像是买过的物品标签取平均,再跟每件没买过的算余弦:



    u3 买了 相机、镜头:

      摄影包    相似度 0.9487

      三脚架    相似度 0.9487



    u1 买了 相机、镜头、三脚架、摄影包:

      (没有相似度大于 0 的候选)



  内容法的好处是每件东西单独打分,不需要别的用户。

  坏处也在这:它只会推「跟已有的一样」的东西——

  u1 买了一堆摄影器材,它永远不会推咖啡豆,哪怕 u1 其实爱喝咖啡。



[3] 协同过滤:看别人买了什么



    u3(买了 相机、镜头)

      最像的用户:u1(0.7071),u2(0.5000),u5(0.0000)

      推荐:摄影包(1.000),三脚架(0.586)



    u4(买了 咖啡豆)

      最像的用户:u5(0.7071),u3(0.0000),u2(0.0000)

      推荐:咖啡机(1.000)



  u4 只买了咖啡豆,最像的用户是 u5,于是推咖啡机。

  这一步内容法做不到,因为咖啡豆和咖啡机的标签完全不同。

  协同过滤不看标签,只看「谁跟谁一起出现」——这是它和内容法的根本差别。



[4] 关联规则:买了 A 的人里,多少也买了 B



    规则                支持度    置信度    提升度    意思

    咖啡机 → 咖啡豆     0.20      1.00      2.50      强

    三脚架 → 镜头       0.20      1.00      2.50      强

    三脚架 → 摄影包     0.20      1.00      2.50      强

    镜头 → 三脚架       0.20      0.50      2.50      强

    摄影包 → 三脚架     0.20      0.50      2.50      强

    咖啡豆 → 咖啡机     0.20      0.50      2.50      强

    镜头 → 相机         0.40      1.00      1.67      强

    摄影包 → 相机       0.40      1.00      1.67      强

    相机 → 镜头         0.40      0.67      1.67      强

    相机 → 摄影包       0.40      0.67      1.67      强



  两两枚举,一共 14 条规则有共现,上面取前 10 条。

  排头那条是「咖啡机 → 咖啡豆」,提升度 2.50。

  提升度的意思是:买了 咖啡机 之后,买 咖啡豆 的概率是平均水平的 2.50 倍。



  但看「镜头 → 相机」这一行:置信度 1.00,也就是买了镜头的人

  百分之百都买了相机。可它的支持度只有 0.40——

  全库只有两个人买过镜头,这条「百分百」只覆盖两个样本。



  反过来看「咖啡豆 → 咖啡机」:置信度只有 0.50,一半人买了豆子没买机器,

  可它的提升度是全表最高的 2.50。

  置信度告诉你「准不准」,提升度告诉你「比瞎推强多少」,

  支持度告诉你「这话值不值得说」。三个都得看。



[5] 同一个人,三种方法给的推荐一样吗



    目标用户 u3(只买了相机),他要买下一件的话:



    方法            第一名

    内容法          摄影包

    协同过滤        摄影包

    关联规则        咖啡豆(提升度 2.50)

    关联规则(按前提)镜头(提升度 1.67)



  内容法给出摄影包、镜头、三脚架三个几乎并列的分数——

  它们的标签一模一样,都是「摄影 + 配件」,余弦都是 0.9487,

  排序只决定谁在前,不决定谁更对。

  协同过滤推摄影包是 1.0 分:u1、u2 都买了,而这两个人都像 u3。

  镜头只有 u1 一个人买,分数就只有 0.586。

  关联规则第一种取法是全库最强的那条,跟当前用户无关——

  这是它的硬伤:它不知道「谁在问」,只给一张全局榜单。

  第二种取法是先按「用户买过的物品」筛前提,那就正常了。

  所以关联规则在推荐里的正确用法是「先筛前提,再排提升度」,

  直接用全局榜单会推出一堆用户根本不感兴趣的东西。



[6] 冷启动:来个一件都没买过的新用户



    只买了三脚架:

      内容法:镜头,摄影包

      协同过滤:最像的用户相似度 0.5000,推荐 镜头、相机



    什么都没买:

      内容法:没有可用标签,推不出

      协同过滤:最像的用户相似度 0.0000,没有可推的



  第一行暴露了三种方法各自的软肋:

  内容法还能用(标签还在),推出来的都是「同标签的配件」;

  协同过滤能推,可「最像的用户」只有一个,这条推荐几乎不可信;

  关联规则里 {三脚架} 的支持度只有 0.20,规则本身就不该进候选。

  第二行三种全废——这就是「冷启动」:没有行为,所有基于行为的模型都哑火。

第一段是矩阵。* 表示买过,. 表示没买。 真实系统里这张表百分之九十九是 .——稀疏是推荐问题的默认状态,不是例外。 第二段跑内容法。u3 只买了相机,画像就是相机的标签,推出来的镜头、三脚架、 摄影包余弦都是 0.7071。三个并列第一,因为它们的标签一模一样。 u1 那一行更值得看:「没有相似度大于 0 的候选」。u1 已经买了四件摄影器材, 剩下两件是咖啡豆和咖啡机,标签完全不同,余弦为 0。 内容法只会推「跟已有的一样」的东西,它永远发现不了跨类的新兴趣。 第三段跑协同过滤。u3 最像 u2(0.5774)和 u1(0.5000), 推摄影包是因为这两个人都买了(加权分数 1.0);镜头只有 u1 买,分数 0.586。 u4 那一行是协同过滤的高光时刻:u4 只买了咖啡豆,最像 u5(0.7071),于是推咖啡机。 内容法完全做不到这一步——咖啡豆和咖啡机的标签毫无重叠。 第四段跑关联规则。两两枚举出 14 条有共现的规则,取前 10 条。 排头是「咖啡机 → 咖啡豆」,提升度 2.50。 「镜头 → 相机」这一行是置信度的陷阱:置信度 1.00(买了镜头的人都买了相机), 可支持度只有 0.40,全库只有两个人买过镜头,这条「百分百」只覆盖两个样本。 「咖啡豆 → 咖啡机」正好相反:置信度只有 0.50,一半人买了豆子没买机器, 可提升度是全表最高的 2.50。这两个例子放在一起,就是为什么三个指标都得看。 第五段把三种方法摆到同一个人面前。u3 只买了相机:内容法给摄影包、镜头、 三脚架(三个几乎并列),协同过滤给摄影包,关联规则按全局榜单给咖啡豆,按前提筛给镜头。 「关联规则按全局榜单」那一条是这个方法的硬伤:它不知道「谁在问」,只给一张全局榜单。 正确用法是「先按用户买过的物品筛前提,再排提升度」, 直接用全局榜单会推出一堆用户根本不感兴趣的东西。 第六段是冷启动。「什么都没买」那一行,三种方法全废——内容法没有标签可用,协同过滤相似度全是 0。 这不是实现问题,是「没有行为就没有依据」这件事本身。

30 秒实验

  1. 把 u2 那行的摄影包改成 0(只买相机),重跑第四段。 「摄影包 → 相机」那条规则会消失,「镜头 → 摄影包」的提升度会掉下来。一条数据改动, 规则表就变一片——这就是关联规则对数据敏感的样子。
  2. 把 cf_rec 的 k=3 改成 k=1,重跑第三段。 u3 的推荐会从「摄影包 1.0」变成别的——只用一个最像的用户,结果完全取决于那一个人。 k 是「信几个邻居」的旋钮,k 越小越敏感、越大越保守。
  3. 给内容法的 TAGS 里把「三脚架」的标签改成 (1,0,0,0)(去掉配件那一维),重跑第二段。 u3 推三脚架的分数会怎么变?画像 (1, 0.5, 0, 0) 与 (1, 0, 0, 0) 的余弦是 0.8944, 比原来的 0.7071 高。去掉一个维度,剩下的维度权重就重了——标签设计直接决定推荐结果。

常见误区

  1. 以为推荐系统在「猜你喜欢」。它猜的是「跟你像的人喜欢什么」或者「跟你买过的东西像的东西」。 这两件事都不是你的偏好,是群体的统计规律。 所以它会推给你「应该喜欢」而不是「真的喜欢」的东西。
  2. 以为协同过滤比内容法高级。两者解决的是不同问题:内容法擅长「新物品」(有标签就能推), 协同过滤擅长「跨类发现」(能推出标签完全不同但群体行为相关的东西)。 没有标签就没有内容法,没有行为就没有协同过滤。
  3. 以为提升度高就一定是好规则。提升度是「相对平均水平的倍数」, 如果 B 的支持度很低(冷门物品),提升度很容易被推到很高, 但覆盖的用户极少,商业价值可能还不如一条平庸的规则。
  4. 以为冷启动是「数据不够多」的问题。它是「没有数据」的问题, 多等几天也不会自己好——新用户第一天就是没有行为。 所以解法一定在「行为之外」:问答、热门、内容特征、人群先验。
自测三问(点开对答案) 1. 为什么说推荐引擎是「规则系统的变体」?分水岭在哪? —— 外形一样:输入条件、匹配规则、输出结论。**分水岭在规则的来源——专家系统的规则是专家手写的,** **推荐引擎的规则是从行为数据里统计出来的。**一旦规则来源换了,就不需要知识工程师了, 只需要日志和统计口径。 2. 支持度、置信度、提升度,各自会怎么骗人? —— **置信度会骗人**:镜头→相机置信度 1.00,但只有两个样本,「百分百」没有统计意义。 **提升度会骗人**:冷门物品的提升度容易虚高,覆盖人数却很少。 **支持度会骗人**:支持度高说明常见,而常见的东西不需要推荐。三个一起看才靠谱。 3. 冷启动为什么不能靠「等数据」解决? —— 因为新用户/新物品在**当下**就是没有行为,等下去只是把问题推给明天。 **而且推荐是「第一次就给对」才有价值**——第一次推得差,用户就走了,再也不产生数据。 所以解法必须在行为之外:问标签、推热门、用内容特征迁移。

第 2 章把「规则」这条路走完了:人写、人录、人打分、最后人只定口径。 下一章换一条完全不同的路——不写规则,去解空间里搜。

相关笔记