(E資格)ラビット・チャレンジ 深層学習 day1 レポート

参考資料





レポートの参考

Section1) 入力層〜中間層

要点

  • 入力層: ニューラルネットワークに入力を受け取る箇所
  • 中間層: 入力層から出力層までに入力と重みを掛け合わせたデータを受け取り、活性化関数を通じて次の中間層や出力層にデータを渡す箇所

数式で表すと以下のように表せる.

$$
\boldsymbol{u} = \boldsymbol{W}\boldsymbol{x} + b
$$

$$
f(u)
$$

  • 入力: $x$
  • 重み: $w$
  • バイアス: $b$
  • 中間層の総入力: $u$
  • 中間層の出力: $z$
  • 中間層の活性化関数: $f$

実装

import numpy as np
from common import functions

def print_vec(text, vec):
    print("*** " + text + " ***")
    print(vec)
    print("shape: " + str(vec.shape))
    print("")

# ネートワークを作成
def init_network():

    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])

    print_vec("重み1", network['W1'])
    print_vec("重み2", network['W2'])
    print_vec("バイアス1", network['b1'])
    print_vec("バイアス2", network['b2'])

    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 = functions.softmax(u2)

    print_vec("総入力1", u1)
    print_vec("中間層出力1", z1)
    print_vec("総入力2", u2)
    print_vec("出力1", y)
    print("出力合計: " + str(np.sum(y)))

    return y, z1

# 訓練データ
x = np.array([[1.0, 5.0]])

# 目標出力
d = np.array([[0, 1]])

#  学習率
learning_rate = 0.01
network =  init_network()
y, z1 = forward(network, x)
*** 重み1 ***
[[0.1 0.3 0.5]
 [0.2 0.4 0.6]]
shape: (2, 3)

*** 重み2 ***
[[0.1 0.4]
 [0.2 0.5]
 [0.3 0.6]]
shape: (3, 2)

*** バイアス1 ***
[0.1 0.2 0.3]
shape: (3,)

*** バイアス2 ***
[0.1 0.2]
shape: (2,)

*** 総入力1 ***
[[1.2 2.5 3.8]]
shape: (1, 3)

*** 中間層出力1 ***
[[1.2 2.5 3.8]]
shape: (1, 3)

*** 総入力2 ***
[[1.86 4.21]]
shape: (1, 2)

*** 出力1 ***
[[0.08706577 0.91293423]]
shape: (1, 2)

出力合計: 1.0

確認テスト

入力層から中間層への図式に動物分類の実例を入れてみよう。

確認テスト

次の数式をpythonで書け。

$$
u = w_1 x_1 + w_2 x_2 + w_3 x_3 + w_4 x_4 + b = Wx + b
$$

u1 = np.dot(x, W1) + b1

確認テスト

1_1_forward_propagation.ipynbファイルから中間層を定義しているソースを抜き出せ

z = functions.relu(u)

Section2) 活性化関数

要点

活性化関数

  • ニューラルネットワークにおいて、次の層の出力の大きさを決める非線形の関数.
  • 入力値において次の層への信号のON/OFFや強度を決める働きを持つ.

中間層用の活性化関数

  • ReLU関数
  • シグモイド関数
  • ステップ関数

出力層用の活性化関数

  • ソフトマックス関数
  • 恒等関数
  • シグモイド関数

各関数の定義式

ReLu関数

$$
{\begin{align}
f(x)=
\begin{cases}
x & ( x>0 ) \\
0 & ( x\leq 0 )
\end{cases}
\end{align}
}
$$

シグモイド関数

$$
{f(x)= \frac{1}{1+e^{-x}}
}
$$

ステップ関数

$$
{\begin{align}
f(x)=
\begin{cases}
1 & ( x>0 ) \\
0 & ( x\leq 0 )
\end{cases}
\end{align}
}
$$

ソフトマックス関数

$$
f(x_i) = \frac{e^{x_i}}{\sum_{k=1}^n e^{x_k}}
$$

恒等写像

$$
f(x) = x
$$

実装演習

# 中間層の活性化関数
# シグモイド関数(ロジスティック関数)
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))
# 入力が0.7の時の各関数の出力をみてみる

def print_value(text, value):
    print("*** " + text + " ***")
    print(value)

x = 0.7

print_value("シグモイド関数", sigmoid(x));
print_value("ReLU関数", relu(x));
print_value("ステップ関数", step_function(x));
*** シグモイド関数 ***
0.6681877721681662
*** ReLU関数 ***
0.7
*** ステップ関数 ***
1

確認テスト

線形と非線形の違いを図に書いて簡易に説明せよ。

線形関数では、以下が成り立つが非線形関数では成り立たない。

  • 加法性: $f(x+y) = f(x)+f(y) $
  • 斉次性: $f(kx) = kf(x)$

確認テスト

配布されたソースコードより、$z=f(u)$ に該当する箇所を抜き出せ

z1 = functions.sigmoid(u)

Section3) 出力層

出力層の役割

人間が欲しいデータを出す必要がある.例えば分類問題であれば各クラスに属する確率など.

誤差関数

ニューラルネットワークの出力層で得られたデータと、正解データを比較することでどれくらいあっていたかを計算することができる.例えば二乗和誤差がある.

  • y: 正解データ
  • d: 予測されたデータ

出力層の活性化関数

分類問題の場合、出力層の出力は0から1の範囲に限定し、総和を1となるような活性化関数を用いる.

各タスクで使用される関数が変わる.

回帰問題

  • 活性化関数: 恒等写像
  • 誤差関数: 二乗誤差

二値問題
活性化関数: シグモイド関数
誤差関数: 交差エントロピー

多クラス分類

  • 活性化関数: ソフトマックス関数
  • 誤差関数: 交差エントロピー

数式

ソフトマックス関数

$$
f(\boldsymbol{i},\boldsymbol{u}) = \frac{e^{u_i}}{\sum_{k=1}^K e^{u_k}}
$$

交差エントロピー

$$
E_n(w) = - \sum_{i_1}^I d_i\log{y_i}
$$

実装

# 平均二乗誤差
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

確認テスト

誤差の計算について、なぜ引き算ではなく二乗するか述べよ。
正負の誤差の足し合わせると、全体の誤差を表すのに都合が悪いため。(総和が0になるなど)

下式の1/2はどういう意味を持つか述べよ

$$
E_n(\boldsymbol{w}) = \frac{1}{2}\sum_{j=1}^j (y_j - d_j)^2 = \frac{1}{2}||\boldsymbol{y}-\boldsymbol{d}||^2
$$

誤差逆伝播で実施する誤差関数の微分の計算を簡略化するため。

確認テスト

以下の数式の

  • ① $f(\boldsymbol{i},\boldsymbol{u})$
  • ② $e^{u_i}$
  • ③ $\sum_{k=1}^K e^{u_k}$

に該当するソースコードを示し、1行ずつ処理の説明をせよ

$$
f(\boldsymbol{i},\boldsymbol{u}) = \frac{e^{u_i}}{\sum_{k=1}^K e^{u_k}}
$$

def softmax(x): 
    if x.ndim == 2: 
        x = x.T
        x = x - np.max(x, axis=0)

        # ② 下記の np.exp(x)に部分。
        # ③ 下記の np.sum(np.exp(x), axis=0)の部分。
        y = np.exp(x)/np.sum(np.exp(x), axis=0) 

        # ① y.T。ソフトマックス関数の返り値
        return(y.T) 

    x = x - np.max(x)
        # 下記も ①。ソフトマックス関数の返り値
        return(np.exp(x)/np.sum(np.exp(x))) 

確認テスト

以下の数式の

  • ①$E_n(w)$
  • ② $- \sum_{i_1}^I d_i\log{y_i}$、
    に該当するソースコードを示し、1行ずつ処理の説明をせよ

$$
E_n(w) = - \sum_{i_1}^I d_i\log{y_i}
$$

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 

Section4) 勾配降下法

要点

ニューラルネットワークのパラメータを最適化する方法

$$
w^{t+1} = w^t - \varepsilon \nabla E
$$

$\varepsilon$ : 学習率

  • 学習率が大きすぎる場合、最小値にいつまでも辿り着かず発散してしまう。
  • 学習率が小さい場合は、収束するまでに時間がかかってしまう。

収束性向上のためのアルゴリズム

以下のようなアルゴリズムがある.

  • Momentum
  • AdaGrad
  • Adadelrta
  • Adam

勾配降下法のバリエーション

確率的勾配降下法(SGD)
ランダムに抽出したサンプルの誤差を使う

  • データが冗長の場合の計算コスト軽減
  • 局所極小解に収束するリスクを抑える
  • オンライン学習ができる <-> バッチ学習

ミニバッチ勾配降下法
ランダムに分割したデータの集合(ミニバッチ)$D_t$に属するサンプルの平均誤差
一般的に使用されている手法.断りがなければ勾配降下法はミニバッチ勾配降下法をさす。

  • 確率的勾配降下法のメリットを損なわず、計算機の計算資源を有効利用できる
  • CPUのスレッド並列化やGPUのSIMD並列化によって各ミニバッチを同時に計算できる

実装

Section5の実装パートで合わせて実装

確認テスト

勾配降下法に該当するソースコードを探してみよう

$$
\boldsymbol{\omega}^{(k+1)} = \boldsymbol{\omega}^{(k)} - \varepsilon \nabla E
$$

network[key] -= learning_rate * grad[key]

$$
\nabla E = \dfrac{\partial E}{\partial \boldsymbol{\omega}} = \left[\dfrac{\partial E}{\partial \omega_1}, \cdots, \dfrac{\partial E}{\partial \omega_M}\right] $$

確認テスト

オンライン学習と2行でまとめよ
学習データが入ってくるたびに都度パラメータを更新し、学習を進めていく方法。
(一方、バッチ学習では一度にすべての学習データを使ってパラメータ更新を行う)

確認テスト

以下の数式を図に書いて説明せよ

$$
\boldsymbol{\omega}^{(t+1)} = \boldsymbol{\omega}^{(t)} -\varepsilon \nabla E_t
$$

使用したサンプルの誤差を元に、重みが逐一更新される。

Section5) 誤差逆伝搬法

要点

算出された誤差を、出力層側から順に微分して、前の層、前の層へと伝播して計算する.
微分の連鎖律を用いて、最小限の計算で各パラメータでの微分値を解析的に計算することができる.
ノードのグラフを見ながら出力層から順繰りに遡って微分値を計算していけばよい.

実装

import matplotlib.pyplot as plt

def f(x):
    y = 3 * x[0] + 2 * x[1]
    return y

# 初期設定
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

# 誤差逆伝播(Section5の範囲)
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_sigmoid(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

# サンプルデータを作成
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]['d'] = f(data_sets[i]['x'])

losses = []
# 学習率
learning_rate = 0.07

# 抽出数
epoch = 100

# パラメータの初期化
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)

plt.plot(lists, losses)
# グラフの表示
plt.show()

確認テスト

誤差逆伝播法では不要な再起処理を避けることができる。
すでに行った計算結果を保持しているソースコードを抽出せよ。

# 出力層でのデルタ
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 = np.dot(delta2, W2.T) * functions.d_sigmoid(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)

確認テスト

2つの空欄に該当するソースコードを探せ

$$
\frac{\partial E}{\partial y} \frac{\partial y}{\partial u}
$$

delta2 = functions.d_mean_squared_error(d, y)

$$
\frac{\partial E}{\partial y} \frac{\partial y}{\partial u} \frac{\partial u}{\partial w_{ji}^{(2)}}
$$

grad['W2'] = np.dot(z1.T, delta2)

以上