Coletânea Python do ZERO às Redes Neurais Artificiais

Fernando Feltrin · Capítulo 267 de 287

Páginas do PDF

Coletânea Python do ZERO às Redes Neurais Artificiais

Regressão de Dados de Planilhas Excel – Autos Dataset

Regressão de Dados de Planilhas Excel – Autos

Dataset

Uma vez entendidos os conceitos básicos do que é uma rede neural artificial e sua estrutura lógica e de código quando o problema computacional proposto é uma classificação de dados, hora de gradualmente avançarmos para outros modelos, não necessariamente de maior complexidade, mas modelos que elucidarão as propostas de outros tipos de redes neurais desenvolvidas para outros fins.

Um dos tipos de redes neurais artificiais mais comumente utilizados são

as redes que aplicam métodos de regressão. Em outras palavras, com base em uma grande base de dados, treinaremos uma rede para reconhecer padrões que permitirão encontrar por exemplo, o valor médio de um automóvel usado se comparado a outros de mesmo tipo.

Aqui como exemplo usaremos o Autos Dataset, uma base de dados com

milhares de carros usados catalogados como em uma revenda. Aproveitaremos esse modelo para ver como se dá o processo de polimento dos dados para criarmos uma base com valores mais íntegros, capazes de gerar resultados mais precisos.

 

A base de dados que iremos usar neste exemplo pode ser baixada gratuitamente no Kaggle, um repositório de machine learning parecido com o ACI, porém mais diversificado e com suporte de toda uma comunidade de desenvolvedores.

 

Usando a ferramenta de busca do site você pode procurar por Used cars database. O dataset em si, como exibido na imagem acima, trata-se de uma base de dados de aproximadamente 370 mil registros de anúncios de carros usados retirado dos classificados do Ebay. Tudo o que precisamos fazer é o download da base de dados no formato .csv.

 

Abrindo o arquivo pelo próprio Excel pode-se ver que é uma daquelas típicas bases de dados encontradas na internet, no sentido de haver muita informação faltando, muitos dados inconsistentes, muito a ser pré-processado antes de realmente começar a trabalhar em cima dessa base.

Dando início ao que interessa, como sempre, todo código começa com as devidas importações das bibliotecas e módulos necessários, por hora, simplesmente importamos a biblioteca pandas para que possamos trabalhar com arrays.

Em seguida já criamos uma variável de nome base que pela função .read_csv( ) realiza a leitura e importação dos dados de nosso arquivo autos.csv, note que aqui há um parâmetro adicional encoding = ‘ISO-8859-1’, este parâmetro nesse caso se faz necessário em função de ser uma base de dados codificada em alguns padrões europeus, para não haver nenhum erro de leitura é necessário especificar a codificação do arquivo para o interpretador.

 

Feita a importação da base de dados, podemos conferir em nosso explorador de variáveis que o objeto foi importado e reconhecido como um DataFrame, contendo 371528 amostras com 20 atributos, respectivamente.

Abrindo a variável para uma rápida análise visual é possível ver que existem muitos dados faltando, ou inconsistentes, que literalmente só atrapalharão nosso processamento. Sendo assim, nessa fase inicial de polimento desses dados vamos remover todos os atributos (todas as colunas) de dados que não interessam à rede.

 

Aplicando diretamente sobre nossa variável base a função .drop( ) passando como parâmetro o nome da coluna e seu eixo, podemos eliminá-la sem maiores complicações. Lembrando apenas que o parâmetro axis quando definido com valor 0 irá aplicar a função sobre uma linha, enquanto valor 1 aplicará a função sobre uma coluna.

 

Base original.

 

Após execução da linha de código anterior. Note que eliminamos a coluna ‘dateCrawled’, e como aplicamos a função sobre nossa variável, eliminamos essa coluna de forma permanente.

 

Sendo assim, por meio da função .drop( ) podemos remover de imediato as colunas que não fazem sentido manter em nossa base de dados, em seguida, podemos verificar se uma ou mais colunas pode ser eliminada em relação a inconsistência de seus dados.

 

