محمد جواد نجادی
محمد جواد نجادی
خواندن ۳۱ دقیقه·۸ ماه پیش

مرحله به مرحله برای ساختن یک شبکه عصبی مصنوعی و دیدن چگونگی رخ دادن overfitting با MNIST و معماری CNN

# دیدن و لمس کردن همه مراحل و مشکلات با کد های تست شده و نمودارهای یادگیری

ساختن و ساختن و ساختن ...

این تصویر توسط Ideogram خلق شده است
این تصویر توسط Ideogram خلق شده است


برای ساختن یک شبکه عصبی مصنوعی اولین مرحله دسترسی به داده ها ست. معمولا داده ها در دنیای واقعی نرمال شده نیستند و ممکن است دارای داده های پرت یا داده های null باشند. به همین خاطر ما برای ساختن یک مدل علاوه بر اینکه به داده ها نیاز داریم باید داده ها را پیرایش کنیم. تا مدل به خوبی آنها را ببیند و پردازش کند. بعد از این مراحل است که می توانیم از کتابخانه های بسیار زیاد و متنوع و کارآمدی که وجود دارد استفاده کنیم تا مدل خود را بسازیم.

در این مقاله می خواهیم مرحله به مرحله ساختن یک مدل را یاد بگیریم. از فراخوانی یا دانلود دیتاست تا Normalization و انتخاب معماری مناسب برای ساختن مدل. همچنین کامل متوجه شویم overfitting چطور رخ میده چطور متوجه آن شویم؟ با چه راهکاری می توانیم آن را برطرف کنیم؟ نه به صورت تئوری بلکه با کد ها و نمودارهای یادگیری عملا خواهیم دید. رویکرد مقایسه ای نمودار های مختلف که با Hyperparameter tuning های مختلف به دست آمده اند باعث درک بهتر مسئله خواهد شد. در این کاوش هیجان انگیز با من همراه باشید.

" تمام کد های استفاده شده در این مقاله و نمودار هایی که خواهید دید. در colab.research.google.com تست شده اند و از آنها خروجی گرفته شده است. "

مختصری در مورد MNIST

به جرات می توان گفت امکان ندارد شخصی در همان ابتدای آموختن یادگیری ماشین دیتا ست MNIST را نشناسد. مانند print(“Hello world”) برای شروع یادگیری برنامه نویسی است. MNIST یکی از دیتاست های مرجع در زمینه یادگیری ماشین و بینایی کامپیوتر است که شامل تصاویر اعداد دست نویس از 0 تا 9 می باشد. اما MNIST از کجا آمده ؟

در واقع MNIST از مجموعه داده NIST به وجود آمده است. مجموعه داده آموزشی NIST از کارمندان اداره سرشماری آمریکا گرفته شده است. در حالی که مجموعه داده آزمایشی از دانش آموزان دبیرستانی آمریکایی گرفته شده است. MNIST در اوایل دهه 1980 توسط Yann LeCun، Corinna Cortes و Christopher J.C. Burges ایجاد شد. MNIST دارای 60000 تصویر Train و 10000 تصویر Test است. تصاویر در ابعاد 28x28 پیکسل ذخیره شده‌اند و خاکستری و تک کانال هستند.

دیتاست Extended MNIST (EMNIST) مجموعه داده جدیدتری است که توسط NIST به عنوان جانشین (نهایی) MNIST توسعه و منتشر شده است. MNIST فقط شامل تصاویر ارقام دست نویس می شد. EMNIST شامل تمام تصاویر پایگاه داده ویژه NIST 19 است که یک پایگاه داده بزرگ از حروف بزرگ و کوچک دست‌نویس و همچنین اعداد است. تصاویر در EMNIST مانند تصاویر MNIST به همان فرمت پیکسلی (28x28) تبدیل شدند. بر این اساس، ابزارهایی که با مجموعه داده‌های قدیمی‌تر و کوچک‌تر MNIST کار می‌کنند، احتمالاً بدون تغییر با EMNIST کار می‌کنند.

در سال های اخیر مجموعه‌ داده های جدید و بزرگتری مانند CIFAR-10، CIFAR-100، و ImageNet به عنوان مجموعه‌های داده مورد استفاده قرار گرفته‌اند که تنوع داده و پیچیدگی بیشتری دارند که باعث می شود برای مسائل پیچیده و تسک های کنونی مناسب تر باشند. با این حال MNIST همچنان به عنوان یکی از مجموعه‌های داده اولیه و مهم برای یادگیری ماشینی و بینایی کامپیوتری مورد استفاده قرار می‌گیرد. در این مقاله ما می خواهیم MNIST را بررسی کنیم و مرحله به مرحله برای آموزش یک مدل شبکه عصبی با آن پیش برویم و موانع و مشکلاتی که در آن وجود دارد رو بررسی کنیم.

صدا زدن مهم ترین کتابخانه ها

اولین مرحله برای شروع فراخوانی کتابخانه های مهمی است که فارغ از اینکه چه معماری یا چه روشی برای ساختن مدل خود داریم یا حتی چه دیتا ستی استفاده کنیم. این کتابخانه ها در مدل ضروری هستند مثلا می دانیم که برای رسم نمودار ها ` matplotlib ` را لازم داریم و چون با ماتریس و آرایه ها سر و کار داریم ` Numpy ` را احتیاج داریم و برای این دیتاست من از `keras` و` Tensorflow ` می خواهم استفاده کنم بنابراین:

# calling important Library
import tensorflow as tf
import matplotlib.pyplot as plt
import numpy as np

فراخوانی دیتاست

برای استفاده از دیتاست MNIST اول باید بتوانیم آن را فراخوانی یا دانلود کنیم. MNIST در مخازن keras وجود دارد و فقط کافی ست که با متد" () load_data " آن را فراخوانی کنیم. و داده های تست و ترین و Shape هر کدام را مشخص می کنیم.

# calling the dataset
from tensorflow.keras.datasets import mnist
# Access the training and testing data
(X_train, y_train) , (X_test, y_test) = mnist.load_data()
(X_train.shape, y_train.shape) , (X_test.shape, y_test.shape)

پراسس کردن داده ها (Data processing)

مرحله مهم قبل از آموزش مدل مرحله Data processing است. تعداد و مراحل مختلف آن بستگی به نوع دیتاست دارد. بعضی از آنها مانند همین دیتا ست MNIST خیلی نیاز به processing ندارند اما داده های دنیای واقعی به این صورت نیست روشهای مختلف Data processing را با هم بررسی می کنیم.

نرمال سازی (Normalization)

در شبکه های عمیق برای رسیدن به یک وضعیت پایداری که مدل بتواند الگوهای داده ها را یاد بگیرد و پیش بینی دقیقی ارائه کند. اصطلاحا مدل باید همگرا شود. در واقع همگرایی یعنی اینکه وزن ها و پارامترهای شبکه طوری تنظیم شوند که تابع هزینه کمترین مقدار شود . دو مشکل اصلی در همگرایی مدل های عمیق انفجار و محو گرادیان است و زمانی رخ می دهند که گرادیان ها بسیار بزرگ یا بسیار کوچک شوند.

تذکر : اگر Normalization انجام نشود یکی از مشکلاتی که پیش می آید مشکلات همگرایی است که ممکن است مدل دیرتر یا حتی به طور کامل همگرا نشود.

روش های Normalization زیادی وجود دارد. از جمله روش Pixel-wise (هر پیکسل از تصویر بر اساس میانگین و واریانس پیکسل‌های مشابه در دیتاست نرمال‌سازی می‌شود) یا Local Contrast (برای تصاویری با تفاوت‌های نوری محلی مفید است. هر ناحیه از تصویر متناسب با نواحی مشابه آن در تمام داده‌ها نرمال‌سازی می‌شود) و Robust Scaling (از میانه و چارچوب داده برای مقیاس دادن استفاده می‌کند، بنابراین در مقابل داده‌های پرت مقاومت دارد) یا Unit Vector Scaling (هر سطر از داده‌ها به نحوی استانداردسازی می‌شود که مجموع مربعات مقادیر آنها یک شود)

کد زیر برای Data processing است. و معمولا قبل از مرحله Normalization انجام میشه. تابع `reshape`، ابعاد Data را برای ورود به شبکه عصبی تغییر می‌دهد. به این صورت که تصاویر به ابعاد 4D با اندازه (تعداد نمونه‌ها، ارتفاع تصویر، عرض تصویر، تعداد کانال) تغییر شکل می ‌یابند. در MNIST ابعاد تصاویر (60000, 28, 28) است که به ابعاد (60000, 28, 28, 1) تغییر شکل می‌یابند. این کار برای تطابق با اندازه ورودی لایه‌های شبکه عصبی ضروری است. مقادیر پیکسل‌های تصاویر که از 0 تا 255 متغیرند، با تقسیم بر 255 به مقادیر اعشاری بین 0 و 1 نرمال می‌شوند. Normalization به مدل کمک می‌کند تا مشکلات مربوط به آموزش غیر منظم (مانند افزایش میزان لرزش در توزیع وزن‌ها) را کاهش دهد.

# Preprocess the data
X_train = X_train.reshape((60000, 28, 28, 1))
X_test = X_test.reshape((10000, 28, 28, 1))

برای دیتاست MNIST، نرمال‌سازی ممکن است مفید باشد، اما اغلب نیازی به آن نیست. اما اگر بخواهیم عملکرد مدل خود ر ا بهبود دهیم می توانیم داده ها را نرمالسازی کنیم. برای نرمال‌سازی داده‌های تصویری، به غیر از روشهای بالا دو روش مهم دیگر هم وجود دارد. یکی از آن ها Standardization با استفاده از Z-score هست که میانگین و واریانس داده ها نرمال سازی میشن به طوریکه میانگین برابر با صفر و واریانس برابر 1 باشد. در کد زیر ابتدا داده ها را به flote32 تبدیل می کنیم. البته می توانستیم به flote8/16/64 هم تبدیل کنیم. `float` نوع داده است که مشخص می‌کند. بعد آن را با استفاده از روش Z-score نرمالسازی می کند و مقدار میانگین و واریانس را محاسبه می کنیم :

# Convert data to float32
X_train = X_train.astype(np.float32)
X_test = X_test.astype(np.float32)
# Calculate mean and standard deviation of the training data
mean = np.mean(X_train) # Mean
std = np.std(X_train) # Variance
# Apply Z-score normalization to the training data
X_train_normalized = (X_train - mean) / std
# Apply Z-score normalization to the test data using the mean and standard deviation of the training data
X_test_normalized = (X_test - mean) / std
# Display the dimensions of the standardized data
print(&quotDimensions of X_train: &quot, X_train_normalized.shape)
print(&quotDimensions of X_test: &quot, X_test_normalized.shape)

روش دیگر Min-Max Scaling است که داده‌ها به یک بازه مشخصی معمولاً [0, 1] تبدیل می‌شوند. روش Min-Max Scaling بیشتر برای داده‌هایی که دارای توزیع نرمال یا به شکلی مشابه آن هستند مفید است. زمانی که داده‌ها توزیع غیر نرمالی دارند، مانند MNIST روش Z-score معمولاً مفیدتر است. چون مقدار داده‌ها را بر اساس میانگین و واریانس نرمالسازی می‌کند. پس روش Min-Max Scaling برای MNIST ممکن است نتایج نامناسبی را تولید کند.

نکته مهم : توجه داشته باشید که باید از یکی از روش های نرمالسازی در مدلمان استفاده کنیم .اگر هر دو را با هم استفاده کنیم فقط باعث پیچیدگی مدل می شود که ممکن است باعث رخ دادن overfitting شود.

دیدن داده ها

در این بخش می خواهیم ابتدا یک نمونه از داده ها را ببینیم. برای این کار می توانیم از کتابخانه‌های متداول مانند matplotlib یا opencv استفاده کنیم. این کد را با استفاده از کتابخانه matplotlib نوشته ایم :

import matplotlib.pyplot as plt
# Display an image from the data along with its label
index = 0 # Index of the image to display
plt.imshow(X_train_normalized[index], cmap='gray') # Display the image
plt.colorbar() # Display the color bar
plt.title(f'Label: {y_train[index]}') # Title with the label
plt.show() # Show the image

