学習レポート: 深層学習(Day 3)

# 再帰型ニューラルネットワーク(RNN, Recurrent NN)

## 再帰型ニューラルネットワークの概念
- 時系列データに対応可能なニューラルネットワーク
    - 時系列データとは、時間的順序を追って一定間隔ごとに観察され、しかも相互に統計的依存関係が認められるようなデータの系列
        - 例えば、音声データ、株価データやテキストデータ
        - テキストデータは単語が前の単語とつながっていると考える
- 入力層〜中間層〜出力層というニューラルネットワークの基本構造は変わらないが、中間層同士に時間的なつながり(数珠つながり)を持つ
    - 時系列モデルを扱うには、初期の状態と過去の時間t-1の状態を保持し、そこから次の時間でのtを再帰的に求める再帰構造が必要となる
    - 中間層の出力を次の時点の中間層の入力として活用する
        - エルマンネットワーク
            - 出力層を戻すジョーダンネットワークというものも存在する
        - 入力$ x_n $と前の時点の中間層 $ z_{n-1} $から、新しい中間層 $ z_n $が得られ、そこから出力$ y_n $を得る
        - 重みは入力の重み$ W_{(in)} $と出力の重み$ W_{(out)} $に加え、前の中間層からの重み$ W $がある
    - 数学的な記述
        - $ u^t = W_{(in)}x^t + Wz^{t-1} + b $
            - $x^t$はt時点の入力、$z^{t-1}$は前の中間層の出力
        - $ z^t = f(W_{(in)}x^t + Wz^{t-1} + b) $
            - $f$は中間層の活性化関数
        - $ v^t = W_{(out)}z^t + c $
        - $ y^t = g(W_{(out)}z^t + c) $
            - $g$は出力層の活性化関数
    - コードでの記述(バイアスb, 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])
        y[:,t] = functions.sigmoid(np.dot(z[:,t+1].reshape(1, -1), W_out))
        ```
        - 数式上は現在がt、前がt-1だが、Pythonでの実装ではtが0から始まるため、現在がt+1、前がtとする
- BPTT(Back Propagation Through Time)
    - RNNにおけるパラメーター調整方法の一種
        - 誤差逆伝播法の一種
    - 数学的な記述
        - Wが3つあるので、3つの偏微分を行う
        - $ \dfrac{∂E}{∂W_{(in)}} = \dfrac{∂E}{∂u^t} \lbrack \dfrac{∂u^t}{∂W_{(in)}} \rbrack ^T = δ^t[x^t]^T$
            - $ δ $は、$E$から$u$までの微分をひとまとめにしたもの
            - RNNにおいて、$ \lbrack &ensp; \rbrack ^T $は時間を遡って全て微分することを表す
        - $ \dfrac{∂E}{∂W_{(out)}} = \dfrac{∂E}{∂v^t} \lbrack \dfrac{∂v^t}{∂W_{(out)}} \rbrack ^T = δ^{out,t}[z^t]^T$
        - $ \dfrac{∂E}{∂W} = \dfrac{∂E}{∂u^t} \lbrack \dfrac{∂u^t}{∂W} \rbrack ^T = δ^t[z^{t-1}]^T$
        - バイアスは2つ
        - $ \dfrac{∂E}{∂b} = \dfrac{∂E}{∂u^t} \dfrac{∂u^t}{∂b} = δ^t &ensp; as &ensp; \dfrac{∂u^t}{∂b} = 1 $
        - $ \dfrac{∂E}{∂c} = \dfrac{∂E}{∂v^t} \dfrac{∂v^t}{∂v} = δ^{out,t} &ensp; as &ensp; \dfrac{∂v^t}{∂c} = 1 $
    - コードでの記述(バイアスb, cは省略)
        ```Python
        np.dot(X.T, delta[:,t].reshape(1, -1))
        np.dot(z[:,t+1].reshape(-1, 1), delta_out[:,t].reshape(-1,1))
        np.dot(z[:,t].reshape(-1, 1), delta[:,t].reshape(1, -1))
        ```

    - $δ^t$は、$E$から$u$までの微分をひとまとめにしたもの
        - $ δ^{t-n-1} = δ^{t-n}　\cdots $ は一つ前の時点とつながっていることを示す
        ```Python
        delta_out[:,t] = functions.d_mean_squared_error(dd, y[:,t]) * functions.d_sigmoid(y[:,t])

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

    - パラメーターの更新式
        ```Python
        # 勾配更新
        W_in_grad += np.dot(X.T, delta[:,t].reshape(1,-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 -= learning_rate * W_in_grad
        W_out -= learning_rate * W_out_grad
        W -= learning_rate * W_grad
        ```
        - 時間的なつながりを辿る部分は$ \sum $がつく
