LoginSignup
5
8

More than 5 years have passed since last update.

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

Posted at

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

プログラム見直し

「ディープラーニングを実装から学ぶ~ (まとめ1)実装は、実は簡単」のプログラムを利用しますが、「ディープラーニングを実装から学ぶ~ (まとめ4)MNISTで99%も簡単!」での変更の対応を事前に行います。
変更点は以下です。

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

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

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

# 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 $移動した場合、以下の式で表せます。

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

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

MNISTは、28$\times$28の画像です。以下のように、画像の中心を原点として考えます。
5_rei.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

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

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

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

            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]

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

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)

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

# 上下、左右に1ピクセル移動
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) # 右

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

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()

左から、元データ、上、下、左、右に1ピクセル移動したデータです。
shift_5_1.png

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

# 上下、左右に1ピクセル移動データを結合
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])

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

  • 学習率を0.25に変更
  • エポックごとにデータをソート
# ノード数設定
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%程度でしたので、大幅な精度向上です。びっくりです。

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

# 斜め方向に1ピクセル移動
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)

左から、元データ、左上、左下、右上、右下に1ピクセル移動したデータです。
shift_5_2.png

元データと結合します。

# 斜め方向に1ピクセル移動
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 $倍した場合、以下の式で表せます。

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

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

            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))

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

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倍に縮小します。

# 拡大・縮小
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番目のデータを表示してみます。

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

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

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

# 拡大・縮小
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 $回転した場合、以下の式で表せます。

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

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

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

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

回転部分を変更します。

            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))

回転関数全体です。

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度回転します。

# 回転
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番目のデータを表示してみます。

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

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

# 拡大・縮小
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

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

参考

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

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エポック実行後のテストデータの正解率と最大の正解率です。

データ拡張方法 テスト正解率 テスト最大
上下左右に1ピクセル移動 98.79 98.81
斜め方向に1ピクセル移動 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

データ生成

乱数によるデータ生成

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

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度の間で決めます。

    # 乱数
    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        # 回転角度

データの変換部分です。

            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))

関数全体です。

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度とします。

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
どうですか?なかなかバリエーションのあるデータが生成できました。

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

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

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

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は数字のため大きく回転しないことを前提としています。

変更した関数です。

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度とします。

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

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

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
先ほどの画像より、バリエーション豊かになりました。

学習の実行

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

        # データ生成
        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エポック実行しました。また、あとで、学習状況をグラフ化するため、エポックごとの正解率、誤差を保存しておくように変更しました。

# ノード数設定
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%です。すごいですね。

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

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

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

        # データ生成
        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度
x_shift_range=1, y_shift_range=1, x_scaling_range=0.1, y_scaling_range=0.1, rotation_range=10
1層ノード数 2層ノード数 学習正解率 テスト正解率 テスト最高
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度
x_shift_range=2, y_shift_range=2, x_scaling_range=0.2, y_scaling_range=0.2, rotation_range=20
1層ノード数 2層ノード数 学習正解率 テスト正解率 テスト最高
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%を超えました。データ拡張は、強力ですね。

プログラム全体

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]
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
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
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]
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
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)
# MNISTデータ読み込み
x_train, t_train, x_test, t_test = load_mnist('data/')

# 入力データの正規化(0~1)
nx_train = x_train/255
nx_test  = x_test/255
# ノード数設定
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)
5
8
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
8