レプリカ交換法を使ってベイズファクターを計算する~相関の検定~

本記事では、前回の記事で失敗に終わった相関係数の検定を、レプリカ交換法を使って解決していきたいと思います。

レプリカ交換法の部分は、松浦健太郎氏が運営されていたはてなブログに掲載されていたコードを流用しています。今現在そのブログが見当たらないですが、こちらのスライドでその内容が確認できるようです。

レプリカ交換法自体の詳細な説明は省きます。イメージは、MCMCサンプルを抽出するメインのchainとは別に、複数のchainを用意しておき、さらにそれらのchainでは対数尤度の変化が通常よりも小さく設定されており、値の移動が柔軟に(ランダムにより近く)なっていると。そして、時々メインのchainとその他のchainを交換することで、対数尤度が極端に小さくなる範囲があっても、それをMCMCが越えられるように工夫したMCMC法になっています。

前回、相関の検定で帰無仮説を $$ H_{0_1}: \rho=0 $$ 、対立仮説を $$ H_{1_3}: \rho < -0.3 ~~~~ 0.3 < \rho $$

としたとき、実際のデータは$\rho = 0.75$あたりが正解にもかかわらず、初期値をマイナスにすると以下のMCMCサンプルがえられたことから、 $-0.3$から$0.3$の間の対数尤度$-∞$のエリアをMCMCchainが越えられなかったこと失敗の原因があったと伺えます。

plot03

まずは、レプリカ交換法を実行する関数を定義していきます。

replica.exchange.mcmc <- function (inv_T, n_ex, stanmodel, data, par_list, init, iter, warmup) {
  n_rep <- length(inv_T)
  len <- iter - warmup
  n_param <- sum(unlist(lapply(par_list, prod))) + 2  # number of parameters included E and lp__
  ms_T1 <- matrix(0, len*n_ex, n_param)               # MCMC samples at inv_T=1
  idx_tbl <- matrix(0, n_ex, n_rep)                   # index table of (exchange time, replica)
  E_tbl   <- matrix(0, n_ex, n_rep)                   # E table along idx_tbl
  init_list <- rep(list(init), n_rep)

  idx4ex <- function (n_rep, e) if (e %% 2 == 0) 1:floor(n_rep/2) * 2 - 1 else 1:(floor(n_rep/2)-1) * 2

  pb <- txtProgressBar(min=1, max=n_ex, style=3)

  for (e in seq_len(n_ex)) {
    setTxtProgressBar(pb, e)
    fit_list <- foreach(r=seq_len(n_rep), .packages='rstan') %dopar% {
      data$Inv_T <- inv_T[r]
      sampling(
        stanmodel, data=data, pars=c(names(par_list), 'E'), init=list(init_list[[r]]),
        iter=iter, warmup=warmup, chains=1, seed=r, refresh=-1
      )
    }
    ms_T1[((e-1)*len+1):(e*len), ] <- extract(fit_list[[1]], permuted=FALSE, inc_warmup=FALSE)[,1,]

    # exchange replicas
    E <- sapply(1:n_rep, function(r) extract(fit_list[[r]], permuted=FALSE, pars='E')[len,1,])
    idx <- 1:n_rep
    for (r in idx4ex(n_rep, e)) {
      w <- exp((inv_T[r] - inv_T[r+1]) * (E[r] - E[r+1]))
      if (runif(1,0,1) < w) {
        idx[r]   <- r+1
        idx[r+1] <- r
      }
    }
    E_tbl[e,] <- E
    idx_tbl[e,] <- idx

    # update init_list
    init_list <- lapply(seq_len(n_rep), function(r) {
      ms <- extract(fit_list[[idx_tbl[e,r]]], permuted=FALSE, pars=names(par_list))[len,1,]
      init <- lapply(names(par_list), function(p) {
        pos <- grep(paste0('^', p, '(\\[.*\\])?$'), names(ms))
        if (identical(par_list[[p]], 1)) unname(ms[pos]) else array(ms[pos], dim=par_list[[p]])
      })
      names(init) <- names(par_list)
      init
    })
  }
  colnames(ms_T1) <- names(fit_list[[1]])
  return(list(ms_T1=ms_T1, idx_tbl=idx_tbl, E_tbl=E_tbl))
}

次に、レプリカ交換法のベースとなるStanコードです。現状では、cmdstanではうまくレプリカ交換法を実行できなかったので、rsta::stan()を使って実行することを想定してコードを書きます。

functions{
  real stretched_symmetric_beta_lpdf(real y, real kappa, real lower_rho, real upper_rho, int model_type){
    if(model_type == 0)
      if( y >= lower_rho && y <= upper_rho )
        return
          -log(inc_beta(1/kappa, 1/kappa, 0.5 * (upper_rho + 1)) - inc_beta(1/kappa, 1/kappa, 0.5 * (lower_rho + 1))) +
          ((kappa-2)/kappa) * log(2) - lbeta(1/kappa, 1/kappa) + ((1-kappa)/kappa) * log((1 - square(y)));
      else
        return negative_infinity();
    if(model_type == 1)
      if( y <= lower_rho || y >= upper_rho )
        return
          -log(1 - inc_beta(1/kappa, 1/kappa, 0.5 * (upper_rho + 1)) + inc_beta(1/kappa, 1/kappa, 0.5 * (lower_rho + 1))) +
          ((kappa-2)/kappa) * log(2) - lbeta(1/kappa, 1/kappa) + ((1-kappa)/kappa) * log((1 - square(y)));
      else
        return negative_infinity();
    else
      return negative_infinity();
  }
}

data {
  int<lower=0> N; // sample size
  vector[2] D[N]; // Data consists of X and Y
  real kappa;
  real Jeffreys_alpha;
  real Jeffreys_beta;
  real<lower=-1, upper=1> lower_rho;
  real<lower=-1, upper=1> upper_rho;
  int model_type;
  real Inv_T;
  //kappa : prior scale of rho
  //Jeffreys_alpha : mean of prior(sigma^2) (sufficiently small values)
  //Jeffreys_beta : variance of prior(sigma^2) (sufficiently small values)
  //model_type
}

parameters {
  real<lower=0> sigma_X;
  real<lower=0> sigma_Y;
  real<lower=-1, upper=1> rho;
  vector[2] mu;
  // mu consists of mu_X, mu_Y
  // mu_X, mu_Y : mean of X and Y, respecttively
  // sigma_X, sigma_Y : standard deviation of X and Y, respectively
  // rho : correlation of X and Y
}

transformed parameters{
  matrix[2,2] cov;
  real E;
  cov[1,1] = square(sigma_X);
  cov[1,2] = sigma_X * sigma_Y * rho;
  cov[2,1] = sigma_X * sigma_Y * rho;
  cov[2,2] = square(sigma_Y);

  {
    E = 0;
    for (n in 1:N){
      E = E - multi_normal_lpdf(D[n] | mu, cov);
    }
    E = E - stretched_symmetric_beta_lpdf(rho | kappa, lower_rho, upper_rho, model_type);
    E = E - gamma_lpdf(square(sigma_X) | Jeffreys_alpha, Jeffreys_beta);
    E = E - gamma_lpdf(square(sigma_Y) | Jeffreys_alpha, Jeffreys_beta);
    E = E - normal_lpdf(mu | 0, 100);
  }

}

model {
  target += -Inv_T * E;
}

これらを使って、レプリカ交換法を実行します。はじめに定義しているlog_density()関数は、後で自由エネルギーの計算に使う対数尤度関数です。今回、レプリカ交換法を使う理由でStanファイルをbridge_sampler()に引数として指定することができません。かわりにbridge_sampler()関数にはMCMCサンプルの行列と対数尤度関数、そして各パラメーターの定義域を指定するためのベクトルを与えてやれば自由エネルギーを計算するオプションがついているので、これを使用しています。


log_density <- function(samples.row, data) {
  lp <- c()

  lp <- stretched_symmetric_beta_lpdf(y=samples.row[3], kappa=kappa, lower_rho = lower_rho,
                                      upper_rho = upper_rho, model_type = model_type) +
    dgamma(x = samples.row[1]^2, shape = Jeffreys_alpha, scale = 1 / Jeffreys_beta, log=TRUE ) +
    dgamma(x = samples.row[2]^2, shape = Jeffreys_alpha, scale = 1 / Jeffreys_beta, log=TRUE ) +
    dnorm(x = samples.row[4], mean = 0, sd = 100, log = TRUE) +
    dnorm(x = samples.row[5], mean = 0, sd = 100, log = TRUE)

  for(n in 1:nrow(data)){
    lp <- lp + dmvnorm(x = data[n,], mean = samples.row[4:5], sigma = matrix(c(samples.row[1]^2,samples.row[1]*samples.row[2]*samples.row[3],samples.row[1]*samples.row[2]*samples.row[3],samples.row[2]^2), nrow=2, ncol=2),
                       log = TRUE)
  }

  return(lp)
}

library(doParallel)
library(rstan)
model_informative_rstan <- rstan::stan_model(file=paste0(getwd(),"/model/model_informative_E.stan"))

kappa <- 3
Jeffreys_alpha <- 1e-5
Jeffreys_beta <- 1e-5

# Informative2
lower_rho <- -0.3
upper_rho <- 0.3
model_type <- 1

init <- list(rho = -0.5)

N_rep <- 10    # number of replicas
N_ex <- 100    # number of exchanges
Inv_T <- 0.5^seq(0, -log(0.02)/log(2), len=N_rep)


data_informative <- list(D = d, N = nrow(d), Jeffreys_alpha=Jeffreys_alpha, Jeffreys_beta=Jeffreys_beta,
                         upper_rho = upper_rho, lower_rho= lower_rho, kappa=kappa, model_type=model_type)


registerDoParallel(detectCores())
res <- replica.exchange.mcmc(inv_T=Inv_T, n_ex=N_ex,
                             stanmodel = model_informative_rstan, data=data_informative, par_list=list(sigma_X=1, sigma_Y=1, rho=1, mu=2), init=init, iter=600, warmup=100)

# Matrixを使ったBridgesampling
library(zipfR)
library(mvtnorm)
posterior <- as.matrix(res$ms_T1)[10000:nrow(res$ms_T1),1:5]

lb <- c(0,0,-1,-Inf,-Inf)
ub <- c(Inf,Inf,1,Inf,Inf)

names(lb) <- names(ub) <- colnames(posterior)
free_energy_alt <-
  bridgesampling::bridge_sampler(samples = posterior, log_posterior = log_density,
                                 lb = lb, ub = ub, data=d)$logml

exp(free_energy_alt - free_energy_null)
# [1] 3.38101

計算の結果推定されたベイズファクターは、

$$ BF_{10_3} ≒ 3.38 $$

でした。BayesFactor関数の結果とは少し値が離れていますね…どこか間違えているのかもしれませんが、事後分布を見る限り問題は見受けられません。レプリカ交換法でも初期値は負の値にしていましたが、対数尤度$-∞$のエリアを越えて正の値にいけていたようです。

plot04

レプリカの交換状況を図化してみましたが、レプリカ同士の交換は問題なくできているようです。

exchange-invT

次にレプリカ交換タイムでのレプリカ毎のエネルギーを描画しましたが、レプリカ間の交換には支障なさそうなエネルギーであることが確認できます。

energy-whole

最後にトレースプロットの確認です。イテレーション50の段階で、$\rho$の値が負の値から脱却出来ていることが確認できました。

traceplot

最終的に計算されたベイズファクターの値は解析解とは異なり、違和感がありましたが、レプリカ交換法自体はうまくいっていたようなので、よしとしましょう。

以上。

コメントを書く


※ コメントは承認されると表示されます

承認されたコメント一覧