Resumo
Neste Projeto iremos abordar um quesito muito importante em Projeto de Machine Learning que é a escolha de um determinado algoritmo para um determinado problema. Neste trabalho o problema se traduz em tomar a decisão sobre qual algoritmo escolher para a construção de um modelo preditivo para um problema de Classificação Binária. Iremos abordar essa questão do início ao fim do projeto, desde a escolha de alguns algoritmos para a construção de modelos preditivos para o problema de Classificação Binária, usando técnicas de transformação dos Dados, Ajustes(Tuning) e a evolução desses modelos para melhorar a precisão(Acurácia) até atingir o limite de precisão de cada modelo,e por fim escolher aquele que possui a melhor acurácia. Para este projeto usaremos o conjunto de dados (Wisconsin Breast Cancer dataset), esse conjunto de dados representa uma amostra de tecido de câncer de mama que foram coletados de hospitais da Universidade de Wisconsin e pode ser baixado também da Internet no Repositório UCI Machine Learning.
O objetivo principal do Projeto é criar um modelo que melhor preveja a Class( benign(positivo) malignant(negativo). Embora as metodologias de teste difiram, os melhores resultados publicados parecem estar na precisão acima de 90%, entorno de 96% e 97%. A obtenção de resultados nesta faixa é o que desejamos neste projeto.
Carregando Pacotes e processando
library(mlbench)
library(caret)
## Loading required package: lattice
## Loading required package: ggplot2
data(BreastCancer)
Aqui criaremos o dataset de treinamento e teste com 80 % e o de validação com 20 % dos dados, este último para usá-lo após o treinamento e aperfeiçoamento do modelo
set.seed(7)
Indice <- createDataPartition(BreastCancer$Class, p=0.80, list=FALSE)
# seleciona 20% dos dados para Validação do modelo
validation <- BreastCancer[-Indice,]
# Usa os 80 % restantes dos dados para treinamente e teste dos modelos
training <- BreastCancer[Indice,]
Análise Exploratória dos Dados
# dimensão do dataset training
dim(training)
## [1] 560 11
# primeiros 5 registros
head(training, 5)
## Id Cl.thickness Cell.size Cell.shape Marg.adhesion Epith.c.size
## 2 1002945 5 4 4 5 7
## 4 1016277 6 8 8 1 3
## 5 1017023 4 1 1 3 2
## 6 1017122 8 10 10 8 7
## 7 1018099 1 1 1 1 2
## Bare.nuclei Bl.cromatin Normal.nucleoli Mitoses Class
## 2 10 3 2 1 benign
## 4 4 3 7 1 benign
## 5 1 3 1 1 benign
## 6 10 9 7 1 malignant
## 7 10 3 1 1 benign
sapply(training, function(x) sum(is.na(x)))
## Id Cl.thickness Cell.size Cell.shape
## 0 0 0 0
## Marg.adhesion Epith.c.size Bare.nuclei Bl.cromatin
## 0 0 13 0
## Normal.nucleoli Mitoses Class
## 0 0 0
Sabemos que determinados atributos do dataset de treinamento as vezes podem ser descartados na análise dos dados. Aqui Podemos ver que o número da amostra (Id) provavelmente não será útil, então vamos removê-lo. Podemos ver também que todas os atributos são inteiros e há também alguns dados faltantes(“NA”), exatamente 13 na coluna Bare.nuclei. Isso sugere que talvez seja necessário remover os registros com valores faltantes (“NA”) ou utilizar algumas técnicas de análise e modelagem para imputar dados nos atributos com valores faltantes.
#tipos de dados
sapply(training, class)
## $Id
## [1] "character"
##
## $Cl.thickness
## [1] "ordered" "factor"
##
## $Cell.size
## [1] "ordered" "factor"
##
## $Cell.shape
## [1] "ordered" "factor"
##
## $Marg.adhesion
## [1] "ordered" "factor"
##
## $Epith.c.size
## [1] "ordered" "factor"
##
## $Bare.nuclei
## [1] "factor"
##
## $Bl.cromatin
## [1] "factor"
##
## $Normal.nucleoli
## [1] "factor"
##
## $Mitoses
## [1] "factor"
##
## $Class
## [1] "factor"
Podemos ver que o atributo Id é character e os restantes são fatores. Eu acho que para modelar pode ser mais útil trabalhar com os dados como números do que fatores. Fatores podem facilitar as coisas para algoritmos de Árvore de Decisão, visto que existe uma relação ordinal entre os níveis dos atributos fatores. Assim irei remover (Id) e converter fatores para numéricos inteiros.
#Remove atributo (Id)
training <- training[,-1]
# Converte atributos fatores para numéricos
for(i in 1:9) {
training[,i] <- as.numeric(as.character(training[,i]))
}
# Sumário após as modificações
summary(training)
## Cl.thickness Cell.size Cell.shape Marg.adhesion
## Min. : 1.000 Min. : 1.000 Min. : 1.000 Min. : 1.000
## 1st Qu.: 2.000 1st Qu.: 1.000 1st Qu.: 1.000 1st Qu.: 1.000
## Median : 4.000 Median : 1.000 Median : 2.000 Median : 1.000
## Mean : 4.384 Mean : 3.116 Mean : 3.198 Mean : 2.875
## 3rd Qu.: 6.000 3rd Qu.: 5.000 3rd Qu.: 5.000 3rd Qu.: 4.000
## Max. :10.000 Max. :10.000 Max. :10.000 Max. :10.000
##
## Epith.c.size Bare.nuclei Bl.cromatin Normal.nucleoli
## Min. : 1.000 Min. : 1.000 Min. : 1.000 Min. : 1.000
## 1st Qu.: 2.000 1st Qu.: 1.000 1st Qu.: 2.000 1st Qu.: 1.000
## Median : 2.000 Median : 1.000 Median : 3.000 Median : 1.000
## Mean : 3.232 Mean : 3.468 Mean : 3.405 Mean : 2.877
## 3rd Qu.: 4.000 3rd Qu.: 5.000 3rd Qu.: 4.250 3rd Qu.: 4.000
## Max. :10.000 Max. :10.000 Max. :10.000 Max. :10.000
## NA's :13
## Mitoses Class
## Min. : 1.000 benign :367
## 1st Qu.: 1.000 malignant:193
## Median : 1.000
## Mean : 1.611
## 3rd Qu.: 1.000
## Max. :10.000
##
Aqui podemos ver que a variável Class está desequilibrada. Vamos analisar este desequilíbrio com mais detalhe através da Distribuição de Classe.
# distribuição de classe
cbind(freq=table(training$Class), percentage=prop.table(table(training$Class))*100)
## freq percentage
## benign 367 65.53571
## malignant 193 34.46429
Podemos perceber facilmente que há um forte desequílibrio estando 65 % para Benigno contra 35 % para Malígno. Mas por enquanto ainda não trataremos esse desequilíbrio.
Agora vamos analisar a correlação entre os atributos, mas excluindo da correlação os 13 registros incompletos com (“Na”).
#correlação entre as variáveis de entrada
complete_cases <- complete.cases(training)
cor(training[complete_cases,1:9])
## Cl.thickness Cell.size Cell.shape Marg.adhesion
## Cl.thickness 1.0000000 0.6200884 0.6302917 0.4741733
## Cell.size 0.6200884 1.0000000 0.9011340 0.7141150
## Cell.shape 0.6302917 0.9011340 1.0000000 0.6846206
## Marg.adhesion 0.4741733 0.7141150 0.6846206 1.0000000
## Epith.c.size 0.5089557 0.7404824 0.7043423 0.5860660
## Bare.nuclei 0.5600770 0.6687226 0.6896724 0.6660165
## Bl.cromatin 0.5290733 0.7502700 0.7276114 0.6660533
## Normal.nucleoli 0.5143933 0.7072182 0.7127155 0.6031036
## Mitoses 0.3426018 0.4506532 0.4345125 0.4314910
## Epith.c.size Bare.nuclei Bl.cromatin Normal.nucleoli
## Cl.thickness 0.5089557 0.5600770 0.5290733 0.5143933
## Cell.size 0.7404824 0.6687226 0.7502700 0.7072182
## Cell.shape 0.7043423 0.6896724 0.7276114 0.7127155
## Marg.adhesion 0.5860660 0.6660165 0.6660533 0.6031036
## Epith.c.size 1.0000000 0.5568406 0.6102032 0.6433364
## Bare.nuclei 0.5568406 1.0000000 0.6668483 0.5795794
## Bl.cromatin 0.6102032 0.6668483 1.0000000 0.6838547
## Normal.nucleoli 0.6433364 0.5795794 0.6838547 1.0000000
## Mitoses 0.4775271 0.3539473 0.3545122 0.4084127
## Mitoses
## Cl.thickness 0.3426018
## Cell.size 0.4506532
## Cell.shape 0.4345125
## Marg.adhesion 0.4314910
## Epith.c.size 0.4775271
## Bare.nuclei 0.3539473
## Bl.cromatin 0.3545122
## Normal.nucleoli 0.4084127
## Mitoses 1.0000000
Podemos ver que há correlações baixas e altas como por exemplo entre os atributos Cell.size e Cell.shape, vale lembrar que alguns algoritmos podem se beneficiar da remoção de atributos altamente correlacionados.
Agora vamos visualizar individuamente através de Histogramas cada atributo.
par(mfrow=c(3,3))
for(i in 1:9) {
hist(training[,i], main=names(training)[i])
}
Podemos ver que quase todas as distribuições têm uma forma exponencial ou bimodal, o que permite nos beneficiar das transformações de (log transforms ou power transforms) mais tarde.
Vamos continuar visualizando os atributos individualmente, mas agora usando gráficos de densidade para obter uma visão mais suave das distribuições.
par(mfrow=c(3,3))
complete_cases <- complete.cases(training)
for(i in 1:9) {
plot(density(training[complete_cases,i]), main=names(training)[i])
}
Esses gráficos acima adicionam mais clareza às nossas ideias iniciais, pois podemos ver distribuições bimodais e exponenciais mais claramente.
Vamos dar uma olhada nas distribuições por outra perspectiva usando esses recursos gráficos Box e whisker.
par(mfrow=c(3,3))
for(i in 1:9) {
boxplot(training[,i], main=names(training)[i])
}
Nós vimos distribuições estreitas (achatadas) devido as formas exponenciais que já observamos e a escala limitada a [1,10] para todas as entradas.
Agora, vamos dar uma olhada nas interações entre os atributos. Iniciaremos por uma matriz de dispersão dos atributos coloridos pelos valores da classe. Como os dados são discretos (valores inteiros), precisamos adicionar alguns jitter para tornar os gráficos de dispersão úteis, caso contrário, os pontos estarão todos em cima uns dos outros.
jittered_x <- sapply(training[,1:9], jitter)
pairs(jittered_x, names(training[,1:9]), col=training$Class)
Podemos ver que a parte preta (benigna) agrupada em torno do canto inferior direito (os valores dos atributos são menores) e vermelha (maligna) estão por todo lugar - possuem valores maiores. Como os dados são discretos, podemos usar gráficos de barras para ter uma ideia da interação da distribuição de cada atributo e como eles são discriminados por valor de classe.
par(mfrow=c(3,3))
for(i in 1:9) {
barplot(table(training$Class,training[,i]), main=names(training)[i],)
}
Isso nos dá uma idéia diferenciada de como os valores benignos agrupados à esquerda (valores menores - Cinza escuro) de cada distribuição e valores malignos estão em todo lugar(Cinza Claro).
Agora que já fizemos uma análise dos dados(atributos) tanto individualmente quanto em conjunto do nosso dataset de treinamento, iremos checar vários métodos diferentes e ver o que parece melhor. Já que os dados são discretos, eu inicialmente optaria usar (Decision Tree - Árvore de decisão) como também métodos baseados em regras. Já a regressão e os métodos baseados em instância, acredito que não iriam tão bem com esse dataset. Mas isso é apenas intuição e posso estar errado. Então vamos testar vários algorítmos lineares e não lineares de forma rápida e eficiente.
Usaremos os seguintes Algoritmos :
LINEARES
Logistic Regression (LG)
Linear Discriminate Analysis (LDA)
Regularized Logistic Regression (GLMNET).
NÃO LINEARES
k-Nearest Neighbors (KNN)
Classification and Regression Trees(CART)
Naive Bayes (NB)
Support Vector Machines with Radial Basis Functions (SVM)
Observação:
Os métodos baseados em instâncias são mais eficazes se os atributos de entrada tiverem a mesma escala, já os métodos de regressão podem funcionar melhor se os atributos de entrada forem padronizados. Essas asserções são heurísticas e não leis rígidas de aprendizado de máquina, porque às vezes podemos obter melhores resultados, simplesmente ignorando-as. Assim devemos testar um conjunto de diferentes algoritmos de aprendizado de máquina para avaliar o melhor para um determinado objetivo.
Treinando os Modelos
Como temos uma quantidade razoável de dados, podemos iniciar usando a Validação Cruzada(K-fold Cross-Validation - 3 Repeats) onde k = 10 com 3 repetições e para simplificar usaremos as Métricas Accuracy e Kappa. Por enquanto usaremos esses parâmetros para cada algoritmo, os ajustes(Tunning) necessários serão feitos mais à frente.
#cross-validation
trainControl <- trainControl(method="repeatedcv", number=10, repeats=3)
metric <- "Accuracy"
Precisamos definir a semente(Seed=n) do número aleatório antes de treinar cada algoritmo para garantir que cada algoritmo seja avaliado exatamente nas mesmas divisões de dados, tornando as comparações posteriores mais simples.
Depois que os modelos forem treinados, eles serão adicionados a uma lista e as reamostras - função resamples() serão chamadas para a lista de modelos. Esta função verifica se os modelos são comparáveis e se eles usaram o mesmo esquema de treinamento (trainControl configuration). Este objeto contém as métricas de avaliação para cada k-Fold e cada repetição para cada algoritmo avaliado.
# Logistic Regression (LG)
set.seed(7)
modelfit.glm <- train(Class~., data=training, method="glm", metric=metric,
trControl=trainControl, na.action=na.omit)
# Linear Discriminate Analysis (LDA)
set.seed(7)
modelfit.lda <- train(Class~., data=training, method="lda", metric=metric,
trControl=trainControl, na.action=na.omit)
# Regularized Logistic Regression (GLMNET)
set.seed(7)
modelfit.glmnet <- train(Class~., data=training, method="glmnet", metric=metric,
trControl=trainControl, na.action=na.omit)
# k-Nearest Neighbors (KNN)
set.seed(7)
modelfit.knn <- train(Class~., data=training, method="knn", metric=metric,
trControl=trainControl, na.action=na.omit)
# Classification and Regression Trees (CART)
set.seed(7)
modelfit.cart <- train(Class~., data=training, method="rpart", metric=metric,
trControl=trainControl, na.action=na.omit)
# Naive Bayes (NB)
set.seed(7)
modelfit.nb <- train(Class~., data=training, method="nb", metric=metric, trControl=trainControl,
na.action=na.omit)
# Support Vector Machines with Radial Basis Functions (SVM)
set.seed(7)
modelfit.svm <- train(Class~., data=training, method="svmRadial", metric=metric,
trControl=trainControl, na.action=na.omit)
# Obtendo o Resultado
results <- resamples(list(LG=modelfit.glm, LDA=modelfit.lda, GLMNET=modelfit.glmnet,
KNN=modelfit.knn, CART=modelfit.cart, NB=modelfit.nb, SVM=modelfit.svm))
Tabela de Métricas dos Algoritmos
A tabela abaixo exibe um algoritmo para cada linha e métricas de avaliação para cada coluna.
summary(results)
##
## Call:
## summary.resamples(object = results)
##
## Models: LG, LDA, GLMNET, KNN, CART, NB, SVM
## Number of resamples: 30
##
## Accuracy
## Min. 1st Qu. Median Mean 3rd Qu. Max. NA's
## LG 0.8909091 0.9498316 0.9636364 0.9609977 0.9814815 1 0
## LDA 0.8888889 0.9454545 0.9629630 0.9585734 0.9771825 1 0
## GLMNET 0.8909091 0.9498316 0.9636364 0.9640392 0.9818182 1 0
## KNN 0.9090909 0.9629630 0.9728836 0.9707620 0.9818182 1 0
## CART 0.8571429 0.9090909 0.9272727 0.9324311 0.9461851 1 0
## NB 0.9090909 0.9446970 0.9636364 0.9621878 0.9817340 1 0
## SVM 0.8909091 0.9444444 0.9629630 0.9518951 0.9641234 1 0
##
## Kappa
## Min. 1st Qu. Median Mean 3rd Qu. Max. NA's
## LG 0.7526237 0.8903491 0.9200813 0.9136612 0.9591931 1 0
## LDA 0.7440758 0.8778682 0.9187970 0.9073541 0.9493543 1 0
## GLMNET 0.7526237 0.8903491 0.9207048 0.9203519 0.9601869 1 0
## KNN 0.8014440 0.9195906 0.9402224 0.9355909 0.9598811 1 0
## CART 0.7083333 0.8011046 0.8464753 0.8531748 0.8859322 1 0
## NB 0.7964471 0.8796434 0.9205797 0.9172069 0.9597332 1 0
## SVM 0.7755102 0.8799491 0.9207048 0.8972223 0.9220518 1 0
Plotando e Comparando Algoritmos
dotplot(results)
Podemos ver que é boa a precisão em toda a linha. Todos os algoritmos têm uma precisão média acima de 90%, bem acima da linha de base de 65%, se apenas previrmos benigno. Podemos ver que KNN (97,07%) e regressão logística (LG) e (GLMNET) foram de 96,09 % e 96,40% respectivamente as melhores precisões no problema. Sem fazer nenhum ajuste (Tunning) dos parâmetros padrões desses algoritmos, seria lógico de pronto escolher o KNN com Acurácia 97,07 %. Embora esses tres algoritmos (KNN, LG e GLMNET) apresentem boa Acurácia vamos ver se podemos melhorar esses algoritmos.
Vimos acima que quase todas as distribuições estão (distorcidas), ou seja, têm uma forma Exponencial ou Bimodal. Existem métodos de transformação que podemos usar para ajustar e normalizar essas distribuições. O método Box-Cox transform é bastante apropriado para atributos de entrada positivos como temos nesse problema.
Avaliando Algoritmos Utilizando Box-Cox
Agora vamos usar o recurso Box-Cox para tentar melhorar a Acurácia dos nossos Algoritmos.
trainControl <- trainControl(method="repeatedcv", number=10, repeats=3)
metric <- "Accuracy"
#Logistic Regression (LG)
set.seed(7)
modelfit.glm <- train(Class~., data=training, method="glm", metric=metric, preProc=c("BoxCox"),
trControl=trainControl, na.action=na.omit)
# Linear Discriminate Analysis (LDA)
set.seed(7)
modelfit.lda <- train(Class~., data=training, method="lda", metric=metric, preProc=c("BoxCox"),
trControl=trainControl, na.action=na.omit)
# Regularized Logistic Regression (GLMNET)
set.seed(7)
modelfit.glmnet <- train(Class~., data=training, method="glmnet", metric=metric,
preProc=c("BoxCox"), trControl=trainControl, na.action=na.omit)
# k-Nearest Neighbors (KNN)
set.seed(7)
modelfit.knn <- train(Class~., data=training, method="knn", metric=metric, preProc=c("BoxCox"),
trControl=trainControl, na.action=na.omit)
# Classification and Regression Trees (CART)
set.seed(7)
modelfit.cart <- train(Class~., data=training, method="rpart", metric=metric,
preProc=c("BoxCox"), trControl=trainControl, na.action=na.omit)
# Naive Bayes (NB)
set.seed(7)
modelfit.nb <- train(Class~., data=training, method="nb", metric=metric, preProc=c("BoxCox"),
trControl=trainControl, na.action=na.omit)
# Support Vector Machines with Radial Basis Functions (SVM)
set.seed(7)
modelfit.svm <- train(Class~., data=training, method="svmRadial", metric=metric,
preProc=c("BoxCox"), trControl=trainControl, na.action=na.omit)
# Obtendo o Resultado
transformResults <- resamples(list(LG=modelfit.glm, LDA=modelfit.lda, GLMNET=modelfit.glmnet, KNN=modelfit.knn,
CART=modelfit.cart, NB=modelfit.nb, SVM=modelfit.svm))
Tabela de Métricas dos Algoritmos utilizando BoxCox
A tabela abaixo exibe um algoritmo para cada linha e métricas de avaliação para cada coluna.
summary(transformResults)
##
## Call:
## summary.resamples(object = transformResults)
##
## Models: LG, LDA, GLMNET, KNN, CART, NB, SVM
## Number of resamples: 30
##
## Accuracy
## Min. 1st Qu. Median Mean 3rd Qu. Max. NA's
## LG 0.8727273 0.9454545 0.9725589 0.9640063 0.9818182 1 0
## LDA 0.8909091 0.9629630 0.9725589 0.9683045 0.9818182 1 0
## GLMNET 0.9259259 0.9629630 0.9636364 0.9694833 0.9818182 1 0
## KNN 0.8727273 0.9631313 0.9814815 0.9719633 0.9818182 1 0
## CART 0.8571429 0.9090909 0.9272727 0.9324311 0.9461851 1 0
## NB 0.9090909 0.9629630 0.9636364 0.9688993 0.9818182 1 0
## SVM 0.9090909 0.9631313 0.9636364 0.9719525 0.9818182 1 0
##
## Kappa
## Min. 1st Qu. Median Mean 3rd Qu. Max. NA's
## LG 0.7286822 0.8808664 0.9402224 0.9208886 0.9602888 1 0
## LDA 0.7701950 0.9195906 0.9407109 0.9313355 0.9602888 1 0
## GLMNET 0.8335901 0.9198692 0.9215407 0.9335337 0.9602888 1 0
## KNN 0.7286822 0.9207048 0.9598811 0.9391947 0.9602888 1 0
## CART 0.7083333 0.8011046 0.8464753 0.8531748 0.8859322 1 0
## NB 0.8062016 0.9198692 0.9215407 0.9323071 0.9602888 1 0
## SVM 0.8107364 0.9207048 0.9215407 0.9396187 0.9602888 1 0
Plotando e Comparando Algoritmos utilizando BoxCox
dotplot(transformResults)
Podemos ver que a precisão algoritmo (KNN) melhorou em relação à anterior, agora com 97,19 % contra 97,07 %. Temos também uma melhora significativa da Acurácia do algoritmo SVM , agora com 97,19 %, em relação à anterior que foi 95,18 %.
Ajustando o Algoritmo SVM
Aqui iremos ajustar(Tuning) os principais algoritmos , começando pelo SVM e ver se podemos melhorar a sua precisão.
A implementação do SVM no pacote Caret tem dois parâmetros que podemos ajustar: sigma, que é um termo de suavização e C, que é uma restrição de custo. Mais detalhes sobre esses parâmetros você pode consultar na ajuda da função ksvm () usando o comando ?Ksvm no console do R/Studio. Vamos tentar um intervalo de valores para C entre 1 e 10 e alguns valores pequenos para sigma em torno do padrão de 0,1.
trainControl <- trainControl(method="repeatedcv", number=10, repeats=3)
metric <- "Accuracy"
set.seed(7)
grid <- expand.grid(.sigma=c(0.025, 0.05, 0.1, 0.15), .C=seq(1, 10, by=1))
modelfit.svm <- train(Class~., data=training, method="svmRadial", metric=metric, tuneGrid=grid,
preProc=c("BoxCox"), trControl=trainControl, na.action=na.omit)
print(modelfit.svm)
## Support Vector Machines with Radial Basis Function Kernel
##
## 560 samples
## 9 predictor
## 2 classes: 'benign', 'malignant'
##
## Pre-processing: Box-Cox transformation (9)
## Resampling: Cross-Validated (10 fold, repeated 3 times)
## Summary of sample sizes: 492, 493, 492, 492, 493, 491, ...
## Resampling results across tuning parameters:
##
## sigma C Accuracy Kappa
## 0.025 1 0.9689109 0.9329235
## 0.025 2 0.9683269 0.9314403
## 0.025 3 0.9695278 0.9341394
## 0.025 4 0.9701343 0.9354965
## 0.025 5 0.9701343 0.9354965
## 0.025 6 0.9689109 0.9329293
## 0.025 7 0.9676876 0.9302032
## 0.025 8 0.9676876 0.9302032
## 0.025 9 0.9682937 0.9315590
## 0.025 10 0.9670591 0.9288215
## 0.050 1 0.9713464 0.9382360
## 0.050 2 0.9725585 0.9408533
## 0.050 3 0.9707291 0.9368374
## 0.050 4 0.9701118 0.9354950
## 0.050 5 0.9694945 0.9341274
## 0.050 6 0.9694945 0.9341274
## 0.050 7 0.9694945 0.9341274
## 0.050 8 0.9688885 0.9327716
## 0.050 9 0.9688997 0.9328178
## 0.050 10 0.9682937 0.9314271
## 0.100 1 0.9731646 0.9422986
## 0.100 2 0.9713352 0.9382492
## 0.100 3 0.9701231 0.9355412
## 0.100 4 0.9688997 0.9327829
## 0.100 5 0.9664751 0.9275447
## 0.100 6 0.9646457 0.9233957
## 0.100 7 0.9640396 0.9220387
## 0.100 8 0.9628275 0.9192922
## 0.100 9 0.9628387 0.9192111
## 0.100 10 0.9634560 0.9204856
## 0.150 1 0.9725585 0.9409753
## 0.150 2 0.9707291 0.9368926
## 0.150 3 0.9670924 0.9289103
## 0.150 4 0.9640508 0.9220156
## 0.150 5 0.9616154 0.9165448
## 0.150 6 0.9622214 0.9179356
## 0.150 7 0.9622326 0.9177849
## 0.150 8 0.9628387 0.9190402
## 0.150 9 0.9622214 0.9175415
## 0.150 10 0.9628163 0.9189235
##
## Accuracy was used to select the optimal model using the largest value.
## The final values used for the model were sigma = 0.1 and C = 1.
Plotando e Avaliando o Ajuste no algoritmo SVM
plot(modelfit.svm)
Podemos perceber que houve uma pequena melhora na Acurácia feita com o ajuste acima, conseguimos obter (0.9731646) 97,31 % com parâmetro sigma = 0.1 e C = 1 contra (0.9719525) 97,17 % da anterior. Caro leitor, caso queira testar uma variação maior com os valores atribuídos a esses 2 parâmetros esteja à vontade. Mas acredito que não haja melhora significativa em relação a esses valores, pois testei em off várias possibilidades e esta foi a melhor que obtive.
Ajustando o Algoritmo KNN
A implementação KNN no pacote Caret tem um parâmetro que podemos sintonizar : k, o número de instâncias mais próximas a colhetar para fazer uma previsão. Vamos tentar todos os valores de k entre 1 e 20.
trainControl <- trainControl(method="repeatedcv", number=10, repeats=3)
metric <- "Accuracy"
set.seed(7)
grid <- expand.grid(.k=seq(1,20,by=1))
modelfit.knn <- train(Class~., data=training, method="knn", metric=metric, tuneGrid=grid,
preProc=c("BoxCox"), trControl=trainControl, na.action=na.omit)
print(modelfit.knn)
## k-Nearest Neighbors
##
## 560 samples
## 9 predictor
## 2 classes: 'benign', 'malignant'
##
## Pre-processing: Box-Cox transformation (9)
## Resampling: Cross-Validated (10 fold, repeated 3 times)
## Summary of sample sizes: 492, 493, 492, 492, 493, 491, ...
## Resampling results across tuning parameters:
##
## k Accuracy Kappa
## 1 0.9500325 0.8894817
## 2 0.9531069 0.8967630
## 3 0.9628495 0.9188475
## 4 0.9603363 0.9128179
## 5 0.9688997 0.9323375
## 6 0.9695166 0.9339598
## 7 0.9719633 0.9391947
## 8 0.9719525 0.9391787
## 9 0.9713572 0.9378967
## 10 0.9701451 0.9351851
## 11 0.9713244 0.9378279
## 12 0.9713464 0.9378517
## 13 0.9719525 0.9392355
## 14 0.9725585 0.9405913
## 15 0.9719525 0.9392355
## 16 0.9713464 0.9378517
## 17 0.9725585 0.9405913
## 18 0.9719412 0.9392854
## 19 0.9713352 0.9379938
## 20 0.9713352 0.9379938
##
## Accuracy was used to select the optimal model using the largest value.
## The final value used for the model was k = 17.
Plotando e Avaliando o Ajuste no algoritmo KNN
plot(modelfit.knn)
Podemos perceber que embora tenhamos feito este ajuste, a melhora do percentual da Acurácia não foi tão significativo, ou seja, a diferença foi somente um pouco melhor (0.9725585) 97,25 % com parâmetro K = 17 contra à anterior que foi de 97,19 % com o uso de Box-cox. Podemos perceber também que este percentual obtido agora (97,25) é muito semelhante ao obtido pelo algoritmo SVM ajustado(Tuning) que foi de 97,31.
Diante disso vamos tentar melhorar esse percentual com outros algoritmos, para isso podemos utilizar Métodos Ensembles.
Utilizando Métodos Ensembles
Vamos tentar melhorar utilizando Métodos Ensembles utilizando 4 algoritmos que se enquadram nesses étodos. São eles :
Bagging:
Bagged CART (BAG)
Random Forest (RF)
Boosting:
Stochastic Gradient Boosting (GBM)
C5.0 (C50).
trainControl <- trainControl(method="repeatedcv", number=10, repeats=3)
metric <- "Accuracy"
# Bagged CART (BAG)
set.seed(7)
modelfit.treebag <- train(Class~., data=training, method="treebag", metric=metric,
trControl=trainControl, na.action=na.omit)
# Random Forest (RF)
set.seed(7)
modelfit.rf <- train(Class~., data=training, method="rf", metric=metric, preProc=c("BoxCox"),
trControl=trainControl, na.action=na.omit)
# Stochastic Gradient Boosting (GBM)
set.seed(7)
modelfit.gbm <- train(Class~., data=training, method="gbm", metric=metric, preProc=c("BoxCox"),
trControl=trainControl, verbose=FALSE, na.action=na.omit)
# C5.0 (C50)
set.seed(7)
modelfit.c50 <- train(Class~., data=training, method="C5.0", metric=metric, preProc=c("BoxCox"),
trControl=trainControl, na.action=na.omit)
# obtendo o resultado
ensembleResults <- resamples(list(BAG=modelfit.treebag, RF=modelfit.rf, GBM=modelfit.gbm, C50=modelfit.c50))
Tabela de Métricas dos Algoritmos Métodos ensemble
A tabela abaixo exibe um algoritmo para cada linha e métricas de avaliação para cada coluna.
summary(ensembleResults)
##
## Call:
## summary.resamples(object = ensembleResults)
##
## Models: BAG, RF, GBM, C50
## Number of resamples: 30
##
## Accuracy
## Min. 1st Qu. Median Mean 3rd Qu. Max. NA's
## BAG 0.8727273 0.9454545 0.9632997 0.9622439 0.9818182 1 0
## RF 0.9259259 0.9629630 0.9725589 0.9713015 0.9818182 1 0
## GBM 0.8727273 0.9498316 0.9636364 0.9622439 0.9818182 1 0
## C50 0.8909091 0.9498316 0.9636364 0.9640284 0.9818182 1 0
##
## Kappa
## Min. 1st Qu. Median Mean 3rd Qu. Max. NA's
## BAG 0.7286822 0.8808664 0.9207048 0.9173235 0.9597332 1 0
## RF 0.8414097 0.9207048 0.9402224 0.9377315 0.9602888 1 0
## GBM 0.7076689 0.8900351 0.9207048 0.9162823 0.9601869 1 0
## C50 0.7646220 0.8903491 0.9207048 0.9214000 0.9602888 1 0
Plotando e Comparando Algoritmos Métodos Ensemble
```r
dotplot(ensembleResults)
```
<!-- -->
Vemos que o algoritmo Random Forest (RF) foi o mais precisa, com uma pontuação de 97,13%, muito semelhante aos nossos modelos ajustados acima. Poderíamos gastar tempo ajustando os parâmetros do Random Forest (por exemplo, aumentando o número de árvores) e os outros Métodos Ensemble, mas talvez não consigamos melhorar muito o que já conseguimos até agora. Embora não tenhamos conseguido uma precisão acima de 97,31 (SVM) e 97,25 (KNN), mas conseguimos ficar um pouco acima dos melhores resultados que estão entre 96% e 97% de precisão.
Agora precisamos escolher qual modelo gostaríamos de usar. Eu particularmente escolho o KNN simplesmente pelo fato de que o KNN não possui outro modelo além de armazenar o conjunto de dados inteiro, portanto, não é necessário aprender. Ou seja o KNN faz previsões usando o conjunto de dados de treinamento diretamente. As previsões são feitas para um novo ponto de dados, pesquisando todo o conjunto de treinamento para as k instâncias mais semelhantes (os vizinhos) e resumindo a variável de saída para essas k instâncias.
O SVM seria uma boa opção para compensar a complexidade do espaço e do tempo. Eu provavelmente não selecionaria o algoritmo Random Forest, dada a complexidade do modelo. Parece um exagero para este conjunto de dados, muitas árvores com pouco benefício na Precisão.
Vamos finalizar o algoritmo KNN. Isso é muito simples, já que não precisamos armazenar um modelo. Nós precisamos capturar os parâmetros da transformação Box-Cox. E também precisamos preparar os dados removendo o atributo Id não utilizado e convertendo todas as entradas para o formato numérico. A implementação do KNN (knn3 ()) pertence ao pacote caret e não suporta valores omissos. Teremos que remover as linhas com valores faltantes do conjunto de dados de treinamento, bem como o conjunto de dados de validação. O código abaixo mostra a preparação dos parâmetros de pré-processamento usando o conjunto de dados de treinamento.
set.seed(7)
trainingNoMissing <- training[complete.cases(training),]
x <- trainingNoMissing[,1:9]
preprocessParams <- preProcess(x, method=c("BoxCox"))
x <- predict(preprocessParams, x)
Agora precisamos preparar o conjunto de dados de Validação para fazer uma previsão, mas inicialmente também teremos que :
1 - Remover o atributo Id.
2 - Remover todas as linhas com dados faltantes.
3 - Converter todos os atributos de entrada para numéricos.
4 - Aplique a transformação Box-Cox aos atributos de entrada usando parâmetros preparados no conjunto de dados de treinamento.
set.seed(7)
# remove atributo Id
validation <- validation[,-1]
# remove valores faltantes
validation <- validation[complete.cases(validation),]
# converte todos os atributos para numerico
for(i in 1:9) {
validation[,i] <- as.numeric(as.character(validation[,i]))
}
# dimensão do dataset validação
dim(validation)
## [1] 136 10
sapply(validation, class)
## Cl.thickness Cell.size Cell.shape Marg.adhesion
## "numeric" "numeric" "numeric" "numeric"
## Epith.c.size Bare.nuclei Bl.cromatin Normal.nucleoli
## "numeric" "numeric" "numeric" "numeric"
## Mitoses Class
## "numeric" "factor"
sapply(validation, function(x) sum(is.na(x)))
## Cl.thickness Cell.size Cell.shape Marg.adhesion
## 0 0 0 0
## Epith.c.size Bare.nuclei Bl.cromatin Normal.nucleoli
## 0 0 0 0
## Mitoses Class
## 0 0
# transform o dataset de validação
validationX <- predict(preprocessParams, validation[,1:9])
Agora estamos prontos para fazer uma previsão no conjunto de dados de treinamento.
set.seed(7)
predictions <- knn3Train(x, validationX, trainingNoMissing$Class, k=9, prob=FALSE)
u <-union(predictions, validation$Class)
t <- table(factor(predictions, u), factor(validation$Class, u))
confusionMatrix(t)
## Confusion Matrix and Statistics
##
##
## benign malignant
## benign 87 0
## malignant 1 48
##
## Accuracy : 0.9926
## 95% CI : (0.9597, 0.9998)
## No Information Rate : 0.6471
## P-Value [Acc > NIR] : <2e-16
##
## Kappa : 0.984
##
## Mcnemar's Test P-Value : 1
##
## Sensitivity : 0.9886
## Specificity : 1.0000
## Pos Pred Value : 1.0000
## Neg Pred Value : 0.9796
## Prevalence : 0.6471
## Detection Rate : 0.6397
## Detection Prevalence : 0.6397
## Balanced Accuracy : 0.9943
##
## 'Positive' Class : benign
##
Conclusão
Nesse trabalho conseguimos atuar em cima de um problema de classificação binária que busca prever o tipo de câncer a partir dos detalhes das amostras de tecido. Para chegarmos a esse objetivo seguimos os seguintes passos:
Exploração de Dados - Fizemos analise exploratória dos dados através de gráficos diversos (observamos as formas exponencial e bimodal de muitos dos atributos).
Algoritmos avaliados - Observamos que devido a sua precisão o algoritmo KNN foi o mais indicado para o problema.
Algoritmos Avaliados com Transform - observamos uma melhor precisão com uma transformação Box-Cox e notamos que o SVM apresentou boa precisão com essa técnica.
Algoritmos ajustados com parâmetros- Notamos pequenas melhorias para o SVM e KNN, mas nada muito significativo.
Métodos Ensemble - Com esses métodos chegamos ao limite da precisão na previsão.
Podemos ver que a precisão do modelo final no conjunto de dados de validação é de 99,26%. Isso é otimista porque há apenas 136 linhas, mas mostra que temos um modelo autônomo preciso que poderíamos usar em outros dados não classificados.