ディープラーニングを実装から学ぶ～ （まとめ5）MNIST NNのみで99.5%(データ拡張)

前回は、CNNを用いてMNISTのテストデータの正解率を99%まで向上させることができました。
今回は、CNNを使わずに通常のニューラルネットワークのみで99%を目指します。
(最終的に、99.5%までテストデータの正解率を向上させることができました。)

# プログラム見直し
「ディープラーニングを実装から学ぶ～ (まとめ１）実装は、実は簡単」のプログラムを利用しますが、「ディープラーニングを実装から学ぶ～ （まとめ４）MNISTで99%も簡単！」での変更の対応を事前に行います。
変更点は以下です。

* 学習データのエポックごとの正解率を学習時の結果を利用します。
* エポックごとの正解率表示を関数とします。また、テストデータの予測をバッチサイズごとに行います。
* 学習時にデータをソートするかどうかをフラグにします。

プログラムは、最後のプログラム全体に記載しますので、そちらを参考にしてください。

まず、MNISTのデータを読み込みます。MNISTのデータファイルは、あらかじめdataフォルダに格納しておくこととします。

```python
# MNISTデータ読み込み
x_train, t_train, x_test, t_test = load_mnist('data/')

# 入力データの正規化(0～1)
nx_train = x_train/255
nx_test  = x_test/255
```

# データ拡張
MNISTは、手書き数字です。大きく書く人や小さく書く人、傾けて書く人など癖があります。データのバリエーションを増やしてみましょう。

## 平行移動
上下、左右に平行移動した図形を考えます。$(x,y)$を横方向に、$ d_x $、縦方向に、$ d_y $移動した場合、以下の式で表せます。

```math
x_{shift} = x + d_x\\
y_{shift} = y + d_y
```

移動するプログラムを考えます。平行移動のみであれば簡単ですが、今後の拡大・縮小や回転にも対応できる方法とします。

MNISTは、28$\times$28の画像です。以下のように、画像の中心を原点として考えます。
![5_rei.png](https://qiita-image-store.s3.amazonaws.com/0/212365/45ce2704-3c7e-4313-52e9-c73608a4bd4c.png)

それぞれのピクセルの中央の座標がどこに移動するかを考えます。
ここでは、横方向に1($ d_x = 1 $)、縦方向に1($ d_y = 1 $)平行移動する場合を考えます。
中央の座標が以下のようになります。
(0.5, 0.5) → (0.5+1, 0.5+1) = (1.5, 1.5)
よって、以下のように移動します。
(0, 0) → (1, 1)
![shift.png](https://qiita-image-store.s3.amazonaws.com/0/212365/f6db5358-9ca1-edfb-296a-95b5ca6269cb.png)

平行移動するプログラムを考えます。
画像のサイズをw,hとすると中心を以下のように計算します。

```python
    mw = int(w/2) # 横方向の中心
    mh = int(h/2) # 縦方向の中心
```

各ピクセルの中心がどこに入るかを計算します。
画像の中心からの位置を計算するためmw,mhを引いて、各ピクセルの中心を求めるため0.5を足します。それに、移動量x_shift_range、y_shift_rangeを加えます。最後に、位置を戻すため、mw,mhを加えます。

```python
            x_gen = int(np.floor((x - mw + 0.5) + x_shift_range + mw))
            y_gen = int(np.floor((y - mh + 0.5) + y_shift_range + mh))
```

移動先の位置に元データのピクセル値を代入します。
気を付けるべきは、移動後にもとの画像サイズをはみ出す場合があることです。そのため、はみ出した部分は無視します。

```python
            # はみ出した部分は無視
            if y_gen < h and x_gen < w and y_gen >= 0 and x_gen >= 0:
                img_gen[:, y_gen, x_gen] = img_r[:, y, x]
```

MNISTのデータを1次元のデータとして読み込んでいました。画像として処理するため2次元にreshapeします。reshapeするための型をパラメータで渡します。
プログラム全体です。

```python
def shift(img, shape=None, x_shift_range=0, y_shift_range=0):
    img_shape = img.shape
    img_r = img
    if shape is not None:
        img_r = img.reshape((img.shape[0],) + shape)
    d, h, w = img_r.shape
    mw = int(w/2) # 横方向の中心
    mh = int(h/2) # 縦方向の中心

    # データ生成
    img_gen = np.zeros_like(img_r)
    for y in range(h):
        for x in range(w):
            # 平行移動
            x_gen = int(np.floor((x - mw + 0.5) + x_shift_range + mw))
            y_gen = int(np.floor((y - mh + 0.5) + y_shift_range + mh))
            # はみ出した部分は無視
            if y_gen < h and x_gen < w and y_gen >= 0 and x_gen >= 0:
                img_gen[:, y_gen, x_gen] = img_r[:, y, x]

    return img_gen.reshape(img_shape)
```

上下、左右に１ピクセル移動します。

```python
# 上下、左右に１ピクセル移動
nx_train_u1 = shift(nx_train, shape=(28,28), x_shift_range= 0, y_shift_range=-1) # 上
nx_train_d1 = shift(nx_train, shape=(28,28), x_shift_range= 0, y_shift_range= 1) # 下
nx_train_l1 = shift(nx_train, shape=(28,28), x_shift_range=-1, y_shift_range= 0) # 左
nx_train_r1 = shift(nx_train, shape=(28,28), x_shift_range= 1, y_shift_range= 0) # 右
```

１番目のデータを表示してみます。

```python
import matplotlib.pyplot as plt

plt.figure(figsize=(20, 6))
plt.subplot(1, 5, 1)
plt.imshow(nx_train[0].reshape(28,28), 'gray')
plt.subplot(1, 5, 2)
plt.imshow(nx_train_u1[0].reshape(28,28), 'gray')
plt.subplot(1, 5, 3)
plt.imshow(nx_train_d1[0].reshape(28,28), 'gray')
plt.subplot(1, 5, 4)
plt.imshow(nx_train_l1[0].reshape(28,28), 'gray')
plt.subplot(1, 5, 5)
plt.imshow(nx_train_r1[0].reshape(28,28), 'gray')
plt.show()
```

左から、元データ、上、下、左、右に１ピクセル移動したデータです。
![shift_5_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212365/3fea4b69-2926-33d0-e616-eadec76fa3eb.png)

元の画像と生成した画像を結合し学習してみます。

```python
# 上下、左右に１ピクセル移動データを結合
nx_train_s = np.concatenate([nx_train,
                             nx_train_u1, nx_train_d1, nx_train_l1, nx_train_r1])
t_train_s  = np.concatenate([t_train,
                             t_train, t_train, t_train, t_train])
```

パラメータは、基本的に、「ディープラーニングを実装から学ぶ～ (まとめ１）実装は、実は簡単」と同じですが、以下の２点を変更しています。

* 学習率を0.25に変更
* エポックごとにデータをソート

```python
# ノード数設定
d0 = nx_train_s.shape[1]
d1 = 100 # 1層目のノード数
d2 = 50  # 2層目のノード数
d3 = 10
# 重みの初期化(-0.1～0.1の乱数)
np.random.seed(8)
W1 = np.random.rand(d0, d1) * 0.2 - 0.1
W2 = np.random.rand(d1, d2) * 0.2 - 0.1
W3 = np.random.rand(d2, d3) * 0.2 - 0.1
# バイアスの初期化(0)
b1 = np.zeros(d1)
b2 = np.zeros(d2)
b3 = np.zeros(d3)

# 学習率
lr = 0.25
# バッチサイズ
batch_size = 100
# 学習回数
epoch = 50
# シャッフルフラグ
shuffle = True

# 誤差、正解率表示
print_metrics(0, nx_train_s, t_train_s, None, nx_test, t_test, None, W1, b1, W2, b2, W3, b3)

for i in range(epoch):
    # データシャッフル
    idx = np.arange(nx_train_s.shape[0])
    if shuffle:
        np.random.shuffle(idx)
    
    # 学習
    y_train_s = np.zeros_like(t_train_s)
    for j in range(0, nx_train_s.shape[0], batch_size):
         y_train_s[idx[j:j+batch_size]], W1, b1, W2, b2, W3, b3 = learn(nx_train_s[idx[j:j+batch_size]], t_train_s[idx[j:j+batch_size]], W1, b1, W2, b2, W3, b3, lr)

    # 誤差、正解率表示
    print_metrics(i+1, nx_train_s, t_train_s, y_train_s, nx_test, t_test, None, W1, b1, W2, b2, W3, b3)
```

```
  0 train_rate= 12.43% test_rate= 12.18% train_err= 2.30582 test_err= 2.30601
  1 train_rate= 93.50% test_rate= 97.73% train_err= 0.20986 test_err= 0.07357
  2 train_rate= 97.61% test_rate= 97.94% train_err= 0.07629 test_err= 0.06255
  3 train_rate= 98.21% test_rate= 98.36% train_err= 0.05618 test_err= 0.04926
  4 train_rate= 98.55% test_rate= 98.38% train_err= 0.04516 test_err= 0.04923
  5 train_rate= 98.78% test_rate= 98.42% train_err= 0.03767 test_err= 0.04756
  6 train_rate= 98.92% test_rate= 98.38% train_err= 0.03251 test_err= 0.05573
  7 train_rate= 99.08% test_rate= 98.44% train_err= 0.02855 test_err= 0.04835
  8 train_rate= 99.18% test_rate= 98.65% train_err= 0.02500 test_err= 0.04671
  9 train_rate= 99.24% test_rate= 98.74% train_err= 0.02253 test_err= 0.04717
 10 train_rate= 99.33% test_rate= 98.60% train_err= 0.01991 test_err= 0.05191
 11 train_rate= 99.41% test_rate= 98.55% train_err= 0.01792 test_err= 0.05125
 12 train_rate= 99.46% test_rate= 98.49% train_err= 0.01618 test_err= 0.05278
 13 train_rate= 99.50% test_rate= 98.55% train_err= 0.01480 test_err= 0.05487
 14 train_rate= 99.56% test_rate= 98.71% train_err= 0.01353 test_err= 0.05082
 15 train_rate= 99.56% test_rate= 98.36% train_err= 0.01292 test_err= 0.06426
 16 train_rate= 99.57% test_rate= 98.52% train_err= 0.01242 test_err= 0.06116
 17 train_rate= 99.61% test_rate= 98.64% train_err= 0.01126 test_err= 0.05805
 18 train_rate= 99.66% test_rate= 98.46% train_err= 0.01006 test_err= 0.06139
 19 train_rate= 99.70% test_rate= 98.54% train_err= 0.00899 test_err= 0.06503
 20 train_rate= 99.70% test_rate= 98.51% train_err= 0.00862 test_err= 0.06596
 21 train_rate= 99.75% test_rate= 98.61% train_err= 0.00729 test_err= 0.06831
 22 train_rate= 99.73% test_rate= 98.47% train_err= 0.00786 test_err= 0.07145
 23 train_rate= 99.74% test_rate= 98.55% train_err= 0.00779 test_err= 0.07440
 24 train_rate= 99.69% test_rate= 98.42% train_err= 0.00895 test_err= 0.07046
 25 train_rate= 99.77% test_rate= 98.51% train_err= 0.00657 test_err= 0.07177
 26 train_rate= 99.80% test_rate= 98.57% train_err= 0.00575 test_err= 0.07639
 27 train_rate= 99.80% test_rate= 98.39% train_err= 0.00568 test_err= 0.08375
 28 train_rate= 99.76% test_rate= 98.70% train_err= 0.00671 test_err= 0.06803
 29 train_rate= 99.81% test_rate= 98.59% train_err= 0.00543 test_err= 0.07394
 30 train_rate= 99.84% test_rate= 98.47% train_err= 0.00455 test_err= 0.07835
 31 train_rate= 99.82% test_rate= 98.53% train_err= 0.00540 test_err= 0.07455
 32 train_rate= 99.85% test_rate= 98.42% train_err= 0.00443 test_err= 0.07507
 33 train_rate= 99.72% test_rate= 98.55% train_err= 0.00794 test_err= 0.07823
 34 train_rate= 99.82% test_rate= 98.25% train_err= 0.00493 test_err= 0.09489
 35 train_rate= 99.77% test_rate= 98.47% train_err= 0.00720 test_err= 0.08478
 36 train_rate= 99.81% test_rate= 98.43% train_err= 0.00564 test_err= 0.09077
 37 train_rate= 99.86% test_rate= 98.56% train_err= 0.00418 test_err= 0.07885
 38 train_rate= 99.90% test_rate= 98.71% train_err= 0.00289 test_err= 0.07677
 39 train_rate= 99.93% test_rate= 98.59% train_err= 0.00222 test_err= 0.08077
 40 train_rate= 99.98% test_rate= 98.71% train_err= 0.00076 test_err= 0.07616
 41 train_rate= 99.99% test_rate= 98.73% train_err= 0.00037 test_err= 0.07609
 42 train_rate=100.00% test_rate= 98.79% train_err= 0.00016 test_err= 0.07562
 43 train_rate=100.00% test_rate= 98.79% train_err= 0.00011 test_err= 0.07662
 44 train_rate=100.00% test_rate= 98.80% train_err= 0.00009 test_err= 0.07654
 45 train_rate=100.00% test_rate= 98.77% train_err= 0.00008 test_err= 0.07676
 46 train_rate=100.00% test_rate= 98.79% train_err= 0.00008 test_err= 0.07738
 47 train_rate=100.00% test_rate= 98.79% train_err= 0.00007 test_err= 0.07778
 48 train_rate=100.00% test_rate= 98.80% train_err= 0.00007 test_err= 0.07768
 49 train_rate=100.00% test_rate= 98.81% train_err= 0.00006 test_err= 0.07820
 50 train_rate=100.00% test_rate= 98.79% train_err= 0.00006 test_err= 0.07786
```

50エポック後のテストデータの正解率は、98.79%となりました。最高では、98.8%を超えました。元は、98.2%程度でしたので、大幅な精度向上です。びっくりです。

今度は、斜め方向に１ピクセル移動します。

```python
# 斜め方向に１ピクセル移動
nx_train_ul1 = shift(nx_train, shape=(28,28), x_shift_range=-1, y_shift_range=-1)
nx_train_dl1 = shift(nx_train, shape=(28,28), x_shift_range=-1, y_shift_range= 1)
nx_train_ur1 = shift(nx_train, shape=(28,28), x_shift_range= 1, y_shift_range=-1)
nx_train_dr1 = shift(nx_train, shape=(28,28), x_shift_range= 1, y_shift_range= 1)
```

左から、元データ、左上、左下、右上、右下に１ピクセル移動したデータです。
![shift_5_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212365/9f2e50a6-372a-488b-ba74-af7fe80fc67a.png)

元データと結合します。

```python
# 斜め方向に１ピクセル移動
nx_train_s = np.concatenate([nx_train,
                             nx_train_ul1, nx_train_dl1, nx_train_ur1, nx_train_dr1])
t_train_s  = np.concatenate([t_train,
                             t_train, t_train, t_train, t_train])
```

学習してみます。学習プログラムは同じです。
結果の一部です。

```
  0 train_rate= 12.96% test_rate= 12.18% train_err= 2.30540 test_err= 2.30601
  1 train_rate= 92.78% test_rate= 97.61% train_err= 0.23281 test_err= 0.07629
  2 train_rate= 97.36% test_rate= 98.22% train_err= 0.08645 test_err= 0.05615
  3 train_rate= 97.95% test_rate= 98.43% train_err= 0.06618 test_err= 0.05400
・・・
 38 train_rate= 99.75% test_rate= 98.68% train_err= 0.00726 test_err= 0.08336
・・・
 50 train_rate= 99.77% test_rate= 98.57% train_err= 0.00695 test_err= 0.09346
```

先ほどより精度が悪いですが、それでも元と比べるとかなり良くなっています。

## 拡大・縮小
次に、拡大縮小を試してみます
$(x,y)$を横方向に$ f_x $倍、縦方向に$ f_y $倍した場合、以下の式で表せます。

```math
x_{scaling} = x \times f_x\\
y_{scaling} = y \times f_y
```

拡大・縮小時ピクセルの中央の座標がどこに移動するかを考えます。
まずは拡大の場合です。横方向、縦方向とも1.5倍($f_x=1.5$,$f_y=1.5$)します。中央の座標は以下のようになります。
(1.5, 1.5) → (1.5*1.5, 1.5*1.5) = (2.25, 2.25)
よって、以下のように移動します。
(1, 1) → (2, 2)
次に縮小の場合です。横方向、縦方向とも0.5倍($f_x=0.5$,$f_y=0.5$)します。中央の座標は以下のようになります。
(1.5, 1.5) → (1.5*0.5, 1.5*0.5) = (0.75, 0.75)
よって、以下のように移動します。
(1, 1) → (0, 0)
![scaling.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212365/a37e9881-4458-e2a7-d49a-1692ebc7ee0d.png)

拡大・縮小するプログラムを考えます。
データを生成する部分を拡大・縮小に変更します。

```python
            x_gen = int(np.floor((x - mw + 0.5) * x_scaling_range + mw))
            y_gen = int(np.floor((y - mh + 0.5) * y_scaling_range + mh))
```

拡大・縮小関数全体です。

```python
def scaling(img, shape=None, x_scaling_range=0, y_scaling_range=0):
    img_shape = img.shape
    img_r = img
    if shape is not None:
        img_r = img.reshape((img.shape[0],) + shape)
    d, h, w = img_r.shape
    mw = int(w/2) # 横方向の中心
    mh = int(h/2) # 縦方向の中心

    # データ生成
    img_gen = np.zeros_like(img_r)
    for y in range(h):
        for x in range(w):
            # 拡大・縮小
            x_gen = int(np.floor((x - mw + 0.5) * x_scaling_range + mw))
            y_gen = int(np.floor((y - mh + 0.5) * y_scaling_range + mh))
            # はみ出した部分は無視
            if y_gen < h and x_gen < w and y_gen >= 0 and x_gen >= 0:
                img_gen[:, y_gen, x_gen] = img_r[:, y, x]

    return img_gen.reshape(img_shape)
```

1.1倍に拡大、0.9倍に縮小、1.2倍に拡大、0.8倍に縮小します。

```python
# 拡大・縮小
nx_train_su1  = scaling(nx_train, shape=(28,28), x_scaling_range=1.1, y_scaling_range=1.1)
nx_train_sd1  = scaling(nx_train, shape=(28,28), x_scaling_range=0.9, y_scaling_range=0.9)
nx_train_su2  = scaling(nx_train, shape=(28,28), x_scaling_range=1.2, y_scaling_range=1.2)
nx_train_sd2  = scaling(nx_train, shape=(28,28), x_scaling_range=0.8, y_scaling_range=0.8)
```

学習データの0番目のデータを表示してみます。

```python
import matplotlib.pyplot as plt

plt.figure(figsize=(20, 6))
plt.subplot(1, 5, 1)
plt.imshow(nx_train[0].reshape(28,28), 'gray')
plt.subplot(1, 5, 2)
plt.imshow(nx_train_su1[0].reshape(28,28), 'gray')
plt.subplot(1, 5, 3)
plt.imshow(nx_train_sd1[0].reshape(28,28), 'gray')
plt.subplot(1, 5, 4)
plt.imshow(nx_train_su2[0].reshape(28,28), 'gray')
plt.subplot(1, 5, 5)
plt.imshow(nx_train_sd2[0].reshape(28,28), 'gray')
plt.show()
```

左から、元データ、1.1倍、0.9倍、1.2倍、0.8倍の画像です。
![scaling_5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212365/8dbd6e87-1703-4f7f-52c0-b6c36f822794.png)

拡大した場合、途中に抜けがあります。正確に拡大・縮小を行う場合はもっと対処が必要ですが、ここではこれでよしとします。

画像を結合し学習を実行します。

```python
# 拡大・縮小
nx_train_s = np.concatenate([nx_train,
                             nx_train_su1, nx_train_sd1, nx_train_su2, nx_train_sd2])
t_train_s  = np.concatenate([t_train,
                             t_train, t_train, t_train, t_train])
```

結果です。


```
  0 train_rate= 11.21% test_rate= 12.18% train_err= 2.30609 test_err= 2.30601
  1 train_rate= 93.06% test_rate= 97.46% train_err= 0.22408 test_err= 0.08599
  2 train_rate= 97.36% test_rate= 97.46% train_err= 0.08397 test_err= 0.08080
  3 train_rate= 98.09% test_rate= 97.92% train_err= 0.06037 test_err= 0.06721
・・・
 45 train_rate=100.00% test_rate= 98.58% train_err= 0.00006 test_err= 0.11247
・・・
 50 train_rate=100.00% test_rate= 98.55% train_err= 0.00005 test_err= 0.11379
```

やはり、精度が向上しました。

## 回転
次に回転を行います。
$(x,y)$を$ \theta $回転した場合、以下の式で表せます。

```math
x_{rotation} = x \cos\theta - y \sin\theta\\
y_{rotation} = x \sin\theta + y \cos\theta
```

回転時ピクセルの中央の座標がどこに移動するかを考えます。
30度左回転します。中央の座標は以下のようになります。
(1.5, 1.5) → ($1.5 \times \cos30$ - $1.5 \times \sin30$, $1.5 \times \sin30$ + $1.5 \times \cos30$) = (2.05, 0.55)
よって、以下のように移動します。
(1, 1) → (2, 0)
![rotation.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212365/45fa3170-7ff6-23ae-0253-c95849c9422a.png)

回転するプログラムを考えます。

パラメータは、度で渡しましたが、sin,cosの計算はラジアンで行うため事前に変換します。

```python
    # 度→ラジアン
    rd_rotation_range = np.radians(rotation_range)
```

回転部分を変更します。

```python
            x_gen = int(np.floor((x - mw + 0.5) * np.cos(rd_rotation_range) - (y - mh + 0.5) * np.sin(rd_rotation_range) + mw))
            y_gen = int(np.floor((x - mh + 0.5) * np.sin(rd_rotation_range) + (y - mh + 0.5) * np.cos(rd_rotation_range) + mh))
```

回転関数全体です。

```python
def rotation(img, shape=None, rotation_range=0):
    img_shape = img.shape
    img_r = img
    if shape is not None:
        img_r = img.reshape((img.shape[0],) + shape)
    d, h, w = img_r.shape
    mw = int(w/2) # 横方向の中心
    mh = int(h/2) # 縦方向の中心
    # 度→ラジアン
    rd_rotation_range = np.radians(rotation_range)

    # データ生成
    img_gen = np.zeros_like(img_r)
    for y in range(h):
        for x in range(w):
            # 回転
            x_gen = int(np.floor((x - mw + 0.5) * np.cos(rd_rotation_range) - (y - mh + 0.5) * np.sin(rd_rotation_range) + mw))
            y_gen = int(np.floor((x - mh + 0.5) * np.sin(rd_rotation_range) + (y - mh + 0.5) * np.cos(rd_rotation_range) + mh))
            # はみ出した部分は無視
            if y_gen < h and x_gen < w and y_gen >= 0 and x_gen >= 0:
                img_gen[:, y_gen, x_gen] = img_r[:, y, x]

    return img_gen.reshape(img_shape)
```

右に10度、左に10度、右に20度、左に20度回転します。

```python
# 回転
nx_train_rr10 = rotation(nx_train, shape=(28,28), rotation_range= 10)
nx_train_rl10 = rotation(nx_train, shape=(28,28), rotation_range=-10)
nx_train_rr20 = rotation(nx_train, shape=(28,28), rotation_range= 20)
nx_train_rl20 = rotation(nx_train, shape=(28,28), rotation_range=-20)
```

学習データの0番目のデータを表示してみます。

```python
import matplotlib.pyplot as plt

plt.figure(figsize=(20, 6))
plt.subplot(1, 5, 1)
plt.imshow(nx_train[0].reshape(28,28), 'gray')
plt.subplot(1, 5, 2)
plt.imshow(nx_train_rr10[0].reshape(28,28), 'gray')
plt.subplot(1, 5, 3)
plt.imshow(nx_train_rl10[0].reshape(28,28), 'gray')
plt.subplot(1, 5, 4)
plt.imshow(nx_train_rr20[0].reshape(28,28), 'gray')
plt.subplot(1, 5, 5)
plt.imshow(nx_train_rl20[0].reshape(28,28), 'gray')
plt.show()
```

左から、元データ、右10度、左10度、右20度、左20度回転の画像です。
![rotation_5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212365/2e6633ae-32f9-adbd-6776-7b3d9a3cffe8.png)

画像を結合し学習を実行します。

```python
# 拡大・縮小
nx_train_s = np.concatenate([nx_train,
                             nx_train_rr10, nx_train_rl10, nx_train_rr20, nx_train_rl20])
t_train_s  = np.concatenate([t_train,
                             t_train, t_train, t_train, t_train])
```

結果です。

```
  0 train_rate= 12.57% test_rate= 12.18% train_err= 2.30448 test_err= 2.30601
  1 train_rate= 92.76% test_rate= 97.42% train_err= 0.23071 test_err= 0.08477
  2 train_rate= 97.20% test_rate= 97.55% train_err= 0.08909 test_err= 0.08142
  3 train_rate= 97.91% test_rate= 97.98% train_err= 0.06625 test_err= 0.06674
・・・
 47 train_rate=100.00% test_rate= 98.58% train_err= 0.00007 test_err= 0.09674
・・・
 50 train_rate=100.00% test_rate= 98.57% train_err= 0.00006 test_err= 0.09705
```

回転でも精度が向上しました。

## 参考
他にも反転が考えられますが、数字のため反転には対応できません。
元データを５つ分結合した場合の結果も確認してみます。

```python
nx_train_s = np.concatenate([nx_train,
                             nx_train, nx_train, nx_train, nx_train])
t_train_s  = np.concatenate([t_train,
                             t_train, t_train, t_train, t_train])
```

学習結果です。

```
  0 train_rate= 11.67% test_rate= 12.18% train_err= 2.30623 test_err= 2.30601
  1 train_rate= 94.99% test_rate= 97.33% train_err= 0.16258 test_err= 0.09243
  2 train_rate= 98.79% test_rate= 97.78% train_err= 0.03947 test_err= 0.08247
  3 train_rate= 99.49% test_rate= 97.95% train_err= 0.01714 test_err= 0.07971
・・・
 32 train_rate=100.00% test_rate= 98.04% train_err= 0.00005 test_err= 0.11965
・・・
 50 train_rate=100.00% test_rate= 98.02% train_err= 0.00003 test_err= 0.12452
```

単にデータを結合するだけでは、精度は改善しませんでした。

これまでの結果を表にします。50エポック実行後のテストデータの正解率と最大の正解率です。

|データ拡張方法|テスト正解率|テスト最大|
|:---------------------------|------:|------:|
|上下左右に１ピクセル移動      | 98.79 | 98.81 |
|斜め方向に１ピクセル移動      | 98.57 | 98.68 |
|1.1,0.9,1.2,0.8倍に拡大・縮小| 98.55 | 98.58 |
|10度、20度、右左回転         | 98.57 | 98.58 |
|参考(データ5倍)              | 98.02 |98.04 |

# データ生成
## 乱数によるデータ生成
今までのようにデータを増やしもよいのですが、学習に必要なリソースが増えること、時間がかかるため別の方法を考えます。
乱数によりデータを変換することを考えます。
今まで、平行移動、拡大・縮小、回転を別々に行いました。合わせて行うことを考えます。
データの変換は、以下の式で表せます。

```math
x_{gen} = (x\cos\theta - y\sin\theta) * f_x + d_x\\
y_{gen} = (x\sin\theta + y\cos\theta) * f_y + d_y
```

$d_x$ : 横方向の移動
$d_y$ : 縦方向の移動
$f_x$ : 横方向の拡大・縮小
$f_y$ : 縦方向の拡大・縮小
$\theta$ : 回転角度

乱数で指定した範囲内で移動、拡大・縮小、回転を決めます。
例えば、移動距離に1を指定した場合、-1～1の範囲で乱数で決定します。同様に、拡大・縮小率を0.1とした場合、0.9～1.1倍の範囲、回転角度を10度とした場合、-10～10度の間で決めます。

```python
    # 乱数
    x_shift = np.random.rand() * x_shift_range*2 - x_shift_range           # 左右の移動距離
    y_shift = np.random.rand() * y_shift_range*2 - y_shift_range           # 上下の移動距離
    x_scaling = 1 + np.random.rand() * x_scaling_range*2 - x_scaling_range # 左右の拡大縮小率
    y_scaling = 1 + np.random.rand() * y_scaling_range*2 - y_scaling_range # 上下の拡大縮小率
    rotation = np.random.rand() * rotation_range*2 - rotation_range        # 回転角度
```

データの変換部分です。

```python
            x_gen = int(np.floor(((x - mw + 0.5) * np.cos(rd_rotation) - (y - mh + 0.5) * np.sin(rd_rotation)) * x_scaling + x_shift + mw))
            y_gen = int(np.floor(((x - mh + 0.5) * np.sin(rd_rotation) + (y - mh + 0.5) * np.cos(rd_rotation)) * y_scaling + y_shift + mh))
```

関数全体です。

```python
def generator(img, shape=None, x_shift_range=0, y_shift_range=0, x_scaling_range=0, y_scaling_range=0, rotation_range=0):
    img_shape = img.shape
    img_r = img
    if shape is not None:
        img_r = img.reshape((img.shape[0],) + shape)
    d, h, w = img_r.shape
    mw = int(w/2) # 横方向の中心
    mh = int(h/2) # 縦方向の中心

    # 乱数
    x_shift = np.random.rand() * x_shift_range*2 - x_shift_range           # 左右の移動距離
    y_shift = np.random.rand() * y_shift_range*2 - y_shift_range           # 上下の移動距離
    x_scaling = 1 + np.random.rand() * x_scaling_range*2 - x_scaling_range # 左右の拡大縮小率
    y_scaling = 1 + np.random.rand() * y_scaling_range*2 - y_scaling_range # 上下の拡大縮小率
    rotation = np.random.rand() * rotation_range*2 - rotation_range        # 回転角度
    # 度→ラジアン
    rd_rotation = np.radians(rotation)
   
    # データ生成
    img_gen = np.zeros_like(img_r)
    for y in range(h):
        for x in range(w):
            # 位置計算
            x_gen = int(np.floor(((x - mw + 0.5) * np.cos(rd_rotation) - (y - mh + 0.5) * np.sin(rd_rotation)) * x_scaling + x_shift + mw))
            y_gen = int(np.floor(((x - mh + 0.5) * np.sin(rd_rotation) + (y - mh + 0.5) * np.cos(rd_rotation)) * y_scaling + y_shift + mh))
            # はみ出した部分は無視
            if y_gen < h and x_gen < w and y_gen >= 0 and x_gen >= 0:
                img_gen[:, y_gen, x_gen] = img_r[:, y, x]

    return img_gen.reshape(img_shape)
```

先頭のデータを10個生成し表示してみます。
移動距離を1、倍率を0.1、回転を10度とします。

```python
import matplotlib.pyplot as plt

plt.figure(figsize=(20,6))
np.random.seed(8)

for i in range(10):
    plt.subplot(1, 10, i+1)
    plt.imshow(generator(nx_train[0:1], shape=(28,28), 
                         x_shift_range=1, y_shift_range=1, x_scaling_range=0.1, y_scaling_range=0.1, rotation_range=10
                        )[0].reshape(28,28), 'gray')
plt.show()
```

![generator_5_11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212365/97e87dbf-9ec1-9e13-4de6-ac99b20182ab.png)
どうですか？なかなかバリエーションのあるデータが生成できました。

次に、移動距離を2、倍率を0.2、回転を20度としてみます。

```python
import matplotlib.pyplot as plt

plt.figure(figsize=(20,6))
np.random.seed(8)

for i in range(10):
    plt.subplot(1, 10, i+1)
    plt.imshow(generator(nx_train[0:1], shape=(28,28), 
                         x_shift_range=2, y_shift_range=2, x_scaling_range=0.2, y_scaling_range=0.2, rotation_range=20
                        )[0].reshape(28,28), 'gray')
plt.show()
```

![generator_5_12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212365/34880825-c6b2-d7c5-844a-00378c241b00.png)

これは、これでよいのですが、もう少し工夫してみます。
回転の部分を別々に乱数で生成します。

```math
x_{gen} = (x\cos\theta_{xx} - y\sin\theta_{yx}) * f_x + d_x\\
y_{gen} = (x\sin\theta_{xy} + y\cos\theta_{yy}) * f_y + d_y
```

注意点があります。回転角度を大きくするとまともな画像となりません。MINISTは数字のため大きく回転しないことを前提としています。

変更した関数です。

```python
def generator(img, shape=None, x_shift_range=0, y_shift_range=0, x_scaling_range=0, y_scaling_range=0, rotation_range=0):
    img_shape = img.shape
    img_r = img
    if shape is not None:
        img_r = img.reshape((img.shape[0],) + shape)
    d, h, w = img_r.shape
    mw = int(w/2) # 横方向の中心
    mh = int(h/2) # 縦方向の中心

    # 乱数
    x_shift = np.random.rand() * x_shift_range*2 - x_shift_range           # 左右の移動距離
    y_shift = np.random.rand() * y_shift_range*2 - y_shift_range           # 上下の移動距離
    x_scaling = 1 + np.random.rand() * x_scaling_range*2 - x_scaling_range # 左右の拡大縮小率
    y_scaling = 1 + np.random.rand() * y_scaling_range*2 - y_scaling_range # 上下の拡大縮小率
    xx_rotation = np.random.rand() * rotation_range*2 - rotation_range        # 回転角度
    yx_rotation = np.random.rand() * rotation_range*2 - rotation_range        # 回転角度
    xy_rotation = np.random.rand() * rotation_range*2 - rotation_range        # 回転角度
    yy_rotation = np.random.rand() * rotation_range*2 - rotation_range        # 回転角度
    # 度→ラジアン
    rd_xx_rotation = np.radians(xx_rotation)
    rd_yx_rotation = np.radians(yx_rotation)
    rd_xy_rotation = np.radians(xy_rotation)
    rd_yy_rotation = np.radians(yy_rotation)
   
    # データ生成
    img_gen = np.zeros_like(img_r)
    for y in range(h):
        for x in range(w):
            # 位置計算
            x_gen = int(np.floor(((x - mw + 0.5) * np.cos(rd_xx_rotation) - (y - mh + 0.5) * np.sin(rd_yx_rotation)) * x_scaling + x_shift + mw))
            y_gen = int(np.floor(((x - mh + 0.5) * np.sin(rd_xy_rotation) + (y - mh + 0.5) * np.cos(rd_yy_rotation)) * y_scaling + y_shift + mh))
            # はみ出した部分は無視
            if y_gen < h and x_gen < w and y_gen >= 0 and x_gen >= 0:
                img_gen[:, y_gen, x_gen] = img_r[:, y, x]

    return img_gen.reshape(img_shape)
```

先頭のデータを10個生成し表示してみます。
移動距離を1、倍率を0.1、回転を10度とします。

```python
import matplotlib.pyplot as plt

plt.figure(figsize=(20,6))
np.random.seed(8)

for i in range(10):
    plt.subplot(1, 10, i+1)
    plt.imshow(generator(nx_train[0:1], shape=(28,28), 
                         x_shift_range=1, y_shift_range=1, x_scaling_range=0.1, y_scaling_range=0.1, rotation_range=10
                        )[0].reshape(28,28), 'gray')
plt.show()
```

![generator_5_21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212365/46a6df06-40ca-ffca-f70a-ca3089a9e89e.png)

次に、移動距離を2、倍率を0.2、回転を20度としてみます。

```python
import matplotlib.pyplot as plt

plt.figure(figsize=(20,6))
np.random.seed(8)

for i in range(10):
    plt.subplot(1, 10, i+1)
    plt.imshow(generator(nx_train[0:1], shape=(28,28), 
                         x_shift_range=2, y_shift_range=2, x_scaling_range=0.2, y_scaling_range=0.2, rotation_range=20
                        )[0].reshape(28,28), 'gray')
plt.show()
```

![generator_5_22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212365/212b0052-8691-4d4f-d878-50e6271a67d4.png)
先ほどの画像より、バリエーション豊かになりました。

## 学習の実行
乱数により生成した画像を利用し学習します。
生成は、ミニバッチごとに行います。
プログラムの変更部分です。generatorでデータを生成し、learnに渡し学習します。

```python
        # データ生成
        nx_train_g = generator(nx_train[idx[j:j+batch_size]], shape=(28,28), 
                               x_shift_range=1, y_shift_range=1, x_scaling_range=0.1, y_scaling_range=0.1, rotation_range=10)
        y_train[idx[j:j+batch_size]], W1, b1, W2, b2, W3, b3 = learn(nx_train_g, t_train[idx[j:j+batch_size]], W1, b1, W2, b2, W3, b3, lr)
```

移動距離を1、倍率を0.1、回転を10度とし、学習します。学習がゆっくり進むため100エポック実行しました。また、あとで、学習状況をグラフ化するため、エポックごとの正解率、誤差を保存しておくように変更しました。

```python
# ノード数設定
d0 = nx_train.shape[1]
d1 = 100 # 1層目のノード数
d2 = 50  # 2層目のノード数
d3 = 10
# 重みの初期化(-0.1～0.1の乱数)
np.random.seed(8)
W1 = np.random.rand(d0, d1) * 0.2 - 0.1
W2 = np.random.rand(d1, d2) * 0.2 - 0.1
W3 = np.random.rand(d2, d3) * 0.2 - 0.1
# バイアスの初期化(0)
b1 = np.zeros(d1)
b2 = np.zeros(d2)
b3 = np.zeros(d3)

# 学習率
lr = 0.25
# バッチサイズ
batch_size = 100
# 学習回数
epoch = 100
# シャッフルフラグ
shuffle = True

# エポックごとの誤差、正解率格納エリア
train_rate, test_rate, train_err, test_err = np.zeros(epoch+1), np.zeros(epoch+1), np.zeros(epoch+1), np.zeros(epoch+1)

# 誤差、正解率表示
train_rate[0], test_rate[0], train_err[0], test_err[0] = print_metrics(0, nx_train, t_train, None, nx_test, t_test, None, W1, b1, W2, b2, W3, b3)

for i in range(epoch):
    # データシャッフル
    idx = np.arange(nx_train.shape[0])
    if shuffle:
        np.random.shuffle(idx)
    
    # 学習
    y_train = np.zeros_like(t_train)
    for j in range(0, nx_train.shape[0], batch_size):
        # データ生成
        nx_train_g = generator(nx_train[idx[j:j+batch_size]], shape=(28,28), 
                               x_shift_range=1, y_shift_range=1, x_scaling_range=0.1, y_scaling_range=0.1, rotation_range=10)
        y_train[idx[j:j+batch_size]], W1, b1, W2, b2, W3, b3 = learn(nx_train_g, t_train[idx[j:j+batch_size]], W1, b1, W2, b2, W3, b3, lr)

    # 誤差、正解率表示
    train_rate[i+1], test_rate[i+1], train_err[i+1], test_err[i+1] = print_metrics(i+1, nx_train, t_train, y_train, nx_test, t_test, None, W1, b1, W2, b2, W3, b3)
```

```
  0 train_rate= 11.67% test_rate= 12.18% train_err= 2.30623 test_err= 2.30601
  1 train_rate= 79.13% test_rate= 94.99% train_err= 0.65019 test_err= 0.15849
  2 train_rate= 92.45% test_rate= 96.97% train_err= 0.24373 test_err= 0.09587
  3 train_rate= 94.31% test_rate= 97.21% train_err= 0.18487 test_err= 0.08591
  4 train_rate= 95.09% test_rate= 97.08% train_err= 0.15908 test_err= 0.09091
  5 train_rate= 95.61% test_rate= 97.73% train_err= 0.14125 test_err= 0.06821
  6 train_rate= 95.86% test_rate= 98.07% train_err= 0.13036 test_err= 0.05904
  7 train_rate= 96.20% test_rate= 97.93% train_err= 0.11967 test_err= 0.06332
  8 train_rate= 96.35% test_rate= 98.32% train_err= 0.11669 test_err= 0.05715
  9 train_rate= 96.52% test_rate= 98.21% train_err= 0.11030 test_err= 0.05385
 10 train_rate= 96.73% test_rate= 98.32% train_err= 0.10482 test_err= 0.04903
 11 train_rate= 96.86% test_rate= 98.40% train_err= 0.10024 test_err= 0.04888
 12 train_rate= 97.03% test_rate= 98.32% train_err= 0.09619 test_err= 0.05135
 13 train_rate= 97.04% test_rate= 98.55% train_err= 0.09392 test_err= 0.04338
 14 train_rate= 97.15% test_rate= 98.63% train_err= 0.08904 test_err= 0.04456
 15 train_rate= 97.23% test_rate= 98.62% train_err= 0.09057 test_err= 0.04170
 16 train_rate= 97.23% test_rate= 98.27% train_err= 0.08701 test_err= 0.04814
 17 train_rate= 97.27% test_rate= 98.50% train_err= 0.08554 test_err= 0.04303
 18 train_rate= 97.42% test_rate= 98.65% train_err= 0.08260 test_err= 0.04230
 19 train_rate= 97.42% test_rate= 98.64% train_err= 0.08210 test_err= 0.04139
 20 train_rate= 97.46% test_rate= 98.67% train_err= 0.07877 test_err= 0.04056
 21 train_rate= 97.55% test_rate= 98.61% train_err= 0.07706 test_err= 0.03883
 22 train_rate= 97.62% test_rate= 98.54% train_err= 0.07435 test_err= 0.04402
 23 train_rate= 97.62% test_rate= 98.68% train_err= 0.07470 test_err= 0.03836
 24 train_rate= 97.62% test_rate= 98.64% train_err= 0.07463 test_err= 0.03885
 25 train_rate= 97.61% test_rate= 98.66% train_err= 0.07349 test_err= 0.03983
 26 train_rate= 97.62% test_rate= 98.88% train_err= 0.07332 test_err= 0.03545
 27 train_rate= 97.74% test_rate= 98.63% train_err= 0.07052 test_err= 0.03997
 28 train_rate= 97.89% test_rate= 98.83% train_err= 0.06760 test_err= 0.03548
 29 train_rate= 97.84% test_rate= 98.69% train_err= 0.06700 test_err= 0.04063
 30 train_rate= 97.80% test_rate= 98.80% train_err= 0.06816 test_err= 0.03841
 31 train_rate= 97.81% test_rate= 98.82% train_err= 0.06609 test_err= 0.03569
 32 train_rate= 97.88% test_rate= 98.82% train_err= 0.06584 test_err= 0.03456
 33 train_rate= 97.91% test_rate= 98.74% train_err= 0.06455 test_err= 0.03728
 34 train_rate= 97.97% test_rate= 98.69% train_err= 0.06152 test_err= 0.03758
 35 train_rate= 97.92% test_rate= 98.74% train_err= 0.06313 test_err= 0.04017
 36 train_rate= 97.95% test_rate= 98.82% train_err= 0.06388 test_err= 0.03284
 37 train_rate= 98.09% test_rate= 98.85% train_err= 0.06058 test_err= 0.03359
 38 train_rate= 97.97% test_rate= 98.84% train_err= 0.06248 test_err= 0.03492
 39 train_rate= 98.05% test_rate= 98.78% train_err= 0.06179 test_err= 0.03609
 40 train_rate= 98.22% test_rate= 98.78% train_err= 0.05814 test_err= 0.03644
 41 train_rate= 98.12% test_rate= 98.75% train_err= 0.05877 test_err= 0.03562
 42 train_rate= 98.02% test_rate= 98.72% train_err= 0.06121 test_err= 0.03767
 43 train_rate= 98.19% test_rate= 98.93% train_err= 0.05625 test_err= 0.03221
 44 train_rate= 98.05% test_rate= 98.87% train_err= 0.06061 test_err= 0.03491
 45 train_rate= 98.07% test_rate= 98.97% train_err= 0.05905 test_err= 0.03221
 46 train_rate= 98.23% test_rate= 98.76% train_err= 0.05659 test_err= 0.03652
 47 train_rate= 98.04% test_rate= 98.81% train_err= 0.06010 test_err= 0.03354
 48 train_rate= 98.16% test_rate= 98.97% train_err= 0.05656 test_err= 0.03342
 49 train_rate= 98.18% test_rate= 99.02% train_err= 0.05648 test_err= 0.03204
 50 train_rate= 98.25% test_rate= 98.88% train_err= 0.05377 test_err= 0.03622
 51 train_rate= 98.21% test_rate= 98.91% train_err= 0.05343 test_err= 0.03233
 52 train_rate= 98.28% test_rate= 99.00% train_err= 0.05306 test_err= 0.03106
 53 train_rate= 98.18% test_rate= 98.93% train_err= 0.05518 test_err= 0.03298
 54 train_rate= 98.23% test_rate= 98.97% train_err= 0.05546 test_err= 0.03106
 55 train_rate= 98.33% test_rate= 98.94% train_err= 0.05412 test_err= 0.03291
 56 train_rate= 98.25% test_rate= 98.88% train_err= 0.05351 test_err= 0.03427
 57 train_rate= 98.29% test_rate= 99.01% train_err= 0.05356 test_err= 0.03286
 58 train_rate= 98.21% test_rate= 98.90% train_err= 0.05334 test_err= 0.03514
 59 train_rate= 98.28% test_rate= 99.04% train_err= 0.05227 test_err= 0.03087
 60 train_rate= 98.39% test_rate= 98.93% train_err= 0.04971 test_err= 0.03371
 61 train_rate= 98.30% test_rate= 99.03% train_err= 0.05447 test_err= 0.02983
 62 train_rate= 98.38% test_rate= 98.95% train_err= 0.05134 test_err= 0.03340
 63 train_rate= 98.38% test_rate= 99.01% train_err= 0.04958 test_err= 0.03134
 64 train_rate= 98.40% test_rate= 98.88% train_err= 0.04865 test_err= 0.03243
 65 train_rate= 98.29% test_rate= 98.94% train_err= 0.05254 test_err= 0.03251
 66 train_rate= 98.34% test_rate= 99.03% train_err= 0.05115 test_err= 0.03043
 67 train_rate= 98.28% test_rate= 98.94% train_err= 0.05372 test_err= 0.03187
 68 train_rate= 98.37% test_rate= 99.05% train_err= 0.05070 test_err= 0.02888
 69 train_rate= 98.33% test_rate= 98.97% train_err= 0.04955 test_err= 0.03076
 70 train_rate= 98.37% test_rate= 98.96% train_err= 0.04920 test_err= 0.03240
 71 train_rate= 98.45% test_rate= 98.99% train_err= 0.04855 test_err= 0.03185
 72 train_rate= 98.46% test_rate= 99.01% train_err= 0.04952 test_err= 0.03194
 73 train_rate= 98.41% test_rate= 99.06% train_err= 0.04954 test_err= 0.03022
 74 train_rate= 98.43% test_rate= 98.99% train_err= 0.04806 test_err= 0.03400
 75 train_rate= 98.38% test_rate= 98.98% train_err= 0.05002 test_err= 0.03233
 76 train_rate= 98.36% test_rate= 98.96% train_err= 0.04998 test_err= 0.03140
 77 train_rate= 98.42% test_rate= 99.00% train_err= 0.04942 test_err= 0.02916
 78 train_rate= 98.37% test_rate= 98.88% train_err= 0.05003 test_err= 0.03231
 79 train_rate= 98.49% test_rate= 98.99% train_err= 0.04612 test_err= 0.02966
 80 train_rate= 98.39% test_rate= 99.06% train_err= 0.04789 test_err= 0.02866
 81 train_rate= 98.48% test_rate= 98.96% train_err= 0.04811 test_err= 0.03142
 82 train_rate= 98.52% test_rate= 98.99% train_err= 0.04682 test_err= 0.03299
 83 train_rate= 98.51% test_rate= 99.03% train_err= 0.04593 test_err= 0.02829
 84 train_rate= 98.43% test_rate= 98.96% train_err= 0.04755 test_err= 0.03072
 85 train_rate= 98.57% test_rate= 99.04% train_err= 0.04492 test_err= 0.03182
 86 train_rate= 98.66% test_rate= 98.95% train_err= 0.04286 test_err= 0.03492
 87 train_rate= 98.47% test_rate= 98.98% train_err= 0.04568 test_err= 0.03389
 88 train_rate= 98.45% test_rate= 98.95% train_err= 0.04651 test_err= 0.03171
 89 train_rate= 98.56% test_rate= 99.17% train_err= 0.04383 test_err= 0.02712
 90 train_rate= 98.46% test_rate= 98.72% train_err= 0.04630 test_err= 0.03920
 91 train_rate= 98.57% test_rate= 98.90% train_err= 0.04532 test_err= 0.03080
 92 train_rate= 98.50% test_rate= 98.92% train_err= 0.04543 test_err= 0.03459
 93 train_rate= 98.54% test_rate= 98.93% train_err= 0.04574 test_err= 0.03267
 94 train_rate= 98.55% test_rate= 99.13% train_err= 0.04535 test_err= 0.03075
 95 train_rate= 98.53% test_rate= 99.08% train_err= 0.04549 test_err= 0.02960
 96 train_rate= 98.53% test_rate= 98.87% train_err= 0.04578 test_err= 0.03491
 97 train_rate= 98.59% test_rate= 99.03% train_err= 0.04344 test_err= 0.02963
 98 train_rate= 98.61% test_rate= 98.85% train_err= 0.04338 test_err= 0.03283
 99 train_rate= 98.60% test_rate= 99.01% train_err= 0.04426 test_err= 0.02978
100 train_rate= 98.51% test_rate= 99.02% train_err= 0.04425 test_err= 0.03243
```

なんと、テストデータの正解率が、99%を超えました。49エポック目で99.02%、最大は、99.17%です。すごいですね。

正解率の変化をグラフ化します。破線が学習データに対する正解率、実線がテストデータです。

```python
import matplotlib.pyplot as plt

times = np.arange(0, epoch+1)
plt.figure(figsize=(10,5))
plt.plot(times, test_rate, label="Test Data", color="blue")
plt.plot(times, train_rate, label="Train Data", color="blue", linestyle="dashed")
plt.title("Accuracy rate")
plt.xlabel("epoch")
plt.ylabel("rate")
plt.ylim(0.942,0.998)
plt.legend()
plt.grid()
plt.show()
```

![generator_graph1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212365/41bf03fd-58df-5b31-6a5c-ee99c5779816.png)


次に、移動距離を2、倍率を0.2、回転を20度として学習してみます。
データの生成部分です。

```python
        # データ生成
        nx_train_g = generator(nx_train[idx[j:j+batch_size]], shape=(28,28), 
                               x_shift_range=2, y_shift_range=2, x_scaling_range=0.2, y_scaling_range=0.2, rotation_range=20)
```

学習結果です。

```
  0 train_rate= 11.67% test_rate= 12.18% train_err= 2.30623 test_err= 2.30601
  1 train_rate= 63.55% test_rate= 93.91% train_err= 1.08837 test_err= 0.19900
  2 train_rate= 85.21% test_rate= 95.31% train_err= 0.46272 test_err= 0.14554
  3 train_rate= 89.46% test_rate= 96.62% train_err= 0.33836 test_err= 0.11102
・・・
 93 train_rate= 96.89% test_rate= 98.90% train_err= 0.09795 test_err= 0.03482
・・・
100 train_rate= 96.94% test_rate= 98.82% train_err= 0.09778 test_err= 0.03610
```

## ノード数の変更
テストデータの正解率が99%を超えました。
せっかくなので、中間層のノード数を増やして試してみます。
100エポック後の結果です。

* 移動距離-1、倍率-0.1、回転-10度

```python
x_shift_range=1, y_shift_range=1, x_scaling_range=0.1, y_scaling_range=0.1, rotation_range=10
```

|１層ノード数|２層ノード数|学習正解率|テスト正解率|テスト最高|
|----------:|----------:|--------:|----------:|--------:|
|  100      |   50      |  98.51  |  99.02    |  99.17  |
|  200      |  100      |  99.08  |  99.12    |  99.25  |
|  500      |  250      |  99.38  |  99.19    |  99.31  |
| 1000      |  500      |  99.47  |  99.21    |  99.28  |

* 移動距離-2、倍率-0.2、回転-20度

```python
x_shift_range=2, y_shift_range=2, x_scaling_range=0.2, y_scaling_range=0.2, rotation_range=20
```

|１層ノード数|２層ノード数|学習正解率|テスト正解率|テスト最高|
|----------:|----------:|--------:|----------:|--------:|
|  100      |   50      |  96.94  |  98.82    |  98.90  |
|  200      |  100      |  97.86  |  99.15    |  99.29  |
|  500      |  250      |  98.36  |  99.35    |  99.36  |
| 1000      |  500      |  98.56  |  99.34    |  99.47  |

ノード数を1000-500まで増やすと最高99.47%になりました。
折角なので200エポックまで実行してみます。

```
  0 train_rate=  9.17% test_rate=  9.27% train_err= 2.57723 test_err= 2.57840
  1 train_rate= 76.72% test_rate= 96.07% train_err= 0.72965 test_err= 0.13537
  2 train_rate= 89.81% test_rate= 97.06% train_err= 0.32831 test_err= 0.08837
  3 train_rate= 92.27% test_rate= 97.66% train_err= 0.24845 test_err= 0.07026
・・・
 98 train_rate= 98.43% test_rate= 99.47% train_err= 0.04820 test_err= 0.01825
 99 train_rate= 98.52% test_rate= 99.38% train_err= 0.04612 test_err= 0.02042
100 train_rate= 98.56% test_rate= 99.34% train_err= 0.04318 test_err= 0.02212
101 train_rate= 98.58% test_rate= 99.46% train_err= 0.04478 test_err= 0.01981
・・・
111 train_rate= 98.58% test_rate= 99.52% train_err= 0.04438 test_err= 0.01741
・・・
198 train_rate= 98.93% test_rate= 99.47% train_err= 0.03181 test_err= 0.01622
199 train_rate= 98.88% test_rate= 99.44% train_err= 0.03284 test_err= 0.01885
200 train_rate= 98.88% test_rate= 99.45% train_err= 0.03405 test_err= 0.01855
```

なんと、111エポック目で99.5%を超えました。

MNISTでCNNを使わず、ニューラルネットワークのみでテストデータの正解率が99.5%を超えました。データ拡張は、強力ですね。

# プログラム全体

```python
import numpy as np
# affine変換
def affine(z, W, b):
    return np.dot(z, W) + b
# affine変換勾配
def affine_back(du, z, W, b):
    dz = np.dot(du, W.T)
    dW = np.dot(z.T, du)
    db = np.dot(np.ones(z.shape[0]).T, du)
    return dz, dW, db
# 活性化関数(ReLU)
def relu(u):
    return np.maximum(0, u)
# 活性化関数(ReLU)勾配
def relu_back(dz, u):
    return dz * np.where(u > 0, 1, 0)
# 活性化関数(softmax)
def softmax(u):
    max_u = np.max(u, axis=1, keepdims=True)
    exp_u = np.exp(u-max_u)
    return exp_u/np.sum(exp_u, axis=1, keepdims=True)
# 誤差(交差エントロピー）
def cross_entropy_error(y, t):
    return -np.sum(t * np.log(np.maximum(y,1e-7)))/y.shape[0]
# 誤差(交差エントロピー）＋活性化関数(softmax)勾配
def softmax_cross_entropy_error_back(y, t):
    return (y - t)/y.shape[0]
```

```python
def learn(x, t, W1, b1, W2, b2, W3, b3, lr):
    # 順伝播
    u1 = affine(x, W1, b1)
    z1 = relu(u1)
    u2 = affine(z1, W2, b2)
    z2 = relu(u2)
    u3 = affine(z2, W3, b3)
    y  = softmax(u3)
    # 逆伝播
    dy = softmax_cross_entropy_error_back(y, t)
    dz2, dW3, db3 = affine_back(dy, z2, W3, b3)
    du2 = relu_back(dz2, u2)
    dz1, dW2, db2 = affine_back(du2, z1, W2, b2)
    du1 = relu_back(dz1, u1)
    dx, dW1, db1 = affine_back(du1, x, W1, b1)
    # 重み、バイアスの更新
    W1 = W1 - lr * dW1
    b1 = b1 - lr * db1
    W2 = W2 - lr * dW2
    b2 = b2 - lr * db2
    W3 = W3 - lr * dW3
    b3 = b3 - lr * db3

    return y, W1, b1, W2, b2, W3, b3
```

```python
def predict(x, W1, b1, W2, b2, W3, b3):
    # 順伝播
    u1 = affine(x, W1, b1)
    z1 = relu(u1)
    u2 = affine(z1, W2, b2)
    z2 = relu(u2)
    u3 = affine(z2, W3, b3)
    y  = softmax(u3)
    return y
```

```python
import gzip
import numpy as np
# MNIST読み込み
def load_mnist( mnist_path ) :
    return _load_image(mnist_path + 'train-images-idx3-ubyte.gz'), \
           _load_label(mnist_path + 'train-labels-idx1-ubyte.gz'), \
           _load_image(mnist_path + 't10k-images-idx3-ubyte.gz'), \
           _load_label(mnist_path + 't10k-labels-idx1-ubyte.gz')
def _load_image( image_path ) :
    # 画像データの読み込み
    with gzip.open(image_path, 'rb') as f:
        buffer = f.read()
    size = np.frombuffer(buffer, np.dtype('>i4'), 1, offset=4)
    rows = np.frombuffer(buffer, np.dtype('>i4'), 1, offset=8)
    columns = np.frombuffer(buffer, np.dtype('>i4'), 1, offset=12)
    data = np.frombuffer(buffer, np.uint8, offset=16)
    image = np.reshape(data, (size[0], rows[0]*columns[0]))
    image = image.astype(np.float32)
    return image
def _load_label( label_path ) :
    # 正解データ読み込み
    with gzip.open(label_path, 'rb') as f:
        buffer = f.read()
    size = np.frombuffer(buffer, np.dtype('>i4'), 1, offset=4)
    data = np.frombuffer(buffer, np.uint8, offset=8)
    label = np.zeros((size[0], 10))
    for i in range(size[0]):
        label[i, data[i]] = 1
    return label

# 正解率
def accuracy_rate(y, t):
    max_y = np.argmax(y, axis=1)
    max_t = np.argmax(t, axis=1)
    return np.sum(max_y == max_t)/y.shape[0]
```

```python
def print_metrics(epoche, x_train, t_train, y_train, x_test, t_test, y_test, W1, b1, W2, b2, W3, b3):
    # 予測（学習データ）
    if y_train is None:
        y_train = np.zeros_like(t_train)
        for j in range(0, x_train.shape[0], batch_size):
            y_train[j:j+batch_size] = predict(x_train[j:j+batch_size], W1, b1, W2, b2, W3, b3)
    # 予測（テストデータ）
    if y_test is None:
        y_test = np.zeros_like(t_test)
        for j in range(0, x_test.shape[0], batch_size):
            y_test[j:j+batch_size] = predict(x_test[j:j+batch_size], W1, b1, W2, b2, W3, b3)
    # 正解率、誤差表示
    train_rate, train_err = accuracy_rate(y_train, t_train), cross_entropy_error(y_train, t_train)
    test_rate, test_err = accuracy_rate(y_test, t_test), cross_entropy_error(y_test, t_test)
    print("{0:3d} train_rate={1:6.2f}% test_rate={2:6.2f}% train_err={3:8.5f} test_err={4:8.5f}".format(epoche, train_rate*100, test_rate*100, train_err, test_err))
    
    return train_rate, test_rate, train_err, test_err
```

```python
def generator(img, shape=None, x_shift_range=0, y_shift_range=0, x_scaling_range=0, y_scaling_range=0, rotation_range=0):
    img_shape = img.shape
    img_r = img
    if shape is not None:
        img_r = img.reshape((img.shape[0],) + shape)
    d, h, w = img_r.shape
    mw = int(w/2) # 横方向の中心
    mh = int(h/2) # 縦方向の中心

    # 乱数
    x_shift = np.random.rand() * x_shift_range*2 - x_shift_range           # 左右の移動距離
    y_shift = np.random.rand() * y_shift_range*2 - y_shift_range           # 上下の移動距離
    x_scaling = 1 + np.random.rand() * x_scaling_range*2 - x_scaling_range # 左右の拡大縮小率
    y_scaling = 1 + np.random.rand() * y_scaling_range*2 - y_scaling_range # 上下の拡大縮小率
    xx_rotation = np.random.rand() * rotation_range*2 - rotation_range        # 回転角度
    yx_rotation = np.random.rand() * rotation_range*2 - rotation_range        # 回転角度
    xy_rotation = np.random.rand() * rotation_range*2 - rotation_range        # 回転角度
    yy_rotation = np.random.rand() * rotation_range*2 - rotation_range        # 回転角度
    # 度→ラジアン
    rd_xx_rotation = np.radians(xx_rotation)
    rd_yx_rotation = np.radians(yx_rotation)
    rd_xy_rotation = np.radians(xy_rotation)
    rd_yy_rotation = np.radians(yy_rotation)
   
    # データ生成
    img_gen = np.zeros_like(img_r)
    for y in range(h):
        for x in range(w):
            x_gen = int(np.floor(((x - mw + 0.5) * np.cos(rd_xx_rotation) - (y - mh + 0.5) * np.sin(rd_yx_rotation)) * x_scaling + x_shift + mw))
            y_gen = int(np.floor(((x - mh + 0.5) * np.sin(rd_xy_rotation) + (y - mh + 0.5) * np.cos(rd_yy_rotation)) * y_scaling + y_shift + mh))
            # はみ出した部分は無視
            if y_gen < h and x_gen < w and y_gen >= 0 and x_gen >= 0:
                img_gen[:, y_gen, x_gen] = img_r[:, y, x]

    return img_gen.reshape(img_shape)
```

```python
# MNISTデータ読み込み
x_train, t_train, x_test, t_test = load_mnist('data/')

# 入力データの正規化(0～1)
nx_train = x_train/255
nx_test  = x_test/255
```

```python
# ノード数設定
d0 = nx_train.shape[1]
d1 = 100 # 1層目のノード数
d2 = 50  # 2層目のノード数
d3 = 10
# 重みの初期化(-0.1～0.1の乱数)
np.random.seed(8)
W1 = np.random.rand(d0, d1) * 0.2 - 0.1
W2 = np.random.rand(d1, d2) * 0.2 - 0.1
W3 = np.random.rand(d2, d3) * 0.2 - 0.1
# バイアスの初期化(0)
b1 = np.zeros(d1)
b2 = np.zeros(d2)
b3 = np.zeros(d3)

# 学習率
lr = 0.25
# バッチサイズ
batch_size = 100
# 学習回数
epoch = 100
# シャッフルフラグ
shuffle = True

# エポックごとの誤差、正解率格納エリア
train_rate, test_rate, train_err, test_err = np.zeros(epoch+1), np.zeros(epoch+1), np.zeros(epoch+1), np.zeros(epoch+1)

# 誤差、正解率表示
train_rate[0], test_rate[0], train_err[0], test_err[0] = print_metrics(0, nx_train, t_train, None, nx_test, t_test, None, W1, b1, W2, b2, W3, b3)

for i in range(epoch):
    # データシャッフル
    idx = np.arange(nx_train.shape[0])
    if shuffle:
        np.random.shuffle(idx)
    
    # 学習
    y_train = np.zeros_like(t_train)
    for j in range(0, nx_train.shape[0], batch_size):
        # データ生成
        nx_train_g = generator(nx_train[idx[j:j+batch_size]], shape=(28,28), 
                               x_shift_range=1, y_shift_range=1, x_scaling_range=0.1, y_scaling_range=0.1, rotation_range=10)
        y_train[idx[j:j+batch_size]], W1, b1, W2, b2, W3, b3 = learn(nx_train_g, t_train[idx[j:j+batch_size]], W1, b1, W2, b2, W3, b3, lr)

    # 誤差、正解率表示
    train_rate[i+1], test_rate[i+1], train_err[i+1], test_err[i+1] = print_metrics(i+1, nx_train, t_train, y_train, nx_test, t_test, None, W1, b1, W2, b2, W3, b3)
```