Bu projede Spaceship Titanic veri seti üzerinde çalışıyorum. Amaç, uzay gemisindeki yolcuların alternatif bir boyuta nakledilip nakledilmediğini tahmin etmek.
Bu analiz için tidymodels ekosistemine ait araçları kullanarak üç farklı makine öğrenmesi modeli kuracağım. Bunlar lojistik regresyon, Random Forest ve XGBoost’tur.
Proje akışı şu şekildedir: Train verisini %75 (deneme_train) ve %25 (deneme_test) olarak ayırıp modelleri değerlendireceğim. En iyi modeli seçtikten sonra tüm train verisiyle yeniden eğitip Kaggle’a submission yapacağım. Tüm veri ön işleme adımları tidymodels recipe ile yapılacak.
library(tidyverse)
library(tidymodels)
library(xgboost)
library(knitr)
library(ranger)
train_raw <- read_csv("train.csv", show_col_types = FALSE)
test_raw <- read_csv("test.csv", show_col_types = FALSE)
Veri setinin yapısına bakalım:
glimpse(train_raw)
## Rows: 8,693
## Columns: 14
## $ PassengerId <chr> "0001_01", "0002_01", "0003_01", "0003_02", "0004_01", "0…
## $ HomePlanet <chr> "Europa", "Earth", "Europa", "Europa", "Earth", "Earth", …
## $ CryoSleep <lgl> FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, TRUE, FA…
## $ Cabin <chr> "B/0/P", "F/0/S", "A/0/S", "A/0/S", "F/1/S", "F/0/P", "F/…
## $ Destination <chr> "TRAPPIST-1e", "TRAPPIST-1e", "TRAPPIST-1e", "TRAPPIST-1e…
## $ Age <dbl> 39, 24, 58, 33, 16, 44, 26, 28, 35, 14, 34, 45, 32, 48, 2…
## $ VIP <lgl> FALSE, FALSE, TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, FA…
## $ RoomService <dbl> 0, 109, 43, 0, 303, 0, 42, 0, 0, 0, 0, 39, 73, 719, 8, 32…
## $ FoodCourt <dbl> 0, 9, 3576, 1283, 70, 483, 1539, 0, 785, 0, 0, 7295, 0, 1…
## $ ShoppingMall <dbl> 0, 25, 0, 371, 151, 0, 3, 0, 17, 0, NA, 589, 1123, 65, 12…
## $ Spa <dbl> 0, 549, 6715, 3329, 565, 291, 0, 0, 216, 0, 0, 110, 0, 0,…
## $ VRDeck <dbl> 0, 44, 49, 193, 2, 0, 0, NA, 0, 0, 0, 124, 113, 24, 7, 0,…
## $ Name <chr> "Maham Ofracculy", "Juanna Vines", "Altark Susent", "Sola…
## $ Transported <lgl> FALSE, TRUE, FALSE, FALSE, TRUE, TRUE, TRUE, TRUE, TRUE, …
glimpse(test_raw)
## Rows: 4,277
## Columns: 13
## $ PassengerId <chr> "0013_01", "0018_01", "0019_01", "0021_01", "0023_01", "0…
## $ HomePlanet <chr> "Earth", "Earth", "Europa", "Europa", "Earth", "Earth", "…
## $ CryoSleep <lgl> TRUE, FALSE, TRUE, FALSE, FALSE, FALSE, TRUE, TRUE, TRUE,…
## $ Cabin <chr> "G/3/S", "F/4/S", "C/0/S", "C/1/S", "F/5/S", "F/7/P", "B/…
## $ Destination <chr> "TRAPPIST-1e", "TRAPPIST-1e", "55 Cancri e", "TRAPPIST-1e…
## $ Age <dbl> 27, 19, 31, 38, 20, 31, 21, 20, 23, 24, 19, 45, 44, 46, 2…
## $ VIP <lgl> FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, F…
## $ RoomService <dbl> 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 339, 932, 0, 0, 0, 0, 2, 0…
## $ FoodCourt <dbl> 0, 9, 0, 6652, 0, 1615, NA, 0, 0, 639, 3, 74, 1561, 0, 0,…
## $ ShoppingMall <dbl> 0, 0, 0, 0, 635, 263, 0, 0, 0, 0, 136, NA, 0, 0, 0, 0, 25…
## $ Spa <dbl> 0, 2823, 0, 181, 0, 113, 0, 0, 0, 0, 237, 7, 14, 0, 1687,…
## $ VRDeck <dbl> 0, 0, 0, 585, 0, 60, 0, 0, 0, 0, 0, 1010, 224, 0, 92, NA,…
## $ Name <chr> "Nelly Carsoning", "Lerome Peckers", "Sabih Unhearfus", "…
Train veri seti 8,693 gözlem ve 14 değişken içermektedir. Test veri seti ise 4,277 gözlem ve 13 değişken içermektedir (Transported yok). Hedef değişken Transported’dır (True/False - yolcu nakledildi mi?).
Değişken Açıklamaları:
| Değişken | Açıklama | Tür |
|---|---|---|
| PassengerId | Yolcu kimlik numarası | Karakter |
| HomePlanet | Kalkış gezegeni (Europa, Earth, Mars) | Kategorik |
| CryoSleep | Dondurulmuş uyku modunda mı? | Boolean |
| Cabin | Kabin numarası (Deck/Num/Side) | Karakter |
| Destination | Varış noktası | Kategorik |
| Age | Yaş | Sayısal |
| VIP | VIP servisi var mı? | Boolean |
| RoomService | Oda servisi harcaması | Sayısal |
| FoodCourt | Yemek alanı harcaması | Sayısal |
| ShoppingMall | Alışveriş harcaması | Sayısal |
| Spa | Spa harcaması | Sayısal |
| VRDeck | VR harcaması | Sayısal |
| Name | Yolcu adı | Karakter |
| Transported | Nakledildi mi? (Hedef) | Boolean |
train_na <- tibble(
Degisken = names(train_raw),
Eksik_Deger = colSums(is.na(train_raw)),
Eksik_Oran = round(colSums(is.na(train_raw)) / nrow(train_raw) * 100, 2)
) %>%
filter(Eksik_Deger > 0) %>%
arrange(desc(Eksik_Deger))
kable(train_na, caption = "Train Verisinde Eksik Değerler")
| Degisken | Eksik_Deger | Eksik_Oran |
|---|---|---|
| CryoSleep | 217 | 2.50 |
| ShoppingMall | 208 | 2.39 |
| VIP | 203 | 2.34 |
| HomePlanet | 201 | 2.31 |
| Name | 200 | 2.30 |
| Cabin | 199 | 2.29 |
| VRDeck | 188 | 2.16 |
| FoodCourt | 183 | 2.11 |
| Spa | 183 | 2.11 |
| Destination | 182 | 2.09 |
| RoomService | 181 | 2.08 |
| Age | 179 | 2.06 |
Görüldüğü gibi birçok değişkende eksik değer var. Bu eksik değerleri tidymodels recipe ile dolduracağız.
Cabin değişkenini ayırıyoruz (recipe’de step_mutate yok, bu yüzden önceden):
# Hedef değişkeni faktöre çevir
train_raw <- train_raw %>%
mutate(Transported = factor(ifelse(Transported, "Yes", "No"),
levels = c("No", "Yes")))
# Cabin'i ayır (Deck/Num/Side)
train_raw <- train_raw %>%
separate(Cabin, into = c("Deck", "CabinNum", "Side"),
sep = "/", remove = FALSE, fill = "right") %>%
mutate(
Deck = ifelse(Deck %in% c("A", "D", "T"), "Other", Deck),
Deck = as.factor(Deck),
CabinNum = as.numeric(CabinNum),
Side = as.factor(Side),
# Diğer kategorik değişkenler de factor olsun
HomePlanet = as.factor(HomePlanet),
CryoSleep = as.factor(CryoSleep),
Destination = as.factor(Destination),
VIP = as.factor(VIP)
)
test_raw <- test_raw %>%
separate(Cabin, into = c("Deck", "CabinNum", "Side"),
sep = "/", remove = FALSE, fill = "right") %>%
mutate(
Deck = ifelse(Deck %in% c("A", "D", "T"), "Other", Deck),
Deck = as.factor(Deck),
CabinNum = as.numeric(CabinNum),
Side = as.factor(Side),
HomePlanet = as.factor(HomePlanet),
CryoSleep = as.factor(CryoSleep),
Destination = as.factor(Destination),
VIP = as.factor(VIP)
)
set.seed(123)
data_split <- initial_split(train_raw, prop = 0.75, strata = Transported)
deneme_train <- training(data_split)
deneme_test <- testing(data_split)
tibble(
Veri_Seti = c("Deneme Train", "Deneme Test"),
Satir_Sayisi = c(nrow(deneme_train), nrow(deneme_test)),
Oran = c("75%", "25%")
) %>% kable(caption = "Train-Test Ayrımı")
| Veri_Seti | Satir_Sayisi | Oran |
|---|---|---|
| Deneme Train | 6519 | 75% |
| Deneme Test | 2174 | 25% |
Stratifikasyon: Transported değişkenine göre stratified
sampling yapıldı, böylece her iki sette de sınıf dağılımı korundu.
Tüm veri ön işleme adımlarını tidymodels recipe ile yapıyoruz:
model_rec <- recipe(Transported ~ ., data = deneme_train) %>%
# Gereksiz kolonları çıkar
step_rm(PassengerId, Name, Cabin) %>%
# Character kolonları factor'e çevir (XGBoost için kritik!)
step_string2factor(all_nominal_predictors()) %>%
# Eksik değerleri doldur
step_impute_mode(all_nominal_predictors()) %>%
step_impute_median(all_numeric_predictors()) %>%
# Kategorik değişkenleri dummy'e çevir
step_dummy(all_nominal_predictors()) %>%
# Sıfır varyanslı kolonları çıkar (dummy sonrası)
step_zv(all_predictors())
# Recipe'yi hazırla ve veriyi dönüştür
prep_rec <- prep(model_rec)
train_baked <- bake(prep_rec, new_data = NULL)
test_baked <- bake(prep_rec, new_data = deneme_test)
tibble(
Veri_Seti = c("Deneme Train (recipe sonrası)", "Deneme Test (recipe sonrası)"),
Toplam_NA = c(sum(is.na(train_baked)), sum(is.na(test_baked))),
Satir_Sayisi = c(nrow(train_baked), nrow(test_baked)),
Sutun_Sayisi = c(ncol(train_baked), ncol(test_baked))
) %>% kable(caption = "Recipe Sonrası Veri Durumu")
| Veri_Seti | Toplam_NA | Satir_Sayisi | Sutun_Sayisi |
|---|---|---|---|
| Deneme Train (recipe sonrası) | 0 | 6519 | 20 |
| Deneme Test (recipe sonrası) | 0 | 2174 | 20 |
Recipe sonrası hiçbir eksik değer kalmadı.
Bu bölümde üç farklı model kuracağım ve her birinin teorik temelini açıklayacağım.
Lojistik regresyon, binary sınıflandırma problemleri için kullanılan parametrik bir istatistiksel yöntemdir. Doğrusal regresyonun bir uzantısıdır ancak çıktıyı [0,1] aralığına sıkıştırmak için lojistik (sigmoid) fonksiyonu kullanır:
\[P(Y=1|X) = \frac{1}{1 + e^{-(\beta_0 + \beta_1X_1 + ... + \beta_pX_p)}}\]
Lojistik regresyon yorumlanabilir bir yöntemdir ve katsayılar odds ratio’ya çevrilebilir. Hızlı eğitim sağlar ve düşük varyans nedeniyle overfitting riski azdır. Baseline model olarak kullanılması uygundur. Ancak doğrusal olmayan ilişkileri yakalayamaz ve feature engineering gerektirir. Kompleks etkileşimleri modellemekte de yetersizdir.
logistic_spec <- logistic_reg() %>%
set_engine("glm") %>%
set_mode("classification")
logistic_wf <- workflow() %>%
add_recipe(model_rec) %>%
add_model(logistic_spec)
logistic_fit <- fit(logistic_wf, deneme_train)
Random Forest, birden fazla karar ağacının tahminlerini birleştiren bir ensemble (topluluk öğrenmesi) yöntemidir. Her ağaç bootstrap sampling ile rastgele seçilmiş örnekler üzerinde eğitilir. Her düğümde rastgele seçilmiş özellikler arasından en iyisi kullanılır ve final tahmin tüm ağaçların oylamasıyla yapılır (classification için majority voting).
Random Forest overfitting’e karşı dayanıklıdır (bagging sayesinde) ve non-linear ilişkileri yakalayabilir. Feature importance sağlar ve hiperparametre ayarına az duyarlıdır. Ancak yorumlanabilirliği düşüktür, eğitim süresi uzun olabilir ve büyük veri setlerinde memory-intensive olabilir.
Modelde mtry = 5 (her split’te rastgele seçilen değişken sayısı), trees = 500 (toplam ağaç sayısı) ve min_n = 10 (terminal düğümdeki minimum gözlem sayısı) hiperparametreleri kullanılmıştır.
rf_spec <- rand_forest(
mtry = 5,
trees = 500,
min_n = 10
) %>%
set_engine("ranger") %>%
set_mode("classification")
rf_wf <- workflow() %>%
add_recipe(model_rec) %>%
add_model(rf_spec)
rf_fit <- fit(rf_wf, deneme_train)
XGBoost (Extreme Gradient Boosting), gradient boosting algoritmasının optimize edilmiş bir implementasyonudur. Random Forest’tan farklı olarak ağaçlar sıralı olarak eklenir (parallel değil). Her yeni ağaç, önceki ağaçların hatalarını düzeltmeye odaklanır. Gradient descent ile loss fonksiyonu minimize edilir ve regularization (L1/L2) ile overfitting önlenir.
XGBoost Kaggle yarışmalarında en başarılı algoritmalardan biridir. Yüksek prediction accuracy sağlar ve missing value handling built-in özelliğine sahiptir. Regularization ile overfitting kontrolü yapar. Ancak hiperparametre tuning zahmetlidir, sequential training nedeniyle paralelize edilemez ve yorumlanabilirliği düşüktür.
Modelde trees = 500 (boosting iterasyon sayısı), tree_depth = 6 (maksimum ağaç derinliği), min_n = 10 (split için minimum gözlem) ve learn_rate = 0.05 (learning rate/shrinkage) hiperparametreleri kullanılmıştır.
xgb_spec <- boost_tree(
trees = 500,
tree_depth = 6,
min_n = 10,
learn_rate = 0.05
) %>%
set_engine("xgboost") %>%
set_mode("classification")
xgb_wf <- workflow() %>%
add_recipe(model_rec) %>%
add_model(xgb_spec)
xgb_fit <- fit(xgb_wf, deneme_train)
Şimdi üç modeli de deneme_test üzerinde
değerlendireceğim.
logistic_pred <- predict(logistic_fit, deneme_test) %>%
bind_cols(predict(logistic_fit, deneme_test, type = "prob")) %>%
bind_cols(select(deneme_test, Transported))
rf_pred <- predict(rf_fit, deneme_test) %>%
bind_cols(predict(rf_fit, deneme_test, type = "prob")) %>%
bind_cols(select(deneme_test, Transported))
xgb_pred <- predict(xgb_fit, deneme_test) %>%
bind_cols(predict(xgb_fit, deneme_test, type = "prob")) %>%
bind_cols(select(deneme_test, Transported))
cm_logistic <- conf_mat(logistic_pred, truth = Transported, estimate = .pred_class)
cm_rf <- conf_mat(rf_pred, truth = Transported, estimate = .pred_class)
cm_xgb <- conf_mat(xgb_pred, truth = Transported, estimate = .pred_class)
Lojistik Regresyon Confusion Matrix:
cm_logistic
## Truth
## Prediction No Yes
## No 841 194
## Yes 238 901
Random Forest Confusion Matrix:
cm_rf
## Truth
## Prediction No Yes
## No 856 179
## Yes 223 916
XGBoost Confusion Matrix:
cm_xgb
## Truth
## Prediction No Yes
## No 874 194
## Yes 205 901
hesapla_metrikler <- function(pred_df, model_adi) {
tibble(
Model = model_adi,
Accuracy = accuracy(pred_df, truth = Transported, estimate = .pred_class)$.estimate,
Precision = precision(pred_df, truth = Transported, estimate = .pred_class,
event_level = "second")$.estimate,
Recall = recall(pred_df, truth = Transported, estimate = .pred_class,
event_level = "second")$.estimate,
F1 = f_meas(pred_df, truth = Transported, estimate = .pred_class,
event_level = "second")$.estimate,
ROC_AUC = roc_auc(pred_df, truth = Transported, .pred_Yes,
event_level = "second")$.estimate
)
}
metrikler <- bind_rows(
hesapla_metrikler(logistic_pred, "Lojistik Regresyon"),
hesapla_metrikler(rf_pred, "Random Forest"),
hesapla_metrikler(xgb_pred, "XGBoost")
)
kable(metrikler, digits = 4, caption = "Model Performans Karşılaştırması")
| Model | Accuracy | Precision | Recall | F1 | ROC_AUC |
|---|---|---|---|---|---|
| Lojistik Regresyon | 0.8013 | 0.7910 | 0.8228 | 0.8066 | 0.8864 |
| Random Forest | 0.8151 | 0.8042 | 0.8365 | 0.8201 | 0.9049 |
| XGBoost | 0.8165 | 0.8146 | 0.8228 | 0.8187 | 0.9067 |
| Metrik | Açıklama | Formül |
|---|---|---|
| Accuracy | Doğru tahminlerin toplam gözleme oranı | (TP + TN) / (TP + TN + FP + FN) |
| Precision | Pozitif tahmin edilenlerin gerçekten pozitif olma oranı | TP / (TP + FP) |
| Recall | Gerçek pozitiflerin yakalanma oranı | TP / (TP + FN) |
| F1 | Precision ve Recall’un harmonik ortalaması | 2 × (Precision × Recall) / (Precision + Recall) |
| ROC-AUC | Sınıfları ayırma gücü (1’e yakın = daha iyi) | ROC eğrisi altındaki alan |
roc_logistic <- roc_curve(logistic_pred, truth = Transported, .pred_Yes,
event_level = "second") %>%
mutate(Model = "Lojistik Regresyon")
roc_rf <- roc_curve(rf_pred, truth = Transported, .pred_Yes,
event_level = "second") %>%
mutate(Model = "Random Forest")
roc_xgb <- roc_curve(xgb_pred, truth = Transported, .pred_Yes,
event_level = "second") %>%
mutate(Model = "XGBoost")
roc_all <- bind_rows(roc_logistic, roc_rf, roc_xgb)
ggplot(roc_all, aes(x = 1 - specificity, y = sensitivity, color = Model)) +
geom_line(linewidth = 1.2) +
geom_abline(linetype = "dashed", color = "gray50") +
labs(
title = "ROC Eğrileri Karşılaştırması",
subtitle = "Yüksek AUC değeri daha iyi model performansını gösterir",
x = "1 - Specificity (False Positive Rate)",
y = "Sensitivity (True Positive Rate)"
) +
theme_minimal() +
theme(
legend.position = "bottom",
plot.title = element_text(size = 14, face = "bold"),
plot.subtitle = element_text(size = 11)
) +
scale_color_brewer(palette = "Set1")
metrikler_long <- metrikler %>%
pivot_longer(cols = -Model, names_to = "Metrik", values_to = "Deger")
ggplot(metrikler_long, aes(x = Metrik, y = Deger, fill = Model)) +
geom_col(position = "dodge", width = 0.7) +
geom_text(aes(label = round(Deger, 3)),
position = position_dodge(width = 0.7),
vjust = -0.5, size = 3) +
labs(
title = "Model Performans Karşılaştırması",
subtitle = "Tüm metrikler için yüksek değer daha iyidir",
y = "Değer",
x = "Metrik"
) +
theme_minimal() +
theme(
legend.position = "bottom",
plot.title = element_text(size = 14, face = "bold")
) +
scale_fill_brewer(palette = "Set2") +
ylim(0, 1.05)
Şimdi üç modeli de tüm train verisiyle yeniden eğitip gerçek test setinde tahmin yapacağım.
final_rec <- recipe(Transported ~ ., data = train_raw) %>%
step_rm(PassengerId, Name, Cabin) %>%
# Character kolonları factor'e çevir
step_string2factor(all_nominal_predictors()) %>%
step_impute_mode(all_nominal_predictors()) %>%
step_impute_median(all_numeric_predictors()) %>%
step_dummy(all_nominal_predictors()) %>%
step_zv(all_predictors())
# Lojistik Regresyon
logistic_wf_final <- workflow() %>%
add_recipe(final_rec) %>%
add_model(logistic_spec)
logistic_final <- fit(logistic_wf_final, train_raw)
# Random Forest
rf_wf_final <- workflow() %>%
add_recipe(final_rec) %>%
add_model(rf_spec)
rf_final <- fit(rf_wf_final, train_raw)
# XGBoost
xgb_wf_final <- workflow() %>%
add_recipe(final_rec) %>%
add_model(xgb_spec)
xgb_final <- fit(xgb_wf_final, train_raw)
Tüm modeller tüm train verisiyle başarıyla eğitildi.
pred_logistic_final <- predict(logistic_final, test_raw)
pred_rf_final <- predict(rf_final, test_raw)
pred_xgb_final <- predict(xgb_final, test_raw)
# Yes/No -> True/False formatına çevir
convert_pred <- function(pred) {
ifelse(pred$.pred_class == "Yes", "True", "False")
}
submission_logistic <- tibble(
PassengerId = test_raw$PassengerId,
Transported = convert_pred(pred_logistic_final)
)
submission_rf <- tibble(
PassengerId = test_raw$PassengerId,
Transported = convert_pred(pred_rf_final)
)
submission_xgb <- tibble(
PassengerId = test_raw$PassengerId,
Transported = convert_pred(pred_xgb_final)
)
# Dosyaları kaydet
write_csv(submission_logistic, "submission_logistic.csv")
write_csv(submission_rf, "submission_rf.csv")
write_csv(submission_xgb, "submission_xgb.csv")
Oluşturulan dosyalar:
submission_logistic.csvsubmission_rf.csvsubmission_xgb.csvİlk 5 tahmin (Lojistik Regresyon):
head(submission_logistic, 5)
## # A tibble: 5 × 2
## PassengerId Transported
## <chr> <chr>
## 1 0013_01 True
## 2 0018_01 False
## 3 0019_01 True
## 4 0021_01 True
## 5 0023_01 True
Aşağıda, model tahminlerinin Kaggle platformuna yüklenmesi sonucunda elde edilen skor yer almaktadır.
Bu projede Spaceship Titanic veri seti üzerinde üç farklı sınıflandırma modeli kurdum ve karşılaştırdım:
Ensemble yöntemler (Random Forest ve XGBoost) genelde lojistik regresyondan daha iyi performans gösterdi. ROC-AUC metriği açısından en yüksek skoru XGBoost elde etti. Accuracy değerleri %79-82 aralığında gerçekleşti.
Veri ön işleme açısından Cabin değişkenini Deck, CabinNum ve Side olarak ayırmak faydalı oldu. Eksik değerler recipe içinde kategorik için mode, sayısal için median ile dolduruldu. Tüm preprocessing tidymodels recipe ile yapıldı.
Değerlendirme metrikleri ile ilgili olarak confusion matrix ile modellerin hata türleri analiz edildi. ROC-AUC modellerin sınıf ayırma gücünü gösterdi ve F1 score precision-recall dengesini değerlendirdi.
Tidymodels ekosistemi model kurma ve değerlendirme süreçlerini tutarlı bir şekilde yönetmemi sağladı. Recipe pattern veri ön işleme adımlarını tekrar üretilebilir hale getirdi. Ensemble yöntemler karmaşık ilişkileri yakalamada daha başarılı oldu.