konchangakita

KPSを一番楽しんでいたブログ 会社の看板を背負いません 転載はご自由にどうぞ

【DeepLearning特訓】MLPの基礎 バッチ正規化(Batch Normalization)

E資格向けの自習アウトプット
自分用メモ

Batch Normalization(Batch Norm)は、ミニバッチ単位で正規化を行いスケールを揃えること。重みの初期値が適切だと、各層のアクティベーション分布に適切な広がりをもち、学習がスムーズに行える 
Batch Norm は、各層のアクティベーション分布を適度な広がりを持つように調整することにある
Batch Norm のレイヤをアフィン変換と活性化関数の間に挿入する

【Batch Norm のメリット】
・学習を速く進行させることができる
・初期値にそれほど依存しない
過学習を抑制する

f:id:konchangakita:20210110183724p:plain

Batch Normalization のアルゴリズム

ミニバッチの単位で正規化する
1.入力 x の平均をとる
f:id:konchangakita:20210110175635p:plain

2.分散をもとめ
f:id:konchangakita:20210110175659p:plain

3.正規化する
f:id:konchangakita:20210110180615p:plain

Batch Norm を計算グラフであらわすと

こんな感じでよいのだろうか
f:id:konchangakita:20210111154320p:plain

まとめ

一旦 MLPの基礎としてはここまで
これからは画像認識や自然言語処理強化学習など、いよいよディープラーニングの本番に突入

【DeepLearning特訓】MLPの基礎 学習のいろいろな工夫

E資格向けの自習アウトプット
自分用メモ

学習の工夫して、汎化性能をあげつつ学習速度の向上、計算リソースの削減をしたり対策していく
取り組む問題によって、組み合わせてみたり

パラメータ拘束とパラメータ共有

パラメータ拘束

パラメータの適切な値がわからない場合は、近いモデルを参考にすると良いのでは、という考え方
互いのパラメータの近さを表現する手法
L2正則化(重み減衰)で、0から遠ざかることに対してペナルティをかける

例として、タスクが似ている2つのモデルA, Bは、パラメータも近いと想定できる
下記をノルムペナルティとする
f:id:konchangakita:20210110002857p:plain
(L2ノルム以外でも可能)
この値に上限を設けて、互いにパラメータ拘束する

パラメータ共有

複数のモデルである部分のパラメータを同じにする
メモリの節約にもなる
CNNのフィルタが典型的な例

アンサンブル学習

複数のモデル(弱学習器)で個々に学習させる

バギング(bootstrap aggregation)

複数モデルを組み合わせ汎化誤差を減少させる
有名どころ:ランダムフォレスト
f:id:konchangakita:20210110013235p:plain

ブースティング

確率分布に基づいて分割、逐次計算していく
f:id:konchangakita:20210110013309p:plain

ドロップアウトとドロップコネクト

ドロップアウト

中間層での出力をランダムで削除(0にする)する。削除する割合はハイパーパラメータとして設定
計算量が少なく、非常に実用性が高い
訓練データが少なすぎるとダメ

# ドロップアウト
# まずはマスクを作る
ratio = 0.2 # 20% 削除
x = np.random.randn(3,10)
randammatrix = np.random.rand(*x.shape)
mask = randammatrix > ratio

# 出力を0にする
x = x * mask

# 勾配は同じマスクする
dout = dout * mask
ドロップコネクト

中間層の重みをランダムに0、性能面ではドロップアウトより優れているが、乱数の値で同じ性能を出す難しいので、実用的でない

その他の工夫

ノイズの注入
スパース表現:ベクトルの多くを0にする
 ・L1正則化:重みをスパース
 ・ReLU:表現をスパース
教師あり学習
 ・最初の段階では教師あり学習、そのあと教師なし学習に移行
マルチタスク学習
 ・タスクの類似性を活かしながら、全タスクを同時に解く。
蒸留
 ・教師モデルの入出力を用いて、軽量な生徒モデルで学習
 ・精度は落ちるが、計算リソースの削減
早期終了
プルーニング
 ・貢献度の低い重みを枝切り

