يعود مؤتمر Google I / O من 18 إلى 20 مايو! حجز مساحة وبناء الجدول الزمني الخاص بك سجل الآن

نقل التعلم والضبط

عرض على TensorFlow.org تشغيل في Google Colab عرض المصدر على جيثب تحميل دفتر

يثبت

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

مقدمة

يتكون التعلم الانتقالي من أخذ الميزات التي تم تعلمها في مشكلة واحدة ، والاستفادة منها في مشكلة جديدة مماثلة. على سبيل المثال ، قد تكون الميزات من نموذج تعلم التعرف على حيوانات الراكون مفيدة لبدء نموذج يهدف إلى تحديد التانوكيس.

عادةً ما يتم إجراء التعلم عن طريق النقل للمهام التي تحتوي فيها مجموعة البيانات الخاصة بك على القليل جدًا من البيانات لتدريب نموذج كامل الحجم من البداية.

التجسد الأكثر شيوعًا لنقل التعلم في سياق التعلم العميق هو سير العمل التالي:

  1. خذ طبقات من نموذج تم تدريبه مسبقًا.
  2. قم بتجميدها ، وذلك لتجنب إتلاف أي من المعلومات التي تحتويها خلال جولات التدريب المستقبلية.
  3. أضف بعض الطبقات الجديدة القابلة للتدريب فوق الطبقات المجمدة. سيتعلمون كيفية تحويل الميزات القديمة إلى تنبؤات على مجموعة بيانات جديدة.
  4. قم بتدريب الطبقات الجديدة على مجموعة البيانات الخاصة بك.

الخطوة الأخيرة والاختيارية ، هي الضبط الدقيق ، والتي تتكون من إلغاء تجميد النموذج بأكمله الذي حصلت عليه أعلاه (أو جزء منه) ، وإعادة تدريبه على البيانات الجديدة بمعدل تعلم منخفض للغاية. يمكن أن يحقق هذا تحسينات ذات مغزى ، من خلال تكييف الميزات المحددة مسبقًا بشكل تدريجي مع البيانات الجديدة.

أولاً ، سنستعرض بالتفصيل واجهة برمجة تطبيقات Keras القابلة trainable ، والتي تكمن وراء معظم عمليات نقل التعلم وضبط تدفقات العمل.

بعد ذلك ، سنعرض سير العمل النموذجي من خلال أخذ نموذج تم اختباره مسبقًا على مجموعة بيانات ImageNet ، وإعادة تدريبه على مجموعة بيانات تصنيف Kaggle "cats vs dogs".

هذا مقتبس من Deep Learning with Python ومنشور مدونة 2016 "بناء نماذج قوية لتصنيف الصور باستخدام القليل جدًا من البيانات" .

طبقات التجميد: فهم السمة trainable

الطبقات والنماذج لها ثلاث سمات وزن:

  • weights هي قائمة بجميع متغيرات الأوزان للطبقة.
  • trainable_weights هي قائمة تلك التي من المفترض أن يتم تحديثها (عبر النسب المتدرج) لتقليل الخسارة أثناء التدريب.
  • non_trainable_weights هي قائمة الأشخاص الذين لم يتم تدريبهم. عادةً ما يتم تحديثها بواسطة النموذج أثناء التمرير الأمامي.

مثال: تحتوي الطبقة Dense على وزنين قابلين للتدريب (النواة والتحيز)

layer = keras.layers.Dense(3)
layer.build((None, 4))  # Create the weights

print("weights:", len(layer.weights))
print("trainable_weights:", len(layer.trainable_weights))
print("non_trainable_weights:", len(layer.non_trainable_weights))
weights: 2
trainable_weights: 2
non_trainable_weights: 0

بشكل عام ، جميع الأوزان هي أوزان قابلة للتدريب. الطبقة المدمجة الوحيدة التي تحتوي على أوزان غير قابلة للتدريب هي طبقة BatchNormalization . يستخدم أوزانًا غير قابلة للتدريب لتتبع متوسط ​​وتباين مدخلاته أثناء التدريب. لمعرفة كيفية استخدام الأوزان غير القابلة للتدريب في طبقاتك المخصصة ، راجع دليل كتابة طبقات جديدة من البداية .

مثال: BatchNormalization طبقة BatchNormalization على BatchNormalization للتدريب BatchNormalization للتدريب

layer = keras.layers.BatchNormalization()
layer.build((None, 4))  # Create the weights

print("weights:", len(layer.weights))
print("trainable_weights:", len(layer.trainable_weights))
print("non_trainable_weights:", len(layer.non_trainable_weights))
weights: 4
trainable_weights: 2
non_trainable_weights: 2

تتميز الطبقات والنماذج أيضًا بسمة منطقية trainable . يمكن تغيير قيمتها. Setting layer.trainable to False يحرك جميع أوزان الطبقة من قابل للتدريب إلى غير قابل للتدريب. هذا يسمى "تجميد" الطبقة: لن يتم تحديث حالة الطبقة المجمدة أثناء التدريب (إما عند التدريب باستخدام fit() أو عند التدريب باستخدام أي حلقة مخصصة تعتمد على trainable_weights لتطبيق تحديثات التدرج).

مثال: ضبط الإعداد trainable على False

layer = keras.layers.Dense(3)
layer.build((None, 4))  # Create the weights
layer.trainable = False  # Freeze the layer

print("weights:", len(layer.weights))
print("trainable_weights:", len(layer.trainable_weights))
print("non_trainable_weights:", len(layer.non_trainable_weights))
weights: 2
trainable_weights: 0
non_trainable_weights: 2

عندما يصبح الوزن القابل للتدريب غير قابل للتدريب ، فلن يتم تحديث قيمته أثناء التدريب.

# Make a model with 2 layers
layer1 = keras.layers.Dense(3, activation="relu")
layer2 = keras.layers.Dense(3, activation="sigmoid")
model = keras.Sequential([keras.Input(shape=(3,)), layer1, layer2])

# Freeze the first layer
layer1.trainable = False

# Keep a copy of the weights of layer1 for later reference
initial_layer1_weights_values = layer1.get_weights()

# Train the model
model.compile(optimizer="adam", loss="mse")
model.fit(np.random.random((2, 3)), np.random.random((2, 3)))

# Check that the weights of layer1 have not changed during training
final_layer1_weights_values = layer1.get_weights()
np.testing.assert_allclose(
    initial_layer1_weights_values[0], final_layer1_weights_values[0]
)
np.testing.assert_allclose(
    initial_layer1_weights_values[1], final_layer1_weights_values[1]
)
1/1 [==============================] - 1s 664ms/step - loss: 0.1025

لا تخلط بين سمة layer.trainable مع training الوسيطة في layer.__call__() (الذي يتحكم في ما إذا كان يجب على الطبقة تشغيل مسارها الأمامي في وضع الاستدلال أو وضع التدريب). لمزيد من المعلومات ، راجع الأسئلة الشائعة عن Keras .

الإعداد التكراري للسمة trainable

إذا قمت بتعيين trainable = False على نموذج أو على أي طبقة تحتوي على طبقات فرعية ، فإن كل الطبقات الفرعية تصبح غير قابلة للتدريب أيضًا.

مثال:

inner_model = keras.Sequential(
    [
        keras.Input(shape=(3,)),
        keras.layers.Dense(3, activation="relu"),
        keras.layers.Dense(3, activation="relu"),
    ]
)

model = keras.Sequential(
    [keras.Input(shape=(3,)), inner_model, keras.layers.Dense(3, activation="sigmoid"),]
)

model.trainable = False  # Freeze the outer model

assert inner_model.trainable == False  # All layers in `model` are now frozen
assert inner_model.layers[0].trainable == False  # `trainable` is propagated recursively

سير عمل النقل والتعلم النموذجي

يقودنا هذا إلى كيفية تنفيذ سير عمل تعلم النقل النموذجي في Keras:

  1. قم بتجسيد نموذج أساسي وتحميل الأوزان المدربة مسبقًا فيه.
  2. قم بتجميد كل الطبقات في النموذج الأساسي عن طريق تعيين قابل trainable = False .
  3. قم بإنشاء نموذج جديد أعلى ناتج طبقة واحدة (أو عدة طبقات) من النموذج الأساسي.
  4. تدريب نموذجك الجديد على مجموعة البيانات الجديدة.

لاحظ أن سير العمل البديل والأكثر خفة يمكن أن يكون أيضًا:

  1. قم بتجسيد نموذج أساسي وتحميل الأوزان المدربة مسبقًا فيه.
  2. قم بتشغيل مجموعة البيانات الجديدة من خلالها وسجل ناتج طبقة واحدة (أو عدة طبقات) من النموذج الأساسي. وهذا ما يسمى استخراج الميزة .
  3. استخدم هذا الإخراج كبيانات إدخال لنموذج جديد أصغر.

تتمثل الميزة الرئيسية لسير العمل الثاني في أنك تقوم بتشغيل النموذج الأساسي مرة واحدة فقط على بياناتك ، بدلاً من تشغيلها مرة واحدة لكل فترة تدريب. لذلك فهو أسرع بكثير وأرخص.

ومع ذلك ، هناك مشكلة في سير العمل الثاني هذا ، وهي أنه لا يسمح لك بتعديل بيانات الإدخال الخاصة بنموذجك الجديد ديناميكيًا أثناء التدريب ، وهو أمر مطلوب عند القيام بزيادة البيانات ، على سبيل المثال. عادةً ما يتم استخدام التعلم عن طريق النقل للمهام عندما تحتوي مجموعة البيانات الجديدة على القليل جدًا من البيانات لتدريب نموذج كامل النطاق من البداية ، وفي مثل هذه السيناريوهات ، يكون زيادة البيانات أمرًا مهمًا للغاية. إذن في ما يلي ، سوف نركز على سير العمل الأول.

إليك ما يبدو عليه سير العمل الأول في Keras:

أولاً ، قم بإنشاء مثيل لنموذج أساسي بأوزان مُدرَّبة مسبقًا.

base_model = keras.applications.Xception(
    weights='imagenet',  # Load weights pre-trained on ImageNet.
    input_shape=(150, 150, 3),
    include_top=False)  # Do not include the ImageNet classifier at the top.

ثم قم بتجميد النموذج الأساسي.

base_model.trainable = False

قم بإنشاء نموذج جديد في الأعلى.

inputs = keras.Input(shape=(150, 150, 3))
# We make sure that the base_model is running in inference mode here,
# by passing `training=False`. This is important for fine-tuning, as you will
# learn in a few paragraphs.
x = base_model(inputs, training=False)
# Convert features of shape `base_model.output_shape[1:]` to vectors
x = keras.layers.GlobalAveragePooling2D()(x)
# A Dense classifier with a single unit (binary classification)
outputs = keras.layers.Dense(1)(x)
model = keras.Model(inputs, outputs)

تدريب النموذج على البيانات الجديدة.

model.compile(optimizer=keras.optimizers.Adam(),
              loss=keras.losses.BinaryCrossentropy(from_logits=True),
              metrics=[keras.metrics.BinaryAccuracy()])
model.fit(new_dataset, epochs=20, callbacks=..., validation_data=...)

الكون المثالى

بمجرد أن يتقارب النموذج الخاص بك مع البيانات الجديدة ، يمكنك محاولة إلغاء تجميد كل أو جزء من النموذج الأساسي وإعادة تدريب النموذج بالكامل من البداية إلى النهاية بمعدل تعلم منخفض للغاية.

هذه خطوة أخيرة اختيارية يمكن أن تمنحك تحسينات تدريجية. يمكن أن يؤدي أيضًا إلى فرط التجهيز السريع - ضع ذلك في الاعتبار.

من الأهمية بمكان القيام بهذه الخطوة فقط بعد تدريب النموذج ذي الطبقات المجمدة على التقارب. إذا قمت بخلط الطبقات القابلة للتدريب التي تمت تهيئتها عشوائيًا مع الطبقات القابلة للتدريب والتي تحتوي على ميزات مُدرَّبة مسبقًا ، فإن الطبقات المُهيأة عشوائيًا ستتسبب في تحديثات متدرجة كبيرة جدًا أثناء التدريب ، مما سيؤدي إلى تدمير الميزات التي تم تدريبها مسبقًا.

من المهم أيضًا استخدام معدل تعليم منخفض جدًا في هذه المرحلة ، لأنك تقوم بتدريب نموذج أكبر بكثير مما كان عليه في الجولة الأولى من التدريب ، على مجموعة بيانات تكون عادةً صغيرة جدًا. نتيجة لذلك ، فأنت معرض لخطر الإفراط في ارتداء الملابس بسرعة كبيرة إذا قمت بتطبيق تحديثات كبيرة للوزن. هنا ، تريد فقط إعادة ضبط الأوزان المعدة مسبقًا بطريقة تدريجية.

هذه هي كيفية تنفيذ الضبط الدقيق للنموذج الأساسي بأكمله:

# Unfreeze the base model
base_model.trainable = True

# It's important to recompile your model after you make any changes
# to the `trainable` attribute of any inner layer, so that your changes
# are take into account
model.compile(optimizer=keras.optimizers.Adam(1e-5),  # Very low learning rate
              loss=keras.losses.BinaryCrossentropy(from_logits=True),
              metrics=[keras.metrics.BinaryAccuracy()])

# Train end-to-end. Be careful to stop before you overfit!
model.fit(new_dataset, epochs=10, callbacks=..., validation_data=...)

ملاحظة مهمة حول compile() trainable

يُقصد باستدعاء compile() في نموذج "تجميد" سلوك ذلك النموذج. هذا يعني أنه يجب الحفاظ على قيم السمات trainable في وقت تجميع النموذج طوال عمر هذا النموذج ، حتى يتم استدعاء compile مرة أخرى. ومن ثم ، إذا قمت بتغيير أي قيمة trainable ، فتأكد من استدعاء compile() مرة أخرى في النموذج الخاص بك لأخذ تغييراتك في الاعتبار.

ملاحظات مهمة حول طبقة BatchNormalization

تحتوي العديد من نماذج الصور على طبقات BatchNormalization . هذه الطبقة هي حالة خاصة لكل عدد يمكن تخيله. وهنا بعض الأشياء أن نأخذ في الاعتبار.

  • يحتوي BatchNormalization على 2 من الأوزان غير القابلة للتدريب والتي يتم تحديثها أثناء التدريب. هذه هي المتغيرات التي تتبع متوسط ​​وتباين المدخلات.
  • عند تعيين bn_layer.trainable = False ، BatchNormalization طبقة BatchNormalization في وضع الاستدلال ، ولن تقوم بتحديث إحصائيات المتوسط ​​والتباين. هذا ليس هو الحال بالنسبة للطبقات الأخرى بشكل عام ، حيث أن التدريب على الوزن وأنماط الاستدلال / التدريب هما مفهومان متعامدان . لكن الاثنين مرتبطان في حالة طبقة BatchNormalization .
  • عندما تقوم BatchNormalization تجميد نموذج يحتوي على طبقات BatchNormalization أجل إجراء ضبط دقيق ، يجب أن تحتفظ بطبقات BatchNormalization في وضع الاستدلال بتمرير training=False عند استدعاء النموذج الأساسي. وإلا فإن التحديثات المطبقة على الأوزان غير القابلة للتدريب ستدمر فجأة ما تعلمه النموذج.

سترى هذا النمط قيد التنفيذ في المثال الشامل في نهاية هذا الدليل.

نقل التعلم والضبط الدقيق باستخدام حلقة تدريب مخصصة

إذا كنت تستخدم حلقة التدريب منخفضة المستوى بدلاً من fit() ، فإن سير العمل يظل كما هو. يجب أن تكون حريصًا على مراعاة نموذج القائمة فقط model.trainable_weights عند تطبيق تحديثات التدرج:

# Create base model
base_model = keras.applications.Xception(
    weights='imagenet',
    input_shape=(150, 150, 3),
    include_top=False)
# Freeze base model
base_model.trainable = False

# Create new model on top.
inputs = keras.Input(shape=(150, 150, 3))
x = base_model(inputs, training=False)
x = keras.layers.GlobalAveragePooling2D()(x)
outputs = keras.layers.Dense(1)(x)
model = keras.Model(inputs, outputs)

loss_fn = keras.losses.BinaryCrossentropy(from_logits=True)
optimizer = keras.optimizers.Adam()

# Iterate over the batches of a dataset.
for inputs, targets in new_dataset:
    # Open a GradientTape.
    with tf.GradientTape() as tape:
        # Forward pass.
        predictions = model(inputs)
        # Compute the loss value for this batch.
        loss_value = loss_fn(targets, predictions)

    # Get gradients of loss wrt the *trainable* weights.
    gradients = tape.gradient(loss_value, model.trainable_weights)
    # Update the weights of the model.
    optimizer.apply_gradients(zip(gradients, model.trainable_weights))

وبالمثل للضبط الدقيق.

مثال شامل: ضبط نموذج تصنيف الصور على مجموعة بيانات قطط مقابل كلاب

لترسيخ هذه المفاهيم ، دعنا نرشدك إلى مثال ملموس لتعلم النقل الشامل والضبط الدقيق. سنقوم بتحميل نموذج Xception ، الذي تم تدريبه مسبقًا على ImageNet ، واستخدامه في مجموعة بيانات تصنيف Kaggle "cats vs. Dog".

الحصول على البيانات

أولاً ، لنجلب مجموعة بيانات القطط مقابل الكلاب باستخدام TFDS. إذا كانت لديك مجموعة بيانات خاصة بك ، فربما ترغب في استخدام الأداة المساعدة tf.keras.preprocessing.image_dataset_from_directory لإنشاء كائنات مجموعة بيانات معنونة مماثلة من مجموعة من الصور الموجودة على القرص tf.keras.preprocessing.image_dataset_from_directory في مجلدات خاصة بالفئة.

يعد التعلم عن طريق النقل مفيدًا للغاية عند العمل مع مجموعات البيانات الصغيرة جدًا. للحفاظ على مجموعة البيانات الخاصة بنا صغيرة ، سنستخدم 40٪ من بيانات التدريب الأصلية (25000 صورة) للتدريب ، و 10٪ للتحقق من الصحة ، و 10٪ للاختبار.

import tensorflow_datasets as tfds

tfds.disable_progress_bar()

train_ds, validation_ds, test_ds = tfds.load(
    "cats_vs_dogs",
    # Reserve 10% for validation and 10% for test
    split=["train[:40%]", "train[40%:50%]", "train[50%:60%]"],
    as_supervised=True,  # Include labels
)

print("Number of training samples: %d" % tf.data.experimental.cardinality(train_ds))
print(
    "Number of validation samples: %d" % tf.data.experimental.cardinality(validation_ds)
)
print("Number of test samples: %d" % tf.data.experimental.cardinality(test_ds))
Number of training samples: 9305
Number of validation samples: 2326
Number of test samples: 2326

هذه هي أول 9 صور في مجموعة بيانات التدريب - كما ترى ، جميعها بأحجام مختلفة.

import matplotlib.pyplot as plt

plt.figure(figsize=(10, 10))
for i, (image, label) in enumerate(train_ds.take(9)):
    ax = plt.subplot(3, 3, i + 1)
    plt.imshow(image)
    plt.title(int(label))
    plt.axis("off")

بي إن جي

يمكننا أن نرى أيضًا أن التصنيف 1 هو "كلب" وأن التصنيف 0 هو "قطة".

توحيد البيانات

صورنا الخام لها أحجام متنوعة. بالإضافة إلى ذلك ، يتكون كل بكسل من 3 قيم صحيحة بين 0 و 255 (قيم مستوى RGB). هذا ليس مناسبًا تمامًا لتغذية الشبكة العصبية. نحتاج إلى القيام بأمرين:

  • التوحيد إلى حجم صورة ثابت. نختار 150x150.
  • تطبيع قيم البكسل بين -1 و 1. سنفعل ذلك باستخدام طبقة Normalization كجزء من النموذج نفسه.

بشكل عام ، من الممارسات الجيدة تطوير النماذج التي تأخذ البيانات الأولية كمدخلات ، على عكس النماذج التي تأخذ بيانات مُعالجة مسبقًا. والسبب هو أنه إذا كان نموذجك يتوقع بيانات مُعالجة مسبقًا ، ففي أي وقت تقوم فيه بتصدير النموذج الخاص بك لاستخدامه في مكان آخر (في متصفح الويب ، في تطبيق جوال) ، ستحتاج إلى إعادة تنفيذ نفس خط أنابيب المعالجة المسبقة بالضبط. هذا يصبح صعبًا جدًا بسرعة كبيرة. لذلك يجب أن نقوم بأقل قدر ممكن من المعالجة المسبقة قبل أن نصل إلى النموذج.

هنا ، سنقوم بتغيير حجم الصورة في خط أنابيب البيانات (لأن الشبكة العصبية العميقة يمكنها فقط معالجة مجموعات متجاورة من البيانات) ، وسنقوم بقياس قيمة الإدخال كجزء من النموذج ، عندما نقوم بإنشائه.

دعنا نغير حجم الصور إلى 150x150:

size = (150, 150)

train_ds = train_ds.map(lambda x, y: (tf.image.resize(x, size), y))
validation_ds = validation_ds.map(lambda x, y: (tf.image.resize(x, size), y))
test_ds = test_ds.map(lambda x, y: (tf.image.resize(x, size), y))

إلى جانب ذلك ، دعنا نجمع البيانات ونستخدم التخزين المؤقت والجلب المسبق لتحسين سرعة التحميل.

batch_size = 32

train_ds = train_ds.cache().batch(batch_size).prefetch(buffer_size=10)
validation_ds = validation_ds.cache().batch(batch_size).prefetch(buffer_size=10)
test_ds = test_ds.cache().batch(batch_size).prefetch(buffer_size=10)

استخدام زيادة البيانات العشوائية

عندما لا يكون لديك مجموعة بيانات صور كبيرة ، فمن الممارسات الجيدة إدخال تنوع العينة بشكل مصطنع عن طريق تطبيق تحويلات عشوائية لكنها واقعية على صور التدريب ، مثل التقليب الأفقي العشوائي أو التدوير العشوائي الصغير. يساعد هذا في تعريض النموذج لجوانب مختلفة من بيانات التدريب مع إبطاء فرط التجهيز.

from tensorflow import keras
from tensorflow.keras import layers

data_augmentation = keras.Sequential(
    [
        layers.experimental.preprocessing.RandomFlip("horizontal"),
        layers.experimental.preprocessing.RandomRotation(0.1),
    ]
)

دعنا نتخيل كيف تبدو الصورة الأولى للدفعة الأولى بعد التحولات العشوائية المختلفة:

import numpy as np

for images, labels in train_ds.take(1):
    plt.figure(figsize=(10, 10))
    first_image = images[0]
    for i in range(9):
        ax = plt.subplot(3, 3, i + 1)
        augmented_image = data_augmentation(
            tf.expand_dims(first_image, 0), training=True
        )
        plt.imshow(augmented_image[0].numpy().astype("int32"))
        plt.title(int(labels[0]))
        plt.axis("off")

بي إن جي

بناء نموذج

الآن دعونا نبني نموذجًا يتبع المخطط الذي شرحناه سابقًا.

لاحظ أن:

  • نضيف طبقة Normalization قيم الإدخال (مبدئيًا في النطاق [0, 255] ) إلى النطاق [-1, 1] .
  • نضيف طبقة Dropout قبل طبقة التصنيف ، من أجل التنظيم.
  • نتأكد من اجتياز training=False عند استدعاء النموذج الأساسي ، بحيث يتم تشغيله في وضع الاستدلال ، بحيث لا يتم تحديث إحصاءات الدُفعة حتى بعد إلغاء تجميد النموذج الأساسي للضبط الدقيق.
base_model = keras.applications.Xception(
    weights="imagenet",  # Load weights pre-trained on ImageNet.
    input_shape=(150, 150, 3),
    include_top=False,
)  # Do not include the ImageNet classifier at the top.

# Freeze the base_model
base_model.trainable = False

# Create new model on top
inputs = keras.Input(shape=(150, 150, 3))
x = data_augmentation(inputs)  # Apply random data augmentation

# Pre-trained Xception weights requires that input be normalized
# from (0, 255) to a range (-1., +1.), the normalization layer
# does the following, outputs = (inputs - mean) / sqrt(var)
norm_layer = keras.layers.experimental.preprocessing.Normalization()
mean = np.array([127.5] * 3)
var = mean ** 2
# Scale inputs to [-1, +1]
x = norm_layer(x)
norm_layer.set_weights([mean, var])

# The base model contains batchnorm layers. We want to keep them in inference mode
# when we unfreeze the base model for fine-tuning, so we make sure that the
# base_model is running in inference mode here.
x = base_model(x, training=False)
x = keras.layers.GlobalAveragePooling2D()(x)
x = keras.layers.Dropout(0.2)(x)  # Regularize with dropout
outputs = keras.layers.Dense(1)(x)
model = keras.Model(inputs, outputs)

model.summary()
Downloading data from https://storage.googleapis.com/tensorflow/keras-applications/xception/xception_weights_tf_dim_ordering_tf_kernels_notop.h5
83689472/83683744 [==============================] - 1s 0us/step
Model: "model"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
input_5 (InputLayer)         [(None, 150, 150, 3)]     0         
_________________________________________________________________
sequential_3 (Sequential)    (None, 150, 150, 3)       0         
_________________________________________________________________
normalization (Normalization (None, 150, 150, 3)       7         
_________________________________________________________________
xception (Functional)        (None, 5, 5, 2048)        20861480  
_________________________________________________________________
global_average_pooling2d (Gl (None, 2048)              0         
_________________________________________________________________
dropout (Dropout)            (None, 2048)              0         
_________________________________________________________________
dense_7 (Dense)              (None, 1)                 2049      
=================================================================
Total params: 20,863,536
Trainable params: 2,049
Non-trainable params: 20,861,487
_________________________________________________________________

تدريب الطبقة العليا

model.compile(
    optimizer=keras.optimizers.Adam(),
    loss=keras.losses.BinaryCrossentropy(from_logits=True),
    metrics=[keras.metrics.BinaryAccuracy()],
)

epochs = 20
model.fit(train_ds, epochs=epochs, validation_data=validation_ds)
Epoch 1/20
291/291 [==============================] - 20s 49ms/step - loss: 0.2226 - binary_accuracy: 0.8972 - val_loss: 0.0805 - val_binary_accuracy: 0.9703
Epoch 2/20
291/291 [==============================] - 8s 29ms/step - loss: 0.1246 - binary_accuracy: 0.9464 - val_loss: 0.0757 - val_binary_accuracy: 0.9712
Epoch 3/20
291/291 [==============================] - 8s 29ms/step - loss: 0.1153 - binary_accuracy: 0.9480 - val_loss: 0.0724 - val_binary_accuracy: 0.9733
Epoch 4/20
291/291 [==============================] - 8s 29ms/step - loss: 0.1055 - binary_accuracy: 0.9575 - val_loss: 0.0753 - val_binary_accuracy: 0.9721
Epoch 5/20
291/291 [==============================] - 8s 28ms/step - loss: 0.1026 - binary_accuracy: 0.9589 - val_loss: 0.0750 - val_binary_accuracy: 0.9703
Epoch 6/20
291/291 [==============================] - 8s 28ms/step - loss: 0.1022 - binary_accuracy: 0.9587 - val_loss: 0.0723 - val_binary_accuracy: 0.9716
Epoch 7/20
291/291 [==============================] - 8s 28ms/step - loss: 0.1009 - binary_accuracy: 0.9570 - val_loss: 0.0731 - val_binary_accuracy: 0.9708
Epoch 8/20
291/291 [==============================] - 8s 28ms/step - loss: 0.0947 - binary_accuracy: 0.9576 - val_loss: 0.0726 - val_binary_accuracy: 0.9716
Epoch 9/20
291/291 [==============================] - 8s 28ms/step - loss: 0.0872 - binary_accuracy: 0.9624 - val_loss: 0.0720 - val_binary_accuracy: 0.9712
Epoch 10/20
291/291 [==============================] - 8s 28ms/step - loss: 0.0892 - binary_accuracy: 0.9622 - val_loss: 0.0711 - val_binary_accuracy: 0.9716
Epoch 11/20
291/291 [==============================] - 8s 29ms/step - loss: 0.0987 - binary_accuracy: 0.9608 - val_loss: 0.0752 - val_binary_accuracy: 0.9712
Epoch 12/20
291/291 [==============================] - 8s 29ms/step - loss: 0.0962 - binary_accuracy: 0.9595 - val_loss: 0.0715 - val_binary_accuracy: 0.9738
Epoch 13/20
291/291 [==============================] - 8s 28ms/step - loss: 0.0972 - binary_accuracy: 0.9606 - val_loss: 0.0700 - val_binary_accuracy: 0.9725
Epoch 14/20
291/291 [==============================] - 8s 28ms/step - loss: 0.1019 - binary_accuracy: 0.9568 - val_loss: 0.0779 - val_binary_accuracy: 0.9690
Epoch 15/20
291/291 [==============================] - 8s 28ms/step - loss: 0.0929 - binary_accuracy: 0.9614 - val_loss: 0.0700 - val_binary_accuracy: 0.9729
Epoch 16/20
291/291 [==============================] - 8s 28ms/step - loss: 0.0937 - binary_accuracy: 0.9610 - val_loss: 0.0698 - val_binary_accuracy: 0.9742
Epoch 17/20
291/291 [==============================] - 8s 28ms/step - loss: 0.0945 - binary_accuracy: 0.9613 - val_loss: 0.0671 - val_binary_accuracy: 0.9759
Epoch 18/20
291/291 [==============================] - 8s 28ms/step - loss: 0.0868 - binary_accuracy: 0.9612 - val_loss: 0.0692 - val_binary_accuracy: 0.9738
Epoch 19/20
291/291 [==============================] - 8s 28ms/step - loss: 0.0871 - binary_accuracy: 0.9647 - val_loss: 0.0691 - val_binary_accuracy: 0.9746
Epoch 20/20
291/291 [==============================] - 8s 28ms/step - loss: 0.0922 - binary_accuracy: 0.9603 - val_loss: 0.0721 - val_binary_accuracy: 0.9738
<tensorflow.python.keras.callbacks.History at 0x7fb73f231860>

قم بإجراء جولة من الضبط الدقيق للنموذج بأكمله

أخيرًا ، دعنا نلغي تجميد النموذج الأساسي وندرب النموذج بأكمله من البداية إلى النهاية بمعدل تعلم منخفض.

الأهم من ذلك ، على الرغم من أن النموذج الأساسي يصبح قابلاً للتدريب ، إلا أنه لا يزال يعمل في وضع الاستدلال منذ أن اجتزنا training=False عند استدعائه عندما قمنا ببناء النموذج. هذا يعني أن طبقات تسوية الدُفعات الموجودة بالداخل لن تقوم بتحديث إحصائيات الدُفعة الخاصة بها. إذا فعلوا ذلك ، فإنهم سيدمرون التمثيلات التي تعلمها النموذج حتى الآن.

# Unfreeze the base_model. Note that it keeps running in inference mode
# since we passed `training=False` when calling it. This means that
# the batchnorm layers will not update their batch statistics.
# This prevents the batchnorm layers from undoing all the training
# we've done so far.
base_model.trainable = True
model.summary()

model.compile(
    optimizer=keras.optimizers.Adam(1e-5),  # Low learning rate
    loss=keras.losses.BinaryCrossentropy(from_logits=True),
    metrics=[keras.metrics.BinaryAccuracy()],
)

epochs = 10
model.fit(train_ds, epochs=epochs, validation_data=validation_ds)
Model: "model"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
input_5 (InputLayer)         [(None, 150, 150, 3)]     0         
_________________________________________________________________
sequential_3 (Sequential)    (None, 150, 150, 3)       0         
_________________________________________________________________
normalization (Normalization (None, 150, 150, 3)       7         
_________________________________________________________________
xception (Functional)        (None, 5, 5, 2048)        20861480  
_________________________________________________________________
global_average_pooling2d (Gl (None, 2048)              0         
_________________________________________________________________
dropout (Dropout)            (None, 2048)              0         
_________________________________________________________________
dense_7 (Dense)              (None, 1)                 2049      
=================================================================
Total params: 20,863,536
Trainable params: 20,809,001
Non-trainable params: 54,535
_________________________________________________________________
Epoch 1/10
291/291 [==============================] - 43s 133ms/step - loss: 0.0814 - binary_accuracy: 0.9677 - val_loss: 0.0527 - val_binary_accuracy: 0.9776
Epoch 2/10
291/291 [==============================] - 38s 129ms/step - loss: 0.0544 - binary_accuracy: 0.9796 - val_loss: 0.0537 - val_binary_accuracy: 0.9776
Epoch 3/10
291/291 [==============================] - 38s 129ms/step - loss: 0.0481 - binary_accuracy: 0.9822 - val_loss: 0.0471 - val_binary_accuracy: 0.9789
Epoch 4/10
291/291 [==============================] - 38s 129ms/step - loss: 0.0324 - binary_accuracy: 0.9871 - val_loss: 0.0551 - val_binary_accuracy: 0.9807
Epoch 5/10
291/291 [==============================] - 38s 129ms/step - loss: 0.0298 - binary_accuracy: 0.9899 - val_loss: 0.0447 - val_binary_accuracy: 0.9807
Epoch 6/10
291/291 [==============================] - 38s 129ms/step - loss: 0.0262 - binary_accuracy: 0.9901 - val_loss: 0.0469 - val_binary_accuracy: 0.9824
Epoch 7/10
291/291 [==============================] - 38s 129ms/step - loss: 0.0242 - binary_accuracy: 0.9918 - val_loss: 0.0539 - val_binary_accuracy: 0.9798
Epoch 8/10
291/291 [==============================] - 38s 129ms/step - loss: 0.0153 - binary_accuracy: 0.9935 - val_loss: 0.0644 - val_binary_accuracy: 0.9794
Epoch 9/10
291/291 [==============================] - 38s 129ms/step - loss: 0.0175 - binary_accuracy: 0.9934 - val_loss: 0.0496 - val_binary_accuracy: 0.9819
Epoch 10/10
291/291 [==============================] - 38s 129ms/step - loss: 0.0171 - binary_accuracy: 0.9936 - val_loss: 0.0496 - val_binary_accuracy: 0.9828
<tensorflow.python.keras.callbacks.History at 0x7fb74f74f940>

بعد 10 حقب ، يكسبنا الضبط الدقيق تحسينًا جيدًا هنا.