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

Random forest ou boosting para classficar dados da base Otto group

0 votos
31 visitas
perguntada Jul 11 em Ciência da Computação por MCarolina Marques (36 pontos)  

O exercício era classificar os produtos de uma rede de lojas em categorias de produtos (por exemplo: têxtil, eletrodomésticos, etc.) com base em características (por exemplo: peso, cor, material, etc). A base está toda desidentificada, de modo que não sabemos quais as categorias ou quais as características que estão sendo analisadas.

O que eu tentei fazer foi comparar o classificador de random forest com o classificador de boosting da biblioteca sklearn.

Compartilhe

1 Resposta

0 votos
respondida Jul 12 por MCarolina Marques (36 pontos)  

primeiro é preciso importar as bibliotecas

import numpy as np # linear algebra
import pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)
import seaborn as sns
from matplotlib import pyplot
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.model_selection import cross_validate
from sklearn.metrics import confusion_matrix
from tqdm import tqdm
import copy
import warnings

Como eu estava trabalhando direto no kaggle, importei os dados direto da competição e os li lá mesmo

#importar os dados de treino
dados_df = pd.read_csv('../input/train.csv')

Em seguida, é preciso verificar as características dos dados:

dados_df.info()
print('\n\n\n')


RangeIndex: 61878 entries, 0 to 61877
Data columns (total 95 columns):
id 61878 non-null int64
feat1 61878 non-null int64
feat
2 61878 non-null int64
feat3 61878 non-null int64
feat
4 61878 non-null int64
feat5 61878 non-null int64
feat
6 61878 non-null int64
feat7 61878 non-null int64
feat
8 61878 non-null int64
feat9 61878 non-null int64
feat
10 61878 non-null int64
feat11 61878 non-null int64
feat
12 61878 non-null int64
feat13 61878 non-null int64
feat
14 61878 non-null int64
feat15 61878 non-null int64
feat
16 61878 non-null int64
feat17 61878 non-null int64
feat
18 61878 non-null int64
feat19 61878 non-null int64
feat
20 61878 non-null int64
feat21 61878 non-null int64
feat
22 61878 non-null int64
feat23 61878 non-null int64
feat
24 61878 non-null int64
feat25 61878 non-null int64
feat
26 61878 non-null int64
feat27 61878 non-null int64
feat
28 61878 non-null int64
feat29 61878 non-null int64
feat
30 61878 non-null int64
feat31 61878 non-null int64
feat
32 61878 non-null int64
feat33 61878 non-null int64
feat
34 61878 non-null int64
feat35 61878 non-null int64
feat
36 61878 non-null int64
feat37 61878 non-null int64
feat
38 61878 non-null int64
feat39 61878 non-null int64
feat
40 61878 non-null int64
feat41 61878 non-null int64
feat
42 61878 non-null int64
feat43 61878 non-null int64
feat
44 61878 non-null int64
feat45 61878 non-null int64
feat
46 61878 non-null int64
feat47 61878 non-null int64
feat
48 61878 non-null int64
feat49 61878 non-null int64
feat
50 61878 non-null int64
feat51 61878 non-null int64
feat
52 61878 non-null int64
feat53 61878 non-null int64
feat
54 61878 non-null int64
feat55 61878 non-null int64
feat
56 61878 non-null int64
feat57 61878 non-null int64
feat
58 61878 non-null int64
feat59 61878 non-null int64
feat
60 61878 non-null int64
feat61 61878 non-null int64
feat
62 61878 non-null int64
feat63 61878 non-null int64
feat
64 61878 non-null int64
feat65 61878 non-null int64
feat
66 61878 non-null int64
feat67 61878 non-null int64
feat
68 61878 non-null int64
feat69 61878 non-null int64
feat
70 61878 non-null int64
feat71 61878 non-null int64
feat
72 61878 non-null int64
feat73 61878 non-null int64
feat
74 61878 non-null int64
feat75 61878 non-null int64
feat
76 61878 non-null int64
feat77 61878 non-null int64
feat
78 61878 non-null int64
feat79 61878 non-null int64
feat
80 61878 non-null int64
feat81 61878 non-null int64
feat
82 61878 non-null int64
feat83 61878 non-null int64
feat
84 61878 non-null int64
feat85 61878 non-null int64
feat
86 61878 non-null int64
feat87 61878 non-null int64
feat
88 61878 non-null int64
feat89 61878 non-null int64
feat
90 61878 non-null int64
feat91 61878 non-null int64
feat
92 61878 non-null int64
feat_93 61878 non-null int64
target 61878 non-null object

Não tem missing e a variável target está como categ[orica não numérica. Isso não é um problema para os modelos que vamos usar, pois não podemos assumir um ordenamento nas categorias (como seria no caso de bom, regular, ruim, por exemplo).
É preciso separar a variável target para usar os modelos:

y = dados_df.loc[:,'target']
dados_df=dados_df.drop(columns='target')

É preciso importar os dados de teste:

dados_df_test = pd.read_csv('../input/test.csv')
dados_df_test.info()

importar e separar os dados de teste

dadosdftest = pd.read_csv('../input/test.csv')

dadosdftest.info()

Dá para notar que a variável target não está disponível na base de teste, o que significa que para ver a adequação do modelo é necessário submetê-lo ao kaggle.

for i in dados_df.columns[1:]:
    print(i,':')
    print("maximum",i,'value:',dados_df.loc[:,i].max())
    print("minimum",i,'value:',dados_df.loc[:,i].min())
    print(i,'standard deviation:',dados_df.loc[:,i].std())
    print("mean",i,'value:',dados_df.loc[:,i].mean())
    print('\n')

Os dados não são binários e são concentrados em valores baixos, porque as médias são baixas e os desvios-padrão, e geral, pequenos.

Eu plotei as distribuições para confirmar, mas não vou fazer upload de 93 imagens, mas segue o código:

#plotar as variáveis contínuas para verificar distribuição
for i in dados_df.columns[2:]:
    sns.distplot(dados_df.loc[:,i])
    pyplot.show()

Os dados de todos os feats parecem muto concentrados no zero, o que é bom, pois os que apresentarem valores diferentes de zero devem ser bastante distintos e fáceis de classificar

Vou calcular a matriz de correlação para verificar se há variáveis que podem ser descartadas para deixar o modelo menor. Como são muitas variáveis, visualizar a matriz de correlação é difícil, então vamos apenas identificar quem tem o módulo da correlação maior que 0.7.

correlation_matrix= dados_df.iloc[:].corr()
for i in range(correlation_matrix.shape[0]):
    correlation_matrix.iloc[i,i]=0
a = np.where(np.absolute(correlation_matrix)>0.7)
print(a)

(array([ 3, 9, 15, 30, 39, 45, 46, 64, 72, 84])
array([46, 64, 72, 84, 45, 39, 3, 9, 15, 30]))

Identificamos 5 pares de variáveis altamente correlacionadas: os pares de features (3 e 46), (9 e 64), (15 e 72), (30 e 84), (39 e 45). Essas vão variáveis candidatas a exclusão para simplificação do modelo. Vamos deixar guardado um dataframe sem elas:

dados_df_smaller=dados_df.drop(columns=['feat_3','feat_9','feat_15','feat_30','feat_39'])

Vou testar o modelo de randomforest, com número variável de árvores e profundidade variável:

x=[]
for i in tqdm(range(100,501,100)):
    for j in range(2, 43, 10):
        classificador = RandomForestClassifier(n_estimators=i, max_depth=j, random_state=0, n_jobs=-2)
        resultado = cross_validate(classificador, dados_df, y, n_jobs=-2, cv=2, return_train_score=True)
        x.append({'n_arvores':i, 'profundidade':j, 'resultado_teste':np.mean(resultado['test_score']),'resultado treino':np.mean(resultado['train_score'])})
x

[{'narvores': 100, 'profundidade': 2, 'resultadoteste': 0.5236432716854211, 'resultado treino': 0.5298493950166479}, {'narvores': 100, 'profundidade': 12, 'resultadoteste':
0.8121430701521739, 'resultado treino': 0.9624754019369757}, {'narvores': 100, 'profundidade': 22, 'resultadoteste':
0.7919592728586164, 'resultado treino': 0.9998868757377288}, {'narvores': 100, 'profundidade': 32, 'resultadoteste':
0.79884272202969, 'resultado treino': 1.0}, {'narvores': 100, 'profundidade': 42, 'resultadoteste': 0.7697234117898906,
'resultado treino': 1.0}, {'narvores': 200, 'profundidade': 2,
'resultado
teste': 0.5174858392681339, 'resultado treino':
0.5220274171967383}, {'narvores': 200, 'profundidade': 12, 'resultadoteste': 0.8108178599275233, 'resultado treino':
0.9625397036543608}, {'narvores': 200, 'profundidade': 22, 'resultadoteste': 0.7935430031282031, 'resultado treino':
0.9998222324066965}, {'narvores': 200, 'profundidade': 32, 'resultadoteste': 0.7921847378656199, 'resultado treino': 1.0},
{'narvores': 200, 'profundidade': 42, 'resultadoteste':
0.7740383933121651, 'resultado treino': 1.0}, {'narvores': 300, 'profundidade': 2, 'resultadoteste': 0.518552397816902,
'resultado treino': 0.5225283418978695}, {'narvores': 300,
'profundidade': 12, 'resultado
teste': 0.8120300117053759,
'resultado treino': 0.9614567601868187}, {'narvores': 300,
'profundidade': 22, 'resultado
teste': 0.79758292611733,
'resultado treino': 0.9998707164720058}, {'narvores': 300,
'profundidade': 32, 'resultado
teste': 0.7884676178343937,
'resultado treino': 1.0}, {'narvores': 300, 'profundidade': 42,
'resultado
teste': 0.7773997336993125, 'resultado treino': 1.0},
{'narvores': 400, 'profundidade': 2, 'resultadoteste':
0.5195866660408556, 'resultado treino': 0.5233040023229226}, {'narvores': 400, 'profundidade': 12, 'resultadoteste':
0.812434112443116, 'resultado treino': 0.961779744920788}, {'narvores': 400, 'profundidade': 22, 'resultadoteste':
0.7926541651616862, 'resultado treino': 0.999919200537315}, {'narvores': 400, 'profundidade': 32, 'resultadoteste':
0.7813897904981912, 'resultado treino': 1.0}, {'narvores': 400, 'profundidade': 42, 'resultadoteste': 0.7747495389030623,
'resultado treino': 1.0}, {'narvores': 500, 'profundidade': 2,
'resultado
teste': 0.5212674051839727, 'resultado treino':
0.5249201451460599}, {'narvores': 500, 'profundidade': 12, 'resultadoteste': 0.8131937232949016, 'resultado treino':
0.9608101545026384}, {'narvores': 500, 'profundidade': 22, 'resultadoteste': 0.7964193963038733, 'resultado treino':
0.9999030381375219}, {'narvores': 500, 'profundidade': 32, 'resultadoteste': 0.7830546273693382, 'resultado treino': 1.0},
{'narvores': 500, 'profundidade': 42, 'resultadoteste':
0.7802114271306876, 'resultado treino': 1.0}]

Identificamos que os melhores resultados ocorrem com mais árvores e com profundidade entre 2 e 22
Vamos refazer com 500 árvores testando só as profundidades

z=[]
for j in tqdm(range(2, 23, 2)):
    classificador = RandomForestClassifier(n_estimators=500, max_depth=j, random_state=0, n_jobs=-2)
    resultado = cross_validate(classificador, dados_df, y, n_jobs=-2, cv=2, return_train_score=True)
    z.append({'profundidade':j, 'resultado_teste':np.mean(resultado['test_score']),'resultado treino':np.mean(resultado['train_score'])})
z

[{'profundidade': 2, 'resultadoteste': 0.5212674051839727, 'resultado treino': 0.5249201451460599}, {'profundidade': 4,
'resultado
teste': 0.6564182478552054, 'resultado treino':
0.7276104190582756}, {'profundidade': 6, 'resultadoteste': 0.7315974608816647, 'resultado treino': 0.8503994234514396}, {'profundidade': 8, 'resultadoteste': 0.7764605782364451,
'resultado treino': 0.9024695075031897}, {'profundidade': 10,
'resultadoteste': 0.7989239853347581, 'resultado treino':
0.9338052791681767}, {'profundidade': 12, 'resultado
teste': 0.8131937232949016, 'resultado treino': 0.9608101545026384}, {'profundidade': 14, 'resultadoteste': 0.8153587076177813,
'resultado treino': 0.9853582291079499}, {'profundidade': 16,
'resultado
teste': 0.8137099892694453, 'resultado treino':
0.9957011840115888}, {'profundidade': 18, 'resultadoteste': 0.7990206996056939, 'resultado treino': 0.9987232989132198}, {'profundidade': 20, 'resultadoteste': 0.7944319507871757,
'resultado treino': 0.9996767927470496}, {'profundidade': 22,
'resultado_teste': 0.7964193963038733, 'resultado treino':
0.9999030381375219}]

A melhor profundidade é 14, depois overfitta.

Resta testar se a base de dados completa é melhor que a base de dados sem as variáveis com alta corrrelação.

z=[]
classificador = RandomForestClassifier(n_estimators=500, max_depth=14, random_state=0, n_jobs=-2)
resultado = cross_validate(classificador, dados_df, y, n_jobs=-2, cv=6, return_train_score=True)
z.append({'resultado_teste':np.mean(resultado['test_score']),'resultado treino':np.mean(resultado['train_score'])})
print(z)    

q=[]
resultado = cross_validate(classificador, dados_df_smaller, y, n_jobs=-2, cv=6, return_train_score=True)
q.append({'resultado_teste':np.mean(resultado['test_score']),'resultado treino':np.mean(resultado['train_score'])})
print(q)    

[{'resultadoteste': 0.9237139421933057, 'resultado treino': 0.9837195462290643}]
[{'resultado
teste': 0.919803861812894, 'resultado treino': 0.9849704449472118}]

O resultado de teste piora quando as variáveis são retiradas, então vamos mantê-las.

Então o modelo final de RandomForest é:

classificador = RandomForestClassifier(n_estimators=500, max_depth=14, random_state=0, n_jobs=-2)
classificador.fit(dados_df,y)
y_hat = classificador.predict_proba(dados_df_test)

Depois preciso exportar os dados no formato pedido na competição para submeter:

final_submission = pd.DataFrame(y_hat)
final_submission.columns =  y.unique()
final_submission['Id'] = dados_df_test.id
final_submission.to_csv('final_submission_1.csv',index=  False)

O resultado não ficou muito bom, mas ficou melhor que o random forest benchmark para esse exercício.

Depois eu decidi testar um modelo de boosting, para ver se o resultado melhorava. A ideia do boosting é criar uma primeira árvore, verificar onde ela está errando, dar um peso maior a esse pedaço da amostra na amostragem puxada para construir a árvore seguinte. Então ele constrói uma floresta de forma sequencial, com uma taxa de aprendizado entre uma árvore e outra. Ao final, ele estabelece um peso para cadaárvore, decrescente da primeira para a última árvore, que soma 1. Para esse tipo de classificador, o ideal é começar com uma profundidade baixa de árvore, até mesmo 1, o que chamam de tree stump. A ideia é testar vários números de iterações (quantidade de árvores) e o learning rate. Eu tentei antes com outras profundidades, mas tinha overfittado, o resultado de treino estava igual a 1 e o de teste muito baixo, ficou melhor fixando a profundidade em 1 (eu reescrevi o código por cima, e não gravei os resultados, rs).

t=[]
for i in tqdm(range(20,101,20)):
    for j in [0.001, 0.002, 0.003, 0.004, 0.005]:
        classificador_boosting = GradientBoostingClassifier(loss='deviance', n_estimators=i, learning_rate=j, max_depth=1, random_state=0)
        resultado = cross_validate(classificador_boosting,dados_df, y, n_jobs=-2, cv=6, return_train_score=True)
        t.append({'numero de iterações':i, 'learning rate':j, 'resultado_teste':np.mean(resultado['test_score']),'resultado treino':np.mean(resultado['train_score'])})
t

[{'numero de iterações': 20, 'learning rate': 0.001,
'resultadoteste': 0.26054495879142303, 'resultado treino':
0.2605449438960583}, {'numero de iterações': 20, 'learning rate': 0.002, 'resultado
teste': 0.36762712311716267, 'resultado treino': 0.36762664024212527}, {'numero de iterações': 20, 'learning rate': 0.003, 'resultadoteste': 0.36762712311716267, 'resultado treino': 0.36762664024212527}, {'numero de iterações': 20, 'learning rate': 0.004, 'resultadoteste': 0.36762712311716267, 'resultado treino': 0.36762664024212527}, {'numero de iterações': 20, 'learning rate': 0.005, 'resultadoteste': 0.36762712311716267, 'resultado treino': 0.36762664024212527}, {'numero de iterações': 40, 'learning rate': 0.001, 'resultadoteste': 0.36762712311716267, 'resultado treino': 0.36762664024212527}, {'numero de iterações': 40, 'learning rate': 0.002, 'resultadoteste': 0.36762712311716267, 'resultado treino': 0.36762664024212527}, {'numero de iterações': 40, 'learning rate': 0.003, 'resultadoteste': 0.36762712311716267, 'resultado treino': 0.36762664024212527}, {'numero de iterações': 40, 'learning rate': 0.004, 'resultadoteste': 0.5518777003316635, 'resultado treino': 0.5805779343191005}, {'numero de iterações': 40, 'learning rate': 0.005, 'resultadoteste': 0.6515164926584576, 'resultado treino': 0.7019296071535802}, {'numero de iterações': 60, 'learning rate': 0.001, 'resultadoteste': 0.36762712311716267, 'resultado treino': 0.36762664024212527}, {'numero de iterações': 60, 'learning rate': 0.002, 'resultadoteste': 0.36762712311716267, 'resultado treino': 0.36762664024212527}, {'numero de iterações': 60, 'learning rate': 0.003, 'resultadoteste': 0.6515164926584576, 'resultado treino': 0.7019296071535802}, {'numero de iterações': 60, 'learning rate': 0.004, 'resultadoteste': 0.6891064003435797, 'resultado treino': 0.7396619541878984}, {'numero de iterações': 60, 'learning rate': 0.005, 'resultadoteste': 0.6898338118005399, 'resultado treino': 0.7402404753284322}, {'numero de iterações': 80, 'learning rate': 0.001, 'resultadoteste': 0.36762712311716267, 'resultado treino': 0.36762664024212527}, {'numero de iterações': 80, 'learning rate': 0.002, 'resultadoteste': 0.5518777003316635, 'resultado treino': 0.5805779343191005}, {'numero de iterações': 80, 'learning rate': 0.003, 'resultadoteste': 0.6891064003435797, 'resultado treino': 0.7396619541878984}, {'numero de iterações': 80, 'learning rate': 0.004, 'resultadoteste': 0.6898176494007467, 'resultado treino': 0.7402307790169091}, {'numero de iterações': 80, 'learning rate': 0.005, 'resultadoteste': 0.8047363530575383, 'resultado treino': 0.8780665260958852}, {'numero de iterações': 100, 'learning rate': 0.001, 'resultadoteste': 0.36762712311716267, 'resultado treino': 0.36762664024212527}, {'numero de iterações': 100, 'learning rate': 0.002, 'resultadoteste': 0.6515164926584576, 'resultado treino': 0.7019296071535802}, {'numero de iterações': 100, 'learning rate': 0.003, 'resultadoteste': 0.6898338118005399, 'resultado treino': 0.7402404753284322}, {'numero de iterações': 100, 'learning rate': 0.004, 'resultadoteste': 0.8047363530575383, 'resultado treino': 0.8780665260958852}, {'numero de iterações': 100, 'learning rate': 0.005, 'resultado_teste': 0.8049140720853623, 'resultado treino': 0.87846087216314}]

O melhor resultado foi obtido com 100 iterações e learning rate 0.005.
Como o resultado de treino ainda não está esgotado, dá pra melhorar aumentando a quantidade de iterações. Um novo teste mostrou que que as melhorias ficam bem marginais a partir de 160.

O modelo final de boosting ficou assim:

classificador = GradientBoostingClassifier(loss='deviance', nestimators=160, learningrate=0.005, maxdepth=1, randomstate=0)
classificador.fit(dadosdf,y)
y
hat = classificador.predictproba(dadosdf_test)

Ainda testei uma profundidade maior, mas mudou muito pouco. o resultado de cerca de 81% é pior no crossvalidation que o random forest.

Quando esse modelo foi submetido no kagle, o resultado também foi pior que o do random forest.

comentou Jul 13 por Pedro Duque (51 pontos)  
Perfeito, Maria! Consegui rodar seu código aqui sem problemas, parabéns! Não há nenhum comentário significativo a ser feito com relação ao código!!!

Entretanto, com relação a apresentação, teria sido legal agrupar os resultados ao final para podermos comparar os modelos de forma mais prática, e também teria sido bom colocar o link para a base na pergunta. Mas conteúdo é muito mais importante que forma, parabéns pelo excelente trabalho!!!
...