Son Sürpriz Durak: Bahçıvanın Atölyesi

Merhaba Dostlar,

Yolculuğumuzun sonuna geldik. Birlikte harika bir model kurduk, onu otomatize ettik ve hatta interaktif bir vitrine koyduk. Projemiz başarılı bir şekilde tamamlandı, değil mi?

Şimdilik evet. Ama bir veri bilimcinin en tehlikeli varsayımı, bir modelin sonsuza dek aynı performansta çalışacağını düşünmektir. Çünkü dünya durmaz; pazar koşulları değişir, yeni rakipler ortaya çıkar, müşteri beklentileri evrilir. Peki bizim aylar önce inşa ettiğimiz o akıllı modelimiz, bugünün dünyasına hala ayak uydurabiliyor mu?

Bu son bölümde, bir mühendis gibi inşa ettiğimiz modelimize, şimdi bir bahçıvan gibi nasıl bakım yapacağımızı, sağlığını nasıl kontrol edeceğimizi ve onu nasıl taze tutacağımızı öğreneceğiz.

1. Adım: Zaman Makinesi - Yeni Gerçekler Ortaya Çıkıyor

Haydi zaman makinemize atlayıp 3 ay sonrasına gidelim. Bölüm 3’te tahmin yaptığımız o “yeni müşterilerin” artık gerçekte ne yaptığını biliyoruz. Onların gerçek kaderleri, bize ilgili departman tarafından gerceklesen_sonuclari_3_ay_sonra.csv adında bir dosya ile gönderildi.

2. Adım: Sağlık Kontrolü - Gerçek Dünya Performansını Ölçmek

Şimdi, bize gelen bu yeni “gerçekleşen sonuçlar” dosyasını okuyarak modelimizin 3 ay önceki tahminleri ile bugünün gerçekleri arasındaki farkı görelim.

library(dplyr)
library(knitr)

# Bölüm 3'te oluşturduğumuz tahmin sonuçlarını okuyoruz
tahminler_3ay_once <- read.csv("tahmin_sonuclari.csv")

# Bize 3 ay sonra geldiği varsayılan "gerçekleşen sonuçlar" dosyasını okuyoruz
# Bu komut sayesinde, "Knit" işlemi de bu dosyayı bulup okuyabilir.
gercekler_bugun <- read.csv("data/gerceklesen_sonuclar_3_ay_sonra.csv")

# Artık her iki veri tablosu da hafızada olduğuna göre, birleştirme yapabiliriz
karsilastirma_df <- left_join(tahminler_3ay_once, gercekler_bugun, by = "musteri_id")

# Yeni, gerçek dünya karnesini oluşturalım
# DİKKAT: 'tahmin_durumu' bir karakter olduğu için onu mantıksal (TRUE/FALSE) bir değere çeviriyoruz
yeni_karne <- table(Gercek = factor(karsilastirma_df$gercek_terk_durumu, levels = c(0, 1)), 
                    Tahmin = factor(karsilastirma_df$tahmin_durumu == "Terk Edecek", levels = c(FALSE, TRUE)))

print("3 Ay Sonraki Gerçek Dünya Karnesi:")
## [1] "3 Ay Sonraki Gerçek Dünya Karnesi:"
print(yeni_karne)
##       Tahmin
## Gercek FALSE TRUE
##      0     8    6
##      1     5    5
# Yeni metrikleri DOĞRU bir şekilde hesaplayalım
yeni_dogruluk <- sum(diag(yeni_karne)) / sum(yeni_karne)
# Hata kontrolü: Eğer hiç "Terk Edecek" tahmini yoksa, kesinlik 0'dır
if (sum(yeni_karne[, "TRUE"]) == 0) {
  yeni_kesinlik <- 0
} else {
  yeni_kesinlik <- yeni_karne["1", "TRUE"] / sum(yeni_karne[, "TRUE"])
}
# Hata kontrolü: Eğer gerçekte hiç terk eden yoksa, duyarlılık 0'dır
if (sum(yeni_karne["1", ]) == 0) {
  yeni_duyarlilik <- 0
} else {
  yeni_duyarlilik <- yeni_karne["1", "TRUE"] / sum(yeni_karne["1", ])
}

3. Adım: Teşhisi Koymak - Model Drift Kapımızı Çaldı!

Şimdi en can alıcı an: Modelimizin ilk günkü performansı ile bugünkü gerçek dünya performansı arasındaki farkı görelim.

# Bölüm 2'deki orijinal metrikleri hatırlayalım (stratejik 0.40 eşiği ile)
orijinal_metrikler <- data.frame(
  Donem = "3 Ay Önce (Beklenti)",
  Dogruluk = 0.828,
  Kesinlik = 0.812,
  Duyarlilik = 0.867
)

# Bugünkü yeni metrikleri ekleyelim
yeni_metrikler <- data.frame(
  Donem = "Bugün (Gerçekleşen)",
  Dogruluk = yeni_dogruluk,
  Kesinlik = yeni_kesinlik,
  Duyarlilik = yeni_duyarlilik
)

performans_karsilastirma <- rbind(orijinal_metrikler, yeni_metrikler)

print("Model Performansının Zaman İçindeki Değişimi:")
## [1] "Model Performansının Zaman İçindeki Değişimi:"
kable(performans_karsilastirma, digits = 3)
Donem Dogruluk Kesinlik Duyarlilik
3 Ay Önce (Beklenti) 0.828 0.812 0.867
Bugün (Gerçekleşen) 0.542 0.455 0.500

İşte bu, Model Drift’tir!

Tablo acı gerçeği yüzümüze vuruyor: Modelimizin en önemli metriği olan Duyarlılık (Recall), yani terk edecek müşterileri yakalama başarısı, beklediğimiz %86.7 seviyesinden ciddi şekilde düşmüş! Bu, modelimizin artık eski keskinliğinde olmadığını ve “eskimeye” başladığını gösteren kırmızı bir alarmdır.

Dostlara Bir Not:

Model drift’in etkisini görselleştirmek, anlamayı kolaylaştırabilir. Aşağıdaki çizgi grafiği, tablodaki performans metriklerini görselleştirerek bu kavramı pekiştirecektir.

# Grafik için veriyi "uzun" formata çevirmemiz gerekiyor
library(tidyr)
library(tidyverse)
performans_uzun <- performans_karsilastirma %>%
  pivot_longer(cols = c(Dogruluk, Kesinlik, Duyarlilik), names_to = "Metrik", values_to = "Deger") %>%
  mutate(Donem = factor(Donem, levels = c("3 Ay Önce (Beklenti)", "Bugün (Gerçekleşen)", "Model v2 (Yeniden Eğitilmiş)"))) # Model v2'yi de ekleyelim

# Sonraki adımdan Model v2 sonuçlarını da buraya ekleyelim ki grafiğimiz tam olsun.
# Bu kod, Adım 5'teki sonuçları da içerecek şekilde düzenlenmiştir.
# ... (Adım 5'teki 'son_karsilastirma' tablosunu burada oluşturup, pivot_longer yapabiliriz) ...

# Şimdilik sadece ilk iki adımı görselleştirelim:
ilk_iki_adim_grafik <- performans_uzun %>% filter(Donem != "Model v2 (Yeniden Eğitilmiş)")

ggplot(ilk_iki_adim_grafik, aes(x = Donem, y = Deger, group = Metrik, color = Metrik)) +
  geom_line(size = 1.2) +
  geom_point(size = 3) +
  labs(title = "Model Performansının Zamanla Düşüşü (Model Drift)",
       x = "Dönem", y = "Metrik Değeri", color = "Performans Metriği") +
  theme_minimal(base_size = 14) +
  ylim(0, 1) # Y eksenini 0-1 arasında sabitleyelim

