ラビットチャレンジレポート 深層学習DAY1

Section 1 入力層~活性層

  • 確認テスト

f:id:tibet:20211118121514p:plain
[答え]
ディープラーニングは明示的なプログラムの代わりに、ニューラルネットワークを用いて各種パラメータを最適化することによって、入力値から目的とする出力地に変換する数学モデルを構築すること
最適化の最終目的:③重み[W]、④バイアス[b]


f:id:tibet:20211118124810p:plain
入力層は、図のように入力  x_i及びバイアスbが入力される層である。各入力層から中間層に至るネットワークには、各入力に重みw_iが乗算される。中間層ではそれらを線形結合して総入力uが作られる。
中間層の出力zは、活性化関数f()を用いて、下記のように書ける。
 z=f(u)
zは、次の中間層の入力となる。
一般には、このように中間層(隠れ層ともいう)を複数積層することで、ニューラルネットワークが形作られる。
数式化すると、重みベクトルW, 入力ベクトルxは次のように書ける。
 W=[w_1....w_I^T]
 x=[x_1....x_I^T]
 u=w_1x_1+w_2x_2+..+b=Wx+b

  • 確認テスト:

この図式に動物分類の実例を入れてみよう
[答え]
 x_1=10 体長
 x_2=300 体重
 x_3=300 髭の本数
 x_4=15 毛の平均長
 x_5=50 耳の大きさ

  • 確認テスト:

この式をパイソンで書け
 u=w_1x_1+w_2x_2+..+b=Wx+b
[答え]

u=np.dot(x,W)+b
  • 確認テスト:

1-1のファイルから中間層の出力を定義しているソースを抜き出せ
[答え]

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


実装演習

numpyと関数のインポート及び表示用関数定義

import numpy as np
import functions

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

単相、単ユニットの順伝搬による入力から中間層出力までのモデル

# 重み
W = np.array([[0.1], [0.2]])
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)

出力結果

*** 重み ***
[[0.1 0.2 0.3]
 [0.2 0.3 0.4]
 [0.3 0.4 0.5]
 [0.4 0.5 0.6]]

*** バイアス ***
[0.1 0.2 0.3]

*** 入力 ***
[ 1.  5.  2. -1.]

*** 総入力 ***
[1.4 2.2 3. ]

*** 中間層出力 ***
[0.80218389 0.90024951 0.95257413]

Section2 活性化関数

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

  • 確認テスト

線形と非線形の違いを図に書いて簡易に説明せよ。
[答え]
f:id:tibet:20211118133641p:plain
線形関数は、左図のように入出力の関係が直線のイメージで表され、加法性と斉次性を持つもの。

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

非線形関数は、加法性と斉次性を持たない関数。左図のように入出力の関係が直線にならないイメージ。

活性化関数の種類

中間層用の活性化関数としては、次がよく知られている。

出力層用の活性化関数としては、下記がよく知られている。

活性化関数:ステップ関数

数式
 f(x)= 1(x \geq 0) or 0 (x<0)
閾値を超えたら発火する関数であり、出力は常に0か1。パーセプトロンで利用された関数。
0-1の間を表現できず、線形分離可能なモノしか学習できなかった。

活性化関数:シグモイド関数

数式:
 \dfrac{1}{1+e^{-u}}
0-1の間を緩やかに変化する関数で、ステップ関数に比べて信号の強弱を伝えられるようになり、ニューラルネットワーク普及のきっかけとなった。
一方で、逆誤差伝搬法を用いると、微分値が小さいために微分値を何度も掛け合わせると消失してしまい、深いニューラルネットワークでは勾配消失問題を引き起こすという課題があった。

活性化関数:ReLu関数

数式:
 f(x)= x (x>0) or 0 (x \leq 0)
現在最も使われている関数。x>0では、微分値が常に1となるため、勾配消失問題が起きにくい。
また、 x\leq 0では常に0のため、層の活性化がスパースとなり、計算量が少なく、精度も向上しやすい。

  • 確認テスト

配布されたソースコードより、該当する場所を抜き出せ。
f:id:tibet:20211118141401p:plain
[答え]

z1=functions.sigmoid(u)

実装演習

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)

結果

*** 入力 ***
[1. 2.]

##### ネットワークの初期化 #####
*** 重み1 ***
[[0.1 0.3 0.5]
 [0.2 0.4 0.6]]

*** 重み2 ***
[[0.1 0.4]
 [0.2 0.5]
 [0.3 0.6]]

*** 重み3 ***
[[0.1 0.3]
 [0.2 0.4]]

*** バイアス1 ***
[0.1 0.2 0.3]

*** バイアス2 ***
[0.1 0.2]

*** バイアス3 ***
[1 2]

##### 順伝播開始 #####
*** 総入力1 ***
[0.6 1.3 2. ]

*** 中間層出力1 ***
[0.6 1.3 2. ]

*** 総入力2 ***
[1.02 2.29]

*** 出力1 ***
[0.6 1.3 2. ]

出力合計: 3.9

Section3 出力層

誤差関数

 誤差関数は、ネットワークの出力値と、あらかじめ訓練データとして用意しておいた正解との誤差を評価する関数である。
特に知られているものは、MSE(平均二乗誤差)である。
 E_n(w)=\dfrac{1}{2}\displaystyle\sum_{J=1}^{l}(y_j-d_j)^2=\dfrac{1}{2}||(y-d)||^2
他にも、平均絶対誤差(MAE)や、平均二乗誤差の平方根(RMSE)、平均二乗対数誤差などが知られている。

-確認テスト
・なぜ引き算でなく二乗するかを述べよ。
[答え]
誤差の和をとって評価するため、正の値と負の値が存在すると打ち消し合って正確に評価が出来ない。二乗すれば常に正だから。
・MSEの1/2はどういう意味を持つか述べよ
[答え]
誤差逆伝搬法の計算時に微分をとるので、計算がしやすいから。

出力層の種類

  • 回帰
    • 恒等関数  f(u)=u

入力がそのまま出力になる関数。誤差関数としては二乗誤差を使う

入力が0~1の間をとる出力に変換される関数。誤差関数としては交差エントロピーを用いる

  • 多クラス分類
    • ソフトマックス関数  f(i,u)=\dfrac{e^{ui_}}{\sum_{K=1}^{K}e^{u_k}

多値の確率が出力される関数。誤差関数は交差エントロピーを用いる。

  • 確認テスト

 f:id:tibet:20211118193407p:plain
[答え]

def softmax(x):
#この関数全体が①に当たる。入力は
 if x.dim ==2 # 入力が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 # yを転置して出力
# 入力が2次元以上の場合
 x=x-np.max(x) #オーバーフロー対策
 return np.exp(x)/np.sum(np.exp(x)) #分子が②、分母が③

  • 確認テスト

交差エントロピーの下記の式で①~②に該当するソースコードを示し、1行ずつ処理の説明をせよ
f:id:tibet:20211118202618p:plain
[答え]

def cross_entropy_error(d, y): #全体の関数は①
 if y.ndim==1: #yが1次元の場合
   d=d.reshape(1, d.size)
   y=y.reshape(1, y.size)
 #教師データがone-hot-vectorの場合、正解ラベルのインデックスに変換
   if d.size==y.size: #dとyのサイズが等しい場合
   d=d.argmax(axis=1) #dにdの最大値のインデックスを代入
   batch_size=y.shape[0] #バッチサイズをyの行数にする(通常1)
 return -np.sum(np.log(y[np.arange(batch_size),d]+1e-7))/batch_size #こちらの数式が②の部分

実装演習

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

入力層サイズ:3
隠れ層サイズ:50
出力層サイズ:6
出力層活性化関数:softmax

def init_network():
    print("##### ネットワークの初期化 #####")

    network = {}
    
    input_layer_size = 3
    hidden_layer_size=50
    output_layer_size = 6
   
    network['W1'] = np.random.rand(input_layer_size, hidden_layer_size)
    network['W2'] = np.random.rand(hidden_layer_size,output_layer_size)

    network['b1'] =  np.random.rand(hidden_layer_size)
    network['b2'] =  np.random.rand(output_layer_size)
    
    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.,  3.])

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

