超幾何分布をマルコフ基底を使って計算する

2023-12-07

こちらは リブセンス Advent Calendar 2023 の 7 日目の記事です。社内 LT 会で喋ったことをより詳しく解説しています。

参考文献

本記事は共立出版の 代数的統計モデル を参考に書いています。フィッシャーの正確確率検定とか超幾何分布とか十分統計量とかは既知のものとして書かれているので、はじめは読むのに手こずりました。しかし話題はとても面白いですし、他に類書があまり無いので、良い本だと思います。

分割表の統計モデル

分割表とは

分割表とは、こんな感じのよくある集計表のことです (値に意味はありません)。各セルは非負整数で、一般には N0×N1×N_0 \times N_1 \times \cdots のように多次元まで考えることができます。

シナリオ Xシナリオ Yシナリオ Z
介入 A248135
介入 B414455
介入 C325157

よくある A/B テストの仮定

簡単のために 2×22 \times 2 の分割表を考えましょう。2×22 \times 2 の分割表は A/B テストで目にすることが多いんじゃないでしょうか。

クリックしたクリックしなかった
A パターン400600
B パターン500500

普通 A/B テストは、各パターンごとに収集するサンプルサイズを予め決めた上で行います (実際にはサンプルサイズではなくテスト期間を決めておくやり方もあると思いますが)。つまりサンプルサイズを所与として、そのうちどれほどのサンプルでクリックが発生したかを調べていることになります。

例えば上の表では A パターンのサンプルサイズは 1 行目の和の 1000 であり、そのうちいくつのサンプルでクリックされたかに興味があります。B パターンのサンプルサイズも 2 行目の和となり同様です。

したがって通常の A/B テストは、各行和を固定した上での分割表の統計的性質を調べていることになります。スローガン的に書くと以下のようになります。

条件付き確率 p(分割表各行和)p(\text{分割表} \mid \text{各行和}) はなんぼのもんじゃい

各行和ごとに固定しているため、各行 (各パターン) は独立です。また、各行は二項分布に従うものとして考えます。実際にはラプラス定理を使って正規近似を行って確率の計算するのが普通でしょう。

フィッシャーの正確確率検定

たまに「サンプルサイズが小さいときは正確な確率が計算できるフィッシャーの正確確率検定を使いましょう」という主張を見かけることがあります。フィッシャーの正確確率検定で考えているのは実は次です。

条件付き確率 p(分割表各行和と各列和)p(\text{分割表} \mid \text{各行和と各列和}) はなんぼのもんじゃい

行和だけではなく列和も固定して考えるのがフィッシャーの正確確率検定です。サンプルサイズが小さいかどうか、確率が正確に計算できるかどうか、というよりはそもそも統計的仮定が異なるのです。

A/B テストで列和を所与としていいものなのか、ということも別途議論が必要なポイントではありますが、本記事では数学的な側面について議論していきます。

さて、行和と列和を固定すると、分割表は超幾何分布に従います。記号の導入とともに定式化しておきましょう。

記号

  • I×JI \times J の 2 元分割表を x={xij}\bm{x} = \{x_{ij}\} と書く。
  • ii 行の和を xi+x_{i+} と書き、jj 列の和を x+jx_{+j} と書く。
  • すべてのセルの総和を nn とする。

用語

行和と列和を固定した分割表全体をファイバーと呼ぶ。

分割表の分布

ファイバー上の分割表は以下のような超幾何分布に従う。

p(xx1+,,xI+,x+1,,x+J)=(nx11,,xIJ)(nx1+,,xI+)(nx+1,,x+J)=i=1Ixi+! j=1Jx+j!n! i,jxij!\begin{aligned} p(\bm{x} \mid x_{1+}, \ldots, x_{I+}, x_{+1}, \ldots, x_{+J}) &= \frac{ \begin{pmatrix} n \\ x_{11}, \ldots, x_{IJ} \end{pmatrix} }{ \begin{pmatrix} n \\ x_{1+}, \ldots, x_{I+} \end{pmatrix} \begin{pmatrix} n \\ x_{+1}, \ldots, x_{+J} \end{pmatrix} } \\[3em] &= \frac{ \prod_{i=1}^I x_{i+}! \ \prod_{j=1}^J x_{+j}! }{ n! \ \prod_{i, j} x_{ij}! } \end{aligned}

具体的な分割表を例にどんな分布になるのかを見ておきましょう。

行和が 7 と 11、列和が 8 と 10 であるような分割表は全部で 8 つありますが、それらの出現確率はこちらの通りです。[3,4,5,6][3,4,5,6] が最頻値 (最頻表?) ですね。

