اخبار

مقدمه

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

رگرسیون بردار پشتیبان خطی

مدل SVR (رگرسیون بردار پشتیبان)، یک مدل یادگیری ماشین نظارتی محسوب می‌شود که  نوع خاصی از مدل) SVM ماشین بردار پشتیبان) است. مدل ماشین بردار پشتیبان در حل مسائل دسته‌بندی بسیار کاربرد دارد.

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

شکل 1: طرح کلی الگوریتم رگرسیون بردار پشتیبان

مدل SVR از ایده‌ی SVM در راستای ایجاد یک مدل رگرسیونی استفاده می‌کند. در SVR به جای خط یا صفحه‌ی جداکننده‌ی کلاس‌ها، یک محدوده‌ی مشخصی‌ ایجاد می‌گردد که با پارامتر ε مشخص می‌شود و در وسط این محدوده، خط رگرسیونی را داریم.

شکل 1 ایده‌ی کلی مدل رگرسیون بردار پشتیبان را نمایش می‌دهد. ایده‌ی اصلی در اینجا این است که یک خط رگرسیونی را با استفاده از داده‌ها برازش دهیم و سپس نمونه‌هایی را که از خط رگرسیونی در فاصله کنترل‌شده قرار دارند را انتخاب کنیم. این نمونه ها به طور صحیح توسط خط رگرسیونی تعیین می‌شوند و مقدار متغیر پاسخ آن‌ها به درستی توسط خط رگرسیونی تعیین می گردد. در واقع نمونه‌هایی که حداکثر ε واحد از خط رگرسیونی فاصله دارند را به عنوان نمونه‌هایی که به درستی پیش‌بینی شده‌اند در نظر می‌گیریم اما برای نمونه‌هایی که بیشتر از ε واحد از خط رگرسیونی فاصله دارند، برای مدل خطا و جریمه لحاظ می‌کنیم.

ناحیه‌ی امن اطراف خط رگرسیونی دارای عرض ε است. منظورمان از عرض، فاصله‌ی خط رگرسیونی تا مرز ناحیه در امتداد محور عمودی است نه عمود بر مرز محدوده. 

الگوریتم رگرسیون بردار پشتیبان به مقدار ε حساس است؛ به این معنی که مدل، خطای هر نمونه از مجموعه داده که در داخل محدوده قرار بگیرد را برابر با صفر در نظر می‌گیرد. در شکل 2 ناحیه‌ی امن با خط زرد مشخص شده است.

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

برای نقاطی که خارج از ناحیه‌ی امن قرار می‌گیرند، خطا در نظر می‌گیریم و آن را با فاصله‌ی بین داده و مرز ناحیه‌ی امن مشخص می‌کنیم و این مقدار را متغیر Slack می‌نامیم.

شکل 2: ناحیه‌ی امن رگرسیون بردار پشتیبان

بردار پشتیبان

نمونه‌هایی که خارج از ناحیه‌ی امن قرار دارند، تعیین می‌کنند که ناحیه‌ی امن چگونه به نظر برسد و چگونه در صفحه‌ی مختصات قرار بگیرد. این نمونه‌های خارج از محدوده‌ی امن به عنوان بردارهای پشتیبان شناخته می‌شوند و به تشکیل ناحیه‌ی امن حساس به ε کمک می‌کنند. شکل 3 نمونه‌های خارج از ناحیه‌ی امن و فواصلشان با ناحیه‌ی امن (متغیرهای Slack) را نشان می‌دهد.

شکل 3: نحوه‌ی اندازه‌گیری متغیرهای Slack

این روش رگرسیون بردار پشتیبان نامیده می‌شود. می‌توان همه‌ی نمونه‌ها را به‌ صورت‌ بردار در فضای دو بعدی یا در صورت داشتن ویژگی‌های بیشتر در مجموعه داده، در فضای چندبعدی نمایش داد.

رگرسیون بردار پشتیبان غیرخطی

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

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

شکل 4: خطی‌جدایی‌پذیرسازی کلاس‌ها به کمک تکنیک تابع نگاشت مناسب

اینجا ما از یک تابع نگاشت (تابعی که داده‌های با ابعاد پایین‌تر را به فضایی با ابعاد بالاتر منتقل می‌کند) استفاده کردیم. بنابراین تابع نگاشت، نمونه‌های مجموعه داده‌ی ما را به فضایی با ابعاد بالاتر ارتقا می‌دهد.

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

در رگرسیون بردار پشتیبان غیرخطی با استفاده از تکنیک تابع هسته فضای ابعادی داده‌ها را افزایش می‌دهیم. تا به امروز انواع مختلفی از توابع هسته معرفی شده‌اند. برخی از توابع هسته‌ی پراستفاده عبارتند از: هسته‌ی RBF یا گاوسی، هسته‌ی چندجمله‌ای، هسته سیگموید.

در شکل 5 نمونه‌ای از یک رابطه‌ی غیرخطی را مشاهده می‌کنیم. مدل ساده‌ی رگرسیون بردار پشتیبان  از پس چنین رابطه‌ی برنمی‌آید اما اگر بتوانیم این مجموعه داده را به فضایی با بعد بالاتر منتقل کنیم، امکان اینکه مدل رگرسیون بردار پشتیبان بتواند به کمک یک خط رگرسیونی و یک حاشیه‌ی امن مناسب بتواند آن را مدل کند فراهم می‌شود.

هسته‌ها، توابع ریاضی‌ای هستند که شباهت بین دو نمونه از داده را در فضای ویژگی با ابعاد بالا اندازه‌گیری می‌کنند. این توابع، ما را قادر می‌سازند تا با اعمال تبدیل‌های غیرخطی روی مجموعه داده، به الگوریتم‌های خطی‌ مانند رگرسیون بردار پشتیبان کمک کنیم تا به طور مؤثر داده‌های غیرخطی را در فضای با ابعاد بالاتر به صورت خطی مدلسازی و بیان کنند.

 

شکل 5: نمونه‌ای از یک رابطه‌ی غیرخطی

هسته‌ی RBF

هسته RBF یکی از پرکاربردترین هسته‌ها در یادگیری ماشین به‌ویژه در ماشین‌ بردار پشتیبان و رگرسیون بردار پشتیبان است.  

هسته‌ی RBF شباهت نمونه‌ها را بر اساس فاصله‌ی شعاعی بین دو نمونه محاسبه می‌کند. این تابع هسته از توزیع احتمال نرمال برای اندازه‌گیری شباهت بین دو نمونه در فضای ویژگی‌ها استفاده می‌کند و برای نقاطی که به هم نزدیک‌تر هستند، مقادیر بالاتر و برای نقاطی که از هم دورتر هستند مقادیر کمتری را لحاظ می‌کند. فرمول هسته RBF به صورت زیر است:

در فرمول بالا:

 x و x' دو نمونه‌ای هستند که با هم مقایسه‌شان می‌کنیم و قصد داریم فاصله‌شان را بسنجیم.

 نشان‌دهنده‌ی مجذور فاصله‌ی اقلیدسی بین x و x' است.

) σسیگما) یک ابرپارامتر است که عرض (انحراف معیار) توزیع نرمال را کنترل می‌کند. مقادیر کوچک‌تر سیگما، تابع هسته را نسبت به فاصله، کمتر حساس می‌کند‌؛ در حالی که مقادیر بزرگ‌تر آن، هسته را نسبت به فواصل حساس‌تر می‌کند.

هسته‌ی RBF به مدل‌های مبتنی بر بردار پشتیبان اجازه می‌دهد تا مرزهای تصمیم‌گیری پیچیده را با نگاشت داده‌ها به فضایی با ابعاد بالاتر، کاملا ساده کنند؛ طوری که داده‌ها به صورت خطی قابل بیان با مدل شوند. 

