Primeira vez aqui? Seja bem vindo e cheque o FAQ!
x

Predição do tipo de vegetação com base em características locais georreferenciadas

+1 voto
45 visitas
perguntada Jun 30 em Ciência da Computação por MCarolina Marques (36 pontos)  

A ideia do exercício é prever o tipo de vegetação em uma área com base nas características locais georreferenciadas, como tipo de solo, incidência de sol em vários horários do dia, distância até a água, entre outras.

Compartilhe
comentou Jun 30 por MCarolina Marques (36 pontos)  
A base de dados utilizada foi a Forest Cover Type Prediction, do Kaggle:
https://www.kaggle.com/uciml/forest-cover-type-dataset

1 Resposta

+1 voto
respondida Jun 30 por MCarolina Marques (36 pontos)  
editado Jul 1 por MCarolina Marques

Primeiro vamos importar as libraries:

from zipfile import ZipFile
import numpy as np
import pandas as pd
import seaborn as sns
from matplotlib import pyplot
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import cross_validate
from sklearn.metrics import confusion_matrix
from tqdm import tqdm
import copy
import warnings

Eu sugiro desligar os warnings, porque eles não são erros, mas formalismos que nesse nível de complexidade de código são irrelevantes.

warnings.filterwarnings('ignore')

Depois de baixar os dados do Kaggle, no link que está no comentário, é necessário deszipar o ler o CSV:

with ZipFile('coverage_type.zip','r') as dados:
    dados.extractall()
dados_df = pd.read_csv('covtype.csv')

A base não está dividida entre treino e teste, e a variável explicada está junto com as explicativas. Então precisamos separar a base:

y = dados_df.loc[:,'Cover_Type']
dados_df=dados_df.drop(columns='Cover_Type')
dados_df_train, dados_df_test, y_train, y_test = train_test_split(dados_df,y,test_size=0.974, random_state=50)

Neste caso, usei 2.6% para treino porque foi o percentual usado na competição do Kaggle, o que vai me permitir comparar meu resultado com o da competição ao final. Eu fixei o random para que a pessoa que vai corrigir possa replicar meus resultados.

Precisamos em primeiro lugar abrir a base e dar uma olhada nos dados, olhando as informações, tabulando os dados e vendo estatísticas descritivas básicas:

dados_df_train.info()
print('\n\n\n')
for i in dados_df_train.columns:
    print(i,':')
    print("maximum",i,'value',dados_df_train.loc[:,i].max())
    print("minimum",i,'value',dados_df_train.loc[:,i].min())
    print(i,'standard deviation',dados_df_train.loc[:,i].std())
    print("mean",i,'value',dados_df_train.loc[:,i].mean())
    print('\n')
    print('valores','contagem')
    print(dados_df_train.loc[:,i].value_counts().sort_index())
    print('\n\n\n')

Descobrimos que a nossa base de treino é formada por variáveis não nulas, inteiras, de até 64 bits e que existem 15.106 observações. Também observamos que os tipos de solo e wilderness areas são variáveis dummy, de valor zero ou um.

Vamos plotar as variáveis contínuas para verificar a distribuição:

for i in dados_df_train.columns[0:10]:
    sns.distplot(dados_df_train.loc[:,i])
    pyplot.show()

A imagem será apresentada aqui.
A imagem será apresentada aqui.
A imagem será apresentada aqui.
A imagem será apresentada aqui.
A imagem será apresentada aqui.
A imagem será apresentada aqui.
A imagem será apresentada aqui.
A imagem será apresentada aqui.
A imagem será apresentada aqui.
A imagem será apresentada aqui.

Agora vamos criar uma matriz de correlação para os dados que não são dummies:

correlation_matrix= dados_df_train.iloc[:].corr()
mask = np.array(correlation_matrix.iloc[0:10,0:10])
mask[np.tril_indices_from(mask)] = False
fig=pyplot.gcf()
fig.set_size_inches(30,12)
sns.heatmap(data=correlation_matrix.iloc[0:10,0:10],mask=mask,square=True,annot=True,cbar=True, cmap="Greens")

