ネットで情報増えてきたけどPython3.4＋Chainer1.9.0でディープラーニングを試してみる

#はじめに
ここ数年，pythonが巷でにぎわっていますね。そん中でも気になるのが，機械学習分野です。ライブラリやソースが充実してきており，気軽に機械学習が試せるようになりました。機械学習でも，特に，ディープラーニングが活発ですね。

Pythonで動くディープラーニングには，次のようなものが出てきています。

+ Caffe
+ Teano
+ TensorFlow
+ Chainer

これらをインストールさえすれば簡単にディープラーニングが組めてしまします。
一つ一つ紹介するのはすでにたくさんあるので，省略して，既に記事を書いている人たちにお任せします。


今回は，Pythonで使えるChanierに注目しました。
なぜ？それは，先日パシフィコ横浜で開かれた[SSII2016](https://confit.atlas.jp/guide/event/ssii2016/top)のチュートリアル講演で，「Pythonによる機械学習」があり，[資料](http://www.slideshare.net/yasutomo57jp/python-svmdeep-learning)がとても充実していたため，試してみたくなりました。

この記事は，ニューラルネットワークや畳み込みニューラルネットワークがわかっている人向けです。
とりあえず動かしてみようという感じの記事です。


#前準備
Pythonがインストールされていることが前提です。ネットで調べてみると，まだPython2.x系を使っている人が多いみたい。

私は，Ptyhon3.4がすでにインストールしてあったので，そのままChainerを入れて動かすことにしました。


##必要なパッケージ
事前にnumpy，scikit-learn，Cyhton，setuptoolsが必要です。

```py3
pip install numpy, scikit-learn, cyhton, setuptools
```

で，インストールできるはずです。

できない→numpyは，公式サイトからダウンロードすればインストールできることが多いみたいです。


[追加]さらに，h5pyもいるもようです。


##Chainerのインストール
Chainerのインストールは，コマンド一発です。

```py3
pip install Chainer
```

しかし，これで失敗する人もいるようです。
私がそうだった。。。

エラーコードを見ていると，どうやら，VCのバージョン違いみたい。。。
[こちらの記事](http://qiita.com/icoxfog417/items/ca1521f9d62183cea234)が役に立ちました。

レジストリの，(略)\Microsoft\VisualStudioの中に\10.0\Setup\VCを作成。
ProductDirのキー，値14.0を追加。
無事にインストール成功
※私の環境では，VS2015がインストールされているので，値は14.0です。
※各人の環境に合わせてください。

入っているパッケージのリストとバージョンです。
Chainerに関係ないものもあります。

<img width="145" alt="pip_list.png" src="https://qiita-image-store.s3.amazonaws.com/0/113616/70cb0fd6-6efa-778a-4c03-0b3f5a8e1b77.png">



#畳み込みニューラルネットワーク

##必要なパッケージ
Chainerモジュール

```py3
from chainer import Chain, Variable, optimizers
import chainer.functions as F
```

scikit-learnモジュール
scikit-learnは，機械学習に関するものが準備されている。
機械学習用の様々なサンプルデータを取得するために次を記述

```py3
from sklearn.datasets import fetch_mldata
```

学習データと評価データを分割するために次を記述

```py3
from sklearn.cross_validation import train_test_split
```

認識率を計算するためと混合行列で評価するために次を記述

```py3
from sklearn.metrics import accuracy_score, confusion_matrix
```

numpyのモジュール
データを画像として扱いために次を記述

```py3
import numpy as np
```

matplotlibモジュール
データを可視化する場合は次を記述

```py3
import matplotlib.pyplot as plt
```

## MNISTのデータを準備
MNISTのデータをダウンロードし，準備する。
Chainerでは，データがfloat32かfloat64。ラベルがint32だと問題ない。

```py3
#
# MNISTのダウンロード
#
print("fetch MNIST dataset")
mnist = fetch_mldata("MNIST original", data_home=".")
dataset = np.asarray(mnist.data, np.float32)
dataset /= dataset.max()

target = np.asarray(mnist.target, np.int32)
```

## 学習データとテストデータの分割
最後のテストサイズが，テストデータの割合を決めています。

```py3
# 学習データとテストデータに分割（8:2）
data_train, data_test, label_train, label_test = train_test_split(dataset, target, test_size=0.2)
```

##学習データを画像形式に変換
（画像枚数，チャネル数，画像サイズ高さ，画像サイズ横）にする。

```py3
data_train = data_train.reshape((len(data_train), 1, 28, 28))
data_test = data_test.reshape((len(data_test), 1, 28, 28))
```

##モデルの作成

```py3
model = Chain(
    conv1=F.Convolution2D(1, 32, 3), #畳み込み（入力1枚，出力32枚，フィルタサイズ3）
    conv2=F.Convolution2D(32, 64, 3), #畳み込み（入力32枚，出力64枚，フィルタサイズ3）
    l1=F.Linear(576, 200), #全結合（入力576ユニット，出力200ユニット）
    l2=F.Linear(200, 100), #全結合（入力200ユニット，出力100ユニット）
    l3=F.Linear(100, 10) #全結合（入力100ユニット，出力10ユニット）
)
```

##ネットワークの構造
データは配列からChainerのVariableという型のオブジェクトに変換する必要がある。
あとは，どのような構造にするか書く。

```py3
def forward(x_batch, t_batch, is_train=True):
    x, t = Variable(x_batch), Variable(t_batch)
    h1 = F.max_pooling_2d(F.relu(model.conv1(x)), 3)
    h2 = F.max_pooling_2d(F.relu(model.conv2(h1)), 3)
    h3 = F.dropout(F.relu(model.l1(h2)), train=is_train)
    h4 = F.dropout(F.relu(model.l2(h3)), train=is_train)
    p = model.l3(h4)
    # 誤差を算出
    return F.softmax_cross_entropy(p, t), F.accuracy(p, t)
```

##最適化手法
ディープラーニングの最適化とは，モデルの予測値と実際の値との差から，重みを更新することです。
最適化手法はいろいろあります。Chainerで用意されている最適化手法は，

+ AdaDelta
+ AdaGrad
+ Adam
+ MomentumSGD(SGDにモーメントを加えたもの)
+ NesterovAG
+ RMSprop
+ RMSpropGraves
+ SGD(確率的勾配降下法)

があります。
書き方は，使い方手法を選択し，モデルを登録するだけ。


```py3
optimizer = optimizers.Adam()
optimizer.setup(model)
```

##学習

今回は，ミニバッチ学習をします。
ミニバッチ学習とは，全学習データからいくつか選んで学習する方法です。
他には，バッチ学習（全学習データを使う）やオンライン（学習データを一つずつ）などあります。

```py3
train_loss = []
train_accuracy = []
test_loss = []
test_accuracy = []

for epoch in range(n_epoch):
    print("epoch : %d" % (epoch+1))

    # ランダムに並び替える
    perm = np.random.permutation(N)
    sum_accuracy = 0
    sum_loss = 0

    # バッチサイズごとに学習
    b_start = time.time()
    for i in range(0, N, batchsize):
        x_batch = data_train[perm[i:i + batchsize]]
        t_batch = label_train[perm[i:i + batchsize]]

        # 勾配を初期化
        optimizer.zero_grads()

        # 順伝搬
        loss, accuracy = forward(x_batch, t_batch)

        # 誤差逆伝搬
        loss.backward()
        optimizer.update() #パラメータ更新

        train_loss.append(loss.data)
        train_accuracy.append(accuracy.data)
        sum_loss += float(loss.data) * batchsize
        sum_accuracy += float(accuracy.data) * batchsize

    batchtime = time.time() - b_start
    # 誤差と精度を表示
    print("[学習]loss: %f, accuracy: %f, time: %f秒" % (sum_loss / N, sum_accuracy / N, batchtime))
```

これで，ネットワークの学習ができます。


##評価
では，実際に学習し終えたネットワークの評価をしてみます。
テストデータは，14000ありますが，とりあえず30程度で試してみます。

```py3
num = 30
results_score = []

p_start = time.time()
# ランダムに並び替える
perm = np.random.permutation(N_test)
x_test = data_test[perm[0:0 + num]] #num枚選ぶ
t_test = label_test[perm[0:0 + num]]
results_score = predict(x_test, is_train=False)
predict_time = time.time() - p_start
print("[認識] %f秒" % predict_time)
```

###認識率や混合行列などみたいとき
axis=1で結果の中の最大値をとる

```py3
results = np.argmax(results_score, axis=1)
```

accuracy_scoreが認識率，confusion_matrixが混合行列

```py3
# 認識率と混合行列
score = accuracy_score(t_test, results)
print(score)
cmatrix = confusion_matrix(t_test, results)
print(cmatrix)
```

scikit-learnにあるclassification_reportを使うと，何がどれくらい正しかったかわかる。

```py3
print(classification_report(t_test, results))
```

#実験

会議中にコードを書いて実験をしたので，環境は次のようになります。

+ Let's note CF-SZ5

|種類|内容|
|:---|:---|
|OS|Windows 10 Pro 64bit|
|CPU|Intel Core i7-6500U 2.50GHz|
|Memory|8GB|

**学習**
エポックは20回，バッチサイズは20

**評価**
テストデータからランダムに30枚選択


#結果
学習時間は，1エポック約390秒
20回目の誤差は，0.024505。認識率は，0.993268。

認識時間は，30枚で，0.035254秒
混合行列による評価とclassification_reportは，次のようになった。

＜混合行列＞
<img width="93" alt="python_cnn_confusionmatrix.png" src="https://qiita-image-store.s3.amazonaws.com/0/113616/356d6440-aa3a-3888-b2e7-8a0a8d9c132c.png">


＜classification_report＞
<img width="246" alt="python_cnn_report.png" src="https://qiita-image-store.s3.amazonaws.com/0/113616/d0c3698d-8034-8bfe-4e1d-78c13dec2dd5.png">


#可視化
学習したconv1とconv2の重みは次のような感じでした。

**conv1の重み**
![conv1.png](https://qiita-image-store.s3.amazonaws.com/0/113616/1953b417-3115-debe-78f1-976a35147fe2.png)

**conv2の重み**
![conv2.png](https://qiita-image-store.s3.amazonaws.com/0/113616/ad73878c-f37d-fcc2-d9be-959199620324.png)


テストデータをいれたときにh1やh2はどのような出力になっているのかも可視化してみました。

**テストデータ**
![test_4.png](https://qiita-image-store.s3.amazonaws.com/0/113616/8ce0b9b0-e1ac-1a27-0a76-ebffab103c66.png)

**h1**
![h1_4.png](https://qiita-image-store.s3.amazonaws.com/0/113616/10f05de7-9990-617f-b992-c79cc5dd87e9.png)

**h2**
![h2_4.png](https://qiita-image-store.s3.amazonaws.com/0/113616/107d52a7-cbd8-4681-83a2-1b06adf49ce7.png)


h1はなんとなく"4"に見えるものもあるけど，h2はまったくわからないですね。



#おわりに
今回は，CPUでの実験をしてみた。NNなどの知識があれば，わりと早く組めると思った。
今後はGPU版も試してみたいな。

やってみる／動かしてみるスタイルの人ならPythonは結構いいのかも。


#付録：コード

```py3
#
# 必要なモジュールの読み込み
#
import time
from chainer import Chain, Variable, optimizers
import chainer.functions as F

# 機械学習用の様々なサンプルデータを取得する関数
from sklearn.datasets import fetch_mldata
# 学習データと評価データを分割するための関数
from sklearn.cross_validation import train_test_split
# 認識率を計算するための関数，混合行列で評価するための関数
from sklearn.metrics import accuracy_score, confusion_matrix, classification_report

import matplotlib.pyplot as plt
import numpy as np

#
# パラメータの定義
#
batchsize = 20 # バッチサイズ
n_epoch = 20 # 学習の繰り返し回数

#
# MNISTのダウンロード
#
print("fetch MNIST dataset")
mnist = fetch_mldata("MNIST original", data_home=".")
dataset = np.asarray(mnist.data, np.float32)
dataset /= dataset.max()

target = np.asarray(mnist.target, np.int32)


# 学習データとテストデータに分割（8:2）
data_train, data_test, label_train, label_test = train_test_split(dataset, target, test_size=0.2)

print("train data = %d" % label_train.size)
print("test data = %d" % label_test.size)

N = label_train.size
N_test = label_test.size


# 学習データを画像に変換
data_train = data_train.reshape((len(data_train), 1, 28, 28))
data_test = data_test.reshape((len(data_test), 1, 28, 28))

#
# モデルの設定
# 層のパラメータ
#
model = Chain(
    conv1=F.Convolution2D(1, 32, 3),
    conv2=F.Convolution2D(32, 64, 3),
    l1=F.Linear(576, 200),
    l2=F.Linear(200, 100),
    l3=F.Linear(100, 10)
)

#
# ネットワークの構造
#
def forward(x_batch, t_batch, is_train=True):
    x, t = Variable(x_batch), Variable(t_batch)
    h1 = F.max_pooling_2d(F.relu(model.conv1(x)), 3)
    h2 = F.max_pooling_2d(F.relu(model.conv2(h1)), 3)
    h3 = F.dropout(F.relu(model.l1(h2)), train=is_train)
    h4 = F.dropout(F.relu(model.l2(h3)), train=is_train)
    p = model.l3(h4)
    # 誤差を算出
    return F.softmax_cross_entropy(p, t), F.accuracy(p, t)

def predict(x_batch, is_train=True):
    x = Variable(x_batch)
    h1 = F.max_pooling_2d(F.relu(model.conv1(x)), 3)
    h2 = F.max_pooling_2d(F.relu(model.conv2(h1)), 3)
    h3 = F.dropout(F.relu(model.l1(h2)), train=is_train)
    h4 = F.dropout(F.relu(model.l2(h3)), train=is_train)
    p = model.l3(h4)
    return p.data

#
# 学習のさせかた
#
optimizer = optimizers.Adam()
optimizer.setup(model)

#
# 学習
#
train_loss = []
train_accuracy = []
test_loss = []
test_accuracy = []

for epoch in range(n_epoch):
    print("epoch : %d" % (epoch+1))

    # ランダムに並び替える
    perm = np.random.permutation(N)
    sum_accuracy = 0
    sum_loss = 0

    # バッチサイズごとに学習
    b_start = time.time()
    for i in range(0, N, batchsize):
        x_batch = data_train[perm[i:i + batchsize]]
        t_batch = label_train[perm[i:i + batchsize]]

        # 勾配を初期化
        optimizer.zero_grads()

        # 順伝搬
        loss, accuracy = forward(x_batch, t_batch)

        # 誤差逆伝搬
        loss.backward()
        optimizer.update() #パラメータ更新

        train_loss.append(loss.data)
        train_accuracy.append(accuracy.data)
        sum_loss += float(loss.data) * batchsize
        sum_accuracy += float(accuracy.data) * batchsize

    batchtime = time.time() - b_start
    # 誤差と精度を表示
    print("[学習]loss: %f, accuracy: %f, time: %f秒" % (sum_loss / N, sum_accuracy / N, batchtime))

# 学習したモデルを保存
import pickle
pickle.dump(model, open('model', 'wb'), -1)

#
# 評価
#
num = 30
results_score = []

p_start = time.time()
# ランダムに並び替える
perm = np.random.permutation(N_test)
x_test = data_test[perm[0:0 + num]] #num枚選ぶ
t_test = label_test[perm[0:0 + num]]
results_score = predict(x_test, is_train=False)
results = np.argmax(results_score, axis=1)

predict_time = time.time() - p_start
print("[認識] %f秒" % predict_time)
#print("結果: " + str(results))
#print("ラベル: " + str(t_test))
print(classification_report(t_test, results))


# 認識率と混合行列
score = accuracy_score(t_test, results)
print(score)
cmatrix = confusion_matrix(t_test, results)
print(cmatrix)
```