TensorFlow, Keras e aprendizado profundo sem doutorado

Neste codelab, você aprenderá a criar e treinar uma rede neural que reconhece dígitos escritos à mão. Ao longo do caminho, à medida que você melhora sua rede neural para conseguir 99% de precisão, também encontrará as ferramentas que o profissional de aprendizado profundo usa para treinar modelos com eficiência.

Este codelab usa o conjunto de dados MNIST, uma coleção de 60.000 dígitos rotulados que mantiveram gerações de PhDs ocupadas por quase duas décadas. Você resolverá o problema com menos de cem 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 tf.keras.
  • Como adicionar mais camadas
  • Como configurar uma programação de taxas de aprendizado
  • Como criar redes neurais convolucionais
  • Como usar técnicas de regularização: desistências, normalização em lote
  • O que é overfitting

Pré-requisitos

Apenas um navegador. Este workshop pode ser realizado totalmente com o Google Colaboratory.

Feedback

Entre em contato se você notar algo errado no laboratório ou achar que isso precisa ser melhorado. Nós processamos os comentários por meio de problemas no GitHub [link do feedback].

Este laboratório usa o Google Colaboratory e não requer nenhuma configuração da sua parte. Você pode executá-lo em um Chromebook. Abra o arquivo abaixo e execute as células para se familiarizar com os notebooks do Colab.

Welcome to Colab.ipynb

Veja mais instruções abaixo:

Selecione um back-end de GPU

No menu do Colab, selecione Runtime > Change runtime type e selecione GPU. A conexão com o ambiente de execução acontecerá automaticamente na primeira execução ou você pode usar o botão "quot;Connect" no canto superior direito.

Execução de notebook

Execute uma célula de cada vez clicando em uma célula e pressionando Shift-ENTER. Também é possível executar o notebook inteiro com Runtime > Run all.

Table of contents

Todos os notebooks têm um índice. Você pode abri-lo usando a seta preta à esquerda.

Células ocultas

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

Vamos analisar primeiro um trem de rede neural. Abra o notebook abaixo e execute todas as células. Não dê atenção ao código ainda, porque ele será explicado mais tarde.

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 escritos à mão que foram rotulados para saber o que cada imagem representa, ou seja, um número entre 0 e 9. No notebook, você verá um trecho:

A rede neural que vamos classificar classifica os dígitos escritos à mão nas 10 classes (0, .., 9). Ele faz isso com base em parâmetros internos que precisam ter um valor correto para que a classificação funcione bem. Esse "valor correto" é aprendido através 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 pode ser uma trapaça. Ele já viu esse conjunto de dados várias vezes durante o treinamento e certamente tem um bom desempenho nele. Precisamos de outro conjunto de dados rotulado, nunca visto durante o treinamento, para avaliar o desempenho da rede no mundo real. Ele é chamado de "conjunto de dados de validação"

Treinamento

À medida que o treinamento progride, um lote de dados de treinamento por vez, os parâmetros do modelo interno são atualizados e o modelo fica cada vez melhor no reconhecimento dos dígitos escritos à mão. É possível vê-lo no gráfico de treinamento:

À direita, "acurácia" é simplesmente a porcentagem de dígitos reconhecidos corretamente. O recurso aumenta conforme o treinamento avança.

À esquerda, vemos a "loss". Para orientar o treinamento, definiremos uma função "loss" que representa o quanto o sistema reconhece os dígitos e tentará minimizá-lo. Você verá que a perda diminui no treinamento e nos dados de validação durante o treinamento. 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 dela em alguns dígitos renderizados em fontes locais (primeira linha) e, depois, nos 10 mil dígitos do conjunto de dados de validação. A classe prevista aparece abaixo de cada dígito, em vermelho, se estiver incorreta.

Como você pode ver, esse modelo inicial não é muito bom, mas ainda reconhece alguns dígitos corretamente. A precisão final de validação é de cerca de 90%, o que não é tão ruim para o modelo simplista que estamos começando,mas ainda significa que faltam 1.000 dígitos de validação entre os 10.000. É muito mais isso que pode ser exibido, e é por isso 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. Há três valores de cor por pixel (vermelho, verde, azul). Sendo assim, uma tabela tridimensional será necessária com as dimensões [28, 28, 3]. Para armazenar um lote de 128 imagens coloridas, é necessária uma tabela tridimensional com as dimensões [128, 28, 28, 3].

