SGDによるDeepLearningの学習
-
Upload
jang-sa-cho -
Category
Software
-
view
232 -
download
0
Transcript of SGDによるDeepLearningの学習
SGDによるDeep Learningの『学習』@ゼロから作るDeep Learning4章後半
jang
さらりとした復習
● 4章のテーマは「学習(training)」○ 学習とは、ある問題に回答するニューラルネットワークにおける最適な「重み( weight)」と「バイアス
(bias)」を見つけるプロセスのこと
※当発表とGopherくんは関係ありません
さらりとした復習
● (耳タコだけど)機械学習とディープラーニングの違い○ 機械学習……特徴量は人が与える必要がある(=めんどい、むずかしい )
○ ディープラーニング……特徴量は、訓練データとニューラルネットワークを基にした汎用的なプロセス
を経て算出される
さらりとした復習
● 学習プロセスに2種類のデータを用いる
○ 訓練データ(training data set)……学習を行うためのデータ
○ テストデータ(test data set)……学習結果の検証を行うためのデータ。学習結果の汎用性や
過学習(overfitting)の程度を検証するため、期待値は同じだが入力値の異なるデータを用いること
が好ましい
■ e.g.答えが同じ”5”のMNISTデータでも、訓練データに利用したイメージとは筆跡の異なる
データを使う
さらりとした復習
● 出力値と教師データの乖離を、損失関数を使って指標化する○ 損失関数の計算結果が大きいほど乖離している(=悪い )
○ (繰り返しになるけど)損失関数の計算結果が小さくなる「重み」と「バイアス」を探すことが
学習の目的
さらりとした復習
● 本章で紹介されている損失関数(loss function)○ 二乗和誤差(Mean Squared Error/MSE)
■
■ mse' ts ys = (/2) $ sumElements $ (**2) $ ys - ts
○ 交差エントロピー誤差(Cross-entropy Error)
■
■ cee' ts ys = negate $ sumElements $ ts * log `cmap` ys
さらりとした復習
● 訓練データを1つずつを学習させるより、複数の訓練データをまとめて学習させて、
汎用性や精度を上げたいンゴ → ミニバッチの出番!
さらりとした復習
● 訓練データ(全量)の真部分集合をまとめて計算することを『ミニバッチ』と呼ぶa. 訓練データのセットから一部を抽出してリスト生成
b. 結果をニューラルネットワークで投射
c. 結果と教師データのペアを損失関数で投射
d. a.〜c.を繰り返す
e. すべての結果を足し算で畳み込む
f. a.〜c.を繰り返した回数で按分する
● 数式で表現すると:
● このミニバッチ方式で学習することを『ミニバッチ学習』と呼ぶ
本日のメニュー
● 学習アルゴリズム○ ミニバッチの復習
○ 勾配の計算
○ パラメータ更新
● 勾配法(確率的勾配降下法/SGD)○ 微分とは
○ 微分の実装あれこれ
○ 偏微分
○ 勾配法の計算
● まとめ
本日のメニュー
● 学習アルゴリズム○ ミニバッチの復習
○ 勾配の計算
○ パラメータ更新
● 勾配法(確率的勾配降下法/SGD)○ 微分とは
○ 微分の実装あれこれ
○ 偏微分
○ 勾配法の計算
● まとめ
まだるっこしいので、Pythonの勉強がてらコードベースで話を進めましょう!!
本章のサンプルコードの置き場所
● 本章で作るプログラムのうち、メイン関数に相当するコードがこちら
● コードベースで理解できればおっけーっしょ!
○ ch04/train_neuralnet.py
https://github.com/oreilly-japan/deep-learning-from-scratch/tree/master/ch04
定義部分から眺めていく
● まず学習の手続きが始まる前の定義部分をみていく○ インポート文
○ 下準備(画像読み込み、ハイパーパラメータなどの設定)
○ クラス
インポート文
● 全体的に省略します
訓練データとテストデータを読み込む
● これもいいでしょう
(x_train, t_train), (x_test, t_test) = load_mnist(normalize=True, one_hot_label=True)
ニューラルネットワークをつくる
network = TwoLayerNet(input_size=784, hidden_size=50, output_size=10)
● 命名からして2層ネットワークっぽい○ 入力層:784○ 隠れ層:50 # 1層目のニューロン数
○ 出力層:10 # 2層目のニューロン数
ハイパーパラメータを設定する
● ハイパーパラメータ=人の手で調整する必要のある値
iters_num = 10000 # ミニバッチを繰り返す回数
train_size = x_train.shape[0] # 訓練データ全量のサイズ
batch_size = 100 # ミニバッチのサイズ
learning_rate = 0.1 # 学習率 → 後述
エポックを定義する
● 学習はミニバッチを繰り返すことで行われる
● ${ミニバッチのサイズ} * ${ミニバッチの繰り返し回数} = ${訓練データの個数}に達
するサイクルをエポックと呼ぶ○ 今回の例では訓練データの総数が 60,000個で、ミニバッチサイズが 100なので、600回周期がエ
ポックにあたる
○ ミニバッチを1,200回繰り返せば2エポック、 1800回で3エポック……
iter_per_epoch = max(train_size / batch_size, 1) # エポックの定義
ミニバッチの繰り返しとエポックのサイクル
● ミニバッチをiters_num回繰り返す
● ちなみに……42行目でエポックのサイクルが制御されている○ ハッシュするため、ゼロ除算にならないよう iter_per_epochを最低1とする配慮がなされてる(前スラ
イド参照)
iter_per_epoch = max(train_size / batch_size, 1) # エポックの定義(温かい配慮)
.
.
.if i % iter_per_epoch == 0: # 42行目
ここまでがメイン関数内の定義部分
● 次は2層ニューラルネットワークのクラス定義を説明するゾ
● メイン関数内のロジック部分は最後の〆にします
2層ネットワークをクラス定義する
class TwoLayerNet:● 振る舞いは以下を持ってるようだ(コンストラクタ除く)
def predict(self, x):def loss(self, x, t):def accuracy(self, x, t):def numerical_gradient(self, x, t):def gradient(self, x, t):
TwoLayerNetのコンストラクタ
● クラスが保持する値を理解しておこう
def __init__(self, input_size, hidden_size, output_size, weight_init_std=0.01):
● self……自己参照(余談:はじめて知ったselfの仕様に感動!)
● input_size……入力層の数っぽい
● hidden_size……隠れ層の数っぽい
● output_size……出力層の数っぽい
● weight_init_std……デフォルト値が設定されてるけど役割が謎
TwoLayerNetのコンストラクタ
● 内部実装はこんな感じ○ 連想配列に各層の重みとバイアスを格納している
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['b1'] = np.zeros(hidden_size) self.params['W2'] = weight_init_std * np.random.randn(hidden_size, output_size) self.params['b2'] = np.zeros(output_size)
TwoLayerNetのコンストラクタ
● np.random.randnは引数に与えられた行と列の数だけガウス分布を基にしたラン
ダム値を吐き出す○ このランダム値にweight_init_stdを掛けて、n-1層目とn層目のニューロンの組み合わせに与える
『重み』にしているようだ
○ ちなみにバイアス値はすべて 0とされている
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['b1'] = np.zeros(hidden_size) self.params['W2'] = weight_init_std * np.random.randn(hidden_size, output_size) self.params['b2'] = np.zeros(output_size)
TwoLayerNetのpredictメソッド
● 3章で習ったニューラルネットワークの推論処理の実装ですね○ 活性化関数は入力層 -> 1層目でシグモイド、1層目 -> 出力層でソフトマックス
def predict(self, x): W1, W2 = self.params['W1'], self.params['W2'] b1, b2 = self.params['b1'], self.params['b2']
a1 = np.dot(x, W1) + b1 z1 = sigmoid(a1) a2 = np.dot(z1, W2) + b2 y = softmax(a2) return y
TwoLayerNetのlossメソッド
● predictの計算結果から損失関数(交差エントロピー誤差)の計算をしている
def loss(self, x, t): y = self.predict(x) return cross_entropy_error(y, t)
TwoLayerNetのaccuracyメソッド
● 認識精度を計算している○ 以下の汚いコメント参照
def accuracy(self, x, t): y = self.predict(x) y = np.argmax(y, axis=1) # NL計算結果の各配列2次元目の最大値の添字
t = np.argmax(t, axis=1) # 同じく教師データ版の添字
accuracy = np.sum(y == t) / float(x.shape[0]) # 添字が一致=正解扱い
# 正解数 ÷ 訓練データ数=認識精度
return accuracy
TwoLayerNetのnumerical_gradientメソッド
def numerical_gradient(self, x, t):
loss_W = lambda W: self.loss(x, t)
grads = {}
grads['W1'] = numerical_gradient(loss_W, self.params['W1'])
grads['b1'] = numerical_gradient(loss_W, self.params['b1'])
grads['W2'] = numerical_gradient(loss_W, self.params['W2'])
grads['b2'] = numerical_gradient(loss_W, self.params['b2'])
return grads
なにこれ? → numerical_gradient
● 答え:与えられた座標における勾配を計算する関数○ 勾配の計算には偏微分が使われる
■ 本章の偏微分の実装には数値微分が使われている
● 数値微分とは、微分の計算方法(アプローチ)のひとつ
高校で数学を捨てた人間の反応
なにこれ? → numerical_gradient
● 答え:与えられた座標における勾配を計算する関数○ 勾配の計算には偏微分が使われる
■ 本章の偏微分の実装には数値微分が使われる
● 数値微分は、微分の実装方法のひとつ
下の用語から説明していくンゴよ
微分(differential)とは?
● ここから少し教科書に戻ってみる
● 微分の定義:
● 極限をプログラムで書くのか……ゴクリ
● あるいは数式を文字列にして解析……
→ 回避方法があるらしい
数値微分:厳密→ベストエフォートへ
● 微分の定義を緩くする:
● これが数値微分(numerical differentiation)
● これなら実装できそうだ!
○ ⊿xを小さい値にするほど、厳密な意味の微分に近づけることができる
○ 極力小さい値 = 丸め誤差の影響を受けない程度に小さい値
■ 余談だがPython標準のfloatはC/Javaでいうdouble相当の精度らしい
■ Numpyにもっと高精度の floatも実装されている: float128
数値微分:ベストエフォート+αへ
● 『ベスト』ってなんだよ(哲学)
● 中心差分を使えばもっと精度が出る:
要はf(x)からf(x+⊿x)の間の傾きと、f(x)からf(x-⊿x)の間の傾きを平均化したもの
f(x-⊿x)
f(x+⊿x)
gradient.pyのnumerical_gradientdef numerical_gradient(f, x): h = 1e-4 # 数値微分の⊿xに相当する値
grad = np.zeros_like(x) it = np.nditer(x, flags=['multi_index'], op_flags=['readwrite']) while not it.finished: # xの要素数だけループさせる
idx = it.multi_index tmp_val = x[idx] x[idx] = float(tmp_val) + h fxh1 = f(x) # f(x+⊿x) x[idx] = tmp_val - h fxh2 = f(x) # f(x-⊿x) grad[idx] = (fxh1 - fxh2) / (2*h) # 中心差分を取得する
x[idx] = tmp_val it.iternext()
return grad
偏微分(partial differential)とは?
● こいつは初見……
● アリティ2の関数があったとする:
● x0に関する偏微分:
○ なんやこのグロ記号……
偏微分(partial differential)とは?
● 偏微分とは……
複数の変数を備える関数に対して、微分対象の変数以外を定数で固定し、変数1
つずつを微分する手法
● 定数を微分すると結果は”0”、ってことは……
● のとき、x0を偏微分(=x1を固定)する場合、
● となる
● もうグロくない
勾配(gradient)とは?
● N個の変数を持つ関数に偏微分を行って、N次元のベクトルを得る
このベクトルが『勾配』である
-2.0〜2.0を0.25刻みで格子点を取り、
各点の勾配を視覚化した図 →
TwoLayerNetのnumerical_gradientメソッド(再)
仮実装のラムダ式でgrads[*]の勾配が取れてる(?)理由を調べ切れなかった 分かる人、だれか……
if numerical_gradient(self, x, t):
# 以下のloss_Wは仮実装# 本来的な実装としては偏微分するために最低4個の引数を持つラムダ式を置くべき# その内訳は『W1』『W2』『b1』『b2』 loss_W = lambda W: self.loss(x, t)
grads = {} grads['W1'] = numerical_gradient(loss_W, self.params['W1']) # 入力層 -> 隠れ層の重みの勾配 grads['b1'] = numerical_gradient(loss_W, self.params['b1']) # 入力層 -> 隠れ層のバイアスの勾配 grads['W2'] = numerical_gradient(loss_W, self.params['W2']) # 隠れ層 -> 出力層の重みの勾配 grads['b2'] = numerical_gradient(loss_W, self.params['b2']) # 隠れ層 -> 出力層のバイアスの勾配
return grads
TwoLayerNetのgradientメソッド
● 誤差逆伝播法で実装した版らしい○ 本章で取り上げている順伝播(数値微分版)よりも、むっちゃ速い
● 次章ご担当の方、解説オナシャス!
TwoLayerNetのクラス定義はここまで
● ここからメイン関数に戻る
ミニバッチのイテレーション部分@25〜46行目
for i in range(iters_num): batch_mask = np.random.choice(train_size, batch_size) x_batch = x_train[batch_mask] t_batch = t_train[batch_mask] # 勾配の計算
grad = network.numerical_gradient(x_batch, t_batch) #grad = network.gradient(x_batch, t_batch) # パラメータの更新
for key in ('W1', 'b1', 'W2', 'b2'): network.params[key] -= learning_rate * grad[key] loss = network.loss(x_batch, t_batch) train_loss_list.append(loss) if i % iter_per_epoch == 0: train_acc = network.accuracy(x_train, t_train) test_acc = network.accuracy(x_test, t_test) train_acc_list.append(train_acc) test_acc_list.append(test_acc) print("train acc, test acc | " + str(train_acc) + ", " + str(test_acc))
ミニバッチのイテレーション部分
for i in range(iters_num): batch_mask = np.random.choice(train_size, batch_size) x_batch = x_train[batch_mask] t_batch = t_train[batch_mask] # 勾配の計算
grad = network.numerical_gradient(x_batch, t_batch) #grad = network.gradient(x_batch, t_batch) # パラメータの更新
for key in ('W1', 'b1', 'W2', 'b2'): network.params[key] -= learning_rate * grad[key] loss = network.loss(x_batch, t_batch) train_loss_list.append(loss) if i % iter_per_epoch == 0: train_acc = network.accuracy(x_train, t_train) test_acc = network.accuracy(x_test, t_test) train_acc_list.append(train_acc) test_acc_list.append(test_acc) print("train acc, test acc | " + str(train_acc) + ", " + str(test_acc))
この辺は1エポック毎のデータを収集してるだけなので省くよ
この辺は学習成果の確認(損失関数の計算結果の履歴取得)なので省くよ
ミニバッチのイテレーション部分
for i in range(iters_num): batch_mask = np.random.choice(train_size, batch_size) x_batch = x_train[batch_mask] t_batch = t_train[batch_mask] # 勾配の計算
grad = network.numerical_gradient(x_batch, t_batch) #grad = network.gradient(x_batch, t_batch) # パラメータの更新
for key in ('W1', 'b1', 'W2', 'b2'): network.params[key] -= learning_rate * grad[key]
ミニバッチのイテレーション部分
for i in range(iters_num): batch_mask = np.random.choice(train_size, batch_size) x_batch = x_train[batch_mask] t_batch = t_train[batch_mask] # 勾配の計算
grad = network.numerical_gradient(x_batch, t_batch) #grad = network.gradient(x_batch, t_batch) # パラメータの更新
for key in ('W1', 'b1', 'W2', 'b2'): network.params[key] -= learning_rate * grad[key]
ミニバッチの訓練&テストデータを抽出する
● 訓練データ総数(60,000個)から無作為にミニバッチサイズ(100個)を選んでいる
batch_mask = np.random.choice(train_size, batch_size) x_batch = x_train[batch_mask] t_batch = t_train[batch_mask]
勾配を計算する
● TwoLayerNetのメソッドを使って勾配を計算しているだけ
● ここまで至って平和
# 勾配の計算
grad = network.numerical_gradient(x_batch, t_batch) #grad = network.gradient(x_batch, t_batch)# 公式サンプルでは後者がデフォ
# ただし後者は数値微分ではなく、誤差逆伝播による実装を行っているので注意
学習する
● ついに来ました
● イテレーションの締め括りに、TwoLayerNetのコンストラクタで宣言されていた『重
み』『バイアス』の値を書き換えている
= まさに学習させている箇所じゃないか!!
● ここは『勾配法(gradient method)』を使って学習させている
● 『勾配法』ってなんぞ? → 次のスライドへ
# パラメータの更新
for key in ('W1', 'b1', 'W2', 'b2'): network.params[key] -= learning_rate * grad[key] # ココ!
● より小さい/大きい値を探索するときに『傾き』をコンパスにする手法○ 傾きが正の値=座標を左にズラす必要がある=負の方向を探索方向とする (正負逆でも同様 )
● 曲線に沿ってボールを移動していき、落ち着ける場所を探すイメージ○ ただし極小値/極大値に囚われ、最小値/最大値を見つけられない状況に陥る可能性もある
○ 最小値や極小値のようなボールが落ち着ける位置を『鞍点』、特に最小値でない鞍点にハマること
を『プラトー』と呼ぶ。これを避けるように『学習率』を補正値として調整する。
勾配法(gradient method)とは?
← プラトー
● ハイパーパラメータのスライドで説明を先送りにされたアイツ
○ learning_rate = 0.1● ${学習率} * ${勾配(=傾きベクトル)} * (-1) = ${重みの更新差分}
○ 以下のグラフは、例えば横軸が W1、縦軸が損失関数の計算結果 loss(W)○ 次のステップまでにボールの中心軸を動かす度合いに相当する
学習率(learning rate)とは?
数バッチ後……
青の差分が${重みの更新差分 }
● つまり学習率は、次のミニバッチに採用するニューラルネットワークの『重み』を決
定する計算に利用されている
● その意味するところは、偏微分の計算結果(=勾配)を『重み』に反映させる割合で
ある○ 学習率を1.0とすると、勾配の各点の傾きを等倍で重みから差っ引くことになる
for key in ('W1', 'b1', 'W2', 'b2'): network.params[key] -= learning_rate * grad[key] # network = 先に解説した2層NLのインスタンス
学習率(learning rate)とは?
学習率はボールの『勢い』
● 勾配法をボール転がしに例えると、ボール学習率はボールを転がす『勢い』に相当
する
● 勾配法をミニバッチで繰り返すイメージは以下のような感じ
ボールの位置の傾きを調べる
→ 傾きに沿って一定の勢い(=学習率 * 傾きベクトル)で発射
→ ボールの位置の傾きを調べる
→ 傾きに沿って一定の勢いで発射
→ (繰り返し)
● ボールの中心軸は極小値にすら至らず、学習処理が終わってしまう
=勢いが無さ過ぎた……
学習率が小さすぎると?
10,000バッチ後……
● プラトーの壁を破った! いけるやん!
学習率が大きすぎると?
1バッチ後……
● おや?
学習率が大きすぎると?
さらに1バッチ後……
● 学習率が大きすぎると発散してしまう……
=勢いが良すぎた……
学習率が大きすぎると?
さらに1バッチ後……
初期位置より最小値から遠くへ……
● 最小値に向けて収束していく学習率=良い学習率○ 理想は傾きゼロの位置=最小値( global minimum)にカップインできること
○ ピッタリな位置に着地させるのは難しいですね ……
適切な学習率
● W1、b1、W2、b2にそれぞれ補正を加えている(=学習している)
for key in ('W1', 'b1', 'W2', 'b2'): network.params[key] -= learning_rate * grad[key] # network = 先に解説した2層NLのインスタンス
学習する
メイン関数の残りは省略
● グラフ描画とか本質から外れてるコードなんで
確率的勾配降下法(Stochastic Gradient Descent/SGD)
● 本章で紹介されたアルゴリズムを確率的勾配降下法(SGD)と呼ぶ○ 確率的?
■ 訓練データ総量からバッチサイズの個数データを無作為に取得したことから
○ 勾配降下法?
■ 最小値を探す勾配法のことを『勾配降下法( Gradient Descent Method)と呼ぶ
○ ゆえに『確率的勾配降下法』と呼ぶ
まとめ
1. 学習プログラムの流れa. 下準備
i. 訓練&テストデータを用意する
ii. NLを作る
iii. ハイパーパラメータを宣言する ……)b. ミニバッチを繰り返す
i. 今回バッチ用のデータを選ぶ
ii. (推論処理をする)
iii. (損失関数を計算する)
iv. 勾配を計算する
v. 勾配法で学習する
※グラフ描画関係の処理は省略してます
おまけ:ミニバッチの適正サイズについて
● 個人的な疑問
● 機械的にサイズを決める方法はないのだろうか?○ https://goo.gl/ywVEDr