Help us understand the problem. What is going on with this article?

【ラビットチャレンジ(E資格)】深層学習(day1)

はじめに

2021/2/19・20に実施される日本ディープラーニング協会(JDLA)E資格合格を目指して、ラビットチャレンジを受講した際の学習記録です。

ラビットチャレンジは「現場で潰しが効くディープラーニング講座」の通学講座録画ビデオを編集した教材を活用したコースです。
質問等のサポートはありませんが、E資格受験のための格安(2020年6月時点での最安値)の講座です。

詳細は以下のリンクからご確認ください。

科目一覧

応用数学
機械学習
深層学習(day1)
深層学習(day2)
深層学習(day3)
深層学習(day4)

全体像

ディープラーニングは、結局何をやろうとしているのか。
→ 重みとバイアスを最適化して、誤差を最小化するパラメータを発見すること。

  • 入力層:2ノード1層、中間層:3ノード2層、出力層:1ノード1層のネットワーク スクリーンショット 2020-07-18 9.27.26.png

【事前に用意する情報】
入力: $x_n=[x_{n1}…x_{ni}]$
訓練データ: $d_n=[d_{n1}…d_{nk}]$
【多層ネットワークのパラメータ】
$$ w^{(l)} = \left\{ \begin{array} \\
重み: & W^{(l)}= \left(
\begin{array}{ccccc}
w_{11}^{(l)} & \cdots & w_{1j}^{(l)} \\
\vdots & \ddots & \vdots \\
w_{j1}^{(l)} & \cdots & w_{jj}^{(l)} \\
\end{array}
\right)\\
バイアス: & b^{(l)}=[b_1^{(l)}…b_j^{(l)}] \\
\end{array} \right. $$
活性化関数: $f^{(l)}(u^{(l)})=[f^{(l)}(u_1^{(l)})…f^{(l)}(u_j^{(l)})]$
中間層出力: $z^{(l)}=[z_1^{(t)}…z_K^{(t)}]=f^{(l)}(u^{(l)})$
総入力: $u^{(f)}=W^{(l)}z^{(l-1)}+b^{(l)}$
出力: $y_n^{(t)}=[y_{n1}^{(t)}…y_{nK}^{(t)}]=z^{(L)}$
誤差関数: $E_n(w)$
→ $\nabla E_n(w)=\frac{\partial E}{\partial w}$
→ $w^{(t+1)}=w^{(t)}-\epsilon\nabla E_n(w)$

入力層ノードのインデックス: $i \, (=1…L)$
中間層ノードのインデックス: $j \, (=1…J)$
出力層ノードのインデックス: $k \, (=1…K)$
層のインデックス: $l \, (=1…L)$
訓練データのインデックス: $n \, (=1…N)$
試行回数のインデックス: $t \, (=1…T)$

Section1:入力層〜中間層

入力層

$$ z_i^{(1)} = x_i, \; z^{(1)} = x $$
入力ユニットは活性化関数を持たず、単に値 $x_i$ を出すだけ。
入力層はニューラルネットの層数には数えない。

中間層

第 $l$ 層中の $j$ 番目のユニットを考えると、第 $l-1$ 層の各ユニットからの入力 $z_i^{(l-1)}$ を入力として受け入れるので、活性は
$$ u_j^{(l)} = \sum_i w_{ji}^{(l)} z_{i}^{(l-1)} $$
となる。

第 $l-1$ 層のユニット $i$と第 $l$ 層のユニット $j$ の間の結合の重み $w_{ji}^{(l)}$ を $(j,i)$ 成分とする重み行列
$$ W^{(l)} = \left(
\begin{array}{ccccc}
w_{11}^{(l)} & w_{12}^{(l)} & \cdots \\
w_{21}^{(l)} & w_{22}^{(l)} & \\
\vdots & & \ddots \\
\end{array}
\right) $$
を導入し、さらにバイアスもまとめて縦ベクトル $b^{(l)}$

$$ u^{(l)} = W^{(l)}z^{(l-1)}, \; z^{(l)}=f^{(l)}\bigl(u^{(l)}+b^{(l)}\bigl) $$

入力層:2ノード1層、中間層:3ノード2層、出力層:1ノード1層のネットワーク
import numpy as np

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

# ReLU関数
def relu(x):
    return np.maximum(0, x)    

# ウェイトとバイアスを設定
# ネートワークを作成
def init_network():
    print("##### ネットワークの初期化 #####")
    network = {}

    network['W1'] = np.random.rand(2,3)
    network['W2'] = np.random.rand(3,3)
    network['W3'] = np.random.rand(3,1)

    network['b1'] = np.random.randn(3)
    network['b2'] = np.random.randn(3)
    network['b3'] = np.random.randn(1)

    print_vec("重み1", np.round(network['W1'], 2))
    print_vec("重み2", np.round(network['W2'], 2) )
    print_vec("重み3", np.round(network['W3'], 2))
    print_vec("バイアス1", np.round(network['b1'], 2))
    print_vec("バイアス2", np.round(network['b2'], 2))
    print_vec("バイアス3", np.round(network['b3'], 2))

    return network

# プロセスを作成
def forward(network, x):

    print("##### 順伝播開始 #####")

    W1, W2, W3 = network['W1'], network['W2'], network['W3']
    b1, b2, b3 = network['b1'], network['b2'], network['b3']

    # 1層の総入力
    u1 = np.dot(x, W1) + b1

    # 1層の総出力
    z1 = functions.relu(u1)

    # 2層の総入力
    u2 = np.dot(z1, W2) + b2

    # 2層の総出力
    z2 = functions.relu(u2)

    # 出力層の総入力
    u3 = np.dot(z2, W3) + b3    

    # 出力層の総出力
    y = u3

    print_vec("中間層1の総入力", np.round(u1,2))
    print_vec("中間層1の総出力", np.round(z1,2))
    print_vec("中間層2の総入力", np.round(u2,2))
    print_vec("中間層2の総出力", np.round(z2,2))
    print_vec("出力層の総入力", np.round(u3,2))
    print("出力層の総出力: " + str(np.round(y,2)))

    return y, z1, z2

# 入力値
x = np.random.randint(0,2,2)
print_vec("入力", x)

# ネットワークの初期化
network =  init_network()

y, z1, z2 = forward(network, x)

Section2:活性化関数

  • ステップ関数
    $$ f(x) = \left\{ \begin{array} \\
    1 & (x \geq 0) \\
    0 & (x \lt 0) \\
    \end{array} \right. $$
    閾値を超えたら発火する関数であり、出力は常に1か0である。
    課題:0〜1の間の値を表現できず、線形分離可能なものしか学習できない。

  • シグモイド関数
    $$ f(u) = \frac{1}{1+e^{-u}} $$
    0〜1の間を緩やかに変化する関数で、ステップ関数ではON/OFFしかない状態に対し、信号の強弱を伝えられるようになり、ニューラルネットワーク普及のきっかけとなった。
    課題:大きな値では出力の変化が微小なため、勾配消失問題を引き起こすことがある。0になることはないので、計算リソースが常に食われてしまう。

step/sigmoid_function
import numpy as np
import matplotlib.pyplot as plt

x = np.arange(-10, 10, 0.1)

# step_function
def step(x):
    return np.where( x > 0, 1, 0) 

y_step = step(x)
plt.plot(x, y_step,label='step_function')

# sigmoid_function
def sigmoid(x):
    return 1/(1+np.exp(-x))

y_sigmoid = sigmoid(x)
plt.plot(x, y_sigmoid,label='sigmoid_function')
plt.legend()

image.png

  • ReLU関数 $$ f(x) = \left\{ \begin{array} \\ x & (x > 0) \\ 0 & (x \leq 0) \\ \end{array} \right. $$ 勾配消失問題の回避とスパース化に貢献することで良い成果をもたらしている。
relu_function
# relu_function
def relu(x):
    return np.maximum(0,x)

x = np.arange(-5, 5, 0.1)
y_relu = relu(x)

plt.plot(x, y_relu,c='g',label='relu_function')
plt.legend()

image.png

Section3:出力層

誤差関数

$$ E_n(w) = \frac{1}{2} \sum_{j=1}^J(y_j-d_j)^2 = \frac{1}{2} ||(y-d)||^2 $$
なぜ引き算ではなく、二乗するか → 値を正にするため
$\frac{1}{2}$ はどういう意味を持つか → 微分を可能にするため

出力層の活性化関数

回帰 二値分類 多クラス分類
活性化関数 恒等写像 シグモイド関数 ソフトマックス関数
活性化関数(式) $f(u)=u$ $f(u)=\frac{1}{1+e^{-u}}$ $f(i,u)=\frac{e^{u_i}}{\sum_{k=1}^K e^{u_k}}$
誤差関数 二乗誤差 交差エントロピー 交差エントロピー

Section4:勾配降下法

深層学習の目的は、学習を通して誤差を最小にするネットワークを作成すること
→ 誤差 $E(w)$ を最小化するパラメータ $w$ を発見すること

$$ \text{誤差勾配} \quad \nabla E = \frac{\partial E}{\partial w} = \Bigl[\frac{\partial E}{\partial w_1}…\frac{\partial E}{\partial w_M}\Bigl] $$

勾配降下法 確率的勾配降下法(SGD) ミニバッチ勾配降下法
$w^{(t+1)}=w^{(t)}-\epsilon\nabla{E}$ $w^{(t+1)}=w^{(t)}-\epsilon\nabla{E_n}$ $w^{(t+1)}=w^{(t)}-\epsilon\nabla{E_t}, \; E_t=\frac{1}{N_t}\sum_{n \in D_t}{E_n}, \; N_t= D_t $
特徴 全サンプルの平均誤差 ランダムに抽出したサンプルの誤差 ランダムに分割したデータの集合(ミニバッチ)$D_t$に属するサンプルの平均誤差
メリット データが冗長な場合の計算コストの軽減。望まない局所極小解に収束するリスクの軽減。オンライン学習ができる。 確率的勾配法のメリットを損なわず、計算機の計算資源を有効利用できる。
Stochastic_Gradient_Descent
import numpy as np
import matplotlib.pyplot as plt

# ReLU関数
def relu(x):
    return np.maximum(0, x)    

# 平均二乗誤差
def mean_squared_error(d, y):
    return np.mean(np.square(d - y)) / 2

# ReLU関数の導関数
def d_relu(x):
    return np.where( x > 0, 1, 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 f(x):
    y = 3 * x[0] + 2 * x[1]
    return y

# 初期設定
def init_network():
    network = {}
    nodesNum = 5
    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 = relu(u1)
    u2 = np.dot(z1, W2) + b2
    y = u2

    return z1, y

# 誤差逆伝播
def backward(x, d, z1, y):
    grad = {}

    W1, W2 = network['W1'], network['W2']
    b1, b2 = network['b1'], network['b2']

    # 出力層でのデルタ
    delta2 = 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) * 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

# サンプルデータを作成
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.05

# 抽出数
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 = mean_squared_error(d, y)
    losses.append(loss)

print("##### 結果表示 #####")    
lists = range(epoch)

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

image.png

Section5:誤差逆伝播法

誤差勾配を数値微分 $ \frac{\partial E}{\partial w_m} = \frac{E(w_m+h)-E(w_m-h)}{2h}$で計算すると、各パラメータ $w_m$ それぞれについて $E(w_m+h)$ や $E(w_m-h)$ を計算するために、順伝播の計算を繰り返し行う必要があり負荷が大きい。
そこで、誤差逆伝播法を利用する。
誤差逆伝播法とは、算出された誤差を出力層側から順に微分し、前の層へと伝播することにより、不要な再帰的計算を避けて各パラメータの微分値を計算する手法。

yoshikou
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away