前回は、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の画像です。以下のように、画像の中心を原点として考えます。
それぞれのピクセルの中央の座標がどこに移動するかを考えます。
ここでは、横方向に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)
平行移動するプログラムを考えます。
画像のサイズを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ピクセル移動したデータです。
元の画像と生成した画像を結合し学習してみます。
# 上下、左右に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ピクセル移動したデータです。
元データと結合します。
# 斜め方向に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)
拡大・縮小するプログラムを考えます。
データを生成する部分を拡大・縮小に変更します。
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倍の画像です。
拡大した場合、途中に抜けがあります。正確に拡大・縮小を行う場合はもっと対処が必要ですが、ここではこれでよしとします。
画像を結合し学習を実行します。
# 拡大・縮小
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)
回転するプログラムを考えます。
パラメータは、度で渡しましたが、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度回転の画像です。
画像を結合し学習を実行します。
# 拡大・縮小
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()
どうですか?なかなかバリエーションのあるデータが生成できました。
次に、移動距離を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()
これは、これでよいのですが、もう少し工夫してみます。
回転の部分を別々に乱数で生成します。
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()
次に、移動距離を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でデータを生成し、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()
次に、移動距離を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)