跳到主要内容

数据截至 (上游 commit c187ef3271d5)

推荐系统与嵌入 — 一个编号怎么变成一串有意义的数

这一章讲三件事。第一件:「猜你喜欢」背后只有三条路,而且每一条的软肋都不一样。

第二件,是全书的一个枢纽:一个编号怎么变成一串有意义的数。 用户 ID 是 3542 还是 3543,本身不含任何信息;可模型只会算数。 这一章给的解法叫嵌入 —— 第 11 章讲一句话怎么进语言模型时,用的是同一招。

第三件:推荐的成绩不能用一个数报。 它要同时回答两个互相打架的问题: 该推的漏了没有,推出去的准不准。

在全书链条里的位置: 三个零件里,这一章换的是「数据摆成什么形状」—— 从数值、从像素,换成一堆没有大小关系的编号。训练循环还是那个循环。

这一章的主走查会失败。 书跑出来的推荐结果是错的,而且书只评了一句「毫不意外」。 我们把病因追到了两处,其中一处是结构性的 —— 第 7 节。

1. 顶层全景:一个玩家,和 5155 个游戏

这一章从头到尾只用一份数据:Steam 平台上 19.9 万条「谁买了什么游戏、玩了多久」的记录。 目标是给某个具体的玩家推十个他还没玩过的游戏。链条:

19.9 万条记录(用户 ID · 游戏名 · 是买还是玩 · 玩了多少小时)

├─① 按玩了多久换算成 1–5 分 ────→ 一张「谁给哪个游戏打几分」的表

├─② 把用户和游戏都编成连续号 ──→ 12393 个用户 × 5155 个游戏

├─③ 给每个编号配一串 32 个数 ──→ 这串数是训出来的,不是查出来的

├─④ 把两串数交给一个小网络 ────→ 吐出一个预测分数

├─⑤ 训 8 轮 ──────────────────→ 损失 0.6219 → 0.5057

├─⑥ 给 2 号玩家的 5155 个游戏各打一分,取最高的十个

└─⑦ 结果:他喜欢格斗和角色扮演,推给他四部足球经理游戏

图说:①②③④ 是这一章的新东西,⑤ 是老循环。⑥⑦ 是失败现场,也是这一章最值钱的部分。

2. 推荐只有三条路

结论先行:三条路分别看「东西像不像」「人像不像」、以及两者掺着用。

第一条,看东西像不像。 你把《泰坦尼克号》三个小时看完了,系统就认为你喜欢它, 于是去找同类型、同年代、同演员的片子推给你1

第二条,看人像不像。 系统不管片子本身什么样,只看行为: 如果甲和乙都喜欢 X、Y、Z,而乙还喜欢 Q,那么甲很可能也喜欢 Q2。 这条路有两种走法 —— 找相似的人(看他们还喜欢什么),或者找相似的东西 (哪些片子总被同一批人喜欢)3

第三条是两者混着用。 书只提了名字,没展开4

第二条路的软肋,书专门点了出来:它扩展性差。 用户一多,算力和内存的需求就暴涨 —— 因为要衡量「谁和谁像」,原则上得把每两个用户都比一遍5

判断(我们的,不是书里的):书用错了一个量级词。 先把词定死。 指数:每多一个用户,总量就翻一倍 —— 照这个长法,100 个用户就已经是天文数字, 任何系统都跑不到那一步。 而两两比对的次数是平方增长:用户数翻 10 倍,要比的次数翻 100 倍 (1 万个用户约 5000 万对,10 万个用户约 50 亿对)。 结论(扩展性差)是对的,量级的说法不对。 如果错,会错在: 如果作者指的是某种特定算法里确实存在指数级的步骤(书没说是哪一种), 那这句话在那个语境下成立。判据是:书通篇讲的是两两相似度这一类做法,那是平方级的。

3. 那张表为什么装不下

先看现象。 最直觉的做法是造一张大表:每一行一个用户,每一列一个物品, 格子里填这个人对这个东西的评分或互动次数6