Essas tabelas multidimensionais são chamadas de "tensores" e a lista de suas dimensões é sua "shape".

Resumindo

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

bruxa.png

Para modelos criados como uma sequência de camadas, a Keras oferece a API Sequential. Por exemplo, um classificador de imagem 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, ... )

Camada única 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 os 28 x 28=784 pixels como entradas para uma rede neural de uma camada.

Captura de tela 2016-07-26 às 12.32.24.png

Cada "neuron" em uma rede neural faz uma soma ponderada de todas as entradas, adiciona uma constante chamada "viés" e alimenta o resultado por meio de uma "função de ativação" não linear. Os atributos "weights" e "biases" são parâmetros que serão determinados com o treinamento. Inicialmente, eles são inicializados com valores aleatórios.

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

Com uma multiplicação de matriz

Veja como uma camada de rede neural, que processa uma coleção de imagens, pode ser representada por uma multiplicação de matriz:

matmul.gif

Usando a primeira coluna de ponderações 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 neurônio. Em seguida, podemos repetir a operação para as 99 imagens restantes. Se chamarmos X a matriz contendo as 100 imagens, todas as somas ponderadas dos 10 neurônios, calculadas com base nas 100 imagens, serão simplesmente X.W, uma multiplicação de matrizes.

Agora, cada neurônio precisa adicionar o viés (constante). Como temos 10 neurônios, temos 10 constantes de viés. Chamaremos esse vetor de 10 valores b. Ela precisa ser adicionada a cada linha da matriz calculada anteriormente. Usando um pouco de magia chamada "broadcasting"isso será escrito com um simples sinal de mais.

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

Captura de tela 2016-07-26 às 16.02.36.png

Na Keras

Com bibliotecas de rede neural de alto nível, como a Keras, não é necessário implementar essa fórmula. No entanto, é importante entender que uma camada de rede neural é apenas um monte de multiplicações e adições. Na 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 as somas ponderadas de pixels. As camadas subsequentes calculam as somas ponderadas das saídas das camadas anteriores.

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 de ativação "relu" para todas as camadas, exceto para a última. A última camada em um classificador usaria a ativação "softmax"

Novamente, um "neuron" calcula uma soma ponderada de todas as entradas, adiciona um valor chamado "bias" e alimenta o resultado por meio da função de ativação.

A função de ativação mais conhecida é chamada "RELU" para a unidade linear retificada. Essa é uma função muito simples, como se vê no gráfico acima.

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

Ativação Softmax para classificação

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

Para aplicar o softmax em um vetor, pegue a exponencial de cada elemento e, em seguida, normalize o vetor, normalmente dividindo-o pela norma "L1" (isto é, a soma de valores absolutos) para que os valores normalizados somem um 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:

Perda de entropia cruzada

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

Qualquer distância pode funcionar, mas para problemas de classificação ela é chamada de "distância de entropia cruzada" e é a mais eficaz. Chamaremos isso de erro ou função "perda:

Gradiente descendente

"Treinamento": a rede neural significa usar imagens e rótulos de treinamento para ajustar pesos e vieses com o objetivo de minimizar a função de perda de entropia cruzada. Veja como funciona.

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

Se calcularmos os derivados parciais da entropia cruzada em relação a todos os pesos e todas as vieses, obtemos um "gradiente', calculado para uma dada imagem, um rótulo e um valor presente de pesos e vieses. Lembre-se de que podemos ter milhões de pesos e vieses para que calcular o gradiente funcione. Felizmente, o TensorFlow faz isso por nós. A propriedade matemática de um gradiente é que ele aponta para "quot;up&quot". Como queremos ir onde a entropia cruzada é baixa, vamos na direção oposta. Atualizamos pesos e vieses em uma fração do gradiente. Fazemos a mesma coisa várias vezes usando os próximos lotes de imagens e rótulos de treinamento em um loop de treinamento. Esperamos que isso converja para um lugar em que a entropia cruzada seja mínima, embora nada garanta que esse mínimo seja único.

gradiente descendente des2.png

Menos lotes e recursos

