Переобучение и недообучивание

Посмотреть на TensorFlow.org Запустить в Google Colab Посмотреть исходный код на 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 , который берет пакеты из 10000 примеров, применяет функцию 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 для гиперболического уменьшения скорости обучения до 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" модели, что вам нужно переключить график на логарифмическую шкалу, чтобы действительно увидеть, что происходит.

Это становится очевидным, если вы начертите и сравните показатели проверки с показателями обучения.

  • Это нормально, что есть небольшая разница.
  • Если обе метрики движутся в одном направлении, все в порядке.
  • Если метрика проверки начинает стагнировать, а метрика обучения продолжает улучшаться, вы, вероятно, близки к переоснащению.
  • Если метрика валидации идет в неправильном направлении, модель явно переоснащается.
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 , где добавленная стоимость пропорциональна абсолютному значению весовых коэффициентов (т. е. тому, что называется «нормой L1» весов).

  • Регуляризация L2 , где добавленная стоимость пропорциональна квадрату значения весовых коэффициентов (т. е. тому, что называется квадратом «нормы 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)

Во- вторых: эта реализация работает, добавляя штрафы за вес к потерям модели, а затем применяя после этого стандартную процедуру оптимизации.

Существует второй подход, который вместо этого запускает оптимизатор только на необработанных потерях, а затем, применяя вычисленный шаг, оптимизатор также применяет некоторое снижение веса. Это «развязанное снижение веса» наблюдается у оптимизаторов, таких как optimizers.FTRL и optimizers.AdamW .

Добавить отсев

Dropout — один из самых эффективных и наиболее часто используемых методов регуляризации нейронных сетей, разработанный Хинтоном и его студентами в Университете Торонто.

Интуитивное объяснение отсева состоит в том, что, поскольку отдельные узлы в сети не могут полагаться на выходные данные других, каждый узел должен выводить функции, которые полезны сами по себе.

Выпадение, применяемое к слою, состоит из случайного «выпадения» (т.е. обнуления) ряда выходных признаков слоя во время обучения. Допустим, данный слой обычно возвращал бы вектор [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, чтобы увидеть, насколько хорошо они уменьшают переобучение:

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.