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

الوحدة 3 — المرمّزات التلقائية المتغايرة

انتهت الوحدة السابقة إلى ملاحظة قاسية: المرمّز التلقائي البسيط لا يولّد جيّدًا لأنّ فضاءه الكامن ليس توزيعًا. VAE تُصلح هذا العطب بحلّ ذكيّ: بدل أن نُشفّر كلّ xx إلى نقطة واحدة zz، نُشفّرها إلى توزيع كامل على zz، ونُلزم هذا التوزيع بالبقاء قريبًا من توزيع مرجعيّ نعرف كيف نأخذ منه عيّنات.

من نقطة إلى توزيع

في VAE، مخرج المرمّز ليس zz بل معلمتان: متوسّط μ(x)\mu(x) وانحراف معياريّ لوغاريتميّ logσ(x)\log\sigma(x). من هاتين نبني توزيعًا طبيعيًّا qθ(zx)=N(μ(x),σ2(x))q_{\theta}(z \mid x) = \mathcal{N}(\mu(x), \sigma^2(x))، ثمّ نأخذ منه عيّنة zz، ثمّ نمرّرها إلى فكّ الترميز.

الفكرة الحاسمة الأولى: نُدرّب المرمّز على أن يكون توزيع كلّ xx الكامن قريبًا من N(0,I)\mathcal{N}(0, I)، التوزيع المرجعيّ الذي سنستعمله لأخذ العيّنات وقت التوليد. هكذا لن يكون التوزيع المتعلَّم منطقةً صغيرة مجهولةً من الفضاء الكامن، بل شكلًا معلومًا يشغل حيّزًا واسعًا حوله.

دالّة الخسارة: حدّ الأرجحية الأدنى

الأرجحية القصوى مباشرةً غير قابلة للحساب هنا لأنّها تتضمّن تكاملًا على zz. الحيلة الرياضيّة الأنيقة لـKingma وWelling هي أنّهما بنيا حدًّا أدنى لهذه الأرجحية، يُدعى ELBO أي حدّ الأرجحية الأدنى، ويكون قابلًا للتحسين مباشرةً بالنزول التدرّجي.

logp(x)Eqθ(zx)[logpϕ(xz)]KL ⁣(qθ(zx)N(0,I))\log p(x) \geq \mathbb{E}_{q_{\theta}(z \mid x)}\left[\log p_{\phi}(x \mid z)\right] - \mathrm{KL}\!\left(q_{\theta}(z \mid x) \,\|\, \mathcal{N}(0, I)\right)

يُترجَم هذا الحدّ إلى خسارة بحدّين واضحي المعنى:

  • حدّ إعادة البناء: توقّع لوغاريتم كثافة xx الحقيقيّة بمعلومية zz المأخوذة. عمليًّا يُقارَب بخطأ تربيعيّ أو بإنتروبيا متقاطعة ثنائية، تمامًا كالمرمّز التلقائي البسيط.
  • حدّ التنظيم KL: تباعد كولباك-لايبلر بين التوزيع الذي أنتجه المرمّز والتوزيع المرجعيّ. يجرّ المرمّز نحو N(0,I)\mathcal{N}(0, I) ويمنعه من الانطواء على نقطة واحدة لكلّ صورة.

بين الحدّين توتّر واضح: إعادة البناء تدفع المرمّز إلى تشفير دقيق ومحدّد لكلّ xx، أمّا KL فيدفعه إلى تشفيرات فضفاضة متداخلة. هذا التوتّر هو ما يجعل الفضاء الكامن مستمرًّا ومنظّمًا وقابلًا للتوليد.

حيلة إعادة التوسيط: كيف نمرّر تدرّجًا عبر عيّنة عشوائية؟

هنا تظهر عقدة تقنية جميلة. لكي نُدرِّب المرمّز، علينا أن نُمرّر تدرّجًا عبر عمليّة أخذ العيّنة zN(μ,σ2)z \sim \mathcal{N}(\mu, \sigma^2). لكنّ العيّنة العشوائية لا تعرف كيف تمرّر تدرّجًا: من أين تدخل، وإلى أين تخرج؟

حيلة إعادة التوسيط تُعيد كتابة العيّنة بحيث ينفصل جزء العشوائية عن جزء التعلّم:

z=μ(x)+σ(x)ϵ,ϵN(0,I)z = \mu(x) + \sigma(x) \odot \epsilon, \qquad \epsilon \sim \mathcal{N}(0, I)

الآن العشوائية كلّها في ϵ\epsilon، أمّا μ\mu وσ\sigma فدوالٌّ اعتيادية بمعالم قابلة للتعلّم، ويمرّ التدرّج فيهما بدون عائق. هذه الحيلة الصغيرة، من ثلاثة أسطر شفرة، هي ما جعل VAE ممكنًا أصلًا.

import torch
import torch.nn as nn

class VAE(nn.Module):
def __init__(self, dim_latente=20):
super().__init__()
self.encodeur = nn.Sequential(
nn.Flatten(),
nn.Linear(784, 400), nn.ReLU(),
)
self.tete_mu = nn.Linear(400, dim_latente)
self.tete_log_sigma = nn.Linear(400, dim_latente)
self.decodeur = nn.Sequential(
nn.Linear(dim_latente, 400), nn.ReLU(),
nn.Linear(400, 784), nn.Sigmoid(),
)

def encoder(self, x):
h = self.encodeur(x)
return self.tete_mu(h), self.tete_log_sigma(h)

def reparametrer(self, mu, log_sigma):
sigma = torch.exp(0.5 * log_sigma)
epsilon = torch.randn_like(sigma)
return mu + sigma * epsilon # حيلة إعادة التوسيط

def forward(self, x):
mu, log_sigma = self.encoder(x)
z = self.reparametrer(mu, log_sigma)
x_reconstruit = self.decodeur(z).view(-1, 1, 28, 28)
return x_reconstruit, mu, log_sigma

def perte_vae(x, x_reconstruit, mu, log_sigma):
recon = nn.functional.binary_cross_entropy(
x_reconstruit, x, reduction="sum"
)
kl = -0.5 * torch.sum(1 + log_sigma - mu.pow(2) - log_sigma.exp())
return recon + kl

حدّ KL له صيغة مغلقة حين يكون التوزيعان طبيعيّين، ولهذا نكتبه صراحةً في السطر الأخير دون أخذ عيّنات.

توليد صور MNIST بعد التدريب

بعد تدريب VAE على MNIST لعشرين حقبة، أخذ العيّنة يصبح مباشرًا: نُسحب zN(0,I)z \sim \mathcal{N}(0, I)، ونمرّرها إلى فكّ الترميز.

with torch.no_grad():
z = torch.randn(64, 20)
x_genere = modele.decodeur(z).view(-1, 28, 28)

النتيجة: أرقام مقروءة، بتنوّع معقول، لكن ضبابيّة. لن ترى الحواف الحادّة التي كنّا نراها في الصور الأصلية. هذا الضباب ليس عطبًا في الشفرة، بل نتيجة مباشرة لشكل الخسارة.

لماذا صور VAE ضبابيّة؟

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

الثاني: حدّ KL يدفع نحو تشفيرات متداخلة، وحين تتشارك صور مختلفة نقاطًا كامنة قريبة، يُنتج فكّ الترميز مزيجًا منها. هذا التداخل هو ثمن تنظيم الفضاء الكامن، ولا يمكن تفاديه دون تعديل بنية الخسارة.

β\beta-VAE لتغيير المفاضلة

يمكن ضرب حدّ KL بمعامل β\beta: قيمة أكبر من 1 تدفع نحو فضاء كامن أكثر تنظيمًا مع صور أكثر ضبابيّة، وقيمة أقلّ من 1 (كـ0.5) تعطي صورًا أوضح لكن أخذ العيّنة يصير هشًّا لأنّ التوزيع الكامن ابتعد عن N(0,I)\mathcal{N}(0, I). القيمة الشائعة لتوازن جيّد هي بين 0.5 و2، ويجب اختيارها بنظرة على العيّنات المولَّدة لا على قيمة الخسارة وحدها.

الفضاء الكامن المُنَظَّم: مكاسب حقيقية

رغم الضبابيّة، فضاء VAE الكامن مُنَظَّم فعلًا. الاستيفاء بين رقمين يعطي تدرّجًا مقنعًا. والحساب على المتّجهات يعمل أحيانًا: إذا حسبنا zˉمبتسمzˉحيادي\bar z_{\text{مبتسم}} - \bar z_{\text{حيادي}} على وجوه CelebA، فإضافة هذا الفارق إلى zz وجه محايد يُعطي وجهًا مبتسمًا. هذا النوع من التلاعب الدلاليّ ليس ممكنًا في نموذج انحداريّ ولا في GAN بالسهولة نفسها.

في الخلاصة

  • VAE يشفّر xx إلى توزيع طبيعيّ لا إلى نقطة، ويُلزمه بالقرب من N(0,I)\mathcal{N}(0, I) عبر حدّ KL.
  • حيلة إعادة التوسيط z=μ+σϵz = \mu + \sigma \odot \epsilon تفصل العشوائية عن المعالم القابلة للتعلّم، فتمرّر التدرّج عبر عيّنة عشوائية.
  • الخسارة ELBO جمع بين إعادة بناء وتنظيم KL؛ توتّرهما هو ما يُنتج فضاءً كامنًا مستمرًّا قابلًا للتوليد.
  • صور VAE ضبابيّة بسبب خسارة بكسل بكسل وتداخل التوزيعات؛ يبقى الفضاء الكامن قيّمًا لـالتلاعب الدلاليّ والتحرير الموجَّه.

الوحدة التالية تغيّر السؤال جذريًّا: بدل تعلّم توزيع صريح، سنُدرّب مولّدًا ضدّ ناقد يقول: «هذه الصورة حقيقيّة أو مزيّفة». هذه هي فكرة GAN.