1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

ラビット・チャレンジ(深層学習 前編 day1&2)

Last updated at Posted at 2021-07-18

参考図書

  • 斎藤 康毅 (2016) 『ゼロから作るDeep Learning ―Pythonで学ぶディープラーニングの理論と実装』, オライリー・ジャパン.
  • Aurélien Géron, 下田倫大 監訳, 長尾高弘 訳 (2020)『scikit-learn、Keras、TensorFlowによる実践機械学習 第2版』, オライリー・ジャパン.

[day1-1] 入力層〜中間層

入力層から中間層にデータが変換されながら行って、中間層の活性化関数を通って出力層にデータが移る。入力層は第0層となる。

入力層

ニューラルネットワークにデータを入力する層である。入力を受け取るひとつひとつを

入力~\boldsymbol{x} =
\begin{pmatrix}
 x_1 \\
 \vdots \\
 x_n
\end{pmatrix}

中間層(隠れ層)

入力層から入ったデータ$~\boldsymbol{x}~$に重み$~\boldsymbol{W}~$をかけたものの総和とバイアス$~b~$の和を総入力$~u~$とし、これを活性化関数$~f(u)~$に入力し、その結果を出力$~z~$として出す層

\begin{align}
重み~\boldsymbol{W} &=
\begin{pmatrix}
 w_1 \\
 \vdots \\
 w_n
\end{pmatrix} \\
~\\

総入力~u &= w_1x_1+w_2x_2+\cdots+w_nx_n+b \\
&=\boldsymbol{W}\boldsymbol{x}+b \\

~\\
出力~z&=f(u)
\end{align}

確認テスト

[Page.13]
問題:この図式に動物分類の実例を入れてみよう。
解答:
スクリーンショット 2021-07-18 4.38.45.png

[Page.15]
問題:この数式をPythonで書け。
解答:u = np.dot(x, W) + b

[Page.17]
問題:1-1のファイルから中間層の出力を定義しているソースを抜き出せ。
解答:z2 = functions.relu(u2)

実装演習

順伝播(単層・単ユニット)

1_1_forward_propagation.ipynb 順伝播(単層・単ユニット)
# 重み
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)

実行結果
スクリーンショット 2021-07-18 2.54.59.png

[day1-2] 活性化関数

中間層用の活性化関数

ステップ関数 シグモイド関数 ReLU関数
$f(x)=\bigg\{\begin{array}{ii}1 & (x\geq 0))\\0 & (x\leq 0))\end{array}$ $ f(u)=\dfrac{1}{1 + e^{-u}}\quad\quad~~$ $f(x)=\bigg\{\begin{array}{ii}x & (x\gt 0))\\0 & (x\leq 0))\end{array}$
現在では活性化関数として使われることは少ない。 非線形な活性化関数として登場。 今もっとも使われている関数。
0〜1 の間を表現できず、線形分離可能なものしか学習できなかった。 大きな値では出力の変化が微小なため、__勾配消失問題__を引き起こすことがあった。 勾配消失問題の回避とスパース化に貢献することで良い結果をもたらしている。

出力層用の活性化関数

  • ソフトマックス関数
    • 出力がすべての入力のノードからの影響を受ける関数。
    • 確率としての解釈が可能。
      • ソフトマックス関数の出力の範囲:0〜1
      • ソフトマックス関数の出力の総和:1
    • 出力後の値の各要素間の大小関係が変わらない。
  • 恒等関数
    • 入力値をそのまま出力値とする関数
  • シグモイド関数

確認テスト

[Page.20]
問題:線形と非線形の違いを図にかいて簡易に説明せよ。
解答:

線形 非線形
加法性 満たす 満たさない
斉次性 満たす 満たさない
関数のグラフ スクリーンショット 2021-07-18 3.40.07.png スクリーンショット 2021-07-18 3.40.34.png
  • 加法性:$f(x;y)=f(x)+f(y)$
  • 斉次性:$f(kx)=kf(x)$

[Page.27]
問題:配布されたソースコードより該当する箇所を抜き出せ。
解答:z1 = functions.relu(u1)

実装演習

順伝播(3層・複数ユニット)

1_1_forward_propagation.ipynb 順伝播(3層・複数ユニット)
# 順伝播(3層・複数ユニット)

# ウェイトとバイアスを設定
# ネートワークを作成
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['W3'] = np.array([
        [0.1, 0.3],
        [0.2, 0.4]
    ])
    network['b1'] = np.array([0.1, 0.2, 0.3])
    network['b2'] = np.array([0.1, 0.2])
    network['b3'] = np.array([1, 2])

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

    return network

