ラビットチャレンジ　レポート　深層学習 day3、day4

#1. 再帰型ニューラルネットワーク（RNN）の概念
###概要
時系列データに対応可能な、ニューラルネットワークである
時系列データとは？
時間的順序を追って一定間隔ごとに観察され，しかも相互に統計的依存関係が認められるようなデータの系列

具体的な時系列データとは？
・音声データ
・テキストデータ
等々

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

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



###演習

```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/201068/facf658c-5a93-2d9c-f874-b0dabae8113a.png)


###確認テスト

(1)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201068/1e897a47-da3d-efde-0e60-3366fe6a12a4.png)

答えは
中間層(t−1)から中間層(t)への重み


(2)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201068/acd74ff0-b4d8-c0a4-3982-d17547667492.png)

答えは
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201068/215e123b-936a-6f91-045b-0244f8b5f730.png)


(3)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201068/f5aac307-69bf-8c3d-5696-f2bd0afd2750.png)

答えは
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201068/0a86fab7-0e1b-1ac4-b93d-0861b19c6167.png)



#2. LSTM
###概要
LSTM(Long short-term memory)は、RNN(Recurrent Neural Network)の拡張として1995年に登場した、時系列データ(sequential data)に対するモデル。

◆入力/出力ゲート
入力ゲートと出力ゲートを追加し、それぞれのゲートへの入力値の重みを重み行列W、Uで可変可能とすることにより、CECの課題を解決。

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

###演習

```python
def lstm(x, prev_h, prev_c, W, U, b):
  # セルへの入力やゲートをまとめて計算し、分割
  lstm_in = _activation(x.dot(W.T)) + prev_h.dot(U.T) + b)
  a, i, f, o = np.hsplit(lstm_in, 4)

  # 値を変換、セルへの入力:(-1, 1)、ゲート:(0, 1)
  a = np.tanh(a)
  input_gate = _sigmoid(i)
  forget_gate = _sigmoid(f)
  output_gate = _sigmoid(o)

  # セルの状態を更新し、中間層の出力を計算
  c = input_gate * a + forget_gate * c
  h = output_gate * np.tanh(c)
  return c, h

```

###確認テスト

(1)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201068/6c67abe8-c584-9af2-deec-567030f43b29.png)

答えは
0.25

(2)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201068/26af7ff1-ce28-cc89-d822-271cca2d574a.png)

答えは
忘却ゲート


#3. GRU
###概要
従来のLSTMでは、パラメータが多数存在していたため、計算付加がとても大きかった。
そこで、GRUではそのパラメータを大幅に削減することで精度は同等またはそれ以上が望めるようになった構造をしている。

###演習

```python

from common.np import *  # import numpy as np (or import cupy as np)
from common.layers import *
from common.functions import softmax, sigmoid

class GRU:
    def __init__(self, Wx, Wh, b):  

        self.params = [Wx, Wh, b]  
        self.grads = [np.zeros_like(Wx), np.zeros_like(Wh), np.zeros_like(b)]  ###
        self.cache = None

    def forward(self, x, h_prev):
        Wx, Wh, b = self.params
        H = Wh.shape[0]
        Wxz, Wxr, Wxh = Wx[:, :H], Wx[:, H:2 * H], Wx[:, 2 * H:]
        Whz, Whr, Whh = Wh[:, :H], Wh[:, H:2 * H], Wh[:, 2 * H:]
        bz, br, bh = b[:H], b[H:2 * H], b[2 * H:]  

        z = sigmoid(np.dot(x, Wxz) + np.dot(h_prev, Whz) + bz)
        r = sigmoid(np.dot(x, Wxr) + np.dot(h_prev, Whr) + br)
        h_hat = np.tanh(np.dot(x, Wxh) + np.dot(r*h_prev, Whh) + bh)
        h_next = (1-z) * h_prev + z * h_hat

        self.cache = (x, h_prev, z, r, h_hat)

        return h_next


    def backward(self, dh_next):
        Wx, Wh, b = self.params  
        H = Wh.shape[0]
        Wxz, Wxr, Wxh = Wx[:, :H], Wx[:, H:2 * H], Wx[:, 2 * H:]
        Whz, Whr, Whh = Wh[:, :H], Wh[:, H:2 * H], Wh[:, 2 * H:]
        x, h_prev, z, r, h_hat = self.cache

```

###確認テスト

(1)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201068/aafa83e9-c40d-9844-3792-026d63bfd125.png)

答えは
LSTMは入力、出力、忘却ゲート、CECの4つのゲートを持ち、パラメータが多くなってしまうこと。複雑で計算量が多くなってしまうこと。CECは、学習機能がなく、周りに学習能力があるゲート（入力、出力、忘却ゲート）が必要であること


(2)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201068/4176dfef-0d56-b36a-5faf-0e86c0332c98.png)

答えは
LSTMは入力、出力、忘却ゲート、CECがある。GRUは更新ゲートとリセットゲートのみであり、パラメータ量（計算量）も小さい

#4. 双方向RNN
###概要
双方向RNN（Bidirectional RNN）とは、系列の最初のステップから繰り返して順方向に予想することに加えて、系列の最後のステップからの逆方向の予測も行う、RNNを双方向形に拡張したモデルであると言える。

###演習

```python
def BiRNN(u, h_0, Wx, Wh, b, propagation_func=tanh, merge="concat"):
    # データ設定（逆方向は、時刻を反転）
    u_dr = {}
    u_dr["f"] = u
    u_dr["r"] = u[:, ::-1]
    # 初期化
    h_n, hs = {}, {}
    # 順方向
    h_n["f"], hs["f"] = RNN(u_dr["f"], h_0["f"], Wx["f"], Wh["f"], b["f"], propagation_func)
    # 逆方向
    h_n["r"], hs["r"] = RNN(u_dr["r"], h_0["r"], Wx["r"], Wh["r"], b["r"], propagation_func)
    # h_n - (n, h)
    # hs - (n, t, h)
    if merge == "concat":
        # 結合
        h_n_merge = np.concatenate([h_n["f"], h_n["r"]], axis=1)
        hs_merge = np.concatenate([hs["f"], hs["r"][:, ::-1]], axis=2)
    elif merge == "sum":
        # 合計
        h_n_merge = h_n["f"] + h_n["r"]
        hs_merge = hs["f"] + hs["r"][:, ::-1]
    return h_n_merge, hs_merge, h_n, hs


def BiRNN_back(dh_n_merge, dhs_merge, u, hs, h_0, Wx, Wh, b, propagation_func=tanh, merge="concat"):
    # データ設定（逆方向は、時刻を反転）
    u_dr = {}
    u_dr["f"] = u
    u_dr["r"] = u[:, ::-1]
    h = Wh["f"].shape[0]
    # dh_n - (n, h*2)(concatの場合)、(n, h)(sumの場合)
    # dhs - (n, t, h)
    dh_n, dhs = {}, {}
    if merge == "concat":
        # 結合
        dh_n["f"] = dh_n_merge[:, :h]
        dh_n["r"] = dh_n_merge[:, h:]
        dhs["f"] = dhs_merge[:, :, :h]
        dhs["r"] = dhs_merge[:, :, h:][:, ::-1]
    elif merge == "sum":
        # 合計
        dh_n["f"] = dh_n_merge
        dh_n["r"] = dh_n_merge
        dhs["f"] = dhs_merge
        dhs["r"] = dhs_merge[:, ::-1]
    # 初期化
    du = {}
    dWx, dWh, db = {}, {}, {}
    dh_0 = {}
    # 順方向
    du["f"], dWx["f"], dWh["f"], db["f"], dh_0["f"] = RNN_back(dh_n["f"], dhs["f"], u_dr["f"], hs["f"], h_0["f"], Wx["f"], Wh["f"], b["f"], propagation_func)
    # 逆方向
    du["r"], dWx["r"], dWh["r"], db["r"], dh_0["r"] = RNN_back(dh_n["r"], dhs["r"], u_dr["r"], hs["r"], h_0["r"], Wx["r"], Wh["r"], b["r"], propagation_func)
    # du - (n, t, d)
    du_merge = du["f"] + du["r"][:, ::-1]
    return du_merge, dWx, dWh, db, dh_0
```

#5. Seq2Seq
###概要
Seq2Seqとは、あるシーケンス（たとえば英語の文章）を受け取り、別のシーケンス（たとえば日本語の文章）に変換するモデルである

◆Encoder
Encoderには各時刻ごとに時系列データ（文章など）が入力される。EncoderのRNNレイヤ（上図ではLSTM）では、入力シーケンスを処理した結果として内部状態を返す

◆Decoder
Decoderでは、ターゲットとなるシーケンスの前の文字が与えられた場合、ターゲットシーケンスの次の文字を予測するように訓練する

###演習

```python
import random
import torch
import torch.nn as nn
from torch import optim
import torch.nn.functional as F
SOS_token = 0
EOS_token = 1
device = "cuda" # torch.device("cuda" if torch.cuda.is_available() else "cpu")                                                                                                                                                                                                                                                                                                                                                                                                                                                                  
class Lang:
    def __init__( self, filename ):
        self.filename = filename
        self.word2index = {}
        self.word2count = {}
        self.sentences = []
        self.index2word = { 0: "SOS", 1: "EOS" }
        self.n_words = 2  # Count SOS and EOS                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   
        with open( self.filename ) as fd:
            for i, line in enumerate( fd.readlines() ):
                line = line.strip()
                self.sentences.append( line )
        self.allow_list = [ True ] * len( self.sentences )
        self.target_sentences = self.sentences[ :: ]
    def get_sentences( self ):
        return self.sentences[ :: ]
    def get_sentence( self, index ):
        return self.sentences[ index ]
    def choice( self ):
        while True:
            index = random.randint( 0, len( self.allow_list ) - 1 )
            if self.allow_list[ index ]:
                break
        return self.sentences[ index ], index
    def get_allow_list( self, max_length ):
        allow_list = []
        for sentence in self.sentences:
            if len( sentence.split() ) < max_length:
                allow_list.append( True )
            else:
                allow_list.append( False )
        return allow_list
    def load_file( self, allow_list = [] ):
        if allow_list:
            self.allow_list = [x and y for (x,y) in zip( self.allow_list, allow_list ) ]
        self.target_sentences = []
        for i, sentence in enumerate( self.sentences ):
            if self.allow_list[ i ]:
                self.addSentence( sentence )
                self.target_sentences.append( sentence )
    def addSentence( self, sentence ):
        for word in sentence.split():
            self.addWord(word)
    def addWord( self, word ):
        if word not in self.word2index:
            self.word2index[ word ] = self.n_words
            self.word2count[ word ] = 1
            self.index2word[ self.n_words ] = word
            self.n_words += 1
        else:
            self.word2count[word] += 1
def tensorFromSentence( lang, sentence ):
    indexes = [ lang.word2index[ word ] for word in sentence.split(' ') ]
    indexes.append( EOS_token )
    return torch.tensor( indexes, dtype=torch.long ).to( device ).view(-1, 1)
def tensorsFromPair( input_lang, output_lang ):
    input_sentence, index = input_lang.choice()
    output_sentence       = output_lang.get_sentence( index )
    input_tensor  = tensorFromSentence( input_lang, input_sentence )
    output_tensor = tensorFromSentence( output_lang, output_sentence )
    return (input_tensor, output_tensor)

class Encoder( nn.Module ):
    def __init__( self, input_size, embedding_size, hidden_size ):
        super().__init__()
        self.hidden_size = hidden_size
        # 単語をベクトル化する。1単語はembedding_sie次元のベクトルとなる                                                                                                                                                          
        self.embedding   = nn.Embedding( input_size, embedding_size )
        # GRUに依る実装.                                                                                                                                                                  
        self.gru         = nn.GRU( embedding_size, hidden_size )
    def initHidden( self ):
        return torch.zeros( 1, 1, self.hidden_size ).to( device )
    def forward( self, _input, hidden ):
        # 単語のベクトル化                                                                                                                                                                                                        
        embedded        = self.embedding( _input ).view( 1, 1, -1 )
        # ベクトル化したデータをGRUに噛ませる。通常のSeq2Seqでは出力outは使われることはない。                                                                                                                                     
        # ただしSeq2Seq + Attentionをする場合にはoutの値を使うことになるので、リターンする                                                                                                                                        
        out, new_hidden = self.gru( embedded, hidden )
        return out, new_hidden

class Decoder( nn.Module ):
    def __init__( self, hidden_size, embedding_size, output_size ):
        super().__init__()
        self.hidden_size = hidden_size
        # 単語をベクトル化する。1単語はembedding_sie次元のベクトルとなる                                                                                                                                                          
        self.embedding   = nn.Embedding( output_size, embedding_size )
        # GRUによる実装（RNN素子の一種）                                                                                                                                                                                          
        self.gru         = nn.GRU( embedding_size, hidden_size )
        # 全結合して１層のネットワークにする                                                                                                                                                                                      
        self.linear         = nn.Linear( hidden_size, output_size )
        # softmaxのLogバージョン。dim=1で行方向を確率変換する(dim=0で列方向となる)                                                                                                                                                
        self.softmax     = nn.LogSoftmax( dim = 1 )
    def forward( self, _input, hidden ):
        # 単語のベクトル化。GRUの入力に合わせ三次元テンソルにして渡す。                                                                                                                                                           
        embedded           = self.embedding( _input ).view( 1, 1, -1 )
        # relu活性化関数に突っ込む( 3次元のテンソル）                                                                                                                                                                             
        relu_embedded      = F.relu( embedded )
        # GRU関数( 入力は３次元のテンソル )                                                                                                                                                                                       
        gru_output, hidden = self.gru( relu_embedded, hidden )
        # softmax関数の適用。outputは３次元のテンソルなので２次元のテンソルを渡す                                                                                                                                                 
        result             = self.softmax( self.linear( gru_output[ 0 ] ) )
        return result, hidden
    def initHidden( self ):
        return torch.zeros( 1, 1, self.hidden_size ).to( device )

def main():
    n_iters       = 75000
    learning_rate = 0.01 * 0.8
    embedding_size = 256
    hidden_size   = 256
    max_length    = 30
    input_lang  = Lang( 'jpn.txt' )
    output_lang = Lang( 'eng.txt')
    # 英単語数がmax_lengthより多い場合は計算しない。（時間がかかるため。）                                                                                                                                                                                                                                                                                                                                  
    allow_list = [x and y for (x,y) in zip( input_lang.get_allow_list( max_length ), output_lang.get_allow_list( max_length ) ) ]
    # allow_listに従って、英語、日本語のファイルをロードする                                                                                                                                                                                                                                                                                                                                                
    input_lang.load_file( allow_list )
    output_lang.load_file( allow_list )
    # Encoder & Decoderの定義                                                                                                                                                                                                                                                                                                                                                                               
    encoder           = Encoder( input_lang.n_words, embedding_size, hidden_size ).to( device )
    decoder           = Decoder( hidden_size, embedding_size, output_lang.n_words ).to( device )
    # Optimizerの設定                                                                                                                                                                                                                                                                                                                                                                                       
    encoder_optimizer = optim.SGD( encoder.parameters(), lr=learning_rate )
    decoder_optimizer = optim.SGD( decoder.parameters(), lr=learning_rate )
    # 学習用のペアデータの作成. He is a dog, 彼は犬だ みたいなペアをエポック数分用意する                                                                                                                                                                                                                                                                                                                    
    training_pairs = [ tensorsFromPair( input_lang, output_lang ) for i in range( n_iters ) ]
    # LOSS関数                                                                                                                                                                                                                                                                                                                                                                                              
    criterion      = nn.NLLLoss()
    for epoch in range( 1, n_iters + 1):
        # 学習用のペア単語の取り出し。                                                                                                                                                                                                                                                                                                                                                                      
        input_tensor, output_tensor = training_pairs[ epoch - 1 ]
        #初期化                                                                                                                                                                                                                                                                                                                                                                                             
        encoder_hidden              = encoder.initHidden()
        encoder_optimizer.zero_grad()
        decoder_optimizer.zero_grad()
        input_length  = input_tensor.size(0)
        output_length = output_tensor.size(0)
        # Encoder phese                                                                                                                                                                                                                                                                                                                                                                                     
        for i in range( input_length ):
            encoder_output, encoder_hidden = encoder( input_tensor[ i ], encoder_hidden )
        # Decoder phese                                                                                                                                                                                                                                                                                                                                                                                     
        loss = 0
        decoder_input  = torch.tensor( [ [ SOS_token ] ] ).to( device )
        decoder_hidden = encoder_hidden
        for i in range( output_length ):
            decoder_output, decoder_hidden = decoder( decoder_input, decoder_hidden )
            # 次の入力野取り出し                                                                                                                                                                                                                                                                                                                                                                            
            decoder_input = output_tensor[ i ]
            # 学習では一定の確率（ここでは５０％）で、自身が前に出力した単語を次の入力とする。                                                                                                                                                                                                                                                                                                              
            if random.random() < 0.5:
                # 確率が最も高い単語を抽出                                                                                                                                                                                                                                                                                                                                                                  
                topv, topi                     = decoder_output.topk( 1 )
                # 確率が一番高かった単語を次段の入力とする                                                                                                                                                                                                                                                                                                                                                  
                decoder_input                  = topi.squeeze().detach()
            # Loss関数                                                                                                                                                                                                                                                                                                                                                                                      
            loss += criterion( decoder_output, output_tensor[ i ] )
            # EOSに当たった場合は終わる。                                                                                                                                                                                                                                                                                                                                                                   
            if decoder_input.item() == EOS_token: break
        loss.backward()
        encoder_optimizer.step()
        decoder_optimizer.step()
        # 進捗状況の表示                                                                                                                                                                                                                                                                                                                                                                                    
        if epoch % 50 == 0:
            print( "[epoch num %d (%d)] [ loss: %f]" % ( epoch, n_iters, loss.item() / output_length ) )
