隠れマルコフ連鎖モデルの変分推論(Rで実装)
今読んでいるベイズ推論のテキストベイズ推論による機械学習入門 の内容より、 隠れマルコフモデルの変分推論についてRで実装したので記事化してみます。
1.隠れマルコフモデルにおける変分推論
モデルの確認
個の状態の間を移りあうマルコフ連鎖を考えます。 この状態が潜在変数で表されているモデルを隠れマルコフモデルといいます。
いくつか記号を用意します。 状態を表す潜在変数を時点における状態がの場合1, それ以外の場合は0であると定めます。また、 とまとめて書きます。
2つのハイパーパラメーター
: の分布
: 遷移行列
を与えると、
はマルコフ連鎖に従うので、同時確率が次の式で与えられます。
また、は直接観測されない代わりに以下の式で与えられる確率変数が観測データとして与えられるとします。
ただし、はハイパーパラメーターとします。
以上の設定のもと、観測データに対する同時分布が次の式で与えられることになります。
完全分解変分推論
隠れマルコフモデルの事後分布を近似的に求める方法として、変分推論を適用することを考えます。
完全分解型変分推論()では事後分布を次のような分布の積によって近似を行います。
テキストでは次の事前分布に対する仮定のもと、完全分解型変分推論に対する更新アルゴリズムの導出が載っていますので気になった方はご参照ください。
2.Rによる実装
library(tidyverse)
library(glue)
#'@param N
#'@param K
#'@param X array([N])
#'@param prior list(alpha[K], beta[K, K], theta list(a[K], b[K]))
#'@param init list(alpha[K], beta[K, K], theta list(a[K], b[K]))
#'@param iterMax
CFVI <- function(N, K, X, prior, init, iterMax) {
softmax <- function(s) exp(s) / sum(exp(s))
entropy.Gamma <- function(a, b) - digamma(a) + log(b)
entropy.Dirichlet <- function(alpha) - digamma(alpha) + digamma(sum(alpha))
hyp.params = list(
S = array(rep(0, K * N), dim = c(K, N)),
pi = init$alpha,
beta = init$beta,
theta = list(a = rep(init$theta$a, K), b = rep(init$theta$b, K)))
for (iter in 1:iterMax) {
#update of hyperparams of S
hyp.params$S <- c(1:N) %>%
map_dfc(function(n) {
entropy.beta <- hyp.params$beta %>%
as.data.frame %>%
map_dfc(~ {entropy.Dirichlet(.)}) %>%
as.matrix
entropy.theta <- entropy.Gamma(hyp.params$theta$a, hyp.params$theta$b)
exp.theta <- hyp.params$theta$a / hyp.params$theta$b
if (n == 1) {
return(c(- X[1] * entropy.theta - exp.theta -
entropy.Dirichlet(hyp.params$pi) -
hyp.params$S[,2] %*% entropy.beta))
} else if (n == N) {
return(c(- X[N] * entropy.theta - exp.theta -
hyp.params$S[,N - 1] %*% entropy.beta))
} else {
return(c(- X[n] * entropy.theta - exp.theta -
hyp.params$S[,n - 1] %*% t(entropy.beta) -
hyp.params$S[,n + 1] %*% entropy.beta))
}
} %>% softmax) %>% as.matrix
#update of hyperparams of alpha
hyp.params$pi <- hyp.params$S[,1] + prior$alpha
#update of hyperparams of beta
hyp.params$beta <- hyp.params$S[,2:N] %*% t(hyp.params$S[,1:(N-1)]) + prior$beta
#update of hyperparams of theta
hyp.params$theta <- list(
a = c(hyp.params$S %*% X) + prior$theta$a,
b = rowSums(hyp.params$S) + prior$theta$b)
}
return(hyp.params)
}
#test###########################################################################
test <- function(){
#'generate sample from hidden markov model with 2 - states = {1, 2}
test.N <- 100L
test.K <- 2L
#'transition matrix
test.A <- array(c(0.2, 0.8, 0.4, 0.6), dim = c(2, 2))
nextState <- function(current, A) {
sample(c(1, 2), size = 1, replace = TRUE, prob = A[,current])
}
test.state <- purrr::reduce(1:test.N, ~ {append(.x, nextState(tail(.x, 1), test.A))})
#test data
test.theta <- c(2, 10)
test.X <- purrr::map_dbl(test.state, ~ {rpois(1, test.theta[.x])})
#目視チェック
plot <- ggplot(data.frame(x = c(1:test.N), val = test.X), aes(x = x, y = val)) +
geom_point() +
labs(title='plot of test data')
print(plot)
#事前分布のパラメーターを与える
test.prior <- list(alpha = c(1, 1),
beta = array(rep(0.5, 4), dim = c(2, 2)),
theta = list(a = 1, b = 1))
test.init <- list(alpha = c(0.1, 1),
beta = array(rep(0.5, 4), dim = c(2, 2)),
theta = list(a = 1, b = 1))
estim <- CFVI(test.N, test.K, test.X, test.prior, test.init, 20L)
#'状態を推定し可視化
estim.state <- ifelse(estim$S[1,] < 0.5, 1, 2)
estim.plot <- ggplot(data.frame(x = c(1:test.N), val = test.X, state = as.factor(estim.state)),
aes(x = x, y = val, colour = state)) +
geom_point() +
labs(title='estimation of states')
print(estim.plot)
#'正解率([推定された状態 = 真の状態]であるデータの個数 / 全データ数)
print(glue('正解率 : {sum(ifelse(estim.state == test.state, 1, 0)) / test.N}'))
}
test()
推定結果による判別の正解率()を
で定めて評価を行ったところ、反復回数10程度で約0.90ほどに達するようです。