Para tal feito selecionamos manualmente o nome de uma coluna seguido da função .value_counts( ) que irá apresentar esses dados agrupados quanto a igualdade.

Acompanhando via console, podemos notar que, por exemplo Ford_Fiesta aparece 657 vezes em nossa base de dados, o problema é que o interpretador está fazendo esse levantamento de forma literal, existem 657 “Ford_Fiesta” assim como devem existir outras dezenas ou centenas de “Ford Fiesta”.

Esse tipo de inconsistência é muito comum ocorrer quando importamos de plataformas onde é o usuário que alimenta com os dados, em função de cada usuário cadastrar seu objeto/item sem uma padronização de sintaxe.

Da mesma forma repare, por exemplo, que existe apenas uma amostra de Audi__TT_Tuev_Neu_Service_Neu!!!!, isso se dá pela forma da escrita do nome do objeto, de fato, ninguém mais iria anunciar seu Audi TT escrevendo aquele nome exatamente daquela forma. Sendo assim, a coluna ‘names’ pode ser eliminada em função de sua inconsistência.

 

Nesta etapa, eliminamos três colunas em função de suas inconsistências em seus dados.

 

Outro processo comumente realizado é tentarmos filtrar e eliminar de nossa base de dados amostras/registros com valores absurdos. Aqui como exemplo inicial criamos uma variável de nome varTeste1 que recebe como atributo base com o método .loc onde queremos descobrir de nossa base, no atributo price, todas amostras que tem valor igual ou menor que 10.

Lembre-se que estamos trabalhando em uma base de dados de carros usados, é simplesmente impossível haver carros com preço igual ou menor a 10 Euros, com certeza isso é inconsistência de dados também.

 

Selecionando a linha de código anterior e a executando note que é criada a variável varTeste1, repare que existem 12118 amostras com valor igual ou menor que 10, tantas amostras com valores tão errados certamente iriam atrapalhar o processamento de nossa rede neural posteriormente.

 

Aplicando este método base.price > 10 diretamente em nossa base, fazemos com que a mesma seja atualizada mantendo apenas os valores de registros com o atributo ‘price’ maior que 10.

Da mesma forma, filtramos e eliminamos de nossa base de dados todos registros com respectivo preço maior que 350000. Totalizando 115 amostras eliminadas da base.

 

Para finalizar nossa fase de polimento dos dados, outro processo bastante comum é corrigir os valores que estiverem literalmente faltando em nossa base de dados. Por meio do método .loc e da função .isnull( ) sobre nossa base, passando como parâmetro ‘vehicleType’, estaremos fazendo este tipo de verificação. Assim como visto anteriormente que pela função .value_counts( ) iremos ver esses dados agrupados quanto a sua nomenclatura.

 

Via console podemos acompanhar o retorno da função .isnull( ). Muito cuidado para não confundir a nomenclatura, neste caso, ja e nein significam sim e não, respectivamente, enquanto NaN é uma abreviação gerada pelo nosso interpretador quando realmente nesta posição da lista/dicionário existem dados faltando.

Em outras palavras, ja e nein são dados normais, presentes na classificação, NaN nos mostra que ali não há dado nenhum.

Acompanhando o retorno da função .value_counts( ) temos os números quanto aos agrupamentos. Esse dado nos será importante porque, podemos pegar o veículo que contem mais amostras, o mais comum deles, e usar como média para substituir os valores faltantes em outras categorias. A ideia é essa mesma, para não eliminarmos esses dados pode inconsistência, podemos preencher os espaços vazios com um valor médio.

 

Da mesma forma repetimos o processo para todas colunas onde houverem dados faltando (NaN), descobrindo os dados/valores médios para preencher tais espaços vazios.

 

Em seguida criamos uma variável de nome valores_medios que recebe em forma de dicionário os parâmetros e seus respectivos dados/valores médios encontrados anteriormente.

Uma vez identificados os dados faltantes em nossa base de dados, e descobertos os dados/valores médios para cada atributo previsor, hora de preencher esses espaços vários com dados. Aplicando sobre nossa variável base a função .fillna( ), passando como parâmetro os dados do dicionário valores_medios, finalmente fazemos a devida alimentação e temos uma base de dados onde possamos operar sem problemas.

 

Com nossa base de dados íntegra, sem colunas desnecessárias, sem dados inconsistentes ou faltantes, podemos finalmente dividir tais dados em dados de entrada e de saída. Para isso criamos uma variável de nome entradas que recebe todos os valores de todas as linhas e todos valores das colunas 1 até a 13 por meio dos métodos .iloc[ ] e .values. Da mesma forma, criamos uma variável de nome saidas que recebe os valores de todas as linhas da coluna 0.

 

Se o processo ocorreu como esperado, podemos ver que de fato foram criadas as variáveis entradas e saidas. Entradas por sua vez tem um total de 12 colunas de atributos previsores e 359291 linhas de amostras a serem processadas.

Note que estes números são bem inferiores aos da base em sua forma original, bruta. É absolutamente normal no processo de polimento eliminarmos boa parte da base de dados, em ciência de dados na verdade estamos mais preocupados com a integridade do que do número de dados a serem processados.

Como até o momento, mesmo após o polimento inicial, temos uma base de dados mista, contendo dados tanto em formato int (números) quanto string (texto), será necessário fazer a conversão desses dados todos para um tipo de dado de mesmo tipo.

Para isso, da mesma forma como já feito no exemplo anterior, usaremos da ferramenta LabelEncoder para converter tudo em números a serem processados pela rede neural. O processo de importação se dá como de costume.

 

Como primeira parte desse processo de conversão, criamos nossa variável labelencoder que inicializa a ferramenta LabelEncoder, sem parâmetros mesmo. Na sequência sobre a coluna 0 de nossa base de dados aplicamos a função .fit_transform( ) que converte e atualiza os dados da coluna 0.

O processo é feito para todas as colunas onde teremos de converter texto em número, ou seja, é replicado o processo também nas colunas 1, 2, 5, 8, 9 e 10.

 

Como última parte desse processo de conversão, precisamos transformar esses dados em algo que seja indexado e interpretado pelo interpretador de nossa IDE corretamente. Tal processo é feito pela ferramenta OneHotEncoder. Importada como de costume do módulo preprocessing da biblioteca sklearn.

 

Em seguida criamos nossa variável de nome onehotencoder que recebe como atributo a ferramenta OneHotEncoder, que por sua vez recebe como parâmetro categorical_features = [0,1,3,5,8,9,10], em outras palavras, agora é feito a transformação de números brutos para valores categóricos indexados internamente.

Logo após aplicamos sobre nossa variável entradas a função .fit_transform da ferramenta onehotencoder, convertendo tudo para uma array pela função .toarray( ).

 

Note que após feitas as devidas conversões houve inclusive uma mudança estrutural na forma de apresentação dos dados. Os dados de entradas agora por sua vez possuem 359291 amostras categorizadas em 316 colunas. Não se preocupe, pois, essa conversão é internamente indexada para que se possam ser aplicadas funções sobre essa matriz.

Também é possível agora abrir a variável e visualmente reconhecer seu novo padrão.

Finalmente encerrada toda a fase de polimento de nossos dados, podemos dar início a próxima etapa que é a criação da estrutura de nossa rede neural artificial.

Como de praxe, todo processo se inicia com as importações das bibliotecas e módulos que serão usados em nosso código e que nativamente não são carregados nem pré-alocados em memória. Para este exemplo também estaremos criando uma rede neural densa (multicamada interconectada) e sequencial (onde cada camada é ligada à sua camada subsequente.

 

A estrutura se mantém muito parecida com as que construímos anteriormente, primeira grande diferença é que no lugar de um classificador estamos criando um regressor. Note que na primeira camada existem 316 neurônios, na segunda 158 e na última apenas 1.

Outro ponto importante de salientar é que o processo de ativação da camada de saída agora é ‘linear’, este tipo de parâmetro de ativação é o mais comum de todos, uma vez que ele na verdade não aplica nenhuma função ao neurônio de saída, apenas pega o dado que consta nele e replica como valor final.

No processo de compilação repare que tanto a função de perda quanto a métrica é definida como ‘mean_absolute_error’, em outras palavras, os pesos a serem corrigidos serão considerados de forma normal, absoluta, consultando a documentação do Keras você verá que outra função muito utilizada é a ‘squared_absolute_error’, onde o valor de erro é elevado ao quadrado e reaplicado na função como forma de penalizar mais os dados incorretos no processamento, ambos os métodos são válidos em um sistema de regressão, inclusive é interessante você realizar seus testes fazendo o uso dos dois.

Estrutura criada, por meio da função .fit( ) alimentamos nossa rede com os dados de entradas, saidas, definimos que os pesos serão atualizados a cada 300 amostras e que a rede por sua vez será executada 100 vezes. Como sempre, é interessante testar taxas de atualização diferentes assim como executar a rede mais vezes a fim de que ela por aprendizado de reforço encontre melhores resultados.

 

Terminado o processamento da rede note que o valor retornado foi de 2242.14, o que em outras palavras significa que a variação de valor desses carros em geral varia em 2242,14 euros para mais ou para menos.

 

Como de costume, sobre esses dados iniciais podemos aplicar nossos métodos preditivos. Para isso criamos uma variável de nome previsoes que aplica a função .predict( ) em nosso regressor passando os dados contidos em entradas como parâmetro.

Explorando de forma visual a variável previsoes podemos ver que de fato é feito um processamento interno usando outras funções aritméticas que resultam em diferentes valores para cada uma das amostras.

 

Via console podemos notar a proximidade dos resultados obtidos, o que nos indica que a rede está configurada corretamente. Uma grande disparidade entre esses valores pode indicar erros de processamento da rede.

Dando sequência podemos realizar sobre este modelo a mesma técnica de validação cruzada aplicada no modelo anterior. Como sempre, importamos o que é necessário, nesse caso a ferramenta cross_val_score do módulo model_selection da biblioteca sklearn.

Também estaremos aplicando e comparando resultados por meio do regressor que a biblioteca keras já tem dentro de si pré-configurado. Para isso importamos KerasRegressor do módulo wrappers.scikit_learn da biblioteca keras.

 

Prosseguindo, criamos nosso regressorValCruzada( ) em forma de função. Internamente repare que a estrutura de rede neural é a mesma anterior. Usaremos essa função/rede para realizar novos testes.

 

Para isso criamos nossa variável regValCruzada que recebe como atributo o KerasRegressor, que por sua vez tem como parâmetro build_fn = regressorValCruzada, o que em outras palavras ele inicializa nossa função criada antes internamente, executando a rede 100 vezes, atualizando os pesos a cada 300 amostras.

Na sequência, criamos nossa variável de nome resValCruzada, que irá executar a validação cruzada propriamente dita, então, por sua vez, resValCruzada recebe como atributo cross_val_score que tem como parâmetros estimator = regValCruzada, ou seja, aplica seu processamento sobre os dados obtidos de nossa função regressorValCruzada( ), usando X e y como dados de parâmetro, dados a serem usados diretamente de nossa base para comparação.

Na sequência é definido que cv = 10, em outras palavras, toda base será dividida em 10 partes iguais e testadas individualmente, por fim scoring = ‘neg_mean_absolute_error’ fará a apresentação da média dos resultados obtidos, desconsiderando o sinal dos mesmos, apenas os números em seu estado bruto.

 

Selecionando e executando todo bloco de código é possível acompanhar via console a rede neural sendo executada, lembrando que esta etapa costuma ser bastante demorada uma vez que toda rede será executada uma vez para treino e mais 10 vezes para testar cada parte da base.

Terminado o processo de validação cruzada é possível ver os valores obtidos para cada uma das partes da base de dados testada individualmente.

Finalizando nosso exemplo, criamos as variáveis dedicadas a apresentar os resultados finais de nossa validação cruzada para as devidas comparações com os dados de nossa rede neural artificial.

 

E o importante a destacar é que, como nos exemplos anteriores, estes testes são realizados como um viés de confirmação para termos certeza da integridade de nossos dados, aqui, realizados os devidos testes, podemos concluir que os resultados são próximos, confirmando que a execução de nossa rede se deu de forma correta.

 

Código Completo: