【DeepLearning特訓】VAE 変分自己符号化器
E資格向けの自習アウトプット
自分用メモ
VAE(変分自己符号化器)は、生成モデルに対するアプローチの一つ
生成モデルとは、「訓練データを生成する確率 p(x) を求めたい」という分野で、訓練データを元にして特徴を捉えて訓練デーアセットに似たデータを生成することができるようになる
あるアニメのキャラを訓練データとして与えたら、そのアニメに出てきそうなキャラを作ってくれるみたいなイメージかな、かな?
オートエンコーダ
教師なし学習のひとつ。なので学習には訓練用データだけで用意すればよい
入力データを Encoder 低次元に圧縮して、Decoder で復元して生成データを作る 潜在変数 𝑧(特徴量)を近似推論する
( seq2seq でも見かけた Encoder-Decoder手法 RNNでは系列データの隠れ層の状態を渡していった)
一般的なオートエンコーダの例
VAE: Variational Auto Encoder のアーキテクチャ
VAE では潜在変数 𝑧 にガウス分布(標準正規分布)𝑧 ~ 𝑁(0,1) を仮定する
潜在変数 𝑧 を確率分布に押し込める
VAEの順伝播
Encoder で平均 μ と分散 σ を出力、正規分布パラメータ(μ,σ)で潜在変数 𝑧 をランダムサンプリングする
Encoder:CNNを使うことが多い
潜在変数: 𝑧=𝜇+𝜎∗𝜀 (𝜀 は平均0、分散1正規分布の乱数)
Reparameterization Trick という手法で、モデルはepsilon(𝜀)を通じることで確率を維持しながら、エンコーダーの勾配をそれぞれ 平均 μ と分散 σ を介して逆伝播できる 。
Decoder:潜在変数 𝑧 から 生成データを出力、CNNで戻す
VAE の損失関数
これが非常にむつかしい。。。。
・入力データと生成データの負の対数尤度(クロスエントロピー)
・潜在変数 𝑧 の分布と符号化された潜在変数 𝑧 の分布のカルバックダイバージェンス
難しくてちょっと何言ってるわからんけど、この2つの和が総損失ということらしい
更に、確率分布をもとめるのは変分下限(わからん)の最大化に帰着するという
更に更に、変分下限の最大化はKLダイバージェンスの最小化と等しい(らしい)
対数尤度とかカルバックダイバージェンスとかから導出の式は、ちょっとややこしいので、ひとまずそこから導きだされるこの式だけは覚えておくことにする
第一項:元データと生成データの一致度、生成の精度、大きくしたい
第二項:潜在変数 𝑧 の分布(平均 0, 分散 1)と符号化された潜在変数 𝑧 の分布(平均 μ , 分散 σ )の差異、正則化項、小さくしたい
TensorFlow で実装しながら頭を整理
TensorFlow公式にチュートリアルがあるので、これに沿いながら
Convolutional Variational Autoencoder | TensorFlow Core
モジュールインポート
import numpy as np import tensorflow as tf from tensorflow.keras.datasets import mnist import matplotlib.pyplot as plt
データセットロード
いつもの MNIST を使う
train_size = 60000 batch_size = 32 test_size = 10000 (x_train, t_train), (x_test, t_test) = mnist.load_data() x_train = x_train.astype('float32') / 255. #各ピクセルの値を[0,1]に制限 x_train = x_train.reshape(x_train.shape + (1,)) #(データ数, 28, 28)→(データ数, 28, 28, 1)へ変換(チャネル数を追加) x_train = np.where(x_train > .5, 1.0, 0.0).astype('float32') x_test = x_test.astype('float32') / 255. #各ピクセルの値を[0,1]に制限 x_test = x_test.reshape(x_test.shape + (1,)) x_test = np.where(x_test > .5, 1.0, 0.0).astype('float32') train_dataset = tf.data.Dataset.from_tensor_slices(x_train).shuffle(train_size).batch(batch_size) test_dataset = tf.data.Dataset.from_tensor_slices(x_test).shuffle(train_size).batch(batch_size)
ネットワーク部分の定義クラス
Encoderネットワーク:畳み込みを2層分+1層
Decoderネットワーク:Encoderと同様に
class CVAE(tf.keras.Model): def __init__(self, latent_dim): super(CVAE, self).__init__() self.latent_dim = latent_dim self.encoder = tf.keras.Sequential([ tf.keras.layers.InputLayer(input_shape=(28,28,1)), tf.keras.layers.Conv2D(filters=32, kernel_size=3, strides=(2,2), activation='relu'), # [13,13,32] tf.keras.layers.Conv2D(64, 3, strides=(2,2), activation=tf.nn.relu), # [6,6,64] tf.keras.layers.Flatten(), # [2304] tf.keras.layers.Dense(latent_dim + latent_dim) # [4] ]) self.decoder = tf.keras.Sequential([ tf.keras.layers.InputLayer(input_shape=(latent_dim)), tf.keras.layers.Dense(units=7*7*32, activation=tf.nn.relu), tf.keras.layers.Reshape(target_shape=(7,7,32)), tf.keras.layers.Conv2DTranspose(64, 3, strides=2, padding='same', activation='relu'), tf.keras.layers.Conv2DTranspose(32, 3, strides=2, padding='same', activation='relu'), tf.keras.layers.Conv2DTranspose(1, 3, strides=1, padding='same') ]) # 学習後のデータ生成用 @tf.function def sample(self, eps=None): if eps is None: eps = tf.random.normal(shape=(100, self.latent_dim)) return self.decode(eps, apply_sigmoid=True) # q(z|x) def encode(self, x): mean, logvar = tf.split(self.encoder(x), num_or_size_splits=2, axis=1) return mean, logvar # 潜在変数の算出 def reparameterize(self, mean, logvar): eps = tf.random.normal(shape=mean.shape) return eps * tf.exp(logvar * .5) + mean # p(x|z) def decode(self, z, apply_sigmoid=False): logits = self.decoder(z) if apply_sigmoid: probs = tf.sigmoid(logits) return probs return logits
定義したネットワークを確認
latent_dim = 2 # 潜在空間の次元 model = CVAE(latent_dim) model.encoder.summary(), model.decoder.summary()
Model: "sequential" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= conv2d (Conv2D) (None, 13, 13, 32) 320 _________________________________________________________________ conv2d_1 (Conv2D) (None, 6, 6, 64) 18496 _________________________________________________________________ flatten (Flatten) (None, 2304) 0 _________________________________________________________________ dense (Dense) (None, 4) 9220 ================================================================= Total params: 28,036 Trainable params: 28,036 Non-trainable params: 0 _________________________________________________________________ Model: "sequential_1" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= dense_1 (Dense) (None, 1568) 4704 _________________________________________________________________ reshape (Reshape) (None, 7, 7, 32) 0 _________________________________________________________________ conv2d_transpose (Conv2DTran (None, 14, 14, 64) 18496 _________________________________________________________________ conv2d_transpose_1 (Conv2DTr (None, 28, 28, 32) 18464 _________________________________________________________________ conv2d_transpose_2 (Conv2DTr (None, 28, 28, 1) 289 ================================================================= Total params: 41,953 Trainable params: 41,953 Non-trainable params: 0 _________________________________________________________________ (None, None)
オプティマイズと損失関数を定義
このチュートリアル損失関数をアレンジしている模様
これをそのまま採用してみる
optimizer = tf.keras.optimizers.Adam(1e-4) def log_normal_pdf(sample, mean, logvar, raxis=1): log2pi = tf.math.log(2. * np.pi) return tf.reduce_sum(-.5 * ((sample - mean)**2. * tf.exp(-logvar) + logvar + log2pi), axis=raxis) def compute_loss(model, x): mean, logvar = model.encode(x) z = model.reparameterize(mean, logvar) x_logit = model.decode(z) cross_loss = tf.nn.sigmoid_cross_entropy_with_logits(logits=x_logit, labels=x) logpx_z = -tf.reduce_sum(cross_loss, axis=[1,2,3]) logpz = log_normal_pdf(z, 0., 0.) logqz_x = log_normal_pdf(z, mean, logvar) return -tf.reduce_mean(logpx_z + logpz - logqz_x) @tf.function def train_step(model, x, optimizer): with tf.GradientTape() as tape: loss = compute_loss(model, x) gradients = tape.gradient(loss, model.trainable_variables) optimizer.apply_gradients(zip(gradients, model.trainable_variables)) return loss
生成データの画像保存関数
def generate_and_save_images(model, epoch, test_sample): mean, logvar = model.encode(test_sample) z = model.reparameterize(mean, logvar) predictions = model.sample(z) fig = plt.figure(figsize=(4,4)) for i in range(predictions.shape[0]): plt.subplot(4, 4, i+1) plt.imshow(predictions[i, :, :, 0], cmap='gray') plt.axis('off') plt.savefig('output/image_at_epoch_epoch{:04d}.png'.format(epoch)) plt.show()
学習前の生成画像を一枚保存
シャッフルされたデータセットから 16枚抜き出している
num_examples_to_generate = 16 assert batch_size >= num_examples_to_generate for test_batch in test_dataset.take(1): test_sample = test_batch[0:num_examples_to_generate, :,:,:] generate_and_save_images(model, 0, test_sample)
学習前前なので、当然わけわからん画像が生成される
10 エポック分トレーニング回してみる
epochs = 10 for epoch in range(1, epochs +1): for x_train in train_dataset: train_step(model, x_train, optimizer) loss = tf.keras.metrics.Mean() for x_test in test_dataset: loss(compute_loss(model, x_test)) elbo = -loss.result() print('Epoch: {}, Test set ELBO: {}, time elapse for current epoch: '.format(epoch, elbo)) generate_and_save_images(model, epoch, test_sample)
エポック 1回目から何かそれっぽい画像が生成され始める
エポック 10回目で生成されたデータ
さいごに
こんな感じで、与えた訓練データに近い画像が生成されてきました
試験が終わったら、もっといろいろいじってみたい
とにかく損失関数の理解がむずかしく、実装にも時間がかかった。。
TensorFlow 自体もこれでちょっと慣れたかな
次は GAN
【DeepLearning特訓】RNN応用 seq2seq編
E資格向けの自習アウトプット
自分用メモ
seq2seq は Encoder-Decoderモデルとも呼ばれている
RNNを使って、系列データを固定次元ベクトルへ変換(エンコード)、逆に固定次元ベクトルから系列を生成できる(デコード)
エンコーダ:系列データをある規則に基づいて変換する(固定長ベクトルへ)
デコーダ:系列データへ変換(可変ベクトルへ)
日本語から英語への翻訳で考えてみると
seq2seq の構造
エンコーダでは、各タイムステップの出力は使わず隠れ層の出力を伝播させる、最終単語の隠れ層の出力をデコーダに渡す
区切り文字
エンコーダとデコーダの構造が違っても良い
seq2seq の問題点
エンコードの出力は固定長ベクトル
➔入力系列が長すぎるとうまくいかない
Attention Mechanism
エンコーダの各ステップの出力を利用する
「入力と出力でどの単語が関連しているか」という対応関係を seq2seq に学習させる
Encoderの改良
最後の隠れ状態だけを Decoder に渡していたのを、入力される文章のすべてステップの出力の集合を作る
「ひとつの固定長ベクトル」という制約から開放
Decoder の改良
各ステップでRNNの出力結果を用いて重要度を算出する
終わりに
seq2seq からの、Attention Mechanism は非常に強力らしい
ただ、numpy で書いていくにはちょっと面倒なので、また TensorFlow Keras などでサクッと試してみようかな
【DeepLearning特訓】RNN応用 GRU編
E資格向けの自習アウトプット
自分用メモ
GRU(Gated Recurrent Unit)は、LSTM を単純化したモデル
LSTMはパラメータが多く学習に時間がかかので、パラメータを減らして計算量を減らす工夫
ゲートを2つに減らし、内部状態をなくした
・reset ゲート (r):過去の隠れ状態をどれだけ無視するか
・update ゲート (z):過去の隠れ状態を更新する役割
GRUの構造
行列の内積を計算するところと、要素積(アダマール積)が混ざっているの要注意
Python に落とし込むと理解しやすい(順伝播まで)
コピペで試せます
# シグモイド def sigmoid(x): return 1 / (1 + np.exp(-x)) # GRUモデル # N:バッチサイズ、D:入力単語数、H:中間層の出力次元数 class GRU: def __init__(self, wx, wh, b): self.params = wx, wh, b # # wx[D,3H], wh[H,3H], b[3H] 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(h_prev, whz) + np.dot(x, wxz) + bz) # updateゲート r = sigmoid(np.dot(h_prev, whr) + np.dot(x, wxr) + br) # resetゲート h_hat = sigmoid(np.dot(r*h_prev, whh) + np.dot(x, wxh) + bh ) h_next = (1-z) * h_prev + z * h_hat return h_next
適当な入力とパラメータで実行
import numpy as np # 入力を適当に定義 x = np.arange(25).reshape(5,5) h_prev = np.ones((5,10)) # 重みを初期化 wx = np.random.randn(5, 30) wh = np.random.randn(10, 30) b = np.zeros(30) # モデルインスタンス gru = GRU(wx, wh, b) # 順伝播 gru.forward(x, h_prev)
array([[7.96586166e-01, 9.79622537e-03, 9.99877107e-01, 9.76598775e-01, 9.98707164e-01, 3.56091373e-01, 9.99988035e-01, 2.55284290e-01, 9.89543388e-01, 9.96706424e-01], [2.47451855e-03, 5.64910120e-03, 9.99999999e-01, 9.99999983e-01, 1.00000000e+00, 4.40713164e-03, 1.00000000e+00, 9.99995693e-01, 9.82514705e-01, 1.00000000e+00], [5.47933744e-04, 3.69982598e-03, 1.00000000e+00, 1.00000000e+00, 1.00000000e+00, 3.66723137e-05, 1.00000000e+00, 1.00000000e+00, 9.70604673e-01, 1.00000000e+00], [1.66534473e-04, 2.42152843e-03, 1.00000000e+00, 1.00000000e+00, 1.00000000e+00, 3.04634406e-07, 1.00000000e+00, 1.00000000e+00, 9.50828177e-01, 1.00000000e+00], [5.06729678e-05, 1.58418307e-03, 1.00000000e+00, 1.00000000e+00, 1.00000000e+00, 2.53141448e-09, 1.00000000e+00, 1.00000000e+00, 9.19010082e-01, 1.00000000e+00]])
【DeepLearning特訓】RNN応用 LSTM編
E資格向けの自習アウトプット
自分用メモ
LSTM:Long Short-Term Memory(長短期記憶)は、RNNで系列が長くなっていった時におきてしまう「長期依存性の課題」への解決アプローチの一つです
長期依存性の課題
RNNの弱点として多くの時間ステップでわたって伝播されて勾配が消失してしまう「勾配消失問題」があります。
【課題解決アプローチ方法】
スキップ接続:時刻をスキップして直接続、粗い時間スケールを得る
Leakyユニット:前の時刻から接続をα倍、現時刻の入力(1-α)倍する
接続の削除
LSTM:ゲート付きRNN
GRU
LSTM の手法
内部状態という考え方を新しく付与
内部状態に影響を与えるj3つ のゲートを導入して、勾配消失問題に対応
忘却ゲート(f):前の状態の影響率
入力ゲート(i):現時刻の入力データの影響率
出力ゲート(o):現在の状態の影響率
LSTM 順伝播の構造
各ゲートを追加してイメージはこんな感じ
かなりややこしくなってきた
整理のためにも順伝播までを Pythonコードの実装
# シグモイド def sigmoid(x): return 1 / (1 + np.exp(-x)) # LSTMモデル # N:バッチサイズ、D:入力単語数、H:中間層の出力次元数 class LSTM: def __init__(self, wx, wh, b): self.params = [wx, wh, b] # wx[D,4H], wh[H,4H], b[4H] self.grads = [np.zeros_like(wx), np.zeros_like(wh), np.zeros_like(b)] self.cache = None def forward(self, x, h_prev, c_prev): wx, wh, b = self.params N, H = h_prev.shape A = np.dot(h_prev, wh) + np.dot(x, wx) + b # [N,4H] # slice f = sigmoid(A[:, :H]) # 忘却ゲート [N,H] i = sigmoid(A[:, 2*H:3*H]) # 入力ゲート [N,H] g = np.tanh(A[:, H:2*H]) o = sigmoid(A[:, 3*H:]) # 出力ゲート [N,H] c_next = f * c_prev + i * g # 現時刻の状態 [N,H] h_next = o * np.tanh(c_next) # 現時刻の出力 [N,H] self.cache = (x, h_prev, c_prev, f, i, g, o, c_next) return h_next, c_next
適当な入力値で実行してみる
import numpy as np # 入力を適当に定義 x = np.arange(25).reshape(5,5) h_prev = np.ones((5,10)) c_prev = np.zeros((5,10)) # 重みを初期化 wx = np.random.randn(5, 40) wh = np.random.randn(10, 40) b = np.zeros(40) # モデルインスタンス lstm = LSTM(wx, wh, b) # 順伝播 lstm.forward(x, h_prev, c_prev)
Peephole Connections
前の内部状態が各ゲートに影響与えるコネクションを追加
さいごに
ここからが自然言語処理の本番だ
実践で使えるRNN応用技術にとりくんでいく
【DeepLearning特訓】RNNモデルの考え方
E資格向けの自習アウトプット
自分用メモ
RNN を利用したモデル構築の考慮事項について
E資格試験対策の意味合いが多し
RNN の学習方法
通時的誤差逆伝播(BPTT: Back Propagation Though Time)
Simple RNNの逆伝播は1つ先の時刻の勾配を含んで計算されていたので、並列で処理できない
つまりメモリコストも大きくなる
中間層同士の接続なので強力ではある
教師強制
訓練時に、前の時刻の正解ラベルを中間層へ入力として与える
並列処理が可能になるが、出力層が必要な情報を全てもっていることは少ないので強力ではない
前日の終値が確実に分かっている、株価とかでは使える
出力回帰(出力層から中間層へ接続)は、RNNほど強力ではない
教師強制で学習できる
有向グラフィカルモデル
求めたいものが出力値の確率であるとき、RNNのモデルは有向グラフィカルモデルといえる
こんなイメージ
RNNを使った「文脈で条件付けされた」系列モデリング
言葉としてややこしいが、文脈=「過去の真の値」と考える
直前の真の値が入力として中間層へ接続される
株価予想もそう(過去の終値が今日には確定している)
深層回帰結合型ネットワーク
RNN の層を深くする方法
・隠れた回帰状態 ➔ 階層別の回帰に分解
・より深い計算 ➔ 中間層でより深い接続
・経路が長くなる ➔ スキップ接続で緩和
再帰型ニューラルネットワーク
RNN のもう一つの一般系と呼ばれている
木構造として扱う、深さを大幅に減らし長期依存を取り扱うのに有利になる可能性がある
最適な木構造をどのように構築するかは未解決の課題
エコーステートネットワーク
RNNで難しい一つ前の時刻からの重み𝑊_ℎ,"現時刻の入力重み" 𝑊_𝑥の学習が難しい
スペクトル半径を3などに固定し、単純な重みに固定する戦略
双方向RNN
出力が入力系列全体に依存していることがある
音声認識:後ろに続く音素によって、直前の音素が決定する
翻訳:言語によって語順が変わってくる
系列全体を参照できる仕組みが必要
逆方向に接続するRNNを追加し、結果をマージ
さいごに
このあたりの考え方は実際にコード組んでみないことには、習得するのが難しそう
【DeepLearning特訓】RNN 入門
E資格向けの自習アウトプット
自分用メモ
RNN(Recurrent Neural Network)回帰結合型ニューラルネットワークは、時系列データを処理するためのニューラルネットワークCNNが格子状のデータを処理するのに特化RNNは系列上の値 𝑥^*1,𝑥^*2,,,,,𝑥^*3 を処理するのに特化、可変長な系列データにも対応
RNN モデル概要
前の時刻の中間層の出力を次の中間層へ渡す
時刻𝑡 の入力データ 𝑥^*4 とすると
(RNNでは縦に描いて横に並べていくことが多くなる)
RNN の中間層の構造
中間層での計算を表すと
計算グラフでそれぞれの要素を書き下してみる
RNN の順伝播
各時刻ごとにAffine変換と損失関数の計算を行って完成
一つ前の時刻の中間層の出力と、この時刻の入力を合わせて計算し
中間層での出力を次の時刻の中間層へ渡す
【DeepLearning特訓】CNN 進化の歴史
E資格向けの自習アウトプット
自分用メモ
CNN発展の歴史を抑えておく
ILSVRC(ImageNet Large Scale Visual Recognition Challenge):画像認識コンペ
で 2012年の トロント大 ヒントン教授の率いるチームが CNN を採用したAlexNetで圧倒的な成績をのしてディープラーニングの火付け役となったと言われています
2012年以降のILSVRCの優勝アーキテクチャと有名どころ
AlexNet(2012) 8層
ZFNet(2013) 8層
GoogleNet(2014) 22層:Inception Moduleで並列化、Auxiliary Loss, Global Average Pooling, Pointwise Convolution
VGG(2014) :深いネットワーク構造が特徴、構造が単純なので応用しやすい
ResNet(2015) 152層:Residual Blockのショートカット構造
DenseNet(2016):Dense Blockで層の出力をショートカット伝搬する、Transition層でダウンサンプリング
SENet(2017) 152層
LeNet
1998年(!?)現Facebook AI ResearchのYann LeCun氏による CNN の元祖
畳み込み、プーリングなどのアーキテクチャは実装済み
活性化関数がシグモイドであることやプーリングがMAXではなくサブサンプリングなのが違い
R-CNN(2013)
Regions with CNN(領域CNN)
Selective Searchで物体がありそうなところにあたりを付けて(Region Proposal)、SVMで分類
CNNは特徴抽出のみなので、分類部分は学習できない
問題点
・複数のモデル個別で学習が必要
・あたりをつけた候補領域の数だけCNNを回すので、実行時間がかかる
・何もない領域にあたりをつけた時の大きく間違ってしまう
ざっくりこんなイメージ
Fast R-CNN (2015)
R-CNN の改良版
候補領域の数の分だけ CNN 回していたのを、まず画像全体に CNN 畳み込んで特徴量マップを抽出し、候補領域で切り抜いた画像部分を特徴量マップを抽出(ROI Pooling)
CNNは一回だけになるので、速くなる
ざっくりこんなイメージ
Faster R-CNN(2015)
Fast R-CNN を更に改良
Fast R-CNN で時間がかかっていたのはほぼ Selective Search で候補領域を指定いいた部分
Selective Search に CNNの特徴マップを使用して物体検知(Regional Proposal Network)
候補領域抽出タスクと分類タスクで損失関数を共有(Multi Task Loss )し End-to-End で学習を実現
ざっくりこんなイメージ
YOLO(2016)
You Only Look Once(You Only Live Onceからもじった)
画像全体をグリッド分割し、各グリッドに対して分類・矩形指定タスクを行う
信頼度スコアを利用して、高速でリアルタイムで物体の「検出」と「識別」を行う
ざっくりこんなイメージ?
とりあえず
CNNに関連する有名どころだけをピックアップ
一旦 CNNを卒業して、自然言語処理へと勉強すすめる