まとめ

実用性的には、ドロップアウトが多いのかな
実際のところはいろんなフレームワークに組み込まれていたりするので、関数呼び出すだけで使えることが多い
ここは試験対策用に用語まとめ的な意味合い
次は、正規化(正則化は前回やった)

【DeepLearning特訓】MLPの基礎 正則化

E資格向けの自習アウトプット
自分用メモ

正則化は、過学習(学習データに特化しすぎる)を防ぎ、汎化性能と予測
精度の向上を両立させる処理
正則化と正規化はこんがらがりがち)

バイアスとバリアンス

バイアスとバリアンスは推定量の誤差を生じる2つの発生源を測定するもの
バイアス:関数やパラメータの真の値からの期待偏差(予測値の平均誤差)
バリアンス:データのサンプル化の方法に起因する期待推定値からの偏差(入力データのとり方による予測のばらつき)
(何言ってるかわからない)

バイアス 大、バリアンス 小 ➔ 正解率が低い
バイアス 小、バリアンス 大 ➔ 正解率が高いが、学習データに過学習している
トレードオフな関係ぽい)

回帰問題における平均二乗誤差をバイアス・バリアンス分解
f:id:konchangakita:20210109183358p:plain


バリアンスが大きく過学習している状態で、予測をできるだけブレないようにするのが正則化
バリアンスを小さくするには、モデルの複雑さに制限をかける

ちょっとかじったくらいでは、この式の成り立ちを理解するのはむずかしかったので、丸暗記にしておこう。。。

パラメータノルムペナルティ

正則化の代表的なアプローチのひとつ
目的関数 𝐽(𝜃;𝑋,𝑦) に対しノルムペナルティ Ω(𝜃) を追加する
f:id:konchangakita:20210109211713p:plain

重みのパラメータのノルムで正則化を行う
ノルムペナルティを種類

Lpノルム

f:id:konchangakita:20210109211754p:plain

L1ノルム(マンハッタン距離)

f:id:konchangakita:20210109211830p:plain

L2ノルム(ユークリッド距離):重み減衰(weight decay)

f:id:konchangakita:20210109211902p:plain

重みパラメータのL2ノルムを正則化項として加えると、重みが全体的に小さくなる方向に進んでいく、これを 重み減衰 という
f:id:konchangakita:20210109230359p:plain

これはちょっと手を加えるだけで組み込めそう

# 重み減衰
weight_decay_lambda = 0.1 # 重み減衰の係数
w1, w2, w3 = np.random.randn(3) #適当に
loss = 1 # 適当に

def lp_norm(w, p=2):
    return np.sum(np.abs(w)**p)**(1/p)

# loss に重みL2ノルムを加える
for w in (w1, w2, w3):
    loss += 0.5 * weight_decay_lambda * (lp_norm(w)**2)

# 勾配計算の時
dw1 += weight_decay_lambda * w1
dw2 += weight_decay_lambda * w2
dw3 += weight_decay_lambda * w3


【L1とL2の覚え方】
f:id:konchangakita:20210109215300p:plain

回帰と正則化項の組み合わせ

Lasso回帰:L1ノルムペナルティ
Ridge回帰:L2ノルムペナルティ
Elastic Net:L1とL2の両方用いる

まとめ

正則化(正規化と間違えがち)以外の方法にもいろいろと工夫ができるので
次回に、学習途中にノイズを入れる方法や、パラメータを拘束、アンサンブル学習やブースティングなど

【DeepLearning特訓】MLPの基礎 学習アルゴリズムの Python実装

E資格向けの自習アウトプット
自分用メモ

学習アルゴリズムを、実装してみて実際の学習の推移を確認してみたいと思います
今回は Python のクラスをちゃんと用いて学習モデルを作っていきます
一気に本格的な感じに

まず挑む問題ですが、過去にも Pytorch でも取り組んだ基本中の基本の MNIST のデータセットを使います
PyTorchを使ってDeep Learningのお勉強 画像認識編(MNIST) - konchangakita

