需要修复广义帕累托分布的 Stan 代码以接受实参而不是向量

Need to fix Stan code for the generalized pareto distribution to take in real arguments rather than vectors

我正在使用此处定义的函数:Stan 中的极值分析和用户定义的概率函数,用于使用广义帕累托分布对数据进行建模,但我的问题是我的模型处于 for 循环中,需要三个实数值参数,而 gpd 函数假设一个向量、实数、实数参数。

我不太确定我的模型块是否适合矢量化,所以我想我需要让 gpd 函数接受实值参数(但也许我错了)。

如果能帮助我切换代码以实现此目的,我将不胜感激。这是我的斯坦码

functions {
  real gpareto_lpdf(vector y, real k, real sigma) {
    // generalised Pareto log pdf 
    int N = rows(y);
    real inv_k = inv(k);
    if (k<0 && max(y)/sigma > -inv_k)
      reject("k<0 and max(y)/sigma > -1/k; found k, sigma =", k, sigma)
    if (sigma<=0)
      reject("sigma<=0; found sigma =", sigma)
    if (fabs(k) > 1e-15)
      return -(1+inv_k)*sum(log1p((y) * (k/sigma))) -N*log(sigma);
    else
      return -sum(y)/sigma -N*log(sigma); // limit k->0
  }

  real gpareto_lcdf(vector y, real k, real sigma) {
    // generalised Pareto log cdf
    real inv_k = inv(k);
    if (k<0 && max(y)/sigma > -inv_k)
      reject("k<0 and max(y)/sigma > -1/k; found k, sigma =", k, sigma)
    if (sigma<=0)
      reject("sigma<=0; found sigma =", sigma)
    if (fabs(k) > 1e-15)
      return sum(log1m_exp((-inv_k)*(log1p((y) * (k/sigma)))));
    else
      return sum(log1m_exp(-(y)/sigma)); // limit k->0
  }
}

data {
  // the input data
  int<lower = 1> n;                       // number of observations
  real<lower = 0> value[n];              // value measurements
  int<lower = 0, upper = 1> censored[n];  // vector of 0s and 1s

  // parameters for the prior
  real<lower = 0> a;
  real<lower = 0> b;
}

parameters {
  real k;
  real sigma;
}

model {
 // prior
 k ~ gamma(a, b);
 sigma ~ gamma(a,b);

 // likelihood
  for (i in 1:n) {
    if (censored[i]) {
      target += gpareto_lcdf(value[i] | k, sigma);  
    } else {
      target += gpareto_lpdf(value[i] | k, sigma);
    }
  }
}

这是日志 PDF 的调整方式。这样,可以传递用于将 y 子集化为截尾和 non-censored 观察的索引数组。

real cens_gpareto_lpdf(vector y, int[] cens, int[] no_cens, real k, real sigma) {
    
  // generalised Pareto log pdf 
  int N = size(cens);
  real inv_k = inv(k);
  if (k<0 && max(y)/sigma > -inv_k)
  reject("k<0 and max(y)/sigma > -1/k; found k, sigma =", k, sigma)

    if (fabs(k) > 1e-15)
      return -(1+inv_k)*sum(log1p((y[no_cens]) * (k/sigma))) -N*log(sigma) + 
      sum(log1m_exp((-inv_k)*(log1p((y[cens]) * (k/sigma)))));
    else
      return -sum(y[no_cens])/sigma -N*log(sigma) + 
      sum(log1m_exp(-(y[cens])/sigma));
}

扩展 data 块:n_censn_not_censcensno_cens 是需要提供的值。

  int<lower = 1> n;               // total number of obs
  int<lower = 1> n_cens;          // number of censored obs
  int<lower = 1> n_not_cens;      // number of regular obs
  
  int cens[n_cens];               // index set censored
  int no_cens[n_not_cens];        // index set regular
  
  vector<lower = 0>[n] value;     // value measurements

gfgm 建议的非零参数:

parameters {
  real<lower=0> k;
  real<lower=0> sigma;
}

重写 model 块:

model {
  // prior
  k ~ gamma(a, b);
  sigma ~ gamma(a,b);
  // likelihood
  value ~ cens_gpareto(cens, no_cens, k, sigma);
}

免责声明:我既没有检查公式的完整性,也没有运行使用测试数据检查模型。刚刚通过 rstan::stan_model() 编译,效果很好。 gfgm 的建议对于 post-processing / generated quantities 等中的计算内容可能更方便。我不是 Stan 专家:-)。

编辑:

修复了 gfgm 通过模拟发现的分歧问题。可能性是 ill-defined(N= rows(y) 而不是 N=size(cens)。现在使用 gfgm 的数据运行良好(使用 set.seed(123)rstan):

         mean se_mean   sd    2.5%     25%     50%     75%   97.5% n_eff Rhat
k        0.16    0.00 0.10    0.02    0.08    0.14    0.21    0.42  1687    1
sigma    0.90    0.00 0.12    0.67    0.82    0.90    0.99    1.16  1638    1
lp__  -106.15    0.03 1.08 -109.09 -106.56 -105.83 -105.38 -105.09  1343    1

您可以让模型接受向量并避免 for 循环。在某种程度上,您需要在声明 value 时更改签名,但随后还将经过审查和未经审查的观察结果的索引作为数据输入。

我在下方发布 运行 合成数据的代码,但我合成的数据只是一些随机 log-normal 变量,而不是帕累托的实际绘制,所以我没有了解模型是否正在恢复参数或它的覆盖范围是什么样的。您可能需要进行一些基于模拟的校准来检查模型。

斯坦码:

functions {
  real gpareto_lpdf(vector y, real k, real sigma) {
    // generalised Pareto log pdf 
    int N = rows(y);
    real inv_k = inv(k);
    if (k<0 && max(y)/sigma > -inv_k)
      reject("k<0 and max(y)/sigma > -1/k; found k, sigma =", k, sigma);
    if (sigma<=0)
      reject("sigma<=0; found sigma =", sigma);
    if (fabs(k) > 1e-15)
      return -(1+inv_k)*sum(log1p((y) * (k/sigma))) -N*log(sigma);
    else
      return -sum(y)/sigma -N*log(sigma); // limit k->0
  }

  real gpareto_lcdf(vector y, real k, real sigma) {
    // generalised Pareto log cdf
    real inv_k = inv(k);
    if (k<0 && max(y)/sigma > -inv_k)
      reject("k<0 and max(y)/sigma > -1/k; found k, sigma =", k, sigma);
    if (sigma<=0)
      reject("sigma<=0; found sigma =", sigma);
    if (fabs(k) > 1e-15)
      return sum(log1m_exp((-inv_k)*(log1p((y) * (k/sigma)))));
    else
      return sum(log1m_exp(-(y)/sigma)); // limit k->0
  }
}

data {
  // the input data
  int<lower = 1> n;                       // number of observations
  int<lower = 0> n_cens;
  vector<lower = 0>[n] value;              // value measurements
  int<lower=1> cens_id[n_cens]; // the indices of censored observations
  int<lower=1> nocens_id[n - n_cens]; // the indices of uncensored obs

  // parameters for the prior
  real<lower = 0> a;
  real<lower = 0> b;
}

parameters {
  real<lower=0> k;
  real<lower=0> sigma;
}

model {
 // prior
 k ~ gamma(a, b);
 sigma ~ gamma(a,b);

 // likelihood
 target += gpareto_lcdf(value[cens_id] | k, sigma);  
 target += gpareto_lpdf(value[nocens_id] | k, sigma);
}

这 运行 在我的 mid-tier 笔记本电脑上使用 cmdstanr 基本上是即时的。您可以 运行 使用 R 代码

library(cmdstanr)
library(bayesplot)

gparet_mod <- cmdstan_model("gpareto_example.stan")

# make some fake data. This won't be correct and its
# no proper test of the capacity of the model to 
# recover the parameters, just seeing if it runs

N <- 100
value <- rlnorm(N)
censored <- rbinom(N, 1, .5)
N_cens <- sum(censored)
cens_id <- which(censored == 1)
nocens_id <- which(censored == 0)

a <- b <- 2

dat <- list(n = N, n_cens = N_cens, value = value,
            cens_id = cens_id, nocens_id = nocens_id,
            a = a, b = b)

ests <- gparet_mod$sample(data = dat, parallel_chains = 2, chains = 2)

这会产生看似正常的样本

ests$summary()
# A tibble: 3 × 10
  variable     mean   median    sd   mad       q5      q95  rhat ess_bulk ess_tail
  <chr>       <dbl>    <dbl> <dbl> <dbl>    <dbl>    <dbl> <dbl>    <dbl>    <dbl>
1 lp__     -103.    -103.    1.10  0.767 -105.    -102.     1.00     750.     807.
2 k           0.316    0.296 0.135 0.131    0.128    0.557  1.00     665.     570.
3 sigma       0.691    0.685 0.113 0.110    0.511    0.887  1.00     772.     887.

和后验分布

mcmc_hist(ests$draws(variables = c("k", "sigma")))

我认为 Martin Arnold 的方法更优雅一些,但是当我试图在相同的数据上将其变为 运行 时,采样器因分歧而崩溃,所以也许我移植了错误的模型或者有些东西需要调整。

编辑

鉴于似然函数,将参数 ksigma 限制为正值更有意义。该模型 运行 更好,您会收到更少的错误消息。我修改了上面的 stan 代码以反映这一点。