ラビットチャレンジ: Stage.4 深層学習 Day3, 4

本ページはラビットチャレンジの、 Stage.4 "深層学習 Day3,4" のレポート提出を兼ねた受講記録です。 提出指示を満たすように、下記の方針でまとめました。

  1. 動画講義の要点まとめ (Day3,4)

    • 自分が講義の流れを思い出せるようなメモを残す。通常であれば要点として記載すべき図・数式などがあっても、それが自分にとって既知であれば、言葉の説明ですませることもある

  2. 実装演習 (Day.3 のみ)

    • 各Sectionで取り上げられた .ipynb or .py ファイルの内容を実行した結果を記載

    • ただし、初学者向けにやや冗長な内容がある場合、抜粋することもある

  3. 確認テスト (Day.3 のみ)

    • 確認テストの解答に相当する内容は、個別の節をもうけず、要点まとめに含めたり、コードに対するコメントとして記載する

      • 確認テストは重要事項だから、出題されているのであって、まとめ/演習と内容がかぶるはず。

      • 事務局がレポートチェックをする時のために、(確認テスト:1-2) のようなタグを付す。1つ目の数字が section番号、2つ目の数字が 「何番目のテストか?」

  4. 1~3 をまとめる上で思うことがあれば、考察やコメントも適宜残しておく。

目次

Day 3

0: CNN の復習

動画 day 3-1 0:00 ~ 18:00

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

動画 day 3-1 18:00 - 2:56:30 くらい

実装演習 simple RNN (バイナリ加算)

import sys
sys.path.append('./data')
import numpy as np
from common import functions
import matplotlib.pyplot as plt

def d_tanh(x):
    return 1-np.tanh(x)**2 

def learn_simple_rnn(hidden_layer_size=16, learning_rate=0.1, weight_init_std=1, initializer='Normal', activation='sigmoid', iters_num=10000):
    # データを用意
    # 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
    output_layer_size = 1

    plot_interval = 100

    if activation == 'sigmoid':
        h_activation = functions.sigmoid
        h_activation_d = functions.d_sigmoid
    elif activation == 'tanh':
        h_activation = np.tanh
        h_activation_d = d_tanh
    elif activation == 'relu':
        h_activation = functions.relu
        h_activation_d = functions.d_relu
    else: 
        print('activation is not valid')

    if initializer == 'Normal':
        # ウェイト初期化 (バイアスは簡単のため省略)
        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)
    elif initializer == 'Xavier':
        # 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)
    elif initializer == 'He':
        # 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)
    else:
        print("initializer is not valid!")


    # 勾配
    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) # (1, i)
            # 時刻tにおける正解データ
            dd = np.array([d_bin[binary_dim - t - 1]]) # (1,)
            
            u[:,t+1] = np.dot(X, W_in) + np.dot(z[:,t].reshape(1, -1), W) # (1,i) (i,hidden) + (1, hidden) (hidden,hidden)  = (hidden)
            z[:,t+1] = h_activation(u[:,t+1]) # = (hidden, )

            y[:,t] = functions.sigmoid(np.dot(z[:,t+1].reshape(1, -1), W_out)) # (1,hidden) * (hidden,0ut) = (1,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]) # (o,) \otimes (o,) = (o,)
            
            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) # (i,i)
            delta[:,t] = (np.dot(delta[:,t+1].T, W.T) + np.dot(delta_out[:,t].T, W_out.T)) * h_activation_d(u[:,t+1]) # (1,hidden) (hidden,hidden) + (1,o)(o,hidden) = (hidden,)

            # 勾配更新
            W_out_grad += np.dot(z[:,t+1].reshape(-1,1), delta_out[:,t].reshape(-1,1)) # (hidden,1) (o,1) = (hidden, o) 
            W_grad += np.dot(z[:,t].reshape(-1,1), delta[:,t].reshape(1,-1)) # (hidden, 1) (1, hidden) = ( hidden,hidden )
            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()

隠れ層の数を変えてみる。8層で必要十分?

learn_simple_rnn(hidden_layer_size = 2,  iters_num=20000)
learn_simple_rnn(hidden_layer_size = 4,  iters_num=20000)
learn_simple_rnn(hidden_layer_size = 8,  iters_num=20000)
learn_simple_rnn(hidden_layer_size = 16, iters_num=20000)
learn_simple_rnn(hidden_layer_size = 32, iters_num=20000)
learn_simple_rnn(hidden_layer_size = 64, iters_num=20000)

svg

svg

svg

svg

svg

svg

初期化によってもかなり変わる

learn_simple_rnn(hidden_layer_size = 8,  weight_init_std=0.1, iters_num=10000)
learn_simple_rnn(hidden_layer_size = 8,  weight_init_std=0.5, iters_num=10000)
learn_simple_rnn(hidden_layer_size = 8,  weight_init_std=1, iters_num=10000)
learn_simple_rnn(hidden_layer_size = 8,  weight_init_std=2, iters_num=10000)
learn_simple_rnn(hidden_layer_size = 8,  weight_init_std=5, iters_num=10000)

svg

svg

svg

svg

svg

learning rate は 0.1付近~0.5 の間くらい?? 0.01 のときは単に収束が遅いだけではなさそうにもみえる。

learn_simple_rnn(hidden_layer_size = 8,  weight_init_std=1, learning_rate=0.01, iters_num=50000)
learn_simple_rnn(hidden_layer_size = 8,  weight_init_std=1, learning_rate=0.1, iters_num=20000)
learn_simple_rnn(hidden_layer_size = 8,  weight_init_std=1, learning_rate=0.5, iters_num=10000)
learn_simple_rnn(hidden_layer_size = 8,  weight_init_std=1, learning_rate=1.0, iters_num=10000)
learn_simple_rnn(hidden_layer_size = 8,  weight_init_std=1, learning_rate=2.0, iters_num=10000)

svg

svg

svg

svg

svg

activation は sigmoid なので Xavier がよいはずだが、あまり効果なさそう?? むしろ Normalが一番安定しているように見える.

learn_simple_rnn(weight_init_std=1, initializer='Normal', learning_rate=0.2, iters_num=10000)
learn_simple_rnn(initializer='Xavier', learning_rate=0.2, iters_num=10000)
learn_simple_rnn(initializer='He', learning_rate=0.2, iters_num=10000)

svg

svg

svg

活性化関数をtanhにしてみると、Normal より Xavier 収束が早くなっていそう。

learn_simple_rnn(activation='tanh',    initializer='Normal', learning_rate=0.075, iters_num=10000)
learn_simple_rnn(activation='tanh',    initializer='Xavier', learning_rate=0.075, iters_num=10000)

svg

svg

活性化関数を relu にかえると。よいときと悪い時とで差が.. 初期化方法が Normal だと ときどき基本的に収束はむずかしそう。 かなりの頻度で、NaN や overflow (これは勾配爆発が原因だろう) が起きる。

learn_simple_rnn(activation='relu',    initializer='Normal', learning_rate=0.1, iters_num=20000)

svg

learn_simple_rnn(activation='relu',    initializer='Normal', learning_rate=0.1, iters_num=20000)
---------------------------------------------------------------------------

    ValueError                                Traceback (most recent call last)

    ValueError: cannot convert float NaN to integer

    
    The above exception was the direct cause of the following exception:


    ValueError                                Traceback (most recent call last)

    <ipython-input-67-6a829eb4e065> in <module>
    ----> 1 learn_simple_rnn(activation='relu',    initializer='Normal', learning_rate=0.1, iters_num=20000)
          2 
          3 


    <ipython-input-43-3497d874a704> in learn_simple_rnn(hidden_layer_size, learning_rate, weight_init_std, initializer, activation, iters_num)
        103             all_loss += loss
        104 
    --> 105             out_bin[binary_dim - t - 1] = np.round(y[:,t])
        106 
        107         ## 誤差逆伝播


    ValueError: setting an array element with a sequence.

He を つかうと 収束していそうなケースと、失敗してそうなのが半々くらい。 傾向としては、NaN や overflow が観測される数が著しく減った。

learn_simple_rnn(activation='relu',    initializer='He', learning_rate=0.1, iters_num=20000)

svg

learn_simple_rnn(activation='relu',    initializer='He', learning_rate=0.1, iters_num=20000)

svg

Section.2 : LSTM

動画 day 3-1 2:57 ~ 4:14

# 勾配クリッピングのコード
def gradient_clipping(grad, threshold):
  """
  grad: gradient
  """
  norm = np.linalg.norm(grad)
  rate = threshold / norm
  if rate < 1:
    return (ここを埋める)
  return grad
#1
LSTMの全体像

Section.3 : GRU

動画 day 3-1 4:25 ~ 4:48

#1
GRUの全体像
ValueError: Variable rnn/basic_rnn_cell/kernel already exists, disallowed. Did you mean to set reuse=True or reuse=tf.AUTO_REUSE in VarScope? Originally defined at (以下略)

- メンテされているかどうかもわからないので、今回この問題は放置してここでは、推論, 予測の関数だけ見ておく.

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 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]

Section.4 : 双方向RNN

動画 day 3-1 4:48 ~ 4:57

#1
双方向RNNの全体像

Section.5 : Seq2Seq

動画 day 3-1 4:57 ~ 5:45

#1
seq2seqの全体像

Section.6 : Word2vec

動画 day 3-1 5:45 ~ + day 3-2 0:00 ~ 1:54

Section.7 : Attention Mechanism

動画 day 3-2 1:54 ~

Day.4

Section.1 : 強化学習

Section.2 : AlphaGo

Section.3 : 軽量化・高速化技術

Section.4 : 応用モデル

Section.5 : Transformer

"Transformer 以前" 的な位置づけで RNNによる seq2seq をまずは取り上げる

Transformer の説明

 - 効果: 入力文が長くなっても精度が落ちないことが確認されている 

svg

Section.6 : 物体検知・セグメンテーション

section. 7 : GAN

レポート課題にはなっていないが、ついでなのでまとめておく

取り組み概要 & 感想

取り組みの記録

今までのステージでは、レポート作成 -> ステージテストの順番でとりくんできたが、 今回は、ステージ3終了直後から、M4MT の E資格受験資格認定テストコースに参加したため、 テストから先に取り組んだ.

ざっくりの取り組み方は下記の通り。実装演習がすくないため大体、1日1セクション終わらせる感じでスムーズにすすめることができた。(メモのまとめは大体動画再生時間)

合計 28.5h 程度か。

感想ほか 

講義動画・資料について。

day3, 4 は Simple RNN 以外はまともにコードを読み書きしていないし、本当に定性的な説明を見聞きしただけなので、試験をパスする・開発での実践、どちらの対しても色々足りていない感じはする。黒本や練習問題を解きながらも、シラバスで紹介されたネットワークを自分で組んで学習させてみるなどしてみようかと考えている。

E資格受験資格認定テストについて。 E資格のテストを意識してか、これまでのステージテストとは違い Stage.4 までの資料・動画を見るだけでは解けない知識問題が数問あった。 調べればすぐにわかることだし(特に調べながら解くことは禁止されていないという認識)、仮にそれらの問題が未回答だとしても、ギリギリ合格基準は超えられるはずなので、 基本的には、ラビットチャレンジでこれまで学習してきた内容の理解をそのまま問う形だと思った。

今後の計画

ラビットチャレンジ実施の計画 & 実績がこれ.

今後の計画は少しだけ復習.

ラビットチャレンジ関連次回の投稿は、試験直前期のレポート & 合格報告となる予定です!