# プロセスを作成
# x:入力値
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", u1)
    print_vec("中間層出力1", z1)
    print_vec("総入力2", u2)
    print_vec("出力1", z1)
    print("出力合計: " + str(np.sum(z1)))

    return y, z1, z2

# 入力値
x = np.array([1., 2.])
print_vec("入力", x)

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

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

実行結果
スクリーンショット 2021-07-18 5.04.48.png

[day1-3] 出力層

最終的に人間が解釈できる各クラスの確率を出力する層の役割である。

誤差関数(損失関数)

ニューラルネットワークから出力されるデータと正解データを比較して、どれくらい間違っているかを数値として出力する関数である。(二乗和誤差関数など)

解く問題により使用する誤差関数が決まっている。

  • 分類問題では、クロス(交差)エントロピー誤差を用いる。

  • 回帰問題では、平均二乗誤差を用いる。

__訓練データサンプルあたりの誤差__

\begin{array}{ii}
E_n(w)=-\sum_{i=1}^I d_i \log y_i & 交差エントロピー誤差\\
E_n(w)=\dfrac{1}{2} \sum_{i=1}^I (y_n-d_n)^2 & 二乗和誤差
\end{array}

学習サイクルあたりの誤差

E(w)=\sum_{n=1}^N E_n

誤差(Error)、損失(Loss)から数式では E や L がよく使われる。

活性化関数

  • 出力層と入力層での活性化関数の違い
    • 値の強弱
      • 中間層:閾値の前後で信号の強弱を調整
      • 出力層:信号の大きさ(比率)はそのままに変換
    • 確率出力
      • 分類問題の場合、出力層の出力は0〜1の範囲に限定し、総和を1とする必要がある。
回帰 二値分類 多クラス分類
   恒等写像   
$~f(u)=u~$
  シグモイド関数  
$~f(u)=\dfrac{1}{1 + e^{-u}}~$
 ソフトマックス関数 
$~f(i,u)=\dfrac{e^{u_i}}{\sum_{k=1}^K e^{u_k}}~$

確認テスト

[Page.38]
問題:なぜ、引き算でなく二乗するか述べよ
   下式の1/2はどういう意味を持つか述べよ
解答:

  • 全体の誤差を集計するとき、ここの誤差の値は誤差の大きさ(絶対値など)のみで表現されていた方が集計しやすいが、引き算だと正負の両方で誤差がでてしまい、場合によっては誤差が大きいにも関わらす、集計した時に正負の誤差が打ち消しあって少なくなったりする。このため、2条することにより必ず正の値になるようにする。絶対値はコンピューターに実装する時に場合分けなどが必要となり、処理が複雑になるため使われない。
  • ニューラルネットワークの誤差逆伝播の計算など微分をすることがあり、その際に2条を微分した時の係数を打ち消すことができるよう1/2がつけられている。

[Page.45]
問題:①~③の数式に該当するソースコードを示し、一行づつ処理の説明をせよ。
解答:
 ① def softmax(x):
 ② np.exp(x)
 ③ np.sum(np.exp(x))

[Page.47]
問題:①~②の数式に該当するソースコードを示し、一行づつ処理の説明をせよ。
解答:
 ① def cross_entropy_error(d, y):
 ② -np.sum(np.log(y[np.arange(batch_size), d] + 1e-7))

実装演習

多クラス分類(2-3-4ネットワーク)

出力層の活性化関数をソフトマックス関数、誤差関数は交差エントロピー誤差を使う多クラス分類の演習である。

1_1_forward_propagation.ipynb 多クラス分類(2-3-4ネットワーク)
# 多クラス分類
# 2-3-4ネットワーク

# ウェイトとバイアスを設定
# ネートワークを作成
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.7, 1.0],
        [0.2, 0.5, 0.8, 1.1],
        [0.3, 0.6, 0.9, 1.2]
    ])
    network['b1'] = np.array([0.1, 0.2, 0.3])
    network['b2'] = np.array([0.1, 0.2, 0.3, 0.4])
    
    print_vec("重み1", network['W1'] )
    print_vec("重み2", network['W2'] )
    print_vec("バイアス1", network['b1'] )
    print_vec("バイアス2", network['b2'] )

    return network

# プロセスを作成
# x:入力値
def forward(network, x):
    
    print("##### 順伝播開始 #####")
    W1, W2 = network['W1'], network['W2']
    b1, b2 = network['b1'], network['b2']
    
    # 1層の総入力
    u1 = np.dot(x, W1) + b1

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

    # 2層の総入力
    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., 2.])

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

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

