
概率图模型、贝叶斯模型、VAE和GAN这四个词放在一起很多人第一反应是这能放一块讲吗。能而且必须放一块讲。因为它们本质上都在回答同一个问题当我们面对一个充满不确定性的世界时如何用一个可计算的框架去描述它、推断它、甚至生成它。我接触这几个方向是从做异常检测开始的。当时需要在一个没有标签的数据集上判断哪些样本不对劲试过孤立森林、One-Class SVM效果都一般。后来转向概率图模型做密度估计才真正理解了用概率描述世界这件事的威力。再往后深入发现VAE和GAN虽然看起来是深度学习的东西但它们的根都在概率推断里。VAE的ELBO推导本质就是变分推断GAN的对抗训练背后是最小化JS散度。不理解概率图模型和贝叶斯推断看VAE和GAN的论文就只能停留在调包层面。这篇文章我打算把这四个东西串起来讲。不是教科书式的定义罗列而是按照为什么需要它→它怎么工作→它和前后左右的关系是什么→实际用的时候要注意什么这条线来展开。适合有一定机器学习基础、想真正搞懂生成模型底层逻辑的读者。如果你只是想知道怎么调库这篇文章可能不太适合你但如果你想在面试里把VAE和GAN的区别讲清楚或者想在自己的项目里判断该用哪个模型那往下看。1. 从不确定性说起为什么我们需要概率图模型1.1 确定性模型的局限在哪里传统机器学习模型大多在做一件事给定输入x输出一个确定的y。图像分类输出类别回归输出数值。这种范式在很多场景下工作得很好但它有一个隐含假设——世界是确定的或者至少噪声是可以被平均掉的。现实往往不是这样。同一个症状可能对应多种疾病同一句话可能有多重含义同一个用户行为可能出于完全不同的动机。这时候如果模型只输出一个确定答案它其实是在假装自己很确定而忽略了真实存在的不确定性。概率图模型的核心思想就是不要假装确定而是把不确定性显式地建模出来。它用节点表示随机变量用边表示变量之间的依赖关系整个图就是一个联合概率分布的紧凑表示。1.2 图结构到底在表达什么很多人学概率图模型的时候卡在为什么要有图这一步。直接用联合概率分布不行吗举个例子。假设有5个二值随机变量联合分布有2^5 - 1 31个参数。如果有20个变量呢2^20 - 1超过一百万。参数量随变量数指数增长根本没法估计。但如果我们知道这些变量之间的依赖关系是稀疏的——比如每个变量只和它的邻居直接相关——那就可以用图来编码这种稀疏性。贝叶斯网络里一个变量的条件概率只依赖于它的父节点马尔可夫随机场里一个变量的条件概率只依赖于它的邻居。参数量从指数级降到线性级。这就是图的威力它把哪些变量直接相关这个先验知识编码进了模型结构里从而大幅降低了需要学习的参数量。1.3 有向图和无向图的分工概率图模型分两大阵营有向图贝叶斯网络和无向图马尔可夫随机场。有向图的边有方向A→B表示A是B的原因。联合分布可以分解为每个节点在给定父节点下的条件概率的乘积P(x₁, x₂, ..., xₙ) ∏ᵢ P(xᵢ | parents(xᵢ))无向图的边没有方向表示变量之间的相关性。联合分布用团势函数的乘积来表示需要除以一个归一化常数Z配分函数。实际用的时候怎么选如果变量之间有明确的因果方向用有向图如果只是相关性或者因果关系循环用无向图。我个人的经验是做因果推断优先有向图做图像分割、社交网络分析这类任务无向图更自然。注意有向图里不能有环这是贝叶斯网络的定义要求。如果你发现自己的因果假设里有循环要么重新审视假设要么改用无向图或者动态贝叶斯网络。2. 贝叶斯模型从先验到后验的推断逻辑2.1 贝叶斯定理不只是公式贝叶斯定理写出来就一行P(θ | D) P(D | θ) · P(θ) / P(D)但它的含义远不止一行。它说的是我们对参数的信念先验P(θ)在看到数据D之后应该更新为后验P(θ | D)。更新的力度取决于数据D在假设θ下的似然P(D | θ)。这个框架的美妙之处在于它把学习这件事从找最优参数变成了更新信念分布。频率学派说参数是固定的、未知的贝叶斯学派说参数本身就有分布数据的作用是让我们对这个分布的认识更精确。2.2 共轭先验让计算可行的关键技巧贝叶斯推断最大的计算障碍是分母P(D)P(D) ∫ P(D | θ) · P(θ) dθ这个积分在高维空间里几乎不可能解析计算。共轭先验就是来解决这个问题的。如果先验分布和后验分布属于同一个分布族那先验就叫似然的共轭先验。比如似然共轭先验后验伯努利Beta分布Beta分布多项式Dirichlet分布Dirichlet分布高斯已知方差高斯分布高斯分布泊松Gamma分布Gamma分布用共轭先验的好处是后验有闭式解不需要做数值积分。这在概率图模型里特别重要因为图模型里有很多条件概率需要推断如果每个都做数值积分计算量会爆炸。2.3 从精确推断到近似推断共轭先验能解决一部分问题但大多数真实模型里后验没有闭式解。这时候就需要近似推断。两大类方法变分推断用一个简单的分布q(θ)去近似真实后验p(θ | D)通过优化让q和p尽可能接近。接近程度用KL散度衡量。变分推断把推断问题转化成了优化问题速度快但精度受限于q的表达能力。采样方法从后验分布中采样用样本估计期望。MCMC马尔可夫链蒙特卡洛是最经典的方法理论上可以收敛到真实后验但计算量大收敛判断也麻烦。我自己的经验是数据量小、模型简单的时候用MCMC精度有保障数据量大、模型复杂的时候用变分推断速度优先。现在深度学习里的贝叶斯方法基本都是变分推断的变体。实操心得变分推断的KL散度是q||p还是p||q结果差别很大。q||p是均值寻求mode-seeking倾向于让q集中在p的一个峰上p||q是均值覆盖mass-covering倾向于让q覆盖p的所有峰。VAE里用的是q||p所以生成的样本有时候会偏保守。3. VAE当变分推断遇上深度学习3.1 VAE到底在优化什么VAE变分自编码器的核心是一个带隐变量的生成模型p(x) ∫ p(x | z) · p(z) dzx是观测数据比如一张图片z是隐变量比如图片的抽象特征p(z)是隐变量的先验通常取标准正态分布p(x | z)是解码器从隐变量生成数据。问题还是那个积分。VAE的解法是引入一个编码器q(z | x)来近似真实后验p(z | x)然后推导出一个可优化的下界——ELBO证据下界log p(x) ≥ E_q(z|x)[log p(x | z)] - KL(q(z | x) || p(z))第一项是重构误差第二项是KL散度衡量编码器输出的分布和先验的差距。最大化ELBO就是在重构得准和隐空间规整之间找平衡。3.2 重参数化技巧让采样可导VAE训练里有一个技术细节特别关键从q(z | x)采样z这个操作本身是不可导的梯度传不回去。重参数化技巧解决了这个问题。假设q(z | x)是正态分布N(μ, σ²)我们不直接从N(μ, σ²)采样而是先采一个标准正态噪声ε ~ N(0, 1)然后令z μ σ · ε。这样z的随机性来自ε而μ和σ是确定性的、可导的。梯度可以通过μ和σ传回编码器。这个技巧看起来简单但它是VAE能端到端训练的关键。没有它VAE就只能用强化学习里的策略梯度来训练方差大、收敛慢。3.3 VAE的隐空间为什么规整KL散度项强迫q(z | x)接近标准正态分布。这意味着隐空间是连续的相近的z解码出相近的x隐空间是稠密的从先验里随便采一个z解码出来都像那么回事隐空间是可插值的两个z之间的线性插值能平滑过渡这三点让VAE的隐空间特别适合做表示学习。我做过一个实验用VAE编码人脸然后在隐空间里做算术戴眼镜的z减去不戴眼镜的z加上不戴眼镜的z解码出来基本就是戴眼镜的。这种结构化的隐空间是VAE相比普通自编码器最大的优势。但代价也很明显KL散度项太强的时候解码器会偷懒生成的图片模糊。这就是VAE生成质量不如GAN的根本原因——它为了隐空间的规整性牺牲了生成分布的锐利度。踩坑记录我最早训VAE的时候重构损失用MSE结果生成的图片全是糊的。后来换成感知损失用预训练网络提取特征算距离清晰度明显提升。再后来发现MSE本身就是在假设数据服从高斯分布对图像这种分布高斯假设太弱了。4. GAN用对抗代替似然4.1 GAN绕开了什么难题GAN的思路和VAE完全不同。它不建模似然不做变分推断而是直接训练一个生成器G让它的输出分布逼近真实数据分布。怎么逼近引入一个判别器DD的任务是区分真实数据和生成数据G的任务是骗过D。两者交替训练最终G生成的样本让D无法区分。这个框架绕开了概率密度估计这个难题。我们不需要知道p(x)的具体形式只需要能从p(x)里采样真实数据本身就是样本然后让G的输出分布和它对齐。4.2 对抗训练背后的散度GAN的原始目标函数是min_G max_D E_x~p_data[log D(x)] E_z~p_z[log(1 - D(G(z)))]理论上可以证明当D最优时最小化这个目标等价于最小化真实分布和生成分布之间的JS散度。JS散度有个问题当两个分布没有重叠的时候JS散度是常数log 2梯度为0。这就是GAN训练不稳定的根源——如果真实分布和生成分布支撑集不重叠判别器完美区分生成器就得不到有效梯度。WGAN用Wasserstein距离替代JS散度解决了这个问题。Wasserstein距离即使两个分布不重叠也能提供有意义的梯度。实现上WGAN要求判别器满足Lipschitz连续性通过权重裁剪或者梯度惩罚来实现。4.3 模式崩溃GAN的顽疾模式崩溃mode collapse是GAN最让人头疼的问题生成器只生成少数几种样本覆盖不了真实分布的所有模式。原因在于生成器的目标是骗过判别器而不是覆盖整个分布。如果生成器发现生成某一种样本就能骗过判别器它就会一直生成这种样本放弃其他模式。缓解方法有不少Mini-batch discrimination让判别器看一批样本而不是单个样本如果一批样本太相似就判为假Unrolled GAN生成器更新时考虑判别器未来几步的反应Wasserstein距离提供更平滑的梯度让生成器不容易卡在单一模式多个生成器每个生成器负责不同模式我自己的经验是WGAN-GP带梯度惩罚的WGAN在大多数任务上最稳但训练速度比原始GAN慢。如果追求生成质量StyleGAN系列是目前的天花板但结构复杂训练成本高。实操心得GAN训练时判别器和生成器的更新频率很关键。原始论文是1:1交替但实践中判别器更新太频繁会导致生成器梯度消失更新太少又会让判别器太弱、生成器得不到有效信号。我一般会监控D的准确率保持在60%-80%之间比较健康。如果D准确率接近100%说明判别器太强了要降低它的学习率或者减少更新次数。5. 四个框架的横向对比与选型逻辑5.1 从建模对象看差异框架建模对象推断方式生成方式概率图模型联合分布P(X)精确/近似推断ancestral sampling贝叶斯模型后验P(θ|D)MCMC/变分推断后验预测VAE隐变量模型P(x,z)变分推断从先验采样z解码GAN生成分布P_G(x)无推断从噪声映射概率图模型和贝叶斯模型是框架VAE和GAN是具体模型。VAE可以看作概率图模型的一种有隐变量的有向图它的训练用的是贝叶斯推断里的变分推断。GAN则跳出了概率图模型的框架用对抗的方式直接对齐分布。5.2 什么时候用哪个需要可解释的因果结构概率图模型。比如医疗诊断你需要知道症状和疾病之间的依赖关系而不是一个黑箱输出概率。需要量化不确定性贝叶斯模型。比如金融风控你需要知道预测的置信区间而不是一个点估计。需要结构化隐空间VAE。比如做表示学习、数据压缩、异常检测VAE的隐空间有良好的几何性质。需要高质量生成GAN。比如图像生成、风格迁移、超分辨率GAN生成的样本锐利度高。需要密度估计VAE或者标准化流normalizing flow。GAN不提供密度VAE提供的是ELBO下界标准化流提供精确密度。5.3 融合趋势最近几年的趋势是融合。比如VAE-GAN用GAN的判别器增强VAE的重构质量Adversarial VAE在VAE的ELBO上加对抗损失Normalizing Flow VAE用可逆变换增强后验的表达能力Diffusion Model本质上是层次化VAE但用去噪得分匹配训练生成质量超过GANDiffusion Model值得单独提一句。它把生成过程拆成很多步每步加一点噪声然后学习逆过程。训练目标简单就是去噪生成质量高但采样慢。现在很多工作在做加速采样比如DDIM、一致性模型。6. 实际项目中的踩坑与调参经验6.1 VAE的KL散度权重VAE的损失是重构损失加KL散度。KL项的权重β直接影响生成质量和隐空间规整度的平衡。β1是标准VAE。β1β-VAE会得到更解耦的隐表示但重构质量下降。β1会得到更清晰的重构但隐空间可能不够规整。我的经验是做表示学习用β4到10做生成用β0.5到1。如果发现隐空间有空洞采样出来解码是噪声说明KL项太弱了要加大β。6.2 GAN的梯度惩罚系数WGAN-GP的梯度惩罚系数λ一般取10。这个值不是随便定的它对应着Lipschitz常数和Wasserstein距离估计之间的权衡。λ太小Lipschitz约束不够Wasserstein距离估计不准λ太大梯度惩罚主导损失生成质量下降。我试过λ从1到10010确实是最稳的。如果训练不稳定可以先检查梯度惩罚项的实际值它应该接近0如果很大说明判别器不满足Lipschitz约束。6.3 概率图模型的结构学习概率图模型最难的不是参数学习是结构学习——怎么从数据里学出图结构。基于评分的方法如BIC、BDeu在小规模问题上可行但变量多了就是NP难。基于约束的方法如PC算法用条件独立性检验来建边但对检验的可靠性很敏感。我的建议是如果领域知识足够手动定结构如果不够先用基于约束的方法得到一个初始结构再用领域知识修正。完全自动的结构学习在变量超过20个的时候基本不可靠。6.4 贝叶斯模型的先验选择先验的选择是贝叶斯建模里最艺术的部分。无信息先验如均匀分布看起来客观但在高维空间里会导致后验不正则化。Jeffreys先验用Fisher信息矩阵来构造有不变性但计算复杂。弱信息先验是实践中最常用的给一个宽的正态分布或者柯西分布既不完全无信息也不强加太多主观信念。我个人的原则是如果对参数有明确的领域知识用信息先验如果没有用弱信息先验如果数据量足够大先验的影响会被似然淹没这时候用什么先验差别不大。注意先验的选择会影响后验的尾部行为。如果做风险敏感的任务比如金融尾部行为很重要要特别小心先验的选择。正态先验的尾部是指数衰减的柯西先验的尾部是多项式衰减的后者更厚尾对异常值更鲁棒。7. 从理论到代码几个关键实现细节7.1 VAE的损失函数实现import torch import torch.nn as nn import torch.nn.functional as F class VAE(nn.Module): def __init__(self, input_dim, hidden_dim, latent_dim): super().__init__() self.encoder nn.Sequential( nn.Linear(input_dim, hidden_dim), nn.ReLU(), nn.Linear(hidden_dim, hidden_dim), nn.ReLU() ) self.mu nn.Linear(hidden_dim, latent_dim) self.logvar nn.Linear(hidden_dim, latent_dim) self.decoder nn.Sequential( nn.Linear(latent_dim, hidden_dim), nn.ReLU(), nn.Linear(hidden_dim, hidden_dim), nn.ReLU(), nn.Linear(hidden_dim, input_dim), nn.Sigmoid() ) def reparameterize(self, mu, logvar): std torch.exp(0.5 * logvar) eps torch.randn_like(std) return mu eps * std def forward(self, x): h self.encoder(x) mu, logvar self.mu(h), self.logvar(h) z self.reparameterize(mu, logvar) x_recon self.decoder(z) return x_recon, mu, logvar def vae_loss(x_recon, x, mu, logvar, beta1.0): recon_loss F.binary_cross_entropy(x_recon, x, reductionsum) kl_loss -0.5 * torch.sum(1 logvar - mu.pow(2) - logvar.exp()) return recon_loss beta * kl_loss这里logvar而不是var是为了保证方差为正。重参数化里std exp(0.5 * logvar)就是σ。7.2 GAN的训练循环def train_gan(G, D, dataloader, optimizer_G, optimizer_D, latent_dim, n_critic5, clip_value0.01): for real_data, _ in dataloader: batch_size real_data.size(0) # 训练判别器 for _ in range(n_critic): z torch.randn(batch_size, latent_dim) fake_data G(z).detach() real_score D(real_data) fake_score D(fake_data) d_loss -torch.mean(real_score) torch.mean(fake_score) optimizer_D.zero_grad() d_loss.backward() optimizer_D.step() # WGAN的权重裁剪 for p in D.parameters(): p.data.clamp_(-clip_value, clip_value) # 训练生成器 z torch.randn(batch_size, latent_dim) fake_data G(z) g_loss -torch.mean(D(fake_data)) optimizer_G.zero_grad() g_loss.backward() optimizer_G.step()这是WGAN的简化实现。n_critic5表示判别器更新5次生成器更新1次。权重裁剪是WGAN的原始做法实践中WGAN-GP用梯度惩罚替代效果更好但计算量更大。7.3 贝叶斯推断的变分实现class BayesianLinear(nn.Module): def __init__(self, in_features, out_features): super().__init__() self.in_features in_features self.out_features out_features # 权重的变分参数 self.weight_mu nn.Parameter(torch.randn(out_features, in_features) * 0.1) self.weight_logvar nn.Parameter(torch.randn(out_features, in_features) * 0.1) # 偏置的变分参数 self.bias_mu nn.Parameter(torch.randn(out_features) * 0.1) self.bias_logvar nn.Parameter(torch.randn(out_features) * 0.1) def forward(self, x): # 采样权重 weight_std torch.exp(0.5 * self.weight_logvar) weight_eps torch.randn_like(weight_std) weight self.weight_mu weight_eps * weight_std bias_std torch.exp(0.5 * self.bias_logvar) bias_eps torch.randn_like(bias_std) bias self.bias_mu bias_eps * bias_std return F.linear(x, weight, bias) def kl_loss(self): # 变分后验和标准正态先验的KL散度 weight_kl -0.5 * torch.sum( 1 self.weight_logvar - self.weight_mu.pow(2) - self.weight_logvar.exp() ) bias_kl -0.5 * torch.sum( 1 self.bias_logvar - self.bias_mu.pow(2) - self.bias_logvar.exp() ) return weight_kl bias_kl贝叶斯神经网络的核心就是把权重从确定值变成分布前向传播时采样权重损失函数里加KL散度项。训练时用重参数化技巧保证可导。8. 几个容易混淆的概念澄清8.1 变分推断和变分自编码器的关系变分推断是一种通用的近似推断方法VAE是变分推断在深度学习里的一个应用。VAE的编码器就是变分推断里的变分分布q(z | x)解码器是生成模型p(x | z)。VAE的创新在于用神经网络参数化q和p并且用重参数化技巧实现了端到端训练。但变分推断不一定要用神经网络。传统的变分推断用指数族分布来构造q用坐标上升法优化。VAE只是把q和p都换成了神经网络。8.2 生成模型和判别模型的区别判别模型建模P(y | x)给定输入预测输出。生成模型建模P(x)或者P(x, y)可以生成新样本。但要注意生成模型不一定能生成高质量样本。比如朴素贝叶斯是生成模型但它生成的样本质量很差。VAE和GAN是生成模型但它们的生成能力来自神经网络的表达能力而不是生成模型这个标签本身。8.3 隐变量模型和降维的关系隐变量模型假设观测数据是由一些不可见的隐变量生成的。如果隐变量维度低于观测维度那隐变量模型就起到了降维的作用。PCA可以看作一个线性高斯隐变量模型VAE是非线性的版本。但隐变量模型不只是降维。它还可以做密度估计、异常检测、缺失值填补。隐变量的含义也不一定是低维表示它可以是任何有助于建模观测数据的中间变量。8.4 贝叶斯深度学习和传统深度学习的区别传统深度学习用最大似然或者最大后验来估计参数得到一个点估计。贝叶斯深度学习用变分推断或者MCMC来估计参数的后验分布得到一个分布。区别在于不确定性量化。传统深度学习给一个预测但不知道这个预测有多可靠。贝叶斯深度学习给一个预测分布可以告诉你置信区间。但贝叶斯深度学习的计算成本高而且先验的选择会影响结果。实践中如果只是追求预测精度传统深度学习往往就够了如果需要不确定性量化才值得上贝叶斯方法。9. 学习路径与资源建议如果你是从零开始我建议按这个顺序第一步概率论基础。条件概率、贝叶斯定理、期望、方差、常见分布。不用太深但基本概念要清楚。第二步概率图模型入门。从贝叶斯网络开始理解有向无环图、条件独立性、d-分离。然后看马尔可夫随机场理解无向图和团势函数。推荐Koller的《Probabilistic Graphical Models》书很厚但前几章读完就能建立直觉。第三步贝叶斯推断。理解先验、后验、共轭先验、MCMC、变分推断。推荐Bishop的《Pattern Recognition and Machine Learning》第10章和第11章。第四步VAE。从ELBO推导开始理解重参数化技巧然后看原始论文和后续改进β-VAE、VQ-VAE。代码实现一遍比看十遍论文都有用。第五步GAN。从原始GAN开始理解对抗训练和JS散度。然后看WGAN、WGAN-GP、StyleGAN。GAN的论文很多但核心思想就那几个不要被各种变体迷惑。第六步扩散模型。如果对生成模型感兴趣扩散模型是现在的前沿。理解前向加噪和反向去噪的过程然后看DDPM、DDIM、一致性模型。我自己的学习路径是反过来的先用了VAE和GAN发现很多地方不理解才回头补的概率图模型和贝叶斯推断。这种从用到学的路径也有好处就是知道每个概念在实际中对应什么问题。但缺点是知识体系不系统容易有盲区。最后分享一个小技巧学这些模型的时候不要只看公式要动手推导一遍。ELBO的推导、GAN的目标函数、重参数化技巧自己推一遍和看别人推一遍理解深度完全不一样。我当初推ELBO推了三遍才真正搞懂每一项的含义但搞懂之后再看VAE的各种变体就一目了然了。