跳到主要内容

把一张图压成两个数再还原 — 然后在这个平面上漫游

这一章讲三件事: 一个「输入和输出长得一模一样」的模型有什么用; 为什么这样的模型练完之后能拿来新图; 以及为什么要在中间那一步故意掺进随机。 它在全书链条里的位置: 上一章造的是文字,而且靠的是「猜下一个」。 图像没有「下一个像素」这种顺序可用,所以造图得换一条完全不同的路——这一章是第一条。

1. 先看一个看起来毫无意义的模型

书画的这个模型,第一眼会让人觉得是在开玩笑1:

输入:一张 28×28 的灰度图(784 个数)

……

输出:一张 28×28 的灰度图(784 个数)

误差怎么算?**拿输出和输入比**,用第 03 章那把均方误差的尺子。

也就是说:**训得好的话,输出和输入完全一样。**

图说:一个费尽力气把图原样吐回来的模型 —— 它有什么用?

谜底在中间那一截

这个模型是个沙漏形:两头宽,中间极窄2

784 个数 ─┐
├─→ **2 个数** ─┤
784 个数 ─┘ ├─→ 784 个数
前半截 后半截

中间那 2 个数,是这张图被压缩之后的全部内容。
后半截必须**只靠这 2 个数**,把整张图重新画出来 —— **没有任何别的信息可用**。

图说:所以这个模型真正在做的事不是「复制」,是**「压到极致再还原」**。
能还原得回来,就说明那 2 个数里装下了这张图的要点。

这套结构叫自编码器。中间那串数叫本征向量,它们所在的那个空间叫本征空间2前半截叫编码器,后半截叫解码器——这两个名字第 14 章已经见过一次,是同一对名字。

⚠ 名字提醒(两条,第二条更要紧): ① 中文版正文里用的是「本征空间」,书的要点清单里又写作「潜在空间」, 这两个是同一个东西1补充(不在书里,来自通用知识): 中文材料里更常见的译名是「潜在空间」,而它的原名 latent space 你出门一定会撞见。

「本征向量」这个中文词已经被别的东西占了。 在中文数学教材里, 本征向量指的是线性代数里的 eigenvector(那是「矩阵作用上去只把它拉长或缩短、 方向不变」的那种向量),和这一章说的完全是两回事。 这本书的「本征」译的是 latent(潜在),不是 eigen。 所以你合上这份文档去搜「本征向量」,搜到的会是另一门课; 要搜就搜「潜在空间」或 latent space (补充(不在书里,来自通用知识)本拆解仍按书里的叫法用「本征向量 / 本征空间」, 只是把这个坑标出来。)

它凭什么压得下去

书用信息量的角度解释了这件事,而且解释得很干净3:

假设一张图带 N 位信息,那 2 个数只装得下 n 位。
**如果 N > n,理论上还原不回来。**

但图像的像素**不是随机的**。真要是随机的,那看起来就只是一片雪花噪点。
它们有模式:颜色是连续的、要符合它们画的那个真实物体的样子。

→ **所以 N 远比「像素数 × 每像素位数」小得多。**

图说:自编码器的活,就是把这些模式学出来。**这也是它能成立的全部原因。**

这一段值得对照第 01 章那句「学的是换一种说法」来读: 它压掉的不是内容,是内容里的冗余。

2. 转折:把前半截扔掉

这一节是「压缩」变成「生成」的那一步,只有一句话。

训完之后,把编码器扔掉,只留解码器4:

你手上现在有一台机器:**给它任意两个数,它吐一张图。**

而且吐出来的图,**符合训练数据的那种模式和风格** ——
你拿 Fashion-MNIST(一堆衣服鞋子的图)训的,它就吐衣服鞋子。

图说:**这就是生成式模型该有的能力。**
——「压缩」和「生成」在这里被证明是同一件事的两面。

而且那两个数可能是有含义的

书举了一个很好记的例子5:

拿一大堆人脸训一个自编码器。

中间那些数里,**可能有一个专管「笑的程度」。**

于是你可以: ① 把一张脸送进编码器,拿到它那几个数
② **只改那一个数**,别的不动
③ 送进解码器

→ 出来的是同一张脸,**只是笑得更开或更收**。

图说:书自己用的是「可能」——**这是一种可能性,不是实测结果。**

3. 经典做法不够好,病在哪

书给了一个很利落的判决6:

经典自编码器压出来的那个本征空间,结构并不是特别有用,压缩率也不是很高所以它在 2013 年之后就不那么流行了。

病在哪?书是通过讲解法来说明的,我们把它摆正:

经典做法:一张图 → **本征空间里的一个点**

于是训练只保证了一件事:**这些点各自解码回去要像样。**
**点与点之间那些没被任何图占过的位置,谁也没管过。**

→ 你在两个点中间取一个新位置去解码,**很可能吐出一堆糊的东西**。

图说:而「生成新图」要的恰恰是**去那些没被占过的位置取点**。
**这就是它当生成器不好用的原因。**

2013 年 12 月和 2014 年 1 月,两组人几乎同时给出了同一个解法,它叫 VAE6

4. 承重节:不输出一个点,输出一片区域

这一节是本章的地基。VAE 相对经典做法的改动只有一处,但改得很狠。

改动本身

经典做法: 编码器吐出 **一个点** → 解码这个点

VAE: 编码器吐出 **一个均值 + 一个方差** (也就是一片模糊的区域)
→ **从这片区域里随机抽一个点** → 解码这个抽出来的点

图说:同一张 T 恤,这次编码出来的点和上次不会完全一样 ——
**每次都在同一片区域里晃一下。**

「均值」和「方差」是描述一片区域的两个数:均值说这片区域的中心在哪, 方差说它摊得有多开(标准差就是方差开平方,说的是同一件事)。

为什么这一改就治好了病

这是全章最要紧的一步推理,书是这么说的7:

这种随机性,是 VAE 提升稳健性、并确保本征空间能编码图像各部位表示的关键从本征空间采样的每个点,经解码器解码之后,都应该是一个正确的图像输出。

我们把这一步的因果补全:

① 同一张 T 恤每次被编码到区域里的**不同位置**
② 而这些位置**全都被要求解码回同一张 T 恤**
③ → 于是这一整片区域,**都被逼着解码成像样的东西**
④ 每张图都占一片区域,片片相接
⑤ → **空洞被填上了,你在任意位置取点都能得到像样的图**

图说:第 ③ 步是关键。**随机不是噪声,是在强迫模型对一整片区域负责。**

书还额外补了一句损失里的手段:有一项专门促使不同输入图像 围绕本征空间的中心更均匀地分布,这让解码器更容易在图像之间做插值8

挡在路上的麻烦:随机的东西求不了导

训练靠的是第 02 章那一套:从损失往回求导,一路推到每个权重。 可「随机抽一个点」这个动作没法求导——它不是一个确定的计算。

做法是把随机挪到旁路9:

z = 均值 + exp(对数方差 × 0.5) × ε
└─可求导─┘ └────可求导────┘ └ 从标准正态分布里抽的一个数
**它是外面丢进来的,不参与求导**

乘 0.5 那一下是在把方差换成标准差(标准差 = 方差开平方)。

图说:随机性被隔离在 ε 这一个外来的数上,
**梯度照样能穿过「均值」和「对数方差」传回编码器。**
这个手法书管它叫**重参数化**。

⚠ 为什么编码器吐的是「对数方差」而不是方差?书专门解释了10: 方差按定义必须是非负的,而没有简单的办法保证一个神经层的输出一定非负。 对数之后就没这个限制了——它正负都行,用的时候做一次指数运算变回来即可。 这是一个很典型的工程手法:把有约束的量,换成一个没约束的量去学。

这一步要自己写一个层

TensorFlow.js 里没有现成的层干这件事,书自己写了一个11

自定义层要交两样东西:输出形状怎么算,和具体算什么第 14 章那个「取最后一步」的层是同一类东西,只是这个更复杂一点。

5. 两笔账要一起算,而且量级差得远

这一节回答:损失怎么写。

VAE 的损失是两项之和12:

这一项它管什么怎么算
重建损失还原得像不像输入和解码输出之间的均方误差(第 03 章那把尺子)
KL 散度项那片区域摆得散不散由均值和对数方差算出来,具体公式书明说不展开

书对第二项给的直觉解释是:它促使不同输入图像围绕本征空间的中心更均匀地分布, 从而让解码器更容易在图像之间插值。所以可以把它看成加在第一项之上的一个正则化项8

「正则化项」这个说法第 11 章讲透了:在损失上再加一项,罚的不是「答得对不对」, 而是别的什么东西。 那一章罚的是权重太大,这一章罚的是区域摆得太散。

配平:同一个手法的第二次出现

重建损失那一项,书给它乘上了 **originalDim**(也就是 784,一张图的像素数)。

为什么?**因为均方误差是「每个像素的平均误差」,而 KL 那一项是整个向量的总量。**
不乘,重建那一路的信号会被压得几乎不起作用。

图说:**这和第 07 章那个「形状标签乘 224」是同一个手法** ——
两路误差量级差太远时,给弱的那一路乘一个数,把它抬到同一量级。
**书自己在这里点破了这条线**[^12]。

而这种损失,fit() 装不下

这是这一章唯一一处工程上的硬约束,书说得很明确13:

`fit()` 只在一种情况下适用:**每个输出的损失,只依赖它自己那个输出。**

可 KL 那一项同时用到了模型四个输出里的两个(均值和对数方差)。
→ **`fit()` 用不了。**

改成手写训练循环:
对每一批图:
用优化器的 minimize(),传进去一个「算出这批的损失」的函数
它负责求梯度、按 adam 的规则算出该挪多少、往梯度反方向更新权重

图说:这是全书第一次**不用 `fit()`** ——
前十五章那条「六步流水线」在这里第一次不够用了。
代价是连进度条都得自己打印。

6. 走完全程之后能看见什么:在平面上漫游

这是这一章的回报,而且它是这本书里最好看的一个演示。

训完之后,拿解码器在一个 20×20 的网格上取点,每个点解出一张图,拼成一张大图14:

本征空间只有 2 维,所以它就是一个平面。
两个维度各在 [−4, 4] 之间均匀取 20 个值 → 400 个点 → 400 张图

┌─────────────────────────┐
│ 靴 靴 靴 鞋 鞋 鞋 鞋 …… │ ← 书说:这一带横着走是「从靴子渐变到鞋子」
│ ………………………………………… │
│ 衫 衫 恤 恤 恤 裤 裤 …… │ ← 书说:右下这一带横着走是「从 T 恤渐变到裤子」
└─────────────────────────┘

图说:**关键词是「完全连续」** —— 沿着一条路走过去,
一种衣服会一点一点变成另一种,中间没有断裂、没有糊掉的格子[^14]。
**这正是第 4 节那个改动买来的东西。**

⚠ 书自己的措辞很克制:某些区域的某个方向「看起来代表」某种渐变。 这是对图的观察,不是模型里标出来的维度。

7. 主走查:一件 T 恤走完全程

这一章每个承重机制在这条走查上各占一步。数字全部来自书里,标注除外。

发生了什么具体的数 / 状态
1拿一件 T 恤的图Fashion-MNIST,28×28 灰度 = 784 个数
2扁平化喂进编码器这一步舍弃了空间信息;书说这么做是因为图太小太简单,一般的图像 VAE 会用卷积层
3编码器:一个隐藏层,然后分两个岔一路输出均值(2 个数),一路输出对数方差(2 个数)
4为什么是对数方差不是方差方差必须非负,而神经层的输出保证不了;对数之后正负都行
5抽一个点:从标准正态里抽一个 εz = 均值 + exp(对数方差 × 0.5) × ε → 得到 2 个数
6这一步凭什么还能训随机性被隔离在 ε 上,梯度照样穿过均值和对数方差回去(重参数化)
7这一步要自己写一个层交两样东西:输出形状怎么算、具体算什么
8解码器:一个隐藏层 + 一个 784 单元的输出层输出层用 sigmoid,保证每个像素落在 0~1 之间
9算第一笔账:还原得像不像输入和输出之间的均方误差,再乘上 784
10算第二笔账:区域摆得散不散KL 散度项,由均值和对数方差算出来
11为什么要乘 784两笔账量级差得远;和第 07 章那个乘 224 是同一个手法
12训不了:fit() 装不下KL 那一项用到四个输出里的两个,必须手写训练循环
13手写循环每批调一次优化器的 minimize(),优化器用 adam;进度条都得自己打印
14训练开销有显卡约 5 分钟,没显卡一小时内能完成;默认 5 轮
15训完之后把编码器扔掉只留解码器——它现在是一台「给两个数吐一张图」的机器
16在平面上取 20×20 个点两个维度各在 [−4, 4] 之间均匀取值
17每个点解一张图,拼起来T 恤连续变成连帽衫、变成裤子、变成靴子、变成鞋子,中间不断裂
18收账拿到了「造图」;代价是告别了 fit(),而且损失里多了一项谁也没法直观核对的账