问题是这张表几乎全是空的。 一个流媒体平台有几万部片子,而任何一个人只看过其中很少几部。 这种状态有个词:稀疏就是「绝大多数格子都没有值」—— 书的原话正是这张表非常稀疏、充满空格7

而推荐要的,恰恰就是把空格填出来。 你要知道的不是他已经看过什么, 是他还没看的那些里面,哪几个他会喜欢

到这一步,问题被重新表述成了一句很清楚的话: 给定一张几乎全空的大表,把空格补上。

4. 矩阵分解:把大表拆成两张瘦长的表

它解决什么问题。 上一节那张大表。

怎么做。 不去逐个填空格,而是把这张大表拆成两张小得多、而且填满了的表相乘8:

用户 × 物品 的大表 用户表 物品表
(几万列,几乎全空) ≈ (每人一串数) × (每个物品一串数)

图说:左边那张表大而空,右边两张表小而满。乘出来的结果会把左边的空格一并算出来。

  • 用户表:每一行是一个用户,那一行上的一串数刻画他的口味;
  • 物品表:每一列是一个物品,那一列上的一串数刻画它的性质9

那串数叫潜在因子 —— 「潜」的意思是:没有人规定第 3 个数代表「爱看科幻」、 第 7 个数代表「偏好长片」,它们是算出来的,谁也说不清每一个具体代表什么

为什么这么做有效。 两张小表乘起来,得到的是一张填满的大表; 原来有值的格子被近似还原,原来空着的格子被算出一个数 —— 那个数就是预测的评分10。 表小了,存得下也算得动;而且不再需要「把每两个用户比一遍」。

边界在哪。 书自己说:表再变大,这个办法也会吃力,所以才轮到神经网络出场11。 另外还有一条书没说的边界:一个全新的用户,他那一行还没被训出来 —— 系统对他一无所知(这条不在书里,来自通用知识,行内叫冷启动)。

5. 嵌入:让编号变成一串有意义的数

先看现象。 到这一步有一个很实际的障碍:用户 ID 是一串数字, 游戏名是一串文字,而模型只吃张量

最朴素的想法是给每个东西编个号 —— 这不行。 编号是随便定的: 3542 号游戏和 3543 号游戏之间没有任何关系,可模型会当成「差 1」; 和 8000 号比又成了「差 4458」。这套大小关系完全是凭空捏造的 (这一段推理是我们补的,书直接给了做法)。

做法叫嵌入:不给一个数,给一串数;而且这串数是训出来的。 书的定义是:把离散的对象转换成低维空间里一串稠密 (每一位都有值,不像上一节那张大表那样大片空着)的数12

为什么这么做有效 —— 关键在「训出来的」这三个字。 这些数是模型的参数,和第 01 章那些权重完全一样,会被同一个循环挪; 挪的方向由损失决定,而损失要的是「预测分数尽量接近真实评分」。

于是训着训着,口味接近的两个玩家,他们那两串数就会靠得很近; 同类型、同热度的两个游戏也一样13

这一招你在第 04 章见过一次 —— 那次被转换成一串数的是图像的一个小方块。 第 11 章还会再见一次 —— 那次是一个词。三次是同一件事换了对象: 把一个离散的东西,换成一串可以训练、可以比距离的数。

边界: 每个编号都要在表里占一行,所以用户或物品特别多的时候,这张表本身就很大; 而且没在训练数据里出现过的编号,表里根本没有它那一行

6. 两把互相打架的尺:找得全 vs 推得准

先看现象: 推荐系统给你十部电影,怎么算好?

书给了两把尺,它们的分母不一样14:

问的问题分子分母
Recall@k该推的漏了没有前 k 个里命中的相关物品数你全部的相关物品数
Precision@k推出去的准不准同上k(推荐列表的长度)

书给的例子一次讲清:你喜欢 A、B、C、D、E 五部片子;系统推了十部, 其中 A 和 B 是你喜欢的15

Recall@10 = 2 ÷ 5 = 40% ← 你喜欢的五部里,它找出来两部
Precision@10 = 2 ÷ 10 = 20% ← 它推的十部里,两部说到了点子上

