有效地采样具有不同sigma(协方差)矩阵

时间:2018-05-04 12:50:39

标签: stan rstan pystan

我是斯坦的新手,所以希望你能指出我正确的方向。我会根据自己的情况确保我们在同一页面上...

如果我有一组单变量法线,那么文档会告诉我:

y ~ normal(mu_vec, sigma);

提供与非版本化版本相同的模型:

for (n in 1:N)
    y[n] ~ normal(mu_vec[n], sigma);

但是矢量化版本(更多?)更快。好的,很好,很有道理。

所以第一个问题是:是否有可能在单变量正态情况下利用这种向量化加速,其中样本的musigma都随着向量中的位置而变化。即如果mu_vecsigma_vec都是向量(在前一种情况下sigma是标量),那就是:

y ~ normal(mu_vec, sigma_vec);

相当于:

for (n in 1:N)
    y[n] ~ normal(mu_vec[n], sigma_vec[n]);

如果是,那么是否有类似的加速?

确定。这是热身。真正的问题是如何最好地接近上述的多变量等价物。

在我的特定情况下,我对某些变量N的双变量数据进行了y次观察,我将其存储在N x 2矩阵中。 (对于数量级,我的用例中N约为1000。)

我的信念是每个观察的每个成分的平均值是0,并且每个成分的每个成分的stdev都是1(并且我很乐意将它们硬编码为这样)。然而,我的观点是相关性(rho)从观察到观察变化为另一个观察变量{(1}}(存储在x元素向量中)的(简单)函数。例如,我们可能会对Nrho[n] = 2*inverse_logit(beta * x[n]) - 1,我们的目标是从我们的数据中了解n in 1:N。即第beta次观测的协方差矩阵将是:

n

我的问题是在STAN模型中将它们组合在一起的最佳方式是什么,这样它就不会变慢?是否有[1, rho[n]] [rho[n], 1 ] 分布的矢量化版本,以便我可以将其指定为:

multi_normal

或者也许是其他类似的配方?或者我需要写:

y ~ multi_normal(vector_of_mu_2_tuples, vector_of_sigma_matrices)

在较早的区块中设置for (n in 1:N) y[n] ~ multi_normal(vector_of_mu_2_tuples[n], vector_of_sigma_matrices[n]) vector_of_sigma_matrices之后?

提前感谢任何指导!

编辑以添加代码

使用python,我可以按照我的问题精神生成数据,如下所示:

vector_of_mu_2_tuples

然后使用以下方法实际生成数据:

import numpy as np
import pandas as pd
import pystan as pys
import scipy as sp
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import make_axes_locatable
import seaborn as sns

def gen_normal_data(N, true_beta, true_mu, true_stdevs):
    N = N

    true_beta = true_beta
    true_mu = true_mu
    true_stdevs = true_stdevs

    drivers = np.random.randn(N)
    correls = 2.0 * sp.special.expit(drivers*true_beta)-1.0

    observations = []
    for i in range(N):
        covar = np.array([[true_stdevs[0]**2, true_stdevs[0] * true_stdevs[1] * correls[i]],
                          [true_stdevs[0] * true_stdevs[1] * correls[i], true_stdevs[1]**2]])
        observations.append(sp.stats.multivariate_normal.rvs(true_mu, covar, size=1).tolist())
    observations = np.array(observations)

    return {
        'N': N,
        'true_mu': true_mu,
        'true_stdev': true_stdevs,
        'y': observations,
        'd': drivers,
        'correls': correls
    }

这里是数据集的样子(左侧窗格中normal_data = gen_normal_data(100, 1.5, np.array([1., 5.]), np.array([2., 5.])) 的{​​{1}}散点图和右侧窗格中的y的散点图...所以这个想法是correls越接近drivers driver越高1越高,correl越接近driver。所以我希望左侧窗格上的红点是“左上”到“右上方”,“蓝点”是“上下左右”和“#34”,实际上它们是:< / p>

-1

scatter plots

使用蛮力方法,我可以设置一个如下所示的STAN模型:

correl

这很合适:

fig, axes = plt.subplots(1, 2, figsize=(15, 6))
x = normal_data['y'][:, 0]
y = normal_data['y'][:, 1]
correls = normal_data['correls']
drivers = normal_data['d']

for ax, colordata, cmap in zip(axes, [correls, drivers], ['coolwarm', 'viridis']):
    color_extreme = max(abs(colordata.max()), abs(colordata.min()))
    sc = ax.scatter(x, y, c=colordata, lw=0, cmap=cmap, vmin=-color_extreme, vmax=color_extreme)
    divider = make_axes_locatable(ax)
    cax = divider.append_axes('right', size='5%', pad=0.05)
    fig.colorbar(sc, cax=cax, orientation='vertical')
fig.tight_layout()

trace for beta

但我希望有人可以指出我正确的方向,以便边缘化&#34;我们将二元正态分解为一对可以使用相关性混合的独立法线的方法。我需要这个的原因是,在我的实际使用案例中,这两个维度都是肥胖的。我很乐意将其建模为student-t发行版,但问题是STAN只允许指定一个model_naked = pys.StanModel( model_name='naked', model_code=""" data { int<lower=0> N; vector[2] true_mu; vector[2] true_stdev; real d[N]; vector[2] y[N]; } parameters { real beta; } transformed parameters { } model { real rho[N]; matrix[2, 2] cov[N]; for (n in 1:N) { rho[n] = 2.0*inv_logit(beta * d[n]) - 1.0; cov[n, 1, 1] = true_stdev[1]^2; cov[n, 1, 2] = true_stdev[1] * true_stdev[2] * rho[n]; cov[n, 2, 1] = true_stdev[1] * true_stdev[2] * rho[n]; cov[n, 2, 2] = true_stdev[2]^2; } beta ~ normal(0, 10000); for (n in 1:N) { y[n] ~ multi_normal(true_mu, cov[n]); } } """ ) (每个维度不能指定一个),所以我认为我需要找到一种将fit_naked = model_naked.sampling(data=normal_data, iter=1000, chains=2)f = fit_naked.plot(); f.tight_layout() 分解为一对独立nu的方法,以便我可以为每个维度单独设置自由度。

2 个答案:

答案 0 :(得分:1)

单变量正态分布确实接受任何或所有参数的向量,并且比使用标量参数将N观察值循环到N次更快。

然而,加速只是线性的,因为计算都是相同的,但如果你只调用一次,它只需要分配一次内存。整个墙上时间更多地受到您必须执行的功能评估数量的影响,每个MCMC迭代最多2^10 - 1(默认情况下),但是您是否达到最大树深度取决于后验分布的几何你试图从中抽样,反过来,它取决于你所关注的所有数据。

双变量正态分布可以是written,作为第一变量的边际单变量正态分布的乘积和给定第一变量的第二变量的条件单变量正态分布。在Stan代码中,我们可以利用逐元素乘法和除法来编写其对数密度,如

target += normal_lpdf(first_variable | first_means, first_sigmas);
target += normal_lpdf(second_variable | second_means + 
  rhos .* first_sigmas ./ second_sigmas .* (first_variable - first_means),
  second_sigmas .* sqrt(1 - square(rhos)));

不幸的是,Stan中更常见的多元正态分布没有输入协方差矩阵数组的实现。

答案 1 :(得分:1)

这还没有完全回答你的问题,但你可以通过删除一堆冗余计算并将比例转换为使用tanh而不是缩放逆logit来提高程序的效率。我已经摆脱了缩放比例,只是使用较小的测试版,但是我把它留下来以便得到相同的结果。

data {
  int<lower=0> N;
  vector[2] mu;
  vector[2] sigma;
  vector[N] d;
  vector[2] y[N];
}
parameters {
  real beta;
}
transformed data {
  real var1 = square(sigma[1]);
  real var2 = square(sigma[2]);
  real covar12 = sigma[1] * sigma[2];
  vector[N] d_div_2 = d * 0.5;
}
model {
  // note: tanh(u) = 2 * inv_logit(u / 2) - 1
  vector[N] rho = tanh(beta * d_div_2);
  matrix[2, 2] Sigma;
  Sigma[1, 1] = var1;
  Sigma[2, 2] = var2;
  // only reassign what's necessary with minimal recomputation
  for (n in 1:N) {
    Sigma[1, 2] = rho[n] * covar12;
    Sigma[2, 1] = Sigma[1, 2];
    y[n] ~ multi_normal(true_mu, Sigma);
  }
  // weakly informative priors fit more easily
  beta ~ normal(0, 8);
}

您还可以通过将Cholesky分解作为rho和其他固定值的函数来计算并使用它 - 它会在多元法线中保存求解器步骤。

您拥有的另一个选择是直接写出multi-student-t而不是使用我们的内置实现。由于整个操作在矩阵求解中占据主导地位,因此内置可能不会快得多。