人はRubyだけで手書き数字認識できるか?

![sabina_demo_01.gif](https://qiita-image-store.s3.amazonaws.com/0/54587/19592722-5b2f-25fa-8440-e5d93f7af67a.gif)

できた。
完。

## はじめに
…というわけで、今回はRubyで手書き数字認識をやってみました。
タイトルに関しては元ネタ様から許可を得ました。

学習用データはMNIST、ライブラリは手前味噌で恐縮ですが今年の春に公開したgemの [sabina](https://github.com/seinosuke/sabina) を用いました（詳細 → [【Ruby】機械学習をするためのgemをつくった](http://qiita.com/seinosuke/items/ba16be7a469a756490be)）。現実が忙しすぎてあれからほとんど手を加えられていないので、この例もただの多層パーセプトロンです。ただ、あの時は2、3次元のデータに対するデモしか用意できなかったので、今回はもう少し実用的なデータの適用例を用意したいなという思いがありこの記事を投稿しました。


## インストール
ソースコード [https://github.com/seinosuke/sabina_mnist_example](https://github.com/seinosuke/sabina_mnist_example)

動作環境は以下の通り。Rubyのバージョンは2以上なら問題ないと思います。

* Ubuntu 16.04
* Ruby 2.3.0
* sabina 0.1.0
* gtk2 3.0.8

必要なgemはGemfileに書いたのでbundlerでインストールできます。

    $ bundle install


## GUI起動までの手順
既に学習済みの重みを bin/data/ 以下にサンプルとして置いてあるので、必要なgem等がインストールできたら

    $ ruby bin/main.rb

で直ちに手書き数字認識用のGUIが起動します。

一から学習をさせる場合は以下に示す手順の通り、学習用データを用意するところから始めます。

1. [http://yann.lecun.com/exdb/mnist/](http://yann.lecun.com/exdb/mnist/) から .gzファイルをダウンロードして bin/data/ 以下においておく
2. `$ ruby bin/generate_mnist_rb.rb`
3. `$ ruby bin/learn.rb`
4. `$ ruby bin/main.rb`

手順3で `learn.rb` を走らせたらお使いのPCを常温で一晩寝かせるのが重みを熟成させるポイントです。2回目移行は `$ ruby bin/main.rb` するだけで学習済みの重みを用いた多層パーセプトロンによる手書き数字認識のGUIが起動します。 `$ ruby bin/learn.rb` すると学習をやり直します。

MNISTデータの読み込みは
[Ruby で MNIST 手書き文字データを扱う](http://d.hatena.ne.jp/n_shuyo/20090913/mnist)
を参考にさせていただきました。

## 多層パーセプトロン

`bin/learn.rb` の中身を途中省略して学習に関する部分だけ載せます。
パラメータの設定は以下の通り。

* 入力層のユニット数 数字画像の画素数 28x28、
* 隠れ層のユニット数 50、
* 出力層のユニット数 数字0〜9の10
* ミニバッチ数 10
* 学習率 0.01

`mp.learn` で学習が一段階すすむのでそれを200回まわしています。

学習に時間がかかり過ぎるため、 [Qiita - Rubyでディープラーニング](http://qiita.com/elgoog/items/8e7102a87889950d060d) を参考に学習用データは手書き数字画像6万枚の内5千枚のランダムに選択したものとなっています。


```ruby:bin/learn.rb
################### (略)

DIM = 28**2
K = 10
EPOCH = 200

options = {
  :layers => [
    Sabina::Layer::MPInputLayer.new(DIM),
    Sabina::Layer::MPHiddenLayer.new(50),
    Sabina::Layer::MPOutputLayer.new(K)
  ],
  :mini_batch_size => 10,
  :learning_rate => 0.01,
  :training_data => training_data,
}
mp = Sabina::MultilayerPerceptron.new(options)

begin
  mp.learn

################### (略)

rescue Interrupt
  break
end while $c < EPOCH

################### (略)
```

## 結果
テストデータに対する正解率は最終的に93％くらいでした。
以下に示す図が前述の条件で学習を行った結果です。

![result.png](https://qiita-image-store.s3.amazonaws.com/0/54587/6025c87d-14e7-e44b-c6e9-5691fe2ca844.png)

### 成功例

「2」や「3」は得意そうでした。

![sabina_03_o.png](https://qiita-image-store.s3.amazonaws.com/0/54587/06a8fd47-930a-4201-31d2-0b236ced6cbe.png)
![sabina_06_o.png](https://qiita-image-store.s3.amazonaws.com/0/54587/138bf236-5022-91ea-3e95-70b5df203289.png)
![sabina_08_o.png](https://qiita-image-store.s3.amazonaws.com/0/54587/d1aec155-6d92-e325-5218-ea9c31412730.png)


### 失敗例

「5」「4」「9」あたりが苦手そうでした。

![sabina_05_x.png](https://qiita-image-store.s3.amazonaws.com/0/54587/7b4e1b05-10b8-6a02-acf9-2e9020ce639c.png)
![sabina_04_x.png](https://qiita-image-store.s3.amazonaws.com/0/54587/e19760e2-2b56-54c0-424f-9a7b7b3cc3a0.png)



## おわりに
簡単なデモですが、それでもやはりちゃんと認識できているのを体験できたので安心しました。これで一応800次元くらいの規模のデータにも（時間はかかりますが）対応できるということが証明できました。

今回使用した sabina の実装では可能な限り行列計算を用いて書いたのでハチャメチャに遅いわけではないですが、標準添付でないものは使わず全て素の Matrix で書いているので、やはりその辺で高速化の余地がありそうです。まあ余地はあってもそれをやる時間がないのですが。。。。 のんびりと盆栽を手入れするような勢いで改良を進めていきたいです。

[tensorflow is coming to ruby!](http://www.somatic.io/blog/tensorflow-is-coming-to-ruby)
など、最近Ruby界隈でも機械学習が盛り上がってるような気がしなくもなきにしもあらずんばあらずのごとしです。



### 参考
岡谷貴之 (2015) 『深層学習』 講談社。
[Ruby で MNIST 手書き文字データを扱う](http://d.hatena.ne.jp/n_shuyo/20090913/mnist)
[Qiita - Rubyでディープラーニング](http://qiita.com/elgoog/items/8e7102a87889950d060d)