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

参考資料





レポートの参考

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

要点

再帰型ニューラルネットワーク

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

BPTT

  • RNNにおけるパラメータ調整方法の一つで、誤差逆伝播の一種。
  • 数学的に記述すると、以下のようになる

$$
\begin{aligned}
\frac{\partial E}{\partial W_{(in)}}
&= \frac{\partial E}{\partial u^t} \left[\frac{\partial u^t}{\partial W_{(in)}}\right]^T
= \delta^t [x^t]^T \\
\frac{\partial E}{\partial W_{(out)}}
&= \frac{\partial E}{\partial v^t} \left[\frac{\partial v^t}{\partial W_{(out)}}\right]^T
= \delta^{out, t} [z^t]^T \\
\frac{\partial E}{\partial W}
&= \frac{\partial E}{\partial u^t} \left[\frac{\partial u^t}{\partial W}\right]^T
= \delta^{t} [z^{t-1}]^T \\
\frac{\partial E}{\partial b}
&= \frac{\partial E}{\partial u^t} \frac{\partial u^t}{\partial b}
= \delta^{t} \\
\frac{\partial E}{\partial c}
&= \frac{\partial E}{\partial v^t} \frac{\partial v^t}{\partial c}
= \delta^{out, t} \\
u^t
&= W_{(in)} x^t + W z^{t-1} + b \\
z^t
&= f(u^t)
= f(W_{(in)} x^t + W z^{t-1} + b) \\
v^t
&= W_{(out)} z^t + c \\
y^t
&= f(v^t)
= f(W_{(out)} z^t + c)
\end{aligned}
$$

確認テスト1

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

残り1つの重みについて説明せよ。

回答) 前の中間層から現在の中間層を定義する際にかけられる重み($W$)。
RNNにおいて、この$W$が再帰のもととなることから一番重要となる。

確認テスト2

連鎖率の原理を使い、dz/dxを求めよ。

$$
\begin{align}
z = t^2
\end{align}
$$

回答)

$$
t = x + y
$$

確認テスト3

図の$y1$を

$$
x, z_0, z_1, w_{in}, w, w_{out}
$$

を用いて数式で表せ。
※バイアスは任意の文字で定義せよ。
※また中間層の出力にシグモイド関数$g(x)$を作用させよ

回答)

$b$, $c$をバイアスとする。この時、

$$
z_1 = f(w_{in}x + wz_0 + b) \\
y_1 = g(w_{out}z_1+c)
$$

実装演習

RNN

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()
/var/folders/4j/1frmwh6d0f99g0wzp0__nk4w0000gp/T/ipykernel_19800/609039800.py:96: DeprecationWarning: Conversion of an array with ndim > 0 to a scalar is deprecated, and will error in future. Ensure you extract a single element from your array before performing this operation. (Deprecated NumPy 1.25.)
  out_bin[binary_dim - t - 1] = np.round(y[:,t])

Section2 LSTM

要点

  • RNNの課題として時系列を遡れば登るほど勾配が消失していくため、長い時系列の学習が困難
  • RNNの勾配消失の解決方法として、構造自体を変えたのがLSTM
  • LSTMのパーツ説明
    • CEC:RNNでは学習と記憶を同時に行っていたが、学習と記憶を分離してCECに’記憶’のみを担うようにしている。
    • 入力ゲート:今回の入力値と前回の出力値をどれだけ使ってCECに値を渡すかを学習する。
    • 出力ゲート:出力ゲートは今回の入力値と前回の出力値をどれだけ使ってCECの値を使うかを学習する。
    • 忘却ゲート:CECは過去の情報を保持し続けてしまうため、忘却ゲートを設けてCECの値に作用させる。
    • 覗き穴結合:CECの値も使って入力ゲート、忘却ゲート、出力ゲートを更新する仕組み。

参考資料

確認テスト

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

回答) 忘却ゲート

実装

# 実装演習ソースコードがないため、演習チャレンジから実装を記載
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_bar = np.tanh(x.dot(W.T) + (r * h).dot(U.T)) 
  h_new = (1-z) * h + z * h_bar  
  return h_new

Section3 GRU

要点

LSTMではパラメータが多く計算負荷が大きかった.
GRU(Gated Recurrent Unit)ではCECの代わりに以下2つのゲートを用いることで、パラメータ量を減らしながらLSTMと同等かそれ以上の精度を達成している。

  • リセットゲート
  • 更新ゲート

確認テスト1

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

回答)

  • LSTM: パラメータ数が多く、計算負荷が高い。
  • CEC: ニューラルネットワークの学習特性がない。

確認テスト2

LSTMとGRUの違いを簡潔に述べよ。

回答 )

  • LSTM: CECと入力、出力、忘却ゲートで構成され、パラメータが多く計算負荷が大きい。
  • GRU: パラメータを大幅に削減し計算負荷を減らしたが、同等またはそれ以上の精度が望める。

実装演習

# 実装演習ソースコードがないため、演習チャレンジから実装を記載
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_bar = np.tanh(x.dot(W.T) + (r * h).dot(U.T)) 
  h_new = (1-z) * h + z * h_bar  
  return h_new

Section4 双方向RNN

要点

  • 双方向RNNとは過去の情報だけでなく、未来の情報を加味することで、精度を向上させるためのモデル
  • 文章の推敲や機械翻訳等で使われている
  • 過去に使われている単語から未来で使われている単語も利用することができるので自然言語処理に適している

実装演習

# 実装演習ソースコードがないため、演習チャレンジから実装を記載
def bidirectional_rnn_net(xs, W_f, U_f, W_b, U_b, V):
  # W_f, U_f: 順方向の重み
  # W_b, U_b: 逆方向の重み
  # 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)
  # ↓足したりかけたりすると特徴量の意味が変わってしまうためconcatenateでベクトルをまとめる
  hs = [np.concatenate([h_f, h_b[::-1]], axis=1)] for h_f, h_b in zip(hs_f, hs_b)]
  ys = hs.dot(V.T)
  return ys

Section5 Seq2Seq

要点

Seq2Seq
  • Seq2Seqとは、Encoder-Decoderの一種であり、機械対話や機械翻訳などに使用されている
  • Encoder RNNはユーザーがインプットしたテキストデータを、単語等のトークンに区切って渡す構造
  • Decoder RNNはシステムがアウトプットデータを、単語等のトークンごとに生成する構造
HRED, VHRED
  • HRED:一文一答型だったSeq2Seqに Context RNNを組み合わせることで考案された、文脈を含めて文を生成するモデル
    短い応答しかしなくなるという課題があった。
  • VHRED:HREDの課題解決のためにVAEの潜在変数の概念を導入したもの。
オートエンコーダ, VAE
  • オートエンコーダ:教師なし学習の一つ. 入力をエンコーダを潜在変数zを計算し、潜在変数zからデコーダを通じて入力と同じ出力になるように学習する.
  • VAE(Variational AutoEncode): 潜在変数zに平均0,分散1である確率分布を仮定したもの

確認テスト1

下記の選択肢から、seq2seqについて説明しているものを選べ。

  1. 時刻に関して順方向と逆方向のRNNを構成し、それら2つの中間層表現を特徴量として利用するものである。
  2. RNNを用いたEncoder-Decoderモデルの一種であり、機械翻訳などのモデルに使われる。
  3. 構文木などの木構造に対して、隣接単語から表現ベクトル(フレーズ)を作るという演算を再帰的に行い(重みは共通)、文全体の表現ベクトルを得るニューラルネットワークである。
  4. RNNの一種であり、単純なRNNにおいて問題となる勾配消失問題をCECとゲートの概念を導入することで解決したものである。

解答)(2) (1は双方向RNN, 3はRNN, 4はLSTMの説明)

確認テスト2

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

seq2seq2は文脈を無視していたのに対して、HREDは文脈を考慮する。
HREDは文脈を字面だけで考慮していたのに対して、VHREDは同じコンテキストに対しても、字面だけではない多様な返答ができる。

確認テスト3

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

回答) 自己符号化器の潜在変数に「確率分布 $Z \sim N(0, 1)$ 」を導入したもの。

Section6 Word2vec

要点

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

Section7 Attention Mechanism

要点

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

確認テスト

RNNとword2vec、seq2seqとAttention Mechanismの違いを簡潔に述べよ。

解答)

RNNとword2vec

  • RNN : 時系列データを処理するのに適したニューラルネットワーク
  • word2vec : 単語の分散表現ベクトルを得る手法

seq2seqとAttention

  • Seq2Seq : ある時系列データから別の時系列データを得るニューラルネットワーク
  • Attention : 時系列の中身に対して、関連性に重みをつける手法

VQ-VAE

要点

VAEの派生の生成モデル.

  • VAE: 潜在変数zがガウス分布に従うベクトルになるように学習を行う
  • VQ-VAE: 潜在変数zが離散的な数値になるように学習を行う

離散的な潜在変数にするメリット

  • 離散変数の方がデータの特徴を捉えられる.
    例えば画像認識で「車」や「猫」という言葉で表現できる.言葉は離散的な表現であり、より簡潔にデータの特徴を捉えられそう
  • posterior collapseを防げる
    VAEで画像生成するとき、デコーダを通して得たデータの輪郭はぼやけることが多い.離散的な値を使うとそれが防げるらしい

フレームワーク演習)双方向RNN/勾配のクリッピング

要点

  • 実装にはspoken digit datasetを使用する(MNISTの音声版となっている)
  • 音声データの長さを(1000に)揃えてから学習に使用する。(長いものはカットし、短いものは0埋め)
    • map関数を使ってデータ1つ1つを取り出して加工。
  • 音声データは波形としてみることができる。

実装演習

!pip install pydub
import tensorflow_datasets as tfds
import matplotlib.pyplot as plt
import numpy as np
import tensorflow as tf

dataset_train, dataset_valid, dataset_test = tfds.load('spoken_digit', split=['train[:70%]', 'train[70%:85%]', 'train[85%:]'], shuffle_files=True)

original_iter = iter(dataset_train)

# 波形確認
plt.plot(next(original_iter)['audio'])

# データの前処理
NUM_DATA_POINTS = 1000
BATCH_SIZE = 8

def cut_if_longer(el):
  return (
          tf.reshape(
              tf.cond(
                tf.greater(tf.shape(el['audio']), NUM_DATA_POINTS),
                true_fn=lambda: tf.slice(el['audio'], begin=[0], size=[NUM_DATA_POINTS]),
                false_fn=lambda: tf.slice(tf.concat([el['audio'], tf.zeros(NUM_DATA_POINTS, tf.int64)], axis=0), begin=[0], size=[NUM_DATA_POINTS])
              ),
              shape=(-1, 1)
          ),
          [el['label']]
        )

dataset_prep_train = dataset_train.map(cut_if_longer).batch(BATCH_SIZE)
dataset_prep_valid = dataset_valid.map(cut_if_longer).batch(BATCH_SIZE)

sample = next(iter(dataset_prep_valid))
# 双方向RNN(LSTM)を使用したモデル
from tensorflow.keras.models import Sequential
from tensorflow.keras import layers

tf.keras.backend.clear_session()

model_4 = tf.keras.models.Sequential()
model_4.add(layers.Input((NUM_DATA_POINTS, 1)))
model_4.add(layers.Bidirectional(layers.LSTM(64)))
model_4.add(layers.Dense(10, activation='softmax'))

model_4.summary()  # <- モデルの確認をおこなっている
model_4.predict(sample[0]).shape

model_4.compile(
    loss=tf.keras.losses.SparseCategoricalCrossentropy(),
    # 勾配のクリッピングをoptimizerで指定
    optimizer=tf.keras.optimizers.Adam(clipvalue=0.5),
    metrics=['accuracy']
)

model_4.fit(
    dataset_prep_train,
    validation_data=dataset_prep_valid,
)

出力

Model: "sequential"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 bidirectional (Bidirection  (None, 128)               33792     
 al)                                                             

 dense (Dense)               (None, 10)                1290      

=================================================================
Total params: 35082 (137.04 KB)
Trainable params: 35082 (137.04 KB)
Non-trainable params: 0 (0.00 Byte)
_________________________________________________________________
1/1 [==============================] - 1s 824ms/step
219/219 [==============================] - 16s 55ms/step - loss: 2.2950 - accuracy: 0.1377 - val_loss: 2.1688 - val_accuracy: 0.1733
loss / accuracy
47/47 [==============================] - 1s 23ms/step - loss: 2.1688 - accuracy: 0.1733
[2.1688475608825684, 0.1733333319425583]

フレームワーク演習)Seq2Seq

要点

  • Seq2Seq(Encoder-Decoder)モデルを用いたsin-cosの変換を行う.
  • エンコーダーの出力であるstateをデコーダーの入力として使用している
  • state以外はエンコーダーとデコーダーは独立したニューラルネットワークモデルとして作成されている

実装演習

import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt

# データの準備
x = np.linspace(-3 * np.pi, 3 * np.pi, 100)
seq_in = np.sin(x)
seq_out = np.cos(x)

# plt.plot(x, seq_in, label='$y=\sin x$')
# plt.plot(x, seq_out, label='$y=\cos x$')
# plt.legend()
# plt.grid()
# plt.show()

