زمانبندی نرخ یادگیری
یافتن learning rate مناسب بسیار مهم است. اگر نرخ خیلی زیاد باشد، آموزش ممکن است واگرا شود؛ اگر خیلی کم باشد، مدل شاید سرانجام به optimum برسد اما زمان بسیار زیادی لازم خواهد داشت. نرخ کمی بیش از حد مناسب میتواند در آغاز پیشرفت سریعی ایجاد کند، ولی در پایان مدل اطراف optimum نوسان میکند و مستقر نمیشود. وقتی بودجه محاسباتی محدود است، ممکن است مجبور شویم آموزش را پیش از همگرایی متوقف کنیم و به راهحلی ضعیفتر برسیم.
شکل ۱۱-۹ - رفتار منحنی آموزش برای مقادیر مختلف نرخ یادگیری η.
همانطور که در فصل ۱۰ دیدیم، یک راه برای یافتن نرخ مناسب این است که چندصد iteration آموزش انجام دهیم و learning rate را از مقداری بسیار کوچک بهصورت نمایی تا مقداری بسیار بزرگ افزایش دهیم. سپس در منحنی loss، نقطهای را پیدا کنیم که loss شروع به جهش رو به بالا میکند و نرخی کمی پایینتر از آن را انتخاب کنیم.
اما نرخ ثابت بهترین گزینه ممکن نیست. اگر آموزش را با نرخ نسبتاً بزرگ آغاز و وقتی پیشرفت کند شد نرخ را کاهش دهیم، ممکن است سریعتر از بهترین نرخ ثابت به راهحل خوب برسیم. حتی در بعضی روشها ابتدا نرخ کم است، سپس افزایش مییابد و در ادامه دوباره کاهش داده میشود. این راهبردها learning schedule نام دارند.
Power Scheduling
η(t) = η₀ / (1 + t/s)c
η₀ نرخ اولیه، c معمولاً ۱ و s تعداد گام مشخصکننده سرعت افت است. بعد از s گام، نرخ تقریباً به نصف میرسد؛ پس از s گام دیگر حدود یکسوم، سپس یکچهارم و به همین ترتیب. بنابراین در ابتدا سریع کاهش مییابد و سپس افت آن کندتر میشود. η₀، s و گاهی c باید تنظیم شوند.
Exponential Scheduling
η(t) = η₀ × 0.1t/s
در این روش نرخ یادگیری هر s گام تقریباً ده برابر کوچکتر میشود. برخلاف power schedule که کاهش آن تدریجاً کندتر میشود، exponential schedule افت دهبرابری را در بازههای مشابه ادامه میدهد.
Piecewise Constant Scheduling
در چند بازه زمانی نرخ ثابتهای متفاوت استفاده میشوند؛ مثلاً ۵ epoch با ۰٫۱، سپس ۵۰ epoch با ۰٫۰۰۱ و بعد مقداری دیگر. این روش میتواند بسیار خوب عمل کند، اما انتخاب نرخها و طول هر بازه نیازمند آزمایش است.
Performance Scheduling
خطای validation هر N گام اندازهگیری میشود و وقتی دیگر کاهش قابل توجهی ندارد، نرخ یادگیری با ضریبی مانند λ کوچک میشود. این ایده شبیه منطق early stopping است، با این تفاوت که بهجای پایان آموزش، نرخ را پایین میآوریم.
1cycle Scheduling
روش 1cycle که Leslie Smith در ۲۰۱۸ معرفی کرد، برخلاف scheduleهای کاهشیابنده سنتی، ابتدا learning rate را خطی از η₀ تا η₁ در نیمه اول آموزش افزایش میدهد. در نیمه دوم دوباره آن را خطی تا η₀ پایین میآورد و در چند epoch آخر نرخ چند مرتبه بزرگی دیگر کاهش مییابد. η₁ با همان روش آزمایش نرخهای رو به افزایش انتخاب میشود و η₀ معمولاً حدود ۱۰ برابر کمتر است.
اگر momentum استفاده شود، schedule آن معمولاً معکوس learning rate است: آموزش با momentum بالا مثل ۰٫۹۵ شروع میشود، در نیمه اول تا حدود ۰٫۸۵ کاهش مییابد و در نیمه دوم دوباره به ۰٫۹۵ برمیگردد. آزمایشها نشان دادهاند 1cycle میتواند هم آموزش را بسیار سریعتر کند و هم عملکرد نهایی را بالا ببرد. در یکی از نتایج CIFAR-10، همان معماری با 1cycle در ۱۰۰ epoch به ۹۱٫۹٪ دقت validation رسید، در حالی که روش استاندارد پس از ۸۰۰ epoch به ۹۰٫۳٪ میرسید؛ این پدیده super-convergence نام گرفته است.
مطالعات روی تشخیص گفتار نیز performance scheduling و exponential scheduling را مؤثر گزارش کردهاند؛ exponential بهعلت تنظیم ساده و همگرایی اندکی سریعتر جذاب است، اما 1cycle در بسیاری از مسائل میتواند حتی بهتر باشد.
پیادهسازی Schedule در Keras
Power schedule
optimizer = tf.keras.optimizers.SGD(
learning_rate=0.01, decay=1e-4)
در API مورد استفاده کتاب، decay تقریباً معکوس s است و c برابر ۱ فرض میشود.
Exponential schedule با callback
def exponential_decay_fn(epoch):
return 0.01 * 0.1 ** (epoch / 20)
برای جلوگیری از hard-code کردن η₀ و s میتوان یک سازنده تابع نوشت:
def exponential_decay(lr0, s):
def exponential_decay_fn(epoch):
return lr0 * 0.1 ** (epoch / s)
return exponential_decay_fn
exponential_decay_fn = exponential_decay(lr0=0.01, s=20)
سپس schedule به LearningRateScheduler داده میشود:
lr_scheduler = tf.keras.callbacks.LearningRateScheduler(
exponential_decay_fn)
history = model.fit(X_train, y_train, [...], callbacks=[lr_scheduler])
Callback در ابتدای هر epoch مقدار optimizer.learning_rate را تغییر میدهد. معمولاً یک بار در هر epoch کافی است؛ اگر گامهای بسیار زیادی در هر epoch داریم، میتوان callback سفارشی نوشت و نرخ را در هر step تغییر داد. نرخهای استفادهشده پس از آموزش از history.history["lr"] قابل دسترسیاند.
تابع schedule میتواند نرخ جاری را نیز بهعنوان ورودی دوم بگیرد:
def exponential_decay_fn(epoch, lr):
return lr * 0.1 ** (1 / 20)
این نسخه به learning rate اولیه optimizer وابسته است. هنگام ذخیره مدل، optimizer و learning rate نیز ذخیره میشوند، بنابراین ادامه آموزش ممکن است. اما اگر schedule به شماره epoch وابسته باشد، باید توجه کرد که epoch با هر fit() از صفر آغاز میشود مگر initial_epoch درست تنظیم شود؛ در غیر این صورت ادامه آموزش ممکن است ناگهان با نرخ بیش از حد بزرگ انجام شود و وزنها آسیب ببینند.
Piecewise constant
def piecewise_constant_fn(epoch):
if epoch < 5:
return 0.01
elif epoch < 15:
return 0.005
else:
return 0.001
Performance scheduling
lr_scheduler = tf.keras.callbacks.ReduceLROnPlateau(
factor=0.5, patience=5)
history = model.fit(X_train, y_train, [...], callbacks=[lr_scheduler])
در این مثال اگر بهترین validation loss برای پنج epoch متوالی بهتر نشود، learning rate نصف میشود.
Schedule object در optimizer
Keras کلاسهایی در tf.keras.optimizers.schedules دارد که نرخ را در هر step و نه صرفاً هر epoch تغییر میدهند. مثلاً exponential decay:
import math
batch_size = 32
n_epochs = 25
n_steps = n_epochs * math.ceil(len(X_train) / batch_size)
scheduled_learning_rate = tf.keras.optimizers.schedules.ExponentialDecay(
initial_learning_rate=0.01,
decay_steps=n_steps,
decay_rate=0.1)
optimizer = tf.keras.optimizers.SGD(
learning_rate=scheduled_learning_rate)
مزیت این روش آن است که هنگام ذخیره مدل، خود schedule و state آن نیز ذخیره میشوند. 1cycle در API مورد بحث کتاب کلاس آماده ندارد، اما با یک callback سفارشی کوتاه که در هر batch نرخ را تغییر دهد قابل پیادهسازی است. در مجموع exponential decay، performance scheduling و 1cycle هر سه میتوانند همگرایی را بهطور جدی سریعتر کنند.
جلوگیری از Overfitting با Regularization
«با چهار پارامتر میتوانم یک فیل را برازش کنم و با پنج پارامتر کاری میکنم خرطومش را تکان دهد.» - نقلشده از John von Neumann
شبکههای عمیق اغلب دهها هزار یا حتی میلیونها پارامتر دارند. این آزادی عظیم اجازه میدهد datasetهای بسیار پیچیده را برازش کنند، اما همان انعطاف آنها را مستعد overfitting میکند. Early stopping که در فصل ۱۰ دیدیم یکی از بهترین regularizerهاست و Batch Normalization نیز با اینکه برای ناپایداری گرادیان طراحی شده، اثر regularization مفیدی دارد. در ادامه ℓ1، ℓ2، dropout و max-norm بررسی میشوند.
Regularization نوع ℓ1 و ℓ2
همانند مدلهای خطی فصل ۴، میتوان ℓ2 را برای محدودکردن وزنهای اتصال و ℓ1 را برای تشویق مدل sparse با وزنهای صفر استفاده کرد. نمونه ℓ2 با ضریب ۰٫۰۱:
layer = tf.keras.layers.Dense(
100, activation="relu",
kernel_initializer="he_normal",
kernel_regularizer=tf.keras.regularizers.l2(0.01))
l2() شیء regularizer میسازد که در هر گام training یک regularization loss محاسبه میکند و آن را به loss نهایی اضافه میکند. برای ℓ1 از l1() و برای ترکیب هر دو از l1_l2() استفاده میشود.
چون معمولاً regularizer، activation و initializer یکسان روی چندین لایه تکرار میشوند، میتوان با functools.partial() یک wrapper تمیز ساخت:
from functools import partial
RegularizedDense = partial(
tf.keras.layers.Dense,
activation="relu",
kernel_initializer="he_normal",
kernel_regularizer=tf.keras.regularizers.l2(0.01))
model = tf.keras.Sequential([
tf.keras.layers.Flatten(input_shape=[28, 28]),
RegularizedDense(100),
RegularizedDense(100),
RegularizedDense(10, activation="softmax")
])
Dropout
Dropout یکی از محبوبترین روشهای regularization در DNNهاست. در هر گام آموزش، هر نورون - شامل نورونهای ورودی ولی نه نورونهای خروجی - با احتمال p موقتاً حذف میشود، یعنی در همان گام کاملاً نادیده گرفته و خروجیاش صفر میشود، اما در گام بعد ممکن است دوباره فعال باشد. p نرخ dropout است و معمولاً بین ۱۰٪ تا ۵۰٪ انتخاب میشود؛ در RNNها غالباً حدود ۲۰ تا ۳۰٪ و در CNNها حدود ۴۰ تا ۵۰٪. پس از پایان آموزش، نورونها دیگر حذف نمیشوند.
شاید در نگاه اول عجیب باشد که حذف تصادفی نورونها مدل را بهتر کند. میتوان آن را به سازمانی تشبیه کرد که هر روز بخشی از کارکنانش تصادفی غایباند: سازمان دیگر نمیتواند برای یک وظیفه حیاتی فقط به یک فرد وابسته باشد؛ دانش و مسئولیت باید بین افراد بیشتری توزیع شود و همکاری انعطافپذیرتر شود. در شبکه نیز نورونها نمیتوانند بیش از حد با همسایههای خاص خود co-adapt شوند یا فقط به چند ورودی اتکا کنند. در نتیجه هر نورون مجبور میشود مستقلتر مفید باشد و شبکه نسبت به تغییرات کوچک ورودی robustتر میشود.
شکل ۱۱-۱۰ - در هر iteration آموزش، زیرمجموعهای تصادفی از نورونهای لایههای انتخابشده موقتاً drop میشوند و خروجی صفر میدهند؛ لایه خروجی drop نمیشود.
تعبیر دیگر این است که در هر training step یک شبکه متفاوت نمونهبرداری میشود. اگر N نورون قابل dropout وجود داشته باشد، تعداد شبکههای ممکن 2N است؛ عددی آنقدر بزرگ که تکرار دقیق یک شبکه بسیار بعید است. بعد از هزاران گام، عملاً مجموعه بزرگی از زیرشبکههای متفاوت با وزنهای مشترک آموزش دیدهاند. شبکه نهایی را میتوان مانند یک ensemble میانگینگیریشده از این زیرشبکهها دید. جزئیات پیادهسازی و مقیاسدهی activationها در بخش بعد ادامه مییابد.
این ترجمه برای استفادهٔ آموزشی تهیه شده است. انتشار عمومی، فروش یا بازنشر آن باید با رعایت حقوق مؤلف و ناشر و اخذ مجوزهای لازم انجام شود.