TensorFlow, Keras e aprendizado profundo, sem um doutorado

1. Visão geral

Este tutorial foi atualizado para o TensorFlow 2.2.

74f6fbd758bf19e6.png

Neste codelab, você vai aprender a criar e treinar uma rede neural que reconhece dígitos escritos à mão. Ao aprimorar sua rede neural para atingir 99% de precisão, você também descobrirá as ferramentas do comércio que os profissionais de aprendizado profundo usam para treinar modelos com eficiência.

Este codelab usa o conjunto de dados MNIST, uma coleção de 60.000 dígitos rotulados que mantém gerações de PhDs ocupadas por quase duas décadas. Você resolverá o problema com menos de 100 linhas de código Python / TensorFlow.

O que você vai aprender

  • O que é uma rede neural e como treiná-la
  • Como criar uma rede neural básica de uma camada usando o tf.keras
  • Como adicionar mais camadas
  • Como configurar uma programação de taxa de aprendizado
  • Como criar redes neurais convolucionais
  • Como usar técnicas de regularização: dropout e normalização em lote.
  • O que está overfitting

O que é necessário

Apenas um navegador. É possível realizar este workshop inteiramente com o Google Colaboratory.

Feedback

Avise nossa equipe se você encontrar algo errado no laboratório ou achar que ele precisa ser melhorado. O feedback é resolvido por problemas do GitHub [link do feedback].

2. Guia de início rápido do Google Colaboratory

Este laboratório usa o Google Colaboratory e não exige configuração. Ele pode ser executado em um Chromebook. Abra o arquivo abaixo e execute as células para se familiarizar com os notebooks do Colab.

c3df49e90e5a654f.png Welcome to Colab.ipynb

Confira mais instruções abaixo:

Selecionar um back-end de GPU

hsy7H7O5qJNvKcRnHRiZoyh0IznlzmrO60wR1B6pqtfdc8Ie7gLsXC0f670zsPzGsNy3QAJuZefYv9CwTHmjiMyywG2pTpnMCE6SlkfEmYK1neVm

No menu do Colab, selecione Ambiente de execução > Mude o tipo de ambiente de execução e selecione a GPU. A conexão com o ambiente de execução vai ocorrer automaticamente na primeira execução ou você pode usar o botão "Conectar" no canto superior direito.

Execução do notebook

evlBKSO15ImjocdEcsIo8unzEe6oDGYnKFe8CoHS_7QiP3sDbrs2jB6lbyitEtE7Gt_1UsCdU5dJA-_2IgBWh9ofYf4yVDE740PwJ6kiQwuXNOLkgktzzf0E_k5VN5mq29ZXI5wb7Q

Execute uma célula de cada vez clicando em uma célula e usando Shift-ENTER. Também é possível executar todo o notebook em Ambiente de execução > Executar tudo

Índice

OXeYYbtKdLCNnw_xovSMeMwSdD7CL_w25EfhnpRhhhO44bYp3zZpU72J5tKaSuo8wpas0GK5B2sTBlIMiFmdGxFRQ9NmwJ7JIRYy5XtpWKQCPdxQVRPy_0J_LshGIKjtw8P9fXozaA

Todos os notebooks têm um índice. Para abri-lo, use a seta preta à esquerda.

Células ocultas

GXTbXUO8xpPFKiGc6Q-cFwFHxHvOa105hHg3vk77EDpStyhU4AQMN3FYenbiBusHXUSk-yGXbRDcK-Cwx18XbDtyqB5WRr3_2jhnLvFxW8a7H_4cGvVDKrEMto_QxhfTeO0hwmrfng

Algumas células mostrarão apenas o título. Este é um recurso de notebook específico para o Colab. É possível clicar duas vezes neles para ver o código deles, mas normalmente não é muito interessante. Normalmente, são funções de suporte ou visualização. Você ainda precisa executar essas células para que as funções internas sejam definidas.

3. Treinar uma rede neural

Primeiro vamos ver um treinamento de rede neural. Abra o notebook abaixo e execute todas as células. Não preste atenção ao código ainda, vamos começar a explicá-lo mais tarde.

c3df49e90e5a654f.png keras_01_mnist.ipynb

Ao executar o notebook, concentre-se nas visualizações. Veja as explicações abaixo.

Dados de treinamento

Temos um conjunto de dados de dígitos manuscritos que foram rotulados para saber o que cada imagem representa, por exemplo, um número entre 0 e 9. No notebook, você verá um trecho:

ad83f98e56054737.png

A rede neural que criaremos classifica os dígitos escritos à mão nas 10 classes (0, ..., 9). Isso é feito com base em parâmetros internos que precisam ter um valor correto para que a classificação funcione bem. O "valor correto" é aprendida por meio de um processo de treinamento que requer um "conjunto de dados rotulado" com imagens e as respostas corretas associadas.

Como saber se a rede neural treinada tem um bom desempenho? Usar o conjunto de dados de treinamento para testar a rede seria fraude. Ele já viu esse conjunto de dados várias vezes durante o treinamento e, com certeza, tem muito desempenho nele. Precisamos de outro conjunto de dados rotulados, nunca visto durante o treinamento, para avaliar o "mundo real" desempenho da rede. Ele é chamado de "conjunto de dados de validação".

Treinamento

Conforme o treinamento avança, um lote de dados de treinamento por vez, os parâmetros internos do modelo são atualizados e o modelo reconhece cada vez mais os dígitos escritos à mão. É possível vê-lo no gráfico de treinamento:

3f7b405649301ea.png

À direita, a "precisão" é simplesmente a porcentagem de dígitos corretamente reconhecidos. Ele aumenta conforme o treinamento avança, o que é bom.

