העבר למידה וכוונון עדין

קל לארגן דפים בעזרת אוספים אפשר לשמור ולסווג תוכן על סמך ההעדפות שלך.

הצג באתר TensorFlow.org הפעל בגוגל קולאב צפה במקור ב-GitHub הורד מחברת

להכין

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

מבוא

למידת העברת מורכב לקיחת תכונות למדו על בעיה אחת, ומינוף אותם על בעיה חדשה, דומה. לדוגמה, תכונות מדגם שלמד לזהות דביבונים עשויות להיות שימושיות כדי להניע מודל שנועד לזהות טנוקים.

למידה של העברה מתבצעת בדרך כלל עבור משימות שבהן יש למערך הנתונים שלך מעט מדי נתונים כדי לאמן מודל בקנה מידה מלא מאפס.

הגלגול הנפוץ ביותר של למידת העברה בהקשר של למידה עמוקה הוא זרימת העבודה הבאה:

  1. קח שכבות מדגם שעבר הכשרה.
  2. הקפיא אותם, כדי להימנע מהשמדת כל המידע שהם מכילים במהלך סבבי אימון עתידיים.
  3. הוסף כמה שכבות חדשות וניתנות לאילוף על גבי השכבות הקפואות. הם ילמדו להפוך את התכונות הישנות לתחזיות על מערך נתונים חדש.
  4. אמן את השכבות החדשות במערך הנתונים שלך.

כצעד אחרון, לא חובה, הוא כוונון עדין, אשר מורכב של והפשרת המודל כולו שקבל לעיל (או חלק ממנה), ו הכשרה מחדש אותו על הנתונים החדשים עם שיעור למידה נמוך מאוד. זה יכול להשיג שיפורים משמעותיים, על ידי התאמה הדרגתית של התכונות שהוכשרו מראש לנתונים החדשים.

ראשית, אנחנו נלך על Keras trainable API בפירוט, אשר ביסוד למידת ההעברה ביותר & זרימות העבודה-כוונון עדין.

לאחר מכן, נדגים את זרימת העבודה האופיינית על ידי נטילת מודל שהוכשר מראש על מערך הנתונים של ImageNet, ואימון מחדש שלו במערך הנתונים הסיווג "חתולים לעומת כלבים" של Kaggle.

זו מותאמת למידה עמוק עם Python ו- בפוסט בבלוג 2016 "בניית מודלים לסיווג תמונה רבת עוצמה באמצעות נתונים מעט מאוד" .

שכבות הקפאה: הבנה trainable התכונה

לשכבות ולדגמים יש שלוש תכונות משקל:

  • weights הוא רשימה של כל המשתנים משקולות של השכבה.
  • trainable_weights הוא ברשימה של אלה שאמורים להתעדכן (דרך ממוצא שיפוע) כדי למזער את ההפסד במהלך אימונים.
  • non_trainable_weights הוא ברשימה של אלה שאינם אמורים להיות מאומנים. בדרך כלל הם מתעדכנים על ידי הדגם במהלך המעבר קדימה.

דוגמא: Dense שכבה יש 2 משקולות שאפשר לאלף (קרנל הטיה)

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 שכבה יש 2 משקולות שאפשר לאלף ו 2 משקולות הלא שאפשר לאלף

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 . ניתן לשנות את ערכו. הגדרת layer.trainable כדי 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 640ms/step - loss: 0.0945

אל תבלבל את layer.trainable מאפיין עם הטיעון training ב layer.__call__() (אשר קובעת אם השכבה חייבת לרוץ לעבור קדימה שלה במצב היקש או מצב אימונים). לקבלת מידע נוסף, ראה שאלות נפוצות Keras .

הגדרה רקורסיבית של trainable תכונה

אם תגדיר trainable = False על מודל או על כל השכבה כי יש sublayers, כל הילדים שכבות הופכים בלתי שאפשר לאלף גם כן.

דוגמא:

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 השכבה תפעל במצב היקש, ולא לעדכן הסטטיסטיקה הממוצעת & השונה שלה. זהו לא המקרה עבור שכבות אחרות בכלל, כפי trainability משקל & היקש / מצבי אימון הם שני מושגים מאונכים . אבל השניים קשורים במקרה של 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.

מקבל את הנתונים

ראשית, בואו נביא את מערך הנתונים של חתולים לעומת כלבים באמצעות TFDS. אם יש לך הנתונים שלך, אתה בטח רוצה להשתמש בכלי השירות tf.keras.preprocessing.image_dataset_from_directory ליצור במערך דומה שכותרתו עצמים מתוך קבוצה של תמונות בדיסק הגיש בתיקיות ספציפיות בכיתה.

למידת העברה שימושית ביותר כאשר עובדים עם מערכי נתונים קטנים מאוד. כדי לשמור על מערך הנתונים שלנו קטן, נשתמש ב-40% מנתוני ההדרכה המקוריים (25,000 תמונות) לאימון, 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")

png

אנו יכולים גם לראות שתווית 1 היא "כלב" ותווית 0 היא "חתול".

סטנדרטיזציה של הנתונים

לתמונות הגולמיות שלנו יש מגוון גדלים. בנוסף, כל פיקסל מורכב מ-3 ערכי מספרים שלמים בין 0 ל-255 (ערכי רמת RGB). זה לא מתאים להזנה של רשת עצבית. אנחנו צריכים לעשות 2 דברים:

  • תקן לגודל תמונה קבוע. אנו בוחרים 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.RandomFlip("horizontal"), layers.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")
2021-09-01 18:45:34.772284: W tensorflow/core/kernels/data/cache_dataset_ops.cc:768] The calling iterator did not fully read the dataset being cached. In order to avoid unexpected truncation of the dataset, the partially cached contents of the dataset  will be discarded. This can happen if you have an input pipeline similar to `dataset.cache().take(k).repeat()`. You should use `dataset.take(k).cache().repeat()` instead.

png

לבנות מודל

עכשיו בואו נבנה מודל שעוקב אחר התוכנית שהסברנו קודם לכן.

ציין זאת:

  • אנו מוסיפים Rescaling שכבה לערכי קלט מיידה (בתחילה [0, 255] הטווח) אל [-1, 1] הטווח.
  • אנו מוסיפים Dropout שכבה לפני שכבת הסיווג, עבור הסדרה.
  • אנו מקפידים להעביר training=False כאשר קוראים את דגם הבסיס, כך הוא פועל במצב היקש, כך הסטטיסטיקה batchnorm לא להתעדכן גם לאחר שאנו להפשיר את דגם הבסיס עבור כוונון עדין.
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 scaled
# from (0, 255) to a range of (-1., +1.), the rescaling layer
# outputs: `(inputs * scale) + offset`
scale_layer = keras.layers.Rescaling(scale=1 / 127.5, offset=-1)
x = scale_layer(x)

# 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 [==============================] - 2s 0us/step
83697664/83683744 [==============================] - 2s 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         
_________________________________________________________________
rescaling (Rescaling)        (None, 150, 150, 3)       0         
_________________________________________________________________
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,529
Trainable params: 2,049
Non-trainable params: 20,861,480
_________________________________________________________________

אימון השכבה העליונה

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
151/291 [==============>...............] - ETA: 3s - loss: 0.1979 - binary_accuracy: 0.9096
Corrupt JPEG data: 65 extraneous bytes before marker 0xd9
268/291 [==========================>...] - ETA: 1s - loss: 0.1663 - binary_accuracy: 0.9269
Corrupt JPEG data: 239 extraneous bytes before marker 0xd9
282/291 [============================>.] - ETA: 0s - loss: 0.1628 - binary_accuracy: 0.9284
Corrupt JPEG data: 1153 extraneous bytes before marker 0xd9
Corrupt JPEG data: 228 extraneous bytes before marker 0xd9
291/291 [==============================] - ETA: 0s - loss: 0.1620 - binary_accuracy: 0.9286
Corrupt JPEG data: 2226 extraneous bytes before marker 0xd9
291/291 [==============================] - 29s 63ms/step - loss: 0.1620 - binary_accuracy: 0.9286 - val_loss: 0.0814 - val_binary_accuracy: 0.9686
Epoch 2/20
291/291 [==============================] - 8s 29ms/step - loss: 0.1178 - binary_accuracy: 0.9511 - val_loss: 0.0785 - val_binary_accuracy: 0.9695
Epoch 3/20
291/291 [==============================] - 9s 30ms/step - loss: 0.1121 - binary_accuracy: 0.9536 - val_loss: 0.0748 - val_binary_accuracy: 0.9712
Epoch 4/20
291/291 [==============================] - 9s 29ms/step - loss: 0.1082 - binary_accuracy: 0.9554 - val_loss: 0.0754 - val_binary_accuracy: 0.9703
Epoch 5/20
291/291 [==============================] - 8s 29ms/step - loss: 0.1034 - binary_accuracy: 0.9570 - val_loss: 0.0721 - val_binary_accuracy: 0.9725
Epoch 6/20
291/291 [==============================] - 8s 29ms/step - loss: 0.0975 - binary_accuracy: 0.9602 - val_loss: 0.0748 - val_binary_accuracy: 0.9699
Epoch 7/20
291/291 [==============================] - 9s 29ms/step - loss: 0.0989 - binary_accuracy: 0.9595 - val_loss: 0.0732 - val_binary_accuracy: 0.9716
Epoch 8/20
291/291 [==============================] - 8s 29ms/step - loss: 0.1027 - binary_accuracy: 0.9566 - val_loss: 0.0787 - val_binary_accuracy: 0.9678
Epoch 9/20
291/291 [==============================] - 8s 29ms/step - loss: 0.0959 - binary_accuracy: 0.9614 - val_loss: 0.0734 - val_binary_accuracy: 0.9729
Epoch 10/20
291/291 [==============================] - 8s 29ms/step - loss: 0.0995 - binary_accuracy: 0.9588 - val_loss: 0.0717 - val_binary_accuracy: 0.9721
Epoch 11/20
291/291 [==============================] - 8s 29ms/step - loss: 0.0957 - binary_accuracy: 0.9612 - val_loss: 0.0731 - val_binary_accuracy: 0.9725
Epoch 12/20
291/291 [==============================] - 8s 29ms/step - loss: 0.0936 - binary_accuracy: 0.9622 - val_loss: 0.0751 - val_binary_accuracy: 0.9716
Epoch 13/20
291/291 [==============================] - 8s 29ms/step - loss: 0.0965 - binary_accuracy: 0.9610 - val_loss: 0.0821 - val_binary_accuracy: 0.9695
Epoch 14/20
291/291 [==============================] - 8s 29ms/step - loss: 0.0939 - binary_accuracy: 0.9618 - val_loss: 0.0742 - val_binary_accuracy: 0.9712
Epoch 15/20
291/291 [==============================] - 8s 29ms/step - loss: 0.0974 - binary_accuracy: 0.9585 - val_loss: 0.0771 - val_binary_accuracy: 0.9712
Epoch 16/20
291/291 [==============================] - 8s 29ms/step - loss: 0.0947 - binary_accuracy: 0.9621 - val_loss: 0.0823 - val_binary_accuracy: 0.9699
Epoch 17/20
291/291 [==============================] - 8s 29ms/step - loss: 0.0947 - binary_accuracy: 0.9625 - val_loss: 0.0718 - val_binary_accuracy: 0.9708
Epoch 18/20
291/291 [==============================] - 8s 29ms/step - loss: 0.0928 - binary_accuracy: 0.9616 - val_loss: 0.0738 - val_binary_accuracy: 0.9716
Epoch 19/20
291/291 [==============================] - 8s 29ms/step - loss: 0.0922 - binary_accuracy: 0.9644 - val_loss: 0.0743 - val_binary_accuracy: 0.9716
Epoch 20/20
291/291 [==============================] - 8s 29ms/step - loss: 0.0885 - binary_accuracy: 0.9635 - val_loss: 0.0745 - val_binary_accuracy: 0.9695
<keras.callbacks.History at 0x7f849a3b2950>

בצע סבב של כוונון עדין של הדגם כולו

לבסוף, בואו נשחרר את המודל הבסיסי ונאמן את המודל כולו מקצה לקצה עם קצב למידה נמוך.

חשוב לציין כי למרות דגם הבסיס הופך שאפשר לאלף, זה עדיין פועל במצב היקש מאז שעברנו 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         
_________________________________________________________________
rescaling (Rescaling)        (None, 150, 150, 3)       0         
_________________________________________________________________
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,529
Trainable params: 20,809,001
Non-trainable params: 54,528
_________________________________________________________________
Epoch 1/10
291/291 [==============================] - 43s 131ms/step - loss: 0.0802 - binary_accuracy: 0.9692 - val_loss: 0.0580 - val_binary_accuracy: 0.9764
Epoch 2/10
291/291 [==============================] - 37s 128ms/step - loss: 0.0542 - binary_accuracy: 0.9792 - val_loss: 0.0529 - val_binary_accuracy: 0.9764
Epoch 3/10
291/291 [==============================] - 37s 128ms/step - loss: 0.0400 - binary_accuracy: 0.9832 - val_loss: 0.0510 - val_binary_accuracy: 0.9798
Epoch 4/10
291/291 [==============================] - 37s 128ms/step - loss: 0.0313 - binary_accuracy: 0.9879 - val_loss: 0.0505 - val_binary_accuracy: 0.9819
Epoch 5/10
291/291 [==============================] - 37s 128ms/step - loss: 0.0272 - binary_accuracy: 0.9904 - val_loss: 0.0485 - val_binary_accuracy: 0.9807
Epoch 6/10
291/291 [==============================] - 37s 128ms/step - loss: 0.0284 - binary_accuracy: 0.9901 - val_loss: 0.0497 - val_binary_accuracy: 0.9824
Epoch 7/10
291/291 [==============================] - 37s 127ms/step - loss: 0.0198 - binary_accuracy: 0.9937 - val_loss: 0.0530 - val_binary_accuracy: 0.9802
Epoch 8/10
291/291 [==============================] - 37s 127ms/step - loss: 0.0173 - binary_accuracy: 0.9930 - val_loss: 0.0572 - val_binary_accuracy: 0.9819
Epoch 9/10
291/291 [==============================] - 37s 127ms/step - loss: 0.0113 - binary_accuracy: 0.9958 - val_loss: 0.0555 - val_binary_accuracy: 0.9837
Epoch 10/10
291/291 [==============================] - 37s 127ms/step - loss: 0.0091 - binary_accuracy: 0.9966 - val_loss: 0.0596 - val_binary_accuracy: 0.9832
<keras.callbacks.History at 0x7f83982d4cd0>

לאחר 10 עידנים, כוונון עדין משיג לנו כאן שיפור יפה.