ホーム » VAE

VAE」カテゴリーアーカイブ

Sonnet 2.0 : Tutorials : VQ-VAE 訓練サンプル

Sonnet 2.0 : Tutorials : VQ-VAE 訓練サンプル (翻訳/解説)

翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 06/10/2020

* 本ページは、Sonnet の以下のドキュメントを翻訳した上で適宜、補足説明したものです:

* サンプルコードの動作確認はしておりますが、必要な場合には適宜、追加改変しています。
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。

 

VQ-VAE 訓練サンプル

TF 2 / Sonnet 2 を使用して、https://arxiv.org/abs/1711.00937 で指定されるモデルをどのように訓練するかの実演です。

Mac と Linux 上、単純に各セルを順番に実行してください。

!pip install dm-sonnet dm-tree
Requirement already satisfied: dm-sonnet in /tmp/sonnet-nb-env/lib/python3.7/site-packages (2.0.0)
Requirement already satisfied: dm-tree in /tmp/sonnet-nb-env/lib/python3.7/site-packages (0.1.5)
Requirement already satisfied: six>=1.12.0 in /tmp/sonnet-nb-env/lib/python3.7/site-packages (from dm-sonnet) (1.14.0)
Requirement already satisfied: tabulate>=0.7.5 in /tmp/sonnet-nb-env/lib/python3.7/site-packages (from dm-sonnet) (0.8.7)
Requirement already satisfied: absl-py>=0.7.1 in /tmp/sonnet-nb-env/lib/python3.7/site-packages (from dm-sonnet) (0.9.0)
Requirement already satisfied: numpy>=1.16.3 in /tmp/sonnet-nb-env/lib/python3.7/site-packages (from dm-sonnet) (1.18.3)
Requirement already satisfied: wrapt>=1.11.1 in /tmp/sonnet-nb-env/lib/python3.7/site-packages (from dm-sonnet) (1.12.1)
import matplotlib.pyplot as plt
import numpy as np
import tensorflow.compat.v2 as tf
import tensorflow_datasets as tfds
import tree

try:
  import sonnet.v2 as snt
  tf.enable_v2_behavior()
except ImportError:
  import sonnet as snt

print("TensorFlow version {}".format(tf.__version__))
print("Sonnet version {}".format(snt.__version__))
TensorFlow version 2.1.0
Sonnet version 2.0.0

 

Cifar10 データをダウンロードする

これはインターネットへの接続を必要として ~160MB をダウンロードします。

cifar10 = tfds.as_numpy(tfds.load("cifar10:3.0.2", split="train+test", batch_size=-1))
cifar10.pop("id", None)
cifar10.pop("label")
tree.map_structure(lambda x: f'{x.dtype.name}{list(x.shape)}', cifar10)
{'image': 'uint8[60000, 32, 32, 3]'}

 

データを Numpy にロードする

下で平均二乗誤差を正規化するために訓練セット全体の分散を計算します。

train_data_dict = tree.map_structure(lambda x: x[:40000], cifar10)
valid_data_dict = tree.map_structure(lambda x: x[40000:50000], cifar10)
test_data_dict = tree.map_structure(lambda x: x[50000:], cifar10)
def cast_and_normalise_images(data_dict):
  """Convert images to floating point with the range [-0.5, 0.5]"""
  images = data_dict['image']
  data_dict['image'] = (tf.cast(images, tf.float32) / 255.0) - 0.5
  return data_dict

train_data_variance = np.var(train_data_dict['image'] / 255.0)
print('train data variance: %s' % train_data_variance)
train data variance: 0.06327039811675479

 

エンコーダ & デコーダ・アーキテクチャ

  def __init__(self, num_hiddens, num_residual_layers, num_residual_hiddens,
               name=None):
    super(ResidualStack, self).__init__(name=name)
    self._num_hiddens = num_hiddens
    self._num_residual_layers = num_residual_layers
    self._num_residual_hiddens = num_residual_hiddens

    self._layers = []
    for i in range(num_residual_layers):
      conv3 = snt.Conv2D(
          output_channels=num_residual_hiddens,
          kernel_shape=(3, 3),
          stride=(1, 1),
          name="res3x3_%d" % i)
      conv1 = snt.Conv2D(
          output_channels=num_hiddens,
          kernel_shape=(1, 1),
          stride=(1, 1),
          name="res1x1_%d" % i)
      self._layers.append((conv3, conv1))

  def __call__(self, inputs):
    h = inputs
    for conv3, conv1 in self._layers:
      conv3_out = conv3(tf.nn.relu(h))
      conv1_out = conv1(tf.nn.relu(conv3_out))
      h += conv1_out
    return tf.nn.relu(h)  # Resnet V1 style


class Encoder(snt.Module):
  def __init__(self, num_hiddens, num_residual_layers, num_residual_hiddens,
               name=None):
    super(Encoder, self).__init__(name=name)
    self._num_hiddens = num_hiddens
    self._num_residual_layers = num_residual_layers
    self._num_residual_hiddens = num_residual_hiddens

    self._enc_1 = snt.Conv2D(
        output_channels=self._num_hiddens // 2,
        kernel_shape=(4, 4),
        stride=(2, 2),
        name="enc_1")
    self._enc_2 = snt.Conv2D(
        output_channels=self._num_hiddens,
        kernel_shape=(4, 4),
        stride=(2, 2),
        name="enc_2")
    self._enc_3 = snt.Conv2D(
        output_channels=self._num_hiddens,
        kernel_shape=(3, 3),
        stride=(1, 1),
        name="enc_3")
    self._residual_stack = ResidualStack(
        self._num_hiddens,
        self._num_residual_layers,
        self._num_residual_hiddens)

  def __call__(self, x):
    h = tf.nn.relu(self._enc_1(x))
    h = tf.nn.relu(self._enc_2(h))
    h = tf.nn.relu(self._enc_3(h))
    return self._residual_stack(h)


class Decoder(snt.Module):
  def __init__(self, num_hiddens, num_residual_layers, num_residual_hiddens,
               name=None):
    super(Decoder, self).__init__(name=name)
    self._num_hiddens = num_hiddens
    self._num_residual_layers = num_residual_layers
    self._num_residual_hiddens = num_residual_hiddens

    self._dec_1 = snt.Conv2D(
        output_channels=self._num_hiddens,
        kernel_shape=(3, 3),
        stride=(1, 1),
        name="dec_1")
    self._residual_stack = ResidualStack(
        self._num_hiddens,
        self._num_residual_layers,
        self._num_residual_hiddens)
    self._dec_2 = snt.Conv2DTranspose(
        output_channels=self._num_hiddens // 2,
        output_shape=None,
        kernel_shape=(4, 4),
        stride=(2, 2),
        name="dec_2")
    self._dec_3 = snt.Conv2DTranspose(
        output_channels=3,
        output_shape=None,
        kernel_shape=(4, 4),
        stride=(2, 2),
        name="dec_3")
    
  def __call__(self, x):
    h = self._dec_1(x)
    h = self._residual_stack(h)
    h = tf.nn.relu(self._dec_2(h))
    x_recon = self._dec_3(h)
    return x_recon
    

class VQVAEModel(snt.Module):
  def __init__(self, encoder, decoder, vqvae, pre_vq_conv1, 
               data_variance, name=None):
    super(VQVAEModel, self).__init__(name=name)
    self._encoder = encoder
    self._decoder = decoder
    self._vqvae = vqvae
    self._pre_vq_conv1 = pre_vq_conv1
    self._data_variance = data_variance

  def __call__(self, inputs, is_training):
    z = self._pre_vq_conv1(self._encoder(inputs))
    vq_output = self._vqvae(z, is_training=is_training)
    x_recon = self._decoder(vq_output['quantize'])
    recon_error = tf.reduce_mean((x_recon - inputs) ** 2) / self._data_variance
    loss = recon_error + vq_output['loss']
    return {
        'z': z,
        'x_recon': x_recon,
        'loss': loss,
        'recon_error': recon_error,
        'vq_output': vq_output,
    }

 

モデルを構築して訓練する

%%time

# Set hyper-parameters.
batch_size = 32
image_size = 32

# 100k steps should take < 30 minutes on a modern (>= 2017) GPU.
# 10k steps gives reasonable accuracy with VQVAE on Cifar10.
num_training_updates = 10000

num_hiddens = 128
num_residual_hiddens = 32
num_residual_layers = 2
# These hyper-parameters define the size of the model (number of parameters and layers).
# The hyper-parameters in the paper were (For ImageNet):
# batch_size = 128
# image_size = 128
# num_hiddens = 128
# num_residual_hiddens = 32
# num_residual_layers = 2

# This value is not that important, usually 64 works.
# This will not change the capacity in the information-bottleneck.
embedding_dim = 64

# The higher this value, the higher the capacity in the information bottleneck.
num_embeddings = 512

# commitment_cost should be set appropriately. It's often useful to try a couple
# of values. It mostly depends on the scale of the reconstruction cost
# (log p(x|z)). So if the reconstruction cost is 100x higher, the
# commitment_cost should also be multiplied with the same amount.
commitment_cost = 0.25

# Use EMA updates for the codebook (instead of the Adam optimizer).
# This typically converges faster, and makes the model less dependent on choice
# of the optimizer. In the VQ-VAE paper EMA updates were not used (but was
# developed afterwards). See Appendix of the paper for more details.
vq_use_ema = True

# This is only used for EMA updates.
decay = 0.99

learning_rate = 3e-4


# # Data Loading.
train_dataset = (
    tf.data.Dataset.from_tensor_slices(train_data_dict)
    .map(cast_and_normalise_images)
    .shuffle(10000)
    .repeat(-1)  # repeat indefinitely
    .batch(batch_size, drop_remainder=True)
    .prefetch(-1))

valid_dataset = (
    tf.data.Dataset.from_tensor_slices(valid_data_dict)
    .map(cast_and_normalise_images)
    .repeat(1)  # 1 epoch
    .batch(batch_size)
    .prefetch(-1))

# # Build modules.
encoder = Encoder(num_hiddens, num_residual_layers, num_residual_hiddens)
decoder = Decoder(num_hiddens, num_residual_layers, num_residual_hiddens)
pre_vq_conv1 = snt.Conv2D(output_channels=embedding_dim,
    kernel_shape=(1, 1),
    stride=(1, 1),
    name="to_vq")

if vq_use_ema:
  vq_vae = snt.nets.VectorQuantizerEMA(
      embedding_dim=embedding_dim,
      num_embeddings=num_embeddings,
      commitment_cost=commitment_cost,
      decay=decay)
else:
  vq_vae = snt.nets.VectorQuantizer(
      embedding_dim=embedding_dim,
      num_embeddings=num_embeddings,
      commitment_cost=commitment_cost)
  
model = VQVAEModel(encoder, decoder, vq_vae, pre_vq_conv1,
                   data_variance=train_data_variance)

optimizer = snt.optimizers.Adam(learning_rate=learning_rate)

@tf.function
def train_step(data):
  with tf.GradientTape() as tape:
    model_output = model(data['image'], is_training=True)
  trainable_variables = model.trainable_variables
  grads = tape.gradient(model_output['loss'], trainable_variables)
  optimizer.apply(grads, trainable_variables)

  return model_output

train_losses = []
train_recon_errors = []
train_perplexities = []
train_vqvae_loss = []

for step_index, data in enumerate(train_dataset):
  train_results = train_step(data)
  train_losses.append(train_results['loss'])
  train_recon_errors.append(train_results['recon_error'])
  train_perplexities.append(train_results['vq_output']['perplexity'])
  train_vqvae_loss.append(train_results['vq_output']['loss'])

  if (step_index + 1) % 100 == 0:
    print('%d train loss: %f ' % (step_index + 1,
                                   np.mean(train_losses[-100:])) +
          ('recon_error: %.3f ' % np.mean(train_recon_errors[-100:])) +
          ('perplexity: %.3f ' % np.mean(train_perplexities[-100:])) +
          ('vqvae loss: %.3f' % np.mean(train_vqvae_loss[-100:])))
  if step_index == num_training_updates:
    break
WARNING:tensorflow:AutoGraph could not transform <function train_step at 0x7f1016cb5f80> and will run it as-is.
Please report this to the TensorFlow team. When filing the bug, set the verbosity to 10 (on Linux, `export AUTOGRAPH_VERBOSITY=10`) and attach the full output.
Cause: Unable to locate the source code of <function train_step at 0x7f1016cb5f80>. Note that functions defined in certain environments, like the interactive Python shell do not expose their source code. If that is the case, you should to define them in a .py source file. If you are certain the code is graph-compatible, wrap the call using @tf.autograph.do_not_convert. Original error: could not get source code
WARNING:tensorflow:AutoGraph could not transform <function train_step at 0x7f1016cb5f80> and will run it as-is.
Please report this to the TensorFlow team. When filing the bug, set the verbosity to 10 (on Linux, `export AUTOGRAPH_VERBOSITY=10`) and attach the full output.
Cause: Unable to locate the source code of <function train_step at 0x7f1016cb5f80>. Note that functions defined in certain environments, like the interactive Python shell do not expose their source code. If that is the case, you should to define them in a .py source file. If you are certain the code is graph-compatible, wrap the call using @tf.autograph.do_not_convert. Original error: could not get source code
WARNING: AutoGraph could not transform <function train_step at 0x7f1016cb5f80> and will run it as-is.
Please report this to the TensorFlow team. When filing the bug, set the verbosity to 10 (on Linux, `export AUTOGRAPH_VERBOSITY=10`) and attach the full output.
Cause: Unable to locate the source code of <function train_step at 0x7f1016cb5f80>. Note that functions defined in certain environments, like the interactive Python shell do not expose their source code. If that is the case, you should to define them in a .py source file. If you are certain the code is graph-compatible, wrap the call using @tf.autograph.do_not_convert. Original error: could not get source code
WARNING:tensorflow:From /tmp/sonnet-nb-env/lib/python3.7/site-packages/tensorflow_core/python/ops/resource_variable_ops.py:1786: calling BaseResourceVariable.__init__ (from tensorflow.python.ops.resource_variable_ops) with constraint is deprecated and will be removed in a future version.
Instructions for updating:
If using Keras pass *_constraint arguments to layers.
WARNING:tensorflow:From /tmp/sonnet-nb-env/lib/python3.7/site-packages/tensorflow_core/python/ops/resource_variable_ops.py:1786: calling BaseResourceVariable.__init__ (from tensorflow.python.ops.resource_variable_ops) with constraint is deprecated and will be removed in a future version.
Instructions for updating:
If using Keras pass *_constraint arguments to layers.
100 train loss: 0.523625 recon_error: 0.483 perplexity: 10.356 vqvae loss: 0.041
200 train loss: 0.248232 recon_error: 0.223 perplexity: 18.294 vqvae loss: 0.026
300 train loss: 0.215068 recon_error: 0.190 perplexity: 23.106 vqvae loss: 0.025
400 train loss: 0.191891 recon_error: 0.164 perplexity: 29.139 vqvae loss: 0.028
500 train loss: 0.180945 recon_error: 0.147 perplexity: 34.253 vqvae loss: 0.033
600 train loss: 0.167115 recon_error: 0.134 perplexity: 39.961 vqvae loss: 0.033
700 train loss: 0.157724 recon_error: 0.124 perplexity: 46.521 vqvae loss: 0.033
800 train loss: 0.153761 recon_error: 0.119 perplexity: 53.559 vqvae loss: 0.035
900 train loss: 0.145033 recon_error: 0.112 perplexity: 62.442 vqvae loss: 0.033
1000 train loss: 0.137589 recon_error: 0.105 perplexity: 71.831 vqvae loss: 0.033
1100 train loss: 0.133044 recon_error: 0.101 perplexity: 79.135 vqvae loss: 0.032
1200 train loss: 0.129990 recon_error: 0.098 perplexity: 87.959 vqvae loss: 0.032
1300 train loss: 0.126507 recon_error: 0.095 perplexity: 96.704 vqvae loss: 0.031
1400 train loss: 0.122403 recon_error: 0.092 perplexity: 104.202 vqvae loss: 0.031
1500 train loss: 0.122003 recon_error: 0.091 perplexity: 112.476 vqvae loss: 0.031
1600 train loss: 0.120192 recon_error: 0.089 perplexity: 122.269 vqvae loss: 0.032
1700 train loss: 0.117041 recon_error: 0.086 perplexity: 129.887 vqvae loss: 0.031
1800 train loss: 0.115004 recon_error: 0.083 perplexity: 138.603 vqvae loss: 0.032
1900 train loss: 0.114134 recon_error: 0.082 perplexity: 147.545 vqvae loss: 0.032
2000 train loss: 0.112840 recon_error: 0.081 perplexity: 153.993 vqvae loss: 0.032
2100 train loss: 0.108815 recon_error: 0.077 perplexity: 161.729 vqvae loss: 0.031
2200 train loss: 0.108596 recon_error: 0.078 perplexity: 171.971 vqvae loss: 0.031
2300 train loss: 0.108132 recon_error: 0.077 perplexity: 181.157 vqvae loss: 0.031
2400 train loss: 0.106273 recon_error: 0.076 perplexity: 186.200 vqvae loss: 0.031
2500 train loss: 0.105936 recon_error: 0.075 perplexity: 194.301 vqvae loss: 0.031
2600 train loss: 0.103880 recon_error: 0.073 perplexity: 201.674 vqvae loss: 0.030
2700 train loss: 0.101655 recon_error: 0.072 perplexity: 207.131 vqvae loss: 0.030
2800 train loss: 0.102564 recon_error: 0.072 perplexity: 216.983 vqvae loss: 0.030
2900 train loss: 0.101613 recon_error: 0.072 perplexity: 219.649 vqvae loss: 0.030
3000 train loss: 0.101227 recon_error: 0.071 perplexity: 226.789 vqvae loss: 0.030
3100 train loss: 0.100786 recon_error: 0.071 perplexity: 235.522 vqvae loss: 0.030
3200 train loss: 0.100130 recon_error: 0.070 perplexity: 243.282 vqvae loss: 0.030
3300 train loss: 0.097764 recon_error: 0.067 perplexity: 249.584 vqvae loss: 0.030
3400 train loss: 0.100630 recon_error: 0.069 perplexity: 260.551 vqvae loss: 0.031
3500 train loss: 0.099929 recon_error: 0.068 perplexity: 266.012 vqvae loss: 0.032
3600 train loss: 0.099245 recon_error: 0.067 perplexity: 272.031 vqvae loss: 0.032
3700 train loss: 0.097812 recon_error: 0.066 perplexity: 279.691 vqvae loss: 0.032
3800 train loss: 0.097137 recon_error: 0.064 perplexity: 284.240 vqvae loss: 0.033
3900 train loss: 0.099217 recon_error: 0.066 perplexity: 293.507 vqvae loss: 0.034
4000 train loss: 0.098570 recon_error: 0.065 perplexity: 300.891 vqvae loss: 0.034
4100 train loss: 0.099238 recon_error: 0.065 perplexity: 306.762 vqvae loss: 0.034
4200 train loss: 0.098172 recon_error: 0.064 perplexity: 311.918 vqvae loss: 0.035
4300 train loss: 0.096449 recon_error: 0.063 perplexity: 316.246 vqvae loss: 0.034
4400 train loss: 0.096487 recon_error: 0.062 perplexity: 319.591 vqvae loss: 0.034
4500 train loss: 0.096092 recon_error: 0.062 perplexity: 322.313 vqvae loss: 0.034
4600 train loss: 0.096474 recon_error: 0.062 perplexity: 324.620 vqvae loss: 0.035
4700 train loss: 0.097075 recon_error: 0.063 perplexity: 324.357 vqvae loss: 0.035
4800 train loss: 0.094709 recon_error: 0.060 perplexity: 326.024 vqvae loss: 0.034
4900 train loss: 0.096557 recon_error: 0.061 perplexity: 327.701 vqvae loss: 0.035
5000 train loss: 0.096185 recon_error: 0.061 perplexity: 326.664 vqvae loss: 0.035
5100 train loss: 0.095646 recon_error: 0.060 perplexity: 327.617 vqvae loss: 0.035
5200 train loss: 0.094689 recon_error: 0.059 perplexity: 328.692 vqvae loss: 0.035
5300 train loss: 0.097047 recon_error: 0.061 perplexity: 327.988 vqvae loss: 0.036
5400 train loss: 0.096259 recon_error: 0.060 perplexity: 327.075 vqvae loss: 0.036
5500 train loss: 0.094588 recon_error: 0.059 perplexity: 327.083 vqvae loss: 0.036
5600 train loss: 0.095947 recon_error: 0.060 perplexity: 328.213 vqvae loss: 0.036
5700 train loss: 0.095466 recon_error: 0.059 perplexity: 329.375 vqvae loss: 0.036
5800 train loss: 0.094849 recon_error: 0.059 perplexity: 326.821 vqvae loss: 0.036
5900 train loss: 0.093799 recon_error: 0.058 perplexity: 328.409 vqvae loss: 0.036
6000 train loss: 0.095373 recon_error: 0.059 perplexity: 326.791 vqvae loss: 0.036
6100 train loss: 0.093989 recon_error: 0.059 perplexity: 325.959 vqvae loss: 0.035
6200 train loss: 0.095549 recon_error: 0.059 perplexity: 330.829 vqvae loss: 0.036
6300 train loss: 0.094730 recon_error: 0.058 perplexity: 330.906 vqvae loss: 0.036
6400 train loss: 0.095038 recon_error: 0.058 perplexity: 329.353 vqvae loss: 0.037
6500 train loss: 0.095891 recon_error: 0.059 perplexity: 330.197 vqvae loss: 0.037
6600 train loss: 0.094342 recon_error: 0.058 perplexity: 331.240 vqvae loss: 0.036
6700 train loss: 0.095096 recon_error: 0.058 perplexity: 330.618 vqvae loss: 0.037
6800 train loss: 0.095581 recon_error: 0.059 perplexity: 324.493 vqvae loss: 0.037
6900 train loss: 0.094467 recon_error: 0.058 perplexity: 328.868 vqvae loss: 0.037
7000 train loss: 0.092967 recon_error: 0.057 perplexity: 328.276 vqvae loss: 0.036
7100 train loss: 0.094339 recon_error: 0.058 perplexity: 327.318 vqvae loss: 0.037
7200 train loss: 0.095227 recon_error: 0.058 perplexity: 326.306 vqvae loss: 0.037
7300 train loss: 0.093832 recon_error: 0.057 perplexity: 328.262 vqvae loss: 0.037
7400 train loss: 0.093331 recon_error: 0.057 perplexity: 327.987 vqvae loss: 0.037
7500 train loss: 0.094718 recon_error: 0.058 perplexity: 328.948 vqvae loss: 0.037
7600 train loss: 0.094199 recon_error: 0.058 perplexity: 328.468 vqvae loss: 0.037
7700 train loss: 0.094603 recon_error: 0.058 perplexity: 327.501 vqvae loss: 0.037
7800 train loss: 0.092299 recon_error: 0.056 perplexity: 327.630 vqvae loss: 0.037
7900 train loss: 0.095228 recon_error: 0.058 perplexity: 329.946 vqvae loss: 0.037
8000 train loss: 0.094291 recon_error: 0.058 perplexity: 326.790 vqvae loss: 0.037
8100 train loss: 0.094481 recon_error: 0.057 perplexity: 328.667 vqvae loss: 0.037
8200 train loss: 0.093992 recon_error: 0.057 perplexity: 329.655 vqvae loss: 0.037
8300 train loss: 0.093976 recon_error: 0.057 perplexity: 323.950 vqvae loss: 0.037
8400 train loss: 0.093422 recon_error: 0.057 perplexity: 324.523 vqvae loss: 0.036
8500 train loss: 0.092898 recon_error: 0.056 perplexity: 325.402 vqvae loss: 0.037
8600 train loss: 0.094298 recon_error: 0.057 perplexity: 329.251 vqvae loss: 0.037
8700 train loss: 0.094489 recon_error: 0.057 perplexity: 331.027 vqvae loss: 0.037
8800 train loss: 0.093022 recon_error: 0.056 perplexity: 327.495 vqvae loss: 0.037
8900 train loss: 0.093427 recon_error: 0.057 perplexity: 328.008 vqvae loss: 0.037
9000 train loss: 0.094884 recon_error: 0.058 perplexity: 327.057 vqvae loss: 0.037
9100 train loss: 0.093559 recon_error: 0.056 perplexity: 331.800 vqvae loss: 0.037
9200 train loss: 0.093282 recon_error: 0.056 perplexity: 328.689 vqvae loss: 0.037
9300 train loss: 0.092217 recon_error: 0.056 perplexity: 323.903 vqvae loss: 0.036
9400 train loss: 0.093902 recon_error: 0.057 perplexity: 326.350 vqvae loss: 0.037
9500 train loss: 0.093772 recon_error: 0.057 perplexity: 325.627 vqvae loss: 0.037
9600 train loss: 0.093123 recon_error: 0.056 perplexity: 327.352 vqvae loss: 0.037
9700 train loss: 0.092934 recon_error: 0.056 perplexity: 328.674 vqvae loss: 0.037
9800 train loss: 0.093284 recon_error: 0.056 perplexity: 329.437 vqvae loss: 0.037
9900 train loss: 0.094147 recon_error: 0.057 perplexity: 330.146 vqvae loss: 0.037
10000 train loss: 0.092876 recon_error: 0.056 perplexity: 326.349 vqvae loss: 0.037
CPU times: user 1h 47min 46s, sys: 14min 12s, total: 2h 1min 59s
Wall time: 4min 29s

 

損失をプロットする

f = plt.figure(figsize=(16,8))
ax = f.add_subplot(1,2,1)
ax.plot(train_recon_errors)
ax.set_yscale('log')
ax.set_title('NMSE.')

ax = f.add_subplot(1,2,2)
ax.plot(train_perplexities)
ax.set_title('Average codebook usage (perplexity).')
Text(0.5, 1.0, 'Average codebook usage (perplexity).')

 

再構築を見る

# Reconstructions
train_batch = next(iter(train_dataset))
valid_batch = next(iter(valid_dataset))

# Put data through the model with is_training=False, so that in the case of 
# using EMA the codebook is not updated.
train_reconstructions = model(train_batch['image'],
                              is_training=False)['x_recon'].numpy()
valid_reconstructions = model(valid_batch['image'],
                              is_training=False)['x_recon'].numpy()


def convert_batch_to_image_grid(image_batch):
  reshaped = (image_batch.reshape(4, 8, 32, 32, 3)
              .transpose(0, 2, 1, 3, 4)
              .reshape(4 * 32, 8 * 32, 3))
  return reshaped + 0.5



f = plt.figure(figsize=(16,8))
ax = f.add_subplot(2,2,1)
ax.imshow(convert_batch_to_image_grid(train_batch['image'].numpy()),
          interpolation='nearest')
ax.set_title('training data originals')
plt.axis('off')

ax = f.add_subplot(2,2,2)
ax.imshow(convert_batch_to_image_grid(train_reconstructions),
          interpolation='nearest')
ax.set_title('training data reconstructions')
plt.axis('off')

ax = f.add_subplot(2,2,3)
ax.imshow(convert_batch_to_image_grid(valid_batch['image'].numpy()),
          interpolation='nearest')
ax.set_title('validation data originals')
plt.axis('off')

ax = f.add_subplot(2,2,4)
ax.imshow(convert_batch_to_image_grid(valid_reconstructions),
          interpolation='nearest')
ax.set_title('validation data reconstructions')
plt.axis('off')
WARNING:matplotlib.image:Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
WARNING:matplotlib.image:Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
(-0.5, 255.5, 127.5, -0.5)

 

以上






TensorFlow 2.0 : 上級 Tutorials : 生成 :- 畳み込み変分オートエンコーダ

TensorFlow 2.0 : 上級 Tutorials : 生成 :- 畳み込み変分オートエンコーダ (翻訳/解説)

翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 11/23/2019

* 本ページは、TensorFlow org サイトの TF 2.0 – Advanced Tutorials – Generative の以下のページを翻訳した上で
適宜、補足説明したものです:

* サンプルコードの動作確認はしておりますが、必要な場合には適宜、追加改変しています。
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。

 

無料セミナー開催中 クラスキャット主催 人工知能 & ビジネス Web セミナー

人工知能とビジネスをテーマにウェビナー (WEB セミナー) を定期的に開催しています。スケジュールは弊社 公式 Web サイト でご確認頂けます。
  • お住まいの地域に関係なく Web ブラウザからご参加頂けます。事前登録 が必要ですのでご注意ください。
  • Windows PC のブラウザからご参加が可能です。スマートデバイスもご利用可能です。

お問合せ : 本件に関するお問い合わせ先は下記までお願いいたします。

株式会社クラスキャット セールス・マーケティング本部 セールス・インフォメーション
E-Mail:sales-info@classcat.com ; WebSite: https://www.classcat.com/
Facebook: https://www.facebook.com/ClassCatJP/

 

生成 :- 畳み込み変分オートエンコーダ

このノートブックは変分オートエンコーダを訓練することにより手書き数字をどのように生成するかを実演します (1, 2)。

# to generate gifs
!pip install -q imageio

 

TensorFlow と他のライブラリをインポートする

from __future__ import absolute_import, division, print_function, unicode_literals

import tensorflow as tf

import os
import time
import numpy as np
import glob
import matplotlib.pyplot as plt
import PIL
import imageio

from IPython import display

 

MNIST データセットをロードする

各 MNIST 画像は元々は 784 整数ベクトルで、その各々は 0-255 の間でピクセル強度を表します。私達のモデルでは各ピクセルを Bernoulli 分布でモデル化して、データセットを統計的に二値化します。

(train_images, _), (test_images, _) = tf.keras.datasets.mnist.load_data()
train_images = train_images.reshape(train_images.shape[0], 28, 28, 1).astype('float32')
test_images = test_images.reshape(test_images.shape[0], 28, 28, 1).astype('float32')

# Normalizing the images to the range of [0., 1.]
train_images /= 255.
test_images /= 255.

# Binarization
train_images[train_images >= .5] = 1.
train_images[train_images < .5] = 0.
test_images[test_images >= .5] = 1.
test_images[test_images < .5] = 0.
TRAIN_BUF = 60000
BATCH_SIZE = 100

TEST_BUF = 10000

 

バッチを作成してデータセットをシャッフルするために tf.data を使用する

train_dataset = tf.data.Dataset.from_tensor_slices(train_images).shuffle(TRAIN_BUF).batch(BATCH_SIZE)
test_dataset = tf.data.Dataset.from_tensor_slices(test_images).shuffle(TEST_BUF).batch(BATCH_SIZE)

 

生成と推論ネットワークを tf.keras.Sequential で配線する (= wire up)

私達の VAE サンプルでは、生成と推論ネットワークのために 2 つの小さな ConvNet を使用します。これらのニューラルネットは小さいので、コードを単純化するために tf.keras.Sequential を使用します。以下の記述において $x$ と $z$ をそれぞれ観測と潜在 (= latent) 変数を示すものとします。

 

生成ネットワーク (= Generative Network)

これは生成モデルを定義します、これは入力として潜在エンコーディングを取り、観測の条件付き分布, i.e. $p(x|z)$ のためのパラメータを出力します。更に、潜在変数のために単位ガウス事前分布 $p(z)$ を使用します。

 

推論ネットワーク

これは近似事後分布 $q(z|x)$ を定義します、これは入力として観測を取り潜在表現の条件付き分布のためのパラメータのセットを出力します。この例では、この分布を単純に対角ガウス分布 (= diagonal Gaussian) としてモデル化します。この場合、推論ネットワークは factorized Gaussian の平均 (= mean) と対数分散 (= log-variance) パラメータを出力します (分散の代わりの対数分散は直接的には数値的安定性のためです)。

 

再パラメータ化 (= Reparameterization) トリック

最適化の間、最初に単位ガウス分布からサンプリングすることにより $q(z|x)$ からサンプリングできて、それから標準偏差を乗じて平均を加えます。これは勾配がサンプルを通して推論ネットワーク・パラメータに渡せることを確かなものにします。

 

ネットワーク・アーキテクチャ

推論ネットワークのために、2 つの畳み込み層とそれに続く完全結合層を使用します。生成ネットワークでは、完全結合層と続く 3 つの convolution transpose 層を使用してこのアーキテクチャを反映 (= mirror) します (a.k.a. あるコンテキストでは deconvolutional 層)。注意してください、VAE を訓練するときバッチ正規化の使用を回避することは一般的な実践です、何故ならばミニバッチを使用することによる追加的な偶然性はサンプリングからの偶然性の上に不安定性を悪化させるかもしれないためです。

class CVAE(tf.keras.Model):
  def __init__(self, latent_dim):
    super(CVAE, self).__init__()
    self.latent_dim = latent_dim
    self.inference_net = 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'),
          tf.keras.layers.Conv2D(
              filters=64, kernel_size=3, strides=(2, 2), activation='relu'),
          tf.keras.layers.Flatten(),
          # No activation
          tf.keras.layers.Dense(latent_dim + latent_dim),
      ]
    )

    self.generative_net = 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(
              filters=64,
              kernel_size=3,
              strides=(2, 2),
              padding="SAME",
              activation='relu'),
          tf.keras.layers.Conv2DTranspose(
              filters=32,
              kernel_size=3,
              strides=(2, 2),
              padding="SAME",
              activation='relu'),
          # No activation
          tf.keras.layers.Conv2DTranspose(
              filters=1, kernel_size=3, strides=(1, 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)

  def encode(self, x):
    mean, logvar = tf.split(self.inference_net(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

  def decode(self, z, apply_sigmoid=False):
    logits = self.generative_net(z)
    if apply_sigmoid:
      probs = tf.sigmoid(logits)
      return probs

    return logits

 

損失関数と optimizer を定義する

VAE は周辺対数尤度上のエビデンス下限 (ELBO, evidence lower bound) を最大化することにより訓練されます :

$$\log p(x) \ge \text{ELBO} = \mathbb{E}_{q(z|x)}\left[\log \frac{p(x, z)}{q(z|x)}\right].$$

実際に、この期待値の単一サンプル・モンテカルロ推定を最適化します :

$$\log p(x| z) + \log p(z) - \log q(z|x),$$

ここで $z$ は $q(z|x)$ からサンプリングされます。

Note: 私達はまた KL 項を解析的に計算することもできますが、ここでは単純化のために総ての 3 つの項をモンテカルロ estimator に組み入れます。

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)

@tf.function
def compute_loss(model, x):
  mean, logvar = model.encode(x)
  z = model.reparameterize(mean, logvar)
  x_logit = model.decode(z)

  cross_ent = tf.nn.sigmoid_cross_entropy_with_logits(logits=x_logit, labels=x)
  logpx_z = -tf.reduce_sum(cross_ent, 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 compute_apply_gradients(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))

 

訓練

  • データセットに渡り反復することから始めます。
  • 各反復の間、近似事後分布 $q(z|x)$ の平均と対数分散パラメータのセットを得るために画像をエンコーダに渡します。
  • それから $q(z|x)$ からサンプリングするために再パラメータ化トリックを適用します。
  • 最後に、生成分布 $p(x|z)$ のロジットを得るために再パラメータ化されたサンプルをデコーダに渡します。
  • Note: 訓練セットの 60k データポイントとテストセットの 10k データポイント持つ、keras によりロードされたデータセットを使用しますので、テストセット上の結果としての ELBO は (Larochelle の MNIST の動的二値化 (= dynamic binarization) を使用している) 文献で報告されている結果よりも僅かにより高いです。

 

画像を生成する

  • 訓練の後、幾つかの画像を生成する時です。
  • 単位ガウス事前分布 $p(z)$ から潜在ベクトルのセットをサンプリングすることから始めます。
  • それから generator は潜在サンプル $z$ を観測のロジットに変換し、分布 $p(x|z)$ を与えます。
  • ここで Bernoulli 分布の確率をプロットします。
epochs = 100
latent_dim = 50
num_examples_to_generate = 16

# keeping the random vector constant for generation (prediction) so
# it will be easier to see the improvement.
random_vector_for_generation = tf.random.normal(
    shape=[num_examples_to_generate, latent_dim])
model = CVAE(latent_dim)
def generate_and_save_images(model, epoch, test_input):
  predictions = model.sample(test_input)
  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')

  # tight_layout minimizes the overlap between 2 sub-plots
  plt.savefig('image_at_epoch_{:04d}.png'.format(epoch))
  plt.show()
generate_and_save_images(model, 0, random_vector_for_generation)

for epoch in range(1, epochs + 1):
  start_time = time.time()
  for train_x in train_dataset:
    compute_apply_gradients(model, train_x, optimizer)
  end_time = time.time()

  if epoch % 1 == 0:
    loss = tf.keras.metrics.Mean()
    for test_x in test_dataset:
      loss(compute_loss(model, test_x))
    elbo = -loss.result()
    display.clear_output(wait=False)
    print('Epoch: {}, Test set ELBO: {}, '
          'time elapse for current epoch {}'.format(epoch,
                                                    elbo,
                                                    end_time - start_time))
    generate_and_save_images(
        model, epoch, random_vector_for_generation)
Epoch: 100, Test set ELBO: -78.4385757446289, time elapse for current epoch 2.0967400074005127

 

エポック数を使用して画像を表示する

def display_image(epoch_no):
  return PIL.Image.open('image_at_epoch_{:04d}.png'.format(epoch_no))
plt.imshow(display_image(epochs))
plt.axis('off')# Display images
(-0.5, 287.5, 287.5, -0.5)

 

総てのセーブされた画像の GIF を生成する

anim_file = 'cvae.gif'

with imageio.get_writer(anim_file, mode='I') as writer:
  filenames = glob.glob('image*.png')
  filenames = sorted(filenames)
  last = -1
  for i,filename in enumerate(filenames):
    frame = 2*(i**0.5)
    if round(frame) > round(last):
      last = frame
    else:
      continue
    image = imageio.imread(filename)
    writer.append_data(image)
  image = imageio.imread(filename)
  writer.append_data(image)

import IPython
if IPython.version_info >= (6,2,0,''):
  display.Image(filename=anim_file)

Colab で作業している場合には下のコードでアニメーションをダウンロードできます :

try:
  from google.colab import files
except ImportError:
   pass
else:
  files.download(anim_file)
 

以上






TensorFlow 2.0 Beta : 上級 Tutorials : 画像生成 :- 畳み込み変分オートエンコーダ

TensorFlow 2.0 Beta : 上級 Tutorials : 画像生成 :- 畳み込み変分オートエンコーダ (翻訳/解説)

翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 07/08/2019

* 本ページは、TensorFlow の本家サイトの TF 2.0 Beta – Advanced Tutorials – Image generation の以下のページを翻訳した上で適宜、補足説明したものです:

* サンプルコードの動作確認はしておりますが、必要な場合には適宜、追加改変しています。
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。

 

画像生成 :- 畳み込み変分オートエンコーダ

このノートブックは変分オートエンコーダを訓練することにより手書き数字をどのように生成するかを実演します (1, 2)。

# to generate gifs
!pip install -q imageio

 

TensorFlow と他のライブラリをインポートする

from __future__ import absolute_import, division, print_function, unicode_literals

!pip install -q tensorflow-gpu==2.0.0-beta1
import tensorflow as tf

import os
import time
import numpy as np
import glob
import matplotlib.pyplot as plt
import PIL
import imageio

from IPython import display

 

MNIST データセットをロードする

各 MNIST 画像は元々は 784 整数ベクトルで、その各々は 0-255 の間でピクセル強度を表します。私達のモデルでは各ピクセルを Bernoulli 分布でモデル化して、データセットを統計的に二値化します。

(train_images, _), (test_images, _) = tf.keras.datasets.mnist.load_data()
train_images = train_images.reshape(train_images.shape[0], 28, 28, 1).astype('float32')
test_images = test_images.reshape(test_images.shape[0], 28, 28, 1).astype('float32')

# Normalizing the images to the range of [0., 1.]
train_images /= 255.
test_images /= 255.

# Binarization
train_images[train_images >= .5] = 1.
train_images[train_images < .5] = 0.
test_images[test_images >= .5] = 1.
test_images[test_images < .5] = 0.
TRAIN_BUF = 60000
BATCH_SIZE = 100

TEST_BUF = 10000

 

バッチを作成してデータセットをシャッフルするために tf.data を使用する

train_dataset = tf.data.Dataset.from_tensor_slices(train_images).shuffle(TRAIN_BUF).batch(BATCH_SIZE)
test_dataset = tf.data.Dataset.from_tensor_slices(test_images).shuffle(TEST_BUF).batch(BATCH_SIZE)

 

生成と推論ネットワークを tf.keras.Sequential で配線する (= wire up)

私達の VAE サンプルでは、生成と推論ネットワークのために 2 つの小さな ConvNet を使用します。これらのニューラルネットは小さいので、コードを単純化するために tf.keras.Sequential を使用します。以下の記述において $x$ と $z$ をそれぞれ観測と潜在 (= latent) 変数を示すものとします。

 

生成ネットワーク (= Generative Network)

これは生成モデルを定義します、これは入力として潜在エンコーディングを取り、観測の条件付き分布, i.e. $p(x|z)$ のためのパラメータを出力します。更に、潜在変数のために単位ガウス事前分布 $p(z)$ を使用します。

 

推論ネットワーク

これは近似事後分布 $q(z|x)$ を定義します、これは入力として観測を取り潜在表現の条件付き分布のためのパラメータのセットを出力します。この例では、この分布を単純に対角ガウス分布 (= diagonal Gaussian) としてモデル化します。この場合、推論ネットワークは factorized Gaussian の平均 (= mean) と対数分散 (= log-variance) パラメータを出力します (分散の代わりの対数分散は直接的には数値的安定性のためです)。

 

再パラメータ化 (= Reparameterization) トリック

最適化の間、最初に単位ガウス分布からサンプリングすることにより $q(z|x)$ からサンプリングできて、それから標準偏差を乗じて平均を加えます。これは勾配がサンプルを通して推論ネットワーク・パラメータに渡せることを確かなものにします。

 

ネットワーク・アーキテクチャ

推論ネットワークのためには、2 つの畳み込み層とそれに続く完全結合層を使用します。生成ネットワークでは、完全結合層と続く 3 つの convolution transpose 層を使用してこのアーキテクチャを反映 (= mirror) します (a.k.a. あるコンテキストでは deconvolutional 層)。注意してください、VAE を訓練するときバッチ正規化の使用を回避することは一般的な実践です、何故ならばミニバッチを使用することによる追加的な偶然性はサンプリングからの偶然性の上に不安定性を悪化させるかもしれないためです。

class CVAE(tf.keras.Model):
  def __init__(self, latent_dim):
    super(CVAE, self).__init__()
    self.latent_dim = latent_dim
    self.inference_net = 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'),
          tf.keras.layers.Conv2D(
              filters=64, kernel_size=3, strides=(2, 2), activation='relu'),
          tf.keras.layers.Flatten(),
          # No activation
          tf.keras.layers.Dense(latent_dim + latent_dim),
      ]
    )

    self.generative_net = 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(
              filters=64,
              kernel_size=3,
              strides=(2, 2),
              padding="SAME",
              activation='relu'),
          tf.keras.layers.Conv2DTranspose(
              filters=32,
              kernel_size=3,
              strides=(2, 2),
              padding="SAME",
              activation='relu'),
          # No activation
          tf.keras.layers.Conv2DTranspose(
              filters=1, kernel_size=3, strides=(1, 1), padding="SAME"),
        ]
    )

  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)

  def encode(self, x):
    mean, logvar = tf.split(self.inference_net(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

  def decode(self, z, apply_sigmoid=False):
    logits = self.generative_net(z)
    if apply_sigmoid:
      probs = tf.sigmoid(logits)
      return probs

    return logits

 

損失関数と optimizer を定義する

VAE は周辺対数尤度上のエビデンス下限 (ELBO, evidence lower bound) を最大化することにより訓練されます :

$$\log p(x) \ge \text{ELBO} = \mathbb{E}_{q(z|x)}\left[\log \frac{p(x, z)}{q(z|x)}\right].$$

実際に、この期待値の単一サンプル・モンテカルロ推定を最適化します :

$$\log p(x| z) + \log p(z) - \log q(z|x),$$

ここで $z$ は $q(z|x)$ からサンプリングされます。

Note: 私達はまた KL 項を解析的に計算することもできますが、ここでは単純化のために総ての 3 つの項をモンテカルロ estimator に組み入れます。

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_ent = tf.nn.sigmoid_cross_entropy_with_logits(logits=x_logit, labels=x)
  logpx_z = -tf.reduce_sum(cross_ent, 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)

def compute_gradients(model, x):
  with tf.GradientTape() as tape:
    loss = compute_loss(model, x)
  return tape.gradient(loss, model.trainable_variables), loss

def apply_gradients(optimizer, gradients, variables):
  optimizer.apply_gradients(zip(gradients, variables))

 

訓練

  • データセットに渡り反復することから始めます。
  • 各反復の間、近似事後分布 $q(z|x)$ の平均と対数分散パラメータのセットを得るために画像をエンコーダに渡します。
  • それから $q(z|x)$ からサンプリングするために再パラメータ化トリックを適用します。
  • 最後に、生成分布 $p(x|z)$ のロジットを得るために再パラメータ化されたサンプルをデコーダに渡します。
  • Note: 訓練セットの 60k データポイントとテストセットの 10k データポイント持つ、keras によりロードされたデータセットを使用しますので、テストセット上の結果としての ELBO は (Larochelle の MNIST の動的二値化 (= dynamic binarization) を使用している) 文献で報告されている結果よりも僅かにより高いです。

 

画像を生成する

  • 訓練の後、幾つかの画像を生成する時です。
  • 単位ガウス事前分布 $p(z)$ から潜在ベクトルのセットをサンプリングすることから始めます。
  • それから generator は潜在サンプル $z$ を観測のロジットに変換し、分布 $p(x|z)$ を与えます。
  • ここで Bernoulli 分布の確率をプロットします。
epochs = 100
latent_dim = 50
num_examples_to_generate = 16

# keeping the random vector constant for generation (prediction) so
# it will be easier to see the improvement.
random_vector_for_generation = tf.random.normal(
    shape=[num_examples_to_generate, latent_dim])
model = CVAE(latent_dim)
def generate_and_save_images(model, epoch, test_input):
  predictions = model.sample(test_input)
  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')

  # tight_layout minimizes the overlap between 2 sub-plots
  plt.savefig('image_at_epoch_{:04d}.png'.format(epoch))
  plt.show()
generate_and_save_images(model, 0, random_vector_for_generation)

for epoch in range(1, epochs + 1):
  start_time = time.time()
  for train_x in train_dataset:
    gradients, loss = compute_gradients(model, train_x)
    apply_gradients(optimizer, gradients, model.trainable_variables)
  end_time = time.time()

  if epoch % 1 == 0:
    loss = tf.keras.metrics.Mean()
    for test_x in test_dataset:
      loss(compute_loss(model, test_x))
    elbo = -loss.result()
    display.clear_output(wait=False)
    print('Epoch: {}, Test set ELBO: {}, '
          'time elapse for current epoch {}'.format(epoch,
                                                    elbo,
                                                    end_time - start_time))
    generate_and_save_images(
        model, epoch, random_vector_for_generation)
Epoch: 100, Test set ELBO: -77.58395385742188, time elapse for current epoch 21.326345205307007

 

エポック数を使用して画像を表示する

def display_image(epoch_no):
  return PIL.Image.open('image_at_epoch_{:04d}.png'.format(epoch_no))
plt.imshow(display_image(epochs))
plt.axis('off')# Display images
(-0.5, 287.5, 287.5, -0.5)

 

総てのセーブされた画像の GIF を生成する

anim_file = 'cvae.gif'

with imageio.get_writer(anim_file, mode='I') as writer:
  filenames = glob.glob('image*.png')
  filenames = sorted(filenames)
  last = -1
  for i,filename in enumerate(filenames):
    frame = 2*(i**0.5)
    if round(frame) > round(last):
      last = frame
    else:
      continue
    image = imageio.imread(filename)
    writer.append_data(image)
  image = imageio.imread(filename)
  writer.append_data(image)

import IPython
if IPython.version_info >= (6,2,0,''):
  display.Image(filename=anim_file)

Colab で作業している場合には下のコードでアニメーションをダウンロードできます :

try:
  from google.colab import files
except ImportError:
  pass
else:
  files.download(anim_file)
 

以上






TensorFlow 2.0 Alpha : 上級 Tutorials : GAN と VAE :- 畳み込み変分オートエンコーダ

TensorFlow 2.0 Alpha : 上級 Tutorials : GAN と VAE :- 畳み込み変分オートエンコーダ (翻訳/解説)

翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 04/09/2019

* 本ページは、TensorFlow の本家サイトの TF 2.0 Alpha – Advanced Tutorials – GANs and VAEs の以下のページを翻訳した上で適宜、補足説明したものです:

* サンプルコードの動作確認はしておりますが、必要な場合には適宜、追加改変しています。
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。

 

GAN と VAE :- 畳み込み変分オートエンコーダ

このノートブックは変分オートエンコーダを訓練することにより手書き数字をどのように生成するかを実演します (1, 2)。

# to generate gifs
!pip install -q imageio
Requirement already satisfied: imageio in /usr/local/lib/python3.6/dist-packages (2.4.1)
Requirement already satisfied: numpy in /usr/local/lib/python3.6/dist-packages (from imageio) (1.14.6)
Requirement already satisfied: pillow in /usr/local/lib/python3.6/dist-packages (from imageio) (4.1.1)
Requirement already satisfied: olefile in /usr/local/lib/python3.6/dist-packages (from pillow->imageio) (0.46)

 

TensorFlow と他のライブラリをインポートする

from __future__ import absolute_import, division, print_function, unicode_literals

!pip install -q tensorflow-gpu==2.0.0-alpha0
import tensorflow as tf

import os
import time
import numpy as np
import glob
import matplotlib.pyplot as plt
import PIL
import imageio

from IPython import display
Requirement already satisfied: tf-nightly-gpu-2.0-preview in /usr/local/lib/python3.6/dist-packages (2.0.0.dev20190329)
Requirement already satisfied: tensorflow-estimator-2.0-preview in /usr/local/lib/python3.6/dist-packages (from tf-nightly-gpu-2.0-preview) (1.14.0.dev2019032900)
Requirement already satisfied: tb-nightly<1.15.0a0,>=1.14.0a0 in /usr/local/lib/python3.6/dist-packages (from tf-nightly-gpu-2.0-preview) (1.14.0a20190319)
Requirement already satisfied: keras-applications>=1.0.6 in /usr/local/lib/python3.6/dist-packages (from tf-nightly-gpu-2.0-preview) (1.0.7)
Requirement already satisfied: absl-py>=0.7.0 in /usr/local/lib/python3.6/dist-packages (from tf-nightly-gpu-2.0-preview) (0.7.1)
Requirement already satisfied: protobuf>=3.6.1 in /usr/local/lib/python3.6/dist-packages (from tf-nightly-gpu-2.0-preview) (3.7.1)
Requirement already satisfied: google-pasta>=0.1.2 in /usr/local/lib/python3.6/dist-packages (from tf-nightly-gpu-2.0-preview) (0.1.4)
Requirement already satisfied: grpcio>=1.8.6 in /usr/local/lib/python3.6/dist-packages (from tf-nightly-gpu-2.0-preview) (1.15.0)
Requirement already satisfied: numpy<2.0,>=1.14.5 in /usr/local/lib/python3.6/dist-packages (from tf-nightly-gpu-2.0-preview) (1.14.6)
Requirement already satisfied: termcolor>=1.1.0 in /usr/local/lib/python3.6/dist-packages (from tf-nightly-gpu-2.0-preview) (1.1.0)
Requirement already satisfied: wheel>=0.26 in /usr/local/lib/python3.6/dist-packages (from tf-nightly-gpu-2.0-preview) (0.33.1)
Requirement already satisfied: keras-preprocessing>=1.0.5 in /usr/local/lib/python3.6/dist-packages (from tf-nightly-gpu-2.0-preview) (1.0.9)
Requirement already satisfied: astor>=0.6.0 in /usr/local/lib/python3.6/dist-packages (from tf-nightly-gpu-2.0-preview) (0.7.1)
Requirement already satisfied: gast>=0.2.0 in /usr/local/lib/python3.6/dist-packages (from tf-nightly-gpu-2.0-preview) (0.2.2)
Requirement already satisfied: six>=1.10.0 in /usr/local/lib/python3.6/dist-packages (from tf-nightly-gpu-2.0-preview) (1.11.0)
Requirement already satisfied: werkzeug>=0.11.15 in /usr/local/lib/python3.6/dist-packages (from tb-nightly<1.15.0a0,>=1.14.0a0->tf-nightly-gpu-2.0-preview) (0.15.1)
Requirement already satisfied: markdown>=2.6.8 in /usr/local/lib/python3.6/dist-packages (from tb-nightly<1.15.0a0,>=1.14.0a0->tf-nightly-gpu-2.0-preview) (3.1)
Requirement already satisfied: h5py in /usr/local/lib/python3.6/dist-packages (from keras-applications>=1.0.6->tf-nightly-gpu-2.0-preview) (2.8.0)
Requirement already satisfied: setuptools in /usr/local/lib/python3.6/dist-packages (from protobuf>=3.6.1->tf-nightly-gpu-2.0-preview) (40.8.0)

 

MNIST データセットをロードする

各 MNIST 画像は元々は 784 整数ベクトルで、その各々は 0-255 の間でピクセル強度を表します。私達のモデルでは各ピクセルを Bernoulli 分布でモデル化して、データセットを統計的に二値化します。

(train_images, _), (test_images, _) = tf.keras.datasets.mnist.load_data()
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/mnist.npz
11493376/11490434 [==============================] - 0s 0us/step
11501568/11490434 [==============================] - 0s 0us/step
train_images = train_images.reshape(train_images.shape[0], 28, 28, 1).astype('float32')
test_images = test_images.reshape(test_images.shape[0], 28, 28, 1).astype('float32')

# Normalizing the images to the range of [0., 1.]
train_images /= 255.
test_images /= 255.

# Binarization
train_images[train_images >= .5] = 1.
train_images[train_images < .5] = 0.
test_images[test_images >= .5] = 1.
test_images[test_images < .5] = 0.
TRAIN_BUF = 60000
BATCH_SIZE = 100

TEST_BUF = 10000

 

生成と推論ネットワークを tf.keras.Sequential で配線する (= wire up)

私達の VAE サンプルでは、生成と推論ネットワークのために 2 つの小さな ConvNet を使用します。これらのニューラルネットは小さいので、コードを単純化するために tf.keras.Sequential を使用します。以下の記述において $x$ と $z$ をそれぞれ観測と潜在 (= latent) 変数を示すものとします。

 

生成ネットワーク (= Generative Network)

これは生成モデルを定義します、これは入力として潜在エンコーディングを取り、観測の条件付き分布, i.e. $p(x|z)$ のためのパラメータを出力します。更に、潜在変数のために単位ガウス事前分布 (訳注: i.e. 標準正規事前分布) $p(z)$ を使用します。

 

推論ネットワーク

これは近似事後分布 $q(z|x)$ を定義します、これは入力として観測を取り潜在表現の条件付き分布のためのパラメータのセットを出力します。この例では、この分布を単純に対角ガウス分布 (= diagonal Gaussian) としてモデル化します。この場合、推論ネットワークは factorized Gaussian の平均 (= mean) と対数分散 (= log-variance) パラメータを出力します (分散の代わりに対数分散は直接的には数値的安定性のためです)。

 

再パラメータ化 (= Reparameterization) トリック

最適化の間、最初に標準正規分布からサンプリングすることにより $q(z|x)$ からサンプリングできて、それから標準偏差を乗じて平均を加えます。これは勾配がサンプルを通して推論ネットワーク・パラメータに渡せることを確かなものにします。

 

ネットワーク・アーキテクチャ

推論ネットワークのためには、2 つの畳み込み層と続く完全結合層を使用します。生成ネットワークでは、完全結合層と続く 3 つの convolution transpose 層を使用してこのアーキテクチャを反映 (= mirror) します (a.k.a. あるコンテキストでは deconvolutional 層)。注意してください、VAE を訓練するときバッチ正規化の使用を回避することは一般的な実践です、何故ならばミニバッチを使用することによる追加的な偶然性はサンプリングからの偶然性の上に不安定性を悪化させるかもしれないためです。

class CVAE(tf.keras.Model):
  def __init__(self, latent_dim):
    super(CVAE, self).__init__()
    self.latent_dim = latent_dim
    self.inference_net = 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'),
          tf.keras.layers.Conv2D(
              filters=64, kernel_size=3, strides=(2, 2), activation='relu'),
          tf.keras.layers.Flatten(),
          # No activation
          tf.keras.layers.Dense(latent_dim + latent_dim),
      ]
    )

    self.generative_net = 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(
              filters=64,
              kernel_size=3,
              strides=(2, 2),
              padding="SAME",
              activation='relu'),
          tf.keras.layers.Conv2DTranspose(
              filters=32,
              kernel_size=3,
              strides=(2, 2),
              padding="SAME",
              activation='relu'),
          # No activation
          tf.keras.layers.Conv2DTranspose(
              filters=1, kernel_size=3, strides=(1, 1), padding="SAME"),
        ]
    )

  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)

  def encode(self, x):
    mean, logvar = tf.split(self.inference_net(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

  def decode(self, z, apply_sigmoid=False):
    logits = self.generative_net(z)
    if apply_sigmoid:
      probs = tf.sigmoid(logits)
      return probs

    return logits

 

損失関数と optimizer を定義する

VAE は周辺対数尤度上のエビデンス下限 (ELBO, evidence lower bound) を最大化することにより訓練されます :

$$\log p(x) \ge \text{ELBO} = \mathbb{E}_{q(z|x)}\left[\log \frac{p(x, z)}{q(z|x)}\right].$$

実際に、この期待値の単一サンプルのモンテカルロ推定を最適化します :

$$\log p(x| z) + \log p(z) - \log q(z|x),$$

ここで $z$ は $q(z|x)$ からサンプリングされます。

Note: 私達はまた KL 項を解析的に計算することもできますが、ここでは単純化のために総ての 3 つの項をモンテカルロ estimator に組み入れます。

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_ent = tf.nn.sigmoid_cross_entropy_with_logits(logits=x_logit, labels=x)
  logpx_z = -tf.reduce_sum(cross_ent, 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)

def compute_gradients(model, x):
  with tf.GradientTape() as tape:
    loss = compute_loss(model, x)
  return tape.gradient(loss, model.trainable_variables), loss

def apply_gradients(optimizer, gradients, variables):
  optimizer.apply_gradients(zip(gradients, variables))

 

訓練

  • データセットに渡り反復することから始めます。
  • 各反復の間、近似事後分布 $q(z|x)$ の平均と対数分散パラメータのセットを得るために画像をエンコーダに渡します。
  • それから $q(z|x)$ からサンプリングするために再パラメータ化トリックを適用します。
  • 最後に、生成分布 $p(x|z)$ のロジットを得るために再パラメータ化されたサンプルをデコーダに渡します。
  • Note: 訓練セットの 60k データポイントとテストセットの 10k データポイント持つ、keras によりロードされたデータセットを使用しますので、テストセット上の結果としての ELBO は (Larochelle の MNIST の動的二値化 (= dynamic binarization) を使用している) 文献で報告されている結果よりも僅かにより高いです。

 

画像を生成する

  • 訓練の後、幾つかの画像を生成する時です。
  • 単位正規事前分布 $p(z)$ から潜在ベクトルのセットをサンプリングすることから始めます。
  • それから generator は潜在サンプル $z$ を観測のロジットに変換し、分布 $p(x|z)$ を与えます。
  • ここで Bernoulli 分布の確率をプロットします。
epochs = 100
latent_dim = 50
num_examples_to_generate = 16

# keeping the random vector constant for generation (prediction) so
# it will be easier to see the improvement.
random_vector_for_generation = tf.random.normal(
    shape=[num_examples_to_generate, latent_dim])
model = CVAE(latent_dim)
def generate_and_save_images(model, epoch, test_input):
  predictions = model.sample(test_input)
  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')

  # tight_layout minimizes the overlap between 2 sub-plots
  plt.savefig('image_at_epoch_{:04d}.png'.format(epoch))
  plt.show()
generate_and_save_images(model, 0, random_vector_for_generation)

for epoch in range(1, epochs + 1):
  start_time = time.time()
  for train_x in train_dataset:
    gradients, loss = compute_gradients(model, train_x)
    apply_gradients(optimizer, gradients, model.trainable_variables)
  end_time = time.time()

  if epoch % 1 == 0:
    loss = tf.keras.metrics.Mean()
    for test_x in test_dataset:
      loss(compute_loss(model, test_x))
    elbo = -loss.result()
    display.clear_output(wait=False)
    print('Epoch: {}, Test set ELBO: {}, '
          'time elapse for current epoch {}'.format(epoch,
                                                    elbo,
                                                    end_time - start_time))
    generate_and_save_images(
        model, epoch, random_vector_for_generation)
Epoch: 100, Test set ELBO: -78.32722473144531, time elapse for current epoch 27.248212575912476

 

エポック数を使用して画像を表示する

def display_image(epoch_no):
  return PIL.Image.open('image_at_epoch_{:04d}.png'.format(epoch_no))
plt.imshow(display_image(epochs))
plt.axis('off')# Display images
(-0.5, 287.5, 287.5, -0.5)

 

総てのセーブされた画像の GIF を生成する

anim_file = 'cvae.gif'

with imageio.get_writer(anim_file, mode='I') as writer:
  filenames = glob.glob('image*.png')
  filenames = sorted(filenames)
  last = -1
  for i,filename in enumerate(filenames):
    frame = 2*(i**0.5)
    if round(frame) > round(last):
      last = frame
    else:
      continue
    image = imageio.imread(filename)
    writer.append_data(image)
  image = imageio.imread(filename)
  writer.append_data(image)
    
import IPython
if IPython.version_info >= (6,2,0,''):
  display.Image(filename=anim_file)

Colab で作業している場合には下のコードでアニメーションをダウンロードできます :

try:
  from google.colab import files
except ImportError:
  pass
else:
  files.download(anim_file)
 

以上






TensorFlow : Tutorials : 生成モデル : 畳み込み VAE : tf.keras と eager のサンプル

TensorFlow : Tutorials : 生成モデル : 畳み込み変分オートエンコーダ (翻訳/解説)

翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 10/11/2018

* TensorFlow 1.9 以後にドキュメント構成が変更され、数篇が新規に追加されましたので再翻訳しました。
* 本ページは、TensorFlow の本家サイトの Tutorials – Generative Models の以下のページを翻訳した上で
適宜、補足説明したものです:

* サンプルコードの動作確認はしておりますが、必要な場合には適宜、追加改変しています。
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。

 

畳み込み変分オートエンコーダ

このノートブックは tf.keras と eager execution を使用して変分オートエンコーダ (= Variational Autoencoder, VAE, [1], [2]) を訓練することにより手書き数字の画像をどのように生成するかを示します。

# to generate gifs
!pip install imageio

 

TensorFlow をインポートして Eager execution を有効にする

from __future__ import absolute_import, division, print_function

# Import TensorFlow >= 1.9 and enable eager execution
import tensorflow as tf
tfe = tf.contrib.eager
tf.enable_eager_execution()

import os
import time
import numpy as np
import glob
import matplotlib.pyplot as plt
import PIL
import imageio
from IPython import display

 

MNIST データセットをロードする

各 MNIST 画像は元々は 784 個の整数のベクトルで、その各々は 0-255 の間でピクセルの強度を表します。私達のモデルでは各ピクセルを Bernoulli 分布でモデル化して、データセットを統計的に二値化します。

(train_images, _), (test_images, _) = tf.keras.datasets.mnist.load_data()
train_images = train_images.reshape(train_images.shape[0], 28, 28, 1).astype('float32')
test_images = test_images.reshape(test_images.shape[0], 28, 28, 1).astype('float32')

# Normalizing the images to the range of [0., 1.]
train_images /= 255.
test_images /= 255.

# Binarization
train_images[train_images >= .5] = 1.
train_images[train_images < .5] = 0.
test_images[test_images >= .5] = 1.
test_images[test_images < .5] = 0.
TRAIN_BUF = 60000
BATCH_SIZE = 100

TEST_BUF = 10000

 

バッチを作成してデータセットをシャッフルするために tf.data を使用する

train_dataset = tf.data.Dataset.from_tensor_slices(train_images).shuffle(TRAIN_BUF).batch(BATCH_SIZE)
test_dataset = tf.data.Dataset.from_tensor_slices(test_images).shuffle(TEST_BUF).batch(BATCH_SIZE)

 

生成と推論ネットワークを tf.keras.Sequential で配線する (= wire up)

私達の VAE サンプルでは、生成と推論ネットワークのために 2 つの小さな ConvNet を使用します。これらのニューラルネットは小さいので、コードを単純化するために tf.keras.Sequential を使用します。以下の記述において $x$ と $z$ をそれぞれ観測と潜在 (= latent) 変数を示すとします。

 

生成ネットワーク (= Generative Network)

これは生成モデルを定義します、これは入力として潜在エンコーディングを取り、観測の条件付き分布, i.e. $p(x|z)$ のためのパラメータを出力します。更に、潜在変数のために単位ガウス prior $p(z)$ を使用します。

 

推論ネットワーク

これは近似事後分布 $q(z|x)$ を定義します、これは入力として観測を取り潜在表現の条件付き分布のためのパラメータのセットを出力します。この例では、この分布を単純に対角ガウス分布 (= diagonal Gaussian) としてモデル化します。この場合、推論ネットワークは factorized Gaussian の平均 (= mean) と対数分散 (= log-variance) パラメータを出力します (直接的に分散の代わりに対数分散は数値的安定性のためです)。

 

再パラメータ化 (= Reparameterization) トリック

最適化の間、まず標準正規分布からサンプリングすることにより $q(z|x)$ からサンプリングできて、それから標準偏差を乗じて平均を加えます。これは勾配がサンプルを通じて推論ネットワーク・パラメータに渡ることを確かなものにします。

 

ネットワーク・アーキテクチャ

推論ネットワークのためには、2 つの畳み込み層と続く完全結合層を使用します。生成ネットワークでは、完全結合層と続く 3 つの convolution transpose 層を使用してこのアーキテクチャを反映 (= mirror) します (a.k.a. あるコンテキストでは deconvolutional 層)。

注意してください、VAE を訓練するときバッチ正規化の使用を回避することは一般的な方法です、何故ならばミニバッチを使用することによる追加的な偶然性はサンプリングからの偶然性の上に不安定性を悪化させるかもしれないためです。

class CVAE(tf.keras.Model):
  def __init__(self, latent_dim):
    super(CVAE, self).__init__()
    self.latent_dim = latent_dim
    self.inference_net = 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=tf.nn.relu),
          tf.keras.layers.Conv2D(
              filters=64, kernel_size=3, strides=(2, 2), activation=tf.nn.relu),
          tf.keras.layers.Flatten(),
          # No activation
          tf.keras.layers.Dense(latent_dim + latent_dim),
      ]
    )

    self.generative_net = 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(
              filters=64,
              kernel_size=3,
              strides=(2, 2),
              padding="SAME",
              activation=tf.nn.relu),
          tf.keras.layers.Conv2DTranspose(
              filters=32,
              kernel_size=3,
              strides=(2, 2),
              padding="SAME",
              activation=tf.nn.relu),
          # No activation
          tf.keras.layers.Conv2DTranspose(
              filters=1, kernel_size=3, strides=(1, 1), padding="SAME"),
        ]
    )

  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)

  def encode(self, x):
    mean, logvar = tf.split(self.inference_net(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

  def decode(self, z, apply_sigmoid=False):
    logits = self.generative_net(z)
    if apply_sigmoid:
      probs = tf.sigmoid(logits)
      return probs

    return logits

 

損失関数と optimizer を定義する

VAE は周辺対数尤度上のエビデンス下限 (ELBO, evidence lower bound) を最大化することにより訓練されます :

$$\log p(x) \ge \text{ELBO} = \mathbb{E}_{q(z|x)}\left[\log \frac{p(x, z)}{q(z|x)}\right].$$

実際に、この期待値の single-sample モンテカルロ推定を最適化します :

$$\log p(x| z) + \log p(z) - \log q(z|x),$$

ここで $z$ は $q(z|x)$ からサンプリングされます。

Note: 私達はまた KL 項を解析的に計算することもできますが、ここでは単純化のために総ての 3 つの項をモンテカルロ estimator に組み入れます。

def log_normal_pdf(sample, mean, logvar, raxis=1):
  log2pi = tf.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_ent = tf.nn.sigmoid_cross_entropy_with_logits(logits=x_logit, labels=x)
  logpx_z = -tf.reduce_sum(cross_ent, 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)

def compute_gradients(model, x):
  with tf.GradientTape() as tape:
    loss = compute_loss(model, x)
  return tape.gradient(loss, model.trainable_variables), loss

optimizer = tf.train.AdamOptimizer(1e-4)
def apply_gradients(optimizer, gradients, variables, global_step=None):
  optimizer.apply_gradients(zip(gradients, variables), global_step=global_step)

 

訓練

  • データセットに渡り反復することから始めます。
  • 各反復の間、近似事後 $q(z|x)$ の平均と対数分散パラメータのセットを得るために画像をエンコーダに渡します。
  • それから $q(z|x)$ からサンプリングするために再パラメータ化トリックを適用します。
  • 最後に、生成分布 $p(x|z)$ のロジットを得るために再パラメータ化されたサンプルをデコーダに渡します。
  • Note: 訓練セットの 60k データポイントとテストセットの 10k データポイント持つ、keras によりロードされたデータセットを使用しますので、テストセット上の結果としての ELBO は (Larochelle の MNIST の動的二値化 (= dynamic binarization) を使用している) 文献で報告されている結果よりも僅かにより高いです。

 

画像を生成する

  • 訓練の後、幾つかの画像を生成する時です。
  • 単位ガウス事前分布 $p(z)$ から潜在ベクトルのセットをサンプリングすることから始めます。
  • それから generator は潜在サンプル $z$ を観測のロジットに変換し、分布 $p(x|z)$ を与えます。
  • ここで Bernoulli 分布の確率をプロットします。
epochs = 100
latent_dim = 50
num_examples_to_generate = 16

# keeping the random vector constant for generation (prediction) so
# it will be easier to see the improvement.
random_vector_for_generation = tf.random_normal(
    shape=[num_examples_to_generate, latent_dim])
model = CVAE(latent_dim)
def generate_and_save_images(model, epoch, test_input):
  predictions = model.sample(test_input)
  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')

  # tight_layout minimizes the overlap between 2 sub-plots
  plt.savefig('image_at_epoch_{:04d}.png'.format(epoch))
  plt.show()
generate_and_save_images(model, 0, random_vector_for_generation)

for epoch in range(1, epochs + 1):
  start_time = time.time()
  for train_x in train_dataset:
    gradients, loss = compute_gradients(model, train_x)
    apply_gradients(optimizer, gradients, model.trainable_variables)
  end_time = time.time()

  if epoch % 1 == 0:
    loss = tfe.metrics.Mean()
    for test_x in test_dataset.make_one_shot_iterator():
      loss(compute_loss(model, test_x))
    elbo = -loss.result()
    display.clear_output(wait=False)
    print('Epoch: {}, Test set ELBO: {}, '
          'time elapse for current epoch {}'.format(epoch,
                                                    elbo,
                                                    end_time - start_time))
    generate_and_save_images(
        model, epoch, random_vector_for_generation)

 

エポック数を使用して画像を表示する

def display_image(epoch_no):
  return PIL.Image.open('image_at_epoch_{:04d}.png'.format(epoch_no))
display_image(epochs)  # Display images

 

総てのセーブされた画像の GIF を生成する

with imageio.get_writer('cvae.gif', mode='I') as writer:
  filenames = glob.glob('image*.png')
  filenames = sorted(filenames)
  last = -1
  for i,filename in enumerate(filenames):
    frame = 2*(i**0.5)
    if round(frame) > round(last):
      last = frame
    else:
      continue
    image = imageio.imread(filename)
    writer.append_data(image)
  image = imageio.imread(filename)
  writer.append_data(image)
    
# this is a hack to display the gif inside the notebook
os.system('cp cvae.gif cvae.gif.png')
display.Image(filename="cvae.gif.png")
 

以上






AI導入支援 #2 ウェビナー

スモールスタートを可能としたAI導入支援   Vol.2
[無料 WEB セミナー] [詳細]
「画像認識 AI PoC スターターパック」の紹介
既に AI 技術を実ビジネスで活用し、成果を上げている日本企業も多く存在しており、競争優位なビジネスを展開しております。
しかしながら AI を導入したくとも PoC (概念実証) だけでも高額な費用がかかり取組めていない企業も少なくないようです。A I導入時には欠かせない PoC を手軽にしかも短期間で認知度を確認可能とするサービの紹介と共に、AI 技術の特性と具体的な導入プロセスに加え運用時のポイントについても解説いたします。
日時:2021年10月13日(水)
会場:WEBセミナー
共催:クラスキャット、日本FLOW(株)
後援:働き方改革推進コンソーシアム
参加費: 無料 (事前登録制)
人工知能開発支援
◆ クラスキャットは 人工知能研究開発支援 サービスを提供しています :
  • テクニカルコンサルティングサービス
  • 実証実験 (プロトタイプ構築)
  • アプリケーションへの実装
  • 人工知能研修サービス
◆ お問合せ先 ◆
(株)クラスキャット
セールス・インフォメーション
E-Mail:sales-info@classcat.com