11  Ders 9A — Stable Diffusion Derinlemesine (Whitaker)

Misafir ders: Jonathan Whitaker (fastai), Ders 9’da kavramsal gördüğümüz hazır pipe()‘ı parça parça açar ve sampling loop’u sıfırdan replike eder. VAE ile latent uzaya inip çıkarız (0.18215 ölçek), CLIP ile metni embedding’e çeviririz, noise schedule (σ) ile gürültü ekleriz, classifier-free guidance’lı bir U-Net her adımda iki tahmin yapar, scheduler gürültüyü kısmen çıkarır. img2img bu mekanizmanın bir uygulamasıdır; en güçlüsü ise özel guidance: istediğin herhangi bir hedefin gradyanını sampling’e enjekte etmek — tam da Ders 9’un ’pixel gradyanı’ sezgisi.

NotBölüm bilgisi

11.1 Bu Derste Ne Var?

Misafir ders. Jonathan Whitaker (fastai), Ders 9’da kavramsal gördüğümüz hazır StableDiffusionPipeline’ı parça parça açar ve sampling loop’u sıfırdan replike eder. Ders 9 “üç bileşen + pixel gradyanı” sezgisini verdi; Whitaker o sezgiyi çalışan koda çevirir: VAE, tokenizer + CLIP, U-Net, scheduler ve classifier-free guidance birer ayrı bileşen olur, gürültü-giderme döngüsü elle kurulur.

Üç temel fikir bu dersin omurgasını kurar:

  1. Latent uzayda çalışma — VAE görüntüyü 4×64×64’e sıkıştırır; tüm diffusion bu küçük uzayda olur (pil_to_latent / latents_to_pil, 0.18215 ölçekleme) (VAE/latentencode/decode).
  2. Gürültü ekleme + sampling loop — eğitimde gürültü eklenir, çıkarımda σ çizelgesine göre adım adım çıkarılır; img2img bu mekanizmanın bir uygulamasıdır (noise schedulesampling döngüsü).
  3. Classifier-free guidance — her adımda iki tahmin (prompt’lu + prompt’suz) yapılır; fark guidance_scale ile çarpılıp prompt yönüne ittirilir (U-Net + CFG).

“We’re going to be basically replicating this code, but now we’ll be doing it ourselves.” — Whitaker, 0:43

Şekil 28.1 bu yapıyı tek bir yol haritasında birleştirir: hazır pipe() beş bileşene (VAE, CLIP, U-Net, scheduler, CFG) ayrılır; sampling loop bunları her adımda birleştirir (rastgele gürültü → CFG’li U-Net tahmini → scheduler.step → VAE decode); ve özel guidance, istediğin herhangi bir hedefin gradyanını döngüye enjekte eder — rose vurgulu “hazır pipe()” ve “sampling loop” düğümleri dersin doruğudur.

flowchart TD
    PIPE["hazır pipe()"]

    VAE["VAE<br/>(latent ↔ piksel, 0.18215)"]
    CLIP["tokenizer + CLIP<br/>(metin → embedding)"]
    UNET["U-Net<br/>(gürültü tahmin)"]
    SCHED["scheduler<br/>(σ noise schedule)"]
    CFG["classifier-free guidance<br/>(iki tahmin)"]

    PIPE --> VAE
    PIPE --> CLIP
    PIPE --> UNET
    PIPE --> SCHED
    PIPE --> CFG

    LOOP["sampling loop"]
    VAE --> LOOP
    CLIP --> LOOP
    UNET --> LOOP
    SCHED --> LOOP
    CFG --> LOOP

    L_START["rastgele gürültü"]
    L_STEP["her adım:<br/>CFG'li U-Net tahmini"]
    L_SCHED["scheduler.step"]
    L_DEC["VAE decode"]

    LOOP --> L_START
    L_START --> L_STEP
    L_STEP --> L_SCHED
    L_SCHED -->|"tekrar"| L_STEP
    L_SCHED --> L_DEC

    CUSTOM["özel guidance:<br/>herhangi hedefin gradyanı<br/>(Ders 9 köprüsü)"]
    L_STEP --> CUSTOM

    classDef cyan fill:#cffafe,stroke:#0e7490,stroke-width:2px,color:#1e293b;
    classDef rose fill:#ffe4e6,stroke:#e11d48,stroke-width:2px,color:#1e293b;

    class PIPE,LOOP rose;
    class VAE,CLIP,UNET,SCHED,CFG,L_START,L_STEP,L_SCHED,L_DEC,CUSTOM cyan;
Şekil 11.1: Whitaker’ın hazır pipe() çağrısını parça parça açışı: VAE, tokenizer+CLIP, U-Net, scheduler ve classifier-free guidance ayrı bileşenler; sampling loop bunları her adımda birleştirir, özel guidance ise herhangi bir hedefin gradyanını enjekte eder (Ders 9 köprüsü). (şematik)
İpucuBuilder Notu — pipe()’ın İçi: Sezgiden Çalışan Koda
  • Geriye (Ders 9): Ders 9’un “sihirli API + pixel gradyanı” sezgisi burada gerçek koda dönüşür — U-Net + scheduler + CFG. Pixel gradyanı fikri hem CFG’de hem özel guidance’ta somutlaşacak.
  • İleriye (Part 2 / NYU §4.J): Bu loop, Part 2 Ders 19-22’de (DDPM/DDIM) sıfırdan kurulacak; CFG ve guidance, enerji-tabanlı modellerle (NYU §4.J) kavramsal akraba.
  • Tek cümle: Whitaker, pipe()’ı VAE + CLIP + U-Net + scheduler + classifier-free guidance parçalarına ayırıp gürültü-giderme döngüsünü elle kurar.

11.2 Sampling Loop’u Replike Etmek

Whitaker hedefi koyar: hazır pipeline’ın yaptığını kendi kodumuzla yapmak — bir dizi sampling adımında latent’i gürültüden arındırıp görüntü üretmek. Önce parçaları (VAE, tokenizer, text_encoder, U-Net, scheduler) ayrı ayrı yükler; sonra sampling döngüsünde hepsini birleştirir. Yani konsept haritasının “hazır pipe() → beş bileşen → sampling loop” akışını gerçek kodla yürütür.

“You’ll see that we’re going to be basically replicating this code, going through a number of sampling time steps and regenerating an image.” — Whitaker, 0:43

İpucuBuilder Notu — Replikasyon: Düşük Seviye Taslağın Çalışır Hâli
  • Geriye (Ders 9): Ders 9’daki düşük seviye pipeline taslağı — beş adım — burada satır satır çalışır koda dönüşür.
  • Geriye (Ders 8): “Framework sihir değil; bizim kuracağımızın temiz hâli” disiplini (Ders 8) burada da geçerli — pipe()’ı söküp parçalarından yeniden kurarız.

11.3 Auto-Encoder (VAE) ve Latent Uzay

İlk bileşen VAE. Whitaker vurgular: Stable Diffusion bir latent diffusion modelidir — piksellerle değil, bir autoencoder’ın latent uzayında çalışır. VAE büyük görüntüyü 4×64×64’lük bir latent’e sıkıştırır. Tüm gürültü ekleme/çıkarma bu küçük uzayda olur; piksele yalnızca en sonda, decode ile geri çıkarız.

“This is a latent diffusion model, which means it doesn’t operate on pixels, it operates in the latent space of an autoencoder. We go from this big image down to this 4 by 64 by 64 latent representation.” — Whitaker, 1:21

İpucuBuilder Notu — Latent Diffusion: 786K → 16K’nın Kod Karşılığı
  • Geriye (Ders 9): Ders 9’daki VAE sıkıştırması (786K → 16K) burada pil_to_latent/latents_to_pil koduyla somutlaşır; latent uzay diffusion’ı ~48× ucuzlatır.
  • Geriye (Ders 8): VAE bir autoencoder’ın (Ders 8) olasılıksal (dağılımdan örnekleyen) hâlidir — encode/decode yapısı aynı, çıkışı bir dağılımdan örnekler.

11.4 Latent’i Kodlama ve Çözme

Whitaker iki yardımcı yazar: pil_to_latent (görüntü → latent, VAE encode) ve latents_to_pil (latent → görüntü, VAE decode). Dikkat: latent’ler 0.18215 ile ölçeklenir — modelin yazarlarının seçtiği, latent’leri U-Net’in eğitildiği büyüklüğe getiren bir normalleştirme sabiti; encode’da çarpılır, decode’da bölünür.

def pil_to_latent(input_im):
    with torch.no_grad():
        latent = vae.encode(tfms.ToTensor()(input_im).unsqueeze(0).to(torch_device)*2-1)
    return 0.18215 * latent.latent_dist.sample()   # olcekleme sabiti

def latents_to_pil(latents):
    latents = (1 / 0.18215) * latents
    with torch.no_grad():
        image = vae.decode(latents).sample
    return image   # (normalize + PIL'e cevirme adimlari izler)

“The decoded version — very impressive compression, this is a factor of [48]; we get nice high-resolution results even though we’re only working with these 64 by 64 latents.” — Whitaker, 1:21

Şekil 11.2 bu gidiş-dönüşü gerçek sayılarla gösterir: sol panel akış şemasıyla görüntü (3×512×512 = 786.432 sayı) → pil_to_latent (×0.18215) → latent (4×64×64 = 16.384 sayı) ve geri dönüşte latents_to_pil (÷0.18215); sağ panel iki büyüklüğü bar olarak karşılaştırır — ~48× az sayı. “Tüm diffusion bu küçük latent’te olur” mesajı dikey köprüyle vurgulanır.

Kod
d = E.latent_roundtrip_demo()
img_n = d["image_nums"]       # 786432
lat_n = d["latent_nums"]      # 16384
ratio = d["ratio"]            # 48.0
scale = d["scale"]            # 0.18215

fig, (axL, axR) = plt.subplots(
    1, 2, figsize=(12, 5), gridspec_kw={"width_ratios": [2.45, 1.0]}
)

# --- Sol panel: roundtrip akış şeması (boxed_node + arrow_between) ---
axL.set_xlim(0, 10)
axL.set_ylim(0, 10)
axL.axis("off")

# Üst sıra: görüntü → encode → latent
y_top = 7.0
viz.boxed_node(axL, 1.65, y_top, 2.7, 2.1,
               "görüntü\n3×512×512\n(786.432 sayı)",
               fc=viz.COL_BG, ec=viz.COL_PRIMARY, tc=viz.COL_TEXT,
               fontsize=10.5)
viz.boxed_node(axL, 5.0, y_top, 2.1, 1.25,
               "pil_to_latent\nVAE encode ×0.18215",
               fc=viz.COL_WHITE, ec=viz.COL_CYAN_700, tc=viz.COL_CYAN_700,
               fontsize=9.5, lw=1.8)
viz.boxed_node(axL, 8.35, y_top, 2.6, 2.1,
               "latent\n4×64×64\n(16.384 sayı)",
               fc=viz.COL_BG_ROSE, ec=viz.COL_ACCENT, tc=viz.COL_TEXT,
               fontsize=10.5)
viz.arrow_between(axL, (3.0, y_top), (3.95, y_top), color=viz.COL_PRIMARY)
viz.arrow_between(axL, (6.05, y_top), (7.05, y_top), color=viz.COL_PRIMARY)

# Alt sıra: latent → decode → görüntü (geri dönüş)
y_bot = 2.4
viz.boxed_node(axL, 8.35, y_bot, 2.6, 1.55,
               "latent 4×64×64\n← tüm diffusion burada →",
               fc=viz.COL_BG_ROSE, ec=viz.COL_ACCENT, tc=viz.COL_TEXT,
               fontsize=9.5)
viz.boxed_node(axL, 5.0, y_bot, 2.1, 1.25,
               "latents_to_pil\nVAE decode ÷0.18215",
               fc=viz.COL_WHITE, ec=viz.COL_CYAN_700, tc=viz.COL_CYAN_700,
               fontsize=9.5, lw=1.8)
viz.boxed_node(axL, 1.65, y_bot, 2.7, 1.55,
               "görüntü\n3×512×512",
               fc=viz.COL_BG, ec=viz.COL_PRIMARY, tc=viz.COL_TEXT,
               fontsize=10.5)
viz.arrow_between(axL, (7.05, y_bot), (6.05, y_bot), color=viz.COL_ACCENT)
viz.arrow_between(axL, (3.95, y_bot), (3.0, y_bot), color=viz.COL_ACCENT)

# Latent kutuları arası dikey köprü (diffusion = bu küçük uzayda)
viz.arrow_between(axL, (8.35, y_top - 1.05), (8.35, y_bot + 0.78),
                  color=viz.COL_SLATE_400, lw=1.8, style="-|>")

# 0.18215 ölçek + ~48× sıkıştırma vurgusu (ortada)
axL.text(5.0, 4.7, f"0.18215 ölçek sabiti", ha="center", va="center",
         fontsize=12, weight="bold", color=viz.COL_CYAN_700,
         bbox=dict(boxstyle="round,pad=0.35", fc=viz.COL_CYAN_50,
                   ec=viz.COL_CYAN_700, lw=1.5))
axL.text(5.0, 9.55, f"~{ratio:.0f}× sıkıştırma  (786.432 → 16.384)",
         ha="center", va="center", fontsize=11.5, weight="bold",
         color=viz.COL_ACCENT)
axL.text(8.35, 0.95, "tüm diffusion bu küçük latent'te olur",
         ha="center", va="center", fontsize=9.5, style="italic",
         color=viz.COL_TEXT)

# --- Sağ panel: 786432 vs 16384 boyut karşılaştırma bar ---
viz.apply_style(axR)
bars = axR.bar([0, 1], [img_n, lat_n],
               color=[viz.COL_PRIMARY, viz.COL_ACCENT], width=0.62, zorder=3)
axR.set_xticks([0, 1])
axR.set_xticklabels(["piksel\n3×512×512", "latent\n4×64×64"],
                    fontsize=10, color=viz.COL_TEXT)
axR.set_ylabel("sayı (eleman) adedi", fontsize=10)
axR.set_title("aynı görüntü, ~48× az sayı", fontsize=11,
              color=viz.COL_CYAN_700, weight="bold")
for b, val in zip(bars, [img_n, lat_n]):
    axR.text(b.get_x() + b.get_width() / 2, val,
             f"{val:,}".replace(",", "."), ha="center", va="bottom",
             fontsize=10, weight="bold", color=viz.COL_TEXT)
axR.set_ylim(0, img_n * 1.16)
axR.margins(x=0.18)

plt.tight_layout()
plt.show()
Şekil 11.2: VAE latent roundtrip: tüm diffusion bu küçük latent uzayda olur (gerçek sayılar: 3×512×512=786.432 → 4×64×64=16.384, ~48×; 0.18215 ölçek sabiti).
İpucuBuilder Notu — encode/decode: Arama Tablosu Değil, Öğrenilmiş Sıkıştırma
  • Geriye (Ders 8): Autoencoder encode/decode (Ders 8); VAE bunun olasılıksal (dağılımdan örnekleyen) hâli — latent_dist.sample() tam bu örneklemedir.
  • Builder ipucu: 0.18215 unutulursa latent’lerin büyüklüğü U-Net’in beklediğiyle uyuşmaz; encode’da çarp, decode’da böl — ölçeği roundtrip boyunca tutarlı tut.

