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

参考図書
ディープラーニングG検定公式テキスト(翔泳社)
徹底攻略ディープラーニングE資格エンジニア問題集(インプレス)


(1)RNN

RNNは、文章や音声などの時系列データを入力や出力として用いることの出来るNNである。

![day3_RNN外観.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1090373/aaac06df-9b1e-dc8b-507c-5eeda0362b20.png)

順伝播
バイアス:$b$,$c$,活性化関数:$f(x),$g(x)$

```math
\begin{align}
\boldsymbol{u}^t=&\boldsymbol{W}_{(in)}x^t+\boldsymbol{W}z^{t-1}+b\\
\boldsymbol{z}^t=&f(\boldsymbol{u}^t)=f\left(\boldsymbol{W}_{(in)}x^t+\boldsymbol{W}z^{t-1}+b\right)\\
\boldsymbol{v}^t=&\boldsymbol{W}_{(out)}\boldsymbol{z}^t+c\\
\boldsymbol{y}^t=&g(\boldsymbol{v}^t)=g\left(\boldsymbol{W}_{(out)}\boldsymbol{z}^t+c\right)
\end{align}
```

逆伝播

```math
\begin{align}
\frac{\partial E}{\partial \boldsymbol{W}_{(in)}}&=\frac{\partial E}{\partial \boldsymbol{u}^t}\left[\frac{\partial \boldsymbol{u}^t}{\partial \boldsymbol{W}_{(in)}}\right]^T=\delta^t[x^t]^T\\
\frac{\partial E}{\partial \boldsymbol{W}_{(out)}}&=\frac{\partial E}{\partial \boldsymbol{v}^t}\left[\frac{\partial \boldsymbol{v}^t}{\partial \boldsymbol{W}_{(out)}}\right]^T=\delta^{out, t}[z^t]^T\\
\frac{\partial E}{\partial \boldsymbol{W}}&=\frac{\partial E}{\partial \boldsymbol{u}^t}\left[\frac{\partial \boldsymbol{u}^t}{\partial \boldsymbol{W}}\right]^T=\delta^t[z^{t-1}]^T\\
\frac{\partial E}{\partial \boldsymbol{b}}&=\frac{\partial E}{\partial \boldsymbol{u}^t}\frac{\partial \boldsymbol{u}^t}{\partial \boldsymbol{b}}=\delta^t\\
\frac{\partial E}{\partial \boldsymbol{c}}&=\frac{\partial E}{\partial \boldsymbol{v}^t}\frac{\partial \boldsymbol{v}^t}{\partial \boldsymbol{c}}=\delta^{out,t}
\end{align}
```

(2)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)

binary_dim = 8
largest_number = pow(2, binary_dim)
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_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)
        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()
