深層学習Day1
ニューラルネットワークの全体像
<確認テスト>
ディープラーニングは、結局何をやろうとしているか2行以内で述べよ。
また、次の中のどの値の最適化が最終目的か、全て選べ。
- ディープラーニングでやろうとしていることは、明示的なプログラムを用いずに、多数の中間層を持つネットワークを用いて、入力値から出力値に変換するモデルを構築すること。
- 最終的に最適化したいのは、重み・バイアスの2つ。
<確認テスト>
次のネットワークを紙に書け。
入力層:2ノード1層
中間層:3ノード2層
出力層:1ノード1層
Section1 入力層~出力層
ニューラルネットワークの入力層とは、ニューラルネットワークへの入力データ。(=モデル自体への入力値)
この入力値に重みをかけてバイアスを加えた値が、次の層(中間層)へと渡される。
最後の層に渡されたものが、出力値として出てくる(出力層)。
中間層や出力層では、活性化関数と呼ばれるものをかけて値を変換する(後述)。
<確認テスト>
図式に動物分類の実例を入れてみる。
<確認テスト>
この数式をpythonで書け
import numpy as np
u = np.dot(x, w) + b
<確認テスト>
1-1のファイルから、中間層の出力を定義しているソースを抜き出せ。
z = functions.relu(u)
print_vec("中間層出力", z)
実装演習
単層・単ユニットのニューラルネットワークを実装
import numpy as np
from common import functions
# 重み
W = np.array([[0.1], [0.2]])
# バイアス
b = 0.5
# 入力値
x = np.array([2, 3])
# 総入力
u = np.dot(x, W) + b
# 中間層出力
z = functions.relu(u)
Section2 活性化関数
ニューラルネットワークにおいて、次の層への出力の大きさを決める非線形の関数。
入力値の値によって、次の層への信号のON/OFFや強弱を定める働きを持つ。
<確認テスト>
線形と非線形の違いを図に書いて簡易に説明せよ。
グラフが直線になるものが線形、曲線になるものが非線形。
-
中間層用の活性化関数
- ReLU
- Sigmoid
- ステップ関数
-
出力層用の活性化関数
- Softmax
- 恒等関数
- Sigmoid
実装演習
活性化関数の実装
# ReLU
def relu(x):
return np.maximum(0, x)
# ステップ関数
def step(x):
if x>0:
return 1
else:
return 0
# Sigmoid関数
def sigmoid(x):
return 1 / (1 + np.exp(-x))
<確認テスト>
配布されたソースコードより該当する箇所を抜き出せ。
z = functions.sigmoid(u)
Section3 出力層
モデルに入力データを入れたときの推定結果を出力する層。
分類であれば、各クラスに属する確率を出力。回帰であれば、目的変数の推定値を出力、といった具合。
この出力層の結果を用いて、モデルの精度を評価する。そのために使われる関数が誤差関数。
一般的には、以下の式で示される平均二乗誤差がよく用いられる。
E_n(\boldsymbol{w}) = \frac{1}{2} \Sigma_{j=1}^{l}(y_j-d_j)^2 = \frac{1}{2}||\boldsymbol{y}-\boldsymbol{d}||^2
ここで、$\boldsymbol{y}$は出力結果、$\boldsymbol{d}$は正解データを表している。
<確認テスト>
なぜ引き算ではなく二乗するのか答えよ
上式の1/2はどういう意味を持つか答えよ
⇒各データでの推定結果と正解の誤差は、正負両方になり得るので、打ち消しあって誤差が小さくならないように、2乗して総和を取ることで、すべてのデータに対する誤差を正しく計算するため。
⇒1/2は誤差逆伝播する際の微分計算を簡単にするためのもので、本質的な意味はない。
<確認テスト>
①~③の数式に該当するソースコードを示し、1行ずつ処理の説明をせよ。
以下のコード中に示す。
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) #分子が②、分母が③。この値が0~1に収まるため、確率とみなせる。
return y.T
x = x - np.max(x) # オーバーフロー対策
return np.exp(x) / np.sum(np.exp(x))
<確認テスト>
①~②の数式に該当するソースコードを示し、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(y[np.arange(batch_size), d] + 1e-7)) / batch_size #②の部分。
実装演習
ネットワークを作成(誤差逆伝播も含む)
def init_network():
print("##### ネットワークの初期化 #####")
network = {}
network['W1'] = np.array([
[0.1, 0.3, 0.5],
[0.2, 0.4, 0.6]
])
network['W2'] = np.array([
[0.1, 0.4],
[0.2, 0.5],
[0.3, 0.6]
])
network['b1'] = np.array([0.1, 0.2, 0.3])
network['b2'] = np.array([0.1, 0.2])
return network
# 順伝播
def forward(network, x):
print("##### 順伝播開始 #####")
W1, W2 = network['W1'], network['W2']
b1, b2 = network['b1'], network['b2']
u1 = np.dot(x, W1) + b1
# 中間層の活性化関数
# ReLU関数
z1 = functions.relu(u1)
u2 = np.dot(z1, W2) + b2
y = functions.softmax(u2)
print("出力合計: " + str(np.sum(y)))
return y, z1
# 誤差逆伝播
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)
return grad
# 訓練データ
x = np.array([[1.0, 5.0]])
# 目標出力
d = np.array([[0, 1]])
# 学習率
learning_rate = 0.01
network = init_network()
y, z1 = forward(network, x)
# 誤差
loss = functions.cross_entropy_error(d, y)
grad = backward(x, d, z1, y)
for key in ('W1', 'W2', 'b1', 'b2'):
network[key] -= learning_rate * grad[key]
# ここまでで重み・バイアスが更新されている。
Section4 勾配降下法
深層学習における目的は、ネットワークにデータを学習させることで、誤差を最小にするネットワークを作成すること。
よって、誤差を最小にするようなパラメータを求めるために行うのが、勾配降下法。
主に以下の3種類。
- 勾配降下法
- 確率的勾配降下法
- ミニバッチ勾配降下法
単純な勾配降下法は、全データをモデルに与えて得られた誤差を計算する。
<確認テスト>
勾配降下法に該当するソースコードを探す。
network[key] -= learning_rate * grad[key]
grad = backward(x, d, z1, y)
確率的勾配降下法では、ランダムに抽出したサンプルの誤差を用いて誤差を計算する。この方法のメリットは、
- データが冗長な場合の計算コストの軽減
- 望まない局所極小解に収束するリスクの軽減
- オンライン学習ができる
ことが挙げられる。
<確認テスト>
オンライン学習とは何か。2行でまとめよ。
⇒モデルにその都度データを与えて学習させる学習手法。学習データが入ってくる都度パラメータを更新し、学習を進めていく。
ミニバッチ勾配降下法では、ランダムに分割したデータの集合(ミニバッチ)に属するサンプルの平均誤差を取る。
これによるメリットは、
- 確率的勾配降下法のメリットを損なわず、計算機の計算資源を有効活用できること。
<確認テスト>
$\boldsymbol{w}^{t+1}=\boldsymbol{w}^{t}-\varepsilon \nabla E_t$
この数式の意味を図に書いて説明せよ。
⇒重み更新の数式。
実装演習
実際に、
f(x,y) = x^2 + y^2
として、$(3.0, 4.0)$を初期位置として勾配法によりパラメータを更新してみる。
import numpy as np
# 関数の定義
def function(x):
return x[0]**2 + x[1] **2
# 数値微分
def numerical_gradient(f, x):
h = 1e-4
grad = np.zeros_like(x)
for idx in range(x.size):
tmp_val = x[idx]
# f(x + h)の計算
x[idx] = tmp_val + h
fxh1 = f(x)
# f(x - h)の計算
x[idx] = tmp_val - h
fxh2 = f(x)
grad[idx] = (fxh1 - fxh2) / (2 * h)
# 値を元に戻す
x[idx] = tmp_val
return grad
# 勾配法による最適化
def gradienr_descent(f, init_x, lr=0.01, step_num=100):
x = init_x
for i in range(step_num):
grad = numerical_gradient(f, x)
x -= lr * grad
return x
init_x = np.array([3.0,4.0])
gradienr_descent(function, init_x, lr=0.1)
この結果は、
array([6.11110793e-10, 8.14814391e-10])
となり、限りなく0に近づいていることがわかる。
Section5 誤差逆伝播法
ニューラルネットワークにおいて、各重み(パラメータ)の微分値を直接計算しようとすると、計算量が膨大になってしまう。そこで利用するのが誤差逆伝播法。
算出された誤差を、出力層側から順に微分し、前の層へと伝播させていく手法。
最小限の計算で各パラメータでの微分値を解析的に計算していく。
<確認テスト>
誤差逆伝播法では不要な再帰的処理を避けることができる。
既に行った計算結果を保持しているソースコードを抽出せよ。
# 出力層でのデルタ
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)
<確認テスト>
2つの空欄に該当するソースコードを探せ
delta2 = functions.d_mean_squared_error(d, y)
grad['W2'] = np.dot(z1.T, delta2)
実装演習
(Section3の実装演習に含まれる)