# ネットワークの初期化
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)

結果(一部)

##### 結果表示 #####
*** 出力 ***
[6.10163245e-08 1.58053510e-08 9.99981036e-01 8.10503745e-16
 7.56282983e-06 1.13244940e-05]
shape: (6,)

*** 訓練データ ***
[0 0 0 1 0 0]
shape: (6,)

*** 交差エントロピー誤差 ***
16.118095642853284
shape: ()

Section4 勾配降下法

勾配降下法

 誤差関数E(w)を最小化するパラメータwを発見的に探索するために活用される。
 誤差関数のパラメータによる偏微分に学習率をかけたものを重みから差し引き、新たな重みとして更新する。
 w^{(t+1)}=w~{(t)}-ε∇E
 ∇E=\dfrac{\partial E}{\partial w}=\displaystyle \left( \dfrac{\partial E}{\partial w_1}, ... , \dfrac{\partial E}{\partial w_M}\right)

  • 確認問題

下記に該当するソースコードを探してみる。
 w^{(t+1)}=w~{(t)}-ε∇E
 ∇E=\dfrac{\partial E}{\partial w}=\displaystyle \left( \dfrac{\partial E}{\partial w_1}, ... , \dfrac{\partial E}{\partial w_M}\right)
[回答]

network[key]-=learning_rate*grad[key]
grad=backward(x,d,z1,y)

勾配降下法は、学習率の値によって学習率の値が大きく異なる

  • 学習率が小さすぎる場合:収束までの時間がかかり、かつ局所解に陥る場合がある。
  • 学習率が大きすぎる場合:解が収束しない場合がある。

 勾配降下法の学習率の決定、収束性向上のためのアルゴリズムは様々な手法が考え出されている。

確率的勾配降下法(SGD)

全サンプルの平均誤差を使用する勾配降下法に対し、確率的勾配降下法は、ランダムに抽出したサンプルの誤差を使用する。
メリット

  • データが冗長な場合の計算コストの低減
  • 局所極小解に収束するリスクの軽減
  • オンライン学習が出来る

  • 確認テスト

オンライン学習とは何か2行でまとめよ
[回答]
学習データが入ってくるたびにその都度、新たなデータのみを使用して学習し、パラメータを随時更新する学習法。

ミニバッチ勾配降下法

勾配降下法と確率的勾配降下法の中間のような手法で、ランダムに分割したデータの集合(ミニバッチ)に属するサンプルの平均誤差を使用して学習する。
メリット
確率的勾配降下法のメリットを損なわず、計算機の計算資源を有効利用できる。
 →CPUを利用したスレッド並列化やGPUを利用したSIMD並列化が可能

  • 確認テスト

 w^{(t+1)}=w^{(t)}-ε∇E_t
この数式の意味を図に書いて説明せよ
[解答]
f:id:tibet:20211118214200p:plain
図のようにエポックが1増えるごとに、-ε∇E分だけ、重み w_tが更新されることを意味している。

実装演習

確率的勾配降下法の演習
3層のニューラルネットワークで、epoch数が増えるごとに損失関数の値がどのように変化するかを図示する。

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

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

# 誤差逆伝播
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)
    
    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]['x'] = np.random.rand(2) * 10 -5 # -5〜5のランダム数値
    
    # 目標出力を設定
    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)

# 勾配降下の繰り返し
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()

結果
f:id:tibet:20211118214813p:plain
Epochが増加すると、損失関数の値が低くなって収束していく様子がわかる。

Section5 誤差逆伝搬法

ニューラルネットワークの誤差勾配を計算する時、算出された誤差を、出力層から順に微分し、前の層へと伝搬させることで、最小限の計算で各パラメータでの微分値を解析的に計算する手法。

-確認テスト
誤差逆伝搬法では不要な再帰的処理を避けることが出来る。すでに行った計算結果を保持しているソースコードを抽出せよ。

    #  出力層でのデルタ
    delta2 = functions.d_sigmoid_with_loss(d, y)
    #  中間層でのデルタ
    delta1 = np.dot(delta2, W2.T) * functions.d_relu(z1)

