今まで気付かなかったんですが、@dichikaさんがこんな記事を書かれていたんですね。
実は僕はほとんどconditional inference treesのことを知らなかったのと、ここで紹介されている{party}パッケージが面白そうだったので、その背景も交えてちょっと簡単に紹介してみようと思います。
そもそもconditional inference treesとは?
全てのポイントを説明していくと大変なので、簡潔にまとめておきましょう。Vignette (PDF)とかCross Validatedにおける議論の内容を大ざっぱにまとめると、
- 従来型のCART / C4.5などの決定木・回帰木では変数同士の関係性を無視してジニ係数などの基準に基づいて変数選択してしまうため、変数同士の関係性によってはオーバーフィッティングや変数選択にバイアスが出るケースがある
- Conditional inference treesでは、permutation test*1の枠組みと偏相関のアナロジーに基づくconditional importance measureによって、第三者変数からの影響を排除しながらpermutation testによって変数選択の有意性検定をし*2、変数選択バイアスを回避するようにしている
ということのようです。これは言われるまで気がつかなかったと言えばそれまでなんですが、確かにCARTなどでは多重共線性が疑われるケースで妙な結果を返すことがあるような気がするんですよね。その意味では納得のいく説明だと思います。
Conditional inference treesを弱学習器とするランダムフォレストとは?
ランダムフォレストは弱学習器を決定木 / 回帰木とするバギングの修正版*3を用いたアンサンブル学習なので、その決定木 / 回帰木をconditional inference treesに置き換えれば修正版ランダムフォレストが出来上がります。
ポイントは変数重要度で、これもジニ係数や通常のpermutation importanceではなくconditional importance measureを用いることで、より変数選択バイアスの少ない結果が得られるようになっている、とのことです。
実際に{party}パッケージで試してみよう
とりあえず2つのデータで、{randomForest}と{party}とで性能比較をしてみましょう。1つ目はこのブログではお馴染みの多変量データ'conflict'です。dとか名前をつけてインポートしておきます。
> d <- read.table("~/Dev/R/conflict_sample.txt", header=T, quote="\"") > require("randomForest") # 略 > require("party") # 略 > tuneRF(d[,-8],d[,8],doBest=T) mtry = 2 OOB error = 6.57% Searching left ... mtry = 1 OOB error = 7.73% -0.177665 0.05 Searching right ... mtry = 4 OOB error = 6.67% -0.01522843 0.05 Call: randomForest(x = x, y = y, mtry = res[which.min(res[, 2]), 1]) Type of random forest: classification Number of trees: 500 No. of variables tried at each split: 2 OOB estimate of error rate: 6.3% Confusion matrix: No Yes class.error No 1400 100 0.06666667 Yes 89 1411 0.05933333 > d.rf<-randomForest(cv~.,d,mtry=2) # ジニ係数によるランダムフォレスト > d.cf<-cforest(cv~.,d) # Conditional importance measureによるランダムフォレスト > print(d.rf) Call: randomForest(formula = cv ~ ., data = d, mtry = 2) Type of random forest: classification Number of trees: 500 No. of variables tried at each split: 2 OOB estimate of error rate: 6.23% Confusion matrix: No Yes class.error No 1404 96 0.06400000 Yes 91 1409 0.06066667 > print(d.cf) Random Forest using Conditional Inference Trees Number of trees: 500 Response: cv Inputs: a1, a2, a3, a4, a5, a6, a7 Number of observations: 3000 > table(d$cv,predict(d.rf,newdata=d[,-8])) No Yes No 1406 94 Yes 82 1418 # ジニ係数ランダムフォレストの学習データ分類スコア > table(d$cv,predict(d.cf,newdata=d[,-8])) No Yes No 1400 100 Yes 78 1422 # Conditional importance measureランダムフォレストの学習データ分類スコア > table(d$cv,predict(d.cf,newdata=d[,-8],OOB=TRUE)) No Yes No 1400 100 Yes 78 1422 # 一応後者にOOBかけてみたけど変わらなかったというかOOB引数の使い方分からん
あれ、cforestの方が分類性能低くない? と思ったので、決定境界が見てみたいということでXOR複雑パターンに対する分類もやってみました。
> xorc <- read.table("~/Dev/R/Blog/xor_complex.txt", header=T, quote="\"") > xorc$label<-xorc$label-1 > xorc$label<-as.factor(xorc$label) # 前処理しておく > px<-seq(from=-3,to=3,length.out=300) > py<-seq(from=-3,to=3,length.out=300) > pgrid<-expand.grid(px,py) > names(pgrid)<-c("x","y") # 決定境界を描くためのグリッドを作っておく > xorc.rf<-randomForest(label~.,xorc) # ジニ係数ランダムフォレスト > xorc.cf<-cforest(label~.,xorc) There were 50 or more warnings (use warnings() to see the first 50) # Conditional importance measureランダムフォレスト > xorc.rf.pred<-predict(xorc.rf,newdata=pgrid) > xorc.cf.pred<-predict(xorc.cf,newdata=pgrid) # 決定境界データをpredictで作る > plot(xorc[,-3],pch=19,col=c(rep('red',50),rep('blue',50)),cex=3,xlim=c(-3,3),ylim=c(-3,3)) > par(new=T) > contour(px,py,array(xorc.rf.pred,dim=c(length(px),length(py))),xlim=c(-3,3),ylim=c(-3,3),col="purple",lwd=3,drawlabels=F,levels=0.5) # ジニ係数ランダムフォレストの決定境界を描く > plot(xorc[,-3],pch=19,col=c(rep('red',50),rep('blue',50)),cex=3,xlim=c(-3,3),ylim=c(-3,3)) > par(new=T) > contour(px,py,array(xorc.cf.pred,dim=c(length(px),length(py))),xlim=c(-3,3),ylim=c(-3,3),col="purple",lwd=3,drawlabels=F,levels=0.5) # Conditional importance measureランダムフォレストの決定境界を描く > table(xorc$label,predict(xorc.rf,newdata=xorc[,-3])) 0 1 0 50 0 1 0 50 # 前者の学習データ分類精度は100% > table(xorc$label,predict(xorc.cf,newdata=xorc[,-3])) 0 1 0 38 12 1 5 45 # 後者の学習データ分類精度は83%。へー、面白い。。。
randomForest(){randomForest}の場合と、
cforest(){party}の場合。
これだけ見比べるとconditional importance measureによるランダムフォレストの方が分類精度が悪そうに見えますが、僕にはむしろ汎化性能が高いのかな?とも映りました。特に決定境界が、真の分布である「(1,1), (-1,1), (-1,-1), (1,-1)のそれぞれを中心とする標準偏差0.5ないし1.0の二次元正規分布が4つ配置されたデータ」をよく反映しているので、個人的には汎化性能が欲しいケースではcforestの方が良いのかなぁとも思いました。
ただ、cforest()はパラメータチューニングがあまり自由に利かないみたいなので*4、そこが改善されたら是非使いたいなぁというところです。この辺は今後のアップデートを待ちたいと思います。