TEKNİK YAZI / Yapay Zekâ Entegrasyonları

Kullback-Leibler Divergence: Dağılımlar Arası Farkı Ölçmek

Kullback-Leibler divergence, bir dağılımın diğerinden sapmasını ölçer; formüller, cross-entropy ilişkisi, forward ve reverse KL, SciPy ve PyTorch örnekleri.

Kullback-Leibler divergence (KL divergence, göreli entropi), bir olasılık dağılımı P’nin ikinci bir dağılım Q’dan ne kadar saptığını ölçen bir sapma ölçüsüdür. Solomon Kullback ve Richard Leibler 1951’de “On Information and Sufficiency” makalesinde tanımladı. Bilgi kuramındaki yorumu şudur: P’den gelen veriyi, Q’ya göre tasarlanmış bir kodla kodlarsanız sembol başına ortalama ne kadar fazladan bilgi harcarsınız. Simetrik olmadığı ve üçgen eşitsizliğini sağlamadığı için bir uzaklık metriği değildir.

Aşağıda ayrık ve sürekli tanım, birimler, cross-entropy ile ilişki, temel özellikler, SciPy ve PyTorch ile hesaplama, forward ve reverse KL farkı ve makine öğrenmesindeki başlıca kullanım alanları ele alınıyor.

Tanım

Ayrık dağılımlar için:

D_KL(P ‖ Q) = Σ_x P(x) · log( P(x) / Q(x) )

Yoğunluk fonksiyonları p ve q olan sürekli dağılımlar için:

D_KL(P ‖ Q) = ∫ p(x) · log( p(x) / q(x) ) dx

Toplamda iki kural geçerlidir. P(x) = 0 olan terimler sıfır sayılır (0 · log 0 = 0). P(x) > 0 iken Q(x) = 0 olan bir x varsa D_KL(P ‖ Q) sonsuzdur: Q’nun imkânsız saydığı bir olay P’de gerçekleşebilmektedir.

Birimler: nat ve bit

Doğal logaritma (ln) kullanılırsa sonuç nat, 2 tabanlı logaritma kullanılırsa bit cinsindendir. İkisi arasında sabit bir çarpan vardır: 1 nat = 1 / ln 2 ≈ 1,4427 bit. NumPy’daki np.log, SciPy ve PyTorch fonksiyonları varsayılan olarak doğal logaritma kullanır; yani sonuç nat cinsindendir. “Kaç fazladan bit” yorumu için sonucu ln 2’ye bölmek ya da 2 tabanlı logaritma kullanmak gerekir.

Cross-entropy ile ilişki

Entropi H(P) ve cross-entropy H(P, Q) şöyle tanımlanır:

H(P)        = −Σ_x P(x) · log P(x)
H(P, Q)     = −Σ_x P(x) · log Q(x)
D_KL(P ‖ Q) = H(P, Q) − H(P)

Makine öğrenmesinde P veri dağılımı, Q model dağılımıdır. H(P) modelden bağımsız bir sabit olduğu için cross-entropy kaybını en küçüklemek, D_KL(P_veri ‖ Q_model) değerini en küçüklemekle aynıdır; bu da maximum likelihood eğitimine karşılık gelir. Sınıflandırma ağlarındaki cross-entropy kaybı ve PixelCNN gibi autoregressive modellerin bits/dim ölçütü bu ilişkiye dayanır.

Özellikler

Python ile hesaplama

scipy.special.rel_entr(x, y) her eleman için x · log(x / y) değerini verir; x = 0 iken 0, x > 0 ve y = 0 iken sonsuz döndürür. Toplamı ayrık KL divergence’tır. scipy.stats.entropy(p, q) aynı değeri hesaplar, girdileri toplamı 1 olacak şekilde normalleştirir ve base parametresiyle logaritma tabanını seçtirir.

import numpy as np
from scipy.special import rel_entr
from scipy.stats import entropy

p = np.array([0.1, 0.4, 0.5])  # gerçek dağılım P
q = np.array([1 / 3, 1 / 3, 1 / 3])  # tekdüze yaklaşım Q

kl_pq = rel_entr(p, q).sum()  # D_KL(P ‖ Q)
kl_qp = rel_entr(q, p).sum()  # D_KL(Q ‖ P)
print(f"D_KL(P||Q) = {kl_pq:.4f} nats")  # 0.1553 nats
print(f"D_KL(Q||P) = {kl_qp:.4f} nats")  # 0.2054 nats: asimetri
print(f"D_KL(P||Q) = {entropy(p, q, base=2):.4f} bit")  # 0.2240 bit

# Sıfır olasılıklar: P'deki sıfırlar sorun değildir, Q'daki sıfırlar sonsuz verir.
p2 = np.array([0.5, 0.5, 0.0])
q2 = np.array([0.25, 0.25, 0.5])
print(rel_entr(p2, q2).sum())  # 0.6931... (ln 2)
print(rel_entr(q2, p2).sum())  # inf: Q(x3) > 0 iken P(x3) = 0

Sıfırdan kaçınmak için iki dağılımı da küçük bir epsilon ile kırpmak yaygın ama hatalı bir yöntemdir. P’yi kırpmak 0 · log 0 = 0 kuralını bozar; Q’yu kırpıp yeniden normalleştirmemek ise hem değeri değiştirir hem de gerçekten sonsuz olması gereken sonucu sonlu bir sayı gibi gösterir. Histogramdan tahmin yapılıyorsa her kutuya küçük bir sayım ekleyip (pseudo-count) dağılımı yeniden normalleştirmek ve bunu açıkça belirtmek daha doğrudur.

PyTorch’ta KL kaybı

torch.nn.functional.kl_div ve nn.KLDivLoss, ilk argüman olarak modelin log-olasılıklarını, ikinci argüman olarak hedef olasılıkları bekler. Matematiksel gösterimdeki sıra ters çevrilmiştir: kl_div(log_q, p) D_KL(P ‖ Q) değerini hesaplar.

import torch
import torch.nn.functional as F

logits_model = torch.randn(8, 10)  # (B, sınıf sayısı)
logits_target = torch.randn(8, 10)

log_q = F.log_softmax(logits_model, dim=1)  # model dağılımı Q, log uzayında
p = F.softmax(logits_target, dim=1)  # hedef dağılım P

loss = F.kl_div(log_q, p, reduction="batchmean")  # örnek başına ortalama D_KL(P ‖ Q)

Varsayılan reduction="mean" toplamı hem batch boyutuna hem de sınıf sayısına böler; gerçek KL değeri için PyTorch belgesi reduction="batchmean" kullanılmasını önerir.

İleri ve ters yönlü KL (forward ve reverse)

Bir hedef dağılım p’ye parametreli bir q_θ ile yaklaşılırken KL’nin yönü sonucu belirgin biçimde değiştirir:

İki tepeli bir hedefe tek bir normal dağılım uyduran küçük bir deney bu farkı gösterir:

import numpy as np
from scipy.special import rel_entr

x = np.linspace(-8, 8, 4001)
dx = x[1] - x[0]


def normal(x, mu, sigma):
    return np.exp(-0.5 * ((x - mu) / sigma) ** 2) / (sigma * np.sqrt(2 * np.pi))


p = 0.5 * normal(x, -2, 0.6) + 0.5 * normal(x, 2, 0.6)  # iki tepeli hedef

grid = [(mu, s) for mu in np.linspace(-3, 3, 61) for s in np.linspace(0.5, 4.0, 36)]
fwd = min(grid, key=lambda m: rel_entr(p, normal(x, *m)).sum() * dx)  # D_KL(p ‖ q)
rev = min(grid, key=lambda m: rel_entr(normal(x, *m), p).sum() * dx)  # D_KL(q ‖ p)
print("forward KL:", np.round(fwd, 2))  # [0.  2.1]: iki tepeyi birlikte kapsar
print("reverse KL:", np.round(rev, 2))  # [-2.  0.6]: tek tepeye yerleşir

Forward KL ortası boş kalan geniş bir dağılım seçer; reverse KL ise iki tepeden birini neredeyse tam olarak yakalar ve diğerini yok sayar.

Makine öğrenmesinde kullanım

Variational Autoencoder (VAE): Kingma ve Welling’in (2013) VAE’sinde kayıp, yeniden oluşturma terimi ile kodlayıcının gizil dağılımı q(z|x) ve standart normal önsel arasındaki KL teriminden oluşur. Köşegen kovaryanslı normal dağılım için KL kapalı formda hesaplanır:

D_KL( N(μ, σ²) ‖ N(0, 1) ) = ½ · Σ_j ( μ_j² + σ_j² − log σ_j² − 1 )

Bilgi damıtma (knowledge distillation): Hinton ve arkadaşlarının (2015) yönteminde öğrenci model, öğretmenin sıcaklık (temperature) ile yumuşatılmış çıkış dağılımına yaklaştırılır (arXiv:1503.02531). Yumuşak hedeflerle hesaplanan cross-entropy, öğretmenin entropisi kadar sabit bir farkla KL divergence’a eşittir.

Pekiştirmeli öğrenme: TRPO (Schulman ve ark., 2015), politika güncellemesini eski ve yeni politika arasındaki ortalama KL değeri üzerine bir kısıtla sınırlar (arXiv:1502.05477). PPO (Schulman ve ark., 2017) ana yöntem olarak olasılık oranını kırpar; makale uyarlanabilir bir KL cezası çeşidini de tanımlar (arXiv:1707.06347).

Dil modellerinde RLHF: Dil modelleri ön eğitimde cross-entropy ile eğitilir. İnsan geri bildirimiyle pekiştirmeli öğrenmede (RLHF) ise KL doğrudan amaç fonksiyonuna girer. Ouyang ve arkadaşlarının (2022) InstructGPT çalışmasında ödüle, politika ile denetimli ince ayar (SFT) modeli arasındaki token başına bir KL cezası eklenir. Ceza, ödül modelinin aşırı optimize edilmesini (over-optimization) azaltmak için politikayı referans modele yakın tutar. DPO (Rafailov ve ark., 2023) aynı KL kısıtlı amaçtan yola çıkıp ayrı bir ödül modeli eğitmeden doğrudan tercih verisiyle optimizasyon yapar (arXiv:2305.18290).

GAN’ler: Orijinal GAN amacı, en iyi discriminator altında Jensen-Shannon divergence’a indirgenir; GAN değerlendirmesinde kullanılan Inception Score da KL değerlerinin ortalamasından hesaplanır. Ayrıntılar GAN yazısında anlatılıyor.

Sonuç

KL divergence, bir olasılık dağılımını başka bir dağılımla temsil etmenin maliyetini ölçmek gerektiğinde doğru araçtır: cross-entropy kaybının, varyasyonel çıkarımın, damıtmanın ve RLHF’teki referans politika cezasının temelinde yer alır. Simetrik bir ölçü ya da gerçek bir uzaklık gerekiyorsa veya dağılımların destekleri örtüşmüyorsa Jensen-Shannon divergence ya da Wasserstein mesafesi gibi alternatifler değerlendirilmelidir. KL kullanılırken yönün (P ‖ Q mu, Q ‖ P mi) bilinçli seçilmesi ve birimin (nat ya da bit) belirtilmesi gerekir.

Birincil kaynaklar