SVI第四部分:提示和技巧¶
pyro.ai/examples/svi_part_iv.html
导致这一个的三个SVI教程(第一部分, 第二部分, & 第三部分)通过使用Pyro做变分推断所涉及的各个步骤。在这个过程中,我们定义了模型和指南(即,变分分布),设置了变分目标(特别是埃尔博斯),以及构造的优化器(pyro.optim).所有这些机制的效果是将贝叶斯推理作为随机优化问题.
这些都非常有用,但是为了达到我们的最终目标——学习模型参数、推断近似后验概率、使用后验预测分布进行预测等。—我们需要成功解决这个优化问题。取决于特定问题的细节——例如潜在空间的维度,我们是否有离散的潜在变量,等等——这可能容易也可能困难。在本教程中,我们涵盖了一些技巧和诀窍,我们希望这些技巧和诀窍对在Pyro中进行变分推理的用户普遍有用。厄尔布没有收敛!?碰到NaNs!?看看下面可能的解决方案!
Pyro论坛¶
如果您在阅读完本教程后仍有优化方面的问题,请不要犹豫,在我们的论坛!
1.从小的学习率开始¶
虽然大的学习率可能适用于某些问题,但从小的学习率开始通常是好的做法,比如10−3或者10−4:
optimizer = pyro.optim.Adam({"lr": 0.001})
这是因为ELBO渐变随机的和潜在的高方差,所以大的学习率会很快导致模型/指南参数空间的区域在数值上不稳定或者是不期望的。
一旦使用较小的学习速率实现了稳定的ELBO优化,就可以尝试较大的学习速率。这通常是一个好主意,因为过小的学习率会导致不良的优化。特别是小的学习率会导致陷入ELBO的局部最优。
2.默认情况下使用Adam或ClippedAdam¶
使用圣经》和《古兰经》传统中)亚当(人类第一人的名字或者克利佩达姆做随机变分推断时默认。注意到ClippedAdam
只是对Adam
这为学习率衰减和梯度削波提供了内置支持。
这些优化算法在变分推理的上下文中通常表现良好的基本原因是,当优化问题非常随机时,它们通过每个参数动量提供的平滑通常是必不可少的。注意,在SVI,随机性可能来自于对潜在变量的抽样,来自于二次抽样数据,或者两者都有。
在某些情况下,除了调整学习率之外,可能还需要调整betas
控制动量的超参数Adam
。特别是对于非常随机的模型,使用更高的值可能是有意义的β1:
betas = (0.95, 0.999)
代替
betas = (0.90, 0.999)
3.考虑使用递减的学习率¶
虽然在优化开始时,当您远离最优值并希望采取较大的梯度步骤时,适度较大的学习速率可能是有用的,但稍后具有较小的学习速率通常是有用的,这样您就不会在最优值附近过度跳跃而不收敛。一种方法是使用学习率调度器假如通过烟火。有关用法的示例,请参见代码片段这里。另一种方便的方法是使用克利佩达姆优化器内置了对学习率衰减的支持,通过lrd
参数:
num_steps = 1000
initial_lr = 0.001
gamma = 0.1 # final learning rate will be gamma * initial_lr
lrd = gamma ** (1 / num_steps)
optim = pyro.optim.ClippedAdam({'lr': initial_lr, 'lrd': lrd})
4.确保您的模型和指南发行版具有相同的支持¶
假设你有一个分布model
有约束支持,例如对数正态分布,在正实轴上有支持:
def model():
pyro.sample("x", dist.LogNormal(0.0, 1.0))
然后你需要确保随行的sample
中的站点guide
具有相同的支持:
def good_guide():
loc = pyro.param("loc", torch.tensor(0.0))
pyro.sample("x", dist.LogNormal(loc, 1.0))
如果您未能做到这一点,并使用例如以下不可接受的指南:
def bad_guide():
loc = pyro.param("loc", torch.tensor(0.0))
# Normal may sample x < 0
pyro.sample("x", dist.Normal(loc, 1.0))
你可能会很快遇到NaNs。这是因为log_prob
对数正态分布的一个样本x
这满足了x<0
未定义,并且bad_guide
很可能会产生这样的样本。
5.约束需要约束的参数¶
同样,您需要确保用于实例化发行版的参数是有效的;否则你会很快碰到NaNs。例如scale
正态分布的参数必须为正。因此如下bad_guide
有问题:
def bad_guide():
scale = pyro.param("scale", torch.tensor(1.0))
pyro.sample("x", dist.Normal(0.0, scale))
而下面的good_guide
正确使用约束来确保积极性:
from pyro.distributions import constraints
def good_guide():
scale = pyro.param("scale", torch.tensor(0.05),
constraint=constraints.positive)
pyro.sample("x", dist.Normal(0.0, scale))
6.如果您在构建自定义指南时遇到问题,请使用自动指南¶
为了使模型/向导对达到稳定的优化,需要满足许多条件,其中一些我们已经在上面讨论过了。有时很难诊断数值不稳定或收敛性差的原因。除了其他原因,这是因为基本问题可能出现在许多不同的地方:在模型中,在指南中,或者在优化算法或超参数的选择中。
有时问题实际上在你的模型中,即使你认为它在指南中。相反,有时问题出在你的指南上,即使你认为问题出在模型或其他地方。由于这些原因,在您尝试确定潜在问题时,减少移动部件的数量会有所帮助。一种方便的方法是用自动制导.
例如,如果模型中的所有潜在变量都是连续的,您可以尝试使用pyro.infer .自动正常导游。或者,您可以使用映射推理而不是全面的变分推理。参见最大似然法/地图教程了解更多细节。一旦你有了映射推理,就有充分的理由相信你的模型设置是正确的(至少就基本的数值稳定性而言)。如果你对获得近似的后验分布感兴趣,你现在可以继续使用完全成熟的SVI。实际上,操作的自然顺序可能会使用以下越来越灵活的自动引导序列:
自动增量 → 自动正常 → AutoLowRankMultivariateNormal
如果您发现您想要一个更灵活的指南,或者您想要更好地控制指南的确切定义方式,此时您可以着手构建一个自定义指南。实现这一点的一个方法是利用简易指南,它在完全自定义指南的控制和自动指南的自动化之间取得了平衡。
还要注意,autoguides提供了几种初始化策略,在某些情况下,为了获得良好的优化性能,可能有必要尝试这些策略。控制初始化行为的一种方法是使用init_loc_fn
。例如,的用法init_loc_fn
,包括简易指南API的示例用法,请参见这里.
7.参数初始化很重要:初始化引导分布以具有低方差¶
优化问题中的初始化可以决定找到一个好的解决方案还是灾难性的失败。很难提出一套完整的初始化良好实践,因为好的初始化方案通常与问题密切相关。在随机变分推理的背景下,初始化你的引导分布通常是个好主意,这样它们就有了低方差。这是因为用于优化ELBO的ELBO梯度是随机的。如果在ELBO优化开始时获得的ELBO梯度显示出较高的方差,则可能会导致数值不稳定或参数空间的其他不良区域。防范这种潜在危险的一种方法是密切关注指南中控制方差的参数。例如,我们通常认为这是一个合理的初始化指南:
from pyro.distributions import constraints
def good_guide():
scale = pyro.param("scale", torch.tensor(0.05),
constraint=constraints.positive)
pyro.sample("x", dist.Normal(0.0, scale))
虽然下面的高方差指南很可能会导致问题:
def bad_guide():
scale = pyro.param("scale", torch.tensor(12345.6),
constraint=constraints.positive)
pyro.sample("x", dist.Normal(0.0, scale))
请注意,自动制导的初始方差可以用init_scale
参数,参见例如这里为AutoNormal
.
8.探索由以下因素控制的权衡num_particles
小批量等。¶
如果您的ELBO表现出很大的差异,优化可能会很困难。缓解这个问题的一种方法是增加用于计算每个随机ELBO估计值的粒子数量:
elbo = pyro.infer.Trace_ELBO(num_particles=10,
vectorize_particles=True)
(注意,要使用vectorized_particles=True
您需要确保您的模型和指南正确矢量化;参见张量形状教程最佳实践。)这导致以更多计算为代价的较低的方差梯度。如果您正在进行数据二次采样,小批量大小提供了类似的权衡:较大的小批量大小以更多计算为代价减少了方差。虽然什么是最好的取决于问题,它通常是值得采取更多的梯度步骤与更少的粒子比更少的梯度步骤与更多的粒子。一个重要的警告是,当你在GPU上运行时,在这种情况下(至少对某些模型而言)增加的成本num_particles
或者你的小批量可能是次线性的,在这种情况下增加num_particles
可能更有吸引力。
9.使用TraceMeanField_ELBO
如果适用¶
基本的ELBO
在Pyro中实现,Trace_ELBO使用随机样本来估计KL散度项。当解析KL发散可用时,您可以通过使用解析KL发散来降低ELBO方差。此功能由提供TraceMeanField_ELBO.
10.考虑正常化你的ELBO¶
默认情况下,Pyro会计算一个非标准化的ELBO,也就是说,它会计算一个数量,该数量是对作为条件的完整数据集计算的对数证据的下限。对于大型数据集,这可能是大量的。由于计算机使用有限精度(例如,32位浮点)来进行算术运算,因此大数值可能会对数值稳定性造成问题,因为它们会导致精度损失、下溢/上溢等。因此,在许多情况下,将ELBO规格化,使其大致为一阶是有帮助的。这也有助于大致了解你的ELBO号码有多好。例如,如果我们有N尺寸的数据点D(例如N维数的实值向量D)那么我们通常期望订购一个合理优化ELBO乘以N\乘以D。因此,如果我们用因子重正化我们的ELBO乘以N\乘以D我们期待一阶的厄尔布。虽然这只是一个粗略的经验法则,但如果我们使用这种标准化并获得ELBO值,如−123.4或者1234.5那么可能出了问题:也许我们的模型被严重错误地指定了;也许我们的初始化非常糟糕,等等。有关如何通过归一化常数缩放ELBO的详细信息,请参见本教程.
11.注意尺度¶
数字的规模很重要。它们之所以重要,至少有两个重要原因:I)规模可以决定一个特定初始化方案的成败;ii)如前一节所述,比例会对数值精度和稳定性产生影响。
具体来说,假设你正在做线性回归,也就是说,你正在学习一个线性映射的形式Y=W@X。数据通常带有特定的单位。例如协变量的某些组成部分X可能以美元为单位(例如房价),而其他可能以密度为单位(例如每平方英里的居民数)。也许第一个协变量的典型值如下105,而第二个协变量的典型值如下102。当您遇到跨越多个数量级的数字时,您应该始终保持警惕。在许多情况下,将事物规范化是有意义的,这样它们就是有序的统一体。例如,您可能以100,000美元为单位来衡量房价。
这些类型的数据转换对下游的建模和推理有很多好处。例如,如果您已经对所有协变量进行了适当的归一化,那么为您的权重设置一个简单的各向同性先验可能是合理的
pyro.sample("W", dist.Normal(torch.zeros(2), torch.ones(2)))
不必为不同的协变量指定不同的先验协方差
prior_scale = torch.tensor([1.0e-5, 1.0e-2])
pyro.sample("W", dist.Normal(torch.zeros(2), prior_scale))
还有其他好处。现在,为您的指南初始化适当的参数变得更加容易。现在,更有可能的是自动制导会解决你的问题。
12.保持验证启用¶
默认情况下,Pyro会启用有助于调试模型和指南的验证逻辑。例如,当分布参数无效时,验证逻辑会通知您。除非您有很好的理由这样做,否则请保持验证逻辑处于启用状态。一旦您对模型和推理过程感到满意,您可能希望使用pyro.enable _验证.
类似地,在ELBOs
这是一个好主意
strict_enumeration_warning=True
当你枚举离散的潜在变量时。
13.张量形状误差¶
如果你遇到张量形状错误,请确保你已经仔细阅读对应教程.
14.如果可能,列举离散的潜在变量¶
如果你的模型包含离散的潜在变量,精确地列举它们是有意义的,因为这可以显著地减少ELBO方差。有关更多讨论,请参见对应教程.
15.一些复杂的模型可以受益于KL退火¶
ELBO的特定形式编码了经由期望对数似然项的模型拟合和经由KL散度的先验正则化项之间的折衷。在某些情况下,KL发散会成为一个障碍,使得很难找到好的最优解。在这些情况下,它有助于在优化期间退火KL发散项的相关强度。有关进一步的讨论,请参见深度马尔可夫模型教程.
16.防御性地考虑剪裁渐变或约束参数¶
模型或指南中的某些参数可能控制对数值问题敏感的分布参数。例如,在concentration
和rate
定义的参数微克分布可能表现出这种敏感性。在这些情况下,修剪梯度或防御性地约束参数可能是有意义的。看见这段代码片段这是一个渐变剪辑的例子。对于“防御性”参数约束的简单示例,考虑concentration
的参数Gamma
分销。该参数必须为正数:concentration
> 0.如果我们想确保concentration
远离零,我们可以使用param
带有适当约束的语句:
from pyro.distributions import constraints
concentration = pyro.param("concentration", torch.tensor(0.5),
constraints.greater_than(0.001))
这些技巧有助于确保模型和引导线远离参数空间中数值危险的部分。