图说:同一次推荐,两个数完全不同,因为分母不同。

为什么必须两个都看。 只看第一个,把 5000 个游戏全推给你,它就是 100%; 只看第二个,只推最有把握的一个,它可能是 100% 但你什么也发现不了。 k 越大,前者必然上升、后者通常下降 —— 这个此消彼长就是推荐这一行的基本张力16

7. 主走查:给 2 号玩家推十个游戏,然后诊断它为什么错

下面每个数都来自书。

第 ① 步,原始数据。 一个 Steam 数据集,没有表头,作者自己给四列起了名: 用户 ID、游戏名、这条记录是「购买」还是「游玩」、以及游玩时长17。 文件里还有一列全是空的,直接删掉。

第 ② 步,造评分。 模型要学的是 1 到 5 分,而数据里只有小时数。 书按四条线换算:不到 10 小时记 1 分、10–20 记 2 分、20–40 记 3 分、40–60 记 4 分、 超过 60 记 5 分18作者自己声明这几条线是随手定的,目的只是让每档都有足够的数据。

第 ③ 步 —— 这一步印出来的数,就是失败的第一个病因。 换算完之后各档的条数是19:

分数12345
条数1745908227607827488357

19.9 万条记录里,17.46 万条是 1 分 —— 87.7%。 书对这个数的评语是「毫不意外」, 理由是多数玩家只对某几款游戏有明确偏好20评语没错,但它只解释了成因,没有追问后果。

第 ④ 步,编号连续化。 用户 ID 和游戏名各自换成从 0 开始的整数, 得到 12393 个用户 × 5155 个游戏21

第 ⑤ 步,搭模型。 两张嵌入表,一张给用户、一张给游戏,每个编号配 32 个数。 一次预测取出一个用户的 32 个数和一个游戏的 32 个数,拼成 64 个数, 送进一个把 64 变成 1 的线性层,吐出预测分数22:

用户 2 ─→ 取出他的 32 个数 ─┐
├─→ 拼成 64 个数 ─→ 线性层 ─→ 一个分数
某个游戏 ─→ 取出它的 32 个数 ┘

第 ⑥ 步,训练。 损失用均方误差(答案是 1–5 的分数,所以这是第 01 章那种回归), 优化器 Adam、学习率 0.001、每批 128 条、训 8 轮。 损失从 0.6219 降到 0.505723

第 ⑦ 步,推荐。 固定用户 2,让模型给全部 5155 个游戏各打一分,取最高的十个24

第 ⑧ 步,看结果。 书打印了两份清单25:

他打了高分的游戏模型推给他的前十个
Ultra Street Fighter IV(格斗)Football Manager 2012 / 2013 / 2015 / 2014 / 2016(四部足球经理)
FINAL FANTASY XIII(日式角色扮演)Breezeblox、Movie Studio 13 Platinum
The Elder Scrolls V Skyrim(开放世界角色扮演)DARK SOULS II、Counter-Strike Global Offensive、Fallout 4

书就在这里结束了这一章,对这份结果一个字没评。

第 ⑨ 步,评估数字也印出来了26:

kRecall@kPrecision@k
50.02240.0058
100.11000.0149
200.17940.0130
500.40190.0114

书的读法是:找得全这一项随 k 明显上升,到 50 个时能找回四成,这是好消息; 而推得准那一项一直很低,说明模型能捞出相关的东西,但排不到前面去27

诊断:两个病因,一个在数据里,一个在结构里

判断(我们的,不是书里的)——第一个病因在第 ③ 步就看得见。 87.7% 的记录被压成了同一个值 1。而模型用的是均方误差: 只要它对所有输入都输出接近 1 的数,损失就已经很低了。 这正好对得上那条损失曲线 —— 8 轮只从 0.6219 挪到 0.5057,几乎没动。 真正的信号只藏在剩下 12.3% 的记录里,而它们被 87.7% 的「1」淹没了。 换算的四条线是作者自己承认随手定的,换一组线,整份结果就会变 —— 这本身就说明这份评分不是从数据里长出来的,是被人为拍出来的。 如果错,会错在: 如果那 12.3% 的高分记录恰好集中在少数几个活跃用户身上、 而评估又只在这些用户上做,那么模型仍可能学到东西,这条诊断就说重了。 判据是那条几乎走平的损失曲线。

