基本的なトレーニングループ

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

以前のガイドでは、テンソル変数勾配テープモジュールについて学習しました。このガイドでは、これらをすべて組み合わせてモデルをトレーニングします。

TensorFlow には、tf.Keras API という、抽象化によってボイラープレートを削減する高度なニューラルネットワーク API も含まれていますが、このガイドでは基本的なクラスを使用します。

セットアップ

import tensorflow as tf

機械学習問題を解決する

機械学習問題を解決する場合、一般的には次の手順を実行します。

  • トレーニングデータを取得する。
  • モデルを定義する。
  • 損失関数を定義する。
  • トレーニングデータを読み込み、理想値から損失を計算する。
  • その損失の勾配を計算し、オプティマイザを使用してデータに適合するように変数を調整します。
  • 結果を評価する。

説明のため、このガイドでは $W$(重み)と $b$(バイアス)の 2 つの変数を持つ単純な線形モデルである $f(x) = x * W + b$ を開発します。

これは最も基本的な機械学習問題です。$x$ と $y$ が与えられている前提で 単純線形回帰を使用して直線の傾きとオフセットを求めてみます。

データ

教師あり学習では、入力(通常は x と表記)と出力y と表記され、ラベルと呼ばれることが多い)を使用します。目標は、入力と出力のペアから学習し、入力から出力の値を予測できるようにすることです。

TensorFlow での各データ入力はほぼ必ずテンソルで表現され、多くの場合はベクトルです。教師ありトレーニングの場合は出力(または予測したい値)もテンソルになります。

以下は、直線上の点にガウス(正規)ノイズを付加することによって合成されたデータです。

# The actual line
TRUE_W = 3.0
TRUE_B = 2.0

NUM_EXAMPLES = 1000

# A vector of random x values
x = tf.random.normal(shape=[NUM_EXAMPLES])

# Generate some noise
noise = tf.random.normal(shape=[NUM_EXAMPLES])

# Calculate y
y = x * TRUE_W + TRUE_B + noise
2021-08-14 01:16:08.255590: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-08-14 01:16:08.264446: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-08-14 01:16:08.265500: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-08-14 01:16:08.267420: I tensorflow/core/platform/cpu_feature_guard.cc:142] This TensorFlow binary is optimized with oneAPI Deep Neural Network Library (oneDNN) to use the following CPU instructions in performance-critical operations:  AVX2 AVX512F FMA
To enable them in other operations, rebuild TensorFlow with the appropriate compiler flags.
2021-08-14 01:16:08.268004: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-08-14 01:16:08.269037: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-08-14 01:16:08.270000: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-08-14 01:16:08.913651: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-08-14 01:16:08.914714: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-08-14 01:16:08.915685: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-08-14 01:16:08.916624: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1510] Created device /job:localhost/replica:0/task:0/device:GPU:0 with 14648 MB memory:  -> device: 0, name: Tesla V100-SXM2-16GB, pci bus id: 0000:00:05.0, compute capability: 7.0
# Plot all the data
import matplotlib.pyplot as plt

plt.scatter(x, y, c="b")
plt.show()

png

テンソルは一般的にバッチか、入力と出力のグループにまとめられます。バッチにはいくつかのトレーニング上のメリットがあり、アクセラレーターやベクトル化計算でうまく機能します。このデータセットの小ささを考慮すると、データセット全体を単一のバッチとして扱うことができます。

モデルを定義する

モデル内のすべての重みを表現するには tf.Variable を使用します。tf.Variable は値を格納し、必要に応じてこれをテンソル形式で提供します。詳細については、変数ガイドを参照してください。

変数と計算のカプセル化には tf.Module を使用します。任意の Python オブジェクトを使用することもできますが、この方法ではより簡単に保存できます。

ここでは wb の両方を変数として定義しています。

class MyModel(tf.Module):
  def __init__(self, **kwargs):
    super().__init__(**kwargs)
    # Initialize the weights to `5.0` and the bias to `0.0`
    # In practice, these should be randomly initialized
    self.w = tf.Variable(5.0)
    self.b = tf.Variable(0.0)

  def __call__(self, x):
    return self.w * x + self.b

model = MyModel()

# List the variables tf.modules's built-in variable aggregation.
print("Variables:", model.variables)

# Verify the model works
assert model(3.0).numpy() == 15.0
Variables: (<tf.Variable 'Variable:0' shape=() dtype=float32, numpy=0.0>, <tf.Variable 'Variable:0' shape=() dtype=float32, numpy=5.0>)
2021-08-14 01:16:10.122532: W tensorflow/python/util/util.cc:348] Sets are not currently considered sequences, but this may change in the future, so consider avoiding using them.

