Explore overfit and underfit

Смотрите на TensorFlow.org Запустите в Google Colab Изучайте код на GitHub Скачайте ноутбук

Как всегда, код в этом примере будет использовать API tf.keras, о котором вы можете узнать больше в руководстве TensorFlow Keras.

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

Другими словами, наша модель переобучилась на тренировочных данных. Важно научиться работать с переобученностью. Хотя часто возможно достичь высокой точности на обучающей выборке, на самом деле мы хотим построить модель которая хорошо обобщается на тестовой выборке (данных которые модель не видела ранее).

Обратным случаем переобучения является недообучение. Недообучение возникает когда еще есть возможность для улучшения модели на тестовых данных. Это может случитья по ряду причин: модель недостаточно сильная, с избыточной регуляризацией или просто недостаточно долго обучалась. Это значит, что сеть не изучила релевантные паттерны в обучающей выборке.

Если ты будешь обучать модель слишком долго, модель начнет переобучаться и настроится на паттерны тренировочных данных которые не обобщаются на тестовые данные. Нам нужно найти баланс. Понимание того, как обучать модель за подходящее количество эпох, как мы выясним ниже - очень полезный навык.

Лучшее решение для предотвращения переобученности - использовать больше тренировочных данных. Модель обученная на большем количестве данных естественным образом обобщает лучше. Когда это более невозможно, следующее решение - использовать техники наподобие регуляризации. Они накладывают ограничения на количество и тип информации которую ваша модель может хранить. Если нейросеть может запомнить только небольшое число паттернов, то процесс оптимизации заставит ее сфокусироваться на наиболее заметных паттернах, у которых более высокий шанс обобщения.

В этом уроке мы познакомимся с двумя распространенными техниками регуляризации - регуляризацией весов и исключением (dropout) и используем их для того, чтобы улучшить нашу модель классификации обзоров фильмов из IMDB.

import tensorflow as tf
from tensorflow import keras

import numpy as np
import matplotlib.pyplot as plt

print(tf.__version__)
2.3.0

Загрузите датасет IMDB

Вместо использования вложения (embedding) как в предыдущем уроке, здесь мы используем multi-hot encode предложений. Эта модель быстро переобучится на тренировочных данных. Мы посмотрим как произойдет переобучение и как его предотвратить.

Multi-hot-encoding наших списков означет их преобразование в вектора из 0 и 1. Конкретнее это значит что например последовательность [3, 5] преобразуется в 10 000-мерный вектор, который будет состоять полностью из нулей за исключением индексов 3 и 5 которые будут единицами.

NUM_WORDS = 10000

(train_data, train_labels), (test_data, test_labels) = keras.datasets.imdb.load_data(num_words=NUM_WORDS)

def multi_hot_sequences(sequences, dimension):
    # Создадим нулевую матрицу размерности (len(sequences), dimension)
    results = np.zeros((len(sequences), dimension))
    for i, word_indices in enumerate(sequences):
        results[i, word_indices] = 1.0  # приравняем требуемые индексы results[i] к 1
    return results


train_data = multi_hot_sequences(train_data, dimension=NUM_WORDS)
test_data = multi_hot_sequences(test_data, dimension=NUM_WORDS)
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/imdb.npz
17465344/17464789 [==============================] - 0s 0us/step

Давайте посмотрим на один из получившихся multi-hot векторов. Индексы слов были отсортированы по частоте поэтому ожидаемо много значений 1 возле нулевого индекса, что мы и видим на этом графике:

plt.plot(train_data[0])
[<matplotlib.lines.Line2D at 0x7f5ec22812b0>]

png

Продемонстрируем переобучение

Простейший способ предотвратить переобучение это сократить размер модели, т.е. количество обучаемых параметров модели (которые определяются числом слоев и элементов в каждом слое). В глубоком обучении количество обучаемых параметров модели часто называют "емкостью" модели. Интуитивно, модель с большим количеством параметров будет иметь большую "запоминающую емкость" и поэтому легко сможет выучить идеальный словарь - как отображение между обучающими примерами и их целевыми значениями, отображение безо всякой обобщающей силы. Но это будет бесполезно при прогнозировании на новых, ранее не виденных данных.

Всегда имейте это ввиду: модели глубокого обучения хорошо настраиваются на тренировочных данных, но настоящим вызовом является обобщение, не обучение.

С другой стороны, если нейросеть имеет ограниченные ресурсы ззапоминания, то она не сможет выучить отображение так легко. Для минимизации функции потерь модель вынуждена выучить только сжатые представления у которых больше предсказательной силы. В то же время, если вы сделаете вашу модель слишком маленькой, она с трудом подстроится под тренировочный сет. Существует баланс между "слишком большой емкостью" и "недостаточной емкостью".

К сожалению, не существует магической формулы, чтобы определить правильный размер или архитектуру модели, говоря о количестве слоев или размере каждого слоя. Вам необходимо поэкспериментировать с использованием разных архитектур модели.

