DEV Community

Cover image for Um "Hello World" em Deep Learning
msc2020
msc2020

Posted on • Originally published at dev.to

Um "Hello World" em Deep Learning

Este post apresenta um passo a passo para rodar um modelo de Deep Learning (DL) que realiza uma tarefa que até pouco tempo atrás era um desafio. A partir de uma base de dados, devidamente organizada e rotulada, contendo dezenas de milhares de dígitos (0 a 9) escritos a mão, o modelo de DL será capaz de predizer qual é o dígito da imagem de entrada. Como nem todo mundo possui uma letra tão bonita, essa tarefa não é trivial, principalmente para um computador.

Modelos desse tipo, são comumente usados para identificar de forma automática o conteúdo de texto em documentos, PDFs, imagens, entre outros. Com poucas alterações no que veremos, é possível classificar outros conteúdos de outros datasets. No final do post falamos um pouco sobre isso.

Vamos lá!? ☕


Pré-requisitos

Para executar os códigos deste post precisamos das seguintes bibliotecas do Python 3:

  • Matplotlib (pip install matplotlib)
  • Numpy (pip install numpy)
  • Scikit-learn (pip install scikit-learn)

Dataset MNIST

Criado em 1994, o dataset MNIST (Modified National Institute of Standards and Technology) é bastante utilizado na área de visão computacional e processamento de imagens. A versão atual é composta por 60k imagens de treino e 10k de teste. Cada amostra do MNIST é uma imagem 28x28 em tons de cinza e representa um dígito manuscrito que assume valores entre 0 e 9.

dígitos do MNIST

Imagem de amostras do MNIST.

Download do MNIST: Usaremos a versão do dataset MNIST disponibilizada neste site: http://yann.lecun.com/exdb/mnist/. Devemos entrar no site e baixar os 4 datasets disponibilizados, colocando-os num diretório. Para facilitar criamos uma pasta chamada datasets e colocamos os arquivos .gz baixados nele.

Download MNIST

Site para download do MNIST. http://yann.lecun.com/exdb/mnist/

Carregando o dataset

Usaremos as seguintes funções para abrir o MNIST, pois o mesmo está no formato .gz.

import gzip
import struct
import numpy as np

def load_dataset(path_dataset):
    with gzip.open(path_dataset,'rb') as f:
        magic, size = struct.unpack('>II', f.read(8))
        nrows, ncols = struct.unpack('>II', f.read(8))
        data = np.frombuffer(f.read(), dtype=np.dtype(np.uint8).newbyteorder('>'))
        data = data.reshape((size, nrows, ncols))
        return data

def load_label(path_label):
     with gzip.open(path_label,'rb') as f:
        magic, size = struct.unpack('>II', f.read(8))
        data = np.frombuffer(f.read(), dtype=np.dtype(np.uint8).newbyteorder('>'))        
        return data
Enter fullscreen mode Exit fullscreen mode

O código abaixo carrega as 4 partes do dataset MNIST: train-images-idx3-ubyte.gz (conjunto de treino); train-labels-idx1-ubyte.gz (labels do conjunto de treino); t10k-images-idx3-ubyte.gz (conjunto de teste) e t10k-labels-idx1-ubyte.gz (labels do conjunto de teste).

X_train = load_dataset(r'./datasets/train-images-idx3-ubyte.gz')
y_train = load_label(r'./datasets/train-labels-idx1-ubyte.gz')
X_test = load_dataset(r'./datasets/t10k-images-idx3-ubyte.gz')
y_test = load_label(r'./datasets/t10k-labels-idx1-ubyte.gz')
Enter fullscreen mode Exit fullscreen mode

Após carregar os datasets, checamos se suas dimensões estão dentro do esperado.

print(X_train.shape) # valor esperado: (60000, 28, 28)
print(y_train.shape) # (60000,)
print(X_test.shape) # (10000, 28, 28)
print(y_test.shape) # (10000, )
Enter fullscreen mode Exit fullscreen mode

Note que a dimensão das imagens dos conjuntos de treino e de teste é 28 x 28.


Histograma dos labels

Uma etapa importante na construção dos datasets em DL é considerar classes balanceadas. Datasets de treino com classes desbalanceadas costumam introduzir um viés, fazendo com que as predições favoreçam algumas classes ao invés de outras. Por ex., se existe uma grande quantidade de amostras do dígito 1 e poucas do dígito 7, é comum que o modelo aprenda esse padrão e tente reproduzi-lo nas predições que fizer. Nesse caso, o modelo tende a achar que "tudo" se parece mais com o dígito 1 e considera o dígito 7 como algo menos comum. Como o MNIST possui 10 classes, referentes aos dígitos de 0 a 9, então é esperado que possuam distribuições semelhantes.

Curiosidade: Na prática, o viés pode levar a falhas brutais. Por ex., um caso ocorrido em 2022 levou um inocente a ficar preso por 26 dias devido a um erro "algorítmico", causando danos e prejuízos reais. Para mais detalhes do caso clique aqui.

Para avaliar a distribuição dos labels de treino do MNIST usaremos o histograma:

import matplotlib.pyplot as plt

fig, axs = plt.subplots(figsize=(15, 5), dpi=70)
x_ticks = np.arange(min(y_train), max(y_train)+2, 1)
bins = x_ticks - 0.5
plt.hist(y_train, edgecolor='k', rwidth=0.8, bins=bins, color='#ABCDEF')
plt.title('Histograma do label de treino y_train', size=16)
plt.xlabel('Classe - Label', size=14)
plt.ylabel('Frequência', size=14)
plt.xticks(x_ticks)
plt.show()
Enter fullscreen mode Exit fullscreen mode

Histograma dos labels de treino

O gráfico acima mostra que as classes do dataset MNIST estão balanceadas. Sendo assim, seguimos para a etapa de treinar e utilizar os modelos treinados para predizer dígitos manuscritos de entrada (input).


Aplicando modelos de Machine Learning e Deep Learning

Para avaliar a performance do modelo de Deep Learning que usaremos, também vamos realizar predições dos dígitos manuscritos com um modelo de Machine Learning (ML).

Nota: Não entraremos nos detalhes do que é ou não é Machine Learning e Deep Learning. No entanto, vale ter em mente que os modelos de Deep Learning costumam ser considerados como um subconjunto dos modelos de Machine Learning. Isso porque eles são modelos capazes de aprender de forma automática (sem intervenção humana) e fazer predições baseadas naquilo que aprenderam. A grande diferença entre os dois é que os modelos de DL são capazes de aprender mais detalhes. Além disso, sua formulação é mais complexa e sua arquitetura, as famosas redes neurais artificiais, geralmente possuem inúmeras camada ocultas.

Uma rede MLP

Imagem de uma MLP. (Rede gerada com ajuda do site https://alexlenail.me/NN-SVG)

O fato de um determinado modelo ser classificado como X ou Y, não mudará sua natureza intrínseca. Questões de nomenclatura/taxonomia geralmente buscam organizar e facilitar o entendimento do objeto de estudo e não criar barreiras ou complexidades extras.

Modelo SGD

O modelo SGD (Stochastic Gradient Descent) SGDClassifier do scikit-learn será o modelo de ML que iremos compara com o de DL. Ele realiza o ajuste de um modelo SVM (Support Vector Machine). O código a seguir treina/ajusta o SGDClassifier sobre o dataset MNIST. Note que usamos uma etapa de pré-processamento com o StandardScaler, que transforma a média dos dados para 0 e o desvio padrão igual a 1. Mais detalhes sobre podem ser encontrados em sua documentação.

from sklearn.linear_model import SGDClassifier
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import make_pipeline

model_sgd_classifier = make_pipeline(StandardScaler(),
                                     SGDClassifier(random_state=2024))

model_sgd_classifier.fit((X_train).reshape(len(X_train), 28*28),
                         y_train)
Enter fullscreen mode Exit fullscreen mode

Após o treinamento do modelo SGDClassifier, já podemos realizar as predições.

Modelo Multilayer Perceptron Classifier

Agora apresentaremos de fato o "Hello World!" (Olá, Mundo!) em DL. O modelo escolhido para isso é uma rede neural conhecida como Multilayer Percepetron ou MLP. Sua arquitetura geralmente possui 3 ou mais camadas ocultas. Esse modelo foi um marco, pois possui capacidade de aprender padrões não lineares e ser treinado em computadores pessoais. Além disso, suas implementações atuais, costumam ter um bom desempenho para tarefas um tanto complexas, como classificar dígitos manuscritos. Embora para os humanos, reconhecer dígitos ou letras seja algo aparentemente simples (principalmente no idioma raiz), para os computadores nem sempre foi assim. No caso do MNIST, de 1998 a 2012 houve um grande salto na performance dos modelos, passando de 88% de acerto para 99.77%.

O código abaixo treina o modelo MLPClassifier, uma implementação de um MLP classificador. Ele também está disponível na biblioteca open-source Scikit-learn.

from sklearn.neural_network import MLPClassifier
model_mlp = MLPClassifier(max_iter=300, random_state=2024)
model_mlp.fit((X_train/255).reshape(len(X_train), 28*28), y_train)
Enter fullscreen mode Exit fullscreen mode

Realizando as predições com os modelos treinados

Agora que treinamos os modelos, realizamos as predições usando o método predict. A seguir realizamos a predição sobre o conjunto de testes (X_test).

print(model_sgd_classifier.score(X_test.reshape(len(X_test), 28*28), y_test))
print(model_mlp.score(X_test.reshape(len(X_test), 28*28), y_test))

'''
saída esperada:

0.9027
0.9784
'''
Enter fullscreen mode Exit fullscreen mode

Comparando o valor do score de ambos, notamos que o desempenho do MLPClassifier foi maior. Vale a pena comentar que ambos modelos se saíram muito bem, com assertividades acima de 90%. No entanto, a diferença entre eles pode ser considerada alta, já que foi de 0.9784 - 0.9027 = 0.0757. Ou seja, cerca de 7.57% .

Salvando e carregando um modelo

As etapas de treinamento podem demorar e consumir recursos como energia, vida útil da CPU ou GPU entre outros. Dessa forma, é interessante salvarmos o modelo treinado em nosso computador e carregá-lo sempre que quisermos realizar uma predição.

Para salvar o modelo utilizamos a biblioteca pickle, responsável por serializar (passar de objeto Python/Scikit-learn para binário) e deserializar (fazer o processo contrário). Como o pickle é uma biblioteca padrão do Python 3, não é necessário sua instalação, basta importá-la de forma usual com o import.

import pickle

output = open('model_classifier_mnist.pkl', 'wb')

pickle.dump(model_mlp, output, -1)

output.close()
Enter fullscreen mode Exit fullscreen mode

O código acima salva o modelo MLPClassifier já treinado (objeto model_mlp) com o nome model_classifier_mnist.pkl. Note que usamos o método dump() do pickle para realizar essa tarefa.

Já para carregar o modelo salvo, utilizamos o load():

pickle_file = open('model_classifier_mnist.pkl', 'rb')

model_test = pickle.load(pickle_file)

pickle_file.close()
Enter fullscreen mode Exit fullscreen mode

Teste com uma amostra aletória

Para finalizar, vamos realizar um simples teste que sorteia de forma aleatória um dígito manuscrito e realiza a predição com ambos modelos. Será que ambos modelos vão reconhecer o dígito manuscrito?

idx_random = np.random.randint(0, len(X_train)) # sorteia um índice
print(f'idx_random = {idx_random}')

some_digit = X_train[idx_random] # seleciona o valor dos pixels no conjunto de treino

# exibe o dígito manuscrito como uma imagem
fig, axs = plt.subplots(figsize=(29, 29), dpi=60)
plt.imshow(some_digit, cmap='binary')
plt.axis('off')
plt.show()
Enter fullscreen mode Exit fullscreen mode

Uma amostra aleatória

# realiza predição do dígito sorteado com os modelos MLP e SGD
print(model_mlp.predict([some_digit.reshape(28*28)]))
print(model_sgd_classifier.predict([some_digit.reshape(28*28)]))

'''
saída esperada:

[2]
[8]
'''
Enter fullscreen mode Exit fullscreen mode

Embora a tendência seja que ambos modelos acertem, visto que escolhemos uma amostra do conjunto de treino, apenas o modelo MLP acertou a predição do dígito sorteado. Para dificultar um pouco mais, podemos escolher uma amostra do conjunto de testes ou fazer uma imagem manuscrita com nossa própria letra e usar como entrada.

Datasets semelhantes

Outros exemplos de datasets que poderíamos ter usado ao invés do MNIST:

  • CIFAR-10: possui 10 classes com imagens de aviões, cães, gatos, carros, caminhões, etc.

  • Fashion MNIST: possui 10 classes com imagens de roupas, sapatos, bolsas e outros itens semelhantes.

  • CIFAR-100: possui 100 classes, contendo imagens como exemplos de animais, veículos, árvores e flores.

Esperamos que tenham gostado e agradecemos a leitura!

🤖 ☕ 🗿 🦥 ♟️

Top comments (0)