渋谷駅前で働くデータサイエンティストのブログ

元祖「六本木で働くデータサイエンティスト」です / 道玄坂→銀座→東京→六本木→渋谷駅前

Conditional inference treesとそのランダムフォレストを{party}パッケージで試してみた

今まで気付かなかったんですが、@さんがこんな記事を書かれていたんですね。


実は僕はほとんどconditional inference treesのことを知らなかったのと、ここで紹介されている{party}パッケージが面白そうだったので、その背景も交えてちょっと簡単に紹介してみようと思います。


そもそもconditional inference treesとは?


全てのポイントを説明していくと大変なので、簡潔にまとめておきましょう。Vignette (PDF)とかCross Validatedにおける議論の内容を大ざっぱにまとめると、

  1. 従来型のCART / C4.5などの決定木・回帰木では変数同士の関係性を無視してジニ係数などの基準に基づいて変数選択してしまうため、変数同士の関係性によってはオーバーフィッティングや変数選択にバイアスが出るケースがある
  2. 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%。へー、面白い。。。

f:id:TJO:20140310175101p:plain

randomForest(){randomForest}の場合と、

f:id:TJO:20140310175123p:plain

cforest(){party}の場合。


これだけ見比べるとconditional importance measureによるランダムフォレストの方が分類精度が悪そうに見えますが、僕にはむしろ汎化性能が高いのかな?とも映りました。特に決定境界が、真の分布である「(1,1), (-1,1), (-1,-1), (1,-1)のそれぞれを中心とする標準偏差0.5ないし1.0の二次元正規分布が4つ配置されたデータ」をよく反映しているので、個人的には汎化性能が欲しいケースではcforestの方が良いのかなぁとも思いました。


ただ、cforest()はパラメータチューニングがあまり自由に利かないみたいなので*4、そこが改善されたら是非使いたいなぁというところです。この辺は今後のアップデートを待ちたいと思います。

*1:この言葉が耳慣れない人にはbootstrap testを思い出してもらえればOKかなと

*2:この辺は僕が以前使っていたpartial Granger causalityと発想が同じ

*3:各弱学習器のツリーに使う説明変数の数を全体数より小さくし、ランダムに選ぶことで弱学習器間の相関を下げている

*4:例えばmtry = 5で固定らしい