¡El Día de la Comunidad de ML es el 9 de noviembre! Únase a nosotros para recibir actualizaciones de TensorFlow, JAX, y más Más información

Overfit y underfit

Ver en TensorFlow.org Ejecutar en Google Colab Ver fuente en GitHub Descargar cuaderno

Como siempre, el código de este ejemplo utilizará el tf.keras API, que se puede aprender más acerca de la TensorFlow guía Keras .

En tanto del anterior ejemplos- texto de clasificación y predicción de la eficiencia del combustible - vimos que la precisión de nuestro modelo de los datos de validación alcanzaría su punto máximo después del entrenamiento para un número de épocas, y que luego se estancan o empezar a disminuir.

En otras palabras, nuestro modelo sería overfit a los datos de entrenamiento. Aprender a lidiar con el sobreajuste es importante. Aunque a menudo es posible lograr una alta precisión en el conjunto de entrenamiento, lo que realmente queremos es desarrollar modelos que generalizan así a un conjunto de pruebas (o datos que no han visto antes).

Lo contrario de overfitting se underfitting. El desajuste se produce cuando todavía hay margen de mejora en los datos del tren. Esto puede suceder por varias razones: si el modelo no es lo suficientemente potente, está sobre regularizado o simplemente no se ha entrenado lo suficiente. Esto significa que la red no ha aprendido los patrones relevantes en los datos de entrenamiento.

Sin embargo, si entrena durante demasiado tiempo, el modelo comenzará a sobreajustarse y aprenderá patrones de los datos de entrenamiento que no se generalizan a los datos de prueba. Necesitamos encontrar un equilibrio. Comprender cómo entrenar para un número apropiado de épocas, como exploraremos a continuación, es una habilidad útil.

Para evitar el sobreajuste, la mejor solución es utilizar datos de entrenamiento más completos. El conjunto de datos debe cubrir la gama completa de entradas que se espera que maneje el modelo. Los datos adicionales solo pueden ser útiles si cubren casos nuevos e interesantes.

Un modelo entrenado con datos más completos, naturalmente, generalizará mejor. Cuando eso ya no sea posible, la siguiente mejor solución es utilizar técnicas como la regularización. Estos imponen restricciones sobre la cantidad y el tipo de información que su modelo puede almacenar. Si una red solo puede permitirse el lujo de memorizar una pequeña cantidad de patrones, el proceso de optimización la obligará a centrarse en los patrones más destacados, que tienen más posibilidades de generalizarse bien.

En este cuaderno, exploraremos varias técnicas de regularización comunes y las usaremos para mejorar un modelo de clasificación.

Configuración

Antes de comenzar, importe los paquetes necesarios:

import tensorflow as tf

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

print(tf.__version__)
2.5.0
!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)

El conjunto de datos de Higgs

El objetivo de este tutorial no es hacer física de partículas, así que no te detengas en los detalles del conjunto de datos. Contiene 11 000 000 de ejemplos, cada uno con 28 funciones y una etiqueta de clase binaria.

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 [==============================] - 148s 0us/step
FEATURES = 28

El tf.data.experimental.CsvDataset clase se puede utilizar para leer los registros CSV directamente desde un archivo gzip sin etapa de descompresión intermedia.

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

Esa clase de lector de csv devuelve una lista de escalares para cada registro. La siguiente función vuelve a empaquetar esa lista de escalares en un par (feature_vector, label).

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

TensorFlow es más eficiente cuando opera con grandes lotes de datos.

Así que en lugar de volver a empaquetar cada fila hacen de forma individual un nuevo Dataset que se lleva a lotes de 10000-ejemplos, se aplica la pack_row función para cada lote, y luego divide los lotes de copia de seguridad en los registros individuales:

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

Echar un vistazo a algunos de los registros de esta nueva packed_ds .

Las funciones no están perfectamente normalizadas, pero esto es suficiente para este tutorial.

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

Para mantener este tutorial relativamente corto, use solo las primeras 1000 muestras para la validación y las siguientes 10000 para el entrenamiento:

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

Los Dataset.skip y Dataset.take métodos facilitan esta tarea.

Al mismo tiempo, utilice el Dataset.cache método para asegurarse de que el cargador no hay necesidad de volver a leer los datos del archivo en cada época:

validate_ds = packed_ds.take(N_VALIDATION).cache()
train_ds = packed_ds.skip(N_VALIDATION).take(N_TRAIN).cache()
train_ds
<CacheDataset shapes: ((28,), ()), types: (tf.float32, tf.float32)>

Estos conjuntos de datos devuelven ejemplos individuales. Usar la .batch método para crear lotes de un tamaño adecuado para la formación. Antes de procesamiento por lotes también recordar a .shuffle y .repeat el conjunto de entrenamiento.

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

Demostrar sobreajuste

La forma más sencilla de evitar el sobreajuste es comenzar con un modelo pequeño: un modelo con una pequeña cantidad de parámetros aprendebles (que está determinada por la cantidad de capas y la cantidad de unidades por capa). En el aprendizaje profundo, la cantidad de parámetros que se pueden aprender en un modelo a menudo se denomina "capacidad" del modelo.

Intuitivamente, un modelo con más parámetros tendrá más "capacidad de memorización" y, por lo tanto, podrá aprender fácilmente un mapeo perfecto tipo diccionario entre las muestras de entrenamiento y sus objetivos, un mapeo sin ningún poder de generalización, pero esto sería inútil al hacer predicciones. en datos nunca antes vistos.

Siempre tenga esto en cuenta: los modelos de aprendizaje profundo tienden a adaptarse bien a los datos de entrenamiento, pero el verdadero desafío es la generalización, no la adaptación.

Por otro lado, si la red tiene recursos de memorización limitados, no podrá aprender el mapeo tan fácilmente. Para minimizar su pérdida, tendrá que aprender representaciones comprimidas que tengan más poder predictivo. Al mismo tiempo, si hace que su modelo sea demasiado pequeño, tendrá dificultades para adaptarse a los datos de entrenamiento. Existe un equilibrio entre "demasiada capacidad" y "poca capacidad".

Desafortunadamente, no existe una fórmula mágica para determinar el tamaño o la arquitectura correctos de su modelo (en términos de la cantidad de capas o el tamaño correcto para cada capa). Tendrás que experimentar utilizando una serie de arquitecturas diferentes.

Para encontrar un tamaño de modelo apropiado, es mejor comenzar con relativamente pocas capas y parámetros, luego comenzar a aumentar el tamaño de las capas o agregar nuevas capas hasta que vea rendimientos decrecientes en la pérdida de validación.

Comenzar con un modelo simple utilizando sólo layers.Dense como línea de base, a continuación, crear versiones más grandes, y los compare.

Procedimiento de entrenamiento

Muchos modelos entrenan mejor si reduce gradualmente la tasa de aprendizaje durante el entrenamiento. Utilice optimizers.schedules para reducir la tasa de aprendizaje en el tiempo:

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)

El código anterior establece un schedules.InverseTimeDecay para disminuir hiperbólicamente la tasa de aprendizaje a 1/2 de la tasa base a 1000 épocas, 1/3 a 2000 épocas y así sucesivamente.

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

Cada modelo de este tutorial utilizará la misma configuración de entrenamiento. Así que configúrelos de una manera reutilizable, comenzando con la lista de devoluciones de llamada.

El entrenamiento para este tutorial se ejecuta durante muchas épocas cortas. Para reducir el ruido de la tala utilizar los tfdocs.EpochDots que simplemente se imprime una . para cada época y un conjunto completo de métricas cada 100 épocas.

A continuación incluyen callbacks.EarlyStopping para evitar largos e innecesarios tiempos de entrenamiento. Tenga en cuenta que esta devolución de llamada está configurado para monitorear la val_binary_crossentropy , no el val_loss . Esta diferencia será importante más adelante.

Uso callbacks.TensorBoard para generar registros TensorBoard para la formación.

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

Del mismo modo cada modelo usará los mismos Model.compile y Model.fit ajustes:

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

Pequeño modelo

Empiece por entrenar un modelo:

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
_________________________________________________________________
WARNING:tensorflow:Callback method `on_train_batch_begin` is slow compared to the batch time (batch time: 0.0031s vs `on_train_batch_begin` time: 0.0346s). Check your callbacks.
WARNING:tensorflow:Callback method `on_train_batch_end` is slow compared to the batch time (batch time: 0.0031s vs `on_train_batch_end` time: 0.0125s). Check your callbacks.

