深層学習day1

0.ニューラルネットワークの構成

 ディープラーニングの目的は重みとバイアスを最適化して、誤差を最小化するパラメータを発見すること。そのk本的な構成がニューラルネットワークである。

f:id:black_07:20210626113939p:plain
ニューラルネットワーク(入力:2ノード1層、中間層:3ノード2層、出力層:1ノード1層
f:id:black_07:20210626114434p:plain
ニューラルネットワークの構成

1.入力層~中間層

入力層

 説明変数(特徴量)の値が入力される個所。
 具体例:犬や猫など動物を分類するとき、説明変数は体長、体重、ひげの本数、平均寿命、などになり、それぞれの値(体長=40cm、体重=10kg、ひげの本数=300本、平均寿命=15年、…)が入力され、中間層へ伝達される。

中間層

 入力層または直前の層の値および重みを内積の形で掛け合わせる。>

f:id:black_07:20210626135240p:plain
中間層
 u = w_{1}x_{1} + w_{2}x_{2} + w_{3}x_{3} + b = W・x + b
 W=\begin{bmatrix} w_{1} \\ w_{2} \\ w_{3} \end{bmatrix}   x=\begin{bmatrix} x_{1} \\ x_{2} \\ x_{3} \end{bmatrix}
なお、pythonでは u の算出式をnumpyライブラリを使って、
u = np.dot(x, W) + b と記述する。

2.活性化関数

ニューラルネットワークにおいて、次の層への出力の大きさを決める非線形の関数。入力値の値によって、次の層への信号のON/OFFや強弱を定める働きをもつ。
非線形、線形の違い
 線形とは、相互間の関係が比例関係にある状態。 グラフを描くと真っ直ぐな線になる。
 非線形とは、相互間の関係が比例関係で表せない状態。

f:id:black_07:20210626142225p:plain
線形と非線形

f:id:black_07:20210626143211p:plain
中間層で使用される活性化関数
 ステップ関数は、  f(x) =\left\{\begin{array}{ll} 1 & (x \geq 0) \\ 0 & (x < 0) \end{array} \right. で表現される。しきい値を超えたら発火し、出力は常に1か0である。線形分離である、簡単なものにしか適用できない。
 シグモイド関数は、  f(x) =\dfrac{1}{(1 + e^{-x})} で表現される。0 ~ 1の間を緩やかに変化する関数で、ステップ関数ではON/OFFしかない状態に対し、信号の強弱を伝えられる。その反面、大きな値では出力の変化が微小なため、勾配消失問題を引き起こす事が課題である。
 RELU関数は、  f(x) =\left\{\begin{array}{ll} x & (x \geq 0) \\ 0 & (x < 0) \end{array} \right. で表現される。今最も使われている活性化関数勾配消失問題の回避とスパース化に貢献することで良い成果をもたらしている。

 入力層から中間層、活性化関数の整理として、順伝播で入力層:4ノード1層、中間層:3ノード1層、中間層活性化関数:シグモイド関数を適用、の事例を計算した。

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

# 重み
W = np.array([
    [0.1, 0.2, 0.3], 
    [0.2, 0.3, 0.4], 
    [0.3, 0.4, 0.5],
    [0.4, 0.5, 0.6]
])

print_vec("重み", W)

# バイアス
b = np.array([0.1, 0.2, 0.3])
print_vec("バイアス", b)

# 入力値
x = np.array([1.0, 5.0, 2.0, -1.0])
print_vec("入力", x)


#  総入力
u = np.dot(x, W) + b
print_vec("総入力", u)

# 中間層出力
z = functions.sigmoid(u)
print_vec("中間層出力", z)

f:id:black_07:20210626151153p:plain
中間層出力結果

3.出力層

 推定結果(構成図中の y)が得られる層。真の出力値(教師データともいう、構成図中の d)との誤差を確認する。誤差の大きさ、特徴に応じて各層の重みをのちに調整する。中間層と同様に前の層の値と重みより内積演算を実施する。活性化関数も適用するが、使用する関数が中間層と異なる。

a.恒等写像

  f(u) = u で表現される。回帰問題によく使用される。

b.シグモイド関数

  f(u) =\dfrac{1}{(1 + e^{-u})} で表現される。前述の通り、0~1の値を取るため、二値分類問題に適用される。

c.ソフトマックス関数

  f(i, u) =\dfrac{e^{u_{i}}}{\sum^{K}_{k=1} e^{u_{k}}} で表現される。3つ以上の多クラス分類問題に適用される。


 誤差関数については、回帰問題では二乗誤差、分類問題では交差エントロピーが一般的に使用される。

a.二乗誤差

  E_{n}(w) = \frac{1}{2} \sum^{J}_{j=1} (y_{j} - d_{j})^{2} で表現される。ここで差の値のままでなく二乗するのは、誤差の向きが正でも負でも評価値を大きくする(誤差が大きいと表示する)ためである。また、1/2を掛けているのは微分するときに二乗の「2」と打ち消すためである。

b.交差エントロピー

  E_{n}(w) = -  \sum^{J}_{j=1} d_{j} \log y_{j} で表現される。真の値である j_{x}において、d_{ j_{x}} = 1である。
 そのとき、出力も y_{j_{x}}=1と一致する場合、  \log y_{j} = 0となり、 d_{j} \log y_{j} = 1*0=0となる、他の jでは、d_{ j} = 0なので交差エントロピーの総和も0になる。
出力が y_{j_{x}}=0.2のように真と異なる場合、  \log y_{j} は負の値となり、 d_{j} \log y_{j} は負の値、交差エントロピーの総和は正の値となる。

4.勾配降下法

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

a.勾配降下法

 w^{(t+1)}= w^{(t)} - \epsilon \nabla E   \nabla E = \dfrac{\partial E}{\partial w} = \begin{bmatrix} \dfrac{\partial E}{\partial w_{1}} & \cdots & \dfrac{\partial E}{\partial w_{M}} \end{bmatrix}
 全サンプルの平均誤差から勾配を取り、学習率 \epsilonを掛けて、重みを修正する。

b.確率的勾配降下法

 w^{(t+1)}= w^{(t)} - \epsilon \nabla E_{n}  
 ランダムに抽出したサンプルの誤差を取る。メリットとしては、データが冗⻑な場合の計算コストの軽減、望まない局所極小解に収束するリスクの軽減、オンライン学習(学習データが入ってくるたびにその都度、新たに入ってきたデータのみを使って学習を行うこと)ができる、が挙げられる。

c.ミニバッチ勾配降下法

 w^{(t+1)}= w^{(t)} - \epsilon \nabla E_{t}   E_{t}= \dfrac{1}{N_{t}} \sum_{n \in D_{t}}E_{n}   N_{t}= |D_{t}|
 ランダムに分割したデータの集合(ミニバッチ) D_{t}に属するサンプルの平均誤差を取る。メリットとしては、確率的勾配降下法のメリットを損なわず、計算機の計算資源を有効利用できることである。

5.誤差逆伝播

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

 最後に演習で入力層:2ノード1層、中間層:3ノード1層、出力層:2ノード1層の2値分類ネットワークで逆誤差伝播を確認した。ポイントになる部分にはコメントを追記した。いかに効率よく勾配および重みの修正しているか、イメージをつかむことができた。

# ウェイトとバイアスを設定
# ネートワークを作成
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])
    
    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 = 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

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

    grad = {}

    W1, W2 = network['W1'], network['W2']
    b1, b2 = network['b1'], network['b2']
    #  出力層でのデルタ(yに対するu2の偏微分)
    delta2 = functions.d_sigmoid_with_loss(d, y)
    print(delta2)
    #  b2の勾配→u2に対するb2の偏微分は1、よってdelta2を保持
    grad['b2'] = np.sum(delta2, axis=0)
    print(grad['b2'])  
    #  W2の勾配→u2に対するw2の偏微分はz1、よって転置したものにdelta2と内積を取る
    grad['W2'] = np.dot(z1.T, delta2)

    #  中間層でのデルタ(yに対するu1の偏微分→u2に対するz1の偏微分はw2、z1に対するu1の偏微分は*の後)
    delta1 = np.dot(delta2, W2.T) * functions.d_relu(z1)
    # b1の勾配→ u1に対するb1の偏微分は1、よってdelta1を保持
    grad['b1'] = np.sum(delta1, axis=0)
    #  W1の勾配 →u1に対するw1の偏微分はx、よって転置したものにdelta1と内積を取る
    grad['W1'] = np.dot(x.T, delta1)
        
    print_vec("偏微分_dE/du2", delta2)
    print_vec("偏微分_dE/du1", delta1)

    print_vec("偏微分_重み1", grad["W1"])
    print_vec("偏微分_重み2", grad["W2"])
    print_vec("偏微分_バイアス1", grad["b1"])
    print_vec("偏微分_バイアス2", grad["b2"])

    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]

print("##### 結果表示 #####")    


print("##### 更新後パラメータ #####") 
print_vec("重み1", network['W1'])
print_vec("重み2", network['W2'])
print_vec("バイアス1", network['b1'])
print_vec("バイアス2", network['b2'])