[0783]0.004[1674]0.053[2565]0.222[3456]0.370[4347]0.264[5238]0.079[6129]0.009[70110]0.000\begin{aligned} &\begin{bmatrix} 0 & 7 \\ 8 & 3 \end{bmatrix} \qquad 0.004 \\ &\begin{bmatrix} 1 & 6 \\ 7 & 4 \end{bmatrix} \qquad 0.053 \\ &\begin{bmatrix} 2 & 5 \\ 6 & 5 \end{bmatrix} \qquad 0.222 \\ &\begin{bmatrix} 3 & 4 \\ 5 & 6 \end{bmatrix} \qquad 0.370 \\ &\begin{bmatrix} 4 & 3 \\ 4 & 7 \end{bmatrix} \qquad 0.264 \\ &\begin{bmatrix} 5 & 2 \\ 3 & 8 \end{bmatrix} \qquad 0.079 \\ &\begin{bmatrix} 6 & 1 \\ 2 & 9 \end{bmatrix} \qquad 0.009 \\ &\begin{bmatrix} 7 & 0 \\ 1 & 10 \end{bmatrix} \qquad 0.000 \\ \end{aligned}

超幾何分布を定常分布とするマルコフ連鎖

上の例では 2×22 \times 2 の分割表で、行和・列和ともに小さかかったため、8 つの確率を計算すれば分布全体を理解することができました。しかし一般には取りうる分割表の数が爆発的に増えますし、たくさんの階乗を計算しなくてはならないため、個別に確率値を計算するのは現実的ではありません。

そこで、超幾何分布を定常分布とするファイバー上のマルコフ連鎖を生成することで解決しようというのが本記事の主旨です。

マルコフ基底

マルコフ連鎖を生成するためには、ある分割表から同一ファイバー内の別の分割表へと遷移するステップを構成する必要があります。同一ファイバー内で遷移するということは、行和も列和も変化しないということです。

以下のように行和と列和が 00 であるような整数 (非負整数ではない) からなる表を足し引きすれば、同一ファイバー内で動き回ることができます。

[123456789]+[110110000]=[213366789]\begin{bmatrix} 1 & 2 & 3 \\ 4 & 5 & 6 \\ 7 & 8 & 9 \\ \end{bmatrix} +\begin{bmatrix} 1 & -1 & 0 \\ -1 & 1 & 0 \\ 0 & 0 & 0 \\ \end{bmatrix} =\begin{bmatrix} 2 & 1 & 3 \\ 3 & 6 & 6 \\ 7 & 8 & 9 \\ \end{bmatrix}

行和と列和が 00 であるような表の加減を繰り返し、ファイバー内の任意の分割表間を遷移することができれば (到達可能性)、マルコフ連鎖が構成できそうな感じがしますね。

ではマルコフ連鎖の素となるマルコフ基底を導入しましょう。

mod

B\mathcal{B} を、行和と列和が 00 であるような表の集合とする。B\mathcal{B} の要素を足し引きすることによって、ファイバー F\mathcal{F} 内の分割表 xx から yy へ移れるとき、xy (mod B)x \sim y \ (\mathrm{mod} \ \mathcal{B}) と書く。

xy (mod B)    defL,ziB, ϵ=±1, i=1,,L,s.t.y=x+i=1Lϵizi, x+i+1lϵiziF, l=1,,L\begin{aligned} x \sim y \ (\mathrm{mod} \ \mathcal{B}) \stackrel{\text{def}}{\iff} &\exists L, z_i \in \mathcal{B},\ \epsilon = \pm 1,\ i = 1,\ldots,L, \\ &\mathrm{s.t.}\quad y = x + \sum_{i = 1}^L \epsilon_i z_i,\ x + \sum_{i + 1}^l \epsilon_i z_i \in \mathcal{F},\ l = 1,\ldots,L \end{aligned}

マルコフ基底

B\mathcal{B} がマルコフ基底であるとは、以下を満たすことである。

F, x,yF, xy (mod B)\forall \mathcal{F},\ \forall x, y \in \mathcal{F},\ x \sim y \ (\mathrm{mod}\ \mathcal{B})

マルコフ基底の要素をいくつか足し引きすれば、ファイバー内を自由に動き回れるということです。しかも、ファイバーごとにマルコフ基底を定めているわけではなく、全てのファイバーに対してマルコフ基底がひとつ定義されていることにも注意です。

マルコフ基底の例

行和と列和が 00 であるような表全体は自明なマルコフ基底ですね。任意の 2 つの分割表間を 1 ステップで遷移することができます。

また、分解可能モデル (たとえば 2 元分割表の独立モデルなど) と呼ばれるモデルにおいては、以下のような一箇所の 2×22 \times 2 の部分だけがプラマイ 11 になったような表全体もマルコフ基底になります。このような表を基本移動と呼びます。基本移動を足し引きするというのは、すぐ隣の分割表に遷移するようなイメージですね。

[00000011000110000000]\begin{bmatrix} 0 & 0 & 0 & 0 & 0 \\ 0 & 1 & -1 & 0 & 0 \\ 0 & -1 & 1 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 \\ \end{bmatrix}

アルゴリズム

天下り的ではありますが、そのアルゴリズムを書き下しましょう。とは言っても、よく見るとメトロポリス・ヘイスティングス法を超幾何分布に具体化したものなので、とくに難しいことはしていないことが分かるでしょう。というわけでこのアルゴリズムで得られるサンプルは、漸近的に超幾何分布に従います。

設定

  • 観測した分割表: xox^o
  • マルコフ基底: B\mathcal{B}
  • 超幾何分布: ff
  • ファイバー: F\mathcal{F}

