Pythonで実装しながら緑本を学ぶ (第6章 GLMの応用範囲を広げる -ロジスティック回帰など-)

データ解析のための統計モデリング入門(通称、緑本)を読み進めています。
述べられている理論を整理しつつ、Rでの実装をPythonに置き換えた際のポイントなども深掘りしていきます。

データ解析のための統計モデリング入門――一般化線形モデル・階層ベイズモデル・MCMC (確率と情報の科学)

今回は第6章です。実装は以下で公開しています。

introduction_to_machine_learning_with_python/chapter6.ipynb at master · ohke/introduction_to_machine_learning_with_python · GitHub

6 GLMの応用範囲を広げる -ロジスティック回帰など-

6.1 さまざまな種類のデータで応用できるGLM

第5章までは、ポアソン分布・対数リンク関数のGLMを扱ってきましたが、確率分布・リンク関数・線形予測子を組み替えることで様々なデータを表現できるようになる。

以下の表は、よく使われる確率分布とリンク関数の組み合わせです。なお、乱数生成はscipy.stats、familyとlinkの指定はstatsmodelsを使った場合の指定方法です。

確率分布 乱数生成 family指定 リンク関数 link指定
離散 二項分布 binom Binomial() ロジット logit
離散 ポアソン分布 poisson Poisson() ログ log
離散 負の二項分布 nbinom NegativeBinomial() ログ log
連続 ガンマ分布 gamma() Gamma() ログ? log
連続 正規分布 norm() Gaussian() 恒等リンク identity

例えば、平均2・分散1の正規分布の乱数を5つ生成する場合は、以下のようになります。他の確率分布でもrvs()メソッドを使うことで生成できます

import scipy

scipy.stats.norm(2, 1).rvs(5)
# array([ 2.976,  0.886,  2.72 ,  1.38 ,  0.664])

ガウス分布・ログリンク関数のモデルを作る場合は、以下のように指定します。statsmodels.formula.api.familiesに確率分布、statsmodels.formula.api.linksにリンク関数用のPython関数がそれぞれ定義されています。

import statsmodels.formula.api  as smf

model = smf.glm(formula='y ~ x', data=data, family=sm.families.Gamma(link=sm.families.links.log))

6.2 例題:上限のあるカウントデータ

二項分布は上限の有るカウントデータ(応答変数が  y \in {0,1,2, \dots ,N} )となる現象のばらつきを表現するために使う。「同じ条件のN個の内、y個は陽性、N-y個は陰性」という構造のデータの説明に適している。

架空植物100個体のそれぞれが持つ  N_i 個(全て8に固定)の種子を観察し、発芽能力がある生きた種子は  y_i 個、死んだ種子は  N-y_i 個というデータが得られたとします。体サイズや施肥処理によって、生存確率に与える変化を調べます。

  • 個体iの種子の生存確率を  q_i とする
  • 個体の大きさ  x_i と施肥処理の有無  f_i ("T"ならば有り、"C"ならば無し)が  q_i に影響を与えるとする

説明変数(  x_i f_i )と応答変数(  y_i )の分布を見てみると、体サイズが大きくなるほど、また、施肥処理すると生存種子数が増えることが伺えます。

f:id:ohke:20180210084911p:plain

6.3 二項分布で表現する「あり・なし」カウントデータ

「N個の内、y個」という構造のカウントデータを表現する統計モデルに二項分布がよく使われる。

二項分布の確率分布は以下で定義されます。  p(y \mid N,q) はN個中y個の事象が起きる確率です。

$$ p(y \mid N,q)={}_n \mathrm{C} _r q^y (1-q)^{N-y} $$

二項分布でq={0.1, 0.3, 0.5, 0.8}の時の確率pをプロットします。全てのyにおけるpの総和を計算すると、1となることにも注目です。

f:id:ohke:20180210095319p:plain

Pythonではscipy.stats.binomで二項分布の確率分布を計算できます。

from scipy.stats import binom

# N=8, q=0.1の時に、y=1となる確率
p = binom.pmf(1, 8, 0.1)

6.4 ロジスティック回帰とロジットリンク関数

確率分布に二項分布、リンク関数にロジットリンク関数を指定したGLMが、ロジスティック回帰です。
二項分布では、事象が起きる確率(ここでは生存確率)  q_i を指定する必要があります。 q_i の値の範囲  0 \geq q_i \geq 1 を表現するために適したリンク関数が、ロジットリンク関数です。

ロジットリンク関数は、ロジスティック関数の逆関数です。
ロジスティック関数は、以下の式で表現されます。  z_i は線形予測子  z_i=\beta_1+\beta_2 x+ \dots です。

$$ q_i=logistic(z_i)=\frac{1}{1+\exp(-z_i)} $$

zを-6〜6で変化させた時に、qは以下のような曲線(シグモイドカーブ)を描きます。zがどのような値になっても  0 \geq q_i \geq 1 に収まっていて都合が良いこと、またz=0でq=0.5となることに着目してください。

