تعمیم پذیری و بهینهسازی : تکنیکهایی برای جلوگیری از بیشبرازش و کمبرازش
محور اصلی یادگیری ماشین، برقرای توازن درست بین بهینهسازی optimization و تعمیم پذیری generalization است. منظور از بهینهسازی، تنظیم مدل برای دستیابی به بهترین عملکرد ممکن است؛ تعمیم پذیری به عمومیسازی مدل اشاره دارد به نحوی که بتواند روی دادههای جدید نیز عملکرد خوبی داشته باشد. همانطور که میبینید، مفاهیم بهینهسازی و تعمیم پذیری با یکدیگر همبستگی دارند.
بدیهی است وقتی مدل هنوز در حال آموزش است، یعنی زمانی که شبکه هنوز تمام الگو را به صورت کامل مدلسازی نکرده، به صورت طبیعی منجربه خطا کمتر بر روی دادههای آموزش (و همچنین منجر به خطای کمتر بر روی دادههای تست) میگردد؛ که تحت عنوان کمبرازش underfitting شناخته میشود. اما بعد از چند دور آموزش، تعمیم پذیری دیگر بهبود نمییابد و در نتیجه، مدل شروع به یادگیری الگویی برای برازش دادههای آموزشی میکند؛ به این قضیه بیشبرازش Overfitting گفته میشود. بیشبرازش چیز خوبی نیست، چون به مدل اجازه نمیدهد عملکرد خوبی روی دادههای جدید داشته باشد.
در این نوشتار چند تکنیک برای مقابله با مشکل بیشبرازش معرفی میکنیم.
افزایش تعداد دادههای آموزشی
یک راه برای حل مشکل بیشبرازش، داشتن بینهایت دادهی آموزشی است! بیشتر بودن دادههای آموزشی به معنی تعمیم پذیری بهتر مدل است. اما افزایش دادههای آموزشی همیشه امکانپذیر نیست و گاهی باید با همان مجموعه دادهای که در دست دارید کار خود را انجام دهید. در این صورت، بهتر است تنها روی الگوهای اصلی تمرکز کنید؛ این الگوها تعمیم پذیری کلی خوبی به شما ارائه میدهند.
کاهش اندازهی شبکه
آسانترین راه برای جلوگیری از بیشبرازش، کاهش اندازهی مدل، یعنی کاهش تعداد لایهها یا گرههای موجود در لایههای آن است؛ اندازه را با نام «ظرفیت مدل» نیز میشناسند. به صورت تئوری، هر چه ظرفیت مدل بیشتر باشد، یادگیری بهتر اتفاق میافتد. اما در این صورت مدل دچار بیشبرازش روی دادههای آموزشی میشود. بنابراین میتوان گفت مدلهای «قویتر» (با ظرفیت بالا) لزوماً تعمیم پذیری بالاتر و به تبع عملکرد بهتری ندارند. متأسفانه یک فرمول جادویی وجود ندارد که بتوان به وسیلهی آن تعداد مناسب لایهها و گرههای مدل را تعیین کرد. کاری که میتوان انجام داد امتحان معماریهای گوناگون برای مدل و ارزیابی آنهاست تا زمانی که اندازهی مناسب و ایدهآل پیدا شود.
اینجا برای مثال از دیتاست IMDB استفاده میکنیم؛ ابتدا دادهها را آماده میکنیم:
from keras.datasets import imdb import numpy as np (train_data, train_labels), (test_data, test_labels) = imdb.load_data(num_words=10000) def vectorize_sequences(sequences, dimension=10000): # Padding results = np.zeros((len(sequences), dimension)) for i, sequence in enumerate(sequences): results[i, sequence] = 1. return results # vectorized training data x_train = vectorize_sequences(train_data) # vectorized test data x_test = vectorize_sequences(test_data) # vectorized labels y_train = np.asarray(train_labels).astype('float32') y_test = np.asarray(test_labels).astype('float32')
برای توضیح و نمایش آنچه گفته شد دو مدل با اندازههای مختلف میسازیم؛
مدل بزرگتر (bigger):
from keras import models from keras import layersbigger_model = models.Sequential()bigger_model.add(layers.Dense(16, activation='relu', input_shape=(10000,))) bigger_model.add(layers.Dense(16, activation='relu')) bigger_model.add(layers.Dense(1, activation='sigmoid')) bigger_model.compile(optimizer='rmsprop',loss='binary_crossentropy', metrics=['acc'])
مدل کوچکتر (Smaller):
smaller_model = models.Sequential() smaller_model.add(layers.Dense(4, activation='relu', input_shape=(10000,))) smaller_model.add(layers.Dense(4, activation='relu')) smaller_model.add(layers.Dense(1, activation='sigmoid')) smaller_model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['acc'])
حال میخواهیم توابع زیان اعتبارسنجی را برای هر دو شبکهی کوچک و بزرگ محاسبه کنیم:
با استفاده از این کد میتوانیم یافتهها را به تصویر درآوریم:
bigger_hist = bigger_model.fit(x_train, y_train, epochs=20, batch_size=512, validation_data=(x_test, y_test)) smaller_hist = smaller_model.fit(x_train, y_train, epochs=20, batch_size=512, validation_data=(x_test, y_test))
import matplotlib.pyplot as pltepochs = range(1, 21) bigger_val_loss = bigger_hist.history['val_loss'] smaller_val_loss = smaller_hist.history['val_loss']plt.plot(epochs, bigger_val_loss, 'b+', label='bigger model') plt.plot(epochs, smaller_val_loss, 'bo', label='Smaller model') plt.xlabel('Epochs') plt.ylabel('Validation loss') plt.legend()plt.show()[irp posts=”15709″]
همانطور که مشاهده میکنید، مدل بزرگتر خیلی سریعتر از مدل کوچک شروع به بیشبرازش کرده است، به نحوی که زیان اعتبارسنجی بعد از دور هشتم به میزان چشمگیری افزایش مییابد.
حال برای درک بهتر آنچه گفته شد، مدلی با ظرفیت خیلی بیشتر میسازیم و آن را با مدل بزرگتر (bigger) بالا مقایسه میکنیم. اسم این مدل را مدل خیلی بزرگتر (much bigger model) میگذاریم:
much_bigger_model = models.Sequential() much_bigger_model.add(layers.Dense(512, activation='relu', input_shape=(10000,))) much_bigger_model.add(layers.Dense(512, activation='relu')) much_bigger_model.add(layers.Dense(1, activation='sigmoid')) much_bigger_model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['acc']) much_bigger_model_hist = much_bigger_model.fit(x_train, y_train, epochs=20,batch_size=512, validation_data=(x_test, y_test))
بدین ترتیب میتوانیم مدلها را مقایسه کنیم:
much_bigger_model_val_loss = much_bigger_model_hist.history['val_loss']plt.plot(epochs, bigger_val_loss, 'b+', label='Bigger model') plt.plot(epochs, much_bigger_model_val_loss, 'bo', label='Much Bigger model') plt.xlabel('Epochs') plt.ylabel('Validation loss') plt.legend()plt.show()
نمودار مربوطه چنین شکلی خواهد داشت:
همانطور که مشاهده میکنید، «مدل خیلی بزرگتر» تقریباً در همان ابتدای کار شروع به بیشبرازش میکند.
حال توابع زیان آموزشی این مدلها را با هم مقایسه میکنیم:
bigger_train_loss = bigger_hist.history['loss']much_bigger_model_train_loss = much_bigger_model_hist.history['loss']plt.plot(epochs, bigger_train_loss, 'b+', label='Bigger model') plt.plot(epochs, much_bigger_model_train_loss, 'bo', label='Much Bigger model') plt.xlabel('Epochs') plt.ylabel('Training loss') plt.legend()plt.show()
پس میتوان اینطور نتیجه گرفت که هرچه شبکه بزرگتر باشد، زودتر به زیان آموزشی صفر میرسد. به بیان دیگر، ظرفیت بالاتر به معنای یبشبرازش روی دادههای آموزشی است.
منظمسازی
همانطور که در قسمت قبلی مشاهده کردید، احتمال بیشبرازش در مدلهای کوچک در مقایسه با مدلهای پیچیده، پایینتر است. منظور از مدل ساده، مدلی است که تعداد پارامترهای آن کمتر و بنابراین آنتروپی مقادیر آن در کل پایینتر است. برای حل این مشکل میتوان توزیع وزنها را انطباق داد یا به عبارت دیگر، منظمسازی regularization کرد. این کار اغلب از طریق افزودن توابع زیان به شبکه انجام میشود. دو نوع منظمسازی وجود دارد:
- منظمسازی L1: تابع هزینهی اضافه شده متناسب با مقدار قدر مطلق ضرایب وزنهاست.
- منظمسازی L2: تابع هزینهی اضافه شده متناسب با مربع مقادیر ضرایب وزنهاست.
همانطور که در کد پایین مشاهده میکنید، منظمسازی یکی از پارامترهای Keras است:
from keras import regularizers l2_model = models.Sequential() l2_model.add(layers.Dense(16, kernel_regularizer=regularizers.l2(0.001), activation='relu', input_shape=(10000,))) l2_model.add(layers.Dense(16, kernel_regularizer=regularizers.l2(0.001), activation='relu')) l2_model.add(layers.Dense(1, activation='sigmoid'))l2_model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['acc'])
با استفاده از این کد میتوان عملکرد را مشاهده کرد:
l2_model_hist = l2_model.fit(x_train, y_train, epochs=20, batch_size=512, validation_data=(x_test, y_test))
سپس بدین طریق نمودار مربوطه را رسم میکنیم:
l2_model_val_loss = l2_model_hist.history['val_loss']plt.plot(epochs, bigger_val_loss, 'b+', label='Bigger model') plt.plot(epochs, l2_model_val_loss, 'bo', label='L2-regularized model') plt.xlabel('Epochs') plt.ylabel('Validation loss') plt.legend() plt.show()[irp posts=”15659″]
همانطور که مشاهده میکنید، مدل با منظمسازی L2 در مقایسه با مدل بزرگتر (bigger در قسمت قبلی) کمتر مستعد بیشبرازش است.
استفاده از تکنیک Dropout
Dropout یکی از کارآمدترین روشهای منظمسازی به شمار میرود. Dropout را میتوان صفر کردن به صورت تصادفی یا خارج کردن برخی از ویژگیهای لایه تعریف کرد که به صورت تصادفی و در طول فرآیند آموزش انجام میشود. نکتهی کلیدی که باید به آن توجه داشت این است که dropout را تنها میتوان هنگام آموزش انجام داد؛ زیرا در مرحلهی آزمایش، مقادیر حذف نمیشوند، بلکه تنها میتوان آنها را مقیاسبندی کرد. نرخ dropout معمول بین 2/0 و 5/0 است.
در keras میتوانید تابع dropout را در لایهی dropout که بلافاصله بعد از لایهی موردنظر قرار میگیرد، اضافه کنید. به این کد توجه کنید:
model.add(layers.Dropout(0.5)) drop_model = models.Sequential() drop_model.add(layers.Dense(16, activation='relu', input_shape=(10000,))) drop_model.add(layers.Dropout(0.5)) drop_model.add(layers.Dense(16, activation='relu')) drop_model.add(layers.Dropout(0.5)) drop_model.add(layers.Dense(1, activation='sigmoid'))drop_model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['acc'])
نمودار مربوطه را رسم میکنیم:
drop_model_val_loss = drop_model_hist.history['val_loss'] plt.plot(epochs, bigger_val_loss, 'b+', label='Original model') plt.plot(epochs, drop_model_val_loss, 'bo', label='Dropout-regularized model') plt.xlabel('Epochs') plt.ylabel('Validation loss') plt.legend() plt.show()
جمعبندی
برای جلوگیری از مشکل بیشبرازش در شبکههای یادگیری عمیق، چندین روش کارآمد وجود دارد:
- افزایش شمار دادههای آموزشی
- ساده کردن شبکه یا تنظیم ظرفیت آن (اگر ظرفیت بیشتر از حد لازم باشد، احتمال بیشبرازش افزایش مییابد)
- منظمسازی
- استفاده از تکنیک dropout