الهواء العليل يُطير قبعتك و لكنك حمدا لله أتى حارس محطة القطار لينقذها قبل وقوعها في السكة قبل صعودك لأول يوم في مهمتك التي يغتاظ منها معظم أبطال القصص و كتابها: مفتش تذاكر طيب القلب و منظم حريص على عمله
بالنسبة لك لا يهم كثرة أو قلة ما يحمله الراكب مادام معه تذكرة مادامت موجودة أو ممثلة لك و أي بطل رواية تصادف انه لا يحمل تذكرة يعتبر غير ظاهرا أو من المفترض عدم وجوده و يجب أن ينزل في المحطة التالية
كذلك ال L0 مفتش التذاكر العزيز حريص دوما على أن يعُد الأوزان في الشبكة العصبية لا يهم كثرتها أو قلتها الذي يقيس وزن كل ما يحمله الراكب و يبقى يجمع الأوزان في تلك الخلية
على عكس ابن عمه البعيد، و لكن لماذا نعد الأوزان الموجودة أو الغير الصفرية للشبكة العصبية حسنا لأشياء كثيرة لكن للاجابة مبدئيا على سؤال واحد بل سؤالين في وقت واحد كيف نصل لنموذج جيد بتوقع و دقة جيدة مع أقل عدد من الأوزان (Louizos et al., 2018; Oliveira et al., 2024)
هذه هي الكفاءة التي يتحدثون عنها ليل نهار و لكن قبل أن تقفل المقال خوفا من رؤية معادلات تبحث عنها لساعتين ثم ترجع و تنسى ما كنت تقرأ هنا لا تقلق هذا الوحش الكبير ليس إلا صديقنا المفتش داخل بذلته الرسمية وسط زملاء عمله
جولة مفتش التذاكر · ٦٠ ثانية
المفتش الأمين
الشكل 1: يتغير مجموع L₁ مع مقدار الوزن، بينما لا يتغير عدد L₀ إلا عندما يصبح الوزن صفرًا تمامًا.
معيار يعدّ كل وزن غير صفري مرة واحدة، مهما كان حجمه. والرمز هو فهرس الوزن في الشبكة.
فمثلا لديك هذا القطار حاول أن تجمع عدد الاوزان تذكر ذلك :
لو زاد حجم بعض الاوزان و قل البعض الاخر ماذا سيحدث ؟ سيتغير قيمة صديقنا L1 لكن L0 سيظل كما هو 3 و بهذه الطريقة عرفنا لويزو و من معه من باحثون (Louizos et al., 2018) على الL0 طريقة جميلة و أنيقة و لكنها قاسية قليلا و ما يسأله الL0 هل يجب أن يبقى هذا الوزن على متن القطار و لكن لماذا نجمع الأوزان الغير صفرية ؟
جرّب بنفسك: احسب ما يراه المفتش
weights = [3, 0, -2, 0, 5]print("L0 count:", sum(weight != 0 for weight in weights))print("L1 total:", sum(abs(weight) for weight in weights))النتيجة: عدد يساوي 3 ومجموع يساوي 10.
الL 0 و ضغط الشبكات العصبية
تخيل شبكة عصبية بها مليون وزن فلو كان كلهم غير صفريين
سيصبح لدينا و هكذا يمكننا أن نرى مبدئيا أن لكل الأوزان ظهورا و لكن عندما ندرب هذه الشبكة العصبية وجدنا أن فقط 100,000 من هذه الأوزان هي ما تؤثر على النتيجة سواء كالتصنيف أو أي شئ آخر لذلك سيكون
و هكذا نصل إلي تنبؤ جيد مع عدد معلمات مشاركة أقل وهي ما تقترن بها الأوزان لكن كيف وصلنا هنا؟ المفتش يستطيع عدد العناصر غير الصفرية جيدا أما بالنسبة لمن لديه أو من يستحق التذكرة فلا يعلم حتى الآن و هنا يدخل لويزو ورفاقه ثانية
يمكن أن يخطر ببالك فكرة إذا كنت تعلم عن الشبكة العصبية مسبقا أن نضيف ال L0 لهدف التمرين وندع الgradient descent أو كما أحب تسميته بالنزول عن التل يخبرنا بما نحتاج إبقاءه
حسنا لن أستطيع من خلال سطرين أن ألخص كيف تتعلم الشبكة العصبية، سأستفيض في شرحها في نهاية السلسلة لكن دعني أخبرك ما نحتاجه هنا، حتى تتمرن الشبكة العصبية نحتاج إلي backpropagation و هو أساس لكي نرجع من نهاية القرار إلى البداية وهو بمثابة النزول من التل، يخبرنا الانتشار العكسي باتجاه انحدار التل، بينما يقوم المُحسِّن فعليًا باتخاذ الخطوة نحو الأسفل.
النزول من التل
الآن نحن على قمة التل- أدري أن أمثلة التضاريس كثيرة في هذه السلسلة لكني جعلتك على قطار تسافر وسط حقول خضراء-تحاول النزول تحتاج أولا أن تعرف من أي جهة ينحدر و هذه هي وظيفة الميل :
فماذا سيحدث إن غيرنا ال w أو الوزن بجزء بسيط
و لننظر من جهة الL 0 كلما قللنا الوزن لن يتغير القرار سيظل للوزن ظهور أما محسن الأداء optimizer ينظر إليه و يسأله كل مرة يحاول أن يغير قيمة الوزن و من بعدها يبحث عن التغير في قيمة الL0 و التي تظل واحدة لذلك لا وجود للميل الذي يقربنا لسفح الجبل أي الصفر بتدرج.
هذه قسوة المفتش التي أتحدث عنها عندما يصطدم بقيمة صفر لا تنزل فجأة فأنت الأن تقفز من 1 إلي 0
الأن المفتش ينظر إلي الوزن أهو موجود أم لا
لذا لا يمكنه أن يسأل هل يمكنك أن تخرج بتدرج من القطار ؟ بل سيرميه خارجا
جرّب بنفسك: خذ خطوة واحدة من الانحدار
weight = 0.0learning_rate = 0.1for step in range(3): loss = (weight - 3.0) ** 2 gradient = 2.0 * (weight - 3.0) weight -= learning_rate * gradient print(f"الخطوة {step + 1}: الوزن={weight:.3f}، الخسارة={loss:.3f}")يتجه الوزن نحو قيمة تقلل خسارة هذا المثال؛ أما عدّ الصلب فلا يعطي هذا الميل.
البوابة
لذلك لويزو ورفاقه بدلا من جعلهم رقما واحدا يجاوب على سؤالي ما هو الوزن؟ و إذا كنا نحتاجه كله أم ماذا ؟في وقت واحد سيفصلونها لجزئين و هذا ما سيقودنا إلي
حيث هو الوزن الأصلي قبل قرار الإبقاء أو الإزالة، و هي البوابة، أما فهو الوزن الفعلي بعدها.
و بذلك الوزن الأساسي × قرار المشاركة = الوزن الفعلي
و بذلك تطور المفتش الذي يشاهد الوجود فقط بل لديه إثنين أحدهما وزن ما يحمله الراكب و أيضا تذكرته ( مساهمة الوزن و الوجود)
و الأن أصبح لدينا سؤال واحد نحتاج إجابته هل البوابة مفتوحة؟
هذه هي بداية حل مشكلة التعلم كيف سنقرر متى تفتح و تغلق البوابة كيف سنقرر من نعطيه ال1 و الصفر في الz
لذلك z هو متغير يأخذ قيمة صفر وواحد و له هذا الاحتمال لذلك من المنطقي استخدامهم لتوزيع برنولي
إذا كان الوزن مفيدًا، ترتفع احتمالية فتح بوابته ، والعكس إذا قلّ إسهامه.
جرّب بنفسك: طبّق البوابة على الأوزان
weights = [4, 2, 7]gates = [1, 0, 1]effective = [weight * gate for weight, gate in zip(weights, gates)]print(effective)البوابة المغلقة تصفّر الوزن الأوسط: [4, 0, 7].
العدد المتوقع للبوابات النشطة
حسنا الخطوة القادمة أن نصل إلي التوقع ل Z_j ورمز التوقع (Louizos et al., 2018)
لدينا:
ومعناها: المتوسط المتوقع لعدد الأوزان الفعالة يساوي مجموع احتمالات فتح بواباتها.
نبدأ من: حيث: هو الوزن الأساسي، و هو البوابة. إذا افترضنا أن
إذا اعتبرنا غير صفري، فإن وحدها تحدد هل الوزن الفعلي موجود أم لا.
إذا: فإن: إذن الوزن موجود ويحسبه L0 كواحد. أما إذا: فإن: إذن الوزن مختفٍ ولا يحسبه L0. لذلك، بالنسبة لوزن واحد فقط، مساهمته في عدد L0 تساوي ببساطة:
لأن نفسه إما 1 أو 0.
فإذا كان عندنا عدة أوزان: فإن عدد الأوزان المفتوحة في عينة معينة يساوي: مثلاً إذا كانت البوابات في لحظة ما: (1,0,1,1,0) فإن: لأن ثلاث بوابات مفتوحة. لكن هنا ليست ثابتة أثناء التدريب، بل عشوائية: يعني: باحتمال
و باحتمال الآن يأتي معنى التوقع. التوقع: يعني: لو كررنا تجربة فتح وإغلاق هذه البوابة مرات كثيرة جدًا، فما متوسط قيمتها؟
وبما أن قيمتها إما 1 أو 0، نحسب المتوسط المتوقع هكذا: فتصبح:
مثلاً إذا: فهذا لا يعني أن البوابة قيمتها 0.8.
البوابة نفسها تظل في كل مرة إما: 0 أو 1.
لكن لو كررنا أخذ عينات كثيرة، نتوقع أن تكون مفتوحة في حوالي من المرات، ولذلك متوسطها يقترب من: 0.8. والآن نعود إلى عدد الأوزان: نأخذ التوقع: خاصية مهمة في التوقع تقول إن توقع المجموع يساوي مجموع التوقعات: وبما أن: إذن:
خذ مثالًا صغيرًا. عندنا ثلاث بوابات: . إذن:
هذا لا يعني أن لدينا 1.8 وزنًا فعليًا.
في أي تجربة فعلية، عدد الأوزان المفتوحة يمكن أن يكون: 0,1,2,أو3. لكن لو كررنا أخذ البوابات مرات كثيرة جدًا، فإن متوسط عدد الأوزان المفتوحة سيقترب من: 1.8. بصياغة أبسط: كل راكب لديه احتمال أن يحمل تذكرة. إذا جمعنا احتمالات امتلاك التذاكر لكل الركاب، نحصل على العدد المتوقع للركاب الموجودين على القطار.
وهذه بالضبط هي النقلة المهمة هنا: بدل أن نحاول التعامل مباشرة مع عدد صلب من الصفر والواحد، أصبح لدينا: وهي كمية تتغير تدريجيًا، مثل: . وهذا يقودنا مباشرة إلى سؤالنا التالي:
جرّب بنفسك: قدّر العدد المتوقع للبوابات المفتوحة
import random probabilities = [0.9, 0.2, 0.7]rng = random.Random(7)trials = 50_000active = sum(sum(rng.random() < p for p in probabilities) for _ in range(trials))print("النظري:", sum(probabilities))print("المحاكاة:", active / trials)ينبغي أن يقترب متوسط المحاكاة من القيمة النظرية 1.8.
إذا أصبح جزء L0 أسهل بهذه الطريقة، فما الذي ما زال صعبًا في دالة التدريب نفسها؟
الهدف
الآن نحتاج للشبكة العصبية أن تتعلم شيئين في آن واحد أن تكون توقعات جيدة وأن نحتفظ بأقل قدر ممكن من المكونات النشطة.
و هذا يتركنا أمام المعادلة الأكبر في رحلتنا حتى الأن
لا تخف منها فهي الشيئان الذين نحاول تعليم الشبكة العصبية التوقع ولكن سنفككها معا
الجزء الأول حدّ التنبؤ هو : متوسط خسارة التنبؤ عبر عينات البوابات. والرمز يعني الضرب عنصرًا بعنصر.
لتكن و L(z) سيكون معناها أن احسب خسارة التنبؤ باستخدام الشبكة بعد أن تقوم البوابات التي تم أخذ عينات منها بإيقاف تشغيل بعض الأوزان.
و الأن سنرجع للتوقع و هو إلي حد ما تقدير للمتوسط و هو يجيب على السؤال في المتوسط، عبر تكوينات البوابات الناتجة عن هذه الاحتمالات، يجب أن يظل التنبؤ جيدًا.
و الان ننتقل للجزء الثاني فاتورة المفتش أما فهو ثمن النشاط المتوقع؛ وتحدد مقدار الضغط نحو إغلاق البوابات. و كلما قل أصبحت الأولوية للتنبؤ و كلما زادت أصبح أولوية قص الأوزان
و الأن في رحلة التعلم يتجادل طرفي المعادلة معا فالتنبؤ يحاول دوما أن يفتح أكبر قدر من البوابات لكي يبقى التنبؤ عاليا و القص يحاول أن يحذف أكثر قدر من البوابات و حل الجدال؟
المكونات أو البوابات هي من تعطينا الدليل.
إذا كان المكوّن مهمًا، تحاول خسارة التنبؤ رفع احتمال بوابته . وإذا لم يكن مهمًا، تدفعه عقوبة sparsity إلى خفض هذا الاحتمال.
جرّب بنفسك — غيّر ثمن البوابة
prediction_loss = 0.18expected_active_gates = 3.4for sparsity_price in [0.01, 0.1, 0.5]: objective = prediction_loss + sparsity_price * expected_active_gates print(f"lambda={sparsity_price:.2f}: objective={objective:.3f}")يوضح المثال مساهمة العقوبة مع تغيّر ؛ وهو تمرين توضيحي لا نتيجة تدريب للشبكة.
سنحتاج إلى تفاضل هذه المعادلة. حدّ العقوبة قابل للاشتقاق لأن متغير مستمر. لكن حدّ التنبؤ ما زال يعتمد على عينة Bernoulli متقطعة ، لذلك لا نستطيع تمرير التدرج عبر خطوة أخذ العينة بالطريقة العادية.
كيف تتعلم الشبكة العصبية
أتتذكر ال backpropagation ؟ حسنا يحتاج إلى سلسلة مثل abc حيث نفاضلهم من خلال قاعدة السلسلة
و لكننا نواجه مشكلة هنا b هي اتباع z توزيع برنولي و هو توزيع متقطع و بذلك z متقطع إما 0 او 1 و بذلك لا يمكننا مفاضلته
نصل الآن إلى الخطوة الكبرى التالية عند لويزو.
الاسترخاء المستمر: إعطاء البوابة مساحة وسطية
الشكل 2: يحافظ القص على قيم البوابة الوسيطة مع السماح أيضًا بصفر وواحد حقيقيين.
حتى الآن، كانت البوابة: وهذا يعني:
- 0: مغلقة
- 1: مفتوحة
وهذا الانتقال حاد جدًا بالنسبة للـ backpropagation العادي.
لذلك يُدخل لويزو أولًا متغيرًا مستمرًا جديدًا:
ثم يعرّف البوابة الفعلية باستخدام القص أو clipping:
وهذه هي الوصفة العامة للبوابة المستمرة (Louizos et al., 2018).
لا تقلق الآن بشأن q أو j. افهم الفكرة أولًا.
قبل ذلك كانت البوابة تستطيع أن تكون فقط: 0 أو: 1. لم تكن هناك حالة وسطية.
أما الآن فننشئ أولًا قيمة مستمرة .
يمكن أن تكون مثلًا: -0.7,0.2,0.65,1.3 أو أي قيم حقيقية أخرى. ثم نمررها عبر:
ماذا تعني clip؟ الـ clipping يعني: أي قيمة أقل من 0 تصبح بالضبط 0. أي قيمة بين 0 و1 تبقى كما هي. أي قيمة أكبر من 1 تصبح بالضبط 1.
رياضيًا:
وهذه هي الصيغة القطعية لتعريف القص.
أمثلة: إذا كان: فإن: . وإذا كان: فإن: .
إذا كان: فإن: . وإذا كان: فإن: . إذن الآن يمكن للبوابة أن تكون مثل: 0,0.1,0.4,0.8,1 بدلًا من أن تكون فقط: 0,1.
لماذا هذا مفيد؟ لأن البوابة يمكنها الآن أن تتحرك تدريجيًا.
بدلًا من: في قفزة واحدة حادة، يمكن أن يكون لدينا: .
وهذا يعطي التحسين المعتمد على التدرجات شيئًا أفضل بكثير للعمل معه.
الآن متسلق الجبل يرى ميلًا تدريجيًا بدلًا من حافة مفاجئة.
لكن لماذا نستخدم clipping أصلًا؟ هذا مهم جدًا. قد تسأل: إذا كنا نريد الاستمرارية، فلماذا لا نستخدم مباشرة؟
لأننا ما زلنا نريد إمكانية الإزالة الحقيقية.
إذا كانت البوابة دائمًا قيمة ناعمة مثل: 0.00001 فهي من الناحية التقنية لا تزال غير صفرية.
وتذكر أن L0 يهتم بـ: صفر مقابل غير صفر. إذن لويزو يريد شيئين معًا:
و
والـ clipping يحقق الاثنين (Louizos et al., 2018). إذا كان: فنحصل على: بالضبط. ليس: 0.00001. بل صفر حقيقي. وإذا كان: فنحصل على: بالضبط.
ولهذا يصنع الـ clipping أصفارًا ووحدات حقيقية، مع السماح بقيم مستمرة بينهما.
تشبيه المفتش
المفتش لم يعد يفكر في حالتين فقط. في السابق كان لديه: تذكرة أو لا توجد تذكرة. أما الآن فأثناء التدريب توجد مساحة وسطية.
يمكن أن يكون لدينا: أي: هذا الوزن يساهم حاليًا بنسبة . أو: أي: هذا الوزن يساهم بنسبة . وإذا تحرك المتغير الأساسي إلى ما دون الصفر، يقول المفتش أخيرًا: انتهى، أُزيل. فنحصل على: . وهكذا أصبح لدينا طريق تدريجي نحو الإزالة.
لكن انتبه إلى نقطة دقيقة: البوابة الفعلية هي: أما المتغير المستمر قبل القص فهو: . وهما ليسا الشيء نفسه.
مثلًا: لكن: . أو: لكن: . إذن يمكن أن يعيش في مجال أوسع من القيم الحقيقية، بينما يُجبر على البقاء داخل: [0,1].
والآن يحدث شيء مهم لاحتمال L0: البوابة تكون فعالة عندما: . وبسبب الـ clipping، فإن: يحدث بالضبط عندما: . إذن: .
إذا كانت هي دالة التوزيع التراكمي CDF الخاصة بـ ، فإن: . وبالتالي:
وهذه هي النسخة المستمرة من فكرة Bernoulli السابقة.
بدلًا من: أصبح لدينا: بوصفه احتمال أن تكون البوابة فعالة.
وبالتالي تصبح دالة الهدف الجديدة
لا تحفظ هذه المعادلة الآن. المهم هو الفكرة:
أي:
وهذه هي الجسور التي تسمح لاحقًا باستخدام توزيعات مستمرة مختلفة.
ولهذا فهذه الخطوة مهمة جدًا عندما نصل بعد ذلك إلى Gaussian gates.
إطار لويزو هنا أوسع من Hard-Concrete نفسه؛ فالفكرة العامة تسمح باستخدام توزيعات مستمرة أخرى طالما يمكن حساب الاحتمالات المطلوبة وإجراء reparameterization مناسب لها.
إذن هذه هي النقطة التي يبدأ منها الطريق نحو Gaussian gates.
والسؤال التالي هو:
وهنا تدخل فكرة:
جرّب بنفسك: قصّ عينة مستمرة
samples = [-0.3, 0.25, 1.4]clipped = [max(0.0, min(1.0, sample)) for sample in samples]print(clipped)القيم الأقل من صفر تصبح صفرًا، والأكبر من واحد تصبح واحدًا.
إعادة صياغة العشوائية: Reparameterization
وصلنا في آخر محطة إلى أن وجود متغير مستمر مثل أفضل من أن نقفز مباشرة بين الصفر والواحد، لكن بقي سؤال مهم: نحن ما زلنا نأخذ كعينة من توزيع احتمالي، فكيف يستطيع الـ backpropagation أن يعرف كيف غيَّرنا معاملات هذا التوزيع؟
الفكرة التي يستخدمها لويزو هنا تسمى Reparameterization، واسمها أكبر من فكرتها. بدل أن نخفي العشوائية داخل عملية أخذ العينة نفسها، نفصل بين شيئين:
- جزء نتعلمه.
- وجزء عشوائي لا نتعلمه.
فنكتب بصورة عامة:
حيث هي معاملات التوزيع التي نريد تعلمها، و ضوضاء عشوائية نأخذها من توزيع ثابت لا يعتمد على .
الفكرة ببساطة أن العشوائية لم تختف، لكنها خرجت من الطريق الذي نريد أن نفاضله. أصبح لدينا رقم عشوائي ، ثم دالة عادية تربطه بالمعامل الذي نريد تعلمه.
مثال سريع قبل Hard-Concrete
لو كان التوزيع Gaussian نستطيع أن نكتب:
لو أخذنا في إحدى الخطوات: سيصبح:
والآن أثناء الـ backward pass نتعامل مع قيمة التي أخذناها كأنها ثابتة في هذه الخطوة. فإذا غيّرنا من إلى ، يتحرك من إلى . أصبح هناك طريق واضح يستطيع التفاضل السير خلاله.
إذن الـ Reparameterization لا يلغي العشوائية. هو فقط يقول: ضع العشوائية في ، واجعل المعامل الذي نريد تعلمه ظاهرًا داخل معادلة يمكن تفاضلها.
لكن انتبه: هذا المثال Gaussian فقط لشرح الفكرة العامة. لويزو في طريقته الأساسية لم يستخدم Gaussian gate؛ اختار Hard-Concrete، والآن وصلنا إليه أخيرًا.
جرّب بنفسك: ثبّت الضوضاء وغيّر المعامل
fixed_noise = -0.4for learned_location in [-0.5, 0.0, 0.5]: sample = learned_location + fixed_noise print(f"الموقع={learned_location:+.1f}: العينة={sample:+.1f}")نستخدم الضوضاء نفسها، بينما يغيّر المعامل المتعلَّم موضع العينة بسلاسة.
Hard-Concrete: بوابة لويزو التي تحاول أن تجمع العالمين
نريد الآن شيئًا يحقق مطلبين كانا يبدوان متعارضين: نريد بوابة ناعمة يمكن التدريب خلالها، وفي نفس الوقت نريد أن تنتهي بعض البوابات إلى صفر حقيقي حتى يصبح لدينا حقيقي وليس مجرد أوزان صغيرة.
Hard-Concrete يبني هذا على عدة خطوات، لذلك لا تنظر إلى المعادلة كاملة مرة واحدة.
الخطوة الأولى: رقم عشوائي بسيط
نبدأ بـ: أي نأخذ رقمًا عشوائيًا بين صفر وواحد.
مثلًا قد يكون: أو: لا يوجد قرار فتح أو إغلاق بعد. هذا فقط مصدر العشوائية.
الخطوة الثانية: نحول الرقم إلى Logistic noise
لويزو يستخدم: هذه العملية تحول الرقم الذي جاء من Uniform distribution إلى ضوضاء Logistic.
لا نحتاج الآن أن نغرق في التوزيع نفسه. المهم أن هو الجزء العشوائي، بينما سنضع بجانبه شيئًا يمكن للشبكة تعلمه.
الخطوة الثالثة: يدخل
الآن:
ثم: هنا ظهر أول متغير مهم تتعلمه الشبكة:
يمكنك التفكير فيه كمقبض يحرك توزيع البوابة. عندما يتحرك في اتجاه كبير موجب تميل البوابة أكثر ناحية الفتح، وعندما يتحرك ناحية السالب تميل أكثر ناحية الإغلاق.
أما فهي الـ temperature. هي تتحكم في مدى حدة أو نعومة الانتقال. لا تحتاج الآن أن تحفظ أثر كل قيمة، فقط تذكر أن هو الجزء الذي نتعلمه لكل بوابة، و تتحكم في شكل الاسترخاء.
والـ sigmoid يأخذ أي رقم حقيقي ويضغطه داخل المجال:
إذن أصبح قيمة ناعمة بين صفر وواحد.
لكن لو بقيت القيمة داخل فلن نحصل على صفر حقيقي
وهنا تأتي حركة Hard-Concrete الأساسية. لويزو لا يكتفي بالقيمة بين صفر وواحد، بل يمد المجال قليلًا إلى الخارج:
حيث: في إعدادات الورقة استُخدمت مثلًا: ما الفائدة من هذا المد؟
تخيل أن كان محبوسًا دائمًا بين صفر وواحد. بعد المد أصبح جزء من القيم يمكن أن يقع أقل من صفر، وجزء آخر يمكن أن يتجاوز الواحد.
ثم نطبق الحركة التي عرفناها مسبقًا:
أي أن القيم التي هبطت تحت الصفر تصبح صفرًا حقيقيًا، والقيم التي ارتفعت فوق الواحد تصبح واحدًا حقيقيًا، وما بينهما يبقى ناعمًا.
لماذا يسمى Hard-Concrete؟
Concrete يعطينا الاسترخاء المستمر الناعم (Maddison et al., 2017)، أما كلمة Hard فتأتي من عملية الـ clipping التي تصنع كتلة حقيقية عند الصفر وعند الواحد.
إذن الفكرة كلها يمكن اختصارها هكذا:
بالمعنى نفسه: لم يعد القرار ورقة تذكرة تظهر فجأة. لدينا طريق ناعم أولًا، ثم في نهايته حاجزان: من خرج تحت الصفر يُغلق عليه الباب تمامًا، ومن تجاوز الواحد تُفتح له البوابة تمامًا.
احتمال أن تكون بوابة Hard-Concrete مفتوحة
تذكر أننا لا نريد فقط عينة واحدة، بل نحتاج كذلك إلى معرفة احتمال أن تكون البوابة غير صفرية حتى نحسب عقوبة .
في Hard-Concrete يمكن حساب هذا الاحتمال مباشرة:
لا تحفظها الآن. المهم أن لدينا مرة أخرى شيئًا شبيهًا بما كان لدينا مع : وبالتالي نستطيع أن نجمع: لنحصل على العدد المتوقع للبوابات النشطة.
وعند تصغير دالة الهدف، عقوبة تضغط على هذه الاحتمالات إلى الأسفل، بينما خسارة التنبؤ تضغط في الاتجاه المعاكس للبوابات التي يحتاج إليها النموذج.
هل انتهى الأمر وأصبح كل صفرًا أو واحدًا؟
لا. أثناء التدريب يمكن أن تكون البوابة مثلًا: أو:
لأننا ما زلنا نعمل باستـرخاء continuous relaxation. في أحيان أخرى ستتشبع بالـ clipping وتصبح صفرًا أو واحدًا بالضبط.
إذن Hard-Concrete لا يعني أن كل forward pass هو شبكة ثنائية تمامًا. هو توزيع مستمر مُقصوص يسمح أيضًا بظهور أصفار ووحدات حقيقية.
بوابة الاختبار عند لويزو ليست نفس عينة التدريب
وهذه نقطة مهمة عندما نفكر في الاستخدام العملي لاحقًا.
أثناء التدريب نأخذ عينات عشوائية، لكن عند الاختبار لا نريد أن تتغير نتيجة النموذج كل مرة بسبب سحب عشوائي جديد، لذلك يعطي لويزو بوابة حتمية تعتمد على الموقع الذي تعلمناه:
لاحظ أنها قد تكون كسرية أيضًا. أي أن deterministic gate لا تعني بالضرورة: قد تكون: وهنا يجب أن نفرق منذ الآن بين ثلاثة أشياء: عينة التدريب العشوائية، والبوابة الحتمية للتقييم، والقرار الثنائي النهائي الذي سيزيل شيئًا من النموذج فعلًا.
جرّب بنفسك: اسحب بوابة Hard-Concrete
import mathimport random rng = random.Random(7)beta, gamma, zeta, log_alpha = 2 / 3, -0.1, 1.1, 0.0u = rng.random()logistic_noise = math.log(u) - math.log1p(-u)soft = 1 / (1 + math.exp(-(logistic_noise + log_alpha) / beta))stretched = soft * (zeta - gamma) + gammagate = max(0.0, min(1.0, stretched))print(f"u={u:.4f}; البوابة={gate:.4f}")قد ينتج القص في النهاية صفرًا أو واحدًا بالضبط.
من وزن واحد إلى عربة كاملة: Group Sparsity
الشكل 4: تحتاج القناة المحذوفة إلى إعادة بناء الموترات المتصلة بها فعليًا لتحقيق مكسب في السرعة.
حتى الآن كنا نتحدث وكأن لكل وزن بوابة خاصة به، لكن لويزو يسمح أيضًا أن تشترك مجموعة كاملة في بوابة واحدة.
وهنا نصل إلى السؤال الذي تركناه من بداية الرحلة: كيف يمكن أن يصل الأمر إلى إزالة Channel كامل وليس وزنًا واحدًا؟
لو وضعنا بوابة واحدة أمام مجموعة من الأوزان، فإن:
لا يغلق وزنًا منفردًا، بل يغلق المجموعة كلها.
في convolutional neural network يمكن أن تكون المجموعة هي output feature map كامل.
أي أن كل Channel له بوابة واحدة، وهذه البوابة تُضرب في كامل خريطة الخصائص: إذا كان:
تختفي خريطة الـ feature كلها، وليس بكسلًا واحدًا ولا وزنًا واحدًا.
وهنا أصبحت تذكرة المفتش لا تخص راكبًا، بل عربة كاملة من القطار. إن أُغلقت بوابة العربة، كل ما بداخلها يخرج من الرحلة.
لكن ماذا نعد؟
لو جمعنا فقط: فنحن نعد العدد المتوقع للمجموعات أو الـ channels النشطة.
لكن هذا ليس بالضرورة نفس عدد الـ parameters، لأن Channel قد يحتوي على عدد من الأوزان يختلف عن Channel آخر.
إذا كانت المجموعة تحتوي على وزنًا، فإن العدد المتوقع للأوزان يكون: إذن يجب ألا نخلط بين: عدد الـ channels، وعدد الأوزان، و FLOPs، والسرعة الفعلية.
هذا الفرق مهم عندما نفكر في أثر الإزالة على التنفيذ.
جرّب بنفسك: احسب المجموعات والأوزان كلًا على حدة
group_sizes = [6, 4]active_probabilities = [0.8, 0.25]print("المجموعات:", sum(active_probabilities))print("الأوزان:", sum(n * p for n, p in zip(group_sizes, active_probabilities)))المتوسط المتوقع 1.05 مجموعة، لكنه 5.8 وزنًا لاختلاف أحجام المجموعات.
كيف تبدو دورة التدريب عند لويزو؟
الآن يمكننا أن نرى الرحلة كاملة في خطوة تدريب واحدة: أولًا نأخذ noise للبوابات. ثم نحوله عبر Hard-Concrete إلى . ثم نضرب الأوزان في البوابات:
ثم نجري الـ forward pass ونحسب prediction loss.
بعدها نحسب عقوبة النشاط المتوقعة من احتمالات البوابات:
ثم نجمع الطرفين، ويعود الـ backpropagation من الخسارة خلال الطريق الذي صنعناه بالـ reparameterization، ويحدّث أوزان الشبكة ومعاملات البوابات معًا.
إذن لويزو لم يكتشف مسبقًا من يستحق التذكرة. بل جعل التنبؤ وعقوبة التعقيد يتجادلان أثناء التدريب حتى تتحرك معاملات البوابات إلى أماكن تجعل بعض الأجزاء أكثر احتمالًا للبقاء وأجزاء أخرى أكثر احتمالًا للإغلاق.
وهنا يجب أن نوقف القطار لحظة
من السهل جدًا بعد كل هذا أن نقول: إذن Gaussian gate التي سنستخدمها بعد قليل هي نفسها طريقة لويزو.
لكن هذا غير صحيح.
لويزو أعطانا الإطار العام لفكرة بالبوابات المستمرة المقصوصة، لكنه اختار في تطبيقه الأساسي Hard-Concrete.
أما Gaussian stochastic gate فله مصدر مباشر آخر سنصل إليه الآن: يامادا وزملاؤه.
من Hard-Concrete إلى Gaussian: نفس الهيكل وأب مختلف
الفكرة العامة التي أخذناها من لويزو هي:
وإلى جانب ذلك نحسب احتمال أن تكون البوابة نشطة من الـ CDF الخاص بالتوزيع الأب.
Hard-Concrete اختار بناءً يعتمد على Logistic noise و sigmoid.
يامادا قال: يمكننا أن نفعل نفس الفكرة بمتغير Gaussian أبسط.
يامادا وزملاؤه: Gaussian Stochastic Gates
في عمل (Yamada et al., 2020) لكل feature بوابة stochastic خاصة به. الصيغة الأصلية يمكن كتابتها:
وهي نفسها إذا استخدمنا reparameterization:
هنا هو المعامل الذي نتعلمه، و هو مقدار الضوضاء. في بناء يامادا يكون مقدار الضوضاء ثابتًا، بينما نتعلم الموقع .
ما وظيفة ؟
تخيل منحنى Gaussian يتحرك على خط الأعداد.
إذا دفعنا ناحية اليمين، أصبح جزء أكبر من التوزيع فوق الصفر، وبالتالي ترتفع فرصة أن تكون البوابة مفتوحة.
إذا دفعناه ناحية اليسار، ينتقل جزء أكبر من التوزيع تحت الصفر، وبعد الـ clipping يتحول هذا الجزء إلى صفر.
إذن هو المقبض الذي يقرر تدريجيًا إن كان الـ feature يستحق البقاء.
احتمال النشاط في Gaussian gate
الشكل 5: هذا احتمال طويل الأمد لبوابة غير صفرية، وليس قيمة عينة واحدة.
البوابة فعالة عندما: وبما أن الـ clipping يحول فقط القيم التي كانت إلى صفر، فإن: لكن: إذن: ننقل :
وباستخدام تماثل التوزيع الطبيعي نحصل على (Yamada et al., 2020):
حيث هي CDF للتوزيع الطبيعي القياسي.
أي أنها تجيب ببساطة: ما مساحة منحنى الـ Gaussian الموجودة إلى يسار قيمة معينة؟
مثال
إذا كان: فإن:
ومن ثم: أي أن البوابة ستكون غير صفرية في نحو من العينات على المدى الطويل.
مرة أخرى: ليست قيمة البوابة نفسها. هي احتمال أن تكون البوابة أكبر من صفر.
عقوبة عند يامادا
بدل أن نجمع كما فعلنا مع Bernoulli، أو صيغة Hard-Concrete الخاصة، نجمع الآن:
وهذا يعطينا العدد المتوقع للبوابات النشطة. فتصبح الفكرة مرة أخرى:
عقوبة الـ تدفع إلى اليسار لتقل احتمالية النشاط، وخسارة التنبؤ تدفع البوابات المهمة في الاتجاه المعاكس.
جرّب بنفسك: قارن احتمال البوابة الغاوسية بالمحاكاة
import mathimport random mu, sigma, trials = 0.3, 0.5, 50_000formula = 0.5 * (1 + math.erf((mu / sigma) / math.sqrt(2)))rng = random.Random(7)measured = sum(mu + sigma * rng.gauss(0, 1) > 0 for _ in range(trials)) / trialsprint(f"الصيغة={formula:.4f}; المحاكاة={measured:.4f}")تقترب المحاكاة من كلما زاد عدد السحبات.
هل يامادا كان يحذف Channels؟
لا في الورقة الأصلية. وهذه نقطة يجب أن نحافظ عليها.
يامادا استخدم البوابة أساسًا كـ feature-selection gate عند مدخل الشبكة: كل input feature له بوابته.
استخدمت الورقة الأصلية Gaussian gates لاختيار الخصائص عند مدخل الشبكة. أما استخدام بوابة مشابهة لاتخاذ قرارات تخص مجموعات كاملة، فسنتركه لجزء لاحق من هذه الرحلة.
لماذا Gaussian بدل Hard-Concrete؟
يامادا قدّم Gaussian stochastic gates كبديل للتوزيعات المبنية على Logistic noise، وذكر في تجاربه تحسنًا في استقرار اختيار الـ features مقارنة بـ Hard-Concrete في سياقه التجريبي.
لكن لا نقفز من هذه الجملة إلى:
Gaussian أفضل دائمًا من Hard-Concrete.
هذا غير ما تثبته الورقة.
الأصح أن نقول: هما طريقتان مختلفتان لبناء نفس الفكرة العامة للبوابة المستمرة المقصوصة، ويامادا أعطى Gaussian gate دعمًا مباشرًا في feature selection.
Hard-Concrete و Gaussian جنبًا إلى جنب
الهيكل المشترك:
وفي الطريقتين نستطيع الوصول إلى صفر حقيقي بعد الـ clipping، ونستطيع حساب احتمال النشاط وتحويله إلى عقوبة .
الاختلاف في التوزيع الذي يولد العينة وفي صيغة احتمال النشاط.
Hard-Concrete:
Gaussian:
إذن المفتش لم يغير وظيفته. الذي تغير هو الآلة التي تولد له احتمالات فتح البوابات.
تلميح لأحداث مستقبلية
في جزء لاحق من هذه الرحلة، سنسأل ماذا يتغير عندما يخص القرار مجموعة كاملة بدلًا من راكب واحد. وسننظر في الوقت نفسه إلى الفرق بين جعل المكوّن غير نشط وإزالة العمليات الحسابية التي يحتاج إليها النموذج فعلًا.
احتفظ بهذا السؤال معنا: عندما تشترك مجموعة كاملة في قرار واحد، ما الذي نعدّه تذكرة، وماذا يعني أن تغادر المجموعة القطار؟
عودة إلى المفتش
في بداية القصة لم يكن مفتشنا يعرف سوى عدّ الركاب: موجود أو غير موجود. ثم اكتشف أن العد وحده لا يخبره من يستحق التذكرة.
فصلنا الوزن عن قرار وجوده، ثم جعلنا القرار احتماليًا. وبعدها أعطيناه طريقًا مستمرًا عبر Reparameterization؛ فبنى Louizos لهذا الطريق Hard-Concrete، واستخدم يامادا طريقًا Gaussian.
ويبقى سؤال المفتش الأساسي:
المراجع
Louizos, C., Welling, M., & Kingma, D. P. (2018). Learning sparse neural networks through regularization. في Proceedings of the International Conference on Learning Representations. النص الكامل.
Maddison, C. J., Mnih, A., & Teh, Y. W. (2017). The Concrete distribution: A continuous relaxation of discrete random variables. في Proceedings of the International Conference on Learning Representations. الورقة.
Oliveira, F. D. R., Batista, E. L. O., & Seara, R. (2024). On the compression of neural networks using -norm regularization and weight pruning. Neural Networks, 171, 343–352. https://doi.org/10.1016/j.neunet.2023.12.019
Yamada, Y., Lindenbaum, O., Negahban, S., & Kluger, Y. (2020). Feature selection using stochastic gates. في Proceedings of the 37th International Conference on Machine Learning (المجلد 119، ص ص. 10648–10659). الورقة وسجل المؤتمر.


