Padding، Stride و اندازهٔ Output در Conv2D
دو حالت رایج Padding در Keras عبارتاند از valid و same. در حالت valid هیچ Zero Padding اضافه نمیشود؛ در same تا حد امکان Spatial Size حفظ میشود. وقتی Stride بزرگتر از ۱ است حتی Same Padding Output کوچکتر میشود، چون Window با Step بزرگتری حرکت میکند.
شکل 14-7. دو گزینهٔ Padding با Stride برابر ۱
شکل 14-8. کوچک شدن Output با Stride بزرگتر از ۱
اگر بخواهیم اندازهٔ خروجی را دقیق محاسبه کنیم، برای padding="valid" و عرض ورودی iw، عرض خروجی برابر است با (iw - fw + sw) / sw که به پایین گرد میشود؛ باقیماندهٔ تقسیم به ستونهایی در سمت راست ورودی مربوط است که نادیده گرفته میشوند. همین منطق برای ارتفاع نیز برقرار است. در padding="same" عرض خروجی برابر iw / sw است که به بالا گرد میشود. اگر عرض خروجی را ow بنامیم، تعداد ستونهای صفر لازم برای Padding برابر (ow - 1) × sw + fw - iw است و Keras آنها را تا حد امکان متقارن در دو طرف قرار میدهد.
Weightهای Conv2D نیز مانند Dense Layer از طریق weights یا get_weights() قابلدسترسیاند. در مثال کتاب، Layer دارای ۳۲ Filter هفتدرهفت روی Input سهکاناله است:
kernels, biases = conv_layer.get_weights()
print(kernels.shape) # (7, 7, 3, 32)
print(biases.shape) # (32,)
آرایهٔ Kernel چهاربعدی است و Shape آن [kernel_height, kernel_width, input_channels, output_channels] است؛ Bias نیز Vector یکبعدی با طول output_channels است. Height و Width خود Image در Shape Kernel ظاهر نمیشوند، زیرا همهٔ Neuronهای هر Feature Map همان Weightها را Share میکنند. در نتیجه همین Layer میتواند Imageهایی با اندازههای Spatial متفاوت را پردازش کند، به شرط آنکه Image از Kernel کوچکتر نباشد و تعداد Channelها با Layer سازگار باشد.
از آنجا که Convolution ذاتاً Operation خطی است، معمولاً همراه آن Activation Function مانند ReLU و Initializer متناسب، مانند He Initialization، تعیین میشود؛ Stack کردن چند Convolution بدون Nonlinearity در نهایت همچنان یک Transformation خطی خواهد بود. Conv Layer Hyperparameterهای زیادی دارد: تعداد Filterها، Kernel Size، Stride، Padding، Activation، Initializer و Regularizer/Constraint. Cross-Validation میتواند برای تنظیم آنها استفاده شود، اما جستوجوی گسترده بسیار زمانبر است؛ به همین دلیل معماریهای استاندارد بخشهای بعدی نقطهٔ شروع عملی خوبی فراهم میکنند.
نیاز حافظه در CNN
Convolutional Layerها علاوه بر Weightها، Activationهای بزرگ تولید میکنند. مثال عددی کتاب این هزینه را روشن میکند: Layerای با ۲۰۰ Filter پنجدرپنج، stride=1 و padding="same" را روی یک Image رنگی 150×100 در نظر بگیرید. تعداد Parameterها فقط (5×5×3 + 1) × 200 = 15,200 است؛ عددی نسبتاً کوچک در مقایسه با Fully Connected Layer هماندازه. با این حال هر یک از ۲۰۰ Feature Map دارای 150×100 Neuron است و هر Neuron مجموع Weightدار 5×5×3 = 75 Input را محاسبه میکند؛ در مجموع حدود ۲۲۵ میلیون ضرب Float برای یک Forward Pass همین Layer لازم است.
اگر Feature Mapها با Float32 ذخیره شوند، Output این Layer برای فقط یک نمونه 200×150×100×32 = 96,000,000 بیت، یعنی حدود ۱۲ MB RAM اشغال میکند. با Batch شامل ۱۰۰ نمونه، تنها Output همین Layer تقریباً ۱٫۲ GB حافظه میخواهد. این مثال نشان میدهد که تعداد Parameter پایین لزوماً به معنی Memory Cost پایین نیست؛ Activationها میتوانند بخش اصلی مصرف حافظه باشند.
در Inference، Memory اشغالشده توسط یک Layer را میتوان بهمحض محاسبهٔ Layer بعدی آزاد کرد، بنابراین تقریباً حافظهٔ دو Layer متوالی بهاضافهٔ Weightها کافی است. اما در Training همهٔ مقدارهای میانی Forward Pass باید تا Reverse Pass نگه داشته شوند تا Gradientها محاسبه شوند، در نتیجه RAM موردنیاز دستکم مجموع حافظهٔ Activationهای Layerهای مختلف است.
طبق پیشنهاد متن منبع، اگر Training به خطای Out-of-Memory برخورد کرد، اولین گزینه کاهش Mini-Batch Size است. گزینههای دیگر عبارتاند از کاهش ابعاد با Stride، حذف چند Layer، استفاده از Float16 بهجای Float32، یا توزیع CNN روی چند Device. اینها راهکارهایی هستند که خود متن فصل برای این مثال ذکر میکند.
Pooling Layerها
Pooling Layer برای Subsampling استفاده میشود: Spatial Dimension را کاهش میدهد، Compute و Memory را پایین میآورد و مقداری Invariance نسبت به Translationهای کوچک ایجاد میکند. Max Pooling معمولاً با Window کوچک، مثلاً ۲×۲، و Stride ۲ اجرا میشود. هر Neuron فقط بیشترین مقدار داخل Receptive Field را برمیگرداند و Parameter قابلTrain ندارد.
شکل 14-9. Max Pooling با Kernel دو در دو و Stride دو
Max Pooling مقدار دقیق Position را تا حدی نادیده میگیرد. اگر Feature کمی جابهجا شود اما همچنان در همان Window قرار گیرد، Output Pooling ممکن است ثابت بماند. این خاصیت در Classification تصویر مفید است، چون Object باید با جابهجایی کوچک همچنان شناسایی شود.
شکل 14-10. Invariance نسبت به جابهجاییهای کوچک
Average Pooling بهجای Max میانگین Window را میگیرد. در بیشتر CNNهای مدرن Max Pooling برای Featureهای محلی رایجتر بوده، هرچند در Architectureهای جدید گاهی Convolution Strided یا Global Average Pooling جای آن را میگیرد.
Pooling در عمق و Global Average Pooling
Pooling معمولاً روی Height/Width انجام میشود و Depth را تغییر نمیدهد، اما میتوان Pooling را در Dimension Channel نیز تصور کرد. Depthwise Max Pooling میتواند بعضی Invarianceها را میان Feature Mapهای مشابه ایجاد کند.
شکل 14-11. Depthwise Max Pooling و Invariance
در Keras:
max_pool = tf.keras.layers.MaxPool2D(pool_size=2)
avg_pool = tf.keras.layers.AveragePooling2D(pool_size=2)
global_avg_pool = tf.keras.layers.GlobalAveragePooling2D()
GlobalAveragePooling2D برای هر Feature Map میانگین تمام Pixelهای Spatial را میگیرد و Tensor [batch, height, width, channels] را به [batch, channels] تبدیل میکند. این کار اغلب پیش از Output Layer در CNNهای مدرن استفاده میشود و تعداد Parameter را نسبت به Flatten + Dense بهشدت کاهش میدهد.
معماری معمول CNN
یک CNN کلاسیک چند Block از Convolution + Activation دارد، گاهی پس از چند Conv یک Pooling Layer اضافه میشود، Depth Feature Mapها بهتدریج زیاد و Spatial Size کم میشود. در پایان، Featureهای سطح بالا وارد Dense/Output Layer میشوند.
شکل 14-12. معماری معمول CNN
مثال ساده برای Fashion MNIST:
from functools import partial
DefaultConv2D = partial(
tf.keras.layers.Conv2D,
kernel_size=3, padding="same",
activation="relu", kernel_initializer="he_normal")
model = tf.keras.Sequential([
tf.keras.layers.Reshape([28, 28, 1], input_shape=[28, 28]),
DefaultConv2D(filters=64, kernel_size=7),
tf.keras.layers.MaxPool2D(pool_size=2),
DefaultConv2D(filters=128),
DefaultConv2D(filters=128),
tf.keras.layers.MaxPool2D(pool_size=2),
DefaultConv2D(filters=256),
DefaultConv2D(filters=256),
tf.keras.layers.MaxPool2D(pool_size=2),
tf.keras.layers.Flatten(),
tf.keras.layers.Dense(128, activation="relu",
kernel_initializer="he_normal"),
tf.keras.layers.Dropout(0.5),
tf.keras.layers.Dense(64, activation="relu",
kernel_initializer="he_normal"),
tf.keras.layers.Dropout(0.5),
tf.keras.layers.Dense(10, activation="softmax")
])
Convolutionهای ابتدایی Featureهای سطح پایین را Extract میکنند، Layerهای عمیقتر Featureهای پیچیدهتر را میسازند، Pooling Spatial Size را کاهش میدهد و Dense Layerهای انتهایی Classification را انجام میدهند. در Practice برای Imageهای بزرگتر، Architectureهای مدرن معمولاً از Batch Normalization، Skip Connection، Global Average Pooling و Blockهای تخصصی استفاده میکنند.