JDLA　E資格対策  深層学習day3　レポート


JDLA　E資格の受験用に、深層学習day3を勉強した備忘録


#Section１：再帰型ニューラルネットワークの概念

##1-1　RNN


時系列データに対応可能な、ニューラルネットワーク

時系列データとは？
時間的順序を追って一定間隔ごとに観察され，
しかも相互に統計的依存関係が認められるようなデータ
　⇒音声データ・テキストデータ、など

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2456345/b7a200de-e2e8-67ac-fe74-6af15827b03a.png)

＜数式＞

```math
u^{t}=W_{(in)}x^{t}+Wz^{t-1}+b
```

```math
z^{t}=f(W_{(in)}x^{t}+Wz^{t-1}+b)
```
```math
v^{t}=W_{(out)}z^{t}+c
```

```math
v^{t}=g(W_{(out)}z^{t}+c)
```

＜コード表示＞

```python
u[:,t+1] = np.dot(X, W_in) + np.dot(z[:,t].reshape(1, -1), W)
z[:,t+1] = functions.sigmoid(u[:,t+1])
np.dot(z[:,t+1].reshape(1, -1), W_out)
y[:,t] = functions.sigmoid(np.dot(z[:,t+1].reshape(1, -1), W_out))
```

＜実装＞

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


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

# データを用意
# 2進数の桁数
binary_dim = 8
# 最大値 + 1
largest_number = pow(2, binary_dim)
# largest_numberまで2進数を用意
binary = np.unpackbits(np.array([range(largest_number)],dtype=np.uint8).T,axis=1)

input_layer_size = 2
hidden_layer_size = 16
output_layer_size = 1

weight_init_std = 1
learning_rate = 0.1

iters_num = 10000
plot_interval = 100

# ウェイト初期化 (バイアスは簡単のため省略)
W_in = weight_init_std * np.random.randn(input_layer_size, hidden_layer_size)
W_out = weight_init_std * np.random.randn(hidden_layer_size, output_layer_size)
W = weight_init_std * np.random.randn(hidden_layer_size, hidden_layer_size)


# 勾配
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()
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2456345/ef7a4abe-9dac-4011-c3ca-b68f421f33a1.png)

6000以降でロスが落ち着いている


##1-2　BPTT

Backpropagation Through Time(BPTT)とは、RNNにおける誤差逆伝播法の1つで、時系列の考え方を取り入れたもの

・計算結果（=誤差）から微分を逆算することで、不要な再帰的計算を避けて微分を算出できる

＜重み＞

```math
\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}
```

```math
\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}
```

```math
\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}
```

＜バイアス＞


```math
\frac{\partial E}{\partial b}=\frac{\partial E}{\partial u^{t}}  \frac{\partial u^{t}}{\partial b}=\delta^{t}
```

```math
\frac{\partial E}{\partial c}=\frac{\partial E}{\partial v^{t}}  \frac{\partial v^{t}}{\partial c}=\delta^{out, t}
```


＜確認テスト1＞
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2456345/8290005a-6617-dc9c-6b7b-b319c5b7bab2.png)

⇒(画像の高さor幅+2*パディング-フィルタの高さor幅)/ストライド+1
　(5+2∗1−3)/2+1=3
　高さ、幅ともに同じため3×3の出力サイズとなる

＜確認テスト2＞
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2456345/8a07e4e9-898e-686d-ef32-1aca3e72929e.png)

⇒前の中間層から、現在の中間層を定義する際にかけられる重み

＜確認テスト3＞
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2456345/c93dd013-d600-05e2-9d78-a78d6f579118.png)

⇒
　　$z=t^2$
　　$t=x+y$
　　$dz/dx=dz/dt･dt/dx=2t×1=2(x+y)$

＜確認テスト4＞
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2456345/1ac8d3f0-00e3-3111-0a73-b8b9050b4de7.png)

⇒

```math
z_1=sigmoid \left( z_0 W+x_1W_{in}+b \right)
```

```math
y_1=sigmoid \left( z_1 W_{out}+c \right)
```

（※b:バイアス）

#Section２：LSTM(Long Short-Term Memory)


・勾配消失問題

誤差逆伝播法が下位層に進んでいくに連れて、勾配がどんどん緩やかになっていく。
そのため、勾配降下法による、更新では下位層のパラメータはほとんど変わらず、訓練は最適値に収束しなくなる。

・勾配爆発
勾配が、層を逆伝播するごとに指数関数的に大きくなっていきます。


＜LSTMの構造＞
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2456345/c5b54d91-eb29-79a9-81c2-d21341adf899.png)


＜CEC＞
過去のデータを保存するための部分です。
CEC単独では以下のような問題があるため、ゲートや覗き穴結合で解決する。

時間依存度に関係なく重みが一律(重み衝突)になってしまう
過去の情報をすべて持っているため、その情報が必要なくなった後も影響を及ぼしてしまう
CEC自身の値はゲートの制御に影響を与えないため、CECの過去の情報を任意のタイミングで伝搬・忘却が出来ない


＜入力ゲート＞
1つ前のユニットの入力をどの程度受け取るか(一律ではなく可変の重み)を調整します。

```math
i=\sigma(W_{i}h_{t-1}+U_{i}x_{t})
```

＜出力ゲート＞
1つ前のユニットの入出力をどの程度受け取るか(一律ではなく可変の重み)を調整します

```math
o=\sigma(W_{o}h_{t-1}+U_{o}x_{t})
```

＜忘却ゲート＞
CECの中身をどの程度残すか(不必要な情報を忘却する)を調整します。

```math
f=\sigma(W_{f}h_{t-1}+U_{f}x_{t})
```

＜メモリセル＞

```math
g=tanh(W_{g}h_{t-1}+U_{g}x_{t})
```

```math
c_{t}=(c_{t-1}\otimes f)\oplus(g\otimes i)
```

＜出力＞

```math
h_{t}=tanh(c_{t})\otimes o
```





＜確認テスト5＞
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2456345/ee29b4a5-f178-2b93-8e68-4b038207e124.png)

⇒(2)　0.25


＜確認テスト6＞
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2456345/998b88a9-299b-59a5-4294-a09124ae43a7.png)
⇒忘却ゲート



Section３：GRU（Gated Recurrent Unit） 

LSTMの改良版：LSTMでは、パラメータ数が多く、計算負荷が高くなる問題があった。
そのため、GRUはパラメータを大幅に削減し（計算負荷が低い）、精度は同等またはそれ以上が望める様になった構造を持つ。


＜GRUの構造＞
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2456345/03faa595-1b8d-e94a-dca4-e4f5b41fcae6.png)

＜確認テスト7＞
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2456345/91b4a747-b844-0d4b-5ea7-9abbc47e1ca7.png)

⇒
LSTM:入力ゲート、出力ゲート、忘却ゲートと過去の情報を補完するCECがあり複雑でパラメータが多く計算量が多い
CEC:過去の情報が全て保管されている
　　　勾配が１で学習能力がない


＜確認テスト8＞
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2456345/b2dad5d2-ef39-93e1-3786-49e7cd6eef6b.png)

⇒
LSTMは、入力ゲート、出力ゲート、忘却ゲートの3つのゲートがあり、ゲートが多いためその分パラメータ数も多い。
GRUは、リセットゲート、更新ゲートの2つで、簡略化されている分、パラメータの数が少ない。

＜実装＞

tensorflowを用いて、LSTMとGRUを使い自然言語処理を実施

```python
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 ) 番目までの単語が１組の入力となる
        # このときの出力は ( 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) という３次元のテンソルなので
        # 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")
```

⇒結果は省略

#Section４：双方向RNN(Bidirectional RNN)

通常のRNNは過去の情報を保持することで時系列データの学習を実現していました。
双方向RNNの場合は、過去の情報に加えて未来の情報も加味させます。
つまり、中間層の出力を、未来への順伝播と過去への逆伝播の両方向に伝播します。
また、学習時に過去と未来の情報の入力を必要とすることから、運用時も過去から未来までのすべての情報を入力して
はじめて予測できるようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2456345/2c086659-1009-a769-4f6e-345f38dd1ea9.png)

実用例：文章の推敲や、機械翻訳等


#Section５：Seq2Seq

Seq2seqとは
Encoder-Decoderモデルの一種を指します

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2456345/f80458cd-d4c9-c89b-61f4-9a1d5287fcef.png)

Seq2seqの具体的な用途とは?機械対話や、機械翻訳などに使用されている

・EncoderとDecoderの二つのモデルのドッキングしたもので、例えば
　機械翻訳の場合、
　　①Encoderで単語列が入力されると順次記憶し、分散表現ベクトルへ変換されて文脈の解釈が可能にする
　  ②それをDecoderで文脈の解釈の分散表現ベクトルから翻訳する言語の表現を逆変換で生成する
ようなシーケンスとシーケンスの繋がれで実現している。

＜HRED＞
・Seq2seqの課題としては、一問一答しかできなく、問に対して文脈
　も何もなく、ただ応答が行われる続けること。
・一つ一つの過去の文脈、文もシーケスと次のシーケース、状態と次の状態
　コンテキスト化して過去n-1 個の発話から次の発話を生成するのが
　HRED (the hierarchical recurrent encoder-decoder) である。

・HREDの課題
　HRED は確率的な多様性が字面にしかなく、
　会話の「流れ」のような多様性が無い。同じコンテキスト（発話リスト）
　を与えられても、答えの内容が毎回会話の流れとしては同じものしか出せない。



＜VHRED＞
・VHRED (Variational Hierarchical Recurrent Encoder Decoder)は、HREDへ
　VAEの潜在変数の概念を追加したもので、HREDの課題である短い単調な
　答えにすることを解決した構造である。


＜VAE (Variational AutoEncoder)＞

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

・オートエンコーダ構造としては、
       Encoder : 入力の画像データから潜在変数 zに変換するNN
       Decoder : 逆に潜在変数 zをインプットとして元画像を復元するNN
   で構成され、次元削減を行える。


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


＜確認テスト8＞
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2456345/5d92d5da-04c5-efff-f6a3-bb06ff26095a.png)

⇒(2)

＜確認テスト9＞
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2456345/69f08e0c-6304-fd82-cb74-30ba71647e37.png)

⇒
＜Seq2SeqとHRED＞
Seq2Seqは一問一答しかできないが、HREDはn−1の発話から文脈に応じた回答ができる
＜HREDとVHRED＞
HREDは発話に多様性がなく情報量に乏しいですが、VHREDはそれらの課題を解決し、多様性ある発話ができる

＜確認テスト10＞
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2456345/05c85944-48c4-43de-80cd-33774ce86e99.png)

⇒確率分布

#Section６：Word2vec

・RNN課題：単語のような可変長の文字列をNNに与えることができず、構造上で固定長形式で単語を表す必要がある。

・解決法：embeddingが分散表現ベクトルとしてword2vecがある。これは既にSeq2seqで解説しているが、
　　学習データからボキャブラリを作成する際に、one-hotの 分散表現ベクトルが辞書の単語数だけできるの
　　をembeddingにより回避している。


#Section７：Attention Mechanism

・seq2seqの課題として、長い文章への対応が難しい
　seq2seq では、2単語でも、100単語でも、固定次元ベクトルの中に入力しなければならなく、中間層の隠れ層が限られている。

・文章が長くなるほど、そのシーケンスの内部表現の次元も大きくなっていく仕組みが必要で、
　「Attention Mechanism」では「入力と出力のどの単語が関連しているのか」の関連度を学習する仕組みを
　　実現している。


＜確認テスト11＞

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2456345/f7bab01d-2629-0373-dcb3-699f415ad841.png)

⇒
＜RNN、Word2vec＞
RNNはボキャブラリー×ボキャブラリー数だけの重みが必要となるが、
Word2vecはボキャブラリー数×単語ベクトルの次元数だけの重みのみが必要となる点。

＜Seq2Seq、Attention＞
Seq2Seqは長い文章に対して対応が難しいが、Attentionを取り入れることで、
入力と出力の関連度に基づくため長い文章にも対応できる点。