深度学习入门站 · 理解深度学习
首页 › 第 16 章

标准化流

一种能精确计算概率的生成模型:把简单分布"流"成复杂分布。
图 16-1:标准化流:简单分布与复杂分布的可逆变换
图 16-1:标准化流:简单分布与复杂分布的可逆变换

这一章的主角是标准化流(Normalizing Flows)——一种既会"采样"又会"打分"的生成模型:能像 GAN 一样生成新样本,也能精确算出任意样本的概率。学完这章,你会懂"变形如何改变密度"、可逆网络怎么揉分布,以及它和 GAN、VAE 的取舍。

生成模型的两副担子:采样与打分

生成模型要干两件事:采样(sampling)——从学到的分布里抽出新样本;算概率(likelihood)——给样本打分,分数越高越像真货。

上一章的 GAN 只会第一件:它能用噪声生成以假乱真的图片,但模型没有定义概率分布,没法回答"这张图多像真的"。标准化流两件都会,而且概率是精确算出来的——这是它的招牌。

核心思想:把简单分布"揉"成复杂分布

标准化流的思路一句话说清:从简单分布出发,通过一连串可逆变换,把它慢慢"揉"成复杂的数据分布。

我们选一个简单的基础分布 $\Pr(z)$——通常是标准正态,即以 0 为中心的钟形曲线——再套一个带参数 $\phi$ 的函数 $x = f[z, \phi]$。$\phi$ 是旋钮,拧到合适位置,$x$ 的分布恰好长成数据的样子。

于是采样出奇简单:从基础分布抽一个 $z^*$,算一遍 $x^* = f[z^*, \phi]$,新样本就诞生了。

🍞
生活类比
把标准正态想象成一块厚薄均匀的面团。每一层变换就是一次揉面:拉长、压扁,一层层揉成数据的复杂形状——揉薄处密度变稀,压厚处变浓。如果每步都能"倒放",面团就能复原成最初均匀的样子:这就是"流(flow)"的来历,"标准化"则指反着走时,任何分布都被归一化回标准正态。

变形要付出代价:变量变换公式

采样容易,反过来问:给定 $x$,它的概率是多少?要小心——变形会改变密度:拉伸输入的地方,概率被"摊薄";压缩的地方,密度被"挤浓"。但总面积必须恒为 1,所以换算密度要乘一个修正因子——一维下就是导数绝对值的倒数:

$$\Pr(x|\phi) = \left|\frac{\partial f(z,\phi)}{\partial z}\right|^{-1} \Pr(z), \quad \text{其中 } z = f^{-1}[x, \phi]$$

这叫变量变换公式(change of variables):逆变换找到产生 $x$ 的 $z$,查它在基础分布里的概率,再除以 $|f'(z)|$。导数大于 1(拉伸)概率变小,小于 1(压缩)概率变大。隐藏要求:$f$ 必须可逆——这是对架构最根本的约束。

💡
核心要点:变形改密度,修正看雅可比。给 $x$ 打分 = 逆变换找到 $z$ → 查 $\Pr(z)$ → 乘修正因子。拉伸变稀、压缩变浓,总面积恒为 1。采样走正向 $f$,打分走逆向 $f^{-1}$,故 $f$ 必须可逆。

到了高维,$z$、$x$ 成向量,导数升级为 $D \times D$ 的雅可比矩阵,修正因子也升级为行列式绝对值的倒数:

$$\Pr(x|\phi) = \left|\det \frac{\partial f(z,\phi)}{\partial z}\right|^{-1} \Pr(z)$$

一维"面积守恒",高维"体积守恒":行列式告诉我们小块邻域被放大几倍,放得越大密度越稀。一般网络算它要 $O(D^3)$,太贵——所有精巧设计都为了让行列式好算。训练时拧 $\phi$ 让样本总概率最大,即最小化负对数似然 $\sum_i -\log \Pr(x_i|\phi)$。

光说不练假把式。用 NumPy 验证:用"负半轴压缩 5 倍"的可逆变换揉标准正态,再用公式算密度,与采样直方图对比:

import numpy as np
import matplotlib.pyplot as plt

# 可逆变换 x = f(z):z 为负时被压缩 5 倍,非负时原样保留
def f(z):
    return np.where(z >= 0, z, 0.2 * z)

def dfdz(z):                     # 一维"雅可比":|f'(z)|
    return np.where(z >= 0, 1.0, 0.2)

# 1) 采样:从基础分布抽 z,正向映射得到"数据"
rng = np.random.default_rng(0)
z = rng.normal(size=200_000)
x = f(z)

# 2) 变量变换公式:Pr(x) = Pr(z) / |f'(z)|,其中 z = f^{-1}(x)
grid = np.linspace(-5, 5, 400)
zx = np.where(grid >= 0, grid, grid / 0.2)   # 逆映射
pr_x = np.exp(-0.5 * zx**2) / np.sqrt(2 * np.pi) / dfdz(grid)

# 3) 对比:公式曲线应当与样本直方图重合
plt.hist(x, bins=100, density=True, alpha=0.5, label="样本")
plt.plot(grid, pr_x, label="公式 Pr(x)")
plt.legend()
plt.show()

跑起来会看到,公式曲线与直方图严丝合缝地重合——压缩一侧密度明显变高,正是修正因子在起作用。

可逆网络与耦合层

单层 $f$ 表达能力太弱。真正的做法是把很多层串起来:$x = f_K[f_{K-1}[\cdots f_1[z]\cdots]]$。好消息是,复合函数的雅可比行列式等于各层行列式的乘积:

$$\left|\det \frac{\partial f}{\partial z}\right| = \prod_{k=1}^{K} \left|\det \frac{\partial f_k}{\partial f_{k-1}}\right|$$

意思是总变形因子等于各层变形因子相乘——把 10 次"放大 2 倍"串起来,总共放大 $2^{10}$ 倍。但每层必须满足四个条件:可逆、逆好算、雅可比好算、有表达力;训练要反复给样本打分,慢一点都不行。

⚠️
注意:普通的卷积层、全连接层大多不可逆——不同输入被压成同一输出,信息一去不返。所以流模型必须专门设计可逆层,不能套现成网络。

最常用的破解办法是耦合层:把输入 $h$ 切成两半,$h_1$ 原样输出,$h_2$ 用可逆小函数 $g$ 变换,$g$ 的参数由 $h_1$ 决定:

$$h_1' = h_1, \qquad h_2' = g[h_2, \phi[h_1]]$$

妙处在于雅可比是块三角的,行列式等于对角块乘积,几乎免费;逆变换也简单:取回 $h_1$ → 算参数 → 对 $h_2'$ 做 $g^{-1}$($g$ 通常是缩放加平移这类简单可逆函数)。代价是每层只动一半变量,所以层间穿插固定随机置换,让每个维度都被照顾到。图像模型 GLOW 就靠它把真实图片一步步"倒放"成噪声。

精确似然 vs 训练难度:三种生成模型的取舍

标准化流这么全能,是不是无敌了?并非如此,它和 GAN、VAE 各有取舍:

  • GAN:采样质量高、速度快,但模型里没有概率分布,算不了似然,对抗训练也难调。
  • VAE:能算概率,但只是下界(ELBO),不是精确值,样本通常偏模糊。
  • 标准化流:采样、精确似然两不误,还能客串密度估计器做异常检测;代价是被"可逆 + 雅可比好算"锁住,图像质量通常逊于 GAN 和扩散模型。

一句话:精确似然是流模型的王牌,可逆架构是它的紧箍咒。

🎯 小测验

1. 相比 GAN,标准化流最大的优势是什么?
2. 变换函数把输入区域"拉伸"时,该区域的概率密度会怎样?
3. 为什么流模型的每一层都必须是可逆的?