Coursera Machine Learningの課題をPythonで: ex4（ニューラルネットワークで手書き数字認識）

# はじめに
[Coursera Machine Learningの課題をPythonで: ex3（ロジスティック回帰で手書き数字認識）](http://qiita.com/nokomitch/items/35c4816b17130c82f7da)に触発されて書きました。
昨年scikit-learnにNeural Networkが実装されたこともあり、本記事では、課題ex4をPythonで書いてみます。

# ex4の概要
ex4は、ニューラルネットワークを用いて手書き数字を認識するタスク。データセットはex3に引き続きMNISTのサブセットということで、20x20ピクセルのグレースケール画像が5000枚、これがMatlab/Octaveの.matというデータ形式で与えられています。

## 前編
まずは5000枚の画像を読み込み、ランダムに選ばれた100枚を表示します。
([Coursera Machine Learningの課題をPythonで: ex3（ロジスティック回帰で手書き数字認識）](http://qiita.com/nokomitch/items/35c4816b17130c82f7da)を参考にしました)

```py3:ex4.py
import numpy as np
import matplotlib.pyplot as plt
import scipy.io as scio
from sklearn.model_selection import train_test_split
from sklearn.neural_network import MLPClassifier

# scipy.io.loadmat()を使ってmatlabデータを読み込み
data = scio.loadmat('ex4data1.mat')
X = data['X']  # X は 5000x400 行列
y = data['y'].ravel()  # y は 5000 x 1 行列、ravel()を使って5000次元ベクトルに変換

sel = np.random.randint(0,len(X),100)
fig = plt.figure()
plt.subplots_adjust(left=None, bottom=None, right=None, top=None, wspace=None, hspace=None)
for i in range(0,100):
    ax = fig.add_subplot(10,10,i+1)
    ax.axis('off')
    ax.imshow(X[sel[i]].reshape(20,20).T, cmap = plt.get_cmap('gray'))
plt.show()
```

実行結果はこちら
![Unknown.png](https://qiita-image-store.s3.amazonaws.com/0/108483/f0d39962-b19d-c666-17c9-8dadd0ae0f20.png)

## 中編
続いて、以下のように25個のユニットを持つ隠れ層を1層持つニューラルネットワークを実装します。
<img width="250" alt="スクリーンショット 2017-02-02 19.31.20.png" src="https://qiita-image-store.s3.amazonaws.com/0/108483/728888cc-fe72-e3a9-6987-02de7b334e5d.png">

とは言っても、scikit-learnのMLPClassifierを使うだけなのでコードはシンプルです。

```py3:ex4.py
#1 hidden units (size 25)
model = MLPClassifier(hidden_layer_sizes=(25, ), solver="sgd",
                      random_state=0,max_iter=1000,
                      alpha = 0.0001, tol=1e-9)
model.fit(X, y)
model.score(X, y)
```

引数は色々取ることができます。詳しくは[こちら(公式)](http://scikit-learn.org/stable/modules/generated/sklearn.neural_network.MLPClassifier.html#sklearn.neural_network.MLPClassifier)。

実行すると、訓練データでの正答率は **0.96579** と表示されました。

Courseraでは伝達関数にシグモイド関数を使っていますが、MLPClassifierではデフォルトだとReLU（ランプ関数）が使われます。
こちらの方が一般的に精度が高いようなので、Courseraの課題とは違いますがReLUを利用しました。もしシグモイド関数を使いたい場合は、```activation='logistic'```と引数を指定すればOKです。
(因みに上記のコードのままシグモイド関数を使った場合の正答率は **0.91859** となりました。結構下がりましたね。。)

## 後編
課題の最後に、隠れ層を可視化してみるコーナーがあります。
こちらはCoursera上では宿題ではありませんが、以下、pythonで書いてみます。

```py3:ex3_hidden.py
fig, axes = plt.subplots(5, 5)
vmin, vmax = model.coefs_[0].min(), model.coefs_[0].max()
for coef, ax in zip(model.coefs_[0].T, axes.ravel()):
    ax.matshow(coef.reshape(20, 20), cmap=plt.cm.gray, vmin=.5 * vmin,
               vmax=.5 * vmax)
    ax.set_xticks(())
    ax.set_yticks(())

plt.show()
```

隠れ層を見るには、学習後のcoefにアクセスすればOKです。
隠れ層の全てのユニットを同じスケールで可視化するために、vminとvmaxにmodel.coefs_[0]の最小値と最大値をセットしました。

実行結果はこちら
![Unknown-1.png](https://qiita-image-store.s3.amazonaws.com/0/108483/9ca711bf-d81b-6c79-78c7-341e5a73e5c5.png)

ほとんど使われていないユニットもあることがわかります。

## おまけ
また、[Coursera Machine Learningの課題をPythonで: ex3（ロジスティック回帰で手書き数字認識）](http://qiita.com/nokomitch/items/35c4816b17130c82f7da)から認識を間違えたデータを表示するコードもお借りして、表示してみました。

```py3:ex4_omake.py
wrong_index = np.array(np.nonzero(np.array([model.predict(X) != y]).ravel())).ravel()
wrong_sample_index = np.random.randint(0,len(wrong_index),25)
fig = plt.figure()
plt.subplots_adjust(left=None, bottom=None, right=None, top=None, wspace=0.5, hspace=0.5)
for i in range(0,25):
    ax = fig.add_subplot(5,5,i+1)
    ax.axis('off')
    ax.imshow(X[wrong_index[wrong_sample_index[i]]].reshape(20,20).T, cmap = plt.get_cmap('gray'))
    ax.set_title(str(model.predict(X[wrong_index[wrong_sample_index[i]]].reshape(1,-1))[0]))
plt.show()
```
※ ax.set_title部分のみ、scikit-learnのバージョン0.18に対応して少しだけ書き換えました。具体的には、1次元配列を渡すことが奨励されないようになったことを受け、サンプルデータをreshape(1, -1)とreshapeしました。

実行結果はこちら
![Unknown-2.png](https://qiita-image-store.s3.amazonaws.com/0/108483/3aa58864-ba51-44c0-af1d-e69bb7960bff.png)
人間が見てもけっこう惜しいものがあって、面白いです。
しかしチューニングが必要ですね。