# 出力
y, z1 = forward(network, x)

# 誤差
loss = functions.cross_entropy_error(d, y)

## 表示
print("\n##### 結果表示 #####")
print_vec("出力", y)
print_vec("訓練データ", d)
print_vec("誤差",  loss)
softmax
# 出力層の活性化関数
# ソフトマックス関数
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))
cross_entropy_error
# クロスエントロピー
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

実行結果

スクリーンショット 2021-07-18 14.23.12.png

[day1-4] 勾配降下法

勾配降下法は、ニューラルネットワークを学習させる手法である。

深層学習の目的は誤差$~E(w)~$を最小化するパラメータ$~w~$を発見することであり、勾配降下法はこれを実現するためのものである。

勾配降下法

\begin{array}{ii}
勾配降下法 & \boldsymbol{w}^{(t+1)} = \boldsymbol{w}^{(t)} - \epsilon\nabla E \quad \small (\epsilon:学習率) \\
& \nabla E = \dfrac{\partial E}{\partial \boldsymbol{w}} = \bigg[ \dfrac{\partial E}{\partial w_1} \cdots \dfrac{\partial E}{\partial w_M} \bigg] ~\quad\quad
\end{array}

学習率

学習率の値によって学習の効率が大きく異なる

学習率$~\epsilon~$ 学習の効率への影響
大き過ぎる 最小値にいつまでもたどり着かず発散してしまう。
小さ過ぎる 発散することはないが、小さすぎると収束するまでに時間がかかってしまう。

確率的勾配降下法(SGD)

勾配降下法が全サンプルの平均誤差であるのに対し、__確率的勾配降下法はランダムに抽出したサンプルの誤差__である。

\begin{array}{ii}
確率的勾配降下法 & \boldsymbol{w}^{(t+1)} = \boldsymbol{w}^{(t)} - \epsilon\nabla E_n \quad 
\end{array}

メリット

  • データが冗⻑な場合の計算コストの軽減
  • 望まない局所極小解に収束するリスクの軽減
  • オンライン学習ができる

ミニバッチ勾配降下法

確率的勾配降下法がランダムに抽出したサンプルの誤差であるのに対し、__ミニバッチ勾配降下法はランダムに抽出したデータの集合(ミニバッチ)$D_t~$に属するサンプルの平均誤差__である。

\begin{array}{ii}
ミニバッチ勾配降下法 & \boldsymbol{w}^{(t+1)} = \boldsymbol{w}^{(t)} - \epsilon\nabla E_t \\
& E_t = \dfrac{1}{N_t} \displaystyle\sum_{n \in D_t} E_n \\
& N_t = |D_t|
\end{array}

メリット

  • 確率的勾配降下法のメリットを損なわず、計算機の計算資源を有効利用できる

 → CPUを利用したスレッド並列化や GPU を利用した SIMD 並列化
 

誤差勾配の計算

これをどう計算するか?

\nabla E = \dfrac{\partial E}{\partial \boldsymbol{w}} = \bigg[ \dfrac{\partial E}{\partial w_1} \cdots \dfrac{\partial E}{\partial w_M} \bigg]

数値微分

プログラムで微小な数値を生成し、擬似的に微分を計算する一般的な手法

\dfrac{\partial E}{\partial w_m} \approx 
\dfrac{E(w_m+h)-E(w_m-h)}{2h}

デメリット
各パラメータ$w_m$それぞれについて$~E(w_m+h)~$や$~E(w_m-h)~$を計算するので、順伝播の計算を繰り返し行う必要があり、負荷が高い。

これを解決するのが、__誤差逆伝播法__である。

確認テスト

[Page.50]
問題:該当するソースコードを探してみよう。
解答:network[key] -= learning_rate * grad[key]
   grad = backward(x, d, z1, y)

[Page.59]
問題:オンライン学習とは何か2行でまとめよ
解答:学習データが入ってくるたびにパラメータ更新し学習を進めていく方法。
   (バッチ学習では、一度にすべての学習データを使ってパラメータ更新をおこなう。)

[Page.62]
問題:・この数式の意味を図に書いて説明せよ。
解答:
image.png

実装演習

確率勾配降下法

1_3_stochastic_gradient_descent.ipynb
# サンプルとする関数
# yの値を予想するAI

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

# 初期設定
def init_network():
    print("##### ネットワークの初期化 #####")
    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()

    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):
    print("##### 順伝播開始 #####")
    
    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

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