アルゴリズム

ステップ 1: x=xox = x^o

ステップ 2: zBz \in \mathcal{B} をランダムに選ぶ。符号 ϵ{1,1}\epsilon \in \{-1, 1\} を等確率 1/21/2 で選ぶ。

ステップ 3: もし x+ϵzFx + \epsilon z \notin \mathcal{F} ならば、xnext=xx_{\mathrm{next}} = x と置きステップ 2 に進む。もし x+ϵzFx + \epsilon z \in \mathcal{F} ならば、uu を区間 [0,1][0, 1] の一様乱数とする。

ステップ 4: もし uf(x+ϵz)f(x)u \leq \frac{f(x + \epsilon z)}{f(x)} ならば xnext=x+ϵzx_{\mathrm{next}} = x + \epsilon z と置いてステップ 2 に進む。もし u>f(x+ϵz)f(x)u > \frac{f(x + \epsilon z)}{f(x)} ならば xnext=xx_{\mathrm{next}} = x と置いてステップ 2 に進む。

これを予め決めたステップ数だけ繰り返します。適宜 burn in を設けたり、サンプルを配列に格納したりは用途に応じて実装すれば良いでしょう。

上のアルゴリズムを見て、あれ、超幾何分布の計算コストが高いから MCMC を使ってるのにアルゴリズム内で超幾何分布使ってるじゃんと思われた方もいるでしょう。一般化されたアルゴリズムにおいては数式上超幾何分布が現れますが、基本移動だけを考えるともっと簡単になります。そもそも計算対象の確率比を考えることで、基準化定数を知らなくていい、みたいなところもメトロポリス・ヘイスティングス法の強みでしたよね。

さて、超幾何分布は以下のような数式で表されるのでした。

f(x)=i=1Ixi+! j=1Jx+j!n! i,jxij!\begin{aligned} f(\bm{x}) = \frac{ \prod_{i=1}^I x_{i+}! \ \prod_{j=1}^J x_{+j}! }{ n! \ \prod_{i, j} x_{ij}! } \end{aligned}

xx に、例えば以下の基本移動を足した分割表を yy としましょう。

[110110000]\begin{bmatrix} 1 & -1 & 0 & \cdots \\ -1 & 1 & 0 & \cdots \\ 0 & 0 & 0 & \cdots \\ \vdots & \vdots & \vdots & \ddots \\ \end{bmatrix}

xxyy は行和、列和、総和が等しいことを踏まえると、2 つの超幾何分布の値の比はかなり簡単になることがわかります。

f(y)f(x)=i=1Iyi+! j=1Jy+j!n! i,jyij!/i=1Ixi+! j=1Jx+j!n! i,jxij!=i,jxij!i,jyij!=x11!x12!x21!x22!y11!y12!y21!y22!=x11!x12!x21!x22!(x11+1)!(x121)!(x211)!(x22+1)!=x12x21(x11+1)(x22+1)\begin{aligned} \frac{f(y)}{f(x)} &= \frac{ \prod_{i=1}^I y_{i+}! \ \prod_{j=1}^J y_{+j}! }{ n! \ \prod_{i, j} y_{ij}! } \Bigg/ \frac{ \prod_{i=1}^I x_{i+}! \ \prod_{j=1}^J x_{+j}! }{ n! \ \prod_{i, j} x_{ij}! } \\ &= \frac{\prod_{i, j} x_{ij}!}{\prod_{i, j} y_{ij}!} \\[1em] &= \frac{x_{11}! x_{12}! x_{21}! x_{22}!}{y_{11}! y_{12}! y_{21}! y_{22}!} \\[1em] &= \frac{x_{11}! x_{12}! x_{21}! x_{22}!}{(x_{11} + 1)! (x_{12} - 1)! (x_{21} - 1)! (x_{22} + 1)!} \\[1em] &= \frac{x_{12} x_{21}}{(x_{11} + 1)(x_{22} + 1)} \end{aligned}

実装

2×22 \times 2 の分割表に限定した実装は logicoffee/fisher-exact-test-mcmc に置いてあります。特筆すべきこともなく、上で説明したものをそのまま書き起こした感じですね。

README にも書いていますが、こんな感じでサンプリングの様子を観察することができます。

$ python3 main.py visualize 3 4 5 6 --sample 10

1
┏━━━┳━━━┓
┃  34  ┃
┣━━━╋━━━┫
┃  56  ┃
┗━━━┻━━━┛


2
┏━━━┳━━━┓
┃  25  ┃
┣━━━╋━━━┫
┃  65  ┃
┗━━━┻━━━┛


3
┏━━━┳━━━┓
┃  34  ┃
┣━━━╋━━━┫
┃  56  ┃
┗━━━┻━━━┛

...

まとめ

普段 Z 検定やカイ二乗検定くらいでしか触れない分割表ですが、結構奥が深いものですね。それでは本記事の要点をまとめておきましょう。

  • 行和と列和を固定すると、分割表は超幾何分布に従う。
  • 超幾何分布を定常分布とするマルコフ連鎖をマルコフ基底から構成できる。
  • マルコフ基底によっては各ステップの計算がかなりシンプルになる。