شبکه عصبی کانولوشن
بهنام خدا، سلام… در این پست میخواهم به آموزش شبکه عصبی کانولوشن بپردازم. دراینجا صرفا شبکه عصبی کانولوشن را توضیح دادهام. در این آموزش شبکه کانولوشن را برای موضوع طبقه بندی توضیح دادهام. امیدوارم با این آموزش دوستی خوبی بین شما و شبکه کانولوشنی شکل بگیرد. بسیارخب، با CNN چیست شروع میکنیم. با هوسم همراه باشید…
شبکه عصبی کانولوشن (CNN) چیست؟
شبکه عصبی کانولوشن (CNN) یک شبکه عصبی قدرتمند و پرکاربرد در یادگیری عمیق است که برای پردازش دادههای شبکهای (Grid-Structured Data) مانند تصاویر به کار میرود. این شبکه شامل اجزای مختلفی از جمله فیلترهای کانولوشنی برای استخراج ویژگی، لایههای pooling برای کاهش بُعد و لایههای fully connected برای طبقهبندی نهایی است. به دلیل توانایی بالای CNN در تشخیص الگوهای پیچیده، این شبکهها نه تنها در پردازش تصویر، بلکه در حوزههای دیگری مانند پردازش زبان طبیعی و تشخیص گفتار نیز کاربرد دارند.
تاریخچه شبکه عصبی کانولوشن (CNN)
از زمان پیدایش یادگیری عمیق (Deep Learning)، شبکه عصبی کانولوشن (Convolutional Neural Network) گل سرسبد ایدهها در یادگیری عمیق بوده است. به شبکه کانولوشن به اختصار CNN یا ConvNet گفته میشود. شبکه CNN در سال 1990 با الهامگیری از آزمایشهای انجامشده توسط Hubel و Wiesel روی قشر بینایی (Visual Cortex) معرفی شد. یکی از اولین پروژههای انجامشده با این شبکه CNN، پروژه معروف شناسایی ارقام دستنویس MNIST در سال 1998 توسط Yann Lecun بود که نتایج امیدوارکنندهای به همراه داشت.

گرد و خاک بهپا کردن شبکه عصبی کانولوشن
اما درخشش اصلی شبکه کانولوشن در سال 2012 در جریان رویداد “ImageNet Large Scale Recognition Challenge” (همان مسابقه ImageNet) اتفاق افتاد. اتفاقی که توجه جامعه محققین را به خود جلب کرد. در مسابقه ImageNet سال 2012، Alex Krizhevsky از دانشگاه تورنتو با شبکه AlexNet وارد مسابقه پیچیده ImageNet شد. مسابقه ImageNet یک چالش دستهبندی (Classification) با 1.2 میلیون تصویر در 1000 کلاس بود. شبکه AlexNet با خطای 16.4٪ به رتبه اول مسابقه رسید. به نظر شما نفر دوم به چه خطایی دست یافته بود؟ 26.2%! اختلاف خطای حداقل 10 درصدی بین شبکه CNN با سایر روشها…
با این نتیجه، AlexNet مشت محکمی بر میز کوبید و نقشه راه جدیدی در بینایی کامپیوتر ترسیم کرد. از آن زمان تا امروز (2020)، تحقیقات CNN رشد چشمگیری داشته است. تنها در 3 سال، محققان از 8 لایه AlexNet به 152 لایه ResNet رسیدند و میزان خطای چالش ImageNet را به کمتر از 4٪ کاهش دادند. 12% کاهش خطا طی 3 سال با شبکه CNN، البته با افزایش پیچیدگی ساختار شبکه CNN!

طوفان شبکه کانولوشن
سرعت رشد شبکه کانولوشنی آنقدر زیاد بود که در مدت کوتاهی، در بسیاری از زمینههای مشکلِ بینایی کامپیوتر مانند شناسایی عمل انسان، تشخیص اشیا، شناسایی چهره و ردیابی انقلابی برپا کرد. با سیطره بر بینایی کامپیوتر، شبکه کانولوشنی در سایر زمینههای هوش مصنوعی مانند پردازش زبان و گفتار نیز وارد شد و اتفاقا نتایج خوبی هم بدست آورد. مثلا، گوگل با استفاده از شبکه کانولوشن در یادگیری تقویتی، مدل AlphaGo را ساخت که توانست بهترین بازیکن بازی سخت Go را قدرتمندانه شکست دهد و در سرتیتر اخبار قرار گیرد. شبکه عصبی کانولوشن همچون مارکوپولو وارد قلمروهای مختلف شد و حتی در حوزه پزشکی نیز به دستاوردهای قابل اعتنایی دست یافت. با این دستاوردها، شبکه CNN فرزند خلف یادگیری عمیق بود که باعث شد یادگیری عمیق به شهرتی روزافزون دست یابد.

بعد از جواب به سوال “شبکه عصبی کانولوشن چیست؟”، میخواهم کمی شما را با شبکه کانولوشن آشنا کنم. در بخش بعدی، خیلی وارد جزئیات نمیشوم، اما رفته رفته تا آخر این پست به جزئیات شبکه CNN هم خواهیم رسید.
آشنایی با شبکه عصبی کانولوشن
شبکه عصبی کانولوشن همانند سایر شبکه های عصبی (مثلا شبکه عصبی MLP) از لایههای نورونی با وزن و بایاس با قابلیت یادگیری تشکیل شده است. قطعا میدانید که در هر نورون اتفاقات زیر رخ میدهد:
- نورون مجموعهای ورودی دریافت میکند.
- ضرب داخلی بین وزنهای نورون و ورودیها انجام میشود.
- حاصل با بایاس جمع میشود.
- درنهایت، از یک تابع غیرخطی (همان activation function) عبور داده میشود.

فرآیند بالا لایه به لایه انجام میشود و درنهایت به لایه خروجی میرسیم. لایه خروجی، پیشبینی شبکه را تولید میکند. اما با این حد از تشابه بین شبکه MLP و شبکه CNN، پس تفاوت در چیست؟ در ادامه به این سوال مهم جواب میدهم…
تفاوت شبکه عصبی MLP و کانولوشن
تفاوت در ورودی است، مثلا تصویر… تصاویر معمولا به شکل یک ماتریس دوبعدی از اعداد نمایش داده میشوند. هر درایه در این ماتریس دوبعدی معادل با یک پیکسل هست. در بخش بعدی درمورد تصویر بیشتر توضیح خواهم داد. اگر یک تصویر 100×100 داشته باشیم، یعنی 10000 پیکسل داریم که به صورت دوبعدی بهصورت مسالمتآمیز کنار همدیگر نشستهاند. حال تصور کنید بخواهیم یک لایه ورودی برای این 10000 پیکسل بسازیم؛ باید 10000 نورون برای لایه ورودی شبکه MLP درنظر بگیریم. اضافه کردن نورون و لایه بیشتر به این شبکه MLP باعث میشود شبکه ما شامل حجم بزرگی از پارامترها شود، محاسباتش هزینهبر باشد و البته Overfitting اتفاق بیفتد.