É possível calcular o gradiente em apenas uma imagem de exemplo e atualizar os pesos e vieses imediatamente. No entanto, fazer isso em um lote de 128 imagens, por exemplo, fornece um gradiente que representa melhor as restrições impostas por imagens de exemplo diferentes. Por isso, é provável que haja uma convergência mais rápida com a solução. O tamanho do minilote é um parâmetro ajustável.

Essa técnica, às vezes chamada de "estocástico gradiente

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

Ilustração: um ponto simples. O gradiente é 0, mas não é um valor mínimo em todas as direções. (Atribuição de imagem Wikimedia: Por Nicoguaro - Own trabalho, CC BY 3.0)

A solução é fazer com que o algoritmo de otimização navegue pelos pontos de sela sem parar.

Glossário

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

perda de entropia cruzada: uma função de perda especial geralmente usada em classificadores.

camada densa: uma camada de neurônios em que cada neurônio é conectado a todos os neurônios na camada anterior.

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

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

taxa de aprendizado: fração do gradiente pela qual os pesos e vieses são atualizados em cada iteração da repetição do treinamento.

logits: os resultados de uma camada de neurônios antes da aplicação da função de ativação são chamados de "logits". O termo vem da "função logística", também conhecida como função "sigmoide", que era a função de ativação mais conhecida. "Saídas de neurônio antes da função logística" foi reduzida para "logits".

loss: a função de erro comparando as saídas da rede neural às respostas corretas

neuron: calcula a soma ponderada das entradas, adiciona um viés e alimenta o resultado por meio de uma função de ativação.

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

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

sigmoid: outra função de ativação que era muito usada 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 para que possa ser interpretado como um vetor de probabilidades. Usado como última etapa nos 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 2 dimensões é uma matriz. Você pode ter tensores com 3, 4, 5 ou mais dimensões.

De volta ao notebook de estudo e, desta vez, vamos ler o código.

keras_01_mnist.ipynb

Vamos percorrer toda a célula neste notebook.

Parâmetros de "célula;

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

Células "Imports"

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

Utilitários de visualização de células;[RUN ME]"

Esta célula contém um código de visualização não interessante. Ela é recolhida por padrão, mas você pode abri-la e ver o código quando tiver tempo clicando duas vezes nele.

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

Essa 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, veja um tutorial que a explica: Pipelines de dados de velocidade da TPU. Por enquanto, o básico é:

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 com bytes de imagem. Eles precisam ser decodificados em imagens. Definimos uma função para fazer isso. A imagem não é compactada, portanto, a função não precisa decodificar nada. decode_raw basicamente não faz nada. A imagem é então convertida em valores de ponto flutuante entre 0 e 1. Poderíamos reformulá-la aqui como uma imagem 2D, mas, na verdade, a mantemos como uma matriz plana de pixels de tamanho 28*28, porque é isso que a camada densa inicial espera.

def read_image(tf_bytestring):
    image = tf.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 conseguimos 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 .zip e usamos 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 toda a função de utilitário necessária para preparar conjuntos de dados:

.cache armazena em cache o conjunto de dados na RAM. Este é um conjunto de dados pequeno para que funcione. A .shuffle a embaralha com um buffer de 5.000 elementos. É importante que os dados de treinamento sejam organizados aleatoriamente. .repeat repete o conjunto de dados. Vamos treiná-lo várias vezes (várias épocas). O .batch extrai várias imagens e rótulos em um mininome. Por fim, .prefetch pode usar a CPU para preparar o próximo lote enquanto o lote 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.

Modelo de célula e "

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. Ele tem 10 neurônios, porque estamos classificando dígitos escritos à mão em 10 classes. Ela usa a ativação "softmax" porque é a última camada em um classificador.

Um modelo 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*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' na Keras. Por fim, pedimos que o modelo calcule a métrica 'accuracy', que é a porcentagem de imagens classificadas corretamente.

A Keras oferece o utilitário model.summary() muito útil que exibe os detalhes do modelo que você criou. O professor do tipo adicionou o utilitário PlotTraining (definido na célula "visualization utilities" ), que exibirá 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, a Keras executa uma rodada de validação no 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])

Na Keras, é possível adicionar comportamentos personalizados durante o treinamento usando callbacks. 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 receber 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 a partir de fontes locais como teste. Lembre-se de que a rede neural retorna um vetor de 10 probabilidades a partir do último "softmax". Para obter o rótulo, precisamos descobrir qual probabilidade é a mais alta. A np.argmax da biblioteca numpy faz isso.

Para entender por que o parâmetro axis=1 é necessário, processamos um lote de 128 imagens. Portanto, o modelo retorna 128 vetores de probabilidades. A forma 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).

Este modelo simples já reconhece 90% dos dígitos. Nada mau, mas você melhorará isso significativamente.

godeep.png

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

Captura de tela 2016-07-27 às 15.36.55.png

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

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

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" do seu modelo. Agora, ele tem pelo menos 10 vezes mais parâmetros. Deve ser 10 vezes melhor! Mas, por algum motivo, não é ...

A perda parece ter caído também pelo teto. Ocorreu um erro.

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

Acontece que as redes neurais profundas com muitas camadas (20, 50 e até mesmo 100 atualmente) funcionam bem, desde que sejam usados alguns truques de matemática para provocar a convergência. A descoberta desses truques simples é uma das razões do renascimento do aprendizado profundo nos anos 2010.

Ativação RELU

Relu.png

A função de ativação sigmoide é, na verdade, bastante problemática em redes profundas. Ele apaga todos os valores entre 0 e 1 e, quando você faz isso repetidamente, as saídas dos neurônios e os gradientes podem desaparecer totalmente. Ele foi mencionado por motivos históricos, mas as redes modernas usam a unidade linear retificada (RELU, na sigla em inglês), que tem a seguinte aparência:

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

Um otimizador melhor

Em espaços de alta dimensão, como este, temos cerca de 10 mil pesos e vieses. Eles são frequentes. Esses são pontos que não são minimas locais, mas em que o gradiente é zero e o otimizador de gradiente descendente permanece lá. O TensorFlow tem uma série completa de otimizadores disponíveis, incluindo alguns que funcionam com uma determinada quantidade de inércia e navegarão com segurança por pontos de sela.

Inicialização aleatória

A arte de inicializar pesos de peso antes do treinamento é uma área de pesquisa, com vários artigos publicados sobre o tema. Veja aqui todos os inicializadores disponíveis no Keras. 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, já que a 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 de softmax, que é essencialmente exponencial e nunca exponencial. Por isso, estamos seguros.

Mesmo? No belo mundo da matemática, estávamos seguros, mas no mundo da computação, exp(-150), representado no formato float32, é igual a ZERO e chega a entropia cruzada.

Felizmente, você não precisa fazer nada aqui, já que o Keras cuida disso e calcula o softmax seguido pela entropia cruzada de uma maneira especialmente cuidadosa para garantir estabilidade numérica e evitar os NaNs temidos.

Tudo bem?

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

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

keras_02_mnist_dense.ipynb

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

Agilizar o dia parece não ajudar muito, e o que é tudo isso?

As curvas de treinamento têm muita barulho e observe as duas curvas de validação: elas pulam para cima e para baixo. Isso significa que estamos indo rápido demais. Podemos voltar à velocidade anterior, mas existe uma maneira melhor.

Desacelerar.png

A boa solução é começar rápido e degradar a taxa de aprendizado exponencialmente. Na 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. Você vê que a maior parte do ruído desapareceu e a precisão do teste agora está acima de 98% de maneira sustentável.

O modelo parece estar convergindo corretamente. Vamos nos aprofundar mais.

Isso ajuda?

Na verdade, a precisão ainda está paralisada em 98% e analisa a perda de validação. Está subindo! O algoritmo de aprendizado funciona apenas com dados de treinamento e otimiza a perda de treinamento adequadamente. Eles nunca veem dados de validação. Por isso, não é de se surpreender que, após algum tempo, o trabalho deles não tenha mais efeito na perda de validação que interrompe o processo de queda e, às vezes, até retorna.

Isso não afeta imediatamente os recursos reais de reconhecimento do modelo, mas impede a execução de muitas iterações e, geralmente, é um sinal de que o treinamento não tem mais um efeito positivo.

dropout.png.

Essa desconexão geralmente é chamada de "overfitting" e, quando você o vê, pode tentar aplicar uma técnica de regularização chamada "dropout". A técnica de desistência descarta os neurônios aleatórios em cada iteração de treinamento.

Funcionou?

O ruído reaparece (sem surpreendentemente, como funciona a desistência). Parece que a perda de validação não está aumentando, mas é mais geral do que sem perda. E a precisão da validação diminuiu um pouco. Esse é um resultado decepcionante.

Parece que a saída não era a solução certa, ou talvez "quotfitting" seja um conceito mais complexo, e algumas das causas dele não são compatíveis com uma "dropout" correção.

O que significa &fitting"? 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 reais. Existem técnicas de regularização como o dropout, que podem forçá-lo a aprender de uma maneira melhor, mas o overfitting também tem raízes mais profundas.

overfitting.png

O overfitting básico ocorre 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 nelas e, em seguida, reconhecê-las por correspondência de padrões. Ocorreria uma falha completamente em dados reais. Uma rede neural precisa ser restrita de forma que seja forçada a generalizar o que aprende durante o treinamento.

Se você tem poucos dados de treinamento, até mesmo uma pequena rede pode aprender com frequência. Nesse caso, você verá "overfitting". De modo geral, você sempre precisa de muitos dados para treinar redes neurais.

Por fim, se você já fez tudo por livro, experimentou diferentes tamanhos de rede para garantir que os graus de liberdade sejam aplicados, desistências aplicadas e treinados com muitos dados. Talvez você ainda esteja em um nível de desempenho que parece não conseguir melhorar. Isso significa que sua rede neural, na forma atual, não consegue extrair mais informações dos seus dados, como nesse caso.

Lembra como estamos usando nossas imagens, separadas em um único vetor? Essa foi uma ideia muito ruim. Dígitos escritos à mão são formados e descartamos as informações da forma quando simplificamos os pixels. No entanto, há um tipo de rede neural que pode aproveitar as informações de forma: redes convolucionais. Vamos testá-las.

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

keras_03_mnist_dense_lrdecay_dropout.ipynb

Resumindo

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

convolutional.gif

Ilustração: filtro de uma imagem com dois filtros sucessivos, criados com pesos de aprendizado 4x4x3=48 cada.

Uma rede neural convolucional simples tem a seguinte aparência na 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')
])

Em uma camada de uma rede convolucional, uma "neuron" faz uma soma ponderada dos pixels logo acima dela em uma pequena região da imagem. Ele 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 normal. Essa operaçã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 "quot;patch" de pesos desliza sobre a imagem em ambas as direções (uma "convolution"). A saída tem a mesma quantidade de valores que a imagem tem pixels (mas alguns padding são necessários nas bordas). É uma operação de filtragem. Na ilustração acima, ele usa um filtro de pesos 4 x 4 x 38.

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 do filtro. Vamos chamá-lo de "canal" de saídas por analogia com os canais R,G,B na imagem de entrada.

Screen Shot 2016-07-29 at 16.02.37.png

Os dois (ou mais) conjuntos de pesos podem ser somados como um tensor adicionando uma nova dimensão. Com isso, podemos determinar o formato genérico do tensor de peso para uma camada convolucional. Como o número de canais de entrada e saída são parâmetros, podemos começar o empilhamento e o encadeamento das camadas convolucionais.

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

Convoluções espalhadas, pool máximo

Realizar as convoluções com um stride de 2 ou 3 também pode reduzir o cubo de dados resultante nas dimensões horizontais. Há duas maneiras de fazer isso:

  • convolução escalonada: um filtro deslizante conforme acima, mas com um giro; 1
  • Pool máximo: uma janela deslizante que aplica a operação MAX (normalmente em patches 2x2, repetidas a cada 2 pixels).

Ilustração: deslizar a janela de computação em três pixels resulta em menos valores de saída. As convoluções espalhadas ou o pooling máximo (máximo em uma janela de 2 x 2 deslizando por 2) são uma forma de reduzir o cubo de dados nas dimensões horizontais.

Camada final

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

A primeira é nivelar o cubo de dados em um vetor e, em seguida, alimentá-lo na camada softmax. Às vezes, é possível até mesmo adicionar uma camada densa antes da camada softmax. Isso tende a 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 e cara, podemos dividir os dados recebidos em um número tão grande quanto a classe, calcular os valores médios deles e fornecê-los em uma função de ativação softmax. Essa maneira de criar o cabeça de classificação custa 0 peso. Na Keras, há uma camada para isso: tf.keras.layers.GlobalAveragePooling2D().

