TensorFlow 2.0+Kerasピット防止ガイド


TensorFlow 2.0は1.xバージョンに対して大きなダイエットを行い、Eager Executionはデフォルトでオープンし、Kerasをデフォルトの高度なAPIとして使用し、これらの改善はTensorFlowの使用難易度を大幅に低下させた.
本文は主にKeras+TensorFlow 2.0を用いたBatchNormalizationのピット経験を記録した.このピットはTF 2.0の新しい特性をほとんど破壊するところだった.TF 2.0の公式チュートリアルを勉強しているなら、見てみてください.
問題の発生
チュートリアル[1]からhttps://www.tensorflow.org/alpha/tutorials/images/transfer_learning?hl=zh-cn(Transfer Learningの使い方を説明する):
IMG_SHAPE = (IMG_SIZE, IMG_SIZE, 3)
# Create the base model from the pre-trained model MobileNet V2
base_model = tf.keras.applications.MobileNetV2(input_shape=IMG_SHAPE,
                                               include_top=False,weights='imagenet')
model = tf.keras.Sequential([
  base_model,
    tf.keras.layers.GlobalAveragePooling2D(),
    tf.keras.layers.Dense(NUM_CLASSES)
])

簡単なコードMobileNetV 2の構造を多重化して分類器モデルを作成し、Kerasのインタフェースを呼び出してモデルを訓練することができます.
model.compile(optimizer=tf.keras.optimizers.RMSprop(lr=base_learning_rate),

              loss='sparse_categorical_crossentropy',

              metrics=['sparse_categorical_accuracy'])

model.summary()

history = model.fit(train_batches.repeat(),

                    epochs=20,

                    steps_per_epoch = steps_per_epoch,

                    validation_data=validation_batches.repeat(),

                    validation_steps=validation_steps)

出力の結果、一緒に完璧です.
Model: "sequential"

_________________________________________________________________

Layer (type)                 Output Shape              Param #

=================================================================

mobilenetv2_1.00_160 (Model) (None, 5, 5, 1280)        2257984

_________________________________________________________________

global_average_pooling2d (Gl (None, 1280)              0

_________________________________________________________________

dense (Dense)                (None, 2)                 1281

=================================================================

Total params: 2,259,265

Trainable params: 1,281

Non-trainable params: 2,257,984

_________________________________________________________________

Epoch 11/20

581/581 [==============================] - 134s 231ms/step - loss: 0.4208 - accuracy: 0.9484 - val_loss: 0.1907 - val_accuracy: 0.9812

Epoch 12/20

581/581 [==============================] - 114s 197ms/step - loss: 0.3359 - accuracy: 0.9570 - val_loss: 0.1835 - val_accuracy: 0.9844

Epoch 13/20

581/581 [==============================] - 116s 200ms/step - loss: 0.2930 - accuracy: 0.9650 - val_loss: 0.1505 - val_accuracy: 0.9844

Epoch 14/20

581/581 [==============================] - 114s 196ms/step - loss: 0.2561 - accuracy: 0.9701 - val_loss: 0.1575 - val_accuracy: 0.9859

Epoch 15/20

581/581 [==============================] - 119s 206ms/step - loss: 0.2302 - accuracy: 0.9715 - val_loss: 0.1600 - val_accuracy: 0.9812

Epoch 16/20

581/581 [==============================] - 115s 197ms/step - loss: 0.2134 - accuracy: 0.9747 - val_loss: 0.1407 - val_accuracy: 0.9828

Epoch 17/20

581/581 [==============================] - 115s 197ms/step - loss: 0.1546 - accuracy: 0.9813 - val_loss: 0.0944 - val_accuracy: 0.9828

Epoch 18/20

581/581 [==============================] - 116s 200ms/step - loss: 0.1636 - accuracy: 0.9794 - val_loss: 0.0947 - val_accuracy: 0.9844

Epoch 19/20

581/581 [==============================] - 115s 198ms/step - loss: 0.1356 - accuracy: 0.9823 - val_loss: 0.1169 - val_accuracy: 0.9828

Epoch 20/20

581/581 [==============================] - 116s 199ms/step - loss: 0.1243 - accuracy: 0.9849 - val_loss: 0.1121 - val_accuracy: 0.9875

しかし、この書き方はまだDebugに不便で、反復のプロセスを細かく制御し、中間結果を見ることができることを望んでいます.そのため、私たちが訓練したプロセスはこのように変更されました.
optimizer = tf.keras.optimizers.RMSprop(lr=base_learning_rate)
train_accuracy = tf.keras.metrics.SparseCategoricalAccuracy(name='train_accuracy')

@tf.function

def train_cls_step(image, label):

    with tf.GradientTape() as tape:

        predictions = model(image)

        loss = tf.keras.losses.SparseCategoricalCrossentropy()(label, predictions)

    gradients = tape.gradient(loss, model.trainable_variables)

    optimizer.apply_gradients(zip(gradients, model.trainable_variables))

    train_accuracy(label, predictions)

for images, labels in train_batches:

    train_cls_step(images,labels)

再訓練後も、結果は完璧だった!
しかし、このときFinetuneとヘビーヘッドのトレーニング開始の違いを比較したいので、モデルを構築するコードをこのように変更しました.
base_model = tf.keras.applications.MobileNetV2(input_shape=IMG_SHAPE,

                                              include_top=False,weights=None)

モデルの重みをランダムに生成すると,訓練結果は風を引き始め,Lossは低下せず,Accuracyは50%付近で安定して遊泳した.
Step #10: loss=0.6937199831008911 acc=46.5625%

Step #20: loss=0.6932525634765625 acc=47.8125%

Step #30: loss=0.699873685836792 acc=49.16666793823242%

Step #40: loss=0.6910845041275024 acc=49.6875%

Step #50: loss=0.6935917139053345 acc=50.0625%

Step #60: loss=0.6965731382369995 acc=49.6875%

Step #70: loss=0.6949992179870605 acc=49.19642639160156%

Step #80: loss=0.6942993402481079 acc=49.84375%

Step #90: loss=0.6933775544166565 acc=49.65277862548828%

Step #100: loss=0.6928421258926392 acc=49.5%

Step #110: loss=0.6883170008659363 acc=49.54545593261719%

Step #120: loss=0.695658802986145 acc=49.453125%

Step #130: loss=0.6875559091567993 acc=49.61538314819336%

Step #140: loss=0.6851695775985718 acc=49.86606979370117%

Step #150: loss=0.6978713274002075 acc=49.875%

Step #160: loss=0.7165156602859497 acc=50.0%

Step #170: loss=0.6945627331733704 acc=49.797794342041016%

Step #180: loss=0.6936900615692139 acc=49.9305534362793%

Step #190: loss=0.6938323974609375 acc=49.83552551269531%

Step #200: loss=0.7030564546585083 acc=49.828125%

Step #210: loss=0.6926192045211792 acc=49.76190185546875%

Step #220: loss=0.6932414770126343 acc=49.786930084228516%

Step #230: loss=0.6924526691436768 acc=49.82337188720703%

Step #240: loss=0.6882281303405762 acc=49.869789123535156%

Step #250: loss=0.6877702474594116 acc=49.86249923706055%

Step #260: loss=0.6933954954147339 acc=49.77163314819336%

Step #270: loss=0.6944763660430908 acc=49.75694274902344%

Step #280: loss=0.6945018768310547 acc=49.49776840209961%

predictionsの結果を印刷すると、batch内の出力はすべて同じであることがわかりました.
0 = tf.Tensor([0.51352817 0.48647183], shape=(2,), dtype=float32)

1 = tf.Tensor([0.51352817 0.48647183], shape=(2,), dtype=float32)

2 = tf.Tensor([0.51352817 0.48647183], shape=(2,), dtype=float32)

3 = tf.Tensor([0.51352817 0.48647183], shape=(2,), dtype=float32)

4 = tf.Tensor([0.51352817 0.48647183], shape=(2,), dtype=float32)

5 = tf.Tensor([0.51352817 0.48647183], shape=(2,), dtype=float32)

6 = tf.Tensor([0.51352817 0.48647183], shape=(2,), dtype=float32)

7 = tf.Tensor([0.51352817 0.48647183], shape=(2,), dtype=float32)

8 = tf.Tensor([0.51352817 0.48647183], shape=(2,), dtype=float32)

9 = tf.Tensor([0.51352817 0.48647183], shape=(2,), dtype=float32)

初期ウェイトを変更しただけで、なぜこのような結果が得られたのでしょうか.
問題の調査
実験1
トレーニングが不十分だったり、learning rateの設定が不適切だったりしますか?数回の調整を経て、どのくらい訓練してもlearning rateが大きく小さくなっても、このような結果を変えることはできないことが分かった.
実験2
重みの問題である以上、重みがランダムに初期化されているかどうかは問題があり、初期重みを統計してみると、すべて正常です.
実験3
この問題は以前の経験によると,Inferenceモデルを導出したときにBatchNormalizationがうまく処理しなかった場合,このようなbatch内のすべての結果が同じ問題が発生する.しかし、訓練中になぜこの問題が発生したのかをどのように説明しますか?そしてなぜFinetuteに問題が発生しないのでしょうか?重みの初期値を変えただけなんですよねこの方向にGoogleに行くと、KerasのBatchNormalizationには確かにissueがたくさんあることがわかりました.その一つの問題は、モデルを保存しているのはBatchNormalzationのmoving meanとmoving varianceが保存されないことです[6]https://github.com/tensorflow/tensorflow/issues/16455,もう一つのissueが問題に言及したのは私たちの問題と関係がある:[2] https://github.com/tensorflow/tensorflow/issues/19643[3] https://github.com/tensorflow/tensorflow/issues/23873最後に、この著者は原因を見つけ、ここにまとめた:[4] https://pgaleone.eu/tensorflow/keras/2019/01/19/keras-not-yet-interface-to-tensorflow/
このヒントに基づいて、次のような試みを行いました.
実験3.1
model.fitの書き方に変えて訓練を行い、最初のいくつかのepochの中で、私たちが良い点を発見したのはtraining accuracyが徐々に向上し始めたことだが、validation accuracyには元の問題がある.さらにmodel.predit_on_batch()は中間結果を得たが,依然としてbatch内出力が同じであることが分かった.
Epoch 1/20

581/581 [==============================] - 162s 279ms/step - loss: 0.6768 - sparse_categorical_accuracy: 0.6224 - val_loss: 0.6981 - val_sparse_categorical_accuracy: 0.4984

Epoch 2/20

581/581 [==============================] - 133s 228ms/step - loss: 0.4847 - sparse_categorical_accuracy: 0.7684 - val_loss: 0.6931 - val_sparse_categorical_accuracy: 0.5016

Epoch 3/20

581/581 [==============================] - 130s 223ms/step - loss: 0.3905 - sparse_categorical_accuracy: 0.8250 - val_loss: 0.6996 - val_sparse_categorical_accuracy: 0.4984

Epoch 4/20

581/581 [==============================] - 131s 225ms/step - loss: 0.3113 - sparse_categorical_accuracy: 0.8660 - val_loss: 0.6935 - val_sparse_categorical_accuracy: 0.5016

しかし、訓練が進むにつれて結果が逆転し、正常になり始めた(tf.functionの書き方はいくら訓練しても変わらず、幸いにも治療をあきらめなかった)(追加:実はここはまだ問題があり、後ろを見続けていると、当時は変だと思っていたので、こんなに遅く収束すべきではなかった)
Epoch 18/20

581/581 [==============================] - 131s 226ms/step - loss: 0.0731 - sparse_categorical_accuracy: 0.9725 - val_loss: 1.4896 - val_sparse_categorical_accuracy: 0.8703

Epoch 19/20

581/581 [==============================] - 130s 225ms/step - loss: 0.0664 - sparse_categorical_accuracy: 0.9748 - val_loss: 0.6890 - val_sparse_categorical_accuracy: 0.9016

Epoch 20/20

581/581 [==============================] - 126s 217ms/step - loss: 0.0631 - sparse_categorical_accuracy: 0.9768 - val_loss: 1.0290 - val_sparse_categorical_accuracy: 0.9031

通多model.predit_on_batch()の結果もこのAccuracyと一致しています
実験3.2
前回の実験により,KerasのAPIのみで訓練すれば,確かに正常であることを検証した.もっと深い原因は何ですか?BatchNomalizationにはupdate moving meanとmoving varianceがないのではないでしょうか.答えはYes私たちはそれぞれ2つの訓練方法の前後で、印刷します. moving meanとmoving varianceの値:
def get_bn_vars(collection):

    moving_mean, moving_variance = None, None    for var in collection:

        name = var.name.lower()

        if "variance" in name:

            moving_variance = var

        if "mean" in name:

            moving_mean = var

    if moving_mean is not None and moving_variance is not None:

        return moving_mean, moving_variance

    raise ValueError("Unable to find moving mean and variance")

mean, variance = get_bn_vars(model.variables)

print(mean)

print(variance)

確かにmodel.fit()を使って訓練を行うと、meanとvarianceはupdateにある(更新速度は変に見えるが)が、tf.functionのような書き方の2つの値はupdateにはされていないことが分かった.
ここではなぜFinetuneが問題にならないのかを説明することができます.imagenetトレーニングのmean、varianceはすでに良い値なので、更新しなくても正常に使用できます.
実験3.3
[4]で述べた方法に変更してダイナミックなInputを構築するかどうかShapeのモデルでOKなんですよね?
class MyModel(Model):

    def __init__(self):

        super(MyModel, self).__init__()

        self.conv1 = Conv2D(32, 3, activation='relu')

        self.batch_norm1=BatchNormalization()

        self.flatten = Flatten()

        self.d1 = Dense(128, activation='relu')

        self.d2 = Dense(10, activation='softmax')

    def call(self, x):

        x = self.conv1(x)

        x = self.batch_norm1(x)

        x = self.flatten(x)

        x = self.d1(x)

        return self.d2(x)

model = MyModel()

#model.build((None,28,28,1))

model.summary()

@tf.functiondef train_step(image, label):

    with tf.GradientTape() as tape:

        predictions = model(image)

        loss = loss_object(label, predictions)

    gradients = tape.gradient(loss, model.trainable_variables)

    optimizer.apply_gradients(zip(gradients, model.trainable_variables))

    train_loss(loss)

    train_accuracy(label, predictions)

モデルは次のとおりです.
Model: "my_model"

_________________________________________________________________

Layer (type)                 Output Shape              Param #  

=================================================================

conv2d (Conv2D)              multiple                  320      

_________________________________________________________________

batch_normalization_v2 (Batc multiple                  128      

_________________________________________________________________

flatten (Flatten)            multiple                  0        

_________________________________________________________________

dense (Dense)                multiple                  2769024  

_________________________________________________________________

dense_1 (Dense)              multiple                  1290      

=================================================================

Total params: 2,770,762

Trainable params: 2,770,698

Non-trainable params: 64

Output Shapeから見ると、構築モデルは問題なくMINSTを1回走ったが、結果も良かった!念のため、meanとvarianceが更新されたかどうかを同じようにテストしましたが、結果は意外で、ありませんでした!つまり[4]で述べた案はわれわれのところでは実行できない
実験3.4
位置付けの問題がBatchNormalizationである以上、BatchNormalizationのtrainingとtestingでは動作が一致しないことを考え、testingでmoving meanとvarianceではupdateは必要ないので、tf.functionのこのような書き方は自動的にこの状態を変更しないのではないでしょうか.ソースコードを確認すると、BatchNormalizationのcall()にtrainingパラメータが存在し、デフォルトはFalseです.
 Call arguments:

   inputs: Input tensor (of any rank).

   training: Python boolean indicating whether the layer should behave in

     training mode or in inference mode.

     - `training=True`: The layer will normalize its inputs using the

       mean and variance of the current batch of inputs.

     - `training=False`: The layer will normalize its inputs using the

       mean and variance of its moving statistics, learned during training.

そこで、以下の改善を行いました.
class MyModel(Model):

    def __init__(self):

        super(MyModel, self).__init__()

        self.conv1 = Conv2D(32, 3, activation='relu')

        self.batch_norm1=BatchNormalization()

        self.flatten = Flatten()

        self.d1 = Dense(128, activation='relu')

        self.d2 = Dense(10, activation='softmax')

    def call(self, x,training=True):

        x = self.conv1(x)

        x = self.batch_norm1(x,training=training)

        x = self.flatten(x)

        x = self.d1(x)

        return self.d2(x)

model = MyModel()

#model.build((None,28,28,1))

model.summary()

@tf.functiondef train_step(image, label):

    with tf.GradientTape() as tape:

        predictions = model(image,training=True)

        loss = loss_object(label, predictions)

    gradients = tape.gradient(loss, model.trainable_variables)

    optimizer.apply_gradients(zip(gradients, model.trainable_variables))

    train_loss(loss)

    train_accuracy(label, predictions)

@tf.functiondef test_step(image, label):

    predictions = model(image,training=False)

    t_loss = loss_object(label, predictions)

    test_loss(t_loss)

    test_accuracy(label, predictions)

その結果、moving meanとvarianceが更新され始め、Accuracyのテストも予想に合っているため、BatchNormalizationがtrainingなのかtestingなのかを指定する必要があることが問題の根源であることを判断することができます.
実験3.5
3.4の方法は我々の問題を解決したが、それはModelを構築するsubclassを使用する方法であり、我々の以前のMobileNetV 2はより柔軟なKeras Functional APIに基づいて構築されたものであり、call()関数の定義を制御できないため、trainingとtestingの状態を柔軟に切り替えることができず、またSequentialで構築する場合も同様である.[5]https://blog.keras.io/keras-as-a-simplified-interface-to-tensorflow-tutorial.html[7]https://github.com/keras-team/keras/issues/7085[8]https://github.com/keras-team/keras/issues/67525[8]から2つの状況がわかりました
  • tf.keras.backend.set_learning_phase()はtrainingとtestingの状態を変えることができる.
  • model.updatesとlayer.updatesはold_を保存していますvalueとnew_valueのAssign Op

  • まず試してみました
     tf.keras.backend.set_learning_phase(True)

    その結果,MobileNetV 2が構築したモデルも正常に動作するようになった.しかも収束の速度はmodel.fit()よりもずっと速いようで、前のmodel.fit()の収束が遅いという戸惑いと結びつけて、ここでまた一つの実験を増やして、model.fit()のバージョンにもこの言葉を加えると、同じ収束速度も速くなったことがわかります!1つのepochでいい結果が得られます!そこで、model.fit()がlearning_を設定しているかどうかという問題が発生しました.phase状態?moving meanとvarianceのupdateをどうやって作ったの?2つ目の方法は、チュートリアルで1.xのバージョンでどのように構築するかを説明しているため、eager executionモードではrunこれらのAssign Operationに行くことはできないようです.参考にしましょう
    update_ops = []
    
    for assign_op in model.updates:
    
        update_ops.append(assign_op))
    #         update_ops eager execution        ?

    結論
    まとめてみると、[4]から問題解決のヒントを見つけたが、[4]の問題と解決方法が私たちのところに使われているのは本当に問題を解決することができないことを最終的に証明した.問題の鍵はKeras+TensorFlow 2.0の中でtrainingとtesting状態で行動が一致しないLayerをどのように処理するかにある.そしてmodel.fit()とtf.funtionの2つの訓練方法の違いについて、最終的にはmodel.fit()には奇妙な行為がたくさん含まれているようです.最終的な使用推奨事項は次のとおりです.
  • model.fit()またはmodel.train_を使用on_batch()というKerasのAPIトレーニングモデルの場合も、tf.keras.backend.set_を手動で設定することをお勧めしますlearning_phase(True)、収束を速めることができます
  • eager executionを使用する場合
  • 1)Modelを構築するsubclassを使用するが、call()に対してtrainingの状態を設定し、BatchNoramlization、DropoutのようなLayerに対して異なる処理を行う
  • 2)Functional APIまたはSequentialを使用してModelを構築し、tf.keras.backend.set_を設定learning_phase(True)ですが、testingで状態を変えることに注意してください
  • 最後に、なぜTF 2.0のチュートリアルではこれらに言及しなかったのですか?デフォルトではケラスに精通していますか?[顔を覆って泣く]
    に感謝
    柏涛帆月は先生の助けに感謝します.
    [1]https://www.tensorflow.org/alpha/tutorials/images/transfer_learning?hl=zh-cn[2] https://github.com/tensorflow/tensorflow/issues/19643[3] https://github.com/tensorflow/tensorflow/issues/23873[4] https://pgaleone.eu/tensorflow/keras/2019/01/19/keras-not-yet-interface-to-tensorflow/[5]https://blog.keras.io/keras-as-a-simplified-interface-to-tensorflow-tutorial.html[6]https://github.com/tensorflow/tensorflow/issues/16455[7]https://github.com/keras-team/keras/issues/7085[8]https://github.com/keras-team/keras/issues/6752
    本文の作者:シンクタンク
    原文を読む
    本文は雲栖コミュニティのオリジナル内容で、許可を得ずに転載してはならない.