本レポートはラビットチャレンジ深層学習day3のレポートである
RNNとは、時系列データに対して有効なニューラルネットワークの一つ。従来のニューラルネットワークとは異なり、自己回帰的な構造を持ち、主に音声認識で使用される。
時系列データとは、時間的順序を追って一定期間ごとに観察され、相互に統計的依存関係が認められるようなデータの系列 (例)音声データ、テキストデータ
import numpy as np
from common import functions
import matplotlib.pyplot as plt
# def d_tanh(x):
# データを用意
# 2進数の桁数
binary_dim = 8
# 最大値 + 1
largest_number = pow(2, binary_dim)
# largest_numberまで2進数を用意
binary = np.unpackbits(np.array([range(largest_number)],dtype=np.uint8).T,axis=1)
input_layer_size = 2
hidden_layer_size = 16
output_layer_size = 1
weight_init_std = 1
learning_rate = 0.1
iters_num = 10000
plot_interval = 100
# ウェイト初期化 (バイアスは簡単のため省略)
W_in = weight_init_std * np.random.randn(input_layer_size, hidden_layer_size)
W_out = weight_init_std * np.random.randn(hidden_layer_size, output_layer_size)
W = weight_init_std * np.random.randn(hidden_layer_size, hidden_layer_size)
# Xavier
# He
# 勾配
W_in_grad = np.zeros_like(W_in)
W_out_grad = np.zeros_like(W_out)
W_grad = np.zeros_like(W)
u = np.zeros((hidden_layer_size, binary_dim + 1))
z = np.zeros((hidden_layer_size, binary_dim + 1))
y = np.zeros((output_layer_size, binary_dim))
delta_out = np.zeros((output_layer_size, binary_dim))
delta = np.zeros((hidden_layer_size, binary_dim + 1))
all_losses = []
for i in range(iters_num):
# A, B初期化 (a + b = d)
a_int = np.random.randint(largest_number/2)
a_bin = binary[a_int] # binary encoding
b_int = np.random.randint(largest_number/2)
b_bin = binary[b_int] # binary encoding
# 正解データ
d_int = a_int + b_int
d_bin = binary[d_int]
# 出力バイナリ
out_bin = np.zeros_like(d_bin)
# 時系列全体の誤差
all_loss = 0
# 時系列ループ
for t in range(binary_dim):
# 入力値
X = np.array([a_bin[ - t - 1], b_bin[ - t - 1]]).reshape(1, -1)
# 時刻tにおける正解データ
dd = np.array([d_bin[binary_dim - t - 1]])
u[:,t+1] = np.dot(X, W_in) + np.dot(z[:,t].reshape(1, -1), W)
z[:,t+1] = functions.sigmoid(u[:,t+1])
y[:,t] = functions.sigmoid(np.dot(z[:,t+1].reshape(1, -1), W_out))
#誤差
loss = functions.mean_squared_error(dd, y[:,t])
delta_out[:,t] = functions.d_mean_squared_error(dd, y[:,t]) * functions.d_sigmoid(y[:,t])
all_loss += loss
out_bin[binary_dim - t - 1] = np.round(y[:,t])
for t in range(binary_dim)[::-1]:
X = np.array([a_bin[-t-1],b_bin[-t-1]]).reshape(1, -1)
delta[:,t] = (np.dot(delta[:,t+1].T, W.T) + np.dot(delta_out[:,t].T, W_out.T)) * functions.d_sigmoid(u[:,t+1])
# 勾配更新
W_out_grad += np.dot(z[:,t+1].reshape(-1,1), delta_out[:,t].reshape(-1,1))
W_grad += np.dot(z[:,t].reshape(-1,1), delta[:,t].reshape(1,-1))
W_in_grad += np.dot(X.T, delta[:,t].reshape(1,-1))
# 勾配適用
W_in -= learning_rate * W_in_grad
W_out -= learning_rate * W_out_grad
W -= learning_rate * W_grad
W_in_grad *= 0
W_out_grad *= 0
W_grad *= 0
if(i % plot_interval == 0):
all_losses.append(all_loss)
print("iters:" + str(i))
print("Loss:" + str(all_loss))
print("Pred:" + str(out_bin))
print("True:" + str(d_bin))
out_int = 0
for index,x in enumerate(reversed(out_bin)):
out_int += x * pow(2, index)
print(str(a_int) + " + " + str(b_int) + " = " + str(out_int))
print("------------")
lists = range(0, iters_num, plot_interval)
plt.plot(lists, all_losses, label="loss")
plt.show()
実行結果
BPTTとは、誤差逆伝播法をRNNにおいて適応した学習アルゴリズムのこと。
BPTTの数学的記述
Q. RNNのネットワークには大きくわけて3つの重みがある。1つは入力から現在の中間層を定義する際にかけられる重み、1つは中間層から出力を定義する際にかけられる重みである。残り1つの重みについて説明せよ。
A. 前の中間層から現在の中間層への重み
Q. (く)にあてはまるのはどれか。
A. (2)W.dot(np.concatenate([left, right]))
Q. 下図の$y_1$を数式を用いて表せ
image.png
A. $z_1 = f(w_{in}x+wz_0+b)$
$y_1 = g(w_{out}z_1+c)$
ゼロから作るDeepLearning2 5章 リカレントニューラルネットワーク
G検定公式テキスト 第6章
LSTM (Long Short-Term Memory)は、RNNの一種で、特に長期的な依存関係を扱うために設計されたニューラルネットワークのアーキテクチャ。LSTMにより時系列を遡れば遡ぼるほど勾配が消失していくというRNNの課題を解決することができる。
LSTMには3つのゲートがある。
忘却ゲート:現在の入力と前の状態を受け取り、どの情報を保持するかを決定するゲート
入力ゲート:現在の入力と前の状態を受け取り、新しい情報をどの程度保持するかを決定するゲート
出力ゲート:現在の入力と前の状態を受け取り、次の状態をどの程度出力するかを決定するゲート
LSTMの全体図
# LSTMレイヤの実装
class LSTM:
# 初期化メソッドの定義
def __init__(self, Wx, Wh, b):
self.params = [Wx, Wh, b] # パラメータ
self.grads = [np.zeros_like(Wx), np.zeros_like(Wh), np.zeros_like(b)] # 勾配
self.cache = None
# 順伝播メソッドの定義
def forward(self, x, h_prev, c_prev):
# パラメータと変数の形状に関する値を取得
Wx, Wh, b = self.params
N, H = h_prev.shape
# 結合したパラメータによる重み付き和の計算
A = np.dot(x, Wx) + np.dot(h_prev, Wh) + b
# 各ゲートの重み付き和を取得
f = A[:, :H] # forgetゲート
g = A[:, H:2*H] # 記憶セル
i = A[:, 2*H:3*H] # inputゲート
o = A[:, 3*H:] # outputゲート
# ゲート値に変換
f = sigmoid(f)
g = np.tanh(g)
i = sigmoid(i)
o = sigmoid(o)
# 出力を計算
c_next = f * c_prev + g * i # 記憶セル
h_next = o * np.tanh(c_next) # 出力データ
# 逆伝播の計算用に変数を保存
self.cache = (x, h_prev, c_prev, i, f, g, o, c_next)
return h_next, c_next
# 逆伝播メソッドの定義
def backward(self, dh_next, dc_next):
# 変数を取得
Wx, Wh, b = self.params
x, h_prev, c_prev, i, f, g, o, c_next = self.cache
# 計算用に活性化記憶セルを計算
tanh_c_next = np.tanh(c_next)
# 現レイヤの記憶セルの勾配を計算
ds = dc_next + (dh_next * o) * (1 - tanh_c_next ** 2)
# 前レイヤの記憶セルの勾配を計算
dc_prev = ds * f
# 活性化後のゲートの勾配を計算
di = ds * g
df = ds * c_prev
do = dh_next * tanh_c_next
dg = ds * i
# 活性化前のゲートの勾配を計算
di *= i * (1 - i)
df *= f * (1 - f)
do *= o * (1 - o)
dg *= (1 - g ** 2)
# ゲートの勾配を結合
dA = np.hstack((df, dg, di, do))
# パラメータの勾配を計算
dWh = np.dot(h_prev.T, dA)
dWx = np.dot(x.T, dA)
db = dA.sum(axis=0)
# パラメータの勾配を格納
self.grads[0][...] = dWx
self.grads[1][...] = dWh
self.grads[2][...] = db
# 入力の勾配を計算
dx = np.dot(dA, Wx.T)
dh_prev = np.dot(dA, Wh.T)
return dx, dh_prev, dc_prev
Q. シグモイド関数を微分したとき、入力値が0のときに最大値をとる。その値として正しいものを選択肢から選べ。
A. 0.25
Q. 以下の文章をLSTMに入力し空欄に当てはまる単語を予測したいとする。文中の「とても」という言葉は空欄の予測においてなくなっても影響を及ぼさないと考えられる。このような場合、どのゲートが作用すると考えられるか。
A. 忘却ゲート
Q. (け)にあてはまるのはどれか
A. (3)input_gatea+forget_gatec
ゼロから作るDeepLearning2 6章 ゲート付きRNN
GRUとは、深層学習における一種の再帰型ニューラルネットワーク。LSTMと同様に、シーケンスデータに対するモデリングに適していて、パラメータが多く計算負荷が大きかったLSTMの課題点を解決するために作られた。GRUはパラメータ量がLSTMよりも少ないが、精度はLSTM同等かそれ以上になった。
GRUにはリセットゲート、更新ゲートの2つのゲートがある。
def gru(x, h, W_r, U_r, W_z, U_z, W, U):
# ゲートを計算
# ↓リセットゲート
r = _sigmoid(x.dot(W_r.T) + h.dot(U_r.T))
# ↓更新ゲート
z = _sigmoid(x.dot(W_z.T) + h.dot(U_z.T))
# 次の状態を計算
h_bar = np.tanh(x.dot(W.T) + (r * h).dot(U.T))
h_new = (1-z) * h + z * h_bar
return h_new
Q. LSTMとCECが抱える課題について、それぞれ簡潔に述べよ
A.
LSTM:パラメータ数が多く、計算量も多い
CEC:勾配が1で、単体では学習できない
Q. LSTMとGRUの違いを簡潔に述べよ
LSTM:パラメータ数が多く、計算量も多い。
GRU:パラメータを削減し計算負荷を減らしたが、同等またはそれ以上の精度が望める。
ゼロから作るDeepLearning2 付録C GRU
【脱初心者】GRUの数式とあの図を攻略!超絶分かりやすい図解でステップ解説【E資格合格者のノート公開】
https://i-main.net/emmanote-ai-gru/
RNNを2つ組み合わせることで、未来から過去方向も含めて学習できるようにしたモデル。通常のRNNは過去から未来への一方向でしか学習をすることができないが、BiRNNによって未来の情報からの学習も可能になった。
文章の推敲や機械翻訳に用いられている。
def bindirectional_rnn_net(xs, W_f, U_f, W_b, U_b, V):
# W_f, U_f:入力から中間層、前の中間層から今の中間層の順方向の重み
# W_b, U_b:W_f, U_fの逆方向
# V:順方向、逆方向の中間層から出力層の重み
xs_f = np.zeros_like(xs)
xs_b = np.zeros_like(xs)
for i, x in enumerate(xs):
xs_f[i] = x
xs_b[i] = x[::-1]
hs_f = _rnn(xs_f, W_f, U_f)
hs_b = _rnn(xs_b, W_b, U_b)
hs = [np.concatenate([h_f, h_b[::-1]], axis=0)] for h_f, h_b in zip(hs_f, hs_b)
ys = hs.dot(V.T)
return ys
A. (4)
ゼロから作るDeepLearning2 8章 4-1 双方向RNN
自然言語処理において、入力と出力の長さが異なるシーケンスを扱うためのモデル。エンコーダとデコーダという2つのニューラルネットワークをしようしている。
Encoder部分では、入力されたシーケンスデータを単語の埋め込み表現に変換し、RNNを使用してシーケンスを表す隠れ状態を生成する。隠れ状態は、最終的に固定長のベクトルに変換される。
Decoder部分では、エンコーダーが生成したベクトルを元に、出力となる文章を生成する。
Seq2Seqモデルを拡張したニューラルネットワークモデル。HREDは、Seq2Seqのエンコーダーを階層的に拡張し、文書全体を階層的に表現することができる。文章を文単位や文書単位などのより細かい単位で表現することができ、より自然な文章生成が可能となっている。
HREDをベースにしたモデルであり、VAEを組み合わせることで、より自然な文章生成を実現するために開発されたニューラルネットワークモデルのこと。
高次元データを低次元の潜在空間に写像し、そこでデータを生成する生成モデル。エンコーダーとデコーダーを使い、確率分布を学習して生成する。画像、音声、自然言語生成などに用いられる。
2022年に公開された画像生成系AIであるStable DiffusionもVAEが用いられている。
Q. Seq2SeqとHREDの違いを説明せよ
A. Seq2Seqが単一のエンコーダーとデコーダーで構成されるのに対し、HREDは複数の階層的なエンコーダーとデコーダーから構成される。
Q. HREDとVHREDの違いを説明せよ
A. HREDが階層的なエンコーダーとデコーダーから構成される一方、VHREDはそれに加えてVAEの概念を追加している。
Q. VAEに関する下記の説明文中の空欄に当てはまる言葉を答えよ。自己符号化器の潜在変数に__を導入したもの。
A. 確率分布
ゼロから作るDeepLearning2 7章 RNN-Seq2Seq
seq2seqの実装と学習【ゼロつく2のノート(実装)】
https://www.anarchive-beta.com/entry/2021/03/29/150000
【世界一分かりやすい解説】Attentionを用いたseq2seqのメカニズム
https://tips-memo.com/translation-jayalmmar-attention
単語の分散表現を生成するためのニューラルネットワークモデル。大規模なテキストコーパスから学習し、各単語をベクトル表現に変換する。
word2vecにはcbowとskip-gramという2つのモデルがあり、cbowは文脈(周囲の単語)からターゲット単語を予測するモデルであり、skip-gramはターゲット単語から周囲の単語を予測するモデルである。
G検定公式テキスト第2版 6章4節 1-4 単語埋め込み(word embedding)
入力された情報のうち、重要な情報に焦点を当てて処理するための仕組み。通常、Seq2SeqモデルやTransformerモデルなどの自然言語処理タスクで使用される。
現在注目を浴びているchatGPTにもAttention機構が使用されている。
Q. RNNとword2vecの違いを説明せよ
A. RNNはシーケンスデータの処理に使用されるが、word2vecは単語の意味表現の学習に使用される。
Q. RNNとword2vecの違いを説明せよ
A. Seq2Seqは入力シーケンスから出力シーケンスを生成するためのモデルである一方、AttentionはSeq2Seqにおいて、より適切な情報に注目するための仕組みである。
通常、Seq2SeqはRNNを使用して実装されるが、AttentionはRNNを使用しない。
G検定公式テキスト第2版 6章4節 2-5 Attention
VQ-VAEは、変分オートエンコーダーにベクトル量子化(Vector Quantizer)を組み合わせたニューラルネットワークの一種。データを圧縮し、潜在表現として得ることができる。
従来のVAEでは潜在変数zを正規分布のベクトルになるような学習を行うが、VQ-VAEでは潜在変数を離散化した数値になるような学習を行う。
VQ-VAEは、特に高次元データの圧縮や生成に有効で、より効率的な学習や高品質の生成が可能になる。
VQ-VAEの理解(
https://qiita.com/nishiha/items/44de5c46ebdfe615f6e8)
【論文解説+Tensorflowで実装】VQ-VAEを理解する(
https://data-analytics.fun/2021/05/14/understanding-vq-vae/)
import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(-3 * np.pi, 3 * np.pi, 100)
seq_in = np.sin(x)
seq_out = np.cos(x)
plt.plot(x, seq_in, label='$y=\sin x$')
plt.plot(x, seq_out, label='$y=\cos x$')
plt.legend()
plt.grid()
plt.show()
image.png
NUM_ENC_TOKENS = 1
NUM_DEC_TOKENS = 1
NUM_HIDDEN_PARAMS = 10
NUM_STEPS = 24
tf.keras.backend.clear_session()
e_input = tf.keras.layers.Input(shape=(NUM_STEPS, NUM_ENC_TOKENS), name='e_input')
_, e_state = tf.keras.layers.SimpleRNN(NUM_HIDDEN_PARAMS, return_state=True, name='e_rnn')(e_input)
d_input = tf.keras.layers.Input(shape=(NUM_STEPS, NUM_DEC_TOKENS), name='d_input')
d_rnn = tf.keras.layers.SimpleRNN(NUM_HIDDEN_PARAMS, return_sequences=True, return_state=True, name='d_rnn')
d_rnn_out, _ = d_rnn(d_input, initial_state=[e_state])
d_dense = tf.keras.layers.Dense(NUM_DEC_TOKENS, activation='linear', name='d_output')
d_output = d_dense(d_rnn_out)
model_train = tf.keras.models.Model(inputs=[e_input, d_input], outputs=d_output)
model_train.compile(optimizer='adam', loss='mean_squared_error')
model_train.summary()
n = len(x) - NUM_STEPS
ex = np.zeros((n, NUM_STEPS))
dx = np.zeros((n, NUM_STEPS))
dy = np.zeros((n, NUM_STEPS))
for i in range(0, n):
ex[i] = seq_in[i:i + NUM_STEPS]
dx[i, 1:] = seq_out[i:i + NUM_STEPS - 1]
dy[i] = seq_out[i: i + NUM_STEPS]
ex = ex.reshape(n, NUM_STEPS, 1)
dx = dx.reshape(n, NUM_STEPS, 1)
dy = dy.reshape(n, NUM_STEPS, 1)
loss = history.history['loss']
plt.plot(np.arange(len(loss)), loss, label='loss')
loss = history.history['val_loss']
plt.plot(np.arange(len(loss)), loss, label='val_loss')
plt.grid()
plt.legend()
plt.show()
image.png
model_pred_e = tf.keras.models.Model(inputs=[e_input], outputs=[e_state])
pred_d_input = tf.keras.layers.Input(shape=(1, 1))
pred_d_state_in = tf.keras.layers.Input(shape=(NUM_HIDDEN_PARAMS))
pred_d_output, pred_d_state = d_rnn(pred_d_input, initial_state=[pred_d_state_in])
pred_d_output = d_dense(pred_d_output)
pred_d_model = tf.keras.Model(inputs=[pred_d_input, pred_d_state_in], outputs=[pred_d_output, pred_d_state])
def predict(input_data):
state_value = model_pred_e.predict(input_data)
_dy = np.zeros((1, 1, 1))
output_data = []
for i in range(0, NUM_STEPS):
y_output, state_value = pred_d_model.predict([_dy, state_value])
output_data.append(y_output[0, 0, 0])
_dy[0, 0, 0] = y_output
return output_data
init_points = [0, 24, 49, 74]
for i in init_points:
_x = ex[i : i + 1]
_y = predict(_x)
if i == 0:
plt.plot(x[i : i + NUM_STEPS], _y, color="red", label='output')
else:
plt.plot(x[i : i + NUM_STEPS], _y, color="red")
plt.plot(x, seq_out, color = 'blue', linestyle = "dashed", label = 'correct')
plt.grid()
plt.legend()
plt.show()
image.png
データ拡張(data augumentation)とは、既存のデータセットに手を加えることにより、新しいデータを生成することでデータセットを拡張する手法のこと。主に、画像データの回転、シフト、反転、切り抜き、色の変換などが行われる。データ拡張により、訓練データを増やすことができ、モデルの汎化性能の向上や過学習の防止などが期待される。
# 一部の処理でNumpyを使用して記述
import numpy as np
# データの水増し用APIを有するライブラリ
import tensorflow as tf
# 擬似乱数を生成するモジュール
import random
# 画像を表示するライブラリ
import matplotlib.pyplot as plt
# 画像をNotebook内に表示させるための指定
%matplotlib inline
#画像を表示するshow_images関数を定義する。
def show_images(images):
"""複数の画像を表示する"""
n = 1
while n ** 2 < len(images):
n += 1
for i, image in enumerate(images):
plt.subplot(n, n, i + 1)
plt.imshow(image)
plt.axis('off')
plt.show()
! mkdir sample_data
! wget -qnc --no-check-certificate -O ./sample_data/image_origin.jpg \
https://github.com/opencv/opencv/raw/master/samples/data/fruits.jpg
! mkdir sample_data
! wget -qnc --no-check-certificate -O ./sample_data/image_origin.jpg \
https://github.com/opencv/opencv/raw/master/samples/data/fruits.jpg
image = image_origin
show_images([image.numpy()])
image.png
#水平方向(左右)反転処理
image = image_origin
image = tf.image.random_flip_left_right(image, seed=123)
show_images([image.numpy()])
image.png
G検定公式テキスト第2版 6章1節 5 データ拡張
活性化関数(activation function)は、ニューラルネットワークにおいて、入力信号の総和を非線形に変換する関数のことを指す。活性化関数は、入力層、中間層、出力層において使用される。
ニューラルネットワークの非線形性を担う重要な要素であり、活性化関数によって出力された値がしきい値を超えた場合に限り、信号を伝達することができる。
代表的な活性化関数には以下のものがある。
シグモイド関数
ReLU関数
Leaky ReLU関数
tanh関数
Softmax関数
sigmoid
def sigmoid(x):
"""forward
sigmoid
シグモイド関数
"""
return 1.0 / (1.0 + np.exp(-x))
def d_sigmoid(x):
"""backward
derivative of sigmoid
シグモイド関数の導関数
"""
dx = sigmoid(x) * (1.0 - sigmoid(x))
return dx
tanh
def tanh(x):
"""forward
tanh
双曲線正接関数
(1)
"""
return np.tanh(x)
# def tanh(x):
# """forward
#
# tanh
# 双曲線正接関数
# (2)
# """
# return np.sinh(x) / np.cosh(x)
#
# def tanh(x):
# """forward
#
# tanh
# 双曲線正接関数
# (3)
# """
# return (np.exp(x) - np.exp(-x)) / (np.exp(x) + np.exp(-x))
#
# def tanh(x):
# """forward
#
# tanh
# 双曲線正接関数
# (4)
# """
# return (np.exp(2.0*x) - 1.0) / (np.exp(2.0*x) + 1.0)
#
# def tanh(x):
# """forward
#
# tanh
# 双曲線正接関数
# (5)
# """
# return 2.0 * sigmoid(2.0*x) - 1.0
def d_tanh(x):
"""backward
derivative of tanh
双曲線正接関数の導関数
(1)
"""
dx = 1.0 / np.square(np.cosh(x))
return dx
# def d_tanh(x):
# """backward
#
# derivative of tanh
# 双曲線正接関数の導関数
# (2)
# """
# dx = 4.0 / np.square(np.exp(x) + np.exp(-x))
# return dx
ReLu
def relu(x):
"""forward
ReLU
正規化線形関数
"""
return np.maximum(0, x)
def d_relu(x):
"""backward
derivative of ReLU
正規化線形関数の導関数
"""
dx = np.where(x > 0.0, 1.0, np.where(x < 0.0, 0.0, np.nan))
return dx
# def d_relu(x):
# """backward
#
# derivative of ReLU
# 正規化線形関数の導関数
# (xが0.0のときの微分値を定義した劣微分)
# """
# dx = step_function(x)
# return dx
Leaky ReLu
alpha = 0.01
def lrelu(x):
"""forward
Leaky ReLU
漏洩正規化線形関数
"""
return np.maximum(alpha*x, x)
def d_lrelu(x):
"""backward
derivative of Leaky ReLU
漏洩正規化線形関数の導関数
"""
dx = np.where(x > 0.0, 1.0, np.where(x < 0.0, alpha, np.nan))
return dx
# def d_lrelu(x):
# """backward
#
# derivative of Leaky ReLU
# 漏洩正規化線形関数の導関数
# (xが0.0のときの微分値を定義した劣微分)
# """
# dx = np.where(x > 0.0, 1.0, alpha)
# return dx
G検定公式テキスト第2版 5章4節 5 活性化関数