Este trabalho é parte da conclusão do curso de Big Data Analytics com R e Microsoft Azure Machine Learning da Data Science Academy (https://www.datascienceacademy.com.br/start) e tem como objetivo colocar em prática os conceitos aprendidos durante o curso, sendo uma excelente forma para que o aluno possa descobrir suas dificuldades e ter contato com problemas reais de quem trabalha com ciência de dados ou utiliza análise de dados para resolver problemas de forma geral.
Utilizaremos a linguagem R e seus pacotes para a criação do modelo preditivo e o RMarkdown para apresentar todas as etapas da construção do modelo, o problema está publicado como uma competição do Kaggle e pode ser visualizado no link: (https://www.kaggle.com/c/talkingdata-adtracking-fraud-detection/overview).
O risco de fraude está em toda parte, mas para as empresas que anunciam on-line, a fraude de cliques pode ocorrer em um volume esmagador, resultando em dados de cliques enganosos e desperdício de dinheiro. Os canais de anúncios podem aumentar os custos simplesmente clicando no anúncio em larga escala. Com mais de 1 bilhão de dispositivos móveis inteligentes em uso ativo todos os meses, a China é o maior mercado móvel do mundo e, portanto, sofre com grandes volumes de tráfego fraudulento.
O TalkingData, a maior plataforma independente de serviço de big data da China, cobre mais de 70% dos dispositivos móveis ativos em todo o país. Eles processam 3 bilhões de cliques por dia, dos quais 90% são potencialmente fraudulentos. Sua abordagem atual para evitar a fraude de cliques para desenvolvedores de aplicativos é medir a jornada do clique de um usuário em seu portfólio e sinalizar endereços IP que produzem muitos cliques, mas nunca acabam instalando aplicativos. Com essas informações, eles criaram uma lista negra de IP e lista negra de dispositivos.
Apesar de bem-sucedidos, eles querem estar sempre um passo à frente dos fraudadores e procuraram a comunidade Kaggle para obter ajuda no desenvolvimento de sua solução. Na segunda competição com o Kaggle, você é desafiado a criar um algoritmo que prevê se um usuário fará o download de um aplicativo depois de clicar em um anúncio de aplicativo para celular. Para dar suporte à sua modelagem, eles forneceram um conjunto de dados generoso, cobrindo aproximadamente 200 milhões de cliques em 4 dias!
O método de avaliação utilizado para o modelo preditivo será a curva ROC entre o valor previsto e o valor real observado.
Cada linha dos dados de treinamento contém um registro de clique, com os seguintes atributos:
As colunas ip, app, dispositivo, so e canal estão codificadas.
Os dados do teste são semelhantes, com as seguintes diferenças:
# Dados de treino
glimpse(treino)
## Observations: 100,000
## Variables: 8
## $ ip <int> 87540, 105560, 101424, 94584, 68413, 93663, 17...
## $ app <int> 12, 25, 12, 13, 12, 3, 1, 9, 2, 3, 3, 3, 3, 6,...
## $ device <int> 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 2...
## $ os <int> 13, 17, 19, 13, 1, 17, 17, 25, 22, 19, 22, 13,...
## $ channel <int> 497, 259, 212, 477, 178, 115, 135, 442, 364, 1...
## $ click_time <fct> 2017-11-07 09:30:38, 2017-11-07 13:40:27, 2017...
## $ attributed_time <fct> , , , , , , , , , , , , , , , , , , , , , , , ,
## $ is_attributed <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0...
# Dados de teste
glimpse(teste)
## Observations: 18,790,469
## Variables: 7
## $ click_id <int> 0, 1, 2, 3, 4, 5, 6, 7, 9, 8, 10, 11, 12, 13, 14, 1...
## $ ip <int> 5744, 119901, 72287, 78477, 123080, 110769, 12540, ...
## $ app <int> 9, 9, 21, 15, 12, 18, 3, 27, 18, 12, 26, 3, 12, 9, ...
## $ device <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, ...
## $ os <int> 3, 3, 19, 13, 13, 13, 1, 19, 10, 53, 19, 19, 13, 13...
## $ channel <int> 107, 466, 128, 111, 328, 107, 137, 153, 107, 424, 4...
## $ click_time <fct> 2017-11-10 04:00:00, 2017-11-10 04:00:00, 2017-11-1...
Vamos começar realizando um pouco de engenharia de atributos (Feature engineering), criando novos atributos (colunas no nosso conjunto de dados) e posteriormente avaliaremos quais serão os atributos com maior importância para a resolução do nosso problema (Feature selection)
#Transformando colunas fatores em POSIXct
# dados de treino
treino$click_time <- as.POSIXct(treino$click_time)
# dados de teste
teste$click_time <- as.POSIXct(teste$click_time)
# Criando novas variáveis e eliminando as que não vou mais utilizar
# dados de treino
treino01 <- treino %>%
select(-c(attributed_time)) %>%
mutate(diaSemana = weekdays(click_time), hora = hour(click_time)) %>%
select(-c(click_time)) %>%
add_count(ip, diaSemana, hora) %>% rename("ip_diaSem_h" = n) %>%
add_count(ip, hora, channel) %>% rename("ip_h_chan" = n) %>%
add_count(ip, hora, os) %>% rename("ip_h_os" = n) %>%
add_count(ip, hora, app) %>% rename("ip_h_app" = n) %>%
add_count(ip, hora, device) %>% rename("ip_h_dev" = n) %>%
select(-c(ip))
# Realizando as mesmas operações para os dados de teste
teste01 <- teste %>%
mutate(diaSemana = weekdays(click_time), hora = hour(click_time)) %>%
select(-c(click_time)) %>%
add_count(ip, diaSemana, hora) %>% rename("ip_diaSem_h" = n) %>%
add_count(ip, hora, channel) %>% rename("ip_h_chan" = n) %>%
add_count(ip, hora, os) %>% rename("ip_h_os" = n) %>%
add_count(ip, hora, app) %>% rename("ip_h_app" = n) %>%
add_count(ip, hora, device) %>% rename("ip_h_dev" = n) %>%
select(-c(ip))
# Transformando variáveis categóricas em fator
treino01$is_attributed <- as.factor(treino01$is_attributed)
treino01$diaSemana <- as.factor(treino01$diaSemana)
teste01$diaSemana <- as.factor(teste01$diaSemana)
Vamos agora verificar a existência de valores nulos entre os nossos dados.
# Contagem de valores nulos por coluna nos dados de treino
colSums(is.na(treino01))
## app device os channel is_attributed
## 0 0 0 0 0
## diaSemana hora ip_diaSem_h ip_h_chan ip_h_os
## 0 0 0 0 0
## ip_h_app ip_h_dev
## 0 0
# Contagem de valores nulos por coluna nos dados de teste
colSums(is.na(teste01))
## click_id app device os channel diaSemana
## 0 0 0 0 0 0
## hora ip_diaSem_h ip_h_chan ip_h_os ip_h_app ip_h_dev
## 0 0 0 0 0 0
Para que o aprendizado seja realizado da melhor maneira possível e o modelo não aprenda mais informações sobre uma classe do que outra, devemos garantir que os dados estejam balanceados, ou seja, possuir aproximadamente o mesmo número de observações para cada classe que queremos prever.
# Proporção original
table(treino01$is_attributed)
##
## 0 1
## 99773 227
Podemos ver que o número de observações da classe “0” (app não baixad) é muito maior do que o número de classes “1” (app baixado), vamos utilizar a função SMOTE do pacote DMwR para realizar o balanceamentoe e resolver o problema.
treino01 <- SMOTE(is_attributed ~ ., as.data.frame(treino01), k = 3, perc.over = 400, perc.under = 150)
# Verificando a nova proporção
table(treino01$is_attributed)
##
## 0 1
## 1362 1135
Vamos utilizar eliminação recursiva de atributos para nos ajudar a escolher quais colunas vamos utilizar para a criação do modelo preditivo. Basicamente, um modelo é criado e ajustado aos dados. Então, com os coeficientes criados para cada atributo, eliminamos o que possui o menor coeficiente ou importância, o processo é repetido até alcançarmos um número predeterminado de atributos ou por algum outro critério que definirmos.
# Separando os dados com as classes que quero prever
treino_rfe <- treino01[-5]
# Definindo o objeto de controle para o modelo de seleção de variáveis
controle <- rfeControl(functions = rfFuncs, method = "cv", number = 10)
# Rodando o algoritmo RFE (Eliminação recursiva de atributos)
resultados <- rfe(treino_rfe, treino01$is_attributed, sizes = c(1:11), rfeControl = controle)
# Resumo dos resultados
print(resultados)
##
## Recursive feature selection
##
## Outer resampling method: Cross-Validated (10 fold)
##
## Resampling performance over subset size:
##
## Variables Accuracy Kappa AccuracySD KappaSD Selected
## 1 0.9612 0.9213 0.009999 0.02044
## 2 0.9575 0.9139 0.018904 0.03869
## 3 0.9459 0.8902 0.017370 0.03555
## 4 0.9619 0.9229 0.014035 0.02854
## 5 0.9692 0.9376 0.013087 0.02652 *
## 6 0.9680 0.9352 0.013465 0.02728
## 7 0.9636 0.9263 0.014411 0.02921
## 8 0.9588 0.9165 0.013990 0.02831
## 9 0.9640 0.9271 0.013079 0.02648
## 10 0.9628 0.9246 0.014120 0.02864
## 11 0.9612 0.9214 0.013597 0.02753
##
## The top 5 variables (out of 5):
## app, channel, device, os, hora
# Lista com as variáveis escolhidas
predictors(resultados)
## [1] "app" "channel" "device" "os" "hora"
# Plotando os resultados
plot(resultados, type=c("g", "o"))
As variáveis escolhidas são aquelas que geraram um modelo com a maior acurácia, e são elas que vamos utilizar para criar modelo preditivo.
Vamos dividir os nossos dados de treino e comparar o desempenho entre diversos algoritmos, escolheremos o melhor entre eles, lembrando que o método de avaliação é a cuva ROC.
# As colunas utilizadas serão as calculadas pelo método rfe
cols <- predictors(resultados)
treino_rfe_cols <- treino01[cols]
treino_rfe_cols$is_attributed <- treino01$is_attributed
split <- createDataPartition(y = treino_rfe_cols$is_attributed, p = 0.7, list = FALSE)
# Criando dados de treino e teste (utilizando o conjunto de dados de treino inicial)
treino_set <- treino_rfe_cols[split, ]
teste_set <- treino_rfe_cols[-split, ]
# Verificando a proporção das classes nos dados de treino
round(prop.table(table(treino_set$is_attributed)),2)
##
## 0 1
## 0.55 0.45
# Verificando a proporção das classes nos dados de teste
round(prop.table(table(teste_set$is_attributed)),2)
##
## 0 1
## 0.55 0.45
Tudo certo, podemos começar a criação dos modelos.
# Criando e treinando o modelo
svmModel <- train(is_attributed ~ ., data = treino_set,
method = "svmPoly",
tuneGrid = data.frame(degree = 1,
scale = 1,
C = 1),
preProcess = c("pca","scale","center"),
na.action = na.omit)
# Realizando previsões
SVMPredictions <-predict(svmModel, teste_set)
# Confusion Matrix
print(confusionMatrix(SVMPredictions, teste_set$is_attributed))
## Confusion Matrix and Statistics
##
## Reference
## Prediction 0 1
## 0 329 122
## 1 79 218
##
## Accuracy : 0.7313
## 95% CI : (0.698, 0.7628)
## No Information Rate : 0.5455
## P-Value [Acc > NIR] : < 2.2e-16
##
## Kappa : 0.4523
##
## Mcnemar's Test P-Value : 0.003052
##
## Sensitivity : 0.8064
## Specificity : 0.6412
## Pos Pred Value : 0.7295
## Neg Pred Value : 0.7340
## Prevalence : 0.5455
## Detection Rate : 0.4398
## Detection Prevalence : 0.6029
## Balanced Accuracy : 0.7238
##
## 'Positive' Class : 0
##
# Plotando curva ROC
svmROC <- evalmod(scores = c(SVMPredictions), labels = teste_set$is_attributed)
autoplot(svmROC)
# Criando e treinando o modelo
DecTreeModel <- train(is_attributed ~ ., data = treino_set,
method = "C5.0",
preProcess=c("scale","center"),
na.action = na.omit)
# Realizando previsões
dtPredictions <-predict(DecTreeModel, teste_set)
# Confusion Matrix
print(confusionMatrix(dtPredictions, teste_set$is_attributed))
## Confusion Matrix and Statistics
##
## Reference
## Prediction 0 1
## 0 397 9
## 1 11 331
##
## Accuracy : 0.9733
## 95% CI : (0.959, 0.9836)
## No Information Rate : 0.5455
## P-Value [Acc > NIR] : <2e-16
##
## Kappa : 0.9461
##
## Mcnemar's Test P-Value : 0.8231
##
## Sensitivity : 0.9730
## Specificity : 0.9735
## Pos Pred Value : 0.9778
## Neg Pred Value : 0.9678
## Prevalence : 0.5455
## Detection Rate : 0.5307
## Detection Prevalence : 0.5428
## Balanced Accuracy : 0.9733
##
## 'Positive' Class : 0
##
# Plotando curva ROC
decROC <- evalmod(scores = c(dtPredictions), labels = teste_set$is_attributed)
autoplot(decROC)
# Criando e treinando o modelo
RandomFModel <- train(is_attributed~., data = treino_set,
method = "rf",
preProcess=c("scale","center"),
na.action = na.omit)
# Realizando previsões
rfPredictions <- predict(RandomFModel, teste_set)
# Confusion Matrix
print(confusionMatrix(rfPredictions, teste_set$is_attributed))
## Confusion Matrix and Statistics
##
## Reference
## Prediction 0 1
## 0 403 15
## 1 5 325
##
## Accuracy : 0.9733
## 95% CI : (0.959, 0.9836)
## No Information Rate : 0.5455
## P-Value [Acc > NIR] : < 2e-16
##
## Kappa : 0.9459
##
## Mcnemar's Test P-Value : 0.04417
##
## Sensitivity : 0.9877
## Specificity : 0.9559
## Pos Pred Value : 0.9641
## Neg Pred Value : 0.9848
## Prevalence : 0.5455
## Detection Rate : 0.5388
## Detection Prevalence : 0.5588
## Balanced Accuracy : 0.9718
##
## 'Positive' Class : 0
##
# Plotando curva ROC
rfROC <- evalmod(scores = c(rfPredictions), labels = teste_set$is_attributed)
autoplot(rfROC)
# Criando e treinando o modelo
knnModel <- train(is_attributed~., data = treino_set,
method = "knn",
preProcess=c("scale","center"),
na.action = na.omit)
# Realizando previsões
knnPredictions <-predict(knnModel, teste_set)
# Confusion Matrix
print(confusionMatrix(knnPredictions, teste_set$is_attributed))
## Confusion Matrix and Statistics
##
## Reference
## Prediction 0 1
## 0 365 27
## 1 43 313
##
## Accuracy : 0.9064
## 95% CI : (0.8832, 0.9263)
## No Information Rate : 0.5455
## P-Value [Acc > NIR] : <2e-16
##
## Kappa : 0.812
##
## Mcnemar's Test P-Value : 0.073
##
## Sensitivity : 0.8946
## Specificity : 0.9206
## Pos Pred Value : 0.9311
## Neg Pred Value : 0.8792
## Prevalence : 0.5455
## Detection Rate : 0.4880
## Detection Prevalence : 0.5241
## Balanced Accuracy : 0.9076
##
## 'Positive' Class : 0
##
# Plotando curva ROC
knnROC <- evalmod(scores = c(knnPredictions), labels = teste_set$is_attributed)
autoplot(knnROC)
# Criando e treinando o modelo
lgmModel <- train(is_attributed~., data = treino_set,
method = "glm",
preProcess=c("scale","center"),
na.action = na.omit)
# Realizando previsões
lgmPredictions <-predict(lgmModel, teste_set)
# Confusion Matrix
print(confusionMatrix(lgmPredictions, teste_set$is_attributed))
## Confusion Matrix and Statistics
##
## Reference
## Prediction 0 1
## 0 329 136
## 1 79 204
##
## Accuracy : 0.7126
## 95% CI : (0.6787, 0.7448)
## No Information Rate : 0.5455
## P-Value [Acc > NIR] : < 2.2e-16
##
## Kappa : 0.4121
##
## Mcnemar's Test P-Value : 0.0001339
##
## Sensitivity : 0.8064
## Specificity : 0.6000
## Pos Pred Value : 0.7075
## Neg Pred Value : 0.7208
## Prevalence : 0.5455
## Detection Rate : 0.4398
## Detection Prevalence : 0.6217
## Balanced Accuracy : 0.7032
##
## 'Positive' Class : 0
##
# Plotando curva ROC
lgmROC <- evalmod(scores = c(lgmPredictions), labels = teste_set$is_attributed)
autoplot(knnROC)
# Utilizando resamples para comparar modelos
compara_Modelos <- resamples(list(SVM = svmModel, DECT = DecTreeModel, RF = RandomFModel, KNN = knnModel, LGM = lgmModel))
# Resumo da performance dos modelos
summary(compara_Modelos)
##
## Call:
## summary.resamples(object = compara_Modelos)
##
## Models: SVM, DECT, RF, KNN, LGM
## Number of resamples: 25
##
## Accuracy
## Min. 1st Qu. Median Mean 3rd Qu. Max. NA's
## SVM 0.6697531 0.7065217 0.7303371 0.7244368 0.7402799 0.7635468 0
## DECT 0.9318182 0.9478673 0.9515625 0.9520349 0.9578125 0.9665145 0
## RF 0.9348172 0.9500000 0.9527439 0.9522506 0.9570552 0.9650986 0
## KNN 0.8599398 0.8724409 0.8873239 0.8861911 0.8920188 0.9254658 0
## LGM 0.6641566 0.7007634 0.7210031 0.7167957 0.7317460 0.7561350 0
##
## Kappa
## Min. 1st Qu. Median Mean 3rd Qu. Max. NA's
## SVM 0.3238876 0.4051259 0.4482943 0.4371833 0.4704961 0.5182809 0
## DECT 0.8625000 0.8933020 0.9028784 0.9026860 0.9151096 0.9313519 0
## RF 0.8682238 0.8979266 0.9049933 0.9034261 0.9121285 0.9291904 0
## KNN 0.7196048 0.7442483 0.7728471 0.7712524 0.7841657 0.8508209 0
## LGM 0.3128654 0.3909925 0.4269559 0.4217935 0.4510101 0.5033535 0
# Comparando de maneira visual
scales <- list(x=list(relation = "free"), y = list(relation = "free"))
bwplot(compara_Modelos, scales = scales)
Podemos ver tanto pela comparação acima quanto pela visualização da curva ROC que o modelo criado com o algoritmo Random Forest e o de árvore de decisão foram os melhores e alcançaram uma boa precisão nos dados utilizados. Podemos agora realizar a previsão nos dados de teste que o kaggle disponibilizou e verificarmos como o nosso modelo irá se sair.
modelo_Escolhido <- RandomFModel
# Importando arquivo para colocarmos as previsões
#submission <- read.csv('C:/dataScience/talkingData/sample_submission.csv')
# Realizando previsões nos dados de teste
#previsoes <- predict(modelo_Escolhido, teste01)
#submission$is_attributed <- previsoes
# Criando arquivo csv
#write_csv(submission, "submission.csv")