À esquerda, podemos ver a "perda". Para conduzir o treinamento, vamos definir uma "perda" que representa o quão mal o sistema reconhece os dígitos e tenta minimizá-los. O que você vê aqui é que a perda diminui nos dados de treinamento e de validação à medida que o treinamento avança: isso é bom. Significa que a rede neural está aprendendo.

O eixo X representa o número de "épocas" ou iterações em todo o conjunto de dados.

Previsões

Quando o modelo é treinado, podemos usá-lo para reconhecer dígitos escritos à mão. A próxima visualização mostra o desempenho em alguns dígitos renderizados de fontes locais (primeira linha) e, em seguida, nos 10.000 dígitos do conjunto de dados de validação. A classe prevista aparece abaixo de cada dígito, em vermelho se estiver errada.

c0699216ba0effdb.png

Como você pode ver, esse modelo inicial não é muito bom, mas ainda reconhece alguns dígitos corretamente. A acurácia da validação final é de cerca de 90%, o que não é tão ruim para o modelo simplista com o qual estamos começando,mas ainda significa que ele deixa passar 1.000 dígitos de validação dos 10.000. É muito mais do que o que pode ser mostrado, e é por isso que parece que todas as respostas estão erradas (vermelho).

Tensores

Os dados são armazenados em matrizes. Uma imagem em escala de cinza de 28 x 28 pixels se encaixa em uma matriz bidimensional de 28 x 28. Mas para uma imagem colorida, precisamos de mais dimensões. Existem três valores de cor por pixel (vermelho, verde, azul). Portanto, é necessária uma tabela tridimensional com dimensões [28, 28, 3]. Para armazenar um lote de 128 imagens coloridas, é necessária uma tabela de quatro dimensões com dimensões [128, 28, 28, 3].

Essas tabelas multidimensionais são chamadas de "tensores", e a lista das dimensões é o "shape" delas.

4. [INFORMAÇÃO]: introdução às redes neurais

Resumindo

Se todos os termos em negrito no próximo parágrafo já forem conhecidos, siga para o próximo exercício. Se você está começando no aprendizado profundo, dê as boas-vindas e continue lendo.

witch.png

Para modelos criados como uma sequência de camadas, o Keras oferece a API Sequential. Por exemplo, um classificador de imagens que usa três camadas densas pode ser escrito em Keras como:

model = tf.keras.Sequential([
    tf.keras.layers.Flatten(input_shape=[28, 28, 1]),
    tf.keras.layers.Dense(200, activation="relu"),
    tf.keras.layers.Dense(60, activation="relu"),
    tf.keras.layers.Dense(10, activation='softmax') # classifying into 10 classes
])

# this configures the training of the model. Keras calls it "compiling" the model.
model.compile(
  optimizer='adam',
  loss= 'categorical_crossentropy',
  metrics=['accuracy']) # % of correct answers

# train the model
model.fit(dataset, ... )

688858c21e3beff2.png

Uma única camada densa

Os dígitos escritos à mão no conjunto de dados MNIST são imagens em escala de cinza de 28 x 28 pixels. A abordagem mais simples para classificá-los é usar 28x28=784 pixels como entradas para uma rede neural de uma camada.

Screen Shot 2016-07-26 at 12.32.24.png

Cada "neurônio" de uma rede neural faz uma soma ponderada de todas as entradas e adiciona uma constante chamada de "viés". e, em seguida, alimenta o resultado por meio de alguma "função de ativação" não linear. Os "pesos" e "vieses" são parâmetros que serão determinados por meio do treinamento. Eles são inicializados com valores aleatórios no início.

A imagem acima representa uma rede neural de uma camada com 10 neurônios de saída, já que queremos classificar dígitos em 10 classes (0 a 9).

Com uma multiplicação de matrizes

Veja como uma camada de rede neural, processando uma coleção de imagens, pode ser representada por uma multiplicação de matrizes:

matmul.gif

Usando a primeira coluna de pesos na matriz de pesos W, calculamos a soma ponderada de todos os pixels da primeira imagem. Essa soma corresponde ao primeiro neurônio. Usando a segunda coluna de pesos, fazemos o mesmo para o segundo neurônio e assim por diante até o 10o. Podemos repetir a operação para as 99 imagens restantes. Se chamarmos X a matriz que contém nossas 100 imagens, todas as somas ponderadas dos 10 neurônios, calculadas em 100 imagens, serão simplesmente X.W, uma multiplicação de matrizes.

Agora, cada neurônio precisa adicionar um viés (uma constante). Como temos 10 neurônios, temos 10 constantes de viés. Vamos chamar esse vetor de 10 valores de b. Ele precisa ser adicionado a cada linha da matriz calculada anteriormente. Usar um pouco de mágica chamado "transmissão" vamos escrever isso com um simples sinal de mais.

Por fim, aplicamos uma função de ativação, por exemplo, "softmax". (explicado abaixo) e receba a fórmula que descreve uma rede neural de uma camada, aplicada a 100 imagens:

Screen Shot 2016-07-26 at 16.02.36.png

No Keras

Com bibliotecas de rede neural de alto nível, como a Keras, não precisamos implementar essa fórmula. No entanto, é importante entender que uma camada de rede neural é apenas um monte de multiplicações e adições. No Keras, uma camada densa seria escrita como:

tf.keras.layers.Dense(10, activation='softmax')

Vá além

É comum encadear camadas de redes neurais. A primeira camada calcula somas ponderadas de pixels. As camadas subsequentes calculam somas ponderadas das saídas das camadas anteriores.

fba0638cc213a29.png

A única diferença, além do número de neurônios, será a escolha da função de ativação.

Funções de ativação: relu, softmax e sigmoid

Você normalmente usaria a função "relu" função de ativação para todas as camadas, exceto a última. A última camada de um classificador usaria "softmax" ativação.

644f4213a4ee70e5.png

Mais uma vez, um "neurônio" calcula uma soma ponderada de todas as entradas, adiciona um valor chamado "viés" e alimenta o resultado com a função de ativação.

