فصل ۱۱: آموزش شبکههای عصبی عمیق
در فصل ۱۰ نخستین شبکههای عصبی مصنوعی را ساختیم، آموزش دادیم و تنظیم کردیم؛ اما آن شبکهها کمعمق و دارای تنها چند لایه پنهان بودند. برای مسئلهای پیچیده، مانند تشخیص صدها نوع شیء در تصاویر با وضوح بالا، ممکن است به یک ANN بسیار عمیق با دهها لایه، صدها نورون در هر لایه و صدها هزار اتصال نیاز داشته باشیم. آموزش چنین شبکهای ساده نیست و چند مشکل مهم مطرح میشود:
- در هنگام انتشار معکوس، گرادیانها ممکن است هرچه به لایههای پایینتر میرسند بسیار کوچک یا بسیار بزرگ شوند و آموزش آن لایهها را دشوار کنند.
- ممکن است داده آموزشی کافی برای چنین مدل بزرگی وجود نداشته باشد یا برچسبگذاری آن بسیار پرهزینه باشد.
- آموزش میتواند بسیار کند باشد.
- مدلی با میلیونها پارامتر، بهویژه با داده کم یا پرنویز، خطر زیادی برای overfitting دارد.
این فصل برای هرکدام از این مسائل تکنیکهایی ارائه میکند: ابتدا گرادیان محوشونده و انفجاری و راهحلهای آنها، سپس یادگیری انتقالی و پیشآموزش بدون نظارت، بعد بهینهسازهایی برای سرعتبخشیدن به آموزش و در پایان روشهای regularization برای شبکههای بزرگ. این مجموعه ابزارها پایه آموزش شبکههای بسیار عمیق و همان چیزی است که معمولاً «یادگیری عمیق» مینامیم.
مشکل گرادیانهای محوشونده و انفجاری
در مرحله دوم backpropagation، خطای گرادیان از لایه خروجی به سمت لایه ورودی منتشر میشود. وقتی گرادیان تابع هزینه نسبت به هر پارامتر محاسبه شد، الگوریتم با یک گام gradient descent پارامترها را بهروزرسانی میکند.
اما در شبکههای عمیق، گرادیانها اغلب با حرکت به سمت لایههای پایین کوچک و کوچکتر میشوند. در نتیجه وزن اتصالهای لایههای پایین تقریباً تغییر نمیکند و مدل به راهحل مناسب همگرا نمیشود. این پدیده گرادیان محوشونده (vanishing gradients) نام دارد. گاهی برعکس، گرادیانها لایهبهلایه بزرگتر میشوند، بهروزرسانی وزنها به مقادیر عظیم میرسد و آموزش واگرا میشود؛ این گرادیان انفجاری (exploding gradients) است و در شبکههای بازگشتی بیشتر دیده میشود. بهطور کلی مسئله، ناپایداری گرادیان است: لایههای مختلف ممکن است با سرعتهای بسیار متفاوت یاد بگیرند.
این رفتار مدتها بهصورت تجربی شناخته شده بود و یکی از دلایل کنار گذاشتهشدن شبکههای عمیق در اوایل دهه ۲۰۰۰ به شمار میرفت. مقاله سال ۲۰۱۰ Xavier Glorot و Yoshua Bengio نشان داد یکی از عوامل اصلی، ترکیب تابع sigmoid با شیوه متداول آن زمان برای مقداردهی اولیه وزنها، یعنی توزیع نرمال با میانگین ۰ و انحراف معیار ۱، است. با این ترکیب، واریانس خروجی هر لایه از واریانس ورودی آن بزرگتر میشود و در مسیر رو به جلو پیوسته افزایش مییابد تا sigmoid در لایههای بالا اشباع شود. از طرفی میانگین sigmoid برابر ۰٫۵ است، نه صفر؛ tanh بهعلت میانگین صفر در شبکههای عمیق کمی بهتر رفتار میکند.
شکل ۱۱-۱ - اشباع تابع فعالسازی sigmoid؛ در مقادیر بسیار مثبت یا منفی، مشتق تقریباً صفر میشود.
وقتی ورودی sigmoid بسیار بزرگ، چه مثبت و چه منفی، باشد، خروجی به ۱ یا ۰ نزدیک میشود و مشتق تقریباً صفر است. بنابراین در backpropagation تقریباً گرادیانی برای عبور به لایههای قبل باقی نمیماند و همان مقدار اندک نیز در گذر از لایههای بالایی بیشتر تضعیف میشود.
مقداردهی اولیه Glorot و He
Glorot و Bengio پیشنهاد کردند برای جریان مناسب سیگنال در هر دو جهت، واریانس خروجی هر لایه تقریباً برابر واریانس ورودی آن باشد و در مسیر معکوس نیز واریانس گرادیان پیش و پس از عبور از لایه ثابت بماند. تحقق کامل هر دو شرط فقط وقتی تعداد ورودیها و خروجیهای لایه برابر باشد ممکن است. این تعدادها بهترتیب fan-in و fan-out نام دارند. سازش عملی آنها بر پایه fanavg=(fanin+fanout)/2 است و امروز با نام Xavier یا Glorot initialization شناخته میشود.
Normal: mean = 0, variance σ² = 1 / fanavg
Uniform: −r … +r, r = √(3 / fanavg)
اگر در این رابطه fan_avg با fan_in جایگزین شود، روش LeCun initialization به دست میآید. وقتی fan_in = fan_out باشد، LeCun و Glorot یکساناند. این ترفند ساده میتواند آموزش را بهطور محسوسی سریعتر کند و از عوامل مهم موفقیت شبکههای عمیق بوده است.
برای توابع فعالسازی مختلف، مقیاس واریانس مناسب متفاوت است. برای ReLU و مشتقهای آن، روش He initialization یا Kaiming initialization معمولاً انتخاب مناسب است؛ برای SELU نیز LeCun normal توصیه میشود.
جدول ۱۱-۱ - مقداردهی اولیه پیشنهادی برای انواع تابع فعالسازی
| روش | توابع فعالسازی | واریانس σ² در توزیع نرمال |
| Glorot | بدون فعالسازی، tanh، sigmoid، softmax | 1 / fan_avg |
| He | ReLU، Leaky ReLU، ELU، GELU، Swish، Mish | 2 / fan_in |
| LeCun | SELU | 1 / fan_in |
Keras بهطور پیشفرض از Glorot با توزیع یکنواخت استفاده میکند. برای He میتوان مقدار kernel_initializer را تغییر داد:
import tensorflow as tf
dense = tf.keras.layers.Dense(50, activation="relu",
kernel_initializer="he_normal")
کلاس VarianceScaling امکان تنظیم دقیقتر مقیاس، mode و نوع توزیع را میدهد. مثلاً He با توزیع یکنواخت اما بر اساس fan_avg:
he_avg_init = tf.keras.initializers.VarianceScaling(
scale=2., mode="fan_avg", distribution="uniform")
dense = tf.keras.layers.Dense(50, activation="sigmoid",
kernel_initializer=he_avg_init)
توابع فعالسازی بهتر
یکی دیگر از نتایج کار Glorot و Bengio این بود که ناپایداری گرادیان تا حدی از انتخاب نامناسب تابع فعالسازی ناشی میشود. سالها sigmoid بهدلیل شباهت تقریبی با رفتار نورون زیستی محبوب بود، اما برای شبکههای عمیق توابع دیگر بهتر عمل میکنند. ReLU بهخصوص به این دلیل مفید است که برای ورودی مثبت اشباع نمیشود و محاسبه آن بسیار سریع است.
بااینحال ReLU بینقص نیست. در مشکل dying ReLU بعضی نورونها در طول آموزش عملاً «میمیرند» و همیشه صفر خروجی میدهند. این وضعیت بهخصوص با نرخ یادگیری بزرگ میتواند بخش بزرگی از نورونها را درگیر کند. اگر وزنهای یک نورون طوری تغییر کنند که جمع وزندار ورودیها بهاضافه bias برای تمام نمونههای آموزش منفی باشد، ReLU همیشه صفر میدهد. چون مشتق ReLU در ناحیه منفی صفر است، gradient descent دیگر نمیتواند آن نورون را بهسادگی تغییر دهد. یکی از راههای مقابله، استفاده از خانواده Leaky ReLU است.
Leaky ReLU
LeakyReLUα(z) = max(αz, z)
پارامتر α شیب تابع در z<0 را تعیین میکند. همین شیب کوچک اجازه میدهد گرادیان در ناحیه منفی صفر نشود، بنابراین نورونها بهطور کامل نمیمیرند؛ ممکن است مدت طولانی «به کما» بروند، اما امکان بازگشت دارند. پژوهشها نشان دادهاند نسخههای نشتی در بسیاری از آزمونها از ReLU معمولی بهترند و حتی α نسبتاً بزرگ مانند ۰٫۲ گاهی از ۰٫۰۱ بهتر عمل میکند.
در RReLU مقدار α هنگام آموزش بهصورت تصادفی از یک بازه انتخاب و هنگام آزمون روی میانگین آن بازه ثابت میشود. این تصادفیبودن میتواند نقش regularizer داشته باشد. در PReLU، α خود یک پارامتر قابل یادگیری است و با backpropagation بهینه میشود؛ این روش روی datasetهای تصویری بزرگ عملکرد خوبی داشته، اما روی دادههای کوچک ممکن است overfitting را افزایش دهد.
شکل ۱۱-۲ - Leaky ReLU؛ مشابه ReLU است اما برای ورودیهای منفی شیب کوچکی دارد.
Keras کلاسهای LeakyReLU و PReLU را در tf.keras.layers دارد و بهتر است همراه آنها از He initialization استفاده شود:
leaky_relu = tf.keras.layers.LeakyReLU(alpha=0.2) # default: alpha=0.3
dense = tf.keras.layers.Dense(50, activation=leaky_relu,
kernel_initializer="he_normal")
میتوان LeakyReLU را بهصورت لایهای جدا نیز نوشت و از نظر آموزش و پیشبینی تفاوتی ایجاد نمیشود:
model = tf.keras.models.Sequential([
[...],
tf.keras.layers.Dense(50, kernel_initializer="he_normal"),
tf.keras.layers.LeakyReLU(alpha=0.2),
[...]
])
برای PReLU کافی است LeakyReLU را با PReLU جایگزین کنیم. Keras در این نسخه پیادهسازی رسمی RReLU ندارد، ولی ساخت آن دشوار نیست.
ELU و SELU
ReLU، Leaky ReLU و PReLU همگی در صفر غیرهموارند و مشتقشان ناگهان تغییر میکند. چنین ناپیوستگیای میتواند gradient descent را اطراف نقطه بهینه به نوسان بیندازد و همگرایی را کند کند. ELU و SELU نسخههای هموارتری هستند.
ELUα(z) = α(exp(z) − 1) if z < 0; z if z ≥ 0
ELU برای z<0 مقدار منفی دارد، بنابراین میانگین خروجی نورون را به صفر نزدیکتر میکند و به کاهش گرادیان محوشونده کمک میکند. α حد منفی تابع را تعیین میکند و معمولاً ۱ است. گرادیان در سمت منفی صفر نیست، پس مشکل نورون مرده کاهش مییابد. اگر α برابر ۱ باشد، تابع حتی اطراف صفر نیز هموار است و این میتواند حرکت gradient descent را روانتر کند. در Keras کافی است activation="elu" و ترجیحاً He initialization استفاده شود. هزینه ELU این است که بهعلت محاسبه تابع نمایی کندتر از ReLU است؛ هرچند همگرایی سریعتر ممکن است این هزینه را در آموزش جبران کند.
شکل ۱۱-۳ - توابع فعالسازی ELU و SELU.
SELU نسخه مقیاسشده ELU است؛ تقریباً 1.05×ELU با α≈1.67. اگر یک MLP فقط از لایههای Dense تشکیل شده باشد و تمام لایههای پنهان SELU داشته باشند، تحت شرایط مشخص شبکه میتواند خودنرمالشونده باشد: خروجی هر لایه در طول آموزش تمایل دارد میانگین ۰ و انحراف معیار ۱ را حفظ کند و مشکل گرادیان محوشونده/انفجاری را کاهش دهد.
برای اینکه self-normalization برقرار شود چند شرط مهم وجود دارد:
- ویژگیهای ورودی باید استاندارد شوند: میانگین ۰ و انحراف معیار ۱.
- وزن همه لایههای پنهان با LeCun normal مقداردهی اولیه شوند:
kernel_initializer="lecun_normal".
- این خاصیت فقط برای MLPهای ساده تضمین شده است؛ در معماریهای بازگشتی یا شبکههای دارای skip connection ممکن است برتری SELU از بین برود.
- regularizationهایی مانند ℓ1، ℓ2، max-norm، batch normalization یا dropout معمولی با شرایط self-normalization سازگار نیستند.
این محدودیتها باعث شده SELU با وجود مزایای نظری، فراگیری زیادی پیدا نکند. افزون بر آن، سه تابع جدیدتر یعنی GELU، Swish و Mish در بسیاری از وظایف عملکردی بسیار قوی دارند.
GELU، Swish و Mish
GELU در سال ۲۰۱۶ معرفی شد و میتوان آن را نسخهای هموار از ReLU دانست. اگر Φ تابع توزیع تجمعی نرمال استاندارد باشد:
GELU(z) = z Φ(z)
GELU برای ورودی بسیار منفی به صفر نزدیک میشود و برای ورودی بسیار مثبت به z میل میکند، اما برخلاف توابعی که تا اینجا دیدیم نه کاملاً convex است و نه monotonic. منحنی آن در بخش منفی کمی پایین میرود، نزدیک z≈−0.75 به کمینهای حدود −۰٫۱۷ میرسد و سپس رو به بالا حرکت میکند. این شکل هموار و دارای انحنا در همه نقاط ممکن است یکی از دلایل توانایی آن در برازش الگوهای پیچیده باشد. در عمل اغلب بسیار خوب عمل میکند، هرچند هزینه محاسباتی بیشتری دارد.
تقریب سریع و بسیار کاربردی آن چنین است:
GELU(z) ≈ z σ(1.702 z)
شکل ۱۱-۴ - مقایسه GELU، Swish، Swish پارامتری و Mish.
در بخش بعدی Swish و Mish را دقیقتر بررسی میکنیم.
این ترجمه برای استفادهٔ آموزشی تهیه شده است. انتشار عمومی، فروش یا بازنشر آن باید با رعایت حقوق مؤلف و ناشر و اخذ مجوزهای لازم انجام شود.