最近読んだ「データ解析のための統計モデリング入門――一般化線形モデル・階層ベイズモデル・MCMC (→ amazon)」に感化されて、モデル選択基準である、AIC、LOOCV (Leave One-Out Cross Validation) について実験してみた(参考:モデル選択の周辺の話の整理)。
LOOCV と AIC はある意味で両極端だ。LOOCV は利用するのに必要な仮定が少なく、直感的にも何をやっているのかわかりやすい(ただし計算量は非常に大きい)。一方で AIC は評価式の簡単さ(計算量の少なさ)とは裏腹に、背後には正則条件だとかネストだとかの直感的とは言いがたい仮定をしたりする。
まあ AIC とか LOOCV とかが常に正しいモデルを選択するわけじゃない、というのは直感的にはわかるけど、実際に試してみたことはなかったので、真のモデルを知っているという神の視点からみて、こいつらがどんな挙動を示すのかをシミュレーションで確かめてみた。
モデルの設定
真のモデルは
\begin{align*}
y_i&\sim \text{Poisson} (\lambda_i)\\
\log \lambda_i &= \beta_0+\beta_1x_1
\end{align*}
という GLM。これに対して、想定するモデルは4つ。
\begin{align*}
&y_i\sim \text{Poisson} (\lambda_i)\\
\\
&\text{Model 1 : } \log \lambda_i = \beta_0\\
&\text{Model 2 : } \log \lambda_i = \beta_0+\beta_1x_1\\
&\text{Model 3 : } \log \lambda_i = \beta_0+\beta_2x_2\\
&\text{Model 4 : } \log \lambda_i = \beta_0+\beta_1x_1+\beta_2x_2
\end{align*}
さらに、\(x_1, x_2\) は正の相関係数 0.7 をもつ二変量正規分布に従うとする。今回は \(\beta_0=1, \beta_1=0.5\) とした。
実験の方法
真のモデルからデータを1000回ほど発生させて、各データに対して AIC と LOOCV でモデル選択させる。言葉では語弊がありそうなので、ruby風擬似コード(実際のRスクリプトは一番下)
resultAIC = [] resultLOOCV = [] 1000.times { data = generate_from_true_model(sampleSize=100) resultAIC.push( modelSelect(data, method="AIC") ) resultLOOCV.push( modelSelect(data, method="LOOCV") ) # ようするにデータは 共通のもの をつかう # 1回の推定に用いるデータ数は 100 }
結果
結果は以下のようになった。可視化には以前紹介した tabplot パッケージを使っている。
- 一番左が AIC の選択結果
- 左から二番目が LOOCV の選択結果
- 右側3つはあとで説明
- …
- AIC は 13% ほど間違えている。真のモデル (Model 2) よりも大きな Model 4 が結構頻繁に選択される。Model 3 も一回だけ選ばれた。
- もっとも、件の本には AIC は真のモデルを選ぶためのものではなく、予測力の高いモデルを選ぶためのものだ、ということが書いてある(AICって汎化誤差を漸近展開することで導出した記憶あり)
- なので「モデル選択を間違えた」という表現は語弊があるのだろう(だけどここでは真のモデルを知っているという神の視点を採用して「間違える」という表現を使いつづける)
- …
- 一方、LOOCV は 17% ほど間違えている。AIC が正解するときは LOOCV も正解する傾向があるが、そうでないこともしばしば。クロス集計してみると
AIC \ LOOCV Model 2 Model 4 Model 2 814 53 Model 3 0 1 Model 4 12 120 となる。AIC が Model 4 を選んでしまう時 LOOCV もまた然り
- 真のモデルである Model 2 よりも小さな Model 1 は 1000 回のうち一度たりとも選択されることはなかった(データ数が 100 というのはそれなりに大きい?)
- …
- 右の3つは Model 4 の推定結果の各係数の p値(左から、切片、\(x_1\)、\(x_2\) : 実際のp値がどの範囲にあるのかは図からは分からないが、下の R スクリプトの summary(results.df) の部分に書いておいた)
- 帰無仮説 \(\beta_2=0\) はこの場合正しいので理論通りp値は [0,1] の範囲に一様分布している(一番右)
- このp値が 0.13 を下回ると急に AIC は間違いばかりになる(この言い回しもよくないかもしれない)
- LOOCV にも類似の傾向はあるが、そこまではっきりとはしていない。
ギモン
AICっていつ使えるのか(使ってもいいのか)イマイチ自信がない。「データ解析のための統計モデリング入門」には
ネストしているモデルを比較するときに、AICは有用なモデル選択基準になっているのでしょう(脚注:ネストしていない複数のモデルでAICによるモデル選択は可能でしょうか?たぶん問題ないだろうといった理由で使われているのが現状です)
ということが書いてある。
- {Model 1, Model 2, Model 4}もしくは{Model 1, Model 3, Model 4}というモデル集合はネスト関係にあるので、AIC をつかってよし
- model 2 と model 3 はネストしていない、これはどう考えればいいのだろう?
- こいつらを AIC で比較するということは、つまるところ尤度を比較しているだけにすぎない
- 確かに、モデルの複雑さ(パラメータ数)が同じならば尤度が大きくなるモデルを選べばいいでしょ、というのも妥当には感じるが…
- それではAICの売りである「データの当てはまりではなく予測力を比較する」という点が抜け落ちてしまう気がする。
- AIC にしろ、LOOCV にしろ、「予測力が高い」という基準でモデルを選んだ結果、真のモデルより大きな Model 4 が結構な頻度で選ばれた。しかし、「真の予測力」で比較すれば Model 2 のほうが高いはず。この原因はデータ数が有限であること「のみ」に依拠しているのだろうか?
一方で、英語版WikipediaのAICの項には
the likelihood-ratio test is valid only for nested models whereas AIC (and AICc) has no such restriction
なんて記述があったりしてますます混乱する(自分はネストしたモデルにしか使えない、という認識だった)。時間のあるときにもう一度AICの導出をやってみたい。
TODO
どういうときに「まちがい」が減るのか理解したい。
- データ数(いまは 100)を増やすと間違いは減るか?
- データ数を減らしていくと Model 1 のようなものが選択されることがあるのか?AICc にすると「まちがい」は減るのか?
- \(x1, x2\) の相関を減らしていくと間違い回数はどんな挙動をする?
- \(x_1\) の係数 \(\beta_1\) をゼロに近づけていくと間違いは増えそう
まとめ
かなり長くなったのでこれで一旦、終わりにします。時間があるときに上のTODOをやってみるかもしれません。
実験に使ったスクリプト
glm関数を40万回程度呼び出すので、ものすごい時間かかる(LOOCVのせい)
#------------------------------------------------- # AIC vs. LOOCV (Leave-one-out cross-validation) # # [なにをするの?] # -- モデル選択手法である AIC と LOOCV を比較してみる # -- 使うモデルは単純なポアソン回帰 (R の glm 関数を使う) # -- library(mvtnorm) generate.data <- function(n){ X <- rmvnorm(n,c(0,0),matrix(c(1,0.7,0.7,1),nc=2)) mu <- exp(0.5*X[,1]+1) Y <- rpois(n,mu) d <- as.data.frame(cbind(X,Y)) colnames(d) <- c("x1","x2","y") d } #------------------ # estimating GLM #------------------ estimate.models <- function(dat,wgt=rep(1,dim(dat)[1])){ models <- list() # Model 0 models[[1]] <- glm(y~1,data=dat,family=poisson,weights=wgt) # Model 1 models[[2]] <- glm(y~x1,data=dat,family=poisson,weights=wgt) # Model 2 models[[3]] <- glm(y~x2,data=dat,family=poisson,weights=wgt) # Model 3 models[[4]] <- glm(y~x1+x2,data=dat,family=poisson,weights=wgt) models } #-------------------------- # Model selection by AIC #-------------------------- model.select.aic <- function(d){ models <- estimate.models(d) which.min(sapply(models,AIC)) # Which model has minimum AIC? } #--------------------------- # Model selection by LOOCV #--------------------------- model.select.loocv <- function(d){ n <- dim(d)[1] for(j in 1:n){ wgts <- rep(1,n) wgts[j] <- 0 models <- estimate.models(d,wgts) LL <- LL + sapply(models, function(fit){ dpois(d[j,]$y, predict(fit,newdata=d[j,],type="response"), log=T) }) } which.max(LL) # Which model has maximum 'predictive' likelihood? } #---------------------------- # Experiment / AIC vs. LOOCV #---------------------------- n <- 100 n.experiments <- 1000 results <- matrix(0,n.experiments,5) colnames(results) <- c("AIC.selected","LOOCV.selected","p.val.Intersept","p.val.x1","p.val.x2") for(i in 1:n.experiments){ d <- generate.data(n) p.values <- summary(estimate.models(d)[[4]])$coef[,4] results[i,] <- c(model.select.aic(d),model.select.loocv(d),p.values) } results.df <- transform(data.frame(results), AIC.selected=as.factor(AIC.selected), LOOCV.selected=as.factor(LOOCV.selected)) levels(results.df[,1]) <- paste("Model",levels(results.df[,1])) levels(results.df[,2]) <- paste("Model",levels(results.df[,2])) library(tabplot) tableplot(results.df,sortCol=5,nBins=100,scales="lin") dev2bitmap("AicVsLoocv.jpg",width=7,height=5,gaa=4,taa=4) summary(results.df) # AIC.selected LOOCV.selected p.val.Intersept p.val.x1 # Model 2:867 Model 2:826 Min. :5.950e-97 Min. :3.384e-21 # Model 3: 1 Model 4:174 1st Qu.:1.281e-61 1st Qu.:4.378e-12 # Model 4:132 Median :1.150e-53 Median :8.938e-10 # Mean :6.746e-31 Mean :2.739e-04 # 3rd Qu.:6.316e-47 3rd Qu.:6.676e-08 # Max. :6.728e-28 Max. :2.596e-01 # p.val.x2 # Min. :0.0007084 # 1st Qu.:0.2461179 # Median :0.5158895 # Mean :0.5053599 # 3rd Qu.:0.7440841 # Max. :0.9994481 table(results.df$AIC.selected,results.df$LOOCV.selected) # Model 2 Model 4 # Model 2 814 53 # Model 3 0 1 # Model 4 12 120