StatModeling Memorandum

StatModeling Memorandum

StanとRとPythonでベイズ統計モデリングします. たまに書評.

階層ベイズモデルとWAIC

この記事では階層ベイズモデルの場合のWAICとは何か、またその場合のWAICの高速な算出方法について書きます。

背景

以下の2つの資料を参照してください。[1]に二種類の実装が載っています。[2]に明快な理論的補足が載っています。

モデル1

資料[1]にあるモデルを扱います。すなわち、

f:id:StatModeling:20201106183418p:plain

ここで N は人数、 n は人のインデックスです。 r[n] は個人差を表す値になります。このモデルにおいては r[n] を解析的に積分消去することができて、負の二項分布を使う以下のモデル式と等価になります。

f:id:StatModeling:20201106183423p:plain

ここでは予測として(WAICとして)2通り考えてみましょう。 以降では事後分布による平均を  \mathbb{E}[\,] 、分散を  \mathbb{V}[\,] と書くことにします。

(1)  r[n] を持つ n が、追加で新しく1つのサンプルを得る場合

この場合には新しいデータ y の予測分布は以下になります。

f:id:StatModeling:20201106183428p:plain

WAICは n ごとに算出され、以下になります。

f:id:StatModeling:20201106183432p:plain

(2) 別の新しい人が新しく1つのサンプルを得る場合

この場合には次のモデルを考えていることに相当します。

f:id:StatModeling:20201106183436p:plain

そして、新しいデータ y の予測分布は以下になります。

f:id:StatModeling:20201106183439p:plain

WAICは以下になります。

f:id:StatModeling:20201106183443p:plain

ソースコード

 n ごとにWAICを算出することや、WAIC内の和(シグマ)はR側で処理します。

(1)のStanコード

(2)に対応する負の二項分布を使ったStanコード

(2)のStanコード

数値積分をR側かStan側のどちらかで実行する必要があります。資料[1]ではR側で行っており、これが多大な時間がかかる原因となっています。ここでは合成シンプソン公式(とlog_sum_exp関数)を使ってStan側で数値積分をして高速化します。

これはこちらのコードをメモ化によって高速化したものになっています。どちらのコードでも6~17行目でシンプソンの公式を使って数値積分をしています。

Rコード

結果

waic1_byG waic2 waic3
2.332 3.244 2.841 ... 2.987 3.14 3.143

計算時間は(1)の場合は、Surface Pro 3で1chainあたり5秒ぐらいです。(3)の場合でもメモ化がバッチリ効いて1chainあたり12秒ぐらいです。

waic1_byGにおいて、r[n]の大きなnr[n]の小さなnと比べて、ガンマ分布の裾部分の確率密度に由来する可能性が高く、(1)の予測が悪くなる(WAICが大きくなる)ことが予想されるでしょう。ここでは図示しませんが調べるとそうなっています。

waic2waic3は理論的に一致するはずですが、Stanコードの違いがMCMCサンプルの違いになり、その影響でわずかにズレます。

なお、資料[1]のp.47-48のソースコードだと(1)の場合のWAICを n ごとに算出したあとに、それらの和をとって N で割った値になります。WAICの和は「各 n が、追加でそれぞれ新しく1つのサンプルを得る場合」の予測に対応します。それを N で割った値が対応する予測はよく分かりません。

また、WAICはMCMCサンプルによって値が変わるので、乱数の種の影響をわずかにうけることに注意です。

モデル2

資料[2]にあるモデルと似たモデルを扱います。すなわち、

f:id:StatModeling:20201106183448p:plain

ここで G はクラス数(グループ数)、 g はそのインデックスです。 N は人数、 n はそのインデックスです。 N2G[n]  n が所属している g を返します。 ここでは予測として(WAICとして)3通り考えてみましょう。

(1) あるクラス g に、新しく1人が加わる場合

この場合には新しいデータ y の予測分布は以下になります。

f:id:StatModeling:20201106183453p:plain

WAICは g ごとに算出され、以下になります。

f:id:StatModeling:20201106183457p:plain

ここで G2N[g] はクラス g に含まれる n のインデックスすべてです。

(2) 別の新しいクラスがまるごとできる場合

この場合には新しいクラス全体のデータ y^n の予測分布は以下になります。

f:id:StatModeling:20201106183501p:plain

 (\,)^n の記法は資料[2]を参照してください。

WAICは以下になります。

f:id:StatModeling:20201106183505p:plain

(3) 別の新しいクラスができて、新しく1人が加わる場合

この場合には新しいデータ y の予測分布は以下になります。

f:id:StatModeling:20201106183509p:plain

WAICは以下になります。

f:id:StatModeling:20201106183513p:plain

ソースコード

(1)のStanコード

(2)のStanコード

グループ差や個人差が正規分布から生成される場合には、-5SDから+5SDぐらいまでを数値積分すればかなりよい近似になります。

(3)のStanコード

これはこちらのコードYによって変わらない部分をはじめに計算して保持しておいて、Yによって変わる部分だけをループで計算することで高速化したものになっています。

Rコード

結果

waic1_byG waic2 waic3
2.537 2.390 2.750 ... 2.496 142.1 3.679

計算時間は(1)(2)(3)の場合がそれぞれ、1chainあたり0.4秒・1秒・12秒ぐらいです。こちらはメモ化ほど高速化が効きませんが、それでも高速化しない場合と比べると1.5倍ぐらい早くなっています。

waic1_byGにおいて、クラスあたりの人数(NbyG)の多いクラスの方がWAICは小さくなるかなと思ったのですが、そこまできれいな関係ではありませんでした。ただし、人数が5人のクラスはWAICは目に見えて高くなっています。

あわせて読みたい

statmodeling.hatenablog.com statmodeling.hatenablog.com

underdispersion(過小分散)な場合のポアソン分布の代替

overdispersion(過分散)なポアソン分布は個体差&ポアソン分布で説明するのがシンプルで解釈しやすくて、個人的には好みです。ただ、個体差を考慮するモデルではunderdispersion(過小分散)の場合に対応できません。そのような場合には「ほぼ確定的な値が存在し、そこから外れるメカニズムをきちんと組み入れたモデル」がよさそうと思うのですが、まだ思案中です。

この記事では、overdispersion(過分散)な場合のポアソン分布の代替として、Rから簡単に使える4つの分布を紹介したいと思います。

分布の紹介とRからの使い方

超幾何分布(Hypergeometric distribution)

関数形は以下です( N,K,n,y は非負の整数、以降の分布でも yの値域は同じ)。

 K  N と比べて小さくて、 n が大きい時にPoisson分布になります。平均と分散は以下です。

合計 N 個のボールが入った壺があって、アタリのボールが K 個入っているとします。そして、ボールを1個ずつ取り出します。ただし、ボールは壺に戻しません。 n 個取り出したあとの、アタリのボールの個数 y の従う分布になっています。Rではプリインストールで関数が用意されています。

dhyper(x, m, n, k)

引数はWikipediaと少し異なります。xが確率変数の値、mがあたりのボールの個数( K に対応)、nがはずれのボールの個数( N-K に対応)、kが試行回数( n に対応)です。いくつか分布を描くと以下になります(記法はWikipediaに揃えました)。

一般化ポアソン分布(generalized Poisson distribution, Lagrangian Poisson distribution)

Wikipediaのページは見つかりませんでした。関数形は以下です( 0 \leq \theta  -1 \leq \lambda \leq 1)。

 \lambda \lt 0の時にunderdispersionです。平均と分散は以下です。

詳しくは以下の論文を読むとよさそうだけど、有料なのでサラリーマンには入手が厳しいです。このあたりの説明を読むと待ち行列と密接な関係がありそうですが分かりません。

  • [2] Consul, P. C., & Jam, G. C. (1973a) A generalization of the Poisson distribution. Technometrics, 15, 791-799
  • [3] Consul, P. C., & Jam, G. C. (1973b) On some interesting properties of the generalized Poisson distribution. Biometrische Zeitschrift, 15, 495-500.
  • [4] Consul, P. C. (1989) Generalized Poisson Distributions: Properties and Applications. New York: Marcel Dekker, Inc.

Rでは{RMKdiscrete}パッケージにあるdLGP関数を使います。

dLGP(x, theta, lambda)

いくつかunderdispersionの分布を描くと以下になります。

Conway-Maxwell-Poisson分布(Conway-Maxwell-Poisson distribution)

関数形は以下です( 0 \leq \lambda  0 \leq \nu)。

和をとって正規化する必要があります。 1 \lt \nuの時にunderdispersionです。平均と分散は陽に表せず定義通りの式になるようです。

原論文が古くて入手が難しいです。Wikipediaの記述を読むと待ち行列と密接な関係がありそうですが分かりません。

  • [6] Conway, R. W.; Maxwell, W. L. (1962) A queuing model with state dependent service rates. Journal of Industrial Engineering, 12: 132–136