11.5 Gürültü Ekleme ve Noise Schedule

Eğitimde görüntüye gürültü eklenir; model bu gürültüyü tahmin etmeyi öğrenir. Ne kadar gürültü eklendiğini noise schedule belirler. scheduler.sigmas her adımdaki gürültü miktarını verir: başta çok yüksek, sona doğru sıfıra iner. Çıkarımda 1000 değil, az adım (örn. 15-50) kullanılır — bu, konsept haritasındaki σ-çizelgesinin alt-örneklenmesidir.

scheduler.set_timesteps(15)
print(scheduler.sigmas)   # her adimdaki gurultu miktari (yuksek -> dusuk)
# gurultu ekleme: noisy = latent + noise * sigma
noisy = scheduler.add_noise(encoded, noise, timesteps)

“This sigma is the amount of noise added; we’re going to slowly slowly try and reduce this down until ideally we get a clean image.” — Whitaker, 8:44

Şekil 11.3 çizelgeyi gerçek hesaplamayla gösterir (SD v1 scaled_linear β: β_start=0.00085, β_end=0.012): sol panel tam σ eğrisini (1000 adım, \(\sigma_t = \sqrt{(1-\bar{\alpha}_t)/\bar{\alpha}_t}\), log eksen) ve ondan alt-örneklenen 15 adımı; sağ panel bu 15 sampling adımının σ değerlerinin \(\sigma_0 \approx 14.6\)’dan \(\sigma_{14} \approx 0\)’a inişini. Bu tepe değer scheduler.sigmas’ın tepesiyle (init_noise_sigma) eşleşir. Whitaker’ın “yavaş yavaş σ’yı sıfıra indir” dediği eğri tam budur — Ders 9’un \(\bar{\alpha}\) çizelgesinden farklı bir ölçek (σ, Karras/LMS).

Kod
d = E.sigma_schedule_demo()
t_full, sigma_full = d["t_full"], d["sigma_full"]
idx, sigmas, steps = d["idx"], d["sigmas"], d["steps"]

fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(11.5, 4.8))

# --- SOL: tam σ eğrisi + alt-örneklenen 15 adım ---
ax1.plot(t_full, sigma_full, color=COL_PRIMARY, lw=2.4,
         label="tam σ eğrisi (1000 adım)", zorder=2)
ax1.scatter(idx, sigmas, color=COL_ACCENT, s=55, zorder=4,
            edgecolor=COL_WHITE, linewidth=1.0,
            label="alt-örneklenen 15 adım")
ax1.set_yscale("log")
ax1.set_xlabel("difüzyon adımı t  (0 = temiz, 1000 = saf gürültü)")
ax1.set_ylabel("σ  (gürültü standart sapması, log)")
ax1.set_title("σ_t = √((1−ᾱ_t)/ᾱ_t) — çıkarım çizelgesi", color=COL_CYAN_700)
viz.apply_style(ax1)
ax1.legend(loc="upper left", framealpha=0.9, fontsize=9)
ax1.annotate("ᾱ değil: σ, Karras/LMS ölçeği\n(L9 ᾱ schedule'ından farklı)",
             xy=(idx[0], sigmas[0]), xytext=(330, sigmas[0] * 1.6),
             fontsize=8.8, color=COL_TEXT,
             arrowprops=dict(arrowstyle="->", color=COL_SLATE_400, lw=1.4))

# --- SAĞ: 15 sampling adımının σ değerleri ---
ax2.plot(steps, sigmas, color=COL_CYAN_400, lw=1.8, zorder=2)
ax2.scatter(steps, sigmas, color=COL_ACCENT, s=45, zorder=4,
            edgecolor=COL_WHITE, linewidth=1.0)
ax2.set_xlabel("sampling adımı  (0 → 14)")
ax2.set_ylabel("σ  (o adımın gürültü miktarı)")
ax2.set_title("15 adımlık döngü: çok gürültü → temiz", color=COL_CYAN_700)
viz.apply_style(ax2)
ax2.annotate(f"başta çok gürültü\nσ₀ ≈ {sigmas[0]:.0f}",
             xy=(steps[0], sigmas[0]),
             xytext=(steps[0] + 2.2, sigmas[0] * 0.78),
             fontsize=9, color=COL_TEXT, weight="bold",
             arrowprops=dict(arrowstyle="->", color=COL_ACCENT, lw=1.6))
ax2.annotate(f"sonda temiz\nσ₁₄ ≈ {sigmas[-1]:.2f}",
             xy=(steps[-1], sigmas[-1]),
             xytext=(steps[-1] - 5.6, sigmas[0] * 0.34),
             fontsize=9, color=COL_TEXT, weight="bold",
             arrowprops=dict(arrowstyle="->", color=COL_PRIMARY, lw=1.6))

plt.tight_layout()
plt.show()
Şekil 11.3: Çıkarımda gürültü çizelgesi σ — sol: tam σ eğrisi (1000 adım) 15 adıma alt-örneklenir; sağ: 15 sampling adımının σ değerleri ~14.6’dan ~0’a iner. (gerçek hesaplama: SD v1 scaled_linear β, σ_t=√((1−ᾱ_t)/ᾱ_t), 15 adıma alt-örneklenmiş; L9’un ᾱ schedule’ından farklı ölçek)
İpucuBuilder Notu — σ Çizelgesi: DDPM/Karras’ın Çekirdeği
  • İleriye (Ders 19-22): β/σ noise schedule, DDPM (Ders 19) ve Karras (Ders 22) ile Part 2’de sıfırdan kurulur (§4.I); Şekil 11.3’ın σ ölçeği orada \(\bar{\alpha}\) çizelgesiyle karşılaştırılır.
  • Geriye (Ders 9): Bu σ, Ders 9’un forward diffusion formülü \(x_t = \sqrt{\bar{\alpha}_t}\,x_0 + \sqrt{1-\bar{\alpha}_t}\,\varepsilon\)’nun çıkarım-zamanı yeniden parametrizasyonudur.

11.6 img2img: Gürültüleyip Yeniden Çizme

img2img basit bir uygulamadır: bir görüntüyü latent’e çevir, belirli bir adıma kadar gürültüle (sıfıra değil), sonra oradan sampling’e devam et. Ne kadar gürültülersen o kadar değişir — bunu strength belirler. Yani σ çizelgesine baştan değil, ortadan gireriz.

“I’m now going to start with this noisy version of my input image and I’m going to [continue sampling from there].” — Whitaker, 3:55

Şekil 11.4 bu girişi σ çizelgesi üstünde gösterir: cyan eğri 15 adımlık σ inişi; her strength için rose dikey çizgi + işaretçi başlangıç noktasını işaretler — strength=0.8 erken başlar (σ≈4.75, çok gürültü → büyük değişim), strength=0.5 ortadan (σ≈1.61, dengeli), strength=0.2 geç (σ≈0.61, orijinale yakın). Çekirdek sezgi: sıfır gürültüden değil, kısmen gürültülenmiş latent’ten başla.

Kod
d = E.img2img_demo()
steps = d["steps"]
sigmas = d["sigmas"]
start_info = d["start_info"]   # [(strength, start_step, sigma), ...]

fig, ax = plt.subplots(figsize=(9.5, 5.2))

# σ schedule eğrisi (cyan): adım ilerledikçe gürültü σ yüksekten ~0'a iner
ax.plot(steps, sigmas, "-o", color=COL_PRIMARY, lw=2.4, markersize=6,
        markerfacecolor=COL_WHITE, markeredgecolor=COL_PRIMARY,
        markeredgewidth=1.8, zorder=3, label="σ schedule (15 adım)")

