Ajuda a proteger a Grande Barreira de Corais com TensorFlow em Kaggle Junte Desafio

Overfit e underfit

Ver no TensorFlow.org Executar no Google Colab Ver fonte no GitHub Baixar caderno

Como sempre, o código neste exemplo usará o tf.keras API, que você pode aprender mais sobre no TensorFlow guia Keras .

Em ambos os exemplos- anterior texto de classificação e previsão de eficiência de combustível -, vimos que a precisão do nosso modelo nos dados de validação atingiria o pico após o treinamento para um número de épocas, e, então, estagnar ou começar a diminuir.

Em outras palavras, o nosso modelo seria overfit aos dados de treinamento. Aprender como lidar com o sobreajuste é importante. Embora muitas vezes é possível alcançar alta precisão sobre o conjunto de treinamento, o que realmente queremos é desenvolver modelos que generalizam bem a um conjunto de testes (ou dados que não tenha visto antes).

O oposto de overfitting é underfitting. O subajuste ocorre quando ainda há espaço para melhorias nos dados do trem. Isso pode acontecer por vários motivos: Se o modelo não for poderoso o suficiente, estiver super regularizado ou simplesmente não tiver sido treinado por tempo suficiente. Isso significa que a rede não aprendeu os padrões relevantes nos dados de treinamento.

No entanto, se você treinar por muito tempo, o modelo começará a se ajustar demais e aprenderá os padrões dos dados de treinamento que não se generalizam para os dados de teste. Precisamos encontrar um equilíbrio. Compreender como treinar por um número apropriado de épocas, conforme exploraremos a seguir, é uma habilidade útil.

Para evitar overfitting, a melhor solução é usar dados de treinamento mais completos. O conjunto de dados deve abranger toda a gama de entradas que o modelo deve manipular. Dados adicionais só podem ser úteis se cobrirem casos novos e interessantes.

Um modelo treinado em dados mais completos irá naturalmente generalizar melhor. Quando isso não for mais possível, a próxima melhor solução é usar técnicas como a regularização. Isso impõe restrições à quantidade e ao tipo de informação que seu modelo pode armazenar. Se uma rede puder memorizar apenas um pequeno número de padrões, o processo de otimização a forçará a se concentrar nos padrões mais proeminentes, que têm uma chance melhor de generalizar bem.

Neste caderno, exploraremos várias técnicas de regularização comuns e as usaremos para aprimorar um modelo de classificação.

Configurar

Antes de começar, importe os pacotes necessários:

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)

O conjunto de dados Higgs

O objetivo deste tutorial não é fazer física de partículas, portanto, não se detenha nos detalhes do conjunto de dados. Ele contém 11 000 000 exemplos, cada um com 28 recursos e um rótulo de classe binária.

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

O tf.data.experimental.CsvDataset classe pode ser usado para ler registros CSV diretamente de um arquivo gzip sem etapa de descompressão intermediária.

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

Essa classe de leitor csv retorna uma lista de escalares para cada registro. A função a seguir reempilha essa lista de escalares em um par (feature_vector, label).

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

O TensorFlow é mais eficiente ao operar em grandes lotes de dados.

Então, ao invés de reembalar cada linha individualmente fazer um novo Dataset que leva lotes de 10000-exemplos, aplica-se o pack_row função para cada lote, e depois divide os lotes backup em registros individuais:

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

Dê uma olhada em alguns dos registros a partir deste novo packed_ds .

Os recursos não estão perfeitamente normalizados, mas isso é 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 manter este tutorial relativamente curto, use apenas as primeiras 1.000 amostras para validação e as próximas 10.000 para treinamento:

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

Os Dataset.skip e Dataset.take métodos fazer isso fácil.

Ao mesmo tempo, use o Dataset.cache método para garantir que o carregador não precisa re-ler os dados do arquivo em 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)>

Esses conjuntos de dados retornam exemplos individuais. Use o .batch método para criar lotes de um tamanho adequado para o treinamento. Antes de lotes também lembre-se de .shuffle e .repeat o conjunto de treinamento.

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

Demonstrar overfitting

A maneira mais simples de evitar overfitting é começar com um modelo pequeno: Um modelo com um pequeno número de parâmetros aprendíveis (que é determinado pelo número de camadas e o número de unidades por camada). No aprendizado profundo, o número de parâmetros aprendíveis em um modelo costuma ser chamado de "capacidade" do modelo.

Intuitivamente, um modelo com mais parâmetros terá mais "capacidade de memorização" e, portanto, será capaz de aprender facilmente um mapeamento perfeito tipo dicionário entre as amostras de treinamento e seus alvos, um mapeamento sem nenhum poder de generalização, mas isso seria inútil para fazer previsões em dados nunca vistos anteriormente.

Sempre tenha isso em mente: modelos de aprendizado profundo tendem a ser bons em se ajustar aos dados de treinamento, mas o verdadeiro desafio é a generalização, não o ajuste.

Por outro lado, se a rede tiver recursos de memorização limitados, não será capaz de aprender o mapeamento tão facilmente. Para minimizar sua perda, ele terá que aprender representações compactadas que têm mais poder preditivo. Ao mesmo tempo, se você tornar seu modelo muito pequeno, ele terá dificuldade de se ajustar aos dados de treinamento. Há um equilíbrio entre "capacidade demais" e "capacidade insuficiente".

Infelizmente, não existe uma fórmula mágica para determinar o tamanho ou a arquitetura certa do seu modelo (em termos de número de camadas ou o tamanho certo para cada camada). Você terá que experimentar usando uma série de arquiteturas diferentes.

Para encontrar um tamanho de modelo apropriado, é melhor começar com relativamente poucas camadas e parâmetros e, em seguida, começar a aumentar o tamanho das camadas ou adicionar novas camadas até ver os retornos decrescentes na perda de validação.

Comece com um modelo simples usando apenas layers.Dense como uma linha de base, em seguida, criar versões maiores, e compará-los.

Procedimento de treinamento

Muitos modelos treinam melhor se você reduzir gradualmente a taxa de aprendizado durante o treinamento. Use optimizers.schedules para reduzir a taxa de aprendizagem ao longo do tempo:

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)

O código acima define um schedules.InverseTimeDecay para diminuir a taxa de aprendizagem hiperbolicamente a 1/2 da taxa de base a 1000 épocas, 1/3 em 2000 épocas e assim por diante.

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 neste tutorial usará a mesma configuração de treinamento. Portanto, configure-os de forma reutilizável, começando com a lista de retornos de chamada.

O treinamento para este tutorial é executado por muitos períodos curtos. Para reduzir o registro de ruído usar os tfdocs.EpochDots que simplesmente imprime uma . para cada época e um conjunto completo de métricas a cada 100 épocas.

Próxima incluem callbacks.EarlyStopping para evitar longas e desnecessárias tempos de treinamento. Note que esta chamada de retorno é definido para monitorar a val_binary_crossentropy , não o val_loss . Essa diferença será importante mais tarde.

Use callbacks.TensorBoard para gerar logs TensorBoard para o treinamento.

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

Da mesma forma cada modelo vai usar as mesmas Model.compile e Model.fit configurações:

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

Modelo minúsculo

Comece treinando um 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,  
...................

Agora verifique como o modelo se saiu:

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 pequeno

Para ver se você consegue superar o desempenho do modelo pequeno, treine progressivamente alguns modelos maiores.

Experimente duas camadas ocultas com 16 unidades cada:

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 médio

Agora tente 3 camadas ocultas com 64 unidades cada:

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

E treine o modelo usando os mesmos dados:

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 exercício, você pode criar um modelo ainda maior e ver como ele começa a se ajustar rapidamente. A seguir, vamos adicionar a este benchmark uma rede que tem muito mais capacidade, muito mais do que o problema justificaria:

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

E, novamente, treine o modelo usando os mesmos dados:

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

Trace as perdas de treinamento e validação

As linhas sólidas mostram a perda de treinamento e as linhas tracejadas mostram a perda de validação (lembre-se: uma perda de validação menor indica um modelo melhor).

Embora construir um modelo maior dê a ele mais poder, se esse poder não for restringido de alguma forma, ele pode facilmente ajustar-se ao conjunto de treinamento.

Neste exemplo, normalmente, apenas o "Tiny" modelo consegue evitar overfitting completamente, e cada um dos modelos maiores overfit os dados mais rapidamente. Isto torna-se tão grave para o "large" modelo que você precisa mudar o enredo de uma escala log para ver realmente o que está acontecendo.

Isso fica aparente se você plotar e comparar as métricas de validação com as métricas de treinamento.

  • É normal que haja uma pequena diferença.
  • Se ambas as métricas estão se movendo na mesma direção, está tudo bem.
  • Se a métrica de validação começar a estagnar enquanto a métrica de treinamento continua a melhorar, você provavelmente está perto do sobreajuste.
  • Se a métrica de validação está indo na direção errada, o modelo está claramente superdimensionado.
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 no TensorBoard

Todos esses modelos gravaram registros do TensorBoard durante o treinamento.

Abra um visualizador do TensorBoard incorporado em um notebook:


# Load the TensorBoard notebook extension
%load_ext tensorboard

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

Você pode ver os resultados de uma execução anterior deste notebook em TensorBoard.dev .

TensorBoard.dev é uma experiência gerenciada para hospedar, rastrear e compartilhar experimentos de ML com todos.

Também está incluído em um <iframe> por conveniência:

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

Se você quiser compartilhar TensorBoard resultados você pode fazer o upload dos registros para TensorBoard.dev copiando o seguinte em uma célula de código.

tensorboard dev upload --logdir  {logdir}/sizes

Estratégias para evitar overfitting

Antes de entrar no conteúdo desta secção copiar os registros de treinamento do "Tiny" modelo acima, para usar como uma base para comparação.

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

Adicionar regularização de peso

Você pode estar familiarizado com o princípio da Navalha de Occam: dadas duas explicações para algo, a explicação mais provavelmente correta é a "mais simples", aquela que faz a menor quantidade de suposições. Isso também se aplica aos modelos aprendidos por redes neurais: dados alguns dados de treinamento e uma arquitetura de rede, existem vários conjuntos de valores de pesos (vários modelos) que podem explicar os dados, e modelos mais simples têm menos probabilidade de super ajuste do que os complexos.

Um "modelo simples" neste contexto é um modelo onde a distribuição dos valores dos parâmetros tem menos entropia (ou um modelo com menos parâmetros ao todo, como vimos na seção acima). Assim, uma maneira comum de mitigar o sobreajuste é colocar restrições na complexidade de uma rede, forçando seus pesos a assumirem apenas valores pequenos, o que torna a distribuição dos valores de peso mais "regular". Isso é chamado de "regularização de peso" e é feito adicionando à função de perda da rede um custo associado a ter grandes pesos. Esse custo vem em dois sabores:

  • L1 regularização , onde o custo adicional é proporcional ao valor absoluto dos coeficientes de pesos (ou seja, para o que é chamado de "L1 norma" dos pesos).

  • L2 regularização , onde o custo adicional é proporcional ao quadrado do valor dos coeficientes de pesos (ou seja, para o que é chamado de quadrado "L2 norma" dos pesos). A regularização L2 também é chamada de redução de peso no contexto das redes neurais. Não deixe que o nome diferente o confunda: a redução do peso é matematicamente igual à regularização L2.

A regularização L1 empurra os pesos para exatamente zero, encorajando um modelo esparso. A regularização de L2 penalizará os parâmetros de pesos sem torná-los esparsos, uma vez que a penalidade vai para zero para pesos pequenos - uma razão pela qual L2 é mais comum.

Em tf.keras , regularização peso é adicionado por passagem instâncias peso regularizer de camadas como argumentos. Vamos adicionar a regularização de peso L2 agora.

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 todos os coeficientes na matriz peso da camada irá adicionar 0.001 * weight_coefficient_value**2 para a perda total da rede.

É por isso que nós estamos monitorando a binary_crossentropy diretamente. Porque não tem esse componente de regularização misturado.

Então, essa mesma "Large" modelo com um L2 executa penalidade regularização muito melhor:

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

png

Como você pode ver, o "L2" modelo regularizada é agora muito mais competitivo com o do "Tiny" modelo. Este "L2" modelo também é muito mais resistente a overfitting que o "Large" modelo foi baseado em apesar de ter o mesmo número de parâmetros.

Mais informações

Há duas coisas importantes a serem observadas sobre esse tipo de regularização.

Primeiro: se você estiver escrevendo seu próprio loop de treinamento, então você precisa ter certeza de perguntar o modelo para suas perdas de regularização.

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

Segundo: Esta implementação funciona adicionando as penalidades de peso para a perda do modelo e, em seguida, aplicar um procedimento de otimização padrão depois disso.

Há uma segunda abordagem que, em vez disso, só executa o otimizador na perda bruta e, ao aplicar a etapa calculada, o otimizador também aplica alguma redução de peso. Este "dissociado Peso Decay" é visto em otimizadores como optimizers.FTRL e optimizers.AdamW .

Adicionar abandono

O abandono é uma das técnicas de regularização mais eficazes e mais comumente usadas para redes neurais, desenvolvida por Hinton e seus alunos na Universidade de Toronto.

A explicação intuitiva para o dropout é que, como os nós individuais na rede não podem contar com a saída dos outros, cada nó deve produzir recursos que sejam úteis por conta própria.

A eliminação, aplicada a uma camada, consiste em "eliminar" aleatoriamente (ou seja, definir como zero) uma série de recursos de saída da camada durante o treinamento. Digamos que uma determinada camada normalmente teria retornado um vetor [0,2, 0,5, 1,3, 0,8, 1,1] para uma determinada amostra de entrada durante o treinamento; após a aplicação do dropout, este vetor terá algumas entradas zero distribuídas aleatoriamente, por exemplo, [0, 0,5, 1,3, 0, 1,1].

A "taxa de abandono" é a fração dos recursos que estão sendo zerados; geralmente é definido entre 0,2 e 0,5. No momento do teste, nenhuma unidade é descartada e, em vez disso, os valores de saída da camada são reduzidos por um fator igual à taxa de desistência, de modo a equilibrar o fato de que mais unidades estão ativas do que no tempo de treinamento.

Em tf.keras você pode introduzir abandono em uma rede através da camada Dropout, que é aplicado à saída da camada direita antes.

Vamos adicionar duas camadas de exclusão em nossa rede para ver o quão bem elas se saem na redução do 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

É claro a partir deste enredo que ambos regularização aproxima melhorar o comportamento do "Large" modelo. Mas isso ainda não bateu mesmo o "Tiny" linha de base.

Em seguida, experimente os dois juntos e veja se isso funciona melhor.

Desistência combinada de 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 com o "Combined" regularização é, obviamente, o melhor até agora.

Ver no TensorBoard

Esses modelos também gravaram registros do TensorBoard.

Para abrir um visualizador de tensorboard incorporado em um notebook, copie o seguinte em uma célula de código:

%tensorboard --logdir {logdir}/regularizers

Você pode ver os resultados de uma execução anterior deste notebook em TensorDoard.dev .

Também está incluído em um <iframe> por conveniência:

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

Isto foi carregado com:

tensorboard dev upload --logdir  {logdir}/regularizers

Conclusões

Para recapitular: aqui estão as maneiras mais comuns de evitar overfitting em redes neurais:

  • Obtenha mais dados de treinamento.
  • Reduza a capacidade da rede.
  • Adicione regularização de peso.
  • Adicionar abandono.

Duas abordagens importantes não cobertas neste guia são:

  • aumento de dados
  • normalização de lote

Lembre-se de que cada método pode ajudar por si só, mas geralmente combiná-los pode ser ainda mais eficaz.

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