Sansan R&D 研究員の小松です。前回までは、ネットワークをシミュレーションする際に発生する問題、およびその解決方法の1つについて議論してきました。
なぜこれらが重要かというと、効用関数のパラメーターを推定するためにネットワークをシミュレーションする必要があるからです。
A naive Metropolis-Hastings sampler doesn't work
今考えている尤度関数は以下のようなものでした。
ただし、 はポテンシャル関数、 です。 この normalizing constant があるために、尤度関数を直接計算することが困難なのでした。
Bayesian estimation の際も、事後分布を求めるときに normalizing constant が計算できないというケースがあります。 の事前分布を とおくと、事後分布 は
と表せます。この事後分布の分母は一般に計算が困難です。そこで Metropolis-Hastings 法によって、この事後分布から をサンプリングすることを考えます。
Metropolis-Hastings 法の手順は以下の通りです。
- パラメーター の初期値 を定める。
- 提案分布 から をドローする。
- Acceptance ratio を計算する。
- 一様分布 ] から をドローする。
- ならば、 とする。
- 2 に戻る。
Metropolis-Hastings 法の詳細はここでは触れません。例えば鎌谷 (2020) をご覧になると良いでしょう。
Metropolis-Hastings 法を用いるモチベーションは、normalizing constant の計算を回避することです。 ただし上のアルゴリズムで計算される acceptance ratio は、
となり、normalizing constant が消えません。これは、normalizing constant がパラメーター の関数になっているためです。
Double Metropolis-Hastings Sampler
そこで Mele (2017) は、Liang (2010) で提案されている Double Metropolis-Hastings 法 を使い、パラメーターの事後分布を求めることを提案しました。 ここで double となっているのは、ネットワーク、パラメーター両方に対して Metorplis-Hastings 法を使うことに由来します。
まず、ネットワークをシミュレーションするアルゴリズムを書き下します。
Algorithm 1 - Metropolis-Hastings for Network Simulations
- パラメーター を固定する。イテレーション におけるネットワークが とする。
- 提案分布 から からネットワーク をドローする。
- Acceptance ratio を計算する。
- 一様分布 ] から をドローする。
- ならば、 とする。
- 2に戻る
Acceptance ratio には normalizing constant が含まれていないので、計算が可能となります。 パラメーター のときの target distribution は
で、ネットワークをドローするにあたり を固定することで
のように normalizing constant がキャンセルアウトされるためです。 これまでの連載でネットワークをシミュレーションする際に使っていたアルゴリズムは、この Algorithm 1 をです。 ネットワークの収束を改善するため、proposed network を生成する際に dyad の状態を一度に複数変更する large sampler を導入したのでした。
Double Metropolis-Hastings 法では、ネットワーク を補助変数として用いながら、パラメーター の事後分布を求めます。
Algorithm 2 - Double-Metropolis Hastings Sampler
- ネットワークのシミュレーション回数を とする。各イテレーション におけるパラメーターを 、観察されたネットワークを とする。
- パラメーター の初期値 を定める。
- 提案分布 から をドローする。
- 観察されたネットワーク を初期値、パラメーターを として、Algorithm 1 を 回走らせる。 回後のシミュレーションにおけるネットワークを とする。
- Acceptance ratio を計算する。
- 一様分布 ] から をドローする。
- ならば、 とする。
- 3に戻る。
以下、acceptance ratio がなぜこのような形で表せるかを確認します。
観察されたネットワークを とすると、今考える density は となります (ここでは補助変数として を考えていることに注意します)。 これは、
- のもとで を観察し ()
- を所与として を提案し ()
- のもとでネットワーク を観察する ())
と分解されますから、
と表せます。したがって
となり、acceptance ratio に normalizing constant が出てきません。
Algorithm 2 のポイントは、ネットワークのシミュレーションを行うときに、ネットワークの初期値を観察されたネットワークに設定していることです。 これは を固定したとき、ネットワークの事後分布が定常分布に収束することを保証します。詳しくは Mele (2017) の Appendix B を参照ください。
また、提案されたパラメーターの値によっては、ネットワークの収束に指数時間かかるケースもありえます。 そうした場合でも素早く収束するように large sampler を導入することが有効なのでした。
Double Metropolis-Hastings sampler を可視化すると以下のようになります。
Estimation in practice
Algorithm 1, 2 を実際に実装して、パラメーターの推定がうまくいくか試してみましょう。
ここでも、以下の GitHub repository のパッケージを利用します。
Network simulation
まずはネットワークを Algorithm 1 を用いてシミュレーションしてみます。今回は以下の効用関数を考えます。
効用関数の各項はそれぞれ、直接のつながりから得られる benefit、人気のある友人から得られる benefit、共通の友人から得られる benefit と考えることができます。
このとき、ネットワーク形成ゲームは以下のようなポテンシャル関数によってポテンシャルゲームとして表現できます。
ここで括弧でくくった項はそれぞれ、ネットワークの edge, twostar, triangle の数と一致します。
Algorithm 1 を計算するためには各イテレーションで
- ネットワーク を提案分布からドローする
- accceptance ratio を計算する
の2つが必要です。
まず今考える sampler では、 となります。 ループのない任意のundirected network を考えます。そこからリンクを1つだけランダムに追加/削除するような sampler を考えると、
となります。詳細は省きますが、large sampler を使う場合も が成立します。
そしてaccceptance ratio を計算していくわけですが、オーバーフローを回避するため
と自然対数をとります。ここで、ポテンシャル関数の各項がネットワークの統計量を表していたことを思い出せば、
のように書けます。 や などのネットワークの統計量の変化分を change statistics と呼んだりします。
そして各イテレーションで] から乱数を発生させて
であれば のようにネットワークを更新していきます。
実際にネットワークをシミュレーションしてみます。
devtools::install_github("komatsuna4747/myergm") library(myergm) library(ggplot2) set.seed(334) # Number of nodes N <- 200 # True parameters theta_true <- c(-2.5, 1/N, 1/N) # Simulate a fake network net <- simulate_network( adjacency_matrix = matrix(0, nrow = N, ncol = N), coefEdges = theta_true[[1]], coefTwostars = theta_true[[2]], coefTriangle = theta_true[[3]], MCMC_interval = 1, MCMC_burnin = 0, MCMC_samplesize = 2[f:id:komatsuna4747:20220314084838p:plain]00000, p_one_node_swap = 0.001, p_large_step = 0.001, p_invert = 0.001, lambda = 0.2, level = "debug" )
今は空のネットワークから出発して、20万回イテレーションを回しました。ネットワークのシミュレーションが収束しているか確認してみます。
# Check convergence df_stat_truenet <- tibble::tibble( iter = rep(1:nrow(net[[2]]), 3), stats = c(net[[2]][, 1], net[[2]][, 2], net[[2]][, 3]), terms = rep(c("edges","twostars", "triangles"), each = nrow(net[[2]])) ) g_stat_truenet <- df_stat_truenet %>% ggplot(aes(x = iter, y = stats, group = terms, color = terms)) + geom_line() plot(g_stat_truenet)
空のネットワークから出発しても、10万回ぐらいイテレーションを回せばシミュレーションが収束してくれるようです。 したがって、後にパラメーターの事後分布を求めるときにネットワークをシミュレーションする回数を とおくことにします。
20万回イテレーションを回したあとに発生したネットワークを、今回は観察されたネットワークとして使用することにします。
Posterior distribution estimation
パラメーターの prior distribution を とおきます。 ここではランダムウォーク型 Metropolis-Hastings 法を使うことにし、パラメーター の提案分布として、 を用いることにします。 は最初は適当に とでもおいておきます。
まず、提案されたパラメーター を用いて、観察されたネットワークを初期値としてネットワークを生成します。 その生成されたネットワークと観察されたネットワークから change statistics を計算し、acceptance ratio を計算します。
今考えている提案分布 は対称ですから、 となります。したがって acceptance ratio は
と書けます。
実際に Double Metropolis-Hastings 法を走らせてみましょう (推定は時間がかかります)。
# Prior sigma0 <- diag(10, nrow = 3) theta0 <- MASS::mvrnorm(n = 1, mu = c(0, 0, 0), Sigma = sigma0) covpars <- diag(c(0.01, 0.01, 0.01), nrow = 3) # Run DMH twice. dmh_first <- run_DMH_cpp( adjacency_matrix = net[[1]], sigma_proposal = covpars, theta_init = theta0, sigma_prior = sigma0, terms_included = c(1, 1, 1), param_sample = 5000, MCMC_network_interval = 100000, level = "info" )
結果を可視化してみます。
df_dmh_first <- tibble::as_tibble(dmh_first) colnames(df_dmh_first) <- c("edges", "twostar", "triangle") plot_stat <- function(data) { data_tidy <- data %>% dplyr::mutate(iter = 1:nrow(.)) %>% tidyr::pivot_longer(-iter) g <- data_tidy %>% dplyr::mutate(name = forcats::fct_relevel(name, "edges", "twostar", "triangle")) %>% ggplot(aes(x = iter, y = value, group = name, color = name)) + geom_line() + facet_grid(. ~ name) return(g) } plot_stat(df_dmh_first)
うまく収束していないようです。提案分布の分散の設定がよくないのでしょう。 最初の1,000回分を捨てて、残りのデータから分散行列を計算し、それを使って Double Metropolis-Hastings をもう一度走らせてみます。
theta <- df_dmh_first %>% dplyr::slice(1000:nrow(.)) %>% tidyr::pivot_longer(dplyr::everything()) %>% dplyr::mutate(name = forcats::fct_relevel(name, "edges", "twostar", "triangle")) %>% dplyr::group_by(name) %>% dplyr::summarise(mean = mean(value)) %>% dplyr::ungroup() %>% dplyr::pull(mean) %>% as.matrix() covpars <- cov(df_dmh_first %>% dplyr::slice(1000:nrow(.))) dmh_second <- run_DMH_cpp( adjacency_matrix = net[[1]], sigma_proposal = covpars, theta_init = theta, sigma_prior = sigma0, terms_included = c(1, 1, 1), param_sample = 10000, MCMC_network_interval = 100000, level = "info" )
推定結果を確認します。
df_dmh_second <- tibble::as_tibble(dmh_second) colnames(df_dmh_second) <- c("edges", "twostar", "triangle") plot_stat(df_dmh_second)
うまく収束しているように見えます。
最初の1,000回分を burnin として捨て、残りのデータから要約統計量を計算します。
summary(df_dmh_second %>% dplyr::slice(1000:nrow(.)))
edges twostar triangle Min. :-3.285 Min. :-0.008715 Min. :-0.161898 1st Qu.:-2.798 1st Qu.: 0.006354 1st Qu.:-0.050359 Median :-2.661 Median : 0.011002 Median :-0.027502 Mean :-2.657 Mean : 0.010702 Mean :-0.030452 3rd Qu.:-2.511 3rd Qu.: 0.015185 3rd Qu.:-0.007288 Max. :-2.017 Max. : 0.029968 Max. : 0.045579
真のパラメーターを と設定していたので、triangle に関しては推定がうまくいってないように見えます。 triangle の係数の推定は難しいと言われるようですが、また別途機会を見つけて原因を解明したいです。
Concluding remarks
今回は Mele (2017) の提案に沿って、Double Metropolis-Hastings 法により ERGM のパラメーターの推定を試みました。
ERGM の推定でよく使われる R のパッケージは {ergm}
ですが、そのパッケージでは Double Metropolis-Hastings ではなく、Monte Carlo for Maximum Likelihood Estimation が使われています。
これはネットワークの Markov chain を生成して、そこから複数のネットワークをサンプリングし ERGM のパラメーターを推定するというものです。
詳細は Statnet チームのドキュメント、または弊社研究員前嶋のブログが参考になるでしょう。
これまでは ERGM の理論的な話が多かったので、次回は ERGM の応用例を見ていくことにします。
References
- Liang, F. (2010). A double Metropolis–Hastings sampler for spatial models with intractable normalizing constants. Journal of Statistical Computation and Simulation, 80(9), 1007-1022.
- Mele, A. (2017). A structural model of dense network formation. Econometrica, 85(3), 825-850.
- Park, J., & Haran, M. (2018). Bayesian inference in the presence of intractable normalizing functions. Journal of the American Statistical Association, 113(523), 1372-1390.
- 鎌谷研吾. (2020). モンテカルロ統計計算. 講談社.
▼本連載のほかの記事はこちら
buildersbox.corp-sansan.com