همانطور که می دانیم پایتون " ایندکس صفر" است در این کد با تابع imshow اولین عکس داده های آموزشی را به همراه Label آن می توانیم ببینیم. با تغییر index هر کدام از اعداد که قابل نمایش اند.مثلا index = 19 عدد بیستم MNIST را به ما نشان می دهد. همچنین shape تصویر که 28 x 28 است و تصویر را هم به رنگ خاکستری انتخاب کرده ایم نتیجه آن تصویر زیر است :

این عکس یک نمونه از دیتاست MNIST است که توسط کد من پردازش شده است و نشان‌دهنده یکی از اعداد این دیتاست می‌باشد. مدلی که با استفاده از این داده‌ها آموزش داده شده، توانایی تشخیص اعداد را داردن تصویر با کد بالا در پایتون توسط من ایجاد شده است
این عکس یک نمونه از دیتاست MNIST است که توسط کد من پردازش شده است و نشان‌دهنده یکی از اعداد این دیتاست می‌باشد. مدلی که با استفاده از این داده‌ها آموزش داده شده، توانایی تشخیص اعداد را داردن تصویر با کد بالا در پایتون توسط من ایجاد شده است

تصمیم گیری

بعد از انجام مراحل Preprocessing باید روش طراحی مدل را انتخاب کنیم. با روش Sequential یا روش Functional API یا حتی روش ترکیبی. انتخاب هر کدام از این دو روش به نوع مسئله و داده های Dataset و همچنین نوع معماری بستگی دارد. چرا که نوع معماری در مسئله ما می تواند تعیین کننده نتیجه نهایی باشد. به نظر شما برای MNIST کدام روش و معماری بهتر است ؟

در مرحله Normalization چون MNIST توزیع غیر نرمال دارد. Z-score می تواند بهتر از Min-Max Scaling عمل کند. در مسئله پیش رو که تشخیص اعداد دست نویس است از معماری CNN استفاده می کنیم چرا که برای پردازش تصویر بسیار مناسب است. هر چند که MNIST تصاویر ساده ای دارد و دیتاست پیچیده ای نیست اما استفاده از CNNمی تواند مناسب باشد. از روش Sequential استفاده می کنیم که در اون مدل ها به صورت خطی و لایه به لایه اضافه می شود و ارتباطی بین لایه ها تعیین نمی شود. در صورتی که بخواهیم مدل را پیچیده تر کنیم انتقال از مدل Sequential به مدل Functional API امکان پذیراست. در قسمت بعد به ساختن مدل با روش Sequential می پردازیم . با من همراه باشید.

مرحله1 : ساختن معماری (مدل) (Creat Architecture (model))

بعد از Normalization و انتخاب نوع معماری باید شروع به ساختن مدل کنیم. با توجه به نوع معماری کتابخانه های مهم را فراخوانی می کنیم ` tensorflow.keras.layers ` برای وارد کردن لایه های مختلف در CNN است تا بتوانیم لایه های شبکه را با متد model.add() اضافه کنیم و دیگری ` tensorflow.keras.models ` است که بتوانیم مدل Sequential را بسازیم.

# calling important Library
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Conv2D, MaxPooling2D, Flatten, Dense, Dropout0

از این کتابخانه ها برای اضافه کردن لایه ها به مدل استفاده می کنیم از Conv2D برای اضافه کردن لایه های کانولوشنی و MaxPooling2D برای عملیات pooling استفاده می کنیم.

لایه Flatten معمولاً بعد از لایه‌های کانولوشنی یا Dropout استفاده می‌شود. Dropout (داده های خروجی از آنها چند بعدی است) ، به عنوان ورودی به لایه‌های کاملا متصل (Dense) نیاز به داده‌های یک بعدی دارند. از لایه‌ی Flatten برای تبدیل داده‌های ورودی به یک بردار یک بعدی استفاده می‌شود.

لایه لایه Flatten معمولاً بعد از لایه‌های کانولوشنی یا Dropout استفاده می‌شود. Dropout (داده های خروجی از آنها چند بعدی است) ، به عنوان ورودی به لایه‌های کاملا متصل (Dense) نیاز به داده‌های یک بعدی دارند. از لایه‌ی Flatten برای تبدیل داده‌های ورودی به یک بردار یک بعدی استفاده می‌شود.

لایه Dropout یک روش ارائه شده توسط Geoffrey Hinton و دیگران در مقاله‌ی با عنوان :

عنوانDropout: A Simple Way to Prevent Neural Networks from Overfittingاست که به صورت Random بعضی از ویژگی ها را در مرحله آموزش مدل حذف می کند تا از Overfitting در شبکه‌های عصبی عمیق جلوگیری کند. این لایه به طور معمول بلافاصله پس از لایه‌های کانولوشنی یا pooling قرار می‌گیرد. ( در ادامه مقاله به صورت مفصل به آن خواهیم پرداخت و به صورت شهودی خواهید دید ) Dropout یک روش ارائه شده توسط Geoffrey Hinton و دیگران در مقاله‌ی با عنوان :

هر نورون در لایه‌های کاملا متصل (Dense) با همه نورون های قبلی و بعدی متصل است و یک وزن (W) و یک Bias دارد که نشان دهنده ارتباط بین ورودی این لایه با خروجی لایه قبلی است که معمولا در لایه (های(ممکن است یک شبکه لایه های پایانی متعدد داشته باشد )) پایانی یک شبکه عصبی استفاده می شود.

لایه آخر یا لایه خروجی که معمولا از تابع فعالسازی Softmax در این لایه استفاده می شود. در کد خواهیم دید که output Layers دارای 10 نورون است چون مسئله ما 10 کلاسی هست. اعداد 0 – 9 بنابراین خروجی هر تصویر که می خواهیم در نهایت بررسی کنیم باید عددی بین 0 – 9 تشخیص داده شود.

بعد از وارد کردن کتابخانه های مهم و دانستن اینکه هر لایه چطور عمل می کند مدل را می سازیم که درکد زیر می بینیم :

# Create the model
model = Sequential()
model.add(Conv2D(32, (3, 3), activation='relu', input_shape=(28, 28, 1)))
model.add(MaxPooling2D((2, 2)))
model.add(Conv2D(64, (3, 3), activation='relu'))
model.add(MaxPooling2D((2, 2)))
model.add(Conv2D(64, (3, 3), activation='relu'))
model.add(Flatten())
model.add(Dense(64, activation='relu'))
model.add(Dropout(0.25))
model.add(Dense(10, activation='softmax'))
# Display model summary
model.summary()

همانطور که می بینید ابتدا با() model = Sequential روش مدل را انتخاب کردیم. سپس شروع به اضافه کردن لایه های مختلف می کنیم. لایه اول (28,1, 28) = input_shape نشان از shap داده ها (28x28 (Number of pixcel)) و 1 نشان می دهد داده ها تک کاناله ( خاکستری) هستند.

لایه (32) Conv2D لایه کانولوشنی است که درCNN استفاده می کنیم. نشان دهنده تعداد یال هایی که از این لایه به لایه بعد می رود و (3x3) ابعاد هسته یا فیلتر kernel(Filter) است که اطلاعات را در تصاویر جمع آوری می کند. چون تصاویر ما خاکستری و بسیار کوچک هستند و لبه ها مقادیر صفر دارند. نیاز زیادی به padding نداریم.

بنابراین از MaxPooling2D استفاده می کنیم. وقتی kernel یا فیلترها روی تصاویر pool می کند. مخصوصا برای داده هایی مثل MNIST که بیشترین مقدارهای (255-0) برای ما مهم است. چونکه برای تشخیص اعداد در تصویر هر چه مقادیر بیشتر باشد تشخیص بهتر می شود. چون تصاویر خاکستری هستند و پیکسل هایی که اعداد دست نویس را مشخص می کنند به سمت سفید یعنی مقدار 255 می رود و مقادیر به سمت 0 در واقع قسمت سیاه مانند لبه ها یا پس زمینه است که از اهمیت کمتری برخوردارند.

از توابع فعالسازی مختلفی هم در لایه های مختلف استفاده کرده ایم که هر کدام وظایفی را انجام می دهند. دو لایه کاملا متصل (FC) یا Dense داریم که در اولی تابع فعالساز ReLU استفاده کرده ایم و در لایه دوم که خروجی مدل است از تابع فعالساز softmax که معمولا در لایه آخر خروجی کاربرد دارد. از Dropout با نرخ حذف 0.25 یعنس به صورت Random 25 درصد از ویژگی های ورودی به لایه حذف می شود. هر کدام از لایه ها دارای پارامتر هایی هستند که در مجموع بعد از کامل شدن آن تعداد کامل پارامترهای شبکه را با model.summary() می توانیم ببینیم. در قسمت بعد می خواهیم کامپایل مدل را انجام دهیم.

کامپایل مدل (compile.model)

بعد از ساختن مدل و لایه ها با معماری CNN باید مرحله compile مدل را با استفاده از تابع `compile` برای تنظیم Loss function و Optimizer و انتخاب معیاری های ارزیابی انجام دهیم. در ماشین لرنینگ Loss function نشان‌دهنده‌ی اختلاف بین خروجی مدل و مقدار واقعی است. در همه تسک هایی که داریم چه در دنیای واقعی چه برای حل مسائل Deep Learning یکی از مهم ترین کارها رساندن خطا به کمترین مقدار است. از Optimizer که مسئول بهینه‌سازی و به روزرسانی وزن‌های (W) مدل است و با تنظیم Loss function ، وزن‌های مدل را به گونه‌ای تغییر می دهد که تابع خطا کمینه شود استفاده می کنیم. Optimizer ها انواع مختلفی مانند Adam، SGD و RMSprop دارند و انتخاب درست هر کدام برای وظیفه خاص شما می‌تواند به بهبود عملکرد مدل کمک کند. همانطور که خواهید دید ما در کد زیر از Adam استفاده می کنیم. سپس با انتخاب معیار های مناسب ارزیابی یعنی Metrics ها عملکرد مدل را در مرحله آموزش و ارزیابی بسنجیم.

# Importing necessary libraries for compiling the model
from tensorflow.keras.optimizers import Adam
from tensorflow.keras.losses import SparseCategoricalCrossentropy
# Compiling the model with SparseCategoricalCrossentropy loss function and Adam optimizer
model.compile(loss=SparseCategoricalCrossentropy(),
optimizer=Adam(learning_rate=0.001), metrics=['accuracy'])

محاسبه Loss function فقط با فراخوانی تابع () SparseCategoricalCrossentropy انجام می شود. در بهینه سازی Adam از نرخ یادگیری (learning rate) استفاده کرده ایم. learning rate تعیین می‌کند چه مقداری از گام‌های آموزش به اندازه کافی کوچک باشند که به نقطه بهینه برسیم. در واقع یک Hyperparameters (پارامترهایی که خارج از مدل قرار دارند و باید تنظیم شوند) محسوب می‌شود. انتخاب learning rate مناسب یکی از موارد حیاتی در آموزش یک مدل است. انتخاب مقدار مناسب learning rate می‌تواند از overshooting جلوگیری کند. Overshooting پدیده‌ای در فرایند بهینه‌سازی است که در آن مقدار پارامتر‌ها به شدت جابه‌جا می‌شود. ممکن است باعث کند شدن فرایند بهینه‌سازی شود. متریک accuracy نیز برای ارزیابی دقت مدل در هنگام آموزش استفاده می‌شود.

اگر کامپایل مدل انجام نشود مدل نمی تواند بهینه سازی کند و عملکرد خود را ارزیابی کند. در قسمت بعد وارد مرحله fit کردن مدل می شویم . با من همراه باشید .

فاز آموزش (Training Phase)

در این مرحله مدل را روی داده های train آموزش می دهیم تا یادگیری انجام شود و بدانیم که دقت مدل چقدر است. اصولا هنگام fit کردن مدل اولین کاری که انجام می دهیم مشخص کردن تعداد epochs است. در هر epochs مدل یک بار تمام داده های آموزشی را می بیند تا بتواند الگوهای پیچیده را در داده ها بیاموزد. طبیعتا هر چه تعداد epochs بیشتر شود مدل بهتر یاد میگیرد چون بیشتر می تواند داده را ببیند اما اگر بیش از حد باشد منجر به overfitting می شود. به عبارت دیگر چون مدل بیش از حد به داده های آموزشی همگرا می شود نمی تواند آن ها را به داده جدید تعمیم دهد. به عبارت دیگر مدل داده ها را فقط حفظ می کند.

فیت کردن مدل (model.fit)

برای آموزش مدل از ()model.fit که حاوی متد های یادگیری مدل است استفاده می کنیم. علاوه بر موارد بالا می توان به متد Validation split اشاره کرد. بهترین مقدار validation split بین 0.1 تا 0.3 است. استفاده از داده‌های اعتبارسنجی این امکان را به ما می‌دهد تا عملکرد مدل را در طول آموزش به طور مستقیم بر روی داده‌هایی که مدل قبلاً دیده است، ارزیابی کنیم. در واقع داده های آموزشی را به دو بخش تقسیم میکند. مثلا= 0.2 Validation split یعنی داده های آموزشی به 80 درصد برای آموزش و 20 درصد برای تست تقسیم می شود. این کار را در مدل های قدیمی ماشین لرنینگ هم انجام می دادیم. اگر عملکرد مدل بر روی این داده‌های جداگانه بهبود نیافته باشد، آموزش متوقف می‌شود. این روش به ما کمک می‌کند تا از افزایش بیش از حد تعداد epoch و ایجاد overfitting جلوگیری کنیم.

اینکه در هر epoch داده ها در دسته های چند تایی به ماشین نشان داده شود را با batch_size مشخص می کنیم. پیدا کردن بهترین batch_size تاثیر زیادی بر سرعت و کیفیت آموزش مدل دارد و طبیعتا هر چه بزرگتر باشد بهتر است. اما به علت محدودیت هایی که برای حافظه داریم در خیلی از موارد مخصوصا در پروژه های بزرگ امکان این کار وجود ندارد. در داده‌های متنوع batch_size بزرگتر ممکن است منجر به از دست دادن تطبیق بهتر مدل با داده‌ها شود که منجر به overfitting یا underfitting می شود. برای جلوگیری از آن با روش آزمون و خطا بهترین batch_size را پیدا کنیم یا می توانیم با روش سیستماتیک و اصولی تر مانند جستجوی شبکه ای (Grid Search) یا به روش Validation Data.

در سال 2018 “Yann LeCun” در توییت معروفی نوشت :

"آموزش با دسته‌های کوچک بزرگ برای سلامتی شما ضرر دارد. بیشتر از همه، برای خطای آزمون شما ضرر دارد. دوستان، به دوستان خود اجازه ندهید از دسته‌های بزرگتر از 32استفاده کنند."

به عبارتی به گفته “Yann LeCun” بهترین batch_size = 32 است.

در کد زیر fit کردن مدل را مشاهده می کنید :

# Fit the model
history = model.fit(X_train, y_train, epochs=50, batch_size=32, validation_split=0.2)

فاز تست (Test Phase) model.evaluation

برای ارزیابی دقت بعد از آموزش مدل از model.evaluate استفاده می کنیم :

# Evaluate the model
test_loss, test_acc = model.evaluate(X_test, y_test)
print('Test accuracy:', test_acc)

در کد های بالا مدل را بدون تنظیم خاصی برای Hyperparametersها train کردیم. متد هایی مثل BatchNormalization و Early Stopping یا L2 regularization بعد از اجرای این کد و و تکمیل آموزش مدل Test accuracy: 0.98879998 است. البته این دقت به دست آمده در داده های Train است و هنوز دقت مدل را با داده واقعی نمی دانیم برای اینکه بفهمیم مدل به چه صورت عمل کرده و اینکه overfitting رخ داده است یا خیر از نمودار های Loss Curve و Accuracy Curve استفاده می کنیم که با استفاده از کتابخانه Matplotlib با کد زیر می توان آنها را رسم کرد :

import matplotlib.pyplot as plt
# Create subplots with smaller figure size
fig, axs = plt.subplots(2, 1, figsize=(7, 9))
# Plotting the loss curve
axs[0].plot(history.history['loss'], label='Training Loss')
axs[0].plot(history.history['val_loss'], label='Validation Loss')
axs[0].set_title('Loss Curve')
axs[0].set_xlabel('Epochs')
axs[0].set_ylabel('Loss')
axs[0].legend()
# Plotting the accuracy curve
axs[1].plot(history.history['accuracy'], label='Training Accuracy')
axs[1].plot(history.history['val_accuracy'], label='Validation Accuracy')
axs[1].set_title('Accuracy Curve')
axs[1].set_xlabel('Epochs')
axs[1].set_ylabel('Accuracy')
axs[1].legend()
plt.tight_layout()
plt.show()

نتیجه این کد نمودار زیر است که نشان دهنده overfitting است :

این تصویر با کد بالا در پایتون توسط من ایجاد شده است
این تصویر با کد بالا در پایتون توسط من ایجاد شده است


همانطور که در تصویر می بینید مدل به احتمال قوی overfitting شده. اگر میان Training accuracy و Validation accuracy تفاوت چندانی نباشد، یعنی به هم نزدیک تر باشند نشان می‌دهد که مدل overfitting نداشته است. اما اگر تفاوت قابل توجهی وجود داشته باشد یا اگر Validation accuracy شروع به کاهش کرد. در حالی که Training accuracy افزایش می‌یابد، مشوک به overfitting است. خطوط accuracy باید صاف تر با نوسانات کمتر باشد هر چه نوسانات بیشتر باشد نشان از عدم ثبات در عملکرد مدل است. نشانه overfitting مدل باشد.

حال برای جلوگیری از overfitting باید راهکارهایی ارائه کرد. در قسمت بعد به صورت کامل و در عمل خواهیم دید که اگر overfitting رخ دهد که در بیشتر موارد رخ می دهد مخصوصا اگر مانند مثال بالا از متدهایی برای کاهش overfitting یا بیشتر کردن accuracy استفاده نکنیم چون که به احتمال قوی مدل خود به خود به سمت حفظ کردن داده ها و پایین بودن accuracy خواهد رفت. اصولا مدل های شبکه عصبی تمایل زیادی به حفظ کردن دارند و معمولا در مرحله آموزش داده ها را بسیار خوب یاد می گیرند و تمایل دارند آنها را حفظ کنند.

بنابراین در قسمت بعد کامل و شهودی در دفعات مختلف Hyperparameter tuning با مقایسه نمودارهای accuracy انجام خواهیم داد. با من همراه باشید. قصه از اینجا جذاب تر میشه...

