Explore overfit and underfit

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

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

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

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

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

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

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

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

from __future__ import absolute_import, division, print_function, unicode_literals

import tensorflow as tf
from tensorflow import keras

import numpy as np
import matplotlib.pyplot as plt

print(tf.__version__)
2.0.0-rc2

Загрузите датасет 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 0x7f86cfd93b70>]

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)
Train on 25000 samples, validate on 25000 samples
Epoch 1/20
WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.5/site-packages/tensorflow_core/python/ops/nn_impl.py:183: where (from tensorflow.python.ops.array_ops) is deprecated and will be removed in a future version.
Instructions for updating:
Use tf.where in 2.0, which has the same broadcast rule as np.where
25000/25000 - 2s - loss: 0.5082 - accuracy: 0.7990 - binary_crossentropy: 0.5082 - val_loss: 0.3621 - val_accuracy: 0.8741 - val_binary_crossentropy: 0.3621
Epoch 2/20
25000/25000 - 1s - loss: 0.2626 - accuracy: 0.9075 - binary_crossentropy: 0.2626 - val_loss: 0.2816 - val_accuracy: 0.8890 - val_binary_crossentropy: 0.2816
Epoch 3/20
25000/25000 - 1s - loss: 0.1845 - accuracy: 0.9338 - binary_crossentropy: 0.1845 - val_loss: 0.2902 - val_accuracy: 0.8861 - val_binary_crossentropy: 0.2902
Epoch 4/20
25000/25000 - 1s - loss: 0.1464 - accuracy: 0.9502 - binary_crossentropy: 0.1464 - val_loss: 0.3090 - val_accuracy: 0.8801 - val_binary_crossentropy: 0.3090
Epoch 5/20
25000/25000 - 1s - loss: 0.1211 - accuracy: 0.9606 - binary_crossentropy: 0.1211 - val_loss: 0.3398 - val_accuracy: 0.8743 - val_binary_crossentropy: 0.3398
Epoch 6/20
25000/25000 - 1s - loss: 0.1010 - accuracy: 0.9685 - binary_crossentropy: 0.1010 - val_loss: 0.3700 - val_accuracy: 0.8712 - val_binary_crossentropy: 0.3700
Epoch 7/20
25000/25000 - 1s - loss: 0.0854 - accuracy: 0.9742 - binary_crossentropy: 0.0854 - val_loss: 0.4058 - val_accuracy: 0.8652 - val_binary_crossentropy: 0.4058
Epoch 8/20
25000/25000 - 1s - loss: 0.0717 - accuracy: 0.9798 - binary_crossentropy: 0.0717 - val_loss: 0.4457 - val_accuracy: 0.8626 - val_binary_crossentropy: 0.4457
Epoch 9/20
25000/25000 - 2s - loss: 0.0599 - accuracy: 0.9844 - binary_crossentropy: 0.0599 - val_loss: 0.4848 - val_accuracy: 0.8605 - val_binary_crossentropy: 0.4848
Epoch 10/20
25000/25000 - 2s - loss: 0.0489 - accuracy: 0.9892 - binary_crossentropy: 0.0489 - val_loss: 0.5299 - val_accuracy: 0.8576 - val_binary_crossentropy: 0.5299
Epoch 11/20
25000/25000 - 2s - loss: 0.0403 - accuracy: 0.9918 - binary_crossentropy: 0.0403 - val_loss: 0.5722 - val_accuracy: 0.8550 - val_binary_crossentropy: 0.5722
Epoch 12/20
25000/25000 - 2s - loss: 0.0325 - accuracy: 0.9948 - binary_crossentropy: 0.0325 - val_loss: 0.6153 - val_accuracy: 0.8545 - val_binary_crossentropy: 0.6153
Epoch 13/20
25000/25000 - 1s - loss: 0.0259 - accuracy: 0.9960 - binary_crossentropy: 0.0259 - val_loss: 0.6603 - val_accuracy: 0.8527 - val_binary_crossentropy: 0.6603
Epoch 14/20
25000/25000 - 1s - loss: 0.0199 - accuracy: 0.9980 - binary_crossentropy: 0.0199 - val_loss: 0.7033 - val_accuracy: 0.8504 - val_binary_crossentropy: 0.7033
Epoch 15/20
25000/25000 - 1s - loss: 0.0152 - accuracy: 0.9988 - binary_crossentropy: 0.0152 - val_loss: 0.7414 - val_accuracy: 0.8511 - val_binary_crossentropy: 0.7414
Epoch 16/20
25000/25000 - 1s - loss: 0.0115 - accuracy: 0.9994 - binary_crossentropy: 0.0115 - val_loss: 0.7866 - val_accuracy: 0.8496 - val_binary_crossentropy: 0.7866
Epoch 17/20
25000/25000 - 1s - loss: 0.0088 - accuracy: 0.9997 - binary_crossentropy: 0.0088 - val_loss: 0.8221 - val_accuracy: 0.8487 - val_binary_crossentropy: 0.8221
Epoch 18/20
25000/25000 - 1s - loss: 0.0067 - accuracy: 0.9998 - binary_crossentropy: 0.0067 - val_loss: 0.8531 - val_accuracy: 0.8485 - val_binary_crossentropy: 0.8531
Epoch 19/20
25000/25000 - 1s - loss: 0.0052 - accuracy: 1.0000 - binary_crossentropy: 0.0052 - val_loss: 0.8838 - val_accuracy: 0.8482 - val_binary_crossentropy: 0.8838
Epoch 20/20
25000/25000 - 1s - loss: 0.0043 - accuracy: 1.0000 - binary_crossentropy: 0.0043 - val_loss: 0.9086 - val_accuracy: 0.8484 - val_binary_crossentropy: 0.9086

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

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

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)
Train on 25000 samples, validate on 25000 samples
Epoch 1/20
25000/25000 - 2s - loss: 0.5599 - accuracy: 0.7644 - binary_crossentropy: 0.5599 - val_loss: 0.4389 - val_accuracy: 0.8559 - val_binary_crossentropy: 0.4389
Epoch 2/20
25000/25000 - 1s - loss: 0.3445 - accuracy: 0.8896 - binary_crossentropy: 0.3445 - val_loss: 0.3342 - val_accuracy: 0.8781 - val_binary_crossentropy: 0.3342
Epoch 3/20
25000/25000 - 1s - loss: 0.2593 - accuracy: 0.9154 - binary_crossentropy: 0.2593 - val_loss: 0.2989 - val_accuracy: 0.8862 - val_binary_crossentropy: 0.2989
Epoch 4/20
25000/25000 - 1s - loss: 0.2146 - accuracy: 0.9290 - binary_crossentropy: 0.2146 - val_loss: 0.2871 - val_accuracy: 0.8874 - val_binary_crossentropy: 0.2871
Epoch 5/20
25000/25000 - 1s - loss: 0.1854 - accuracy: 0.9384 - binary_crossentropy: 0.1854 - val_loss: 0.2845 - val_accuracy: 0.8876 - val_binary_crossentropy: 0.2845
Epoch 6/20
25000/25000 - 1s - loss: 0.1636 - accuracy: 0.9462 - binary_crossentropy: 0.1636 - val_loss: 0.2879 - val_accuracy: 0.8854 - val_binary_crossentropy: 0.2879
Epoch 7/20
25000/25000 - 1s - loss: 0.1457 - accuracy: 0.9534 - binary_crossentropy: 0.1457 - val_loss: 0.2952 - val_accuracy: 0.8835 - val_binary_crossentropy: 0.2952
Epoch 8/20
25000/25000 - 1s - loss: 0.1316 - accuracy: 0.9592 - binary_crossentropy: 0.1316 - val_loss: 0.3066 - val_accuracy: 0.8799 - val_binary_crossentropy: 0.3066
Epoch 9/20
25000/25000 - 1s - loss: 0.1189 - accuracy: 0.9636 - binary_crossentropy: 0.1189 - val_loss: 0.3168 - val_accuracy: 0.8780 - val_binary_crossentropy: 0.3168
Epoch 10/20
25000/25000 - 1s - loss: 0.1081 - accuracy: 0.9678 - binary_crossentropy: 0.1081 - val_loss: 0.3312 - val_accuracy: 0.8751 - val_binary_crossentropy: 0.3312
Epoch 11/20
25000/25000 - 1s - loss: 0.0982 - accuracy: 0.9715 - binary_crossentropy: 0.0982 - val_loss: 0.3470 - val_accuracy: 0.8723 - val_binary_crossentropy: 0.3470
Epoch 12/20
25000/25000 - 1s - loss: 0.0895 - accuracy: 0.9748 - binary_crossentropy: 0.0895 - val_loss: 0.3599 - val_accuracy: 0.8724 - val_binary_crossentropy: 0.3599
Epoch 13/20
25000/25000 - 1s - loss: 0.0813 - accuracy: 0.9779 - binary_crossentropy: 0.0813 - val_loss: 0.3767 - val_accuracy: 0.8699 - val_binary_crossentropy: 0.3767
Epoch 14/20
25000/25000 - 1s - loss: 0.0744 - accuracy: 0.9812 - binary_crossentropy: 0.0744 - val_loss: 0.3944 - val_accuracy: 0.8681 - val_binary_crossentropy: 0.3944
Epoch 15/20
25000/25000 - 1s - loss: 0.0673 - accuracy: 0.9840 - binary_crossentropy: 0.0673 - val_loss: 0.4146 - val_accuracy: 0.8659 - val_binary_crossentropy: 0.4146
Epoch 16/20
25000/25000 - 1s - loss: 0.0611 - accuracy: 0.9867 - binary_crossentropy: 0.0611 - val_loss: 0.4323 - val_accuracy: 0.8654 - val_binary_crossentropy: 0.4323
Epoch 17/20
25000/25000 - 1s - loss: 0.0559 - accuracy: 0.9884 - binary_crossentropy: 0.0559 - val_loss: 0.4529 - val_accuracy: 0.8625 - val_binary_crossentropy: 0.4529
Epoch 18/20
25000/25000 - 1s - loss: 0.0504 - accuracy: 0.9905 - binary_crossentropy: 0.0504 - val_loss: 0.4728 - val_accuracy: 0.8609 - val_binary_crossentropy: 0.4728
Epoch 19/20
25000/25000 - 1s - loss: 0.0458 - accuracy: 0.9922 - binary_crossentropy: 0.0458 - val_loss: 0.4932 - val_accuracy: 0.8595 - val_binary_crossentropy: 0.4932
Epoch 20/20
25000/25000 - 1s - loss: 0.0414 - accuracy: 0.9932 - binary_crossentropy: 0.0414 - val_loss: 0.5140 - val_accuracy: 0.8586 - val_binary_crossentropy: 0.5140

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

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

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)
Train on 25000 samples, validate on 25000 samples
Epoch 1/20
25000/25000 - 2s - loss: 0.3653 - accuracy: 0.8338 - binary_crossentropy: 0.3653 - val_loss: 0.2921 - val_accuracy: 0.8798 - val_binary_crossentropy: 0.2921
Epoch 2/20
25000/25000 - 1s - loss: 0.1456 - accuracy: 0.9469 - binary_crossentropy: 0.1456 - val_loss: 0.3184 - val_accuracy: 0.8766 - val_binary_crossentropy: 0.3184
Epoch 3/20
25000/25000 - 2s - loss: 0.0440 - accuracy: 0.9872 - binary_crossentropy: 0.0440 - val_loss: 0.4476 - val_accuracy: 0.8680 - val_binary_crossentropy: 0.4476
Epoch 4/20
25000/25000 - 2s - loss: 0.0057 - accuracy: 0.9992 - binary_crossentropy: 0.0057 - val_loss: 0.5737 - val_accuracy: 0.8708 - val_binary_crossentropy: 0.5737
Epoch 5/20
25000/25000 - 1s - loss: 6.7439e-04 - accuracy: 1.0000 - binary_crossentropy: 6.7439e-04 - val_loss: 0.6801 - val_accuracy: 0.8717 - val_binary_crossentropy: 0.6801
Epoch 6/20
25000/25000 - 2s - loss: 1.8795e-04 - accuracy: 1.0000 - binary_crossentropy: 1.8795e-04 - val_loss: 0.7240 - val_accuracy: 0.8718 - val_binary_crossentropy: 0.7240
Epoch 7/20
25000/25000 - 1s - loss: 1.1903e-04 - accuracy: 1.0000 - binary_crossentropy: 1.1903e-04 - val_loss: 0.7537 - val_accuracy: 0.8718 - val_binary_crossentropy: 0.7537
Epoch 8/20
25000/25000 - 2s - loss: 8.5924e-05 - accuracy: 1.0000 - binary_crossentropy: 8.5924e-05 - val_loss: 0.7773 - val_accuracy: 0.8720 - val_binary_crossentropy: 0.7773
Epoch 9/20
25000/25000 - 2s - loss: 6.6491e-05 - accuracy: 1.0000 - binary_crossentropy: 6.6491e-05 - val_loss: 0.7955 - val_accuracy: 0.8722 - val_binary_crossentropy: 0.7955
Epoch 10/20
25000/25000 - 2s - loss: 5.3184e-05 - accuracy: 1.0000 - binary_crossentropy: 5.3184e-05 - val_loss: 0.8122 - val_accuracy: 0.8720 - val_binary_crossentropy: 0.8122
Epoch 11/20
25000/25000 - 2s - loss: 4.3780e-05 - accuracy: 1.0000 - binary_crossentropy: 4.3780e-05 - val_loss: 0.8263 - val_accuracy: 0.8725 - val_binary_crossentropy: 0.8263
Epoch 12/20
25000/25000 - 2s - loss: 3.6625e-05 - accuracy: 1.0000 - binary_crossentropy: 3.6625e-05 - val_loss: 0.8402 - val_accuracy: 0.8724 - val_binary_crossentropy: 0.8402
Epoch 13/20
25000/25000 - 2s - loss: 3.1187e-05 - accuracy: 1.0000 - binary_crossentropy: 3.1187e-05 - val_loss: 0.8514 - val_accuracy: 0.8724 - val_binary_crossentropy: 0.8514
Epoch 14/20
25000/25000 - 2s - loss: 2.6848e-05 - accuracy: 1.0000 - binary_crossentropy: 2.6848e-05 - val_loss: 0.8636 - val_accuracy: 0.8726 - val_binary_crossentropy: 0.8636
Epoch 15/20
25000/25000 - 2s - loss: 2.3433e-05 - accuracy: 1.0000 - binary_crossentropy: 2.3433e-05 - val_loss: 0.8730 - val_accuracy: 0.8726 - val_binary_crossentropy: 0.8730
Epoch 16/20
25000/25000 - 2s - loss: 2.0542e-05 - accuracy: 1.0000 - binary_crossentropy: 2.0542e-05 - val_loss: 0.8832 - val_accuracy: 0.8726 - val_binary_crossentropy: 0.8832
Epoch 17/20
25000/25000 - 2s - loss: 1.8195e-05 - accuracy: 1.0000 - binary_crossentropy: 1.8195e-05 - val_loss: 0.8924 - val_accuracy: 0.8724 - val_binary_crossentropy: 0.8924
Epoch 18/20
25000/25000 - 2s - loss: 1.6205e-05 - accuracy: 1.0000 - binary_crossentropy: 1.6205e-05 - val_loss: 0.9006 - val_accuracy: 0.8726 - val_binary_crossentropy: 0.9006
Epoch 19/20
25000/25000 - 2s - loss: 1.4523e-05 - accuracy: 1.0000 - binary_crossentropy: 1.4523e-05 - val_loss: 0.9089 - val_accuracy: 0.8724 - val_binary_crossentropy: 0.9089
Epoch 20/20
25000/25000 - 2s - loss: 1.3082e-05 - accuracy: 1.0000 - binary_crossentropy: 1.3082e-05 - val_loss: 0.9164 - val_accuracy: 0.8724 - val_binary_crossentropy: 0.9164

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