ここでは初期変数が固定されていますが、Keras には他の Keras の有無にかかわらず使用できる多くの初期化子があります。

損失関数を定義する

損失関数は、特定の入力に対するモデルの出力と目標出力との一致度を評価します。目標は、トレーニング中のこの差を最小限に抑えることです。 「平均二乗」誤差としても知られる標準 L2 損失を定義します。

# This computes a single loss value for an entire batch
def loss(target_y, predicted_y):
  return tf.reduce_mean(tf.square(target_y - predicted_y))

モデルをトレーニングする前に、モデルの予測を赤でプロットし、トレーニングデータを青でプロットすることにより、損失の値を視覚化できます。

plt.scatter(x, y, c="b")
plt.scatter(x, model(x), c="r")
plt.show()

print("Current loss: %1.6f" % loss(y, model(x)).numpy())

png

Current loss: 8.788773

トレーニングループを定義する

トレーニングループは、次の 3 つを順番に繰り返し実行するタスクで構成されます。

  • モデル経由で入力のバッチを送信して出力を生成する
  • 出力を出力(またはラベル)と比較して損失を計算する
  • 勾配テープを使用して勾配を検出する
  • これらの勾配を使用して変数を最適化する

この例では、最急降下法を使用してモデルをトレーニングできます。

tf.keras.optimizers でキャプチャされる勾配降下法のスキームには多くのバリエーションがありますが、ここでは基本原理から構築するという姿勢で自動微分を行う tf.GradientTape と値を減少させる tf.assign_subtf.assigntf.sub の組み合わせ)を使用して基本的な計算を自分で実装してみましょう。

# Given a callable model, inputs, outputs, and a learning rate...
def train(model, x, y, learning_rate):

  with tf.GradientTape() as t:
    # Trainable variables are automatically tracked by GradientTape
    current_loss = loss(y, model(x))

  # Use GradientTape to calculate the gradients with respect to W and b
  dw, db = t.gradient(current_loss, [model.w, model.b])

  # Subtract the gradient scaled by the learning rate
  model.w.assign_sub(learning_rate * dw)
  model.b.assign_sub(learning_rate * db)

トレーニングを観察するため、トレーニングループを介して xy の同じバッチを送信し、Wb がどのように変化するかを見ることができます。

model = MyModel()

# Collect the history of W-values and b-values to plot later
Ws, bs = [], []
epochs = range(10)

# Define a training loop
def training_loop(model, x, y):

  for epoch in epochs:
    # Update the model with the single giant batch
    train(model, x, y, learning_rate=0.1)

    # Track this before I update
    Ws.append(model.w.numpy())
    bs.append(model.b.numpy())
    current_loss = loss(y, model(x))

    print("Epoch %2d: W=%1.2f b=%1.2f, loss=%2.5f" %
          (epoch, Ws[-1], bs[-1], current_loss))
print("Starting: W=%1.2f b=%1.2f, loss=%2.5f" %
      (model.w, model.b, loss(y, model(x))))

# Do the training
training_loop(model, x, y)

# Plot it
plt.plot(epochs, Ws, "r",
         epochs, bs, "b")

plt.plot([TRUE_W] * len(epochs), "r--",
         [TRUE_B] * len(epochs), "b--")

plt.legend(["W", "b", "True W", "True b"])
plt.show()
Starting: W=5.00 b=0.00, loss=8.78877
Epoch  0: W=4.61 b=0.40, loss=6.03433
Epoch  1: W=4.30 b=0.71, loss=4.25086
Epoch  2: W=4.05 b=0.97, loss=3.09590
Epoch  3: W=3.84 b=1.17, loss=2.34785
Epoch  4: W=3.68 b=1.33, loss=1.86327
Epoch  5: W=3.54 b=1.46, loss=1.54933
Epoch  6: W=3.44 b=1.56, loss=1.34590
Epoch  7: W=3.35 b=1.65, loss=1.21406
Epoch  8: W=3.28 b=1.71, loss=1.12861
Epoch  9: W=3.22 b=1.77, loss=1.07322

png

# Visualize how the trained model performs
plt.scatter(x, y, c="b")
plt.scatter(x, model(x), c="r")
plt.show()

print("Current loss: %1.6f" % loss(model(x), y).numpy())

png

Current loss: 1.073216

Keras を使用した場合の同じ方法

上記のコードを Keras で書いたコードと対比すると参考になります。

tf.keras.Model をサブクラス化すると、モデルの定義はまったく同じように見えます。Keras モデルは最終的にモジュールから継承するということを覚えておいてください。

class MyModelKeras(tf.keras.Model):
  def __init__(self, **kwargs):
    super().__init__(**kwargs)
    # Initialize the weights to `5.0` and the bias to `0.0`
    # In practice, these should be randomly initialized
    self.w = tf.Variable(5.0)
    self.b = tf.Variable(0.0)

  def __call__(self, x, **kwargs):
    return self.w * x + self.b

keras_model = MyModelKeras()

# Reuse the training loop with a Keras model
training_loop(keras_model, x, y)

# You can also save a checkpoint using Keras's built-in support
keras_model.save_weights("my_checkpoint")
Epoch  0: W=4.61 b=0.40, loss=6.03433
Epoch  1: W=4.30 b=0.71, loss=4.25086
Epoch  2: W=4.05 b=0.97, loss=3.09590
Epoch  3: W=3.84 b=1.17, loss=2.34785
Epoch  4: W=3.68 b=1.33, loss=1.86327
Epoch  5: W=3.54 b=1.46, loss=1.54933
Epoch  6: W=3.44 b=1.56, loss=1.34590
Epoch  7: W=3.35 b=1.65, loss=1.21406
Epoch  8: W=3.28 b=1.71, loss=1.12861
Epoch  9: W=3.22 b=1.77, loss=1.07322

モデルを作成するたびに新しいトレーニングループを作成する代わりに、Keras の組み込み機能をショートカットとして使用できます。これは、Python トレーニングループを作成またはデバッグしたくない場合に便利です。

その場合は model.compile() を使用してパラメーターを設定し、model.fit() でトレーニングする必要があります。L2 損失と最急降下法の Keras 実装を再びショートカットとして使用するとコード量を少なくすることができます。Keras の損失とオプティマイザーはこれらの便利な関数の外でも使用できます。また、前の例ではこれらを使用できた可能性があります。

keras_model = MyModelKeras()

# compile sets the training paramaeters
keras_model.compile(
    # By default, fit() uses tf.function().  You can
    # turn that off for debugging, but it is on now.
    run_eagerly=False,

    # Using a built-in optimizer, configuring as an object
    optimizer=tf.keras.optimizers.SGD(learning_rate=0.1),

    # Keras comes with built-in MSE error
    # However, you could use the loss function
    # defined above
    loss=tf.keras.losses.mean_squared_error,
)

Keras fit は、バッチ処理されたデータまたは完全なデータセットを NumPy 配列として想定しています。NumPy 配列はバッチに分割され、デフォルトでバッチサイズは 32 になります。

この場合は手書きループの動作に一致させるため、x をサイズ 1000 の単一バッチとして渡す必要があります。

print(x.shape[0])
keras_model.fit(x, y, epochs=10, batch_size=1000)
1000
2021-08-14 01:16:11.331601: I tensorflow/compiler/mlir/mlir_graph_optimization_pass.cc:185] None of the MLIR Optimization Passes are enabled (registered 2)
Epoch 1/10
1/1 [==============================] - 0s 207ms/step - loss: 8.7888
Epoch 2/10
1/1 [==============================] - 0s 3ms/step - loss: 6.0343
Epoch 3/10
1/1 [==============================] - 0s 2ms/step - loss: 4.2509
Epoch 4/10
1/1 [==============================] - 0s 3ms/step - loss: 3.0959
Epoch 5/10
1/1 [==============================] - 0s 2ms/step - loss: 2.3478
Epoch 6/10
1/1 [==============================] - 0s 2ms/step - loss: 1.8633
Epoch 7/10
1/1 [==============================] - 0s 2ms/step - loss: 1.5493
Epoch 8/10
1/1 [==============================] - 0s 2ms/step - loss: 1.3459
Epoch 9/10
1/1 [==============================] - 0s 2ms/step - loss: 1.2141
Epoch 10/10
1/1 [==============================] - 0s 2ms/step - loss: 1.1286
<keras.callbacks.History at 0x7fb96ebe6f90>

Keras はトレーニング前ではなくトレーニング後に損失を出力するため、最初の損失は低く表示されますが、それ以外の場合は基本的に同じトレーニングパフォーマンスを示します。

次のステップ

このガイドでは、テンソル、変数、モジュール、勾配テープの基本的なクラスを使用してモデルを構築およびトレーニングする方法と、それらの概念を Keras にマッピングする方法について説明しました。

ただし、これはごく単純な問題です。より実践的な説明については、カスタムトレーニングのウォークスルーをご覧ください。

組み込みの Keras トレーニングループを使用する方法の詳細は、こちらのガイドを参照してください。トレーニングループと Keras の詳細は、こちらのガイドを参照してください。独自の分散トレーニングループを書く方法については、こちらのガイドを参照してください。