A função de ativação mais conhecida é chamada de "RELU" (unidade linear retificada). Essa é uma função muito simples, como mostra o gráfico acima.

A função de ativação tradicional em redes neurais era o "sigmoide", mas a "relu" mostrou ter melhores propriedades de convergência em quase todos os lugares e agora é o preferido.

41fc82288c4aff5d.png

Ativação do Softmax para classificação

A última camada da rede neural tem 10 neurônios porque queremos classificar dígitos manuscritos em 10 classes (0, 9). Ela deve gerar 10 números entre 0 e 1, representando a probabilidade desse dígito ser 0, 1, 2 e assim por diante. Para isso, na última camada, usaremos uma função de ativação chamada "softmax".

A aplicação de softmax a um vetor é feita tomando a exponencial de cada elemento e, em seguida, normalizando o vetor, normalmente dividindo-o por seu "L1" norma (ou seja, soma de valores absolutos) para que os valores normalizados somem 1 e possam ser interpretados como probabilidades.

A saída da última camada, antes da ativação, às vezes é chamada de "logits". Se esse vetor for L = [L0, L1, L2, L3, L4, L5, L6, L7, L8, L9], então:

ef0d98c0952c262d.png d51252f75894479e.gif

Perda de entropia cruzada

Agora que nossa rede neural produz previsões a partir de imagens de entrada, precisamos medir a qualidade delas, ou seja, a distância entre o que a rede nos diz e as respostas corretas, geralmente chamadas de "rótulos". Lembre-se de que temos rótulos corretos para todas as imagens no conjunto de dados.

Qualquer distância funcionaria, mas, para problemas de classificação, a chamada "distância da entropia cruzada" é o mais eficaz. Vamos chamar isso de erro ou "perda" função:

6dbba1bce3cadc36.png

Gradiente descendente

"Treinamento" a rede neural significa usar imagens e rótulos de treinamento para ajustar pesos e vieses, minimizando a função de perda de entropia cruzada. Confira como funciona.

A entropia cruzada é uma função de pesos, vieses, pixels da imagem de treinamento e sua classe conhecida.

Se computamos os derivados parciais da entropia cruzada em relação a todos os pesos e todas as tendências, obtemos um "gradiente", calculado para uma determinada imagem, rótulo e valor presente de pesos e tendências. Lembre-se de que podemos ter milhões de pesos e vieses, então calcular o gradiente parece muito trabalho. Felizmente, o TensorFlow faz isso por nós. A propriedade matemática de um gradiente é que ele aponta para "para cima". Como queremos chegar onde a entropia cruzada é baixa, vamos na direção oposta. Atualizamos os pesos e as tendências por uma fração do gradiente. Em seguida, fazemos a mesma coisa várias vezes usando os próximos lotes de imagens e rótulos de treinamento, em um loop de treinamento. Com sorte, isso converge para um lugar em que a entropia cruzada é mínima, embora nada garanta que esse mínimo seja único.

gradiente descendente

Minilote e momentum

É possível calcular o gradiente em apenas uma imagem de exemplo e atualizar os pesos e vieses imediatamente. No entanto, ao fazer isso em um lote de, por exemplo, 128 imagens, o gradiente representa melhor as restrições impostas por diferentes exemplos de imagem e, portanto, provavelmente converge para a solução mais rapidamente. O tamanho do minilote é um parâmetro ajustável.

Essa técnica, às vezes chamada de "gradiente descendente estocástico" tem outro benefício mais pragmático: trabalhar com lotes também significa trabalhar com matrizes maiores, e elas geralmente são mais fáceis de otimizar em GPUs e TPUs.

No entanto, a convergência pode ser um pouco caótica e até parar se o vetor do gradiente for todo zero. Isso significa que encontramos um mínimo? Nem sempre. Um componente de gradiente pode ser zero no mínimo ou no máximo. Em um vetor de gradiente com milhões de elementos, se todos eles forem zeros, a probabilidade de que cada zero corresponda a um mínimo e nenhum deles a um ponto máximo será muito pequena. Em um espaço de muitas dimensões, os pontos de sela são muito comuns e não queremos parar neles.

cc544924671fa208.png

Ilustração: um suporte para sentar. O gradiente é 0, mas não é o mínimo em todas as direções. (Atribuição da imagem Wikimedia: por Nicoguaro - Próprio trabalho, CC BY 3.0)

A solução é adicionar um pouco de impulso ao algoritmo de otimização para que ele possa ultrapassar os pontos de obstáculos sem parar.

Glossário

lote ou minilote: o treinamento é sempre executado em lotes de dados de treinamento e rótulos. Isso ajuda o algoritmo a convergir. O "lote" normalmente é a primeira dimensão dos tensores de dados. Por exemplo, um tensor de forma [100, 192, 192, 3] contém 100 imagens de 192x192 pixels com três valores por pixel (RGB).

perda de entropia cruzada: uma função de perda especial usada com frequência em classificadores.

camada densa: uma camada de neurônios em que cada um deles está conectado a todos os neurônios da camada anterior.

features: as entradas de uma rede neural às vezes são chamadas de "atributos". A arte de descobrir quais partes de um conjunto de dados (ou combinações de partes) devem ser alimentadas em uma rede neural para receber boas previsões é chamada de "engenharia de atributos".

labels: outro nome para "classes" ou respostas corretas em um problema de classificação supervisionado

Taxa de aprendizado: fração do gradiente em que pesos e vieses são atualizados a cada iteração do loop de treinamento.

logits: as saídas de uma camada de neurônios antes da aplicação da função de ativação são chamadas de "logits". O termo vem da "função logística", também conhecida como "função sigmoide" que costumava ser a função de ativação mais conhecida. "Saídas de neurônios antes da função logística" foi encurtado para "logits".