Непрерывные линии показывают потери во время обучения, а прерывистые - во время проверки (помни - меньшие потери на проверочных данных указывают на лучшую модель). В нашем случае самая маленькая модель начинает переобучаться позже, чем основная (после 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)
Train on 25000 samples, validate on 25000 samples
Epoch 1/20
25000/25000 - 2s - loss: 0.5196 - accuracy: 0.8158 - binary_crossentropy: 0.4781 - val_loss: 0.3822 - val_accuracy: 0.8776 - val_binary_crossentropy: 0.3379
Epoch 2/20
25000/25000 - 1s - loss: 0.3049 - accuracy: 0.9104 - binary_crossentropy: 0.2561 - val_loss: 0.3390 - val_accuracy: 0.8856 - val_binary_crossentropy: 0.2873
Epoch 3/20
25000/25000 - 1s - loss: 0.2549 - accuracy: 0.9311 - binary_crossentropy: 0.2007 - val_loss: 0.3399 - val_accuracy: 0.8872 - val_binary_crossentropy: 0.2843
Epoch 4/20
25000/25000 - 1s - loss: 0.2320 - accuracy: 0.9399 - binary_crossentropy: 0.1748 - val_loss: 0.3576 - val_accuracy: 0.8798 - val_binary_crossentropy: 0.2994
Epoch 5/20
25000/25000 - 1s - loss: 0.2173 - accuracy: 0.9468 - binary_crossentropy: 0.1579 - val_loss: 0.3656 - val_accuracy: 0.8788 - val_binary_crossentropy: 0.3054
Epoch 6/20
25000/25000 - 1s - loss: 0.2077 - accuracy: 0.9496 - binary_crossentropy: 0.1466 - val_loss: 0.3774 - val_accuracy: 0.8762 - val_binary_crossentropy: 0.3158
Epoch 7/20
25000/25000 - 1s - loss: 0.1982 - accuracy: 0.9541 - binary_crossentropy: 0.1359 - val_loss: 0.3910 - val_accuracy: 0.8730 - val_binary_crossentropy: 0.3284
Epoch 8/20
25000/25000 - 1s - loss: 0.1942 - accuracy: 0.9556 - binary_crossentropy: 0.1311 - val_loss: 0.4079 - val_accuracy: 0.8713 - val_binary_crossentropy: 0.3442
Epoch 9/20
25000/25000 - 1s - loss: 0.1886 - accuracy: 0.9572 - binary_crossentropy: 0.1242 - val_loss: 0.4174 - val_accuracy: 0.8696 - val_binary_crossentropy: 0.3527
Epoch 10/20
25000/25000 - 1s - loss: 0.1824 - accuracy: 0.9600 - binary_crossentropy: 0.1174 - val_loss: 0.4294 - val_accuracy: 0.8675 - val_binary_crossentropy: 0.3641
Epoch 11/20
25000/25000 - 1s - loss: 0.1772 - accuracy: 0.9629 - binary_crossentropy: 0.1117 - val_loss: 0.4464 - val_accuracy: 0.8632 - val_binary_crossentropy: 0.3808
Epoch 12/20
25000/25000 - 1s - loss: 0.1742 - accuracy: 0.9636 - binary_crossentropy: 0.1082 - val_loss: 0.4484 - val_accuracy: 0.8658 - val_binary_crossentropy: 0.3821
Epoch 13/20
25000/25000 - 1s - loss: 0.1724 - accuracy: 0.9646 - binary_crossentropy: 0.1054 - val_loss: 0.4670 - val_accuracy: 0.8621 - val_binary_crossentropy: 0.3997
Epoch 14/20
25000/25000 - 1s - loss: 0.1674 - accuracy: 0.9653 - binary_crossentropy: 0.0998 - val_loss: 0.4736 - val_accuracy: 0.8635 - val_binary_crossentropy: 0.4058
Epoch 15/20
25000/25000 - 1s - loss: 0.1616 - accuracy: 0.9694 - binary_crossentropy: 0.0937 - val_loss: 0.4885 - val_accuracy: 0.8604 - val_binary_crossentropy: 0.4206
Epoch 16/20
25000/25000 - 1s - loss: 0.1592 - accuracy: 0.9694 - binary_crossentropy: 0.0912 - val_loss: 0.4992 - val_accuracy: 0.8588 - val_binary_crossentropy: 0.4308
Epoch 17/20
25000/25000 - 1s - loss: 0.1567 - accuracy: 0.9702 - binary_crossentropy: 0.0880 - val_loss: 0.5086 - val_accuracy: 0.8592 - val_binary_crossentropy: 0.4395
Epoch 18/20
25000/25000 - 1s - loss: 0.1532 - accuracy: 0.9726 - binary_crossentropy: 0.0841 - val_loss: 0.5182 - val_accuracy: 0.8553 - val_binary_crossentropy: 0.4487
Epoch 19/20
25000/25000 - 1s - loss: 0.1511 - accuracy: 0.9724 - binary_crossentropy: 0.0815 - val_loss: 0.5323 - val_accuracy: 0.8572 - val_binary_crossentropy: 0.4625
Epoch 20/20
25000/25000 - 1s - loss: 0.1525 - accuracy: 0.9716 - binary_crossentropy: 0.0821 - val_loss: 0.5427 - val_accuracy: 0.8568 - val_binary_crossentropy: 0.4716

