优化Stan / rstan中的高斯过程

时间:2018-02-02 02:24:38

标签: r stan rstan rstanarm

我最近遇到过高斯过程模型,碰巧认为它们可能是我在实验室中一直在解决的问题的解决方案。我有一个关于Cross Validated的开放和相关的问题,但我想从我的编程问题中分离出我的建模/数学问题。因此,这第二个相关的帖子。如果了解更多关于我的问题的背景会有所帮助,虽然这里是我打开的链接CV question

这是我的stan代码,对应于我的简历中提供的更新的协方差函数:

functions{
    //covariance function for main portion of the model
    matrix main_GP(
        int Nx,
        vector x,
        int Ny,
        vector y, 
        real alpha1,
        real alpha2,
        real alpha3,
        real rho1,
        real rho2,
        real rho3,
        real rho4,
        real rho5,
        real HR_f,
        real R_f){
                    matrix[Nx, Ny] K1;
                    matrix[Nx, Ny] K2;
                    matrix[Nx, Ny] K3;
                    matrix[Nx, Ny] Sigma;

                    //specifying random Gaussian process that governs covariance matrix
                    for(i in 1:Nx){
                        for (j in 1:Ny){
                            K1[i,j] = alpha1*exp(-square(x[i]-y[j])/2/square(rho1));
                        }
                    }

                    //specifying random Gaussian process incorporates heart rate
                    for(i in 1:Nx){
                        for(j in 1:Ny){
                            K2[i, j] = alpha2*exp(-2*square(sin(pi()*fabs(x[i]-y[j])*HR_f))/square(rho2))*
                            exp(-square(x[i]-y[j])/2/square(rho3));
                        }
                    }

                    //specifying random Gaussian process incorporates heart rate as a function of respiration
                    for(i in 1:Nx){
                        for(j in 1:Ny){
                            K3[i, j] = alpha3*exp(-2*square(sin(pi()*fabs(x[i]-y[j])*HR_f))/square(rho4))*
                            exp(-2*square(sin(pi()*fabs(x[i]-y[j])*R_f))/square(rho5));
                        }
                    }

                    Sigma = K1+K2+K3;
                    return Sigma;
                }
    //function for posterior calculations
    vector post_pred_rng(
        real a1,
        real a2,
        real a3,
        real r1, 
        real r2,
        real r3,
        real r4,
        real r5,
        real HR,
        real R,
        real sn,
        int No,
        vector xo,
        int Np, 
        vector xp,
        vector yobs){
                matrix[No,No] Ko;
                matrix[Np,Np] Kp;
                matrix[No,Np] Kop;
                matrix[Np,No] Ko_inv_t;
                vector[Np] mu_p;
                matrix[Np,Np] Tau;
                matrix[Np,Np] L2;
                vector[Np] yp;

    //--------------------------------------------------------------------
    //Kernel Multiple GPs for observed data
    Ko = main_GP(No, xo, No, xo, a1, a2, a3, r1, r2, r3, r4, r5, HR, R);
    Ko = Ko + diag_matrix(rep_vector(1, No))*sn;

    //--------------------------------------------------------------------
    //kernel for predicted data
    Kp = main_GP(Np, xp, Np, xp, a1, a2, a3, r1, r2, r3, r4, r5, HR, R);
    Kp = Kp + diag_matrix(rep_vector(1, Np))*sn;

    //--------------------------------------------------------------------
    //kernel for observed and predicted cross 
    Kop = main_GP(No, xo, Np, xp, a1, a2, a3, r1, r2, r3, r4, r5, HR, R);

    //--------------------------------------------------------------------
    //Algorithm 2.1 of Rassmussen and Williams... 
    Ko_inv_t = Kop'/Ko;
    mu_p = Ko_inv_t*yobs;
    Tau=Kp-Ko_inv_t*Kop;
    L2 = cholesky_decompose(Tau);
    yp = mu_p + L2*rep_vector(normal_rng(0,1), Np);
    return yp;
    }
}

data { 
    int<lower=1> N1;
    int<lower=1> N2;
    vector[N1] X; 
    vector[N1] Y;
    vector[N2] Xp;
    real<lower=0> mu_HR;
    real<lower=0> mu_R;
}

transformed data { 
    vector[N1] mu;
    for(n in 1:N1) mu[n] = 0;
}

parameters {
    real loga1;
    real loga2;
    real loga3;
    real logr1;
    real logr2;
    real logr3;
    real logr4;
    real logr5;
    real<lower=.5, upper=3.5> HR;
    real<lower=.05, upper=.75> R;
    real logsigma_sq;
}

transformed parameters {
    real a1 = exp(loga1);
    real a2 = exp(loga2);
    real a3 = exp(loga3);
    real r1 = exp(logr1);
    real r2 = exp(logr2);
    real r3 = exp(logr3);
    real r4 = exp(logr4);
    real r5 = exp(logr5);
    real sigma_sq = exp(logsigma_sq);
}

model{ 
    matrix[N1,N1] Sigma;
    matrix[N1,N1] L_S;

    //using GP function from above 
    Sigma = main_GP(N1, X, N1, X, a1, a2, a3, r1, r2, r3, r4, r5, HR, R);
    Sigma = Sigma + diag_matrix(rep_vector(1, N1))*sigma_sq;

    L_S = cholesky_decompose(Sigma);
    Y ~ multi_normal_cholesky(mu, L_S);

    //priors for parameters
    loga1 ~ student_t(3,0,1);
    loga2 ~ student_t(3,0,1);
    loga3 ~ student_t(3,0,1);
    logr1 ~ student_t(3,0,1);
    logr2 ~ student_t(3,0,1);
    logr3 ~ student_t(3,0,1);
    logr4 ~ student_t(3,0,1);
    logr5 ~ student_t(3,0,1);
    logsigma_sq ~ student_t(3,0,1);
    HR ~ normal(mu_HR,.25);
    R ~ normal(mu_R, .03);
}

generated quantities {
    vector[N2] Ypred;
    Ypred = post_pred_rng(a1, a2, a3, r1, r2, r3, r4, r5, HR, R, sigma_sq, N1, X, N2, Xp, Y);
}

我对内核中包含的参数进行了修改,一些参数化速度提高了一些(在某些情况下速度提高了两倍,但即使在这些情况下仍然会产生相对较慢的链)。

我正在尝试使用受污染部分之前和之后15秒的数据(以3.33 Hz采样,因此100个总数据点)预测值为3.5s的数据值(以10 Hz采样 - 因此35个数据点) 。

R中指定的模型如下:

 fit.pred2 <- stan(file = 'Fast_GP6_all.stan',
                 data = dat, 
                 warmup = 1000,
                 iter = 1500,
                 refresh=5,
                 chains = 3,
                 pars= pars.to.monitor
                 )

我不知道我是否需要那么多热身迭代才能说实话。我想,缓慢估计的一部分是相当无信息的先验(除了心率和呼吸HRR的结果,因为那些在健康成人中具有相当熟知的休息范围)。

欢迎任何建议加快我的计划的运行时间。

感谢。

1 个答案:

答案 0 :(得分:2)

您可以获取Stan Math Library的开发分支,该分支具有最近更新的multi_normal_cholesky版本,该版本在内部使用分析渐变而不是autodiff。为此,您可以在R中执行 source("https://raw.githubusercontent.com/stan-dev/rstan/develop/install_StanHeaders.R") 但是你需要在你的〜/ .R / Makevars文件中加CXXFLAGS+=-std=c++11,之后可能需要重新安装 rstan 包。

multi_normal_choleskymain_GP都是O(N ^ 3),所以你的Stan程序永远不会特别快,但这两个函数的增量优化将产生最大的不同。

除此之外,还有一些小事 Sigma = Sigma + diag_matrix(rep_vector(1, N1))*sigma_sq; 应改为 for (n in 1:N1) Sigma[n,n] += sigma_sq; 原因是将sigma_sq乘以对角矩阵将N1平方节点放到自动生成树上,就像将其添加到Sigma一样,这会进行大量的内存分配和释放。沿对角线的显式循环只将N1个节点放到自动生成树上,或者如果我们对+=运算符很聪明,它可能只更新现有的树。 post_pred_rng函数中的相同交易虽然不那么重要,因为生成的数量块是用双精度计算的,而不是反向模式自动驾驶的自定义Stan类型。

我想/希望如此 vector[N2] Ypred = post_pred_rng(...); 比...快一点 vector[N2] Ypred; // preallocates Ypred with NaNs Ypred = post_pred_rng(...); 通过避免预分配步骤;无论如何,它阅读起来更好。

最后,虽然它不会影响速度,但您没有义务以日志形式指定参数,然后在转换后的参数块中对它们进行反对。您可以使用<lower=0>声明事物并且这将导致相同的事情,尽管那时您将在正面约束的事物而不是无约束的事物上指定您的先验。在大多数情况下,这更直观。那些具有3个自由度的学生是非常重的,这可能导致Stan至少在预热期间采取了很多跳跃步骤(默认情况下达到10的限制)。越级步骤的数量是运行时的主要贡献者,因为每次迭代都需要2^s - 1函数/梯度评估。