Executando verificação de segurança...
10

Criando uma inteligência artificial com Pokemon

INTELIGÊNCIA ARTIFICAL DE POKEMON


Esse aqui é um projeto de inteligencia artifical, que tem como objetivo identificar os tipos de pokemon. Aqui eu vou comentar sobre o processo e criação de uma rede neural convolucional e demonstrarei os codigos que utilizei, tamém deixarei disponivel o video que fiz sobre a criação da IA.


Bᴀsᴇ ᴅᴇ ᴅᴀᴅᴏs

→ 𝑳𝑰𝑵𝑲 𝑫𝑨 𝑩𝑨𝑺𝑬 𝑫𝑬 𝑫𝑨𝑫𝑶𝑺 ←



Antes de tudo, como qualquer inteligencia artificial necessita de informações e base de dados. Eu utilizei a base de dados publicada no kaggle, que foi criada pelo Vishal Subbiah. Que é uma coletânea de imagens de todos os Pokémon da geração 1 à geração 7(Infelizmente não tinha os pokemons do Sword e Shield para os fãs mais hardcore), junto com seus tipos (primário e secundário) em formato csv.


Bɪʙʟɪᴏᴛᴇᴄᴀs



import torch
import torch.nn as nn
from torch import optim
import torch.nn.functional as F


from torch.utils.data import DataLoader
from torchvision.utils import make_grid
from torchvision import datasets
import torchvision.transforms as transforms


import numpy as np
import matplotlib.pyplot as plt

import pandas as pd
import opendatasets as od

from torchvision.utils import make_grid

from sklearn.model_selection import train_test_split
from sklearn.preprocessing import LabelEncoder

from sklearn.metrics import confusion_matrix, accuracy_score     
from sklearn import preprocessing

import plotly.express as px
import plotly.graph_objects as go
from PIL import Image
import matplotlib.pyplot as plt

import seaborn as sns

od.download('https://www.kaggle.com/datasets/vishalsubbiah/pokemon-images-and-types')
torch.manual_seed(123)

Importei MUITOS pacote, mas o principal foi o PyTorch para construir e treinar redes neurais, além de pacotes de visualização como Matplotlib, Seaborn e plotly, e por ultimo pacotes para manipulação de dados como Pandas e Scikit-Learn. a Scikit-Learn.

Utilizei a biblioteca opendatasets para baixar o conjunto de dados do Kaggle.O que faz o arquivo automaticamente ser baixado na área do google colab (A plataforma perfeita para o meu projeto).


Exᴘʟᴏʀᴀɴᴅᴏ ᴅᴀᴅᴏs



Antes de começar qualquer construção da rede neural, eu tinha que dar uma olhada nos dados, explorar e entender como os tipos de pokemon estavam espalhadas. Precisamos de uma base sólida antes de erguer a casa. Isso não apenas facilita o desenvolvimento do modelo, mas também faz com que a gente entenda os problemas em dados.


transform = transforms.ToTensor()
le = preprocessing.LabelEncoder()
tipos = pd.read_csv('/content/pokemon-images-and-types/pokemon.csv')

Aqui, estou utilizando transforms.ToTensor() do PyTorch para transformar os dados em tensores, o que é necessário para que o PyTorch possa processá-los eficientemente em modelos de rede neural. (Isso são detalhes mais automaticos do processo, são meio entediantes, mas são necessários.)

Além disso, estou utilizando preprocessing.LabelEncoder() do Scikit-Learn para converter as categorias dos tipos de Pokémon em números. Isso é útil para que o modelo possa lidar com os dados categóricos de forma numérica durante o treinamento. Para a nossa infelicidade, ele não entende que um tipo fada é um tipo fada, ele vai ter que considerar um tipo fada como um valor em número.


rotulos = {}
for c in range(0, len(tipos)):
  rotulos[list(tipos.Name)[c]] = list(tipos.Type1)[c]
rotulos = dict(sorted(rotulos.items()))


trans = transforms.Compose([
    transforms.Resize((64, 64)), 
    transforms.ToTensor()])

imagens = datasets.ImageFolder('/content/pokemon-images-and-types/', transform=trans)


FINALMENTE, preparando as imagens dos Pokémon para serem utilizadas como entrada na rede neural, transformando todos os arquivos na pasta de imagens em tensores.

O que vai fazer uma série de configurações nas imagens, e a primeira transformação é transforms.Resize((64, 64)), que redimensiona todas as imagens para o tamanho de 64x64 pixels. Isso garante que todas as imagens tenham a mesma dimensão antes de serem alimentadas no modelo. Eu optei por não usar o tamanho original das imagens, por causa da demora do treinamento e essa quantidade é o suficiente para até mesmo nós identificarmos os pokemons, por que uma IA não conseguiria??


np.unique(list(rotulos.values()))
encoder = LabelEncoder()
rotulos = encoder.fit_transform(list(rotulos.values()))
print(np.unique(rotulos))

a = 0
for n in imagens.samples:
  imagens.samples[a] = (imagens.samples[a][0], rotulos[a])
  a+=1

img_treinamento, img_teste = train_test_split(imagens, test_size=0.10, shuffle=True)
train_loader = torch.utils.data.DataLoader(img_treinamento, batch_size=126, shuffle=True)
img_testes = torch.utils.data.DataLoader(img_teste)

Neste trecho do código, estou associando os rótulos (tipos) aos exemplos de imagens dos Pokémon carregados no dataset imagens.

Inicialmente, a é inicializado com 0 para percorrer os exemplos de imagens e rótulos. Utilizo um loop for para iterar sobre imagens.samples, que contém pares (caminho_da_imagem, índice_do_tipo). Eu acho que foi um jeito muito ruim de fazer isso e sinceramente, se alguém tiver um jeito melhor, por favor, diga aqui nós comentarios. E no final do codigo já separei as imagens que se tornariam parte do treinamento e alguns para a gente fazer a avaliação depois.


Agora que eu já tinha todas as imagens da forma que eu queria, utilizei a biblioteca Plotly Express (px) para criar um histograma baseado nos dados de tipos, para sermos capazes de visualizar a quantidade de cada tipo de pokemon que nós temos. E esse aqui foi o resultado:



Com base na imagem, o que nós podemos analisar? Primeiramente que tá tudo desequilibrado e isso já é um pessimo sinal para criar uma IA. O segundo é que o Pokémon mais comuns são Normal, Água(O que me supreendeu), Inseto e Grama. E terceiramente que os menos comuns são Fada, Dragão e Aço, que representam apenas 2,5%, 2,5% e 2,1% do total. O que de certa forma é um problema quando pensamos na ideia de criar uma rede neural, pois isso pode gerar diversos erros em relação a dispersão de dados, eu poderia retirar os pokemons em excesso e o que tem em menor quantidade, para garantir uma melhor precisão(em troca, fingir que não existem alguns tipos de pokemon), mas eu preferi manter assim.



E por que não ver nossos pokemons? Eu acabei de mostrar uma pancada de codigos, mas até agora não vimos a imagem de nenhum deles. E neste código acim, estou visualizando uma imagem específica do dataset imagens, juntamente com seu rótulo correspondente. Isso serve para que possamos visualizar melhor as imagens dos pokemons, que mesmo em 64x64 pixels, fornece uma visão agradavel e reconhecivel deles.



Mᴏᴅᴇʟᴏ



VAMO CRIAR A REDE NEURAL

class classificador(nn.Module):
  def __init__(self):
    super().__init__()

    self.conv1 = nn.Conv2d(in_channels=3, out_channels=32, kernel_size=(3,3))
    self.conv2 = nn.Conv2d(32, 32, (3, 3))
    self.activation = nn.ReLU()
    self.bnorm = nn.BatchNorm2d(num_features=32)
    self.pool = nn.MaxPool2d(kernel_size = (2,2))
    self.flatten = nn.Flatten()

    self.linear1 = nn.Linear(in_features=6272, out_features=126)
    self.linear2 = nn.Linear(126, 126)
    self.output = nn.Linear(126, 18)
    self.dropout = nn.Dropout(p = 0.2)

  def forward(self, X):
    X = self.pool(self.bnorm(self.activation(self.conv1(X))))
    X = self.pool(self.bnorm(self.activation(self.conv2(X))))
    X = self.flatten(X)

    X = self.dropout(self.activation(self.linear1(X)))
    X = self.dropout(self.activation(self.linear2(X)))
    X = self.output(X)

    return X

Aqui estou definindo a arquitetura de um modelo de rede neural convoluciona, para classificar imagens de Pokémon de acordo com seus 18 tipos. O que posso falar vai parecer bruxaria ou latim, mas basicamente o método forward, defino como os dados fluem pela rede. As imagens X passam pelas camadas, ai seguem por ReLU, normalização por batch e max pooling. Depois de serem achatadas, elas passam por duas camadas lineares com ativação ReLU e dropout aplicado entre elas. A camada de saída final fornece as previsões para as classes de tipo de Pokémon. Se você entendeu essa explicação. apenas quero lhe parabenizar, ok?

Mas resumidamente; arquitetura é projetada para aprender automaticamente características relevantes das imagens dos Pokémon através das camadas convolucionais e classificar corretamente os tipos utilizando as camadas totalmente conectadas. Falando assim fica bem mais fácil de compreender (A forma mais complexa é através de cálculo).

net = classificador()
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(net.parameters())

Agora to preparando o modelo de rede neural para classificar tipos de Pokémon. Começo instanciando o modelo `classificador`. Em seguida, seleciono função de perda, que basicamente vai corrigir os parametros da rede neural, a que eu utilizei é consideravelmente apropriada para problemas de classificação multiclasse como o nosso. E por ultimo, para otimizar o modelo durante o treinamento, configuro o otimizador, que ajustará os pesos da rede com base no gradiente da função de perda. Esses passos são fundamentais para preparar o modelo antes de iniciar o treinamento, esse blog é com o objetivo de mostrar todo o processo, mas eu não vou me estender explicando de forma matematica o que cada uma dessas funções fazem(pelo menos não nesse blog), mas se eu fosse recomendar um lugar para iniciar a compreensão por trás das redes neurais, séria na playlist de vídeos do canal Machine Learning para Humanos .


Tʀᴇɪɴᴀᴍᴇɴᴛᴏ



def training_loop(loader, epoch):
    running_loss = 0.
    running_accuracy = 0.

    for i, data in enumerate(loader):
        inputs, labels = data
        inputs, labels = inputs, labels

        optimizer.zero_grad()
        outputs = net(inputs)

        loss = criterion(outputs, labels)
        loss.backward()

        optimizer.step()

        running_loss += loss.item()

        ps = F.softmax(outputs)
        top_p, top_class = ps.topk(k = 1, dim = 1)
        equals = top_class == labels.view(*top_class.shape)

        accuracy = torch.mean(equals.type(torch.float))

        running_accuracy += accuracy

        print('\rÉpoca {:3d} - Loop {:3d} de {:3d}: perda {:03.2f} - precisão {:03.2f}'.format(epoch + 1, i + 1, len(loader), loss,
                                   accuracy), end = '\r')

    print('\rÉPOCA {:3d} FINALIZADA: perda {:.5f} - precisão {:.5f}'.format(epoch+1, running_loss/len(loader),
                     running_accuracy/len(loader)))

for epoch in range(50):
  print('Treinando...')
  training_loop(train_loader, epoch)
  net.eval()
  print('Validando...')
  training_loop(img_testes, epoch)
  net.train()

Este código executa o treinamento e validação do nosso modelo de classificação ao longo de 50 épocas. A função `training_loop` é responsável por iterar sobre os dados de treinamento e calcular a perda e a precisão do modelo a cada iteração. Após cada época de treinamento, o modelo é avaliado usando dados de teste. Durante o treinamento, os resultados são exibidos em tempo real, mostrando a perda e a precisão para cada época e lote de dados.

Aᴠᴀʟɪᴀᴄ̧ᴀ̃ᴏ


Para avaliação; usando a biblioteca Plotly para visualizar cada imagem de Pokémon, mostrando o rótulo real e a previsão da IA na tela, facilitando a análise visual dos resultados. E aqui temos na parte superior o tipo original do pokemon e abaixo da imagem a classificação da inteligencia artificial:



matriz = confusion_matrix(resultados_IA, resultados_reais)

fig = go.Figure(data=go.Heatmap(
    z=matriz,
    colorscale='hot',
    showscale=True,
    text=[[str(val) for val in row] for row in matriz], 
    texttemplate="%{text}",
    textfont={"size": 12}
))
fig.update_layout(
    font=dict(family='Courier New, monospace',  size=14, color='white'),
    plot_bgcolor='#070A0D', paper_bgcolor='#070A0D')

fig.show()

Por fim, vamos avaliar de forma grafica a nossa rede neural. Porque ver imagem por imagem não é uma forma muito legal de verificar a precisão da imagem. Nesse codigo acima usei a biblioteca Plotly para criar um gráfico de calor, para verificar o numero de acerto conforme os tipos e identificar visualmente as áreas de maior e menor desempenho da rede. O que para ser sincero? Foi bem fraco.



precisao = accuracy_score(resultados_reais, resultados_IA)

Agora, como ultima analise. Peguei o porcentual de acerto, que foi apenas de 16% da base de teste. E visualizei através de um grafico de barras todas as previsões e os valores reais também.



E comparando entre os rótulos originais e as previsões da IA para cada classe de Pokémon. É um grafico bem simples de compreender, as barras roxas representam os rótulos reais, enquanto as barras vermelhas mostram as previsões feitas pela rede neural.

E analisamos aqui como a variedade de classes não foi atingida e apenas garantindo porcentuais muito pequenos de acertos. Eu posteriormente tentei fazer outros treinamentos, utilizando a tecnica de 𝑽𝒂𝒍𝒊𝒅𝒂𝒄̧𝒂̃𝒐 𝒄𝒓𝒖𝒛𝒂𝒅𝒂, que vai estar dentro do codigo principal, mas apenas garantiu um acerto de 40% no máximo.


Cᴏɴᴄʟᴜsᴀ̃ᴏ


E chegamos ao final da nossa análise sobre a rede neural convolucional desenvolvida para identificar tipos de Pokémon a partir de suas imagens. Exploramos cada etapa do processo de avaliação, desde a comparação das previsões da IA com os rótulos reais, passando pela análise detalhada da matriz de confusão, até a visualização interativa dos resultados. A IA teve em si um desempenho muito ruim, e espero que publicando e demonstrando esse projeto, alguém consiga melhorar e além disso fazer algo com a ideia da rede neural.

Se você ficou interessado em ver o projeto em ação, não deixe de se inscrever no canal Borboleta vermelha, onde postei um vídeo sobre a rede neural.

Obrigado por acompanhar até aqui e até a próxima!

~ ɢɪᴛʜᴜʙ ᴅᴏ ᴘʀᴏᴊᴇᴛᴏ https://github.com/Borboleta-Vermelha/Pokemons


→ ʟɪɴᴋ ᴅᴏ ᴠɪᴅᴇᴏ ←


Carregando publicação patrocinada...