Coletânea Python do ZERO às Redes Neurais Artificiais

Fernando Feltrin · Capítulo 264 de 287

Páginas do PDF

Coletânea Python do ZERO às Redes Neurais Artificiais

Classificação Multiclasse – Aprendizado de Máquina Simples – Iris Dataset

Classificação Multiclasse – Aprendizado de

Máquina Simples – Iris Dataset

 

Em 1936, o estatístico e biólogo britânico Ronald Fisher publicou seu artigo intitulado The use of multiple measurements in taxonomic problems, que em tradução livre seria algo como O Uso de Múltiplas Medições em Problemas Taxonômicos, onde publicou seu estudo classificatório das plantas do tipo Íris quanto às características dos formatos de suas pétalas e sépalas em relação ao seu tamanho.

 

Fazendo o uso desse artigo científico como base, Edgar Anderson coletou e quantificou os dados para finalmente classificar estas amostras em 3 tipos de flores diferentes a partir de métodos computacionais estatísticos publicando em 2012 o seu estudo An Approach for Iris Plant Classification Using Neural Network, que em tradução livre seria algo como Uma Abordagem para Classificação das Plantas Íris Usando Redes Neurais.

O método de classificação utilizado em ambos estudos foi igualmente dividir o conjunto em grupos de amostras e separá-las de acordo com o comprimento e a largura em centímetros das pétalas e das sépalas das mesmas, já que este era um padrão entre aqueles tipos de flores na natureza. Vale lembrar que a metodologia de classificação dessas amostras em sua época foi manual e estatística, agora o que faremos é aplicar um modelo computacional para classificação das mesmas e, até mais importante que isso, criar um modelo que possa ser aplicado para novas classificações.

Por fim, para pormos em prática os métodos de classificação de dados

utilizaremos da Iris Dataset, que nada mais é do que um dos conjuntos de dados inclusos na biblioteca SKLearn, baseado no trabalho de Ronald Fisher. *Tal modelo pode ser aplicado para diferentes situações e tipos de dados, aqui utilizaremos o Iris Dataset para inicialmente entendermos as mecânicas usadas quando temos de classificar amostras de vários tipos.

Inicialmente faremos uma abordagem mais simples aplicando alguns

modelos classificatórios e no capítulo subsequente estaremos aplicando uma metodologia baseada em rede neural artificial densa.

Como já comentado anteriormente, estaremos aprendendo de forma

procedural, quanto mais formos avançando nossos estudos, mais prática será a abordagem.

 

Partindo para o Código:

 

Como sempre, todo processo se inicia com a importação das bibliotecas e módulos que serão utilizados ao longo do código e que não fazem parte das bibliotecas pré-alocadas da IDE. Aqui inicialmente iremos utilizar a Iris Dataset que está integrada na biblioteca SKLearn para fins de estudo.

Quando estamos importando uma biblioteca inteira simplesmente executamos o comando import seguido do nome da biblioteca, aqui o que faremos é importar parte do conteúdo de uma biblioteca, é bastante comum importarmos algum módulo ou função no lugar de toda uma biblioteca. Por meio do comando from seguido de sklearn.datasets seguido de import load_iris, estamos importando a sub biblioteca de exemplo load_iris, do módulo datasets da biblioteca sklearn.

Selecionando e executando esse código (Ctrl + ENTER) se não houve nenhum erro de sintaxe você simplesmente não terá nenhuma mensagem no console, caso haja algum traceback significa que houve algum erro no processo de importação.

Em seguida criamos uma variável de nome base que recebe como atributo todo conteúdo contido em load_iris. Sem parâmetros mesmo.

 

Dando uma olhada no Explorador de Variáveis, é possível ver que foi criada uma variável reconhecida como objeto parte da biblioteca sklearn.

 

Clicando duas vezes sobre a variável é possível explorar a mesma de forma visual. Assim é possível reconhecer que existem 4 colunas (Chave, Tipo, Tamanho e Valor) assim como 6 linhas (DESCR, data, feature_names, filename, target e target_names).

Uma das etapas que será comum a praticamente todos os exemplos que iremos utilizar é uma fase onde iremos fazer o polimento desses dados, descartando os que não nos interessam e reajustando os que usaremos de forma a poder aplicar operações aritméticas sobre os mesmos.

Podemos aplicar essa etapa diretamente na variável ou reaproveitar partes do conteúdo da mesma em outras variáveis. Aqui, inicialmente faremos o reaproveitamento, em exemplos futuros trabalharemos aplicando esse polimento diretamente sobre o banco de dados inicial.

 

Podemos visualizar tais dados de forma manual por meio da função .print( ), dessa forma, teremos a exibição do conteúdo em sua forma normal no console/terminal.

 

Retorno obtido em print(base.data), aqui se encontram os dados das medições em forma de uma matriz com 150 amostras/registros.

 

Retorno obtido em print(base.target), onde é possível ver que para cada uma das 150 amostras anteriores existe um valor referente a sua classificação indo de 0 a 2.

Retorno obtido em print(base.target_names), onde podemos relacionar a numeração anterior 0, 1 e 2 agora com os nomes dos três tipos de plantas, 0 para setosa, 1 para versicolor e 2 para virgínica.

 

Em seguida criamos três variávels, entradas que recebe como atributo os dados contidos apenas em data da variável base. saidas que recebe como atributo os dados de target da variável base e por fim uma variável de nome rotulos que recebe o conteúdo de target_names da variável base.

 

Se todo processo correu normalmente no explorador de variáveis é possível visualizar tanto as variáveis quanto seu conteúdo.

Relacionando os dados, em entradas temos os quatro atributos que foram coletados para cada uma das 150 amostras (tamanho e comprimento das pétalas e das sépalas). Em saidas temos a relação de que, de acordo com os parâmetros contidos em entradas tal planta será classificada em um dos três tipos, 0, 1 e 2. Por fim, de acordo com essa numeração podemos finalmente saber de acordo com a classificação qual o nome da planta.

Por meio do comando .shape podemos de fato verificar o tamanho dessas matrizes de dados para que possamos fazer o cruzamento e/ou aplicar funções aritméticas sobre os mesmos.

 

Selecionando e executando esse bloco de código podemos verificar via terminal que data tem um formato de 150 linhas e 4 colunas, assim como saidas tem 150 linhas e uma coluna.

Terminada a fase de polimento dos dados hora de começarmos a finalmente processar os mesmos para transformá-los de dados brutos para informação estatística.

 

Aplicando o Modelo KNN

Agora iremos pôr em prática a análise estatística dos nossos dados, para isto começaremos usando o modelo KNN, que do inglês K Nearest Neighbors, em tradução livre significa Vizinhos Próximos de K. Neste tipo de modelo estatístico teremos um item de verificação por convenção chamado K e o interpretador com base nos dados fornecidos fará a leitura e exibirá como resultado os dados mais próximos, que mais se assemelham a K.

Em outras palavras, definiremos um parâmetro, onde de acordo com a proximidade (ou distância) do valor da amostra para esse parâmetro, conseguimos obter uma classificação.

Repare no gráfico, o ponto vermelho representa K, o número/parâmetro que definiremos para análise KNN, próximo a K existem 3 outros pontos, um referente a classe A e dois referentes a classe B. O modelo estatístico KNN trabalhará sempre desta forma, a partir de um número K ele fará a classificação dos dados quanto a proximidade a ele.

Pela documentação do KNN podemos verificar que existe uma série de etapas a serem seguidas para aplicação correta deste modelo:

 

1º - Pegamos uma série de dados não classificados.

2º - O interpretador irá mensurar a proximidade desses dados quanto a sua semelhança.

3º - Definimos um número K, para que seja feita a análise deste e de seus vizinhos.

4º - Aplicamos os métodos KNN.

5º - Analisamos e processamos os resultados.

Para aplicar o modelo estatístico KNN não precisamos criar a estrutura lógica mencionada acima do zero, na verdade iremos usar tal classificador que já vem pronto e pré-configurado no módulo neighbors da biblioteca sklearn.

Assim como fizemos no início de nosso código, iremos por meio do comando from importar apenas o KneighborsClassifier de dentro de sklearn, muita atenção a sintaxe do código, para importação correta você deve respeitar as letras maiúsculas e minúsculas da nomenclatura.

 

Logo após criamos uma variável de nome knn que recebe como atributo KneighborsClassifier, que por sua vez tem o parâmetro n_neighbors = 1, o que significa que tais dados serão classificados quanto a proximidade de 1 número em relação a sua amostra vizinha. Este parâmetro, inclusive, pode ser alterado para realização de outros testes de performance posteriormente.

Em seguida aplicamos sobre knn a função .fit( ), essa função por sua vez serve para alimentar o classificador com os dados a serem processados. Repare que knn.fit( ) recebe como parâmetro todos dados de entradas e saidas. Por fim, também podemos fazer uma simples previsão por meio da função .predict( ), aqui, pegando os dados de uma linha de nossa base de dados podemos realizar uma previsão sobre ela.

 

Após selecionar todo bloco de código anterior e executá-lo, acompanhando via console podemos ver que a criação do classificador foi feita de maneira correta assim como a alimentação do mesmo com os valores contidos em entradas e saidas. Via console também é possível ver o retorno da função de previsão sobre os valores retirados de uma das linhas da matriz base.

O retorno por hora foi um simples “0” o que de imediato não significa muita coisa. Será comum você ter de apresentar seus dados e suas conclusões, seja para fins de estudo ou profissionais, e mostrar um zero pode não significar nada para quem não entende do assunto. Para um resultado mais claro vamos associar este resultado inicial com os rótulos que separamos lá no início desse código.

 

Para isso criamos uma variável de nome especie que recebe como atributo nosso classificador knn e a função .predict( ) que por sua vez recebe como parâmetro outra linha da matriz escolhida aleatoriamente. Note que ao final do código de atribuição existe um parâmetro em forma de posição de lista [0].

Executando uma função print( ) sobre especie, e associando o conteúdo da mesma com o de rotulos como retorno deveremos ter o rotulo associado a posição 0 da lista de especies.

 

De fato, é o que acontece, agora como resultado das associações temos como retorno ‘virginica’ o que é uma informação muito mais relevante do que ‘0’.

Seguindo com a análise dos dados, um método bastante comum de ser aplicado é o de treino e teste do algoritmo, para que de fato se possa avaliar a performance do modelo. Na prática imagine que você tem uma amostra a ser classificada, feita toda a primeira etapa anterior, uma maneira de obter um viés de confirmação é pegar novamente a base de dados, dividir ela em partes e as testar individualmente, assim é possível comparar e avaliar a performance com base nos testes entre si e nos testes em relação ao resultado anterior.

Todo processo pode ser realizado por uma ferramenta dedicada a isso da biblioteca sklearn, novamente, tal ferramenta já vem pré-configurada para uso, bastando fazer a importação, a alimentação e a execução da mesma.

 

Executando o comando acima, respeitando a sintaxe, importamos a ferramenta train_test_split do módulo model_selection da biblioteca sklearn.

 

A seguir criamos quatro variáveis dedicadas a conter amostras dos dados para treino e para teste do modelo, por convenção as chamamos de etreino (entradas par treino), eteste (entradas para teste), streino (saídas para treino) e steste (saídas para teste).

Todas elas recebem como atributo a função train_test_split( ) que por sua vez recebe como parâmetro os dados contidos em entradas, saidas e um terceiro parâmetro chamado test_size, aqui definido como 0.25, onde 25% das amostras serão dedicadas ao treino, consequentemente 75% das amostras serão usadas para teste.

Você pode alterar esse valor livremente, porém é notado que se obtém melhores resultados quando as amostras não são divididas igualmente (50%/50%), lembrando também que uma base de treino muito pequena pode diminuir a precisão dos resultados.

 

Em seguida usamos novamente nossa variável de nome knn que já executa sobre si a função .fit( ) que por sua vez recebe como parâmetro os dados contidos em etreino e streino. Dando sequência criamos uma variável de nome previsor que executa como atributo a função knn.predict( ) passando como parâmetro para a mesma os dados contidos em eteste.

