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()










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")

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,
'resultadoteste': 0.7299718644454763,
'resultado treino': 0.7732423692390172},
{'depth': 15,
'resultadoteste': 0.7751197357060288,
'resultado treino': 0.8880840230995882},
{'depth': 20,
'resultadoteste': 0.8005436438824057,
'resultado treino': 0.9676019964687539},
{'depth': 25,
'resultadoteste': 0.8102751238955138,
'resultado treino': 0.9962928537825452},
{'depth': 30,
'resultadoteste': 0.8100756616220318,
'resultado treino': 0.9999470426610175},
{'depth': 35,
'resultadoteste': 0.814446366424414,
'resultado treino': 0.9999735239606484},
{'depth': 40,
'resultadoteste': 0.8113332212241197,
'resultado treino': 0.9999735239606484},
{'depth': 45,
'resultadoteste': 0.8131865984402462,
'resultado treino': 0.9999867630317952},
{'depth': 50,
'resultadoteste': 0.8134524108888445,
'resultado treino': 0.9999867630317952},
{'depth': 55,
'resultadoteste': 0.813518679941197,
'resultado treino': 0.9999867630317952},
{'depth': 60,
'resultadoteste': 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,
'resultadoteste': 0.8021965350686706,
'resultado treino': 0.9978551494473612},
{'number of trees': 30,
'resultadoteste': 0.8080886155206466,
'resultado treino': 0.999245328742246},
{'number of trees': 40,
'resultadoteste': 0.8094797653250804,
'resultado treino': 0.9997087172853653},
{'number of trees': 50,
'resultadoteste': 0.8099445679467004,
'resultado treino': 0.9998543596946545},
{'number of trees': 60,
'resultadoteste': 0.8106713989484726,
'resultado treino': 0.9999337993828172},
{'number of trees': 70,
'resultadoteste': 0.8123270211680932,
'resultado treino': 0.9999735260635904},
{'number of trees': 80,
'resultadoteste': 0.8125257493913404,
'resultado treino': 1.0},
{'number of trees': 90,
'resultadoteste': 0.8133202669915133,
'resultado treino': 1.0},
{'number of trees': 100,
'resultadoteste': 0.813518679941197,
'resultado treino': 0.9999867630317952},
{'number of trees': 110,
'resultadoteste': 0.8147105499050884,
'resultado treino': 1.0},
{'number of trees': 120,
'resultadoteste': 0.814511506510187,
'resultado treino': 0.9999735208049824},
{'number of trees': 130,
'resultadoteste': 0.8151733023634403,
'resultado treino': 0.9999867577731871},
{'number of trees': 140,
'resultadoteste': 0.8147758977598828,
'resultado treino': 1.0},
{'number of trees': 150,
'resultadoteste': 0.815106927705478,
'resultado treino': 1.0},
{'number of trees': 160,
'resultadoteste': 0.8155042267449105,
'resultado treino': 1.0},
{'number of trees': 170,
'resultadoteste': 0.8153057613269111,
'resultado treino': 1.0},
{'number of trees': 180,
'resultadoteste': 0.8159018407246613,
'resultado treino': 1.0},
{'number of trees': 190,
'resultadoteste': 0.8155040686070789,
'resultado treino': 1.0},
{'number of trees': 200,
'resultadoteste': 0.8164977888662897,
'resultado treino': 1.0},
{'number of trees': 210,
'resultadoteste': 0.8157025610734733,
'resultado treino': 1.0},
{'number of trees': 220,
'resultadoteste': 0.8166953876855642,
'resultado treino': 1.0},
{'number of trees': 230,
'resultadoteste': 0.8158359665487848,
'resultado treino': 1.0},
{'number of trees': 240,
'resultadoteste': 0.81510755963304,
'resultado treino': 1.0},
{'number of trees': 250,
'resultadoteste': 0.8151734079501104,
'resultado treino': 1.0},
{'number of trees': 260,
'resultadoteste': 0.8155694713732721,
'resultado treino': 1.0},
{'number of trees': 270,
'resultadoteste': 0.8153713212090263,
'resultado treino': 1.0},
{'number of trees': 280,
'resultadoteste': 0.8155697863974299,
'resultado treino': 1.0},
{'number of trees': 290,
'resultadoteste': 0.8163635162699014,
'resultado treino': 1.0},
{'number of trees': 300,
'resultadoteste': 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,
'resultadoteste': 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,
'resultadoteste': 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,
'resultadoteste': 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,
'resultadoteste': 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,
'resultadoteste': 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.