```

###確認テスト

(1)![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201068/5e4ab82e-e588-b081-4866-db1644fb83d3.png)

答えは
RNNを用いたEncoder-Decoderモデルの一種であり、機械翻訳などのモデルに使われる。

(2)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201068/7ce8a5e4-a137-6985-64d9-1206f5c24fa0.png)

答えは
seq2seqは、一文の一問一答に対して処理できるモデルである。HREDは一問一答ではなく、今までの会話の文脈から答えを導き出せるようにしたモデル。VHREDは、HREDよりもバリエーションのある回答、豊かな回答を道美出せるようにしたモデルである


(3)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201068/96d1f4fd-435c-ded3-09bd-89566df1094e.png)

答えは
確率分布

#6. Word2Vec
###概要
Word2Vecの特徴は、2層のニューラルネットワークのみで構成されるシンプルな構造をとっている点にある。この構造のシンプルさにより、大規模データによる分散表現学習が現実的な計算量で可能となり、分散表現での自然言語処理が飛躍的に進む契機となった。

Word2Vecには、skip-gram法（Continuous Skip-Gram Model）とCBOW（Continuous Bag-of-Words Model）のふたつのモデルが内包されている

◆Skip-Gram法
入力として中心語を与え、その周辺語の予測を出力する
◆CBOW
CBOWは周辺の単語から中心語を予測するという逆の手法をとる

###演習

```python
class CBOW:
    # 初期化メソッドの定義
    def __init__(self, vocab_size, hidden_size, window_size, corpus):
        V, H = vocab_size, hidden_size

        # 重みを初期化
        W_in = 0.01 * np.random.randn(V, H).astype('f')
        W_out = 0.01 * np.random.randn(V, H).astype('f')

        # 入力層を作成
        self.in_layers = []
        for i in range(2 * window_size):
            layer = Embedding(W_in)
            self.in_layers.append(layer)

        # 出力・損失層を作成
        self.ns_loss = NegativeSamplingLoss(W_out, corpus, power=0.75, sample_size=5)

        # レイヤをまとめる
        layers = self.in_layers + [self.ns_loss]

        # 重みと勾配をまとめる
        self.params = [] # 重み
        self.grads = []  # 勾配
        for layer in layers:
            self.params += layer.params
            self.grads += layer.grads

        # 分散表現を保存
        self.word_vecs = W_in

    # 順伝播メソッドの定義
    def forward(self, contexts, target):
        # 中間層のニューロンを計算
        h = 0
        for i, layer in enumerate(self.in_layers):
            h += layer.forward(contexts[:, i])
        h *= 1 / len(self.in_layers)

        # 損失を計算
        loss = self.ns_loss.forward(h, target)
        return loss

    # 逆伝播メソッドの定義
    def backward(self, dout=1):
        # 出力層の勾配を計算
        dout = self.ns_loss.backward(dout)

        # 入力層の勾配を計算
        dout *= 1 / len(self.in_layers)
        for layer in self.in_layers:
            layer.backward(dout)

        return None
```

#7. Attention Mechanism
###概要
seq2seqの問題は、長い文章への対応が難しい。seq2seqでは、2単語でも、100単語でも、固定次元ベクトルの中に入力しなければならない。
そこで、文章が長くなるほどそのシーケンスの内部表現の次元も大きくなっていく仕組みが必要になる。

Attention Mechanismとは、「入力と出力のどの単語が関連しているのか」の関連度を学習する仕組みである。

###演習

```python
class SimpleAttention(tf.keras.models.Model):
    '''
    Attention の説明をするための、 Multi-head ではない単純な Attention です
    '''

    def __init__(self, depth: int, *args, **kwargs):
        '''
        コンストラクタです。
        :param depth: 隠れ層及び出力の次元
        '''
        super().__init__(*args, **kwargs)
        self.depth = depth

        self.q_dense_layer = tf.keras.layers.Dense(depth, use_bias=False, name='q_dense_layer')
        self.k_dense_layer = tf.keras.layers.Dense(depth, use_bias=False, name='k_dense_layer')
        self.v_dense_layer = tf.keras.layers.Dense(depth, use_bias=False, name='v_dense_layer')
        self.output_dense_layer = tf.keras.layers.Dense(depth, use_bias=False, name='output_dense_layer')

     def call(self, input: tf.Tensor, memory: tf.Tensor) -> tf.Tensor:
        '''
        モデルの実行を行います。
        :param input: query のテンソル
        :param memory: query に情報を与える memory のテンソル
        '''
        q = self.q_dense_layer(input)  # [batch_size, q_length, depth]
        k = self.k_dense_layer(memory)  # [batch_size, m_length, depth]
        v = self.v_dense_layer(memory)

        # ここで q と k の内積を取ることで、query と key の関連度のようなものを計算します。
        logit = tf.matmul(q, k, transpose_b=True)  # [batch_size, q_length, k_length]

        # softmax を取ることで正規化します
        attention_weight = tf.nn.softmax(logit, name='attention_weight')

        # 重みに従って value から情報を引いてきます
        attention_output = tf.matmul(attention_weight, v)  # [batch_size, q_length, depth]
        return self.output_dense_layer(attention_output)

```

###確認テスト
(1)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201068/81d828f3-7e56-87c0-6c57-a23a7226cc39.png)

答えは
RNNは時系列データを処理するのに適したニューラルネットワークのこと。word2vecは単語の分散表現ベクトルを得る手法。seq2seqは、ある1つの時系列データから別の時系列データを得る手法。attentionは時系列データの中身に対して重要度などの重みをつける手法

#8. 強化学習
###概要
長期的に報酬を最大化できるように環境のなかで行動を選択できるエージェントを作ることを目標とする機械学習の一分野行動の結果として与えられる利益(報酬)をもとに、行動を決定する原理を改善していく仕組みです。

◆価値関数
価値を表す関数としては、状態価値関数と行動価値関数の二種類がある。

◆方策関数
方策ベースの強化学習手法において、ある状態でどのような行動をとるのかの確立を与える関数のこと。この方策を最大化するように学習を行う。

◆Q学習
行動価値関数を行動するごとに更新することにより学習を進める方法である。

◆強化学習の応用例

環境:会社の販売促進部エージェント:プロフィールと購入履歴に基づいて、キャンペーンメールを送る顧客を決めるソフトウェアである。行動:顧客ごとに送信、非送信のふたつの行動を選ぶことになる。報酬:キャンペーンのコストという負の報酬とキャンペーンで生み出されると推測される売上という正の報酬を受ける

#9. AlphaGo
###概要
PolicyNetにて、エージェントがどこに打つかを決定するモデル。
AlphaGoには、様々な機械学習の手法が使われている。

・PolicyNetの教師あり学習
・PolicyNetの強化学習
・ValueNetの学習
・RollOutの教師あり学習
・モンテカルロ木探索


#10. 軽量化・高速化技術
###概要
分散深層学習とは
・深層学習は多くのデータを使用したり、パラメータ調整のために多くの時間を使用したりするため、高速な計算が求められる
・複数の計算資源(ワーカー)を使用し、並列的にニューラルネットを構成することで、効率の良い学習を行いたい
・データ並列化、モデル並列化、GPUによる高速技術は不可欠である。

◆分散深層学習
複数の計算資源を使用し、並列的にニューラルネットを構成することで、効率の良い学習を行える。
◆データ並列
親モデルを各計算資源に子モデルとしてコピーする。データを分割し、各計算資源にそれぞれ計算させ、最終的にデータをマージする手法
◆モデル並列化
親モデルを分割し各計算資源に分割し、それぞれのモデルを学習させる。全てのデータで学習が終わった後で一つのモデルに復元する手法。
◆GPUによる高速化の開発環境
・CUDA
GPU上で並列コンピューティングを行うためのプラットフォーム。DeepLearningように提供されている。
・OpenCL
オープンな並列コンピューティングのプラットフォーム。

◆量子化
通常のパラメータの64bit浮動小数店を32bitなどの下位の精度に落とすことでメモリと演算処理の削減を行う手法
◆蒸留
規模の大きな既知のモデルの知識を使い、そのノウハウを軽量なモデルへ継承させる手法。
◆プルーニング
ネットワークが大きくなると大量のパラメータの全てのニューロンが計算精度に寄与しているわけではない。そのため、役に立っていないニューロンを消すことにより、高速・軽量化をすることがでる

#11. 応用モデル
###概要
E資格ではモデルの特徴を問われる問題が多い。
下記でいくつかのモデルを紹介する。

◆MobileNet
画像認識モデルで、高速化・軽量化が適用されたもの。畳み込みの演算方法を工夫している
Depthwise combolutionでは、フィルタを2次元にすることで計算量の削減を実現する

◆DenseNet
画像認識のモデルである。特徴はDense Blockという部分があるのが特徴である。このブロックを通るたびに、画像のチャネルがどんどん増えていく

◆Batch Norm
レイヤー間を流れるデータの分布を、ミニバッチ単位で平均0、分散1になるように正規化する手法。

◆Layer Norm
BatchNormとLayerNormでは、正規化の仕方が違っている。BatchNormは複数の同チャネルごとに正規化するが、LayerNormでは画像ごとに正規化を行う。

◆Instance Norm
Instance Normは一画像の一チャネルごとに正規化する手法。


◆WaveNet
音声波形を生成する深層学習モデルである。この手法では、時系列データに対して畳み込みを適用する。

#12. Transformer
###概要
Attentionを利用することでより精度が高まった一方で、以前としてRNNなどを併用することで生じている問題は解決されないままだった。理由は時系列を逐次的に処理するため、データを並列処理できず、計算の高速化が困難であったからである。高速化を目指した過程でAttention付きCNNも考案されたが、長文の依存関係モデルを構築することが難しいという問題が生じ、結局抜本的な解決には至らなかった。
　それらの問題を乗り越えたのがTransformerである。RNNやCNNなどを利用することを一切やめ、完全にAttention層だけを用いて構築することで、TransformerはRNNやAttention付きSeq2seqなどが抱えていた「高速化できない」という問題や「精度の高い依存関係モデルを構築できない」という問題を解決することに成功した


#13. 物体検知・セグメンテーション
###概要

・R-CNN
位置検出と物体判別（合わせて物体検知）をクリアするための仕組みに、R-CNNと呼ばれるネットワークがある。関心領域の切り出しにCNNではない従来手法（HOGなどの非CNN手法）を用いている。関心領域の切り出しの後に、領域ごとに個別にCNNを呼び出す2段階のモデルで、物体検知を実現している。関心領域の切り出しと切り出した領域の物体認識を同時に行うことが可能になった、高速R-CNNもある。

・SSD
R-CNNではバウンディングボックスを色々と動かしてそのたびにCNNによる演算を行っていたので、１枚の画像から物体検出を行うのにかなりの処理時間がかかってた。一方でSSDでは"Single Shot"という名前が暗示しているように、１度のCNN演算で物体の「領域候補検出」と「クラス分類」の両方を行うことができる。これにより物体検出処理の高速化、CNNを関心領域の切り出しにも使うことによる精度向上を可能にした。

・セマンティックセグメンテーション
R-CNNのような矩形の領域を切り出すのではなく、より詳細な領域分割を得るモデルである。物体認識においては、領域分割を詳細に（ピクセル単位で）行う。通常のCNNは出力層のユニット数が識別すべきカテゴリ数だったが、セマンティックセグメンテーションでは入力画像の画素数だけ出力層が必要になる。すなわち、各画像がそれぞれどのカテゴリーに属するのかを出力する必要があるため、出力層には入力画像の縦ピクセル数××入力画像の横ピクセル数××分類のカテゴリ数の出力ニューロンが必要になる。