```
結果推移
![day3_バイナリ1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1090373/a5063245-31c1-386a-2431-9d826124a942.png)
![day3_バイナリ2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1090373/c1c6a839-949b-af24-c16a-475ff231a90a.png)
![day3_バイナリ3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1090373/eca275d2-6ff1-af92-960a-9a4cfcdb8992.png)
学習が進むにつれて最適されていくのが分かる。


(3)確認テスト
問題1RNNネットワークには大きく分けて3つの重みがある。1つは入力から現在の中間層を定義する際にかけられる重み、1つは中間層から出力を定義する際にかけられる重みである。残りひとつの重みについて説明せよ。
前の中間層($t-1$)から現在の中間層($t$)への重み

問題2連鎖律の原理を使い、下記の$dz/dx$を求めよ。

```math
\begin{align}
z=t^2\\
t=x+y
\end{align}
```
$$\frac{dx}{dx}=\frac{dx}{dt}\frac{dt}{dx}=2t\times1=2(x+y)$$

問題3下図の$y_1$を、$x$、$ｚ_0$、$z_1$、$w_i$、$w_o$を用いて数式で表せ。ただし、バイアスは$b$とし、活性化関数は$g(x)$とする。
![day3_RNN外観.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1090373/8c4968a3-e093-d96a-aa08-5d8bdb3a13d3.png)

```math
\begin{align}
y_1=g\{W_{out}(x_1 W_{in}+z_0 W+b)\}
\end{align}
```

問題4シグモイド関数を微分した時、入力値が0の時に最大値を取る。その値を答えよ。
0.25

(4)LSTM
LSTMは、Long Short-Term(長,短期) Memory(記憶)という意味のRNN中に用いられるもので、入力,出力,忘却ゲートを持つ。忘却ゲートは不要になった時点でCellにある過去の情報を忘却することで、時系列に従って深くなったために起こりやすくなった勾配消失を防ぐ事が出来る。

```math
\begin{align}
i=\sigma(\boldsymbol{W}_ih_{t-1}+\boldsymbol{U}_ix_t)\\
o=\sigma(\boldsymbol{W}_oh_{t-1}+\boldsymbol{U}_ox_t)
\end{align}
```

```math
\begin{align}
f=\sigma(\boldsymbol{W}_fh_{t-1}+\boldsymbol{U}_fx_t)
\end{align}
```

Cell

```math
\begin{align}
g=tanh(\boldsymbol{W}_gh_{t-1}+\boldsymbol{U}_gx_t)\\
c_t=(c_{t-1}\otimes f)\oplus(g\otimes i)
\end{align}
```

出力

```math
\begin{align}
h_t=tahn(c_t)\otimes o
\end{align}
```

(5)LSTM実装演習

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

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

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

問題2LSTMとCECが抱える問題について、それぞれ簡潔に述べよ。
LSTMは入力、出力、忘却ゲート、CECと、パラメータ数が多く、計算コストが大きいこと。CECは、それ自体が学習能力を持たないこと。

(7)GRU
GRUは、LSTMよりもパラメータ数を減らして計算コストや精度を改善したものである。
 - Update gate: $z_t=\sigma(\boldsymbol{W}^{(z)}x_t+\boldsymbol{U}^{(z)}h_{t-1})$
 - Reset gate: $r_t=\sigma(\boldsymbol{W}^{(r)}x_t+\boldsymbol{U}^{(r)}h_{t-1})$
 - State refresh: Reset gateが0で前回の状態を無視し、Update gateが1で前回の状態を用いる。

(8)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
```

(9)確認テスト
問題1LSTMとGRUの違いを簡潔に述べよ。
GRUはLSTMよりパラメータ数が少ない(4→2)。


(10)BiRNN
BiRNNは、時系列方向と逆方向にも学習、予測を行うものである。双方向へ学習を行うため、潜在変数が2つに増える。

(11)BiRNN実装演習

```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)
    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, 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_merge = du["f"] + du["r"][:, ::-1]
    return du_merge, dWx, dWh, db, dh_0
```


(12)Seq2Seq
Seq2Seqは、系列データの入力から系列データで出力を行うものである。翻訳や文章要約、会話文生成などによく用いられる。学習は系列データを扱うため、RNNで行い、入力を内部状態に変換するエンコーダとそれを出力にするデコーダを持つ構造となる。エンコーダはデコーダの入力となる内部状態を出力し、デコーダは内部状態だけを入力としてエンコーダから受け取り、次の時刻の入力を教師として学習する。

(13)Seq2Seq実装演習

```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                                                                                                                                                         
        self.embedding   = nn.Embedding( input_size, embedding_size )                                                                                                                                                                  
        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 )                                                                                                                                       
        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                                                                                                                                                         
        self.embedding   = nn.Embedding( output_size, embedding_size )                                                                                                                                                                                        
        self.gru         = nn.GRU( embedding_size, hidden_size )                                                                                                                                                                                    
        self.linear         = nn.Linear( hidden_size, output_size )                                                                                                                                                
        self.softmax     = nn.LogSoftmax( dim = 1 )
    def forward( self, _input, hidden ):                                                                                                                                                          
        embedded           = self.embedding( _input ).view( 1, 1, -1 )                                                                                                                                                                            
        relu_embedded      = F.relu( embedded )                                                                                                                                                                                   
        gru_output, hidden = self.gru( relu_embedded, hidden )                                                                                                                                                
        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')                                                                                                                                                                                                                                                                                                                                 
    allow_list = [x and y for (x,y) in zip( input_lang.get_allow_list( max_length ), output_lang.get_allow_list( max_length ) ) ]                                                                                                                                                                                                                                                                                                                                               
    input_lang.load_file( allow_list )
    output_lang.load_file( allow_list )                                                                                                                                                                                                                                                                                                                                                                             
    encoder           = Encoder( input_lang.n_words, embedding_size, hidden_size ).to( device )
    decoder           = Decoder( hidden_size, embedding_size, output_lang.n_words ).to( device )                                                                                                                                                                                                                                                                                                                                                                                   
    encoder_optimizer = optim.SGD( encoder.parameters(), lr=learning_rate )
    decoder_optimizer = optim.SGD( decoder.parameters(), lr=learning_rate )                                                                                                                                                                                                                                                                                                                  
    training_pairs = [ tensorsFromPair( input_lang, output_lang ) for i in range( n_iters ) ]
                                                                                                                                                                                                                                                                                                                                                                                             
    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)                                                                                                                                                                                                                                                                                                                                                                                  
        for i in range( input_length ):
            encoder_output, encoder_hidden = encoder( input_tensor[ i ], encoder_hidden )                                                                                                                                                                                                                                                                                                                                                                                   
        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 += criterion( decoder_output, output_tensor[ i ] )                                                                                                                                                                                                                                                                                                                                                                  
            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 ) )
```

(14)確認テスト
問題1Seq2seqについて説明しているものは？
RNNを用いたEncoder-Decoderモデルの一種であり、機械翻訳などのモデルに使われる。

問題2Seq2seqとHRED、HREDとVHREDの違いを簡潔に述べよ。
Seq2seqは、系列データの入力から系列データの出力を行うものである。HREDそれまでの文脈を入力として系列データを出力するものである。VHREDは、HREDは出力が短くて無難な回答やオウム返しの回答ばかりになるという問題点を改善し、より人間味のある回答を出力するようになったモデルである。


(15)Word2Vec
Word2Vecは、単語などの分散表現を行う2層のNNである。Word2Vecは、Skip-gramとCBOWを取り入れている。Skip-gramは中心語が入力、周りの単語が出力の、CBOWは周りの単語が入力、中心語が出力の教師あり学習である。

(16)Word2Vec実装演習

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


(17)Attention
Attentionは、入出力のどの単語がどれほど関連しているかをを学習するものである。

(18)Attention実装演習

```python
class SimpleAttention(tf.keras.models.Model):

    def __init__(self, depth: int, *args, **kwargs):

        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:

        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)

        logit = tf.matmul(q, k, transpose_b=True)  # [batch_size, q_length, k_length]

        attention_weight = tf.nn.softmax(logit, name='attention_weight')

        attention_output = tf.matmul(attention_weight, v)  # [batch_size, q_length, depth]
        return self.output_dense_layer(attention_output)
```

(19)確認テスト
問題1RNNとWord2Vec、seq2seqとAttentionの違いを簡潔に述べよ。
RNNは時系列データを扱うNNである。Word2vecは単語を2層のNNで分散表現するものである。Seq2seqは、時系列データの入力から時系列データの出力を行うものである。Attentionは時系列データの内容に重みを持たせるものである。


(20)強化学習
強化学習は、行動の結果得られる長期的な報酬を最大化することを目的に学習するものである。
Q学習は行動が状態の価値に与える影響の価値を表す行動価値関数、ある状態の価値を表す状態価値関数の二つの価値関数のうち、行動価値関数を行動した際に更新して学習を行うものである。行動は方策関数によって与えられた確率によって決まる。

(21)AlphaGo

AlphaGo Leeは、
PolicyNetが次の一手を決める方策関数の役割を、ValueNetが勝率を予測する価値関数の役割を果たす。このAlphaGoの学習はPolicyNetの教師あり･強化学習、ValueNetの学習、方策関数によるRollOutの教師あり学習など、様々な種類の学習が行われている。

AlphaGo Zeroは、教師データである人間の棋譜を用いずに学習を行うモデルで、AlphaGo Leeよりも性能が向上している。AlphaGo Leeからの主な変更点はPolicyNetとValueNetが結合している、ショートカットコネクションを持つResidual Networkを取り入れている、RollOutシミュレーションを行わない等である。

(22)軽量･高速化技術
DLのモデルは扱うデータ量や持つパラメータ数が多く、計算を軽量･高速化する必要がある。軽量化は、性能をなるべく保ったまま行う計算量を簡略化し、削減するものである。浮動小数点のBit数を減らす量子化、大規模なモデルで学習して得た知識を用い、教師モデルから軽量な生徒モデルの学習を行う蒸留、重みの小さなノードをある閾値をもって削除するプルーニングなどが軽量化の例である。高速化は、同じ計算量に対してかかる時間を計算資源を有効利用することで短縮するものである。複数の計算資源に対して学習するモデル(親モデル)をコピーし、それぞれ(子モデル)に配分したデータを並列で学習させるデータ並列化、複数の計算資源に対して学習するモデルを分割し、それぞれに同じデータを並列して学習させるモデル並列化、CUDAやOpenCLなどの並列コンピューティングのためのプラットフォームであるGPUによる高速化の開発環境の利用などが高速化である。

(23)応用モデル

MobileNetは、軽量･高速化を利用した画像認識モデルである。が適用されたもの。２次元のカーネルを用いて畳み込むDepthwise Convolutionを用いることで計算量を通常の$O(H\times W\times K\times K\times C\times M)$から$O(H\times W\times K\times K\times C)$へ削減している。カーネルサイズ$1\times1$のフィルタを用いる1x1-Convolutionでは計算量が$O(H\times W\times M\times C)$へ削減される。

DenseNetは前層の出力に入力特徴マップを足し合わせるDense Blockを持つ画像認識モデルである。畳み込みとプーリングでDense Blockを通って増えたチャンネル数を減らしている。

Batch Normalizationは、ミニバッチ(画像データではチャンネル)ごとにデータの平均を0、分散を1に正規化するものである。これによって学習が早く収束し、過学習などの問題を防ぐことができる。

Layer Normalizationは画像ごとにデータの平均を0、分散を1に正規化するものである。入力のスケールや重み行列のスケールシフトに対してロバストになる。Batch Sizeが小さいときのBatch Normalizationの代わりにもなる。

Instance Normalizationは、一つの画像データに対してBatch Normalizationを行うものである。

WaveNetは、系列データに畳み込みを行って学習する音声生成モデルである。

(24)Transformer
Transformerは、Attentionだけで構成されたNLPモデルで、RNNやCNNを用いていない。Self-Attention層とTarget-Source-Attention層の2種類のAttention層が用いられ、並列計算が可能なのでRNNなどの場合よりも高速化されている。また、RNNが役割を果たしていた文脈情報の保持を単語の位置を記憶するPositional Encodingで補っている。また、従来のSource-Target型ではなくQuery-Key-Valueモデルを用いることで正確にアライメントを得る事が出来るようになった。

(25)物体検知･Segmentation

R-CNNは、位置検知にHOGなどのCNN以外を、切出した関心領域ごとにCNNを用いて位置検出とクラス分類の両方を行うモデルである。Faster R-CNNやFastest R-CNNでは、位置検知とそのクラス分類を同時に行う。

SSDは、Single Shot Detecterを意味し、１回のCNN演算のうちに領域候補検出とクラス分類を行う。

Semantic Segmentationは、クラス分類を行う領域をBBではなく、ピクセルによって分割するものである。このため、出力の数はR-CNNに比べて入力の画像データの画素数分倍だけ多くなった。