GradienTapeの収束はKeras.model.fitよりもはるかに遅い


8

私は現在TF2.0 API を取得しようとしていますが、GradientTapeを通常のkeras.Model.fitと比較すると、次のことに気づきました。

  1. 実行速度が遅くなりました(おそらく意欲的な実行が原因です)

  2. 収束が非常に遅くなった(そして、なぜかはわからない)。

+--------+--------------+--------------+------------------+
|  Epoch | GradientTape | GradientTape | keras.Model.fit  |
|        |              |  shuffling   |                  |
+--------+--------------+--------------+------------------+
|    1   |     0.905    |     0.918    |      0.8793      |
+--------+--------------+--------------+------------------+
|    2   |     0.352    |     0.634    |      0.2226      |
+--------+--------------+--------------+------------------+
|    3   |     0.285    |     0.518    |      0.1192      |
+--------+--------------+--------------+------------------+
|    4   |     0.282    |     0.458    |      0.1029      |
+--------+--------------+--------------+------------------+
|    5   |     0.275    |     0.421    |      0.0940      |
+--------+--------------+--------------+------------------+

これが、GradientTapeで使用したトレーニングループです。


optimizer = keras.optimizers.Adam()
glove_model = GloveModel(vocab_size=len(labels))
train_loss = keras.metrics.Mean(name='train_loss')

@tf.function
def train_step(examples, labels):
    with tf.GradientTape() as tape:
        predictions = glove_model(examples)
        loss = glove_model.glove_loss(labels, predictions)

    gradients = tape.gradient(loss, glove_model.trainable_variables)
    optimizer.apply_gradients(zip(gradients, glove_model.trainable_variables))

    train_loss(loss)



total_step = 0
for epoch in range(epochs_number):

    pbar = tqdm(train_ds.enumerate(), total=int(len(index_data) / batch_size) + 1)

    for ix, (examples, labels) in pbar:

        train_step(examples, labels)


    print(f"Epoch {epoch + 1}, Loss {train_loss.result()}")

    # Reset the metrics for the next epoch
    train_loss.reset_states()

そして、これがKeras.Model.fitトレーニングです:

glove_model.compile(optimizer, glove_model.glove_loss)
glove_model.fit(train_ds, epochs=epochs_number)

これがtf.data.Datasetソースです

train_ds = data.Dataset.from_tensor_slices(
    (np.hstack([index_rows.reshape(-1, 1), index_cols.reshape(-1, 1)]), index_data)
).shuffle(100000).batch(batch_size, drop_remainder=True)

そしてこちらがモデルです。

class GloveModel(keras.Model):

    def __init__(self, vocab_size, dim=100, a=3/4, x_max=100):
        super(GloveModel, self).__init__()

        self.vocab_size = vocab_size
        self.dim = dim
        self.a = a
        self.x_max = x_max

        self.target_embedding = layers.Embedding(
            input_dim=self.vocab_size, output_dim=self.dim, input_length=1, name="target_embedding"
        )
        self.target_bias = layers.Embedding(
            input_dim=self.vocab_size, output_dim=1, input_length=1, name="target_bias"
        )

        self.context_embedding = layers.Embedding(
            input_dim=self.vocab_size, output_dim=self.dim, input_length=1, name="context_embedding"
        )
        self.context_bias = layers.Embedding(
            input_dim=self.vocab_size, output_dim=1, input_length=1, name="context_bias"
        )

        self.dot_product = layers.Dot(axes=-1, name="dot")

        self.prediction = layers.Add(name="add")
        self.step = 0

    def call(self, inputs):

        target_ix = inputs[:, 0]
        context_ix = inputs[:, 1]

        target_embedding = self.target_embedding(target_ix)
        target_bias = self.target_bias(target_ix)

        context_embedding = self.context_embedding(context_ix)
        context_bias = self.context_bias(context_ix)

        dot_product = self.dot_product([target_embedding, context_embedding])
        prediction = self.prediction([dot_product, target_bias, context_bias])

        return prediction

    def glove_loss(self, y_true, y_pred):

        weight = tf.math.minimum(
            tf.math.pow(y_true/self.x_max, self.a), 1.0
        )
        loss_value = tf.math.reduce_mean(weight * tf.math.pow(y_pred - tf.math.log(y_true), 2.0))

        return loss_value


複数の構成とオプティマイザを試してみましたが、収束率に変化はありません。


1
注目すべきことの1つは、各エポックの前にデータをシャッフルすることです。
THN

tf.Data APIを使用しているため、fitメソッドとGradientTapeの間でまったく同じようにシャッフルします。
ベンジャミンブルトン

1
まったく同じではないと思います。あなたのコードを見せてもらえますtfdsか?ケラは.fitデフォルトで各エポックの前にシャッフルすることに注意してください。ケラのシャッフルをオフにしてテストし、収束率を比較できます。
THN

@THN送信しますが、tf.Dataset APIですでにシャッフルを実行しているため、何も変更されません。
ベンジャミンブルトン

@THN私はtf.data.Datasetを追加しました
Benjamin Breton

回答:


2

Dataset.shuffle()各ミニバッチをシャッフルするだけなので、各エポックは同じ順序になります。Kerasは、.fit()各エポックの前にデータセット全体をシャッフルするためにいくつかの魔法を使用しています。TFでこれを行うには、Dataset .repeat(epochs_number).shuffle(..., reshuffle_each_iteration=True)次を使用する必要があります。

train_ds = data.Dataset.from_tensor_slices(
    (np.hstack([index_rows.reshape(-1, 1), index_cols.reshape(-1, 1)]), index_data)
    ).shuffle(100000, reshuffle_each_iteration=True
    ).batch(batch_size, drop_remainder=True
    ).repeat(epochs_number)

for ix, (examples, labels) in train_ds.enumerate():
    train_step(examples, labels)
    current_epoch = ix // (len(index_data) // batch_size)

この回避策は美しくも自然でもありません。今のところ、これを使用して各エポックをシャッフルできます。これは既知の問題であり、修正される予定です。将来は、のfor epoch in range(epochs_number)代わりに使用できます.repeat()


申し訳ありませんが、コードを追加しましたが、収束はさらに遅くなります。列GradientTape shuffleに結果を追加しました。それは私には意味がありません...
ベンジャミン・ブルトン

@BenjaminBretonこの時点で、コードに他のいくつかのエラーが潜んでいるとは思えません。多分それはあなたのリポジトリにリンクして完全なコードを表示するのが最善です。実験が正しく行われていることを確認したら、テンソルフローリポジトリで問題を開く必要があります。
THN

助けてくれてありがとう@THN私はTF2.0リポジトリgithub.com/tensorflow/tensorflow/issues/33898に問題を投稿しました。別のモデルでエラーを再現してみます。
Benjamin Breton

1
私はnumpyを使用してシャッフルし、それが問題を解決しました。包括的な回答を投稿します
Benjamin Breton

0

問題は、tf.Datasetメソッドを使用したシャッフルから発生しました。一度に1つのバケットのみデータセットをシャッフルしました。Keras.Model.fitを使用すると、別のシャッフルが追加される可能性があるため、より良い結果が得られました。

私はシャッフルを追加し、numpy.random.shuffle両方のトレーニング方法でパフォーマンスを改善しました:

データセットの生成は次のとおりです。

numpy_data = np.hstack([index_rows.reshape(-1, 1), index_cols.reshape(-1, 1), index_data.reshape(-1, 1)])

np.random.shuffle(numpy_data)

indexes = np.array(numpy_data[:, :2], dtype=np.uint32)
labels = np.array(numpy_data[:, 2].reshape(-1, 1), dtype=np.float32)

train_ds = data.Dataset.from_tensor_slices(
    (indexes, labels)
).shuffle(100000).batch(batch_size, drop_remainder=True)

結果は次のとおりです。

+--------+--------------+------------------+
|  Epoch | GradientTape |  keras.Model.fit |
+--------+--------------+------------------+
|    1   |     0.294    |      0.294       |
+--------+--------------+------------------+
|    2   |     0.111    |      0.110       |
+--------+--------------+------------------+
|    3   |     0.089    |      0.089       |
+--------+--------------+------------------+
|    4   |     0.074    |      0.075       |
+--------+--------------+------------------+
|    5   |     0.063    |      0.063       |
+--------+--------------+------------------+

エポックごとのトレーニングタイプは、エポックごと2分とほぼ同じです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.