@xdev-asia/xdev-knowledge-mcp 1.0.57 → 1.0.59
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/content/blog/ai/minimax-danh-gia-chi-tiet-nen-tang-ai-full-stack-trung-quoc.md +450 -0
- package/content/blog/ai/nvidia-dli-generative-ai-chung-chi-va-lo-trinh-hoc.md +894 -0
- package/content/metadata/authors/duy-tran.md +2 -0
- package/content/series/luyen-thi/luyen-thi-nvidia-dli-generative-ai/chapters/01-deep-learning-foundations/lessons/01-bai-1-pytorch-neural-network-fundamentals.md +790 -0
- package/content/series/luyen-thi/luyen-thi-nvidia-dli-generative-ai/chapters/01-deep-learning-foundations/lessons/02-bai-2-transformer-architecture-attention.md +984 -0
- package/content/series/luyen-thi/luyen-thi-nvidia-dli-generative-ai/chapters/02-diffusion-models/lessons/01-bai-3-unet-architecture-denoising.md +1111 -0
- package/content/series/luyen-thi/luyen-thi-nvidia-dli-generative-ai/chapters/02-diffusion-models/lessons/02-bai-4-ddpm-forward-reverse-diffusion.md +1007 -0
- package/content/series/luyen-thi/luyen-thi-nvidia-dli-generative-ai/chapters/02-diffusion-models/lessons/03-bai-5-clip-text-to-image-pipeline.md +1037 -0
- package/content/series/luyen-thi/luyen-thi-nvidia-dli-generative-ai/chapters/03-llm-applications-rag/lessons/01-bai-6-llm-inference-pipeline-design.md +929 -0
- package/content/series/luyen-thi/luyen-thi-nvidia-dli-generative-ai/chapters/03-llm-applications-rag/lessons/02-bai-7-rag-retrieval-augmented-generation.md +1099 -0
- package/content/series/luyen-thi/luyen-thi-nvidia-dli-generative-ai/chapters/03-llm-applications-rag/lessons/03-bai-8-rag-agent-build-evaluate.md +1249 -0
- package/content/series/luyen-thi/luyen-thi-nvidia-dli-generative-ai/chapters/04-agentic-ai-customization/lessons/01-bai-9-agentic-ai-multi-agent-systems.md +1357 -0
- package/content/series/luyen-thi/luyen-thi-nvidia-dli-generative-ai/chapters/04-agentic-ai-customization/lessons/02-bai-10-llm-evaluation-lora-fine-tuning.md +1867 -0
- package/content/series/luyen-thi/luyen-thi-nvidia-dli-generative-ai/index.md +237 -0
- package/data/quizzes/nvidia-dli-generative-ai.json +350 -0
- package/data/quizzes.json +14 -0
- package/package.json +1 -1
|
@@ -0,0 +1,1007 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: 019c9619-nv01-p2-l04
|
|
3
|
+
title: 'Bài 4: DDPM — Forward & Reverse Diffusion'
|
|
4
|
+
slug: bai-4-ddpm-forward-reverse-diffusion
|
|
5
|
+
description: >-
|
|
6
|
+
Forward diffusion: Markov chain, variance schedule, reparameterization.
|
|
7
|
+
Reverse diffusion: predict noise, denoise step-by-step.
|
|
8
|
+
Noise scheduling: linear, cosine schedules.
|
|
9
|
+
Training objective: simplified ELBO loss.
|
|
10
|
+
Classifier-Free Diffusion Guidance (CFG).
|
|
11
|
+
duration_minutes: 90
|
|
12
|
+
is_free: true
|
|
13
|
+
video_url: null
|
|
14
|
+
sort_order: 4
|
|
15
|
+
section_title: "Part 2: Generative AI with Diffusion Models"
|
|
16
|
+
course:
|
|
17
|
+
id: 019c9619-nv01-7001-c001-nv0100000001
|
|
18
|
+
title: 'Luyện thi NVIDIA DLI — Generative AI with Diffusion Models & LLMs'
|
|
19
|
+
slug: luyen-thi-nvidia-dli-generative-ai
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
<h2 id="gioi-thieu">1. Giới thiệu: Toán học đằng sau Diffusion Models</h2>
|
|
23
|
+
|
|
24
|
+
<p>Bài này là <strong>phần khó nhất</strong> trong toàn bộ khoá DLI. Bạn sẽ đi sâu vào nền tảng toán học của <strong>Denoising Diffusion Probabilistic Models (DDPM)</strong> — paper gốc từ Ho et al. (2020). Mọi diffusion model hiện đại (Stable Diffusion, DALL·E, Imagen) đều dựa trên framework này.</p>
|
|
25
|
+
|
|
26
|
+
<p>Bài trước bạn đã xây xong <strong>U-Net</strong> — kiến trúc backbone. Giờ bạn sẽ hiểu chính xác U-Net học cái gì, bằng cách nào, và tại sao nó hoạt động.</p>
|
|
27
|
+
|
|
28
|
+
<blockquote><p><strong>Exam tip:</strong> NVIDIA DLI assessment yêu cầu bạn implement cả <strong>forward diffusion</strong>, <strong>reverse sampling</strong>, và <strong>training loop</strong> từ đầu. Hiểu rõ từng công thức và cách chúng map sang code PyTorch là bắt buộc — không chỉ chạy code mẫu.</p></blockquote>
|
|
29
|
+
|
|
30
|
+
<pre><code class="language-text">
|
|
31
|
+
DDPM Overview — Two Processes
|
|
32
|
+
═════════════════════════════
|
|
33
|
+
|
|
34
|
+
FORWARD DIFFUSION q(x_t | x_{t-1}) REVERSE DIFFUSION p_θ(x_{t-1} | x_t)
|
|
35
|
+
────────────────────────────────── ───────────────────────────────────────
|
|
36
|
+
|
|
37
|
+
x_0 ──► x_1 ──► x_2 ──►...──► x_T x_T ──► x_{T-1} ──►...──► x_1 ──► x_0
|
|
38
|
+
(clean) +ε +ε (noise) (noise) U-Net U-Net (clean)
|
|
39
|
+
|
|
40
|
+
┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐
|
|
41
|
+
│ ████ │→ │ ▓▓▓▓ │→ │ ░░░░ │→ │ ···· │ Forward: add noise (fixed, no learning)
|
|
42
|
+
│ ████ │ │ ▓▓▓▓ │ │ ░░░░ │ │ ···· │
|
|
43
|
+
└──────┘ └──────┘ └──────┘ └──────┘
|
|
44
|
+
t = 0 t = 100 t = 500 t = 1000
|
|
45
|
+
|
|
46
|
+
┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐
|
|
47
|
+
│ ···· │→ │ ░░░░ │→ │ ▓▓▓▓ │→ │ ████ │ Reverse: remove noise (learned by U-Net)
|
|
48
|
+
│ ···· │ │ ░░░░ │ │ ▓▓▓▓ │ │ ████ │
|
|
49
|
+
└──────┘ └──────┘ └──────┘ └──────┘
|
|
50
|
+
t = 1000 t = 500 t = 100 t = 0
|
|
51
|
+
</code></pre>
|
|
52
|
+
|
|
53
|
+
<figure><img src="/storage/uploads/2026/04/nvidia-dli-bai4-ddpm-diffusion-process.png" alt="DDPM — Forward Diffusion (thêm noise) và Reverse Diffusion (khử noise)" loading="lazy" /><figcaption>DDPM — Forward Diffusion (thêm noise) và Reverse Diffusion (khử noise)</figcaption></figure>
|
|
54
|
+
|
|
55
|
+
<h2 id="forward-diffusion">2. Forward Diffusion Process</h2>
|
|
56
|
+
|
|
57
|
+
<h3 id="markov-chain">2.1 Markov Chain formulation</h3>
|
|
58
|
+
|
|
59
|
+
<p>Forward diffusion là quá trình dần dần thêm <strong>Gaussian noise</strong> vào ảnh gốc x_0 qua T timesteps. Đây là một <strong>Markov chain</strong> — mỗi bước chỉ phụ thuộc vào bước ngay trước đó:</p>
|
|
60
|
+
|
|
61
|
+
<pre><code class="language-text">
|
|
62
|
+
q(x_{1:T} | x_0) = ∏_{t=1}^{T} q(x_t | x_{t-1})
|
|
63
|
+
</code></pre>
|
|
64
|
+
|
|
65
|
+
<p>Tại mỗi timestep t, chúng ta thêm noise theo phân phối Gaussian:</p>
|
|
66
|
+
|
|
67
|
+
<pre><code class="language-text">
|
|
68
|
+
q(x_t | x_{t-1}) = N(x_t; √(1 - β_t) · x_{t-1}, β_t · I)
|
|
69
|
+
▲ mean ▲ variance
|
|
70
|
+
</code></pre>
|
|
71
|
+
|
|
72
|
+
<p>Trong đó <strong>β_t</strong> (beta) là <strong>variance schedule</strong> — một giá trị nhỏ tăng dần từ β_1 ≈ 0.0001 đến β_T ≈ 0.02. Nó kiểm soát lượng noise được thêm tại mỗi step.</p>
|
|
73
|
+
|
|
74
|
+
<table>
|
|
75
|
+
<thead>
|
|
76
|
+
<tr><th>Ký hiệu</th><th>Ý nghĩa</th><th>Giá trị điển hình</th></tr>
|
|
77
|
+
</thead>
|
|
78
|
+
<tbody>
|
|
79
|
+
<tr><td>β_t</td><td>Variance tại timestep t</td><td>0.0001 → 0.02</td></tr>
|
|
80
|
+
<tr><td>α_t = 1 - β_t</td><td>Signal retention ratio</td><td>0.9999 → 0.98</td></tr>
|
|
81
|
+
<tr><td>ᾱ_t = ∏_{s=1}^{t} α_s</td><td>Cumulative signal retention</td><td>≈1.0 → ≈0.0</td></tr>
|
|
82
|
+
<tr><td>T</td><td>Tổng số timesteps</td><td>1000 (DDPM gốc)</td></tr>
|
|
83
|
+
<tr><td>ε</td><td>Standard Gaussian noise</td><td>ε ~ N(0, I)</td></tr>
|
|
84
|
+
</tbody>
|
|
85
|
+
</table>
|
|
86
|
+
|
|
87
|
+
<h3 id="closed-form">2.2 Closed-form: Nhảy thẳng đến timestep bất kỳ</h3>
|
|
88
|
+
|
|
89
|
+
<p>Điểm then chốt: ta <strong>không cần chạy tuần tự</strong> T bước forward. Nhờ tính chất cộng của Gaussian, ta có closed-form để tính x_t trực tiếp từ x_0:</p>
|
|
90
|
+
|
|
91
|
+
<pre><code class="language-text">
|
|
92
|
+
q(x_t | x_0) = N(x_t; √(ᾱ_t) · x_0, (1 - ᾱ_t) · I)
|
|
93
|
+
|
|
94
|
+
Trong đó:
|
|
95
|
+
ᾱ_t = α_1 · α_2 · ... · α_t = ∏_{s=1}^{t} (1 - β_s)
|
|
96
|
+
</code></pre>
|
|
97
|
+
|
|
98
|
+
<p>Điều này cực kỳ quan trọng cho training — ta có thể sample bất kỳ timestep t nào mà không cần simulate toàn bộ chain.</p>
|
|
99
|
+
|
|
100
|
+
<h3 id="reparameterization">2.3 Reparameterization Trick</h3>
|
|
101
|
+
|
|
102
|
+
<p>Để sample x_t từ phân phối trên và <strong>backpropagate gradient</strong>, ta dùng <strong>reparameterization trick</strong>:</p>
|
|
103
|
+
|
|
104
|
+
<pre><code class="language-text">
|
|
105
|
+
x_t = √(ᾱ_t) · x_0 + √(1 - ᾱ_t) · ε where ε ~ N(0, I)
|
|
106
|
+
──────────────── ──────────────────
|
|
107
|
+
signal component noise component
|
|
108
|
+
</code></pre>
|
|
109
|
+
|
|
110
|
+
<p>Công thức này nói: tại timestep t, ảnh x_t là <strong>trộn tuyến tính</strong> giữa ảnh gốc (scaled bởi √ᾱ_t) và noise thuần (scaled bởi √(1−ᾱ_t)). Khi t nhỏ → ᾱ_t ≈ 1 → gần như toàn signal. Khi t lớn → ᾱ_t ≈ 0 → gần như toàn noise.</p>
|
|
111
|
+
|
|
112
|
+
<pre><code class="language-text">
|
|
113
|
+
Signal vs Noise qua timestep (T=1000, linear schedule)
|
|
114
|
+
═══════════════════════════════════════════════════════
|
|
115
|
+
|
|
116
|
+
Signal: √(ᾱ_t) Noise: √(1-ᾱ_t)
|
|
117
|
+
1.0 ┤████████░░░░░░ 0.0 ┤░░░░░░░░████████
|
|
118
|
+
│████████░░░░░░ │░░░░░░░░████████
|
|
119
|
+
│██████░░░░░░░░ │░░░░░░██████████
|
|
120
|
+
│████░░░░░░░░░░ │░░░░████████████
|
|
121
|
+
│██░░░░░░░░░░░░ │░░██████████████
|
|
122
|
+
0.0 ┤░░░░░░░░░░░░░░ 1.0 ┤████████████████
|
|
123
|
+
└────────────── └────────────────
|
|
124
|
+
t=0 t=T t=0 t=T
|
|
125
|
+
|
|
126
|
+
Tại t ≈ T/2: signal ≈ noise (ảnh nửa sạch nửa noise)
|
|
127
|
+
Tại t = T: signal ≈ 0 (pure Gaussian noise)
|
|
128
|
+
</code></pre>
|
|
129
|
+
|
|
130
|
+
<h3 id="forward-code">2.4 Implementation: forward_diffusion()</h3>
|
|
131
|
+
|
|
132
|
+
<pre><code class="language-python">
|
|
133
|
+
import torch
|
|
134
|
+
import torch.nn as nn
|
|
135
|
+
import math
|
|
136
|
+
|
|
137
|
+
def forward_diffusion(x_0, t, sqrt_alpha_bar, sqrt_one_minus_alpha_bar):
|
|
138
|
+
"""
|
|
139
|
+
Apply forward diffusion: x_t = sqrt(alpha_bar_t) * x_0 + sqrt(1 - alpha_bar_t) * eps
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
x_0: (B, C, H, W) — clean images
|
|
143
|
+
t: (B,) — timestep indices (0-indexed)
|
|
144
|
+
sqrt_alpha_bar: (T,) — precomputed sqrt(ᾱ_t)
|
|
145
|
+
sqrt_one_minus_alpha_bar: (T,) — precomputed sqrt(1 - ᾱ_t)
|
|
146
|
+
Returns:
|
|
147
|
+
x_t: (B, C, H, W) — noisy images at timestep t
|
|
148
|
+
noise: (B, C, H, W) — the noise that was added (needed for loss)
|
|
149
|
+
"""
|
|
150
|
+
# Sample Gaussian noise
|
|
151
|
+
noise = torch.randn_like(x_0)
|
|
152
|
+
|
|
153
|
+
# Gather coefficients for each sample in batch
|
|
154
|
+
# (B,) -> (B, 1, 1, 1) for broadcasting with (B, C, H, W)
|
|
155
|
+
s_alpha = sqrt_alpha_bar[t].view(-1, 1, 1, 1)
|
|
156
|
+
s_one_minus = sqrt_one_minus_alpha_bar[t].view(-1, 1, 1, 1)
|
|
157
|
+
|
|
158
|
+
# Reparameterization: x_t = sqrt(ᾱ_t) * x_0 + sqrt(1-ᾱ_t) * ε
|
|
159
|
+
x_t = s_alpha * x_0 + s_one_minus * noise
|
|
160
|
+
|
|
161
|
+
return x_t, noise
|
|
162
|
+
</code></pre>
|
|
163
|
+
|
|
164
|
+
<blockquote><p><strong>Exam tip:</strong> Chú ý <code>.view(-1, 1, 1, 1)</code> — đây là pattern bắt buộc khi gather scalar coefficients rồi broadcast với 4D tensor. Quên reshape sẽ gây shape mismatch. Assessment thường test chính xác điểm này.</p></blockquote>
|
|
165
|
+
|
|
166
|
+
<h2 id="noise-scheduling">3. Noise Scheduling</h2>
|
|
167
|
+
|
|
168
|
+
<h3 id="linear-schedule">3.1 Linear Schedule (DDPM gốc)</h3>
|
|
169
|
+
|
|
170
|
+
<p>Paper DDPM gốc dùng <strong>linear schedule</strong>: β tăng tuyến tính từ β_1 = 0.0001 đến β_T = 0.02 qua T = 1000 steps.</p>
|
|
171
|
+
|
|
172
|
+
<pre><code class="language-python">
|
|
173
|
+
def linear_beta_schedule(T, beta_start=1e-4, beta_end=0.02):
|
|
174
|
+
"""
|
|
175
|
+
Linear variance schedule: β_t increases linearly from beta_start to beta_end.
|
|
176
|
+
Original DDPM (Ho et al. 2020).
|
|
177
|
+
"""
|
|
178
|
+
return torch.linspace(beta_start, beta_end, T)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def precompute_schedule(betas):
|
|
182
|
+
"""Precompute all coefficients from beta schedule."""
|
|
183
|
+
alphas = 1.0 - betas # α_t = 1 - β_t
|
|
184
|
+
alpha_bar = torch.cumprod(alphas, dim=0) # ᾱ_t = ∏ α_s
|
|
185
|
+
sqrt_alpha_bar = torch.sqrt(alpha_bar) # √(ᾱ_t)
|
|
186
|
+
sqrt_one_minus_alpha_bar = torch.sqrt(1.0 - alpha_bar) # √(1 - ᾱ_t)
|
|
187
|
+
sqrt_alpha = torch.sqrt(alphas) # √(α_t) — for reverse step
|
|
188
|
+
|
|
189
|
+
return {
|
|
190
|
+
'betas': betas,
|
|
191
|
+
'alphas': alphas,
|
|
192
|
+
'alpha_bar': alpha_bar,
|
|
193
|
+
'sqrt_alpha_bar': sqrt_alpha_bar,
|
|
194
|
+
'sqrt_one_minus_alpha_bar': sqrt_one_minus_alpha_bar,
|
|
195
|
+
'sqrt_alpha': sqrt_alpha,
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
# Usage
|
|
199
|
+
T = 1000
|
|
200
|
+
schedule = precompute_schedule(linear_beta_schedule(T))
|
|
201
|
+
</code></pre>
|
|
202
|
+
|
|
203
|
+
<h3 id="cosine-schedule">3.2 Cosine Schedule (Improved DDPM)</h3>
|
|
204
|
+
|
|
205
|
+
<p><strong>Vấn đề</strong> với linear schedule: ᾱ_t giảm quá nhanh ở giữa → ảnh bị destroy quá sớm, gây mất thông tin. <strong>Cosine schedule</strong> (Nichol & Dhariwal 2021) khắc phục bằng cách thiết kế ᾱ_t theo hàm cosine — giảm mượt hơn, đặc biệt tốt cho ảnh high-resolution.</p>
|
|
206
|
+
|
|
207
|
+
<pre><code class="language-python">
|
|
208
|
+
def cosine_beta_schedule(T, s=0.008):
|
|
209
|
+
"""
|
|
210
|
+
Cosine variance schedule (Nichol & Dhariwal 2021).
|
|
211
|
+
Designs alpha_bar directly via cosine function, then derives betas.
|
|
212
|
+
The 's' offset prevents beta from being too small near t=0.
|
|
213
|
+
"""
|
|
214
|
+
steps = torch.arange(T + 1, dtype=torch.float32)
|
|
215
|
+
# f(t) = cos( (t/T + s) / (1+s) * π/2 )²
|
|
216
|
+
f_t = torch.cos(((steps / T) + s) / (1 + s) * (math.pi / 2)) ** 2
|
|
217
|
+
alpha_bar = f_t / f_t[0] # normalize so alpha_bar[0] = 1
|
|
218
|
+
|
|
219
|
+
# Derive betas from alpha_bar: β_t = 1 - ᾱ_t / ᾱ_{t-1}
|
|
220
|
+
betas = 1 - (alpha_bar[1:] / alpha_bar[:-1])
|
|
221
|
+
betas = torch.clamp(betas, min=1e-5, max=0.999) # numerical stability
|
|
222
|
+
|
|
223
|
+
return betas
|
|
224
|
+
</code></pre>
|
|
225
|
+
|
|
226
|
+
<table>
|
|
227
|
+
<thead>
|
|
228
|
+
<tr><th>Đặc điểm</th><th>Linear Schedule</th><th>Cosine Schedule</th></tr>
|
|
229
|
+
</thead>
|
|
230
|
+
<tbody>
|
|
231
|
+
<tr><td>ᾱ_t tại t=T/2</td><td>≈ 0.05 (gần 0)</td><td>≈ 0.50 (vẫn còn signal)</td></tr>
|
|
232
|
+
<tr><td>Signal destruction</td><td>Nhanh, aggressive</td><td>Mượt, gradual</td></tr>
|
|
233
|
+
<tr><td>High-resolution images</td><td>Kém (mất detail sớm)</td><td>Tốt hơn nhiều</td></tr>
|
|
234
|
+
<tr><td>Original paper</td><td>DDPM (Ho 2020)</td><td>Improved DDPM (Nichol 2021)</td></tr>
|
|
235
|
+
<tr><td>Dùng trong Stable Diffusion</td><td>Không</td><td>Có (biến thể)</td></tr>
|
|
236
|
+
<tr><td>NVIDIA DLI focus</td><td>Implement trong lab</td><td>Hiểu concept, so sánh</td></tr>
|
|
237
|
+
</tbody>
|
|
238
|
+
</table>
|
|
239
|
+
|
|
240
|
+
<pre><code class="language-text">
|
|
241
|
+
ᾱ_t Comparison: Linear vs Cosine (T=1000)
|
|
242
|
+
══════════════════════════════════════════
|
|
243
|
+
|
|
244
|
+
ᾱ_t
|
|
245
|
+
1.0 ┤C C C C L
|
|
246
|
+
│C C C L
|
|
247
|
+
0.8 ┤ C C L
|
|
248
|
+
│ C L
|
|
249
|
+
0.6 ┤ C L
|
|
250
|
+
│ C L
|
|
251
|
+
0.4 ┤ C L
|
|
252
|
+
│ C L
|
|
253
|
+
0.2 ┤ C L
|
|
254
|
+
│ C C L L
|
|
255
|
+
0.0 ┤ C C C L L L L
|
|
256
|
+
└─────────────────────────
|
|
257
|
+
t=0 t=250 t=500 t=750 t=1000
|
|
258
|
+
|
|
259
|
+
L = Linear schedule (drops fast mid-range)
|
|
260
|
+
C = Cosine schedule (smooth decay, retains signal longer)
|
|
261
|
+
|
|
262
|
+
Key: Cosine giữ signal lâu hơn → better generation quality
|
|
263
|
+
</code></pre>
|
|
264
|
+
|
|
265
|
+
<blockquote><p><strong>Exam tip:</strong> Câu hỏi sẽ hỏi "tại sao cosine schedule tốt hơn linear?" — Đáp: vì cosine giữ signal lâu hơn ở timestep trung bình, tránh <strong>information destruction</strong> quá sớm. Với linear, ᾱ_{T/2} ≈ 0.05 nghĩa là 95% signal đã mất ở giữa quá trình.</p></blockquote>
|
|
266
|
+
|
|
267
|
+
<h2 id="reverse-diffusion">4. Reverse Diffusion Process</h2>
|
|
268
|
+
|
|
269
|
+
<h3 id="reverse-goal">4.1 Mục tiêu: học phân phối ngược</h3>
|
|
270
|
+
|
|
271
|
+
<p><strong>Reverse diffusion</strong> là quá trình ngược lại — bắt đầu từ pure noise x_T ~ N(0, I) và dần dần denoise về ảnh sạch x_0. Đây là phần <strong>learned</strong> — U-Net sẽ học:</p>
|
|
272
|
+
|
|
273
|
+
<pre><code class="language-text">
|
|
274
|
+
p_θ(x_{t-1} | x_t) = N(x_{t-1}; μ_θ(x_t, t), σ²_t · I)
|
|
275
|
+
▲ predicted mean ▲ fixed variance
|
|
276
|
+
</code></pre>
|
|
277
|
+
|
|
278
|
+
<p>Thay vì dự đoán mean μ trực tiếp, DDPM chọn cách elegant hơn: <strong>model dự đoán noise ε</strong> mà đã được thêm vào ảnh. Từ ε̂ predicted, ta suy ra mean:</p>
|
|
279
|
+
|
|
280
|
+
<pre><code class="language-text">
|
|
281
|
+
μ_θ(x_t, t) = ────────── · ( x_t − ──────────── · ε_θ(x_t, t) )
|
|
282
|
+
1 1 - α_t
|
|
283
|
+
─────── ──────────────
|
|
284
|
+
√(α_t) √(1 - ᾱ_t)
|
|
285
|
+
|
|
286
|
+
Viết gọn:
|
|
287
|
+
1 (1 - α_t)
|
|
288
|
+
μ_θ(x_t, t) = ───────── · (x_t − ─────────── · ε_θ(x_t, t))
|
|
289
|
+
√(α_t) √(1 - ᾱ_t)
|
|
290
|
+
</code></pre>
|
|
291
|
+
|
|
292
|
+
<h3 id="sampling-algorithm">4.2 Sampling Algorithm</h3>
|
|
293
|
+
|
|
294
|
+
<p>Thuật toán sampling đi từ x_T về x_0:</p>
|
|
295
|
+
|
|
296
|
+
<pre><code class="language-text">
|
|
297
|
+
Algorithm: DDPM Sampling
|
|
298
|
+
════════════════════════
|
|
299
|
+
Input: trained model ε_θ, noise schedule {β_t, α_t, ᾱ_t}
|
|
300
|
+
|
|
301
|
+
1. Sample x_T ~ N(0, I) ← start from pure noise
|
|
302
|
+
2. For t = T, T-1, ..., 1:
|
|
303
|
+
a. If t > 1: sample z ~ N(0, I)
|
|
304
|
+
Else: z = 0 ← no noise at final step
|
|
305
|
+
b. ε̂ = ε_θ(x_t, t) ← U-Net predicts noise
|
|
306
|
+
c. μ = (1/√α_t) · (x_t − ((1-α_t)/√(1-ᾱ_t)) · ε̂)
|
|
307
|
+
d. x_{t-1} = μ + σ_t · z ← denoise one step
|
|
308
|
+
where σ_t = √(β_t) ← simplified variance
|
|
309
|
+
|
|
310
|
+
3. Return x_0
|
|
311
|
+
</code></pre>
|
|
312
|
+
|
|
313
|
+
<pre><code class="language-text">
|
|
314
|
+
Reverse Process Visualization
|
|
315
|
+
═════════════════════════════
|
|
316
|
+
|
|
317
|
+
x_T (pure noise) x_0 (clean image)
|
|
318
|
+
┌──────────┐ ┌──────────┐
|
|
319
|
+
│ ·:·:·:·: │ U-Net × T │ ████████ │
|
|
320
|
+
│ :·:·:·:· │ ──────────► │ ██ ██ │
|
|
321
|
+
│ ·:·:·:·: │ denoise │ ██ ██ │
|
|
322
|
+
│ :·:·:·:· │ iteratively │ ████████ │
|
|
323
|
+
└──────────┘ └──────────┘
|
|
324
|
+
|
|
325
|
+
Step-by-step (T=1000):
|
|
326
|
+
t=1000 t=750 t=500 t=250 t=0
|
|
327
|
+
┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐
|
|
328
|
+
│ ···· │ → │ ░··░ │ → │ ░▓▓░ │ → │ ▓██▓ │ → │ ████ │
|
|
329
|
+
│ ···· │ │ ·░░· │ │ ▓░░▓ │ │ █▓▓█ │ │ █ █ │
|
|
330
|
+
│ ···· │ │ ·░░· │ │ ▓░░▓ │ │ █▓▓█ │ │ █ █ │
|
|
331
|
+
│ ···· │ │ ░··░ │ │ ░▓▓░ │ │ ▓██▓ │ │ ████ │
|
|
332
|
+
└──────┘ └──────┘ └──────┘ └──────┘ └──────┘
|
|
333
|
+
noise only structure shape details clean!
|
|
334
|
+
emerges forms sharpen
|
|
335
|
+
</code></pre>
|
|
336
|
+
|
|
337
|
+
<h3 id="reverse-code">4.3 Implementation: reverse sampling loop</h3>
|
|
338
|
+
|
|
339
|
+
<pre><code class="language-python">
|
|
340
|
+
@torch.no_grad()
|
|
341
|
+
def sample_ddpm(model, shape, schedule, device='cuda'):
|
|
342
|
+
"""
|
|
343
|
+
DDPM sampling: generate images from pure noise.
|
|
344
|
+
|
|
345
|
+
Args:
|
|
346
|
+
model: trained U-Net noise predictor ε_θ
|
|
347
|
+
shape: (B, C, H, W) — output shape
|
|
348
|
+
schedule: dict with 'betas', 'alphas', 'alpha_bar', etc.
|
|
349
|
+
device: torch device
|
|
350
|
+
Returns:
|
|
351
|
+
x_0: (B, C, H, W) — generated images
|
|
352
|
+
"""
|
|
353
|
+
T = len(schedule['betas'])
|
|
354
|
+
betas = schedule['betas'].to(device)
|
|
355
|
+
alphas = schedule['alphas'].to(device)
|
|
356
|
+
alpha_bar = schedule['alpha_bar'].to(device)
|
|
357
|
+
sqrt_alpha = schedule['sqrt_alpha'].to(device)
|
|
358
|
+
sqrt_one_minus_alpha_bar = schedule['sqrt_one_minus_alpha_bar'].to(device)
|
|
359
|
+
|
|
360
|
+
# Step 1: Start from pure Gaussian noise
|
|
361
|
+
x_t = torch.randn(shape, device=device)
|
|
362
|
+
|
|
363
|
+
# Step 2: Iteratively denoise from t=T-1 down to t=0
|
|
364
|
+
for t in reversed(range(T)):
|
|
365
|
+
t_batch = torch.full((shape[0],), t, device=device, dtype=torch.long)
|
|
366
|
+
|
|
367
|
+
# (a) Predict noise using U-Net
|
|
368
|
+
eps_pred = model(x_t, t_batch)
|
|
369
|
+
|
|
370
|
+
# (b) Compute predicted mean μ_θ
|
|
371
|
+
# μ = (1/√α_t) * (x_t - (1-α_t)/√(1-ᾱ_t) * ε̂)
|
|
372
|
+
coeff_xt = 1.0 / sqrt_alpha[t]
|
|
373
|
+
coeff_eps = (1.0 - alphas[t]) / sqrt_one_minus_alpha_bar[t]
|
|
374
|
+
mu = coeff_xt * (x_t - coeff_eps * eps_pred)
|
|
375
|
+
|
|
376
|
+
# (c) Add noise (except at t=0)
|
|
377
|
+
if t > 0:
|
|
378
|
+
sigma = torch.sqrt(betas[t])
|
|
379
|
+
z = torch.randn_like(x_t)
|
|
380
|
+
x_t = mu + sigma * z
|
|
381
|
+
else:
|
|
382
|
+
x_t = mu # final step: no noise added
|
|
383
|
+
|
|
384
|
+
return x_t
|
|
385
|
+
</code></pre>
|
|
386
|
+
|
|
387
|
+
<blockquote><p><strong>Exam tip:</strong> Hai sai lầm phổ biến nhất trong sampling loop: (1) quên <code>@torch.no_grad()</code> → tốn VRAM gấp 3-4x, OOM crash. (2) Thêm noise ở bước t=0 → ảnh output bị noisy. Luôn check <code>if t > 0</code> trước khi thêm z.</p></blockquote>
|
|
388
|
+
|
|
389
|
+
<h2 id="training-objective">5. Training Objective: Simplified ELBO Loss</h2>
|
|
390
|
+
|
|
391
|
+
<h3 id="elbo-intuition">5.1 Từ ELBO đến Simplified Loss</h3>
|
|
392
|
+
|
|
393
|
+
<p>Về mặt lý thuyết, DDPM optimize <strong>variational lower bound (ELBO)</strong> của log-likelihood. Tuy nhiên, Ho et al. phát hiện rằng một <strong>simplified loss</strong> hoạt động tốt hơn trong thực tế:</p>
|
|
394
|
+
|
|
395
|
+
<pre><code class="language-text">
|
|
396
|
+
Full ELBO Loss (lý thuyết):
|
|
397
|
+
L_vlb = L_0 + L_1 + ... + L_{T-1} + L_T
|
|
398
|
+
= ∑_t KL(q(x_{t-1}|x_t,x_0) || p_θ(x_{t-1}|x_t))
|
|
399
|
+
|
|
400
|
+
Simplified Loss (thực tế — DDPM paper):
|
|
401
|
+
L_simple = E_{t ~ U{1,T}, x_0, ε} [ || ε − ε_θ(x_t, t) ||² ]
|
|
402
|
+
|
|
403
|
+
Ý nghĩa:
|
|
404
|
+
- Sample timestep t ngẫu nhiên
|
|
405
|
+
- Tạo x_t từ x_0 via forward diffusion
|
|
406
|
+
- U-Net dự đoán noise ε̂ = ε_θ(x_t, t)
|
|
407
|
+
- Loss = MSE giữa noise thật (ε) và noise dự đoán (ε̂)
|
|
408
|
+
</code></pre>
|
|
409
|
+
|
|
410
|
+
<p>Đây chính là lý do tại sao bạn cần trả về <code>noise</code> từ <code>forward_diffusion()</code> — nó là <strong>ground truth label</strong> cho training.</p>
|
|
411
|
+
|
|
412
|
+
<h3 id="training-algorithm">5.2 Training Algorithm</h3>
|
|
413
|
+
|
|
414
|
+
<pre><code class="language-text">
|
|
415
|
+
Algorithm: DDPM Training
|
|
416
|
+
════════════════════════
|
|
417
|
+
Repeat until convergence:
|
|
418
|
+
1. Sample x_0 ~ q(x_0) ← batch from dataset
|
|
419
|
+
2. Sample t ~ Uniform({1, ..., T}) ← random timestep per sample
|
|
420
|
+
3. Sample ε ~ N(0, I) ← target noise
|
|
421
|
+
4. Compute x_t = √(ᾱ_t)·x_0 + √(1−ᾱ_t)·ε ← forward diffusion
|
|
422
|
+
5. Compute ε̂ = ε_θ(x_t, t) ← U-Net predicts noise
|
|
423
|
+
6. Loss = MSE(ε, ε̂) ← compare real vs predicted noise
|
|
424
|
+
7. Backprop & update θ
|
|
425
|
+
</code></pre>
|
|
426
|
+
|
|
427
|
+
<h3 id="training-code">5.3 Implementation: Complete Training Loop</h3>
|
|
428
|
+
|
|
429
|
+
<pre><code class="language-python">
|
|
430
|
+
def train_ddpm(model, dataloader, schedule, epochs=100, lr=2e-4, device='cuda'):
|
|
431
|
+
"""
|
|
432
|
+
Full DDPM training loop.
|
|
433
|
+
|
|
434
|
+
Args:
|
|
435
|
+
model: U-Net noise predictor
|
|
436
|
+
dataloader: yields (images, labels) batches
|
|
437
|
+
schedule: precomputed noise schedule dict
|
|
438
|
+
epochs: number of training epochs
|
|
439
|
+
lr: learning rate
|
|
440
|
+
device: torch device
|
|
441
|
+
"""
|
|
442
|
+
optimizer = torch.optim.Adam(model.parameters(), lr=lr)
|
|
443
|
+
loss_fn = nn.MSELoss()
|
|
444
|
+
T = len(schedule['betas'])
|
|
445
|
+
|
|
446
|
+
sqrt_alpha_bar = schedule['sqrt_alpha_bar'].to(device)
|
|
447
|
+
sqrt_one_minus_alpha_bar = schedule['sqrt_one_minus_alpha_bar'].to(device)
|
|
448
|
+
|
|
449
|
+
model.train()
|
|
450
|
+
for epoch in range(epochs):
|
|
451
|
+
epoch_loss = 0.0
|
|
452
|
+
for batch_idx, (x_0, _) in enumerate(dataloader):
|
|
453
|
+
x_0 = x_0.to(device)
|
|
454
|
+
B = x_0.shape[0]
|
|
455
|
+
|
|
456
|
+
# Step 2: Sample random timesteps
|
|
457
|
+
t = torch.randint(0, T, (B,), device=device)
|
|
458
|
+
|
|
459
|
+
# Steps 3-4: Forward diffusion (sample noise + compute x_t)
|
|
460
|
+
x_t, noise = forward_diffusion(
|
|
461
|
+
x_0, t, sqrt_alpha_bar, sqrt_one_minus_alpha_bar
|
|
462
|
+
)
|
|
463
|
+
|
|
464
|
+
# Step 5: Predict noise
|
|
465
|
+
noise_pred = model(x_t, t)
|
|
466
|
+
|
|
467
|
+
# Step 6: Compute loss
|
|
468
|
+
loss = loss_fn(noise_pred, noise)
|
|
469
|
+
|
|
470
|
+
# Step 7: Backprop
|
|
471
|
+
optimizer.zero_grad()
|
|
472
|
+
loss.backward()
|
|
473
|
+
optimizer.step()
|
|
474
|
+
|
|
475
|
+
epoch_loss += loss.item()
|
|
476
|
+
|
|
477
|
+
avg_loss = epoch_loss / len(dataloader)
|
|
478
|
+
if (epoch + 1) % 10 == 0:
|
|
479
|
+
print(f"Epoch {epoch+1}/{epochs}, Loss: {avg_loss:.4f}")
|
|
480
|
+
</code></pre>
|
|
481
|
+
|
|
482
|
+
<table>
|
|
483
|
+
<thead>
|
|
484
|
+
<tr><th>Training Component</th><th>Vai trò</th><th>Code tương ứng</th></tr>
|
|
485
|
+
</thead>
|
|
486
|
+
<tbody>
|
|
487
|
+
<tr><td>x_0 from dataset</td><td>Clean image input</td><td><code>x_0 = batch[0].to(device)</code></td></tr>
|
|
488
|
+
<tr><td>t ~ Uniform</td><td>Random timestep</td><td><code>torch.randint(0, T, (B,))</code></td></tr>
|
|
489
|
+
<tr><td>ε ~ N(0,I)</td><td>Target noise</td><td><code>torch.randn_like(x_0)</code></td></tr>
|
|
490
|
+
<tr><td>x_t via reparameterization</td><td>Noisy image</td><td><code>forward_diffusion(...)</code></td></tr>
|
|
491
|
+
<tr><td>ε_θ(x_t, t)</td><td>U-Net prediction</td><td><code>model(x_t, t)</code></td></tr>
|
|
492
|
+
<tr><td>MSE(ε, ε̂)</td><td>Simplified loss</td><td><code>nn.MSELoss()(pred, target)</code></td></tr>
|
|
493
|
+
</tbody>
|
|
494
|
+
</table>
|
|
495
|
+
|
|
496
|
+
<blockquote><p><strong>Exam tip:</strong> Trong assessment, bạn sẽ phải viết training loop từ đầu. Thứ tự các bước là critical: forward diffusion → predict → loss → backprop. Nếu bạn đặt <code>optimizer.zero_grad()</code> sai vị trí hoặc quên <code>loss.backward()</code>, model sẽ không học — và bạn mất điểm.</p></blockquote>
|
|
497
|
+
|
|
498
|
+
<h2 id="cfg">6. Classifier-Free Diffusion Guidance (CFG)</h2>
|
|
499
|
+
|
|
500
|
+
<h3 id="cfg-concept">6.1 Conditional Generation và CFG</h3>
|
|
501
|
+
|
|
502
|
+
<p>DDPM vanilla tạo ảnh không điều kiện (unconditional). Để tạo ảnh theo điều kiện (class label, text prompt), ta cần <strong>conditional generation</strong>. <strong>Classifier-Free Guidance (CFG)</strong> là phương pháp elegant nhất:</p>
|
|
503
|
+
|
|
504
|
+
<pre><code class="language-text">
|
|
505
|
+
Classifier-Free Guidance
|
|
506
|
+
═════════════════════════
|
|
507
|
+
|
|
508
|
+
Training: Model nhận condition c, nhưng randomly drop c → ∅ với xác suất p_uncond
|
|
509
|
+
┌──────────────────────────────────────┐
|
|
510
|
+
│ if random() < p_uncond (e.g. 0.1): │
|
|
511
|
+
│ c = ∅ (null / empty) │ ← 10% unconditional
|
|
512
|
+
│ ε̂ = ε_θ(x_t, t, c) │
|
|
513
|
+
└──────────────────────────────────────┘
|
|
514
|
+
|
|
515
|
+
Inference: Combine conditional & unconditional predictions
|
|
516
|
+
┌──────────────────────────────────────────────────────────────┐
|
|
517
|
+
│ ε̂_uncond = ε_θ(x_t, t, ∅) ← unconditional pred │
|
|
518
|
+
│ ε̂_cond = ε_θ(x_t, t, c) ← conditional pred │
|
|
519
|
+
│ │
|
|
520
|
+
│ ε̂_guided = ε̂_uncond + w · (ε̂_cond − ε̂_uncond) │
|
|
521
|
+
│ ▲ ▲ guidance direction │
|
|
522
|
+
│ baseline amplified by scale w │
|
|
523
|
+
└──────────────────────────────────────────────────────────────┘
|
|
524
|
+
|
|
525
|
+
w = guidance scale:
|
|
526
|
+
w = 1.0 → standard conditional (no guidance)
|
|
527
|
+
w = 7.5 → typical value (Stable Diffusion default)
|
|
528
|
+
w = 20 → very strong guidance → faithful but less diverse
|
|
529
|
+
w = 0.0 → purely unconditional
|
|
530
|
+
</code></pre>
|
|
531
|
+
|
|
532
|
+
<h3 id="cfg-tradeoff">6.2 Guidance Scale Trade-off</h3>
|
|
533
|
+
|
|
534
|
+
<table>
|
|
535
|
+
<thead>
|
|
536
|
+
<tr><th>Guidance Scale w</th><th>Quality</th><th>Diversity</th><th>Condition Fidelity</th><th>Use case</th></tr>
|
|
537
|
+
</thead>
|
|
538
|
+
<tbody>
|
|
539
|
+
<tr><td>0.0</td><td>Thấp</td><td>Rất cao</td><td>Không (unconditional)</td><td>Exploration</td></tr>
|
|
540
|
+
<tr><td>1.0</td><td>Trung bình</td><td>Cao</td><td>Chuẩn</td><td>No guidance</td></tr>
|
|
541
|
+
<tr><td>3.0 – 5.0</td><td>Tốt</td><td>Trung bình</td><td>Tốt</td><td>Balanced generation</td></tr>
|
|
542
|
+
<tr><td>7.0 – 8.5</td><td>Rất tốt</td><td>Thấp hơn</td><td>Rất tốt</td><td>Default Stable Diffusion</td></tr>
|
|
543
|
+
<tr><td>15.0 – 20.0</td><td>Oversaturated</td><td>Rất thấp</td><td>Quá mức</td><td>Artistic, stylized</td></tr>
|
|
544
|
+
</tbody>
|
|
545
|
+
</table>
|
|
546
|
+
|
|
547
|
+
<h3 id="cfg-code">6.3 Implementation: CFG Training & Sampling</h3>
|
|
548
|
+
|
|
549
|
+
<pre><code class="language-python">
|
|
550
|
+
def train_ddpm_cfg(model, dataloader, schedule, epochs=100,
|
|
551
|
+
lr=2e-4, p_uncond=0.1, num_classes=10, device='cuda'):
|
|
552
|
+
"""
|
|
553
|
+
DDPM training with Classifier-Free Guidance.
|
|
554
|
+
Model takes (x_t, t, class_label) as input.
|
|
555
|
+
During training, randomly replace class_label with null_class.
|
|
556
|
+
"""
|
|
557
|
+
optimizer = torch.optim.Adam(model.parameters(), lr=lr)
|
|
558
|
+
loss_fn = nn.MSELoss()
|
|
559
|
+
T = len(schedule['betas'])
|
|
560
|
+
null_class = num_classes # use num_classes as "no class" token
|
|
561
|
+
|
|
562
|
+
sqrt_ab = schedule['sqrt_alpha_bar'].to(device)
|
|
563
|
+
sqrt_omab = schedule['sqrt_one_minus_alpha_bar'].to(device)
|
|
564
|
+
|
|
565
|
+
model.train()
|
|
566
|
+
for epoch in range(epochs):
|
|
567
|
+
for x_0, labels in dataloader:
|
|
568
|
+
x_0, labels = x_0.to(device), labels.to(device)
|
|
569
|
+
B = x_0.shape[0]
|
|
570
|
+
|
|
571
|
+
# Random timestep
|
|
572
|
+
t = torch.randint(0, T, (B,), device=device)
|
|
573
|
+
|
|
574
|
+
# CFG: randomly drop condition
|
|
575
|
+
mask = torch.rand(B, device=device) < p_uncond
|
|
576
|
+
labels_cfg = labels.clone()
|
|
577
|
+
labels_cfg[mask] = null_class # replace with null token
|
|
578
|
+
|
|
579
|
+
# Forward diffusion
|
|
580
|
+
x_t, noise = forward_diffusion(x_0, t, sqrt_ab, sqrt_omab)
|
|
581
|
+
|
|
582
|
+
# Predict noise (conditioned on possibly-null label)
|
|
583
|
+
noise_pred = model(x_t, t, labels_cfg)
|
|
584
|
+
|
|
585
|
+
# Loss
|
|
586
|
+
loss = loss_fn(noise_pred, noise)
|
|
587
|
+
optimizer.zero_grad()
|
|
588
|
+
loss.backward()
|
|
589
|
+
optimizer.step()
|
|
590
|
+
|
|
591
|
+
|
|
592
|
+
@torch.no_grad()
|
|
593
|
+
def sample_ddpm_cfg(model, shape, schedule, class_label, guidance_scale=7.5,
|
|
594
|
+
num_classes=10, device='cuda'):
|
|
595
|
+
"""
|
|
596
|
+
DDPM sampling with Classifier-Free Guidance.
|
|
597
|
+
|
|
598
|
+
ε̂_guided = ε̂_uncond + w * (ε̂_cond - ε̂_uncond)
|
|
599
|
+
|
|
600
|
+
Args:
|
|
601
|
+
class_label: (B,) — target class for each sample
|
|
602
|
+
guidance_scale: w — higher = more faithful to condition
|
|
603
|
+
"""
|
|
604
|
+
T = len(schedule['betas'])
|
|
605
|
+
betas = schedule['betas'].to(device)
|
|
606
|
+
alphas = schedule['alphas'].to(device)
|
|
607
|
+
alpha_bar = schedule['alpha_bar'].to(device)
|
|
608
|
+
sqrt_alpha = schedule['sqrt_alpha'].to(device)
|
|
609
|
+
sqrt_omab = schedule['sqrt_one_minus_alpha_bar'].to(device)
|
|
610
|
+
null_class = num_classes
|
|
611
|
+
|
|
612
|
+
x_t = torch.randn(shape, device=device)
|
|
613
|
+
class_label = class_label.to(device)
|
|
614
|
+
null_label = torch.full_like(class_label, null_class)
|
|
615
|
+
|
|
616
|
+
for t in reversed(range(T)):
|
|
617
|
+
t_batch = torch.full((shape[0],), t, device=device, dtype=torch.long)
|
|
618
|
+
|
|
619
|
+
# Two forward passes: conditional + unconditional
|
|
620
|
+
eps_cond = model(x_t, t_batch, class_label) # ε_θ(x_t, t, c)
|
|
621
|
+
eps_uncond = model(x_t, t_batch, null_label) # ε_θ(x_t, t, ∅)
|
|
622
|
+
|
|
623
|
+
# CFG formula
|
|
624
|
+
eps_guided = eps_uncond + guidance_scale * (eps_cond - eps_uncond)
|
|
625
|
+
|
|
626
|
+
# Compute mean
|
|
627
|
+
coeff_xt = 1.0 / sqrt_alpha[t]
|
|
628
|
+
coeff_eps = (1.0 - alphas[t]) / sqrt_omab[t]
|
|
629
|
+
mu = coeff_xt * (x_t - coeff_eps * eps_guided)
|
|
630
|
+
|
|
631
|
+
# Denoise step
|
|
632
|
+
if t > 0:
|
|
633
|
+
sigma = torch.sqrt(betas[t])
|
|
634
|
+
x_t = mu + sigma * torch.randn_like(x_t)
|
|
635
|
+
else:
|
|
636
|
+
x_t = mu
|
|
637
|
+
|
|
638
|
+
return x_t
|
|
639
|
+
</code></pre>
|
|
640
|
+
|
|
641
|
+
<blockquote><p><strong>Exam tip:</strong> CFG cần <strong>hai lần forward pass</strong> mỗi sampling step — một lần conditional, một lần unconditional. Đây là lý do sampling với CFG chậm gấp đôi. Trong DLI lab, nếu bạn chỉ chạy một forward pass thì guidance sẽ không có tác dụng — output giống unconditional.</p></blockquote>
|
|
642
|
+
|
|
643
|
+
<h2 id="cheat-sheet">7. Cheat Sheet: Tổng hợp công thức DDPM</h2>
|
|
644
|
+
|
|
645
|
+
<table>
|
|
646
|
+
<thead>
|
|
647
|
+
<tr><th>Công thức</th><th>Ý nghĩa</th><th>Dùng ở đâu</th></tr>
|
|
648
|
+
</thead>
|
|
649
|
+
<tbody>
|
|
650
|
+
<tr><td>x_t = √ᾱ_t · x_0 + √(1−ᾱ_t) · ε</td><td>Forward diffusion (closed-form)</td><td>Training: tạo x_t từ x_0</td></tr>
|
|
651
|
+
<tr><td>ε̂ = ε_θ(x_t, t)</td><td>U-Net dự đoán noise</td><td>Training: output | Sampling: denoise</td></tr>
|
|
652
|
+
<tr><td>L = MSE(ε, ε̂)</td><td>Simplified ELBO loss</td><td>Training: compute loss</td></tr>
|
|
653
|
+
<tr><td>μ = (1/√α_t)(x_t − (1−α_t)/√(1−ᾱ_t) · ε̂)</td><td>Predicted mean for reverse step</td><td>Sampling: compute μ_θ</td></tr>
|
|
654
|
+
<tr><td>x_{t−1} = μ + √β_t · z</td><td>Sampling step (z=0 khi t=0)</td><td>Sampling: denoise one step</td></tr>
|
|
655
|
+
<tr><td>ε̂ = ε̂_∅ + w(ε̂_c − ε̂_∅)</td><td>CFG guidance formula</td><td>Conditional sampling</td></tr>
|
|
656
|
+
</tbody>
|
|
657
|
+
</table>
|
|
658
|
+
|
|
659
|
+
<pre><code class="language-text">
|
|
660
|
+
DDPM Pipeline Summary
|
|
661
|
+
═════════════════════
|
|
662
|
+
|
|
663
|
+
┌─────────────────────────────────────────────────────────┐
|
|
664
|
+
│ TRAINING │
|
|
665
|
+
│ │
|
|
666
|
+
│ x_0 ──[forward_diffusion]──► x_t ──[U-Net]──► ε̂ │
|
|
667
|
+
│ │ ↑ │ │
|
|
668
|
+
│ └─── t,ε (random) ───────────────── MSE(ε, ε̂) │ │
|
|
669
|
+
│ │ │
|
|
670
|
+
│ backprop │
|
|
671
|
+
└─────────────────────────────────────────────────────────┘
|
|
672
|
+
|
|
673
|
+
┌─────────────────────────────────────────────────────────┐
|
|
674
|
+
│ SAMPLING │
|
|
675
|
+
│ │
|
|
676
|
+
│ x_T ──► [U-Net] ──► ε̂ ──► μ_θ ──► x_{T-1} │
|
|
677
|
+
│ │ │
|
|
678
|
+
│ [U-Net] ──► ε̂ ──► μ_θ ──► x_{T-2} │
|
|
679
|
+
│ │ │
|
|
680
|
+
│ ...repeat T times... │ │
|
|
681
|
+
│ ▼ │
|
|
682
|
+
│ x_0 (generated!) │
|
|
683
|
+
└─────────────────────────────────────────────────────────┘
|
|
684
|
+
|
|
685
|
+
┌─────────────────────────────────────────────────────────┐
|
|
686
|
+
│ CFG SAMPLING │
|
|
687
|
+
│ │
|
|
688
|
+
│ At each step t: │
|
|
689
|
+
│ ε̂_∅ = UNet(x_t, t, null) ← unconditional │
|
|
690
|
+
│ ε̂_c = UNet(x_t, t, class) ← conditional │
|
|
691
|
+
│ ε̂ = ε̂_∅ + w · (ε̂_c − ε̂_∅) ← guided prediction │
|
|
692
|
+
│ x_{t-1} = denoise(x_t, ε̂) │
|
|
693
|
+
└─────────────────────────────────────────────────────────┘
|
|
694
|
+
</code></pre>
|
|
695
|
+
|
|
696
|
+
<h2 id="practice">8. Practice Questions</h2>
|
|
697
|
+
|
|
698
|
+
<p>5 coding questions — hãy tự implement trước khi xem đáp án.</p>
|
|
699
|
+
|
|
700
|
+
<p><strong>Q1: Implement forward_diffusion(x_0, t, noise_schedule) → x_t, noise</strong></p>
|
|
701
|
+
|
|
702
|
+
<p>Viết hàm <code>forward_diffusion</code> nhận một batch ảnh x_0, tensor timesteps t, và dictionary noise_schedule chứa các precomputed coefficients. Trả về x_t và noise ε đã dùng.</p>
|
|
703
|
+
|
|
704
|
+
<pre><code class="language-python">
|
|
705
|
+
def forward_diffusion(x_0, t, noise_schedule):
|
|
706
|
+
"""
|
|
707
|
+
Args:
|
|
708
|
+
x_0: (B, C, H, W) — clean images, normalized to [-1, 1]
|
|
709
|
+
t: (B,) — integer timestep indices
|
|
710
|
+
noise_schedule: dict with keys:
|
|
711
|
+
'sqrt_alpha_bar': (T,) tensor
|
|
712
|
+
'sqrt_one_minus_alpha_bar': (T,) tensor
|
|
713
|
+
Returns:
|
|
714
|
+
x_t: (B, C, H, W) — noisy images
|
|
715
|
+
noise: (B, C, H, W) — the Gaussian noise added
|
|
716
|
+
"""
|
|
717
|
+
# TODO: Implement forward diffusion using reparameterization trick
|
|
718
|
+
pass
|
|
719
|
+
</code></pre>
|
|
720
|
+
|
|
721
|
+
<details>
|
|
722
|
+
<summary>Show Answer Q1</summary>
|
|
723
|
+
|
|
724
|
+
<pre><code class="language-python">
|
|
725
|
+
def forward_diffusion(x_0, t, noise_schedule):
|
|
726
|
+
sqrt_alpha_bar = noise_schedule['sqrt_alpha_bar']
|
|
727
|
+
sqrt_one_minus_alpha_bar = noise_schedule['sqrt_one_minus_alpha_bar']
|
|
728
|
+
|
|
729
|
+
# Sample noise ε ~ N(0, I)
|
|
730
|
+
noise = torch.randn_like(x_0)
|
|
731
|
+
|
|
732
|
+
# Gather coefficients for batch and reshape for broadcasting
|
|
733
|
+
# (B,) → (B, 1, 1, 1)
|
|
734
|
+
s_ab = sqrt_alpha_bar[t].view(-1, 1, 1, 1)
|
|
735
|
+
s_omab = sqrt_one_minus_alpha_bar[t].view(-1, 1, 1, 1)
|
|
736
|
+
|
|
737
|
+
# Reparameterization trick:
|
|
738
|
+
# x_t = √(ᾱ_t) * x_0 + √(1 - ᾱ_t) * ε
|
|
739
|
+
x_t = s_ab * x_0 + s_omab * noise
|
|
740
|
+
|
|
741
|
+
return x_t, noise
|
|
742
|
+
</code></pre>
|
|
743
|
+
|
|
744
|
+
<p><em>Explanation: Ba bước key: (1) sample noise cùng shape với x_0 bằng <code>torch.randn_like</code>, (2) gather coefficients theo index t rồi reshape <code>.view(-1, 1, 1, 1)</code> để broadcast 4D, (3) áp dụng reparameterization trick. Lưu ý phải trả về cả noise vì training loop cần nó làm target cho MSE loss.</em></p>
|
|
745
|
+
</details>
|
|
746
|
+
|
|
747
|
+
<p><strong>Q2: Implement reverse diffusion sampling loop</strong></p>
|
|
748
|
+
|
|
749
|
+
<p>Viết hàm <code>sample_ddpm</code> tạo ảnh mới từ pure noise bằng cách lặp reverse diffusion steps. Model đã được train xong.</p>
|
|
750
|
+
|
|
751
|
+
<pre><code class="language-python">
|
|
752
|
+
@torch.no_grad()
|
|
753
|
+
def sample_ddpm(model, n_samples, img_channels, img_size, schedule, device):
|
|
754
|
+
"""
|
|
755
|
+
Args:
|
|
756
|
+
model: trained U-Net, expects (x_t, t_batch) → predicted noise
|
|
757
|
+
n_samples: int — number of images to generate
|
|
758
|
+
img_channels: int — e.g., 1 for MNIST
|
|
759
|
+
img_size: int — e.g., 28
|
|
760
|
+
schedule: dict with 'betas', 'alphas', 'alpha_bar',
|
|
761
|
+
'sqrt_alpha', 'sqrt_one_minus_alpha_bar'
|
|
762
|
+
Returns:
|
|
763
|
+
images: (n_samples, C, H, W) — generated images
|
|
764
|
+
"""
|
|
765
|
+
# TODO: Implement the full DDPM sampling algorithm
|
|
766
|
+
pass
|
|
767
|
+
</code></pre>
|
|
768
|
+
|
|
769
|
+
<details>
|
|
770
|
+
<summary>Show Answer Q2</summary>
|
|
771
|
+
|
|
772
|
+
<pre><code class="language-python">
|
|
773
|
+
@torch.no_grad()
|
|
774
|
+
def sample_ddpm(model, n_samples, img_channels, img_size, schedule, device):
|
|
775
|
+
T = len(schedule['betas'])
|
|
776
|
+
betas = schedule['betas'].to(device)
|
|
777
|
+
alphas = schedule['alphas'].to(device)
|
|
778
|
+
sqrt_alpha = schedule['sqrt_alpha'].to(device)
|
|
779
|
+
sqrt_omab = schedule['sqrt_one_minus_alpha_bar'].to(device)
|
|
780
|
+
|
|
781
|
+
shape = (n_samples, img_channels, img_size, img_size)
|
|
782
|
+
|
|
783
|
+
# Start from pure noise x_T ~ N(0, I)
|
|
784
|
+
x_t = torch.randn(shape, device=device)
|
|
785
|
+
|
|
786
|
+
for t in reversed(range(T)):
|
|
787
|
+
t_batch = torch.full((n_samples,), t, device=device, dtype=torch.long)
|
|
788
|
+
|
|
789
|
+
# U-Net predicts noise
|
|
790
|
+
eps_pred = model(x_t, t_batch)
|
|
791
|
+
|
|
792
|
+
# Compute predicted mean:
|
|
793
|
+
# μ_θ = (1/√α_t) * (x_t − ((1−α_t) / √(1−ᾱ_t)) * ε̂)
|
|
794
|
+
coeff_xt = 1.0 / sqrt_alpha[t]
|
|
795
|
+
coeff_eps = (1.0 - alphas[t]) / sqrt_omab[t]
|
|
796
|
+
mu = coeff_xt * (x_t - coeff_eps * eps_pred)
|
|
797
|
+
|
|
798
|
+
# Sample x_{t-1}: add noise for t > 0, otherwise return mean
|
|
799
|
+
if t > 0:
|
|
800
|
+
sigma = torch.sqrt(betas[t])
|
|
801
|
+
z = torch.randn_like(x_t)
|
|
802
|
+
x_t = mu + sigma * z
|
|
803
|
+
else:
|
|
804
|
+
x_t = mu
|
|
805
|
+
|
|
806
|
+
return x_t
|
|
807
|
+
</code></pre>
|
|
808
|
+
|
|
809
|
+
<p><em>Explanation: Sampling loop chạy ngược từ t=T-1 về t=0. Tại mỗi step: (1) U-Net dự đoán noise ε̂, (2) tính mean μ_θ bằng công thức DDPM, (3) thêm noise z nếu t > 0 (stochastic sampling). Critical: dùng <code>@torch.no_grad()</code> để tránh tích luỹ gradient qua 1000 steps — sẽ gây OOM. Bước t=0 không thêm noise vì đó là output cuối cùng.</em></p>
|
|
810
|
+
</details>
|
|
811
|
+
|
|
812
|
+
<p><strong>Q3: Debug — model outputs black images</strong></p>
|
|
813
|
+
|
|
814
|
+
<p>Một sinh viên implement reverse sampling nhưng kết quả luôn ra ảnh đen (gần 0). Tìm bug trong code dưới đây:</p>
|
|
815
|
+
|
|
816
|
+
<pre><code class="language-python">
|
|
817
|
+
@torch.no_grad()
|
|
818
|
+
def buggy_sample(model, shape, schedule, device):
|
|
819
|
+
T = len(schedule['betas'])
|
|
820
|
+
betas = schedule['betas'].to(device)
|
|
821
|
+
alphas = schedule['alphas'].to(device)
|
|
822
|
+
sqrt_alpha = schedule['sqrt_alpha'].to(device)
|
|
823
|
+
sqrt_omab = schedule['sqrt_one_minus_alpha_bar'].to(device)
|
|
824
|
+
|
|
825
|
+
x_t = torch.randn(shape, device=device)
|
|
826
|
+
|
|
827
|
+
for t in reversed(range(T)):
|
|
828
|
+
t_batch = torch.full((shape[0],), t, device=device, dtype=torch.long)
|
|
829
|
+
eps_pred = model(x_t, t_batch)
|
|
830
|
+
|
|
831
|
+
# BUG IS HERE — find it!
|
|
832
|
+
coeff_xt = 1.0 / sqrt_alpha[t]
|
|
833
|
+
coeff_eps = (1.0 - alphas[t]) / sqrt_omab[t]
|
|
834
|
+
mu = coeff_xt * (x_t + coeff_eps * eps_pred) # line A
|
|
835
|
+
|
|
836
|
+
if t > 0:
|
|
837
|
+
sigma = torch.sqrt(betas[t])
|
|
838
|
+
x_t = mu + sigma * torch.randn_like(x_t)
|
|
839
|
+
else:
|
|
840
|
+
x_t = mu
|
|
841
|
+
|
|
842
|
+
return x_t
|
|
843
|
+
</code></pre>
|
|
844
|
+
|
|
845
|
+
<details>
|
|
846
|
+
<summary>Show Answer Q3</summary>
|
|
847
|
+
|
|
848
|
+
<p><strong>Bug:</strong> Dòng tính μ dùng dấu <code>+</code> thay vì <code>−</code> trước <code>coeff_eps * eps_pred</code>.</p>
|
|
849
|
+
|
|
850
|
+
<pre><code class="language-python">
|
|
851
|
+
# BUG (line A):
|
|
852
|
+
mu = coeff_xt * (x_t + coeff_eps * eps_pred) # ← WRONG: + instead of -
|
|
853
|
+
|
|
854
|
+
# FIX:
|
|
855
|
+
mu = coeff_xt * (x_t - coeff_eps * eps_pred) # ← CORRECT: subtract noise
|
|
856
|
+
</code></pre>
|
|
857
|
+
|
|
858
|
+
<p><em>Explanation: Công thức DDPM reverse mean là μ = (1/√α_t)(x_t <strong>−</strong> ((1−α_t)/√(1−ᾱ_t)) · ε̂). Dấu trừ là bản chất của "denoise" — ta trừ đi phần noise predicted. Khi dùng dấu cộng, ta thực chất <strong>thêm noise</strong> thay vì bỏ noise → qua 1000 steps, ảnh bị trung hoà (oscillate quanh 0) → output ra ảnh gần 0 (đen). Đây là bug tinh vi vì code vẫn chạy không lỗi, output vẫn đúng shape — chỉ giá trị sai.</em></p>
|
|
859
|
+
</details>
|
|
860
|
+
|
|
861
|
+
<p><strong>Q4: Implement CFG sampling with guidance scale</strong></p>
|
|
862
|
+
|
|
863
|
+
<p>Model đã được train với condition dropout. Viết hàm sampling có Classifier-Free Guidance.</p>
|
|
864
|
+
|
|
865
|
+
<pre><code class="language-python">
|
|
866
|
+
@torch.no_grad()
|
|
867
|
+
def sample_cfg(model, shape, schedule, class_labels, guidance_scale,
|
|
868
|
+
num_classes, device):
|
|
869
|
+
"""
|
|
870
|
+
Args:
|
|
871
|
+
model: U-Net with signature model(x_t, t, class_label) → noise
|
|
872
|
+
shape: (B, C, H, W)
|
|
873
|
+
class_labels: (B,) — target class indices
|
|
874
|
+
guidance_scale: float w — e.g. 7.5
|
|
875
|
+
num_classes: int — total classes (null_class = num_classes)
|
|
876
|
+
Returns:
|
|
877
|
+
images: (B, C, H, W)
|
|
878
|
+
"""
|
|
879
|
+
# TODO: Implement CFG sampling
|
|
880
|
+
# Hint: two forward passes per step — conditional & unconditional
|
|
881
|
+
pass
|
|
882
|
+
</code></pre>
|
|
883
|
+
|
|
884
|
+
<details>
|
|
885
|
+
<summary>Show Answer Q4</summary>
|
|
886
|
+
|
|
887
|
+
<pre><code class="language-python">
|
|
888
|
+
@torch.no_grad()
|
|
889
|
+
def sample_cfg(model, shape, schedule, class_labels, guidance_scale,
|
|
890
|
+
num_classes, device):
|
|
891
|
+
T = len(schedule['betas'])
|
|
892
|
+
betas = schedule['betas'].to(device)
|
|
893
|
+
alphas = schedule['alphas'].to(device)
|
|
894
|
+
sqrt_alpha = schedule['sqrt_alpha'].to(device)
|
|
895
|
+
sqrt_omab = schedule['sqrt_one_minus_alpha_bar'].to(device)
|
|
896
|
+
null_class = num_classes
|
|
897
|
+
|
|
898
|
+
x_t = torch.randn(shape, device=device)
|
|
899
|
+
class_labels = class_labels.to(device)
|
|
900
|
+
null_labels = torch.full_like(class_labels, null_class)
|
|
901
|
+
|
|
902
|
+
for t in reversed(range(T)):
|
|
903
|
+
t_batch = torch.full((shape[0],), t, device=device, dtype=torch.long)
|
|
904
|
+
|
|
905
|
+
# Two forward passes
|
|
906
|
+
eps_uncond = model(x_t, t_batch, null_labels) # ε_θ(x_t, t, ∅)
|
|
907
|
+
eps_cond = model(x_t, t_batch, class_labels) # ε_θ(x_t, t, c)
|
|
908
|
+
|
|
909
|
+
# CFG: ε̂ = ε_uncond + w * (ε_cond - ε_uncond)
|
|
910
|
+
eps_guided = eps_uncond + guidance_scale * (eps_cond - eps_uncond)
|
|
911
|
+
|
|
912
|
+
# Reverse step with guided noise prediction
|
|
913
|
+
coeff_xt = 1.0 / sqrt_alpha[t]
|
|
914
|
+
coeff_eps = (1.0 - alphas[t]) / sqrt_omab[t]
|
|
915
|
+
mu = coeff_xt * (x_t - coeff_eps * eps_guided)
|
|
916
|
+
|
|
917
|
+
if t > 0:
|
|
918
|
+
sigma = torch.sqrt(betas[t])
|
|
919
|
+
x_t = mu + sigma * torch.randn_like(x_t)
|
|
920
|
+
else:
|
|
921
|
+
x_t = mu
|
|
922
|
+
|
|
923
|
+
return x_t
|
|
924
|
+
</code></pre>
|
|
925
|
+
|
|
926
|
+
<p><em>Explanation: CFG sampling khác standard sampling ở chỗ tại mỗi step ta chạy <strong>hai lần U-Net</strong>: (1) unconditional với null_class, (2) conditional với class thật. Sau đó combine: ε̂ = ε̂_∅ + w·(ε̂_c − ε̂_∅). Khi w=1.0 → standard conditional (không có guidance). Khi w>1.0 → amplify sự khác biệt giữa conditional và unconditional → ảnh rõ nét hơn nhưng ít diverse. null_class thường = num_classes (index nằm ngoài class thật).</em></p>
|
|
927
|
+
</details>
|
|
928
|
+
|
|
929
|
+
<p><strong>Q5: Compare linear vs cosine schedule — when does ᾱ_t drop below 0.01?</strong></p>
|
|
930
|
+
|
|
931
|
+
<p>Viết code tính và so sánh: với T=1000, ᾱ_t giảm xuống dưới 0.01 tại timestep nào cho mỗi schedule? Điều này có ý nghĩa gì cho chất lượng generation?</p>
|
|
932
|
+
|
|
933
|
+
<pre><code class="language-python">
|
|
934
|
+
def compare_schedules(T=1000):
|
|
935
|
+
"""
|
|
936
|
+
Compute alpha_bar for both linear and cosine schedules.
|
|
937
|
+
Find the timestep where alpha_bar drops below 0.01 for each.
|
|
938
|
+
Print comparison results.
|
|
939
|
+
"""
|
|
940
|
+
# TODO: implement using linear_beta_schedule() and cosine_beta_schedule()
|
|
941
|
+
pass
|
|
942
|
+
</code></pre>
|
|
943
|
+
|
|
944
|
+
<details>
|
|
945
|
+
<summary>Show Answer Q5</summary>
|
|
946
|
+
|
|
947
|
+
<pre><code class="language-python">
|
|
948
|
+
import torch
|
|
949
|
+
import math
|
|
950
|
+
|
|
951
|
+
def linear_beta_schedule(T, beta_start=1e-4, beta_end=0.02):
|
|
952
|
+
return torch.linspace(beta_start, beta_end, T)
|
|
953
|
+
|
|
954
|
+
def cosine_beta_schedule(T, s=0.008):
|
|
955
|
+
steps = torch.arange(T + 1, dtype=torch.float32)
|
|
956
|
+
f_t = torch.cos(((steps / T) + s) / (1 + s) * (math.pi / 2)) ** 2
|
|
957
|
+
alpha_bar = f_t / f_t[0]
|
|
958
|
+
betas = 1 - (alpha_bar[1:] / alpha_bar[:-1])
|
|
959
|
+
return torch.clamp(betas, min=1e-5, max=0.999)
|
|
960
|
+
|
|
961
|
+
def compare_schedules(T=1000):
|
|
962
|
+
# Linear schedule
|
|
963
|
+
betas_lin = linear_beta_schedule(T)
|
|
964
|
+
alphas_lin = 1.0 - betas_lin
|
|
965
|
+
alpha_bar_lin = torch.cumprod(alphas_lin, dim=0)
|
|
966
|
+
|
|
967
|
+
# Cosine schedule
|
|
968
|
+
betas_cos = cosine_beta_schedule(T)
|
|
969
|
+
alphas_cos = 1.0 - betas_cos
|
|
970
|
+
alpha_bar_cos = torch.cumprod(alphas_cos, dim=0)
|
|
971
|
+
|
|
972
|
+
# Find where alpha_bar < 0.01
|
|
973
|
+
threshold = 0.01
|
|
974
|
+
t_lin = (alpha_bar_lin < threshold).nonzero(as_tuple=True)[0][0].item()
|
|
975
|
+
t_cos = (alpha_bar_cos < threshold).nonzero(as_tuple=True)[0][0].item()
|
|
976
|
+
|
|
977
|
+
print(f"Linear schedule: ᾱ_t < {threshold} at t = {t_lin}")
|
|
978
|
+
print(f" ᾱ at t=250: {alpha_bar_lin[250]:.4f}")
|
|
979
|
+
print(f" ᾱ at t=500: {alpha_bar_lin[500]:.4f}")
|
|
980
|
+
print(f" ᾱ at t=750: {alpha_bar_lin[750]:.6f}")
|
|
981
|
+
print()
|
|
982
|
+
print(f"Cosine schedule: ᾱ_t < {threshold} at t = {t_cos}")
|
|
983
|
+
print(f" ᾱ at t=250: {alpha_bar_cos[250]:.4f}")
|
|
984
|
+
print(f" ᾱ at t=500: {alpha_bar_cos[500]:.4f}")
|
|
985
|
+
print(f" ᾱ at t=750: {alpha_bar_cos[750]:.4f}")
|
|
986
|
+
print()
|
|
987
|
+
print(f"Difference: cosine giữ signal thêm {t_cos - t_lin} timesteps")
|
|
988
|
+
|
|
989
|
+
compare_schedules()
|
|
990
|
+
# Output (approximate):
|
|
991
|
+
# Linear schedule: ᾱ_t < 0.01 at t ≈ 650
|
|
992
|
+
# ᾱ at t=250: 0.6766
|
|
993
|
+
# ᾱ at t=500: 0.0473
|
|
994
|
+
# ᾱ at t=750: 0.000014
|
|
995
|
+
#
|
|
996
|
+
# Cosine schedule: ᾱ_t < 0.01 at t ≈ 940
|
|
997
|
+
# ᾱ at t=250: 0.8536
|
|
998
|
+
# ᾱ at t=500: 0.5000
|
|
999
|
+
# ᾱ at t=750: 0.1464
|
|
1000
|
+
#
|
|
1001
|
+
# Difference: cosine giữ signal thêm ~290 timesteps
|
|
1002
|
+
</code></pre>
|
|
1003
|
+
|
|
1004
|
+
<p><em>Explanation: Linear schedule destroy signal sớm — ᾱ_t < 0.01 quanh t≈650, nghĩa là 35% cuối của chain gần như vô ích (noise gần như pure). Cosine giữ ᾱ_t > 0.01 đến t≈940, sử dụng hiệu quả hơn toàn bộ T steps. Đặc biệt chú ý: tại t=500 (giữa chain), linear chỉ còn ᾱ≈0.05 (5% signal) trong khi cosine còn ᾱ≈0.50 (50% signal). Điều này giải thích vì sao cosine cho chất lượng generation tốt hơn — model có gradient hữu ích từ nhiều timesteps hơn, không bị wasted computation ở vùng noise thuần.</em></p>
|
|
1005
|
+
</details>
|
|
1006
|
+
|
|
1007
|
+
<blockquote><p><strong>Exam tip:</strong> Trong DLI assessment, câu hỏi về noise schedule thường yêu cầu bạn <strong>giải thích tại sao</strong> một schedule tốt hơn. Key insight: schedule tốt phải phân bố signal destruction <strong>đều qua tất cả timesteps</strong> — không quá nhanh (linear), không quá chậm. Cosine đạt điều này bằng cách thiết kế ᾱ_t trực tiếp thay vì β_t.</p></blockquote>
|