حالا نقطه قوت شبکه CNN چیست؟
این شبکهها بهگونهای طراحی شدهاند که برای ورودیهای با ساختار ماتریسی (دوبعدی و سهبعدی) بهخوبی کار میکنند. شبکه MLP، ساختار دادههای ورودی را عوض میکند و یک ماتریس دوبعدی 100×100 را تبدیل به یک بردار به ابعاد 10000 میکند. اما شبکه CNN ساختار ورودی را عوض نمیکند و به ارتباط بین پیکسلهای همسایه اهمیت میدهد.
چرا باید به ارتباط به پیکسلهای همسایه اهمیت دهیم؟ به تصویر زیر نگاه کنید؛ مگر نهاینکه از کنارهم قرار گرفتن تعدادی پیکسل در راستای سطر و ستون یک تصویر از پاندای خسته تشکیل شده است؟ بنابراین، مهم است که ساختار تصویر یا ورودی را عوض نکنیم (تبدیل به بردار نکنیم) و همان ورودی اصلی را به شبکه بدهیم. دقیقا همان کاری که شبکه CNN انجام میدهد.

آیا شبکه CNN تنها برای تصویر دوبعدی مناسب است؟ خیر، دادههای زیر غذای خوشمزهای برای شبکه CNN هستند:
- دادههای یک بعدی: سیگنال و sequence (مثلا یک sequence از کلمات)
- دادههای دوبعدی: تصویر و طیف (spectogram) صوت
- دادههای سه بعدی: ویدئو و تصاویر حجمی (مثلا تصاویر MRI)
- دادههای چهاربعدی: تصاویر حجمی همراه با زمان (مانند fMRI)
بعد از آشنایی با مزایای شبکه کانولوشن و بررسی تفاوتش با شبکه MLP، وقتش رسیده که معماری این شبکه را زیر تیغ جراحی ببریم. اما بیایید اول یک آشنایی کلی با معماری شبکه CNN بدست آوریم.
یک معماری ساده از معماری شبکه عصبی کانولوشن
در شکل 7 یک مثال ساده از معماری شبکه عصبی کانولوشن نشان داده شده است. شبکه کانولوشن معمولا از بلوکهای مختلفی تشکیل شده است. لایهها یا بلوکهای مختلف در شبکه CNN عبارتنداز:
- لایه ورودی (Input layer) [بلوک زرد در شکل 7]
- لایه کانولوشن (Convolutional layer) [بلوکهای آبی در شکل 7]
- لایه غیرخطی (Non-linear activation function) [بلوکهای آبی در شکل 7] (معمولا تابع غیرخطی را همراه با لایه کانولوشنی یکجا نشان میدهند)
- لایه پولینگ (Pooling layer) [بلوکهای قرمز در شکل 7]
- لایه فولی کانکتد (Fully connected layer) [بلوکهای سبز در شکل 7]

در ادامه این پستِ آموزشِ شبکه عصبی کانولوشن، لایههای بالا را توضیح میدهم. شاید بگویید که لایه ورودی که صرفا تصویر است و جز لایههای شبکه محسوب نمیشود. اما، من لایه ورودی را هم اضافه کردم و لازم هست درباره آن توضیح دهم. پس، ابتدا ورودی را بررسی میکنیم، سپس به لایههای شبکه کانولوشن میرسیم. در بالا اشاره کردم که شبکه CNN ورودیهای متنوعی را میپذیرد. اما من دراینجا میخواهم معروفترین داده ورودی به شبکه CNN، یعنی تصویر را فقط بررسی کنم.
تصویر در پردازش تصویر
در سادهترین تعریف ممکن، تصویر یک آرایه یا ماتریس دوبعدی از اعداد است که هریک از درایههای آن معادل یک پیکسل هست. در شکل 8 یک نمونه تصویر نشان داده شده است. نمایی زومشده از تصویر نیز نشان داده شده. مشاهده میکنید که چگونه مربعهای کوچک در کنارهم قرار گرفتهاند. به جدول اعداد نگاه کنید؛ اگرچه درحالت معمولی ما یک تصویر با رنگ میبینیم (تصویر سمت چپ شکل 8)، اما این اعداد هستند که این رنگها را میسازند (جدول سمت راست شکل 8)!
مقادیر موجود در تصویر یا ماتریس بین 0 تا 255 هستند. مقدار صفر معادل با رنگ سیاه مطلق و مقدار 255 معادل با رنگ سفید است. هرچقدر این مقادیر به صفر نزدیکتر باشد، آن پیکسل تیرهتر میشود. هرچقدر مقدار پیکسل به 255 نزدیکتر باشد، رنگ آن پیکسل روشنتر میشود. همانطور که در شکل 8 مشاهده میکنید، نواحی تیره در ناحیه بزرگنمایی شده (سمت چپ و پایین) مقادیر کمتری دارند (مثلا اعداد 59 61 65). همچنین نواحی روشن (سمت راست و بالا)، مقادیر بزرگتری دارند (مثلا اعداد 206 207 204).

دو نوع تصویر سطح خاکستری (Gray scale image) و تصویر رنگی (RGB image) معمولا بهعنوان ورودی به شبکه عصبی کانولوشن داده میشود.
تصویر سطح خاکستری در پردازش تصویر
سادهترین نوع تصویر، یک تصویر سطح خاکستری است. در واقع این تصویر یک ماتریس دوبعدی است. تصویر شتر در شکل 8 یک نمونه تصویر سطح خاکستری است. شتر چیه؟! زرافه هست! 😅 داشتم متن رو بعد از مدتی مرور میکردم، دیدم شاخ شمشاد رو نوشتم شتر!
تصویر رنگی در پردازش تصویر
تصاویر رنگی ساختار کمی متفاوت دارند. تصویر رنگی از سه صفحه تشکیل شده است. این صفحات عبارتنداز صفحه قرمز (R)، صفحه سبز (G) و صفحه آبی (B). مشخصات هر صفحه مشابه همان تصویر سطح خاکستری است و مقادیر اعداد بین 0 تا 255 است. از ترکیب سه عدد، یک رنگ نهایی حاصل میشود. بههمین خاطر به تصاویر رنگی، تصویر RGB گفته میشود. از ترکیب این سه صفحه، یک تصویر رنگی حاصل میشود. یک نمونه تصویر RGB در شکل 9 نشان داده شده است.

پس تصویر رنگی از سه صفحه RGB تشکیل شده است. حالا که تصویر را شناختید، میخواهم شما را با تنسور آشنا کنم. در یادگیری عمیق به وفور کلمه تنسور را میشنوید. بهصورت کوتاه در ادامه تنسور را توضیح میدهم.
تنسور چیست؟
در یادگیری عمیق، عبارت تنسور را زیاد خواهیم شنید. در گذشته همه ما با گونههای خاصی از تنسورها کار کردیم. اعداد، بردارها و ماتریسها همگی حالات خاص یک تنسور هستند! تنسورها در واقع تعمیم یافته ماتریسها هستند و میتوان آنها را به صورت یک آرایه چندبعدی نمایش داد. یعنی، به عدد، بردار و ماتریس بهترتیب تنسور صفربعدی، یک بعدی و دوبعدی گفته میشود. دقت کنید که تنسورها دقیقا همانند ماتریسها و بردارها میتوانند اندیسدهی شوند. یعنی ما با کمک اندیسدهی میتوانیم به تمامی عناصرِ یک تنسور دسترسی داشته باشیم. یک تصویر سطح خاکستری معادل با تنسور دوبعدی و تصویر رنگی معادل با تنسور سه بعدی است. البته، در یادگیری عمیق تنسور چهاربعدی هم خواهید دید! پیشنهاد میکنم بخش آشنایی با تنسور در ریاضیات را مطالعه کنید.
بسیارخب، درادامه توضیح لایههای اصلی شبکه عصبی کانولوشن را شروع میکنم. اولین لایه، لایه کانولوشنی است. پس برویم با کانولوشن آشنا شویم و بعد هم لایه کانولوشنی را زیر ذرهبین قرار دهیم…
کانولوشن چیست؟
در عملگر کانولوشن، مطابق شکل 10 چهار مولفه مهم وجود دارد که عبارتنداز:
- ماتریس یا تصویر ورودی (Input)
- فیلتر یا کرنل کانولوشنی (Convolution Filter)
- عملگر کانولوشن (*)
- ویژگی خروجی کانولوشن (Output)

خیلی ساده بخواهم عملکرد کانولوشن را توضیح دهم، باید بگویم که عملگر کانولوشن (*)، کرنل یا فیلتر کانولوشنی را برمیدارد و روی تصویر یا ماتریس ورودی میلغزاند. بهعبارتی دیگر، کرنل یا فیلتر روی تصویر حرکت میکند یا تصویر ورودی را اسکن میکند. به شکل زیر نگاه کنید؛ ماتریس آبی معادل با تصویر یا ویژگی ورودی و ماتریس قرمز معادل با فیلتر یا کرنل کانولوشنی است. به نحوه حرکت یا اسکن فیلتر روی تصویر دقت کنید. فیلتر ابتدا هرسطر را ستون به ستون طی میکند و بعد یک سطر پایین میآید و دوباره ستون به ستون جلو میرود و این فرآیند تا آخر ادامه دارد…

مثال عددی از کانولوشن
اما ماتریس ورودی و فیلتر بالا شامل هیچگونه عددی نیستند. حالا به تصویر زیر نگاه کنید؛ هر فیلتر کانولوشنی، شامل مجموعهای عدد است. با قرار گرفتن فیلتر روی هربخش از تصویر، اعدادِ در فیلتر درایه به درایه در پیکسلهای متناظر تصویر ضرب میشوند. درنهایت همه اعداد باهم جمع میشوند. مثلا در تصویر زیر، 9 ضرب نظیر به نظیر بین یک بخش 3×3 از ماتریس آبی با ماتریس قرمز انجام شده است. درنهایت، این 9 ضرب باید باهم به شکل زیر جمع شوند:
1×1 + 2×-1 + 3×4 + 5×-2 + 8×1 + 9×0 + 5×0 + 0×2 + 1×3 = 12

حالا این عدد 25 را کجا باید ذخیره کرد؟ فعلا مطابق شکل زیر، خروجی را در یک آرایه تنها قرار میدهیم.

حالا باید فرآیند اسکن کردن (شکل 11) و محاسبه خروجی (شکل 13) باهم ترکیب شود. یعنی فیلتر کانولوشنی سفرهای استانی را شروع کند و در هر محلی خروجی را از طریق ضرب درایه به درایه بین فیلتر و تصویر محاسبه نماید. ضربها را باهم جمع و در خروجی میچینیم. شکل زیر گویای فرآیند کامل کانولوشن است. دقت کنید، هرطوری که فیلتر حرکت میکند (مثلا یک ستون جلو میرود یا یک سطر پایین میآید)، ما هم خروجی را به همان شکل کنار هم میچینیم.

چرا سایز خروجی کانولوشن کوچک شد؟!
چرا ماتریس خروجی در شکل 14، دو سطر و ستون کمتر از ماتریس ورودی دارد؟ چرا ماتریس خروجی در شکل 14، 3×3 است اما ماتریس ورودی 5×5 است؟ چرا هماندازه نیستند؟ جواب این سوالها این است که بسته به اندازه فیلتر، اندازه ماتریس خروجی تغییر میکند. مثلا وقتی فیلتر 3×3 باشد، خروجی 2 سطر و ستون کمتر از ورودی خواهد داشت. حالا بیایید این مساله را فرموله کنیم. فرض کنید ماتریس ورودی و فیلتر مربعی باشند و سایز آنها بهترتیب برابر با n و k باشد. دراینصورت، سایز خروجی m با رابطه زیر بدست میآید:
m = n – (k-1)
به همین سادگی… حالا اگر سایز ورودی و فیلتر بهترتیب 5×5 و 3×3 باشد، اندازه خروجی برابر با 3×3 خواهد بود. آیا راهی وجود دارد که اندازه ماتریس خروجی کاهش پیدا نکند و همان 5×5 بماند؟ بله وجود دارد، تکنیکی به نام padding یا لایه گذاری ماتریس وجود دارد که به دور ماتریس ورودی سطر و ستونهایی اضافه میشود که باعث میشود جلوی کاهش بعد خروجی گرفته شود. مثلا، در شکل 14، اگر ورودی را بهنوعی جعلی به یک ماتریس 7×7 تبدیل کنیم، آنوقت خروجی 5×5 خواهد بود. یعنی اندازه خروجی برابر با همان ورودی است. دقت کنید؛ ما بهصورت جعلی ورودی را افزایش بعد میدهیم. در بخش لایه کانولوشنی راهحلش را توضیح میدهم.
قصد بررسی عمیق عملگر کانولوشن را ندارم، چون خود یک پست جداگانه و طولانی را میطلبد. اما میخواهم خیلی خلاصه درباره کارکرد کانولوشن توضیح دهم. پس بخش بعد را از دست ندهید…
کانولوشن زیر ذرهبین
بیایید چند نکته از کانولوشن را باهم مرور کنیم.
نکته اول
اعدادی که در ماتریس خروجی ذخیره میشوند، تابعی از ورودی و فیلتر هستند. چه زمانی به ازای ضرب بین فیلتر روی یک محل از تصویر، خروجی بزرگ یا کوچک میشود؟ ساده است، هروقت فیلتر با یک پنجره از تصویر خیلی شبیه هم باشند (از لحاظ عددی)، خروجی عدد بزرگی میشود. اگرهم شبیه هم نباشند، خروجی عدد کوچکی میشود. یعنی چه؟ یعنی اینکه، فیلتر به دنبال پیدا کردن نواحی مشابه خود در تصویر است و هرجایی ناحیه مشابهِ خود را پیدا کرد بلند فریاد میزند (عدد بزرگ).
پس کانولوشن منجر به یافتن الگوهای خاص در تصویر باتوجه به فیلتر میشود. اعداد موجود در فیلتر بسیار مهم هستند. به شکل 15 که یک سیگنال یک بعدی است دقت کنید. ببینید، چطور نواحی مشابه با فیلتر در خروجی آشکار شده است. دقیقا آنجایی که دو مربع روی هم قرار میگیرند، یک مقدار پیک در خروجی بهوجود میآید. یعنی شباهت در آن نقطه بسیار بالاست.

نکته دوم
ارتباط دادن عمل کانولوشن به عملکرد یک نورون است. یک نورون یک جمع وزندار (ضرب بین پارامترها و ورودی و نهایتا جمع) بود. اینجا هم همین است! فیلتر که شامل یک سری اعداد است به ورودیها وزن میدهد (ضرب درایه به درایه بین فیلتر و پنجرههای ماتریس ورودی) و نهایتا اعداد وزندهیشده ورودی را باهم جمع میکند.
نکته سوم
در نورون پارامترها متغیر بودند و از طریق فرآیند آموزش بدست میآمدند. اینجا هم اعداد موجود در فیلتر از طریق فرآیند آموزش بهدست میآیند. البته، قبل از پیدایش شبکه عصبی کانولوشن، از کانولوشن در پردازش تصویر و سیگنال بسیار زیاد استفاده میشد. اما اعداد فیلترهای کانولوشنی ثابت بودند و توسط یک متخصص این اعداد طراحی میشوند. مثلا در شکل 16 یک فیلتر لبهیابی با نتیجه خروجی را آوردهام. ببینید، فیلترها چه بلایی سر تصویر میآورند و فقط دنبال مطلوب خود هستند.

تا اینجا، به شما درباره کانولوشن تصویر دوبعدی (تصویر سطح خاکستری) توضیح دادم. اما در شبکه عصبی کانولوشن، معمولا ورودی یک تصویر رنگی (ماتریس سه بعدی) است. دراینحالت کانولوشن چگونه انجام میشود؟ بسیار ساده است…
کانولوشن با ورودی سهبعدی
همانطور که در بخش قبلی گفتم، تصویر رنگی سه کانال یا صفحه دارد. بنابراین، فیلتر شما هم باید سه کانال داشته باشد. یعنی اگر قبلا یک فیلتر 3×3 برای کانولوشن در تصویر دوبعدی داشتیم، حالا یک فیلتر 3×3×3 خواهیم داشت. چرا؟ چون هرصفحه از تصویر رنگی بهصورت مجزا برای خودش یک فیلتر دارد. به شکل 17 نگاه کنید؛ سه صفحه از فیلتر بهصورت موازی باهم روی سه صفحه از تصویر حرکت میکنند. با فرض اینکه فیلتر 5×5 باشد، در هرلحظه 25 ضرب در هرصفحه انجام میشود و درمجموع برای سه صفحه میشود 75 ضرب… درنهایت خروجی برابراست با جمع این 75 ضرب. پس درنهایت نتیجه ضرب سه صفحه باهم جمع میشود و خروجی نهایی یک تصویر 100×100×3 نیست، بلکه یک تصویر 100×100 است.

حالا مثلا مثلا مثلا، اگر ورودی بجای 3 صفحه، 10 صفحه داشته باشد، برای خروجی چه اتفاقی میافتد؟ فیلتر چه شکلی خواهد بود؟ فیلتر باید 10 صفحه داشته باشد و اگر فرضا فیلتر را 5×5 درنظر بگیریم، اندازه فیلتر به شکل 5×5×10 باشد. هریک از این 10 صفحه، در صفحه متناظر با صفحه تصویر کانوالو میشود. بعد از توضیح درباره کانولوشن، وقت آن رسیده که درباره لایه کانولوشنی در شبکه عصبی CNN توضیح بدهم. برویم سراغ بخش بعدی…
لایه کانولوشن در شبکه عصبی کانولوشن (Convolutional Layer)
هسته اصلی شبکه CNN لایه کانولوشنی است که درصد اعظم محاسبات شبکه عصبی کانولوشن را به خود اختصاص داده است. هر لایه کانولوشن در شبکه عصبی کانولوشن شامل مجموعهای فیلتر است و از کانولوشن بین فیلترها و لایه ورودی است که خروجی ساخته میشود. به خروجی لایه کانولوشنی، فیچرمپ (Feature Map) گفته میشود. میخواهم این جمله را کمی بشکافم و دقیق بگویم چیست.
چرا یک لایه کانولوشنی نیاز است؟
همانطور که بخش قبلی گفتم، یک فیلتر میتواند شامل یک الگویی خاص باشد و در تصویر به دنبال آن الگو باشد. اتفاقا در فرآیند آموزش شبکه، به دنبال این هستیم که این فیلترها الگوهای معناداری از هر تصویر استخراج کنند. مثلا، فیلتری داریم که شامل الگوی گوش گربه هست و میتواند حضور یک گربه در تصویر ورودی را تشخیص دهد. اما فقط به یک الگو بسنده کنیم؟ آیا گربه، فقط شامل یک الگوی خاص است؟ مثلا، دهان، گوشها، چشمها و دُم هرکدام الگوی خاصی از گربه نیستند؟ با یک فیلتر میشود همه این الگوها را شکار کرد؟ بهتر نیست شواهد بیشتری جمع کنیم و بعد تصمیم بگیریم؟
این همان کاری است که در لایه کانولوشنی انجام میشود. جستجو در تصویر برای یافتن تنها یک الگو منجر به نتایج خوبی نمیشود و باعث میشود شبکه از لحاظ کارایی محدود باشد. برای حل این مشکل، نیاز است که لایه کانولوشنی چندین فیلتر داشته باشد. هریک از فیلترها به تنهایی یک الگوی خاص داشته باشند و خروجی لایه کانولوشنی مجموعهای از الگوهای مختلف باشد. در شکل زیر، تصویری نشان داده شده که یک تصویر با فیلترهای مختلفی کانوالو شده و هر فیلتر هم یک خروجی جداگانه برای خود تولید کرده است. همه تصاویر مربوط به یک چهره با ماسک هست؛ اما دقت کنید که در هر تصویری یک مجموعه ویژگی خاص مورد توجه بوده است. مثلا در تصویری، لبههای افقی، در دیگری لبههای عمودی و غیره. در شکل زیر 64 فیلتر مختلف به تصویر ورودی اعمال شده است.

اما، یک لایه کانولوشنی در شبکه عصبی کانولوشن را چگونه نمایش میدهند؟ بریم ببینیم…
نمایش لایه کانولوشن
نمایش بلوک دیاگرامی شکل زیر، یک نمایش مرسوم برای لایه کانولوشنی است. حالا جزبهجز درمورد شکل 19 توضیح میدهم؛ اول، تکلیف ورودی را مشخص کنم که یک تنسور سه بعدی است. دوم، فیلترها را مشاهده میکنید. دیگر یک فیلتر نداریم بلکه یک مجموعه فیلتر داریم (دراینجا N تا فیلتر). معمولا همه فیلترها را یکجا در قالب یک تنسور چهاربعدی نمایش میدهند. از سمت چپ به راست، هریک از ابعاد موجود در فیلتر معادل با موارد زیر هستند:
- بعد اول، معادل با تعداد سطر (5)
- بعد دوم، معادل با تعداد ستون (5)
- بعد سوم، معادل با تعداد کانال (3)
- بعد چهارم، معادل با تعداد فیلتر (N)
یعنی یک تنسور چهاربعدی به ابعاد 5×5×3×N معادل با N فیلتر به ابعاد 3×5×5 است. پس قراراست روی تصویر ورودی، N فیلتر اعمال شود. شما به عنوان یک متخصص مجاز هستید که اندازه سطر و ستون و تعداد فیلترها را خودتان تعیین کنید. اما بعد سوم (تعداد کانالها)، از روی تعداد کانال ورودی تعیین میشود. چرا؟ خودتان دلیلش را بگویید، در آخر بخش قبلی توضیح دادم.
درنهایت، خروجی یک فیچرمپ است به ابعاد 100×100×N که عدد سوم برابر با تعداد فیلترهاست. چرا؟ چون هر فیلتر یک خروجی تولید میکند و با N فیلتر، ما N صفحه خواهیم داشت. بنابراین، مطابق شکل زیر خروجی یک لایه کانولوشنی یک تنسور سه بعدی خواهد بود. اگر به شکل 18 دوباره نگاه کنید، میبینید که تعداد فیلترها برابر با N=64 است و خروجی هم یک تنسور سه بعدی با 64 صفحه هست. البته، در شکل 18، این 64 صفحه بازشده و بهصورت جداگانه نمایش داده شده است.

خب، انتظار دارم با لایه کانولوشنی آشنا شده باشید. اما همواره با کانولوشن، مفاهیم یا پارامترهای مهمی مطرح میشوند که دانستن آنها ضروری است. درادامه، این پارامترها را به شما معرفی میکنم.
تعداد فیلتر در لایه کانولوشن
تعداد فیلترها، تعداد ویژگیهای تشخیص داده شده را نشان میدهد. این هایپرپارامتر معمولا نمایی از 2 (معمولا بین 32 تا 4096) انتخاب میشود. معمولا استفاده از فیلترهای بیشتر باعث ایجاد یک شبکه عصبی قدرتمندتر میشود. اما این افزایش پارامترها ممکن است باعث overfitting شود.
اندازه فیلتر در لایه کانولوشن
اندازه فیلتر معمولا به صورت مربعی (برای تصاویر) و 3×3 تعریف میشود. اما 5×5 و 7×7 نیز گاهی استفاده میشود. با فیلترهای کوچک، تعداد پارامترهای قابل یادگیری بسیار کمتر میشود.
اندازه پدینگ (padding) در لایه کانولوشن
در بخش قبل دیدید که بعد از فیلتر کردن یک ماتریس 5×5 با فیلتر 3×3، سایز خروجی دو سطر و ستون کمتر از ماتریس ورودی داشت. گفتیم که با لایه گذاری یا padding میتوانیم به صورت جعلی اندازه ورودی را افزایش دهیم تا ماتریس خروجی هماندازه ماتریس ورودی بشود. درمورد لایه گذاری صحبت بسیار است و معمولا در پردازش تصویر بررسی میشود. اما یک راه ساده و رایج آن اضافه کردن سطر و ستون صفر بهصورت متقارن به دور ماتریس ورودی است. به لایه گذاری صفر zero padding گفته میشود. به شکل 20 نگاه کنید که چگونه یک لایه صفر دور ماتریس قرار گرفته است. حالا فیلتر کانولوشنی ما فضای بیشتر برای گام برداشتن و اسکن کردن دارد و طبیعتا خروجی هم بزرگتر میشود.

تمرین: علامتهای سوال شکل بالا را پر کنید!
از کجا بفهمیم چه تعداد لایه باید دور ماتریس اضافه کرد؟ بستگی به اندازه فیلتر دارد؛ قبلا هم گفته بودم که به اندازه n – (k-1)/2 از ماتریس خروجی کاسته میشود. بسیارخب، واضح است که به همین اندازه باید سطر و ستون به ماتریس ورودی اضافه کنیم. مثلا فرمول n – (k-1)/2 برای فیلتر 3×3 میگوید که باید به ماتریس ورودی 2 سطر و ستون اضافه شود. یک سطر را بالای ماتریس ورودی قرار میدهیم و یکی را هم پایین ماتریس… یک ستون را سمت چپ ماتریس و دیگری را سمت راست… بهعنوان مثالی دیگر، اگر اندازه فیلتر 5×5 باشد، چهار سطر و ستون باید به ماتریس ورودی اضافه شود.
اندازه stride در لایه کانولوشن
اندازه stride را میتوان گام هم تعریف کرد. منظور از گام این است که فیلتر بعد از محاسبه در یک پنجره از ورودی، چند درایه یا خانه باید جلو برود تا دوباره محاسبات را انجام دهد. اگر اندازه گام را 1 درنظر بگیریم، یعنی فیلتر باید درایه به درایه در ورودی به سمت راست و پایین برود و اسکن را انجام دهد. یعنی، همان صحبتهایی که در بخش قبل (کانولوشن) داشتم. اما اگر مثلا stride عدد 2 درنظر گرفته شود، طبق شکل 21 مشاهده میکنید که دوتا دوتا میپرد.

معمولا stride در لایه های کانولوشنی عدد 1 درنظر گرفته میشود.
عملگر کانولوشن، یکی از مهمترین مولفههایی است که باعث میشود CNN نسبت به تغییرات مکانی مقاوم باشد. برای یک فیلتر با یک الگوی گربه، فرقی ندارد که گربه در کجای تصویر قرار دارد. درهرصورت آن گربه در تصویر را تشخیص میدهد. چون فیلتر کل تصویر را اسکن میکند و فارغ از موقعیت، گربه را تشخیص دهد.
لایه غیرخطی در شبکه عصبی کانولوشن (Activation Function)
مشابه با سایر شبکه های عصبی، شبکه عصبی کانولوشن هم از تابع تحریک یا activation function غیرخطی بعد از لایه کانولوشنی استفاده میکند. استفاده از تابع غیرخطی باعث ایجاد خاصیت غیرخطی در شبکه عصبی میشود که خیلی مهم است. بعضی از فریمورکها در همان تعریف لایه کانولوشنی، به شما این امکان را میدهند که نوع تابع غیرخطی را هم مشخص کنید. در بعضی از فریمورکها هم باید یک لایه جداگانه بسازید. تعریف تابع غیرخطی به صورت جدا از لایه کانولوشنی انعطاف پذیری بیشتری ایجاد میکند.
در بین تمام توابع غیرخطی، تابع ReLU بیشترین محبوبیت را دارد. البته، از خانواده ReLU اعضای دیگری مانند PReLU Leaky-ReLU و غیره وجود دارند. در شکل زیر، تعدادی از توابع غیرخطی را مشاهده میکنید. بهعنوان نمونه، تابع ReLU مقادیر کوچکتر از صفر (منفی) را صفر و مقادیر بزرگتر از صفر را بدون هیچگونه تغییری به خروجی میبرد.

محاسبات موجود در تابع ReLU ساده و صرفا یک مقایسه است. به همین خاطر محاسبات در بخش تابع غیرخطی با استفاده از ReLU نسبت به سایر توابع غیرخطی (مانند tanh sigmoid) با سرعت بیشتری انجام میشود. از طرفی فرآیند آموزش با ReLU نسبت به سایر توابع غیرخطی سریعتر است. چون، توابع غیرخطی tanh sigmoid در مقادیر خیلی بزرگ و کوچک به اشباع میرسند و این باعث میشود که گرادیان این توابع به سمت صفر میل کند. درنتیجه کل فرآیند بهینه سازی یا بهتر بگویم آموزش با سرعت پایینتری نسبت به ReLU انجام میشود. در شکل زیر، خروجی یک نمونه اعمال تابع تحریک ReLU به یک ماتریس ورودی را مشاهده میکنید.

لایه پولینگ در شبکه عصبی کانولوشن (Pooling Layer)
لایه پولینگ یکی دیگر از لایه های مهم در شبکه عصبی کانولوشن است. هدف لایه پولینگ کاهش اندازه مکانی فیچرمپ بدست آمده با استفاده از لایه کانولوشنی است. لایه پولینگ پارامتر قابل آموزش ندارد. صرفا یک نمونه برداری ساده و موثر انجام میدهد. پولینگ عملکردی شبیه کانولوشن دارد و یک پنجره روی تصویر حرکت میکند. رایجترین نمونه پولینگ max pooling و average pooling است. مطابق شکل 24 ماکس پولینگ شامل یک پنجره از پیش تعریف شده است (مثلا 3×3) که روی تصویر حرکت میکند (تصویر را اسکن میکند) و در هر پنجره مقدار ماکزیمم را انتخاب میکند و بقیه را دور میریزد. مشابه با لایه کانولوشنی، اندازه فیلتر، پدینگ و استراید در اینجا هم صدق میکند. در پولینگ، معمولا استراید 2 و اندازه فیلتر 3 درنظر گرفته میشود. برای اینکه سایز ویژگی به 1/2 کاهش پیدا کند.

در شکل زیر یک نمونه کامل از max pooling نشان داده شده است.

همچنین، در شکل زیر یک نمونه کامل از average pooling نشان داده شده است.

صبر کنید، صبر کنید! تا اینجا لایه کانولوشن، لایه غیرخطی و لایه پولینگ را آموختید. اما قبل از اینکه بخشهای بعدی را توضیح بدهم، میخواهم یک شبکه عصبی کانولوشن نصف و نیمه با همین ماژولها بسازم. برای ساخت یک شبکه کانولوشنی ساده لازم است نکاتی را مدنظر داشته باشید. بخش بعدی را دقیق بخوانید…
طراحی شبکه عصبی کانولوشن ساده
من در شکل 27 یک شبکه کانولوشن نصف و نیمه ساختم. به شکل نگاه کنید و نکتههای زیر را بخوانید:
- معمولا در شبکه عصبی کانولوشن، ابتدا لایه کانولوشنی قرار میگیرد. یعنی، قبل از پولینگ، تایع غیرخطی و غیره. ابتدا، یک لایه کانولوشنی روی تصویر با تعداد فیلتر کم اعمال میشود. لایه 1، با 32 فیلتر را در شکل زیر مشاهده میکنید. پدینگ و استراید طوری تنظیم شده که سایز فیچرمپ خروجی کم نشود. اندازه فیچرمپ خروجی برابر با 32×32×32 است.
- معمولا بعد از لایه کانولوشنی، یک تابع تحریک قرار داده میشود. طبیعی است، چون اگر یادتان باشد در نورون هم بعد از جمع وزندهی و جمع با بایاس، تابع تحریک اعمال میشد. طبق شکل زیر، یک تابع ReLU روی فیچرمپ اعمال میشود. کارش هم اینست که منفیها را صفر میکند، تمام!
- حالا میخواهم یک لایه پولینگ بعد از تابع تحریک قرار دهم. این پولینگ از نوع ماکس پولینگ است. اما استراید را 2 درنظرگرفتم که اندازه فیچرمپ خروجی نصف شود. پس اندازه فیچرمپ خروجی برابر با 16×16×32 خواهد شد.
- تا اینجا یک دور کانولوشنی، غیرخطی و پولینگ را به تصویر ورودی اعمال کردم. دوباره هرسه اینها را اعمال میکنم. تعداد فیلترهای لایه کانولوشنی دوم را 64 درنظر گرفتم. فیچرمپ خروجی ماکس پولینگ: 8×8×64
- در سومین مرحله، یک لایه کانولوشنی با 128 فیلتر درنظر گرفته شده و استراید هم 2 است. بعد از لایه کانولوشنی یک تابع تحریک گذاشتم. فیچرمپ خروجی تابع تحریک برابر با 4×4×128 است.
- در آخر یک لایه میانگین پولینگ با سایز 4×4 گذاشتهام. فیچرمپ خروجی این لایه برابر با 1×1×128 خواهد شد. یعنی، تصویر ورودی بعد از چندمرحله تبدیل به یک بردار به طول 128 شد. این بردار، بردار ویژگی تصویر ورودی است. این بردار شناسنامه یا اثر انگشت تصویر است. به چه دردی میخورد؟ دربخش بعدی خواهید دید…

لایه فولی کانکتد در شبکه عصبی کانولوشن (Fully Connected Layer)
معمولا آخرین لایه های یک شبکه عصبی کانولوشن برای طبقه بندی را لایه های فولی کانکتد تشکیل میدهند. این لایه ها همان لایه هایی هستند که در شبکه عصبی MLP دیدهاید. برای آشنایی بیشتر این مقاله را بخوانید. یکی از کاربردهای اصلی لایه فولی کانکتد در شبکه کانولوشن، استفاده به عنوان طبقه بند یا کلاسیفایر (Classifier) است. یعنی مجموعه ویژگیهای استخراج شده با استفاده از لایه های کانولوشنی درنهایت تبدیل به یک بردار میشوند. درنهایت این بردار ویژگی به یک کلاسیفایر فولی کانکتد داده میشود تا کلاس درست را شناسایی کند. شکل 28 را با فولی کانکتد کامل کردم و میتوانید در شکل زیر مشاهده کنید. شکل زیر بلوک دیاگرام کامل یک شبکه عصبی کانولوشن ساده برای طبقه بندی است.

سوالات رایج درباره شبکه عصبی کانولوشن
انتظار دارم بعد از خواندن این پست بلندوبالا، یک عالمه سوال برایتان ایجاد شده باشد. من به تعدادی از سوالات رایج دانشجویان در ادامه جواب دادهام. سعی میکنم این بخش را بازهم با سوالات جدید تکمیل کنم. سوالاتی مثل:
- همیشه اول لایه کانولوشنی، تابع تحریک و بعد هم پولینگ؟ خیر، ممکن است تصمیم بگیریم چندلایه پشت هم کانولوشنی بگذاریم و بعد یک پولینگ قرار دهیم. برچه اساسی این کار انجام میشود؟ جواب این سوال سخت است، باید مقالههای مختلف در حوزه شبکه عصبی کانولوشن را بخوانید.
- چرا رفته رفته در شبکه جلوتر رفتیم، تعداد فیلترهای لایه کانولوشن بیشتر شد؟ سعی میکنیم رفته رفته ویژگیهای بیشتر و متنوعتری تولید کنیم.
- ماکس پولینگ و پولینگ میانگین را برچه اساسی انتخاب میکنیم؟ معمولا در لایههای میانی از ماکس پولینگ استفاده میشود و در پایان شبکه لایه پولینگ میانگین بهکار گرفته میشود.
- حالا این بردار ویژگی 128-تایی به چه دردی میخورد؟ همان ابتدا گفتم که من میخواهم یک شبکه عصبی کانولوشن برای طبقه بندی بسازم. این خروجی به من کمک میکند که من فرق بین گربه و سگ را شناسایی کنم. باید بردارهای ویژگی 128-تایی بهگونهای باشد که بردار ویژگی تمام تصاویر گربه شبیه هم باشد. همچنین، بردار ویژگی تصاویر سگ مشابه هم باشد، اما بردار ویژگی سگ و گربه باید از هم دور باشند. دو مجموعه بردار ویژگی داریم که گربهها نزدیک هم و سگها نیز نزدیک بههم است. پس، بردار ویژگی یک تصویر جدید تستی، به هرکدام از این دو بردار که بیشتر شبیه باشد، به همان کلاس متعلق هست.
دریافت PDF آموزش شبکه عصبی کانولوشن
برای شما هدیهای داریم. 😊 خیلی علاقهمند بودیم که PDF پست شبکه عصبی CNN را آماده کنیم. اتفاقا خیلی از دوستان هم پیام میدادند و PDF این آموزش را میخواستند. بالاخره بعد از مدتها، در تاریخ 17 آذر 1400 این PDF آماده شد. 😍 اگر بگوییم یک کتابچه شده، بیراه نگفتهایم! از طریق لینک زیر میتوانید دریافت کنید.
آموزش یادگیری عمیق رایگان
آموزش شبکه CNN یکی از سلسله پستهای آموزش یادگیری عمیق رایگان است. در جدول زیر، فهرست مطالب دوره آموزش یادگیری عمیق رایگان هوسم را آوردهایم.
جلسه 01: نورون مصنوعی
نورون مصنوعی
جلسه 02: یادگیری نورون مصنوعی
یادگیری نورون مصنوعی
جلسه 03: شبکه عصبی MLP
شبکه عصبی MLP
جلسه 04: پروژه دستهبندی MLP با تنسورفلو
دسته بندی با mlp و تنسورفلو
جلسه 05: پروژه رگرسیون MLP با تنسورفلو
رگرسیون با mlp و تنسورفلو
جلسه 06: شبکه عصبی کانولوشن (CNN)
شبکه عصبی کانولوشن
جلسه 07: شبکه عصبی بازگشتی (RNN)
شبکه عصبی بازگشتی
جلسه 08: شبکه عصبی GAN
شبکه عصبی GAN
جلسه 09: شبکه عصبی LSTM
شبکه عصبی LSTM
واقعا به پایان رسیدم؟!
جواب کوتاه اینکه، نه! 😅 اما، خداقوت، گام بزرگی برداشتید. از ابتدا تا انتهای یک شبکه CNN را خدمت شما توضیح دادم. امیدوارم، با این شبکه محبوب ارتباط برقرار کرده باشید. مفاهیم و لایههای دیگری نیز در شبکه عصبی کانولوشن وجود دارد. مثلا، باید درباره Batch Normalization, Dropout, Data Augmentation, Receptive Field هم توضیح بدهم. اما فعلا تا همین اندازه کافی است و بقیه بماند در پستی جداگانه… لطفا فیدبک بدهید، این آموزش چطور بود؟ در کامنت درباره این پست با ما صحبت کنید. هوسم را هم فراموش نکنید و لطفا به دوستانتان هم معرفی کنید…
مطالب زیر را حتما مطالعه کنید
LLM Research یا LLM Engineering؟ راهنمای یادگیری مدلهای زبانی بزرگ
یادگیری انتقالی
شبکه VGG
مهندسی پرامپت
مدل nanoGPT
شگفتانگیزترین ایده هوش مصنوعی از نظر Andrej Karpathy
278 دیدگاه
به گفتگوی ما بپیوندید و دیدگاه خود را با ما در میان بگذارید.

سلام.
ممنون از آموزش کاربردی و مفیدتون
لطفا اگر پاورپویینت در خصوص این روش بصورت آماده دارید به اشتراک بگذارید.
با تشکر
شنیدین قدیمیا میگفتن ذکات علمت رو بده؟ مصداق این مقالست . چقدر شیوا و رسا و ساده و سلیس و سهل و ممتنع و … تو این مقاله شما جمع شده بود . چقدر گره ذهنی از من باز کرد .کلی انرژی مثبت و دعای خیر نصیبتان باد
ممنون امیرحسین عزیز،
موفق باشید 🌺
سلام وقت بخیر
تبریک بابت نثر شیرین و جذاب این همه سخاوت در نشر دانش.
پاینده باشید
فوق العاده است کارتون
سلام
خداقوت
بسیار عالی و واضح بود.
تشکر
سلام و با سپاس بسیار از آموزش بسیار زیبا و شیوا و ساده تون
در شکل 27 و 28 چرا سایز فیلتر Max-Pooling را سه در سه(3*3) انتخاب کردید و 2*2 انتخاب نکردید؟ دلیل خاصی داشت؟
اگر از فیلتر سه در سه(3*3) و طول گام 2 استفاده کنیم، خروجی ما 15*15 میشه نه 16*16.ضمن اینکه عنصر 32 ام چه در طول چه در عرض نادیده گرفته میشه و اینگور میشه.برای همین خروجی 15*15 میشه.مگر اینکه در لایه پولینگ(ادغام) نیز از padding استفاده کنیم.که اشاره ای به آن نکردید
فرمولی که چت جی پی تی بهم داد(درحالت بدون پدینگ)
Output Size=( (Input Size−Filter Size) +1) /Stride
سلام
خوشحالیم که آموزش رو دوست داشتید. 😊
دلیل خاصی نداشت. صرفا یک مثال بود. رایجتر هست که 2×2 باشه.
اگر پدینگ رو درست تنظیم کنید (عدد 1 برای فیلتر 3×3)، خروجی 16×16 میشه. شاید در همین آموزش ذکر شده باشه که معمولا پدینگ رو برای لایههای کانولوشنی و پولینگ فعال میکنیم. اتفاقا اگر به شکل 27 نگاه کنید، مشاهده میکنید که خروجی لایه کانولوشنی هم به شکلی هست که باید پدینگ فعال باشه.
سلام بسیار عالی بود من روی تعداد فیلترها مشکل داشتم که با توضیحات و انیمیشن ها تا عمق مغزم رفت. سپاس فراوان دست مریزاد
بسیار عالی 😊
ممنون 🙏
موفق باشید 🌺
سلام
آقا بذار خداوکیلی دستتو ببوسم! نه خداوکیلی بذار ببوسم! ۱۰ ساعته دارم یه مقاله رو بالا پایین میکنم دریغ از اینکه یه جمله شو بفهمم الان که اومدم مقاله شما رو خوندم ۸۰ درصدش رفع شد. شاید اون ۲۰ درصد باقیمونده رو هم از تو همین سایت پیدا کردم. دمت گرمه مشتی
سلام، باعث خوشحالی و افتخاره که آموزش شبکه عصبی کانولوشن بهتون کمک کرده. 😊🙏🌹
سلام، واقعا عالی بود، خدا خیرتون بده
سلام، سپاس 🌹🙏😊
سلا و عرض ادب
فوق العاده بود مفید و مختصر
لطفا آموزش موارد زیر هم بذاری
Batch Normalization, Dropout, Data Augmentation, Receptive Field
سلام
سپاس 🙏
حتما برای موارد ذکر شده آموزش تهیه میکنیم.
سلام خانم مرجان می دونم برای کدوم مقاله اینارو می خوای خودمم ارائه اون مقاله رو دارم
سلام, چقد زیبا و منطقی, از سیر تکامل منطقی این شبکه کانوولوشنی بسیار لذت بردم, این میسر نمیشد مگر با بیان و نگارش زیبا,
واقعا خسته نباشید میگم به تیم هوسم, به جرئت بهترین منبع فارسی برای هوش مصنوعی هستید
سلام
نظرتون برای ما بسیار ارزشمند هست.
ممنون 🙏🌹
سلام من که اصلا با این موضوعات آشنا نبودم بالاخره فهمیدم دمت گرم استاد.
😊🙏🌹
سلام
واقعا دست مریزاد .همیشه موفق و سلامت باشید.
سلام
سپاس از شما که 250-مین کامنت این آموزش رو ثبت کردید. 🙏😊
همچنین، از همه دوستانی که کامنت گذاشتن و تشویقمون کردن، بسیار بسیار ممنونیم. تشویقهاتون شبیه نوشابه انرژیزاست.
موفق باشید ✌