E資格 深層学習Day3

# はじめに
ラビット・チャレンジ Deep Learning 「深層学習Day3」の受験レポート
要点のまとめ、実装演習結果、確認テスト結果の考察
講座受講中は手元のノートに記載している。
ここでは、振り返りながらメモレベルで表す。

# 深層学習Day3
## 再帰型ニューラルネットワークの概念
### 要点まとめ
時系列データに対応可能な、ニューラルネットワーク。
音声データ・テキストデータetc

時系列データを扱うために、初期の状態と過去の時間t-1の状態を保持し、そこから次の時間でのtを再帰的に求める再帰構造が必要になる。

BPTTはRNNの誤差逆伝搬法、RNNにおいてのパラメータ調整方法の一種



### 確認テスト
NNのネットワークには大きくわけて3つの重みがある。1つは入力から現在の中間層を定義する際にかけられる重み、1つは中間層から出力を定義する際にかけられる重みである。残り1つの重みについて説明せよ。
→ 前の中間層から現在の中間層に至るまでの重み

### 実装演習
基準設定(weight_init_std=1、learning_rate=0.1、hidden_layer_size=16)
最終的には誤差がなくなっていることがわかる。

```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)
# 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))
# 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)


# 勾配
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])
#         z[:,t+1] = functions.relu(u[:,t+1])
#         z[:,t+1] = np.tanh(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])
#         delta[:,t] = (np.dot(delta[:,t+1].T, W.T) + np.dot(delta_out[:,t].T, W_out.T)) * functions.d_relu(u[:,t+1])
#         delta[:,t] = (np.dot(delta[:,t+1].T, W.T) + np.dot(delta_out[:,t].T, W_out.T)) * d_tanh(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/1344334/b668b304-6ea2-2206-43a0-f19a3b7ba97b.png)


## LSTM
### 要点まとめ
RNNの課題
時系列を遡るほど、勾配が消失していく。
構造自体を変えて解決したものがLSTM
学習と記憶を行う機能を分離させる。

勾配消失問題とは逆に、層を逆伝搬するごとに指数関数的に大きくなっていくことを、勾配爆発という。
勾配爆発を防ぐために勾配のクリッピングを行うという手法がある。

#### CEC
勾配消失および勾配爆発の解決方法として、勾配が、1であれば解決できる。
記憶を担当する。（学習能力はない）

CECの課題
入力データについて時間依存度に関係なく重みが一律である。
ニューラルネットワークの学習特性がないということ。

#### 入力ゲートと出力ゲート
入力・出力ゲートを追加することで、それぞれのゲートへの入力値の重みを、重み行列W,Uで可変可能とする。
CECに覚えさせる方法を身につける。
学習を担当

#### 忘却ゲート
CECは、過去の情報がすべて保管されている。
過去の情報が要らなくなった場合、削除することはできず、保管され続ける。
過去の情報が要らなくなった場合、そのタイミングで情報を忘却する機能。

#### 覗き穴結合
CECの保存されている過去の情報を、任意のタイミングで他のノードに伝播させたり、あるいは任意のタイミングで忘却させたい。CEC自身の値は、ゲート制御に影響を与えていない。
覗き穴結合とは、CEC自身の値に、重み行列を介して伝播可能にした構造。

### 確認テスト
以下の文章をLSTMに入力し空欄に当てはまる単語を予測したいとする。文中の「とても」という言葉は空欄の予測においてなくなっても影響を及ぼさないと考えられる。このような場合、どのゲートが作用すると考えられるか。
「映画おもしろかったね。ところで、とてもお腹が空いたから何か____。」

答え：忘却ゲート


## GRU
### 要点まとめ
従来のLSTMでは、パラメータが多数存在していたため、計算負荷が大きかった。しかし、GRUでは、そのパラメータを大幅に削減し、精度は同等またはそれ以上が望める様になった構造。
計算負荷が低いことがメリット
リセットゲート・更新ゲートを持つ

### 確認テスト
LSTMとCECが抱える課題について、それぞれ簡潔に述べよ。

答え：
LSTMはパラメタが多く計算不可が高い。
CECは勾配が１（学習機能がない）そのため、複数の機能を周りに持つ必要があった。

LSTMとGRUの違いを簡潔に述べよ。

答え：
LSTMに比べ、GRUはパラメータが少なく計算量が小さい。

## 双方向RNN
### 要点まとめ
過去の情報だけでなく、未来の情報を加味することで、精度を向上させるためのモデル
実用例文章の推敲や、機械翻訳

### 確認テスト

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1344334/a2ed2c42-7c0f-7656-1463-ec002bfedd2f.png)

(4)


## Seq2Seq
### 要点まとめ

Seq2seqとはEncoder-Decoderモデルの一種を指す。
入力を取るエンコーダ、出力を出すデコーダで構成される。
機械対話や機械翻訳などに使用される。
時系列のデータを取って時系列のデータを出力する。

Encoder RNN
ユーザーがインプットしたテキストデータを、単語等のトークンに区切って渡す構造。
Taking :文章を単語等のトークン毎に分割し、トークンごとのIDに分割する。
Embedding :IDから、そのトークンを表す分散表現ベクトルに変換。
Encoder RNN:ベクトルを順番にRNNに入力していく。

・vec1をRNNに入力し、hidden stateを出力。このhiddenstateと次の入力vec2をまたRNNに入力してきたhidden stateを出力という流れを繰り返す。
・最後のvecを入れたときのhiddenstateをfinalstateとしてとっておく。このfinalstateがthoughtvectorと呼ばれ、入力した文の意味を表すベクトルとなる。


Decoder RNN
システムがアウトプットデータを、単語等のトークンごとに生成する構造。
1.Decoder RNN: Encoder RNN のfinal state (thought vector) から、各token の生成確率を出力していきますfinal state をDecoder RNN のinitial state ととして設定し、Embedding を入力。
2.Sampling:生成確率にもとづいてtoken をランダムに選びます。
3.Embedding:2で選ばれたtoken をEmbedding してDecoder RNN への次の入力とします。4.Detokenize:1 -3 を繰り返し、2で得られたtoken を文字列に直します。

HREDとは？
Context RNN: Encoder のまとめた各文章の系列をまとめて、これまでの会話コンテキスト全体を表すベクトルに変換する構造。
Seq2Seq+ Context RNN過去の発話の履歴を加味した返答をできる。
同じコンテキスト（発話リスト）を与えられても、答えの内容が毎回会話の流れとしては同じものしか出せない。
HRED は短く情報量に乏しい答えをしがちである。

VHREDとは？HREDに、VAEの潜在変数の概念を追加したもの。
HREDの課題を、VAEの潜在変数の概念を追加することで解決した構造

オートエンコーダとは？
教師なし学習の一つ。そのため学習時の入力データは訓練データのみで教師データは利用しない。

VAE
元のデータが近ければ同じデータになるようにしたい。
元のデータの類似度をそのままzに当てはめる。
Decoderの入力にランダム性をもたせる。

### 確認テスト
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1344334/66122627-b01e-1419-85f5-dcca9b2891e1.png)

答え：２


seq2seqとHRED、HREDとVHREDの違いを簡潔に述べよ。
答え：１



![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1344334/d7f4c862-2f52-571b-1198-4e3aa29f9fff.png)

答え：確率分布


## Word2vec
### 要点まとめ
one-hotベクトルからEnbeddingに置き換えるための仕組み。

課題:RNNでは、単語のような可変長の文字列をNNに与えることはできない
固定長形式で単語を表す必要がある。
学習データからボキャブラリを作成

メリット
大規模データの分散表現の学習が、現実的な計算速度とメモリ量で実現可能にした

Word2vecには2種類のモデルが存在する。１つはCBoW、もうひとつはSkip-gramである。

CBOWモデルでは、文中の抜けおちた単語の周辺の単語から抜けおちた単語を推定する手法である。

反対に、Skip-gramは、ある単語に隣接する単語を推定する。

### 確認テスト
RNNとword2vecの違いを完結に述べよ

答え：
RNNは時系列データを処理するのに適したニューラルネットワーク。
word2vecは単語の分散表現ベクトルを得る手法。

## Attention Mechanism
### 要点まとめ
「入力と出力のどの単語が関連しているのか」の関連度を学習する仕組み。
固定長のベクトルにEncoderの全ての情報に圧縮するのは無理があるためEncoderの全ての状態をDecoderが利用できるようにする。

近年、性能が上がっている自然言語モデル。

### 確認テスト
seq2seqとAttentionの違いを簡潔に述べよ。

答え：
seq2seqは一つの時系列データから別の時系列データを得るニューラルネットワーク。
Attentionは時系列データの中身の関連性に重みをつける手法。

seq2seqはEncoderの最後の状態のみをDecoderに渡すが、Attentionは全ての状態をDecoderに渡す。