# 誤差逆伝播
def backward(x, d, z1, y):
    print("\n##### 誤差逆伝播開始 #####")    

    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)
    
    print_vec("偏微分_重み1", grad["W1"])
    print_vec("偏微分_重み2", grad["W2"])
    print_vec("偏微分_バイアス1", grad["b1"])
    print_vec("偏微分_バイアス2", grad["b2"])

    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 = 1000

# パラメータの初期化
network = init_network()
# データのランダム抽出
random_datasets = np.random.choice(data_sets, epoch)

# 勾配降下の繰り返し
count = 1
for dataset in random_datasets:
    print("エポック: ", count)
    count += 1
    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()

実行結果
スクリーンショット 2021-07-18 17.19.11.png
スクリーンショット 2021-07-18 17.19.38.png

[day1-5] 誤差逆伝播法

算出された誤差を、出力層側から順に微分し、前の層前の層へと伝播。最小限の計算で各パラメータでの微分値を__解析的に計算__する手法。(__微分の連鎖律__の考え方を利用している。)

image.png

計算結果(=誤差)から微分を逆算することで、不要な再帰的計算を避けて微分を算出できる。

スクリーンショット 2021-07-18 18.00.25.png

確認テスト

[Page.71]
問題:誤差逆伝播法では不要な再帰的処理を避ける事が出来る。
   既に行った計算結果を保持しているソースコードを抽出せよ。
解答:
1)delta2 に計算結果を保持

    # 出力層でのデルタ
    delta2 = functions.d_mean_squared_error(d, y)

2)delta2 を再利用し、delta1 に計算結果を保持

    # 中間層でのデルタ
    delta1 = np.dot(delta2, W2.T) * functions.d_sigmoid(z1)
    delta1 = delta1[np.newaxis, :]

[Page.76]
問題:2つの空欄に該当するソースコードを探せ
解答:

$\dfrac{\partial E}{\partial t} \dfrac{\partial y}{\partial u}$
  → delta1 = np.dot(delta2, W2.T) * functions.d_sigmoid(z1)

$\dfrac{\partial E}{\partial t} \dfrac{\partial y}{\partial u} \dfrac{\partial u}{\partial w_{ji}^{(2)}}$
  → grad['W1'] = np.dot(x.T, delta1)

実装演習

minst

1_4_1_mnist_sample.ipynb
import numpy as np
from data.mnist import load_mnist
import pickle
from common import functions
import matplotlib.pyplot as plt

# mnistをロード
(x_train, d_train), (x_test, d_test) = load_mnist(normalize=True, one_hot_label=True)
train_size = len(x_train)

print("データ読み込み完了")

# 重み初期値補正係数
wieght_init = 0.01 # 変更してみよう
#入力層サイズ
input_layer_size = 784 # 変更してみよう
#中間層サイズ
hidden_layer_size = 40 # 変更してみよう
#出力層サイズ
output_layer_size = 10 # 変更してみよう
# 繰り返し数
iters_num = 1000 # 変更してみよう
# ミニバッチサイズ
batch_size = 100 # 変更してみよ
# 学習率
learning_rate = 0.1 # 変更してみよう
# 描写頻度
plot_interval=10

# 初期設定
def init_network():
    network = {} 
    network['W1'] = wieght_init * np.random.randn(input_layer_size, hidden_layer_size)
    network['W2'] = wieght_init * np.random.randn(hidden_layer_size, output_layer_size)
    network['b1'] = np.zeros(hidden_layer_size)
    network['b2'] = np.zeros(output_layer_size)

    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)
 
    return z1, y

# 誤差逆伝播
def backward(x, d, z1, y):
    grad = {}
    
    W1, W2 = network['W1'], network['W2']
    b1, b2 = network['b1'], network['b2']    
    # 出力層でのデルタ
    delta2 = functions.d_softmax_with_loss(d, y)
    # b2の勾配
    grad['b2'] = np.sum(delta2, axis=0)
    # W2の勾配
    grad['W2'] = np.dot(z1.T, delta2)
    # 1層でのデルタ
    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

# パラメータの初期化
network = init_network()


accuracies_train = []
accuracies_test = []

# 正答率
def accuracy(x, d):
    z1, y = forward(network, x)
    y = np.argmax(y, axis=1)
    if d.ndim != 1 : d = np.argmax(d, axis=1)
    accuracy = np.sum(y == d) / float(x.shape[0])
    return accuracy

