پیادهسازی MLP با Keras؛ Fashion MNIST، Sequential API و آموزش مدل
اکنون مفاهیم لازم برای پیادهسازی پرسپترون چندلایه با Keras را در اختیار داریم. Keras رابط سطحبالای یادگیری عمیق در TensorFlow است و امکان ساخت، آموزش، ارزیابی و اجرای انواع شبکههای عصبی را فراهم میکند. نسخهٔ اولیهٔ Keras را فرانسوا شوله در قالب یک پروژهٔ پژوهشی توسعه داد و در مارس ۲۰۱۵ بهصورت یک پروژهٔ متنباز مستقل منتشر کرد. سادگی استفاده، انعطاف و طراحی مناسب آن باعث شد خیلی سریع محبوب شود.
در ادامه یک MLP برای طبقهبندی تصویر میسازیم. محیطهای Colab معمولاً نسخههای جدید TensorFlow و Keras را از پیش نصب دارند.
ساخت طبقهبند تصویر با Sequential API
برای شروع به یک مجموعهداده نیاز داریم. از Fashion MNIST استفاده میکنیم که قالب آن دقیقاً مانند MNIST است: ۷۰٬۰۰۰ تصویر خاکستری ۲۸×۲۸ پیکسلی در ۱۰ کلاس. تفاوت اصلی این است که تصاویر Fashion MNIST بهجای رقم دستنویس، اقلام پوشاک را نمایش میدهند؛ بنابراین تنوع هر کلاس بیشتر و مسئله دشوارتر است. یک مدل خطی ساده روی MNIST حدود ۹۲٪ دقت میگیرد، اما روی Fashion MNIST حدود ۸۳٪.
بارگذاری داده با Keras
Keras توابع کمکی برای دریافت و بارگذاری مجموعهدادههای رایج از جمله MNIST و Fashion MNIST دارد. Fashion MNIST از پیش Shuffle شده و به ۶۰٬۰۰۰ نمونهٔ آموزشی و ۱۰٬۰۰۰ نمونهٔ آزمون تقسیم شده است. در این مثال ۵٬۰۰۰ تصویر پایانی مجموعهٔ آموزش را برای اعتبارسنجی کنار میگذاریم:
import tensorflow as tf
fashion_mnist = tf.keras.datasets.fashion_mnist.load_data()
(X_train_full, y_train_full), (X_test, y_test) = fashion_mnist
X_train, y_train = X_train_full[:-5000], y_train_full[:-5000]
X_valid, y_valid = X_train_full[-5000:], y_train_full[-5000:]
وقتی MNIST یا Fashion MNIST را با Keras بارگذاری میکنیم، هر تصویر بهصورت آرایهٔ ۲۸×۲۸ نگهداری میشود، نه یک بردار یکبعدی ۷۸۴تایی. شدت پیکسلها نیز از نوع عدد صحیح و در بازهٔ صفر تا ۲۵۵ هستند:
>>> X_train.shape
(55000, 28, 28)
>>> X_train.dtype
dtype('uint8')
برای سادگی، شدت پیکسلها را با تقسیم بر ۲۵۵ به بازهٔ صفر تا یک میبریم؛ این کار نوع داده را نیز به اعشاری تبدیل میکند:
X_train, X_valid, X_test = X_train / 255., X_valid / 255., X_test / 255.
برخلاف MNIST که عدد برچسب مستقیماً رقم تصویر را مشخص میکند، برای Fashion MNIST لازم است نام کلاسها را بدانیم:
class_names = ["T-shirt/top", "Trouser", "Pullover", "Dress", "Coat",
"Sandal", "Shirt", "Sneaker", "Bag", "Ankle boot"]
برای نمونه، نخستین تصویر مجموعهٔ آموزش یک نیمبوت را نمایش میدهد:
>>> class_names[y_train[0]]
'Ankle boot'
شکل ۱۰-۱۰. چند نمونه از مجموعهدادهٔ Fashion MNIST
ساخت مدل با Sequential API
حال شبکه را میسازیم. مدل زیر یک MLP طبقهبندی با دو لایهٔ پنهان است:
tf.random.set_seed(42)
model = tf.keras.Sequential()
model.add(tf.keras.layers.Input(shape=[28, 28]))
model.add(tf.keras.layers.Flatten())
model.add(tf.keras.layers.Dense(300, activation="relu"))
model.add(tf.keras.layers.Dense(100, activation="relu"))
model.add(tf.keras.layers.Dense(10, activation="softmax"))
خط نخست Seed تصادفی TensorFlow را تنظیم میکند تا نتایج تا حد ممکن بازتولیدپذیر باشند؛ در نتیجه وزنهای تصادفی اولیه در اجرای دوباره یکسان خواهند بود. تابع tf.keras.utils.set_random_seed() نیز میتواند Seed مربوط به TensorFlow، Python و NumPy را یکجا تنظیم کند.
سپس یک مدل Sequential ساخته میشود. این سادهترین نوع مدل Keras برای شبکههایی است که فقط از یک پشتهٔ ترتیبی از لایهها تشکیل شدهاند. لایهٔ Input شکل هر نمونه را تعیین میکند؛ اندازهٔ Batch در این شکل وارد نمیشود. Keras با دانستن شکل ورودی میتواند ابعاد ماتریس وزن نخستین لایهٔ پنهان را تعیین کند.
لایهٔ Flatten هر تصویر دوبعدی را به بردار یکبعدی تبدیل میکند. برای مثال Batch با شکل [32, 28, 28] به [32, 784] تبدیل میشود. این لایه پارامتر قابلآموزشی ندارد و صرفاً پیشپردازش سادهای انجام میدهد.
لایهٔ Dense نخست ۳۰۰ نورون با فعالسازی ReLU دارد. هر لایهٔ Dense ماتریس وزن اتصالها و یک بردار بایاس را مدیریت میکند. لایهٔ پنهان دوم ۱۰۰ نورون و همان فعالسازی را دارد. در پایان، لایهٔ خروجی شامل ۱۰ نورون، یکی برای هر کلاس، با Softmax است، زیرا کلاسها انحصاریاند.
بهجای افزودن لایهها یکییکی، میتوان فهرست لایهها را هنگام ساخت Sequential داد. همچنین میتوان لایهٔ Input را حذف و input_shape را روی نخستین لایه تعیین کرد:
model = tf.keras.Sequential([
tf.keras.layers.Flatten(input_shape=[28, 28]),
tf.keras.layers.Dense(300, activation="relu"),
tf.keras.layers.Dense(100, activation="relu"),
tf.keras.layers.Dense(10, activation="softmax")
])
بررسی ساختار و پارامترهای مدل
متد summary() لایههای مدل، نام هر لایه، شکل خروجی و تعداد پارامترهای آن را نمایش میدهد. مقدار None در بُعد نخست به این معنی است که اندازهٔ Batch میتواند دلخواه باشد:
>>> model.summary()
Model: "sequential"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
flatten (Flatten) (None, 784) 0
dense (Dense) (None, 300) 235500
dense_1 (Dense) (None, 100) 30100
dense_2 (Dense) (None, 10) 1010
=================================================================
Total params: 266,610
Trainable params: 266,610
Non-trainable params: 0
_________________________________________________________________
لایههای Dense میتوانند پارامترهای زیادی داشته باشند. مثلاً لایهٔ پنهان نخست 784 × 300 وزن و ۳۰۰ بایاس دارد؛ در مجموع ۲۳۵٬۵۰۰ پارامتر. این تعداد زیاد انعطاف مدل را برای برازش داده بالا میبرد، اما خطر Overfitting را نیز، بهخصوص در مجموعهدادههای کوچک، افزایش میدهد.
هر لایه باید نام یکتا داشته باشد. Keras معمولاً نام را از نام کلاس میسازد و در صورت نیاز یک اندیس اضافه میکند، مثلاً dense_2. یکتایی نامها در کل Session باعث میشود ادغام مدلها بدون برخورد نام سادهتر شود. وضعیت سراسری Keras را میتوان با tf.keras.backend.clear_session() پاک کرد که شمارندههای نام را نیز Reset میکند.
فهرست لایهها از ویژگی layers و یک لایهٔ مشخص از متد get_layer() قابل دسترسی است:
>>> hidden1 = model.layers[1]
>>> hidden1.name
'dense'
>>> model.get_layer('dense') is hidden1
True
پارامترهای یک لایه با get_weights() و set_weights() خوانده و تنظیم میشوند. در Dense این پارامترها شامل وزنهای اتصال و بایاسها هستند:
>>> weights, biases = hidden1.get_weights()
>>> weights.shape
(784, 300)
>>> biases.shape
(300,)
وزنها بهصورت تصادفی مقداردهی اولیه میشوند تا تقارن بین نورونها شکسته شود، در حالی که صفرکردن بایاسها مشکلی ندارد. با آرگومانهای kernel_initializer و bias_initializer میتوان روش مقداردهی اولیه را تغییر داد.
Compile کردن مدل
پس از ساخت مدل باید با متد compile() تابع Loss و Optimizer را مشخص کنید. میتوانید معیارهای اضافی برای محاسبه در زمان آموزش و ارزیابی نیز تعیین کنید:
model.compile(loss="sparse_categorical_crossentropy",
optimizer="sgd",
metrics=["accuracy"])
نامهای رشتهای در اینجا میانبر هستند. برای مثال optimizer="sgd" معادل ساخت tf.keras.optimizers.SGD() است و metrics=["accuracy"] برای این مسئله به معیار دقت طبقهبندی Sparse نگاشت میشود.
از sparse_categorical_crossentropy استفاده میکنیم چون برچسبها Sparse هستند؛ یعنی برای هر نمونه فقط اندیس کلاس هدف، از صفر تا ۹، وجود دارد و کلاسها انحصاریاند. اگر برای هر نمونه یک بردار احتمال One-hot داشتیم باید categorical_crossentropy را بهکار میبردیم. در طبقهبندی دودویی یا چندبرچسبی دودویی، خروجی Sigmoid و Loss از نوع binary_crossentropy مناسب است.
Optimizer با مقدار "sgd" یعنی آموزش با گرادیان کاهشی تصادفی. Keras در پشت صحنه همان پسانتشار، یعنی مشتقگیری خودکار معکوس بهعلاوهٔ گرادیان کاهشی، را اجرا میکند. در SGD تنظیم نرخ یادگیری اهمیت زیادی دارد و معمولاً بهتر است صریحاً مثلاً با tf.keras.optimizers.SGD(learning_rate=...) تعیین شود؛ مقدار پیشفرض رشتهٔ "sgd" برابر ۰٫۰۱ است.
آموزش و ارزیابی مدل
اکنون مدل آمادهٔ آموزش است و کافی است fit() را فراخوانی کنیم:
history = model.fit(X_train, y_train, epochs=30,
validation_data=(X_valid, y_valid))
ویژگیهای ورودی، کلاسهای هدف و تعداد Epochها به مدل داده میشوند. مجموعهٔ اعتبارسنجی اختیاری است، اما بسیار مفید است چون Keras در پایان هر Epoch مقدار Loss و معیارهای اضافی را روی آن محاسبه میکند. اگر عملکرد روی مجموعهٔ آموزش بهمراتب بهتر از اعتبارسنجی باشد، مدل احتمالاً Overfit شده یا مشکلی مانند ناسازگاری داده بین دو مجموعه وجود دارد.
Epoch 1/30
1719/1719 - loss: 0.7220 - sparse_categorical_accuracy: 0.7649
- val_loss: 0.4959 - val_sparse_categorical_accuracy: 0.8332
...
Epoch 30/30
1719/1719 - loss: 0.2235 - sparse_categorical_accuracy: 0.9200
- val_loss: 0.3056 - val_sparse_categorical_accuracy: 0.8894
اندازهٔ Batch پیشفرض ۳۲ است. با ۵۵٬۰۰۰ تصویر آموزشی، هر Epoch شامل ۱٬۷۱۹ Batch میشود: ۱٬۷۱۸ Batch با اندازهٔ ۳۲ و یک Batch با اندازهٔ ۲۴. Keras در هر Epoch نوار پیشرفت، میانگین زمان پردازش و مقادیر Loss و معیارها را برای آموزش و اعتبارسنجی نمایش میدهد. در این نمونه دقت اعتبارسنجی پس از ۳۰ Epoch به حدود ۸۸٫۹۴٪ رسیده است. فاصلهٔ اندک آن با دقت آموزش نشاندهندهٔ کمی Overfitting است.
اگر توزیع کلاسها نامتوازن باشد، آرگومان class_weight در fit() اجازه میدهد به کلاسهای کمنماینده وزن بیشتری و به کلاسهای پرنماینده وزن کمتری بدهیم. برای وزندهی در سطح نمونه از sample_weight استفاده میشود. اگر هر دو داده شوند، Keras آنها را در هم ضرب میکند. وزن نمونه زمانی مفید است که کیفیت Labelها متفاوت باشد؛ مثلاً نمونههای برچسبخورده توسط متخصص قابلاعتمادتر از نمونههای جمعسپاریشده باشند. برای دادهٔ اعتبارسنجی نیز میتوان وزن نمونه را بهعنوان عضو سوم Tuple مربوط به validation_data داد.
متد fit() یک شیء History برمیگرداند که شامل پارامترهای آموزش در history.params، فهرست Epochها در history.epoch و مهمتر از همه تاریخچهٔ Loss و معیارها در دیکشنری history.history است.
این ترجمه برای استفادهٔ آموزشی تهیه شده است. انتشار عمومی، فروش یا بازنشر آن باید با رعایت حقوق مؤلف و ناشر و اخذ مجوزهای لازم انجام شود.