در مقاله قبل (این مقاله را میتوانید در این لینک بخوانید) ساختار اصلی یک شبکه چندلایه پرسپترون را بررسی کردیم. حال در این مقاله به آموزش یک شبکه ساده کانولوشنی و نحوه کد نویسی آن در کتابخانه keras میپردازیم.
شبکه عصبی کانولوشنی چیست؟
شبکه عصبی کانولوشنی (Convolutional Neural Network) یا به اختصار CNN یک الگوریتم خاص از شبکه های یادگیری عمیق است که برای وظایفی طراحی شده است که نیاز به شناسایی دارد مانند: دستهبندی تصاویر، تشخیص و ناحیه بندی تصاویر.
در مرحله اول بیاید بررسی کنیم که چرا شبکههای کانولوشنی مهم هستند:
یکی از قابلیت شبکههای عصبی کانولوشنی توانایی استخراج ویژگی به صورت اتوماتیک است که بر خلاف الگوریتمهای کلاسیک مانند ماشین بردار پشتیبان و یا درخت تصمیمگیری که استخراج ویژگی و مهندسی ویژگی مستقل و دستی را داشتهاند کارآمدتر و نتیجه بهتری را ایجاد میکند.
قابلیت دیگر این شبکههای کانولوشنی که لایههای کانولوشنی داخل آن برای شبکه ایجاد میکند استخراج الگوهای پنهان از بین دادهها بدون توجه به تغییرات در موقعیت آنهاست.
معماریهای مختلفی که توسعه داده شدهاند مانند: ResNet ، Inception و EfficientNet عملکرد بالایی را از خود به نمایش گذاشتهاند. از این مدلها میتوان با استفاده از تکنیکهای مانند fine-tuning با استفاده از دادههای کمتر مدل مناسبی درست کرد.
برخلاف تصور موجود شبکههای کانولوشنی در طیف گستردهای از وظایف به غیر از پردازش تصویر مانند، پردازش زبان طبیعی، تجزیه و تحلیل سریهای زمانی و تشخیص گفتار کاربرد دارد.
سیستم بصری انسان و الهام از آن
جالب است که بدانیم شبکههای عصبی کانولوشنی از سیستم بینایی انسان الهام گرفته شده است.
معماری سلسله مراتبی: معماری شبکههای CNN و بخش بینایی کورتکس یک معماری سلسله مراتبی دارند که در لایههای ابتدای ویژگیهای ساده و در لایههای بالاتر ویژگیهای پیچیدهتر استخراج میشود.
در قشر بینایی مغز، نورونها فقط به یک ناحیه محلی از ورودی متصل میشوند نه به کل میدان دید و بینایی، به طور مشابهی نورونها در یک لایه شبکه CNN از طریق عملیات کانوال به یک ناحیه از ورودی متصل میشوند.
نورونهای قشر بینایی میتوانند ویژگیهای تصویر را بدون در نظر گرفتن موقعیت آنها در میدان دید تشخیص دهند. در طرف دیگر فیلترهای شبکه کانولوشنی نیز ویژگیهای تصاویر را بدون در نظر گرفتن موقعیت مکانی میتوانند شناسایی کنند.
در هر مرحله از پردازش بصری، تعداد زیادی feature map یا نقشه ویژگی از تصاویر استخراج میشود. شبکه کانولوشنی نیز با استفاده از فیلترهای متفاوت همین کار را انجام میدهند و نقشه ویژگیهای متعددی استخراج میکنند.
نورونهای قشر بینایی غیرخطی هستند همچنین با استفاده از توابع فعالسازی در شبکههای عصبی مانند ReLU قابلیت استخراج ویژگیهای غیر خطی را پیدا میکنند.
اجزای کلیدی شبکههای کانولوشنی
لایههای کانولوشنی
اولین بلاک در شبکههای کانولوشنی را لایههای کانولوشن تشکیل میدهد. همانطور که از اسم این لایه مشخص است، این لایه وظیفه اعمال یک تابع ریاضی را دارد که در آن یک پنجره لغزند بر روی ماتریس تصویر حرکت میکند و عملیات کانوال روی آن انجام میشود. به این پنجره لغزنده فیلتر و یا kernel نیز گفته میشود.
در این لایهها چندین فیلتر با ابعاد یکسان وجود دارد که هر کدام از این فیلترها برای تشخیص الگوی خاصی از تصویر مورد استفاده قرار میگیرد. اگر بخواهیم به زبان ساده بیان کنیم، در لایههای کانولوشنی کرنلهای استفاده میشود که بر روی تصاویر حرکت میکنند. وظیفه کرنلها استخراج الگوهای خاصی است که در تصاویر مانند خطوط منحنی یا اشکال مختلف است. این فیلترها همانطور که بر روی سراسر تصویر حرکت میکنند یک شبکه جدید ایجاد میکنند که این ویژگیهای محلی را برجسته میکنند. بعضی از فیلترها میتوانند خطوط مستقیم را به درستی شناسایی کنند و بعضی دیگر میتوانند خطوط منحنی را به درستی تشخیص دهند.
در شکل بالا شما یک فیلتر با ابعاد سه در بر روی تصویر در حال حرکت است و عمل کانولوشن انجام میشود. همانطور که در شکل مشخص است فیلتر بر اساس گامی که برای آن تعریف شده است روی تصویر اصلی حرکت میکند و مقادیر هر پیکسل با مقدار متناظر آن بر روی فیلتر ضرب شده و جمع جبری آنها در خروجی به عنوان feature map استخراج میشود و به عنوان ورودی به لایه بعدی انتقال داده میشود.
در شکل بالا ما میتوانیم feature map های که در لایه کانولوشنی از تصویر استخراج شده است را مشاهده کنیم. همانطور که مشخص است هر فیلتر در هر لایه ویژگی منحصر به فردی را تصویر استخراج میکند.
تابع فعال سازی ReLU
یکی دیگر از اجزای اصلی در ساخت یک شبکه کانولوشنی تابع فعالسازی است که در مقاله ای جداگانه انواع مهمی از آن را معرفی کردیم(در این لینک میتوانید این مقاله را بخوانید). انتخاب تابع فعال سازی تاثیر زیادی در عملکرد شبکه کانولوشنی دارد.
در بسیاری از معماریهای شبکه کانولوشنی از تابع ReLU به عنوان تابع فعال سازی استفاده میشود. از مزایایی آن میتوان به سریع بودن آن اشاره کرد، البته که یکی از مهمترین نقاط ضعف این تابع زمانی است که ورودی آن مقدار منفی باشد. در این حالت تابع عملا خروجی صفر است و نورون مرده محسوب میشود.
لایه pooling
هدف این لایه کاهش ابعاد نقشه ویژگی با اعمال برخی از عملیات مثل بیشینه گیری یا میانگین گیری است. این لایه عملا نورون و یا پارامتری برای یادگیری ندارد و فقط با استفاده از یک پنجره لغزان روی نقشه ویژگی حرکت کرده و بر اساس این که از چه نوعی تعریف شده است مقادیری را از آن ویژگی استخراج میکند.
به طور مثلا در تصویر زیر عملکرد یک لایه pooling با ابعاد سه در سه را میبینید که مقدار بیشنیه هر قسمت که روی آن قرار گرفته است را استخراج میکند.
انواع مختلفی از لایههای pooling توسعه داده شدهاند که هر کدام مزایا و معایب خود را دارند (میتواند انواع لایههای pooling که توسط keras توسعه داده شدهاند را در این لینک ببینید).
لایه تمام متصل یا fully connected
این قسمت بخش آخر یک شبکه کانولوشنی است که ورودی آن ویژگیهای مسطح شده از آخرین لایه pooling است، به طور معمول از توابع ReLU به عنوان تابع فعال سازی استفاده میشود. لایه آخر آن نیز به طور معمول از تابع Softmax استفاده تا مقادیر احتمال برای هر یک از برچسبهای خروجی ممکن تولید شود و در نهایت برچسب نهایی پیش بینی شده را بر اساس بیشترین احتمال انتخاب شود.
کد شبکه عصبی کانولشنی
برای این که بتوانیم به راحتی یک شبکه عصبی کانولوشنی را پیاده سازی کنیم از فریمورک Keras استفاده میکنیم. در مرحله اول نیاز است کتابخانههای مورد استفاده آن را باید import کنیم.
1 2 3 4 |
import tensorflow as tf from tensorflow import keras import matplotlib.pyplot as plt import numpy as np |
کتابخانه tensorflow و keras به عنوان کتابخانههای که برای طراحی شبکه مورد استفاده قرار میگیرند. از کتابخانه matplotlib جهت ترسیم اشکال و نمودارها استفاده میشود، همچنین از کتابخانه numpy برای محاسبات استفاده میشود.
در مرحله بعدی نیاز است که دیتاستی که نیاز است مدل روی آن اجرا شود را فراخوانی کنیم. (در این مثال از دیتاست MNIST استفاده شده است)
1 |
(x_train, y_train), (x_test, y_test) = keras.datasets.mnist.load_data() |
کتابخانه keras تعدادی از دیتاستهای معروف را به صورت API در اختیار ما قرار داده است، به طور مثال میتوانیم با قطعه کد بالا دیتاست MNIST را به راحتی در دو بخش train و test دانلود کرد و استفاده کرد.(میتوانید دیتاستهای که توسط keras ارائه شده است را در این لینک ببینید.) همانطور که در کد مشخص است دیتاست بر روی دو قسمت train و test ذخیره میشود و هر کدام از این بخشها دارای دیتای تصویر اعداد که در بخش x ذخیره شده و لیبل هر کدام از تصاویر که در بخش y ذخیره شده میباشد.
1 2 |
x_train = x_train.reshape(x_train.shape[0], 28, 28, 1).astype('float32') / 255 x_test = x_test.reshape(x_test.shape[0], 28, 28, 1).astype('float32') / 255 |
در بخش بعدی با استفاده از قطعه کد بالا تمام پیکسلهای تصویر را بر 255 تقسیم میکنیم. دلیل این کار نرمال کردن دادههاست.
1 2 3 4 5 6 7 8 |
model = keras.Sequential([ keras.layers.Conv2D(32, (3, 3), activation='relu', input_shape=(28,28,1), name='conv2d_20'), #added name keras.layers.MaxPooling2D((2, 2)), keras.layers.Conv2D(64, (3, 3), activation='relu', name='conv2d_21'), #added name keras.layers.MaxPooling2D((2, 2)), keras.layers.Flatten(), keras.layers.Dense(10, activation='softmax') ]) |
بعد از نرمال سازی مرحله طراحی شبکه است. در اینجا یک مدل با دو لایه کانولوشنی به همراه یک لایه در قسمت تمام متصل طراحی میشود. همان طور که مشخص است مدل از نوع Sequential است که هر لایه بعد از لایه دیگر قرار میگیرد. در بخش اول بهتر است یک نام برای model خود تعریف کنیم همانطور که مشخص است مدلی که در اینجا طراحی شده است را model نامیدیم که در ادامه بتوانیم آن را fit کنیم و برای train اماده کنیم. لایه اول یک کانولوشنی دو بعدی است (به این معنی که پنجره لغزان هم روی محور x حرکت میکند و هم بر روی محور y). در این لایه عدد 32 تعداد فیلترهای کانولوشنی که در این لایه قرار گرفته است را نشان میدهد. (3,3 ) نشان دهنده اندازه پنجره لغزان است که نشان میدهد یک ماتریس 3 در 3 است. نوع تابع فعال سازی با آرگومان activation تعیین میشود، همانطور که گفته شد نوع تابع فعال سازی این شبکه از نوع ReLU است. آرگومان input_shape اندازه تصویر ورودی را برای شبکه تعیین میکند، در اینجا (28,28,1) نشان دهنده این است که تصاویر ورودی 28 در 28 پیکسل است و یک بعدی به معنای تصویر خاکستری و یا سیاه سفید است.
لایه دوم این شبکه یک لایه MaxPooling دو بعدی است (به این معنی که پنجره لغزان هم روی محور x حرکت میکند و هم بر روی محور y) با اندازه (2,2) است، همانند فیلترها اندازه این پنجره 2 در 2 است.
لایه سوم یک لایه کانولوشنی دو بعدی دیگر است که این بار 64 فیلتر در آن با اندازه (3,3) تعریف شده است.
لایه چهارم دوباره یک لایه MaxPooling دو بعدی با اندازه (2,2) است. و لایه بعدی یک لایه برای مسطح کردن نقشه ویژگیهای استخراج شده از لایههای کانولوشنی است، این کار باعث میشود که یک بردار ویژگی داشته باشیم که بتوانیم آن را به قسمت تمام متصل بدهیم.
لایه آخر این شبکه یک لایه از نوع Dense با تابع Softmax است برای احتمال پیشبینی هر ورودی( در اینجا تعداد نورونها باید با تعداد کلاسهای دیتاست که در شبکه قرار گرفته است برابر باشد)
1 2 3 |
model.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['accuracy']) |
در ادامه مدل compile میشود، در اینجا نوع optimizer و loss برای شبکه تنظیم میشود و در نهایت سنجههای (metrics) که میخواهیم با آن شبکه را مورد بررسی قرار دهیم را در این قسمت تعریف میکنیم.
1 |
history = model.fit(x_train, y_train, epochs=10, validation_data=(x_test, y_test)) |
در بخش بعدی مدل بر روی دادهها trian میشود که برای این کار ما از دستور fit استفاده میکنیم. دادههای train که با x_train و y_train مشخص شده است و همچنین تعداد epochs ها را برای آموزش شبکه تعریف میکنیم و در ادامه قسمت validation دادهها را نیز تنظیم میکنیم. و بعد از آموزش شبکه میتوانیم دقت خروجی آن را ببینیم.
شما میتوانید کد آماده این آموزش را در این لینک در گوگل کولب ببینید و آن را اجرا کنید.