# NUM_ENC_TOKENS: 入力データの次元数
# NUM_DEC_TOKENS: 出力データの次元数
# NUM_HIDDEN_PARAMS: 単純RNN層の出力次元数(コンテキストの次元数にもなる)
# NUM_STEPS: モデルへ入力するデータの時間的なステップ数。
NUM_ENC_TOKENS = 1
NUM_DEC_TOKENS = 1
NUM_HIDDEN_PARAMS = 10
NUM_STEPS = 24

# 学習を行うためのモデルを定義する。
tf.keras.backend.clear_session()

e_input = tf.keras.layers.Input(shape=(NUM_STEPS, NUM_ENC_TOKENS), name='e_input')
_, e_state = tf.keras.layers.SimpleRNN(NUM_HIDDEN_PARAMS, return_state=True, name='e_rnn')(e_input)

d_input = tf.keras.layers.Input(shape=(NUM_STEPS, NUM_DEC_TOKENS), name='d_input')
d_rnn = tf.keras.layers.SimpleRNN(NUM_HIDDEN_PARAMS, return_sequences=True, return_state=True, name='d_rnn')
d_rnn_out, _ = d_rnn(d_input, initial_state=[e_state])

d_dense = tf.keras.layers.Dense(NUM_DEC_TOKENS, activation='linear', name='d_output')
d_output = d_dense(d_rnn_out)

model_train = tf.keras.models.Model(inputs=[e_input, d_input], outputs=d_output)
model_train.compile(optimizer='adam', loss='mean_squared_error')

model_train.summary()

# モデルの定義に合わせて学習用データを準備する。
n = len(x) - NUM_STEPS
ex = np.zeros((n, NUM_STEPS))
dx = np.zeros((n, NUM_STEPS))
dy = np.zeros((n, NUM_STEPS))

for i in range(0, n):
  ex[i] = seq_in[i:i + NUM_STEPS]
  dx[i, 1:] = seq_out[i:i + NUM_STEPS - 1]
  dy[i] = seq_out[i: i + NUM_STEPS]

ex = ex.reshape(n, NUM_STEPS, 1)
dx = dx.reshape(n, NUM_STEPS, 1)
dy = dy.reshape(n, NUM_STEPS, 1)

# 学習を行う。
BATCH_SIZE = 16
EPOCHS = 80

history = model_train.fit([ex, dx], dy, batch_size=BATCH_SIZE, epochs=EPOCHS, validation_split=0.2, verbose=False)

# 学習の進行状況をグラフに描画する。収束していることが読み取れる。
loss = history.history['loss']
plt.plot(np.arange(len(loss)), loss, label='loss')

loss = history.history['val_loss']
plt.plot(np.arange(len(loss)), loss, label='val_loss')

plt.grid()
plt.legend()
plt.show()
Model: "model"
__________________________________________________________________________________________________
 Layer (type)                Output Shape                 Param #   Connected to                  
==================================================================================================
 e_input (InputLayer)        [(None, 24, 1)]              0         []                            

 d_input (InputLayer)        [(None, 24, 1)]              0         []                            

 e_rnn (SimpleRNN)           [(None, 10),                 120       ['e_input[0][0]']             
                              (None, 10)]                                                         

 d_rnn (SimpleRNN)           [(None, 24, 10),             120       ['d_input[0][0]',             
                              (None, 10)]                            'e_rnn[0][1]']               

 d_output (Dense)            (None, 24, 1)                11        ['d_rnn[0][0]']               

==================================================================================================
Total params: 251 (1004.00 Byte)
Trainable params: 251 (1004.00 Byte)
Non-trainable params: 0 (0.00 Byte)
__________________________________________________________________________________________________

推論用モデルの準備〜推論
# 推論を行うためのモデルを構築する。
model_pred_e = tf.keras.models.Model(inputs=[e_input], outputs=[e_state])

pred_d_input = tf.keras.layers.Input(shape=(1, 1))
pred_d_state_in = tf.keras.layers.Input(shape=(NUM_HIDDEN_PARAMS))

pred_d_output, pred_d_state = d_rnn(pred_d_input, initial_state=[pred_d_state_in])
pred_d_output = d_dense(pred_d_output)

pred_d_model = tf.keras.Model(inputs=[pred_d_input, pred_d_state_in], outputs=[pred_d_output, pred_d_state])

# モデルの推論を行う関数を準備する。
def predict(input_data):
  state_value = model_pred_e.predict(input_data)
  _dy = np.zeros((1, 1, 1))

  output_data = []
  for i in range(0, NUM_STEPS):
    y_output, state_value = pred_d_model.predict([_dy, state_value])

    output_data.append(y_output[0, 0, 0])
    _dy[0, 0, 0] = y_output

  return output_data

# 推論の実行
init_points = [0, 24, 49, 74]

for i in init_points:
  _x = ex[i : i + 1]
  _y = predict(_x)

  if i == 0:
    plt.plot(x[i : i + NUM_STEPS], _y, color="red", label='output')
  else:
    plt.plot(x[i : i + NUM_STEPS], _y, color="red")

plt.plot(x, seq_out, color = 'blue', linestyle = "dashed", label = 'correct')
plt.grid()
plt.legend()
plt.show()

フレームワーク演習)data-augmentation

要点

データの水増し(データ拡張)のこと. 画像の場合は、手元にあるデータから擬似的に別の画像を生成して学習データに使うことで画像認識制度を上げる.
具体的な水増し手法には主に以下があり、それらを組み合わせて使用する.

  • 反転: Horizontal Flip, Vertical Flip
  • 回転: Rotate
  • 変形: Random Erasing, Mixup
  • 切り出し: Crop
  • 加工: Contrast, Brightness, Hue(色相)

実装演習

import numpy as np
import tensorflow as tf
import random
import matplotlib.pyplot as plt

def show_images(images):
    """複数の画像を表示する"""
    n = 1
    while n ** 2 < len(images):
        n += 1
    for i, image in enumerate(images):
        plt.subplot(n, n, i + 1)
        plt.imshow(image)
        plt.axis('off')
    plt.show()

! mkdir sample_data
! wget -qnc --no-check-certificate -O ./sample_data/image_origin.jpg \
https://github.com/opencv/opencv/raw/master/samples/data/fruits.jpg

contents = tf.io.read_file("./sample_data/image_origin.jpg")
image_origin = tf.image.decode_jpeg(contents, channels=3)

image = image_origin
show_images([image.numpy()])

# 複数の手法を組み合わせる
def data_augmentation(image):
    image = tf.image.random_flip_left_right(image)  # <- 左右反転
    image = tf.image.random_flip_up_down(image)  # <- 上下反転
    image = tf.image.random_contrast(image, lower=0.4, upper=0.6)  # <- コントラスト変更
    image = tf.image.random_brightness(image, max_delta=0.8)  # <- 輝度変更
    image = tf.image.rot90(image, k=random.choice((0, 1, 2)))  # <- 0度, 90, 180度回転
    image = tf.image.random_hue(image, max_delta=0.1)  # <- 色相変更
    return image

image = image_origin

show_images([data_augmentation(image).numpy() for _ in range(36)])

フレームワーク演習)activate_functions

要点

活性化関数について

ニューラルネットワークの順伝播(forward)では、線形変換で得た値に対して、非線形な変換を行う。非線形な変換を行う際に用いられる関数を、活性化関数という。

中間層に用いる活性化関数

  • ステップ関数
  • シグモイド関数
  • tanh
  • ReLU(勾配書室が発生しにくいため現在主流の関数)
  • Leakey ReLU
  • Swish

出力層に用いる活性化関数

  • シグモイド関数
  • ソフトマックス関数(多クラス分類で使用される)
  • 恒等関数

実装演習

# Swishを実装する

import numpy as np
import matplotlib.pyplot as plt
plt.style.use('ggplot')
%matplotlib inline

beta = 1.0
def sigmoid(x):
    return 1.0 / (1.0 + np.exp(-x))

def swish(x):
    """forward

       Swish
       シグモイド加重線形関数
    """
    return x * sigmoid(beta*x)

def d_swish(x):
    """backward

       derivative of Swish
       シグモイド加重線形関数の導関数
    """
    dx = beta*swish(x) + sigmoid(beta*x)*(1.0 - beta*swish(x)) 
    return dx

x = np.arange(-600, 601, 1) * 0.01
f, d = swish, d_swish
y1, y2 = f(x), d(x)

_, ax = plt.subplots()
# label=を見ると関数のコメントの箇所から"forward", "backward"を抜き出していることがわかる
ax.plot(x, y1, label=f.__doc__.split("\n")[0].strip())
ax.plot(x, y2, label=d.__doc__.split("\n")[0].strip(), linewidth=1.0) 
ax.set_xlabel("$x$")
ax.set_ylabel("$y_{1}=f(x), y_{2}=f^{\prime}(x)$") 
ax.set_title(f.__doc__.split("\n")[2].strip())   # <- 関数のコメントの箇所から"Swish"を抜き出す
ax.legend()

plt.show()

以上