Day 2:
Day 2のまとめ->Day 2の問題演習->Day 2の実装演習のサマリーの考察という順番
【勾配消失問題について】
【活性化関数】
Relu:
今最も使われている活性化関数勾配消失問題の回避とスパース化に貢献することで良い成果をもたらしている。
Xavier:
【初期値の設定方法】
重みの要素を、前の層のノード数の平方根で除算した値
【Xavierの初期値を設定する際の活性化関数】
・ReLU関数
・シグモイド(ロジスティック)関数
・双曲線正接関数
network['W1'] = np.random.randn(input_size, hidden_size) / np.sqrt(input_layer_size)
network['W2'] = np.random.randn(hidden_size, output_size) / np.sqrt(hidden_layer_size)
He:
【初期値の設定方法】
重みの要素を、前の層のノード数の平方根で除算した値に対し√2をかけ合わせた値
【Heの初期値を設定する際の活性化関数】
・ReLU関数
適用活性化関数: He
network['W1'] = np.random.randn(input_size, hidden_size) / np.sqrt(input_layer_size)
network['W2'] = np.random.randn(hidden_size, output_size) / np.sqrt(hidden_layer_size)
【バッチ正規化】
・ミニバッチ単位で、入力値のデータの偏りを抑制する手法
・バッチ正規化の使い所とは
・活性化関数に値を渡す前後に、バッチ正規化の処理を孕んだ層を加える
【学習率最適化手法について】
・初期の学習率設定方法の指針・初期の学習率を大きく設定し、徐々に学習率を小さくしていく
・パラメータ毎に学習率を可変させる学習率最適化手法を利用して学習率を最適化
【モメンタム】
誤差をパラメータで微分したものと学習率の積を減算した後、現在の重みに前回の重みを減算した値と慣性の積を加算する
モメンタムのメリット: 局所的最適解にはならず、大域的最適解となる。谷間についてから最も低い位置(最適値)にいくまでの時間が早い。
self.v[key] = self.momentum* self.v[key] -self.learning_rate* grad[key]
params[key] += self.v[key]
【AdaGrad】
誤差をパラメータで微分したものと再定義した学習率の積を減算する。
AdaGradのメリット:勾配の緩やかな斜面に対して、最適値に近づける
AdaGradの問題点:課題学習率が徐々に小さくなるので、鞍点問題を引き起こす事があった
self.h[key] = np.zeros_like(val)
self.h[key] += grad[key] * grad[key]
params[key] -= self.learning_rate* grad[key] / (np.sqrt(self.h[key]) + 1e-7)
【RMSProp】
誤差をパラメータで微分したものと再定義した学習率の積を減算する
RMSPropのメリット: 局所的最適解にはならず、大域的最適解となる。ハイパーパラメータの調整が必要な場合が少ない。
self.h[key] = self.decay_rate
self.h[key] += (1 -self.decay_rate) * grad[key] * grad[key]
params[key] -= self.learning_rate grad[key] / (np.sqrt(self.h[key]) + 1e-7)
【Adam】
モメンタムの、過去の勾配の指数関数的減衰平均・RMSPropの、過去の勾配の2乗の指数関数的減衰平均上記をそれぞれ孕んだ最適化アルゴリズムである。
Adamのメリット: モメンタムおよびRMSPropのメリットを孕んだアルゴリズムである。
【過学習について】
【正則化とは】
ネットワークの自由度(層数、ノード数、パラメータの値etc...)を制約すること。
正則化手法を利用して過学習を抑制する
Weight Decay (荷重減衰)
過学習の原因n重みが大きい値をとることで、過学習が発生することがある。誤差に対して、正則化項を加算することで、重みを抑制する。過学習がおこりそうな重みの大きさ以下で重みをコントロールし、かつ重みの大きさにばらつきを出す必要がある。
【L1 / L2 正則化】
\| {\bf x} \|_p =( |x_1|^p +..... + |x_n|^p)^{1/p}
p=1の場合、L1正規化と呼ぶ
p=2の場合、L2正規化と呼ぶ
【ドロップアウト】
ランダムにノードを削除して学習させること。過学習に対して有効。
正則化の中でも多く使われる手法。
【畳み込みニューラルネットワークの概念】
【畳み込み層】
画像の場合、縦、横、チャンネルの3次元のデータをそのまま学習し、次に伝えることができる。結論:3次元の空間情報も学習できるような層が畳み込み層である。
【パディング】
画像の周りに値を埋める方法。特に0が画像の周りに埋めらる。この方法を特にゼロパディング と言う。この方法により、入力のShapeと出力のShapeを一致させることができます。
【プーリング層】プーリング層では、特徴を残しながら情報量を削減する。
マックスプーリング: 対象領域のMax値を取得。
あベレージプーリング: 対象領域の平均値を取得。
【AlexNet】2012年の ILSVRCで優勝したモデル。
モデルの構造5層の畳み込み層およびプーリング層など、それに続く3層の全結合層から構成される。過学習を防ぐ施策・サイズ4096の全結合層の出力にドロップアウトを使用している。
確認テスト
(問題)連鎖律の原理を使い、dz/dxを求めよ。$z=t^{2}$, $t = x + y$
(答)$dz/dx = ∂z/∂t∂t/∂x = 2t = 2(x+y)$
(問題)シグモイド関数を微分した時、入力値が0の時に最大値をとる。その値として正しいものは?
(答)シグモイド関数をσ(x)とおくと、σ'(x) = σ(x)(1-σ(x))になる。よって、
σ'(x) = σ(0)(1-σ(0)) = 0.25
(問題)重みの初期値に0を設定すると、どのような問題が発生するか。簡潔に説明せよ。
(答)全ての重みの値が均一に更新される。たくさんの重みを持つ意味がなくなる。
(問題)一般的に考えられるバッチ正規化の効果を2点挙げよ。
(答)計算が高速化される。勾配消失が起こりづらくなる。
(問題)モメンタム、AdaGrad、RMSPropの特徴をそれぞれ簡潔に説明せよ。
(答)
モメンタム: 局所的最適解にはならず、大域的最適解が得られやすい。谷間についてから最も低い位置にいくまでの時間が早い。
AdaGrad: 勾配の緩やかな斜面に対して、最適値に近づける。学習率が徐々に小さくなるので、鞍点問題を引き起こす場合がある。
RMSProp: ハイパーパラメータの調整が必要ないことが多い。局所的最適解にはならず、大域的最適解になる。
(問題)機械学習で使われる線形モデル(線形回帰、主成分分析・・・etc)の正則化は、モデルの重みを制限することで可能となる。前述の線形モデルの正則化手法の中にリッジ回帰という手法があり、その特徴として正しいものを選択しなさい。
(答)(a) ハイパーパラメータを大きな値に設定すると、すべての重みが限りなく0に近づく。ハイパーパラメータを大きくすると、罰則項が大きくなるのがポイント。
(問題)下図について、L1正則化を表しているグラフはどちらか答えよ。
(答)右側。スパース推定を表す。
(問題)サイズ6×6の入力画像を、サイズ2×2のフィルタで畳み込んだ時の出力画像のサイズを答えよ。なおストライドとパディングは1とする。
(答)サイズ6×6の入力画像を、サイズ2×2のフィルタ畳み込んだ時の出力画像のサイズはストライド1で5×5。パディングすると、一辺が 5 + 1 x 2 = 7となる。出力画像サイズは 7 x 7
実装演習のサマリーの考察という順番
# ReLU layer
class Relu:
def __init__(self):
self.mask = None
def forward(self, x):
# mask.shape = x.shape
# True or Falseを要素として持つ
self.mask = (x <= 0)
out = x.copy()
# Trueの箇所を0にする
out[self.mask] = 0
return out
def backward(self, dout):
# Trueの箇所を0にする
dout[self.mask] = 0
dx = dout
return dx
# Affine layer(全結合 layer)
class Affine:
def __init__(self, W, b):
self.W =W
self.b = b
self.x = None
self.original_x_shape = None
# 重み・バイアスパラメータの微分
self.dW = None
self.db = None
def forward(self, x):
out = np.dot(self.x, self.W) + self.b
return out
def backward(self, dout):
dx = np.dot(dout, self.W.T)
self.dW = np.dot(self.x.T, dout)
self.db = np.sum(dout, axis=0)
return dx
outには、0以下の数字も、0より大きい数字も入っていて、xが例えば3だと、self.maskはFalse, xが例えば-2だとTrueになって、out[self.mask] = 0で、outの中で0のものと、0より大きいものだけになるから、outは0以上のものとなる。これでoutがxの集まりの部分集合となった。
dout[self.mask] = 0は、dが付いているから、微分であろうか?関数も集合も似たようなものだから、先程と同じように集合で考えると、doutという集合は、Trueの箇所を0にする、すなわち、0以下なものを0とするような集合。0より大きいものは、そのまま何もしない。
次に見ていくと、dout = ∂L/∂y (y = xW + b)と判明した。
dx = np.dot(dout, self.W.T)
self.dW = np.dot(self.x.T, dout)
self.db = np.sum(dout, axis=0)
ここで、次のように定める。
y = xW + b
out = y
dout = ∂L/∂y
dx = ∂L/∂x
dW = ∂L/∂W
db = ∂L/∂b とおくと、
$dx$ = $∂L/∂x$ = $∂L/∂y∂y/∂x$ = $∂L/∂y$$W^{T}$
= np.dot(dout, self.W.T)
dW = ∂L/∂W = ∂L/∂y∂y/∂W = $x^{T}$∂L/∂y = np.dot(self.x.T, dout)
db = ∂L/∂y∂y/∂b = ∂L/∂y = dout -> dbは各成分の和
# Affine layer(全結合 layer)
class Affine:
def __init__(self, W, b):
self.W =W
self.b = b
self.x = None
self.original_x_shape = None
# 重み・バイアスパラメータの微分
self.dW = None
self.db = None
def forward(self, x):
out = np.dot(self.x, self.W) + self.b
return out
def backward(self, dout):
dx = np.dot(dout, self.W.T)
self.dW = np.dot(self.x.T, dout)
self.db = np.sum(dout, axis=0)
return dx
最後の部分の、backward(self, dout)は、∂L/∂yを引数にとり、∂L/∂xを返す。
class TwoLayerNet:
'''
input_size: 入力層のノード数
hidden_size: 隠れ層のノード数
output_size: 出力層のノード数
weight_init_std: 重みの初期化方法
'''
def __init__(self, input_size, hidden_size, output_size, weight_init_std = 0.01):
# 重みの初期化
self.params = {}
self.params['W1'] = weight_init_std * np.random.randn(input_size, hidden_size)
self.params['W2'] = weight_init_std * np.random.randn(hidden_size, output_size)
self.params['b1'] = np.zeros(hidden_size)
self.params['b2'] = np.zeros(output_size)
# レイヤの生成
self.layers = OrderedDict()
self.layers['Affine1'] = layers.Affine(self.params['W1'], self.params['b1'])
self.layers['Relu1'] = layers.Relu()
self.layers['Affine2'] = layers.Affine(self.params['W2'], self.params['b2'])
self.lastLayer = layers.SoftmaxWithLoss()
# 順伝播
def predict(self, x):
for layer in self.layers.values():
x = layer.forward(x)
return x
# 誤差
def loss(self, x, d):
y = self.predict(x)
return self.lastLayer.forward(y, d)
# 精度
def accuracy(self, x, d):
y = self.predict(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
# 勾配
def gradient(self, x, d):
# forward
self.loss(x, d)
# backward
dout = 1
dout = self.lastLayer.backward(dout)
layers = list(self.layers.values())
layers.reverse()
for layer in layers:
dout = layer.backward(dout)
# 設定
grad = {}
grad['W1'], grad['b1'] = self.layers['Affine1'].dW, self.layers['Affine1'].db
grad['W2'], grad['b2'] = self.layers['Affine2'].dW, self.layers['Affine2'].db
return grad
要点: そもそもなぜ、逆伝播で∂L/∂yを更新できるのだろうか?
$dx$ = $∂L/∂x$ = $∂L/∂y∂y/∂x$ = $∂L/∂y$$W^{T}$
= np.dot(dout, self.W.T)
という式より、∂L/∂yと、Wが分かれば、∂L/∂xは求まる。これを∂L/∂yと置けば、どんどん更新できる。
これで一通り理解できたが、このコードはかなり洗練されている。
Vanishing gradient
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("データ読み込み完了")
# 重み初期値補正係数
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
# 初期設定
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
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]
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()
訓練データとテストデータの正答率は相当低い。20%未満。print(grad)で確かめて見ると、勾配が消失していることが分かった。中間層の活性化関数が、sigmoidなのも良くないだろう。
import numpy as np
from data.mnist import load_mnist
from PIL import Image
import pickle
from common import functions
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("データ読み込み完了")
# 重み初期値補正係数
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
# 初期設定
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.relu
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_relu
(x_train, d_train), (x_test, d_test) = load_mnist(normalize=True, one_hot_label=True)
train_size = len(x_train)
normalize は、入力画像を正規化する。one_hot_label は、ラベルを one-hot表現に変えてくれる。pickleは、オブジェクトをファイルとして保存する機能。
hidden_f = functions.sigmoidをhidden_f = functions.reluに、
hidden_d_f = functions.d_sigmoidをhidden_d_f = functions.d_reluに変える。
中間層の活性化関数をreluに変えるだけで、訓練データとテストデータの正答率は90%以上に上がった。
ReLU - He
import numpy as np
from data.mnist import load_mnist
from PIL import Image
import pickle
from common import functions
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("データ読み込み完了")
# 重み初期値補正係数
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
# 初期設定
def init_network():
network = {}
########### 変更箇所 ##############
# 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)
#################################
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.relu
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_relu
# 出力層でのデルタ
delta3 = functions.d_softmax_with_loss(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
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]
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()
Heの初期値を設定すると、テストデータの正解率が上がっている。過学習はないようだ。それから特徴的なのが、少ないcountで訓練データとテストデータの正解率が上がるところ。つまり学習の進みが速い。これからnp.sqrt(2)を除いた、Xavierの初期値は、Heの初期値と大して変わらないようである。少ないcountで訓練データとテストデータの正解率が上がるところも似ている。
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)
2_2_2_vanishing_gradient_modified
network = MultiLayerNet(input_size=784, hidden_size_list=[40, 20], output_size=10, activation='relu', weight_init_std='He')
で、weight_init_std='He'をweight_init_std='Xavier'に変えてみたが、訓練データとテストデータの正解率はあまり変化がなかった。そこで、hidden_size_list=[4, 2]に変えてみたところ、正解率は、HeもXavierも訓練データとテストデータで40%ぐらいと落ち込んだが、両者に差が見られた。Xavierのテストデータの正解率は42.0%であり、Xavierのテストデータの正解率は45.81%であり。このことから、条件次第では、両者の性能に違いがみられる可能性がある。
2_3_batch_normalization.ipynb
network = MultiLayerNet(input_size=784, hidden_size_list=[40, 20], output_size=10,
activation='sigmoid', weight_init_std='Xavier', use_batchnorm=True)
use_batchnorm=Trueから、use_batchnorm=Falseに変えると、テストデータの正答率が、80.83%から、21.38%に落ち込んだ。use_batchnorm=Trueのままで、
weight_init_std='Xavier'からweight_init_std='He'に変えると、テストデータの正答率が、79.81%になった。あまり差はない。このままの条件で、活性化関数をReluに変えると、テストデータの正答率がXavierで91.92%、Heで89.03%になった。
2_4_optimizer_after.ipynb
SGD, Momentum, AdaGrad, RMSProp, Adamといった最適化手法を下記の条件で試した。テストデータの正答率は、SGDだけ他より2%ほど低く、他のものは、96%台であった。学習の進み速さに関しても、SGDだけが遅かった。他のものは同じぐらいだった。そこでSGDについては特別に、weight_init_stdにHeやXavierを入れてみたが、性能の向上は見られなかった。
network = MultiLayerNet(input_size=784, hidden_size_list=[40, 20], output_size=10, activation='relu', weight_init_std=0.01,
use_batchnorm=True)
2_5_overfiting.ipynb
weight_decay += 0.5 * weight_decay_lambda * np.sqrt(np.sum(network.params['W' + str(idx)] ** 2))
L2正則化の罰則項である。weight_decay_lambdaは、少しの変化でパフォーマンスが大きく変化することがわかった。調整が難しい。そもそも、ドロップアウトは過学習を防ぐものなので、テストデータの正答率が低い時に、dropout_ratioに数字を入れると、更に正答率が低くなる。L1正則化についてもやってみたが、訓練データの正答率が100%近くになるようなドロップアウト率は避けるべきであるが、最適なドロップアウト率を探すのが意外に難しい。
2_6_simple_convolution_network_after.ipynb
im2col: 多次元配列データを、二次元配列にする。それにより行列演算が出来るようになる。
参考:
im2col関数をどう実現するか
out_h = (H + 2 * pad - filter_h)//stride + 1
out_w = (W + 2 * pad - filter_w)//stride + 1
img = np.pad(input_data, [(0,0), (0,0), (pad, pad), (pad, pad)], 'constant')
col = np.zeros((N, C, filter_h, filter_w, out_h, out_w))
パディングはpad関数を使う。一次元目、二次元目はパディングしない。これは下の例題を見れば、一発だ。三次元目はout_hに対応するので、上下に0でパディングする。4次元目は、out_wに対応するので、横方向に0でパディングする。
a = [1, 2, 3, 4, 5]
np.pad(a, (2, 3), 'constant', constant_values=(4, 6))
でarray([4, 4, 1, ..., 6, 6, 6])が出力する。
2_7_double_comvolution_network_after.ipynb
self.layers = OrderedDict()
self.layers['Conv1'] = layers.Convolution(self.params['W1'], self.params['b1'], conv_param_1['stride'], conv_param_1['pad'])
layers.Convolution(self.params['W1'], self.params['b1'], conv_param_1['stride'], conv_param_1['pad'])は順序付き辞書のvalue
だが、grad['W1'] = self.layers['Conv1'].dWを利用して、W1に対応する、dW1つまり、grad['W1']が出るような辞書を作っている。
2_8_deep_convolution_net.ipynb
# 重みの初期化===========
# 各層のニューロンひとつあたりが、前層のニューロンといくつのつながりがあるか
pre_node_nums = np.array([1*3*3, 16*3*3, 16*3*3, 32*3*3, 32*3*3, 64*3*3, 64*4*4, hidden_size])
wight_init_scales = np.sqrt(2.0 / pre_node_nums) # Heの初期値
最後が64*4*4になってるのは、input_dim=(1, 28, 28)でプーリング層が3つあるので、
プーリング層を通るたびに、28->14->7->4とサイズが小さくなるため。
W1 - W8まであって、W6までがconv層で、残り2層が、affine層。実装は、今までのところをしっかり勉強してれば、ここは難しくない。