判断(我们的,不是书里的)——第二个病因是结构性的,而且它决定了这个模型 根本不可能做出个性化推荐。

看第 ⑤ 步那个网络:两串数拼接之后直接进一个线性层,中间没有任何激活函数28。 线性层做的事就是「每个输入乘一个权重再全加起来,最后加个偏置」。 把 64 个数拆成前 32 个(来自用户)和后 32 个(来自游戏),这一步等于:

分数 = (前 32 个权重 · 用户那串数) + (后 32 个权重 · 游戏那串数) + 偏置
↑ 只跟这个人有关,换游戏它不变 ↑ 只跟这个游戏有关,换人它不变

对固定的一个用户,第一项是一个常数。 排序只由第二项决定 —— 而第二项跟这个人是谁完全无关。

结论:这个模型给每一个用户的前十名推荐,是同一份清单。 用户 2 拿到四部足球经理,不是因为模型误判了他的口味, 而是因为它给谁都推这十个。那份清单其实就是一张「全站最受宠的十个游戏」的榜。

这也解释了上面那张评估表:找得全那一项随 k 上升属于纯粹的机械效应 (推得越多,撞上的越多),而推得准那一项低到 0.58% 也就顺理成章了。

这和书自己讲的矩阵分解(本章第 4 节)不是一回事。 矩阵分解是把两串数对应位置乘起来再求和 (这个运算叫点积),乘法才让「这个人配这个游戏」这件事有可能被表达出来; 而拼接加一个线性层是加法,加法拆得开、乘法拆不开。 书讲了理论 A,实现了做法 B,从未说明两者不同。

如果错,会错在: 如果那个线性层之后还有一个非线性激活, 或者两串数是相乘/做点积而不是拼接,上面的拆解就不成立。 判据在书的代码里:torch.cat 之后紧接着 self.fc(x)return 了,中间什么都没有。

这两条合起来,才是这一章真正该带走的东西: 一次推荐失败,病因可以在数据准备那一步就看得见,也可以藏在三行模型代码里。

8. 书里的立场与证据

书里给了证据的:

  • 三类推荐系统的定义与协同过滤的两种走法 —— 标准教科书内容,表述准确;
  • 矩阵分解能填空格 —— 是数学事实,书给了示意图;
  • 两把尺的定义和那个 A/B 的算例 —— 算得清楚,可以自己复核;
  • 两把尺此消彼长 —— 书用自己跑出来的四组数坐实了。

作者的经验判断(书里没给证据):

  • 评分的四条分界线(10/20/40/60 小时) —— 作者自己声明是随手定的, 这一点值得称赞:他没有把一个拍出来的数写成合理的数;
  • 「1 分占绝大多数毫不意外」 —— 是解释,不是证据;
  • 32 维、8 轮、批 128 —— 都没解释为什么。

书里没说的(而且是这一章最要紧的): 它没有把「理论讲的矩阵分解」和「代码写的拼接 + 线性层」对照一句, 也没有诊断那份明显错误的推荐结果。这两处我们在第 7 节补上了。

9. 边界与局限

这本书对的地方先说清: 这一章把「推荐 = 把一张几乎全空的表填满」这个问题定义讲得很干净, 嵌入那一节的解释也准确;两把评估尺的算例更是难得的清楚。 而且它诚实地把失败的结果原样印了出来 —— 很多教材会悄悄换一个好看的用户。

但有三处要当心:

  1. 「指数级增长」用词错误 —— 见第 2 节末的判断块;
  2. 理论与代码不是一回事 —— 见第 7 节末的判断块;
  3. 推荐结果是错的,书没评 —— 同上。

