Introdução ao Pytorch - Parte 1

A primeira pergunta que qualquer pessoa sã faz é: por que escolher esse framework ao invés dos demais? A lista de Frameworks de Deep Learning conta com nomes como Keras, TensorFlow, Lasagne, e talvez até o dia em que você estiver lendo isso, o próprio scikit-learn já tenha um módulo DL. Pelo pouco que vi destas outras alternativas, o que torna o Pytorch atraente é basicamente simplicidade (comparado ao TensorFlow) e grande comunidade (o projeto é abraçado pelo Facebook). Embora pelo elo com o Facebook sempre fique aquele medo de o projeto deixar de ser open source da noite pro dia e tentarem te cobrar pelo uso, acredito que isso seja pouco provável (mas o risco existe).

Projeto no Github
Esse tutorial considera que você já tenha uma experiência mínima com Python, sabe o que é um Jupyter Notebook, e por último, que tem conta no Gmail (what?). Explico: vamos usar um ambiente que até o momento de escrita deste post oferece o uso de placas de vídeo (GPUs) gratuitamente chamado Google Colab. Eu não vou explicar como configurar, pra isso tem tutoriais prontos por aí. Acredite: faz toda diferença usar GPUs para treinar os modelos. Um processo que dura 20 minutos com GPU pode durar horas somente com CPU.

O projeto descrito nesse tutorial está disponível no meu Github: baixe os arquivos fonte por lá se quiser acompanhar a execução do código.

Classificação de Flores

O projeto final da bolsa da Udacity chamada Pytorch Challenge era sobre como classificar fotos de flores por nome. Lembrando que o projeto está no github, caso queira baixar e executar. O fluxo do processo é simples:  acessamos nossas imagens e sua classificação, definimos qual modelo de rede neural vamos usar - no caso, as redes pré treinadas da biblioteca, treinamos a rede, verificamos os resultados com um Dataset diferente do usado para o treino, e por fim, selecionamos uma imagem qualquer e vemos qual predição o modelo faz.

Nosso dataset é constituido de um grupo de treino e outro de testes ou validação. Estão agrupados por pastas enumeradas, e existe um arquivo json que correlaciona os números aos nomes das flores.



Acessando os dados

O primeiro passo é carregar o Dataset em uma estrutura apropriada. Quais as vantagens de fazer isso ao invés de implementar um jeito próprio? Quando vamos treinar uma rede neural, é interessante que os dados tenham certa variabilidade, para que nosso modelo fique mais generalista. Intuitivamente, nem toda foto de flor será centralizada, sem recortes, sem nenhuma oclusão parcial, etc. Por isso é comum que sejam aplicadas alterações nas fotos de treino como inverter a imagem no eixo vertical ou horizontal, utilizar apenas um pedaço recortado da imagem original, rotacionar a imagem em alguns graus, pequenas alterações de brilho e luminosidade, etc. Isso também faz com que o tamanho dos exemplos de treino sejam muito maiores, o que é desejável, visto que quanto maior e mais variado o dataset, melhor o resultado da rede neural. A resposta para pergunta então é: todas estas transformações já estão prontas e são facilmente ligadas e desligadas ao usar a ferramenta de carregar dados do Pytorch.

Outro ponto importante tem a ver com a ordem com que as imagens são usadas pela rede neural. Normalmente, a cada ciclo de treinamento (chamado "epoch"), as imagens são reembaralhadas. A classe de carregamento de dados também possui essa função pronta.

Finalmente, quando usamos uma placa de vídeo para realizar o processamento, podemos processar diversos exemplos em paralelo. Por isso, organizamos nosso dataset em batches, para serem avaliados simultaneamente. Aqui é onde a performance da GPU se sobressai com relação a usar somente a CPU. Esse processo de ordenação e organização também já é realizado ao utilizar a funcionalidade pronta do Pytorch.

Primeiro, vamos aos imports:
# Imports here
import torch
from torch import nn
import torch.nn.functional as F
from torchvision import models
from torch.utils.data import Dataset, DataLoader
from torchvision import transforms, utils
from torchvision.datasets import ImageFolder
import matplotlib.pyplot as plt
import PIL
import numpy as np
import json
Aqui fica uma dica importante: será necessário reiniciar o runtime no Colab logo após instalar o PIL.
Outro passo importante é verificar se o processamento por GPU está habilitado antes de esperar horas de treinamento.
# check if CUDA is available
train_on_gpu = torch.cuda.is_available()
if not train_on_gpu:
    print('CUDA is not available.  Training on CPU ...')
else:
    print('CUDA is available!  Training on GPU ...')
Agora, a criação dos objetos que gerenciam as imagens: o bloco a seguir contém muita informação, de uma vez, mas vou tentar explicar parte por parte. Fazemos dois objetos de gerenciamente de imagens: um para o conjunto de treino e outro pro de testes, lembrando que é uma boa prática usar conjuntos de imagens diferentes para esta finalidade (afinal, se usassemos o mesmo conjunto do treino, nossa acurácia pareceria muito maior do que realmente é). Repare que as transformações para cada um desses conjuntos é diferente: no treino queremos "dificultar" pra rede neural fazendo recortes em locais aleatórios e invertendo a imagem no eixo horizontal randomicamente. A operação de normalização com esses "números mágicos" pode ser detalhada na documentação do Pytorch, e é necessária quando usamos modelos pré-treinados, pois estes modelos usaram essa normalização no seu  treino. Finalmente, escolhemos um tamanho de batch: se for usar GPU, é uma boa pratica usar uma potência de 2, entre 8 e 128 para este parâmetro, dependendo da capacidade da sua placa de vídeo. Preste atenção para a transformação ToTensor: basicamente ela faz uma conversão do tipo de matriz (como as imagens são armazenadas pelo computador) para um Tensor, que também é uma matriz mas com algumas funcionalidades extras que o Pytorch usa.
data_dir = 'flower_data'
train_dir = data_dir + '/train'
valid_dir = data_dir + '/valid'
# TODO: Define your transforms for the training and validation sets
data_transforms = transforms.Compose([
                                      transforms.Resize(330),
                                      transforms.RandomResizedCrop(224),
                                      transforms.RandomHorizontalFlip(),
                                      transforms.ToTensor(),
                                      transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
                                     ])


valid_transforms = transforms.Compose([ transforms.Resize(256),
                                        transforms.CenterCrop(224),
                                        transforms.ToTensor(),
                                        transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
                                        ])

# TODO: Load the datasets with ImageFolder
train_dataset = ImageFolder(train_dir,data_transforms)
valid_dataset = ImageFolder(valid_dir,valid_transforms)

# TODO: Using the image datasets and the trainforms, define the dataloaders
# define dataloader parameters
batch_size = 32
num_workers=0
# prepare data loaders
train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=batch_size, 
                                           num_workers=num_workers, shuffle=True)
valid_loader = torch.utils.data.DataLoader(valid_dataset, batch_size=batch_size, 
                                          num_workers=num_workers, shuffle=True)
Finalmente, vamos fazer uma função para mostrar o que acabamos de carregar, e verificar se está tudo ok. Primeiro, acessamos o arquivo json com os nomes das flores. Como o objeto de carregamento prepara as imagens para o Pytorch, teremos que que converter de volta para numpy. Finalmente, do mesmo jeito que normalizamos as imagens para serem usadas pela rede pre-treinada, agora precisamos fazer a operação inversa.

# obtain one batch of training images
dataiter = iter(train_loader)
images, labels = dataiter.next()
images = images.numpy() # convert images to numpy for display

mean = np.array([0.485, 0.456, 0.406])
std = np.array([0.229, 0.224, 0.225])

# plot the images in the batch, along with the corresponding labels
fig = plt.figure(figsize=(25, 4))
for idx in np.arange(20):
    ax = fig.add_subplot(2, 20/2, idx+1, xticks=[], yticks=[])
    plt.imshow(np.clip(std * np.transpose(images[idx], (1, 2, 0)) + mean,0,1))
    #print(labels[idx].numpy())
    ax.set_title(cat_to_name[str(labels[idx].numpy())])
A próxima etapa será a criação do modelo usando uma rede pré-treinada, mas isso ficará para um próximo post. Abraço!

Comentários

Top 3 em 1 ano: