Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
tensorflow
GitHub Repository: tensorflow/docs-l10n
Path: blob/master/site/es-419/tutorials/keras/overfit_and_underfit.ipynb
25118 views
Kernel: Python 3
#@title Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License.
#@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.

Sobreajuste y subajuste

Como de costumbre, el código de este ejemplo usará la API tf.keras, de la que puede obtener más información en la guía Keras de TensorFlow.

En ambos ejemplos anteriores, clasificación de texto y predicción de la eficiencia del combustible, la precisión de los modelos en los datos de validación alcanzaba su pico tras entrenar durante un determinado número de épocas y luego
se estancaba o comenzaba a disminuir.

En otras palabras, el modelo tendía a sobreajustarse a los datos de entrenamiento. Es importante aprender a lidiar con el sobreajuste. Si bien a menudo es posible alcanzar altos niveles de precisión en el conjunto de entrenamiento, lo que realmente querrá es desarrollar modelos que generalicen bien en un conjunto de prueba (o datos que no hayan visto previamente).

Lo contrario al sobreajuste es el subajuste. El subajuste se presenta cuando todavía se puede mejorar el rendimiento de los datos de entrenamiento. Son varios los motivos por los que puede suceder esto: si el modelo no es lo suficientemente potente, si está excesivamente regularizado o si sencillamente no se ha entrenado lo suficiente. Esto significa que la red no ha aprendido los patrones relevantes de los datos de entrenamiento.

No obstante, si entrena durante demasiado tiempo, el modelo comenzará a sobreajustarse y aprenderá patrones de los datos de entrenamiento que no podrán generalizarse a los datos de prueba. Es necesario encontrar el equilibrio. Comprender cómo entrenar durante un número adecuado de épocas, como veremos a continuación, es una habilidad muy útil.

Para evitar el sobreajuste, la mejor solución es usar datos de entrenamiento más completos. Los conjuntos de datos deberían abarcar el rango completo de entradas que se espera que maneje el modelo. Los datos adicionales solo serán útiles si cubren casos nuevos o interesantes.

Un modelo entrenado con datos más completos naturalmente generalizará mejor. Cuando esto ya no sea posible, la siguiente mejor solución será utilizar técnicas como la regularización. Estas técnicas limitan la cantidad y el tipo de información que el modelo puede almacenar. Si una red puede memorizar solo un pequeño número de patrones, el proceso de optimización la obligará a centrarse en los patrones más destacados, que tienen más posibilidades de generalizar bien.

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

Preparar

Antes de comenzar, importe los paquetes necesarios:

import tensorflow as tf from tensorflow.keras import layers from tensorflow.keras import regularizers print(tf.__version__)
!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 Higgs

El objetivo de este tutorial no es el de hacer física de partículas, así que no se detenga en los detalles del conjunto de datos. Contiene 11 000 000 ejemplos, cada uno con 28 características 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')
FEATURES = 28

La clase tf.data.experimental.CsvDataset se puede usar para leer registros csv directamente desde un archivo gzip sin necesidad de implementar el paso intermedio de descompresión.

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.

Entonces, en vez de volver a empaquetar individualmente cada fila, cree un nuevo tf.data.Dataset que tome lotes de 10 000 ejemplos, aplique la función pack_row a cada lote y luego divida la copia de seguridad de los lotes en registros individuales:

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

Inspeccione algunos de los archivos de este nuevo packed_ds.

Las características no están perfectamente normalizadas, pero 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)

Para que este tutorial sea relativamente breve, use solo las primeras 1000 muestras para validación y las siguientes 10 000 para entrenamiento:

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

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

Al mismo tiempo, use el método Dataset.cache para asegurarse de que el cargador no necesite 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

Estos conjuntos de datos devuelven ejemplos individuales. Use el método Dataset.batch para crear lotes del tamaño adecuado para entrenamiento. Antes de separar en lotes, recuerde también usar Dataset.shuffle y Dataset.repeat en 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 un pequeño número de parámetros que se pueden aprender (que está determinado por la cantidad de capas y la cantidad de unidades por capa). En el aprendizaje profundo, el número de parámetros que se pueden aprender en un modelo suele denominarse "capacidad" del modelo.

Se puede deducir que un modelo con más parámetros tendrá más "capacidad de memorización" y, por lo tanto, será capaz de aprender fácilmente una asignación perfecta similar a un diccionario entre las muestras de entrenamiento y sus objetivos, una asignación sin ningún poder de generalización, pero esto sería inútil a la hora de hacer predicciones sobre datos que no se hayan visto previamente.

No olvide esto: los modelos de aprendizaje profundo tienden a ajustarse bien a los datos de entrenamiento, pero el verdadero reto no consiste en realizar el ajuste, sino en lograr la generalización.

Por otro lado, si la red tiene recursos de memorización limitados, no será capaz de aprender la asignación tan fácilmente. Para minimizar su pérdida, deberá aprender representaciones comprimidas que tengan mayor poder predictivo. Al mismo tiempo, si crea un modelo demasiado pequeño, tendrá dificultad para ajustarse a los datos de entrenamiento. Existe un equilibrio entre "demasiada capacidad" y "sin suficiente capacidad".

Desafortunadamente, no existe una fórmula mágica para determinar el tamaño o la arquitectura adecuados para un modelo (en lo que respecta al número de capas o al tamaño adecuado de cada capa). Deberá experimentar con una serie de arquitecturas diferentes.

Para encontrar un tamaño de modelo adecuado, lo mejor es empezar con una cantidad relativamente pequeña de capas y parámetros, y luego ir aumentando el tamaño de las capas o añadiendo nuevas capas hasta que vea que disminuye la pérdida de validación.

Comience con un modelo simple que use solo capas densamente conectadas (tf.keras.layers.Dense) como línea de base, luego, cree modelos más grandes y compárelos.

Procedimiento de entrenamiento

Muchos modelos se entrenan mejor si reduce gradualmente la tasa de aprendizaje durante el entrenamiento. Use tf.keras.optimizers.schedules para reducir la tasa de aprendizaje a lo largo del 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 configura tf.keras.optimizers.schedules.InverseTimeDecay para que disminuya hiperbólicamente la tasa de aprendizaje a la mitad de la tasa inicial en 1000 épocas, a 1/3 en 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')

Cada modelo de este tutorial usará la misma configuración de entrenamiento. Por lo que es recomendable que se configuren de forma reutilizable, empezando por la lista de retrollamadas.

El entrenamiento para este tutorial se ejecuta durante varias épocas cortas. Para reducir el ruido de registro, use tfdocs.EpochDots que simplemente imprime un . para cada época, y un conjunto completo de métricas cada 100 épocas.

Luego, incluya tf.keras.callbacks.EarlyStopping para evitar tiempos de entrenamiento largos e innecesarios. Tenga en cuenta que esta retrollamada se configura para monitorear val_binary_crossentropy, no val_loss. Esta diferencia resultará importante más adelante.

Use callbacks.TensorBoard para generar registros de TensorBoard para el entrenamiento.

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

De forma similar, cada modelo usará las mismas configuraciones Model.compile y Model.fit:

def compile_and_fit(model, name, optimizer=None, max_epochs=10000): if optimizer is None: optimizer = get_optimizer() model.compile(optimizer=optimizer, loss=tf.keras.losses.BinaryCrossentropy(from_logits=True), metrics=[ tf.keras.metrics.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

Modelo Tiny

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

Ahora, compruebe el desempeño del modelo:

plotter = tfdocs.plots.HistoryPlotter(metric = 'binary_crossentropy', smoothing_std=10) plotter.plot(size_histories) plt.ylim([0.5, 0.7])

Modelo Small

Para comprobar si puede superar el rendimiento del modelo pequeño, entrene modelos más grandes de forma progresiva.

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

Modelo Medium

Ahora, pruebe tres 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 entrene el modelo con los mismos datos:

size_histories['Medium'] = compile_and_fit(medium_model, "sizes/Medium")

Modelo Large

A modo de ejercicio práctico, puede crear un modelo aún más grande y comprobar con qué rapidez empieza a sobreajustarse. A continuación, añada a este modelo de referencia una red que tenga mucha más capacidad, mucho más de la 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 con los mismos datos:

size_histories['large'] = compile_and_fit(large_model, "sizes/large")

Trazar las pérdidas de entrenamiento y de 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 que una pérdida de validación menor corresponde a un mejor modelo).

Si bien generar un modelo más grande le da más potencia, si no se limita de algún modo esta potencia, puede sobreajustarse fácilmente al conjunto de entrenamiento.

En este ejemplo, generalmente, solo el modelo "Tiny" logra evitar por completo el sobreajuste, y cada uno de los modelos más grandes se sobreajusta a los datos con mayor rapidez. Esto afecta tanto al modelo "large" que, para saber realmente qué está sucediendo, deberá cambiar el trazado a una escala logarítmica.

Esto resulta 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 que la métrica de entrenamiento continúa mejorando, probablemente esté a punto de sobreajustarse.

  • Si la métrica de validación se mueve en la dirección equivocada, es claro que el modelo se está sobreajustando.

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

Nota: En todas las ejecuciones de entrenamiento anteriores se usó callbacks.EarlyStopping para finalizar el entrenamiento una vez que sea claro que el modelo ya no está haciendo avances.

Ver en TensorBoard

Todos estos modelos escribieron registros de TensorBoard durante el entrenamiento.

Abra un visor de TensorBoard incorporado dentro de un bloc de notas (Lo sentimos, esto no se puede ver en tensorflow.org):

# 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 previa de este bloc de notas en TensorBoard.dev.

Estrategias para evitar el sobreajuste

Antes de comenzar a ver el contenido de esta sección, copie los registros de entrenamiento del modelo "Tiny" que vimos más arriba para que los pueda usar como línea de base para una comparación.

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

Añadir regularización de pesos

Probablemente conozca el principio de la navaja de Ockham: frente a dos explicaciones de algo, lo más probable es que la explicación correcta sea la "más simple", la que genera menor cantidad de hipótesis. Esto también se puede aplicar a los modelos que aprenden las redes neuronales: ante una serie de datos de entrenamiento y una arquitectura de red, existen múltiples conjuntos de valores de pesos (múltiples modelos) que podrían explicar los datos, y lo más probable es que los modelos más complejos tengan mayor tendencia a sobreajustarse que los modelos más simples.

En este contexto, un "modelo simple" es un modelo donde la distribución de valores de parámetros tenga menor entropía (o un modelo con menor cantidad de parámetros en total, como se demostró anteriormente). Por lo tanto, una forma común de reducir el sobreajuste es establecer límites a la complejidad de una red al forzar sus pesos para que solo tengan en cuenta los valores más bajos, lo que a su vez hace que la distribución de los valores de pesos sea más "regular". A este proceso se lo denomina "regularización de pesos", y se lleva a cabo mediante la incorporación de un costo asociado con mayores pesos a la función de pérdida de la red. Existen dos tipos de costos:

  • Regularización L1, donde el costo añadido es proporcional al valor absoluto de los coeficientes de los pesos (es decir, lo que se conoce como "Norma L1" de los pesos).

  • Regularización L2, donde el costo añadido es proporcional al cuadrado del valor de los coeficientes de los pesos (es decir, lo que se conoce como la "Norma L2" al cuadrado de los pesos). La regularización L2 también se conoce como caída del peso en el contexto de las redes neuronales. No deje que los nombres lo confundan: en términos matemáticos, la caída del peso es exactamente lo mismo que la regularización L2.

La regularización L1 lleva los pesos a exactamente cero, lo que fomenta un modelo disperso. La regularización L2 penalizará los parámetros de pesos sin volverlos dispersos, ya que la penalización va a cero para los pesos más bajos; este es uno de los motivos por los que L2 es más común.

En tf.keras, la regularización de pesos se agrega pasando instancias regularizadoras de pesos en las capas a modo de argumentos de palabra clave. Agregue regularización de pesos 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")

l2(0.001) significa que cada coeficiente de matriz de pesos de la capa agregará 0.001 * weight_coefficient_value**2 a la pérdida total de la red.

Es por eso que monitoreamos binary_crossentropy de forma directa. Porque no incluye ese componente de regularización.

Entonces, ese mismo modelo "Large" con una penalización de regularización L2 tiene un mejor rendimiento:

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

Como se demuestra en el diagrama de arriba, el modelo de regularización "L2" ahora es mucho más competitivo con el modelo "Tiny". Este modelo "L2" también es mucho más resistente al sobreajuste que el modelo "Large" en el que se basó, a pesar de tener la misma cantidad de parámetros.

Más información

Hay dos cosas importantes que se deben tener en cuenta sobre este tipo de regularización:

  1. Si está escribiendo su propio bucle de entrenamiento, debe asegurarse de pedirle al modelo que indique sus pérdidas de regularización.

result = l2_model(features) regularization_loss=tf.add_n(l2_model.losses)
  1. Esta implementación funciona mediante la incorporación de penalizaciones de peso a la pérdida del modelo, para luego aplicar un procedimiento de optimización estándar.

Hay un segundo enfoque que directamente ejecuta el optimizador sobre la perdida bruta y, luego, mientras aplica el paso calculado, el optimizador también aplica cierto grado de caída de pesos. Esta "caída de pesos desacoplada" se usa en optimizadores como tf.keras.optimizers.Ftrl y tfa.optimizers.AdamW.

Añadir abandono

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

La explicación intuitiva del abandono es que, dado que los nodos individuales en la red no pueden depender de los resultados de los demás, cada nodo debe producir características útiles por sí mismo.

El abandono, aplicado a una capa, consiste en "descartar" aleatoriamente (es decir, establecer en cero) una cantidad determinada de características de la capa durante el entrenamiento. Por ejemplo, una capa dada normalmente tendría como resultado un vector [0.2, 0.5, 1.3, 0.8, 1.1] para una muestra de entrada dada durante el entrenamiento; luego de aplicar el abandono, este vector tendrá algunas entradas cero distribuidas aleatoriamente, por ejemplo, [0, 0.5, 1.3, 0, 1.1].

La "tasa de abandono" es la fracción de las características que se llevan a cero; generalmente se establece entre 0.2 y 0.5. Al momento de la prueba, no se descarta ninguna unidad, sino que los valores de salida de la capa se reducen por un factor equivalente a la tasa de abandono, para compensar el hecho de que durante el entrenamiento hay más unidades activas.

En Keras, puede introducir abandono en una red a través de la capa tf.keras.layers.Dropout, que se aplica a la salida de la capa anterior.

Agregue dos capas de abandono a su red para comprobar qué tan eficientes son a la hora de 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")
plotter.plot(regularizer_histories) plt.ylim([0.5, 0.7])

En este trazado podemos ver que ambos enfoques de regularización mejoran el comportamiento del modelo "Large". Pero, aun así, no supera la línea de base del modelo "Tiny".

A continuación, pruébelos juntos para ver si eso mejora los resultados.

Combinación de L2 y abandono

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")
plotter.plot(regularizer_histories) plt.ylim([0.5, 0.7])

Resulta evidente que este modelo con regularización "Combined" es el mejor hasta el momento.

Ver en TensorBoard

Estos modelos también se registran en registros de TensorBoard.

Para abrir un visor incorporado, ejecute lo siguiente dentro de una celda de código (Lo sentimos, esto no se puede ver en tensorflow.org):

%tensorboard --logdir {logdir}/regularizers

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

Conclusiones

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

  • Conseguir más datos de entrenamiento

  • Reducir la capacidad de la red

  • Añadir regularización de pesos

  • Añadir abandono

Hay dos enfoques importantes que no se incluyen en esta guía:

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