---
title: "Karşıtsal SSL, Sparse Coding ve VAE"
subtitle: "İki hocalı hafta — EBM'yi eğitmek enerji fonksiyonunu şekillendirmektir. Yann LeCun (Lecture) bunun iki ailesini kurar: contrastive (karşıtsal) yöntemler veri noktalarında enerjiyi aşağı, üretilmiş negatif örneklerde yukarı iter (push down / push up); non-contrastive (mimari) yöntemler ise negatif örnek üretmeden, modelin yapısını kısıtlayarak düşük-enerji bölgesinin hacmini sınırlar. Alfredo Canziani (Practicum) ikinci ailenin en zarif örneğini gösterir: VAE (değişimsel autoencoder) — encoder bir kod değil bir dağılım üretir, latent reparameterization ile örneklenir ve Gaussian kısıt latent uzayı düzenli, üretken kılar."
---
::: {.callout-note title="Bölüm bilgisi"}
- **LeCun'un Lecture videosu:** [YouTube — Energy-based models II: contrastive methods](https://www.youtube.com/watch?v=ZaVP2SY23nc) (≈99 dk)
- **Canziani'nin Practicum videosu:** [YouTube — Variational autoencoders](https://www.youtube.com/watch?v=7Rb4s9wNOmc) (≈58 dk)
- **Edition:** Spring 2020 (NYU-DLSP20)
- **Hocalar:** Yann LeCun (Lecture, teorik) + Alfredo Canziani (Practicum, pratik)
- **Kaynak:** [atcold.github.io/NYU-DLSP20](http://atcold.github.io/NYU-DLSP20)
- **Okuma süresi:** ≈25 dk
:::
```{python}
#| echo: false
# ============================================================================
# SETUP — NYU sayısal motor (_engine.py) + NYU Violet+gold viz (_viz.py)
# Bu hücre gizlidir (#| echo: false). Aşağıdaki TÜM figür hücreleri burada
# tanımlanan energy_1d / vae_reparam / ae_latent_clusters /
# make_manifold_curve + önceki hafta yardımcıları + COL_* +
# apply_style / draw_pipeline / style_legend / CLASS_COLORS isimlerini kullanır.
# _engine.py saf numpy (torch YOK); _viz.py NYU Violet+gold paleti.
# İçerikler VERBATIM gömülüdür.
#
# NOT: matplotlib backend'i AYARLANMAZ (matplotlib.use(...) ÇAĞRILMAZ).
# Quarto kendi inline (figür yakalayan) backend'ini kurar; Agg backend
# inline figür-yakalamayı bozar (plt.show() çıktı üretmez). Standalone
# figür testinde savefig kullanılır.
# ============================================================================
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
from matplotlib.patches import (
FancyBboxPatch, FancyArrowPatch, Rectangle, Circle, Ellipse, Polygon, Patch,
)
from matplotlib.lines import Line2D
from matplotlib.colors import ListedColormap, BoundaryNorm, LinearSegmentedColormap
np.random.seed(0)
# ===========================================================================
# _engine.py — saf numpy sayısal yardımcılar (torch YOK)
# ===========================================================================
# ---------------------------------------------------------------------------
# Afin / lineer dönüşümler
# ---------------------------------------------------------------------------
def affine_transform(P, W, b=None):
"""P[N,2] noktalarına y = W x (+ b) afin/lineer dönüşümü uygular (satır-vektör konvansiyonu)."""
P = np.asarray(P, float)
Y = P @ np.asarray(W, float).T
if b is not None:
Y = Y + np.asarray(b, float)
return Y
def unit_grid(n=11, lim=2.0, fine=61):
"""Izgara çizgileri listesi (yatay + dikey). Her çizgi (fine,2) dizisi.
Lineer dönüşüm görsellerinde 'uzayın kumaşı' için."""
t = np.linspace(-lim, lim, n)
f = np.linspace(-lim, lim, fine)
lines = []
for yi in t:
lines.append(np.column_stack([f, np.full_like(f, yi)]))
for xi in t:
lines.append(np.column_stack([np.full_like(f, xi), f]))
return lines
def linear_transforms():
"""Canziani'nin dört temel lineer dönüşümü (Bölüm 8) — 2x2 matrisler."""
th = np.deg2rad(35.0)
R = np.array([[np.cos(th), -np.sin(th)], [np.sin(th), np.cos(th)]]) # rotation (ortonormal)
S = np.array([[1.8, 0.0], [0.0, 0.55]]) # scaling (köşegen)
Sh = np.array([[1.0, 0.85], [0.0, 1.0]]) # shearing
F = np.array([[1.0, 0.0], [0.0, -1.0]]) # reflection (det<0)
return dict(rotation=R, scaling=S, shearing=Sh, reflection=F)
# ---------------------------------------------------------------------------
# SVD geometrisi (Canziani'nin 'patatesi')
# ---------------------------------------------------------------------------
def unit_circle(n=240):
t = np.linspace(0, 2 * np.pi, n)
return np.column_stack([np.cos(t), np.sin(t)])
def svd_demo(W):
"""W = U Σ Vᵀ. Geometrik okuma: rotation(Vᵀ) · scaling(Σ) · rotation(U)."""
U, s, Vt = np.linalg.svd(np.asarray(W, float))
return U, s, Vt
def near_singular_matrix():
"""Bir tekil değeri ≈0 olan matris — bir boyutu 'ezer' (elips çizgiye çöker)."""
th = np.deg2rad(30.0)
R = np.array([[np.cos(th), -np.sin(th)], [np.sin(th), np.cos(th)]])
Sigma = np.diag([1.6, 0.04]) # ikinci tekil değer ≈ 0
return R @ Sigma @ R.T
# ---------------------------------------------------------------------------
# Spiral (manifold germe demosu)
# ---------------------------------------------------------------------------
def make_spiral(n_per=120, k=5, noise=0.18, seed=0):
"""k-kollu spiral (CS231n stili). Her kol bir sınıf; kollar orijinde iç içe → lineer ayrılamaz."""
rng = np.random.default_rng(seed)
X = np.zeros((n_per * k, 2))
y = np.zeros(n_per * k, dtype=int)
for j in range(k):
ix = slice(n_per * j, n_per * (j + 1))
r = np.linspace(0.0, 1.0, n_per)
t = np.linspace(j * 4.0, (j + 1) * 4.0, n_per) + rng.normal(0, 1, n_per) * noise
X[ix] = np.column_stack([r * np.sin(t), r * np.cos(t)])
y[ix] = j
return X, y
def unroll_spiral(X, y, k=5, seed=0):
"""ŞEMATİK 'öğrenilen temsil': her sınıfı kendi yatay bandına taşır → lineer ayrılabilir.
Gerçek bir eğitilmiş ağ DEĞİL; 'ağ manifoldu açar' sezgisinin deterministik görseli."""
rng = np.random.default_rng(seed)
r = np.linalg.norm(X, axis=1)
yb = (y - (k - 1) / 2.0) * 0.55 + rng.normal(0, 0.045, len(y))
return np.column_stack([r * 2.2 - 1.1, yb])
# ---------------------------------------------------------------------------
# İki hilal (make_moons — sklearn YOK, numpy ile elle)
# ---------------------------------------------------------------------------
def make_moons_np(n=400, noise=0.20, seed=0):
"""Doğrusal ayrılamaz iki-hilal veri (sklearn.make_moons eşdeğeri)."""
rng = np.random.default_rng(seed)
n_out = n // 2
n_in = n - n_out
to = np.linspace(0, np.pi, n_out)
ti = np.linspace(0, np.pi, n_in)
outer = np.column_stack([np.cos(to), np.sin(to)])
inner = np.column_stack([1.0 - np.cos(ti), 1.0 - np.sin(ti) - 0.5])
X = np.vstack([outer, inner]) + rng.normal(0, noise, (n, 2))
y = np.array([0] * n_out + [1] * n_in)
return X, y
# ---------------------------------------------------------------------------
# Minik numpy sınıflandırıcılar (lineer vs ReLU MLP) — karar sınırı kontrastı
# ---------------------------------------------------------------------------
def _onehot(y, c):
Y = np.zeros((len(y), c))
Y[np.arange(len(y)), y] = 1.0
return Y
def _softmax(z):
z = z - z.max(axis=1, keepdims=True)
e = np.exp(z)
return e / e.sum(axis=1, keepdims=True)
def logreg_train(X, y, steps=500, lr=1.0, seed=0):
"""Lineer (gizli katmansız) softmax sınıflandırıcı → DÜZ karar sınırı."""
rng = np.random.default_rng(seed)
n, d = X.shape
c = int(y.max() + 1)
W = rng.normal(0, 0.01, (d, c))
b = np.zeros(c)
Y = _onehot(y, c)
for _ in range(steps):
p = _softmax(X @ W + b)
dz = (p - Y) / n
W -= lr * (X.T @ dz)
b -= lr * dz.sum(axis=0)
return dict(W=W, b=b)
def logreg_forward(params, X):
return X @ params["W"] + params["b"]
def mlp_train(X, y, hidden=16, act="relu", steps=600, lr=1.0, seed=0, return_history=False):
"""numpy 2-katmanlı MLP (afine → nonlinearite → afine), softmax + cross-entropy.
ReLU ile EĞRİ karar sınırı (hilalleri/spirali ayırır).
return_history=True → (params, loss_history) döner (eğitim eğrisi figürü için)."""
rng = np.random.default_rng(seed)
n, d = X.shape
c = int(y.max() + 1)
W1 = rng.normal(0, 1, (d, hidden)) * np.sqrt(2.0 / d)
b1 = np.zeros(hidden)
W2 = rng.normal(0, 1, (hidden, c)) * np.sqrt(2.0 / hidden)
b2 = np.zeros(c)
Y = _onehot(y, c)
history = []
for _ in range(steps):
z1 = X @ W1 + b1
a1 = np.maximum(0, z1) if act == "relu" else np.tanh(z1)
p = _softmax(a1 @ W2 + b2)
if return_history:
history.append(float(-np.log(p[np.arange(n), y] + 1e-12).mean()))
dz2 = (p - Y) / n
dW2 = a1.T @ dz2
db2 = dz2.sum(axis=0)
da1 = dz2 @ W2.T
dz1 = da1 * ((z1 > 0) if act == "relu" else (1 - np.tanh(z1) ** 2))
dW1 = X.T @ dz1
db1 = dz1.sum(axis=0)
W1 -= lr * dW1
b1 -= lr * db1
W2 -= lr * dW2
b2 -= lr * db2
params = dict(W1=W1, b1=b1, W2=W2, b2=b2, act=act)
if return_history:
return params, np.array(history)
return params
def mlp_forward(params, X):
z1 = X @ params["W1"] + params["b1"]
a1 = np.maximum(0, z1) if params["act"] == "relu" else np.tanh(z1)
return a1 @ params["W2"] + params["b2"]
def accuracy(forward_fn, params, X, y):
return float((forward_fn(params, X).argmax(axis=1) == y).mean())
def decision_grid(X, pad=0.6, h=0.02):
"""Karar bölgesi için koordinat ızgarası (xx, yy) ve düz nokta listesi."""
x0, x1 = X[:, 0].min() - pad, X[:, 0].max() + pad
y0, y1 = X[:, 1].min() - pad, X[:, 1].max() + pad
xx, yy = np.meshgrid(np.arange(x0, x1, h), np.arange(y0, y1, h))
grid = np.column_stack([xx.ravel(), yy.ravel()])
return xx, yy, grid
# ---------------------------------------------------------------------------
# Enerji manzarası (EBM — çoklu minimum = çoklu geçerli cevap)
# ---------------------------------------------------------------------------
def energy_landscape(xx, yy):
"""F(x,y): birkaç Gauss kuyusu → çoklu yerel minimum. Düşük enerji = uyumlu cevap."""
wells = [(-1.25, -0.65, 1.0, 0.55), (1.15, 0.85, 1.05, 0.6), (0.15, -1.15, 0.8, 0.45)]
F = np.ones_like(xx) * 1.0
for cx, cy, depth, width in wells:
F = F - depth * np.exp(-((xx - cx) ** 2 + (yy - cy) ** 2) / (2 * width))
return F
# ---------------------------------------------------------------------------
# Hafta 2 — gradient descent, SGD, backprop, eğitim
# ---------------------------------------------------------------------------
def loss_surface_2d(xx, yy):
"""2D parametre uzayında bir kayıp yüzeyi (gradient descent 'dağ' görseli için):
eğik, farklı-ölçekli bir vadi (konveks taban) + hafif dalgalanma."""
u = 0.85 * xx + 0.53 * yy # eksenleri döndür (eğik vadi)
v = -0.53 * xx + 0.85 * yy
return 1.0 * u ** 2 + 3.5 * v ** 2 + 0.6 * np.sin(1.5 * xx) * np.cos(1.5 * yy)
def _loss_grad_2d(p):
"""loss_surface_2d gradyanı (merkezi sonlu fark)."""
eps = 1e-4
gx = (loss_surface_2d(p[0] + eps, p[1]) - loss_surface_2d(p[0] - eps, p[1])) / (2 * eps)
gy = (loss_surface_2d(p[0], p[1] + eps) - loss_surface_2d(p[0], p[1] - eps)) / (2 * eps)
return np.array([gx, gy])
def gd_path(theta0, lr=0.08, steps=40):
"""loss_surface_2d üzerinde gradient descent yörüngesi (parametre adımları)."""
p = np.array(theta0, float)
path = [p.copy()]
for _ in range(steps):
p = p - lr * _loss_grad_2d(p)
path.append(p.copy())
return np.array(path)
def sgd_loss_curves(steps=80, seed=0):
"""Üç rejim için kayıp eğrisi (simülasyon): full-batch (gürültüsüz, düz),
mini-batch (orta gürültü), saf SGD (yüksek gürültü). Aynı yumuşak üstel düşüş."""
rng = np.random.default_rng(seed)
t = np.arange(steps)
base = 0.2 + 2.6 * np.exp(-t / 14.0)
full = base.copy()
mini = base + rng.normal(0, 0.10, steps) * np.exp(-t / 45.0)
sgd = base + rng.normal(0, 0.45, steps) * np.exp(-t / 70.0)
return t, np.clip(full, 0, None), np.clip(mini, 0, None), np.clip(sgd, 0, None)
def tanh_deriv(x):
"""tanh'ın türevi: 1 − tanh²(x). Backprop 'twiddling' figürü için."""
return 1.0 - np.tanh(x) ** 2
def ce_loss_curve(p):
"""Doğru sınıf olasılığı p için cross-entropy kaybı: −log p."""
p = np.clip(np.asarray(p, float), 1e-6, 1.0)
return -np.log(p)
def mse_loss_curve(p):
"""Doğru sınıf olasılığı p için (one-hot hedefe) MSE: (1 − p)²."""
return (1.0 - np.asarray(p, float)) ** 2
# ---------------------------------------------------------------------------
# Hafta 3 — convolution / ConvNet / doğal sinyaller
# ---------------------------------------------------------------------------
def conv_output_size(n, k, s=1):
"""Çıktı boyutu: o = ⌊(n − k)/s⌋ + 1."""
return (n - k) // s + 1
def conv1d(x, w):
"""1B cross-correlation (ML 'convolution'): out[i] = Σ_k x[i+k]·w[k].
Örnek: conv1d([1,2,3,4,5],[1,0,-1]) → [-2,-2,-2] (kenar/fark dedektörü)."""
x = np.asarray(x, float)
w = np.asarray(w, float)
n = len(x) - len(w) + 1
return np.array([np.dot(x[i:i + len(w)], w) for i in range(n)])
def conv2d(img, kernel, stride=1):
"""2B valid cross-correlation (ag öznitelik haritası üretir)."""
img = np.asarray(img, float)
k = np.asarray(kernel, float)
kh, kw = k.shape
H, W = img.shape
oh, ow = conv_output_size(H, kh, stride), conv_output_size(W, kw, stride)
out = np.zeros((oh, ow))
for i in range(oh):
for j in range(ow):
patch = img[i * stride:i * stride + kh, j * stride:j * stride + kw]
out[i, j] = np.sum(patch * k)
return out
def make_synthetic_image(n=28, seed=0):
"""Basit gri-tonlu sentetik görüntü: kare + daire (kenar tespiti net görünsün)."""
img = np.zeros((n, n))
img[5:14, 6:15] = 1.0 # dolu kare
yy, xx = np.ogrid[:n, :n]
cy, cx, r = 19, 19, 6
img[(yy - cy) ** 2 + (xx - cx) ** 2 <= r ** 2] = 0.85 # daire
img[20:22, 4:24] = 0.6 # yatay çizgi (yatay kenar için)
return img
def edge_kernels():
"""Klasik 3×3 öznitelik dedektörü kernel'leri."""
return dict(
sobel_x=np.array([[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]], float), # dikey kenar
sobel_y=np.array([[-1, -2, -1], [0, 0, 0], [1, 2, 1]], float), # yatay kenar
blur=np.ones((3, 3)) / 9.0, # bulanıklaştırma
laplace=np.array([[0, 1, 0], [1, -4, 1], [0, 1, 0]], float), # tüm kenarlar
)
def max_pool2d(img, k=2):
"""k×k max pooling (complex cell / invariance)."""
img = np.asarray(img, float)
H, W = img.shape
oh, ow = H // k, W // k
return np.array([[img[i * k:(i + 1) * k, j * k:(j + 1) * k].max() for j in range(ow)]
for i in range(oh)])
def avg_pool2d(img, k=2):
"""k×k average pooling (LeNet5 stili)."""
img = np.asarray(img, float)
H, W = img.shape
oh, ow = H // k, W // k
return np.array([[img[i * k:(i + 1) * k, j * k:(j + 1) * k].mean() for j in range(ow)]
for i in range(oh)])
# ---------------------------------------------------------------------------
# Hafta 4 — convolution cebiri (Toeplitz) + optimizasyon (κ, momentum, Adam)
# ---------------------------------------------------------------------------
def build_toeplitz(kernel, n):
"""1B convolution'ın Toeplitz matrisi: out = T @ x, T[i, i:i+k] = kernel.
conv1d(x, kernel) == build_toeplitz(kernel, len(x)) @ x (çoğu eleman sıfır = seyrek)."""
kernel = np.asarray(kernel, float)
k = len(kernel)
oh = n - k + 1
T = np.zeros((oh, n))
for i in range(oh):
T[i, i:i + k] = kernel
return T
def quad_loss(xx, yy, kappa=20.0):
"""Eğik, koşullanması κ = L/μ olan kuadratik vadi (condition number görseli).
κ büyük → uzun ince vadi → gradient descent zikzak çizer."""
a, b = 0.92, 0.39 # ~25° dönme
u = a * xx + b * yy
v = -b * xx + a * yy
return 0.5 * (1.0 * u ** 2 + kappa * v ** 2) # L=κ, μ=1
def quad_grad(p, kappa=20.0):
"""quad_loss'un analitik gradyanı."""
a, b = 0.92, 0.39
x, y = p
u = a * x + b * y
v = -b * x + a * y
gu, gv = u, kappa * v
return np.array([gu * a + gv * (-b), gu * b + gv * a])
def optimize_quad(method, theta0, lr, steps, kappa=20.0, beta=0.9, beta2=0.999, eps=1e-8):
"""quad_loss üzerinde bir optimizer'ı koştur; yörünge + kayıp geçmişi döner.
method ∈ {'gd','momentum','adam'}. Defazio'nun zikzak/sönümleme/adaptif anlatısı için."""
p = np.array(theta0, float)
path = [p.copy()]
losses = [float(quad_loss(p[0], p[1], kappa))]
m = np.zeros(2)
v = np.zeros(2)
for t in range(1, steps + 1):
g = quad_grad(p, kappa)
if method == "gd":
step = lr * g
elif method == "momentum":
m = beta * m + g # heavy ball
step = lr * m
elif method == "adam":
m = beta * m + (1 - beta) * g
v = beta2 * v + (1 - beta2) * g * g
mh = m / (1 - beta ** t) # bias correction
vh = v / (1 - beta2 ** t)
step = lr * mh / (np.sqrt(vh) + eps)
else:
raise ValueError(method)
p = p - step
path.append(p.copy())
losses.append(float(quad_loss(p[0], p[1], kappa)))
return np.array(path), np.array(losses)
# ---------------------------------------------------------------------------
# Hafta 5 — autograd worked example (Canziani)
# ---------------------------------------------------------------------------
def autograd_worked(X=None):
"""Canziani worked example: X → Y=X−2 → Z=3·Y² → a=mean(Z) (skaler).
İleri değerler + ∂a/∂xᵢ = (1/4)·3·2·(xᵢ−2) = 1.5·(xᵢ−2).
X=[1,2,3,4] için grad = [−1.5, 0, 1.5, 3] (PyTorch X.grad ile birebir)."""
if X is None:
X = np.array([1.0, 2.0, 3.0, 4.0])
X = np.asarray(X, float)
Y = X - 2.0
Z = 3.0 * Y ** 2
a = float(Z.mean())
grad = 1.5 * (X - 2.0)
return dict(X=X, Y=Y, Z=Z, a=a, grad=grad)
def conv_pad_output(n, k, padding=0, stride=1):
"""Padding'li çıktı boyutu: o = ⌊(n + 2·padding − k)/stride⌋ + 1.
padding=(k−1)/2 (k tek) → çıktı boyutu korunur."""
return (n + 2 * padding - k) // stride + 1
# ---------------------------------------------------------------------------
# Hafta 6 — RNN / vanishing gradient / attention
# ---------------------------------------------------------------------------
def vanishing_demo(steps=50, eigenvalues=(0.5, 1.0, 1.5)):
"""Aynı matristen n kez geçen gradient: λⁿ. λ<1 söner (vanishing), λ>1 patlar
(exploding), λ=1 sabit. Backprop-through-time vanishing gradient görseli."""
t = np.arange(steps + 1)
curves = {ev: np.asarray(ev, float) ** t for ev in eigenvalues}
return t, curves
def rnn_forward(X_seq, Wx, Wz, b, z0=None):
"""Basit RNN hücresi: zₜ = tanh(Wₓ·xₜ + W_z·zₜ₋₁ + b).
X_seq [T, d_in] → gizli durumlar Z [T, d_h] (aynı Wₓ,W_z her adımda = paylaşım)."""
X_seq = np.asarray(X_seq, float)
T = len(X_seq)
dh = np.asarray(Wz).shape[0]
z = np.zeros(dh) if z0 is None else np.asarray(z0, float)
Z = []
for t in range(T):
z = np.tanh(np.asarray(Wx) @ X_seq[t] + np.asarray(Wz) @ z + np.asarray(b))
Z.append(z.copy())
return np.array(Z)
def attention_weights(scores):
"""Attention ağırlıkları = softmax(skorlar); toplamı 1 (olasılık dağılımı, 'odak')."""
scores = np.asarray(scores, float)
e = np.exp(scores - scores.max())
return e / e.sum()
# ---------------------------------------------------------------------------
# Hafta 7 — EBM / autoencoder / manifold
# ---------------------------------------------------------------------------
def energy_1d(y, data_points=(-2.0, 0.0, 2.0), width=0.35):
"""1B enerji F(y): veri noktalarında düşük (çukur), aralarda yüksek (tepe).
'İyi enerji şekli' = veride düşük, dışında yüksek (EBM); çoklu minimum = çoklu cevap."""
y = np.asarray(y, float)
F = np.ones_like(y)
for d in data_points:
F = F - np.exp(-(y - d) ** 2 / (2 * width))
return F
def energy_1d_grad(y, data_points=(-2.0, 0.0, 2.0), width=0.35):
"""energy_1d'nin türevi (çıkarım = enerji minimizasyonu, gradient ile arama)."""
y = np.asarray(y, float)
g = np.zeros_like(y)
for d in data_points:
g = g + (y - d) / width * np.exp(-(y - d) ** 2 / (2 * width))
return g
def make_manifold_curve(n=160, noise=0.0, seed=0):
"""2B veri manifoldu: bir eğri (sinüs yayı) üzerinde noktalar (Hafta 1 manifold hipotezi)."""
t = np.linspace(-2.6, 2.6, n)
M = np.column_stack([t, np.sin(1.3 * t)])
if noise:
M = M + np.random.default_rng(seed).normal(0, noise, M.shape)
return M
def project_to_manifold(points, manifold):
"""Her noktayı manifoldun EN YAKIN noktasına çeker (autoencoder reconstruction sezgisi:
off-manifold girdiyi manifolda geri çek → düşük enerji)."""
points = np.asarray(points, float)
proj = np.array([manifold[np.linalg.norm(manifold - p, axis=1).argmin()] for p in points])
return proj
# ---------------------------------------------------------------------------
# Hafta 8 — contrastive / VAE
# ---------------------------------------------------------------------------
def vae_reparam(mu, sigma, n=400, seed=0):
"""Reparameterization trick: z = μ + σ⊙ε, ε~N(0,I). Düzenli (sürekli, dolu)
Gaussian latent örnekleri — VAE'nin üretken latent uzayı."""
mu = np.atleast_1d(np.asarray(mu, float))
sigma = np.atleast_1d(np.asarray(sigma, float))
rng = np.random.default_rng(seed)
eps = rng.normal(0, 1, (n, len(mu)))
return mu + sigma * eps
def ae_latent_clusters(n=400, seed=0):
"""Sıradan AE latent'i: dağınık, boşluklu kümeler (düzensiz uzay — VAE kontrastı)."""
rng = np.random.default_rng(seed)
centers = np.array([[-2.2, 1.8], [2.0, 2.1], [0.3, -2.3], [-1.8, -1.5]])
pts = []
for c in centers:
pts.append(c + rng.normal(0, 0.28, (n // len(centers), 2)))
return np.vstack(pts)
# ===========================================================================
# _viz.py — NYU Violet + gold matplotlib stil sabitleri ve yardımcıları
# ===========================================================================
COL_VIOLET = "#57068c" # NYU Violet — birincil çizgi/çerçeve/vurgu
COL_VIOLET_D = "#3d0463" # koyu violet — güçlü vurgu / gradyan
COL_VIOLET_M = "#7b2cbf" # orta violet — ikincil
COL_VIOLET_SOFT = "#b56ad6" # soluk violet
COL_GOLD = "#d4a017" # gold accent
COL_GOLD_D = "#a87d0a" # koyu gold
COL_TEXT = "#2a2535" # gövde metni (hafif violet tint)
COL_INK = "#1e1a2e" # en koyu — başlık
COL_BG = "#f4eefa" # açık violet — dolgu/arka plan
COL_GRID = "#cdbbe0" # soluk violet — ızgara/pasif kenar
COL_WHITE = "#ffffff"
# 5-sınıf kategorik palet (spiral 5 kol / moons ilk 2) — violet↔gold ekseni, tema-uyumlu
CLASS_COLORS = ["#57068c", "#7b2cbf", "#b56ad6", "#d4a017", "#a87d0a"]
# Çizgi-grafik tutarlı renkler
LINE_PRIMARY = COL_VIOLET
LINE_ACCENT = COL_GOLD
LINE_SECONDARY = COL_VIOLET_M
def apply_style(ax):
"""Bir eksene tutarlı NYU Violet+gold görünümü uygular."""
ax.set_facecolor(COL_WHITE)
ax.grid(True, alpha=0.25, color=COL_GRID, linewidth=0.8)
for spine in ax.spines.values():
spine.set_color(COL_GRID)
ax.tick_params(colors=COL_TEXT)
ax.title.set_color(COL_TEXT)
ax.xaxis.label.set_color(COL_TEXT)
ax.yaxis.label.set_color(COL_TEXT)
return ax
def draw_pipeline(ax, stages, title=None, y0=0.0):
"""Soldan-sağa kutu+ok boru hattı şeması (örüntü tanıma vs uçtan-uca).
stages : [(label:str, is_learned:bool), ...]
is_learned=True -> öğrenilen modül (violet dolgulu)
is_learned=False -> elle-tasarlanan/sabit (gold kenarlı, açık dolgu)
"""
n = len(stages)
box_w, box_h, gap = 1.7, 1.0, 0.9
step = box_w + gap
ax.set_xlim(-0.3, n * step)
ax.set_ylim(y0 - 1.1, y0 + 1.1)
ax.axis("off")
if title:
ax.set_title(title, color=COL_TEXT, fontsize=12, pad=10)
for i, (lbl, learned) in enumerate(stages):
x = i * step
fc = "#ece0f7" if learned else COL_BG
ec = COL_VIOLET if learned else COL_GOLD_D
lw = 2.4 if learned else 2.0
box = FancyBboxPatch(
(x, y0 - box_h / 2), box_w, box_h,
boxstyle="round,pad=0.02,rounding_size=0.12",
fc=fc, ec=ec, lw=lw, zorder=2,
)
ax.add_patch(box)
ax.text(x + box_w / 2, y0, lbl, ha="center", va="center",
fontsize=9.5, color=COL_TEXT, zorder=3, wrap=True)
if i > 0:
ax.add_patch(FancyArrowPatch(
(x - gap, y0), (x, y0),
arrowstyle="-|>", mutation_scale=16,
color=COL_VIOLET_M, lw=1.9, zorder=1,
))
return ax
def style_legend(ax, **kw):
"""Tema-uyumlu legend."""
leg = ax.legend(frameon=True, framealpha=0.95, edgecolor=COL_GRID, **kw)
if leg is not None:
leg.get_frame().set_facecolor(COL_WHITE)
for t in leg.get_texts():
t.set_color(COL_TEXT)
return leg
```
## Bu Derste Ne Var? {#sec-genel-bakis-d8}
Hafta 7'de EBM'nin merkezi zorluğunu bırakmıştık: enerji fonksiyonunu **veride düşük, dışında yüksek** yapmak kolay değil — özellikle "dışarıyı yükseltmek" zor. Bu hafta **Yann LeCun** bu sorunun iki ailesini çözüyor: **contrastive (karşıtsal) yöntemler** (negatif örneklerle dışarıyı it) ve **architectural/non-contrastive** yöntemler (mimariyle enerjiyi kısıtla). **Alfredo Canziani** ise bu ikinci ailenin en zarif örneğini gösteriyor: **VAE (değişimsel autoencoder)**.
LeCun'un çerçevesi: EBM'yi eğitmek = enerji fonksiyonunu şekillendirmek. İki yol var: (1) **contrastive** — veride enerjiyi düşür, üretilmiş "negatif" örneklerde yükselt (push down/push up). (2) **non-contrastive** — modelin yapısını öyle kısıtla ki düşük-enerji bölgesinin "hacmi" sınırlı kalsın (negatif örnek üretmeden). VAE ikinci aileden: latent uzayı düzenli (Gaussian) tutarak enerjiyi kısıtlar.
Bu haftanın üç ana fikri:
1. **EBM eğitimi iki sınıf:** contrastive (negatif it) vs non-contrastive/architectural (yapıyla kısıtla).
2. **Contrastive SSL:** pozitif çiftleri (uyumlu) aşağı, negatif çiftleri (rastgele) yukarı bas; ama **yüksek boyutta gittikçe daha çok negatif gerekir** (sınır).
3. **VAE:** encoder bir kod değil, bir **dağılım** (ortalama + varyans) üretir; latent'ten örneklenir — düzenli, üretken bir latent uzay.
```{mermaid}
%%| echo: false
flowchart TB
Egitim["EBM eğitimi = enerji şekillendirme<br/>(Hafta 7'nin merkezi zorluğu)"]
subgraph Contrastive["(A) Contrastive (karşıt örnekli)"]
direction TB
PushDownUp["Push down (veri) / push up (negatif)<br/>= maximum likelihood"]
Pozitif["Pozitif çift = augmentation<br/>(kırp + renk)"]
DAE["Denoising AE = maskeleme<br/>(BERT fikri, Hafta 12)"]
Sinir["SINIR: yüksek boyutta çok negatif<br/>→ non-contrastive (post-2020, KURSTA YOK)"]
PushDownUp --> Pozitif
Pozitif --> DAE
DAE --> Sinir
end
subgraph NonContrastive["(B) Non-contrastive (mimari kısıtlı)"]
direction TB
VAE["VAE = olasılıksal autoencoder"]
Dagilim["Encoder bir DAĞILIM üretir<br/>ortalama μ + varyans σ"]
Reparam["Reparameterization<br/>z = μ + σ⊙ε"]
Latent["Düzenli, üretken latent<br/>(Gaussian kısıt)"]
VAE --> Dagilim
Dagilim --> Reparam
Reparam --> Latent
end
Egitim --> PushDownUp
Egitim --> VAE
Sinir -. "negatifsiz çözüm arayışı" .-> VAE
```
::: {.callout-tip title="Builder Notu — İki Şekillendirme Yolu"}
**Geriye (önkoşul kurslar):**
- **Push down/push up = enerji şekillendirme** → Hafta 7 EBM (veride düşük/dışında yüksek) + Hafta 1 cross-entropy (negatif örnek = uyumsuz).
- **VAE latent = Gaussian** → Stat 110 çok-değişkenli normal (mean + diagonal variance) + Hafta 7 latent EBM.
- **Reparameterization** → Calculus zincir kuralı (örneklemeyi türevlenebilir yapmak).
**İleriye (production / research):**
- Contrastive SSL → SimCLR/MoCo (MoCo kursta var); "çok negatif" sorunu → **post-2020 non-contrastive (BYOL, VICReg) — kursta YOK** (Hafta 10 İleriye Köprü).
- VAE → diffusion modelleri (Hafta 9), üretken modellerin olasılıksal latent'i.
**Tek cümleyle:** EBM'yi eğitmek enerjiyi şekillendirmektir — contrastive yöntemler veriyi aşağı/negatifi yukarı iter, non-contrastive yöntemler (VAE gibi) yapıyla kısıtlar; VAE, latent'i bir Gaussian dağılıma oturtarak düzenli, üretken bir uzay kurar.
:::
## (LeCun) EBM Eğitimi: İki Sınıf {#sec-iki-sinif}
LeCun, Hafta 7'nin sorusunu — "enerjiyi nasıl şekillendiririz?" — iki temel aileye ayırıyor:
> "the first class is contrastive methods, which consist in basically pushing down [on the energy of data points and pushing up elsewhere]... the other [class is architectural methods]." — LeCun, 5:20 / 6:46
1. **Contrastive yöntemler:** Veri noktalarında enerjiyi **aşağı bas**; üretilmiş başka noktalarda **yukarı bas**. Bir noktayı aşağı basınca gerisi göreli olarak yükselir, ama bu yeterli olmayabilir — bu yüzden aktif olarak negatif noktalarda yukarı basılır.
2. **Architectural / non-contrastive yöntemler:** Modelin **yapısını** öyle kısıtla ki düşük-enerji bölgesinin "hacmi" sınırlı kalsın — negatif örnek üretmeden (örn. bottleneck, sparse coding, VAE).
LeCun bir liste veriyor: contrastive divergence, metric learning, noise contrastive estimation, ratio matching, minimum probability flow — hepsi contrastive ailesinden. @fig-two-classes bu iki yolu yan yana koyuyor: solda contrastive (veriyi aşağı, negatifi yukarı it), sağda non-contrastive (negatif örnek yok, düşük-enerji hacmini yapıyla — bir darboğazla — sınırla).
```{python}
#| label: fig-two-classes
#| fig-cap: "EBM eğitiminde iki yol. SOL (contrastive): enerji eğrisi üzerinde veri (pozitif) noktasını aşağı iten ve rastgele (negatif) noktayı yukarı iten karşıt kuvvetler. SAĞ (non-contrastive / mimari kısıtlı): negatif örnek kullanılmadan, düşük-enerji hacmi darboğaz (bottleneck) gibi mimari kısıtlarla sınırlanır. İki yol: negatif örneği it vs enerjiyi yapıyla kısıtla."
fig, (axL, axR) = plt.subplots(1, 2, figsize=(11, 4.8))
y = np.linspace(-3.4, 3.4, 400)
data_points = (-2.0, 0.0, 2.0)
F = energy_1d(y, data_points=data_points, width=0.35)
# SOL panel — Contrastive: enerjiyi veride aşağı it, negatifte yukarı it
apply_style(axL)
axL.plot(y, F, color=COL_VIOLET, lw=2.6, zorder=3)
axL.fill_between(y, F, F.max() + 0.15, color=COL_BG, alpha=0.5, zorder=1)
y_pos = 0.0
F_pos = energy_1d(np.array([y_pos]), data_points=data_points, width=0.35)[0]
axL.scatter([y_pos], [F_pos], s=130, color=COL_VIOLET_D, zorder=6,
edgecolor=COL_WHITE, linewidth=1.4, label="veri (pozitif)")
axL.add_patch(FancyArrowPatch(
(y_pos, F_pos + 0.85), (y_pos, F_pos + 0.12),
arrowstyle="-|>", mutation_scale=20, color=COL_VIOLET, lw=2.6, zorder=7))
axL.text(y_pos, F_pos + 1.02, "aşağı it", ha="center", va="bottom",
fontsize=10, color=COL_VIOLET_D, fontweight="bold")
y_neg = -1.0
F_neg = energy_1d(np.array([y_neg]), data_points=data_points, width=0.35)[0]
axL.scatter([y_neg], [F_neg], s=130, color=COL_GOLD, zorder=6,
edgecolor=COL_WHITE, linewidth=1.4, label="rastgele (negatif)")
axL.add_patch(FancyArrowPatch(
(y_neg, F_neg - 0.78), (y_neg, F_neg - 0.05),
arrowstyle="-|>", mutation_scale=20, color=COL_GOLD_D, lw=2.6, zorder=7))
axL.text(y_neg, F_neg - 0.95, "yukarı it", ha="center", va="top",
fontsize=10, color=COL_GOLD_D, fontweight="bold")
axL.set_title("Contrastive (karşıt örnekli)", color=COL_INK, fontsize=12.5, fontweight="bold")
axL.set_xlabel("ÿ (örnek uzayı)", fontsize=10)
axL.set_ylabel("enerji F(ÿ)", fontsize=10)
axL.set_ylim(F.min() - 1.2, F.max() + 0.6)
style_legend(axL, loc="lower right", fontsize=8.5)
# SAĞ panel — Non-contrastive (architectural): yapıyla kısıtla, negatif yok
apply_style(axR)
axR.plot(y, F, color=COL_VIOLET, lw=2.6, zorder=3, alpha=0.55)
low_mask = F < (F.min() + 0.45)
axR.fill_between(y, F.min() - 1.2, F.max() + 0.6, where=low_mask,
color=COL_GOLD, alpha=0.16, zorder=1)
for d in data_points:
Fd = energy_1d(np.array([d]), data_points=data_points, width=0.35)[0]
axR.scatter([d], [Fd], s=110, color=COL_VIOLET_D, zorder=6,
edgecolor=COL_WHITE, linewidth=1.4)
# bottleneck / kısıt şeması — küçük boru hattı kutucukları (kısıtlı kapasite)
bx, by = -3.0, F.max() + 0.16
bh = 0.46
labels = ["giriş", "darboğaz", "çıkış"]
widths = [1.25, 1.05, 1.25]
heights = [bh, bh * 0.62, bh]
xc = bx
centers = []
for lbl, w, h in zip(labels, widths, heights):
fc = "#ece0f7" if lbl != "darboğaz" else COL_GOLD
ec = COL_VIOLET if lbl != "darboğaz" else COL_GOLD_D
box = FancyBboxPatch((xc, by - h / 2), w, h,
boxstyle="round,pad=0.02,rounding_size=0.06",
fc=fc, ec=ec, lw=2.0, zorder=8,
transform=axR.transData, clip_on=False)
axR.add_patch(box)
txt_col = COL_GOLD_D if lbl == "darboğaz" else COL_TEXT
axR.text(xc + w / 2, by, lbl, ha="center", va="center",
fontsize=7.5, color=txt_col, zorder=9, fontweight="bold")
centers.append((xc, xc + w))
xc += w + 0.36
for (l0, r0), (l1, r1) in zip(centers[:-1], centers[1:]):
axR.add_patch(FancyArrowPatch((r0, by), (l1, by),
arrowstyle="-|>", mutation_scale=12,
color=COL_VIOLET_M, lw=1.6, zorder=8, clip_on=False))
axR.text(0.0, F.min() - 0.55, "düşük-enerji hacmi\nyapıyla SINIRLI",
ha="center", va="center", fontsize=9, color=COL_GOLD_D, fontweight="bold")
axR.set_title("Non-contrastive (mimari kısıtlı)", color=COL_INK, fontsize=12.5, fontweight="bold")
axR.set_xlabel("ÿ (örnek uzayı)", fontsize=10)
axR.set_ylabel("enerji F(ÿ)", fontsize=10)
axR.set_ylim(F.min() - 1.2, F.max() + 0.6)
fig.suptitle("İki yol: negatif örneği it vs enerjiyi yapıyla kısıtla",
color=COL_INK, fontsize=13.5, fontweight="bold", y=1.02)
fig.tight_layout()
```
::: {.callout-tip title="Builder Notu — İki Sınıf = SSL Haritası"}
**Geriye (Hafta 7):** Bu, Hafta 7 Egzersiz 5'in cevabıdır: yalnızca veride enerji düşürürsen model çöker (her yer düşer); contrastive yöntem negatifte yukarı basarak bunu önler.
**İleriye:** İki sınıf ayrımı, modern SSL'in (Hafta 10) tüm haritasıdır: contrastive (SimCLR, MoCo) vs non-contrastive (BYOL, VICReg — post-2020, kursta yok).
:::
## (LeCun) Contrastive Yöntemler: Aşağı Bas, Yukarı Bas {#sec-push-down-up}
Contrastive yöntemin mekaniği: enerji fonksiyonunu, gerçek veride (pozitif) aşağı, üretilmiş uyumsuz örneklerde (negatif) yukarı iterek şekillendir.
> "push down on the energy [of data points]... and you have to generate random negative samples [and push up on their energy]." — LeCun, 12:38
Akıllı bir ayrıntı: bir negatif örneğin enerjisi ne kadar **düşükse** (yani model onu yanlışlıkla "iyi" sanıyorsa), o kadar **sert** yukarı basılır. Bu, modelin en çok karıştırdığı yerleri düzeltir.
Enerjiyi olasılığa çevirirsen (energy = −log p, Stat 110), bu push-down/push-up tam olarak maximum likelihood'un yaptığı şeydir: gerçek veriye olasılık ver, geri kalandan olasılık çek. @fig-push-down-up bu mekaniği iki aşamada gösterir: önce düz/şekillenmemiş bir enerji, sonra veride çukur (aşağı it) ve negatifte tepe (yukarı it) ile şekillenmiş enerji.
```{python}
#| label: fig-push-down-up
#| fig-cap: "Kontrastif mekaniğin iki aşaması: enerji manzarasını şekillendirme. Üst panel başlangıç durumu — düz/şekillenmemiş enerji eğrisi; veri ile dış uzay henüz ayrışmamış, her yer benzer enerjide. Alt panel şekillenmiş durum — energy_1d(y, (−2,0,2)) ile veri noktalarında çukur (violet, aşağı ok = push down) ve aralarda/negatifte tepe (gold, yukarı ok = push up). Düşük-enerjili negatife daha sert basılır; push down/up = maximum likelihood (enerji = −log p)."
data_points = (-2.0, 0.0, 2.0)
y = np.linspace(-3.4, 3.4, 600)
# UST: 'baslangic' — duz/bozuk enerji egrisi (henuz sekillenmemis)
E_flat = 0.10 * y**2 + 0.18 * np.sin(2.1 * y) + 0.55
# ALT: 'sekillenmis' — veri noktalarinda cukur, aralarda tepe
E_shaped = energy_1d(y, data_points)
fig, (ax_top, ax_bot) = plt.subplots(
2, 1, figsize=(9.5, 6.4), sharex=True,
gridspec_kw={"hspace": 0.28},
)
# --- UST PANEL: baslangic ---
apply_style(ax_top)
ax_top.plot(y, E_flat, color=COL_VIOLET_M, lw=2.4, alpha=0.9)
ax_top.set_title("1. Aşama — Başlangıç: düz / şekillenmemiş enerji",
color=COL_INK, fontsize=12.5, pad=8)
ax_top.set_ylabel("enerji F(y)", fontsize=10.5)
ax_top.set_ylim(-0.15, 1.25)
ax_top.annotate(
"Veri ile dış uzay\nayrışmamış —\nher yer benzer enerjide",
xy=(0.0, E_flat[np.argmin(np.abs(y - 0.0))]), xytext=(1.55, 0.95),
fontsize=9.0, color=COL_TEXT, ha="left", va="center",
bbox=dict(boxstyle="round,pad=0.35", fc=COL_BG, ec=COL_GRID, lw=1.0),
)
# --- ALT PANEL: sekillenmis ---
apply_style(ax_bot)
ax_bot.plot(y, E_shaped, color=COL_VIOLET, lw=2.6, zorder=3)
ax_bot.set_title("2. Aşama — Şekillenmiş: veride çukur (push down), arada tepe (push up)",
color=COL_INK, fontsize=12.5, pad=8)
ax_bot.set_xlabel("y (örnek uzayı)", fontsize=10.5)
ax_bot.set_ylabel("enerji F(y)", fontsize=10.5)
ax_bot.set_ylim(-1.35, 1.15)
# Veri noktalari: CUKUR (violet) + asagi ok = PUSH DOWN
for d in data_points:
fd = energy_1d(np.array([d]), data_points)[0]
ax_bot.scatter([d], [fd], s=80, color=COL_VIOLET, ec=COL_WHITE,
lw=1.4, zorder=5)
ax_bot.annotate(
"", xy=(d, fd + 0.06), xytext=(d, fd + 0.62),
arrowprops=dict(arrowstyle="-|>", color=COL_VIOLET_D, lw=2.4,
mutation_scale=16),
zorder=4,
)
ax_bot.text(data_points[0] - 0.18, energy_1d(np.array([data_points[0]]), data_points)[0] + 0.66,
"push down\n(veri)", ha="center", va="bottom", fontsize=8.8,
color=COL_VIOLET_D, fontweight="bold")
# Tepe noktalari: negatif/aralarda (gold) + yukari ok = PUSH UP
peak_xs = [-3.0, -1.0, 1.0, 3.0]
for px in peak_xs:
fp = E_shaped[np.argmin(np.abs(y - px))]
ax_bot.scatter([px], [fp], s=72, color=COL_GOLD, ec=COL_WHITE,
lw=1.4, zorder=5, marker="X")
ax_bot.annotate(
"", xy=(px, fp - 0.06), xytext=(px, fp - 0.50),
arrowprops=dict(arrowstyle="-|>", color=COL_GOLD_D, lw=2.4,
mutation_scale=16),
zorder=4,
)
ax_bot.text(peak_xs[-1], E_shaped[np.argmin(np.abs(y - peak_xs[-1]))] - 0.58,
"push up\n(negatif)", ha="center", va="top", fontsize=8.8,
color=COL_GOLD_D, fontweight="bold")
# Ana annotation: kontrastif sezgi
ax_bot.annotate(
"Düşük-enerjili negatife daha SERT bas;\n"
"push down/up = maximum likelihood (enerji = −log p)",
xy=(0.0, energy_1d(np.array([0.0]), data_points)[0]),
xytext=(-0.05, 0.97),
fontsize=9.2, color=COL_TEXT, ha="center", va="center",
bbox=dict(boxstyle="round,pad=0.4", fc=COL_WHITE, ec=COL_VIOLET, lw=1.4),
)
fig.suptitle("Kontrastif mekanik: enerji manzarasını şekillendirmenin 2 aşaması",
color=COL_INK, fontsize=13.5, y=0.99);
```
::: {.callout-tip title="Builder Notu — Push Down/Up = MLE"}
**Geriye (Hafta 1 + Stat 110):** Push-down/push-up, Hafta 1'in cross-entropy'sinin EBM dilidir: doğru sınıfa (pozitif) olasılık ver, yanlışlardan (negatif) çek; maximum likelihood (Stat 110).
**İleriye:** "En düşük-enerjili negatife en sert bas" fikri, hard-negative mining'in temelidir; modern retrieval ve contrastive learning'de kritik.
:::
## (LeCun) Contrastive SSL: Pozitif/Negatif Çiftler {#sec-contrastive-ssl}
LeCun contrastive fikrini **öz-denetimli öğrenmeye (SSL)** uyguluyor. Etiket olmadan, veriden **pozitif çiftler** (uyumlu) ve **negatif çiftler** (uyumsuz) üretirsin:
- **Pozitif çift:** bir örneği al, **bozarak/dönüştürerek** (data augmentation: kırp, döndür, renk değiştir) ikinci bir versiyonunu üret — ikisi "aynı şey" sayılır, enerjileri aşağı basılır.
- **Negatif çift:** rastgele iki farklı örnek — uyumsuz, enerjileri yukarı basılır.
> "the energy function for similar pairs [is pushed down], push up on the energy function for dissimilar pairs." — LeCun, 13:22
Hafta 3'te gördüğümüz **PIRL** (Ishan Misra, Hafta 10'da derinlemesine) bu yaklaşımı kullanır; kullandığı amaç fonksiyonu **noise contrastive estimation**'dır. Kursta ayrıca **MoCo** (momentum contrast) da geçer. @fig-contrastive-ssl bu çift kurgusunu somutlaştırır: aynı görüntünün iki augmentation'ı pozitif çift (enerji aşağı), rastgele başka bir görüntü negatif (enerji yukarı).
```{python}
#| label: fig-contrastive-ssl
#| fig-cap: "Contrastive SSL: aynı görüntünün iki augmentation'ı (kırpma + renk) pozitif çift oluşturur ve enerjisi aşağı çekilir; rastgele başka bir görüntü (köpek) negatif çifttir, enerjisi yukarı itilir. PIRL, NCE ve MoCo bu pozitif/negatif kontrastı üzerine kuruludur."
np.random.seed(0)
# Contrastive SSL — pozitif / negatif çift şeması (Bölüm 3)
fig, ax = plt.subplots(figsize=(10.5, 5.6))
ax.set_xlim(0, 10.5)
ax.set_ylim(0, 5.6)
ax.axis("off")
ax.set_facecolor(COL_WHITE)
fig.patch.set_facecolor(COL_WHITE)
ax.set_title("Contrastive SSL — pozitif çift birbirini çeker, negatif çift iter",
color=COL_INK, fontsize=14, fontweight="bold", pad=14)
# --- yardımcı: minik "kedi" ikonu (kulaklı yüz) ---
def draw_cat(ax, cx, cy, r, face, ear, line):
for dx in (-0.62, 0.62):
ax.add_patch(Polygon(
[(cx + dx * r - 0.30 * r, cy + 0.55 * r),
(cx + dx * r + 0.30 * r, cy + 0.55 * r),
(cx + dx * r, cy + 1.15 * r)],
closed=True, fc=face, ec=line, lw=1.6, zorder=4))
ax.add_patch(Circle((cx, cy), r, fc=face, ec=line, lw=1.8, zorder=5))
for dx in (-0.38, 0.38):
ax.add_patch(Circle((cx + dx * r, cy + 0.18 * r), 0.10 * r, fc=line, ec=line, zorder=6))
ax.add_patch(Polygon(
[(cx - 0.12 * r, cy - 0.05 * r), (cx + 0.12 * r, cy - 0.05 * r), (cx, cy - 0.22 * r)],
closed=True, fc=ear, ec=line, lw=0.8, zorder=6))
for s in (-1, 1):
for dy in (-0.10, 0.05):
ax.plot([cx + s * 0.15 * r, cx + s * 0.85 * r],
[cy - 0.05 * r + dy * r, cy - 0.05 * r + dy * r],
color=line, lw=0.9, zorder=6)
# --- yardımcı: minik "köpek" ikonu (sarkık kulaklı yüz) ---
def draw_dog(ax, cx, cy, r, face, ear, line):
for dx in (-1, 1):
ax.add_patch(Ellipse(
(cx + dx * 0.78 * r, cy + 0.05 * r), 0.42 * r, 0.95 * r,
angle=dx * 18, fc=ear, ec=line, lw=1.6, zorder=4))
ax.add_patch(Circle((cx, cy), r, fc=face, ec=line, lw=1.8, zorder=5))
for dx in (-0.38, 0.38):
ax.add_patch(Circle((cx + dx * r, cy + 0.20 * r), 0.10 * r, fc=line, ec=line, zorder=6))
ax.add_patch(Circle((cx, cy - 0.18 * r), 0.16 * r, fc=line, ec=line, zorder=6))
def chip(ax, cx, cy, w, h, fc, ec, lw=2.0):
ax.add_patch(FancyBboxPatch(
(cx - w / 2, cy - h / 2), w, h,
boxstyle="round,pad=0.02,rounding_size=0.10",
fc=fc, ec=ec, lw=lw, zorder=2))
VIOLET_FILL = "#ece0f7"
GOLD_FILL = "#f7edd0"
# SOL: kaynak görüntü (kedi)
src_x, src_y = 1.55, 3.2
chip(ax, src_x, src_y, 1.7, 1.7, VIOLET_FILL, COL_VIOLET, lw=2.4)
draw_cat(ax, src_x, src_y + 0.05, 0.42, COL_VIOLET_SOFT, COL_GOLD, COL_INK)
ax.text(src_x, src_y - 1.18, "girdi görüntü", ha="center", va="center",
fontsize=10, color=COL_TEXT, fontweight="bold")
# ÜST DAL: augment 1 (kırpılmış kedi)
a1_x, a1_y = 4.7, 4.55
chip(ax, a1_x, a1_y, 1.55, 1.4, VIOLET_FILL, COL_VIOLET, lw=2.2)
ax.add_patch(Rectangle((a1_x - 0.42, a1_y - 0.42), 0.84, 0.84,
fill=False, ec=COL_VIOLET_M, lw=1.3, ls=(0, (3, 2)), zorder=4))
draw_cat(ax, a1_x, a1_y + 0.03, 0.30, COL_VIOLET_SOFT, COL_GOLD, COL_INK)
ax.text(a1_x, a1_y - 0.95, "augment₁ (kırpma)", ha="center", va="center",
fontsize=9.5, color=COL_VIOLET_D, fontweight="bold")
# ORTA DAL: augment 2 (renk-değişik kedi)
a2_x, a2_y = 4.7, 2.55
chip(ax, a2_x, a2_y, 1.55, 1.4, VIOLET_FILL, COL_VIOLET, lw=2.2)
draw_cat(ax, a2_x, a2_y + 0.03, 0.40, COL_GOLD, COL_VIOLET_M, COL_INK)
ax.text(a2_x, a2_y - 0.92, "augment₂ (renk)", ha="center", va="center",
fontsize=9.5, color=COL_VIOLET_D, fontweight="bold")
# kaynaktan iki augment'e oklar (violet)
for (tx, ty) in [(a1_x - 0.80, a1_y), (a2_x - 0.80, a2_y)]:
ax.add_patch(FancyArrowPatch(
(src_x + 0.88, src_y + (0.35 if ty > src_y else -0.35)),
(tx, ty), arrowstyle="-|>", mutation_scale=16,
color=COL_VIOLET_M, lw=1.8,
connectionstyle="arc3,rad=" + ("-0.18" if ty > src_y else "0.18"), zorder=1))
# POZİTİF ÇİFT bağlantısı (violet, "çek / enerji AŞAĞI")
pc_x = a1_x - 1.05
mid_y = (a1_y + a2_y) / 2
ax.add_patch(FancyArrowPatch(
(pc_x, a1_y - 0.55), (pc_x, a2_y + 0.55),
arrowstyle="<|-|>", mutation_scale=15,
color=COL_VIOLET, lw=3.0,
connectionstyle="arc3,rad=-0.35", zorder=3))
ax.add_patch(FancyBboxPatch((pc_x - 0.62, mid_y - 0.23), 0.52, 0.46,
boxstyle="round,pad=0.02,rounding_size=0.10",
fc=COL_VIOLET, ec=COL_VIOLET_D, lw=1.0, zorder=4))
ax.text(pc_x - 0.36, mid_y, "↓", ha="center", va="center",
fontsize=15, color=COL_WHITE, fontweight="bold", zorder=5)
ax.text(pc_x - 0.34, mid_y - 0.92, "POZİTİF çift\naynı → enerji ↓",
ha="center", va="center", fontsize=10.5, color=COL_VIOLET_D, fontweight="bold")
# SAĞ: rastgele başka görüntü (köpek) = NEGATİF
neg_x, neg_y = 8.85, 3.55
chip(ax, neg_x, neg_y, 1.7, 1.55, GOLD_FILL, COL_GOLD_D, lw=2.4)
draw_dog(ax, neg_x, neg_y + 0.02, 0.40, "#f3e3bf", COL_GOLD, COL_INK)
ax.text(neg_x, neg_y - 1.05, "rastgele görüntü", ha="center", va="center",
fontsize=10, color=COL_GOLD_D, fontweight="bold")
# NEGATİF çift bağlantısı (gold, "it / enerji YUKARI")
ax.add_patch(FancyArrowPatch(
(a2_x + 0.80, a2_y + 0.10), (neg_x - 0.88, neg_y - 0.30),
arrowstyle="<|-|>", mutation_scale=15,
color=COL_GOLD, lw=2.6, ls="--",
connectionstyle="arc3,rad=-0.18", zorder=2))
neg_mid_x = (a2_x + neg_x) / 2 + 0.10
neg_mid_y = (a2_y + neg_y) / 2 - 0.10
ax.text(neg_mid_x, neg_mid_y + 0.62, "NEGATİF çift\nfarklı → enerji ↑",
ha="center", va="center", fontsize=10.5, color=COL_GOLD_D, fontweight="bold")
badge_x, badge_y = neg_mid_x, neg_mid_y - 0.05
ax.add_patch(FancyBboxPatch((badge_x - 0.26, badge_y - 0.23), 0.52, 0.46,
boxstyle="round,pad=0.02,rounding_size=0.10",
fc=COL_GOLD, ec=COL_GOLD_D, lw=1.0, zorder=4))
ax.text(badge_x, badge_y, "↑", ha="center", va="center",
fontsize=15, color=COL_INK, fontweight="bold", zorder=5)
# ALT ANNOTATION
ax.add_patch(FancyBboxPatch((0.45, 0.18), 9.6, 0.78,
boxstyle="round,pad=0.02,rounding_size=0.10",
fc=COL_BG, ec=COL_GRID, lw=1.2, zorder=1))
ax.text(5.25, 0.57,
"augment = pozitif (aynı örnek, iki görünüm) · rastgele = negatif · "
"yöntemler: PIRL, NCE, MoCo",
ha="center", va="center", fontsize=10, color=COL_TEXT, fontweight="bold")
plt.tight_layout()
plt.show()
```
::: {.callout-tip title="Builder Notu — Augmentation = Pozitif Çift"}
**Geriye (Hafta 3):** "Augment et = pozitif çift" fikri, Hafta 3'ün stationarity/invariance sezgisinin SSL hâli: bir kediyi döndürsen de kedidir; model bu değişmezliği öğrenir.
**İleriye:** Pozitif çift = augmentation, negatif çift = rastgele örnek — SimCLR/MoCo'nun (kursta MoCo var) çekirdeği; ileriye köprü (Bölüm 5).
:::
## (LeCun) Denoising Autoencoder (DAE): Bozulmuşu Yukarı Bas {#sec-dae-bert}
LeCun ilginç bir contrastive yöntem daha veriyor: **denoising autoencoder (DAE)**. Temiz bir y al, onu **boz** (x üret) — bir parçasını sil, gürültü ekle, ya da (metinde) bazı kelimeleri **maskele**. Sonra ağı bozulmuş x'ten temiz y'yi kurmaya zorla.
> "the idea of denoising autoencoder is that you take a y, and you generate x by corrupting y... [for text, masking a subset of the input]." — LeCun, 23:17
Bu neden contrastive? Çünkü bozulmuş nokta **manifold dışındadır** (yüksek enerji olmalı); ağ onu temize (manifolda) çekerek dışarının enerjisini yükseltmiş olur. Metinde "kelime maskeleme" — bu, **BERT**'in (Hafta 12) tam fikridir: maskelenmiş kelimeyi tahmin et. @fig-dae-bert hem görüntü (temiz → boz → kur) hem metin (maskelenmiş token → tahmin) versiyonunu gösterir.
```{python}
#| label: fig-dae-bert
#| fig-cap: "Gürültü gidermeli otokodlayıcı (denoising AE) ve BERT maskeleme. Üst panel: temiz örnek y manifold üzerinde (pozitif, düşük enerji); BOZ adımı (maskeleme / gürültü ekleme) onu manifold dışına iterek bozuk girdi x üretir (negatif, enerji yukarı bas); ağ bu bozuk girdiden gürültüyü giderip kurulan ŷ örneğini manifolda geri çeker. Alt panel: aynı fikrin metin karşılığı — 'kedi [MASK] uyuyor' dizisindeki maskelenmiş token ağ tarafından 'koltukta' olarak tahmin edilir. Maskeleme, BERT'in temel fikridir (Hafta 12'de işlenir)."
fig, (axA, axB) = plt.subplots(2, 1, figsize=(10.5, 6.4),
gridspec_kw=dict(height_ratios=[2.0, 1.0]))
fig.suptitle("Gürültü Gidermeli Otokodlayıcı (Denoising AE) ve BERT Maskeleme",
color=COL_INK, fontsize=14, fontweight="bold", y=0.99)
# =====================================================================
# ÜST PANEL (axA): manifold şeması temiz → BOZ → bozuk → ağ → kurulan
# =====================================================================
axA.set_xlim(0, 12)
axA.set_ylim(-0.2, 4.6)
axA.axis("off")
# --- veri manifoldu (eğri) ---
M = make_manifold_curve(n=200)
# manifoldu sol-bölge eksen koordinatlarına ölçekle
mx = 0.6 + (M[:, 0] + 2.6) / 5.2 * 3.0 # x: 0.6..3.6
my = 1.0 + (M[:, 1] + 1.0) / 2.0 * 2.2 # y: 1.0..3.2
axA.plot(mx, my, color=COL_VIOLET, lw=2.6, zorder=2)
axA.text(3.3, 0.55, "veri manifoldu", color=COL_VIOLET_D, fontsize=9.5,
ha="center", style="italic")
# temiz nokta y (manifold üzerinde, pozitif)
i_clean = 120
yx, yy = mx[i_clean], my[i_clean]
axA.scatter([yx], [yy], s=130, color=COL_VIOLET, edgecolors=COL_INK,
linewidths=1.4, zorder=5)
axA.annotate("temiz y\n(pozitif, manifoldda)", (yx, yy),
xytext=(yx - 1.65, yy + 0.85), fontsize=9, color=COL_VIOLET_D,
ha="center",
arrowprops=dict(arrowstyle="-", color=COL_VIOLET_D, lw=1.0))
# bozuk nokta x (manifold dışı = off-manifold, gold) — "yukarı bas"
cx_, cy_ = yx + 0.55, yy + 1.15
axA.scatter([cx_], [cy_], s=130, color=COL_GOLD, edgecolors=COL_GOLD_D,
linewidths=1.4, zorder=5, marker="X")
axA.annotate("bozuk x\n(negatif, manifold dışı)", (cx_, cy_),
xytext=(cx_ + 0.2, cy_ + 0.55), fontsize=9, color=COL_GOLD_D,
ha="center",
arrowprops=dict(arrowstyle="-", color=COL_GOLD_D, lw=1.0))
# BOZ oku: temiz → bozuk (yukarı it)
axA.add_patch(FancyArrowPatch((yx, yy), (cx_, cy_), arrowstyle="-|>",
mutation_scale=18, color=COL_GOLD_D, lw=2.0, zorder=4,
connectionstyle="arc3,rad=-0.2"))
axA.text((yx + cx_) / 2 + 1.05, (yy + cy_) / 2 + 0.05,
"BOZ\n(maskele / gürültü ekle)", fontsize=8.6, color=COL_GOLD_D,
ha="center", va="center", fontweight="bold")
# ağ kutusu (ortada)
nb = FancyBboxPatch((5.2, 1.55), 1.7, 1.3,
boxstyle="round,pad=0.03,rounding_size=0.14",
fc="#ece0f7", ec=COL_VIOLET, lw=2.4, zorder=3)
axA.add_patch(nb)
axA.text(6.05, 2.2, "ağ\n(gürültü gider)", ha="center", va="center",
fontsize=10, color=COL_VIOLET_D, fontweight="bold", zorder=4)
# bozuk → ağ
axA.add_patch(FancyArrowPatch((cx_, cy_), (5.2, 2.55), arrowstyle="-|>",
mutation_scale=16, color=COL_VIOLET_M, lw=1.9, zorder=2))
# kurulan ŷ (manifolda geri, violet) sağ bölge
M2x = 8.0 + (M[:, 0] + 2.6) / 5.2 * 3.0 # 8.0..11.0
M2y = 1.0 + (M[:, 1] + 1.0) / 2.0 * 2.2
axA.plot(M2x, M2y, color=COL_VIOLET, lw=2.6, zorder=2)
rx, ry = M2x[i_clean], M2y[i_clean]
axA.scatter([rx], [ry], s=130, color=COL_VIOLET, edgecolors=COL_INK,
linewidths=1.4, zorder=5)
axA.annotate("kurulan ŷ\n(manifolda geri)", (rx, ry),
xytext=(rx + 0.1, ry + 1.0), fontsize=9, color=COL_VIOLET_D,
ha="center",
arrowprops=dict(arrowstyle="-", color=COL_VIOLET_D, lw=1.0))
# ağ → kurulan
axA.add_patch(FancyArrowPatch((6.9, 2.2), (rx, ry + 0.25), arrowstyle="-|>",
mutation_scale=16, color=COL_VIOLET_M, lw=1.9, zorder=2,
connectionstyle="arc3,rad=-0.15"))
# annotation kutusu (enerji sezgisi)
axA.text(6.0, 0.15,
"bozuk = negatif (enerji yukarı bas) · temiz = pozitif (düşük enerji)",
ha="center", va="center", fontsize=9, color=COL_TEXT,
bbox=dict(boxstyle="round,pad=0.4", fc=COL_BG, ec=COL_GRID, lw=1.0))
# =====================================================================
# ALT PANEL (axB): BERT metin örneği 'kedi [MASK] uyuyor' → ag → ...
# =====================================================================
axB.set_xlim(0, 12)
axB.set_ylim(0, 1.6)
axB.axis("off")
def token_box(ax, x, y, w, txt, masked=False):
fc = "#fbe6c0" if masked else COL_WHITE
ec = COL_GOLD_D if masked else COL_VIOLET
tc = COL_GOLD_D if masked else COL_TEXT
ax.add_patch(FancyBboxPatch((x, y), w, 0.6,
boxstyle="round,pad=0.02,rounding_size=0.08",
fc=fc, ec=ec, lw=2.0, zorder=2))
ax.text(x + w / 2, y + 0.3, txt, ha="center", va="center",
fontsize=9.5, color=tc, fontweight="bold", zorder=3)
return x + w
# girdi: kedi [MASK] uyuyor
x0, yrow = 0.3, 0.75
x0 = token_box(axB, x0, yrow, 1.1, "kedi"); x0 += 0.12
x0 = token_box(axB, x0, yrow, 1.5, "[MASK]", masked=True); x0 += 0.12
x0 = token_box(axB, x0, yrow, 1.4, "uyuyor")
# ağ kutusu
axB.add_patch(FancyBboxPatch((5.2, 0.62), 1.7, 0.86,
boxstyle="round,pad=0.03,rounding_size=0.12",
fc="#ece0f7", ec=COL_VIOLET, lw=2.2, zorder=2))
axB.text(6.05, 1.05, "ağ", ha="center", va="center", fontsize=10.5,
color=COL_VIOLET_D, fontweight="bold", zorder=3)
axB.add_patch(FancyArrowPatch((4.95, 1.05), (5.2, 1.05), arrowstyle="-|>",
mutation_scale=14, color=COL_VIOLET_M, lw=1.8, zorder=1))
# çıktı: kedi koltukta uyuyor
x1 = 7.4
x1 = token_box(axB, x1, yrow, 1.1, "kedi"); x1 += 0.1
x1 = token_box(axB, x1, yrow, 1.7, "koltukta", masked=False); x1 += 0.1
x1 = token_box(axB, x1, yrow, 1.4, "uyuyor")
axB.add_patch(FancyArrowPatch((6.9, 1.05), (7.4, 1.05), arrowstyle="-|>",
mutation_scale=14, color=COL_VIOLET_M, lw=1.8, zorder=1))
axB.text(6.0, 0.18,
"maskeleme = BERT fikri (Hafta 12): eksik tokeni tahmin et",
ha="center", va="center", fontsize=9, color=COL_TEXT,
bbox=dict(boxstyle="round,pad=0.4", fc=COL_BG, ec=COL_GRID, lw=1.0))
plt.tight_layout(rect=[0, 0, 1, 0.97])
plt.show()
```
::: {.callout-tip title="Builder Notu — DAE = BERT"}
**Geriye (Hafta 7):** DAE = Hafta 7'nin "off-manifold noktayı manifolda geri çek" autoencoder'ının contrastive okuması: bozuk = negatif (yüksek enerji), temiz = pozitif (düşük).
**İleriye:** Maskeleme-tabanlı DAE → BERT (Hafta 12), masked autoencoder (MAE — post-2020, kursta yok), ve diffusion'ın gürültü-giderme adımları (Hafta 9).
:::
## (LeCun) Contrastive'in Sınırı → (İleriye Köprü: Post-2020) {#sec-contrastive-limit}
Contrastive yöntemlerin temel bir sınırı var: **boyut arttıkça gittikçe daha çok negatif örnek gerekir**.
> "as you increase the dimension of the representation, you need more and more negative samples." — LeCun, 22:54
Yüksek-boyutlu bir temsilde "dışarısı" devasadır; her yeri yukarı basmak için çok sayıda negatif gerekir — bu pahalı ve verimsizdir. İşte bu sınır, kurstan **sonra** non-contrastive yöntemleri doğurdu. @fig-contrastive-limit solda bu üstel maliyeti (boyut arttıkça negatif sayısı patlar), sağda ise bu sınırın doğurduğu post-2020 non-contrastive yöntemleri (kursta yok) gösterir.
```{python}
#| label: fig-contrastive-limit
#| fig-cap: "Kontrastif SSL'in negatif-örnek maliyeti ve non-contrastive çıkış. Sol panel: temsil boyutu (d) arttıkça gerekli negatif örnek sayısı üstel büyür (log ölçekte düz çizgi olarak görünür) — düşük boyut az/ucuz negatif, yüksek boyut çok/pahalı/verimsiz negatif gerektirir. Sağ panel: bu maliyet sınırının doğurduğu, negatif örnek gerektirmeyen post-2020 non-contrastive yöntemler (BYOL, Barlow Twins, VICReg, MAE) — kursta yok, ileriye köprü."
fig, (axL, axR) = plt.subplots(1, 2, figsize=(11, 5),
gridspec_kw={"width_ratios": [1.15, 1.0]})
# -------------------------------------------------------------------
# SOL PANEL — temsil boyutu arttıkça gerekli NEGATİF SAYISI üstel artar
# -------------------------------------------------------------------
d = np.linspace(2, 256, 400) # temsil boyutu
# Üstel büyüme: yüksek boyutta uzayı "doldurmak" için katlanarak çok negatif gerekir
neg = 8.0 * np.exp(d / 42.0) # gerekli negatif örnek sayısı (göreli)
axL.plot(d, neg, color=COL_VIOLET, lw=2.8, zorder=3,
label="gerekli negatif örnek")
axL.fill_between(d, neg, color=COL_VIOLET, alpha=0.08, zorder=1)
apply_style(axL)
axL.set_yscale("log")
axL.set_xlim(2, 256)
axL.set_xlabel("temsil boyutu (d)", fontsize=11)
axL.set_ylabel("gerekli negatif sayısı (log ölçek)", fontsize=11)
axL.set_title("Kontrastif öğrenmenin sınırı", color=COL_INK,
fontsize=12.5, fontweight="bold", pad=10)
# Düşük boyut → az negatif (ucuz) işaretle
axL.scatter([16], [8.0 * np.exp(16 / 42.0)], s=70, color=COL_GOLD,
edgecolors=COL_VIOLET_D, lw=1.4, zorder=5)
axL.annotate("düşük boyut\naz negatif", xy=(16, 8.0 * np.exp(16 / 42.0)),
xytext=(34, 8.0 * np.exp(16 / 42.0) * 0.18),
fontsize=9, color=COL_TEXT, ha="left",
arrowprops=dict(arrowstyle="-|>", color=COL_VIOLET_M, lw=1.4))
# Yüksek boyut → ÇOK negatif (pahalı) işaretle
axL.scatter([220], [8.0 * np.exp(220 / 42.0)], s=70, color=COL_GOLD,
edgecolors=COL_VIOLET_D, lw=1.4, zorder=5)
axL.annotate("yüksek boyut\nÇOK negatif\n(pahalı / verimsiz)",
xy=(220, 8.0 * np.exp(220 / 42.0)),
xytext=(120, 8.0 * np.exp(220 / 42.0) * 0.012),
fontsize=9, color=COL_VIOLET_D, ha="left", fontweight="bold",
arrowprops=dict(arrowstyle="-|>", color=COL_VIOLET_M, lw=1.4))
style_legend(axL, loc="upper left", fontsize=9)
# -------------------------------------------------------------------
# SAĞ PANEL — POST-2020 NON-CONTRASTIVE kutusu (kursta YOK, ileriye köprü)
# -------------------------------------------------------------------
axR.set_xlim(0, 10)
axR.set_ylim(0, 10)
axR.axis("off")
# Kesikli-soluk kenarlı kutu
box = FancyBboxPatch((0.6, 1.4), 8.8, 7.2,
boxstyle="round,pad=0.1,rounding_size=0.25",
fc=COL_BG, ec=COL_VIOLET_M, lw=2.2, ls="--",
alpha=0.55, zorder=1)
axR.add_patch(box)
axR.text(5.0, 7.9, "POST-2020 NON-CONTRASTIVE", ha="center", va="center",
fontsize=12.5, color=COL_VIOLET_D, fontweight="bold", zorder=3)
axR.text(5.0, 7.15, "(KURSTA YOK — ileriye köprü)", ha="center", va="center",
fontsize=9.5, color=COL_TEXT, style="italic", zorder=3)
# Yöntem listesi (gold) — negatif örnek GEREKTİRMEYEN yöntemler
methods = [
("BYOL", "negatifsiz — bootstrap hedef ağı"),
("Barlow Twins", "negatifsiz — çapraz-korelasyon = I"),
("VICReg", "varyans + değişmezlik + kovaryans"),
("MAE", "maskele-yeniden kur (üretken)"),
]
y = 6.1
for name, desc in methods:
axR.scatter([1.4], [y], s=90, marker="s", color=COL_GOLD,
edgecolors=COL_GOLD_D, lw=1.3, zorder=4)
axR.text(2.1, y, name, ha="left", va="center", fontsize=11.5,
color=COL_GOLD_D, fontweight="bold", zorder=4)
axR.text(2.1, y - 0.42, desc, ha="left", va="center", fontsize=8.3,
color=COL_TEXT, zorder=4)
y -= 1.28
apply_style(axR)
for spine in axR.spines.values():
spine.set_visible(False)
# -------------------------------------------------------------------
# Alt açıklama (figür geneli)
# -------------------------------------------------------------------
fig.text(0.5, -0.02,
"Bu sınır non-contrastive devrimini doğurdu — post-2020, kursta YOK "
"(ileriye köprü).",
ha="center", va="top", fontsize=9.5, color=COL_VIOLET_D,
style="italic")
fig.suptitle("Kontrastif SSL: negatif maliyeti ve non-contrastive çıkış",
fontsize=13.5, color=COL_INK, fontweight="bold", y=1.02)
fig.tight_layout(rect=[0, 0.02, 1, 1])
```
::: {.callout-warning title="İleriye Köprü Notu (post-2020 — KURSTA YOK)"}
Contrastive'in "çok negatif" sorununu çözen non-contrastive yöntemler DLSP20'den (Mart 2020) **sonra** geldi ve bu kursta **YOKTUR** (yalnızca ileriye köprü olarak anılır):
- **BYOL** (Grill ve ark., Haz 2020) — negatif örnek olmadan SSL (iki ağın tutarlılığı)
- **Barlow Twins** (2021) — çapraz-korelasyon düzenlileştirme
- **VICReg** (2021) — variance-invariance-covariance düzenlileştirme
- **MAE** (He ve ark., 2021) — masked autoencoder (DAE'nin ViT hâli)
Bunlar LeCun'un "architectural/non-contrastive" sınıfının modern temsilcileridir; kurs içeriğine kurs terimi gibi eklenmez.
:::
::: {.callout-tip title="Builder Notu — Çok Negatif Sınırı"}
**Geriye (Hafta 7 + Bölüm 1):** "Çok negatif gerekir" sorunu, LeCun'un iki-sınıf ayrımının (contrastive vs architectural) *neden* önemli olduğunu gösterir: architectural yöntemler negatif gerektirmez.
**İleriye:** Bu sınır, LeCun'un bugünkü JEPA programının (negatif-örneksiz, architectural SSL) doğuş gerekçesidir — post-2020, kursta yok.
:::
## Geçiş: LeCun'dan Canziani'ye {#sec-gecis-d8}
LeCun EBM eğitiminin iki ailesini kurdu: contrastive (negatif it) ve architectural/non-contrastive (yapıyla kısıtla). Şimdi **Canziani** ikinci ailenin en zarif örneğini gösteriyor: **VAE (değişimsel autoencoder)**. VAE, Hafta 7'nin sıradan autoencoder'ını alıp latent uzayı bir **olasılık dağılımına** oturtur — böylece düşük-enerji bölgesini negatif örnek olmadan, *yapıyla* düzenler. LeCun çerçeveyi, Canziani somut mekanizmayı veriyor.
## (Canziani) AE vs VAE: Encoder Bir Dağılım Üretir {#sec-ae-vs-vae}
Canziani sıradan autoencoder'ı (Hafta 7) hatırlatıp tek bir kritik farkla VAE'ye geçiyor. Sıradan AE'de encoder bir **kod** (tek vektör) üretir. VAE'de encoder **iki** şey üretir: bir **ortalama** $E(z)$ ve bir **varyans** $V(z)$ — yani latent değişkenin bir **Gaussian dağılımını** tanımlar.
> "[in a VAE] you have E of Z and V of Z... representing the mean and the variance of this latent variable Z, then we are going to be sampling from this distribution." — Canziani, 3:12
Sonra bu dağılımdan **örneklenir** ve elde edilen z decoder'a verilir. D-boyutlu bir latent için encoder D ortalama + D varyans üretir (köşegen kovaryans varsayımı — bileşenler bağımsız). Canziani'nin gözlemi: varyans sıfıra inerse VAE, sıradan (deterministik) bir autoencoder'a **çöker**. @fig-ae-vs-vae bu farkı yan yana koyar: solda AE tek kod, sağda VAE dağılım + örnekleme (reparameterization).
```{python}
#| label: fig-ae-vs-vae
#| fig-cap: "Sıradan AE vs VAE. Solda sıradan autoencoder: encoder girdiyi daraltıp tek bir kod vektörüne (gold nokta z) sıkıştırır, decoder onu geri açar — latent uzay boşluklu ve düzensiz. Sağda VAE: encoder bir dağılım (μ, σ) üretir; reparameterization hilesiyle z = μ + σ⊙ε örneklenir (ε~N(0,I)), böylece rastgelelik ε'a izole edilir ve gradyan μ ile σ üzerinden geriye akabilir. Sonuç: sürekli, dolu ve örneklenebilir bir latent uzay."
fig, (axL, axR) = plt.subplots(1, 2, figsize=(11, 5.2))
def funnel(ax, x0, w_in, w_out, h, y0, fc, ec, lw=2.2):
"""Daralan (encoder) veya genişleyen (decoder) huni gövdesi."""
pts = [(x0, y0 + w_in / 2), (x0 + h, y0 + w_out / 2),
(x0 + h, y0 - w_out / 2), (x0, y0 - w_in / 2)]
ax.add_patch(Polygon(pts, closed=True, fc=fc, ec=ec, lw=lw, zorder=2))
def arrow(ax, p0, p1, color=COL_VIOLET_M, lw=1.9):
ax.add_patch(FancyArrowPatch(p0, p1, arrowstyle="-|>", mutation_scale=15,
color=color, lw=lw, zorder=4))
# ------------------------------------------------------------------ SOL: AE
axL.set_title("Sıradan AE", color=COL_INK, fontsize=13, fontweight="bold", pad=8)
axL.set_xlim(0, 10)
axL.set_ylim(-3.2, 3.2)
axL.axis("off")
axL.set_aspect("auto")
# encoder (daralan huni)
funnel(axL, 0.6, 3.4, 1.0, 2.6, 0.0, "#ece0f7", COL_VIOLET)
axL.text(1.9, 2.1, "encoder", ha="center", va="center", fontsize=10,
color=COL_VIOLET_D, rotation=0)
# kod vektörü z (gold nokta)
arrow(axL, (3.4, 0.0), (4.5, 0.0))
zx = 5.0
axL.scatter([zx], [0.0], s=240, c=COL_GOLD, edgecolors=COL_GOLD_D,
linewidths=2.0, zorder=5)
axL.text(zx, -0.85, "kod z", ha="center", va="center", fontsize=10,
color=COL_GOLD_D, fontweight="bold")
# decoder (genişleyen huni)
arrow(axL, (5.5, 0.0), (6.6, 0.0))
funnel(axL, 6.6, 1.0, 3.4, 2.6, 0.0, "#ece0f7", COL_VIOLET)
axL.text(7.9, 2.1, "decoder", ha="center", va="center", fontsize=10,
color=COL_VIOLET_D)
axL.text(5.0, -2.7, "tek nokta → boşluklu uzay", ha="center", va="center",
fontsize=9.5, color=COL_TEXT, style="italic")
# ------------------------------------------------------------------ SAG: VAE
axR.set_title("VAE", color=COL_INK, fontsize=13, fontweight="bold", pad=8)
axR.set_xlim(0, 10)
axR.set_ylim(-3.2, 3.2)
axR.axis("off")
axR.set_aspect("auto")
# encoder
funnel(axR, 0.4, 3.4, 1.0, 2.2, 0.0, "#ece0f7", COL_VIOLET)
axR.text(1.5, 2.1, "encoder", ha="center", va="center", fontsize=10,
color=COL_VIOLET_D)
# dağılım μ,σ — Gaussian bulut + elipsler
arrow(axR, (2.7, 0.0), (3.5, 0.0))
cx, cy = 4.7, 0.0
mu = [cx, cy]
sigma = [0.62, 0.5]
cloud = vae_reparam(mu, sigma, n=350, seed=0)
axR.scatter(cloud[:, 0], cloud[:, 1], s=7, c=COL_VIOLET_M, alpha=0.32, zorder=3)
for k, a in ((1, 0.45), (2, 0.28), (3, 0.16)):
axR.add_patch(Ellipse((cx, cy), width=4 * sigma[0] * k / 2 * 1.0,
height=4 * sigma[1] * k / 2 * 1.0,
fill=False, ec=COL_VIOLET, lw=1.6, alpha=a, zorder=4))
axR.scatter([cx], [cy], s=70, c=COL_GOLD_D,
linewidths=1.5, zorder=6, marker="x")
axR.text(cx, -1.55, "dağılım μ, σ", ha="center", va="center", fontsize=10,
color=COL_VIOLET_D, fontweight="bold")
# örnekle z = μ + σ⊙ε
arrow(axR, (6.1, 0.0), (6.9, 0.0))
zx2 = 7.3
axR.scatter([zx2], [0.0], s=150, c=COL_GOLD, edgecolors=COL_GOLD_D,
linewidths=1.8, zorder=6)
axR.text(zx2, -0.85, "örnek z", ha="center", va="center", fontsize=9.5,
color=COL_GOLD_D, fontweight="bold")
# decoder
arrow(axR, (7.7, 0.0), (8.0, 0.0))
funnel(axR, 8.0, 1.0, 3.0, 1.9, 0.0, "#ece0f7", COL_VIOLET)
axR.text(8.9, 1.85, "decoder", ha="center", va="center", fontsize=10,
color=COL_VIOLET_D)
axR.text(5.0, -2.7, "sürekli, dolu latent uzay", ha="center", va="center",
fontsize=9.5, color=COL_TEXT, style="italic")
# reparameterization annotation kutusu (alt orta)
fig.text(0.5, -0.02,
r"$z = \mu + \sigma \odot \varepsilon,\quad \varepsilon \sim \mathcal{N}(0, I)$"
"\nAE tek kod, VAE dağılım; reparameterization rastgeleliği "
r"$\varepsilon$'a izole eder → backprop μ, σ'dan akar",
ha="center", va="top", fontsize=10.5, color=COL_TEXT,
bbox=dict(boxstyle="round,pad=0.5", fc=COL_BG, ec=COL_GOLD, lw=1.5))
fig.suptitle("Sıradan AE vs VAE — Reparameterization Hilesi",
color=COL_INK, fontsize=14, fontweight="bold", y=1.04)
plt.show()
```
::: {.callout-tip title="Builder Notu — Encoder Bir Dağılım"}
**Geriye (Stat 110 + Hafta 7):** $E(z)$, $V(z)$ = çok-değişkenli normal'in ortalama + (köşegen) kovaryansı (Stat 110); VAE latent'i, Hafta 7'nin latent değişkeninin **olasılıksal** hâli.
**İleriye:** "Encoder bir dağılım üretir" fikri, tüm olasılıksal üretken modellerin (diffusion, normalizing flows) ortak temelidir.
:::
## (Canziani) Latent'ten Örnekleme ve Reparameterization {#sec-reparam}
VAE'nin kalbi **örnekleme**dir: z'yi encoder'ın verdiği $\mathcal{N}(\mu, \sigma^2)$ dağılımından çekersin. Ama örnekleme rastgeledir — backprop rastgele bir işlemden geçemez. Çözüm **reparameterization trick**: rastgeleliği dışarı al, z'yi türevlenebilir bir formülle yaz:
$$
z = \mu(x) + \sigma(x) \odot \epsilon, \qquad \epsilon \sim \mathcal{N}(0, I)
$$
Artık ε (sabit gürültü) dışında her şey türevlenebilir; gradient μ ve σ üzerinden akar (Hafta 5 autograd). Encoder deterministiktir (Gaussian parametrelerini üretir); tek stokastik kısım ε'dur.
::: {.callout-tip title="Builder Notu — Reparameterization Trick"}
**Geriye (Stat 110 + Calculus + Hafta 5):** Reparameterization = konum-ölçek dönüşümü ($X = \mu + \sigma Z$, Stat 110); türevlenebilirliği koruması Calculus zincir kuralı + Hafta 5 autograd sayesindedir (rastgeleliği sabit ε'a izole et).
**İleriye:** Reparameterization, VAE'yi eğitilebilir kılan kilit trick'tir; diffusion modelleri de benzer "gürültüyü izole et" fikrini kullanır (Hafta 9).
:::
## (Canziani) VAE Neden? Düzenli, Üretken Latent {#sec-duzenli-latent}
Neden sıradan AE yerine VAE? Çünkü VAE latent uzayı **düzenli (regularized)** tutar. Sıradan AE latent'i dağınık olabilir — aradaki noktalar anlamsız olabilir. VAE, latent'i bir Gaussian'a oturtarak (örnekleme + dağılım kısıtı) latent uzayı **sürekli ve doldurulmuş** yapar; böylece latent'ten yeni örnek çekip decoder'la **yeni veri üretebilirsin** (üretken model).
EBM köprüsü (LeCun): VAE bir **non-contrastive/architectural** yöntemdir — latent'i Gaussian'a kısıtlamak, düşük-enerji bölgesinin hacmini *yapıyla* sınırlar (negatif örnek üretmeden). Yani VAE, LeCun'un ikinci ailesinin somut hâlidir. @fig-vae-latent bu farkı 2B latent uzayında gösterir: solda AE'nin boşluklu dağınık kümeleri, sağda VAE'nin dolu, sürekli Gaussian bulutu.
```{python}
#| label: fig-vae-latent
#| fig-cap: "İki boyutlu latent uzay: sıradan AE (sol) vs VAE (sağ). Sıradan otokodlayıcının latent uzayı dağınık, boşluklu kümelerden oluşur; kümeler arasındaki bölge (kesikli daire) anlamsızdır — oradan çekilen bir z, decoder ile geçerli bir veri vermez. VAE ise reparameterization trick (z = μ + σ⊙ε) ile latent uzayı dolu ve sürekli bir Gaussian buluta zorlar. Bu sayede herhangi bir z örneği çekip decoder'dan geçirerek YENİ veri üretilebilir — VAE üretkendir, sıradan AE değildir."
fig, (axL, axR) = plt.subplots(1, 2, figsize=(10.5, 5.0))
# --- SOL: Sıradan AE latent — dağınık, boşluklu kümeler ---
ae = ae_latent_clusters(400, seed=0)
axL.scatter(ae[:, 0], ae[:, 1], s=14, c=COL_GOLD_D, alpha=0.65,
edgecolors="none", zorder=2)
# "boş bölge: anlamsız" — kümeler arası merkez bölge kesikli daire ile işaretle
gap = Circle((0.0, 0.0), 1.15, fill=False, ls=(0, (5, 4)),
ec=COL_GOLD, lw=2.2, zorder=3)
axL.add_patch(gap)
axL.annotate("boş bölge:\nanlamsız", xy=(0.0, 0.0), xytext=(0.0, 0.05),
ha="center", va="center", fontsize=9.5, color=COL_GOLD_D,
fontweight="bold", zorder=4)
axL.set_title("Sıradan AE latent", color=COL_INK, fontsize=12, fontweight="bold")
axL.set_xlabel("z₁", color=COL_TEXT)
axL.set_ylabel("z₂", color=COL_TEXT)
axL.set_xlim(-3.4, 3.4)
axL.set_ylim(-3.4, 3.4)
apply_style(axL)
axL.set_aspect("equal")
# --- SAĞ: VAE latent — dolu, sürekli Gaussian bulut ---
vae = vae_reparam([0.0, 0.0], [1.0, 1.0], n=400, seed=0)
axR.scatter(vae[:, 0], vae[:, 1], s=14, c=COL_VIOLET, alpha=0.55,
edgecolors="none", zorder=2)
axR.set_title("VAE latent", color=COL_INK, fontsize=12, fontweight="bold")
axR.set_xlabel("z₁", color=COL_TEXT)
axR.set_ylabel("z₂", color=COL_TEXT)
axR.set_xlim(-3.4, 3.4)
axR.set_ylim(-3.4, 3.4)
apply_style(axR)
axR.set_aspect("equal")
# --- Alt açıklama (annotation) ---
fig.text(
0.5, -0.04,
"VAE latent dolu/sürekli → herhangi bir z çek, decoder ile YENİ VERİ üret "
"(üretken);\nAE bunu yapamaz (boşluklar anlamsız).",
ha="center", va="top", fontsize=10, color=COL_TEXT,
)
fig.suptitle("Düzenli latent (üretken): AE vs VAE", color=COL_INK,
fontsize=13.5, fontweight="bold", y=1.02)
fig.tight_layout()
```
::: {.callout-tip title="Builder Notu — Düzenli Latent = Üretken"}
**Geriye (LeCun bu hafta + Hafta 1):** VAE'nin latent kısıtı = architectural enerji şekillendirme (LeCun Bölüm 1); düzenli latent = Hafta 1 manifoldunun pürüzsüz, örneklenebilir hâli.
**İleriye:** VAE → diffusion (Hafta 9 teaser), latent diffusion (Stable Diffusion), ve üretken modellerin tüm ailesi.
:::
## Bu Dersin Özeti {#sec-ozet-d8}
1. **EBM eğitimi iki sınıf:** **contrastive** (veride aşağı bas, negatifte yukarı bas) ve **architectural/non-contrastive** (yapıyla kısıtla).
2. **Contrastive SSL:** pozitif çift (augmentation), negatif çift (rastgele); benzeri aşağı, benzemezi yukarı bas (PIRL/NCE, MoCo).
3. **Denoising AE:** temizi boz (maskele), geri kur; bozuk = off-manifold = yukarı bas. (BERT'in fikri.)
4. **Contrastive'in sınırı:** yüksek boyutta çok negatif gerekir → post-2020 non-contrastive (BYOL/VICReg/MAE — kursta YOK).
5. **VAE (Canziani):** encoder ortalama + varyans üretir; latent $\mathcal{N}(\mu,\sigma^2)$'den örneklenir (reparameterization $z = \mu + \sigma\epsilon$).
6. **VAE = non-contrastive EBM:** latent'i Gaussian'a kısıtlamak enerjiyi yapıyla şekillendirir; düzenli latent → üretken model.
::: {.callout-important title="Tek Bir Cümle"}
EBM'yi eğitmek enerjiyi şekillendirmektir: contrastive yöntemler veride enerjiyi düşürüp üretilmiş negatiflerde yükseltir (ama yüksek boyutta çok negatif ister), non-contrastive yöntemler ise yapıyla kısıtlar — VAE bunun zarif örneğidir: latent'i bir Gaussian'a oturtup reparameterization ile eğitilebilir, düzenli, üretken bir uzay kurar.
:::
## Kontrol Soruları {#sec-kontrol-d8}
::: {.callout-note collapse="true" title="Soru 1: EBM eğitiminin iki sınıfı nedir? Aralarındaki temel fark?"}
**Cevap:** (1) **Contrastive yöntemler:** veri noktalarında enerjiyi **aşağı bas**, üretilmiş negatif örneklerde **yukarı bas** (LeCun 5:20). Örnekler: contrastive divergence, NCE, denoising AE. (2) **Architectural / non-contrastive yöntemler:** modelin **yapısını** kısıtlayarak düşük-enerji bölgesinin hacmini sınırla — negatif örnek üretmeden (örn. bottleneck, sparse coding, VAE). Temel fark: contrastive negatif örnek **üretir ve iter**; non-contrastive negatif gerektirmez, enerjiyi **mimariyle** şekillendirir. (Hafta 7'deki "yalnızca veride düşürürsen model çöker" sorununun iki farklı çözümü.)
:::
::: {.callout-note collapse="true" title="Soru 2: Contrastive SSL'de pozitif ve negatif çiftler nasıl üretilir? Bu yöntemin temel sınırı nedir?"}
**Cevap:** **Pozitif çift:** bir örneği al, **augmentation** ile (kırp/döndür/renk) ikinci versiyonunu üret — ikisi "aynı" sayılır, enerjileri aşağı basılır. **Negatif çift:** rastgele iki farklı örnek — uyumsuz, enerjileri yukarı basılır (LeCun 13:22). Benzeri aşağı, benzemezi yukarı (PIRL/NCE, MoCo). **Sınır:** temsil boyutu arttıkça "dışarısı" büyür, enerjiyi her yerde yükseltmek için **gittikçe daha çok negatif örnek** gerekir (LeCun 22:54) — pahalı ve verimsiz. Bu sınır, post-2020 non-contrastive yöntemleri (BYOL/VICReg — kursta yok) doğurdu.
:::
::: {.callout-note collapse="true" title="Soru 3: Denoising autoencoder neden bir contrastive yöntemdir? BERT ile ilişkisi nedir?"}
**Cevap:** DAE, temiz bir y'yi **bozarak** (parça silme, gürültü, maskeleme) x üretir ve ağı x'ten y'yi kurmaya zorlar (LeCun 23:17). Bu contrastive'dir çünkü bozulmuş nokta **manifold dışındadır** (yüksek enerji olmalı); ağ onu temize/manifolda çekerek dışarının enerjisini **yukarı basmış** olur — pozitif (temiz) aşağı, negatif (bozuk) yukarı. **BERT** tam bu fikirdir: metinde bazı kelimeleri **maskele**, modeli onları tahmin etmeye zorla (masked language modeling) — DAE'nin metin hâli. (Hafta 7'nin "off-manifold geri çek" autoencoder'ının contrastive okuması.)
:::
::: {.callout-note collapse="true" title="Soru 4: (Builder) VAE sıradan AE'den nasıl farklıdır? Reparameterization trick neden gerekli? Denklemi yaz."}
**Cevap:** Sıradan AE encoder'ı tek bir **kod** üretir; VAE encoder'ı bir **dağılım** üretir — ortalama $\mu(x)$ ve varyans $\sigma^2(x)$ (Gaussian) — ve z bu dağılımdan **örneklenir** (Canziani 3:12). Bu, latent'i düzenli/üretken yapar (varyans 0 → sıradan AE'ye çöker). **Reparameterization gerekli** çünkü backprop rastgele örneklemeden geçemez; rastgeleliği sabit bir ε'a izole edersin:
$$
z = \mu(x) + \sigma(x) \odot \epsilon, \qquad \epsilon \sim \mathcal{N}(0, I)
$$
Artık μ, σ türevlenebilir (gradient onlardan akar, Hafta 5 autograd); yalnızca ε stokastiktir (Stat 110 konum-ölçek $X=\mu+\sigma Z$). VAE bir **non-contrastive EBM**'dir: latent'i Gaussian'a kısıtlamak enerjiyi yapıyla şekillendirir.
:::
## Egzersizler {#sec-egzersiz-d8}
**Egzersiz 1 (Push down/up).** 1B bir enerji eğrisi düşün. (a) Yalnızca veri noktalarında enerjiyi düşür — ne olur (enerji her yerde düşer, "çöküş")? (b) Birkaç rastgele negatif noktada enerjiyi yükselt — şekil nasıl düzelir? Contrastive'in neden negatif gerektirdiğini açıkla.
```python
import numpy as np
def energy_1d(y, data=(-2.0, 0.0, 2.0), width=0.35):
y = np.asarray(y, float)
F = np.ones_like(y)
for d in data: # her veri noktasi bir cukur
F = F - np.exp(-(y - d) ** 2 / (2 * width))
return F
y = np.linspace(-4, 4, 400)
F = energy_1d(y)
# (a) SADECE veride dusur: tum egriyi asagi cek -> her yer duser = COKUS
# (b) contrastive: veri ASAGI (push down) + rastgele negatif YUKARI (push up)
# -> veride cukur, aralarda tepe = iyi enerji sekli
neg = np.array([-1.0, 1.0]) # rastgele negatif noktalar
print("veri enerjisi (dusuk):", np.round(energy_1d(np.array([-2, 0, 2])), 2))
print("negatif enerjisi (yukari basilacak):", np.round(energy_1d(neg), 2))
# negatif olmadan -> collapse; negatif ile -> sekillenmis enerji
```
**Egzersiz 2 (Pozitif çift = augmentation).** Bir görüntüye iki farklı augmentation (kırpma + renk) uygula. Bu iki versiyon neden "pozitif çift"tir? Negatif çift nasıl üretilir? Boyut arttıkça neden daha çok negatif gerekir?
```python
import torch
# augment = ayni goruntunun iki gorunumu -> POZITIF cift (ayni sey sayilir)
def augment(img, seed):
g = torch.Generator().manual_seed(seed)
crop = img[:, 4:, 4:] # kirpma (rastgele konum)
color = img * (0.8 + 0.4 * torch.rand(1, generator=g)) # renk jitter
return crop, color
img = torch.rand(3, 32, 32)
view1, _ = augment(img, 0)
_, view2 = augment(img, 1)
# (view1, view2) = POZITIF cift -> enerji ASAGI (ayni goruntu, iki gorunum)
other = torch.rand(3, 32, 32) # rastgele BASKA goruntu
# (view1, other) = NEGATIF cift -> enerji YUKARI (uyumsuz)
# boyut d artarsa: "disarisi" ustel buyur -> her yeri itmek icin COK negatif gerekir
```
**Egzersiz 3 (Denoising AE).** Bir görüntüye gürültü/maskeleme uygula, küçük bir DAE ile temizini kur. Bunun (a) Hafta 7'nin "manifolda geri çek"i ve (b) BERT'in maskeleme fikriyle ilişkisini açıkla.
```python
import torch
import torch.nn as nn
class DenoisingAE(nn.Module):
def __init__(self, d=784, code=64):
super().__init__()
self.enc = nn.Sequential(nn.Linear(d, code), nn.ReLU())
self.dec = nn.Sequential(nn.Linear(code, d), nn.Sigmoid())
def forward(self, x): # x = BOZUK girdi
return self.dec(self.enc(x)) # -> temiz kurulan
model = DenoisingAE()
clean = torch.rand(1, 784) # temiz y (manifoldda)
mask = (torch.rand(1, 784) > 0.3).float() # %30 maskele
corrupt = clean * mask # bozuk x (off-manifold)
recon = model(corrupt)
loss = ((recon - clean) ** 2).mean() # bozuktan temizi kur
# (a) bozuk=off-manifold -> manifolda GERI CEK (Hafta 7)
# (b) maskeleme = BERT fikri (eksik tokeni/pikseli tahmin et, Hafta 12)
```
**Egzersiz 4 (VAE kur).** PyTorch'ta bir VAE kur: encoder μ ve log σ² üretsin, reparameterization ile z örnekle, decoder kursun. Reconstruction loss + KL terimini ekle. Latent'ten rastgele z çekip yeni örnek üret (sıradan AE bunu yapamaz — neden?).
```python
import torch
import torch.nn as nn
class VAE(nn.Module):
def __init__(self, d=784, h=400, zdim=20):
super().__init__()
self.enc = nn.Linear(d, h)
self.fc_mu = nn.Linear(h, zdim) # ortalama mu
self.fc_logvar = nn.Linear(h, zdim) # log varyans
self.dec1 = nn.Linear(zdim, h)
self.dec2 = nn.Linear(h, d)
def reparam(self, mu, logvar): # z = mu + sigma * eps
sigma = torch.exp(0.5 * logvar)
eps = torch.randn_like(sigma) # eps ~ N(0, I) (tek stokastik kisim)
return mu + sigma * eps
def forward(self, x):
h = torch.relu(self.enc(x))
mu, logvar = self.fc_mu(h), self.fc_logvar(h)
z = self.reparam(mu, logvar)
recon = torch.sigmoid(self.dec2(torch.relu(self.dec1(z))))
return recon, mu, logvar
def vae_loss(recon, x, mu, logvar):
bce = nn.functional.binary_cross_entropy(recon, x, reduction="sum")
kl = -0.5 * torch.sum(1 + logvar - mu.pow(2) - logvar.exp()) # KL(q||N(0,I))
return bce + kl # reconstruction + duzenlileme
# Uretim: z = torch.randn(1, 20); ornek = decoder(z)
# AE bunu YAPAMAZ -> latent bosluklu/duzensiz, rastgele z anlamsiz cikti verir
```
**Egzersiz 5 (Hafta 9 habercisi — GAN ve dünya modelleri).** VAE veriyi olasılıksal kurar; başka bir üretken yaklaşım **GAN**'dır (üretici vs ayırıcı). (a) VAE "veriyi yeniden kur" derken GAN "ayırıcıyı kandır" der — bu iki üretken felsefenin farkı nedir? (b) Hafta 9'da LeCun sparse coding, **dünya modelleri** ve GAN'ı (EBM'nin üçüncü dersi) anlatacak — dünya modeli neden bir EBM'dir (gelecek durumun enerjisi)?
```python
# (a) VAE vs GAN — iki uretken felsefe
# VAE : encoder->dagilim->decoder; kayip = reconstruction + KL
# "veriyi olasiliksal YENIDEN KUR" (likelihood-tabanli)
# GAN : uretici G (gurultuden veri uretir) vs ayirici D (gercek/sahte ayirir)
# "ayiriciyi KANDIR" (adversarial, min-max oyun)
# min_G max_D E[log D(x)] + E[log(1 - D(G(z)))]
# (b) dunya modeli = EBM:
# F(s_t, a_t, s_{t+1}) = gelecek durumun enerjisi
# uyumlu (gercekci) gecis -> DUSUK enerji; uyumsuz -> YUKSEK enerji
# -> planlama = gelecek durumun enerjisini minimize et (Hafta 9)
print("VAE: yeniden kur (likelihood) | GAN: ayiriciyi kandir (adversarial)")
```
## Sonraki Ders İçin Hazırlık {#sec-sonraki-d8}
::: {.callout-warning title="Sonraki Hafta — H9: Sparse Coding, Dünya Modelleri ve GAN"}
**Üretken modeller geliyor.** Bu hafta EBM eğitiminin iki yolunu kurduk (contrastive vs non-contrastive) ve VAE'yi gördük. Hafta 9, EBM serisinin üçüncü ve son dersi: LeCun **sparse coding**, **dünya modelleri (world models)** ve **GAN**'ı (hepsi EBM çerçevesinde) anlatacak; Canziani GAN/DCGAN'ı PyTorch'ta gösterecek. Egzersiz 4 (VAE) ve Egzersiz 5 (GAN/dünya modeli habercisi) tam bu derse hazırlar.
:::
**Hafta 9: Sparse Coding, Dünya Modelleri ve GAN** — LeCun (Lecture) + Canziani (Practicum)
Hafta 9, EBM serisinin üçüncü ve son dersi: LeCun **sparse coding**, **dünya modelleri (world models)** ve **GAN**'ı (hepsi EBM çerçevesinde) anlatacak; Canziani GAN/DCGAN'ı PyTorch'ta gösterecek.
**Hafta 9 öncesi yapılacak:**
- Egzersiz 1 (push down/up) ve Egzersiz 4 (VAE) çöz.
- "Contrastive vs non-contrastive" ayrımını kendi sözcüklerinle yaz.
- Hafta 7-8'i bağla: EBM çerçevesi (7) → enerji şekillendirme yöntemleri (8) → üretken modeller (9).
## Anahtar Kavramlar (Cheat Sheet) {#sec-cheat-d8}
| Kavram | Tanım | Hoca / timestamp |
|---|---|---|
| Contrastive yöntem | Veride enerjiyi aşağı, negatifte yukarı bas | LeCun 5m20 |
| Architectural / non-contrastive | Yapıyla enerji hacmini kısıtla (negatifsiz) | LeCun 6m46 |
| Pozitif / negatif çift | Augmentation (uyumlu) vs rastgele (uyumsuz) | LeCun 13m22 |
| Noise contrastive estimation | PIRL'in contrastive amaç fonksiyonu | LeCun 16m32 |
| Denoising AE (DAE) | Bozulmuşu kur; maskeleme = BERT fikri | LeCun 23m17 |
| Contrastive sınırı | Yüksek boyutta çok negatif gerekir | LeCun 22m54 |
| VAE | Encoder ortalama + varyans üretir; latent örneklenir | Canziani 3m12 |
| Reparameterization | $z = \mu + \sigma\odot\epsilon$; rastgeleliği ε'a izole et | Canziani 3m26 |
| Düzenli latent | Gaussian kısıt → sürekli, üretken latent uzay | Canziani 4m37 |
| VAE = non-contrastive EBM | Latent kısıtı enerjiyi yapıyla şekillendirir | Canziani / LeCun |
## ML Builder Bağlantıları {#sec-koprular-d8}
**Geriye köprüler (önkoşul kurslar):**
1. **Push down/up = cross-entropy/MLE** → Hafta 1 cross-entropy + Stat 110 (energy = −log p).
2. **VAE latent = Gaussian** → Stat 110 çok-değişkenli normal (mean + diagonal covariance).
3. **Reparameterization = konum-ölçek** → Stat 110 $X=\mu+\sigma Z$ + Calculus zincir kuralı + Hafta 5 autograd.
4. **Contrastive SSL = invariance** → Hafta 3 (augmentation = stationarity/invariance).
5. **DAE = manifolda geri çek** → Hafta 7 autoencoder.
**İleriye köprüler (production / research):**
1. **Contrastive (MoCo kursta)** → SimCLR; "çok negatif" sorunu → **BYOL/VICReg/MAE (post-2020, KURSTA YOK)**.
2. **DAE maskeleme** → BERT (Hafta 12), MAE (post-2020).
3. **VAE** → diffusion (Hafta 9), latent diffusion (Stable Diffusion).
4. **Non-contrastive EBM** → LeCun JEPA programı (post-2020).
::: {.callout-important title="Bu dersten tek bir şey alıp gideceksen"}
EBM'yi eğitmek enerji fonksiyonunu şekillendirmektir, ve bunun iki yolu vardır — **contrastive** (veriyi aşağı, üretilmiş negatifi yukarı it; ama yüksek boyutta çok negatif ister) ve **non-contrastive/architectural** (yapıyla kısıtla, negatifsiz). VAE ikincisinin zarif örneğidir: encoder bir dağılım üretir, latent reparameterization ile örneklenir ($z = \mu + \sigma\epsilon$), ve Gaussian kısıt latent'i düzenli, üretken kılar. LeCun'un "çok negatif gerekir" sınırı ise kurstan sonraki non-contrastive devrimi (BYOL/VICReg/MAE — kursta yok) doğurdu.
:::