f:id:ohke:20180210104439p:plain

 z_i=\beta_1+\beta_2 x における、 \beta_2=2 のとき(左図)と、  \beta_1=0 のとき(右図)のシグモイドカーブをそれぞれ描いてみます。 \beta_1=0, \beta_2=-1 においては右肩下がりになっており、負の傾きも表現できていることが確認できます。

f:id:ohke:20180210104735p:plainf:id:ohke:20180210104826p:plain

ロジスティック関数を変形したときの、

$$ \log \frac{q_i}{1-q_i}=z_i $$

左辺がロジット関数となります。

$$ logit(q_i) = \log \frac{q_i}{1-q_i} $$

それではロジスティック回帰によるパラメータの推定を行います。

尤度関数と対数尤度関数は以下で定義され、 \log L を最大にする推定値  \hat{\beta_j} を探し出します。

$$ L({\beta_j})=\prod_i {}_n \mathrm{C} _r q_i^{y_i}(1-q_i)^{N_i-y_i} $$

$$ \log L({\beta_j})=\sum_i {\log {}_n \mathrm{C} _r+y_i\log(q_i)+(N_i-y_i)\log(1-q_i)} $$

Rでは以下のように記述します。応答変数は(生存した数, 死んだ数)の2列100行で表現するため、cbindでその組み合わせを生成しています。

> glm(cbind(y, N-y) ~ x + f, data = data, family = binomial)

Pythonでは、statsmodelsで以下のように記述すると、同じ計算を行えます。cbindではなくI(N-y)となっている点に注意してください。
statsmodels.formula.apiのformulaで渡される式の解析にはpatsyが使われています。patsyにはcbindと同等の組み合わせを作るI()が定義されており、y+I(N-y)としています。

http://patsy.readthedocs.io/en/latest/index.html

import statsmodels.formula.api as smf

results = smf.glm(formula='y + I(N - y) ~ x + f', data=data, family=sm.families.Binomial()).fit(disp=0)

結果を出力すると、  \hat{\beta}={\beta_1=-19.54, \beta_2=1.95, \beta_3=2.02} の時に最大対数尤度-133.11(残差123.03)が得られていることがわかります。

f:id:ohke:20180210130003p:plain

ロジット関数の線形予測子を得られた値で置き換えると、以下の式になります。
左辺は(生存する確率)/(生存しない確率)のオッズになっており、例えばxが1増加するとオッズは  =\exp(1.95)=7.06 倍になります。

$$ \frac{q_i}{1-q_i}=\exp(\beta_1+\beta_2 x_i+\beta_3 f_i)=\exp(\beta_1)+\exp(\beta_2 x_i)+\exp(\beta_3 f_i)=\exp(-19.54)+\exp(1.95x_i)+\exp(2.02f_i) $$

最後に、以下の4つのモデルからAICによる選択を行います。AICについては第4章の投稿も参考にしてください。

  • 一定モデル(定数)
  • xモデル
  • fモデル
  • x+fモデル

Rであれば、MASS packageのstepAICという関数を使えば、ネストしたモデルからAICで適切にパラメータを選択されるようです。
Pythonでは準じたものが無さそうなので、1つずつ線形予測子を定義してAICを計算しました。x+fモデルが272で最小となったため、このモデルを選択すべきとなります。

formulas = [
    'y + I(N - y) ~ 1',
    'y + I(N - y) ~ x',
    'y + I(N - y) ~ f',
    'y + I(N - y) ~ x + f'
]
results = []

for formula in formulas:
    results.append(smf.glm(formula=formula, data=data, family=sm.families.Binomial()).fit(disp=0))
    
for i in range(4):
    print(formulas[i], ':', results[i].aic)

# y + I(N - y) ~ 1 : 644.409341662
# y + I(N - y) ~ x : 364.345443284
# y + I(N - y) ~ f : 637.759753457
# y + I(N - y) ~ x + f : 272.211129285

6.5 交互作用項の入った線形予測子

交互作用項を追加した線形予測子を新たに導入します。
交互作用項は説明変数間の積で、観測データの複雑なパターンの抽出を狙ったものです。

Pythonでも、xとfの交互作用であればx:fで表現できます。

result = smf.glm(formula='y + I(N - y) ~ x + f + x:f', data=data, family=sm.families.Binomial()).fit()

最尤推定の結果、  \hat{\beta_4}= 0.22 となっています。fの係数  \beta_3 が-0.06とほとんど0に近づいてしまっており(交互作用項無しの場合は2.02)、  \beta_3 から  \beta_4 へ重みが移っています。

f:id:ohke:20180211104837p:plain

交互作用項無し(左図)と有り(右図)でそれぞれ予測値をプロットしてみると、モデル全体への変化は見られないことがわかります。AICで比較しても、交互作用項無しで272.2だったのに対して、有りで273.6と悪くなっています。

f:id:ohke:20180211110116p:plainf:id:ohke:20180211110126p:plain

上で見たように、交互作用項を過大推定されることで、交互作用項を多数含んだ統計モデルはAICが最良になることがよくある。説明変数では説明できない(不足している)データでは特にその傾向が強いため、むやみに交互作用項を入れるのは避けるべき。

Red wine quality data setを使ったロジスティック回帰と交互作用項

UCIで提供されているRed wine quality data setに対して、ロジスティック回帰と交互作用項を適用してみます。

https://archive.ics.uci.edu/ml/datasets/Wine+Quality

このデータセットは、1599レコードからなります。各レコード0〜10(実際にデータがあるのは3〜8)の評価値と、アルコール度数やpH値などの情報11項目の値を持ちます。今回は、酢酸含有量(va)とアルコール度数(a)を説明変数、評価値(q)を応答変数とするモデルを作ります。

f:id:ohke:20180211224027p:plain

酢酸含有量およびアルコール度数と評価値の関係をプロットします。酢酸含有量と評価値には負の相関(左図)、アルコール度数と評価値には正の相関(右図)があることがわかります。

f:id:ohke:20180211225002p:plainf:id:ohke:20180211225008p:plain

ロジスティック回帰

確率分布を二項分布、線形予測子を  \beta_1+\beta_2 va + \beta_3 a とした、リンク関数をロジット関数に設定したロジスティック回帰を行う。

result = smf.glm(formula='q + I(N - q) ~ va + a', data=data, family=sm.families.Binomial()).fit()

 \hat{\beta}={\hat{\beta_1},\hat{\beta_2},\hat{\beta_3}}={-0.80, -0.57, 0.13} で、AIC=4719のモデルが得られました。

f:id:ohke:20180211225622p:plain

交互作用項

線形予測子に交互作用項を追加した  \beta_1+\beta_2 va + \beta_3 a + \beta_4 \times va \times a において、ロジスティック回帰を行う。

result = smf.glm(formula='q + I(N - q) ~ va + a + va:a', data=data, family=sm.families.Binomial()).fit()

結果としては、例題同様、vaの係数  \beta_2 多項式の係数  \beta_4 へ移る形となり、モデルを複雑にするだけの結果となりました(AICも4721で、交互作用項を含むことで予測精度は落ちていると判定できます)。

f:id:ohke:20180211230057p:plain

6.6 割算値の統計モデリングはやめよう

観測値の割算による統計モデリングは、2つの不具合を含む。ロジスティック回帰では「N個の内y個で事象が起きる確率」を明示的に扱うことで、割算値の使用を回避する。

  • 割算値にすることで、情報が失われる (3/10と300/1000は確からしさは異なる)
  • 誤差が入った数量同士の割算値は、確率分布が複雑になる (分子と分母が独立でない場合、さらにややこしい)

単位面積や単位時間あたりの数量を扱う上で便利なのがオフセット項です。

「観測地iにおける植物個体の密度が明るさ  x_i に与える影響を知る」というタスクを考えます。

  • 100箇所で調査  i \in {1, 2, \dots, 100}
  • 観測地iの明るさ  x_i と、植物個体数  y_i を計測
  • ただし、観測地毎の面積  A_i は異なる

平均個体数  \lambda_i は面積×個体密度で求められます。このうち、個体密度は  x_i に依存しているため、以下の式で表現できます。これは、線形予測子  z_i=\beta_1+\beta_2x_i+\log A_i・対数リンク関数・ポアソン分布のGLMです(もちろん割算無しです)。

$$ \lambda_i=A_i \times \mbox{個体密度}=A_i \exp(\beta_1+\beta_2x_i)=\exp(\beta_1+\beta_2 x_i+\log A_i) $$

 \log A_i には係数がついていない定数で、オフセット項と呼ばれます。

statsmodelsでも、GLMにoffsetオプションが提供されており、以下のように指定します。

result = smf.glm(formula='y ~ x', offset=np.log(data['A']), data=data, family=sm.families.Poisson()).fit()

6.7 正規分布とその尤度

正規分布(ガウス分布)は、連続値のデータを統計モデルで扱うための確率分布です。平均値  \mu と、標準偏差  \sigma を指定します。確率密度関数  p(y\mid\mu,\sigma) は以下のとおりです。


p(y\mid\mu,\sigma)=\frac{1}{\sqrt{2\pi\sigma^2}}\exp{-\frac{(y-\mu)^2}{2\sigma^2}}

3パターンの確率密度関数を図示します。

f:id:ohke:20180211143049p:plain

ポアソン分布のように「x回発生する確率」といった離散確率分布と異なり、連続確率分布は「x1からx2までに収まる確率」といった範囲に入る確率の表現となります。例えば、 \mu=0, \sigma=1 正規分布において、yが1.2〜1.8となる確率  p(1.2\le y \le 1.8 \mid \mu,\sigma) は、 積分の形で  \int_{1.2}^{1.8} p(y \mid \mu,\sigma)dy となります。
Pythonでは、累積分布関数cdfで計算できます。

norm.cdf(1.8, 0, 1) - norm.cdf(1.2, 0, 1)
# 0.079

正規分布最尤推定では、尤度を確率密度の積で計算します。対数尤度関数は以下になります。  sigma^2 が0に近い場合、AICや逸脱度が負の値になることもあります。

$$ \log L(\mu,\sigma)=-0.5N\log (2\pi\sigma^2)-\frac{1}{2\sigma^2}\sum_i(y_i-\mu)^2 $$

対数尤度関数を見ると、  \sum_i(y_i-\mu)^2 を最小となる  \hat{\mu} において  \log L(\mu, \sigma) が最大となり、最小二乗法による推定と同等であることがわかります。

6.8 ガンマ分布のGLM

ガンマ分布は、取りうる確率変数が0以上の連続確率分布で、確率密度関数は以下です。平均は  \frac{s}{r} 、分散は  \frac{s}{r^2}=\frac{\mbox{平均}}{r} となります(sはshape、rはrateパラメータと呼ばれます)。


p(y \mid s,r)=\frac{r^s}{\Gamma(s)}y^{s-1}\exp(-ry)

3パターンの確率密度関数を図示します。

f:id:ohke:20180211155304p:plain

Python確率密度関数は以下のように計算できます。scaleは  \frac{1}{r} です。

from scipy.stats import gamma

gamma.pdf(1, a=1, scale=1/1) 
# 0.368

Boston housing data setを使った正規分布とガンマ分布のGLM

scikit-learn付属のBoston housing data setを使い、正規分布とガンマ分布でGLMを行います。

https://www.cs.toronto.edu/~delve/data/boston/bostonDetail.html

このデータセットは、506レコードからなります。住宅価格の中央値と、犯罪発生率や平均部屋数などの13項目の値を持ちます。今回は、平均部屋数(RM)と低所得者の割合(LSTAT)を説明変数、住宅価格の中央値(y)を応答変数とするモデルを作ります。

f:id:ohke:20180211232545p:plain

正規分布

目的変数yが連続値ですが、0以上の値のため、正規分布ではあてはまりが良くなさそうですが、線形予測子  \beta_1+\beta_2 RM 、恒等リンク関数として最尤推定を行います。

result = smf.glm(formula='y ~ RM', data=data, family=sm.families.Gaussian()).fit()

予測値をプロットすると以下のようになります。マイナスの予測値を含むこと(左下端)、恒等リンク関数のため直線回帰となっていることが確認できます。

f:id:ohke:20180211232927p:plain

さらに、LSTATを線形予測子に追加してみます。

result = smf.glm(formula='y ~ RM+LSTAT', data=data, family=sm.families.Gaussian()).fit()

実測値と予測値をプロットしました。またLSTAT={5, 15, 25, 35}の場合の予測値も直線で重ねています。LSTATが高くなるほど、直線が下に来ていることがわかります。

f:id:ohke:20180211233930p:plain

ガンマ分布

確率分布はガンマ分布、線形予測子は  \beta_1+\beta_2 RM 、リンク関数にはlog関数を使って、最尤推定を行います。応答変数は0以上のため、正規分布よりも適切そうです。

result = smf.glm(formula='y ~ RM', data=data, family=sm.families.Gamma(link=sm.families.links.log)).fit()

f:id:ohke:20180211234738p:plain

リンク関数にlogを指定しているため、右肩上がりの曲線で予測されていることがわかります。

f:id:ohke:20180211234753p:plain

Pythonで実装しながら緑本を学ぶ (第5章 GLMの尤度比検定と検定の非対称性)

データ解析のための統計モデリング入門(通称、緑本)を読み進めています。
述べられている理論を整理しつつ、Rでの実装をPythonに置き換えた際のポイントなども深掘りしていきます。

データ解析のための統計モデリング入門――一般化線形モデル・階層ベイズモデル・MCMC (確率と情報の科学)

今回は第5章です。実装は以下で公開しています。

introduction_to_machine_learning_with_python/chapter5.ipynb at master · ohke/introduction_to_machine_learning_with_python · GitHub

5 GLMの尤度比検定と検定の非対称性

検定は、推定された統計モデルを比較する方法のひとつ。 どのようなモデルにも適用可能な検定として、尤度比検定を導入する。

5.1 統計学的な検定のわくぐみ

代表的な統計学的な検定の枠組みとして、Neyman-Pearsonの検定があります。この検定は以下の手順に従います。
1.と2.は第4章まで行ってきたモデル作りですが、3.以降は「帰無仮説が正しい」という命題が否定できるかどうかを調べます。

  1. データを確定する
    • 最後までそのデータを、常に全て、使う
  2. 統計モデルを作り、パラメータの最尤推定を行う
    • (比較用の)パラメータの少ないモデルを帰無仮説、帰無仮説をネストしたパラメータの多いモデルを対立仮説と呼ぶ
  3. 帰無仮説が真のモデルと仮定して、検定統計量の値が取りうる範囲を定める
    • 検定統計量は任意に設定する(今回は尤度比を使う)
    • 取りうる値の範囲が95%の場合、5%の有意水準を設定したという
  4. 対立仮説のモデルで得られた検定統計量がこの範囲からはみ出ているか確認する
    • はみ出ていれば、帰無仮説が棄却(対立仮説が支持)されたという

5.2 尤度比検定の例題:逸脱度の差を調べる

これまで使ってきた種子数データを使い、帰無仮説(一定モデル)が棄却できるかどうかを調べる。

  • 一定モデル:  \lambda_i が定数のモデルで、  \lambda_i=\exp(\beta_1)
  • xモデル:  \lambda_i が定数と体サイズxで定まるモデルで、  \lambda_i=\exp(\beta_1+\beta_2x)

最尤推定(ステップ2.)まで行った結果が、以下表となります。xモデルのほうが逸脱度は改善されていますが、ネストしたモデルの場合、パラメータの多いほうが常に逸脱度は小さくなります。

モデル 最大対数尤度 逸脱度 Interceptの最尤推定 xの最尤推定
一定モデル -237.64 475.28 2.06 -
xモデル -235.39 470.78 1.29 0.076

検定統計量の計算のために、尤度比を導入する。尤度比は(帰無仮説の最大尤度÷対立仮説の最大尤度)で、値が大きいほど対立仮説の方が当てはまりが良いと言えます。


\frac{\mbox{帰無仮説の最大尤度}}{\mbox{対立仮説の最大尤度}}=\frac{\mbox{一定モデルの最大尤度:}L_1^{*}}{\mbox{xモデルの最大尤度:}L_2^{*}}=\frac{\exp(-237.6)}{\exp(-235.4)}

さらに式変形して、この値  \Delta D_{1,2} を検定統計量とする。  \Delta D_{1,2} は逸脱度の差となります。

$$ \Delta D_{1,2} = -2 \times (\log L_1^{*} - \logL_2^{*}) $$

表より、逸脱度の差は4.5でした。この差が、パラメータxを追加したことで改善されたのか、それともたまたま良いあてはまりが得られたのか(=改善されていないのか)を調べていきます。

5.3 2種類の過誤と統計学的な検定の非対称性

Neyman-Pearsonの検定では、2種類の誤り(過誤)が起こりうるが、第一種の過誤のみを防ぐことに注意する(第二種の過誤は検討されません)。検定の非対称性と呼ばれます。

  • 帰無仮説が真のモデルであるにも関わらず、棄却される(第一種の過誤、type I error)
  • 帰無仮説が真のモデルでないにも関わらず、棄却されない(第二種の過誤、type II error)

第一種の過誤のみを防げばいいので、観測データについて帰無仮説モデル上で起こる確率が低い場合に棄却するだけで良くなり、シンプルになります。

  1. 帰無仮説が正しいものと仮定して、真のモデルを作る
    • ここでは一定モデルを真のモデルとする
  2. この真のモデルからデータを生成し、帰無仮説モデルと対立仮説モデルに当てはめて、たくさんの  \Delta_{1,2} を得る
    • 例えば、100個の  \Delta_{1,2} を計算する
  3.  D_{1,2}\ge 4.5 が起こる確率Pを評価する
    • Pの計測方法は、この後2つ紹介する

5.4 帰無仮説を棄却するための有意水準

確率Pの値によって帰無仮説を棄却できるかどうかを決定します。
Pが大きければ帰無仮説モデルでもよくあることなので棄却できませんし、Pが小さければ帰無仮説モデルでは珍しいので棄却できる(対立仮説モデルを支持する)ことになります。

この棄却できるかどうかのPの閾値αは、有意水準と呼ばれます。多くの場合、有意水準には0.05が使われます(この値自体は決め打ちで良く、特に根拠は無いようです)。

P値の計測方法を2つを紹介します。

パラメトリックブートストラップ法

パラメトリックブートストラップ法(PB法)はシミュレーションに基づいた方法で、帰無仮説モデルから乱数でデータを大量生成し、検定統計量(ここでは逸脱度の差  \Delta_{1,2} )の確率分布を調べます。
生成された  \Delta_{1,2} 100個の内、4.5以上が12個であれば  P=0.12 、と計算します。αを0.05とすると、P<α (0.12<0.05)のため、帰無仮説は棄却できないと評価されます。

  • メリット
    • どのような統計モデルでも適用できる
    • 元のデータ数が小さくても、サンプルサイズを大きくすれば高い精度が得られる
  • デメリット
    • 生成するデータ数を適切に設定する必要がある

Pythonでは以下のような実装となります。

def pb(data, n):
    # パラメトリックブートストラップ法(PB法)によるP値の計算
    const_model_data = data.copy() # 種子数データ100行(各行は種子数yと体サイズxの2列)をコピー
    pb_d = []

    # n個のデータセットからn個の逸脱度の差を計算
    for i in range(n):
        # 一定モデルから目的変数100個生成
        const_model_data['y'] = np.random.poisson(const_model_results.params['Intercept'], len(data))

        # 逸脱度の差を計算
        const_model_llf = smf.poisson('y ~ 1', data=const_model_data).fit(disp=0).llf
        x_model_llf = smf.poisson('y ~ x', data=const_model_data).fit(disp=0).llf
        pb_d.append(-2 * (const_model_llf - x_model_llf))
        
    return pb_d
   
# 1000個のデータセットで計算 
pb_d = pb(data, 1000)

# P値の計算
p = (pb_d > d).sum() / len(pb_d)

逸脱度の差は以下の分布となります。この時、4.5を超えるのは1000個中41個で、P値は0.041と計算されます。
α=0.05とすると、P<αのため、帰無仮説(一定モデル)は棄却され、対立仮説(xモデル)が支持されたと言えます。

f:id:ohke:20180202085436p:plain

ばらつきを考慮したデータセット数を適切に設定する必要があります。
100個のデータセットでP値を計測したところ、0.05以上(つまり帰無仮説が棄却できない)が29個でした。

f:id:ohke:20180203102532p:plain

 \chi^2 分布を使った近似計算

検定統計量  D_{1,2} のばらつきを、自由度1(パラメータ数1のため)の  \chi^2 分布で近似する方法です。

 \chi^2 分布近似は、元のデータ数が小さい場合に精度が高くありません。
その場合、PB法にするか、データのばらつきが(ポアソン分布ではなく)等分散正規分布に従うならば別の確率分布を使う方が、正確なP値が得られます。適切な確率分布、検定統計量の特性によって異なります。

  • 平均の差を検定統計量とする場合は、t分布
  • 分散比を検定統計量とする場合は、F分布

この近似計算ではANOVAが使われます。ANOVAについては、こちらの解説(PDF)が詳しいです。

https://www.jstage.jst.go.jp/article/kagakutoseibutsu/51/7/51_483/_pdf

Rでは、構築済みの一定モデルとxモデルを使って計算できます。

> fit1 <- glm(y ~ 1, data = d family = poisson)
> fit2 <- glm(y ~ x, data = d family = poisson)
> anova(fit1, fit2, test="Chisq")

statsmodelsでも類似のanova_lmが提供されていますが、GLMには適用できません(以下のコードはエラーとなります。おそらく線形回帰で求める残差二乗和ssrがプロパティに無いよ、と怒られてます[参考])。

import statsmodels.api as sm

# エラーになります
sm.stats.anova_lm(const_model_results, x_model_results, test='Chisq')

要するに、自由度1の  \chi^2 分布において  D_{1,2} が4.5(これはstatsmodelsで計算済み)を超える確率を求めれば良いので、以下で十分かと思います(自信がなく間違っているかもです)。
以下では、自由度1の時の逸脱度の差(ここでは4.5)の生存関数の値を求めています。つまり4.5以上の値が得られる確率です。Pは0.034で、α以下のため、棄却できると結論付けられます。

from scipy.stats import chi2

p = chi2.sf(x=d, df=1) # 自由度1のカイ二乗分布における逸脱度の差(4.5)以上の確率を計算

5.5 「帰無仮説を棄却できない」は「差がない」ではない

 P \ge \alpha の場合は、帰無仮説を棄却できないだけで、「帰無仮説が正しい」ということにはならない。

Neyman-Pearsonの検定では、第二種の過誤の確率  \beta を評価しません。
第二種の過誤を検討する時は、帰無仮説を棄却できる確率  1-\beta がよく使われます(  1-\beta 検定力と呼ばれます)。サンプルサイズを大きくすることなどで、この検定力を高めることができます。

5.6 検定とモデル選択,そして検定された統計モデルの解釈

尤度比検定では、帰無仮説の棄却のみに着目しているため、帰無仮説が棄却された後に残る対立仮説が良いモデルなのかどうかは解釈が必要となる。

Pythonで実装しながら緑本を学ぶ (第4章 GLMのモデル選択)

前回から引き続き、データ解析のための統計モデリング入門(通称、緑本)を読み進めています。
述べられている理論を整理しつつ、Rでの実装をPythonに置き換えた際のポイントなども深掘りしていきます。

データ解析のための統計モデリング入門――一般化線形モデル・階層ベイズモデル・MCMC (確率と情報の科学)

今回は第4章です。実装は以下で公開しています。

introduction_to_machine_learning_with_python/chapter4.ipynb at master · ohke/introduction_to_machine_learning_with_python · GitHub

4 GLMのモデル選択 -AICとモデルの予測の良さ-

モデルが複雑になるほど、観測データに対する当てはまりは良くなってしまうため、「当てはまりの良いモデル=良いモデル」ではない。

「良い予測を行うモデル=良いモデル」という前提のもと、AICではこの「予測の良さ」を測る。

第3章で扱われている架空植物100個体の種子数のデータを使います。以下の著者サイトから入手できます。目的変数は種子数(y)です。
http://hosho.ees.hokudai.ac.jp/~kubo/ce/IwanamiBook.html

この投稿では種子数データと呼ぶことにします。

4.1 データはひとつ、モデルはたくさん

種子数を予測するモデルを検討する。

前章で現れた3つのモデルに、一定モデル(  \lambda=exp\beta_1 )を加えた4つのモデルについて考える。

  • 体サイズ(  x_i )が影響するxモデル(xモデル)
  • 施肥処理(  f_i )が影響するyモデル(yモデル)
  • 体サイズと施肥処理の両方が影響するx+fモデル
  • 体サイズも施肥処理も影響しない一定モデル

xモデルで推定した結果は以下となります。第3章で得られたものと同じです。

results = sm.GLM(data['y'], sm.add_constant(data['x']), family=sm.families.Poisson()).fit()

f:id:ohke:20180129092531p:plain

4.2 統計モデルの当てはまりの悪さ:逸脱度

これまで、当てはまりの良さを対数尤度  \log L で測ってきました。最も当てはまりの良いモデルは最大対数尤度として、  \log L* とします。


\log L(\lambda)=\sum_i(y_i\log\lambda-\lambda-\sum_k^{y_i}\log k)

この最大対数尤度を使い、当てはまりの悪さを逸脱度Dとして定義する(-2をかけただけです)。


D=-2\log L*

ポアソン分布モデルで可能な最小逸脱度から、モデルの逸脱度Dを引いたものを、そのモデルの残差逸脱度とする。

ポアソン分布モデルで可能な最小逸脱度は、 y_1=6 なので  \lambda_1=6  y_2=6 なので  \lambda_2=6 ・・・と全データを使ってあてはめた、つまり  \lambda_i=y_i となるモデルの逸脱度となります。この比較用の統計モデルは(Rでは)フルモデルと呼ばれます。フルモデルの逸脱度D*は以下で計算されます。


D*=-2 \times \sum_i \log \frac{y_i^{y_i}exp(-\lambda)}{y_i!}

フルモデルの最大対数尤度は-192.89、逸脱度は385.78となります。
xモデルの逸脱度は470.78(-2*-235.39)であることから、残差逸脱度は85.0(470.78-385.78)と計算されます(上の結果の"Deviance"と値が一致していることがわかります)。

# フルモデルの最大対数尤度
full_ll = sum([poisson.logpmf(y_i, y_i) for y_i in data['y']])

# フルモデルの逸脱度(最小逸脱度)
full_deviance = (-2) * full_ll

この残差逸脱度が最大になるモデル(=最もあてはまりの悪いモデル)もあります。切片  \beta_1 のみの一定モデルがこれに該当し、Rではnull modelと呼ばれます。
この時の残差逸脱度は89.5となります。

results = sm.GLM(data['y'], np.ones(len(data)), family=sm.families.Poisson()).fit()

f:id:ohke:20180129092805p:plain

4.3 モデル選択規準 AIC

「予測の良さ」を重視したモデルの選択規準としてAICを導入する。

最尤推定するパラメータの個数をkとするとAICは以下で計算されます。 このAICが小さいモデルが良いモデルとなるため、最小のモデルを選択することになります。


AIC=-2(\log L*-k)=D+2k

残差逸脱度(あてはまりの悪さ)+パラメータ数×2なので、直感的には2kがモデルの複雑度に対するペナルティとなっているように見えます。

4.5 なぜAICでモデル選択してよいのか?

予測の良さを表す平均対数尤度を導入する。そして、最大対数尤度と平均対数尤度の差であるバイアスが、パラメータの数と一致することを示すことで、AICによって予測の良さを計測できることを説明する。

まずは真のモデルを知っている状態からスタートするために、  \lambda=8 ポアソン分布モデルから生成される50個のデータを生成します。

np.random.seed(10) # 一定の値になるようにシードを0で固定

# xに4〜12のポアソン乱数をセット
data_random = pd.DataFrame(np.random.poisson(8, 50), columns=['y'])

この時の平均値は7.44となっており、真のモデルから少し外れていることがわかります。

f:id:ohke:20180127140815p:plain

この50個のデータを観測されたデータとして、一定モデルでの最尤推定を行います。
切片2.0069(  \fallingdotseq exp(7.44) )で、その時の最大対数尤度は-116.47となります。

model = smf.poisson('y ~ 1', data=data_random)
results = model.fit()

f:id:ohke:20180127141128p:plain

最大対数尤度は、たまたま得られた観測データに対するあてはまりの良さでした。
予測の良さ、つまり他のデータにも適用できるかどうかを調べたいのですが、今回は真のモデルを知っているため、この真のモデルから新たにデータ(ここでは50データ×200セット)を作り出します。
そして上で得られたモデルを使って各セットの対数尤度を計算し、その平均値を求めます。これを平均対数尤度(  E(\log L) と表記)と呼びます。

平均対数尤度を計算すると-122.48となり、最大対数尤度と比較すると約6小さい(=あてはまりが悪い)ことがわかります。

# 真のモデルから200セットのデータを生成
data_random_200 = [pd.DataFrame(np.random.poisson(8, len(data_random)), columns=['y']) for i in range(200)]

# 200セットのデータに対して、β1=2.0069で200個の対数尤度を計算
lls = []
for d in data_random_200:
    lls.append(sum(poisson.logpmf(d, math.exp(results.params['Intercept']))))
    