Bu grafik, tablodaki sayıların anlattığı hikayeyi çok daha dramatik bir şekilde ortaya koyuyor: Tüm metriklerimizde keskin bir düşüş var.

4. Adım: Tedavi - Modeli Taze Kanla Yeniden Eğitmek (Retraining)

Teşhisi koyduğumuza göre, şimdi tedavi zamanı. Modelimizi, bu 3 ayda öğrendiğimiz yeni gerçekleri de içerecek şekilde, yani taze kanla güncelleyeceğiz.

# Bu adımın çalışması için, Bölüm 2'nin sonunda musteri_ozet dosyasını kaydetmemiz gerekir.
# Bölüm 2 sonuna ekleyin: write.csv(musteri_ozet, "musteri_ozet.csv", row.names=FALSE)
if(!file.exists("musteri_ozet.csv")){
    stop("Lütfen önce Bölüm 2'yi çalıştırıp 'musteri_ozet.csv' dosyasını oluşturun.")
}
orijinal_veri <- read.csv("musteri_ozet.csv")

# Son 3 ayın yeni, etiketlenmiş verisini oluşturalım
# ÖNEMLİ: Bölüm 3'teki ham verileri de okumamız gerekiyor.
yeni_sikayetler_ham <- read.csv("data/yeni_sikayetler.csv")
yeni_musteriler_ham <- read.csv("data/yeni_musteri_bilgileri.csv")

# Bu ham verilerden, Bölüm 2'deki gibi 'musteri_ozet' formatında bir tablo oluşturalım
yeni_ogrenilen_veri <- left_join(yeni_sikayetler_ham, yeni_musteriler_ham, by = "musteri_id") %>%
  group_by(musteri_id, uyelik_suresi_ay, aylik_harcama) %>%
  summarise(toplam_sikayet_sayisi = n(), .groups = 'drop') %>%
  # Şimdi bu tabloya gerçek sonuçları ekleyelim
  left_join(read.csv("data/gerceklesen_sonuclar_3_ay_sonra.csv"), by="musteri_id") %>%
  rename(terk_durumu = gercek_terk_durumu) %>% 
  select(names(orijinal_veri)) # Sütunları aynı tut

# Eski ve yeni veriyi birleştirerek daha büyük bir eğitim seti oluşturalım
yeni_buyuk_egitim_seti <- rbind(orijinal_veri, yeni_ogrenilen_veri)

# Bu yeni ve daha bilge veri seti üzerinde modeli YENİDEN EĞİTELİM
model_v2 <- glm(terk_durumu ~ toplam_sikayet_sayisi + uyelik_suresi_ay + aylik_harcama, 
                data = yeni_buyuk_egitim_seti, 
                family = "binomial")

# Yeni ve güncel modelimizi farklı bir isimle kaydedelim (versiyonlama)
saveRDS(model_v2, file = "churn_model_v2.rds")

print("Model yeniden eğitildi ve 'churn_model_v2.rds' olarak kaydedildi!")
## [1] "Model yeniden eğitildi ve 'churn_model_v2.rds' olarak kaydedildi!"

5. Adım: İyileşmeyi Onaylamak - Beklenmedik Ama Değerli Bir Ders

Dostlar, “bahçıvanlık” görevimizi yerine getirdik ve modelimizi taze kanla besleyerek daha bilge bir versiyonunu (model_v2) yarattık. Peki, bu operasyon beklediğimiz sonucu verdi mi? Gelin, yeni modelimizin karnesine bakalım.

# Modelimizin tahmin yapabilmesi için gerekli olan ham verileri yüklemeliyiz.
# Bu veriler, Bölüm 3'te kullandığımız ve tahmin yaptığımız müşterilerin orijinal bilgileridir.
yeni_sikayetler_ham <- read.csv("data/yeni_sikayetler.csv")
yeni_musteriler_ham <- read.csv("data/yeni_musteri_bilgileri.csv")

# Bu ham verilerden, modelin anlayacağı 'musteri_ozet' formatında bir tablo oluşturalım.
# Bu, Bölüm 3'teki veri hazırlama adımının bir tekrarıdır.
test_verisinin_ham_hali <- left_join(yeni_sikayetler_ham, yeni_musteriler_ham, by = "musteri_id") %>%
  group_by(musteri_id, uyelik_suresi_ay, aylik_harcama) %>%
  summarise(toplam_sikayet_sayisi = n(), .groups = 'drop')

# Şimdi, 'karsilastirma_df' tablomuza bu eksik sütunları ekleyelim.
karsilastirma_df_zengin <- left_join(karsilastirma_df, test_verisinin_ham_hali, by = "musteri_id")

# Adım 1: Yeni, yeniden eğitilmiş modelimizi yükleyelim
model_v2 <- readRDS("churn_model_v2.rds")

# Adım 2: Bu YENİ modelle, 3 ay sonraki veriler için TEKRAR tahmin yapalım
# DİKKAT: Tahmin için 'karsilastirma_df' yerine ZENGİNLEŞTİRİLMİŞ versiyonu kullanıyoruz!
yeni_tahminler <- predict(model_v2, newdata = karsilastirma_df_zengin, type = "response")

# Stratejik 0.40 eşiğimizi uygulayalım
yeni_tahmin_siniflari <- ifelse(yeni_tahminler > 0.40, "Terk Edecek", "Kalacak")

# Adım 3: YENİ modelin karnesini oluşturalım
model_v2_karne <- table(Gercek = factor(karsilastirma_df_zengin$gercek_terk_durumu, levels=c(0,1)), 
                        Tahmin = factor(yeni_tahmin_siniflari == "Terk Edecek", levels=c(FALSE,TRUE)))

# Adım 4: YENİ modelin metriklerini hesaplayalım
model_v2_dogruluk <- sum(diag(model_v2_karne)) / sum(model_v2_karne)
model_v2_kesinlik <- model_v2_karne["1", "TRUE"] / sum(model_v2_karne[, "TRUE"])
model_v2_duyarlilik <- model_v2_karne["1", "TRUE"] / sum(model_v2_karne["1", ])

# Adım 5: Nihai Karşılaştırma - Ameliyat Başarılı mı?
son_karsilastirma <- data.frame(
  Model_Versiyonu = c("Model v1 (Eski)", "Model v2 (Yeniden Eğitilmiş)"),
  Donem = "Gerçek Dünya (3 Ay Sonra)",
  Dogruluk = c(yeni_dogruluk, model_v2_dogruluk),
  Kesinlik = c(yeni_kesinlik, model_v2_kesinlik),
  Duyarlilik = c(yeni_duyarlilik, model_v2_duyarlilik)
)

print("Eski ve Yeni Modelin Gerçek Dünya Performans Karşılaştırması:")
## [1] "Eski ve Yeni Modelin Gerçek Dünya Performans Karşılaştırması:"
kable(son_karsilastirma, digits = 3)
Model_Versiyonu Donem Dogruluk Kesinlik Duyarlilik
Model v1 (Eski) Gerçek Dünya (3 Ay Sonra) 0.542 0.455 0.5
Model v2 (Yeniden Eğitilmiş) Gerçek Dünya (3 Ay Sonra) 0.500 0.417 0.5

İşte bu, veri biliminin en öğretici anlarından biridir!

Tabloya dikkatlice bakın. Beklentimizin aksine, daha fazla veriyle yeniden eğittiğimiz Model v2, eski modelimizden daha iyi bir performans göstermedi. Özellikle ana hedefimiz olan Duyarlılık (Recall) metriğinde hiçbir ilerleme kaydedemedik.

Peki Neden? Bu bir başarısızlık mı?

Hayır! Bu, bize modelleme süreci hakkında paha biçilmez bir ders veriyor: Her problem, her modelle çözülmez ve daha fazla veri her zaman çözüm değildir. Bu sonuç bize şunu söylüyor:

-Dünya Değişmiş: Müşteri davranışları son 3 ayda o kadar değişmiş ki, bizim Lojistik Regresyon modelimizin öğrendiği basit kurallar artık yetersiz kalıyor.

-Aracımız Yetersiz Kaldı: Elimizdeki “düz cetvel” (Lojistik Regresyon), artık “virajlı bir yola” dönüşen bu yeni problemi çözemiyor. Bu tablo, yeniden eğitim sürecinin neden bir “zafer” olmadığını değil, neden bir “teşhis aracı” olduğunu kanıtlıyor. Bu sonuç sayesinde artık biliyoruz ki, bir sonraki adımımız sadece daha fazla veri eklemek değil, daha akıllı modeller (Random Forest gibi) denemek veya daha yaratıcı özellikler (feature engineering) geliştirmek olmalıdır.

Bu, Model İzleme ve Yeniden Eğitim döngüsünün neden bir lüks değil, bir zorunluluk olduğunun en somut kanıtıdır. Bize sadece modelin ne zaman “öldüğünü” değil, aynı zamanda onu diriltmek için ne tür bir “tedaviye” ihtiyacımız olduğunu da söyler.

Serinin Sonsözü: Pusulanın Bize Öğrettikleri

Dostlar, yolculuğumuzun sonuna geldik. Ham bir sorudan yola çıktık ve bu altı durakta, bir veri bilimi projesinin sadece teknik adımlarını değil, aynı zamanda felsefesini, zorluklarını ve zaferlerini de birlikte yaşadık.

Pusulamız bize sadece bir yol göstermedi, aynı zamanda paha biçilmez dersler öğretti:

  1. Keşiften Değere: Her şeyin doğru soruyu sormakla başladığını ve verinin içindeki hikayeyi sabırla dinlememiz gerektiğini öğrendik.
  2. Modelden Stratejiye: Bir modelin başarısının sadece matematiksel doğruluğuyla değil, hizmet ettiği iş hedefine (bizim için Recall) ne kadar uygun olduğuyla ölçüldüğünü gördük. Teknik bir ayarı, stratejik bir karara dönüştürdük.
  3. Prototip’ten Motora: Bir fikrin, herkesin kullanabileceği sağlam, güvenilir ve otomatize bir sisteme nasıl dönüştürüleceğinin mühendislik adımlarını attık.
  4. Veriden Hikayeye: Rakamların ve grafiklerin, doğru kitleye doğru bir hikaye ile sunulduğunda nasıl güçlü bir ikna aracına dönüştüğüne tanıklık ettik.
  5. Motordan Vitrine: İnşa ettiğimiz motorun gücünü, Shiny ile herkes için erişilebilir ve somut hale getirdik.
  6. Zaferden Bilgeliğe: Ve son olarak, bir modelin ölümsüz olmadığını, zamanla “eskiyebileceğini” ve bazen en iyi derslerin, beklenmedik sonuçlardan çıktığını acı ama değerli bir şekilde tecrübe ettik.

Unutmayın, bir veri bilimci sadece bir makineyi inşa eden bir mühendis değil, aynı zamanda o makinenin sürekli değişen bir dünyada sağlıklı kalmasını sağlayan bir bahçıvandır. Modellerimizi ekeriz, büyütürüz, performanslarını izler ve gerektiğinde onları daha iyi aletlerle veya yeni tohumlarla yenileyerek taze kalmalarını sağlarız.

Bu yolculukta bana eşlik ettiğiniz, sorduğunuz, sorguladığınız ve öğrendiğiniz için her birinize ayrı ayrı teşekkür ederim. Artık pusula sizin elinizde. Kendi veri bilimi maceralarınıza atılma zamanı!

Veriyle kalın.Lütfen takipte kalın dostlar…