[Keras]ドーナッツ型領域分類問題を機械学習で解いてみた[習作]

こんにちはみなさん

ちょっと前に[PHPでニューラルネットワークを作ってみて](http://qiita.com/niisan-tokyo/items/2182c9adccf54387c367)、だいたい何やっているのかを検証してみました。
とはいえ、現在色んな所で検証・実践され、洗練された計算手法みたいのは一切ないので、遅い上に制度もあまり良くはありませんでした。
そこで今回は、Kerasというフレームワークを使って、イマドキの機械学習機構を試してみます。

# Keras

KerasはTensorFlow上で動くニューラルネットワークライブラリで、Pythonで書かれています。
使ってみた感覚では、直感に任せてネットワークが書けそうだなぁ、って思いました。
TensorFlowを生で書くと、色々パラメータの設定が面倒くさかったのですが、
その辺もやりやすくなってました。

## Keras on Docker

ああ、やっぱりあるんですよね、Keras入りのイメージ
https://hub.docker.com/r/gw000/keras/

これで、自分の環境を汚すことなく、Kerasを試すことができます。

# 問題設定

問題設定は以前やったドーナッツ型の分類問題です。

```math
{f(x, y) = \left\{
\begin{array}{1}
1, (1 < x^2 + y^2 < 4)\\
0,  ( \rm{otherwise} )
\end{array}
\right.
}
```

![double_circle.png](https://qiita-image-store.s3.amazonaws.com/0/102182/59946095-b890-19fd-bd6c-4e410eb4df44.png)

# 実装

やりたいことを決めたので、実装を初めましょう

## 学習機構
まずは学習機構から作ります。

```learn.py
from keras.models import Sequential
from keras.layers import Dense, Activation
import numpy as np
import random
import math

def double_circle():
    x = random.uniform(-2, 2)
    y = random.uniform(-2, 2)
    sample = (x,y)
    norm = math.sqrt(x * x + y * y)
    if norm > 1 and norm < 2:
        label = 1
    else:
        label = 0

    return (sample, label)

# Model Definition
model = Sequential()
model.add(Dense(32, input_dim=2))
model.add(Dense(64, activation='relu'))
model.add(Dense(32, activation='relu'))
model.add(Dense(1, activation='sigmoid'))
model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['accuracy'])

data = []
labels = []
for num in range(1024):
    (sample, label) = double_circle()
    data.append(sample)
    labels.append(label)

model.fit(np.array(data), np.array(labels), nb_epoch=100, batch_size=32)

model.save('/srv/model/double_circle')
```
関数`double_circle`を使って、ランダムな座標を選び、その点がドーナッツ図形の内部にあるかどうかを判断したラベルを返します。
そして、座標とラベルの組をリストに投入していきます。
次にモデルを作るわけですが、Kerasはニューラルネットワークの各層を積んでいくイメージでモデルを形成していきます。

```
model.add(Dense(32, input_dim=2))
model.add(Dense(64, activation='relu'))
```
例えば、ここは第１層と第２層を定義しています。
第１層は入力次元がわからないので、ちゃんと明示しています。今回は座標点が入力なので、`input_dim=2`を入れます。
第１層の第１引数が出力次元になっているので、第２層は入力次元がいりません。
また、第２層の活性化関数に`activation='relu'`設定しています( 設定がない場合はそのまま出力 )。
こんな感じで好きなように層を積んでいけます。

```
$  docker run --rm -v `pwd`:/srv/ gw000/keras python learn.py
```
KerasのDockerコンテナを動かしてみて、学習させてみましょう。
私のMacで大体5秒くらいで終わりました。
modelディレクトリにモデルファイルが生成されています。

# 生成モデルの利用
生成したモデルが、どの程度うまく行っているのか確かめてみましょう。

```use.py
from keras.models import load_model
import numpy as np

model = load_model('/srv/ai/model/double_circle')

def check(x):
    data = np.array([x])
    pred = model.predict(np.array([x]))
    #print pred
    if pred > 0.5:
        return 1
    else:
        return 0

for y in range(20):
    labels = []
    for x in range(20):
        data = [(x-10.0)/5, (10.0-y)/5]
        labels.append(check(data))

    print labels
```
Kerasはモデルをロードするだけで、learnで学習したときのネットワークを再現してくれます。
このスクリプトはロードしたモデルを使って、$-2< x < 2$, $-2 < y <2$の範囲を0.1刻みで指定の領域に入っているかどうかを判別します。
こいつを実行すると、こんな感じになります。

```
$ docker run --rm -v `pwd`:/srv/ gw000/keras python use.py
[0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0]
[0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0]
[0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0]
[0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0]
[0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1]
[0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1]
[1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1]
[1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1]
[1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1]
[1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1]
[1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1]
[1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1]
[0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0]
[0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0]
[0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0]
[0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0]
[0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0]
```
丸い形が浮かび上がっているので、うまく行っている気がしないでもないですね。

もうちょい細かく点を取ってみましょう。
今度は0.02刻みでデータ点を取ってみます。
今回は領域に含まれると判断される座標点をCSV形式で吐き出すようにしてみます

```to_csv.py
from keras.models import load_model
import numpy as np

model = load_model('/srv/model/double_circle')

def check(x):
    data = np.array([x])
    pred = model.predict(np.array([x]))
    #print pred
    if pred > 0.5:
        return 1
    else:
        return 0

for y in range(100):
    for x in range(100):
        data = [(x-50.0)/20, (50.0-y)/20]
        if check(data) == 1:
            print "%f,%f" % (data[0],data[1])
```
で、例によってdockerで処理します。

```
docker run --rm -v `pwd`:/srv/ gw000/keras python to_csv.py > result.csv
```
出てきたresult.csvをplotしてみましょう。
<img width="550" alt="result.png" src="https://qiita-image-store.s3.amazonaws.com/0/102182/484e1bc7-7209-367d-cfc7-cdab71fe60ec.png">
まあ、こんなものかもしれませんね

# まとめ
とりあえずKerasを始めるために、以前に自分がPHPで作ったやつをこちらで実現してみました。
ネットワークや層を組み立てるのなら、TensorFlowよりもわかりやすいですね。

今回はこんなところです。

# 参考
[公式サイト](https://keras.io/ja/)