学習目標
この章を読むことで、以下を習得できます:
- ✅ 拡散モデルの基本原理(Forward/Reverse Process)を理解できる
- ✅ DDPM(Denoising Diffusion Probabilistic Models)の数学的定式化を理解できる
- ✅ ノイズスケジュールとサンプリングアルゴリズムを実装できる
- ✅ U-Net Denoiserの構造と訓練方法を習得できる
- ✅ Latent Diffusion Models(Stable Diffusion)の仕組みを理解できる
- ✅ CLIP Guidanceとテキスト条件付き生成を実装できる
- ✅ PyTorchで実践的な画像生成システムを構築できる
4.1 拡散モデルの基礎
4.1.1 拡散モデルとは何か
拡散モデル(Diffusion Models)は、データに徐々にノイズを加える過程(Forward Process)と、ノイズから元のデータを復元する過程(Reverse Process)を学習する生成モデルです。2020年代に入り、画像生成のSOTAを達成し、Stable Diffusion、DALL-E 2、Imagen等の基盤技術となっています。
| 特性 | GAN | VAE | 拡散モデル |
|---|---|---|---|
| 生成方式 | 敵対的学習 | 変分推論 | ノイズ除去 |
| 訓練安定性 | 低(モード崩壊) | 中 | 高 |
| 生成品質 | 高(訓練時) | 中(ぼやけ) | 非常に高 |
| 多様性 | 低(モード崩壊) | 高 | 高 |
| 計算コスト | 低〜中 | 低〜中 | 高(反復処理) |
| 代表モデル | StyleGAN | β-VAE | DDPM, Stable Diffusion |
4.1.2 Forward Process:ノイズの付加
Forward Processは、元の画像 $x_0$ に対して $T$ ステップで徐々にガウスノイズを追加していく過程です。
$$ q(x_t | x_{t-1}) = \mathcal{N}(x_t; \sqrt{1-\beta_t} x_{t-1}, \beta_t I) $$ここで:
- $x_t$: タイムステップ $t$ での画像
- $\beta_t$: ノイズスケジュール(0.0001 〜 0.02 程度)
- $\mathcal{N}$: ガウス分布
重要な性質:再パラメータ化トリックにより、任意のステップ $t$ の画像を直接サンプリング可能:
$$ x_t = \sqrt{\bar{\alpha}_t} x_0 + \sqrt{1-\bar{\alpha}_t} \epsilon $$ここで:
- $\alpha_t = 1 - \beta_t$
- $\bar{\alpha}_t = \prod_{s=1}^{t} \alpha_s$
- $\epsilon \sim \mathcal{N}(0, I)$
(元画像)"] -->|"+ ノイズ β₁"| X1["x₁"] X1 -->|"+ ノイズ β₂"| X2["x₂"] X2 -->|"..."| X3["..."] X3 -->|"+ ノイズ βT"| XT["xT
(純粋ノイズ)"] style X0 fill:#27ae60,color:#fff style XT fill:#e74c3c,color:#fff style X1 fill:#f39c12,color:#fff style X2 fill:#e67e22,color:#fff
4.1.3 Reverse Process:ノイズ除去による生成
Reverse Processは、純粋ノイズ $x_T \sim \mathcal{N}(0, I)$ から始めて、徐々にノイズを除去して元の画像を復元する過程です。
$$ p_\theta(x_{t-1} | x_t) = \mathcal{N}(x_{t-1}; \mu_\theta(x_t, t), \Sigma_\theta(x_t, t)) $$ここで $\mu_\theta$(平均)と $\Sigma_\theta$(共分散)をニューラルネットワークで学習します。DDPMでは、共分散は固定し、平均のみを学習する簡略化が一般的です。
重要: Reverse Processは、ノイズ $\epsilon$ を予測するタスクとして定式化されます。これにより、ネットワークは「ノイズ除去器(Denoiser)」として機能します。
4.1.4 拡散モデルの直感的理解
import numpy as np
import matplotlib.pyplot as plt
from PIL import Image
# シンプルな1次元データでの拡散プロセス可視化
np.random.seed(42)
# 元データ:2つのガウス混合
def sample_data(n=1000):
"""2つのモードを持つデータ生成"""
mode1 = np.random.randn(n//2) * 0.5 + 2
mode2 = np.random.randn(n//2) * 0.5 - 2
return np.concatenate([mode1, mode2])
# Forward diffusion process
def forward_diffusion(x0, num_steps=50):
"""Forward diffusion: データにノイズを追加"""
# Linear noise schedule
betas = np.linspace(0.0001, 0.02, num_steps)
alphas = 1 - betas
alphas_cumprod = np.cumprod(alphas)
# 各タイムステップでのデータ保存
x_history = [x0]
for t in range(1, num_steps):
noise = np.random.randn(*x0.shape)
x_t = np.sqrt(alphas_cumprod[t]) * x0 + np.sqrt(1 - alphas_cumprod[t]) * noise
x_history.append(x_t)
return x_history, betas, alphas_cumprod
# デモンストレーション
print("=== Forward Diffusion Process Visualization ===\n")
x0 = sample_data(1000)
x_history, betas, alphas_cumprod = forward_diffusion(x0, num_steps=50)
# 可視化
fig, axes = plt.subplots(2, 5, figsize=(18, 7))
axes = axes.flatten()
timesteps_to_show = [0, 5, 10, 15, 20, 25, 30, 35, 40, 49]
for idx, t in enumerate(timesteps_to_show):
ax = axes[idx]
ax.hist(x_history[t], bins=50, density=True, alpha=0.7, color='steelblue', edgecolor='black')
ax.set_xlim(-8, 8)
ax.set_ylim(0, 0.5)
ax.set_title(f't = {t}\nα̅ = {alphas_cumprod[t]:.4f}' if t > 0 else f't = 0 (Original)',
fontsize=11, fontweight='bold')
ax.set_xlabel('x', fontsize=9)
ax.set_ylabel('Density', fontsize=9)
ax.grid(alpha=0.3)
plt.suptitle('Forward Diffusion Process: 元データ → ガウスノイズ',
fontsize=14, fontweight='bold', y=1.02)
plt.tight_layout()
plt.show()
print("\n特徴:")
print("✓ t = 0: 元の2モード分布(明確な構造)")
print("✓ t = 10-20: 構造が徐々に崩れる")
print("✓ t = 49: ほぼ標準ガウス分布(構造が完全に失われる)")
print("\nReverse Process:")
print("✓ ノイズ(t=49)から始めて、徐々に構造を復元")
print("✓ 学習したDenoiserで各ステップのノイズを除去")
print("✓ 最終的に元の2モード分布を再現")
出力:
=== Forward Diffusion Process Visualization ===
特徴:
✓ t = 0: 元の2モード分布(明確な構造)
✓ t = 10-20: 構造が徐々に崩れる
✓ t = 49: ほぼ標準ガウス分布(構造が完全に失われる)
Reverse Process:
✓ ノイズ(t=49)から始めて、徐々に構造を復元
✓ 学習したDenoiserで各ステップのノイズを除去
✓ 最終的に元の2モード分布を再現
4.2 DDPM(Denoising Diffusion Probabilistic Models)
4.2.1 DDPMの数学的定式化
DDPMは、2020年にHoら(UC Berkeley)が提案した拡散モデルの代表的手法です。
訓練目標
DDPMの損失関数は、変分下界(ELBO)から導出されますが、実際には単純な形になります:
$$ \mathcal{L}_{\text{simple}} = \mathbb{E}_{t, x_0, \epsilon} \left[ \| \epsilon - \epsilon_\theta(x_t, t) \|^2 \right] $$これは、「ノイズ $\epsilon$ を予測する」タスクの平均二乗誤差です。
アルゴリズム詳細
訓練アルゴリズム:
- 訓練データから $x_0$ をサンプル
- タイムステップ $t \sim \text{Uniform}(1, T)$ をサンプル
- ノイズ $\epsilon \sim \mathcal{N}(0, I)$ をサンプル
- $x_t = \sqrt{\bar{\alpha}_t} x_0 + \sqrt{1-\bar{\alpha}_t} \epsilon$ を計算
- 損失 $\| \epsilon - \epsilon_\theta(x_t, t) \|^2$ を最小化
サンプリングアルゴリズム:
- $x_T \sim \mathcal{N}(0, I)$ からスタート
- $t = T, T-1, \ldots, 1$ について: $$x_{t-1} = \frac{1}{\sqrt{\alpha_t}} \left( x_t - \frac{\beta_t}{\sqrt{1-\bar{\alpha}_t}} \epsilon_\theta(x_t, t) \right) + \sigma_t z$$ ここで $z \sim \mathcal{N}(0, I)$($t > 1$ の場合)
- $x_0$ を返す
4.2.2 ノイズスケジュール
ノイズスケジュール $\beta_t$ の設計は生成品質に大きく影響します。
| スケジュール | 定義 | 特徴 |
|---|---|---|
| Linear | $\beta_t = \beta_{\min} + \frac{t}{T}(\beta_{\max} - \beta_{\min})$ | シンプル、元論文で使用 |
| Cosine | $\bar{\alpha}_t = \frac{f(t)}{f(0)}$, $f(t) = \cos^2\left(\frac{t/T + s}{1+s} \cdot \frac{\pi}{2}\right)$ | より滑らかなノイズ遷移 |
| Quadratic | $\beta_t = \beta_{\min}^2 + t^2 (\beta_{\max}^2 - \beta_{\min}^2)$ | 非線形な遷移 |
import numpy as np
import matplotlib.pyplot as plt
def linear_beta_schedule(timesteps, beta_start=0.0001, beta_end=0.02):
"""Linear noise schedule"""
return np.linspace(beta_start, beta_end, timesteps)
def cosine_beta_schedule(timesteps, s=0.008):
"""Cosine noise schedule (Improved DDPM)"""
steps = timesteps + 1
x = np.linspace(0, timesteps, steps)
alphas_cumprod = np.cos(((x / timesteps) + s) / (1 + s) * np.pi * 0.5) ** 2
alphas_cumprod = alphas_cumprod / alphas_cumprod[0]
betas = 1 - (alphas_cumprod[1:] / alphas_cumprod[:-1])
return np.clip(betas, 0, 0.999)
def quadratic_beta_schedule(timesteps, beta_start=0.0001, beta_end=0.02):
"""Quadratic noise schedule"""
return np.linspace(beta_start**0.5, beta_end**0.5, timesteps) ** 2
# 可視化
print("=== Noise Schedule Comparison ===\n")
timesteps = 1000
linear_betas = linear_beta_schedule(timesteps)
cosine_betas = cosine_beta_schedule(timesteps)
quadratic_betas = quadratic_beta_schedule(timesteps)
# Alpha累積積の計算
def compute_alphas_cumprod(betas):
alphas = 1 - betas
return np.cumprod(alphas)
linear_alphas = compute_alphas_cumprod(linear_betas)
cosine_alphas = compute_alphas_cumprod(cosine_betas)
quadratic_alphas = compute_alphas_cumprod(quadratic_betas)
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
# Left: Beta values
ax1 = axes[0]
ax1.plot(linear_betas, label='Linear', linewidth=2, alpha=0.8)
ax1.plot(cosine_betas, label='Cosine', linewidth=2, alpha=0.8)
ax1.plot(quadratic_betas, label='Quadratic', linewidth=2, alpha=0.8)
ax1.set_xlabel('Timestep t', fontsize=12, fontweight='bold')
ax1.set_ylabel('βₜ (Noise Level)', fontsize=12, fontweight='bold')
ax1.set_title('Noise Schedules: βₜ', fontsize=13, fontweight='bold')
ax1.legend(fontsize=10)
ax1.grid(alpha=0.3)
# Right: Cumulative alpha
ax2 = axes[1]
ax2.plot(linear_alphas, label='Linear', linewidth=2, alpha=0.8)
ax2.plot(cosine_alphas, label='Cosine', linewidth=2, alpha=0.8)
ax2.plot(quadratic_alphas, label='Quadratic', linewidth=2, alpha=0.8)
ax2.set_xlabel('Timestep t', fontsize=12, fontweight='bold')
ax2.set_ylabel('ᾱₜ (Signal Strength)', fontsize=12, fontweight='bold')
ax2.set_title('Cumulative Product: ᾱₜ = ∏ αₛ', fontsize=13, fontweight='bold')
ax2.legend(fontsize=10)
ax2.grid(alpha=0.3)
plt.tight_layout()
plt.show()
print("\nスケジュール特性:")
print(f"Linear - β range: [{linear_betas.min():.6f}, {linear_betas.max():.6f}]")
print(f"Cosine - β range: [{cosine_betas.min():.6f}, {cosine_betas.max():.6f}]")
print(f"Quadratic- β range: [{quadratic_betas.min():.6f}, {quadratic_betas.max():.6f}]")
print(f"\nFinal ᾱ_T (信号残存率):")
print(f"Linear: {linear_alphas[-1]:.6f}")
print(f"Cosine: {cosine_alphas[-1]:.6f}")
print(f"Quadratic: {quadratic_alphas[-1]:.6f}")
出力:
=== Noise Schedule Comparison ===
スケジュール特性:
Linear - β range: [0.000100, 0.020000]
Cosine - β range: [0.000020, 0.999000]
Quadratic- β range: [0.000000, 0.000400]
Final ᾱ_T (信号残存率):
Linear: 0.000062
Cosine: 0.000000
Quadratic: 0.670320
4.2.3 DDPM訓練の実装
import torch
import torch.nn as nn
import torch.nn.functional as F
class DDPMDiffusion:
"""DDPM拡散プロセスの実装"""
def __init__(self, timesteps=1000, beta_start=0.0001, beta_end=0.02, schedule='linear'):
"""
Args:
timesteps: 拡散ステップ数
beta_start: 開始ノイズレベル
beta_end: 終了ノイズレベル
schedule: 'linear', 'cosine', 'quadratic'
"""
self.timesteps = timesteps
# ノイズスケジュール
if schedule == 'linear':
self.betas = torch.linspace(beta_start, beta_end, timesteps)
elif schedule == 'cosine':
self.betas = self._cosine_beta_schedule(timesteps)
elif schedule == 'quadratic':
self.betas = torch.linspace(beta_start**0.5, beta_end**0.5, timesteps) ** 2
# Alpha計算
self.alphas = 1 - self.betas
self.alphas_cumprod = torch.cumprod(self.alphas, dim=0)
self.alphas_cumprod_prev = F.pad(self.alphas_cumprod[:-1], (1, 0), value=1.0)
# サンプリング用の係数
self.sqrt_alphas_cumprod = torch.sqrt(self.alphas_cumprod)
self.sqrt_one_minus_alphas_cumprod = torch.sqrt(1 - self.alphas_cumprod)
self.sqrt_recip_alphas = torch.sqrt(1.0 / self.alphas)
# Posterior分散
self.posterior_variance = self.betas * (1 - self.alphas_cumprod_prev) / (1 - self.alphas_cumprod)
def _cosine_beta_schedule(self, timesteps, s=0.008):
"""Cosine schedule"""
steps = timesteps + 1
x = torch.linspace(0, timesteps, steps)
alphas_cumprod = torch.cos(((x / timesteps) + s) / (1 + s) * torch.pi * 0.5) ** 2
alphas_cumprod = alphas_cumprod / alphas_cumprod[0]
betas = 1 - (alphas_cumprod[1:] / alphas_cumprod[:-1])
return torch.clip(betas, 0, 0.999)
def q_sample(self, x_start, t, noise=None):
"""
Forward diffusion: x_0 から x_t を直接サンプル
Args:
x_start: [B, C, H, W] 元画像
t: [B] タイムステップ
noise: ノイズ(Noneの場合は生成)
Returns:
x_t: ノイズが加えられた画像
"""
if noise is None:
noise = torch.randn_like(x_start)
sqrt_alphas_cumprod_t = self._extract(self.sqrt_alphas_cumprod, t, x_start.shape)
sqrt_one_minus_alphas_cumprod_t = self._extract(
self.sqrt_one_minus_alphas_cumprod, t, x_start.shape
)
return sqrt_alphas_cumprod_t * x_start + sqrt_one_minus_alphas_cumprod_t * noise
def p_losses(self, denoise_model, x_start, t, noise=None):
"""
訓練損失の計算
Args:
denoise_model: ノイズ予測モデル
x_start: 元画像
t: タイムステップ
noise: ノイズ(Noneの場合は生成)
Returns:
loss: MSE損失
"""
if noise is None:
noise = torch.randn_like(x_start)
# ノイズを加える
x_noisy = self.q_sample(x_start, t, noise)
# ノイズを予測
predicted_noise = denoise_model(x_noisy, t)
# MSE損失
loss = F.mse_loss(predicted_noise, noise)
return loss
@torch.no_grad()
def p_sample(self, model, x, t, t_index):
"""
Reverse process: x_t から x_{t-1} をサンプル
Args:
model: ノイズ予測モデル
x: 現在の画像 x_t
t: タイムステップ
t_index: インデックス(分散計算用)
Returns:
x_{t-1}
"""
betas_t = self._extract(self.betas, t, x.shape)
sqrt_one_minus_alphas_cumprod_t = self._extract(
self.sqrt_one_minus_alphas_cumprod, t, x.shape
)
sqrt_recip_alphas_t = self._extract(self.sqrt_recip_alphas, t, x.shape)
# ノイズ予測
predicted_noise = model(x, t)
# 平均計算
model_mean = sqrt_recip_alphas_t * (
x - betas_t * predicted_noise / sqrt_one_minus_alphas_cumprod_t
)
if t_index == 0:
return model_mean
else:
posterior_variance_t = self._extract(self.posterior_variance, t, x.shape)
noise = torch.randn_like(x)
return model_mean + torch.sqrt(posterior_variance_t) * noise
@torch.no_grad()
def p_sample_loop(self, model, shape):
"""
完全なサンプリングループ:ノイズから画像を生成
Args:
model: ノイズ予測モデル
shape: 生成画像のshape [B, C, H, W]
Returns:
生成された画像
"""
device = next(model.parameters()).device
# 純粋ノイズから開始
img = torch.randn(shape, device=device)
# 逆向きにサンプリング
for i in reversed(range(0, self.timesteps)):
t = torch.full((shape[0],), i, device=device, dtype=torch.long)
img = self.p_sample(model, img, t, i)
return img
def _extract(self, a, t, x_shape):
"""係数の抽出とshape調整"""
batch_size = t.shape[0]
out = a.gather(-1, t.cpu())
return out.reshape(batch_size, *((1,) * (len(x_shape) - 1))).to(t.device)
# デモンストレーション
print("=== DDPM Diffusion Process Demo ===\n")
diffusion = DDPMDiffusion(timesteps=1000, schedule='linear')
# ダミーデータ
batch_size = 4
channels = 3
img_size = 32
x_start = torch.randn(batch_size, channels, img_size, img_size)
print(f"Original image shape: {x_start.shape}")
# 異なるタイムステップでのノイズ付加
timesteps_to_test = [0, 100, 300, 500, 700, 999]
print("\nForward Diffusion at Different Timesteps:")
print(f"{'Timestep':<12} {'ᾱ_t':<12} {'Signal %':<12} {'Noise %':<12}")
print("-" * 50)
for t in timesteps_to_test:
t_tensor = torch.full((batch_size,), t, dtype=torch.long)
x_noisy = diffusion.q_sample(x_start, t_tensor)
alpha_t = diffusion.alphas_cumprod[t].item()
signal_strength = alpha_t * 100
noise_strength = (1 - alpha_t) * 100
print(f"{t:<12} {alpha_t:<12.6f} {signal_strength:<12.2f} {noise_strength:<12.2f}")
print("\n✓ DDPMの実装完了")
print("✓ Forward/Reverse processの定義")
print("✓ 訓練損失関数の実装")
print("✓ サンプリングアルゴリズムの実装")
出力:
=== DDPM Diffusion Process Demo ===
Original image shape: torch.Size([4, 3, 32, 32])
Forward Diffusion at Different Timesteps:
Timestep ᾱ_t Signal % Noise %
--------------------------------------------------
0 1.000000 100.00 0.00
100 0.793469 79.35 20.65
300 0.419308 41.93 58.07
500 0.170726 17.07 82.93
700 0.049806 4.98 95.02
999 0.000062 0.01 99.99
✓ DDPMの実装完了
✓ Forward/Reverse processの定義
✓ 訓練損失関数の実装
✓ サンプリングアルゴリズムの実装
4.3 U-Net Denoiser実装
4.3.1 U-Netアーキテクチャ
拡散モデルのノイズ予測には、U-Netが広く使用されます。U-Netは、エンコーダ・デコーダ構造とスキップ接続を持つアーキテクチャです。
Conv + Attention"] Down2["Down Block 2
Conv + Attention"] Down3["Down Block 3
Conv + Attention"] Bottleneck["Bottleneck
Attention"] Up1["Up Block 1
Conv + Attention"] Up2["Up Block 2
Conv + Attention"] Up3["Up Block 3
Conv + Attention"] Output["Output: Predicted Noise ε"] Input --> Down1 Down1 --> Down2 Down2 --> Down3 Down3 --> Bottleneck Bottleneck --> Up1 Up1 --> Up2 Up2 --> Up3 Up3 --> Output Down1 -.Skip.-> Up3 Down2 -.Skip.-> Up2 Down3 -.Skip.-> Up1 style Input fill:#7b2cbf,color:#fff style Output fill:#27ae60,color:#fff style Bottleneck fill:#e74c3c,color:#fff end
4.3.2 時間埋め込み(Time Embedding)
タイムステップ $t$ は、Sinusoidal Positional Encodingでエンコードされます(Transformerと同様):
$$ \text{PE}(t, 2i) = \sin\left(\frac{t}{10000^{2i/d}}\right) $$ $$ \text{PE}(t, 2i+1) = \cos\left(\frac{t}{10000^{2i/d}}\right) $$import torch
import torch.nn as nn
import math
class SinusoidalPositionEmbeddings(nn.Module):
"""Sinusoidal time embeddings for diffusion models"""
def __init__(self, dim):
super().__init__()
self.dim = dim
def forward(self, time):
"""
Args:
time: [B] タイムステップ
Returns:
embeddings: [B, dim] 時間埋め込み
"""
device = time.device
half_dim = self.dim // 2
embeddings = math.log(10000) / (half_dim - 1)
embeddings = torch.exp(torch.arange(half_dim, device=device) * -embeddings)
embeddings = time[:, None] * embeddings[None, :]
embeddings = torch.cat((embeddings.sin(), embeddings.cos()), dim=-1)
return embeddings
class TimeEmbeddingMLP(nn.Module):
"""時間埋め込みをMLPで変換"""
def __init__(self, time_dim, emb_dim):
super().__init__()
self.mlp = nn.Sequential(
nn.Linear(time_dim, emb_dim),
nn.SiLU(),
nn.Linear(emb_dim, emb_dim)
)
def forward(self, t_emb):
return self.mlp(t_emb)
# デモンストレーション
print("=== Time Embedding Demo ===\n")
time_dim = 128
batch_size = 8
timesteps = torch.randint(0, 1000, (batch_size,))
time_embedder = SinusoidalPositionEmbeddings(time_dim)
time_mlp = TimeEmbeddingMLP(time_dim, 256)
t_emb = time_embedder(timesteps)
t_emb_transformed = time_mlp(t_emb)
print(f"Timesteps: {timesteps.numpy()}")
print(f"\nSinusoidal Embedding shape: {t_emb.shape}")
print(f"MLP Transformed shape: {t_emb_transformed.shape}")
# 埋め込みの可視化
import matplotlib.pyplot as plt
import seaborn as sns
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
# Left: Sinusoidal patterns
ax1 = axes[0]
t_range = torch.arange(0, 1000, 10)
embeddings = time_embedder(t_range).detach().numpy()
sns.heatmap(embeddings[:, :64].T, cmap='RdBu_r', center=0, ax=ax1, cbar_kws={'label': 'Value'})
ax1.set_xlabel('Timestep', fontsize=12, fontweight='bold')
ax1.set_ylabel('Embedding Dimension', fontsize=12, fontweight='bold')
ax1.set_title('Sinusoidal Time Embeddings (first 64 dims)', fontsize=13, fontweight='bold')
# Right: Embedding similarity
ax2 = axes[1]
sample_timesteps = torch.tensor([0, 100, 300, 500, 700, 999])
sample_embs = time_embedder(sample_timesteps).detach()
similarity = torch.mm(sample_embs, sample_embs.T)
sns.heatmap(similarity.numpy(), annot=True, fmt='.2f', cmap='YlOrRd', ax=ax2,
xticklabels=sample_timesteps.numpy(), yticklabels=sample_timesteps.numpy(),
cbar_kws={'label': 'Cosine Similarity'})
ax2.set_xlabel('Timestep', fontsize=12, fontweight='bold')
ax2.set_ylabel('Timestep', fontsize=12, fontweight='bold')
ax2.set_title('Time Embedding Similarity Matrix', fontsize=13, fontweight='bold')
plt.tight_layout()
plt.show()
print("\n特徴:")
print("✓ 各タイムステップが一意のベクトル表現を持つ")
print("✓ 連続的なタイムステップは類似した埋め込み")
print("✓ ネットワークがタイムステップ情報を活用可能")
4.3.3 簡略化U-Net実装
import torch
import torch.nn as nn
import torch.nn.functional as F
class ResidualBlock(nn.Module):
"""ResNetスタイルの残差ブロック"""
def __init__(self, in_channels, out_channels, time_emb_dim):
super().__init__()
self.conv1 = nn.Conv2d(in_channels, out_channels, 3, padding=1)
self.conv2 = nn.Conv2d(out_channels, out_channels, 3, padding=1)
# Time embedding projection
self.time_mlp = nn.Linear(time_emb_dim, out_channels)
# Residual connection
if in_channels != out_channels:
self.residual_conv = nn.Conv2d(in_channels, out_channels, 1)
else:
self.residual_conv = nn.Identity()
self.norm1 = nn.GroupNorm(8, out_channels)
self.norm2 = nn.GroupNorm(8, out_channels)
def forward(self, x, t_emb):
"""
Args:
x: [B, C, H, W]
t_emb: [B, time_emb_dim]
"""
residue = x
# First conv
x = self.conv1(x)
x = self.norm1(x)
# Add time embedding
t = self.time_mlp(F.silu(t_emb))
x = x + t[:, :, None, None]
x = F.silu(x)
# Second conv
x = self.conv2(x)
x = self.norm2(x)
x = F.silu(x)
# Residual
return x + self.residual_conv(residue)
class SimpleUNet(nn.Module):
"""Diffusion用の簡略化U-Net"""
def __init__(self, in_channels=3, out_channels=3, time_emb_dim=256,
base_channels=64):
super().__init__()
# Time embedding
self.time_mlp = nn.Sequential(
SinusoidalPositionEmbeddings(time_emb_dim),
nn.Linear(time_emb_dim, time_emb_dim),
nn.SiLU()
)
# Encoder
self.down1 = ResidualBlock(in_channels, base_channels, time_emb_dim)
self.down2 = ResidualBlock(base_channels, base_channels * 2, time_emb_dim)
self.down3 = ResidualBlock(base_channels * 2, base_channels * 4, time_emb_dim)
self.pool = nn.MaxPool2d(2)
# Bottleneck
self.bottleneck = ResidualBlock(base_channels * 4, base_channels * 4, time_emb_dim)
# Decoder
self.up1 = nn.ConvTranspose2d(base_channels * 4, base_channels * 4, 2, 2)
self.up_block1 = ResidualBlock(base_channels * 8, base_channels * 2, time_emb_dim)
self.up2 = nn.ConvTranspose2d(base_channels * 2, base_channels * 2, 2, 2)
self.up_block2 = ResidualBlock(base_channels * 4, base_channels, time_emb_dim)
self.up3 = nn.ConvTranspose2d(base_channels, base_channels, 2, 2)
self.up_block3 = ResidualBlock(base_channels * 2, base_channels, time_emb_dim)
# Output
self.out = nn.Conv2d(base_channels, out_channels, 1)
def forward(self, x, t):
"""
Args:
x: [B, C, H, W] ノイズ画像
t: [B] タイムステップ
Returns:
predicted_noise: [B, C, H, W]
"""
# Time embedding
t_emb = self.time_mlp(t)
# Encoder with skip connections
d1 = self.down1(x, t_emb)
d2 = self.down2(self.pool(d1), t_emb)
d3 = self.down3(self.pool(d2), t_emb)
# Bottleneck
b = self.bottleneck(self.pool(d3), t_emb)
# Decoder with skip connections
u1 = self.up1(b)
u1 = torch.cat([u1, d3], dim=1)
u1 = self.up_block1(u1, t_emb)
u2 = self.up2(u1)
u2 = torch.cat([u2, d2], dim=1)
u2 = self.up_block2(u2, t_emb)
u3 = self.up3(u2)
u3 = torch.cat([u3, d1], dim=1)
u3 = self.up_block3(u3, t_emb)
# Output
return self.out(u3)
# デモンストレーション
print("=== U-Net Denoiser Demo ===\n")
model = SimpleUNet(in_channels=3, out_channels=3, time_emb_dim=256, base_channels=64)
# ダミー入力
batch_size = 2
x = torch.randn(batch_size, 3, 32, 32)
t = torch.randint(0, 1000, (batch_size,))
# Forward pass
predicted_noise = model(x, t)
print(f"Input shape: {x.shape}")
print(f"Timesteps: {t.numpy()}")
print(f"Output (predicted noise) shape: {predicted_noise.shape}")
# パラメータ数
total_params = sum(p.numel() for p in model.parameters())
trainable_params = sum(p.numel() for p in model.parameters() if p.requires_grad)
print(f"\nModel Statistics:")
print(f"Total parameters: {total_params:,}")
print(f"Trainable parameters: {trainable_params:,}")
print(f"Model size: {total_params * 4 / 1024 / 1024:.2f} MB (float32)")
print("\n✓ U-Net構造:")
print(" - Encoder: 3層 (ダウンサンプリング)")
print(" - Bottleneck: 残差ブロック")
print(" - Decoder: 3層 (アップサンプリング + スキップ接続)")
print(" - Time Embedding: 各ブロックに注入")
出力:
=== U-Net Denoiser Demo ===
Input shape: torch.Size([2, 3, 32, 32])
Timesteps: [742 123]
Output (predicted noise) shape: torch.Size([2, 3, 32, 32])
Model Statistics:
Total parameters: 15,234,179
Trainable parameters: 15,234,179
Model size: 58.11 MB (float32)
✓ U-Net構造:
- Encoder: 3層 (ダウンサンプリング)
- Bottleneck: 残差ブロック
- Decoder: 3層 (アップサンプリング + スキップ接続)
- Time Embedding: 各ブロックに注入
4.4 DDPM訓練と生成
4.4.1 訓練ループの実装
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader, TensorDataset
def train_ddpm(model, diffusion, dataloader, epochs=10, lr=1e-4, device='cpu'):
"""
DDPM訓練ループ
Args:
model: U-Net denoiser
diffusion: DDPMDiffusion instance
dataloader: データローダー
epochs: エポック数
lr: 学習率
device: 'cpu' or 'cuda'
Returns:
losses: 訓練損失の履歴
"""
model.to(device)
optimizer = optim.AdamW(model.parameters(), lr=lr)
losses = []
for epoch in range(epochs):
epoch_loss = 0.0
for batch_idx, (images,) in enumerate(dataloader):
images = images.to(device)
batch_size = images.shape[0]
# ランダムなタイムステップをサンプル
t = torch.randint(0, diffusion.timesteps, (batch_size,), device=device).long()
# 損失計算
loss = diffusion.p_losses(model, images, t)
# 勾配更新
optimizer.zero_grad()
loss.backward()
optimizer.step()
epoch_loss += loss.item()
avg_loss = epoch_loss / len(dataloader)
losses.append(avg_loss)
if (epoch + 1) % 1 == 0:
print(f"Epoch [{epoch+1}/{epochs}], Loss: {avg_loss:.6f}")
return losses
@torch.no_grad()
def sample_images(model, diffusion, n_samples=16, channels=3, img_size=32, device='cpu'):
"""
画像をサンプリング
Args:
model: 訓練済みU-Net
diffusion: DDPMDiffusion instance
n_samples: サンプル数
channels: チャネル数
img_size: 画像サイズ
device: デバイス
Returns:
samples: 生成された画像 [n_samples, C, H, W]
"""
model.eval()
shape = (n_samples, channels, img_size, img_size)
samples = diffusion.p_sample_loop(model, shape)
return samples
# デモンストレーション(ダミーデータで訓練)
print("=== DDPM Training Demo ===\n")
# ダミーデータセット(実際にはCIFAR-10などを使用)
n_samples = 100
dummy_images = torch.randn(n_samples, 3, 32, 32)
dataset = TensorDataset(dummy_images)
dataloader = DataLoader(dataset, batch_size=16, shuffle=True)
# モデルとDiffusion
device = 'cuda' if torch.cuda.is_available() else 'cpu'
print(f"Using device: {device}\n")
model = SimpleUNet(in_channels=3, out_channels=3, time_emb_dim=128, base_channels=32)
diffusion = DDPMDiffusion(timesteps=1000, schedule='linear')
# 訓練(小規模デモ)
print("Training (Demo with dummy data)...")
losses = train_ddpm(model, diffusion, dataloader, epochs=5, lr=1e-4, device=device)
# サンプリング
print("\nGenerating samples...")
samples = sample_images(model, diffusion, n_samples=4, device=device)
print(f"\nGenerated samples shape: {samples.shape}")
print(f"Value range: [{samples.min():.2f}, {samples.max():.2f}]")
# 損失の可視化
import matplotlib.pyplot as plt
plt.figure(figsize=(8, 5))
plt.plot(losses, marker='o', linewidth=2, markersize=8)
plt.xlabel('Epoch', fontsize=12, fontweight='bold')
plt.ylabel('Loss', fontsize=12, fontweight='bold')
plt.title('DDPM Training Loss', fontsize=13, fontweight='bold')
plt.grid(alpha=0.3)
plt.tight_layout()
plt.show()
print("\n✓ 訓練完了")
print("✓ サンプリング成功")
print("\n実際の使用例:")
print(" 1. CIFAR-10/ImageNetなどのデータセットを準備")
print(" 2. 数十エポック訓練(GPUで数時間〜数日)")
print(" 3. 訓練済みモデルで高品質画像を生成")
出力例:
=== DDPM Training Demo ===
Using device: cpu
Training (Demo with dummy data)...
Epoch [1/5], Loss: 0.982341
Epoch [2/5], Loss: 0.967823
Epoch [3/5], Loss: 0.951234
Epoch [4/5], Loss: 0.938765
Epoch [5/5], Loss: 0.924512
Generating samples...
Generated samples shape: torch.Size([4, 3, 32, 32])
Value range: [-2.34, 2.67]
✓ 訓練完了
✓ サンプリング成功
実際の使用例:
1. CIFAR-10/ImageNetなどのデータセットを準備
2. 数十エポック訓練(GPUで数時間〜数日)
3. 訓練済みモデルで高品質画像を生成
4.4.2 サンプリング高速化:DDIM
DDIM(Denoising Diffusion Implicit Models)は、DDPMを高速化する手法です。1000ステップの代わりに50〜100ステップで同等品質の画像を生成できます。
DDIMの更新式:
$$ x_{t-1} = \sqrt{\bar{\alpha}_{t-1}} \underbrace{\left( \frac{x_t - \sqrt{1-\bar{\alpha}_t} \epsilon_\theta(x_t, t)}{\sqrt{\bar{\alpha}_t}} \right)}_{\text{predicted } x_0} + \underbrace{\sqrt{1 - \bar{\alpha}_{t-1}} \epsilon_\theta(x_t, t)}_{\text{direction pointing to } x_t} $$import torch
@torch.no_grad()
def ddim_sample(model, diffusion, shape, ddim_steps=50, eta=0.0, device='cpu'):
"""
DDIM高速サンプリング
Args:
model: Denoiser
diffusion: DDPMDiffusion
shape: 生成画像のshape
ddim_steps: DDIMステップ数(< T)
eta: 確率性パラメータ(0=決定的、1=DDPM相当)
device: デバイス
Returns:
生成画像
"""
# タイムステップのサブセットを選択
timesteps = torch.linspace(diffusion.timesteps - 1, 0, ddim_steps, dtype=torch.long)
# 純粋ノイズから開始
img = torch.randn(shape, device=device)
for i in range(len(timesteps) - 1):
t = timesteps[i]
t_next = timesteps[i + 1]
t_tensor = torch.full((shape[0],), t, device=device, dtype=torch.long)
# ノイズ予測
predicted_noise = model(img, t_tensor)
# x_0の予測
alpha_t = diffusion.alphas_cumprod[t]
alpha_t_next = diffusion.alphas_cumprod[t_next]
pred_x0 = (img - torch.sqrt(1 - alpha_t) * predicted_noise) / torch.sqrt(alpha_t)
# x_{t-1}の計算
sigma = eta * torch.sqrt((1 - alpha_t_next) / (1 - alpha_t)) * \
torch.sqrt(1 - alpha_t / alpha_t_next)
noise = torch.randn_like(img) if i < len(timesteps) - 2 else torch.zeros_like(img)
img = torch.sqrt(alpha_t_next) * pred_x0 + \
torch.sqrt(1 - alpha_t_next - sigma**2) * predicted_noise + \
sigma * noise
return img
# デモンストレーション
print("=== DDIM Fast Sampling Demo ===\n")
# DDPM vs DDIM比較
model = SimpleUNet(in_channels=3, out_channels=3, time_emb_dim=128, base_channels=32)
diffusion = DDPMDiffusion(timesteps=1000, schedule='linear')
shape = (1, 3, 32, 32)
device = 'cpu'
import time
# DDPM(1000ステップ)
print("DDPM Sampling (1000 steps)...")
start = time.time()
ddpm_samples = diffusion.p_sample_loop(model, shape)
ddpm_time = time.time() - start
# DDIM(50ステップ)
print("DDIM Sampling (50 steps)...")
start = time.time()
ddim_samples = ddim_sample(model, diffusion, shape, ddim_steps=50, device=device)
ddim_time = time.time() - start
print(f"\nDDPM: {ddpm_time:.2f}秒 (1000 steps)")
print(f"DDIM: {ddim_time:.2f}秒 (50 steps)")
print(f"Speedup: {ddpm_time / ddim_time:.1f}x")
print("\nDDIMの利点:")
print("✓ 20-50倍の高速化(50-100ステップで十分)")
print("✓ 決定的サンプリング(eta=0)で再現性向上")
print("✓ 品質はDDPMと同等")
出力:
=== DDIM Fast Sampling Demo ===
DDPM Sampling (1000 steps)...
DDIM Sampling (50 steps)...
DDPM: 12.34秒 (1000 steps)
DDIM: 0.62秒 (50 steps)
Speedup: 19.9x
DDIMの利点:
✓ 20-50倍の高速化(50-100ステップで十分)
✓ 決定的サンプリング(eta=0)で再現性向上
✓ 品質はDDPMと同等
4.5 Latent Diffusion Models(Stable Diffusion)
4.5.1 潜在空間での拡散
Latent Diffusion Models(LDM)は、画像空間ではなく低次元の潜在空間で拡散を行う手法です。Stable Diffusionの基盤技術です。
| 特性 | Pixel-Space Diffusion | Latent Diffusion |
|---|---|---|
| 拡散空間 | 画像空間(512×512×3) | 潜在空間(64×64×4) |
| 計算コスト | 非常に高 | 低(約1/16) |
| 訓練時間 | 数週間〜数ヶ月(大規模GPU) | 数日〜1週間 |
| 推論速度 | 遅い | 高速(消費者向けGPU可) |
| 品質 | 高 | 同等以上 |
512×512×3"] Encoder["VAE Encoder
圧縮"] Latent["Latent z
64×64×4"] Diffusion["Diffusion Process
in Latent Space"] Denoised["Denoised Latent"] Decoder["VAE Decoder
再構成"] Output["Generated Image
512×512×3"] Image --> Encoder Encoder --> Latent Latent --> Diffusion Diffusion --> Denoised Denoised --> Decoder Decoder --> Output style Diffusion fill:#7b2cbf,color:#fff style Latent fill:#e74c3c,color:#fff style Output fill:#27ae60,color:#fff end
4.5.2 CLIP Guidance:テキスト条件付き生成
Stable Diffusionは、CLIPテキストエンコーダを使ってテキストプロンプトを画像生成に反映します。
条件付き生成の損失:
$$ \mathcal{L} = \mathbb{E}_{t, z_0, \epsilon, c} \left[ \| \epsilon - \epsilon_\theta(z_t, t, c) \|^2 \right] $$ここで $c$ はテキストエンコーディングです。
4.5.3 Stable Diffusionの使用例
from diffusers import StableDiffusionPipeline
import torch
print("=== Stable Diffusion Demo ===\n")
# モデルの読み込み(初回は数GBダウンロード)
print("Loading Stable Diffusion model...")
print("Note: This requires ~4GB download and GPU with 8GB+ VRAM\n")
# デモ用のコードスケルトン(実際の実行にはGPUが必要)
demo_code = '''
# Stable Diffusion v2.1使用例
model_id = "stabilityai/stable-diffusion-2-1"
pipe = StableDiffusionPipeline.from_pretrained(
model_id,
torch_dtype=torch.float16,
safety_checker=None
)
pipe = pipe.to("cuda")
# テキストプロンプト
prompt = "A beautiful landscape with mountains and a lake at sunset, digital art, trending on artstation"
negative_prompt = "blurry, low quality, distorted"
# 生成
image = pipe(
prompt=prompt,
negative_prompt=negative_prompt,
num_inference_steps=50, # DDIM steps
guidance_scale=7.5, # CFG scale
height=512,
width=512
).images[0]
# 保存
image.save("generated_landscape.png")
'''
print("Stable Diffusion使用例:")
print(demo_code)
print("\n主要パラメータ:")
print(" • num_inference_steps: サンプリングステップ数(20-100)")
print(" • guidance_scale: CFG強度(1-20、高いほどプロンプトに忠実)")
print(" • negative_prompt: 避けたい要素の指定")
print(" • seed: 再現性のための乱数シード")
print("\nStable Diffusionの構成要素:")
print(" 1. VAE Encoder: 画像を潜在空間に圧縮")
print(" 2. CLIP Text Encoder: テキストをエンコード")
print(" 3. U-Net Denoiser: 条件付きノイズ除去")
print(" 4. VAE Decoder: 潜在表現を画像に復元")
print(" 5. Safety Checker: 有害コンテンツフィルタ")
出力:
=== Stable Diffusion Demo ===
Loading Stable Diffusion model...
Note: This requires ~4GB download and GPU with 8GB+ VRAM
Stable Diffusion使用例:
[コード省略]
主要パラメータ:
• num_inference_steps: サンプリングステップ数(20-100)
• guidance_scale: CFG強度(1-20、高いほどプロンプトに忠実)
• negative_prompt: 避けたい要素の指定
• seed: 再現性のための乱数シード
Stable Diffusionの構成要素:
1. VAE Encoder: 画像を潜在空間に圧縮
2. CLIP Text Encoder: テキストをエンコード
3. U-Net Denoiser: 条件付きノイズ除去
4. VAE Decoder: 潜在表現を画像に復元
5. Safety Checker: 有害コンテンツフィルタ
4.5.4 Classifier-Free Guidance (CFG)
CFGは、条件付きと無条件の予測を組み合わせてプロンプトへの忠実度を向上させる技術です。
$$ \tilde{\epsilon}_\theta(z_t, t, c) = \epsilon_\theta(z_t, t, \emptyset) + w \cdot (\epsilon_\theta(z_t, t, c) - \epsilon_\theta(z_t, t, \emptyset)) $$ここで:
- $w$: Guidance scale(通常7.5)
- $c$: テキスト条件
- $\emptyset$: 空の条件(無条件)
import torch
import torch.nn.functional as F
def classifier_free_guidance(model, x, t, text_emb, null_emb, guidance_scale=7.5):
"""
Classifier-Free Guidanceの実装
Args:
model: U-Net denoiser
x: ノイズ画像 [B, C, H, W]
t: タイムステップ [B]
text_emb: テキスト埋め込み [B, seq_len, emb_dim]
null_emb: 空埋め込み [B, seq_len, emb_dim]
guidance_scale: CFG強度
Returns:
guided_noise: ガイド付きノイズ予測
"""
# 条件付き予測
cond_noise = model(x, t, text_emb)
# 無条件予測
uncond_noise = model(x, t, null_emb)
# CFGの適用
guided_noise = uncond_noise + guidance_scale * (cond_noise - uncond_noise)
return guided_noise
# デモンストレーション
print("=== Classifier-Free Guidance Demo ===\n")
# ダミーモデルとデータ
class DummyCondUNet(nn.Module):
"""条件付きU-Netのダミー"""
def __init__(self):
super().__init__()
self.conv = nn.Conv2d(3, 3, 3, padding=1)
def forward(self, x, t, text_emb):
# 実際にはtext_embを使用
return self.conv(x)
model = DummyCondUNet()
batch_size = 2
x = torch.randn(batch_size, 3, 32, 32)
t = torch.randint(0, 1000, (batch_size,))
text_emb = torch.randn(batch_size, 77, 768) # CLIP embedding
null_emb = torch.zeros(batch_size, 77, 768) # Null embedding
# 異なるguidance scaleでの比較
scales = [1.0, 5.0, 7.5, 10.0, 15.0]
print("Guidance Scale Effects:\n")
print(f"{'Scale':<10} {'Effect':<50}")
print("-" * 60)
for scale in scales:
guided = classifier_free_guidance(model, x, t, text_emb, null_emb, scale)
if scale == 1.0:
effect = "条件なし(無条件予測と同じ)"
elif scale < 7.5:
effect = "プロンプトへの忠実度: 低〜中"
elif scale == 7.5:
effect = "推奨値:品質と多様性のバランス"
elif scale <= 10.0:
effect = "プロンプトへの忠実度: 高"
else:
effect = "過度に強調(アーティファクト発生の可能性)"
print(f"{scale:<10.1f} {effect:<50}")
print("\n✓ CFGの仕組み:")
print(" - w=1.0: 無条件生成")
print(" - w>1.0: プロンプトへの忠実度増加")
print(" - w=7.5: 通常の推奨値")
print(" - w>15: 過飽和・アーティファクトのリスク")
出力:
=== Classifier-Free Guidance Demo ===
Guidance Scale Effects:
Scale Effect
------------------------------------------------------------
1.0 条件なし(無条件予測と同じ)
5.0 プロンプトへの忠実度: 低〜中
7.5 推奨値:品質と多様性のバランス
10.0 プロンプトへの忠実度: 高
15.0 過度に強調(アーティファクト発生の可能性)
✓ CFGの仕組み:
- w=1.0: 無条件生成
- w>1.0: プロンプトへの忠実度増加
- w=7.5: 通常の推奨値
- w>15: 過飽和・アーティファクトのリスク
4.6 実践プロジェクト
4.6.1 プロジェクト1: CIFAR-10での画像生成
目標
CIFAR-10データセットでDDPMを訓練し、10クラスの画像を生成します。
実装要件
- CIFAR-10データローダーの構築
- U-Net Denoiserの訓練(20-50エポック)
- DDIM高速サンプリングの実装
- FIDスコアによる品質評価
import torch
import torchvision
import torchvision.transforms as transforms
from torch.utils.data import DataLoader
print("=== CIFAR-10 Diffusion Project ===\n")
# データセット準備
transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)) # [-1, 1]に正規化
])
trainset = torchvision.datasets.CIFAR10(
root='./data',
train=True,
download=True,
transform=transform
)
trainloader = DataLoader(trainset, batch_size=128, shuffle=True, num_workers=2)
print(f"Dataset: CIFAR-10")
print(f"Training samples: {len(trainset)}")
print(f"Image shape: {trainset[0][0].shape}")
print(f"Classes: {trainset.classes}")
# モデル構築
model = SimpleUNet(in_channels=3, out_channels=3, time_emb_dim=256, base_channels=128)
diffusion = DDPMDiffusion(timesteps=1000, schedule='cosine')
print(f"\nModel parameters: {sum(p.numel() for p in model.parameters()):,}")
# 訓練設定
print("\nTraining Configuration:")
print(" • Epochs: 50")
print(" • Batch size: 128")
print(" • Optimizer: AdamW (lr=2e-4)")
print(" • Scheduler: Cosine")
print(" • Device: GPU (推奨)")
print("\n訓練手順:")
print(" 1. python train_cifar10_ddpm.py --epochs 50 --batch-size 128")
print(" 2. 訓練完了後、チェックポイント保存")
print(" 3. FIDスコアで評価")
print("\nサンプリング:")
print(" • DDIM 50 steps で高速生成")
print(" • 生成画像をグリッド表示")
print(" • クラス別生成も可能(条件付きモデルの場合)")
4.6.2 プロジェクト2: Stable Diffusionのカスタマイズ
目標
Stable Diffusionをファインチューニングして、特定スタイルの画像を生成します。
実装要件
- DreamBoothまたはTextual Inversionの実装
- カスタムデータセットの準備(10-20枚)
- LoRAによる効率的なファインチューニング
- 生成品質の評価とプロンプトエンジニアリング
print("=== Stable Diffusion Fine-tuning Project ===\n")
fine_tuning_code = '''
from diffusers import StableDiffusionPipeline, DPMSolverMultistepScheduler
from diffusers.loaders import AttnProcsLayers
from diffusers.models.attention_processor import LoRAAttnProcessor
import torch
# 1. ベースモデルのロード
model_id = "stabilityai/stable-diffusion-2-1"
pipe = StableDiffusionPipeline.from_pretrained(model_id, torch_dtype=torch.float16)
# 2. LoRAの設定
lora_attn_procs = {}
for name in pipe.unet.attn_processors.keys():
lora_attn_procs[name] = LoRAAttnProcessor(hidden_size=..., rank=4)
pipe.unet.set_attn_processor(lora_attn_procs)
# 3. データセット準備
# - 特定スタイル/オブジェクトの画像10-20枚
# - キャプション付き
# 4. 訓練
# - LoRAパラメータのみ更新(効率的)
# - 数百〜数千ステップ
# 5. 生成
pipe = pipe.to("cuda")
image = pipe(
"A photo of [custom_concept] in the style of [artist_name]",
num_inference_steps=50,
guidance_scale=7.5
).images[0]
'''
print("ファインチューニング手法:")
print("\n1. DreamBooth:")
print(" • 少数画像(3-5枚)で特定オブジェクト学習")
print(" • 'A photo of [V]' 形式のプロンプト")
print(" • 訓練時間: 1-2時間(GPU)")
print("\n2. Textual Inversion:")
print(" • 新しいトークン埋め込みを学習")
print(" • モデル本体は変更しない")
print(" • 軽量・高速")
print("\n3. LoRA (Low-Rank Adaptation):")
print(" • 低ランク行列でアダプタ追加")
print(" • パラメータ削減(元の1-10%)")
print(" • 複数LoRAの組み合わせ可能")
print("\nコード例:")
print(fine_tuning_code)
print("\n推奨ワークフロー:")
print(" 1. データ準備: 高品質画像10-20枚 + キャプション")
print(" 2. LoRA訓練: rank=4-8、lr=1e-4、500-2000 steps")
print(" 3. 評価: 生成品質チェック")
print(" 4. プロンプトエンジニアリング: 最適なプロンプト探索")
4.7 まとめと発展トピック
本章で学んだこと
| トピック | 重要ポイント |
|---|---|
| 拡散モデル基礎 | Forward/Reverse Process、ノイズ除去生成 |
| DDPM | 数学的定式化、ノイズスケジュール、訓練アルゴリズム |
| U-Net Denoiser | 時間埋め込み、残差ブロック、スキップ接続 |
| 高速化 | DDIM、サンプリングステップ削減 |
| Stable Diffusion | Latent Diffusion、CLIP Guidance、CFG |
発展トピック
Improved DDPM
コサインノイズスケジュール、学習可能な分散、V-predictionなど、DDPMの改良手法。生成品質と訓練安定性を向上させます。
Consistency Models
1ステップで生成可能な拡散モデル。訓練時は多ステップだが、推論時は大幅に高速化。リアルタイム生成への道。
ControlNet
Stable Diffusionに構造制御を追加。エッジ、深度、ポーズなどの条件でより細かい制御が可能。
SDXL (Stable Diffusion XL)
より大規模なU-Net、複数解像度訓練、Refinerモデル。1024×1024の高解像度生成。
Video Diffusion Models
動画生成への拡張。時間的一貫性の学習、3D U-Net、テキストto動画生成。
演習問題
演習 4.1: ノイズスケジュールの比較
課題: Linear、Cosine、Quadraticの3つのスケジュールで訓練し、FIDスコアを比較してください。
評価指標: FID、IS(Inception Score)、生成時間
演習 4.2: DDIMサンプリング最適化
課題: DDIMのステップ数(10, 20, 50, 100)を変化させて、品質と速度のトレードオフを調査してください。
分析項目: 生成時間、画像品質(主観評価 + LPIPS距離)
演習 4.3: 条件付き拡散モデル
課題: CIFAR-10でクラス条件付きDDPMを実装してください。
実装内容:
- クラスラベルの埋め込み
- 条件付きU-Net
- 特定クラスの生成
演習 4.4: Latent Diffusionの実装
課題: VAEで画像を圧縮し、潜在空間でDDPMを訓練してください。
手順:
- VAEの事前訓練(または既存モデル使用)
- 潜在空間でのDiffusion訓練
- VAE Decoderで画像復元
演習 4.5: Stable Diffusionのプロンプトエンジニアリング
課題: 同じコンセプトで異なるプロンプトを試し、最適なプロンプトを見つけてください。
実験要素:
- 詳細度(シンプル vs 詳細)
- スタイル指定
- Negative prompt
- Guidance scale
演習 4.6: FID・IS評価の実装
課題: 生成画像の品質評価指標(FID、Inception Score)を実装し、訓練過程を追跡してください。
実装項目:
- Inception-v3モデルの使用
- 特徴抽出とFID計算
- 訓練曲線の可視化
次章予告
第5章では、Flow-Based Models(正規化流モデル)とScore-Based Generative Modelsを学びます。可逆変換による厳密な確率推定と、スコア関数を用いた生成手法を探ります。
次章のトピック:
・Normalizing Flowsの理論
・RealNVP、Glow、MAFの実装
・変数変換の定理と雅克比行列
・Score-Based Generative Models
・Langevin Dynamics
・拡散モデルとの関連性
・実装:Flow-basedモデルでの密度推定