趋近智
与变分自编码器(VAEs)或生成对抗网络 (GAN)(GANs)相比,归一化 (normalization)流为生成建模提供了一种独特的方法。其主要优点在于能够精确计算数据似然,同时在数据空间与具有简单分布(如标准高斯分布)的潜在空间之间定义一个可逆映射。这使得它们特别适用于需要精确密度估计或可逆性有益的场合。
核心思路是学习一个变换 ,将复杂的输入数据点 映射到更简单的潜在变量 ,其中 是一个已知、易于处理的概率分布(例如,)。由于 被设计为可逆的 () 且可微分,我们可以使用概率论中的变量变换定理来关联数据 的密度与潜在变量 的密度。
变量变换公式表明,对于一个将 映射到 的可逆、可微分函数 ,它们概率密度之间的关系是:
在此, 是变换 在 处计算的雅可比矩阵,而 表示其行列式的绝对值。
这个公式很重要。它告诉我们,如果能计算 及其雅可比矩阵的行列式,我们就可以使用已知的 密度,计算任何给定数据点 的精确概率密度 。
对于生成建模,我们通常使用对数似然,这避免了数值下溢并简化了计算:
训练归一化 (normalization)流涉及在数据集上最大化此对数似然。这要求变换 具备两个性质:
复杂的变换 通常通过组合更简单的可逆函数来构建,这些函数常被称为双射函数或耦合层:。如果每个 都是可逆的,并且具有易于计算的雅可比行列式,那么复合函数 就会继承这些性质。
复合函数 的雅可比矩阵是各层雅可比矩阵的乘积:
其中 且 。
由于性质 ,整体雅可比矩阵的对数行列式变成一个和:
这种组合性使我们能够从更简单的模块构建富有表现力的变换,同时保持计算的可处理性。
这是一个归一化 (normalization)流的图示:
数据空间 通过一系列可逆函数 变换为简单的潜在空间 (例如,高斯分布)。逆变换 允许通过从 中抽取 并计算 来从 采样。
设计有效的双射函数是归一化 (normalization)流的核心。一种常用且成功的方法包含耦合层。
Real Non-Volume Preserving (RealNVP) 流,基于NICE (Non-linear Independent Components Estimation) 构建,采用巧妙的掩码策略。它们将输入向量 (vector) 分成两部分, 和 。变换按以下方式进行:
逆变换也很简单:
此变换的雅可比矩阵是下三角矩阵(或上三角矩阵,取决于哪个部分被变换):
值得注意的是, 是一个对角矩阵,其对角元素为 。因此,行列式就是对角元素的乘积:。对数行列式就是缩放网络输出的总和:。
这种结构保证了易于求逆和对数行列式的高效计算。为了确保所有维度都被变换,连续的耦合层通常会交换 和 的角色或使用不同的掩码。
另一类重要的模型是自回归流。在这些模型中,潜在变量的每个维度 仅以前面的维度 为条件(或 ,取决于方向)。
它们常使用掩码神经网络来强制自回归特性。
PyTorch的 torch.distributions 模块提供了构建归一化流的优秀工具。重要组件包括:
torch.distributions.Distribution:概率分布的基类(如 Normal)。torch.distributions.Transform:可逆变换的基类。提供了如 AffineTransform、ExpTransform 等实现。通常会继承此基类定义自定义变换。torch.distributions.TransformedDistribution:通过将一系列 Transform 对象应用于基础分布来创建新分布。它自动处理变量变换的计算。torch.distributions.constraints:用于定义分布的支持域和变换参数 (parameter)的有效性检查。让我们大致描述一下如何使用耦合层定义一个简单流。您通常会定义一个继承自 torch.distributions.Transform 的 CouplingLayer 类。
import torch
import torch.nn as nn
import torch.distributions as dist
class CouplingLayer(dist.Transform):
def __init__(self, input_dim, hidden_dim, mask, base_transform_type='affine'):
super().__init__()
self.input_dim = input_dim
# 确保掩码是二元张量(0和1)
self.register_buffer('mask', mask)
# 定义计算缩放和平移参数的网络 's_t_network'
self.s_t_network = nn.Sequential(
nn.Linear(input_dim, hidden_dim),
nn.ReLU(),
nn.Linear(hidden_dim, hidden_dim),
nn.ReLU(),
nn.Linear(hidden_dim, input_dim * 2) # 输出所有维度的缩放和平移
)
self.bijective = True
# 对于仿射变换:定义域 = 实数,值域 = 实数
self.domain = dist.constraints.real_vector
self.codomain = dist.constraints.real_vector
# 可选:使用内置变换,如 AffineTransform
# if base_transform_type == 'affine' ...(实现细节)
def _call(self, x):
""" 应用变换:x -> z """
x_masked = x * self.mask
s_t_params = self.s_t_network(x_masked)
# 将输出分为缩放(s)和平移(t)
# 确保缩放为正,例如使用 tanh 增加稳定性和缩放
s = torch.tanh(s_t_params[..., :self.input_dim])
t = s_t_params[..., self.input_dim:]
# 仅对未掩码的元素应用变换
# z = x_masked + (x_unmasked * exp(s) + t) * (1 - mask)
z = self.mask * x + (1 - self.mask) * (x * torch.exp(s) + t)
return z
def _inverse(self, z):
""" 应用逆变换:z -> x """
z_masked = z * self.mask
s_t_params = self.s_t_network(z_masked)
s = torch.tanh(s_t_params[..., :self.input_dim])
t = s_t_params[..., self.input_dim:]
# 仅对未掩码的元素应用逆变换
# x = z_masked + ((z_unmasked - t) * exp(-s)) * (1 - mask)
x = self.mask * z + (1 - self.mask) * ((z - t) * torch.exp(-s))
return x
def log_abs_det_jacobian(self, x, z):
""" 计算 log |det J(x)| """
x_masked = x * self.mask
s_t_params = self.s_t_network(x_masked)
s = torch.tanh(s_t_params[..., :self.input_dim])
# 对数行列式是变换维度上 's' 的总和
log_det_jacobian = (1 - self.mask) * s
# 对变换维度求和
return log_det_jacobian.sum(-1)
# 示例用法:
input_dim = 10
hidden_dim = 64
num_flows = 5
# 定义基础分布(例如,标准正态分布)
base_dist = dist.Normal(torch.zeros(input_dim), torch.ones(input_dim))
# 创建掩码(在步骤之间交替是常见的)
masks = []
for i in range(num_flows):
mask = torch.zeros(input_dim)
mask[i % 2::2] = 1 # 简单的交替掩码
# 或者存在更复杂的掩码策略
masks.append(mask)
# 创建一系列变换(耦合层)
transforms = []
for i in range(num_flows):
# 交替哪一部分是恒等变换,哪一部分是变换
current_mask = masks[i] if i % 2 == 0 else (1 - masks[i])
transforms.append(CouplingLayer(input_dim, hidden_dim, current_mask))
# 可选:在流之间添加置换/激活归一化层
# 构建变换后的分布
flow_dist = dist.TransformedDistribution(base_dist, transforms)
# --- 训练 ---
# optimizer = torch.optim.Adam(flow_dist.parameters(), lr=1e-4)
#
# for data_batch in dataloader:
# optimizer.zero_grad()
#
# # 计算数据批次的对数概率
# log_prob = flow_dist.log_prob(data_batch) # 形状:[batch_size]
#
# # 最大化对数似然 -> 最小化负对数似然
# loss = -log_prob.mean()
#
# loss.backward()
# optimizer.step()
# --- 采样 ---
# n_samples = 64
# samples = flow_dist.sample(torch.Size([n_samples])) # 形状:[n_samples, input_dim]
优点:
考量:
归一化 (normalization)流已在多个应用场景中得到采用:
总之,归一化流为生成建模和密度估计提供了一个数学上优雅且功能强大的框架。凭借可逆变换和变量变换公式,它们允许进行精确的似然计算,为高级深度学习 (deep learning)工具集中特定概率建模任务提供了特有的优点。
这部分内容有帮助吗?
torch.distributions 模块的官方文档,对在PyTorch中实现归一化流至关重要。© 2026 ApX Machine Learning用心打造