Convnets modernas, squeezenet, Xception, com Keras e TPUs

1. Visão geral

Neste laboratório, você vai aprender sobre a arquitetura convolucional moderna e usar seu conhecimento para implementar uma convnet simples e eficaz chamada "squeezenet".

Este laboratório inclui as explicações teóricas necessárias sobre redes neurais convolucionais e é um bom ponto de partida para desenvolvedores que estão aprendendo sobre aprendizado profundo.

Este laboratório é a parte 4 da série "Keras na TPU". Você pode fazer isso na ordem a seguir ou de forma independente.

ca8cc21f6838eccc.png

O que você vai aprender

  • Para dominar o estilo funcional do Keras
  • Para criar um modelo usando a arquitetura de squeezenet
  • usar TPUs para treinar rapidamente e iterar sua arquitetura;
  • Implementar o aumento de dados com o tf.data.dataset
  • Ajustar um modelo grande pré-treinado (Xception) na TPU

Feedback

Se você encontrar algo de errado nesse codelab, informe-nos. O feedback pode ser enviado pela página de problemas do GitHub [link do feedback].

2. Introdução ao Google Colaboratory

Este laboratório usa o Google Collaboratory, e você não precisa configurar nada. O Colaboratory é uma plataforma de cadernos on-line para fins educacionais. Ele oferece treinamento sem custo financeiro de CPU, GPU e TPU.

688858c21e3beff2.png

Abra este notebook de exemplo e execute algumas células para se familiarizar com o Colaboratory.

c3df49e90e5a654f.png Welcome to Colab.ipynb

Selecionar um back-end de TPU

8832c6208c99687d.png

No menu do Colab, selecione Ambiente de execução > Alterar tipo de ambiente de execução e selecione TPU. Neste codelab, você usará uma TPU (Unidade de Processamento de Tensor) poderosa com suporte para treinamento acelerado por hardware. A conexão com o ambiente de execução vai acontecer automaticamente na primeira execução. Você também pode usar o botão "Conectar" no canto superior direito.

Execução de notebooks

76d05caa8b4db6da.png

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

429f106990037ec4.png

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

Células ocultas

edc3dba45d26f12a.png

Algumas células só mostram o título. Esse é um recurso específico do Colab. Você pode clicar duas vezes neles para ver o código, mas ele geralmente 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 sejam definidas.

Authentication

cdd4b41413100543.png

O Colab pode acessar seus buckets particulares do Google Cloud Storage se você fizer a autenticação com uma conta autorizada. O snippet de código acima aciona um processo de autenticação.

3. [INFO] O que são as Unidades de Processamento de Tensor (TPUs)?

Resumindo

f88cf6facfc70166.png

O código para treinar um modelo em TPU no Keras (e usar a GPU ou CPU se uma TPU não estiver disponível):

try: # detect TPUs
    tpu = tf.distribute.cluster_resolver.TPUClusterResolver.connect()
    strategy = tf.distribute.TPUStrategy(tpu)
except ValueError: # detect GPUs
    strategy = tf.distribute.MirroredStrategy() # for CPU/GPU or multi-GPU machines

# use TPUStrategy scope to define model
with strategy.scope():
  model = tf.keras.Sequential( ... )
  model.compile( ... )

# train model normally on a tf.data.Dataset
model.fit(training_dataset, epochs=EPOCHS, steps_per_epoch=...)

Vamos usar TPUs hoje para criar e otimizar um classificador de flores em velocidades interativas (minutos por execução de treinamento).

688858c21e3beff2.png

Por que usar TPUs?

As GPUs modernas são organizadas em "núcleos" programáveis, uma arquitetura muito flexível que permite processar várias tarefas, como renderização 3D, aprendizado profundo, simulações físicas etc. Por outro lado, as TPUs combinam um processador vetorial clássico com uma unidade de multiplicação de matriz dedicada e se destacam em qualquer tarefa em que as multiplicações de matrizes grandes dominam, como redes neurais.

8eb3e718b8e2ed08.png

Ilustração: uma camada de rede neural densa como uma multiplicação de matrizes, com um lote de oito imagens processadas pela rede neural de uma só vez. Execute uma multiplicação de linha x coluna para verificar se ela está realmente fazendo uma soma ponderada de todos os valores de pixels de uma imagem. Também é possível representar camadas convolucionais como multiplicações de matrizes, embora isso seja um pouco mais complicado ( explicação aqui, na seção 1).

O hardware

MXU e VPU

Um núcleo de TPU v2 é composto por uma unidade de multiplicação de matriz (MXU, na sigla em inglês), que executa multiplicações de matriz, e uma unidade de processamento vetorial (VPU, na sigla em inglês) para todas as outras tarefas, como ativações, softmax etc. A VPU processa cálculos de float32 e int32. O MXU, por outro lado, opera em um formato de ponto flutuante de precisão mista de 16 a 32 bits.

7d68944718f76b18.png

Ponto flutuante de precisão mista e bfloat16

O MXU calcula multiplicações de matrizes usando entradas bfloat16 e saídas float32. Acumulações intermediárias são realizadas com precisão de float32.

19c5fc432840c714.png

O treinamento de rede neural costuma ser resistente ao ruído introduzido por uma precisão de ponto flutuante reduzida. Há casos em que o ruído até ajuda a convergência do otimizador. A precisão de ponto flutuante de 16 bits é tradicionalmente usada para acelerar as computações, mas os formatos float16 e float32 têm intervalos muito diferentes. Reduzir a precisão de float32 para float16 geralmente resulta em fluxos excessivos e insuficientes. Existem soluções, mas é necessário um trabalho adicional para que o float16 funcione.

É por isso que o Google introduziu o formato bfloat16 em TPUs. bfloat16 é um float32 truncado com exatamente os mesmos bits e intervalo expoentes que float32. Isso, somado ao fato de que as TPUs calculam multiplicações de matriz com precisão mista com entradas bfloat16, mas saídas float32, significa que, normalmente, nenhuma mudança de código é necessária para se beneficiar dos ganhos de desempenho da precisão reduzida.

Matriz systolic

O MXU implementa multiplicações de matrizes no hardware usando uma arquitetura chamada de "matriz sistólica", na qual os elementos de dados fluem por uma matriz de unidades de computação de hardware. (Na medicina, "sistêmico" se refere às contrações cardíacas e ao fluxo sanguíneo, e aqui se refere ao fluxo de dados.)

O elemento básico de uma multiplicação de matrizes é um produto escalar entre uma linha de uma matriz e uma coluna da outra matriz (consulte a ilustração na parte de cima desta seção). No caso da multiplicação de matrizes Y=X*W, um elemento do resultado seria:

Y[2,0] = X[2,0]*W[0,0] + X[2,1]*W[1,0] + X[2,2]*W[2,0] + ... + X[2,n]*W[n,0]

Em uma GPU, esse produto escalar seria programado em um "core" da GPU e executado em tantos "cores" disponíveis em paralelo para tentar calcular todos os valores da matriz resultante de uma só vez. Se a matriz resultante tiver 128 x 128, será necessário que 128 x 128 = 16K "cores" estejam disponíveis, o que normalmente não é possível. As maiores GPUs têm cerca de 4.000 núcleos. Por outro lado, uma TPU usa o mínimo de hardware para as unidades de computação na MXU: apenas acumuladores de multiplicação bfloat16 x bfloat16 => float32, nada mais. Eles são tão pequenos que uma TPU pode implementar 16 K deles em um MXU de 128 x 128 e processar essa multiplicação de matrizes de uma vez.

f1b283fc45966717.gif

Ilustração: a matriz sistólica da MXU. Os elementos de computação são acumuladores de multiplicação. Os valores de uma matriz são carregados nela (pontos vermelhos). Os valores da outra matriz fluem pela matriz (pontos cinza). As linhas verticais propagam os valores para cima. As linhas horizontais propagam somas parciais. Como exercício, o usuário precisa verificar se, à medida que os dados fluem pela matriz, o resultado da multiplicação de matrizes é obtido do lado direito.

Além disso, enquanto os produtos pontuais são calculados em um MXU, as somas intermediárias simplesmente fluem entre as unidades de computação adjacentes. Eles não precisam ser armazenados e recuperados da/da memória ou mesmo de um arquivo de registro. O resultado final é que a arquitetura de matriz sistólico da TPU tem uma vantagem significativa de densidade e potência, além de uma vantagem de velocidade não desprezível em relação a uma GPU, ao calcular multiplicações de matriz.

Cloud TPU

Quando você solicita um Cloud TPU v2 no Google Cloud Platform, recebe uma máquina virtual (VM) com uma placa TPU conectada por PCI. A placa de TPU tem quatro chips de TPU de dois núcleos. Cada núcleo de TPU tem uma unidade de processamento vetorial (VPU) e uma unidade de multiplicação de matrizes (MXU) de 128 x 128. Esse "Cloud TPU" geralmente é conectado por meio da rede à VM que o solicitou. O quadro completo fica assim:

dfce5522ed644ece.png

Ilustração: sua VM com um acelerador "Cloud TPU" conectado à rede. O "Cloud TPU" é composto por uma VM com uma placa TPU conectada a PCI com quatro chips de TPU de núcleo duplo.

Pods TPU

Nos data centers do Google, as TPUs são conectadas a uma interconexão de computação de alto desempenho (HPC, na sigla em inglês), o que pode fazer com que elas apareçam como um acelerador muito grande. O Google os chama de pods, e eles podem abranger até 512 núcleos de TPU v2 ou 2.048 núcleos de TPU v3.

2ec1e0d341e7fc34.jpeg

Ilustração: um pod da TPU v3. Placas e racks de TPU conectados por interconexão de HPC.

Durante o treinamento, os gradientes são trocados entre os núcleos da TPU usando o algoritmo de redução total. Confira uma boa explicação sobre a redução total aqui (link em inglês). O modelo que está sendo treinado pode aproveitar o hardware ao treinar em grandes tamanhos de lote.

d97b9cc5d40fdb1d.gif

Ilustração: sincronização de gradientes durante o treinamento usando o algoritmo de redução total na rede HPC de malha toroidal 2D do Google TPU.

O software

Treinamento de lote grande

O tamanho de lote ideal para TPUs é de 128 itens de dados por núcleo de TPU, mas o hardware já mostra uma boa utilização de 8 itens de dados por núcleo de TPU. Lembre-se de que uma Cloud TPU tem 8 núcleos.

Neste codelab, vamos usar a API Keras. No Keras, o lote especificado é o tamanho global do lote de toda a TPU. Seus lotes serão divididos automaticamente em oito e executados nos oito núcleos da TPU.

da534407825f01e3.png

Para mais dicas de performance, consulte o Guia de performance de TPU. Para tamanhos de lote muito grandes, pode ser necessário um cuidado especial em alguns modelos. Consulte LARSOptimizer para mais detalhes.

Em segundo plano: XLA

Os programas do TensorFlow definem grafos de computação. A TPU não executa diretamente o código Python, mas sim o gráfico de computação definido pelo programa do TensorFlow. Por trás, um compilador chamado XLA (álgebra linear acelerada) transforma o gráfico do TensorFlow de nós de computação em código de máquina TPU. Ele também executa muitas otimizações avançadas no código e no layout da memória. A compilação acontece automaticamente quando o trabalho é enviado para a TPU. Você não precisa incluir o XLA explicitamente na sua cadeia de compilação.

edce61112cd57972.png

Ilustração: para ser executado em uma TPU, o gráfico de computação definido pelo programa Tensorflow é primeiro convertido em uma representação do XLA (acelerador de álgebra linear) e depois compilado pelo XLA no código de máquina da TPU.

Como usar TPUs no Keras

As TPUs têm suporte na API Keras a partir do TensorFlow 2.1. O suporte a Keras funciona em TPUs e pods da TPU. Confira um exemplo que funciona em TPU, GPU e CPU:

try: # detect TPUs
    tpu = tf.distribute.cluster_resolver.TPUClusterResolver.connect()
    strategy = tf.distribute.TPUStrategy(tpu)
except ValueError: # detect GPUs
    strategy = tf.distribute.MirroredStrategy() # for CPU/GPU or multi-GPU machines

# use TPUStrategy scope to define model
with strategy.scope():
  model = tf.keras.Sequential( ... )
  model.compile( ... )

# train model normally on a tf.data.Dataset
model.fit(training_dataset, epochs=EPOCHS, steps_per_epoch=...)

Neste snippet de código:

  • TPUClusterResolver().connect() encontra a TPU na rede. Ele funciona sem parâmetros na maioria dos sistemas do Google Cloud (jobs do AI Platform, Colaboratory, Kubeflow, VMs de aprendizado profundo criadas pelo utilitário "ctpu up"). Esses sistemas sabem onde a TPU está graças a uma variável de ambiente TPU_NAME. Se você criar uma TPU manualmente, defina a variável env. TPU_NAME na VM da qual ela está sendo usada ou chame TPUClusterResolver com parâmetros explícitos: TPUClusterResolver(tp_uname, zone, project)
  • TPUStrategy é a parte que implementa a distribuição e o algoritmo de sincronização de gradiente "all-reduce".
  • A estratégia é aplicada por meio de um escopo. O modelo precisa ser definido dentro do scope() da estratégia.
  • A função tpu_model.fit espera um objeto tf.data.Dataset para entrada no treinamento da TPU.

Tarefas comuns de portabilidade de TPU

  • Embora haja muitas maneiras de carregar dados em um modelo do TensorFlow, para TPUs, o uso da API tf.data.Dataset é necessário.
  • As TPUs são muito rápidas, e a ingestão de dados geralmente se torna um gargalo durante a execução delas. Existem ferramentas que podem ser usadas para detectar gargalos de dados e outras dicas de desempenho no Guia de desempenho da TPU.
  • Os números int8 ou int16 são tratados como int32. A TPU não tem hardware inteiro que opera em menos de 32 bits.
  • Algumas operações do TensorFlow não são compatíveis. Confira a lista aqui. A boa notícia é que essa limitação se aplica apenas ao código de treinamento, ou seja, à passagem para frente e para trás pelo seu modelo. Você ainda pode usar todas as operações do TensorFlow no pipeline de entrada de dados, porque ele será executado na CPU.
  • tf.py_func não é compatível com TPU.

4. [INFO] Introdução ao classificador de rede neural

Resumo

Se você já conhece todos os termos em negrito no próximo parágrafo, avance para o próximo exercício. Se você está começando no aprendizado profundo, seja bem-vindo e continue lendo.

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 no Keras como:

model = tf.keras.Sequential([
    tf.keras.layers.Flatten(input_shape=[192, 192, 3]),
    tf.keras.layers.Dense(500, activation="relu"),
    tf.keras.layers.Dense(50, activation="relu"),
    tf.keras.layers.Dense(5, activation='softmax') # classifying into 5 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

Rede neural densa

Essa é a rede neural mais simples para classificar imagens. Ele é feito de "neurônios" organizados em camadas. A primeira camada processa dados de entrada e alimenta as saídas em outras camadas. Ela é chamada de "densa" porque cada neurônio está conectado a todos os neurônios da camada anterior.

c21bae6dade487bc.png

É possível alimentar uma imagem nessa rede nivelando os valores RGB de todos os seus pixels em um vetor longo e usando-o como entradas. Não é a melhor técnica para reconhecimento de imagem, mas vamos melhorá-la mais tarde.

Neurônios, ativações, RELU

Um "neurônio" calcula uma soma ponderada de todas as entradas, adiciona um valor chamado "viés" e alimenta o resultado por meio de uma chamada "função de ativação". Os pesos e o viés são desconhecidos no início. Elas serão inicializadas aleatoriamente e "aprendidas" treinando a rede neural com muitos dados conhecidos.

644f4213a4ee70e5.png

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

Ativação do Softmax

A rede acima termina com uma camada de 5 neurônios porque estamos classificando as flores em cinco categorias (rosa, tulipa, dente-de-leão, margarida, girassol). Os neurônios em camadas intermediárias são ativados usando a função de ativação RELU clássica. Porém, na última camada, queremos calcular números entre 0 e 1 que representam a probabilidade dessa flor ser uma rosa, uma tulipa e assim por diante. Para isso, usaremos uma função de ativação chamada "softmax".

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

ef0d98c0952c262d.png d51252f75894479e.gif

Perda de entropia cruzada

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

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

7bdf8753d20617fb.png

Gradiente descendente

"Treinar" a rede neural significa usar imagens e rótulos de treinamento para ajustar pesos e vieses, a fim de minimizar 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 calcularmos as derivadas parciais da entropia cruzada em relação a todos os pesos e vieses, vamos conseguir um "gradiente", calculado para uma determinada imagem, rótulo e valor atual de pesos e vieses. 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 ciclo 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, tem maior probabilidade de convergir para a solução mais rapidamente. O tamanho do minilote é um parâmetro ajustável.

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

A convergência ainda pode ser um pouco caótica e pode até parar se o vetor de 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. 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 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.

52e824fe4716c4a0.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 realizado em lotes de dados e rótulos de treinamento. Isso ajuda o algoritmo a convergir. A dimensão "batch" é geralmente 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 neurônio 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) alimentar em uma rede neural para obter boas previsões é chamada de "engenharia de recursos".

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

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 popular. "Saídas de neurônios antes da função logística" foi encurtada para "logits".

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

Neurono: computa 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 5 elementos, todos zeros, exceto o terceiro, que é 1.

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

Sigmóide: outra função de ativação que costumava ser popular 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. E então você pode ter tensores com 3, 4, 5 ou mais dimensões.

5. [INFO] Redes neurais convolucionais

Resumo

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

convolutional.gif

Exemplo: filtragem de uma imagem com dois filtros sucessivos feitos de 4x4x3=48 pesos estimáveis cada.

Confira como uma rede neural convolucional simples fica no Keras:

model = tf.keras.Sequential([
  # input: images of size 192x192x3 pixels (the three stands for RGB channels)
  tf.keras.layers.Conv2D(kernel_size=3, filters=24, padding='same', activation='relu', input_shape=[192, 192, 3]),
  tf.keras.layers.Conv2D(kernel_size=3, filters=24, padding='same', activation='relu'),
  tf.keras.layers.MaxPooling2D(pool_size=2),
  tf.keras.layers.Conv2D(kernel_size=3, filters=12, padding='same', activation='relu'),
  tf.keras.layers.MaxPooling2D(pool_size=2),
  tf.keras.layers.Conv2D(kernel_size=3, filters=6, padding='same', activation='relu'),
  tf.keras.layers.Flatten(),
  # classifying into 5 categories
  tf.keras.layers.Dense(5, activation='softmax')
])

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

688858c21e3beff2.png

Redes neurais convolucionais 101

Em uma camada de uma rede convolucional, um "neurônio" faz uma soma ponderada dos pixels logo acima dele, em uma pequena região da imagem. Ela adiciona um viés e alimenta a soma por uma função de ativação, assim como um neurônio em uma camada densa regular faria. 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 "patch" de pesos desliza pela imagem em ambas as direções (uma "convolução"). A saída tem tantos valores quantos pixels há na imagem, mas é necessário algum padding nas bordas. É uma operação de filtragem, usando um filtro de 4x4x3=48 pesos.

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 um "canal" de saídas 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 resumidos como um tensor adicionando 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 com passo, max pooling

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

  • Convolução com passo: um filtro deslizante como o anterior, 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. As convoluções com passo ou a pooling máxima (máxima em uma janela 2x2 deslizante com um passo de 2) são uma maneira de reduzir o cubo de dados nas dimensões horizontais.

Classificador convolucional

Por fim, anexamos um cabeçalho de classificação nivelando o último cubo de dados e alimentando-o por uma camada densa ativada pelo softmax. Um classificador convolucional típico pode ter a seguinte aparência:

4a61aaffb6cba3d1.png.

Ilustração: um classificador de imagens usando camadas convolucionais e softmax. Ela usa filtros 3x3 e 1x1. As camadas maxpool usam o máximo de grupos de pontos de dados 2x2. O cabeçalho de classificação é implementado com uma camada densa com ativação softmax.

No Keras

A pilha convolucional ilustrada acima pode ser escrita em Keras assim:

model = tf.keras.Sequential([
  # input: images of size 192x192x3 pixels (the three stands for RGB channels)    
  tf.keras.layers.Conv2D(kernel_size=3, filters=32, padding='same', activation='relu', input_shape=[192, 192, 3]),
  tf.keras.layers.Conv2D(kernel_size=1, filters=32, padding='same', activation='relu'),
  tf.keras.layers.MaxPooling2D(pool_size=2),
  tf.keras.layers.Conv2D(kernel_size=3, filters=32, padding='same', activation='relu'),
  tf.keras.layers.Conv2D(kernel_size=1, filters=32, padding='same', activation='relu'),
  tf.keras.layers.MaxPooling2D(pool_size=2),
  tf.keras.layers.Conv2D(kernel_size=3, filters=32, padding='same', activation='relu'),
  tf.keras.layers.Conv2D(kernel_size=1, filters=32, padding='same', activation='relu'),
  tf.keras.layers.MaxPooling2D(pool_size=2),
  tf.keras.layers.Conv2D(kernel_size=3, filters=32, padding='same', activation='relu'),
  tf.keras.layers.Conv2D(kernel_size=1, filters=32, padding='same', activation='relu'),
  tf.keras.layers.MaxPooling2D(pool_size=2),
  tf.keras.layers.Conv2D(kernel_size=3, filters=16, padding='same', activation='relu'),
  tf.keras.layers.Conv2D(kernel_size=1, filters=8, padding='same', activation='relu'),
  tf.keras.layers.Flatten(),
  # classifying into 5 categories
  tf.keras.layers.Dense(5, activation='softmax')
])

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

6. [NOVA INFORMAÇÃO] Arquiteturas convolucionais modernas

Resumo

7968830b57b708c0.png

Ilustração: um "módulo" convolucional. O que é melhor neste momento? Uma camada "max-pool" seguida por uma camada convolucional 1x1 ou uma combinação diferente de camadas? Teste todos, concatena os resultados e deixe a rede decidir. À direita, está a arquitetura convolucional de início que usa esses módulos.

No Keras, para criar modelos em que o fluxo de dados pode se ramificar, é necessário usar o estilo de modelo "funcional". Exemplo:

l = tf.keras.layers # syntax shortcut

y = l.Conv2D(filters=32, kernel_size=3, padding='same',
             activation='relu', input_shape=[192, 192, 3])(x) # x=input image

# module start: branch out
y1 = l.Conv2D(filters=32, kernel_size=1, padding='same', activation='relu')(y)
y3 = l.Conv2D(filters=32, kernel_size=3, padding='same', activation='relu')(y)
y = l.concatenate([y1, y3]) # output now has 64 channels
# module end: concatenation

# many more layers ...

# Create the model by specifying the input and output tensors.
# Keras layers track their connections automatically so that's all that's needed.
z = l.Dense(5, activation='softmax')(y)
model = tf.keras.Model(x, z)

688858c21e3beff2.png

Outros truques baratos

Filtros pequenos de 3 x 3

40a7b15fb7dbe75c.png

Nesta ilustração, você vê o resultado de dois filtros 3x3 consecutivos. Tente rastrear quais pontos de dados contribuíram para o resultado: esses dois filtros 3x3 consecutivos calculam alguma combinação de uma região 5x5. Não é exatamente a mesma combinação que um filtro 5x5 calcularia, mas vale a pena tentar porque dois filtros 3x3 consecutivos são mais baratos do que um único filtro de 5x5.

Convoluções 1x1?

fd7cac16f8ecb423.png

Em termos matemáticos, uma convolução "1x1" é uma multiplicação por uma constante, não um conceito muito útil. No entanto, em redes neurais convolucionais, o filtro é aplicado a um cubo de dados, não apenas a uma imagem 2D. Portanto, um filtro "1x1" calcula uma soma ponderada de uma coluna de dados 1x1 (consulte a ilustração). Ao deslizar o filtro pelos dados, você vai conseguir uma combinação linear dos canais da entrada. Isso é muito útil. Se você pensar nos canais como resultados de operações de filtragem individuais, por exemplo, um filtro para "orelhas pontiagudas", outro para "bigodes" e uma terceira para "olhos de fenda", uma camada convolucional "1 x 1" calculará várias combinações lineares possíveis desses atributos, o que pode ser útil ao procurar um "gato". Além disso, as camadas 1x1 usam menos pesos.

7. Esquete

Uma maneira simples de reunir essas ideias foi demonstrada no documento "Squeezenet". Os autores sugerem um design de módulo de convolução muito simples, usando apenas camadas de convolução 1x1 e 3x3.

1730ac375379269b.png

Ilustração: arquitetura squeezenet baseada em "módulos de disparo". Eles alternam uma camada 1 x 1 que "comprimir" os dados recebidos na dimensão vertical, seguidas por duas camadas convolucionais paralelas 1 x 1 e 3 x 3 que "expandem" a profundidade dos dados novamente.

Prático

Continue no notebook anterior e crie uma rede neural convolucional inspirada na squeezenet. Você vai precisar mudar o código do modelo para o "estilo funcional" do Keras.

c3df49e90e5a654f.png Keras_Flowers_TPU (playground).ipynb

Informações adicionais

Neste exercício, será útil definir uma função auxiliar para um módulo de squeezenet:

def fire(x, squeeze, expand):
  y = l.Conv2D(filters=squeeze, kernel_size=1, padding='same', activation='relu')(x)
  y1 = l.Conv2D(filters=expand//2, kernel_size=1, padding='same', activation='relu')(y)
  y3 = l.Conv2D(filters=expand//2, kernel_size=3, padding='same', activation='relu')(y)
  return tf.keras.layers.concatenate([y1, y3])

# this is to make it behave similarly to other Keras layers
def fire_module(squeeze, expand):
  return lambda x: fire(x, squeeze, expand)

# usage:
x = l.Input(shape=[192, 192, 3])
y = fire_module(squeeze=24, expand=48)(x) # typically, squeeze is less than expand
y = fire_module(squeeze=32, expand=64)(y)
...
model = tf.keras.Model(x, y)

O objetivo desta vez é atingir 80% de precisão.

O que tentar

Comece com uma única camada convolucional e depois siga com "fire_modules", alternando com camadas MaxPooling2D(pool_size=2). Você pode testar de 2 a 4 camadas de max pooling na rede e também 1, 2 ou 3 módulos de disparo consecutivos entre as camadas de max pooling.

Em módulos de disparo, o parâmetro "squeeze" geralmente é menor que o parâmetro "expand". Esses parâmetros são, na verdade, números de filtros. Elas podem variar de 8 a 196, normalmente. Você pode testar arquiteturas em que o número de filtros aumenta gradualmente pela rede ou arquiteturas simples em que todos os módulos de disparo têm o mesmo número de filtros.

Exemplo:

x = tf.keras.layers.Input(shape=[*IMAGE_SIZE, 3]) # input is 192x192 pixels RGB

y = tf.keras.layers.Conv2D(kernel_size=3, filters=32, padding='same', activation='relu')(x)
y = fire_module(24, 48)(y)
y = tf.keras.layers.MaxPooling2D(pool_size=2)(y)
y = fire_module(24, 48)(y)
y = tf.keras.layers.MaxPooling2D(pool_size=2)(y)
y = fire_module(24, 48)(y)
y = tf.keras.layers.GlobalAveragePooling2D()(y)
y = tf.keras.layers.Dense(5, activation='softmax')(y)

model = tf.keras.Model(x, y)

Nesse ponto, você pode notar que seus experimentos não estão indo tão bem e que a meta de 80% de precisão parece remota. Hora de fazer alguns truques baratos.

Normalização em lote

A norma de lote vai ajudar com os problemas de convergência que você está enfrentando. Explicações detalhadas sobre essa técnica serão explicadas no próximo workshop. Por enquanto, use-a como um auxiliar "mágico" de caixa preta adicionando esta linha após cada camada convolucional na rede, incluindo as camadas dentro da função fire_module:

y = tf.keras.layers.BatchNormalization(momentum=0.9)(y)
# please adapt the input and output "y"s to whatever is appropriate in your context

O parâmetro momentum deve ser reduzido de seu valor padrão de 0,99 para 0,9 porque nosso conjunto de dados é pequeno. Esqueça esse detalhe por enquanto.

Aumento de dados

Você terá mais alguns pontos percentuais ao aumentar os dados com transformações fáceis, como a inversão de saturação da esquerda para a direita:

4ed2958e09b487ca.png

ad795b70334e0d6b.png

Isso é muito fácil de fazer no Tensorflow com a API tf.data.Dataset. Defina uma nova função de transformação para seus dados:

def data_augment(image, label):
    image = tf.image.random_flip_left_right(image)
    image = tf.image.random_saturation(image, lower=0, upper=2)
    return image, label

Em seguida, use-o na transformação de dados final (célula "conjuntos de dados de treinamento e validação", função "get_batched_dataset"):

dataset = dataset.repeat() # existing line
# insert this
if augment_data:
  dataset = dataset.map(data_augment, num_parallel_calls=AUTO)
dataset = dataset.shuffle(2048) # existing line

Não se esqueça de tornar o aumento de dados opcional e adicionar o código necessário para garantir que apenas o conjunto de dados de treinamento seja aumentado. Não faz sentido aumentar o conjunto de dados de validação.

A precisão de 80% em 35 períodos agora deve estar ao seu alcance.

Solução

Este é o notebook da solução. Você pode usá-la se tiver dificuldades.

c3df49e90e5a654f.png Keras_Flowers_TPU_squeezenet.ipynb

O que vimos

  • 🤔 Modelos de "estilo funcional" do Keras
  • 🤓 Arquitetura do Squeezenet
  • 🤓 Aumento de dados com tf.data.datset

Leia esta lista de verificação.

8. Xception ajustada

Convoluções separáveis

Uma maneira diferente de implementar camadas convolucionais vem ganhando popularidade recentemente: convoluções separáveis por profundidade. Sei que é um bocado, mas o conceito é bem simples. Elas são implementadas no Tensorflow e no Keras como tf.keras.layers.SeparableConv2D.

Uma convolução separável também executa um filtro na imagem, mas usa um conjunto distinto de pesos para cada canal da imagem de entrada. Em seguida, é feita uma "convolução 1x1", uma série de produtos escalares que resultam em uma soma ponderada dos canais filtrados. Com novos pesos a cada vez, quantas recombinações ponderadas dos canais são calculadas conforme necessário.

615720b803bf8dda.gif

Ilustração: convoluções separáveis. Fase 1: convoluções com um filtro separado para cada canal. Fase 2: recombinações lineares de canais. Repetida com um novo conjunto de pesos até que o número desejado de canais de saída seja alcançado. A fase 1 também pode ser repetida, com novos pesos a cada vez, mas, na prática, isso raramente é.

As convoluções separáveis são usadas nas arquiteturas de redes convolucionais mais recentes: MobileNetV2, Xception e EfficientNet. A propósito, você usou o MobileNetV2 para o aprendizado por transferência anteriormente.

Elas são mais baratas do que as convoluções normais e foram consideradas igualmente eficazes na prática. Aqui está a contagem de peso para o exemplo ilustrado acima:

Camada convolucional: 4 x 4 x 3 x 5 = 240

Camada convolucional separável: 4 x 4 x 3 + 3 x 5 = 48 + 15 = 63

O leitor pode calcular o número de multiplicações necessárias para aplicar cada estilo de escala de camada convolucional de maneira semelhante. As convoluções separáveis são menores e muito mais eficientes em termos computacionais.

Atividade prática

Reinicie a partir do notebook de teste "transfer learning", mas desta vez selecione Xception como o modelo pré-treinado. O xception usa apenas convoluções separáveis. Deixe todos os pesos treináveis. Vamos ajustar os pesos pré-treinados dos nossos dados em vez de usar as camadas pré-treinadas como tal.

c3df49e90e5a654f.png Keras Flowers transfer learning (playground).ipynb

Meta: precisão > 95% (É sério, é possível!)

Este é o exercício final, mas exige um pouco mais de trabalho com códigos e ciência de dados.

Mais informações sobre os ajustes

O Xception está disponível nos modelos pré-treinados padrão em tf.keras.application.* Não se esqueça de deixar todos os pesos treináveis desta vez.

pretrained_model = tf.keras.applications.Xception(input_shape=[*IMAGE_SIZE, 3],
                                                  include_top=False)
pretrained_model.trainable = True

Para ter bons resultados ao ajustar um modelo, preste atenção à taxa de aprendizado e use uma programação com um período de otimização. Assim:

9b1af213b2b36d47.png

Começar com uma taxa de aprendizado padrão interromperia os pesos pré-treinados do modelo. Ao começar, eles são preservados progressivamente até que o modelo retenha os dados, e eles são capazes de modificá-los de maneira coerente. Depois da rampa, você pode continuar com uma taxa de aprendizado constante ou exponencialmente decrescente.

No Keras, a taxa de aprendizado é especificada por um callback em que é possível calcular a taxa de aprendizado apropriada para cada período. O Keras vai transmitir a taxa de aprendizado correta ao otimizador em cada período.

def lr_fn(epoch):
  lr = ...
  return lr

lr_callback = tf.keras.callbacks.LearningRateScheduler(lr_fn, verbose=True)

model.fit(..., callbacks=[lr_callback])

Solução

Este é o notebook da solução. Você pode usá-la se tiver dificuldades.

c3df49e90e5a654f.png 07_Keras_Flowers_TPU_xception_fine_tuned_best.ipynb

O que vimos

  • 🤔 Convolução separável de profundidade
  • 🤓 Programações da taxa de aprendizado
  • 😈 Ajustar um modelo pré-treinado.

Reserve um momento para rever esta lista de verificação em sua cabeça.

9. Parabéns!

Você criou sua primeira rede neural convolucional moderna e a treinou com mais de 90% de precisão, iterando em treinamentos sucessivos em apenas alguns minutos, graças às TPUs. Isso conclui os quatro "Codelabs do Keras em TPU":

TPUs na prática

TPUs e GPUs estão disponíveis na AI Platform do Cloud:

Finalmente, adoramos feedback. Avise nossa equipe se você encontrar algo errado no laboratório ou achar que ele precisa ser melhorado. O feedback pode ser enviado pela página de problemas do GitHub [link do feedback].

HR.png

Martin Görner ID small.jpg
O autor: Martin Görner
Twitter: @martin_gorner

Logotipo do TensorFlow.jpg
www.tensorflow.org