Overfit ו-underfit

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

כמו תמיד, הקוד בדוגמה זו ישתמש ב-API של tf.keras , עליו תוכל ללמוד עוד במדריך TensorFlow Keras .

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

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

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

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

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

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

במחברת זו, נחקור מספר טכניקות רגוליזציה נפוצות, ונשתמש בהן כדי לשפר מודל סיווג.

להכין

לפני שתתחיל, ייבא את החבילות הדרושות:

import tensorflow as tf

from tensorflow.keras import layers
from tensorflow.keras import regularizers

print(tf.__version__)
2.8.0-rc1
!pip install git+https://github.com/tensorflow/docs

import tensorflow_docs as tfdocs
import tensorflow_docs.modeling
import tensorflow_docs.plots
from  IPython import display
from matplotlib import pyplot as plt

import numpy as np

import pathlib
import shutil
import tempfile
logdir = pathlib.Path(tempfile.mkdtemp())/"tensorboard_logs"
shutil.rmtree(logdir, ignore_errors=True)

מערך הנתונים של היגס

המטרה של מדריך זה היא לא לעשות פיזיקת חלקיקים, אז אל תתעכב על הפרטים של מערך הנתונים. הוא מכיל 11,000,000 דוגמאות, כל אחת עם 28 תכונות, ותווית מחלקה בינארית.

gz = tf.keras.utils.get_file('HIGGS.csv.gz', 'http://mlphysics.ics.uci.edu/data/higgs/HIGGS.csv.gz')
Downloading data from http://mlphysics.ics.uci.edu/data/higgs/HIGGS.csv.gz
2816409600/2816407858 [==============================] - 123s 0us/step
2816417792/2816407858 [==============================] - 123s 0us/step
FEATURES = 28

ניתן להשתמש במחלקה tf.data.experimental.CsvDataset לקריאת רשומות CSV ישירות מקובץ gzip ללא שלב ביניים של פירוק.

ds = tf.data.experimental.CsvDataset(gz,[float(),]*(FEATURES+1), compression_type="GZIP")

כיתת קורא ה-CSV מחזירה רשימה של סקלרים עבור כל רשומה. הפונקציה הבאה אורזת מחדש את רשימת הסקלרים לזוג (feature_vector, label).

def pack_row(*row):
  label = row[0]
  features = tf.stack(row[1:],1)
  return features, label

TensorFlow הוא היעיל ביותר כאשר הוא פועל על קבוצות גדולות של נתונים.

אז במקום לארוז מחדש כל שורה בנפרד, צור Dataset חדש שלוקח אצוות של 10,000 דוגמאות, החל את הפונקציה pack_row על כל אצווה, ולאחר מכן מפצל את האצווה לרשומות בודדות:

packed_ds = ds.batch(10000).map(pack_row).unbatch()

עיין בכמה מהרשומות מה- packed_ds החדש הזה.

התכונות אינן מנורמלות לחלוטין, אבל זה מספיק עבור הדרכה זו.

for features,label in packed_ds.batch(1000).take(1):
  print(features[0])
  plt.hist(features.numpy().flatten(), bins = 101)
tf.Tensor(
[ 0.8692932  -0.6350818   0.22569026  0.32747006 -0.6899932   0.75420225
 -0.24857314 -1.0920639   0.          1.3749921  -0.6536742   0.9303491
  1.1074361   1.1389043  -1.5781983  -1.0469854   0.          0.65792954
 -0.01045457 -0.04576717  3.1019614   1.35376     0.9795631   0.97807616
  0.92000484  0.72165745  0.98875093  0.87667835], shape=(28,), dtype=float32)

png

כדי לשמור על הדרכה קצרה יחסית, השתמש רק ב-1000 הדוגמאות הראשונות לאימות, וב-10,000 הבאות להדרכה:

N_VALIDATION = int(1e3)
N_TRAIN = int(1e4)
BUFFER_SIZE = int(1e4)
BATCH_SIZE = 500
STEPS_PER_EPOCH = N_TRAIN//BATCH_SIZE

השיטות Dataset.skip ו- Dataset.take כך.

במקביל, השתמש בשיטת Dataset.cache כדי להבטיח שהמטען לא יצטרך לקרוא מחדש את הנתונים מהקובץ בכל תקופה:

validate_ds = packed_ds.take(N_VALIDATION).cache()
train_ds = packed_ds.skip(N_VALIDATION).take(N_TRAIN).cache()
train_ds
<CacheDataset element_spec=(TensorSpec(shape=(28,), dtype=tf.float32, name=None), TensorSpec(shape=(), dtype=tf.float32, name=None))>

מערכי נתונים אלה מחזירים דוגמאות בודדות. השתמש בשיטת .batch כדי ליצור קבוצות בגודל מתאים לאימון. לפני האצווה, זכור גם .shuffle .repeat על ערכת האימונים.

validate_ds = validate_ds.batch(BATCH_SIZE)
train_ds = train_ds.shuffle(BUFFER_SIZE).repeat().batch(BATCH_SIZE)

הדגימו התאמה יתרה

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

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

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

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

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

כדי למצוא גודל דגם מתאים, עדיף להתחיל עם מעט שכבות ופרמטרים יחסית, ואז להתחיל להגדיל את גודל השכבות או להוסיף שכבות חדשות עד שתראה התשואה הולכת ופוחתת על אובדן האימות.

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

נוהל הדרכה

דגמים רבים מתאמנים טוב יותר אם מפחיתים בהדרגה את קצב הלמידה במהלך האימון. השתמש ב- optimizers.schedules כדי להפחית את קצב הלמידה לאורך זמן:

lr_schedule = tf.keras.optimizers.schedules.InverseTimeDecay(
  0.001,
  decay_steps=STEPS_PER_EPOCH*1000,
  decay_rate=1,
  staircase=False)

def get_optimizer():
  return tf.keras.optimizers.Adam(lr_schedule)

הקוד שלמעלה מגדיר schedules.InverseTimeDecay . InverseTimeDecay כדי להקטין בצורה היפרבולית את קצב הלמידה ל-1/2 מקצב הבסיס ב-1000 עידנים, 1/3 ב-2000 עידנים וכן הלאה.

step = np.linspace(0,100000)
lr = lr_schedule(step)
plt.figure(figsize = (8,6))
plt.plot(step/STEPS_PER_EPOCH, lr)
plt.ylim([0,max(plt.ylim())])
plt.xlabel('Epoch')
_ = plt.ylabel('Learning Rate')

png

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

ההדרכה להדרכה זו נמשכת תקופות קצרות רבות. כדי להפחית את רעש הרישום השתמש ב- tfdocs.EpochDots שפשוט מדפיס . עבור כל תקופה, ומערכת מלאה של מדדים כל 100 עידנים.

בשלב הבא callbacks.EarlyStopping . עצירה מוקדמת כדי למנוע זמני אימון ארוכים ומיותרים. שים לב שהקריאה חוזרת זו מוגדרת לנטר את val_binary_crossentropy , לא את val_loss . ההבדל הזה יהיה חשוב בהמשך.

השתמש ב- callbacks.TensorBoard כדי ליצור יומני TensorBoard עבור ההדרכה.

def get_callbacks(name):
  return [
    tfdocs.modeling.EpochDots(),
    tf.keras.callbacks.EarlyStopping(monitor='val_binary_crossentropy', patience=200),
    tf.keras.callbacks.TensorBoard(logdir/name),
  ]

באופן דומה כל דגם ישתמש באותן הגדרות Model.compile ו- Model.fit :

def compile_and_fit(model, name, optimizer=None, max_epochs=10000):
  if optimizer is None:
    optimizer = get_optimizer()
  model.compile(optimizer=optimizer,
                loss=tf.keras.losses.BinaryCrossentropy(from_logits=True),
                metrics=[
                  tf.keras.losses.BinaryCrossentropy(
                      from_logits=True, name='binary_crossentropy'),
                  'accuracy'])

  model.summary()

  history = model.fit(
    train_ds,
    steps_per_epoch = STEPS_PER_EPOCH,
    epochs=max_epochs,
    validation_data=validate_ds,
    callbacks=get_callbacks(name),
    verbose=0)
  return history

דגם זעיר

התחל בהכשרת דוגמנית:

tiny_model = tf.keras.Sequential([
    layers.Dense(16, activation='elu', input_shape=(FEATURES,)),
    layers.Dense(1)
])
size_histories = {}
size_histories['Tiny'] = compile_and_fit(tiny_model, 'sizes/Tiny')
Model: "sequential"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 dense (Dense)               (None, 16)                464       
                                                                 
 dense_1 (Dense)             (None, 1)                 17        
                                                                 
=================================================================
Total params: 481
Trainable params: 481
Non-trainable params: 0
_________________________________________________________________

Epoch: 0, accuracy:0.4961,  binary_crossentropy:0.7294,  loss:0.7294,  val_accuracy:0.4840,  val_binary_crossentropy:0.7200,  val_loss:0.7200,  
....................................................................................................
Epoch: 100, accuracy:0.5931,  binary_crossentropy:0.6279,  loss:0.6279,  val_accuracy:0.5860,  val_binary_crossentropy:0.6288,  val_loss:0.6288,  
....................................................................................................
Epoch: 200, accuracy:0.6157,  binary_crossentropy:0.6178,  loss:0.6178,  val_accuracy:0.6200,  val_binary_crossentropy:0.6134,  val_loss:0.6134,  
....................................................................................................
Epoch: 300, accuracy:0.6370,  binary_crossentropy:0.6086,  loss:0.6086,  val_accuracy:0.6220,  val_binary_crossentropy:0.6055,  val_loss:0.6055,  
....................................................................................................
Epoch: 400, accuracy:0.6522,  binary_crossentropy:0.6008,  loss:0.6008,  val_accuracy:0.6260,  val_binary_crossentropy:0.5997,  val_loss:0.5997,  
....................................................................................................
Epoch: 500, accuracy:0.6513,  binary_crossentropy:0.5946,  loss:0.5946,  val_accuracy:0.6480,  val_binary_crossentropy:0.5911,  val_loss:0.5911,  
....................................................................................................
Epoch: 600, accuracy:0.6636,  binary_crossentropy:0.5894,  loss:0.5894,  val_accuracy:0.6390,  val_binary_crossentropy:0.5898,  val_loss:0.5898,  
....................................................................................................
Epoch: 700, accuracy:0.6696,  binary_crossentropy:0.5852,  loss:0.5852,  val_accuracy:0.6530,  val_binary_crossentropy:0.5870,  val_loss:0.5870,  
....................................................................................................
Epoch: 800, accuracy:0.6706,  binary_crossentropy:0.5824,  loss:0.5824,  val_accuracy:0.6590,  val_binary_crossentropy:0.5850,  val_loss:0.5850,  
....................................................................................................
Epoch: 900, accuracy:0.6709,  binary_crossentropy:0.5796,  loss:0.5796,  val_accuracy:0.6680,  val_binary_crossentropy:0.5831,  val_loss:0.5831,  
....................................................................................................
Epoch: 1000, accuracy:0.6780,  binary_crossentropy:0.5769,  loss:0.5769,  val_accuracy:0.6530,  val_binary_crossentropy:0.5851,  val_loss:0.5851,  
....................................................................................................
Epoch: 1100, accuracy:0.6735,  binary_crossentropy:0.5752,  loss:0.5752,  val_accuracy:0.6620,  val_binary_crossentropy:0.5807,  val_loss:0.5807,  
....................................................................................................
Epoch: 1200, accuracy:0.6759,  binary_crossentropy:0.5729,  loss:0.5729,  val_accuracy:0.6620,  val_binary_crossentropy:0.5792,  val_loss:0.5792,  
....................................................................................................
Epoch: 1300, accuracy:0.6849,  binary_crossentropy:0.5716,  loss:0.5716,  val_accuracy:0.6450,  val_binary_crossentropy:0.5859,  val_loss:0.5859,  
....................................................................................................
Epoch: 1400, accuracy:0.6790,  binary_crossentropy:0.5695,  loss:0.5695,  val_accuracy:0.6700,  val_binary_crossentropy:0.5776,  val_loss:0.5776,  
....................................................................................................
Epoch: 1500, accuracy:0.6824,  binary_crossentropy:0.5681,  loss:0.5681,  val_accuracy:0.6730,  val_binary_crossentropy:0.5761,  val_loss:0.5761,  
....................................................................................................
Epoch: 1600, accuracy:0.6828,  binary_crossentropy:0.5669,  loss:0.5669,  val_accuracy:0.6690,  val_binary_crossentropy:0.5766,  val_loss:0.5766,  
....................................................................................................
Epoch: 1700, accuracy:0.6874,  binary_crossentropy:0.5657,  loss:0.5657,  val_accuracy:0.6600,  val_binary_crossentropy:0.5774,  val_loss:0.5774,  
....................................................................................................
Epoch: 1800, accuracy:0.6845,  binary_crossentropy:0.5655,  loss:0.5655,  val_accuracy:0.6780,  val_binary_crossentropy:0.5752,  val_loss:0.5752,  
....................................................................................................
Epoch: 1900, accuracy:0.6837,  binary_crossentropy:0.5644,  loss:0.5644,  val_accuracy:0.6790,  val_binary_crossentropy:0.5753,  val_loss:0.5753,  
....................................................................................................
Epoch: 2000, accuracy:0.6853,  binary_crossentropy:0.5632,  loss:0.5632,  val_accuracy:0.6780,  val_binary_crossentropy:0.5753,  val_loss:0.5753,  
....................................................................................................
Epoch: 2100, accuracy:0.6871,  binary_crossentropy:0.5625,  loss:0.5625,  val_accuracy:0.6670,  val_binary_crossentropy:0.5769,  val_loss:0.5769,  
...................................

עכשיו בדוק איך הדגם הצליח:

plotter = tfdocs.plots.HistoryPlotter(metric = 'binary_crossentropy', smoothing_std=10)
plotter.plot(size_histories)
plt.ylim([0.5, 0.7])
(0.5, 0.7)

png

דגם קטן

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

נסה שתי שכבות נסתרות עם 16 יחידות כל אחת:

small_model = tf.keras.Sequential([
    # `input_shape` is only required here so that `.summary` works.
    layers.Dense(16, activation='elu', input_shape=(FEATURES,)),
    layers.Dense(16, activation='elu'),
    layers.Dense(1)
])
size_histories['Small'] = compile_and_fit(small_model, 'sizes/Small')
Model: "sequential_1"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 dense_2 (Dense)             (None, 16)                464       
                                                                 
 dense_3 (Dense)             (None, 16)                272       
                                                                 
 dense_4 (Dense)             (None, 1)                 17        
                                                                 
=================================================================
Total params: 753
Trainable params: 753
Non-trainable params: 0
_________________________________________________________________

Epoch: 0, accuracy:0.4864,  binary_crossentropy:0.7769,  loss:0.7769,  val_accuracy:0.4930,  val_binary_crossentropy:0.7211,  val_loss:0.7211,  
....................................................................................................
Epoch: 100, accuracy:0.6386,  binary_crossentropy:0.6052,  loss:0.6052,  val_accuracy:0.6020,  val_binary_crossentropy:0.6177,  val_loss:0.6177,  
....................................................................................................
Epoch: 200, accuracy:0.6697,  binary_crossentropy:0.5829,  loss:0.5829,  val_accuracy:0.6310,  val_binary_crossentropy:0.6018,  val_loss:0.6018,  
....................................................................................................
Epoch: 300, accuracy:0.6838,  binary_crossentropy:0.5721,  loss:0.5721,  val_accuracy:0.6490,  val_binary_crossentropy:0.5940,  val_loss:0.5940,  
....................................................................................................
Epoch: 400, accuracy:0.6911,  binary_crossentropy:0.5656,  loss:0.5656,  val_accuracy:0.6430,  val_binary_crossentropy:0.5985,  val_loss:0.5985,  
....................................................................................................
Epoch: 500, accuracy:0.6930,  binary_crossentropy:0.5607,  loss:0.5607,  val_accuracy:0.6430,  val_binary_crossentropy:0.6028,  val_loss:0.6028,  
.........................

דגם בינוני

כעת נסה 3 שכבות נסתרות עם 64 יחידות כל אחת:

medium_model = tf.keras.Sequential([
    layers.Dense(64, activation='elu', input_shape=(FEATURES,)),
    layers.Dense(64, activation='elu'),
    layers.Dense(64, activation='elu'),
    layers.Dense(1)
])

ואמן את המודל באמצעות אותם נתונים:

size_histories['Medium']  = compile_and_fit(medium_model, "sizes/Medium")
Model: "sequential_2"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 dense_5 (Dense)             (None, 64)                1856      
                                                                 
 dense_6 (Dense)             (None, 64)                4160      
                                                                 
 dense_7 (Dense)             (None, 64)                4160      
                                                                 
 dense_8 (Dense)             (None, 1)                 65        
                                                                 
=================================================================
Total params: 10,241
Trainable params: 10,241
Non-trainable params: 0
_________________________________________________________________

Epoch: 0, accuracy:0.5017,  binary_crossentropy:0.6840,  loss:0.6840,  val_accuracy:0.4790,  val_binary_crossentropy:0.6723,  val_loss:0.6723,  
....................................................................................................
Epoch: 100, accuracy:0.7173,  binary_crossentropy:0.5221,  loss:0.5221,  val_accuracy:0.6470,  val_binary_crossentropy:0.6111,  val_loss:0.6111,  
....................................................................................................
Epoch: 200, accuracy:0.7884,  binary_crossentropy:0.4270,  loss:0.4270,  val_accuracy:0.6390,  val_binary_crossentropy:0.7045,  val_loss:0.7045,  
..............................................................

דגם גדול

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

large_model = tf.keras.Sequential([
    layers.Dense(512, activation='elu', input_shape=(FEATURES,)),
    layers.Dense(512, activation='elu'),
    layers.Dense(512, activation='elu'),
    layers.Dense(512, activation='elu'),
    layers.Dense(1)
])

ושוב, אמן את המודל באמצעות אותם נתונים:

size_histories['large'] = compile_and_fit(large_model, "sizes/large")
Model: "sequential_3"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 dense_9 (Dense)             (None, 512)               14848     
                                                                 
 dense_10 (Dense)            (None, 512)               262656    
                                                                 
 dense_11 (Dense)            (None, 512)               262656    
                                                                 
 dense_12 (Dense)            (None, 512)               262656    
                                                                 
 dense_13 (Dense)            (None, 1)                 513       
                                                                 
=================================================================
Total params: 803,329
Trainable params: 803,329
Non-trainable params: 0
_________________________________________________________________

Epoch: 0, accuracy:0.5145,  binary_crossentropy:0.7740,  loss:0.7740,  val_accuracy:0.4980,  val_binary_crossentropy:0.6793,  val_loss:0.6793,  
....................................................................................................
Epoch: 100, accuracy:1.0000,  binary_crossentropy:0.0020,  loss:0.0020,  val_accuracy:0.6600,  val_binary_crossentropy:1.8540,  val_loss:1.8540,  
....................................................................................................
Epoch: 200, accuracy:1.0000,  binary_crossentropy:0.0001,  loss:0.0001,  val_accuracy:0.6560,  val_binary_crossentropy:2.5293,  val_loss:2.5293,  
..........................

תכנן את הפסדי ההדרכה והאימות

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

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

בדוגמה זו, בדרך כלל, רק המודל "Tiny" מצליח להימנע לחלוטין מהתאמת יתר, וכל אחד מהדגמים הגדולים יותר מתאים את הנתונים מהר יותר. זה הופך להיות כל כך חמור עבור הדגם "large" שאתה צריך להעביר את העלילה ל-log-scale כדי לראות באמת מה קורה.

זה ניכר אם אתה מתווה ומשווה את מדדי האימות למדדי האימון.

  • זה נורמלי שיש הבדל קטן.
  • אם שני המדדים נעים באותו כיוון, הכל בסדר.
  • אם מדד האימות מתחיל לקפוא בזמן שמדד האימון ממשיך להשתפר, כנראה שאתה קרוב להתאמת יתר.
  • אם מדד האימות הולך בכיוון הלא נכון, ברור שהמודל מתאים יותר מדי.
plotter.plot(size_histories)
a = plt.xscale('log')
plt.xlim([5, max(plt.xlim())])
plt.ylim([0.5, 0.7])
plt.xlabel("Epochs [Log Scale]")
Text(0.5, 0, 'Epochs [Log Scale]')

png

הצג ב- TensorBoard

כל המודלים הללו כתבו יומני TensorBoard במהלך האימון.

פתח מציג TensorBoard מוטבע בתוך מחברת:

#docs_infra: no_execute

# Load the TensorBoard notebook extension
%load_ext tensorboard

# Open an embedded TensorBoard viewer
%tensorboard --logdir {logdir}/sizes

אתה יכול לראות את התוצאות של הפעלה קודמת של מחברת זו ב- TensorBoard.dev .

TensorBoard.dev היא חוויה מנוהלת לאירוח, מעקב ושיתוף של ניסויי ML עם כולם.

זה כלול גם ב- <iframe> מטעמי נוחות:

display.IFrame(
    src="https://tensorboard.dev/experiment/vW7jmmF9TmKmy3rbheMQpw/#scalars&_smoothingWeight=0.97",
    width="100%", height="800px")

אם ברצונך לשתף תוצאות של TensorBoard, תוכל להעלות את היומנים ל- TensorBoard.dev על ידי העתקת הפרטים הבאים לתא קוד.

tensorboard dev upload --logdir  {logdir}/sizes

אסטרטגיות למניעת התאמת יתר

לפני שנכנסים לתוכן של סעיף זה, העתק את יומני ההדרכה מהמודל "Tiny" למעלה, כדי להשתמש בהם כבסיס להשוואה.

shutil.rmtree(logdir/'regularizers/Tiny', ignore_errors=True)
shutil.copytree(logdir/'sizes/Tiny', logdir/'regularizers/Tiny')
PosixPath('/tmp/tmpn1rdh98q/tensorboard_logs/regularizers/Tiny')
regularizer_histories = {}
regularizer_histories['Tiny'] = size_histories['Tiny']

הוסף הסדרת משקל

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

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

  • L1 regularization , כאשר העלות הנוספת היא פרופורציונלית לערך המוחלט של מקדמי המשקולות (כלומר למה שנקרא "נורמת L1" של המשקולות).

  • L2 regularization , כאשר העלות הנוספת היא פרופורציונלית לריבוע ערך מקדמי המשקולות (כלומר למה שנקרא "נורמת L2" בריבוע של המשקולות). הסדרת L2 נקראת גם דעיכה במשקל בהקשר של רשתות עצביות. אל תתנו לשם השונה לבלבל אתכם: דעיכה במשקל זהה מבחינה מתמטית בדיוק לסדרת L2.

הסדרת L1 דוחפת משקלים לאפס בדיוק ומעודדת מודל דליל. הסדרת L2 תעניש את פרמטרי המשקולות מבלי להפוך אותם לדלילים מכיוון שהעונש הולך לאפס בגלל משקלים קטנים - סיבה אחת לכך ש-L2 נפוץ יותר.

ב- tf.keras , הסדרת משקל מתווספת על ידי העברת מופעי מסדר משקל לשכבות כארגומנטים של מילות מפתח. בואו נוסיף עכשיו הסדרת משקל L2.

l2_model = tf.keras.Sequential([
    layers.Dense(512, activation='elu',
                 kernel_regularizer=regularizers.l2(0.001),
                 input_shape=(FEATURES,)),
    layers.Dense(512, activation='elu',
                 kernel_regularizer=regularizers.l2(0.001)),
    layers.Dense(512, activation='elu',
                 kernel_regularizer=regularizers.l2(0.001)),
    layers.Dense(512, activation='elu',
                 kernel_regularizer=regularizers.l2(0.001)),
    layers.Dense(1)
])

regularizer_histories['l2'] = compile_and_fit(l2_model, "regularizers/l2")
Model: "sequential_4"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 dense_14 (Dense)            (None, 512)               14848     
                                                                 
 dense_15 (Dense)            (None, 512)               262656    
                                                                 
 dense_16 (Dense)            (None, 512)               262656    
                                                                 
 dense_17 (Dense)            (None, 512)               262656    
                                                                 
 dense_18 (Dense)            (None, 1)                 513       
                                                                 
=================================================================
Total params: 803,329
Trainable params: 803,329
Non-trainable params: 0
_________________________________________________________________

Epoch: 0, accuracy:0.5126,  binary_crossentropy:0.7481,  loss:2.2415,  val_accuracy:0.4950,  val_binary_crossentropy:0.6707,  val_loss:2.0653,  
....................................................................................................
Epoch: 100, accuracy:0.6625,  binary_crossentropy:0.5945,  loss:0.6173,  val_accuracy:0.6400,  val_binary_crossentropy:0.5871,  val_loss:0.6100,  
....................................................................................................
Epoch: 200, accuracy:0.6690,  binary_crossentropy:0.5864,  loss:0.6079,  val_accuracy:0.6650,  val_binary_crossentropy:0.5856,  val_loss:0.6076,  
....................................................................................................
Epoch: 300, accuracy:0.6790,  binary_crossentropy:0.5762,  loss:0.5976,  val_accuracy:0.6550,  val_binary_crossentropy:0.5881,  val_loss:0.6095,  
....................................................................................................
Epoch: 400, accuracy:0.6843,  binary_crossentropy:0.5697,  loss:0.5920,  val_accuracy:0.6650,  val_binary_crossentropy:0.5878,  val_loss:0.6101,  
....................................................................................................
Epoch: 500, accuracy:0.6897,  binary_crossentropy:0.5651,  loss:0.5907,  val_accuracy:0.6890,  val_binary_crossentropy:0.5798,  val_loss:0.6055,  
....................................................................................................
Epoch: 600, accuracy:0.6945,  binary_crossentropy:0.5610,  loss:0.5864,  val_accuracy:0.6820,  val_binary_crossentropy:0.5772,  val_loss:0.6026,  
..........................................................

l2(0.001) פירושו שכל מקדם במטריצת המשקל של השכבה יוסיף 0.001 * weight_coefficient_value**2 לאובדן הכולל של הרשת.

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

אז אותו דגם "Large" עם עונש הסדרת L2 מניב ביצועים טובים בהרבה:

plotter.plot(regularizer_histories)
plt.ylim([0.5, 0.7])
(0.5, 0.7)

png

כפי שאתה יכול לראות, הדגם המוסדר "L2" הוא כעת הרבה יותר תחרותי עם הדגם "Tiny" . דגם "L2" זה גם עמיד הרבה יותר בפני התאמה יתר מהדגם "Large" שעליו התבסס למרות שיש לו אותו מספר פרמטרים.

עוד מידע

יש שני דברים חשובים לשים לב לסוג זה של הסדרה.

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

result = l2_model(features)
regularization_loss=tf.add_n(l2_model.losses)

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

ישנה גישה שניה שבמקום זאת מפעילה את האופטימיזר רק על ההפסד הגולמי, ולאחר מכן תוך יישום הצעד המחושב האופטימיזר מחיל גם דעיכה מסוימת במשקל. "Decoupled Weight Decay" זה נראה באופטימייזרים כמו optimizers.FTRL ו- optimizers.AdamW .

הוסף נשירה

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

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

נשירה, המוחלת על שכבה, מורכבת מ"נשירה" אקראית (כלומר הגדרה לאפס) של מספר תכונות פלט של השכבה במהלך האימון. נניח ששכבה נתונה בדרך כלל הייתה מחזירה וקטור [0.2, 0.5, 1.3, 0.8, 1.1] עבור דגימת קלט נתונה במהלך האימון; לאחר החלת נשירה, לוקטור הזה יהיו כמה ערכים אפסיים המחולקים באקראי, למשל [0, 0.5, 1.3, 0, 1.1].

"שיעור הנשירה" הוא חלק מהתכונות שמאופסות; הוא מוגדר בדרך כלל בין 0.2 ל-0.5. בזמן הבדיקה, לא נושרות יחידות, ובמקום זאת ערכי הפלט של השכבה מוקטנים לפי גורם השווה לשיעור הנשירה, כדי לאזן את העובדה שיותר יחידות פעילות מאשר בזמן האימון.

ב- tf.keras אתה יכול להציג נשירה ברשת דרך שכבת ה-Dropout, אשר מוחלת על הפלט של השכבה ממש לפני.

בואו נוסיף שתי שכבות נשירה ברשת שלנו כדי לראות עד כמה הן מצליחות להפחית התאמה יתר:

dropout_model = tf.keras.Sequential([
    layers.Dense(512, activation='elu', input_shape=(FEATURES,)),
    layers.Dropout(0.5),
    layers.Dense(512, activation='elu'),
    layers.Dropout(0.5),
    layers.Dense(512, activation='elu'),
    layers.Dropout(0.5),
    layers.Dense(512, activation='elu'),
    layers.Dropout(0.5),
    layers.Dense(1)
])

regularizer_histories['dropout'] = compile_and_fit(dropout_model, "regularizers/dropout")
Model: "sequential_5"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 dense_19 (Dense)            (None, 512)               14848     
                                                                 
 dropout (Dropout)           (None, 512)               0         
                                                                 
 dense_20 (Dense)            (None, 512)               262656    
                                                                 
 dropout_1 (Dropout)         (None, 512)               0         
                                                                 
 dense_21 (Dense)            (None, 512)               262656    
                                                                 
 dropout_2 (Dropout)         (None, 512)               0         
                                                                 
 dense_22 (Dense)            (None, 512)               262656    
                                                                 
 dropout_3 (Dropout)         (None, 512)               0         
                                                                 
 dense_23 (Dense)            (None, 1)                 513       
                                                                 
=================================================================
Total params: 803,329
Trainable params: 803,329
Non-trainable params: 0
_________________________________________________________________

Epoch: 0, accuracy:0.4961,  binary_crossentropy:0.8110,  loss:0.8110,  val_accuracy:0.5330,  val_binary_crossentropy:0.6900,  val_loss:0.6900,  
....................................................................................................
Epoch: 100, accuracy:0.6557,  binary_crossentropy:0.5961,  loss:0.5961,  val_accuracy:0.6710,  val_binary_crossentropy:0.5788,  val_loss:0.5788,  
....................................................................................................
Epoch: 200, accuracy:0.6871,  binary_crossentropy:0.5622,  loss:0.5622,  val_accuracy:0.6860,  val_binary_crossentropy:0.5856,  val_loss:0.5856,  
....................................................................................................
Epoch: 300, accuracy:0.7246,  binary_crossentropy:0.5121,  loss:0.5121,  val_accuracy:0.6820,  val_binary_crossentropy:0.5927,  val_loss:0.5927,  
............
plotter.plot(regularizer_histories)
plt.ylim([0.5, 0.7])
(0.5, 0.7)

png

ברור מהעלילה הזו ששתי גישות ההסדרה הללו משפרות את התנהגות המודל "Large" . אבל זה עדיין לא מנצח אפילו את קו הבסיס של "Tiny" .

לאחר מכן נסה את שניהם ביחד, ותראה אם ​​זה משתפר.

משולב L2 + נשירה

combined_model = tf.keras.Sequential([
    layers.Dense(512, kernel_regularizer=regularizers.l2(0.0001),
                 activation='elu', input_shape=(FEATURES,)),
    layers.Dropout(0.5),
    layers.Dense(512, kernel_regularizer=regularizers.l2(0.0001),
                 activation='elu'),
    layers.Dropout(0.5),
    layers.Dense(512, kernel_regularizer=regularizers.l2(0.0001),
                 activation='elu'),
    layers.Dropout(0.5),
    layers.Dense(512, kernel_regularizer=regularizers.l2(0.0001),
                 activation='elu'),
    layers.Dropout(0.5),
    layers.Dense(1)
])

regularizer_histories['combined'] = compile_and_fit(combined_model, "regularizers/combined")
Model: "sequential_6"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 dense_24 (Dense)            (None, 512)               14848     
                                                                 
 dropout_4 (Dropout)         (None, 512)               0         
                                                                 
 dense_25 (Dense)            (None, 512)               262656    
                                                                 
 dropout_5 (Dropout)         (None, 512)               0         
                                                                 
 dense_26 (Dense)            (None, 512)               262656    
                                                                 
 dropout_6 (Dropout)         (None, 512)               0         
                                                                 
 dense_27 (Dense)            (None, 512)               262656    
                                                                 
 dropout_7 (Dropout)         (None, 512)               0         
                                                                 
 dense_28 (Dense)            (None, 1)                 513       
                                                                 
=================================================================
Total params: 803,329
Trainable params: 803,329
Non-trainable params: 0
_________________________________________________________________

Epoch: 0, accuracy:0.5090,  binary_crossentropy:0.8064,  loss:0.9648,  val_accuracy:0.4660,  val_binary_crossentropy:0.6877,  val_loss:0.8454,  
....................................................................................................
Epoch: 100, accuracy:0.6445,  binary_crossentropy:0.6050,  loss:0.6350,  val_accuracy:0.6630,  val_binary_crossentropy:0.5871,  val_loss:0.6169,  
....................................................................................................
Epoch: 200, accuracy:0.6660,  binary_crossentropy:0.5932,  loss:0.6186,  val_accuracy:0.6880,  val_binary_crossentropy:0.5722,  val_loss:0.5975,  
....................................................................................................
Epoch: 300, accuracy:0.6697,  binary_crossentropy:0.5818,  loss:0.6100,  val_accuracy:0.6900,  val_binary_crossentropy:0.5614,  val_loss:0.5895,  
....................................................................................................
Epoch: 400, accuracy:0.6749,  binary_crossentropy:0.5742,  loss:0.6046,  val_accuracy:0.6870,  val_binary_crossentropy:0.5576,  val_loss:0.5881,  
....................................................................................................
Epoch: 500, accuracy:0.6854,  binary_crossentropy:0.5703,  loss:0.6029,  val_accuracy:0.6970,  val_binary_crossentropy:0.5458,  val_loss:0.5784,  
....................................................................................................
Epoch: 600, accuracy:0.6806,  binary_crossentropy:0.5673,  loss:0.6015,  val_accuracy:0.6980,  val_binary_crossentropy:0.5453,  val_loss:0.5795,  
....................................................................................................
Epoch: 700, accuracy:0.6937,  binary_crossentropy:0.5583,  loss:0.5938,  val_accuracy:0.6870,  val_binary_crossentropy:0.5477,  val_loss:0.5832,  
....................................................................................................
Epoch: 800, accuracy:0.6911,  binary_crossentropy:0.5576,  loss:0.5947,  val_accuracy:0.7000,  val_binary_crossentropy:0.5446,  val_loss:0.5817,  
.......................
plotter.plot(regularizer_histories)
plt.ylim([0.5, 0.7])
(0.5, 0.7)

png

המודל הזה עם ההסדרה "Combined" הוא ללא ספק הטוב ביותר עד כה.

הצג ב- TensorBoard

מודלים אלה גם רשמו יומני TensorBoard.

כדי לפתוח מציג tensorboard מוטבע בתוך מחברת, העתק את הדברים הבאים לתא קוד:

%tensorboard --logdir {logdir}/regularizers

אתה יכול לצפות בתוצאות של הפעלה קודמת של מחברת זו ב- TensorDoard.dev .

זה כלול גם ב- <iframe> מטעמי נוחות:

display.IFrame(
    src="https://tensorboard.dev/experiment/fGInKDo8TXes1z7HQku9mw/#scalars&_smoothingWeight=0.97",
    width = "100%",
    height="800px")

זה הועלה עם:

tensorboard dev upload --logdir  {logdir}/regularizers

מסקנות

לסיכום: הנה הדרכים הנפוצות ביותר למניעת התאמת יתר ברשתות עצביות:

  • קבל יותר נתוני אימון.
  • צמצם את הקיבולת של הרשת.
  • הוסף הסדרת משקל.
  • הוסף נשירה.

שתי גישות חשובות שאינן מכוסות במדריך זה הן:

  • הגדלת נתונים
  • נורמליזציה של אצווה

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

# MIT License
#
# Copyright (c) 2017 François Chollet
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the "Software"),
# to deal in the Software without restriction, including without limitation
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
# and/or sell copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
# DEALINGS IN THE SOFTWARE.