こんなふうな数字の画像と 正解ラベル「4」がセットになっています
f:id:konchangakita:20210109144758p:plain

画像データセットの読み込み

MNIST データセットを読み込み
正解ラベルを One-Hot-Vector に変換します

import numpy as np
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split

#乱数シード指定
np.random.seed(seed=0)

# MNIST画像データ読み込み 
from sklearn.datasets import fetch_openml
mnist = fetch_openml(name='mnist_784', version=1)

# 画像とラベルを取得
X, T = mnist.data, mnist.target
# 訓練データとテストデータに分割
x_train, x_test, t_train, t_test = train_test_split(X, T, test_size=0.2)

# ラベルデータをint型にし、one-hot-vectorに変換
t_train = np.eye(10)[t_train.astype("int")]
t_test = np.eye(10)[t_test.astype("int")]

# Dataframe を numpy に変換
x_train = np.array(x_train.values)
x_test = np.array(x_test.values)

print('訓練データ(数, サイズ) \t', x_train.shape)
続きを読む

【DeepLearning特訓】MLPの基礎 学習アルゴリズム

E資格向けの自習アウトプット
自分用メモ

学習アルゴリズムとして、確率的勾配降下法はよく利用される方法ですが、勾配を求めてパラメータを更新する方向は分かれど、どれくらい更新すればよいかが明確に示されるわけではないので、学習が遅くなる場合がある。そこで過去の勾配情報を利用したりしながらパラメータ更新する工夫を行う
きっとモーメンタムとか今はほとんど使われていないのだろうけど、試験にでそうなので丸暗記

モメンタム

運動量の考え方を追加、速度 v、減速率 momentum を追加
速度 v は初期値は「0」で、アップデートされ減速していく

f:id:konchangakita:20210107233116p:plain

# モーメンタム
# w:重み、dw:重みの勾配
lr = 0.01   # 学習率
v = 0       # 初期値は0で更新されていく
momentum = 0.9     # モーメンタム

def momentum(w, lr, v, momentum):
    v = momentum * v - lr * dw
    w += v
    return w, v


ネステロフのモーメンタム

モーメントに直前の速度を加味する
f:id:konchangakita:20210108000229p:plain

書いてみたもののよくわからぬ

# ネステロフのモーメンタム
# w:重み、dw:重みの勾配
lr = 0.01   # 学習率
v = 0       # 初期値は0で更新されていく
momentum = 0.9     # モーメンタム

def momentum(dw, lr, v, momentum):
    old_v = v
    v = momentum * v - lr * dw
    w += -momentum * old_v + (1+momentum) * v
    return w, v


AdaGrad

モーメンタムアルゴリズムでは運動量が追加されていたが、AdaGradでは過去の勾配を蓄積させていく(どんどん小さくなる)
f:id:konchangakita:20210108005327p:plain:w400

# AdaGrad
delta = 1e-7    # 0で割ると困るから
lr = 0.01       # 学習率
h = 0           # 初期値は0で更新されていく

def adagrad(dw, lr, h):
    h += dw * dw
    w -= lr * dw / (np.sqrt(h) + delta)
    return w, h


RMSProp

AdaGrad の修正版
AdaGrad の弱点は、学習に十分時間がたつと勾配がどんどん小さくなり最適解の前に学習が止まってしまう
減衰率をもちいて、過去の勾配の影響を小さくする
累積二乗和ではなく、移動平均

# RMSProp
delta = 1e-7    # 0で割ると困るから
lr = 0.01       # 学習率
h = 0           # 初期値は0で更新されていく
decay_late = 0.9    # 減衰率

def rmsprop(dw, lr, h, decay_rate):
    h = h * decay_rate + (1 - decay_rate) * dw * dw # 減衰率を加味
    w -= lr * dw / (np.sqrt(h) + delta)
    return w, h


Adam

モーメンタム + AdaGrad/RMSProp の組み合わせ
運動量と移動平均を両方組み合わせ
よく利用されている、とりあえずこれにしておけば間違いない(?)

f:id:konchangakita:20210108111519p:plain
beta の部分をなんて呼ぶのかは知らない

# Adam
delta = 1e-7        # 0で割ると困るから
lr = 0.01           # 学習率
first_m, second_m = 0, 0    # 初期値は0で更新されていく
beta1, beta2 = 0.9, 0.99    # 減衰率?
t = 1   # 更新回数ごとの増やす(補正の影響を小さくしていく)

def adam(dw, lr, first_m, second_m, beta1, beta2):
    first_m = beta1 * first_m + (1 - beta1) * dx
    second_m = beta2 * second_m + (1 -beta2) * dx * dx
    first_unbias = first_m / (1 - beta1**t)
    second_unbias = second_m / (1 - beta2**t)
    w -= lr * first_unbias / (np.sqrt(second_unbias) + delta)
    t += 1
    return w, first_unbias, second_unbias

まとめ

パラメータ更新方法の考え方がいくつか出てきました。ムズイ
結局どれがよいのか?は扱う問題によって変わってきます
(きっとAdamが汎用性あるのだろうけども)
Python 実装してみてそれぞれの学習する過程を確認してみたいと

【DeepLearning特訓】MLPの基礎 確率的勾配降下法

E資格向けの自習アウトプット
自分用メモ

確率的勾配降下法SGD: Stochastic Gradient Descent)は、ミニバッチの単位で無作為に選んだデータで勾配降下法を使ってパラメータを更新していく方法
f:id:konchangakita:20210107125257p:plain

パラメータの更新方法は、誤差逆伝播で求めた勾配を使って各パラメータ(重み:w、バイアス:b)を更新する
誤差逆伝播で求めた勾配とハイパーパラメータで設定した学習率をつかって、現在のパラメータから算出する

重み(w)を更新する例
f:id:konchangakita:20210107124201p:plain

SGD は下記の条件で収束する
f:id:konchangakita:20210107124344p:plain

学習率は 0.01 や 0.001 あたり初期設定するが
 ・学習率が小さすぎると、学習が遅い
 ・学習率が大きすぎると、勾配が「0」になる地点にを通りこしてしまう
いずれにしてもいつまでたっても最適なパラメータにならないので、学習がうまくいく手動で調整していく
(最適な学習率を算出する方法もあるぽい)

# 確率的勾配降下法(SGD)でパラメータ更新
lr = 0.01
w1 = w1 - lr * dw1
b1 = b1 - lr * db1
w2 = w2 - lr * dw2
b2 = b2 - lr * db2


ここまでのステップをミニバッチの単位で、順伝播、誤差逆伝播、パラメータ更新を入力データ分一回回すことをエポックという

ミニバッチは、128や256くらいが多い
エポックは、1000回とか


入力データは1個だけ適当な入力データで、エポック 10回分まとめてみた
SGD で Loss が 減っていくはず


初期値の運が悪いと変わらないこともある

Epoch  0  loss: 0.5865723008185625
Epoch  1  loss: 0.13462761604484846
Epoch  2  loss: 0.06494275759481465
Epoch  3  loss: 0.043350135099269806
Epoch  4  loss: 0.03267105727966781
Epoch  5  loss: 0.026833979478593808
Epoch  6  loss: 0.023582050848936328
Epoch  7  loss: 0.020985603880502638
Epoch  8  loss: 0.018869993146677137
Epoch  9  loss: 0.017116506612223987


まとめ

確率的勾配降下法SGDSDGsと間違えがち))で、ようやく学習の第一歩
もっとも基本的なパラメータ更新方法ですが、学習速度が遅いという弱点があります
ここからはデータの前処理の工夫や学習の最適化の方法を学んでいきます

【DeepLearning特訓】MLPの基礎 誤差逆伝播までPython実装

E資格向けの自習アウトプット
自分用メモ

誤差逆伝播までの流れを一旦 python実装 してみる
 1.データ定義
 2.順伝播(アフィン変換、ReLU)
 3.コスト関数(誤差の計算)
 4.誤差逆伝播

とりあえず絵にしてみる
f:id:konchangakita:20210108224928p:plain
求めたいのはパラメータの勾配

入力データの定義

実際には、入力データと正解ラベルが対になっているが、とりあえず適当に数値を用意
(例:「車の画像」と「車」というラベル)
重みとバイアスもランダムな値で

import numpy as np

# 入力データの用意(1セットのみ適当なランダムな数値)
# 実際には1000かと1万とかもっといっぱいある
x1 = np.random.rand(1,3)
# 正解ラベル one-hot-vector
t = np.array((1,0,0))

# 重み w, バイアス b の初期設定
w1 = np.random.randn(3,4)
b1 = np.random.randn(4)
w2 = np.random.randn(4,3)
b2 = np.random.randn(3)


順伝播

入力データを「アフィン変換、ReLU関数」で中間層へ
ソフトマックスとで最後の

import numpy as np

# 順伝播:アフィン変換
def forward(x, w, b):
    y = np.dot(x, w) + b
    return y

# 順伝播:活性化関数 ReLU
# mask: 勾配の計算用
def relu(x):
    mask = (x <= 0)
    return np.maximum(0, x), mask

# 順伝播:出力ユニット ソフトマックス
def softmax(x):
    # ※[N, M] - [N]はできないので、 転置して[M, N] - [N]
    x = x.T             
    _x = x - np.max(x)
    _x = np.exp(x) / np.sum(np.exp(x), axis=0)
    return _x.T

# コスト関数 クロスエントロピー誤差
def cross_entropy_error(t, y):
    delta = 1e-8
    loss = -np.mean(t * np.log(y + delta))
    return loss


# 入力データの用意(1セットのみ適当なランダムな数値)
x1 = np.random.rand(1,3)
# 正解ラベル one-hot-vector
t = np.array((1,0,0))

# 重み w, バイアス b の初期設定
w1 = np.random.randn(3,4)
b1 = np.random.randn(4)
w2 = np.random.randn(4,3)
b2 = np.random.randn(3)


誤差逆伝播

順伝播で出力された「誤差」を持って、勾配を逆伝播していく
順伝播の中間層で一度算出された値の再利用を一度変数に収めているが、Python の Class を使えばもっとシンプルに書くことができる
今回は敢えて各層の計算で何が必要なのかを見やすくするために、関数のみで下記揃える

# 誤差逆伝播:ReLU
def drelu(dout, mask):
    dout[mask] = 0
    return dout

# 誤差逆伝播:アフィン変換
def backward(dout, x, w):
    dx = np.dot(dout, w.T)
    dw = np.dot(x.T, dout)
    db = np.sum(dout, axis=0)
    return dx, dw, db

# 誤差逆伝播
dout2 = y - t # ソフトマックスwithクロスエントロピーの勾配
dx2, dw2, db2 = backward(dout2, x2, w2)
dout1 = drelu(dx2, wmask1)
dx1, dw1, db1 = backward(dout1, x1, w1)

# 誤差
print('Loss: ', loss)
print('')

# 重み、バイアスの勾配
print('第1層の勾配')
print('w1:\n', w1)
print('b1:\n', b1)
print('')
print('第2層の勾配')
print('w2:\n', w2)
print('b2:\n', b2)

アウトプット

Loss:  0.4813766638499986

第1層の勾配
w1:
 [[-0.81608621  0.78189308 -1.98234178  0.52644459]
 [ 0.21205568  0.86098345  0.1558696  -0.62931689]
 [ 1.21615824 -0.51937533  0.33134833 -0.43714047]]
b1:
 [-0.58540344 -0.44224125  0.46466139 -0.06018246]

第2層の勾配
w2:
 [[ 0.01044176 -0.0790746  -0.70335393]
 [-0.65571456 -0.10833987  0.32557463]
 [-0.52371017  0.08358353 -0.26835567]
 [-0.77081821  0.52365595 -0.10367158]]
b2:
 [ 0.7871995   1.71908927 -1.39842447]

まとめ

誤差逆伝播を使って、パラメータの勾配を計算するところまではこんな感じ
学習モデルのステップとしては、パラメータをどう更新していくかを考えていく