「ゼロから作るDeep Learning」自習メモ（その６の３）９未満か９以上か

「ゼロから作るDeep Learning」(斎藤 康毅　著　オライリー・ジャパン刊)を読んでいる時に、参照したサイト等をメモしていきます。 [その６の２](https://qiita.com/slow_learner/items/95603dcbe8784036fadb)←　→ [その７](https://qiita.com/slow_learner/items/efbe24999562c3630c93)

いまだに２層のニューラルネットであれこれやってます。

#1桁の数字を２つ１組にして、その和が９未満か９以上かを、２層ニューラルネットに判定させます

こんなのは、計算させたほうが早そうですが、

>P86
ニューラルネットワークの利点は、すべての問題を同じ流れで解くことができる点にあります。たとえば、解くべき問題が「5」を認識する問題なのか、「犬」を認識する問題なのか、それとも、「人の顔」を認識する問題なのかといった詳細とは関係なしに、ニューラルネットワークは与えられたデータをただひたすら学習し、与えられた問題のパターンを発見しようと試みます。

ということなので、あえてやってみました。
少ないデータ量でテストできるはずなので、その処理過程も検証してみようかと。

処理の核となる２層ニューラルネットは、p114のものを流用。

```python
import numpy as np
from common.functions import *

class TwoLayerNet:
    def __init__(self, input_size, hidden_size, output_size, weight_init_std=0.01):
        # 重みの初期化
        self.params = {}
        self.params['W1'] = weight_init_std * np.random.randn(input_size, hidden_size)
        self.params['b1'] = np.zeros(hidden_size)
        self.params['W2'] = weight_init_std * np.random.randn(hidden_size, output_size)
        self.params['b2'] = np.zeros(output_size)
        
    def predict(self, x):
        W1, W2 = self.params['W1'], self.params['W2']
        b1, b2 = self.params['b1'], self.params['b2']
        a1 = np.dot(x, W1) + b1
        z1 = sigmoid(a1)
        a2 = np.dot(z1, W2) + b2
        y = softmax(a2)
        return y

    # x:入力データ、　t:教師データ
    def loss(self, x, t):
        y = self.predict(x)
        return cross_entropy_error(y, t)
    
    def accuracy(self, x, t):
        y = self.predict(x)
        y = np.argmax(y, axis=1)
        t = np.argmax(t, axis=1)
        accuracy = np.sum(y == t) / float(x.shape[0])
        return accuracy
    
    def gradient(self, x, t):
        W1, W2 = self.params['W1'], self.params['W2']
        b1, b2 = self.params['b1'], self.params['b2']
        grads = {}
        batch_num = x.shape[0]
        
        # forward
        a1 = np.dot(x, W1) + b1
        z1 = sigmoid(a1)
        a2 = np.dot(z1, W2) + b2
        y = softmax(a2)
        
        # backward
        dy = (y - t) / batch_num
        grads['W2'] = np.dot(z1.T, dy)
        grads['b2'] = np.sum(dy, axis=0)
        
        dz1 = np.dot(dy, W2.T)
        da1 = sigmoid_grad(a1) * dz1
        grads['W1'] = np.dot(x.T, da1)
        grads['b1'] = np.sum(da1, axis=0)

        return grads  
```
（追記）
このプログラムの predict 定義の最後のところ、出力層の活性化関数のところですが、
>p64
出力層で利用する活性化関数は、解く問題の性質に応じて決めます。たとえば、
回帰問題では恒等関数、2 クラス分類問題ではシグモイド関数、多クラス分類
ではソフトマックス関数を使うのが一般的です。出力層の活性化関数について
は、次節で詳しく説明します。

ということなので、
y = sigmoid(a2)
としてもいいのかもしれません。実際に、そのように変更して動かしてみても、ちゃんと学習できました。

訓練データはこんな感じで作成。100通りある組合せのうち、90組を訓練データ、10組をテストデータにしました。

```python
# データ作成
import random

w = np.zeros((100,2), dtype=np.uint8)
for i in range(0,10):
    for j in range(0,10):
        w[i*10+j][:] = [i, j]

r = list(range(0,100))
random.shuffle(r)

t = np.zeros((100,2), dtype=np.uint8)
l = np.zeros((100), dtype=np.uint8)
j = 0
for i in r:
    t[j] = w[i]
    if (t[j][0]+t[j][1])<9:
        l[j] = 1
    else:
        l[j] = 0
    j += 1

train_mask = np.array(range(90))
x_train = t[train_mask]
t_train = l[train_mask]

batch_mask = np.array(range(90,100))
x_test = t[batch_mask]
t_test = l[batch_mask]
```
訓練データの内容。２つの数字と、その和が９未満なら１、９以上なら０

>[3 5] : 1
[3 0] : 1
[2 0] : 1
[1 3] : 1
[3 3] : 1
[5 1] : 1
[1 9] : 0
[7 5] : 0
・
・
・
[1 5] : 1
[9 7] : 0
[0 1] : 1
[3 4] : 1
[8 6] : 0
[5 6] : 0

```python
# 学習
def to_one_hot(label):
    t = np.zeros((label.size, 2))
    for i in range(label.size):
        t[i][label[i]] = 1
    return t

l_train = to_one_hot(t_train)
    
# ハイパーパラメータ
iters_num = 100
train_size = x_train.shape[0]
batch_size = 10
learning_rate = 0.1

train_loss_list = []

network = TwoLayerNet(input_size=2, hidden_size=3, output_size=2)
for i in range(iters_num):
    # ミニバッチの取得
    batch_mask = np.random.choice(train_size, batch_size)
    x_batch = x_train[batch_mask]
    t_batch = l_train[batch_mask]
    
    # 勾配の計算
    grad = network.gradient(x_batch, t_batch) # 誤差逆伝播法 速い
    
    # パラメータの更新
    for key in ('W1', 'b1', 'W2', 'b2'):
        network.params[key] -= learning_rate * grad[key]

    #学習経過の記録
    loss = network.loss(x_batch, t_batch)
    train_loss_list.append(loss)
```
ハイパーパラメータが、
iters_num = 100
batch_size = 10
だと、学習経過の記録が、こんな感じで
![6-3-1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/686010/7d2f2b71-8110-0957-b2e1-ab8329365359.jpeg)
学習回数が不足している感じです。
テストデータを判定させると、60%の精度しかありません。

```python
# テストデータで評価
x = x_test
t = t_test
#network は、上の処理で作成したものをそのまま使う
accuracy_cnt = 0
for i in range(len(x)):
    y = network.predict(x[i])
    p= np.argmax(y)
    if p == t[i]:
        print(str(x[i]) + " : " + str(p))
        accuracy_cnt += 1
    else:
        print(str(x[i]) + " : " + str(p) + " ←間違い")

print("Accuracy:" + str(float(accuracy_cnt) / len(x))) 
```
>[9 2] : 0
[9 3] : 0
[2 3] : 0　←間違い
[1 4] : 0　←間違い
[6 9] : 0
[3 2] : 0　←間違い
[9 5] : 0
[4 5] : 0
[6 2] : 0　←間違い
[7 2] : 0
Accuracy:0.6

ハイパーパラメータを、
iters_num = 1000
batch_size = 10
にすると、90%の精度に。
![6-3-0.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/686010/f8ce725c-b08e-1841-f26f-2e62f3874663.jpeg)

>[9 2] : 0
[9 3] : 0
[2 3] : 1
[1 4] : 1
[6 9] : 0
[3 2] : 1
[9 5] : 0
[4 5] : 0
[6 2] : 0  ←間違い
[7 2] : 0
Accuracy:0.9

iters_num = 10000
batch_size = 10
で、100%の精度に。
![6-3-2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/686010/1cd9f09a-1f95-db59-5f29-13fe428a0b88.jpeg)
>[9 2] : 0
[9 3] : 0
[2 3] : 1
[1 4] : 1
[6 9] : 0
[3 2] : 1
[9 5] : 0
[4 5] : 0
[6 2] : 1
[7 2] : 0
Accuracy:1.0

#バッチ件数を増やして、学習回数を減らしてみた
iters_num = 200
batch_size = 50
精度８０％
![6-3-3.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/686010/ecc8e091-6704-2c19-90a7-bb3d3233061b.jpeg)

iters_num = 200
batch_size = 80
精度６０％
![6-3-4.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/686010/c1b66fab-aa67-73f3-6dee-655831a16c5c.jpeg)


以上から、バッチ件数よりも学習回数が重要らしいことがわかる。
最低2000回くらい必要か？

#バッチ件数を減らしてみた

iters_num = 2000
batch_size = 5
精度９０％
![6-3-5.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/686010/677f75a0-61d7-1295-4987-743d6598c955.jpeg)

学習回数が2000回あれば、学習は進んでいくが、バッチ件数が少なすぎると、ブレが大きくなるようだ。

iters_num = 10000
batch_size = 5
精度９０％
![6-3-6.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/686010/8f428056-b6be-ccb2-b7ed-76b141e2b6f1.jpeg)
少ないデータで学習すると、極端に偏ったデータだけがそろった時に、偏った学習をしてしまうようだ。

#中間層のサイズは？

このデータの場合、１本の直線で区切って判別できるので、それほど複雑な中間層はいらないのではないか？　というか、中間層の層の数や層のサイズっていうのは、どうやって決めればいいのだろう？

まあ、悩むよりも実際にやってみたほうがわかりやすいかも。
ということで、バッチサイズ２０、学習回数１万回の設定で、層のサイズだけ変えてみた。

##隠れ層のサイズを１にした場合

けっこう、ふつうに学習できているような？
![6-3-9.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/686010/f71ed15e-fe1a-c51b-0db7-023f8e4369a5.jpeg)
テストデータの推論も100%あってるし。

しかし、データを変えて何回かためしていると、こんな風に収束しなくなることも。
勾配をうまく見つけられなくなったのだろうか？

![6-3-10.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/686010/9e07c307-48de-6208-b875-316b0373d650.jpeg)
テストデータを与えて推論させても、50から70%の精度しかない。内容を見ると、すべてを０と判定している。つまり、まともに直線を引けていないようだ。このときと同じデータを、隠れ層のサイズを２以上にしてやると、ちゃんと学習できています。

##隠れ層のサイズを２に

ふつうに学習できている。何回か試行したが、収束しなくなることはなかった。
![6-3-11.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/686010/d7a3250c-e9c8-17a8-c442-09115bceb90a.jpeg)

##隠れ層のサイズを１０に
![6-3-12.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/686010/33bceb2a-7fd0-bc79-d593-f10c1b473e91.jpeg)

##隠れ層のサイズを100に
![6-3-13.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/686010/6eb2bf81-85aa-4832-cd05-bcd748af5e1d.jpeg)

##隠れ層のサイズを1000に
さすがに、サイズが1000もあると、ムダに計算の時間が長い。しかし、結果はたいして変わらない。
![6-3-14.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/686010/811b0ee7-c3b5-5492-de70-2569fdb686e4.jpeg)

たぶん、隠れ層のサイズは入力や出力の要素数より多くないといけないと思われる。まあ、ふつうに考えても、そうだろうな、と思います。しかし、多ければいいというものでもないようです。いくつがいいのかは、今回のデータは単純すぎるので、よくわかりませんが。
今回は試していませんが、中間層の数は、判定の複雑さによるようです。今回は直線１本で区切れるので、２層でまったく問題ありませんでしたが、これが何本もの線や平面で区切らないといけないなら、層の数を増やさないといけないのだろうな、と思います。


#おまけ fill_between にはまってしまった
５章を読んでも、よくわからないから、未だに４章までのところで、あれこれ遊んでます。
このメモでやっていることをグラフにしてみようと思ったら、matplotlib沼にはまってしまいました。

作ろうとしたグラフは、これ。今回やったことというのは、このグラフの青い部分と赤い部分を分類しているだけ、ということを示したかったわけです。おそらく、画像の識別でやっていることというのもこれと同じことなんだろうと。元データの要素数が違いますが、多次元空間で多次元平面で仕切られた部分のどちらにあるかで判定している、というところは、多分、同じ仕組みなんだろうと、納得できました。
![6-3-7.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/686010/32dc1e87-6ca6-7676-c6f5-e1453c1b0dc4.jpeg)

この程度のグラフを描くのに、いろんなところでつまづいた結果、こんなプログラムでなんとかできました。

```python
import numpy as np 
import matplotlib.pylab as plt

plt.figure(figsize=(8,8))
plt.xlabel("x",fontsize=30)
plt.ylabel("y",fontsize=30, labelpad=18)
plt.grid(True)

x = np.arange(-1, 11, 1)
y = 9 - x
plt.plot(x, y, color='black')

plt.axhline(y=9, xmin=0.0, xmax=1.0, color="red",linewidth=1,linestyle="dashed")
plt.axvline(x=9, ymin=0.0, ymax=1.0, color="red",linewidth=1,linestyle="dashed")
plt.fill_between(x, y, 9, where=(x*y>=0), color='pink', alpha=.5)
plt.text(4,6,"(x+y)>=9",fontsize=30,color="red")

plt.axhline(y=0, xmin=0.0, xmax=1.0, color="blue",linewidth=1,linestyle="dashed")
plt.axvline(x=0, ymin=0.0, ymax=1.0, color="blue",linewidth=1,linestyle="dashed")
plt.fill_between(x, y, 0, where=(x*y>=0),color='blue', alpha=.25)
plt.text(2,2,"(x+y)<9",fontsize=30,color="blue")

plt.show()
```
fill_between　を使えば、グラフとグラフの間に色を塗れるのはすぐにわかったのですが、ｘ＜０の範囲やｘ＞９の範囲にも色を塗ってしまうので、これをどうすれば無くせるかに悩みました。
where=(x>=0) とすると ｘ＞９の範囲は塗ってしまいます。
where=(x>=0 and x<=9) や where=(x between 0 and 9)とするとエラーになります。

![6-3-8.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/686010/ccaf23e0-31f3-3484-1120-2e21e25c6e59.jpeg)
塗りたくないのは、x<0 と x>9 の範囲ですが、x<0 のとき y>9 、x>9 のとき y<0 ということに気がついて、 where=(x*y>=0) という条件を指定すればいいことに気がつきました。気がつくまで１時間以上かかりました。

#追記
ハイパーパラメータを
iters_num = 10000
batch_size = 10
として学習し、テストの正答が100%になったとき、

```python
network.params['W1'] 
```
>array([[0.78114325, 0.73202525, 1.01373241],
       [0.73870312, 0.69091733, 0.96524673]])

```python
network.params['b1'] 
```
>array([-6.55019861, -6.12926691, -8.54456424])

となった。
このパラメータをニューラルネットの１層目にあてはめると、

```math
a_1 = 0.78114325x_1 + 0.72870312x_2 - 6.55019861
```

という式になる。
 $a_1=0$となる直線を、$x_1$を横軸、$x_2$を縦軸にした座標上に描くと、

$0.78114325x_1 + 0.72870312x_2 - 6.55019861 = 0$
$0.72870312x_2 = -0.78114325x_1 + 6.55019861$
$x_2 = \frac{-0.78114325}{0.72870312}x_1\ + \frac{6.55019861}{0.72870312}$
$x_2 = -1.057452214x_1 + 8.8671598$

となり、このようなグラフになる。

![6p3p2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/686010/55322913-240e-5aa4-4e1b-27b559c551e2.jpeg)

本来の式とはズレがあるが、そこそこ近い式になるように、重みＷが求められています。
直線が a = 0 のとき。赤い部分が a > 0 で、青い部分が a < 0 の場合ということになります。

そして、この a の値をシグモイド曲線にあてはめると
![6p3p3.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/686010/6908e34d-ec5a-67ef-8706-aeb56dd89060.jpeg)
a > 0 のとき、0.5より大きくなり
a < 0 のとき、0.5より小さくなります。


#参考にしたサイト
[1.4. Matplotlib: 作図](http://www.turbare.net/transl/scipy-lecture-notes/intro/matplotlib/matplotlib.html)
[早く知っておきたかったmatplotlibの基礎知識、あるいは見た目の調整が捗るArtistの話](https://qiita.com/skotaro/items/08dc0b8c5704c94eafb9)
[matplotlib.org](https://matplotlib.org/gallery/lines_bars_and_markers/fill_between_demo.html#sphx-glr-gallery-lines-bars-and-markers-fill-between-demo-py)

 [その６の２](https://qiita.com/slow_learner/items/95603dcbe8784036fadb)←　→ [その７](https://qiita.com/slow_learner/items/efbe24999562c3630c93)

[読めない用語集](https://qiita.com/slow_learner/items/03eb3367119e98b5a22c)