همه مشکلات از overfitting است !!!!!

برای اینکه بتوانیم مدلی را که می سازیم بهبود ببخشیم باید Hyperparameter tuning کنیم. یعنی متد هایی که برای مسئله داریم را با مقدارهای مختلف تست کنیم که Hyperparameter مناسبی را بیابیم که accuracy مدل را افزایش و از overfittingجلوگیری کند. بنابراین تصمیم گرفتم که Hyperparameter tuning را برای متد هایی مانند Early Stopping ,Dropout, batch_size ,learning_rate, L2 regularization را با مقدارهای مختلف تست کنم و اونها رو مستند سازی کنم. کد زیر بهبود یافته کدی است که در بالا برای ساختن مدل دیدیم و حاوی پارامترهایی است که برای Hyperparameter tuning لازم است :

from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Conv2D, MaxPooling2D, Flatten, Dense, Dropout
from tensorflow.keras.layers import BatchNormalization
fromtensorflow.keras.regularizers import l2
# Create the model
model = Sequential()
# Convolutional layers
model.add(Conv2D(32, (3, 3), activation='relu', kernel_regularizer=l2(0.01), input_shape=(28, 28, 1)))
model.add(MaxPooling2D((2, 2))) # Max pooling layer
# Adding Batch Normalization layer with momentum and epsilon settings
model.add(BatchNormalization(momentum=0.9, epsilon=1e-5))
model.add(Conv2D(64, (3, 3), activation='relu', kernel_regularizer=l2(0.01))) # Convolutional layer
model.add(MaxPooling2D((2, 2))) # Max pooling layer
model.add(Conv2D(64, (3, 3), activation='relu', kernel_regularizer=l2(0.01))) # Convolutional layer
model.add(Flatten()) # Flatten layer to convert 2D matrix to a vector
# Fully connected layers
model.add(Dense(64, activation='relu', kernel_regularizer=l2(0.01))) # Dense (fully connected) layer
model.add(Dropout(0.30)) # Dropout layer for regularization
model.add(Dense(10, activation='softmax')) # Output layer with softmax activation for classification
# Display model summary
model.summary() # Print model architecture and parameters

بعد از اجرای این کد می توانیم summary از معماری ای که با CNN ساخته ایم را مشاهده کنیم. تعداد پارامترهای مدل و اینکه درهر لایه چه تعداد پارامتر داریم و اطلاعات ارزشمند دیگر. بعد از اجرای این کد مرحله compile.model را به همان صورت که در بالا ذکر شد انجام می دهیم.

در مرحله بعد می خواهیم Early Stopping را به مدل اضافه کنیم. کد زیر را ببینید:

# Importing the EarlyStopping callback from TensorFlow Keras
from tensorflow.keras.callbacks import EarlyStopping
# Creating an EarlyStopping callback object with a patience of 5 epochs
es = EarlyStopping(patience=5)

دراین کد Stopping Early را از کتابخانه tensorflow.keras.callbacks فراخوانی می کنیم و مقدار صبر (patience) برابر با 5 قرار می دهیم . callbacks این امکان به مدل را می‌دهد که در صورتی که دقت آزمایشی (validation accuracy) بعد از 5 epoch افزایش نیابد، فرآیند آموزش متوقف شود. بهترین مقدار epochs را می توان به صورت رندوم تست مشخص کرد. اما روش اصولی آن Early Stopping است. روش Early Stopping به مدل اجازه می‌دهد تا در هر epoch ، عملکرد خود را بر روی یک مجموعه جداگانه از داده‌ها مانند داده‌های اعتبارسنجی(validation data) ارزیابی کند.

حال مرحله model.fit را با پارامترهای جدید انجام می دهیم کد جدید آن را قرار می دهم به پارامتر callbacks=[es] دقت کنید که به آن اضافه شده اگر به سمت overfitting برود آن را متوقف می کند.

# Fit the model
history = model.fit(X_train, y_train, epochs=50, batch_size=32, validation_split=0.2 , callbacks=[es])

بعد از اجرای این کد model.evaluation را انجام می دهیم تا accuracy مدل را بیابیم. توجه کنید که این فقط accuracy در داده های آموزش را نشان می دهد. یعنی دقت مدل روی داده های تستی که مدل هنوز ندیده است را نشان نمی دهد. در واقع با این کار بیشتر می خواهم که طریقه Hyperparameter tuning رو تمرین کنیم و بفهمیم وقتی میگوییم مدلی overfitting شده به چه معناست.

جلوگیری از overfitting !؟!؟ چگونه ؟؟

برای جلوگیری از overfitting باید راهکارهایی ارائه کرد. لایه BatchNormalization تغییرات وزن‌ها را کنترل می‌کند و موجب جلوگیری از انفجار گرادیان و حتی Overfitting و در در نتیجه سرعت بخشیدن به فرایند آموزش مدل شود. البته ممکن است استفاده از BatchNormalization در برخی موارد باعث افزایش زمان آموزش شود، زیرا این لایه‌ها محاسبات بیشتری را نیاز دارند. همچنین، استفاده از BatchNormalization در برخی معماری‌های شبکه مانند شبکه‌های بازگشتی (RNNs) به ‌دلیل ویژگی‌های خاص آن‌ها ممکن است موجب مشکل شود.

همانطور که می دانیم L2 regularization مانند یک ترم جریمه به تابع هدف است که در زمان Train مدل افزوده می شود وقتی l2 را به مدل اضافه می کنیم در واقع ، وزن‌های آن لایه با جمع مقدار جریمه L2 ضرب می‌شوند. این باعث می‌شود وزن‌ها کوچک‌تر شوند و از overfitting شدن مدل جلوگیری کنند. که به وزن دهی غیر منظم (weight decay) هم شناخته می شود. اما اگر l2 خیلی بزرگ شود می تواند منجر به underfitting شود چرا که یادگیری Training Data مدل کاهش می باید بنابرای مقدار l2 باید بسیار دقیق و حساب شده باشد.

در ادامه جدولی را می بینید که حاوی پارامترها accuracy هر کدام از آنها ست. تمام داده های این جدول کاملا تست شده هستند و با کدی که در ادامه می بینید به دست آمده اند برای تهیه این جدول بارها و بارها مدل از نو راه شده است.

