Post on 15-Jan-2015
description
> library(kernlab)
> # データの読み込み(データは"../data/"ディレクトリに置いておく)
> abalone <- read.csv("../data/abalone.data", header=FALSE)
> # 19番目のクラスを正例に,それ以外のクラスを負例とする
> label <- abalone[, 9]
> label[label==19] <- "positive"
> label[label!="positive"] <- "negative"
> label <- factor(label)
> table(label)
label
negative positive
4145 32 正例32サンプル,負例4145サンプルのデータ
> set.seed(123)
> # クロスバリデーションの実行(多項式カーネルを用い,次数は2とする)
> idx <- sample(1:10, nrow(abalone), replace=TRUE)
> for (i in 1:10) {
+ is.test <- idx == i
+ abalone.train <- abalone[!is.test, ]
+ abalone.test <- abalone[is.test, -9]
+ fit.ksvm <- ksvm(label ~., data=abalone.train, kernel="polydot", kpar=list(degree=2))
+ pred[is.test] <- as.character(predict(fit.ksvm, abalone.test))
+ }
> # 予測結果の集計
> table(pred)
pred
negative
4177
全てを負例と判別!!
> label.table <- table(label)> # 正例の重み(負例と正例のサンプル数の比とする)> weight.positive <- as.numeric(label.table[1]/label.table[2])> # 10-fold クロスバリデーションの実行> for (i in 1:10) {+ is.test <- idx == i+ abalone.train <- abalone[!is.test, ]+ abalone.test <- abalone[is.test, -9]+ fit.ksvm <- ksvm(label ~., data=abalone.train, + class.weights=c("positive"=weight.positive, + "negative"=1), + kernel="polydot", kapr=list(degree=2))+ pred[is.test] <- as.character(predict(fit.ksvm, abalone.test))+ }> table(label, pred) pred label negative positive negative 3118 1027 positive 19 13
何も工夫しないよりは良くなったが,まだまだ(モデルパラメータのチューニングの余地もまだまだあり)
正例と負例のサンプル数に反比例したペナルティの重みを指定
> library(kernlab)> library(DMwR)> set.seed(123)> # 元のデータにサンプル名の付値> rownames(abalone) <- paste("original",
1:nrow(abalone), sep="")> # SMOTE関数を用いて人工的な正例の生成,負例をアンダー
サンプリング> abalone.smote <- ++ SMOTE(label ~ ., data=abalone, perc.over=2000,
perc.under=10)
人工的な正例を2000/100倍(=20 倍)増やす
負例の数を次式で調整する(正例の数+人工的な正例の数)×10/100(=0.1)
> idx <- sample(1:10, nrow(abalone.smote), replace=T)> pred <- rep(NA, nrow(abalone.smote))> # 10-fold クロスバリデーションの実行> for (i in 1:10) {+ is.test <- idx == i+ abalone.train <- abalone.smote[!is.test, ]+ abalone.test <- abalone.smote[is.test, -9]+ fit.ksvm <- ksvm(label ~., data=abalone.train, kernel="polydot", kpar=list(degree=2))+ pred[is.test] <- predict(fit.ksvm, abalone.test)+ }
> # SMOTEでの補間点も含むデータに対する分割表> table(abalone.smote$label, pred) negative positive negative 19 13 positive 1 351> # 元々のデータに対する分割表> is.original <- rownames(abalone.smote) %in% + rownames(abalone)> table(abalone.smote[is.original, "label"], + pred[is.original]) negative positive negative 19 13 positive 0 32
まだまだチューニングの余地はあるが,
それでも精度はかなり向上
Warning!!
SMOTE関数を使用する際は,
データのクラスラベルは
最後の列に配置しよう(SMOTE関数が使用している
smoote.exs関数の仕様)
thanks to @dichikaさん
Weighted Random Forest 正例,負例をそれぞれ誤って分類する際のペ
ナルティを以下の2箇所で考慮する. Gini係数を評価基準として決定木の枝を作成する
とき
予測ラベルを決定する際に重み付き多数決を取るとき
Balanced Random Forest各ツリーを構築する際, 正例のデータ数と同じだけ負例のデータをサンプリングして学習する.
RのrandomForestパッケージでは,
Weighted Random Forest 引数classwtが部分的に対応している模様
(Gini係数のみ?,古いfortranコードを用いている)
Balanced Random Forest 引数sampsizeに正例と負例に対して同じサ
ンプル数を指定する
引数classwtを調整してもあまり効果はない?
randomForestパッケージの管理者によると・・・(http://bit.ly/xJ2mUJ)
現在のclasswtオプションはパッケージが開発された当初から存在しているが,公式のfortranのコード(バージョン4以降)の実装とは異なる.クラスの重みを考慮するのは,ノード分割時にGini係数を算出する際のみである.
我々は,クラスの重みをGini係数の算出においてのみ用いても,極端に不均衡なデータ(例えば1:100やそれ以上)に対してはあまり役に立たないことが分かった.そこで,Breiman教授はクラスの重みを考慮する新しい方法を考案した.この方法は新しいfortranコードに実装されている.
現在のパッケージのclasswtにクラスの重みを指定しても,我々が望んでいた結果が過去に得られなかったことだけは付記しておく.
まとめ
不均衡データ="各クラスに属するサンプル数に偏りがあるデータ"
不均衡データに対するクラス分類においてはいろいろと工夫が必要な場合がある
対応方法としては,正例の誤判別ペナルティを調整する方法とサンプリングを工夫する方法が代表的
個々の問題に応じていろいろと試すべし