این نگاشت به طور ضمنی و بدون محاسبه‌ی صریح و کامل فضای ویژگی جدید انجام می‌شود. تکنیک استفاده از تابع هسته، که هسته RBF بخشی از آن است، از نیاز به این محاسبات صریح جلوگیری می‌کند و به همین دلیل ماشین بردار پشتیبان و رگرسیون بردار پشتیبان از نظر محاسباتی کارآمد و مناسب هستند.

تابع هسته‌ی چند‌جمله‌ای یکی دیگر از هسته‌های متداول در یادگیری ماشین است. مشابه هسته‌ی RBF، هسته چندجمله‌ای نیز به مدل اجازه می‌دهد تا ‌دسته‌بندی خطی را در فضای ویژگی‌های با ابعاد بالا انجام دهد و داده‌های غیرخطی را به صورت خطی قابل تفکیک می‌کند.

هسته‌ی چندجمله‌ای

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

فرمول تابع هسته چندجمله‌ای به صورت زیر است:

در فرمول بالا:

 x  و x’ دو نمونه‌ی داده‌ای هستند که ما با هم مقایسه می‌کنیم و می‌خواهیم فاصله‌شان را بسنجیم.

x, x'حاصلضرب داخلی بین x و x’ در فضای ویژگی‌ اصلی است. این عبارت شباهت یا تطابق بین دو بردار را اندازه‌گیری می‌کند.

 c   یک عبارت ثابت است که کمترین میزان ممکن شباهت را تعیین می‌کند. اغلب روی 0 یا 1 تنظیم می‌شود.

 d   درجه‌ی تابع چند‌جمله‌ای است. این ابرپارامتر، پیچیدگی مرزهای تصمیم را کنترل می‌‌کند. مقادیر بالاتر d درجه‌ی هسته‌ی چند‌جمله‌ای را افزایش می‌دهد و به تابع هسته اجازه می‌دهد تا روابط پیچیده‌تری را بین نمونه‌های مجموعه داده ثبت کند.

این تابع هسته به مدل کمک می‌کند تا مرزهای تصمیم‌‌گیری را که در فضای اصلی، غیرخطی هستند را به صورت خطی در فضای با بعد بالاتر پیدا کند. با افزایش قابل توجه ابعاد فضای ویژگی، هسته‌ی چندجمله‌ای انعطاف‌پذیری بیشتری را در مدل‌سازی مجموعه داده‌های پیچیده فراهم می‌کند.

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

هسته‌ی نمایی

تابع هسته‌ی نمایی که به عنوان هسته‌ی لاپلاس نیز شناخته می‌‌شود، یکی دیگر از توابع هسته‌ی محبوب است که در یادگیری ماشین، به ویژه در الگوریتم‌های ماشین بردار پشتیبان و رگرسیون بردار پشتیبان استفاده می‌شود.

          هسته‌ی نمایی مانند دیگر هسته‌ها به الگوریتم اجازه می‌دهد که داده‌های غیرخطی را با نگاشت ضمنی به یک فضای ویژگی با ابعاد بالاتر، به صورت خطی بیان کند.

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

فرمول هسته‌ی نمایی به صورت زیر است:

در فرمول بالا:

  x و x’ دو نمونه‌ی مجموعه داده هستند که ما آن‌ها را با هم مقایسه می‌کنیم.

 نشان‌دهنده‌ی فاصله‌ی اقلیدسی بین x و x’ در فضای ویژگی‌ها است.

) σ سیگما) ابرپارامتری است که تابع نمایی را کنترل می‌کند. مقادیر کوچک‌تر سیگما، هسته را نسبت به فاصله‌ی نمونه‌ها کمتر حساس می‌کند؛ در حالی که مقادیر بزرگتر آن، حساسیت را نسبت به فاصله‌ی نمونه‌ها افزایش می‌دهد. تنظیم این ابرپارامتر به کمک روش‌های اعتبارسنجی و یا روش‌های تنظیم مقدار ابرپارامتر، در عملکرد مدل عملکرد به‌سزایی دارد.

مزایای الگوریتم SVR

  1. توانا در مدل‌سازی فضاهای با ابعاد بالا: SVR  می‌تواند مجموعه داده‌هایی که تعداد زیادی متغیر مستقل دارند را خوبی مدیریت کند. این الگوریتم برای داده‌های با فضاهای ابعادی بالا کارآمد است و برای مسائلی که در آن تعداد زیادی از متغیرهای مستقل وجود دارند مناسب است.

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

  1. کنترل پیچیدگی مدل: SVR به شما اجازه می‌دهد تا پیچیدگی مدل را با تنظیم ابرپارامترها، مانند نوع تابع هسته و ابرپارامترهای مربوط به تابع هسته کنترل کنید. این انعطاف‌پذیری شما را قادر می‌سازد بین بیش‌برازش و کم‌ارزش مدل تعادل برقرار کنید و در نهایت به مدل مطلوب مسئله برسید.

  1. توانا در مدل کردن مجموعه داده‌های کوچک: SVR حتی زمانی که مقدار داده‌های آموزشی موجود، کم و محدود است نیز عملکرد خوبی دارد. این الگوریتم از مفهوم بردارهای پشتیبان، که نمونه‌های محدودی از داده هستند که بیشترین سهم را در تعریف تابع رگرسیونی دارند، استفاده می‌کند. به همین دلیل حتی وقتی مجموعه داده کوچک باشد نیز می‌تواند تنها با پیدا کردن بردارهای پشتیبان مناسب، تابع رگرسیونی مناسب حل مسئله را تخمین بزند. 

  1. بهینه‌سازی کلی: هدف فرآیند آموزش  SVR، یافتن یک جواب بهینه‌ی کلی است نه یک بهینه‌ی محلی؛ یعنی این الگوریتم بهترین راه‌حل ممکن را در کل فضای راه‌حل‌ها جستجو می‌کند که منجر به پیش‌بینی‌های دقیق‌تری می‌شود.

  1. تئوری ریاضیاتی تثبیت شده: SVR  بر اساس چارچوب الگوریتم ماشین‌ بردار پشتیبان (SVM) تعریف شده‌است که پایه‌ی نظری ریاضیاتی محکمی دارد. در سال‌های اخیر SVM به طور گسترده مورد مطالعه قرار گرفته و در حوزه‌های مختلف پژوهشی به کار گرفته شده‌است. این پشتوانه‌ی قوی، SVR  را به یک الگوریتم قابل اعتماد و پراستفاده تبدیل کرده است.

معایب الگوریتم SVR

  1. پیچیدگی: مدل SVR می‌تواند از نظر محاسباتی گران باشد؛ به خصوص برای مجموعه داده‌های بزرگ. آموزش مدل SVR روی مجموعه داده‌های بزرگ ممکن است به منابع محاسباتی و زمان قابل توجهی نیاز داشته باشد.

  1. حساسیت به ابرپارامترها: مدل SVR دارای ابرپارامترهای مختلفی مانند نوع تابع هسته، ابرپارامترهای مربوط به تابع هسته، ابرپارامتر منظم‌سازی تابع خطا و ε است. این ابرپارامترها برای دستیابی به عملکرد خوب باید با دقت تنظیم شوند و انتخاب ترکیب بهینه می‌تواند چالش‌برانگیز باشد. تنظیم نامناسب ابرپارامترها ممکن است منجر به مدل کم‌برازش یا بیش‌برازش مدل بشود.

  1. مصرف حافظه: مدل SVR زیر مجموعه‌ای از نمونه‌های آموزشی به نام بردارهای پشتیبان را در حافظه ذخیره می‌کند تا بتواند پیش‌بینی انجام دهد. اگر تعداد بردارهای پشتیبان در یک مسئله زیاد باشد، می‌تواند منجر به مصرف حافظه‌ی قابل توجهی بشود.

  1. عدم تفسیرپذیری: مدل SVR عموما به عنوان یک مدل جعبه سیاه در نظر گرفته می‌شود زیرا فاقد تفسیرپذیری و توضیح‌پذیری هستند. به دست آوردن بینش در مورد سازوکار داخل مدل یا درک دلایل پشت پیش‌بینی‌ها می‌تواند دشوار باشد.

  1. عملکرد ضعیف روی مجموعه داده‌های نویزی: مانند اکثر مدل‌های رگرسیونی، SVR  می‌تواند به نویز در داده‌های آموزشی حساس باشد. نمونه‌های آموزشی نویزی می‌تواند منجر به پیش‌بینی‌های غیربهینه‌ شود. بنابراین این الگوریتم نیازمند انجام مراحل پیش‌پردازش داده‌ها برای مدیریت نمونه‌های دورافتاده و نویز است.

  1. مقیاس پذیری: اگرچه SVR روی مجموعه داده‌های با اندازه‌ی متوسط ​​به خوبی کار می‌کند، ممکن است به طور کارآمدی برای مجموعه داده‌های بسیار بزرگ عمل نکند. آموزش یک مدل SVR بر روی میلیون‌ها یا میلیاردها نمونه‌ ممکن است به دلیل محدودیت‌ منابع محاسباتی، چالش‌هایی ایجاد کند.

  1. عدم انتخاب ویژگی خودکار: مدل SVR به صورت خودکار انتخاب ویژگی را انجام نمی‌دهد؛ به این معنی که به تمام ویژگی‌های ارائه شده به صورت یکسان نگاه می‌کند. اگر فضای ویژگی‌ها ابعاد بالایی داشته باشد یا دارای ویژگی‌های نامربوط یا اضافی باشد، می‌تواند بر عملکرد مدل تأثیر منفی بگذارد.

مثال عملی در پایتون

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

        با مجموعه داده‌ی گل‌های زنبق در پست‌های قبلی آشنا شدیم بنابراین از معرفی مجدد آن صرف نظر می‌کنیم و مستقیم به سراغ برازش مدل می‌رویم.

        برای این کار ابتدا باید مجموعه داده را فراخوانی کنیم و متغیرهای مستقل و وابسته را مشخص کنیم. در این مثال به می‌خواهیم به کمک متغیرهای طول و عرض گلبرگ و طول کاسبرگ گل‌های زنبق، مقدار عرض کاسبرگشان را پیش‌بینی کنیم.

        80 درصد مجموعه داده را به آموزش و 20 درصد باقیمانده را به آزمون مدل اختصاص می‌دهیم.

شکل 6: فراخوانی و آماده‌سازی مجموعه داده

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

شکل 7: فرایند تنظیم ابرپارامترها

حال مدل را بر روی مجموعه داده‌ی آموزشی، آموزش می‌دهیم و روی مجموعه داده‌ی آزمونی، ارزیابی می‌کنیم و در نهایت میانگین مربعات خطای مدل روی مجموعه داده‌ی آموزشی و آزمونی را چاپ می‌کنیم.

        شکل 8: خطای مدل روی مجموعه داده‌ی آموزشی و آزمونی

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

حال نمودار پراکندگی مقادیر واقعی عرض کاسبرگ‌های مجموعه داده‌ی آزمونی را در مقابل مقادیر پیش‌بینی‌شده‌ی متناظر رسم می‌کنیم تا شهود بهتری نسبت به عملکرد مدل پیدا کنیم.

شکل 9: ترسیم نمودار مقادیر واقعی متغیر هدف در مقابل مقادیر پیش‌بینی‌شده

نمودار مربوطه در شکل 10 نمایش داده شده است.

        شکل 10: نمودار مقادیر واقعی متغیر هدف در مقابل مقادیر پیش‌بینی‌شده

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

منبع

https://link.medium.com/iIBZ33KSNDb

( )( )( )( )( )
به این مطلب امتیاز دهید

نظرات

جهت ارسال نظر و دیدگاه خود باید ابتدا وارد سایت شوید