# Her strength için BAŞLANGIÇ noktası: rose dikey çizgi + işaretçi + annotate
notes = {
    0.8: ("strength=0.8 → erken başla\nσ≈4.75, çok gürültü → büyük değişim", 12.5, COL_ACCENT),
    0.5: ("strength=0.5 → orta\nσ≈1.61, dengeli değişim", 8.5, COL_ROSE_500),
    0.2: ("strength=0.2 → geç başla\nσ≈0.61, orijinale yakın", 4.5, COL_ROSE_400),
}
for strength, start_step, sigma in start_info:
    txt, ytext, col = notes[strength]
    ax.axvline(start_step, color=col, lw=1.8, ls="--", alpha=0.75, zorder=2)
    ax.plot(start_step, sigma, "D", color=col, markersize=11,
            markeredgecolor=COL_WHITE, markeredgewidth=1.6, zorder=5)
    ax.annotate(txt, xy=(start_step, sigma),
                xytext=(start_step + 1.4, ytext),
                fontsize=9.5, color=COL_TEXT, weight="bold", va="center",
                bbox=dict(boxstyle="round,pad=0.4", fc=COL_BG_ROSE,
                          ec=col, lw=1.4),
                arrowprops=dict(arrowstyle="-|>", color=col, lw=1.6,
                                shrinkA=4, shrinkB=8))

# Çekirdek sezgi annotate'i
ax.text(0.97, 0.95,
        "img2img: sıfır gürültüden değil,\nkısmen gürültülenmiş latent'ten başla",
        transform=ax.transAxes, ha="right", va="top",
        fontsize=10.5, color=COL_CYAN_700, weight="bold",
        bbox=dict(boxstyle="round,pad=0.5", fc=COL_BG,
                  ec=COL_PRIMARY, lw=2.0))

ax.set_xlabel("sampling adımı (yüksek t → düşük t)")
ax.set_ylabel("σ  (gürültü standart sapması)")
ax.set_xlim(-0.5, len(steps) - 0.5 + 6)
ax.set_xticks(list(steps))
viz.apply_style(ax)
ax.legend(loc="center right", frameon=True, fontsize=9.5)

plt.tight_layout()
plt.show()
Şekil 11.4: img2img: σ schedule eğrisi üstünde her strength’in başlangıç noktası. strength → sampling’in hangi σ’dan başlayacağını belirler; yüksek strength = çok gürültü = büyük değişim. (gerçek schedule: strength → sampling’in hangi σ’dan başlayacağı; yüksek strength = çok gürültü = büyük değişim)
İpucuBuilder Notu — strength: Yörüngeye Hangi σ’dan Girersin
  • Geriye (Ders 9): Ders 9’daki img2img strength’inin iç mekanizması budur: “ne kadar adımdan / hangi σ’dan başlanacağı”.
  • Builder ipucu: strength orijinalin ne kadarının korunacağını ayarlar — düşük strength stil aktarımı/ince düzenleme, yüksek strength neredeyse sıfırdan üretim; aynı mekanizma inpainting ve DiffEdit’in temelidir.

11.7 Text Encoding: Tokenizer → CLIP

Prompt önce token’lara bölünür (tokenizer), sonra CLIP text encoder bunları embedding dizisine çevirir. Whitaker hazır pipe’ın atladığı bu adımları açıkça gösterir; bu embedding dizisi, sampling döngüsünde U-Net’i koşullar.

text_input = tokenizer(prompt, padding="max_length",
    max_length=tokenizer.model_max_length, truncation=True, return_tensors="pt")
output_embeddings = text_encoder(text_input.input_ids.to(torch_device))[0]

“How do we get from this sequence of tokens to [embeddings]? We look at the text_encoder embeddings.” — Whitaker, 8:44

İpucuBuilder Notu — CLIP Koşullama: Tokenize + Embedding’in Çok-Modal Hâli
  • Geriye (Ders 4/7): Tokenization (Ders 4) + embedding (Ders 7); CLIP, metin embedding’lerini U-Net’e koşullama olarak verir.
  • İleriye: Bu embedding dizisinin tek tek satırlarına müdahale token oynama ve textual inversion’ın kapısını açar.

11.8 Token Embeddings ile Oynama

Whitaker embedding’lere token düzeyinde müdahale edebileceğimizi gösterir: bir token’ın embedding’ini alıp değiştirebilir veya yenisini ekleyebiliriz. Bu, CLIP’in ürettiği dizinin tek bir satırını okuyup yazmaktır — textual inversion’ın temelidir.

prompt = 'skunk'
print(tokenizer(prompt))                  # kelime -> token id'leri
token_emb_layer(torch.tensor([8797]))     # token id -> embedding vektoru
İpucuBuilder Notu — Embedding = Arama Tablosu Satırı
  • Geriye (Ders 7/8): Embedding bir arama tablosudur (Ders 7); burada o tablonun tek bir satırını (token_emb_layer[8797]) okuyup değiştiriyoruz.
  • Builder ipucu: Token id → embedding eşlemesi türevlenebilir olduğundan, bu tek satır eğitilebilir bir parametredir — textual inversion tam bu satırı öğretir.

11.9 Textual Inversion

Textual inversion: yeni bir kavram için (örn. özel bir stil) yeni bir embedding eğitilir ve token sözlüğüne eklenir. Whitaker, eğitilmiş bir embedding’i prompt’un token embedding’leri arasına ekleyerek o kavramı çağırır — yani token oynama fikrini öğrenilmiş bir satırla yapar.

“I’m inserting them into my set of token embeddings for my prompt.” — Whitaker, 15:03

İpucuBuilder Notu — Tek Satır Eğit, Kavramı Çağır
  • Geriye (Ders 9): Ders 9’da textual inversion/DreamBooth kavramsal verildi; burada eğitilmiş embedding’i prompt dizisine elle enjekte ederek mekanizmasını görüyoruz.
  • İleriye (production): Textual inversion (tek embedding) + DreamBooth (model ağırlıkları), kişiselleştirilmiş üretimin (özel stil/nesne) standart yöntemleridir.

11.10 U-Net ve Classifier-Free Guidance

En kritik teknik: classifier-free guidance (CFG). Her sampling adımında U-Net iki tahmin yapar — biri prompt’la (koşullu), biri boş prompt’la (koşulsuz). Sonuç: \[\text{noise} = \text{uncond} + s \cdot (\text{text} - \text{uncond})\] Yani “prompt’un yönüne” ittiririz; \(s\) (guidance_scale) 1’den büyükse prompt etkisini abartırız. Bu yön farkını sampling döngüsünde her adımda hesaplarız.

“We get out two predictions for the noise… guidance_scale times the difference. If I predict without the prompt [and with], I’d like to move more in that direction, push it even further.” — Whitaker, 18:38

Şekil 11.5 bu iki-tahmin yapısını vektör aritmetiğiyle gösterir: noise_uncond (boş prompt) ile noise_text (prompt) iki nokta; aralarındaki rose ok fark = text − uncond (prompt yönü). Her scale için (\(s = 1, 7, 20\)) cyan oklar pred = uncond + s·(text − uncond)’u çizer — \(s\) büyüdükçe tahmin koşullu noktanın ötesine taşar. Whitaker’ın “farkı daha da abart” dediği şey tam bu uzayan oktur.

Kod
d = E.cfg_twopred_demo()
uncond = d["uncond"]
text = d["text"]
preds = d["preds"]
diff = text - uncond            # prompt yönü (text − uncond)

fig, ax = plt.subplots(figsize=(8, 6))

# --- prompt yönü farkı: uncond -> text (kalın rose ok) ---
viz.arrow_between(ax, tuple(uncond), tuple(text),
                  color=COL_ACCENT, lw=3.0, mutation_scale=22, shrink=0)
ax.annotate("fark = text − uncond\n(prompt yönü)",
            xy=(uncond + 0.5 * diff), xytext=(uncond + 0.5 * diff + np.array([-0.95, 0.55])),
            fontsize=10.5, color=COL_ACCENT, weight="bold", ha="center",
            arrowprops=dict(arrowstyle="-", color=COL_ROSE_400, lw=1.2))

# --- CFG tahmin zinciri: uncond'dan her scale için pred (cyan oklar) ---
scale_list = sorted(preds.keys())
chain_cols = [COL_CYAN_400, COL_PRIMARY, COL_CYAN_700]
for s, col in zip(scale_list, chain_cols):
    p = preds[s]
    viz.arrow_between(ax, tuple(uncond), tuple(p),
                      color=col, lw=2.0, mutation_scale=16, shrink=0,
                      style="-|>")
    ax.scatter([p[0]], [p[1]], s=70, color=col, zorder=5,
               edgecolor=COL_WHITE, linewidth=1.2)
    ax.annotate(f"pred (s={int(s)})", xy=(p[0], p[1]),
                xytext=(p[0] + 0.18, p[1] - 0.30 if s > 1 else p[1] + 0.22),
                fontsize=10, color=col, weight="bold")

# --- iki temel tahmin noktası ---
ax.scatter([uncond[0]], [uncond[1]], s=150, color=COL_SLATE_400, zorder=6,
           edgecolor=COL_TEXT, linewidth=1.4)
ax.annotate("noise_uncond\n(boş prompt)", xy=tuple(uncond),
            xytext=(uncond[0] - 0.05, uncond[1] - 0.55),
            fontsize=10.5, color=COL_TEXT, weight="bold", ha="center")
ax.scatter([text[0]], [text[1]], s=150, color=COL_ACCENT, zorder=6,
           edgecolor=COL_TEXT, linewidth=1.4)
ax.annotate("noise_text\n(prompt)", xy=tuple(text),
            xytext=(text[0] - 0.55, text[1] + 0.45),
            fontsize=10.5, color=COL_ACCENT, weight="bold", ha="center")

# "scale ile bu farkı abart" notu (s=20 ucu civarı)
p_max = preds[max(scale_list)]
ax.annotate("scale ile bu farkı abart\n(s=20 → çok ötesine taşar)",
            xy=tuple(p_max), xytext=(p_max[0] - 4.2, p_max[1] + 0.6),
            fontsize=10, color=COL_CYAN_700, weight="bold", ha="left",
            arrowprops=dict(arrowstyle="-|>", color=COL_CYAN_700, lw=1.4))

viz.apply_style(ax)
ax.set_xlabel("gürültü-tahmin boyutu 1")
ax.set_ylabel("gürültü-tahmin boyutu 2")
ax.set_title("CFG: pred = uncond + s·(text − uncond)", color=COL_TEXT, weight="bold")
ax.axhline(0, color=COL_SLATE_400, lw=0.8, alpha=0.4)
ax.axvline(0, color=COL_SLATE_400, lw=0.8, alpha=0.4)
ax.set_xlim(-5.0, 21.0)
ax.set_ylim(-1.5, 16.5)

plt.tight_layout()
plt.show()
Şekil 11.5: Classifier-free guidance iki-tahmin yapısı: U-Net her adımda noise_uncond (boş prompt) ve noise_text (prompt) üretir; birleştirme noise_uncond + s·(noise_text − noise_uncond). Scale (Whitaker 1/7/20) prompt yönü farkını abartır. (gerçek hesaplama: CFG iki-tahmin vektör aritmetiği, Whitaker scale 1/7/20)
İpucuBuilder Notu — CFG: İki Tahminin Farkını Abartmak
  • Geriye (Ders 9): Ders 9’daki guidance_scale’in iç mekanizması; “iki tahminin farkını (\(s\) ile) abartmak” — sınıflandırıcı eğitmeden (classifier-free) prompt etkisini güçlendirmek. Maliyeti: adım başına iki U-Net geçişi.
  • Geriye (NYU §4.J): CFG, koşullu/koşulsuz enerji farkını kullanır — enerji-tabanlı modellerle (NYU §4.J) kavramsal akraba; “fark = yön” sezgisi ortak.

11.11 Sampling Döngüsü

Her şey burada birleşir: rastgele gürültüden başla, her timestep’te CFG ile gürültüyü tahmin et, scheduler ile kısmen çıkar, tekrarla. Konsept haritasının sampling-loop kolu, satır satır budur.

for i, t in enumerate(scheduler.timesteps):
    latent_model_input = torch.cat([latents] * 2)              # kosullu + kosulsuz
    noise_pred = unet(latent_model_input, t, text_embeddings).sample
    noise_uncond, noise_text = noise_pred.chunk(2)
    noise_pred = noise_uncond + guidance_scale * (noise_text - noise_uncond)  # CFG
    latents = scheduler.step(noise_pred, t, latents).prev_sample              # gurultuyu kismen cikar

“Current noisy latents minus sigma times the model prediction.” — Whitaker, 19:38

Şekil 11.6 bu döngüyü uçtan uca şematize eder — bu dersin flagship diyagramıdır. Üstte rastgele gürültü latent (× init σ); büyük kesikli çerçeve içinde beş sıralı adım: (1) cat([latents]×2) (koşullu + koşulsuz birlikte), (2) unet(input, t, text_emb) (her iki dal için gürültü tahmini), (3) chunk(2) (iki tahmine ayır), (4) rose vurgulu CFG birleştirme uncond + s·(text − uncond), (5) scheduler.step (gürültüyü kısmen çıkar); rose geri-besleme oku “sonraki t”ye döner. Döngü bitince latents_to_pil (VAE decode) → görüntü.

Kod
fig, ax = plt.subplots(figsize=(12, 7))
ax.set_xlim(0, 12)
ax.set_ylim(0, 14)
ax.axis("off")

# --- Giriş: rastgele gürültü latent ---
viz.boxed_node(ax, 6.0, 13.2, 6.6, 1.0,
               "rastgele gürültü latent  (× init σ)",
               fc=COL_BG, ec=COL_PRIMARY, fontsize=11)

# --- Büyük DÖNGÜ çerçevesi ---
loop_box = FancyBboxPatch((0.55, 2.55), 10.9, 9.55,
                          boxstyle="round,pad=0.02,rounding_size=0.10",
                          fc=COL_CYAN_50, ec=COL_CYAN_700, linewidth=2.4,
                          linestyle="--", zorder=1)
ax.add_patch(loop_box)
ax.text(6.0, 11.55, "for t in scheduler.timesteps:   (döngü)",
        ha="center", va="center", fontsize=11.5, weight="bold",
        color=COL_CYAN_700, zorder=2)

# --- Döngü içi 5 sıralı adım ---
step_specs = [
    (1, 10.30, "latent_model_input = cat([latents] × 2)\n(koşullu + koşulsuz birlikte)",
     COL_BG, COL_PRIMARY, COL_TEXT),
    (2, 8.70, "noise_pred = unet(input, t, text_emb)\n(her iki dal için gürültü tahmini)",
     COL_BG, COL_PRIMARY, COL_TEXT),
    (3, 7.10, "noise_uncond, noise_text = noise_pred.chunk(2)\n(iki tahmine ayır)",
     COL_BG, COL_PRIMARY, COL_TEXT),
    (4, 5.50, "noise = noise_uncond + scale · (noise_text − noise_uncond)\n(CFG birleştir)",
     COL_BG_ROSE, COL_ACCENT, COL_TEXT),
    (5, 3.55, "latents = scheduler.step(noise, t, latents)\n(gürültüyü kısmen çıkar)",
     COL_BG, COL_PRIMARY, COL_TEXT),
]
centers = {}
for num, y, text, fc, ec, tc in step_specs:
    viz.boxed_node(ax, 6.7, y, 8.2, 1.05, text, fc=fc, ec=ec, tc=tc,
                   fontsize=10, lw=2.2)
    # numara rozeti (kutu solunda)
    badge_x = 1.95
    ax.add_patch(plt.Circle((badge_x, y), 0.34, fc=ec, ec=ec, zorder=4))
    ax.text(badge_x, y, str(num), ha="center", va="center",
            color=COL_WHITE, fontsize=12, weight="bold", zorder=5)
    centers[num] = y

# --- Adımlar arası dikey oklar ---
for a, b in [(1, 2), (2, 3), (3, 4), (4, 5)]:
    viz.arrow_between(ax, (6.7, centers[a] - 0.55), (6.7, centers[b] + 0.55),
                      color=COL_CYAN_700, lw=2.2, shrink=2)

# giriş latent -> döngü çerçevesi -> adım 1
viz.arrow_between(ax, (6.0, 12.7), (6.0, 12.1), color=COL_PRIMARY, lw=2.2, shrink=2)
viz.arrow_between(ax, (6.7, 11.25), (6.7, centers[1] + 0.55),
                  color=COL_CYAN_700, lw=2.2, shrink=2)

# --- Döngü geri-besleme oku (adım 5 -> döngü başı, sol kenardan) ---
viz.arrow_between(ax, (2.6, centers[5]), (1.05, 7.3),
                  color=COL_ROSE_400, lw=2.0,
                  connectionstyle="arc3,rad=-0.45", shrink=4)
viz.arrow_between(ax, (1.05, 7.3), (2.6, centers[1]),
                  color=COL_ROSE_400, lw=2.0,
                  connectionstyle="arc3,rad=-0.45", shrink=4)
ax.text(0.78, 7.3, "sonraki t", ha="center", va="center", rotation=90,
        fontsize=9.5, weight="bold", color=COL_ACCENT)

# --- Döngü çıkışı: VAE decode -> görüntü ---
viz.arrow_between(ax, (6.0, 2.5), (6.0, 1.95), color=COL_PRIMARY, lw=2.2, shrink=2)
viz.boxed_node(ax, 6.0, 1.45, 5.6, 0.95,
               "latents_to_pil  (VAE decode)",
               fc=COL_BG, ec=COL_PRIMARY, fontsize=10.5)
viz.arrow_between(ax, (6.0, 0.97), (6.0, 0.55), color=COL_PRIMARY, lw=2.2, shrink=2)
viz.boxed_node(ax, 6.0, 0.40, 3.2, 0.78, "görüntü",
               fc=COL_BG_ROSE, ec=COL_ACCENT, fontsize=11)

plt.tight_layout()
plt.show()
Şekil 11.6: (şematik: Whitaker’ın sampling loop’u — her adım CFG’li U-Net tahmini + scheduler.step ile gürültü giderme)
İpucuBuilder Notu — scheduler.step = Diffusion’ın Gradient Adımı
  • Geriye (Ders 5): scheduler.step = Ders 5’teki “gradient adımı”nın diffusion karşılığı — latent’i biraz daha temize çeker; Whitaker’ın “current latents − σ × prediction” cümlesi tam bu adımdır.
  • Geriye (Ders 9): Bu döngü, Ders 9’un düşük seviye pipeline beş adımının (CLIP → uncond emb → latent → U-Net+scheduler döngüsü → VAE decode) çalışan hâlidir.

11.12 Özel Guidance: Kendi Guidance Fonksiyonun

Whitaker’ın güçlü gösterisi: sampling’e kendi guidance fonksiyonunu ekleyebilirsin. Örnek olarak “görüntü daha mavi olsun” hedefi koyar — her adımda latent’i, mavi kanal hatasının gradyanı yönünde de ittirir. Bu, Ders 9’daki “pixel gradyanı” sezgisinin doğrudan uygulamasıdır: CFG modelin öğrendiği yönü kullanırken, özel guidance istediğin herhangi bir türevlenebilir hedefi enjekte eder.

“My error is going to be the difference between the blue channel [and target], and [I push the latents by its gradient].” — Whitaker, 36:32

Şekil 11.7 bunu gerçek hesaplamayla gösterir: sol panel başlangıç (nötr) görüntüsü, orta panel guidance sonrası (gözle görülür daha mavi), sağ panel mavi-kanal MSE kayıp eğrisinin gerçek düşüşü (~0.27 → ~0). Her adımda mavi kanalı hedefe (0.9) yaklaştıran gradyan yönünde itme; alt not bunun Ders 9’un pixel-gradient sezgisinin doğrudan uygulaması olduğunu vurgular.

Kod
d = E.custom_guidance_demo()
start_rgb = d["start_rgb"]
end_rgb = d["end_rgb"]
target = d["target"]
loss = d["loss_curve"]

fig, (ax0, ax1, ax2) = plt.subplots(1, 3, figsize=(12, 4.5))

# --- SOL: başlangıç (nötr) görüntü ---
ax0.imshow(start_rgb, interpolation="nearest")
ax0.set_title("başlangıç (nötr)", color=COL_TEXT, fontsize=11, weight="bold")
ax0.set_xticks([]); ax0.set_yticks([])
for s in ax0.spines.values():
    s.set_edgecolor(COL_SLATE_400); s.set_linewidth(1.5)

# --- ORTA: mavi-kanal hedefe itilmiş görüntü ---
ax1.imshow(end_rgb, interpolation="nearest")
ax1.set_title("guidance sonrası (daha mavi)", color=COL_CYAN_700,
              fontsize=11, weight="bold")
ax1.set_xticks([]); ax1.set_yticks([])
for s in ax1.spines.values():
    s.set_edgecolor(COL_PRIMARY); s.set_linewidth(2.0)
ax1.annotate("her adım: mavi kanalı\nhedefe (0.9) yaklaştıran\ngradyan yönünde ittir",
             xy=(0.5, -0.10), xycoords="axes fraction",
             ha="center", va="top", fontsize=8.8, color=COL_TEXT,
             weight="bold",
             bbox=dict(boxstyle="round,pad=0.35", fc=COL_BG, ec=COL_PRIMARY, lw=1.5))

# --- SAĞ: MSE kayıp eğrisi (gerçek düşüş ~0.27 → 0) ---
ax2.plot(range(len(loss)), loss, color=COL_PRIMARY, lw=2.6,
         marker="o", markersize=4.5, markerfacecolor=COL_WHITE,
         markeredgecolor=COL_PRIMARY, zorder=3)
ax2.scatter([0], [loss[0]], color=COL_ACCENT, s=55, zorder=4)
ax2.annotate(f"{loss[0]:.2f}", xy=(0, loss[0]), xytext=(1.0, loss[0]),
             color=COL_ACCENT, fontsize=9.5, weight="bold", va="center")
ax2.annotate(f"{loss[-1]:.3f}", xy=(len(loss) - 1, loss[-1]),
             xytext=(len(loss) - 1, loss[-1] + 0.025),
             color=COL_CYAN_700, fontsize=9.5, weight="bold", ha="right")
ax2.set_title("mavi-kanal MSE (gerçek düşüş)", color=COL_TEXT,
              fontsize=11, weight="bold")
ax2.set_xlabel("guidance adımı", color=COL_TEXT, fontsize=10)
ax2.set_ylabel("MSE(mavi, 0.9)", color=COL_TEXT, fontsize=10)
viz.apply_style(ax2)
ax2.set_ylim(bottom=0)

fig.text(0.5, 0.015,
         "= Ders 9 pixel-gradient sezgisinin doğrudan uygulaması "
         "(CFG modelin yönünü kullanır; özel guidance İSTEDİĞİN hedefin gradyanını enjekte eder)",
         ha="center", va="bottom", fontsize=8.6, color=COL_CYAN_700, style="italic")

