ALGORITMOS DE CLASSIFICAÇÃO - TUTORIAL
INTRODUÇÃO
Sumário
Tutorial com a implementação básica dos principais algoritmos de classificação no R.
Problema
Prever se um cliente comprará um produto bancário (CDB) após uma campanha de marketing.
Classe: 0 se o cliente não comprou; 1 se comprou.
Dataset
Bank Marketing, com dados já pré-processados.
<- read.csv("datasets/dados_preprocessados.csv")
dados # 45211 observações e 43 campos, sendo um deles a classe
str(dados)
'data.frame': 45211 obs. of 43 variables:
$ classe : int 0 0 0 0 0 0 0 0 0 0 ...
$ age : num 1.607 0.289 -0.747 0.571 -0.747 ...
$ balance : num 0.2564 -0.4379 -0.4468 0.0472 -0.4471 ...
$ day : num -1.3 -1.3 -1.3 -1.3 -1.3 ...
$ duration : num 0.011 -0.416 -0.707 -0.645 -0.234 ...
$ campaign : num -0.569 -0.569 -0.569 -0.569 -0.569 ...
$ pdays : num -0.411 -0.411 -0.411 -0.411 -0.411 ...
$ previous : num -0.252 -0.252 -0.252 -0.252 -0.252 ...
$ job_blue : int 0 0 0 1 0 0 0 0 0 0 ...
$ job_entrepreneur : int 0 0 1 0 0 0 0 1 0 0 ...
$ job_housemaid : int 0 0 0 0 0 0 0 0 0 0 ...
$ job_management : int 1 0 0 0 0 1 1 0 0 0 ...
$ job_retired : int 0 0 0 0 0 0 0 0 1 0 ...
$ job_self : int 0 0 0 0 0 0 0 0 0 0 ...
$ job_services : int 0 0 0 0 0 0 0 0 0 0 ...
$ job_student : int 0 0 0 0 0 0 0 0 0 0 ...
$ job_technician : int 0 1 0 0 0 0 0 0 0 1 ...
$ job_unemployed : int 0 0 0 0 0 0 0 0 0 0 ...
$ job_unknown : int 0 0 0 0 1 0 0 0 0 0 ...
$ marital_married : int 1 0 1 1 0 1 0 0 1 0 ...
$ marital_single : int 0 1 0 0 1 0 1 0 0 1 ...
$ education_secondary: int 0 1 1 0 0 0 0 0 0 1 ...
$ education_tertiary : int 1 0 0 0 0 1 1 1 0 0 ...
$ education_unknown : int 0 0 0 1 1 0 0 0 0 0 ...
$ default_yes : int 0 0 0 0 0 0 0 1 0 0 ...
$ housing_yes : int 1 1 1 1 0 1 1 1 1 1 ...
$ loan_yes : int 0 0 1 0 0 0 1 0 0 0 ...
$ contact_telephone : int 0 0 0 0 0 0 0 0 0 0 ...
$ contact_unknown : int 1 1 1 1 1 1 1 1 1 1 ...
$ month_aug : int 0 0 0 0 0 0 0 0 0 0 ...
$ month_dec : int 0 0 0 0 0 0 0 0 0 0 ...
$ month_feb : int 0 0 0 0 0 0 0 0 0 0 ...
$ month_jan : int 0 0 0 0 0 0 0 0 0 0 ...
$ month_jul : int 0 0 0 0 0 0 0 0 0 0 ...
$ month_jun : int 0 0 0 0 0 0 0 0 0 0 ...
$ month_mar : int 0 0 0 0 0 0 0 0 0 0 ...
$ month_may : int 1 1 1 1 1 1 1 1 1 1 ...
$ month_nov : int 0 0 0 0 0 0 0 0 0 0 ...
$ month_oct : int 0 0 0 0 0 0 0 0 0 0 ...
$ month_sep : int 0 0 0 0 0 0 0 0 0 0 ...
$ poutcome_other : int 0 0 0 0 0 0 0 0 0 0 ...
$ poutcome_success : int 0 0 0 0 0 0 0 0 0 0 ...
$ poutcome_unknown : int 1 1 1 1 1 1 1 1 1 1 ...
# 11.7% dos clientes compraram o produto após a campanha
prop.table(table(dados$classe))
0 1
0.8830152 0.1169848
SPLIT
Os dados serão divididos em 70% para treinamento e 30% para teste. A classe precisa ser uma variável do tipo factor.
set.seed(1234)
<- sample(1:nrow(dados), nrow(dados) * 0.7, replace = FALSE)
amostra <- dados[amostra, ]
dados_treino $classe <- as.factor(dados_treino$classe)
dados_treino<- dados[-amostra, ]
dados_teste $classe <- as.factor(dados_teste$classe) dados_teste
ALGORITMOS
Adaboost
Pacotes
library(adabag)
Treinamento
# mfinal -> total de iterações coeflearn -> fórmula para o amount of say;
# 'Breiman' a usual 1/2ln((1-err)/err).
<- boosting(classe ~ ., data = dados_treino, boos = TRUE, mfinal = 30,
mod_adaboost coeflearn = "Breiman")
Classificações
<- predict.boosting(mod_adaboost, dados_teste[, -1])
classificacoes_adaboost <- as.factor(classificacoes_adaboost$class) classificacoes_adaboost
Matriz de confusão
<- confusionMatrix(classificacoes_adaboost, dados_teste$classe, positive = "1",
mc_adaboost mode = "prec_recall")
$table mc_adaboost
Reference
Prediction 0 1
0 11518 874
1 452 720
Métricas
<- mc_adaboost$overall["Accuracy"]
acuracia_adaboost <- mc_adaboost$byClass["Precision"]
precisao_adaboost <- mc_adaboost$byClass["Recall"]
recall_adaboost <- mc_adaboost$byClass["F1"]
f1_adaboost <- c(acuracia_adaboost, precisao_adaboost, recall_adaboost, f1_adaboost)
metricas_adaboost metricas_adaboost
Accuracy Precision Recall F1
0.9022412 0.6143345 0.4516939 0.5206074
Árvores de decisão
Pacotes
library(caret)
library(rpart)
library(rpart.plot)
Treinamento
= rpart.control(minsplit = 15, minbucket = 5)
pruneControl = rpart(classe ~ ., data = dados_treino, control = pruneControl)
mod_arvores # Visualização da árvore
prp(mod_arvores)
Classificações
<- predict(mod_arvores, dados_teste[, -1])
classificacoes_arvores <- as.data.frame(classificacoes_arvores)
classificacoes_arvores "classe"] <- ifelse(classificacoes_arvores >= 0.5, 1, 0) classificacoes_arvores[
Matriz de confusão
<- confusionMatrix(as.factor(classificacoes_arvores$class[, 2]), as.factor(dados_teste[,
mc_arvores 1]), positive = "1", mode = "prec_recall")
$table mc_arvores
Reference
Prediction 0 1
0 11649 1024
1 321 570
Métricas
<- mc_arvores$overall["Accuracy"]
acuracia_arvores <- mc_arvores$byClass["Precision"]
precisao_arvores <- mc_arvores$byClass["Recall"]
recall_arvores <- mc_arvores$byClass["F1"]
f1_arvores <- c(acuracia_arvores, precisao_arvores, recall_arvores, f1_arvores)
metricas_arvores metricas_arvores
Accuracy Precision Recall F1
0.9008405 0.6397306 0.3575910 0.4587525
Gradient boosting
Pacotes
library(caret)
library(gbm)
Treinamento
# Função gbm precisa da classe com formato character
$classe <- as.character(dados_treino$classe)
dados_treino$classe <- as.character(dados_teste$classe)
dados_teste
# Bernoulli para saída binária; 1000 iterações; mínimo 30 observações nas folhas
<- gbm(classe ~ ., data = dados_treino, n.trees = 1000, n.minobsinnode = 30,
mod_gb distribution = "bernoulli")
Classificações
<- predict.gbm(mod_gb, dados_teste[, -1], type = "response")
classificacoes_gb <- as.data.frame(classificacoes_gb)
classificacoes_gb "classe"] <- ifelse(classificacoes_gb >= 0.5, 1, 0) classificacoes_gb[
Matriz de confusão
<- confusionMatrix(as.factor(classificacoes_gb$classe), as.factor(dados_teste[,
mc_gb 1]), positive = "1", mode = "prec_recall")
$table mc_gb
Reference
Prediction 0 1
0 11620 964
1 350 630
Métricas
<- mc_gb$overall["Accuracy"]
acuracia_gb <- mc_gb$byClass["Precision"]
precisao_gb <- mc_gb$byClass["Recall"]
recall_gb <- mc_gb$byClass["F1"]
f1_gb <- c(acuracia_gb, precisao_gb, recall_gb, f1_gb)
metricas_gb metricas_gb
Accuracy Precision Recall F1
0.9031259 0.6428571 0.3952321 0.4895105
kNN
Pacotes
library(class)
library(caret)
Classificações
# k=sqrt(n)
<- ifelse(round(sqrt(nrow(dados)), 0)%%2 == 1, round(sqrt(nrow(dados)), 0), round(sqrt(nrow(dados)),
k 0) + 1)
<- knn(dados_treino, dados_teste, cl = dados_treino$classe, k = k) classificacoes_knn
Matriz de confusão
<- confusionMatrix(as.factor(classificacoes_knn), as.factor(dados_teste[,
mc_knn 1]), positive = "1", mode = "prec_recall")
$table mc_knn
Reference
Prediction 0 1
0 11939 1047
1 31 547
Métricas
<- mc_gb$overall["Accuracy"]
acuracia_knn <- mc_gb$byClass["Precision"]
precisao_knn <- mc_gb$byClass["Recall"]
recall_knn <- mc_gb$byClass["F1"]
f1_knn <- c(acuracia_knn, precisao_knn, recall_knn, f1_knn)
metricas_knn metricas_knn
Accuracy Precision Recall F1
0.9031259 0.6428571 0.3952321 0.4895105
Naive Bayes
Pacotes
library(e1071)
library(caret)
Treinamento
# Com correção de laplace
= naiveBayes(classe ~ ., data = dados_treino, laplace = TRUE) mod_nb
Classificações
# type = 'raw' porque o RMarkdown, incompreensivelmente, bugou com type='class'
<- predict(mod_nb, dados_teste[, -1], type = "raw")
classificacoes_nb <- as.data.frame(classificacoes_nb)
classificacoes_nb "classe"] <- ifelse(classificacoes_nb[, 2] >= 0.5, 1, 0) classificacoes_nb[
Matriz de confusão
<- confusionMatrix(as.factor(classificacoes_nb$classe), as.factor(dados_teste[,
mc_nb 1]), positive = "1", mode = "prec_recall")
$table mc_nb
Reference
Prediction 0 1
0 11202 1000
1 768 594
Métricas
<- mc_nb$overall["Accuracy"]
acuracia_nb <- mc_nb$byClass["Precision"]
precisao_nb <- mc_nb$byClass["Recall"]
recall_nb <- mc_nb$byClass["F1"]
f1_nb <- c(acuracia_gb, precisao_gb, recall_gb, f1_gb)
metricas_nb metricas_nb
Accuracy Precision Recall F1
0.9031259 0.6428571 0.3952321 0.4895105
Random forest
Pacotes
library(caret)
library(randomForest)
Treinamento
# ntree: número de árvores
<- randomForest(as.factor(classe) ~ ., data = dados_treino, ntree = 101) mod_rf
Classificações
<- predict(mod_rf, dados_teste[, -1]) classificacoes_rf
Matriz de confusão
<- confusionMatrix(classificacoes_rf, as.factor(dados_teste[, 1]), positive = "1",
mc_rf mode = "prec_recall")
$table mc_rf
Reference
Prediction 0 1
0 11637 935
1 333 659
Métricas
<- mc_rf$overall["Accuracy"]
acuracia_rf <- mc_rf$byClass["Precision"]
precisao_rf <- mc_rf$byClass["Recall"]
recall_rf <- mc_rf$byClass["F1"]
f1_rf <- c(acuracia_rf, precisao_rf, recall_rf, f1_rf)
metricas_rf metricas_rf
Accuracy Precision Recall F1
0.9065173 0.6643145 0.4134253 0.5096674
Redes neurais
Pacotes
library(caTools)
library(h2o)
library(caret)
Split
# Split usando cartools
set.seed(1234)
<- sample.split(dados$classe, SplitRatio = 0.7)
divisao <- subset(dados, divisao == TRUE)
dados_treinamento <- subset(dados, divisao == FALSE) dados_teste
Treinamento
h2o.init()
# Função de ativação ReLU;duas camadas de 22 neurônios;1000 epochs
<- h2o.deeplearning(y = "classe", training_frame = as.h2o(dados_treinamento),
mod_rn activation = "Rectifier", hidden = c(22, 22), epochs = 100)
Classificações
h2o.init()
<- h2o.predict(mod_rn, newdata = as.h2o(dados_teste[, -1]))
classificacoes_rn = classificacoes_rn > 0.5
classificacoes_rn <- as.vector(classificacoes_rn) classificacoes_rn
Matriz de confusão
<- confusionMatrix(as.factor(classificacoes_rn), as.factor(dados_teste[, 1]),
mc_rn positive = "1", mode = "prec_recall")
$table mc_rn
Reference
Prediction 0 1
0 11607 1008
1 370 579
Métricas
<- mc_rn$overall["Accuracy"]
acuracia_rn <- mc_rn$byClass["Precision"]
precisao_rn <- mc_rn$byClass["Recall"]
recall_rn <- mc_rn$byClass["F1"]
f1_rn <- c(acuracia_rn, precisao_rn, recall_rn, f1_rn)
metricas_rn metricas_rn
Accuracy Precision Recall F1
0.8984075 0.6101159 0.3648393 0.4566246
Regressão logística
Pacotes
library(caret)
Treinamento
= glm(as.factor(classe) ~ ., data = dados_treino, family = binomial("logit")) mod_rl
Classificações
<- predict(mod_rl, dados_teste[2:43], type = "response")
classificacoes_rl <- ifelse(classificacoes_rl > 0.5, 1, 0)
classificacoes_rl <- sapply(classificacoes_rl, as.factor) classificacoes_rl
Matriz de confusão
<- confusionMatrix(as.factor(classificacoes_rl), as.factor(dados_teste[, 1]),
mc_rl positive = "1", mode = "prec_recall")
$table mc_rl
Reference
Prediction 0 1
0 11815 1291
1 162 296
Métricas
<- mc_rl$overall["Accuracy"]
acuracia_rl <- mc_rl$byClass["Precision"]
precisao_rl <- mc_rl$byClass["Recall"]
recall_rl <- mc_rl$byClass["F1"]
f1_rl <- c(acuracia_rl, precisao_rl, recall_rl, f1_rl)
metricas_rl metricas_rl
Accuracy Precision Recall F1
0.8928782 0.6462882 0.1865154 0.2894866
SVM
Pacotes
library(e1071)
library(caret)
Treinamento
# kernel: rbf; custo:0.3 e gamma:0.1
<- svm(as.factor(classe) ~ ., data = dados_treino, kernel = "radial", cost = 0.3,
mod_svm gamma = 0.1)
Classificações
<- predict(mod_svm, dados_teste[, -1]) classificacoes_svm
Matriz de confusão
<- confusionMatrix(as.factor(classificacoes_svm), as.factor(dados_teste[,
mc_svm 1]), positive = "1", mode = "prec_recall")
$table mc_svm
Reference
Prediction 0 1
0 11768 1098
1 209 489
Métricas
<- mc_svm$overall["Accuracy"]
acuracia_svm <- mc_svm$byClass["Precision"]
precisao_svm <- mc_svm$byClass["Recall"]
recall_svm <- mc_svm$byClass["F1"]
f1_svm <- c(acuracia_svm, precisao_svm, recall_svm, f1_svm)
metricas_svm metricas_svm
Accuracy Precision Recall F1
0.9036420 0.7005731 0.3081285 0.4280088
XGBoost
library(xgboost)
library(caret)
Split
# XGBoost precisa do dataset em formato de matriz e de atributos e classe
# separados
set.seed(1234)
<- sample(1:nrow(dados), nrow(dados) * 0.7, replace = FALSE)
amostra <- dados[amostra, ]
dados_treino <- dados[-amostra, ]
dados_teste <- as.numeric(dados_treino[, 1])
label_treino <- ifelse(label_treino == 1, 1, 0)
label_treino <- as.numeric(dados_teste[, 1])
label_teste <- ifelse(label_teste == 1, 1, 0)
label_teste <- sapply(label_teste, as.factor)
label_teste <- as.matrix(dados_treino[, -1])
dados_treino <- as.matrix(dados_teste[, -1]) dados_teste
Treinamento
# profundidade máxima das árvores:10; taxa de aprendizagem: 0.1; 4 threads para
# processar; 100 iterações; classe binária
<- xgboost(data = dados_treino, label = label_treino, max.depth = 10,
mod_xgboost eta = 0.1, nthread = 4, nrounds = 100, objective = "binary:logistic")
[03:24:19] WARNING: amalgamation/../src/learner.cc:1061: Starting in XGBoost 1.3.0, the default evaluation metric used with the objective 'binary:logistic' was changed from 'error' to 'logloss'. Explicitly set eval_metric if you'd like to restore the old behavior.
[1] train-logloss:0.620121
[2] train-logloss:0.560138
[3] train-logloss:0.510011
[4] train-logloss:0.466530
[5] train-logloss:0.429026
[6] train-logloss:0.397094
[7] train-logloss:0.368925
[8] train-logloss:0.344449
[9] train-logloss:0.322770
[10] train-logloss:0.303598
[11] train-logloss:0.286754
[12] train-logloss:0.271805
[13] train-logloss:0.258299
[14] train-logloss:0.246145
[15] train-logloss:0.235085
[16] train-logloss:0.225461
[17] train-logloss:0.216735
[18] train-logloss:0.208905
[19] train-logloss:0.201693
[20] train-logloss:0.194875
[21] train-logloss:0.188897
[22] train-logloss:0.183516
[23] train-logloss:0.178304
[24] train-logloss:0.173588
[25] train-logloss:0.169805
[26] train-logloss:0.165978
[27] train-logloss:0.162682
[28] train-logloss:0.159377
[29] train-logloss:0.156210
[30] train-logloss:0.153590
[31] train-logloss:0.151233
[32] train-logloss:0.148606
[33] train-logloss:0.146528
[34] train-logloss:0.144308
[35] train-logloss:0.142231
[36] train-logloss:0.140434
[37] train-logloss:0.138849
[38] train-logloss:0.137517
[39] train-logloss:0.135869
[40] train-logloss:0.134342
[41] train-logloss:0.132941
[42] train-logloss:0.131590
[43] train-logloss:0.130202
[44] train-logloss:0.128997
[45] train-logloss:0.127563
[46] train-logloss:0.126781
[47] train-logloss:0.125591
[48] train-logloss:0.124887
[49] train-logloss:0.123962
[50] train-logloss:0.123116
[51] train-logloss:0.122318
[52] train-logloss:0.121588
[53] train-logloss:0.120853
[54] train-logloss:0.119852
[55] train-logloss:0.119051
[56] train-logloss:0.118352
[57] train-logloss:0.117625
[58] train-logloss:0.117191
[59] train-logloss:0.116134
[60] train-logloss:0.115067
[61] train-logloss:0.114468
[62] train-logloss:0.113777
[63] train-logloss:0.113094
[64] train-logloss:0.112405
[65] train-logloss:0.111512
[66] train-logloss:0.110826
[67] train-logloss:0.110437
[68] train-logloss:0.110195
[69] train-logloss:0.109397
[70] train-logloss:0.108938
[71] train-logloss:0.108470
[72] train-logloss:0.108253
[73] train-logloss:0.107931
[74] train-logloss:0.107764
[75] train-logloss:0.107250
[76] train-logloss:0.106522
[77] train-logloss:0.105654
[78] train-logloss:0.105250
[79] train-logloss:0.105040
[80] train-logloss:0.104587
[81] train-logloss:0.103956
[82] train-logloss:0.103029
[83] train-logloss:0.102391
[84] train-logloss:0.101889
[85] train-logloss:0.101478
[86] train-logloss:0.101111
[87] train-logloss:0.100868
[88] train-logloss:0.100734
[89] train-logloss:0.100506
[90] train-logloss:0.100388
[91] train-logloss:0.100314
[92] train-logloss:0.100142
[93] train-logloss:0.099945
[94] train-logloss:0.099761
[95] train-logloss:0.099055
[96] train-logloss:0.098092
[97] train-logloss:0.097989
[98] train-logloss:0.097306
[99] train-logloss:0.096803
[100] train-logloss:0.096430
Classificações
<- predict(mod_xgboost, dados_teste)
classificacoes_xgboost <- ifelse(classificacoes_xgboost > 0.5, 1, 0)
classificacoes_xgboost <- sapply(classificacoes_xgboost, as.factor) classificacoes_xgboost
Matriz de confusão
<- confusionMatrix(classificacoes_xgboost, label_teste, positive = "1",
mc_xgboost mode = "prec_recall")
$table mc_xgboost
Reference
Prediction 0 1
0 11440 796
1 530 798
Métricas
<- mc_xgboost$overall["Accuracy"]
acuracia_xgboost <- mc_xgboost$byClass["Precision"]
precisao_xgboost <- mc_xgboost$byClass["Recall"]
recall_xgboost <- mc_xgboost$byClass["F1"]
f1_xgboost <- c(acuracia_xgboost, precisao_xgboost, recall_xgboost, f1_xgboost)
metricas_xgboost metricas_xgboost
Accuracy Precision Recall F1
0.9022412 0.6009036 0.5006274 0.5462012
COMPARAÇÃO
<- c("Adaboost", "Árvores de decisão", "Gradient boosting", "kNN", "Naive Bayes",
row_names "Random forest", "Redes neurais", "Regressão logística", "SVM", "XGBoost")
<- c("Acurácia", "Precisão", "Recall", "F1")
col_names = c(metricas_adaboost, metricas_arvores, metricas_gb, metricas_knn, metricas_nb,
data
metricas_rf, metricas_rn, metricas_rl, metricas_svm, metricas_xgboost)<- matrix(ncol = 4, data = data, byrow = TRUE)
tbl_comp dimnames(tbl_comp) <- list(row_names, col_names)
library(DT)
::datatable(tbl_comp, options = list(dom = "t")) %>% formatPercentage(1:4, digits = 1) DT
PRODUÇÃO
Após escolher o algoritmo que entrará em produção, pode-se salvá-lo no formato .rds para fazer as previsões.
O exemplo abaixo salva o modelo SVM, que teve maior precisão. Assumindo que o banco entrará em contato com todos os clientes classificados como compradores (classe=1), faz sentido escolher o algoritmo que se saiu melhor nessa métrica.
saveRDS(mod_svm, "mod_final.rds")
Para carregá-lo:
<- readRDS("mod_final.rds") mod_final
Para fazer uma nova classificação:
<- c(-1.124068, -0.07825628, 0.9845124, -0.9015068, -0.5693443, -0.4114486,
novo_cliente -0.2519376, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1)
<- predict(mod_final, matrix(novo_cliente, ncol = 42))
nova_classificacao paste("Classificação:", as.vector(nova_classificacao))
[1] "Classificação: 0"
Assim, o algoritmo prevê que esse novo_cliente não comprará o produto bancário.