bc$Id <- NULL
bc <- na.omit(bc)
bc[, 1:9] <- lapply(bc[, 1:9], function(x) as.numeric(as.character(x)))
str(bc)
## 'data.frame': 683 obs. of 10 variables:
## $ Cl.thickness : num 5 5 3 6 4 8 1 2 2 4 ...
## $ Cell.size : num 1 4 1 8 1 10 1 1 1 2 ...
## $ Cell.shape : num 1 4 1 8 1 10 1 2 1 1 ...
## $ Marg.adhesion : num 1 5 1 1 3 8 1 1 1 1 ...
## $ Epith.c.size : num 2 7 2 3 2 7 2 2 2 2 ...
## $ Bare.nuclei : num 1 10 2 4 1 10 10 1 1 1 ...
## $ Bl.cromatin : num 3 3 3 3 3 9 3 3 1 2 ...
## $ Normal.nucleoli: num 1 2 1 7 1 7 1 1 1 1 ...
## $ Mitoses : num 1 1 1 1 1 1 1 1 5 1 ...
## $ Class : Factor w/ 2 levels "benign","malignant": 1 1 1 1 1 2 1 1 1 1 ...
## - attr(*, "na.action")= 'omit' Named int [1:16] 24 41 140 146 159 165 236 250 276 293 ...
## ..- attr(*, "names")= chr [1:16] "24" "41" "140" "146" ...
table(bc$Class)
##
## benign malignant
## 444 239
prop.table(table(bc$Class)) %>% round(3)
##
## benign malignant
## 0.65 0.35
train_indices <- createDataPartition(bc$Class, times=1, p=0.7, list=FALSE)
bc_train <- bc[train_indices, ]
bc_test <- bc[-train_indices, ]
prop.table(table(bc_train$Class)) %>% round(3)
##
## benign malignant
## 0.649 0.351
prop.table(table(bc_test$Class)) %>% round(3)
##
## benign malignant
## 0.652 0.348
Από τα παραπάνω φαίνεται πως ο χωρισμός έγινε σωστά και η ισορροποία των κλάσεων έχει διατηρηθεί.
set.seed(42)
rf_model <-randomForest(Class ~ .,data = bc_train, ntree = 500, importance = TRUE)
print(rf_model)
##
## Call:
## randomForest(formula = Class ~ ., data = bc_train, ntree = 500, importance = TRUE)
## Type of random forest: classification
## Number of trees: 500
## No. of variables tried at each split: 3
##
## OOB estimate of error rate: 3.76%
## Confusion matrix:
## benign malignant class.error
## benign 301 10 0.03215434
## malignant 8 160 0.04761905
Παρατηρείται μικρό OOB Error (3,55%) πράγμα που σημαίνει πως για το train σετ έχουμε περίπου 96,45% ακρίβεια (accuracy) μοντέλου. Επίσης η ασυμμετρία μεταξύ των κλάσεων είναι μικρή και το ίδιο το νούμερο είναι πολύ κοντά και για τις δύο κλάσεις στην συνολική απόκλιση του μοντέλου. Δεν υποφέρει συνεπώς από ζητήματα στην απόδοση μεταξύ των δύο κλάσεων.
rf_pred <- predict(rf_model, bc_test)
rf_prob <- predict(rf_model, bc_test, type = "prob")[, "malignant"]
# Confusion Matrix
rf_cm <- confusionMatrix(rf_pred, bc_test$Class, positive = "malignant")
print(rf_cm)
## Confusion Matrix and Statistics
##
## Reference
## Prediction benign malignant
## benign 131 2
## malignant 2 69
##
## Accuracy : 0.9804
## 95% CI : (0.9506, 0.9946)
## No Information Rate : 0.652
## P-Value [Acc > NIR] : <2e-16
##
## Kappa : 0.9568
##
## Mcnemar's Test P-Value : 1
##
## Sensitivity : 0.9718
## Specificity : 0.9850
## Pos Pred Value : 0.9718
## Neg Pred Value : 0.9850
## Prevalence : 0.3480
## Detection Rate : 0.3382
## Detection Prevalence : 0.3480
## Balanced Accuracy : 0.9784
##
## 'Positive' Class : malignant
##
# AUC
rf_auc <- roc(bc_test$Class, rf_prob, levels = c("benign", "malignant"))$auc
## Setting direction: controls < cases
cat("RF AUC:", round(rf_auc, 3), "\n")
## RF AUC: 0.999
To μοντέλο παρουσιάζει μεγάλα σκορ στα Sensitivity, Specificity, Accuracy και AUC, καθώς όλα είναι μεγαλύτερα του 97%. Αυτό είναι πολύ καλό για το test set και δείχνει πως δεν υπήρξε υπερπροσαρμογή. Για το πεδίο της ιατρικής το επίπεδο ακρίβειας σημαίνει πως 3% θα διαγνωστούν λανθασμένα, ωστόσο δεδομένου πως το error μεταξύ των κλάσεων είναι σχεδόν ίδιο, αυτό σημαίνει πως η περίπτωση να διαγνωστεί λανθασμένα (3%) έχει οριακά 50/50 περίπτωση να είναι είτε benign είτε malignant. Συνεπώς παρόρτι όχι το ιδανικό, δεν είναι προκατηλημένο το μοντέλο κατά την διάγνωση.
varImpPlot(rf_model, main = "Variable Importance — Random Forest")
importance(rf_model)
## benign malignant MeanDecreaseAccuracy MeanDecreaseGini
## Cl.thickness 12.702656 19.830096 19.363837 11.845927
## Cell.size 11.605774 16.330563 19.826188 60.344346
## Cell.shape 10.417048 20.502944 21.800283 53.561147
## Marg.adhesion 6.228395 12.425305 13.009741 5.977215
## Epith.c.size 10.510480 3.579421 11.128863 11.709247
## Bare.nuclei 20.271034 19.036320 26.035566 34.154820
## Bl.cromatin 6.086543 14.621620 14.691182 14.587449
## Normal.nucleoli 11.849778 11.676750 15.229482 23.815034
## Mitoses 4.984157 0.839361 4.608274 1.550928
Τα τρια πιο σημαντικά χαρακτηριστικά εδώ είναι τα Bare.nuclei, Cell.shape & Cell.size, καθώς είναι τα τρια με τις δύο υψηλότερες τιμές και στα δύο διαγράμματα.
train_x <- as.matrix(bc_train[, -10])
train_y <- ifelse(bc_train$Class == "malignant", 1, 0)
test_x <- as.matrix(bc_test[, -10])
test_y <- ifelse(bc_test$Class == "malignant", 1, 0)
dtrain <- xgb.DMatrix(data = train_x, label = train_y)
dtest <- xgb.DMatrix(data = test_x, label = test_y)
params <- list(
objective = "binary:logistic",
eval_metric = "auc",
max_depth = 4,
eta = 0.1, # learning rate
subsample = 0.8,
colsample_bytree = 0.8
)
xgb_model <- xgb.train(
params = params,
data = dtrain,
nrounds = 500,
watchlist = list(train = dtrain, test = dtest),
early_stopping_rounds = 20,
print_every_n = 25,
verbose = 1
)
## Warning in throw_err_or_depr_msg("Parameter '", match_old, "' has been renamed
## to '", : Parameter 'watchlist' has been renamed to 'evals'. This warning will
## become an error in a future version.
## Multiple eval metrics are present. Will use test_auc for early stopping.
## Will train until test_auc hasn't improved in 20 rounds.
##
## [1] train-auc:0.982564 test-auc:0.979932
## [26] train-auc:0.997722 test-auc:0.996717
## Stopping. Best iteration:
## [29] train-auc:0.998258 test-auc:0.996717
##
## [29] train-auc:0.998258 test-auc:0.996717
Βρήκαμε άρα καλύτερο iteration στο 29 και σταματάμε εδώ.
xgb_prob <- predict(xgb_model, dtest)
xgb_pred <- factor(ifelse(xgb_prob > 0.5, "malignant", "benign"),
levels = c("benign", "malignant"))
xgb_cm <- confusionMatrix(xgb_pred, bc_test$Class, positive = "malignant")
xgb_auc <- roc(bc_test$Class, xgb_prob, levels = c("benign", "malignant"))$auc
## Setting direction: controls < cases
results <- data.frame(
Model = c("Random Forest", "XGBoost"),
Accuracy = c(rf_cm$overall["Accuracy"],
xgb_cm$overall["Accuracy"]),
Sensitivity = c(rf_cm$byClass["Sensitivity"],
xgb_cm$byClass["Sensitivity"]),
Specificity = c(rf_cm$byClass["Specificity"],
xgb_cm$byClass["Specificity"]),
F1 = c(rf_cm$byClass["F1"],
xgb_cm$byClass["F1"]),
AUC = c(rf_auc, xgb_auc)
) %>% mutate(across(where(is.numeric), ~ round(.x, 3)))
print(results)
## Model Accuracy Sensitivity Specificity F1 AUC
## 1 Random Forest 0.980 0.972 0.985 0.972 0.999
## 2 XGBoost 0.966 0.930 0.985 0.950 0.998
Εδώ βλέπουμε πως σχεδόν σε όλες τις μετρικές το Random Forest εχει οριακά καλύτερη απόδοση από το XGBoost. Δεδομένου πως το XGBoost είναι σχεδιασμένο για την βελτιστοποιήση των αποτελεσμάτων, τα αποτελέσματα κρίνονται μη αναμενόμενα.
roc_rf <- roc(bc_test$Class, rf_prob, levels = c("benign", "malignant"))
## Setting direction: controls < cases
roc_xgb <- roc(bc_test$Class, xgb_prob, levels = c("benign", "malignant"))
## Setting direction: controls < cases
plot(roc_rf, col = "blue", lwd = 3, main = "ROC Curve Comparison")
lines(roc_xgb, col = "red", lwd = 3)
legend("bottomright",
legend = c(paste0("Random Forest (AUC = ", round(auc(roc_rf), 3), ")"),
paste0("XGBoost (AUC = ", round(auc(roc_xgb), 3), ")")),
col = c("blue", "red"), lwd = 3)
params <- list(
objective = "binary:logistic",
eval_metric = "auc",
max_depth = 4,
eta = 0.01, # learning rate
subsample = 0.8,
colsample_bytree = 0.8
)
xgb_model_0.01 <- xgb.train(
params = params,
data = dtrain,
nrounds = 500,
watchlist = list(train = dtrain, test = dtest),
early_stopping_rounds = 20,
print_every_n = 25,
verbose = 1
)
## Warning in throw_err_or_depr_msg("Parameter '", match_old, "' has been renamed
## to '", : Parameter 'watchlist' has been renamed to 'evals'. This warning will
## become an error in a future version.
## Multiple eval metrics are present. Will use test_auc for early stopping.
## Will train until test_auc hasn't improved in 20 rounds.
##
## [1] train-auc:0.983301 test-auc:0.966907
## [26] train-auc:0.993110 test-auc:0.996082
## [51] train-auc:0.994249 test-auc:0.995976
## Stopping. Best iteration:
## [53] train-auc:0.994287 test-auc:0.995976
##
## [53] train-auc:0.994287 test-auc:0.995976
params <- list(
objective = "binary:logistic",
eval_metric = "auc",
max_depth = 4,
eta = 0.3, # learning rate
subsample = 0.8,
colsample_bytree = 0.8
)
xgb_model_0.3 <- xgb.train(
params = params,
data = dtrain,
nrounds = 500,
watchlist = list(train = dtrain, test = dtest),
early_stopping_rounds = 20,
print_every_n = 25,
verbose = 1
)
## Warning in throw_err_or_depr_msg("Parameter '", match_old, "' has been renamed
## to '", : Parameter 'watchlist' has been renamed to 'evals'. This warning will
## become an error in a future version.
## Multiple eval metrics are present. Will use test_auc for early stopping.
## Will train until test_auc hasn't improved in 20 rounds.
##
## [1] train-auc:0.974765 test-auc:0.966907
## [26] train-auc:0.999847 test-auc:0.995976
## Stopping. Best iteration:
## [29] train-auc:0.999809 test-auc:0.995764
##
## [29] train-auc:0.999809 test-auc:0.995764
Παρατηρείται πως με την μειώση του eta από 0,1 σε 0,01 έχουμε μεγάλη αύξηση στον αριθμό επαναλήψεων από 29 σε 39, ενώ αν αυξήσουμε το eta εχουμε επίσης αύξηση των επαναλήψεων αλλά σε μικρότερο βαθμό. Από τα δύο φαίνεται πως η απόδοση (AUC) είναι βέλτιστη στο 0,3. Είναι αναμενόμενο καθώς όσο μειώνουμε το eta τόσο συνήθως αυξάνεται ο αριθμός των βημάτων/ επαναλήψεων.