for i in range(iters_num):
    # ランダムにバッチを取得    
    batch_mask = np.random.choice(train_size, batch_size)
    # ミニバッチに対応する教師訓練画像データを取得    
    x_batch = x_train[batch_mask]
    # ミニバッチに対応する訓練正解ラベルデータを取得する
    d_batch = d_train[batch_mask]

    z1, y = forward(network, x_batch)
    grad = backward(x_batch, d_batch, z1, y)

    if (i+1)%plot_interval==0:
        accr_test = accuracy(x_test, d_test)
        accuracies_test.append(accr_test)
        
        accr_train = accuracy(x_batch, d_batch)
        accuracies_train.append(accr_train)

        print('Generation: ' + str(i+1) + '. 正答率(トレーニング) = ' + str(accr_train))
        print('                : ' + str(i+1) + '. 正答率(テスト) = ' + str(accr_test))

    # パラメータに勾配適用
    for key in ('W1', 'W2', 'b1', 'b2'):
        network[key]  -= learning_rate * grad[key]

lists = range(0, iters_num, plot_interval)
plt.plot(lists, accuracies_train, label="training set")
plt.plot(lists, accuracies_test,  label="test set")
plt.legend(loc="lower right")
plt.title("accuracy rate")
# グラフの表示
plt.show()

実行結果

日本語フォントの警告”RuntimeWarning: Glyph xxxxxxx missing from current font.”がでたので、応急処置で plt.title("正答率") を plt.title("accuracy rate") に変更した。
(Google Colaboratory での解決方法はわからない。)

スクリーンショット 2021-07-18 19.03.45.png

[day2-1] 勾配消失問題

誤差逆伝播法が下位層に進んでいくに連れて、勾配がどんどん緩やかになっていく。そのため、勾配降下法による、更新では下位層のパラメータはほとんど変わらず、訓練は最適値に収束しなくなる。これを__勾配消失問題__という。

下の左側のグラフ(縦軸:正答率、横軸:学習回数)が勾配消失問題が起きている状態である。右側のグラフは学習回数が増えるごとに正答率が上がっているが、左側はいつまで経っても正答率が上がっていないことが確認できる。
スクリーンショット 2021-07-18 19.53.22.png

勾配消失問題が発生するしくみ

シグモイド関数の微分がとる値の範囲は 0〜0.25 なので、最大の 0.25 と考えても、これが連鎖律で掛け算されていくと、0.25, 0.0625, 0.015625, ... とどんどん 0 に近づいていってしまうのがわかる。これが逆伝播されていくにつれ勾配が消失していってしまうしくみである。
スクリーンショット 2021-07-18 20.26.33.png

勾配消失の解決方法

  • 活性化関数の選択:ReLU関数などを使う。スパース化
    • ReLU関数:入力値が0より大きいとき微分結果は常に1となり、0以下の時は0となる。
  • 重みの初期値設定:Xavier, He を使って重みを初期化する。
    • Xavier:重みの要素を、前の層のノード数の平方根で除算した値
    • He:重みの要素を、前の層のノード数の平方根で除算した値に対し$\sqrt{2}$をかけ合わせた値
  • バッチ正規化:ミニバッチ単位で、入力値のデータの偏りを抑制する手法
    • 活性化関数に値を渡す前後に、バッチ正規化の処理をおこなう層を加える。
    • バッチ正規化層への入力値は、 $\boldsymbol{u}^{(l)}=\boldsymbol{w}^{(l)}\boldsymbol{z}^{(l-1)}+\boldsymbol{b}^{(l)}$ または $\boldsymbol{z}$ 。

確認テスト&例題チャレンジ

[Page.10]
問題:連鎖律の原理を使い、dz/dxを求めよ。
解答:

\dfrac{\partial z}{\partial t} = 2t, \qquad\dfrac{\partial t}{\partial x} = 1 \\
\therefore \dfrac{\partial z}{\partial x} = \dfrac{\partial z}{\partial t} \dfrac{\partial t}{\partial x} = 2t = 2(x+y)

[Page.18]
問題:
シグモイド関数を微分した時、入力値が0の時に最大値をとる。その値として正しいものを選択肢から選べ。
 (1)0.15 (2)0.25 (3)0.35 (4)0.45
解答:(2)0.25
スクリーンショット 2021-07-18 20.19.01.png

[Page.26]
問題:重みの初期値に0を設定すると、どのような問題が発生するか。簡潔に説明せよ。
解答:すべての重みの値が均一に更新されるため、多数の重みをもつ意味がなくなる。

[Page.29]
問題:一般的に考えられるバッチ正規化の効果を2点挙げよ。
解答:① 学習が早くなる
   ② 過学習が抑えられる

[Page.33]
問題:
スクリーンショット 2021-07-18 21.27.21.png
解答:(1) data_x[i:i_end], data_t[i:i_end]

実装演習

batch normalization