誤差勾配の計算には、順方向の計算結果と微分の連鎖律を利用する。
 E(y)=\dfrac{1}{2}\displaystyle \sum_{j=1}^{J}(y_j-d_j)^2=\dfrac{1}{2}||y-d||^2 :誤差関数

 y=u^{(L)} :出力層の活性化関数
 u^{(l)}=w^{(i)}z^{(l-1)}+b^{(l)} :総入力の計算

連鎖律
 \dfrac{\partial E}{\partial w_{ji}^{(2)}}= \dfrac{\partial E}{\partial y}\dfrac{\partial y}{\partial u}\dfrac{\partial u}{\partial w_{ji}^{(2)}}
第一項の計算
 \dfrac{\partial E(y)}{\partial y}=\dfrac{\partial}{\partial y}\dfrac{1}{2}||y-d||^2=y-d
第二項の計算
 \dfrac{\partial y(u)}{\partial u}=\dfrac{\partial u}{\partial u}=1
第三項の計算
 \dfrac{\partial u(w)}{\partial w_{ji}}=\dfrac{\partial}{\partial w_{ji}}(w^{(l)}Z^{(l-1)}z^{(l-1)}+b^{(l)})=(0....z_j...0)^T

これらの結果より、誤差関数の重みによる微分は下記のように計算できる。
 \dfrac{\partial E}{\partial y} \dfrac{\partial y}{\partial u} \dfrac{\partial u}{\partial w_{ji}^{(2)}}=(y_j-d_j)z_i

-確認テスト
2つの空欄に該当するソースコードを探せ
[解答]
 \dfrac{\partial E}{\partial y}

delta2=functions.d_mean_squared_error(d,y)

 \dfrac{\partial E}{\partial y}\dfrac{\partial y}{\partial u}

delta2=functions.d_mean_squared_error(d,y)

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

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

実装演習

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['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']
    #  出力層でのデルタ
    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)
        
    print_vec("偏微分_dE/du2", delta2)
    print_vec("偏微分_dE/du2", 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'])

結果

##### ネットワークの初期化 #####
*** 重み1 ***
[[0.1 0.3 0.5]
 [0.2 0.4 0.6]]

*** 重み2 ***
[[0.1 0.4]
 [0.2 0.5]
 [0.3 0.6]]

*** バイアス1 ***
[0.1 0.2 0.3]

*** バイアス2 ***
[0.1 0.2]

##### 順伝播開始 #####
*** 総入力1 ***
[[1.2 2.5 3.8]]

*** 中間層出力1 ***
[[1.2 2.5 3.8]]

*** 総入力2 ***
[[1.86 4.21]]

*** 出力1 ***
[[0.08706577 0.91293423]]

出力合計: 1.0

##### 誤差逆伝播開始 #####
*** 偏微分_dE/du2 ***
[[ 0.08706577 -0.08706577]]

*** 偏微分_dE/du2 ***
[[-0.02611973 -0.02611973 -0.02611973]]

*** 偏微分_重み1 ***
[[-0.02611973 -0.02611973 -0.02611973]
 [-0.13059866 -0.13059866 -0.13059866]]

*** 偏微分_重み2 ***
[[ 0.10447893 -0.10447893]
 [ 0.21766443 -0.21766443]
 [ 0.33084994 -0.33084994]]

*** 偏微分_バイアス1 ***
[-0.02611973 -0.02611973 -0.02611973]

*** 偏微分_バイアス2 ***
[ 0.08706577 -0.08706577]

##### 結果表示 #####
##### 更新後パラメータ #####
*** 重み1 ***
[[0.1002612  0.3002612  0.5002612 ]
 [0.20130599 0.40130599 0.60130599]]

*** 重み2 ***
[[0.09895521 0.40104479]
 [0.19782336 0.50217664]
 [0.2966915  0.6033085 ]]

*** バイアス1 ***
[0.1002612 0.2002612 0.3002612]

*** バイアス2 ***
[0.09912934 0.20087066]