تولید تصویر مصنوعی یکی از جذابترین حوزههای یادگیری عمیق است؛ اما آموزش شبکههای زایشی رقابتی (Generative Adversarial Networks یا GAN) معمولاً با چالشهای بزرگی مثل ناپایداری گرادیانها یا فروپاشی مُد (Mode Collapse) همراه میشود. بدون شناخت دقیق قواعد معماری و تنظیمات ریاضیِ آموزش، مدل بهجای تولید تصاویر واقعگرایانه، دچار نوسان دائم یا تولید نمونههای تکراری میشود.
در این درس یاد میگیرید که چطور یک معماری استاندارد DCGAN را در کمتر از 60 خط کد در پایتورچ پیادهسازی کنید و با بهرهگیری از تکنیکهای اثباتشدهای مانند تابع زیان غیراشباعکننده، نرمالسازی طیفی (Spectral Normalization) و قاعده TTUR، پایداری فرایند آموزش را به شکل چشمگیری افزایش دهید.
کدها و تمرینهای این درس بهگونهای طراحی شدهاند که بدون نیاز به GPUهای ابری گرانقیمت یا پرداختهای ارزی غیرقابل دسترس، بهراحتی روی سختافزار شخصی، پردازندههای معمولی (CPU) یا تراشههای Apple Silicon قابل اجرا و آزمایش هستند.
یک GAN (شبکه زایشی رقابتی یا Generative Adversarial Network) از دو شبکه عصبی در یک بازی مشخص تشکیل شده است: یکی نقاشی میکشد و دیگری نقد میکند. آنها پابهپای هم پیشرفت میکنند تا جایی که نقاشیها منتقد را فریب دهند.
اهداف یادگیری
- بازی مینیماکس (minimax) بین مولد و تفکیککننده و دلیل تطابق نقطه تعادل آن با را توضیح دهید.
- یک DCGAN را در PyTorch پیادهسازی کنید و با کمتر از 60 خط کد، تصاویر ساختگیِ 32x32 منسجم بسازید.
- آموزش GAN را با سه ترفند استاندارد پایدار کنید: تابع زیان غیراشباعکننده (non-saturating loss)، نرم طیفی (spectral norm) و قانون بهروزرسانی در دو مقیاس زمانی (TTUR یا two-timescale update rule).
- نمودارهای آموزش را طوری تحلیل کنید که همگرایی سالم از فروپاشی مد (mode collapse)، نوسان شدید (oscillation) و برد کامل تفکیککننده متمایز شود.
صورت مسئله
در دستهبندی، به شبکه یاد میدهیم که تصاویر را به برچسبها نگاشت کند. در تولید داده مسئله برعکس است: نمونهبرداری از تصاویر جدیدی که انگار از همان توزیع دادههای واقعی آمدهاند. در اینجا هیچ خروجیِ «درستِ» مشخصی وجود ندارد که بتوانید با آن تفاضل بگیرید؛ فقط یک توزیع آماری در دست دارید که میخواهید شبیهسازیاش کنید.
توابع زیان استاندارد (مانند MSE و آنتروپی متقاطع) نمیتوانند بسنجند که «آیا این نمونه واقعاً از دل توزیع واقعی آمده یا نه». کمینهسازی خطای پیکسلی فقط میانگینهای مات و تار تحویل میدهد، نه نمونههای واقعگرایانه. نقطه عطف ماجرا یادگیریِ خودِ تابع زیان (loss) بود: آموزش یک شبکه دوم که کارش تشخیص واقعیت از جعل باشد، و استفاده از قضاوت آن برای هدایت مولد.
شبکههای GAN (معرفیشده توسط Goodfellow و همکاران، 2014) این چارچوب را بنا نهادند. تا سال 2018، مدل StyleGAN چهرههایی با رزولوشن 1024x1024 تولید میکرد که تشخیص آنها از عکس واقعی غیرممکن بود. هرچند امروزه مدلهای انتشار (diffusion models) از نظر کیفیت و کنترلپذیری جایگاه اول را گرفتهاند، اما تمام ترفندهایی که استفاده از مدلهای انتشار را عملی کردند — مثل انتخابهای نرمالسازی، فضاهای نهفته (latent spaces) و توابع زیانِ ویژگیمحور — ابتدا روی GANها درک و تثبیت شدند.
مفاهیم پایه
دو شبکه
مولد (Generator یا G) یک بردار نویز z را میگیرد و یک تصویر تحویل میدهد. تفکیککننده (Discriminator یا D) یک تصویر میگیرد و یک اسکالر خروجی میدهد: احتمال اینکه تصویر واقعی باشد.
بازی
شبکه G میخواهد D اشتباه کند؛ شبکه D میخواهد درست تشخیص دهد. به بیان ریاضی:
از راست به چپ بخوانید: D در حال بیشینهسازی دقت روی تصاویر واقعی (log D(real)) و جعلی (log (1 - D(fake))) است. در مقابل، G دقت D را روی تصاویر جعلی کمینه میکند — یعنی میخواهد مقدار D(G(z)) بالا باشد.
گودفلو اثبات کرد که این بازی مینیماکس یک تعادل سراسری دارد که در آن p_G = p_data، خروجی D در تمام نقاط برابر 0.5 است و واگرایی جنسن-شنون بین توزیع دادههای تولیدی و واقعی به صفر میرسد. بخش سخت ماجرا، رساندن آموزش به این نقطه است.
تابع زیان غیراشباعکننده
فرمول بالا از نظر عددی ناپایدار است. در اوایل آموزش، مقدار D(G(z)) برای همه تصاویر جعلی نزدیک صفر است؛ در نتیجه گرادیان log(1 - D(G(z))) نسبت به G محو میشود (vanishing gradients). راهحل: معکوس کردن تابع زیان G.
حالا وقتی D(G(z)) نزدیک صفر باشد، تابع زیان G بزرگ است و گرادیان آن اطلاعات مفیدی برای یادگیری دارد. تمام مدلهای مدرن GAN با همین شیوه آموزش میبینند.
قواعد معماری DCGAN
ردفورد، متز و چینتالا (2015) سالها آزمونوخطای ناموفق را در قالب پنج قاعده خلاصه کردند که آموزش GAN را پایدار میکند:
- لایههای ادغام (pooling) را با کانولوشنهای گامدار (strided convolutions) در هر دو شبکه جایگزین کنید.
- از نرمالسازی دستهای (batch norm) در هر دو شبکه استفاده کنید، بهجز خروجی G و ورودی D.
- لایههای تماممتصل (fully connected) را در معماریهای عمیقتر حذف کنید.
- در G برای تمام لایهها از ReLU استفاده کنید، بهجز خروجی که برای قرار گرفتن در بازه [-1, 1] از tanh بهره میبرد.
- در D برای تمام لایهها از LeakyReLU (با negative_slope=0.2) استفاده کنید.
تمام مدلهای مدرن GAN مبتنی بر کانولوشن (StyleGAN، BigGAN و GigaGAN) کار را با همین قواعد شروع میکنند و سپس بخشها را یکییکی ارتقا میدهند.
حالتهای شکست و نشانههای آنها
- فروپاشی مد (Mode collapse): شبکه G فقط یک تصویر پیدا میکند که D را فریب میدهد و مدام همان را تولید میکند. راهحل: افزودن تفکیک مینیدسته (minibatch discrimination)، نرم طیفی یا شرطیسازی با برچسب.
- برد تفکیککننده (Discriminator wins): شبکه D خیلی سریع قوی میشود و گرادیانهای G محو میشوند. راهحل: کوچکتر کردن D، کاهش نرخ یادگیری D، یا اعمال هموارسازی برچسبها (label smoothing) روی برچسبهای واقعی.
- نوسان شدید (Oscillation): دو شبکه مدام از یکدیگر سبقت میگیرند بدون اینکه به تعادل نزدیک شوند. راهحل: استفاده از TTUR (آموزش D با سرعتی 2 تا 4 برابر G)، یا تغییر تابع زیان به واسرشتاین (Wasserstein loss).
ارزیابی
در GANها داده مبنای قطعی (ground truth) وجود ندارد؛ پس چطور متوجه عملکرد درست آنها شویم؟
- بررسی چشمی نمونهها — در انتهای هر دور آموزش (epoch)، 64 نمونه را بررسی کنید. این کار بدون چونوچرا ضروری است.
- معیار FID (فاصله فرشه اینسپشن یا Fréchet Inception Distance) — فاصله بین توزیع ویژگیهای Inception-v3 در مجموعهداده واقعی و تولیدی. هرچه کمتر باشد بهتر است و استاندارد فعلی جامعه پژوهشی به شمار میرود.
- امتیاز اینسپشن (Inception Score) — معیاری قدیمیتر و شکنندهتر؛ ترجیحاً از FID استفاده کنید.
- دقت و بازخوانی (Precision/Recall) برای مدلهای زایشی — کیفیت (دقت) و پوشش تنوع (بازخوانی) را جداگانه میسنجد و اطلاعات بیشتری نسبت به FIDِ تنها ارائه میدهد.
برای یک اجرای آزمایشی روی دادههای ساختگی کوچک، همان بررسی چشمی نمونهها کفایت میکند.
پیادهسازی از صفر
مرحله 1: مولد
یک مولد کوچک DCGAN که نویز 64-بُعدی را دریافت کرده و تصویری با ابعاد 32x32 تولید میکند.
import torch
import torch.nn as nn
class Generator(nn.Module):
def __init__(self, z_dim=64, img_channels=3, feat=64):
super().__init__()
self.net = nn.Sequential(
nn.ConvTranspose2d(z_dim, feat * 4, kernel_size=4, stride=1, padding=0, bias=False),
nn.BatchNorm2d(feat * 4),
nn.ReLU(inplace=True),
nn.ConvTranspose2d(feat * 4, feat * 2, kernel_size=4, stride=2, padding=1, bias=False),
nn.BatchNorm2d(feat * 2),
nn.ReLU(inplace=True),
nn.ConvTranspose2d(feat * 2, feat, kernel_size=4, stride=2, padding=1, bias=False),
nn.BatchNorm2d(feat),
nn.ReLU(inplace=True),
nn.ConvTranspose2d(feat, img_channels, kernel_size=4, stride=2, padding=1, bias=False),
nn.Tanh(),
)
def forward(self, z):
return self.net(z.view(z.size(0), -1, 1, 1))
چهار لایه کانولوشن ترانهاده (transposed conv) که هرکدام با kernel_size=4, stride=2, padding=1 ابعاد فضایی را دقیقاً دو برابر میکنند. خروجی با تابع tanh در بازه [-1, 1] مقیاسبندی میشود.
مرحله 2: تفکیککننده
قرینه مولد: با لایههای LeakyReLU، کانولوشنهای گامدار، و یک لاجیت اسکالر در انتها.
class Discriminator(nn.Module):
def __init__(self, img_channels=3, feat=64):
super().__init__()
self.net = nn.Sequential(
nn.Conv2d(img_channels, feat, kernel_size=4, stride=2, padding=1),
nn.LeakyReLU(0.2, inplace=True),
nn.Conv2d(feat, feat * 2, kernel_size=4, stride=2, padding=1, bias=False),
nn.BatchNorm2d(feat * 2),
nn.LeakyReLU(0.2, inplace=True),
nn.Conv2d(feat * 2, feat * 4, kernel_size=4, stride=2, padding=1, bias=False),
nn.BatchNorm2d(feat * 4),
nn.LeakyReLU(0.2, inplace=True),
nn.Conv2d(feat * 4, 1, kernel_size=4, stride=1, padding=0),
)
def forward(self, x):
return self.net(x).view(-1)
آخرین کانولوشن، نقشه ویژگی 4x4 را به 1x1 فشرده میکند. خروجی به ازای هر تصویر یک اسکالر است؛ تابع سیگموئید را فقط هنگام محاسبه تابع زیان اعمال کنید.
مرحله 3: گام آموزش
بهصورت متناوب: در هر بچ (batch)، ابتدا یک بار D و سپس یک بار G را بهروزرسانی کنید.
import torch.nn.functional as F
def train_step(G, D, real, z, opt_g, opt_d, device):
real = real.to(device)
bs = real.size(0)
# D step
opt_d.zero_grad()
d_real = D(real)
d_fake = D(G(z).detach())
loss_d = (F.binary_cross_entropy_with_logits(d_real, torch.ones_like(d_real))
+ F.binary_cross_entropy_with_logits(d_fake, torch.zeros_like(d_fake)))
loss_d.backward()
opt_d.step()
# G step
opt_g.zero_grad()
d_fake = D(G(z))
loss_g = F.binary_cross_entropy_with_logits(d_fake, torch.ones_like(d_fake))
loss_g.backward()
opt_g.step()
return loss_d.item(), loss_g.item()
استفاده از G(z).detach() در مرحله تفکیککننده حیاتی است: نمیخواهیم گرادیانها در طول این بهروزرسانی وارد G شوند. فراموش کردن این نکته باگ کلاسیک تازهکارهاست.
مرحله 4: حلقه کامل آموزش روی اشکال هندسی ساختگی
from torch.utils.data import DataLoader, TensorDataset
import numpy as np
def synthetic_images(num=2000, size=32, seed=0):
rng = np.random.default_rng(seed)
imgs = np.zeros((num, 3, size, size), dtype=np.float32) - 1.0
for i in range(num):
r = rng.uniform(6, 12)
cx, cy = rng.uniform(r, size - r, size=2)
yy, xx = np.meshgrid(np.arange(size), np.arange(size), indexing="ij")
mask = (xx - cx) ** 2 + (yy - cy) ** 2 < r ** 2
color = rng.uniform(-0.5, 1.0, size=3)
for c in range(3):
imgs[i, c][mask] = color[c]
return torch.from_numpy(imgs)
device = "cuda" if torch.cuda.is_available() else "cpu"
data = synthetic_images()
loader = DataLoader(TensorDataset(data), batch_size=64, shuffle=True)
G = Generator(z_dim=64, img_channels=3, feat=32).to(device)
D = Discriminator(img_channels=3, feat=32).to(device)
opt_g = torch.optim.Adam(G.parameters(), lr=2e-4, betas=(0.5, 0.999))
opt_d = torch.optim.Adam(D.parameters(), lr=2e-4, betas=(0.5, 0.999))
for epoch in range(10):
for (batch,) in loader:
z = torch.randn(batch.size(0), 64, device=device)
ld, lg = train_step(G, D, batch, z, opt_g, opt_d, device)
print(f"epoch {epoch} D {ld:.3f} G {lg:.3f}")
تنظیمات Adam(lr=2e-4, betas=(0.5, 0.999)) پیشفرض کلاسیک DCGAN است؛ پایین بودن پارامتر beta1 جلوی تثبیت بیش از حد تکانه (momentum) را در این بازی رقابتی میگیرد.
مرحله 5: نمونهبرداری
@torch.no_grad()
def sample(G, n=16, z_dim=64, device="cpu"):
G.eval()
z = torch.randn(n, z_dim, device=device)
imgs = G(z)
imgs = (imgs + 1) / 2
return imgs.clamp(0, 1)
همیشه قبل از نمونهبرداری مدل را به حالت eval ببرید. این کار در DCGAN اهمیت زیادی دارد؛ چون آمار تجمعی نرمالسازی دستهای را جایگزین آمارهای بچ جاری میکند.
مرحله 6: نرمالسازی طیفی
جایگزینی مستقیم برای نرمالسازی دستهای در تفکیککننده که تضمین میکند شبکه 1-لیپشیتز است. این کار بیشتر موارد شکست ناشی از «برد کامل تفکیککننده» را درمان میکند.
from torch.nn.utils import spectral_norm
def build_sn_discriminator(img_channels=3, feat=64):
return nn.Sequential(
spectral_norm(nn.Conv2d(img_channels, feat, 4, 2, 1)),
nn.LeakyReLU(0.2, inplace=True),
spectral_norm(nn.Conv2d(feat, feat * 2, 4, 2, 1)),
nn.LeakyReLU(0.2, inplace=True),
spectral_norm(nn.Conv2d(feat * 2, feat * 4, 4, 2, 1)),
nn.LeakyReLU(0.2, inplace=True),
spectral_norm(nn.Conv2d(feat * 4, 1, 4, 1, 0)),
)
کافی است Discriminator را با build_sn_discriminator() تعویض کنید؛ در بیشتر مواقع دیگر نیازی به ترفند TTUR هم نخواهید داشت. نرم طیفی سادهترین و موثرترین ارتقای پایداری است که میتوانید اعمال کنید.
استفاده از ابزارهای واقعی
برای پروژههای جدیِ تولید تصویر، از وزنهای پیشآموزشدیده استفاده کنید یا سراغ مدلهای انتشار بروید. دو کتابخانه استاندارد در این زمینه:
- ابزار
torch_fidelityکه مقادیر FID و IS را بدون نیاز به نوشتن کد ارزیابی سفارشی برای مولد شما محاسبه میکند. - مخزن
pytorch-gan-zoo(قدیمی) وStudioGANکه پیادهسازیهای آزمودهشدهای از DCGAN، WGAN-GP، SN-GAN، StyleGAN و BigGAN دارند.
در سال 2026، GANها هنوز بهترین گزینه برای این کاربردها هستند: تولید تصویر درلحظه (تاخیر کمتر از 10 میلیثانیه)، انتقال سبک (style transfer)، و تبدیل تصویر به تصویر با کنترل دقیق (مانند Pix2Pix و CycleGAN). در مقابل، مدلهای انتشار در زمینه واقعگرایی تصاویر و تنظیم با پرامپت متنی دست بالاتر را دارند.
خروجی این درس
این درس دو خروجی تولید میکند:
- فایل
outputs/prompt-gan-training-triage.md— پرامپتی که توضیحات منحنی آموزش را تحلیل کرده و حالت شکست (فروپاشی مد، غلبه تفکیککننده، نوسان) را همراه با یک راهحل پیشنهادیِ مشخص شناسایی میکند. - فایل
outputs/skill-dcgan-scaffold.md— مهارتی که اسکلت کد یک DCGAN را بر اساس مقادیرz_dim، ابعاد هدفimage_sizeو تعداد کانالهایnum_channels، همراه با حلقه آموزش و ذخیرهساز نمونهها میسازد.
تمرینها
- (ساده) مدل DCGAN بالا را روی مجموعهداده اشکال دایرهای ساختگی آموزش دهید و در پایان هر دور، یک شبکه 16تایی از نمونهها ذخیره کنید. دایرههای تولیدی از کدام دور شکل واضحی به خود میگیرند؟
- (متوسط) نرمالسازی دستهای تفکیککننده را با نرم طیفی جایگزین کنید. هر دو نسخه را موازی آموزش دهید. کدام مدل زودتر همگرا میشود؟ واریانس نتایج کدام مدل در طول سه سید (seed) مختلف کمتر است؟
- (سخت) یک DCGAN شرطی بسازید: برچسب کلاس را به ورودی هر دو شبکه G و D تزریق کنید (در G برچسب one-hot را به نویز بچسبانید، و در D یک کانال امبدینگ کلاس اضافه کنید). مدل را روی مجموعهداده ساختگی «دایرهها در برابر مربعها» از درس 7 آموزش دهید و با نمونهبرداری با برچسبهای مشخص نشان دهید که شرطیسازی کار میکند.
اصطلاحات کلیدی
| اصطلاح | در گفتوگوها | معنای دقیق فنی |
|---|---|---|
| مولد (G) | «شبکه نقاش» | نویز را به تصویر نگاشت میکند؛ آموزش میبیند تا تفکیککننده را فریب دهد |
| تفکیککننده (D) | «منتقد» | یک دستهبند دودویی؛ آموزش میبیند تا تصاویر واقعی را از تصاویر جعلی متمایز کند |
| مینیماکس (Minimax) | «بازی» | کمینهسازی روی G و بیشینهسازی روی D در یک تابع زیان رقابتی؛ نقطه تعادل برابر با است |
| زیان غیراشباعکننده (Non-saturating loss) | «نسخه پایدار عددی» | زیان G برابر با است، نه ؛ این فرمول مانع از بین رفتن گرادیانها در ابتدای آموزش میشود |
| فروپاشی مد (Mode collapse) | «مولد فقط یک چیز میسازد» | شبکه G فقط زیرمجموعه کوچکی از تنوع توزیع داده را تولید میکند؛ راهحل: نرم طیفی، تفکیک مینیدسته، یا بچ بزرگتر |
| قاعده TTUR | «دو نرخ یادگیری متفاوت» | آموزش سریعترِ D نسبت به G (معمولاً 2 تا 4 برابر) که باعث پایداری روند آموزش میشود |
| نرم طیفی (Spectral norm) | «لایه 1-لیپشیتز» | یک تکنیک نرمالسازی وزنها که ثابت لیپشیتز هر لایه را محدود میکند و مانع از شیبهای تند و بیرویه در D میشود |
| معیار FID | «فاصله فرشه اینسپشن» | فاصله بین توزیع ویژگیهای Inception-v3 برای دادههای واقعی و تولیدی؛ معیار استاندارد ارزیابی مدلها |
برای مطالعهی بیشتر
- Generative Adversarial Networks (Goodfellow et al., 2014) — مقالهای که این مسیر را آغاز کرد
- DCGAN (Radford, Metz, Chintala, 2015) — قواعد معماری که آموزش GANها را ممکن و عملی کرد
- Spectral Normalization for GANs (Miyato et al., 2018) — کاربردیترین ترفند منفرد برای پایداری آموزش
- StyleGAN3 (Karras et al., 2021) — پیشرفتهترین GAN تاریخ؛ شبیه مجموعهای منتخب از بهترین ترفندهای یک دهه گذشته
