SlideShare une entreprise Scribd logo
1  sur  38
Télécharger pour lire hors ligne
v1.1
scikit-learnとTensorFlowによる
実践機械学習
14章 再帰型ニューラルネットワーク
説明者:飯塚孝好
2019年2月23日
1
v1.1
自己紹介
• 日立の中央研究所等で約20年間、コンパイラ等の研究開発に従事
– 日立のスパコン用の最適化、自動ベクトル化、自動並列化技術の開発
– 日立の大型ストレージ装置用ソフトウェアの高速化技術の開発
※ 入社時は人工知能希望だったが、同じ研究室の同僚と互い違いの部署に配属... 残念...
• 約10年間、ソフトウェア・サービスの事業企画業務に従事
– 新ソフトウェア・サービスの企画・立上げ
– 海外ビジネス展開、アライアンスの推進、社外発表・広報活動取り纏め
※ プログラミング等の現場からかなり離れた仕事が続いた... 残念...
• 2015/10:クラウド事業(当時担当)の先が見えたため、早期退職
• 2015/10~2016/10:最新のIT技術に飢えており、貪るように勉強
– 勉強内容はブログ(http://itsukara.hateblo.jp/)、github(https://github.com/Itsukara)等で共有
– Alpha GOで人工知能技術の進展を知り、色々本を買ったが読み進まず、輪講会に参加
– DeepMindの強化学習での成果が輪講会に話題になり、当時最難関のゲームの強化学習に挑戦
– 良い結果が出てOpenAI Gymにアップ。OpenAIから面接オファーがあり、サンフランシスコ旅行を楽しむ
• 2016/10:スタッフサービス経由で日立に再就職 (AIとは全く関係ない仕事)
• ~現在: 強化学習の成果を国際学会と国内セミナ(JulyTech)で発表したが、
AI関連の仕事に就くのは難しいことを実感。細々とAIの勉強を継続中。
2
v1.1
• 出力を入力に再帰するタイプのニューラルネットワーク
• 入力データの履歴を記憶し、未来の予測に活用
3
再帰型ニューラルネットワーク(RNN)とは
RNN: Recurrent Neural Network
v1.1
RNNの特徴
• 未来を予測できるタイプのニューラルネットワーク
– 時間と共に次々に生成されるデータから未来を予測
– 株価などの時系列データを分析し、売買時期を予測
– 自動運転システムで、車の軌跡を予測し、衝突回避
• 任意の長さのシーケンス(sequence)を操作可能
• 自然言語処理(NLP: Natural Language Processing)等で活用
– 文章、文書、オーディオ➡自動翻訳、テキスト変換、感情分析等
4
v1.1
RNN適用例
• Magenta https://magenta.tensorflow.org/
– 機械学習を活用して音楽や芸術を作成
– 自動生成音楽例 https://goo.gl/IxIL1V
• 文章自動生成 https://goo.gl/onkPNd
– ポール・グレアム(詩人)、シェークスピア等の文章を学習し、生成
• 写真タイトル自動生成 https://goo.gl/Nwx7Kh
5
v1.1
目次
14.1 再帰ニューロン
14.2 TensorFlowによる初歩的なRNN
14.3 RNNの訓練
14.4 深層RNN
14.5 LSTMセル
14.6 GRUセル
14.7 自然言語処理
付録 RNNに少し関連した最近の動向
6
v1.1
14.1 再帰ニューロン (1)
• 再帰ニューロン:もっとも単純なRNN(Recurrent Neural Network)
• 下図左のように、入力「x」に加え、自分自身の出力「y」も入力とする
• タイムステップ「t」を明示し、時間軸に沿って表現すると下図右になる
• これを時間軸に沿ってネットワークを「アンロールする」という
※ 本ページでは、「x」はベクトル、「y」はスカラ
7
v1.1
14.1 再帰ニューロン (2)
• 再帰ニューロンが複数並んだ層を考え、層単位での再帰を考える
※本ページ以降は、「x」「y」共にベクトル
• 単一インスタンスに対する出力「y(t)」は下記
• ミニバッチの全インスタンスに対する出力「Y(t)」は下記
8
m X(t) Y(t-1)
Wx
b・ +
ni nn
ni
nn
nn
1
nn: ニューロン数
m: ニバッチのインスタンス数
ni: 入力のフィーチャー数
• 「X(t)」は「X(0),X(1) ,X(2) , ...,X(t-1)」の関数となる
※ 「X(t)」は「X(t),Y(t-1)」の関数で、 「Y(t-1)」は「X(t-1),Y(t-2)」の関数で、...
Wy
nn
v1.1
14.1.1 記憶セル
• 再帰ニューロンの出力「X(t)」は全ての過去の入力「X(0),X(1) ,X(2) , ...,X(t-1)」の
関数なので、再帰ニューロンは、一種の記憶を持っていると言える
• そこで、単一の再帰ニューロンを記憶セル(memory cell)/セルと呼ぶ
• タイムステップ「t」でのセルの状態は「h(t)」と書く (hはhiddenの意味)
• 式で表現すると、h(t) = f(h(t-1), x (t))
• もっと複雑なセル(後述)では、 「h(t)」以外の入出力を含むため、式も複雑
9
v1.1
14.1.2 入出力シーケンス
• RNNの入力・出力の繋ぎ方で以下の4つに大分類される
• 左上:入力シーケンスを受け取り、同時に出力シーケンスを生成、利用
株価予測などの時系列データの予測に有用 (各タイムステップで、1日後の株価を出力)。
• 右上:入力シーケンスを受け取り、最後の出力(ベクトル)のみ利用
シーケンスをベクトルに変換。例えば映画の評論文から、文のスコア(好き:1~嫌い:-1)を出力。
• 左下:最初の入力(ベクトル)のみ受取、シーケンスを出力
ベクトルをシーケンスに変換。例えば画像を1つ入力し、その画像のタイトルを複数出力。
• 右下:入力シーケンスをベクトルに変換し(エンコーダ)、ベクトルからシーケンスを生成(デコーダ)
言語翻訳で有用。例えば、ある言語で書かれた文を、エンコーダがベクトルに変換し、
デコーダがベクトルから別の言語の文を生成。エンコーダ-デコーダと呼ばれる。 10
v1.1
14.2 TensorFlowによる初歩的なRNN (1)
• 理解を深めるために、TensorFlowのRNNを使わずに、手作業でRNNを実装
• ネットワーク作成
– 同じ重み「Wx、Wy」とバイアス「 b 」を2つの層で共有
– 各層に入力を与え、各層から出力
11
n_inputs = 3 # 入力のフィーチャー数
n_neurons = 5 # ニューロンの数
X0 = tf.placeholder(tf.float32, [None, n_inputs]) # タイムステップ「0」のミニバッチ入力用プレスホルダー
X1 = tf.placeholder(tf.float32, [None, n_inputs]) # タイムステップ「1」のミニバッチ入力用プレスホルダー
Wx = tf.Variable(tf.random_normal(shape=[n_inputs, n_neurons],dtype=tf.float32)) # Wx (乱数で初期化)
Wy = tf.Variable(tf.random_normal(shape=[n_neurons,n_neurons],dtype=tf.float32)) # Wy (乱数で初期化)
b = tf.Variable(tf.zeros([1, n_neurons], dtype=tf.float32)) # b (0で初期化)
Y0 = tf.tanh(tf.matmul(X0, Wx) + b) # X0 ・Wx
Y1 = tf.tanh(tf.matmul(Y0, Wy) + tf.matmul(X1, Wx) + b) # Xy ・Wy + X1 ・Wx + b
init = tf.global_variables_initializer()
b・ +
nn
1
nn: ニューロン数
m: ニバッチのインスタンス数
ni: 入力のフィーチャー数
+ ・
m
ni
m X(t) Y(t-1)
Wx
ni
ni nn Wy
nn
nn
nn
v1.1
14.2 TensorFlowによる初歩的なRNN (2)
• 各タイムステップでの入力を準備し、1回実行
• ミニバッチの全インスタンスに対する全ニューロンの出力
• 手動➡タイムステップが大きくなると、作成するグラフも大きくなり作成大変
12
import numpy as np
# ミニバッチ:インスタンス0、インスタンス1、インスタンス2、インスタンス3
X0_batch = np.array([[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 0, 1]]) # t = 0
X1_batch = np.array([[9, 8, 7], [0, 0, 0], [6, 5, 4], [3, 2, 1]]) # t = 1
with tf.Session() as sess:
init.run()
Y0_val, Y1_val = sess.run([Y0, Y1], feed_dict={X0: X0_batch, X1: X1_batch})
X(0)
3
4
X(1)
3
4
>> print(Y0_val) # t = 0 の時点での出力
[[-0.06640061 0.9625767 0.6810579 0.7091854 -0.89821595] # インスタンス0
[ 0.99777555 -0.71978873 -0.99657613 0.96739244 -0.99989706] # インスタンス1
[ 0.99999785 -0.9989881 -0.99999887 0.9967763 -0.9999999 ] # インスタンス2
[ 1. -1. -1. -0.9981892 0.9995087 ]] # インスタンス3
>> print(Y1_val) # t = 1 の時点での出力
[[ 1. -1. -1. 0.40200272 -0.99999994] # インスタンス0
[-0.12210432 0.62805295 0.96718436 -0.9937122 -0.2583932 ] # インスタンス1
[ 0.99999815 -0.9999994 -0.99999744 -0.8594331 -0.99998796] # インスタンス2
[ 0.99928296 -0.9999981 -0.9999059 0.98579615 -0.9220575 ]] # インスタンス3
Y(0)4
5
Y(0)4
5
v1.1
14.2.1 時系列に沿った静的なアンロール (1)
• TensorFlowのstatic_rnn()関数を用い、前回と同じRNNを作る
• セルファクトリ:関数「__call__()」を呼び出すことで、「num_units」個の再帰
ニューロンから構成されるセル(セル層)を作成するファクトリ
• static_rnn():入力ごと(X0とX1)に1度ずつセルファクトリの「__call__()」を呼び
出して、共通の重みとバイアスを持つセルを2つ作り、手動構築と同様に連
鎖的にグラフを構築する
• output_seqs:個々のタイムステップの出力テンソルを一つにまとめたリスト
• states:ネットワークの最終状態を格納するテンソル
基本セルの場合、最終状態は最後の出力と同じ
• 上記では、タイムステップが50あると、50個の入力プレスホルダーと50個の
出力テンソルを定義しなければならず面倒。次ページでこれを単純化。
13
X0 = tf.placeholder(tf.float32, [None, n_inputs]) # タイムステップ「0」のミニバッチ入力用プレスホルダー
X1 = tf.placeholder(tf.float32, [None, n_inputs]) # タイムステップ「1」のミニバッチ入力用プレスホルダー
basic_cell = tf.nn.rnn_cell.BasicRNNCell(num_units=n_neurons) # セルファクトリBasicRNNCellを作成
output_seqs, states = tf.nn.static_rnn(basic_cell, [X0, X1], # セルファクトリと入力テンソルリストを与え、
dtype=tf.float32) # 静的にアンロールされたRNNを作成
Y0, Y1 = output_seqs
v1.1
14.2.1 時系列に沿った静的なアンロール (2)
• タイムステップ数分の入力プレスホルダー(出力テンソル)を1つ(X)に纏める
• 入力を準備し、1回実行
• 実装容易化したがグラフは大きなまま
➡メモリ不足発生要因 (次頁で解決)
14
X = tf.placeholder(tf.float32, [None, n_steps, n_inputs]) # [ミニバッチサイズ、タイムステップ数、入力フィーチャー数]
X_seqs = tf.unstack(tf.transpose(X, perm=[1, 0, 2])) # タイムステップ数、[ミニバッチサイズ、入力フィーチャー数]
# transpose():転置したテンソルを返す unstack():テンソルから、「1次元低いテンソル」のリストを作成して返す
basic_cell = tf.nn.rnn_cell.BasicRNNCell(num_units=n_neurons)
output_seqs, states = tf.nn.static_rnn(basic_cell, X_seqs, # X_seqsは[X0, X1]に相当
dtype=tf.float32)
outputs = tf.transpose(tf.stack(output_seqs), perm=[1, 0, 2])
# タイムステップ数(リスト)、[ミニバッチサイズ、ニューロン数]という形状の出力を、stack()で1つのテンソルンに纏めた後、
# transpose()で転置し、[ミニバッチサイズ、タイムステップ数、入力フィーチャー数]と形状に変更
X_batch = np.array([
# t = 0 t = 1
[[0, 1, 2], [9, 8, 7]], # instance 0
[[3, 4, 5], [0, 0, 0]], # instance 1
[[6, 7, 8], [6, 5, 4]], # instance 2
[[9, 0, 1], [3, 2, 1]], # instance 3
])
with tf.Session() as sess:
init.run()
outputs_val = outputs.eval(feed_dict={X: X_batch})
実行結果
v1.1
14.2.2 時系列に沿った動的なアンロール
• dynamic_rnn():while_loop()を使い、セルを適切な回数(動的に)実行
– 入力テンソル:[None, n_steps, n_inputs]という形状 (転置不要)
– 出力テンソル:[None, n_steps, n_neurons]という形状 (転置不要)
• dynamic_rnn()を使って前回と同じRNNを作る
• (入力、実行、出力は前回と同じ(形状)のため省略)
15
X = tf.placeholder(tf.float32, [None, n_steps, n_inputs])
basic_cell = tf.nn.rnn_cell.BasicRNNCell(num_units=n_neurons)
outputs, states = tf.nn.dynamic_rnn(basic_cell, X, dtype=tf.float32)
v1.1
14.2.3 可変長入力シーケンスの処理
• 入力シーケンスの長さが一定しない場合(文等)、dynamic_rnn()を呼び出す
ときに、インスタンス毎の入力シーケンスの長さを示す1Dテンソルを
sequence_length引数として渡せばよい
• 入力シーケンス長が短いインスタンスは、0ベクトルでパディングする
• 実行時は、sequence_length引数で渡したseq_lengthに値を渡す
• 入力シーケンスの長さを超えるタイムステップは0ベクトルが出力される
16
seq_length = tf.placeholder(tf.int32, [None]) #インスタンス毎の入力シーケンスの長さを示す1Dテンソル
outputs, states = tf.nn.dynamic_rnn(basic_cell, X, dtype=tf.float32,
sequence_length=seq_length) # sequence_length引数
X_batch = np.array([
# step 0 step 1
[[0, 1, 2], [9, 8, 7]], # instance 0
[[3, 4, 5], [0, 0, 0]], # instance 1 (0ベクトルでパディング)
[[6, 7, 8], [6, 5, 4]], # instance 2
[[9, 0, 1], [3, 2, 1]], # instance 3
])
seq_length_batch = np.array([2, 1, 2, 2])
with tf.Session() as sess:
init.run()
outputs_val, states_val = sess.run(
[outputs, states], feed_dict={X: X_batch, seq_length: seq_length_batch})
v1.1
14.2.4 可変長出力シーケンスの処理
• 出力シーケンスも可変長の場合の対応方法は以下
• 入力シーケンスと同じ長さになる場合は、sequence lengthパラメータを設定
• 上記が同じにならない場合(例えば翻訳後の文の長さは不明)、EOSトークン
(EOS token:End-Of-Sequence token)を出力し、それ以降は無視する
17
v1.1
14.3 RNNの訓練
• RNNの訓練では、時系列に沿ってアンロールし、単純に通常のバックプロ
パゲーションを行う (BPTT: backpropagation through time:通時的逆伝搬)
• 最初、アンロールされたネットワークを前進パスで通り抜ける (破線)
• 次に、無視されない出力を使ったコスト関数(上記ではC(Y(2), Y(3), Y (4) ))を
使って出力を評価し、その勾配を、後退方向に伝えていく (実線)
• 最後に、上記過程で計算した勾配を使ってパラメータを更新する
– 各ステップで同じW、bが使われるので、全タイムステップの勾配をW、bに反映
18
v1.1
14.3.1 シーケンス分類器の訓練 (1)
• RNNでMNISTイメージを分類してみる (なお、イメージ分類はCNNの方が適してる)
19
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
X(0)
X(1)
X(2)
X(3)
X(4)
X(5)
X(6)
X(7)
X(8)
X(9)
X(10)
X(11)
X(12)
X(13)
X(14)
X(15)
X(16)
X(17)
X(18)
X(19)
X(20)
X(21)
X(22)
X(23)
X(24)
X(25)
X(26)
X(27)
150個の再帰ニューロン
n_steps = 28
n_inputs = 28
n_neurons = 150
n_outputs = 10
learning_rate = 0.001
X = tf.placeholder(tf.float32, [None, n_steps, n_inputs])
y = tf.placeholder(tf.int32, [None])
basic_cell = tf.nn.rnn_cell.BasicRNNCell(num_units=n_neurons)
outputs, states = tf.nn.dynamic_rnn(basic_cell, X, dtype=tf.float32)
logits = tf.layers.dense(states, n_outputs)
xentropy = tf.nn.sparse_softmax_cross_entropy_with_logits(labels=y,
logits=logits)
loss = tf.reduce_mean(xentropy)
optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate)
training_op = optimizer.minimize(loss)
correct = tf.nn.in_top_k(logits, y, 1)
accuracy = tf.reduce_mean(tf.cast(correct, tf.float32))
init = tf.global_variables_initializer()
(X_train, y_train), (X_test, y_test) = tf.keras.datasets.mnist.load_data()
X_train = X_train.astype(np.float32).reshape(-1, 28*28) / 255.0
X_test = X_test.astype(np.float32).reshape(-1, 28*28) / 255.0
y_train = y_train.astype(np.int32)
y_test = y_test.astype(np.int32)
X_valid, X_train = X_train[:5000], X_train[5000:]
y_valid, y_train = y_train[:5000], y_train[5000:]
def shuffle_batch(X, y, batch_size):
rnd_idx = np.random.permutation(len(X))
n_batches = len(X) // batch_size
for batch_idx in np.array_split(rnd_idx, n_batches):
X_batch, y_batch = X[batch_idx], y[batch_idx]
yield X_batch, y_batch
X_test = X_test.reshape((-1, n_steps, n_inputs))
※青字部分以外は、CNNとほぼ同じ
※CNNとほぼ同じ
v1.1
14.3.1 シーケンス分類器の訓練 (2)
• RNNでMNISTイメージを訓練する
• 訓練中の出力
• Testデータで98%を超える正確度が得られた。➡悪くない数字
• HeでRNNの重みを初期化、訓練長期化、正規化等でもっと上がるだろう
• RNNの初期化は、ネットワーク構築コードを変数スコープでラップする
20
n_epochs = 100
batch_size = 150
with tf.Session() as sess:
init.run()
for epoch in range(n_epochs):
for X_batch, y_batch in shuffle_batch(X_train, y_train, batch_size):
X_batch = X_batch.reshape((-1, n_steps, n_inputs))
sess.run(training_op, feed_dict={X: X_batch, y: y_batch})
acc_batch = accuracy.eval(feed_dict={X: X_batch, y: y_batch})
acc_test = accuracy.eval(feed_dict={X: X_test, y: y_test})
print(epoch, "Last batch accuracy:", acc_batch, "Test accuracy:", acc_test)
0 Last batch accuracy: 0.9533333 Test accuracy: 0.9288
1 Last batch accuracy: 0.96 Test accuracy: 0.9471
2 Last batch accuracy: 0.96 Test accuracy: 0.9499
...
98 Last batch accuracy: 0.99333334 Test accuracy: 0.977
99 Last batch accuracy: 0.99333334 Test accuracy: 0.9805
with tf.variable_scope(“rnn”, initializer=variance_scaling_initializer()):
# ネットワーク構築コードを記載
※CNNとほぼ同じ
v1.1
14.3.2 時系列データを予測するための訓練 (1)
• 株価、基本、脳波パタンなどの、時系列データの処理方法を少し見る
• 例として、時系列データから無作為に選出した20個の値で訓練する
• ターゲットシーケンスは、タイムステップを一つ先にずらしたシーケンス
• 各タイムステップで予測値1つのみが
欲しいので、全結合(FC)ラッパーを被せる
• なお、この全結合層は、全て同じ重み、
とバイアスを共有する
21
訓練インスタンス
インスタンス
ターゲット
訓練インスタンス時系列データ
v1.1
14.3.2 時系列データを予測するための訓練 (2)
• MSE(平均2乗誤差)をコスト関数として、以下のようなコード・結果になる
22
n_steps = 20
n_inputs = 1
n_neurons = 100
n_outputs = 1
X = tf.placeholder(tf.float32, [None, n_steps, n_inputs])
y = tf.placeholder(tf.float32, [None, n_steps, n_outputs])
cell = tf.contrib.rnn.OutputProjectionWrapper(
tf.nn.rnn_cell.BasicRNNCell(num_units=n_neurons, activation=tf.nn.relu),
output_size=n_outputs) # 全結合(FC: Full Connect)ラッパー
outputs, states = tf.nn.dynamic_rnn(cell, X, dtype=tf.float32)
learning_rate = 0.001
loss = tf.reduce_mean(tf.square(outputs - y)) # MSE
optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate)
training_op = optimizer.minimize(loss)
init = tf.global_variables_initializer()
saver = tf.train.Saver()
n_iterations = 1500
batch_size = 50
with tf.Session() as sess:
init.run()
for iteration in range(n_iterations):
X_batch, y_batch = next_batch(batch_size, n_steps)
sess.run(training_op, feed_dict={X: X_batch, y: y_batch})
if iteration % 100 == 0:
mse = loss.eval(feed_dict={X: X_batch, y: y_batch})
print(iteration, "¥tMSE:", mse)
saver.save(sess, "./my_time_series_model") # not shown in the book
0 MSE: 10.261382
100 MSE: 0.3879291
200 MSE: 0.10900871
300 MSE: 0.06135445
400 MSE: 0.059347205
500 MSE: 0.05819268
600 MSE: 0.05220854
700 MSE: 0.047660623
800 MSE: 0.04888802
900 MSE: 0.047597326
1000 MSE: 0.047681753
1100 MSE: 0.046209298
1200 MSE: 0.039961636
1300 MSE: 0.046320174
1400 MSE: 0.041305345
モデルのテスト結果
インスタンス
ターゲット
予測
実行結果
v1.1
14.3.2 時系列データを予測するための訓練 (3)
• 全結合ラッパーよりも、下記の方が効率が良く大幅なスピードアップが可能
 RNNの形状[ステップ数、バッチサイズ、ニューロン数]の出力を、
 積み上げて、形状[ステップ数×バッチサイズ、ニューロン数]のテンソルにリシェープし、
 全結合(FC)層を通した形状[ステップ数×バッチサイズ、1]の出力を
 各ステップでの[バッチサイズ]の出力に展開
23
v1.1
14.3.3 独創的RNN
• 訓練結果を基に、独創的なシーケンスを生成
• n_steps個の初期値(例えば0)を与え、これを種に、時系列データを生成
• 具体的には、1回毎に1個、1ステップ先のデータを生成し、
これを入力シーケンスに加え、最新n_stepsのシーケンスで処理を繰り返す
• 結果例は下記(赤枠部分が、それぞれの初期シーケンス)
24
sequence = [0.] * n_steps
for iteration in range(300):
X_batch = np.array(sequence[-n_steps:]).reshape(1, n_steps, 1) # 最新のn_stepsのシーケンスを取り出す
y_pred = sess.run(outputs, feed_dict={X: X_batch})
sequence.append(y_pred[0, -1, 0]) # 予測シーケンスの最後のデータを入力シーケンスに追加
(注) 初期シーケンスが同じでも、訓練結果によって、独創的シーケンスの内容は変わり、上記と別の結果になる
v1.1
14.4 深層RNN
• 通常NN同様にセルを積み上げたものが深層RNN
• Tensorflowでは、MultiRNNCellで構築可能
25
n_neurons = 100
n_layers = 3
layers = [tf.nn.rnn_cell.BasicRNNCell(num_units=n_neurons) for layer in range(n_layers)] # セルのリスト
multi_layer_cell = tf.nn.rnn_cell.MultiRNNCell(layers) # 深層RNNのセル
outputs, states = tf.nn.dynamic_rnn(multi_layer_cell, X, dtype=tf.float32) # 深層RNN
深層RNN
時系列に沿って深層RNNをアンロールしたもの
v1.1
14.4.1 複数のGPUによる深層RNNの分散処理
• 深層RNNを複数GPUで分散処理するには、工夫が必要
– BasicRNNCellがセルそのものではないため、BasicRNNCell作成時にGPUを指定できない
– MultiRNNCellがBasicRNNCellを呼び出してセルを作るが、デバイスを指定できない
• 下記の様に、独自のラッパセルを作ることで、処理を複数GPUに分散可能
26
class DeviceCellWrapper(tf.nn.rnn_cell.RNNCell):
def __init__(self, device, cell):
self._cell = cell
self._device = device
@property
def state_size(self):
return self._cell.state_size
@property
def output_size(self):
return self._cell.output_size
def __call__(self, inputs, state, scope=None): # 実セル作成時に呼び出される
with tf.device(self._device): # セルの処理をデバイスに割り当てる
return self._cell(inputs, state, scope)
devices = ["/gpu:0", "/gpu:1", "/cpu:0"]
cells = [DeviceCellWrapper(dev,tf.nn.rnn_cell.BasicRNNCell(num_units=n_neurons))
for dev in devices]
multi_layer_cell = tf.nn.rnn_cell.MultiRNNCell(cells)
outputs, states = tf.nn.dynamic_rnn(multi_layer_cell, X, dtype=tf.float32)
v1.1
14.4.2 ドロップアウトの適用
• RNNの層間でドロップアウトを適用するにはDropoutWrapperを使う
• keep_probは、訓練中は好きな値(通常0.5)、テスト時はデフォルト(1.0)にする
27
keep_prob = tf.placeholder_with_default(1.0, shape=())
cells = [tf.nn.rnn_cell.BasicRNNCell(num_units=n_neurons)
for layer in range(n_layers)]
cells_drop = [tf.nn.rnn_cell.DropoutWrapper(cell, input_keep_prob=keep_prob)
for cell in cells]
multi_layer_cell = tf.nn.rnn_cell.MultiRNNCell(cells_drop)
rnn_outputs, states = tf.nn.dynamic_rnn(multi_layer_cell, X, dtype=tf.float32)
# 訓練中
train_keep_prob = 0.5
with tf.Session() as sess:
init.run()
for iteration in range(n_iterations):
X_batch, y_batch = next_batch(batch_size, n_steps)
_, mse = sess.run([training_op, loss],
feed_dict={X: X_batch, y: y_batch, keep_prob: train_keep_prob})
saver.save(sess, "./my_dropout_time_series_model")
# テスト時
with tf.Session() as sess:
saver.restore(sess, "./my_dropout_time_series_model")
X_new = time_series(np.array(t_instance[:-1].reshape(-1, n_steps, n_inputs)))
# keep_prob未指定なので、変数定義時のデフォルト値(1.0)になる
y_pred = sess.run(outputs, feed_dict={X: X_new})
v1.1
14.4.3 多数のタイムステップによる訓練の難しさ
• 長いシーケンスに対するRNNは、ステップ(横)方向に深いネットワークになる
• そのため、深層NNの常として、勾配消失/爆発問題の影響を受ける
• 対策として、パラメータの良い初期化方法、飽和を起こさない活性化関数、
バッチ正規化、勾配クリップなどは、RNNでも使える
• しかし、少し長いシーケンスでも100個程度になり、訓練が遅くなりがち
• 良く使われる対策は、時系列に沿ったバックプロパゲーションの途中打ち切
り (訓練中は、入力シーケンスを途中で切ってしまう)だが、色々と副作用あり
• また、長いRNNは、最初の入力の記憶が次第に消えるという問題もあり
– 最初のステップの入力の痕跡が、後の方のステップでは殆ど無くなる
– 文章の最初に結論を言って、後から情報を追加すると、結論が忘れられる可能性あり
• 記憶が消える問題への対策として、長期記憶を持つ各種セルが考案された
– 長期記憶を持つセルは効果が、その効果が実証されている
• 以下では、長期記憶セルとして人気のあるLSTMとGRUを見ていく
28
v1.1
14.5 LSTMセル (1)
• LSTM(長期短期記憶:long short-term memory)は、Sepp Hochreiter他が
1997年に提案し、多くの人々が研究に少しずつ改良し、現在の形になった
• 基本セルの代わりにLSTMを使うだけで、訓練は短時間で収束し、データ内
の長期的な依存関係を検出できる、非常の性能の良いRNNになる
• 短期的な記憶「h(t)」に加え、長期的な記憶「c(t)」を入力・出力する
• 長期記憶から捨てるべき要素(忘却)、長期的記憶に格納すべき要素(入力)、
短期記憶/出力で読み取るべき要素(出力)を、3つのゲートコントローラ(下記
のf(t)、i(t)、o(t))で制御し、最適なゲートコントローラを学習できることが特徴
29
lstm_cell = tf.nn.rnn_cell.BasicLSTMCell(num_units=n_neurons)
前ステップの長期記憶
前ステップの短期記憶
出力長期記憶
出力短期記憶
忘却ゲート
入力ゲート
出力ゲート 要素毎の乗算
要素毎の加算
出力は0~1の範囲
v1.1
14.5 LSTMセル (2)
30
式14-3 LSTMの計算式
v1.1
14.5.1 ピープホール接続
• 基本LSTMセルでは、各ゲートのコントローラ(全結合層)は、「x(t)」と直前の
短期記憶「h(t-1)」を入力しているが、長期記憶「c(t)」を入力に加えることを、
ピープホール接続と呼ぶ (Felix他が2000年に提案)
• TensorFlowでピープホール接続を実装するには、BasicLSTMCellでなく、
LSTMCellを使い、use_peepholes=Trueを指定すればよい
• LSTMセルには、ほかにも多数の変種がある
その中で特に人気のあるGRUセルを次に紹介する
31
lstm_cell = tf.nn.rnn_cell.LSTMCell(num_units=n_neurons, use_peepholes=True)
v1.1
14.6 GRUセル
• GRU (Gated Recurrent Unit)は、Kyunghyun Chao等が2014年提案
• LSTMを単純化したものであり、LSTMとほぼ同様に機能する
– 長期記憶と短期記憶が結合されて、一つのベクトル「h(t)」になっている
– 一つのゲートコントローラ「z(t)」で、忘却ゲートと入力ゲートの両方を制御
• 値が1の場合忘却ゲートが開き、入力ゲートが閉じる。値が0の場合はその逆
– 出力ゲートが無い。代わりに、直前の状態のどの部分をメイン層で用いるかを制御す
る新しいゲートコントローラ「r(t)」が追加されている
32
式14-4 GRUの計算式
• LSTM、GRUセルは、RNN成功の理由であり、自然言語処理でも応用されている
v1.1
14.7 自然言語処理
• 機械翻訳、自動要約、構文解析、感情分析などの最先端の自然言語処理
(NLP: Natural Language Processing)の多くは、RNNを基礎としている
• 以下では、機械翻訳のモデルがどのようになっているかを簡単に紹介
• 詳細は、以下で説明されている
– Word2Vec: https://www.tensorflow.org/tutorials/representation/word2vec
– Seq2Seq: https://github.com/tensorflow/nmt
33
v1.1
14.7.1 単語の埋め込み
• 自然言語処理の入口として、まずは、単語の表現方法を選ぶ必要がある
• 単語間の関係は当初不明なので、ワンホットベクトル表現が考えられる
– ワンホットベクトルの例: (0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,...,0)
– 語彙集が5万語含むとき、1要素が1、他は0の疎な表現になり、メモリ効率が非常に悪い
• もっとも一般的な買いは、埋め込みと呼ばれる手法であり、単語を小さく密な
ベクトル空間(150次元程度)に埋め込み、NNで単語のベクトル値を学習する
• 訓練開始時は、埋め込みベクトルの値は無作為だが、訓練中にバックプロパ
ゲーションによって、埋め込みベクトルの値を自動的に書き換えていく
– 文章の途中までの単語列を入力シーケンスにし、次の単語を予測するようNNを訓練
• 訓練で、類似する単語は互いに近づき、かなり意味のある形で組織される
34
woman – man ≒ queen - king walked – walking ≒ swam - swimming 首都 – 国は、ほぼ同じベクトル
v1.1
14.7.2 機械翻訳のためのエンコーダ-デコーダネットワーク (1)
• 英語の文をフランス語に翻訳する簡単な機械翻訳モデルを見てみる
※ seq2seqサイト(https://github.com/tensorflow/nmt) の内容を記載(入力文の語順は元のまま)
• エンコーダが入力文を意味ベクトルに変換、デコーダが意味ベクトルから翻訳文を生成
• エンコーダとデコーダは深層RNN(下図は2層)で、前段に埋め込み層を持つ
35
入力は単語の語彙辞書内番号の列
例えば、[12, 47, 7, 834]
各単語が150次元ベクトル空間に埋め込まれる
深層RNN (2層)
各ステップの出力を出力辞書長のロジットに変換
(完全結合)
ロスとして、ターゲット文と翻訳文の差異を計算
(sparse_softmax_cross_entropy_with_logits)
v1.1
14.7.2 機械翻訳のためのエンコーダ-デコーダネットワーク (2)
• 下記、英独翻訳の訓練済みデータを含む、各種DLモデルが公開されている
– https://github.com/tensorflow/tensor2tensor
– Google Colaboratoryで、英独翻訳の訓練済みデータを試せる
• https://colab.research.google.com/github/tensorflow/tensor2tensor/blob/master/tensor2tensor/notebooks/hello_t2t.ipynb
36
入力と出力の例
v1.1
付録 RNNに少し関連した最近の動向(1)
• 2018/11/2、Googleが言語表現事前訓練手法「BERT」をオープンソース公開
– 世界最高レベルの訓練手法であり、事前学習済モデルも一緒に公開されている
– Google曰く:『世界の誰もが、「単一Cloud TPUなら約30分」「単一GPUなら約数時間」で、独
自の「最新の質問応答システム」や「その他のさまざまなモデル」を訓練できる』
– BERTを使った例が、Google Colaboratoryノートブックで公開されている
• https://colab.research.google.com/github/google-
research/bert/blob/master/predicting_movie_reviews_with_bert_on_tf_hub.ipynb
• 2018/12/4、NIPSで、時系列データの全く新しい分析手法(ODE)が提案された
– 時系列データに欠損値があったり、入力データ感覚が不揃いな場合に対応できる
– ResNetの層構造の式が、常微分方程式(ODE)の解法(オイラー法)に似ていることに着想
– ODEなら、賢いODE SOlverを活用でき、また、離散化された層構造に縛られない
– MNISTでの試行結果:RestNetと同じ精度だが、パラメータ・メモリ使用量が少ない
– 紹介記事:https://www.slideshare.net/DeepLearningJP2016/dlneural-ordinary-differential-equations
37
BERT:Bidirectional Encoder Representations from Transformers
v1.1
付録 RNNに少し関連した最近の動向 (2)
• 2019/2/19、OpenAIが本物と見分けがつかないフェイクニュースを自動生成
– https://gigazine.net/news/20190216-ai-fake-text-generator-dangerous-release/
38
入力 出力
自動生成

Contenu connexe

Similaire à Scikit-learn and TensorFlow Chap-14 RNN (v1.1)

ぱっと見でわかるC++11
ぱっと見でわかるC++11ぱっと見でわかるC++11
ぱっと見でわかるC++11
えぴ 福田
 
Androidの入力システム
Androidの入力システムAndroidの入力システム
Androidの入力システム
magoroku Yamamoto
 
C++0x in programming competition
C++0x in programming competitionC++0x in programming competition
C++0x in programming competition
yak1ex
 
Boost9 session
Boost9 sessionBoost9 session
Boost9 session
freedom404
 

Similaire à Scikit-learn and TensorFlow Chap-14 RNN (v1.1) (20)

2023_freshman
2023_freshman2023_freshman
2023_freshman
 
2018/06/23 Sony"s deep learning software and the latest information
2018/06/23 Sony"s deep learning software and the latest information2018/06/23 Sony"s deep learning software and the latest information
2018/06/23 Sony"s deep learning software and the latest information
 
ぱっと見でわかるC++11
ぱっと見でわかるC++11ぱっと見でわかるC++11
ぱっと見でわかるC++11
 
[第2版]Python機械学習プログラミング 第14章
[第2版]Python機械学習プログラミング 第14章[第2版]Python機械学習プログラミング 第14章
[第2版]Python機械学習プログラミング 第14章
 
Androidの入力システム
Androidの入力システムAndroidの入力システム
Androidの入力システム
 
最適化計算の概要まとめ
最適化計算の概要まとめ最適化計算の概要まとめ
最適化計算の概要まとめ
 
JavaFXでマルチタッチプログラミング
JavaFXでマルチタッチプログラミングJavaFXでマルチタッチプログラミング
JavaFXでマルチタッチプログラミング
 
Enjoy handwritten digits recognition AI !!
Enjoy handwritten digits recognition AI !!Enjoy handwritten digits recognition AI !!
Enjoy handwritten digits recognition AI !!
 
Rでのtry関数によるエラー処理
Rでのtry関数によるエラー処理Rでのtry関数によるエラー処理
Rでのtry関数によるエラー処理
 
Aw svs trifortクラウド選びのポイント
Aw svs trifortクラウド選びのポイントAw svs trifortクラウド選びのポイント
Aw svs trifortクラウド選びのポイント
 
Tokyor23 doradora09
Tokyor23 doradora09Tokyor23 doradora09
Tokyor23 doradora09
 
Mesh tensorflow
Mesh tensorflowMesh tensorflow
Mesh tensorflow
 
Polyphony の行く末(2018/3/3)
Polyphony の行く末(2018/3/3)Polyphony の行く末(2018/3/3)
Polyphony の行く末(2018/3/3)
 
C++0x in programming competition
C++0x in programming competitionC++0x in programming competition
C++0x in programming competition
 
Boost9 session
Boost9 sessionBoost9 session
Boost9 session
 
わんくま同盟大阪勉強会#61
わんくま同盟大阪勉強会#61わんくま同盟大阪勉強会#61
わんくま同盟大阪勉強会#61
 
IoTデバイス センサデータ分析システム
IoTデバイス センサデータ分析システムIoTデバイス センサデータ分析システム
IoTデバイス センサデータ分析システム
 
「深層学習」勉強会LT資料 "Chainer使ってみた"
「深層学習」勉強会LT資料 "Chainer使ってみた"「深層学習」勉強会LT資料 "Chainer使ってみた"
「深層学習」勉強会LT資料 "Chainer使ってみた"
 
x64 のスカラー,SIMD 演算性能を測ってみた @ C++ MIX #10
x64 のスカラー,SIMD 演算性能を測ってみた @ C++  MIX #10x64 のスカラー,SIMD 演算性能を測ってみた @ C++  MIX #10
x64 のスカラー,SIMD 演算性能を測ってみた @ C++ MIX #10
 
15分でざっくり分かるScala入門
15分でざっくり分かるScala入門15分でざっくり分かるScala入門
15分でざっくり分かるScala入門
 

Scikit-learn and TensorFlow Chap-14 RNN (v1.1)

  • 2. v1.1 自己紹介 • 日立の中央研究所等で約20年間、コンパイラ等の研究開発に従事 – 日立のスパコン用の最適化、自動ベクトル化、自動並列化技術の開発 – 日立の大型ストレージ装置用ソフトウェアの高速化技術の開発 ※ 入社時は人工知能希望だったが、同じ研究室の同僚と互い違いの部署に配属... 残念... • 約10年間、ソフトウェア・サービスの事業企画業務に従事 – 新ソフトウェア・サービスの企画・立上げ – 海外ビジネス展開、アライアンスの推進、社外発表・広報活動取り纏め ※ プログラミング等の現場からかなり離れた仕事が続いた... 残念... • 2015/10:クラウド事業(当時担当)の先が見えたため、早期退職 • 2015/10~2016/10:最新のIT技術に飢えており、貪るように勉強 – 勉強内容はブログ(http://itsukara.hateblo.jp/)、github(https://github.com/Itsukara)等で共有 – Alpha GOで人工知能技術の進展を知り、色々本を買ったが読み進まず、輪講会に参加 – DeepMindの強化学習での成果が輪講会に話題になり、当時最難関のゲームの強化学習に挑戦 – 良い結果が出てOpenAI Gymにアップ。OpenAIから面接オファーがあり、サンフランシスコ旅行を楽しむ • 2016/10:スタッフサービス経由で日立に再就職 (AIとは全く関係ない仕事) • ~現在: 強化学習の成果を国際学会と国内セミナ(JulyTech)で発表したが、 AI関連の仕事に就くのは難しいことを実感。細々とAIの勉強を継続中。 2
  • 4. v1.1 RNNの特徴 • 未来を予測できるタイプのニューラルネットワーク – 時間と共に次々に生成されるデータから未来を予測 – 株価などの時系列データを分析し、売買時期を予測 – 自動運転システムで、車の軌跡を予測し、衝突回避 • 任意の長さのシーケンス(sequence)を操作可能 • 自然言語処理(NLP: Natural Language Processing)等で活用 – 文章、文書、オーディオ➡自動翻訳、テキスト変換、感情分析等 4
  • 5. v1.1 RNN適用例 • Magenta https://magenta.tensorflow.org/ – 機械学習を活用して音楽や芸術を作成 – 自動生成音楽例 https://goo.gl/IxIL1V • 文章自動生成 https://goo.gl/onkPNd – ポール・グレアム(詩人)、シェークスピア等の文章を学習し、生成 • 写真タイトル自動生成 https://goo.gl/Nwx7Kh 5
  • 6. v1.1 目次 14.1 再帰ニューロン 14.2 TensorFlowによる初歩的なRNN 14.3 RNNの訓練 14.4 深層RNN 14.5 LSTMセル 14.6 GRUセル 14.7 自然言語処理 付録 RNNに少し関連した最近の動向 6
  • 7. v1.1 14.1 再帰ニューロン (1) • 再帰ニューロン:もっとも単純なRNN(Recurrent Neural Network) • 下図左のように、入力「x」に加え、自分自身の出力「y」も入力とする • タイムステップ「t」を明示し、時間軸に沿って表現すると下図右になる • これを時間軸に沿ってネットワークを「アンロールする」という ※ 本ページでは、「x」はベクトル、「y」はスカラ 7
  • 8. v1.1 14.1 再帰ニューロン (2) • 再帰ニューロンが複数並んだ層を考え、層単位での再帰を考える ※本ページ以降は、「x」「y」共にベクトル • 単一インスタンスに対する出力「y(t)」は下記 • ミニバッチの全インスタンスに対する出力「Y(t)」は下記 8 m X(t) Y(t-1) Wx b・ + ni nn ni nn nn 1 nn: ニューロン数 m: ニバッチのインスタンス数 ni: 入力のフィーチャー数 • 「X(t)」は「X(0),X(1) ,X(2) , ...,X(t-1)」の関数となる ※ 「X(t)」は「X(t),Y(t-1)」の関数で、 「Y(t-1)」は「X(t-1),Y(t-2)」の関数で、... Wy nn
  • 9. v1.1 14.1.1 記憶セル • 再帰ニューロンの出力「X(t)」は全ての過去の入力「X(0),X(1) ,X(2) , ...,X(t-1)」の 関数なので、再帰ニューロンは、一種の記憶を持っていると言える • そこで、単一の再帰ニューロンを記憶セル(memory cell)/セルと呼ぶ • タイムステップ「t」でのセルの状態は「h(t)」と書く (hはhiddenの意味) • 式で表現すると、h(t) = f(h(t-1), x (t)) • もっと複雑なセル(後述)では、 「h(t)」以外の入出力を含むため、式も複雑 9
  • 10. v1.1 14.1.2 入出力シーケンス • RNNの入力・出力の繋ぎ方で以下の4つに大分類される • 左上:入力シーケンスを受け取り、同時に出力シーケンスを生成、利用 株価予測などの時系列データの予測に有用 (各タイムステップで、1日後の株価を出力)。 • 右上:入力シーケンスを受け取り、最後の出力(ベクトル)のみ利用 シーケンスをベクトルに変換。例えば映画の評論文から、文のスコア(好き:1~嫌い:-1)を出力。 • 左下:最初の入力(ベクトル)のみ受取、シーケンスを出力 ベクトルをシーケンスに変換。例えば画像を1つ入力し、その画像のタイトルを複数出力。 • 右下:入力シーケンスをベクトルに変換し(エンコーダ)、ベクトルからシーケンスを生成(デコーダ) 言語翻訳で有用。例えば、ある言語で書かれた文を、エンコーダがベクトルに変換し、 デコーダがベクトルから別の言語の文を生成。エンコーダ-デコーダと呼ばれる。 10
  • 11. v1.1 14.2 TensorFlowによる初歩的なRNN (1) • 理解を深めるために、TensorFlowのRNNを使わずに、手作業でRNNを実装 • ネットワーク作成 – 同じ重み「Wx、Wy」とバイアス「 b 」を2つの層で共有 – 各層に入力を与え、各層から出力 11 n_inputs = 3 # 入力のフィーチャー数 n_neurons = 5 # ニューロンの数 X0 = tf.placeholder(tf.float32, [None, n_inputs]) # タイムステップ「0」のミニバッチ入力用プレスホルダー X1 = tf.placeholder(tf.float32, [None, n_inputs]) # タイムステップ「1」のミニバッチ入力用プレスホルダー Wx = tf.Variable(tf.random_normal(shape=[n_inputs, n_neurons],dtype=tf.float32)) # Wx (乱数で初期化) Wy = tf.Variable(tf.random_normal(shape=[n_neurons,n_neurons],dtype=tf.float32)) # Wy (乱数で初期化) b = tf.Variable(tf.zeros([1, n_neurons], dtype=tf.float32)) # b (0で初期化) Y0 = tf.tanh(tf.matmul(X0, Wx) + b) # X0 ・Wx Y1 = tf.tanh(tf.matmul(Y0, Wy) + tf.matmul(X1, Wx) + b) # Xy ・Wy + X1 ・Wx + b init = tf.global_variables_initializer() b・ + nn 1 nn: ニューロン数 m: ニバッチのインスタンス数 ni: 入力のフィーチャー数 + ・ m ni m X(t) Y(t-1) Wx ni ni nn Wy nn nn nn
  • 12. v1.1 14.2 TensorFlowによる初歩的なRNN (2) • 各タイムステップでの入力を準備し、1回実行 • ミニバッチの全インスタンスに対する全ニューロンの出力 • 手動➡タイムステップが大きくなると、作成するグラフも大きくなり作成大変 12 import numpy as np # ミニバッチ:インスタンス0、インスタンス1、インスタンス2、インスタンス3 X0_batch = np.array([[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 0, 1]]) # t = 0 X1_batch = np.array([[9, 8, 7], [0, 0, 0], [6, 5, 4], [3, 2, 1]]) # t = 1 with tf.Session() as sess: init.run() Y0_val, Y1_val = sess.run([Y0, Y1], feed_dict={X0: X0_batch, X1: X1_batch}) X(0) 3 4 X(1) 3 4 >> print(Y0_val) # t = 0 の時点での出力 [[-0.06640061 0.9625767 0.6810579 0.7091854 -0.89821595] # インスタンス0 [ 0.99777555 -0.71978873 -0.99657613 0.96739244 -0.99989706] # インスタンス1 [ 0.99999785 -0.9989881 -0.99999887 0.9967763 -0.9999999 ] # インスタンス2 [ 1. -1. -1. -0.9981892 0.9995087 ]] # インスタンス3 >> print(Y1_val) # t = 1 の時点での出力 [[ 1. -1. -1. 0.40200272 -0.99999994] # インスタンス0 [-0.12210432 0.62805295 0.96718436 -0.9937122 -0.2583932 ] # インスタンス1 [ 0.99999815 -0.9999994 -0.99999744 -0.8594331 -0.99998796] # インスタンス2 [ 0.99928296 -0.9999981 -0.9999059 0.98579615 -0.9220575 ]] # インスタンス3 Y(0)4 5 Y(0)4 5
  • 13. v1.1 14.2.1 時系列に沿った静的なアンロール (1) • TensorFlowのstatic_rnn()関数を用い、前回と同じRNNを作る • セルファクトリ:関数「__call__()」を呼び出すことで、「num_units」個の再帰 ニューロンから構成されるセル(セル層)を作成するファクトリ • static_rnn():入力ごと(X0とX1)に1度ずつセルファクトリの「__call__()」を呼び 出して、共通の重みとバイアスを持つセルを2つ作り、手動構築と同様に連 鎖的にグラフを構築する • output_seqs:個々のタイムステップの出力テンソルを一つにまとめたリスト • states:ネットワークの最終状態を格納するテンソル 基本セルの場合、最終状態は最後の出力と同じ • 上記では、タイムステップが50あると、50個の入力プレスホルダーと50個の 出力テンソルを定義しなければならず面倒。次ページでこれを単純化。 13 X0 = tf.placeholder(tf.float32, [None, n_inputs]) # タイムステップ「0」のミニバッチ入力用プレスホルダー X1 = tf.placeholder(tf.float32, [None, n_inputs]) # タイムステップ「1」のミニバッチ入力用プレスホルダー basic_cell = tf.nn.rnn_cell.BasicRNNCell(num_units=n_neurons) # セルファクトリBasicRNNCellを作成 output_seqs, states = tf.nn.static_rnn(basic_cell, [X0, X1], # セルファクトリと入力テンソルリストを与え、 dtype=tf.float32) # 静的にアンロールされたRNNを作成 Y0, Y1 = output_seqs
  • 14. v1.1 14.2.1 時系列に沿った静的なアンロール (2) • タイムステップ数分の入力プレスホルダー(出力テンソル)を1つ(X)に纏める • 入力を準備し、1回実行 • 実装容易化したがグラフは大きなまま ➡メモリ不足発生要因 (次頁で解決) 14 X = tf.placeholder(tf.float32, [None, n_steps, n_inputs]) # [ミニバッチサイズ、タイムステップ数、入力フィーチャー数] X_seqs = tf.unstack(tf.transpose(X, perm=[1, 0, 2])) # タイムステップ数、[ミニバッチサイズ、入力フィーチャー数] # transpose():転置したテンソルを返す unstack():テンソルから、「1次元低いテンソル」のリストを作成して返す basic_cell = tf.nn.rnn_cell.BasicRNNCell(num_units=n_neurons) output_seqs, states = tf.nn.static_rnn(basic_cell, X_seqs, # X_seqsは[X0, X1]に相当 dtype=tf.float32) outputs = tf.transpose(tf.stack(output_seqs), perm=[1, 0, 2]) # タイムステップ数(リスト)、[ミニバッチサイズ、ニューロン数]という形状の出力を、stack()で1つのテンソルンに纏めた後、 # transpose()で転置し、[ミニバッチサイズ、タイムステップ数、入力フィーチャー数]と形状に変更 X_batch = np.array([ # t = 0 t = 1 [[0, 1, 2], [9, 8, 7]], # instance 0 [[3, 4, 5], [0, 0, 0]], # instance 1 [[6, 7, 8], [6, 5, 4]], # instance 2 [[9, 0, 1], [3, 2, 1]], # instance 3 ]) with tf.Session() as sess: init.run() outputs_val = outputs.eval(feed_dict={X: X_batch}) 実行結果
  • 15. v1.1 14.2.2 時系列に沿った動的なアンロール • dynamic_rnn():while_loop()を使い、セルを適切な回数(動的に)実行 – 入力テンソル:[None, n_steps, n_inputs]という形状 (転置不要) – 出力テンソル:[None, n_steps, n_neurons]という形状 (転置不要) • dynamic_rnn()を使って前回と同じRNNを作る • (入力、実行、出力は前回と同じ(形状)のため省略) 15 X = tf.placeholder(tf.float32, [None, n_steps, n_inputs]) basic_cell = tf.nn.rnn_cell.BasicRNNCell(num_units=n_neurons) outputs, states = tf.nn.dynamic_rnn(basic_cell, X, dtype=tf.float32)
  • 16. v1.1 14.2.3 可変長入力シーケンスの処理 • 入力シーケンスの長さが一定しない場合(文等)、dynamic_rnn()を呼び出す ときに、インスタンス毎の入力シーケンスの長さを示す1Dテンソルを sequence_length引数として渡せばよい • 入力シーケンス長が短いインスタンスは、0ベクトルでパディングする • 実行時は、sequence_length引数で渡したseq_lengthに値を渡す • 入力シーケンスの長さを超えるタイムステップは0ベクトルが出力される 16 seq_length = tf.placeholder(tf.int32, [None]) #インスタンス毎の入力シーケンスの長さを示す1Dテンソル outputs, states = tf.nn.dynamic_rnn(basic_cell, X, dtype=tf.float32, sequence_length=seq_length) # sequence_length引数 X_batch = np.array([ # step 0 step 1 [[0, 1, 2], [9, 8, 7]], # instance 0 [[3, 4, 5], [0, 0, 0]], # instance 1 (0ベクトルでパディング) [[6, 7, 8], [6, 5, 4]], # instance 2 [[9, 0, 1], [3, 2, 1]], # instance 3 ]) seq_length_batch = np.array([2, 1, 2, 2]) with tf.Session() as sess: init.run() outputs_val, states_val = sess.run( [outputs, states], feed_dict={X: X_batch, seq_length: seq_length_batch})
  • 17. v1.1 14.2.4 可変長出力シーケンスの処理 • 出力シーケンスも可変長の場合の対応方法は以下 • 入力シーケンスと同じ長さになる場合は、sequence lengthパラメータを設定 • 上記が同じにならない場合(例えば翻訳後の文の長さは不明)、EOSトークン (EOS token:End-Of-Sequence token)を出力し、それ以降は無視する 17
  • 18. v1.1 14.3 RNNの訓練 • RNNの訓練では、時系列に沿ってアンロールし、単純に通常のバックプロ パゲーションを行う (BPTT: backpropagation through time:通時的逆伝搬) • 最初、アンロールされたネットワークを前進パスで通り抜ける (破線) • 次に、無視されない出力を使ったコスト関数(上記ではC(Y(2), Y(3), Y (4) ))を 使って出力を評価し、その勾配を、後退方向に伝えていく (実線) • 最後に、上記過程で計算した勾配を使ってパラメータを更新する – 各ステップで同じW、bが使われるので、全タイムステップの勾配をW、bに反映 18
  • 19. v1.1 14.3.1 シーケンス分類器の訓練 (1) • RNNでMNISTイメージを分類してみる (なお、イメージ分類はCNNの方が適してる) 19 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 X(0) X(1) X(2) X(3) X(4) X(5) X(6) X(7) X(8) X(9) X(10) X(11) X(12) X(13) X(14) X(15) X(16) X(17) X(18) X(19) X(20) X(21) X(22) X(23) X(24) X(25) X(26) X(27) 150個の再帰ニューロン n_steps = 28 n_inputs = 28 n_neurons = 150 n_outputs = 10 learning_rate = 0.001 X = tf.placeholder(tf.float32, [None, n_steps, n_inputs]) y = tf.placeholder(tf.int32, [None]) basic_cell = tf.nn.rnn_cell.BasicRNNCell(num_units=n_neurons) outputs, states = tf.nn.dynamic_rnn(basic_cell, X, dtype=tf.float32) logits = tf.layers.dense(states, n_outputs) xentropy = tf.nn.sparse_softmax_cross_entropy_with_logits(labels=y, logits=logits) loss = tf.reduce_mean(xentropy) optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate) training_op = optimizer.minimize(loss) correct = tf.nn.in_top_k(logits, y, 1) accuracy = tf.reduce_mean(tf.cast(correct, tf.float32)) init = tf.global_variables_initializer() (X_train, y_train), (X_test, y_test) = tf.keras.datasets.mnist.load_data() X_train = X_train.astype(np.float32).reshape(-1, 28*28) / 255.0 X_test = X_test.astype(np.float32).reshape(-1, 28*28) / 255.0 y_train = y_train.astype(np.int32) y_test = y_test.astype(np.int32) X_valid, X_train = X_train[:5000], X_train[5000:] y_valid, y_train = y_train[:5000], y_train[5000:] def shuffle_batch(X, y, batch_size): rnd_idx = np.random.permutation(len(X)) n_batches = len(X) // batch_size for batch_idx in np.array_split(rnd_idx, n_batches): X_batch, y_batch = X[batch_idx], y[batch_idx] yield X_batch, y_batch X_test = X_test.reshape((-1, n_steps, n_inputs)) ※青字部分以外は、CNNとほぼ同じ ※CNNとほぼ同じ
  • 20. v1.1 14.3.1 シーケンス分類器の訓練 (2) • RNNでMNISTイメージを訓練する • 訓練中の出力 • Testデータで98%を超える正確度が得られた。➡悪くない数字 • HeでRNNの重みを初期化、訓練長期化、正規化等でもっと上がるだろう • RNNの初期化は、ネットワーク構築コードを変数スコープでラップする 20 n_epochs = 100 batch_size = 150 with tf.Session() as sess: init.run() for epoch in range(n_epochs): for X_batch, y_batch in shuffle_batch(X_train, y_train, batch_size): X_batch = X_batch.reshape((-1, n_steps, n_inputs)) sess.run(training_op, feed_dict={X: X_batch, y: y_batch}) acc_batch = accuracy.eval(feed_dict={X: X_batch, y: y_batch}) acc_test = accuracy.eval(feed_dict={X: X_test, y: y_test}) print(epoch, "Last batch accuracy:", acc_batch, "Test accuracy:", acc_test) 0 Last batch accuracy: 0.9533333 Test accuracy: 0.9288 1 Last batch accuracy: 0.96 Test accuracy: 0.9471 2 Last batch accuracy: 0.96 Test accuracy: 0.9499 ... 98 Last batch accuracy: 0.99333334 Test accuracy: 0.977 99 Last batch accuracy: 0.99333334 Test accuracy: 0.9805 with tf.variable_scope(“rnn”, initializer=variance_scaling_initializer()): # ネットワーク構築コードを記載 ※CNNとほぼ同じ
  • 21. v1.1 14.3.2 時系列データを予測するための訓練 (1) • 株価、基本、脳波パタンなどの、時系列データの処理方法を少し見る • 例として、時系列データから無作為に選出した20個の値で訓練する • ターゲットシーケンスは、タイムステップを一つ先にずらしたシーケンス • 各タイムステップで予測値1つのみが 欲しいので、全結合(FC)ラッパーを被せる • なお、この全結合層は、全て同じ重み、 とバイアスを共有する 21 訓練インスタンス インスタンス ターゲット 訓練インスタンス時系列データ
  • 22. v1.1 14.3.2 時系列データを予測するための訓練 (2) • MSE(平均2乗誤差)をコスト関数として、以下のようなコード・結果になる 22 n_steps = 20 n_inputs = 1 n_neurons = 100 n_outputs = 1 X = tf.placeholder(tf.float32, [None, n_steps, n_inputs]) y = tf.placeholder(tf.float32, [None, n_steps, n_outputs]) cell = tf.contrib.rnn.OutputProjectionWrapper( tf.nn.rnn_cell.BasicRNNCell(num_units=n_neurons, activation=tf.nn.relu), output_size=n_outputs) # 全結合(FC: Full Connect)ラッパー outputs, states = tf.nn.dynamic_rnn(cell, X, dtype=tf.float32) learning_rate = 0.001 loss = tf.reduce_mean(tf.square(outputs - y)) # MSE optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate) training_op = optimizer.minimize(loss) init = tf.global_variables_initializer() saver = tf.train.Saver() n_iterations = 1500 batch_size = 50 with tf.Session() as sess: init.run() for iteration in range(n_iterations): X_batch, y_batch = next_batch(batch_size, n_steps) sess.run(training_op, feed_dict={X: X_batch, y: y_batch}) if iteration % 100 == 0: mse = loss.eval(feed_dict={X: X_batch, y: y_batch}) print(iteration, "¥tMSE:", mse) saver.save(sess, "./my_time_series_model") # not shown in the book 0 MSE: 10.261382 100 MSE: 0.3879291 200 MSE: 0.10900871 300 MSE: 0.06135445 400 MSE: 0.059347205 500 MSE: 0.05819268 600 MSE: 0.05220854 700 MSE: 0.047660623 800 MSE: 0.04888802 900 MSE: 0.047597326 1000 MSE: 0.047681753 1100 MSE: 0.046209298 1200 MSE: 0.039961636 1300 MSE: 0.046320174 1400 MSE: 0.041305345 モデルのテスト結果 インスタンス ターゲット 予測 実行結果
  • 23. v1.1 14.3.2 時系列データを予測するための訓練 (3) • 全結合ラッパーよりも、下記の方が効率が良く大幅なスピードアップが可能  RNNの形状[ステップ数、バッチサイズ、ニューロン数]の出力を、  積み上げて、形状[ステップ数×バッチサイズ、ニューロン数]のテンソルにリシェープし、  全結合(FC)層を通した形状[ステップ数×バッチサイズ、1]の出力を  各ステップでの[バッチサイズ]の出力に展開 23
  • 24. v1.1 14.3.3 独創的RNN • 訓練結果を基に、独創的なシーケンスを生成 • n_steps個の初期値(例えば0)を与え、これを種に、時系列データを生成 • 具体的には、1回毎に1個、1ステップ先のデータを生成し、 これを入力シーケンスに加え、最新n_stepsのシーケンスで処理を繰り返す • 結果例は下記(赤枠部分が、それぞれの初期シーケンス) 24 sequence = [0.] * n_steps for iteration in range(300): X_batch = np.array(sequence[-n_steps:]).reshape(1, n_steps, 1) # 最新のn_stepsのシーケンスを取り出す y_pred = sess.run(outputs, feed_dict={X: X_batch}) sequence.append(y_pred[0, -1, 0]) # 予測シーケンスの最後のデータを入力シーケンスに追加 (注) 初期シーケンスが同じでも、訓練結果によって、独創的シーケンスの内容は変わり、上記と別の結果になる
  • 25. v1.1 14.4 深層RNN • 通常NN同様にセルを積み上げたものが深層RNN • Tensorflowでは、MultiRNNCellで構築可能 25 n_neurons = 100 n_layers = 3 layers = [tf.nn.rnn_cell.BasicRNNCell(num_units=n_neurons) for layer in range(n_layers)] # セルのリスト multi_layer_cell = tf.nn.rnn_cell.MultiRNNCell(layers) # 深層RNNのセル outputs, states = tf.nn.dynamic_rnn(multi_layer_cell, X, dtype=tf.float32) # 深層RNN 深層RNN 時系列に沿って深層RNNをアンロールしたもの
  • 26. v1.1 14.4.1 複数のGPUによる深層RNNの分散処理 • 深層RNNを複数GPUで分散処理するには、工夫が必要 – BasicRNNCellがセルそのものではないため、BasicRNNCell作成時にGPUを指定できない – MultiRNNCellがBasicRNNCellを呼び出してセルを作るが、デバイスを指定できない • 下記の様に、独自のラッパセルを作ることで、処理を複数GPUに分散可能 26 class DeviceCellWrapper(tf.nn.rnn_cell.RNNCell): def __init__(self, device, cell): self._cell = cell self._device = device @property def state_size(self): return self._cell.state_size @property def output_size(self): return self._cell.output_size def __call__(self, inputs, state, scope=None): # 実セル作成時に呼び出される with tf.device(self._device): # セルの処理をデバイスに割り当てる return self._cell(inputs, state, scope) devices = ["/gpu:0", "/gpu:1", "/cpu:0"] cells = [DeviceCellWrapper(dev,tf.nn.rnn_cell.BasicRNNCell(num_units=n_neurons)) for dev in devices] multi_layer_cell = tf.nn.rnn_cell.MultiRNNCell(cells) outputs, states = tf.nn.dynamic_rnn(multi_layer_cell, X, dtype=tf.float32)
  • 27. v1.1 14.4.2 ドロップアウトの適用 • RNNの層間でドロップアウトを適用するにはDropoutWrapperを使う • keep_probは、訓練中は好きな値(通常0.5)、テスト時はデフォルト(1.0)にする 27 keep_prob = tf.placeholder_with_default(1.0, shape=()) cells = [tf.nn.rnn_cell.BasicRNNCell(num_units=n_neurons) for layer in range(n_layers)] cells_drop = [tf.nn.rnn_cell.DropoutWrapper(cell, input_keep_prob=keep_prob) for cell in cells] multi_layer_cell = tf.nn.rnn_cell.MultiRNNCell(cells_drop) rnn_outputs, states = tf.nn.dynamic_rnn(multi_layer_cell, X, dtype=tf.float32) # 訓練中 train_keep_prob = 0.5 with tf.Session() as sess: init.run() for iteration in range(n_iterations): X_batch, y_batch = next_batch(batch_size, n_steps) _, mse = sess.run([training_op, loss], feed_dict={X: X_batch, y: y_batch, keep_prob: train_keep_prob}) saver.save(sess, "./my_dropout_time_series_model") # テスト時 with tf.Session() as sess: saver.restore(sess, "./my_dropout_time_series_model") X_new = time_series(np.array(t_instance[:-1].reshape(-1, n_steps, n_inputs))) # keep_prob未指定なので、変数定義時のデフォルト値(1.0)になる y_pred = sess.run(outputs, feed_dict={X: X_new})
  • 28. v1.1 14.4.3 多数のタイムステップによる訓練の難しさ • 長いシーケンスに対するRNNは、ステップ(横)方向に深いネットワークになる • そのため、深層NNの常として、勾配消失/爆発問題の影響を受ける • 対策として、パラメータの良い初期化方法、飽和を起こさない活性化関数、 バッチ正規化、勾配クリップなどは、RNNでも使える • しかし、少し長いシーケンスでも100個程度になり、訓練が遅くなりがち • 良く使われる対策は、時系列に沿ったバックプロパゲーションの途中打ち切 り (訓練中は、入力シーケンスを途中で切ってしまう)だが、色々と副作用あり • また、長いRNNは、最初の入力の記憶が次第に消えるという問題もあり – 最初のステップの入力の痕跡が、後の方のステップでは殆ど無くなる – 文章の最初に結論を言って、後から情報を追加すると、結論が忘れられる可能性あり • 記憶が消える問題への対策として、長期記憶を持つ各種セルが考案された – 長期記憶を持つセルは効果が、その効果が実証されている • 以下では、長期記憶セルとして人気のあるLSTMとGRUを見ていく 28
  • 29. v1.1 14.5 LSTMセル (1) • LSTM(長期短期記憶:long short-term memory)は、Sepp Hochreiter他が 1997年に提案し、多くの人々が研究に少しずつ改良し、現在の形になった • 基本セルの代わりにLSTMを使うだけで、訓練は短時間で収束し、データ内 の長期的な依存関係を検出できる、非常の性能の良いRNNになる • 短期的な記憶「h(t)」に加え、長期的な記憶「c(t)」を入力・出力する • 長期記憶から捨てるべき要素(忘却)、長期的記憶に格納すべき要素(入力)、 短期記憶/出力で読み取るべき要素(出力)を、3つのゲートコントローラ(下記 のf(t)、i(t)、o(t))で制御し、最適なゲートコントローラを学習できることが特徴 29 lstm_cell = tf.nn.rnn_cell.BasicLSTMCell(num_units=n_neurons) 前ステップの長期記憶 前ステップの短期記憶 出力長期記憶 出力短期記憶 忘却ゲート 入力ゲート 出力ゲート 要素毎の乗算 要素毎の加算 出力は0~1の範囲
  • 31. v1.1 14.5.1 ピープホール接続 • 基本LSTMセルでは、各ゲートのコントローラ(全結合層)は、「x(t)」と直前の 短期記憶「h(t-1)」を入力しているが、長期記憶「c(t)」を入力に加えることを、 ピープホール接続と呼ぶ (Felix他が2000年に提案) • TensorFlowでピープホール接続を実装するには、BasicLSTMCellでなく、 LSTMCellを使い、use_peepholes=Trueを指定すればよい • LSTMセルには、ほかにも多数の変種がある その中で特に人気のあるGRUセルを次に紹介する 31 lstm_cell = tf.nn.rnn_cell.LSTMCell(num_units=n_neurons, use_peepholes=True)
  • 32. v1.1 14.6 GRUセル • GRU (Gated Recurrent Unit)は、Kyunghyun Chao等が2014年提案 • LSTMを単純化したものであり、LSTMとほぼ同様に機能する – 長期記憶と短期記憶が結合されて、一つのベクトル「h(t)」になっている – 一つのゲートコントローラ「z(t)」で、忘却ゲートと入力ゲートの両方を制御 • 値が1の場合忘却ゲートが開き、入力ゲートが閉じる。値が0の場合はその逆 – 出力ゲートが無い。代わりに、直前の状態のどの部分をメイン層で用いるかを制御す る新しいゲートコントローラ「r(t)」が追加されている 32 式14-4 GRUの計算式 • LSTM、GRUセルは、RNN成功の理由であり、自然言語処理でも応用されている
  • 33. v1.1 14.7 自然言語処理 • 機械翻訳、自動要約、構文解析、感情分析などの最先端の自然言語処理 (NLP: Natural Language Processing)の多くは、RNNを基礎としている • 以下では、機械翻訳のモデルがどのようになっているかを簡単に紹介 • 詳細は、以下で説明されている – Word2Vec: https://www.tensorflow.org/tutorials/representation/word2vec – Seq2Seq: https://github.com/tensorflow/nmt 33
  • 34. v1.1 14.7.1 単語の埋め込み • 自然言語処理の入口として、まずは、単語の表現方法を選ぶ必要がある • 単語間の関係は当初不明なので、ワンホットベクトル表現が考えられる – ワンホットベクトルの例: (0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,...,0) – 語彙集が5万語含むとき、1要素が1、他は0の疎な表現になり、メモリ効率が非常に悪い • もっとも一般的な買いは、埋め込みと呼ばれる手法であり、単語を小さく密な ベクトル空間(150次元程度)に埋め込み、NNで単語のベクトル値を学習する • 訓練開始時は、埋め込みベクトルの値は無作為だが、訓練中にバックプロパ ゲーションによって、埋め込みベクトルの値を自動的に書き換えていく – 文章の途中までの単語列を入力シーケンスにし、次の単語を予測するようNNを訓練 • 訓練で、類似する単語は互いに近づき、かなり意味のある形で組織される 34 woman – man ≒ queen - king walked – walking ≒ swam - swimming 首都 – 国は、ほぼ同じベクトル
  • 35. v1.1 14.7.2 機械翻訳のためのエンコーダ-デコーダネットワーク (1) • 英語の文をフランス語に翻訳する簡単な機械翻訳モデルを見てみる ※ seq2seqサイト(https://github.com/tensorflow/nmt) の内容を記載(入力文の語順は元のまま) • エンコーダが入力文を意味ベクトルに変換、デコーダが意味ベクトルから翻訳文を生成 • エンコーダとデコーダは深層RNN(下図は2層)で、前段に埋め込み層を持つ 35 入力は単語の語彙辞書内番号の列 例えば、[12, 47, 7, 834] 各単語が150次元ベクトル空間に埋め込まれる 深層RNN (2層) 各ステップの出力を出力辞書長のロジットに変換 (完全結合) ロスとして、ターゲット文と翻訳文の差異を計算 (sparse_softmax_cross_entropy_with_logits)
  • 36. v1.1 14.7.2 機械翻訳のためのエンコーダ-デコーダネットワーク (2) • 下記、英独翻訳の訓練済みデータを含む、各種DLモデルが公開されている – https://github.com/tensorflow/tensor2tensor – Google Colaboratoryで、英独翻訳の訓練済みデータを試せる • https://colab.research.google.com/github/tensorflow/tensor2tensor/blob/master/tensor2tensor/notebooks/hello_t2t.ipynb 36 入力と出力の例
  • 37. v1.1 付録 RNNに少し関連した最近の動向(1) • 2018/11/2、Googleが言語表現事前訓練手法「BERT」をオープンソース公開 – 世界最高レベルの訓練手法であり、事前学習済モデルも一緒に公開されている – Google曰く:『世界の誰もが、「単一Cloud TPUなら約30分」「単一GPUなら約数時間」で、独 自の「最新の質問応答システム」や「その他のさまざまなモデル」を訓練できる』 – BERTを使った例が、Google Colaboratoryノートブックで公開されている • https://colab.research.google.com/github/google- research/bert/blob/master/predicting_movie_reviews_with_bert_on_tf_hub.ipynb • 2018/12/4、NIPSで、時系列データの全く新しい分析手法(ODE)が提案された – 時系列データに欠損値があったり、入力データ感覚が不揃いな場合に対応できる – ResNetの層構造の式が、常微分方程式(ODE)の解法(オイラー法)に似ていることに着想 – ODEなら、賢いODE SOlverを活用でき、また、離散化された層構造に縛られない – MNISTでの試行結果:RestNetと同じ精度だが、パラメータ・メモリ使用量が少ない – 紹介記事:https://www.slideshare.net/DeepLearningJP2016/dlneural-ordinary-differential-equations 37 BERT:Bidirectional Encoder Representations from Transformers
  • 38. v1.1 付録 RNNに少し関連した最近の動向 (2) • 2019/2/19、OpenAIが本物と見分けがつかないフェイクニュースを自動生成 – https://gigazine.net/news/20190216-ai-fake-text-generator-dangerous-release/ 38 入力 出力 自動生成