这一章没覆盖的:

  • 冷启动(全新用户或全新物品没有嵌入)一个字没提,而这是推荐系统最常见的实际难题;
  • 基于内容那条路只讲了概念,没有代码 —— 全章只实现了协同过滤那一条;
  • 混合模型只提了名字;
  • 没有和基线比。 第 03 章立下的规矩在这一章没执行 —— 如果和「按全站热度排序」 这样一个不看用户的基线比一比,第 7 节那个结构问题当场就会暴露;
  • 公式是图片。 Recall@k 与 Precision@k 的算式在原文里是空行, 第 6 节那两行是我们按书的文字描述写的;
  • 书自己提了一句改进方向(把物品本身的特征也喂进去),但明说超出本书范围29

10. 可带走的

主走查一行写完: 19.9 万条 Steam 记录 → 按 10/20/40/60 小时换成 1–5 分 (87.7% 落在 1 分)→ 12393 用户 × 5155 游戏各配 32 个数 → 拼成 64 个数过一个线性层 → MSE 训 8 轮,损失 0.6219 → 0.5057 → 给用户 2 的 5155 个游戏各打一分取前十 → 推出四部足球经理,而他喜欢格斗和角色扮演。

  1. 推荐三条路:看东西像不像、看人像不像、两者混用;第二条的软肋是用户一多就撑不住;
  2. 那张「谁玩过什么」的大表几乎全是空的,而推荐要的正是把空格填出来;
  3. 矩阵分解:把大而空的表拆成两张小而满的表相乘,空格自动被算出来;
  4. 那串数叫潜在因子 —— 没人规定每一位代表什么,它们是训出来的;
  5. 嵌入 = 给每个离散编号配一串可训练的数,喜好接近的人、性质接近的物,数串也接近;
  6. 编号不能直接当数用 —— 3542 和 3543 之间没有任何真实关系;
  7. Recall@k 的分母是「你全部的相关物品」,Precision@k 的分母是 k;分母不同,别混;
  8. k 越大,找得全上升、推得准下降,这是推荐这一行的基本张力;
  9. 数据准备里那张「各档各有多少条」的表,可以在训练开始之前就决定成败 —— 87.7% 压成同一个值;
  10. 拼接 + 一个线性层 = 加法,它表达不了「这个人配这个东西」 —— 对固定用户,排序只由物品那一项决定,于是人人拿到同一份清单;
  11. 想表达交互,两串数得对应位置相乘再求和(点积),而不是拼起来;
  12. 这一章没和基线比 —— 如果比了,第 10 条当场就会暴露。

11. 原文地图

主题原书章原文位置
三类推荐系统Chapter 5text/07-ch05-chapter-5.txt:25(搜「roughly divided into three categories」) · text/07-ch05-chapter-5.txt:49(搜「With content-based filtering」) · text/07-ch05-chapter-5.txt:52(搜「preferences of similar users」)
协同过滤两种走法与扩展性Chapter 5text/07-ch05-chapter-5.txt:56(搜「User-based」) · text/07-ch05-chapter-5.txt:64(搜「scale poorly」)
勘误 「指数级增长」Chapter 5text/07-ch05-chapter-5.txt:69(搜「increase exponentially」)
交互矩阵与稀疏Chapter 5text/07-ch05-chapter-5.txt:75(搜「rows correspond to the users」) · text/07-ch05-chapter-5.txt:89(搜「very sparsely populated」)
矩阵分解与潜在因子Chapter 5text/07-ch05-chapter-5.txt:90(搜「two smaller, dense」) · text/07-ch05-chapter-5.txt:94(搜「latent factors」) · text/07-ch05-chapter-5.txt:98(搜「approximate the original」)
数据列与评分换算Chapter 5text/07-ch05-chapter-5.txt:136(搜「the title of the game」) · text/07-ch05-chapter-5.txt:172(搜「divides the playing time」)
评分分布与作者的评语Chapter 5text/07-ch05-chapter-5.txt:189(搜「174590」) · text/07-ch05-chapter-5.txt:196(搜「hardly surprising」)
编号连续化与规模Chapter 5text/07-ch05-chapter-5.txt:200(搜「not consecutive numbers」) · text/07-ch05-chapter-5.txt:340(搜「Number of users: 12393」)
嵌入的定义与效果Chapter 5text/07-ch05-chapter-5.txt:283(搜「Embedding is a technique」) · text/07-ch05-chapter-5.txt:306(搜「similar preferences receive embedding」)
模型结构(拼接 + 线性层)Chapter 5text/07-ch05-chapter-5.txt:301(搜「passed through a linear layer」) · text/07-ch05-chapter-5.txt:325(搜「torch.cat」)
损失、优化器、训练结果Chapter 5text/07-ch05-chapter-5.txt:355(搜「mean-squared error loss」) · text/07-ch05-chapter-5.txt:381(搜「Epoch 1/8 - Loss: 0.6219」)
两把尺的定义与算例Chapter 5text/07-ch05-chapter-5.txt:421(搜「percentage of relevant items」) · text/07-ch05-chapter-5.txt:426(搜「Recall@k is 2/5」) · text/07-ch05-chapter-5.txt:517(搜「Precision@k is 2/10」)
四组评估数字与解读Chapter 5text/07-ch05-chapter-5.txt:635(搜「Recall@5: 0.0224」) · text/07-ch05-chapter-5.txt:685(搜「only about 0.58%」) · text/07-ch05-chapter-5.txt:687(搜「clear trade-off」)
用户 2 的清单与推荐结果Chapter 5text/07-ch05-chapter-5.txt:735(搜「highly rated games」) · text/07-ch05-chapter-5.txt:741(搜「Football Manager 2012」)
作者提的改进方向Chapter 5text/07-ch05-chapter-5.txt:691(搜「outside the scope of this book」)

Footnotes

  1. 出处:「Chapter 5 Recommendation Systems」第 49-51 段(text/07-ch05-chapter-5.txt:49,搜「With content-based filtering」)。原文:系统分析用户的行为,基于她看过的影片之间的相似性给出推荐 —— 比如她把三小时的《泰坦尼克号》整个看完,系统就理解为她喜欢它,然后去找同类型、同年代或同演员的片子。

  2. 出处:「Chapter 5」第 52-53 段(text/07-ch05-chapter-5.txt:52,搜「preferences of similar users」)。原文的核心想法:如果甲和乙都喜欢电影 X、Y、Z,而乙还喜欢 Q,那么甲也喜欢 Q 的概率很高;这类算法忽略影片本身的特征,只关注用户行为。

  3. 出处:「Chapter 5」第 55-62 段(text/07-ch05-chapter-5.txt:56,搜「User-based」)。原文两种做法:基于用户 —— 找出偏好相似的用户,把他们喜欢而当前用户还没看过的片子推过去;基于物品 —— 找出被相似用户群喜欢的影片。

  4. 出处:「Chapter 5」第 25-28 段(text/07-ch05-chapter-5.txt:25,搜「roughly divided into three categories」)。原文三类:基于内容的过滤、协同过滤、混合模型。混合模型全章只出现了这一次。

  5. 出处:「Chapter 5」第 64-69 段(text/07-ch05-chapter-5.txt:64,搜「scale poorly」)。原文:协同过滤的常见算法有一个问题 —— 扩展性差;以流媒体为例,用户数一旦爆炸,算法的计算量和内存需求会突然增长。「呈指数增长」这个措辞见第 69 段(text/07-ch05-chapter-5.txt:69,搜「increase exponentially」),我们认为用词有误,见正文的判断块。

  6. 出处:「Chapter 5」第 75-77 段(text/07-ch05-chapter-5.txt:75,搜「rows correspond to the users」)。原文:行对应用户、列对应影片,矩阵里的值代表用户与这些影片的评分或互动(比如点击和观看时长)。

  7. 出处:「Chapter 5」第 88-89 段(text/07-ch05-chapter-5.txt:89,搜「very sparsely populated」)。原文:由于大多数用户只看了平台大量片库中的很小一部分,这个矩阵非常稀疏,有很多空字段。

  8. 出处:「Chapter 5」第 90-91 段(text/07-ch05-chapter-5.txt:90,搜「two smaller, dense」)。原文:矩阵分解把这个又大又稀疏的矩阵拆成两个更小、更稠密的矩阵 —— 一个用户特征矩阵和一个物品特征矩阵。

  9. 出处:「Chapter 5」第 92-96 段(text/07-ch05-chapter-5.txt:94,搜「latent factors」)。原文:用户特征矩阵里每一行代表一个用户,包含一串被称为潜在因子的值来描述这个用户的偏好;物品特征矩阵里每一列代表平台提供的影片。

  10. 出处:「Chapter 5」第 98-99 段(text/07-ch05-chapter-5.txt:98,搜「approximate the original」)。原文:把这两个较小的矩阵相乘,算法就能近似出原矩阵、预测出缺失的值,并把这些缺失值当作推荐呈现给用户。

  11. 出处:「Chapter 5」第 100-102 段(text/07-ch05-chapter-5.txt:100,搜「problems arise when this matrix becomes larger」)。原文:矩阵更大时问题就出现了,这时 PyTorch 来帮忙 —— 我们可以训练一个神经网络来学习用户行为里的复杂模式。

  12. 出处:「Chapter 5」第 282-287 段(text/07-ch05-chapter-5.txt:283,搜「Embedding is a technique」)。原文:嵌入是一种把离散对象(这里是用户和物品的属性)转换成低维空间中稠密向量的技术,这样一来内容上(语义上)的关系就能用这些向量之间的接近程度来表示;原文还说嵌入这个主题极其重要,所以要在这里详细讨论。

  13. 出处:「Chapter 5」第 305-313 段(text/07-ch05-chapter-5.txt:306,搜「similar preferences receive embedding」)。原文:训练的结果是,偏好相似的两个玩家会得到在向量空间里彼此接近的嵌入向量,物品也一样 —— 类型或热度相近的游戏,嵌入向量也彼此接近;这些向量稠密而紧凑,而且即使两个玩家只有很少的共同游戏,模型仍能在嵌入空间里比较他们的偏好、找出共同点。

  14. 出处:「Chapter 5」第 416-421 段(text/07-ch05-chapter-5.txt:421,搜「percentage of relevant items」)与第 511-513 段(text/07-ch05-chapter-5.txt:512,搜「how many of these are really good suggestions」)。原文:Recall@k 算的是「你的相关物品里,有多大比例出现在前 k 个推荐里」;Precision@k 算的是「前 k 个推荐里有多大比例是相关的」。两个公式在原书里都是图片,提取出来是空行。

  15. 出处:「Chapter 5」第 424-427 段(text/07-ch05-chapter-5.txt:426,搜「Recall@k is 2/5」)与第 515-517 段(text/07-ch05-chapter-5.txt:517,搜「Precision@k is 2/10」)。原文用的就是同一个例子:喜欢 A、B、C、D、E,系统推了 A、B、F、G、H、I、J、K、L、M。

  16. 出处:「Chapter 5」第 687-689 段(text/07-ch05-chapter-5.txt:687,搜「clear trade-off」)。原文:找回率与精确率之间存在明显的取舍 —— 系统擅长「记住」相关物品(找回率高),但不擅长把它们放到列表前面(精确率低)。

  17. 出处:「Chapter 5」第 133-138 段(text/07-ch05-chapter-5.txt:136,搜「the title of the game」)。原文:文件不含列名,所以要自己定义;数据集包含用户 ID、游戏名、是否购买、以及行为一列,最后这一列代表玩家的游戏使用时长(小时)。另有一列没有任何值,直接删掉(text/07-ch05-chapter-5.txt:149,搜「doesn’t contain any values」)。

  18. 出处:「Chapter 5」第 169-178 段(text/07-ch05-chapter-5.txt:172,搜「divides the playing time」)。原文:需要一个给出 1 到 5 的算法,1 代表兴趣低(使用时长短)、5 代表兴趣极高;为此新建一个 rating 列,把游戏时长分成 5 组,而这里引入的界限是随意设定的,只为让五组里都有足够的数据。代码里的四条线是 10、20、40、60 小时。

  19. 出处:「Chapter 5」第 186-194 段(text/07-ch05-chapter-5.txt:189,搜「174590」)。原文输出的五个数:1 → 174590、5 → 8357、2 → 8227、3 → 6078、4 → 2748。

  20. 出处:「Chapter 5」第 196-199 段(text/07-ch05-chapter-5.txt:196,搜「hardly surprising」)。原文:绝大多数落在第 1 组,这毫不意外 —— 多数玩家对某个类型里的特定游戏有明确偏好,所以提供的大部分内容与他们无关;但其他组也有足够的元素供模型学习行为。

  21. 出处:「Chapter 5」第 200-208 段(text/07-ch05-chapter-5.txt:200,搜「not consecutive numbers」)与第 331-341 段(text/07-ch05-chapter-5.txt:340,搜「Number of users: 12393」)。原文:当前的用户 ID 不是连续的数字,游戏名只是字符串,所以要新建两列从 0 开始的整数编号;数据里有 12393 个玩家和 5155 个游戏。

  22. 出处:「Chapter 5」第 300-301 段(text/07-ch05-chapter-5.txt:301,搜「passed through a linear layer」)与第 315-327 段的模型代码(text/07-ch05-chapter-5.txt:325,搜「torch.cat」)。原文:两个嵌入的张量被连接起来,这一层再经过一个线性层,然后输出模型结果。代码里两个嵌入都是 32 维,线性层是 nn.Linear(64, 1)

  23. 出处:「Chapter 5」第 354-359 段(text/07-ch05-chapter-5.txt:355,搜「mean-squared error loss」)与第 381-383 段(text/07-ch05-chapter-5.txt:381,搜「Epoch 1/8 - Loss: 0.6219」)。超参数见第 246-253 段(text/07-ch05-chapter-5.txt:251,搜「EPOCHS = 8」)。

  24. 出处:「Chapter 5」第 694-708 段(text/07-ch05-chapter-5.txt:700,搜「torch.full」)。原文的函数:把同一个用户 ID 重复 5155 次、配上 0 到 5154 的全部物品编号,一次性算出所有分数,再取最高的 k 个。

  25. 出处:「Chapter 5」第 712-752 段(text/07-ch05-chapter-5.txt:735,搜「highly rated games」)与第 740-751 段(text/07-ch05-chapter-5.txt:741,搜「Football Manager 2012」)。原文原样打印了两份清单,之后直接进入本章小结。

  26. 出处:「Chapter 5」第 632-648 段(text/07-ch05-chapter-5.txt:635,搜「Recall@5: 0.0224」)。评估时把评分 ≥ 4 的物品算作「相关」(text/07-ch05-chapter-5.txt:603,搜「rating >= 4 as relevant」)。

  27. 出处:「Chapter 5」第 681-689 段(text/07-ch05-chapter-5.txt:685,搜「only about 0.58%」)。原文:Recall@50 达到 0.4,意味着系统平均能在前 50 个推荐里找到该用户四成的相关物品;而 Precision@5 只有约 0.58%,说明前五个推荐里相关的比例极小。

  28. 出处:「Chapter 5」第 322-327 段(text/07-ch05-chapter-5.txt:322,搜「def forward(self, user_id, item_id)」)。原文的 forward 一共四行:取出用户嵌入、取出物品嵌入、torch.cat 拼接、过 self.fc 然后返回 —— 中间没有任何激活函数。

  29. 出处:「Chapter 5」第 689-692 段(text/07-ch05-chapter-5.txt:691,搜「outside the scope of this book」)。原文:要让系统对终端用户更有用,应该聚焦于提高精确率,办法可以是微调模型或者引入额外特征(比如物品的内容特征);但这超出本书范围,只作为进一步深入的建议提出。