- BPTTの全体像
    - $ z^t $を開くと$ z^{t-1} $が出てきて、$ z^{t-1} $を開くと$ z^{t-2} $が出てきてと、zが数珠つながりになっている(=再帰)

## LSTM(Long Short-Term Memory)
- RNNの課題
    - 時系列を遡れば遡るほど、勾配が消失していく
        - 結果的に長い時系列の学習が困難
    - 逆の勾配爆発という現象もある
        - 勾配が層を逆伝搬するごとに指数関数的に大きくなってしまい、結果的に学習がうまく行かない
- LSTMは構造自体を変えて勾配消失問題を解決するRNNの一種
- CEC: Constant Error Carousel
    - CECは記憶機能しかない(考える機能は持たない)
    - 勾配消失および勾配爆発の解決方法として、理屈上、勾配が1であれば解決できる
        - 勾配を1にするために導入されたのがCEC
    - 逆に勾配が1ということは、ニューラルネットワークの学習特性がないということ(覚えるが、学習しない)
        - CECの周りに学習機能を配置する
- 入力ゲートと出力ゲート
    - CECは記憶機能しか持たず、学習機能を持たないが、入力ゲートと出力ゲートを追加することにより、全体として学習機能を持たせる
        - 入力・出力ゲートを追加することで、それぞれのゲートへの入力値の重みを、重み行列W,Uで可変可能とする
    - 入力ゲートは入力された情報をどのように記憶するかをCECに指示する
        - 入力ゲートは適切に指示できるように学習する
    - 出力ゲートはCECが保管している情報(記憶)をどのように使うかを指示する
        - 出力ゲートは適切に指示できるように学習する
    - 今回の入力値(赤い矢印)と前回の出力値(青い矢印)をもとに、入力ゲート、出力ゲート共に学習する
        - 入力ゲートの重み$W_i$(今回の入力値に対する重み)、$U_i$(前回の出力値に対する重み)を学習する
            - 入力ゲートの出力(どれだけCECに記憶させるか)$i(t)$
    - 出力ゲートの重み$W_o$(今回の入力値に対する重み)、$U_o$(前回の出力値に対する重み)を学習する
        - 出力ゲートの出力(CECの記憶をどう使うか)$o(t)$
- 忘却ゲート
    - CECには過去の情報が全て保管されているが、過去の情報が入らなくなっても、削除することができず、保管され続ける
        - 忘却ゲートは、過去の情報が要らなくなった時点で、その情報を忘却させる機能を持つ
    - CECの出力$c(t)　= i(t)·a(t) + f(t)·c(t-1) $
        - ここで$a(t)$が入力、$i(t)$が入力ゲートからの指示
        - $f(t)$が忘却ゲートからの指示
- 覗き穴結合
    - CEC自体の値は、ゲート(入力/出力/忘却)制御に影響を与えていないが、与えることはできないか
        - 覗き穴結合はCEC自体の値を重み行列を介してゲートに伝播可能にした構造
        - ただ実際にはあまり大きな効果は得られていない
- LSTMでは、勾配爆発を避けるために、勾配クリッピングを使うことがある
    - 大きい勾配に対し、ノルムを閾値以下に正規化する