نکته بسیار بسیار مهم : تمام داده های این جدول با استفاده از cpu ترین شده اند این کار را به این خاطر انجام دادم که بتوانیم تفاوت ترین شدن با cpu و gpu را متوجه شویم. اگر داده ها را در همان معماری جایگرین کنید و با gpu ترین کنید احتمالا نتایج مقداری متفاوت خواهند بود و این نکته بسیار مهم است حتی اگر داده ها را با cpu هم ترین کنید بازهم ممکن است نتایج متفاوت باشد. چرا که عامل هایی مثل تصادفی بودن فرایند آموزش ( بخصوص اگر از یک مدل تصادفی Deep Neural Networks (DNNs) استفاده کنیم. در DNNs ها آموزش از وزن‌های تصادفی آغاز می شود) و شرایط محیطی (میزان اشباع شدن GPU و CPU می‌تواند به تفاوت در نتایج منجر شود) به نتایج متفاوتی منجر می شود. این کارا به این خاطر انجام دادم که این مسئله را به صورت شهودی ببینیم و فراموش نکنیم.
این جدول با پرامترهای مختلف درست شده و نمودارهاییی که در آینده میبینید مطابق با داده های این جدول است
این جدول با پرامترهای مختلف درست شده و نمودارهاییی که در آینده میبینید مطابق با داده های این جدول است

ز جدول بالا برای ترین کردن مدل با Hyperparameter tuning متد ها برای رسم نمودارهای مختلف استفاده می کنیم تا بتوانیم تفاوت های آنها را ببینیم. با استفاده از این نمودار ها و مقایسه آنها می فهمیم که تنظیم درست حتی یک Hyperparameter چقدر می تواند در بهبود عملکرد یک مدل تاثیر گذار باشد. و در واقع با مشاهده آنها می توانیم ببینیم که تاثیر بعضی از متد ها نسبت به دیگری بیشتر است. در قسمت بعد به بررسی و مقایسه نمودارهایی که با استفاده از جدول بالا به دست آمده است می پردازیم.

نمودارها

مقایسه 4 نمودار سری A را ببینید. در نمودار های زیر با پارامترهای batch_size=32 و learning_rate=0.001 و Dropout (0.25, 0.30, 0.40, 0.50) ترین شده اند. خیلی خوب به نمودارهای سری A نگاه کنید. تقربیا در همه آنها دقت در حال کاهش است. اعوجاج های زیادی در نمودارهای آنها به وضوح می توان دید.

به نمودارها توجه کنید در بسیاری از نقاط همدیگر را قطع کرده اند ، قطع شدن نمودارهای آموزش و ارزیابی به معنای عدم تطابق میان داده‌های Training و Validation است و نشان می‌دهد که مدل به طور نامناسبی تنظیم شده است. این معمولا به مشکلاتی مانند overfitting یا underfitting اشاره دارد.

اگر Training Accuracy درحال کاهش و ادامه دار باشد در حالی که Validation Accuracy شروع به افزایش یا توقف کند، این نشان دهنده overfitting است. برعکس، اگر هر دو دقت آموزش و اعتبار به طور پایدار کاهش یابند یا در سطح‌های مشابهی ثابت شوند، این نشان دهنده این است که مدل به طور مؤثری یاد گرفته است. اگر فاصله بین دقت آموزش و اعتبار زیاد باشد ممکن است نشان دهنده overfitting باشد. در بهترین حالت، می‌خواهیم دقت Training و Validation را به طور پایدار افزایش دهیم یا در سطح‌های مشابهی ثابت شویم.

نمودارهای خطی مجموعه A (این نمودارها با استفاده از داده‌های جدول بالا کشیده شده‌اند).
نمودارهای خطی مجموعه A (این نمودارها با استفاده از داده‌های جدول بالا کشیده شده‌اند).


حال مقایسه 4 نمودار سری B را ببینید در نمودار های زیر batch_size=64 و learning_rate=0.01 اما Dropout ها مانند مدل هایی است که نمودارهایشان را در بالا می بیند.

نمودارهای خطی مجموعه B (این نمودارها با استفاده از داده‌های جدول بالا کشیده شده‌اند).
نمودارهای خطی مجموعه B (این نمودارها با استفاده از داده‌های جدول بالا کشیده شده‌اند).


آیا این تفاوت بسیار زیاد را می بینید؟ همانطور که می بینید در نحوه یادگیری مدل بهبودهایی حاصل شده اعوجاج ها کمتر شده است. در سری B تجمیع داده‌ها و اعتبار روی نمودار بهتر شده است. تجمیع داده‌ها به معنای ثبات یا استوار شدن نمودارها در طول دوره‌های آموزش است. تجمع زمانی رخ می‌دهد که نمودارها دیگر تغییرات معناداری در طول دوره‌ها نشان نمی‌دهند. در واقع مقدار Loss یا Accuracy (در صورت استفاده از نمودار دقت) دیگر با تغییرات کوچک در طول دوره‌ها تغییر نمی‌کند. این مرحله معمولاً به عنوان پایدارسازی مدل شناخته می‌شود، زیرا مدل دیگر اطلاعات جدیدی از داده‌ها نمی‌گیرد و به یک حالت ثابت و بهینه می‌رسد. در نمودارهای لرنینگ (Learning curves)، تجمع معمولاً به وضوح قابل مشاهده است؛ زیرا خطوط یا منحنی‌ها کمتر جابجا می‌شوند و به سمت یک نقطه ثابت متمایل می‌شوند.

به نمودارهای سری B دقت کنید. برخورد نمودار های Training Accuracy و Validation Accuracy بسیار کمتر و می توان گفت تقربا برخوردی با هم ندارند. همچنین یادگیری مدل ها با epoch های کمتری انجام شده است. و این یعنی بالا رفتن سرعت محاسبات و سرعت یادگیری مدل. اگر نمودار ها خیلی زود به یک دقت قابل قبول برسند و با افزایش epochs بهبودی در آنها مشاهده نشود نشان می دهد که نیاز داریم مجددا Hyperparameter tuning کنیم. نمودار های بعدی قدرت L2 regularization را خواهیم دید و به بررسی و مقایسه نمودارهای آن خواهیم پرداخت.

