انتقل إلى المحتوى الرئيسي

الوحدة 4 — GAN: المولّد في مواجهة المميّز

اقترح Ian Goodfellow في 2014 فكرة تبدو غريبة: بدل أن نتعلّم توزيع البيانات صراحةً، ندرّب شبكتين تتعارضان — إحداهما تُنتج مزيّفات، والأخرى تحاول كشفها. هذه اللعبة هي GAN، وقد أنتجت لأوّل مرّة صور وجوه واقعيّة على ImageNet ثمّ على CelebA. لكنّها أدخلت أيضًا نوعًا جديدًا من الأعطال لم نكن نعرفه: تدريب لا يتقارب ومنحنيات خسارة لا تقول شيئًا.

اللعبة: مولّد ضدّ مميّز

الفكرة الأساسية بسيطة. المولّد GG يستقبل ضجيجًا zN(0,I)z \sim \mathcal{N}(0, I) ويُنتج صورة G(z)G(z). المميّز DD يستقبل صورة ويُنتج احتمالًا في [0,1][0, 1] يمثّل: «هل هذه الصورة من مجموعة التدريب الحقيقية؟». الشبكتان تُدرَّبان بأهداف متعاكسة:

  • المميّز يريد أن يُميّز الحقيقيّ (يعطيه احتمالًا قريبًا من 1) والمزيّف (يعطيه احتمالًا قريبًا من 0).
  • المولّد يريد أن يخدع المميّز، فيدفع D(G(z))D(G(z)) نحو 1.

رياضيًّا نكتب لعبة قيمة صفريّة:

minGmaxD  Expdata[logD(x)]+Ezpz[log(1D(G(z)))]\min_{G} \max_{D} \; \mathbb{E}_{x \sim p_{\text{data}}}[\log D(x)] + \mathbb{E}_{z \sim p_{z}}[\log(1 - D(G(z)))]

النقطة الحاسمة الأولى: المولّد لا يرى الصور الحقيقية أبدًا. يتعلّم إنتاج الصور فقط من التدرّجات التي يمرّرها المميّز عبره. هذا يبعده عن المرمّز التلقائي بمسافة كبيرة، ويجعل GAN نموذجًا ضمنيًّا: لا يعرّف كثافة، ولا يعطي أرجحية، ولا يمكن حساب logp(x)\log p(x) منه.

تناوب التدريب في PyTorch

عمليًّا نُدرِّب الشبكتين تناوبًا: خطوة على المميّز، ثمّ خطوة على المولّد، على الدفعة نفسها.

import torch
import torch.nn as nn

optim_d = torch.optim.Adam(mimyz.parameters(), lr=2e-4, betas=(0.5, 0.999))
optim_g = torch.optim.Adam(mulid.parameters(), lr=2e-4, betas=(0.5, 0.999))
critere = nn.BCEWithLogitsLoss()

for x_reel, _ in chargeur:
B = x_reel.size(0)
y_reel = torch.ones(B, 1)
y_faux = torch.zeros(B, 1)

# --- خطوة المميّز ---
z = torch.randn(B, 128)
x_faux = mulid(z).detach() # detach يمنع تدرّج المولّد هنا
perte_d = critere(mimyz(x_reel), y_reel) + critere(mimyz(x_faux), y_faux)
optim_d.zero_grad(); perte_d.backward(); optim_d.step()

# --- خطوة المولّد ---
z = torch.randn(B, 128)
x_faux = mulid(z)
perte_g = critere(mimyz(x_faux), y_reel) # يريد أن يُصنَّف المزيّف حقيقيًّا
optim_g.zero_grad(); perte_g.backward(); optim_g.step()

ملاحظتان تدلّان على فخّين ينبغي إدراكهما. الأولى: detach() عند خطوة المميّز، وإلّا تدرّج المميّز يعبر إلى المولّد فيُفسد التحديث. الثانية: خسارة المولّد ليست log(1D(G(z)))\log(1 - D(G(z))) الأصلية بل logD(G(z))-\log D(G(z)) (وهذا ما يعطيه سطر critere(mimyz(x_faux), y_reel)). التغيير يبدو جماليًّا لكنّه حاسم: الصيغة الأصلية تعطي تدرّجات تكاد تكون معدومة حين يفوز المميّز، فيتوقّف تعلّم المولّد. هذا الخدعة اللوغاريتمية التي اقترحها Goodfellow نفسه، وينبغي دائمًا استخدامها.

DCGAN: أوّل معمارية تعمل

النسخة الأولى من GAN كانت طبقات كثيفة على MNIST. القفزة الحقيقية جاءت في 2015 مع DCGAN (Deep Convolutional GAN) لـRadford وMetz وChintala. أدخلت مجموعة من الوصفات الهندسية التي جعلت التدريب مستقرًّا بما يكفي:

  • المولّد يستعمل الالتفاف المعكوس لصعود التمثيل من متّجه إلى صورة، بلا طبقات كثيفة إلّا في المدخل.
  • المميّز يستعمل الالتفاف مع خطوة 2 للنزول، بلا طبقات تجميع.
  • تطبيع الحزم في كلّ الطبقات ما عدا مخرج المولّد ومدخل المميّز.
  • تنشيط LeakyReLU في المميّز (بمعامل 0.2) لتفادي التدرّجات الميّتة، وReLU في المولّد.
  • Tanh في مخرج المولّد لصور في [1,1][-1, 1]، وطبيعة صور التدريب تُطبَّع على المدى نفسه.

