konchangakita

KPSを一番楽しんでいたブログ 会社の看板を背負いません 転載はご自由にどうぞ

【おてがる開発環境をつくろう】まずは Docker Desktop インストール

プログラミング言語いれたら、得てしてアレもコレも追加モジュールいれてなんだか PC自体おかしくなって、全部消してやりなおした経験はでしょうか?
開発環境のお作法なんてのは、本職で毎日さわってないと、すぐ忘れる。。。
忘れたってええじゃない、開発環境をつくること自体が目的になってはいけません
新品のPCでもソッコーで以前までと同じ開発環境をつくれるような、「おてがる」な開発環境を作る


きっかけは AI の勉強をしていく上での勉強用に class="st" Python 環境を作って触ってきました
Python 環境もいろいろ実現方法があり

WindowsPython をそのままインストール
Windows に Anaconda をインストールして、仮想環境上に Python
Linux系 OSで Python インストール
・pyenv を使う
・Docker で python 入りコンテナを使う

すでに 5種類。。。。色々試していくうちに、どれが正しいのかわからないまま、ずいぶんぐちゃぐちゃのボロボロになりました
結果、たどり着いた開発環境を自分の備忘録も含めまとめていきます

こんな感じでまとめていこうかと
【おてがる開発環境をつくろう】
1.まずは Docker Desktop インストール←今ココ
2.Docker であそぶ Python 入りのコンテナつくる
3.コンテナで Jupyter Lab 環境
4.さいきょうのえでぃた VS Code

まずは今となっては、開発者の方にとっては常識になってるかと思う Docker から
f:id:konchangakita:20210516222119p:plain

なんでコンテナ使うのか

壊れてもすぐ作り治せる
別のPCでも共通環境をソッコーで準備
自分のPCを汚さない、開発言語はちょっとしたバージョン変更、ライブラリ依存が大変

WSL 2 をセットアップ

Windows10 で標準に入っている Hyper-Vだけでも、DockerDesktop はインストールできますが、WSL で Ubuntu を用意しておくと何かと便利なので、一緒に準備しておく

https://docs.microsoft.com/ja-jp/windows/wsl/install-win10
※バージョン 1903 以降、ビルド 18362 以上
このバージョンアップに結構時間がかかった

Hyper-Vを有効にする

f:id:konchangakita:20210516213448p:plain

Powershell

> dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart
> dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart

これが実行出来ない場合は、ビルドを確認
> wsl --set-default-version 2

UbuntuWindows Terminal を準備

Microsoft Storeでインストール
 - Windows Terminal
 - Ubuntu
 ※MSのアカウントが必要
f:id:konchangakita:20210516215000p:plain


Windows Terminal で WSL の Ubuntu が実行できるようになると、Windows上でも Linuxコマンドが実行できるようになるので何かと便利
f:id:konchangakita:20210516215029p:plain

Docker Desktop のインストール

ここでようやく Docker Desktop をインストール
ダウンロードして、インストール(5分くらい)
https://www.docker.com/products/docker-desktop

インストール後、一度ログアウトする

まずは dockerhub アカウントを作ろう

dockerhubサイト
https://hub.docker.com/

Windows Terminal を開いて dockerhubアカウントを config 登録しておく

$ docker login

はじめてのコンテナ起動

こんな感じでとりあえず流してみる

$ docker version
$ docker pull hello-world   # dockerhub からコンテナダウンロード
$ docker ps                 # 起動しているコンテナの状況確認
$ docker ps –a              # 起動していないコンテナも確認
$ docker rm <CONTAINER ID>  #コンテナ削除

実行例)

$ docker run hello-world

Hello from Docker!
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
 1. The Docker client contacted the Docker daemon.
 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
    (amd64)
 3. The Docker daemon created a new container from that image which runs the
    executable that produces the output you are currently reading.
 4. The Docker daemon streamed that output to the Docker client, which sent it
    to your terminal.

To try something more ambitious, you can run an Ubuntu container with:
 $ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker ID:
 https://hub.docker.com/

For more examples and ideas, visit:
 https://docs.docker.com/get-started/


よくつかう Dockerコマンド

・起動中のコンテナを表示
$ docker ps
・コンテナイメージを表示
$ docker images
・コンテナ起動
$ docker run
・起動中のコンテナに入る
$ docker exec



さいごに

まずは基本中の基本の Docker を Windowsで使うための環境整備
Docker コンテナさえ扱えてしまえれば、開発環境としてはどこでも共通に扱えるようになります
Docker の準備は MAC / Linux だともっと簡単ですね

E資格対策 で丸暗記した Pythonコード

f:id:konchangakita:20210311202353p:plain

とうとうこれを堂々と使うことができます
JDLA E資格 2021#1 取得しました

喜びとともに忘れないうちに試験の振り返り第2段
Pythonコード問題は、基本的に穴埋めなので何がやりたいかを理解していれば時間さえかければ答えにたどり着くことはできるわけですが、試験時間もそんなに余裕があるものではないので公式の丸暗記と一緒で、時間短縮には結構大切かも

python コード

順伝播と誤差逆伝播

基本中の基本、コードを書いてると自然と覚えてるもんですが(デバッグもできるし)、いざ4択になると転置とかいるっけ?とか混乱するかも

アフィン変換 順伝播
f:id:konchangakita:20210227140515p:plain

y = np.dot(x, w) + b

アフィン変換 誤差逆伝播
f:id:konchangakita:20210227141607p:plain

dx = np.dot(dout, w.T)
dw = np.dot(x.T, dout)
db = np.sum(dout, axis=0)
活性化関数の勾配

シグモイドの勾配

dout = dout * (1.0 - out) * out 

tanh(ハイパボリックタンジェント

dout = (1.0 - y**2)

ReLUの勾配
順伝播で「0」にした部分を覚えておいて、0にする

dout[relu_mask] = 0
損失関数(コスト関数)の勾配

平均二乗誤差の勾配

d = -2 * (t - y) # t: 正解データ、y: 予測データ

交差エントロピーの勾配

delta = 1e-8
dout = -np.mean(t * np.log(y + delta))

交差エントロピー with ソフトマックス

dout = y - t 
最適化関数

SDG
モーメント
ネストロフのモーメント
AdaGrad
RMSprop
Adam


バッチノーマライゼーション

これが結構有用で、標準偏差を求める式も一緒に頭に叩き込めます

mu = np.mean(x, axis=0)  #平均
xc = x - mu  # 今回のミニバッチの平均との差分
var = np.mean(xc**2, axis=0)  # 分散
std = np.sqrt(var + 10e-7)   # 今回のミニバッチの標準偏差
xn = xc / std  # 正規化
CNN の im2col

これは 畳み込み処理の頻出問題のようなので、覚えておいて損はない

for y in range(filter_h):
    y_max = y + stride*out_h
    for x in range(filter_w):
        x_max = x + stride*out_w
        col[:, :, y, x, :, :] = img[:,:, y:y_max:stride, x:x_max:stride]

特に重要なのはココ

col[:, :, y, x, :, :] = img[:,:, y:y_max:stride, x:x_max:stride]

必要なのは、shape がどうなっているかを意識しておけば、im2col かかわる応用問題にも対応できるようになります
N:バッチサイズ
Ch:入力チャネル
H, W:入力高さ、 横幅
out_H, out_W:出力高さ、横幅

(N, Ch, H, W) ➔ (N*out_H*out_W, C*H*W)

LSTMの重み計算

LSTM覚えておけば、GRUも簡単に連想できます
特徴は、各ゲート分の重みをいっぺんに計算しておいて、各ゲート用にスライスして取り出すところになります

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]

おわりに

これからは実践活用のAI構築をやっていこう
KPS へ導入すっぞ!

E資格受けてみまして

合格ラインとかそんなのよくわかってないままに受けてきました
毎回こういうガチな試験を受ける時は人生で最高に勉強したーとか言ってるようなきがするけど、今回もまさしく人生で一番勉強した気がする
手応えは7-8割くらい。。。。(9割以上とる覚悟で受けたので結構凹んでる)

通常は2-3週間くらいで結果らしいので、それまで仕事が手に付きません
(テスト前も手につかないとか言ってたのでどんだけ仕事してないん)


試験内容は、守秘義務やらなんやらで語ることはできませんが
どんなことを中心に覚えておいたか備忘録(また受けることになったとき用)

丸暗記した公式をつらつらと

