Python - ディープラーニング - 勾配降下法(続き)
公開日:2019-09-27 更新日:2019-09-27
[Python]
1. 概要
論理演算(XOR)を再現するニューラルネットワークの重みとバイアスを、勾配降下法により求めます。
2. 動画
3. 勾配降下法の実装
以下のニューラルネットワークを使用して、論理演算を再現します。

損失が最小となる重みとバイアスを求めるため、今回の勾配降下法の対象となる関数は、 各ノードの計算を行い、ソフトマックス関数と損失関数(クロスエントロピー誤差)を通した、ニューラルネットワーク全体が対象となります。
そして、全ての重みとバイアスの数だけ編微分を行い、重みとバイアスを更新していきます。

損失が最小となる重みとバイアスを求めるため、今回の勾配降下法の対象となる関数は、 各ノードの計算を行い、ソフトマックス関数と損失関数(クロスエントロピー誤差)を通した、ニューラルネットワーク全体が対象となります。
そして、全ての重みとバイアスの数だけ編微分を行い、重みとバイアスを更新していきます。
import matplotlib.pyplot as plt
import numpy as np
# ReLU(ndarray版)
def relu(lst):
lst[lst <= 0] = 0
return lst
# ソフトマックス関数(ndarray版)
def softmax(x):
#転置しない場合のやり方
#x2 = x.copy()
#x2 = x2 - np.max(x2, axis = 1).reshape(x2.shape[0], 1)
#y2 = np.exp(x2) / np.sum(np.exp(x2), axis = 1).reshape(x2.shape[0], 1)
# max() と sum() で次元が減り、
# 元の x と直接計算ができなくなるため、転置(行列の入替)しておきます
x = x.T
# オーバーフロー対策。最大値が0になり、他はマイナスになる。
x = x - np.max(x, axis = 0)
# ソフトマックス
y = np.exp(x) / np.sum(np.exp(x), axis = 0)
return y.T #転置していたので元に戻して返す
# クロスエントロピー誤差の取得
def cross_entropy_error(y, t):
size = y.shape[0]
loss = 0
for i in range(size):
# 正解データ(one-hot)の 1 になっているインデックスを取得します
idx = t[i].argmax()
# 損失を加算します(正解データの 1 の予測結果だけを使う)
loss = loss + np.log(y[i, idx] + 1e-7)
loss = - loss / size
return loss
# 予測します
# ニューラルネットワークの計算を行い、ソフトマックスで結果を返す
def predict(v1, w1, b1, w2, b2):
v2 = np.dot(v1, w1) + b1
v2 = relu(v2)
v3 = np.dot(v2, w2) + b2
return softmax(v3)
# 損失の取得
def get_loss(v, w1, b1, w2, b2, t_value):
# 予測(分類)します
result = predict(v, w1, b1, w2, b2)
# 損失を取得します
loss = cross_entropy_error(result, t_value)
return loss
# 勾配降下法による学習
def gradient_descent(v1, w1, b1, w2, b2, t_value):
h = 0.1
# 重みの微分
dw1 = np.zeros_like(w1) # w1 と同じサイズの配列の作成
org_w1 = w1.copy() # h を増減した値を戻すのに使用
for i in range(w1.shape[0]):
for j in range(w1.shape[1]):
# 数値微分 f(x+h)-f(x-h)/2h
w1[i, j] = org_w1[i, j] + h
f1 = get_loss(v1, w1, b1, w2, b2, t_value)
w1[i, j] = org_w1[i, j] - h
f2 = get_loss(v1, w1, b1, w2, b2, t_value)
dw1[i, j] = (f1 - f2) / (h * 2)
# 元に戻す
w1[i, j] = org_w1[i, j]
# 重みの微分
dw2 = np.zeros_like(w2)
org_w2 = w2.copy()
for i in range(w2.shape[0]):
for j in range(w2.shape[1]):
w2[i, j] = org_w2[i, j] + h
f1 = get_loss(v1, w1, b1, w2, b2, t_value)
w2[i, j] = org_w2[i, j] - h
f2 = get_loss(v1, w1, b1, w2, b2, t_value)
dw2[i, j] = (f1 - f2) / (h * 2)
w2[i, j] = org_w2[i, j]
# バイアスの微分
db1 = np.zeros_like(b1)
org_b1 = b1.copy()
for i in range(b1.shape[0]):
b1[i] = org_b1[i] + h ; f1 = get_loss(v1, w1, b1, w2, b2, t_value)
b1[i] = org_b1[i] - h ; f2 = get_loss(v1, w1, b1, w2, b2, t_value)
db1[i] = (f1 - f2) / (h * 2)
b1[i] = org_b1[i]
# バイアスの微分
db2 = np.zeros_like(b2)
org_b2 = b2.copy()
for i in range(b2.shape[0]):
b2[i] = org_b2[i] + h ; f1 = get_loss(v1, w1, b1, w2, b2, t_value)
b2[i] = org_b2[i] - h ; f2 = get_loss(v1, w1, b1, w2, b2, t_value)
db2[i] = (f1 - f2) / (h * 2)
b2[i] = org_b2[i]
# 重みの学習
lr = 0.01
for i in range(w1.shape[0]):
for j in range(w1.shape[1]):
w1[i, j] = w1[i, j] + lr * dw1[i, j] * -1
for i in range(w2.shape[0]):
for j in range(w2.shape[1]):
w2[i, j] = w2[i, j] + lr * dw2[i, j] * -1
# バイアスの学習
b1 = b1 + lr * db1 * -1
b2 = b2 + lr * db2 * -1
return w1, b1, w2, b2
## グラフの描画
def draw_graph(w1, b1, w2, b2):
plt.grid() # グリッド表示
plt.xlim([-1.5, 1.5]) # グラフ描画範囲(X軸)
plt.ylim([-1.5, 1.5]) # グラフ描画範囲(Y軸)
# グラフ描画
x_list = np.arange(-1, 1.6, 0.1) # x軸
y_list = np.arange(-1, 1.6, 0.1) # y軸
for y in y_list:
for x in x_list:
p = np.argmax(predict(np.array([x, y]), w1, b1, w2, b2))
if p == 1:
plt.scatter(x, y, c = 'b')
plt.show()
print("{0} {1} {2} {3}".format(
np.argmax(predict(np.array([0, 0]), w1, b1, w2, b2)),
np.argmax(predict(np.array([0, 1]), w1, b1, w2, b2)),
np.argmax(predict(np.array([1, 0]), w1, b1, w2, b2)),
np.argmax(predict(np.array([1, 1]), w1, b1, w2, b2))))
# ここからメイン
# 重み
w1 = np.random.randn(2, 8) * 0.1 #入力数:2 中間のノード数:8
w2 = np.random.randn(8, 2) * 0.1 # 中間のノード数:8 出力数:2
# バイアス
b1 = np.zeros(8)
b2 = np.zeros(2)
# 入力データ(論理演算の全ての組み合わせ)
input_value = np.array([
[0, 0],
[0, 1],
[1, 0],
[1, 1]
])
# 正解データ(XOR) one-hot表現
t_value = np.array([
[1, 0], # 0
[0, 1], # 1
[0, 1], # 1
[1, 0] # 0
])
# 勾配降下法による学習
c = 0 # グラフ描画用のカウント
for i in range(5001):
# 勾配降下法による学習
w1, b1, w2, b2 = gradient_descent(input_value, w1, b1, w2, b2, t_value)
if c % 100 == 0:
draw_graph(w1, b1, w2, b2)
c = 0
c = c + 1