l2(0.001) значит что каждый коэффициент в матрице весов слоя добавит 0.001 * weight_coefficient_value**2 к значению потерь нейросети. Заметьте, что поскольку этот штраф доавляется только во время обучения, потери сети во время этой стадии будут гораздо выше чем во время теста.

Так выглядит влияние регуляризации 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)
Train on 25000 samples, validate on 25000 samples
Epoch 1/20
25000/25000 - 2s - loss: 0.6438 - accuracy: 0.6053 - binary_crossentropy: 0.6438 - val_loss: 0.5163 - val_accuracy: 0.8452 - val_binary_crossentropy: 0.5163
Epoch 2/20
25000/25000 - 1s - loss: 0.4929 - accuracy: 0.7653 - binary_crossentropy: 0.4929 - val_loss: 0.3575 - val_accuracy: 0.8791 - val_binary_crossentropy: 0.3575
Epoch 3/20
25000/25000 - 1s - loss: 0.3810 - accuracy: 0.8430 - binary_crossentropy: 0.3810 - val_loss: 0.2951 - val_accuracy: 0.8861 - val_binary_crossentropy: 0.2951
Epoch 4/20
25000/25000 - 1s - loss: 0.3162 - accuracy: 0.8784 - binary_crossentropy: 0.3162 - val_loss: 0.2761 - val_accuracy: 0.8900 - val_binary_crossentropy: 0.2761
Epoch 5/20
25000/25000 - 1s - loss: 0.2696 - accuracy: 0.8981 - binary_crossentropy: 0.2696 - val_loss: 0.2739 - val_accuracy: 0.8889 - val_binary_crossentropy: 0.2739
Epoch 6/20
25000/25000 - 1s - loss: 0.2399 - accuracy: 0.9119 - binary_crossentropy: 0.2399 - val_loss: 0.2798 - val_accuracy: 0.8874 - val_binary_crossentropy: 0.2798
Epoch 7/20
25000/25000 - 1s - loss: 0.2128 - accuracy: 0.9220 - binary_crossentropy: 0.2128 - val_loss: 0.2874 - val_accuracy: 0.8858 - val_binary_crossentropy: 0.2874
Epoch 8/20
25000/25000 - 1s - loss: 0.1886 - accuracy: 0.9307 - binary_crossentropy: 0.1886 - val_loss: 0.3169 - val_accuracy: 0.8816 - val_binary_crossentropy: 0.3169
Epoch 9/20
25000/25000 - 1s - loss: 0.1757 - accuracy: 0.9357 - binary_crossentropy: 0.1757 - val_loss: 0.3214 - val_accuracy: 0.8832 - val_binary_crossentropy: 0.3214
Epoch 10/20
25000/25000 - 1s - loss: 0.1565 - accuracy: 0.9422 - binary_crossentropy: 0.1565 - val_loss: 0.3425 - val_accuracy: 0.8809 - val_binary_crossentropy: 0.3425
Epoch 11/20
25000/25000 - 1s - loss: 0.1465 - accuracy: 0.9465 - binary_crossentropy: 0.1465 - val_loss: 0.3896 - val_accuracy: 0.8763 - val_binary_crossentropy: 0.3896
Epoch 12/20
25000/25000 - 1s - loss: 0.1369 - accuracy: 0.9499 - binary_crossentropy: 0.1369 - val_loss: 0.3839 - val_accuracy: 0.8776 - val_binary_crossentropy: 0.3839
Epoch 13/20
25000/25000 - 1s - loss: 0.1273 - accuracy: 0.9508 - binary_crossentropy: 0.1273 - val_loss: 0.3974 - val_accuracy: 0.8778 - val_binary_crossentropy: 0.3974
Epoch 14/20
25000/25000 - 1s - loss: 0.1157 - accuracy: 0.9548 - binary_crossentropy: 0.1157 - val_loss: 0.4081 - val_accuracy: 0.8788 - val_binary_crossentropy: 0.4081
Epoch 15/20
25000/25000 - 1s - loss: 0.1070 - accuracy: 0.9588 - binary_crossentropy: 0.1070 - val_loss: 0.4347 - val_accuracy: 0.8787 - val_binary_crossentropy: 0.4347
Epoch 16/20
25000/25000 - 1s - loss: 0.1030 - accuracy: 0.9587 - binary_crossentropy: 0.1030 - val_loss: 0.4541 - val_accuracy: 0.8770 - val_binary_crossentropy: 0.4541
Epoch 17/20
25000/25000 - 1s - loss: 0.0946 - accuracy: 0.9627 - binary_crossentropy: 0.0946 - val_loss: 0.4805 - val_accuracy: 0.8754 - val_binary_crossentropy: 0.4805
Epoch 18/20
25000/25000 - 1s - loss: 0.0942 - accuracy: 0.9598 - binary_crossentropy: 0.0942 - val_loss: 0.4849 - val_accuracy: 0.8764 - val_binary_crossentropy: 0.4849
Epoch 19/20
25000/25000 - 1s - loss: 0.0869 - accuracy: 0.9642 - binary_crossentropy: 0.0869 - val_loss: 0.5324 - val_accuracy: 0.8765 - val_binary_crossentropy: 0.5324
Epoch 20/20
25000/25000 - 1s - loss: 0.0848 - accuracy: 0.9635 - binary_crossentropy: 0.0848 - val_loss: 0.5436 - val_accuracy: 0.8750 - val_binary_crossentropy: 0.5436
plot_history([('baseline', baseline_history),
              ('dropout', dpt_model_history)])

png

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

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

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

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

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