A Neural Network PlaygroundというDeep Learningのパラメータを変えながらその挙動を見て学べる(楽しめる)サイトがTensorFlowプロジェクトからの提供であるんですが、そのサンプルデータセットの4番目に「渦巻きサンプル」(別名「スイスロール」)があります。以下の画像の右下のやつです。
これってDeep NNでやろうとすると意外としちめんどくさいんですが、確か杉山先生のイラスト機械学習本だとガウシアンカーネルSVMでサクッと分類できるみたいに書かれていた気がいたしまして。

イラストで学ぶ 機械学習 最小二乗法による識別モデル学習を中心に (KS情報科学専門書)
- 作者: 杉山将
- 出版社/メーカー: 講談社
- 発売日: 2013/09/18
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (7件) を見る
ということで、ちょうど今ネタ切れなのもあるのでちょっとした余興としてこのデータの二値分類をやってみようと思います。なおスペースの都合上試行錯誤した部分自体は割愛して、どんなパラメータにしたらどんな風にダメだったかを簡単にコメントするに留めておきますので、悪しからずご了承ください。。。
データセット
一応、それっぽいデータセットを適当に生成してGitHubに置いときました。今回もRでやるので、dという名前でもつけてデータフレームとして読み込んでおいてください。
適当にプロットするとこんな感じになります。ついでに決定境界を描くためのグリッドも作っておきます。
> px <- seq(-4,4,0.03) > py <- seq(-4,4,0.03) > pgrid <- expand.grid(px,py) > names(pgrid) <- names(d)[-3]
SVMでやってみる
杉山本だと簡単にできるみたいなイメージで書かれているんですが、やってみると意外とうまくいかなかったです(笑)。LIBSVM由来の{e1071}のsvmだとガウシアンカーネル選択時のgammaがデフォルト値だと1/ncol(x)になるんですが、これだと汎化が強過ぎて全く渦巻き模様にフィットしない謎の決定境界を描きます。そこで、少しキツく過学習気味になるように、gamma = 8としてみました。
> library(e1071) > d.svm <- svm(as.factor(label)~., d, gamma=8) > out.svm <- predict(d.svm, newdata=pgrid) > plot(d[,-3], pch=19, col=d[,3]+1, cex=2, xlim=c(-4,4), ylim=c(-4,4)) > par(new=T) > contour(px, py, array(out.svm, c(length(px),length(py))), levels=0.5, drawlabels = F, col='purple', lwd=3, xlim=c(-4,4), ylim=c(-4,4))
決定境界自体は全然渦巻いてくれていませんが(汗)、とりあえず学習サンプルを綺麗に囲むように決定境界が描かれているのが分かるかと思います。
ランダムフォレストでやってみる
一般にこの手の曲線的な決定境界が求められるシチュエーションではランダムフォレストのような樹木モデル由来の学習器は良くないと予想されるんですが、それでも物は試しにやってみましょう。
> library(randomForest) > d.rf <- randomForest(as.factor(label)~., d) > out.rf <- predict(d.rf, newdata=pgrid) > plot(d[,-3], pch=19, col=d[,3]+1, cex=2, xlim=c(-4,4), ylim=c(-4,4)) > par(new=T) > contour(px, py, array(out.rf, c(length(px),length(py))), levels=0.5, drawlabels = F, col='purple', lwd=3, xlim=c(-4,4), ylim=c(-4,4))
おい、もうちょっとどうにかなんねーのかよ(笑)。案の定、樹木モデル特有のカクカクした決定境界がモロに過学習的な傾向を示しています。やはりこの手のデータセットには樹木モデル系の学習器はダメなようです。よってxgboostもやろうかと思っていましたが、割愛することにします。
DNNでやってみる
ということで、NN playgroundと同様にDNNでやってみます。ここではRで回すということで、MXnetでやることにします(やってみたいという方はKeras in Rでも良いと思いますが)。そう言えば今気付いたんですが、{mxnet}のバージョンが0.10.1のままのようです。。。0.11.1で最適化エンジンや損失関数の定義が増えているはずなので、最新バージョンだと違う結果が出せるかもしれません。
そして結果より先に書いておきますが、これめちゃくちゃ試行錯誤するのに時間かかりました(汗)。ぶっちゃけ、learning rateをわずかにいじっただけで全くデタラメな決定境界を描くの連続で、いつまで経ってもうまく学習しない(ほんの0.001変えただけでtraining accuracyが0.5とかひどいと0.39とかに張り付いて変わらないみたいな状況も見られた)。これは実はNN Playgroundでも明確に見られる現象で、そもそも学習中もなかなか渦巻き状の学習サンプルにきちんとモデルが追随しないんですよね。なので正直以下の結果を出したところで心が折れました。。。
> library(mxnet) > train.x <- data.matrix(d[,-3]) > train.y <- d[,3] > test <- data.matrix(pgrid) > data <- mx.symbol.Variable("data") > fc1 <- mx.symbol.FullyConnected(data, name="fc1", num_hidden=5) > act1 <- mx.symbol.Activation(fc1, name="tanh1", act_type="tanh") > fc2 <- mx.symbol.FullyConnected(act1, name="fc2", num_hidden=5) > act2 <- mx.symbol.Activation(fc2, name="tanh2", act_type="tanh") > fc3 <- mx.symbol.FullyConnected(act2, name="fc3", num_hidden=5) > act3 <- mx.symbol.Activation(fc3, name="tanh3", act_type="tanh") > fc4 <- mx.symbol.FullyConnected(act3, name="fc4", num_hidden=2) > softmax <- mx.symbol.SoftmaxOutput(fc4, name="softmax") > devices <- mx.cpu() > mx.set.seed(71) > model <- mx.model.FeedForward.create(softmax, X=train.x, y=train.y, ctx=devices, num.round=2000, array.batch.size=10, learning.rate=0.0008, momentum=0.99, eval.metric=mx.metric.accuracy, initializer=mx.init.uniform(0.5), array.layout = "rowmajor", epoch.end.callback=mx.callback.log.train.metric(100)) > preds <- predict(model, test, array.layout = "rowmajor") > pred.label <- max.col(t(preds)) - 1 > plot(d[,-3], pch=19, col=d[,3]+1, cex=2, xlim=c(-4,4), ylim=c(-4,4)) > par(new=T) > contour(px, py, array(pred.label, c(length(px),length(py))), levels=0.5, drawlabels = F, col='purple', lwd=3, xlim=c(-4,4), ylim=c(-4,4))
ということで、DNNのご利益が全く感じられない結果になりました(泣)。ちなみにNN Playgroundでやってもうまくいくことってあまり多くなくて、しかもそこでうまくいったパラメータ設定をMXnetに突っ込んでも再現しないorz どういうことですか。。。
なお、上2つにかかった時間はたったの10分。DNNでここまでチューニングするのにかかった時間は2時間半でした。Train accuracyも0.8ちょっとが限界で、それ以上は上がらず。
最後に
僕はよくこういう喩えを使うことが多いのですが。
- 従来型の機械学習は、自動車に喩えればセダン。特に何も工夫しなくても簡単に街中を走れるが、スピードを出そうとすると限界がある
- Deep Learningは、自動車に喩えればF1レーシングカー。果てしなく速いスピードを出すことが出来るが、チューニングが難しい
この喩えを地で行くような余興の結果になりました。。。Deep Learningも適材適所、ということで。
追記1
@kazunori_279さんがいつもNN Playgroundのデモで使っているという設定を参考に、以下のように変えてみました。
> data <- mx.symbol.Variable("data") > fc1 <- mx.symbol.FullyConnected(data, name="fc1", num_hidden=8) > act1 <- mx.symbol.Activation(fc1, name="relu1", act_type="relu") > fc2 <- mx.symbol.FullyConnected(act1, name="fc2", num_hidden=8) > act2 <- mx.symbol.Activation(fc2, name="relu2", act_type="relu") > fc3 <- mx.symbol.FullyConnected(act2, name="fc3", num_hidden=5) > act3 <- mx.symbol.Activation(fc3, name="relu3", act_type="relu") > fc4 <- mx.symbol.FullyConnected(act3, name="fc4", num_hidden=2) > softmax <- mx.symbol.SoftmaxOutput(fc4, name="softmax") > devices <- mx.cpu() > mx.set.seed(71) > model <- mx.model.FeedForward.create(softmax, X=train.x, y=train.y, ctx=devices, num.round=3000, array.batch.size=10, learning.rate=0.0008, momentum=0.99, eval.metric=mx.metric.accuracy, initializer=mx.init.uniform(0.5), optimizer='sgd', array.layout = "rowmajor", epoch.end.callback=mx.callback.log.train.metric(100)) > preds <- predict(model, test, array.layout = "rowmajor") > pred.label <- max.col(t(preds)) - 1 > plot(d[,-3], pch=19, col=c(rep(1,100),rep(2,100)), cex=2, xlim=c(-4,4), ylim=c(-4,4)) > par(new=T) > contour(px, py, array(pred.label, c(length(px),length(py))), levels=0.5, drawlabels = F, col='purple', lwd=3, xlim=c(-4,4), ylim=c(-4,4))
多少スイスロールっぽくはなりました。が、unit数が8とかって結構このシチュエーションだと色々迷う感じですよね。。。
追記2
何か腹が立ってきたので(笑)、{keras}つまりKeras in Rでやってみました。
> library(keras) > model <- keras_model_sequential() model %>% layer_dense(units=8, input_shape=2) %>% layer_activation(activation='relu') %>% layer_dense(units=8) %>% layer_activation(activation='relu') %>% layer_dense(units=5) %>% layer_activation(activation='relu') %>% layer_dense(units=1, activation='sigmoid') model %>% compile( loss='binary_crossentropy', optimizer=optimizer_sgd(lr=0.03), metric=c('accuracy') ) > model %>% fit(train.x, train.y, epoch=1500, batch_size=10) > pred_class <- model %>% predict(test, batch_size=10) > pred_class <- round(pred_class,0) > plot(d[,-3], pch=19, col=c(rep(1,100),rep(2,100)), cex=2, xlim=c(-4,4), ylim=c(-4,4)) > par(new=T) > contour(px, py, array(pred_class, c(length(px),length(py))), levels=0.5, drawlabels = F, col='purple', lwd=3, xlim=c(-4,4), ylim=c(-4,4))
スイスロールにはなるんですが、やっぱり学習が収束し切れてないですねぇ。。。