در این قسمت می خواهیم نمودارهای سریC را بررسی کنیم. در این نمودار ها مقدار L2=0.01 و learning_rate = 0.01 برای Dropout=(0.30, 0.50) و batch_size=32 و اونها رو با زمانیکه مقدار L2=0.001 هست مقایسه کرده ایم به نمودارها دقت کنید تا تفاوت ها را کامل درک کنید. نمودار اول با Dropout=0.30 است. خوب به نمودار دقت کنید.

نمودارهای خطی مجموعه C (این نمودارهای مقایسه‌ای با استفاده از داده‌های جدول بالا رسم شده‌اند).
نمودارهای خطی مجموعه C (این نمودارهای مقایسه‌ای با استفاده از داده‌های جدول بالا رسم شده‌اند).


مقایسه دوم که در پایین می خواهیم ببینیم batch_size=32 و Dropout=0.50 و فقط مقادیر l2 متفاوت است. میخواهیم نمودارهای A4.32 را با C2.32 که فقط مقدار L2آن را بزرگتر انتخاب کرده ایم مقایسه کنیم. خوب توجه کنید.

نمودارهای خطی مجموعه C (این نمودارهای مقایسه‌ای با استفاده از داده‌های جدول بالا رسم شده‌اند).
نمودارهای خطی مجموعه C (این نمودارهای مقایسه‌ای با استفاده از داده‌های جدول بالا رسم شده‌اند).


واوو!! تفاوت رو ببینید! در واقع وقتی مقدار l2 رو بزرگتر کردیم دیگر overfitting رخ نداد الان داریم underfitting میبینیم. اگرمقدار L2 regularization بسیار بالا باشد، ممکن است باعث کاهش کارایی مدل و ایجاد underfitting شود. مخصوصا در این مثال که Dropout=0.50 می باشد و این مقدار زیاد l2 هم مزید بر علت شد و underfitting رخ داد.

در قسمت بعد به طریقه ذخیره مدلی که آموزش داده ایم و تست اون با داده های تست می پردازیم.



تست نهایی

بعد از Train Phase نوبت به تست نهایی می رسد تا عملکرد مدل روی داده های تستی که ندیده است ببینیم.ابتدا باید مدل را ذخیره کنیم من از colab.research.google برای ساختن مدل استفاده کردم بخاطر همین مسیری را برای ذخیره مدل انتخاب می کنم به طور مثال :

# Define the path to save the model
model_path = &quot/content/drive/MyDrive/Colab Notebooks/trained_model.h5&quot
# Save the trained model
model.save(model_path)
print(&quotModel saved at:&quot, model_path)

بعد از اجرای این کد و اطمینان از ذخیره شدن مدل در گوگل درایو فقط کافی است برای استفاده آن را از مسیر خودش صدا بزنیم :

# Load the trained model
New_model = tf.keras.models.load_model(&quot/content/drive/MyDrive/Colab Notebooks/trained_model.h5&quot)

سپس برای پیش بینی در کد زیر از مدل استفاده می کنیم. منظور 10000 داده تست MNIST است که در در زمان لود کردن دیتا ست دیدیم:

predictions = New_model.predict([X_test])
print(predictions)

حالا کافی ست با کد زیر از مدل بخواهیم که آن را نشان دهد بعد از اجرای شماره عددی که در ایندکس 1 هست را می توانیم ببینیم:

print(np.argmax(predictions[1]))

با کد زیر عدد مورد نظر را مشاهده خواهیم کرد

plt.imshow(X_test[1])
plt.show()



در این مقاله مدلی را با داده MNIST با استفاده از CNN ساختیم . با روند ساختن مدل و مراحل مختلف آن آشنا شدیم. همانطور که دیدیم ممکن است درصد validation accuracy مدل های مختلف تفاوت چندانی نداشته باشد. اما عملکرد آنها با داده تست می تواند بسیار متفاوت باشد. آموختیم که با تنظیم پارامترها همانطور که در بخش Normalization این مقاله ذکر شد. می توانیم زمان و مقدار محاسبات را کاهش دهیم. دیدیم که تعداد epochs برای یادگیری با دو مدل مختلف به چه صورت است و همچنین هدف از بررسی و رسم نمودار مقایسه ای این بود که هم به اهمیت Hyperparameters پی ببریم و هم با دیدن و تحلیل نمودار های مختلف با پارامترهای مختلف بتوانیم چگونگی رخ دادن overfitting یا underfitting را متوجه شویم. در واقع overfitting ممکن است همیشه در مدل به وجود بیاید و رفع این مشکل باعث تفاوت عملکرد افراد می شود. همچنین راهکارهایی برای رفع آن ارائه دادیم. در واقع پارامترهای بسیار مهم مثل L2 regularization و Dropout خیلی می توانند در کارایی و دقت مدل مهم باشند و تغییر جزیی در هرکدام می تواند پیامدهای بسیار منفی یا مثبت داشته باشد. برای ساختن یک مدل به تمرین و دقت فراوان نیاز داریم برای همین مدل در این مقاله و پی بردن به این مراحل نزدیک به 20 بار با تنظیم های مختلف Hyperparameters و اضافه و کم کردن الگو های جدید به نتیجه نزدیک به مطلوبی که در پایان ذکر شد رسیدم . ممنون که با من همراه بودید.

خوشحال میشم که نظرتون رو راجع به این مقاله با من در ارتباط بزارید.



ممنون از شما که در این سفر به دنیای Deeps با من همراه بودید.

این مقاله را به زبان انگلیسی می توانید در Medium در صفحه شخصی من ببینید.

مقالات دیگر من در Medium را می توانید مشاهده کنید.

عمیق‌تر و عمیق‌تر در عمق یادگیری عمیق و شبکه‌های عصبی مصنوعی # پارت 3 : انواع مهم ترین معماری های شبکه های عصبی عمیق (RNN / LSTM / CNN)

استخراج داده با پایتون – راهنمای جامع برای بهره‌برداری از API‌ها

امیدوارم از مطالب لذت برده باشید .

شبکه عصبیدیتا ساینسیادگیری عمیقهوش مصنوعیبیش برازش
جونیور دیتاساینتیتست
شاید از این پست‌ها خوشتان بیاید