Sıradaki

Çok sınıflı çapraz entropi

Zorluk: Zor

kayıp fonksiyonları

Çok sınıflı bir sınıflandırma (classification) modeli, her örnek (sample) için KK sınıfın her birine bir skor verir. Bu ham skorlar (logits) herhangi bir gerçek sayı olabilir. Softmax onları toplamı 11 olan olasılıklara (probability) çevirir.

Çapraz entropi (cross-entropy) kaybı (loss), modelin doğru sınıfa verdiği olasılığın ne kadar düşük olduğunu ölçer:

  • Olasılık 11‘e yakınsa kayıp 00‘a yakındır.
  • Olasılık 00‘a yakınsa kayıp büyür.

cross_entropy(logits, y) fonksiyonunu yaz. Girdiler şunlardır:

  • logits: şekli (shape) (n, k) olan bir dizi (array). Her satır bir örneğin logits’idir.
  • y: (n,) shape’inde bir tam sayı array’i. Her örneğin doğru sınıfını, 00 ile K−1K - 1 arasında bir etiket (label) olarak verir.

Fonksiyon örneklerin loss’unun ortalamasını döndürmeli:

Pij=eZij∑c=0K−1eZic,L=−1N∑i=1Nlog⁡Pi, yiP_{ij} = \frac{e^{Z_{ij}}}{\sum_{c=0}^{K-1} e^{Z_{ic}}}, \qquad L = -\frac{1}{N}\sum_{i=1}^{N} \log P_{i,\,y_i}

Burada:

  • ZZ: logits.
  • PP: softmax’ın olasılıkları.
  • Pi, yiP_{i,\,y_i}: ii. örneğin doğru sınıfa verdiği olasılık.
  • log⁡\log: doğal logaritma.

Formülü bu sırayla hesaplamak işe yaramaz. Sonuç, logits çok büyük ya da birbirinden çok uzak olsa da sonlu ve doğru çıkmalı.

Örneğin logits = [[1000.0, 0.0]] ve y = [1] için loss 1000’dir. Önce olasılıkları hesaplayıp sonra logaritma almak ona ulaşamaz.

Fonksiyon tek bir Python float döndürmeli.

Örnekler

Girdi: logits = [[2.0, 1.0, 0.1], [0.5, 2.5, 0.3], [1.2, 0.2, 3.0], [0.1, 0.2, 0.3]], y = [0, 1, 2, 0]
Çıktı: 0.5107170220038892

Girdi: logits = [[1000.0, 0.0, -1000.0], [0.0, 500.0, 1000.0]], y = [2, 0]
Çıktı: 1500.0

Girdi: logits = [[-1.0, -2.0, -0.5, -3.0], [0.0, 0.0, 0.0, 0.0], [-5.0, 1.0, 1.0, -4.0]], y = [3, 0, 1]
Çıktı: 1.7440187236679012

İpucu 1

logits[np.arange(n), y], her satırdan kendi doğru sınıfının logit’ini seçer. np.arange(n) satır numaralarını (0, 1, ..., n - 1), y sütun numaralarını verir. İndeksleme bunları eşleştirir ve (0, y[0]), (1, y[1]) gibi elemanları (n,) shape’inde bir array olarak seçer.

logits[:, y] ise bütün satırlardan bütün etiketleri seçer ve (n, n) verir.

İpucu 2

Önce olasılığı bulup sonra log almak yerine logaritmayı formülün içine al:

log⁡Pij=Zij−log⁡∑ceZic\log P_{ij} = Z_{ij} - \log\sum_{c} e^{Z_{ic}}

Böylece küçük bir olasılığın 0’a yuvarlanıp log(0) vermesi sorunu ortadan kalkar. Geriye bir toplamın logaritması kalır.

İpucu 3

Her satırdan kendi maksimumunu çıkar. Burada mim_i satırın maksimumudur:

log⁡∑ceZic=mi+log⁡∑ceZic−mi\log\sum_c e^{Z_{ic}} = m_i + \log\sum_c e^{Z_{ic} - m_i}

Çıkardıktan sonra exp’in en büyük argümanı 00 olur. Toplam en az 11 kalır ve log güvenlidir.

logits.max(axis=1) (n,) shape’inde bir vektör verir. Onu logits’ten çıkarırsan NumPy vektörü sütunlara hizalar, satırlara değil. Bu işleme yayınlama (broadcasting) denir.

Maksimumu logits.max(axis=1, keepdims=True) ya da logits.max(axis=1).reshape(-1, 1) ile (n, 1) shape’inde bir sütun olarak al. Toplamı da aynı biçimde, .sum(axis=1, keepdims=True) ile al.

Gereksinimler

  • logits (n, k) shape’inde bir float array’i, y (n,) shape’inde bir tam sayı array’idir. Her y[i], 0 ile k - 1 arasındadır.
  • Dönüş değeri tek bir float: array ya da liste değil. np.mean’in verdiği np.float64 de kabul edilir. float(...) ile çevirmek zorunda değilsin.
  • Örnekler üzerinde ortalama al, toplam değil.
  • Sonuç, logits ne kadar büyük olursa olsun sonlu ve doğru olmalı. Doğru sınıfın olasılığı 0’a yuvarlansa bile loss inf ya da nan çıkmamalı.
  • logits’i ve y’yi değiştirme. Fonksiyon bittiğinde çağıran kodun array’leri aynı kalmalı.
  • Örnekler üzerinde döngü kullanma: birkaç NumPy ifadesi yeter.

Doğru sınıfa verilen olasılık düştükçe loss hızla artar. Üç sınıflı bir örneğin logits’i (2,1,0)(2, 1, 0) olsun. Softmax bunları yaklaşık (0,665; 0,245; 0,090)(0{,}665;\ 0{,}245;\ 0{,}090) olasılıklarına çevirir.

Loss, doğru sınıfa göre değişir:

  • Doğru sınıf 00 ise loss −ln⁡0,665≈0,41-\ln 0{,}665 \approx 0{,}41 olur.
  • Doğru sınıf 22 ise loss −ln⁡0,090≈2,41-\ln 0{,}090 \approx 2{,}41 olur.

Olasılık 00‘a giderken loss sonsuza gider. Birçok örnek olduğunda loss, her örneğin değerinin ortalamasıdır.

Lojistik regresyondaki ikili çapraz entropi (binary cross-entropy), K=2K = 2 olan bu formülün özel hâlidir.

Loss’u bu sırayla hesaplamak sayısal olarak kırılgandır. Z=(1000,0)Z = (1000, 0) ve doğru sınıf 11 olsun:

  1. Softmax (1,e−1000)(1, e^{-1000}) verir.
  2. e−1000e^{-1000} float’ta 00‘a yuvarlanır.
  3. −log⁡0=∞-\log 0 = \infty çıkar.

Oysa gerçek loss sonludur: −Zi,1+log⁡(e1000+e0)≈0+1000=1000-Z_{i,1} + \log(e^{1000} + e^0) \approx 0 + 1000 = 1000.

Logaritmayı softmax’ın içinde hesaplarsan olasılık hiç oluşmaz:

log⁡Pij=Zij−log⁡∑ceZic\log P_{ij} = Z_{ij} - \log\sum_c e^{Z_{ic}}

Toplamın logaritması için kullanılan maksimumu çıkarma hilesine log-sum-exp denir. PyTorch’un cross_entropy’si ve Keras’ın from_logits=True seçeneği bu yüzden olasılığı değil logits’i alır.

Daha fazlası için Nöronlar ve aktivasyonlar dersine bak.

Logaritmayı softmax’ın içinde hesapla, böylece olasılık hiç oluşmaz.

  1. Her satırdan kendi maksimumunu çıkar.
  2. log⁡P\log P‘yi, logaritmayı softmax’ın içinde hesaplayarak bul.
  3. Doğru sınıfların değerlerini seç.
  4. Ortalamalarının eksisini döndür.

keepdims=True maksimumu ve toplamı (n, 1) shape’inde bir sütun olarak tutar. .reshape(-1, 1) de aynı işi görür. log_probs[np.arange(n), y] ise her satırdan y[i]. sütunu seçer.

import numpy as npdef cross_entropy(logits: np.ndarray, y: np.ndarray) -> float:    n = logits.shape[0]    shifted = logits - logits.max(axis=1, keepdims=True)    log_probs = shifted - np.log(np.exp(shifted).sum(axis=1, keepdims=True))    return float(-np.mean(log_probs[np.arange(n), y]))

Sık yapılan üç hata var:

  • Softmax’ı hesaplayıp np.log(probs[np.arange(n), y]) almak: softmax kararlı bile olsa doğru sınıfın olasılığı 0’a yuvarlanınca log(0) loss’u inf yapar.
  • Olasılığa 1e-12 gibi küçük bir sayı eklemek: inf’i önler ama değeri bozar. [[1000.0, 0.0]] ve y = [1] için 1000 yerine yaklaşık 27,6 verir.
  • Maksimumu satır başına değil bütün array için çıkarmak: ölçeği farklı satırlarda küçük olan satır 00‘a yuvarlanır ve yine inf çıkar.

Bu problemde bir sorun mu var?

solution.py
import numpy as npdef cross_entropy(logits: np.ndarray, y: np.ndarray) -> float:    """Logits ve doğru sınıf etiketlerinden ortalama çapraz entropi kaybını döndürür."""    # Kodunu buraya yaz.    return 0.0

İçindekiler

Geri bildirim

Tür