ホーム » 「TensorFlow 2.0 Advanced Tutorials」タグがついた投稿

タグアーカイブ: TensorFlow 2.0 Advanced Tutorials

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 : 上級 Tutorials : 生成 :- FGSM を使用した敵対的サンプル

TensorFlow 2.0 : 上級 Tutorials : 生成 :- FGSM を使用した敵対的サンプル (翻訳/解説)

翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 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/

 

生成 :- FGSM を使用した敵対的サンプル

このチュートリアルは Goodfellow et al による Explaining and Harnessing Adversarial Examples で記述されているように Fast Gradient Signed メソッド (FGSM) 攻撃を使用して敵対的サンプルを作成します。これはニューラルネットワークを騙す最初の最もポピュラーな攻撃の一つでした。

 

敵対的サンプルとは何でしょう?

敵対的サンプルはニューラルネットワークを混乱させ、与えられた入力の誤分類という結果になる目的で作成された特殊化された (= specialised) 入力です。これらの悪名高い入力は人間の目には見分けがつきませんが、ネットワークに画像の内容を識別することを失敗させます。そのような攻撃の幾つかのタイプがありますが、ここでは焦点は fast gradient sign メソッド攻撃にあります、これはホワイトボックス攻撃でそのゴールは誤分類を確実にすることです。ホワイトボックス攻撃では攻撃者は攻撃されるモデルへの完全なアクセスを持ちます。下で示される敵対的画像の最も有名な例の一つは前述のペーパーから取られました。

ここで、パンダの画像から始め、攻撃者は元の画像に小さな摂動 (= perturbations) (歪み (= distortion)) を追加します、これはモデルがこの画像をテナガザルとして、高い信頼度でラベル付けする結果になります。これらの摂動を追加する過程は下で説明されます。

 

Fast gradient sign メソッド

fast gradient sign メソッドは敵対的サンプルを作成するニューラルネットワークの勾配を利用することにより動作します。入力画像に対して、メソッドは損失を最大化する新しい画像を作成する入力画像に関する損失の勾配を使用します。この新しい画像は敵対的画像と呼ばれます。これは次の式を使用して要約できます :

\(adv\_x = x + \epsilon*\text{sign}(\nabla_xJ(\theta, x, y))\)

ここで

  • adv_x : 敵対的画像。
  • x : 元の入力画像。
  • y : 元の入力ラベル。
  • $\epsilon$ : 摂動が小さいことを確実にする乗数。
  • $\theta$ : モデルパラメータ。
  • $J$ : 損失。

ここで面白い特質は、勾配は入力画像に関して取られることです。これが成されるのは目的が損失を最大化する画像を作成することだからです。これを達成する方法は画像の各ピクセルが損失値にどのくらい寄与するかを見出し、それに従って摂動を追加することです。これはかなり高速に動作します、何故ならば連鎖率を使用してそして必要な勾配を見つけることにより、各ピクセルが損失にどのくらい寄与するかを見い出すことは容易だからです。こうして、画像に関する勾配が使用されます。加えて、モデルはもはや訓練されませんので (そのため勾配は訓練可能な変数, i.e. モデルパラメータに関して取られません)、モデルパラメータは定数で在り続けます。唯一のゴールは既に訓練されたモデルを騙すことです。

それでは事前訓練されたモデルを試して騙してみましょう。このチュートリアルでは、モデルは MobileNetV2 で、ImageNet 上で事前訓練されました。

from __future__ import absolute_import, division, print_function, unicode_literals

import tensorflow as tf
import matplotlib as mpl
import matplotlib.pyplot as plt

mpl.rcParams['figure.figsize'] = (8, 8)
mpl.rcParams['axes.grid'] = False

事前訓練された MobileNetV2 モデルと ImageNet クラス名をロードしましょう。

pretrained_model = tf.keras.applications.MobileNetV2(include_top=True,
                                                     weights='imagenet')
pretrained_model.trainable = False

# ImageNet labels
decode_predictions = tf.keras.applications.mobilenet_v2.decode_predictions
Downloading data from https://github.com/JonathanCMitchell/mobilenet_v2_keras/releases/download/v1.1/mobilenet_v2_weights_tf_dim_ordering_tf_kernels_1.0_224.h5
14540800/14536120 [==============================] - 1s 0us/step
# Helper function to preprocess the image so that it can be inputted in MobileNetV2
def preprocess(image):
  image = tf.cast(image, tf.float32)
  image = image/255
  image = tf.image.resize(image, (224, 224))
  image = image[None, ...]
  return image

# Helper function to extract labels from probability vector
def get_imagenet_label(probs):
  return decode_predictions(probs, top=1)[0][0]

 

元の画像

Wikimedia Common から ラブラドール・レトリバー by Mirko CC-BY-SA 3.0 のサンプル画像を使用してそして敵対的サンプルを作成します。最初のステップはそれを MobileNetV2 モデルに入力として供給できるように前処理することです。

image_path = tf.keras.utils.get_file('YellowLabradorLooking_new.jpg', 'https://storage.googleapis.com/download.tensorflow.org/example_images/YellowLabradorLooking_new.jpg')
image_raw = tf.io.read_file(image_path)
image = tf.image.decode_image(image_raw)

image = preprocess(image)
image_probs = pretrained_model.predict(image)
Downloading data from https://storage.googleapis.com/download.tensorflow.org/example_images/YellowLabradorLooking_new.jpg
90112/83281 [================================] - 0s 0us/step

画像を見てみましょう。

plt.figure()
plt.imshow(image[0])
_, image_class, class_confidence = get_imagenet_label(image_probs)
plt.title('{} : {:.2f}% Confidence'.format(image_class, class_confidence*100))
plt.show()
Downloading data from https://storage.googleapis.com/download.tensorflow.org/data/imagenet_class_index.json
40960/35363 [==================================] - 0s 0us/step

 

敵対的画像を作成する

fast gradient sign メソッドを実装する

最初のステップは摂動を作成することです、これは元の画像を歪めるために使用されて敵対的画像という結果になります。言及したように、このタスクのためには、勾配は画像に関して取られます。

loss_object = tf.keras.losses.CategoricalCrossentropy()

def create_adversarial_pattern(input_image, input_label):
  with tf.GradientTape() as tape:
    tape.watch(input_image)
    prediction = pretrained_model(input_image)
    loss = loss_object(input_label, prediction)

  # Get the gradients of the loss w.r.t to the input image.
  gradient = tape.gradient(loss, input_image)
  # Get the sign of the gradients to create the perturbation
  signed_grad = tf.sign(gradient)
  return signed_grad

結果としての摂動もまた可視化できます。

# Get the input label of the image.
labrador_retriever_index = 208
label = tf.one_hot(labrador_retriever_index, image_probs.shape[-1])

perturbations = create_adversarial_pattern(image, label)
plt.imshow(perturbations[0])
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).

<matplotlib.image.AxesImage at 0x7f392c049978>

epsilon の異なる値のためにこれを試して結果の画像を観測してみましょう。epsilon の値が増加するにつれてネットワークを騙すことが容易になることに気がつくでしょう、けれども、これはより同一視できる摂動という結果とのトレードオフとなります。

def display_images(image, description):
  _, label, confidence = get_imagenet_label(pretrained_model.predict(image))
  plt.figure()
  plt.imshow(image[0])
  plt.title('{} \n {} : {:.2f}% Confidence'.format(description,
                                                   label, confidence*100))
  plt.show()
epsilons = [0, 0.01, 0.1, 0.15]
descriptions = [('Epsilon = {:0.3f}'.format(eps) if eps else 'Input')
                for eps in epsilons]

for i, eps in enumerate(epsilons):
  adv_x = image + eps*perturbations
  adv_x = tf.clip_by_value(adv_x, 0, 1)
  display_images(adv_x, descriptions[i])

 

Next steps

敵対的攻撃について知った今、これを異なるデータセットと異なるアーキテクチャで試すことができます。貴方自身のモデルを作成して訓練し、そしてそれから同じメソッドを使用してそれを騙すことを試みることもできます。epsilon を変更するにつれて予測の信頼度がどのように変化するかを試して見ることもできます。

パワフルですが、このチュートリアルで示される攻撃は敵対的攻撃への研究の単なるスタートで、それから更にパワフルな攻撃を作成する複数のペーパーがあります。敵対的攻撃に加えて、研究は防御の作成にも繋がりました、これは強固な機械学習モデルを作成することを目的とします。敵対的攻撃と防御の包括的なリストのためにこの survey ペーパー をレビューしても良いです。

敵対的攻撃と防御のより多くの実装については、敵対的サンプル・ライブラリ CleverHans を見ることを望むかもしれません。

 

以上






TensorFlow 2.0 : 上級 Tutorials : 生成 :- CycleGAN

TensorFlow 2.0 : 上級 Tutorials : 生成 :- CycleGAN (翻訳/解説)

翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 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/

 

 

生成 :- CycleGAN

このノートブックは Unpaired Image-to-Image Translation using Cycle-Consistent Adversarial Networks で説明されている、CycleGAN としても知られる、条件付き GAN を使用して不対な (= unpaired) 画像から画像への変換を実演します。このペーパーはメソッドを提案します、これは一つの画像ドメインの特質を捉えてどのような対となる訓練サンプルも総て欠落しながら、これらの特質がどのようにもう一つの画像ドメインに翻訳できるかを見出すことができます。

このノートブックは貴方が Pix2Pix について馴染みがあることを仮定しています、これについては Pix2Pix チュートリアル で学習することができます。CycleGAN のためのコードも類似していますが、主な違いは追加の損失関数と、不対な訓練データの使用です。

CycleGAN はペアデータを必要とすることなく訓練を可能にするために cycle consistency 損失を使用します。換言すれば、それはソースとターゲット・ドメイン間の 1対1 マッピングなしに 1 つのドメインからもう 1 つのドメインへ変換することができます。

これは写真拡張、画像彩色、画風変換, etc. のような多くの興味深いタスクを行なう可能性を広げます。貴方が必要なことの総てはソースとターゲット・データセットです (これは単純に画像のディレクトリです)。

 

入力パイプラインをセットアップする

tensorflow_examples パッケージをインストールします、これは generator と discriminator のインポートを可能にします。

!pip install -q git+https://github.com/tensorflow/examples.git
import tensorflow as tf
from __future__ import absolute_import, division, print_function, unicode_literals

import tensorflow_datasets as tfds
from tensorflow_examples.models.pix2pix import pix2pix

import os
import time
import matplotlib.pyplot as plt
from IPython.display import clear_output

tfds.disable_progress_bar()
AUTOTUNE = tf.data.experimental.AUTOTUNE

 

入力パイプライン

このチュートリアルは馬の画像からシマウマの画像に変換するためにモデルを訓練します。このデータセット及び類似のものは ここ で見つけられます。

ペーパー で言及されているように、訓練データセットにランダムな jittering とミラーリングを適用します。これらは overfitting を回避する画像増強テクニックの幾つかです。

これは pix2pix で行われたことに類似しています

  • ランダムな jittering では、画像は 286 x 286 にリサイズされてから 256 x 256 にランダムにクロップされます。
  • ランダムなミラーリングでは、画像はランダムに水平に i.e. 左から右に反転されます。
dataset, metadata = tfds.load('cycle_gan/horse2zebra',
                              with_info=True, as_supervised=True)

train_horses, train_zebras = dataset['trainA'], dataset['trainB']
test_horses, test_zebras = dataset['testA'], dataset['testB']
Downloading and preparing dataset cycle_gan (111.45 MiB) to /home/kbuilder/tensorflow_datasets/cycle_gan/horse2zebra/0.1.0...

/home/kbuilder/.local/lib/python3.5/site-packages/urllib3/connectionpool.py:1004: InsecureRequestWarning: Unverified HTTPS request is being made. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#ssl-warnings
  InsecureRequestWarning,

WARNING:tensorflow:From /home/kbuilder/.local/lib/python3.5/site-packages/tensorflow_datasets/core/file_format_adapter.py:209: tf_record_iterator (from tensorflow.python.lib.io.tf_record) is deprecated and will be removed in a future version.
Instructions for updating:
Use eager execution and: 
`tf.data.TFRecordDataset(path)`

WARNING:tensorflow:From /home/kbuilder/.local/lib/python3.5/site-packages/tensorflow_datasets/core/file_format_adapter.py:209: tf_record_iterator (from tensorflow.python.lib.io.tf_record) is deprecated and will be removed in a future version.
Instructions for updating:
Use eager execution and: 
`tf.data.TFRecordDataset(path)`

Dataset cycle_gan downloaded and prepared to /home/kbuilder/tensorflow_datasets/cycle_gan/horse2zebra/0.1.0. Subsequent calls will reuse this data.
BUFFER_SIZE = 1000
BATCH_SIZE = 1
IMG_WIDTH = 256
IMG_HEIGHT = 256
def random_crop(image):
  cropped_image = tf.image.random_crop(
      image, size=[IMG_HEIGHT, IMG_WIDTH, 3])

  return cropped_image
# normalizing the images to [-1, 1]
def normalize(image):
  image = tf.cast(image, tf.float32)
  image = (image / 127.5) - 1
  return image
def random_jitter(image):
  # resizing to 286 x 286 x 3
  image = tf.image.resize(image, [286, 286],
                          method=tf.image.ResizeMethod.NEAREST_NEIGHBOR)

  # randomly cropping to 256 x 256 x 3
  image = random_crop(image)

  # random mirroring
  image = tf.image.random_flip_left_right(image)

  return image
def preprocess_image_train(image, label):
  image = random_jitter(image)
  image = normalize(image)
  return image
def preprocess_image_test(image, label):
  image = normalize(image)
  return image
train_horses = train_horses.map(
    preprocess_image_train, num_parallel_calls=AUTOTUNE).cache().shuffle(
    BUFFER_SIZE).batch(1)

train_zebras = train_zebras.map(
    preprocess_image_train, num_parallel_calls=AUTOTUNE).cache().shuffle(
    BUFFER_SIZE).batch(1)

test_horses = test_horses.map(
    preprocess_image_test, num_parallel_calls=AUTOTUNE).cache().shuffle(
    BUFFER_SIZE).batch(1)

test_zebras = test_zebras.map(
    preprocess_image_test, num_parallel_calls=AUTOTUNE).cache().shuffle(
    BUFFER_SIZE).batch(1)
sample_horse = next(iter(train_horses))
sample_zebra = next(iter(train_zebras))
plt.subplot(121)
plt.title('Horse')
plt.imshow(sample_horse[0] * 0.5 + 0.5)

plt.subplot(122)
plt.title('Horse with random jitter')
plt.imshow(random_jitter(sample_horse[0]) * 0.5 + 0.5)
<matplotlib.image.AxesImage at 0x7fed182e1cf8>

plt.subplot(121)
plt.title('Zebra')
plt.imshow(sample_zebra[0] * 0.5 + 0.5)

plt.subplot(122)
plt.title('Zebra with random jitter')
plt.imshow(random_jitter(sample_zebra[0]) * 0.5 + 0.5)
<matplotlib.image.AxesImage at 0x7fed182be7f0>

 

Pix2Pix モデルをインポートして再利用する

Pix2Pix で使用された generator と discriminator をインストールされた tensorflow_examples パッケージを通してインポートします。

このチュートリアルで使用されるモデル・アーキテクチャは pix2pix で使用されたものに非常に類似しています。違いの幾つかは :

ここで訓練される 2 generator ($G$ と $F$) と 2 discriminators (X と Y) (訳注: 原文ママ、正しくは $D_X$ と $D_Y$) があります。

  • Generator $G$ は画像 X から画像 Y への変換を学習します。$(G: X -> Y)$
  • Generator $F$ は画像 Y から画像 X への変換を学習します。$(F: Y -> X)$
  • Discriminator $D_X$ は画像 X と生成された画像 X (F(Y)) を識別します。
  • Discriminator $D_Y$ は画像 Y と生成された画像 Y (G(X)) を識別します。

OUTPUT_CHANNELS = 3

generator_g = pix2pix.unet_generator(OUTPUT_CHANNELS, norm_type='instancenorm')
generator_f = pix2pix.unet_generator(OUTPUT_CHANNELS, norm_type='instancenorm')

discriminator_x = pix2pix.discriminator(norm_type='instancenorm', target=False)
discriminator_y = pix2pix.discriminator(norm_type='instancenorm', target=False)
to_zebra = generator_g(sample_horse)
to_horse = generator_f(sample_zebra)
plt.figure(figsize=(8, 8))
contrast = 8

imgs = [sample_horse, to_zebra, sample_zebra, to_horse]
title = ['Horse', 'To Zebra', 'Zebra', 'To Horse']

for i in range(len(imgs)):
  plt.subplot(2, 2, i+1)
  plt.title(title[i])
  if i % 2 == 0:
    plt.imshow(imgs[i][0] * 0.5 + 0.5)
  else:
    plt.imshow(imgs[i][0] * 0.5 * contrast + 0.5)
plt.show()
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).

plt.figure(figsize=(8, 8))

plt.subplot(121)
plt.title('Is a real zebra?')
plt.imshow(discriminator_y(sample_zebra)[0, ..., -1], cmap='RdBu_r')

plt.subplot(122)
plt.title('Is a real horse?')
plt.imshow(discriminator_x(sample_horse)[0, ..., -1], cmap='RdBu_r')

plt.show()

 

損失関数

CycleGAN では、訓練するペアとなるデータはありません、そのため訓練の間入力 x とターゲット y ペアに意味がある保証はありません。このためネットワークが正しいマッピングを学習することを強要するために、著者は cycle consistency 損失を提案しています。

discriminator 損失と generator 損失は pix2pix で使用されたものに類似しています。

LAMBDA = 10
loss_obj = tf.keras.losses.BinaryCrossentropy(from_logits=True)
def discriminator_loss(real, generated):
  real_loss = loss_obj(tf.ones_like(real), real)

  generated_loss = loss_obj(tf.zeros_like(generated), generated)

  total_disc_loss = real_loss + generated_loss

  return total_disc_loss * 0.5
def generator_loss(generated):
  return loss_obj(tf.ones_like(generated), generated)

cycle consistency は結果は元の入力に近くあるべきといういう意味です。例えば、もしセンテンスを英語からフランス語に翻訳して、それからフランス語から英語に翻訳し戻す場合、結果としてのセンテンスは元のセンテンスと同じであるべきということです。

cycle consistency 損失では、

  • 画像 $X$ は generator $G$ を通して渡されます、これは生成画像 $\hat{Y}$ を生成します。
  • 生成画像 $\hat{Y}$ は generator $F$ を通して渡されます、これは cycled 画像 $\hat{X}$ を生成します。
  • $X$ と $\hat{X}$ の間の mean absolute error (平均絶対誤差) が計算されます。
$$forward\ cycle\ consistency\ loss: X -> G(X) -> F(G(X)) \sim \hat{X}$$
$$backward\ cycle\ consistency\ loss: Y -> F(Y) -> G(F(Y)) \sim \hat{Y}$$

def calc_cycle_loss(real_image, cycled_image):
  loss1 = tf.reduce_mean(tf.abs(real_image - cycled_image))
  
  return LAMBDA * loss1

上で示されるように、generator $G$ は画像 $X$ を画像 $Y$ に変換する責任を負います。Identity 損失は、画像 $Y$ を generator $G$ に供給した場合、それは real 画像 $Y$ か画像 $Y$ に近い何かを生成するべきであると言っています。

$$Identity\ loss = |G(Y) – Y| + |F(X) – X|$$
def identity_loss(real_image, same_image):
  loss = tf.reduce_mean(tf.abs(real_image - same_image))
  return LAMBDA * 0.5 * loss

総ての generator と discriminator のために optimizer を初期化します。

generator_g_optimizer = tf.keras.optimizers.Adam(2e-4, beta_1=0.5)
generator_f_optimizer = tf.keras.optimizers.Adam(2e-4, beta_1=0.5)

discriminator_x_optimizer = tf.keras.optimizers.Adam(2e-4, beta_1=0.5)
discriminator_y_optimizer = tf.keras.optimizers.Adam(2e-4, beta_1=0.5)

 

チェックポイント

checkpoint_path = "./checkpoints/train"

ckpt = tf.train.Checkpoint(generator_g=generator_g,
                           generator_f=generator_f,
                           discriminator_x=discriminator_x,
                           discriminator_y=discriminator_y,
                           generator_g_optimizer=generator_g_optimizer,
                           generator_f_optimizer=generator_f_optimizer,
                           discriminator_x_optimizer=discriminator_x_optimizer,
                           discriminator_y_optimizer=discriminator_y_optimizer)

ckpt_manager = tf.train.CheckpointManager(ckpt, checkpoint_path, max_to_keep=5)

# if a checkpoint exists, restore the latest checkpoint.
if ckpt_manager.latest_checkpoint:
  ckpt.restore(ckpt_manager.latest_checkpoint)
  print ('Latest checkpoint restored!!')

 

訓練

Note: このチュートリアルのために訓練時間を合理的に保つためにこのサンプルモデルはペーパー (200) よりも少ないエポック (40) の間訓練されます。予測は少し正確ではないかもしれません。

EPOCHS = 40
def generate_images(model, test_input):
  prediction = model(test_input)
    
  plt.figure(figsize=(12, 12))

  display_list = [test_input[0], prediction[0]]
  title = ['Input Image', 'Predicted Image']

  for i in range(2):
    plt.subplot(1, 2, i+1)
    plt.title(title[i])
    # getting the pixel values between [0, 1] to plot it.
    plt.imshow(display_list[i] * 0.5 + 0.5)
    plt.axis('off')
  plt.show()

訓練ループは複雑に見えますけれども、それは 4 つの基本ステップから成ります :

  • 予測を得る。
  • 損失を計算する。
  • 逆伝播を使用して勾配を計算する。
  • 勾配を optimizer に適用する。
@tf.function
def train_step(real_x, real_y):
  # persistent is set to True because the tape is used more than
  # once to calculate the gradients.
  with tf.GradientTape(persistent=True) as tape:
    # Generator G translates X -> Y
    # Generator F translates Y -> X.
    
    fake_y = generator_g(real_x, training=True)
    cycled_x = generator_f(fake_y, training=True)

    fake_x = generator_f(real_y, training=True)
    cycled_y = generator_g(fake_x, training=True)

    # same_x and same_y are used for identity loss.
    same_x = generator_f(real_x, training=True)
    same_y = generator_g(real_y, training=True)

    disc_real_x = discriminator_x(real_x, training=True)
    disc_real_y = discriminator_y(real_y, training=True)

    disc_fake_x = discriminator_x(fake_x, training=True)
    disc_fake_y = discriminator_y(fake_y, training=True)

    # calculate the loss
    gen_g_loss = generator_loss(disc_fake_y)
    gen_f_loss = generator_loss(disc_fake_x)
    
    total_cycle_loss = calc_cycle_loss(real_x, cycled_x) + calc_cycle_loss(real_y, cycled_y)
    
    # Total generator loss = adversarial loss + cycle loss
    total_gen_g_loss = gen_g_loss + total_cycle_loss + identity_loss(real_y, same_y)
    total_gen_f_loss = gen_f_loss + total_cycle_loss + identity_loss(real_x, same_x)

    disc_x_loss = discriminator_loss(disc_real_x, disc_fake_x)
    disc_y_loss = discriminator_loss(disc_real_y, disc_fake_y)
  
  # Calculate the gradients for generator and discriminator
  generator_g_gradients = tape.gradient(total_gen_g_loss, 
                                        generator_g.trainable_variables)
  generator_f_gradients = tape.gradient(total_gen_f_loss, 
                                        generator_f.trainable_variables)
  
  discriminator_x_gradients = tape.gradient(disc_x_loss, 
                                            discriminator_x.trainable_variables)
  discriminator_y_gradients = tape.gradient(disc_y_loss, 
                                            discriminator_y.trainable_variables)
  
  # Apply the gradients to the optimizer
  generator_g_optimizer.apply_gradients(zip(generator_g_gradients, 
                                            generator_g.trainable_variables))

  generator_f_optimizer.apply_gradients(zip(generator_f_gradients, 
                                            generator_f.trainable_variables))
  
  discriminator_x_optimizer.apply_gradients(zip(discriminator_x_gradients,
                                                discriminator_x.trainable_variables))
  
  discriminator_y_optimizer.apply_gradients(zip(discriminator_y_gradients,
                                                discriminator_y.trainable_variables))
for epoch in range(EPOCHS):
  start = time.time()

  n = 0
  for image_x, image_y in tf.data.Dataset.zip((train_horses, train_zebras)):
    train_step(image_x, image_y)
    if n % 10 == 0:
      print ('.', end='')
    n+=1

  clear_output(wait=True)
  # Using a consistent image (sample_horse) so that the progress of the model
  # is clearly visible.
  generate_images(generator_g, sample_horse)

  if (epoch + 1) % 5 == 0:
    ckpt_save_path = ckpt_manager.save()
    print ('Saving checkpoint for epoch {} at {}'.format(epoch+1,
                                                         ckpt_save_path))

  print ('Time taken for epoch {} is {} sec\n'.format(epoch + 1,
                                                      time.time()-start))

Saving checkpoint for epoch 40 at ./checkpoints/train/ckpt-8
Time taken for epoch 40 is 183.51983284950256 sec

 

テストデータセットを使用して生成する

# Run the trained model on the test dataset
for inp in test_horses.take(5):
  generate_images(generator_g, inp)

 

Next Steps

このチュートリアルは Pix2Pix チュートリアルで実装された generator と discriminator から始めて CycleGAN をどのように実装するかを示しました。次のステップとして、TensorFlow データセット からの異なるデータセットを使用して試すことができます。

貴方はまた結果を改善するためにより大きな数のエポックの間訓練することもできます、あるいはここで使用された U-Net generator の代わりに ペーパー で使用された修正された ResNet generator を実装することもできるでしょう。

 

以上






TensorFlow 2.0 : 上級 Tutorials : 生成 :- Pix2Pix

TensorFlow 2.0 : 上級 Tutorials : 生成 :- Pix2Pix (翻訳/解説)

翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 11/22/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/

 

生成 :- Pix2Pix

このノートブックは Image-to-Image Translation with Conditional Adversarial Networks で記述されている、条件付き GAN を使用して画像から画像への変換を実演します。このテクニックを使用して白黒写真を彩色したり、google マップを google earth に変換する等のことができます。ここでは、建物の正面 (= facade) をリアルな建物に変換します。

サンプルでは、CMP Facade データベース を使用します、これは プラハの Czech Technical UniversityCenter for Machine Perception により役立つように提供されています。サンプルを短く保つために、上の ペーパー の著者たちにより作成された、このデータセットの前処理された コピー を使用します。

各エポックは単一の V100 GPU 上で 15 秒前後かかります。
(訳注: Beta 版での記述によれば > 各エポックは単一の P100 GPU 上で 58 秒前後かかります。)

下は 200 エポックの間モデルを訓練した後に生成された出力です。

 

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

from __future__ import absolute_import, division, print_function, unicode_literals

import tensorflow as tf

import os
import time

from matplotlib import pyplot as plt
from IPython import display
!pip install -q -U tensorboard
データセットをロードする データセットと類似のデータセットを ここ からダウンロードできます。(上の) ペーパーで言及されているように、訓練データセットにランダムに jittering とミラーリングを適用します。
  • ランダム jittering では、画像は 286 x 286 にリサイズされてからランダムに 256 x 256 にクロップされます。
  • ランダム・ミラーリングでは、画像は水平に i.e. 左から右にランダムにフリップ (反転) されます。
_URL = 'https://people.eecs.berkeley.edu/~tinghuiz/projects/pix2pix/datasets/facades.tar.gz'

path_to_zip = tf.keras.utils.get_file('facades.tar.gz',
                                      origin=_URL,
                                      extract=True)

PATH = os.path.join(os.path.dirname(path_to_zip), 'facades/')
Downloading data from https://people.eecs.berkeley.edu/~tinghuiz/projects/pix2pix/datasets/facades.tar.gz
30171136/30168306 [==============================] - 2s 0us/step
BUFFER_SIZE = 400
BATCH_SIZE = 1
IMG_WIDTH = 256
IMG_HEIGHT = 256
def load(image_file):
  image = tf.io.read_file(image_file)
  image = tf.image.decode_jpeg(image)

  w = tf.shape(image)[1]

  w = w // 2
  real_image = image[:, :w, :]
  input_image = image[:, w:, :]

  input_image = tf.cast(input_image, tf.float32)
  real_image = tf.cast(real_image, tf.float32)

  return input_image, real_image
inp, re = load(PATH+'train/100.jpg')
# casting to int for matplotlib to show the image
plt.figure()
plt.imshow(inp/255.0)
plt.figure()
plt.imshow(re/255.0)
<matplotlib.image.AxesImage at 0x7ff6617e4898>

def resize(input_image, real_image, height, width):
  input_image = tf.image.resize(input_image, [height, width],
                                method=tf.image.ResizeMethod.NEAREST_NEIGHBOR)
  real_image = tf.image.resize(real_image, [height, width],
                               method=tf.image.ResizeMethod.NEAREST_NEIGHBOR)

  return input_image, real_image
def random_crop(input_image, real_image):
  stacked_image = tf.stack([input_image, real_image], axis=0)
  cropped_image = tf.image.random_crop(
      stacked_image, size=[2, IMG_HEIGHT, IMG_WIDTH, 3])

  return cropped_image[0], cropped_image[1]
# normalizing the images to [-1, 1]

def normalize(input_image, real_image):
  input_image = (input_image / 127.5) - 1
  real_image = (real_image / 127.5) - 1

  return input_image, real_image
@tf.function()
def random_jitter(input_image, real_image):
  # resizing to 286 x 286 x 3
  input_image, real_image = resize(input_image, real_image, 286, 286)

  # randomly cropping to 256 x 256 x 3
  input_image, real_image = random_crop(input_image, real_image)

  if tf.random.uniform(()) > 0.5:
    # random mirroring
    input_image = tf.image.flip_left_right(input_image)
    real_image = tf.image.flip_left_right(real_image)

  return input_image, real_image

下の画像で画像がランダム jittering を通り抜けているのを見ることができます。ペーパーで記述されているランダム jittering は以下を行なうためです :

  1. 画像をより大きい高さと幅にリサイズします
  2. ランダムにターゲットサイズにクロップします
  3. ランダムに画像を水平に反転します
plt.figure(figsize=(6, 6))
for i in range(4):
  rj_inp, rj_re = random_jitter(inp, re)
  plt.subplot(2, 2, i+1)
  plt.imshow(rj_inp/255.0)
  plt.axis('off')
plt.show()

def load_image_train(image_file):
  input_image, real_image = load(image_file)
  input_image, real_image = random_jitter(input_image, real_image)
  input_image, real_image = normalize(input_image, real_image)

  return input_image, real_image
def load_image_test(image_file):
  input_image, real_image = load(image_file)
  input_image, real_image = resize(input_image, real_image,
                                   IMG_HEIGHT, IMG_WIDTH)
  input_image, real_image = normalize(input_image, real_image)

  return input_image, real_image

 

入力パイプライン

train_dataset = tf.data.Dataset.list_files(PATH+'train/*.jpg')
train_dataset = train_dataset.map(load_image_train,
                                  num_parallel_calls=tf.data.experimental.AUTOTUNE)
train_dataset = train_dataset.shuffle(BUFFER_SIZE)
train_dataset = train_dataset.batch(BATCH_SIZE)
test_dataset = tf.data.Dataset.list_files(PATH+'test/*.jpg')
test_dataset = test_dataset.map(load_image_test)
test_dataset = test_dataset.batch(BATCH_SIZE)

 

Generator を構築する

  • generator のアーキテクチャは変更された U-Net です。
  • エンコーダの各ブロックは (Conv -> Batchnorm -> Leaky ReLU)
  • デコーダの各ブロックは (Transposed Conv -> Batchnorm -> Dropout(applied to the first 3 blocks) -> ReLU)
  • エンコーダとデコーダの間に (U-Net 内のように) スキップ接続があります。
OUTPUT_CHANNELS = 3
def downsample(filters, size, apply_batchnorm=True):
  initializer = tf.random_normal_initializer(0., 0.02)

  result = tf.keras.Sequential()
  result.add(
      tf.keras.layers.Conv2D(filters, size, strides=2, padding='same',
                             kernel_initializer=initializer, use_bias=False))

  if apply_batchnorm:
    result.add(tf.keras.layers.BatchNormalization())

  result.add(tf.keras.layers.LeakyReLU())

  return result
down_model = downsample(3, 4)
down_result = down_model(tf.expand_dims(inp, 0))
print (down_result.shape)
(1, 128, 128, 3)
def upsample(filters, size, apply_dropout=False):
  initializer = tf.random_normal_initializer(0., 0.02)

  result = tf.keras.Sequential()
  result.add(
    tf.keras.layers.Conv2DTranspose(filters, size, strides=2,
                                    padding='same',
                                    kernel_initializer=initializer,
                                    use_bias=False))

  result.add(tf.keras.layers.BatchNormalization())

  if apply_dropout:
      result.add(tf.keras.layers.Dropout(0.5))

  result.add(tf.keras.layers.ReLU())

  return result
up_model = upsample(3, 4)
up_result = up_model(down_result)
print (up_result.shape)
(1, 256, 256, 3)
def Generator():
  inputs = tf.keras.layers.Input(shape=[256,256,3])

  down_stack = [
    downsample(64, 4, apply_batchnorm=False), # (bs, 128, 128, 64)
    downsample(128, 4), # (bs, 64, 64, 128)
    downsample(256, 4), # (bs, 32, 32, 256)
    downsample(512, 4), # (bs, 16, 16, 512)
    downsample(512, 4), # (bs, 8, 8, 512)
    downsample(512, 4), # (bs, 4, 4, 512)
    downsample(512, 4), # (bs, 2, 2, 512)
    downsample(512, 4), # (bs, 1, 1, 512)
  ]

  up_stack = [
    upsample(512, 4, apply_dropout=True), # (bs, 2, 2, 1024)
    upsample(512, 4, apply_dropout=True), # (bs, 4, 4, 1024)
    upsample(512, 4, apply_dropout=True), # (bs, 8, 8, 1024)
    upsample(512, 4), # (bs, 16, 16, 1024)
    upsample(256, 4), # (bs, 32, 32, 512)
    upsample(128, 4), # (bs, 64, 64, 256)
    upsample(64, 4), # (bs, 128, 128, 128)
  ]

  initializer = tf.random_normal_initializer(0., 0.02)
  last = tf.keras.layers.Conv2DTranspose(OUTPUT_CHANNELS, 4,
                                         strides=2,
                                         padding='same',
                                         kernel_initializer=initializer,
                                         activation='tanh') # (bs, 256, 256, 3)

  x = inputs

  # Downsampling through the model
  skips = []
  for down in down_stack:
    x = down(x)
    skips.append(x)

  skips = reversed(skips[:-1])

  # Upsampling and establishing the skip connections
  for up, skip in zip(up_stack, skips):
    x = up(x)
    x = tf.keras.layers.Concatenate()([x, skip])

  x = last(x)

  return tf.keras.Model(inputs=inputs, outputs=x)
generator = Generator()
tf.keras.utils.plot_model(generator, show_shapes=True, dpi=64)

gen_output = generator(inp[tf.newaxis,...], training=False)
plt.imshow(gen_output[0,...])
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).

<matplotlib.image.AxesImage at 0x7ff5d87565c0>

  • Generator 損失
    • それは生成画像と 1 の配列 の sigmoid 交差エントロピー損失です。
    • ペーパー はまた L1 損失を含みます、これは生成画像とターゲット画像の間の MAE (mean absolute error, 平均絶対誤差) です。
    • これは生成画像にターゲット画像に構造的に類似することを可能にします。
    • 総計の generator 損失を計算するための式は = gan_loss + LAMBDA * l1_loss です、ここで LAMBDA = 100 。この値は ペーパー の著者により決められました。

generator の訓練手続きは下で示されます :

LAMBDA = 100
def generator_loss(disc_generated_output, gen_output, target):
  gan_loss = loss_object(tf.ones_like(disc_generated_output), disc_generated_output)

  # mean absolute error
  l1_loss = tf.reduce_mean(tf.abs(target - gen_output))

  total_gen_loss = gan_loss + (LAMBDA * l1_loss)

  return total_gen_loss, gan_loss, l1_loss

 

Discriminator を構築する

  • discriminator は PatchGAN です。
  • discriminator の各ブロックは (Conv -> BatchNorm -> Leaky ReLU) です。
  • 最後の層の後の出力の shape は (batch_size, 30, 30, 1) です。
  • 出力の各 30x30 パッチが入力画像の 70x70 の断片を分類します (そのようなアーキテクチャは PatchGAN と呼ばれます)。
  • discriminator は 2 つの入力を受け取ります。
    • 入力画像とターゲット画像、これは real として分類するべきです。
    • 入力画像と生成画像 (generator の出力)、これは fake として分類するべきです。
    • これらの 2 つの入力をコード (tf.concat([inp, tar], axis=-1)) で一緒に結合します。
def Discriminator():
  initializer = tf.random_normal_initializer(0., 0.02)

  inp = tf.keras.layers.Input(shape=[256, 256, 3], name='input_image')
  tar = tf.keras.layers.Input(shape=[256, 256, 3], name='target_image')

  x = tf.keras.layers.concatenate([inp, tar]) # (bs, 256, 256, channels*2)

  down1 = downsample(64, 4, False)(x) # (bs, 128, 128, 64)
  down2 = downsample(128, 4)(down1) # (bs, 64, 64, 128)
  down3 = downsample(256, 4)(down2) # (bs, 32, 32, 256)

  zero_pad1 = tf.keras.layers.ZeroPadding2D()(down3) # (bs, 34, 34, 256)
  conv = tf.keras.layers.Conv2D(512, 4, strides=1,
                                kernel_initializer=initializer,
                                use_bias=False)(zero_pad1) # (bs, 31, 31, 512)

  batchnorm1 = tf.keras.layers.BatchNormalization()(conv)

  leaky_relu = tf.keras.layers.LeakyReLU()(batchnorm1)

  zero_pad2 = tf.keras.layers.ZeroPadding2D()(leaky_relu) # (bs, 33, 33, 512)

  last = tf.keras.layers.Conv2D(1, 4, strides=1,
                                kernel_initializer=initializer)(zero_pad2) # (bs, 30, 30, 1)

  return tf.keras.Model(inputs=[inp, tar], outputs=last)
discriminator = Discriminator()
tf.keras.utils.plot_model(discriminator, show_shapes=True, dpi=64)

disc_out = discriminator([inp[tf.newaxis,...], gen_output], training=False)
plt.imshow(disc_out[0,...,-1], vmin=-20, vmax=20, cmap='RdBu_r')
plt.colorbar()
<matplotlib.colorbar.Colorbar at 0x7ff5d84dcf28>

  • Discriminator 損失
    • discriminator 損失関数は 2 つの入力を取ります; real 画像生成画像 です。
    • real_loss は real 画像1 の配列 (何故ならばこれらは real 画像だからです) の sigmod 交差エントロピー損失です。
    • generated_loss は 生成画像ゼロの配列 (何故ならばこれらは fake 画像だからです) の sigmod 交差エントロピー損失です。
    • それから total_loss は real_loss と generated_loss の合計です。
loss_object = tf.keras.losses.BinaryCrossentropy(from_logits=True)
def discriminator_loss(disc_real_output, disc_generated_output):
  real_loss = loss_object(tf.ones_like(disc_real_output), disc_real_output)

  generated_loss = loss_object(tf.zeros_like(disc_generated_output), disc_generated_output)

  total_disc_loss = real_loss + generated_loss

  return total_disc_loss

discriminator のための訓練手続きは下で示されます。

アーキテクチャとハイパーパラメータについて更に学習するためには、ペーパー を参照できます。

 

損失関数と Optimizer とチェックポイント・セーバーを定義する

generator_optimizer = tf.keras.optimizers.Adam(2e-4, beta_1=0.5)
discriminator_optimizer = tf.keras.optimizers.Adam(2e-4, beta_1=0.5)
checkpoint_dir = './training_checkpoints'
checkpoint_prefix = os.path.join(checkpoint_dir, "ckpt")
checkpoint = tf.train.Checkpoint(generator_optimizer=generator_optimizer,
                                 discriminator_optimizer=discriminator_optimizer,
                                 generator=generator,
                                 discriminator=discriminator)

 

チェックポイント (オブジェクトベースのセーブ)

checkpoint_dir = './training_checkpoints'
checkpoint_prefix = os.path.join(checkpoint_dir, "ckpt")
checkpoint = tf.train.Checkpoint(generator_optimizer=generator_optimizer,
                                 discriminator_optimizer=discriminator_optimizer,
                                 generator=generator,
                                 discriminator=discriminator)

 

画像を生成する

訓練の間に幾つかの画像をプロットするための関数を書きます。

  • テスト・データセットから generator に画像を渡します。
  • それから generator は入力画像を出力に変換します。
  • 最後のステップは予測をプロットすることです、そして voila !

Note: training=True はここでは意図的です、何故ならばテストデータセット上でモデルを実行する間バッチ統計を望むからです。training=False を使用する場合、訓練データセットから学習された累積統計を得ます (それは私達は望みません)。

def generate_images(model, test_input, tar):
  prediction = model(test_input, training=True)
  plt.figure(figsize=(15,15))

  display_list = [test_input[0], tar[0], prediction[0]]
  title = ['Input Image', 'Ground Truth', 'Predicted Image']

  for i in range(3):
    plt.subplot(1, 3, i+1)
    plt.title(title[i])
    # getting the pixel values between [0, 1] to plot it.
    plt.imshow(display_list[i] * 0.5 + 0.5)
    plt.axis('off')
  plt.show()
for example_input, example_target in test_dataset.take(1):
  generate_images(generator, example_input, example_target)

 

訓練

  • 各サンプル入力に対して出力を生成します。
  • discriminator は最初の入力として input_image と生成画像を受け取ります。2 番目の入力は input_image と target_image です。
  • 次に generator と discriminator 損失を計算します。
  • それから、generator と discriminator 変数 (入力) の両者に関する損失の勾配を計算してそれらを optimizer に適用します。
  • それから損失を TensorBoard に記録します。
EPOCHS = 150
import datetime
log_dir="logs/"

summary_writer = tf.summary.create_file_writer(
  log_dir + "fit/" + datetime.datetime.now().strftime("%Y%m%d-%H%M%S"))
@tf.function
def train_step(input_image, target, epoch):
  with tf.GradientTape() as gen_tape, tf.GradientTape() as disc_tape:
    gen_output = generator(input_image, training=True)

    disc_real_output = discriminator([input_image, target], training=True)
    disc_generated_output = discriminator([input_image, gen_output], training=True)

    gen_total_loss, gen_gan_loss, gen_l1_loss = generator_loss(disc_generated_output, gen_output, target)
    disc_loss = discriminator_loss(disc_real_output, disc_generated_output)

  generator_gradients = gen_tape.gradient(gen_total_loss,
                                          generator.trainable_variables)
  discriminator_gradients = disc_tape.gradient(disc_loss,
                                               discriminator.trainable_variables)

  generator_optimizer.apply_gradients(zip(generator_gradients,
                                          generator.trainable_variables))
  discriminator_optimizer.apply_gradients(zip(discriminator_gradients,
                                              discriminator.trainable_variables))

  with summary_writer.as_default():
    tf.summary.scalar('gen_total_loss', gen_total_loss, step=epoch)
    tf.summary.scalar('gen_gan_loss', gen_gan_loss, step=epoch)
    tf.summary.scalar('gen_l1_loss', gen_l1_loss, step=epoch)
    tf.summary.scalar('disc_loss', disc_loss, step=epoch)

実際の訓練ループは :

  • エポック数に渡り反復します。
  • 各エポックで、それは表示をクリアして、その進捗を示すために generate_images を実行します。
  • 各エポックで、それは訓練データセットに渡り反復し、各サンプルのために '.' をプリントします。
  • それは 20 エポック毎にチェックポイントをセーブします。
def fit(train_ds, epochs, test_ds):
  for epoch in range(epochs):
    start = time.time()

    display.clear_output(wait=True)

    for example_input, example_target in test_ds.take(1):
      generate_images(generator, example_input, example_target)
    print("Epoch: ", epoch)

    # Train
    for n, (input_image, target) in train_ds.enumerate():
      print('.', end='')
      if (n+1) % 100 == 0:
        print()
      train_step(input_image, target, epoch)
    print()

    # saving (checkpoint) the model every 20 epochs
    if (epoch + 1) % 20 == 0:
      checkpoint.save(file_prefix = checkpoint_prefix)

    print ('Time taken for epoch {} is {} sec\n'.format(epoch + 1,
                                                        time.time()-start))
  checkpoint.save(file_prefix = checkpoint_prefix)

この訓練ループは訓練進捗を監視するために TensorBoard で容易に見ることができるログをセーブします。ローカルで作業するときには別の tensorboard プロセスを起動します。ノートブックでは、TensorBoard で監視することを望む場合、訓練を始める前に viewer を起動するのが最も容易です。

viewer を起動するには次をコードセルにペーストしてください :

%load_ext tensorboard
%tensorboard --logdir {log_dir}

今は訓練ループを実行します :

fit(train_dataset, EPOCHS, test_dataset)

Epoch:  140
............................................................

TensorBoard の結果を公に共有することを望む場合次をコードセルにコピーすることによりログを TensorBoard.dev にアップロードできます。

Note: これは Google アカウントを必要とします。

!tensorboard dev upload --logdir  {log_dir}
警告: このコマンドは終了しません。それは長時間実行の実験の結果を連続的にアップロードするように設計されています。一度データがアップロードされればそれをノートブック・ツールの "interrupt execution" オプションを使用して停止する必要があります。

 
このノートブックの 前の実行の結果TensorBoard.dev で見ることができます。

TensorBoard.dev はホスティング、追跡そして ML 実験の皆との共有のための managed experience です。

それはまた <iframe> を使用してインラインで含むこともできます :

display.IFrame(
    src="https://tensorboard.dev/experiment/lZ0C6FONROaUMfjYkVyJqw",
    width="100%",
    height="1000px")

(訳注: ここではインライン展開せずにスナップショットで代替しています。)

 
GAN からのログの解釈は単純な分類や回帰モデルよりも微妙です。探すべきものは ::

  • いずれのモデルも「勝利」していないことを確認してください。gen_gan_loss か disc_loss のいずれかが非常に低い場合、このモデルは他を支配している指標で、結合されたモデルを成功的に訓練していません。
  • それらの損失のために値 log(2) = 0.69 は良い参照ポイントです、何故ならばそれは 2 の perplexity を示すからです。That the discriminator is on average equally uncertain about the two options.
  • disc_loss について 0.69 より下の値はリアル+生成画像の結合セット上、discriminator がランダムよりも上手くやっていることを意味します。
  • gen_gan_loss について 0.69 よりも下の値は generator が discriminator を騙す点でランダムよりも上手くやっていることを意味します。
  • 訓練が進むにつれて gen_l1_loss は下がるはずです。

 

最新のチェックポイントをリストアしてテストする

!ls {checkpoint_dir}
checkpoint          ckpt-5.data-00000-of-00002
ckpt-1.data-00000-of-00002  ckpt-5.data-00001-of-00002
ckpt-1.data-00001-of-00002  ckpt-5.index
ckpt-1.index            ckpt-6.data-00000-of-00002
ckpt-2.data-00000-of-00002  ckpt-6.data-00001-of-00002
ckpt-2.data-00001-of-00002  ckpt-6.index
ckpt-2.index            ckpt-7.data-00000-of-00002
ckpt-3.data-00000-of-00002  ckpt-7.data-00001-of-00002
ckpt-3.data-00001-of-00002  ckpt-7.index
ckpt-3.index            ckpt-8.data-00000-of-00002
ckpt-4.data-00000-of-00002  ckpt-8.data-00001-of-00002
ckpt-4.data-00001-of-00002  ckpt-8.index
ckpt-4.index
# restoring the latest checkpoint in checkpoint_dir
checkpoint.restore(tf.train.latest_checkpoint(checkpoint_dir))
<tensorflow.python.training.tracking.util.CheckpointLoadStatus at 0x7ff04ea33588>

 

テスト・データセットを使用して生成する

# Run the trained model on a few examples from the test dataset
for inp, tar in test_dataset.take(5):
  generate_images(generator, inp, tar)

 

以上






TensorFlow 2.0 : 上級 Tutorials : 生成 :- 深層畳み込み敵対的生成ネットワーク

TensorFlow 2.0 : 上級 Tutorials : 生成 :- 深層畳み込み敵対的生成ネットワーク (翻訳/解説)

翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 11/21/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/

 

生成 :- 深層畳み込み敵対的生成ネットワーク

このチュートリアルは DCGAN (Deep Convolutional Generative Adversarial Network, 深層畳み込み敵対的生成ネットワーク) を使用して手書き数字の画像をどのように生成するかを実演します。コードは tf.GradientTape 訓練ループで Keras Sequential API を使用して書かれています。

 

GAN とは何でしょう?

敵対的生成ネットワーク (GAN, Generative Adversarial Networks) は現在コンピュータ・サイエンスで最も興味深いアイデアの一つです。2 つのモデルが敵対するプロセスにより同時に訓練されます。generator (「芸術家 = the artist」) がリアルに見える画像を作成することを学習し、その一方で discriminator (「芸術評論家 = the art critic」) がフェイクからリアル画像を識別することを学習します。

訓練の間、generator はリアルに見える画像を作成することに次第に上達し、その一方で discriminator はそれらを識別することに上達します。discriminator がもはやフェイクからリアル画像を識別できないとき、プロセスは均衡に達します。

このノートブックは MNIST データセット上でこのプロセスを実演します。次のアニメーションは (generator が) 50 エポックの間訓練されたとき generator により生成された画像のシリーズを示します。画像はランダム・ノイズとして始まり、そして時間とともに手書き数字にだんだん似ていきます。

GAN について更に学習するためには、MIT の Intro to Deep Learning コースを勧めます。

 

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

from __future__ import absolute_import, division, print_function, unicode_literals
import tensorflow as tf
tf.__version__
'2.0.0'
# To generate GIFs
!pip install -q imageio
import glob
import imageio
import matplotlib.pyplot as plt
import numpy as np
import os
import PIL
from tensorflow.keras import layers
import time

from IPython import display

 

データセットをロードして準備する

generator と discriminator を訓練するために MNIST データセットを使用します。generator は MNIST データに似ている手書き数字を生成します。

(train_images, train_labels), (_, _) = tf.keras.datasets.mnist.load_data()
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/mnist.npz
11493376/11490434 [==============================] - 0s 0us/step
train_images = train_images.reshape(train_images.shape[0], 28, 28, 1).astype('float32')
train_images = (train_images - 127.5) / 127.5 # Normalize the images to [-1, 1]
BUFFER_SIZE = 60000
BATCH_SIZE = 256
# Batch and shuffle the data
train_dataset = tf.data.Dataset.from_tensor_slices(train_images).shuffle(BUFFER_SIZE).batch(BATCH_SIZE)

 

モデルを作成する

generator と discriminator の両者は Keras Sequential API を使用して定義されます。

 

Generator

generator は seed (ランダムノイズ) から画像を生成するために tf.keras.layers.Conv2DTranspose (upsampling) 層を使用します。この seed を入力として取る Dense 層から始めて、それから 28x28x1 の望まれるサイズに到達するまで幾度かアップサンプリングします。tanh を使用する出力層を除いて、各層のために tf.keras.layers.LeakyReLU 活性が (使用されることが) 分かるでしょう。

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) # Note: None is the batch size

    model.add(layers.Conv2DTranspose(128, (5, 5), strides=(1, 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, 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, 2), padding='same', use_bias=False, activation='tanh'))
    assert model.output_shape == (None, 28, 28, 1)

    return model

画像を作成するために (まだ訓練されていない) generator を使用します。

generator = make_generator_model()

noise = tf.random.normal([1, 100])
generated_image = generator(noise, training=False)

plt.imshow(generated_image[0, :, :, 0], cmap='gray')
<matplotlib.image.AxesImage at 0x7f0c49ae6c18>

 

Discriminator

discriminator は CNN-ベースの画像分類器です。

def make_discriminator_model():
    model = tf.keras.Sequential()
    model.add(layers.Conv2D(64, (5, 5), strides=(2, 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, 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 を使用します。モデルはリアル画像のためにはポジティブ値を、そしてフェイク画像のためにはネガティブ値を出力するように訓練されます。

discriminator = make_discriminator_model()
decision = discriminator(generated_image)
print (decision)
tf.Tensor([[-0.00366611]], shape=(1, 1), dtype=float32)

 

損失と optimizer を定義する

両者のモデルのために損失関数と optimizer を定義します。

# This method returns a helper function to compute cross entropy loss
cross_entropy = tf.keras.losses.BinaryCrossentropy(from_logits=True)

 

Discriminator 損失

このメソッドは discriminator がフェイク (画像) からリアル画像をどの程度上手く識別できるかを数値化します。それはリアル画像上の discriminator の予測を 1 の配列と、そしてフェイク (生成された) 画像上の discriminator の予測を 0 の配列と比較します。

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

 

Generator 損失

generator 損失はそれがどの程度上手く discriminator を欺くことができたかを数値化します。直感的には、generator が上手く遂行していれば、discriminator はフェイク画像をリアル (or 1) として分類するでしょう。ここでは、生成された画像上の discriminator の決定を 1 の配列と比較します。

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

discriminator と generator の optimizer は異なります、何故ならば 2 つのネットワークを別々に訓練するからです。

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

 

チェックポイントをセーブする

このノートブックはまたモデルをどのようにセーブしてリストアするかを実演します、これは長い実行訓練タスクが中断された場合に有用であり得ます。

checkpoint_dir = './training_checkpoints'
checkpoint_prefix = os.path.join(checkpoint_dir, "ckpt")
checkpoint = tf.train.Checkpoint(generator_optimizer=generator_optimizer,
                                 discriminator_optimizer=discriminator_optimizer,
                                 generator=generator,
                                 discriminator=discriminator)

 

訓練ループを定義する

EPOCHS = 50
noise_dim = 100
num_examples_to_generate = 16

# We will reuse this seed overtime (so it's easier)
# to visualize progress in the animated GIF)
seed = tf.random.normal([num_examples_to_generate, noise_dim])

訓練ループは generator が入力としてランダムシードを受け取ることから開始されます。そのシードは画像を生成するために使用されます。それから discriminator は (訓練セットからドローされた) リアル画像と (generator により生成された) フェイク画像を分類するために使用されます。損失はこれらのモデルの各々のために計算されて、勾配は generator と discriminator を更新するために使用されます。

# Notice the use of `tf.function`
# This annotation causes the function to be "compiled".
@tf.function
def train_step(images):
    noise = tf.random.normal([BATCH_SIZE, noise_dim])

    with tf.GradientTape() as gen_tape, tf.GradientTape() as disc_tape:
      generated_images = generator(noise, training=True)

      real_output = discriminator(images, training=True)
      fake_output = discriminator(generated_images, training=True)

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

    gradients_of_generator = gen_tape.gradient(gen_loss, generator.trainable_variables)
    gradients_of_discriminator = disc_tape.gradient(disc_loss, discriminator.trainable_variables)

    generator_optimizer.apply_gradients(zip(gradients_of_generator, generator.trainable_variables))
    discriminator_optimizer.apply_gradients(zip(gradients_of_discriminator, discriminator.trainable_variables))
def train(dataset, epochs):
  for epoch in range(epochs):
    start = time.time()

    for image_batch in dataset:
      train_step(image_batch)

    # Produce images for the GIF as we go
    display.clear_output(wait=True)
    generate_and_save_images(generator,
                             epoch + 1,
                             seed)

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

    print ('Time for epoch {} is {} sec'.format(epoch + 1, time.time()-start))

  # Generate after the final epoch
  display.clear_output(wait=True)
  generate_and_save_images(generator,
                           epochs,
                           seed)

 
画像を生成してセーブする

def generate_and_save_images(model, epoch, test_input):
  # Notice `training` is set to False.
  # This is so all layers run in inference mode (batchnorm).
  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('image_at_epoch_{:04d}.png'.format(epoch))
  plt.show()

 

モデルを訓練する

generator と discriminator を同時に訓練するために上で定義された train() メソッドを呼び出します。注意してください、GAN の訓練はトリッキーであり得ます。generator と discriminator は互いに圧倒 (= overpower) しないこと (e.g., それらは同様なレートで訓練します) は重要です。

訓練の最初では、生成された画像はランダムノイズのように見えます。訓練が進むにつれて、生成される数字は段々とリアルに見えます。およそ 50 エポック後、それらは MNIST 数字に似ます。Colab のデフォルト設定で約 1 分 / エポックかかるかもしれません。

train(train_dataset, EPOCHS)

 
最新のチェックポイントをリストアします。

checkpoint.restore(tf.train.latest_checkpoint(checkpoint_dir))
<tensorflow.python.training.tracking.util.CheckpointLoadStatus at 0x7f0b0049df98>

 

GIF を作成する

# Display a single image using the epoch number
def display_image(epoch_no):
  return PIL.Image.open('image_at_epoch_{:04d}.png'.format(epoch_no))
display_image(EPOCHS)

 
訓練の間にセーブされた画像を使用して animated gif を作成するために imageio を使用します。

anim_file = 'dcgan.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)

 

Next steps

このチュートリアルは GAN を書いて訓練するために必要な完全なコードを示しました。次のステップとして、異なるデータセットで実験することを望むかもしれません、例えば、Kaggle で利用可能な Large-scale Celeb Faces Attributes (CelebA) データセットです。GAN についてより多く学習するためには NIPS 2016 Tutorial: Generative Adversarial Networks を勧めます。

 

以上






TensorFlow 2.0 : 上級 Tutorials : 生成 :- DeepDream

TensorFlow 2.0 : 上級 Tutorials : 生成 :- DeepDream (翻訳/解説)

翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 11/21/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/

 

生成 :- DeepDream

このチュートリアルは、Alexander Mordvintsev の ブログ投稿 で説明されている、DeepDream の最小限の実装を含みます。

DeepDream はニューラルネットワークにより学習されたパターンを可視化する実験です。子供が雲を見てランダムな形状を解釈しようとするときと同様に、DeepDream はそれが画像で見るパターンを過剰解釈 (= over-interpret) して強化します (= enhance)。

それはネットワークを通して画像を forward してから、特定の層の活性に関して画像の勾配を計算することによりそれを行ないます。それから画像はこれらの活性を増加するために変更されて、ネットワークにより見られたパターンを強化して、そして夢のような画像という結果になります。このプロセスは “Inceptionism” として呼称されました (InceptionNet と、映画 Inception への参照です)。

ニューラルネットワーク “dream” をどのように作成してそれが画像で見たシュールなパターンを強化できるかを実演しましょう。

from __future__ import absolute_import, division, print_function, unicode_literals
import tensorflow as tf
import numpy as np

import matplotlib as mpl

from IPython.display import clear_output
from matplotlib import pyplot as plt
from tensorflow.keras.preprocessing import image

 

夢にする (= dream-ify) 画像を選択する

このチュートリアルのために、ラブラドール の画像を使用しましょう。

url = 'https://storage.googleapis.com/download.tensorflow.org/example_images/YellowLabradorLooking_new.jpg'
# Download an image and read it into a NumPy array.
def download(url, target_size=None):
  name = url.split('/')[-1]
  image_path = tf.keras.utils.get_file(name, origin=url)
  img = tf.keras.preprocessing.image.load_img(image_path, target_size=target_size)
  return img

# Normalize an image
def deprocess(img):
  img = 255*(img + 1.0)/2.0
  return tf.cast(img, tf.uint8)


# Display an image
def show(img):
  plt.figure(figsize=(12,12))
  plt.grid(False)
  plt.axis('off')
  plt.imshow(img)
  plt.show()

# Downsizing the image makes it easier to work with.
original_img = download(url, target_size=[225, 375])
original_img = np.array(original_img)

show(original_img)
Downloading data from https://storage.googleapis.com/download.tensorflow.org/example_images/YellowLabradorLooking_new.jpg
90112/83281 [================================] - 0s 0us/step

 

特徴抽出モデル

事前訓練された画像分類モデルをダウンロードして準備します。InceptionV3 を使用します、これは DeepDream で元々使用されたモデルに類似しています。任意の 事前訓練されたモデル は動作しますが、けれども下で層の名前を (これを変更している場合) 調整しなければならないことに注意してください。

base_model = tf.keras.applications.InceptionV3(include_top=False, weights='imagenet')
Downloading data from https://github.com/fchollet/deep-learning-models/releases/download/v0.5/inception_v3_weights_tf_dim_ordering_tf_kernels_notop.h5
87916544/87910968 [==============================] - 6s 0us/step

DeepDream のアイデアは層 (or 層群) を選択して画像が層を「励起する」ような方法で「損失」を段々と最大化することです。組み入れられる特徴の複雑さは貴方により選択された層に依拠します、i.e., より低い層はストローク (短い線) や単純なパターンを生成する一方で、より深い層は画像の洗練された特徴、あるいはオブジェクト全体でさえも与えます。

InceptionV3 アーキテクチャは非常に巨大です (モデル・アーキテクチャのグラフについては TensorFlow の 研究 repo を見てください)。DeepDream については、関心のある層は畳み込みが結合されてるところのものです。InceptionV3 では 11 のこれらの層があります、’mixed0′ から ‘mixed10’ と名前付けられています。異なる層の使用は異なる夢のような画像という結果になります。より深い層は (目や顔のような) 高位な特徴に対応する一方で、早い層は (エッジ、形状とテクスチャーのような) 単純な特徴に対応します。下で選択された層で自由に実験してください、しかしより深い層はその上で訓練するためにより長い時間かかることに留意してください、何故ならば勾配計算がより深いためです。

# Maximize the activations of these layers
names = ['mixed3', 'mixed5']
layers = [base_model.get_layer(name).output for name in names]

# Create the feature extraction model
dream_model = tf.keras.Model(inputs=base_model.input, outputs=layers)

 

損失を計算する

損失は選択された層の活性の総計です。損失は各層で正規化されますので、より巨大な層からの寄与がより小さい層より重いということはありません。通常は、損失は勾配降下を通して最小化することを望む量です。DeepDream では、損失を勾配上昇を通して最大化します。

def calc_loss(img, model):
  # Pass forward the image through the model to retrieve the activations.
  # Converts the image into a batch of size 1.
  img_batch = tf.expand_dims(img, axis=0)
  layer_activations = model(img_batch)

  losses = []
  for act in layer_activations:
    loss = tf.math.reduce_mean(act)
    losses.append(loss)

  return  tf.reduce_sum(losses)

 

勾配上昇 (= gradient = ascent)

ひとたび選択された層のための損失を計算すれば、残されたことの総ては画像に関する勾配を計算してそれらを元の画像に追加することです。

勾配を画像に追加することはネットワークにより見られたパターンを強化します。各ステップで、ネットワークの特定の層の活性を段々と励起する画像を作成します。

@tf.function
def deepdream(model, img, step_size):
    with tf.GradientTape() as tape:
      # This needs gradients relative to `img`
      # `GradientTape` only watches `tf.Variable`s by default
      tape.watch(img)
      loss = calc_loss(img, model)

    # Calculate the gradient of the loss with respect to the pixels of the input image.
    gradients = tape.gradient(loss, img)

    # Normalize the gradients.
    gradients /= tf.math.reduce_std(gradients) + 1e-8 
    
    # In gradient ascent, the "loss" is maximized so that the input image increasingly "excites" the layers.
    # You can update the image by directly adding the gradients (because they're the same shape!)
    img = img + gradients*step_size
    img = tf.clip_by_value(img, -1, 1)

    return loss, img
def run_deep_dream_simple(model, img, steps=100, step_size=0.01):
  # Convert from uint8 to the range expected by the model.
  img = tf.keras.applications.inception_v3.preprocess_input(img)

  for step in range(steps):
    loss, img = deepdream(model, img, step_size)
    
    if step % 100 == 0:
      clear_output(wait=True)
      show(deprocess(img))
      print ("Step {}, loss {}".format(step, loss))


  result = deprocess(img)
  clear_output(wait=True)
  show(result)

  return result
dream_img = run_deep_dream_simple(model=dream_model, img=original_img, 
                                  steps=800, step_size=0.001)

 

それをオクターブ引き上げる (= Taking it up an octave)

悪くはないですが、この最初の試みでは 2, 3 の問題点があります :

  1. 出力は noisy です (これは tf.image.total_variation 損失で対処されるでしょう)。
  2. 画像は低解像度です。
  3. パターンはそれらは総て同じ細かさ (= granularity) で起きているようです。

これら総ての問題に対処する一つのアプローチは勾配上昇を異なるスケールで適用することです。これはより小さいスケールで生成されたパターンがより高いスケールのパターンに組み込まれるて追加の詳細で満たされることを可能にします。

これを行なうために前の勾配上昇アプローチを遂行してから画像のサイズを増やし (これはオクターブとして参照されます)、そしてこのプロセスを複数のオクターブのために繰り返すことができます。

OCTAVE_SCALE = 1.3

img = tf.constant(np.array(original_img))
base_shape = tf.cast(tf.shape(img)[:-1], tf.float32)

for n in range(3):
  new_shape = tf.cast(base_shape*(OCTAVE_SCALE**n), tf.int32)

  img = tf.image.resize(img, new_shape).numpy()

  img = run_deep_dream_simple(model=dream_model, img=img, steps=200, step_size=0.001)

clear_output(wait=True)
show(img)

 

タイルでスケールアップする

考えるべき一つのことは画像がサイズ的に増加するために、勾配計算を遂行するために必要な時間とメモリです。上のオクターブ実装は非常に巨大な画像や多くのオクターブ上では動作しません。この問題を回避するために画像をタイルに分割して各タイルのための勾配を計算することができます。

各 tiled された計算の前に画像にランダムシフトを適用することはタイルの継ぎ目が現れることを防ぎます。

ランダムシフトを実装することから始めます :

def random_roll(img, maxroll):
  # Randomly shift the image to avoid tiled boundaries.
  shift = tf.random.uniform(shape=[2], minval=-maxroll, maxval=maxroll, dtype=tf.int32)
  shift_down, shift_right = shift[0],shift[1] 
  img_rolled = tf.roll(tf.roll(img, shift_right, axis=1), shift_down, axis=0)
  return shift_down, shift_right, img_rolled
shift_down, shift_right, img_rolled = random_roll(np.array(original_img), 512)
show(img_rolled)

ここに先に定義された deepdream 関数の tiled された同値があります :

@tf.function
def get_tiled_gradients(model, img, tile_size=512):
  shift_down, shift_right, img_rolled = random_roll(img, tile_size)

  # Initialize the image gradients to zero.
  gradients = tf.zeros_like(img_rolled)

  for x in tf.range(0, img_rolled.shape[0], tile_size):
    for y in tf.range(0, img_rolled.shape[1], tile_size):
      # Calculate the gradients for this tile.
      with tf.GradientTape() as tape:
        # This needs gradients relative to `img_rolled`.
        # `GradientTape` only watches `tf.Variable`s by default.
        tape.watch(img_rolled)

        # Extract a tile out of the image.
        img_tile = img_rolled[x:x+tile_size, y:y+tile_size]
        loss = calc_loss(img_tile, model)

      # Update the image gradients for this tile.
      gradients = gradients + tape.gradient(loss, img_rolled)

  # Undo the random shift applied to the image and its gradients.
  gradients = tf.roll(tf.roll(gradients, -shift_right, axis=1), -shift_down, axis=0)

  # Normalize the gradients.
  gradients /= tf.math.reduce_std(gradients) + 1e-8 

  return gradients 

これをまとめるとスケーラブルで、octave-aware な deepdream 実装を与えます :

def run_deep_dream_with_octaves(model, img, steps_per_octave=100, step_size=0.01, 
                                num_octaves=3, octave_scale=1.3):
  img = tf.keras.preprocessing.image.img_to_array(img)
  img = tf.keras.applications.inception_v3.preprocess_input(img)

  for octave in range(num_octaves):
    # Scale the image based on the octave
    if octave>0:
      new_size = tf.cast(tf.convert_to_tensor(img.shape[:2]), tf.float32)*octave_scale
      img = tf.image.resize(img, tf.cast(new_size, tf.int32))

    for step in range(steps_per_octave):
      gradients = get_tiled_gradients(model, img)
      img = img + gradients*step_size
      img = tf.clip_by_value(img, -1, 1)

      if step % 10 == 0:
        clear_output(wait=True)
        show(deprocess(img))
        print ("Octave {}, Step {}".format(octave, step))
    
  clear_output(wait=True)
  result = deprocess(img)
  show(result)

  return result
dream_img = run_deep_dream_with_octaves(model=dream_model, img=original_img, step_size=0.01)

clear_output()
show(original_img)
show(dream_img)

遥かに良いです!貴方の DeepDream-ed 画像がどのように見えるかを変更するためにオクターブの数、オクターブ・スケールそして活性化された層で遊んでください。

読者はまた TensorFlow Lucid に興味があるかもしれません、これはニューラルネットワークを可視化して解釈するためにこのチュートリアルで紹介されたアイデアを拡張します。

 

以上






TensorFlow 2.0 : 上級 Tutorials : 構造化データ :- 時系列予報

TensorFlow 2.0 : 上級 Tutorials : 構造化データ :- 時系列予報 (翻訳/解説)

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

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

* サンプルコードの動作確認はしておりますが、必要な場合には適宜、追加改変しています。
* ご自由にリンクを張って頂いてかまいませんが、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/

 

構造化データ :- 時系列予報

このチュートリアルはリカレント・ニューラルネットワーク (RNN) を使用する時系列予報 (= forecasting) へのイントロダクションです。これは 2 つのパートでカバーされます : 最初に、単量時系列を予報して、それから多変量時系列を予報します。

from __future__ import absolute_import, division, print_function, unicode_literals
import tensorflow as tf

import matplotlib as mpl
import matplotlib.pyplot as plt
import numpy as np
import os
import pandas as pd

mpl.rcParams['figure.figsize'] = (8, 6)
mpl.rcParams['axes.grid'] = False

 

天気データセット

このチュートリアルは Max-Planck-Institute for Biogeochemistry (生物地球化学) により記録された 天気時系列データセット を使用します。

このデータセットは、気温、気圧と湿度のような 14 の異なる特徴を含みます。これらは 2003 年に始まり、10 分毎に収集されました。効率のために、2009 と 2016 の間に収集されたデータだけを使用します。データセットのこのセクションは François Chollet により彼の書籍 Deep Learning with Python のために準備されました。

zip_path = tf.keras.utils.get_file(
    origin='https://storage.googleapis.com/tensorflow/tf-keras-datasets/jena_climate_2009_2016.csv.zip',
    fname='jena_climate_2009_2016.csv.zip',
    extract=True)
csv_path, _ = os.path.splitext(zip_path)
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/jena_climate_2009_2016.csv.zip
13574144/13568290 [==============================] - 0s 0us/step
df = pd.read_csv(csv_path)

データを少し見てみましょう。

df.head()

Date Time p (mbar) T (degC) Tpot (K) Tdew (degC) rh (%) VPmax (mbar) VPact (mbar) VPdef (mbar) sh (g/kg) H2OC (mmol/mol) rho (g/m**3) wv (m/s) max. wv (m/s) wd (deg)
0 01.01.2009 00:10:00 996.52 -8.02 265.40 -8.90 93.3 3.33 3.11 0.22 1.94 3.12 1307.75 1.03 1.75 152.3
1 01.01.2009 00:20:00 996.57 -8.41 265.01 -9.28 93.4 3.23 3.02 0.21 1.89 3.03 1309.80 0.72 1.50 136.1
2 01.01.2009 00:30:00 996.53 -8.51 264.91 -9.31 93.9 3.21 3.01 0.20 1.88 3.02 1310.24 0.19 0.63 171.6
3 01.01.2009 00:40:00 996.51 -8.31 265.12 -9.07 94.2 3.26 3.07 0.19 1.92 3.08 1309.19 0.34 0.50 198.0
4 01.01.2009 00:50:00 996.51 -8.27 265.15 -9.04 94.1 3.27 3.08 0.19 1.92 3.09 1309.00 0.32 0.63 214.3

上で見れるように、観測は 10 分毎に記録されます。これは、単一の時間 (= hour) について、6 観測を持つことを意味します。同様に、単一の日 (= day) は 144 (6×24) 観測を含みます。

特定の時間が与えられたとき、未来の 6 時間で気温を予測することを望むとしましょう。この予測を行なうために、観測の 5 日を使用することを選択します。こうして、モデルを訓練するために最後の 720(5×144) 観測を含むウィンドウを作成します。このデータセットを実験するために良いものにする、多くのそのような構成が可能です。

下の関数はその上で訓練するモデルのために上で説明された時間のウィンドウを返します。パラメータ history_size は情報の過去のウィンドウのサイズです。target_size は未来のどのくらい離れてモデルが予測することを学習する必要があるかです。target_size は予測される必要があるラベルです。

def univariate_data(dataset, start_index, end_index, history_size, target_size):
  data = []
  labels = []

  start_index = start_index + history_size
  if end_index is None:
    end_index = len(dataset) - target_size

  for i in range(start_index, end_index):
    indices = range(i-history_size, i)
    # Reshape data from (history_size,) to (history_size, 1)
    data.append(np.reshape(dataset[indices], (history_size, 1)))
    labels.append(dataset[i+target_size])
  return np.array(data), np.array(labels)

次のチュートリアルの両者で、データの最初の 300,000 行は訓練データセットで、残りの検証データセットがあります。これは訓練データの ~2100 日分になります。

TRAIN_SPLIT = 300000

再現性を保証するために seed を設定します。

tf.random.set_seed(13)

 

パート 1 : 単変量時系列を予報する

最初に、単一の特徴 (temperature) だけを使用してモデルを訓練し、そしてそれを未来のその値のための予測を行なうために使用します。

最初にデータセットから temperature だけを抽出しましょう。

uni_data = df['T (degC)']
uni_data.index = df['Date Time']
uni_data.head()
Date Time
01.01.2009 00:10:00   -8.02
01.01.2009 00:20:00   -8.41
01.01.2009 00:30:00   -8.51
01.01.2009 00:40:00   -8.31
01.01.2009 00:50:00   -8.27
Name: T (degC), dtype: float64

このデータが時間に渡りどのように見えるか観察しましょう。

uni_data.plot(subplots=True)
array([],
      dtype=object)

uni_data = uni_data.values

ニューラルネットワークを訓練する前に特徴を正規化することは重要です。それを行なう一般的な方法は各特徴の平均を引いてそして標準偏差で除算することです。

Note: 平均と標準偏差は訓練データだけを使用して計算されるべきです。

uni_train_mean = uni_data[:TRAIN_SPLIT].mean()
uni_train_std = uni_data[:TRAIN_SPLIT].std()

データを正規化しましょう。

uni_data = (uni_data-uni_train_mean)/uni_train_std

今は単変量モデルのためのデータを作成しましょう。パート 1 については、モデルは最後の 20 の記録された気温観測が与えられて、そして次の時間ステップでの気温を予測することを学習する必要があります。

univariate_past_history = 20
univariate_future_target = 0

x_train_uni, y_train_uni = univariate_data(uni_data, 0, TRAIN_SPLIT,
                                           univariate_past_history,
                                           univariate_future_target)
x_val_uni, y_val_uni = univariate_data(uni_data, TRAIN_SPLIT, None,
                                       univariate_past_history,
                                       univariate_future_target)

これが univariate_data 関数が返すものです。

print ('Single window of past history')
print (x_train_uni[0])
print ('\n Target temperature to predict')
print (y_train_uni[0])
Single window of past history
[[-1.99766294]
 [-2.04281897]
 [-2.05439744]
 [-2.0312405 ]
 [-2.02660912]
 [-2.00113649]
 [-1.95134907]
 [-1.95134907]
 [-1.98492663]
 [-2.04513467]
 [-2.08334362]
 [-2.09723778]
 [-2.09376424]
 [-2.09144854]
 [-2.07176515]
 [-2.07176515]
 [-2.07639653]
 [-2.08913285]
 [-2.09260639]
 [-2.10418486]]

 Target temperature to predict
-2.1041848598100876

データが作成された今、単一のサンプルを見てみましょう。ネットワークに与えられる情報は青色で与えられ、そしてそれは赤い × 印の値を予測しなければなりません。

def create_time_steps(length):
  time_steps = []
  for i in range(-length, 0, 1):
    time_steps.append(i)
  return time_steps
def show_plot(plot_data, delta, title):
  labels = ['History', 'True Future', 'Model Prediction']
  marker = ['.-', 'rx', 'go']
  time_steps = create_time_steps(plot_data[0].shape[0])
  if delta:
    future = delta
  else:
    future = 0

  plt.title(title)
  for i, x in enumerate(plot_data):
    if i:
      plt.plot(future, plot_data[i], marker[i], markersize=10,
               label=labels[i])
    else:
      plt.plot(time_steps, plot_data[i].flatten(), marker[i], label=labels[i])
  plt.legend()
  plt.xlim([time_steps[0], (future+5)*2])
  plt.xlabel('Time-Step')
  return plt
show_plot([x_train_uni[0], y_train_uni[0]], 0, 'Sample Example')
<module 'matplotlib.pyplot' from '/home/kbuilder/.local/lib/python3.6/site-packages/matplotlib/pyplot.py'>

 

ベースライン

モデルを訓練することに進む前に、最初に単純なベースラインを設定しましょう。入力ポイントが与えられたとき、baseline メソッドは総ての履歴を見てそして次のポイントを最後の 20 観測の平均として予測します。

def baseline(history):
  return np.mean(history)
show_plot([x_train_uni[0], y_train_uni[0], baseline(x_train_uni[0])], 0,
           'Baseline Prediction Example')
<module 'matplotlib.pyplot' from '/home/kbuilder/.local/lib/python3.6/site-packages/matplotlib/pyplot.py'>

リカレント・ニューラルネットワークを使用してこのベースラインに打ち勝てるか見ましょう。

 

リカレント・ニューラルネットワーク

リカレント・ニューラルネットワーク (RNN) は時系列データに適切なニューラルネットワークのタイプです。RNN は、そこまでに見た情報を要約した内部状態を保持しながら、時系列をステップ毎に処理します。より詳細については、RNN チュートリアル を読んでください。このチュートリアルでは、Long Short Term Memory (LSTM) と呼ばれる特殊な RNN 層を使用します。

今はデータセットを shuffle, batch と cache するために tf.data を使用しましょう。

BATCH_SIZE = 256
BUFFER_SIZE = 10000

train_univariate = tf.data.Dataset.from_tensor_slices((x_train_uni, y_train_uni))
train_univariate = train_univariate.cache().shuffle(BUFFER_SIZE).batch(BATCH_SIZE).repeat()

val_univariate = tf.data.Dataset.from_tensor_slices((x_val_uni, y_val_uni))
val_univariate = val_univariate.batch(BATCH_SIZE).repeat()

次の可視化はデータがバッチ化の後でどのように表わされるかを理解する助けになるはずです。

LSTM はそれが与えられるデータの入力 shape を要求することを見るでしょう。

simple_lstm_model = tf.keras.models.Sequential([
    tf.keras.layers.LSTM(8, input_shape=x_train_uni.shape[-2:]),
    tf.keras.layers.Dense(1)
])

simple_lstm_model.compile(optimizer='adam', loss='mae')

モデルの出力を確認するために、サンプル予測を作成しましょう。

for x, y in val_univariate.take(1):
    print(simple_lstm_model.predict(x).shape)
(256, 1)

今はモデルを訓練しましょう。巨大なサイズのデータセットであるため、時間を節約するために、通常成されるような完全な訓練データの代わりに、各エポックは 200 ステップの間だけ実行します。

EVALUATION_INTERVAL = 200
EPOCHS = 10

simple_lstm_model.fit(train_univariate, epochs=EPOCHS,
                      steps_per_epoch=EVALUATION_INTERVAL,
                      validation_data=val_univariate, validation_steps=50)
Train for 200 steps, validate for 50 steps
Epoch 1/10
200/200 [==============================] - 3s 16ms/step - loss: 0.4075 - val_loss: 0.1351
Epoch 2/10
200/200 [==============================] - 1s 5ms/step - loss: 0.1118 - val_loss: 0.0360
Epoch 3/10
200/200 [==============================] - 1s 5ms/step - loss: 0.0490 - val_loss: 0.0289
Epoch 4/10
200/200 [==============================] - 1s 5ms/step - loss: 0.0444 - val_loss: 0.0257
Epoch 5/10
200/200 [==============================] - 1s 5ms/step - loss: 0.0299 - val_loss: 0.0235
Epoch 6/10
200/200 [==============================] - 1s 5ms/step - loss: 0.0317 - val_loss: 0.0223
Epoch 7/10
200/200 [==============================] - 1s 5ms/step - loss: 0.0287 - val_loss: 0.0206
Epoch 8/10
200/200 [==============================] - 1s 5ms/step - loss: 0.0263 - val_loss: 0.0196
Epoch 9/10
200/200 [==============================] - 1s 5ms/step - loss: 0.0254 - val_loss: 0.0182
Epoch 10/10
200/200 [==============================] - 1s 5ms/step - loss: 0.0227 - val_loss: 0.0173

<tensorflow.python.keras.callbacks.History at 0x7fc322e8b908>

 
単純な LSTM モデルを使用して予測する

単純な LSTM を訓練した今、試して 2, 3 の予測を行なってみましょう。

for x, y in val_univariate.take(3):
  plot = show_plot([x[0].numpy(), y[0].numpy(),
                    simple_lstm_model.predict(x)[0]], 0, 'Simple LSTM model')
  plot.show()

これはベースラインよりも良いようです。基本を見た今、パート 2 に移りましょう、そこでは多変量時系列で作業します。

 

パート 2 : 多変量時系列を予報する

元のデータセットは 14 の特徴を含みます。単純化のために、このセクションは元の 14 の 3 つだけを考えます。使用される特徴は気温 (= air temperature)、気圧 (= atmospheric pressure) と空気密度 (= air density) です。

より多くの特徴を使用するには、このリストにそれらの名前を追加します。

features_considered = ['p (mbar)', 'T (degC)', 'rho (g/m**3)']
features = df[features_considered]
features.index = df['Date Time']
features.head()

p (mbar) T (degC) rho (g/m**3)
Date Time
01.01.2009 00:10:00 996.52 -8.02 1307.75
01.01.2009 00:20:00 996.57 -8.41 1309.80
01.01.2009 00:30:00 996.53 -8.51 1310.24
01.01.2009 00:40:00 996.51 -8.31 1309.19
01.01.2009 00:50:00 996.51 -8.27 1309.00

これらの特徴の各々が時間に渡りどのように変換するか見てみましょう。

features.plot(subplots=True)
array([,
       ,
       ],
      dtype=object)

言及されたように、最初のステップは訓練データの平均と標準偏差を使用してデータセットを正規化することです。

dataset = features.values
data_mean = dataset[:TRAIN_SPLIT].mean(axis=0)
data_std = dataset[:TRAIN_SPLIT].std(axis=0)

dataset = (dataset-data_mean)/data_std

 

シングルステップ・モデル

シングルステップ・セットアップでは、モデルは提供される幾つかの履歴をもとに未来の単一ポイントを予測することを学習します。

下の関数は下と (訳注: 原文ママ) 同じ windowing タスクを遂行しますが、けれども、ここではそれは与えられたステップサイズに基づいて過去の観測をサンプリングします。

def multivariate_data(dataset, target, start_index, end_index, history_size,
                      target_size, step, single_step=False):
  data = []
  labels = []

  start_index = start_index + history_size
  if end_index is None:
    end_index = len(dataset) - target_size

  for i in range(start_index, end_index):
    indices = range(i-history_size, i, step)
    data.append(dataset[indices])

    if single_step:
      labels.append(target[i+target_size])
    else:
      labels.append(target[i:i+target_size])

  return np.array(data), np.array(labels)

このチュートリアルでは、ネットワークは最後の 5 日間からデータを見せられます、i.e. 毎時サンプリングされる 720 観測です。サンプリングは 1 時間毎に行なわれます、何故ならば 60 分内に極端な変更は想定されないためです。こうして、120 観測が最後の 5 日間の履歴を表します。シングルステップ予測モデルについては、データポイントのためのラベルは未来に向けた気温 12 時間です。このためのラベルを作成するため、72(12*6) 観測後の気温が使用されます。

past_history = 720
future_target = 72
STEP = 6

x_train_single, y_train_single = multivariate_data(dataset, dataset[:, 1], 0,
                                                   TRAIN_SPLIT, past_history,
                                                   future_target, STEP,
                                                   single_step=True)
x_val_single, y_val_single = multivariate_data(dataset, dataset[:, 1],
                                               TRAIN_SPLIT, None, past_history,
                                               future_target, STEP,
                                               single_step=True)

単一データポイントを見ましょう。

print ('Single window of past history : {}'.format(x_train_single[0].shape))
Single window of past history : (120, 3)
train_data_single = tf.data.Dataset.from_tensor_slices((x_train_single, y_train_single))
train_data_single = train_data_single.cache().shuffle(BUFFER_SIZE).batch(BATCH_SIZE).repeat()

val_data_single = tf.data.Dataset.from_tensor_slices((x_val_single, y_val_single))
val_data_single = val_data_single.batch(BATCH_SIZE).repeat()
single_step_model = tf.keras.models.Sequential()
single_step_model.add(tf.keras.layers.LSTM(32,
                                           input_shape=x_train_single.shape[-2:]))
single_step_model.add(tf.keras.layers.Dense(1))

single_step_model.compile(optimizer=tf.keras.optimizers.RMSprop(), loss='mae')

サンプル予測を確認しましょう。

for x, y in val_data_single.take(1):
  print(single_step_model.predict(x).shape)
(256, 1)
single_step_history = single_step_model.fit(train_data_single, epochs=EPOCHS,
                                            steps_per_epoch=EVALUATION_INTERVAL,
                                            validation_data=val_data_single,
                                            validation_steps=50)
Train for 200 steps, validate for 50 steps
Epoch 1/10
200/200 [==============================] - 5s 25ms/step - loss: 0.3090 - val_loss: 0.2646
Epoch 2/10
200/200 [==============================] - 2s 11ms/step - loss: 0.2625 - val_loss: 0.2428
Epoch 3/10
200/200 [==============================] - 2s 11ms/step - loss: 0.2613 - val_loss: 0.2457
Epoch 4/10
200/200 [==============================] - 2s 11ms/step - loss: 0.2569 - val_loss: 0.2444
Epoch 5/10
200/200 [==============================] - 2s 11ms/step - loss: 0.2263 - val_loss: 0.2357
Epoch 6/10
200/200 [==============================] - 2s 11ms/step - loss: 0.2411 - val_loss: 0.2696
Epoch 7/10
200/200 [==============================] - 2s 10ms/step - loss: 0.2414 - val_loss: 0.2564
Epoch 8/10
200/200 [==============================] - 2s 10ms/step - loss: 0.2407 - val_loss: 0.2414
Epoch 9/10
200/200 [==============================] - 2s 10ms/step - loss: 0.2447 - val_loss: 0.2459
Epoch 10/10
200/200 [==============================] - 2s 10ms/step - loss: 0.2381 - val_loss: 0.2499
def plot_train_history(history, title):
  loss = history.history['loss']
  val_loss = history.history['val_loss']

  epochs = range(len(loss))

  plt.figure()

  plt.plot(epochs, loss, 'b', label='Training loss')
  plt.plot(epochs, val_loss, 'r', label='Validation loss')
  plt.title(title)
  plt.legend()

  plt.show()
plot_train_history(single_step_history,
                   'Single Step Training and validation loss')

 
シングルステップ未来を予測する

モデルが訓練された今、幾つかのサンプル予測を行ないましょう。モデルは (120 データポイントの) 毎時サンプリングされた過去 5 日間に渡る 3 つの特徴の履歴が与えられます、ゴールは気温を予測することですので、プロットは過去の気温を表示するだけです。予測は未来に向けて 1 日間行なわれます (履歴と予測の隔たりゆえに)。

for x, y in val_data_single.take(3):
  plot = show_plot([x[0][:, 1].numpy(), y[0].numpy(),
                    single_step_model.predict(x)[0]], 12,
                   'Single Step Prediction')
  plot.show()

 

マルチステップ・モデル

マルチステップ予測モデルでは、過去の履歴が与えられたとき、モデルは未来の値の範囲を予測することを学習する必要があります。こうして、単一の特徴ポイントだけが予測されるシングルステップ・モデルと違い、マルチステップ・モデルは未来のシークエンスを予測します。

マルチステップ・モデルについて、訓練データは再度毎時サンプリングされた過去 5 日間に渡る記録から成ります。けれども、ここでは、モデルは次の 12 時間について気温を予測することを学習する必要があります。観測は 10 分毎に取られますので、出力は 72 予測です。このタスクのために、データセットはそれに応じて準備される必要がありますので、最初のステップはそれを単に再度作成することですが、異なるターゲット・ウィンドウでです。

future_target = 72
x_train_multi, y_train_multi = multivariate_data(dataset, dataset[:, 1], 0,
                                                 TRAIN_SPLIT, past_history,
                                                 future_target, STEP)
x_val_multi, y_val_multi = multivariate_data(dataset, dataset[:, 1],
                                             TRAIN_SPLIT, None, past_history,
                                             future_target, STEP)

サンプル・データポイントを確認しましょう。

print ('Single window of past history : {}'.format(x_train_multi[0].shape))
print ('\n Target temperature to predict : {}'.format(y_train_multi[0].shape))
Single window of past history : (120, 3)

 Target temperature to predict : (72,)
train_data_multi = tf.data.Dataset.from_tensor_slices((x_train_multi, y_train_multi))
train_data_multi = train_data_multi.cache().shuffle(BUFFER_SIZE).batch(BATCH_SIZE).repeat()

val_data_multi = tf.data.Dataset.from_tensor_slices((x_val_multi, y_val_multi))
val_data_multi = val_data_multi.batch(BATCH_SIZE).repeat()

サンプル・データポイントをプロットする。

def multi_step_plot(history, true_future, prediction):
  plt.figure(figsize=(12, 6))
  num_in = create_time_steps(len(history))
  num_out = len(true_future)

  plt.plot(num_in, np.array(history[:, 1]), label='History')
  plt.plot(np.arange(num_out)/STEP, np.array(true_future), 'bo',
           label='True Future')
  if prediction.any():
    plt.plot(np.arange(num_out)/STEP, np.array(prediction), 'ro',
             label='Predicted Future')
  plt.legend(loc='upper left')
  plt.show()

このプロットと続く同様のプロットでは、履歴と未来データは毎時サンプリングされます。

for x, y in train_data_multi.take(1):
  multi_step_
plot(x[0], y[0], np.array([0]))

ここでのタスクは前のタスクよりも少しだけ複雑ですので、今はモデルは 2 つの LSTM 層から成ります。最後に、72 予測が行なわれますので、dense 層は 72 予測を出力します。

multi_step_model = tf.keras.models.Sequential()
multi_step_model.add(tf.keras.layers.LSTM(32,
                                          return_sequences=True,
                                          input_shape=x_train_multi.shape[-2:]))
multi_step_model.add(tf.keras.layers.LSTM(16, activation='relu'))
multi_step_model.add(tf.keras.layers.Dense(72))

multi_step_model.compile(optimizer=tf.keras.optimizers.RMSprop(clipvalue=1.0), loss='mae')

モデルがそれが訓練される前にどのように予測するかを見ましょう。

for x, y in val_data_multi.take(1):
  print (multi_step_model.predict(x).shape)
(256, 72)
multi_step_history = multi_step_model.fit(train_data_multi, epochs=EPOCHS,
                                          steps_per_epoch=EVALUATION_INTERVAL,
                                          validation_data=val_data_multi,
                                          validation_steps=50)
Train for 200 steps, validate for 50 steps
Epoch 1/10
200/200 [==============================] - 26s 132ms/step - loss: 0.4964 - val_loss: 0.3075
Epoch 2/10
200/200 [==============================] - 22s 112ms/step - loss: 0.3469 - val_loss: 0.2830
Epoch 3/10
200/200 [==============================] - 22s 111ms/step - loss: 0.3313 - val_loss: 0.2461
Epoch 4/10
200/200 [==============================] - 22s 111ms/step - loss: 0.2431 - val_loss: 0.2052
Epoch 5/10
200/200 [==============================] - 23s 114ms/step - loss: 0.1973 - val_loss: 0.1946
Epoch 6/10
200/200 [==============================] - 22s 112ms/step - loss: 0.2060 - val_loss: 0.2099
Epoch 7/10
200/200 [==============================] - 22s 109ms/step - loss: 0.1978 - val_loss: 0.2066
Epoch 8/10
200/200 [==============================] - 22s 109ms/step - loss: 0.1965 - val_loss: 0.2015
Epoch 9/10
200/200 [==============================] - 22s 109ms/step - loss: 0.1978 - val_loss: 0.1859
Epoch 10/10
200/200 [==============================] - 22s 110ms/step - loss: 0.1901 - val_loss: 0.1821
plot_train_history(multi_step_history, 'Multi-Step Training and validation loss')

 
マルチステップ未来を予測する

今は貴方のネットワークが未来を予測することをどの程度上手く学習したかを見ましょう。

for x, y in val_data_multi.take(3):
  multi_step_plot(x[0], y[0], multi_step_model.predict(x)[0])

 

Next steps

このチュートリアルは RNN を使用する時系列予報への簡単なイントロダクションです。今ではストックマーケットを予測することを試して億万長者になるかもしれません。

加えて、(uni/multivariate_data 関数の代わりに) データを生成するための generator も書くかもしれません、それはよりメモリ効率的です。この time series windowing ガイドを確認してそれをこのチュートリアルで使用することもできます。

更なる理解のために、Hands-on Machine Learning with Scikit-Learn, Keras, and TensorFlow, 2nd Edition の 15 章と Deep Learning with Python の 6 章を読んでも良いです。

 

以上



TensorFlow 2.0 : 上級 Tutorials : 生成 :- 画風変換

TensorFlow 2.0 : 上級 Tutorials : 生成 :- 画風変換 (翻訳/解説)

翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 11/19/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/

 

生成 :- 画風変換

このチュートリアルは一つの画像をもう一つの画像のスタイルで構成するために深層学習を使用します (Picasso や Van Gogh のように描けたらと思うことがありませんか?)。これは画風変換 (= neural style transfer) として知られこのテクニックは A Neural Algorithm of Artistic Style (Gatys et al.) で概説されています。

Note: このチュートリアルは元の画風変換アルゴリズムを実演します。それは画像コンテンツを特定のスタイルに最適化します。現代的なアプローチはモデルを stylized 画像を直接生成するように訓練します (cyclegan に類似しています)。このアプローチは遥かに高速です (up to 1000x)。事前訓練された Arbitrary Image Stylization モジュールTensorFlow Hub で、そして TensorFlow Lite のために利用可能です。

画風変換は 2 つの画像 — コンテンツ画像と (有名な画家によるアートワークのような) スタイル参照画像 — を取りそしてそれらを一緒に混ぜ合わせるために使用される最適化テクニックです、その結果出力画像はコンテンツ画像のように見えますが、スタイル参照画像のスタイルで「描かれて」います。

これはコンテンツ画像のコンテンツ統計とスタイル参照画像のスタイル統計が適合するように出力画像を最適化するように実装されます。これらの統計は畳み込みニューラルネットワークを使用して画像から抽出されます。

例えば、この犬と Wassily Kandinsky のコンポジション 7 の画像を取りましょう :

イエロー・ラブラドール Looking, from Wikimedia Commons

さて Kandinsky がこのスタイルだけでこの犬の絵を描くと決めた場合それはどのようなものでしょう?このような感じでしょうか?

 

セットアップ

モジュールをインポートして configure する

from __future__ import absolute_import, division, print_function, unicode_literals
import tensorflow as tf
import IPython.display as display

import matplotlib.pyplot as plt
import matplotlib as mpl
mpl.rcParams['figure.figsize'] = (12,12)
mpl.rcParams['axes.grid'] = False

import numpy as np
import PIL.Image
import time
import functools
def tensor_to_image(tensor):
  tensor = tensor*255
  tensor = np.array(tensor, dtype=np.uint8)
  if np.ndim(tensor)>3:
    assert tensor.shape[0] == 1
    tensor = tensor[0]
  return PIL.Image.fromarray(tensor)

画像をダウンロードしてスタイル画像とコンテンツ画像を選択します :

content_path = tf.keras.utils.get_file('YellowLabradorLooking_new.jpg', 'https://storage.googleapis.com/download.tensorflow.org/example_images/YellowLabradorLooking_new.jpg')

# https://commons.wikimedia.org/wiki/File:Vassily_Kandinsky,_1913_-_Composition_7.jpg
style_path = tf.keras.utils.get_file('kandinsky5.jpg','https://storage.googleapis.com/download.tensorflow.org/example_images/Vassily_Kandinsky%2C_1913_-_Composition_7.jpg')
Downloading data from https://storage.googleapis.com/download.tensorflow.org/example_images/YellowLabradorLooking_new.jpg
90112/83281 [================================] - 0s 0us/step
Downloading data from https://storage.googleapis.com/download.tensorflow.org/example_images/Vassily_Kandinsky%2C_1913_-_Composition_7.jpg
196608/195196 [==============================] - 0s 0us/step

 

入力を可視化する

画像をロードするための関数を定義してその最大次元を 512 ピクセルに制限します。

def load_img(path_to_img):
  max_dim = 512
  img = tf.io.read_file(path_to_img)
  img = tf.image.decode_image(img, channels=3)
  img = tf.image.convert_image_dtype(img, tf.float32)

  shape = tf.cast(tf.shape(img)[:-1], tf.float32)
  long_dim = max(shape)
  scale = max_dim / long_dim

  new_shape = tf.cast(shape * scale, tf.int32)

  img = tf.image.resize(img, new_shape)
  img = img[tf.newaxis, :]
  return img

画像を表示するための単純な関数を作成します :

def imshow(image, title=None):
  if len(image.shape) > 3:
    image = tf.squeeze(image, axis=0)

  plt.imshow(image)
  if title:
    plt.title(title)
content_image = load_img(content_path)
style_image = load_img(style_path)

plt.subplot(1, 2, 1)
imshow(content_image, 'Content Image')

plt.subplot(1, 2, 2)
imshow(style_image, 'Style Image')

 

TF-Hub を使用する高速画風変換

このチュートリアルは元の画風変換アルゴリズムを実演します。それは画像コンテンツを特定のスタイルに最適化します。詳細に入る前に TensorFlow Hub モジュールがどのように行なうかを見ましょう :

import tensorflow_hub as hub
hub_module = hub.load('https://tfhub.dev/google/magenta/arbitrary-image-stylization-v1-256/1')
stylized_image = hub_module(tf.constant(content_image), tf.constant(style_image))[0]
tensor_to_image(stylized_image)

 

コンテンツとスタイル表現を定義する

画像のコンテンツとスタイル表現を得るためにモデルの中間層を使用します。ネットワークの入力層から始めて、最初の幾つかの層活性はエッジやテクスチャーのような低位特徴を表します。ネットワークを通り抜けるにつれて、最後の幾つかの層は高位特徴を表します — 車輪や目のような物体パーツ。このケースでは、貴方は VGG19 ネットワーク・アーキテクチャを使用しています、事前訓練された画像分類ネットワークです。これらの中間層は画像からのコンテンツとスタイルの表現を定義するために必要です。入力画像について、これらの中間層で対応するスタイルとコンテンツ・ターゲット表現をマッチさせようとします。

VGG19 をロードしてそれが正しく使用されていることを確かにするためにそれを画像上でテスト実行します。

x = tf.keras.applications.vgg19.preprocess_input(content_image*255)
x = tf.image.resize(x, (224, 224))
vgg = tf.keras.applications.VGG19(include_top=True, weights='imagenet')
prediction_probabilities = vgg(x)
prediction_probabilities.shape
Downloading data from https://github.com/fchollet/deep-learning-models/releases/download/v0.1/vgg19_weights_tf_dim_ordering_tf_kernels.h5
574717952/574710816 [==============================] - 36s 0us/step

TensorShape([1, 1000])
predicted_top_5 = tf.keras.applications.vgg19.decode_predictions(prediction_probabilities.numpy())[0]
[(class_name, prob) for (number, class_name, prob) in predicted_top_5]
Downloading data from https://storage.googleapis.com/download.tensorflow.org/data/imagenet_class_index.json
40960/35363 [==================================] - 0s 0us/step

[('Labrador_retriever', 0.49317113),
 ('golden_retriever', 0.23665294),
 ('kuvasz', 0.03635755),
 ('Chesapeake_Bay_retriever', 0.024182769),
 ('Greater_Swiss_Mountain_dog', 0.018646086)]

今は分類ヘッドなしで VGG19 をロードして、層名をリストします。

vgg = tf.keras.applications.VGG19(include_top=False, weights='imagenet')

print()
for layer in vgg.layers:
  print(layer.name)
Downloading data from https://github.com/fchollet/deep-learning-models/releases/download/v0.1/vgg19_weights_tf_dim_ordering_tf_kernels_notop.h5
80142336/80134624 [==============================] - 6s 0us/step

input_2
block1_conv1
block1_conv2
block1_pool
block2_conv1
block2_conv2
block2_pool
block3_conv1
block3_conv2
block3_conv3
block3_conv4
block3_pool
block4_conv1
block4_conv2
block4_conv3
block4_conv4
block4_pool
block5_conv1
block5_conv2
block5_conv3
block5_conv4
block5_pool

画像のスタイルとコンテンツを表わすためにネットワークから中間層を選択します :

# Content layer where will pull our feature maps
content_layers = ['block5_conv2'] 

# Style layer of interest
style_layers = ['block1_conv1',
                'block2_conv1',
                'block3_conv1', 
                'block4_conv1', 
                'block5_conv1']

num_content_layers = len(content_layers)
num_style_layers = len(style_layers)

スタイルとコンテンツのための中間層

それでは何故、事前訓練された画像分類ネットワーク内のこれらの中間層出力はスタイルとコンテンツ表現を定義することを可能にするのでしょう。

高位では、ネットワークが (このネットワークがそのために訓練された) 画像分類を遂行するために、それは画像を理解しなければなりません。これは生画像を入力ピクセルとして取り、生画像ピクセルを画像内の特徴表現の複雑な理解へと変換する内部表現を構築することを必要とします。

これはまた何故畳み込みニューラルネットワークが上手く一般化できるかの理由でもあります : それらは不変性を獲得することができてクラス内 (e.g. 猫 vs. 犬) で (背景ノイズと他の妨害には不可知論である) 特徴を定義します。こうして、生画像がモデルに供給されるところと出力分類ラベルの間のどこかで、モデルは複雑な特徴抽出器としてサーブします。モデルの中間層にアクセスすることにより、入力画像のコンテンツとスタイルを記述することができます。

 

モデルを構築する

tf.keras.applications でネットワークは設計されますので Keras functional API を使用して中間層値を容易に抽出できます。

functional API を使用してモデルを定義するには、入力と出力を指定します :

model = Model(inputs, outputs)

次の関数は中間層出力のリストを返す VGG19 モデルを構築します。

def vgg_layers(layer_names):
  """ Creates a vgg model that returns a list of intermediate output values."""
  # Load our model. Load pretrained VGG, trained on imagenet data
  vgg = tf.keras.applications.VGG19(include_top=False, weights='imagenet')
  vgg.trainable = False
  
  outputs = [vgg.get_layer(name).output for name in layer_names]

  model = tf.keras.Model([vgg.input], outputs)
  return model

そしてモデルを作成するために :

style_extractor = vgg_layers(style_layers)
style_outputs = style_extractor(style_image*255)

#Look at the statistics of each layer's output
for name, output in zip(style_layers, style_outputs):
  print(name)
  print("  shape: ", output.numpy().shape)
  print("  min: ", output.numpy().min())
  print("  max: ", output.numpy().max())
  print("  mean: ", output.numpy().mean())
  print()
block1_conv1
  shape:  (1, 336, 512, 64)
  min:  0.0
  max:  835.5256
  mean:  33.97525

block2_conv1
  shape:  (1, 168, 256, 128)
  min:  0.0
  max:  4625.8857
  mean:  199.82687

block3_conv1
  shape:  (1, 84, 128, 256)
  min:  0.0
  max:  8789.239
  mean:  230.78099

block4_conv1
  shape:  (1, 42, 64, 512)
  min:  0.0
  max:  21566.135
  mean:  791.24005

block5_conv1
  shape:  (1, 21, 32, 512)
  min:  0.0
  max:  3189.2542
  mean:  59.179478

 

スタイルを計算する

画像のコンテンツは中間特徴マップの値により表わされます。

画像のスタイルは異なる特徴マップに渡る平均と相関により記述できることが判明しています。各位置で特徴ベクトルの外積を取り、総ての位置に渡りその外積を平均することによりこの情報を含むグラム行列を計算します。このグラム行列は特定の層のために次のように計算できます :

$$G^l_{cd} = \frac{\sum_{ij} F^l_{ijc}(x)F^l_{ijd}(x)}{IJ}$$

これは tf.linalg.einsum 関数を使用して簡潔に実装できます :

def gram_matrix(input_tensor):
  result = tf.linalg.einsum('bijc,bijd->bcd', input_tensor, input_tensor)
  input_shape = tf.shape(input_tensor)
  num_locations = tf.cast(input_shape[1]*input_shape[2], tf.float32)
  return result/(num_locations)

 

スタイルとコンテンツを抽出する

スタイルとコンテンツ tensor を返すモデルを構築します。

class StyleContentModel(tf.keras.models.Model):
  def __init__(self, style_layers, content_layers):
    super(StyleContentModel, self).__init__()
    self.vgg =  vgg_layers(style_layers + content_layers)
    self.style_layers = style_layers
    self.content_layers = content_layers
    self.num_style_layers = len(style_layers)
    self.vgg.trainable = False

  def call(self, inputs):
    "Expects float input in [0,1]"
    inputs = inputs*255.0
    preprocessed_input = tf.keras.applications.vgg19.preprocess_input(inputs)
    outputs = self.vgg(preprocessed_input)
    style_outputs, content_outputs = (outputs[:self.num_style_layers], 
                                      outputs[self.num_style_layers:])

    style_outputs = [gram_matrix(style_output)
                     for style_output in style_outputs]

    content_dict = {content_name:value 
                    for content_name, value 
                    in zip(self.content_layers, content_outputs)}

    style_dict = {style_name:value
                  for style_name, value
                  in zip(self.style_layers, style_outputs)}
    
    return {'content':content_dict, 'style':style_dict}

画像上で呼び出されたとき、このモデルは style_layers のグラム行列 (style) と content_layers のコンテンツを返します :

extractor = StyleContentModel(style_layers, content_layers)

results = extractor(tf.constant(content_image))

style_results = results['style']

print('Styles:')
for name, output in sorted(results['style'].items()):
  print("  ", name)
  print("    shape: ", output.numpy().shape)
  print("    min: ", output.numpy().min())
  print("    max: ", output.numpy().max())
  print("    mean: ", output.numpy().mean())
  print()

print("Contents:")
for name, output in sorted(results['content'].items()):
  print("  ", name)
  print("    shape: ", output.numpy().shape)
  print("    min: ", output.numpy().min())
  print("    max: ", output.numpy().max())
  print("    mean: ", output.numpy().mean())
Styles:
   block1_conv1
    shape:  (1, 64, 64)
    min:  0.0055228453
    max:  28014.557
    mean:  263.79025

   block2_conv1
    shape:  (1, 128, 128)
    min:  0.0
    max:  61479.496
    mean:  9100.949

   block3_conv1
    shape:  (1, 256, 256)
    min:  0.0
    max:  545623.44
    mean:  7660.976

   block4_conv1
    shape:  (1, 512, 512)
    min:  0.0
    max:  4320502.0
    mean:  134288.84

   block5_conv1
    shape:  (1, 512, 512)
    min:  0.0
    max:  110005.37
    mean:  1487.0381

Contents:
   block5_conv2
    shape:  (1, 26, 32, 512)
    min:  0.0
    max:  2410.8796
    mean:  13.764149

 

勾配降下を実行する

このスタイルとコンテンツ抽出器により、今では画風変換アルゴリズム実装できます。各ターゲットに関連する画像の出力のための平均二乗誤差を計算することによりこれを行ない、それからこれらの損失の重み付けられた総計を取ります。

スタイルとコンテンツ・ターゲット値を設定します :

style_targets = extractor(style_image)['style']
content_targets = extractor(content_image)['content']

最適化するための画像を含む tf.Variable を定義します。これを迅速に行なうために、それをコンテンツ画像で初期化します (tf.Variable はコンテンツ画像と同じ shape でなければなりません) :

image = tf.Variable(content_image)

これは float 画像ですので、ピクセル値を 0 と 1 の間に保持する関数を定義します :

def clip_0_1(image):
  return tf.clip_by_value(image, clip_value_min=0.0, clip_value_max=1.0)

optimizer を作成します。ペーパーは LBFGS を勧めていますが、Adam もまた問題なく動作します :

opt = tf.optimizers.Adam(learning_rate=0.02, beta_1=0.99, epsilon=1e-1)

これを最適化するために、総計損失を得るために 2 つの損失の重み付けられた結合を使用します :

style_weight=1e-2
content_weight=1e4
def style_content_loss(outputs):
    style_outputs = outputs['style']
    content_outputs = outputs['content']
    style_loss = tf.add_n([tf.reduce_mean((style_outputs[name]-style_targets[name])**2) 
                           for name in style_outputs.keys()])
    style_loss *= style_weight / num_style_layers

    content_loss = tf.add_n([tf.reduce_mean((content_outputs[name]-content_targets[name])**2) 
                             for name in content_outputs.keys()])
    content_loss *= content_weight / num_content_layers
    loss = style_loss + content_loss
    return loss

画像を更新するために tf.GradientTape を使用します。

@tf.function()
def train_step(image):
  with tf.GradientTape() as tape:
    outputs = extractor(image)
    loss = style_content_loss(outputs)

  grad = tape.gradient(loss, image)
  opt.apply_gradients([(grad, image)])
  image.assign(clip_0_1(image))

今はテストのために数ステップ実行します :

train_step(image)
train_step(image)
train_step(image)
tensor_to_image(image)

それは動作していますので、より長い最適化を遂行します :

import time
start = time.time()

epochs = 10
steps_per_epoch = 100

step = 0
for n in range(epochs):
  for m in range(steps_per_epoch):
    step += 1
    train_step(image)
    print(".", end='')
  display.clear_output(wait=True)
  display.display(tensor_to_image(image))
  print("Train step: {}".format(step))
  
end = time.time()
print("Total time: {:.1f}".format(end-start))

Train step: 1000
Total time: 22.1

 

総計 variation 損失

この基本的実装への一つの不都合な点はそれは多くの高周波な人工物 (= artifacts) を生成することです。画像の高周波成分上で明示的な正則化項を使用してこれらを減じます。画風変換では、これはしばしば総計 variation 損失と呼ばれます :

def high_pass_x_y(image):
  x_var = image[:,:,1:,:] - image[:,:,:-1,:]
  y_var = image[:,1:,:,:] - image[:,:-1,:,:]

  return x_var, y_var
x_deltas, y_deltas = high_pass_x_y(content_image)

plt.figure(figsize=(14,10))
plt.subplot(2,2,1)
imshow(clip_0_1(2*y_deltas+0.5), "Horizontal Deltas: Original")

plt.subplot(2,2,2)
imshow(clip_0_1(2*x_deltas+0.5), "Vertical Deltas: Original")

x_deltas, y_deltas = high_pass_x_y(image)

plt.subplot(2,2,3)
imshow(clip_0_1(2*y_deltas+0.5), "Horizontal Deltas: Styled")

plt.subplot(2,2,4)
imshow(clip_0_1(2*x_deltas+0.5), "Vertical Deltas: Styled")

これは高周波成分がどのように増加したかを示します。

また、この高周波成分は基本的にはエッジ検出器です。例えば、Sobel エッジ検出器から類似の出力を得ることができます :

plt.figure(figsize=(14,10))

sobel = tf.image.sobel_edges(content_image)
plt.subplot(1,2,1)
imshow(clip_0_1(sobel[...,0]/4+0.5), "Horizontal Sobel-edges")
plt.subplot(1,2,2)
imshow(clip_0_1(sobel[...,1]/4+0.5), "Vertical Sobel-edges")

これに関連する正則化損失は値の二乗の総計です :

def total_variation_loss(image):
  x_deltas, y_deltas = high_pass_x_y(image)
  return tf.reduce_sum(tf.abs(x_deltas)) + tf.reduce_sum(tf.abs(y_deltas))
total_variation_loss(image).numpy()
149235.58

That demonstrated what it does. しかしそれを貴方自身で実装する必要はありません、TensorFlow は標準的な実装を含みます :

tf.image.total_variation(image).numpy()
array([149235.58], dtype=float32)

 

最適化を再実行する

total_variation_loss のための重みを選択します :

total_variation_weight=30

今はそれを train_step 関数に含めます :

@tf.function()
def train_step(image):
  with tf.GradientTape() as tape:
    outputs = extractor(image)
    loss = style_content_loss(outputs)
    loss += total_variation_weight*tf.image.total_variation(image)

  grad = tape.gradient(loss, image)
  opt.apply_gradients([(grad, image)])
  image.assign(clip_0_1(image))

最適化変数を再初期化します :

image = tf.Variable(content_image)

そして最適化を実行します :

import time
start = time.time()

epochs = 10
steps_per_epoch = 100

step = 0
for n in range(epochs):
  for m in range(steps_per_epoch):
    step += 1
    train_step(image)
    print(".", end='')
  display.clear_output(wait=True)
  display.display(tensor_to_image(image))
  print("Train step: {}".format(step))

end = time.time()
print("Total time: {:.1f}".format(end-start))

Train step: 1000
Total time: 23.2

最後に、結果をセーブします :

file_name = 'stylized-image.png'
tensor_to_image(image).save(file_name)

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

以上






TensorFlow 2.0 : 上級 Tutorials : 構造化データ :- 不均衡なデータ上の分類

TensorFlow 2.0 : 上級 Tutorials : 構造化データ :- 不均衡なデータ上の分類 (翻訳/解説)

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

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

* サンプルコードの動作確認はしておりますが、必要な場合には適宜、追加改変しています。
* ご自由にリンクを張って頂いてかまいませんが、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/

 

構造化データ :- 不均衡なデータ上の分類

このチュートリアルでは非常に不均衡なデータセットをどのように分類するかを実演します、そこでは一つのクラスのサンプル数がもう一つの (クラスの) サンプルに大いに数で上回ります。Kaggle でホストされている Credit Card Fraud Detection データセットで作業します。目的は全部で 284,807 トランザクションから僅か 492 の詐欺トランザクションを検出することです。不均衡なデータからモデルが学習することを助けるためにモデルと クラス重み を定義するために Keras を使用します。

このチュートリアルは以下を行なうための完全なコードを含みます :

  • Pandas を使用して CSV ファイルをロードする。
  • 訓練、検証とテストセットを作成する。
  • (クラス重みを設定することを含む) Keras を使用してモデルを定義して訓練する。
  • (精度 (= precision) と recall を含む) 様々なメトリクスを使用してモデルを評価する。
  • 次のような不均衡なデータを扱うための一般的なテクニックを試す :
    • クラス重み付け (= Class weighting)
    • Oversampling

 

セットアップ

from __future__ import absolute_import, division, print_function, unicode_literals
import tensorflow as tf
from tensorflow import keras

import os
import tempfile

import matplotlib as mpl
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import seaborn as sns

import sklearn
from sklearn.metrics import confusion_matrix
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
mpl.rcParams['figure.figsize'] = (12, 10)
colors = plt.rcParams['axes.prop_cycle'].by_key()['color']

 

データ前処理と調査

Kaggle Credit Card Fraud データセットをダウンロードする

Pandas は構造化データをロードしてそれで作業するための多くの役立つユティリティを持つ Python ライブラリで CSV を dataframe にダウンロードするために使用できます。

Note: このデータセットはビッグ・データマイニングと詐欺検出上の Worldline と ULB (Université Libre de Bruxelles) の 機械学習グループ の研究コラボレーションの間に収集されて解析されました。関連トピックについて現在と過去のプロジェクトのより詳細は ここDefeatFraud プロジェクトのページで利用可能です。

file = tf.keras.utils
raw_df = pd.read_csv('https://storage.googleapis.com/download.tensorflow.org/data/creditcard.csv')
raw_df.head()

Time V1 V2 V3 V4 V5 V6 V7 V8 V9 V21 V22 V23 V24 V25 V26 V27 V28 Amount Class
0 0.0 -1.359807 -0.072781 2.536347 1.378155 -0.338321 0.462388 0.239599 0.098698 0.363787 -0.018307 0.277838 -0.110474 0.066928 0.128539 -0.189115 0.133558 -0.021053 149.62 0
1 0.0 1.191857 0.266151 0.166480 0.448154 0.060018 -0.082361 -0.078803 0.085102 -0.255425 -0.225775 -0.638672 0.101288 -0.339846 0.167170 0.125895 -0.008983 0.014724 2.69 0
2 1.0 -1.358354 -1.340163 1.773209 0.379780 -0.503198 1.800499 0.791461 0.247676 -1.514654 0.247998 0.771679 0.909412 -0.689281 -0.327642 -0.139097 -0.055353 -0.059752 378.66 0
3 1.0 -0.966272 -0.185226 1.792993 -0.863291 -0.010309 1.247203 0.237609 0.377436 -1.387024 -0.108300 0.005274 -0.190321 -1.175575 0.647376 -0.221929 0.062723 0.061458 123.50 0
4 2.0 -1.158233 0.877737 1.548718 0.403034 -0.407193 0.095921 0.592941 -0.270533 0.817739 -0.009431 0.798278 -0.137458 0.141267 -0.206010 0.502292 0.219422 0.215153 69.99 0

5 rows × 31 columns
raw_df[['Time', 'V1', 'V2', 'V3', 'V4', 'V5', 'V26', 'V27', 'V28', 'Amount', 'Class']].describe()

Time V1 V2 V3 V4 V5 V26 V27 V28 Amount クラス
カウント 284807.000000 2.848070e+05 2.848070e+05 2.848070e+05 2.848070e+05 2.848070e+05 2.848070e+05 2.848070e+05 2.848070e+05 284807.000000 284807.000000
平均 94813.859575 1.165980e-15 3.416908e-16 -1.373150e-15 2.086869e-15 9.604066e-16 1.687098e-15 -3.666453e-16 -1.220404e-16 88.349619 0.001727
標準偏差 47488.145955 1.958696e+00 1.651309e+00 1.516255e+00 1.415869e+00 1.380247e+00 4.822270e-01 4.036325e-01 3.300833e-01 250.120109 0.041527
最小値 0.000000 -5.640751e+01 -7.271573e+01 -4.832559e+01 -5.683171e+00 -1.137433e+02 -2.604551e+00 -2.256568e+01 -1.543008e+01 0.000000 0.000000
25% 54201.500000 -9.203734e-01 -5.985499e-01 -8.903648e-01 -8.486401e-01 -6.915971e-01 -3.269839e-01 -7.083953e-02 -5.295979e-02 5.600000 0.000000
50% 84692.000000 1.810880e-02 6.548556e-02 1.798463e-01 -1.984653e-02 -5.433583e-02 -5.213911e-02 1.342146e-03 1.124383e-02 22.000000 0.000000
75% 139320.500000 1.315642e+00 8.037239e-01 1.027196e+00 7.433413e-01 6.119264e-01 2.409522e-01 9.104512e-02 7.827995e-02 77.165000 0.000000
最大値 172792.000000 2.454930e+00 2.205773e+01 9.382558e+00 1.687534e+01 3.480167e+01 3.517346e+00 3.161220e+01 3.384781e+01 25691.160000 1.000000

 

クラスラベルが不均衡であることを調べる

不均衡なデータセットを見てみましょう :

neg, pos = np.bincount(raw_df['Class'])
total = neg + pos
print('Examples:\n    Total: {}\n    Positive: {} ({:.2f}% of total)\n'.format(
    total, pos, 100 * pos / total))
Examples:
    Total: 284807
    Positive: 492 (0.17% of total)

これはポジティブなサンプルの小さい割合を示します。

 

データをクリーンアップ、分割して正規化する

生データは 2, 3 の問題を持ちます。最初に Time と Amount カラムは直接使用するには変化し過ぎです。Time カラムは破棄して (何故ならばそれが何を意味するか明確でないからです) そして範囲を減じるために Amount カラムの対数を取ります。

cleaned_df = raw_df.copy()

# You don't want the `Time` column.
cleaned_df.pop('Time')

# The `Amount` column covers a huge range. Convert to log-space.
eps=0.001 # 0 => 0.1¢
cleaned_df['Log Ammount'] = np.log(cleaned_df.pop('Amount')+eps)

データセットを訓練、検証とテストセットに分割します。検証セットは損失と任意のメトリクスを評価するためにモデル fitting の間に使用されます、けれどもモデルはこのデータでは fit されません。テストセットは訓練段階の間には全く使用されません、そしてモデルが新しいデータにどのくらい上手く一般化されたかを評価するために最後に使用されるだけです。これは不均衡なデータセットでは特に重要です、そこでは overfitting は訓練データの欠落から本質的な関心事です。

# Use a utility from sklearn to split and shuffle our dataset.
train_df, test_df = train_test_split(cleaned_df, test_size=0.2)
train_df, val_df = train_test_split(train_df, test_size=0.2)

# Form np arrays of labels and features.
train_labels = np.array(train_df.pop('Class'))
bool_train_labels = train_labels != 0
val_labels = np.array(val_df.pop('Class'))
test_labels = np.array(test_df.pop('Class'))

train_features = np.array(train_df)
val_features = np.array(val_df)
test_features = np.array(test_df)

sklearn StandardScaler を使用して入力特徴を正規化します。これは平均を 0 そして標準偏差を 1 に設定します。

Note: StandardScaler はモデルが検証やテストセットを覗き見ないことを確実にするために train_features を使用して fit するだけです。

scaler = StandardScaler()
train_features = scaler.fit_transform(train_features)

val_features = scaler.transform(val_features)
test_features = scaler.transform(test_features)

train_features = np.clip(train_features, -5, 5)
val_features = np.clip(val_features, -5, 5)
test_features = np.clip(test_features, -5, 5)


print('Training labels shape:', train_labels.shape)
print('Validation labels shape:', val_labels.shape)
print('Test labels shape:', test_labels.shape)

print('Training features shape:', train_features.shape)
print('Validation features shape:', val_features.shape)
print('Test features shape:', test_features.shape)
Training labels shape: (182276,)
Validation labels shape: (45569,)
Test labels shape: (56962,)
Training features shape: (182276, 29)
Validation features shape: (45569, 29)
Test features shape: (56962, 29)
警告: モデルを配備することを望む場合、前処理の計算を保持することは重要です。それらを実装するための最も容易な方法は層として、そしてそれらを貴方のモデルにエクスポートする前にアタッチします。

 

データ分布を見る

次に 2,3 の特徴に渡りポジティブとネガティブ・サンプルの分布を比較します。この時点で貴方自身に尋ねる良い質問は :

  • これらの分布は意味があるでしょうか?
    • はい。入力を正規化してそしてこれらは +/- 2 範囲に殆ど集中しています。
  • 分布の間に違いを見ることができるでしょうか?
    • はい、ポジティブ・サンプルは遥かに高いレートの極値 (= extreme values) を含みます。
pos_df = pd.DataFrame(train_features[ bool_train_labels], columns = train_df.columns)
neg_df = pd.DataFrame(train_features[~bool_train_labels], columns = train_df.columns)

sns.jointplot(pos_df['V5'], pos_df['V6'],
              kind='hex', xlim = (-5,5), ylim = (-5,5))
plt.suptitle("Positive distribution")

sns.jointplot(neg_df['V5'], neg_df['V6'],
              kind='hex', xlim = (-5,5), ylim = (-5,5))
_ = plt.suptitle("Negative distribution")

 

モデルとメトリクスを定義する

密に接続された隠れ層、overfitting を減じる dropout 層、そしてトランザクションが不正である確率を返す出力 sigmoid 層を持つ単純なネットワークを作成する関数を定義します :

METRICS = [
      keras.metrics.TruePositives(name='tp'),
      keras.metrics.FalsePositives(name='fp'),
      keras.metrics.TrueNegatives(name='tn'),
      keras.metrics.FalseNegatives(name='fn'), 
      keras.metrics.BinaryAccuracy(name='accuracy'),
      keras.metrics.Precision(name='precision'),
      keras.metrics.Recall(name='recall'),
      keras.metrics.AUC(name='auc'),
]

def make_model(metrics = METRICS, output_bias=None):
  if output_bias is not None:
    output_bias = tf.keras.initializers.Constant(output_bias)
  model = keras.Sequential([
      keras.layers.Dense(
          16, activation='relu',
          input_shape=(train_features.shape[-1],)),
      keras.layers.Dropout(0.5),
      keras.layers.Dense(1, activation='sigmoid',
                         bias_initializer=output_bias),
  ])

  model.compile(
      optimizer=keras.optimizers.Adam(lr=1e-3),
      loss=keras.losses.BinaryCrossentropy(),
      metrics=metrics)

  return model

 

有用なメトリクスを理解する

パフォーマンスを評価するときに有用である、モデルにより計算可能な上で定義された 2, 3 のメトリクスがあることに注意してください。

  • false negativefalse positive間違って 分類されたサンプルです
  • true negativetrue positive正しく 分類されたサンプルです
  • accuracy は正しく分類されたサンプルのパーセンテージです > $\frac{\text{true samples}}{\text{total samples}}$
  • precision は正しく分類された 予測された ポジティブのパーセンテージです > $\frac{\text{true positives}}{\text{true positives + false positives}}$
  • recall は正しく分類された 実際の ポジティブのパーセンテージです > $\frac{\text{true positives}}{\text{true positives + false negatives}}$
  • AUC は ROC曲線下の面積 (ROC-AUC, Area Under the Curve of a Receiver Operating Characteristic curve) を参照します。このメトリックは、分類器がランダムなポジティブ・サンプルをランダムなネガティブ・サンプルよりも高く位置付ける確率に等しいです。

Note: accuracy (精度) はこのタスクのために有用なメトリクスではありません。常に False を予測することによりこのタスク上 99.8%+ 精度を得られるでしょう。

Read more: * True vs. False と Positive vs. Negative * Accuracy * Precision と Recall * ROC-AUC

 

ベースライン・モデル

モデルを構築する

今は先に定義された関数を使用してモデルを作成して訓練します。モデルは 2048 のデフォルトよりも大きいバッチサイズを使用して fit することに注意してください、これは各バッチが 2, 3 のポジティブ・サンプルを含む妥当な機会を持つことを確実にするために重要です。バッチサイズが小さ過ぎれば、そこから学習するための詐欺的なトランザクションを持たない傾向になるでしょう。

Note: このモデルは不均衡なクラスを上手く扱いません。このチュートリアルの後でそれを改良します。

EPOCHS = 100
BATCH_SIZE = 2048

early_stopping = tf.keras.callbacks.EarlyStopping(
    monitor='val_auc', 
    verbose=1,
    patience=10,
    mode='max',
    restore_best_weights=True)
model = make_model()
model.summary()
Model: "sequential"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
dense (Dense)                (None, 16)                480       
_________________________________________________________________
dropout (Dropout)            (None, 16)                0         
_________________________________________________________________
dense_1 (Dense)              (None, 1)                 17        
=================================================================
Total params: 497
Trainable params: 497
Non-trainable params: 0

Test run the model:

model.predict(train_features[:10])
array([[0.35366294],
       [0.17252561],
       [0.08079773],
       [0.13621533],
       [0.18569201],
       [0.22863552],
       [0.3243227 ],
       [0.25937212],
       [0.27047133],
       [0.1558134 ]], dtype=float32)

 

オプション: 正しい初期バイアスを設定する

これらは初期推論は良くはありません。貴方はデータセットは不均衡であることを知っています。それを反映するように出力層のバイアスを設定します (参照: A Recipe for Training Neural Networks: “init well”)。これは初期収束に役立つことができます。

デフォルトのバイアス初期化では損失はおよそ math.log(2) = 0.69314 になるはずです

results = model.evaluate(train_features, train_labels, batch_size=BATCH_SIZE, verbose=0)
print("Loss: {:0.4f}".format(results[0]))
Loss: 0.2624

設定する正しいバイアスは以下から導出できます :

$$ p_0 = pos/(pos + neg) = 1/(1+e^{-b_0}) $$
$$ b_0 = -log_e(1/p_0 – 1) $$
$$ b_0 = log_e(pos/neg)$$
initial_bias = np.log([pos/neg])
initial_bias
array([-6.35935934])

それを初期バイアスとして設定すると、モデルは遥かに合理的な初期推論を与えます。

それは pos/total = 0.0018 近くになるはずです

model = make_model(output_bias = initial_bias)
model.predict(train_features[:10])
array([[0.00416172],
       [0.0006738 ],
       [0.00036496],
       [0.00125635],
       [0.00200993],
       [0.0012227 ],
       [0.01357868],
       [0.00356525],
       [0.00580611],
       [0.02072738]], dtype=float32)

この初期化で初期損失はおよそ次になるはずです :

$$-p_0log(p_0)-(1-p_0)log(1-p_0) = 0.01317$$
results = model.evaluate(train_features, train_labels, batch_size=BATCH_SIZE, verbose=0)
print("Loss: {:0.4f}".format(results[0]))
Loss: 0.0164

この初期損失は素朴な初期化であるよりもおよそ 50 倍少ないです。

このようにしてモデルはポジティブサンプルが可能性が低いことを単に学習する、最初の数エポックを消費する必要がありません。これはまた訓練の間に損失のプロットを読むことを容易にします。

 

初期重みをチェックポイントする

様々な訓練をより比較可能に実行するために、この初期モデルの重みをチェックポイント・ファイルに保持します、そしてそれらを訓練前に各モデルにロードします。

initial_weights = os.path.join(tempfile.mkdtemp(),'initial_weights')
model.save_weights(initial_weights)

 

バイアス修正 (= fix) が役立つことをを確認する

進む前に、注意深いバイアス初期化が実際に役立つことを素早く確認します。

この注意深い初期化ありとなしで、20 エポックの間モデルを訓練します、そして損失を比較します :

model = make_model()
model.load_weights(initial_weights)
model.layers[-1].bias.assign([0.0])
zero_bias_history = model.fit(
    train_features,
    train_labels,
    batch_size=BATCH_SIZE,
    epochs=20,
    validation_data=(val_features, val_labels), 
    verbose=0)
model = make_model()
model.load_weights(initial_weights)
careful_bias_history = model.fit(
    train_features,
    train_labels,
    batch_size=BATCH_SIZE,
    epochs=20,
    validation_data=(val_features, val_labels), 
    verbose=0)
def plot_loss(history, label, n):
  # Use a log scale to show the wide range of values.
  plt.semilogy(history.epoch,  history.history['loss'],
               color=colors[n], label='Train '+label)
  plt.semilogy(history.epoch,  history.history['val_loss'],
          color=colors[n], label='Val '+label,
          linestyle="--")
  plt.xlabel('Epoch')
  plt.ylabel('Loss')
  
  plt.legend()
plot_loss(zero_bias_history, "Zero Bias", 0)
plot_loss(careful_bias_history, "Careful Bias", 1)

上の図は次を明白にします: この問題上、検証損失の観点から、この注意深い初期化は明白な優位を与えます。

 

モデルを訓練する

model = make_model()
model.load_weights(initial_weights)
baseline_history = model.fit(
    train_features,
    train_labels,
    batch_size=BATCH_SIZE,
    epochs=EPOCHS,
    callbacks = [early_stopping],
    validation_data=(val_features, val_labels))
Train on 182276 samples, validate on 45569 samples
Epoch 1/100
182276/182276 [==============================] - 3s 15us/sample - loss: 0.0148 - tp: 33.0000 - fp: 107.0000 - tn: 181864.0000 - fn: 272.0000 - accuracy: 0.9979 - precision: 0.2357 - recall: 0.1082 - auc: 0.6736 - val_loss: 0.0072 - val_tp: 3.0000 - val_fp: 2.0000 - val_tn: 45485.0000 - val_fn: 79.0000 - val_accuracy: 0.9982 - val_precision: 0.6000 - val_recall: 0.0366 - val_auc: 0.9058
Epoch 2/100
182276/182276 [==============================] - 1s 3us/sample - loss: 0.0087 - tp: 94.0000 - fp: 34.0000 - tn: 181937.0000 - fn: 211.0000 - accuracy: 0.9987 - precision: 0.7344 - recall: 0.3082 - auc: 0.8194 - val_loss: 0.0049 - val_tp: 40.0000 - val_fp: 7.0000 - val_tn: 45480.0000 - val_fn: 42.0000 - val_accuracy: 0.9989 - val_precision: 0.8511 - val_recall: 0.4878 - val_auc: 0.9264
Epoch 3/100
182276/182276 [==============================] - 1s 3us/sample - loss: 0.0075 - tp: 122.0000 - fp: 33.0000 - tn: 181938.0000 - fn: 183.0000 - accuracy: 0.9988 - precision: 0.7871 - recall: 0.4000 - auc: 0.8638 - val_loss: 0.0043 - val_tp: 47.0000 - val_fp: 7.0000 - val_tn: 45480.0000 - val_fn: 35.0000 - val_accuracy: 0.9991 - val_precision: 0.8704 - val_recall: 0.5732 - val_auc: 0.9266
Epoch 4/100
182276/182276 [==============================] - 1s 3us/sample - loss: 0.0065 - tp: 131.0000 - fp: 32.0000 - tn: 181939.0000 - fn: 174.0000 - accuracy: 0.9989 - precision: 0.8037 - recall: 0.4295 - auc: 0.8855 - val_loss: 0.0040 - val_tp: 57.0000 - val_fp: 7.0000 - val_tn: 45480.0000 - val_fn: 25.0000 - val_accuracy: 0.9993 - val_precision: 0.8906 - val_recall: 0.6951 - val_auc: 0.9327
Epoch 5/100
182276/182276 [==============================] - 1s 3us/sample - loss: 0.0055 - tp: 172.0000 - fp: 28.0000 - tn: 181943.0000 - fn: 133.0000 - accuracy: 0.9991 - precision: 0.8600 - recall: 0.5639 - auc: 0.9170 - val_loss: 0.0038 - val_tp: 59.0000 - val_fp: 7.0000 - val_tn: 45480.0000 - val_fn: 23.0000 - val_accuracy: 0.9993 - val_precision: 0.8939 - val_recall: 0.7195 - val_auc: 0.9326
Epoch 6/100
182276/182276 [==============================] - 1s 3us/sample - loss: 0.0056 - tp: 158.0000 - fp: 32.0000 - tn: 181939.0000 - fn: 147.0000 - accuracy: 0.9990 - precision: 0.8316 - recall: 0.5180 - auc: 0.9008 - val_loss: 0.0036 - val_tp: 59.0000 - val_fp: 7.0000 - val_tn: 45480.0000 - val_fn: 23.0000 - val_accuracy: 0.9993 - val_precision: 0.8939 - val_recall: 0.7195 - val_auc: 0.9326
Epoch 7/100
182276/182276 [==============================] - 1s 3us/sample - loss: 0.0055 - tp: 157.0000 - fp: 31.0000 - tn: 181940.0000 - fn: 148.0000 - accuracy: 0.9990 - precision: 0.8351 - recall: 0.5148 - auc: 0.9112 - val_loss: 0.0035 - val_tp: 62.0000 - val_fp: 7.0000 - val_tn: 45480.0000 - val_fn: 20.0000 - val_accuracy: 0.9994 - val_precision: 0.8986 - val_recall: 0.7561 - val_auc: 0.9326
Epoch 8/100
182276/182276 [==============================] - 1s 3us/sample - loss: 0.0048 - tp: 172.0000 - fp: 33.0000 - tn: 181938.0000 - fn: 133.0000 - accuracy: 0.9991 - precision: 0.8390 - recall: 0.5639 - auc: 0.9130 - val_loss: 0.0034 - val_tp: 62.0000 - val_fp: 7.0000 - val_tn: 45480.0000 - val_fn: 20.0000 - val_accuracy: 0.9994 - val_precision: 0.8986 - val_recall: 0.7561 - val_auc: 0.9326
Epoch 9/100
182276/182276 [==============================] - 1s 3us/sample - loss: 0.0051 - tp: 159.0000 - fp: 28.0000 - tn: 181943.0000 - fn: 146.0000 - accuracy: 0.9990 - precision: 0.8503 - recall: 0.5213 - auc: 0.9081 - val_loss: 0.0033 - val_tp: 62.0000 - val_fp: 7.0000 - val_tn: 45480.0000 - val_fn: 20.0000 - val_accuracy: 0.9994 - val_precision: 0.8986 - val_recall: 0.7561 - val_auc: 0.9326
Epoch 10/100
182276/182276 [==============================] - 1s 3us/sample - loss: 0.0050 - tp: 169.0000 - fp: 33.0000 - tn: 181938.0000 - fn: 136.0000 - accuracy: 0.9991 - precision: 0.8366 - recall: 0.5541 - auc: 0.9215 - val_loss: 0.0033 - val_tp: 62.0000 - val_fp: 7.0000 - val_tn: 45480.0000 - val_fn: 20.0000 - val_accuracy: 0.9994 - val_precision: 0.8986 - val_recall: 0.7561 - val_auc: 0.9326
Epoch 11/100
182276/182276 [==============================] - 1s 3us/sample - loss: 0.0051 - tp: 157.0000 - fp: 29.0000 - tn: 181942.0000 - fn: 148.0000 - accuracy: 0.9990 - precision: 0.8441 - recall: 0.5148 - auc: 0.9233 - val_loss: 0.0032 - val_tp: 62.0000 - val_fp: 7.0000 - val_tn: 45480.0000 - val_fn: 20.0000 - val_accuracy: 0.9994 - val_precision: 0.8986 - val_recall: 0.7561 - val_auc: 0.9326
Epoch 12/100
182276/182276 [==============================] - 1s 3us/sample - loss: 0.0045 - tp: 178.0000 - fp: 37.0000 - tn: 181934.0000 - fn: 127.0000 - accuracy: 0.9991 - precision: 0.8279 - recall: 0.5836 - auc: 0.9317 - val_loss: 0.0031 - val_tp: 62.0000 - val_fp: 7.0000 - val_tn: 45480.0000 - val_fn: 20.0000 - val_accuracy: 0.9994 - val_precision: 0.8986 - val_recall: 0.7561 - val_auc: 0.9326
Epoch 13/100
182276/182276 [==============================] - 1s 3us/sample - loss: 0.0047 - tp: 160.0000 - fp: 32.0000 - tn: 181939.0000 - fn: 145.0000 - accuracy: 0.9990 - precision: 0.8333 - recall: 0.5246 - auc: 0.9185 - val_loss: 0.0031 - val_tp: 63.0000 - val_fp: 8.0000 - val_tn: 45479.0000 - val_fn: 19.0000 - val_accuracy: 0.9994 - val_precision: 0.8873 - val_recall: 0.7683 - val_auc: 0.9326
Epoch 14/100
161792/182276 [=========================>....] - ETA: 0s - loss: 0.0045 - tp: 155.0000 - fp: 31.0000 - tn: 161488.0000 - fn: 118.0000 - accuracy: 0.9991 - precision: 0.8333 - recall: 0.5678 - auc: 0.9256Restoring model weights from the end of the best epoch.
182276/182276 [==============================] - 1s 3us/sample - loss: 0.0044 - tp: 175.0000 - fp: 33.0000 - tn: 181938.0000 - fn: 130.0000 - accuracy: 0.9991 - precision: 0.8413 - recall: 0.5738 - auc: 0.9251 - val_loss: 0.0031 - val_tp: 64.0000 - val_fp: 8.0000 - val_tn: 45479.0000 - val_fn: 18.0000 - val_accuracy: 0.9994 - val_precision: 0.8889 - val_recall: 0.7805 - val_auc: 0.9326
Epoch 00014: early stopping

 

訓練履歴を確認する

このセクションでは、モデルの精度と訓練と検証セット上の損失のプロットを生成します。これは overfitting を確認するために有用です、それについてはこの チュートリアル で更に学習できます。

更に、上で作成した任意のメトリクスのためにこれらのプロットを生成できます。false negative がサンプルとして含まれます。

def plot_metrics(history):
  metrics =  ['loss', 'auc', 'precision', 'recall']
  for n, metric in enumerate(metrics):
    name = metric.replace("_"," ").capitalize()
    plt.subplot(2,2,n+1)
    plt.plot(history.epoch,  history.history[metric], color=colors[0], label='Train')
    plt.plot(history.epoch, history.history['val_'+metric],
             color=colors[0], linestyle="--", label='Val')
    plt.xlabel('Epoch')
    plt.ylabel(name)
    if metric == 'loss':
      plt.ylim([0, plt.ylim()[1]])
    elif metric == 'auc':
      plt.ylim([0.8,1])
    else:
      plt.ylim([0,1])

    plt.legend()
plot_metrics(baseline_history)

Note: 検証カーブは訓練カーブよりも一般により良く遂行します。これは主として、モデルを評価するとき dropout 層が有効ではないという事実によります。

 

メトリクスを評価する

実際 (= actual) vs 予測されたラベルを要約するために 混同行列 を使用できます、そこでは X 軸は予測されたラベルで Y 軸は実際のラベルです。

train_predictions_baseline = model.predict(train_features, batch_size=BATCH_SIZE)
test_predictions_baseline = model.predict(test_features, batch_size=BATCH_SIZE)
def plot_cm(labels, predictions, p=0.5):
  cm = confusion_matrix(labels, predictions > p)
  plt.figure(figsize=(5,5))
  sns.heatmap(cm, annot=True, fmt="d")
  plt.title('Confusion matrix @{:.2f}'.format(p))
  plt.ylabel('Actual label')
  plt.xlabel('Predicted label')

  print('Legitimate Transactions Detected (True Negatives): ', cm[0][0])
  print('Legitimate Transactions Incorrectly Detected (False Positives): ', cm[0][1])
  print('Fraudulent Transactions Missed (False Negatives): ', cm[1][0])
  print('Fraudulent Transactions Detected (True Positives): ', cm[1][1])
  print('Total Fraudulent Transactions: ', np.sum(cm[1]))

テストデータセット上のモデルを評価して上で作成したメトリクスのための結果を表示します。

baseline_results = model.evaluate(test_features, test_labels,
                                  batch_size=BATCH_SIZE, verbose=0)
for name, value in zip(model.metrics_names, baseline_results):
  print(name, ': ', value)
print()

plot_cm(test_labels, test_predictions_baseline)
loss :  0.004311129764585171
tp :  68.0
fp :  11.0
tn :  56846.0
fn :  37.0
accuracy :  0.9991573
precision :  0.8607595
recall :  0.64761907
auc :  0.92348534

Legitimate Transactions Detected (True Negatives):  56846
Legitimate Transactions Incorrectly Detected (False Positives):  11
Fraudulent Transactions Missed (False Negatives):  37
Fraudulent Transactions Detected (True Positives):  68
Total Fraudulent Transactions:  105

モデルが総てを完全に予測したのであれば、これは 対角行列 になるでしょう、そこでは (正しくない予測を示す) 主対角線から離れた値はゼロになるでしょう。この場合行列は比較的少ない false positive を持つことを示します、これは誤ってフラグを建てられた比較的少ない正当なトランザクションがあったことを意味します。けれども、false positive の数が増加しているコストにもかかわらず、貴方はより少ない false negative でさえも望みがちでしょう。このトレードオフは好ましいかもしれません、何故ならば false positive はカスタマーにカードの動きを検証することを要求するための電子メールが送られることを引き起こすかもしれない一方で、false negative は詐欺的なトランザクションが通り抜けることを可能にするからです。

 

ROC をプロットする

今は ROC をプロットします。このプロットは有用です、何故ならばそれはひと目で、単に出力閾値を調整することによりモデルが到達できるパフォーマンスの範囲を示すからです。

def plot_roc(name, labels, predictions, **kwargs):
  fp, tp, _ = sklearn.metrics.roc_curve(labels, predictions)

  plt.plot(100*fp, 100*tp, label=name, linewidth=2, **kwargs)
  plt.xlabel('False positives [%]')
  plt.ylabel('True positives [%]')
  plt.xlim([-0.5,20])
  plt.ylim([80,100.5])
  plt.grid(True)
  ax = plt.gca()
  ax.set_aspect('equal')
plot_roc("Train Baseline", train_labels, train_predictions_baseline, color=colors[0])
plot_roc("Test Baseline", test_labels, test_predictions_baseline, color=colors[0], linestyle='--')
plt.legend(loc='lower right')
<matplotlib.legend.Legend at 0x7fc6ba4dcbe0>

それは precision は比較的高いように見えますが、recall と ROC 曲線下の面積 (AUC) は貴方が好むほどには高くありません。分類器は precision と recall の両者を最大化しようとするときしばしば課題に直面します、これは不均衡なデータセットで作業するときに特に真です。関心がある問題のコンテキストで異なるタイプのエラーのコストを考えることは重要です。この例では、false negative (詐欺的なトランザクションが取り逃がされる) がファイナンシャルコストを持つかもしれない一方で、false positive (トランザクションは誤って詐欺的とフラグが立てられる) はユーザの幸せを減らすかもしれません。

 

クラス重み

クラス重みを計算する

ゴールは詐欺的トランザクションを識別することですが、作業するためのそれらの非常に多くの positive サンプルを持ちませんので、分類器に利用可能な少ないサンプルに大きく重み付けすることを望むでしょう。Keras にパラメータを通して各クラスのための重みを渡すことによりこれを行なうことができます。これらはモデルに under-represented クラスからのサンプルに「より大きな注意を払う」ことをさせます。

# Scaling by total/2 helps keep the loss to a similar magnitude.
# The sum of the weights of all examples stays the same.
weight_for_0 = (1 / neg)*(total)/2.0 
weight_for_1 = (1 / pos)*(total)/2.0

class_weight = {0: weight_for_0, 1: weight_for_1}

print('Weight for class 0: {:.2f}'.format(weight_for_0))
print('Weight for class 1: {:.2f}'.format(weight_for_1))
Weight for class 0: 0.50
Weight for class 1: 289.44

 

クラス重みでモデルを訓練する

今は予測にどのように影響するかを見るためにクラス重みでモデルを再訓練して評価してみます。

Note: class_weights の使用は損失の範囲を変更します。これは optimizer に依拠して訓練の安定性に影響するかもしれません。optimizers.SGD のような、ステップサイズが勾配の大きさに依拠する optimizer は失敗するかもしれません。ここで使用される optimizer、optimizers.Adam はスケーリング変更の影響を受けません。また重み付けゆえに、トータル損失は 2 つのモデル間で比較可能ではないことに注意してください。

weighted_model = make_model()
weighted_model.load_weights(initial_weights)

weighted_history = weighted_model.fit(
    train_features,
    train_labels,
    batch_size=BATCH_SIZE,
    epochs=EPOCHS,
    callbacks = [early_stopping],
    validation_data=(val_features, val_labels),
    # The class weights go here
    class_weight=class_weight) 
Train on 182276 samples, validate on 45569 samples
Epoch 1/100
182276/182276 [==============================] - 3s 17us/sample - loss: 2.3320 - tp: 49.0000 - fp: 550.0000 - tn: 181421.0000 - fn: 256.0000 - accuracy: 0.9956 - precision: 0.0818 - recall: 0.1607 - auc: 0.6815 - val_loss: 0.7987 - val_tp: 40.0000 - val_fp: 14.0000 - val_tn: 45473.0000 - val_fn: 42.0000 - val_accuracy: 0.9988 - val_precision: 0.7407 - val_recall: 0.4878 - val_auc: 0.9440
Epoch 2/100
182276/182276 [==============================] - 1s 3us/sample - loss: 1.0274 - tp: 151.0000 - fp: 1122.0000 - tn: 180849.0000 - fn: 154.0000 - accuracy: 0.9930 - precision: 0.1186 - recall: 0.4951 - auc: 0.8471 - val_loss: 0.4155 - val_tp: 65.0000 - val_fp: 28.0000 - val_tn: 45459.0000 - val_fn: 17.0000 - val_accuracy: 0.9990 - val_precision: 0.6989 - val_recall: 0.7927 - val_auc: 0.9664
Epoch 3/100
182276/182276 [==============================] - 1s 3us/sample - loss: 0.6664 - tp: 196.0000 - fp: 1788.0000 - tn: 180183.0000 - fn: 109.0000 - accuracy: 0.9896 - precision: 0.0988 - recall: 0.6426 - auc: 0.8987 - val_loss: 0.3144 - val_tp: 69.0000 - val_fp: 58.0000 - val_tn: 45429.0000 - val_fn: 13.0000 - val_accuracy: 0.9984 - val_precision: 0.5433 - val_recall: 0.8415 - val_auc: 0.9748
Epoch 4/100
182276/182276 [==============================] - 1s 3us/sample - loss: 0.4983 - tp: 221.0000 - fp: 2856.0000 - tn: 179115.0000 - fn: 84.0000 - accuracy: 0.9839 - precision: 0.0718 - recall: 0.7246 - auc: 0.9235 - val_loss: 0.2678 - val_tp: 70.0000 - val_fp: 109.0000 - val_tn: 45378.0000 - val_fn: 12.0000 - val_accuracy: 0.9973 - val_precision: 0.3911 - val_recall: 0.8537 - val_auc: 0.9746
Epoch 5/100
182276/182276 [==============================] - 1s 3us/sample - loss: 0.4914 - tp: 228.0000 - fp: 3722.0000 - tn: 178249.0000 - fn: 77.0000 - accuracy: 0.9792 - precision: 0.0577 - recall: 0.7475 - auc: 0.9132 - val_loss: 0.2342 - val_tp: 71.0000 - val_fp: 215.0000 - val_tn: 45272.0000 - val_fn: 11.0000 - val_accuracy: 0.9950 - val_precision: 0.2483 - val_recall: 0.8659 - val_auc: 0.9767
Epoch 6/100
182276/182276 [==============================] - 1s 3us/sample - loss: 0.4245 - tp: 240.0000 - fp: 4810.0000 - tn: 177161.0000 - fn: 65.0000 - accuracy: 0.9733 - precision: 0.0475 - recall: 0.7869 - auc: 0.9247 - val_loss: 0.2168 - val_tp: 71.0000 - val_fp: 414.0000 - val_tn: 45073.0000 - val_fn: 11.0000 - val_accuracy: 0.9907 - val_precision: 0.1464 - val_recall: 0.8659 - val_auc: 0.9772
Epoch 7/100
182276/182276 [==============================] - 1s 3us/sample - loss: 0.3187 - tp: 256.0000 - fp: 5810.0000 - tn: 176161.0000 - fn: 49.0000 - accuracy: 0.9679 - precision: 0.0422 - recall: 0.8393 - auc: 0.9448 - val_loss: 0.2047 - val_tp: 71.0000 - val_fp: 535.0000 - val_tn: 44952.0000 - val_fn: 11.0000 - val_accuracy: 0.9880 - val_precision: 0.1172 - val_recall: 0.8659 - val_auc: 0.9772
Epoch 8/100
182276/182276 [==============================] - 1s 3us/sample - loss: 0.3666 - tp: 256.0000 - fp: 6358.0000 - tn: 175613.0000 - fn: 49.0000 - accuracy: 0.9649 - precision: 0.0387 - recall: 0.8393 - auc: 0.9320 - val_loss: 0.2008 - val_tp: 71.0000 - val_fp: 595.0000 - val_tn: 44892.0000 - val_fn: 11.0000 - val_accuracy: 0.9867 - val_precision: 0.1066 - val_recall: 0.8659 - val_auc: 0.9779
Epoch 9/100
182276/182276 [==============================] - 1s 3us/sample - loss: 0.3080 - tp: 254.0000 - fp: 6611.0000 - tn: 175360.0000 - fn: 51.0000 - accuracy: 0.9635 - precision: 0.0370 - recall: 0.8328 - auc: 0.9521 - val_loss: 0.1941 - val_tp: 71.0000 - val_fp: 685.0000 - val_tn: 44802.0000 - val_fn: 11.0000 - val_accuracy: 0.9847 - val_precision: 0.0939 - val_recall: 0.8659 - val_auc: 0.9781
Epoch 10/100
182276/182276 [==============================] - 1s 3us/sample - loss: 0.2565 - tp: 263.0000 - fp: 7082.0000 - tn: 174889.0000 - fn: 42.0000 - accuracy: 0.9609 - precision: 0.0358 - recall: 0.8623 - auc: 0.9594 - val_loss: 0.1907 - val_tp: 73.0000 - val_fp: 746.0000 - val_tn: 44741.0000 - val_fn: 9.0000 - val_accuracy: 0.9834 - val_precision: 0.0891 - val_recall: 0.8902 - val_auc: 0.9790
Epoch 11/100
182276/182276 [==============================] - 1s 3us/sample - loss: 0.2785 - tp: 263.0000 - fp: 7027.0000 - tn: 174944.0000 - fn: 42.0000 - accuracy: 0.9612 - precision: 0.0361 - recall: 0.8623 - auc: 0.9541 - val_loss: 0.1873 - val_tp: 73.0000 - val_fp: 790.0000 - val_tn: 44697.0000 - val_fn: 9.0000 - val_accuracy: 0.9825 - val_precision: 0.0846 - val_recall: 0.8902 - val_auc: 0.9790
Epoch 12/100
182276/182276 [==============================] - 1s 3us/sample - loss: 0.3415 - tp: 256.0000 - fp: 7570.0000 - tn: 174401.0000 - fn: 49.0000 - accuracy: 0.9582 - precision: 0.0327 - recall: 0.8393 - auc: 0.9374 - val_loss: 0.1861 - val_tp: 73.0000 - val_fp: 863.0000 - val_tn: 44624.0000 - val_fn: 9.0000 - val_accuracy: 0.9809 - val_precision: 0.0780 - val_recall: 0.8902 - val_auc: 0.9792
Epoch 13/100
182276/182276 [==============================] - 1s 3us/sample - loss: 0.2324 - tp: 272.0000 - fp: 7138.0000 - tn: 174833.0000 - fn: 33.0000 - accuracy: 0.9607 - precision: 0.0367 - recall: 0.8918 - auc: 0.9638 - val_loss: 0.1861 - val_tp: 73.0000 - val_fp: 821.0000 - val_tn: 44666.0000 - val_fn: 9.0000 - val_accuracy: 0.9818 - val_precision: 0.0817 - val_recall: 0.8902 - val_auc: 0.9800
Epoch 14/100
182276/182276 [==============================] - 1s 3us/sample - loss: 0.2341 - tp: 271.0000 - fp: 7125.0000 - tn: 174846.0000 - fn: 34.0000 - accuracy: 0.9607 - precision: 0.0366 - recall: 0.8885 - auc: 0.9662 - val_loss: 0.1851 - val_tp: 74.0000 - val_fp: 830.0000 - val_tn: 44657.0000 - val_fn: 8.0000 - val_accuracy: 0.9816 - val_precision: 0.0819 - val_recall: 0.9024 - val_auc: 0.9800
Epoch 15/100
182276/182276 [==============================] - 1s 3us/sample - loss: 0.2603 - tp: 269.0000 - fp: 6786.0000 - tn: 175185.0000 - fn: 36.0000 - accuracy: 0.9626 - precision: 0.0381 - recall: 0.8820 - auc: 0.9581 - val_loss: 0.1843 - val_tp: 74.0000 - val_fp: 824.0000 - val_tn: 44663.0000 - val_fn: 8.0000 - val_accuracy: 0.9817 - val_precision: 0.0824 - val_recall: 0.9024 - val_auc: 0.9806
Epoch 16/100
182276/182276 [==============================] - 1s 3us/sample - loss: 0.2448 - tp: 267.0000 - fp: 6749.0000 - tn: 175222.0000 - fn: 38.0000 - accuracy: 0.9628 - precision: 0.0381 - recall: 0.8754 - auc: 0.9605 - val_loss: 0.1836 - val_tp: 74.0000 - val_fp: 810.0000 - val_tn: 44677.0000 - val_fn: 8.0000 - val_accuracy: 0.9820 - val_precision: 0.0837 - val_recall: 0.9024 - val_auc: 0.9812
Epoch 17/100
182276/182276 [==============================] - 1s 3us/sample - loss: 0.2779 - tp: 263.0000 - fp: 7040.0000 - tn: 174931.0000 - fn: 42.0000 - accuracy: 0.9611 - precision: 0.0360 - recall: 0.8623 - auc: 0.9531 - val_loss: 0.1799 - val_tp: 74.0000 - val_fp: 866.0000 - val_tn: 44621.0000 - val_fn: 8.0000 - val_accuracy: 0.9808 - val_precision: 0.0787 - val_recall: 0.9024 - val_auc: 0.9814
Epoch 18/100
182276/182276 [==============================] - 1s 3us/sample - loss: 0.2188 - tp: 272.0000 - fp: 7279.0000 - tn: 174692.0000 - fn: 33.0000 - accuracy: 0.9599 - precision: 0.0360 - recall: 0.8918 - auc: 0.9712 - val_loss: 0.1791 - val_tp: 74.0000 - val_fp: 902.0000 - val_tn: 44585.0000 - val_fn: 8.0000 - val_accuracy: 0.9800 - val_precision: 0.0758 - val_recall: 0.9024 - val_auc: 0.9816
Epoch 19/100
182276/182276 [==============================] - 1s 3us/sample - loss: 0.2562 - tp: 268.0000 - fp: 6972.0000 - tn: 174999.0000 - fn: 37.0000 - accuracy: 0.9615 - precision: 0.0370 - recall: 0.8787 - auc: 0.9599 - val_loss: 0.1789 - val_tp: 74.0000 - val_fp: 864.0000 - val_tn: 44623.0000 - val_fn: 8.0000 - val_accuracy: 0.9809 - val_precision: 0.0789 - val_recall: 0.9024 - val_auc: 0.9817
Epoch 20/100
182276/182276 [==============================] - 1s 3us/sample - loss: 0.2241 - tp: 274.0000 - fp: 6996.0000 - tn: 174975.0000 - fn: 31.0000 - accuracy: 0.9614 - precision: 0.0377 - recall: 0.8984 - auc: 0.9671 - val_loss: 0.1800 - val_tp: 74.0000 - val_fp: 862.0000 - val_tn: 44625.0000 - val_fn: 8.0000 - val_accuracy: 0.9809 - val_precision: 0.0791 - val_recall: 0.9024 - val_auc: 0.9818
Epoch 21/100
182276/182276 [==============================] - 1s 3us/sample - loss: 0.2015 - tp: 273.0000 - fp: 6637.0000 - tn: 175334.0000 - fn: 32.0000 - accuracy: 0.9634 - precision: 0.0395 - recall: 0.8951 - auc: 0.9752 - val_loss: 0.1765 - val_tp: 74.0000 - val_fp: 855.0000 - val_tn: 44632.0000 - val_fn: 8.0000 - val_accuracy: 0.9811 - val_precision: 0.0797 - val_recall: 0.9024 - val_auc: 0.9824
Epoch 22/100
182276/182276 [==============================] - 1s 3us/sample - loss: 0.2209 - tp: 271.0000 - fp: 6428.0000 - tn: 175543.0000 - fn: 34.0000 - accuracy: 0.9645 - precision: 0.0405 - recall: 0.8885 - auc: 0.9674 - val_loss: 0.1760 - val_tp: 74.0000 - val_fp: 835.0000 - val_tn: 44652.0000 - val_fn: 8.0000 - val_accuracy: 0.9815 - val_precision: 0.0814 - val_recall: 0.9024 - val_auc: 0.9826
Epoch 23/100
182276/182276 [==============================] - 1s 3us/sample - loss: 0.1654 - tp: 282.0000 - fp: 6397.0000 - tn: 175574.0000 - fn: 23.0000 - accuracy: 0.9648 - precision: 0.0422 - recall: 0.9246 - auc: 0.9817 - val_loss: 0.1763 - val_tp: 74.0000 - val_fp: 773.0000 - val_tn: 44714.0000 - val_fn: 8.0000 - val_accuracy: 0.9829 - val_precision: 0.0874 - val_recall: 0.9024 - val_auc: 0.9827
Epoch 24/100
182276/182276 [==============================] - 1s 3us/sample - loss: 0.2263 - tp: 271.0000 - fp: 6441.0000 - tn: 175530.0000 - fn: 34.0000 - accuracy: 0.9645 - precision: 0.0404 - recall: 0.8885 - auc: 0.9670 - val_loss: 0.1740 - val_tp: 74.0000 - val_fp: 802.0000 - val_tn: 44685.0000 - val_fn: 8.0000 - val_accuracy: 0.9822 - val_precision: 0.0845 - val_recall: 0.9024 - val_auc: 0.9829
Epoch 25/100
182276/182276 [==============================] - 1s 3us/sample - loss: 0.2225 - tp: 271.0000 - fp: 6317.0000 - tn: 175654.0000 - fn: 34.0000 - accuracy: 0.9652 - precision: 0.0411 - recall: 0.8885 - auc: 0.9678 - val_loss: 0.1715 - val_tp: 74.0000 - val_fp: 831.0000 - val_tn: 44656.0000 - val_fn: 8.0000 - val_accuracy: 0.9816 - val_precision: 0.0818 - val_recall: 0.9024 - val_auc: 0.9808
Epoch 26/100
182276/182276 [==============================] - 1s 3us/sample - loss: 0.2039 - tp: 279.0000 - fp: 6674.0000 - tn: 175297.0000 - fn: 26.0000 - accuracy: 0.9632 - precision: 0.0401 - recall: 0.9148 - auc: 0.9691 - val_loss: 0.1716 - val_tp: 74.0000 - val_fp: 798.0000 - val_tn: 44689.0000 - val_fn: 8.0000 - val_accuracy: 0.9823 - val_precision: 0.0849 - val_recall: 0.9024 - val_auc: 0.9833
Epoch 27/100
182276/182276 [==============================] - 1s 3us/sample - loss: 0.2030 - tp: 276.0000 - fp: 6050.0000 - tn: 175921.0000 - fn: 29.0000 - accuracy: 0.9666 - precision: 0.0436 - recall: 0.9049 - auc: 0.9722 - val_loss: 0.1748 - val_tp: 74.0000 - val_fp: 722.0000 - val_tn: 44765.0000 - val_fn: 8.0000 - val_accuracy: 0.9840 - val_precision: 0.0930 - val_recall: 0.9024 - val_auc: 0.9813
Epoch 28/100
182276/182276 [==============================] - 1s 3us/sample - loss: 0.2273 - tp: 270.0000 - fp: 6034.0000 - tn: 175937.0000 - fn: 35.0000 - accuracy: 0.9667 - precision: 0.0428 - recall: 0.8852 - auc: 0.9656 - val_loss: 0.1730 - val_tp: 74.0000 - val_fp: 748.0000 - val_tn: 44739.0000 - val_fn: 8.0000 - val_accuracy: 0.9834 - val_precision: 0.0900 - val_recall: 0.9024 - val_auc: 0.9814
Epoch 29/100
182276/182276 [==============================] - 1s 3us/sample - loss: 0.1998 - tp: 274.0000 - fp: 5768.0000 - tn: 176203.0000 - fn: 31.0000 - accuracy: 0.9682 - precision: 0.0453 - recall: 0.8984 - auc: 0.9758 - val_loss: 0.1718 - val_tp: 74.0000 - val_fp: 783.0000 - val_tn: 44704.0000 - val_fn: 8.0000 - val_accuracy: 0.9826 - val_precision: 0.0863 - val_recall: 0.9024 - val_auc: 0.9813
Epoch 30/100
182276/182276 [==============================] - 1s 3us/sample - loss: 0.1844 - tp: 277.0000 - fp: 5926.0000 - tn: 176045.0000 - fn: 28.0000 - accuracy: 0.9673 - precision: 0.0447 - recall: 0.9082 - auc: 0.9767 - val_loss: 0.1700 - val_tp: 74.0000 - val_fp: 783.0000 - val_tn: 44704.0000 - val_fn: 8.0000 - val_accuracy: 0.9826 - val_precision: 0.0863 - val_recall: 0.9024 - val_auc: 0.9815
Epoch 31/100
182276/182276 [==============================] - 1s 3us/sample - loss: 0.1811 - tp: 276.0000 - fp: 6118.0000 - tn: 175853.0000 - fn: 29.0000 - accuracy: 0.9663 - precision: 0.0432 - recall: 0.9049 - auc: 0.9785 - val_loss: 0.1679 - val_tp: 74.0000 - val_fp: 791.0000 - val_tn: 44696.0000 - val_fn: 8.0000 - val_accuracy: 0.9825 - val_precision: 0.0855 - val_recall: 0.9024 - val_auc: 0.9816
Epoch 32/100
182276/182276 [==============================] - 1s 3us/sample - loss: 0.1889 - tp: 276.0000 - fp: 6230.0000 - tn: 175741.0000 - fn: 29.0000 - accuracy: 0.9657 - precision: 0.0424 - recall: 0.9049 - auc: 0.9774 - val_loss: 0.1677 - val_tp: 74.0000 - val_fp: 777.0000 - val_tn: 44710.0000 - val_fn: 8.0000 - val_accuracy: 0.9828 - val_precision: 0.0870 - val_recall: 0.9024 - val_auc: 0.9817
Epoch 33/100
182276/182276 [==============================] - 1s 3us/sample - loss: 0.1986 - tp: 280.0000 - fp: 5610.0000 - tn: 176361.0000 - fn: 25.0000 - accuracy: 0.9691 - precision: 0.0475 - recall: 0.9180 - auc: 0.9717 - val_loss: 0.1702 - val_tp: 74.0000 - val_fp: 679.0000 - val_tn: 44808.0000 - val_fn: 8.0000 - val_accuracy: 0.9849 - val_precision: 0.0983 - val_recall: 0.9024 - val_auc: 0.9817
Epoch 34/100
182276/182276 [==============================] - 1s 3us/sample - loss: 0.1848 - tp: 277.0000 - fp: 5707.0000 - tn: 176264.0000 - fn: 28.0000 - accuracy: 0.9685 - precision: 0.0463 - recall: 0.9082 - auc: 0.9729 - val_loss: 0.1689 - val_tp: 74.0000 - val_fp: 713.0000 - val_tn: 44774.0000 - val_fn: 8.0000 - val_accuracy: 0.9842 - val_precision: 0.0940 - val_recall: 0.9024 - val_auc: 0.9818
Epoch 35/100
182276/182276 [==============================] - 1s 3us/sample - loss: 0.1910 - tp: 273.0000 - fp: 5492.0000 - tn: 176479.0000 - fn: 32.0000 - accuracy: 0.9697 - precision: 0.0474 - recall: 0.8951 - auc: 0.9747 - val_loss: 0.1665 - val_tp: 74.0000 - val_fp: 748.0000 - val_tn: 44739.0000 - val_fn: 8.0000 - val_accuracy: 0.9834 - val_precision: 0.0900 - val_recall: 0.9024 - val_auc: 0.9820
Epoch 36/100
163840/182276 [=========================>....] - ETA: 0s - loss: 0.1844 - tp: 245.0000 - fp: 5415.0000 - tn: 158154.0000 - fn: 26.0000 - accuracy: 0.9668 - precision: 0.0433 - recall: 0.9041 - auc: 0.9783Restoring model weights from the end of the best epoch.
182276/182276 [==============================] - 1s 3us/sample - loss: 0.1787 - tp: 277.0000 - fp: 6024.0000 - tn: 175947.0000 - fn: 28.0000 - accuracy: 0.9668 - precision: 0.0440 - recall: 0.9082 - auc: 0.9795 - val_loss: 0.1624 - val_tp: 74.0000 - val_fp: 790.0000 - val_tn: 44697.0000 - val_fn: 8.0000 - val_accuracy: 0.9825 - val_precision: 0.0856 - val_recall: 0.9024 - val_auc: 0.9821
Epoch 00036: early stopping

 

訓練履歴を確認する

plot_metrics(weighted_history)

 

メトリクスを評価する

train_predictions_weighted = weighted_model.predict(train_features, batch_size=BATCH_SIZE)
test_predictions_weighted = weighted_model.predict(test_features, batch_size=BATCH_SIZE)
weighted_results = weighted_model.evaluate(test_features, test_labels,
                                           batch_size=BATCH_SIZE, verbose=0)
for name, value in zip(weighted_model.metrics_names, weighted_results):
  print(name, ': ', value)
print()

plot_cm(test_labels, test_predictions_weighted)
loss :  0.07607786336474939
tp :  92.0
fp :  937.0
tn :  55920.0
fn :  13.0
accuracy :  0.9833222
precision :  0.08940719
recall :  0.8761905
auc :  0.9757879

Legitimate Transactions Detected (True Negatives):  55920
Legitimate Transactions Incorrectly Detected (False Positives):  937
Fraudulent Transactions Missed (False Negatives):  13
Fraudulent Transactions Detected (True Positives):  92
Total Fraudulent Transactions:  105

ここでクラス重みで accuracy と precision はより低いことを見て取れます、何故ならばより多くの false positive があるからです、しかし反対に recall と AUC はより高いです、何故ならばモデルはまたより多くの true positive を見つけたからです。より低い accuracy を持つにもかかわらず、このモデルはより高い recall を持ちます (そしてより多くの詐欺的トランザクションを識別します)。もちろん、両者のタイプのエラーへのコストはあります (非常に多くの正当なトランザクションを詐欺的としてフラグを立ててユーザを困らせることもまた望まないでしょう)。貴方のアプリケーションのためにこれらの異なるタイプのエラーの間のトレードオフを注意深く考えてください。

 

ROC をプロットする

plot_roc("Train Baseline", train_labels, train_predictions_baseline, color=colors[0])
plot_roc("Test Baseline", test_labels, test_predictions_baseline, color=colors[0], linestyle='--')

plot_roc("Train Weighted", train_labels, train_predictions_weighted, color=colors[1])
plot_roc("Test Weighted", test_labels, test_predictions_weighted, color=colors[1], linestyle='--')


plt.legend(loc='lower right')
<matplotlib.legend.Legend at 0x7fc6b41b67f0>

 

オーバーサンプリング

少数 (= minotiry) クラスをオーバーサンプリングする

関連するアプローチは少数クラスをオーバーサンプリングすることによりデータセットを再サンプリングすることです。

pos_features = train_features[bool_train_labels]
neg_features = train_features[~bool_train_labels]

pos_labels = train_labels[bool_train_labels]
neg_labels = train_labels[~bool_train_labels]

 
NumPy を使用する

positive サンプルからランダムインデックスの適切な数を選択してデータセットを手動で平衡にすることができます :

ids = np.arange(len(pos_features))
choices = np.random.choice(ids, len(neg_features))

res_pos_features = pos_features[choices]
res_pos_labels = pos_labels[choices]

res_pos_features.shape
(181971, 29)
resampled_features = np.concatenate([res_pos_features, neg_features], axis=0)
resampled_labels = np.concatenate([res_pos_labels, neg_labels], axis=0)

order = np.arange(len(resampled_labels))
np.random.shuffle(order)
resampled_features = resampled_features[order]
resampled_labels = resampled_labels[order]

resampled_features.shape
(363942, 29)

 
tf.data を使用する

tf.data を使用している場合、バランスの取れたサンプルを生成する最も容易な方法は positive と negative データセットから始めて、それらをマージすることです。より多くのサンプルについては tf.data ガイド を見てください。

BUFFER_SIZE = 100000

def make_ds(features, labels):
  ds = tf.data.Dataset.from_tensor_slices((features, labels))#.cache()
  ds = ds.shuffle(BUFFER_SIZE).repeat()
  return ds

pos_ds = make_ds(pos_features, pos_labels)
neg_ds = make_ds(neg_features, neg_labels)

各データセットは (feature, label) ペアを生成します :

for features, label in pos_ds.take(1):
  print("Features:\n", features.numpy())
  print()
  print("Label: ", label.numpy())
Features:
 [-2.17940656  1.76763197 -3.92031926  4.88344634 -2.52559973 -0.87402282
 -5.          2.39895859 -2.28526198 -4.70820898  5.         -5.
  2.80944748 -5.         -2.29981762 -5.         -5.         -5.
  0.62439703 -0.30217952  2.21481335  2.15973127 -0.93161994 -0.09709966
 -3.50901907 -0.15144646  0.34072986 -1.87496516 -0.45141544]

Label:  1

experimental.sample_from_datasets を使用して 2 つを一緒にマージします :

resampled_ds = tf.data.experimental.sample_from_datasets([pos_ds, neg_ds], weights=[0.5, 0.5])
resampled_ds = resampled_ds.batch(BATCH_SIZE).prefetch(2)
for features, label in resampled_ds.take(1):
  print(label.numpy().mean())
0.51220703125

このデータセットを使用するために、エポック毎のステップ数を必要とします。

この場合の「エポック」の定義は曖昧です。例えばそれは各 negative サンプルを一度見るのに必要なバッチ数としましょう :

resampled_steps_per_epoch = np.ceil(2.0*neg/BATCH_SIZE)
resampled_steps_per_epoch
278.0

 

オーバーサンプリングされたデータ上で訓練する

今はこれらのメソッドがどのように比較できるかを見るためにクラス重みを使用する代わりに再サンプリングされたデータセットでモデルを訓練してみます。

Note: positive サンプルを複製することによりデータはバランスが取られましたので、総計のデータセットサイズは大きくなり、そして各エポックはより多くの訓練ステップのために実行されます。

resampled_model = make_model()
resampled_model.load_weights(initial_weights)

# Reset the bias to zero, since this dataset is balanced.
output_layer = resampled_model.layers[-1] 
output_layer.bias.assign([0])

val_ds = tf.data.Dataset.from_tensor_slices((val_features, val_labels)).cache()
val_ds = val_ds.batch(BATCH_SIZE).prefetch(2) 

resampled_history = resampled_model.fit(
    resampled_ds,
    epochs=EPOCHS,
    steps_per_epoch=resampled_steps_per_epoch,
    callbacks = [early_stopping],
    validation_data=val_ds)
Train for 278.0 steps, validate for 23 steps
Epoch 1/100
278/278 [==============================] - 11s 38ms/step - loss: 0.5141 - tp: 235679.0000 - fp: 89290.0000 - tn: 195069.0000 - fn: 49306.0000 - accuracy: 0.7566 - precision: 0.7252 - recall: 0.8270 - auc: 0.8606 - val_loss: 0.2508 - val_tp: 74.0000 - val_fp: 1342.0000 - val_tn: 44145.0000 - val_fn: 8.0000 - val_accuracy: 0.9704 - val_precision: 0.0523 - val_recall: 0.9024 - val_auc: 0.9747
Epoch 2/100
278/278 [==============================] - 8s 30ms/step - loss: 0.2221 - tp: 256642.0000 - fp: 18273.0000 - tn: 266245.0000 - fn: 28184.0000 - accuracy: 0.9184 - precision: 0.9335 - recall: 0.9010 - auc: 0.9667 - val_loss: 0.1341 - val_tp: 74.0000 - val_fp: 976.0000 - val_tn: 44511.0000 - val_fn: 8.0000 - val_accuracy: 0.9784 - val_precision: 0.0705 - val_recall: 0.9024 - val_auc: 0.9772
Epoch 3/100
278/278 [==============================] - 8s 29ms/step - loss: 0.1627 - tp: 263207.0000 - fp: 10666.0000 - tn: 273652.0000 - fn: 21819.0000 - accuracy: 0.9429 - precision: 0.9611 - recall: 0.9234 - auc: 0.9824 - val_loss: 0.0934 - val_tp: 73.0000 - val_fp: 822.0000 - val_tn: 44665.0000 - val_fn: 9.0000 - val_accuracy: 0.9818 - val_precision: 0.0816 - val_recall: 0.8902 - val_auc: 0.9768
Epoch 4/100
278/278 [==============================] - 8s 30ms/step - loss: 0.1373 - tp: 266123.0000 - fp: 8945.0000 - tn: 275364.0000 - fn: 18912.0000 - accuracy: 0.9511 - precision: 0.9675 - recall: 0.9337 - auc: 0.9876 - val_loss: 0.0773 - val_tp: 74.0000 - val_fp: 807.0000 - val_tn: 44680.0000 - val_fn: 8.0000 - val_accuracy: 0.9821 - val_precision: 0.0840 - val_recall: 0.9024 - val_auc: 0.9776
Epoch 5/100
278/278 [==============================] - 8s 30ms/step - loss: 0.1242 - tp: 266967.0000 - fp: 8276.0000 - tn: 276477.0000 - fn: 17624.0000 - accuracy: 0.9545 - precision: 0.9699 - recall: 0.9381 - auc: 0.9902 - val_loss: 0.0669 - val_tp: 74.0000 - val_fp: 771.0000 - val_tn: 44716.0000 - val_fn: 8.0000 - val_accuracy: 0.9829 - val_precision: 0.0876 - val_recall: 0.9024 - val_auc: 0.9766
Epoch 6/100
278/278 [==============================] - 8s 30ms/step - loss: 0.1150 - tp: 268272.0000 - fp: 7786.0000 - tn: 276538.0000 - fn: 16748.0000 - accuracy: 0.9569 - precision: 0.9718 - recall: 0.9412 - auc: 0.9918 - val_loss: 0.0604 - val_tp: 74.0000 - val_fp: 727.0000 - val_tn: 44760.0000 - val_fn: 8.0000 - val_accuracy: 0.9839 - val_precision: 0.0924 - val_recall: 0.9024 - val_auc: 0.9778
Epoch 7/100
278/278 [==============================] - 8s 30ms/step - loss: 0.1076 - tp: 268596.0000 - fp: 7335.0000 - tn: 277522.0000 - fn: 15891.0000 - accuracy: 0.9592 - precision: 0.9734 - recall: 0.9441 - auc: 0.9930 - val_loss: 0.0548 - val_tp: 73.0000 - val_fp: 668.0000 - val_tn: 44819.0000 - val_fn: 9.0000 - val_accuracy: 0.9851 - val_precision: 0.0985 - val_recall: 0.8902 - val_auc: 0.9786
Epoch 8/100
278/278 [==============================] - 9s 31ms/step - loss: 0.1011 - tp: 269934.0000 - fp: 7025.0000 - tn: 277658.0000 - fn: 14727.0000 - accuracy: 0.9618 - precision: 0.9746 - recall: 0.9483 - auc: 0.9940 - val_loss: 0.0505 - val_tp: 73.0000 - val_fp: 621.0000 - val_tn: 44866.0000 - val_fn: 9.0000 - val_accuracy: 0.9862 - val_precision: 0.1052 - val_recall: 0.8902 - val_auc: 0.9792
Epoch 9/100
278/278 [==============================] - 8s 30ms/step - loss: 0.0959 - tp: 270679.0000 - fp: 6711.0000 - tn: 277837.0000 - fn: 14117.0000 - accuracy: 0.9634 - precision: 0.9758 - recall: 0.9504 - auc: 0.9948 - val_loss: 0.0483 - val_tp: 73.0000 - val_fp: 603.0000 - val_tn: 44884.0000 - val_fn: 9.0000 - val_accuracy: 0.9866 - val_precision: 0.1080 - val_recall: 0.8902 - val_auc: 0.9794
Epoch 10/100
278/278 [==============================] - 8s 30ms/step - loss: 0.0913 - tp: 271592.0000 - fp: 6656.0000 - tn: 277917.0000 - fn: 13179.0000 - accuracy: 0.9652 - precision: 0.9761 - recall: 0.9537 - auc: 0.9953 - val_loss: 0.0440 - val_tp: 73.0000 - val_fp: 528.0000 - val_tn: 44959.0000 - val_fn: 9.0000 - val_accuracy: 0.9882 - val_precision: 0.1215 - val_recall: 0.8902 - val_auc: 0.9795
Epoch 11/100
278/278 [==============================] - 8s 30ms/step - loss: 0.0875 - tp: 272110.0000 - fp: 6604.0000 - tn: 278063.0000 - fn: 12567.0000 - accuracy: 0.9663 - precision: 0.9763 - recall: 0.9559 - auc: 0.9957 - val_loss: 0.0408 - val_tp: 73.0000 - val_fp: 491.0000 - val_tn: 44996.0000 - val_fn: 9.0000 - val_accuracy: 0.9890 - val_precision: 0.1294 - val_recall: 0.8902 - val_auc: 0.9799
Epoch 12/100
278/278 [==============================] - 8s 29ms/step - loss: 0.0832 - tp: 272899.0000 - fp: 6475.0000 - tn: 278107.0000 - fn: 11863.0000 - accuracy: 0.9678 - precision: 0.9768 - recall: 0.9583 - auc: 0.9962 - val_loss: 0.0392 - val_tp: 73.0000 - val_fp: 481.0000 - val_tn: 45006.0000 - val_fn: 9.0000 - val_accuracy: 0.9892 - val_precision: 0.1318 - val_recall: 0.8902 - val_auc: 0.9803
Epoch 13/100
278/278 [==============================] - 8s 30ms/step - loss: 0.0795 - tp: 274008.0000 - fp: 6417.0000 - tn: 278228.0000 - fn: 10691.0000 - accuracy: 0.9700 - precision: 0.9771 - recall: 0.9624 - auc: 0.9966 - val_loss: 0.0358 - val_tp: 73.0000 - val_fp: 442.0000 - val_tn: 45045.0000 - val_fn: 9.0000 - val_accuracy: 0.9901 - val_precision: 0.1417 - val_recall: 0.8902 - val_auc: 0.9806
Epoch 14/100
278/278 [==============================] - 8s 30ms/step - loss: 0.0747 - tp: 275586.0000 - fp: 6254.0000 - tn: 277642.0000 - fn: 9862.0000 - accuracy: 0.9717 - precision: 0.9778 - recall: 0.9655 - auc: 0.9970 - val_loss: 0.0336 - val_tp: 73.0000 - val_fp: 432.0000 - val_tn: 45055.0000 - val_fn: 9.0000 - val_accuracy: 0.9903 - val_precision: 0.1446 - val_recall: 0.8902 - val_auc: 0.9808
Epoch 15/100
278/278 [==============================] - 8s 31ms/step - loss: 0.0719 - tp: 274707.0000 - fp: 6051.0000 - tn: 279108.0000 - fn: 9478.0000 - accuracy: 0.9727 - precision: 0.9784 - recall: 0.9666 - auc: 0.9971 - val_loss: 0.0306 - val_tp: 73.0000 - val_fp: 407.0000 - val_tn: 45080.0000 - val_fn: 9.0000 - val_accuracy: 0.9909 - val_precision: 0.1521 - val_recall: 0.8902 - val_auc: 0.9808
Epoch 16/100
278/278 [==============================] - 8s 30ms/step - loss: 0.0697 - tp: 275176.0000 - fp: 6024.0000 - tn: 278945.0000 - fn: 9199.0000 - accuracy: 0.9733 - precision: 0.9786 - recall: 0.9677 - auc: 0.9972 - val_loss: 0.0305 - val_tp: 73.0000 - val_fp: 400.0000 - val_tn: 45087.0000 - val_fn: 9.0000 - val_accuracy: 0.9910 - val_precision: 0.1543 - val_recall: 0.8902 - val_auc: 0.9808
Epoch 17/100
278/278 [==============================] - 8s 30ms/step - loss: 0.0681 - tp: 275922.0000 - fp: 6047.0000 - tn: 278615.0000 - fn: 8760.0000 - accuracy: 0.9740 - precision: 0.9786 - recall: 0.9692 - auc: 0.9973 - val_loss: 0.0277 - val_tp: 73.0000 - val_fp: 368.0000 - val_tn: 45119.0000 - val_fn: 9.0000 - val_accuracy: 0.9917 - val_precision: 0.1655 - val_recall: 0.8902 - val_auc: 0.9808
Epoch 18/100
278/278 [==============================] - 8s 30ms/step - loss: 0.0671 - tp: 276085.0000 - fp: 6258.0000 - tn: 278545.0000 - fn: 8456.0000 - accuracy: 0.9742 - precision: 0.9778 - recall: 0.9703 - auc: 0.9974 - val_loss: 0.0264 - val_tp: 73.0000 - val_fp: 351.0000 - val_tn: 45136.0000 - val_fn: 9.0000 - val_accuracy: 0.9921 - val_precision: 0.1722 - val_recall: 0.8902 - val_auc: 0.9752
Epoch 19/100
278/278 [==============================] - 8s 30ms/step - loss: 0.0652 - tp: 276364.0000 - fp: 6108.0000 - tn: 278761.0000 - fn: 8111.0000 - accuracy: 0.9750 - precision: 0.9784 - recall: 0.9715 - auc: 0.9975 - val_loss: 0.0263 - val_tp: 73.0000 - val_fp: 354.0000 - val_tn: 45133.0000 - val_fn: 9.0000 - val_accuracy: 0.9920 - val_precision: 0.1710 - val_recall: 0.8902 - val_auc: 0.9699
Epoch 20/100
278/278 [==============================] - 8s 30ms/step - loss: 0.0640 - tp: 277072.0000 - fp: 6092.0000 - tn: 278461.0000 - fn: 7719.0000 - accuracy: 0.9757 - precision: 0.9785 - recall: 0.9729 - auc: 0.9975 - val_loss: 0.0247 - val_tp: 73.0000 - val_fp: 333.0000 - val_tn: 45154.0000 - val_fn: 9.0000 - val_accuracy: 0.9925 - val_precision: 0.1798 - val_recall: 0.8902 - val_auc: 0.9645
Epoch 21/100
278/278 [==============================] - 8s 29ms/step - loss: 0.0635 - tp: 277050.0000 - fp: 6229.0000 - tn: 278537.0000 - fn: 7528.0000 - accuracy: 0.9758 - precision: 0.9780 - recall: 0.9735 - auc: 0.9976 - val_loss: 0.0247 - val_tp: 73.0000 - val_fp: 338.0000 - val_tn: 45149.0000 - val_fn: 9.0000 - val_accuracy: 0.9924 - val_precision: 0.1776 - val_recall: 0.8902 - val_auc: 0.9590
Epoch 22/100
278/278 [==============================] - 8s 30ms/step - loss: 0.0619 - tp: 276909.0000 - fp: 6199.0000 - tn: 278826.0000 - fn: 7410.0000 - accuracy: 0.9761 - precision: 0.9781 - recall: 0.9739 - auc: 0.9977 - val_loss: 0.0242 - val_tp: 73.0000 - val_fp: 328.0000 - val_tn: 45159.0000 - val_fn: 9.0000 - val_accuracy: 0.9926 - val_precision: 0.1820 - val_recall: 0.8902 - val_auc: 0.9534
Epoch 23/100
278/278 [==============================] - 8s 30ms/step - loss: 0.0610 - tp: 278124.0000 - fp: 6195.0000 - tn: 278074.0000 - fn: 6951.0000 - accuracy: 0.9769 - precision: 0.9782 - recall: 0.9756 - auc: 0.9977 - val_loss: 0.0231 - val_tp: 73.0000 - val_fp: 311.0000 - val_tn: 45176.0000 - val_fn: 9.0000 - val_accuracy: 0.9930 - val_precision: 0.1901 - val_recall: 0.8902 - val_auc: 0.9536
Epoch 24/100
278/278 [==============================] - 8s 30ms/step - loss: 0.0605 - tp: 277101.0000 - fp: 6262.0000 - tn: 278966.0000 - fn: 7015.0000 - accuracy: 0.9767 - precision: 0.9779 - recall: 0.9753 - auc: 0.9977 - val_loss: 0.0222 - val_tp: 73.0000 - val_fp: 312.0000 - val_tn: 45175.0000 - val_fn: 9.0000 - val_accuracy: 0.9930 - val_precision: 0.1896 - val_recall: 0.8902 - val_auc: 0.9538
Epoch 25/100
278/278 [==============================] - 8s 30ms/step - loss: 0.0596 - tp: 277655.0000 - fp: 6154.0000 - tn: 278556.0000 - fn: 6979.0000 - accuracy: 0.9769 - precision: 0.9783 - recall: 0.9755 - auc: 0.9977 - val_loss: 0.0212 - val_tp: 73.0000 - val_fp: 295.0000 - val_tn: 45192.0000 - val_fn: 9.0000 - val_accuracy: 0.9933 - val_precision: 0.1984 - val_recall: 0.8902 - val_auc: 0.9482
Epoch 26/100
278/278 [==============================] - 8s 30ms/step - loss: 0.0586 - tp: 278013.0000 - fp: 6165.0000 - tn: 278169.0000 - fn: 6997.0000 - accuracy: 0.9769 - precision: 0.9783 - recall: 0.9754 - auc: 0.9978 - val_loss: 0.0217 - val_tp: 72.0000 - val_fp: 292.0000 - val_tn: 45195.0000 - val_fn: 10.0000 - val_accuracy: 0.9934 - val_precision: 0.1978 - val_recall: 0.8780 - val_auc: 0.9483
Epoch 27/100
277/278 [============================>.] - ETA: 0s - loss: 0.0581 - tp: 276446.0000 - fp: 6154.0000 - tn: 277889.0000 - fn: 6807.0000 - accuracy: 0.9772 - precision: 0.9782 - recall: 0.9760 - auc: 0.9978Restoring model weights from the end of the best epoch.
278/278 [==============================] - 8s 30ms/step - loss: 0.0581 - tp: 277470.0000 - fp: 6173.0000 - tn: 278871.0000 - fn: 6830.0000 - accuracy: 0.9772 - precision: 0.9782 - recall: 0.9760 - auc: 0.9978 - val_loss: 0.0209 - val_tp: 72.0000 - val_fp: 281.0000 - val_tn: 45206.0000 - val_fn: 10.0000 - val_accuracy: 0.9936 - val_precision: 0.2040 - val_recall: 0.8780 - val_auc: 0.9485
Epoch 00027: early stopping

訓練プロセスが各勾配更新でデータセット全体を考えていた場合、このオーバーサンプリングは基本的にはクラス重み付けと同値です。

しかしここで行なったように、モデルをバッチ-wise に訓練するとき、オーバーサンプリングされたデータはより滑らかな勾配信号を提供します : 各 positive サンプルが一つのバッチで巨大な重み上で見せられる代わりに、それらは小さい重みで各回に多くの異なるバッチで示されます。

この滑らかな勾配信号はモデルを訓練することを容易にします。

 

訓練履歴を確認する

ここではメトリクスの分布は異なることに注意してください、何故ならば訓練データは検証とテストデータからは大きく異なる分布を持つからです。

plot_metrics(resampled_history )

 

再訓練

訓練はバランスの取れたデータ上では容易ですので、上の訓練手続きは素早く overfit するかもしれません。

そこで callbacks.EarlyStopping にいつ訓練を停止するかについてより良い制御を与えるためにエポックを分割します。

resampled_model = make_model()
resampled_model.load_weights(initial_weights)

# Reset the bias to zero, since this dataset is balanced.
output_layer = resampled_model.layers[-1] 
output_layer.bias.assign([0])

resampled_history = resampled_model.fit(
    resampled_ds,
    # These are not real epochs
    steps_per_epoch = 20,
    epochs=10*EPOCHS,
    callbacks = [early_stopping],
    validation_data=(val_ds))
Train for 20 steps, validate for 23 steps
Epoch 1/1000
20/20 [==============================] - 3s 168ms/step - loss: 1.2909 - tp: 10811.0000 - fp: 11937.0000 - tn: 8418.0000 - fn: 9794.0000 - accuracy: 0.4695 - precision: 0.4753 - recall: 0.5247 - auc: 0.4887 - val_loss: 0.9277 - val_tp: 78.0000 - val_fp: 28484.0000 - val_tn: 17003.0000 - val_fn: 4.0000 - val_accuracy: 0.3748 - val_precision: 0.0027 - val_recall: 0.9512 - val_auc: 0.8413
Epoch 2/1000
20/20 [==============================] - 1s 33ms/step - loss: 0.8973 - tp: 14267.0000 - fp: 11350.0000 - tn: 9081.0000 - fn: 6262.0000 - accuracy: 0.5700 - precision: 0.5569 - recall: 0.6950 - auc: 0.6600 - val_loss: 0.8451 - val_tp: 80.0000 - val_fp: 25378.0000 - val_tn: 20109.0000 - val_fn: 2.0000 - val_accuracy: 0.4430 - val_precision: 0.0031 - val_recall: 0.9756 - val_auc: 0.9362
Epoch 3/1000
20/20 [==============================] - 1s 35ms/step - loss: 0.7107 - tp: 15904.0000 - fp: 10547.0000 - tn: 9941.0000 - fn: 4568.0000 - accuracy: 0.6310 - precision: 0.6013 - recall: 0.7769 - auc: 0.7582 - val_loss: 0.7505 - val_tp: 79.0000 - val_fp: 21165.0000 - val_tn: 24322.0000 - val_fn: 3.0000 - val_accuracy: 0.5355 - val_precision: 0.0037 - val_recall: 0.9634 - val_auc: 0.9478
Epoch 4/1000
20/20 [==============================] - 1s 35ms/step - loss: 0.5944 - tp: 16759.0000 - fp: 9572.0000 - tn: 10917.0000 - fn: 3712.0000 - accuracy: 0.6757 - precision: 0.6365 - recall: 0.8187 - auc: 0.8177 - val_loss: 0.6589 - val_tp: 77.0000 - val_fp: 16659.0000 - val_tn: 28828.0000 - val_fn: 5.0000 - val_accuracy: 0.6343 - val_precision: 0.0046 - val_recall: 0.9390 - val_auc: 0.9519
Epoch 5/1000
20/20 [==============================] - 1s 32ms/step - loss: 0.5151 - tp: 17257.0000 - fp: 8350.0000 - tn: 12146.0000 - fn: 3207.0000 - accuracy: 0.7178 - precision: 0.6739 - recall: 0.8433 - auc: 0.8568 - val_loss: 0.5801 - val_tp: 77.0000 - val_fp: 12473.0000 - val_tn: 33014.0000 - val_fn: 5.0000 - val_accuracy: 0.7262 - val_precision: 0.0061 - val_recall: 0.9390 - val_auc: 0.9552
Epoch 6/1000
20/20 [==============================] - 1s 32ms/step - loss: 0.4546 - tp: 17829.0000 - fp: 7203.0000 - tn: 13200.0000 - fn: 2728.0000 - accuracy: 0.7575 - precision: 0.7122 - recall: 0.8673 - auc: 0.8886 - val_loss: 0.5138 - val_tp: 76.0000 - val_fp: 9021.0000 - val_tn: 36466.0000 - val_fn: 6.0000 - val_accuracy: 0.8019 - val_precision: 0.0084 - val_recall: 0.9268 - val_auc: 0.9587
Epoch 7/1000
20/20 [==============================] - 1s 32ms/step - loss: 0.4176 - tp: 17998.0000 - fp: 6179.0000 - tn: 14182.0000 - fn: 2601.0000 - accuracy: 0.7856 - precision: 0.7444 - recall: 0.8737 - auc: 0.9035 - val_loss: 0.4574 - val_tp: 76.0000 - val_fp: 6272.0000 - val_tn: 39215.0000 - val_fn: 6.0000 - val_accuracy: 0.8622 - val_precision: 0.0120 - val_recall: 0.9268 - val_auc: 0.9618
Epoch 8/1000
20/20 [==============================] - 1s 32ms/step - loss: 0.3846 - tp: 18020.0000 - fp: 5174.0000 - tn: 15298.0000 - fn: 2468.0000 - accuracy: 0.8134 - precision: 0.7769 - recall: 0.8795 - auc: 0.9166 - val_loss: 0.4109 - val_tp: 75.0000 - val_fp: 4351.0000 - val_tn: 41136.0000 - val_fn: 7.0000 - val_accuracy: 0.9044 - val_precision: 0.0169 - val_recall: 0.9146 - val_auc: 0.9651
Epoch 9/1000
20/20 [==============================] - 1s 37ms/step - loss: 0.3631 - tp: 17972.0000 - fp: 4520.0000 - tn: 16011.0000 - fn: 2457.0000 - accuracy: 0.8297 - precision: 0.7990 - recall: 0.8797 - auc: 0.9230 - val_loss: 0.3704 - val_tp: 74.0000 - val_fp: 3021.0000 - val_tn: 42466.0000 - val_fn: 8.0000 - val_accuracy: 0.9335 - val_precision: 0.0239 - val_recall: 0.9024 - val_auc: 0.9674
Epoch 10/1000
20/20 [==============================] - 1s 32ms/step - loss: 0.3378 - tp: 17994.0000 - fp: 3772.0000 - tn: 16815.0000 - fn: 2379.0000 - accuracy: 0.8498 - precision: 0.8267 - recall: 0.8832 - auc: 0.9308 - val_loss: 0.3381 - val_tp: 74.0000 - val_fp: 2323.0000 - val_tn: 43164.0000 - val_fn: 8.0000 - val_accuracy: 0.9488 - val_precision: 0.0309 - val_recall: 0.9024 - val_auc: 0.9693
Epoch 11/1000
20/20 [==============================] - 1s 33ms/step - loss: 0.3213 - tp: 18268.0000 - fp: 3260.0000 - tn: 17041.0000 - fn: 2391.0000 - accuracy: 0.8620 - precision: 0.8486 - recall: 0.8843 - auc: 0.9355 - val_loss: 0.3113 - val_tp: 74.0000 - val_fp: 1888.0000 - val_tn: 43599.0000 - val_fn: 8.0000 - val_accuracy: 0.9584 - val_precision: 0.0377 - val_recall: 0.9024 - val_auc: 0.9715
Epoch 12/1000
20/20 [==============================] - 1s 34ms/step - loss: 0.3040 - tp: 17977.0000 - fp: 2813.0000 - tn: 17832.0000 - fn: 2338.0000 - accuracy: 0.8742 - precision: 0.8647 - recall: 0.8849 - auc: 0.9420 - val_loss: 0.2880 - val_tp: 74.0000 - val_fp: 1621.0000 - val_tn: 43866.0000 - val_fn: 8.0000 - val_accuracy: 0.9643 - val_precision: 0.0437 - val_recall: 0.9024 - val_auc: 0.9733
Epoch 13/1000
20/20 [==============================] - 1s 34ms/step - loss: 0.2907 - tp: 18262.0000 - fp: 2594.0000 - tn: 17824.0000 - fn: 2280.0000 - accuracy: 0.8810 - precision: 0.8756 - recall: 0.8890 - auc: 0.9460 - val_loss: 0.2679 - val_tp: 74.0000 - val_fp: 1426.0000 - val_tn: 44061.0000 - val_fn: 8.0000 - val_accuracy: 0.9685 - val_precision: 0.0493 - val_recall: 0.9024 - val_auc: 0.9744
Epoch 14/1000
20/20 [==============================] - 1s 31ms/step - loss: 0.2803 - tp: 18314.0000 - fp: 2190.0000 - tn: 18192.0000 - fn: 2264.0000 - accuracy: 0.8913 - precision: 0.8932 - recall: 0.8900 - auc: 0.9491 - val_loss: 0.2503 - val_tp: 74.0000 - val_fp: 1330.0000 - val_tn: 44157.0000 - val_fn: 8.0000 - val_accuracy: 0.9706 - val_precision: 0.0527 - val_recall: 0.9024 - val_auc: 0.9752
Epoch 15/1000
20/20 [==============================] - 1s 32ms/step - loss: 0.2679 - tp: 18275.0000 - fp: 1967.0000 - tn: 18471.0000 - fn: 2247.0000 - accuracy: 0.8971 - precision: 0.9028 - recall: 0.8905 - auc: 0.9528 - val_loss: 0.2350 - val_tp: 74.0000 - val_fp: 1258.0000 - val_tn: 44229.0000 - val_fn: 8.0000 - val_accuracy: 0.9722 - val_precision: 0.0556 - val_recall: 0.9024 - val_auc: 0.9759
Epoch 16/1000
20/20 [==============================] - 1s 32ms/step - loss: 0.2575 - tp: 18217.0000 - fp: 1735.0000 - tn: 18790.0000 - fn: 2218.0000 - accuracy: 0.9035 - precision: 0.9130 - recall: 0.8915 - auc: 0.9558 - val_loss: 0.2215 - val_tp: 73.0000 - val_fp: 1194.0000 - val_tn: 44293.0000 - val_fn: 9.0000 - val_accuracy: 0.9736 - val_precision: 0.0576 - val_recall: 0.8902 - val_auc: 0.9766
Epoch 17/1000
20/20 [==============================] - 1s 31ms/step - loss: 0.2514 - tp: 18223.0000 - fp: 1700.0000 - tn: 18896.0000 - fn: 2141.0000 - accuracy: 0.9062 - precision: 0.9147 - recall: 0.8949 - auc: 0.9582 - val_loss: 0.2085 - val_tp: 73.0000 - val_fp: 1136.0000 - val_tn: 44351.0000 - val_fn: 9.0000 - val_accuracy: 0.9749 - val_precision: 0.0604 - val_recall: 0.8902 - val_auc: 0.9767
Epoch 18/1000
20/20 [==============================] - 1s 37ms/step - loss: 0.2424 - tp: 18402.0000 - fp: 1466.0000 - tn: 18902.0000 - fn: 2190.0000 - accuracy: 0.9107 - precision: 0.9262 - recall: 0.8936 - auc: 0.9601 - val_loss: 0.1980 - val_tp: 73.0000 - val_fp: 1105.0000 - val_tn: 44382.0000 - val_fn: 9.0000 - val_accuracy: 0.9756 - val_precision: 0.0620 - val_recall: 0.8902 - val_auc: 0.9771
Epoch 19/1000
20/20 [==============================] - 1s 32ms/step - loss: 0.2358 - tp: 18413.0000 - fp: 1449.0000 - tn: 18924.0000 - fn: 2174.0000 - accuracy: 0.9115 - precision: 0.9270 - recall: 0.8944 - auc: 0.9624 - val_loss: 0.1889 - val_tp: 73.0000 - val_fp: 1074.0000 - val_tn: 44413.0000 - val_fn: 9.0000 - val_accuracy: 0.9762 - val_precision: 0.0636 - val_recall: 0.8902 - val_auc: 0.9772
Epoch 20/1000
20/20 [==============================] - 1s 33ms/step - loss: 0.2254 - tp: 18587.0000 - fp: 1249.0000 - tn: 19002.0000 - fn: 2122.0000 - accuracy: 0.9177 - precision: 0.9370 - recall: 0.8975 - auc: 0.9653 - val_loss: 0.1801 - val_tp: 73.0000 - val_fp: 1040.0000 - val_tn: 44447.0000 - val_fn: 9.0000 - val_accuracy: 0.9770 - val_precision: 0.0656 - val_recall: 0.8902 - val_auc: 0.9771
Epoch 21/1000
20/20 [==============================] - 1s 34ms/step - loss: 0.2200 - tp: 18517.0000 - fp: 1188.0000 - tn: 19205.0000 - fn: 2050.0000 - accuracy: 0.9209 - precision: 0.9397 - recall: 0.9003 - auc: 0.9675 - val_loss: 0.1720 - val_tp: 73.0000 - val_fp: 1024.0000 - val_tn: 44463.0000 - val_fn: 9.0000 - val_accuracy: 0.9773 - val_precision: 0.0665 - val_recall: 0.8902 - val_auc: 0.9775
Epoch 22/1000
20/20 [==============================] - 1s 34ms/step - loss: 0.2154 - tp: 18254.0000 - fp: 1078.0000 - tn: 19556.0000 - fn: 2072.0000 - accuracy: 0.9231 - precision: 0.9442 - recall: 0.8981 - auc: 0.9685 - val_loss: 0.1645 - val_tp: 73.0000 - val_fp: 1007.0000 - val_tn: 44480.0000 - val_fn: 9.0000 - val_accuracy: 0.9777 - val_precision: 0.0676 - val_recall: 0.8902 - val_auc: 0.9773
Epoch 23/1000
20/20 [==============================] - 1s 32ms/step - loss: 0.2092 - tp: 18465.0000 - fp: 1074.0000 - tn: 19455.0000 - fn: 1966.0000 - accuracy: 0.9258 - precision: 0.9450 - recall: 0.9038 - auc: 0.9706 - val_loss: 0.1588 - val_tp: 74.0000 - val_fp: 1022.0000 - val_tn: 44465.0000 - val_fn: 8.0000 - val_accuracy: 0.9774 - val_precision: 0.0675 - val_recall: 0.9024 - val_auc: 0.9776
Epoch 24/1000
20/20 [==============================] - 1s 32ms/step - loss: 0.2034 - tp: 18590.0000 - fp: 1025.0000 - tn: 19414.0000 - fn: 1931.0000 - accuracy: 0.9278 - precision: 0.9477 - recall: 0.9059 - auc: 0.9720 - val_loss: 0.1532 - val_tp: 74.0000 - val_fp: 1011.0000 - val_tn: 44476.0000 - val_fn: 8.0000 - val_accuracy: 0.9776 - val_precision: 0.0682 - val_recall: 0.9024 - val_auc: 0.9772
Epoch 25/1000
20/20 [==============================] - 1s 33ms/step - loss: 0.1999 - tp: 18484.0000 - fp: 1076.0000 - tn: 19531.0000 - fn: 1869.0000 - accuracy: 0.9281 - precision: 0.9450 - recall: 0.9082 - auc: 0.9736 - val_loss: 0.1472 - val_tp: 74.0000 - val_fp: 1009.0000 - val_tn: 44478.0000 - val_fn: 8.0000 - val_accuracy: 0.9777 - val_precision: 0.0683 - val_recall: 0.9024 - val_auc: 0.9774
Epoch 26/1000
20/20 [==============================] - 1s 32ms/step - loss: 0.1961 - tp: 18723.0000 - fp: 1048.0000 - tn: 19357.0000 - fn: 1832.0000 - accuracy: 0.9297 - precision: 0.9470 - recall: 0.9109 - auc: 0.9739 - val_loss: 0.1415 - val_tp: 74.0000 - val_fp: 989.0000 - val_tn: 44498.0000 - val_fn: 8.0000 - val_accuracy: 0.9781 - val_precision: 0.0696 - val_recall: 0.9024 - val_auc: 0.9772
Epoch 27/1000
20/20 [==============================] - 1s 38ms/step - loss: 0.1890 - tp: 18580.0000 - fp: 990.0000 - tn: 19537.0000 - fn: 1853.0000 - accuracy: 0.9306 - precision: 0.9494 - recall: 0.9093 - auc: 0.9760 - val_loss: 0.1367 - val_tp: 74.0000 - val_fp: 976.0000 - val_tn: 44511.0000 - val_fn: 8.0000 - val_accuracy: 0.9784 - val_precision: 0.0705 - val_recall: 0.9024 - val_auc: 0.9773
Epoch 28/1000
20/20 [==============================] - 1s 33ms/step - loss: 0.1835 - tp: 18707.0000 - fp: 921.0000 - tn: 19609.0000 - fn: 1723.0000 - accuracy: 0.9354 - precision: 0.9531 - recall: 0.9157 - auc: 0.9777 - val_loss: 0.1316 - val_tp: 74.0000 - val_fp: 967.0000 - val_tn: 44520.0000 - val_fn: 8.0000 - val_accuracy: 0.9786 - val_precision: 0.0711 - val_recall: 0.9024 - val_auc: 0.9774
Epoch 29/1000
20/20 [==============================] - 1s 34ms/step - loss: 0.1820 - tp: 18835.0000 - fp: 862.0000 - tn: 19527.0000 - fn: 1736.0000 - accuracy: 0.9366 - precision: 0.9562 - recall: 0.9156 - auc: 0.9776 - val_loss: 0.1272 - val_tp: 74.0000 - val_fp: 936.0000 - val_tn: 44551.0000 - val_fn: 8.0000 - val_accuracy: 0.9793 - val_precision: 0.0733 - val_recall: 0.9024 - val_auc: 0.9774
Epoch 30/1000
20/20 [==============================] - 1s 35ms/step - loss: 0.1764 - tp: 18796.0000 - fp: 844.0000 - tn: 19677.0000 - fn: 1643.0000 - accuracy: 0.9393 - precision: 0.9570 - recall: 0.9196 - auc: 0.9791 - val_loss: 0.1234 - val_tp: 74.0000 - val_fp: 931.0000 - val_tn: 44556.0000 - val_fn: 8.0000 - val_accuracy: 0.9794 - val_precision: 0.0736 - val_recall: 0.9024 - val_auc: 0.9769
Epoch 31/1000
20/20 [==============================] - 1s 33ms/step - loss: 0.1727 - tp: 18808.0000 - fp: 793.0000 - tn: 19706.0000 - fn: 1653.0000 - accuracy: 0.9403 - precision: 0.9595 - recall: 0.9192 - auc: 0.9800 - val_loss: 0.1200 - val_tp: 74.0000 - val_fp: 909.0000 - val_tn: 44578.0000 - val_fn: 8.0000 - val_accuracy: 0.9799 - val_precision: 0.0753 - val_recall: 0.9024 - val_auc: 0.9773
Epoch 32/1000
20/20 [==============================] - 1s 32ms/step - loss: 0.1704 - tp: 18934.0000 - fp: 824.0000 - tn: 19610.0000 - fn: 1592.0000 - accuracy: 0.9410 - precision: 0.9583 - recall: 0.9224 - auc: 0.9807 - val_loss: 0.1171 - val_tp: 74.0000 - val_fp: 907.0000 - val_tn: 44580.0000 - val_fn: 8.0000 - val_accuracy: 0.9799 - val_precision: 0.0754 - val_recall: 0.9024 - val_auc: 0.9772
Epoch 33/1000
18/20 [==========================>...] - ETA: 0s - loss: 0.1659 - tp: 17032.0000 - fp: 717.0000 - tn: 17676.0000 - fn: 1439.0000 - accuracy: 0.9415 - precision: 0.9596 - recall: 0.9221 - auc: 0.9817Restoring model weights from the end of the best epoch.
20/20 [==============================] - 1s 34ms/step - loss: 0.1654 - tp: 18936.0000 - fp: 788.0000 - tn: 19642.0000 - fn: 1594.0000 - accuracy: 0.9418 - precision: 0.9600 - recall: 0.9224 - auc: 0.9818 - val_loss: 0.1142 - val_tp: 74.0000 - val_fp: 908.0000 - val_tn: 44579.0000 - val_fn: 8.0000 - val_accuracy: 0.9799 - val_precision: 0.0754 - val_recall: 0.9024 - val_auc: 0.9771
Epoch 00033: early stopping

 

訓練履歴を再確認する

plot_metrics(resampled_history)

 

メトリクスを評価する

train_predictions_resampled = resampled_model.predict(train_features, batch_size=BATCH_SIZE)
test_predictions_resampled = resampled_model.predict(test_features, batch_size=BATCH_SIZE)
resampled_results = resampled_model.evaluate(test_features, test_labels,
                                             batch_size=BATCH_SIZE, verbose=0)
for name, value in zip(resampled_model.metrics_names, resampled_results):
  print(name, ': ', value)
print()

plot_cm(test_labels, test_predictions_weighted)
loss :  0.1573299011001384
tp :  92.0
fp :  1193.0
tn :  55664.0
fn :  13.0
accuracy :  0.978828
precision :  0.07159533
recall :  0.8761905
auc :  0.96230674

Legitimate Transactions Detected (True Negatives):  55920
Legitimate Transactions Incorrectly Detected (False Positives):  937
Fraudulent Transactions Missed (False Negatives):  13
Fraudulent Transactions Detected (True Positives):  92
Total Fraudulent Transactions:  105

 

ROC をプロットする

plot_roc("Train Baseline", train_labels, train_predictions_baseline, color=colors[0])
plot_roc("Test Baseline", test_labels, test_predictions_baseline, color=colors[0], linestyle='--')

plot_roc("Train Weighted", train_labels, train_predictions_weighted, color=colors[1])
plot_roc("Test Weighted", test_labels, test_predictions_weighted, color=colors[1], linestyle='--')

plot_roc("Train Resampled", train_labels, train_predictions_resampled,  color=colors[2])
plot_roc("Test Resampled", test_labels, test_predictions_resampled,  color=colors[2], linestyle='--')
plt.legend(loc='lower right')
<matplotlib.legend.Legend at 0x7fc67a751588>

 

このチュートリアルを貴方の問題に適用する

不均衡なデータ分類は本質的に困難なタスクです、何故ならばそこから学習する非常に少ないサンプルがあるからです。貴方は常に最初にデータから開始してできる限り多くのサンプルを収集することに最善をつくしてそしてどの特徴が関連するかもしれないのかをしっかりと念頭におくべきです、そうすればモデルは貴方の最小クラスを最大限に活用できます。ある点で貴方のモデルは改良されて望む結果を生成するために努力するかもしれません、そして貴方の問題のコンテキストとエラーの異なるタイプの間のトレードオフを念頭に置くことは重要です。

 

以上






TensorFlow 2.0 : 上級 Tutorials : 構造化データ :- 特徴カラムで構造化データを分類する

TensorFlow 2.0 : 上級 Tutorials : 構造化データ :- 特徴カラムで構造化データを分類する (翻訳/解説)

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

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

* サンプルコードの動作確認はしておりますが、必要な場合には適宜、追加改変しています。
* ご自由にリンクを張って頂いてかまいませんが、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/

 

構造化データ :- 特徴カラムで構造化データを分類する

このチュートリアルは構造化データ (e.g. CSV の表形式データ) をどのように分類するかを実演します。モデルを定義するために Keras を、そして CSV のカラムからモデルを訓練するために使用される特徴へマップするためのブリッジとして feature columns を使用します。このチュートリアルは以下を行なうための完全なコードを含みます :

  • Pandas を使用して CSV ファイルをロードする。
  • tf.data を使用して行をバッチ処理してシャッフルするために入力パイプラインを構築する。
  • feature columns を使用して CSV のカラムからモデルを訓練するために使用される特徴にマップする。
  • Keras を使用して、モデルを構築、訓練そして評価する。

 

データセット

Cleveland Clinic Foundation for Heart Disease により提供される小さい データセット を使用します。CSV には数百行あります。各行は患者を表し、各カラム (列) は属性を表します。患者が心臓疾患を持つか否かを予測するためにこの情報を使用します、これはこのデータセットにおける二値分類タスクです。

次はこのデータセットの 記述 です。numeric と categorical カラムの両者があることが分かるでしょう。

カラム 説明 特徴型 データ型
Age 年齢 Numerical integer
Sex (1 = male; 0 = female) Categorical integer
CP 胸の痛みのタイプ (0, 1, 2, 3, 4) Categorical integer
Trestbpd 安静時血圧 (in mm Hg 入院時) Numerical integer
Chol 血清コレステロール in mg/dl Numerical integer
FBS (空腹時血糖値 > 120 mg/dl) (1 = true; 0 = false) Categorical integer
RestECG 安静時心電図結果 (0, 1, 2) Categorical integer
Thalach 得られた最大心拍数 Numerical integer
Exang 労作性狭心症 (1 = yes; 0 = no) Categorical integer
Oldpeak ST 低下 induced by exercise relative to rest Numerical integer
Slope The slope of 最大運動時 ST 部分 Numerical float
CA 透視法により色付けられた主要管の数 (0-3) Numerical integer
Thal 3 = normal; 6 = fixed defect; 7 = reversible defect Categorical string
Target 心臓疾患の診断 (1 = true; 0 = false) Classification integer

 

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

!pip install -q sklearn
from __future__ import absolute_import, division, print_function, unicode_literals

import numpy as np
import pandas as pd

import tensorflow as tf

from tensorflow import feature_column
from tensorflow.keras import layers
from sklearn.model_selection import train_test_split

 

dataframe を作成するために Pandas を使用する

Pandas はロードや構造化データで作業するための多くの役立つユティリティを持つ Python ライブラリです。URL からデータセットをダウンロードしてそれを dataframe にロードするために Pandas を使用します。

URL = 'https://storage.googleapis.com/applied-dl/heart.csv'
dataframe = pd.read_csv(URL)
dataframe.head()

age sex cp trestbps chol fbs restecg thalach exang oldpeak slope ca thal target
0 63 1 1 145 233 1 2 150 0 2.3 3 0 fixed 0
1 67 1 4 160 286 0 2 108 1 1.5 2 3 normal 1
2 67 1 4 120 229 0 2 129 1 2.6 2 2 reversible 0
3 37 1 3 130 250 0 0 187 0 3.5 3 0 normal 0
4 41 0 2 130 204 0 2 172 0 1.4 1 0 normal 0

 

dataframe を訓練、検証とテストに分割する

ダウンロードしたデータセットは単一の CSV ファイルでした。これを訓練、検証とテストセットに分割します。

train, test = train_test_split(dataframe, test_size=0.2)
train, val = train_test_split(train, test_size=0.2)
print(len(train), 'train examples')
print(len(val), 'validation examples')
print(len(test), 'test examples')
193 train examples
49 validation examples
61 test examples

 

tf.data を使用して入力パイプラインを作成する

次に、tf.data で dataframe をラップします。これは Pandas dataframe のカラムからモデルを訓練するために使用される features へマップするブリッジとして feature columns を使用することを可能にします。 (メモリに収まらないほどに) 非常に巨大な CSV ファイルで作業していたとしても、それをディスクから直接読むために tf.data を使用するでしょう。それはこのチュートリアルではカバーされません。

# A utility method to create a tf.data dataset from a Pandas Dataframe
def df_to_dataset(dataframe, shuffle=True, batch_size=32):
  dataframe = dataframe.copy()
  labels = dataframe.pop('target')
  ds = tf.data.Dataset.from_tensor_slices((dict(dataframe), labels))
  if shuffle:
    ds = ds.shuffle(buffer_size=len(dataframe))
  ds = ds.batch(batch_size)
  return ds
batch_size = 5 # A small batch sized is used for demonstration purposes
train_ds = df_to_dataset(train, batch_size=batch_size)
val_ds = df_to_dataset(val, shuffle=False, batch_size=batch_size)
test_ds = df_to_dataset(test, shuffle=False, batch_size=batch_size)

 

入力パイプラインを理解する

入力パイプラインを作成した今、それが返すデータのフォーマットを見るためにそれを呼び出しましょう。出力を可読に維持するために小さいバッチサイズを使用しました。

for feature_batch, label_batch in train_ds.take(1):
  print('Every feature:', list(feature_batch.keys()))
  print('A batch of ages:', feature_batch['age'])
  print('A batch of targets:', label_batch )
Every feature: ['restecg', 'trestbps', 'exang', 'thalach', 'slope', 'chol', 'oldpeak', 'ca', 'cp', 'sex', 'fbs', 'age', 'thal']
A batch of ages: tf.Tensor([60 53 69 54 54], shape=(5,), dtype=int32)
A batch of targets: tf.Tensor([0 0 0 0 1], shape=(5,), dtype=int32)

dataset が (dataframe からの) カラム名の辞書を返すことを見ることができます、それは dataframe の行からカラム値へマップします。

 

feature column の幾つかのタイプを実演する

TensorFlow は feature columns の多くのタイプを提供します。このセクションでは、feature column の幾つかのタイプを作成して、それらが dataframe からのカラムをどのように変換するかを実演します。

# We will use this batch to demonstrate several types of feature columns
example_batch = next(iter(train_ds))[0]
# A utility method to create a feature column
# and to transform a batch of data
def demo(feature_column):
  feature_layer = layers.DenseFeatures(feature_column)
  print(feature_layer(example_batch).numpy())

 

数値 (= Numeric) カラム

feature column の出力はモデルへの入力になります (上で定義された demo 関数を使用して、dataframe からの各カラムがどのように変換されるかを正確に見ることができます)。数値カラム はカラムの最も単純なタイプです。それは実数値の特徴を表わすために使用されます。このカラムを使用するとき、貴方のモデルは (不変の) dataframe からカラム値を受け取ります。

age = feature_column.numeric_column("age")
demo(age)
WARNING:tensorflow:Layer dense_features is casting an input tensor from dtype float64 to the layer's dtype of float32, which is new behavior in TensorFlow 2.  The layer has dtype float32 because it's dtype defaults to floatx.

If you intended to run this layer in float32, you can safely ignore this warning. If in doubt, this warning is likely only an issue if you are porting a TensorFlow 1.X model to TensorFlow 2.

To change all layers to have dtype float64 by default, call `tf.keras.backend.set_floatx('float64')`. To change just this layer, pass dtype='float64' to the layer constructor. If you are the author of this layer, you can disable autocasting by passing autocast=False to the base Layer constructor.

[[47.]
 [56.]
 [52.]
 [63.]
 [57.]]

心臓疾患データセットでは、dataframe からの殆どのカラムは numeric です。

 

Bucketized カラム

しばしば、数値を直接モデルに供給することを望みません、しかし代わりにその値を数値の範囲に基づく異なるカテゴリーに分割します。人の年齢を表わす生データを考えます。年齢を数値カラムとして表わす代わりに、bucketized カラム を使用して年齢を幾つかのバケツに分割できるでしょう。下の one-hot 値が各行がどの年齢範囲に一致するかを記述していることが分かるでしょう。

age_buckets = feature_column.bucketized_column(age, boundaries=[18, 25, 30, 35, 40, 45, 50, 55, 60, 65])
demo(age_buckets)
WARNING:tensorflow:Layer dense_features_1 is casting an input tensor from dtype float64 to the layer's dtype of float32, which is new behavior in TensorFlow 2.  The layer has dtype float32 because it's dtype defaults to floatx.

If you intended to run this layer in float32, you can safely ignore this warning. If in doubt, this warning is likely only an issue if you are porting a TensorFlow 1.X model to TensorFlow 2.

To change all layers to have dtype float64 by default, call `tf.keras.backend.set_floatx('float64')`. To change just this layer, pass dtype='float64' to the layer constructor. If you are the author of this layer, you can disable autocasting by passing autocast=False to the base Layer constructor.

[[0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0.]
 [0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0.]
 [0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0.]]

 

Categorical カラム

このデータセットでは、thal は文字列 (e.g. ‘fixed’, ‘normal’ または ‘reversible’) として表わされます。文字列をモデルに直接には供給できません。代わりに、最初にそれらを数値にマップしなければなりません。categorical 語彙カラムは文字列を one-hot ベクトルとして表わす方法を提供します (上で見た年齢バケツに良く似ています)。語彙は categorical_column_with_vocabulary_list を使用してリストとして渡したり、categorical_column_with_vocabulary_file を使用してファイルからロードすることができます。

thal = feature_column.categorical_column_with_vocabulary_list(
      'thal', ['fixed', 'normal', 'reversible'])

thal_one_hot = feature_column.indicator_column(thal)
demo(thal_one_hot)
WARNING:tensorflow:Layer dense_features_2 is casting an input tensor from dtype float64 to the layer's dtype of float32, which is new behavior in TensorFlow 2.  The layer has dtype float32 because it's dtype defaults to floatx.

If you intended to run this layer in float32, you can safely ignore this warning. If in doubt, this warning is likely only an issue if you are porting a TensorFlow 1.X model to TensorFlow 2.

To change all layers to have dtype float64 by default, call `tf.keras.backend.set_floatx('float64')`. To change just this layer, pass dtype='float64' to the layer constructor. If you are the author of this layer, you can disable autocasting by passing autocast=False to the base Layer constructor.

WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.5/site-packages/tensorflow_core/python/feature_column/feature_column_v2.py:4276: IndicatorColumn._variable_shape (from tensorflow.python.feature_column.feature_column_v2) is deprecated and will be removed in a future version.
Instructions for updating:
The old _FeatureColumn APIs are being deprecated. Please use the new FeatureColumn APIs instead.
WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.5/site-packages/tensorflow_core/python/feature_column/feature_column_v2.py:4331: VocabularyListCategoricalColumn._num_buckets (from tensorflow.python.feature_column.feature_column_v2) is deprecated and will be removed in a future version.
Instructions for updating:
The old _FeatureColumn APIs are being deprecated. Please use the new FeatureColumn APIs instead.
[[0. 1. 0.]
 [0. 1. 0.]
 [0. 1. 0.]
 [0. 1. 0.]
 [0. 0. 1.]]

より複雑なデータセットでは、多くのカラムが categorical (e.g. 文字列) でしょう。Feature columns は categorical データと作業するときにもっとも役立ちます。このデータセットには一つの categorical カラムがあるだけですが、他のデータセットで作業するときに使用できる feature columns の幾つかの重要なタイプを実演するためにそれを使用します。

 

埋め込みカラム

ごく僅かの可能な文字列を持つ代わりに、カテゴリー毎に数千 (or それ以上) の値を持つことを仮定します。幾つかの理由で、カテゴリー数が巨大になるにつれて、one-hot エンコーディングを使用してニューラルネットワークを訓練することは実行不可能になります。この制限を打開するために埋め込みカラムを使用することができます。データを多くの次元の one-hot ベクトルとして表わす代わりに、埋め込みカラム はそのデータをより低次元な、密ベクトルとして表します、そこでは各セルは (単に 0 か 1 ではなく) 任意の数字を含むことができます。埋め込みのサイズ (下の例では 8) は調整しなければならないパラメータです。

Key point: categorical カラムが多くの可能な値を持つとき埋め込みカラムを使用することが最善です。ここでは実演目的で一つを使用していますので、将来的に異なるデータセットのために変更可能な完全なサンプルを貴方は持つことになります。

# Notice the input to the embedding column is the categorical column
# we previously created
thal_embedding = feature_column.embedding_column(thal, dimension=8)
demo(thal_embedding)
WARNING:tensorflow:Layer dense_features_3 is casting an input tensor from dtype float64 to the layer's dtype of float32, which is new behavior in TensorFlow 2.  The layer has dtype float32 because it's dtype defaults to floatx.

If you intended to run this layer in float32, you can safely ignore this warning. If in doubt, this warning is likely only an issue if you are porting a TensorFlow 1.X model to TensorFlow 2.

To change all layers to have dtype float64 by default, call `tf.keras.backend.set_floatx('float64')`. To change just this layer, pass dtype='float64' to the layer constructor. If you are the author of this layer, you can disable autocasting by passing autocast=False to the base Layer constructor.

[[ 0.17819044  0.16483337  0.24669515  0.21694453  0.3330087   0.4549101
   0.07683729 -0.04714879]
 [ 0.17819044  0.16483337  0.24669515  0.21694453  0.3330087   0.4549101
   0.07683729 -0.04714879]
 [ 0.17819044  0.16483337  0.24669515  0.21694453  0.3330087   0.4549101
   0.07683729 -0.04714879]
 [ 0.17819044  0.16483337  0.24669515  0.21694453  0.3330087   0.4549101
   0.07683729 -0.04714879]
 [-0.44885436  0.53802776 -0.10125531  0.2730379   0.3384754   0.6002057
   0.11581234 -0.21320769]]

 

Hashed 特徴カラム

非常に多数の値を持つ categorical column を表わすもう一つの方法は categorical_column_with_hash_bucket を使用することです。この feature column は入力のハッシュ値を計算し、それから文字列をエンコードするために hash_bucket_size バケツの一つを選択します。このカラムを使用するとき、語彙を提供する必要はなく、そして容量をセーブするために実際のカテゴリの数よりも hash_buckets の数をかなり小さくすることを選択できます。

Key point: このテクニックの重要な不都合な点は異なる文字列が同じバケツにマップされる衝突があるかもしれないことです。実際には、これは幾つかのデータセットに対して関係なく上手く動作します。

thal_hashed = feature_column.categorical_column_with_hash_bucket(
      'thal', hash_bucket_size=1000)
demo(feature_column.indicator_column(thal_hashed))
WARNING:tensorflow:Layer dense_features_4 is casting an input tensor from dtype float64 to the layer's dtype of float32, which is new behavior in TensorFlow 2.  The layer has dtype float32 because it's dtype defaults to floatx.

If you intended to run this layer in float32, you can safely ignore this warning. If in doubt, this warning is likely only an issue if you are porting a TensorFlow 1.X model to TensorFlow 2.

To change all layers to have dtype float64 by default, call `tf.keras.backend.set_floatx('float64')`. To change just this layer, pass dtype='float64' to the layer constructor. If you are the author of this layer, you can disable autocasting by passing autocast=False to the base Layer constructor.

WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.5/site-packages/tensorflow_core/python/feature_column/feature_column_v2.py:4331: HashedCategoricalColumn._num_buckets (from tensorflow.python.feature_column.feature_column_v2) is deprecated and will be removed in a future version.
Instructions for updating:
The old _FeatureColumn APIs are being deprecated. Please use the new FeatureColumn APIs instead.
[[0. 0. 0. ... 0. 0. 0.]
 [0. 0. 0. ... 0. 0. 0.]
 [0. 0. 0. ... 0. 0. 0.]
 [0. 0. 0. ... 0. 0. 0.]
 [0. 0. 0. ... 0. 0. 0.]]

 

Crossed 特徴カラム

特徴交差 (= feature cross) として良く知られる、特徴 (群) を単一の特徴に結合することはモデルに特徴の各組み合わせについて別個の重みを学習することを可能にします。ここで、age と thal の交差である新しい特徴を作成します。crossed_column は総ての可能な組み合わせの完全なテーブルを構築しないことに注意してください (それは非常に巨大でしょう)。代わりに、それは hashed_column により支援されますので、テーブルがどのくらい巨大であるかを選択できます。

crossed_feature = feature_column.crossed_column([age_buckets, thal], hash_bucket_size=1000)
demo(feature_column.indicator_column(crossed_feature))
WARNING:tensorflow:Layer dense_features_5 is casting an input tensor from dtype float64 to the layer's dtype of float32, which is new behavior in TensorFlow 2.  The layer has dtype float32 because it's dtype defaults to floatx.

If you intended to run this layer in float32, you can safely ignore this warning. If in doubt, this warning is likely only an issue if you are porting a TensorFlow 1.X model to TensorFlow 2.

To change all layers to have dtype float64 by default, call `tf.keras.backend.set_floatx('float64')`. To change just this layer, pass dtype='float64' to the layer constructor. If you are the author of this layer, you can disable autocasting by passing autocast=False to the base Layer constructor.

WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.5/site-packages/tensorflow_core/python/feature_column/feature_column_v2.py:4331: CrossedColumn._num_buckets (from tensorflow.python.feature_column.feature_column_v2) is deprecated and will be removed in a future version.
Instructions for updating:
The old _FeatureColumn APIs are being deprecated. Please use the new FeatureColumn APIs instead.
[[0. 0. 0. ... 0. 0. 0.]
 [0. 0. 0. ... 0. 0. 0.]
 [0. 0. 0. ... 0. 0. 0.]
 [0. 0. 0. ... 0. 0. 0.]
 [0. 0. 0. ... 0. 0. 0.]]

 

どのカラムを使用するか選択する

feature columns の幾つかのタイプをどのように使用するか見てきました。今はそれらをモデルを訓練するために使用します。このチュートリアルのゴールは feature column で作業するために必要な完全なコード (e.g. mechanics) を示すことです。下のモデルを訓練するために幾つかのカラムを恣意的に選択しました。

Key point: 貴方の目的が正確なモデルを構築することであれば、貴方自身のより大きなデータセット試してください、そしてどの特徴が含めるために最も意味があるか、そしてそれらがどのように表わされるべきかを注意深く考えてください。

feature_columns = []

# numeric cols
for header in ['age', 'trestbps', 'chol', 'thalach', 'oldpeak', 'slope', 'ca']:
  feature_columns.append(feature_column.numeric_column(header))

# bucketized cols
age_buckets = feature_column.bucketized_column(age, boundaries=[18, 25, 30, 35, 40, 45, 50, 55, 60, 65])
feature_columns.append(age_buckets)

# indicator cols
thal = feature_column.categorical_column_with_vocabulary_list(
      'thal', ['fixed', 'normal', 'reversible'])
thal_one_hot = feature_column.indicator_column(thal)
feature_columns.append(thal_one_hot)

# embedding cols
thal_embedding = feature_column.embedding_column(thal, dimension=8)
feature_columns.append(thal_embedding)

# crossed cols
crossed_feature = feature_column.crossed_column([age_buckets, thal], hash_bucket_size=1000)
crossed_feature = feature_column.indicator_column(crossed_feature)
feature_columns.append(crossed_feature)

 

feature 層を作成する

私達の feature columns を定義した今、それらを Keras モデルへ入力するために DenseFeatures 層を使用します。

feature_layer = tf.keras.layers.DenseFeatures(feature_columns)

先程は、feature columns がどのように動作したかを実演するために小さいバッチサイズを使用しました。より大きなバッチサイズを持つ新しい入力パイプラインを作成します。

batch_size = 32
train_ds = df_to_dataset(train, batch_size=batch_size)
val_ds = df_to_dataset(val, shuffle=False, batch_size=batch_size)
test_ds = df_to_dataset(test, shuffle=False, batch_size=batch_size)

 

モデルを作成、コンパイルそして訓練する

model = tf.keras.Sequential([
  feature_layer,
  layers.Dense(128, activation='relu'),
  layers.Dense(128, activation='relu'),
  layers.Dense(1, activation='sigmoid')
])

model.compile(optimizer='adam',
              loss='binary_crossentropy',
              metrics=['accuracy'])

model.fit(train_ds,
          validation_data=val_ds,
          epochs=5)
WARNING:tensorflow:Layer sequential is casting an input tensor from dtype float64 to the layer's dtype of float32, which is new behavior in TensorFlow 2.  The layer has dtype float32 because it's dtype defaults to floatx.

If you intended to run this layer in float32, you can safely ignore this warning. If in doubt, this warning is likely only an issue if you are porting a TensorFlow 1.X model to TensorFlow 2.

To change all layers to have dtype float64 by default, call `tf.keras.backend.set_floatx('float64')`. To change just this layer, pass dtype='float64' to the layer constructor. If you are the author of this layer, you can disable autocasting by passing autocast=False to the base Layer constructor.

Epoch 1/5
7/7 [==============================] - 2s 307ms/step - loss: 3.6217 - accuracy: 0.6062 - val_loss: 0.0000e+00 - val_accuracy: 0.0000e+00
Epoch 2/5
7/7 [==============================] - 0s 11ms/step - loss: 2.2438 - accuracy: 0.6373 - val_loss: 1.1479 - val_accuracy: 0.2857
Epoch 3/5
7/7 [==============================] - 0s 10ms/step - loss: 1.1346 - accuracy: 0.6166 - val_loss: 0.7250 - val_accuracy: 0.7143
Epoch 4/5
7/7 [==============================] - 0s 10ms/step - loss: 1.0081 - accuracy: 0.4611 - val_loss: 1.2260 - val_accuracy: 0.7143
Epoch 5/5
7/7 [==============================] - 0s 10ms/step - loss: 1.1604 - accuracy: 0.7150 - val_loss: 0.6827 - val_accuracy: 0.7143

<tensorflow.python.keras.callbacks.History at 0x7f39b05c04e0>
loss, accuracy = model.evaluate(test_ds)
print("Accuracy", accuracy)
2/2 [==============================] - 0s 5ms/step - loss: 0.4188 - accuracy: 0.7705
Accuracy 0.7704918

 
Key point: 典型的には遥かに巨大でより複雑なデータセットを伴う深層学習で最善の結果を見るでしょう。この一つのように小さいデータセットで作業するときは、決定木やランダムフォレストを強力なベースラインとして使用することを推奨します。このチュートリアルのゴールは正確なモデルを訓練することではなく、構造化データで作業するメカニクスを実演することで、将来的に貴方自身のデータセットで作業するときの開始点として使用するコードを貴方は持っています。

 

以上



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