Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
tensorflow
GitHub Repository: tensorflow/docs-l10n
Path: blob/master/site/pt-br/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.

Overfitting e underfitting

Como sempre, o código deste exemplo usará a API tf.keras. Se quiser saber mais sobre ela, confira o guia do Keras do TensorFlow.

Nos dois exemplos anteriores, classificação de textos e previsão de eficiência de combustível, a exatidão dos modelos para os dados de validação atinge o pico após fazer o treinamento com um determinado número de épocas. Depois, ela fica estagnada ou começa a cair.

Em outras palavras, ocorre um overfitting do modelo para os dados de treinamento. Aprender a lidar com o overfitting é importante. Embora geralmente seja possível atingir uma alta exatidão para o conjunto de treinamento, o que você realmente vai querer é desenvolver modelos que fazem boas generalizações para um conjunto de teste (ou dados nunca vistos).

O oposto de overfitting é underfitting, que ocorre quando ainda há espaço para melhorias com os dados de treinamento. Há vários motivos para isso correr: se o modelo não for poderoso o bastante, se estiver regularizado demais ou se simplesmente não tiver sido treinado o bastante. Isso significa que a rede não aprendeu padrões relevantes para os dados de treinamento.

Porém, se você treinar demais, o modelo começará a fazer overfitting e aprender padrões dos dados de treinamento que não fazem generalizações para os dados de teste. É preciso atingir um ponto de equilíbrio. Entender como escolher o número de épocas adequado para o treinamento (mostrado abaixo) é uma habilidade muito útil.

Para evitar o overfitting, a melhor solução é utilizar dados de treinamento mais completos. O dataset deve abranger o intervalo completo de entradas que o modelo deverá tratar. Dados adicionais serão úteis somente se contiverem casos novos e interessantes.

Um modelo treinado com dados mais completos vai fazer generalizações naturalmente melhores. Quando isso não é mais possível, a melhor solução é usar outras técnicas, como regularização, que colocam restrições na quantidade e no tipo de informações que o modelo pode armazenar. Se uma rede puder memorizar somente uma pequena quantidade de padrões, o processo de otimização forçará que ela se concentre nos padrões mais presentes, o que dará uma maior chance de fazer generalizações melhores.

Neste notebook, você verá diversas técnicas comuns de regularização e as utilizará para melhorar um modelo de classificação.

Configuração

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__)
!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)

Dataset Higgs

O objetivo deste tutorial não é falar sobre física de partículas, então não se preocupe com os detalhes do dataset, que contém 11 milhões de exemplos, cada um com 28 características, além de 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')
FEATURES = 28

A classe tf.data.experimental.CsvDataset pode ser usada para ler registros CSV diretamente de um arquivo gzip, sem nenhum passo intermediário de descompactação.

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

A classe que lê o CSV retorna uma lista de escalares para cada registro. A seguinte função reencapsula 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 grandes lotes de dados.

Portanto, em vez de reencapsular cada linha individualmente, crie um novo tf.data.Dataset que receba lotes de 10 mil exemplos, aplique a função pack_row a cada lote e depois divida os lotes em registros individuais:

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

Avalie alguns registros desse novo packed_ds.

As características não estão perfeitamente normalizadas, mas isso será 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 manter este tutorial relativamente curto, use somente os primeiros mil exemplos para validação e os 10 mil seguintes 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 métodos Dataset.skip e Dataset.take facilitam esse processo.

Além disso, use o método Dataset.cache para garantir que o loader não precise ler novamente 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

Esses datasets retornam exemplos individuais. Use o método Dataset.batch para criar lotes de tamanho adequado para o treinamento. Antes de fazer a divisão em lotes, lembre-se de usar Dataset.shuffle e Dataset.repeat no conjunto de treinamento.

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

Demonstração do overfitting

A forma mais simples de evitar o overfitting é começar com um modelo pequeno: um modelo com uma pequena quantidade de parâmetros que podem ser aprendidos (determinada pelo número de camadas e pelo número de unidades por camada). No aprendizado profundo, a quantidade de parâmetros que podem ser aprendidos em um modelo costuma ser chamada de "capacidade" do modelo.

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

Nunca se esqueça de que os modelos de aprendizado profundo costumam ser bons em se adequarem aos dados de treinamento, mas o verdadeiro desafio é a generalização, não a adequação.

Por outro lado, se a rede tiver recursos de memorização limitados, não conseguirá aprender o mapeamento tão facilmente. Para minimizar a perda, ela terá que aprender as representações compactadas, que têm maior poder de previsão. Ao mesmo tempo, se o seu modelo for pequeno demais, terá dificuldades de se adequar 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 a arquitetura ou tamanho certo do modelo (quanto ao número de camadas ou ao tamanho certo de cada camada). Você precisará fazer testes, usando uma série de diferentes arquiteturas.

Para encontrar o tamanho adequado para o modelo, é melhor começar com poucas camadas e parâmetros, depois aumentar o tamanho das camadas ou adicionar novas camadas até observar ganhos decrescentes quanto à perda de validação.

Comece com um modelo simples, usando somente camadas densamente conectadas (tf.keras.layers.Dense) como linha de base, depois crie modelos maiores e compare-os.

Procedimento de treinamento

Diversos modelos são treinados de forma melhor se você reduzir gradualmente a taxa de aprendizado durante o treinamento. Use tf.keras.optimizers.schedules para reduzir a taxa de aprendizado 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 tf.keras.optimizers.schedules.InverseTimeDecay para diminuir a taxa de aprendizado hiperbolicamente para metade da taxa base ao chegar a 1.000 épocas, um terço ao chegar a 2.000 é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')

Cada modelo neste tutorial usará a mesma configuração de treinamento. Portanto, configure tudo de uma maneira reutilizável, começando pela lista de callbacks.

Neste tutorial, o treinamento é feito com várias épocas curtas. Para reduzir o ruído de criação de logs, use tfdocs.EpochDots, que simplesmente registra um . para cada época e um conjunto completo de métricas a cada 100 épocas.

Em seguida, inclua tf.keras.callbacks.EarlyStopping para evitar tempos de treinamento longos e desnecessários. É importante notar que esse callback monitora val_binary_crossentropy, não val_loss. Essa diferença será importante mais adiante.

Use callbacks.TensorBoard para gerar logs do TensorBoard referentes ao 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), ]

De maneira similar, cada modelo usará as mesmas configurações de Model.compile de 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 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')

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

Modelo pequeno

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

Experimente usar 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')

Modelo médio

Agora, experimente usar três 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")

Modelo grande

Para fins demonstrativos, você pode criar um modelo maior ainda e verificar com que velocidade ele sofre overfitting. Em seguida, adicione a esse comparativo uma rede que tenha muito mais capacidade, bem maior do que seria necessário para o problema em questão:

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

Novamente, treine o modelo usando os mesmos dados:

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

Plotar as perdas de treinamento e validação

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

Embora criar um modelo maior ofereça uma potência maior, se essa potência não for restringida de alguma forma, é fácil ocorrer overfitting para o conjunto de treinamento.

Tipicamente, neste exemplo, somente o modelo "Tiny" (minúsculo) consegue evitar totalmente o overfitting, e cada modelo maior sofre overfitting mais rapidamente. Isso fica tão grave para o modelo "large" (grande) que você precisa mudar o gráfico para uma escala logarítmica para entender o que está acontecendo.

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

  • É normal que haja uma pequena diferença.

  • Se as duas métricas estiverem caminhando na mesma direção, está tudo certo.

  • Se a métrica de validação começar a ficar estagnada enquanto a de treinamento continuar melhorando, provavelmente o overfitting está perto de ocorrer.

  • Se a métrica de validação estiver caminhando na direção errada, o modelo está claramente sofrendo overfitting.

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

Observação: todas as execuções de treinamento acima usaram callbacks.EarlyStopping para encerrar o treinamento quando estava claro que o modelo não estava progredindo.

Ver no TensorBoard

Todos esses modelos criaram logs do TensorBoard durante o treinamento.

Abra um visualizador integrado do TensorBoard dentro de um notebook (lamentamos, mas não é possível exibir em tensorflow.org):

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

Estratégias para evitar o overfitting

Antes de ler o conteúdo desta seção, copie os logs de treinamento do modelo "Tiny" (minúsculo) acima para usá-lo como linha de base de comparação.

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

Acrescentar regularização de pesos

Talvez você já conheça o princípio da Navalha de Occam: dadas duas explicações para alguma coisa, a explicação com maior probabilidade de ser a correta é a "mais simples", aquela que faz o menor número de suposições. Isso também se aplica aos modelos aprendidos por redes neurais: para determinados dados de treinamento e uma arquitetura de rede, há diversos conjuntos de valores de peso (diversos modelos) que podem explicar os dados, e os modelos mais simples têm menor probabilidade de sofrerem overfitting do que os mais complexos.

Um "modelo simples" neste contexto é um modelo em que a distribuição de valores de parâmetros tem menos entropia (ou um modelo com menos parâmetros, conforme demonstrado na seção acima). Portanto, uma forma comum de mitigar o overfitting é colocar restrições na complexidade de uma rede, forçando que seus pesos tenham somente valores pequenos, o que deixa a distribuição dos valores de pesos mais "regular". Isso é chamado de "regularização de pesos" e é feito acrescentando-se um custo associado a ter pesos maiores à função de perda da rede. Há dois tipos de custo:

  • Regularização L1, em que o custo acrescentado é proporcional ao valor absoluto dos coeficientes de pesos (ou seja, o que é chamado de "norma L1" dos pesos).

  • Regularização L2, em que o custo acrescentado é proporcional ao quadrado do valor dos coeficientes de pesos (ou seja, o que é chamado de "norma L2" quadrada dos pesos). A regularização L2 também é chamada de decaimento de pesos no contexto de redes neurais. Não se confunda com os diferentes nomes: o decaimento de pesos é matematicamente o mesmo que a regularização L2.

A regularização L1 tende os pesos a exatamente zero, o que incentiva um modelo esparso. A regularização L2 penaliza os parâmetros de pesos sem torná-los esparsos, já que a penalidade vai a zero para pesos pequenos, um motivo pelo qual a regularização L2 é mais comum.

Em tf.keras, a regularização de pesos é acrescentada passando-se instâncias do regularizador de pesos às camadas como argumentos de palavras-chave. Acrescente a regularização 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 na matriz de pesos da camada acrescentará 0.001 * weight_coefficient_value**2 à perda total da rede.

É por isso que estamos monitorando binary_crossentropy diretamente, pois esse componente de regularização não está presente.

Portanto, aquele mesmo modelo "Large" (grande) com penalidade de regularização L2 tem desempenho muito melhor:

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

Conforme demonstrado no diagrama acima, agora o modelo com regularização "L2" é muito mais competitivo em relação ao modelo "Tiny" (minúsculo). Esse modelo "L2" também é muito mais resistente ao overfitting do que o modelo "Large" (grande) no qual foi baseado, apesar de ter a mesma quantidade de parâmetros.

Mais informações

É preciso notar dois aspectos importantes sobre esse tipo de regularização:

  1. Se você estiver escrevendo seu próprio loop de treinamento, precisa perguntar ao modelo suas perdas de regularização.

result = l2_model(features) regularization_loss=tf.add_n(l2_model.losses)
  1. Essa implementação funciona adicionando-se as penalidades de pesos à perda do modelo e depois aplicando-se um procedimento de otimização padrão em seguida.

Existe uma segunda estratégia, que executa o otimizador somente na perda bruta e então, ao aplicar o passo calculado, o otimizador também aplica um decaimento de pesos. Esse "decaimento de pesos desacoplado" é usado em otimizadores como tf.keras.optimizers.Ftrl e tfa.optimizers.AdamW.

Acrecentar dropout

O dropout é uma das técnicas de regularização mais eficazes e mais usadas para redes neurais, desenvolvida por Hinton e seus estudantes da Universidade de Toronto.

Veja uma explicação intuitiva para o dropout: como nós individuais da rede não podem depender da saída de outros, cada nós precisa gerar características que sejam úteis por si só.

O dropout, quando aplicado a uma camada, consiste da "eliminação" (ou seja, definição como zero) aleatória de uma quantidade de características de saída da camada durante o treinamento. Por exemplo, normalmente uma camada retornaria 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, esse mesmo vetor terá algumas entradas iguais a zero distribuídas aleatoriamente (por exemplo, [0, 0.5, 1.3, 0, 1.1]).

A "taxa de dropout" é a fração das características que estão sendo definidas como zero. Geralmente, fica entre 0,2 e 0,5. No momento do teste, nenhuma unidade sofre dropout e, em vez disso, os valores de saída da camada são reduzidos por um fator igual à taxa de dropout para balancear o fato de que mais unidades estão ativas do que no momento de treinamento.

No Keras, você pode acrescentar dropout a uma rede pela camada tf.keras.layers.Dropout, que é aplicada à saída da camada logo antes dela.

Acrescente duas camadas de dropout à sua rede para verificar o desempenho delas em reduzir o overfitting:

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

Com esse gráfico, fica claro que essas duas estratégias de regularização melhoram o comportamento do modelo "Large" (grande), mas ele ainda não supera a linha de base "Tiny" (minúsculo).

Agora experimente os dois juntos e veja se fica melhor.

Combinar L2 + dropout

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

Este modelo com a regularização "Combined" (combinada) é nitidamente o melhor até agora.

Ver no TensorBoard

Esses modelos também são registrados nos logs do TensorBoard.

Para abrir uma visualização integrada, execute o seguinte em uma célula de código (lamentamos, mas não é possível exibir em tensorflow.org):

%tensorboard --logdir {logdir}/regularizers

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

Conclusões

Recapitulando, veja abaixo as formas mais comuns de evitar o overfitting em redes neurais:

  • Obter mais dados de treinamento.

  • Reduzir a capacidade da rede.

  • Acrescentar regularização de pesos.

  • Acrecentar dropout.

Confira duas estratégias importantes não discutidas neste guia:

Lembre-se de que cada método pode ajudar individualmente, mas combiná-los pode ser ainda mais eficaz.