2_3_batch_normalization.ipynb(1/2)
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    
2_3_batch_normalization.ipynb(2/2)
(x_train, d_train), (x_test, d_test) = load_mnist(normalize=True)

print("データ読み込み完了")


# batch_normalizationの設定 =======================
use_batchnorm = True
# use_batchnorm = False
# ====================================================

network = MultiLayerNet(input_size=784, hidden_size_list=[40, 20], output_size=10,
                        activation='sigmoid', weight_init_std='Xavier', use_batchnorm=use_batchnorm)

iters_num = 1000
train_size = x_train.shape[0]
batch_size = 100
learning_rate=0.01

train_loss_list = []
accuracies_train = []
accuracies_test = []

plot_interval=10


for i in range(iters_num):
    batch_mask = np.random.choice(train_size, batch_size)
    x_batch = x_train[batch_mask]
    d_batch = d_train[batch_mask]

    grad = network.gradient(x_batch, d_batch)
    for key in ('W1', 'W2', 'W3', 'b1', 'b2', 'b3'):
        network.params[key] -= learning_rate * grad[key]

        loss = network.loss(x_batch, d_batch)
        train_loss_list.append(loss)        
        
    if (i + 1) % plot_interval == 0:
        accr_test = network.accuracy(x_test, d_test)
        accuracies_test.append(accr_test)        
        accr_train = network.accuracy(x_batch, d_batch)
        accuracies_train.append(accr_train)
        
        print('Generation: ' + str(i+1) + '. 正答率(トレーニング) = ' + str(accr_train))
        print('                : ' + str(i+1) + '. 正答率(テスト) = ' + str(accr_test))
                

lists = range(0, iters_num, plot_interval)
plt.plot(lists, accuracies_train, label="training set")
plt.plot(lists, accuracies_test,  label="test set")
plt.legend(loc="lower right")
plt.title("accuracy")
plt.xlabel("count")
plt.ylabel("accuracy")
plt.ylim(0, 1.0)
# グラフの表示
plt.show()

実行結果
スクリーンショット 2021-07-18 21.10.42.png

[day2-2] 学習率最適化手法

学習率は学習効率に大きく影響する。

  • 学習率の値が大きい場合
    • 最適値にいつまでもたどり着かず発散してしまう。
  • 学習率の値が小さい場合
    • 発散することはないが、小さすぎると収束するまでに時間がかかってしまう。
    • 大域局所最適値に収束しづらくなる。

学習率最適化手法を利用して学習率を最適化する。
基本的な動きとしては、初期の学習率を大きく設定し、徐々に学習率を小さくしていく。

手法 数式 概要
勾配降下法 $\boldsymbol{w}^{(t+1)}=\boldsymbol{w}^{(t)}-\epsilon\nabla E$ 誤差をパラメータで微分したものと学習率の積を減算する
モメンタム $V_t=\mu V_{t-1}-\epsilon \nabla E\\ \boldsymbol{w}^{(t+1)}=\boldsymbol{w}^{(t)}-V_t$ 誤差をパラメータで微分したものと学習率の積を減算した後、現在の重みに前回の重みを減算した値と慣性の積を加算する
AdaGrad $h_t=\theta\\ h_t=h_{t-1}+(\nabla E)^2\\ \boldsymbol{w}^{(t+1)}=\boldsymbol{w}^{(t)}-\epsilon \frac{1}{\sqrt{h_t}+\theta}\nabla E$ 誤差をパラメータで微分したものと再定義した学習率の積を減算する
RMSProp $h_t=\alpha h_{t-1}+(1-\alpha)(\nabla E)^2\\ \boldsymbol{w}^{(t+1)}=\boldsymbol{w}^{(t)}-\epsilon \frac{1}{\sqrt{h_t}+\theta}\nabla E$ 誤差をパラメータで微分したものと再定義した学習率の積を減算する

学習率最適化手法のメリット

モメンタム

  • 局所的最適解にはならず、大域的最適解となる。
  • 谷間についてから最も低い位置(最適値)にいくまでの時間が早い

AdaGrad

  • 勾配の緩やかな斜面に対して、最適値に近づける
  • 課題:学習率が徐々に小さくなるので、鞍点(あんてん)問題を引き起こす事があった。

RMSProp

  • 局所的最適解にはならず、大域的最適解となる。
  • ハイパーパラメータの調整が必要な場合が少ない。

Adam

  • 最強らしい。
  • モメンタムおよびRMSPropのメリットを含んだアルゴリズムである。
  • 以下を両方含んだ最適化アルゴリズムである。
    • モメンタムの過去の勾配の指数関数的減衰平均
    • RMSPropの過去の勾配の2乗の指数関数的減衰平均

