餡子付゛録゛

ソフトウェア開発ツールの便利な使い方を紹介。

Rで機械学習(SVM)

人間の直観的な認識力を手軽に模倣するフレームワークである機械学習がもてはやされるようになってもう10年以上経つと思いますが、世間では第3次人工知能ブームでまだまだ注目されているようです。扱いやすいパッケージが拡充されているのはもちろん『情報処理』2015年5月号に載った「機械学習のための数学」のように理屈の方も簡潔で分かりやすい紹介が増えてきました。もう何か付け足すべき事など無さそうですが、Rで説明用のコードを書く必要があったので、ひっそりと紹介したいと思います。
機械学習と言っても手法のバリエーションは豊富なのですが、SVM(Support Vector Machine)が試すには手頃なようです。Karatzoglou, Meyer and Hornik (2005)をはじめとしてパッケージの解説が豊富です。ブログでRでの著名パッケージの使い方も良く紹介されていますね。

1. 学習データと評価データの作成

機械学習は、第一段階で分類情報有りの学習データから分類器を構築し、第二段階で構成した分類器に分類情報無しの他のデータの分類作業をさせます。今回は学習データと、分類器の性能評価のための評価データを用意します。
人間の直観力を示していそうな男女の体格データを用いました。遺伝子をチェックしないと厳密には性別は判別できませんが、ほとんどの場合は外見だけで性別を判別しているものです。残念ながら十分な量の男女の体格データをネット上で発見できなかったので、今回はブートストラップで増やしたデータを用います。

#
# 以下のページから男性と女性の胸囲と臀囲のサイズを拝借
#
# http://homepage3.nifty.com/orangejuice/body4.html
# http://homepage3.nifty.com/orangejuice/body6.html
#
man <- data.frame(
  chest = c(84.8, 87.3, 85.2, 83.4, 83.9, 78.3),
  hip = c(91.3, 92.7, 89.7, 88.1, 88.3, 82.7)
)

woman <- data.frame(
  chest = c(85.0, 89.1, 83.4, 84.1, 81.3, 81.8, 82.1),
  hip = c(95.5, 96.6, 93.3, 92.1, 90.1, 89.6, 89.8)
)

#
# bootstrapでデータ作成
#
library(boot)

create_data <- function(n){
  #
  # データ合成関数
  # df:データフレーム, i:ランダムに選ばれる添字番号
  #
  statistic <- function(df, i){
    # iだけをデータフレームから抜き出す
    s <- df[i, ]
    # 平均をとって戻す
    c(mean(s$chest), mean(s$hip))
  }

  #
  # 男性と女性のそれぞれのデータからサンプル作成
  #
  r_m_boot <- boot(man, statistic, n/2)
  r_w_boot <- boot(woman, statistic, n/2)

  #
  # 男性データと女性データを合成して戻す
  #
  df <- data.frame(
    gender = factor(c(rep("M", n/2), rep("W", n/2))),
    chest = c(r_m_boot$t[,1], r_w_boot$t[,1]),
    hip = c(r_m_boot$t[,2], r_w_boot$t[,2])
  )
}

set.seed(20160614)
s_materials <- create_data(10) # 学習データ・サイズは10
e_materials <- create_data(100) # 評価データ・サイズは100

2. 学習データから分類器を構築

作業を表現すると堅苦しいわけですが、実際はe1071パッケージのおかげで3行で済みます。

library("e1071")
set.seed(722)

#
# 性別(gender)と、胸囲(chest)と臀囲(hip)の関係を学習
#
svp <- svm(gender~chest+hip, data=s_materials)

オプションを工夫すると精度が上がる*1わけですが、今回は作業の流れを追うためにしません。

3. 評価データで構築された分類器の性能評価

データが単純かつSVM向きなせいで、正答率97.0%と高性能になっています。

#
# 性別の推定値をans列、本当の性別との比較結果をchk列に入れる
#
e_materials$ans <- predict(svp, e_materials)
e_materials$chk <- e_materials$ans == e_materials$gender

#
# 正答率を見てみる
#
sprintf("正答率: %.1f%%", 100*sum(e_materials$chk)/length(e_materials$chk))

4. 評価データで構築された分類器の性能評価

以下のようにプロットすると分類器が男性だと判断する水色領域と、女性だと判断するピンク色領域の上に、黒xとoが真の男性データと、赤xとoが女性データを表示してくれます*2

# 学習データでプロット
plot(svp, s_materials, chest~hip, col=c("lightblue1", "pink1"))

f:id:uncorrelated:20160614202844p:plain
上の学習データの当てはまりが良いのは当然なので、評価データを見て見ましょう。なお、実用では正解が分かりませんが、今回の評価データにはgender列に正解が書いてあるので答え合わせになります。

# 評価データでプロット
plot(svp, e_materials, chest~hip, col=c("lightblue1", "pink1"))

f:id:uncorrelated:20160614202854p:plain

hipが90から91ぐらいの女性データが男性に分類されてしまっていますが、全体としては悪くない当てはまりのようです。

5. まとめ

画像処理にも使われるのですが、システム構築としては特徴量の取得と整備が重要で、機械学習自体はちょっと前からコモディティ化しつつあるのが、実際に練習してみると良く分かりますね。
なお、機械学習分野でやられている手の込んだものではない、統計学で習うような素朴なロジスティック回帰で同じことが出来ないか試してみたのですが、分類自体は似たような精度で出来たものの、「数値的に 0 か 1 である確率が生じました」と推定量が不安定になったり*3、初期パラメーターによっては収束しなかったりするので、思いつきで実装するのはやめた方が良さそうです。

*1:例えば引数に、method = "C-classification", kernel = "radial", cost = 10, gamma = 0.1を加えると、評価データへの当てはまりが良くなります。なお、e1071パッケージには最適パラメーターを模索するための関数tune.svmが用意されていて、これで試行錯誤ができます。

*2: xはデータポイントかつサポートベクターとなります。

*3:完全分離可能なデータだと推定量が無限大になるので出るエラーだそうです。