私は、2020年7月に「G検定(2020 #2)」に合格した。
次は、2021年2月実施の「E資格(2021 #1)」受験資格である__JDLA認定プログラム「3カ月で現場で潰しが効くディープラーニング講座」__に2020年8月から挑戦中。
本記事では、__JDLA認定プログラム「3カ月で現場で潰しが効くディープラーニング講座」の課題であり、「深層学習:Day2」__実装演習結果をまとめる。
1.「深層学習:Day2」講義動画の要点まとめ
Section1)勾配消失問題
・誤差逆伝播法が下位層に進んでいくにつれて、勾配がどんどん緩やかになっていく。そのため、勾配降下法による更新では下位層のパラメータはほとんど変わらず、訓練は最適値に収束しなくなる。
・なぜ勾配消失問題が起こるのか
活性化関数にシグモイド関数を用いると、誤差逆伝播の際に中間層が増えるにつれ、微分式の連鎖律による計算が増える。シグモイド関数は微分すると0~1の値が0~0.25になり、値が小さくなっていくため、学習ができなくなっていく。
・勾配消失の解決法:
1-1.活性化関数の選択
・ReLU関数は微分すると値が0か1になり、勾配消失問題の回避とスパース化に貢献できる
1-2.重みの初期値設定
重みを作るときに乱数をよく利用される。
乱数を利用しての重みの設定の仕方がいくつかある
・Xavier(ザビエル) 前の層のノード数の平方根で除算する $*\frac{1}{\sqrt{n}}$
・He(ヒー) 前の層のノード数の平方根で除算した値に対しroot2をかける $ * \sqrt{\frac{2}{n}}$
1-3.バッチ正規化
・バッチ正規化とはミニバッチ単位で入力値のデータの偏りを抑制する手法
・バッチ正規化の使いどころは、活性化関数に値を渡す前後にバッチ正規化の処理を行った層を加える
・バッチ正規化の効果
・計算の高速化
・過学習が起きづらくなる
Section2)学習率最適化手法
-
学習率の値について
-
値が大きすぎると最適値にいつまでもたどり着かず発散する
-
値が小さすぎると収束するまでに時間がかかる また、大域局所最適値に収束しづらくなる
-
学習率の決め方とは?
- 初期の学習率を大きく設定し、徐々に学習率を小さくしていく
- パラメータ毎に学習率を可変させる
⇒学習率最適化手法を利用して学習率を最適化
-
学習率最適化(オプティマイザー)手法
- 1.モメンタム
- 2.AdaGrad
- 3.RMSProp
- 4.Adam
1.モメンタム
\begin{align}
&V_t = \mu V_{t-1}-\epsilon \nabla E\\
&w^{(t+1)}=w^{(t)}+V_t\\
&\mu :慣性, \; \epsilon :学習率
\end{align}
- モメンタム:誤差をパラメータで微分したものと学習率の積を減算した後、現在の重みに前回の重みを減算した値と慣性の積を加算する
- 勾配降下法<参考>:誤差をパラメータで微分したものと学習率の積を減算する
- メリット
- 局所的最適解にならず、大域的最適解となる
- 谷間についてから最も低い位置(最適値)にいくまでの時間が早い
2.AdaGrad
\begin{align}
&h_0= \theta \\
&h_t=h_{t-1}+( \nabla E)^2 \\
&w^{(t+1)}=w^{(t)}- \epsilon \frac{1}{\sqrt{h_t}+\theta} \nabla E\\
\end{align}
- AdaGrad:誤差をパラメータで微分したものと再定義した学習率の積を減算する
- 勾配降下法<参考>:誤差をパラメータで微分したものと学習率の積を減算する
- メリット
- 勾配の緩やかな斜面に対して、最適値に近づける
- デメリット
- 学習率が徐々に小さくなるので、鞍点問題を引き起こす事がある
※鞍点問題:ある次元で見ると極小値だが、ある次元からみると極大値となっている
3.RMSProp
\begin{align}
&h_t=\alpha h_{t-1}+(1- \alpha)( \nabla E)^2 \\
&w^{(t+1)}=w^{(t)}- \epsilon \frac{1}{\sqrt{h_t}+\theta} \nabla E\\
\end{align}
- RMSProp:誤差をパラメータで微分したものと再定義した学習率の積を減算する
- 勾配降下法<参考>:誤差をパラメータで微分したものと学習率の積を減算する
- AdaGradの進化系:鞍点問題をスムーズに解消できるように改善したもの
- $\alpha$は0~1の値を取り、どれくらい今回の経験を次に活かすかを定めたパラメータ
- メリット
- 局所的最適解にはならず、大域最適解となる
- ハイパーパラメータの調整が必要な場合が少ない
4.Adam
- Adam:以下それぞれが含まれた最適化アルゴリズム
- モメンタムの過去の勾配の指数関数的減衰平均
- RMSPropの過去の勾配の2乗の指数関数的減衰平均
- メリット
- モメンタム及びRMSPropのメリットを含んだアルゴリズム
- 今までの良いところ取り、悪いところを改善した最適化手法
Section3)過学習
-
過学習とは、テスト誤差と訓練誤差とで学習曲線が乖離すること
⇒特定の訓練サンプルに対して、特化して学習したことにより発生
-
過学習の原因
-
パラメータの数が多い
-
パラメータの値が適切でない
-
ノードが多い など
上記に対し、ネットワークの自由度(層数、ノード数、パラメータの値など)が高いと過学習が起こる -
過学習抑制手法:正則化
- 正則化とは、ネットワークの自由度(層数、ノード数、パラメータの値など)を制約すること
- 正則化手法として、L1正則化、L2正則化、__ドロップアウト__などがある
-
Weight decay(荷重減衰)
重みが大きい値をとることで、過学習が発生することがある
そのため、誤差に対して、正則化項を加算することで重みを抑制する(荷重減衰) -
L1,L2正則化
-
p=1の場合L1正則化(ラッソ回帰)
-
p=2の場合L2正則化(リッジ回帰)
-
p1ノルム:マンハッタン距離 $x+y$
-
p2ノルム:ユークリッド距離 $\sqrt{x^2+y^2}$
\begin{align}
誤差&関数にpノルムを加える\\
&E_n(W)+ \frac{1}{p}\lambda ||x||_p \\
pノ&ルムの計算\\
&||x||_p=(|x_1|^p+ \cdots +|x_n|^p)^{\frac{1}{p}}\\
\end{align}
-
ドロップアウト
-
過学習の課題である__ノード数が多い__という点に対し、ランダムにノードを削除して学習させる手法
-
メリット:データ量を変化させずに異なるモデルを学習させていると解釈できる
Section4)畳み込みニューラルネットワークの概念
__CNN__は__Convolutional Neural Network(畳み込みニューラルネットワーク)__の略で、次元間でつながりのあるデータを扱えるニューラルネットワークである。画像認識等でよく活用されるが、次元間でつながりがあるものでは活用でき、画像データ以外にも活用される。
-
入力層⇒畳み込み層⇒プーリング層⇒全結合層⇒出力層
-
畳み込み層:画像の場合、縦、横、チャンネルの3次元のデータをそのまま学習し、次に伝えることができる
-
畳み込み層の演算概念:入力画像*フィルター(全結合でという重み)⇒出力画像
-
バイアス:フィルターを用いて演算したものにバイアスを加える
-
パディング:固定データ(0以外でも可能)を追加し、元画像と同サイズを実現
-
ストライド:フィルターの移動量
-
チャンネル:複数の入力(RGB等)。フィルターもチャンネル数分用意する。
-
全結合層:画像の場合、縦、横、チャンネルの3次元データだが、1次元のデータとして処理される⇒RGBの各チャンネル間の関連性が、学習に反映されないということ
-
プーリング層 入力データをより扱いやすい形に変形するために、情報を圧縮し、ダウンサンプリングする。Maxプーリング,Aveプーリング
CNNの原型として有名な__LeNet__がある。LeNetは1990年代に提案された畳み込み層とプーリング層で構成されるニューラルネットワークである。
- LeNet(ルネット):32x32画像入力データを10クラスに分類
- 構造:
- 入力データ (32,32) 1024個
- 畳み込み層(5x5フィルター) → (28,28,6) 4704個
- プーリング層(2x2フィルター) → (14,14,6) 1176個
- 畳み込み層(5x5フィルター) → (10,10,16) 1600個
- プーリング層(2x2フィルター) → (5,5,16) 400個
- 全結合層 →(120,) 120個
- 全結合層 →(84,) 84個
- 全結合層 →(10,) 10個
Section5)最新のCNN
__AlexNet__は2012年に開かれた画像認識コンペティションで2位に大差をつけて優勝したモデルである。A lexNetの登場でディープラーニングが大きく注目を集めた。
- 5層の畳み込み層およびプーリング層など、それに続く3層の全結合層から構成
- 4096の全結合層の出力にドロップアウトを使用して過学習を防いでいる
- 構造:
- 入力データ (224,224,3) 150,528個
- 畳み込み層(11x11フィルター) → (55,55,96) 290,400個
- Maxプーリング層(5x5フィルター) → (27,27,256) 186,624個
- Maxプーリング層(3x3フィルター) → (13,13,384) 64,896個
- 畳み込み層(3x3フィルター) → (13,13,384) 64,896個
- 畳み込み層(5x5フィルター) → (13,13,256) 43,264個(※1)
- 全結合層 →(4096,) 4096個
- 全結合層 →(4096,) 4096個
- 全結合層 →(1000,) 1000個
全結合層では直前の出力データを以下の加工を行い入力として利用する(例:上記※1)
- Fratten:全ての要素を一列に並べる43,264要素
- Global MaxPooling:13×13の中で一番大きい要素を抽出 256要素
- Global AvgPooling:13×13の中で平均値を抽出 256要素
2.「深層学習:Day2」確認テスト考察
★確認テスト1
連鎖律の原理を使い、$dz/dx$を求めよ。
- $z = t^2$
- $t = x + y$
解答
\begin{align}
\frac{dz}{dx} &= \frac{dz}{dt} \frac{dt}{dx} \\
&= \frac{d(t^2)}{dt} \frac{d(x + y)}{dx} \\
&= 2t * 1 = 2t\\
&= 2(x + y)\\
\end{align}
★確認テスト2
シグモイド関数を微分した時、入力値が0の時に最大値をとる。その値として正しいものを選択しから選べ。
(1) 0.15
(2) 0.25
(3) 0.35
(4) 0.45
★確認テスト3
重みの初期値に0を設定すると、どのような問題が発生するか。簡潔に説明せよ。
解答
重みの初期値に0を設定すると、全ての重みが均一に更新されるため、多数の重みを持つ意味が無くなるため、正しい学習が行えないから。
解答
右:L1正則化(ラッソ回帰)
左:L2正則化(リッジ回帰)
解答
★確認テスト8
サイズ6×6の入力画像を、サイズ2×2のフィルタで畳み込んだ時の出力画像のサイズを応えよ。なお、ストライドとパディングは1とする。
解答
以下、公式を元に求める。
\begin{align}
高さ&= \frac{画像の高さ+2*パディング高さ-フィルター高さ}{ストライド}+1 \\
&=\frac{6+2*1-2}{1}+1 =7\\
幅&= \frac{画像の幅+2*パディング幅-フィルター幅}{ストライド}+1 \\
&=\frac{6+2*1-2}{1}+1 =7\\
\end{align}
7×7
3.「深層学習:Day2」実装演習結果と考察
3-1.勾配消失問題
2_2_1_vanishing_gradient.ipynb
の勾配消失問題について演習する。
3-1-1. シグモイド関数
活性化関数にシグモイド関数を用いると、誤差逆伝播の際に中間層が増えるにつれ、微分式の連鎖律による計算が増え、勾配消失問題が起きやすい。
誤差逆伝播を繰り返し、勾配消失問題を実感する。
3-1-1-1. importとデータロード
importと関数の定義を実施する。
今回のデータは、__mnist__の手書きデータセットを使う。
import numpy as np
from common import layers
from collections import OrderedDict
from common import functions
from data.mnist import load_mnist
import matplotlib.pyplot as plt
# mnistをロード
(x_train, d_train), (x_test, d_test) = load_mnist(normalize=True, one_hot_label=True)
train_size = len(x_train)
print("データ読み込み完了")
3-1-1-2. 係数設定
ここでは各種係数の設定を実施。入力層、中間層、出力層のサイズ設定や、繰り返し学習数の設定、ミニバッチサイズ等を一度に設定している。
# 重み初期値補正係数
wieght_init = 0.01
#入力層サイズ
input_layer_size = 784
#中間層サイズ
hidden_layer_1_size = 40
hidden_layer_2_size = 20
#出力層サイズ
output_layer_size = 10
# 繰り返し数
iters_num = 2000
# ミニバッチサイズ
batch_size = 100
# 学習率
learning_rate = 0.1
# 描写頻度
plot_interval=10
3-1-1-3. 関数の定義
ここでは順伝播、誤差逆伝播等の関数を定義している。
中間層の活性化関数にシグモイド関数を、出力層の活性化関数にはソフトマックス関数を定義している。
# 初期設定
def init_network():
network = {}
network['W1'] = wieght_init * np.random.randn(input_layer_size, hidden_layer_1_size)
network['W2'] = wieght_init * np.random.randn(hidden_layer_1_size, hidden_layer_2_size)
network['W3'] = wieght_init * np.random.randn(hidden_layer_2_size, output_layer_size)
network['b1'] = np.zeros(hidden_layer_1_size)
network['b2'] = np.zeros(hidden_layer_2_size)
network['b3'] = np.zeros(output_layer_size)
return network
# 順伝播
def forward(network, x):
W1, W2, W3 = network['W1'], network['W2'], network['W3']
b1, b2, b3 = network['b1'], network['b2'], network['b3']
hidden_f = functions.sigmoid
u1 = np.dot(x, W1) + b1
z1 = hidden_f(u1)
u2 = np.dot(z1, W2) + b2
z2 = hidden_f(u2)
u3 = np.dot(z2, W3) + b3
y = functions.softmax(u3)
return z1, z2, y
# 誤差逆伝播
def backward(x, d, z1, z2, y):
grad = {}
W1, W2, W3 = network['W1'], network['W2'], network['W3']
b1, b2, b3 = network['b1'], network['b2'], network['b3']
hidden_d_f = functions.d_sigmoid
last_d_f = functions.d_softmax_with_loss
# 出力層でのデルタ
delta3 = last_d_f(d, y)
# b3の勾配
grad['b3'] = np.sum(delta3, axis=0)
# W3の勾配
grad['W3'] = np.dot(z2.T, delta3)
# 2層でのデルタ
delta2 = np.dot(delta3, W3.T) * hidden_d_f(z2)
# b2の勾配
grad['b2'] = np.sum(delta2, axis=0)
# W2の勾配
grad['W2'] = np.dot(z1.T, delta2)
# 1層でのデルタ
delta1 = np.dot(delta2, W2.T) * hidden_d_f(z1)
# b1の勾配
grad['b1'] = np.sum(delta1, axis=0)
# W1の勾配
grad['W1'] = np.dot(x.T, delta1)
return grad
# パラメータの初期化
network = init_network()
accuracies_train = []
accuracies_test = []
# 正答率
def accuracy(x, d):
z1, z2, y = forward(network, x)
y = np.argmax(y, axis=1)
if d.ndim != 1 : d = np.argmax(d, axis=1)
accuracy = np.sum(y == d) / float(x.shape[0])
return accuracy
3-1-1-4. 繰り返し学習
データロードや関数定義、計数設定が終わったら、いよいよ学習に入る。ランダムにミニバッチを取得(batch_size = 100
)し、それぞれのデータを繰り返し数(iters_num = 2000
)で学習を実施する。
その繰り返し学習の中で、グラフ表示、学習経過表示のため、一工夫が加えられている。描写頻度(plot_interval=10
)毎の__print文__の表示と、グラフ化用に__numpy__配列へのデータ保存である。
また、学習と合わせ、検証用データの正答率も確認している。ミニバッチ数として__100__を設定、検証用データは__10000__なので、__100__のデータ数で学習した重み、バイアスを使い、__10000__の検証用データで正答率を確認している。
for i in range(iters_num):
# ランダムにバッチを取得
batch_mask = np.random.choice(train_size, batch_size)
# ミニバッチに対応する教師訓練画像データを取得
x_batch = x_train[batch_mask]
# ミニバッチに対応する訓練正解ラベルデータを取得する
d_batch = d_train[batch_mask]
z1, z2, y = forward(network, x_batch)
grad = backward(x_batch, d_batch, z1, z2, y)
if (i+1)%plot_interval==0:
accr_test = accuracy(x_test, d_test)
accuracies_test.append(accr_test)
accr_train = accuracy(x_batch, d_batch)
accuracies_train.append(accr_train)
print('Generation: ' + str(i+1) + '. 正答率(トレーニング) = ' + str(accr_train))
print(' : ' + str(i+1) + '. 正答率(テスト) = ' + str(accr_test))
# パラメータに勾配適用
for key in ('W1', 'W2', 'W3', 'b1', 'b2', 'b3'):
network[key] -= learning_rate * grad[key]
3-1-1-5. 学習結果
上記学習結果を以下コードで表示する。
lists = range(0, iters_num, plot_interval)
plt.plot(lists, accuracies_train, label="training set")
plt.plot(lists, accuracies_test, label="test set")
plt.legend(loc="lower right")
plt.title("accuracy")
plt.xlabel("count")
plt.ylabel("accuracy")
plt.ylim(0, 1.0)
# グラフの表示
plt.show()
描写頻度plot_interval
を__10__に設定しているため、繰り返し数iters_num
の__2000__の中で10ずつ結果表示がされている。また、グラフも同様に10毎のポイントで表記されている。
肝心な結果としては、グラフを見ての通り、学習繰り返し数__2000__の中で学習用データの正答率、検証用データの正答率は上がっていない。活性化関数が__シグモイド関数__であることによる__勾配消失問題__が起きていることが推測できる。
3-1-2. ReLU関数
活性化関数に__シグモイド関数__を用いると、__勾配消失問題__が起きていると推測できる学習結果を示すことが出来た。それでは、活性化関数を__勾配消失問題__が起きにくい__ReLU関数__に変えて見たらどうなるか、引き続き検証する。
3-1-2-1. 関数の定義の変更箇所
今回は、順伝播、誤差逆伝播の活性関数定義部分のみ変更している
- 変更前
# 順伝播
hidden_f = functions.sigmoid
# 誤差逆伝播
hidden_d_f = functions.d_sigmoid
- 変更後
# 順伝播
hidden_f = functions.relu
# 誤差逆伝播
hidden_d_f = functions.d_relu
3-1-2-2. 学習結果
上記学習結果を表示する。
結果としては、活性化関数を__シグモイド関数__から__ReLU関数__に変更しただけで、グラフを見ての通り、学習用データの正答率、検証用データの正答率共に上がっている結果が見えた。__ReLU関数__により、__勾配消失問題__が解決できていることがわかる結果となった。
3-1-3. シグモイド関数+重み初期値設定(Xavier)
次に__勾配消失問題__の解決法である重みの初期値設定について検証する。初期値設定として__Xavier(ザビエル)__と__He(ヒー)__の2種類があり、まずは__Xavier(ザビエル)__による重み初期値を活性化関数シグモイド関数を使い検証する。
3-1-3-1. 重み設定の変更箇所
__Xavier(ザビエル)__は、ランダムに設定する重みの初期値に対し、前の層のノード数の平方根で除算する方法である。($*\frac{1}{\sqrt{n}}$)
具体的には以下のようにコードを修正している。
- 変更前
# 重み初期値補正係数
wieght_init = 0.01
network['W1'] = wieght_init * np.random.randn(input_layer_size, hidden_layer_1_size)
network['W2'] = wieght_init * np.random.randn(hidden_layer_1_size, hidden_layer_2_size)
network['W3'] = wieght_init * np.random.randn(hidden_layer_2_size, output_layer_size)
- 変更後
# Xavierの初期値
network['W1'] = np.random.randn(input_layer_size, hidden_layer_1_size) / (np.sqrt(input_layer_size))
network['W2'] = np.random.randn(hidden_layer_1_size, hidden_layer_2_size) / (np.sqrt(hidden_layer_1_size))
network['W3'] = np.random.randn(hidden_layer_2_size, output_layer_size) / (np.sqrt(hidden_layer_2_size))
3-1-3-2. 学習結果
上記学習結果を表示する。
結果としては、重み初期値設定方法を__Xavier(ザビエル)__に変更しただけで、グラフを見ての通り、学習用データの正答率、検証用データの正答率共に上がっている結果が見え、__勾配消失問題__が解決できていることがわかる結果となった。
また、活性化関数を__ReLU関数__に変えたときに比べ、正答率の上昇に早く変化があることが見える反面、__2000__回の繰り返し関数では正答率は77%程度との結果となった。(ReLU関数:92%)
3-1-4. ReLU関数+重み初期値設定(He)
次に__勾配消失問題__の解決法である活性化関数を__ReLU関数__へと、重みの初期値設定について合わせて変更、検証する。初期値設定として今回は__He(ヒー)__を使用する。
3-1-4-1. 重み設定の変更箇所
__He(ヒー)__は、ランダムに設定する重みの初期値に対し、前の層のノード数の平方根で除算した値に対しroot2をかける方法である。($ * \sqrt{\frac{2}{n}}$)
具体的には以下のようにコードを修正している。
- 変更前
# 重み初期値補正係数
wieght_init = 0.01
network['W1'] = wieght_init * np.random.randn(input_layer_size, hidden_layer_1_size)
network['W2'] = wieght_init * np.random.randn(hidden_layer_1_size, hidden_layer_2_size)
network['W3'] = wieght_init * np.random.randn(hidden_layer_2_size, output_layer_size)
- 変更後
# Heの初期値
network['W1'] = np.random.randn(input_layer_size, hidden_layer_1_size) / np.sqrt(input_layer_size) * np.sqrt(2)
network['W2'] = np.random.randn(hidden_layer_1_size, hidden_layer_2_size) / np.sqrt(hidden_layer_1_size) * np.sqrt(2)
network['W3'] = np.random.randn(hidden_layer_2_size, output_layer_size) / np.sqrt(hidden_layer_2_size) * np.sqrt(2)
3-1-4-2. 学習結果
上記学習結果を表示する。
結果としては、活性化関数を__ReLU関数__に変更しただけよりも、重み初期値設定方法を__Xavier(ザビエル)__に変更しただけよりも、学習用データの正答率、検証用データの正答率共に上がっていると共に、正答率の上昇に早く変化が現れ、__勾配消失問題__が解決できていることがわかる結果となった。
(ReLU関数:92%、Xavier:77%、ReLU+He:96%)
3-2.学習率最適化手法
2_4_optimizer_after.ipynb
の学習最適化手法について演習する。
学習率を最適化することにより、最適値にたどり着かず発散してしまったり、大域局所最適値に収束しない現象を抑制する。
最適化手法として以下の4つの方法で演習する。
- モメンタム
- AdaGrad
- RMSprop
- Adam
その比較対象として、__確率的勾配降下法(SGD)__の演習も実施する。
今回の演習は、別定義されている__MultiLayerNet__というネットワーク関数を用い演習するが、その関数の詳細は割愛する。
3-2-1. 確率的勾配降下法(SGD)
3-2-1-1. importとデータロード
importと関数の定義を実施する。
今回のデータは、__mnist__の手書きデータセットを使う。
import numpy as np
from collections import OrderedDict
from common import layers
from data.mnist import load_mnist
import matplotlib.pyplot as plt
from multi_layer_net import MultiLayerNet
# データの読み込み
(x_train, d_train), (x_test, d_test) = load_mnist(normalize=True, one_hot_label=True)
print("データ読み込み完了")
3-2-1-2. MultiLayerNet設定
ここでは__MultiLayerNet__の設定をしている。入力層、中間層、出力層のノード数、活性化関数、重み設定係数に加え、バッチ正規化無し(batch_normalization = False
)を設定する。
# batch_normalizationの設定 =======================
# use_batchnorm = True
use_batchnorm = False
# ====================================================
network = MultiLayerNet(input_size=784, hidden_size_list=[40, 20], output_size=10, activation='sigmoid', weight_init_std=0.01,
use_batchnorm=use_batchnorm)
3-2-1-3. 係数設定
ここでは各種係数の設定を実施。繰り返し学習数の設定、ミニバッチサイズ、学習率等を一度に設定する。今回は繰り返し数を__1000__(iters_num = 1000
)で設定しており、重みの初期化対策である__Xavier__や__He__は使用していない。
iters_num = 1000
train_size = x_train.shape[0]
batch_size = 100
learning_rate = 0.01
train_loss_list = []
accuracies_train = []
accuracies_test = []
plot_interval=10
3-2-1-4. 繰り返し学習
データロードや関数定義、計数設定が終わったら、いよいよ学習に入る。
学習率は、上記で設定したlearning_rate = 0.01
をそのまま使用する。それ以外の設定は__3-1.勾配消失問題__と同様。
for i in range(iters_num):
batch_mask = np.random.choice(train_size, batch_size)
x_batch = x_train[batch_mask]
d_batch = d_train[batch_mask]
# 勾配
grad = network.gradient(x_batch, d_batch)
for key in ('W1', 'W2', 'W3', 'b1', 'b2', 'b3'):
network.params[key] -= learning_rate * grad[key]
loss = network.loss(x_batch, d_batch)
train_loss_list.append(loss)
if (i + 1) % plot_interval == 0:
accr_test = network.accuracy(x_test, d_test)
accuracies_test.append(accr_test)
accr_train = network.accuracy(x_batch, d_batch)
accuracies_train.append(accr_train)
print('Generation: ' + str(i+1) + '. 正答率(トレーニング) = ' + str(accr_train))
print(' : ' + str(i+1) + '. 正答率(テスト) = ' + str(accr_test))
3-2-1-5. 学習結果
上記学習結果を以下コードで表示する。この設定も__3-1.勾配消失問題__と同様のまま使用している。
lists = range(0, iters_num, plot_interval)
plt.plot(lists, accuracies_train, label="training set")
plt.plot(lists, accuracies_test, label="test set")
plt.legend(loc="lower right")
plt.title("accuracy")
plt.xlabel("count")
plt.ylabel("accuracy")
plt.ylim(0, 1.0)
# グラフの表示
plt.show()
描写頻度plot_interval
を__10__に設定しているため、繰り返し数iters_num
の__1000__の中で10ずつ結果表示がされている。また、グラフも同様に10毎のポイントで表記されている。
学習結果は、グラフを見ての通り、学習繰り返し数__1000__の中で学習用データの正答率、検証用データの正答率は上がっていない。活性化関数が__シグモイド関数__であることによる__勾配消失問題__が起きていることが推測できる。
3-2-2. モメンタム
比較対象となる__確率的勾配降下法(SGD)__での学習結果を示したのち、学習率最適化手法の1つ__モメンタム__で学習率最適化手法の効果を検証する。
モメンタムとは以下の特徴がある。この特徴を元に演習を実施する。
\begin{align}
&V_t = \mu V_{t-1}-\epsilon \nabla E\\
&w^{(t+1)}=w^{(t)}+V_t\\
&\mu :慣性, \; \epsilon :学習率
\end{align}
- モメンタム:誤差をパラメータで微分したものと学習率の積を減算した後、現在の重みに前回の重みを減算した値と慣性の積を加算する
- メリット
- 局所的最適解にならず、大域的最適解となる
- 谷間についてから最も低い位置(最適値)にいくまでの時間が早い
3-2-2-1. 関数の定義の変更箇所
今回は、係数の追加、学習率計算方法の変更部分のみ比較している。係数は、$\mu :慣性$を、momentum
とし、$V_t$をv[key]
で設定、モメンタムの学習率最適化を実現している。
- 変更前
learning_rate = 0.01
# 勾配
grad = network.gradient(x_batch, d_batch)
for key in ('W1', 'W2', 'W3', 'b1', 'b2', 'b3'):
network.params[key] -= learning_rate * grad[key]
loss = network.loss(x_batch, d_batch)
train_loss_list.append(loss)
- 変更後
learning_rate = 0.3
# 慣性
momentum = 0.9
# 勾配
grad = network.gradient(x_batch, d_batch)
if i == 0:
v = {}
for key in ('W1', 'W2', 'W3', 'b1', 'b2', 'b3'):
if i == 0:
v[key] = np.zeros_like(network.params[key])
v[key] = momentum * v[key] - learning_rate * grad[key]
network.params[key] += v[key]
loss = network.loss(x_batch, d_batch)
train_loss_list.append(loss)
3-2-2-2. 学習結果
上記学習結果を表示する。
結果としては、学習率最適化手法として__モメンタム__に変更しただけで、学習用データの正答率、検証用データの正答率共に上がっており、活性化関数が__シグモイド関数__のままで__勾配消失問題__が解決できていることがわかる結果となった。
(SGD:11%、モメンタム:93%)
3-2-3. AdaGrad
学習率最適化手法の1つ__モメンタム__に続き、次は__AdaGrad__での学習率最適化手法の効果を検証する。
__AdaGrad__とは以下の特徴がある。この特徴を元に演習を実施する。
\begin{align}
&h_0= \theta \\
&h_t=h_{t-1}+( \nabla E)^2 \\
&w^{(t+1)}=w^{(t)}- \epsilon \frac{1}{\sqrt{h_t}+\theta} \nabla E\\
\end{align}
- AdaGrad:誤差をパラメータで微分したものと再定義した学習率の積を減算する
- メリット
- 勾配の緩やかな斜面に対して、最適値に近づける
- デメリット
- 学習率が徐々に小さくなるので、鞍点問題を引き起こす事がある
※鞍点問題:ある次元で見ると極小値だが、ある次元からみると極大値となっている
3-2-3-1. 関数の定義の変更箇所
今回は、係数の追加、学習率計算方法の変更部分のみ比較している。係数は、$\theta$を0として扱い、$h_t$をh[key]
で設定、AdaGradの学習率最適化を実現している。
- 変更前
learning_rate = 0.01
# 勾配
grad = network.gradient(x_batch, d_batch)
for key in ('W1', 'W2', 'W3', 'b1', 'b2', 'b3'):
network.params[key] -= learning_rate * grad[key]
loss = network.loss(x_batch, d_batch)
train_loss_list.append(loss)
- 変更後
learning_rate = 0.1
# 勾配
grad = network.gradient(x_batch, d_batch)
if i == 0:
h = {}
for key in ('W1', 'W2', 'W3', 'b1', 'b2', 'b3'):
if i == 0:
h[key] = np.full_like(network.params[key], 1e-4)
else:
h[key] += np.square(grad[key])
network.params[key] -= learning_rate * grad[key] / (np.sqrt(h[key]))
loss = network.loss(x_batch, d_batch)
train_loss_list.append(loss)
3-2-3-2. 学習結果
上記学習結果を表示する。
結果としては、学習率最適化手法として__モメンタム__同様、学習用データの正答率、検証用データの正答率共に上がっており、活性化関数が__シグモイド関数__のままで__勾配消失問題__が解決できていることがわかる結果となった。最終的な正答率はモメンタムとAdaGradで大差は無かったが、AdaGradでの学習用データ正答率は96%と高く、若干過学習気味となっていると推測できる。
(SGD:11%、モメンタム:93%、AdaGrad:93%)
3-2-4. RMSprop
学習率最適化手法の1つ__モメンタム__、__AdaGrad__に続き、次は__RMSprop__での学習率最適化手法の効果を検証する。
__RMSprop__とは以下の特徴がある。この特徴を元に演習を実施する。
\begin{align}
&h_t=\alpha h_{t-1}+(1- \alpha)( \nabla E)^2 \\
&w^{(t+1)}=w^{(t)}- \epsilon \frac{1}{\sqrt{h_t}+\theta} \nabla E\\
\end{align}
- RMSProp:誤差をパラメータで微分したものと再定義した学習率の積を減算する
- AdaGradの進化系:鞍点問題をスムーズに解消できるように改善したもの
- $\alpha$は0~1の値を取り、どれくらい今回の経験を次に活かすかを定めたパラメータ
- メリット
- 局所的最適解にはならず、大域最適解となる
- ハイパーパラメータの調整が必要な場合が少ない
3-2-4-1. 関数の定義の変更箇所
今回は、係数の追加、学習率計算方法の変更部分のみ比較している。係数は、$\alpha$をdecay_rate
とし、$\theta$は0として扱う。そして、$h_t$をh[key]
で設定し、RMSpropの学習率最適化を実現している。
- 変更前
learning_rate = 0.01
# 勾配
grad = network.gradient(x_batch, d_batch)
for key in ('W1', 'W2', 'W3', 'b1', 'b2', 'b3'):
network.params[key] -= learning_rate * grad[key]
loss = network.loss(x_batch, d_batch)
train_loss_list.append(loss)
- 変更後
learning_rate = 0.01
decay_rate = 0.99
# 勾配
grad = network.gradient(x_batch, d_batch)
if i == 0:
h = {}
for key in ('W1', 'W2', 'W3', 'b1', 'b2', 'b3'):
if i == 0:
h[key] = np.zeros_like(network.params[key])
h[key] *= decay_rate
h[key] += (1 - decay_rate) * np.square(grad[key])
network.params[key] -= learning_rate * grad[key] / (np.sqrt(h[key]) + 1e-7)
loss = network.loss(x_batch, d_batch)
train_loss_list.append(loss)
3-2-4-2. 学習結果
上記学習結果を表示する。
結果としては、学習率最適化手法として__モメンタム__、__AdaGrad__同様、学習用データの正答率、検証用データの正答率共に上がっており、活性化関数が__シグモイド関数__のままで__勾配消失問題__が解決できていることがわかる結果となった。また、モメンタム、__AdaGrad__と比べ、正答率90%までの上昇が少ない繰り返し数で実現できている。最終的な正答率はモメンタムやAdaGradと大差は無かったが、RMSpropでの学習用データ正答率は99%と高い。
(SGD:11%、モメンタム:93%、AdaGrad:93%、RMSprop:94%)
3-2-5. Adam
学習率最適化手法の最後は__Adam__。次は__Adam__での学習率最適化手法の効果を検証する。
__Adam__とは以下の特徴がある。この特徴を元に演習を実施する。
- Adam:以下それぞれが含まれた最適化アルゴリズム
- モメンタムの過去の勾配の指数関数的減衰平均
- RMSPropの過去の勾配の2乗の指数関数的減衰平均
- メリット
- モメンタム及びRMSPropのメリットを含んだアルゴリズム
- 今までの良いところ取り、悪いところを改善した最適化手法
3-2-5-1. 関数の定義の変更箇所
今回は、係数の追加、学習率計算方法の変更部分のみ比較している。係数はbeta1
、beta2
を追加。そして、h[key]
、v[key]
を設定し、Adamの学習率最適化を実現している。
- 変更前
learning_rate = 0.01
# 勾配
grad = network.gradient(x_batch, d_batch)
for key in ('W1', 'W2', 'W3', 'b1', 'b2', 'b3'):
network.params[key] -= learning_rate * grad[key]
loss = network.loss(x_batch, d_batch)
train_loss_list.append(loss)
- 変更後
learning_rate = 0.01
beta1 = 0.9
beta2 = 0.999
# 勾配
grad = network.gradient(x_batch, d_batch)
if i == 0:
m = {}
v = {}
learning_rate_t = learning_rate * np.sqrt(1.0 - beta2 ** (i + 1)) / (1.0 - beta1 ** (i + 1))
for key in ('W1', 'W2', 'W3', 'b1', 'b2', 'b3'):
if i == 0:
m[key] = np.zeros_like(network.params[key])
v[key] = np.zeros_like(network.params[key])
m[key] += (1 - beta1) * (grad[key] - m[key])
v[key] += (1 - beta2) * (grad[key] ** 2 - v[key])
network.params[key] -= learning_rate_t * m[key] / (np.sqrt(v[key]) + 1e-7)
loss = network.loss(x_batch, d_batch)
train_loss_list.append(loss)
3-2-5-2. 学習結果
上記学習結果を表示する。
結果としては、学習率最適化手法として__モメンタム__、AdaGrad、__RMSprop__同様、学習用データの正答率、検証用データの正答率共に上がっており、活性化関数が__シグモイド関数__のままで__勾配消失問題__が解決できていることがわかる結果となった。最終的な正答率はモメンタムやAdaGrad、RMSpropと大差は無かった。
(SGD:11%、モメンタム:93%、AdaGrad:93%、RMSprop:94%、Adam:94%)
3-3.過学習
2_5_overfiting.ipynb
の過学習について演習する。
過学習の結果に対し、正則化やドロップアウトにて過学習抑制効果を検証する。
3-3-1. 過学習
__3-2.学習率最適化手法__の演習項目同様、__mnist__の手書きデータセットを使い、__MultiLayerNet__のクラスを用い演習を行う。
3-3-1-1. importとデータロード、MultiLayerNet設定
過学習を再現するため、ロードしたデータを__1000__データに削減して学習を行う。
また、ここでは、__MultiLayerNet__の設定をしている。学習最適化法としては、確率的勾配降下法(SGD)を用いる。
import numpy as np
from collections import OrderedDict
from common import layers
from data.mnist import load_mnist
import matplotlib.pyplot as plt
from multi_layer_net import MultiLayerNet
from common import optimizer
(x_train, d_train), (x_test, d_test) = load_mnist(normalize=True)
print("データ読み込み完了")
# 過学習を再現するために、学習データを削減
x_train = x_train[:1000]
d_train = d_train[:1000]
network = MultiLayerNet(input_size=784, hidden_size_list=[100, 100, 100, 100, 100, 100], output_size=10)
optimizer = optimizer.SGD(learning_rate=0.01)
3-3-1-2. 学習結果
上記学習結果を表示する。
学習データが少なく、多くのデータに対して学習が出来ていないため、学習データと検証データの正答率に差が出る__過学習__という結果になっていることがわかる。
3-3-2. L2正則化
3-3-2-1. 関数の定義の変更箇所
過学習の演習からの変更箇所のみ記載する。誤差関数にpノルム(weight_decay
)を加える計算、パラメータを更新する計算(grad['W' + str(idx)] = ・・・
)等を追加する。
正則化項を加えることで、重みの大きさを抑制、過学習を抑えることができるはずである。
- 変更前
for i in range(iters_num):
batch_mask = np.random.choice(train_size, batch_size)
x_batch = x_train[batch_mask]
d_batch = d_train[batch_mask]
grad = network.gradient(x_batch, d_batch)
optimizer.update(network.params, grad)
loss = network.loss(x_batch, d_batch)
train_loss_list.append(loss)
- 変更後
# 正則化強度設定 ======================================
weight_decay_lambda = 0.1
# =================================================
for i in range(iters_num):
batch_mask = np.random.choice(train_size, batch_size)
x_batch = x_train[batch_mask]
d_batch = d_train[batch_mask]
grad = network.gradient(x_batch, d_batch)
weight_decay = 0
for idx in range(1, hidden_layer_num+1):
grad['W' + str(idx)] = network.layers['Affine' + str(idx)].dW + weight_decay_lambda * network.params['W' + str(idx)]
grad['b' + str(idx)] = network.layers['Affine' + str(idx)].db
network.params['W' + str(idx)] -= learning_rate * grad['W' + str(idx)]
network.params['b' + str(idx)] -= learning_rate * grad['b' + str(idx)]
weight_decay += 0.5 * weight_decay_lambda * np.sqrt(np.sum(network.params['W' + str(idx)] ** 2))
loss = network.loss(x_batch, d_batch) + weight_decay
train_loss_list.append(loss)
3-3-2-2. 学習結果
上記学習結果を表示する。
過学習の結果と比べると、学習データに対する検証データの正答率の差は小さくなっていることで過学習が抑制できていることがわかる。ただし、それと合わせ学習データの正答率自身も下がっている結果となった。
- 過学習時の学習データ正答率:99.9%
- 過学習時の検証データ正答率:85.3%(差:14.6%)
- L2正則化時の学習データ正答率:81.9%
- L2正則化時の検証データ正答率:74.7%(差:7.2%)
3-4.畳み込みニューラルネットワーク
2_7_double_comvolution_network_after.ipynb
の2層の畳み込み層を含むニューラルネットワークにて演習を実施する。
今回実装する__CNN__は以下の9層構成となっている。
- Conv(フィルタ数:10, フィルタサイズ:7x7, パディング:1, ストライド:1)
- ReLU
- Conv(フィルタ数:20, フィルタサイズ:3x3, パディング:1, ストライド:1)
- ReLU
- Pooling(ウィンドウサイズ:2x2, ストライド:2, Maxプーリング)
- Affine
- ReLU
- Affine
- Softmax
3-4-1. importと関数定義
importとDoubleConvNet
関数の定義を実施する。
使用するライブラリは__numpy__や__matplotlib__等に加え、個別定義されたClassやデータロードのための関数等を読み込んでいる。
また、定義した関数はDoubleConvNet
。畳み込み層内演算のための、フィルタ数やフィルタサイズ等の設定、ネットワーク構成の順番等設定している。
import pickle
import numpy as np
from collections import OrderedDict
from common import layers
from common import optimizer
from data.mnist import load_mnist
import matplotlib.pyplot as plt
class DoubleConvNet:
# conv - relu - conv - relu - pool - affine - relu - affine - softmax
def __init__(self, input_dim=(1, 28, 28),
conv_param_1={'filter_num':10, 'filter_size':7, 'pad':1, 'stride':1},
conv_param_2={'filter_num':20, 'filter_size':3, 'pad':1, 'stride':1},
hidden_size=50, output_size=10, weight_init_std=0.01):
conv_output_size_1 = (input_dim[1] - conv_param_1['filter_size'] + 2 * conv_param_1['pad']) / conv_param_1['stride'] + 1
conv_output_size_2 = (conv_output_size_1 - conv_param_2['filter_size'] + 2 * conv_param_2['pad']) / conv_param_2['stride'] + 1
pool_output_size = int(conv_param_2['filter_num'] * (conv_output_size_2 / 2) * (conv_output_size_2 / 2))
# 重みの初期化
self.params = {}
self.params['W1'] = weight_init_std * np.random.randn(conv_param_1['filter_num'], input_dim[0], conv_param_1['filter_size'], conv_param_1['filter_size'])
self.params['b1'] = np.zeros(conv_param_1['filter_num'])
self.params['W2'] = weight_init_std * np.random.randn(conv_param_2['filter_num'], conv_param_1['filter_num'], conv_param_2['filter_size'], conv_param_2['filter_size'])
self.params['b2'] = np.zeros(conv_param_2['filter_num'])
self.params['W3'] = weight_init_std * np.random.randn(pool_output_size, hidden_size)
self.params['b3'] = np.zeros(hidden_size)
self.params['W4'] = weight_init_std * np.random.randn(hidden_size, output_size)
self.params['b4'] = np.zeros(output_size)
# レイヤの生成
self.layers = OrderedDict()
self.layers['Conv1'] = layers.Convolution(self.params['W1'], self.params['b1'], conv_param_1['stride'], conv_param_1['pad'])
self.layers['Relu1'] = layers.Relu()
self.layers['Conv2'] = layers.Convolution(self.params['W2'], self.params['b2'], conv_param_2['stride'], conv_param_2['pad'])
self.layers['Relu2'] = layers.Relu()
self.layers['Pool1'] = layers.Pooling(pool_h=2, pool_w=2, stride=2)
self.layers['Affine1'] = layers.Affine(self.params['W3'], self.params['b3'])
self.layers['Relu3'] = layers.Relu()
self.layers['Affine2'] = layers.Affine(self.params['W4'], self.params['b4'])
self.last_layer = layers.SoftmaxWithLoss()
def predict(self, x):
for key in self.layers.keys():
x = self.layers[key].forward(x)
return x
def loss(self, x, d):
y = self.predict(x)
return self.last_layer.forward(y, d)
def accuracy(self, x, d, batch_size=100):
if d.ndim != 1 : d = np.argmax(d, axis=1)
acc = 0.0
for i in range(int(x.shape[0] / batch_size)):
tx = x[i*batch_size:(i+1)*batch_size]
td = d[i*batch_size:(i+1)*batch_size]
y = self.predict(tx)
y = np.argmax(y, axis=1)
acc += np.sum(y == td)
return acc / x.shape[0]
def gradient(self, x, d):
# forward
self.loss(x, d)
# backward
dout = 1
dout = self.last_layer.backward(dout)
layers = list(self.layers.values())
layers.reverse()
for layer in layers:
dout = layer.backward(dout)
# 設定
grad = {}
grad['W1'], grad['b1'] = self.layers['Conv1'].dW, self.layers['Conv1'].db
grad['W2'], grad['b2'] = self.layers['Conv2'].dW, self.layers['Conv2'].db
grad['W3'], grad['b3'] = self.layers['Affine1'].dW, self.layers['Affine1'].db
grad['W4'], grad['b4'] = self.layers['Affine2'].dW, self.layers['Affine2'].db
return grad
3-4-3. 学習前後の重み可視化関数定義
今回も、__mnist__の手書きデータセットを使う。__CNN__による学習にて重み(CNNで言うフィルタ)がどのような画像となっているか、可視化するための関数を定義する。
この関数を使うことで、どのような__特徴マップ__で__mnist__データを推測して正答させているのかわかるようになる。
import numpy as np
import matplotlib.pyplot as plt
#from simple_convnet import SimpleConvNet
def filter_show(filters, nx=8, margin=3, scale=10):
"""
c.f. https://gist.github.com/aidiary/07d530d5e08011832b12#file-draw_weight-py
"""
FN, C, FH, FW = filters.shape
ny = int(np.ceil(FN / nx))
fig = plt.figure()
fig.subplots_adjust(left=0, right=1, bottom=0, top=1, hspace=0.05, wspace=0.05)
for i in range(FN):
ax = fig.add_subplot(ny, nx, i+1, xticks=[], yticks=[])
ax.imshow(filters[i, 0], cmap=plt.cm.gray_r, interpolation='nearest')
plt.show()
3-4-4. 学習開始
データをロードし、学習を実施する。今回は、バッチサイズを__100__、学習データ数を__5000__、検証データ数を__1000__で正答率を確認する。また、学習率最適化手法として、今回は__Adam__を使う。
- 繰り返し数 1000
# データの読み込み
(x_train, d_train), (x_test, d_test) = load_mnist(flatten=False)
print("データ読み込み完了")
# 処理に時間のかかる場合はデータを削減
x_train, d_train = x_train[:5000], d_train[:5000]
x_test, d_test = x_test[:1000], d_test[:1000]
network = DoubleConvNet(input_dim=(1,28,28),
conv_param_1={'filter_num':10, 'filter_size':7, 'pad':1, 'stride':1},
conv_param_2={'filter_num':20, 'filter_size':3, 'pad':1, 'stride':1},
hidden_size=100, output_size=10, weight_init_std=0.01)
optimizer = optimizer.Adam()
# 時間がかかるため100に設定
# iters_num = 100
iters_num = 1000
train_size = x_train.shape[0]
batch_size = 100
train_loss_list = []
accuracies_train = []
accuracies_test = []
plot_interval=10
print("### 学習前 ###")
print("重み W1")
filter_show(network.params['W1'])
print("重み W2")
filter_show(network.params['W2'])
for i in range(iters_num):
batch_mask = np.random.choice(train_size, batch_size)
x_batch = x_train[batch_mask]
d_batch = d_train[batch_mask]
grad = network.gradient(x_batch, d_batch)
optimizer.update(network.params, grad)
loss = network.loss(x_batch, d_batch)
train_loss_list.append(loss)
if (i+1) % plot_interval == 0:
accr_train = network.accuracy(x_train, d_train)
accr_test = network.accuracy(x_test, d_test)
accuracies_train.append(accr_train)
accuracies_test.append(accr_test)
print('Generation: ' + str(i+1) + '. 正答率(トレーニング) = ' + str(accr_train))
print(' : ' + str(i+1) + '. 正答率(テスト) = ' + str(accr_test))
print("### 学習後 ###")
print("重み W1")
filter_show(network.params['W1'])
print("重み W2")
filter_show(network.params['W2'])
lists = range(0, iters_num, plot_interval)
plt.plot(lists, accuracies_train, label="training set")
plt.plot(lists, accuracies_test, label="test set")
plt.legend(loc="lower right")
plt.title("accuracy")
plt.xlabel("count")
plt.ylabel("accuracy")
plt.ylim(0, 1.0)
# グラフの表示
plt.show()
3-4-5. 学習結果
繰り返し__1000__回の学習の結果、正答率推移及び、繰り返し学習前後の畳み込み層の重み(フィルタ)画像は以下のようになった。
正答率は、学習データで99.2%、検証データで95.6%と良い結果となった。また、重みの画像は、学習前のランダムな重みに比べ、学習後の画像は、白・黒のエッジや塊が明確になっているものに変化していることがわかる。
更なる畳み込み層の追加により、より詳細な特徴にも判別できる重み画像が作られると推測できる。
###関連ページ
- ディープラーニング講座「応用数学」要点まとめ
- ディープラーニング講座「機械学習」要点まとめ
- ディープラーニング講座「機械学習」実装演習
- ディープラーニング講座「深層学習:Day1」要点まとめ&実装演習
- ディープラーニング講座「深層学習:Day2」要点まとめ&実装演習<本記事>
- ディープラーニング講座「深層学習:Day3」要点まとめ&実装演習
- ディープラーニング講座「深層学習:Day4」要点まとめ&実装演習