確認テスト

[Page.44]
問題:モメンタム・AdaGrad・RMSPropの特徴をそれぞれ簡潔に説明せよ。
解答:

  • モメンタム
    • 局所的最適解にはならず、大域的最適解となる。
    • 谷間についてから最も低い位置(最適値)にいくまでの時間が早い
  • AdaGrad
    • 勾配の緩やかな斜面に対して、最適値に近づける
  • RMSProp
    • 局所的最適解にはならず、大域的最適解となる。
    • ハイパーパラメータの調整が必要な場合が少ない。

実装演習

optimizer

2_4_optimizer.ipynb

SGD

実行結果

バッチ正規化なし バッチ正規化あり
use_batchnorm = False use_batchnorm = True
スクリーンショット 2021-07-18 22.40.00.png

バッチ正規化する前は全然ダメだったが、バッチ正規化を使っただけで学習が進んだ!

Momentum

実行結果

バッチ正規化なし バッチ正規化あり
use_batchnorm = False use_batchnorm = True
スクリーンショット 2021-07-18 22.40.21.png

バッチ正規化する前は全然ダメだったが、バッチ正規化を使っただけで学習が進んだ!
SGDのときよりも良い結果がでている。

MomentumをもとにAdaGradを作ってみよう

実行結果

バッチ正規化なし バッチ正規化あり
use_batchnorm = False use_batchnorm = True
スクリーンショット 2021-07-18 22.40.29.png

バッチ正規化する前は全然ダメだったが、バッチ正規化を使っただけで学習が進んだ!
モメンタムと同じくらいの結果となった。

RSMprop

実行結果

バッチ正規化なし バッチ正規化あり
use_batchnorm = False use_batchnorm = True
スクリーンショット 2021-07-18 22.40.39.png

バッチ正規化する前もいい感じだったが、バッチ正規化を使うと最初からいきなり学習が進んだ!

Adam

実行結果

バッチ正規化なし バッチ正規化あり
use_batchnorm = False use_batchnorm = True
スクリーンショット 2021-07-18 22.40.47.png

Adamの場合も、RSMPropと同じように、バッチ正規化を使うと学習の進み方が速くなった!

[day2-3] 過学習

テスト誤差と訓練誤差とで学習曲線が乖離すること。
スクリーンショット 2021-07-18 23.15.02.png

原因

  • パラメータの数が多い。
  • パラメータの値が適切でない。
  • ノードが多い。

つまり、ネットワークの自由度(層数、ノード数、パラメータの値...)が高すぎるということ。

正則化

ネットワークの自由度(層数、ノード数、パラメータの値...)を制約すること。
正則化手法を利用して過学習を抑制する。

Weight decay(荷重減衰)

  • 過学習の原因:重みが大きい値をとることで、過学習が発生することがある。
  • 過学習の解決策:誤差に対して、正則化項を加算することで、重みを抑制する.。

L1 / L2 正則化

\begin{array}{ii}
E_n(\boldsymbol{w})+\dfrac{1}{p}\lambda~||\boldsymbol{x}||_p & 誤差関数にpノルムを加える\\
||\boldsymbol{x}||_p = (|x_1|^p + \cdots + |x_n|^p)^{\frac{1}{p}} & pノルムの計算 \\
~\\
\cdot~~p=1の場合、L1 正則化(ラッソ回帰)という。\\
\cdot~~p=2の場合、L2 正則化(リッジ回帰)という。
\end{array}

ドロップアウト

過学習の原因のひとつとして、ノード数が多い場合が考えられる。

スクリーンショット 2021-07-18 23.53.50.png

ドロップアウトは、ランダムにノードを削除して学習させる方法である。
(データ量を変化させずに、異なるモデルを学習させていると解釈できる。)

確認テスト

[Page.59]
問題:
スクリーンショット 2021-07-18 3.23.41.png
解答:(c)

[Page.64]
問題:下図について、L1正則化を表しているグラフはどちらか答えよ。
スクリーンショット 2021-07-18 3.26.08.png
解答:右側のLasso推定量のグラフ(左側のRidge推定量のグラフはL2正則化)

[Page.65]
問題:
スクリーンショット 2021-07-18 3.27.55.png
解答:(4) param

[Page.67]
問題:
スクリーンショット 2021-07-18 3.29.21.png
解答:(3) np.sign(param)

[Page.69]
問題:
スクリーンショット 2021-07-18 3.30.27.png
解答:(4) image[top:bottom, left:right, :]

実装演習

overfiting

スクリーンショット 2021-07-19 0.04.25.png

weight decay L2

正則化強度(weight_decay_lambda)をいろいろ変えて実行した。
少し変えただけで、結果に大きな差が見られた。 一定の値を超えると、一度正解率が上がるが途中から急降下するようになることがわかった。

0.08 0.1 0.15 0.2

weight decay L1

正則化強度(weight_decay_lambda)をいろいろ変えて実行した。
少し変えただけで、結果に大きな差が見られた。 一定の値を超えると、一度正解率が上がるが途中から急降下するようになることがわかった。

0.001 0.005 0.01 0.012

Dropout

dropout_ratio をいろいろ変えて実行した。
少し変えただけで、結果に大きな差が見られた。 いずれも同じ回数付近で一度正解率が落ち込み、その後また上がっていく傾向が見られた。なぜだろう?

0.1 0.14 0.15 0.2

Dropout + L1

スクリーンショット 2021-07-19 0.08.01.png

全体を通して

典型的な過学習のグラフのような、訓練データの正解率が100%に向かい、検証データの正解率が下がっていくというようなところまでは確認できなかった。

[day2-4] 畳み込みニューラルネットワークの概念

image.png
image.png

畳み込み層

畳み込み層では、画像の場合、縦、横、チャンネルの3次元のデータをそのまま学習し、次に伝えることができる。つまり、3次元の空間情報も学習できるような層が畳み込み層である。
スクリーンショット 2021-07-19 1.17.25.png

畳み込み層の演算概念

畳み込み演算のイメージは以下の通り。
スクリーンショット 2021-07-19 1.19.16.png

バイアス

バイアスは、下図のようにフィルター適用後のデータの全ての要素に対して一律に加算される。またバイアスは、フィルタ適用後のデータに対して、ひとつ(1×1)だけ存在する。
スクリーンショット 2021-07-19 1.20.23.png

パディング

畳み込み層の処理を行う前に、入力データの周囲に固定のデータ(たとえば0など)を埋めること。この主な理由は、出力サイズを調整するためである。

以下の例は、幅1のパディングを行った例である。
スクリーンショット 2021-07-19 1.21.06.png
※ 固定のデータは、0以外でも可能ただし、今回は0とした。

ストライド

フィルターはずらしながら適用していくが、このずらす間隔をストライドという。

以下の例は、ストライドを2としたものである。
スクリーンショット 2021-07-19 1.22.38.png

チャンネル

チャンネルはフィルターの数を示す。
スクリーンショット 2021-07-19 1.23.05.png

全結合で画像を学習した際の課題

画像の場合、縦、横、チャンネルの3次元データだが、1次元のデータとして処理される。
 → RGBの各チャンネル間の関連性が、学習に反映されないということである。

プーリング層

プーリング層の特徴

  • 重み(学習パラメータ)が存在しない。
  • チャンネル数は変化しない。
  • 微小な位置変化に対してロバスト(頑健)

プーリング層の演算概念

プーリング層のイメージは以下の通り。
スクリーンショット 2021-07-19 2.12.57.png

プーリングの種類

  • Max プーリング:対象領域から最大値を取る。
  • Average プーリング:対象領域の平均を計算する。

確認テスト

[Page.95]
問題:
サイズ6×6の入力画像を、サイズ2×2のフィルタで畳み込んだ時の出力画像のサイズを答えよ。なおストライドとパディングは1とする。
解答:7x7

参考動画

実装演習

simple convolution network

2_6_simple_convolution_network_after.ipynb

image to column

高速に処理するため、フィルタの大きさに合わせてデータの並び替えを行う。
具体的には画像データを2次元配列に変換することで、ベクトル演算ができるようにしている。
スクリーンショット 2021-07-19 2.02.08.png

column to image

スクリーンショット 2021-07-19 2.02.46.png

simple convolution network class

実行結果
スクリーンショット 2021-07-19 2.05.40.png

[day2-5] 最新のCNN

AlexNet

AlexNet は CNN の層をシンプルに積み上げたモデル。

「 3つの特徴 」

  • 活性化関数としてReLU関数をつかう。
    • 勾配消失問題の解消、深いモデルを学習できる。
  • サイズ 4096 の全結合層の出力にドロップアウトを使用している。
    • 過学習を防ぐため。
  • GPU で並列計算した。
    • GPU のメモリ不足の解消のため。

この他、LRN という正規化でチャネルごとに別ものになるようにしている。

スクリーンショット 2021-07-19 2.30.58.png

確認テスト

なし

参考動画

実装演習

なし

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?