loss: a função de erro que compara as saídas da rede neural com as respostas corretas

neuron: calcula a soma ponderada das entradas, adiciona uma polarização e alimenta o resultado com uma função de ativação.

codificação one-hot: a classe 3 de 5 é codificada como um vetor de cinco elementos, todos os zeros, exceto o terceiro, que é 1.

relu: unidade linear retificada. Uma função de ativação conhecida para os neurônios.

sigmoid: outra função de ativação que era conhecida e ainda é útil em casos especiais.

softmax: uma função de ativação especial que atua em um vetor, aumenta a diferença entre o maior componente e todos os outros, além de normalizar o vetor para ter uma soma de 1, de modo que possa ser interpretado como um vetor de probabilidades. Usado como a última etapa em classificadores.

tensor: um "tensor" é como uma matriz, mas com um número arbitrário de dimensões. Um tensor unidimensional é um vetor. Um tensor de duas dimensões é uma matriz. Você pode ter tensores com 3, 4, 5 ou mais dimensões.

5. Vamos direto ao código

Voltando ao notebook do estudo, agora vamos ler o código.

c3df49e90e5a654f.png keras_01_mnist.ipynb

Vamos analisar todas as células deste notebook.

Célula "Parâmetros"

Aqui são definidos o tamanho do lote, o número de períodos de treinamento e o local dos arquivos de dados. Os arquivos de dados são hospedados em um bucket do Google Cloud Storage (GCS). É por isso que o endereço começa com gs://.

Célula "Importações"

Todas as bibliotecas Python necessárias são importadas aqui, incluindo o TensorFlow e também matplotlib para visualizações.

Célula "Utilitários de visualização [RUN ME]****"

Esta célula contém um código de visualização desinteressante. Ele fica recolhido por padrão, mas você pode abri-lo e ver o código quando tiver tempo, clicando duas vezes nele.

Célula "tf.data.Dataset: analisar arquivos e preparar conjuntos de dados de treinamento e validação"

Esta célula usou a API tf.data.Dataset para carregar o conjunto de dados do MNIST dos arquivos de dados. Não é necessário gastar muito tempo nessa célula. Se você tiver interesse na API tf.data.Dataset, confira um tutorial que explica o assunto: pipelines de dados de velocidade da TPU. Por enquanto, os princípios básicos são:

As imagens e os rótulos (respostas corretas) do conjunto de dados MNIST são armazenados em registros de comprimento fixo em quatro arquivos. Os arquivos podem ser carregados com a função de registro fixo dedicada:

imagedataset = tf.data.FixedLengthRecordDataset(image_filename, 28*28, header_bytes=16)

Agora temos um conjunto de dados de bytes de imagem. Eles precisam ser decodificados em imagens. Para isso, definimos uma função. A imagem não está compactada, então a função não precisa decodificar nada (decode_raw praticamente não faz nada). A imagem é então convertida em valores de ponto flutuante entre 0 e 1. Podemos remodelá-la aqui como uma imagem 2D, mas, na verdade, a mantemos como uma matriz plana de pixels de tamanho 28 x 28, porque é isso que nossa camada densa inicial espera.

def read_image(tf_bytestring):
    image = tf.io.decode_raw(tf_bytestring, tf.uint8)
    image = tf.cast(image, tf.float32)/256.0
    image = tf.reshape(image, [28*28])
    return image

Aplicamos essa função ao conjunto de dados usando .map e recebemos um conjunto de dados de imagens:

imagedataset = imagedataset.map(read_image, num_parallel_calls=16)

Fazemos o mesmo tipo de leitura e decodificação para rótulos e .zip imagens e rótulos juntos:

dataset = tf.data.Dataset.zip((imagedataset, labelsdataset))

Agora temos um conjunto de dados de pares (imagem, rótulo). Isso é o que nosso modelo espera. Ainda não estamos prontos para usá-lo na função de treinamento:

dataset = dataset.cache()
dataset = dataset.shuffle(5000, reshuffle_each_iteration=True)
dataset = dataset.repeat()
dataset = dataset.batch(batch_size)
dataset = dataset.prefetch(tf.data.experimental.AUTOTUNE)

A API tf.data.Dataset tem todas as funções de utilitário necessárias para preparar conjuntos de dados:

.cache armazena o conjunto de dados em cache na RAM. Esse é um conjunto de dados pequeno, por isso ele funcionará. .shuffle o embaralha com um buffer de 5.000 elementos. É importante que os dados de treinamento sejam bem embaralhados. .repeat repete o conjunto de dados. Vamos treinar nela várias vezes (vários períodos). .batch reúne várias imagens e rótulos em um minilote. Por fim, .prefetch pode usar a CPU para preparar o próximo lote enquanto o atual está sendo treinado na GPU.

O conjunto de dados de validação é preparado de maneira semelhante. Agora estamos prontos para definir um modelo e usar esse conjunto de dados para treiná-lo.

Célula "Keras Model"

Todos os nossos modelos serão sequências retas de camadas para que possamos usar o estilo tf.keras.Sequential para criá-los. Inicialmente aqui, é uma única camada densa. Ela tem 10 neurônios porque estamos classificando dígitos escritos à mão em 10 classes. Ela usa "softmax" porque ele é a última camada do classificador.

Um modelo do Keras também precisa saber o formato das entradas. tf.keras.layers.Input pode ser usado para defini-lo. Aqui, os vetores de entrada são vetores planos de valores de pixel com comprimento de 28 x 28.

model = tf.keras.Sequential(
  [
    tf.keras.layers.Input(shape=(28*28,)),
    tf.keras.layers.Dense(10, activation='softmax')
  ])

model.compile(optimizer='sgd',
              loss='categorical_crossentropy',
              metrics=['accuracy'])

# print model layers
model.summary()

# utility callback that displays training curves
plot_training = PlotTraining(sample_rate=10, zoom=1)

A configuração do modelo é feita no Keras usando a função model.compile. Aqui, usamos o otimizador básico 'sgd' (gradiente descendente estocástico). Um modelo de classificação requer uma função de perda de entropia cruzada chamada 'categorical_crossentropy' no Keras. Por fim, pedimos para o modelo calcular a métrica 'accuracy', que é a porcentagem de imagens classificadas corretamente.

O Keras oferece o utilitário model.summary(), que mostra os detalhes do modelo que você criou. O instrutor do tipo adicionou o utilitário PlotTraining (definido na célula "utilitários de visualização") que vai mostrar várias curvas de treinamento durante o treinamento.

Célula "Treinar e validar o modelo"

É aqui que o treinamento acontece, chamando model.fit e transmitindo os conjuntos de dados de treinamento e validação. Por padrão, o Keras executa uma rodada de validação ao final de cada período.

model.fit(training_dataset, steps_per_epoch=steps_per_epoch, epochs=EPOCHS,
          validation_data=validation_dataset, validation_steps=1,
          callbacks=[plot_training])

No Keras, é possível usar callbacks para adicionar comportamentos personalizados durante o treinamento. Foi assim que o gráfico de treinamento com atualização dinâmica foi implementado neste workshop.

Célula "Visualizar previsões"

Depois que o modelo for treinado, poderemos fazer previsões dele chamando model.predict():

probabilities = model.predict(font_digits, steps=1)
predicted_labels = np.argmax(probabilities, axis=1)

Preparamos um conjunto de dígitos impressos renderizados com fontes locais como teste. Lembre-se de que a rede neural retorna um vetor de 10 probabilidades a partir do "softmax" final. Para conseguir o rótulo, precisamos descobrir qual a probabilidade mais alta. O np.argmax da biblioteca numpy faz isso.

Para entender por que o parâmetro axis=1 é necessário, lembre-se de que processamos um lote de 128 imagens e, portanto, o modelo retorna 128 vetores de probabilidades. O formato do tensor de saída é [128, 10]. Estamos calculando o argmax nas 10 probabilidades retornadas para cada imagem, portanto, axis=1 (o primeiro eixo é 0).

Esse modelo simples já reconhece 90% dos dígitos. Nada mal, mas agora você vai melhorar isso significativamente.

396c54ef66fad27f.png

6. Adicionar camadas

godeep.png

Para melhorar a precisão do reconhecimento, vamos adicionar mais camadas à rede neural.

Screen Shot 2016-07-27 at 15.36.55.png

Mantemos a softmax como a função de ativação na última camada porque é o que funciona melhor para a classificação. No entanto, em camadas intermediárias, usaremos a função de ativação mais clássica: sigmoide:

41fc82288c4aff5d.png

Por exemplo, seu modelo pode ter esta aparência (não se esqueça das vírgulas, tf.keras.Sequential usa uma lista de camadas separada por vírgulas):

model = tf.keras.Sequential(
  [
      tf.keras.layers.Input(shape=(28*28,)),
      tf.keras.layers.Dense(200, activation='sigmoid'),
      tf.keras.layers.Dense(60, activation='sigmoid'),
      tf.keras.layers.Dense(10, activation='softmax')
  ])

Veja o "resumo" de seu modelo. Agora, ela tem pelo menos 10 vezes mais parâmetros. Deve ser 10 vezes melhor! Mas, por algum motivo, não é ...

5236f91ba6e07d85.png

A perda parece ter chegado no telhado também. Algo não está certo.

7. Cuidado especial para redes profundas

Você acabou de experimentar redes neurais, como as pessoas costumavam projetá-las nos anos 80 e 90. Não é à toa que eles desistiram da ideia, inaugurando o chamado "inverno da IA". De fato, à medida que você adiciona camadas, as redes neurais têm cada vez mais dificuldade para convergir.

Acontece que redes neurais profundas com muitas camadas (20, 50 e até mesmo 100 hoje) podem funcionar muito bem, com alguns truques matemáticos sujos para fazê-las convergir. A descoberta desses truques simples é uma das razões para o renascimento do aprendizado profundo na década de 2010.

Ativação RELU

relu.png

A função de ativação sigmoide é, na verdade, bastante problemática em redes profundas. Ela comprime todos os valores entre 0 e 1 e, quando você faz isso repetidamente, as saídas de neurônios e seus gradientes podem desaparecer completamente. Isso foi mencionado por motivos históricos, mas as redes modernas usam a Unidade Linear Retificada (RELU, na sigla em inglês), que tem esta aparência:

1abce89f7143a69c.png

O relu, por outro lado, tem uma derivada de 1, pelo menos em seu lado direito. Com a ativação RELU, mesmo que os gradientes provenientes de alguns neurônios possam ser zero, sempre haverá outros oferecendo um gradiente diferente de zero claro, e o treinamento pode continuar em um bom ritmo.

Um otimizador melhor

Em espaços com dimensões muito altas como aqui, temos cerca de 10 mil pesos e vieses, "pontos de dificuldade" são frequentes. Esses são pontos que não são mínimos locais, mas onde o gradiente é zero e o otimizador do gradiente descendente permanece preso ali. O TensorFlow tem uma variedade completa de otimizadores disponíveis, incluindo alguns que funcionam com bastante inércia e vão navegar com segurança além dos pontos de controle.

Inicializações aleatórias

A arte de inicializar os vieses de pesos antes do treinamento é uma área de pesquisa por si só, com vários artigos publicados sobre o assunto. Veja todos os inicializadores disponíveis no Keras aqui. Felizmente, o Keras faz a coisa certa por padrão e usa o inicializador 'glorot_uniform', que é o melhor em quase todos os casos.

Você não precisa fazer nada, porque o Keras já faz a coisa certa.

NaN ???

A fórmula de entropia cruzada envolve um logaritmo e log(0) não é um número (NaN, uma falha numérica, se você preferir). A entrada para a entropia cruzada pode ser 0? A entrada vem da softmax, que é essencialmente uma exponencial, que nunca é zero. Então, estamos bem!

Mesmo? No belo mundo da matemática, estaríamos seguros, mas no mundo da computação, exp(-150), representado no formato float32, é o mesmo que ZERO, e a entropia cruzada falha.

Felizmente, você também não precisa fazer nada aqui, já que o Keras cuida disso e calcula o softmax seguido pela entropia cruzada com bastante cuidado para garantir a estabilidade numérica e evitar os temidos NaNs.

Sucesso?

e1521c9dd936d9bc.png

Agora você terá 97% de precisão. O objetivo deste workshop é ficar significativamente acima de 99%, então vamos continuar.

Se você não souber o que fazer, veja a solução neste momento:

c3df49e90e5a654f.png keras_02_mnist_dense.ipynb

8. Redução da taxa de aprendizado

Talvez possamos tentar treinar mais rápido? A taxa de aprendizado padrão no otimizador Adam é de 0,001. Vamos tentar aumentá-la.

Acelerar não parece ajudar muito, e o que é todo esse barulho?

d4fd66346d7c480e.png

As curvas de treinamento têm muito ruído e analisam as duas curvas de validação: elas saltam para cima e para baixo. Isso significa que estamos indo muito rápido. Poderíamos voltar à velocidade anterior, mas há uma maneira melhor.

desacelerar.png

A melhor solução é começar rápido e diminuir exponencialmente a taxa de aprendizado. No Keras, é possível fazer isso com o callback tf.keras.callbacks.LearningRateScheduler.

Código útil para copiar e colar:

# lr decay function
def lr_decay(epoch):
  return 0.01 * math.pow(0.6, epoch)

# lr schedule callback
lr_decay_callback = tf.keras.callbacks.LearningRateScheduler(lr_decay, verbose=True)

# important to see what you are doing
plot_learning_rate(lr_decay, EPOCHS)

Não se esqueça de usar o lr_decay_callback que você criou. Adicione-o à lista de callbacks em model.fit:

model.fit(...,  callbacks=[plot_training, lr_decay_callback])

O impacto dessa pequena mudança é espetacular. Observe que a maior parte do ruído desapareceu e a precisão do teste agora está acima de 98% de forma contínua.

8c1ae90976c4a0c1.png

9. Dropout, overfitting

Agora o modelo está convergindo bem. Vamos tentar ir ainda mais fundo.

Isso ajudou?

e36c09a3088104c6.png

Na verdade, não. A precisão ainda está presa em 98%. Analise a perda de validação. Está subindo! O algoritmo de aprendizado só trabalha com dados de treinamento e otimiza a perda de treinamento de acordo com isso. Ele nunca vê dados de validação. Por isso, não surpreende que, depois de um tempo, seu trabalho não tenha mais efeito na perda de validação, que para de cair e, às vezes, até mesmo retorna.

Isso não afeta imediatamente os recursos de reconhecimento do seu modelo no mundo real, mas impede que você execute muitas iterações e geralmente é um sinal de que o treinamento não está mais tendo um efeito positivo.

dropout.png

Essa desconexão geralmente é chamada de "overfitting" Quando isso acontecer, tente aplicar uma técnica de regularização chamada "dropout". A técnica de dropout dispara neurônios aleatórios em cada iteração de treinamento.

Funcionou?

43fd33801264743f.png

O ruído reaparece, como o dropout funciona. A perda de validação parece não estar mais aumentando, mas é maior no geral do que sem dropout. E a acurácia da validação diminuiu um pouco. Este é um resultado bastante decepcionante.

Parece que o dropout não era a solução correta, ou talvez houvesse "overfitting" é um conceito mais complexo e algumas de suas causas não são favoráveis a uma "dropout" corrigir?

O que é "overfitting"? O overfitting acontece quando uma rede neural aprende "mal", de uma maneira que funciona para os exemplos de treinamento, mas não tão bem com dados do mundo real. Existem técnicas de regularização, como dropout, que forçam o aprendizado de uma maneira melhor, mas o overfitting também tem raízes mais profundas.

overfitting.png

O overfitting básico acontece quando uma rede neural tem muitos graus de liberdade para o problema em questão. Imagine que temos tantos neurônios que a rede pode armazenar todas as nossas imagens de treinamento neles e reconhecê-las por correspondência de padrões. Ele falharia completamente nos dados reais. Uma rede neural precisa ser um pouco restrita para que seja forçada a generalizar o que aprende durante o treinamento.

Se você tiver poucos dados de treinamento, até mesmo uma rede pequena poderá aprender de forma automática e você verá um "overfitting". De modo geral, você sempre precisa de muitos dados para treinar redes neurais.

Por fim, se você fez tudo conforme o livro, experimentou com diferentes tamanhos de rede para garantir que seus graus de liberdade sejam restritos, aplicados dropout e treinados em muitos dados, você ainda pode ficar preso em um nível de desempenho que parece não ser capaz de melhorar. Isso significa que a rede neural, na forma atual, não é capaz de extrair mais informações dos dados, como neste caso.

Lembra como estamos usando nossas imagens, simplificadas em um único vetor? Foi uma má ideia. Os dígitos escritos à mão são feitos de formas, e descartamos as informações de forma quando achatamos os pixels. No entanto, existe um tipo de rede neural que pode aproveitar as informações de forma: as redes convolucionais. Vamos testá-las.

Se você não souber o que fazer, veja a solução neste momento:

c3df49e90e5a654f.png keras_03_mnist_dense_lrdecay_dropout.ipynb

10. [INFO] redes convolucionais

Resumindo

Se todos os termos em negrito no próximo parágrafo já forem conhecidos, siga para o próximo exercício. Se você está começando a trabalhar com redes neurais convolucionais, continue lendo.

convolutional.gif

Ilustração: filtrar uma imagem com dois filtros sucessivos feitos de 4 x 4 x 3=48 pesos aprendidos cada.

É assim que uma rede neural convolucional simples aparece no Keras:

model = tf.keras.Sequential([
    tf.keras.layers.Reshape(input_shape=(28*28,), target_shape=(28, 28, 1)),
    tf.keras.layers.Conv2D(kernel_size=3, filters=12, activation='relu'),
    tf.keras.layers.Conv2D(kernel_size=6, filters=24, strides=2, activation='relu'),
    tf.keras.layers.Conv2D(kernel_size=6, filters=32, strides=2, activation='relu'),
    tf.keras.layers.Flatten(),
    tf.keras.layers.Dense(10, activation='softmax')
])

688858c21e3beff2.png

Em uma camada de uma rede convolucional, um "neurônio" faz uma soma ponderada dos pixels logo acima dela, em uma pequena região da imagem. Ela adiciona um viés e alimenta a soma por meio de uma função de ativação, assim como um neurônio em uma camada densa regular faria. Essa operação é então repetida em toda a imagem usando os mesmos pesos. Lembre-se de que, nas camadas densas, cada neurônio tinha os próprios pesos. Aqui, um único "patch" de pesos desliza pela imagem em ambas as direções (uma "convolução"). A saída tem tantos valores quanto pixels na imagem, mas é necessário um pouco de padding nas bordas. Trata-se de uma operação de filtragem. Na ilustração acima, ele usa um filtro de pesos 4x4x3=48.

No entanto, 48 pesos não serão suficientes. Para adicionar mais graus de liberdade, repetimos a mesma operação com um novo conjunto de pesos. Isso produz um novo conjunto de saídas de filtro. Vamos chamá-lo de "canal" por analogia com os canais R, G e B na imagem de entrada.

Captura de tela 2016-07-29 às 16.02.37.png

Os dois (ou mais) conjuntos de pesos podem ser somados como um tensor com a adição de uma nova dimensão. Isso nos dá o formato genérico do tensor de pesos para uma camada convolucional. Como o número de canais de entrada e saída são parâmetros, podemos começar a empilhar e encadear camadas convolucionais.

d1b557707bcd1cb9.png

Ilustração: uma rede neural convolucional transforma "cubos" de dados em outros "cubos" de dados.

Convoluções tracejadas, pooling máximo

Ao realizar as convoluções com um passo de 2 ou 3, também podemos reduzir o cubo de dados resultante em suas dimensões horizontais. Há duas maneiras comuns de fazer isso:

  • Convolução em desvio: um filtro deslizante como acima, mas com um passo >1
  • Pool máximo: uma janela deslizante que aplica a operação MAX (normalmente em patches 2x2, repetidos a cada 2 pixels)

2b2d4263bb8470b.gif

Ilustração: deslizar a janela de computação em 3 pixels resulta em menos valores de saída. Convoluções em excesso ou pooling máximo (máximo em uma janela 2x2 deslizando em um salto de 2) são uma maneira de reduzir o cubo de dados nas dimensões horizontais.

A camada final

Após a última camada convolucional, os dados estão na forma de um "cubo". Há duas maneiras de alimentá-lo pela camada densa final.

A primeira é nivelar o cubo de dados em um vetor e, em seguida, alimentar a camada softmax. Às vezes, é possível até adicionar uma camada densa antes da camada softmax. Isso costuma ser caro em termos de número de pesos. Uma camada densa no final de uma rede convolucional pode conter mais da metade dos pesos de toda a rede neural.

Em vez de usar uma camada densa cara, também podemos dividir os dados recebidos em "cubo" em tantas partes quanto tivermos classes, calcular a média de seus valores e alimentá-los por meio de uma função de ativação softmax. Essa maneira de criar o cabeçalho de classificação não custa peso. No Keras, há uma camada para isso: tf.keras.layers.GlobalAveragePooling2D().

a44aa392c7b0e32a.png

Vá para a próxima seção e crie uma rede convolucional para o problema em questão.

11. Uma rede convolucional

Vamos criar uma rede convolucional para reconhecimento de dígitos manuscritos. Usaremos três camadas convolucionais na parte de cima (nossa camada tradicional de leitura de softmax) na parte de baixo e as conectaremos a uma camada totalmente conectada:

e1a214a170957da1.png

Observe que a segunda e a terceira camadas convolucionais têm um salto de dois, o que explica por que elas reduzem o número de valores de saída de 28x28 para 14x14 e depois 7x7.

Vamos escrever o código Keras.

É necessária atenção especial antes da primeira camada convolucional. Na realidade, espera-se um "cubo" 3D de dados, mas nosso conjunto de dados foi configurado até agora para camadas densas e todos os pixels das imagens estão achatados em um vetor. Precisamos remodelá-las para imagens de 28 x 28 x 1 (um canal para imagens em escala de cinza):

tf.keras.layers.Reshape(input_shape=(28*28,), target_shape=(28, 28, 1))

Você pode usar essa linha em vez da camada tf.keras.layers.Input que tinha até agora.

No Keras, a sintaxe de uma camada convolucional ativada por "relu" é:

140f80336b0e653b.png

tf.keras.layers.Conv2D(kernel_size=3, filters=12, padding='same', activation='relu')

Para uma convolução truncada, você escreveria:

tf.keras.layers.Conv2D(kernel_size=6, filters=24, padding='same', activation='relu', strides=2)

Para nivelar um cubo de dados em um vetor para que ele possa ser consumido por uma camada densa:

tf.keras.layers.Flatten()

E para a camada densa, a sintaxe não mudou:

tf.keras.layers.Dense(200, activation='relu')

Seu modelo quebrou a barreira de precisão dos 99%? Quase... mas observe a curva de perda de validação. Isso soa como um alerta?

ecc5972814885226.png

Observe também as previsões. Pela primeira vez, você verá que a maioria dos 10.000 dígitos de teste agora são reconhecidos corretamente. Apenas cerca de 4,5 linhas de detecções incorretas permanecem (cerca de 110 dígitos de 10.000)

37e4cbd3f390c89e.png

Se você não souber o que fazer, veja a solução neste momento:

c3df49e90e5a654f.png keras_04_mnist_convolutional.ipynb

12. Desistência de novo

O treinamento anterior mostra sinais claros de overfitting (e ainda está abaixo de 99% de precisão). Devemos tentar dropout novamente?

Como foi desta vez?

63e0cc982cee2030.png

Parece que o dropout funcionou desta vez. A perda de validação não está mais aumentando, e a precisão final deve estar muito acima de 99%. Parabéns!

Na primeira vez que tentamos aplicar dropout, pensamos que tínhamos um problema de overfitting quando, na verdade, o problema estava na arquitetura da rede neural. Não poderíamos ir mais longe sem as camadas convolucionais, e não há nada que o dropout possa fazer a respeito.

Desta vez, parece que o overfitting foi a causa do problema e o dropout realmente ajudou. Lembre-se de que há muitas coisas que podem causar uma desconexão entre as curvas de perda de treinamento e de validação, com a perda de validação subindo. O overfitting (muitos graus de liberdade, mal usado pela rede) é apenas um deles. Se o conjunto de dados for muito pequeno ou se a arquitetura da rede neural não for adequada, você verá um comportamento semelhante nas curvas de perda, mas o dropout não ajudará.

13. Normalização em lote

oggEbikl2I6_sOo7FlaX2KLdNeaYhJnVSS8GyG8FHXid75PVJX73CRiOynwpMZpLZq6_xAy69wgyez5T-ZlpuC2XSlcmjk7oVcOzefKKTFhTEoLO3kljz2RDyKcaFtHvtTey-I4VpQ

Por fim, vamos adicionar a normalização em lote.

Essa é a teoria. Na prática, lembre-se de algumas regras:

Vamos dar uma olhada no manual por enquanto e adicionar uma camada de norma em lote em cada camada de rede neural, exceto na última. Não a adicione ao último "softmax" camada Isso não seria útil.

# Modify each layer: remove the activation from the layer itself.
# Set use_bias=False since batch norm will play the role of biases.
tf.keras.layers.Conv2D(..., use_bias=False),
# Batch norm goes between the layer and its activation.
# The scale factor can be turned off for Relu activation.
tf.keras.layers.BatchNormalization(scale=False, center=True),
# Finish with the activation.
tf.keras.layers.Activation('relu'),

Como está a precisão agora?

ea48193334c565a1.png

Com alguns ajustes (BATCH_SIZE=64, parâmetro de redução da taxa de aprendizado 0,666, taxa de dropout na camada densa 0,3) e um pouco de sorte, você pode chegar a 99,5%. Os ajustes na taxa de aprendizado e nas desistências foram feitos seguindo as "práticas recomendadas" para usar a norma em lote:

  • A norma em lote ajuda as redes neurais a convergir e geralmente permite o treinamento mais rápido.
  • A norma em lote é um regularizador. Em geral, é possível diminuir a quantidade de dropout usada ou até mesmo não usá-los.

O notebook da solução tem uma execução de treinamento de 99,5%:

c3df49e90e5a654f.png keras_05_mnist_batch_norm.ipynb

14. Treine na nuvem em um hardware avançado: AI Platform

d7d0282e687bdad8.png

Você vai encontrar uma versão do código pronta para a nuvem na pasta mlengine no GitHub, além de instruções para executar esse código na AI Platform do Google Cloud. Antes de executar esta parte, é preciso criar uma conta do Google Cloud e ativar o faturamento. Os recursos necessários para concluir o laboratório devem ser menores que alguns dólares (considerando-se 1h de tempo de treinamento em uma GPU). Para preparar sua conta:

  1. Crie um projeto do Google Cloud Platform ( http://cloud.google.com/console).
  2. Ative o faturamento.
  3. Instale as ferramentas de linha de comando do GCP ( SDK do GCP aqui).
  4. Crie um bucket do Google Cloud Storage (coloque na região us-central1). Ele vai ser usado para organizar o código de treinamento e armazenar o modelo treinado.
  5. Ative as APIs necessárias e solicite as cotas. Execute o comando de treinamento uma vez para receber mensagens de erro informando o que você precisa ativar.

15. Parabéns!

Você criou sua primeira rede neural e a treinou com 99% de precisão. As técnicas aprendidas ao longo do caminho não são específicas do conjunto de dados do MNIST. Na verdade, elas são amplamente utilizadas no trabalho com redes neurais. Como presente de despedida, aqui estão as "notas do aperto" para o laboratório, na versão em desenho animado. Você pode usá-lo para lembrar o que aprendeu:

notas de penhascos tensorflow lab.png

Próximas etapas

  • Depois das redes totalmente conectadas e convolucionais, você deve dar uma olhada nas redes neurais recorrentes.
  • Para executar o treinamento ou a inferência na nuvem em uma infraestrutura distribuída, o Google Cloud oferece o AI Platform.
  • Finalmente, adoramos feedback. Avise nossa equipe se você encontrar algo errado no laboratório ou achar que ele precisa ser melhorado. O feedback é resolvido por problemas do GitHub [link do feedback].

HR.png

ID de Martin Görner pequeno.jpgAutor: Martin GörnerTwitter: @martin_gorner

Todas as imagens de desenhos animados neste laboratório têm direitos autorais: fotos de banco de fotos alexpokusay / 123RF