plt.tight_layout(rect=(0, 0.05, 1, 1))
plt.show()
Şekil 11.7: Özel guidance — mavi-kanal MSE’sinin gradyanı ile görüntüyü hedefe doğru ittir (gerçek hesaplama: mavi-kanal MSE’sinin gradyanı ile görüntüyü ittir — özel guidance = istediğin hedefin gradyanı)
İpucuBuilder Notu — Özel Guidance: Herhangi Bir Loss’un Gradyanıyla Yönlendir
  • Geriye (Ders 3/9): Bu tam Ders 9’un “sihirli fonksiyon gradyanı” fikri — istediğin herhangi bir hedef fonksiyonun (renk, CLIP benzerliği, kenar haritası) gradyanıyla üretimi yönlendirme; f.backward() aynı autograd (Ders 3).
  • İleriye: Özel guidance, CLIP-guided diffusion ve ControlNet gibi yönlendirme tekniklerinin temel mekanizmasıdır — üretimi bir loss’un gradyanıyla itmek.

11.13 Kapanış

Whitaker, hazır pipe()’ı tamamen açtı: VAE ile latent’e gir/çık, noise schedule ile gürültü ekle, CLIP ile metni kodla, U-Net + classifier-free guidance ile her adımda gürültüyü tahmin et, scheduler ile çıkar. Ders 9’un sezgisi (pixel gradyanı) burada hem CFG’de hem özel guidance’ta somutlaştı.

“In Part 2 we’re going to build our own everything from scratch.” — Howard, 0:45 (Ders 9)

Şekil 11.8 dersin sentezidir: solda 9A’da yazdığımız kod parçaları (rose), sağda her parçanın hangi dersin / hangi sezginin uygulaması olduğu (cyan). Sampling loop ↔︎ Ders 9 döngü sezgisi + Ders 5 gradient adımı; CFG ↔︎ Ders 9 guidance_scale + NYU §4.J EBM; VAE latent + 0.18215 ↔︎ Ders 8/9 autoencoder; CLIP encoding ↔︎ Ders 4/7 tokenize + embedding; özel guidance ↔︎ Ders 9 pixel gradyanı. Yeni olan parçalar değil, çalışan koda dönüşleridir.

Kod
fig, ax = plt.subplots(figsize=(12, 6.5))
ax.set_xlim(0, 12)
ax.set_ylim(0, 6.5)
ax.axis("off")

# Başlık şeritleri
ax.text(2.55, 6.15, "9A'da gördüğümüz (kod)", ha="center", va="center",
        fontsize=12.5, weight="bold", color=COL_ACCENT)
ax.text(9.35, 6.15, "nereden / hangi sezgi", ha="center", va="center",
        fontsize=12.5, weight="bold", color=COL_CYAN_700)

# 5 satır: (sol kod parçası rose, sağ kaynak cyan)
rows = [
    ("sampling loop\n(CFG'li U-Net + scheduler.step)",
     "Ders 9: gürültü-giderme\ndöngüsü sezgisi + Ders 5\ngradient adımı"),
    ("classifier-free guidance\n(iki tahmin)",
     "Ders 9: guidance_scale'in\niç mekanizması;\nNYU §4.J EBM akrabası"),
    ("VAE latent + 0.18215",
     "Ders 8/9: autoencoder,\nlatent diffusion ~48×"),
    ("CLIP text encoding",
     "Ders 4/7: tokenize\n+ embedding"),
    ("özel guidance\n(mavi-kanal gradyanı)",
     "Ders 9: sihirli-fonksiyon\npixel gradyanı — herhangi\ntürevlenebilir hedef"),
]

ys = [5.35, 4.25, 3.15, 2.05, 0.85]
hs = [0.78, 0.72, 0.62, 0.62, 0.82]

x_left, w_left = 2.55, 3.9
x_right, w_right = 9.35, 4.0

for (code, src), y, h in zip(rows, ys, hs):
    # SOL — 9A kod parçası (rose)
    viz.boxed_node(ax, x_left, y, w_left, h, code,
                   fc=COL_BG_ROSE, ec=COL_ACCENT, tc=COL_TEXT,
                   fontsize=9.8, lw=2.0)
    # SAĞ — kaynak / sezgi (cyan)
    viz.boxed_node(ax, x_right, y, w_right, h, src,
                   fc=COL_BG, ec=COL_PRIMARY, tc=COL_TEXT,
                   fontsize=9.4, lw=2.0)
    # ok: kod → kaynak (cyan, "şu sezginin uygulaması")
    viz.arrow_between(ax, (x_left + w_left / 2, y), (x_right - w_right / 2, y),
                      color=COL_CYAN_700, lw=2.0, shrink=6)

plt.tight_layout()
plt.show()
Şekil 11.8: 9A = Ders 9’un sezgisinin KODA dönüşü. Solda 9A’da yazdığımız kod parçaları (rose), sağda her parçanın hangi dersin / hangi sezginin uygulaması olduğu (cyan). (sentez: 9A, Ders 9’un sezgisini çalışan koda çevirir — CFG, latent, sampling loop, özel guidance)
İpucuBuilder Notu — 9A → 9B/10: Sezgiden Matematiğe ve Temellere
  • İleriye (Ders 9B): Ders 9B (Waseem + Tanishq, Stability AI) bu sürecin matematiğini açar — forward/reverse process, noise schedule’ın türetimi, score ve olasılıksal çerçeve.
  • İleriye (Ders 10): Ders 10 “from the foundations”ı başlatır — matmul’dan backprop’a, her şeyi sıfırdan.

11.14 Bu Dersin Özeti

  1. Stable Diffusion latent diffusion’dır: VAE görüntüyü 4×64×64 latent’e sıkıştırır, tüm iş orada olur (0.18215 ölçekleme) (VAE/latent).
  2. pil_to_latent/latents_to_pil ile latent’e gir/çık; ~48× sıkıştırmaya rağmen yüksek kalite (encode/decode).
  3. Noise schedule (σ) her adımda eklenen/çıkarılan gürültüyü belirler; img2img bunun bir uygulamasıdır (noise schedule, img2img).
  4. Prompt tokenizer → CLIP ile embedding’e çevrilir; token embedding’lerine müdahale textual inversion’ı sağlar (text encoding, textual inversion).
  5. Classifier-free guidance: her adımda iki tahmin (koşullu + koşulsuz); sonuç = koşulsuz + \(s\) × (koşullu − koşulsuz) (CFG).
  6. Sampling loop: rastgele gürültü → her adımda CFG ile gürültü tahmini → scheduler.step ile çıkar → tekrarla (sampling döngüsü).
  7. Özel guidance ile herhangi bir hedef fonksiyonun gradyanı sampling’e eklenebilir (örn. “daha mavi”) (özel guidance).
  8. Bu loop, Ders 9’un “pixel gradyanı” sezgisinin tam kod karşılığıdır (kapanış).
ÖnemliTek Bir Cümle

Stable Diffusion’ın hazır pipeline’ı; VAE ile latent uzaya inip, CLIP ile metni kodlayıp, her adımda classifier-free guidance’lı bir U-Net tahminiyle gürültüyü scheduler üzerinden adım adım gideren bir döngüden ibarettir — ve istediğin herhangi bir hedefin gradyanıyla yönlendirilebilir.

11.15 Kontrol Soruları

Cevap:

U-Net her adımda iki gürültü tahmini üretir: biri prompt’la koşullu, biri boş prompt’la (koşulsuz). Bu ikisinin farkı, “prompt’un üretimi hangi yöne ittiği”ni gösterir. Sonuç = koşulsuz_tahmin + guidance_scale × (koşullu − koşulsuz). guidance_scale = 1 ise sade koşullu tahmin; 1’den büyükse prompt’un yönü abartılır (daha sadık ama bazen aşırı). Böylece ayrı bir sınıflandırıcı eğitmeden (classifier-free) prompt etkisi güçlendirilir — hesaplama maliyeti adım başına iki U-Net geçişidir. (Şekil 11.5 iki-tahmin vektör aritmetiğini, Şekil 11.6 adım-4 birleştirmesini gösterir.)

Cevap:

“Latent diffusion” demek, diffusion’ın piksel uzayında değil, VAE’nin sıkıştırdığı latent uzayda (4×64×64) yapılması demektir — bu ~48× daha az veri, dolayısıyla çok daha ucuz/hızlı. pil_to_latent VAE ile görüntüyü latent’e çevirir, latents_to_pil geri açar. 0.18215, latent’leri U-Net’in eğitildiği ölçeğe getiren bir normalleştirme sabitidir (modelin yazarlarının seçtiği); encode’da çarpılır, decode’da bölünür. Olmazsa latent’lerin büyüklüğü U-Net’in beklediğiyle uyuşmaz. (Şekil 11.2 786.432 → 16.384 roundtrip’ini gösterir.)

Cevap:

img2img, sıfır gürültüden değil, bir başlangıç görüntüsünden başlar: görüntü VAE ile latent’e çevrilir, sonra noise schedule’da belirli bir adıma kadar gürültülenir (tamamen değil), ve sampling oradan devam eder. strength bu “ne kadar gürültüleneceğini” (hangi σ’dan başlanacağını) belirler: düşük strength → az gürültü → çıktı orijinale yakın; yüksek strength → çok gürültü → prompt’a göre büyük değişim. Yani strength, orijinal görüntünün ne kadarının korunacağını ayarlar. (Şekil 11.4 her strength’in σ çizelgesindeki başlangıç noktasını gösterir.)

Cevap:

Ders 9’daki “sihirli fonksiyonun pixel gradyanı yönünde ittir” fikrini. Whitaker, sampling’e kendi hedef fonksiyonunu ekler (örn. “görüntü daha mavi olsun”): her adımda bu hedefin hatasının latent’lere göre gradyanını hesaplar ve latent’leri o yönde de ittirir. Yani CFG modelin öğrendiği yönü kullanırken, özel guidance istediğin herhangi bir türevlenebilir hedefi (renk, CLIP benzerliği, kenar haritası) sampling’e enjekte eder. Builder açısından: bu, CLIP-guided diffusion ve ControlNet gibi tüm yönlendirme tekniklerinin temel mekanizmasıdır — üretimi bir loss’un gradyanıyla yönlendirmek. (Şekil 11.7 mavi-kanal MSE’sinin gerçek düşüşünü gösterir.)

11.16 Egzersizler

Egzersiz 1 (Direkt uygulama). pil_to_latent ve latents_to_pil ile bir görüntüyü latent’e çevirip geri aç; sıkıştırma kaybını gözle (0.18215 ölçeği doğru uygula).

Egzersiz 2 (İki-aşamalı). scheduler.sigmas’ı çiz; bir görüntüye farklı timestep’lerde add_noise uygulayıp gürültü seviyelerini karşılaştır (Şekil 11.3’ı kendi adım sayınla yeniden üret).

Egzersiz 3 (Edge case). Sampling loop’unda guidance_scale’i 1, 7, 20 yap; çıktının prompt’a sadakatini ve “aşırı doygunluk” eşiğini gözlemle.

Egzersiz 4 (Kavramsal). CFG formülünü (koşulsuz + \(s\) × (koşullu − koşulsuz)) bir adım için elle yaz; \(s = 0, 1, 10\) için sonucu açıkla.

Egzersiz 5 (Sonraki dersin habercisi — 9B). Noise schedule’ın (\(\beta\), \(\bar{\alpha}\)) nereden geldiğini ve “forward process”in matematiğini araştır (Ders 9B).

11.17 Sonraki: Ders 9B İçin Hazırlık

Ders 9B: Diffusion’ın Matematiği (Waseem + Tanishq)

Ders 9 ve 9A diffusion’ı sezgi ve kodla verdi. Ders 9B (misafirler: Waseem + Tanishq, Stability AI) altındaki matematiği açar: forward/reverse process, noise schedule’ın türetilmesi, score ve olasılıksal çerçeve.

Ana konular (Ders 9B):

  • Forward process (gürültü ekleme) ve Markov zinciri
  • Reverse process (gürültü giderme) matematiği
  • Noise schedule (\(\beta\), \(\bar{\alpha}\)) türetimi
  • Olasılıksal/score çerçevesi
UyarıDers 9B Öncesi Yapılacak
  • Bu dersin egzersizlerini çöz (özellikle 1 ve 4 — latent + CFG).
  • Sampling loop’unu kendi notebook’unda çalıştır.
  • Ana cümleyi tekrar oku: “Sampling = CFG’li U-Net tahmini + scheduler ile adım adım gürültü giderme.”

11.18 Anahtar Kavramlar (Cheat Sheet)

Kavram Tanım Whitaker’da
Latent diffusion Diffusion’ı piksel değil latent uzayda yapma 1:21
pil_to_latent VAE encode: görüntü → latent (×0.18215) 1:21
latents_to_pil VAE decode: latent → görüntü (÷0.18215) 1:21
Noise schedule (σ) Her adımdaki gürültü miktarı (yüksek → sıfır) 8:44
add_noise latent + noise × σ (belirli timestep’te) 8:44
img2img Görüntüyü kısmen gürültüleyip sampling’e devam 3:55
Text encoder (CLIP) tokenizer → embedding dizisi 8:44
Textual inversion Yeni embedding’i token sözlüğüne ekleme 15:03
Classifier-free guidance uncond + \(s\) × (cond − uncond) 18:38
Sampling loop CFG tahmini + scheduler.step ile gürültü giderme 19:38
Özel guidance Herhangi bir hedefin gradyanını sampling’e ekleme 36:32
scheduler.step Tahmin edilen gürültüyü latent’ten kısmen çıkarma 19:38

11.19 ML Bağlantıları Özeti

İpucuBuilder Notu — 6 ML Köprüsü: 9A’nın Sezgiden Koda Dönüşü

Bu ders, Ders 9’un sezgisini çalışan koda çevirir; köprülerin özeti:

  1. Latent diffusion → VAE sıkıştırması (Ders 8/9); pahalı işlemi küçük uzayda yapma (VAE/latent).
  2. Classifier-free guidance → koşullu/koşulsuz fark; üretimi prompt yönüne abartma; NYU EBM (§4.J) akrabası (CFG).
  3. Sampling loopscheduler.step = gradient adımı (Ders 5) diffusion karşılığı (sampling döngüsü).
  4. Özel guidance → Ders 9’un pixel-gradient sezgisi; CLIP-guided/ControlNet temeli (özel guidance).
  5. CLIP text encoder → tokenize (Ders 4) + embedding (Ders 7) çok-modal koşullama (text encoding).
  6. Noise schedule → DDPM/DDIM/Karras (Ders 19-22) ile Part 2’de sıfırdan kurulur (noise schedule).
ÖnemliBu dersten tek bir şey alıp gideceksen

Stable Diffusion’ın “büyülü” pipeline’ı, ayrı ayrı anlaşılabilir parçaların bir döngüsüdür — VAE latent’e indirir, CLIP yön verir, U-Net + classifier-free guidance her adımda gürültüyü tahmin eder, scheduler çıkarır. Ve en güçlüsü: istediğin herhangi bir hedefin gradyanını bu döngüye ekleyerek üretimi yönlendirebilirsin — tıpkı Ders 9’daki “geçerlilik gradyanı” sezgisi gibi.