目次
- 0. 初めに
- 1. プロローグ
- 2. 入力層~中間層
- 3. 活性化関数
- 4. 勾配降下法
- 5. 誤差逆伝播法
- 6. 勾配消失問題
- 7. 学習率最適化手法
- 8. 過学習
- 9. 畳み込みニューラルネットワーク
- 10. CNNの発展形
#0. 初めに
- E資格検定の深層学習パート(1/2)についてメモします。
- ここでは深層学習の基礎を取り扱う。
- 各パートについて、要件まとめ と 演習結果 を記載する。
#1. プロローグ
###1-1. 要点まとめ
- 識別と生成
- 識別
- データを目的のクラスに分類する
- 例)犬や猫の画像のデータを分類する
- $p(C_k|\boldsymbol{x})$
- 条件”あるデータ$\boldsymbol{x}$が与えられた”という条件の下でクラス$C_k$である確率
- 生成
- 特定のクラスのデータを生成する ※識別の逆パターンといえる
- 例)犬らしい画像を生成する
- $p(\boldsymbol{x}|C_k)$
- 条件”あるクラス$C_k$が与えられた”という条件の下でデータ$\boldsymbol{x}$の分布
- 識別
- 具体例
識別モデル | 生成モデル | |
---|---|---|
具体的なモデル | - 決定木 - ロジスティック回帰 - サポートベクターマシン(SVM) - ニューラルネットワーク |
- 隠れマルコフモデル - ベイジアンネットワーク - 変分オートエンコーダ(VAE) - 敵対的生成ネットワーク(GAN) |
特徴 | - 高次元→低次元 - 必要な学習データ:少 |
- 低次元→高次元 - 必要な学習データ:多 |
応用例 | - 画像認識 | - 画像の超解像(解像度を上げる) - テキスト生成 |
- 識別機(Classifier)の開発アプローチ
- 2007年出版の名著「パターン認識と機械学習」では、「生成モデル」「識別モデル」「識別関数」の3つで分類されていた。
- 「識別モデル」と「識別関数」の違い
- 識別モデル(確率的識別モデル)
- データがクラスに属する確率をモデリング
- 「推論」⇒「決定」の2ステップになる。
- 間違いの程度を測ることができる。推論結果の取り扱いを決められる(棄却するなど)
- 識別関数(決定的識別モデル)
- データの属するクラス情報のみモデリング。確率は計算しない。
- 入力データから識別結果を一気に得る。間違いの程度を測ることができない。
- 識別モデル(確率的識別モデル)
- 識別器の学習内容の違い
- 識別モデルは、クラス分類に必要な情報のみに限定して学習。
- 生成モデルは、クラス分類のみならず、データのクラス条件付き密度を学習する。計算量が多い。
- 万能近似定理
- ニューラルネットワークは、どんな関数でもうまいこと近似できる。
#2. 入力層~中間層
###2-1. 要点まとめ
- ニューラルネットワークの全体像
- 「入力層」「中間層」「出力層」の3つに分かれる。
- ディープラーニングとは
- 多数の中間層を持つニューラルネットワークを用いて、入力値から目的とする出力値に変換する数学モデルを構築すること。
- 最適化の最終目標
- 重み[W]、バイアス[B]
- ニューラルネットワークの構成要素
- 事前に用意するデータ
- 入力:$ \boldsymbol{x}_n$
- 訓練データ:$\boldsymbol{d}_n$
- 多層ネットワークのパラメータ
- 重み: $\boldsymbol{W}$
- バイアス: $\boldsymbol{b}$
- 活性化関数
- $f(\boldsymbol{u})$
- 中間層出力
- $\boldsymbol{z} = f(\boldsymbol{u})$
- 総入力
- $\boldsymbol{u} = \boldsymbol{W} \boldsymbol{z} + \boldsymbol{b}$
- 出力
- $\boldsymbol{y}_n = \boldsymbol{z}$
- 誤差関数
- $E_n(\boldsymbol{W})$
- 事前に用意するデータ
- 確認テスト2
- 入力層:2ノード1層、中間層:3ノード2層、出力層:1ノード1層を図示しろ
- 入力層→中間層1→中間層2→出力層の順に配置する。
- 隣接する各層同士で、全ノード間を結合させた形となる。
- 入力層:2ノード1層、中間層:3ノード2層、出力層:1ノード1層を図示しろ
- ニューラルネットワークでできること
- 回帰問題、分類問題など幅広い問題に対応可能。→万能近似定理。
- 実用例・・・数値データとなっていれば対応可能。
- 自動売買(トレード)
- チャットボット
- 翻訳
- 音声認識
- 囲碁、将棋AI
- 入力層~中間層
- 数式表現
- $\boldsymbol{u} = \boldsymbol{W} \boldsymbol{x} + \boldsymbol{b} $
- 数式表現
###2-2. 演習
- 順伝播(単層・単ユニット)
- 以下コードにより、入力層から受け取ったデータを変換し、次の中間層へ出力する
- 「u = np.dot(x, W) + b」
- 以下コードにより、入力層から受け取ったデータを変換し、次の中間層へ出力する
import numpy as np
from common import functions
def print_vec(text, vec):
print("*** " + text + " ***")
print(vec)
print("")
# 重み
W = np.array([[0.1], [0.2]])
print_vec("重み", W)
# バイアス
b = 0.5
print_vec("バイアス", b)
# 入力値
x = np.array([2, 3])
print_vec("入力", x)
# 総入力
u = np.dot(x, W) + b
print_vec("総入力", u)
# 中間層出力
z = functions.relu(u)
print_vec("中間層出力", z)
- 順伝播(3層・複数ユニット)
- 中間層への出力を定義している箇所を抜き出すと以下となる。
# 2層の総入力
u2 = np.dot(z1, W2) + b2
# 2層の総出力
z2 = functions.relu(u2)
# 出力層の総入力
u3 = np.dot(z2, W3) + b3
#3. 活性化関数
###3-1. 要点まとめ
-
活性化関数
-
誤差関数
- 訓練データ(入力データとその正解のペア)を用意する
- 問題集を解いてみて、答え合わせをする。「出した答え」と「正解」をもとに、どのくらい合致したかをを誤差関数で出す。
- 二乗誤差の場合の数式
- $E_n(w) = \frac{1}{2} \sum_{j=1}^J (y_j - d_j)^2 = \frac{1}{2} ∥ (y - d) ∥^2$
- 誤差は正負両方の値が発生するため、単純に足し合わせると0となる。2乗することで正負を気にしなくてよくなる。
- $\frac{1}{2}$の意味は、微分すると出てくる$2$を打ち消して計算を単純化するため。
-
誤差関数の種類
- 回帰問題: 平均二乗誤差
- 分類問題: クロスエントロピー誤差
-
活性化関数の種類
- 中間層用
- ReLU関数
- $ f(x) = \left\{ \begin{array}{ll}x & (x \geq 0) \\0 & (x \lt 0) \end{array} \right. $
- 勾配消失問題の回避とスパース化に貢献する
- シグモイド(ロジスティック)関数
- $f(u) = \frac{1}{1 + e^{-u}} $
- 0~1の間を緩やかに変化する関数。
- 勾配消失問題を引き起こすことがあった。
- ステップ関数
- $ f(x) = \left\{ \begin{array}{ll}1 & (x \geq 0) \\0 & (x \lt 0) \end{array} \right. $
- 0~1の間を表現できない。
- 現在は利用されていない。線形分離可能なものしか学習できない課題がある。
- ReLU関数
- 出力層用
- 出力層の中間層との違い
- 値の強弱
- 中間層: 閾値の前後で信号の強弱を調整
- 出力層: 信号の大きさ(比率)を変えずに変換。分類問題の場合は、0~1の範囲に限定都し、合計1となるようにする。
- 値の強弱
- 出力層の中間層との違い
- 中間層用
回帰 | 二値分類 | 他クラス分類 | |
---|---|---|---|
活性化関数 | 恒等写像 $f(u) = u$ |
シグモイド関数 $f(u)=\frac{1}{1+e^{-u}}$ |
ソフトマックス関数 $f(i,u)=\frac{e^{u_i}}{\sum_{k=1}^K e^{u_k}}$ |
誤差関数 | 二乗誤差 | 交差エントロピー | 交差エントロピー |
- 訓練データサンプル当たりの誤差
- 二乗誤差
- $E_n(w) = \frac{1}{2} \sum_{j=1}^J (y_j - d_j)^2 = \frac{1}{2} ∥ (y - d) ∥^2$
- 交差エントロピー
- $E_n(w) = - \sum_{i=1}^I d_i \log y_i $
- 二乗誤差
- 学習サイクル当たりの誤差
- $E(w) = \sum_{n=1}^N E_n$
###3-2. 演習
- 活性化関数の実装
# 中間層の活性化関数
# シグモイド関数(ロジスティック関数)
def sigmoid(x):
return 1/(1 + np.exp(-x))
# ReLU関数
def relu(x):
return np.maximum(0, x)
# ステップ関数(閾値0)
def step_function(x):
return np.where( x > 0, 1, 0)
# 出力層の活性化関数
# ソフトマックス関数
def softmax(x):
if x.ndim == 2:
x = x.T
x = x - np.max(x, axis=0)
y = np.exp(x) / np.sum(np.exp(x), axis=0)
return y.T
x = x - np.max(x) # オーバーフロー対策
return np.exp(x) / np.sum(np.exp(x))
# ソフトマックスとクロスエントロピーの複合関数
def softmax_with_loss(d, x):
y = softmax(x)
return cross_entropy_error(d, y)
- 誤差関数の実装
- クロスエントロピーの分母に微小の数「1e-7」を足すことで安定性を向上させる。対数は0に近づくとマイナス無限大に落ちてしまうための対応。
# 平均二乗誤差
def mean_squared_error(d, y):
return np.mean(np.square(d - y)) / 2
# クロスエントロピー
def cross_entropy_error(d, y):
if y.ndim == 1:
d = d.reshape(1, d.size)
y = y.reshape(1, y.size)
# 教師データがone-hot-vectorの場合、正解ラベルのインデックスに変換
if d.size == y.size:
d = d.argmax(axis=1)
batch_size = y.shape[0]
return -np.sum(np.log(y[np.arange(batch_size), d] + 1e-7)) / batch_size
- 導関数の実装
- 各関数の数式について、微分した結果と一致していることを確認
# シグモイド関数(ロジスティック関数)の導関数
def d_sigmoid(x):
dx = (1.0 - sigmoid(x)) * sigmoid(x)
return dx
# ReLU関数の導関数
def d_relu(x):
return np.where( x > 0, 1, 0)
# ステップ関数の導関数
def d_step_function(x):
return 0
# 平均二乗誤差の導関数
def d_mean_squared_error(d, y):
if type(d) == np.ndarray:
batch_size = d.shape[0]
dx = (y - d)/batch_size
else:
dx = y - d
return dx
# ソフトマックスとクロスエントロピーの複合導関数
def d_softmax_with_loss(d, y):
batch_size = d.shape[0]
if d.size == y.size: # 教師データがone-hot-vectorの場合
dx = (y - d) / batch_size
else:
dx = y.copy()
dx[np.arange(batch_size), d] -= 1
dx = dx / batch_size
return dx
# シグモイドとクロスエントロピーの複合導関数
def d_sigmoid_with_loss(d, y):
return y - d
#4. 勾配降下法
###4-1. 要点まとめ
- 勾配降下法
- 概要
- 全サンプルの平均誤差を計算する
- デメリット
- 並列化できず、処理に時間がかかる。
- デメリット
- 全サンプルの平均誤差を計算する
- 数式
- $w^{t+1} = w^{(t)} - \epsilon \Delta E $
- $\Delta E$ 誤差勾配・・・正解と予測値の差(重み$w$を最適化するためのギャップ)
- $\epsilon$ 学習率
- 小さすぎると収束に時間がかかる、局所最適解に陥る
- 大きすぎると発散し、収束しない
- 学習率の決定方法や収束性向上のためのアルゴリズム
- Momentum
- AdaGrad
- Adadelta
- Adam ※よく使われる
- エポック
- 学習のサイクルのこと
- 概要
- 確率的勾配降下法
- 概要
- 勾配降下法のバリエーションの一つで、「SGD」といわれる
- やってくる訓練データを都度計算する。少量メインメモリで対応。
- ランダムに抽出したサンプルデータの誤差を計測
- メリット
- データが冗長な場合の計算コストの軽減
- 局所最適解に陥るリスクの軽減
- オンライン学習ができる(オンライン学習⇔バッチ学習)
- 数式
- $w^{t+1} = w^{(t)} - \epsilon \Delta E_n$
- 概要
- ミニバッチ勾配降下法
- 概要
- 訓練データを小分け(ミニバッチ)にして学習する。
- メリット
- SGDのメリットを損なわず、計算機の計算資源を有効利用できる。
- CPUを利用したスレッド並列化やGPUを利用したSIMD(Single Instruction Multi Data)並列化が可能。
- 数式
- $w^{t+1} = w^{(t)} - \epsilon \Delta E_t$
- $E_t = \frac{1}{N_t} \sum_{n \in D_t} E_n$
- $N_t = |D_t|$
- 概要
###4-2. 演習
- 確率的勾配降下法を実現しているコード
- 「random_datasets」にランダムで選んだ訓練データを格納し、順伝播→誤差逆伝播でパラメータを更新。
# サンプルデータを作成
data_sets_size = 100000
data_sets = [0 for i in range(data_sets_size)]
for i in range(data_sets_size):
data_sets[i] = {}
# ランダムな値を設定
data_sets[i]['x'] = np.random.rand(2)
## 試してみよう_入力値の設定
# data_sets[i]['x'] = np.random.rand(2) * 10 -5 # -5〜5のランダム数値
# 目標出力を設定
data_sets[i]['d'] = f(data_sets[i]['x'])
losses = []
# 学習率
learning_rate = 0.07
# 抽出数
epoch = 1000
# パラメータの初期化
network = init_network()
# データのランダム抽出
random_datasets = np.random.choice(data_sets, epoch)
# 勾配降下の繰り返し
for dataset in random_datasets:
x, d = dataset['x'], dataset['d']
z1, y = forward(network, x)
grad = backward(x, d, z1, y)
# パラメータに勾配適用
for key in ('W1', 'W2', 'b1', 'b2'):
network[key] -= learning_rate * grad[key]
# 誤差
loss = functions.mean_squared_error(d, y)
losses.append(loss)
print("##### 結果表示 #####")
lists = range(epoch)
- 結果表示
#5. 誤差逆伝播法
###5-1. 要点まとめ
- 誤差勾配の計算方法
- 数値微分
- 微小な数値を生成し擬似的に微分を計算する
- $\frac{\partial E}{\partial w_m} \approx \frac{E(w_m+h) - E(w_m -h)}{2h} $
- デメリット
- 各パラメータ$w_m$それぞれについて、$E(w_m+h)$や$E(w_m-h)$を計算すると無駄が大きい。
- 深層学習ではパラメータ数が多いため、計算量が大きくなる。
- 誤差逆伝播法
- 微分の連鎖律を利用することで効率的に誤差勾配の計算が可能。
- 計算結果(=誤差)から微分を逆算することで、不要な再帰的計算を避けることが可能。
- 微分計算結果を再利用できるため効率的。
- 数式
- 前提(例)
- $E(y) = \frac{1}{2} \sum_{j=1}^J (y_j - d_j)^2 = \frac{1}{2} ||y_j - d_j||^2$
- $y = u^{(L)}$
- $u^{l} = w^{(l)} z^{l-1} + b^{l}$
- 重み$w^{(2)}$の誤差勾配の算出
- $\frac{\partial E}{\partial w^{(2)}} = \frac{\partial E}{\partial y} \frac{\partial y}{\partial u} \frac{\partial u}{\partial w_{ji}^{(2)}} $
- $\frac{\partial E(y)}{\partial y} = \frac{\partial}{\partial y} \frac{1}{2} || y - d ||^2 = y - d$
- $\frac{\partial y(y)}{\partial u} = \frac{\partial u}{\partial u} = 1 $
- $\frac{\partial u(w)}{\partial w_{ji}} = \frac{\partial}{\partial w_{ji}} \left( w^{(l)} z^{(l-1)} + b^{(l)} \right) = \frac{\partial}{\partial w_{ji}} \left( \begin{bmatrix} w_{11}z_1 + \cdots + w_{1i}z_i + \cdots + w_{1I}z_I \\ \vdots \\ w_{i1}z_1 + \cdots + w_{ji}z_i + \cdots + w_{jI}z_I \\ \vdots \\ w_{J1}z_1 + \cdots + w_{Ji}z_i + \cdots + w_{JI}z_I \end{bmatrix} + \begin{bmatrix} b_1 \\ \vdots \\ b_j \\ \vdots \\ b_J \end{bmatrix} \right) = \begin{bmatrix} 0 \\ \vdots \\ z_i \\ \vdots \\ 0 \end{bmatrix} $
- $\frac{\partial E}{\partial w^{(2)}} = \frac{\partial E}{\partial y} \frac{\partial y}{\partial u} \frac{\partial u}{\partial w_{ji}^{(2)}} = (y-d) \begin{bmatrix} 0 \\ \vdots \\ z_i \\ \vdots \\ 0 \end{bmatrix} = (y_j - d_j) z_i $
- $\frac{\partial E}{\partial w^{(2)}} = \frac{\partial E}{\partial y} \frac{\partial y}{\partial u} \frac{\partial u}{\partial w_{ji}^{(2)}} $
- 前提(例)
- 数値微分
###5-2. 演習
- ネットワーク定義、順伝播のコード
# 初期設定
def init_network():
network = {}
nodesNum = 10
network['W1'] = np.random.randn(2, nodesNum)
network['W2'] = np.random.randn(nodesNum)
network['b1'] = np.random.randn(nodesNum)
network['b2'] = np.random.randn()
return network
# 順伝播
def forward(network, x):
W1, W2 = network['W1'], network['W2']
b1, b2 = network['b1'], network['b2']
u1 = np.dot(x, W1) + b1
z1 = functions.relu(u1)
u2 = np.dot(z1, W2) + b2
y = u2
return z1, y
- 誤差逆伝播を実現しているコード
- $\frac{\partial E}{\partial y}$ = delta2 = 平均二乗誤差を微分した値
- バイアス2(b2) = delta2の合計
- 重み2(w2) = z1転置(中間層の出力値)とdelta2の内積
- $\frac{\partial E}{\partial y} \frac{\partial y}{\partial u}$ = delta1 = delta2とW2(重み2)の内積 × 活性化関数ReLUの微分した値
- バイアス1(b1) = delta1の合計
- $\frac{\partial E}{\partial y} \frac{\partial y}{\partial u} \frac{\partial u}{\partial w_{ji}^{(2)}}$ = 重み1(w1) = 入力値(x)とdelta1転置の内積
# 誤差逆伝播
def backward(x, d, z1, y):
grad = {}
W1, W2 = network['W1'], network['W2']
b1, b2 = network['b1'], network['b2']
# 出力層でのデルタ
delta2 = functions.d_mean_squared_error(d, y)
# b2の勾配
grad['b2'] = np.sum(delta2, axis=0)
# W2の勾配
grad['W2'] = np.dot(z1.T, delta2)
# 中間層でのデルタ
delta1 = np.dot(delta2, W2.T) * functions.d_relu(z1)
delta1 = delta1[np.newaxis, :]
# b1の勾配
grad['b1'] = np.sum(delta1, axis=0)
x = x[np.newaxis, :]
# W1の勾配
grad['W1'] = np.dot(x.T, delta1)
return grad
#6. 勾配消失問題
###6-1. 要点まとめ
-
中間層を増やしていったときに、入力層に近い層のパラメータ更新ができなくなる。入力層に近づくにつれて勾配が小さくなる。
-
発生原因
- 入力層に近い重みパラメータを更新するための勾配について、微分の掛け算が多くなる。
- 微分結果は小さい値になりがちであり、それらの掛け算により結果的に勾配が小さくなる。
- 勾配が小さくなることで重みを更新できなくなる。
-
シグモイド関数による勾配消失
- シグモイド関数の微分は $(1 - sigmoid(x)) * sigmoid(x)$ となる。
- 微分結果は以下となる。0~0.25の値の範囲となることがわかる。
- この結果を多数掛け合わせると小さくなりすぎてしまう。
- 勾配消失の解決策
- 活性化関数の選択
- ReLU関数:微分結果が小さくならない(<=0の場合は0、>0の場合は1)
- メリット
- 勾配消失問題の回避(微分結果が0または1のため、勾配消失が起こりづらい)
- スパース化(微分値が0の重みは役に立たないものとなる。有効な重みが選択されて残る)
- メリット
- ReLU関数:微分結果が小さくならない(<=0の場合は0、>0の場合は1)
- 重みの初期値を工夫
- 当初、標準正規分布(平均0、分散1)で重みの初期値を設定していたが、勾配消失の問題が発生。
- Xavier
- 重みの要素を、前の層のノード数の平方根で除算する。
- ある程度の層を重ねても活性化関数の表現力を保つことができる。
- シグモイド関数などの活性化関数を利用する場合に用いる。
- He
- ReLU関数などの活性化関数を利用する場合に用いる。
- 重みの要素を、前の層のノード数の平方根で除算した値に対し$\sqrt{2}$を掛け合わせる。
- 参考
- バッチ正則化
- ミニバッチ単位で入力値のデータの偏りを抑制する手法
- 活性化関数に値を渡す前後に、バッチ正規化の処理を挟んだ層を加える
- メリット
- 安定する
- 過学習が起きづらくなる
- バッチ正規化の数式 以下1~4の順に計算
-
- ミニバッチの平均 $\mu_t = \frac{1}{N_t} \sum_{i=1}^{N_t} x_{ni}$
-
- ミニバッチの分散 $\sigma_t^2 = \frac{1}{N_t} \sum_{i=1}^{N_t} \left( x_{ni} - \mu_t \right)^2$
-
- ミニバッチの正規化 $\hat{x_{ni}} = \frac{ x_{ni} - \mu_t }{ \sqrt{ \sigma_t^2 + \theta } }$
-
- 変数倍・移動 $y_{ni} = \gamma x_{ni} + \beta$
- 補足
- $\gamma$:スケーリングパラメータ
- $\beta$:シフトパラメータ
- $y_{ni}$:ミニバッチのインデックス値とスケーリングの席にシフトを加算した値(バッチ正規化オペレーションの出力)
-
- ミニバッチ単位で入力値のデータの偏りを抑制する手法
- 活性化関数の選択
###6-2. 演習
- シグモイド関数+ガウス分布による重み初期化(失敗パターン)
- 想定通りうまく学習できず
- ReLU関数+ガウス分布による重み初期化
- 学習回数500手前くらいから学習が進み始め、結果として問題なく学習できている
- シグモイド関数+Xavierによる重み初期化
- 問題なく学習が進んでいる
- ReLU関数+Heによる重み初期化
- 問題なく学習が進んでいる
- ReLU関数+Xavierによる重み初期化はだめなのか試してみた→問題なく収束
- ソース変更箇所
# 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)
"""
# 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))
- バッチ正規化コード
- 順伝播および逆伝播にそれぞれバッチ正規化処理が入っていることを確認
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
# バッチ正則化 layer
class BatchNormalization:
'''
gamma: スケール係数
beta: オフセット
momentum: 慣性
running_mean: テスト時に使用する平均
running_var: テスト時に使用する分散
'''
def __init__(self, gamma, beta, momentum=0.9, running_mean=None, running_var=None):
self.gamma = gamma
self.beta = beta
self.momentum = momentum
self.input_shape = None
self.running_mean = running_mean
self.running_var = running_var
# backward時に使用する中間データ
self.batch_size = None
self.xc = None
self.std = None
self.dgamma = None
self.dbeta = None
def forward(self, x, train_flg=True):
if self.running_mean is None:
N, D = x.shape
self.running_mean = np.zeros(D)
self.running_var = np.zeros(D)
if train_flg:
mu = x.mean(axis=0) # 平均
xc = x - mu # xをセンタリング
var = np.mean(xc**2, axis=0) # 分散
std = np.sqrt(var + 10e-7) # スケーリング
xn = xc / std
self.batch_size = x.shape[0]
self.xc = xc
self.xn = xn
self.std = std
self.running_mean = self.momentum * self.running_mean + (1-self.momentum) * mu # 平均値の加重平均
self.running_var = self.momentum * self.running_var + (1-self.momentum) * var #分散値の加重平均
else:
xc = x - self.running_mean
xn = xc / ((np.sqrt(self.running_var + 10e-7)))
out = self.gamma * xn + self.beta
return out
def backward(self, dout):
dbeta = dout.sum(axis=0)
dgamma = np.sum(self.xn * dout, axis=0)
dxn = self.gamma * dout
dxc = dxn / self.std
dstd = -np.sum((dxn * self.xc) / (self.std * self.std), axis=0)
dvar = 0.5 * dstd / self.std
dxc += (2.0 / self.batch_size) * self.xc * dvar
dmu = np.sum(dxc, axis=0)
dx = dxc - dmu / self.batch_size
self.dgamma = dgamma
self.dbeta = dbeta
return dx
- ネットワーク定義を変更して再実行 → 学習スピードアップ&Accuracyが向上
- 活性化関数:sigmoid→relu
- 中間層のノード数:(40,20)→(80,40)
#network = MultiLayerNet(input_size=784, hidden_size_list=[40, 20], output_size=10,
# activation='sigmoid', weight_init_std='Xavier', use_batchnorm=use_batchnorm)
network = MultiLayerNet(input_size=784, hidden_size_list=[80, 40], output_size=10,
activation='relu', weight_init_std='Xavier', use_batchnorm=use_batchnorm)
#7. 学習率最適化手法
###7-1. 要点まとめ
- 学習率の設定が収束可否や収束にかかるスピードに影響する
- 学習率($\epsilon$)が大きすぎる:発散してしまい、最適解にいつまでもたどり着けない
- 学習率($\epsilon$)が小さすぎる:収束に時間がかかる。局所最適解に陥りやすい
- 学習率の設定の工夫(オプティマイザ)
- オプティマイザの基本
- 初期の学習率を大きく設定し、徐々に学習率を小さくしていく
- パラメータ毎に学習率を可変にする
- モメンタム
- 誤差をパラメータで微分したものと学習率の積を減算したのち、前回の重みと慣性の積を加算する
- 数式
- $V_t = \mu V_{t-1} - \epsilon \Delta E$ ※$\mu$:慣性
- $w^{(t+1)} = w^{(t)} + V_t$
- メリット
- 局所最適解に陥りにくい
- 谷間についてから最も低い位置(最適解)に行くまでの時間が早い
- AdaGrad
- 誤差をパラメータで微分したものと再定義した学習率の積を減算する(これまでの学習を覚えておきながら学習を進める)
- 数式
- $h_0 = \theta$
- $h_t = h_{t-1} + \left( \Delta E \right)^2$
- $w^{(t+1)} = w^{(t)} - \epsilon \frac{1}{ \sqrt{h_t} + \theta } \Delta E$
- メリット
- 勾配の緩やかな斜面に対して、最適地に近づける
- 課題
- 学習率が徐々に小さくなるので、鞍点問題を引き起こすことがあった
- RMSProp
- AdaGradにおける鞍点問題の解決を目指したもの
- 数式
- $h_t = \alpha h_{t-1} + \left( 1 - \alpha \right) \left( \Delta E \right)^2$
- $w^{(t+1)} = w^{(t)} - \epsilon \frac{1}{ \sqrt{h_t} + \theta } \Delta E$
- メリット
- 局所最適解に陥りずらい
- ハイパーパラメータ(慣性)の調整が不要
- Adam
- 各手法のいいとこどり
- モメンタムの特徴:過去の勾配の指数関数的減数平均
- RMSPropの特徴:過去の勾配の2乗の指数関数的減数平均
- 各手法のいいとこどり
- 参考
- オプティマイザの動き
- スーパー分かりやすい解説
- オプティマイザの基本
###7-2. 演習
- モメンタムの実装コード → 学習が進まない
### 省略 ###
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]
### 省略 ###
- モメンタム 活性化関数を変更(sigmoid→relu) → 学習が進んだ
- AdaGradの実装コード → 学習が進まない
# 勾配
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] += grad[key] * grad[key]
h[key] = momentum * h[key] - learning_rate * grad[key]
network.params[key] += h[key]
- AdaGrad 学習率を変更(learning_rate 0.01→0.15) → 学習が進んだ
- RMSPropの実装コード
### 省略 ###
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)
- Adamの実装コード
- ハイパーパラメータとして、learning_rateに加えて、beta1とbeta2の2つを持つ
- mとvの2変数を蓄積しながら学習を進める
### 省略 ###
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)
- Adam バッチ正則化を実施 → 学習がスピードアップした
#8. 過学習
###8-1. 要点まとめ
- 過学習とは
- 特定の訓練データに特化したモデルであり、汎化性能が出ない状態
- 原因
- 入力パラメータ数:少ない ニューラルネットワーク:大きい
- ネットワークの自由度(層数、ノード数、パラメータ値など)が高い
- 入力パラメータ数:少ない ニューラルネットワーク:大きい
- イメージ(右図が過学習)
- 過学習への対処
- アプローチ
- 自由度が高すぎると過学習が起こる → ネットワークの自由度を制約する
- 重みが大きすぎる状態 → 一部の入力データに対して極端な反応を示す(過大評価)
- 対策として、誤差に対して正則化項を加算することで重みを抑制する(誤差+正則化項で最適化)
- アプローチ
- Lp正則化
- 数式
- $E_n(w) + \frac{1}{p} \lambda ||x||_p \\ ||x||_p = \left( |x_1|^p + \cdots + |x_n|^p \right)^{\frac{1}{p}}$
- L1正則化 Lasso回帰
- p1ノルム:マンハッタン距離
- L2正則化 Ridge回帰
- p2ノルム:ユーグリッド距離
- イメージ
- 数式
- ドロップアウト
- ランダムにノードを削除して学習させること
- メリットとして、データ量を変化させずに、異なるモデルを学習させていると解釈できる
###8-2. 演習
- 正則化なし
- Accuracy100%で過学習している
- L2正則化
- 訓練データのAccuracyが100%に届いていない=正則化が効いている
- テストデータのAccuracyはそれほどでもない
- L1正則化
- L2正則化と同様、訓練データのAccuracyが100%に届いていない=正則化が効いている
- L1正則化 weight_decay_lambdaを変更1(0.005→0.01)
- 罰則を強めた結果、学習が訓練データ、テストデータのいずれもAccuracyが低下
- L1正則化 weight_decay_lambdaを変更2(0.005→0.002)
- 罰則を弱めた結果、過学習が発生
- ドロップアウト
- 学習スピードが鈍化している(ドロップアウトの効果)
- ドロップアウト+L1正則化
- weight_decay_lambda=0.005(そのまま)、dropout_ratio:0.1(0.15から変更)
#9. 畳み込みニューラルネットワーク
###9-1. 要点まとめ
- CNNで扱えるデータの種類
- CNNでは次元間でつながりのあるデータを扱える(画像でいえば近隣のピクセルに関連性がある)
1次元 | 2次元 | 3次元 | |
---|---|---|---|
単一チャンネル (強度のみ) |
音声 [時刻,強度] |
フーリエ変換した音声 [時刻,周波数,強度] |
CTスキャン画像 [x,y,z,強度] |
複数チャンネル (複数値) |
アニメのスケルトン [時刻,(腕の位置,膝の位置$\cdots$) |
カラー画像 [x,y,(R,G,B)] |
動画 [時刻,x,y,(R,G,B)] |
- CNNの構造
- LeNetの構造図
- 32x32の画像をインプットに最終的に10種類の値に分類
- LeNetの構造図
INPUT | C1:f.maps | S2:f.maps | C3:f.maps | S4:f.maps | C5:layer | F6:layer | OUTPUT | |
---|---|---|---|---|---|---|---|---|
shape | (32,32) | (28,238,6) | (14,14,6) | (10,10,16) | (5,5,16) | (120,) | (84,) | (10,) |
個数 | 1,024 | 4,704 | 1,176 | 1,600 | 400 | 120 | 84 | 10 |
- Convolutions(畳み込み層)
- フィルタをずらしながら演算を行い、情報が集約する。フィルタの数だけ繰り返し行う
- 入力→出力について、次元間のつながりが保たれていることがポイント
- Pooling(プーリング)
- 重みなし。以下種類の演算ルールに沿って処理される
- Max Pooling フィルタ範囲について最大値を取る
- Average Pooling フィルタ範囲について平均値を取る
- 重みなし。以下種類の演算ルールに沿って処理される
- Padding(パディング)
- 畳み込み演算をすると、入力サイズよりも出力サイズが小さくなる(演算を繰り返すと問題になる)
- 上下左右の周りに何かしらの値を埋めることでサイズをそろえる(0で埋めるなど)
- Stride(ストライド)
- フィルタを動かす大きさ。ストライドの大きさで出力サイズが変わる
- Channel(チャンネル)
- フィルタの数を指す
- Full connection(全結合層)
- CNNで集約した特徴量をもとに、普通のニューラルネットワークで分析タスクを解く。
- 畳み込み演算後のサイズ計算 ※パディングは縦横2倍の影響力があるため注意
- $\mbox{高さ}_H = \frac{\mbox{入力画像の高さ} + 2 \mbox{×} \mbox{パディング高} - \mbox{フィルタ高さ} }{\mbox{ストライド}} + 1 $
- $\mbox{幅}_W = \frac{\mbox{入力画像の幅} + 2 \mbox{×} \mbox{パディング幅} - \mbox{フィルタ幅} }{\mbox{ストライド}} + 1 $
###9-2. 演習
- 高速化の工夫
- image to column
- 入力データの行列に対して、フィルタをストライドさせて読み込まれる各数値を行または列に展開して保持しておく
- これをもとに行列演算を行うことで処理を高速化できる
- image to column
'''
input_data: 入力値
filter_h: フィルターの高さ
filter_w: フィルターの横幅
stride: ストライド
pad: パディング
'''
def im2col(input_data, filter_h, filter_w, stride=1, pad=0):
# N: number, C: channel, H: height, W: width
N, C, H, W = input_data.shape
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))
for y in range(filter_h):
y_max = y + stride * out_h
for x in range(filter_w):
x_max = x + stride * out_w
col[:, :, y, x, :, :] = img[:, :, y:y_max:stride, x:x_max:stride]
col = col.transpose(0, 4, 5, 1, 2, 3) # (N, C, filter_h, filter_w, out_h, out_w) -> (N, filter_w, out_h, out_w, C, filter_h)
col = col.reshape(N * out_h * out_w, -1)
return col
# im2colの処理確認(フィルタサイズ:3x3)
input_data = np.random.rand(2, 1, 4, 4)*100//1 # number, channel, height, widthを表す
print('========== input_data ===========\n', input_data)
print('==============================')
filter_h = 3
filter_w = 3
stride = 1
pad = 0
col = im2col(input_data, filter_h=filter_h, filter_w=filter_w, stride=stride, pad=pad)
print('============= col ==============\n', col)
print('==============================')
- im2colのcol.transposeをコメントアウト
- 途中状態となっていることがわかる。
- col.transposeにより最終的に得たい状態に変換されている
- 確認事項
- 各行は2x2の4列になっている。(フィルタサイズ)
- 0が埋められている(パディング)
- ストライドが適用されている
- フィルタサイズ、パディング、ストライドをいじって確認
- フィルタサイズ:3x3 → 2x2
- パディング :0 → 1
- ストライド:1 → 2
- im2colの確認で出力したcolをimageに変換して確認しよう
- 単純に元に戻るかと思ったが、どうやら違うらしい・・
# 2次元配列を画像データに変換
def col2im(col, input_shape, filter_h, filter_w, stride=1, pad=0):
# N: number, C: channel, H: height, W: width
N, C, H, W = input_shape
# 切り捨て除算
out_h = (H + 2 * pad - filter_h)//stride + 1
out_w = (W + 2 * pad - filter_w)//stride + 1
col = col.reshape(N, out_h, out_w, C, filter_h, filter_w).transpose(0, 3, 4, 5, 1, 2) # (N, filter_h, filter_w, out_h, out_w, C)
img = np.zeros((N, C, H + 2 * pad + stride - 1, W + 2 * pad + stride - 1))
for y in range(filter_h):
y_max = y + stride * out_h
for x in range(filter_w):
x_max = x + stride * out_w
img[:, :, y:y_max:stride, x:x_max:stride] += col[:, :, y, x, :, :]
return img[:, :, pad:H + pad, pad:W + pad]
# im2colの処理確認
input_data = np.random.rand(2, 1, 4, 4)*100//1 # number, channel, height, widthを表す
print('========== input_data ===========\n', input_data)
print('==============================')
filter_h = 3
filter_w = 3
stride = 1
pad = 0
col = im2col(input_data, filter_h=filter_h, filter_w=filter_w, stride=stride, pad=pad)
print('============= col ==============\n', col)
print('==============================')
# col2imの処理確認
im = col2im(col, input_shape=input_data.shape, filter_h=filter_h, filter_w=filter_w, stride=stride, pad=pad)
print('============= im ==============\n', im)
print('==============================')
- プーリング処理を含む順伝播の処理
-
- 入力データをim2colで変換(高速化のため)
-
- プーリングサイズに合わせてリサイズ
-
- 行ごとに最大値を求める
-
- 整形して戻す
-
def forward(self, x):
N, C, H, W = x.shape
out_h = int(1 + (H - self.pool_h) / self.stride)
out_w = int(1 + (W - self.pool_w) / self.stride)
# xを行列に変換
col = im2col(x, self.pool_h, self.pool_w, self.stride, self.pad)
# プーリングのサイズに合わせてリサイズ
col = col.reshape(-1, self.pool_h*self.pool_w)
# 行ごとに最大値を求める
arg_max = np.argmax(col, axis=1)
out = np.max(col, axis=1)
# 整形
out = out.reshape(N, out_h, out_w, C).transpose(0, 3, 1, 2)
self.x = x
self.arg_max = arg_max
return out
#10. CNNの発展形
###10-1. 要点まとめ
- AlexNet
- ImageNetをつかった分類問題を解くためのモデル。
- ImageNetはスタンフォード大学がインターネット上から画像を集め分類したデータセット。
- ImageNetをつかった分類問題を解くためのモデル。
- CNNとFC(全結合層)のつなぎこみ
- Flatten
- 横方向に単純に並べる
- Global MaxPooling
- 縦×横×チャネルで考えたときに、チャネルごとの平面から最大値をチャネル数分取得する(イメージ) ※Flattenより性能が良い
- Global AvgPooling
- 縦×横×チャネルで考えたときに、チャネルごとの平面の各値の平均値をチャネル数分取得する(イメージ) ※Flattenより性能が良い
- Flatten
###10-2. 演習
- double_comvolution_networkの実行
- DoubleConvNetをアレンジしよう
- Pooling1を減らしたことにより、Conv2の入力サイズが変更になる
- 試行錯誤の末、conv_output_size_2を2倍にすることで実行可能になった・・
- accuracyのグラフを見比べると、Pooling1を減らした方が学習が早くなっているように見える
class DoubleConvNet2:
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=100, 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 / 2 - conv_param_2['filter_size'] + 2 * conv_param_2['pad']) / conv_param_2['stride'] + 1
### ↓変更箇所
conv_output_size_2 *= 2
### ↑変更箇所
print('conv_output_size_2', conv_output_size_2)
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)
# レイヤの生成
#conv - relu - conv - relu - pool - affine - relu - affine - softmax
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['Pool1'] = layers.Pooling(pool_h=2, pool_w=2, stride=2)
### ↑変更箇所
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['Pool2'] = 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()
### 以下略 ###