Coletânea Python do ZERO às Redes Neurais Artificiais

Fernando Feltrin · Capítulo 265 de 287

Páginas do PDF

Coletânea Python do ZERO às Redes Neurais Artificiais

Classificação Multiclasse via Rede Neural Artificial – Iris Dataset

Classificação Multiclasse via Rede Neural Artificial

– Iris Dataset

 

Ao longo deste livro estaremos usando alguns datasets de exemplo disponibilizados em domínio público por meio de repositórios. Um dos maiores repositórios é o UCI Machine Learning Repository, mantido por algumas instituições educacionais e por doadores, conta atualmente com mais de 470 datasets organizados de acordo com o tipo de aprendizado de máquina a ser estudado e treinado.

 

Na página inicial é possível ver que, da listagem de datasets mais populares o Iris ocupa a primeira posição, até mesmo em outras comunidades ele costuma ser o mais popular, sendo o passo inicial para a maioria dos cientistas de dados que iniciam seus estudos em aprendizado de máquina.

Abrindo a página dedicada ao Iris Dataset podemos ter uma descrição detalhada sobre do que se trata esse banco de dados assim como suas características. O que de imediato nos importa é que, como trabalhado anteriormente, trata-se de um banco de dados com 150 amostras das flores do tipo iris, divididas em 4 atributos previsores que usaremos para classificar tais amostras em 3 tipos de plantas diferentes.

A única diferença por hora é que antes estávamos usando a base de dados exemplo contida na biblioteca SKLearn, agora iremos aprender como trabalhar com dados a partir de planilhas Excel, que será o mais comum eu seu dia-a-dia. Na própria página da Iris Dataset é possível fazer o download do banco de dados que iremos utilizar, uma vez feito o download do arquivo iris.csv podemos dar início ao código.

Abrindo o arquivo iris.csv em nosso Excel é possível verificar como os dados estão organizados nessa base de dados.

 

Partindo para o Código:

 

Todo processo se inicia com a importação das bibliotecas e módulos que iremos utilizar ao longo de nosso código. Para este exemplo usaremos a biblioteca Pandas, o processo de importação segue os mesmos moldes de sempre, por meio do comando import assim como a referência pelo comando as como no exemplo acima.

 

Em seguida criamos uma variável de nome base, por meio da função read_csv( ) do pandas conseguimos fazer a importação dos dados a partir de uma planilha Excel. Note que no parâmetro, em formato de string passamos o nome do arquivo assim como sua extensão, caso você tenha o arquivo com outro nome basta fazer a importação com o nome de seu arquivo.

Outro ponto interessante é que nesse formato o interpretador busca o arquivo no mesmo local onde está salvo seu arquivo de código .py, caso você tenha salvo em uma pasta diferente é necessário colocar todo o caminho em forma de string como parâmetro.

 

Se o processo de importação ocorreu da maneira certa no Explorador de Variáveis será possível ver a variável base assim como explorar seu conteúdo de forma visual.

 

Repare que o objeto em questão é um DataFrame, este é um modelo parecido com o de matriz usado no exemplo anterior, onde tínhamos uma array do tipo Numpy, aqui, o sistema de interpretador do Pandas transforma os dados em um DataFrame, a grosso modo a grande diferença é que um DataFrame possui uma modelo de indexação próprio que nos será útil posteriormente quando for necessário trabalhar com variáveis do tipo Dummy em exemplos mais avançados.

Por hora, você pode continuar entendendo que é uma forma de apresentação dos dados com linhas e colunas, parecido com o modelo matricial.

 

Logo após criamos nossas variáveis que armazenarão os dados separados como características das amostras (entradas) assim como o tipo de planta (saidas). Para isso criamos nossa variável entradas que por meio da função .iloc[ ] e do comando .values pega todos valores contidos em todas as linhas e das 4 primeiras colunas de base (desconsiderando a coluna de índice).

O mesmo é feito com nossa variável saidas, por meio da mesma função pega como atributo todos os valores de todas as linhas, mas somente da 4ª coluna (lembrando que as colunas com informação começam em 0, a 4ª coluna é a que contém os dados de saída).

 

Dessa forma separamos os dados de entrada e de saída de nossa base de dados, permitindo o cruzamento desses dados ou a aplicação de funções sobre os mesmos.

 

Quando estamos fazendo nossos ajustes iniciais, na chamada fase de polimento dos dados, um dos processos mais comuns a se fazer é a conversão de nossos dados de base, todos para o mesmo tipo. Raciocine que é impossível fazer, por exemplo, uma multiplicação entre texto e número.

Até o momento os dados que separamos estão em duas formas diferentes, os atributos de largura e comprimento das pétalas e sépalas em nossos dados de entrada são numéricos enquanto os dados de saída, referente a nomenclatura dos tipos de plantas classificadas, estão em formato de texto. Você até pode tentar fazer o cruzamento desses dados, mas terá um traceback mostrando a incompatibilidade entre os mesmos.

Sendo assim, usaremos uma ferramenta capaz de converter texto em número para que possamos finalmente aplicar funções sobre os mesmos. A ferramenta em questão é a LabelEncoder, do módulo preprocessing da biblioteca sklearn. O processo de importação é feito como de praxe. Em seguida criamos uma variável labelencoder que inicializa essa ferramenta.

 

Repare no explorador de variáveis que por hora o objeto saidas não é reconhecido corretamente pelo Numpy.

 

Aplicando a função .fit_transform( ) de labelencoder sobre nossa variável saidas fazemos a devida conversão de dados do formato string para int.

 

Executada a linha de código anterior, note que agora saidas possui seus dados em formato numérico, dentro de um intervalo entre 0 e 2.

Na sequência, outro processo que teremos de fazer, uma vez que processaremos nossos dados em uma rede neural densa, é a criação de variáveis do tipo Dummy. Variáveis Dummy são variáveis que criam uma indexação para nossa saída.

Lembre-se que aqui estamos classificando 150 amostras em 3 tipos diferentes, porém os neurônios da camada de saída retornarão valores 0 ou 1. Dessa forma, precisamos criar um padrão de indexação que faça sentido tanto para o usuário quanto para o interpretador.

Em outras palavras, teremos de criar um padrão numérico binário para 3 saídas diferentes de acordo, nesse caso, com o tipo de planta. Por exemplo

Tipo de Planta Neurônio Neurônio Neurônio

1 2 3

Setosa 0 0 1 Versicolor 0 1 0 Virginica 1 0 0

 

A nível de código, fazemos a importação da ferramenta np_utils, parte do módulo utils da biblioteca keras. Em seguida criamos uma variável de nome saidas_dummy que recebe sobre si como atributo a função np_utils.to_categorical( ) para que sejam criados os respectivos números de indexação como na tabela exemplo acima, para cada uma das amostras da base de dados.

 

Selecionando e executando mais esse bloco de código podemos ver que foi criada a variável saidas_dummy, abrindo-a você verá o padrão como o da tabela, para cada uma das 150 amostras.

 

Dando sequência, como havíamos feito no exemplo anterior, aqui novamente podemos dividir nossa base de dados em partes e realizar testes com essas partes para avaliação da performance do modelo. Também é possível aplicar o processamento da rede diretamente sobre nossa base de dados, porém uma prática recomendável para se certificar da integridade dos dados é os processar em partes distintas e realizar a comparação.

Dessa forma, criamos quatro variáveis dedicadas a partes para treino e teste de nossa rede neural. Logo etreino, eteste, streino, steste recebem como atributo train_test_split, que por sua vez recebe como parâmetro os dados contidos em entradas, saidas_dummy e por fim é definido que 25% das amostras sejam separadas para teste, enquanto os outros 75% serão separadas para treino.

 

Se todo processo correu normalmente é possível visualizar no explorador de variáveis tais objetos.

 

Finalmente vamos dar início a criação da estrutura de nossa rede neural densa, como sempre, inicialmente é necessário fazer as devidas importações para que possamos usufruir das ferramentas adequadas.

Então do módulo models da biblioteca keras importamos Sequential, da mesma forma, do módulo layers importamos Dense. Sequential é um modelo pré-configurado onde conseguimos criar e fazer as devidas conexões entre camadas de neurônios, Dense por sua vez permite a conexão entre todos nós da rede assim como a aplicação de funções em todas etapas da mesma.

Numa representação visual como fizemos anteriormente com os perceptrons, uma rede densa como a que estaremos criando agora seguiria uma estrutura lógica como a da figura acima.

 

Inicialmente criamos uma variável de nome classificador, que recebe como atributo a inicialização da ferramenta Sequential, aqui, por enquanto, sem parâmetros mesmo. Em seguida por meio da função .add( ) começamos de fato a criação da camada de entrada e da sua comunicação com a primeira camada oculta.

Para isso passamos como parâmetro para add Dense que por sua vez tem como parâmetros units = 4, ou seja, 4 neurônios na primeira camada oculta, activation = ‘relu’ (Rectfied Linear Units, em tradução livre Unidades Linearmente Retificadas, algo como a StepFunction usada no exemplo anterior, porém entre camadas de redes neurais quando aplicamos uma função degrau é por meio da relu), que é o método de ativação dessa camada, e por fim input_dim = 4, que é o parâmetro que determina quantos neurônios existem em nossa camada de entrada, aqui, 4 tipos de características das plantas representam 4 neurônios da camada de entrada.

 

Logo após criamos mais uma camada oculta, também com 4 neurônios e também recebendo como função de ativação relu. Repare que aqui não é mais necessário especificar os neurônios de entrada, raciocine que esta camada é a conexão entre a primeira camada oculta e a camada de saída dessa rede neural.

 

Dando sequência criamos a camada de saída dessa rede, nos mesmos moldes das anteriores, porém com a particularidade que na camada de saída temos 3 neurônios, já que estamos classificando as amostras em 3 tipos, e o método de ativação agora é o ‘softmax’, método esse que segundo a documentação do keras é o que mais se aproxima da função sigmoide que usamos anteriormente, a diferença basicamente é que sigmoid oferece a probabilidade de ser de um tipo ou de outro (2 tipos), enquanto softmax suporta mostrar a probabilidade de uma amostra ser classificada em 3 ou mais tipos.

 

Prosseguindo, por meio da função .compile( ) criamos o compilador para nossa rede neural, nesta etapa é necessário passar três parâmetros, o primeiro deles, optimizer = ‘adam’, com o nome autoexplicativo, aplica alguns métodos para otimização dos resultados dos processamentos, loss = ‘categorical_crossentropy’ é a nossa função de perda, raciocine que em uma rede neural há uma pequena parcela das amostras que podem ter dados inconsistentes ou erros de processamento, estas caem nessa categoria, o parâmetro da função de perda por sua vez tentará pegar essas amostras fora da curva e tentar encaixar de acordo com a semelhança/proximidade, a outras amostras que foram processadas corretamente, por fim metrics = [‘categorical_accuracy’] faz a avaliação interna do modelo, mensurando sua precisão quanto ao processo de categorizar corretamente as amostras.

 

Por fim, por meio da função .fit( ) faremos a alimentação de nossa rede e definiremos os parâmetros de processamento dela. Sendo assim, .fit( ) recebe como parâmetros os dados de etreino e de streino, batch_size = 10 diz respeito a taxa de atualização dos pesos, ou seja, de quantas em quantas amostras serão testadas, corrigidas e testadas e por fim epochs = 1000 define quantas vezes a rede neural será executada.

Esses parâmetros na prática devem inclusive serem modificados e testados para tentar sempre encontrar a melhor performance do algoritmo. Em certos casos a taxa de atualização pode influenciar bastante a diminuição da margem de erro, assim como executar a rede neural mais vezes faz o que em algumas literaturas é chamado de aprendizagem por reforço, toda rede neural começa com rápidas atualizações e correções, porém ela chega em níveis onde após a execução de algumas milhares (ou até mesmo milhões) de vezes executada ela pode acabar estagnada em um valor.

 

Selecionando todo bloco de código da estrutura da rede neural e executando, você pode acompanhar pelo terminal a mesma sendo executada, assim como acompanhar a atualização da mesma. Aqui, neste exemplo, após a execução da mesma 1000 vezes para o processo de classificação, categorical_accuracy: 0.98 nos mostra que a taxa de precisão, a taxa de acertos na classificação das 150 amostras foi de 98%.

Tenha em mente que bases de dados grandes ou redes que são executadas milhões de vezes, de acordo com seu hardware essa rede pode levar um tempo considerável de processamento.

Aqui nesse exemplo, 150 amostras, 3 categorias, 1000 vezes processadas, toda tarefa é feita em poucos segundos, mas é interessante ter esse discernimento de que grandes volumes de dados demandam grande tempo de processamento.

Terminada a fase de processamento da rede neural, hora de fazer algumas avaliações de sua performance para conferir e confirmar a integridade dos resultados.

 

Para isso inicialmente criamos uma variável de nome avalPerformance que recebe como atributo a função .evaluate( ) de nosso classificador, tendo como parâmetros os dados de eteste e steste. Também criamos uma variável de nome previsoes, que recebe a função .predict( ) sobre os dados de eteste.

Por fim, criamos uma variável de nome previsoesVF que basicamente retorna os resultados de previsões em formato True ou False, por meio da condicional previsoes > 0.5.

Selecionando e executando esse bloco de código é possível ver a criação das devidas variáveis.

 

Abrindo avalPerformance temos dados para comparar com os do processamento da rede neural. A rede neural havia nos retornado uma taxa de precisão de 98%, enquanto aqui realizado testes de outras formas pela função .evaluate( ) o retorno foi de 97%. O retorno de loss function da rede havia sido de 0.05%, o da evaluate 0.03%.

O que confirma uma excelente precisão nos resultados dos processamentos, tenha em mente que essa fase de avaliação é dada até mesmo como opcional em algumas literaturas, porém para uma atividade profissional é imprescindível que você se certifique ao máximo da integridade de seus resultados.

Abrindo previsoes podemos ver a probabilidade de cada amostra ser de um determinado tipo, por exemplo, na linha 0, em azul, há o dado probabilístico de 99% de chance dessa amostra ser do tipo 1 (versicolor), na segunda linha, 99% de chance de tal amostra ser do tipo 2 (virginica) e assim por diante.

Abrindo previsoesVF podemos ver de forma ainda mais clara que a amostra da linha 0 é do tipo 1, que a amostra da linha 1 é do tipo 2, e assim subsequente para todas as amostras.

Outra metodologia de avaliação de nosso modelo é feita pela chamada

Matriz de Confusão, método que nos mostrará quantas classificações foram feitas corretamente e as que foram classificadas erradas para cada saída. Para aplicar a matriz de confusão em nosso modelo precisaremos fazer algumas alterações em nossos dados, assim como fizemos lá no início na fase de polimento.

 

Dessa vez importamos a biblioteca Numpy e a referenciamos como np. Em seguida criamos uma variável de nome steste2 que recebe como atributo a função np.argmax(t), que pega os valores de steste e gera uma indexação própria, contendo a posição da matriz onde a planta foi classificada. O mesmo é feito em previsoes e atribuído para previsoes2.

 

Logo a seguir fazemos mais uma importação necessária, dessa vez, da ferramenta confusion_matrix do módulo metrics da biblioteca sklearn.

 

Dando sequência criamos uma variável de nome matrizConfusao que por sua vez realiza a aplicação dessa ferramenta sobre previsoes2 e steste2.

Selecionando e executando o bloco de código acima é possível ver que foi criada a variável matrizConfusao, explorando seu conteúdo podemos fazer a leitura, em forma de verificação que: Para o tipo de planta 0, foram classificadas 13 mostras corretamente, para o tipo de planta 1, foram classificadas 12 amostras corretamente, e para o tipo de planta 2, foram classificadas 12 amostras de maneira correta e 1 amostra de maneira incorreta.

Finalizando este modelo, outra prática muito usada que é interessante já

darmos início a seu entendimento é a chamada Validação Cruzada. Este método é de suma importância para se certificar de que seus testes não estão “viciados”, uma vez que uma rede neural mal configurada pode entrar em loops de repetição ou até mesmo retornar resultados absurdos. A técnica em si consiste em pegar nossa base de dados inicial, separar a mesma em partes iguais e aplicar o processamento da rede neural individualmente para cada uma das partes, para por fim se certificar que o padrão de resultados está realmente correto.

 

Dessa vez importamos a ferramenta cross_val_score do módulo model_selection da biblioteca sklearn.

 

E m seguida criamos uma rede interna em forma de função chamada

ValCruzada( ), sem parâmetros mesmo. Repare que sua estrutura interna é a mesma da rede criada anteriormente com a exceção que agora não a alimentaremos por meio da função .fit( ) mas sim fazendo o uso do KerasClassifier.

Como o KerasClassifier ainda não havia sido incorporado em nosso código é necessário fazer a importação do mesmo, nos mesmos moldes de sempre.

 

Criamos uma variável a executar a ferramenta KerasClassifier de nome classificadorValCruzada. Tal ferramenta por sua vez recebe como parâmetros buind_fn = valCruzada, o que significa que a função de ativação desse classificador é a própria rede neural criada anteriormente de nome valCruzada, outro parâmetro é epochs = 1000, ou seja, definindo que o processamento da rede será feito mil vezes e por fim batch_size = 10, parâmetro que defini de quantos em quantos pesos seus valores serão corrigidos.

 

Na sequência criamos uma variável chamada resultadosValCruzada que executará a própria ferramenta cross_val_score, que por sua vez recebe como parâmetros estimator = classificadorValCruzada, o que em outras palavras nada mais é do que aplicar essa função sobre o processamento da rede valCruzada( ), X = entradas e y = saidas, auto sugestivo, variáveis temporárias com os dados contidos em entradas e saidas, cv = 10, parâmetro que define em quantas partes nossa base de dados será dividida igualmente para ser processada pela rede e por fim scoring = ‘accuracy’ define o método a ser formatados os resultados, aqui, com base em precisão de resultados.

Abrindo a variável resultadosValCruzada é possível ver de forma bruta, mas fácil conversão para percentual, os resultados do processamento da rede sobre cada amostra. Note que aqui houve inclusive uma exceção onde o processamento em cima da amostra nº 8 teve uma margem de acerto de apenas 60%, enquanto para todas as outras a margem ficou dentro de um intervalo entre 93% e 100%. Isto pode acontecer por diversos fatores, os mais comuns, erros de leitura dos dados da base ou aplicação de parâmetros errados sobre a rede neural.

 

Por fim, apenas para apresentar nossos dados de forma mais clara, criamos uma variável de nome media que recebe o valor médio entre todos valores obtidos em resultadosValCruzada, assim como é interessante apresentar o cálculo do desvio (algo como a margem de erro em percentual), aqui inclusive, este valor vai sofrer um grande impacto devido ao resultado da validação cruzada sobre a 8ª amostra.

 

Via explorador de variáveis podemos ver facilmente os valores de media (taxa de acerto de 94%) e de desvio (11% de margem de erro para mais ou para menos, o que é um número absurdo, porém justificável devido ao erro de leitura da 8ª amostra anterior).

 

Código Completo: