Stage4 深層学習 Day3

1. 再帰型ニューラルネットワークの概念

1-1. RNN全体像

時系列データに対応可能なニューラルネットワークである。時系列データとは、時間的順序を追って一定間隔ごとに観察され,しかも相互に統計的依存関係が認められるようなデータの系列のことである。

全体像は以下の通り。前の層からインプットを受け取っている。

画像1

<確認テスト>
RNNのネットワークには大きく分けて3つの重みがある。1つは入力から現在の中間層を定義する際にかけられる重み、1つは中間層から出力を定義する際にかけられる重みである。残り1つの重みについて説明せよ。

<解答>
前の中間層から現在の中間層を定義する際にかけられる重み。

1-2. BPTT

誤差逆伝播の一種で、RNNにおけるパラメータ調整方法の一種

BPTTの数式とコードは以下の通り。

画像2

パラメータ更新式は以下の通り。

画像3

<確認テスト1>

連鎖律の原理を使い、dz/dxを求めよ。
z=t^2   t=x+y

<解答>

dz/dx = dz/dt × dt/dx = 2t = 2(x+y)

<確認テスト2>
下図のy1をx・s0・s1・win・w・woutを用いて数式で表せ。※バイアスは任意の文字で定義せよ。※また中間層の出力にシグモイド関数g(x)を作用させよ。

画像4

<解答>

z1=g( z0 × W + x1 × Win + b)

y1=g( z1 × Wout + c)

<実装演習>

コードは以下の通り。

import numpy as np
from common import functions
import matplotlib.pyplot as plt


def d_tanh(x):
   return 1/(np.cosh(x) ** 2)

# データを用意
# 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
# W_in = np.random.randn(input_layer_size, hidden_layer_size) / (np.sqrt(input_layer_size))
# W_out = np.random.randn(hidden_layer_size, output_layer_size) / (np.sqrt(hidden_layer_size))
# W = np.random.randn(hidden_layer_size, hidden_layer_size) / (np.sqrt(hidden_layer_size))
# He
# W_in = np.random.randn(input_layer_size, hidden_layer_size) / (np.sqrt(input_layer_size)) * np.sqrt(2)
# W_out = np.random.randn(hidden_layer_size, output_layer_size) / (np.sqrt(hidden_layer_size)) * np.sqrt(2)
# W = np.random.randn(hidden_layer_size, hidden_layer_size) / (np.sqrt(hidden_layer_size)) * np.sqrt(2)


# 勾配
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])
#         z[:,t+1] = functions.relu(u[:,t+1])
#         z[:,t+1] = np.tanh(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])
#         delta[:,t] = (np.dot(delta[:,t+1].T, W.T) + np.dot(delta_out[:,t].T, W_out.T)) * functions.d_relu(u[:,t+1])
#         delta[:,t] = (np.dot(delta[:,t+1].T, W.T) + np.dot(delta_out[:,t].T, W_out.T)) * d_tanh(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()

実行結果は以下の通り。誤差関数は収束している。

画像5


2. LSTM

RNNは、時系列を遡れば遡るほど勾配が消失していくため、長い時系列の学習が困難。LSTMは構造自体を変えることで解決している。

・CEC

考えることと記憶することを分離して、記憶することだけをCECが担う。RNNでは考えることも記憶することも一緒にしていた。結果、 勾配消失問題が起こった。
勾配が1であれば、勾配消失も勾配爆発も起きない。CECは勾配を1にする。

・入力ゲート/出力ゲート

入力ゲートはCECに対して、どんなふうに記憶してもらうか決める。
出力ゲートはCECの記憶に対して、どんなふうにCECの記憶を使うか、決める。

・忘却ゲート

CECは過去の記憶は保持し続ける。忘却ゲートで削除する。

・覗き穴結合

CECの保存されている過去の情報を、任意のタイミングで他のノードに伝播させたり、あるいは任意のタイミングで忘却させるため、CEC自身の値に重み行列を介して伝播可能にした構造。

<確認テスト>

以下の文章をLSTMに入力し空欄に当てはまる単語を予測したいとする。文中の「とても」という言葉は空欄の予測においてなくなっても影響を及ぼさないと考えられる。このような場合、どのゲートが作用すると考えられるか。「映画おもしろかったね。ところで、とてもお腹が空いたから何か____。」

<回答>

忘却ゲート

3. GRU

LSTMはパラメータが多すぎて複雑という問題点があった。GRUではパラメータを減らしつつ精度を落とさないようにしている。

<確認テスト1>

LSTMとCECが抱える課題について、それぞれ簡潔に述べよ

<回答>

LSTMは、パラメータ数が多く計算負荷がかかる。
CECは学習機能を備えていない。

<確認テスト2>

LSTMとGRUの違いを簡潔に述べよ。

<回答>

LSTMはCEC、入力ゲート、出力ゲート、忘却ゲートを持ち、パラメータが多く計算コストが大きい。一方で、GRUはリセットゲートの更新ゲートを持ち、パラメータが少ないため計算コストが小さい。

4. 双方向RNN

過去の情報だけでなく、未来の情報も持たせて精度を向上させるモデル。
例: 機械翻訳。過去も未来も情報を使う。

5. Seq2Seq

Seq2Seqは、入力側と出力側で別々のRNNを使う。自然言語処理や機械翻訳などに使用される。

・Encoder RNN
インプットのデータを単語等で区切り、RNNの入力として渡す。最後の区切りを入力した際にできたhidden Stateが、入力した分の意味を表すベクトルとなる。

・Decorder RNN
Encoder と逆方向の動きをすることで意味を解釈する。

<確認テスト1>
下記の選択肢から、seq2seqについて説明しているものを選べ。
(1)時刻に関して順方向と逆方向のRNNを構成し、それら2つの中間層表現を特徴量として利用するものである。
(2)RNNを用いたEncoder-Decoderモデルの一種であり、機械翻訳などのモデルに使われる。
(3)構文木などの木構造に対して、隣接単語から表現ベクトル(フレーズ)を作るという演算を再帰的に行い(重みは共通)、文全体の表現ベクトルを得るニューラルネットワークである。
(4)RNNの一種であり、単純なRNNにおいて問題となる勾配消失問題をCECとゲートの概念を導入することで解決したものである。

<解答>
(2)

(1)は双方向RNNについての説明
(3)は構文木についての説明
(4)はLSTMについての説明

・HRED

過去n-1 個の発話から次の発話を生成する。Seq2seqでは、会話の文脈無視で、応答がなされたが、HREDでは、前の単語の流れに即して応答されるため、より人間らしい文章が生成される。

RED は確率的な多様性が字面にしかなく、会話の「流れ」のような多様性が無い。同じコンテキスト(発話リスト)を与えられても、答えの内容が毎回会話の流れとしては同じものしか出せない。また、HRED は短く情報量に乏しい答えをしがちである。短いよくある答えを学ぶ傾向がある。

・VHRED

HREDに、VAEの潜在変数の概念を追加したもの。HREDの課題を、VAEの潜在変数の概念を追加することで解決した構造。

<確認テスト2>
seq2seqとHRED、HREDとVHREDの違いを簡潔に述べよ。
<解答>
seq2seqは会話の文脈を無視した処理を行うが、HREDは文脈の流れを考慮した処理を行う。また、HREDは短く情報量の乏しいな返事しかできない課題があったが、VHREDはHREDの課題をVAEの潜在変数の概念を追加して解決した。

・オートエンコーダ

オートエンコーダとは教師なし学習の一つ。そのため学習時の入力データは訓練データのみで教師データは利用しない。

説明入力データから潜在変数に変換するニューラルネットワークがEncoderで、潜在変数をインプットとして元画像を復元するニューラルネットワークがDecoderである。メリットとして、次元削減が行えることが挙げられる。

・VAE

通常のオートエンコーダーの場合、何かしら潜在変数zにデータを押し込めているものの、その構造がどのような状態かわからない。VAEはこの潜在変数zに確率分布z∼N(0,1)を仮定したもの。VAEは、データを潜在変数zの確率分布という構造に押し込めることを可能にする。

<確認テスト3>

VAEに関する下記の説明文中の空欄に当てはまる言葉を答えよ。
「自己符号化器の潜在変数に____を導入したもの。」

<解答>

確率分布

6. Word2vec

RNNでは、単語のような可変長の文字列をNNに与えることはできないため、固定長で表現する必要がある。Word2vecは単語をベクトル表現する手法
であり、embedding表現を得ることが可能。
一つ一つのベクトルで単語の意味同士が近くなるようなものを生成。

7. AttentionMmechanism

Seq2Seqは長い文章に対応できない。Attension Mechanismは、入力と出力のどの単語が関連しているのか、という関連度を学習する仕組み。重要な単語を自分で見分ける。


この記事が気に入ったらサポートをしてみませんか?