Comparação de modelos de Decision Tree de Regressão e Classificação
Author
João F. Quentino
Essa atividade se trata sobre comparar a acurácia dos modelos Cart e M5 em um dataset de classificação, e comparar o algoritmo CART e C5.0 em um dataset de regressão. Os datasets escolhidos foram um conjunto de vinhos envolvendo sua origem, e outro conjunto sobre acidentes de trânsito envolvendo pedestres na república tcheca.
Procedimento padrão:
Leitura dos dados;
Exclusão de NA (neste caso: ‘?’);
Get_dummies devido a presença de variáveis categóricas;
Seleção do alvo/target (income) e das features. X e y, respectivamente;
Separando os dados em conjuntos de treino e teste (train_test_split do sklearn).
Code
import pandas as pdimport numpy as npfrom sklearn.tree import DecisionTreeClassifier, ExtraTreeClassifierfrom sklearn.model_selection import train_test_splitfrom sklearn.metrics import confusion_matrix, accuracy_score# Lendo o datasetdata = pd.read_csv('data/pedestrian.csv')# Removendo as linhas com valores faltantes NAdata = data.dropna()# REmovendo as duas primeiras colunas: Unnamed e Iddata = data.drop(['Unnamed: 0', 'id'], axis=1)# Separando os dados em features e targetdata_target = data['pedestrian_condition']data_features = data.drop('pedestrian_condition', axis=1)# Convertendo variáveis categóricas em dummiesdata_features = pd.get_dummies(data_features)# features names feature_names = data_features.columns.tolist()# Dividindo os dados em treino e testeX_train, X_test, y_train, y_test = train_test_split(data_features, data_target, test_size=0.3, random_state=1)
Agora que estabelecemos a base para os modelos, iremos utilizar a Random Search para acharmos os melhores hiperparâmetros.
Code
from sklearn.model_selection import RandomizedSearchCV# Definindo os hiperparâmetros e as distribuições para a busca aleatóriaparam_dist = {"max_depth": [None] +list(np.arange(2, 20)),"min_samples_split": np.arange(2, 20),"min_samples_leaf": np.arange(1, 20),"criterion": ["gini", "entropy"]}# Inicializando o classificador de árvore de decisãotree = DecisionTreeClassifier()# Inicializando a busca aleatóriarandom_search = RandomizedSearchCV(tree, param_distributions=param_dist, n_iter=100, cv=3, random_state=0, n_jobs=-1)# Executando a busca aleatóriarandom_search.fit(X_train, y_train)# Imprimindo os melhores hiperparâmetros encontradosprint(random_search.best_params_)
from sklearn.preprocessing import StandardScalerscaler = StandardScaler()scaler.fit(X_train) # calcula a média e o desvio padrão para cada colunaX_train = scaler.transform(X_train) # subtrai a média e divide pelo desvio padrãoX_test = scaler.transform(X_test) # subtrai a média e divide pelo desvio padrão
Fazendo o modelo CART
Code
def train_model_cart(height): model = DecisionTreeClassifier(criterion='entropy', max_depth=height, min_samples_split=14, min_samples_leaf=16,random_state=0) # cria o modelo model.fit(X_train, y_train) # treina o modeloreturn model
Fazendo o modelo M5
Code
def train_model_m5(): model = ExtraTreeClassifier(min_samples_leaf=10, min_samples_split=11) # cria o modelo, na M5 a altura pode ser ilimitada model.fit(X_train, y_train) # treina o modeloreturn model
Executando o modelo CART
Code
# Acurácia para diferentes alturas da árvore CARTprint('---------------------- CART --------------------------')for height inrange(1, 20): # testa diferentes alturas para a árvore model = train_model_cart(height) y_pred = model.predict(X_test) # faz a prediçãoprint('--------------------------------------------------')print(f'Altura - {height}\n')print(f'Acurácia: {accuracy_score(y_test, y_pred)}') # acurácia# A maior acurácia foi de 0.75, ALTURA 7
Exibindo a Árvore do modelo CART (abra a imagem em uma nova guia)
Code
# Exibindo a árvore de decisãofrom IPython.display import Imagefrom sklearn.tree import export_graphvizimport pydotplusmodel = train_model_cart(7) # treina o modelo com altura 11classes_names = [str(i) for i in model.classes_]dot_data = export_graphviz(model, filled=True, feature_names=feature_names, class_names=classes_names, rounded=True, special_characters=True) # cria o gráficograph = pydotplus.graph_from_dot_data(dot_data) Image(graph.create_png()) # exibe o gráficograph.write_png('cart-pedestrian.png') # salva o gráficoImage('cart-pedestrian.png')
Executando o modelo M5
Code
print('----------------------- M5 ---------------------------')for epocs inrange(1, 21): model = train_model_m5() y_pred = model.predict(X_test) # faz a prediçãoprint('--------------------------------------------------')print(f'Teste {epocs}\n')print(f'Acurácia: {accuracy_score(y_test, y_pred)}') # acurácia# A acurácia em alguns casos (randômicos) a árvore M5 é melhor em comparação com a árvore CART# A maior acurácia foi de 0.759, com 20 testes reproduzidos várias vezes.
----------------------- M5 ---------------------------
--------------------------------------------------
Teste 1
Acurácia: 0.7476505395057431
--------------------------------------------------
Teste 2
Acurácia: 0.7535676992690568
--------------------------------------------------
Teste 3
Acurácia: 0.7438217890706579
--------------------------------------------------
Teste 4
Acurácia: 0.7521754263835712
--------------------------------------------------
Teste 5
Acurácia: 0.7483466759484859
--------------------------------------------------
Teste 6
Acurácia: 0.7460842325095719
--------------------------------------------------
Teste 7
Acurácia: 0.757396449704142
--------------------------------------------------
Teste 8
Acurácia: 0.7502610511660286
--------------------------------------------------
Teste 9
Acurácia: 0.7483466759484859
--------------------------------------------------
Teste 10
Acurácia: 0.731813435433345
--------------------------------------------------
Teste 11
Acurácia: 0.7547859380438566
--------------------------------------------------
Teste 12
Acurácia: 0.7432996867386008
--------------------------------------------------
Teste 13
Acurácia: 0.7368604246432301
--------------------------------------------------
Teste 14
Acurácia: 0.742603550295858
--------------------------------------------------
Teste 15
Acurácia: 0.7518273581621998
--------------------------------------------------
Teste 16
Acurácia: 0.7387747998607727
--------------------------------------------------
Teste 17
Acurácia: 0.7532196310476853
--------------------------------------------------
Teste 18
Acurácia: 0.7460842325095719
--------------------------------------------------
Teste 19
Acurácia: 0.7537417333797425
--------------------------------------------------
Teste 20
Acurácia: 0.7523494604942569
Exibindo a árvore M5 (abra a imagem em uma nova guia)
Code
model = train_model_m5() classes_names = [str(i) for i in model.classes_]dot_data = export_graphviz(model, filled=True, feature_names=feature_names, class_names=classes_names, rounded=True, special_characters=True) # cria o gráficograph = pydotplus.graph_from_dot_data(dot_data) Image(graph.create_png()) # exibe o gráficograph.write_png('m5-pedestrian.png') # salva o gráficoImage('m5-pedestrian.png')
Considerações entre M5 e CART:
Os dois modelos obtiveram uma acurácia praticamente idêntica, mas isso se deve pela procura dos melhores hiperparâmetros para o modelo do CART, como o modelo M5 funciona basicamente atraveś de ‘descent gradient’, em alguns cenários ele vai obter uma acurácia superior (+1%), em outros casos vai se manter perto de outras alturas !=7 do modelo CART.
Comparação entre CART e C5.0 no conjunto wine.data
Realizar o mesmo procedimento do CART realizado no dataset anterior
Code
import numpy as npimport pandas as pdimport matplotlib.pyplot as plotimport seaborn as snsimport pydotplusdataset = pd.read_csv('data/wine.data', header=None)dataset.columns = ['label','alcohol','malic_acid','ash','alcalinity_of_ash','magnesium','total_phenols','flavanoids','nonflavanoid_phenols', 'proanthocyanins', 'color_intensity', 'hue','OD280/OD315','proline']# Divisão treino-testefrom sklearn.model_selection import train_test_splitX = dataset.values[:, 1:]y = dataset.values[:, 0] # a primeira coluna do dataset indica a origem do vinhoX_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.20, random_state=0)# Feature Scalingfrom sklearn.preprocessing import StandardScalerscaler = StandardScaler()scaler.fit(X_train) # calcula a média e o desvio padrão para cada colunaX_train = scaler.transform(X_train) # subtrai a média e divide pelo desvio padrãoX_test = scaler.transform(X_test) # subtrai a média e divide pelo desvio padrão# Treinamento do modelofrom sklearn.tree import DecisionTreeClassifierfrom sklearn.metrics import classification_report, confusion_matrix, accuracy_scoredef train_model(height): model = DecisionTreeClassifier(criterion='entropy', max_depth=height, random_state=0) # cria o modelo model.fit(X_train, y_train) # treina o modeloreturn model
Acurácia de CART
Code
for height inrange(1, 21): # testa diferentes alturas para a árvore model = train_model(height) y_pred = model.predict(X_test) # faz a prediçãoprint('--------------------------------------------------')print(f'Altura - {height}\n')print(f'Acurácia: {accuracy_score(y_test, y_pred)}') # acurácia
# Visualização da árvore de decisãofrom IPython.display import Imagefrom sklearn.tree import export_graphvizmodel = train_model(8) # treina o modelo com altura 3feature_names = ['alcohol','malic_acid','ash','alcalinity_of_ash', 'magnesium', 'total_phenols', 'flavanoids', 'nonflavanoid_phenols', 'proanthocyanins', 'color_intensity', 'hue','OD280/OD315','proline']classes_names = ['%.f'% i for i in model.classes_]dot_data = export_graphviz(model, filled=True, feature_names=feature_names, class_names=classes_names, rounded=True, special_characters=True) # cria o gráficograph = pydotplus.graph_from_dot_data(dot_data) # cria o gráficoImage(graph.create_png()) # exibe o gráficograph.write_png('classification-tree.png') # salva o gráficoImage('classification-tree.png')
Modelo C5.0
Code
def train_model_c5(height):# A árvore C5 se diferencia da CART pela escolha dos intervalos de corte clf = DecisionTreeClassifier(criterion='entropy', splitter='best', max_depth=height, min_samples_split=2, min_samples_leaf=1, min_weight_fraction_leaf=0.0, max_features=None, random_state=None, max_leaf_nodes=None, min_impurity_decrease=0.0, class_weight=None) clf.fit(X_train, y_train)return clf
Testando o modelo
Code
for height inrange(1, 21): # testa diferentes alturas para a árvore model = train_model_c5(height) y_pred = model.predict(X_test) # faz a prediçãoprint('--------------------------------------------------')print(f'Altura - {height}\n')print(f'Acurácia: {accuracy_score(y_test, y_pred)}') # acurácia
A maior acuráciafoi 0.94444… , que foi obtida tanto em alturas menores e alturas maiores.
Exibindo a árvore
Code
classes_names = [str(i) for i in model.classes_]model = train_model_c5(8) # treina o modelo com altura 3dot_data = export_graphviz(model, filled=True, feature_names=feature_names, class_names=classes_names, rounded=True, special_characters=True) # cria o gráficograph = pydotplus.graph_from_dot_data(dot_data) # cria o gráficoImage(graph.create_png()) # exibe o gráficograph.write_png('regression-tree-c5.png') # salva o gráficoImage('regression-tree-c5.png')
Conclusão:
O algoritmo c5 funcionou de uma maneira peculiar, tanto em alturas altas (15, 18, 19, 20) quanto em alturas baixas (2, 3) ele obteve uma maior acurácia: 94.4%. Isso se deve pelo fato do intervalo de corte (parâmetro splitter) ser do tipo ‘best’, fazendo com que o algoritmo faça de tudo o possível para a entropia diminuir, independentemente da altura. É um algoritmo mais usual para o usuário, diferente do CART que é necessário você colocar hiperparâmetros que o programador provavelmente só irá descobrir qual é o melhor pela tentativa e erro. Além de que o desempenho no dataset de vinhos ter sido inferior a partir da altura 4, com uma acurácia reduzida em mais de 3%. O CART e C5.0 teve o mesmo desempenho em alturas menores, mas o C5.0 conseguiu ter um desempenho alto em algumas alturas maiores.