このページは Cloud Translation API によって翻訳されました。
Switch to English

最初からトレーニングループを作成する

TensorFlow.orgで見る Google Colabで実行 GitHubでソースを表示 ノートブックをダウンロード

セットアップ

 import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
import numpy as np
 

前書き

Kerasは、デフォルトのトレーニングおよび評価ループであるfit()およびevaluate()提供します。それらの使用法は、組み込みのメソッドを使用したトレーニングと評価のガイドで説明されています

fit()利便性を活用しながらモデルの学習アルゴリズムをカスタマイズする場合(たとえば、 fit()を使用してGANをトレーニングする場合)、 Modelクラスをサブクラス化し、独自のtrain_step()メソッドを実装できます。 fit()実行中fit()繰り返し呼び出されます。これについては、 fit()で行われる処理のカスタマイズガイドで説明しています。

ここで、トレーニングと評価を非常に低レベルで制御したい場合は、独自のトレーニングと評価ループを最初から作成する必要があります。これがこのガイドの目的です。

GradientTape使用:最初のエンドツーエンドの例

GradientTapeスコープ内でモデルを呼び出すと、損失値に対するレイヤーのトレーニング可能な重みの勾配を取得できます。オプティマイザーインスタンスを使用すると、これらの勾配を使用してこれらの変数を更新できます( model.trainable_weightsを使用して取得できます)。

単純なMNISTモデルを考えてみましょう:

 inputs = keras.Input(shape=(784,), name="digits")
x1 = layers.Dense(64, activation="relu")(inputs)
x2 = layers.Dense(64, activation="relu")(x1)
outputs = layers.Dense(10, name="predictions")(x2)
model = keras.Model(inputs=inputs, outputs=outputs)
 

カスタムトレーニングループでミニバッチグラデーションを使用してトレーニングします。

まず、オプティマイザ、損失関数、およびデータセットが必要です。

 # Instantiate an optimizer.
optimizer = keras.optimizers.SGD(learning_rate=1e-3)
# Instantiate a loss function.
loss_fn = keras.losses.SparseCategoricalCrossentropy(from_logits=True)

# Prepare the training dataset.
batch_size = 64
(x_train, y_train), (x_test, y_test) = keras.datasets.mnist.load_data()
x_train = np.reshape(x_train, (-1, 784))
x_test = np.reshape(x_test, (-1, 784))
train_dataset = tf.data.Dataset.from_tensor_slices((x_train, y_train))
train_dataset = train_dataset.shuffle(buffer_size=1024).batch(batch_size)
 

これが私たちのトレーニングループです:

  • エポックを反復するforループを開きます
  • エポックごとに、バッチでデータセットを反復するforループを開きます
  • バッチごとに、 GradientTape()スコープを開きます
  • このスコープ内で、モデル(フォワードパス)を呼び出し、損失を計算します
  • スコープの外で、損失に関するモデルの重みの勾配を取得します
  • 最後に、オプティマイザを使用して、勾配に基づいてモデルの重みを更新します
 epochs = 2
for epoch in range(epochs):
    print("\nStart of epoch %d" % (epoch,))

    # Iterate over the batches of the dataset.
    for step, (x_batch_train, y_batch_train) in enumerate(train_dataset):

        # Open a GradientTape to record the operations run
        # during the forward pass, which enables autodifferentiation.
        with tf.GradientTape() as tape:

            # Run the forward pass of the layer.
            # The operations that the layer applies
            # to its inputs are going to be recorded
            # on the GradientTape.
            logits = model(x_batch_train, training=True)  # Logits for this minibatch

            # Compute the loss value for this minibatch.
            loss_value = loss_fn(y_batch_train, logits)

        # Use the gradient tape to automatically retrieve
        # the gradients of the trainable variables with respect to the loss.
        grads = tape.gradient(loss_value, model.trainable_weights)

        # Run one step of gradient descent by updating
        # the value of the variables to minimize the loss.
        optimizer.apply_gradients(zip(grads, model.trainable_weights))

        # Log every 200 batches.
        if step % 200 == 0:
            print(
                "Training loss (for one batch) at step %d: %.4f"
                % (step, float(loss_value))
            )
            print("Seen so far: %s samples" % ((step + 1) * 64))
 

Start of epoch 0
Training loss (for one batch) at step 0: 92.5677
Seen so far: 64 samples
Training loss (for one batch) at step 200: 1.9201
Seen so far: 12864 samples
Training loss (for one batch) at step 400: 0.7029
Seen so far: 25664 samples
Training loss (for one batch) at step 600: 1.0511
Seen so far: 38464 samples
Training loss (for one batch) at step 800: 0.5134
Seen so far: 51264 samples

Start of epoch 1
Training loss (for one batch) at step 0: 0.4872
Seen so far: 64 samples
Training loss (for one batch) at step 200: 0.5805
Seen so far: 12864 samples
Training loss (for one batch) at step 400: 0.6873
Seen so far: 25664 samples
Training loss (for one batch) at step 600: 0.5880
Seen so far: 38464 samples
Training loss (for one batch) at step 800: 0.3768
Seen so far: 51264 samples

メトリックの低レベルの処理

この基本ループにメトリックの監視を追加しましょう。

ゼロから作成されたトレーニングループでは、組み込みメトリック(またはユーザーが作成したカスタムメトリック)をすぐに再利用できます。フローは次のとおりです。

  • ループの開始時にメトリックをインスタンス化します
  • 各バッチの後にmetric.update_state()呼び出しmetric.update_state()
  • メトリックの現在の値を表示する必要がある場合は、 metric.result()呼び出します
  • メトリックの状態をクリアする必要がある場合(通常はエポックの最後metric.reset_states()metric.reset_states()呼び出します。

この知識を使用して、各エポックの最後に検証データのSparseCategoricalAccuracyを計算してみましょう。

 # Get model
inputs = keras.Input(shape=(784,), name="digits")
x = layers.Dense(64, activation="relu", name="dense_1")(inputs)
x = layers.Dense(64, activation="relu", name="dense_2")(x)
outputs = layers.Dense(10, name="predictions")(x)
model = keras.Model(inputs=inputs, outputs=outputs)

# Instantiate an optimizer to train the model.
optimizer = keras.optimizers.SGD(learning_rate=1e-3)
# Instantiate a loss function.
loss_fn = keras.losses.SparseCategoricalCrossentropy(from_logits=True)

# Prepare the metrics.
train_acc_metric = keras.metrics.SparseCategoricalAccuracy()
val_acc_metric = keras.metrics.SparseCategoricalAccuracy()

# Prepare the training dataset.
batch_size = 64
train_dataset = tf.data.Dataset.from_tensor_slices((x_train, y_train))
train_dataset = train_dataset.shuffle(buffer_size=1024).batch(batch_size)

# Prepare the validation dataset.
# Reserve 10,000 samples for validation.
x_val = x_train[-10000:]
y_val = y_train[-10000:]
x_train = x_train[:-10000]
y_train = y_train[:-10000]
val_dataset = tf.data.Dataset.from_tensor_slices((x_val, y_val))
val_dataset = val_dataset.batch(64)
 

トレーニングと評価のループは次のとおりです。

 import time

epochs = 2
for epoch in range(epochs):
    print("\nStart of epoch %d" % (epoch,))
    start_time = time.time()

    # Iterate over the batches of the dataset.
    for step, (x_batch_train, y_batch_train) in enumerate(train_dataset):
        with tf.GradientTape() as tape:
            logits = model(x_batch_train, training=True)
            loss_value = loss_fn(y_batch_train, logits)
        grads = tape.gradient(loss_value, model.trainable_weights)
        optimizer.apply_gradients(zip(grads, model.trainable_weights))

        # Update training metric.
        train_acc_metric.update_state(y_batch_train, logits)

        # Log every 200 batches.
        if step % 200 == 0:
            print(
                "Training loss (for one batch) at step %d: %.4f"
                % (step, float(loss_value))
            )
            print("Seen so far: %d samples" % ((step + 1) * 64))

    # Display metrics at the end of each epoch.
    train_acc = train_acc_metric.result()
    print("Training acc over epoch: %.4f" % (float(train_acc),))

    # Reset training metrics at the end of each epoch
    train_acc_metric.reset_states()

    # Run a validation loop at the end of each epoch.
    for x_batch_val, y_batch_val in val_dataset:
        val_logits = model(x_batch_val, training=False)
        # Update val metrics
        val_acc_metric.update_state(y_batch_val, val_logits)
    val_acc = val_acc_metric.result()
    val_acc_metric.reset_states()
    print("Validation acc: %.4f" % (float(val_acc),))
    print("Time taken: %.2fs" % (time.time() - start_time))
 

Start of epoch 0
Training loss (for one batch) at step 0: 86.8484
Seen so far: 64 samples
Training loss (for one batch) at step 200: 1.5473
Seen so far: 12864 samples
Training loss (for one batch) at step 400: 1.1590
Seen so far: 25664 samples
Training loss (for one batch) at step 600: 0.9719
Seen so far: 38464 samples
Training loss (for one batch) at step 800: 0.9732
Seen so far: 51264 samples
Training acc over epoch: 0.6902
Validation acc: 0.8232
Time taken: 5.97s

Start of epoch 1
Training loss (for one batch) at step 0: 0.7543
Seen so far: 64 samples
Training loss (for one batch) at step 200: 1.0089
Seen so far: 12864 samples
Training loss (for one batch) at step 400: 0.3905
Seen so far: 25664 samples
Training loss (for one batch) at step 600: 0.4995
Seen so far: 38464 samples
Training loss (for one batch) at step 800: 0.4404
Seen so far: 51264 samples
Training acc over epoch: 0.8263
Validation acc: 0.8719
Time taken: 5.76s

tf.functionしてトレーニングステップをtf.function

TensorFlow 2.0のデフォルトのランタイムは熱心な実行です。そのため、上記のトレーニングループは熱心に実行されます。

これはデバッグに最適ですが、グラフのコンパイルには明確なパフォーマンス上の利点があります。計算を静的グラフとして記述すると、フレームワークでグローバルパフォーマンス最適化を適用できます。これは、フレームワークが次の操作を知らないまま、次の操作を貪欲に実行するように制約されている場合は不可能です。

テンソルを入力として受け取る関数を静的グラフにコンパイルできます。次のように、 @tf.functionデコレータを追加するだけです。

 @tf.function
def train_step(x, y):
    with tf.GradientTape() as tape:
        logits = model(x, training=True)
        loss_value = loss_fn(y, logits)
    grads = tape.gradient(loss_value, model.trainable_weights)
    optimizer.apply_gradients(zip(grads, model.trainable_weights))
    train_acc_metric.update_state(y, logits)
    return loss_value

 

評価ステップでも同じようにしましょう:

 @tf.function
def test_step(x, y):
    val_logits = model(x, training=False)
    val_acc_metric.update_state(y, val_logits)

 

ここで、このコンパイルされたトレーニングステップでトレーニングループを再実行してみましょう。

 import time

epochs = 2
for epoch in range(epochs):
    print("\nStart of epoch %d" % (epoch,))
    start_time = time.time()

    # Iterate over the batches of the dataset.
    for step, (x_batch_train, y_batch_train) in enumerate(train_dataset):
        loss_value = train_step(x_batch_train, y_batch_train)

        # Log every 200 batches.
        if step % 200 == 0:
            print(
                "Training loss (for one batch) at step %d: %.4f"
                % (step, float(loss_value))
            )
            print("Seen so far: %d samples" % ((step + 1) * 64))

    # Display metrics at the end of each epoch.
    train_acc = train_acc_metric.result()
    print("Training acc over epoch: %.4f" % (float(train_acc),))

    # Reset training metrics at the end of each epoch
    train_acc_metric.reset_states()

    # Run a validation loop at the end of each epoch.
    for x_batch_val, y_batch_val in val_dataset:
        test_step(x_batch_val, y_batch_val)

    val_acc = val_acc_metric.result()
    val_acc_metric.reset_states()
    print("Validation acc: %.4f" % (float(val_acc),))
    print("Time taken: %.2fs" % (time.time() - start_time))
 

Start of epoch 0
Training loss (for one batch) at step 0: 0.7263
Seen so far: 64 samples
Training loss (for one batch) at step 200: 0.3066
Seen so far: 12864 samples
Training loss (for one batch) at step 400: 0.8204
Seen so far: 25664 samples
Training loss (for one batch) at step 600: 0.2876
Seen so far: 38464 samples
Training loss (for one batch) at step 800: 0.2506
Seen so far: 51264 samples
Training acc over epoch: 0.8668
Validation acc: 0.8946
Time taken: 1.44s

Start of epoch 1
Training loss (for one batch) at step 0: 0.2911
Seen so far: 64 samples
Training loss (for one batch) at step 200: 0.2892
Seen so far: 12864 samples
Training loss (for one batch) at step 400: 0.6538
Seen so far: 25664 samples
Training loss (for one batch) at step 600: 0.2441
Seen so far: 38464 samples
Training loss (for one batch) at step 800: 0.5973
Seen so far: 51264 samples
Training acc over epoch: 0.8846
Validation acc: 0.9057
Time taken: 1.11s

はるかに速いですね。

モデルによって追跡される損失の低レベルの処理

レイヤーとモデルは、 self.add_loss(value)を呼び出すレイヤーによって転送パス中に作成された損失を再帰的に追跡します。結果のスカラー損失値のリストは、フォワードパスの最後にあるmodel.lossesプロパティを介して利用できます。

これらの損失コンポーネントを使用する場合は、それらを合計して、トレーニングステップのメイン損失に追加する必要があります。

アクティビティの正規化の損失を引き起こすこのレイヤーを考えてみましょう:

 class ActivityRegularizationLayer(layers.Layer):
    def call(self, inputs):
        self.add_loss(1e-2 * tf.reduce_sum(inputs))
        return inputs

 

それを使う本当にシンプルなモデルを作ってみましょう:

 inputs = keras.Input(shape=(784,), name="digits")
x = layers.Dense(64, activation="relu")(inputs)
# Insert activity regularization as a layer
x = ActivityRegularizationLayer()(x)
x = layers.Dense(64, activation="relu")(x)
outputs = layers.Dense(10, name="predictions")(x)

model = keras.Model(inputs=inputs, outputs=outputs)
 

トレーニングステップは次のようになります。

 @tf.function
def train_step(x, y):
    with tf.GradientTape() as tape:
        logits = model(x, training=True)
        loss_value = loss_fn(y, logits)
        # Add any extra losses created during the forward pass.
        loss_value += sum(model.losses)
    grads = tape.gradient(loss_value, model.trainable_weights)
    optimizer.apply_gradients(zip(grads, model.trainable_weights))
    train_acc_metric.update_state(y, logits)
    return loss_value

 

概要

これで、組み込みのトレーニングループを使用して、独自のループを最初から作成することについて知っておくべきことがすべてわかりました。

結論として、このガイドで学習したすべてを結び付ける簡単なエンドツーエンドの例を次に示します。MNISTディジットでトレーニングされたDCGANです。

エンドツーエンドの例:ゼロからのGANトレーニングループ

あなたは生成的敵対的ネットワーク(GAN)に精通しているかもしれません。 GANは、画像のトレーニングデータセット(画像の「潜在空間」)の潜在的な分布を学習することにより、ほとんど実物に見える新しい画像を生成できます。

GANは2つの部分で構成されています。潜在空間のポイントを画像空間のポイントにマップする「ジェネレータ」モデル、「弁別器」モデル、(トレーニングデータセットからの)実際の画像の違いを判別できる分類子と偽の画像(ジェネレーターネットワークの出力)。

GANトレーニングループは次のようになります。

1)弁別器を訓練する。 -潜在空間でランダムなポイントのバッチをサンプリングします。 -「ジェネレータ」モデルを使用して、ポイントを偽の画像に変換します。 -実際の画像のバッチを取得し、生成された画像と組み合わせます。 -生成された画像と実際の画像を分類するために、「弁別器」モデルをトレーニングします。

2)発電機を訓練します。 -潜在空間のランダムポイントをサンプリングします。 -「ジェネレータ」ネットワークを介して、ポイントを偽の画像に変えます。 -実際の画像のバッチを取得し、生成された画像と組み合わせます。 -「ジェネレーター」モデルをトレーニングして、弁別子を「だます」ようにし、偽の画像をリアルとして分類します。

GANの仕組みの詳細については、Pythonによるディープラーニングをご覧ください。

このトレーニングループを実装してみましょう。まず、偽の数字と実際の数字を分類するための識別子を作成します。

 discriminator = keras.Sequential(
    [
        keras.Input(shape=(28, 28, 1)),
        layers.Conv2D(64, (3, 3), strides=(2, 2), padding="same"),
        layers.LeakyReLU(alpha=0.2),
        layers.Conv2D(128, (3, 3), strides=(2, 2), padding="same"),
        layers.LeakyReLU(alpha=0.2),
        layers.GlobalMaxPooling2D(),
        layers.Dense(1),
    ],
    name="discriminator",
)
discriminator.summary()
 
Model: "discriminator"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
conv2d (Conv2D)              (None, 14, 14, 64)        640       
_________________________________________________________________
leaky_re_lu (LeakyReLU)      (None, 14, 14, 64)        0         
_________________________________________________________________
conv2d_1 (Conv2D)            (None, 7, 7, 128)         73856     
_________________________________________________________________
leaky_re_lu_1 (LeakyReLU)    (None, 7, 7, 128)         0         
_________________________________________________________________
global_max_pooling2d (Global (None, 128)               0         
_________________________________________________________________
dense_4 (Dense)              (None, 1)                 129       
=================================================================
Total params: 74,625
Trainable params: 74,625
Non-trainable params: 0
_________________________________________________________________

次に、潜在ネットワークを形状(28, 28, 1) 28、28、1 (28, 28, 1)出力(MNIST桁を表す(28, 28, 1)変換するジェネレーターネットワークを作成します。

 latent_dim = 128

generator = keras.Sequential(
    [
        keras.Input(shape=(latent_dim,)),
        # We want to generate 128 coefficients to reshape into a 7x7x128 map
        layers.Dense(7 * 7 * 128),
        layers.LeakyReLU(alpha=0.2),
        layers.Reshape((7, 7, 128)),
        layers.Conv2DTranspose(128, (4, 4), strides=(2, 2), padding="same"),
        layers.LeakyReLU(alpha=0.2),
        layers.Conv2DTranspose(128, (4, 4), strides=(2, 2), padding="same"),
        layers.LeakyReLU(alpha=0.2),
        layers.Conv2D(1, (7, 7), padding="same", activation="sigmoid"),
    ],
    name="generator",
)
 

ここで重要なのは、トレーニングループです。ご覧のとおり、それは非常に簡単です。トレーニングステップ関数は17行しかかかりません。

 # Instantiate one optimizer for the discriminator and another for the generator.
d_optimizer = keras.optimizers.Adam(learning_rate=0.0003)
g_optimizer = keras.optimizers.Adam(learning_rate=0.0004)

# Instantiate a loss function.
loss_fn = keras.losses.BinaryCrossentropy(from_logits=True)


@tf.function
def train_step(real_images):
    # Sample random points in the latent space
    random_latent_vectors = tf.random.normal(shape=(batch_size, latent_dim))
    # Decode them to fake images
    generated_images = generator(random_latent_vectors)
    # Combine them with real images
    combined_images = tf.concat([generated_images, real_images], axis=0)

    # Assemble labels discriminating real from fake images
    labels = tf.concat(
        [tf.ones((batch_size, 1)), tf.zeros((real_images.shape[0], 1))], axis=0
    )
    # Add random noise to the labels - important trick!
    labels += 0.05 * tf.random.uniform(labels.shape)

    # Train the discriminator
    with tf.GradientTape() as tape:
        predictions = discriminator(combined_images)
        d_loss = loss_fn(labels, predictions)
    grads = tape.gradient(d_loss, discriminator.trainable_weights)
    d_optimizer.apply_gradients(zip(grads, discriminator.trainable_weights))

    # Sample random points in the latent space
    random_latent_vectors = tf.random.normal(shape=(batch_size, latent_dim))
    # Assemble labels that say "all real images"
    misleading_labels = tf.zeros((batch_size, 1))

    # Train the generator (note that we should *not* update the weights
    # of the discriminator)!
    with tf.GradientTape() as tape:
        predictions = discriminator(generator(random_latent_vectors))
        g_loss = loss_fn(misleading_labels, predictions)
    grads = tape.gradient(g_loss, generator.trainable_weights)
    g_optimizer.apply_gradients(zip(grads, generator.trainable_weights))
    return d_loss, g_loss, generated_images

 

画像のバッチに対してtrain_stepを繰り返し呼び出して、GANをトレーニングしましょう。

私たちの弁別子と生成器はconvnetsなので、このコードをGPUで実行する必要があります。

 import os

# Prepare the dataset. We use both the training & test MNIST digits.
batch_size = 64
(x_train, _), (x_test, _) = keras.datasets.mnist.load_data()
all_digits = np.concatenate([x_train, x_test])
all_digits = all_digits.astype("float32") / 255.0
all_digits = np.reshape(all_digits, (-1, 28, 28, 1))
dataset = tf.data.Dataset.from_tensor_slices(all_digits)
dataset = dataset.shuffle(buffer_size=1024).batch(batch_size)

epochs = 1  # In practice you need at least 20 epochs to generate nice digits.
save_dir = "./"

for epoch in range(epochs):
    print("\nStart epoch", epoch)

    for step, real_images in enumerate(dataset):
        # Train the discriminator & generator on one batch of real images.
        d_loss, g_loss, generated_images = train_step(real_images)

        # Logging.
        if step % 200 == 0:
            # Print metrics
            print("discriminator loss at step %d: %.2f" % (step, d_loss))
            print("adversarial loss at step %d: %.2f" % (step, g_loss))

            # Save one generated image
            img = tf.keras.preprocessing.image.array_to_img(
                generated_images[0] * 255.0, scale=False
            )
            img.save(os.path.join(save_dir, "generated_img" + str(step) + ".png"))

        # To limit execution time we stop after 10 steps.
        # Remove the lines below to actually train the model!
        if step > 10:
            break
 

Start epoch 0
discriminator loss at step 0: 0.68
adversarial loss at step 0: 0.67

それでおしまい! Colab GPUで約30秒のトレーニングを行うと、見栄えの良い偽のMNIST桁が表示されます。