LoginSignup
1
2

More than 1 year has passed since last update.

深層学習Day3・4(E資格ラビットチャレンジレポート4)

Posted at

この記事はJDLA E資格認定講座のラビットチャレンジの成果レポートである。
なるべく事項そのものだけではなく、初心者が分かりづらい点とその克服法、学習を通じて自分なりに変化した点を盛り込むように心がけた。

深層学習Day3:1.再帰型ニューラルネットワークの概念

(1)概要

 ●RNNとは、時系列データに対応可能なニューラルネットワークのことである。
 ●時系列データとは、時間的順序を追って一定間隔ごとに観察され,しかも相互に統計的依存関係が認められるようなデータの系列である。(例:音声データ・テキストデータなど)
 ●再帰型ニューラルネットワークでは、時間方向に状態を引き継ぎながら計算を進めることができるため、自然言語や音声などのように、時間方向に順番にならんでいるデータを扱うタスクに向いている。

 ●RNNの数学的記述
   $u^t=W_(in)x^t+w_z^(t−1)+b$
   $z^t=f(W_(in)x^t+Wz^(t−1)+b)$
    $v^t=W_(out)z^t+c$
   $y^t=g(W_(out)z^t+c)$

(2)確認テスト

 ●RNNのネットワークには大きくわけて3つの重みがある。1つは入力から現在の中間層を定義する際にかけられる重み、1つは中間層から出力を定義する際にかけられる重みである。残り1つの重みについて説明せよ。
  ⇒前の中間層からの次の中間層へ至る際にかけられる重み

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

(3)実装演習

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:2.LSTM

◎概要

 ●RNNの課題は、時系列を遡れば遡るほど、勾配が消失していくので長い時系列の学習が困難であったこと。この解決策として、活性化関数の選択、重みの初期値設定、バッチ正規化等と別にネットワークの構造自体を変えて解決したものがLSTMである。
 ●勾配消失問題とは、誤差逆伝播法が下位層に進んでいくに連れて、勾配がどんどん緩やかになっていく。そのため、勾配降下法による、更新では下位層のパラメータはほとんど変わらず、訓練は最適値に収束しなくなる。
 ●考えることと記憶することを分離し、記憶だけをやり、学習はしないのがCECである。

 ※ シグモイド関数を使うところとtanh関数を使うところの区別が最初は難しかった。丸暗記するしかないのかと思っていたところ、下記のサイトを読んで腑に落ちた。要は意味のあるものを通す時にはtanh、単にデータとどれだけ通すだけという単純な門番の役割の時はシグモ関数である。この点を理解してから改めてLSTM,GRUの図を読むとなるほど、そういうことか、となる。やはり根本の部分を理解することが重要であることを改めて感じた。

tanhの出力は-1.0〜1.0の実数である。この-1.0〜1.0の数値には、何らかのエンコードされた「情報」に対する強弱が表されていると解釈できる。一方、sigmoid関数の出力は0.0〜1.0である。これはデータをどれだけ通すかの割合を表す。そのため多くの場合、ゲートではsigmoid関数、実質的な「情報」を持つデータにはtanh関数が活性化関数として利用される。https://www.takapy.work/entry/2019/01/09/080338

◎確認テスト

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

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

 ●LSTMとCECの違いを簡潔に述べよ。
  ⇒LSTMは複雑であり計算量が大きい。CECは計算能力がなく入力・出力。忘却ゲートが必要。

 ●LSTMとGRUの違いを簡潔に述べよ。
  ⇒計算量 LSTM>GRU

◎実装演習

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

 ◎演習チャレンジ

 ●以下のプログラムはLSTMの順伝播を行うプログラムである。ただし_sigmoid関数は要素ごとにシグモイド関数を作用させる関数である。(け)にあてはまるのはどれか。
   ⇒ (3)input_gate * a + forget_gate * c
    a:入力データにシグモイド関数を作用させた値、c:シグモイド関数を作用させた後、出力ゲートを作用させる値と考えられるため、cに入る値が推測される 

深層学習Day3:3.GRU

◎概要

 ●従来のLSTMでは、パラメータが多数存在していたため、計算負荷が大きいという課題があった。GRU(Gated recurrent unit)はゲート付き回帰型ユニットと呼ばれ、LSTMをシンプルにしたモデルにあり、LSTMより高速に動作する。GRUでは、そのパラメータを大幅に削減し、精度は同等またはそれ以上が望める様になった。

◎確認テスト

 ●LSTMとCECが抱える課題について、それぞれ簡潔に述べよ。
  ⇒LSTM:パラメータ数が多く、計算負荷が高い
   CEC:ニューラルネットワークの学習特性がない

 ●LSTMとGRUの違いを簡潔に述べよ。
  ⇒ LSTMでは、パラメータが多数存在していたため、計算負荷が大きかったが、GRUでは、パラメータを大幅に削減し、精度は同等またはそれ以上が望める様になっており、計算負荷が低い。

深層学習Day3:4.双方向RNN

◎概要

 ●時間軸に対して未来方向と過去の方向のRNNを組み合わせたものを双方向RNNと呼ぶ。
 ●過去の情報だけでなく、未来の情報を加味することで、精度を向上させるためのモデルである。
 ●実用例:文章の推敲や、機械翻訳等

 ※ 時間に対して順方向に隠れ状態を伝播させる層と、逆方向の層は結合していないことに注意が必要。問題集をやる前はなんとなく図を見て、上記の2つの層がくっついているようにうろ覚えをしていた。

◎演習チャレンジ

  以下は双方向RNNの順伝播を行うプログラムである。順方向については、入力から中間層への重みW_f, 一ステップ前の中間層出力から中間層への重みをU_f、逆方向に関しては同様にパラメータW_b, U_bを持ち、両者の中間層表現を合わせた特徴から出力層への重みはVである。_rnn関数はRNNの順伝播を表し中間層の系列を返す関数であるとする。(か)にあてはまるのはどれか
    ⇒ (4)np.concatenate([h_f, h_b[::-1]], axis=1)
      順方向と逆方向で同じ時間の値は同じ場所に入るように axis=1で配列を結合し
      特徴量を失わずに1つの特徴ベクトルを得ている

◎実装演習

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

深層学習Day3:5.Seq2Seq

◎概要

 
 ●Seq2seqとは、
  ①系列(Sequence)を入力として系列を出力するもの
  単語列などの系列(sequence)を受け取り、別の系列へ変換するモデルのことを「系列変換モデル」という。(「RNNエンコーダ・デコーダ」や「seq2seq(sequence-to-sequence)」などと呼ばれることもある)
  ②入力系列がEncode(内部状態に変換)され内部状態からDecode(系列に変換)するもの
  ③Encoder-Decoderモデルの一種
  ④機械翻訳、対話、質問応答、文書要約などの出力として文を作成するようなタスクで用いられる。

◎確認テスト

 ●下記の選択肢から、seq2seqについて説明しているものを選べ。
  ⇒ (2)RNNを用いたEncoder-Decoderモデルの一種であり機械翻訳などのモデルに使われる。
    (1)は双方向RNN
    (3)は構文木
    (4)はLSTM

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

 ●確認テスト
  VAEに関する下記の説明文中の空欄に当てはまる言葉を答えよ。
  自己符号化器の潜在変数に____を導入したもの。
  ⇒ 確率分布

◎演習チャレンジ

 ●機械翻訳タスクにおいて、入力は複数の単語から成る文(文章)であり、それぞれの単語はone-hotベクトルで表現されている。Encoderにおいて、それらの単語は単語埋め込みにより特徴量に変換され、そこからRNNによって(一般にはLSTMを使うことが多い)時系列の情報をもつ特徴へとエンコードされる。以下は、入力である文(文章)を時系列の情報をもつ特徴量へとエンコードする関数である。ただし_activation関数はなんらかの活性化関数を表すとする。(き)にあてはまるのはどれか。
  ⇒(1)E.dot(w)
  E(embed_size, vocab_size)、w(vocab_size)から計算できるdot積は(1)になるため。
 

◎実装演習

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

深層学習Day3:6.word2vec

◎概要

 ●word2vecとは、embedding表現を得る手法(単語をベクトル表現する手法)の1つであり、CBOW(Continuous Bag-of-Words Model)とskip-gramという2つのモデルがある。
  ・CBOW:単語周辺の文脈から中心の単語を推定
  ・skip-gram:中心の単語からその文脈を構成する単語を推定
   

多くの場合、CBOWよりskip-gramの方が良い結果が出る。特にコーパスが大規模になるにつれ、低頻出の単語や類推問題でskip-gramの方がよい。(ゼロつく②p125)

 ●学習データからボキャブラリを作成すると辞書の単語数だけできあがる。これを入力層のone-hotベクトル(ボキャブラリ数)×任意の単語ベクトル次元数の重み行列とすることで大規模データの分散表現の学習を現実的な計算速度とメモリ量で実現可能にした。 

深層学習Day3:7.Attention Mechanism

◎概要

 ●Attention Mechanismとは入力と出力のどの単語が関連しているのかの関連度を学習する仕組みである。
 ●seq2seq は、固定次元ベクトルの中に入力しなければならないため長い文章への対応が難しい。文章が長くなるほどそのシーケンスの内部表現の次元も大きくなっていくため、その解決策がAttention Mechanismであり文章より重要な単語を見分けて選択的に隠れ層の状態として用いて処理を行う。
 ●ニューラルネットワークにおいて、何らかの特徴があったときにその特徴のどこを重視すればよいかを学習する機構のことをアテンションという。

(attentionの図解)

 ●RNNでよく用いられるが、CNNにも取り入れられるなどRNNに限ったものではない。
   $y=∑_i α(x_1,x_2 ) y_i$
 ●m個の訓練データ(x_i,y_i )がある場合に、任意の説明変数xに対して予測値yを求める考え方。具体的には、関数αを用いてxとx_iの関係をそれぞれ評価付けし、その関係の強さに応じてy_iの重みづけ平均をとったものを予測値yとする。
 $x$:Query 検索文 
 $x_i$:Key辞書のインデックス
 $y_i$:Value 辞書の本文
 ととらえ、検索文(x)と近いインデックス($x_i$)を探し、そこに記述されている本文($y_i$)を選んで回答する(y)と理解できる。

 ※Attentionはどこに注意するのかという点までは簡単に理解できるが、どのようなMechanismでとなるとにわかに説明することが困難になる。そこで安定の分かりやすさのAlciaさんの動画。分かりにくい用語があった時はまずこのシリーズの動画を見ればおおよそどころか、かなり深い部分もさらっと解説してくれる。

◎確認テスト

 ●RNNとword2vec,seq2seqとattentionの違いを完結に述べよ。
 ⇒RNNは時系列データを扱うNNである。
  Word2vecは単語を2層のNNで分散表現するものである。
  Seq2seqは時系列データの入力から時系列データの出力を行うものである。
  Attentionは時系列データに重みを持たせるものである。

深層学習Day4:1.強化学習

◎概要

 ●強化学習とは、長期的に報酬を最大化できるように環境のなかで行動を選択できるエージェントを作ることを目標とする機械学習の一分野である。

 ●行動の結果として与えられる利益(報酬)をもとに行動を決定する原理を改善していく仕組みである。
 ●価値関数:価値を表す関数としては、状態価値関数と行動価値関数の二種類がある。
 ●方策関数:方策ベースの強化学習手法において、ある状態でどのような行動をとるのかの確立を与える関数のこと。この方策を最大化するように学習を行う。
 ●強化学習の応用例:環境:会社の販売促進部エージェント:プロフィールと購入履歴に基づいて、キャンペーンメールを送る顧客を決めるソフトウェアである。行動:顧客ごとに送信、非送信のふたつの行動を選ぶことになる。報酬:キャンペーンのコストという負の報酬とキャンペーンで生み出されると推測される売上という正の報酬を受ける

 ※強化学習は上記のような図があちこちにあるが、環境、エージェントがひっくり返ったりしていることがあるので、理解を阻害しない意味でも1つの図を使い続けた方がよい。私は上記の図に立ち返って理解するように努めた。特に、状態、報酬が何度も出てくる式では何の話をしているのか分からなくなるので言葉ではなく、図どこに何があるのかをきちんと理解した方がよい。

 ●強化学習のTD解法である、Sarsa(方策オン型)とQ学習(方策オフ型)。
 Q学習:行動価値関数を行動するごとに更新することにより学習を進める方法である。
 

※このSarsaとQ学習の違いはかなり理解が難しいところ。式に入る前に概念の差を理解しておかないとちんぷんかんぷんになってしまう。下記サイトでは非常に分かりやすく解説。
 https://cookie-box.hatenablog.com/entry/2016/04/17/120548


深層学習Day4:2.AlphaGo

◎概要

●PolicyNet(方策関数)

●ValueNet(価値関数)

●Alpha Go の学習
 ①教師あり学習によるRollOutPolicyとPolicyNetの学習
 ②強化学習によるPolicyNetの学習
 ③強化学習によるValueNetの学習
   
 ●RollOutPolicy
  ①PolicyNetよりも多少精度は落ちるが、高速な方策関数
  ②ニューラスルネットワークであるPolicyNetと異なり、線形の方策関数
  ③探索中に高速に着手確率を出すために使用される
  
 ●モンテカルロ木探索
  ①コンピュータ囲碁ソフトでは現在もっとも有効とされている探索法
  ②価値観数を学習させるときに用いる
  ③RollOutPolicyを利用してモンテカルロ木探索を行う

 ◆AlphaGoZero
  ●AlphaGo(Lee) との違い
   ①教師あり学習を一切行わず、強化学習のみで作成
   ②特徴入力からヒューリスティックな要素を排除し、石の配置のみにした
   ③PolicyNetとValueNetを1つのネットワークに統合した
   ④Residual Netを導入した
   ⑤モンテカルロ木探索からRollOutシミュレーションをなくした

 ●ResidualNetwork
  ネットワークにショートカット構造を追加して勾配の爆発、消失を抑える効果を狙ったものであり、そのおかげで深いネットワークでも安定した学習が可能。

 ※ AlphaGOはおそらくE資格の範囲でも最難関の項目。理由は、それまで出てきた技術(とその応用)をこれでもかと組み合わせるから。学習段階だけでSL方策ネットワーク、RL方策ネットワーク、価値ネットワークの学習、実戦段階では探索木、PUCTアルゴリズム、強化学習の理解が必要。完全な理解に達するまではおそらく数年の修行が必要だと思われるが、それでも人類最高の知的ゲームにプログラムで勝つには魔法を使っているわけではなく、レゴのように部分部分では理解可能なパーツを組み立てるとあのAlphaGoも自分で組み立てられるのだ、ということ自体が分かることは深層学習を学ぶ大きな知的醍醐味であると思う。

深層学習Day4:3.軽量化・高速化技術

◎概要

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

※ 最初は革新的な仕組みのような話ばかりでなく、なぜ深層学習で1つの大項目を立ててまで軽量化、高速化を学ぶのかが不思議だった。一通りの学習を終わり、何周かそれぞれの技術を学んでいく中で、深層学習が真に求めていることは、形と内容の両方の単純化、言い換えると、”それって、要は●●だ”っていかに早く、簡単に言えるかということであると気がついた。それが腑に落ちてからは軽量化、高速化自体を目指しているのだから当然重視すべき項目なのだと考えるようになった。

深層学習Day4:4.応用モデル

◎概要

 ●MobileNet(画像を認識するネットワーク)
 ・一般的な畳み込みレイヤーは計算量が多い。
 ・MobileNetsはDepthwise ConvolutionとPointwise Convolutionの組み合わせで軽量化を実現
 ・現在ではMobileNetは第1世代~第3世代まである。

 ●一般的な畳み込みレイヤーは下図のとおりであり、出力マップ全体の計算量は$H×W×K×K×C×M$となる。

深層学習Day4:5.Transformer

◎概要

①Transformer
 ●2017年に導入されたディープラーニングモデルで主に自然言語処理の分野で使用される。
 ●RNNと同様に自然言語などの時系列データを処理するように設計されているがRNNで用いる再帰、CNNで用いる畳み込みは使わない。
 ●Attention層のみで構築される
 ●翻訳やテキストの要約など様々なタスクで利用可能
 ●並列化が容易であり、訓練時間を大きく削減できる
 ●ニューラル機械翻訳の問題点である「長さに弱い」を解消したモデル
 ●BERTTはTransformerを使用している

②Attention (注意機構)
 ●情報量が多くなってきたときに何に注意を払って、何に注意を払わないかを学習的に決定していく
 ●Attentionは辞書オブジェクトであり、query(検索クエリ)に一致するkeyを索引し、対応するvalueを取り出す操作であると見做すことができる。これは辞書オブジェクトの機能と同じである。

 ※ Transfomerもまた様々なパーツの積み上げの上にある技術であるため、前提となっている技術が理解できていないと当然全体で何をしているのかもよく分からなくなる。面白いのは、これまで学習で積み上げてきたCNN,RNNなどの深い層の学習をここでは使わず、アテンションという武器を最大限に使っていることである。RNNに比べると明らかに簡単な仕組みのように思えるのにどうしてこのようによい性能が出せるのか、という問を解きたいと思ったことが学びを深めるきっかけとなった。ただ、精神の概要(言わんとしていること、その表現系としての数式など)は理解できたが、真髄レベルで完全に理解するのはまだまだ修行が必要だと感じる。

深層学習Day4:6.物体検知・セグメンテーション

◎概要

 ●広義の物体認識タスク
  

  ●R-CNNは、位置検知にHOGなどのCNN以外を、切出した関心領域ごとにCNNを用いて位置検出とクラス分類の両方を行うモデルである。Faster R-CNNやFastest R-CNNでは、位置検知とそのクラス分類を同時に行う。
 ●SSDは、Single Shot Detecterを意味し、1回のCNN演算のうちに領域候補検出とクラス分類を行う。

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

 ※ セマンティックセグメンテーションも深層学習の勉強を始める前は勝手に人の顔を判定している画像を見て魔法のように思っていた。内実は数式(それなりに難しいことは難しいが理解できないほどでもない)で、位置決定と物体認識に分けて行い、それらを繰り返して適当な判断に結びつける、という道具の組み合わせの妙を感じた。深層学習全体を通じてだが、複雑怪奇な珍奇な必殺技を繰り出しているわけではなく、基本的な技をうまく組み合わせると創造もしないような効果が生まれるという創発(Emergence)的な不思議さと面白さを感じた。

参考文献

 ●JDLA E資格シラバス最新版(2020)
 https://www.jdla.org/wp-content/uploads/2019/09/JDLA_E%E3%82%B7%E3%83%A9%E3%83%90%E3%82%B9_2020%E7%89%88.pdf
 ●「深層学習」(Ian Goodfellow,Yoshua Bengio,Aaron Courville,2016)
 ●「ゼロからつくるPyhon機械学習プログラミング入門」(八谷大岳・講談社)
 ●「徹底攻略ディープラーニングE資格問題集 第2版」(インプレス) 
 ●「ゼロからつくるPyhon機械学習プログラミング入門」(八谷大岳・講談社)
 ●「ゼロから作るDeep Learning1,2,3」(斉藤康毅・オライリー)
 ●「Python機械学習プログラミング 達人データサイエンティストによる理論と実践」(インプレス)
 ●「徹底攻略ディープラーニングE資格問題集 第2版」(インプレス) 

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2