Selecionando e executando todo bloco de código acima é possível ver que as devidas variáveis foram criadas.

 

Selecionando as variáveis com dados de saída é possível fazer a comparação entre elas e avaliar de forma visual se os valores encontrados em ambos os testes condizem. É normal haver disparidade entre esses dados, e isso é uma consequência normal de termos uma base de dados pequena, e a reduzir ainda mais no processo de a dividir para treino e teste. Raciocine que aqui nossa base de dados contém apenas 150 amostras, dependendo do contexto você terá bases de dados de centenas de milhares de amostras/registros, além de seus atributos. Quanto menor for a base de dados, menos é a precisão na taxa de acertos para previsões.

Além de dividir as amostras e as testar individualmente, outro processo bastante comum é usar ferramentas que de fato avaliam a performance de nossos modelos, uma das mais comuns a ser usada é a ferramenta accuracy_score da biblioteca sklearn.

 

Um pouco diferente das importações realizadas anteriormente, aqui não temos como importar apenas a ferramenta em questão, ela depende de outras funções contidas no mesmo pacote em seu módulo, sendo necessário importar todo o módulo do qual ela faz parte. Para isso simplesmente importamos o módulo metrics da biblioteca sklearn por meio do código acima.

 

Prosseguindo com o código, criamos uma nova variável de nome margem_acertos que recebe como atributo a execução de metrics.accuracy_score, que por sua vez recebe como parâmetro os dados de steste e de previsor.

 

Selecionando e executando o bloco de código anterior é criada a variável margem_acertos, que nos mostra de forma mais clara um valor de 0.947, o que em outras palavras significa que o resultado das comparações dos valores entre steste e previsores tem uma margem de acerto de 94%.

Outra metodologia bastante comum é, retornando ao KNN, alterar aquele parâmetro n_neighbors definido anteriormente como 1 e realizar novos testes, inclusive é possível criar um laço de repetição que pode testar e exibir resultados com vários parâmetros dentro de um intervalo.

Aqui vamos diretamente ao bloco de código inteiro, supondo que você domina a lógica de laços de repetição em Python. Inicialmente o que fazemos é criar uma variável valores_k que recebe como atributo uma chave vazia. Em seguida definimos que o valor inicial de k é 1. Sendo assim, criamos uma estrutura de laço de repetição onde, enquanto o valor de k for menor que 25 é executado o seguinte bloco de código: Em primeiro lugar nossa variável knn recebe o classificador mas agora seu parâmetro não é mais 1, mas o valor que estiver atribuído a k. Em seguida knn é alimentada pela função .fit( ) com os dados contidos em etreino e streino. Logo após criamos uma variável previsores_k que recebe a função previsora sob os dados contidos em eteste.

Na sequência criamos uma variável de nome acertos que realiza o teste de previsão sobre os valores contidos em steste e previsores_k. Sendo assim valores_k recebe cada valor atribuído a k e replica esse valor em acertos. Por fim k é incrementado em 1 e todo laço se repete até que k seja igual a 25.

Selecionando e executando todo esse código é criada a variável valores_k onde podemos visualizar que foram realizados testes, cada um com um valor de k diferente, entre 1 e 25 com sua respectiva margem de acerto.

Dessa forma, para essa pequena base de dados, podemos ver que vários valores diferentes de k tem uma altíssima margem de acertos, em bases maiores ou com dados mais heterogêneos, é mais comum que esses valores de k sejam bastante divergentes entre si. Aqui nesse exemplo o valor mais baixo encontrado foi de 94% de precisão mesmo.

Outra situação bastante comum é, uma vez que temos informações relevantes a partir de nossos dados, criar mecanismos para que os mesmos possam ser exibidos de forma mais clara. Uma das coisas mais comuns em ciência de dados é apresentar os dados em forma de gráfico. Para isso também usaremos uma ferramenta de fácil integração e uso em nossa IDE, chamada matplotlib.

 

A importação dessa biblioteca segue os mesmos moldes usados anteriormente, por meio do comando import e podemos referenciá-la como plt por comodidade.

 

