Lojistik regresyon
Regresyon (regression) modellerinin çıktısı bir sayıdır, örneğin kira, sıcaklık ya da fiyat. Bir e-postanın spam olup olmadığını ya da bir öğrencinin sınavı geçip geçmeyeceğini tahmin etmek sınıflandırma (classification) işidir. Sınıflandırmada çıktı bir sayı değil, bir sınıftır.
Bu derste şunları yapacaksın:
- Modelin skorunu, her sayıyı 0 ile 1 arasına sıkıştıran sigmoidsigmoid: Her gerçek sayıyı 0 ile 1 arasına sıkıştıran S biçimli fonksiyon; lojistik regresyonda ham skoru olasılığa çevirir. fonksiyonuyla olasılığa çevireceksin.
- Olasılığı bir sınıfa dönüştüren eşiği (threshold) ve karar sınırını (decision boundary)karar sınırı (decision boundary): Modelin iki sınıf arasında kararsız kaldığı noktaların kümesi; lojistik regresyonda ham skorun 0 olduğu doğru ya da düzlem. öğreneceksin.
- Ortalama kare hatanın (mean squared error, MSE)ortalama kare hata (mean squared error, MSE): Tahminlerle hedef değerler arasındaki farkların karelerinin ortalaması. Büyük hataları küçüklerden daha ağır cezalandırır. bu iş için neden yetmediğini göreceksin.
- Yerine çapraz entropiyi (cross-entropy)çapraz entropi (cross-entropy): Modelin doğru sınıfa verdiği olasılığın eksi logaritması; emin olup yanılan tahminleri çok ağır cezalandıran bir sınıflandırma kaybı. kuracaksın.
- Doğruluğun (accuracy) neden yanıltabildiğini göreceksin.
- Kesinlik (precision)kesinlik (precision): Modelin pozitif dediği örneklerin kaçının gerçekten pozitif olduğunu gösteren oran. ile duyarlılığın (recall)duyarlılık (recall): Gerçekte pozitif olan örneklerin kaçının model tarafından yakalandığını gösteren oran. neyi ölçtüğünü öğreneceksin.
Skordan karara
Doğrusal bir skor, sigmoid ile olasılığa, eşikle de sınıfa dönüşür.
Bir öğrencinin sınavı geçip geçmediğini, o hafta çalıştığı saate bakarak tahmin edelim. Çalışılan saat, modelin tek özniteliği (feature)öznitelik (feature): Bir örneği tanımlayan ve modele girdi olarak verilen ölçülebilir değerlerden biri. olsun. Etiket (label) geçti, kaldı demek.
Doğrusal skor
Doğrusal regresyondaki gibi bir skor hesaplarız: . Burada ağırlık (weight)ağırlık (weight): Bir girdiyle çarpılarak onun çıktıya ne kadar katkıda bulunacağını belirleyen parametre. Doğrusal modelde her özniteliğin kendi ağırlığı vardır., ise sapma (bias)sapma (bias): Girdilerle çarpılmadan çıktıya eklenen parametre. Doğrusal modelde bütün öznitelikler 0 iken modelin verdiği tahmindir..
ve ile skor şöyle çıkar:
- 4 saat çalışan biri için .
- 2 saat çalışan biri için .
- 10 saat çalışan biri için .
Aynı hesabı NumPy ile yap:
import numpy as npw, b = 1.5, -4.5hours = np.array([2, 4, 10])z = w * hours + bprint(z) # → [-1.5 1.5 10.5]
Sigmoid
Skor 0 ile 1 arasında kalmadığı için olasılık gibi okunamaz. Her skoru 0 ile 1 arasına taşıyan düzgün bir fonksiyon isteriz. Bu işi sigmoid görür:
Sigmoid şöyle davranır:
- çok büyükse sıfıra yaklaşır ve 1’e yaklaşır.
- çok negatifse çok büyür ve 0’a yaklaşır.
- ise olur ve sonuç tam ‘tir.
Öğrenci örneğinde şu değerleri elde edersin:
| Saat () | Skor () | Olasılık () |
|---|---|---|
| 1 | −3 | 0,05 |
| 2 | −1,5 | 0,18 |
| 3 | 0 | 0,50 |
| 4 | 1,5 | 0,82 |
| 5 | 3 | 0,95 |
Skor arttıkça olasılık da artar. Tablo simetriktir, çünkü . Örneğin 2 ve 4 saatin olasılıkları eder.
Tabloyu ve simetriyi kodda yeniden üret:
def sigmoid(z): return 1 / (1 + np.exp(-z))hours = np.array([1, 2, 3, 4, 5])p = sigmoid(w * hours + b)print(np.round(p, 2)) # → [0.05 0.18 0.5 0.82 0.95]print(sigmoid(-1.5) + sigmoid(1.5)) # → 1.0
Matematikte çıktı hiçbir zaman tam 0 ya da 1 olmaz. Bilgisayarda olabilir; bunu “Sayılar taşınca” bölümünde göreceksin.
Çıktıyı olasılık gibi okuruz ve ona deriz. , modelin “label 1” diye verdiği olasılıktır. Label’ın 0 olma olasılığı olur.
Birden çok feature
Birden çok feature olduğunda skor, doğrusal regresyondaki tahminin aynısıdır. Sigmoid her örneğe ayrı ayrı uygulanır:
İki feature’lı bir örnek kuralım. çalışılan saat, çözülen deneme sınavı sayısı olsun.
, ve seçelim. Skor olur.
Üç öğrenci X’in satırları olsun: 4 saat ve 2 deneme, 1 saat ve 1 deneme, 2 saat ve 2 deneme. X @ w her satır için hesaplar:
X = np.array([[4, 2], [1, 1], [2, 2]])w = np.array([1.0, 2.0])b = -6.0z = X @ w + bp = sigmoid(z)print(z) # → [ 2. -3. 0.]print(np.round(p, 2)) # → [0.88 0.05 0.5 ]
Ham skorlar (logits)
Sigmoid’e girmeden önceki değerlerine ham skorlar (logits)ham skorlar (logits): Bir sınıflandırma modelinin sigmoid ya da softmax uygulanmadan önceki çıktıları; lojistik regresyonda doğrusal kısmın verdiği skor. denir. Doğrusal kısım logit’i verir, sigmoid onu olasılığa çevirir. Sigmoid’in tersi şudur:
Yani logit, ile oranının logaritmasıdır. Logits adı buradan gelir.
Üç öğrencinin olasılıklarından logit’lere geri dön:
print(np.round(np.log(p / (1 - p)), 2)) # → [ 2. -3. 0.]Lojistik regresyon modeli
Doğrusal bir logit’i sigmoid’den geçirerek kurulan modele lojistik regresyon (logistic regression) denir. Modelin tamamı iki satırdır:
# önceki bloktaki w = [1, 2] ve b = -6 iledef predict_proba(X, w, b): return sigmoid(X @ w + b) # logit, sonra sigmoidx_new = np.array([[3, 1]]) # 3 saat, 1 denemeprint(np.round(predict_proba(x_new, w, b), 2)) # → [0.27]
Adındaki regresyon yanıltmasın. Model bir olasılık, yani sürekli bir sayı tahmin eder. Sınıfı bu olasılıktan çıkarırız.
Eşik
Olasılık bir karara dönüşmelidir. Bunun için bir eşik seçersin; en yaygını ‘tir. ise model sınıf 1, değilse sınıf 0 tahmin eder.
Sigmoid ‘da verir ve hep artar. Bu yüzden kural, logit’in işaretine bakmakla aynıdır:
Öğrenci örneğinde iki kural aynı sınıfları verir:
w, b = 1.5, -4.5hours = np.array([1, 2, 3, 4, 5])z = w * hours + bp = sigmoid(z)print((p >= 0.5).astype(int)) # → [0 0 1 1 1]print(np.array_equal(p >= 0.5, z >= 0)) # → True
Karar sınırı
Öğrenci örneğinde denkleminden saat çıkar. 3 saat ve daha fazla çalışan geçer, daha azı kalır. Logit’in tam 0 olduğu noktalar iki sınıfı ayırır ve decision boundary adını alır.
Tek feature’da decision boundary bir noktadır. Sınırı ile bulursun:
w, b = 1.5, -4.5boundary = -b / w # logit'in 0 olduğu noktaprint(boundary) # → 3.0
Aynı ve ile 14 öğrenciye bakalım. Her öğrencinin çalışma saati ve sonucu (1 geçti, 0 kaldı) bellidir. Sınır 3 saat olduğundan model 3 saat ve üstüne “geçer” der.
hours = np.array([0.7, 1.4, 2.0, 2.5, 3.2, 3.9, 5.0, 2.8, 3.6, 4.3, 4.8, 5.6, 6.4, 7.3])passed = np.array([0] * 7 + [1] * 7)pred = (1.5 * hours - 4.5 >= 0).astype(int)print(pred) # → [0 0 0 0 1 1 1 0 1 1 1 1 1 1]print(np.sum(pred == passed)) # → 10
14 öğrencinin 10’u doğru sınıflanır. Yanlış olanlar 3,2, 3,9 ve 5,0 saat çalışıp kalanlar ile 2,8 saat çalışıp geçendir. Veri bir doğruyla tam ayrılmaz.
Aşağıda sigmoid eğrisini sürükle: orta noktası decision boundary’yi, öbür tutamaç ‘yi değiştirir.
p̂ = σ(1,5·x − 4,5)
doğruluk 0,71 · BCE 0,544
İki feature’da decision boundary
İki feature’da decision boundary bir doğrudur. Önceki örnekte logit idi.
Sınır ise doğrusudur. Üç öğrenciye bakalım:
- 4 saat ve 2 deneme: ve . Öğrenci geçer.
- 1 saat ve 1 deneme: ve . Öğrenci kalır.
- 2 saat ve 2 deneme: . Öğrenci tam sınırdadır.
Kodda sınıfları bul. Sonra sınırdaki noktaları kur: her için alırsan logit 0 çıkar.
X = np.array([[4, 2], [1, 1], [2, 2]])w = np.array([1.0, 2.0])b = -6.0print((X @ w + b >= 0).astype(int)) # → [1 0 1]x2 = np.array([0.0, 1.0, 2.0, 3.0])X_edge = np.stack([6 - 2 * x2, x2], axis=1) # iki sütunu yan yana koyprint(X_edge @ w + b) # → [0. 0. 0. 0.]print(sigmoid(X_edge @ w + b)) # → [0.5 0.5 0.5 0.5]
Sınır her zaman düzdür
Sınır her zaman düzdür, çünkü denklemi ‘in doğrusal bir ifadesidir. Feature sayısına göre sınır şöyle olur:
- Üç feature’da sınır bir düzlemdir.
- Daha fazla feature’da sınır bir hiperdüzlemdir (hyperplane).
0,5 eşiğiyle lojistik regresyon, logit’in işaretine bakan doğrusal bir sınıflandırıcıdır. Sigmoid o logit’e bir olasılık anlamı verir.
Düz bir sınır her veriyi ayıramaz. İç içe iki halka gibi bir dağılımı hiçbir doğru ikiye bölemez.
Çare bu modelleri üst üste koymaktır. Nöronlar ve aktivasyonlar dersinde bir nöronun (neuron) tam olarak bu model olduğunu göreceksin.
Eşiği değiştirmek
Eşiği ‘ten başka bir değere çekmek sınırı kendine paralel kaydırır. Bunu son bölümde kullanacaksın.
Eşik ise sınır, olan noktaya kayar. Orada logit değerine eşittir. Öğrenci örneğinde sınır 3 saatten yaklaşık saate geçer:
w, b = 1.5, -4.5hours = np.array([1, 2, 3, 4, 5])p = sigmoid(w * hours + b)threshold = 0.8z_edge = np.log(threshold / (1 - threshold)) # yeni sınırda logitprint(round(z_edge, 3)) # → 1.386print(round((z_edge - b) / w, 2)) # → 3.92print((p >= threshold).astype(int)) # → [0 0 0 1 1]
Hangi kayıp?
Modeli eğitmek için bir kayıp (loss) gerekir, ama ilk akla gelen MSE bu iş için yetmez.
MSE ve MAE neden yetmez
Doğrusal regresyondaki MSE, label ile arasındaki farkın karesidir. Label’ı olan bir örneğe bakalım:
- Model derse hata , kare hata olur.
- Model derse doğru sınıfa verdiği olasılık on kat küçülür. Kare hata yalnızca olur.
Ortalama mutlak hata (mean absolute error, MAE) de aynı yerde takılır. için , için çıkar. İkisinin cezası da 1’de tavan yapar, çünkü ile arasındaki fark en çok 1 olabilir.
MSE ve MAE, yanlışta emin olmayı yeterince cezalandırmaz. Oysa , “neredeyse kesin olarak sınıf 0” demektir. Modelin bunun bedelini ağır ödemesini isteriz.
Eksi logaritma
İstediğimiz ceza, doğru sınıfa verilen olasılık 0’a yaklaşırken sınırsız büyümelidir. Eksi logaritma bunu yapar. olur ve olasılık küçüldükçe büyür.
Aşağıdaki tabloda label’ı 1 olan bir örnek için üç loss yan yana durur. Tek örnekte MSE, kare hatanın kendisidir.
| MSE | MAE | ||
|---|---|---|---|
| 0,9 | 0,01 | 0,1 | 0,105 |
| 0,5 | 0,25 | 0,5 | 0,693 |
| 0,1 | 0,81 | 0,9 | 2,303 |
| 0,01 | 0,980 | 0,99 | 4,605 |
on kat küçüldükçe her seferinde artar. Eğri 0’a yaklaşırken sonsuza gider. MSE ile MAE ise 1’de kalır.
Tabloyu kodda üret. Son satır, on kat küçüldükçe artışın hep olduğunu gösterir:
p = np.array([0.9, 0.5, 0.1, 0.01]) # y = 1 iken doğru sınıfın olasılığıprint(np.round((p - 1) ** 2, 3)) # → [0.01 0.25 0.81 0.98]print(np.round(np.abs(p - 1), 2)) # → [0.1 0.5 0.9 0.99]print(np.round(-np.log(p), 3)) # → [0.105 0.693 2.303 4.605]print(np.round(np.diff(-np.log([0.1, 0.01, 0.001])), 3)) # → [2.303 2.303] (np.diff: komşu farklar)
İkili çapraz entropi
Bunu iki label’ı birden kapsayan tek bir formüle çevirelim:
Formül, label’a göre bir terimi eler:
- iken ikinci terim 0 ile çarpılıp yok olur ve kalır.
- iken ilk terim yok olur ve kalır.
Yani formül, doğru sınıfa verilen olasılığın eksi logaritmasıdır. Bu loss’a ikili çapraz entropi (binary cross-entropy) denir. örnekte, MSE’de olduğu gibi, örnek başına loss’ların ortalamasını alırsın:
Formül her örneğin loss’unu hesaplar ve ortalamasını alır. Modelin doğru label’lara verdiği olasılıklar yükseldikçe ortalama düşer.
Dört örnekle dene: ikisinin label’ı 1, ikisinin 0. Son örnek yanlış ve oldukça emin:
y = np.array([1, 1, 0, 0])p = np.array([0.9, 0.6, 0.2, 0.7])per_sample = -(y * np.log(p) + (1 - y) * np.log(1 - p))print(np.round(per_sample, 3)) # → [0.105 0.511 0.223 1.204]print(round(per_sample.mean(), 3)) # → 0.511
Üç kaybı karşılaştır
Aşağıdaki grafikte üç loss’u kaydırıcıyla karşılaştır. Şunları dene:
Kayıp karşılaştırması · loss comparison · MSE, MAE ve ikili çapraz entropi
y = 1 · p̂ = 0,80 · MSE = 0,040 · MAE = 0,200 · ikili çapraz entropi = 0,223
- ile başla ve kaydırıcısını 1’den 0’a doğru çek. MSE ve MAE 1’de dururken binary cross-entropy fırlar.
- ‘a geç. Bu kez yanlış olan uç olur.
İpucu
- Kaydırıcıyı ‘e getir. Üç loss da ve için aynı değeri verir, çünkü model hangi sınıfı seçeceğini bilmiyor.
- Oradan iki yana çek. Doğru tarafta üçü de sıfıra iner. Yanlış tarafta binary cross-entropy ötekilerden ayrılır.
MSE’nin ikinci, daha ciddi bir sorunu gradyandadır. Bunu “Gradyan ve eğitim” bölümünde hesaplayacaksın.
Sayılar taşınca
Formüller doğrudur, ama bilgisayar sayıları sonlu bir aralıkta tutar ve bu aralık iki yerde işi bozar.
Sigmoid’de taşma
Birincisi sigmoid’deki ‘dir. olursa bilgisayar hesaplar.
Bu sayı float64’ün tutabildiği en büyük değeri (yaklaşık ) aşar. Sonuç inf olur. Buna taşma (overflow) denir.
NumPy bu bloklarda bir RuntimeWarning basar ama hesaba devam eder.
print(np.finfo(np.float64).max) # float64'ün sınırları; → 1.7976931348623157e+308print(np.exp(709.0)) # → 8.218407461554972e+307print(np.exp(710.0)) # → infz = -1000.0print(1 / (1 + np.exp(-z))) # → 0.0
Burada çıkar. Sonuç doğrudur, ama ara adımda bir inf oluşur. Bu formülde inf zararsız kalır; aynı fonksiyonun başka bir yazılışında kalmaz.
Sigmoid’in ikinci yazılışı
Pay ve paydayı ile çarparsan sigmoid’i başka biçimde yazarsın:
İki biçim matematikte aynı sayıyı verir. Farkı, exp’e büyük bir sayı verdikleri yerdedir:
- İlki büyük ve negatifken
exp’e büyük bir sayı verir. Sonuç yine de 0 çıkar. - İkincisi büyük ve pozitifken
exp’e büyük bir sayı verir. Pay ve payda birdeninfolur veinf / inf = nançıkar.
İkinci biçimi ile dene:
z = 1000.0print(np.exp(z) / (1 + np.exp(z))) # → nan
Kararlı sigmoid
Sayısal kararlılık (numerical stability), aynı matematiği bilgisayarın tuttuğu sayı aralığında kalacak biçimde yazmaktır. Sigmoid için yol, exp’e hiçbir zaman büyük pozitif bir sayı vermemektir.
İki biçimden ‘nin işaretine göre uygun olanı seç:
- ise ilk biçimi kullan. Üs negatiftir ve küçülür.
- ise ikinci biçimi kullan. Üs negatiftir ve küçülür.
Kodda iki biçim de exp(-abs(z)) değerini kullanır. Bu değer hiçbir zaman taşmaz. np.where iki dalı da hesaplar; ortak e her ikisinde güvenli olduğu için hiçbir dal taşmaz.
def sigmoid(z): # öncekinin yerini alır e = np.exp(-np.abs(z)) # üs hiçbir zaman pozitif olmaz return np.where(z >= 0, 1 / (1 + e), e / (1 + e))z = np.array([-1000.0, -2.0, 0.0, 2.0, 1000.0])print(sigmoid(z)) # → [0. 0.11920292 0.5 0.88079708 1. ]
Dikkat
Tek bir nan yeter. Bir örneğin değeri nan ise şunlar olur:
- Gradyanın her bileşeni
nanolur. - Tek bir güncellemede bütün weight’ler
nan’a döner.
Eğitim hata vermeden sessizce bozulur.
Logaritma ve kırpma
İkinci tuzak logaritmada. Aşırı bir logit, sigmoid’in çıktısını tam 1’e ya da tam 0’a yuvarlayabilir:
- 1 tarafında yeterlidir. o kadar küçüktür ki bilgisayarda tam 1 olur.
- Sıfır tarafında
float64çok daha küçük sayıları tutabilir. Tam 0, ancak gibi çok daha uç bir logit’te çıkar.
print(1 + np.exp(-40.0) == 1.0) # → Truefor z in [40.0, -745.0, -746.0]: print(z, sigmoid(z))# → 40.0 1.0# → -745.0 5e-324# → -746.0 0.0
Kararlı bir sigmoid bu tuzağı kaldırmaz. O da yeterince uç logit’lerde tam 0 ya da tam 1 verir.
Bu yuvarlama loss’u iki biçimde bozar:
- iken olursa loss çıkar.
- Daha sinsisi iken olmasıdır. Loss 0 olmalı, ama formülün ikinci terimi olur ve sonuç
nançıkar. Model doğru ve emin olduğu hâlde bu olur.
def raw_loss(p, y): # kırpmasız formül return -(y * np.log(p) + (1 - y) * np.log(1 - p))y = np.array([1])print(raw_loss(np.array([0.0]), y)) # → [inf]print(raw_loss(np.array([1.0]), y)) # → [nan]
Çare, olasılığı aralığına kırpmaktır (clipping):
, gibi küçük bir sabittir. Kırpma iki şey yapar:
- ‘nın hiçbir zaman tam 0 ya da 1 olmasına izin vermez.
- En kötü loss’u ile sınırlar.
Kırpmalı binary cross-entropy şöyle yazılır:
def binary_cross_entropy(p, y, eps=1e-12): p = np.clip(p, eps, 1 - eps) # p tam 0 ya da 1 olamaz return float(-np.mean(y * np.log(p) + (1 - y) * np.log(1 - p)))y = np.array([1])print(round(binary_cross_entropy(np.array([0.0]), y), 1)) # → 27.6print(f"{binary_cross_entropy(np.array([1.0]), y):.1e}") # → 1.0e-12
‘u çok küçük seçersen ( gibi) bilgisayarda yine tam 1 olur. Kırpma üst uçta işe yaramaz:
print(1 - 1e-20 == 1.0) # → TrueYukarıdaki grafikte seç ve kaydırıcıyı en sola, ‘e çek. Binary cross-entropy okunur; bu, tablodaki değerdir. Kaydırıcı burada durur. Daha küçük ‘de eğri sınırsız tırmanırdı; kırpma onu ‘da keser.
Gradyan ve eğitim
Sigmoid ve logaritma araya girer, ama gradyan yine doğrusal regresyondaki biçime çöker.
Zincir kuralı
Doğrusal regresyon dersinde MSE’nin gradyanı şu çıkmıştı:
Tek bir örnekle hesaplayalım. Şunları tanımlayalım:
Zincir şöyledir:
- , ‘ya bağlıdır.
- , ‘ye bağlıdır.
- , ve ‘ye bağlıdır.
Böyle iç içe geçmiş bir zincirde türev, her halkanın türevlerinin çarpımıdır. Buna zincir kuralı (chain rule) denir. Geri yayılım dersi bunu ayrıca işler.
Loss’un türevi
Önce loss’un ‘ya göre türevini al:
Son eşitlik, iki kesri ortak paydada toplamaktan gelir:
- Payda olur.
- Pay kalır.
Formülü merkezi farkla (central difference) kontrol et. Bu yöntemi Gradyan inişi dersinde görmüştün:
def loss(p, y): return -(y * np.log(p) + (1 - y) * np.log(1 - p))p, y, h = 0.1, 1.0, 1e-6numeric = (loss(p + h, y) - loss(p - h, y)) / (2 * h)formula = (p - y) / (p * (1 - p))print(round(numeric, 4), round(formula, 4)) # → -10.0 -10.0
Sigmoid’in türevi
Sonra sigmoid’in türevini al:
Son adımda şunu kullandık:
Sigmoid’in eğimi, ile çarpımına eşittir:
- ‘te en büyüktür ().
- 0’a ya da 1’e yaklaştıkça sıfıra iner.
Kodda noktasında kontrol et:
# önceki bloktaki sigmoid ilez, h = 0.0, 1e-6numeric = (sigmoid(z + h) - sigmoid(z - h)) / (2 * h)p = sigmoid(z)print(round(numeric, 4), p * (1 - p)) # → 0.25 0.25
Türevleri çarpmak
İki türevi çarpınca paydadaki çarpan gider:
Sayıyla kontrol et. ve olsun:
- Loss’un türevini al: .
- Sigmoid’in eğimini al: .
- İkisini çarp: çıkar. Bu, ile tam olarak aynıdır.
y, p = 1.0, 0.1dL_dp = -y / p + (1 - y) / (1 - p) # loss'un türevidp_dz = p * (1 - p) # sigmoid'in eğimiprint(round(dL_dp, 4), round(dp_dz, 4), round(dL_dp * dp_dz, 4)) # → -10.0 0.09 -0.9print(round(p - y, 4)) # → -0.9
Parametrelerin gradyanı
Geriye ‘nin parametrelere göre türevi kalır. ve olur.
Bir örnek için gradyan ve olur. örnekte ortalamasını alırsın:
Formülü şöyle oku:
- için her örneğin hatasını o örneğin feature’larıyla çarpıp ortalarsın.
- için çarpan yoktur; hataları doğrudan ortalarsın.
Bu, doğrusal regresyondaki gradyanla aynı biçimdir: tahmin eksi label, çarpı feature. Fark iki şeydedir:
- Tahmin olur.
- 2 çarpanı kalkar.
Bu sadeleşme sigmoid ve binary cross-entropy birlikte seçildiği için olur: sigmoid’in türevi ile loss türevinin paydası birbirini götürür.
Formülü bir fonksiyona dök ve üç öğrencili örnekte dene. Label’lar olsun:
def gradient(X, y, w, b): p = sigmoid(X @ w + b) error = p - y # tahmin eksi label grad_w = X.T @ error / len(y) grad_b = np.mean(error) return grad_w, grad_bX = np.array([[4, 2], [1, 1], [2, 2]])y = np.array([1, 0, 1])w, b = np.array([1.0, 2.0]), -6.0grad_w, grad_b = gradient(X, y, w, b)print(np.round(grad_w, 3), round(grad_b, 3)) # → [-0.476 -0.397] -0.191
Bir gradyan adımı
Dört örnekle bir adım atalım. ve label’lar olsun:
- ve ile başla.
- Her logit 0 olur, bu yüzden her örnekte çıkar.
- Hataları ile hesapla: .
Bu tek katmanlı modelde sıfırdan başlamak sorun çıkarmaz. Sinir ağlarında çıkarır; bunu ileride göreceksin.
Gradyan:
Öğrenme oranı (learning rate)öğrenme oranı (learning rate): Gradyan inişinde atılan adımın büyüklüğü. Çok küçükse eğitim yavaşlar, çok büyükse kayıp büyür. ile olur. yerinde kalır. Adımın sonucu şöyledir:
- Yeni olasılıklar yaklaşık olur.
- Loss ‘ten ‘e iner.
- Label’ı 0 olan ilk iki örneğin değeri ‘ten ve ‘ye çıktı, yani biraz kötüleşti. Loss’u label’ı 1 olan örnekler düşürdü. Decision boundary hâlâ ‘dadır, çünkü ilk adımda kıpırdamadı.
Sonraki adımda pozitif çıkar ve ‘yi negatife çeker.
Aynı adımı gradient ile at:
X = np.array([[1.0], [2.0], [3.0], [4.0]])y = np.array([0, 0, 1, 1])w, b, lr = np.zeros(1), 0.0, 0.5grad_w, grad_b = gradient(X, y, w, b)print(grad_w, grad_b) # → [-0.5] 0.0print(round(binary_cross_entropy(sigmoid(X @ w + b), y), 3)) # → 0.693w = w - lr * grad_wb = b - lr * grad_bp = sigmoid(X @ w + b)print(w, b) # → [0.25] 0.0print(np.round(p, 2)) # → [0.56 0.62 0.68 0.73]print(round(binary_cross_entropy(p, y), 3)) # → 0.625
Eğitim döngüsü
Geri kalan, gradyan inişiyle (gradient descent)gradyan inişi (gradient descent): Kaybı azaltmak için parametreleri gradyanın tersine küçük adımlarla güncelleyen yöntem. doğrusal regresyonda yaptığının aynısıdır:
- Tahmin et.
- Gradyanı hesapla.
- ve ‘yi ile güncelle.
- Bunu epoch’lar, yani veri kümesi (dataset)veri kümesi (dataset): Her biri öznitelikler ve bir hedef değerden oluşan örneklerin tamamı. üzerinden turlar boyunca tekrarla.
Mini yığınlar (mini-batch) ve öznitelik ölçekleme (feature scaling) de aynı biçimde geçerlidir. Ölçeksiz feature’lar burada da loss yüzeyini uzatır. Eğitimden sonra sınıfı eşiğiyle tahmin edersin.
Döngünün tamamı bir fonksiyondur. Fonksiyon eğitim kümesiyle ve ‘yi öğrenir, sonra X_test için sınıfları döndürür:
def train(X, y, X_test, lr, epochs): n, d = X.shape w = np.zeros(d) b = 0.0 for _ in range(epochs): p = sigmoid(X @ w + b) # 1. tahmin et w = w - lr * (X.T @ (p - y)) / n # 2. gradyan, 3. güncelle b = b - lr * np.mean(p - y) p_test = sigmoid(X_test @ w + b) return (p_test >= 0.5).astype(int) # eşik: 0,5X = np.array([[1.0], [2.0], [3.0], [4.0]])y = np.array([0, 0, 1, 1])X_test = np.array([[1.5], [2.4], [2.6], [3.5]])print(train(X, y, X_test, lr=0.5, epochs=1000)) # → [0 0 1 1]print(train(X, y, X_test, lr=0.5, epochs=0)) # → [1 1 1 1]
İki çağrının sonucu şöyle okunur:
- İlk çağrıda veri 2,5 etrafında simetriktir ve sınır 2,5’e yakın bir yere oturur. 2,4 sınıf 0, 2,6 sınıf 1 çıkar.
epochs=0iken her olasılık tam kalır. Eşikte sınıf 1 seçildiği için bütün tahminler 1 çıkar.
MSE ile gradyan
Aynı zinciri MSE ile kuralım. için olur. Sigmoid’in eğimiyle çarpılınca sadeleşecek bir şey kalmaz:
çarpanı, 0’a ya da 1’e yaklaşırken sıfıra gider. Label’ı 1 olan bir örnekte negatife indikçe iki gradyana bakalım:
| MSE gradyanı | Binary cross-entropy gradyanı | ||
|---|---|---|---|
| 0 | 0,5 | −0,25 | −0,5 |
| −2 | 0,119 | −0,185 | −0,881 |
| −4 | 0,018 | −0,035 | −0,982 |
| −6 | 0,0025 | −0,005 | −0,998 |
Model ne kadar emin ve yanlışsa MSE’nin gradyanı o kadar küçülür. ‘te gradyan, ‘dakinin yaklaşık yedide biridir. Binary cross-entropy’de gradyan hata büyüdükçe büyür ve ‘e yaklaşır.
Yani MSE’de en çok yanılan örnek en az düzeltilir. MAE’nin gradyanı da aynı çarpanını taşır. O da aynı yerde takılır.
Tabloyu kodda üret:
# önceki bloktaki sigmoid ilez = np.array([0.0, -2.0, -4.0, -6.0])p = sigmoid(z)y = 1.0grad_mse = 2 * (p - y) * p * (1 - p) # sigmoid eğimi gradyanı küçültürgrad_bce = p - y # sadeleşmiş gradyanprint(np.round(p, 4)) # → [0.5 0.1192 0.018 0.0025]print(np.round(grad_mse, 3)) # → [-0.25 -0.185 -0.035 -0.005]print(np.round(grad_bce, 3)) # → [-0.5 -0.881 -0.982 -0.998]print(round(grad_mse[0] / grad_mse[2], 1)) # → 7.2
Doğruluk yetmediğinde
Accuracy tek başına yetmez; modelin nerede yanıldığını görmek için hataları türlerine göre saymak gerekir.
Accuracy ve dengesiz sınıflar
Eğitilmiş bir modelin ne kadar iyi olduğunu söylemenin en doğal yolu accuracy’dir. Accuracy, doğru tahminlerin tüm örneklere oranıdır.
100 kişilik bir hastalık taramasında 5 kişinin hasta olduğunu düşün. Herkese “sağlam” diyen bir model 95 kişiyi doğru bilir ve %95 accuracy alır. Ama tek bir hastayı bile bulamaz.
Sorun, sınıfların dengesiz olmasıdır (imbalanced classes). Bir sınıf ötekinden çok daha sık görülünce, çoğunluk sınıfını söylemek bile yüksek accuracy verir.
y = np.array([1] * 5 + [0] * 95) # 5 hasta, 95 sağlamy_hat = np.zeros(100, dtype=int) # herkese "sağlam" deprint(np.mean(y_hat == y)) # → 0.95
Karışıklık matrisi
Hata sayımlarının 2×2’lik tablosuna karışıklık matrisi (confusion matrix) denir. Hastaları pozitif sınıf (label 1) sayalım. Dört sayım vardır:
- doğru pozitif (true positive, TP): model hasta dedi, gerçekte hasta.
- yanlış pozitif (false positive, FP): model hasta dedi, gerçekte sağlam; yanlış alarm.
- yanlış negatif (false negative, FN): model sağlam dedi, gerçekte hasta; kaçırılan hasta.
- doğru negatif (true negative, TN): model sağlam dedi, gerçekte sağlam.
Bir model 20 kişiyi taradı. 7 kişi hasta, 13 kişi sağlam. Model her kişiye bir skor verir ve eşik ‘dir. Skorlar:
- hastalar: 0,93; 0,86; 0,78; 0,67; 0,49; 0,41; 0,31;
- sağlamlar: 0,72; 0,58; 0,46; 0,39; 0,36; 0,33; 0,27; 0,22; 0,17; 0,12; 0,09; 0,05; 0,02.
Sonuç şu tabloda:
| Tahmin: hasta (1) | Tahmin: sağlam (0) | |
|---|---|---|
| Gerçekte hasta (1) | 4 (TP) | 3 (FN) |
| Gerçekte sağlam (0) | 2 (FP) | 11 (TN) |
Accuracy şu çıkar:
Herkese “sağlam” diyen modelin accuracy’si ‘tir. Bu model ondan iyi görünür, ama 7 hastanın 3’ünü kaçırdı. İki ayrı ölçüt gerekir.
Kodda skorları kur ve dört sayımı boolean koşullarla say:
y = np.array([1] * 7 + [0] * 13) # 7 hasta, 13 sağlamscores = np.array([ 0.93, 0.86, 0.78, 0.67, 0.49, 0.41, 0.31, # hastalar 0.72, 0.58, 0.46, 0.39, 0.36, 0.33, 0.27, 0.22, 0.17, 0.12, 0.09, 0.05, 0.02, # sağlamlar])y_hat = (scores >= 0.5).astype(int)tp = np.sum((y_hat == 1) & (y == 1))fp = np.sum((y_hat == 1) & (y == 0))fn = np.sum((y_hat == 0) & (y == 1))tn = np.sum((y_hat == 0) & (y == 0))print(tp, fp, fn, tn) # → 4 2 3 11print((tp + tn) / len(y)) # → 0.75
Precision ve recall
İki ölçüt şunlardır:
Precisionkesinlik (precision): Modelin pozitif dediği örneklerin kaçının gerçekten pozitif olduğunu gösteren oran. ve recallduyarlılık (recall): Gerçekte pozitif olan örneklerin kaçının model tarafından yakalandığını gösteren oran. şu soruları yanıtlar:
| Ölçüt | Sorduğu soru | Örnekte |
|---|---|---|
| Precision | Modelin pozitif dediği örneklerin kaçı gerçekten pozitif? | |
| Recall | Gerçek pozitiflerin kaçı yakalandı? |
Paydanın 0 olduğu uç durumlar vardır:
- Hiç pozitif tahmin yoksa precision’ın paydası 0 olur.
- Hiç gerçek pozitif yoksa recall’un paydası 0 olur.
Bu durumlarda ilgili değer genellikle 0 sayılır.
Fonksiyon sayımları ve iki oranı tek yerde toplar. Herkese “sağlam” diyen model için precision’ın paydası 0 olur:
# önceki bloktaki y ve y_hat iledef precision_recall(y, y_hat): tp = np.sum((y_hat == 1) & (y == 1)) fp = np.sum((y_hat == 1) & (y == 0)) fn = np.sum((y_hat == 0) & (y == 1)) precision = tp / (tp + fp) if tp + fp > 0 else 0.0 # payda 0 ise 0 recall = tp / (tp + fn) if tp + fn > 0 else 0.0 return float(precision), float(recall)print(precision_recall(y, y_hat)) # → (0.6666666666666666, 0.5714285714285714)print(precision_recall(y, np.zeros(20, dtype=int))) # → (0.0, 0.0)
Eşik ve denge
İkisi arasında bir denge var ve onu eşik ayarlar. Eşiği düşürürsen model daha az emin olduğunda bile “hasta” der. Daha çok hasta yakalarsın, ama daha çok yanlış alarm verirsin.
Aynı 20 kişi için üç eşiğe bakalım:
| Eşik | TP | FP | FN | TN | Precision | Recall |
|---|---|---|---|---|---|---|
| 0,8 | 2 | 0 | 5 | 13 | 1,00 | 0,29 |
| 0,5 | 4 | 2 | 3 | 11 | 0,67 | 0,57 |
| 0,3 | 7 | 6 | 0 | 7 | 0,54 | 1,00 |
Tablo iki uçtaki eşiği gösterir:
- 0,8 eşiğinde model yalnızca çok emin olduğunda hasta der. Hiç yanlış alarm yoktur (precision 1), ama 7 hastadan 5’i kaçar (recall 0,29).
- 0,3 eşiğinde hiçbir hasta kaçmaz (recall 1), ama 13 “hasta” tahmininin yalnızca 7’si doğrudur.
Aynı skorlarla eşiği üç değere çek:
for threshold in [0.8, 0.5, 0.3]: y_hat = (scores >= threshold).astype(int) precision, recall = precision_recall(y, y_hat) print(threshold, round(precision, 2), round(recall, 2))# → 0.8 1.0 0.29# → 0.5 0.67 0.57# → 0.3 0.54 1.0
Aşağıda eşiği 0’dan 1’e sürükle ve karışıklık matrisiyle precision ve recall’a bak.
20 hasta · modelin hasta olasılığı p̂ · p̂ ≥ eşik ise "hasta" der
| Gerçek sınıf | Tahmin: hasta | Tahmin: sağlam |
|---|---|---|
| Gerçekte hasta | 4TP | 3FN |
| Gerçekte sağlam | 2FP | 11TN |
eşik 0,50 · 6 / 20 kişiye "hasta" dendi · doğruluk 0,75 2 yanlış alarm (FP) · 3 kaçırılan hasta (FN)
Hangisinin önemli olduğu probleme bağlıdır:
- Hastalık taramasında bir hastayı kaçırmak pahalıdır. Bu yüzden recall yüksek tutulur.
- Spam filtresinde önemli bir e-postayı spam diye silmek pahalıdır. Bu yüzden precision yüksek tutulur.
Tek bir accuracy sayısı bu seçimi saklar.
Özet
- Lojistik regresyon, doğrusal bir logit’i () sigmoid ile olasılığa çevirir. Eşik (genellikle 0,5) olasılığı sınıfa dönüştürür, decision boundary ise logit’in 0 olduğu doğru ya da düzlemdir.
- MSE ve MAE, yanlışta emin olmanın cezasını 1’de keser ve sigmoid yüzünden gradyanı sıfıra yaklaştırır. Binary cross-entropy bedeli sınırsız büyütür ve gradyanı olur.
- Gradyan doğrusal regresyondakiyle aynı biçimdedir: için çarpımının katı, için hataların ortalaması. Eğitim döngüsü de aynı kalır, yalnızca tahmin sigmoid’den geçer.
- Bilgisayarda taşabilir ve sonsuzdur. Sigmoid’i ‘nin işaretine göre uygun biçimde hesaplamak ve olasılığı ile kırpmak numerical stability sağlar.
- Accuracy dengesiz sınıflarda yanıltır. Confusion matrix’ten okunan precision (pozitif dediklerinin kaçı doğru) ve recall (gerçek pozitiflerin kaçı yakalandı) hatanın türünü gösterir. Eşik ikisi arasındaki dengeyi ayarlar.
Terimler
| Türkçe | English |
|---|---|
| regresyon | regression |
| ağırlık | weight |
| sapma | bias |
| öznitelik | feature |
| öğrenme oranı | learning rate |
| gradyan inişi | gradient descent |
| veri kümesi | dataset |
| mini yığın | mini-batch |
| öznitelik ölçekleme | feature scaling |
| zincir kuralı | chain rule |
| epoch | epoch |
| olasılık | probability |
| sınıflandırma | classification |
| sigmoid | sigmoid |
| ham skorlar | logits |
| lojistik regresyon | logistic regression |
| eşik | threshold |
| karar sınırı | decision boundary |
| hiperdüzlem | hyperplane |
| etiket | label |
| ortalama kare hata | mean squared error, MSE |
| ortalama mutlak hata | mean absolute error, MAE |
| kayıp | loss |
| çapraz entropi | cross-entropy |
| ikili çapraz entropi | binary cross-entropy |
| taşma | overflow |
| sayısal kararlılık | numerical stability |
| kırpma | clipping |
| doğruluk | accuracy |
| dengesiz sınıflar | imbalanced classes |
| karışıklık matrisi | confusion matrix |
| doğru pozitif | true positive (TP) |
| yanlış pozitif | false positive (FP) |
| yanlış negatif | false negative (FN) |
| doğru negatif | true negative (TN) |
| kesinlik | precision |
| duyarlılık | recall |
Şimdi sıra sende
Ders tamamlandı. Sıradaki: ilk problem.
- Kararlı sigmoidZorluk: KolayBaşla →
- İkili çapraz entropiZorluk: OrtaBaşla →
- Lojistik regresyon eğitZorluk: ZorBaşla →
- Kesinlik ve duyarlılıkZorluk: OrtaBaşla →
Notların
Metinden bir bölüm seçip Not al'a basarak da ekleyebilirsin.
Bu derse dair söylemek istediğin bir şey var mı?