Epoch: 0, accuracy:0.5093,  binary_crossentropy:0.7681,  loss:0.7681,  val_accuracy:0.5000,  val_binary_crossentropy:0.7207,  val_loss:0.7207,  
....................................................................................................
Epoch: 100, accuracy:0.6058,  binary_crossentropy:0.6244,  loss:0.6244,  val_accuracy:0.5700,  val_binary_crossentropy:0.6322,  val_loss:0.6322,  
....................................................................................................
Epoch: 200, accuracy:0.6220,  binary_crossentropy:0.6124,  loss:0.6124,  val_accuracy:0.5990,  val_binary_crossentropy:0.6202,  val_loss:0.6202,  
....................................................................................................
Epoch: 300, accuracy:0.6388,  binary_crossentropy:0.6045,  loss:0.6045,  val_accuracy:0.6150,  val_binary_crossentropy:0.6114,  val_loss:0.6114,  
....................................................................................................
Epoch: 400, accuracy:0.6475,  binary_crossentropy:0.5976,  loss:0.5976,  val_accuracy:0.6270,  val_binary_crossentropy:0.6012,  val_loss:0.6012,  
....................................................................................................
Epoch: 500, accuracy:0.6579,  binary_crossentropy:0.5917,  loss:0.5917,  val_accuracy:0.6390,  val_binary_crossentropy:0.5929,  val_loss:0.5929,  
....................................................................................................
Epoch: 600, accuracy:0.6662,  binary_crossentropy:0.5878,  loss:0.5878,  val_accuracy:0.6410,  val_binary_crossentropy:0.5890,  val_loss:0.5890,  
....................................................................................................
Epoch: 700, accuracy:0.6664,  binary_crossentropy:0.5847,  loss:0.5847,  val_accuracy:0.6670,  val_binary_crossentropy:0.5865,  val_loss:0.5865,  
....................................................................................................
Epoch: 800, accuracy:0.6709,  binary_crossentropy:0.5822,  loss:0.5822,  val_accuracy:0.6460,  val_binary_crossentropy:0.5896,  val_loss:0.5896,  
....................................................................................................
Epoch: 900, accuracy:0.6772,  binary_crossentropy:0.5793,  loss:0.5793,  val_accuracy:0.6540,  val_binary_crossentropy:0.5880,  val_loss:0.5880,  
...................

Ahora compruebe cómo le fue al modelo:

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

Modelo pequeño

Para ver si puede superar el rendimiento del modelo pequeño, entrene progresivamente algunos modelos más grandes.

Prueba dos capas ocultas con 16 unidades cada una:

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
_________________________________________________________________
WARNING:tensorflow:Callback method `on_train_batch_begin` is slow compared to the batch time (batch time: 0.0030s vs `on_train_batch_begin` time: 0.0258s). Check your callbacks.
WARNING:tensorflow:Callback method `on_train_batch_end` is slow compared to the batch time (batch time: 0.0030s vs `on_train_batch_end` time: 0.0176s). Check your callbacks.

Epoch: 0, accuracy:0.4757,  binary_crossentropy:0.7130,  loss:0.7130,  val_accuracy:0.4630,  val_binary_crossentropy:0.7012,  val_loss:0.7012,  
....................................................................................................
Epoch: 100, accuracy:0.6295,  binary_crossentropy:0.6092,  loss:0.6092,  val_accuracy:0.6120,  val_binary_crossentropy:0.6145,  val_loss:0.6145,  
....................................................................................................
Epoch: 200, accuracy:0.6575,  binary_crossentropy:0.5879,  loss:0.5879,  val_accuracy:0.6520,  val_binary_crossentropy:0.5976,  val_loss:0.5976,  
....................................................................................................
Epoch: 300, accuracy:0.6758,  binary_crossentropy:0.5774,  loss:0.5774,  val_accuracy:0.6610,  val_binary_crossentropy:0.5958,  val_loss:0.5958,  
....................................................................................................
Epoch: 400, accuracy:0.6830,  binary_crossentropy:0.5698,  loss:0.5698,  val_accuracy:0.6690,  val_binary_crossentropy:0.5949,  val_loss:0.5949,  
....................................................................................................
Epoch: 500, accuracy:0.6873,  binary_crossentropy:0.5650,  loss:0.5650,  val_accuracy:0.6720,  val_binary_crossentropy:0.5930,  val_loss:0.5930,  
....................................................................................................
Epoch: 600, accuracy:0.6923,  binary_crossentropy:0.5600,  loss:0.5600,  val_accuracy:0.6570,  val_binary_crossentropy:0.5946,  val_loss:0.5946,  
......................................................

Modelo mediano

Ahora prueba 3 capas ocultas con 64 unidades cada una:

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

Y entrena el modelo usando los mismos datos:

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
_________________________________________________________________
WARNING:tensorflow:Callback method `on_train_batch_begin` is slow compared to the batch time (batch time: 0.0033s vs `on_train_batch_begin` time: 0.0251s). Check your callbacks.
WARNING:tensorflow:Callback method `on_train_batch_end` is slow compared to the batch time (batch time: 0.0033s vs `on_train_batch_end` time: 0.0189s). Check your callbacks.

Epoch: 0, accuracy:0.5026,  binary_crossentropy:0.6944,  loss:0.6944,  val_accuracy:0.4740,  val_binary_crossentropy:0.6830,  val_loss:0.6830,  
....................................................................................................
Epoch: 100, accuracy:0.7164,  binary_crossentropy:0.5242,  loss:0.5242,  val_accuracy:0.6490,  val_binary_crossentropy:0.6316,  val_loss:0.6316,  
....................................................................................................
Epoch: 200, accuracy:0.7919,  binary_crossentropy:0.4224,  loss:0.4224,  val_accuracy:0.6480,  val_binary_crossentropy:0.7022,  val_loss:0.7022,  
.......................................

Modelo grande

Como ejercicio, puede crear un modelo aún más grande y ver qué tan rápido comienza a sobreajustarse. A continuación, agreguemos a este punto de referencia una red que tiene mucha más capacidad, mucho más de lo que el problema justificaría:

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

Y, nuevamente, entrene el modelo usando los mismos datos:

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
_________________________________________________________________
WARNING:tensorflow:Callback method `on_train_batch_begin` is slow compared to the batch time (batch time: 0.0033s vs `on_train_batch_begin` time: 0.0237s). Check your callbacks.
WARNING:tensorflow:Callback method `on_train_batch_end` is slow compared to the batch time (batch time: 0.0033s vs `on_train_batch_end` time: 0.0182s). Check your callbacks.

Epoch: 0, accuracy:0.5116,  binary_crossentropy:0.7680,  loss:0.7680,  val_accuracy:0.5440,  val_binary_crossentropy:0.6753,  val_loss:0.6753,  
....................................................................................................
Epoch: 100, accuracy:1.0000,  binary_crossentropy:0.0021,  loss:0.0021,  val_accuracy:0.6610,  val_binary_crossentropy:1.8058,  val_loss:1.8058,  
....................................................................................................
Epoch: 200, accuracy:1.0000,  binary_crossentropy:0.0001,  loss:0.0001,  val_accuracy:0.6500,  val_binary_crossentropy:2.4712,  val_loss:2.4712,  
.........................

Trazar las pérdidas de formación y validación

Las líneas continuas muestran la pérdida de entrenamiento y las líneas discontinuas muestran la pérdida de validación (recuerde: una pérdida de validación menor indica un modelo mejor).

Si bien la construcción de un modelo más grande le da más poder, si este poder no está limitado de alguna manera, puede sobreajustarse fácilmente al conjunto de entrenamiento.

En este ejemplo, por lo general, sólo el "Tiny" modelo logra evitar el sobreajuste en conjunto, y cada uno de los modelos más grandes overfit los datos más rápidamente. Esto se vuelve tan severa para el "large" modelo que tiene que cambiar la trama a una escala logarítmica para ver realmente lo que está pasando.

Esto es evidente si traza y compara las métricas de validación con las métricas de entrenamiento.

  • Es normal que haya una pequeña diferencia.
  • Si ambas métricas se mueven en la misma dirección, todo está bien.
  • Si la métrica de validación comienza a estancarse mientras la métrica de entrenamiento continúa mejorando, probablemente esté cerca de sobreajustar.
  • Si la métrica de validación va en la dirección incorrecta, el modelo está claramente sobreajustado.
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

Ver en TensorBoard

Todos estos modelos escribieron registros de TensorBoard durante el entrenamiento.

Abra un visor de TensorBoard integrado dentro de una computadora portátil:


# Load the TensorBoard notebook extension
%load_ext tensorboard

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

Puede ver los resultados de una ejecución anterior de este bloc de notas en TensorBoard.dev .

TensorBoard.dev es una experiencia administrada para alojar, rastrear y compartir experimentos de AA con todos.

También se incluye en un <iframe> para mayor comodidad:

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

Si desea compartir TensorBoard resultados que se pueden cargar los registros a TensorBoard.dev copiando el siguiente en una celda de código.

tensorboard dev upload --logdir  {logdir}/sizes

Estrategias para prevenir el sobreajuste

Antes de entrar en el contenido de esta sección copiar los registros de entrenamiento de la "Tiny" modelo anterior, para su uso como una base para la comparación.

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

Agregar regularización de peso

Es posible que esté familiarizado con el principio Razor de Occam: dadas dos explicaciones para algo, la explicación más probable es la "más simple", la que hace la menor cantidad de suposiciones. Esto también se aplica a los modelos aprendidos por las redes neuronales: dados algunos datos de entrenamiento y una arquitectura de red, existen múltiples conjuntos de valores de ponderación (múltiples modelos) que podrían explicar los datos, y los modelos más simples tienen menos probabilidades de sobreajustarse que los complejos.

Un "modelo simple" en este contexto es un modelo donde la distribución de los valores de los parámetros tiene menos entropía (o un modelo con menos parámetros en total, como vimos en la sección anterior). Por lo tanto, una forma común de mitigar el sobreajuste es imponer restricciones a la complejidad de una red obligando a que sus ponderaciones solo tomen valores pequeños, lo que hace que la distribución de los valores de ponderación sea más "regular". Esto se llama "regularización de peso" y se realiza agregando a la función de pérdida de la red un costo asociado con tener grandes pesos. Este costo viene en dos sabores:

  • Regularización L1 , donde el coste añadido es proporcional al valor absoluto de los coeficientes de pesos (es decir, a lo que se llama la "norma L1" de los pesos).

  • Regularización L2 , donde el coste añadido es proporcional al cuadrado del valor de los coeficientes de pesos (es decir, a lo que se llama el cuadrado "norma L2" de los pesos). La regularización de L2 también se denomina decadencia de peso en el contexto de las redes neuronales. No permita que el nombre diferente lo confunda: la disminución del peso es matemáticamente exactamente igual que la regularización L2.

La regularización L1 empuja los pesos hacia exactamente cero, lo que fomenta un modelo disperso. La regularización de L2 penalizará los parámetros de ponderaciones sin hacerlos escasos, ya que la penalización llega a cero para las ponderaciones pequeñas, una de las razones por las que L2 es más común.

En tf.keras , regularización peso se añade al pasar instancias peso regularizador a capas como argumentos de palabra clave. Agreguemos ahora la regularización de peso 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
_________________________________________________________________
WARNING:tensorflow:Callback method `on_train_batch_begin` is slow compared to the batch time (batch time: 0.0038s vs `on_train_batch_begin` time: 0.0242s). Check your callbacks.
WARNING:tensorflow:Callback method `on_train_batch_end` is slow compared to the batch time (batch time: 0.0038s vs `on_train_batch_end` time: 0.0199s). Check your callbacks.

Epoch: 0, accuracy:0.5059,  binary_crossentropy:0.7720,  loss:2.2831,  val_accuracy:0.4620,  val_binary_crossentropy:0.7035,  val_loss:2.1321,  
....................................................................................................
Epoch: 100, accuracy:0.6490,  binary_crossentropy:0.5996,  loss:0.6228,  val_accuracy:0.6270,  val_binary_crossentropy:0.5898,  val_loss:0.6131,  
....................................................................................................
Epoch: 200, accuracy:0.6737,  binary_crossentropy:0.5826,  loss:0.6061,  val_accuracy:0.6680,  val_binary_crossentropy:0.5857,  val_loss:0.6096,  
....................................................................................................
Epoch: 300, accuracy:0.6842,  binary_crossentropy:0.5748,  loss:0.5993,  val_accuracy:0.6840,  val_binary_crossentropy:0.5754,  val_loss:0.5998,  
....................................................................................................
Epoch: 400, accuracy:0.6934,  binary_crossentropy:0.5620,  loss:0.5862,  val_accuracy:0.6690,  val_binary_crossentropy:0.5825,  val_loss:0.6066,  
.....................................................................................

l2(0.001) significa que cada coeficiente en la matriz de peso de la capa añadirá 0.001 * weight_coefficient_value**2 a la pérdida total de la red.

Es por eso que estamos monitoreando la binary_crossentropy directamente. Porque no tiene este componente de regularización mezclado.

Por lo tanto, ese mismo "Large" modelo con una L2 lleva a cabo la regularización de penalización mucho mejor:

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

png

Como se puede ver, el "L2" modelo regularizado es ahora mucho más competitivo con el de la "Tiny" modelo. Este "L2" modelo también es mucho más resistente a overfitting que la "Large" modelo se basa en a pesar de tener el mismo número de parámetros.

Más información

Hay dos cosas importantes a tener en cuenta sobre este tipo de regularización.

En primer lugar: si está escribiendo su propio circuito de entrenamiento, entonces usted necesita estar seguro de pedir el modelo de sus pérdidas de regularización.

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

Segundo: Esta aplicación funciona mediante la adición de las sanciones a la pérdida de peso del modelo, y luego aplicar un procedimiento de optimización estándar después de eso.

Hay un segundo enfoque que, en cambio, solo ejecuta el optimizador en la pérdida bruta, y luego, mientras aplica el paso calculado, el optimizador también aplica cierta disminución de peso. Este "desconectados Peso Decay" se ve en los optimizadores como optimizers.FTRL y optimizers.AdamW .

Agregar abandono

La deserción es una de las técnicas de regularización más efectivas y más utilizadas para las redes neuronales, desarrollada por Hinton y sus estudiantes en la Universidad de Toronto.

La explicación intuitiva de la deserción es que debido a que los nodos individuales de la red no pueden depender de la salida de los demás, cada nodo debe generar características que sean útiles por sí mismas.

La eliminación, aplicada a una capa, consiste en "eliminar" aleatoriamente (es decir, establecer en cero) una serie de características de salida de la capa durante el entrenamiento. Digamos que una capa dada normalmente habría devuelto un vector [0.2, 0.5, 1.3, 0.8, 1.1] para una muestra de entrada dada durante el entrenamiento; después de aplicar la deserción, este vector tendrá algunas entradas cero distribuidas al azar, por ejemplo, [0, 0.5, 1.3, 0, 1.1].

La "tasa de abandono" es la fracción de las funciones que se están reduciendo a cero; normalmente se establece entre 0,2 y 0,5. En el momento de la prueba, no se descarta ninguna unidad y, en cambio, los valores de salida de la capa se reducen en un factor igual a la tasa de abandono, para equilibrar el hecho de que hay más unidades activas que en el momento del entrenamiento.

En tf.keras se puede introducir de abandono en una red a través de la capa de deserción, que consigue alcanzar la salida de la capa justo antes.

Agreguemos dos capas de deserción en nuestra red para ver qué tan bien lo hacen para reducir el sobreajuste:

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
_________________________________________________________________
WARNING:tensorflow:Callback method `on_train_batch_begin` is slow compared to the batch time (batch time: 0.0040s vs `on_train_batch_begin` time: 0.0241s). Check your callbacks.
WARNING:tensorflow:Callback method `on_train_batch_end` is slow compared to the batch time (batch time: 0.0040s vs `on_train_batch_end` time: 0.0208s). Check your callbacks.

Epoch: 0, accuracy:0.5060,  binary_crossentropy:0.7949,  loss:0.7949,  val_accuracy:0.5140,  val_binary_crossentropy:0.6710,  val_loss:0.6710,  
....................................................................................................
Epoch: 100, accuracy:0.6623,  binary_crossentropy:0.5950,  loss:0.5950,  val_accuracy:0.6840,  val_binary_crossentropy:0.5723,  val_loss:0.5723,  
....................................................................................................
Epoch: 200, accuracy:0.6897,  binary_crossentropy:0.5559,  loss:0.5559,  val_accuracy:0.6800,  val_binary_crossentropy:0.5971,  val_loss:0.5971,  
....................................................................................................
Epoch: 300, accuracy:0.7202,  binary_crossentropy:0.5114,  loss:0.5114,  val_accuracy:0.6800,  val_binary_crossentropy:0.5984,  val_loss:0.5984,  
...............................................
plotter.plot(regularizer_histories)
plt.ylim([0.5, 0.7])
(0.5, 0.7)

png

Es claro de esta trama que ambos regularización se aproxima a mejorar el comportamiento de la "Large" del modelo. Pero esto todavía no se anda incluso el "Tiny" línea de base.

A continuación, pruébelos juntos y vea si funciona mejor.

Abandono combinado 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
_________________________________________________________________
WARNING:tensorflow:Callback method `on_train_batch_begin` is slow compared to the batch time (batch time: 0.0040s vs `on_train_batch_begin` time: 0.0245s). Check your callbacks.
WARNING:tensorflow:Callback method `on_train_batch_end` is slow compared to the batch time (batch time: 0.0040s vs `on_train_batch_end` time: 0.0214s). Check your callbacks.

Epoch: 0, accuracy:0.5056,  binary_crossentropy:0.8201,  loss:0.9784,  val_accuracy:0.5130,  val_binary_crossentropy:0.6691,  val_loss:0.8269,  
....................................................................................................
Epoch: 100, accuracy:0.6409,  binary_crossentropy:0.6052,  loss:0.6362,  val_accuracy:0.6670,  val_binary_crossentropy:0.5831,  val_loss:0.6139,  
....................................................................................................
Epoch: 200, accuracy:0.6673,  binary_crossentropy:0.5893,  loss:0.6147,  val_accuracy:0.6880,  val_binary_crossentropy:0.5666,  val_loss:0.5920,  
....................................................................................................
Epoch: 300, accuracy:0.6724,  binary_crossentropy:0.5814,  loss:0.6092,  val_accuracy:0.6850,  val_binary_crossentropy:0.5638,  val_loss:0.5916,  
....................................................................................................
Epoch: 400, accuracy:0.6791,  binary_crossentropy:0.5764,  loss:0.6061,  val_accuracy:0.6960,  val_binary_crossentropy:0.5536,  val_loss:0.5832,  
....................................................................................................
Epoch: 500, accuracy:0.6750,  binary_crossentropy:0.5722,  loss:0.6037,  val_accuracy:0.6760,  val_binary_crossentropy:0.5583,  val_loss:0.5899,  
....................................................................................................
Epoch: 600, accuracy:0.6818,  binary_crossentropy:0.5651,  loss:0.5989,  val_accuracy:0.6940,  val_binary_crossentropy:0.5422,  val_loss:0.5761,  
....................................................................................................
Epoch: 700, accuracy:0.6882,  binary_crossentropy:0.5594,  loss:0.5943,  val_accuracy:0.6880,  val_binary_crossentropy:0.5436,  val_loss:0.5786,  
....................................................................................................
Epoch: 800, accuracy:0.6886,  binary_crossentropy:0.5567,  loss:0.5927,  val_accuracy:0.6960,  val_binary_crossentropy:0.5446,  val_loss:0.5807,  
....................................................................................................
Epoch: 900, accuracy:0.6994,  binary_crossentropy:0.5535,  loss:0.5907,  val_accuracy:0.6900,  val_binary_crossentropy:0.5463,  val_loss:0.5835,  
................................................
plotter.plot(regularizer_histories)
plt.ylim([0.5, 0.7])
(0.5, 0.7)

png

Este modelo con el "Combined" regularización es, obviamente, la mejor hasta ahora.

Ver en TensorBoard

Estos modelos también registraron registros de TensorBoard.

Para abrir un visor de tensorboard incrustado dentro de un cuaderno, copie lo siguiente en una celda de código:

%tensorboard --logdir {logdir}/regularizers

Puede ver los resultados de una ejecución anterior de este bloc de notas en TensorDoard.dev .

También se incluye en un <iframe> para mayor comodidad:

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

Esto fue subido con:

tensorboard dev upload --logdir  {logdir}/regularizers

Conclusiones

En resumen: estas son las formas más comunes de prevenir el sobreajuste en las redes neuronales:

  • Obtén más datos de entrenamiento.
  • Reducir la capacidad de la red.
  • Agrega regularización de peso.
  • Agregar deserción.

Dos enfoques importantes que no se tratan en esta guía son:

  • aumento de datos
  • normalización de lotes

Recuerde que cada método puede ayudar por sí solo, pero a menudo combinarlos puede ser aún más efectivo.

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