شبکه عصبی پرسپترون چند لایه (Multilayer perceptron) یا به اختصار MLP یکی از انواع شبکههای عصبی مصنوعی است که از چند لایه از نورونها تشکیل شده است که به طور معمول از توابع فعال سازی غیرخطی استفاده میکند تا بتواند الگوهای پیچیده در دادهها را یاد بگیرد از این رو باعث میشود که این مدلها قابلیت بالایی در انجام وظایف مختلف مانند دستهبندی ،پیشبینی و یا شناسایی الگو داشته باشد.
مفاهیم پایهای از شبکههای عصبی
شبکه عصبی یا شبکه عصبی مصنوعی یکی از ابزارهای اساسی و مهم در یادگیری ماشین است، که بسیاری از اپلیکیشنهای موجود در حوزههای مختلف مثل بینایی ماشین، پردازش زبان طبیعی و غیره از آن استفاده میکنند.
همانطور که از اسم شبکههای عصبی مشخص است، یک شبکه متصل داخلی از نود ها است که به آن نورون میگویند که در هر لایه قرار گرفتهاند و یک ورودی را دریافت کرده و بر اساس تابع فعالسازی محاسبات لازم را روی آن انجام میدهد و خروجی را تولید کرده و در صورت لزوم این خروجی را به لایه بعدی منتقل میکند.
همانطور که گفته شد خروجی هر نورون وابسته به تابع فعال سازی آن است که یک تابع غیرخطی است و قابلیت یادگیری الگوهای پیچیده از روی دادهها را فراهم میکند.
معمولا هر شبکه به صورت لایههای پشت سرهم سازماندهی شدهاند که از لایه ورودی، جایی که دادهها به شبکه منتقل میشود آغاز میشود. در مرحله بعدی لایههای پنهان شبکه قرار گرفته است که محاسبات اصلی در آن قرار گرفته است و در نهایت لایه خروجی قرار دارد که بسته به وظیفهای که برای شبکه طراحی شده است دستهبندی انجام میدهد و یا پیشبینی.( میتوانید انواع روشهای یادگیری ماشین را در این مقاله بخوانید.)
نورونها در هر لایه توسط اتصالات وزنی به لایه مجاور خود متصل هستند و هر سیگنال ورودی را از یک لایه به لایه دیگر منتقل میکنند. بر اساس وزنی که هر نورون به خود میگیرد قدرت اتصالات در بین لایهها متفاوت میشود و بر نورونهای دیگر تاثر میگذارد. در فرآیند یادگیری، شبکه یاد میگیرد که وزنهای خود را بر اساس دادههای ارائه شده تنظیم کند. علاوه بر این هر نورون یک بایاس دارد که به آن اجازه میدهد که آستانه خروجی خودش را تنظیم کند.
فرآیند یادگیری در شبکههای عصبی توسط دو الگوریتم انتشار پیشخور (feedforward propagation) و فرآیند پسانتشار انجام میشود. در مرحله feedforward ،دیتاها از سمت ورودی لایه به لایه شبکه را طی میکند و در هر لایه بر اساس ورودیهای که دریافت میکند محاسباتی انجام داده و نتیجه محاسبات را به لایه بعدی ارسال میکند.
پسانتشار خطا یا (Backpropagation) یک الگوریتم است که برای آموزش شبکههای عصبی استفاده میشود، در این الگوریتم وزنها و بایاسها به طور مکرر بهروز رسانی میشود تا تابع Loss به حداقل خودش برسد. تابع هزینه یا تابع Loss function یک معیار اندازه گیری است که نشان میدهد که مدل ما چقدر خوب کار میکند به عبارت دیگر چقدر خوب پیش بینی یا دسته بندی میکند. در واقع این تابع میتواند تفاوت بین خروجی پیشبینی شده مدل با خروجی واقعی مدل محاسبه کند و در نهایت یک سیگنالی را ارائه کند که فرآیند بهینه سازی در طول آموزش شبکه را بهبود دهد.
هدف نهایی شبکههای عصبی کمینه کردن این تابع Loss است، این کار توسط الگوریتمهای بینهسازی مانند گرادیان نزولی انجام میشود.
اجزای تشکیل دهند MLP و پیاده سازی آن به کمک کتابخانه tensorflow
– فراخوانی کتابخانه ها و دیتاست
در مرحله اول کتابخانههای مورد نیاز برای پیادهسازی مدل را import میکنیم. برای آموزش مدل نیز از دیتاست mnist استفاده میکنیم. این دیتاست شامل تصاویر اعداد دست نویس انگلیسی از 0 تا 9 است .که در شکل زیر نمای از این دیتاها را مشاهده میکنید.
1 2 3 4 5 |
import numpy as np from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Dense, Activation, Dropout from tensorflow.keras.utils import to_categorical, plot_model from tensorflow.keras.datasets import mnist |
برای فراخوانی دیتاست mnist از روش زیر استفاده میشود.
1 |
(x_train, y_train), (x_test, y_test) = (mnist.load_data) |
با این دستور به راحتی دیتاست که شامل 70 هزار تصویر در اندازه 28 در 28 است بر روی سیستم شما دانلود میشود، این دیتاست شامل دو بخش train و test است که 60 هزار تصویر در بخش train و 10 هزار تصویر در بخش test این دیتاست قرار گرفته است.
1 2 3 4 5 6 7 8 9 10 11 |
# sample 25 mnist digits from train dataset indexes = np.random.randint(0, x_train.shape[0], size=25) images = x_train[indexes] labels = y_train[indexes] # plot the 25 mnist digits plt.figure(figsize=(5,5)) for i in range(len(indexes)): plt.subplot(5, 5, i + 1) image = images[i] plt.imshow(image, cmap='gray') plt.axis('off') |
اگر بخواهیم تصاویر را بخوانیم و به نمایش در بیاریم باید از دستورات بالا استفاده کنیم. این دستورات 25 تصویر را به صورت رندوم انتخاب میکند و به صورت 5 در 5 به نمایش در میآورد. مانند شکل زیر.
برای این که این تصاویر برای ورودی شبکه MLP آماده شود نیاز است تبدیل به یک بردار تک بعدی تبدیل شود. این کار را فقط برای MLP با ورودی تصویر انجام میدهیم.(در ادامه و بعد از آشنایی با شبکههای پیچشی دیگر نیازی به تبدیل تصاویر به بردار یک بعدی نیست). برای این کار از دستورات زیر استفاده میشود.
1 2 3 4 5 |
# resize and normalize x_train = np.reshape(x_train, [-1, input_size]) x_train = x_train.astype('float32') / 255 x_test = np.reshape(x_test, [-1, input_size]) x_test = x_test.astype('float32') / 255 |
در ابتدا با استفاده از دستور np.reshape دادههای test و train را تغییر میدهیم. Input_size که برابر با ضرب ابعاد تصویر است نشانمیدهد که یک تصویر 28 در 28 به یک بردار 784 تایی تبدیل میشود و از آنجایی که تصویر خاکستری است هر عضو بردار عددی بین 0 تا 255 است از این رو برای نرمال کردن دادههای ورودی و قرار دادن هر کدام از آنها بین بازه صفر و یک کل هر بردار را بر 255 تقسیم میکنیم.نرمال سازی باعث میشود که مدل با سرعت بیشتری همگرا شود و دقت بهتری داشته باشد.
– لایه ورودی:
لایه ورودی شامل گرههای و یا نورونهای است که دادههای ورودی را دریافت میکند، هر نورون در لایه ورودی نمایانگر یک ویژگی یا بعد در دادههای ورودی است. از اینرو تعداد نورونهای ورودی با ابعاد ویژگیهای ورودی برابر است.
1 2 3 |
model = Sequential() model.add(Dense(hidden_units, input_dim=input_size)) model.add(Activation('relu')) |
برای اینکه مدل خود را بسازیم از روشهای متفاوتی وجود دارد اما یکی از ساده ترین روشها استفاده از مدل Sequential است. در این حالت ما میتوانیم مدل را به صورت توالی از لایههای مختلف تشکیل دهیم. خطر اول قطعه کد بالا نمایش دهنده این موضوع است که ما میخواهیم از یک مدل Sequential استفاده کنیم. در ادامه لایه اول مدل تشکیل میشود. برای لایه اول از نورونهای Dense استفاده شده است که در واقع همان نورونهای ساده است که به مدل اضافه میشوند، در ادامه ما ابعاد ورودی دادهها برای شبکه تعریف میکنیم، برای این کار از آرگومان input_dim استفاده میکنیم که برابر است بردار تک بعدی از هر تصویر (1,784). در کنار اندازه ورودی دادهها باید تعداد گرهها و یا نورونهای هر لایه را نیز مشخص کنیم که در خط بالا با با متغیر Hidden_units مشخص شده است. تعداد نورونهای لایهها یک شبکه به عنوان یک پارامتر قابل تنظیم شناخته میشود که برای افزایش دقت و یا سرعت باید مقدار بهینه آن را پیدا کنیم. همانطور که قبل تر نیز گفته شده است، نورونهای داخل هر لایه دارای یک تابع فعال سازی است. توابع فعال سازی مختلفی توسط کتابخانه keras و tensorflow پیاده سازی شده است که به راحتی در دسترس است(انواع توابع مختلف را میتوانید در این لینک ببینید). برای این مرحله ما از تابع فعال سازی ReLU استفاده میکنیم.
– لایه پنهان:
بین لایه ورودی و لایه خروجی یک یا چند لایه نورون میتواند وجود داشته باشد که به آنها لایههای پنهان شبکه گفته میشود. وظیفه اصلی این لایهها پردازش اصلی و استخراج الگوها از بین دادههاست هر نورون در این لایه اطلاعات ورودی خودش را از نورونهای لایه قبل دریافت میکند و خروجی را تولید میکند و به لایه بعدی میدهد. تعداد لایهها و نورونها در این لایه هم به عنوان پارامترهای قابل تنظیم شناخته میشوند و برای بهتر شدن نتیجه خروجی باید این پارامترها را به روشهای مختلف تنظیم کرد.
در قطعه کد زیر لایههای پناها در شبکه طراحی شده را میتوانید ببینید.
1 2 3 4 |
model.add(Dropout(dropout)) model.add(Dense(hidden_units)) model.add(Activation('relu')) model.add(Dropout(dropout)) |
لایه اول یک لایه Dropout است که برای جلوگیری از بیش برازش (overfitting) مدل قرار گرفته است، ورودی این تابع عددی درصدی است که نشان میدهد در هر بار فرآیند آموزش چند درصد از نورونها به صورت تصادفی خاموش باشند و مورد استفاده قرار نگیرند. دیگر در این لایهها اندازه ورودی اهمیتی ندارد ولی باید تعداد نورونها در این لایه مشخص شود.
– لایه خروجی:
در این لایه عملا خروجی نهایی بدست میآید، تعداد نورونها موجود در این لایه بسته به نوع تسک خروجی انتخاب میشود، در زمانی که نوع شبکه و وظیفه آن طبقهبندی باشد (مثل تسک پیش رو) تعداد نورونها برابر است با تعداد دستهها و نوع تابع فعال سازی از نوع Softmax است.
1 2 3 |
model.add(Dense(num_labels)) model.add(Activation('softmax')) model.summary() |
در انتها آن یک summary از مدل طراحی شده، جزئیات هر لایه را نمایش میدهد. این که در هر لایه چه تعداد پارامتر برای آموزش وجود دارد. ابعاد ویژگیهای هر لایه نیز مشخص است.
– آموزش شبکه
بعد از این که لایههای شبکه را تعریف کردیم حال زمان آموزش شبکه است. در این مرحله بر شبکه طراحی شده سعی در شناخت و پیدا کردن الگوها بین دادههاست. همانطور که گفته شد هر نورون دارای وزن و بایاس است و هر نورون از یک لایه به تمام نورونهای لایه قبل و تمام نورونهای لایه بعد متصل است. هر کدام از این اتصالات دارای یک وزنی است که قدرت اتصال نورونها را مشخص میکند، این وزنها در طول آموزش به روزرسانی میشوند. همچنین هر لایه داخل لایه پنهان، یک نورون بایاس نیز است که ورودی ثابت به لایه بعدی انتقال میدهد. این نورونها وزن مخصوص به خود را در ارتباط با سایر نورونها دارند که در طول آموزش به روز رسانی میشود.
برای این که بخواهیم مدل را برای آموزش آماده کنیم، در ابتدا از دستور compile استفاده میکنیم.
1 |
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy']) |
این دستور چندین آرگومان به عنوان ورودی دریافت میکند که عبارتند از: Loss و optimizer و metrics. برای مدلی که در اینجا توسعه داده شده است از categorical_crossentropy به عنوان تابع Loss استفاده شده است. همچنین adam به عنوان optimizer و accuracy نیز برای metrics مورد استفاده قرار گرفته است.
در ادامه با دستور fit مدل را آموزش میدهیم.
1 |
history = model.fit(x_train, y_train, validation_split= 0.2 , epochs=20, batch_size=batch_size) |
این تابع نیز دارای آرگومانهای متفاوتی است، اولین و مهمترین آرگومان ورودیهای دادههاست، همانطور که مشخص است دادههای x_train, y_train که قبلا به عنوان بخش آموزش شبکه آماده شدهاند را به عنوان ورودی به تابع fit دادهایم، در مرحله بعد با استفاده از دستور validation_split قسمتی از دادهها را به عنوان اعتبارسنجی آن انتخاب میکنیم، در مدل مورد بررسی 20 درصد از کل دادههای آموزش برای اعتبارسنجی مورد استفاده قرار میگیرد. به زبان ساده میتوان گفت Epochs تعداد دفعاتی است که مدل مورد آموزش دادههای آموزش را میبیند. به عبارت دیگر هر تکراری که کل دادههای آموزش به مدل داده میشود تا وزنها و پارامترها به روزرسانی شود را یک Epoch گفته میشود که در اینجا 20 بار این کار انجام میشود. قاعدتا هر چه میزان این تکرار بیشتر باشد مدل موارد بیشتری را یاد میگیرد و دقت مدل بیشتر میشود اما امکان overfitting نیز وجود دارد.
آرگومان دیگری که برای تابع fit اینجا تنظیم میشود batch_size است، به صورت کلی ما میتوانیم به سه حالت مختلف دادههای آموزش را به شبکه جهت آموزش انتقال دهیم، حالت اول به صورتی است که کل دادهها وارد شبکه شده و عملیات به روزرسانی وزنها بعد از ارسال کل دادهها و محاسبه خطا انجام شود، در این محاسبه خطای کل دادهها باعث افت دقت آن میشود، در طرف مقابل میتوان بعد از این که یک نمونه از دادههای آموزش را به شبکه انتقال دادیم عملیات محاسبه خطا و به روزرسانی صورت بگیرد، در این حالت خطای محاسباتی به شدت کاهش پیدا میکند اما فرآیند به روزرسانی و آموزش طولانی میشود، در حالت بهینهتر میتوان دستههای از دادهها را به شبکه منتقل کرد و در انتها خطا محاسبه شده و به روز رسانی انجام شود. در این حالت دقت نه به اندازه حالت اول کاهش پیدا میکند و همچنین سرعت به اندازه حالت اول کند نیست، به اصطلاح این بستهها batch گفته میشود و ما با تنظیم batch_size اندازه هر کدام از این بستهها را انتخاب میکنیم.
در نهایت بعد از این که آموزش شبکه به اتمام رسید، میتوان از مدل برای تشخیص استفاده کرد. شما میتوانید کد شبکه طراحی شده را به طور کلی در این لینک ببینید و بر اساس نیاز خود ساختار شبکه را تغییر دهید تا متوجه تاثیر تغییر پارامترهای گفته شده بر روی دقت خروجی باشید.