# 平均対数尤度を計算
elogl = np.sum(lls) / len(lls)

「1セットで最大対数尤度を求め、そのモデルを使って200セットの平均対数尤度を求める」というのを200回繰り返してプロットした結果が、以下です。概ね、平均対数尤度(青▲)が最大対数尤度(赤●)よりもあてはまりが悪いことがわかります(ただし、常に平均対数尤度<最大対数尤度とも限らず、ばらつきは大きいものになってます)。
最大対数尤度と平均対数尤度の差をバイアスと呼びます。今回の例では、200回の平均バイアスは1.25となりました。


b=\log L*-E(\log L)

f:id:ohke:20180127143912p:plain

一般的に真のモデルは不明です。つまり  E(\log L) は未知の値となるはずです。
しかし、最尤推定するパラメータをk個持つモデルの平均対数尤度の推定量は、 log L-kとなることが、解析的かつ一般的に導出されている。これにより、先程導入したバイアスb=パラメータ数kと言えることにも着目してください。一定モデルはk=1のため、今回はE(log L)=log L-1となります。

AICはこれに-2をかけたもので、  AIC=-2(\log L*-1) です。
平均対数尤度は予測の良さを表すので、AICの値が小さいほど、予測の良いモデルと言えます。

これまでは目的変数yだけのモデルを見てきました。これに説明変数xも導入して、2つのモデルを評価します。y、xのいずれも乱数で生成され、2つの変数の間に何の関係も無いデータとします(xは予測の役に立たない変数のはずです)。

  •  \log\lambda_i=\beta_1 : 一定モデル(k=1)
  •  \log\lambda_i=\beta_1+\beta_2x_i : xモデル(k=2)

まずはデータ200セットで最大対数尤度を比較します。
以下のグラフは(xモデルの最大対数尤度 - 一定モデルの最大対数尤度)の分布ですが、xモデルの方が平均すると0.46大きくなっています。関係ないはずのxを使った何らかのあてはめの方が最大対数尤度は良くなることが確認できます。

f:id:ohke:20180128161734p:plain

一方で、平均対数尤度の差(xモデルの平均対数尤度 - 一定モデルの平均対数尤度)を比較すると、今度は逆に一定モデルの方が平均0.48大きくなります。

f:id:ohke:20180128162527p:plain

したがって、バイアスの差(最大対数尤度の差 - 平均対数尤度の差)は平均0.95で、パラメータ数の差(2-1=1)に近い値となっていることがわかります。分布でもそれほどばらつきが大きくなっていません。
今回の例のように、ネストしたモデル(xモデルが一定モデルの変数  \beta_1 も包含している)の場合、バイアスもばらつきが小さくなります。

f:id:ohke:20180128164353p:plain

200セットの内の1つ目のデータでAICを比較すると、一定モデル:258.1 < xモデル:259.0で、一定モデルを選択すべきとなります(逆に、説明変数xを追加したことで最大対数尤度が1以上増加した場合、xモデルを選択すべきと判定されます)。

欠席数データに対してAICでモデルを選択する

最後に、以前の投稿でも使った欠席数のデータで、AICによるモデル選択を見ていきます。

このデータはUCIで提供されているStudent Performance Data Setです。目的変数は欠席数(absences)です(これまで同様、外れ値の除去や、欠席2回を1回とカウントするなど、若干の前処理を行っています)。
https://archive.ics.uci.edu/ml/datasets/student+performance

f:id:ohke:20180131140254p:plain

ここでは2つのモデルを比較します。

  • 年齢(age)が欠席数に影響を与えるとするageモデル
  • どの変数も欠席数に影響を与えないとする一定モデル
# ageモデルを作る
age_model = smf.poisson('absences ~ age', data=student_data)

f:id:ohke:20180131140426p:plain

# 一定モデルを作る
const_model = smf.poisson('absences ~ 1', data=student_data)

f:id:ohke:20180131140446p:plain

一定モデルの最大対数尤度-996.8に対して、ageモデル-982.7で、約5.9改善されていることがわかります。

次に、フルモデルを求めます。最大対数尤度は-392.9(最小逸脱度は786.7)となります。

# フルモデルの最大対数尤度
full_model_ll = sum([poisson.logpmf(y_i, y_i) for y_i in student_data['absences']])
# -392.854706449

# フルモデルの逸脱度(最小逸脱度)
full_model_deviance = (-2) * full_model_ll
# 785.709412899

最後に、フルモデルとの残差逸脱度を計算します。

# 一定モデルの残差逸脱度
const_model_d = -2 * (const_model_results.llf - full_model_ll)
# 1207.95059528

# ageモデルの残差逸脱度
age_model_d = -2 * (age_model_results.llf - full_model_ll)
# 1179.67358728

AICはそれぞれ一定モデルで1210.0(=1208.0+2)、ageモデルで1183.7(=1179.7+4)となり、ageモデルを選択すべき、という結論になります。