Overfit i underfit

Zobacz na TensorFlow.org Uruchom w Google Colab Wyświetl źródło na GitHub Pobierz notatnik

Jak zawsze, kod w tym przykładzie będzie korzystał z API tf.keras , o którym możesz dowiedzieć się więcej w przewodniku TensorFlow Keras .

W obu poprzednich przykładach — klasyfikowaniu tekstu i przewidywaniu zużycia paliwa — widzieliśmy, że dokładność naszego modelu na danych walidacyjnych osiąga szczyt po uczeniu przez kilka epok, a następnie stagnuje lub zaczyna spadać.

Innymi słowy, nasz model nadawałby się do danych uczących. Ważna jest nauka radzenia sobie z nadmiernym dopasowaniem. Chociaż często można uzyskać wysoką dokładność zestawu uczącego , tak naprawdę chcemy opracować modele, które dobrze uogólniają zestaw testowy (lub dane, których wcześniej nie widzieli).

Przeciwieństwem overfittingu jest underfitting . Niedopasowanie występuje, gdy nadal istnieje możliwość poprawy danych dotyczących pociągu. Może się tak zdarzyć z wielu powodów: jeśli model nie jest wystarczająco wydajny, jest nadmiernie uregulowany lub po prostu nie był trenowany wystarczająco długo. Oznacza to, że sieć nie nauczyła się odpowiednich wzorców w danych treningowych.

Jeśli jednak trenujesz zbyt długo, model zacznie się przesadzać i uczyć się wzorców z danych szkoleniowych, które nie są uogólniane na dane testowe. Musimy znaleźć równowagę. Zrozumienie, jak trenować przez odpowiednią liczbę epok, co omówimy poniżej, jest użyteczną umiejętnością.

Aby zapobiec nadmiernemu dopasowaniu, najlepszym rozwiązaniem jest korzystanie z pełniejszych danych treningowych. Zbiór danych powinien obejmować pełny zakres danych wejściowych, które model ma obsłużyć. Dodatkowe dane mogą być przydatne tylko wtedy, gdy obejmują nowe i interesujące przypadki.

Model wyszkolony na pełniejszych danych naturalnie będzie lepiej uogólniał. Kiedy nie jest to już możliwe, następnym najlepszym rozwiązaniem jest użycie technik takich jak regularyzacja. Nakładają one ograniczenia na ilość i typ informacji, które może przechowywać Twój model. Jeśli sieć może sobie pozwolić tylko na zapamiętanie niewielkiej liczby wzorców, proces optymalizacji zmusi ją do skupienia się na najbardziej widocznych wzorcach, które mają większą szansę na dobre uogólnienie.

W tym notatniku omówimy kilka typowych technik regularyzacji i wykorzystamy je do ulepszenia modelu klasyfikacji.

Ustawiać

Przed rozpoczęciem zaimportuj niezbędne pakiety:

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)

Zbiór danych Higgsa

Celem tego samouczka nie jest fizyka cząstek elementarnych, więc nie zajmuj się szczegółami zestawu danych. Zawiera 11 000 000 przykładów, każdy z 28 cechami i binarną etykietą klasy.

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

Klasa tf.data.experimental.CsvDataset może służyć do odczytywania rekordów csv bezpośrednio z pliku gzip bez pośredniego kroku dekompresji.

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

Ta klasa czytnika csv zwraca listę skalarów dla każdego rekordu. Poniższa funkcja przepakowuje listę skalarów do pary (wektor_funkcji, etykieta).

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

TensorFlow jest najbardziej wydajny podczas pracy na dużych partiach danych.

Dlatego zamiast przepakowywania każdego wiersza osobno, utwórz nowy Dataset , który pobiera partie 10000 przykładów, stosuje funkcję pack_row do każdej partii, a następnie dzieli partie z powrotem na pojedyncze rekordy:

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

Spójrz na niektóre rekordy z tego nowego packed_ds .

Funkcje nie są idealnie znormalizowane, ale to wystarczy w tym samouczku.

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

Aby ten samouczek był stosunkowo krótki, użyj tylko pierwszych 1000 próbek do walidacji i następnych 10 000 do szkolenia:

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

Ułatwiają to metody Dataset.skip i Dataset.take .

Jednocześnie użyj metody Dataset.cache , aby upewnić się, że program ładujący nie musi ponownie odczytywać danych z pliku w każdej epoce:

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))>

Te zbiory danych zwracają poszczególne przykłady. Użyj metody .batch , aby utworzyć partie o odpowiednim rozmiarze do szkolenia. Przed dozowaniem pamiętaj również o .shuffle i .repeat zestawu treningowego.

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

Zademonstruj nadmierne dopasowanie

Najprostszym sposobem uniknięcia nadmiernego dopasowania jest rozpoczęcie od małego modelu: Model z niewielką liczbą parametrów do nauczenia (co jest określane przez liczbę warstw i liczbę jednostek na warstwę). W uczeniu głębokim liczba parametrów, których można się nauczyć w modelu, jest często określana jako „pojemność” modelu.

Intuicyjnie, model z większą liczbą parametrów będzie miał większą „zdolność zapamiętywania”, a zatem będzie w stanie łatwo nauczyć się doskonałego odwzorowania słownikowego między próbkami uczącymi a ich celami, mapowania bez żadnej mocy uogólniania, ale byłoby to bezużyteczne przy przewidywaniu na wcześniej niewidocznych danych.

Zawsze miej to na uwadze: modele uczenia głębokiego zwykle dobrze dopasowują się do danych treningowych, ale prawdziwym wyzwaniem jest uogólnianie, a nie dopasowanie.

Z drugiej strony, jeśli sieć ma ograniczone zasoby zapamiętywania, nie będzie w stanie tak łatwo nauczyć się mapowania. Aby zminimalizować jego utratę, będzie musiał nauczyć się skompresowanych reprezentacji, które mają większą moc predykcyjną. Jednocześnie, jeśli zmniejszysz swój model, będzie miał trudności z dopasowaniem do danych treningowych. Istnieje równowaga między „zbyt dużą pojemnością” a „niewystarczającą pojemnością”.

Niestety nie ma magicznej formuły, która pozwoliłaby określić odpowiedni rozmiar lub architekturę Twojego modelu (pod względem liczby warstw lub odpowiedniego rozmiaru dla każdej warstwy). Będziesz musiał poeksperymentować, używając szeregu różnych architektur.

Aby znaleźć odpowiedni rozmiar modelu, najlepiej zacząć od stosunkowo niewielkiej liczby warstw i parametrów, a następnie zacząć zwiększać rozmiar warstw lub dodawać nowe warstwy, aż zobaczysz malejące zwroty z utraty walidacji.

Zacznij od prostego modelu, używając tylko warstw. layers.Dense jako punkt odniesienia, a następnie utwórz większe wersje i porównaj je.

Procedura szkolenia

Wiele modeli trenuje lepiej, jeśli stopniowo zmniejszasz tempo uczenia się podczas treningu. Użyj optimizers.schedules , aby zmniejszyć tempo uczenia się w czasie:

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)

Powyższy kod ustawia schedules.InverseTimeDecay aby hiperbolicznie zmniejszyć szybkość uczenia się do 1/2 stawki podstawowej w 1000 epok, 1/3 w 2000 epok i tak dalej.

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

Każdy model w tym samouczku będzie używał tej samej konfiguracji szkolenia. Więc skonfiguruj je w sposób wielokrotnego użytku, zaczynając od listy wywołań zwrotnych.

Szkolenie do tego samouczka trwa przez wiele krótkich epok. Aby zredukować hałas związany z logowaniem, użyj tfdocs.EpochDots , które po prostu wyświetlają . dla każdej epoki oraz pełny zestaw metryk co 100 epok.

Następnie callbacks.EarlyStopping , aby uniknąć długich i niepotrzebnych czasów szkolenia. Zauważ, że to wywołanie zwrotne jest ustawione na monitorowanie val_binary_crossentropy , a nie val_loss . Ta różnica będzie ważna później.

Użyj funkcji callbacks.TensorBoard , aby wygenerować logi TensorBoard na potrzeby szkolenia.

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

Podobnie każdy model będzie używał tych samych ustawień Model.compile i 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

Mały model

Zacznij od wytrenowania modelu:

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,  
...................................

Teraz sprawdź, jak poradził sobie model:

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

Mały model

Aby sprawdzić, czy możesz pobić wydajność małego modelu, stopniowo trenuj kilka większych modeli.

Wypróbuj dwie ukryte warstwy po 16 jednostek każda:

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,  
.........................

Model średni

Teraz wypróbuj 3 ukryte warstwy po 64 jednostki każda:

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)
])

I trenuj model, używając tych samych danych:

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,  
..............................................................

Duży model

W ramach ćwiczenia możesz stworzyć jeszcze większy model i zobaczyć, jak szybko zaczyna się on przesadzać. Następnie dodajmy do tego testu sieć, która ma znacznie większą pojemność, znacznie większą niż wynikałoby to z problemu:

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)
])

I znowu wytrenuj model, używając tych samych danych:

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,  
..........................

Wykreśl straty związane ze szkoleniem i walidacją

Linie ciągłe pokazują utratę treningu, a linie przerywane utratę walidacji (pamiętaj: mniejsza strata walidacji oznacza lepszy model).

Podczas gdy budowanie większego modelu daje mu większą moc, jeśli ta moc nie jest jakoś ograniczona, może łatwo przełożyć się na zestaw treningowy.

W tym przykładzie zazwyczaj tylko model "Tiny" pozwala całkowicie uniknąć nadmiernego dopasowania, a każdy z większych modeli szybciej dopasowuje się do danych. Staje się to tak poważne dla "large" modelu, że musisz przełączyć wykres na skalę logarytmiczną, aby naprawdę zobaczyć, co się dzieje.

Jest to widoczne, jeśli wykreślisz i porównasz metryki walidacji z metrykami uczącymi.

  • To normalne, że istnieje niewielka różnica.
  • Jeśli obie metryki idą w tym samym kierunku, wszystko jest w porządku.
  • Jeśli metryka walidacji zaczyna się zatrzymywać, podczas gdy metryka treningu nadal się poprawia, prawdopodobnie jesteś bliski nadmiernego dopasowania.
  • Jeśli metryka walidacji idzie w złym kierunku, model wyraźnie przesadza.
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

Zobacz w TensorBoard

Wszystkie te modele zapisały logi TensorBoard podczas treningu.

Otwórz wbudowaną przeglądarkę TensorBoard w notatniku:

#docs_infra: no_execute

# Load the TensorBoard notebook extension
%load_ext tensorboard

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

Możesz zobaczyć wyniki poprzedniego uruchomienia tego notatnika na TensorBoard.dev .

TensorBoard.dev to zarządzane środowisko do hostingu, śledzenia i udostępniania eksperymentów ML ze wszystkimi.

Jest również zawarty w <iframe> dla wygody:

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

Jeśli chcesz udostępnić wyniki TensorBoard, możesz przesłać logi do TensorBoard.dev , kopiując następujące elementy do komórki kodu.

tensorboard dev upload --logdir  {logdir}/sizes

Strategie zapobiegania nadmiernemu dopasowaniu

Zanim przejdziesz do treści tej sekcji, skopiuj dzienniki treningowe z powyższego modelu "Tiny" , aby wykorzystać je jako punkt odniesienia dla porównania.

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']

Dodaj regularyzację wagi

Być może znasz zasadę Brzytwy Ockhama: biorąc pod uwagę dwa wyjaśnienia, wyjaśnieniem, które najprawdopodobniej będzie poprawne, jest to „najprostsze”, takie, które zawiera najmniej założeń. Dotyczy to również modeli wyuczonych przez sieci neuronowe: biorąc pod uwagę niektóre dane szkoleniowe i architekturę sieci, istnieje wiele zestawów wartości wag (wiele modeli), które mogą wyjaśniać dane, a prostsze modele są mniej podatne na przeuczenie niż te złożone.

„Prosty model” w tym kontekście to model, w którym rozkład wartości parametrów ma mniejszą entropię (lub model o całkowitej mniejszej liczbie parametrów, jak widzieliśmy w sekcji powyżej). Dlatego powszechnym sposobem łagodzenia nadmiernego dopasowania jest nałożenie ograniczeń na złożoność sieci poprzez wymuszenie na jej wagach przyjmowania małych wartości, co sprawia, że ​​rozkład wartości wag jest bardziej „regularny”. Nazywa się to „regulacją wagi” i odbywa się poprzez dodanie do funkcji strat sieci kosztów związanych z posiadaniem dużych wag. Koszt ten występuje w dwóch wariantach:

  • Regularyzacja L1 , gdzie dodany koszt jest proporcjonalny do bezwzględnej wartości współczynników wag (tj. do tzw. „norma L1” wag).

  • Regularyzacja L2 , gdzie dodany koszt jest proporcjonalny do kwadratu wartości współczynników wag (tj. do kwadratu „norma L2” wag). Regularyzacja L2 jest również nazywana zanikiem wagi w kontekście sieci neuronowych. Nie pozwól, aby ta inna nazwa Cię zmyliła: spadek wagi jest matematycznie dokładnie taki sam jak regularyzacja L2.

Regularyzacja L1 przesuwa wagi w kierunku dokładnie zera, zachęcając do rzadkiego modelu. Regularyzacja L2 będzie karać parametry wag, nie czyniąc ich rzadkimi, ponieważ kara spada do zera za małe wagi — jeden z powodów, dla których L2 jest bardziej powszechny.

W tf.keras , regularyzacja wagi jest dodawana przez przekazywanie instancji regulatora wagi do warstw jako argumentów słów kluczowych. Dodajmy teraz regularyzację wagi 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) oznacza, że ​​każdy współczynnik w macierzy wag warstwy doda 0.001 * weight_coefficient_value**2 do całkowitej straty sieci.

Dlatego bezpośrednio monitorujemy binary_crossentropy . Ponieważ nie ma wmieszanego składnika regularyzacji.

Tak więc ten sam "Large" model z karą za regularyzację L2 działa znacznie lepiej:

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

png

Jak widać, regularyzowany model "L2" jest teraz znacznie bardziej konkurencyjny w stosunku do modelu "Tiny" . Ten model "L2" jest również znacznie bardziej odporny na przefazowanie niż model "Large" , na którym był oparty, mimo że ma taką samą liczbę parametrów.

Więcej informacji

Należy zwrócić uwagę na dwie ważne kwestie dotyczące tego rodzaju regularyzacji.

Po pierwsze: jeśli piszesz własną pętlę treningową, musisz koniecznie zapytać model o jego straty w regularyzacji.

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

Po drugie: ta implementacja działa poprzez dodanie kar wagi do utraty modelu, a następnie zastosowanie standardowej procedury optymalizacji.

Istnieje drugie podejście, które zamiast tego uruchamia optymalizator tylko na surowej stracie, a następnie, stosując obliczoną wartość kroku, optymalizator stosuje również pewien zanik wagi. Ten „Decoupled Weight Deas” jest widoczny w optimizers.FTRL , takich jakOptimizers.FTRL optimizers.AdamW .

Dodaj rezygnację

Dropout to jedna z najskuteczniejszych i najczęściej stosowanych technik regularyzacji sieci neuronowych, opracowana przez Hintona i jego studentów z Uniwersytetu w Toronto.

Intuicyjne wyjaśnienie porzucania jest takie, że ponieważ poszczególne węzły w sieci nie mogą polegać na danych wyjściowych innych, każdy węzeł musi samodzielnie udostępniać funkcje, które są przydatne.

Dropout, nałożony na warstwę, polega na losowym „wyrzuceniu” (tj. ustawionym na zero) szeregu cech wyjściowych warstwy podczas treningu. Powiedzmy, że dana warstwa normalnie zwróciłaby wektor [0,2, 0,5, 1,3, 0,8, 1,1] dla danej próbki wejściowej podczas uczenia; po zastosowaniu dropout wektor ten będzie miał kilka wpisów zerowych rozmieszczonych losowo, np. [0, 0.5, 1.3, 0, 1.1].

„Wskaźnik rezygnacji” to ułamek funkcji, które są wyzerowane; zwykle wynosi od 0,2 do 0,5. W czasie testu żadne jednostki nie są pomijane, a zamiast tego wartości wyjściowe warstwy są zmniejszane o współczynnik równy wskaźnikowi rezygnacji, aby zrównoważyć fakt, że więcej jednostek jest aktywnych niż w czasie treningu.

W tf.keras możesz wprowadzić dropout w sieci za pośrednictwem warstwy Dropout, która jest stosowana na wyjściu warstwy tuż przed.

Dodajmy dwie warstwy Dropout w naszej sieci, aby zobaczyć, jak dobrze radzą sobie z redukcją nadmiernego dopasowania:

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

Z tego wykresu jasno wynika, że ​​oba te podejścia do regularyzacji poprawiają zachowanie modelu "Large" . Ale to nadal nie przebije nawet linii podstawowej "Tiny" .

Następnie wypróbuj je oba razem i zobacz, czy to lepiej.

Połączone L2 + odpadanie

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

Ten model z regularyzacją "Combined" jest oczywiście najlepszym do tej pory.

Zobacz w TensorBoard

Modele te rejestrowały również logi TensorBoard.

Aby otworzyć wbudowaną przeglądarkę tensorboard w notatniku, skopiuj następujące elementy do komórki kodu:

%tensorboard --logdir {logdir}/regularizers

Możesz zobaczyć wyniki poprzedniego uruchomienia tego notatnika na TensorDoard.dev .

Jest również zawarty w <iframe> dla wygody:

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

To zostało przesłane z:

tensorboard dev upload --logdir  {logdir}/regularizers

Wnioski

Podsumowując: oto najczęstsze sposoby zapobiegania nadmiernemu dopasowaniu w sieciach neuronowych:

  • Uzyskaj więcej danych treningowych.
  • Zmniejsz pojemność sieci.
  • Dodaj regularyzację wagi.
  • Dodaj rezygnację.

Dwa ważne podejścia nieomówione w tym przewodniku to:

  • powiększanie danych
  • normalizacja wsadowa

Pamiętaj, że każda metoda może pomóc sama z siebie, ale często ich łączenie może być jeszcze skuteczniejsze.

# 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.