深層学習後編(day4)
1. 再帰型ニューラルネットワークの概念
1.1. 要点まとめ
再帰型ニューラルネットワーク(RNN)はテキストデータや音声データ、株価などの時系列データをうまく扱うことができるニューラルネットワークである。通常のニューラルネットワークで扱えなかった系列間の依存関係をRNNでは扱うことができる。
時刻$t$での入力層の出力を$x^t$、中間層の入出力を$u^t$、$z^t$、出力層の入出力を$v^t$、$y^t$、入力層と中間層の重みを$W_{(in)}$、中間層と出力層の重みを$W_{(out)}$、中間層間の重みを$W$、中間層の活性化関数を$f$、出力層の活性化関数を$g$、$b$と$c$をそれぞれバイアスとすると、RNNは数学的に以下の形で表現される。
u^t = W_{(in)}x^t + W z^{t-1} + b,\ z^t = f(u^t),\\
v^t = W_{(out)}z^t + c,\ y^t = g(v^t)
BPTTはRNNにおけるパラメータ調整法の1つで誤差逆伝播の1種である。数学的には以下のように表現される。
\frac{\partial E}{\partial W_{(in)}}=\frac{\partial E}{\partial u^t}\left[\frac{\partial u^t}{\partial W_{(in)}}\right]^T=\delta^t[x^t]^T,\\
\frac{\partial E}{\partial W_{(out)}}=\frac{\partial E}{\partial v^t}\left[\frac{\partial v^t}{\partial W_{(out)}}\right]^T=\delta^{out,t}[z^t]^T,\\
\frac{\partial E}{\partial W}=\frac{\partial E}{\partial u^t}\left[\frac{\partial u^t}{\partial W}\right]^T=\delta^t[z^{t-1}]^T,\\
\frac{\partial E}{\partial b}=\frac{\partial E}{\partial u^t}\frac{\partial u^t}{\partial b} = \delta^t,\\
\frac{\partial E}{\partial c}=\frac{\partial E}{\partial v^t}\frac{\partial v^t}{\partial c} = \delta^{out,t}
上記を用いると、パラメータ更新式は以下のように表現される。
W^{t+1}_{(in)}=W^{t}_{(in)}-\epsilon\frac{\partial E}{\partial W_{(in)}}=W^t_{(in)}-\epsilon\sum_{z=0}^{T_t}\delta^{t-z}[x^{t-z}]^T,\\
W^{t+1}_{(out)}=W^{t}_{(out)}-\epsilon\frac{\partial E}{\partial W_{(in)}}=W^t_{(out)}-\epsilon\delta^{out,t}[z^{t}]^T,\\
W^{t+1}=W^{t}-\epsilon\frac{\partial E}{\partial W}=W^t_{(in)}-\epsilon\sum_{z=0}^{T_t}\delta^{t-z}[z^{t-z-1}]^T,\\
b^{t+1}=b^t−\epsilon \frac{\partial E}{\partial b}=b^t−\epsilon \sum_{z=0}^{T_t} \delta^{t−z},\\
c^{t+1}=c^t−\epsilon\frac{\partial E}{\partial c} =c^t−\epsilon \delta^{out,t}
1-2. 確認テスト考察
●P. 11
問
サイズ5×5の入力画像を、サイズ3×3のフィルタで畳み込んだ時の出力画像のサイズを答えよ。なおストライドは2、パディングは1とする。
回答
(5 + 1 * 2 -3)/2 + 1 = 3
よって3x3
●P. 23
問
RNNのネットワークには大きくわけて3つの重みがある。1つは入力から現在の中間層を定義する際にかけられる重み、1つは中間層から出力を定義する際にかけられる重みである。残り1つの重みについて説明せよ。
回答
中間層から、次の時刻の中間層への入力を定義する際の重み。RNNでは中間層からの出力の一部は、再帰的に次の時刻の中間層への入力となるようにノードが連けるされている。
●P. 36
問
連鎖律の原理を使い、dz/dxを求めよ。
z = t2
t = x + y
回答
dz/dxは連鎖率より、(dz/dt)(dt/dx)と書けるので、
dz/dt = 2t
dt/dx = 1
したがって
dz/dx = 2t
●P. 45
問
下図のy1をx・z0・z1・win・w・woutを用いて数式で表せ。
※バイアスは任意の文字で定義せよ。
※また中間層の出力にシグモイド関数g(x)を作用させよ。
回答
y1 = g(wout(wz0 + winx1 + b) + c)
2. LSTM
2.1. 要点まとめ
RNNは時間軸を考えるため、長期の時間依存性を考慮したモデルを作るとなると、ネットワークの規模が大きくなるため、勾配消失(爆発)問題が起きる問題を抱えており、構造を変えてその問題を解決したのがLSTMとなる。
-
CEC
CECは記憶機能を持ち、勾配が1であれば勾配消失(爆発)問題が起きないため、勾配を1にするために導入された。しかしCECでは記憶のみに特化しているため、時間依存度に関係なく重みが一律でニューラルネットワークの学習特性が無いという課題がある。そのため、CECのまわりに学習機能を持たせる。(何を覚えるか、記憶しているもののうち何を使うか) -
入力ゲートと出力ゲート
入力ゲートは入力をどのくらいの重みで記憶を保存するか制御し、出力ゲートは出力する際にどう記憶を使えばよいか制御を行う。入力ゲート・出力ゲートを追加することで、学習機能を持たせることができ、CECの課題を解決している。 -
忘却ゲート
CECは過去の情報が全て保管されているため、過去の情報が要らなくなった場合、削除することはできず、保管され続けるという課題がある。過去の情報が要らなくなった場合、そのタイミングで情報を忘却する機能が必要で、その役割を担うのが忘却ゲートとなる。 -
覗き穴ゲート
入力ゲート・出力ゲートは判断材料として今回の入力と前回の出力を見ていたが、ついでにCECの今の記憶も見てみようというのが覗き穴ゲートの考え方となるが、あまり効果がなかったことが知られている。
2.2. 確認テスト
以下の文章をLSTMに入力し空欄に当てはまる単語を予測したいとする。文中の「とても」という言葉は空欄の予測においてなくなっても影響を及ぼさないと考えられる。このような場合、どのゲートが作用すると考えられるか。
「映画おもしろかったね。ところで、とてもお腹が空いたから何か____。」
⇒忘却ゲート
3. GRU
3.1. 要点まとめ
LSTMでは、パラメータ数が多く、計算負荷が高くなる問題があったため、それを改善したGRUが考え出された。GRUではそのパラメータを大幅に削減し、精度は同等またはそれ以上が望める様になった構造となる。GRUのメリットとしては計算負荷が低いことにある。
2-B. 実装演習結果
通常のRNNを用いると、some of them looks likeに続き単語はetと予想された。
3. GRU
3.1. 要点まとめ
LSTMでは、パラメータ数が多く、計算負荷が高くなる問題があったため、それを改善したGRUが考え出された。GRUではそのパラメータを大幅に削減し、精度は同等またはそれ以上が望める様になった構造となる。GRUのメリットとしては計算負荷が低いことにある。
LSTMとの比較
・CECが存在しない
・隠れ層の状態で、これまでの入出力を記憶
・入力ゲート、出力ゲート、忘却ゲートがなくなり、リセットゲート、更新ゲートを追加
・各ゲートへの入力は時刻tの中間層への入力と、時刻t-1の中間層からの出力である点は同じ
・各ゲートからの出力は内積演算ユニットに対して行われる点は同じ
3-2. 実装演習結果
3-3. 確認テスト考察
●P. 88
問
LSTMとCECが抱える課題について、それぞれ簡潔に述べよ。
回答
・LSTMは構成が複雑であるためにパラメータが多く、計算負荷が大きい。
・CECそのものに学習能力がないこと。そのため、入力ゲート、出力ゲート、忘却ゲートをCECの周囲に配置する必要があり、上記の原因となる。
●P. 92
問
LSTMとGRUの違いを簡潔に述べよ。
回答
LSTMは主にCEC、入力ゲート、出力ゲート、忘却ゲートの4つの部分からなり、パラメータ数が多いため計算負荷が大きくなる。GRUにはCECがなく、リセットゲート、更新ゲートの比較的単純な構造であるため、パラメータ数が少なく計算負荷も小さい。
3.4. 実装演習
これまではnumpyを使って実装してきたが、以下は深層学習のフレームワークであるtensorflowを用いて実装した例となる。
import tensorflow as tf
import numpy as np
import re
import glob
import collections
import random
import pickle
import time
import datetime
import os
# logging levelを変更
tf.logging.set_verbosity(tf.logging.ERROR)
class Corpus:
def __init__(self):
self.unknown_word_symbol = "<???>" # 出現回数の少ない単語は未知語として定義しておく
self.unknown_word_threshold = 3 # 未知語と定義する単語の出現回数の閾値
self.corpus_file = "./corpus/**/*.txt"
self.corpus_encoding = "utf-8"
self.dictionary_filename = "./data_for_predict/word_dict.dic"
self.chunk_size = 5
self.load_dict()
words = []
for filename in glob.glob(self.corpus_file, recursive=True):
with open(filename, "r", encoding=self.corpus_encoding) as f:
# word breaking
text = f.read()
# 全ての文字を小文字に統一し、改行をスペースに変換
text = text.lower().replace("\n", " ")
# 特定の文字以外の文字を空文字に置換する
text = re.sub(r"[^a-z '\-]", "", text)
# 複数のスペースはスペース一文字に変換
text = re.sub(r"[ ]+", " ", text)
# 前処理: '-' で始まる単語は無視する
words = [ word for word in text.split() if not word.startswith("-")]
self.data_n = len(words) - self.chunk_size
self.data = self.seq_to_matrix(words)
def prepare_data(self):
"""
訓練データとテストデータを準備する。
data_n = ( text データの総単語数 ) - chunk_size
input: (data_n, chunk_size, vocabulary_size)
output: (data_n, vocabulary_size)
"""
# 入力と出力の次元テンソルを準備
all_input = np.zeros([self.chunk_size, self.vocabulary_size, self.data_n])
all_output = np.zeros([self.vocabulary_size, self.data_n])
# 準備したテンソルに、コーパスの one-hot 表現(self.data) のデータを埋めていく
# i 番目から ( i + chunk_size - 1 ) 番目までの単語が1組の入力となる
# このときの出力は ( i + chunk_size ) 番目の単語
for i in range(self.data_n):
all_output[:, i] = self.data[:, i + self.chunk_size] # (i + chunk_size) 番目の単語の one-hot ベクトル
for j in range(self.chunk_size):
all_input[j, :, i] = self.data[:, i + self.chunk_size - j - 1]
# 後に使うデータ形式に合わせるために転置を取る
all_input = all_input.transpose([2, 0, 1])
all_output = all_output.transpose()
# 訓練データ:テストデータを 4 : 1 に分割する
training_num = ( self.data_n * 4 ) // 5
return all_input[:training_num], all_output[:training_num], all_input[training_num:], all_output[training_num:]
def build_dict(self):
# コーパス全体を見て、単語の出現回数をカウントする
counter = collections.Counter()
for filename in glob.glob(self.corpus_file, recursive=True):
with open(filename, "r", encoding=self.corpus_encoding) as f:
# word breaking
text = f.read()
# 全ての文字を小文字に統一し、改行をスペースに変換
text = text.lower().replace("\n", " ")
# 特定の文字以外の文字を空文字に置換する
text = re.sub(r"[^a-z '\-]", "", text)
# 複数のスペースはスペース一文字に変換
text = re.sub(r"[ ]+", " ", text)
# 前処理: '-' で始まる単語は無視する
words = [word for word in text.split() if not word.startswith("-")]
counter.update(words)
# 出現頻度の低い単語を一つの記号にまとめる
word_id = 0
dictionary = {}
for word, count in counter.items():
if count <= self.unknown_word_threshold:
continue
dictionary[word] = word_id
word_id += 1
dictionary[self.unknown_word_symbol] = word_id
print("総単語数:", len(dictionary))
# 辞書を pickle を使って保存しておく
with open(self.dictionary_filename, "wb") as f:
pickle.dump(dictionary, f)
print("Dictionary is saved to", self.dictionary_filename)
self.dictionary = dictionary
print(self.dictionary)
def load_dict(self):
with open(self.dictionary_filename, "rb") as f:
self.dictionary = pickle.load(f)
self.vocabulary_size = len(self.dictionary)
self.input_layer_size = len(self.dictionary)
self.output_layer_size = len(self.dictionary)
print("総単語数: ", self.input_layer_size)
def get_word_id(self, word):
# print(word)
# print(self.dictionary)
# print(self.unknown_word_symbol)
# print(self.dictionary[self.unknown_word_symbol])
# print(self.dictionary.get(word, self.dictionary[self.unknown_word_symbol]))
return self.dictionary.get(word, self.dictionary[self.unknown_word_symbol])
# 入力された単語を one-hot ベクトルにする
def to_one_hot(self, word):
index = self.get_word_id(word)
data = np.zeros(self.vocabulary_size)
data[index] = 1
return data
def seq_to_matrix(self, seq):
print(seq)
data = np.array([self.to_one_hot(word) for word in seq]) # (data_n, vocabulary_size)
return data.transpose() # (vocabulary_size, data_n)
class Language:
"""
input layer: self.vocabulary_size
hidden layer: rnn_size = 30
output layer: self.vocabulary_size
"""
def __init__(self):
self.corpus = Corpus()
self.dictionary = self.corpus.dictionary
self.vocabulary_size = len(self.dictionary) # 単語数
self.input_layer_size = self.vocabulary_size # 入力層の数
self.hidden_layer_size = 30 # 隠れ層の RNN ユニットの数
self.output_layer_size = self.vocabulary_size # 出力層の数
self.batch_size = 128 # バッチサイズ
self.chunk_size = 5 # 展開するシーケンスの数。c_0, c_1, ..., c_(chunk_size - 1) を入力し、c_(chunk_size) 番目の単語の確率が出力される。
self.learning_rate = 0.005 # 学習率
self.epochs = 1000 # 学習するエポック数
self.forget_bias = 1.0 # LSTM における忘却ゲートのバイアス
self.model_filename = "./data_for_predict/predict_model.ckpt"
self.unknown_word_symbol = self.corpus.unknown_word_symbol
def inference(self, input_data, initial_state):
"""
:param input_data: (batch_size, chunk_size, vocabulary_size) 次元のテンソル
:param initial_state: (batch_size, hidden_layer_size) 次元の行列
:return:
"""
# 重みとバイアスの初期化
hidden_w = tf.Variable(tf.truncated_normal([self.input_layer_size, self.hidden_layer_size], stddev=0.01))
hidden_b = tf.Variable(tf.ones([self.hidden_layer_size]))
output_w = tf.Variable(tf.truncated_normal([self.hidden_layer_size, self.output_layer_size], stddev=0.01))
output_b = tf.Variable(tf.ones([self.output_layer_size]))
# BasicLSTMCell, BasicRNNCell は (batch_size, hidden_layer_size) が chunk_size 数ぶんつながったリストを入力とする。
# 現時点での入力データは (batch_size, chunk_size, input_layer_size) という3次元のテンソルなので
# tf.transpose や tf.reshape などを駆使してテンソルのサイズを調整する。
input_data = tf.transpose(input_data, [1, 0, 2]) # 転置。(chunk_size, batch_size, vocabulary_size)
input_data = tf.reshape(input_data, [-1, self.input_layer_size]) # 変形。(chunk_size * batch_size, input_layer_size)
input_data = tf.matmul(input_data, hidden_w) + hidden_b # 重みWとバイアスBを適用。 (chunk_size, batch_size, hidden_layer_size)
input_data = tf.split(input_data, self.chunk_size, 0) # リストに分割。chunk_size * (batch_size, hidden_layer_size)
# RNN のセルを定義する。RNN Cell の他に LSTM のセルや GRU のセルなどが利用できる。
cell = tf.nn.rnn_cell.BasicRNNCell(self.hidden_layer_size)
outputs, states = tf.nn.static_rnn(cell, input_data, initial_state=initial_state)
# 最後に隠れ層から出力層につながる重みとバイアスを処理する
# 最終的に softmax 関数で処理し、確率として解釈される。
# softmax 関数はこの関数の外で定義する。
output = tf.matmul(outputs[-1], output_w) + output_b
return output
def loss(self, logits, labels):
cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=logits, labels=labels))
return cost
def training(self, cost):
# 今回は最適化手法として Adam を選択する。
# ここの AdamOptimizer の部分を変えることで、Adagrad, Adadelta などの他の最適化手法を選択することができる
optimizer = tf.train.AdamOptimizer(learning_rate=self.learning_rate).minimize(cost)
return optimizer
def train(self):
# 変数などの用意
input_data = tf.placeholder("float", [None, self.chunk_size, self.input_layer_size])
actual_labels = tf.placeholder("float", [None, self.output_layer_size])
initial_state = tf.placeholder("float", [None, self.hidden_layer_size])
prediction = self.inference(input_data, initial_state)
cost = self.loss(prediction, actual_labels)
optimizer = self.training(cost)
correct = tf.equal(tf.argmax(prediction, 1), tf.argmax(actual_labels, 1))
accuracy = tf.reduce_mean(tf.cast(correct, tf.float32))
# TensorBoard で可視化するため、クロスエントロピーをサマリーに追加
tf.summary.scalar("Cross entropy: ", cost)
summary = tf.summary.merge_all()
# 訓練・テストデータの用意
# corpus = Corpus()
trX, trY, teX, teY = self.corpus.prepare_data()
training_num = trX.shape[0]
# ログを保存するためのディレクトリ
timestamp = time.time()
dirname = datetime.datetime.fromtimestamp(timestamp).strftime("%Y%m%d%H%M%S")
# ここから実際に学習を走らせる
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
summary_writer = tf.summary.FileWriter("./log/" + dirname, sess.graph)
# エポックを回す
for epoch in range(self.epochs):
step = 0
epoch_loss = 0
epoch_acc = 0
# 訓練データをバッチサイズごとに分けて学習させる (= optimizer を走らせる)
# エポックごとの損失関数の合計値や(訓練データに対する)精度も計算しておく
while (step + 1) * self.batch_size < training_num:
start_idx = step * self.batch_size
end_idx = (step + 1) * self.batch_size
batch_xs = trX[start_idx:end_idx, :, :]
batch_ys = trY[start_idx:end_idx, :]
_, c, a = sess.run([optimizer, cost, accuracy],
feed_dict={input_data: batch_xs,
actual_labels: batch_ys,
initial_state: np.zeros([self.batch_size, self.hidden_layer_size])
}
)
epoch_loss += c
epoch_acc += a
step += 1
# コンソールに損失関数の値や精度を出力しておく
print("Epoch", epoch, "completed ouf of", self.epochs, "-- loss:", epoch_loss, " -- accuracy:",
epoch_acc / step)
# Epochが終わるごとにTensorBoard用に値を保存
summary_str = sess.run(summary, feed_dict={input_data: trX,
actual_labels: trY,
initial_state: np.zeros(
[trX.shape[0],
self.hidden_layer_size]
)
}
)
summary_writer.add_summary(summary_str, epoch)
summary_writer.flush()
# 学習したモデルも保存しておく
saver = tf.train.Saver()
saver.save(sess, self.model_filename)
# 最後にテストデータでの精度を計算して表示する
a = sess.run(accuracy, feed_dict={input_data: teX, actual_labels: teY,
initial_state: np.zeros([teX.shape[0], self.hidden_layer_size])})
print("Accuracy on test:", a)
def predict(self, seq):
"""
文章を入力したときに次に来る単語を予測する
:param seq: 予測したい単語の直前の文字列。chunk_size 以上の単語数が必要。
:return:
"""
# 最初に復元したい変数をすべて定義してしまいます
tf.reset_default_graph()
input_data = tf.placeholder("float", [None, self.chunk_size, self.input_layer_size])
initial_state = tf.placeholder("float", [None, self.hidden_layer_size])
prediction = tf.nn.softmax(self.inference(input_data, initial_state))
predicted_labels = tf.argmax(prediction, 1)
# 入力データの作成
# seq を one-hot 表現に変換する。
words = [word for word in seq.split() if not word.startswith("-")]
x = np.zeros([1, self.chunk_size, self.input_layer_size])
for i in range(self.chunk_size):
word = seq[len(words) - self.chunk_size + i]
index = self.dictionary.get(word, self.dictionary[self.unknown_word_symbol])
x[0][i][index] = 1
feed_dict = {
input_data: x, # (1, chunk_size, vocabulary_size)
initial_state: np.zeros([1, self.hidden_layer_size])
}
# tf.Session()を用意
with tf.Session() as sess:
# 保存したモデルをロードする。ロード前にすべての変数を用意しておく必要がある。
saver = tf.train.Saver()
saver.restore(sess, self.model_filename)
# ロードしたモデルを使って予測結果を計算
u, v = sess.run([prediction, predicted_labels], feed_dict=feed_dict)
keys = list(self.dictionary.keys())
# コンソールに文字ごとの確率を表示
for i in range(self.vocabulary_size):
c = self.unknown_word_symbol if i == (self.vocabulary_size - 1) else keys[i]
print(c, ":", u[0][i])
print("Prediction:", seq + " " + ("<???>" if v[0] == (self.vocabulary_size - 1) else keys[v[0]]))
return u[0]
def build_dict():
cp = Corpus()
cp.build_dict()
if __name__ == "__main__":
#build_dict()
ln = Language()
# 学習するときに呼び出す
#ln.train()
# 保存したモデルを使って単語の予測をする
ln.predict("some of them looks like")
??> : 2.919565e-05ストリーミング出力は最後の 5000 行に切り捨てられました。
beating : 1.3192031e-14
authentic : 1.4901815e-14
glow : 1.5293715e-14
oy : 1.4680216e-14
emotion : 1.4983698e-14
delight : 1.40041135e-14
nuclear : 1.4859978e-14
dropped : 1.4963249e-14
hiroshima : 1.3928354e-14
beings : 1.58225e-14
tens : 1.5364736e-14
burned : 1.3809686e-14
homeless : 1.564374e-14
albert : 1.4478778e-14
・
・
・
bmi : 3.1721086e-07
covariates : 2.834505e-06
yhl : 2.2330451e-07
yol : 9.9930106e-11
obesity : 1.3501609e-09
evgfp : 6.1830234e-09
unintended : 4.67851e-09
sizes : 2.5699424e-07
obese : 1.9368164e-07
Prediction: some of them looks like et
4-1. 確認テスト考察
●P. 109
問
下記の選択肢から、seq2seqについて説明しているものを選べ。
(1)時刻に関して順方向と逆方向のRNNを構成し、それら2つの中間層表現を特徴量として利用するものである。
(2)RNNを用いたEncoder-Decoderモデルの一種であり、機械翻訳などのモデルに使われる。
(3)構文木などの木構造に対して、隣接単語から表現ベクトル(フレーズ)を作るという演算を再帰的に行い(重みは共通)、文全体の表現ベクトルを得るニューラルネットワークである。
(4)RNNの一種であり、単純なRNNにおいて問題となる勾配消失問題をCECとゲートの概念を導入することで解決したものである。
回答
(2)
(1)は双方向RNNの説明。(3)は構文木の説明。(4)はLSTMの説明。
●P. 119
問
seq2seqとHRED、HREDとVHREDの違いを簡潔に述べよ。
回答
seq2seqとHRED
seq2seqは一問一答形式での文章生成であるため、以前の文章の文脈を加味した文章を作成できない。一方、HREDはある時刻の文脈ベクトルを次の時刻の文脈ベクトルに伝達するため、文脈を考慮した文章を生成できる。
HREDとVHRED
HREDは短く情報量に乏しい回答を生成する傾向があるが、VHREDはHREDにVAEの潜在変数の概念を追加することで、バリエーションに富んだ回答を生成できる。
●P. 128
問
VAEに関する下記の説明文中の空欄に当てはまる言葉を答えよ。自己符号化器の潜在変数に____を導入したもの。
回答
確率分布∼N(0,1)
潜在変数zにN(0,1)の確率分布を仮定することで、元の入力データの特徴をできる限り保ったまま情報を圧縮できる。
4-2. 関連記事レポート
5. Word2vec
5-1. 講義内容
RNNの課題
RNNは、可変長の系列を処理できるが、文書の単語に相当する系列の要素が可変長である場合には、処理ができない。そこで、単語を単なる文字列として扱うのではなく、固定長で表現する必要がある。
One-hotベクトル
単語をベクトルで表現する手法の一つで、1つの単語を1要素のみが1で、他の要素はすべて0となるベクトルに対応させる方法。訓練データの単語を重複なく取り出し、単語のリストを作成して通し番をふり、単語ごとに通し番の要素のみを1としたベクトルを得る。単語リストの数だけベクトルの次元が大きくなる。また、単語同士の意味の類似度はベクトルに表現されていない。
Word2vec
単語のEmbedding表現を得る手法の一つで、One-hotベクトルよりも小さな次元で単語を表現し、ベクトルの類似度は単語の類似度を示している。
5-2. 実装演習結果
5-3. 確認テスト考察
確認テストなし
5-4. 関連記事レポート
6. Attention Mechanism
6-1. 講義内容
Attention Mechanism
Seq2seqは、入力データが何単語からなる文章であっても、固定の文脈ベクトルに情報を格納していくため、長い文章への対応が難しい。この問題を解決するには、文章が長くなるほどそのシーケンスの内部表現の次元も大きくなっていく、仕組みが必要になる。そこで、「入力と出力のどの単語が関連しているのか」の関連度を学習する仕組みが開発され、これをAttention Mechanismという。例えば、機械翻訳の課題では英単語「I」は、日本語の「私」と関連度が高くなるよう学習が行われる。
7-2. 実装演習結果
7-3. 確認テスト考察
●P. 137
問
RNNとword2vec、seq2seqとAttentionの違いを簡潔に述べよ。
回答
RNNとword2vec
RNNは再帰的な構造を持った時系列データの処理に適したNNのこと。一方、word2vecはNNではなく、分散表現ベクトルを得るための手法である。
seq2seqとAttention
seq2seqは時系列データを入力にとり、時系列データを出力するNNのこと。AttentionはNNではなく、時系列データ間・時系列データ自身の構成要素の間で関連性に重みづけをする手法。
7-4. 関連記事レポート
深層学習 Day4
1. 強化学習
1-1. 講義内容
強化学習
長期的に報酬を最大化できるように、環境のなかで行動を選択できるエージェントを作ることを目標とする機械学習の一分野。行動の結果として与えられる利益(報酬)をもとに、行動を決定する原理を改善していく。
強化学習の応用例
マーケティングの場合
■ 環境:会社の販売促進部
■ エージェント:プロフィールと購入履歴に基づいて、キャンペーンメールを送る顧客を決めるソフトウェア
■ 行動:顧客ごとに送信、非送信の2つの行動から1つを選択する
■ 報酬:キャンペーンのコストという負の報酬、またはキャンペーンで生み出されると推測される売上という正の報酬
探索と利用のトレードオフ
強化学習では、探索と利用はトレードオフの関係にある。探索とは、特に学習初期などの知識が不足している状態において、ランダムのような行動のことを意味し、ここでいう利用とは、学習が進んでいきある程度知識を得た際に、その知識を利用してベストな行動をとること。探索を続けていると、新たな経験から新たな知識を入手できるが、過去の知見を活かせず、逆に利用を続けていると新たな知識が身につかず、ベストな解にたどり着かない。この関係を探索と利用のトレードオフという。
強化学習と教師あり/なし学習の違い
強化学習と、これまで学んできた教師あり/なし学習は、目標が異なっている
■ 強化学習:優れた方策を見つけることが目的
■ 教師あり/なし学習:データに含まれるパターンを見つけ出すこと、及びそのデータから予想することが目的
強化学習の歴史
強化学習は、その考え方は古くから存在していたが、当時のコンピューターには計算量が多く処理しきれなかった。近年、強化学習の計算を処理できるまでにコンピューティング技術が発展したため、強化学習は再び脚光を浴び始めた。また、関数近似法とQ学習を組み合わせた手法が登場したことも強化学習の発展の一因である。
・Q学習:行動価値関数を行動する毎に更新することにより学習を進める方法
・関数近似法:価値関数や方策関数を関数近似する手法のこと
関数近似法以前は、状態に対して行動を紐づける巨大な表を基に強化学習が行われていた
NN技術が発達した現在では、この関数をNNに置き換えるなどの手法がとられている
価値関数
価値を表す関数で、状態価値関数と行動価値関数の2種類がある
■ 状態価値関数:ある状態の価値に注目する関数
■ 行動価値関数:状態と行動を組み合わせた価値に注目する関数
最近の流行はこちら
方策関数
方策ベースの強化学習手法において、ある状態でどのような行動をとるのかの確率を与える関数のこと
方策勾配法
方策関数をNNを用いて実装した場合の、最適化法の一つ。NNでは誤差関数が小さくなるように学習を進めていたが、強化学習では対照的に期待収益がより大きくなるように学習を行う。
9. AlphaGo
AlphaGoにAlphaGo LeeとAlphaGo Zeroがある。
AlphaGo LeeはPolicyNetとValueNetの2つのネットワークが登場し、ともに畳み込みニューラルネットワークになっている。PolicyNetが強化学習での方策関数で、ValueNetが価値関数である。PolicyNetでは、48チャンネルある19×19の盤面の特徴を入力し、19×19マスの着手予想確率が出力される。ValueNetでは、49チャンネルある19×19の盤面の特徴を入力し、現局面の勝率を-1~1で表したものが出力される。
AlphaGo Leeの学習は以下のステップで行われる。
- 教師あり学習によるRollOutPolicyとPolicyNetの学習
- 強化学習によるPolicyNetの学習
- 強化学習によるValueNetの学習
PolicyNetでは現在の盤面の状態から石をどこに置くか計算するのに3ミリ秒かかるため、何億回と繰り返す学習においては時間がかかる問題が発生する。そこで精度は落ちるがスピード重視で作られたものがRollOutPolicyで、こちらはPolicyNetより1000倍速い3マイクロ秒しかかからない。このRollOutPolicyを学習の最初のステップである教師あり学習で用いられる。価値関数を学習させる手法としてモンテカルロ木探索があるが、これを行う際に、RollOutPolicyを用いることで高速に学習を進めることができる。
AlphaGo ZeroはPolicyValueNetという1つのネットワークに統合され、Leeとは以下の違いがある。
- 教師あり学習を一切行わず、強化学習のみで作成
- 特徴入力からヒューリスティックな要素を排除し、石の配置のみにした
- PolicyNetとValueNetを1つのネットワークに統合した
- Residual Netを導入した
- モンテカルロ木探索からRollOutシミュレーションをなくした
Residual Netはネットワークにショートカット構造を追加して、勾配の爆発、消失を抑える効果を狙ったものである。
2. 軽量化・高速化技術
2-1. 講義内容
データ並列
親モデルを各ワーカーに子モデルとしてコピーし、データを分割して各ワーカーごとに計算させる手法。各モデルのパラメータの合わせ方で、同期型か非同期型かに分類できる。
■ 同期型
各ワーカーの計算が終わるのを待ち、全ワーカーの勾配が算出されたところで勾配の平均を計算し、親モデルのパラメータを更新する。互いのワーカーの計算が終わるのを待つため、処理に時間がかかってしまうが、最終的な精度は同期型より良いことが多いため、現在はこちらが主流となっている。
■ 非同期型
学習が終わった子モデルはパラメータサーバにPushされる。新たに学習を始める時は、パラメータサーバからPopしたモデルに対して学習していく。互いのワーカーの計算が終わるのを待たないため、処理が早く終わる。最新のモデルのパラメータを利用できないので、学習が不安定になりやすい。
モデル並列
データではなく、親モデルそのものを各ワーカーに分割し、それぞれのモデルを学習させる方法。全てのデータで学習が終わった後で、1つのモデルに復元させる。モデルが大きい時はモデル並列化を、データが大きい時はデータ並列化を利用すると効率が良い。
GPU
CPUと比較して、一般的に低性能なコアを多数含んでいる。簡単な並列処理を得意としており、単純な行列演算が多いNNの学習の高速化に向いている。元々の使用目的であるグラフィック以外の用途で使用されるGPUをGPGPU(General-purpose on GPU)という。GPGPUの開発環境には、NVIDIA社のGPUのみで利用できるCUDAや、NVIDIA社以外の会社(Intel, AMD, ARMなど)のGPUで利用可能なOpenCLがある。
量子化
通常のパラメータの64bit浮動小数点(倍精度演算)を、32bit浮動小数点(単精度演算)など下位の精度に落とすことでメモリと演算処理の削減を行う手法。
蒸留
精度の高いモデルは一般にニューロンの規模が大きいため、推論に多くのメモリと演算処理が必要となる。そこで規模が大きなモデルから、小さなモデルへと知識の伝承を行うことで、軽量でありながら高精度のモデルを得ようとする手法。知識を伝承するモデルを教師モデル、伝承を受けるモデルを生徒モデルといい、モデル間の誤差を用いて生徒モデルの重みのみを更新していく。
プルーニング
ネットワークが大きくなるとパラメータが増えてゆくが、すべてのニューロンの計算が制度に寄与しているわけではない。そこで、モデルの制度への寄与が少ないニューロンを削減することでモデルの軽量化・高速化を行う手法。
2-2. 関連記事レポート
ネットワークの軽量化技術の一つ蒸留について調べました。
蒸留にはいくつかのバリエーションがあるようです。
モデルの出力を利用
教師モデルと生徒モデルに同一の学習データを与え、教師モデルの出力値、または出力層に近い層の値を、教師データとして生徒モデルを学習させる手法。教師モデルの出力と、生徒モデルの出力間の誤差関数にもバリエーションがあり下記などが使用される。
・ユークリッド損失関数
・温度付きソフトマックス関数
・KLダイバージェンス
モデルの中間層の値を利用
生徒モデルに、教師モデルの中間層の値を学習させる手法。中間層のノード数が異なる場合は、一層分重みを作用させてから誤差を計算する。出力値を利用する方法と組み合わせて行うことができる。
特権情報を利用
教師モデルの訓練時にのみ、入力データに追加の情報を付加し、生徒モデルに伝承する手法。生徒モデルは、追加の情報を利用することはできない。
例:画像の分類問題で、教師モデルには画像とそのキャプションを与え、生徒モデルには画像のみを与える。
参考:
http://codecrafthouse.jp/p/2018/01/knowledge-distillation/
2-3. 応用モデル
11.1. 要点まとめ
11.1.1. MobileNet
MobileNetは画像認識モデルの一種。一般的な畳み込みレイヤーは計算量が多いが、MobileNetはDepthwise ConvolutionとPointwise Convolutionの組み合わせで軽量化を実現している。Depthwise Convolutionではフィルタの数は1で固定で入力マップのチャネルごとに畳み込みを実施し、Pointwise Convolutionではカーネルを1×1に固定して入力マップのポイントごとに畳み込みを実施する。これらを組み合わせることでMobileNetでは計算量を削減する。
2-4. DenseNet
DenseNetは画像認識モデルの一種。DenseNetで特徴的なのはDenseBlockと呼ばれる部分となる。DenseBlockではその中のレイヤーを通過するごとにチャネルが増えていくような演算が行われている。DenseBlockではDenseBlockに入ってきた画像データにDenseBlock内の前のレイヤーでの計算結果が付け加えられたものが処理対象となる。それに対してBatch正規化やRelu関数による変換、3x3畳み込み層による処理などを行ってDenseBlock内の次のレイヤーに進んでいく。進むたびに計算結果が付け加えられていき、チャネルがどんどん増えていくのが特徴である。この増える量のことを成長率(ハイパーパラメータ)という。チャネル数が増えていくためのDenseBlockを通過すると、チャネルを減らすためのレイヤーであるTransiton Layerが用意されている。これは畳み込みとプーリングから構成される。
2.5. Batch Normalization
Batch Normalizationはレイヤー間を流れるデータの分布をミニバッチ単位で平均が0・分散が1になるように正規化する。学習時間の短縮や初期値への依存低減、過学習の抑制など効果がある。ミニバッチ単位であるためバッチサイズに影響を受けやすいという問題点がある。バッチサイズが小さいと学習が収束しないことがあり、代わりにLayer Normalization(1画像に対して平均が0・分散が1になるようにする)やInstance Nrom(1画像中の1チャネルに対して平均が0・分散が1になるようにする)などの正規化手法が使われることが多い。
2.6. Wavenet
Wavenetは音声生成モデルとなる。時系列データに対して畳み込み(Dilated convolution)を適用している。層が深くなるにつれて畳み込むリンクを離すということを行っている。これによってより長い範囲の情報をうまく使えるようにしている。
2.7. 確認テスト
(い)$H×W×C×D_k×D_k$
(う)$H×W×C×M$
答えは「Dilated causal convolution」
5. Transeformer
5-A. 講義内容
従来法の課題
従来のニューラル機械翻訳は、Encoder・Decoder間で翻訳元の文の内容を固定長のベクトルに保存するため、長い文章に対応できないという問題があった。そこで、翻訳先の各単語を選択する際に、翻訳元の文中の各単語の隠れ状態を利用するAttentionという機構が、Encoder-Decoderモデルに組み合わせて利用され始めた。Attentionの動作は、query(検索クエリ)に一致するkeyを索引し、対応するvalueを取り出す操作であると見做すことができ、これは辞書オブジェクトの機能と同じである。
Transeformer
2017年6月に発表されたAttentionを利用したネットワークで、従来のEncoder-Decoderモデルと異なりRNNを一切利用していない。当時のSOTAをはるかに少ない計算量で実現した。
Attention
AttentionはQuery、Key、Valueの3要素からなるが、これらの由来によって2種類に分類される。
■ Source Target Attention:Queryはターゲット由来。Key、Valueはソース由来。
■ Self-Attention:Query、Key、Valueすべてがソース由来。
Transformer-Encoder
TransformerのEncoderでは、自己注意機構(Self-Attention)により文脈を考慮して各単語をエンコードしている。
■ Position-Wise Feed-Forward Networks
位置情報を保持したまま順伝播を行う仕組みで、線形変換 → ReLu → 線形変換の2層の全結合NNの構造をしており、各Attention層の出力を決定している。
■ Scaled dot product attention
全単語に関するAttentionをまとめて計算する仕組み。
■ Multi-Head attention
8個のScaled Dot Product Attentionの出力をConcatし、重みパラメーターの異なるそれぞれのヘッドが、異なる種類の情報を収集する。
Transformer-Decoder
Encoderと同じく6層構造で、各層でSelf-AttentionとEncoder-Decoder attentionの2種類の注意機構を実装している。
■ Add & Norm
Add & NormはDecoderの各層に3つ、Encoderの各層にも2つずつ存在する。
・Add (Residual Connection)
入出力の差分を学習させる手法であるが、実装上は出力に入力をそのまま加算させる機構である。学習・テストエラーを低減させる効果がある。
・Norm (Layer Normalization)
各層においてバイアスを除く活性化関数への入力を平均0、分散1に正規化する手法で、学習を高速化させる効果がある。
■ Position Encoding
TransformerではRNNを用いないため、単語列の語順情報を記憶できない。そのため別途、単語の位置情報の分散表現に加算してエンコードする仕組みがPosition Encodingである。Position EncodingはEncoder/Decoderの先頭に付加されている。
5-B. 実装演習結果
Transformaerのサンプルコードを実行しました。
学習の様子がこちらです。
英文サンプルを翻訳させてみると、下記のようになりました。「show」を「見せる」の意味で直訳的にとらえているようです。
3. 物体検知・セグメンテーション
3-1. 講義内容
物体認識タスク
物体認識タスクは下記に分類される。
■ 分類:(画像に対し単一または複数の)クラスラベルを出力する。画像中の対象物体の位置は問わない。
■ 物体検知:Bounding Boxを出力するを出力する。対象物体の位置は特定するが、個体の区別は問わない。
■ 意味領域分割:(各ピクセルに対し単一の)クラスラベルを出力する。対象物体の位置は特定するが、個体の区別は問わない。
■ 個体領域分割:(各ピクセルに対し単一の)クラスラベルを個体ごとに出力する。対象物体の位置特定、個体の区別を行う。
代表的データセット
下記は、いずれも物体検出コンペティションで用いられた深層学習の分野では代表的なデータセットである。クラス数・Box/画像の大小を比較し、目的に応じた適切なデータセットの選択が必要となる。
■ VOC12
・VOCはVisual Object Classesの略
・主要貢献者が2012年に亡くなったことに伴いコンペも終了
クラス:20
Train+Val:11,540
Box/画像:2.4
■ ILSVRC17
・ILSVRCはImageNet Scale Visual Recognition Challengeの略
・コンペは2017年に終了しているが、後継はOpen Images Challenge
・ImageNet(21,841クラス/1400万枚以上)のサブセット
クラス:200
Train+Val:476,668
Box/画像:1.1
■ MS COCO18
・COCOはCommon Object in Contextの略
・物体位置推定に対する新たな評価指標mAPCOCOを提案
クラス:80
Train+Val:123,287
Box/画像:7.3
■ OICOD18
・ILSVRCやMS COCOとは異なるannotation process
・Open Images V4(6000クラス以上/900万枚以上)のサブセット
クラス:500
Train+Val:1,743,042
Box/画像:7.0
評価指標
■ IoU:物体検出において物体位置の予測精度を評価する指標(Intersection over Union)。Area of Overlap / Area of Unionにて計算する。Confusion Matrixの要素で表現すると、TP / (TP + FP + FN)となる。Jaccard係数とも呼ばれる。
■ AP:判定のconfidenceの閾値を変化させると、一般的にPrecisionもRecallも変化していくが、これら2つの指標はトレードオフの関係にある場合がある。そこでPrecision・Recallを加味したAP(Average Precision)という指標が用いられる。APは、PrecisionをRecallの関数(P=f(R))と見たときに、この関数が描く曲線の下側の面積となる。厳密には各Recallのレベルに対して最大のPrecisionにスムージングして計算される。また。積分はInterpolated APとして有限点(e.g., 11点)で計算される。クラスラベルごとに計算されます。
AP = \int^{1}_{0} P(R) \, dR
■ mAP:APがクラスラベルごと別々に計算される指標であるのに対し、mAPはクラスラベルごとに平均して計算される。クラス数がCの時、下記となる。
mAP = \frac{1}{C} \sum^{C}_{i=1} AP_{i}
■ FPS:Flames per Secondの略で、検出精度に加え検出速度を考慮した指標。
物体検知の大枠
代表的なネットワーク
・2012
AlexNet
・2014
VGGNet、GoogLeNet(Inception-v1)
・2015
ResNet
・2016
DenseNet、Inception-ResNet(Inception-v4)
・2017
MobileNet
・2018
AmoebaNet
代表的な物体検知のフレームワーク
・2012
DCNN
・2013
DetectorNet、RCNN
・2014
SPPNet
・2015
Fast RCNN、Faster RCNN、YOLO、SSD
・2016
RFCN、YOLO9000、FPN
・2017
RetinaNet、Mask RCNN
・2018
CornerNet
物体検知のフレームワークは大きく2つに分類できる。
■ 2段階検出器(Two-stage detector)
・候補領域の検出とクラス推定を別々に行う
・相対的に精度が高い傾向
・相対的に計算量が大きく推論も遅い傾向
■ 1段階検出器(One-stage detector)
・候補領域の検出とクラス推定を同時に行う
・相対的に精度が低い傾向
・相対的に計算量が小さく推論も早い傾向
SSD:Single Shot Detector
SSDでは、物体を検知する際デフォルトボックスと呼ばれるbounding boxをネットワーク全体としては8,732個ランダムに配置する。学習を進めていくと、SSDはデフォルトボックスを適切な形に変形し、クラスラベル、confidenceとともに出力するようになる。このように多数の、デフォルトボックスを配置するための問題とその対処方法が下記である。
■ Non-Maximum Suppression
画像上の1つの検出対象に対して複数のbounding boxが配置されてしまうため、bounding box同士でIoUを計算し、数値が大きく変わらない(=大体同じ位置にある)のであれば、confidenceの大きいほうのみを残し、他は削除するという手法。
■ Hard Negative Mining
VGG12をベースとしたSSDの検出対象クラスは21個あり、中にはネガティブクラスとして「背景」のクラスが存在する。一般的に物体検出を行う画像では、検出対象の物体が占める面積よりも背景部分が占める面積のほうが大きいため、クラス間でbounding boxの数に大きな偏りが生じる。そこで、背景クラス(ネガティブクラス)とそれ以外のクラス(ポジティブクラス)の比が3:1以下となるように条件を設けている。
Semantic Segmentationの概略
CNNでは、受容野を広げるためにプーリング層を実装しているが、その結果、画像データが層を進むごとに解像度が落ちていく。一方、セマンティックセグメンテーションは元画像のピクセルレベルでのクラス分類を出力するため、層を進んできた画像データ解像度を回復する必要がある。
■ Deconvolution/Transposed convolution
解像度を回復する(Up-sampling)ための手法。通常の畳み込み層のようにkernel size、padding、strideの設定が必要となる。逆畳み込みと呼ばれることも多いが、畳み込みの逆演算ではなく、poolingによって失われた情報が復元されるわけではない。
■ U-Net
U-Netは、低レイヤーの画像情報(特にリーカルな輪郭情報)をSkip-connectionを介してpooling後の特徴量に伝達し、セグメンテーションの精度を向上している代表的なネットワークである。
■ Unpooling
Up-samplingの手法で、max-poolingで最大の値を持っていたピクセル位置を記憶しておき、その位置情報を加味してUp-samplingを行う。
■ Dilated Convolution
Convolutionの段階で受容野を広げる手法。ピクセルを飛ばして畳み込みを行うことで、大きなフィルターを用いた時の受容野を少ない演算量で得ようとする工夫。