یادگیری انتقال و تنظیم دقیق شبکه های عصبی پیچشی
یادگیری انتقال در مباحث یادگیری ماشین به این اشاره دارد که دانش کسب شده توسط ماشین ذخیره میشود و سپس از این دانش ذخیره شده برای حل مسائل متفاوت اما مرتبط دیگر استفاده میشود.
میدانیم که استفاده از معماریهای طراحی شده توسط تیمهای تحقیقاتی و بهرهگیری از توانایی آنها برای پیشبینی و کسب نتایج بهتر در مدلهای یادگیری عمیق وجود دارد.
با اینکه آموزش شبکههای عصبی کار زمانبَری است؛ خوشبختانه، امروزه راههایی برای صرفهجویی در زمان ارائه شده که در ادامه به آنها اشاره میکنیم:
• معماری شبکه عصبی را تعریف کنید.
• از ابتدا به آموزشِ آن بپردازید.
قبلاً هم با راهکارهای اجتناب از تعریفِ معماری آشنا شدهایم. در همین راستا، میتوانیم از معماریهای ازپیش تعریف شدهای استفاده کنیم که کارآیی خوبی دارند. از جمله این معماریها میتوان به ResNet ،AlexNet ،VGG ،Inception ،DenseNet و… اشاره کرد.
آیا از همان ابتدا میتوان آموزش این معماریها را نادیده گرفت؟
مقداردهی اولیه به شبکههای عصبی معمولاً با وزنهای تصادفی صورت میگیرد. وزنها بعد از چند دوره به مقداری میرسند که فرصتِ دستهبندی مناسب تصاویر ورودی را به ما میدهند.
اگر مقداردهی وزنها به گونهای تعیین شود که میدانیم تاثیر خوبی در دستهبندی مجموعهداده معینی خواهند داشت، چه اتفاقی میافتد؟ در این صورت، از مجموعهداده بزرگی که برای آموزش شبکه از صفر، نیاز داشتیم، بینیاز میشویم. به علاوه، دیگر نیاز نیست چندین دوره صبر کنیم تا وزنها به مقادیر مناسبی برسند و عملیات دستهبندی اجرا شود. فرایند مقداردهیِ اولیه باعث سهولت در این کار میشود.
اجازه دهید نحوه انجام این کار را با «روشهای انتقال یادگیری» و «تنظیم دقیق» بررسی کنیم.
یادگیری انتقال
شبکه «VGG16» را به عنوان مثال در نظر بگیرید که در مجموعهداده ImageNet آموزش داده شده است. بیایید معماری آن را ببینیم:
میدانیم که «ImageNet» از مجموعهدادهای تشکیل شده که حاوی 1.2 میلیون تصویر برای آموزش، 50.000 تصویر برای اعتبارسنجی و 100.000 تصویر برای آزمایش است. این مجموعهداده در 1000 دسته قابلدسترس است.
حالا فرض کنید میخواهیم شبکه «VGG16» را در مجموعهداده دیگری به کار بگیریم و مجموعهداده منتخبِ ما «CIFAR-10» باشد، چطور میتوانیم این کار را انجام دهیم؟ اگر طرح کلی شبکه عصبی پیچشی (Convolutional Neural Network یا CNN) را به خاطر داشته باشید، میدانید که یک استخراجکنندۀ ویژگی feature extractor و یک دستهبندیکننده classifier داریم:
چه اتفاقی میافتد اگر لایۀ آخر «VGG16» را با لایهای جایگزین کنیم که به جای 1000 احتمال فقط 10 احتمال میگیرد؟ به این ترتیب، میتوانیم از تمام دانشی که «VGG16» در ImageNet آموزش دیده برای حل مسئله خودمان استفاده کنیم.
همانطور که قبلاً مشاهده شد، ابتدا باید مرحله دستهبندی را تغییر دهیم تا لایۀ آخر یکی از 10 نورون باشد (CIFAR 10
دارای 10 دسته میباشد). سپس باید شبکهای را مجددا آموزش دهیم که امکان تغییر وزن لایههای کاملا متصل fully connected layer را فراهم کند.
به همین منظور، مقداردهی اولیه به وزنهای شبکه با استفاده از وزن حاصل از «ImageNet» انجام میشود. پس از آن، همه لایههای پیچشی و «max-pooling» فریز میشوند تا وزن آنها تغییر پیدا نکنند. در چنین شرایطی، فقط لایههای کاملا متصل قابل بروزرسانی هستند و به به محض اینکه این کار به انجام رسید، میتوان پروسه آموزش مجدد را آغاز نمود.
پس میتوان از مرحله استخراج ویژگی در شبکه به خوبی استفاده و فقط دستهبندیکنندۀ نهایی را بهطور دقیق تنظیم کرد تا بتواند به شکل بهتری با مجموعهدادۀ ما کار کند. این فرایند را «یادگیری انتقال» نامگذاری کردهاند؛ فرایندی که در آن از از دانش مربوط مسئله دیگری برای حل مسئلهای که خودمان با آن دست و پنجه نرم میکنیم استفاده میشود.
این فرایند با ذخیره کردن خصوصیاتِ حاصل از آخرین لایۀ «max pooling» و جایگذاری دادهها در یک دستهبندیکننده (SVM، logreg و…) انجامپذیر است. بگذارید مراحل انجام این کار را با یکدیگر بررسی کنیم:
اجرای «Keras»
ابتدا باید به بارگذاریِ مجموعهداده و کتابخانههای ضروری بپردازیم و ابعاد آن را مطابق با «VGG16» به حداقل برسانیم:
import tensorflow as tf
from keras import callbacks
from keras import optimizers
from keras.engine import Model
from keras.layers import Dropout, Flatten, Dense
from keras.optimizers import Adam
from keras.applications import VGG16
from keras.datasets import cifar10
from keras.utils import to_categorical
import numpy as np
(input_shape = (48, 48, 3
()X_train, y_train), (X_test, y_test) = cifar10.load_data)
(Y_train = to_categorical(y_train
(Y_test = to_categorical(y_test
تغییر اندازه مجموعه آموزش:
[]=X_train_resized
:for img in X_train
X_train_resized.append(np.resize(img, input_shape) / 255)
X_train_resized = np.array(X_train_resized)
print(X_train_resized.shape)
تغییر اندازه مجموعه آزمایش:
[]=X_test_resized
:for img in X_test
(X_test_resized.append(np.resize(img, input_shape) / 255
(X_test_resized = np.array(X_test_resized
(print(X_test_resized.shape
ساختن مدل پایه:
(base_model = VGG16(weights=’imagenet’, include_top=False, input_shape=input_shape
()base_model.summary
ما تمام لایهها در مدل پایهمان را فریز میکنید تا در مرحله آموزش تغییر نیابند، به عبارت دیگر، میخواهیم استخراجکننده ویژگیمان تغییر نکند –> یادگیری انتقال
:for layer in base_model.layers
layer.trainable = False
(print(‘Layer ‘ + layer.name + ‘ frozen.’
لایه آخر مدلمان را انتخاب میکنیم و آن را به طبقهبندیمان اضافه میکنیم:
last = base_model.layers[-1].output
(x = Flatten()(last
(x = Dense(1000, activation=’relu’, name=’fc1′)(x
(x = Dropout(0.3)(x
(x = Dense(10, activation=’softmax’, name=’predictions’)(x
(model = Model(base_model.input, x
مدل را کامپایل میکنیم:
([‘model.compile(optimizer=Adam(lr=0.001), loss=’categorical_crossentropy’, metrics=[‘accuracy
–
()model.summary
آموزش را شروع میکنیم:
epochs = 10
batch_size = 256
آموزش میدهیم:
,model.fit(X_train_resized, Y_train
,batch_size=batch_size
,validation_data=(X_test_resized, Y_test)
(epochs=epochs
دقت و هزینه را برای مجموعه آزمایش محاسبه میکنیم:
(scores = model.evaluate(X_test_resized, Y_test, verbose=1
([print(‘Test loss:’, scores[0
([print(‘Test accuracy:’, scores[1
اصلاً مجبور به آموزش نبودیم. اتفاقا نتایج بدی هم به دست نیامد، اما این مسئله را به خاطر داشته باشید که اگر این کار را به صورت تصادفی انجام میدادیم، احتمال اینکه به درستی انجام شود به 10 درصد کاهش پیدا میکرد، چرا که 10 دسته داشتیم.
هرچقدر میزان شباهت مجموعهداده ما و مجموعهدادهای که در ابتدا، شبکه به وسیله آن آموزش دیده بیشتر باشد، امکان حصولِ نتایج بهتر نیز افزایش پیدا میکند. حالا اگر مجموعهدادۀ ما ارتباطی با «ImageNet» نداشته باشد یا بخواهیم نتایج را به شکل چشمگیری بهبود ببخشیم، باید چه رویکردی داشته باشیم؟ انجام این کار با تنظیم دقیق میسر میشود.
تنظیم دقیق
در فرایند تنظیم دقیق، ابتدا آخرین لایه را عوض میکنیم تا آن را با دستههای موجود در مجموعهداده خودمان تطبیق دهیم. این کار قبلاً هم در خصوص یادگیری انتقال انجام شده است. به علاوه، لایههای شبکۀ دلخواهمان را نیز مجدداً آموزش میدهیم.
معماری «VGG16» را به خاطر آورید:
در مثال قبل فقط لایههای مرحلۀ دستهبندی را تغییر دادیم، اما دانشی که شبکه در هنگام استخراج خصوصیات (الگوها) در کار قبلی بدست آورده بود را تغییر ندادیم. وزنها با توجه به کار قبلی بارگذاری میشوند. در تنظیم دقیق، نیازی نیست خودمان را فقط محدود به آموزشِ مجددِ مرحله دستهبندی کنیم (یعنی لایههای کاملاً متصل)؛ کاری که باید انجام دهیم این است که مرحله استخراج ویژگی را هم مجدداً آموزش دهیم (یعنی لایههای پیچشی و ادغام pooling layer).
باید این نکتۀ مهم را به خاطر سپرد که لایههای نخست در شبکههای عصبی به شناساییِ مجموعهدادههای کلیتر و سادهتر میپردازند. هر چه بیشتر در مرحله معماری پیش برویم، الگوهای پیچیدهتری مورد شناسایی قرار میگیرند. بنابراین، باید زمینه را برای آموزش مجدد آخرین بلوک پیچش و ادغام (pooling) فراهم کنیم. چه زمانی باید اقدام به تنظیم دقیق و یادگیری انتقال کرد؟ از کجا باید تشخیص داد که چه لایهای نیاز به آموزش مجدد دارد؟
اولین کار این است که یادگیری انتقال را انجام دهیم؛ نباید شبکهمان را مجدداً آموزش دهیم. اقدام بعدی، آموزش مجدد مرحله دستهبندی است. بعد از آن میتوان به آموزش مجدد بلوک پیچشی هم پرداخت. اگر این مراحل را رد کنیم، اکثر مواقع به نتایج مناسبی برای مسئله خواهیم رسید. البته به نوع مسئلهای که داریم هم بستگی دارد. اگر:
• مجموعهداده جدید کوچک است و به مجموعهداده اصلی شباهت دارد: باید جانب احتیاط را در هنگام تنظیم دقیق رعایت کنید؛ شاید بهتر است خصوصیات لایۀ آخرِ مرحله پیچشی را انتخاب و از ماشین بردار پشتیبانSupport Vector Machine (SVM) (SVM) یا دستهبندیکننده خطّی استفاده کنید.
• مجموعهداده جدید بزرگ است و به مجموعهداده اصلی شباهت دارد: داشتن دادههای بیشتر به این معنا نیست که دچار بیشبرازش Over-fitting خواهیم شد؛ بنابراین، با اطمینان بیشتری میتوان فرایند تنظیم دقیق را انجام داد.
• مجموعهداده جدید کوچک است و تفاوت زیادی با مجموعهداده اصلی دارد: بهترین کار این است که از ویژگیهای لایه ابتدایی مرحله پیچشی استفاده کنیم. سپس میتوان از دستهبندیکننده خطی استفاده کرد.
• مجموعهداده جدید بزرگ است و تفاوت زیادی با مجموعهداده اصلی دارد: باید از ابتدا به آموزشِ آن پرداخت. با این حال، توصیه میشود که وزنها را با ImageNet مقداردهی اولیه کنید.
در هنگام استفاده از هر یک از این روشها، باید به محدودیتهای احتمالیِ مدلهای از پیش آموزش یافته هم توجه داشته باشید. برای مثال، شاید این مدلها به حداقل اندازه عکس احتیاج داشته باشند. بنابراین در هنگام آموزش مجدد شبکهها، معمولا نرخ یادگیری learning rate را پایین انتخاب میکنیم، زیرا ما با مقداردهی اولیهای از وزنها شروع کردهایم که فکر میکنیم مقادیر مناسبی هستند.
تنظیم دقیق «Keras»
مثال تنظیم دقیق، طبقهبندی CIFAR10 با استفاه از VGG16، ابتدا کتابخانههای ضروری را اضافه میکنیم:
import tensorflow as tf from keras import callbacks from keras import optimizers from keras.engine import Model from keras.layers import Dropout, Flatten, Dense from keras.optimizers import Adam from keras.preprocessing.image import ImageDataGenerator from keras.applications import VGG16 from keras.datasets import cifar10 from keras.utils import to_categorical import numpy as np
ابتدا دیتاست را لود میکنیم، سپس ابعاد آن را برای استفاده VGG16 تغییر میدهیم (48, 48, 3):
(input_shape = (48, 48, 3 ()(X_train, y_train), (X_test, y_test) = cifar10.load_data (Y_train = to_categorical(y_train (Y_test = to_categorical(y_test resize train set # []=X_train_resized :for img in X_train (X_train_resized.append(np.resize(img, input_shape) / 255 (X_train_resized = np.array(X_train_resized (print(X_train_resized.shape
تغییر اندازه مجموعه آزمایش:
[]=X_test_resized :for img in X_test (X_test_resized.append(np.resize(img, input_shape) / 255 (X_test_resized = np.array(X_test_resized (print(X_test_resized.shape
مدل پایه را میسازیم:
(base_model = VGG16(weights='imagenet', include_top=False, input_shape=input_shape ()base_model.summary
اجازه میدهیم که کانولوشن آخر و مراحل طبقهبندی آموزش ببینند:
:for layer in base_model.layers :'if layer.name == 'block5_conv1 break layer.trainable = False ('.print('Layer ' + layer.name + ' frozen
بند خودمان را به لایه آخر مدل اضافه میکنیم:
last = base_model.layers[-1].output
(x = Flatten()(last
(x = Dense(1000, activation=’relu’, name=’fc1′)(x
(x = Dropout(0.3)(x
(x = Dense(10, activation=’softmax’, name=’predictions’)(x
(model = Model(base_model.input, x
مدل را کامپایل میکنیم:
(['model.compile(optimizer=Adam(lr=0.001), loss='categorical_crossentropy', metrics=['accuracy
میتوانیم ساختار جدید مدل را ببینیم
()model.summary
آموزش را شروع میکنیم:
epochs = 15 batch_size = 256
مدل را آموزش میدهیم:
,model.fit(X_train_resized, Y_train ,batch_size=batch_size (,validation_data=(X_test_resized, Y_test (epochs=epochs
دقت و هزینه را برای مجموعه آزمایش محاسبه میکنیم
(scores = model.evaluate(X_test_resized, Y_test, verbose=1 ([print('Test loss:', scores[0 ([print('Test accuracy:', scores[1