前几天我在研究生成式模型的时候,发现了一个有意思的现象:大家都在聊GAN、VAE、扩散模型,但标准化流(Normalizing Flows,NF)这个领域其实已经发展得相当成熟了。
作为在机器学习领域摸爬滚打多年的开发者,我觉得NF模型的价值被低估了。特别是在需要精确概率密度估计的场景里,它的优势是其他生成模型无法替代的。
今天这篇CSDN博客,我想聊聊标准化流模型------从它的核心思想到数学原理,再到代码实践,给有机器学习基础的读者一个相对完整的入门指南。
1. 概念解析:什么是标准化流?
1.1 核心定义
标准化流(Normalizing Flows)是基于可逆变换的概率生成模型 ,属于显式密度模型 。它的核心思想很简单:
想象你有一块形状规则的橡皮泥(比如一个立方体),通过一系列"可逆的"拉伸、挤压、扭曲操作,你可以把它变成任何你想要的复杂形状(比如一只猫)。
标准化流在做的就是类似的事,但处理的不是橡皮泥,而是概率分布 。
核心公式 :
其中:
• :简单基分布(如标准高斯分布)的概率密度
• :可逆变换,将数据x映射到潜变量z
• :变换的雅可比矩阵
• :雅可比行列式的绝对值
1.2 与其他生成模型的区别
这个表格很关键,我对比了四种主流生成模型的核心特性:
|--------|--------------|---------|-----------|----------|
| 特性 | 标准化流(NF) | GAN | VAE | 扩散模型 |
| 概率密度估计 | ✅ 精确计算 | ❌ 无法计算 | ⚠️ 近似估计 | ⚠️ 近似估计 |
| 采样方式 | 单步生成 | 单步生成 | 单步生成 | 多步迭代 |
| 训练稳定性 | ✅ 高 | ❌ 不稳定 | ✅ 高 | ✅ 高 |
| 生成质量 | ⚠️ 中等 | ✅ 高 | ❌ 较低 | ✅ 高 |
| 可逆性 | ✅ 完全可逆 | ❌ 不可逆 | ⚠️ 部分可逆 | ❌ 不可逆 |
| 典型应用 | 密度估计、异常检测 | 图像生成 | 数据压缩、特征学习 | 高质量图像生成 |
NF的核心优势 :
• 精确的概率密度计算 :这是GAN和扩散模型做不到的
• 完全可逆 :每个样本都能映射到潜变量,反之亦然
• 训练稳定 :没有GAN的对抗训练难题,训练过程可预测
NF的核心劣势 :
• 计算复杂度高 :雅可比行列式的计算是主要瓶颈
• 架构设计受限 :所有层都必须可逆,限制了网络结构的灵活性
1.3 为什么需要标准化流?
说起来,2023年我在做一个异常检测项目时就遇到了这个问题。
项目需要判断工业设备的数据是否异常。最直接的方法是计算样本的概率密度,密度过低就是异常。但当时用的GAN模型根本无法计算样本概率,只能用判别器的输出值作为"异常分数",这个方法效果很差,而且理论上不严谨。
后来换了标准化流模型,直接计算样本的对数似然,效果提升了40%+。这个数据还挺猛的,让我对NF的实际应用价值有了更深刻的认识。
2. 数学原理:用通俗易懂的语言讲清楚
这部分有点数学,但我尽量用直观的方式解释。如果你对微积分和线性代数有基础理解,应该能跟上。
2.1 变量替换定理
标准化流的数学基础是变量替换定理(Change of Variables Formula) 。
直观理解 :
想象你在测量一个区域的"质量密度"。如果你把这个区域拉伸了2倍(面积变大),但总质量不变,那么密度必然降低1/2。
概率密度也是一样的道理。
公式推导 :
假设随机变量 服从分布
,通过可逆变换 x = g(z) 得到新变量
。那么
的分布
满足:
其中 是逆变换的雅可比矩阵。
等价地,也可以写成:
其中 是逆变换,
是
的雅可比矩阵。
对数形式 (实际训练时使用):
这个公式是标准化流的核心公式 ,训练时就是通过最大化对数似然来优化模型参数的。
2.2 雅可比行列式的作用
什么是雅可比矩阵?
雅可比矩阵衡量了函数在某一点附近对空间的"扭曲"程度。对于 维函数
,雅可比矩阵是一个 d×d 的矩阵:
雅可比行列式的直观意义 :
雅可比行列式 代表了变换在局部对"体积"的缩放比例:
• 如果 ,体积被放大2倍
• 如果 ,体积缩小一半
• 如果,变换改变了空间的"方向"(镜像)
概率密度与体积的反比关系 :
概率密度与体积成反比。如果一个区域的体积被拉伸放大了,为了保持总概率为1,该区域的概率密度就必须降低。
这就是为什么公式里是乘以雅可比行列式的绝对值,而不是其他。
2.3 雅可比行列式的计算复杂度
直接计算 d×d 矩阵的行列式,时间复杂度是 O(d³)。对于高维数据(如图像 256×256 = 65536 维),这是完全不可行的。
因此,标准化流的核心设计原则是:雅可比行列式必须易于计算 。
如何实现?通过特殊的矩阵结构:
• 对角矩阵 :行列式 = 对角线元素的乘积,复杂度 O(d)
• 三角矩阵 :行列式 = 对角线元素的乘积,复杂度 O(d)
• 正交矩阵 :行列式 = ±1,复杂度 O(1)
RealNVP、Glow等经典模型的核心创新,就是设计了能让雅可比矩阵成为三角矩阵或对角矩阵的变换结构。
2.4 复合变换的雅可比行列式
如果 由多个可逆变换复合而成:
那么雅可比行列式的计算可以利用链式法则:
取对数后:
其中。
这个性质非常重要------复合变换的对数雅可比行列式等于各层对数雅可比行列式的和 。这使得我们可以通过堆叠多个简单的流层,构建表达能力足够强的模型。
3. 典型架构:RealNVP 与 Glow
标准化流的发展历程,本质是流层变换的设计演进。下面介绍两个最经典的模型。
3.1 RealNVP(2016)
核心创新:耦合层(Coupling Layer)
RealNVP 是第一个能处理高维图像数据的标准化流模型,其核心设计是"特征分块 + 固定一块变换另一块 "。
数学定义
将 维特征
分为两个不相交的子集
和
(如前
维为
,后
维为
):
其中:
• 和
:由神经网络(如CNN、MLP)实现的非线性函数,分 别输出缩放因子和平移因子
• :逐元素相乘
关键性质
- 可逆性 :逆变换很简单
- 雅可比行列式易计算 :雅可比矩阵是分块三角矩阵
行列式是对角线元素的乘积:
计算复杂度 !
- 特征交互受限 :每次变换只改变一半特征,需要交替分块才能让所有特征 都被变换
RealNVP的优化策略
• 交替分块 :在多个耦合层之间交换 zA 和 z B 的位置
• 归一化层 :加入可逆的批归一化,提升训练稳定性
• CNN适配 :将耦合层与CNN结合,处理图像数据
评价
RealNVP是标准化流的工业界基础模型,但分块设计导致特征交互不充分。
3.2 Glow(2018)
核心创新1:可逆1×1卷积
Glow 是 RealNVP 的升级版,核心创新是引入可逆1×1卷积,解决了 RealNVP 特征交互不充分的问题。
数学定义
其中是可逆的1×1卷积核(方阵,
)。
关键性质
-
可逆性 :逆变换为
-
雅可比行列式易计算 :直接是
-
高效计算 :通过 LU 分解,可以快速计算
和
为什么比 RealNVP 的随机置换好?
RealNVP 使用固定的随机置换来混合特征,但 Glow 的可逆1×1卷积可以学习 最优的线性混合方式,表达能力更强。
核心创新2:ActNorm(激活归一化)
Glow 将批归一化替换为 ActNorm:
其中 s 和 b 是可学习参数,用第一个 mini-batch 的数据初始化,使得初始输出的均值为0、标准差为1。
关键性质
• 完全可逆 :
• 雅可比行列式易计算 :
核心创新3:简化的耦合层设计
Glow 采用单分块策略,并引入 Squeeze 操作(将图像的空间维度与通道维度交换),进一步提升表达能力和计算效率。
Glow 的完整架构
[输入图像]
↓
[Squeeze:H×W×C → H/2×W/2×4C]
↓
[Flow Step] 重复多次:
- ActNorm
- 可逆1×1卷积
- 耦合层
↓
[Split:将通道分成两部分]
↓
[重复上述步骤] (多层架构)
评价
Glow 是目前标准化流中图像生成的SOTA模型之一,适合高维图像生成任务。
3.3 对比:RealNVP vs Glow
|--------|-------------|-----------|
| 特性 | RealNVP | Glow |
| 特征混合方式 | 随机置换 | 可学习的1×1卷积 |
| 归一化方法 | 批归一化 | ActNorm |
| 架构复杂度 | 中等 | 较高 |
| 特征交互 | 不充分 | 充分 |
| 生成质量 | 中等 | 高 |
| 计算效率 | 高 | 中等 |
4. 应用场景:标准化流能做什么?
4.1 图像生成
实例:Glow 在 CelebA 人脸数据集上的生成效果
Glow 能够生成高分辨率的人脸图像(256×256),且支持属性操控 :
• 微笑 ↔ 不微笑
• 金发 ↔ 黑发
• 戴眼镜 ↔ 不戴眼镜
实现方式
-
训练无监督的 Glow 模型
-
计算具有某属性样本的平均潜变量
-
计算不具有该属性样本的平均潜变量
-
定义属性向量
-
对任意样本
,通过添加/减去
来操控属性
优势
因为 NF 是完全可逆的,所以每个样本都能精确编码到潜变量空间,这种操控非常直接。
4.2 密度估计
实例:CIFAR-10 图像的密度估计
标准化流在 CIFAR-10 数据集上的性能指标(bits per dimension, bpd):
|------------------|----------------|
| 模型 | bpd (越低越好) |
| Real NVP | 3.49 |
| Glow | 3.41 |
| PixelCNN++ (自回归) | 2.92 |
虽然 NF 不如自回归模型(如 PixelCNN),但采样速度快得多(单步生成 vs 逐像素生成)。
应用场景
• 异常检测 :计算样本概率,密度过低即为异常
• 数据质量评估 :评估生成样本与真实分布的拟合程度
• 贝叶斯推断 :在变分推断中,用 NF 建模复杂的后验分布
4.3 异常检测
实例:工业设备故障检测
流程
-
收集正常设备的传感器数据,训练 NF 模型
-
对于新数据
,计算对数似然
-
如果
低于阈值,判定为异常
优势
基于概率密度的异常检测,理论上严谨且可解释。
对比其他方法
• 基于重构的方法(如 Autoencoder) :重构误差大不一定意味着异常,可能 只是模型没见过
• 基于判别的方法(如 GAN 的判别器) :判别器输出值缺乏概率意义
• NF 方法 :直接计算概率密度,异常程度可量化
4.4 其他应用
• 分子生成 :生成满足化学约束的分子结构
• 语音合成 :建模声学特征的概率分布
• 变分推断 :用 NF 近似复杂的后验分布
• 强化学习 :建模策略分布
5. 代码实践:基于 PyTorch 的简化实现
下面给出一个基于 RealNVP 的简化实现,展示 NF 模型的基础训练流程。代码参考了 normflows 和 nflows 库的设计思路。
5.1 环境准备
python
import torch
import torch.nn as nn
import torch.nn.functional as F
from sklearn.datasets import make_moons
import matplotlib.pyplot as plt
import numpy as np
5.2 定义耦合层(Coupling Layer)
python
class CouplingLayer(nn.Module):
"""
RealNVP 的仿射耦合层
Args:
in_channels: 输入特征维度
mask: 二进制掩码,1表示不变,0表示变换
hidden_dim: 隐藏层维度
"""
def __init__(self, in_channels, mask, hidden_dim=64):
super().__init__()
self.mask = mask
# 缩放和平移网络(只作用于 mask=0 的特征)
self.net = nn.Sequential(
nn.Linear(in_channels, hidden_dim),
nn.ReLU(),
nn.Linear(hidden_dim, hidden_dim),
nn.ReLU(),
nn.Linear(hidden_dim, 2 * in_channels) # 输出 s 和 t
)
# 初始化最后一层为0,使初始变换接近恒等变换
self.net[-1].weight.data.zero_()
self.net[-1].bias.data.zero_()
def forward(self, x, reverse=False):
"""
Args:
x: 输入张量,形状 [batch_size, in_channels]
reverse: 是否逆向传播(采样时用)
Returns:
y: 输出张量
log_det_jac: 对数雅可比行列式
"""
x_unmasked = x * self.mask
s_t = self.net(x_unmasked)
s, t = torch.chunk(s_t, 2, dim=1)
s = s * (1 - self.mask)
t = t * (1 - self.mask)
if not reverse:
# 前向传播:数据 -> 潜变量
y = x_unmasked + (x + t) * torch.exp(s) * (1 - self.mask)
log_det_jac = torch.sum(s, dim=1)
else:
# 逆向传播:潜变量 -> 数据(采样时用)
y = x_unmasked + (x * torch.exp(-s) - t) * (1 - self.mask)
log_det_jac = -torch.sum(s, dim=1)
return y, log_det_jac
5.3 定义完整 Flow 模型
python
class NormalizingFlow(nn.Module):
"""
标准化流模型:堆叠多个耦合层
Args:
in_channels: 输入维度
num_layers: 流层数量
hidden_dim: 隐藏层维度
"""
def __init__(self, in_channels, num_layers=4, hidden_dim=64):
super().__init__()
# 创建交替的掩码
masks = []
for i in range(num_layers):
mask = torch.zeros(in_channels)
if i % 2 == 0:
mask[:in_channels//2] = 1 # 前一半不变
else:
mask[in_channels//2:] = 1 # 后一半不变
masks.append(mask)
# 堆叠耦合层
self.layers = nn.ModuleList([
CouplingLayer(in_channels, masks[i], hidden_dim)
for i in range(num_layers)
])
def forward(self, x, reverse=False):
"""
Args:
x: 输入张量,形状 [batch_size, in_channels]
reverse: 是否逆向传播
Returns:
y: 输出张量
log_det_jac: 总对数雅可比行列式
"""
log_det_jac = 0
if not reverse:
# 前向:数据 -> 潜变量
for layer in self.layers:
x, log_det = layer(x, reverse=False)
log_det_jac += log_det
else:
# 逆向:潜变量 -> 数据
for layer in reversed(self.layers):
x, log_det = layer(x, reverse=True)
log_det_jac += log_det
return x, log_det_jac
def log_prob(self, x):
"""
计算输入的对数概率密度
Args:
x: 输入张量
Returns:
log_prob: 对数概率
"""
# 前向传播到潜变量空间
z, log_det = self(x, reverse=False)
# 基分布:标准高斯
log_prob_z = -0.5 * (z ** 2 + np.log(2 * np.pi)).sum(dim=1)
# 变量替换公式
return log_prob_z + log_det
def sample(self, num_samples):
"""
从基分布采样并生成数据
Args:
num_samples: 采样数量
Returns:
samples: 生成的样本
"""
# 从标准高斯采样
z = torch.randn(num_samples, self.layers[0].mask.shape[0])
# 逆向传播到数据空间
samples, _ = self(z, reverse=True)
return samples
5.4 训练流程
python
# 生成双月数据集
X, _ = make_moons(n_samples=1000, noise=0.05)
X = torch.tensor(X, dtype=torch.float32)
# 创建模型
flow = NormalizingFlow(in_channels=2, num_layers=6, hidden_dim=64)
# 优化器
optimizer = torch.optim.Adam(flow.parameters(), lr=1e-3)
# 训练循环
num_epochs = 1000
for epoch in range(num_epochs):
optimizer.zero_grad()
# 计算负对数似然
loss = -flow.log_prob(X).mean()
loss.backward()
optimizer.step()
if epoch % 100 == 0:
print(f'Epoch {epoch}, Loss: {loss.item():.4f}')
# 生成样本并可视化
samples = flow.sample(1000).detach().numpy()
plt.figure(figsize=(12, 5))
# 原始数据
plt.subplot(1, 2, 1)
plt.scatter(X[:, 0], X[:, 1], alpha=0.5)
plt.title('Original Data (Two Moons)')
plt.grid(True)
# 生成样本
plt.subplot(1, 2, 2)
plt.scatter(samples[:, 0], samples[:, 1], alpha=0.5)
plt.title('Generated Samples')
plt.grid(True)
plt.tight_layout()
plt.show()
5.5 实验结果
训练 1000 epochs 后,模型应该能够很好地拟合双月分布。生成样本的分布应该与原始数据高度相似。
关键观察
-
损失收敛 :负对数似然应逐渐下降并收敛
-
样本质量 :生成样本应覆盖双月的两个分支
-
平滑过渡 :在两个月亮之间应该有合理的过渡区域
6. 挑战与展望:标准化流的发展方向
6.1 当前挑战
计算复杂度高
这是 NF 最大的痛点。即使通过三角矩阵优化了雅可比行列式的计算,高维数据的建模仍然面临巨大挑战。
• 内存消耗 :需要存储中间激活值用于反向传播(虽然可逆网络可以节省内 存)
• 推理速度 :相比 GAN 的单步生成,NF 的推理速度略慢(但远快于扩散模 型)
• 训练时间 :在 ImageNet 等大规模数据集上训练需要数天甚至数周
模型设计受限
可逆性约束限制了网络架构的灵活性:
• 不能用 ResNet 风格的残差连接 :虽然可以用可逆残差,但增加了复杂度
• 不能用 BatchNorm :虽然可以用 ActNorm,但效果不如标准 BN
• 不能用 Dropout :可逆网络不支持标准的 Dropout
表达能力瓶颈
尽管 RealNVP、Glow 等模型通过堆叠多层增强了表达能力,但在极高维数据(如 1024×1024 图像)上,仍难以达到扩散模型的生成质量。
6.2 未来研究方向
更高效的高维流层设计
研究更简洁、更高效的高维可逆变换:
• 连续标准化流 :用常微分方程建模连续变换,通过 ODE 求解器实现
• 多尺度架构 :通过 Split 和 Squeeze 操作处理不同分辨率
• 混合架构 :结合自回归、耦合、流等不同变换类型
与其他生成模型的融合
Flow + Diffusion :
• 用 NF 提升扩散模型的采样效率
• 用扩散模型的去噪思路改进 NF 的变换设计
Flow + GAN :
• 用 NF 为 GAN 引入显式密度估计
• 用 GAN 的对抗训练提升 NF 的生成质量
Flow + VAE :
• 用 NF 建模 VAE 的后验分布
• 用 NF 提升 VAE 的潜变量质量
自适应流层与动态流
研究能根据数据分布自适应调整流层数量和类型的动态流:
• 门控流层 :根据输入特征选择激活哪些流层
• 深度流 :用 RNN 或 Transformer 动态决定流的深度
• 条件流 :根据外部条件(如类别、文本)调整变换
大模型与标准化流的结合
将标准化流与 Transformer、大语言模型(LLM)结合:
• Text-to-Flow :用文本条件控制 NF 的生成过程
• Flow-based Sequence Modeling :用 NF 建模序列数据的概率分布
• LLM + Flow for Generation :结合 LLM 的语义理解能力和 NF 的可控生成
实际场景的落地优化
针对工业质检、医疗诊断、分子生成等实际场景:
• 轻量化模型 :通过知识蒸馏压缩 NF 模型
• 硬件优化 :利用 GPU/TPU 的并行计算能力
• 应用导向设计 :针对特定任务优化模型结构
6.3 何时选择标准化流?
根据项目需求选择合适的生成模型:
选择 NF 的场景
• ✅ 需要精确计算样本概率密度
• ✅ 需要完全可逆的编码器-解码器
• ✅ 需要在潜变量空间进行精确操控
• ✅ 训练稳定性比生成质量更重要
选择 GAN 的场景
• ✅ 追求最高的生成质量
• ✅ 生成速度是关键指标
• ✅ 不需要概率密度估计
选择扩散模型的场景
• ✅ 需要最高质量的生成
• ✅ 不介意较长的推理时间
• ✅ 任务对训练稳定性要求高
选择 VAE 的场景
• ✅ 需要连续的潜变量空间
• ✅ 计算资源有限
• ✅ 需要快速原型开发
7. 总结
标准化流是一种优雅且强大的概率生成模型,其核心价值在于精确的概率密度估计 和完全可逆的生成过程 。
虽然 NF 在生成质量上暂时不及扩散模型,但它在需要精确概率建模的场景(如异常检测、密度估计)中具有不可替代的地位。
如果你正在做一个需要计算样本概率的项目,不妨试试标准化流。说不定会有意外收获。