Rでは{CompGLM}パッケージにあるdcomp関数を使います。

dcomp(x, lam, nu)

いくつかunderdispersionの分布を描くと以下になります。

Double Poisson分布(double Poisson distribution)

Wikipediaのページは見つかりませんでした。関数形は以下です( 0 \lt \mu  0 \lt \sigma)。

和をとって正規化する必要があります。 \sigma \lt 1の時にunderdispersionです。平均と分散は以下です(非常によい近似です)。

詳しくは以下の原論文([7]と[8])を読むとよいです(あのEfronです!)。一読したところ、exponential familyの拡張としてdouble exponential familyという分布族を定義して、そこにポアソン分布の確率質量関数を代入して整理すると出てくるようです。2次元の分割表にも関係しているようです。でも論文が難解で分かりませんでした。詳しい人いましたら教えてください。

  • [7] Efron, B., (1986) Double exponential families and their use in generalized linear Regression. Journal of the American Statistical Association 81 (395), 709-721. (link, preprint)
  • [8] Diaconis, P. & Efron, B. (1985) Rejoinder: Testing for Independence in a Two-Way Table: New Interpretations of the Chi-Square Statistic, Annals of Statistics, 13, 845–913. (link)
  • [9] Zou, Y., Geedipally, S.R. & Lord, D. (2013) Evaluating the Double Poisson Generalized Linear Model. Accident Analysis & Prevention, 59, 497–505 (link)

Rでは{gamlss.dist}パッケージにあるdDPO関数を使います(link)。

dDPO(x, mu, sigma)

いくつかunderdispersionの分布を描くと以下になります。

Stanを使って推定

デモデータは以下です。[10]のFig.1のAから逆算しました(厳密にはずれているかもしれません)。

  • [10] Kitazawa M. S., Fujimoto K. (2014) A Developmental basis for stochasticity in floral organ numbers Frontiers in Plant Science, 5, 545 (link)
  • [11] Kitazawa M. S., Fujimoto K. (2016) Relationship between the species-representative phenotype and intraspecific variation in Ranunculaceae floral organ and Asteraceae flower numbers. Annals of Botany. 117(5), 925-935 (link)

ある植物の個体ごとに花びらの数をカウントし、個体数を集計したものです。この後の論文([11])を読むと分かりますが、同じ種類の植物でも調査の場所によって分布がかなり異なるようです。もちろん植物の種類によっても分布は変わります。かなり面白いです。基本的にはoverdispersionの場合が多そうですが、ここではunderdispersionのデータを取り上げます。

本来は論文中に記述があるように、花びらの発生過程を考慮して分布を導くのがベストと思います。途中でerf関数が出てきますので、考察の基点は論文と異なりますが、分子の拡散現象と密接に関係しているのかなと思いました(次回の記事参照)。しかし、この記事ではそこまで深堀りしないで、単に上で紹介した超幾何分布を除く3つの分布をあてはめてみようとおもいます。

Stanコードだけ載せます。なお、どの分布も1chainあたり1秒以内に収束しました。

一般化ポアソン分布

推定結果はlambdaが境界値である-1にべったり張り付いていたので、モデルはダメそうです。この分布はデータのような激しいunderdispersionを表現できないのが原因と思います。

Conway-Maxwell-Poisson分布

上の式のままだと \lambda の値が大きくなりすぎてMCMCが非効率になります。そこで、[9]に記述がある以下の再パラメータ化をしました。

正規化項を別途計算しないといけないので少し複雑になっています。推定結果はmunuの中央値と95%ベイズ信頼区間がそれぞれ、5.65 [5.61, 5.68]と34.3 [31.6, 37.4]ほどになります。lambdaに直すと5.65^34.3ほどの値になりますので、データのような激しいunderdispersionにあてはめるのは厳しいのかもしれません。

なお、本来は確率質量関数を表すユーザ定義関数のsuffixは_log_likではなく_lpmfとするのが行儀よいですが、現在のStanのバージョンにはバグがあって、target +=と併せて使うことができません。次期バージョンでは直ります。

Double Poisson分布

正規化項を別途計算しないといけないので少し複雑になっています。推定結果はmusigmaの中央値と95%ベイズ信頼区間がそれぞれ、5.15 [5.12, 5.18]と0.03 [0.03, 0.03]ほどになります。個人的には、平均と分散をバラバラに変化させたい場合に、かなり自然にポアソン分布を2パラメータに拡張した分布になっているように思いました。