- GRU(Gated Recurrent Unit)
    - LSTMの改良版
        - LSTMではパラメーター数が多く、計算負荷が高くなる
        - GRUはパラメーターを減らすことによって、計算負荷が低い
    - CEC、入力ゲート、出力ゲート、忘却ゲートがなく、その代わりに、リセットゲートと更新ゲートがある
    - リセットゲートは、隠れ層にどのような状態で保存しておくかを指示する
        - リセットゲートの出力: $ r(t) = W_rx(t)+U_h·h(t-1)+b_r(t) $
        - リセットゲートの重み: $U_h$
    - 活性化関数の出力
        - $ h(t) = f(W_hx(t)+U_h·r(t)·h(t-1)+b_h(t)) $
            - ここで$W_hx(t)$が入力
            - $U_h$と$r(t)$がリセットゲートからの重みと出力
            - $h(t-1)$が前回の出力
    - 更新ゲートは今覚えていることをどのように出力するかを指示する
        - 更新ゲートの出力: $ z(t) = W_zx(t)+U_z·h(t-1)+b_z(t) $
        - $1-z(t)$分は直接出力
        - $z(t)$分は前回の出力$h(t-1)$を混ぜ合わせる
        - 結果的に出力は、$z(t)·h(t-1) + (1-z(t))·h(t)$となる

- 双方向RNN
    - 過去の情報だけでなく、未来の情報を加味することで、精度を向上させるためのモデル
        - 文章の推敲や、機械翻訳等に活用される
        - 文章は過去(文の前段)も未来(文の後段)も同時に情報が得られるので、過去から未来への情報の伝達だけでなく、未来から過去への情報の伝達も活用できる


# RNNの応用例
## Seq2Seq
- 2つのネットワークがつながれている自然言語処理用のネットワーク
    - 入力だけでなく出力も時系列で予測できる
    - 一つ目のネットワークで文の意味(=文脈)を抽出・保持し、それを二つ目のネットワークに渡す
    - 一つ目のネットワークがEncoder、二つ目がDecoder
    - Seq2seqはEncoder-Decoderモデルの一種で、機械対話や機械翻訳などに使用される
- Encoder RNN
    - ユーザーがインプットしたテキストデータを、単語等のトークンに区切って渡す構造
    - Tokening: 文章を単語等のトークン毎に分割し、トークンご
とのIDに分割する
        - IDはone hot vectorで表現できる
    - Embedding: IDから、そのトークンを表す分散表現ベクトルに変換
        - one hot vectorは数万列(単語数だけ)あるが、それを数百列の数字行列に圧縮する
            - 意味が似ている単語が似た行列になるようにする
            - 結果的に単語の意味を抽出していることになる
    - Encoder RNN: ベクトルを順番にRNNに入力していく
    - 処理手順
        1. vec1をRNNに入力し、hidden stateを出力する
        1. このhidden stateと次の入力vec2をまたRNNに入力し、結果のhidden stateを出力という流れを繰り返す
        1. 最後のvecを入れたときのhidden stateをfinal stateとしてとっておく
        1. このfinal stateがthought vectorと呼ばれ、入力した文の意味を表すベクトルとなる
    - 意味が似ている単語が似た行列になるようにする(=特徴量の抽出)という処理が難しい
        - GoogleのBERTはMLM(Masked Language Model)で
            - 単語に分解した上で、ある単語をマスク、前後の単語から予測させる
            - それによって、似た意味の単語が似たような意味ベクトルになるようになる
            - 人間が学習データを作る必要がないので、大量のデータを学習できる

    - Decoder RNN
        - システムがアウトプットデータを単語等のトークンごとに生成する構造
        - 処理手順
            1. Decoder RNN: Encoder RNNのfinal state(thought vector)から、各token の生成確率を出力
            1. Sampling: 生成確率にもとづいてtokenをランダムに選ぶ
            1. Embedding: 2で選ばれたtokenをEmbeddingして Decoder RNNへの次の入力とする
            1. Detokenize: 1~3を繰り返し、2で得られたtokenを文字列に直す
- HRED
    - Seq2seqでは一問一答しかできない(それまでの文脈が活かされない)
    - HREDでは、過去n-1個の発話から次の発話を生成する
        - 一文一文も単語と同様にSeq2seqにかける
        - 文の意味ベクトルを次に引き継ぐ
            - Seq2seqでは単語の意味ベクトルだったが、その上にさらに文レベルでのRNNを構築する
    - つまり、HREDとは、Seq2seq + Context RNN
        - Context RNN: Encoderのまとめた各文章の系列をまとめて、これまでの会話コンテキスト全体を表すベクトルに変換する構造
            - これによって、過去の発話の履歴を加味した返答をできる
    - HREDの課題
        - HREDは確率的な多様性が字面にしかなく、会話の「流れ」のような多様性が無い
        - HREDは「うん」「そうだね」など、短く情報量に乏しい答えをしがちである
- VHRED
    - HREDに、VAEの潜在変数の概念を追加したもの
        - HREDの課題をVAEの潜在変数の概念を追加することで解決した構造

- オートエンコーダー(自己符号化器)
    - 教師なし学習の一つであり、学習時の入力データは訓練データのみで教師データは利用しない
    - 構造
        - 入力データから潜在変数zに変換するニューラルネットワークをEncoder、逆に潜在変数zをインプットとして元画像を復元するニューラルネットワークをDecoderとする
            - 間にzをはさみつつ、入力と出力が同じになるような学習をする(入力層と出力層のノード数が同じ)
            - zの次元が入力データより小さいため、次元削減となる
- VAE(Variational Auto Encoder)
    - 通常のオートエンコーダーの場合、何かしら潜在変数zにデータを押し込めているものの、その構造がどのような状態かわからない
    - VAEはこの潜在変数zに確率分布z~N(0, 1)を導入したもの
        - データを潜在変数zの確率分布という構造に押し込めることを可能にする
    - 元のデータが似ていればzも似たようなベクトルになるような学習がなされる
    - 学習に際しては、ノイズを加えた状態でzを生成する
        - これによって汎用性が高くなる
- VQ-VAE
    - VAEの派生技術
    - 自然界の様々な事物の特徴を捉えるには離散変数の方が適しているという発想から、潜在変数zが離散的な数値となるように学習が行う
        - これによって従来のVAEで起こりやすいとされるposterior collapse(VAEをPixel CNNなどの強力なデコーダーと組み合わせた時に、潜在変数がデータの特徴をうまく捉えることが出来なくなる現象)の問題を回避する
    - EncoderとDecoderの間に、Encoderの出力を離散的な潜在変数に対応させるベクトル量子化処理(VQ: Vector Quantization)を行う
    - Encoderの出力$\boldsymbol{z_e}(\boldsymbol{x})$と、予め用意しておいた埋め込みベクトル(embedding vector)とのユークリッド距離を計算し、最も距離が近い(=最も似ている)埋め込みベクトルに置き換える
        - $\boldsymbol{z_q}(\boldsymbol{x}) = \boldsymbol{e_k}$
        - ここで$\boldsymbol{e_k}$は最も距離が近い埋め込みベクトル
    - VQ-VAEを階層構造にすることでさらに高解像度画像を生成できるようにしたVQ-VAE2と呼ばれる技術も開発されている

## Word2vec
- RNNでは、単語のような可変長の文字列をNNに与えることができず、固定長形式で単語を表す必要がある
- Word2vecでは、学習データからボキャブラリーを作成
    - 辞書の単語数だけone-hot vectorができる
- これにボキャブラリ数 × 単語ベクトルの次元数の重み行列を導入することで、現実的な計算速度とメモリ量で大規模データの分散表現の学習を実現可能にした

## Attention Mechanism
- Seq2seqは、文章が長くなるほどそのシーケンスの内部表現の次元も大きくなっていくため、長い文章への対応が難しい
- Attention Mechanismは、「入力と出力のどの単語が関連しているのか」という関連性の重みを学習する仕組み
    - 一文の中で特に重要な単語を自力で見出す


# 参考文献
- [ディープラーニング入門 Chainer チュートリアル](https://tutorials.chainer.org/ja/tutorial.html)
- ディープラーニングE資格エンジニア問題集
- ゼロから作るDeep Learning
- ゼロから作るDeep Learning 2 自然言語処理編
- 機械学習のエッセンス -実装しながら学ぶPython,数学,アルゴリズム- (Machine Learning)


# 確認テスト
- サイズ5×5の入力画像を、サイズ3×3のフィルタで畳み込んだ時の出力画像のサイズ
    - ストライド2、パディング1
    - (5+2*1-3)/2 + 1 = 3
    - 3×3

- RNNにおける3つの重み
    - 入力から現在の中間層を生成する際の重み
    - 前の中間層の出力から現在の中間層を生成する際の重み
    - 中間層から出力を生成する際の重み

- 微分の連鎖律
    - $ z = t^2 $
    - $ t = x + y $
    - $ \dfrac{dz}{dx} = \dfrac{dz}{dt}·\dfrac{dt}{dx} $
    - $ = 2t·1 $
    - $ = 2(x + y) $

- RNNを数式で表現する
    - 数学的な記述
        - $ u_1 = W_{(in)}x_1 + Wz_0 + b $
        - $ z_1 = f(W_{(in)}x_1 + Wz_0 + b) $
        - $ y_1 = g(W_{(out)}z_1 + c) $
            - $f$は中間層の活性化関数、$g$は出力層の活性化関数(シグモイド)

- シグモイド関数の微分の最大値
    - 0.25

- LSTMでのゲート
    - 入力ゲート
    - 忘却ゲート(「とても」を忘却)
    - 出力ゲート

- LSTMとCECの課題[確認要]
    - LSTMではパラメーター数が多く、計算負荷が高くなる
    - CECは勾配が1で学習能力がない
        - そのため周囲に入力ゲート、忘却ゲート、出力ゲートを配置して全体として学習できるようにしている

- LSTMとGRUの違い
    - GRUはLSTMの改良版
        - LSTMではパラメーター数が多く、計算負荷が高くなる
        - GRUはパラメーターを減らすことによって、計算負荷が低い
        - GRUにはCEC、入力ゲート、出力ゲート、忘却ゲートがなく、その代わりに、リセットゲートと更新ゲートがある

- seq2seqの説明
    - （2) RNNを用いたEncoder-Decoderモデルの一種であり、機械翻訳などのモデルに使われる

- Seq2seqとHREDの違い
    - Seq2seqではそれまでの文脈が活かされないため、一問一答しかできない
    - HREDでは、過去n-1個の発話から次の発話を生成する
    - つまり、HREDとは、Seq2seq + Context RNN
- HREDとVHREDの違い
    - VHREDは、HREDにVAEの潜在変数の概念を追加したもの

- VAE
    - VAEは自己符号化器の潜在変数に確率分布を導入したもの

- RNNとword2vecの違い
    - RNNでは、単語のような可変長の文字列をNNに与えることができず、固定長形式で単語を表す必要がある
    - これに対し、Word2vecでは、学習データからボキャブラリーを作成し、ボキャブラリ数 × 単語ベクトルの次元数の重み行列を導入することで、現実的な計算速度とメモリ量で大規模データの分散表現の学習を実現可能にした
- Seq2seqとAttentionの違い
    - Seq2seqは、文章が長くなるほどそのシーケンスの内部表現の次元も大きくなっていくため、長い文章への対応が難しい
    - これに対し、Attention Mechanismは、「入力と出力のどの単語が関連しているのか」という関連性の重みを学習する仕組みであり、一文の中で特に重要な単語を自力で見出すことができる