Raciocine que esta plotagem em gráfico é uma forma simples e visual de apresentar os dados via console/terminal. Sendo assim, primeiramente criamos uma função plt.plot( ) que recebe como atributos, em forma de lista, as chaves e os valores de valores_k, respectivamente as referenciando por .keys( ) e por .values( ). Assim podemos ter um plano cartesiano com eixos X e Y onde tais dados serão exibidos.

 

Inclusive uma prática comum é definir rótulos para os eixos X e Y do gráfico para que fique de ainda mais fácil interpretação.

 

Executando a função plt.show( ) o gráfico será gerado e exibido em nosso console.

Analisando o gráfico podemos ver que no eixo X (horizontal) existe a relação dos valores de k no intervalo entre 1 e 25. Já no eixo Y (vertical) podemos notar que foram exibidos valores entre 95% e 100% de margem de acerto.

Apenas por curiosidade, muito provavelmente você replicou as linhas de código exatamente iguais as minhas e seu gráfico é diferente, note que a cada execução do mesmo código o gráfico será diferente porque a cada execução do mesmo ele é reprocessado gerando novos valores para todas as variáveis.

 

Aplicando Regressão Logística

Outra prática bastante comum que também pode ser aplicada sobre nosso modelo é a chamada Regressão Logística, método bastante usado para realizar previsões categóricas. Ou seja, testar amostras para descobrir qual a probabilidade da mesma fazer parte de um tipo ou de outro.

 

Como sempre, o processo se inicia com a importação do que for necessário para nosso código, nesse caso, importamos a ferramenta LogisticRegression do módulo linear_model da biblioteca sklearn por meio do comando acima.

 

Inicialmente criamos uma variável de nome regl que recebe como atributo o regressor LogisticRegression, sem parâmetros mesmo. Em seguida já o alimentamos com os dados de etreino e streino por meio da função .fit( ).

 

Repare em seu console que, como nesse caso, quando criamos uma variável e atribuímos a ela alguma ferramenta, sem passar nenhum parâmetro, a mesma é criada normalmente com seus parâmetros pré-configurados, se no código não haviam parâmetros não significa que tal ferramenta internamente não possua parâmetros, toda ferramenta já vem pré-configurada dessa forma, com valores padrão, você pode se sentir à vontade para ler a documentação de tal ferramenta e estudar a fundo o que cada parâmetro faz.

Aqui ao longo dos exemplos serão dadas as devida explicações para os parâmetros que estão em uso, os internos devem ser consultados na documentação.

 

Dando sequência, aplicando sob nossa variável regl as funções .predict( ) e .predict_proba( ), passando como parâmetro uma linha escolhida aleatoriamente em nossa amostra, é realizada a devida previsão em forma de regressão logística.

 

Via console é possível ver o retorno da função .predict( ) que é array([2]), lembrando que nossa classificação é para valores entre 0 e 1 e 2, tal amostra analisada é do tipo 2 (virginica). Já o retorno de predict_proba( ) é interessante pois podemos ver que ele nos mostra uma array([[0.00, 0.14, 0.85]]) o que nos mostra que tal amostra tinha 0% de chance de ser do tipo 0, 14% de chance de ser do tipo 1 e 85% de chance de ser do tipo 2, o que é uma informação relevante do funcionamento do algoritmo.

 

Por fim criamos uma variável de nome previsor_regl que recebe como atributo o previsor de regl sob os valores de eteste. Por último criamos uma variável de nome margem_acertos_regl que aplica a função de taxa de precisão com base nos valores de steste e previsor_regl.

Executando o último bloco de código podemos ver que são criadas as respectivas variáveis, uma com os valores previstos com base nos testes, assim como sua taxa de precisão (usando inclusive um dos valores de k que haviam dado como resultado 100% de precisão anteriormente), pois a taxa de previsão sobre as amostras se manteve em 100%.

Sendo assim terminamos o entendimento básico do processamento e análise de dados sobre a Iris Dataset usando modelo de rede neural simples.

 

Código Completo: