🐈

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

2022/03/20に公開

Section1:再帰型ニューラルネットワークの概念

1-1 要点

  • 再帰型ニューラルネットワーク(RNN)とは、時系列データに対応可能なネットワークである
  • 音声データやテキストデータなどの時系列データに用いられる
  • 時系列モデルを扱うには、初期の状態と過去の時間t-1の状態を保持し、そこから次の時間でのtを再帰的に求める再帰構造が必要になる。

1-2 実装

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)

# 勾配
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])
        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()

1-3 確認テスト

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

中間層から中間層への重み

  • 連鎖率の原理を使い、dz/dxを求めよ
    $$
    \begin{align}
    z = t^2
    \end{align}
    $$
\begin{align} t = x + y \end{align}
  • 図のy1をx・z0・z1・win・w・woutを用いて数式で表せ
    ※バイアスは任意の文字で定義せよ。※また中間層の出力にシグモイド関数g(x)を作用させよ

Section2:LSTM

2-1 要点

  • RNNの課題として時系列を遡れば登るほど勾配が消失していくため、長い時系列の学習が困難
  • RNNの勾配消失の解決方法として、構造自体を変えたのがLSTM
  • 勾配爆発とは、勾配が層を逆伝播するごとに指数関数的に大きくなっていくこと

2-2 実装

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

2-3 確認テスト

  • シグモイド関数を微分した時、入力値が0の時に最大値をとる。その値として正しいものを選択肢から選べ

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

忘却ゲート

Section3:GRU

3-1 要点

  • GRUとは、従来のLSTMではパラメータが多数存在していたため、計算負荷が大きかったが、GRUではそのパラメータを大幅に削減し、精度は同等またはそれ以上が望める様になった構造

  • メリットとして、計算負荷が低い

3-2 実装

def gru(x, h, W_r, U_r, W_z, U_z, W, U):
  # リセットゲート
  r = _sigmoid(x.dot(W_r.T) + h.dot(U_r.T))
  # 更新ゲート
  z = _sigmoid(x.dot(W_z.T) + h.dot(U_z.T))

  # 仮h
  h_bar = np.tanh(x.dot(W.T) + (r * h).dot(U.T))
  # h_{t}
  h_new = (1 - z) * h + z * h_bar
  return h_new

3-3 確認テスト

  • LSTMとCECが抱える課題について、それぞれ簡潔に述べよ

    • LSTM
      パラメータ数が多く、計算量も多い
    • CEC
      勾配が1で、単体では学習できない
  • LSTMとGRUの違いを簡潔に述べよ

    • LSTM:CECと入力、出力、忘却ゲートで構成され、パラメータ数が多いため計算負荷が高い
    • GRU:更新、リセットゲートのみで構成され、計算負荷が低い

Section4:双方向RNN

4-1 要点

  • 双方向RNNとは過去の情報だけでなく、未来の情報を加味することで、精度を向上させるためのモデル

  • 文章の推敲や機械翻訳等で使われている

  • 過去に使われている単語から未来で使われている単語も利用することができるので自然言語処理に適している

4-2 実装

def bindirectional_rnn_net(xs, W_f, U_f, W_b, U_b, V):
  # W_f, U_f:入力から中間層、前の中間層から今の中間層の順方向の重み
  # W_b, U_b:W_f, U_fの逆方向
  # V:順方向、逆方向の中間層から出力層の重み
  xs_f = np.zeros_like(xs)
  xs_b = np.zeros_like(xs)

  for i, x in enumerate(xs):
    xs_f[i] = x
    xs_b[i] = x[::-1]

  hs_f = _rnn(xs_f, W_f, U_f)
  hs_b = _rnn(xs_b, W_b, U_b)
  hs = [np.concatenate([h_f, h_b[::-1]], axis=0)] for h_f, h_b in zip(hs_f, hs_b)
  ys = hs.dot(V.T)
  return ys

4-3 演習問題

np.concatenate([h_f, h_b[::-1]], axis = 1)

Section5:Seq2Seq

5-1 要点

  • Seq2Seqとは、EncoderとDecoderの一種
  • Seq2seqの具体的な用途として、機械対話や機械翻訳などに使用されている
  • Encoder RNNはユーザーがインプットしたテキストデータを、単語等のトークンに区切って渡す構造
  • Decoder RNNはシステムがアウトプットデータを、単語等のトークンごとに生成する構造

5-2 実装

def encode(words, E, W, U, b):
  # words:文。各単語はone-hotベクトル
  # E:単語の埋め込み行列
  # W:重み
  # U:重み
  # b:バイアス
  hidden_size = W.shape[0]
  h = np.zeros(hidden_size)

  for w in words:
    e = E.dot(w)
    h = _activation(W.dot(e) + U.dot(h) + b)
  return h

5-3 確認テスト

  • seq2seqについて説明しているものを選べ

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

  • seq2seqとHRED、HREDとVHREDの違いを簡潔に述べよ

    • seq2seqとHRED
      文脈を考慮するか否か
    • HREDとVHRED
      当たり障りのない回答が多いHREDを解決したのがVHRED
  • VAEに関する下記の説明文中の空欄に当てはまる言葉を答えよ
    自己符号化器の潜在変数に____を導入したもの

確率分布

Section6:Word2vec

6-1 要点

  • RNNでは、単語のような可変長の文字列をニューラルネットワークに与えることはできない
  • Word2vecとは、文字を分散表現ベクトルに変換する方法
  • メリットとして、大規模データの分散表現の学習が、現実的な計算速度とメモリ量で実現可能になる

6-2 実装

なし

6-3 確認テスト

確認テストはないため、省略

6-4 関連記事

  • word2vecにはスキップグラムとCBOWの2つの手法がある
    • スキップグラム:ある単語を与えて周辺の言語を予測するモデル
    • CBOW:周辺の単語を与えて単語を予測するモデル

入力1つに対して出力が複数個か、入力が複数個にたいして出力が1つかの違いがある

Section7:Attention Mechanism

7-1 要点

  • seq2seq の問題は長い文章への対応が難しく、2単語でも100単語でも固定次元ベクトルの中に入力しなければならない
  • Attention Mechanismとは、「入力と出力のどの単語が関連しているのか」の関連度を学習する仕組み

7-2 実装

なし

7-3 確認テスト

  • RNNとword2vec、seq2seqとAttentionの違いを簡潔に述べよ
    • RNNとword2vec
      word2vecは単語の分散表現ベクトルを得る手法でRNNの一種である
    • seq2seqとAttention
      時系列データの関連性に重みを付けてseq2seqの課題を解決したのがAttention

7-4 関連記事

  • 日本語では注意機構と言われる。例えば英語から日本語に翻訳する場合、どの英単語がどの日本語の単語に訳されているか?というのに注意しながら学習するモデル

Discussion

ログインするとコメントできます