Vá até a próxima seção para criar uma rede convolucional para o problema em questão.

Vamos criar uma rede convolucional para reconhecimento de dígitos escritos à mão. Usaremos três camadas convolucionais na parte superior, nossa camada de leitura softmax tradicional na parte inferior, e conectá-las a uma camada totalmente conectada:

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

Vamos escrever o código da Keras.

É necessária atenção especial antes da primeira camada convolucional. Na verdade, ele espera um "cubo" de 3D; mas nosso conjunto de dados até agora foi configurado para camadas densas e todos os pixels das imagens são achatados em um vetor. Precisamos reformulá-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.

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

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

Para uma convolução diferenciada, você precisa escrever:

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 quebrau a barreira da precisão de 99%? Quase... mas observe a curva de perda de validação. Isso toca um sino?

Veja também as previsões. Pela primeira vez, você verá que a maioria dos 10 mil dígitos de teste foi reconhecida corretamente. Restam apenas cerca de 41⁄2 linhas de erros de detecção (cerca de 110 dígitos de 10 mil).

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

keras_04_mnist_convolutional.ipynb

O treinamento anterior mostra sinais claros de overfitting e ainda fica abaixo de 99% de precisão. Vamos tentar desistir de novo?

Como foi a situação?

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

Na primeira vez que tentamos aplicar dropout, pensávamos 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 camadas convolucionais, e não há nada que possa ser resolvido com isso.

Desta vez, parece que o overfitting foi a causa do problema, e a desistência acabou de ajudar. Lembre-se, há muitos fatores que causam uma desconexão entre as curvas de perda de treinamento e de validação, com o aumento da perda de validação. O overfitting (muitos graus de liberdade, usado incorretamente pela rede) é apenas um deles. Se o conjunto de dados for muito pequeno ou a arquitetura da rede neural não for adequada, você poderá ver um comportamento semelhante nas curvas de perda, mas a desistência não ajudará.

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

Essa é a teoria, na prática, basta lembrar de algumas regras:

Por enquanto, vamos jogar no livro e adicionar uma camada de norma em lote em cada camada da rede neural, mas a última. Não a adicione à última camada "softmax". Ele 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?

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

  • A norma em lote ajuda as redes neurais a convergirem e, geralmente, permite que você treine mais rápido.
  • A norma em lote é um regularizador. Em geral, é possível reduzir a quantidade de desistências, ou até mesmo não usá-las.

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

keras_05_mnist_batch_norm.ipynb

Você encontrará uma versão do código pronta para nuvem na pasta mlengine no GitHub e instruções para executá-la na Google Cloud AI Platform. Antes de executar essa parte, você precisará criar uma conta do Google Cloud e ativar o faturamento. Os recursos necessários para concluir o laboratório precisam ser de menos de dois dólares, supondo que seja uma hora de treinamento em uma GPU. Para preparar sua conta, faça o seguinte:

  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 na região us-central1. Ele será usado para organizar o código de treinamento e armazenar o modelo treinado.
  5. Ative as APIs necessárias e solicite as cotas necessárias. Execute o comando de treinamento uma vez e você receberá mensagens de erro informando o que ativar.

Você criou sua primeira rede neural e a treinou até 99%. As técnicas aprendidas ao longo do caminho não são específicas ao conjunto de dados do MNIST. Na verdade, elas são amplamente usadas ao trabalhar com redes neurais. Como um presente de festa, aqui está o cartão de "notas" do laboratório, em versão de desenho animado. Você pode usá-la para lembrar o que aprendeu:

penhascos notas tensorflow lab.png

Próximas etapas

  • Depois de se conectar completamente e convolucionalmente, confira as redes neurais recorrentes.
  • Para executar seu treinamento ou inferência na nuvem em uma infraestrutura distribuída, o Google Cloud fornece AI Platform.
  • Por fim, adoramos feedbacks. Entre em contato se você notar algo errado no laboratório ou achar que isso precisa ser melhorado. Nós processamos os comentários por meio de problemas no GitHub [link do feedback].

RH.png

ID de Martin Görner small.jpg

O autor: Martin Görner

Twitter: @martin_gorner

Todas as imagens de desenhos animados deste laboratório são: alexpokusay / 123RF foto