未知の非線形な交互作用をマルチレベルモデリングでモデル化してみる
はじめに
なんか変なタイトルですが、この記事では次のような現象について考えます。
これはぱっと見は通常の重回帰モデルですが、よく見るとの回帰係数はの関数になっています。本記事ではこういった 間の未知の(非線形かもしれない)交互作用について考えることを目的とします。
どういった状況でこんなよくわからないモデルを考えないといけないのでしょうか?
例えば、とあるシステムの開発プロジェクトの開発にかかる工数を分析するモデルだとして、目的変数 が開発期間、説明変数 が開発規模(人数)、が開発者のコミュ力だとしましょう(※ 単なる例なので現象の妥当性は気にしてはいけません)。
小規模(が小)ならばコミュニケーションを取る相手が少なく文脈も常に共有しているため、コミュ力の影響は小さいと考えられます。つまり、は小さくなります。
一方で規模が大きくなる(が大きくなる)ほどコミュニケーションを取る相手が多く、かつ、文脈も共有されていないことが増えるため、コミュ力の影響は非常に大きくなると考えられます。つまり、は非常に大きいと考えられます。
従って、は線形関数ではなく、例えばみたいな感じでについて指数的に増大したりしそうです。また、N次の多項式で表される極大値や極小値があるような複雑な形をしているかもしれません。ここでの目的はコミュ力の開発期間への影響が規模によってどのように変わってくるか? (つまりの形)について知ることです。
モデル
では、上記のが未知の関数だとして、どうにかしての形を推定する方法を考えます。
この記事ではをで適当に区切って、その区間内を線形関数で近似することを考えます。
すなわち、
という形で近似することを考えます。ここではが所属する区間、はその区間の中央の値を表します。この場合、は区間数個分だけ推定する必要があります。で階層分けをしたマルチレベルモデリングと言ってもいいと思います。
例えば、で、区間数のときは[x_2]の回帰係数である関数は次のように近似されるイメージです。
二次曲線が4つの線形関数で近似されていることがわかります。
実験
それでは実際に未知の非線形関数を線形関数で近似できるかやってみましょう。
ここでは、とし、とします。
以下にこのモデルを表すStanのコードを示します。
data { int<lower=0> N; //データの数 int<lower=0> C; //区間の数 real<lower=0> m; //x_1の最大値(5) int c[N]; //データが所属する区間id(1〜C) vector[N] x1; //説明変数x_1 vector[N] x2; //説明変数x_2 vector[N] y; //目的変数 } parameters { real beta0; real beta1; vector[C] beta2_1; vector[C] beta2_0; real s; } model { for(i in 1:N) y[i]~normal(beta0 + beta1 * x1[i] + (beta2_1[c[i]] * (x1[i] - m*(c[i]-0.5)/C) + beta2_0[c[i]]) * x2[i], s); } generated quantities{ vector[N] log_lik; //対数尤度。WAICを求めるため vector[N] pred; //予測値 for (i in 1:N){ log_lik[i] <- normal_log(y[i], beta0 + beta1 * x1[i] + (beta2_1[c[i]] * (x1[i] - m*(c[i]-0.5)/C) + beta2_0[c[i]]) * x2[i], s); pred[i] <- beta0 + beta1 * x1[i] + (beta2_1[c[i]] * (x1[i] - m*(c[i]-0.5)/C) + beta2_0[c[i]]) * x2[i]; } }
以下にテスト用のデータを生成して上記のStanを実行するRのコードを示します。ここでは区間数を5としています。※ 2015/03/23 使用ライブラリが書いてなかったので追記しました。
library(ggplot2) library(dplyr) library(rstan) library(matrixStats) #パラメータ N <- 1000 beta1 <- 2 beta0 <- 1 #データ生成 d <- tbl_df(data.frame(x1=runif(N, 0, 5), x2=runif(N, 1, 5))) beta2 <- (d$x1 - 5/2)^2 / 5 d <- d %>% mutate(y=rnorm(N, beta0 + beta1 * x1 + beta2 * x2, 0.1)) #区間作成 L <- 5 d <- d %>% mutate(cls=ceiling(x1 / (5/L))) dat <- list(y=d$y, x1=d$x1, x2=d$x2, c=d$cls, N=N, C=L, m=5) #MCMC fit <- stan(file='ml.stan', data=dat, iter=3000)
上記のコードを実行して、結果を見てみましょう。Rhatは1.00付近でいい感じだったのでMCMCは収束したと判断します。
まず、モデルが正しい結果を推定できているか否か? について見てます。以下を実行することで予測値(pred)と実際の値(y)の散布図を書きます。
d %>% mutate(pred=colMeans(extract(fit, 'pred')$pred)) %>% ggplot(aes(y, pred)) + geom_point()
以下が、その結果です。モデルはフィットしてそうであることがわかります。
それでは、真の関数と求めたを比較してみましょう。は各区間ごとに中央値で評価しています。
黒い線が真の関数、カラフルな直線が各区間で求めた線形関数です。二次関数が線形関数である程度近似されていることがわかります。
それでは区間数を変えてどんな感じになるか見てみましょう。
以下に区間数としたときのを示します。
L=1ではの特徴が全く表現できていないこと、Lが大きくなるに伴い、によくフィットしたことがわかります。ただし、のように区間数が大きくなりすぎると過学習が発生してしまうこともわかります。
以下に区間数LとそれぞれのモデルのWAICの値を示します。は1〜20と100について求めました。から6までは急激に減少し、その後は似たりよったりで、になるとよりも値が大きくなりました。ここから、の間にWAICを最小にする値があることがわかります。そのため、WAICによってを適切に決めることでモデルを決めることができそうです。
ただし、ここでのWAICが支持するモデルは必ずしも解釈しやすいとは限らないので、結果を見て適当にLを決めてしまってもいいんじゃないかなと思います(今回の件で言えばぐらいでも十分に傾向を見て取ることができます)。
ちなみにWAICは以下のようにして求める事ができます(多分合ってるはず(´・ω・`))。
- AICの求め方参考: waic example in manual · Issue #473 · stan-dev/stan · GitHub ※2015/03/23 追記
log_lik <- extract(fit, 'log_lik')$log_lik lppd <- sum(log(colMeans(exp(log_lik)))) p_waic <- sum(colVars(log_lik)) (waic.pp <- -2*lppd + 2*p_waic)
まとめ
ちょっと無理矢理感がありますがやってみたらできました。
データサイエンティスト養成読本 [ビッグデータ時代のビジネスを支えるデータ分析力が身につく! ] (Software Design plus)
- 作者: 佐藤洋行,原田博植,下田倫大,大成弘子,奥野晃裕,中川帝人,橋本武彦,里洋平,和田計也,早川敦士,倉橋一成
- 出版社/メーカー: 技術評論社
- 発売日: 2013/08/08
- メディア: 大型本
- この商品を含むブログ (12件) を見る
- 作者: 安道知寛
- 出版社/メーカー: 朝倉書店
- 発売日: 2010/02
- メディア: 単行本
- 購入: 3人 クリック: 98回
- この商品を含むブログ (3件) を見る