مقابله با Gradient ناپایدار در RNNهای طولانی
در RNNهای طولانی همان Weightها بارها در Time Stepهای مختلف استفاده میشوند. اگر یک تغییر کوچک در Output مرحلهٔ اول ایجاد شود، ممکن است در مراحل بعد بارها تقویت شود و Output یا Gradientها منفجر شوند. استفاده از Learning Rate کوچکتر، Activation اشباعشونده مثل tanh و Gradient Clipping از راهحلهای رایج هستند.
Batch Normalization در RNN به اندازهٔ شبکههای Feedforward مناسب نیست. BN را میتوان بین Recurrent Layerها قرار داد، اما استفاده از یک BN یکسان داخل Cell برای تمام Time Stepها معمولاً نتیجهٔ مطلوبی ندارد، زیرا Scale و Offset داده در زمانهای مختلف میتواند متفاوت باشد.
Layer Normalization در RNN
Layer Normalization بهجای Normalization روی Batch، Featureهای هر Instance را در همان Time Step نرمال میکند. بنابراین Statisticها در لحظه محاسبه میشوند، رفتار Train و Inference یکسان است و نیازی به Moving Average ندارد. در RNN معمولاً LayerNorm پس از Linear Combination ورودی و Hidden State و قبل از Activation قرار میگیرد.
class LNSimpleRNNCell(tf.keras.layers.Layer):
def __init__(self, units, activation="tanh", **kwargs):
super().__init__(**kwargs)
self.state_size = units
self.output_size = units
self.simple_rnn_cell = tf.keras.layers.SimpleRNNCell(
units, activation=None)
self.layer_norm = tf.keras.layers.LayerNormalization()
self.activation = tf.keras.activations.get(activation)
def call(self, inputs, states):
outputs, new_states = self.simple_rnn_cell(inputs, states)
norm_outputs = self.activation(self.layer_norm(outputs))
return norm_outputs, [norm_outputs]
برای استفاده از این Cell سفارشی، آن را در یک Layer عمومی RNN قرار میدهیم:
custom_ln_model = tf.keras.Sequential([
tf.keras.layers.RNN(LNSimpleRNNCell(32),
return_sequences=True,
input_shape=[None, 5]),
tf.keras.layers.Dense(14)
])
Keras همچنین پارامترهای dropout و recurrent_dropout را در بیشتر Recurrent Layerها ارائه میدهد. اولی روی Inputهای Cell و دومی روی Hidden State بین Time Stepها اعمال میشود.
برای برآورد عدمقطعیت Forecast میتوان از MC Dropout استفاده کرد: Model را چند بار با training=True اجرا کنید، سپس Mean و Standard Deviation Predictionها را برای هر Horizon محاسبه کنید.
حل مشکل حافظهٔ کوتاهمدت
در RNN ساده، اطلاعات در هر Time Step چند Transformation را طی میکند و بهتدریج اثر ورودیهای ابتدایی از Hidden State حذف میشود. Cellهای LSTM و GRU برای حفظ اطلاعات بلندمدت طراحی شدهاند و امروزه در بسیاری از کاربردها جای Cell ساده را گرفتهاند.
LSTM: حافظهٔ کوتاهمدت بلند
Long Short-Term Memory در سال ۱۹۹۷ توسط Sepp Hochreiter و Jürgen Schmidhuber معرفی شد. یک LSTM دو State دارد: h(t) بهعنوان State کوتاهمدت و c(t) بهعنوان State بلندمدت. در Keras کافی است SimpleRNN را با LSTM جایگزین کنیم:
model = tf.keras.Sequential([
tf.keras.layers.LSTM(32, return_sequences=True,
input_shape=[None, 5]),
tf.keras.layers.Dense(14)
])
شکل 15-12. LSTM: حافظهٔ کوتاهمدت بلند
ایدهٔ اصلی این است که LSTM یاد میگیرد چه اطلاعاتی را در Memory ذخیره کند، چه چیزهایی را فراموش کند و چه بخشی را به Output بدهد. چهار Fully Connected Component در Cell وجود دارد:
- g(t): Candidate Memory یا محتوای جدیدی که از Input و Hidden State قبلی ساخته میشود.
- Forget Gate f(t): تعیین میکند کدام بخش از Long-term State قبلی حذف شود.
- Input Gate i(t): تعیین میکند چه مقدار از Candidate جدید وارد Long-term State شود.
- Output Gate o(t): تعیین میکند چه بخش از Long-term State در Output و Short-term State دیده شود.
i(t) = σ(Wxiᵀx(t) + Whiᵀh(t-1) + bi)
f(t) = σ(Wxfᵀx(t) + Whfᵀh(t-1) + bf)
o(t) = σ(Wxoᵀx(t) + Whoᵀh(t-1) + bo)
g(t) = tanh(Wxgᵀx(t) + Whgᵀh(t-1) + bg)
c(t) = f(t) ⊗ c(t-1) + i(t) ⊗ g(t)
y(t) = h(t) = o(t) ⊗ tanh(c(t))
TensorFlow معمولاً Bias مربوط به Forget Gate را با 1 مقداردهی اولیه میکند تا Cell در شروع Training همهٔ Memory را فوراً فراموش نکند.
GRU: نسخهٔ سادهتر LSTM
Gated Recurrent Unit در سال ۲۰۱۴ معرفی شد و در بسیاری از Taskها عملکردی نزدیک به LSTM دارد، اما معماری آن سادهتر است.
شکل 15-13. GRU: نسخهٔ سادهتر LSTM
در GRU State کوتاهمدت و بلندمدت در یک Vector ادغام میشوند. Gate به نام z(t) هم نقش Forget و هم Input Gate را بازی میکند؛ Gate دیگری به نام r(t) مشخص میکند چه مقدار از State قبلی به Candidate جدید نشان داده شود. Output Gate جداگانه وجود ندارد.
z(t) = σ(Wxzᵀx(t) + Whzᵀh(t-1) + bz)
r(t) = σ(Wxrᵀx(t) + Whrᵀh(t-1) + br)
g(t) = tanh(Wxgᵀx(t) + Whgᵀ(r(t) ⊗ h(t-1)) + bg)
h(t) = z(t) ⊗ h(t-1) + (1-z(t)) ⊗ g(t)
Keras Layerهای GRU و GRUCell را مستقیماً ارائه میدهد.
Conv1D برای کوتاهکردن Sequence
حتی LSTM و GRU روی Sequenceهای بسیار طولانی محدودیت دارند. یک راه حل این است که پیش از Recurrent Layer از 1D Convolution استفاده شود. Conv1D Kernelهای کوچک را روی محور زمان Sliding میکند و Patternهای محلی را یاد میگیرد. با Stride بزرگتر از 1، Sequence کوتاهتر میشود و RNN بعدی میتواند Patternهای دورتر را مؤثرتر ببیند.
conv_rnn_model = tf.keras.Sequential([
tf.keras.layers.Conv1D(filters=32, kernel_size=4, strides=2,
activation="relu", input_shape=[None, 5]),
tf.keras.layers.GRU(32, return_sequences=True),
tf.keras.layers.Dense(14)
])
چون Kernel Size برابر 4 و Stride برابر 2 است، Targetها نیز باید Crop و Downsample شوند تا Alignment زمانی حفظ شود.
WaveNet: Convolution Dilated برای Sequenceهای بسیار بلند
در سال ۲۰۱۶ پژوهشگران DeepMind معماری WaveNet را معرفی کردند. WaveNet مجموعهای از Conv1Dهای Dilated را Stack میکند و Dilation Rate را در Layerهای متوالی افزایش میدهد: 1، 2، 4، 8 و ... . به این ترتیب Layerهای پایین Pattern کوتاه و Layerهای بالا Pattern بلند را میبینند، در حالی که تعداد Parameter و هزینهٔ محاسباتی کنترل میشود.
شکل 15-14. WaveNet: Convolution Dilated برای Sequenceهای بسیار بلند
در مدل اصلی، Stackهای ۱۰ لایهای با Dilationهای 1 تا 512 چند بار تکرار میشوند. یک Stack ۱۰لایهای Receptive Field تقریباً معادل Kernel با طول 1024 ایجاد میکند، اما بسیار کارآمدتر است.
wavenet_model = tf.keras.Sequential()
wavenet_model.add(tf.keras.layers.Input(shape=[None, 5]))
for rate in (1, 2, 4, 8) * 2:
wavenet_model.add(tf.keras.layers.Conv1D(
filters=32, kernel_size=2, padding="causal",
activation="relu", dilation_rate=rate))
wavenet_model.add(tf.keras.layers.Conv1D(filters=14, kernel_size=1))
padding="causal" باعث میشود Zero Padding فقط در ابتدای Sequence قرار گیرد و Model هنگام Forecast به آینده نگاه نکند. WaveNet برای Audio بسیار موفق بود؛ یک ثانیهٔ Audio میتواند دهها هزار Time Step داشته باشد، طولی که برای LSTM و GRU بسیار دشوار است.
مدل Forecast حملونقل شیکاگو وقتی روی دادههای ۲۰۲۰ Test میشود افت شدید دارد، زیرا Pandemic کووید-۱۹ Patternهای گذشته را شکست. این مثال یادآوری میکند که Model فقط تا زمانی قابل اعتماد است که Distribution و Patternهای آموختهشده در دادهٔ جدید حفظ شوند؛ Performance در Production باید دائماً Monitoring شود.
تمرینهای فصل ۱۵
- برای Sequence-to-Sequence، Sequence-to-Vector و Vector-to-Sequence چند کاربرد واقعی پیشنهاد کنید.
- ورودی RNN Layer چند Dimension دارد و هر Dimension چه معنایی دارد؟ Output چطور؟
- در Deep Sequence-to-Sequence کدام Recurrent Layerها باید
return_sequences=True داشته باشند؟ در Sequence-to-Vector چطور؟
- اگر یک سری روزانهٔ Univariate دارید و میخواهید ۷ روز آینده را Forecast کنید، چه معماری RNN مناسب است؟
- مشکلات اصلی Training RNN چیست و چگونه میتوان آنها را کاهش داد؟
- معماری LSTM Cell را ترسیم و نقش Gateها را توضیح دهید.
- چرا ممکن است Conv1D را در کنار RNN استفاده کنید؟
- برای Classification ویدئو چه معماری Neural Network میتوان به کار برد؟
- یک Classifier برای Dataset مربوط به SketchRNN در TensorFlow Datasets بسازید.
- با Bach Chorales مدلی Recurrent، Convolutional یا ترکیبی بسازید که Time Step بعدی را پیشبینی کند و سپس موسیقی شبیه Bach تولید کند.