リアルデータ強い…

蟻本シリーズ 3 スライド最小値

今回は以下の問題を考えます。

長さNの数列x[1], x[2], ..., x[N]と数Kが与えられます。y[i] = min{x[i], x[i+1], ..., x[i+K-1]} (i = 1, ..., N-K+1)として定義される数列y[i]を計算しなさい。

この問題は両端キュー(デック, deque)を用いることで、なんと \mathcal{O} (N) のオーダーで解けます。詳しくは蟻本の4-4節(p.300)を読んでください。

ここでは蟻本から例題を引用してN = 5, K = 3, x = {1, 3, 5, 4, 2}, y = {1, 3, 2}の場合について簡単に説明します。

  • まずdeqは余裕をもって長さIの配列として定義し、両端キューの先頭を指すインデックスをs、末尾の次の要素を指すインデックスをtとします。そして、deqsからt-1の部分を両端キューとします。
  • 両端キューdeqxのインデックスiを格納します。ただし、両端キューの中でインデックスは昇順になるように格納します(すなわちdeq[j] < deq[j+1])。さらに各インデックスに対応するxの値も昇順になるように格納します(すなわちx[deq[j]] < x[deq[j+1]])。この結果、この先最小値となりうるxのインデックスだけを格納することができます。

具体的な構成の仕方は以下の通りです。

  • はじめにxK個分のインデックスを両端キューの末尾に格納していきます。ただし、途中で末尾のインデックスに対応するxの値(x[deq[t-1]])が、いま入れようとしているxの値(x[i])以上の場合には、x[deq[t-1]] < x[i]を満たすようになるまで末尾tを縮めます。(★)
  • K個分追加したら、K個のxの中の最小値を計算できます。両端キューの先頭の値deq[s]を使ったx[deq[s]]が最小値となります。したがってy[1] = x[deq[s]]です。そして、deq[s]が1ならば、次回から最小値を求める範囲の外になるため、sを1つ増やして両端キューを縮めます。(♪)
  • 次にy[2]を求めるため、x[deq[t-1]]x[K+1]の大きさを比べ、同じようにK+1を両端キューの末尾に追加します。以下この操作を繰り返します。

例題の場合のdeqの挙動を見ると以下になります。

f:id:StatModeling:20201106190138p:plain


以上は自分用の備忘録でした。以下では次のような問題を考えます。

上記のデータがあります。i列はインデックスです。X_true列は長さ100の数列です。X_obs列はそのうち部分的に与えられたものです。Y列はy[i] = min{X_true[i], X_true[i+1], ..., X_true[i+K-1]} (i = 1, ..., N-K+1)として定義される数列y[i]に、さらに測定ノイズを加えたものです。ここでK = 8とします。さてX_obsYからX_trueを推定することを考えます。ただし、X_trueの欠測部分に関しては事前分布をnormal(0, 5)とします。

Stanでの実装例は以下になりました。

  • 2~21行目: 両端キューを使ってスライド最小値を計算する関数です。
  • 10~11行目: 上記で説明した構成の仕方の(★)に対応しています。
  • 14~18行目: 同じく(♪)に対応しています。
  • 46行目: 値の分からないx_misを混ぜた状態で、スライド最小値を求めてmuに代入しています。
  • 52行目: muに測定誤差が加わって観測したデータYになっています。

実行するRコードは以下の通りです。

推定結果は以下になりました。

meanse_meansd2.5%25%50%75%97.5%n_effRhat
x_mis[1]0.890.074.20-6.11-2.200.603.669.9936861.00
x_mis[2]0.840.074.25-5.99-2.530.553.729.8332871.00
x_mis[3]0.890.074.38-6.17-2.510.583.8110.1537871.00
x_mis[68]1.800.083.88-3.53-1.331.414.1710.5423101.00
x_mis[69]1.680.083.86-3.77-1.461.264.1910.0825341.00
x_mis[70]-5.340.010.39-6.12-5.60-5.35-5.09-4.6040001.00
s_y0.370.000.030.320.350.370.390.4513421.00
lp__11.970.236.32-1.357.8212.3616.4723.287771.00

推定結果の図を描くと以下になります。黒点X_obs、ピンク点はY、黒線はX_true、青帯はMCMCサンプルから算出した、未知のX_trueの80%ベイズ信頼区間です。

f:id:StatModeling:20201106190143p:plain