1. Visão geral
Neste laboratório, você vai aprender a criar, treinar e ajustar suas próprias redes neurais convolucionais do zero com a Keras e o TensorFlow 2. Isso pode ser feito em minutos usando a potência das TPUs. Você também vai conhecer várias abordagens, desde o aprendizado por transferência muito simples até arquiteturas convolucionais modernas, como a Squeezenet. Este laboratório inclui explicações teóricas sobre redes neurais e é um bom ponto de partida para desenvolvedores que estão aprendendo sobre aprendizado profundo.
Ler artigos de aprendizado profundo pode ser difícil e confuso. Vamos conferir as arquiteturas modernas de redes neurais convolucionais.
O que você vai aprender
- Como usar Keras e Unidades de Processamento de Tensor (TPUs) para criar modelos personalizados com mais rapidez.
- Usar a API tf.data.Dataset e o formato TFRecord para carregar dados de treinamento de forma eficiente.
- Para trapacear 😝, use o aprendizado por transferência em vez de criar seus próprios modelos.
- Usar os estilos de modelo sequencial e funcional do Keras.
- Crie seu próprio classificador Keras com uma camada softmax e uma perda de entropia cruzada.
- Para ajustar seu modelo com uma boa escolha de camadas convolucionais.
- Para explorar ideias modernas de arquitetura de convnet, como módulos, pooling de média global etc.
- Criar uma convnet moderna simples usando a arquitetura Squeezenet.
Feedback
Se você notar algo errado neste codelab, informe-nos. O feedback pode ser enviado pela página de problemas do GitHub [link do feedback].
2. Guia de início rápido do Google Colaboratory
Este laboratório usa o Google Collaboratory, e você não precisa configurar nada. Ele pode ser executado em um Chromebook. Abra o arquivo abaixo e execute as células para se familiarizar com os notebooks do Colab.
Selecionar um back-end de TPU
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
Execute as células uma por 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
Todos os notebooks têm um sumário. Para abri-lo, use a seta preta à esquerda.
Células ocultas
Algumas células só mostram o título. Este é um recurso de notebook específico para o 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 internas sejam definidas.
Authentication
O Colab pode acessar seus buckets particulares do Google Cloud Storage desde que você faça a autenticação com uma conta autorizada. O snippet de código acima acionará um processo de autenticação.
3. [INFO] O que são as Unidades de Processamento de Tensor (TPUs)?
Resumo
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=...)
Hoje, vamos usar TPUs para criar e otimizar um classificador de flores em velocidades interativas (minutos por execução de treinamento).
Por que usar TPUs?
As GPUs modernas são organizadas em torno de "núcleos programáveis", uma arquitetura muito flexível que permite lidar com diversas tarefas, como renderização 3D, aprendizado profundo, simulações físicas etc. As TPUs, por outro lado, combinam um processador vetorial clássico com uma unidade de multiplicação de matriz dedicada e se destacam em qualquer tarefa em que grandes multiplicações de matrizes dominem, como redes neurais.
Ilustração: uma camada densa de rede neural 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. As camadas convolucionais também podem ser representadas 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.
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.
O treinamento de redes neurais geralmente é resistente ao ruído introduzido por uma precisão de ponto flutuante reduzida. Há casos em que o ruído até ajuda a convergir o otimizador. A precisão de ponto flutuante de 16 bits tem sido usada tradicionalmente para acelerar cálculos, mas os formatos float16 e float32 têm intervalos muito diferentes. Reduzir a precisão de float32 para float16 geralmente resulta em overflows e underflows. 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. O bfloat16 é um float32 truncado com exatamente os mesmos bits e intervalo de expoente que o float32. Isso, somado ao fato de que as TPUs computam multiplicações de matrizes em precisão mista com entradas bfloat16 e saídas float32, significa que, normalmente, nenhuma mudança no código é necessária para se beneficiar dos ganhos de desempenho com precisão reduzida.
Matriz systolic
A MXU implementa multiplicações de matrizes em hardware usando uma arquitetura chamada "matriz sistólica", em que os elementos de dados fluem por uma matriz de unidades de computação de hardware. (Em medicina, “sistólico” refere-se às contrações cardíacas e ao fluxo sanguíneo; aqui, 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 de outra matriz (veja a ilustração no topo 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 fosse 128 x 128 grande, seria necessário que 128 x 128=16 mil "núcleos" estivessem disponíveis, o que normalmente não é possível. As maiores GPUs têm cerca de 4.000 núcleos. Por outro lado, as TPUs usam o mínimo de hardware possível para as unidades de computação na MXU: apenas bfloat16 x bfloat16 => float32
de acumuladores de multiplicação, 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.
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 escalares são calculados em uma MXU, as somas intermediárias simplesmente fluem entre unidades de computação adjacentes. Eles não precisam ser armazenados e recuperados da memória ou de um arquivo de registro. O resultado final é que a arquitetura de matriz sistólica de TPU tem uma vantagem significativa de densidade e potência, além de uma vantagem de velocidade insignificante em relação a uma GPU, ao computar multiplicações de matrizes.
Cloud TPU
Ao solicitar uma "Cloud TPU v2" no Google Cloud Platform, você recebe uma máquina virtual (VM) com uma placa de TPU conectada a PCI. A placa de TPU tem quatro chips de TPU de núcleo duplo. Cada núcleo de TPU possui uma unidade de processamento vetorial (VPU, na sigla em inglês) e uma unidade de multiplicação de matriz (MXU, na sigla em inglês) de 128 x 128. Esse "Cloud TPU" geralmente é conectado por meio da rede à VM que o solicitou. O quadro completo fica assim:
Ilustração: sua VM com um acelerador "Cloud TPU" conectado à rede. "O próprio Cloud TPU é composto de uma VM com uma placa de TPU conectada a PCI e quatro chips de TPU dual-core.
Pods de 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.
Ilustração: um pod da TPU v3. Placas e racks de TPU conectados por interconexão HPC.
Durante o treinamento, os gradientes são trocados entre núcleos de TPU usando o algoritmo de redução total. Confira uma boa explicação sobre a redução total. O modelo que está sendo treinado pode aproveitar o hardware ao treinar em grandes tamanhos de lote.
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 tamanho de lote grande
O tamanho de lote ideal para TPUs é de 128 itens de dados por núcleo, mas o hardware já pode mostrar uma boa utilização com 8 itens de dados por núcleo. 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 de lote para toda a TPU. Seus lotes serão divididos automaticamente em oito e executados nos oito núcleos da TPU.
Para mais dicas sobre desempenho, consulte o Guia de desempenho da TPU. Para tamanhos de lote muito grandes, pode ser necessário cuidado especial em alguns modelos. Consulte LARSOptimizer para mais detalhes.
Em segundo plano: XLA
Os programas do TensorFlow definem grafos de computação. Ela não executa o código Python diretamente, e 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.
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 são compatíveis com a API Keras a partir do Tensorflow 2.1. O suporte a Keras funciona em TPUs e pods da TPU. Veja um exemplo que funciona em TPU, GPU(s) 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 criados com o utilitário "gsutil 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 chameTPUClusterResolver
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 no escopo 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 quando elas são executadas. 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 de número inteiro operando em menos de 32 bits.
- Algumas operações do TensorFlow não são compatíveis. Confira a lista. 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. Ainda é possível usar todas as operações do TensorFlow no pipeline de entrada de dados, já que elas serão executadas na CPU.
tf.py_func
não é compatível com a TPU.
4. Como carregar dados
Vamos trabalhar com um conjunto de dados de fotos de flores. O objetivo é aprender a classificá-las em cinco tipos de flores. O carregamento de dados é realizado usando a API tf.data.Dataset
. Primeiro, vamos conhecer a API.
Na prática
Abra o notebook a seguir, execute as células (Shift-ENTER) e siga as instruções sempre que encontrar o rótulo "WORK REQUIRED".
Fun with tf.data.Dataset (playground).ipynb
Informações adicionais
Sobre o conjunto de dados "flowers"
O conjunto de dados é organizado em cinco pastas. Cada pasta contém flores de um tipo. As pastas são chamadas de girassóis, margaridas, dentes-de-leão, tulipas e rosas. Os dados são hospedados em um bucket público no Google Cloud Storage. Trecho:
gs://flowers-public/sunflowers/5139971615_434ff8ed8b_n.jpg
gs://flowers-public/daisy/8094774544_35465c1c64.jpg
gs://flowers-public/sunflowers/9309473873_9d62b9082e.jpg
gs://flowers-public/dandelion/19551343954_83bb52f310_m.jpg
gs://flowers-public/dandelion/14199664556_188b37e51e.jpg
gs://flowers-public/tulips/4290566894_c7f061583d_m.jpg
gs://flowers-public/roses/3065719996_c16ecd5551.jpg
gs://flowers-public/dandelion/8168031302_6e36f39d87.jpg
gs://flowers-public/sunflowers/9564240106_0577e919da_n.jpg
gs://flowers-public/daisy/14167543177_cd36b54ac6_n.jpg
Por que usar o tf.data.Dataset?
O Keras e o Tensorflow aceitam Datasets em todas as funções de treinamento e avaliação. Depois de carregar dados em um conjunto, a API oferece todas as funcionalidades comuns que são úteis para dados de treinamento de rede neural:
dataset = ... # load something (see below)
dataset = dataset.shuffle(1000) # shuffle the dataset with a buffer of 1000
dataset = dataset.cache() # cache the dataset in RAM or on disk
dataset = dataset.repeat() # repeat the dataset indefinitely
dataset = dataset.batch(128) # batch data elements together in batches of 128
AUTOTUNE = tf.data.AUTOTUNE
dataset = dataset.prefetch(AUTOTUNE) # prefetch next batch(es) while training
Confira dicas de desempenho e práticas recomendadas de conjuntos de dados neste artigo. Confira a documentação de referência aqui.
Noções básicas sobre tf.data.Dataset
Os dados geralmente vêm em vários arquivos, como imagens. É possível criar um conjunto de dados de nomes de arquivos chamando:
filenames_dataset = tf.data.Dataset.list_files('gs://flowers-public/*/*.jpg')
# The parameter is a "glob" pattern that supports the * and ? wildcards.
Em seguida, você "mapeia" uma função para cada nome de arquivo, que normalmente carrega e decodifica o arquivo em dados reais na memória:
def decode_jpeg(filename):
bits = tf.io.read_file(filename)
image = tf.io.decode_jpeg(bits)
return image
image_dataset = filenames_dataset.map(decode_jpeg)
# this is now a dataset of decoded images (uint8 RGB format)
Para iterar em um conjunto de dados:
for data in my_dataset:
print(data)
Conjuntos de tuplas
No aprendizado supervisionado, um conjunto de dados de treinamento normalmente é composto por pares de dados de treinamento e respostas corretas. Para permitir isso, a função de decodificação pode retornar tuplas. Em seguida, você terá um conjunto de dados de tuplas e tuplas que será retornado quando você iterar nele. Os valores retornados são tensores do TensorFlow prontos para serem consumidos pelo modelo. Chame .numpy()
neles para ver valores brutos:
def decode_jpeg_and_label(filename):
bits = tf.read_file(filename)
image = tf.io.decode_jpeg(bits)
label = ... # extract flower name from folder name
return image, label
image_dataset = filenames_dataset.map(decode_jpeg_and_label)
# this is now a dataset of (image, label) pairs
for image, label in dataset:
print(image.numpy().shape, label.numpy())
Conclusão: carregar imagens uma por uma é lento!
Ao iterar nesse conjunto de dados, você verá que é possível carregar algo como 1 ou 2 imagens por segundo. Muito lento! Os aceleradores de hardware que vamos usar para o treinamento podem suportar muitas vezes essa taxa. Confira na próxima seção como fazer isso.
Solução
Este é o notebook da solução. Você pode usá-lo se ficar preso.
Fun with tf.data.Dataset (solution).ipynb
O que vimos
- 🤔 tf.data.Dataset.list_files
- 🤔 tf.data.Dataset.map
- 🤔 Conjuntos de dados de tuplas
- 😀 iterando com conjuntos de dados
Reserve um momento para rever esta lista de verificação em sua cabeça.
5. Carregamento rápido de dados
Os aceleradores de hardware da Unidade de Processamento de Tensor (TPU) que vamos usar neste laboratório são muito rápidos. O desafio muitas vezes é alimentá-los com dados rápido o suficiente para mantê-los ocupados. O Google Cloud Storage (GCS) é capaz de sustentar uma alta capacidade de processamento. No entanto, como acontece com todos os sistemas de armazenamento em nuvem, iniciar uma conexão custa certa rede para ida e volta. Portanto, não é ideal armazenar nossos dados como milhares de arquivos individuais. Vamos agrupar esses arquivos em um número menor e usar o poder do tf.data.Dataset para ler vários arquivos em paralelo.
Leitura
O código que carrega arquivos de imagem, os redimensiona para um tamanho comum e os armazena em 16 arquivos TFRecord está no notebook a seguir. Por favor, leia-o rapidamente. Não é necessário executá-la porque os dados formatados em TFRecord serão fornecidos corretamente para o restante do codelab.
Flower pictures to TFRecords.ipynb
Layout de dados ideal para a melhor taxa de transferência do GCS
O formato de arquivo TFRecord
O formato de arquivo preferido do TensorFlow para armazenar dados é o formato TFRecord baseado em protobuf. Outros formatos de serialização também funcionam, mas é possível carregar um conjunto de dados de arquivos TFRecord diretamente escrevendo:
filenames = tf.io.gfile.glob(FILENAME_PATTERN)
dataset = tf.data.TFRecordDataset(filenames)
dataset = dataset.map(...) # do the TFRecord decoding here - see below
Para um desempenho ideal, recomendamos usar o código mais complexo abaixo para ler vários arquivos TFRecord de uma só vez. Esse código vai ler de N arquivos em paralelo e ignorar a ordem dos dados em favor da velocidade de leitura.
AUTOTUNE = tf.data.AUTOTUNE
ignore_order = tf.data.Options()
ignore_order.experimental_deterministic = False
filenames = tf.io.gfile.glob(FILENAME_PATTERN)
dataset = tf.data.TFRecordDataset(filenames, num_parallel_reads=AUTOTUNE)
dataset = dataset.with_options(ignore_order)
dataset = dataset.map(...) # do the TFRecord decoding here - see below
Folha de referência do TFRecord
Três tipos de dados podem ser armazenados em TFRecords: strings de bytes (lista de bytes), inteiros de 64 bits e floats de 32 bits. Eles são sempre armazenados como listas, um único elemento de dados será uma lista de tamanho 1. Você pode usar as funções auxiliares abaixo para armazenar dados em TFRecords.
como gravar strings de bytes
# warning, the input is a list of byte strings, which are themselves lists of bytes
def _bytestring_feature(list_of_bytestrings):
return tf.train.Feature(bytes_list=tf.train.BytesList(value=list_of_bytestrings))
Gravar números inteiros
def _int_feature(list_of_ints): # int64
return tf.train.Feature(int64_list=tf.train.Int64List(value=list_of_ints))
gravar flutuações
def _float_feature(list_of_floats): # float32
return tf.train.Feature(float_list=tf.train.FloatList(value=list_of_floats))
Gravar um TFRecord usando os auxiliares acima
# input data in my_img_bytes, my_class, my_height, my_width, my_floats
with tf.python_io.TFRecordWriter(filename) as out_file:
feature = {
"image": _bytestring_feature([my_img_bytes]), # one image in the list
"class": _int_feature([my_class]), # one class in the list
"size": _int_feature([my_height, my_width]), # fixed length (2) list of ints
"float_data": _float_feature(my_floats) # variable length list of floats
}
tf_record = tf.train.Example(features=tf.train.Features(feature=feature))
out_file.write(tf_record.SerializeToString())
Para ler dados do TFRecords, primeiro é preciso declarar o layout dos registros armazenados. Na declaração, é possível acessar qualquer campo nomeado como uma lista de comprimento fixo ou variável:
lendo no TFRecords
def read_tfrecord(data):
features = {
# tf.string = byte string (not text string)
"image": tf.io.FixedLenFeature([], tf.string), # shape [] means scalar, here, a single byte string
"class": tf.io.FixedLenFeature([], tf.int64), # shape [] means scalar, i.e. a single item
"size": tf.io.FixedLenFeature([2], tf.int64), # two integers
"float_data": tf.io.VarLenFeature(tf.float32) # a variable number of floats
}
# decode the TFRecord
tf_record = tf.io.parse_single_example(data, features)
# FixedLenFeature fields are now ready to use
sz = tf_record['size']
# Typical code for decoding compressed images
image = tf.io.decode_jpeg(tf_record['image'], channels=3)
# VarLenFeature fields require additional sparse.to_dense decoding
float_data = tf.sparse.to_dense(tf_record['float_data'])
return image, sz, float_data
# decoding a tf.data.TFRecordDataset
dataset = dataset.map(read_tfrecord)
# now a dataset of triplets (image, sz, float_data)
Snippets de código úteis:
Como ler elementos de dados únicos
tf.io.FixedLenFeature([], tf.string) # for one byte string
tf.io.FixedLenFeature([], tf.int64) # for one int
tf.io.FixedLenFeature([], tf.float32) # for one float
ler listas de elementos de tamanho fixo
tf.io.FixedLenFeature([N], tf.string) # list of N byte strings
tf.io.FixedLenFeature([N], tf.int64) # list of N ints
tf.io.FixedLenFeature([N], tf.float32) # list of N floats
Ler um número variável de itens de dados
tf.io.VarLenFeature(tf.string) # list of byte strings
tf.io.VarLenFeature(tf.int64) # list of ints
tf.io.VarLenFeature(tf.float32) # list of floats
Um VarLenFeature retorna um vetor esparso. Depois de decodificar o TFRecord, é necessário realizar uma etapa adicional:
dense_data = tf.sparse.to_dense(tf_record['my_var_len_feature'])
Também é possível ter campos opcionais em TFRecords. Se você especificar um valor padrão ao ler um campo, ele será retornado em vez de um erro se o campo estiver ausente.
tf.io.FixedLenFeature([], tf.int64, default_value=0) # this field is optional
O que vimos
- 🤔 Divisão de arquivos de dados para acesso rápido no GCS
- 😓 como gravar TFRecords. (Você já esqueceu a sintaxe? Tudo bem. Adicione esta página aos favoritos como uma folha de referência.
- 🤔 Como carregar um conjunto de dados de TFRecords usando o TFRecordDataset
Leia esta lista de verificação.
6. [INFORMAÇÃO] Introdução ao classificador de rede neural
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, 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 em 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, ... )
Rede neural densa
Essa é a rede neural mais simples para classificar imagens. Ele é feito de "neurônios" organizados em camadas. A primeira camada processa os dados de entrada e alimenta as saídas em outras camadas. É chamado de "denso" porque cada neurônio está conectado a todos os neurônios da camada anterior.
É 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. Essa não é a melhor técnica de reconhecimento de imagens, 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 "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.
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, vamos usar 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.
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, frequentemente 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 de entropia cruzada" é a mais eficaz. Vamos chamar isso de função de erro ou "perda":
Gradiente descendente
"Treinar" a rede neural significa, na verdade, 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 a classe conhecida dela.
Se computamos as derivadas 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 ciclo 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.
Minilotes 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.
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.
Ilustração: um ponto de sela. 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 "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 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".
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 conhecida. "Neuron outputs before logistic function" foi abreviado 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 cinco elementos, todos os zeros, exceto o terceiro, que é 1.
relu: unidade linear retificada. Uma função de ativação comum para 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.
7. Aprendizado por transferência
Para um problema de classificação de imagem, as camadas densas provavelmente não serão suficientes. Precisamos aprender sobre as camadas convolucionais e as muitas maneiras de organizá-las.
Mas também podemos usar um atalho. Há redes neurais convolucionais totalmente treinadas disponíveis para download. É possível cortar a última camada deles, o cabeçalho de classificação softmax, e substituí-la pela sua própria camada. Todos os pesos e vieses treinados permanecem como estão. Você só treina novamente a camada de softmax adicionada. Essa técnica é chamada de aprendizado por transferência e, surpreendentemente, funciona desde que o conjunto de dados em que a rede neural é pré-treinada esteja “próximo” ao seu.
Prático
Abra o notebook a seguir, execute as células (Shift + ENTER) e siga as instruções sempre que aparecer um rótulo "WORK REQUIRED".
Keras Flowers transfer learning (playground).ipynb
Informações adicionais
Com o aprendizado por transferência, você se beneficia das arquiteturas avançadas de rede neural convolucional desenvolvidas pelos principais pesquisadores e do pré-treinamento em um enorme conjunto de dados de imagens. No nosso caso, vamos transferir o aprendizado de uma rede treinada no ImageNet, um banco de dados de imagens que contém muitas plantas e cenas externas, o que é bem parecido com flores.
Ilustração: usar uma rede neural convolucional complexa já treinada como uma caixa preta, retreinando apenas a cabeça de classificação. Isso é aprendizado por transferência. Vamos ver como esses arranjos complicados de camadas convolucionais funcionam mais tarde. Por enquanto, o problema é de outra pessoa.
Aprendizado por transferência no Keras
No Keras, é possível instanciar um modelo pré-treinado da coleção tf.keras.applications.*
. O MobileNet V2, por exemplo, é uma arquitetura convolucional muito boa que mantém um tamanho razoável. Ao selecionar include_top=False
, você recebe o modelo pré-treinado sem a camada softmax final para que possa adicionar seu próprio modelo:
pretrained_model = tf.keras.applications.MobileNetV2(input_shape=[*IMAGE_SIZE, 3], include_top=False)
pretrained_model.trainable = False
model = tf.keras.Sequential([
pretrained_model,
tf.keras.layers.Flatten(),
tf.keras.layers.Dense(5, activation='softmax')
])
Observe também a configuração pretrained_model.trainable = False
. Ele congela os pesos e vieses do modelo pré-treinado para que você treine apenas a camada softmax. Isso geralmente envolve relativamente poucos pesos e pode ser feito rapidamente e sem precisar de um conjunto de dados muito grande. No entanto, se você tiver muitos dados, o aprendizado de transferência pode funcionar ainda melhor com pretrained_model.trainable = True
. Os pesos pré-treinados fornecem valores iniciais excelentes e ainda podem ser ajustados pelo treinamento para se adequar melhor ao seu problema.
Por fim, observe a camada Flatten()
inserida antes da camada softmax densa. Camadas densas funcionam em vetores planos de dados, mas não sabemos se é isso que o modelo pré-treinado retorna. É por isso que precisamos separar. No próximo capítulo, vamos nos aprofundar nas arquiteturas convolucionais e explicar o formato de dados retornado pelas camadas convolucionais.
Você deve chegar a uma precisão de 75% com essa abordagem.
Solução
Este é o notebook da solução. Você pode usá-lo se ficar preso.
Keras Flowers transfer learning (solution).ipynb
O que vimos
- 🤔 Como escrever um classificador no Keras
- 🤓 configurado com uma última camada softmax e perda de entropia cruzada
- 😝 Aprendizado por transferência
- 🤔 Como treinar seu primeiro modelo
- 🧐 Acompanhar a perda e a precisão durante o treinamento
Reserve um momento para rever esta lista de verificação em sua cabeça.
8. [INFO] Redes neurais convolucionais
Resumindo
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.
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([
# 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'])
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 é então repetida em toda a imagem usando os mesmos pesos. Lembre-se de que, em camadas densas, cada neurônio tem 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. É 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.
Os dois (ou mais) conjuntos de pesos podem ser resumidos como um tensor adicionando uma nova dimensão. Isso nos dá a forma genérica 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.
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 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)
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 de 2x2 deslizando em um salto de 2) são uma maneira de reduzir o cubo de dados nas dimensões horizontais.
Classificador convolucional
Por fim, anexamos uma cabeça de classificação achatando o último cubo de dados e alimentando-o por uma camada densa e ativada por softmax. Um classificador convolucional típico pode ter esta aparência:
Ilustração: um classificador de imagens usando camadas convolucionais e softmax. Ela usa filtros 3x3 e 1x1. As camadas do maxpool usam o máximo de grupos de pontos de dados 2x2. A cabeça de classificação é implementada com uma camada densa com ativação softmax.
No Keras
A pilha de convolução ilustrada acima pode ser escrita no 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'])
9. Sua rede neural convolucional personalizada
Atividade prática
Vamos criar e treinar uma rede neural convolucional do zero. Usar uma TPU nos permite iterar muito rapidamente. Abra o notebook a seguir, execute as células (Shift-ENTER) e siga as instruções sempre que encontrar o rótulo "WORK REQUIRED".
Keras_Flowers_TPU (playground).ipynb
O objetivo é superar a precisão de 75% do modelo de aprendizado de transferência. Esse modelo tinha uma vantagem, tendo sido pré-treinado em um conjunto de dados de milhões de imagens, enquanto temos apenas 3.670 imagens aqui. Você pode pelo menos igualar?
Informações adicionais
Quantas camadas, qual o tamanho?
Selecionar os tamanhos das camadas é mais uma arte do que uma ciência. Você precisa encontrar o equilíbrio entre ter poucos e muitos parâmetros (ponderações e vieses). Com poucos pesos, a rede neural não pode representar a complexidade das formas das flores. Com muitos, pode haver uma tendência de "superajuste", ou seja, a rede se especializa nas imagens de treinamento e não consegue generalizar. Com muitos parâmetros, o treinamento do modelo também será lento. No Keras, a função model.summary()
mostra a estrutura e a contagem de parâmetros do modelo:
Layer (type) Output Shape Param #
=================================================================
conv2d (Conv2D) (None, 192, 192, 16) 448
_________________________________________________________________
conv2d_1 (Conv2D) (None, 192, 192, 30) 4350
_________________________________________________________________
max_pooling2d (MaxPooling2D) (None, 96, 96, 30) 0
_________________________________________________________________
conv2d_2 (Conv2D) (None, 96, 96, 60) 16260
_________________________________________________________________
...
_________________________________________________________________
global_average_pooling2d (Gl (None, 130) 0
_________________________________________________________________
dense (Dense) (None, 90) 11790
_________________________________________________________________
dense_1 (Dense) (None, 5) 455
=================================================================
Total params: 300,033
Trainable params: 300,033
Non-trainable params: 0
_________________________________________________________________
Algumas dicas:
- Ter várias camadas é o que torna as redes neurais "profundas" eficazes. Para este problema simples de reconhecimento de flores, 5 a 10 camadas fazem sentido.
- Use filtros pequenos. Normalmente, os filtros 3x3 são bons em todos os lugares.
- Os filtros 1x1 também podem ser usados e são baratos. Eles não "filtram" nada, mas calculam combinações lineares de canais. Alterne com filtros reais. Saiba mais sobre "convoluções 1x1" na próxima seção.
- Para um problema de classificação como esse, reduza a amostragem com frequência usando camadas de max-pooling (ou convoluções com passo >1). Você não se importa onde está a flor, apenas que ela é uma rosa ou um dente-de-leão, portanto, perder informações de x e y não é importante e filtrar áreas menores é mais barato.
- O número de filtros geralmente se torna semelhante ao número de classes no final da rede. Por quê? Veja o truque "pooling médio global" abaixo. Se você classificar em centenas de classes, aumente a contagem de filtros progressivamente em camadas consecutivas. Para o conjunto de dados de flores com cinco classes, a filtragem com apenas cinco filtros não seria suficiente. Você pode usar a mesma contagem de filtros na maioria das camadas, por exemplo, 32 e diminuir até o final.
- As camadas densas finais são caras. Elas podem ter mais pesos do que todas as camadas convolucionais combinadas. Por exemplo, mesmo com uma saída muito razoável do último cubo de dados de 24x24x10 pontos de dados, uma camada densa de 100 neurônios custaria 24x24x10x100 = 576.000 pesos. Tente pensar bem ou tente a agregação de média global (confira abaixo).
Pooling médio global
Em vez de usar uma camada densa cara no final de uma rede neural convolucional, é possível dividir o "cubo" de dados recebidos em quantas partes houver, calcular a média dos valores e alimentá-los com uma função de ativação softmax. Essa forma de criar o cabeçalho de classificação custa 0 pesos. No Keras, a sintaxe é tf.keras.layers.GlobalAveragePooling2D().
Solução
Este é o notebook da solução. Você pode usá-lo se ficar preso.
Keras_Flowers_TPU (solution).ipynb
O que vimos
- 🤔 Jogado com camadas convolucionais
- 🤓 Testar pooling máximo, strides, pooling médio global...
- 😀 iterou rapidamente em um modelo real na TPU.
Leia esta lista de verificação.
10. [INFORMAÇÃO] Arquiteturas convolucionais modernas
Resumindo
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? Experimente todas, concatene os resultados e deixe a rede decidir. À direita: a arquitetura convolucional inception usando 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)
Outros truques baratos
Filtros pequenos de 3 x 3
Nesta ilustração, você vê o resultado de dois filtros 3 x 3 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 1 x 1?
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 a soma ponderada de uma coluna de dados 1x1 (veja a ilustração) e, à medida que você desliza pelos dados, obtém uma combinação linear dos canais de 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.
11. 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.
Ilustração: arquitetura de 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.
Atividade prática
Continue no notebook anterior e crie uma rede neural convolucional inspirada em squeezenets. Você vai precisar mudar o código do modelo para o "estilo funcional" do Keras.
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 siga com "fire_modules
", alternando com camadas MaxPooling2D(pool_size=2)
. Você pode testar com 2 a 4 camadas de pooling no máximo na rede e também com 1, 2 ou 3 módulos de disparo consecutivos entre as camadas de pooling máximas.
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. Normalmente, elas variam de 8 a 196. 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)
A essa altura, você pode notar que seus experimentos não estão indo tão bem e que o objetivo de precisão de 80% parece remoto. É hora de fazer mais alguns truques baratos.
Normalização em lote
A norma em lote vai ajudar com os problemas de convergência que você está enfrentando. Haverá explicações detalhadas sobre essa técnica no próximo workshop. Por enquanto, use-a como uma caixa preta "mágica" adicionando esta linha após cada camada de convolução 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:
É muito fácil fazer isso no TensorFlow com a API tf.data.Dataset. Defina uma nova função de transformação para os 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 épocas agora está ao seu alcance.
Solução
Este é o notebook da solução. Você pode usá-lo se ficar preso.
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.
12. Xception ajustado
Convoluções separáveis
Uma maneira diferente de implementar camadas convolucionais estava ganhando popularidade recentemente: convoluções separáveis em profundidade. Eu sei, é uma boca a boca, mas o conceito é bem simples. Elas são implementadas no Tensorflow e no Keras como um 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. Na sequência, há uma "convolução 1x1", uma série de produtos escalares que resulta em uma soma ponderada dos canais filtrados. Com novos pesos a cada vez, são calculadas tantas recombinações ponderadas dos canais quanto necessário.
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 acontece.
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 pelo notebook do playground de "aprendizado por transferência", 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.
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:
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. Após a rampa, é possível continuar com uma taxa de aprendizado constante ou que diminui exponencialmente.
No Keras, a taxa de aprendizado é especificada por um callback, em que você pode calcular a taxa de aprendizado adequada para cada época. O Keras vai transmitir a taxa de aprendizado correta para o otimizador em cada época.
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á-lo se ficar preso.
07_Keras_Flowers_TPU_xception_fine_tuned_best.ipynb
O que vimos
- 🤔 Convolução separável de profundidade
- 🤓 Programações da taxa de aprendizado
- 😝 Ajuste um modelo pré-treinado.
Leia esta lista de verificação.
13. Parabéns!
Você criou sua primeira rede neural convolucional moderna e a treinou com mais de 90% de precisão, iterando em execuções sucessivas de treinamento em apenas alguns minutos graças às TPUs.
TPUs na prática
As TPUs e GPUs estão disponíveis na Vertex AI do Google Cloud:
- Em VMs de aprendizado profundo
- nos Notebooks da Vertex AI
- Em jobs de Jobs do Vertex AI Training
Por fim, adoramos receber 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].
|