数式っていうのは、日常生活でみかけることがないので、知らない数式がでると若干パニック(あきらめ)になりますし、公式丸暗記の効果はそのものが選択肢出てきたら時間短縮にもなりますので、何度何度も見直して記憶定着に時間をかけました
丸暗記の労力を減らすため細かい条件とかは一旦抜きで

確率分布

一見ややこしいけど、いろんなところでよく出てくるやつ
対数尤度は特に
ベルヌーイ分布
確率関数:
f:id:konchangakita:20210222210418p:plain
期待値:𝑝
𝑝 の最尤推定
f:id:konchangakita:20210222210457p:plain
負の対数尤度:
f:id:konchangakita:20210222210521p:plain

マルチヌーイ分布
負の対数尤度:
f:id:konchangakita:20210222210707p:plain

一変量正規分布の確率密度(平均 μ 、分散 𝜎^2)
f:id:konchangakita:20210223153628p:plain

ベイズの定理
f:id:konchangakita:20210223154220p:plain

情報理論

結構忘れがち
情報量:
f:id:konchangakita:20210223160244p:plain
エントロピー
f:id:konchangakita:20210223160308p:plain
交差エントロピー
f:id:konchangakita:20210223160352p:plain
カルバックライブラーダイバージェンス
f:id:konchangakita:20210223160439p:plain

活性化関数と損失関数の微分

それぞれの関数自体は必須として、微分の式も覚えておいて損なし!
シグモイド関数
f:id:konchangakita:20210223163205p:plain
tanh(ハイパボリックタンジェント):
f:id:konchangakita:20210223163320p:plain
平均二乗誤差:
f:id:konchangakita:20210223163336p:plain
交差エントロピー誤差:
f:id:konchangakita:20210223163350p:plain
ソフトマックス交差エントロピー誤差:
f:id:konchangakita:20210223163412p:plain

GANの最適化

f:id:konchangakita:20210223171316p:plain

強化学習

ベルマン方程式
f:id:konchangakita:20210223171430p:plain
f:id:konchangakita:20210223171500p:plain

Sarsa 更新式
f:id:konchangakita:20210223171553p:plain
Q関数 更新式
f:id:konchangakita:20210223171625p:plain

勾配方策定理の勾配
f:id:konchangakita:20210223171716p:plain

丸暗記の策

覚えなければいけないことはたくさんあるわけですが、この数式というのは中途半端に覚えるとかえって混乱しがちなので丸暗記が得策
Python コードは書くしかない(写経)

【DeepLearning特訓】GAN 敵対的生成ネットワーク

E資格向けの自習アウトプット
自分用メモ

GAN 敵対的生成ネットワークは、2014年にイアン・グッドフェロー氏らが「Generative Adversarial Network」という論文で発表
生成モデルと識別モデルの組み合わせ、敵対させ競い合わせることで精度を上げていく手法

 ・生成(Generator)モデル:見破られないようなニセモノを作る
 ・識別(Discriminator)モデル:訓練データを使ってニセモノを見破ろうとする

「偽造犯と警察」とか「怪盗と探偵」とか「ルパンと銭形」とかそんなライバル同士が切磋琢磨していって、結果として騙す側(トリック)巧妙になっていくような関係

GAN のアーキテクチャ

f:id:konchangakita:20210207211937p:plain
1.ノイズを乱数からサンプリングする
2.Generator で Fakeデータを生成
3.Realデータと Fakeデータを Discriminator に識別させる
4.学習方針
  - Generator(生成モデル)は、識別判定のロスが大きくなるように ➔うまく騙せた
  - Discriminator(識別モデル)は、識別判定のロスが小さくなるように ➔みやぶった

GAN の目的関数(損失関数)

いつものごとく導出の過程の理解は一旦おいておいて

以下をセットで覚えておく
Generator ネットワーク 𝐺:𝑧→𝑥'
Discriminator ネットワーク 𝐷:𝑥→(0,1)
f:id:konchangakita:20210211163038p:plain

Discriminator:Realデータ真(1)、Fakeデータ偽(0)の時の、Discriminatorの予測に対する交差エントロピー
見破れてるのか
Generator:Realデータ真(1)、Fakeデータ真(1)の時の、Discriminatorの予測に対する交差エントロピー
うまく騙せてるのか

このあたりは、考えると沼っていくので、実装しながら理解することにする

GAN の種類

DCGAN(Deep Convolutinal GAN):CNNを使う
LAPGAN(Laplacian Pyramid):低解像度と高解像度の画像の差を比較
Conditional GAN:訓練時に教師データのラベル情報を用いて、生成するクラスを指定できる
StarGAN:マルチドメインに適用できるように拡張

この他にもたくさん研究されているらしい

TensorFlow で実装しながら頭を整理

TensorFlow公式に DCGAN のチュートリアルがあったので、こちらで参考に実装してみる
www.tensorflow.org

今回はこのあたりを使う

import tensorflow as tf
from tensorflow.keras import layers 
from tensorflow.keras.datasets import mnist, fashion_mnist
import matplotlib.pyplot as plt
データセット

データセットの準備、今回は Fashion MNIST を使ってみる

# データのロード
(x_train, t_train), (x_test, t_test) = fashion_mnist.load_data()

# 設定
BUFFER_SIZE = x_train.shape[0]  # 60000
BATCH_SIZE = 256

x_train = x_train.reshape(BUFFER_SIZE, 28, 28, 1).astype('float32')
x_train = (x_train - 127.5) / 127.5     # Normalize [-1, 1]
train_dataset = tf.data.Dataset.from_tensor_slices(x_train).shuffle(BUFFER_SIZE).batch(BATCH_SIZE)


どんな画像か確認しておく

fig = plt.figure(figsize=(4,4))

for i in range(16):
    plt.subplot(4, 4, i+1)
    plt.imshow(x_train[i, :, :, 0] * 127.5 + 127.5, cmap='gray')
    plt.axis('off')

plt.show()

f:id:konchangakita:20210207233715p:plain

Generator ネットワーク

ノイズを受け取って、CNN の逆畳み込みで画像を作っていく

# Generator
# noise から画像を作る
def make_generator_model():
    model = tf.keras.Sequential()
    model.add(layers.Dense(7*7*256, use_bias=False, input_shape=(100,)))
    model.add(layers.BatchNormalization())
    model.add(layers.LeakyReLU())
    
    model.add(layers.Reshape((7,7,256)))
    assert model.output_shape == (None, 7,7,256)    # Noneはバッチサイズ

    model.add(layers.Conv2DTranspose(128, (5,5), strides=1, padding='same', use_bias=False))
    assert model.output_shape == (None, 7,7,128)
    model.add(layers.BatchNormalization())
    model.add(layers.LeakyReLU())

    model.add(layers.Conv2DTranspose(64, (5,5), strides=2, padding='same', use_bias=False))
    assert model.output_shape == (None, 14,14,64)
    model.add(layers.BatchNormalization())
    model.add(layers.LeakyReLU())

    model.add(layers.Conv2DTranspose(1, (5,5), strides=2, padding='same', use_bias=False))
    assert model.output_shape == (None, 28,28,1)

    return model


None はバッチサイズがはいる

generator = make_generator_model()
generator.summary()
Model: "sequential_2"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
dense_2 (Dense)              (None, 12544)             1254400   
_________________________________________________________________
batch_normalization_3 (Batch (None, 12544)             50176     
_________________________________________________________________
leaky_re_lu_5 (LeakyReLU)    (None, 12544)             0         
_________________________________________________________________
reshape_1 (Reshape)          (None, 7, 7, 256)         0         
_________________________________________________________________
conv2d_transpose_3 (Conv2DTr (None, 7, 7, 128)         819200    
_________________________________________________________________
batch_normalization_4 (Batch (None, 7, 7, 128)         512       
_________________________________________________________________
leaky_re_lu_6 (LeakyReLU)    (None, 7, 7, 128)         0         
_________________________________________________________________
conv2d_transpose_4 (Conv2DTr (None, 14, 14, 64)        204800    
_________________________________________________________________
batch_normalization_5 (Batch (None, 14, 14, 64)        256       
_________________________________________________________________
leaky_re_lu_7 (LeakyReLU)    (None, 14, 14, 64)        0         
_________________________________________________________________
conv2d_transpose_5 (Conv2DTr (None, 28, 28, 1)         1600      
=================================================================
Total params: 2,330,944
Trainable params: 2,305,472
Non-trainable params: 25,472
_________________________________________________________________


試し画像を一枚だけ作ってみる

noise = tf.random.normal([1, 100])
generated_image = generator(noise, training=False)
plt.imshow(generated_image[0, :, :, 0], cmap='gray')

学習してないので、当然わけわからん Fake 画像が表示される
f:id:konchangakita:20210207235519p:plain

Discriminatorネットワーク

CNN で畳み込んで特徴量を抽出していく

# Discriminator
# 真の場合には正の数値を、偽の場合は負の数値を返す
def make_discriminator_model():
    model = tf.keras.Sequential()
    model.add(layers.Conv2D(64, (5,5), strides=2, padding='same', input_shape=[28,28,1]))
    model.add(layers.LeakyReLU())
    model.add(layers.Dropout(0.3))

    model.add(layers.Conv2D(128, (5,5), strides=2, padding='same'))
    model.add(layers.LeakyReLU())
    model.add(layers.Dropout(0.3))

    model.add(layers.Flatten())
    model.add(layers.Dense(1))

    return model
discriminator = make_discriminator_model()
discriminator.summary()
Model: "sequential_3"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
conv2d_2 (Conv2D)            (None, 14, 14, 64)        1664      
_________________________________________________________________
leaky_re_lu_8 (LeakyReLU)    (None, 14, 14, 64)        0         
_________________________________________________________________
dropout_2 (Dropout)          (None, 14, 14, 64)        0         
_________________________________________________________________
conv2d_3 (Conv2D)            (None, 7, 7, 128)         204928    
_________________________________________________________________
leaky_re_lu_9 (LeakyReLU)    (None, 7, 7, 128)         0         
_________________________________________________________________
dropout_3 (Dropout)          (None, 7, 7, 128)         0         
_________________________________________________________________
flatten_1 (Flatten)          (None, 6272)              0         
_________________________________________________________________
dense_3 (Dense)              (None, 1)                 6273      
=================================================================
Total params: 212,865
Trainable params: 212,865
Non-trainable params: 0
_________________________________________________________________


適当な Fake 画像の適当な特徴量

decision = discriminator(generated_image)
decision
<tf.Tensor: shape=(1, 1), dtype=float32, numpy=array([[1.2810372]], dtype=float32)>


損失関数とオプティマイザー

損失関数はクロスエントロピーオプティマイザーにはAdam

【損失関数の考え方】
Generatorでは、fake_loss を Fakeデータを Real と判定できたか(ちゃんと騙せたか)
Discriminatorでは、Realロス と Fakeロス の和
 - real_loss は 訓練データを Real 判定できたか
 - fake_loss は Fakeデータを Fake 判定できたか(見破ったか)

# 損失関数とオプティマイザー
cross_entropy = tf.keras.losses.BinaryCrossentropy(from_logits=True)

def discriminator_loss(real_output, fake_output):
    real_loss = cross_entropy(tf.ones_like(real_output), real_output)
    fake_loss = cross_entropy(tf.zeros_like(fake_output), fake_output)
    total_loss = real_loss + fake_loss
    return total_loss

def generator_loss(fake_output):
    return cross_entropy(tf.ones_like(fake_output), fake_output)

generator_optimizer = tf.keras.optimizers.Adam(1e-4)
discriminator_optimizer = tf.keras.optimizers.Adam(1e-4)

レーニングループ

def train_step(images):
    noise = tf.random.normal([BATCH_SIZE, noise_dim])

    with tf.GradientTape() as gen_tape, tf.GradientTape() as dis_tape:
        generated_images = generator(noise, training=True)
        real_output = discriminator(images, training=True)
        fake_output = discriminator(generated_images, training=True)

        dis_loss = discriminator_loss(real_output, fake_output)
        gen_loss = generator_loss(fake_output)

    # 勾配の保存
    gradients_of_discriminator = dis_tape.gradient(dis_loss, discriminator.trainable_variables)
    gradients_of_generator = gen_tape.gradient(gen_loss, generator.trainable_variables)

    # パラメータ更新
    discriminator_optimizer.apply_gradients(zip(gradients_of_discriminator, discriminator.trainable_variables))
    generator_optimizer.apply_gradients(zip(gradients_of_generator, generator.trainable_variables))

# エポック数分のループ
def train(dataset, epochs):
    for epoch in range(epochs):
        for image_batch in dataset:
            gen_loss, dis_loss = train_step(image_batch)

        print('EPOCH:{}, {}, {},'.format(epoch, gen_loss, dis_loss))

        generate_and_save_images(generator, epoch+1, seed)

        # Save the model every 15 epochs
        if (epoch + 1) % 15 == 0:
            checkpoint.save(file_prefix = checkpoint_dir)

# Fake画像(生成画像)の表示と保存
def generate_and_save_images(model, epoch, test_input):
    predictions = model(test_input, training=False)
    fig = plt.figure(figsize=(4,4))

    for i in range(predictions.shape[0]):
        plt.subplot(4, 4, i+1)
        plt.imshow(predictions[i, :, :, 0] * 127.5 + 127.5, cmap='gray')
        plt.axis('off')

    plt.savefig('output/dcgan_image_at_epoch_{:04d}.png'.format(epoch))
    plt.show()
# トレーニング設定
EPOCHS = 50
noise_dim = 100
num_examples_to_generate = 16

seed = tf.random.normal([num_examples_to_generate, noise_dim])

レーニング実行

train(train_dataset, EPOCHS)

エポック 10、エポック 50、エポック 100 のそれぞれの Fake画像
f:id:konchangakita:20210208004241p:plain:w180f:id:konchangakita:20210208004308p:plain:w180f:id:konchangakita:20210208004328p:plain:w180

それっぽいものが出来てきています
構造としてはシンプルなのに、オリジナルの画像が作られてくるのすごい

さいごに

構造をざっくり理解するために書いてきましたが、つまるところ Generate された分布と入力されたデータの分布を近づけていくということが重要なようです
(訓練データそのものを作るわけではない)
生成データは実用的実におもしろい、試験が終わったら実装してみたい
これからは強化学習

【DeepLearning特訓】VAE 変分自己符号化器

E資格向けの自習アウトプット
自分用メモ

VAE(変分自己符号化器)は、生成モデルに対するアプローチの一つ
生成モデルとは、「訓練データを生成する確率 p(x) を求めたい」という分野で、訓練データを元にして特徴を捉えて訓練デーアセットに似たデータを生成することができるようになる

あるアニメのキャラを訓練データとして与えたら、そのアニメに出てきそうなキャラを作ってくれるみたいなイメージかな、かな?

オートエンコーダ

教師なし学習のひとつ。なので学習には訓練用データだけで用意すればよい
入力データを Encoder 低次元に圧縮して、Decoder で復元して生成データを作る 潜在変数 𝑧(特徴量)を近似推論する
( seq2seq でも見かけた Encoder-Decoder手法 RNNでは系列データの隠れ層の状態を渡していった)

一般的なオートエンコーダの例
f:id:konchangakita:20210207003732p:plain

VAE: Variational Auto Encoder のアーキテクチャ

VAE では潜在変数 𝑧 にガウス分布(標準正規分布)𝑧 ~ 𝑁(0,1) を仮定する
潜在変数 𝑧 を確率分布に押し込める

VAEの順伝播

Encoder で平均 μ と分散 σ を出力、正規分布パラメータ(μ,σ)で潜在変数 𝑧 をランダムサンプリングする
f:id:konchangakita:20210207003759p:plain
Encoder:CNNを使うことが多い
潜在変数: 𝑧=𝜇+𝜎∗𝜀 (𝜀 は平均0、分散1正規分布の乱数)
Reparameterization Trick という手法で、モデルはepsilon(𝜀)を通じることで確率を維持しながら、エンコーダーの勾配をそれぞれ 平均 μ と分散 σ を介して逆伝播できる 。
Decoder:潜在変数 𝑧 から 生成データを出力、CNNで戻す

VAE の損失関数

これが非常にむつかしい。。。。

 ・入力データと生成データの負の対数尤度(クロスエントロピー
f:id:konchangakita:20210206215816p:plain:w330
 ・潜在変数 𝑧 の分布と符号化された潜在変数 𝑧 の分布のカルバックダイバージェンス
f:id:konchangakita:20210206215843p:plain:w330
難しくてちょっと何言ってるわからんけど、この2つの和が総損失ということらしい
更に、確率分布をもとめるのは変分下限(わからん)の最大化に帰着するという
更に更に、変分下限の最大化はKLダイバージェンスの最小化と等しい(らしい)

対数尤度とかカルバックダイバージェンスとかから導出の式は、ちょっとややこしいので、ひとまずそこから導きだされるこの式だけは覚えておくことにする
f:id:konchangakita:20210206220350p:plain
第一項:元データと生成データの一致度、生成の精度、大きくしたい
第二項:潜在変数 𝑧 の分布(平均 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)
オプティマイズと損失関数を定義

このチュートリアル損失関数をアレンジしている模様
f:id:konchangakita:20210206230619p:plain
これをそのまま採用してみる

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)

学習前前なので、当然わけわからん画像が生成される
f:id:konchangakita:20210206232400p:plain

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回目から何かそれっぽい画像が生成され始める
f:id:konchangakita:20210206232829p:plain

エポック 10回目で生成されたデータ
f:id:konchangakita:20210206233241p:plain


さいごに

こんな感じで、与えた訓練データに近い画像が生成されてきました
試験が終わったら、もっといろいろいじってみたい
とにかく損失関数の理解がむずかしく、実装にも時間がかかった。。
TensorFlow 自体もこれでちょっと慣れたかな
次は GAN

【DeepLearning特訓】RNN応用 seq2seq編

E資格向けの自習アウトプット
自分用メモ

seq2seq は Encoder-Decoderモデルとも呼ばれている
RNNを使って、系列データを固定次元ベクトルへ変換(エンコード)、逆に固定次元ベクトルから系列を生成できる(デコード)

エンコーダ:系列データをある規則に基づいて変換する(固定長ベクトルへ)
デコーダ:系列データへ変換(可変ベクトルへ)
f:id:konchangakita:20210131152837p:plain

日本語から英語への翻訳で考えてみると
f:id:konchangakita:20210131153008p:plain


seq2seq の構造

エンコーダでは、各タイムステップの出力は使わず隠れ層の出力を伝播させる、最終単語の隠れ層の出力をデコーダに渡す
区切り文字 で開始/終了を示す

f:id:konchangakita:20210131153049p:plain
エンコーダとデコーダの構造が違っても良い

f:id:konchangakita:20210131153109p:plain

seq2seq の問題点

エンコードの出力は固定長ベクトル
 ➔入力系列が長すぎるとうまくいかない

Attention Mechanism

エンコーダの各ステップの出力を利用する
「入力と出力でどの単語が関連しているか」という対応関係を seq2seq に学習させる

Encoderの改良

最後の隠れ状態だけを Decoder に渡していたのを、入力される文章のすべてステップの出力の集合を作る
「ひとつの固定長ベクトル」という制約から開放
f:id:konchangakita:20210131170742p:plain

Decoder の改良

各ステップでRNNの出力結果を用いて重要度を算出する
f:id:konchangakita:20210131170805p:plain


終わりに

seq2seq からの、Attention Mechanism は非常に強力らしい
ただ、numpy で書いていくにはちょっと面倒なので、また TensorFlow Keras などでサクッと試してみようかな

【DeepLearning特訓】RNN応用 GRU編

E資格向けの自習アウトプット
自分用メモ

GRU(Gated Recurrent Unit)は、LSTM を単純化したモデル
LSTMはパラメータが多く学習に時間がかかので、パラメータを減らして計算量を減らす工夫
ゲートを2つに減らし、内部状態をなくした
 ・reset ゲート (r):過去の隠れ状態をどれだけ無視するか
 ・update ゲート (z):過去の隠れ状態を更新する役割

f:id:konchangakita:20210130193220p:plain:w300

GRUの構造

行列の内積を計算するところと、要素積(アダマール積)が混ざっているの要注意
f:id:konchangakita:20210130193333p:plain
f:id:konchangakita:20210202110524p:plain:w350

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

おわりに

LSTM と GRU どちらを使うべきか?ですが、文献によると、タスクやハイパーパラメータの調整によりケースバイケースの模様
最近では LSTM の方(+LSTM改良版)が多く使われているようですが、GRUは計算量が少なくて済むので、小さいデータセットやモデルの設計を繰り返し修正しながら作っていくには向いているらしい
LSTM のその応用はここでおしまい
他の自然言語処理モデルへつづく

LSTM応用編はおわり