A imagem será apresentada aqui.

Percebemos uma correlação positiva alta entre:
- as distâncias vertical e horizontal até a água,
- a sombra 3 da tarde e a variável de aspecto

Percebemos uma correlação negativa alta entre:
- sombra 9 da manhã e 3 da tarde
- sombra 9 da manhã e aspecto
- sombra ao meio dia e inclinação

Como os dados são de florestas, resolvi testar um classificador de florestas aleatórias (também por isso a matriz de correlação é verde, rs).

Minha ideia é pegar o pacote básico e testar várias configurações pra ver qual fica melhor.
Em primeiro lugar, vou testar várias profundidades de árvore:
A biblioteca tqdm nos permite acompanhar o andamento dos loops, o que é ótimo para ver se está rodando ou dando pau.
A ideia é rodar várias florestas com profundidades diferentes e fazer uma cross-validação par ver qual a profundidade que oferece os melhores resultados. Eu coloquei para rodar em mais de um core paralelamente para ir um pouco mais rápido, deixando um livre para ver memes enquanto roda. A ideia é fazer uma floresta com profundidade 5, e ir aumentando a profundidade de cinco em cinco e vendo se e o quanto os resultados melhoram.

    x=[]
for i in tqdm(range(5,101,5)):
    classificador = RandomForestClassifier(n_estimators=100, max_depth=i, random_state=0, n_jobs=-2)
    resultado = cross_validate(classificador, dados_df_train, y_train, n_jobs=-2, cv=6, return_train_score=True)
    x.append({'depth':i, 'resultado_teste':np.mean(resultado['test_score']),'resultado treino':np.mean(resultado['train_score'])})
x1 = copy.deepcopy(x)
x1

[{'depth': 5,
'resultadoteste': 0.670724939225111,
'resultado treino': 0.67571831817326},
{'depth': 10,
'resultado
teste': 0.7299718644454763,
'resultado treino': 0.7732423692390172},
{'depth': 15,
'resultadoteste': 0.7751197357060288,
'resultado treino': 0.8880840230995882},
{'depth': 20,
'resultado
teste': 0.8005436438824057,
'resultado treino': 0.9676019964687539},
{'depth': 25,
'resultadoteste': 0.8102751238955138,
'resultado treino': 0.9962928537825452},
{'depth': 30,
'resultado
teste': 0.8100756616220318,
'resultado treino': 0.9999470426610175},
{'depth': 35,
'resultadoteste': 0.814446366424414,
'resultado treino': 0.9999735239606484},
{'depth': 40,
'resultado
teste': 0.8113332212241197,
'resultado treino': 0.9999735239606484},
{'depth': 45,
'resultadoteste': 0.8131865984402462,
'resultado treino': 0.9999867630317952},
{'depth': 50,
'resultado
teste': 0.8134524108888445,
'resultado treino': 0.9999867630317952},
{'depth': 55,
'resultadoteste': 0.813518679941197,
'resultado treino': 0.9999867630317952},
{'depth': 60,
'resultado
teste': 0.813518679941197,
'resultado treino': 0.9999867630317952},

O test_score aumenta de acordo com a profundidade da árvore, até a profundidade 55. Então vamos adotar essa profundidade.
Agora vamos fazer a mesma coisa, variando a quantidade de árvores na floresta. No teste de profundidade, nossa floresta tinha 100 árvores.
Esperamos que, com mais árvores, o resultado do teste melhore, pela redução do overfitting. Mas, com mais árvores, o tempo de processamento aumenta, então vamos ver se podemos reduzir ou se precisamos aumentar.

z=[]
for i in tqdm(range(10,301,10)):
    classificador = RandomForestClassifier(n_estimators=i, max_depth=55, random_state=0, n_jobs=-2)
    resultado = cross_validate(classificador, dados_df_train, y_train, n_jobs=-2, cv=6, return_train_score=True)
    z.append({'number of trees':i, 'resultado_teste':np.mean(resultado['test_score']),'resultado treino':np.mean(resultado['train_score'])})
z1 = (z)
z1

[{'number of trees': 10,
'resultadoteste': 0.7820710187425997,
'resultado treino': 0.9903481982654269},
{'number of trees': 20,
'resultado
teste': 0.8021965350686706,
'resultado treino': 0.9978551494473612},
{'number of trees': 30,
'resultadoteste': 0.8080886155206466,
'resultado treino': 0.999245328742246},
{'number of trees': 40,
'resultado
teste': 0.8094797653250804,
'resultado treino': 0.9997087172853653},
{'number of trees': 50,
'resultadoteste': 0.8099445679467004,
'resultado treino': 0.9998543596946545},
{'number of trees': 60,
'resultado
teste': 0.8106713989484726,
'resultado treino': 0.9999337993828172},
{'number of trees': 70,
'resultadoteste': 0.8123270211680932,
'resultado treino': 0.9999735260635904},
{'number of trees': 80,
'resultado
teste': 0.8125257493913404,
'resultado treino': 1.0},
{'number of trees': 90,
'resultadoteste': 0.8133202669915133,
'resultado treino': 1.0},
{'number of trees': 100,
'resultado
teste': 0.813518679941197,
'resultado treino': 0.9999867630317952},
{'number of trees': 110,
'resultadoteste': 0.8147105499050884,
'resultado treino': 1.0},
{'number of trees': 120,
'resultado
teste': 0.814511506510187,
'resultado treino': 0.9999735208049824},
{'number of trees': 130,
'resultadoteste': 0.8151733023634403,
'resultado treino': 0.9999867577731871},
{'number of trees': 140,
'resultado
teste': 0.8147758977598828,
'resultado treino': 1.0},
{'number of trees': 150,
'resultadoteste': 0.815106927705478,
'resultado treino': 1.0},
{'number of trees': 160,
'resultado
teste': 0.8155042267449105,
'resultado treino': 1.0},
{'number of trees': 170,
'resultadoteste': 0.8153057613269111,
'resultado treino': 1.0},
{'number of trees': 180,
'resultado
teste': 0.8159018407246613,
'resultado treino': 1.0},
{'number of trees': 190,
'resultadoteste': 0.8155040686070789,
'resultado treino': 1.0},
{'number of trees': 200,
'resultado
teste': 0.8164977888662897,
'resultado treino': 1.0},
{'number of trees': 210,
'resultadoteste': 0.8157025610734733,
'resultado treino': 1.0},
{'number of trees': 220,
'resultado
teste': 0.8166953876855642,
'resultado treino': 1.0},
{'number of trees': 230,
'resultadoteste': 0.8158359665487848,
'resultado treino': 1.0},
{'number of trees': 240,
'resultado
teste': 0.81510755963304,
'resultado treino': 1.0},
{'number of trees': 250,
'resultadoteste': 0.8151734079501104,
'resultado treino': 1.0},
{'number of trees': 260,
'resultado
teste': 0.8155694713732721,
'resultado treino': 1.0},
{'number of trees': 270,
'resultadoteste': 0.8153713212090263,
'resultado treino': 1.0},
{'number of trees': 280,
'resultado
teste': 0.8155697863974299,
'resultado treino': 1.0},
{'number of trees': 290,
'resultadoteste': 0.8163635162699014,
'resultado treino': 1.0},
{'number of trees': 300,
'resultado
teste': 0.816827320222811,
'resultado treino': 1.0}]

Podemos ver que, a partir de 200 árvores, os resultados começam a ficar parecidos e só aumenta o tempo de processamento. Então vamos adotar 200 árvores e profundidade 55.

Em seguida podemos introduzir um critério de parada. Vaos trabalhar com o número de features considerado em cada divisão das folhas. O padrão do algoritmo é a raiz quadrada do número de features. Como temos 54 features, ele usa 7 features. Vamos ver se escolhendo mais ou menos o desempenho melhora.

w=[]
for i in tqdm(range(2,32,3)):
    classificador = RandomForestClassifier(n_estimators=200, max_depth=55, random_state=0, n_jobs=-2, max_features=i)
    resultado = cross_validate(classificador, dados_df_train, y_train, n_jobs=-2, cv=6, return_train_score=True)
    w.append({'numero maximo de variaves em cada corte':i, 'resultado_teste':np.mean(resultado['test_score']),'resultado treino':np.mean(resultado['train_score'])})
w1 = copy.deepcopy(w)
w1

[{'numero maximo de variaves em cada corte': 2,
'resultadoteste': 0.8050424230779308,
'resultado treino': 1.0},
{'numero maximo de variaves em cada corte': 5,
'resultado
teste': 0.8108698117499138,
'resultado treino': 1.0},
{'numero maximo de variaves em cada corte': 8,
'resultadoteste': 0.8152395445140245,
'resultado treino': 1.0},
{'numero maximo de variaves em cada corte': 11,
'resultado
teste': 0.820070244716408,
'resultado treino': 1.0},
{'numero maximo de variaves em cada corte': 14,
'resultadoteste': 0.8193423906900197,
'resultado treino': 1.0},
{'numero maximo de variaves em cada corte': 17,
'resultado
teste': 0.8212606690613375,
'resultado treino': 1.0},
{'numero maximo de variaves em cada corte': 20,
'resultadoteste': 0.8227167481580316,
'resultado treino': 1.0},
{'numero maximo de variaves em cada corte': 23,
'resultado
teste': 0.8233142730499164,
'resultado treino': 1.0},
{'numero maximo de variaves em cada corte': 26,
'resultadoteste': 0.8225180988041919,
'resultado treino': 1.0},
{'numero maximo de variaves em cada corte': 29,
'resultado
teste': 0.8225178888245424,
'resultado treino': 1.0}]

O resultado melhora a partir de 17, entáo vamos adotar 20 no modelo final.

Assim, o modelo final tem 200 árvores, profundidade máxima de 55 e 20 parâmetros observados a cada divisão:

classificador = RandomForestClassifier(n_estimators=200, max_depth=55, random_state=0, n_jobs=-2, max_features=20)
classificador.fit(dados_df_train,y_train)
classificador.score(dados_df_test,y_test)

Esse classificador teve desempenho de 84.18% na previsão dos dados de teste.
De acordo com o paper e um aluno da puc rio:

esse resultado coloca esse modelo entre o 1% da competição do kaggle.

Se dividíssemos a base de dados entre teste e treino 50%, 50%, o desempenho seria ainda melhor: 95.89% de desempenho na previsão.

comentou Jun 30 por danielcajueiro (5,776 pontos)  
Oi Carol!
As imagens não estão aparecendo, nem os links. Use as ferramentas do site que o problema deve resolver.
comentou Jul 1 por MCarolina Marques (36 pontos)  
Corrigido!
Eu não tinha visto que precisava clicar dois botões para subir a imagem.
comentou Jul 6 por Monica Guo (41 pontos)  
Oi, Carolina! Gostei bastante da sua análise dos dados, principalmente da matriz de correlação, pois foi possível visualizar bem quais features estão correlacionadas. A única coisa que não ficou muito clara foi se você decidiu abandonar alguma(s) variável(is) com base nessa análise.

De modo geral, gostei bastante da sua implementação, principalmente da forma automatizada com que você avaliou as possibilidades de parâmetros (as várias combinações possíveis de profundidade, número de árvores, etc). Contudo, uma coisa que me chamou a atenção foi o fato de você ter escolhido esses parâmetros de forma sequencial. Será que não seria melhor ter avaliado todas as combinações possíveis (grid search)? Por exemplo, primeiro você definiu a melhor profundidade como sendo 55, certo? E depois você fixou esse valor de profundidade para escolher o número de árvores. A forma mais sistemática não seria avaliar todas as possíveis combinações de todos os parâmetros? Mas isso poderia demorar bastante para rodar também...
comentou Jul 13 por danielcajueiro (5,776 pontos)  
Gostei das piadas.
...