Чтобы найди подходящий размер модели лучше начать с относительно небольшого количества слоев и параметров, затем начать увеличивать размер слоев или добавлять новые до тех пор, пока вы не увидите уменьшение отдачи функции ошибок на проверочных данных. Давай попробуем это на примере нашей сети для классификации обзоров фильмов.

Мы построим простую модель используя только Dense слои в качестве базовой, затем создадим меньшую и большую версии модели и сравним их.

Создайте базовую модель

baseline_model = keras.Sequential([
    # Параметр `input_shape` нужен только для того, чтобы заработал `.summary`.
    keras.layers.Dense(16, activation='relu', input_shape=(NUM_WORDS,)),
    keras.layers.Dense(16, activation='relu'),
    keras.layers.Dense(1, activation='sigmoid')
])

baseline_model.compile(optimizer='adam',
                       loss='binary_crossentropy',
                       metrics=['accuracy', 'binary_crossentropy'])

baseline_model.summary()
Model: "sequential"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
dense (Dense)                (None, 16)                160016    
_________________________________________________________________
dense_1 (Dense)              (None, 16)                272       
_________________________________________________________________
dense_2 (Dense)              (None, 1)                 17        
=================================================================
Total params: 160,305
Trainable params: 160,305
Non-trainable params: 0
_________________________________________________________________

baseline_history = baseline_model.fit(train_data,
                                      train_labels,
                                      epochs=20,
                                      batch_size=512,
                                      validation_data=(test_data, test_labels),
                                      verbose=2)
Epoch 1/20
49/49 - 2s - loss: 0.4401 - accuracy: 0.8211 - binary_crossentropy: 0.4401 - val_loss: 0.3109 - val_accuracy: 0.8814 - val_binary_crossentropy: 0.3109
Epoch 2/20
49/49 - 1s - loss: 0.2300 - accuracy: 0.9164 - binary_crossentropy: 0.2300 - val_loss: 0.2836 - val_accuracy: 0.8873 - val_binary_crossentropy: 0.2836
Epoch 3/20
49/49 - 1s - loss: 0.1730 - accuracy: 0.9397 - binary_crossentropy: 0.1730 - val_loss: 0.2945 - val_accuracy: 0.8822 - val_binary_crossentropy: 0.2945
Epoch 4/20
49/49 - 1s - loss: 0.1393 - accuracy: 0.9533 - binary_crossentropy: 0.1393 - val_loss: 0.3167 - val_accuracy: 0.8795 - val_binary_crossentropy: 0.3167
Epoch 5/20
49/49 - 1s - loss: 0.1153 - accuracy: 0.9632 - binary_crossentropy: 0.1153 - val_loss: 0.3445 - val_accuracy: 0.8744 - val_binary_crossentropy: 0.3445
Epoch 6/20
49/49 - 1s - loss: 0.0980 - accuracy: 0.9697 - binary_crossentropy: 0.0980 - val_loss: 0.3803 - val_accuracy: 0.8682 - val_binary_crossentropy: 0.3803
Epoch 7/20
49/49 - 1s - loss: 0.0820 - accuracy: 0.9757 - binary_crossentropy: 0.0820 - val_loss: 0.4157 - val_accuracy: 0.8652 - val_binary_crossentropy: 0.4157
Epoch 8/20
49/49 - 1s - loss: 0.0692 - accuracy: 0.9816 - binary_crossentropy: 0.0692 - val_loss: 0.4560 - val_accuracy: 0.8610 - val_binary_crossentropy: 0.4560
Epoch 9/20
49/49 - 1s - loss: 0.0587 - accuracy: 0.9862 - binary_crossentropy: 0.0587 - val_loss: 0.4990 - val_accuracy: 0.8594 - val_binary_crossentropy: 0.4990
Epoch 10/20
49/49 - 1s - loss: 0.0484 - accuracy: 0.9894 - binary_crossentropy: 0.0484 - val_loss: 0.5420 - val_accuracy: 0.8568 - val_binary_crossentropy: 0.5420
Epoch 11/20
49/49 - 1s - loss: 0.0404 - accuracy: 0.9918 - binary_crossentropy: 0.0404 - val_loss: 0.5874 - val_accuracy: 0.8545 - val_binary_crossentropy: 0.5874
Epoch 12/20
49/49 - 1s - loss: 0.0339 - accuracy: 0.9939 - binary_crossentropy: 0.0339 - val_loss: 0.6219 - val_accuracy: 0.8537 - val_binary_crossentropy: 0.6219
Epoch 13/20
49/49 - 1s - loss: 0.0274 - accuracy: 0.9960 - binary_crossentropy: 0.0274 - val_loss: 0.6711 - val_accuracy: 0.8520 - val_binary_crossentropy: 0.6711
Epoch 14/20
49/49 - 1s - loss: 0.0219 - accuracy: 0.9975 - binary_crossentropy: 0.0219 - val_loss: 0.7194 - val_accuracy: 0.8495 - val_binary_crossentropy: 0.7194
Epoch 15/20
49/49 - 1s - loss: 0.0175 - accuracy: 0.9982 - binary_crossentropy: 0.0175 - val_loss: 0.7521 - val_accuracy: 0.8496 - val_binary_crossentropy: 0.7521
Epoch 16/20
49/49 - 1s - loss: 0.0141 - accuracy: 0.9990 - binary_crossentropy: 0.0141 - val_loss: 0.7912 - val_accuracy: 0.8482 - val_binary_crossentropy: 0.7912
Epoch 17/20
49/49 - 1s - loss: 0.0115 - accuracy: 0.9994 - binary_crossentropy: 0.0115 - val_loss: 0.8311 - val_accuracy: 0.8479 - val_binary_crossentropy: 0.8311
Epoch 18/20
49/49 - 1s - loss: 0.0095 - accuracy: 0.9995 - binary_crossentropy: 0.0095 - val_loss: 0.8711 - val_accuracy: 0.8472 - val_binary_crossentropy: 0.8711
Epoch 19/20
49/49 - 1s - loss: 0.0077 - accuracy: 0.9996 - binary_crossentropy: 0.0077 - val_loss: 0.9006 - val_accuracy: 0.8473 - val_binary_crossentropy: 0.9006
Epoch 20/20
49/49 - 1s - loss: 0.0063 - accuracy: 0.9998 - binary_crossentropy: 0.0063 - val_loss: 0.9309 - val_accuracy: 0.8462 - val_binary_crossentropy: 0.9309

Создайте меньшую модель

Давайте построим модель с меньшим количеством скрытых нейронов чтобы сравнить ее с базовой моделью, которую мы только создали:

smaller_model = keras.Sequential([
    keras.layers.Dense(4, activation='relu', input_shape=(NUM_WORDS,)),
    keras.layers.Dense(4, activation='relu'),
    keras.layers.Dense(1, activation='sigmoid')
])

smaller_model.compile(optimizer='adam',
                      loss='binary_crossentropy',
                      metrics=['accuracy', 'binary_crossentropy'])

smaller_model.summary()
Model: "sequential_1"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
dense_3 (Dense)              (None, 4)                 40004     
_________________________________________________________________
dense_4 (Dense)              (None, 4)                 20        
_________________________________________________________________
dense_5 (Dense)              (None, 1)                 5         
=================================================================
Total params: 40,029
Trainable params: 40,029
Non-trainable params: 0
_________________________________________________________________

И обучим модель используя те же данные:

smaller_history = smaller_model.fit(train_data,
                                    train_labels,
                                    epochs=20,
                                    batch_size=512,
                                    validation_data=(test_data, test_labels),
                                    verbose=2)
Epoch 1/20
49/49 - 2s - loss: 0.5731 - accuracy: 0.7560 - binary_crossentropy: 0.5731 - val_loss: 0.4529 - val_accuracy: 0.8586 - val_binary_crossentropy: 0.4529
Epoch 2/20
49/49 - 1s - loss: 0.3591 - accuracy: 0.8956 - binary_crossentropy: 0.3591 - val_loss: 0.3453 - val_accuracy: 0.8797 - val_binary_crossentropy: 0.3453
Epoch 3/20
49/49 - 1s - loss: 0.2680 - accuracy: 0.9186 - binary_crossentropy: 0.2680 - val_loss: 0.3035 - val_accuracy: 0.8878 - val_binary_crossentropy: 0.3035
Epoch 4/20
49/49 - 1s - loss: 0.2202 - accuracy: 0.9307 - binary_crossentropy: 0.2202 - val_loss: 0.2878 - val_accuracy: 0.8883 - val_binary_crossentropy: 0.2878
Epoch 5/20
49/49 - 1s - loss: 0.1895 - accuracy: 0.9398 - binary_crossentropy: 0.1895 - val_loss: 0.2857 - val_accuracy: 0.8856 - val_binary_crossentropy: 0.2857
Epoch 6/20
49/49 - 1s - loss: 0.1670 - accuracy: 0.9462 - binary_crossentropy: 0.1670 - val_loss: 0.2872 - val_accuracy: 0.8854 - val_binary_crossentropy: 0.2872
Epoch 7/20
49/49 - 1s - loss: 0.1497 - accuracy: 0.9531 - binary_crossentropy: 0.1497 - val_loss: 0.2928 - val_accuracy: 0.8842 - val_binary_crossentropy: 0.2928
Epoch 8/20
49/49 - 1s - loss: 0.1351 - accuracy: 0.9585 - binary_crossentropy: 0.1351 - val_loss: 0.3006 - val_accuracy: 0.8819 - val_binary_crossentropy: 0.3006
Epoch 9/20
49/49 - 1s - loss: 0.1231 - accuracy: 0.9627 - binary_crossentropy: 0.1231 - val_loss: 0.3113 - val_accuracy: 0.8798 - val_binary_crossentropy: 0.3113
Epoch 10/20
49/49 - 1s - loss: 0.1124 - accuracy: 0.9669 - binary_crossentropy: 0.1124 - val_loss: 0.3236 - val_accuracy: 0.8776 - val_binary_crossentropy: 0.3236
Epoch 11/20
49/49 - 1s - loss: 0.1029 - accuracy: 0.9704 - binary_crossentropy: 0.1029 - val_loss: 0.3357 - val_accuracy: 0.8748 - val_binary_crossentropy: 0.3357
Epoch 12/20
49/49 - 1s - loss: 0.0941 - accuracy: 0.9739 - binary_crossentropy: 0.0941 - val_loss: 0.3501 - val_accuracy: 0.8734 - val_binary_crossentropy: 0.3501
Epoch 13/20
49/49 - 1s - loss: 0.0864 - accuracy: 0.9768 - binary_crossentropy: 0.0864 - val_loss: 0.3658 - val_accuracy: 0.8712 - val_binary_crossentropy: 0.3658
Epoch 14/20
49/49 - 1s - loss: 0.0798 - accuracy: 0.9792 - binary_crossentropy: 0.0798 - val_loss: 0.3876 - val_accuracy: 0.8681 - val_binary_crossentropy: 0.3876
Epoch 15/20
49/49 - 1s - loss: 0.0730 - accuracy: 0.9822 - binary_crossentropy: 0.0730 - val_loss: 0.3978 - val_accuracy: 0.8681 - val_binary_crossentropy: 0.3978
Epoch 16/20
49/49 - 1s - loss: 0.0666 - accuracy: 0.9855 - binary_crossentropy: 0.0666 - val_loss: 0.4177 - val_accuracy: 0.8659 - val_binary_crossentropy: 0.4177
Epoch 17/20
49/49 - 1s - loss: 0.0616 - accuracy: 0.9862 - binary_crossentropy: 0.0616 - val_loss: 0.4333 - val_accuracy: 0.8649 - val_binary_crossentropy: 0.4333
Epoch 18/20
49/49 - 1s - loss: 0.0562 - accuracy: 0.9894 - binary_crossentropy: 0.0562 - val_loss: 0.4499 - val_accuracy: 0.8637 - val_binary_crossentropy: 0.4499
Epoch 19/20
49/49 - 1s - loss: 0.0517 - accuracy: 0.9904 - binary_crossentropy: 0.0517 - val_loss: 0.4687 - val_accuracy: 0.8619 - val_binary_crossentropy: 0.4687
Epoch 20/20
49/49 - 1s - loss: 0.0471 - accuracy: 0.9919 - binary_crossentropy: 0.0471 - val_loss: 0.4944 - val_accuracy: 0.8584 - val_binary_crossentropy: 0.4944

Создайте большую модель

В качестве упражнения вы можете построить еще большую модель и увидеть как быстро она начнет переобучаться. Далее давайте сравним с эталоном нейросеть которая имеет намного большую емкость чем того требует задача:

bigger_model = keras.models.Sequential([
    keras.layers.Dense(512, activation='relu', input_shape=(NUM_WORDS,)),
    keras.layers.Dense(512, activation='relu'),
    keras.layers.Dense(1, activation='sigmoid')
])

bigger_model.compile(optimizer='adam',
                     loss='binary_crossentropy',
                     metrics=['accuracy','binary_crossentropy'])

bigger_model.summary()
Model: "sequential_2"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
dense_6 (Dense)              (None, 512)               5120512   
_________________________________________________________________
dense_7 (Dense)              (None, 512)               262656    
_________________________________________________________________
dense_8 (Dense)              (None, 1)                 513       
=================================================================
Total params: 5,383,681
Trainable params: 5,383,681
Non-trainable params: 0
_________________________________________________________________

И опять обучим модель используя те же данные:

bigger_history = bigger_model.fit(train_data, train_labels,
                                  epochs=20,
                                  batch_size=512,
                                  validation_data=(test_data, test_labels),
                                  verbose=2)
Epoch 1/20
49/49 - 2s - loss: 0.3522 - accuracy: 0.8481 - binary_crossentropy: 0.3522 - val_loss: 0.2933 - val_accuracy: 0.8812 - val_binary_crossentropy: 0.2933
Epoch 2/20
49/49 - 1s - loss: 0.1448 - accuracy: 0.9480 - binary_crossentropy: 0.1448 - val_loss: 0.3362 - val_accuracy: 0.8705 - val_binary_crossentropy: 0.3362
Epoch 3/20
49/49 - 1s - loss: 0.0547 - accuracy: 0.9836 - binary_crossentropy: 0.0547 - val_loss: 0.4302 - val_accuracy: 0.8680 - val_binary_crossentropy: 0.4302
Epoch 4/20
49/49 - 1s - loss: 0.0106 - accuracy: 0.9982 - binary_crossentropy: 0.0106 - val_loss: 0.5823 - val_accuracy: 0.8638 - val_binary_crossentropy: 0.5823
Epoch 5/20
49/49 - 1s - loss: 0.0018 - accuracy: 0.9999 - binary_crossentropy: 0.0018 - val_loss: 0.6690 - val_accuracy: 0.8690 - val_binary_crossentropy: 0.6690
Epoch 6/20
49/49 - 1s - loss: 3.2183e-04 - accuracy: 1.0000 - binary_crossentropy: 3.2183e-04 - val_loss: 0.7210 - val_accuracy: 0.8701 - val_binary_crossentropy: 0.7210
Epoch 7/20
49/49 - 1s - loss: 1.7965e-04 - accuracy: 1.0000 - binary_crossentropy: 1.7965e-04 - val_loss: 0.7548 - val_accuracy: 0.8700 - val_binary_crossentropy: 0.7548
Epoch 8/20
49/49 - 1s - loss: 1.2564e-04 - accuracy: 1.0000 - binary_crossentropy: 1.2564e-04 - val_loss: 0.7791 - val_accuracy: 0.8704 - val_binary_crossentropy: 0.7791
Epoch 9/20
49/49 - 1s - loss: 9.5007e-05 - accuracy: 1.0000 - binary_crossentropy: 9.5007e-05 - val_loss: 0.7980 - val_accuracy: 0.8708 - val_binary_crossentropy: 0.7980
Epoch 10/20
49/49 - 1s - loss: 7.4694e-05 - accuracy: 1.0000 - binary_crossentropy: 7.4694e-05 - val_loss: 0.8144 - val_accuracy: 0.8708 - val_binary_crossentropy: 0.8144
Epoch 11/20
49/49 - 1s - loss: 6.0348e-05 - accuracy: 1.0000 - binary_crossentropy: 6.0348e-05 - val_loss: 0.8303 - val_accuracy: 0.8704 - val_binary_crossentropy: 0.8303
Epoch 12/20
49/49 - 1s - loss: 4.9928e-05 - accuracy: 1.0000 - binary_crossentropy: 4.9928e-05 - val_loss: 0.8432 - val_accuracy: 0.8704 - val_binary_crossentropy: 0.8432
Epoch 13/20
49/49 - 1s - loss: 4.2043e-05 - accuracy: 1.0000 - binary_crossentropy: 4.2043e-05 - val_loss: 0.8550 - val_accuracy: 0.8706 - val_binary_crossentropy: 0.8550
Epoch 14/20
49/49 - 1s - loss: 3.5804e-05 - accuracy: 1.0000 - binary_crossentropy: 3.5804e-05 - val_loss: 0.8666 - val_accuracy: 0.8706 - val_binary_crossentropy: 0.8666
Epoch 15/20
49/49 - 1s - loss: 3.0857e-05 - accuracy: 1.0000 - binary_crossentropy: 3.0857e-05 - val_loss: 0.8775 - val_accuracy: 0.8705 - val_binary_crossentropy: 0.8775
Epoch 16/20
49/49 - 1s - loss: 2.6894e-05 - accuracy: 1.0000 - binary_crossentropy: 2.6894e-05 - val_loss: 0.8878 - val_accuracy: 0.8706 - val_binary_crossentropy: 0.8878
Epoch 17/20
49/49 - 1s - loss: 2.3593e-05 - accuracy: 1.0000 - binary_crossentropy: 2.3593e-05 - val_loss: 0.8967 - val_accuracy: 0.8707 - val_binary_crossentropy: 0.8967
Epoch 18/20
49/49 - 1s - loss: 2.0852e-05 - accuracy: 1.0000 - binary_crossentropy: 2.0852e-05 - val_loss: 0.9056 - val_accuracy: 0.8707 - val_binary_crossentropy: 0.9056
Epoch 19/20
49/49 - 1s - loss: 1.8551e-05 - accuracy: 1.0000 - binary_crossentropy: 1.8551e-05 - val_loss: 0.9139 - val_accuracy: 0.8707 - val_binary_crossentropy: 0.9139
Epoch 20/20
49/49 - 1s - loss: 1.6597e-05 - accuracy: 1.0000 - binary_crossentropy: 1.6597e-05 - val_loss: 0.9218 - val_accuracy: 0.8708 - val_binary_crossentropy: 0.9218

Постройте графики потерь на тренировочных и проверочных данных

Непрерывные линии показывают потери во время обучения, а прерывистые - во время проверки (помни - меньшие потери на проверочных данных указывают на лучшую модель). В нашем случае самая маленькая модель начинает переобучаться позже, чем основная (после 6 эпох вместо 4) и ее показатели ухудшаются гораздо медленее после переобучения.

def plot_history(histories, key='binary_crossentropy'):
  plt.figure(figsize=(16,10))

  for name, history in histories:
    val = plt.plot(history.epoch, history.history['val_'+key],
                   '--', label=name.title()+' Val')
    plt.plot(history.epoch, history.history[key], color=val[0].get_color(),
             label=name.title()+' Train')

  plt.xlabel('Epochs')
  plt.ylabel(key.replace('_',' ').title())
  plt.legend()

  plt.xlim([0,max(history.epoch)])


plot_history([('baseline', baseline_history),
              ('smaller', smaller_history),
              ('bigger', bigger_history)])

png

Обратите внимание, что большая сеть начинает переобучаться почти сразу же после первой эпохи, и переобучение происходит гораздо быстрее. Чем больше емкость модели, тем легче она смоделирует тренировочные данные (и мы получим низкое значение потерь на тренировочных данных). Но в таком случае она будет более чувствительна к переобучению: разница потерь между обучением и проверкой будет очень велика.

Стратегии предотвращения переобучения

Добавить регуляризацию весов

Вам может быть знаком принцип бритвы Оккама: из двух толкований некоторого явления, правильным скорее всего является самое "простое" - то, которое содержит меньше всего предположений. Этот принцип также применим к моделям, обучемым при помощи нейронных сетей: если у наших данных и сетевой архитектуры существует несколько наборов значений весов (несколько моделей) которые могут объяснить данные и более простые модели переобучаются реже, чем сложные.

В этом контексте "простая модель" это модель в которой распределение значений параметров имеет меньшую энтропию (или модель с меньшим количеством параметров, как та которую мы строили выше). Таким образом, для предотвращение переобучения часто используется ограничение сложности сети путем принуждения ее коэфицентов принимать только небольшие значения, что делает распределение весов более "регулярным". Этот метод называется "регуляризация весов": к функции потерь нашей сети мы добавляем штраф (или cost, стоимость) за использование больших весов. Регуляризация бывает двух видов:

  • L1 регуляризация, где добавляемый штраф пропорционален абсолютным значениям коэффициентов весов (т.е. то что называется "L1 нормой" весов).

  • L2 регуляризация, где добавляемый штраф пропорционален квадрату значений коэффициентов весов (т.е. то, что называется квадратом "L2 нормы" весов). L2 регуляризацию также называют сокращением весов в контексте нейросетей. Не дайте разным названиям запутать себя: сокращение весов математически ровно то же самое что и L2 регуляризация.

L1 регуляризация вводит разреженность обнуляя некотороые из ваших весовых параметров. L2 регуляризация оштрафует весовые параметры не делая их разреженными - это одна из причин, почему L2 более распространена.

В tf.keras регуляризация весов добавляется передачей экземпляров регуляризатора слоям в качестве аргумента. Добавим сейчас L2 регуляризатор весов.

l2_model = keras.models.Sequential([
    keras.layers.Dense(16, kernel_regularizer=keras.regularizers.l2(0.001),
                       activation='relu', input_shape=(NUM_WORDS,)),
    keras.layers.Dense(16, kernel_regularizer=keras.regularizers.l2(0.001),
                       activation='relu'),
    keras.layers.Dense(1, activation='sigmoid')
])

l2_model.compile(optimizer='adam',
                 loss='binary_crossentropy',
                 metrics=['accuracy', 'binary_crossentropy'])

l2_model_history = l2_model.fit(train_data, train_labels,
                                epochs=20,
                                batch_size=512,
                                validation_data=(test_data, test_labels),
                                verbose=2)
Epoch 1/20
49/49 - 2s - loss: 0.4906 - accuracy: 0.8254 - binary_crossentropy: 0.4488 - val_loss: 0.3615 - val_accuracy: 0.8814 - val_binary_crossentropy: 0.3167
Epoch 2/20
49/49 - 1s - loss: 0.2863 - accuracy: 0.9167 - binary_crossentropy: 0.2377 - val_loss: 0.3341 - val_accuracy: 0.8867 - val_binary_crossentropy: 0.2828
Epoch 3/20
49/49 - 1s - loss: 0.2432 - accuracy: 0.9340 - binary_crossentropy: 0.1894 - val_loss: 0.3626 - val_accuracy: 0.8749 - val_binary_crossentropy: 0.3071
Epoch 4/20
49/49 - 1s - loss: 0.2201 - accuracy: 0.9444 - binary_crossentropy: 0.1630 - val_loss: 0.3608 - val_accuracy: 0.8793 - val_binary_crossentropy: 0.3024
Epoch 5/20
49/49 - 1s - loss: 0.2056 - accuracy: 0.9506 - binary_crossentropy: 0.1461 - val_loss: 0.3840 - val_accuracy: 0.8735 - val_binary_crossentropy: 0.3237
Epoch 6/20
49/49 - 1s - loss: 0.1999 - accuracy: 0.9533 - binary_crossentropy: 0.1382 - val_loss: 0.3912 - val_accuracy: 0.8743 - val_binary_crossentropy: 0.3288
Epoch 7/20
49/49 - 1s - loss: 0.1902 - accuracy: 0.9567 - binary_crossentropy: 0.1271 - val_loss: 0.4095 - val_accuracy: 0.8707 - val_binary_crossentropy: 0.3458
Epoch 8/20
49/49 - 1s - loss: 0.1886 - accuracy: 0.9568 - binary_crossentropy: 0.1242 - val_loss: 0.4308 - val_accuracy: 0.8670 - val_binary_crossentropy: 0.3656
Epoch 9/20
49/49 - 1s - loss: 0.1810 - accuracy: 0.9609 - binary_crossentropy: 0.1149 - val_loss: 0.4561 - val_accuracy: 0.8625 - val_binary_crossentropy: 0.3896
Epoch 10/20
49/49 - 1s - loss: 0.1757 - accuracy: 0.9635 - binary_crossentropy: 0.1090 - val_loss: 0.4516 - val_accuracy: 0.8642 - val_binary_crossentropy: 0.3849
Epoch 11/20
49/49 - 1s - loss: 0.1735 - accuracy: 0.9642 - binary_crossentropy: 0.1061 - val_loss: 0.4673 - val_accuracy: 0.8620 - val_binary_crossentropy: 0.3996
Epoch 12/20
49/49 - 1s - loss: 0.1681 - accuracy: 0.9661 - binary_crossentropy: 0.1002 - val_loss: 0.4827 - val_accuracy: 0.8602 - val_binary_crossentropy: 0.4145
Epoch 13/20
49/49 - 1s - loss: 0.1656 - accuracy: 0.9678 - binary_crossentropy: 0.0970 - val_loss: 0.4968 - val_accuracy: 0.8591 - val_binary_crossentropy: 0.4280
Epoch 14/20
49/49 - 1s - loss: 0.1638 - accuracy: 0.9678 - binary_crossentropy: 0.0945 - val_loss: 0.5133 - val_accuracy: 0.8553 - val_binary_crossentropy: 0.4438
Epoch 15/20
49/49 - 1s - loss: 0.1601 - accuracy: 0.9695 - binary_crossentropy: 0.0904 - val_loss: 0.5147 - val_accuracy: 0.8600 - val_binary_crossentropy: 0.4448
Epoch 16/20
49/49 - 1s - loss: 0.1600 - accuracy: 0.9694 - binary_crossentropy: 0.0898 - val_loss: 0.5426 - val_accuracy: 0.8548 - val_binary_crossentropy: 0.4719
Epoch 17/20
49/49 - 1s - loss: 0.1596 - accuracy: 0.9696 - binary_crossentropy: 0.0883 - val_loss: 0.5448 - val_accuracy: 0.8566 - val_binary_crossentropy: 0.4733
Epoch 18/20
49/49 - 1s - loss: 0.1541 - accuracy: 0.9713 - binary_crossentropy: 0.0827 - val_loss: 0.5670 - val_accuracy: 0.8521 - val_binary_crossentropy: 0.4953
Epoch 19/20
49/49 - 1s - loss: 0.1520 - accuracy: 0.9725 - binary_crossentropy: 0.0804 - val_loss: 0.5769 - val_accuracy: 0.8514 - val_binary_crossentropy: 0.5055
Epoch 20/20
49/49 - 1s - loss: 0.1520 - accuracy: 0.9714 - binary_crossentropy: 0.0804 - val_loss: 0.5807 - val_accuracy: 0.8537 - val_binary_crossentropy: 0.5082


Так выглядит влияние регуляризации L2:

plot_history([('baseline', baseline_history),
              ('l2', l2_model_history)])

png

Как вы можете видеть L2 регуляризованная модель стала более устойчивой к переобучению чем базовая модель, несмотря на то что обе модели имеют одинаковое количество параметров.

Добавьте дропаут

Дропаут(исключение) одна из наиболее эффективных и часто используемых техник регуляризации нейросетей разработанная Джеффом Хинтоном и его студентами в университете Торонто. Примененный к слою Dropout состоит из некоторого количества случайно "исключенных" (т.е. приравненных к нулю) во время обучения выходных параметров слоя. Допустим что наш слой возвращает вектор [0.2, 0.5, 1.3, 0.8, 1.1] для некоторых входных данных при обучении; после применения дропаута, в этом векторе появится несколько нулевых значений распределенных случайным образом, например [0, 0.5, 1.3, 0, 1.1]. "Коэффициент дропаута(dropout rate)" это доля признаков которые будут обнулены; его обычно устанавливают между 0.2 и 0.5. Во время теста дропаут не используется, вместо этого выходные данные слоев масштабируются на коэффициент равный коэффициенту дропаута, чтобы сбалансировать тот факт, что во время проверки активно больше нейронов чем во время обучения.

В tf.keras можно ввести дропаут с помощью слоя Dropout, который применяется к выходным данным предыдущего слоя.

Давайте применим два слоя Dropout к нашей нейросети IMDB и посмотрим насколько хорошо она сократит переобучение:

dpt_model = keras.models.Sequential([
    keras.layers.Dense(16, activation='relu', input_shape=(NUM_WORDS,)),
    keras.layers.Dropout(0.5),
    keras.layers.Dense(16, activation='relu'),
    keras.layers.Dropout(0.5),
    keras.layers.Dense(1, activation='sigmoid')
])

dpt_model.compile(optimizer='adam',
                  loss='binary_crossentropy',
                  metrics=['accuracy','binary_crossentropy'])

dpt_model_history = dpt_model.fit(train_data, train_labels,
                                  epochs=20,
                                  batch_size=512,
                                  validation_data=(test_data, test_labels),
                                  verbose=2)
Epoch 1/20
49/49 - 2s - loss: 0.6327 - accuracy: 0.6360 - binary_crossentropy: 0.6327 - val_loss: 0.4990 - val_accuracy: 0.8490 - val_binary_crossentropy: 0.4990
Epoch 2/20
49/49 - 1s - loss: 0.4707 - accuracy: 0.8002 - binary_crossentropy: 0.4707 - val_loss: 0.3484 - val_accuracy: 0.8802 - val_binary_crossentropy: 0.3484
Epoch 3/20
49/49 - 1s - loss: 0.3679 - accuracy: 0.8596 - binary_crossentropy: 0.3679 - val_loss: 0.2925 - val_accuracy: 0.8865 - val_binary_crossentropy: 0.2925
Epoch 4/20
49/49 - 1s - loss: 0.3055 - accuracy: 0.8887 - binary_crossentropy: 0.3055 - val_loss: 0.2752 - val_accuracy: 0.8906 - val_binary_crossentropy: 0.2752
Epoch 5/20
49/49 - 1s - loss: 0.2603 - accuracy: 0.9097 - binary_crossentropy: 0.2603 - val_loss: 0.2744 - val_accuracy: 0.8896 - val_binary_crossentropy: 0.2744
Epoch 6/20
49/49 - 1s - loss: 0.2261 - accuracy: 0.9228 - binary_crossentropy: 0.2261 - val_loss: 0.2850 - val_accuracy: 0.8872 - val_binary_crossentropy: 0.2850
Epoch 7/20
49/49 - 1s - loss: 0.1994 - accuracy: 0.9318 - binary_crossentropy: 0.1994 - val_loss: 0.2942 - val_accuracy: 0.8869 - val_binary_crossentropy: 0.2942
Epoch 8/20
49/49 - 1s - loss: 0.1772 - accuracy: 0.9392 - binary_crossentropy: 0.1772 - val_loss: 0.3126 - val_accuracy: 0.8852 - val_binary_crossentropy: 0.3126
Epoch 9/20
49/49 - 1s - loss: 0.1607 - accuracy: 0.9444 - binary_crossentropy: 0.1607 - val_loss: 0.3388 - val_accuracy: 0.8836 - val_binary_crossentropy: 0.3388
Epoch 10/20
49/49 - 1s - loss: 0.1435 - accuracy: 0.9507 - binary_crossentropy: 0.1435 - val_loss: 0.3510 - val_accuracy: 0.8823 - val_binary_crossentropy: 0.3510
Epoch 11/20
49/49 - 1s - loss: 0.1311 - accuracy: 0.9534 - binary_crossentropy: 0.1311 - val_loss: 0.3709 - val_accuracy: 0.8804 - val_binary_crossentropy: 0.3709
Epoch 12/20
49/49 - 1s - loss: 0.1219 - accuracy: 0.9591 - binary_crossentropy: 0.1219 - val_loss: 0.3902 - val_accuracy: 0.8790 - val_binary_crossentropy: 0.3902
Epoch 13/20
49/49 - 1s - loss: 0.1115 - accuracy: 0.9590 - binary_crossentropy: 0.1115 - val_loss: 0.4164 - val_accuracy: 0.8788 - val_binary_crossentropy: 0.4164
Epoch 14/20
49/49 - 1s - loss: 0.1031 - accuracy: 0.9620 - binary_crossentropy: 0.1031 - val_loss: 0.4208 - val_accuracy: 0.8784 - val_binary_crossentropy: 0.4208
Epoch 15/20
49/49 - 1s - loss: 0.0998 - accuracy: 0.9625 - binary_crossentropy: 0.0998 - val_loss: 0.4448 - val_accuracy: 0.8777 - val_binary_crossentropy: 0.4448
Epoch 16/20
49/49 - 1s - loss: 0.0950 - accuracy: 0.9632 - binary_crossentropy: 0.0950 - val_loss: 0.4681 - val_accuracy: 0.8778 - val_binary_crossentropy: 0.4681
Epoch 17/20
49/49 - 1s - loss: 0.0875 - accuracy: 0.9666 - binary_crossentropy: 0.0875 - val_loss: 0.4754 - val_accuracy: 0.8769 - val_binary_crossentropy: 0.4754
Epoch 18/20
49/49 - 1s - loss: 0.0826 - accuracy: 0.9686 - binary_crossentropy: 0.0826 - val_loss: 0.4942 - val_accuracy: 0.8761 - val_binary_crossentropy: 0.4942
Epoch 19/20
49/49 - 1s - loss: 0.0803 - accuracy: 0.9691 - binary_crossentropy: 0.0803 - val_loss: 0.5378 - val_accuracy: 0.8765 - val_binary_crossentropy: 0.5378
Epoch 20/20
49/49 - 1s - loss: 0.0781 - accuracy: 0.9703 - binary_crossentropy: 0.0781 - val_loss: 0.5576 - val_accuracy: 0.8774 - val_binary_crossentropy: 0.5576

plot_history([('baseline', baseline_history),
              ('dropout', dpt_model_history)])

png

Добавление дропаута явно улучает базовую модель.

Подведем итоги - вот самые основные способы предотвращения переобучения нейросетей:

  • Использовать больше данных для обучения.
  • Уменьшить емкость сети.
  • Использовать регуляризацию весов.
  • Добавить дропаут.

Два важных подхода которые не были рассмотрены в данном руководстве это аугментация данных (data-augmentation) и батч-нормализация (batch normalization).


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