#はじめに
本記事は、日本ディープラーニング協会のDeep Learning資格試験(E資格)の受験に必要な、協会認定の講座プログラムのひとつであるラビットチャレンジのレポート記事(深層学習編(後半))となります。
#day3-1.再起型ニューラルネットワークの概念
- RNNとは、時系列データに対応可能な、ニューラルネットワーク
- 時系列データとは、時間的順序を追って一定間隔ごとに観察され,しかも相互に統計的依存関係が認められるようなデータの系列
ANS:
W_{(in)},W_{(out)},W
残り一つの重みは、中間層から中間層に到る重み(W)である。
- RNNの特徴とは?
- 時系列モデルを扱うには、初期の状態と過去の時間t-1の状態を保持し、そこから次の時間でのtを再帰的に求める再帰構造が必要になる
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()
ANS:2
隣接単語(表現ベクトル)から表現ベクトルを作るという処理は、隣接している表現leftとrightを合わせたものを特徴量としてそこに重みを掛けることで実現する。つまり、W.dot(np.concatenate([left, right]))である。
- BPTTとは?
- RNNにおいてのパラメータ調整方法の一種
ANS:
$$
\frac{dz}{dx} = \frac{dz}{dt}\frac{dt}{dx} = 2t = 2(x+y)
$$
ANS:
$$
S_1 = x_1W_{in} + S_0W + b \
y_1 = g(w_{out} + S_1)
$$
ANS:2
NNでは中間層出力h_{t}が過去の中間層出力h_{t-1},.., h_{1}に依存する。RNNにおいて損失関数を重みWやUに関して偏微分するときは、それを考慮する必要があり、dh_{t}/dh_{t-1} = Uであることに注意すると、過去に遡るたびにUが掛けられる。つまり、delta_t= delta_t.dot(U)となる。
#day3-2.LSTM
- RNNの課題時系列を遡れば遡るほど、勾配が消失していく
- 勾配消失の解決方法とは、別で、構造自体を変えて解決したものがLSTM
- 勾配消失問題とは?
- 誤差逆伝播法が下位層に進んでいくに連れて、勾配がどんどん緩やかになっていく。そのため、勾配降下法による、更新では下位層のパラメータはほとんど変わらず、訓練は最適値に収束しなくなる。
ANS:0.25
- LSTMは、CEC、入力ゲート、出力ゲート、忘却ゲート、覗き穴結合からなる
ANS:忘却ゲート
#day3-3.GRU
- GRUとは?
- 従来のLSTMでは、パラメータが多数存在していたため、計算負荷が大きかった。しかし、GRUでは、そのパラメータを大幅に削減し、精度は同等またはそれ以上が望める様になった構造。
- メリット計算負荷が低い
ANS:
LSTMが抱える課題は、パラメータ数が多くなり計算量が多くなること
CECが抱える課題は、勾配が1となり、学習機能がないこと
ANS:4
新しい中間状態は、1ステップ前の中間表現と計算された中間表現の線形和で表現される。つまり更新ゲートzを用いて、(1-z) * h + z * h_barと書ける。
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")
ANS:
LSTMは、入力ゲート・出力ゲート・忘却ゲート3つの機能に加えて、CECが存在する。一方のGRUはCECがなく、更新ゲートとリセットゲートをもつ
GRUの方が計算量が少なくて済む
#day3-4.双方向RNN
- 過去の情報だけでなく、未来の情報を加味することで、精度を向上させるためのモデル
- 実用例文章の推敲や、機械翻訳等図式に利用される
ANS:4
双方向RNNでは、順方向と逆方向に伝播したときの中間層表現をあわせたものが特徴量となるので、np.concatenate([h_f, h_b[::-1]], axis=1)である。
(1)h_f + h_b[::-1]
(2)h_f * h_b[::-1]
(3)np.concatenate([h_f, h_b[::-1]], axis=0)
(4)np.concatenate([h_f, h_b[::-1]], axis=1)
#day3-5.Seq2Seq
- Seq2seqとは?
- Encoder-Decoderモデルの一種を指す。
- Seq2seqの具体的な用途とは?
- 機械対話や、機械翻訳などに使用されている
- Encoder RNNとは、ユーザーがインプットしたテキストデータを、単語等のトークンに区切って渡す構造。
- Taking :文章を単語等のトークン毎に分割し、トークンごとのIDに分割する
- Embedding :IDから、そのトークンを表す分散表現ベクトルに変換。
- Encoder RNN:ベクトルを順番にRNNに入力していく。
- Encoder RNN処理手順
- vec1をRNNに入力し、hidden stateを出力。このhiddenstateと次の入力vec2をまたRNNに入力してきたhidden stateを出力という流れを繰り返す。
- 最後のvecを入れたときのhiddenstateをfinalstateとしてとっておく。
- このfinalstateがthoughtvectorと呼ばれ、入力した文の意味を表すベクトルとなる。
ANS:2
#day3-6.Word2vec
- 課題:RNNでは、単語のような可変長の文字列をNNに与えることはできない。
- 固定長形式で単語を表す必要がある。
- 学習データからボキャブラリを作成※わかりやすく7語のボキャブラリを作成したら本来は、辞書の単語数だけできあがる。
- one-hot:ベクトルApplesを入力する場合は、入力層には以下のベクトルが入力される。※本来は、辞書の単語数だけone-hotベクトルができあがる。1...apples 0...eat 0...I 0...like 0...to
- メリットとして、大規模データの分散表現の学習が、現実的な計算速度とメモリ量で実現可能にした
#day3-7.Attention Mechanism
- 課題:seq2seq の問題は長い文章への対応が難しい
- seq2seq では、2単語でも、100単語でも、固定次元ベクトルの中に入力しなければならない。
- 解決策:文章が長くなるほどそのシーケンスの内部表現の次元も大きくなっていく、仕組みが必要になる
- Attention Mechanismとは、この課題を解決するべく「入力と出力のどの単語が関連しているのか」の関連度を学習する仕組みである
ANS:
RNNは時系列データを処理するのに適したネットワーク
word2vecは、単語の分散表現ベクトルを得る手法
seq2seqは、ひとつの時系列データから別の時系列データを得るネットワーク
Attention Mechanismは、時系列データの関連性に重みをつける手法
#day4-1.強化学習
-
強化学習とは、長期的に報酬を最大化できるように環境のなかで行動を選択できるエージェントを作ることを目標とする機械学習の一分野
-
行動の結果として与えられる利益(報酬)をもとに、行動を決定する原理を改善していく仕組みです。
-
強化学習の応用例
-
マーケティングの場合
-
エージェント:プロフィールと購入履歴に基づいて、キャンペーンメールを送る顧客を決めるソフトウェアである。
-
行動:顧客ごとに送信、非送信のふたつの行動を選ぶことになる。
-
報酬:キャンペーンのコストという負の報酬とキャンペーンで生み出されると推測される売上という正の報酬を受ける
-
環境について事前に完璧な知識があれば、最適な行動を予測し決定することは可能。
-
どのような顧客にキャンペーンメールを送信すると、どのような行動を行うのかが既知である状況。強化学習の場合、上記仮定は成り立たないとする。
-
不完全な知識を元に行動しながら、データを収集。最適な行動を見つけていく。
-
最後にゲームを題材とした強化学習の記事を紹介する
参考:https://hampen2929.com/reinforcement-learning-game/804 -
記事に記載のある通り、私たちにも馴染みのあるゲームについて、強化学習の技術を利用して、ゲームに強いモデルを作成していることがわかる。私自身やったことのあるゲームについても動画付きで紹介されており、非常に興味深かった。今後、機会があれば、強化学習の勉強をかねて、作成したモデルと戦ってみたいと考えている。
#day4-2.AlphaGo
-
AlphaGoは、囲碁AIである
-
AlphaGo (Lee)とAlphaGo Zeroのふたつがある
-
AlphaGoの学習は以下のステップで行われる
-
1、教師あり学習によるRollOutPolicyとPolicyNetの学習
-
2、強化学習によるPolicyNetの学習
-
3、強化学習によるValueNetの学習
-
PolicyNetの教師あり学習
-
KGS Go Server(ネット囲碁対局サイト)の棋譜データから3000万局面分の教師を用意し、教師と同じ着手を予測できるよう学習を行った。
-
具体的には、教師が着手した手を1とし残りを0とした19×19次元の配列を教師とし、それを分類問題として学習した。
-
この学習で作成したPolicyNetは57%ほどの精度である。
-
PolicyNetの強化学習
-
現状のPolicyNetとPolicyPoolからランダムに選択されたPolicyNetと対局シミュレーションを行い、その結果を用いて方策勾配法で学習を行った。
-
PolicyPoolとは、PolicyNetの強化学習の過程を500Iteraionごとに記録し保存しておいたものである。
-
現状のPolicyNet同士の対局ではなく、PolicyPoolに保存されているものとの対局を使用する理由は、対局に幅を持たせて過学習を防ごうというのが主である。
-
この学習をminibatch size 128で1万回行った。
-
AlphaGo(Lee) とAlphaGoZeroの違い
-
1、教師あり学習を一切行わず、強化学習のみで作成
-
2、特徴入力からヒューリスティックな要素を排除し、石の配置のみにした
-
3、PolicyNetとValueNetを1つのネットワークに統合した
-
4、Residual Net(後述)を導入した
-
5、モンテカルロ木探索からRollOutシミュレーションをなくした
-
最後に、AlphaGoと人類の戦いについてまとめた記事を紹介したい
参考:https://wired.jp/2017/05/28/future-of-go-summit-day5/ -
世界最強の囲碁棋士・柯潔(カ・ケツ)と、グーグル傘下のDeepMindが開発する人工知能(AI)「AlphaGo」による一連の対局は、2017年5月27日に柯が負けを喫して幕を閉じた。柯は試合後の記者会見で、「AlphaGoは世界を変えてしまったが、ぼくはぼく自身でありたい。そして囲碁が楽しいことを伝えたい。その責任がある」と語った。
-
上記記事を読んで、プロ棋士としてのプライドがありながらも、AIとの対決を承諾し、破れてしまったとて、今後も囲碁が楽しいことを伝えていきたいと語った柯プロは本当にすごい人格の持ち主なのだと感じた。
#day4-3.軽量化・高速化技術
- 分散深層学習とは?
- 深層学習は多くのデータを使用したり、パラメータ調整のために多くの時間を使用したりするため、高速な計算が求められる。
- 複数の計算資源(ワーカー)を使用し、並列的にニューラルネットを構成することで、効率の良い学習を行いたい。
- データ並列化、モデル並列化、GPUによる高速技術は不可欠である。
- データ並列化:親モデルを各ワーカーに子モデルとしてコピーする。データを分割し、各ワーカーごとに計算させる。
- モデル並列化:親モデルを各ワーカーに分割し、それぞれのモデルを学習させる。全てのデータで学習が終わった後で、一つのモデルに復元。モデルが大きい時はモデル並列化を、データが大きい時はデータ並列化をすると良い。
- GPUによる高速化
- GPGPU (General-purpose on GPU):元々の使用目的であるグラフィック以外の用途で使用されるGPUの総称
- GPUは、比較的低性能なコアが多数存在し、簡単な並列処理が得意である。ニューラルネットの学習は単純な行列演算が多いので、高速化が可能
- モデルの軽量化
- 量子化:重みの精度を下げることにより計算の高速化と省メモリ化を行う技術
- 蒸留:複雑で精度の良い教師モデルから軽量な生徒モデルを効率よく学習を行う技術
- プルーニング:寄与の少ないニューロンをモデルから削減し高速化と省メモリ化を行う技術
#day4-4.応用モデル
- MobileNets
- ディープラーニングモデルは精度は良いが、その分ネットワークが深くなり計算量が増える。
- 計算量が増えると、多くの計算リソースが必要で、お金がかかってしまう。
- ディープラーニングモデルの軽量化・高速化・高精度化を実現
- 一般的な畳み込みレイヤーは計算量が多い
- MobileNetsはDepthwise ConvolutionとPointwise Convolutionの組み合わせで軽量化を実現
- DenseNet
- Dense Convolutional Network(以下、DenseNet)は、畳込みニューラルネットワーク(以下、CNN)アーキテクチャの一種である。
- ニューラルネットワークでは層が深くなるにつれて、学習が難しくなるという問題があったが、Residual Network(以下、ResNet)などのCNNアーキテクチャでは前方の層から後方の層へアイデンティティ接続を介してパスを作ることで問題を対処した。
- DenseBlockと呼ばれるモジュールを用いた、DenseNetもそのようなアーキテクチャの一つである。
#day4-5.Transformer
- TransformerはRNNやCNNを使用せず、Attentionのみを用いるSeq2Seqモデルです。
- 並列計算が可能なためRNNに比べて計算が高速な上、Self-Attentionと呼ばれる機構を用いることにより、局所的な位置しか参照できないCNNと異なり、系列内の任意の位置の情報を参照することを可能にしています。
- その他にもいくつかの工夫が加えられており、翻訳に限らない自然言語処理のあらゆるタスクで圧倒的な性能を示すことが知られています。
- TransformerのモデルもEncoder-Decoderモデルの構造になっています。 EncoderとDecoderはPositional Encoding: 入出力の単語のEmbedding時に単語の位置情報を埋め込む、Scaled Dot-Product Attention: 内積でAttentionを計算し、スケーリングを行う、Multi-head Attention: Scaled Dot-Product Attentionを複数のヘッドで並列化する、Position-Wise Feed Forward Network: 単語列の位置ごとに独立して処理を行う など、いくつかのモジュールから構成されているため、それぞれのモジュールを個別に定義していきます。
#Position Encodingの実装例
def position_encoding_init(n_position, d_pos_vec):
"""
Positional Encodingのための行列の初期化を行う
:param n_position: int, 系列長
:param d_pos_vec: int, 隠れ層の次元数
:return torch.tensor, size=(n_position, d_pos_vec)
"""
# PADがある単語の位置はpos=0にしておき、position_encも0にする
position_enc = np.array([
[pos / np.power(10000, 2 * (j // 2) / d_pos_vec) for j in range(d_pos_vec)]
if pos != 0 else np.zeros(d_pos_vec) for pos in range(n_position)])
position_enc[1:, 0::2] = np.sin(position_enc[1:, 0::2]) # dim 2i
position_enc[1:, 1::2] = np.cos(position_enc[1:, 1::2]) # dim 2i+1
return torch.tensor(position_enc, dtype=torch.float)
- Position Encodingを可視化すると以下のようになる
pe = position_encoding_init(50, 256).numpy()
plt.figure(figsize=(16,8))
sns.heatmap(pe, cmap='Blues')
plt.show()
#day4-6.物体検知・セグメンテーション
- 物体検知とは、画像を取り込み、画像の中から定められた物体の位置と種類、個数を特定する技術である
- また、単純な分類問題と比較すると、物体の位置についても注目している点が異なる
- 物体検出で利用される代表的なデータセットは以下
- VOC12,ILSVRC17,MS CICI18,OICOD18
- 物体検知は、外観検査によく使われており、幅広い分野で活躍している
- 物体検出にはいくつかの手法がある
- 具体的な手法の内容については、下記記事に分かりやすくまとめられていた
参考:https://crystal-method.com/topics/object-detection/ - セグメンテーションについては、下記記事の記載を参照
参考:https://www.skillupai.com/blog/tech/segmentation1/