深層学習(前編1)
Section1 入力層~中間層
- 入力層とは数値データを受け取る層、中間層とは前の層(入力層または中間層)からデータを受け取り次の層へ出力する層のことを言う。中間層は複数ある場合もある。
- 入力層のデータを次の層が受け取る際、各入力にそれぞれ異なるw(重み)を掛けたものの総和とb(バイアス)を足したものが渡される。
- 中間層は前の層から受け取った値を活性化関数と呼ばれる関数の入力値とし、結果として出力された値を次の層へ渡す。
- 入力層から次の層へデータを渡すまでの流れをpythonで記述すると以下のようになる。
u1 = np.dot(x, W1) + b1
- 中間層から値を出力する処理をpythonで記述すると以下のようになる。
def relu(x):
return np.maximum(0, x)
z = functions.relu(u)
def sigmoid(x):
return 1/(1 + np.exp(-x))
z = functions.sigmoid(u)
実装演習
考察
- 重みが0の場合は出力がバイアスの値と同じになる。重みが1の場合は出力が入力値とバイアスの和となる
- 重みの値が大きくなるにつれバイアスが出力に与える影響は小さくなる。逆に重みが0に近いとバイアスが出力に与える影響は大きくなる。
活性化関数
- 入力された値を次の層へ出力する際、値を増減させる非線形の関数。(線形関数とは直線で表せる関数のこと。非線形関数とは直線で表せない、つまり曲線で表す関数のこと)
- 中間層でよく使われる活性化関数はReLU関数、シグモイド関数、ステップ関数がある。
- ステップ関数
- 閾値によって0または1のどちらかを出力する関数。0~1の間を表現できないため線形分離できるものしか学習できなかった。
def step_function(x):
if x > 0:
return 1
else:
return 0
- シグモイド関数
- ステップ関数と違い0~1の間を強弱をつけて表現できるため線形分離不可能なものも学習できる。しかし、極端に大きな値または極端に小さな値になると出力の変化量が小さくなるため、勾配消失問題(詳細は後述)を引き起こす原因となる。
def sigmoid(x):
return 1/(1 + np.exp(-x))
- ReLU関数
- 現在もっとも使われている活性化関数。入力値が0以上の場合は入力値をそのまま出力する。入力値が0未満なら0を出力する関数
- 勾配消失問題の回避とスパース化(詳細は後述)によって良い結果を出力できる。
def relu(x):
return np.maximum(0, x)
実装演習
考察
- ステップ関数は閾値を超えない限り、パラメータは出力に影響を与えない
- シグモイド関数はパラメータの値が小さければ、出力の値が変化しやすい
- 同じパラメータでシグモイド関数とReLU関数を比較すると、パラメータを大きくすればするほどReLU関数の値は大きくなるが、シグモイド関数の値は変化しない
出力層
- 入力層で得たデータに対しての予測値を出力する層
- 分類問題の場合はクラスに所属する確率(0~1の間)が出力される。
- 例)与えられた画像を犬、猫、ネズミのどれかを判断するニューラルネットワークの場合、犬の確率が0.2、猫の確率が0.1、ネズミの確率が0.7などのように出力される(このとき、それぞれの確率の和が1になる)
誤差関数
- 予測値と正解値の間には誤差が生じる。全学習データの誤差を表す関数を誤差関数と呼ぶ。分類問題の誤差関数はクロスエントロピーを用い、回帰問題の場合の誤差関数は平均二乗誤差を用いる。
def mean_squared_error(d, y):
return np.mean(np.square(d - y)) / 2
- 絶対値ではなく二乗した値を使う理由は微分しやすい、計算が容易などの利点があるため。
- 2で割る理由は二乗を微分したとき2が係数となり、2×1/2=1で全体の係数が1となるため計算しやすくなるから
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(d * y[np.arange(batch_size), d] + 1e-7)) / batch_size
- 「1e-7」は分子が0にならないようにするために加算する微小な値
出力層の活性化関数
- 中間層と出力層では使われる活性化関数が違う。中間層では値の強弱を調整する活性化関数を用いたが、出力層では値の大きさ(比率)をそのまま出力する活性化関数を使う。
- 分類問題の場合出力は確率なので0~1の範囲に限定し、総和が1となる活性化関数を使う必要がある。
- 「回帰」
- 活性化関数:恒等写像
- 誤差関数:二乗誤差
- 「二値分類」
- 活性化関数:シグモイド関数
- 誤差関数:交差エントロピー
- 「多クラス分類」
- 活性化関数:ソフトマックス関数
- 誤差関数:交差エントロピー
- 各関数の詳細は以下の通り
- 恒等写像
- 入力された値をそのまま出力する関数
- シグモイド関数
- 中間層で使われる活性化関数と同じ。0~1でそのクラスに所属する確率を表す
- ソフトマックス関数
- 多項分類で用いられる。すべての出力の和が1となる関数
- 二乗誤差
- 前述の誤差関数の項で記述したものと同じ
- 交差エントロピー
- 前述の誤差関数の項で記述したものと同じ
- 恒等写像
- ソフトマックス関数pythonで記述すると以下のようになる
# ソフトマックス関数
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))
勾配降下法
- 深層学習の目的は誤差を最小にするパラメータを求めること、つまり誤差関数を最小にする重みとバイアスを求めること。
- 勾配降下法を使って最適な重みとバイアスを求めることができる。
- ①、②の式をpythonで記述すると以下のようになる
grad = backward(x, d, z1, y) # ①の式
for key in ('W1', 'W2', 'b1', 'b2'):
network[key] -= learning_rate * grad[key] # ②の式
- 勾配降下法とは誤差関数の勾配が0になる方向に向かって少しづづ勾配の大きさを減らしていく方法である
- 現在の勾配から誤差関数を微分した値と学習率の積を減算した値を新しい勾配とする
- 学習率が高ければ勾配の移動が大きくなり最小値にたどり着かず発散する可能性が高くなる。学習率が小さければ勾配の移動も小さくなり最小値にたどり着くのが遅くなってしまう
- 最適な学習率を決め収束性を向上させるための方法としてMomentum、AdaGrad、Adadelta、Adamなどのアルゴリズムがある(詳細は後述)
確率的勾配降下法
- 通常の勾配降下法だとすべての学習データ数だけ計算量が増えてしまう。そのためデータが多い場合計算コストが増えてしまう。その他にも複数ある局所極小解のうち最適でない極小解に収束する可能性があるという欠点がある。これを改善した方法が確率的勾配降下法である
- 確率的勾配降下法とはランダムに選んだ学習データ1つに対し勾配降下法を行う。これを複数回繰り返し最適なパラメータを求める。
- 確率的勾配降下法には勾配降下法のデメリットを改善している。また、すでに作成されたモデルを使い、新たに入手したデータを随時学習してパラメータを更新していくこともできる。これをオンライン学習という。
ミニバッチ勾配降下法
- 学習データをランダムに分割したデータの集合(ミニバッチ)Dtを使い勾配降下法を行う。
- 確率的勾配降下法では学習データを1つずつ取り出して勾配降下法の計算を行うので計算資源を有効活用することができない。ミニバッチ勾配降下法は確率的勾配降下法の利点を損なわずに、CPUやGPUを有効活用し学習をすることができる
- しかし、各パラメータに対して数値微分を使い勾配降下法を行うと、順伝播の計算を繰り返し行う必要があり効率が悪い。そこで誤差逆伝播法を利用する。
誤差逆伝播法
- 順伝播で求めた誤差を出力層側から順に微分することで、各パラメータでの微分値を求める手法。こうすることで不要な計算をせずに微分の計算をすることができる。
- pythonで記述すると以下のようになる
# 誤差逆伝播
def backward(x, d, z1, y):
print("\n##### 誤差逆伝播開始 #####")
grad = {}
W1, W2 = network['W1'], network['W2']
b1, b2 = network['b1'], network['b2']
# 出力層でのデルタ
delta2 = functions.d_sigmoid_with_loss(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)
# b1の勾配
grad['b1'] = np.sum(delta1, axis=0)
# W1の勾配
grad['W1'] = np.dot(x.T, delta1)
print_vec("偏微分_dE/du2", delta2)
print_vec("偏微分_dE/du2", delta1)
print_vec("偏微分_重み1", grad["W1"])
print_vec("偏微分_重み2", grad["W2"])
print_vec("偏微分_バイアス1", grad["b1"])
print_vec("偏微分_バイアス2", grad["b2"])
return grad
- 誤差逆伝播法の計算は以下の通り
実装演習
考察
-
ReLU関数を使った場合は早い段階で学習が終了しているように見える。シグモイド関数を使った場合はReLU関数を使った場合に比べ、誤差のばらつきが少なく学習の終了が遅い
-
入力値の幅が大きいと学習が進まなくなった。学習データの違いによって予測結果が大きく変化することがわかった
-
$\frac{ΔE}{Δy}$および$\frac{ΔE}{Δy}\frac{Δy}{Δu}$および$\frac{ΔE}{Δy}\frac{Δy}{Δu}\frac{Δu}{Δw^{(2)}_{ij}}$をpythonで記述すると以下のようになる
# ΔE/Δyの表記
delta2 = functions.d_sigmoid_with_loss(d, y)
# ΔE/Δy*Δy/Δuの表記(ここではyは恒等写像のためΔy/Δu=1)
delta2 = functions.d_sigmoid_with_loss(d, y)
# ΔE/Δy*Δy/Δu*Δu/Δwの表記
grad['W2'] = np.dot(z1.T, delta2)
[次へ(勾配消失問題~学習率最適化手法)]
(https://qiita.com/yuma-k105/items/bd19dc3527a35d681208)