هذه الوصفات تبدو صغيرة لكنّ خرقها يُفشل التدريب في ساعات. جرّب استبدال LeakyReLU بـReLU في المميّز: التدرّجات تختفي بسرعة والمولّد يتوقّف عن التحسّن.

قراءة منحنيات الخسارة: ما لا تدلّ عليه

هنا الفرق الأخطر بين تدريب مصنِّف اعتيادي وتدريب GAN. في المصنِّف، خسارة تنخفض تعني أنّ التعلّم يسير. في GAN، الخسارة لا تدلّ على شيء.

لماذا؟ لأنّ الخسارتين تتحرّكان معًا في توازن ديناميكيّ. حين يتقدّم المولّد، ترتفع خسارة المميّز؛ وحين يتقدّم المميّز، ترتفع خسارة المولّد. التوازن المثاليّ نظريًّا هو حين يُنتج المولّد صورًا مطابقة لتوزيع البيانات، فيصبح المميّز عاجزًا ويعطي 0.5 لكلّ صورة، وتستقرّ الخسارتان على قيم متوسّطة. لكن في الواقع نادرًا ما يصل التدريب إلى ذلك، والقيمتان تتذبذبان بلا نمط واضح.

لا تحكم على GAN بمنحنى الخسارة

هذا الفخّ يقتل المشاريع. مطوّر مصنِّف يُلقي نظرة على TensorBoard كلّ عشر دقائق ويقرّر التوقّف إن كانت الخسارة تنخفض جيّدًا. مطوّر GAN يفعل ذلك ويجد الخسارة تتذبذب فيظنّ التدريب يتحلّل، بينما الصور المولَّدة تتحسّن. أو العكس: يجد الخسارة تنخفض بانتظام فيظنّ التدريب يسير، بينما المولّد قد انهار على صورة واحدة (سنعود إلى ذلك في الوحدة التالية). المقياس الوحيد الجدير بالثقة هو رؤية الصور، ودوريًّا حساب FID الذي سنراه في الوحدة 8. اجعل عيّنة من 64 صورة تُحفَظ في كلّ حقبة، وشاهدها بعينك.

أوّل GAN على MNIST

لنبني DCGAN بسيطًا على MNIST. الفكرة أن نصل إلى صور أرقام مقنعة في 30 حقبة على وحدة معالجة مركزيّة معقولة أو دقائق على بطاقة رسوميّات.

class Mulid(nn.Module):
def __init__(self, dim_z=100):
super().__init__()
self.projection = nn.Linear(dim_z, 128 * 7 * 7)
self.net = nn.Sequential(
nn.BatchNorm2d(128), nn.ReLU(),
nn.ConvTranspose2d(128, 64, 4, 2, 1), # 7 -> 14
nn.BatchNorm2d(64), nn.ReLU(),
nn.ConvTranspose2d(64, 1, 4, 2, 1), # 14 -> 28
nn.Tanh(),
)

def forward(self, z):
h = self.projection(z).view(-1, 128, 7, 7)
return self.net(h)

class Mimyz(nn.Module):
def __init__(self):
super().__init__()
self.net = nn.Sequential(
nn.Conv2d(1, 64, 4, 2, 1), # 28 -> 14
nn.LeakyReLU(0.2),
nn.Conv2d(64, 128, 4, 2, 1), # 14 -> 7
nn.BatchNorm2d(128), nn.LeakyReLU(0.2),
nn.Flatten(),
nn.Linear(128 * 7 * 7, 1),
)

def forward(self, x):
return self.net(x)

GAN مقابل VAE: مفاضلة صريحة

قبل أن نغوص في مشكلات التدريب في الوحدة القادمة، جدير أن نضع الأسرتين وجهًا لوجه على البيانات نفسها.

الجانبVAEGAN
الحدّة البصريّةضبابيّة عمومًاصور حادّة
التنوّعجيّد، يغطّي التوزيعضعيف غالبًا، انهيار محتمل
استقرار التدريبمستقرّهشّ جدًّا
الأرجحيةتقرِّبها بـELBOلا تعرّفها
الفضاء الكامنمُنَظَّم، قابل للتلاعبفوضويّ في الغالب

هذه الصورة أنتجت الحاجة إلى نموذج يجمع حدّة GAN باستقرار VAE. الجواب سيأتي في الوحدتين 6 و7 مع نماذج الانتشار.

في الخلاصة

  • GAN لعبة بين مولّد ومميّز، تتناوب فيها خطوتا التدريب، والمولّد لا يرى صورًا حقيقيّة أبدًا.
  • الخدعة اللوغاريتمية لخسارة المولّد (logD(G(z))-\log D(G(z)) بدل log(1D(G(z)))\log(1 - D(G(z)))) ضرورية لتفادي انعدام التدرّجات.
  • وصفات DCGAN (الالتفاف المعكوس، LeakyReLU، تطبيع الحزم، Tanh) هي الحدّ الأدنى لتدريب مستقرّ، وخرقها يُفشل التدريب دون تفسير.
  • منحنيات الخسارة لا تدلّ على تقدّم التدريب؛ الحكم يكون بالنظر إلى الصور، وبحساب مقاييس موضوعيّة كـFID.

الوحدة التالية تتعمّق في العلّة الأخطر في GAN — انهيار الأنماط — وفي الحلول التي جعلت StyleGAN ممكنًا.