8. 作者的判断与证据

书里给了证据的:

  • 本征空间是连续的。 书印了那张 20×20 的实测网格图,并逐块描述了渐变方向14
  • 重参数化那个式子。 给了数学写法和对应的一行代码,可以对着看9
  • fit() 用不了。 书给了确切的原因:fit() 要求每个输出的损失不依赖别的输出13
  • 训练开销。 有显卡 5 分钟、没显卡一小时内15
  • Fashion-MNIST 比 MNIST 难。 给了两个顶尖成绩:96.35% 对 99.75%16

属于作者判断、书里没给证据的:

  • 「经典自编码器的本征空间结构不特别有用、压缩率也不高」。 书直接下了这个判断,没有给对照实验6
  • 「微笑维度」那个例子。 书用的是「可能和……相关联」,是设想不是结果5
  • KL 项的直觉解释(促使分布更均匀、便于插值)。书自己说「可以像下面这样在直觉层面理解它」, 并把数学推给了一篇外部博客8
  • 网格图上「这一带是从靴子到鞋子的渐变」。 书用的是「看起来代表」「似乎表示」—— 这是看图得出的观察14

判断(我们的,不是书里的): 这一章真正的贡献是把「压缩」和「生成」证明成了同一件事: 能把 784 个数无损地压进 2 个数再还原,和能从任意 2 个数造出一张像样的图, 是同一个能力的两面。 而 VAE 的那一处改动之所以关键,是因为 它把训练目标从「这些点要还原得对」改成了「这一整片区域都要还原得对」—— 生成需要的正是后者。这两句话把整章串成了一条线,而书没有明写第二句。 如果错,会错在: 如果你只想要压缩、根本不打算造新图,那经典做法完全够用, VAE 多出来的那两笔麻烦(随机、KL、手写循环)全是白付的。

9. 边界与局限

  • 本征空间只有 2 维,是为了能画出来。 真实用途上 2 维远远不够; 书没有讨论该取多少维,也没有做对照。
  • 图被扁平化了,空间信息被丢掉。 书自己承认这么做「有些简单,或者说特殊」, 并说一般的图像 VAE 会用卷积层和池化层17
  • KL 散度项的数学书明说不讲。 推给了一篇外部博客8
  • 没有量化「VAE 比经典自编码器好多少」。 只有一句「经典的不特别有用」的判断, 没有并排的实验。
  • 生成质量怎么评,整章没有尺子。 这是第 15 章那个缺口的延续—— 两笔损失都不能直接告诉你「生成的图好不好看」。
  • 只保存了解码器。 书说编码器没存,因为浏览器演示里用不上; 所以「改一改某个维度再解码」那个玩法,这个示例其实做不了。
  • 训练循环是手写的,意味着第 03 章那套回调、第 10 章那套曲线全都用不了。 书只能在控制台里打点。

10. 可带走的

  1. 自编码器是个沙漏:两头一样宽,中间极窄。 训练目标是把输入原样还原;
  2. 有用的地方在中间那一截: 784 个像素被挤成 2 个数, 解码器只靠这 2 个数把整张图画回来;
  3. 它压得下去是因为像素不随机——颜色连续、要符合真实物体的样子; 模型学的就是这些模式;
  4. 训完把编码器扔掉,解码器就是一台生成机:给它两个数,它吐一张图;
  5. 经典做法当生成器不好用:它只保证「被图占过的那些点」解码得对, 点与点之间的空洞谁也没管;
  6. VAE 的改动只有一处:不输出一个点,输出一个均值加一个方差,再从中随机抽一个点去解码;
  7. 这一改为什么管用:同一张图每次落在区域里的不同位置,而这些位置都被要求还原成同一张图—— 于是一整片区域都被逼着解码得像样;
  8. 随机的东西求不了导,做法是把随机挪到旁路: z = 均值 + exp(对数方差 × 0.5) × ε,ε 是外面丢进来的,梯度照样穿过前两项;
  9. 编码器吐的是「对数方差」不是方差,因为方差必须非负而神经层保证不了, 取对数就没这个约束了;
  10. 损失有两笔账:还原得像不像(均方误差)+ 区域摆得散不散(KL 散度)。 量级差得远,给前者乘 784 来配平——和第 07 章那个乘 224 是同一个手法;
  11. 这种损失 fit() 装不下(它要求每个输出的损失不依赖别的输出), 必须手写训练循环——这是全书第一次离开那条六步流水线;
  12. 回报是能在这个平面上漫游: 20×20 的网格上取点, 一件 T 恤会连续变成连帽衫、裤子、靴子、鞋子,中间不断裂。

11. 原文地图

主题原书章原文位置
自编码器的架构;输入输出同尺寸、用均方误差第 10 章text/22-ch10.txt:229(搜「输入和输出的图像尺寸是相同的」)
沙漏结构;本征向量与本征空间;编码器解码器第 10 章text/22-ch10.txt:231(搜「本征向量」)
它是一个极其高效的降维器第 10 章text/22-ch10.txt:235(搜「降维器」)
信息论视角:像素不是随机的第 10 章text/22-ch10.txt:237(搜「只有噪声信号而没有内容」)
扔掉编码器,解码器就是生成器;微笑维度第 10 章text/22-ch10.txt:239(搜「微笑」)
经典自编码器不够好;VAE 的两篇出处第 10 章text/22-ch10.txt:243(搜「2013年之后就不那么流行了」)
VAE:输出均值和方差,再随机采样第 10 章text/22-ch10.txt:249(搜「随机采样出一个向量」)
Fashion-MNIST:28×28、10 类、比 MNIST 难第 10 章text/22-ch10.txt:253(搜「96.35%」)
为什么用对数方差第 10 章text/22-ch10.txt:280(搜「没有什么简单的方法」)
重参数化的式子第 10 章text/22-ch10.txt:286(搜「zMean + exp」) · text/22-ch10.txt:287(搜「标准差本身就是方差的平方根」)
自定义层 ZLayer:两个关键方法第 10 章text/22-ch10.txt:294(搜「computeOutputShape」)
编码器分两个岔;函数式模型第 10 章text/22-ch10.txt:356(搜「共用一个相同的隐藏层」)
解码器;sigmoid 保证像素在 0~1第 10 章text/22-ch10.txt:373(搜「保证输出图像的像素值范围」)
图被扁平化,舍弃空间信息第 10 章text/22-ch10.txt:358(搜「舍弃了空间信息」)
两项损失;和第 5 章目标检测同一个手法第 10 章text/22-ch10.txt:393(搜「两项之和」) · text/22-ch10.txt:408(搜「originalDim」)
KL 散度的直觉解释;数学推给外部第 10 章text/22-ch10.txt:395(搜「更均匀地分布」)
fit() 用不了的确切原因;手写训练循环第 10 章text/22-ch10.txt:414(搜「不依赖于其他输出」) · text/22-ch10.txt:428(搜「optimizer.minimize」)
训练开销:5 分钟 / 一小时第 10 章text/22-ch10.txt:273(搜「5分钟」)
20×20 网格漫游;各段渐变的方向第 10 章text/22-ch10.txt:444(搜「完全连续」) · text/22-ch10.txt:446(搜「20 × 20的网格」)

Footnotes

  1. 出处:「第 10 章 生成式深度学习」第 229 段(text/22-ch10.txt:229,搜「输入和输出的图像尺寸是相同的」)。「潜在空间」这个译名出现在本章要点清单里(text/22-ch10.txt:11,搜「潜在空间」),正文用的是「本征空间」,两者指同一个东西。 2

  2. 出处:「第 10 章」第 231 段(text/22-ch10.txt:231,搜「本征向量」)。原文明说本征向量和 z 向量「这两个术语在使用中没有差别」,本征空间和 z 空间同理。 2

  3. 出处:「第 10 章」第 237 段(text/22-ch10.txt:237,搜「只有噪声信号而没有内容」)。原文:「自编码器的责任就是学习像素背后的模式。这也是自编码器这样的模型可行的原因。」

  4. 出处:「第 10 章」第 239 段(text/22-ch10.txt:239,搜「脱离编码器部分」)。原文说对于任何给定的本征向量,解码器「可以生成符合训练图像模式和风格的图像。这是生成式模型应有的能力」。

  5. 出处:「第 10 章」第 239 段与图 10-5 说明(text/22-ch10.txt:239,搜「微笑」)。原文用的语气是「本征空间的一个维度可能和微笑的程度相关联」。 2

  6. 出处:「第 10 章」第 243 段(text/22-ch10.txt:243,搜「2013年之后就不那么流行了」)。两篇出处见同段脚注:Kingma 与 Welling 的「Auto-Encoding Variational Bayes」(2013 年 12 月),Rezende、Mohamed、Wierstra 的「Stochastic Backpropagation and Approximate Inference in Deep Generative Models」(2014 年 1 月)。 2 3

  7. 出处:「第 10 章」第 249 段(text/22-ch10.txt:249,搜「随机采样出一个向量」)。原文:「这种随机性是VAE提升稳健性,并确保本征空间能够编码图像各部位的表示的关键所在。经由解码器解码后,从本征空间采样的每个点都应是一个正确的图像输出。」本章第 4 节那五步因果链是我们把这一句展开写的,书只给了结论。

  8. 出处:「第 10 章」第 395 段与第 397 段脚注(text/22-ch10.txt:395,搜「更均匀地分布」)。原文明说「此处不会赘述算法的具体细节」,并把数学推给了 Irhum Shafkat 的博文「Intuitively Understanding Variational Autoencoders」。 2 3 4

  9. 出处:「第 10 章」第 284~289 段(text/22-ch10.txt:286,搜「zMean + exp」;text/22-ch10.txt:287,搜「标准差本身就是方差的平方根」)。原文把这个手法称为「重新参数化」。 2

  10. 出处:「第 10 章」第 280 段(text/22-ch10.txt:280,搜「没有什么简单的方法」)。同段第 282 段脚注补充说明:之所以对数方差是一个向量而不是矩阵,是因为把协方差矩阵约束成了对角矩阵,也就是假定本征向量的各个元素之间没有关联。

  11. 出处:「第 10 章」第 292~294 段与代码清单 10-3(text/22-ch10.txt:294,搜「computeOutputShape」)。原文回指第 9 章那个自定义层(我们的第 14 章里那个「取最后一步」的层)。

  12. 出处:「第 10 章」第 393 段与代码清单 10-7(text/22-ch10.txt:393,搜「两项之和」;text/22-ch10.txt:408,搜「originalDim」)。原文自己把这个配平手法和第 5 章那个目标检测的自定义损失并列:「在该函数中,一项是目标的分类,另一项是目标在图像中的位置。」——那正是我们第 07 章讲的乘 224。

  13. 出处:「第 10 章」第 414 段(text/22-ch10.txt:414,搜「不依赖于其他输出」)。原文:「在TensorFlow.js中,fit()和fitDataset()只有在模型每个输出的损失函数不依赖于其他输出时才适用。」 2

  14. 出处:「第 10 章」第 444 与 446 段(text/22-ch10.txt:444,搜「完全连续」;text/22-ch10.txt:446,搜「20 × 20的网格」)。原文对渐变方向用的措辞是「横向维度看起来代表的是……」「横向维度似乎表示的是……」。 2 3

  15. 出处:「第 10 章」第 273 段(text/22-ch10.txt:273,搜「5分钟」)。默认训练轮次为 5 见第 416 段。

  16. 出处:「第 10 章」第 253 段(text/22-ch10.txt:253,搜「96.35%」)。原文说 Fashion-MNIST 对机器学习算法来说比 MNIST 更难学。

  17. 出处:「第 10 章」第 358 段(text/22-ch10.txt:358,搜「舍弃了空间信息」)。原文:「针对图像的VAE一般会使用卷积层和池化层,但是因为此处的图像很简单……这种扁平化策略非常适用于这种使用场景。」