<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
    <channel>
        <title>نوشته های مجتبی میر یعقوب زاده</title>
        <link>https://virgool.io/feed/@TabaMojj</link>
        <description>فارغ التحصیل علوم کامپیوتر</description>
        <language>fa</language>
        <pubDate>2026-06-07 14:10:16</pubDate>
        <image>
            <url>https://static.virgool.io/images/default-avatar.jpg</url>
            <title>مجتبی میر یعقوب زاده</title>
            <link>https://virgool.io/@TabaMojj</link>
        </image>

                    <item>
                <title>کد تمیز و ساده بنویسید</title>
                <link>https://virgool.io/@TabaMojj/%DA%A9%D8%AF-%D8%AA%D9%85%DB%8C%D8%B2-%D9%88-%D8%B3%D8%A7%D8%AF%D9%87-%D8%A8%D9%86%D9%88%DB%8C%D8%B3%DB%8C%D8%AF-ftvqfurhzixp</link>
                <description>کد تمیز کدی است که خواندن، فهمیدن و تغییر آن راحت است. درست است که نوشتن کد تمیز بیشتر یک هنر است تا یک علم، اما صنعت مهندسی نرم‌افزار بر اصول متعددی توافق کرده است که اگر رعایت شوند، به نوشتن کد تمیزتر کمک می‌کنند. در این فصل، با ۱۷ اصل برای نوشتن کد تمیز آشنا می‌شویم.دقت کنید که این مطلب ترجمه‌ی خط به خط کتاب نیست و فقط قسمت‌های مهم آن آورده شده است.شاید درباره‌ی تفاوت بین کد ساده (simple) و تمیز (clean) کنجکاو باشید. این دو مفهوم بسیار به هم مرتبط هستند چون کد تمیز ساده است و کد ساده تمیز است. اما ممکن است با کد پیچیده‌ای مواجه شوید که تمیز است. سادگی در مورد این است که از پیچیدگی دوری کنیم. تمیزی یک قدم فراتر از این می‌رود و به مدیریت پیچیدگی اجتناب‌ ناپذیر نیز می‌پردازد. برای مثال، این کار را با استفاده‌ی موثر از کامنت‌ها و استانداردها انجام دهیم.بچرا کد تمیز بنویسیم؟در فصل‌های قبلی خواندیم که دشمن شماره‌ یک هر پروژه‌ی نرم‌افزاری، پیچیدگی است. کد تمیز، هم برای خود آینده‌تان و هم برای دیگر برنامه‌نویس‌ها، قابل فهم‌تر است. احتمال اینکه افراد در کد تمیز مشارکت داشته باشند و به آن اضافه کنند، بیشتر است. کد تمیز از هزینه‌های یک پروژه نیز کم می‌کند. همان‌طور که Robert C. Martin در کتاب Clean Code به این نکته اشاره می‌کند که بسیاری از برنامه‌نویس‌ها بیشتر زمان خود را صرف خواندن کدهای قدیمی می‌کنند تا بتوانند کد جدید بنویسند. اگر کد قدیمی را راحت بتوان خواند، پس سرعت فرآیند نوشتن کد جدید بیشتر خواهد بودنسبت زمانی که کد می‌خوانیم به زمانی که کد می‌نویسیم بیش از ۱۰ به ۱ است. ما مدام در حال خواندن کد قدیمی هستیم تا کد جدید بنویسیم. پس اگر راحت بتوان کد را خواند راحت هم می‌توان کد نوشت.اگر این نسبت را در نظر بگیریم، شکل زیر به دست می‌آید. محور x نشان دهنده‌ی تعداد خط کدی است که در یک پروژه‌ی نرم‌افزاری نوشته شده است. محور y نشان‌ دهنده‌ی زمان مورد نیاز برای نوشتن یک خط اضافه است. در کل، هر چقدر کد بیشتری در یک پروژه نوشته باشید، به زمان بیشتری برای اضافه کردن به آن کد احتیاج خواهید داشت. این هم برای کد تمیز و هم برای کد کثیف صادق است.فرض کنید n خط کد نوشته‌اید و n+1 امین خط را به کد اضافه می‌کنید. اضافه کردن این خط احتمالا تمام خط‌هایی را که قبلا نوشته‌اید، تحت تاثیر قرار خواهد داد. برای مثال ممکن است کمی از پرفورمنس بکاهد که یعنی پرفورمنس تمام پروژه را کم می‌کند. ممکن است از یک متغیر که در جای دیگری معرفی شده استفاده کند. می‌تواند یک باگ با احتمال c اضافه کند که برای پیدا کردن آن باید تمام پروژه را جستجو کنید. این یعنی زمان تخمینی و هزینه‌ی شما به ازای هر خط کد برابر c * T(n) است که T تابع افزایشی پیوسته با ورودی افزایشی n است.این شکل همچنین تفاوت بین نوشتن کد تمیز و کثیف را نشان می‌دهد. کد کثیف در کوتاه مدت و در پروژه‌های کوچک، زمان کمتری می‌گیرد؛ به هر حال اگر فایده‌ای نداشت که کسی کد کثیف نمی‌نوشت! اگر تمام قابلیت‌های پروژه‌ی خود را در ۱۰۰ خط جمع کنید، نیازی به صرف برای فکر کردن و بازسازی پروژه‌ی خود ندارید. مشکل‌ها زمانی شروع می‌شوند که کد بیشتری اضافه می‌کنید. وقتی تک فایل پروژه‌ی شما بین ۱۰۰ تا ۱۰۰۰ خط کد دارد، به نسبت زمانی که در ماژول‌ها، کلاس‌ها و فایل‌های مختلف به صورت منطقی ساختار یافته است، کمتر کارآمد خواهد بود.به عنوان یک قاعده‌ی سرانگشتی: همیشه کد تمیز و بافکر بنویسید. هزینه‌های اضافی برای دوباره فکر کردن، دوباره ساختار بندی کردن و ریفکتور کردن در هر پروژه‌ی غیر پیش پا افتاده‌ای چند برابر خواهد شد.هزینه‌ها ممکن است خیلی زیاد باشد: در سال ۱۹۶۲ ناسا قصد داشت یک فضاپیما به زهره ارسال کند اما یک باگ کوچک، یعنی نبود کاراکتر - در کد، باعث شد مهندس‌ها یک دستور خودتخریبی ارسال کنند و در نتیجه یک فضاپیمای ۱۸ میلیون دلاری نابود شود. اگر کد تمیزتر بود، مهندس‌ها می‌توانستند قبل از ارسال متوجه آن باگ شوند.اصول نوشتن کد تمیزاصل ۱: به تصویر بزرگ فکر کنیداگر بر روی یک پروژه‌ی غیر پیش پا افتاده کار کنید، در نهایت با چند فایل، ماژول و کتابخانه مواجه خواهید شد که با هم کار می‌کنند تا یک برنامه کار کند. معماری نرم‌افزاری شما نحوه‌ی ارتباط اجزای نرم‌افزار شما را تعریف می‌کند. تصمیم‌های خوب معماری منجر به جهش‌های عظیمی در عملکرد، نگه‌داری و  قابلیت استفاده می‌شود. برای ساختن یک معماری خوب باید یک قدم عقب بیایید و به تصویر بزرگ فکر کنید. به فیچرهایی فکر کنید که در قدم اول به آن‌ها نیاز داریم. در فصل قبل درباره‌ی MVP خواندیم و فهمیدیم که چطور باید بر روی فیچرهای ضروری پروژه تمرکز کرد. اگر این کار را انجام دهید، از انجام کارهای اضافه جلوگیری می‌کنید و کد تمیزتری می‌نویسید. در اینجا فرض می‌کنیم که شما برنامه‌ی اول خود را که چند ماژول، فایل و کلاس دارد ساختید. چطور می‌توانید تفکر تصویر بزرگ را به آن اعمال کنید تا بتوانید کمی نظم اضافه کنید؟ با در نظر گرفتن سوال‌های زیر، می‌توانید چند ایده برای تمیزتر کردن کدتان به دست بیاورید:آیا به تمام فایل‌ها و ماژول‌های جدا از هم نیاز دارید؟ یا می‌توانید چند تا از آن‌ها را یکی کنید تا وابستگی قسمت‌های مختلف کد را کم کنید ؟آیا می‌توانید یک فایل بزرگ و پیچیده را به دو فایل ساده‌تر تقسیم کنید؟ دقت کنید که همیشه یک نقطه‌ی مطلوب بین دو حالت افراطی وجود دارد: یک بلاک بزرگ یکپارچه از کد که کاملا غیر قابل خواندن است یا تعداد زیادی از بلاک‌های کوچک کد که دنبال کردن آن‌ها غیرممکن است. هر دوی آن‌ها مطلوب نیست و حالت‌های بین این دو گزینه‌های بهتری هستند. این را به شکل یک U برعکس در نظر بگیرید که ماکسیمم آن، حالت مطلوب بین بلاک‌های بزرگ با تعداد کم و بلاک‌های کوچک با تعداد زیاد است.آیا می‌توانید کد را generalize کنید و آن را تبدیل به یک کتابخانه کنید تا اپلیکیشن اصلی را ساده کنید؟آیا می‌توانید از کتابخانه‌های موجود استفاده کنید تا از تعداد زیادی خط کد خلاص شوید؟آیا می‌توانید از کش‌کردن برای اجتناب از محاسبه‌ی دوباره‌ی یک نتیجه استفاده کنید؟آیا می‌توانید از الگوریتم‌های مناسب و سرراست استفاده کنید که همین نتیجه‌ی الگوریتم فعلی‌تان را می‌دهد؟آیا می‌توانید بهینه‌سازی‌های زودرس (premature optimizations) را که پرفورمنس کلی را بهبود نمی‌دهند حذف کنید؟آیا می‌توانید از یک زبان برنامه‌نویسی دیگر که برای مسئله‌ی شما مناسب‌تر است، استفاده کنید؟تفکر تصویر بزرگ روشی کارآمد از نظر زمان است که پیچیدگی برنامه‌ را به طرز قابل توجهی کاهش می‌دهد. بعضی اوقات سخت است که این تغییرات را در مراحل بعدی اعمال کنید. به طور خاص، این طرز تفکر برای برنامه‌هایی که میلیون‌ها خط کد دارند سخت است مثل سیستم عامل ویندوز. اما به این راحتی نیز نمی‌توانید این سوال‌ها را نادیده بگیرید چون هر بهینه‌سازی کوچکی که اعمال کنید نمی‌تواند تاثیر تصمیم‌های طراحی غلط را جبران کند.اصل ۲: بر روی شانه‌های غول‌ها بایستید(درباره‌ی این اصطلاح) ابداع دوباره‌ی چرخ به ندرت ارزشمند است. برنامه‌نویسی یک صنعت با عمر چند دهه‌ای است. بهترین برنامه‌نویس‌ها در جهان برای ما یک میراث با ارزش فراهم کرده‌اند: یک پایگاه داده از میلیون‌ها الگوریتم و توابع که به خوبی تست و بهینه شده‌اند. دسترسی به این دانش جمعی میلیون‌ها برنامه‌نویس، به سادگی یک خط import کردن است. هیچ دلیلی وجود ندارد که این کار را انجام ندهید.استفاده از کدهای کتابخانه‌ها احتمالا کارایی کد شما را بهتر می‌کند. توابعی که هزاران برنامه‌نویس از آن‌ها استفاده کرده‌اند، بسیار بهینه‌تر از توابع شما هستند. علاوه بر این، فهمیدن توابع کتابخانه‌ای راحت‌تر است و فضای کمتری در پروژه‌ی شما می‌گیرند.اصل ۳: برای انسان‌ها کد بنویسید، نه ماشین‌هاممکن است فکر کنید که هدف اصلی یک سورس کد این است که به ماشین بگوییم که چه کار باید انجام دهد و چگونه آن را انجام دهد. نه دقیقا. تنها هدف یک زبان برنامه‌نویسی این است که به انسان‌ها کمک کنند که کد بنویسند. کامپایلرها وظیفه‌ی سنگین ترجمه‌ی کد سطح بالا به کد سطح پایین را که ماشین می‌فهمد برعهده دارند. بله، در نهایت ماشین کد شما را اجرا خواهد کرد. اما کد را انسان‌ها نوشته‌اند و احتمالا قبل از اینکه دیپلوی شود، قرار است از چند مرحله‌ از قضاوت‌ انسان‌ها بگذرد. نکته‌ی مهم: برای انسان‌ها کد می‌نویسید، نه ماشین‌ها.همیشه فرض کنید که دیگران قرار است کد شما را بخوانند. فرض کنید به یک پروژه‌ی جدید منتقل شده‌اید و شخص دیگری در جای قبلی شما قرار گرفته است. راه‌های زیادی برای راحت‌تر کردن کار او و کم کردن تنش‌ها وجود دارد. اول از همه، از نام‌های متغیر بامعنا استفاده کنید تا خواننده راحت بفهمد که یک خط کد چه هدفی دارد و قرار است چه کاری انجام دهد. در اینجا یک مثال از یک کد با نام‌های متغیر بد می‌بینید.به سختی می‌توان فهمید این کد چه کار می‌کند.کد زیر همان کار بالا را انجام می‌دهد، صرفا از نام‌های با معنا استفاده می‌کند.اینجا راحت‌تر می‌توان فهمید چه اتفاقی می‌افتد.هدف کد تمیز این است که انسان بهتر بتواند کد را بخواند. همان‌طور که Martin Fowler، متخصص مهندسی نرم‌افزار و نویسنده‌ی کتاب معروف Refactoring می‌گوید:هر کسی می‌تواند کدی بنویسد که کامیپوتر بفهمد. برنامه‌نویس‌های خوب کدی می‌نویسند که انسان‌ها بتوانند بفهمند.اصل ۴: از نام‌های درست استفاده کنیدبرنامه‌نویس‌های باتجربه بر روی مجموعه‌ای از قراردادهای نامگذاری برای توابع، آرگومان‌های تابع، آبجکت‌ها، متدها و متغیرها توافق کرده‌اند. همه از رعایت این‌ها سود می‌برند: کد خواناتر، قابل فهم‌تر و کمتر به هم ریخته می‌شود. اگر این قرارداد ها را زیر پا بگذارید، خواننده‌های کد شما احتمالا فرض می‌کنند که این کد را یک برنامه‌نویس بدون تجربه نوشته است و ممکن است به کد شما اهمیتی ندهند.این قراردادها ممکن است از زبان به زبان فرق کند. برای مثال جاوا از camelCaseNaming استفاده می‌کند و پایتون از underscore_naming. اگر از camelCase در پایتون استفاده کنید، خواننده را گیج خواهید کرد. اصلا دلتان نمی‌خواهد که نام‌گذاری‌های غیرمعمول شما حواس خواننده‌ی کد را منحرف کند. شما می‌خواهید که خواننده بر روی کاری که کد شما انجام می‌دهد متمرکز شود نه بر روی استایل کد‌نویسی شما. طبق اصل کمترین غافلگیری (principle of least surprise): هیچ ارزشی در غافلگیر کردن دیگر برنامه‌نویس‌ها با استفاده از نام‌گذاری غیرمعمول متغیرها وجود ندارد.در اینجا چند مورد از قوانین نام‌گذاری را می‌خوانیم.از نام‌های توصیفی استفاده کنید: فرض کنید در پایتون یک تابع نوشته‌اید که دلار را به یورو تبدیل می‌کند. به جای نام f(x) از usd_to_euro(amount) استفاده کنید.از نام‌های بدون ابهام استفاده کنید: شاید فکر کنید که نام dollar_to_euro(amount) یک نام خوب برای این تابع است. اگر چه بهتر از f(x) است اما بدتر از usd_to_euro(amount) است چون به ابهام اضافه می‌کند. منظور شما از دلار، دلار آمریکا، کانادا یا استرالیا است؟ اگر در آمریکا باشید ممکن است جواب بدیهی باشد اما یک برنامه‌نویس استرالیایی نمی‌داند که این کد در آمریکا نوشته شده است و ممکن است انتظار یک خروجی دیگر داشته باشد. این سردرگمی‌ها را به حداقل برسانید!از نام‌های قابل تلفظ استفاده کنید: بیشتر برنامه‌نویس‌ها ناخودآگاه کد را با تلفظ کردن در ذهن‌شان می‌خوانند. اگر نام یک متغیر غیر قابل تلفظ باشد، رمزگشایی آن توجه را به خود جلب می‌کند. برای مثال، متغیر cstmr_lst شاید توصیفی و بدون ابهام باشد اما قابل تلفظ نیست. نام‌گذاری customer_list ارزش جای اضافه در کدتان را دارد.از متغیرهای ثابت دارای نام استفاده کنید نه اعداد جادویی: در کدتان شاید از عدد جادویی ۰.۹ چند بار به عنوان ضریب برای تبدیل مبلغ از دلار آمریکا به یورو کرده باشید. اما خواننده‌ی کد شما، از جمله‌ خود شما در آینده، باید به مقصود این عدد فکر کند. این عدد خود توصیف نیست. یک روش بهتر این است که این عدد در یک متغیر که با حروف بزرگ نوشته شده است، ذخیره شود. استفاده از حروف بزرگ نشان می‌دهد که این متغیر ثابت است و تغییر پیدا نمی‌کند، مثلا CONVERTSION_RATE = 0.9 و سپس تبدیل کد به income_euro = CONVERTSION_RATE * income_usd.فقط چند قانون نام‌گذاری وجود دارد. بهترین راه برای یادگیری این قراردادها، خواندن کدهایی است که متخصص‌ها نوشته‌اند. جستجوی قراردادهای نام‌گذاری مثل Python naming convention یک نقطه‌ی شروع خوب است. همچنین می‌توانید آموزش‌های برنامه‌نویسی بخوانید، به StackOverflow بپیوندید و کدهای پروژه‌های اپن سورس را در GitHub بخوانید.اصل ۵: به استانداردها پایبند باشید و یکپارچه بمانیدهر زبان برنامه‌نویسی دسته‌ای از قوانین ضمنی یا صریح درباره‌ی چگونه کد تمیز نوشتن دارد. برای مثال، راهنمای رسمی پایتون که PEP8 نام دارد، در این لینک قابل دسترسی است. مانند هر راهنمای استایل کد، PEP8 شیوه‌ی صحیح layout و indentation، نحوه‌ی شکستن خط‌ها، بیشترین تعداد کاراکتر در یک خط، استفاده‌ی درست از کامنت، نام‌گذاری صحیح کلاس‌ها، تابع‌ها و متغیرها را تعریف می‌کند.در شکل زیر، شیوه‌ی غلط نوشتن را می‌بینیم. آرگومان‌ها هم‌راستا نیستند، چند کلمه به درستی در یک تابع قرار نگرفته‌اند، لیست آرگومان‌ها به درستی توسط فاصله جدا نشده‌اند و indentation به جای ۴ فاصله از ۲ یا ۳ فاصله استفاده کرده است.تمام خواننده‌های کد شما انتظار دارند که به استانداردهای پذیرفته شده وفادار باشید. هر چیز دیگری منجر به آشفتگی دیگران می‌شود.خواندن راهنماهای استایل کد می‌تواند کار خسته‌کننده‌ای باشد. به‌عنوان یک روش کمتر خسته کننده برای یادگیری قراردادها و استانداردها، از linter ها و IDE هایی استفاده کنید که به شما می‌گویند که چگونه و کجای کد اشتباه کرده‌اید.اصل ۶: از کامنت استفاده کنیدهمانطور که قبلا گفته شد، وقتی برای انسان‌ها کد می‌نویسیم، باید از کامنت‌ها استفاده کنیم تا کمک کنیم خواننده بفهمد. کد زیر را در نظر بگیرید.کد بالا یک متن کوتاه از رومئو و ژولیت شکسپیر را با استفاده از regular expressionها تحلیل می‌کند. اگر با regular expression ها آشنا نباشید، احتمالا به سختی متوجه خواهید شد که کد چه کار می‌کند. حتی متغیرهای با معنا نیز کمکی نمی‌کنند. حال همین کد با کامنت را در نظر بگیرید.همچنین می‌توانید از کامنت‌ها قبل از یک بلاک کد استفاده کنید تا خلاصه‌ی کاری را که انجام می‌دهد، توضیح دهید.همچنین می‌توانید از کامنت برای هشدار دادن درباره‌ی عواقب نامطلوب به برنامه‌نویس‌ها هشدار دهید. اصل ۷: از کامنت‌های غیرضروری دوری کنیدتمام کامنت‌ها به بهتر فهمیدن خواننده کمک نمی‌کنند. در بعضی مواقع کامنت‌ها از شفافیت کم و خواننده را گیج می‌کنند. برای نوشتن کد تمیز، نه تنها باید از کامنت‌های با ارزش استفاده کنید بلکه باید از کامنت‌های غیرضروری نیز دوری کنید.هنگامی که من یک محقق علوم کامیپوتر بودم، یکی از دانشجوهای با استعداد من برای یک شغل در گوگل اقدام کرد. او گفت که headhunter های گوگل از استایل کدنویسی او انتقاد کردند چون کامنت‌های غیرضروری زیادی نوشته بود. اعتبارسنجی کامنت‌ها یک راه دیگر برای متخصصین کدنویسی است که می‌توانند بفهمند شما تازه‌کار، متوسط یا حرفه‌ای هستید. مشکلات کد مانند رعایت نکردن استانداردها، تنبلی در کامنت‌نویسی یا نوشتن کد به شکلی که قواعد یک زبان برنامه‌نویسی را رعایت نمی‌کند، با نام code smell شناخته می‌شوند که منجر به مشکلات متعدد در کد می‌شود و حرفه‌ای ها آن‌ها را از چند کیلومتری تشخیص می‌دهند.از کجا باید فهمید که چه کامنت‌هایی خوب نیستند؟ در بیشتر مواقع، اگر کامنت اضافی باشد، غیرضروری است. برای مثال، از متغیرهای بامعنا استفاده کرده‌اید و کد به خوبی خود را توضیح می‌دهد و احتیاجی به کامنت ندارد. مثلا کد زیرکاملا واضح است که این کد چه کار می‌کند. حال شکل زیر کامنت‌های اضافی را نشان می‌دهد. تمام کامنت‌های این شکل اضافه هستند.در بیشتر مواقع باید از common sense برای تشخیص ضروری بودن یک کامنت استفاده کنید اما در اینجا چند راهنما را می‌خوانیم.از کامنت‌های درون خطی دوری کنید: با استفاده از متغیرهای با نام معنادار، می‌توان از این نوع کامنت‌ها جلوگیری کرد.از کامنت‌های واضح دوری کنید: در شکل ۱۰-۴، کامنت مربوط به حلقه‌ی for غیرضروری است. همه‌ی کدنویس‌ها می‌دانند که حلقه‌ی for چیست و احتیاجی به توضیح ندارد.کدهای قدیمی را کامنت نکنید، حذف کنید: همیشه کدهای قدیمی و غیرلازم را حذف کنید. کامنت کردن آن‌ها، خوانایی کد را کاهش می‌دهد. از ابزارهای کنترل ورژن مانند Git برای ذخیره کردن نسخه‌های اولیه کد استفاده کنید.از قابلیت‌های داکیومنت‌کردن استفاده کنید: بسیاری از زبان‌های برنامه‌نویسی ابزارهای داکیومنت‌ built-in دارند که به شما اجازه می‌دهند هدف هر تابع و کلاس را بنویسید. اگر هر کدام از آن‌ها فقط یک مسئولیت دارند (در اصل ۱۰ بررسی خواهیم کرد)، معمولا استفاده از داکیومنت به جای کامنت برای توضیح کد ، کافی است.اصل ۸: اصل کمترین غافلگیری (The Principle of Least Surprise)این اصل بیان می‌کند که یک جزء از سیستم باید به روشی رفتار کند که بیشتر کاربران انتظار دارند رفتار کند. این اصل یکی از قانون‌های طلایی به هنگام طراحی برنامه‌های کارا و تجربه‌ی کاربری است. برای مثال، اگر گوگل را باز کنید، نشان‌گر به صورت خودکار در قسمت نوشتن ورودی قرار می‌گیرد و شما می‌توانید در همان لحظه شروع به تایپ کردن عبارت مورد نظر بکنید. دقیقا به همان صورتی که انتظار دارید: بدون غافلگیری.کد تمیز نیز از این اصل طراحی پیروی می‌کند. فرض کنید یک تابع برای تبدیل ارز کاربر از دلار آمریکا به ین چین نوشته‌اید. ورودی کاربر را در یک متغیر ذخیره می‌کنید. کدام نام متغیر بهتر است؟ user_input یا var_x ؟ اصل کمترین غافلگیری پاسخ این سوال را می‌دهد!اصل ۹: خودت را تکرار نکناصل Don’t Repeat Yourself (DRY) یک اصل مشهور است که پیشنهاد می‌کند از کد تکراری اجتناب کنید. برای مثال،  این کد پنج بار یک رشته را چاپ می‌کند. این کد در واقع همان کار را انجام می‌دهد اما تکرار کمتری دارد.استفاده از تابع نیز یک ابزار مناسب برای کاهش تکرار است. با این کار نگه‌داری کد نیز راحت‌تر است. برای مثال می‌توانید تابع را ویرایش کنید تا دقت تبدیل بیشتر شود. با این کار کافی است فقط یکبار تابع را ویرایش کنید تا این تغییر در کل کد اعمال شود. اگر از تابع استفاده نمی‌کردید، باید در کل کد جستجو می‌کردید تا این ویرایش را در تمام قسمت‌ها اعمال کنید. تخطی از DRY اغلب WET نامیده می‌شود: ما تایپ کردن را دوست داریم (We Enjoy Typing)، همه چیز را دوبار بنویس (Write Everything Twice) و وقت همه را تلف کن (Waste Everyone&#x27;s time)اصل ۱۰: اصل یگانگی مسئولیتاصل یگانگی مسئولیت (The Single Responsibility Principle) بیان می‌کند که هر تابع باید یک وظیفه‌ی اصلی داشته باشد. بهتر است که از تعداد زیادی تابع کوچک استفاده شود تا یک تابع بزرگ که تمام کارها را یکجا انجام می‌دهد. این کار پیچیدگی کد را کاهش می‌دهد.به عنوان یک قانون سرانگشتی، هر کلاس و هر متد باید یک وظیفه داشته باشد. Robert C. Martin، مبدع این اصل، مسئولیت (Responsibility) را یک دلیل برای تغییر (A reason to change) تعریف می‌کند. استاندارد طلایی او به هنگام تعریف کلاس و متد این است که این‌ها بر روی یک مسئولیت تمرکز کنند به طوری که فقط برنامه‌نویسی که نیاز دارد این یک مسئولیت تغییر کند، درخواست تغییر در تعریف بکند و دیگر برنامه‌نویس‌ها با مسئولیت‌های دیگر، با فرض صحیح بودن کد، درخواست تغییر برای کلاس ندهند.برای مثال، تابعی که وظیفه‌ی خواندن از دیتابیس را دارد، نباید وظیفه‌ی پردازش داده را نیز داشته باشد. در غیر این صورت این تابع دو دلیل برای تغییر دارد: یک تغییر در مدل دیتابیس و یک تغییر در پردازش‌ها. اگر چند علت برای تغییر وجود دارد، ممکن است چند برنامه‌نویس همزمان همان کلاس را تغییر دهند. کلاس شما مسئولیت‌های زیادی دارد و آشفته و در هم ریخته می‌شود.یک مثال کوچک از یک کد پایتون را در نظر بگیرید که بر روی یک کتابخوان الکترونیکی اجرا می‌شود و تجربه‌ی کاربر را مدیریت می‌کند.در شماره‌ی ۱ کلاس Book را تعریف می‌کنیم که ۴ attribute دارد: title, author, publisher و current page. در شماره‌ی ۲ متدهای getter را برای attribute ها تعریف می‌کنید. در شماره‌ی ۳ متدهای رفتن به صفحه‌ی بعدی را پیاده‌سازی می‌کنید و شماره‌ی ۴ صفحه‌ی فعلی را در دستگاه نشان می‌دهد. این صرفا یک مثال است و در جهان واقعی بسیار پیچیده‌تر خواهد بود. در آخر یک آبجکت Book با نام python_one_liners می‌سازید و به توابعی که گفته شد، از طریق آن دسترسی خواهید داشت. اگر چه که کد تمیز و ساده به نظر می‌رسد اما اصل یگانگی مسئولیت را زیر پا می‌گذارد. کلاس Book هم مسئولیت مدل کردن داده و هم مسئولیت پرینت کردن کتاب در دستگاه را دارد. مدل کردن و پرینت کردن دو عمل متفاوت هستند اما در یک کلاس آمده‌اند. چند دلیل برای تغییر وجود دارد. شاید بخواهید نحوه‌ی مدل کردن اطلاعات کتاب را تغییر دهید. برای مثال شاید بخواهید از دیتابیس استفاده کنید. همچنین شاید بخواهید نحوه‌ی ارائه‌ی داده‌ی مدل شده را نیز تغییر دهید. این مشکل را در شکل زیر حل کردیم.برای نشان دادن اطلاعات کتاب از کلاس Book و برای نشان دادن کتاب در دستگاه از کلاس Printer استفاده می‌کنیم.  با این کار، نحوه‌ی مدل کردن داده (داده چه چیزی است؟) و نحوه‌ی ارائه‌ی داده (داده چطور به کاربر ارائه می‌شود؟) از هم جدا شده‌اند و نگه‌داری کد نیز راحت‌تر شده است. اگر بخواهید یک attribute جدید به نام publishing_year اضافه کنید، آن را به کلاس Book اضافه می‌کنید. اگر بخواهید این تغییر را در نحوه‌ی نشان دادن نیز تغییر دهید، تغییری در کلاس Printer ایجاد می‌کنید.اصل ۱۱: تستتوسعه‌ی مبتنی بر تست، یک بخش جدایی‌ناپذیر از توسعه‌ی نرم‌افزار امروزی است. تفاوتی ندارد که چقدر مهارت دارید، قطعا اشتباه‌هایی در کدتان خواهید کرد. برای گرفتن این خطاها، باید تست‌های دوره‌ای بگیرید یا در وهله‌ی اول، کد مبتنی بر تست بنوسید. هر شرکت نرم‌افزاری بزرگ، قبل از دیپلوی کردن محصول برای عموم، آن را از چند مرحله تست می‌گذارند. در واقع بهتر است که این خطاها را خودشان کشف کنند تا اینکه از کاربران ناراضی درباره‌ی از آن‌ها مطلع شوند.اگرچه که هیچ محدودیتی در نوع تستی که انجام می‌دهید وجو ندارد، در اینجا انواع رایج آن را بررسی می‌کنیم.تست‌های واحد (Unit test): در Unit test، یک برنامه‌ی جدا برای بررسی رابطه‌ی صحیح بودن ورودی و خروجی برای ورودی‌های مختلف هر تابع می‌نویسید. Unit testها معمولا در بازه‌های منظم اعمال می‌شوند؛ برای مثال هر بار که نسخه‌ی جدید برنامه منتشر می‌شود. این کار احتمال از کار افتادن فیچرهای قبلی با انتشار نسخه‌ی جدید را کم می‌کند.تست‌های تایید کاربر (User acceptance test): این تست‌ها به افرادی که در بازار هدف شما هستند اجازه می‌دهد تا برنامه‌ی شما را در یک محیط کنترل شده استفاده کنند. در این محیط شما رفتار آن‌ها را بررسی می‌کنید و سپس از آن‌ها می‌پرسید که از چه چیزهای برنامه راضی بودند و چطور می‌توان آن را بهتر کرد. این تست‌ها معمولا در مرحله‌ی آخر توسعه انجام می‌شوند، بعد از اینکه تست‌های درون سازمانی انجام شده است.تست دود (Smoke test): این تست‌ها تست‌های سختی هستند که طراحی شده‌اند تا برنامه‌ی در حال توسعه را از کار بیندازند، قبل از اینکه تیم توسعه‌دهنده آن را به تیم تست تحویل دهد. به عبارت دیگر، این تست‌ها را اغلب تیم توسعه‌دهنده انجام می‌دهد تا قبل از اینکه کد را به تیم تست تحویل دهد، از کیفیت آن مطمئن شود. وقتی برنامه از Smoke testها عبور می‌کند، آماده‌ی مرحله‌ی بعدی تست است.تست کارایی (Performance test): هدف این تست‌ها این است که نشان دهند آیا کارایی برنامه مطابق انتظار یا حتی فراتر از آن است یا نه. برای مثال قبل از اینکه نتفلیکس یک فیچر جدید منتشر کنید، باید سرعت بارگزاری صفحه‌ی وبسایت را تست کند. اگر فیچر جدید سرعت بارگزاری را کم می‌کند، آن را منتشر نمی‌کند تا آسیبی به تجربه‌ی کاربری نرسد.تست مقیاس‌پذیری (Scalability test): اگر برنامه‌ی شما موفق شود، شاید مجبور شوید به جای ۲ درخواست، ۱۰۰۰ درخواست در دقیقه را مدیریت کنید. تست مقیاس‌پیذری نشان می‌دهد که آیا برنامه‌ی شما به اندازه کافی مقیاس‌پذیر برای مدیریت این شرایط است یا نه. دقت کنید که یک برنامه با کارایی خوب لزوما مقیاس‌پذیر نیست و برعکس. مثلا یک قایق سرعتی کارایی خوبی دارد اما هزار نفر را نمی‌تواند جابجا کند!تست و ریفکتور کردن اغلب از پیچیدگی و تعداد خطاهای کد کم می‌کند. اگرچه، دقت کنید که بیش-مهندسی نکنید (در اصل ۱۴ می‌خوانیم)؛ باید سناریوهایی را بررسی کنید که در دنیای واقعی نیز رخ می‌دهند. مثلا نتفلیکس لازم نیست بررسی کند ببیند توانایی پشتیبانی از ۱۰۰ میلیارد دستگاه استریم دارد یا نه چون جمعیت زمین ۷ میلیارد است!اصل ۱۲: کوچک زیبا استکد کوچک (Small code) کدی است که به تعداد نسبتا کمی از خطوط کد نیاز دارد تا یک کار را انجام دهد. اینجا یک نمونه از کد کوچک را می‌بینیم که یک عدد را از کاربر می‌گیرد و مطمئن می‌شود که ورودی عدد است:کد تا جایی ادامه پیدا می‌کند که کاربر یک عدد وارد کند. با جدا کردن منطق خواندن عدد از ورودی، می‌توانید یک تابع را چند بار استفاده کنید. اما مهم‌تر از همه، کد را به قسمت‌های کوچکی تبدیل کردید که فهمیدن و خواندن آن‌ها آسان است.اما بسیاری از کدنویس‌های تازه‌کار یا کدنویس‌های متوسط تنبل، توابع بزرگ می‌نویسند که همه‌ی کارها را انجام می‌دهند. به این توابع God Object نیز گفته می‌شود. این کد بلاک‌های یکپارچه به سختی قابل نگه‌داری هستند. درک یک تابع کد کوچک در آن واحد آسان‌تر از فهمیدن یک فیچر در یک کد بلاک ۱۰ هزار خطی است. احتمالا در یک کد بلاک بزرگ به نسبت چند کد بلاک کوچک، خطاهای بیشتری داشته باشید.در ابتدای این فصل شکل ۱-۴ نشان داد که نوشتن کد با اضافه شدن کدهای جدید، زمان بیشتری می‌گیرد. نوشتن کد تمیز در طولانی مدت سریع‌تر از نوشتن کد کثیف است. شکل ۲-۴ زمان مورد نیاز برای کار کردن با کد بلاک‌های کوچک در برابر کد بلاک‌های بزرگ یکپارچه را مقیاسه می‌کند. برای کد بلاک‌های بزرگ، زمان مورد نیاز برای اضافه کردن هر خط کد جدید، نمایی افزایش پیدا می‌کند. اما اگر از چند تابع کوچک با هم دیگر استفاده کنید، زمان مورد نیاز به ازای هر خط جدید شبه‌خطی افزایش پیدا می‌کند. برای دستیابی به این نتیجه، باید مطمئن باشید که هر تابع تقریبا مستقل از توابع دیگر است. این قانون را در مورد بعدی بررسی می‌کنیم.اصل ۱۳: قانون دمتر (The Law of Demeter)وقتی یک کتابخانه را در کد import می‌کنیم، کد شما بر این کتابخانه متکی می‌شود؛ همچنین خود این کتابخانه نیز درون خود dependency هایی دارد. dependency ها همه جا هستند. در برنامه‌نویسی شی‌گرا، یک تابع ممکن است به تابع دیگری، یک آبجکت به آبجکت دیگری و یک کلاس به کلاس دیگری احتیاج داشته باشد.برای نوشتن کد تمیز، وابستگی‌های متقابل (interdependency) را با پیروی از Law of Demeter به حداقل برسانید. Ian Holland این قانون را در سال ۱۹۸۰ وقتی بر روی یک پروژه با نام Demeter، الهه کشاورزی، رشد و باروری یونان، کار می‌کرد، پیشنهاد داد. گروه این پروژه، ایده‌ی بزرگ کردن نرم‌افزار را به جای صرفا درست کردن آن ارائه دادند. اگرچه، چیزی که به Law of Demeter شناخته شد زیاد به ایده‌های متافیزیکی ربطی نداشت. در اینجا یک نقل قول خلاصه از سایت پروژه می‌خوانیم که به طور خلاصه این قانون را توضیح می‌دهد.یک مفهوم مهم Demeter این است که نرم‌افزار را به حداقل دو قسمت تقسیم کنیم: اولین قسمت آبجکت‌ها را تعریف می‌کند. دومین قسمت عمل‌ها را تعریف می‌کند. هدف Demeter این است که بین عمل و آبجکت یک همراهی آزادانه برقرار کند به طوری که هر کدام بتوانند بر دیگری تغییراتی انجام دهند، بدون آنکه تاثیر جدی‌ای روی دیگری داشته باشند. این کار از زمان نگه‌داری، کم می‌کند.به عبارت دیگر باید dependencyهای آبجکت‌های کدتان را به حداقل برسانید. با کم شدن این dependency، پیچیدگی کد نیز کاهش میابد و نگه‌داری آن راحت‌تر می‌شود. یک مفهوم مهم این است که هر آبجکت باید تنها متدهای خودش یا متدهای آبجکت‌های نزدیک را فراخوانی کند. برای مثال، فرض کنید دو آبجکت A و B داریم که در صورتی این دو را با هم دوست می‌دانیم که A تابعی را که B ارائه می‌دهد، فراخوانی می‌کند. اما اگر تابع B یک آبجکت C برگرداند چه؟ حال A شاید چنین کاری کند: B.method_of_B().method_of_C()  به این کار زنجیر کردن فراخوانی‌های تابع یا chaining of method calls می‌گویند؛ به زبان خودمان، شما با دوست دوست‌تان صحبت می‌کنید. قانون دمتر می‌گوید که فقط با دوستان نزدیک خود صحبت کنید. پس چنین فراخوانی تابع را منع می‌کند. ممکن است در ابتدا گیج‌کننده باید پس مثال زیر را در نظر بگیرید.شکل ۳-۴ دو پروژه‌ی شی‌گرایی را نشان می‌دهد که برای یک شخص، قیمت هر فنجان قهوه را محاسبه می‌کند. یکی از پیاده‌سازی‌ها، قانون دمتر را زیر پا می‌گذارد و دیگری بر آن پایبند است. ابتدا مورد اول را بررسی می‌کنیم که در آن از method chaining در کلاس Person استفاده می‌کنیم تا با یک غریبه صحبت کنیم (خط کدی که با شماره‌ی ۱ مشخص شده است)شما تابع price_per_cup() را می‌سازید که بر اساس قیمت ماشین قهوه‌ساز و تعداد قهوه‌ها، هزینه‌ی هر فنجان قهوه را محاسبه می‌کند. آبجکت Coffee_Cup اطلاعات قیمت قهوه‌ی ماشین قهوه‌ساز را جمع‌آوری می‌کند که قیمت هر فنجان را تحت تاثیر قرار می‌دهد و آن را به صدا زننده‌ی price_per_cup() در آبجکت Person پاس می‌دهد.توضیح قدم به قدم این کد:۱- متد price_per_cup() تابع Coffee_Cup.get_creator_machine() را فراخوانی می‌کند تا یک رفرنس از آبجکت Coffee_Machine به دست بیاورد.۲- متد get_creator_machine() آبجکتی که به Coffee_Machine رفرنس می‌دهد را بازمی‌گرداند.۳- متد price_per_cup() متد Coffee_Machine.get_price() را روی آبجکت Coffee_Machine فراخوانی می‌کند.۴- متد get_price() قیمت دستگاه را بازمی‌گرداند.۵- متد price_per_cup() هزینه‌ی هر فنجان را محاسبه می‌کند.این یک استراتژی بد است چون کلاس Person متکی بر دو آبجکت Coffee_Cup و Coffee_Machine است. یک برنامه‌نویس که وظیفه‌ی نگه‌داری از این را برعهده دارد باید درباره‌ی هر دو کلاس والد بداند؛ هر تغییری در هر کدام کلاس Person را هم تحت تاثیر قرار می‌دهد.قانون دمیتر چنین dependencyهایی را به حداقل می‌رساند. مدل بهتر را می‌توانید در شکل ۳-۴ ببینید. در اینجا کلاس Person مستقیما با کلاس Machine صحبت نمی‌کند؛ در واقع اصلا احتیاجی ندارد که بداند چنین کلاسی وجود دارد!۱- متد price_per_cup() متد Coffee_Cup.get_cost_per_cup() را فراخوانی می‌کند تا قیمت حدودی هر فنجان را به دست بیاورد.۲- متد get_cost_per_cup() ، قبل از اینکه به متدی که آن را فراخوانی کرده پاسخ دهد، متد Coffee_Machine.get_price() را فراخوانی می‌کند تا قیمت دستگاه را به دست بیاورد.۳- متد get_price() اطلاعات قیمت را برمی‌گرداند۴- متد get_cost_per_cup() قیمت هر فنجان را محاسبه می‌کند و آن را در جواب فراخوانی تابع price_per_cup() بازمی‌گرداند.۵- متد price_per_cup() این مقدار را به صدازننده بازمی‌گرداند.این رویکرد بهتری است چرا که کلاس Person حالا مستقل از کلاس Coffee_Machine است. تعداد dependencyها کاهش یافته است. برای یک پروژه با صدها کلاس، کم کردن dependecyها پیچیدگی کلی برنامه را کاهش می‌دهد. خطر افزایش پیچیدگی در پروژه‌های بزرگ چنین است: تعداد dependencyها با افزایش آبجکت‌ها، به‌صورت نمایی افزایش میابد. مثلا دو برابر شدن تعداد آبجکت‌ها می‌تواند dependencyها را ۴ برابر افزایش دهد. اما با پیروی از قانون دیمتر می‌توان جلوی این را گرفت. اگر هر آبجکت فقط با k آبجکت دیگر صحبت کند و شما n آبجکت داشته باشید، تعداد dependencyها حداکثر k*n است که اگر k یک ثابت باشد، به یک رابطه‌ی خطی می‌رسیم. پس قانون دیمتر از نظر ریاضی می‌تواند به شما کمک کند تا مقیاس برنامه‌های خود را به خوبی افزایش دهید.اصل ۱۴: شما به آن نیاز نخواهید داشتاین اصل پیشنهاد می‌کند که اگر شک دارید که به کدی در آینده نیاز پیدا خواهید کرد، هیچوقت نباید آن را پیاده‌سازی کنید، چون هیچوقت قرار نیست به آن نیاز پیدا کنید! فقط کدی را بنویسید که ۱۰۰ درصد مطمئن هستید ضروری است؛ کدی که برای امروز است و نه فردا. اگر در آینده دیدید که به این کد احتیاج دارید، به راحتی می‌توانید آن را پیاده سازی کنید. با این کار از خطوط غیرضروری جلوگیری کرده‌اید.بیاید از اصولی که یاد گرفتیم استفاده کنیم: ساده‌ترین و تمیزترین کد، یک فایل خالی است. حال از اینجا به بعد، چه چیزی لازم است که به آن اضافه کنید؟ در فصل ۳ درباره‌ی MVP خواندید؛ کدی که فیچرهای اضافه ندارد و فقط بر روی عملکردهای اصلی متمرکز است. اگر تعداد فیچرهایی را که به دنبالش هستید، به حداقل برسانید، کد تمیزتر و ساده‌تری به دست می‌آورید تا اینکه بخواهید کد را مطابق اصولی که گفتیم ریفکتور کنید. فیچرهایی که ارزش کمی به نسبت بقیه دارند حذف کنید. قبل از اینکه پیاده‌سازی یک فیچر را در نظر بگیرید، باید واقعا به آن احتیاج پیدا کرده باشید.یک پیامد این کار، اجتناب از مهندسی بیش از حد (overengineering) است: یعنی ساختن محصولی که خیلی عملکرد خوبی دارد و قوی است یا فیچرهای بیش از حد نیاز دارد. این کار پیچیدگی غیرضروری را افزایش می‌دهد.اصل ۱۵: بیش از حد از تورفتگی استفاده نکنیداغلب زبان‌های برنامه‌نویسی از تورفتگی (Indentation) برای نمایش ساختار سلسله مراتبی بلاک‌های شرط، تعاریف توابع و حلقه‌ها استفاده می‌کنند. استفاده بیش از حد از این تورفتگی‌ها، می‌تواند از خوانایی کد کم کند. در اینجا یک مثال می‌بینیم.حال اگر سعی کنید که خروجی این تابع را حدس بزنید، می‌بینید که دنبال کردن کد کار سختی است. در شکل زیر یک کد دیگر می‌بینیم که همان کار را انجام می‌دهد اما ساده‌تر و تمیزتر است.بیشتر کدنویس‌ها از خواندن کدهای flat بیش‌تر از کدهای nested لذت می‌برند؛ حتی اگر این کار به قیمت بررسی‌های اضافه باشد، مثلا در اینجا x&gt;y چند بار بررسی شده است.اصل ۱۶: از متریک‌های کیفیت کد استفاده کنیدبرای دنبال کردن میزان پیچیدگی کد خود، از متریک‌های کیفیت کد استفاده کنید. متریک نهایی و البته غیررسمی، تعداد WTF ها در دقیقه است. با این کار میزان آشفتگی خواننده‌ی کد اندازه‌گیری می‌شود. این تعداد برای کد تمیز و ساده کم است و برای کد کثیف بالا است. همچنین در بسیاری از IDEها، پلاگین‌هایی وجود دارند که پیچیدگی کد را بررسی می‌کنند.اصل ۱۷: قانون پیشاهنگ و ریفکتور کردنقانون پیشاهنگ ساده است: کمپ را تمیزتر از موقعی که آن را پیدا کردید، ترک کنید. این عادت را توسعه دهید که هر وقت به هر کدی رسیدید، آن را تمیز کنید. این کار نه تنها کدبیسی که در آن هستید بهتر می‌کند بلکه به شما کمک می‌کند تا یاد بگیرید مانند یک برنامه‌نویس حرفه‌ای، کد را سریع ارزیابی کنید. دقت کنید که این کار نباید قانونی که بالاتر گفتیم نقض کنید (مهندسی بیش از حد) به طور خلاصه، مهندسی بیش از حد احتمالا پیچیدگی را افزایش دهد حال اینکه تمیز کردن کد از آن کم می‌کند.فرآیند بهتر کردن کد را refactoring می‌گویند. به عنوان یک برنامه‌نویس عالی، از خیلی از اصل‌هایی که گفتیم استفاده خواهید کرد. اما باز هم، بعضی اوقات نیاز است که کد را ریفکتور کنیم. به طور خاص، قبل از اینکه یک نسخه‌ی جدید منتشر کنید، بهتر است کد را ریفکتور کنید.راه‌های مختلفی برای ریفکتور کردن وجود دارد. یک راه این است که کد را برای همکار خود توضیح دهید یا از آن‌ها بخواهید که به کد شما نگاه کنند تا تصمیم‌های بد شما را که خودتان متوجه نشدید، کشف کنند. این کار شما را مجبور می‌کند تا به تصمیم‌های خود فکر کنید و به کارهای خود از بالا نگاه کنید و سعی کنید آن‌ها را توضیح دهید.اگر کدنویس درون‌گرایی هستید، می‌توانید کد خود را به یک اردک پلاستیکی توضیح دهید؛ تکنیکی که به آن rubber duck debugging می‌گویند.علاوه بر صحبت کردن با همکار یا اردک، می‌توانید از اصول کد تمیزی که گفته شد برای ارزیابی کدتان استفاده کنید.</description>
                <category>مجتبی میر یعقوب زاده</category>
                <author>مجتبی میر یعقوب زاده</author>
                <pubDate>Mon, 29 Aug 2022 12:03:47 +0430</pubDate>
            </item>
                    <item>
                <title>یک کمینه محصول پذیرفتنی بسازید</title>
                <link>https://virgool.io/@TabaMojj/%D9%81%D8%B5%D9%84-%DB%B3-%DB%8C%DA%A9-%DA%A9%D9%85%DB%8C%D9%86%D9%87-%D9%85%D8%AD%D8%B5%D9%88%D9%84-%D9%BE%D8%B0%DB%8C%D8%B1%D9%81%D8%AA%D9%86%DB%8C-%D8%A8%D8%B3%D8%A7%D8%B2%DB%8C%D8%AF-actbdwrenxkt</link>
                <description>این فصل یک ایده‌ی کم ارزش‌دهی‌شده اما مشهور را بررسی می‌کند که در کتاب The Lean Startup اثر Eric Ries مطرح شده است. ایده این است که یک کمینه محصول پذیرفتنی (Minimum Viable Product) بسازید. MVP یک نسخه از محصول شما است که فقط شامل ضروری‌ترین فیچر محصول است. با این کار می‌توانید فرضیه‌های خود را آزمایش و اعتبارسنجی کنید، بدون اینکه زمان زیادی را صرف پیاده‌سازی فیچرهایی بکنید که کاربران شما ممکن است از آن‌ها استفاده نکنند. یاد می‌گیرید که چگونه با تمرکز بر روی فیچرهایی که می‌دانید کاربران شما می‌خواهند، از پیچیدگی در فرآیند توسعه‌ی نرم‌افزار کم کنید. در این فصل MVPها را با نگاه کردن به تله‌هایی که توسعه‌ی نرم‌افزار بدون استفاده از MVPها دارد، معرفی می‌کنیم.دقت کنید که این مطلب ترجمه‌ی خط به خط کتاب نیست و فقط قسمت‌های مهم آن آورده شده است.یک سناریوی مسئلهایده‌ی پشت MVP این است که با مشکل‌هایی که به هنگام برنامه‌نویسی در حالت مخفی‌کاری رخ می‌دهد، مقابله کنیم. حالت مخفی‌کاری موقعی است که شما بر روی پروژه‌ای کار می‌کنید تا به اتمام برسد، بدون اینکه از کاربران فیدبکی دریافت کنید. فرض کنید به یک ایده‌ی فوق العاده رسیده‌اید که دنیا را تغییر می‌دهد: یک موتور جستجوی مبتنی بر ماشین لرنینگ که مناسب جستجوی کد است. به مدت چند شب با اشتیاق به برنامه‌نویسی می‌پردازید.اما در عمل، نوشتن یکباره‌ی برنامه بسیار بسیار به ندرت به موفقیت منجر می‌شود. اینجا یک نتیجه‌ی محتمل برنامه‌نویسی در حالت مخفی‌کاری را می‌خوانیم:سریع نسخه‌ی اولیه را توسعه دادید، اما وقتی موتور جستجوی خود را امتحان می‌کنید، می‌بینید که بسیاری از نتایج نشان داده شده اصلا مرتبط نیستند. وقتی درباره‌ی Quicksort جستجو انجام می‌دهید، در نتیجه یک کد MergeSort نشان داده‌ می‌شود که این کامنت را دارد: # این کد Quicksort نیست.این درست نیست. پس مدل را تنظیم می‌کنید اما هر بار که نتایج را برای یک کلمه بهتر می‌کنید، مشکلات جدیدی را برای دیگر نتایج جستجو ایجاد می‌کنید. هیچ وقت کامل از نتیجه راضی نیستید و فکر می‌کنید که نمی‌توانید کد خود را به دنیا معرفی کنید. به ۳ دلیل: کسی فکر نمی‌کند که آن مفید است اولین کاربرها تبلیغات منفی برای آن ایجاد خواهند کرد چون به به نظر آن‌ها حرفه‌ای نیست شما نگران این هستید که رقیب‌ها ایده‌ی شما را که بد پیاده‌سازی شده است، ببینند و آن را بهتر پیاده‌سازی کنند این افکار باعث می‌شوند که امید و انگیزه را از دست بدهید و پیشرفت شما بر روی این پروژه صفر خواهد شد.این شکل نشان می‌دهد که در حالت مخفی‌کاری، چه اتفاق‌هایی بدی می‌تواند بیفتد:اینجا ۶ مورد از تله‌هایی که کار کردن در حالت مخفی‌کاری دارد را بررسی می‌کنیم. از بین رفتن انگیزهدر حالت مخفی‌کاری، شما با ایده‌ی خود تنها هستید و شک‌ها به صورت منظم ظاهر می‌شوند. ابتدا در برابر شک‌ها مقابله می‌کنید چون اشتیاق اولیه‌ی شما برای پروژه به اندازه کافی بزرگ است اما هر چقدر بیشتر روی پروژه وقت می‌گذارید، شک‌های شما بزرگ‌تر می‌شوند.در مقابل اگر یک ورژن اولیه از ابزارتان را منتشر کنید، جملات تشویق کننده از یک کاربر اولیه می‌تواند به شما انگیزه کافی بدهد تا ادامه دهید و فیدبک از کاربران می‌تواند به شما الهام ببخشد که ابزار را بهبود ببخشید و بر مشکلات غلبه کنید. شما انگیزه‌ی خارجی دارید.حواس‌پرتیوقتی در حالت مخفی‌کار به تنهایی کار می‌کنید، به سختی می‌توان از حواس‌پرتی‌های روزانه صرف نظر کرد. هر چقدر بیشتر در این حالت باشید، احتمال پرت شدن حواس قبل از تمام شدن پروژه نیز بیشتر می‌شود.یک MVP می‌تواند با کم کردن زمان بین ایده و مارکت، و ساخت یک محیط که در آن سریع فیدبک دریافت می‌شود، از پرت شدن حواس جلوگیری کند و کمک می‌کند دوباره متمرکز شوید. کمبود زمانیک دشمن قدرتمند دیگر تمام کردن پروژه، برنامه‌ریزی معیوب است. فرض کنید پیش‌بینی کرده‌اید که محصول شما به 60 ساعت زمان برای تمام شدن احتیاج دارد. پس برنامه‌ریزی می‌کنید که روزانه 2 ساعت به مدت یک ماه کار کنید. اما به دلیل از بین رفتن انگیزه و حواس‌پرتی، به طور میانگین روزانه 1 ساعت کار می‌کنید. تاخیر های بعدی بخاطر تحقیقاتی که باید انجام دهید، باگ‌ها و اتفاقات غیرمنتظره‌ای که با آن‌ها روبرو می‌شوید، رخ می‌دهند.بی‌نهایت دلیل برای زیاد شدن وقت پروژه و تعداد کمی دلیل برای کم شدن وقت آن وجود دارند. در پایان اولین ماه، اصلا به چیزی که برنامه‌ریزی کرده بودید نرسیدید و دوباره به حلقه‌ی از بین رفتن انگیزه باز می‌گردید.یک MVP فقط شامل فیچرهای ضروری است. پس اشتباه‌های برنامه‌ی شما کمتر خواهد بود و پیشرفت شما قابل پیش‌بینی‌تر می‌شود. داشتن فیچرهای کمتر یعنی چیزهای کمتری ممکن است بد پیش برود و افراد بیشتری به موفقیت پروژه‌ی شما امیدوار خواهند بود. سرمایه‌گذاران عاشق قابل پیش‌بینی بودن هستند.نبود پاسخفرض کنیم شما بر نبود انگیزه غلبه کرده‌اید و محصول را به پایان رسانده‌اید. محصول را لانچ می‌کنید و هیچ اتفاقی نمی‌افتد. فقط تعداد کمی از کاربران آن را چک می‌کنند که آن‌ها هم ذوق و شوقی درباره‌ی آن ندارند. محتمل‌ترین اتفاق برای یک محصول نرم‌افزاری سکوت است؛ نبود فیدبک مثبت و منفی. یک دلیل رایج این است که محصول شما ارزش مخصوصی را که کاربر می‌خواهد، ارائه نمی‌دهد. در بار اول نمی‌توان محصولی را ساخت که مناسب مارکت باشد. اگر حین توسعه، فیدبکی از دنیای واقعی دریافت نکنید، از واقعیت دور خواهید شد و فیچرهایی تولید خواهید که کسی استفاده نخواهد کرد.یک MVP کمک می‌کند تا محصول مناسب مارکت را زودتر پیدا کنید.مفروضات غلطعلت اصلی شکست در حالت مخفی‌کاری، مفروضات شماست. یک پروژه را با یک سری مفروضات شروع می‌کنید، مثلا کاربران چه کسانی خواهند بود و چقدر از محصول شما استفاده خواهند کرد. این مفروضات اغلب غلط هستند و بدون آزمایش خارجی، کورکورانه  محصولی را تولید خواهید کرد که مخاطب واقعی شما آن را نمی‌خواهد.پیچیدگی غیرضروریفرض کنید یک محصول تولید کردید که شامل 4 فیچر است. شانس آورده‌اید و مارکت آن را قبول کرده است. زمان قابل توجهی را صرف پیاده‌سازی آن فیچرها کرده‌اید و فیدبک مثبت از کاربران دریافت می‌کنید. تمام نسخه‌های بعدی محصول شامل آن 4 فیچر + فیچرهای جدید خواهد بود.اما با منتشر کردن 4 فیچر به یکباره، به جای منتشر کردن 1 یا 2 فیچر در هر بار، نمی‌دانید که آیا مارکت زیرمجموعه‌ای از فیچرها را قبول یا حتی ترجیح می‌دهد یا نه.فیچر 1 ممکن است کاملا نامربوط باشد، اگر چه بیشترین زمان را از شما گرفته است. فیچر 4 ممکن است بسیار با ارزش باشد و مارکت آن را بخواهد. با داشتن n فیچر، 2 به توان n حالت مختلف برای فیچرهای محصول وجود دارد. اگر همه‌ی آن‌ها را با هم منتشر کنید، چطور می‌توانید بفهمید که کدام فیچر با ارزش و کدام وقت تلف کردن است؟هزینه‌ی پیاده‌سازی فیچرهای غلط، به خودی خود بالا است. منتشر کردن چند فیچر غلط، هزینه‌ی تجمعی نگه‌داری از این فیچرهای غیرضروری را نیز تحمیل می‌کند:پروژه‌های طولانی مدت و با تعداد فیچر بالا، احتیاج به زمان بیشتری برای بارگذاری تمام پروژه در ذهنتان داردهر فیچر ریسک تولید باگ جدید را داردهر خط از کد به زمان باز شدن، بارگذاری و کامپایل شدن پروژه اضافه می‌کندپیاده‌سازی n امین فیچر نیازمند این است که فیچرهای 1 تا n-1 را بررسی کنید و مطمئن شوید که این فیچر جدید با فیچرهای قبلی تداخل نداردهر فیچر جدید نیازمند تست‌های جدید است که باید کامپایل شود و قبل از انتشار نسخه‌ی بعدی، اجرا شودهر فیچر اضافه شده، کدبیس را پیچیده‌تر می‌کند و زمان یادگیری افراد جدید تیم را افزایش می‌دهدپس اگر احتمال موفقیت برنامه‌نویسی در حالت مخفی کاری کم است، راه حل چیست ؟ساختن یک کمینه محصول پذیرفتنیراه حل ساده است: دنباله‌ای از MVPها بسازید. یک فرضیه‌ی شفاف بنویسید؛ مثلا کاربران از حل پازل‌های پایتون لذت می‌برند‌ و یک محصول بسازید که فقط این فرضیه را اعتبارسنجی کند. تمام فیچرهایی که به اعتبارسنجی این فرضیه کمک نمی‌کنند، حذف کنید. یک MVP بر اساس این فیچر بسازید. با پیاده‌سازی تنها یک فیچر در هر انتشار، بیشتر می‌فهمید که مارکت چه فیچرهایی می‌خواهد و کدام فرضیه‌ها درست هستند. اما به هر قیمتی، از پیچیدگی دوری کنید. وقتی MVP را در دنیای واقعی آزمایش کردید، MVP دوم را می‌توانید بسازید که مهم‌ترین فیچر بعدی را اضافه می‌کند. اصطلاحی که برای توصیف این استراتژی که در آن محصول مناسب از طریق یک سری MVP جستجو می‌شود، نمونه‌سازی سریع (rapid prototyping) نام دارد. هر نمونه‌ی اولیه بر اساس چیزهایی است که از لانچ‌های قبلی یاد گرفته‌اید و هر کدام به گونه‌ای طراحی شده‌اند که حداکثر یادگیری را در حداقل زمان و با حداقل تلاش برای شما به ارمغان بیاورد.در این شکل این استاندارد طلایی توسعه‌ی نرم‌افزار و خلق محصول را می‌بینیم.ابتدا، یک محصول مناسب مارکت را با لانچ مرتب MVP ها پیدا می‌کنیم. لانچ زنجیره‌ای MVPها در طول زمان، علاقه ایجاد می‌کند و به شما اجازه می‌دهد که از فیدبک کاربران استفاده کنید و ایده‌ی اصلی نرم‌افزار خود را بهبود ببخشید. وقتی به محصول مناسب مارکت رسیدید، فیچرهای جدید اضافه می‌کنید. تنها فیچرهایی در محصول باقی می‌مانند که معیارهای کلیدی کاربران را بهبود می‌دهند.در کتاب The Lean Startup می‌خوانیم که چطور شرکت میلیون دلاری Dropbox از رویکرد MVP استفاده کرده است. به جای صرف زمان و تلاش بر روی یک ایده‌ی آزمایش نشده برای پیاده‌سازی عملکرد پیچیده‌ی Dropbox در سینک کردن فولدرها در cloud، سازنده‌ها این ایده را با یک ویدئوی ساده از محصول اعتبارسنجی کردند، اگرچه که محصول داخل ویدئو هنوز وجود نداشت. اگر مارکت به شما بگوید که کاربران ایده‌ی محصول شما را دوست دارند، شما با یک MVP ساده به یک محصول مناسب مارکت رسیده‌اید. وقتی از رویکرد مبتنی بر MVP برای توسعه‌ی نرم‌افزار استفاده می‌کنید، مهم است که بدانید کدام فیچرها را نگه دارید و کدام‌ها را رد کنید. در ساخت نرم‌افزار با روش MVP، آخرین قدم Split Testing نام دارد. به جای اینکه هر نسخه از نرم‌افزار را برای تمام کاربران منتشر کنیم، محصول جدید را فقط برای کسری از کاربران منتشر می‌کنیم و به پاسخ‌ها توجه می‌کنیم. تنها در صورتی یک فیچر را نگه می‌دارید که از چیزی که می‌بینید راضی هستید؛ مثلا میانگین زمانی که کاربران در سایت شما سپری کردند، افزایش یافته است. در غیر این صورت آن فیچر را رد می‌کنید. این یعنی زمان و انرژی‌ای را که صرف آن فیچر کردید، باید فدا کنید اما این کار به شما اجازه می‌دهد که محصول خود را تا جای ممکن ساده نگه دارید.۴ پایه‌ی اصلی برای ساخت کمینه محصول پذیرفتنیوقتی بر اساس تفکر MVP اولین نرم‌افزار خود را می‌سازید، این ۴ پایه‌ی اصلی را در نظر داشته باشید:عملکرد: محصول باید یک عملکرد واضح فرموله‌شده را برای کاربر فراهم کند و این کار را هم باید به خوبی انجام دهد.طراحی: محصول به خوبی طراحی شده است و تمرکز دارد. طراحی آن از ارزشی پشتیبانی می‌کند که محصول شما برای کاربر فراهم می‌کند.قابلیت اطمینان: اینکه محصول شما کمینه است به این معنی نیست که به آن نمی‌توان اطمینان کرد. مطمئن شوید که تست کیس نوشته‌اید و تمام عملکردها در کد شما آزمایش می‌شوند.قابلیت استفاده: استفاده از MVP باید آسان باشد. کاربران نباید زمان زیادی را صرف فهمیدن نحوه‌ی کارکرد نرم‌افزار بکنند.بسیاری از افراد فیچرهای MVP را اشتباه درک می‌کنند؛ آن‌ها فکر می‌کنند چون MVP یک نسخه‌ی مینیمال از محصول است پس باید در طراحی آن تنبلی کنند، محصول ارزش کمی تولید کند و به سختی بتوان از آن استفاده کرد. اما یک مینیمالیست می‌داند که خلاصه بودن MVP از روی تنبلی نیست بلکه بخاطر این است که تمرکز خود را روی یک عملکرد اصلی می‌گذاریم.مزایای کمینه محصول پذیرفتنیبا هزینه‌ی کمی می‌توان فرضیه را اعتبارسنجی کرداغلب می‌توان بعد از مطمئن شدن از ضروری بودن چیزی، شروع به کدنویسی کردزمان کمتری صرف نوشتن کد و پیدا کردن باگ می‌شودهر فیچر جدیدی که به کاربران معرفی می‌کنید، فیدبک دریافت می‌کنید و این پیشرفت مداوم، شما و تیم را باانگیزه نگه می‌داردهزینه‌ی نگه‌داری در آینده را کم می‌کنید چون MVP از پیچیدگی کدبیس کم می‌کندسریع‌تر پیشرفت می‌کنید و پیاده‌سازی آسان‌تر می‌شودسریع‌تر محصول را تحویل می‌دهید و سریع‌تر کسب درآمد می‌کنیدرویکرد کمینه محصول پذیرفتنی در برابر مخفی‌کاریک ضد استدلال رایج که در مقابل Rapid Prototyping استفاده می‌شود این است که برنامه‌نویسی مخفی‌کاری از ایده‌های شما محافظت می‌کند. مردم فکر می‌کنند که ایده‌های آن‌ها به قدری خاص و یکتا است که اگر آن را به صورت خام و به شکل MVP منتشر کنند، شرکت‌های بزرگ‌تر و قدرتمندتر آن را می‌دزدند و خیلی سریع‌تر آن را پیاد‌ه‌سازی می‌کنند. صادقانه بگویم، این یک اشتباه است. ایده‌ها ارزان هستند، این اجرای ایده‌ها است که اهمیت دارند. بعید است که یک ایده منحصر به فرد باشد، و یک احتمال قوی وجود دارد که همین الان شخص دیگری ایده‌ی شما را در ذهن دارد. برنامه‌نویسی در حالت مخفی‌کاری، به جای کم کردن رقابت، حتی شاید دیگران را تشویق کند که بر روی همان ایده کار کنند، چون همانند شما، آن‌ها هم فکر می‌کنند که آن ایده به ذهن کسی نرسیده است. برای اینکه یک ایده موفق شود، یک شخص باید آن را به واقعیت بیاورد. شخصی موفق می‌شود که سریع اقدام می‌کند، یک نسخه‌ی اولیه‌ منتشر می‌کند، از کاربران واقعی فیدبک دریافت می‌کند و مدام محصول خود را بهبود می‌بخشد. با مخفی نگه داشتن ایده صرفا پتانسیل رشد آن را محدود می‌کنیم.</description>
                <category>مجتبی میر یعقوب زاده</category>
                <author>مجتبی میر یعقوب زاده</author>
                <pubDate>Sun, 21 Aug 2022 17:06:51 +0430</pubDate>
            </item>
                    <item>
                <title>اصل ۸۰/۲۰</title>
                <link>https://virgool.io/@TabaMojj/%D8%A7%D8%B5%D9%84-%DB%B8%DB%B0%DB%B2%DB%B0-umzrd52pnyho</link>
                <description>در این فصل درباره‌ی اصل ۸۰/۲۰ می‌آموزیم. (نام دیگر آن اصل پارتو است) این اصل بیان می‌کند که ۸۰ درصد اثرات از ۲۰ درصد علل ناشی می‌شود.دقت کنید که این مطلب ترجمه‌ی خط به خط کتاب نیست و فقط قسمت‌های مهم آن آورده شده است.پایه‌های اصل ۸۰/۲۰مثال‌هایی از این اصل: اکثر درآمدها را اقلیت مردم به دست می‌آورند، اکثر نوآوری‌ها را اقلیت محققین به دست می‌آورند، اقلیت نویسنده‌ها اکثریت کتاب‌ها را نوشته‌اند و...دو علت برای محبوبیت این اصل وجود دارد. اول، این اصل به شما اجازه می‌دهد که تا زمانی که بتوانید چیزهای پراهمیت را بفهمید، آرام و پربازده باشید. این چیزهای پراهمیت شامل ۲۰ درصد فعالیت‌ها می‌شوند که منجر به ۸۰ درصد نتایج می‌شود. دوم، این اصل را می‌توانیم در شرایط متنوعی مشاهده کنیم. به همین خاطر اعتبار قابل توجهی دارد. در این شکل اصل پارتو با توزیع پارتو رسم شده است.توزیع پارتو نتایج را در برابر علت‌ها رسم می‌کند (محور y در برابر x) نتایج می‌تواند هر میزان از موفقیت یا شکست باشد مانند درآمد، بازدهی یا تعداد باگ‌ها در یک پروژه‌ی نرم‌افزاری. علت‌ها می‌توانند هر موجودیتی باشند که این نتایج با آن‌ها در ارتباط هستند مانند کارمندان، کسب‌وکار یا پروژه‌های نرم‌افزاری.برای به دست آوردن منحنی پارتو، علت‌ها را بر اساس نتایجی که تولید می‌کنند، مرتب می‌کنیم. برای مثال، شخص با بالاترین درآمد در ابتدای محور x ظاهر می‌شود و بعد از آن شخص با دومین-بیشترین درآمد.بهینه‌سازی نرم‌افزارشکل زیر اصل پارتو را در عمل و در یک پروژه‌ی نرم‌افزاری خیالی نشان می‌دهد. اقلیت کد، مسئول اکثریت زمان اجرای برنامه است.محور x نشان دهنده‌ی تابع‌های برنامه است که بر اساس زمان اجرا مرتب شده‌اند. محور y نشان دهنده‌ی زمان اجرای هر تابع است. ناحیه‌ی رنگی، نشان می‌دهد که بیشتر کدها زمان کمی به زمان کلی اجرای برنامه اضافه می‌کنند و زمان اجرای برنامه بخاطر تعداد کمی از تابع‌ها است. Juseph Juran، یکی از اولین کسانی که به اصل ۸۰/۲۰ پی برده است، مورد اول را vital few (کم‌های حیاتی) و مورد دوم را trivial many (خیلی‌های بی‌اهمیت) می‌نامد. صرف زمان زیاد برای بهینه‌سازی خیلی‌های بی‌اهمیت، به سختی زمان اجرا را بهبود می‌بخشد. وجود اصل پارتو در پروژه‌های نرم‌افزاری با شواهد علمی پشتیبانی می‌شود  (“Power Laws in Software” by Louridas,Spinellis, and Vlachos (2008))شرکت‌های بزرگ مانند Microsoft، IBM و Apple از اصل پارتو برای ساختن کامپیوترهای سریع‌تر و کاربرپسندتر استفاده می‌کنند؛ آن‌ها تمرکز خود را بر روی کم‌های حیاتی می‌گذارند؛ یعنی ۲۰ درصد کد را که بیشتر از همه کاربران آن را اجرا کرده‌اند، مدام بهینه می‌کنند. تمام کدها برابر ساخته نشده‌اند. اقلیت کدها تاثیر خیلی زیادی روی تجربه‌ی کاربری دارند، در حالی که اکثریت کدها تاثیر خیلی کمی دارند. ممکن است شما آیکون File Explorer را در روز چند بار دابل کلیک کنید اما به ندرت دسترسی‌های یک فایل را تغییر دهید. اصل ۸۰/۲۰ می‌گوید که بهینه‌سازی‌های خود را در کدام بخش‌ها متمرکز کنید.بهره‌وریبا تمرکز بر روی کم‌های حیاتی تا خیلی‌های بی‌اهمیت، می‌توانید بهره‌وری خود را ۱۰ برابر یا حتی ۱۰۰ برابر کنید.شکل زیر نشان می‌دهد که در یک شرکت با ۱۰ کارمند، فقط ۲ کارمند ۸۰ درصد نتیجه را تولید می‌کنند. در حالی که ۸ کارمند دیگر ۲۰ درصد نتیجه را تولید می‌کنند. ۸۰ درصد را تقسیم بر ۲ کارمند می‌کنیم تا به میانگین ۴۰ درصد به ازای هر کارمند با عملکرد عالی در شرکت برسیم. اگر ۲۰ درصد نتیجه را تقسیم بر ۸ کنیم، به میانگین ۲.۵ درصد به ازای هر کارمند با عملکرد ضعیف در شرکت می‌رسیم. تفاوت عملکرد ۱۶ برابر است.تفاوت ۱۶ برابری در میانگین عملکرد در واقع در میلیون‌ها شرکت در سراسر جهان وجود دارد. همچنین توزیع پارتو فراکتال نیز است، یعنی ۲۰ درصد بالای ۲۰ درصد بالا، ۸۰ درصد ِ۸۰ درصد نتایج را تولید می‌کنند. دلیل تفاوت در نتیجه‌ها را نمی‌توان هوش دانست؛ یک شخص نمی‌تواند ۱۰۰۰ برابر باهوش‌تر از شخص دیگری باشد. بلکه، تفاوت در نتایج، ناشی از تفاوت‌های رفتاری افراد مختلف یک سازمان می‌باشد. اگر شما هم همان رفتارها را داشتید، به همان نتایج می‌رسیدید. اما قبل از اینکه رفتار خود را تغییر دهید، باید به طور شفاف بدانید که می‌خواهید به چه نتیجه‌ای برسید؛ چون تحقیق‌ها در هر معیاری که بتوانید تصور کنید، نابرابری شدیدی‌ را در نتایج نشان می‌دهد.درآمد: ۱۰ درصد مردم تقریبا ۵۰ درصد درآمد را در ایالات متحده به دست می‌آورند.خوشحالی: کمتر از ۲۵ درصد مردم در آمریکای شمالی، در مقیاس ۰ تا ۱۰، خوشحالی خود را ۹ یا ۱۰ نمره‌دهی می‌کنند که ۰ بدترین زندگی ممکن و ۱۰ بهترین زندگی ممکن است.کاربران فعال ماهانه: تنها ۲ تا از ۱۰ وبسایت برتر در نظر گرفته شده برای همه‌ی کاربران،  ۴۸ درصد از ترافیک تجمعی را دریافت می‌کنند (بر اساس این اطلاعات)فروش کتاب:  تنها ۲۰ درصد از نویسنده‌ها ممکن است ۹۷ درصد فروش را به دست بیاورند.نابرابری در نتایج، یک پدیده‌ی اثبات شده در علوم اجتماعی است و اغلب با یک متریک به نام Gini coefficient اندازه‌گیری می‌شود.چطور می‌توانیم در سازمان خود، به سمت چپ منحنی توزیع پارتو حرکت کنیم ؟معیارهای موفقیتفرض کنید می‌خواهیم درآمد را بهینه‌سازی کنیم. چطور می‌توانیم به سمت چپ منحنی پارتو حرکت کنیم؟ عبارت معیارهای موفقیت را چنین تعریف می‌کنیم: اندازه‌گیری رفتارهایی که باعث می‌شوند در زمینه‌‌ی شما به موفقیت بیشتر رسید. اصل ۸۰/۲۰ برای این معیارها نیز صادق است؛ بعضی معیارهای موفقیت تاثیر زیادی روی عملکرد در زمینه‌ی شما دارند، بعضی اصلا اهمیتی ندارند.برای مثال، وقتی من به عنوان یک محقق دکتری تحقیق می‌کردم، خیلی زود فهمیدم که موفقیت یعنی cite شدن توسط دیگر محققین. به عنوان یک محقق، هرچقدر citation های بیشتری داشته باشید، اعتبار، دیده شدن و موقعیت‌های بیشتری نیز خواهید داشت. اما، افزایش تعداد citation ها به سختی یک معیار موفقیت عملی به حساب می‌آید که بتوانید روزانه آن را بهینه کنید. تعداد citation ها یک lagging indicator است چون بر اساس کارهایی است که شما در گذشته انجام دادید. مشکل lagging indicator این است که فقط عواقب کارهای گذشته را نشان می‌دهند. آن‌ها به شما نمی‌گویند که روزانه چه کارهایی را انجام دهید تا به موفقیت برسید.برای به‌دست آوردن معیاری برای انجام اقدامات درست، مفهوم leading indicator معرفی شد. leading indicator یک معیار است که قبل از اینکه یک تغییر در lagging indicator رخ دهد، آن را پیش‌بینی می‌کند. leading indicator می‌تواند lagging indicator را بهبود ببخشد. به عنوان یک محقق، اگر مقاله‌های با کیفیت بیشتری تولید کنید (leading indicator) آنگاه citation های بیشتری دریافت خواهید کرد (lagging indicator) این یعنی نوشتن مقاله‌های با کیفیت، مهم‌ترین فعالیت برای بیشتر دانشمندان است، نه فعالیت‌هایی مثل درس دادن یا آماده کردن ارائه. پس معیار موفقیت برای محققین، تولید بیشترین تعداد مقاله‌های با کیفیت است، همانطور که در این شکل آمده است.برای رفتن به سمت چپ، باید امروز کلمات بیشتری بنویسید، مقاله‌ی با کیفیت بعدی‌تان را هر چه زودتر منتشر کنید، citation های بیشتری دریافت کنید و دانشمند موفق‌تری شوید.اصل ۸۰/۲۰ به شما اجازه می‌دهد که فعالیت‌هایی را که باید روی آن‌ها متمرکز شوید، شناسایی کنید. انجام بیشتر معیارهای موفقیت، موفقیت حرفه‌ای شما را نیز افزایش خواهد داد. زمان کمتری روی کارهای دیگر صرف کنید.تمرکز و توزیع پارتواصل ۸۰/۲۰ توضیح می‌دهد که چرا تمرکز قدرتمند است.توزیع پارتو در شکل زیر، درصد پیشرفت حرکت به سمت بالای توزیع را نشان می‌دهد. آلیس پنجمین فرد سازنده در سازمان خود است. اگر بتواند یک نفر را در سازمان شکست دهد، تبدیل به چهارمین فرد سازنده در سازمان می‌شود و در آمد خود را ۱۰ درصد افزایش می‌دهد. یک قدم از این جلوتر برود، ۲۰ درصد دیگر می‌تواند به درآمد خود اضافه کند. در توزیع پارتو، پیشرفت در هر مرتبه به طور تصاعدی رشد می‌کند، پس حتی افزایش کوچک در سازندگی می‌تواند منجر به نتیجه‌ی بزرگی در درآمد شود. افزایش سازندگی می‌تواند درآمد، خوشحالی و لذت بردن از کار را افزایش دهد. بعضی‌ها از این پدیده به عنوان برنده صاحب همه چیز می‌شود (the winner takes all) یاد می‌کنند.به همین خاطر اصلا به نفع شما نیست که تمرکز نداشته باشید؛ اگر تمرکز نداشته باشید، در بسیاری از توزیع‌های پارتو حضور خواهید داشت.شکل زیر را در نظر بگیرید. فرض کنید آلیس و باب هر کدام می‌توانند روزانه ۳ واحد تلاش برای یادگیری صرف کنند. آلیس تمرکز خود را روی یک چیز می‌گذارد: برنامه‌نویسی. او تمام ۳ واحد خود را روی یادگیری کدنویسی صرف می‌کند. باب تمرکز خود را بین چند مورد پخش می‌کند: یک واحد زمانی برای بهبود توانایی‌های شطرنج، یک واحد برای بهبود توانایی برنامه‌نویسی، یک واحد هم برای بهبود توانایی‌های سیاسی. او در تمام این ۳ زمینه، به توانایی میانگین دست یافته است. اما توزیع پارتو، به طور نامتناسب به برترین‌ها پاداش می‌دهد، پس آلیس پاداش بیشتری را دریافت می‌کند.این پاداش نامتناسب در هر کدام از این سطح‌ها نیز وجود دارد. برای مثال، باب تمام وقت خود را صرف خواندن سه کتاب عمومی می‌کند (فرض کنید اسم آن‌ها مقدمه‌ای بر پایتون، مقدمه‌ای بر C++ و مقدمه ای بر جاوا است). در حالی که آلیس سه کتاب درباره‌ی درک عمیق ماشین لرنینگ با پایتون می‌خواند  (فرض کنید اسم آن‌ها مقدمه‌ای بر پایتون، مقدمه‌ای بر ماشین لرنینگ با پایتون و ماشین لرنینگ برای حرفه‌ای‌ها است) به عنوان نتیجه، آلیس تمرکز خود را روی تبدیل شدن به یک متخصص ماشین لرنینگ شدن گذاشته است و می‌تواند درخواست درآمد بیشتری بخاطر توانایی‌های مخصوص خود بکند.نتایج برای برنامه‌نویس‌هادر برنامه‌نویسی به نسبت دیگر زمینه‌ها، نتایج بسیار بیشتر به سمت بالا کج شده‌اند. به جای ۸۰/۲۰، توزیع بیشتر شبیه به ۹۰/۱۰ یا ۹۵/۵ است. این جمله از بیل گیتس را در نظر بگیرید:یک تراشکار حرفه‌ای خواهان درآمد چند برابری به نسبت یک تراشکار متوسط است اما یک برنامه‌نویس عالی ۱۰۰۰۰ برابر یک برنامه‌نویس متوسط ارزش دارد.منظور بیل گیتس این است که تفاوت بین یک برنامه‌نویس عالی و متوسط ۱۶ برابر نیست بلکه ۱۰۰۰۰ برابر است! در اینجا چند دلیل می‌خوانیم که چرا دنیای نرم‌افزار مستعد چنین توزیع پارتو است: یک برنامه‌نویس عالی می‌تواند مسائلی را حل کند که یک برنامه‌نویس متوسط نمی‌تواند. در بعضی مواقع، این باعث می‌شود که برنامه‌نویس عالی بی‌نهایت بار سازنده‌تر باشدیک برنامه‌نویس عالی می‌تواند کدی بنویسد که ۱۰۰۰۰ بار سریع‌تر از کد یک برنامه‌نویس متوسط باشدیک برنامه‌نویس عالی کدی می‌نویسد که باگ کمتری دارد. تصور کنید یک باگ امنیتی چطور می‌تواند اعتبار و برند مایکروسافت را تحت تاثیر قرار دهد! همچنین هر باگ، می‌تواند زمان، انرژی و هزینه‌ی زیادی را متحمل کندیک برنامه‌نویس عالی می‌تواند کدی بنویسد که به راحتی بتوان آن را گسترش داد. این باعث می‌شود که سازندگی هزاران برنامه‌نویس که بعدها قرار است بر روی آن کد کار کنند، بیشتر شود یک برنامه‌نویس عالی خارج از چارچوب فکر می‌کند و راه‌حل‌های خلاقانه پیدا می‌کندمعیار موفقیت برای برنامه‌نویس‌هامتاسفانه جمله‌ی تبدیل به یک برنامه‌نویس عالی شوید یک معیار موفقیت نیست که بتوانید بهینه کنید؛ این مسئله چندبعدی است. یک برنامه‌نویس عالی کد را سریع می‌فهمد، الگوریتم‌ها و ساختمان‌های داده را می‌داند، تکنولوژی‌های مختلف و نقاط قوت و ضعف آن‌ها را می‌شناسد، می‌تواند با دیگر افراد همکاری کند، خلاق و خوش‌برخورد است، دانش خود را به‌روزرسانی می‌کند و... اما نمی‌توانید استاد همه‌ی این کارها باشید! اگر بر روی اقلیت حیاتی تمرکز نکنید، توسط خیلی‌های بی‌اهمیت غرق خواهید شد. برای اینکه یک برنامه‌نویس عالی شوید، باید روی اقلیت‌های حیاتی تمرکز کنید.یکی از اقلیت‌های حیاتی که باید بر روی آن تمرکز کنید، نوشتن تعداد خطوط بیشتر کد است. هر چه قدر کد بیشتری بنویسید، برنامه‌نویس بهتری خواهید شد.با نوشتن کدهای زیاد، درک بهتری نسبت به کد پیدا می‌کنید و مانند یک برنامه‌نویس حرفه‌ای صحبت و رفتار خواهید کرد، برنامه‌نویس‌های بهتری را به دایره‌ی ارتباطات خود جذب و تسک‌های برنامه‌نویسی چالشی‌تری را پیدا خواهید کرد.یک فعالیت ۸۰/۲۰ که می‌توانید هر روز آن را دنبال کنید: تعداد خط کدهایی که در روز می‌نویسید بشمارید و آن را بهینه کنید. این را تبدیل به یک بازی کنید طوری که هر روز حداقل به اندازه‌ی میانگین روزانه‌ی خود، کد بنویسید.توزیع پارتو در دنیای واقعیریپازیتوری گیت‌هاب TensorFlowمثال‌های زیادی از توزیع پارتو را در ریپازیتوری‌های گیت‌هاب می‌توانیم ببینیم. برای مثال بیایید TensorFlow را در نظر بگیریم. در این شکل‌ها می‌توانید هفت contributor در ریپازیتوری TensorFlow را ببینید.کاربر tensorflow-gardener بیش از ۲۰ درصد از ۹۳۰۰۰ کامیت این ریپازیتوری‌ را به خود اختصاص داده است. با توجه به این که هزاران contributor وجود دارند، این توزیع خیلی با توزیع ۸۰/۲۰ فاصله دارد. علت این است که کاربر tensorflow-gardener شامل تیمی از برنامه‌نویس‌های گوگل است که این ریپازیتوری‌ را ساخته‌اند. اگر این کاربر را کنار بگذاریم، افرادی که باقی می‌مانند برنامه‌نویس‌های بسیار موفقی هستند. بسیاری از آن‌ها در شرکت‌های بزرگی موقعیت‌های خوبی داشته‌اند. اینکه قبل از اضافه کردن تعداد زیادی کامیت به این ریپازیتوری موفق بودند یا بعد از آن موفق شده‌اند، صرفا یک بحث تئوری است. برای بحث عملی، این را در نظر بگیرید: باید عادت موفقیت خود را شروع کنید و هر روز کدهای بیشتری بنویسید. هیچ چیز شما را از تبدیل شدن به شماره دو در ریپازیتوری TensorFlow باز نمی‌دارد؛ روزانه دو یا سه کد با ارزش به این مخزن اضافه کنید و این کار را تا دو یا سه سال آینده ادامه دهید. اگر پافشاری کنید، می‌توانید با یک عادت و چسبیدن به آن، جزء بهترین برنامه‌نویس‌های جهان شوید.توزیع پارتو فراکتال استاگر زوم کنید و فقط قسمتی از توزیع را بررسی کنید، می‌بینید که یک توزیع پارتوی دیگر وجود دارد! این تا موقعی صادق است که داده زیاد پراکنده نباشد (در این صورت خاصیت فراکتال خود را از دست می‌دهد) یک نقطه به تنهایی نمی‌تواند یک توزیع پارتو باشد.توزیع پارتو کاربردهای عملی زیادی در زندگی و برنامه‌نویسی دارد و در ادامه‌ی کتاب باز هم آن را بررسی خواهیم کرد. اما به تجربه‌ی من، متحول‌کننده‌ترین کاربرد آن این است که تبدیل به یک متفکر ۸۰/۲۰ شوید؛ یعنی مدام راه‌هایی را پیدا کنید که با خیلی کمتر، کارهای بیشتری انجام دهید. دقت کنید که اعداد پارتو ممکن است در زندگی شما متفاوت باشند (۷۰/۳۰ ، ۸۰/۲۰ یا ۹۰/۱۰) اما می‌توانید از خاصیت فراکتال توزیع‌های سازندگی، ارزش‌هایی را به دست‌ بیاورید. برای مثال، همیشه درست است که تعدادی از برنامه‌نویس‌ها از تعدادی دیگر، درآمد بیشتری دارند و از بین این برنامه‌نویس‌های پردرآمد، بازهم کسانی وجود دارند که از بقیه درآمد بیشتری دارند. این روند تنها موقعی متوقف می‌شود که داده خیلی پراکنده شود. درآمد: ۲۰ درصد از ۲۰ درصد برتر برنامه‌نویس‌ها، ۸۰ درصد از ۸۰ درصد درآمد را کسب می‌کنند. به عبارت دیگر، ۴ درصد از برنامه‌نویس‌ها ۶۴ درصد از درآمد را کسب می‌کنند. این یعنی شما هیچوقت در شرایط اقتصادی فعلی‌ خود باقی نمی‌مانید، حتی اگر جز ۲۰ درصد برتر برنامه‌نویس‌ها باشید. (این مقاله صرفا یکی از چند مقاله‌ای است که خاصیت فراکتال توزیع درآمد را نشان می‌دهد)پیشرفت: مهم نیست که کجای توزیع پارتو حضور دارید، می‌توانید خروجی خود را با حرکت به سمت چپ به صورت نمایی افزایش دهید. این کار را با عادت‌های موفقیت و قدرت تمرکز می‌توانید انجام دهید. تا جایی که به حالت بهینه دست پیدا نکرده‌اید، جا برای پیشرفت وجود دارد تا با انجام کارهای کمی به چیزهای زیادی برسید؛ حتی اگر در حال حاضر نیز یک شخص، شرکت یا اقتصاد باشید که به خوبی گسترش یافته است.فعالیت‌هایی که شما را به بالای منحنی پارتو منتقل می‌کنند همیشه واضح نیستند اما هیچوقت هم تصادفی نیستند. بسیاری از افراد در جستجو برای یافتن معیارهای موفقیت در زمینه‌ی خود، تسلیم می‌شوند چون می‌گویند که طبیعت تصادفی نتایج، موفقیت را تصادفی می‌کند. چه نتیجه‌گیری‌ اشتباهی! نمی‌توان با نوشتن تعداد کد کم در روز تبدیل به یک برنامه‌نویس ارشد شد. فاکتورهای دیگری نیز تاثیر گذار هستند اما باز هم موفق شدن را تبدیل به یک بازی شانسی نمی‌کنند. با تمرکز بر روی معیارهای موفقیت در صنعت خود، می‌توانید احتمال‌ها را به نفع خود تغییر دهید.نکات تمرینی ۸۰/۲۰این فصل را با بیان ۹ نکته برای استفاده از اصل پارتو به اتمام می‌رسانیم.معیارهای موفقیت خود را پیدا کنیدابتدا صنعت خود را تعریف کنید. ببینید موفق‌ترین حرفه‌‌ای‌های صنعت شما چه کارهایی را خارق‌العاده انجام می‌دهند و شما با انجام روزانه‌ی چه کارهایی می‌توانید به ۲۰ درصد برتر نزدیک‌تر شوید. اگر برنامه‌نویس هستید، ممکن است معیار موفقیت شما تعداد کدهایی باشید که روزانه می‌نویسید. این را تبدیل به یک بازی برای خود بکنید و مدام از خود عبور کنید. یک آستانه‌ی حداقلی تعیین کنید و تا زمانی که در روز به این آستانه نرسیده‌اید، روز را تمام نکنید.اهداف بزرگ خود را در زندگی مشخص کنیدآن‌ها را بنویسید. بدون داشتن اهداف بزرگی که به خوبی تعریف نشده‌اند، نمی‌توانید برای مدت طولانی به یک چیز بچسبید. دیدید که استراتژی ضروری برای حرکت در منحنی پارتو، این است که مدت زمان طولانی‌ای در بازی باشید و در بازی‌های کمتری شرکت کنید.به دنبال راه‌هایی باشید که چیزهای مشابه را با منابع کمتری به دست بیاوریدچطور می‌توانید ۸۰ درصد نتایج را در ۲۰ درصد زمان به دست بیاورید؟  آیا می‌توانید فعالیت‌های باقی‌مانده را که ۸۰ درصد زمان را می‌گیرند اما منجر به ۲۰ درصد نتایج می‌شوند، حذف کنید؟ اگر نه، آیا می‌توانید انجام آن را به کس دیگری بسپارید؟به موفقیت‌های خود فکر کنیدچه کارهایی انجام دادید که منجر به نتایج عالی شد؟ چطور می‌توانید این کارها را بیشتر انجام دهید؟به شکست‌های خود فکر کنیدچطور می‌توانید کارهایی را که منجر به شکست شد، کمتر انجام دهید؟کتاب‌های بیشتری مربوط به صنعت خود مطالعه کنیدبا خواندن کتاب های بیشتر، تجربه‌ی عملی را بدون سرمایه‌گذاری هنگفت زمان و انرژی برای تجربه واقعی آن، شبیه سازی می کنید. از تجربه‌های دیگران استفاده می‌کنید. درباره‌ی روش‌های جدید انجام دادن کارها یاد می‌گیرید. مهارت‌های بیشتری در زمینه‌ی خود کسب می‌کنید. یک برنامه‌نویس با سطح سواد بالا، می‌تواند یک مسئله را ۱۰ تا ۱۰۰ برابر سریع‌تر از یک تازه‌کار حل کند. خواندن کتاب در زمینه‌ی خود می‌تواند یکی از معیارهای موفقیت در زمینه‌ی خود باشد که می‌تواند شما را به موفقیت برساند.بیشتر زمان خود را صرف بهبود و تنظیم محصولات موجود کنیدبه جای ساخت محصولات جدید، محصولات موجود را بهبود و تنظیم کنید. این از توزیع پارتو می‌آید. اگر یک محصول در کسب‌وکار خود دارید، می‌توانید تمام انرژی خود را صرف بالا بردن این محصول در توزیع پارتو کنید. با این کار نتایج نمایی را برای خود و شرکت حاصل می‌کنید. اما اگر بدون بهبود و تنظیم محصولات قدیمی، محصولات جدید تولید کنید، همیشه محصولات با کیفیت زیر میانگین خواهید داشت. فراموش نکنید: نتایج بزرگ در سمت چپ توزیع پارتو یافت می‌شوند.لبخند بزنیدتعجب آور است که برخی از پیامدها چقدر ساده هستند. اگر یک شخص مثبت هستید، خیلی از چیزها آسان‌تر خواهند بود. افراد بیشتری با شما همکاری خواهند کرد. مثبت بودن، پشتیبانی و خوشحالی بیشتری را تجربه خواهید کرد. لبخند زدن یک فعالیت کلیدی است که هزینه‌ی کم و تاثیر بسیار زیادی دارد.کارهایی که از ارزش کم می‌کنند، انجام ندهیداین چیزها می‌توانند شامل سیگار کشیدن، کم خوابیدن، غذای ناسالم خوردن و ... باشد. اگر کارهایی را که به شما آسیب می‌رسانند انجام ندهید، سالم‌تر، خوشحال‌تر و موفق‌تر خواهید بود.</description>
                <category>مجتبی میر یعقوب زاده</category>
                <author>مجتبی میر یعقوب زاده</author>
                <pubDate>Fri, 12 Aug 2022 13:21:21 +0430</pubDate>
            </item>
                    <item>
                <title>چگونه پیچیدگی به بهره‌وری شما آسیب می‌رساند</title>
                <link>https://virgool.io/@TabaMojj/%D9%81%D8%B5%D9%84-%DB%B1-%DA%86%DA%AF%D9%88%D9%86%D9%87-%D9%BE%DB%8C%DA%86%DB%8C%D8%AF%DA%AF%DB%8C-%D8%A8%D9%87-%D8%A8%D9%87%D8%B1%D9%87-%D9%88%D8%B1%DB%8C-%D8%B4%D9%85%D8%A7-%D8%A2%D8%B3%DB%8C%D8%A8-%D9%85%DB%8C-%D8%B1%D8%B3%D8%A7%D9%86%D8%AF-rzkeo5vsmlee</link>
                <description>فصل اول کتاب درباره‌ی پیچیدگی صحبت می‌کند؛ اینکه پیچیدگی چیست، کجا رخ می‌دهد و چگونه به بهره‌وری شما آسیب می‌رساند. دقت کنید که این مطلب ترجمه‌ی خط به خط کتاب نیست و فقط قسمت‌های مهم آن آورده شده است.پیچیدگی چیست ؟در زمینه‌های مختلف، عبارت پیچیدگی معناهای متفاوتی دارد. در این کتاب پیچیدگی را این چنین تعریف می‌کنیمیک کل که از اجزایی تشکیل شده است که تحلیل، فهمیدن یا توضیح دادن این کل، سخت است.پیچیدگی در چرخه‌ی زندگی یک پروژهبر اساس استاندارد IEEE برای مهندسی نرم‌افزار، یک پروژه‌ی نرم‌افزاری شامل ۶ مرحله است:حتی در پروژه‌های نرم‌افزاری کوچک هم این مراحل طی می‌شوند. لزوما هر مرحله یکبار انجام نمی‌شود و ممکن است یک مرحله را چند بار انجام دهیم.برنامه‌ریزی (Planning)برنامه‌ریزی، اولین مرحله از چرخه‌ی زندگی توسعه‌ی نرم‌افزار است که در ادبیات مهندسی با نام تحلیل نیازمندی‌ها (Requirement Analysis) نیز شناخته می‌شود. هدف این مرحله این است که مشخص شود محصول چگونه خواهد بود. یک برنامه‌ریزی موفق، منجر به تعریف دقیق فیچرهای مورد نیاز برای تحویل به کاربر نهایی می‌شود.اگر خودتان به تنهایی بر روی یک پروژه کار می‌کنید یا مسئول مدیریت همکاری‌ها بین چند تیم توسعه‌ی نرم‌افزار هستید، باید مجموعه‌ی فیچرهای بهینه‌ی نرم‌افزار را به دست بیاورید.چند فاکتور باید در نظر گرفته شود: هزینه‌ی تولید یک فیچر، میزان ریسک عدم توانایی در پیاده‌سازی موفقیت‌آمیز فیچر، ارزش مورد انتظار کاربر نهایی، بازاریابی، قابلیت نگه‌داری، مقیاس‌پذیری و ...این مرحله مهم است چون می‌تواند از هدر رفت انرژی و منابع مالی در آینده جلوگیری کند.تعریف کردن (Defining)این مرحله شامل تبدیل نتایج بخش برنامه‌ریزی به نیازمندی‌های نرم‌افزاری مشخص است. به عبارت دیگر، خروجی مرحله‌ی قبل را به‌صورت رسمی تعریف می‌کند تا از کاربران نهایی که بعدها قرار است از این محصول استفاده کنند، فیدبک و تاییدیه دریافت شود.اگر زمان زیادی را صرف برنامه‌ریزی کرده‌اید اما نتوانستید آن را به خوبی انتقال دهید، بعدها مشکلات و سختی‌های زیادی را ایجاد خواهد کرد. نیازمندی‌هایی که به غلط تعریف شده‌اند و به پروژه کمک می‌کنند دقیقا به بدی نیازمندی‌هایی هستند که به خوبی تعریف شده‌اند اما به پروژه کمکی نمی‌کنند.اگر بخواهید ایده‌ها (در اینجا نیازمندی‌ها) را سرسری از ذهن خود به ذهن دیگری انتقال دهید، در دام پیچیدگی خواهید افتاد!طراحی (Designing)هدف این مرحله، تهیه‌ی یک پیش‌نویس از معماری سیستم، طراحی رابط کاربری و تصمیم‌گیری برای ماژول‌ها و اجزایی که عملکردهای تعریف شده را تحویل می‌دهند است. حین انجام این کار، به نیازمندی‌هایی که در مراحل قبلی تولید شده‌اند توجه می‌کنیم.استاندارد طلایی این مرحله این است که یک تصویر شفاف از نرم‌افزار نهایی بسازیم و بدانیم که محصول نهایی چه ظاهری خواهد داشت و چگونه ساخته خواهد شد.ساختن (Building)این مرحله جایی است که بیشتر کدنویس‌ها دوست دارند زمان‌شان را صرف کنند. اینجا جایی است که پیش‌نویس معماری به محصول نرم‌افزاری تبدیل می‌شود.با آماده‌سازی‌های مناسب در مراحل قبلی، بسیاری از پیچیدگی‌ها حذف شده‌اند. سازندگان باید بدانند که از بین تمام فیچرها، کدام فیچرها را پیاده‌سازی کنند، این فیچر چه شکلی خواهد داشت و از چه ابزاری برای پیاده‌سازی آن استفاده کنند.آزمایش (Testing)تمام فیچرهای خواسته شده پیاده‌سازی شده‌اند و به نظر می‌آید برنامه کار می‌کند. اما هنوز کار شما تمام نشده است. باید عملکرد برنامه را برای ورودی‌ها و طرز استفاده‌های مختلف آزمایش کنیم. معمولا این مرحله مهم‌ترین مرحله است؛ به طوری که بسیاری از فعالان حوزه‌ی نرم‌افزار، از توسعه‌ی مبتنی بر تست (test-driven development) دفاع می‌کنند. در این نوع توسعه، بدون نوشتن تمام تست‌ها، روند توسعه شروع نمی شود.دیپلوی کردن (Deployment)حال نرم‌افزار مرحله‌ی تست را گذرانده است و آماده‌ی انتشار است. انتشار می‌تواند صورت‌های مختلفی داشته باشد. یک نرم‌افزار ممکن است در مارکت‌پلیس یا یک repository منتشر شود. این مرحله ممکن است با استفاده از Continuous Deployment چندین بار انجام شود. بسته به پروژه، ممکن است در این مرحله مجبور باشید محصول را منتشر کنید، کمپین‌های تبلیغاتی بسازید و با کاربران اولیه‌ی محصول صحبت کنید و باگ‌های آن را برطرف کنید.</description>
                <category>مجتبی میر یعقوب زاده</category>
                <author>مجتبی میر یعقوب زاده</author>
                <pubDate>Fri, 12 Aug 2022 13:18:56 +0430</pubDate>
            </item>
                    <item>
                <title>یکپارچگی داده</title>
                <link>https://virgool.io/@TabaMojj/%DB%8C%DA%A9%D9%BE%D8%A7%D8%B1%DA%86%DA%AF%DB%8C-%D8%AF%D8%A7%D8%AF%D9%87-rmtsczzb7itg</link>
                <description>فصل دوم از کتاب I ♥ Logsاز بین کاربردهای لاگ، اولین کاربردی که می‌خواهیم درباره‌ی آن صحبت کنیم، Data Integration یا یکپارچگی داده است. ابتدا آن را تعریف کنیم:یکپارچگی داده یعنی در دسترس قرار دادن کلیه داده های یک سازمان در اختیار کلیه سرویس ها و سیستم هایی که به آن داده احتیاج دارنداصطلاح Data Integration آنچنان رایج نیست اما معادل بهتری هم وجود ندارد. اصطلاح  ETL یعنی Extract, Transform, Load معمولا یک قسمت محدود از Data Integration را شامل می‌شود. اما خب بیشتر چیزهایی که اینجا می‌گوییم می‌تواند به عنوان ETLای در نظر گرفته شود که همچنین شامل سیستم‌های بلا درنگ و جریان پردازش می‌شود.استفاده‌ی بهینه از داده‌ها، احتیاج به چیزی مانند هرم سلسله‌مراتب نیازهای مزلو دارد. پایه‌ی هرم شامل ضبط تمام داده‌های مربوطه و قرار دادن آن‌ها در یک محیط پردازش مناسب است (مثلا یک سیستم پردازش کوئری یا اسکریپت پایتون) این داد‌ه‌ها باید به روشی یکسان مدل سازی شوند تا خواندن و پردازش آن آسان شود. وقتی این نیاز برطرف شد، منطقی است که به دنبال زیرساختی باشیم تا این داده‌ را به روش‌های مختلف پردازش کنیم: MapReduce، سیستم‌های کوئری بلا درنگ و ...یکپارچگی داده: ۲ پیچیدگیدو مورد باعث می‌شوند یکپارچگی داده تبدیل به یک مسئله‌ی دشوار شود.داده‌ها متنوع‌تر هستنداگر ۱۵ سال پیش از یک شرکت می‌پرسیدید که چه داده‌ای دارند، شروع به توصیف داده‌های معاملاتی خود شامل کاربران، محصولات و سفارش‌ها می‌کردند که در یک Relational Database نگهداری می‌شد.اما تعریف‌ها گسترش پیدا کرده‌اند. امروزه شرکت‌ها شامل Event Data نیز هستند. داده‌های رویدادی، چیزهایی را که اتفاق می‌افتند ضبط می‌کنند.مثلا در سیستم‌های مبتنی بر وب، این یعنی فعالیت‌های کاربر. این نوع داده‌ها باعث می‌شوند روش‌های سنتی یکپارچگی داده، تکانی به خود بدهند چون داده‌های رویدادی بسیار بسیار بزرگتر از داده‌های معاملاتی قدیمی می‌شوند.هیاهوی سیستم‌های داده‌ای تخصصیدومین مورد، هیاهوی سیستم‌های داده‌ای تخصصی است که طرفدارهای زیادی پیدا کرده‌اند و معمولا هم به صورت رایگان در اختیار هستند. این سیستم‌های تخصصی برای OLAP، جستجو، ذخیره‌سازی آنلاین، پردازش دسته‌ای، تحلیل گراف و ... وجود دارند.ترکیب داده‌های زیاد در انواع مختلف و میل به وارد کردن این داده‌ها در سیستم‌های متنوع، باعث می‌شود یکپارچگی داده تبدیل به یک مسئله‌ی پیچیده شود.جریان داده با ساختاربندی لاگچطور می‌توانیم این مشکل را حل کنیم؟ اینطور که پیداست ساختمان داده‌ی طبیعی برای مدیریت جریان داده بین سیستم‌ها، لاگ است. دستورالعمل آن ساده است:تمام داده‌های یک سازمان را بگیر و برای اشتراک‌گذاری بلا درنگ، آن را در یک لاگ مرکزی قرار بده هر منبع داده را می‌توان با لاگ خود مدل کرد. منبع داده می‌تواند یک برنامه باشد که ایونت‌ها را لاگ می‌کند (مثلا کلیک‌ها یا بازدیدهای صفحه‌ها) یا یک تیبل دیتابیس باشد که تغییرات را لاگ می‌کند. هر سیستمی که در لاگ مشترک می‌شود (Subscribe)، با حداکثر سرعت لاگ را می‌خواند، هر رکورد تازه را به استور خود اعمال می‌کند و مکان خود را در فایل لاگ به جلو می‌برد. هر نوع سیستم داده‌ای می‌تواند در این لاگ‌ها مشترک شود: کش، هدوپ، یک دیتابیس سایت، سیستم جستجو و ...مفهوم لاگ به هر کدام از تغییرها یک ساعت منطقی می‌دهد تا همه‌ی مشترکین را بتوان با آن اندازه گیری کرد. این امر استدلال درباره‌ی وضعیت سیستم‌هایی که در لاگ مشترک هستند، راحت‌تر می‌کند چون هر کدام زمانی را که یک چیزی را خوانده‌اند، دارند.برای اینکه این مورد بیشتر ملموس شود، یک حالت ساده را در نظر بگیرید که یک دیتابیس و چند سرور کش وجود دارد. لاگ یک راه برای همگام‌سازی تمام آپدیت‌ها در تمام این سیستم‌ها ارائه می‌دهد و بر اساس زمانی که در لاگ قرار دارند، می‌توان درباره‌ی وضعیت آن‌ها استدلال کرد. فرض کنید یک رکورد با ورودی X در لاگ می‌نویسیم و بعد می‌خواهیم آن را از کش بخوانیم. اگر می‌خواهیم تضمین کنیم که داده‌های  قدیمی را نمی‌بینیم، باید مطمئن باشیم از کشی که تا ورودی X نخوانده است، نمی‌خوانیم.رابطه با ETL و Data Warehouseانبار داده، پایگاه داده‌ی تحلیلی یا Data Warehouse یک مخزن(Repository) برای داده‌ی تمیز، یکپارچه‌‌ و ساختار یافته است تا بتوان بر روی آن تحلیل انجام داد. تعریفی که از Data Warehouse ارائه شد، شامل چند بخش است. داده از منابع داده‌ی مختلف استخراج می‌شود، تبدیل به یک فرم قابل فهم می‌شود(ساختار پیدا می‌کند) و در یک انبار داده‌ی مرکزی ذخیره می‌شود. داشتن یک همچین مرکزی که یک کپی تمیز از تمام داده‌های ما دارد، یک دارایی بسیار ارزشمند برای تحلیل‌ و پردازش‌های مبتنی بر داده است. اما روش‌های رسیدن به آن کمی تاریخ گذشته هستند.مشکل اصلی یک سازمان داده‌محور، اتصال داده‌ی تمیز و یکپارچه به انبار داده است. انبار داده یک زیرساخت کوئری دسته‌ای است که مناسب گزارش‌ و تحلیل است؛ مخصوصا زمانی که کوئری‌ها شامل Count، Aggregation و Filtering است. اما داشتن یک سیستم کوئری دسته‌ای که تنها مخزن داده‌ی تمیز و کامل است، یعنی برای سیستم‌هایی که به وارد شدن لحظه‌ای داده احتیاج دارند، داده غیر  قابل دسترس است. مثل سیستم‌های پردازش بلا درنگ، سیستم‌های جستجو و سیستم‌های مانیتورینگ.در واقع ETL شامل دو چیز است. اول یک فرآیند استخراج و پاکسازی داده انجام می‌شود. در اینجا داده‌هایی که در سیستم‌های مختلف وجود دارند، جمع‌آوری می‌شوند و عملا محدودیت‌هایی که هر سیستم خاص ایجاد می‌کند، از بین می‌رود. دوم، داده برای کوئری‌های مناسب انبار داده دوباره ساختار بندی می‌شود (مثلا تبدیل به Star Schema یا Snowflake Schema می‌شود، یا به فرمت ستونی تبدیل می‌شود و ...) ترکیب این دو مورد یک مشکل است. مخزنی که شامل داده‌ی تمیز و یکپارچه است، باید به صورت بلا درنگ برای پردازش با تاخیر کم در دسترس باشد.مقیاس‌پذیری سازمانی و ETLمشکل قدیمی تیم انبار داده این است که آنها مسئول جمع‌آوری و پاکسازی تمام داده‌ای هستند که توسط بقیه‌ی تیم‌های سازمان تولید می‌شود. تولید کننده‌های داده معمولا اطلاعی از استفاده‌ی داده در انبار داده ندارند و در نهایت داده‌ای تولید می‌کنند که استخراج آن سخت است و احتیاج به تبدیل‌های پیچیده دارد تا به شکل قابل استفاده در بیاید.یک روش بهتر این است که یک پایپ‌لاین مرکزی داشته باشیم، یعن لاگ، که یک API کاملا مشخص شده برای اضافه کردن داده دارد. وظیفه‌ی ایجاد یکپارچگی با این پایپ‌لاین و ارائه‌ی داده‌‌ی تمیز با ساختار مناسب، برعهده‌ی سازنده‌ی داده است. این یعنی به عنوان قسمتی از طراحی و پیاده‌سازی سیستم‌شان، آن‌ها باید مسئله‌ی خارج کردن داده با ساختار مناسب برای تحویل به پایپ‌لاین را در نظر بگیرند.اضافه شدن سیستم‌های ذخیره‌سازی جدید هیچ عواقبی برای تیم انبار داده ندارد، چون آن‌ها یک نقطه‌ی مرکزی برای ادغام دارند. تیم انبار داده تنها مسئله‌ی بارگزاری داده‌ی ساختار یافته از لاگ مرکزی و انجام تغییرات مناسب با سیستم‌شان را انجام می‌دهند.این نکته‌ی مقیاس‌پذیری سازمانی موقعی اهمیت پیدا می‌کند که یک سازمان می‌خواهد یک سری سیستم‌های داده‌ای به انبار داده‌های قدیمی خود اضافه کند. برای مثال، می‌خواهد قابلیت جستجو را به تمام دیتاست‌های سازمان اضافه کند. یا می‌خواهد سیستم مانیتورینگ داده‌ی جریانی به همراه نمودار و سیستم اطلاع‌رسانی اضافه کند. در هر صورت، زیرساخت انبار داده‌‌ی قدیمی یا کلاستر هدوپ، نامناسب خواهد بود. این توجیه می کند که چرا بعضی از سازمان‌ها این قابلیت‌ها را برای تمام داده‌های خود ندارند. در مقابل، اگر سازمان داده‌های خود را با ساختار و یک شکل ساخته بود، اضافه کردن یک سیستم که به تمام داده‌ها دسترسی دارد تنها به چند تغییر کوچک در پایپ‌لاین احتیاج داشت.تبدیل‌های داده را باید کجا بگذاریم؟این معماری اختیار‌های مختلفی در اختیار ما می‌گذارد که داده را کجا تمیز یا تبدیل کنیم:می‌تواند قبل از اضافه شدن داده به لاگ شرکت و توسط تولید کننده‌ی داده انجام شودمی‌تواند به صورت بلا درنگ روی لاگ انجام شود (که منجر به تولید یک لاگ جدید ِ تبدیل شده خواهد شد)می‌تواند به عنوان مرحله‌ای از لود داده به یک سیستم مقصد انجام شودبهترین مدل این است که تولید کننده‌ی داده، قبل از وارد شدن داده به لاگ، پاکسازی را انجام دهد. این یعنی داده‌ها به شکل متعارف هستند و  تحت تاثیر و در اختیار یک کد که آن‌ها را تولید کرده  یا سیستمی که در آن قرار گرفته بودند، نیستند. این جزئیات بهتر است توسط تیمی که داده را تولید می‌کند، مدیریت شود چون این تیم بیشترین اطلاعات را درباره‌ی داده‌ی خود دارد. هر منطقی که در این مرحله‌ اعمال می‌شود، باید برگشت‌پذیر (Reversible) و بدون از دست دادن (Loseless) باشد.هر نوع تبدیلی که یک مقداری به داده اضافه می‌کند و می‌تواند بلا درنگ انجام شود، باید به صورت پس‌پردازش روی لاگ خام که تولید شده است، انجام شود. با این کار، لاگ اصلی همچنان در دسترس است، و همچنین عملیات پس‌پردازش ما باعث تولید یک لاگ فایل اضافی شده است.[منظور این است که اطلاعات درون لاگ اصلی قرار می‌گیرند. با انجام عملیات پس‌پردازش، یک لاگ جدید تولید می‌شود که مستقل از لاگ اصلی است]در نهایت، فقط Aggregationای که مختص سیستم مقصد است باید به عنوان بخشی از فرآیند بارگیری در سیستم مقصد تعریف شود. این ممکن است شامل تبدیل داده به یک Star Schema یا Snowflake Schema برای تحلیل و گزارش در انبار داده باشد.جدا کردن سیستم‌هابیایید درباره‌ی مزایای جانبی این معماری صحبت کنیم: سیستم‌های جداشده و ایونت‌ محور را در اختیار ما می‌گذارد. روش معمول برای داده‌های کاربران در صنعت وب، این است که داده‌ها در یک فایل تکست لاگ شوند تا بتوانند در یک انبار داده یا Hadoop برای Aggregation و Query قرار بگیرند. مشکل این روش همان مشکل روش &quot;تمام ETL&quot; است که قبلا گفتیم: جریان داده را به قابلیت‌ها و برنامه‌ی پردازش انبار داده مبتنی می‌کند.لینکدین از روش Log-Centric برای داده‌های ایونت استفاده می‌کند. از Kafka به عنوان لاگ مرکزی که چند سیستم در آن مشترک می‌شوند، استفاده می‌کند. صد ها نوع ایونت تعریف شده است که هر کدام یک Attribiute خاص درباره‌ی یک عمل را ضبط می‌کنند. برای فهمیدن خوبی این روش، یک عملیات ساده‌ی نمایش آگهی شغلی را در نظر بگیرید. صفحه‌ی آگهی شغلی باید فقط شامل منطق نمایش شغل باشد. اما در یک سایت داینامیک، این می‌تواند همراه با یک سری عملیات دیگر که نامربوط به نمایش شغل هستند، همراه شوند. برای مثال فرض کنید میان این سیستم‌ها باید یکپارچگی ایجاد کنیم:باید این داده‌ها را به Hadoop و انبار داده برای پردازش‌های آفلاین ارسال کنیمیک سیستم امنیتی باید تعداد بازدیدها را بشمارد تا مطمئن شود بازدید کننده قصد Scraping نداردباید این آمار بازدید برای صفحه‌ی تحلیل آگهی شغلی جمع آوری شودسیستم پیشنهاد شغل باید تعداد بازدید را ضبط کند تا مطمئن باشد یک شغل بارها و بارها برای یک کاربر نمایش داده نشودخیلی زود، عمل ساده‌ی نمایش آگهی شغلی تبدیل به یک عمل پیچیده شد. با اضافه کردن دیگر جاها برای نمایش، مانند اپلیکیشن موبایل، این پیچیدگی مدام در حال افزایش است. بدتر، سیستم‌ها در هم تنیده می‌شوند؛ شخصی که روی نمایش شغل کار می‌کرد، حالا باید درباره‌ی بقیه‌ی سیستم‌ها و نحوه‌ی کارکرد آن‌ها آشنا باشد تا مطمئن باشد یکپارچگی حفظ شده است. این فقط یک مثال بود، در عمل سخت‌تر از این هم می‌شود.روش ایونت‌محور یک روش برای ساده‌سازی این عملیات ارائه می‌دهد. صفحه‌ی نمایش آگهی شغلی حالا فقط یک شغل نمایش می‌دهد و این مورد را که یک شغل به همراه یک سری ویژگی‌ها نمایش داده شده است، ضبط می‌کند.  حال هر کدام از بقیه‌ی سیستم‌ها که به این ویژگی‌ها احتیاج دارند،  مثلا سیستم پیشنهادگر یا سیستم امنیتی، در لاگ مشترک می‌شوند و پردازش‌ خود را انجام می‌دهند. کد مختص نمایش لازم نیست از کاری که بقیه‌ی سیستم‌ها انجام می‌دهند، مطلع باشد یا اگر یک مصرف کننده‌ی داده‌ی جدید اضافه شد، تغیری بکند.</description>
                <category>مجتبی میر یعقوب زاده</category>
                <author>مجتبی میر یعقوب زاده</author>
                <pubDate>Mon, 12 Jul 2021 22:47:30 +0430</pubDate>
            </item>
                    <item>
                <title>لاگ چیست؟</title>
                <link>https://virgool.io/@TabaMojj/%D9%84%D8%A7%DA%AF-%DA%86%DB%8C%D8%B3%D8%AA-dcx1gp8an5lj</link>
                <description>فصل اول از کتاب I ♥ Logsاین کتاب درباره‌ی Logها است. چرا یک کتاب درباره‌ی لاگ‌ها نوشته شده است؟ لاگ‌ یک مفهوم است که در قلب بسیاری از سیستم‌ها وجود دارد؛ از دیتابیس‌های NoSQL تا کریپتوکارنسی. در این مطلب می‌خواهیم ببینیم لاگ‌ها در یک سیستم توزیع یافته، چگونه کار می‌کنند و سپس چند مثال عملی از این مفهوم را بررسی کنیم: وارد کردن داده، معماری پروژه، پردازش داده‌‌ی لحظه‌ای و طراحی سیستم داده‌ای.لاگ چیست؟بیشتر مردم وقتی کلمه‌ی لاگ را می‌شنوند، یاد چیزی مانند شکل زیر می‌افتند. بیشتر برنامه‌نویس‌ها با چنین لاگی آشنا هستند؛ یک سری از درخواست‌ها، ارورها و دیگر پیام‌ها که در یک فایل آمده‌اند.این نوع از لاگ با لاگی که ما می‌خواهیم بررسی کنیم متفاوت است. بزرگ‌ترین تفاوت اینجاست که لاگی که در شکل آمده بیشتر برای این است که انسان بتواند آن را بخواند اما لاگی که ما می‌خواهیم بررسی کنیم، در برنامه‌نویسی هم می‌توان به آن دسترسی پیدا کرد.لاگی که ما بررسی خواهیم کرد کمی عمومی‌تر و نزدیک‌تر به چیزی است که دیتابیس‌ها یا سیستم‌ها از آن به عنوان Commit یا Journal یاد می‌کنند. آن‌ها یک فایل Append-Only از رکوردها هستند که به ترتیب زمان آمده‌اند؛ مانند شکل زیرهر مستطیل نشان‌دهنده‌ی یک رکورد است که به لاگ Append شده است. رکوردها به ترتیبی که Append شده‌اند، ذخیره شده‌اند. هر ورودی‌ای که به فایل Append شده‌است، یک لاگ نامبر منحصر به فرد و ترتیبی دارد که به عنوان Unique Key آن عمل می‌کند. محتویات و فرمت رکوردها در بحث ما اهمیتی ندارد. برای مثال، می‌توانیم فرض کنیم که هر رکورد یک JSON است.ترتیب رکوردها یک ادراک از زمان به ما می‌دهند چون ورودی‌های سمت چپ قدیمی‌تر از ورودی‌های سمت راست هستند. لاگ نامبر می‌تواند به عنوان یک Timestamp ورودی در نظر گرفته شود.لاگ تفاوت زیادی با یک فایل یا یک Table ندارد. یک فایل، آرایه‌ای از بایت‌ها است، یک Table آرایه‌ای از رکوردها است و یک لاگ نوعی از Table یا فایل است که در آن رکوردها به ترتیب زمان سورت شده‌اند.اینجا ممکن است سوال پیش بیاید که &quot;چرا باید درباره‌ی چیزی به این سادگی صحبت کنیم؟ یک دنباله‌ از رکوردها چگونه به یک سیستم داده ربط دارد؟&quot; جواب این است که لاگ یک هدف مخصوص دارد: آن‌ها اتفاق‌ها و زمان اتفاق افتادن‌ها را ذخیره می‌کنند. برای سیستم‌های توزیع یافته، این قلب مسئله است.لاگ در دیتابیس‌ها ما نمی‌دانیم که مفهوم لاگ از کجا نشات می‌گیرد؛ احتمالا جزء آن چیزهای خیلی ساده‌ای باشد که سازنده‌ی آن اصلا نفهمیده که یک اختراع است. کاربرد آن در دیتابیس‌ها، همگام‌ سازی انواع ساختمان داده‌ها و ایندکس‌ها، به هنگام خرابی است. برای پایدار و اَتومیک کردن آن، یک دیتابیس قبل از اعمال تغییرات در تمام ساختمان‌ داده‌هایی که ذخیره می‌کند، از لاگ استفاده می‌کند تا اطلاعاتی درباره‌ی رکوردهایی که می‌خواهد تغییر دهد، بنویسد. لاگ، یک رکورد از چیزی است که اتفاق افتاد و هر Table یا ایندکس، یک تصویر از این تاریخچه روی ساختمان داده‌ها یا ایندکس است. از آنجایی که لاگ در لحظه ذخیره می‌شود، به عنوان یک منبع معتبر در بازگردانی ساختمان داده‌ها به هنگام خرابی استفاده می‌شود.در طول زمان، کاربرد لاگ از یک پیاده‌سازی برای جزئیات اطلاعات یک دیتابیس ACID، به یک روش برای Replicate کردن داده بین دیتابیس‌ها رشد پیدا کرد. معلوم شد که دنباله‌ای از تغییرات که روی یک دیتابیس اتفاق می‌افتد، دقیقا همان چیزی است که برای همگام‌سازی یک دیتابیس Replica نیاز است. Oracle MySQL، PostgreSQL و MongoDB شامل پروتکل‌های جابجایی لاگ هستند تا دسته‌ای از لاگ‌ها را به دیتابیس‌های Replica ارسال کنند تا این دیتابیس‌ها به عنوان Slave عمل کنند. سپس Slaveها می‌توانند اطلاعات درون لاگ‌ها را درون ساختمان داده‌های خود اعمال کنند تا با Master همگام شوند.کاربرد لاگ در ادامه‌ی این کتاب، شامل دو نوع از کاربردها است که در دیتابیس‌ها استفاده می‌شود:لاگ به عنوان یک مکانیسم Publish/Subscribe استفاده می‌شود تا داده به Replicaها جابجا شودلاگ به عنوان یک مکانیسم پایدار(Consistency) استفاده می‌شود تا  بروزرسانی‌هایی که به چندین Replica اعمال می‌شوند، ترتیب پیدا کنندلاگ در سیستم‌های توزیع یافتهمشکلی که دیتابیس‌ها با استفاده از لاگ‌ها حل می‌کنند (مانند توزیع کردن داده در Replicaها) جزء اساسی‌ترین مشکل‌ها برای سیستم‌های توزیع یافته است.کتاب در اینجا یک مشاهده‌ای را تحت عنوان قاعده‌ی State Machine Replication آورده است:اگر دو پردازش  ِ یکسان ِ قطعی (Deterministic)  در یک وضعیت (State) یکسان آغاز شوند و ورودی‌های یکسانی را با ترتیب یکسان دریافت کنند، خروجی یکسانی را تولید خواهند کرد و در یک وضعیت یکسان پایان خواهند یافتممکن است کمی فهمیدن آن سخت باشد، کمی بیشتر توضیح می‌دهیم.قطعی (Deterministic) بودن یعنی پردازش به زمان بستگی ندارد و نمی‌گذارد ورودی دیگری بر روی نتیجه‌ی آن تاثیر بگذارد. برای Non Deterministic می‌توان از این مثال استفاده کرد: برنامه‌ای که بر اساس نتیجه‌ای که فراخوانی تابع getTimeOfDay() بازمی‌گرداند، تصمیم می‌گیرد.وضعیت(State) یک پردازش، داده‌ای است که بعد از پردازش ما، بر روی هارد یا حافظه‌ی ماشین باقی می‌ماند.قسمت ورودی یکسان در زمان یکسان هم ما را یاد لاگ می‌اندازد.حالت شهودی آن این است: اگر به یک تکه کد قطعی، لاگ ورودی یکسانی را بدهید، خروجی یکسانی را با ترتیب یکسان تولید می‌کنند.کاربرد آن در پردازش توزیع یافته مشخص است. می‌توان مسئله‌ی &quot;ساختن چند ماشین برای انجام یک کار یکسان&quot; را به مسئله‌ی &quot;پیاده‌سازی  یک لاگ پایدار برای خوراندن ورودی به این پردازش‌ها&quot; کاهش داد. در اینجا هدف لاگ این است که تمام Non Deterministic بودن را از جریان ورودی حذف کند تا مطمئن باشد هر Replica که دارد ورودی را پردازش می‌کند، همگام می‌ماند.یکی از چیزهای خوب درباره‌ی لاگ این است که اعداد گسسته‌ی لاگ‌ نامبرها به عنوان یک ساعت برای Stateهای Replicaها عمل می‌کنند؛ می‌توان State هر Replica را با یک عدد توصیف کرد: Timestamp برای آخرین ورودی لاگ که پردازش کرده است. دو Replica در یک زمان یکسان State یکسانی خواهند داشت. به همین خاطر، این Timestamp به همراه لاگ، وضعیت Replica را ذخیره خواهد کرد. این یک ادراک گسسته و متکی بر ایونت به ما می‌دهد که بر خلاف ساعت محلی ماشین‌ها [که ممکن است متفاوت باشند]، می‌توان آن‌ها را به راحتی در ماشین‌های مختلف مقایسه کرد.تنوع طراحی‌های مرتبط با لاگانواع مختلفی برای اعمال این اصل وجود دارد. این بستگی به این دارد که چه چیزی قرار است در لاگ قرار بگیرد. برای مثال، می‌توانیم درخواست‌های ورودی به یک سرویس را بنویسیم تا هر Replica آن‌ها را مستقل از هم پردازش کند. یا می‌توانیم یک درخواست پردازش داشته باشیم و تغییرات وضعیت‌ها را که در پاسخ به درخواست رخ می‌دهند، در لاگ بنویسیم.کامیونیتی‌های مختلف، پترن‌های مختلف را به شکل متفاوت توصیف می‌کنند. افراد دیتابیسی، معمولا بین Physical Logging و Logical Logging تفاوت قائل می‌شوند. Physical یا Row-Based Logging یعنی لاگ کردن محتویات هر ردیفی که تغییر کرده است. Logical یا Statement Logging یعنی ردیف‌های تغییر یافته را لاگ نکنیم، بلکه دستورهای SQLای را که منجر به آن تغییرات در ردیف‌ها شده است، لاگ کنیم.ادبیات سیستم‌های توزیع یافته، معمولا بین دو شیوه‌ برای پردازش و Replication تفاوت قائل می‌شود. State Machine Model معمولا به یک مدل Active-Active اشاره دارد، که در آن ما یک لاگ از درخواست‌های ورودی نگه می‌داریم و هر Replica هر درخواست را به ترتیب لاگ، پردازش می‌کند. یک نسخه‌ی تغییر یافته‌ی این مدل، Primary-Backup Model است که در آن یک Replica به عنوان Leader انتخاب می‌شود. این لیدر درخواست‌ها را به ترتیبی که آمده‌اند، پردازش می‌کند و تغییرات مربوط به حالت خود را که در نتیجه‌ی پردازش درخواست‌ها رخ می‌دهد، ثبت می‌کند. بقیه‌ی Replicaها تغییراتی که لیدر ایجاد می‌کند، اعمال می‌کنند تا همگام شوند. با این کار به هنگام از کار افتادن لیدر، می‌توانند جای آن را بگیرند.همانطور که در شکل زیر مشخص است، در Primary Backup Model یک Master Node انتخاب شده است تا تمام خواند‌ن‌ها و نوشتن‌ها را مدیریت کند. هر نوشتن درون لاگ ذخیره می‌شود. Slaveها در لاگ مشترک می‌شوند و تغییراتی را که Master اجرا کرده است، در وضعیت محلی خود اعمال می‌کنند. اگر Master از کار بیفتد، یک Master جدید از بین Slaveها انتخاب خواهد شد. در State Machine Replication Model تمام Nodeها یکسان هستند. نوشتن‌ها ابتدا به لاگ می‌روند و تمام Nodeها نوشتن را به ترتیبی که توسط لاگ مشخص شده است، اجرا می‌کنند.یک مثالبرای فهمیدن روش‌های متفاوت برای ساخت یک سیستم با استفاده از لاگ، یک مسئله‌ را در نظر می‌گیریم. فرض کنید می‌خواهیم یک سرویس حساب Replicated ایجاد کنیم که یک سری متغیر را نگه‌داری می‌کند و یک سری عملیات ریاضی روی آن‌ها اعمال می‌کند. سرویس ما به دستورهای زیر پاسخ خواهد داد: x? // get the current value of x
x+=5 // add 5 to x
x-=2 // subtract 2 from x
 y*=2 // double yفرض کنیم این به عنوان یک ریموت وب سرویس اجرا خواهد شد که درخواست‌ها و پاسخ‌ها با استفاده از HTTP ارسال می‌شوند.اگر فقط یک سرور داشتیم، پیاده‌سازی آن آسان خواهد بود. می‌تواند متغیرها را در حافظه یا دیسک ذخیره کند و آن‌ها را با هر ترتیبی که اتفاق می‌افتند، آپدیت کند. اما چون فقط یک سرور داریم، Fault Tolerance را از دست می‌دهیم و به هیچ وجه نمی‌توانیم مقیاس را بالا ببریم.این‌ها را می‌توانیم با اضافه کردن سرورهایی که این وضعیت را Replicate می‌کند و دستورها را پردازش می‌کنند، حل کرد. اما این یک مشکل جدید ایجاد می‌کند: سرور ممکن است از همگام‌سازی خارج شود. راه‌های مختلفی برای اتفاق افتادن این مورد وجود دارد. مثلا سرورها دستورهای آپدیت را در ترتیب‌های متفاوت دریافت می‌کنند یا یک سرور از کار افتاده و آپدیت‌ها را از دست می‌دهد.اگرچه در عمل، بیشتر افراد کوئری‌ها و آپدیت‌ها را به یک دیتابیس ریموت ارسال می‌کنند. با استفاده از این روش مشکل از برنامه‌ی ما خارج می‌شود اما واقعا حل نمی‌شود؛ بالاخره ما حالا می‌خواهیم مشکل Fault Tolerance را در دیتابیس حل کنیم. پس برای اینکه به مثال پایبند باشیم، بیایید کاربرد لاگ را در برنامه‌مان بررسی کنیم.راه‌های مختلفی برای حل این مشکل با استفاده از لاگ وجود دارد. State Machine Replication در ابتدا عملیاتی را که قرار است اجرا شود، در لاگ می‌نویسد و سپس هر Replica عملیات موجود در لاگ را به ترتیب اجرا می‌کند. در این حالت، لاگ حاوی دنباله‌ای از دستورها مانند 5=+x یا 2=*y است.روش Primary Backup هم امکان پذیر است. در این طراحی، یکی از Replicaها را به عنوان Leader انتخاب می‌کنیم. این لیدر هر دستوری را که دریافت کند اجرا می‌کند و مقادیر متغیرها را که خروجی اجرای هر دستور است، در لاگ می‌نویسد. در این طراحی، لاگ فقط حاوی متغیرهای نهایی مانند 1=x یا 6=y است، نه دستورهایی که منجر به این نتایج می‌شود. Replicaها مانند بکاپ عمل خواهند کرد؛ آن‌ها در این لاگ مشترک می‌شوند و این متغیرهای جدید را به ساختمان‌ داده‌های محلی خود اضافه می‌کنند. اگر لیدر از کار بیفتد، یک لیدر جدید از بین این Replicaها انتخاب خواهد شد.این مثال همچنین نشان می‌دهد که چرا ترتیب، یک عنصر مهم برای تضمین پایداری میان Replicaها است، جابجایی ترتیب عمل ضرب و جمع، منجر به یک نتیجه‌ی متفاوت می‌شود. همچنین جابجایی ترتیب اعمال آپدیت‌ها برای یک متغیر هم منجر به نتیجه‌ی متفاوت می‌شود.تیبل‌ها و ایونت‌ها دوگانه هستندبرگردیم به دیتابیس‌ها. همزادی خوبی بین لاگ تغییرات و Table وجود دارد. لاگ شبیه به یک لیست از تمام وام‌ها و بدهی‌هایی است که یک بانک دسترسی دارد. Table تمام موجودی اکانت‌های فعلی است. اگر یک لاگ از تغییرات داشته باشید، می‌توانید این تغییرات را اعمال کنید تا Table را بسازید و وضعیت نهایی را به دست بیاورید. این Table آخرین وضعیت برای هر Key را ذخیره خواهد کرد. لاگ نه تنها می‌تواند Table اصلی را بسازد، بلکه می‌توان آن را طوری تغییر داد تا Tableهای نشأت گرفته از Table اصلی را هم بسازد. (در دیتابیس‌های Non-Reltaional، تیبل همان Keyed Data Store است)این فرآیند را معکوس هم می‌توان انجام داد: اگر یک Table داریم که آپدیت‌هایی را دریافت می‌کند، می‌توان این تغییرات را ذخیره کرد و یک Changelog از تمام آپدیت‌ها منتشر کرد. این Changelog دقیقا همان چیزی است که برای پشتیبانی از Near-Real-Time Replicaها نیاز داریم. در این صورت، می‌توان ایونت‌ها و تیبل‌ها را به عنوان یک دوگانه دید: تیبل‌ها از داده‌ی ایستا پشتیبانی می‌کنند و لاگ‌ها تغییرات را ذخیره می‌کنند. جادوی لاگ اینجاست که اگر یک Complete Log از تغییرات باشد، نه تنها آخرین نسخه‌ی محتویات تیبل را ذخیره می‌کند بلکه می‌تواند تمام ورژن‌های قبلی را هم دوباره بسازد. در واقع به نوعی یک بکاپ از تمام وضعیت‌های قبلی تیبل است.این ممکن است شما را یاد کنترل ورژن سورس کد بیندازد. یک رابطه‌ی نزدیک بین کنترل سورس کد و دیتابیس وجود دارد. ورژن کنترل یک مشکلی را حل می‌کند که شباهت زیادی به مشکلی دارد که سیستم‌های داده‌ی توزیع یافته باید حل کند: مدیریت تغییرهای توزیع‌یافته‌ی همزمان در وضعیت.</description>
                <category>مجتبی میر یعقوب زاده</category>
                <author>مجتبی میر یعقوب زاده</author>
                <pubDate>Tue, 06 Jul 2021 10:51:09 +0430</pubDate>
            </item>
                    <item>
                <title>بهینه‌سازی Apache Spark</title>
                <link>https://virgool.io/@TabaMojj/%D8%A8%D9%87%DB%8C%D9%86%D9%87-%D8%B3%D8%A7%D8%B2%DB%8C-%D8%A7%D8%B3%D9%BE%D8%A7%D8%B1%DA%A9-n4odzsworwfg</link>
                <description>بهینه‌سازی‌هایی که در این مطلب آمده، حاصل مشاهده‌های عملی افراد کامیونیتی اسپارک و تمرکز آن‌ها بر ماکسیسمم‌سازی اختصاص منابع در کلاسر برای افزایش کارایی است.افزایش مقیاس اسپارک برای حجم کار زیادحجم کار زیاد در اسپارک، معمولا دسته‌ای از جاب‌ها است- بعضی از آن‌ها طبق عادت شبانه اجرا می‌شوند در حالیکه بعضی دیگر برنامه‌ریزی شده‌اند که در ساعات مشخصی از روز کار کنند. در بعضی مواقع، این جاب‌ها ممکن است ده‌ها ترابایت از داده‌ را پردازش کنند. برای جلوگیری از بروز مشکل بخاطر کمبود منابع یا کاهش عملکرد در انجام جاب، تعدادی کانفیگوریشن وجود دارد که می‌توانید فعال یا تغییر دهید. این کانفیگوریشن‌ها ۳ تا از اجزای اسپارک را تغییر می‌دهد: Spark Driver، Spark Executor و Shuffle Service که روی Executor در حال اجرا است.وظیفه‌ی Spark Driver، هماهنگ شدن با Cluster Manager برای اجرای Executorها در یک کلاستر و برنامه‌ریزی تسک‌های اسپارک بر روی آن‌ها است. در حجم کار زیاد، ممکن است هزاران تسک داشته باشیم. بعضی از تنظیماتی که در اینجا معرفی می‌شوند، حاصل تجربه‌های شرکت‌های بزرگی مانند Facebook است که این تجربه‌ها را در سمینار Spark + AI Summit به اشتراک گذاشته‌اند.تخصیص منابع ایستا (Static) در برابر پویا (Dynamic)وقتی منابع محاسباتی را از طریق روشی مانند spark-submit در command-line مشخص می‌کنیم، حداکثر را مشخص می‌کنیم. این یعنی اگر بعد ها حین انجام عملیات، اسپارک به منابع بیشتر از حد مشخص شده احتیاج داشت، نمی‌تواند منابع بیشتری اختصاص دهد.در برابر اگر از کانفیگوریشن تخصیص منابع پویا در اسپارک استفاده کنید، Spark Driver می‌تواند با افزایش حجم کار، درخواست افزایش منبع بدهد.یکی از کاربردها در Streaming است، یعنی حالتی که حجم جریان داده غیر یکنواخت باشد. یکی دیگر از کاربردها تحلیل داده‌ی on-demand است، یعنی حالتی که شما حجم بالایی از کوئری‌های SQL در ساعات اوج دارید. با فعال‌سازی تخصیص حافظه‌ی پویا، به اسپارک اجازه می‌دهید بهتر منابع را مدیریت کند، Executorهایی را که استفاده نمی‌شوند آزاد کند و در مواقع نیاز از منابع جدید استفاده کند.مقدار حافظه‌ی Spark Executor و Shuffle Serviceفقط فعال کردن تخصیص منابع پویا کافی نیست. همچنین باید بدانید که تخصیص حافظه‌ی Executor چگونه است و اسپارک چگونه از آن‌ها استفاده می‌کند تا Executorها بدون حافظه نمانند یا JVM Garbage Collection به مشکل بر نخورد.مقدار حافظه‌ای که هر Executor دارد توسط spark.executor.memory کنترل می‌شود. همانطور که در شکل زیر نمایش داده شده، به ۳ بخش تقسیم می‌شود: Execution Memory, Storage Memory, Reserved Memory. تقسیم دیفالت به صورت ۳۰۰ مگابایت برای Reserved Memory که با ارورهای OOM روبرو نشویم و سپس  ۶۰ درصد برای Execution Memory و ۴۰ درصد برای Storage Memory است. داکیومنت اسپارک توصیه می‌کند که این حالت در بیشتر مواقع پاسخگو خواهد بود. هر وقت از Storage Memory استفاده نشود، اسپارک می‌تواند از آن برای Execution Memory استفاده کند و همینطور برعکس.کاربرد Execution Memory در Spark Shuffle, join, sort, aggregation است. از آنجایی که کوئری‌های مختلف ممکن است به مقدار حافظه‌ی متفاوت احتیاج داشته باشند، اختصاص کسر مقدار حافظه ی موجود احتیاج به کمی مهارت دارد. (spark.memory.fraction مقدار دیفالت ۰.۶ است) Storage Memory در کش‌کردن ساختمان داده‌های کاربر و پارتیشن‌هایی که از DataFrame استخراج شده، کاربرد دارد.در طی عملیات map و shuffle، اسپارک از فایل‌های شافل دیسک محلی می‌خواند و به آن می‌نویسد، پس فعالیت I/O فراوانی وجود دارد. این منجر به باتل‌نک می‌شود چون کانفیگوریشن دیفالت، مناسب جاب‌های اسپارک در مقیاس بالا است. دانستن اینکه کدام کانفیگوریشن‌ها را باید تغییر بدهیم می‌تواند ریسک را در این مرحله کاهش دهد.در شکر زیر تعدادی کانفیگوریشن توصیه شده را می‌بینید که با تنظیم آن‌ها، map, spill و merge با کمبود I/O روبرو نمی‌شوند و می‌توانند قبل از نوشتن پارتیشن‌های شافل به دیسک، از بافر مموری استفاده کنند. تنظیم Shuffle Serviceای که در هر Executor در حال اجرا می‌باشد هم می‌تواند عملکرد کلی را افزایش دهد.موارد پیشنهادی در این عکس در همه‌ی مواقع صادق نیستند اما می‌توانند ایده‌ی خوبی بدهند که چگونه طبق حجم کار،‌آن‌ها را تغییر دهید.افزایش موازی‌کاری اسپارکبیشتر کارایی اسپارک در توانایی آن در انجام چند تسک به‌صورت موازی است. برای اینکه بفهمید چطور می‌توانید موازی‌کاری را به بیشترین مقدار ممکن برسانید، باید ببینید که اسپارک چگونه داده را از فضای ذخیره‌سازی به حافظه می‌خواند و پارتیشن‌ها چه معنایی برای اسپارک دارند.در اصطلاحات مدیریت داده، پارتیشن یک روش برای مرتب‌کردن داده به زیرمجموعه‌ای از تکه‌ها یا بلاک‌های قابل خواندن و قابل کانفیگ‌کردن داده‌‌های مجاور روی دیسک است. این زیرمجموعه‌های داده می‌توانند به‌صورت مستقل یا موازی، در حداقل یک thread، خوانده و یا پردازش شوند. این استقلال مهم است چون اجازه‌ی موازی‌سازی پردازش داده در مقیاس بالا را می‌دهد.اسپارک به طرز وحشتناکی در پردازش تسک‌ها به ‌صورت موازی بهینه است. همانطور که در مطلب ****لینک به مطلب **** خواندیم، در حجم کار بالا، یک جاب Stageهای زیادی خواهد داشت و در هر Stage تعداد زیادی تسک خواهد بود. اسپارک به بهترین حالت یک thread به ازای هر تسک به ازای هر هسته برنامه‌ریزی می‌کند و هر تسک یک پارتیشن مجزا را پردازش خواهد کرد. برای بهینه‌سازی تخصیص منابع و حداکثر کردن موازی‌کاری، ایده‌ال این است که تعداد پارتیشن‌ها حداقل به اندازه‌ی هسته‌های Executorها باشد. اگر تعداد پارتیشن‌ها از تعداد هسته‌های Executorها بیشتر باشد، تمام هسته‌ها مشغول خواهند شد. می‌توانید پارتیشن را واحدهای اتمی در موازی‌کاری در نظر بگیرید: یک thread در یک هسته می‌توانید روی یک پارتیشن کار کند.پارتیشن‌ها چگونه ساخته می‌شوندهمانطور که گفتیم، تسک‌های اسپارک داده را به صورت پارتیشن‌هایی از دیسک به حافظه می‌خوانند.بسته به نوع ذخیره‌سازی،  داده‌ی روی دیسک به صورت chunkها یا بلاک‌های فایل پشت سر هم ذخیره شده است. به‌صورت دیفالت، بلاک‌های فایل در دیتا استورها سایزی بین ۶۴ مگابایت تا ۱۲۸ مگابایت دارند. برای مثال در HDFS و S3 سایز دیفالت ۱۲۸ مگابایت است. کالکشنی مجاور از این بلاک‌ها تشکیل پارتیشن می‌دهند.در اسپارک، سایز پارتیشن‌ها توسط spark.sql.files.maxPartitionBytes کنترل می‌شود که دیفالت آن ۱۲۸ مگابایت است. می‌توانید آن را کاهش دهید اما ممکن است با مشکلی به نام Small File Problem روبرو شوید؛ تعداد خیلی زیادی از پارتیشن‌های کوچک فایل‌، که منجر به تعداد زیادی I/O می‌شود و به لطف عملیاتی مانند باز و بستن، کارایی کاهش میابد.پارتیشن‌ها همچنین وقتی از متدهای خاصی از DataFrame API استفاده می‌کنید، ساخته می‌شوند. برای مثال، به هنگام ساختن یک DataFrame بزرگ یا خواندن یک فایل بزرگ از دیسک. می‌توانید به اسپارک بگویید که یک مقدار معین از پارتیشن‌ها را تولید کند. برای این کار از متد ()repartition استفاده می‌کنیم.در نهایت، پارتیشن‌های شافل به هنگام مرحله‌ی شافل ساخته می‌شوند. به طور دیفالت تعداد پارتیشن‌های شافل ۲۰۰ است که می‌توانید بر اساس اندازه‌ی داده آن را تغییر دهید. با تغییر آن،می‌توانید مقدار پارتیشن‌های کوچکی که در در شبکه به Executorها ارسال می‌شود، تغییر دهید.مقدار دیفالت spark.sql.shuffle.partitions برای کارها یا استریم‌های کوچک، بسیار زیاد است. بهتر است آن را به مقادیر کمی مانند تعداد هسته‌های Executorها و یا کمتر کاهش دهید.شافل پارتیشن‌ها که به هنگام عملیات Wide Transformation مانند ()groupBy یا ()join ساخته می‌شوند، هم شبکه و هم منابع I/O را مصرف می‌کنند. در این عملیات، نتایج در دیسک‌های محلی Executorها در مسیری که توسط spark.local.directory مشخص شده است، ذخیره می‌شود. داشتن SSD منجر به بهتر شدن عملکرد این عملیات خواهد شد.هیچ فرمول طلایی‌ برای تعیین تعداد شافل پارتیشن‌ها وجود ندارد؛ تعداد بسته به کار شما، دیتاست، تعداد هسته و مقدار حافظه‌ی Executorها متغیر است. برای افزایش بیشتر کارایی اسپارک در حجم کاری بالا، بهتر است دیتافریم‌هایی را که مدام دسترسی پیدا می‌کند، ذخیره یا کش کنید.کش کردن و ذخیره کردن دادهتفاوت بین کش‌کردن و ذخیره‌کردن چیست؟DataFrame.cache()تابع () cache تا جایی که حافظه اجازه دهد، هر مقدار پارتیشن خوانده شده در حافظه‌ی Executorها را ذخیره می‌کند. ممکن است یک کسری از DataFrame را بتوانید کش کنید اما پارتیشن‌ها را نمی‌توانید؛ یعنی اگر ۸ پارتیشن داشته باشید و فقط ۴.۵ تا پارتیشن در حافظه قرار بگیرد، فقط ۴ تا کش خواهد شد. اگر همه‌ی پارتیشن‌ها کش نشوند و بخواهید به داده دوباره دسترسی پیدا کنید، پارتیشن‌هایی که کش نشده‌اند دوباره باید محاسبه شوند که جاب را کند خواهند کرد.وقتی از ()cache یا ()persist استفاده می‌کنید، تمام DataFrame کش نمی‌شود مگر تا زمانی که یک Action را فعال کنید که بر تمام نمونه‌ها تاثیر می‌گذارد( مثال ()count) اگر از یک Action مانند (1)take استفاده کنید، فقط یک پارتیشن کش می‌شود چون Catalyst Optimizer متوجه می‌شود که شما برای دسترسی به یک نمونه، نیازی به محاسبه‌ی تمام پارتیشن‌ها ندارید.در شکل زیر می‌بینیم که چگونه یک DataFrame در یک Executor ذخیره شده است. می‌بینیم که همه در حافظه جای گرفته‌اند.DataFrame.persist()تابع (StorageLevel.LEVEL)persist اختلاف جزئی‌ای با تابع قبلی دارد. این تابع امکان کنترل چگونگی کش شدن داده از طریق StorageLevel را می‌دهد. شکل زیر تفاوت سطوح‌ مختلف ذخیره‌سازی را نشان می‌دهد. داده‌ی روی دیسک همیشه می‌تواند با استفاده از Java Serilization یا Kyro Serialization سریال‌‌سازی‌شده شود.هر StorageLevel(به جز OFF_HEAP) یک معادل LEVEL_NAME_2 دارد که یعنی در دو Spark Executor متفاوت ذخیره شود:‌MEMORY_ONLY_2, MEMORY_AND_DIST_SER_2 و ...اگرچه این کار هزینه‌بر است اما اجازه می‌دهد داده در ۲ جا ذخیره شود که باعث Fault Tolerance  می‌شود.چه موقع از Cache و Persist استفاده کنیماستفاده‌های رایج از کش‌کردن در مواقعی است که می‌خواهید مدام برای ترنسفورم‌ها و کوئری‌ها، به دیتاستی بزرگ دست پیدا کنید؛ ماننداستفاده مداوم از DataFrameها به هنگام ترین یک مدل ماشین لرنینگدسترسی مداوم به DataFrameها به‌هنگام ترنسفورم‌های مداوم در ETL یا ساخت دیتا پایپ‌لاینچه موقع از Cache و Persist استفاده نکنیماستفاده از کش در همه‌ی کاربری‌ها الزامی نیست. ماننددیتافریم‌هایی که برای گنجاندن در حافظه بسیار بزرگ هستندیک ترنسفورم کم‌هزینه روی دیتافریم که قرار نیست مدام استفاده شودبه‌عنوان یک قانون رایج بهتر است از Memory Caching عاقلانه استفاده کنید چون می‌تواند منجر به هزینه در serialize و deserialize شود. (بسته به StorageLevel ای که استفاده می‌کنید)خانواده‌ی Join در اسپارکعملیات Join از ترنسفورم‌های رایج در تحلیل داده است که در آن ۲ دیتاست، به شکل تیبل یا DataFrame، توسط یک matching key ادغام می‌شوند. مانند Relational Databaseها، اسپارک در DataFrame و DatasetAPI ا،  Joinهای inner  join,  outer  join,  left join, right join و ... را ارائه می‌دهد. تمام این عملیات باعث حرکت مقدار زیادی داده در Executorها می‌شوند.در قلب این ترنسفورم‌ها، نحوه‌ی محاسبه‌ی اینکه چه داده‌ای تولید شود، چه keyها و کدام‌ داده‌های مربوط به آن به دیسک نوشته شود و چگونگی انتقال این keyها و داده‌ها به نودها به‌عنوان قسمتی از عملیاتی مانند ()groupBy، ()join, ()، agg و ... ، قرار دارد. به این حرکت معمولا Shuffle می‌گویند.اسپارک پنج Join دارد که داده را در Executorها جابجا، مرتب، گروه و ادغام می‌کند: ‌Broadcast Hash Join (BHJ)Shuffle Hash Join (SHJ)Shuffle  Sort  Merge  Join  (SMJ)Broadcast  Nested  Loop  Join  (BNLJ)Shuffle-and-Replicated Nested Loop Join (a.k.a. Cartesian product join)در اینجا تمرکز خود را روی BHJ و SMJ می‌گذاریم چون پرکاربرد ترین هستند.Broadcast Hash Joinنام دیگر آن Map-Side-Only Join است. Broadcast Hash Join موقعی به کار می‌رود که ۲ دیتاست، یکی کوچک (که در حافظه‌ی Driver و Executor جا می‌گیرد) و دیگری آنقدر بزرگ که از جابجایی آن صرف نظر کنیم، باید روی شرط‌ها یا ستون‌های معین Join شوند. با استفاده از یک Spark Broadcast Variable، دیتاست کوچکتر توسط Driver به تمام Executorها broadcast می‌شود و بعد روی هر Executor با دیتاست بزرگ‌تر Join می‌شود.به‌صورت دیفالت، اگر دیتاست کوچک‌تر از ۱۰ مگابایت باشد، اسپارک از Broadcast Join استفاده خواهد کرد. می‌توانید این مقدار را با spark.sql.autoBroadcastJoinThreshold تغییر دهید.یک کاربرد رایج آن وقتی است که بین ۲ دیتافریم، که یکی حجم کمتری نسبت به دیگری دارد، یک سری Keyهای مشترک وجود دارد و شما اختیاج دارید که یک صورت ادغام شده از این ۲ را ببینید.راحت‌ترین و سریع‌ترین Join در اسپارک BHJ است چون هیچ جابجایی داده (Shuffle) وجود ندارد؛ بعد از Broadcast، تمام داده به‌صورت محلی برای Executor قابل دسترس است. فقط باید مطمئن شوید که Driver و Executorها حافظه‌ی کافی دارند تا بتوانند دیتاست کوچکتر را در حافظه نگه دارند.چه موقع از ‌Broadcast Hash Join استفاده کنیموقتی اسپارک هر Key در دیتاست کوچک و بزرگ را به یک پارتیشن یکسان Hash کرده استوقتی یک دیتاست بسیار کوچکتر از دیتاست دیگر استوقتی می‌خواهید فقط یک Equi-Join انجام دهید تا دو دیتاست را بر اساس unsorted keyهای یکسان ادغام کنیدوقتی نگرانی‌ای بابت استفاده‌ی بیش از حد از پهنای باند شبکه یا ارورهای OOM ندارید چون دیتاست کوچکتر به تمام Executorها Broadcast خواهد شد.اگر مقدار spark.sql.autoBroadcastJoinThreshold را ۱- قرار دهید، اسپارک همیشه از Shuffle Sort Merge Join استفاده خواهد کرد.Shuffle Sort Merge Joinالگوریتم Sort-Merge یک راه بهینه است برای ادغام ۲ دیتاست بزرگ روی Key مشترک که یکتا و قابل Sort است و می‌توان آن را در پارتیشن یکسان ذخیره کرد؛ به این معنا که دو دیتاست با یک key قابل هش و مشترک را می‌توان در یک پارتیشن یکسان ذخیره کرد. در نگاه اسپارک این یعنی تمام ردیف‌های درون هر دیتاست با key یکسان بر روی پارتیشن یکسان در Executor یکسان هش می‌شوند. مشخص است که این یعنی داده باید مکان یکسانی داشته باشد یا بین Executorها جابجا شود.همانطور که از نامش پیداست، این Join دو فاز دارد: یک فاز sort و یک فاز merge. فاز sort هر دیتاست را با join key دلخواه مرتب می‌کند؛ فاز merge در ردیف هر دیتاست، روی هر key پیمایش می‌کند و اگر دو key با هم مچ شدند، آن‌ها را merge می‌کند.چه موقع از Shuffle Sort Merge Join استفاده کنیموقتی هر key در هر دو دیتاست بزرگ می‌تواند در یک پارتیشن یکسان sort و hash شودوقتی می‌خواهید فقط equi-join انجام دهید تا دو دیتاست بر حسب keyهای مرتب‌شده‌ی مچ ادغام شوندوقتی می‌خواهید از Exchange(یا همان Shuffle) و Sort جلوگیری کنید تا Shuffleهای بزرگ در شبکه رخ ندهندآشنایی با Spark UIاسپارک یک محیط کاربری تحت وب ارائه می‌دهد که امکان بررسی اجزای مختلف برنامه‌ی ما را فراهم می‌کند. امکاناتی که فراهم می‌کند شامل حافظه‌ی مصرفی، جاب‌ها، استیج‌ها، تسک‌ها، تایم‌لاین‌ها و آمار و معیارهایی است که برای درک کردن برنامه‌ی اسپارک ما مفید است. spark-submit محیط کاربری را آغاز می‌کند و می‌توانید در حالت لوکال در پورت دیفالت ۴۰۴۰ به آن دسترسی پیدا کنید.تب‌های مختلف Spark UIاین محیط کاربری شامل ۶ تب است. در نسخه‌ی  ۳ یک تب دیگر با نام Structured Streaming اضافه شده است.Jobs and Stagesهمانطور که می‌دانید، اسپارک یک اپلیکشن‌ را به جاب‌ها، استیج‌ها و تسک‌های مختلف تبدیل می‌کند. تب‌های جاب و استیج به شما اجازه می‌دهد ریز به ریز جزئیات‌ را بررسی کنید. می‌توانید میزان پیشرفت، معیارهای مربوط به I/O، حافظه‌ی مصرفی، مدت زمان اجرا و ... را مشاهده کنید.شکل زیر تب جاب را با Event Timeline گسترش یافته نشان می‌دهد. می‌توان مشاهده کرد که کی Executorها اضافه یا حذف شده‌اند. همچنین یک لیست از جاب‌های تمام شده نشان می‌دهد. ستون Duration مدت زمان هر جاب‌ را نشان می‌دهد. اگر این زمان زیاد باشد، بهتر است استیج‌های آن جاب را ببینید و بفهمید که کدام تسک باعث طولانی شدن شده است. از این صفحه می‌توانید به صفحه‌ی پر جزيیات هر جاب که شامل DAG نیز هم هست، دسترسی پیدا کنید.تب استیج یک خلاصه از وضعیت فعلی تمام استیج‌های تمام جاب‌ها می‌دهد. همچنین میتوانید به یک صفحه‌ی پرجزئیات برای هر استیج نیز دست پیدا کنید. می‌توانید میانگین زمان اجرا برای هر تسک، زمان سپری شده در Garbage Collection و تعداد بایت‌ها/نمونه‌های خوانده شده در شافل را ببینید. اگر داده‌ی شافل از Executorهای ریموت خوانده می‌شود، Shuffle Read Blocked Time بالا می‌تواند نشان‌دهنده‌ی مشکلات I/O باشد. زمان GC بالا می‌تواند نشان‌دهنده‌ی وجود آبجکت‌های زیادی در Heap باشد(ممکن است Executorهای شما به حافظه‌ی زیادی احتیاج داشته باشند) اگر بیشترین زمان یک تسک بسیار بزرگتر از متوسط باشد، احتمالا بخاطر توزیع نامساوی داده در پارتیشن‌ها، با Data Skew روبرو شده‌اید.Executorsاین تب اطلاعاتی درباره‌ی Executorهای برنامه در اختیار می‌گذارد. می‌توانید جزئیات شکل زیر را ببینید.علاوه بر آمارها، می‌توانید ببینید که هر Executor چگونه از حافظه و برای چه کاری استفاده می‌کند. همچنین این کمک می‌کند تا میزان مصرف منابع را به هنگام استفاده از توابع cache یا persist بررسی کنیم.Storageاین تب اطلاعات هر تیبل یا DataFrame که برنامه آن را کش کرده، نشان می‌دهد.SQLاثر کوئری‌های Spark SQL که در برنامه‌ی شما اجرا می‌شوند، قابل دنبال‌کردن و دیدن هستند. می‌توانید ببینید که هر جاب چه موقع کدام کوئری را اجرا می‌کند و مدت زمان آن چقدر بوده است.با کلیک بر روی هر کوئری، جزيات و برنامه‌ی اجرای آن نمایش داده می‌شود. این جزئیات در مواقعی که می‌خواهیم جزئیات هر اپراتور و تغییرات آن‌ها را ببینیم، مفید است.Environmentشناختن محیطی که برنامه‌ی اسپارک در آن در حال اجرا است، نکات مفید زیادی را ارائه می‌کند که در عیب‌یابی مفید هستند. در واقع، ضروری است که از متغیرهای محیط مطلع باشیم، بدانیم از چه jarهایی استفاده شده است، مقدار متغیرهای اسپارک و سیستم چیست و ... تمام این اطللاعات read-only یک معدن طلا از اطلاعات هستند که به هنگام برخورد با یک رفتار غیرطبیعی در برنامه، باید آن‌ها را بررسی کنیم.</description>
                <category>مجتبی میر یعقوب زاده</category>
                <author>مجتبی میر یعقوب زاده</author>
                <pubDate>Tue, 06 Jul 2021 01:30:51 +0430</pubDate>
            </item>
                    <item>
                <title>ای‌پی‌آی‌های ساختار یافته در Apache Spark</title>
                <link>https://virgool.io/@TabaMojj/%D8%A7%DB%8C-%D9%BE%DB%8C-%D8%A2%DB%8C-%D9%87%D8%A7%DB%8C-%D8%B3%D8%A7%D8%AE%D8%AA%D8%A7%D8%B1-%DB%8C%D8%A7%D9%81%D8%AA%D9%87-%D8%AF%D8%B1-apache-spark-yxolpy1kpbyw</link>
                <description>پایه‌ترین مفهوم در اسپارک، (RDD)‌Resilient Distributed Dataset است. RDD سه مولفه‌ی اصلی دارد:وابستگی‌ها‌ (Dependencies)پارتیشن‌ها تابع محاسبه: Partition  =&gt; Iterator[T]هر ۳ از اجزای اصلی RDD Programming API در اسپارک هستند. اول، یک لیست از Dependencyها که به اسپارک می‌گویند چگونه یک RDD با ورودی‌هایش باید ساخته شود، نیاز است. در مواقعی که نیاز است نتایج دوباره ساخته شوند، اسپارک می‌تواند با استفاده از این Dependencyها، RDD را بسازد و عملیات را دوباره انجام دهد. این مشخصه به RDDها انعطاف‌پذیری می‌بخشد.پارتیشن‌ها این امکان را به اسپارک می‌دهند که محاسبات را به‌صورت موازی در Executorها انجام دهند. در بعضی مواقع، مثلا به هنگام خواندن از HDFS، اسپارک از اطلاعات محلی برای ارسال عملیات به Executorهایی که به داده نزدیک‌تر هستند، استفاده می‌کند. در این صورت داده‌ی کمتری در شبکه جابجا شده است.و در نهایت،‌ RDD یک تابع محاسبه دارد که یک Iterator[T] برای داده‌ای که در RDD ذخیره خواهد شد، تولید می‌کند.اما این مدل یک سری مشکل دارد. تابع محاسبه برای اسپارک مبهم است؛ اسپارک نمی‌داند که شما چه کاری در این تابع انجام می‌دهید. فارغ از اینکه شما join, filter,select, aggregation انجام می‌دهید، اسپارک آن را به شکل یک Lambda Expression می‌بینید. مشکل دیگر این است که نوع دا‌ده‌ی Iterator[T] برای Python RDD مبهم است؛ اسپارک فقط می‌داند که این یک Generic Object در پایتون است.به‌همین دلایل، چون اسپارک نمی‌تواند گزاره یا محاسبه‌ی درون تابع را ببیند، هیچ راهی هم برای بهینه‌سازی آن ندارد. و در نهایت، اسپارک هیچ اطلاعی درباره‌ی نوع داده‌ی T ندارد، برای اسپارک این یک آبجکت مبهم است و تنها کاری که می‌تواند بکند این است که آبجکت مبهم را به‌عنوان سری‌هایی از بایت‌ها سریال‌سازی کند، بدون استفاده از تکنیک‌های فشرده‌سازی داده.این ابهام‌ها توانایی اسپارک در بهینه کردن و مرتب‌سازی عملیات و تبدیل آن به یک Query Plan بهینه را می‌گیرد. پس راه حل چیست؟ساختاربندی اسپارکاسپارک ۲ برای ساختاربندی اسپارک، چند راه حل ارائه داد. یکی این بود که محاسبات، با استفاده از پترن‌های رایج در تحلیل داده بیان شوند. این پترن‌ها به‌عنوان عملیات سطح بالا بیان می‌شوند مانند: filter, select, count, aggregate, average, group. این‌ها باعث شدند به سادگی و شفافیت اسپارک اضافه شود.از طریق مجموعه‌ای از دستورها در domain-specific languageها که به‌عنوان APIها در زبان‌های پشتیبانی‌شده‌ی اسپارک آمده‌اند، این دستورها به اسپارک می‌گویند که چه کاری می‌خواهید با داده‌ی خود انجام دهید و در نتیجه، اسپارک یک Query Plan بهینه برای اجرا می‌سازد.راه حل بعدی این بود که اجازه می‌داد داده به فرمت Tabular ، مانند SQL، مرتب شوند.اما همه‌ی این ساختاربندی‌ها چه فایده‌ای دارند؟محسن کلیدی اسپارکساختاربندی، یک سری خوبی‌ها دارد مانند بهتر شدن عملکرد و مدیریت بهتر فضا در اجزای اسپارک. در اینجا می‌خواهیم درباره‌ی ویژگی‌های رسا بودن (Expressivity)، سادگی (Simplicity)، ترکیب‌پذیری (Composability) و یکنواختی (Uniformly) صحبت کنیم.در مثال زیر می‌خواهیم رسا بودن و سادگی را نشان دهیم. در اینجا می‌خواهیم تمام سن‌ها برای هر نام را Aggregate کنیم، بر اساس نام group کنیم و سپس میانگین را حساب کنیم. این یک پترن رایج در تحلیل داده به‌حساب می‌آید. اگر از Low-Level RDD API برای این کار استفاده کنیم،‌ کد آن به شکل زیر است: این کد رمزآلود و برای خواندن سخت است. این کد به اسپارک می‌گوید که چطور aggregate را انجام دهد و میانگین را محاسبه کند، یعنی چطور کوئری را محاسبه کند. حال می‌خواهیم همین کد را با استفاده از High-Level DSLها و DataFrame API بیان کنیم. در اینجا به اسپارک می‌گوییم که چه کار کند:این نسخه از کد بسیار رساتر و ساده‌تر از نسخه‌ی قبلی است، چون از اپراتورهای High-Level DSL و APIها استفاده می‌کنیم تا به اسپارک بگوییم که چه کار کند. در نتیجه از این اپراتورها استفاده کرده‌ایم تا کوئری ما را بسازند. و چون اسپارک می‌تواند به این کوئری نگاه کند و هدف ما را بفهمد، می‌تواند آن را بهینه و یا ترتیب عملیات را تغییر دهد تا بهینه اجرا شود. اسپارک دقیقا می‌داند که ما می‌خواهیم چکار کنیم: اشخاص را با نام‌هایشان group کن، سن آن‌ها را aggregate کن و سپس میانگین را محاسبه کن. ما با استفاده از اپراتورهای سطح بالا تمام یک عملیات را به‌عنوان یک کوئری بیان کردیم- این کار چقدر هزینه‌بر است؟بعضی افراد معتقدند که فقط استفاده کردن از اپراتورهای زبان‌های سطح بالا، توانایی توسعه‌دهندگان را در کنترل کوئری خود یا راهنمایی کامپایلر در چگونگی محاسبه کوئری، محدود می‌کند. بعضی دیگر می‌گویند که ما محدود به این پترن‌های ساختار یافته نیستیم؛ می‌توانیم هر وقت خواستیم از RDD API سطح پایین استفاده کنیم، اگرچه که معمولا احتیاجی به این کار پیدا نمی‌کنیم.علاوه بر سادگی در خواندن، ساختار API سطح بالای اسپارک میان اجزا و زبان‌ها یکنواخت است. برای مثال این کد Scala همان کاری را انجام می‌دهد که در بالا انجام دادیم:اگر با SQL آشنا باشید، بعضی از این اپراتورهای DSL می‌توانند عملیات Relational را انجام دهند. مانند select, filter, group, aggregateتمام این سادگی و رسایی بخاطر وجود موتور Spark SQL است که Structered APIهای سطح بالا بر روی آن ساخته شده‌اند. بخاطر این موتور، که زیربنای تمام اجزای اسپارک است، ما به APIهای یکنواخت دسترسی داریم.DataFrame APIدر اسپارک، DataFrame در ساختار، فرمت و تعدادی از عملیات، از  Pandas DataFrames الهام گرفته‌ است. Spark DataFrames را می‌توان تیبل‌های درون‌حافظه‌ای توزیع‌شده‌ای در نظر گرفت که ستون‌های آن نام‌گذاری شده‌اند؛ طوری که هر ستون یک نوع داده‌ی مخصوص دارد: integer, string, array, map, real, date, timestamp,.... وقتی داده‌ها به شکل یک تیبل ساختار یافته نمایش داده‌ می‌شود، نه تنها وارد کردن آن راحت‌تر می‌شود بلکه کار کردن با ردیف‌ها و ستون‌ها به‌هنگام اعمال عملیات هم راحت‌تر می‌شود. همچنین، اسپارک دیتافریم Immutable است و اسپارک یک Lineage از تمام ترنسفورم‌ها ذخیره می‌کند. می‌توانید به راحتی هر تغییری روی دیتافریم ایجاد کنید، بدون اینکه نسخه‌ی اصلی تغییری کند.در اینجا می‌خواهیم نوع داده‌های موجود در اسپارک را بررسی کنیم.انواع داده‌ی رایج در اسپارکبرای هماهنگی با زبان‌های برنامه‌نویسی‌ای که پشتیبانی می‌کند، اسپارک از انواع داده‌ی رایج پشتیبانی می‌کند. این انواع داده می‌توانند در برنامه‌ی اسپارک یا در تعریف Schema استفاده شوند. برای مثال در اسپارک می‌توانید از String , Byte , Long , or Map, و غیره استفاده کنید.انواع داده‌ی پیچیده و ساختار یافته در اسپارکدر تحلیل‌ داده‌های پیچیده، فقط داده‌های رایج و معمولی حضور ندارند. داده‌ها پیچیده و معمولا ساختار یافته و یا درهم خواهند بود. در این مواقع نیاز داریم تا اسپارک این داده‌های پیچیده را برای ما مدیریت کند. این داده‌ها می‌توانند در شکل‌های maps, arrays, structs, dates, timestamps باشند.ساختن DataFrame و Schemaدر اسپارک، یک Schema نام ستون‌ها و نوع داده‌ی DataFrame را تعریف می‌کند. در بیشتر مواقع، Schemaها به‌هنگام خواندن داده‌های ساختار یافته از منابع داده‌ی خارجی به کار می‌آیند.تعریف کردن Schema قبل از خواندن داده، در مقابل تعریف Schema به هنگام خواندن داده، چند مزیت دارد:وظیفه‌ی تشخیص نوع داده‌ را از دوش اسپارک برمی‌داریداسپارک را از ساختن یک جاب جدید برای خواندن داده و تشخیص Schema منع می‌کنید. این کار برای داده‌های حجیم زمان‌بر و هزینه‌بر خواهد بوداگر داده‌ مطابق با Schema نباشد، می‌توانید ارور ها را تشخیص دهیدپس پیشنهاد می‌شود همیشه قبل از خواندن داده از منبع، Schema آن را تعریف کنید. البته اگر نمی‌خواهید Schema را تعریف کنید، یک راه این است که یک سمپل از کل داده‌ی خود تهیه کنید و بگذارید اسپارک از آن سمپل کوچک، Schema کل داده را استخراج کند. برای این کار می‌توان از samplingRatio استفاده کرد.Dataset APIاسپارک ۲ APIهای DataFrame و Dataset را با عنوان Structured API یکپارچه کرد تا توسعه‌دهندگان فقط یک API را یاد بگیرند. همانطور که در شکل زیر آمده، Datasetها ۲ ویژگی دارند: Typed API و Untyped APIمی‌توانید یک DataFrame را در Scala، یک نام مستعار برای کالکشنی از Generic Objectها یعنی Dataset[Row] در نظر بگیرید؛ که Row یک Generic Untyped JVM Object است که ممکن است تایپ‌های متنوعی را نگه‌داری کند. در مقابل، Dataset یک کالکشن از Strongly Typed JVM Object در Scala یا یک کلاس در Java است. در داکیومنت Dataset آمده که:یک کالکشن از Strongly Typed Object که می‌تواند به‌صورت موازی با استفاده از عملیات Functional یا Relational ترنسفورم شود. هر Dataset [در Scala] یک شکل Untyped به نام DataFrame نیز دارد که یک Dataset از Row است.Typed Objects, Untyped Objects, and Generic Rowsدر زبان‌هایی که اسپارک پشتیبانی می‌کند، Dataset فقط در Java و Scala، و DataFrame فقط در R و Python  معنی پیدا می‌کند. علت این امر این است که R و پایتون، compile-time type-safe نیستند؛ تایپ‌ها در زمان اجرا به‌صورت داینامیک مشخص و معلوم می‌شوند - نه به هنگام کامپایل. در Scala و Java، تایپ‌ها  به هنگام کامپایل مقید به متغیرها و آبجکت‌ها هستند. اگرچه در Scala یک Dataframe نام دیگر برای Untyped Dataset[Row] است.در اسپارک، Row یک Generic Object Type است که یک کالکشن از تایپ‌های مختلف را نگه می‌دارد و می‌توان به آن‌ها از طریق ایندکس دست پیدا کرد. اسپارک این آبجکت‌های Row را تغییر می‌دهد و آن‌ها را به تایپ‌هایی مثل IntegerType و دیگر تایپ‌های رایج تبدیل می‌کند.به طور خلاصه، عملیاتی که می‌توانیم روی Dataset اجرا کنیم- مانند filter() , map() , groupBy() ,select() , take() - در DataFrame هم یکسان است. به نوعی، ‌Dataset شبیه RDD است که توابع گفته شده را به همراه Compile-Time Safety در اختیار می‌گذارد اما خواندن اینترفیس آن راحت‌تر و به شکل Object-Oriented Programming است.وقتی از Dataset استفاده می‌کنیم، موتور Spark SQL وظیفه‌ی ساخت، تبدیل و سریال‌سازی شی‌های JVM را برعهده دارد. همچنین با استفاده از Dataset Encoderها، وظیفه‌ی مدیریت حافظه‌ی Java Heap را هم برعهده دارد.DataFrames Versus Datasetsتا به اینجا ممکن است سوال پیش بیاید که چرا و چه موقع باید از DataFrame و یا Dataset استفاده کرد. در بیشتر مواقع استفاده از هر کدام ممکن است، بسته به اینکه از چه زبانی استفاده می‌کنید، اما بعضی مواقع یکی از آن دو به دیگری ارجحیت دارد. مثال‌هایی را در زیر می‌بینیم:اگر می‌خواید به اسپارک بگویید که چه کار کند، نه اینکه چگونه انجامش دهد، از DataFrame یا Dataset استفاده کنید.اگر فهم بیشتر، مفاهیم سطح بالا و عملیات DSL را می‌خواهید، از DataFrame یا Dataset استفاده کنید.اگر Compile-Time Type Safety می‌خواهید و مشکلی با ساختن چند Case Class برای یک Dataset[T] به خصوص ندارید، از Dataset استفاده کنید.اگر پردازش شما  به عبارت‌های سطح بالا، filter، map، aggregation، محاسبه‌ی میانگین و مجموعه، کوئری‌های SQL، دسترسی به ستون‌ها و یا عملیات Relational روی داده‌های نیمه‌ ساختار یافته احتیاج دارد، از DataFrame یا Dataset استفاده کنید.اگر پردازش شما اجبار می‌کند که از Relational Transformation مانند کوئری‌های SQLمانند استفاده شود، از DataFrame استفاده کنید.اگر می‌خواهید از از مزایای سریال‌سازی بهینه‌ی Tungesten به‌همراه Encoderها بهره‌مند شوید، از Dataset استفاده کنید.اگر یکپارچگی، بهینگی کد و سادگی API میان تمام اجزای اسپارک احتیاج دارید، از DataFrame استفاده کنید.اگر برنامه‌نویس R هستید، از DataFrame استفاده کنیداگر برنامه‌نویس Python هستید، از DataFrame استفاده کنید و اگر کنترل بیشتری می‌خواهید، از RDD استفاده کنیداگر فضا و کارآمدی سرعت می‌خواهید، از DataFrame استفاده کنید.اگر می‌خواهید ارورها به‌هنگام کامپایل مشخص شوند و نه ران‌تایم، API مناسب را مطابق شکل زیر انتخاب کنید.چه موقع از RDD استفاده کنیمممکن است سوال پیش بیاید که با این اوصاف، آیا RDD دارد تبدیل به شهروند درجه‌ی دوم می‌شود؟ آیا آن Deprecate می‌شود؟ جواب نه است. RDD API همچنان پشتیبانی خواهد شد، اگرچه تمام ویژگی‌هایی که در اسپارک ۲ و ۳ هستند و خواهند بود، اینترفیس DataFrame خواهد داشت.مواقعی هستند که شما از RDD استفاده می‌کنید مانند:از یک پکیج third-party استفاده می‌کنید که با استفاده از RDD نوشته شده استمی‌توانید از بهینه‌سازی کد، استفاده بهینه از فضا و کارایی عملکرد که در DataFrame و Dataset وجود دارد، چشم‌پوشی کنیدمی‌خواهید دقیقا به اسپارک بگویید که چگونه یک کوئری را انجام بدهدمی‌توانید بین DataFrame یا Dataset و RDD با فراخوانی تابع df.rdd جابجا شوید. (البته این کار هزینه دارد و باید جلوگیری شود مگر اینکه واجب است) بالاخره، DataFrame و Dataset بر روی RDD ساخته شده‌اند و به‌هنگام مرحله‌ی ساخت کد، به RDD فشرده تجزیه می‌شوند.موتور Spark SQL در سطح بالا، Spark SQL به توسعه دهنده اجازه می‌دهد تا کوئری‌های مطابق با ANSI SQL:2003 بر روی داده‌ی ساختار یافته‌ منتشر کند. از ابتدای منتشر شدن آن در نسخه‌ی ۱.۳، Spark SQL به یک موتور قابل توجه تبدیل شده است که بسیاری از کاربری‌های سطح بالا با استفاده از آن ساخته شده است. علاوه بر امکان انتشار کوئری‌های SQL مانند، Spark SQL کارهای زیر را انجام می‌دهد:اجزای اسپارک را یکپارچه و یکنواخت می‌کند و اجازه استفاده از مفاهیم DataFrame/Dateset را می‌دهدامکان اتصال به Apache Hive را فراهم می‌کندداده‌های ساختاریافته با Schema مشخص را از فایل‌های با فرمت JSON, CSV, Text, Avro, Parquet, ORC و... می‌خواند و آن‌ها را به تیبل‌های موقت تبدیل می‌کندپلی است برای اتصال به JDBC/ODBCدر اجرای نهایی، Query Plan بهینه و کد فشرده برای JVM می‌سازددر مرکز Spark SQL Engine، بهینه‌ساز Catalyst Optimizer و Project Tungsten قرار دارد. این دو با هم امکان دسترسی به APIهای سطح بالای DataFrame و Dataset و کوئری‌های SQL را فراهم می‌سازند.Catalyst Optimizerبهینه‌ساز Catalyst Optimizer یک کوئری محاسباتی را می‌گیرد و آن را تبدیل به برنامه‌ی اجرا (Execution Plan)  می‌کند. این عملیات شامل ۴ فاز است:1- تحلیل (Analysis)2- بهینه‌سازی منطقی (Logical Optimization)3- برنامه‌ریزی فیزیکی (Physical Planning)4- تولید کد (Code Generation)فاز اول: تحلیل (Analysis)موتور Spark SQL کار خود را با ایجاد یک Abstract Syntax Tree برای کوئری SQL یا DataFrame شروع می‌کند. در این مرحله‌ی آغازین، هر ستون یا نام تیبل با استفاده از Catalog داخلی به‌دست خواهد آمد. Catalog یک اینترفیس برای Spark SQL است که لیست نام‌های ستون‌ها، انواع داده، تابع‌ها، تیبل‌ها، دیتابیس‌ها و ... را ذخیره می‌کند. وقتی همه‌ی این‌ها به ‌دست آمدند، وارد فاز بعدی می‌شویم.فاز دوم: بهینه‌سازی منطقی (Logical Optimization)همانطور که شکل بالا نشان می‌دهد، این فاز شامل ۲ مرحله‌ی درونی است. Catalyst Optimizer ابتدا مجموعه‌ای از برنامه‌ریزی‌ها را ایجاد خواهد کرد و سپس با استفاده از Cost-Based Optimizer(CBO)، هزینه‌ی هر برنامه‌ریزی را به آن متصل خواهد کرد. فاز سوم: برنامه‌ریزی فیزیکی (Physical Planning)در این فاز، Spark  SQL یک برنامه‌ریزی فیزیکی بهینه را برای Logical Plan انتخاب شده در مرحله‌ی قبلی تولید می‌کند.فاز چهارم: تولید کد (Code Generation)فاز آخر بهینه‌سازی کوئری شامل تولید بایت‌کدهای بهینه‌ی جاوا است که بر روی هر ماشین اجرا شود. چون Spark SQL می‌تواند روی دیتاست‌های لود شده در حافظه عملیات انجام دهد، اسپارک می‌تواند از آخرین تکنولوژی‌های کامپایلر برای تولید کد استفاده کند تا سرعت اجرا را افزایش دهد. به عبارت دیگر، به‌عنوان یک کامپایلر عمل می‌کند. Project Tungsten که امکان تولید کد تمام مرحله‌ای (Whole-Stage Code Generation) را فراهم می‌کند، در اینجا نقش دارد.ممکن است بپرسید Whole-Stage Code Generation چیست؟ یک فاز بهینه‌سازی فیزیکی کوئری است که کل کوئری را به یک فانکشن تبدیل می‌کند؛ با این کار از شر فراخوانی‌های مجازی فانکشن راحت می‌شود و از CPU برای داده‌های میانی استفاده می‌کند. نسخه‌ی دوم موتور Tungsten که در اسپارک ۲ معرفی شد، از این روش برای تولید کد‌های فشرده‌ی RDD استفاده می‌کند. این استراتژی به طرز فوق‌العاده‌ای کارآمدی CPU و عملکرد را افزایش می‌دهد.</description>
                <category>مجتبی میر یعقوب زاده</category>
                <author>مجتبی میر یعقوب زاده</author>
                <pubDate>Wed, 07 Apr 2021 11:18:30 +0430</pubDate>
            </item>
                    <item>
                <title>درک مفاهیم در یک برنامه‌ی Apache Spark</title>
                <link>https://virgool.io/@TabaMojj/%D8%AF%D8%B1%DA%A9-%D9%85%D9%81%D8%A7%D9%87%DB%8C%D9%85-%D8%AF%D8%B1-%DB%8C%DA%A9-%D8%A8%D8%B1%D9%86%D8%A7%D9%85%D9%87-%DB%8C-apche-spark-efqg2jnqledl</link>
                <description>مفاهیم در یک برنامه‌ی Apche Sparkبرای فهمیدن اینکه در یک برنامه‌ی اسپارک دقیقا چه اتفاق‌هایی می‌افتد، باید با تعدادی از مفاهیم کلیدی برنامه‌های اسپارک آشنا شویم.Application:برنامه‌ی کاربر در اسپارک که با APIهای آن نوشته شده است. این شامل یک Driver Program و Executorها بر روی کلاستر است.SparkSession:یک آبجکت است که نقطه‌ی آغازین برای تعامل با امکانات اسپارک به‌حساب می‌آید و اجازه‌ی برنامه‌نویسی با استفاده از APIها را می‌دهد. اگر از Shell استفاده کنید، خود اسپارک یک SparkSession برای شما ایجاد می‌کند، در حالیکه در یک برنامه‌ی اسپارک خودتان باید SparkSession را بسازید.Job:یک محاسبات موازی که شامل چند تسک است و با فراخوانی Spark Actionها مانند ()save و ()collect شروع به کار می‌کند.Stage:هر جاب به مجموعه‌های کوچکی از تسک‌ها تقسیم می‌شود که به آن‌ها Stage گفته می‌شود. هر کدام از آن‌ها به دیگری متکی است.Task:یک واحد از کار یا اجرا که به Spark Executor ارسال می‌شود.حال کمی بیشتر در این مفاهیم عمیق می‌شویم.Spark Application و SparkSessionدر مرکز هر برنامه‌ی اسپارک(Spark Application)، Spark Driver قرار دارد که آبجکت SparkSession را می‌سازد. وقتی از Shell استفاده می‌کند، این درایور بخشی از Shell است و آبجکت SparkSession خودش ساخته می‌شود. می‌توان با استفاده از متغیر spark به آن دسترسی پیدا کرد.Spark Jobدرایور طی تعامل با Spark Shell، برنامه‌ی اسپارک را تبدیل به یک یا چند جاب می‌کند. سپس هر جاب را تبدیل به یک DAG می‌کند. این در اصل Spark Execution Plan یا برنامه‌ی اجرای اسپارک است که در آن هر گره‌ی درون DAG می‌تواند یک یا چند Stage باشد.Spark Stageبر اساس اینکه چه عملیاتی می‌تواند به‌صورت موازی یا سریالی انجام شود، Stageها به‌عنوان گره‌های DAG ساخته می‌شوند. همه‌ی عملیات اسپارک نمی‌توانند در یک Stage انجام شود، پس باید به چندین Stage تقسیم شوند. Spark Taskهر Stage شامل Taskهایی است(یک واحد از عملیات اجرا) که بعد در هر کدام از Executorها پخش می‌شوند؛ هر تسک به یک هسته نگاشت می‌شود و بر روی یک پارتیشن از داده کار می‌کند. به‌همین خاطر، یک Executor با ۱۶ هسته می‌تواند حداقل ۱۶ تسک داشته باشد و روی حداقل ۱۶ پارتیشن از داده کار کند. این باعث می‌شود اجراهای اسپارک فوق‌العاده موازی شوند!Transformations, Actions, and Lazy Evaluationتمام عملیات اسپارک به ۲ دسته تقسیم می‌شوند: Transformations و Actions. همانطور که از نام Transformation معلوم است، این‌ها یک Spark DataFrame را تبدیل به یک DataFrame جدید می‌کنند، بدون آنکه DataFrame اصلی را تغییر دهند(به آن خاصیت immutability می‌دهد) به عبارت دیگر، وقتی یک عملیات مانند ()select یا ()filter فراخوانی می‌شود، DataFrame اصلی تغییری نمی‌کند بلکه نتیجه‌ی ترنسفورم شده به‌عنوان یک DataFrame جدید باز می‌گردد.تمام ترنسفورم‌ها Laze Evaluated هستند؛ یعنی نتایج همان لحظه محاسبه نمی‌شوند بلکه به‌عنوان یک Lineage ذخیره می‌شوند. یک Lineage به اسپارک اجازه می‌دهد که بعدا در برنامه‌ی اجرا، یک سری ترنسفورم‌ها را دوباره مرتب‌سازی کند، آن‌ها را یکی کند یا آن‌ها را تبدیل به Stageهایی بکند تا عملیات بهینه‌ انجام شود. Lazy Evaluation استراتژی اسپارک برای به تاخیر انداختن عملیات اجرا تا زمانی است که یک Action فراخوانی شده است یا داده‌ها لمس شده‌اند (از روی دیسک خواندن یا به روی دیسک نوشتن)یک Action باعث به‌ حرکت افتادن Lazy Evalutaion تمام ترنسفورم‌های ذخیره شده می‌شود. در شکل زیر، تمام ترنسفورم‌های T ذخیره شده‌اند تا زمانی که Action A فراخوانی می‌شود. هر کدام از ترنسفورم‌های T یک DataFrame جدید تولید می‌کنند.این Lazy Evaluation باعث می‌شود تا اسپارک با نگاه کردن به ترنسفورم‌هایی که پشت سر هم آمده‌اند، کوئری‌ها را [با ترتیب مناسب و] بهینه اجرا کند. Lineage و Immutability به ما Fault Tolerance می‌دهند. چون اسپارک هر ترنسفورم را در Lineage ذخیره می‌کند و در این ترنسفورم‌ها، DataFrame بدون تغییر باقی می‌ماند، در صورت بروز هر گونه خطا، می‌تواند با دوباره اجرا کردن Lineage ذخیره شده، حالت اصلی را دوباره تولید کند.تمام Actionها و Transformationها در یک Query Plan هستند. تا زمانی که یک Action فراخوانی نشده است، هیچ چیزی در Query Plan اجرا نمی‌شود.Narrow and Wide Transformationsیک نکته‌ی بسیار خوب Lazy Evaluation این است که اسپارک می‌تواند محاسبات کوئری شما را نگاه کند و مشخص کند که چطور می‌تواند آن را بهینه کند. این بهینه‌سازی می‌تواند به‌صورت متصل کردن یا پایپ‌لاین کردن بعضی عملیات و اختصاص دادن آن‌ها به یک Stage باشد یا اینکه آن‌ها را با توجه به نیاز داشتن به جابجایی داده در کلاسترها، به چند Stage تبدیل می‌کند.این Transformationها می‌توانند به دو دسته‌ی Narrow Dependencies یا Wide Dependencies تقسیم شوند. هر ترنسفورمی که یک پارتیشن خروجی می‌تواند از یک پارتیشن ورودی محاسبه شود، یک Narrow Transformation است.بعضی توابع مانند ()groupBy اسپارک را ملزم به انجام Wide Transformation می‌کند یعنی Data Shuffle(حرکت داده) باید در هر کدام از پارتیشن‌های Executorها اتفاق بی‌افتد. در این Transformationها، توابع، به خروجی دیگر پارتیشن‌ها برای محاسبه‌ی نتیجه‌ی نهایی احتیاج دارند.Spark UIاسپارک یک محیط گرافیکی به نام Spark UI ارائه می‌دهد که می‌توانید برنامه‌ها، جاب‌ها، استیج‌ها و تسک‌ها را مانیتور کنید. بسته به اینکه اسپارک چگونه دیپلوی می‌شود، درایور یک Web UI را تولید می‌کند که به‌صورت دیفالت بر روی پورت ۴۰۴۰ است.</description>
                <category>مجتبی میر یعقوب زاده</category>
                <author>مجتبی میر یعقوب زاده</author>
                <pubDate>Mon, 05 Apr 2021 22:37:55 +0430</pubDate>
            </item>
                    <item>
                <title>مقدمه‌ای بر Apache Spark</title>
                <link>https://virgool.io/@TabaMojj/%D9%85%D9%82%D8%AF%D9%85%D9%87-%D8%A7%DB%8C-%D8%A8%D8%B1-apache-spark-qwdi1v6pdept</link>
                <description>بیگ دیتا و محاسبات توزیع‌شده در گوگلوقتی به مقیاس(Scale) فکر می‌کنیم، نمی‌توان توانایی موتور جستجوی گوگل در ایندکس‌گذاری و جستجوی داده‌های جهان روی اینترنت با سرعت برق‌آسا را نادیده گرفت. نام گوگل مترادف است با مقیاس. در واقع نام Google یک اشتباه عمدی در تلفظ Googol است - یعنی ۱ با ۱۰۰ صفر.نه سیستم‌های ذخیره‌سازی قدیمی مانند Relational Database Management Systems(RDBMS) و نه برنامه‌نویسی Imperative نمی‌توانست پاسخگوی مقیاسی که گوگل می‌خواست اسناد ایندکس‌گذاری شده روی اینترنت را جستجو کند، باشد. این نیاز منجر به درست شدن Google File System&#40;GFS&#41;، MapReduce(MR) و Bigtable شد.در حالیکه GFS یک FileSystem توزیع‌شده و خطاپذیر (Fault-Tolerant) در سرور‌های یک کلاستر ارائه می‌داد،‌ Bigtable ذخیره‌سازی مقیاس‌پذیر داده‌های ساختاریافته در GFS را فراهم می‌کرد. MR یک پارادایم جدید برنامه‌نویسی موازی برای پردازش داده با مقیاس بالا در GFS و Bigtable ارائه داد.برنامه‌های MR با سیستم MapReduce در ارتباط هستند که کدهای محاسباتی (توابع Map و Reduce) را به جایی که داده‌ها ساکن هستند ارسال می‌کند؛ در این روش ساکن بودن داده‌ها، به انتقال داده‌ها به اپلیکیشن ترجیح داده می‌شود.در کلاستر، Workerها محاسبات میانی را Aggregate و Reduce می‌کنند و یک نتیجه‌ی نهایی از تابع Reduce به‌عنوان خروجی می‌دهند؛ که بعد در یک فضای ذخیره‌سازی توزیع‌شده ذخیره می‌شود تا برای اپلیکیشن قابل دسترسی باشد. این روش به طرز قابل توجهی ترافیک شبکه را کم می‌کند و بیشتر Input/Output(IO) را به‌جای توزیع در شبکه، به‌صورت Local نگه‌داری می‌کند.بیشتر کار گوگل به‌عنوان کار اختصاصی بود [و قابل دسترس برای عموم نبود]  اما ایده‌هایی که در این مقاله‌ها آمده‌‌اند، باعث بوجود آمدن ایده‌های خلاقانه‌ای در جامعه‌ی اوپن سورس شد - مخصوصا در Yahoo که در آن زمان با این چنین چالش‌های بیگ‌دیتا برای موتور جستجوی خود روبرو بود.استفاده از Hadoop در Yahooچالش‌های محاسباتی و راه‌حل‌های ارائه شده در مقاله‌ی Google GFS، منجر به ارائه‌ی یک طرح اولیه برای Hadoop File System &#40;HDFS&#41; شد. در این طرح اولیه MapReduce به‌عنوان یک فریم‌ورک برای محاسبات توزیع‌شده ارائه شده بود. در سال ۲۰۰۶ با بخشیدن HDFS به Apache Software Foundation، بخشی از فریم‌ورک Apache Hadoop شد.اگرچه Apache Hadoop خارج از یاهو اقتباس‌های زیادی داشت و Contributerهای زیادی را در جامعه‌ی اوپن سورس به خود جلب کرد و ۲ شرکت تجاری بر اساس این ابزار اوپن‌ سورس بوجود آمد (Cloudera و Hortonworks) اما فریم‌ورک MapReduce روی HDFS ایرادهایی داشت.اول اینکه مدیریت آن سخت بود و پیچیدگی عملیاتی دست‌ و پاگیری داشت. دوم، MapReduce API برای پردازش دسته‌ای (Batch-Processing) پرکلمه و طولانی بود و احتیاج به کدهای ستاپی داشت که تکراری و زیاد بودند. سوم، با دسته‌های بزرگ از داده‌ها که هر کدام تعداد زیادی از جفت تسک‌های MR داشتند، نتیجه‌ی محاسبه‌شده‌ی میانی هر جفت بر روی دیسک محلی نوشته می‌شد تا در عملیات بعدی استفاده شود (شکل پایین) این عملیات تکراری I/O دیسک عواقب بدی داشت: جاب‌های بزرگ  MR می‌توانستند ساعت‌ها و حتی روزها به طول انجامند.و در نهایت، اگرچه Hadoop MR در جاب‌های با مقیاس بزرگ مفید بود اما با ترکیب‌های کارهای دیگری مانند ماشین لرنینگ، استریمینگ و کوئری‌های اینتراکتیو SQLمانند عملکرد بدی پیدا می‌کرد.برای برطرف کردن این مشکلات، مهندس‌ها سیستم‌های سفارشی را توسعه دادند (Apache Hive,Apache Storm,  Apache  Impala,  Apache  Giraph,  Apache  Drill,  Apache  Mahout و ...) که هر کدام شامل API و پیکربندی کلاستر مخصوص خود بود. این خود باعث شد به پیچیدگی عملیاتی Hadoop اضافه و یادگیری آن برای توسعه‌دهنده‌ها سخت‌تر شود.سوالی که پیش آمد این بود که آیا راهی وجود دارد که بتوان Hadoop و MR را ساده‌تر و سریع‌تر کرد؟ (با در نظر گرفتن جمله‌ی Alan Kay: چیزهای ساده باید ساده باشند، چیزهای پیچیده باید ممکن باشند)سال‌های اولیه‌ی Spark در AMPLabمحققین در UC Berkeley که قبلا بر روی Hadoop MapReduce کار کرده بودند، این چالش را در پروژه‌ای تحت عنوان Spark قبول کردند. آن‌ها می‌دانستند که MR برای انجام محاسبات جاب‌های تکراری یا تعاملی ناکارآمد است و از طرفی یادگیری آن هم سخت است. پس از همان ابتدا این ایده را داشتند که Spark را ساده‌تر، سریع‌تر و راحت‌تر بسازند. این تلاش‌ها در سال ۲۰۰۹ از RAD Lab شروع شد که بعدها تبدیل به AMPLab شد (و حالا نام آن RISELab است)مقاله‌های ابتدایی Spark نشان می‌دادند که در بعضی جاب‌ها، ۱۰ تا ۲۰ برابر سریع‌تر از Hadoop MapReduce عمل می‌کرد. هدف پروژه‌ی Spark این بود که ایده‌های برگرفته شده از Hadoop MapReduce را بگیرد و سیستم را بهبود ببخشد: Fault Tolerance آن افزایش یابد، بتواند به‌صورت موازی کار کند، از ذخیره‌سازی درون-حافظه‌ای پشتیبانی کند و APIهای ساده‌ای را به زبان‌های مختلف برنامه‌نویسی ارائه دهد. تا سال ۲۰۱۳ استفاده از Spark افزایش یافت و تعدادی از سازنده‌ها و محققین اصلی آن یعنی Matei  Zaharia,  Ali  Ghodsi,  Reynold  Xin,  Patrick  Wendell,  Ion  Stoica,and Andy Konwinski پروژه را به ASF بخشیدند و یک کمپانی با نام Databricks تاسیس کردند.کمپانی Databricks و جامعه‌ی توسعه‌ دهندگان اپن سورس با هم کار کردند تا در می ۲۰۱۴ Apache Spark 1.0 را تحت نظر ASF منتشر کنند.آپاچی اسپارک (Apache Spark) چیست ؟آپاچی اسپارک (Apache Spark) یک موتور یکپارچه برای پردازش توزیع‌یافته‌ی داده در مقیاس بالا است که محل استفاده‌ی آن در دیتا سنترها و فضاهای ابری است.اسپارک امکان ذخیره‌سازی درون‌حافظه‌ای را برای محاسبات میانی فراهم می‌کند که همین باعث می‌شود از Hadoop MapReduce سریع‌تر باشد. اسپارک شامل کتابخانه‌هایی برای ماشین لرنینگ (MLlib)، SQL برای کوئری‌های اینتراکتیو (Spark SQL) پردازش جریانی برای کار با داده‌های لحظه‌ای (Structered Streaming) و پردازش گراف (GraphX)فلسفه‌ی طراحی اسپارک ۴ مولفه‌ی اصلی دارد:سرعتراحتی در استفادهماژولار بودنتوسعه‌پذیریدر اینجا می‌خوانیم که هر کدام چه معنایی برای این فریم‌ورک دارد.سرعتاسپارک هدف پرسرعت بودن را به چند طریق دنبال کرده است. اول، پیاده‌سازی آن از پیشرفت‌های اخیر تکنولوژی در زمینه‌ی هزینه و پرفورمنس CPU و حافظه نفع می‌برد. امروزه سرورها ارزان هستند و چندین گیگابایت حافظه و چند هسته دارند و سیستم عامل مبتنی بر Unix آن‌ها از پس Multithreading و پردازش موازی بر می‌آید. این فریم‌ورک از تمامی فاکتورهای گفته شده نهایت استفاده را می‌کند.دوم، اسپارک محاسبات کوئری خود را به‌عنوان یک Directed Acyclic Graph(DAG) انجام می‌دهد؛ برنامه‌ریزی DAG‌ و بهینه‌ساز کوئری آن می‌تواند یک گراف محاسباتی بهینه را تولید کند که معمولا می‌توان آن را به چند تسک تبدیل کرد که محاسبات هر کدام را می‌توان به‌صورت موازی در Workerهای یک Cluster انجام داد. سوم، موتور اجرای آن یعنی Tungsten برای تولید کدهای فشرده از Whole-Stage Code Generation استفاده می‌کند.با نگه‌داری نتایج میانی در حافظه و کم بودن تعداد خواندن و نوشتن از دیسک، پرفورمنس اسپارک به طرز قابل توجهی افزایش میابد.راحتی در استفادهاسپارک با ارائه‌ی یک ساختمان داده‌ی لاجیکال به نام Resilient Distributed Dataset(RDD) به ساده‌ بودن دست پیدا کرده است. با ارائه‌ی مجموعه‌ای از Transformationها و Actionها به‌عنوان Operation، اسپارک یک مدل برنامه‌نویسی ساده را ارائه می‌دهد که می‌توان برای ساخت برنامه‌های بیگ دیتا در زبان‌های برنامه‌نویسی رایج از آن استفاده کرد.ماژولار بودناسپارک می‌تواند از پس چندین نوع کار بر بیاید و از آن در زبان‌های برنامه‌نویسی پشتیبانی‌شده استفاده کرد: Scala, Java, Python, SQL, R. اسپارک کتابخانه‌های یکپارچه با APIهایی با داکیومنت خوب را ارائه می‌دهد که این‌ها از اجزای اصلی آن است:  Spark SQL, Spark Structed Streaming, Spark MLlib, GraphX که از همه‌ی آن‌ها می‌توان در یک موتور استفاده کرد. می‌توانید یک برنامه‌ی اسپارک بنویسید که از پس همه‌ی این کارها برمی‌آید.توسعه‌پذیریاسپارک به‌جای ذخیره‌سازی، تمرکز خود را روی موتور محاسبات سریع و موازی خود می‌گذارد. بر خلاف Apache Hadoop که شامل هم ذخیره‌سازی و هم محاسبات بود، اسپارک این دو را از هم جدا می‌کند. یعنی می‌توانید از اسپارک استفاده کنید تا داده‌ را از منابع مختلف بخوانید: ApacheHadoop, Apache Cassandra, Apache HBase, MongoDB, Apache Hive, RDBMS و سپس محاسبات داده‌ها را در حافظه انجام دهید. تحلیل‌های یکپارچهیکپارچگی مفهومی نیست که مختص اسپارک باشد اما یکی از مولفه‌های اصلی فلسفه‌ی طراحی و توسعه‌ی آن بوده است. در نوامبر ۲۰۱۶ ACM جایزه‌ی ACM Award را به سازندگان اسپارک برای این مقاله داد. این مقاله توضیح می‌دهد که اسپارک چگونه جایگزینی برای موتورهایی است که هر کدام مختص یک کار به‌خصوص مانند تحلیل گراف و پردازش دسته‌ای بودند. اسپارک یک موتور یکپارچه است که تمام این اجزاء را درون خود جای داده است.اجزای آپاچی اسپارک به‌عنوان یک بسته‌ی یکپارچههمانطور که در شکل زیر می‌بینید، اسپارک ۴ جز اصلی را به‌عنوان کتابخانه‌ درون خود دارد. هر کدام از این اجزا از موتور Fault-Tolerant اسپارک مجزا است که در آن ما از APIها برای نوشتن برنامه‌های اسپارک استفاده می‌کنیم و اسپارک آن را تبدیل به DAG می‌کند که موتور اصلی آن را اجرا می‌کند. پس کدهایی که با استفاده از APIهای Java, R, Scala, Python نوشته شده‌اند تبدیل به بایت‌کدهای فشرده‌ای می‌شوند که در JVMهای Workerها در Cluster اجرا می‌شوند.Spark SQLاین ماژول به‌ خوبی با داده‌های ساختار یافته کار می‌کند. می‌توانید داده‌هایی را که در تیبل‌های RDBMS یا در فایل‌های با فرمت‌های داده‌های ساختار یافته ذخیره شده‌اند، بخوانید و سپس تیبل‌های موقت یا دائمی در اسپارک بسازید. همچنین، وقتی از Structured APIهای Java, Python,Scala, R استفاده می‌کنید، می‌توانید از کوئری‌های SQLمانند برای کوئری داده استفاده کنید.Spark MLlibاسپارک یک کتابخانه برای الگوریتم‌های رایج ماشین لرنینگ دارد که نام آن MLlib است. از ابتدای انتشار اسپارک عملکرد این کتابخانه بهتر و بهتر شده است.Spark Structured Streamingآپاچی اسپارک ۲.۰ یک ویژگی آزمایشی با نام Continuous Streaming model و Structured Streaming API معرفی کرد. در نسخه‌ی ۲.۲ Structured Streaming در دسترس عموم قرار گرفت. برای توسعه دهندگان بیگ دیتا، پاسخگویی آنی به داده‌های ثابت و جریانی و ترکیب آن‌ها امری مهم است. این مدل جدید یک جریان را به شکل یک تیبل دائما در حال بزرگ شدن می‌بیند که ردیف‌های جدید داده به انتهای آن اضافه می‌شود. توسعه دهندگان می‌توانند با آن به شکل یک تیبل ساختار یافته رفتار و کوئری‌های خود را مانند داده‌های ثابت اجرا کنند.پشت Structured Streaming model موتور Spark SQL تمام جنبه‌های Fault Tolerance و داده‌های اضافه‌شونده را مدیریت می‌کند تا توسعه‌دهنده تمرکز خود را بر نوشتن برنامه‌های جریانی بگذارد. اسپارک ۲ و ۳ منابع داده‌های جریانی را گسترش دادند تا بتوان از Apache Kafka, Kinesis, ، HDFS-based و فضای ذخیره‌سازی ابری هم بتوان استفاده کرد.Graphهمانطور که از نامش پیداست، این کتابخانه مخصوص کار با گراف‌ها(گراف‌های شبکه‌های اجتماعی، جاده‌ها و نقاط اتصال، گراف‌های شبکه‌های توپولوژی) و انجام محاسبات موازی است. الگوریتم‌هایی که در این کتابخانه موجود هستند: PageRank, Connected Components, و TriangleCounting.اجرای توزیع‌یافته در اسپارکاگر تا به اینجای مطلب خوانده باشید، می‌دانید که اسپارک یک موتور پردازش داده‌ی توزیع‌یافته است که اجزای آن با همکاری یکدیگر بر روی کلاستر کار می‌کنند. در اینجا می‌خواهیم بدانیم چگونه تمام اجزای معماری توزیع‌یافته‌ی اسپارک کار می‌کنند و با هم در ارتباط هستند.با شکل زیر شروع می‌کنیم. در سطح بالای معماری اسپارک، یک Spark Application شامل یک Driver Program است که وظیفه‌ی هماهنگ کردن عملیات موازی را روی کلاستر اسپارک دارد. این درایور از طریق SparkSession به اجزای توزیع‌یافته در کلاستر دسترسی دارد یعنی Spark Executor و Cluster Manager.Spark Driverچون بخشی از Spark Application وظیفه‌ی راه‌اندازی SparkSession را دارد، Spark Driver چندین نقش دارد: با Cluster Manager در ارتباط است، منابع (CPU, Memory) را از Cluster Manager برای Spark Executorها(JVMها) درخواست می‌کند، تمام عملیات اسپارک را تبدیل به محاسبات DAG می‌کند،‌ آن‌ها را برنامه‌ریزی می‌کند و اجرای آن‌ها را به‌عنوان تسک در Spark Executorها توزیع می‌کند. وقتی منابع اختصاص داده شدند، به‌طور مستقیم با Executorها ارتباط برقرار می‌کند.SparkSessionدر اسپارک ۲.۰ SparkSession تبدیل به یک مجرای یکپارچه برای تمام عملیات اسپارک و داده‌ها شد. نه تنها کارهای ابتدایی قبلی اسپارک مانند SparkContext, SQLContenxt, HiveContext, SparkConf را انجام می‌دهد بلکه کار با اسپارک را راحت‌تر و ساده‌تر کرده است.اگرچه در اسپارک ۲ SparkSession شامل دیگر Contextها است اما همچنان می‌توان به تک تک Contextها  و توابع آن‌ها به طور مجزا دسترسی داشت. با این روش Backward Compatibility فراهم شده است تا کدهای قدیمی که در اسپارک ۱ با SparkContext و ... نوشته شده‌اند، همچنان کار کنند.با استفاده از همین یک مجرا، می‌توان پارامترهای ران‌تایم JVM را ساخت، DataFrame و Dataset ها معرفی کرد، از منابع داده‌ی مختلف خواند، کوئری‌های Spark SQL را اجرا کرد. SparkSession یک نقطه‌ی آغاز یکپارچه برای تمام امکانات اسپارک را فراهم می‌کند.در یک برنامه‌ی اسپارک، می‌توانید با استفاده از یکی از APIهای زبان‌های سطح بالا SparkSession را درست کنید. هنگام استفاده از Spark Shell به‌طور خودکار SparkSession ساخته شده است و می‌توانید با استفاده از یک Global Variable به نام spark یا sc به آن دسترسی پیدا کنید.در اسپارک ۱ باید Contextهای مجزا درست می‌کردید( برای Stream و SQL و ...) و برای هر کدام کدهای ابتدایی معرفی را می‌نوشتید، در اسپارک ۲ می‌توانید به ازای هر JVM یک SparkSession بسازید و از آن برای اجرای عملیات اسپارک استفاده کنید.Cluster Managerمدیریت و اختصاص منابع کلاستر، وظیفه‌ی Cluster Manager است. در حال حاضر اسپارک از ۴ Cluster Manager پشتیبانی می‌کند: نسخه‌ی داخلی خود اسپارک،‌Apache Hadoop YARN، Apache Mesos و Kubernetes.Spark Executorیک Spark Executor بر روی هر Worker Node در کلاستر اجرا می‌شود. این Executorها با Driver Program در ارتباط هستند و وظیفه‌ی اجرای تسک‌ها را بر روی Workerها بر عهده دارند. در بیشتر Deployment Modeها یک Executor به ازای هر نود اجرا می‌شود.Deployment Modeیک ویژگی جذاب اسپارک، پشتیبانی آن از تعداد زیادی Deployment Mode است که امکان اجرای اسپارک در کانفیگ‌ها و محیط‌های مختلف را فراهم می‌سازد. از آنجایی که Cluster Manager کاری با اینکه در کجا دارد اجرا می‌شود ندارد( و تنها به این فکر می‌کند که Spark Executorها را مدیریت کند و پاسخگوی درخواست‌های منابع باشد) اسپارک می‌تواند در محیط‌های محبوب اجرا شود، مانند Apache Hadoop YARN و Kubernetes.Distributed Data and Partitionsداده‌ی فیزیکی واقعی در فضای ذخیره‌سازی، به‌عنوان پارتیشن‌هایی که یا در HDFS و یا فضای ابری ساکن هستند، توزیع‌ شده است.(شکل پایین) اگرچه داده به‌عنوان پارتیشن‌هایی در کلاسترهای فیزیکی توزیع شده است، اسپارک با هر پارتیشن به‌عنوان یک داده‌ی لاجیکال برخورد می‌کند- به‌عنوان یک DataFrame در حافظه. البته این همیشه امکان‌پذیر نیست، به هر Spark Executor، متناسب با نزدیکی به داده، یک تسک سپرده می‌شود که نیاز دارد پارتیشنی را بخواند که به آن نزدیک است.پارتیشن‌بندی امکان پردازش موازی بهینه‌ را فراهم می‌سازد. شکستن داده به قسمت‌ها یا پارتیشن‌های مختلف این امکان را به Spark Executorها می‌دهد تا فقط داده‌هایی را که به آن‌ها نزدیک هستند، پردازش کنند؛ که این پهنای باند شبکه را کاهش می‌دهد.چه کسانی برای چه کارهایی از اسپارک استفاده می‌کنند؟بیشتر توسعه‌دهندگانی که با بیگ دیتا سر و کار دارند مهندس‌های داده، داشمندهای داده و یا مهندس‌های یادگیری ماشین هستند. علت جذب شدن آن‌ها به اسپارک این است که به آن‌ها اجازه می‌دهد برنامه‌های مختلفی را با استفاده از یک موتور و زبان‌های برنامه‌نویسی آشنا بسازند.فعالیت‌های علوم داده‌ایعلوم داده از داده استفاده می‌کند تا داستان‌هایی را بیان کند. اما قبل از بیان داستان، دانشمندان داده باید داده را تمیز کنند، آن را جستجو کنند تا به الگوهایی برسند و مدل‌هایی بسازند تا چیزهایی را پیش‌بینی کنند. بیشتر دانشمندان داده در استفاده از SQL، کتابخانه‌های NumPy و Pandas و زبان‌هایی مانند R و Python ماهر هستند. همچنین ضروری است که بدانند چطور با داده کار کنند، آن را تبدیل کنند و چگونه از الگوریتم‌های یادگیری ماشین موجود برای ساخت مدل استفاده کنند. خوشبختانه اسپارک از این ابزارها پشتیبانی می‌کند. برای ساخت مدل می‌توان از کتابخانه‌ی Spark MLlib استفاده کرد.فعالیت‌های مهندس داده‌ایبعد از ساخت مدل‌ها، دانشمندان داده معمولا نیاز دارند تا با دیگر اعضای تیم که وظیفه‌ی دیپلوی کردن مدل را دارند، در ارتباط باشند یا شاید هم باید با کسانی کار کنند تا داده‌ی خام را تبدیل به داده‌ی تمیز کنند که برای دیگر دانشمندان قابل استفاده باشد. مثلا ممکن است یک مدل Classification با دیگر اجزا مانند یک اپلیکیشن وب یا یک موتور جریانی مانند Apache Kafka در ارتباط باشد. این پایپ‌لاین را مهندس‌های داده می‌سازند.مهندس‌های داده آشنایی زیادی با اصول طراحی نرم‌افزار دارند و توانایی‌های زیادی در ساخت دیتا پایپ‌لاین‌های کارآمد دارند. دیتا پایپ‌لاین‌ها امکان تبدیل End-to-End داده‌های خام را که از منابع مختلف می‌آیند فراهم می‌سازند؛ داده‌ها تمیز می‌شوند تا توسعه‌دهندگان بتوانند از آن‌ها استفاده کنند، در فضای ذخیره‌سازی ابری یا در NoSQL یا RDBMS برای گزارش‌نویسی ذخیره می‌شوند.مهندس‌های داده از اسپارک استفاده می‌کنند چون موازی‌سازی محاسبات راحت و پیچیدگی‌ آن کمتر است. این باعث می‌شود تا تمرکز خود را روی استفاده از APIهای مبتنی بر DataFrame و زبان‌های برنامه‌نویسی مختص یک زمینه برای ETL، خواندن و ترکیب داده استفاده کنند.بهبود عملکرد اسپارک در نسخه‌های ۲ و ۳ به لطف استفاده از Catalyst Optimizer برای SQL و Tungsten برای تولید کدهای فشرده، کار را برای مهندس‌های داده آسان‌تر کرده است. این امکان برای آن‌ها فراهم است که از هر کدام از ۳ API موجود استفاده کنند: ‌RDD، DataFrame و Datasetدیگر موارد استفاده‌ از اسپارکفارغ از اینکه مهندس داده، دانشمند داده و یا مهندس یادگیری ماشین هستید، اسپارک می‌تواند در این زمینه‌ها کاربردی باشد:پردازش داده‌های حجیم به‌صورت موازی که در یک کلاستر توزیع شده استاجرای کوئری برای جستجو و مصورسازی دادهساخت، آموزش و ارزیابی مدل‌های ماشین لرنینگ با استفاده از MLlibپیاده‌سازی دیتا پایپ‌لاین‌هایی که از چندین منبع داده استفاده می‌کنندتحلیل دیتاست‌های گراف و شبکه‌های اجتماعیمحبوبیت میان توسعه‌دهندگان و توسعه‌ی آناسپارک به محبوبیت زیادی در جامعه‌ی اپن سورس دست پیدا کرده است. امروزه بیش از ۶۰۰ گروه‌ در سراسر جهان وجود دارند که تعداد اعضای آن‌ها نزدیک به نیم میلیون نفر می‌رسد. هر هفته، یک شخص در جهان یک سخنرانی یا یک پست وبلاگ درباره‌ی چگونگی استفاده از اسپارک در دیتا پایپ‌لاین‌ها به اشتراک می‌گذارد. در میان همه‌ی آنها، Spark + AI Summit بزرگ‌ترین کنفرانسی است که به استفاده‌ی اسپارک در یادگیری ماشین، مهندسی داده و علوم داده اختصاص داده شده است.</description>
                <category>مجتبی میر یعقوب زاده</category>
                <author>مجتبی میر یعقوب زاده</author>
                <pubDate>Sat, 03 Apr 2021 20:22:07 +0430</pubDate>
            </item>
                    <item>
                <title>بررسی Dimensionality Reduction در ماشین لرنینگ</title>
                <link>https://virgool.io/@TabaMojj/%D8%A8%D8%B1%D8%B1%D8%B3%DB%8C-dimensionality-reduction-%D8%AF%D8%B1-%D9%85%D8%A7%D8%B4%DB%8C%D9%86-%D9%84%D8%B1%D9%86%DB%8C%D9%86%DA%AF-nt43owl9vumt</link>
                <description>بررسی Dimensionality Reduction در ماشین لرنینگبسیاری از مسائل ماشین لرنینگ شامل هزاران یا حتی میلیون‌ها فیچر برای هر نمونه‌ی آموزشی میشن. نه تنها همه‌ی این فیچرها باعث میشن فرآیند آموزش به‌شدت آهسته بشه، بلکه پیدا کردن یک راه‌حل مناسب رو هم سخت‌تر میکنن. به این مشکل اغلب Curse of Dimensionality (مشقت بعدچندی) میگن.خوشبختانه در مسائل واقعی، اغلب این امکان وجود داره که تعداد فیچرها رو کاهش داد به‌طوری که یک مسئله‌ی رام‌نشدنی تبدیل به یک مسئله‌ی رام‌شدنی بشه. برای مثال تصاویر MNIST رو در نظر بگیرید (طبقه‌بندی اعداد با MNIST) پیکسل‌هایی که در گوشه‌های تصویر قرار دارن تقریبا همیشه سفید هستند، در نتیجه می‌تونید بدون از دست دادن اطلاعات زیاد، این پیکسل‌ها رو از مجموعه‌ی آموزشی حذف کنید. عکس زیر تایید می‌کنه که این پیکسل‌ها برای طبقه‌بندی، کاملا بی‌اهمیت هستن. هم‌چنین، دو پیکسل ِکنار هم، اغلب به‌شدت با هم همبستگی دارن: اگر این دو رو تبدیل به یک پیکسل کنید (برای مثال میانگین دو پیکسل رو بگیرید)، اطلاعات زیادی رو از دست نمی‌دید.کاهش ابعاد باعث از دست رفتن اطلاعات میشه (درست مثل کم حجم کردن یک عکس با تبدیل اون به JPEG که می‌تونه کیفیت اون رو کم کنه) پس درسته که این کار باعث میشه فرآیند آموزش سریع‌تر بشه، اما شاید باعث بشه که سیستم شما عملکرد بدی داشته باشه. همچنین پایپلاین‌های‌ شما رو کمی پیچیده می‌کنه و به‌همین خاطر نگه‌داری از اون‌ها سخت‌تر میشه. پس، اگر فرآیند آموزش آهسته هست، بهتره قبل از کاهش ابعاد، اول سیستم خودتون رو با مجموعه‌ی داده‌ی اصلی آموزش بدید. در بعضی مواقع، کاهش ابعاد مجموعه‌ی داده‌ی آموزشی ممکنه بعضی نویزها و اطلاعات غیرضروری رو حذف کنه و باعث بشه عملکرد افزایش پیدا کنه اما عموما این اتفاق نمیفته؛ این کار فقط باعث میشه سرعت آموزش بیشتر بشه.جدای از افزایش سرعت آموزش، کاهش ابعاد می‌تونه به‌شدت توی مصورسازی داده کاربردی باشه. کاهش ابعاد به دو یا سه بعد، رسم یک نمای متراکم از یک مجموعه‌ی داده‌ با ابعاد بالا رو امکان پذیر می‌کنه و باعث میشه با تشخیص الگوهایی مثل خوشه‌ها، بینش‌های مهمی رو به‌دست بیاریم. هم‌چنین، مصورسازی داده برای انتقال نتیجه گیری‌ها به افرادی که دانشمند داده نیستند، ضروری هست؛ به‌طور دقیق‌تر، افراد تصمیم‌گیرنده‌ای که از نتایج شما قراره استفاده بکنند.در این فصل قراره Curse of Dimensionality رو بررسی کنیم و بفهمیم که در فضاهای با ابعاد زیاد چه اتفاق‌هایی میفته. بعد دو رویکرد اصلی برای کاهش ابعاد و سه روش محبوب برای کاهش ابعاد رو بررسی می‌کنیم.مشقت بعدچندی‌ (Curse of DimensionalityCurse of Dimensionality)ما به قدری به زندگی کردن در سه بعد عادت کردیم که وقتی می‌خوایم یک فضا با ابعاد بالا رو تصور کنیم، شهود ما ناکام می‌مونه. حتی تصور یک ابرمکعب 4 بعدی ساده هم برای ذهن ما سخت هست، چه برسه به یک بیضوی که در یک فضای 1000 بعدی خم شده.به‌نظر میرسه که خیلی از چیزها در فضای با ابعاد بالا، رفتار متفاوتی دارن. برای مثال، اگر یک نقطه‌ی تصادفی در یک مربع واحد انتخاب کنید (مربعی با ابعاد 1×1) احتمال اینکه فاصله‌اش از گوشه‌ها کمتر از 0.001 باشه، حدودا برابر 0.4 هست ( به عبارت دیگه، احتمال کمی وجود داره که یک نقطه‌ی تصادفی در طول هر بعدی، دورترین نقطه باشه) اما در یک ابرمکعب با 10000 بعد، این احتمال بیش از 99.99999 درصد هست. در یک ابرمکعب با ابعاد بالا، بیشتر نقاط نزدیک به گوشه‌ها هستند.این هم یک تفاوت سخت دیگه: اگر دو نقطه‌ی تصادفی در یک مربع واحد انتخاب کنید، فاصله‌ی بین این دو نقطه به‌طور میانگین برابر 0.52 خواهد بود. اگر دو نقطه‌ی تصادفی در یک مکعب 3 بعدی انتخاب کنید، فاصله‌ی میانگین تقریبا 0.66 خواهد بود. اما دو نقطه‌ی تصادفی در یک ابرمکعب 1000000 بعدی چی ؟ فاصله‌ی میانگین، باور کنید یا نه، حدودا برابر 408.02 خواهد بود ! این تناقض هست: چطور دو نقطه می‌تونن انقدر از هم دور باشن درحالی که هر دو در یک ابرمکعب واحد یکسان هستند ؟ خب، در ابعاد بالا فضای زیادی هست. در نتیجه، مجموعه داده‌های با ابعاد بالا، در معرض خطر پراکندگی هستند: بیشتر نمونه‌های آموزشی احتمالا در فاصله‌ی زیادی از هم باشن. این هم‌چنین به این معناست که یک نمونه‌ی جدید بسیار دورتر از نمونه‌های آموزشی خواهد بود که باعث میشه این پیش‌بینی‌ها به نسبت پیش‌بینی در ابعاد کم، کمتر قابل اعتماد باشه چون این پیش‌بینی‌ها قراره بر اساس یک برون‌یابی بزرگ‌تر انجام بشن. خلاصه که، هرچقدر ابعاد مجموعه‌ی آموزشی بیشتر باشه، خطر اورفیت شدن هم بیشتر میشه.در تئوری، یک راه حل برای Curse of Dimensionality، افزایش اندازه‌ی مجموعه‌ی آموزشی هست به‌طوری‌که نمونه‌های آموزشی به یک چگالی کافی برسن. متاسفانه در عمل، تعداد نمونه‌های لازم برای رسیدن به یک چگالی، به‌طور نمایی با تعداد ابعاد افزایش پیدا میکنه. با تنها 100 فیچر (کمتر از مسئله‌ی MNIST) تعداد نمونه‌های آموزشی‌ برای اینکه نمونه‌ها به‌طور میانگین در فاصله‌ی 0.1 یکدیگه باشن، بیشتر از اتم‌های موجود در جهان ِ قابل مشاهده هست - با فرض اینکه اون‌ها در تمام ابعاد به‌طور یکنواخت پخش شدند.رویکردهای اصلی برای کاهش ابعادقبل از بررسی الگوریتم‌های کاهش بعد، بیاید یک به دو رویکرد اصلی برای کاهش ابعاد نگاهی بندازیم: تصویر (Projection) و Manifold Learningرویکرد اول: Projectionدر مسائل دنیای واقعی، نمونه‌های آموزشی در طول تمام ابعاد، به‌طور یکنواخت پخش نشدند. بسیاری از فیچرها تقریبا ثابت هستند در حالیکه بعضی‌هاشون به‌شدت با هم همبستگی دارن (همونطور که درمورد MNIST گفتیم) در نتیجه، تمام نمونه‌های آموزشی در یک زیرفضای با ابعاد کمتر از فضای چند بعدی قرار می‌گیرن ( یا نزدیک به اون ). این به نظر خیلی انتزاعی میاد، بیاید یک مثال رو بررسی کنیم. در شکل زیر می‌تونید یک مجموعه‌ی داده‌ی 3 بعدی رو که با دایره‌ها نشون داده شدن ببینید.دقت کنید که همه‌ی نمونه‌های آموزشی در نزدیکی یک صفحه قرار دارن: این یک زیرفضای با ابعاد کمتر ( 2 بعدی ) از فضای با ابعاد بالا هست ( 3 بعدی ) اگر همه‌ی نمونه‌های آموزشی رو به‌طور عمودی به این زیرفضا تصویر کنیم مجموعه‌ی داده‌ی 2 بعدی شکل زیر به‌دست میاد.( این کار با خط‌های کوتاهی که نمونه‌هارو به صفحه وصل میکنن نشون داده شده ) ما همین الان ابعاد مجموعه‌ی داده رو از 3 بعدی به 2 بعدی کاهش دادیم. دقت کنید که محورها نشون دهنده‌ی فیچرهای جدید z_1 و z_2 هستند ( مختصات تصویرها روی صفحه ) اگرچه، تصویر کردن همیشه بهترین روش برای کاهش ابعاد نیست. در بسیاری از مواقع زیرفضا ممکنه پیچ و تاب و چرخش داشته باشه، مثل مجموعه‌ی داده‌ی Swiss roll که در شکل زیر می‌بینید.تصویر اون به یک صفحه ( مثلا با حذف فیچر x_3 ) باعث میشه لایه‌های متفاوتی از مجموعه‌ی داده با هم قاطی بشن که در سمت چپ شکل می‌بینید. چیزی که ما واقعا می‌خوایم اینه که مجموعه‌ی داده رو تبدیل به شکل سمت راست کنیم که 2 بعدی هست.رویکرد دوم: Manifold Learningمجموعه‌ی داده‌ی Swiss roll یک نمونه از یک منیفلد دو بعدی هست. ساده‌تر، یک منیفلد دو بعدی یک شکل 2 بعدی هست که می‌تونه در یک فضا با ابعاد بالاتر، خمیده و پیچیده بشه. به‌طور عمومی‌تر، یک منیفلد d-بعدی یک بخشی از یک فضای n-بعدی هست (d &lt; n) که به‌صورت محلی نشون دهنده‌ی یک ابرصفحه‌ی d-بعدی هست. در مثالی که زدیم، d=2 و n=3 هست که به‌طور محلی نشون دهنده‌ی یک صفحه‌ی دو بعدی هست اما در بعد سوم پیچیده شده.بسیاری از الگوریتم های کاهش بعد با مدل سازی منیفولدی که نمونه‌های آموزشی روی آن قرار دارند کار می کنند؛ به این کار میگن Manifold Learning. این بر فرض Manifold تکیه می‌کنه (نام دیگر:  Manifold Hypothesis):  در دنیای واقعی، اکثر مجموعه های داده با ابعاد بالا نزدیک به یک منیفلد با ابعاد پایین‌تر قرار دارن. این ادعا اغلب به‌صورت تجربی مشاهده میشه.یکبار دیگه MNIST رو در نظر بگیرید: بیشتر عکس‌های دست‌نویس اعداد شباهت‌هایی به هم دارن. همشون از خطوط متصل به‌هم ساخته شدن، حاشیه‌ها سفید هستند و تقریبا در مرکز قرار دارن. اگر به‌صورت تصادفی عکس‌هایی تولید کنید، فقط یک کسر بسیار کمی از اون‌ها شبیه به اعداد دست‌نویس خواهند شد. به عبارت دیگه، اگر بخواید عکس یک عدد رو درست کنید، نسبت به موقعی که بخواید هر تصویری درست کنید، درجه‌ی آزادی کمتری خواهید داشت. این محدودیت‌ها باعث میشن که مجموعه‌ی داده به یک منیفلد با ابعاد کمتر، فشرده بشن.فرض منیفلد اغلب با یک فرض ضمنی دیگه هم همراه هست: کاری که میخوایم انجام بدیم ( طبقه‌بندی یا رگرسیون ) آسون تر خواهد شد اگر در فضای ِ با بعد ِ کمتر ِ منیفلد بیان بشه. برای مثال در شکل زیر، مجموعه‌ی داده تبدیل به دو کلاس شده: در فضای 3 بعدی ( سمت چپ ) مرز تصمیم بسیار پیچیده خواهد بود اما در فضای منیفلد 2 بعدی ( سمت راست ) مرز تصمیم گیری یک خط صاف خواهد بود.اگرچه این فرض ضمنی همیشه صادق نیست. برای مثال، در ردیف آخر همین شکل مرز تصمیم در x_1 = 5 واقع شده. این مرز تصمیم در فضای 3 بعدی بسیار ساده به‌نظر میاد ( یک خط عمودی ) اما در منیفلد پیچیده‌تر هست (مجموعه‌ای از 4 خط مستقل از هم )به‌طور خلاصه، کاهش ابعاد مجموعه‌ی داده قبل از آموزش مدل، معمولا سرعت رو افزایش میده اما همیشه ما رو به یک راه حل بهتر یا ساده‌تر نمیرسونه؛ همه چیز بسته به مجموعه‌ی داده داره.خوشبختانه حالا به درک خوبی از Curse of Dimensionality و طرز مقابله‌ی الگوریتم‌ها با اون‌ها رسیدید؛ مخصوصا وقتی که فرض منیفلد صادق هست. بقیه‌ی این مطلب درباره‌ی الگوریتم‌های محبوب هست.تحلیل مولفه‌های اصلی (PCAPCAPCA)تحلیل مولفه‌های اصلی یا Principal Component Analysis محبوب‌ترین الگوریتم برای کاهش ابعاد هست. PCA ابتدا ابرصفحه‌ای رو که در نزدیک‌ترین فاصله با داده‌ها قرار داره شناسایی می‌کنه و بعد داده‌هارو به اون تصویر میکنه؛ درست مثل شکل 2-8حفظ واریانسقبل از اینکه بتونید مجموعه‌ی داده‌های آموزشی رو به یک ابرصفحه‌ی با ابعاد پایین‌تر تصویر کنید، ابتدا باید ابرصفحه‌ی مناسب رو انتخاب کنید. برای مثال، در شکل پایین سمت چپ یک مجموعه‌ی داده‌ی 2 بعدی با 3 محور مختلف نمایش داده شده ( ابرصفحه‌های 1 بعدی ) در سمت راست نتیجه‌ی تصویر مجموعه‌ی داده به هر محور قرار داره. همونطور که می‌بینید، تصویر به خط تو پر بیشترین واریانس رو حفظ می‌کنه، درحالیکه تصویر به خط نقطه‌ای واریانس کمی رو حفظ می‌کنه و تصویر به خط بریده مقدار متوسطی از واریانس رو حفظ می‌کنه.منطقی هست که محورهایی رو انتخاب کنیم که بیشترین مقدار واریانس رو حفظ می‌کنه چون به نسبت بقیه تصویرها، اطلاعات کمتری رو از دست خواهیم داد. یک راه دیگه برای توجیه این انتخاب اینه که این محور محوری هست که فاصله‌ی میانگین مربعات رو بین مجموعه‌ی داده‌ی اصلی و تصویر رو به حداقل می‌رسونه. این ایده‌ی ساده‌ی پشت PCA هست.اجزای اصلی (Principal Components)روش PCA محوری رو شناسایی می‌کنه که بیشترین مقدار واریانس رو در مجموعه‌ی داده‌ی آموزشی داره. در شکل بالا این محور، خط تو پر هست. همچنین یک محور دوم هم پیدا می‌کنه که قائم بر محور اول هست. این محور دوم نشون دهنده‌ی بیشترین مقدار واریانس باقی مونده هست. در این مثال 2 بعدی انتخاب دیگه‌ای وجود نداره: خط نقطه‌ای هست. اگر مجموعه‌ی داده ابعاد بیشتری داشت، PCA یک محور سوم هم انتخاب می‌کرد که قائم بر دو محور قبلی بود و یک محور چهارم و یک محور پنجم و الی آخر؛ تا تعداد محورها برابر ابعاد مجموعه‌ی داده بشه.به i-امین محور میگن i-امین جزء اصلی داده (i^th Principal Component) در شکل بالا اولین PC محوری هست که بردار c_1 روی اون قرار داره و دومین PC محوری هست که بردار c_2 روی اون قرار داره. در شکل 2-8 دو PC اول محورهای عمودی هستند که دو پیکان روی اون‌ها قرار داره و سومین PC محور عمودی‌ روی صفحه هست.برای هر جزء اصلی، PCA یک بردار واحد به مرکز صفر پیدا می‌کنه که در جهت PC هست. از اونجایی که دو بردار واحد مخالف در یک محور قرار دارند، جهت بردارهای واحدی که PCA برمی‌گردونه ثابت نیست: اگر کمی مجموعه‌ی داده‌های آموزشی رو کمی تغییر بدید و دوباره PCA رو اجرا کنید، بردارهای واحد ممکنه در جهت مخالف بردارهای اصلی قرار بگیرن. اگرچه، اون‌ها عموماً همچنان روی یک محور یکسان قرار خواهند گرفت. در بعضی مواقع، جفت‌هایی از بردارهای واحد ممکنه حتی بچرخن یا جابجا بشن ( اگر واریانس بین این دو محور نزدیک هم باشن ) اما صفحه‌ای که تعریف می‌کنن عموماً یکسان باقی می‌مونه.خب حالا چطور می‌تونیم اجزای اصلی یک مجموعه‌ی داده‌ی آموزشی رو پیدا کرد؟ خوشبختانه یک تکنیک فاکتورگیری استاندارد از ماتریس وجود داره به نام Singular Value Decomposition (SVD) که می‌تونه ماتریس مجموعه‌ی آموزشی X رو تجزیه کنه به ضرب 3 ماتریس U, E, V که V شامل بردارهای واحدی هست که تمام اجزای اصلی رو تعریف می‌کنن. این ماتریس رو می‌تونید در زیر ببینید:کدی که در زیر می‌بینید از تابع svd در کتابخانه‌ی NumPy استفاده می‌کنه تا تمام اجزای اصلی مجموعه‌ی آموزشی رو بدست بیاره بعد دو بردار واحدی رو که دو جز اصلی اول رو تعریف می‌کنن، استخراج می‌کنه:X_centered = X - X.mean(axis=0)
U, s, Vt = np.linalg.svd(X_centered)
c1 = Vt.T[:, 0]
c2 = Vt.T[:, 1]روش PCA فرض می‌کنه که مجموعه‌ی داده در اطراف مبدا قرار دارد. خواهیم دید که کلاس PCA در Scikit-Learn خودش در مرکز بودن داده‌ها رو مدیریت می‌کنه. اگر PCA رو خودتون پیاده‌سازی می‌کنید یا اگر از کتابخانه‌های دیگه استفاده می‌کنید، فراموش نکنید که ابتدا داده‌ها در مرکز قرار گرفته باشن.تصویر کردن به d بعدوقتی همه‌ی اجزای اصلی رو شناسایی کردید، می‌تونید ابعاد مجموعه‌ی داده رو به d بعد کاهش بدید. این کار با تصویر کردن مجموعه‌ی داده به ابرصفحه‌ای که با d جز اصلی اول تعریف شدند، انجام‌پذیر هست. انتخاب این ابرصفحه تضمین می‌کنه که این تصویر تا جای ممکن واریانس رو حفظ می‌کنه. برای مثال در تصویر 2-8 که مجموعه‌ی داده‌ی 3 بعدی به ابرصفحه‌ی 2 بعدی که با دو جزء اصلی اول تعریف شده، تصویر شده، قسمت بزرگی از واریانس رو حفظ می‌کنه. به همین خاطر، تصویر 2 بعدی شباهت زیادی به مجموعه‌ داده‌ی 3 بعدی داره.برای تصویر داده‌ها به ابرصفحه و به‌دست آوردن یک مجموعه‌ی داده‌ی کاهش یافته به نام X_d-proj با d-بعد، ضرب ماتریسی مجموعه داده X رو با W_d محاسبه کنید. W_d یعنی d ستون اول ماتریس Vکد زیر داده‌ها رو به صفحه‌ای که توسط دو جزء اصلی اول تعریف شده، تصویر می‌کنه.W2 = Vt.T[:, :2]
X2D = X_centered.dot(W2)این هم از این! حالا یاد گرفتید که ابعاد هر مجموعه داده‌ای رو به هر تعداد بعدی کاهش بدید و همزمان، واریانس رو هم حفظ کنید.استفاده از Scikit-Learnکلاس PCA کتابخانه Scikit-Learn از تجزیه‌ی SVD برای پیاده‌سازی PCA استفاده می‌کنه، مثل کاری که قبلا اینجا انجام دادیم. کد زیر PCA رو برای کاهش ابعاد مجموعه داده به 2 بعد، اعمال می‌کنه. ( دقت کنید که به صورت خودکار داده‌ها رو به مرکز میاره )from sklearn.decomposition import PCA

pca = PCA(n_components = 2)
X2D = pca.fit_transofrm(X)Explained Variance Ratioیکی دیگه از اطلاعات به‌درد بخور، Explained Variance Ratio برای هر جزء اصلی هست که از طریق explained_variance_ratio_ قابل دسترسی هست. این نسبت نشون‌دهنده‌ی نسبت واریانس مجموعه‌ی داده‌ها هست که در طول هر جزء اصلی قرار داره. برای مثال بیاید این مقدار رو برای دو جز اصلی اول شکل 2-8 بررسی کنیم:pca.explained_variance_ratio_
# array([0.84248607, 0.14631839])این خروجی یعنی 84.2 درصد واریانس داده‌ها در طول اولین PC و 14.6 درصد داده‌ها در طول PC دوم قرار داره. باقی‌مونده میشه 1.2 درصد که برای PC سوم هست. پس میشه نتیجه گرفت که PC سوم حاوی اطلاعات کمی هست.انتخاب تعداد بعد مناسببه‌جای انتخاب خودسرانه‌ی تعداد ابعاد مورد نظر برای کاهش، ساده تره که تعدادی از ابعاد رو انتخاب کنیم که انتخاب اون‌ها باعث میشه مقدار زیادی به واریانس اضافه بشه. ( برای مثال 95درصد ) مگر اینکه هدف شما از کاهش ابعاد، مصورسازی داده هست؛ که در اون صورت بهتره که ابعاد رو به 2 یا 3 بعد کاهش بدید.کد زیر PCA رو بدون کاهش ابعاد اجرا می‌کنه، سپس کم‌ترین تعداد ابعاد لازم رو برای حفظ 95 درصد از واریانس مجموعه‌ی داده، محاسبه می‌کنه:pca = PCA()
pca.fit(X_train)
cumsum = np.cumsum(pca.explained_variance_ratio_)
d = np.argmax(cumsum &gt;= 0.95) + 1بعد می‌تونید n_components=d قرار بدید و PCA رو دوباره اجرا کنید. اما یک گزینه‌ی بهتر هم هست: به‌جای اینکه تعداد اجزای اصلی رو مشخص کنید، می‌تونید n_components رو برابر یک مقدار اعشاری بین0.0 و 1.0 قرار بدید که نشون‌دهنده‌ی واریانسی هست که می‌خواید حفظ بشه:pca = PCA(n_components = 0.95)
X_reduced = pca.fit_transform(X_train)یه گزینه‌ی دیگه هم اینه‌که نمودار Eplained Variance رو برای تعداد ابعاد رسم کنیم ( یعنی رسم cumsum - شکل 8-8) معمولاً یک آرنج‌شکل در منحنی ایجاد خواهد شد، جایی که رشد سریع EV متوقف میشه. در این حالت، می‌بینید که با کاهش ابعاد تا حدود 100 تا، EV زیادی رو از دست نخواهد رفت.استفاده از PCA برای فشرده‌سازیبعد از کاهش ابعاد، مجموعه‌ی داده‌ی آموزشی فضای کمتری خواهد گرفت. برای مثال، PCA رو روی MNIST با حفظ واریانس 95 درصد امتحان کنید. ملاحظه خواهید کرد که هر نمونه کمی بیش از 150 فیچر خواهد داشت، به‌جای 784 فیچر اصلی. پس، درحالیکه بیشتر واریانس حفظ شده، حالا مجموعه‌ی داده‌های ما 20 درصد اندازه اصلی هست! این یک نسبت فشرده‌سازی معقول هست و می‌تونید ببینید که این کاهش اندازه چقدر یک الگوریتم طبقه‌بندی رو سریع‌تر میکنه. ( مثل SVM )همچنین این امکان وجود داره که این مجموعه‌ی داده‌ی فشرده شده رو نافشرده کرد تا به 784 بعد اصلی برسه. این کار با تبدیل معکوس تصویر PCA امکان پذیر هست. با این کار مجموعه‌ی داده‌ی اصلی برنخواهد گشت چون با تصویر کردن مقداری از اطلاعات رو از دست خواهیم داد ( با اون 5 درصد واریانسی که حذف کردیم ) اما احتمالا شبیه به نسخه‌ی اصلی خواهد بود. فاصله‌ی مربع میانگین بین داده‌های اصلی و داده‌های بازسازی شده ( قبل و بعد از فشرده‌سازی ) Reconstruction Error یا خطای بازسازی نام داره.کد زیر MNIST رو به 154 بعد کاهش میده و سپس با استفاده از تابع inverse_transofrm اون رو 784 بعد برمی‌گردونیم:pca = PCA( n_components = 154 )
X_reduced = pca.fit_transform(X_Train)
X_recovered = pca.inverse_transform(X_reduced)شکل 9-8 تعدادی عدد رو از مجموعه‌ی داده‌ی اصلی ( در سمت چپ ) و عدد متناظر بعد از فشرده‌سازی و نافشرده‌سازی رو نشون میده. ملاحظه می‌کنید که کمی کاهش کیفیت در عکس‌ها وجود داره اما اعداد هم‌چنان اکثرا دست‌نخورده هستند.معادله تبدیل معکوس رو در زیر می‌بینید:Randomized PCAاگر هایپرپارامتر svd_solver رو برابر &quot;randomized&quot; قرار بدید، Scikit-Learn از یک الگوریتم تصادفی به نام Randomized PCA استفاده می‌کنه که به‌سرعت تعداد حدودی اولین d جزء اصلی رو پیدا می‌کنه. پیچیدگی محاسباتی اون برابر O(m × d^2) + O(d^3) هست. پیچیدگی محاسباتی روش SVD کامل برابر (m × n^2) + O(n^3) هست. پس اگر d خیلی کوچکتر از n باشه این روش سریع‌تر هست:rnd_pca = PCA(n_components = 154, svd_solver = &amp;quotrandomized&amp;quot)
X_reduced = rnd_pca.fit_transform(X_train)به‌صورت دیفالت، svd_solver=&quot;auto&quot; هست: اگر m یا n بزرگتر از 500 و d کمتر از 80 درصد از m یا n باشه، Scikit-Learn از Randomized PCA استفاده خواهد کرد و در غیر این صورت از Full SVD استفاده خواهد کرد. اگر می‌خواهید کتابخونه رو مجبور کنید که از Full SVD استفاده کنه، می‌تونید svd_solver=&quot;full&quot; قرار بدید.Incremental PCAیک مشکل با پیاده‌سازی قبلی PCA اینه که برای اجرای الگوریتم ابتدا باید تمام مجموعه‌ی داده در حافظه قرار بگیره. خوشبختانه الگوریتم‌های PCA افزایشی یا Incremental PCA برای رفع این مشکل گسترش یافته. این الگوریتم‌ها به ما اجازه میدن تا مجموعه‌ی داده رو به چند دسته‌ی کوچک تقسیم کنیم و به یک الگوریتم IPCA هر دفعه یک دسته‌ی کوچک بدیم. این برای مجموعه‌ی داده‌های بزرگ و پیاده‌سازی PCA به‌صورت برخط کاربرد داره. ( بر خطی یعنی همون لحظه که نمونه‌های جدید می‌رسن )کد زیر مجموعه‌ی داده MNIST رو به 100 دسته‌ی کوچک تقسیم می‌کنه ( با استفاده از تابع array_split ) و اون‌ها رو وارد کلاس IncrementalPCA می‌کنه تا ابعاد MNIST رو به 154 تا کاهش بده ( مثل قبل ) دقت کنید که باید به‌جای تابع fit که با کل مجموعه‌ی داده سروکار داشت، تابع partial_fit رو فراخوانی کنید که مناسب داده‌های دسته‌ای هست:from sklearn.decomposition import IncrementalPCA

n_batches = 100
inc_pca = IncrementalPCA(n_components = 154)
for X_batch in np.array_split(X_train, n_batches):
         inc_pca.partial_fit(X_batch)
X_reduced = inc_pca.transform(X_train)به‌عنوان گزینه‌ی دیگه می‌تونید از کلاس memmap کتابخونه‌ی NumPy استفاده کنید که به شما اجازه میده یک آرایه‌ی بزرگ رو که در یک فایل باینری روی دیسک ذخیره شده، دستکاری کنید، طوری که انگار تمام اون در حافظه قرار داره؛ این کلاس در صورت نیاز، فقط داده‌‌های مورد نیاز رو در حافظه بارگزاری می‌کنه. از اونجایی که کلاس IncrementalPCA فقط از قسمت کوچکی از آرایه استفاده می‌کنه، مصرف حافظه تحت کنترل قرار می‌گیره. این کار این امکان رو میده که از تابع fit استفاده کنیمX_mm = np.memmap(filname, dtype=&amp;quotfloat32&amp;quot, mode=&amp;quotreadonly&amp;quot, shape=(m,n))

batch_size = m // n_batches
inc_pca = IncrementalPCA(n_components=154, batch_size=batch_size)
inc_pca.fit(X_mm)Kernel PCAدر فصل 5 درباره Kernel Trick خوندیم: یک روش ریاضیاتی برای نگاشت نمونه‌ها به یک فضای با ابعاد بالا ( به نام فضای فیچر ) این روش اجازه می‌داد که طبقه‌بندی‌های غیرخطی و رگرسیون با SVM انجام بشه. به‌یاد بیارید که یک مرز تصمیم خطی در یک فضای فیچر با ابعاد بالا برابر هست با یک مرز تصمیم پیچیده‌ی غیر خطی در فضای اصلی.اینطور که پیداست از همین روش میشه توی PCA هم استفاده کرد که این امکان رو فراهم می‌کنه که برای کاهش ابعاد، از تصویرهای پیچیده‌ی غیرخطی استفاده کرد. به این روش می‌گن Kernel PCA یا kPCA. این روش اغلب اوقات در حفظ خوشه‌های نمونه‌ها بعد از تصویر و بعضی اوقات باز کردن مجموعه‌ی داده‌هایی که در نزدیکی یک منیفلد پیچ‌خورده هستند، خوب عمل می‌کنه.کد زیر از کلاس KernelPCA برای پیاده‌سازی kPCA با RBF Kernel استفاده می‌کنه.from sklearn.decomposition import KernelPCA
rbf_pca = KernelPCA(n_components = 2, kernel = &amp;quotrbf&amp;quot, gamma = 0.04)
X_reduced = rbd_pca.fit_transform(X)شکل زیر مجموعه‌ی داده‌ی Swill Roll رو نشون میده که با استفاده از یک کرنل خطی ( معادل با کلاس PCA عادی )، یک RBF Kernel و یک Sigmoid Kernel به دو بعد کاهش یافته.انتخاب یک کرنل و تنظیم هایپرپارامترهااز اونجایی که kPCA یک الگوریتم یادگیری بدون نظارت هست، برای انتخاب بهترین کرنل و هایپرپارامترها هیچ روش اندازه‌گیری عملکردی وجود نداره. از اونجایی که کاهش ابعاد معمولاً یک مرحله‌ی آماده‌سازی برای یادگیری با نظارت هست، می‌تونید از Grid Search برای پیدا کردن کرنل و هایپرپارامترهایی که عملکرد رو افزایش میدن، استفاده کنید. کد زیر یک پایپلاین دو مرحله‌ای درست می‌کنه، ابتدا ابعاد رو به 2 بعد با استفاده از kPCA کاهش میده و سپس برای طبقه‌بندی از Logistic Regression استفاده می‌کنه. بعد از GridSearchCV برای پیدا کردن بهترین کرنل و gamma برای kPCA استفاده می‌کنه:from sklearn.model_selection import GridSearchCV
from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import Pipeline

clf = Pipeline([
     (&amp;quotkpca&amp;quot, KernelPCA(n_components=2)),
     (&amp;quotlog_reg&amp;quot, LogisticRegresion())
])param_grid = [{     &amp;quotkpca__gamma&amp;quot:np.linspace(0.03, 0.05, 10),
      &amp;quotkpca__kernel&amp;quot:[&amp;quotrbf&amp;quot, &amp;quotsigmoid&amp;quot]}]grid_search = GridSeachCV(clf, param_grid, cv=3)
grid_search.fit(X, y)از طریق best_params_ هم می‌تونید بهترین هایپرپارامتر و کرنل رو ببینید:grid_search.best_params_
# {&#039;kpca__gamma&#039;: 0.043333333333333335, &#039;kpca__kernel&#039;: &#039;rbf&#039;}یک روش بدون نظارت هم اینه که کرنل و هایپرپارامترهایی رو که کمترین نرخ بازسازی رو دارن انتخاب کنیم. دقت کنید که بازسازی به سادگی PCA خطی نیست. علتش رو در شکل 11-8 می‌بینید. در بالا سمت چپ می‌تونید مجموعه‌ی داده‌ی اصلی رو ببینید. در بالا سمت راست مجموعه‌ی داده‌ی 2 بعدی بعد از اعمال kPCA با RBF Kernel هست. به لطف Kernel Trick این تبدیل از لحاظ ریاضی معادل هست با استفاده از تابع نگاشت فی برای نگاشت نمونه‌های آموزشی به یک فضای فیچر بی‌نهایت‌بعدی ( پایین سمت راست ) و سپس تصویر مجموعه‌ی داده‌ی آموزشی به 2 بعد با استفاده از PCA خطی.دقت کنید که اگه می‌تونستیم مرحله‌ی PCA خطی رو برای یک نمونه‌ی داده شده در فضای کاهش یافته، معکوس کنیم، نقطه‌ی بازسازی شده در فضای فیچر قرار می‌گرفت نه در فضای اصلی. ( به‌عنوان مثال نقطه‌ای که با X نشون داده شده ) از اون‌جایی که فضای فیچر بی‌نهایت‌-بعدی هست، ما نمی‌تونیم نقطه‌ی بازسازی شده رو محاسبه کنیم و در نتیجه نمی‌تونیم مقدار حقیقی نرخ بازسازی رو محاسبه کنیم. خوش‌بختانه، این امکان هست که یک نقطه رو در فضای اصلی پیدا کنیم که نگاشت نزدیکی به نقطه‌ی بازسازی شده داشته باشه. این نقطه پیش-تصویر بازسازی نام داره. وقتی این پیش-تصویر رو داشته باشید، می‌تونید فاصله‌ی مربع اون رو با مجموعه‌ی داده‌ی اصلی محاسبه کنید. بعد می‌تونید کرنل و هایپرپارامترهایی رو انتخاب کنید که این خطای پیش-تصویر بازسازی رو به حداقل برسونه.ممکنه براتون سوال پیش بیاد که چطور این بازسازی رو انجام بدیم. یک راه آموزش یک مدل رگرسیون با استفاده از نمونه‌های تصویر شده به‌عنوان مجموعه‌ی داده‌ی آموزشی و نمونه‌های اصلی به‌عنوان اهداف. اگر fit_inverse_transform=True قرار بدید، این کار به‌صورت خودکار اتفاق میفته:rbf_pca = KernelPCA(n_components = 2, kernel = &amp;quotrbf&amp;quot, gamma=0.0433, 
                     fit_inverse_transform = True)
X_reduced = rbf_pca.fit_transform(X)
X_preimage = rbf_pca.inverse_transform(X_reduced)به‌صورت دیفالت fit_inverse_transform=False هست و KernelPCA هیچ تابعی به نام inverse_transform نداره. این تابع فقط موقعی ایجاد میشه که fit_inverse_transform=True باشه.میزان خطای پیش-تصویر بازسازی رو می‌تونید با کد زیر محاسبه کنید:from sklearn.metrics import mean_squared_error

mean_squared_eror(X, X_preimage)
# 32.786308795766132حالا می‌تونید از Grid Search با Cross-Validation استفاده کنید تا کرنل و هایپرپارامتری رو پیدا کنید که این خطا رو به حداقل می‌رسونه.LLEیک تکنیک غیرخطی قدرتمند دیگه برای کاهش بعد(NLDR)، Locally Linear Embedding(LLE) نام داره. این یک تکنیک Manifold Learning هست که برخلاف الگوریتم‌های دیگه، متکی بر تصویر کردن نیست. به‌طور خلاصه، LLE با اندازه‌گیری نحوه‌ی ارتباط خطی هر نمونه‌ی آموزشی با نزدیک‌ترین همسایه‌های خود کار می‌کنه. سپس دنبال یک نمایش با ابعاد کم از نمونه‌های آموزشی هست که در اون روابط محلی به بهترین شکل حفظ می‌شود.کد زیر از کلاس LocallyLinearEmbedding برای باز کردن Swiss Roll استفاده می‌کنه:from sklearn.manifold import LocallyLinearEmbedding

lle = LocallyLinearEmbedding(n_component = 2, n_neighbors = 10)
X_reduced = lle.fit_transofrm(X)مجموعه‌ی داده‌ی 2 بعدی به دست اومده رو در شکل 12-8 می‌بینید. همون‌طور که می‌بینید، این مجموعه‌ داده کاملا باز شده و فاصله‌ی بین نمونه‌ها به‌صورت محلی به خوبی حفظ شده. اگرچه، فاصله‌ها در یک مقیاس بزرگتر حفظ نشدند: سمت چپ شکل کشیده شده در حالی که سمت راست فشرده شده. با این حال LLE در مدل کردن منیفلد عملکرد خوبی داشته.طرز کار LLE به این صورت هست:برای هر نمونه‌ی آموزشی x^(i)، الگوریتم k تا نزدیک‌ترین همسایه‌ها رو شناسایی می‌کنه ( در کد قبل این مقدار 10 بود ) بعد سعی می‌کنه x^(i) رو به‌صورت یک تابع خطی از این همسایه‌ها بازسازی کنه. به‌طور دقیق‌تر، وزن‌های w_i,j رو طوری پیدا می‌کنه که مربع فاصله بین x^(i) و سامیشن w_i,jx^(j) از j=1 تا m تا جای ممکن کوچیک باشه؛ با فرض اینکه w_i,j=0 ، اگر x^(j) یکی از k نزدیک‌ترین همسایه‌های x^(i) نباشه. به‌این ترتیب اولین قدم LLE مسئله‌ی بهینه‌سازی محدود شده‌ای هست که در معادله‌ی 4-8 معرفی شده. در این معادله W ماتریس وزنی شامل تمام وزن‌های w_i,j هست. دومین اعمال محدودیت، نرمال کردن تمام وزن‌ها برای هر نمونه‌ی آموزشی x^(i) هست.بعد از این قدم، ماتریس وزن W که شامل وزن های w_i,j هست، رابطه‌های خطی میان نمونه‌های آموزشی رو رمزگذاری می‌کنه. دومین قدم نگاشت نمونه‌های آموزشی به یک فضای d-بعدی هست ( d &lt; n ) به‌طوری که تا جای ممکن این روابط محلی حفظ بشن. اگر z^(i) تصویر x^(i) در این فضای d-بعدی باشه، آنگاه می‌خوایم که فاصله‌ی مربع بین z^(i) و سامیشن w_i,jz^(j) از j=1 تا m تاجای ممکن کوچیک باشه. این ایده به مسئله‌ی بهینه‌سازی بدون محدودیتی منتهی میشه که در تساوی 5-8 می‌بینید. شباهت زیادی داره به قدم اول اما به‌جای اینکه نمونه‌هارو ثابت نگه داریم و وزن‌های مناسب رو پیدا کنیم، داریم برعکس این کار رو انجام میدیم: وزن‌هارو ثابت نگه می‌داریم و مکان مناسب تصویر نمونه‌هارو در فضای با ابعاد کم پیدا می‌کنیم. دقت کنید که  Z ماتریس شامل تمام z^(i) هاست.پیاده‌سازی LLE در Scikit-Learn این پیچیدگی محاسباتی رو داره: O(mlog(m)n log(k)) برای پیدا کردن k تا نزدیک ترین همسایه‌ها، O(mnk^3) برای بهینه‌سازی وزن‌ها و O(dm^2) برای ساختن نمایش‌های با ابعاد کم. متاسفانه m^2 باعث میشه که این الگوریتم عملکرد ضعیفی در مجموعه‌ی داده‌های بسیار بزرگ داشته باشه.دیگر تکنیک‌‌ها برای کاهش بعدتعداد زیادی تکنیک برای کاهش بعد وجود داره که چندتایی هم در Scikit-Learn موجود هستند. در اینجا چندتا از محبوب‌ترین‌هارو می‌بینید:Random Projection:همونطور که از اسم پیداست، با استفاده از یک تصویر خطی تصادفی، داده‌ها به یک فضای با بعد کم تصویر میشن. شاید عجیب باشه اما معلوم میشه که یک همچین تصویر تصادفی‌ای به احتمال زیاد فاصله‌هارو به‌خوبی حفظ می‌کنه؛ همونطور که William  B.  Johnson  و Joram  Lindenstrauss به‌صورت ریاضی در یک لم معروف نشون دادند. کیفیت این کاهش بعد بسته به تعداد نمونه‌ها و بعد مورد نظر داره اما به‌طرز حیرت آوری به بعد اولیه بستگی نداره. می‌تونید داکیومنت sklearn.random_projection رو برای اطلاعات بیشتر مطالعه کنید.Multidimensional Scaling (MDS):این روش همزمان با حفظ فاصله‌ی بین نمونه‌ها، بعد رو کاهش میده. Isomap:این روش با اتصال هر نمونه به نزدیک‌ترین همسایه یک گراف درست می‌کنه و همزمان با حفظ فاصله‌ی ژئودزیک بین نمونه‌ها، بعد رو کاهش میده.t-Distributed Stochastic Neighbor Embedding (t-SNE):این روش با کنار هم نگه‌داشتن نمونه‌های شبیه به هم و دور نگه‌داشتن نمونه‌های متفاوت از هم، بعد رو کاهش میده. این روش بیشتر برای مصورسازی کاربرد داره مخصوصا در مصورسازی خوشه‌هایی از نمونه‌ها در فضای با ابعاد بالا. ( برای مثال مصورسازی MNIST در 2 بعد )Linear Discriminant Analysis (LDA):یک الگوریتم طبقه‌بندی هست اما حین آموزش یادمیگیره که متمایزکننده‌ترین محورها رو بین کلاس‌ها پیدا کنه. بعد از این محورها میشه استفاده کرد تا یک ابرصفحه برای تصویر داده تعریف کرد. خوبی این روش این هست که تصویر داده تا جای ممکن کلاس ها رو از هم دور خواهد کرد پس LDA یک تکنیک خوب برای کاهش بعد قبل از اجرای یک الگوریتم طبقه‌بندی مثل SVM هست.در این تصویر نتیجه‌ی هرکدوم از این روش ها رو می‌بینید.</description>
                <category>مجتبی میر یعقوب زاده</category>
                <author>مجتبی میر یعقوب زاده</author>
                <pubDate>Mon, 29 Mar 2021 15:30:28 +0430</pubDate>
            </item>
                    <item>
                <title>بررسی الگوریتم‌های یادگیری تجمعی در ماشین لرنینگ</title>
                <link>https://virgool.io/@TabaMojj/%D8%A8%D8%B1%D8%B1%D8%B3%DB%8C-%D8%A7%D9%84%DA%AF%D9%88%D8%B1%DB%8C%D8%AA%D9%85-%D9%87%D8%A7%DB%8C-%DB%8C%D8%A7%D8%AF%DA%AF%DB%8C%D8%B1%DB%8C-%D8%AA%D8%AC%D9%85%D8%B9%DB%8C-%D8%AF%D8%B1-%D9%85%D8%A7%D8%B4%DB%8C%D9%86-%D9%84%D8%B1%D9%86%DB%8C%D9%86%DA%AF-wouiom3aruey</link>
                <description>بررسی الگوریتم‌های یادگیری تجمعی در ماشین لرنینگفرض کنید یک مسئله‌ی پیچیده رو به هزاران شخص رندوم بدیم و بعد جواب‌های اون‌ها رو تجمیع کنیم. در بسیاری از مواقع مشاهده خواهیم کرد که این جواب، بهتر از جواب یک شخص خبره هست. به این اتفاق میگن خرد جمع یا Wisdom of Crowd. به‌طور مشابه، اگر تخمین گروهی از Predictorها رو جمع کنید (مثلا Classifierها یا Regressorها) اغلب اوقات تخمین‌های بهتری رو نسبت به بهترین Predictor به‌دست میارید. به یک گروه از Predictorها میگن Ensemble و به همین خاطر به این تکنیک می‌گن Ensemble Learning و به یک الگوریتم Ensemble Learning میگن Ensemble Method.یک مثال از Ensemble Method آموزش یک گروه از Decision Tree Classifierها هست؛ به‌طوری که هر کدوم از اونها روی قسمت‌های رندوم و مختلف یک دیتاست آموزش دیده باشند. برای انجام تخمین، پیش‌بینی تمام درخت‌ها رو دریافت می‌کنیم و پیش‌بینی ای که بیشترین رای رو دریافت کنه به‌عنوان پیش‌بینی نهایی انتخاب میشه. به این روش میگن Random Forest یا جنگل تصادفی و علارغم سادگی‌ش، امروزه یکی از قوی‌ترین الگوریتم‌های ماشین لرنینگ به‌حساب میاد.همونطور که در فصل دوم گفته شد، موقعی از الگوریتم‌های Ensemble استفاده می‌کنیم که به آخر پروژه نزدیک شده باشیم؛ یعنی موقعی که چندتا Predictor خوب ساخته باشیم و حالا می‌خواهیم اون‌ها رو با هم ترکیب کنیم تا به یک Predictor خوب برسیم. در واقع راه‌حل‌های پیروز در مسابقات ماشین لرنینگ، اغلب شامل چند الگوریتم Ensemble هستند. (مثلا این مسابقه‌ی Netflix) در این فصل الگوریتم‌های Ensemble محبوب شامل Bagging، Boosting و Stacking و همچنین Random Forest رو بررسی خواهیم کرد.Voting Classifiersفرض کنید چندتا Classifier آموزش دادید و هر کدوم دقتی حدود 80 درصد دارند. شاید از Logistic Regression، SVM، Random Forest، K-Nearest Neighbor و ... استفاده کرده باشید.یک راه ساده برای درست کردن یک Classifier بهتر، ترکیب پیش‌بینی هر Classifier هست به‌طوری که هر پیش‌بینی‌ای که بیشترین رای رو داشت، به‌عنوان پیش‌بینی نهایی انتخاب بشه. این Majority-Vote Classifier (طبقه‌بندی کننده‌ای که تخمین رو با رای اکثریت انجام میده) Hard Voting Classifier نام داره.به‌طرز تعجب‌آوری، این Voting Classifier در اکثر اوقات دقت بیشتری نسبت به بهترین Classifier در Ensemble داره. در واقع، اگر همه‌ی Classifierها یک یادگیرنده‌ی ضعیف ( Weak Learner ) باشند (یعنی فقط مقدار کمی بهتر از رندوم حدس زدن عمل میکنن) تجمیع اون‌ها با هم دیگه یک یادگیرنده‌ی قوی ( Strong Learner ) رو تشکیل میده که دقت بالایی داره؛ با فرض اینکه تعداد یادگیرنده‌های ضعیف کافی باشن و به اندازه کافی هم متفاوت از همدیگه باشن.چطور همچین چیزی امکان داره؟ مقایسه‌ی زیر میتونه به روشن شدن این معما کمک بکنه. فرض کنید یک سکه‌ی معیوب دارید که احتمال شیر اومدن 51 درصد و احتمال خط اومدن 49 درصد هست. اگر این سکه رو 1000 بار بندازید، معمولا بیشتر یا کمتر از 510 تا شیر و 490 تا خط میارید. در نتیجه بیشتر شیر میارید. اگر از دید ریاضی به این مسئله نگاه کنیم، می‌بینید که احتمال رسیدن به تعداد شیرهای بیشتر بعد از 1000 بار تلاش، نزدیک به 75 درصد هست. هرچقدر بیشتر سکه رو پرتاب کنید، احتمال هم بیشتر میشه (برای مثال در ده هزار پرتاب، احتمال نزدیک به 97 درصد هست) دلیل این امر قانون اعداد بزرگ هست (Law of Large Numbers) : همینطور که به پرتاب سکه ادامه میدید، نسبت شیرها نزدیک و نزدیک‌تر میشه به احتمال شیر اومدن یعنی 51 درصد. شکل زیر پرتاب‌های 10 سری از سکه‌های معیوب رو نشون میده. ملاحظه می‌کنید که هرچقدر تعداد پرتاب‌ها افزایش پیدا میکنه، نسبت شیرها هم به 51درصد نزدیک میشه. در نهایت تمام 10 سری اونقدری به 51 درصد نزدیک میشن که همواره بالای 50 درصد هستن.به‌طور مشابه، فرض کنید یک مدل Ensemble ساختید که شامل 1000 Classifer میشه که هر کدوم 51 درصد اوقات درست هستند (در واقع فقط 1 درصد بهتر از رندوم حدس زدن بهتر عمل میکنن) حالا اگر با این مدل یک تخمین انجام بدید، می‌تونید امید داشته باشید که به دقت 75 درصد می‌رسید! اگرچه، این فقط مواردی صادق هست که همه Classifierها کاملا مستقل از هم باشند و خطاهای نامرتبط از هم داشته باشن (Uncorrelated Errors) که مشخصا در این مورد ما صدق نمی‌کنه چون همشون با یک ترینینگ دیتاست مشابه آموزش دیدند. در نتیجه احتمالا همشون یک نوع خطا رو انجام بدن و رای اکثریت برای کلاس اشتباه باشه که باعث بشه دقت مدل پایین بیاد.مدل‌های Ensemble در مواقعی بهترین عملکرد رو دارن که Predictorها تا جایی که امکان داره از هم مستقل باشند. یک راه برای رسیدن به Classifierهای متفاوت از هم، این هست که اون‌ها رو با الگوریتم‌های متفاوت آموزش بدیم. این باعث میشه شانس انجام‌ اشتباه‌های متفاوت بیشتر بشه و دقت مدل افزایش پیدا کنه.کدی که می‌بینید یک Voting Classifier رو در Scikit-Learn درست و ترین میکنه که شامل 3 Classifier متفاوت هست(دیتاست برای فصل پنجم هست):from sklearn.ensemble import RandomForestClassifier
from sklearn.ensemble import VottingClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.svm import SVClog_clf = LogisticRegression()
rnd_clf = RandomForestClassifier()
svm_clf = SVC()votting_clf =  VottingClassifier(    estimators = [(&#039;lr&#039;,log_clf), (&#039;rf&#039;,rnd_clf), (&#039;svc&#039;, svm_clf)], votting = &#039;hard&#039;)votting_clf.fit(X_train, y_train)
from sklearn.metricsimport accuracy_score
for clf in (log_clf, rnd_clf, svm_clf, voting_clf):
    clf.fit(X_train, y_train)
    y_pred = clf.predict(X_test)
    print(clf.__class__.__name__, accuracy_score(y_test, y_pred))

# LogisticRegression 0.864
# RandomForestClassifier 0.896
# SVC 0.888
# VotingClassifier 0.904همونطور که می‌بینید Voting Classifier تک تک Classifier هارو شکست میده.اگر تمام Classifierها قابلیت این رو داشتن که احتمال هر کلاس رو بهمون بدن (یعنی تابع predict_proba داشتن) اونوقت میتونستیم به Scikit-Learn بگیم که کلاسی رو که به‌طور میانگین احتمال بیشتری رو بین Classifierها داره به‌عنوان خروجی برگردونه. به این کار میگن Soft Voting. این کار معمولا عملکرد بهتری نسبت به Hard Voting داره چون وزن بیشتری رو به رای‌هایی میده که اعتماد بالایی دارن. تنها کاری که باید بکنید این هست که votting = &#x27;hard&#x27; رو به votting = &#x27;soft&#x27; تغییر بدید و مطمئن بشید که همه Classifierها می‌تونن احتمال هر کلاس رو به‌دست بیارن. این مورد در SVC در حالت دیفالت صادق نیست. به همین دلیل باید هایپرپارامتر probablity=True رو لحاظ کنید. این کار باعث میشه که کلاس SVC برای محاسبه احتمال کلاس‌ها از Cross-Validation استفاده کنه که باعث آرام شدن روند ترین میشه و یک تابع predict_proba هم اضافه میکنه. اگر این تغییر رو در کد بالا انجام بدید می‌بینید که دقت مدل به 91.2 درصد افزایش پیدا میکنه!Bagging and Pastingهمونطور که گفتیم، یکی از راه‌های رسیدن به مجموعه‌ای متنوع از Classifierها استفاده از الگوریتم‌های متفاوت هست. یک راه دیگه استفاده از یک الگوریتم یکسان برای تمام Predictorها و آموزش اون‌ها روی ترینینگ ست‌های مختلف هست. نمونه‌گیری (Sampling) با استفاده از جایگذاری، Bagging و بدون استفاده از جایگذاری Pasting نام داره. Bagging مخفف Bootstrap Aggregating هست.به‌عبارت دیگه، هردوی این روش‌ها اجازه می‌دن تا نمونه‌های ترینینگ چندین بار در چندین Predictor استفاده بشن اما فقط Bagging اجازه میده که نمونه‌ها چندین بار برای یک Predictor یکسان استفاده بشن. این توضیح‌هارو می‌تونید تو شکل زیر ببینید.وقتی تمام Predictorها آموزش دیدند، این گروه از Predictorها می‌تونن با تجمیع پیش‌بینی همه‌ی Predictorها یک پیش‌بینی برای یک نمونه انجام بدن. تابع تجمیع برای Classification  ، مد یا Statistical Mode نام داره (پیش‌بینی‌ای که بیشترین تکرار رو داره، مثل یک Hard Voting Classifier). برای Regression هم میانگین محاسبه میشه. اگر قرار بود هر کدوم از این Predictorها رو روی ترینینگ ست آموزش بدیم، خطای بیشتری داشتن اما تجمیع اون‌ها باعث میشه تا مقدار خطا و واریانس کم بشه.همونطور که در تصویر بالا می‌بینید، Predictorها رو میشه به‌صورت موازی، با استفاده از هسته‌های مختلف CPU یا در سرورهای متفاوت، آموزش داد. به‌طور مشابه، عملیات پیش‌بینی رو هم میشه به صورت موازی انجام داد. این یکی از دلایلی‌ هست که Bagging و Pasting جزء روش‌های محبوب به حساب میان: به‌خوبی مقیاس‌پذیر هستند.پیاده‌سازی Bagging و Pasting در Scikit-Learnکتابخانه Scikit-Learn یک API ساده برای Bagging و Pasting از طریق کلاس BaggingClassifier ارائه میده (برای رگرسیون هم BaggingRegressor) کدی که در زیر می‌بینید 500 Decision Tree رو آموزش میده. هر کدوم از اون‌ها روی 100 نمونه از نمونه‌های ترینینگ آموزش می‌بینه که به‌صورت رندوم و با جایگذاری نمونه‌گیری شدند. (این یک مثال از Bagging هست، برای استفاده از Pasting از هایپرپارامتر bootstrap=False استفاده کنید) هایپرپارامتر n_jobs به Scikit-Learn میگه که از چه تعداد هسته‌ی CPU برای آموزش و پیش‌بینی استفاده کنه. (قرار دادن 1- باعث میشه از تمام هسته‌ها استفاده کنه)from sklearn.ensemble import BaggingClassifier
from sklearn.tree import DecisionTreeClassifier
bag_clf = BaggingClassifier(
    DecisionTreeClassifier(), n_estimators=500,
    max_samples=100, bootstrap=True, n_jobs-1)
bag_clf.fit(X_train, y_train)
y_pred = bag_clf.predict(X_test)اگر Base Classifier (که در اینجا ما DecisionTreeClassifier قرار دادیم) بتونه احتمال هر کلاس رو محاسبه کنه، BaggingClassifier به‌صورت خودکار از Soft Voting استفاده میکنه، در غیر این صورت از Hard Voting. منظور از محاسبه احتمال هر کلاس این هست که تابع predict_proba داشته باشه.شکل زیر مقایسه‌ی بین مرز تصمیم یک Decision Tree  رو با 500 درخت در Ensemble نشون میده (همون کد قبلی) همونطور که می‌بینید عمومی‌سازی مدل Ensemble  بهتر از یک مدل مفرد هست: مدل Ensemble  سوگیری (Bias) یکسان اما واریانس کمتری داره (تقریبا همون مقدار خطا رو انجام میده اما مرز تصمیم‌گیری اون کمتر نامنظم هست)روش Bootstrap به زیرمجموعه‌هایی که هر Predictor با استفاده از اون‌ها آموزش می‌بینیه، تنوع بیشتری می‌بخشه؛ در نتیجه Bagging سوگیری بیشتری نسبت به Pasting داره ولی تنوع بیشتر همچنین باعث میشه که Predictorها کمتر با هم همبستگی داشته باشن در نتیجه واریانس این Ensemble کم میشه. به‌طور کلی، Bagging مدل بهتری رو ارائه میده و به همین علت معمولاً ترجیح داده میشه. اگرچه، اگر زمان و قدرت CPU زیادی دارید، می‌تونید از Cross-Validation استفاده کنید تا هردوی Bagging و Pasting رو مقایسه کنید و بهترین مدل رو انتخاب کنید.Out-of-Bag Evaluationدر روش Bagging بعضی از نمونه‌ها ممکنه بارها نمونه‌گیری بشن درحالیکه از بعضی‌ها اصلا استفاده نشه. به‌صورت خودکار، یک BaggingClassifier تعداد m نمونه رو با استفاده از جایگذاری آموزش میده (bootstrap=True) که m اندازه‌ی ترینینگ ست هست. این یعنی به‌طور میانگین 63 درصد نمونه‌های ترینینگ روی هر Predictor آموزش داده میشه. (هرچقدر m بیشتر میشه، نسبت نزدیک میشه به exp(-1) - 1 که حدودا برابر 63 درصد هست) باقی 37 درصد نمونه‌ها که نمونه‌گیری نمیشن، نمونه‌های Out-of-Bag(OOB) نام دارن. دقت کنید که برای همه‌ی Predictorها لزوما مقدار برابر 37 درصد نیست.از اونجایی که یک Predictor هیچوقت نمونه‌های OOB رو حین آموزش نمی‌بینه، میشه از اون‌ها برای ارزیابی مدل استفاده کرد؛ بدون اینکه یک Validation Set جدا بسازیم. میشه کل Ensemble رو هم با گرفتن میانگین از ارزیابی هر Predictor با OOB ، ارزیابی کرد.با استفاده از هایپرپارامتر oob_score=True به‌هنگام ساخت یک BaggingClassifier ، از Scikit-Learn می‌خوایم که به‌صورت خودکار یک OOB Evaluation هم بعد از تموم شدن فرایند آموزش انجام بده. از طریق oob_score_ هم میشه به این امتیاز دست پیدا کرد:bag_clf = BaggingClassifier(
    DecisionTreeClassifier(), n_estimators=500,
    oob_score = True, bootstrap=True, n_jobs-1)
bag_clf.fit(X_train, y_train)
bag_clf.oob_score_
# 0.90133333333333332اینطور که این OOB Evaluation میگه، این مدل احتمالاً به دقت 90.1 درصد روی تست ست برسه. ببینیم:from sklearn.metrics import accuracy_score
y_pred = bag_clf.predict(X_test)
accuracy_score(y_test, y_pred)
# 0.91200000000000003بعد از اجرای این کد می‌بینید که دقت مدل روی تست ست برابر 91.2 درصد هست !از طریق oob_decision_function_ هم میشه به تابع تصمیم OOB برای هر نمونه ترین دست پیدا کرد. در مثال ما چون Base Estimator یک تابع predict_proba داره، تابع تصمیم احتمال هر کلاس رو برای هر نمونه برمیگردونه. برای مثال OOB Evaluation تخمین میزنه که اولین نمونه ترین به احتمال 68.25 درصد متعلق به کلاس مثبت و به احتمال 31.75 درصد متعلق به دسته منفی هست.Random Patches and Random Subspacesکلاس BaggingClassifier از نمونه‌گیری فیچرها هم پشتیبانی میکنه. این نمونه‌گیری با استفاده از دو هایپرپارامتر کنترل میشه: max_features و bootstrap_features. طرز کارشون مثل max_samples و bootstrap هست اما به‌جای نمونه‌ها، برای فیچرها کاربرد دارن. با این کار، هر Predictor روی زیرمجموعه‌ای از فیچرها آموزش می‌بینه.این تکنیک در مواقعی پرکاربرد هست که داریم با ورودی‌هایی با ابعاد بالا کار می‌کنیم ( مثل عکس‌ها ) نمونه‌گیری از هردوی نمونه‌ها و فیچرها Random Patches Method نام داره. نگه داشتن همه نمونه‌های ترینینگ ( bootstrap=False, max_sample=1.0) و نمونه‌گیری از فیچرها (bootstrap_features=True and/or max_features &lt; 1.0) Random Subspaces method نام داره.نمونه‌گیری فیچرها باعث میشه تنوع Predictorها بیشتر هم بشه و این کار باعث میشه سوگیری بیشتر اما واریانس کمتری داشته باشیم.Random Forestsهمونطور که گفتیم، Random Forest یک گروه از درخت‌های تصمیم هست که عموماً با استفاده از روش Bagging آموزش دیده (بعضی اوقات هم Pasting) و معمولا هایپرپارامتر max_samples برابر تعداد نمونه‌های ترینینگ ست هست. به‌جای اینکه یک BaggingClassifier بسازیم و DecisionTreeClassifier رو به اون بدیم، می‌تونیم از کلاس RandomForestClassifier استفاده کنیم که هم راحت‌تر هست و هم برای درخت‌های تصمیم بهینه شده. (برای رگرسیون هم RandomForestRegressor وجود داره) کلاس BaggingClassifier در مواقعی کاربردی هست که بخواید یک گروه از مدل‌هایی جز درخت تصمیم استفاده کنیدکدی که در زیر می‌بینید از تمام CPU استفاده می‌کنه تا یک Random Forest Classifier رو با 500 درخت که هر کدوم به ماکسیموم 16 گره محدود شدن، آموزش بده:from sklearn.ensemble import RandomForestClassifier

rnd_clf = RandomForestClassifier(n_estimators=500, max_leaf_node = 16, n_jobs=-1)
rnd_clf.fit(X_train, y_train)

y_pred_rf = rnd_clf.predict(X_test)همونطور که انتظارش رو داشتیم، RandomForestClassifier تمام هایپرپارامترهای یک DecisionTreeClassifier رو داره که بتونیم رشد درخت رو کنترل کنیم و همینطور تمام هایپرپارامترهای BaggingClassifier تا بتونیم خود Ensemble رو کنترل کنیم.الگوریتم Random Forest به‌هنگام رشد درخت‌ها رندوم عمل می‌کنه؛ به‌جای اینکه به‌هنگام قطع یک گره، به‌دنبال بهترین فیچر باشه (مطلب درخت تصمیم رو ببینید) دنبال بهترین فیچر در میان زیرمجموعه‌ای از فیچرها می‌گرده. این الگوریتم تنوع درخت‌ها رو بالا می‌بره که باعث میشه سوگیری بیشتر و واریانس کمتری داشته باشیم، که عموما عملکرد مدل رو افزایش میده. BaggingClassifierای که می‌بینید تقریبا برابر RandomForestClassifier قبلی هست:bag_clf = BaggingClassifier(
    DecisionTreeClassifier(splitter=&amp;quotrandom&amp;quot, max_leaf_nodes=16),
    n_extimators=500, max_samples=1.0, bootstrap=True, n_jobs=-1)Extra-Treesدر Random Forest وقتی یک درخت در حال رشد هست، در هر گره فقط زیرمجموعه‌ای رندوم از فیچرها برای قطع شدن در نظر گرفته می‌شه. به‌جای جستجو برای بهترین آستانه‌ی ممکن، میشه با استفاده از آستانه‌های رندوم برای هر فیچر، درخت‌هارو رندوم‌تر هم کرد.جنگلی به این اندازه رندوم، Extremly Randomized Trees نام داره. (یا به‌طور خلاصه: Extra-Trees) این تکنیک هم سوگیری زیاد و واریانس کمی داره. این تکنیک هم‌چنین نسبت به Random Forest معمولی، در زمان کمتری آموزش می‌بینه چون پیدا کردن بهترین مرز برای هر فیچر در هر گره، یکی از زمان‌بر ترین کارها برای یک درخت در حال رشد هست.برای ساخت این مدل، از کلاس ExtraTreesClassifier استفاده می‌کنیم. این کلاس APIای شبیه به کلاس RandomForestClassifier هست. به‌طور مشابه ExtraTreesRegressor هم وجود داره که APIای شبیه به کلاس RandomForestRegressor داره.نمیشه گفت که RandomForestClassifier عملکرد بهتر یا بدتری نسبت به ExtraTreesClassifier داره. عموما تنها راه برای فهمیدن این موضوع، استفاده از هر دو و مقایسه‌ی اون‌ها با استفاده از Cross-Validation هست.Feature Importanceیکی دیگه از خصوصیت‌های Random Forest اینه که به راحتی میشه اهمیت نسبی هر فیچر رو اندازه‌گیری کرد. Scikit-Learn نگاه می‌کنه که گره‌های درختی که از یک ویژگی استفاده میکنه، چقدر به‌طور متوسط ناخالصی (Imputiry) رو کاهش میده(در تمام درخت‌های جنگل) و با این روش اهمیت یک فیچر رو محاسبه می‌کنه. به‌طور دقیق‌تر، این یک میانگین وزن‌دار هست که وزن هر گره برابر تعداد نمونه‌های ترینینگ مرتبط با اون هست.کتابخانه Scikit-Learn این امتیاز رو برای هر فیچر بعد از آموزش به‌صورت خودکار محاسبه میکنه و سپس این نتایج رو طوری مقیاس‌بندی میکنه که مجموع اهمیت‌ها برابر 1 بشه. این نتیجه رو می‌تونید با استفاده از feature_importances_ ببینید. کدی که می‌بینید یک RandomForestClassifier رو روی دیتاست Iris آموزش میده و اهمیت هر فیچر رو به‌عنوان خروجی میده. اینطور که پیداست پر اهمیت ترین فیچرها Petal Length و Width هست. در حالیکه کمترین فیچرها Sepal Length و Width هست.from sklearn.datasets import load_iris

iris = load_iris()
rnd_clf = RandomForestClassifier(n_extimators=500, n_jobs=-1)
rnd_clf.fit(iris[&#039;data&#039;], iris[&#039;target&#039;])
for name,score in zip(iris[&#039;feature_name&#039;], rnd_clf.feature_importances_):
    print(name, score)
#sepal length (cm) 0.112492250999
#sepal width (cm) 0.0231192882825
#petal length (cm) 0.441030464364
#petal width (cm) 0.423357996355به‌طور مشابه، اگر یک Random Forest روی دیتاست MNIST آموزش بدید و اهمیت هر پیکسل رو با نمودار نشون بدید، با این شکل روبرو می‌شید:اگر احتیاج دارید که از بین فیچرها انتخاب کنید، Random Forest یک راه سریع برای فهمیدن اهمیت فیچرهاست.Boostingروش Boosting (که در اصل Hypothesis Boosting نام داره)  به هر روش Ensembleای گفته میشه که می‌تونه چند یادگیرنده‌ی ضعیف رو با هم ترکیب کنه تا یک یادگیرنده‌ی قوی بسازه. ایده‌ی عمومی بیشتر روش‌های Boosting اینه‌که Predictorها به ترتیب آموزش ببینن که هر کدوم سعی می‌کنن اشتباهات پیشنیان رو درست کنن. روش‌های Boosting زیادی وجود داره اما تا اینجا محبوبترین ها AdaBoost (مخفف Adaptive Boosting) و Gradient Boosting هستن.AdaBoostیک روش برای یک Predictor برای اصلاح پیشنیان اینه که به نمونه‌های ترینینگ که پیشنیان آندرفیت می‌کنن توجه بیشتری بکنه. این باعث میشه که Predictorهای جدید توجه بیشتر و بیشتری روی نمونه‌های سخت بکنن. این تکنیکی هست که AdaBoost استفاده میکنه.برای مثال وقتی داریم یک AdaBoost Classifier آموزش میدیم، الگوریتم در ابتدا یک Base Classifier آموزش میده (مثلا یک درخت تصمیم) و از اون استفاده میکنه تا پیش‌بینی‌هایی روی مجموعه‌ی آموزشی انجام بده. بعد الگوریتم وزن نسبی نمونه‌های آموزشی‌ای که به اشتباه طبقه‌بندی شدن، افزایش میده. بعد یک Classifier دوم رو با استفاده از وزن‌های به‌روزرسانی شده آموزش میده و دوباره روی ترینینگ ست پیش‌بینی انجام میده، وزن نمونه‌هارو به‌روزرسانی میکنه و الی آخر.شکلی که مشاهده می‌کنید مرزهای تصمیم 5 Predictor پشت سر هم رو روی دیتاست Moons نشون میده. (در این مثال، هر Predictor یک SVM Classifier با RBF Kernel هست. البته این فقط برای نمایش هست. معمولا SVM ها Base Predictor خوبی برای AdaBoost به‌حساب نمیان چون آهسته هستند و با این الگوریتم ناپایداری نشون میدن) اولین Classifier توی نمونه‌های زیادی اشتباه می‌کنه، پس وزن اون‌ها افزایش پیدا میکنه. بخاطر همین دومین Classifier روی این نمونه‌ها نتیجه‌ی بهتری رو نشون میده و الی آخر. شکل سمت راست همین ترتیب از Predictorها رو نشون میده با این تفاوت که Learning Rate نصف شده (یا به عبارتی دیگه در هر تکرار، وزن نمونه‌های به‌اشتباه طبقه‌بندی شده یک دوم افزایش پیدا میکنه) همونطور که می‌بینید این تکنیک ِ یادگیری ِ ترتیبی، یک سری شباهت‌ها به Gradient Descent داره، به‌جز اینکه به‌جای اینکه پارامترهای یک Predictor رو دستکاری کنیم تا تابع هزینه کاهش پیدا کنه، AdaBoost یک سری Predictorها رو به Ensemble اضافه میکنه تا به‌تدریج نتیجه رو بهتر کنه.وقتی تمام Predictorها آموزش دیدن، Ensemble شبیه به Bagging یا Pasting پیش‌بینی رو انجام میده،اما با این تفاوت که بسته به دقت کلی Predictorها در ترینینگ ست وزن‌دار، وزن‌های متفاوتی خواهند داشت.این الگوریتم ترتیبی یک اشکال مهم داره:این الگوریتم رو نمیشه به‌صورت موازی یا جزء به جزء استفاده کرد چون هر Predictor بعد از آموزش و ارزیابی ِPredictor قبلی آموزش می‌بینه. به‌همین خاطر مثل Bagging یا Pasting مقیاس‌پذیر نیست.بیاید یک نگاه نزدیک‌تر به الگوریتم AdaBoost بندازیم.در ابتدا، وزن هر نمونه ( (i)^w ) برابر 1-^(m) هست. اولین Predictor آموزش می‌بینه و نرخ خطای وزن دار اون ( r_1 ) بر روی ترینینگ ست محاسبه میشه:حالا با استفاده از فرمول زیر وزن Predictor محاسبه میشه که اون رو با آلفای جِی نشون میدیم. علامت اِتا در اینجا هایپرپارامتر Learning Rate هست (دیفالت برابر 1) هرچقدر Predictor دقیق‌تر باشه، وزن‌ش هم بیشتر میشه. اگر به‌صورت رندوم پیش‌بینی‌هارو انجام بده، وزن اون نزدیک به صفر میشه. اگر در اکثر مواقع اشتباه کنه (یعنی دقتش حتی از حدس زدن رندوم هم کمتر باشه) مقدارش منفی میشه.این الگوریتم در قدم بعدی وزن نمونه‌هارو به‌روزرسانی میکنه. فرمول زیر وزن نمونه‌های اشتباه‌طبقه‌بندی‌شده رو به‌روزرسانی میکنه:بعد از اون تمام وزن‌ها نرمال میشن (یعنی تقسیم بر سامیشن همه وزن‌ها میشن)و در نهایت یک Predictor جدید با استفاده از وزن‌های به‌روزرسانی‌ شده آموزش می‌بینه و دوباره این روند تکرار میشه. ( وزن Predictor جدید محاسبه میشه، وزن نمونه‌ها به‌روزرسانی میشن و یک Predictor دیگه آموزش می‌بینه) هروقت به تعداد Predictor دلخواه رسیدیم یا هروقت به یک Predictor عالی رسیدیم، الگوریتم می‌ایسته.برای پیش‌بینی، AdaBoost پیش‌بینی‌های همه‌ی Predictorها رو محاسبه می‌کنه و با استفاده از وزن Predictor یعنی آلفا، به اون‌ها وزن میده. کلاس خروجی کلاسی هست که اکثریت به اون رای میدن:کتابخانه Scikit-Learn از ورژن چندکلاسی AdaBoost به نام SAMME استفاده می‌کنه (Stagewise Additive Modeling using a Multiclass Exponential loss function ) وقتی فقط دوتا کلاس داریم SAMME برابر با AdaBoost هست.اگر Predictorها بتونن احتمال هر کلاس رو محاسبه کنن (یعنی تابع predict_proba داشته باشن) Scikit-Learn می‌تونه از یک نوع SAMME به نام SAMME.R استفاده کنه (R مخفف Real هست) این نوع به‌جای تکیه بر پیش‌بینی‌ها، برپایه‌ی احتمالات هست و عموما بهتر عمل می‌کنه.کد زیر یک AdaBoost Classifier رو بر پایه‌ی 200 Decision Stump با استفاده از کلاس AdaBoostClassifier درست می‌کنه. Decision Stump یک درخت تصمیم هست که هایپرپارامتر max_depth=1 هست. به عبارت دیگه، یک درخت که شامل یک گره‌ی تصمیم به‌اضافه‌ی دو برگ هست. این Base Estimator دیفالت برای این کلاس هست. همونطور که انتظار دارید، یک کلاس AdaBoostRegressor هم وجود داره.from sklearn.ensemble import AdaBoostClassifier

ada_clf = AdaBoostClassifier(
    DecisionTreeClassifier(max_depth=1), n_estimators=200,
    algorithm=&amp;quotSAMME.R&amp;quot, learning_rate=0.5
    )

ada_clf.fit(X_train, y_train)اگر AdaBoost اورفیت شده، می‌تونید تعداد Estimatorها رو کم کنید یا Base Estimator رو Regularize کنید.Gradient Boostingیک الگوریتم Boosting دیگه که خیلی پرطرفدار هست، Gradient Boosting نام داره. مثل AdaBoost ، این الگوریتم هم با اضافه کردن پی در پی Predictor ها به یک Ensemble کار می‌کنه که هر کدوم از اون‌ها پیشنیان خودشون رو اصلاح می‌کنن. اما به‌جای اینکه مثل AdaBoost وزن هر نمونه رو در هر تکرار تنظیم کنه، این روش سعی می‌کنه Predictor جدید رو به Residual Error که Predictor قبلی درست کرده، فیت کنه.بذارید یک مثال رگرسیون ساده رو بررسی کنیم که توی اون Base Predictor یک درخت تصمیم هست (بله البته، Gradient Boosting از پس رگرسیون هم برمیاد) به این کار میگن Gradient Tree Boosting یا Gradient Boosted Regression Trees (GBRT). اول یک DecisionTreeRegressor رو با ترینینگ ست فیت می‌کنیم (برای مثال یک ترینینگ ست درجه دو که نویز داره):from sklearn.tree import DecisionTreeRegressor

tree_reg1 = DecisionTreeRegressor(max_depth=2)
tree_reg1.fit(x, y)بعد یک DecisionTreeRegressor دوم رو با Residual Error مدل قبلی درست می‌کنیم:y2 = y - tree_reg1.predict(X)
tree_reg2 = DecisionTreeRegressor(max_depth=2)
tree_reg2.fit(X, y2)حالا یک مدل سوم رو با Residual Error مدل قبلی درست می‌کنیم:y3 = y2 - tree_reg2.predict(X)
tree_reg3 = DecisionTreeRegressor(max_depth=2)
tree_reg3.fit(X, y3)
حالا یک Ensemble داریم که شامل 3 درخت هست. این مدل می‌تونه با جمع کردن پیش‌بینی 3 مدل، یک پیش‌بینی انجام بده:y_pred = sum(tree.predict(X_new) for tree in (tree_reg1, tree_reg2, tree_reg3))شکلی که پایین می‌بینید، پیش‌بینی این 3 درخت رو در ستون چپ و پیش‌بینی Ensemble رو در ستون راست نشون میده. در ردیف اول، Ensemble فقط یک درخته در نتیجه پیش‌بینی‌ اون دقیقا مثل اولین درخته. در ردیف دوم یک درخت جدید روی Residual Error درخت اول ایجاد شده. در سمت راست می‌بینید که پیش‌بینی Ensemble برابر مجموع پیش‌بینی‌های دو درخت قبلی هست. به‌طور مشابه، در ردیف سوم یک درخت دیگه روی Residual Error درخت دوم ایجاد شده. ملاحظه می‌کنید که پیش‌بینی‌های Ensemble به‌تدریج با اضافه شدن درخت‌ها، بهتر و بهتر میشه.یک راه ساده برای آموزش یک GBRT Ensemble استفاده از کلاس GradientBoostingRegressor هست.مثل کلاس RandomForestRegressor ، این کلاس هم هایپرپارامترهایی برای کنترل رشد درخت‌های تصمیم داره. (برای مثال، max_depth, min_samples_leaf) همینطور هایپرپارامترهایی برای کنترل آموزش Ensemble مثل تعداد درخت‌ها (n_estimators). کدی که می‌بینید همون Ensemble ای رو تولید می‌کنه که بالاتر درست کردیم:from sklearn.ensemble import GradientBoostingRegressor

gbrt = GradientBoostingRegressor(max_depth=2, n_estimators=3, learning_rate=1.0)
gbrt.fit(X, y)
هایپرپارامتر learning_rate میزان مشارکت هر درخت رو مقیاس‌بندی می‌کنه. اگر اون رو برابر یک مقدار کم مثل 0.1 قرار بدید، به درخت‌های بیشتری در Ensemble احتیاج خواهیم داشت تا روی ترینینگ ست فیت بشه اما معمولا در این حالت پیش‌بینی‌ها بهتر عمومی‌سازی میشن. این تکنیک Regularization میگن Shrinkage. شکل پایین دو GBRT Ensemble رو نشون میده که Learning Rate توی اون‌ها کم هست. مدل سمت چپ تعداد درخت‌های کافی نداره تا روی ترینینگ ست فیت بشه در حالیکه مدل سمت راست تعداد درخت‌های زیادی داره و ترینینگ ست رو اورفیت میکنه.برای پیدا کردن تعداد مناسبی از درخت‌ها، میتونیم از Early Stopping استفاده کنیم.(آشنایی با Early Stopping) یک راه ساده برای پیاده‌سازی این روش، استفاده از تابع staged_predict هست: این تابع یک Iterator روی پیش‌بینی‌هایی که Ensemble در هر مرحله از آموزش انجام داده، برمیگردونه.(با یک درخت، دو درخت و الی آخر) کد زیر یک GBRT Ensemble رو با 120 درخت آموزش میده، بعد Validation Error رو در هر مرحله از آموزش محاسبه می‌کنه تا تعداد بهینه‌ی درخت‌هارو پیدا کنه و در نهایت یک GBRT Ensemble رو با استفاده از تعداد درخت‌های بهینه‌ای که پیدا کرده، درست می‌کنه:import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error

X_train, X_val , y_train, y_val  = train_test_split(X, y)

gbrt = GradientBoostingRegressor(max_depth=2, n_estimators=120)
gbrt.fit(X_train, y_train)

errors = [mean_squared_error(y_val,y_pred) for y_pred in gbrt.staged_predict(X_val)]
best_n_estimators = np.argmin(errors) + 1

gbrt_best = GradientBoostingRegerssor(max_depth=2, n_estimators=best_n_estimators)
gbrt_best.fit(X_train, y_train)میزان خطاها در سمت چپ شکل و پیش‌بینی‌های بهترین مدل در سمت راست قرار دارن:همچنین میشه برای پیاده‌سازی Early Stopping به‌جای اینکه اول تعداد زیادی از درخت‌هارو آموزش بدیم و سپس بگردیم که بهترین تعداد درخت رو پیدا کنیم، فرآیند آموزش رو زودتر متوقف کنیم. استفاده از هایپرپارامتر warm_start = True باعث میشه Scikit-Learn وقتی تابع fit فراخوانی میشه، درخت‌های موجود رو نگه‌داره و امکان آموزش افزایشی یا Incremental Learning رو فراهم کنه. کد زیر هر وقت Validation Error برای 5 پیمایش پشت سر هم بهتر نشه، فرآیند آموزش رو متوقف می‌کنه:gbrt = GradientBoostingRegressor(max_depth = 2, warm_start = True)

min_val_error = float(&#039;inf&#039;)
error_going_up = 0

for n_estimators in range(1, 120):
    gbrt.n_estimators = n_estimators
    gbrt.fit(X_train, y_train)
    y_pred = gbrt.predict(X_val)
    val_error = mean_squared_error(y_val, y_pred)

    if val_error &lt; min_val_error:
        min_val_error = val_error
        error_going_up = 0
    else:
        error_going_up += 1
        if error_going_up == 5:
            break # early stoppingکلاس GradientBoostingRegressor همچنین از یک هایپرپارامتر به نام subsample پشتیبانی می‌کنه که این هایپرپارامتر کسری از نمونه‌های آموزشی رو مشخص می‌کنه که برای آموزش هر درخت باید استفاده بشه. برای مثال اگر subsample=0.25 باشه، هر درخت با 25درصد از نمونه‌های آموزشی آموزش می‌بینه که اون‌ها هم رندوم انتخاب میشن. همونطور که حدس زدید، این تکنیک سوگیری بالا و واریانس کمی داره. همچنین به طرز قابل توجهی هم فرآیند آموزش رو سریع می‌کنه. این روش Stochastic Gradient Boosting نام داره.همچنین این امکان وجود داره که از Gradient Boosting به‌همراه دیگر تابع‌های هزینه استفاده کرد. این کار رو میشه با استفاده از هایپرپارامتر loss انجام داددر اینجا نکته‌‌ی قابل توجه اینه که یک پیاده‌سازی بهینه‌ از Gradient Boosting در کتابخانه محبوب XGBoost وجود داره، که مخفف Extreme Gradient Boosting هست. در ابتدا این پکیج رو Tianqi Chen به‌عنوان قسمتی از Distributed(Deep) Machine Learning Community گسترش داد و هدفش این بود که فوق العاده سریع، مقیاس‌پذیر و قابل حمل باشه. در واقع XGBoost اغلب اوقات یکی از اجزای مهم برندگان مسابقات ماشین لرنینگ هست. XGBoost یک API شبیه به Scikit-Learn داره:import xgboost

xgb_reg = xgboost.XGBRegressor()
xgb_reg.fit(X_train, y_train)
y_pred = xgb_reg.predict(X_val)این کتابخونه همچنین یک سری ویژگی‌های خوب هم داره، مثل خودکار مدیریت کردن Early Stopping:xgb_reg.fit( X_train, y_train, eval_set=[(X_val, y_val)], early_stopping_rounds=2 )
y_pred = xgb_reg.predict(X_val)Stackingآخرین روش Ensemble که توی این فصل می‌خوایم بررسی کنیم، Stacking نام داره (کوتاه شده‌ی Stacked Generalization). این روش برپایه‌ی یک ایده‌ی ساده هست: به‌جای استفاده از تابع‌های اضافه‌ای (مثل Hard Voting) برای تجمیع پیش‌بینی همه‌ی Predictorها در یک Ensemble ، چرا یک مدل آموزش ندیم که این تجمیع رو انجام بده؟ شکلی که در پایین می‌بینید یک همچین Ensemble رو نشون میده که یک عملیات رگرسیون رو بر روی یک نمونه‌ی جدید انجام میده. هر کدوم از سه Predictor پایین یک مقدار متفاوت رو پیش‌بینی می‌کنه (3.1و 2.7 و 2.9) و Predictor نهایی (به نام Blender یا Meta Learner) این پیش‌بینی‌هارو به‌عنوان ورودی می‌گیره و پیش‌بینی نهایی رو انجام میده (3.0)یک راه عمومی برای آموزش یک Blender ، استفاده از Hold-Out Set هست. ببینیم چجوری کار می‌کنه. اول ترینینگ ست به دو زیرمجموعه تقسیم میشه. از اولین زیرمجموعه برای آموزش Predictorها در لایه‌ی اول استفاده میشه.بعد، از Predictorهای لایه‌ی اول استفاده میشه تا پیش‌بینی‌هایی روی زیرمجموعه‌ی دوم انجام بشه. با این کار اطمینان حاصل میشه که پیش‌بینی‌ها پاک هستن چونPredictorها هیچوقت حین آموزش این نمونه‌هارو ندیدن. برای هر نمونه در Hold-Out Set سه مقدار ِ پیش‌بینی شده وجود داره. می‌تونیم یک ترینینگ ست جدید با استفاده از این مقادیر پیش‌بینی‌شده به‌عنوان ورودی درست کنیم (که باعث میشه این ترینینگ ست 3 بعدی بشه) و مقادیر Target رو نگه داریم. Blender روی این ترینینگ ست آموزش می‌بینه، پس یاد می‌گیره که مقادیر Target رو با دریافت پیش‌بینی‌های لایه‌ی اول پیش‌بینی کنه.با این روش می‌تونیم چندین Blender متفاوت رو آموزش بدیم تا یک لایه‌ی کامل از Blenderها داشته باشیم. (مثلا یکی با استفاده از Linear Regression و یکی با استفاده از Radnom Forest Regression) نکته اینجاست که ترینینگ ست رو به سه زیرمجموعه تقسیم کنیم: از اولین زیرمجموعه استفاده میشه تا لایه‌ی اول آموزش ببینه، از دومی استفاده میشه تا یک ترینینگ ست برای آموزش لایه‌ی دوم درست بشه (با استفاده از پیش‌بینی‌هایی که توسط Predictorهای لایه‌ی اول انجام شده) و از سومی استفاده میشه تا یک ترینینگ ست برای آموزش لایه‌ی سوم درست بشه ( با استفاده از پیش‌بینی‌هایی که توسط Predictorهای لایه‌ی دوم انجام شده) وقتی این کار تموم بشه، می‌تونیم با رفتن به هر لایه به‌صورت ترتیبی، یک پیش‌بینی برای یک نمونه‌ی جدید انجام بدیم. در شکل پایین نشون داده شده.این هم از فصل ۷ کتاب Hands-On Machine Learning. امیدوارم مفید واقع شده باشه.</description>
                <category>مجتبی میر یعقوب زاده</category>
                <author>مجتبی میر یعقوب زاده</author>
                <pubDate>Mon, 29 Mar 2021 15:25:23 +0430</pubDate>
            </item>
                    <item>
                <title>آشنایی با الگوریتم‌های Weighted Majority</title>
                <link>https://virgool.io/@TabaMojj/%D8%A2%D8%B4%D9%86%D8%A7%DB%8C%DB%8C-%D8%A8%D8%A7-%D8%A7%D9%84%DA%AF%D9%88%D8%B1%DB%8C%D8%AA%D9%85%D9%87%D8%A7%DB%8C-weighted-majority-cugojyhuge6w</link>
                <description>با افزایش حجم داده‌ها در جهان، بهترین شیوه‌ی یادگیری استفاده از الگوریتم‌های یادگیری برخط است. روش‌های برخط گروهی (Online Ensemble Methods) الگوریتم‌هایی هستند که از یک گروه طبقه‌بندی کننده (Classifier) برای پیش‌بینی استفاده می‌کنند. در یادگیری برخط گروهی، پیش‌بینی با استفاده از خبره (Expert)، مسئله‌ای است که بسیار مورد مطالعه قرار گرفته است. الگوریتم‌های Weighted Majority و Randomized Weighted Majority از معروف‌ترین راه‌حل‌ها برای این مسئله هستند که هدف آن‌ها همگرایی به نظر بهترین خبره است. در میان خبرگان، آن‌هایی که عملکرد عالی‌ای دارند لزوماً در تمام مناطق فضای داده کمترین میزان خطا را ندارند؛ در نتیجه باید مناطق خاصی را تعریف کنیم و در هر یک از این مناطق، به خبره‌ای رجوع کنیم که بهترین عملکرد را دارد. در این مقاله می‌خواهیم این نقص الگوریتم‌های RWM را با ارائه یک نسخه‌ی آبشاری/دنباله‌ای حل کنیم. Cascading RWM نه تنها عملکرد خوبی دارد بلکه در دیتاست‌های بسیار بزرگ هم مقدار خطای کمی دارد.الگوریتم‌های اکثریت وزن‌دار در ماشین لرنینگاین مطلب فقط قسمت‌هایی از مقاله‌ی Cascading Randomized Weighted Majority را پوشش می‌دهد. برای مطالعه‌ی بیشتر به لینک مقاله مراجعه کنید.معرفیدر الگوریتم‌های یادگیری با نظارت، داده‌ها از قبل لیبل‌گذاری شده‌اند. در این الگوریتم‌ها هر نمونه یک لیبل دارد که نشان‌دهنده‌ی کلاسی است که آن داده متعلق به آن است. در یادگیری با نظارت هدف این است که یک فرضیه‌ی کلی از تعدادی نمونه‌ی لیبل‌شده به منظور پیش‌بینی استخراج کنیم. هر الگوریتم از تعدادی فرض استفاده می‌کند؛ در نتیجه در بعضی از زمینه‌ها خوب عمل می‌کند و در بعضی دیگر عملکرد مناسبی ندارد. در نتیجه، برای افزایش عملکرد دسته‌بندی، Classifier هارا با هم ترکیب می‌کنیم. چارچوب پیش‌بینی با خبره سعی می‌کند در میان دسته‌ای از خبرگان، به آن‌هایی همگرا شود که کمترین میزان خطا یا Misclassification را دارد. این مفهوم در ماشین لرنینگ بسیار مورد مطالعه قرار گرفته است و توجه‌های زیادی را به خود جلب کرده است.در بسیاری از زمینه‌ها، الگوریتم‌های گروهی عملکرد بسیار خوبی دارند؛ برای مثال در تشخیص اسپم، نفوذ و ردیابی اشیاء. از روش‌های گروهی معروف می‌توان به Bagging و Boosting اشاره کرد. این روش‌ها از ترکیب تعدادی مدل ضعیف استفاده می‌کنند تا عملکرد کلی مدل را افزایش دهند. روش‌های برخطی مثل Online Bagging و Online Boosting در مواقعی کاربرد دارند که تمام دیتاست در حافظه جای نمی‌گیرد یا ماهیت داده‌ها جریانی است. اگرچه الگوریتم‌های اشاره شده از تعدادی مدل ضعیف تشکیل شده‌اند اما آن‌ها هر مدل را برای ناحیه‌ای از داده‌ها انتخاب می‌کنند که آن مدل در آن ناحیه خبره محسوب می‌شود.در پیش‌بینی با نظر خبرگان، هدف این است که لیبل یک نمونه را با خطایی نزدیک به بهترین خبره پیش‌بینی کنیم. یک راه حل ساده برای این مسئله نصف‌کردن (Halving) است. Weighted Majority و Randomized Weighted Majority معروف‌ترین راه حل ها برای این مسئله هستند. اساس این الگوریتم ها نصف‌کردن (Halving) است اما بسته به تعداد خبرگان و میزان خطای بهترین خبره، کران خطای بهتری دارند. یک راه حل دیگر برای این مسئله Exponential Weighted Average است که اساس آن شبیه به RWM است. در RWM از تابع هزینه‌ای استفاده می‌کنیم که فقط شامل 0 و 1 است ولی در EWA از تابع هزینه‌‌ی محدبی استفاده می‌کنیم (Convex Function)اساس تمام الگوریتم‌های اشاره شده، تعریف بهترین خبره است. بهترین خبره خبره‌ای است که میانگین خطای کمتری در طول آزمایش داشته باشد. به‌همین دلیل، بهترین خبره لزوما هم بیشترین میزان منفی واقعی و مثبت واقعی را ندارد. (برای مطالعه بیشتر درباره True Positive Rate و True Negative Rate کلیک کنید). آزمایش‌های انجام شده نشان می‌دهند که پیدا کردن بهترین Classifier برای هر ناحیه به‌صورت جداگانه و پیش‌بینی بر اساس آن‌ها، عملکرد کلی طبقه‌بندی را افزایش می‌دهد.در این مطلب یک راه ساده و کارا برای پیدا کردن خبرگان ارائه شده است. این خبرگان کمترین False Positive و False Negative را دارند. روش ارائه شده Cascading Randomized Weighted Majority یا CRWM نام دارد و نسخه‌ی آبشاری/دنباله‌ای RWM است.در تقریباً تمام الگوریتم‌های یادگیری برخط برای طبقه‌بندی، یک سناریوی کلی وجود دارد که از این بخش‌ها تشکیل شده است: در ابتدا یک نمونه به یادگیرنده داده می‌شود، یادگیرنده یک لیبل را به آن اختصاص می‌دهد و در نهایت لیبل صحیح آن نمونه به یادگیرنده داده می‌شود. یادگیرنده این نمونه لیبل شده را یاد می‌گیرد تا عملکرد را افزایش دهد. در ادامه، پیش‌بینی با استفاده از رای خبرگان را معرفی خواهیم کرد که سناریویی شبیه به این دارد.پیش‌بینی بر اساس نظر خبرگانبرای شهود بهتر این مثال را در نظر بگیرید. یک الگوریتم یادگیرنده وظیفه حدس زدن آب‌وهوای هر روز را دارد؛ باید بگوید امروز باران می‌بارد یا نه. برای انجام این پیش‌بینی، نظر چندین خبره به الگوریتم داده می‌شود. هر روز هر خبره بله یا خیر می‌گوید و الگوریتم یادگیرنده باید این اطلاعات را به‌کار ببرد تا پیش‌بینی نهایی را درباره آب‌وهوا انجام دهد. بعد از انجام پیش‌بینی، به الگوریتم گفته می‌شود که امروز هوا چگونه بود. حالت خوب این است که یادگیرنده بتواند تشخیص بدهد کدام خبره بهترین است و نظر بهترین خبره را به‌عنوان خروجی پیش‌بینی کند. از آنجایی که ما نمی‌دانیم کدام خبره بهترین است، در عوض هدف ما تا اینجا این است که تا جای ممکن عملکردی نزدیک به عملکرد بهترین خبره داشته باشیم. این یعنی یک یادگیرنده خوب باید تضمین کند که در هیچ زمانی، عملکردی بدتر از هیچ‌کدام از خبرگان ندارد. یک الگوریتم که بتواند این مشکل را حل کند، شامل این مراحل است: اول پیش‌بینی‌های خبره را دریافت می‌کند، دوم پیش‌بینی‌های خود را انجام می‌دهد و سوم پاسخ درست به آن گفته می‌شود.الگوریتم اکثریت وزن‌دار تصادفیالگوریتم اکثریت وزن‌دار و نسخه تصادفی آن، مشهورترین راه‌حل برای پیش‌بینی با نظر خبرگان است. RWM از چند Classifier پایه تشکیل شده است (همان خبرگان) و هر Classifier یک فاکتور وزن دارد. هر بار که یک نمونه جدید دریافت می‌شود، هرکدام از آن Classifierها یک لیبل را برای آن نمونه پیش‌بینی می‌کند و الگوریتم بر اساس این پیش‌بینی‌ها و وزن هر Classifier، یک لیبل را انتخاب می‌کند. هر زمان که الگوریتم لیبل درست آن نمونه را دریافت می‌کند، فاکتور وزن هر Classifier باید طوری تغییر کند که اگر یک Classifier غلط پیش‌بینی کرده است با یک ضریب ثابت جریمه شود. ثابت شده است که این الگوریتم در میان همه‌ی خبرگان، به بهترین خبره همگرا می‌شود. شبه‌کد این الگوریتم را می‌توانید ببینید:نشان داده شده است که تعداد اشتباهاتی که انتظار می‌رود این الگوریتم انجام بدهد در این نامساوی صدق می‌کند:که در آن m تعداد اشتباه‌هایی است که بهترین خبره تا به‌این‌جای کار انجام داده است و بتا یک مقدار ثابت برای جریمه است.الگوریتم اکثریت وزن‌دار تصادفی آبشاریوقتی حجم داده‌ها زیاد است، الگوریتم RWM بر اساس نظر بهترین خبره، پیش‌بینی می‌کند. این الگوریتم به زمان زیادی احتیاج دارد تا به بهترین خبره همگرا شود و یادگیرنده ممکن است نسبت به بهترین خبره، اشتباهات بیشتری انجام دهد. اگرچه، وقتی بهترین خبره توسط یادگیرنده پیدا می‌شود، باید مطمئن شویم که تعداد اشتباهات الگوریتم بیشتر از بهترین خبره نیست و باید بتوانیم بگوییم که از حالا به بعد همان چیزی را پیش‌بینی می‌کند که بهترین خبره می‌گوید. در این بخش یک الگوریتم یادگیری برخط معرفی می‌کنیم که ایده اصلی آن، معرفی بیش از یک خبره است؛ برای هر دسته از داده‌ها، یک خبره. در واقع این الگوریتم سعی می‌کند که بهترین خبره‌ها را پیدا کند و برای هر نمونه جدید تصمیم بگیرد که کدام خبره مناسب‌تر است و بر اساس نظر آن خبره پیش‌بینی کند.هر خبره در واقع یک Classifier است. بعد از مطالعه‌ی Classifierهای متعدد، مشاهده شد که آن‌ها معمولا همزمان هم FP و هم FN کم ندارند. شکلی که در زیر می‌بینید یک دیتاست با دو کلاس را به همراه سه Classifier نشان می‌دهد. اگرچه C_0 کمترین مقدار خطا را دارد اما FP و FN آن در میان سه Classifier بهترین نیست. همانطور که در شکل نشان داده شده است، C_p مقدار FP کمتری نسبت به C_0 و C_n مقدار FN کمتری نسبت به C_0 دارد. در نتیجه، به‌جای اینکه به‌دنبال خبره‌ای با کمترین میزان خطا باشیم، به دنبال خبره‌ای با کمترین FP و FN می‌گردیم.هدف الگوریتم WM ، پیدا کردن بهترین خبره در یک گروه از Classifierهای برخط است؛ اما پیدا کردن بهترین خبره‌ی مثبت و بهترین خبره‌ی منفی، به‌طور همزمان همچنان یک مشکل است و برای حل آن، راه حل زیر را ارائه می‌دهیم.همانطور که در شکل زیر می‌بینید، الگوریتم ارائه شده n Classifier و سه یادگیرنده با مکانیسم RWM دارد که از این Classifierها استفاده می‌کند. به عبارت دیگر، برای هر یادگیرنده n فاکتور وزن وجود دارد یعنی  n × 3. برای هر نمونه جدید، هر کدام از این n Classifier ها یک لیبل را پیش‌بینی می‌کنند. با استفاده از فاکتورهای وزن، این پیش‌بینی‌ها به یادگیرنده‌ها داده می‌شود تا آن‌ها هم پیش‌بینی خود را کنند. از آنجایی که الگوریتم اول وظیفه پیش‌بینی لیبل‌های منفی را دارد، اگر این الگوریتم لیبل را منفی پیش‌بینی کند آنگاه لیبل منفی در نظر گرفته می‌شود؛ در غیر این صورت الگوریتم دوم به کار گرفته می‌شود.اگر الگوریتم دوم لیبل را مثبت پیش‌بینی کند آنگاه لیبل خروجی مثبت خواهد بود؛ در غیر این صورت الگوریتم سوم به کار گرفته می‌شود و لیبل پیش‌بینی این الگوریتم لیبل خروجی برای آن نمونه خواهد بود.اگرچه این سناریو برای دیتاست با دو کلاس گفته شد اما برای دیتاست‌های چندکلاسی هم صادق است. برای یک دیتاست چندکلاسی، الگوریتم همچنان از n Classifier استفاده خواهد کرد اما به L + 1 یادگیرنده احتیاج خواهد داشت که L تعداد کلاس‌های متفاوت است؛ برای هر کلاس یک یادگیرنده + یک یادگیرنده نهایی. پس در یک دیتاست با L کلاس ، الگوریتم به n ×  (L + 1) فاکتور وزن احتیاج خواهد داشت. باقی الگوریتم، مانند الگوریتم برای دیتاست دوکلاسی است.هرگاه لیبل صحیح یک نمونه دریافت شود، این لیبل صحیح فقط به یادگیرنده‌ای داده خواهد شد که لیبل خروجی را تولید می‌کنید. در نتیجه برای هر نمونه جدید، فاکتورهای وزن فقط یک یادگیرنده می‌تواند به‌روزرسانی شود. از آنجایی که یادگیرنده‌ها از مکانیسم RWM استفاده می‌کنند، فاکتور وزن Classifier ای که پیش‌بینی اشتباهی انجام داده بود، به‌وسیله ثابت بتا جریمه می‌شود.  الگوریتم CRWM از سه یادگیرنده‌ی RWM در سه مرحله استفاده می‌کند و با توجه به ساختار آبشاری/دنباله‌ای آن، Cascading Randomized Weighted Majority نام‌گذاری شده است.در تصاویر زیر می‌توانید عملکرد این الگوریتم را مشاهده کنید.</description>
                <category>مجتبی میر یعقوب زاده</category>
                <author>مجتبی میر یعقوب زاده</author>
                <pubDate>Fri, 30 Oct 2020 14:52:49 +0330</pubDate>
            </item>
                    <item>
                <title>تاریخچه نظریه کدگذاری</title>
                <link>https://virgool.io/@TabaMojj/%D8%AA%D8%A7%D8%B1%DB%8C%D8%AE%DA%86%D9%87-%D9%86%D8%B8%D8%B1%DB%8C%D9%87-%DA%A9%D8%AF%DA%AF%D8%B0%D8%A7%D8%B1%DB%8C-z5j8j0ebrocp</link>
                <description>کاوشگرهای فضایی دهه‌هاست که داده‌ها را از دورترین سیاره‌ها به زمین ارسال می‌کنند. این در حالی است که توان فرستنده‌های رادیویی این دستگاه‌ها، تنها چند وات است. حال سوالی که پیش می‌آید این است که چطور این حجم از داده، بدون گرفتار شدن در بهبوهه‌ی نویزها، از میلیون‌ها مایل دورتر به ما می‌رسد؟برای تحقق این عمل، رشته‌های مختلفی به کمک هم می‌آیند: مهندسی برق، کامپیوتر و ریاضیات.نظریه کدگذاری شاخه‌ای از ریاضیات است که مربوط به انتقال داده‌ها از طریق کانال‌های نویزدار و بازیابی پیام است. نظریه کدگذاری درباره آسان کردن خواندن پیام هاست؛ آن را با رمزنگاری که درباره سخت کردن خواندن پیام‌هاست اشتباه نکنید!فرض کنیم پیام ما در قالب اعداد یا بیت‌های باینری هستند یعنی رشته‌هایی از 0 و 1. ما باید این بیت‌ها را در یک کانال منتقل کنیم (مثلا یک خط تلفن) که به‌صورت تصادفی، خطاهایی در آن رخ می‌دهد اما این خطا‌ها یک نرخ قابل حدس دارند. برای جبران این خطاها، باید بیت‌های بیشتری را در پیام اصلی ارسال کنیم.ساده‌ترین راه برای شناسایی خطا در داده‌های باینری، Parity Code (بیت توازن) است. بیت توازن بعد از هر 7 بیت، یک بیت اضافه ارسال می‌کند. اگرچه، این روش فقط می‌تواند خطاها را شناسایی کند و برای تصحیح آن‌ها باید درخواست کنیم تا داده‌ها را دوباره ارسال کنند!یک راه ساده برای تشخیص و تصحیح خطاها، تکرار هر بیت به تعداد مشخصی است. گیرنده نگاه می‌کند که کدام مقدار، 0 یا 1، بیشتر تکرار می‌شود و فرض می‌کند که این بیت مورد نظر است. این روش می‌تواند نرخ خطاهای 1 در هر 2 بیت ارسال شده را پاسخگو باشد اما هزینه برای این کار، افزایش تعداد تکرار است.روزهای ابتدایینکته منفی روش تکرار این است که تعداد بیت‌های ارسالی را تا حدغیرقابل زیادی بالا می‌برد. در سال 1948، Claude Shannon، که در آزمایشگاه‌های Bell فعالیت می‌کرد، نشان داد که می‌توان پیام‌ها را طوری کدگذاری کرد که تعداد بیت‌های اضافی‌ ِ ارسالی، تا حد ممکن پایین باشند. با این کار، او مبحث نظریه کدگذاری را دایر کرد. متاسفانه اثبات او هیچ دستورالعمل صریحی برای این کدهای مطلوب ارائه نکرده است.دو سال بعد بود که Richard Hamming ، که او هم در آزمایشگاه‌های Bell فعالیت می‌کرد، مطالعه کدهای تصحیح‌شونده‌ای را آغاز کرد که نرخ انتقال اطلاعات در این روش، کارآمدتر از روش تکرار بود. در اولین تلاش، یک کد تولید کرد که چهار حرف اول آن داده‌های واقعی بودند و سه حرف بعدی آن برای چک بود (Check Bit) که این سه حرف نه تنها امکان تشخیص بلکه تصحیح را هم فراهم می‌کرد. توجه کنید انجام این کار در روش تکرار، احتیاج به 9 چک بیت داشت.گفته می‌شود Hamming این کد را بعد از تلاش‌های فراوان برای نوشتن یک پیام روی نوار کاغذی با استفاده از کد توازن  اختراع کرد. او غر میزد که &quot;اگر می‌تواند خطا را تشخیص دهد، پس چرا نمی‌تواند تصحیح کند!&quot;در حالیکه Shannon و Hamming روی انتقال اطلاعات در آمریکا کار می‌کردند، John Leech کدهای مشابه را در Group Coding در کمبریج اختراع کرد. این تحقیق شامل کار روی مسئله بسته‌بندی گوی‌ها (Sphere Packing Problem) بود و منجر به ایجاد یک شبکه‌بندی 24 بعدی شد. این تحقیق یک عنصر اصلی برای درک و طبقه‌بندی گروه‌های متقارن بود.کاربردهاارزش کدهای قابل تصحیح در انتقال اطلاعات، چه در زمین چه در فضا، به‌سرعت مورد توجه قرار گرفت و کدهای متنوعی تولید شدند که هم به صرفه‌جویی در ارسال و هم به قابلیت تصحیح خطا دست یافتند. بین سال‌های 1969 و 1973، مریخ‌نورد های ناسا از کد Reed-Muller استفاده کردند. این کد می‌توانست 7 خطا را در 32 بیت تصحیح کند. توجه کنید که این 32 بیت شامل 6 بیت داده واقعی و 26 چک بیت بود! بیش از 160000 بیت در هر ثانیه به زمین ارسال می‌شد.یک کاربرد دیگر کدهای قابل تصحیح، با گسترش دیسک‌ها ارائه شد. در CDها، سیگنال‌ها به‌صورت دیجیتالی رمزگذاری شده‌اند. برای محافظت CDها در برابر خراش‌ها و شکستگی‌ها، از دو نوع کد در هم تنیده استفاده شده که می‌توانند تا 4000 خطا را تصحیح کنند. پخش‌کننده‌های صدا می‌توانند حتی خطاهای بیشتری را با تفسیر سیگنال‌ها تصحیح کنند.تحولات مدرندر سال‌های گذشته تلاش‌های زیادی برای گذشتن از محدودیت‌هایی که Shannon در کارهایش پیش‌بینی کرده بود، انجام شده است. انجام این کار، احتیاج به تکنیک‌هایی از زمینه‌های متنوعی دارد از جمله جبر خطی، نظریه میدان‌ها و هندسه جبری. نظریه کدگذاری نه تنها به حل مسائل خارج از ریاضیات کمک کرده است، بلکه شاخه های دیگر ریاضیات را با مسائل جدید و همچنین راه حل های جدید غنی کرده است.منبع</description>
                <category>مجتبی میر یعقوب زاده</category>
                <author>مجتبی میر یعقوب زاده</author>
                <pubDate>Fri, 30 Oct 2020 14:49:06 +0330</pubDate>
            </item>
                    <item>
                <title>این اشتباه‌های ماشین لرنینگ را تکرار نکنید: پژوهش یا کاربرد</title>
                <link>https://virgool.io/@TabaMojj/%D8%A7%DB%8C%D9%86-%D8%A7%D8%B4%D8%AA%D8%A8%D8%A7%D9%87%D9%87%D8%A7%DB%8C-%D9%85%D8%A7%D8%B4%DB%8C%D9%86-%D9%84%D8%B1%D9%86%DB%8C%D9%86%DA%AF-%D8%B1%D8%A7-%D8%AA%DA%A9%D8%B1%D8%A7%D8%B1-%D9%86%DA%A9%D9%86%DB%8C%D8%AF-%D9%BE%DA%98%D9%88%D9%87%D8%B4-%DB%8C%D8%A7-%DA%A9%D8%A7%D8%B1%D8%A8%D8%B1%D8%AF-zrnnlsklpwak</link>
                <description>ماشین لرنینگ: پژوهش یا کاربرداین روزها همه می‌خواهند وارد ماشین لرنینگ شوند. این مسیر برای بسیاری از کسب‌وکارها عالی است چون می‌تواند در مدت زمان کوتاه، درآمد زیادی را حاصل کند. تقاضا برای مهارت‌های ماشین لرنینگ، بیشتر از هر زمان دیگری است.به‌همین دلیل است که هر کسب‌وکاری می‌گوید:هی! خیلی سریع به یه تیم پژوهش ماشین لرنینگ احتیاج داریم! بهترین دانشمندان علوم داده رو که مقاله‌های زیادی دارن، پیدا می‌کنیم و پول زیادی بهشون می‌دیم تا بتونیم ماشین لرنینگ رو وارد کسب‌وکارمون کنیم! هورا!! اما یک دقیقه صبر کنید. به‌عنوان یک کسب‌وکار، آیا احتیاج به یک تیم پژوهش دارید؟ اصلا چه‌قدر به ماشین لرنینگ احتیاج دارید؟ آیا کسب‌وکار شما به خوبی از آن استفاده خواهد کرد؟برای پاسخ به این سوال‌ها، باید تفاوت بین دو روشی را که ماشین لرنینگ کاربرد دارد، متوجه شویم: پژوهش(Research) و کاربر (Application)پژوهش ماشین لرنینگپژوهش ماشین لرنینگ تماماً درباره علم است. یک پژوهش‌گر ماشین لرنینگ سعی می‌کند تا مرزهای علم را جابجا کند، به‌خصوص در زمینه هوش مصنوعی. این افراد معمولا مدرک فوق لیسانس یا دکترا در علوم کامپیوتر دارند، مقالات زیادی را در منتشر کردند و در کنفرانس‌های متعدد شرکت کرده‌اند. این افراد در فضای پژوهشی بسیار محبوب هستند!اگر در حال انجام یک کار مربوط به فناوری‌های پیشرفته هستید، پس پژوهش‌گر ماشین لرنینگ برای کار شما عالی است. این افراد سعی می‌کنند برای مشکل شما یک راه‌حل علمی پیدا کنند. اگر به آن‌ها بگویید &quot;ما تو تشخیص مجرم‌ها با دقت 95% عالی هستیم. میتونی مارو به 97% برسونی؟&quot; در این صورت پژوهش‌گر ماشین لرنینگ به کار شما می آید.نکته اینجاست: این شخص احتمالاً تا به حال هیچ نرم افزاری تولید نکرده است! احتمالاً این شخص متخصص نرم افزار به‌عنوان سرویس (SaaS) نیست و نمی‌تواند پژوهش را به مرحله عمل برساند.کاربرد ماشین لرنینگاپلیکیشن ماشین لرنینگ تماماً درباره مهندسی است. یک مهندس ماشین لرنینگ می‌داند که چگونه از آخرین پژوهش ماشین لرنینگ استفاده کند و آن را تبدیل به یک چیز با ارزش کند. او پژوهش را می‌گیرید و آن را تبدیل به یک محصول یا یک سرویس می‌کند. این افراد به‌خوبی با سرویس‌های پردازش ابری مثل AWS و GCP کار می‌کنند. آن‌ها درمورد Software Development Life Cycle (SDLC)  و Agile اطلاعات دارند.متاسفانه کسب‌وکارهایی که به دنبال استفاده از ماشین لرنینگ در محصولات خود هستند، این افراد را نادیده می‌گیرند. مهندس‌های ماشین لرنینگ معمولاً در در دید راس هستند و تجربه‌های زیادی در پیاده‌سازی محصولات فناوری پیشرفته دارند. به‌علاوه، می‌دانند چطور از ماشین لرنینگ استفاده کنند.مهندس ماشین لرنینگ شاید مثل پژوهش‌گر پر زرق و برق نباشد چون مانند سوپراستارهای ماشین لرنینگ، مدرک دکترا ندارد و چندین هزار نفر هم به مقالات وی ارجاع ندادند. اما اگر می‌خواهید از ماشین لرنینگ در محصولات خود استفاده کنید، به مهندس ماشین لرنینگ احتیاج دارید.چگونه از ماشین لرنینگ در کسب‌وکار استفاده کنیم؟چگونگی استفاده از ماشین لرنینگ در کسب‌وکار شما، بستگی به محصول شما دارد. اگر محصول شما شخصی‌سازی زیادی دارد و یا از سطح بالایی از هوش مصنوعی استفاده می‌کند، احتمالا به پژوهش‌گر ماشین لرنینگ احتیاج خواهید داشت.برای بیشتر تیم‌ها و کسب‌وکارها، احتیاجی به پژوهش‌گر نیست. علم ماشین لرنینگ کنونی کاربردهای زیادی دارد. آنقدرها هم پیچیده نیست. شما احتیاجی به شخصی ندارید که چرخ را از اول اختراع کند، به شخصی احتیاجی دارید که بداند چگونه باید از چرخ استفاده کند: مهندس ماشین لرنینگ!و در پایان این نکته را بدانید که ماشین لرنینگ، مانند دیگر ابزارهای نرم‌افزاری، فقط یک ابزار است. پژوهش‌گرها ابزارهای جدید را تولید می‌کنند و مهندس‌ها تشخیص می‌دهند که چگونه می‌توان از آن‌ها به‌خوبی استفاده کرد. ماشین لرنینگ کارهای جالبی انجام می‌دهد اما هدف نهایی آن این است که در نهایت یک ارزش به یک محصول اضافه کند.منبع: TowardsDataScience</description>
                <category>مجتبی میر یعقوب زاده</category>
                <author>مجتبی میر یعقوب زاده</author>
                <pubDate>Wed, 07 Oct 2020 23:27:18 +0330</pubDate>
            </item>
                    <item>
                <title>چرا در رسم نمودار از لگاریتم استفاده می‌کنیم؟</title>
                <link>https://virgool.io/@TabaMojj/%DA%86%D8%B1%D8%A7-%D8%AF%D8%B1-%D8%B1%D8%B3%D9%85-%D9%86%D9%85%D9%88%D8%AF%D8%A7%D8%B1-%D8%A7%D8%B2-%D9%84%DA%AF%D8%A7%D8%B1%DB%8C%D8%AA%D9%85-%D8%A7%D8%B3%D8%AA%D9%81%D8%A7%D8%AF%D9%87-%D9%85%DB%8C%DA%A9%D9%86%DB%8C%D9%85-ve2clhderpch</link>
                <description>مقیاس لگاریتمی در نمودارهادو دلیل برای استفاده از مقیاس‌های لگاریتمی در نمودارها وجود دارد. اولین دلیل برای پاسخ دادن به چولگی داده‌هاست؛ یعنی هنگامی که یک یا چند نقطه بسیار بزرگ‌تر از کل داده‌ها هستند. دومین دلیل برای نشان دادن تغییر درصد یا عوامل ضرب است.در این مطلب می‌خواهیم اهمیت استفاده از مقیاس لگاریتمی در مصورسازی را بررسی کنیماول مفهوم لگاریتم را یادآوری کنیم. لگاریتم کمک می‌کند تا این سوال را پاسخ دهیم: چند تا از یک عدد را باید ضرب کنیم تا به عدد دیگری برسیم؟برای مثال، برای رسیدن به 9، چند تا 3 را باید ضرب کنیم؟ جواب 3 است چون 3*3=9 می‌شود. تصویر زیر کمک می‌کند تا بهتر متوجه مقیاس لگاریتمی شویم:مقیاس لگاریتمیبه‌طور خلاصه، استفاده از مقیاس لگاریتمی کمک می‌کند تا داده‌هایی را که فاصله بسیار زیادی با یکدیگر دارند رسم کنیم؛ مثلا رسم نمودار اشخاصی که درآمدشان بین 40,000$ تا 800,000,000$ است.داده‌های زیر را در نظر بگیرید. این داده‌ها نشان دهنده ثروت افراد است.بیشترین مقدار ثروت برابر $7,700,000,000 و کمترین مقدار برابر $3,200 است.می‌خواهیم نمودار این داده‌ها را رسم کنیم.اول نمودار میله‌ای را با مقیاس خطی رسم می‌کنیم.همانطور که مشخص است با استفاده از مقیاس خطی، فقط می‌توان میزان ثروت 3 شخص را دید. این مشکل باعث می‌شود که نتوان چیز خاصی از نمودار فهمید.حالا نمودار بالا را در مقیاس لگاریتمی رسم می‌کنیم.با استفاده از مقیاس لگاریتمی مشکل نمودار ما حل شد!در نمودار های زیر هم می‌توانید تفاوت بین مقیاس خطی و لگاریتمی را مشاهده کنید:</description>
                <category>مجتبی میر یعقوب زاده</category>
                <author>مجتبی میر یعقوب زاده</author>
                <pubDate>Fri, 11 Sep 2020 22:07:15 +0430</pubDate>
            </item>
                    <item>
                <title>بررسی الگوریتم درخت تصمیم در ماشین لرنینگ</title>
                <link>https://virgool.io/@TabaMojj/%D8%A8%D8%B1%D8%B1%D8%B3%DB%8C-%D8%A7%D9%84%DA%AF%D9%88%D8%B1%DB%8C%D8%AA%D9%85-%D8%AF%D8%B1%D8%AE%D8%AA-%D8%AA%D8%B5%D9%85%DB%8C%D9%85-%D8%AF%D8%B1-%D9%85%D8%A7%D8%B4%DB%8C%D9%86-%D9%84%D8%B1%D9%86%DB%8C%D9%86%DA%AF-zrvevpgjj2rt</link>
                <description>الگوریتم درخت تصمیمدرست مثل الگوریتم‌های SVM، الگوریتم Decision Tree (درخت تصمیم) هم جزء الگوریتم‌های همه‌ کاره‌ی ماشین لرنینگ هست که از پس طبقه‌بندی، رگرسیون و حتی تسک‌های دارای چند خروجی برمیاد. این الگوریتم‌ها قدرت زیادی دارند و می‌تونند در دیتاست‌های پیچیده استفاده بشن. برای مثال، در مطلب قبلی یک DecisionTreeRegressor رو روی دیتاست خانه‌های کالیفرنیا پیاده‌سازی کردیم.درخت تصمیم هم‌چنین جزء مولفه‌های اساسی الگوریتم Random Forest به‌حساب میاد. این الگوریتم هم جزء الگوریتم‌های قدرتمند ماشین لرنینگ هست.در این مطلب می‌خوایم ببینیم چجوری درخت تصمیم رو ترین، مصورسازی و در نهایت برای حدس زدن آماده می‌کنند. سپس از الگوریتم CART استفاده می‌کنیم و می‌بینیم چطور درخت‌ها رو برای رگرسیون Regularize می‌کنن. https://virgool.io/@TabaMojj/%D8%A8%DB%8C%D8%A7%DB%8C%D8%AF-%DB%8C%DA%A9-%D9%85%D8%AF%D9%84-%D9%85%D8%A7%D8%B4%DB%8C%D9%86-%D9%84%D8%B1%D9%86%DB%8C%D9%86%DA%AF-%D8%A8%D8%B3%D8%A7%D8%B2%DB%8C%D9%85-%D9%82%D8%B3%D9%85%D8%AA-%D8%A7%D9%88%D9%84-bt7gdka3krxz آموزش و مصورسازی یک درخت تصمیمبرای فهمیدن درخت‌های تصمیم، بیاید اول یکی بسازیم و ببینیم که چجوری حدس میزنه.import numpy as np
import os
import matplotlib as mpl
import matplotlib.pyplot as plt
mpl.rc(&#039;axes&#039;, labelsize=14)
mpl.rc(&#039;xtick&#039;, labelsize=12)
mpl.rc(&#039;ytick&#039;, labelsize=12)

# Where to save the figures
PROJECT_ROOT_DIR = &amp;quot******&amp;quot
CHAPTER_ID = &amp;quotdecision_trees&amp;quot
IMAGES_PATH = os.path.join(PROJECT_ROOT_DIR, &amp;quotimages&amp;quot, CHAPTER_ID)
os.makedirs(IMAGES_PATH, exist_ok=True)

from sklearn.datasets import load_iris
from sklearn.tree import DecisionTreeClassifier
iris = load_iris()
X = iris.data[:, 2:] # petal length and width
y = iris.target
tree_clf = DecisionTreeClassifier(max_depth=2, random_state=42)
tree_clf.fit(X, y)برای ترسیم این درخت تصمیم از graphviz استفاده می‌کنیم:from graphviz import Source
from sklearn.tree import export_graphviz
        
export_graphviz(
        tree_clf,
        out_file=os.path.join(IMAGES_PATH, &amp;quotiris_tree.dot&amp;quot)
        feature_names=iris.feature_names[2:],
        class_names=iris.target_names,
        rounded=True,
        filled=True
)
g = Source.from_file&#40;os.path.join(IMAGES_PATH, &amp;quotiris_tree.dot&amp;quot&#41;)
g.view()حدس زدنحالا ببینیم درختی که در تصویر بالا مشخص شده، چطور حدس میزنه. فرض کنید یک گل زنبق دارید و می‌خواید اون رو طبقه‌بندی کنید. از گره‌ی ریشه(Root Node) شروع می‌کنید (عمق صفر، واقع در بالای شکل): این گره می‌پرسه که آیا طول گلبرگ گل کوچک‌تر از 2.45 سانتی متر هست یا نه. اگر هست، به سمت گره‌ی فرزند واقع در سمت چپ حرکت می‌کنید (عمق یک، واقع در سمت چپ). با این شرایط، یک برگ به‌حساب میاد (یعنی گره‌ی فرزند نداره) پس هیچ سوالی نمیپرسه و حدسی که میزنه این هست که این گل یک Iris seosa هست (class = setosa)حالا فرض کنید یک گل زنبق دیگه دارید و گلبرگ اون بزرگ‌تر از 2.45 سانتی متر هست. به گره‌ی فرزند واقع در سمت راست حرکت ‌می‌کنیم (عمق یک، واقع در سمت راست) چون این گره برگ نیست، پس باز هم سوال می‌پرسه: آیا عرض گلبرگ کوچک‌تر از 1.75 سانتی متر هست؟ اگر هست پس این گل یک Iris vesicolor هست (عمق 2 ، واقع در سمت چپ) اگر نیست، پس Iris virginica هست (عمق دو، واقع در سمت راست)یکی از خوبی‌های درخت تصمیم این هست که برای استفاده از اون‌ها، آنچنان لازم نیست که داده‌ها رو آماده کنیم. در واقع، اصلا به تغییر مقیاس یا نرمال کردن احتیاج ندارندر یک گره، sample تعداد نمونه‌های تمرینی که در اون گره هستند رو نشون میده. مثلا، 100 نمونه تمرین دارای گلبرگ با عرض بیشتر از 2.45 سانتی متر هستند (عمق 1، واقع در سمت راست) و از این بین، 54 نمونه عرض گلبرگ کوچک‌تر از 1.75 سانتی متر دارن (عمق 2، واقع در سمت چپ)در یک گره، value تعداد نمونه‌های هر کلاس رو که در این گره هستند، نشون میده. برای مثال، گره‌ی پایین سمت راست دارای 0 نمونه از کلاس Iris setosa، یک نمونه از کلاس Iris versicolor و 45 نمونه از کلاس iris virginica هست.در یک گره، gini مقدار impurity رو محاسبه می‌کنه (ناخالصی). یک گره pure (خالص) به‌حساب میاد(gini=0) اگر تمام نمونه‌های ترینینگ متعلق به یک کلاس باشند. مثلا گره‌ی سمت چپ با عمق 1 چون شامل تمام نمونه‌های iris setosa میشه، کاملا خالص هست و gini برابر 0 هست. معادله‌ی پایین مقدار Gini Impurity رو محاسبه می‌کنه.در این فرمول، p نشون دهنده نسبت نمونه‌های کلاس k در میان نمونه‌های گره‌ی i ام هست.کتابخانه Scikit-Learn از الگوریتم CART استفاده می‌کنه. این الگوریتم فقط درخت‌های باینری تولید می‌کنه: گره‌های غیر برگ همیشه دو فرزند دارن ( یعنی همیشه جواب سوال‌ها بله/خیر هست) اگرچه دیگر الگوریتم‌ها مثل ID3 می‌تونن درخت‌های تصمیم با گره‌های بیش از دو فرزند تولید کنندشکل زیر مرزهای تصمیم‌گیری درخت تصمیم رو نشون میده.خط عمودی زخیم نشون دهنده مرز تصمیم گره‌ی ریشه واقع در عمق صفر هست: طول گلبرگ برابر 2.45 سانتی متر. از اونجایی که قسمت سمت چپ خالص هست (فقط شامل نمونه‌های iris setosa هست)، بیشتر از این نمیشه اون رو جدا کرد. اما قسمت سمت راست ناخالصی داره در نتیجه عمق 1 در عرض گلبرگ برابر 1.75 سانتی متر جدا میشه (با خط بریده بریده نشون داده شده). از اونجایی که خودمون max_depth رو برابر 2 قرار دادیم، درخت تصمیم همینجا متوقف میشه. اگر اون رو برابر 3 قرار می‌دادیم یک مرز تصمیم دیگه هم اضافه می‌شد (با خط نقطه نقطه نشون داده شده)تفسیر مدل: جعبه سیاه در برابر جعبه سفیددرخت های تصمیم شهودی هستند و نتیجه‌ای هم که میدن به راحتی قابل تفسیر هست. این مدل ها معروف به مدل های جعبه سفید هستند. در برابر اون‌ها، مدل های Random Forest و Neural Netwrok هستند که به مدل های جعبه سیاه معروف هستند. این مدل‌ها حدس‌های خوبی می‌زنند و می‌تونید محاسباتی هم که برای این حدس انجام شده ببینید؛ اما با این وجود معمولاً سخته که بشه توضیح داد چرا این حدس انجام شده. برای مثال، اگر یک شبکه عصبی بگه که یک شخص خاص در این تصویر حضور داره، سخته که بشه فهمید چه چیزی منجر به این حدس شده: آیا مدل چشم‌های شخص رو شناخت؟ یا دهن؟ یا دماغ؟ یا حتی مبلی که این شخص روش نشسته؟ اما در مقابل، درخت های تصمیم قوانین طبقه بندی ساده و خوبی دارند که حتی به صورت دستی هم میشه اون‌ها رو پیاده سازی کرد. https://virgool.io/@TabaMojj/%D8%A8%D8%B1%D8%B1%D8%B3%DB%8C-%D8%A7%D9%84%DA%AF%D9%88%D8%B1%DB%8C%D8%AA%D9%85-svm-%D8%AF%D8%B1-%D9%85%D8%A7%D8%B4%DB%8C%D9%86-%D9%84%D8%B1%D9%86%DB%8C%D9%86%DA%AF-mlrknsxjvd5t تخمین احتمالات کلاسدرخت تصمیم می‌تونه احتمال تعلق یک نمونه به کلاس k رو تخمین بزنه. در ابتدا از مسیر های درخت عبور میکنه تا گره‌ی برگ رو برای یک نمونه پیدا کنه و بعد نسبت نمونه‌های کلاس k رو در این برگ برمیگردونه. برای مثال، فرض کنید گلی داریم با طول گلبرگ 5 و عرض 1.5 سانتی متر. گره‌ی متناظر اون گره‌ی واقع در سمت چپ عمق 2 هست پس درخت تصمیم این احتمال‌ها رو بعنوان خروجی میده: 0% برای Iris setosa (0/54) 90.7% برای iris versicolor (49/54) و 9.3% برای Iris virginica(5/54). اگر ازش بخوایم که کلاس رو حدس بزنه، باید کلاس 1 یعنی Iris versicolor رو برگردونه چون بیشترین احتمال رو داره:tree_clf.predict_proba([[5, 1.5]])
# array([[0.        , 0.90740741, 0.09259259]])
tree_clf.predict([[5, 1.5]])
# array([1])الگوریتم CARTکتابخانه Scikit-Learn از الگوریتم Classification and Regression Tree برای ترین Decision Tree استفاده می‌کنه. روش کار این الگوریتم به این صورت هست که اول ترینینگ ست رو با استفاده از یک فیچر k و یک مرز tk (مثلا طول گلبرگ کوچکتر از 2.45 سانتی متر) به دو زیر مجموعه تقسیم می‌کنه. حالا چطور k و tk رو انتخاب می‌کنه؟ در واقع به دنبال جفت (k, tk) ای می‌گرده که خالص ترین زیرمجموعه رو تولید می‌کنه. معادله‌ی زیر تابع هزینه‌ی این الگوریتم رو نشون میده.وقتی الگوریتم CART با موفقیت ترینینگ ست رو به دو قسمت تبدیل کرد، با همین منطق دوباره این زیرمجموعه‌ها رو تقسیم می‌کنه و به‌همین صورت بازگشتی ادامه پیدا می‌کنه. در دو صورت متوقف میشه: وقتی که به بیشترین عمق برسه (این عمق توسط هایپرپارامتر max_depth مشخص میشه) یا وقتی که دیگه نتونه تقسیمی رو پیدا کنه که ناخالصی رو کاهش میده. هایپرپارامتر های دیگه‌ای هم هستند که در ادامه صحبت می‌کنیم.همونطور که می‌بینید الگوریتم CART یک الگوریتم حریصانه هست: به‌طور حریصانه برای یک تقسیم مطلوب جستجو می‌کنه و این روند رو در هر مرحله تکرار می‌کنه. این الگوریتم بررسی نمیکنه که آیا این تقسیم در مراحل پایین تر منجر به ناخالصی کمتر میشه یا نه. یک الگوریتم حریصانه معمولاً یک راه‌حل خوب ارائه میده اما تضمینی نیست که این راه حل ایده‌آل باشه.متاسفانه، پیدا کردن درخت ایده‌آل جزء مسائل NP هست: به زمان O(exp(m)) برای حل شدن احتیاج داره که باعث میشه حتی برای ترینینگ ست های کوچیک هم غیر قابل کنترل بشه. بخاطر همین هست که همیشه باید به یک راه حل خوب قانع باشیم.پیچیدگی محاسباتیبرای حدس زدن لازمه که از کل مسیر‌های درخت عبور کنیم و از ریشه به برگ برسیم. درخت تصمیم معمولاً به خوبی بهینه شده در نتیجه طی کردن مسیرهای درخت به O(log(m)) ریشه احتیاج داره(لگاریتم در مبنای 2 هست). چون هر ریشه باید مقدار یک فیچر رو بررسی کنه، پیچیدگی حدس برابر همین مقدار هست و مستقل از تعداد فیچرهاست. پس حدس زدن سریع هست و حتی با بزرگ‌تر شدن اندازه ترینینگ ست، باز هم به این صورت باقی میمونه.الگوریتم در مرحله‌ی ترینینگ، تمام فیچر هارو برای هر نمونه در هر ریشه، مقایسه میکنه (یا اگر مقدار max_features تعیین شده باشه به همون اندازه چک می‌کنه). مقایسه کردن تمام فیچرها روی تمام نمونه‌ها در هر ریشه، پیچیدگی‌ای برابر O(n * m * log(m)) داره (لگاریتم در مبنای 2 هست) برای ترینینگ ست‌های کوچیک(کمتر از 1000 نمونه)،  Scikit-Learn عمل ترین رو با مرتب‌سازی داده‌ها، سریع‌تر میکنه (presort=True) اما انجام این کار برای ترینینگ ست‌های بزرگ مارو با کاهش سرعت روبرو می‌کنه.آنتروپی یا ناخالصی جینی؟به‌صورت دیفالت، از ناخالصی جینی استفاده میشه اما می‌تونید از معیار ناخالصی آنتروپی هم استفاده کنید. برای این کار هایپرپارامتر criterion رو برابر entropy قرار می‌دیم. مفهوم آنتروپی از ترمودینامیک گرفته شده و معیاری برای بی‌نظمی مولکول‌هاست: اگر مولکول‌ها ساکن باشند و به‌خوبی نظم داشته باشند، آنتروپی نزدیک صفر خواهد بود. مفهوم آنتروپی بعدها در زمینه‌های دیگه‌ای هم استفاده شد مثل تئوری اطلاعات Shannon که میانگین محتوای اطلاعاتی یک پیام رو اندازه‌گیری میکنه. در اینجا اگر تمام پیغام‌ها یکسان باشند، آنتروپی برابر صفر هست. در ماشین لرنینگ، آنتروپی معیاری برای محاسبه‌ی ناخالصیه: وقتی یک مجموعه فقط شامل نمونه‌های یک کلاس باشه، آنتروپی اون مجموعه برابر صفر هست. فرمول زیر نشون دهنده آنتروپی iامین ریشه هست.خب حالا باید از ناخالصی جینی استفاده کنیم یا آنتروپی؟ واقعیت اینه که بیشتر اوقات تفاوت خاصی بینشون نیست و مارو به یک درخت یکسان میرسونن. محاسبه ناخالصی جینی سریع‌تر هست و درنتیجه دیفالت از این استفاده می‌کنیم. اما در جاهایی که متفاوت هستند،ناخالصی جینی کلاسی که بیشترین تکرار رو داره، در شاخه‌ی درخت خودش ایزوله میکنه در حالیکه آنتروپی درخت‌های متعادل‌تری تولید میکنه.هایپرپارامترهای منظم‌سازیدرخت تصمیم فرض‌های کمی درباره ترینینگ ست داره (بر خلاف مدل‌های خطی که فرض می‌کنن داده‌ها خطی هستند) اگر درخت تصمیم رو منظم‌سازی نکنیم، ساختار درخت خودش رو با ترینینگ ست وفق میده و خودش رو خیلی خوب با داده‌ها منطبق می‌کنه؛ درواقع دچار اورفیت میشه. به همچین مدلی میگن nonparamteric model (مدل غیر پارامتری). این به این معنی نیست که هیچ پارامتری نداره (اتفاقا پارامترهای زیادی داره) بلکه به این معنی هست که پارامترها قبل از آموزش تعیین نشدند در نتیجه ساختار مدل آزاد هست که خودش رو تا جایی که می‌تونه به داده‌ها نزدیک کنه. در طرف مقابل، paramteric model (مدل پارامتری)، مثل مدل خطی، تعدادی پارامتر از پیش تعیین شده داره، در نتیجه درجه‌ی آزادی اون محدود شده. در نتیجه احتمال اورفیت شدنش کاهش میابه (اما احتمال آندرفیت شدن رو افزایش میده) https://virgool.io/@TabaMojj/%D8%B7%D8%A8%D9%82%D9%87-%D8%A8%D9%86%D8%AF%DB%8C-%D8%A7%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A8%D8%A7-%D8%AF%DB%8C%D8%AA%D8%A7%D8%B3%D8%AA-mnist-b0ei4thu9xmo برای جلوگیری از اورفیت شدن، باید در مرحله‌ی ترین آزادی درخت تصمیم رو محدود کنید. همونطور که می‌دونید، به این کار میگن Regularization. هایپرپارامترهای Regularization بستگی به الگوریتم دارن اما عموماً می‌تونید حداکثر عمق الگوریتم رو تعیین کنید. در Scikit-Learn این کار رو با استفاده از max_depth انجام میدیم. مقدار دیفالت این هایپرپارامتر None هست. کم‌کردن max_depth مدل رو Regularize میکنه و احتمال اورفیت شدن رو کم می‌کنه.کلاس DecisionTreeClassifer تعدادی هایپرپارامتر برای محدود کردن شکل درخت تصمیم داره:یک ) min_samples_split: حداقل تعداد نمونه‌هایی که یک گره باید داشته باشه تا تقسیم بشهدو ) min_samples_leaf: حداقل تعداد نمونه‌هایی که یک گره‌ی برگ باید داشته باشه تا تقسیم بشهسه ) min_weight_fraction_leaf: همون بالایی اما به‌صورت کسری از تعداد کل نمونه های وزنی بیان شدهچهار ) max_leaf_nodes: حداکثر تعداد گره‌های برگپنج ) max_features: حداکثر تعداد فیچرها که برای تقسیم در هر گره ارزیابی می شونددر حالت کلی، کم کردن هایپرپارامترهای min_* یا max_* مدل رو Regularize میکنه.بقیه الگوریتم‌ها اول درخت تصمیم رو ترین میکنن بعد ریشه‌های غیرضروری رو  هرس (حذف) میکنن. یک ریشه که فرزندهای اون همگی گره‌ی برگ باشن غیرضروری به‌حساب میاد اگر افزایش بهبود خلوص از نظر آماری قابل توجیه نباشه. تست‌های استاندارد آماری مثل Che-Squarred Test برای تخمین احتمال اینکه بهبود خالصی صرفا نتیجه شانس بوده، استفاده میشه. (که بهش میگن Null Hypothesis) اگر این احتمال که بهش میگن p-value ،بیشتر از یک محدوده ای باشه ( معمولا 5%، که توسط یک هایپرپارامتر کنترل میشه) اونوقت اون ریشه غیرضروری محسوب میشه و فرزندان اون حذف میشن. این هرس تا جایی ادامه پیدا می‌کنه که تمام ریشه‌های غیرضروری حذف شده باشند.تصویر زیر دو درخت تصمیم رو نشون میده که روی دیتاست moon ترین شده. در سمت چپ مدل با هایپرپارامترهای دیفالت درست شده (بدون Regularization) و در سمت راست با استفاده از min_samples_leaf=4. مشخصه که مدل سمت چپ اورفیت شده و مدل سمت راست بهتر عمومی‌سازی می‌کنه.رگرسیوندرخت تصمیم همچنین می‌تونه عملیات مربوط به رگرسیون رو هم انجام بده. بیاید از کلاس DecisionTreeRegressor استفاده کنیم:from sklearn.tree import DecisionTreeRegressor
tree_reg = DecisionTreeRegressor(max+depth=2)
tree_reg.fit(X, y)این درخت شبیه درختی هست که برای طبقه‌بندی درست کردیم. اصلی‌ترین تفاوت در این هست که به‌جای اینکه در هر ریشه یک کلاس حدس بزنه، یک مقدار حدس میزنه. برای مثال، فرض کنید می‌خوایم یک حدس برای نمونه‌ای با x1=0.6 انجام بدیم. از ریشه‌ی درخت شروع می‌کنیم و در نهایت مقدار value=0.111 رو حدس می‌زنیم. این مقدار در واقع میانگین مقادیر حدس زده شده برای 110 نمونه ترینینگ هست که توی این ریشه‌ی برگ وجود دارند و نتیجه‌ی اون mean squarred error برابر 0.015 هست. https://virgool.io/@TabaMojj/%D8%A8%D8%B1%D8%B1%D8%B3%DB%8C-%D8%A7%D9%84%DA%AF%D9%88%D8%B1%DB%8C%D8%AA%D9%85-%D9%87%D8%A7%DB%8C-%D8%B1%DA%AF%D8%B1%D8%B3%DB%8C%D9%88%D9%86-%D8%AF%D8%B1-%D9%85%D8%A7%D8%B4%DB%8C%D9%86-%D9%84%D8%B1%D9%86%DB%8C%D9%86%DA%AF-uvcinqzvuf5e حدس‌هایی که مدل زده در شکل مشخص شدند. اگر max_depth=3 باشه، شکل سمت راست نسیب‌تون میشه. دقت کنید که مقدار حدس زده شده برای هر منطقه همیشه برابر میانگین مقادیر حدس زده شده‌ی نمونه‌های اون منطقه هست. الگوریتم هر منطقه رو جوری بخش بندی میکنه که بیشتر نمونه‌های آموزش رو تا حد ممکن به مقدار پیش بینی شده نزدیک کنه.الگوریتم CART به‌جای اینکه بیاد ترینینگ ست رو طوری تقسیم کنه که ناخالصی رو کاهش بده، تلاش می‌کنه که ترینینگ ست رو طوری تقسیم کنه که MSE کاهش پیدا کنه. تابع هزینه رو می‌تونید ببینید:مثل طبقه‌بندی، درخت تصمیم به‌هنگام رگرسیون هم در خطر اورفیت شدن هست. بدون Regularization (یعنی استفاده از هایپرپارامترهای دیفالت) حدس‌هامون شکل سمت چپ میشه. خب مشخصه که اورفیت شده. با تعیین min_samples_leaf=10 به مدل بهتری می‌رسیم یعنی شکل سمت راست.بی‌ثباتی (Instability)تا الان متقاعد شدید که درخت تصمیم خوبی‌های زیادی داره: فهمیدن و پیاده‌سازی راحتی داره، راحت میشه استفاده کرد و قدرتمند و وسیع هست. اگرچه، محدودیت‌هایی هم دارند. اول، همونطور که متوجه شدید، درخت تصمیم مرزهای تصمیم‌گیری قائم رو خیلی دوست داره (تمام تقسیم‌بندی ها عمود بر محورها هستند) این باعث میشه که به چرخش دیتاست حساس باشند. برای مثال شکل زیر رو در نظر بگیرید. این دیتاست رو به راحتی میشه با خط جدا کرد. در شکل سمت چپ درخت تصمیم به راحتی این کار رو انجام داده اما در شکل سمت راست که داده‌ها رو 45درجه چرخوندیم، مرز تصمیم‌گیری بی‌جهت پیچیده میشه. اگرچه که هر دو مرز تصمیم‌گیری به‌خوبی با داده‌ها تطابق پیدا می‌کنند اما احتمالا مدل سمت راست به‌خوبی عمومی‌سازی نکنه. یک راه برای حل این مشکل استفاده از Principal Component Analysis هست که باعث میشه ترینینگ ست در یک جهت مناسب قرار بگیره. در مطالب بعدی در این مورد صحبت خواهیم کرد.به طور کلی، اصلی ترین مشکل درخت تصمیم اینه که خیلی به تغییرات و دگرگونی‌های داخل داده‌های ترینینگ حساس هستند. برای مثال، اگر عریض‌ترین Iris versicolor رو از ترینینگ ست حذف کنیم (اون‌هایی که طول 4.8سانتی‌متر و عرض 1.8سانتی‌متر دارن) مدل شکل پایین حاصل میشه. همونطور که می‌بینید این مدل خیلی با مدلی که اول ارائه دادیم فرق داره. در واقع چون الگوریتیمی که Scikit-Learn استفاده میکنه Stochastic هست (در هر ریشه به‌صورت رندوم مجموعه‌ای از فیچرها رو انتخاب می‌کنه تا ارزیابی کنه) بخاطر همین روی دیتاست یکسان، به همچین مدل متفاوتی میرسیم. (مگر اینکه random_state رو تعیین کنید)الگوریتم Random Forests می‌تونه این بی‌ثباتی رو با محاسبه میانگین حدس‌ها در درخت‌های متعدد، برطرف کنه. در مطالب بعدی به این الگوریتم می‌پردازیم.این هم از الگوریتم Decision Tree. امیدوارم مفید واقع شده باشه.</description>
                <category>مجتبی میر یعقوب زاده</category>
                <author>مجتبی میر یعقوب زاده</author>
                <pubDate>Wed, 09 Sep 2020 16:09:15 +0430</pubDate>
            </item>
                    <item>
                <title>بررسی الگوریتم SVM در ماشین لرنینگ</title>
                <link>https://virgool.io/@TabaMojj/%D8%A8%D8%B1%D8%B1%D8%B3%DB%8C-%D8%A7%D9%84%DA%AF%D9%88%D8%B1%DB%8C%D8%AA%D9%85-svm-%D8%AF%D8%B1-%D9%85%D8%A7%D8%B4%DB%8C%D9%86-%D9%84%D8%B1%D9%86%DB%8C%D9%86%DA%AF-mlrknsxjvd5t</link>
                <description>الگوریتم ماشین بردار پشتیبانالگوریتم Support Vector Machine (ماشین بردار پشتیبان) یا به اختصار SVM از مدل های قدرتمند و همه کاره‌ی ماشین لرنینگ هست. این الگوریتم میتونه برای طبقه بندی های خطی یا غیر خطی، رگرسیون و حتی شناسایی داده های پرت هم استفاده بشه. SVM یکی از محبوب ترین مدل های ماشین لرنینگ هست و یادگیری این الگوریتم برای علاقه مندان ماشین لرنینگ ضروری هست. SVM ها بطور خاص مناسب طبقه بندی دیتاست های با اندازه کوچیک یا متوسط هستند.توی این مطلب میخوایم با مفاهیم اصلی SVM، طرز استفاده از اون‌ها و کارکرد اون‌ها رو بررسی کنیم.طبقه بندی با Linear SVMمفاهیم اصلی پشت SVM رو بهتره با عکس توضیح بدیم.عکسی که پایین میبینید بخشی از دیتاست MNIST هست که تو مطالب قبلی بهش پرداختیم. دو کلاس به راحتی میتونن به وسیله یک خط صاف جدا بشن چون قابلیت جدا شدن بوسیله خط رو دارن (Linearly Separable) شکل سمت چپ نشون دهنده مرز های تصمیم‌گیری (Decision Boundary) برای سه طبقه بندی خطی هست. مدلی که مرز تصمیم گیری اون بوسیله خط سبز نشون داده شده به قدری بد عمل میکنه که اصلا نتونسته کلاس رو به درستی جدا کنه. دو مدل بعدی روی ترینینگ ست خوب عمل میکنن اما به قدری به نمونه ها نزدیک هستند که احتمالا روی داده های جدید به خوبی عمل نکنند. در مقابل، خط توپر شکل سمت راست نشون دهنده مرز تصمیم گیری یک طبقه بندی SVM هست. این خط نه تنها دسته ها رو جدا میکنه بلکه تا جایی که امکان داره از نمونه های ترینینگ دور هست. میتونید SVM رو اینجوری در نظر بگیرید که بیشترین فضای ممکن بین کلاس ها رو با استفاده از خط های موازی کناری در نظر میگیره. به این روش میگن Large Margin Classification.دقت کنید که اضافه کردن نمونه های ترینینگ به خارج از فضای خالی میانی، تاثیری روی مرز های تصمیم گیری نخواهد داشت. در واقع مرز های تصمیم گیری بوسیله نمونه هایی که در بیرونی ترین جای فضای خالی هستند مشخص شده. به عبارت دیگه مرز های تصمیم گیری توسط نمونه هایی که با دایره مشخص شدن پشتیبانی میشن (Supported). به این نمونه ها میگن بردارهای پشتیبان (Support Vectors ).الگوریتم SVM به شدت به مقیاس داده ها حساس هست. همون طور که در شکل سمت چپ می‌بینید مقیاس عمودی بیشتر از افقی هست بخاطر همین فضای خالی نزدیک به افق هست. بعد از تغییر مقیاس داده ها با استفاده از StandardScaler واقع در کتابخانه Scikit-Learn میبینیم که مرز های تصمیم گیری شکل بهتری پیدا کردن.تغییر مقیاس دادهطبقه بندی حاشیه نرماگه تحمیل کنیم که تمام نمونه ها باید خارج از فضای خالی میانی باشن و حتما باید در سمت مناسب قرار بگیرن، بهش میگن Hard Margin Classification (طبقه بندی حاشیه سخت). این روش دو مشکل اساسی داره.اول اینکه فقط برای داده‌هایی کار میکنه که به‌صورت خطی جدا میشن. دوم اینکه به داده های پرت به‌شدت حساس هست. شکلی که پایین می‌بینید دیتاست Iris رو نشون میده که در سمت چپ یک داده پرت وجود داره و پیدا کردن یک Hard Margin برای اون غیر ممکن هست. در سمت راست هم مرز تصمیم‌گیری با چیزی که در بالا دیدیم، خیلی فرق داره و احتمالا به‌خوبی نمیتونه عمومی‌سازی کنه.طبقه بندی حاشیه سختبرای جلوگیری از این مشکلات، از یک مدل انعطاف پذیر تر استفاده می‌کنیم. هدف ما پیدا کردن یک تعادل مناسب بین بیشترین مقدار فضای خالی میانی و محدود کردن Margin Violations هست (نقض حاشیه یعنی قرار گرفتن نمونه ها در فضای میانی یا در سمت اشتباه) به این کار میگن Soft Margin Classification.وقتی مدل های SVM رو با استفاده از Scikit-Learn می‌سازیم، یک سری هایپرپارامتر ها رو می‌تونیم تنظیم کنیم. یکی از این هایپرپارامتر ها C نام داره. اگر مقدار اون کم باشه، حاصل مدل سمت چپ شکل پایین میشه. با مقدار زیاد به شکل سمت راست می‌رسیم. نقض حاشیه اتفاق خوبی نیست و بهتره تا جایی که امکان داره، کمتر اتفاق بیفته. اگرچه، در این مورد، درسته که مدل سمت چپ نقض حاشیه های زیادی داره اما احتمالا بهتر عمومی‌سازی می‌کنه. در واقع هایپرپارامتر C مقدار مجازات مدل برای هر نمونه‌ای رو که اشتباه طبقه بندی میکنه، تعیین میکنه. هر چقدر C کمتر باشه، حاشیه نرم تر میشه.طبقه بندی حاشیه نرماگر مدل شما Overfit شده باشه، می‌تونید با استفاده از هایپرپارامتر C اون رو Regularize کنیدمقدار C بزرگ: سوگیری کم، واریانس زیادمقدار C کوچک: سوگیری زیاد، واریانس کمکدی که در پایین مشاهده می‌کنید، دیتاست Iris رو لود می‌کنه، تغییر مقیاس فیچر ها رو انجام میده و یک مدل Linear SVM رو برای تشخیص گل های Iris Virginica درست میکنه. مدل حاصل رو می‌تونید در شکل بالا ببینید.برخلاف Logistic Regression، خروجی مدل های SVM احتمال هر کلاس نیستبه‌جای استفاده از کلاس LinearSVC، می‌تونستیم از کلاس SVC به همراه یک کرنل خطی استفاده کنیم. برای این کار، وقتی میخوایم مدل SVC رو بسازیم، می‌نویسیم SVC(kernel=&#x27;linear&#x27;, C=1). یا می‌تونیم از کلاس SGDClassifier به‌صورت SGDClassifier(loss=&#x27;hinge&#x27;, alpha=1/(m*C)) استفاده کنیم. با این روش می‌تونیم از Stochastic Gradient Descent استفاده کنیم تا یک Linear SVM Classifier درست کنیم. این روش به سرعت کلاس LinearSVC مارو به جواب نمی‌رسونه اما می‌تونه کمک کنه تا دیتاست های آنلاین یا دیتاست های بزرگ رو که در حافظه جا نمیشن، به‌خوبی مدیریت کنیم.یکی از تفاوت های LinearSVC با (&#x27;SVC(kernel=&#x27;linear در این هست که اولی از One-Vs-All استفاده می‌کنه و N * N-1 / 2 تا مدل مختلف درست میکنه (N تعداد کلاس هاست). در حالیکه دومی از One-Vs-One استفاده میکنه و N تا مدل مختلف درست میکنه.کلاس LinearSVC مقدار خطا رو Regularize می‌کنه، به‌همین سبب باید ترینینگ ست رو با کم کردن مقدار میانگین اون، متمرکز کنید.اگر از StandardScaler برای تغییر مقیاس استفاده کنید، این اتفاق به‌طور خودکار رخ میده. همچنین مطمئن باشید که هایپرپارامتر loss برابر hinge هست چون دیفالت اون چیز دیگه‌ای هست. در آخر، برای افزایش بهره‌وری، هایپرپارامتر dual رو برابر False قرار بدید، مگر اینکه فیچر ها بیشتر از نمونه های ترینینگ باشن.طبقه‌بندی با Nonlinear SVMگرچه Linear SVM ها کارایی زیادی دارن و در اکثر مواقع هم کار راه انداز هستند، اما خیلی از دیتاست ها اصلا خطی نیستند. یک راه برای مدیریت دیتاست های غیرخطی، اضافه کردن فیچر های بیشتر مثل Polynomial Feature هست. بعضی اوقات این راه جواب میده و به ما یک دیتاست خطی تحویل میده. مثلا شکل سمت چپ رو در نظر بگیرید. یک دیتاست رو با یک فیچر x1 نشون میده. این دیتاست به صورت خطی جداپذیر نیست. اما اگر فیچر دومی به نام x2 که برابر توان دوم x1 هست، اضافه کنید، میشه دیتاست رو با یک خط جدا کرد.اضافه کردن فیچر به دیتاستبرای پیاده‌سازی این ایده اول یک Pipeline شامل ترنسفورمر PolynomialFeatures، به‌همراه StandardScaler و LinearSVC درست می‌کنیم. بیاید این رو با دیتاست Moon امتحان کنیم. این دیتاست یک دیتاست تفننی محسوب میشه که برای Binary Classification کاربرد داره.کرنل چندجمله‌ایاضافه کردن فیچر های Polynomial کار آسونی هست. این روش نه فقط با SVMها بلکه با تمام الگوریتم های ماشین لرنینگ به‌خوبی کار می‌کنه. چندجمله‌ای های با درجه‌ی کم، نمی‌تونن با دیتاست های پیچیده کار کنن. چندجمله‌ای های با درجه‌ی بالا، فیچر های زیادی رو تولید میکنن که مدل رو آهسته میکنه.خوشبختانه وقتی از SVM استفاده می‌کنید، می‌تونید از یک تکنیک ریاضی با نام Kernel Trick استفاده کنید. کرنل باعث میشه بدون اضافه کردن Polynomial Feature، همون نتیجه‌ای رو بگیرید که انگار اون‌هارو اضافه کردید. با این روش می‌تونید درجه‌های بالا رو هم اضافه کنید. پس در این روش لازم نیست نگران تعداد بالای فیچر ها باشیم چون اصلا فیچری اضافه نمی‌کنیم. بیاید روی دیتاست Moon امتحانش کنیم:این کد با استفاده از یک کرنل درجه 3، یک SVM Classifier ترین می‌کنه. شکل حاصل رو می‌تونید در سمت چپ ببینید. در سمت راست یک مدل با کرنل درجه 10 رو می‌بینید. مطمئناً اگر مدل شما Overfit شده، باید درجه چندجمله‌ای رو کم کنید. در طرف مقابل، اگر Underfit شده، باید درجه چندجمله‌ای رو زیاد کنید. هایپرپارامتر coef0 مقدار تاثیرپذیری مدل از چندجمله‌ای درجه بالا، در مقابل چند جمله‌ای درجه پایین رو کنترل می‌کنه. کرنل چند جمله اییک روش خوب برای پیدا کردن هایپرپارامتر های مناسب، استفاده از Grid Search هست. اینکه بدونید هر هایپرپارامتر دقیقا چه کاری انجام میده کمک میکنه بهتر در فضای هایپرپارامتر ها جست‌و‌جو کنید.ویژگی های تشابهیک تکنیک دیگه برای حل مشکلات غیرخطی بودن، اضافه کردن فیچرهای جدید با استفاده از تابع تشابه هست. (SImilarity Function). این تابع، شبیه بودن هر نمونه به یک نقطه عطف خاص رو اندازه گیری میکنه. مثلا دیتاست یک بعدی مثال قبل رو در نظر بگیرید. دو نقطه عطف در x1 = -2 و x1 = 1 بهش اضافه میکنیم.حالا تابع تشابه رو Gaussian  Radial  Basis  Function با مقدار گاما برابر 0.3 در نظر می‌گیریم.این یک تابع با شکل زنگوله هست که مقدار اون از 0 (خیلی دور از نقطه عطف) تا 1 (خود نقطه عطف) متغیر هست. حالا آماده هستیم که فیچر های جدید رو محاسبه کنیم. برای مثال نمونه واقع در x1 = -1 رو در نظر بگیرید. فاصله اون از اولین نقطه عطف برابر 1 و از دومین نقطه عطف برابر 2 هست. به‌همین دلیل فیچر های جدید اون برابر x2 = exp( -0.3 * 1^2) (حدودا 0.74) و x3 = exp( -0.3 * 2^2) هست (حدودا 0.30). شکل سمت راست دیتاست ترنسفورم شده رو نشون میده(فیچر های اصلی حذف شدند) همونطور که می‌بینید حالا جداپذیر با خط هست.ممکنه براتون سوال پیش اومده باشه که چطور نقاط عطف رو انتخاب کنیم. یک راه ساده اینه که در مکان هر نمونه، یک نقطه عطف درست کنیم. با این کار، بعد های زیادی تولید میکنیم و شانس جداپذیر بودن با خط دیتاست ترنسفورم شده رو افزایش میدیم. نکته منفی این روش اینه که اگر m نمونه و n فیچر داشته باشیم، ترینینگ ست تبدیل شده m نمونه و m فیچر خواهد داشت (با فرض اینکه فیچر های اصلی رو حذف کردید) اگر دیتاست شما بزرگ باشه، با همون اندازه هم فیچر های زیادی خواهید داشت.کرنل Gaussian RBFاین روش هم مثل روش Polynomial Feature با هر الگوریتم ماشین لرنینگی سازگار هست اما از لحاظ محاسباتی، محاسبه همه فیچر های اضافه مخصوصا در ترینینگ ست های بزرگ، هزینه زیادی داره. حالا بیاید این کرنل رو پیاده سازی کنیم:این مدل رو میتونید در پایین سمت چپ شکل زیر ببینید. شکل های دیگه نشون دهنده ای مدل هایی هستند که با هایپرپارامتر های گاما و C متفاوتی ترین شدند.با افزایش گاما، منحنی زنگوله‌ای باریک تر و محدود تر میشه. نتیجه این کار، کوچکتر شدن رنج تاثیر هر نمونه هست: مرز تصمیم‌گیری نامنظم‌تر میشه و اطراف نمونه ها حرکت می‌کنه. پس در واقع گاما مثل یک هایپرپارامتر Regularization عمل میکنه: اگر مدل اورفیت شده، باید مقدارش رو کم کنید. اگر مدل آندرفیت شده، باید مقدارش رو زیاد کنید. (مثل هایپرپارامتر C)کرنل های دیگه‌ای هم وجود دارند اما به‌ندرت استفاده میشن. بعضی کرنل ها مخصوص برخی ساختمان داده‌ها هستند. کرنل های رشته‌ای (String Kernel) بعضی اوقات برای دسته بندی داده های متنی یا رشته های DNA استفاده میشن.از بین این همه کرنل، از کدوم باید استفاده کنیم؟ طبق فانون سرانگشتی، همیشه اول از کرنل خطی استفاده کنید (یادتون باشه که LinearSVC خیلی سریع‌تر از SVC(kernel=&#x27;linear&#x27;) هست) مخصوصاً وقتی که ترینینگ ست خیلی بزرگه یا فیچر های زیادی داره. اگه دیتاست زیاد بزرگ نیست، بهتره که کرنل Gaussian RBF رو هم امتحان کنید، در بیشتر مواقع به‌خوبی کار میکنه. اگر وقت و توان محاسباتی کافی دارید، می‌تونید کرنل های مختلف رو با استفاده از Cross-Validation و Grid Search امتحان کنید. وقتی ترینینگ ست شما ساختمان داده مخصوصی داره، پیشنهاد میشه کرنل های مختلف رو امتحان کنید.پیچیدگی محاسباتیاساس کلاس LinearSVC، کتابخانه liblinear هست که برای Linear SVM یک الگوریتم بهینه‌شده پیاده‌سازی می‌کنه. این کلاس از کرنل پشتیبانی نمیکنه اما با توجه به تعداد نمونه‌ها و فیچرها مقیاس خودش رو تقریباً به‌صورت خطی تغییر میده. پیچیدگی زمانی ترینینگ اون O(m * n) هست.اگر دقت بیشتری لازم دارید، این الگوریتم زمان بیشتری احتیاج داره. این مورد توسط هایپرپارامتر Tolerance کنترل میشه. (در Scikit-Learn با tol نشون داده میشه) در بیشتر طبقه‌بندی‌ها، مقدار دیفالت این هایپرپارامتر کارراه انداز هست.اساس کلاس SVC، کتابخانه libsvm هست که یک الگوریتم با پشتیبانی از کرنل پیاده‌سازی می‌کنه. پیچیدگی زمانی ترینینگ تایم معمولا بین O(m^2 * n) و O(m^3 * n) هست. متاسفانه این یعنی وقتی نمونه‌ها زیاد هستند، به شدت آهسته میشه. این الگوریتم برای ترینینگ ست های پیچیده‌ با اندازه کوچیک یا متوسط مناسب هست. مقیاس‌پذیری خوبی با تعداد فیچر ها انجام میده مخصوصا Sparse Features (یعنی وقتی که هر نمونه تعداد کمی فیچر غیرصفر داره) در این صورت، مقیاس الگوریتم با میانگین تعداد فیچر های غیرصفر هر نمونه، تقریباً تغییر می‌کنه.رگرسیون SVMهمونطور که قبلا هم اشاره شد، الگوریتم SVM گسترده هست. نه‌تنها از طبقه‌بندی خطی و غیرخطی پشتیبانی میکنه بلکه از پس رگرسیون خطی و غیر خطی هم برمیاد. برای استفاده از SVM برای رگرسیون، کافیه هدف رو برعکس کنید: به‌جای اینکه فضای میانی بین کلاس هارو تا جایی که امکان داره کوچیک کنیم تا نقض حاشیه کم بشه، رگرسیون SVM سعی میکنه تا جایی که امکان داره نمونه‌های زیادی رو در فضای میانی جا بده . به‌طور همزمان تعداد نقض حاشیه‌هارو پایین نگه داره. عرض این فضا توسط هایپرپارامتر اپسیلون کنترل میشه. شکلی که می‌بینید دو مدل رگرسیون خطی SVM رو نشون میشه با دو مقدار اپسیلون متفاوت.اضافه کردن نمونه های ترینینگ تاثیری در حدس های مدل ایجاد نمی‌کنه، در نتیجه این مدل نسبت به مقدار اپسیلون حساس نیست. (Epsilon Insensitive)می‌تونید از کلاس LinearSVR برای اجرای رگرسیون خطی SVM استفاده کنید. کدی که می‌بینید مدل سمت چپ شکل بالا رو تولید می‌کنه.برای اجرای رگرسیون غیرخطی، میتونید از یک مدل SVM به‌همراه کرنل استفاده کنید. شکلی که می‌بینید یک رگرسیون SVM رو روی یک ترینینگ ست درجه 2 با استفاده از کرنل درجه 2، نشون میده. مقدار Regularization شکل سمت چپ کم(مقدار C بزرگ) و شکل سمت راست زیاد هست (مقدار C کوچیک)کدی که می‌بینید از کلاس SVR استفاده میکنه تا مدل سمت چپ شکل رو ایجاد کنه. این کلاس از کرنل پشتیبانی میکنه.در واقع کلاس SVR معادل رگرسیون کلاس SVC و کلاس LinearSVR معادل رگرسیون کلاس LinearSVC هست. کلاس LinearSVR متناسب با اندازه ترینینگ ست، به‌صورت خطی تغییر می‌کنه(درست مثل LinearSVC) در حالیکه کلاس SVR وقتی ترینینگ ست بزرگ میشه، خیلی آروم عمل می‌کنه (مثل SVC)الگوریتم های SVM برای تشخیص داده‌های پرت هم کاربرد دارن. برای اطلاعات بیشتر به داکیومنت Scikit-Learn مراجعه کنیدخب این هم از بررسی الگوریتم SVM. امیدوارم مفید واقع شده باشه. https://virgool.io/@TabaMojj/%D8%A8%DB%8C%D8%A7%DB%8C%D8%AF-%DB%8C%DA%A9-%D9%85%D8%AF%D9%84-%D9%85%D8%A7%D8%B4%DB%8C%D9%86-%D9%84%D8%B1%D9%86%DB%8C%D9%86%DA%AF-%D8%A8%D8%B3%D8%A7%D8%B2%DB%8C%D9%85-%D9%82%D8%B3%D9%85%D8%AA-%D8%A7%D9%88%D9%84-bt7gdka3krxz  https://virgool.io/@TabaMojj/%D8%B7%D8%A8%D9%82%D9%87-%D8%A8%D9%86%D8%AF%DB%8C-%D8%A7%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A8%D8%A7-%D8%AF%DB%8C%D8%AA%D8%A7%D8%B3%D8%AA-mnist-b0ei4thu9xmo  https://virgool.io/@TabaMojj/%D8%A8%D8%B1%D8%B1%D8%B3%DB%8C-%D8%A7%D9%84%DA%AF%D9%88%D8%B1%DB%8C%D8%AA%D9%85-%D9%87%D8%A7%DB%8C-%D8%B1%DA%AF%D8%B1%D8%B3%DB%8C%D9%88%D9%86-%D8%AF%D8%B1-%D9%85%D8%A7%D8%B4%DB%8C%D9%86-%D9%84%D8%B1%D9%86%DB%8C%D9%86%DA%AF-uvcinqzvuf5e </description>
                <category>مجتبی میر یعقوب زاده</category>
                <author>مجتبی میر یعقوب زاده</author>
                <pubDate>Thu, 03 Sep 2020 16:02:17 +0430</pubDate>
            </item>
                    <item>
                <title>بررسی الگوریتم های رگرسیون در ماشین لرنینگ</title>
                <link>https://virgool.io/@TabaMojj/%D8%A8%D8%B1%D8%B1%D8%B3%DB%8C-%D8%A7%D9%84%DA%AF%D9%88%D8%B1%DB%8C%D8%AA%D9%85-%D9%87%D8%A7%DB%8C-%D8%B1%DA%AF%D8%B1%D8%B3%DB%8C%D9%88%D9%86-%D8%AF%D8%B1-%D9%85%D8%A7%D8%B4%DB%8C%D9%86-%D9%84%D8%B1%D9%86%DB%8C%D9%86%DA%AF-uvcinqzvuf5e</link>
                <description>الگوریتم های رگرسیونتوی مطالب قبلی ما با مدل های ماشین لرنینگ کار کردیم. تا به اینجای کار الگوریتم ها برای ما مثل جعبه سیاه بودن. دونستن طرز کار هر الگوریتم به ما کمک میکنه که که الگوریتم مناسب رو به همراه هایپرپارامتر های ایده‌آل انتخاب کنیم. همچنین باعث میشه که مشکلات پیش اومده رو به راحتی حل کنیم و از بروز مشکلات بعدی جلوگیری کنیم. توی این مطلب میخوایم مدل رگرسیون خطی رو بررسی کنیم.رگرسیون خطییک مدل Linear Regression با محاسبه یک حاصل جمع وزن دار از فیچر ها و یک ثابت به نام Bias Term یا Intercept Term ، یک حدس میزنه. معادله رگرسیون خطی رو میتونید مشاهده کنید:این معادله رو میتونیم با استفاده از بردار، خلاصه تر هم بنویسیم:در ماشین لرنینگ بردار ها معمولا بصورت بردار های ستونی، که آرایه های دو بعدی با یک ستون هستند، نشون داده میشن. اگه θ و x بردار های ستونی باشن، مقدار حدس زده شده برابر هست با ضرب x در ماتریس ترانهاده θ یا Transpose θ. خب این از رگرسیون خطی. ولی چطور ترین کنیم ؟ یادتون باشه توی مطلب قبلی گفتیم ترین یک مدل یعنی تنظیم پارامتر های این مدل بطوری که بهترین عملکرد رو روی ترینینگ ست نشون بده. برای این کار، اول به یک معیار احتیاج داریم که بدونیم مدل چجوری عمل میکنه. همچنین گفتیم که یکی از رایج ترین راه ها استفاده از RMSE هست. پس برای یک مدل رگرسیون خطی، باید مقدار θ رو جوری پیدا کنیم که RMSE رو به کمترین حد برسونه. در عمل، ساده تر هست که مقدار MSE رو کم کنیم چون این کار مقدار RMSE رو هم کم میکنه. (وقتی یک عدد کوچیک بشه ، جذرش هم کوچیک میشه)فرمول MSE رو میتونید ببینید:معادله نرمالبرای پیدا کردن مقدار θ ای که مقدار تابع هزینه (Cost Function) رو به حداقل میرسونه، از یک معادله به نام معادله نرمال یا Normal Equation استفاده میکنیم.حالا بیاید چند تا داده خطی درست کنیم تا این معادله رو امتحان کنیم. معادله ای که ازش استفاده میکنیم y= 4 + 3x + noise هست.دقت کنید که بخاطر رندوم بودن داده ها، شکل و اعدادی که بدست میارید متفاوت خواهند بود:حالا بیاید تتا هت رو با استفاده از معادله نرمال محاسبه کنیم. برای این کار اول از تابع inv ، واقع در ماژول np.linalg ، استفاده میکنیم تا معکوس ماتریس رو محاسبه کنیم و از تابع dot استفاده میکنیم تا ضرب ماتریس ها رو انجام بدیم:چیزی که در بهترین حالت رخ میده و ما امیدوار بودیم که رخ بده، این بود که مقدار تتا صفر برابر 4 و تتا 1 برابر 3 بشه ولی خب 4.33 و 3.11 شده. به اندازه کافی نزدیک هست. علت اینکه نتونسته مقدار دقیق رو به دست بیاره این هست که ما به داده ها نویز اضافه کردیم. (چیزی که در واقعیت هم در داده ها رخ میده)حالا با استفاده از تتا هت بدست اومده میخوایم یک حدس بزنیم:پیاده سازی رگرسیون خطی با استفاده از Scikit-Learn ساده است. برای دسترسی به وزن فیچر ها از coef و میزان خطا از intercept استفاده میکنیم:کلاس LinearRegression بر پایه تابع scipy.linalg.lstsq هست (تابع برای محاسبه Least Square) که میتونیم به طور مستقیم فراخوانی کنیم:این تابع ، این مقدار رو محاسبه میکنه. توی این فرمول، ایکس مثبت مقدار ماتریس شبه-وارون هست ( به طور دقیق تر ، Moore-Penrose Inverse ) میتونیم از تابع np.linalg.pinv برای محاسبه استفاده کنیم:ماتریس شبه-وارون توسط یک تکنیک به نام Singular Value Decomposition محاسبه میشه. برای درک بهتر این فرمول میتونید این لینک رو بخونید. این راه کارآمد تر از محاسبه معادله نرمال هست و حتی اگه ماتریس مورد نظر وارون پذیر نباشه، همچنان میتونه ماتریس وارون رو محاسبه کنه.پیچیدگی محاسباتیمعادله نرمال، حاصل ضرب ماتریس X در ترانهاده X رو معکوس میکنه. این ماتریس یک ماتریس n+1 در n+1 هست که n تعداد فیچر هاست. پیچیدگی محاسباتی محاسبه همچین ماتریسی بین O(n^2.4) تا O(n^3) هست. هر دوی معادله نرمال و SVD، با افزایش تعداد فیچر ها، به شدت کند میشن. البته نکته مثبت این دو، خطی بودن اون هاست که باعث میشه در صورت وجود حافظه به اندازه کافی، ترینیگ ست های بزرگ رو مدیریت کننهمچنین، وقتی مدل رگرسیون خطی رو ترین کردیم ( حالا یا با روش معادله نرمال یا هر روش دیگه ای ) حدس ها سریع تر انجام میشن چون پیچیدگی محاسباتی خطی هست. به عبارت دیگه، با دو برابر شدن داده ها یا فیچر ها، زمان هم دو برابر میشه.حالا یک روش دیگه برای ترین یک مدل رگرسیون خطی معرفی میکنیم که مناسب وقت هایی هست که تعداد فیچر ها یا نمونه ها خیلی زیاد هست و توی حافظه جا نمیشن.گرادیان کاهشیگرادیان کاهشی (Gradient Descent) یک الگوریتم بهینه سازی هست که میتونه راه حل بهینه رو برای مسائل گوناگونی پیدا کنه. ایده GD این هست که پارامتر ها رو برای کم شدن تابع هزینه، بصورت تکرار شونده (iteratively) بهینه سازی کنیم.فرض کنید توی مه در یک کوهستان گم شدید و فقط میتونید شیب زمین زیر پاتون رو حس کنید. یک استراتژی خوب برای رسیدن به پایین دره این هست که به جهت تند ترین شیب به پایین دره حرکت کنیم. این کاری هست که GD انجام میده: گرادیان محلی تابع خطا رو با توجه به تتا محاسبه میکنه و در جهتی حرکت میکنه که گرادیان کاهش پیدا میکنه. وقتی گرادیان صفر بشه ، به مینیمم رسیدیم!در آغاز، یک مقدار رندوم به تتا میدیم (به این کار میگن Random Initialization). بعد با برداشتن قدم های کوچیک، سعی میکنیم که گرادیان رو بهتر کنیم و مقدار تابع هزینه (MSE) رو کم کنیم تا جایی که الگوریتم همگرا به مینیمم بشه.یک پارامتر مهم در گرادیان کاهشی، اندازه قدم ها هست که توسط هایپرپارامتر Learning Rate مشخص میشه. اگه LR خیلی کوچیک باشه، الگوریتم تکرار های زیادی رو برای همگرایی انجام میده و زمان زیادی طول میکشه.اگر LR خیلی زیاد باشه، ممکنه به جهات مختلف پرش کنیم و الگوریتم نتونه مقدار مناسب رو پیدا کنه و واگرایی رخ میده.و در آخر، این نکته رو در نظر داشته باشید که همه تابع های هزینه، ظاهر خوبی ندارن و لزوما به شکل کاسه نیستند. ممکنه سوراخ، چاله ،پشته و یا هر جور بالا پایینی وجود داشته باشه که باعث بشه پیدا کردن مینیمم کار سختی بشه. شکل زیر دو تا از چالش های GD رو نشون میده. اگر Random Initialization در طرف چپ باشه، اونوقت به یک مینیمم محلی همگرا میشه که به خوبی مینیمم سراسری نیست. اگر در طرف راست باشه، زمان خیلی زیادی طول میکشه تا قسمت مسطح رو رد کنه. اگر هم خیلی زود بایسیتیم، هیچوقت نمیتونیم به مینیمم سراسری برسیم.خوشبختانه MSE برای یک مدل رگرسیون خطی یک تابع محدب (Convex Function) هست. به این معنی که اگر هر دو نقطه دلخواه روی این محدب رو انتخاب کنید، خط بین این دو نقطه هیچگاه از محدب رد نمیشه؛ یعنی هیچ مینیمم محلی ای وجود نداره و فقط یک مینیمم سراسری وجود داره. همچنین یک تابع پیوسته هست که شیب اون هیچوقت ناگهانی تغییر نمیکنه. این دو فاکتور باعث میشن که گرادیان کاهشی به اندازه کافی به مینیمم سراسری نزدیک بشه (البته اگر به اندازه کافی صبر کنیم و LR هم زیاد نباشه)در واقع تابع هزینه شکل کاسه مانندی داره اما اگر فیچر ها در مقیاس های متفاوت باشن، میتونه دراز بشه. شکل زیر گرادیان کاهشی رو روی یک ترینینگ ست نشون میده که سمت چپ فیچر 1 و 2 مقیاس های برابر دارن و سمت راست مقادیر فیچر 1 کوچک تر از فیچر 2 هست.(چرا شکل در طول فیچر 1 کشیده شده ؟ چون این فیچر کوچک تر هست و برای تاثیرگذاری روی تابع هزینه، این فیچر باید تغییر بیشتری بکنه. بخاطر همین شکل در طول این فیچر کشیده شده)همونطور که میبینید در شکل چپ، الگوریتم مستقیم میره به سمت مینیمم و به سرعت اون رو پیدا میکنه. در حالیکه در سمت راست اول در جهتی که تقریبا با مینیمم زاویه 90 درجه داره حرکت میکنه و بعدش آروم آروم به سمت مینیمم نزدیک میشه. در نهایت هم به مینیمم میرسه ولی خب زمان بیشتری طول میکشه.وقتی از گرادیان کاهشی استفاده میکنیم، حتما باید مطمئن باشیم که همه فیچر ها در مقیاس برابر هستند وگرنه برای همگرایی زمان زیادی لازم میشه. برای تغییر مقیاس هم از کلاس StandardScaler واقع در کتابخانه Scikit-Learn استفاده میکنیم.شکل بالا همچنین نشون میده که ترین یک مدل یعنی جستجو برای پیدا کردن ترکیبی از پارامتر ها که تابع هزینه رو به حدقل میرسونه. در واقع یک جستجو در فضای پارامتر های مدل هست: هر چقدر که مدل پارامتر های بیشتری داشته باشه، فضای مدل ابعاد بیشتری داره و جستجو هم سخت تر میشه. پیدا کردن سوزن تو انبار کاه 300 بعدی سخت تر از 3 بعدی هست. خوشبختانه، چون تابع هزینه برای رگرسیون خطی به شکل کاسه هست، سوزن در پایین کاسه قرار داره.گرادیان کاهشی دسته‌ایبرای استفاده از گرادیان کاهشی، باید گرادیان تابع هزینه رو با توجه به هر پارامتر مدل محاسبه کنیم. به عبارت دیگه، باید مقدار تغییر تابع هزینه با تغییر تتا رو محاسبه کنیم. به این کار میگن مشتق جزئی. مثل این میمونه که سوال بپرسید &quot;اگر به سمت شرق برم، شیب زمین کوهستان زیر پام چقدر تغییر میکنه ؟ &quot; و همین سوال رو درباره شمال و تمام ابعاد موجود بپرسید. معادله ای که در زیر میبینید مشتق جزئی تابع هزینه رو محاسبه میکنه:به جای محاسبه تک تک مشتق های جزئی، میتونیم از معادله پایین استفاده کنیم تا یک دفعه تمام محاسبات رو انجام بده. بردار گرادیان که در زیر میبینید، شامل همه مشتق های جزئی تابع هزینه هست (برای هر پارامتر مدل):توجه کنید که این فرمول در هر قدم گرادیان کاهشی، محاسبات رو برای همه ترینینگ ست انجام میده! بخاطر همین بهش میگن گرادیان کاهشی دسته‌ای (Batch Gradient Descent) چون در هر قدم از کل ترینینگ ست استفاده میکنه. بخاطر همین روی ترینینگ ست های بزرگ به شدت کند هست (در ادامه الگوریتم های گرادیان کاهشی با سرعت بیشتر رو هم خواهیم دید). گرچه، گرادیان کاهشی با افزایش تعداد فیچر ها، به خوبی میتونه خودش رو تطبیق بده؛ بطوریکه ترین یک مدل رگرسیون خطی با هزاران فیچر با استفاده از گرادیان کاهشی، سریع تر از استفاده از معادله نرمال یا SVD هستبردار گرادیان به سمت بالا هست. بعد از محاسبه این بردار، کافیه که در جهت مخالف و به سمت پایین حرکت کنیم. معادله ای که پایین می‌بینید بیان کننده همین موضوع هست. توی این فرمول Eta نشون دهنده اندازه قدم های رو به پایین ما هست:حالا میخوایم این الگوریتم رو پیاده سازی کنیم:خب این مقداری هست که معادله نرمال هم پیدا کرده بود. این یعنی گرادیان کاهشی ما به خوبی کار میکنه. توی تصاویر زیر میتونید حالاتی رو ببینید که مقادیر eta یا Learning Rate متفاوت هست:در سمت چپ LR کم هست. الگوریتم در نهایت به راه حل میرسه اما خب زمان زیادی طول میکشه. عکس وسط خوب به نظر میاد. فقط با چند تکرار تونسته به راه حل نزدیک بشه. عکس سمت راست LR زیاد هست. الگوریتم از راه حل دور تر میشه و فقط از جایی به جای دیگه میپره.برای پیدا کردن یک LR خوب، میتونیم از Grid Search استفاده کنیم. بهتر هست که تعداد تکرار ها رو کم کنید تا GS مدل هایی رو که خیلی طول میکشن به راه حل نزدیک بشن، حذف کنه.ممکنه براتون سوال پیش بیاد که چطور باید تعداد تکرار ها رو مشخص کرد. اگر خیلی کم باشه، ممکنه الگوریتم موقعی که هنوز فاصله زیادی با راه حل داریم، بایسته. اگر خیلی زیاد باشه، زمان زیادی رو، در حالی که پارامتر ها هم تغییری نمیکنن، تلف کردیم. یک راه حل ساده اینه که تعداد تکرار ها رو زیاد بذاریم اما هر وقت بردار گرادیان خیلی کوچیک شد، الگوریتم رو متوقف کنیم. یعنی وقتی که نرم بردار از یک مقدار کوچیکی به نام tolerance کمتر شد چون این اتفاق موقعی میفته که گرادیان کاهشی تقریبا به مینیمم رسیده. (tolerance رو با این نماد نشون میدن)نرخ همگراییوقتی تابع هزینه به شکل محدب هست و شیب‌ش ناگهانی تغییر نمیکنه، استفاده از Batch Gradient Descent به‌همراه یک LR ثابت ما رو به راه حل میرسونه اما باید کمی صبر کنیم. بسته به شکل تابع هزینه، میتونه به این تعداد تکرار طول بکشه تا به راه حل برسیم. اگر Tolerance رو تقسیم بر 10 کنیم تا به دقت بیشتری برسیم، اونوقت زمان اجرای الگوریتم 10 برابر بیشتر میشه.گرادیان کاهشی نامنظممشکل اصلی Batch Gradient Descent این هست که برای محاسبه گرادیان از کل ترینینگ ست استفاده میکنه که باعث میشه زمان زیادی طول بکشه. در حالیکه Stochastic Gradient Descent در هر قدم بصورت رندوم یک نمونه از ترینینگ ست رو انتخاب میکنه و گرادیان رو بر اساس اون نمونه محاسبه میکنه. مطمئناً استفاده از یک نمونه به‌جای کل نمونه ها، زمان محاسبه رو کم میکنه. همچنین با استفاده از این روش، کار با دیتاست های بزرگ هم امکان پذیر میشه چون در هر تکرار فقط یک نمونه در حافظه قرار میگیره.از طرف دیگه، بخاطر Stochastic یا همون رندوم بودن این الگوریتم، در مقایسه با Batch Gradient Descent کمتر منظم هست چون به جای آروم آروم کم کردن مقدار برای رسیدن به مینیمم، تابع هزینه مکرر بالا پایین میشه. با گذشت زمان به مینیمم میرسه اما وقتی به اونجا برسه آروم نمیشه بلکه به اطراف حرکت میکنه. پس وقتی الگوریتم می‌ایسته، مقدار پارامتر های نهایی خوب هستن ولی ایده‌آل نیستند.وقتی تابع هزینه شکل نامنظم داشته باشه، این الگوریتم میتونه از مینیمم محلی بپره. پس SGD در مقابل BGD، شانس بهتری برای پیدا کردن مینمم سراسری داره.به‌خاطر همین، رندوم بودن برای فرار از مینیمم محلی خوبه اما چون باعث میشه الگوریتم هیچوقت توی مینیمم متوقف نشه، بده. یک راه حل برای این دو راهی اینه که رفته رفته LR رو کم کنیم. قدم ها بزرگ آغاز میشن (که باعث میشه سریع پیشرفت کنیم و از مینیمم محلی عبور کنیم) بعد کوچکتر و کوچکتر میشن. این به الگوریتم اجازه میده که توی مینیمم سراسری بایسته. تابعی که در هر تکرار مقدار LR رو مشخص میکنه Learning Schedule نام داره. اگر LR خیلی سریع کم بشه، ممکنه تو مینیمم محلی گیر کنیم. اگر LR خیلی آروم کم بشه، ممکنه برای مدت طولانی دور مینیمم بچرخیم و آخر هم توی یک راه حل نیمه بهینه بایسیتم.این کد با استفاده از یک LS ساده الگوریتم SGD رو پیاده سازی میکنه:به طور قراردادی تعداد تکرار ها رو برابر m گذاشتیم ( m در تصاویر بالاتر مقدار دهی شده) هر دور رو میگن epoch. در حالیکه BGD هزار بار کل ترینینگ ست رو تکرار کرد، این کد فقط 50 بار تکرار میکنه و به راه حل خوبی هم میرسه.دقت کنید چون نمونه ها رو رندوم انتخاب میکنیم، بعضی نمونه ها ممکنه چند بار انتخاب و بعضی هم اصلا انتخاب نشن. اگر میخواید مطمئن باشید که این اتفاق نمیفته، میتونید ترینینگ ست رو Shuffle کنید. بعد دونه دونه هر نمونه رو انتخاب کنید و بعد دوباره Shuffle کنید. اگرچه که، این راه حل آروم به نتیجه میرسه.وقتی از SGD استفاده میکنیم، نمونه ها باید مستقل و به طور یکسان توزیع شده باشند (IID) تا مطمئن بشیم پارامتر ها به سمت مینیمم سراسری کشیده میشن. یک راه ساده برای این کار، شافل کردن نمونه ها هنگام ترینینگ هست (انتخاب هر نمونه بصورت رندوم یا شافل کردن ترینینگ ست در آغاز هر تکرار) برای مثال اگر نمونه ها بر اساس لیبل مرتب شده باشند و ما اونهارو شافل نکنیم، SGD شروع به بهینه شدن برای یک لیبل میکنه و همینطور ادامه میده و در نهایت هم به مینیمم سراسری نمیرسهاگر بخوایم رگرسیون خطی رو با استفاده از SGD اعمال کنیم، میتونیم از کلاس SGDRegressor استفاده کنیم. دیفالت این کلاس بهینه سازی MSE هست. کدی که پایین میبینید برای 1000 بار تکرار میشه تا موقعی که هزینه در هر تکرار به زیر 0.0001 برسه (max_iter=1000, tol=1e-3) مقدار LR اولیه برابر با 0.1(eta0=0.1) و از Regularization هم استفاده نمیکنه (penalty=None ، بعدا درباره این پارامتر صحبت میکنیم). در آخر هم به راه حلی میرسیم که نزدیک به جواب معادله نرمال هست:گرادیان کاهشی با دسته کوچکآخرین الگوریتم گرادیان کاهشی، Mini-batch Gradient Descent نام داره. اگه دو تا الگوریتم قبلی رو فهمیده باشید، این کاری نداره. این الگوریتم، در هر قدم به جای اینکه گرادیان رو بر اساس همه ترینینگ ست محاسبه کنه (BGD) یا بر اساس یک نمونه محاسبه کنه (SGD)، از یک دسته رندوم و کوچیک ترینینگ ست استفاده میکنه که بهش میگن Mini-batch. نکته مثبت Mini-batch GD نسبت به Stochastic GD این هست که میتونیم با بهینه سازی سخت افزار برای عملیات ماتریسی، به عملکرد بهتری برسیم، مخصوصاً وقتی که از GPU استفاده میکنیم.  ( آیا دیپ لرنینگ به کارت گرافیک احتیاج دارد؟ )روند پیشرفت این الگوریتم در فضای پارامتر ها به نسبت باقی الگوریتم ها، کمتر پراکنده هست. همونطور که میبینید، MbGD به نسبت SGD یه کمی بیشتر نزدیک مینیمم میشه ولی فرار از مینیمم های محلی براش سخت هست (این برای مسائلی که توشون مینیمم محلی وجود داره صادق هست نه رگرسیون خطی) در شکل زیر میتونید مسیر هایی رو که این سه الگوریتم گرادیان در فضای پارامتر ها طی میکنن، ببینید. هر سه در نهایت به مینیمم سراسری رسیدن اما BGD در مینیمم می‌ایسته، در حالیکه دو الگوریتم دیگه به حرکت دور مینیمم ادامه میدن. البته فراموش نکنید که BGD زمان زیادی رو در هر قدم سپری میکنه؛ و اگر از یک Learning Schedule خوب استفاده کرده باشید، میتونید با این دو الگوریتم هم به مینیمم برسید.حالا بیاید این الگوریتم ها رو برای رگرسیون خطی مقایسه کنیم. نکته اول : معادله نرمال فقط در رگرسیون خطی کاربرد داره. اما از گرادیان کاهشی میشه برای مدل های دیگه هم استفاده کردنکته دوم : m تعداد نمونه های ترینینگ هست و n تعداد فیچر هاستنکته سوم : بعد از اتمام ترینینگ، هیچ فرقی بین مدل هایی که از طریق این الگوریتم ها به دست میاریم، وجود نداره و در نهایت به یک روش روند حدس انجام میشهرگرسیون چند جمله ایاگه داده هامون پیچیده تر از یک خط مستقیم باشن چی؟ خوشبختانه میتونیم از یک مدل خطی برای داده های غیرخطی استفاده کنیم. یک راه ساده برای انجام این کار، اضافه کردن توان های هر فیچر به عنوان یک فیچر جدید هست. بعد با استفاده از این فیچر های جدید، یک مدل خطی رو ترین میکنیم. این تکنیک Polynomial Regression نام داره.بیاید یک مثال ببینیم. اول با استفاده از یک معادله درجه دو یک سری داده غیر خطی درست میکنیم:خب همونطور که مشخصه یک خط صاف هیچوقت با این داده ها تطابق پیدا نمیکنه. بخاطر همین از کلاس PolynomialFeatures برای ترنسفورم ترینینگ دیتا استفاده میکنیم و بعد مربع هر فیچر رو محاسبه میکنیم و بعنوان فیچر جدید اضافه میکنیم:حالا X_poly شامل فیچر های اصلی به اضافه مربع اون فیچر هاست. میتونیم این داده ها رو به یک مدل رگرسیون خطی بدیم:عملکرد مدل خوبه. تابع اصلی ما y = 0.5x^2 + 1.0x + 2.0 بود، چیزی که مدل به دست آورده حدودا برابر y = 0.5x^2 + 1.0x + 2.48 هست.توجه کنید که موقعی که چند تا فیچر هست، Polynomial Regression میتونه رابطه بین فیچر ها رو بدست بیاره (Linear Regression نمیتونه). علت این توانایی این هست که PolynomiaFeatures تمام حالات فیچر ها رو تا اون درجه ای که ما مشخص کردیم اضافه میکنه. برای مثال، اگه دو فیچر a و b داشتیم و درجه رو 3 مشخص کرده بودیم، نه تنها فیچر های a^3 , a^2 , b^3 , b^2 اضافه میشد، بلکه ab , a^2b و ab^2 هم اضافه میشدن.کلاس PolynomialFeatures با درجه d، یک آرایه شامل n فیچر رو به یک آرایه شامل (d!n!)/!(n+d) فیچر تبدیل میکنه. دقت کنید که عدد حاصل ممکنه خیلی بزرگ باشه!منحنی های یادگیریاگر از یک Polynomial Regression با درجه بزرگ استفاده کنید، احتمالا مدل به نسبت Linear Regression تطابق بهتری با داده ها پیدا کنه. برای مثال عکس زیر یک مدل با درجه 300 رو نشون میده و اون رو با درجه 1 و 2 مقایسه میکنه. توجه کنید که مدل با درجه 300 چقدر سعی میکنه خودش رو به نمونه ها نزدیک کنه:مدل با درجه 300 اورفیت و مدل با درجه 1 آندرفیت شده. بهترین حالت برای این مدل استفاده از درجه 2 هست و این حالت منطقی هم هست؛ چون داده ها با استفاده از یک معادله درجه 2 ساخته شدند. اما در حالت کلی ما اطلاعی از تابعی که داده ها رو تولید کرده نداریم. پس چطور میتونیم درمورد پیچیدگی مدل تصمیم بگیریم؟ چطور میتونیم بگیم که مدل اورفیت یا آندرفیت شده؟ https://virgool.io/@TabaMojj/%D9%85%D9%82%D8%AF%D9%85%D9%87%D8%A7%DB%8C-%D8%A8%D8%B1-%D8%A8%D8%B1%D8%A7%D8%B2%D8%B4-%D9%85%D8%AF%D9%84-%D8%AF%D8%B1-%D9%85%D8%A7%D8%B4%DB%8C%D9%86-%D9%84%D8%B1%D9%86%DB%8C%D9%86%DA%AF-chmnm5ch7f0y توی مطالب قبل، از Cross-Validation استفاده کردیم تا عملکرد مدل رو تخمین بزنیم. اگر یک مدل روی ترنینگ دیتا عالی عمل کنه اما توی CV عملکرد بدی داشته باشه، مدل اورفیت شده و اگر در هر دو عملکرد بدی داشته باشه، آندرفیت شده. این یک راه برای فهمیدن سادگی یا پیچیدگی مدل هست.یک راه دیگه استفاده از منحنی های یادگیری (Learning Curves) هست. این نمودار ها نشون دهنده عملکرد مدل روی ترینینگ ست و ولیدیشن ست هست. برای تولید این نمودار ها، چندین بار مدل رو با زیر مجموعه هایی با اندازه مختلف ترین میکنیم. تابعی که پایین می‌بینید، با دریافت چند داده ترینینگ، منحنی یادگیری مدل رو رسم میکنه:from sklearn.metrics import mean_squared_errorfrom sklearn.model_selection import train_test_splitdef plot_learning_curves(model, X, y):    X_train, X_val, y_train, y_val = train_test_split(X, y, test_size=0.2, random_state=10)    train_errors, val_errors = [], []    for m in range(1, len(X_train)):        model.fit(X_train[:m], y_train[:m])        y_train_predict = model.predict(X_train[:m])        y_val_predict = model.predict(X_val)        train_errors.append(mean_squared_error(y_train[:m], y_train_predict))        val_errors.append(mean_squared_error(y_val, y_val_predict))    plt.plot(np.sqrt(train_errors), &quot;r-+&quot;, linewidth=2, label=&quot;train&quot;)    plt.plot(np.sqrt(val_errors), &quot;b-&quot;, linewidth=3, label=&quot;val&quot;)    plt.legend(loc=&quot;upper right&quot;, fontsize=14)       plt.xlabel(&quot;Training set size&quot;, fontsize=14)     plt.ylabel(&quot;RMSE&quot;, fontsize=14)    lin_reg = LinearRegression()plot_learning_curves(lin_reg, X, y)این مدل آندرفیت شده و احتیاج به کمی توضیح داره. اول به عملکرد روی ترینینگ دیتا توجه کنید. وقتی یک یا دو تا نمونه توی ترینینگ دیتا وجود داره، مدل به راحتی میتونه با اونها تطابق پیدا کنه. بخاطر همین هست که منحنی از صفر شروع میشه. اما همینطور که نمونه های تازه اضافه میشن، مدل نمیتونه با همه اونها تطابق پیدا کنه چون هم داده ها نویز زیادی دارن و اینکه داده ها اصلا خطی نیستند. این روند افزایش خطا تا جایی پیش میره که به یک سطح مسطح میرسه و دیگه با افزایش نمونه مقدار خطا تغییری نمیکنه. حالا بیاید به عملکرد مدل روی ولیدیشن ست نگاه کنیم. وقتی مدل با نمونه های کمی ترین میشه، نمیتونه به خوبی تطابق پیدا کنه. بخاطر همین در ابتدا مقدار خطا زیاد هست. بعد با افزایش تعداد نمونه ها، مدل یاد میگیره و خطا هم کاهش پیدا میکنه. این منحنی ها برای یک مدل که آندرفیت شده عادیه. هر دوی منحنی ها به یک سطح مسطح میرسن که نزدیک هم هستند و مقدار زیادی هم دارن.اگر مدل آندرفیت شده، زیاد کردن نمونه های ترینینگ تاثیری ایجاد نمیکنه. به جای این کار، باید از مدل پیچیده تر یا فیچر های بهتر استفاده کردحالا بیاید به منحنی یادگیری یک مدل با درجه 10 نگاه کنیم:این منحنی هم کمی شبیه به قبلی هست اما تفاوت هایی داره:مقدار خطا روی ترینینگ ست خیلی کمتر از Linear Regression هستفاصله خیلی زیادی بین منحنی ها هست. این یعنی مدل فقط عملکرد خوبی روی ترینینگ دیتا داره که نشون دهنده اورفیتینگ هست. اگر از ترینینگ ست بزرگتری استفاده میکردیم، فاصله بین دو نمودار کمتر میشد.یک راه برای افزایش عملکرد یک مدل اورفیت شده، افزایش ترینینگ دیتا تا جایی هست که مقدار دو خطا نزدیک هم بشنمبادله سوگیری و واریانسخطا های یک مدل جمع سه نوع خطای مختلف هست:سوگیری (Bias) این قسمت از خطا ها بخاطر اشتباه در فرضیات هست؛ مثلا فرض اینکه داده ها خطی هستند در حالی که درجه دو هستند. علت سوگیری زیاد یک مدل بخاطر آندرفیت بودن در ترینینگ ست هست.واریانس (Variance)این قسمت از خطا بخاطر حساسیت فوق‌العاده مدل به تغییرات کوچک در ترینینگ دیتا هست. یک مدل با درجه های آزادی زیاد (مثل Polynomial با درجه بالا) بیشتر در معرض داشتن واریانس بالا هست و واریانس بالا منجر به اورفیت شدن میشه.خطای غیرقابل تقلیل (Irreducible Error)این نوع خطا بخاطر نویز داشتن خود داده ها رخ میده. تنها راه برای کاهش این ارور تمیز کردن داده هاست ( مثلا مشکل منبع اطلاعات رو حل کنیم یا داده های پرت رو حذف کنیم)افزایش پیچیدگی مدل معمولا منجر به افزایش واریانس و کاهش سوگیری میشه. از طرفی، کاهش پیچیدگی مدل منجر به کاهش واریانس و افزایش سوگیری میشه. به خاطر همین بهش میگن Bias Varience Trade-Off. https://virgool.io/@TabaMojj/%D9%85%D9%82%D8%AF%D9%85%D9%87%D8%A7%DB%8C-%D8%A8%D8%B1-%D8%AE%D8%B7%D8%A7-%D9%88-%D9%88%D8%A7%D8%B1%DB%8C%D8%A7%D9%86%D8%B3-%D8%AF%D8%B1-%D9%85%D8%A7%D8%B4%DB%8C%D9%86-%D9%84%D8%B1%D9%86%DB%8C%D9%86%DA%AF-q72dmim5ygwk منظم سازی مدل های خطیهمونطور که در مطلب قبلی دیدیم، یک راه خوب برای کم کردن اورفیتینگ، Regularize کردن مدل هست (اعمال محدودیت). هرچقدر درجه آزادی کمتری داشته باشه، کمتر اورفیت میشه. یک راه ساده برای Regularize کردن مدل Polynomial، کم کردن درجه هاست.برای یک مدل خطی، این عمل بصورت اعمال محدودیت در وزن های مدل انجام میشه. در ادامه مطلب درباره مدل های Ridge Regression، Lasso Regression و Elastic Net میخونید که سه راه مختلف برای محدود کردن وزن مدل هستن.رگرسیون ستیغیاین رگرسیون Ridge Regression یا Tikhonov Regularization نام داره. این مقدار منظم سازی به تابع هزینه اضافه میشه. این مقدار، الگوریتم رو مجبور میکنه که نه تنها با داده ها تطابق پیدا کنه بلکه وزن های مدل رو تا جایی که امکان داره کوچیک نگه داره. دقت کنید که این مقدار باید در مرحله ترینینگ به تابع هزینه اضافه بشه. وقتی ترین مدل تموم شد، از معیار های ارزیابی عملکرد بدون این مقدار استفاده میکنیم. (استفاده از تابع های هزینه متفاوت در ترینینگ و تستینگ، کاملا رایج هست)در فرمولی که دیدید، مقدار منظم سازی رو میشه با هایپرپارامتر آلفا مشخص کرد. اگر مقدار آلفا صفر باشه، درواقع داریم از رگرسیون خطی استفاده میکنیم. اگر آلفا خیلی بزرگ باشه، تمام وزن ها خیلی نزدیک به صفر میشن و نتیجه یک خط صاف خواهد بود که از میان میانگین داده ها عبور میکنه. تابع هزینه Ridge Regression رو میتونید ببینید:حتما قبل از استفاده از Ridge Regression باید مقیاس داده ها رو تغییر بدیم. این مدل هم مثل بیشتر مدل های منظم، به مقایس داده های ورودی حساس هست.عکس زیر چند تا مدل Ridge رو نشون میده که با استفاده از یک سری داده خطی و مقادیر مختلف آلفا، ترین شدند. در سمت چپ، از مدل های ساده استفاده کردیم که منجر به خطی شدن تخمین ها شده. در سمت راست، اول داده ها با استفاده از PolynomialFeatures(degree=10) گسترده شدن، بعد با استفاده از StandardScaler مقیاس شون تغییر کرده و بعد از مدل Ridge استفاده شده. این یک Polynomial Regression با استفاده از Ridge Regularization هست. دقت کنید که چطور افزایش آلفا منجر به صاف شدن تخمین ها و در نهایت کم شدن واریانس و افزایش سوگیری شده.برای Linear Regression هم میتونیم با استفاده از معادله نرمال یا گرادیان کاهشی، از Ridge Regression استفاده کنیم. کد های زیر نحوه پیاده سازی رو نشون میده (مقدار penalty=l2 نشان دهنده Ridge Regression هست):رگرسیون لسونام این مدل در واقع مخفف Least  Absolute  Shrinkage  and  Selection  Operator Regression (LASSO) هست. این الگوریتم یک نوع دیگه از منظم سازی Linear Regression هست. مثل Ridge یک مقدار Regularization به تابع هزینه اضافه میکنه. فرمول رو میتونید ببینید:شکل زیر مثل شکل Ridge اما برای مدل Lasso هست.یک ویژگی مهم این الگوریتم این هست که وزن های فیچر های کم اهمیت رو حذف میکنه (برابر صفر قرار میده) به عبارت دیگه Lasso Regression به طور خودکار Feature Selection رو انجام میده.با نگاه به شکل زیر میشه فهمید چرا این اتفاق میفته. محور های افقی و عمودی نشون دهنده پارامتر های دو مدل و پس زمینه نشون دهنده تابع های هزینه مختلف هست. نمودار گوشه بالا سمت چپ نشون دهنده L1 Loss  (قدر مطلق تتا1 + قدر مطلق تتا2) هست که وقتی به هر کدوم از محور ها نزدیک میشیم، به صورت خطی کم میشه. مثلا اگر مقدار اولیه پارامتر های مدل رو برابر نقطه زرد قرار بدید و از گرادیان کاهشی استفاده کنید، هر دو پارامتر رو به مقدار مساوی کم میکنه (خط زرد) بخاطر همین تتا2 اول به صفر میرسه چون به صفر نزدیک تر هست. بعد از اون در طول محور حرکت میکنه تا زمانی که مقدار تتا1 هم برابر صفر بشه. نمودار بالا سمت راست نشون دهنده تابع هزینه Lasso هست. دایره های سفید نشون دهنده راهی هست که گرادیان کاهشی طی میکنه تا پارامتر هایی که مقدار اولیه تتا1 برابر 0.25 و تتا2 برابر -1 هست، بهینه کنه. دقت کنید باز هم راهی که طی کرده به سرعت به تتا2 مساوی 0 میرسه و بعد مدتی طول میکشه تا به حالت بهینه یعنی مربع قرمز برسه. اگر مقدار آلفا رو افزایش بدیم، حالت بهینه در طول خط زرد به سمت چپ و اگر کاهش بدیم، به سمت راست حرکت میکرد.دو نمودار پایین همین موضوع رو با L2 Loss نشون میدن. نمودار پایین سمت چپ نشون میده که L2 Loss با نزدیک شدن به مبدا کاهش پیدا میکنه پس گرادیان کاهشی یک راه صاف رو برای رسیدن بهش طی خواهد کرد. نمودار Ridge دو تا فرق اساسی با Lasso داره. اول، مقدار گرادیان با نزدیک شدن به نقطه بهینه کمتر میشه پس گرادیان کاهشی به طور طبیعی آروم میشه و این قضیه به همگرایی کمک میکنه. دوم، نقطه بهینه که با قرمز مشخص شده با بیشتر شدن آلفا به مرکز نزدیک تر میشه.در Lasso، برای جلوگیری از حرکت اضافه ی گرادیان کاهشی در اطراف نقطه بهینه، باید Learning Rate رو آروم آروم کم کنیم. با این کار باز هم حرکت اضافه خواهد داشت اما این بار با قدم های کوچکتر که منجر به همگرایی میشهالگوریتم Elastic Netاین الگوریتم مابین Ridge و Lasso هست. در فرمولی که پایین میبینید، اگر r=0 باشه، به Ridge Regression و اگر r=1 باشه، به Lasso Regression میرسیم:الان براتون سوال پیش میاد که کی باید از کدوم الگوریتم استفاده کنیم؟ خوبه که همیشه مقداری منظم سازی داشته باشیم، پس پیشنهاد میشه از Linear Regression خالی استفاده نکنید. بصورت دیفالت از Ridge استفاده کنید اما اگه شک دارید که فقط یک تعداد از فیچر ها به درد بخور هستند، از Lasso یا Elastic Net استفاده کنید چون این دو وزن فیچر های به درد نخور رو صفر میکنند. در حالت کلی، Elastic Net رو به Lasso ترجیح بدید چون اگر تعداد فیچر ها بیشتر از تعداد نمونه های ترینینگ باشه یا موقعی که بعضی فیچر ها به شدت همبسته باشن، Lasso نامنظم عمل میکنه.توقف زودهنگام الگوریتمیک راه کاملا متفاوت برای منظم سازی الگوریتم هایی مثل گرادیان کاهشی، متوقف کردن ترینینگ به محض رسیدن Validation Error به مینیمم هست. به این کار Early Stopping میگن. شکلی که میبینید یک مدل پیچیده رو نشون میده که با استفاده از BGD ترین شده. با افزایش Epoch مقدار ارور هم کاهش پیدا میکنه. اما از یک جایی به بعد، دیگه کم نمیشه و دوباره روند افزایش رو در پیش میگیره. با استفاده از Early Stopping میتونیم درست موقعی که به مقدار مینیمم ارور میرسیم، بایستیم.در الگوریتم های SGD و MbGD منحنی ها صاف نیستند و فهمیدن اینکه به مینیمم رسیدیم یا نه کار آسونی نیست. یک راه حل این هست اگه تا یک مدتی توی مینیمم بودیم، بایسیتیم (یعنی موقعی که مطمئن هستیم که عملکرد مدل دیگه قرار نیست بهتر از این بشه) بعد پارامتر های مدل رو به نقطه ای برگردونیم که ارور توی مینیمم بودرگرسیون لجستیکاز بعضی الگوریتم های رگرسیون میشه برای دسته بندی استفاده کرد(و برعکس). Logistic Regression یک الگوریتم هست که احتمال متعلق بودن یک نمونه به یک دسته خاص رو تخمین میزنه. (مثلا چقدر احتمال داره که این ایمیل اسپم باشه؟) اگر احتمال تخمین زده شده بیشتر از 50% باشه، مدل نمونه رو جزء دسته مثبت و در غیر این صورت جزء دسته منفی میدونه. (کلاس مثبت با 1 و منفی با 0 لیبل گذاری شده) این قابلیت این الگوریتم رو به یک Binary Classifier تبدیل میکنه. https://virgool.io/@TabaMojj/%D8%B7%D8%A8%D9%82%D9%87-%D8%A8%D9%86%D8%AF%DB%8C-%D8%A7%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A8%D8%A7-%D8%AF%DB%8C%D8%AA%D8%A7%D8%B3%D8%AA-mnist-b0ei4thu9xmo تخمین احتمالاتخب رگرسیون لجستیک چطور کار میکنه؟ این الگوریتم هم مثل رگرسیون خطی، مجموع وزن دار فیچر های ورودی رو به همراه یک مقدار خطا، محاسبه میکنه اما به جای اینکه مثل رگرسیون خطی، نتیجه نهایی رو نشون بده، شکل لجستیک نتیجه رو نشون میده.مدل لجستیک درواقع یک تابع سیگموئید (Sigmoid Function) که خروجی اون یک عدد بین 0 و 1 هست. نماد تابع سیگموئید رو به همراه شکل این تابع میتونید ببینید:رگرسیون لجستیک بعد از تخمین احتمال، از این معادله استفاده میکنه تا نمونه رو دسته بندی کنه:دقت کنید که به ازای t کوچکتر از صفر،مقدار تابع سیگموئید کوچکتر از 0.5 و اگر t بزرگتر یا مساوی صفر باشه، مقدار تابع بزرگ تر یا مساوی 0.5 خواهد بود.مقدار t با نام logit هم شناخته میشه. ریشه این نام در تابع logit نهفته ست که معکوس تابع لجستیک هست. اگر p که احتمال رو نشون میده، به‌عنوان ورودی این تابع بدید، میبینید که نتیجه t خواهد بود. این تابع همچنین log-odds هم نام داره چون لگاریتم نسبت بین احتمال کلاس مثبت و احتمال کلاس منفی هست.ترین و تابع هزینهخب حالا میدونیم که یک مدل Logstic Regression چطور احتمالات رو محاسبه میکنه و حدس میزنه. اما چطور ترین میشه؟ هدف از ترین، مشخص کردن مقدار بردار تتا به گونه ای هست که احتمال نمونه های مثبت (y=1) و منفی (y=0) رو تخمین بزنه. این عمل برای هر نمونه بوسیله تابع هزینه زیر انجام میشه:اگر t نزدیک به صفر باشه، log(t)- خیلی بزرگ میشه؛ در نتیجه هنگامی که مدل برای یک نمونه مثبت، احتمال نزدیک به صفر رو تخمین میزنه، و برای یک نمونه منفی، احتمال نزدیک به یک رو تخمین میزنه، مقدار هزینه خیلی زیاد میشه. از طرف دیگه، وقتی t نزدیک به 1 باشه، مقدار log(t)- نزدیک به 0 میشه. پس اگر احتمال یک نمونه منفی نزدیک به 0 و یک نمونه مثبت نزدیک به 1 باشه، مقدار هزینه هم نزدیک به 0 خواهد بود.تابع هزینه برای کل ترینینگ ست، میانگین هزینه های تمام نمونه هاست. فرمول این تابع هزینه، Log Loss نام داره:خبر بد اینه که برای محاسبه مقدار تتا، معادله نرمالی وجود نداره. خبر خوب اینه که این تابع هزینه محدب هست و میشه از گرادیان کاهشی(یا هر الگوریتم بهینه سازی دیگه ای) برای محاسبه مینیمم سراسری استفاده کرد. مشتق جزئی تابع هزینه به این صورت هست:این فرمول برای هر نمونه مقدار خطای تخمین رو محاسبه میکنه و ضربدر مقدار j امین فیچر میکنه و بعد میانگین رو برای همه نمونه ها محاسبه میکنه. بعد از اینکه بردار گرادیان ها رو که شامل همه مقادیر مشتق جزئی هست، به دست آوردیم، میتونیم در BGD استفاده کنیم.همچنین برای SGD یک نمونه و برای MbGD یک دسته کوچک رو میگیریم.مرز های تصمیم گیریبیاید از دیتاست Iris برای شفاف سازی Logistic Regression استفاده کنیم. این دیتاست شامل طول و عرض کاسبرگ و گلبرگ 150 گل زنبق هست. این گلها از سه نوع مختلف به نام هایIris setosa، Iris versicolor Iris virginica تشکیل شدند.بیاید برای تشخیص Iris virginica با استفاده از عرض گلبرگ، یک Classifier درست کنیم.اول داده ها رو دریافت میکنیم:بعد مدل رو ترین میکنیم و نگاهی به احتمالاتی که تخمین زده میندازیم:برای رسم از این کد استفاده میکنیم:decision_boundary = X_new[y_proba[:, 1] &gt;= 0.5][0]
plt.figure(figsize=(8, 3))
plt.plot(X[y==0], y[y==0], &amp;quotbs&amp;quot)
plt.plot(X[y==1], y[y==1], &amp;quotg^&amp;quot)
plt.plot([decision_boundary, decision_boundary], [-1, 2], &amp;quotk:&amp;quot, linewidth=2)
plt.plot(X_new, y_proba[:, 1], &amp;quotg-&amp;quot, linewidth=2, label=&amp;quotIris virginica&amp;quot)
plt.plot(X_new, y_proba[:, 0], &amp;quotb--&amp;quot, linewidth=2, label=&amp;quotNot Iris virginica&amp;quot)
plt.text(decision_boundary+0.02, 0.15, &amp;quotDecision  boundary&amp;quot, fontsize=14, color=&amp;quotk&amp;quot, ha=&amp;quotcenter&amp;quot)
plt.arrow(decision_boundary, 0.08, -0.3, 0, head_width=0.05, head_length=0.1, fc=&#039;b&#039;, ec=&#039;b&#039;)
plt.arrow(decision_boundary, 0.92, 0.3, 0, head_width=0.05, head_length=0.1, fc=&#039;g&#039;, ec=&#039;g&#039;)
plt.xlabel(&amp;quotPetal width (cm)&amp;quot, fontsize=14)
plt.ylabel(&amp;quotProbability&amp;quot, fontsize=14)
plt.legend(loc=&amp;quotcenter left&amp;quot, fontsize=14)
plt.axis([0, 3, -0.02, 1.02])مثلث هایی که میبینید نشون دهنده عرض این نوع گل هست که از 1.4 تا 2.5 سانتی متر متغیره. مربع ها نشون دهنده عرض دیگر نوع های این گل هست که مقدار اونها 0.1 تا 1.8 سانتی متر هست. دقت کنید که بعضی جا ها این مقادیر هم‌پوشانی دارن. در بالای 2 سانتی متر، مدل مطمئن هست که گل از نوع Iris virginica هست (چون احتمالش بالا هست) در حالیکه در زیر 1 سانتی متر مطمئن هست که گل Iris virginica نیست. ما بین این مقادیر، مدل شک داره. اگر ازش بخواید که برای یک نمونه حدس بزنه (با استفاده از تابع predict) چیزی که برمیگردونه کلاسی هست که بیشترین احتمال رو داره. بخاطر همین یک مرز تصمیم گیری دور و بر 1.6 سانتی متر وجود داره چون اینجا هر دوی احتمال ها برابر 50% هست. اگر عرض بیشتر از 1.6 سانتی متر باشه نمونه جز دسته Iris virginica قرار میگیره و در غیر اینصورت، در دسته not Iris virginica.شکل زیر دو تا فیچر این دیتاست رو نشون میده. رگرسیون لجستیک با استفاده از این دو فیچر، احتمال Iris virginica بودن یک نمونه رو تخمین میزنه. خط بریده بریده وسط نشون دهنده نقاطی هست که مدل احتمال 50% رو حدس میزنه: در واقع اینجا مرز تصمیمی گیری مدل هست. دقت کنید که این یک مرز خطی هست. هر خط نشون دهنده نقاطی هست که مدل یک احتمال خاص رو خروج میده؛ از 15% در سمت چپ تا 90% در سمت راست. تمام گل هایی که در بالا سمت راست خط قرار دارن، به احتمال 90% جزء دسته Iris virginica هستند.مثل بقیه مدل های خطی، رگرسیون لجستیک هم با استفاده از L1 و L2 میتونه منظم سازی بشه. Scikit-Learn به‌صورت دیفالت L2 رو پیاده سازی میکنه.در مدل های خطی، هایپرپارامتر alpha وظیفه مشخص کردن میزان منظم سازی هست. اما در کلاس LogisticRegression ِ کتابخانه Scikit-Learn، هایپرپرامتر C این کار رو انجام میده. هر چقدر مقدار C بیشتر باشه، مدل کمتر منظم سازی میشهرگرسیون سافت‌مکسمدل رگرسیون لجستیک رو میشه عمومی‌سازی کرد تا بتونه به‌طور مستقیم چند کلاس رو دسته بندی کنه. این روش Softmax Regression یا Multinomial Logistic Regression نام داره.ایده پشت این روش ساده‌ست: این مدل با دریافت یک نمونه، اول برای همه کلاس ها یک امتیاز رو محاسبه میکنه و بعد با دادن این امتیاز ها به تابع Softmax احتمال بودن در هر کلاس رو حدس میزنه. در فرمول زیر s نشون دهنده امتیاز هست و k نشون دهنده کلاس.بعد از محاسبه امتیاز هر کلاس برای نمونه x ، میتونیم احتمال تعلق x به دسته k رو به دست بیاریم (Pk). این تابع اول exp همه امتیاز ها رو به دست میاره بعد با تقسیم این مقدار به جمع همه exp ها، اون رو نرمال میکنه.رگرسیون سافت‌مکس میتونه یک کلاس رو حدس بزنه (multiclass هست نه multioutput). پس این الگوریتم رو فقط باید برای کلاس های متقابل منحصر به فرد استفاده بشه، مثلا انواع مختلف گیاهان. برای تشخیص افراد متعدد در یک عکس از این الگوریتم استفاده نمیکنیمحالا که فهمیدیم مدل چجوری احتمالات رو تخمین میزنه و حدس انجام میده، بیاید یک نگاهی به روند ترین بندازیم. هدف، داشتن یک مدل هست که احتمال زیادی رو برای دسته هدف محاسبه میکنه (و احتمال کمی رو برای کلاس های دیگه). این کار رو میشه با به حداقل رسوندن مقدار تابع هزینه ای که در زیر میبینید، انجام داد. این فرمول Cross Entropy نام داره و هر وقت مدل یک احتمال کم برای کلاس هدف محاسبه میکنه، جریمه میشه. دقت کنید که اگر 2 تا کلاس داشته باشیم (K=2)، این تابع هزینه برابر با تابع هزینه رگرسیون لجستیک میشه.بردار گرادیان این تابع هم به شکل زیر هست:حالا میتونیم بردار گرادیان رو برای هر کلاس محاسبه کنیم و از گرادیان کاهشی (یا هر الگوریتم بهینه سازی دیگه) برای پیدا کردن پارامتری که مقدار تابع هزیه رو مینیمم میکنه، استفاده کنیم.بیاید از Softmax Regression برای دسته بندی گل ها به سه دسته مختلف استفاده کنیم. به صورت دیفالت، کلاس LogisticRegression از one-versus-the-rest استفاده میکنه اما با استفاده از هایپرپارامتر multi_class=multinomial میتونیم از Softmax استفاده کنیم.پس‌زمینه شکلی که میبینید، مرز های تصمیم گیری مختلف رو نشون میده. دقت کنید که مرز تصمیم گیری بین هر دو کلاس، خطی هست. همچنین احتمال کلاس Iris versicolor رو هم نشون میده (خطی که با 0.450 نامگذاری شده نشون دهنده مرز 45% هست) دقت کنید که مدل میتونه کلاسی رو که احتمالش زیر 50% هست، حدس بزنه. برای مثال، نقطه ای که همه مرز ها با هم برخورد کردند، احتمال همه کلاس ها برابر 33% هست.خب این هم از بررسی مدل های رگرسیون خطی. امیدوارم مفید واقع شده باشه.</description>
                <category>مجتبی میر یعقوب زاده</category>
                <author>مجتبی میر یعقوب زاده</author>
                <pubDate>Wed, 19 Aug 2020 17:21:24 +0430</pubDate>
            </item>
                    <item>
                <title>طبقه بندی اعداد با دیتاست MNIST</title>
                <link>https://virgool.io/@TabaMojj/%D8%B7%D8%A8%D9%82%D9%87-%D8%A8%D9%86%D8%AF%DB%8C-%D8%A7%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A8%D8%A7-%D8%AF%DB%8C%D8%AA%D8%A7%D8%B3%D8%AA-mnist-b0ei4thu9xmo</link>
                <description>طبقه بندی دیتاست MNISTاز کار های رایج توی Supervised Learning حدس زدن مقدار (Regression) و و حدس زدن دسته (Classification) هست. توی مطلب قبلی قیمت خونه های یک محله رو تخمین زدیم. توی این مطلب میخوایم با استفاده از الگوریتم های Classification اعداد رو با استفاده از دیتاست MNIST تشخیص بدیم.دیتاست MNISTاین دیتاست از 70000 عکس تشکیل شده که شامل اعداد نوشته شده توسط دانش آموزان دبیرستانی و کارمندان سازمان سرشماری آمریکا هست. هر عکس با شماره ای که توی عکس هست لیبل گذاری شده. این دیتاست انقدر مطالعه شده که بعضی اوقات بهش میگن &quot;hello world&quot; ِ ماشین لرنینگ! هر وقت افراد یک الگوریتم Classification جدید پیدا میکنن ، اول روی این دیتاست امتحانش میکنن. افراد تازه کار هم کارشون رو با این دیتاست شروع میکنن.کتابخانه Scikit-Learn توابع زیادی برای دریافت این دیتاست داره. میتونید از کد زیر استفاده کنید:دریافت دیتاست MNISTدیتاست توی مسیر USERNAME/scikit_learn_data قرار میگیره.دیتاست هایی که توسط Scikit-Learn دانلود میشن معمولا ساختار دیکشنری یکسانی دارن که شامل موارد زیر هستند:یک DESCR key که دیتاست رو توصیف میکنهیک data key که شامل یک آرایه با ردیف هایی به ازای هر نمونه و یک ستون به ازای هر فیچر هستیک target key که شامل آرایه لیبل ها هستبیاید نگاهی به این آرایه ها بندازیم:70000 عکس وجود داره که هر عکس 784 تا فیچر داره. این به این بخاطر هست که هر عکس 28 * 28 پیکسل هست و هر فیچر نشون دهنده شدت هر پیکسل هست. مقدار این شدت از 0 (سفید) تا 255 (سیاه) متغیر هست. بیاید به یک عدد از این دیتاست نگاهی بندازیم:بهش میخوره که 5 باشه. برای اینکه مطمئن بشیم y[0] رو میزنیم که نتیجش میشه 5.دقت کنید که لیبل بصورت رشته هست. در حالیکه بیشتر الگوریتم های ماشین لرنینگ انتظار عدد دارن پس باید y رو تبدیل به عدد کرد:خب همینجا باید صبر کنید. همیشه باید یک تست ست بسازیم و اونو بذاریم کنار و هیچ کاری هم باهاش نداشته باشیم. دیتاست MNIST خودش به ترینینگ ست (60000 نمونه اول) و تست ست(10000 نمونه بعدی) تقسیم شده و فقط کافیه کد زیر رو اجرا کنیم:ترین کردن یک Binary Classifierبیاید فعلا مسئله رو کوچیک کنیم و فرض کنیم که فقط میخوایم یک عدد رو تشخیص بدیم، مثلا عدد 5. به این چیزی که قراره عدد 5 رو تشخیص بده میگن Binary Classifier چون میتونه تفاوت بین دو تا دسته رو بفهمه، 5 ها و غیر 5 ها. بیاید وکتور های لازم برای این الگوریتم Classification رو بسازیم:حالا بیاید یک Classifier انتخاب کنیم. برای شروع میتونیم از Stochastic Gradient Descent(SGD) استفاده کنیم. یکی از نکات مثبت این الگوریتم این هست که به راحتی میتونه با دیتاست های بزرگ کار کنه.حالا ازش استفاده میکنیم تا عدد 5 رو حدس بزنه:معیار های محاسبه عملکردارزیابی عملکرد یک Classifier اغلب اوقات سخت تر از Regressor هست بخاطر همین بیشتر این مطلب قراره صرف این کار بشه.محاسبه دقت با استفاده از Cross-Validationمثل مطلب قبلی، اینجا هم استفاده از CV روش خوبی هست. بیاید از تابع cross_val_score برای ارزیابی مدل SGFClassifier استفاده کنیم.اگه میخواید درمورد این روش بیشتر بدونید به مطلب قبلی مراجعه کنید.دقت بالای 93 درصد شاید اولش خوب به نظر بیاد ولی زیاد هیجان زده نشید. بیاید به یک Classifier نگاه کنیم که غیر 5 ها رو دسته بندی میکنه:دقت این مدل برابر هست با :علت دقت بالای 90 درصد این هست که فقط 10 درصد عکس ها 5 هستند و همیشه داره حدس میزنه که این عکس 5 نیست، بخاطر همین 90 درصد مواقع درست حدس میزنه.این نشون میده که چرا مقدار دقت همیشه معیار مناسبی برای ارزیابی عملکرد یک Classifier نیست. مخصوصا وقتی که با دیتاست های دارای چولگی سر و کار داریم.ماتریس درهم‌ریختگییک راه بهتر ارزیابی مدل استفاده از Confusion Matrix هست. ایده کلی این ماتریس این هست که میاد تعداد دفعاتی رو که نمونه های دسته A ، جزء نمونه های دسته B دسته بندی شدند میشماره. مثلا اگه بخوایم تعداد دفعاتی که مدل، 5 ها رو با 3 ها اشتباه گرفته بدونیم، به ردیف پنجم و ستون سوم این ماتریس مراجعه میکنیم.برای استفاده از این ماتریس اول باید یک سری حدس ها داشته باشیم که بتونیم اون ها رو با مقادیر واقعی مقایسه کنیم. میتونیم با استفاده از تست ست یک سری حدس ها بزنیم ولی فعلا بذارید دست نخوره باقی بمونه. (یادتون باشه که از تست وقتی استفاده میکنیم که کار پروژه ما تموم شده و آماده لانچ هست). به جای این کار میتونیم از cross_val_pridict استفاده کنیم:این تابع درست مثل cross_val_score عمل میکنه منتهی با این تفاوت که بجای امتیاز، حدس هایی که تو هر فولد زده رو برمیگردونه. یعنی به ازای هر نمونه تو ترینینگ ست،یک حدس دریافت میکنیم.حالا میتونیم با استفاده از تابع confusion_matrix این ماتریس رو ببینیم:هر ردیف این ماتریس نشون دهنده‌ی یک دسته هست و هر ستون نشون دهنده‌ی دسته حدس زده شده ست.(این اعداد ممکنه برای شما فرق داشته باشن) اولین ردیف این ماتریس نشون دهنده‌ی عکس های غیر 5 هست (دسته منفی): 53057 عدد به درستی بعنوان غیر 5 دسته بندی شدند (منفی های درست) در حالیکه 1522 تا به اشتباه بعنوان 5 دسته بندی شدند (مثبت های غلط). دومین ردیف نشون دهنده‌ی عکس های 5 هست (دسته مثبت): 1325 تا به اشتباه غیر 5 دسته بندی شدند (منفی های غلط) و 4096 تا به درستی 5 دسته بندی شدند (مثبت های درست)یک Classifier با عملکرد عالی فقط مقادیر مثبت های درست و منفی های درست خواهد داشت و باقی درایه ها صفر خواهند بود. این ماتریس اطلاعات خوبی به ما میده اما بعضی اوقات شاید یک معیار خلاصه تر لازم داشته باشیم. میتونیم از Precision استفاده کنیم تا ببینیم دقت حدس های درست چقدر بودن:در اینجا TP به معنای True Positive( مثبت های درست) و FP به معنای False Positive(مثبت های غلط) هست.Precision همیشه با یک معیار دیگه به نام Recall استفاده میشه که نام های دیگه اون Sensitivity و True Positive Rate هست. در اینجا FN به معنای False Negative(منفی های غلط) هست.این تصویر Confusion Matrix رو به خوبی نشون میده:پیاده سازی Precision و Recallکتابخانه Scikit-Learn توابع زیادی برای محاسبه معیار ها داره:این اعداد به ما میگن وقتی مدل ادعا میکنه که یک عکس 5 هست، درواقع 72.9 درصد مواقع درست میگه و کلا 75 درصد 5 ها رو میتونه تشخیص بده.بیشتر اوقات این دو تا معیار رو با هم ترکیب میکنن تا معیاری به نام F1 Score ساخته بشه. این معیار درواقع میانگین هارمونیک Precision و Recall هست. تفاوت این میانگین با میانگین معمولی این هست که میانگین معمولی با همه مقادیر یکسان برخورد میکنه، درحالیکه میانگین هارمونیک وزن بیشتری به مقادیر کم میده. بخاطر همین Classifier موقعی F1 Score بالایی به دست میاره که هر دوی Precision و Recall مقادیر زیادی داشته باشن.برای محاسبه هم میتونیم از تابع زیر استفاده کنیم:البته دقت کنید که همیشه نمیخوایم F1 Score بالایی داشته باشیم. بعضی جا ها برای ما فقط مقدار Precision مهم هست و بعضی اوقات هم فقط مقدار Recall. برای مثال فرض کنید یک مدل برای تشخیص ویدئو های مناسب برای کودکان درست کردیم. توی همچین مدلی ترجیح میدیم که ویدئو های زیادی که محتوای خوبی دارن رد بشن (مقدار Recall کم) اما فقط ویدئو هایی که مناسب کودکان هستن رو نگه داره (مقدار Precision بالا). در نقطه مقابل مدلی رو در نظر بگیرید که Recall بالایی داره ولی عوضش هر از گاهی چند تا ویدئو با محتوای نامناسب هم پیشنهاد میده (در چنین مواقعی بهتره افرادی هم باشن تا ویدئو های تایید شده رو دوباره بررسی کنن). مثال دیگه فرض کنید یک مدل درست کردیم برای تشخیص دزد در عکس های دوربین های نظارتی. توی این مدل اگه Precision 30% و Recall 99% باشه مشکلی پیش نمیاد. درسته که نگهبان ها چند تا هشدار اشتباه دریافت میکنن ولی خب عوضش تقریبا همه دزد ها دستگیر میشن.متاسفانه نمیتونیم هر دوی Precision و Recall رو با هم داشته باشیم. با افزایش Precision مقدار Recall کم میشه و برعکس. مبادله بین Precision و Recallهمونطور که گفتیم، نمیتونیم هر دوی Precision و Recall رو با هم داشته باشیم که بهش میگن Precision/Recall Trade-off. برای فهمیدن این عبارت بیاید ببینیم SGDClassifier چطور تصمیم میگیره.برای هر نمونه، مدل میاد یک امتیاز با استفاده از تابع تصمیم (Decision Function) محاسبه میکنه. اگر اون امتیاز بیشتر از یک آستانه (Threshold) باشه، اون نمونه رو جزء دسته مثبت و در غیر این صورت جزء دسته منفی قرار میده.توی تصویر بالا یک سری اعداد رو میبینید که از کمترین امتیاز در سمت چپ به بیشترین امتیاز در سمت راست قرار گرفتن. فرض کنید آستانه تصمیم (Decision Threshold) در وسط قرار گرفته: در این صورت 4 تا مثبت درست (5 های واقعی) در سمت راست آستانه و 1 مثبت غلط خواهیم داشت( 6 به اشتباه 5 در نظر گرفته شده). در نتیجه، با در نظر گرفتن این آستانه، مقدار Precision 80% خواهد بود (4 تا از 5 تا). اما از 6 مورد 5 واقعی، مدل فقط 4 تاش رو میتونه تشخیص بده، پس %Recall 67 هست(4 تا از 6 تا). اگر آستانه رو افزایش بدیم (به سمت راست حرکت بدیم) مثبت های غلط (همون 6) تبدیل میشه به منفی درست و مقدار Precision به 100 افزایش پیدا میکنه. اما یکی از مثبت های درست تبدیل به یک منفی غلط میشه و Recall رو به 50 درصد کاهش میده.کتابخانه Scikit-Learn اجازه تغییر آستانه رو به طور مستقیم به ما نمیده اما دسترسی به امتیاز های تصمیم گیری رو در اختیار میذاره. میتونیم تابع decision_function رو فراخوانی کنیم که امتیاز یک نمونه رو برگردونه و بعد بر اساس امتیاز هایی که نشون میده، میتونیم آستانه رو تغییر بدیم:نتیجه کد بالا با تابع predict یکسان هست چون آستانه SGDClassifier برابر صفر هست. حالا آستانه رو افزایش میدیم:این تایید میکنه که افزایش آستانه Recall رو کاهش میده. عکسی که انتخاب کردیم 5 رو نشون میده و وقتی آستانه 0 هست میتونه تشخیص بده اما وقتی آستانه به 8000 تغییر میکنه نمیتونه تشخیص بده.چطور میفهمیم که از چه آستانه ای باید استفاده کنیم؟ اول از تابع cross_val_predict استفاده میکنیم تا امتیاز همه نمونه های ترینینگ ست رو به دست بیاریم:حالا با این امتیاز ها، از precision_recall_curve استفاده میکنیم تا Precision و Recall برای همه آستانه ها محاسبه بشه:و در نهایت از Matplotlib استفاده میکنیم تا مقادیر اونها رو رسم کنیم:ممکنه براتون سوال پیش بیاد که چرا نمودار Precision بالا پایین بیشتری داره. علتش به این خاطر هست که بعضی اوقات وقتی آستانه رو زیاد میکنیم، Precision کاهش پیدا میکنه.(اگر چه معمولاً افزایش پیدا میکنه) اگر توی شکلی که آستانه رو توضیح میداد، آستانه رو از مرکز فقط به اندازه یک شماره به سمت راست حرکت بدیم، Precision از 4/5 یعنی 80% به 3/4 یعنی 75 درصد کاهش پیدا میکنه. در طرف مقابل، با افزایش آستانه، مقدار Recall فقط میتونه کم بشه. به همین خاطر نمودارش بالا پایین نداره.یک راه دیگه برای انتخاب مقدار مناسب Precision/Recall اینه که این دو تا رو در برابر هم دیگه رسم کنیم. کد زیر این نمودار رو رسم میکنه:همونطور که میبینید Recall توی 80% شروع به کم شدن میکنه. پس بهتره قبل از اینکه به این عدد برسیم، مثلا دور و بر 60%، مقدار Precision و Recall رو انتخاب کنیم. البته بستگی به پروژه هم داره.فرض کنید میخوایم به Precision 90% برسیم. یک نگاه به نمودار اول میکنیم و میبینیم که برای این کار، آستانه باید حدود 3400 باشه. اگر بخواید دقیق تر باشید، میتونید کمترین مقدار آستانه رو که به ما حداقل Precision 90% میده با استفاده از دستور زیر بدست بیارید:برای حدس زدن، میتونید به جای تابع predict، از خط اول کد زیر استفاده کنید.توی خطوط بعد مقادیر Precision و Recall رو چک میکنیم:حالا تونستیم یک Classifier با Precision 90% درست کنیم. همونطور که دیدید ساخت همچین چیزی آسون بود: فقط مقدار آستانه رو باید تعیین کنید. اما خب در نظر بگیرید که مدلی که مقدار Precision بالا ولی Recall خیلی پایینی داره، خیلی کاربردی نیست!اگه کسی بهمون گفت بیا به Precision 99% برسیم، باید بپرسید با چه مقدار Recall؟منحنی ROCمنحنی عملیاتی دریافت کننده ( Receiver Operating Characteristic ) یا به اختصار ROC Curve یک ابزار رایج دیگه برای ارزیابی عملکرد Binary Classifier هاست. این نمودار شبیه نمودار Precision/Recall هست، اما به جای اینکه Precision رو در برابر Recall رسم کنه، True Positive Rate (همون Recall) رو در برابر False Positive Rate رسم میکنه. FPR نسبت نمونه های دسته منفی هست که به اشتباه در دسته مثبت قرار گرفتن. مقدار FPR برابر هست با یک منهای نسبت منفی واقعی(True Negative Rate). TNR نسبت نمونه های دسته منفی هست که به درستی در دسته منفی قرار گرفتن. TNR رو گاهی Specificity هم میگن. در این صورت نمودار ROC مقدار Sensitivity(Recall) رو در برابر یک منهای Specificity رسم میکنه.برای رسم منحنی ROC اول از تابع roc_curve استفاده میکنیم تا TPR و FPR رو برای آستانه های مختلف حساب کنه:بعد هم از Matplotlib استفاده میکنیم:اینجا هم مبادله وجود داره: هرچقدر Recall(TPR) بالاتر باشه، مدل مثبت های غلط(FPR) بیشتری تولید میکنه. یک مدل خوب تا جایی که میشه باید از خط میانی فاصله داشته باشه و به گوشه بالا سمت چپ نزدیک باشه. اگر نمودار یک مدل به گوشه پایین سمت راست نزدیک باشه، یعنی مدل نمونه های دسته اول رو تو دسته دوم و نمونه های دسته دوم رو تو دسته اول قرار میده.یک راه دیگه برای مقایسه Classifier ها محاسبه مساحت زیر منحنی (Area Under the Curve) یا AUC هست. یک مدل عالی ROC AUC برابر یک و یک مدل کاملا غلط ROC AUC نزدیک به 0.5 خواهد داشت. از Scikit-Learn برای محاسبه ROC AUC استفاده میکنیم:از اونجایی که منحنی ROC شبیه به منحنی Precision/Recall هست، ممکنه براتون سوال پیش بیاد که از کدومشون باید استفاده کنیم. بر اساس قاعده سرانگشتی، اگر دسته مثبت تعداد کمی دارن یا وقتی مثبت های غلط اهمیت بیشتری برای ما دارن، استفاده از منحنی PR ارجحیت داره. در غیر این صورت از ROC استفاده میکنیم. برای مثال، توی مثال ROC قبل، ممکنه فکر کنید که عملکرد مدل عالیه. اما این به این خاطر هست که تعداد دسته مثبت یعنی 5 ها در برابر دسته منفی یعنی غیر 5 ها، خیلی کمه. در مقابل منحنی PR نشون میده که مدل هنوز جا برای بهتر شدن داره چون هنوز به اندازه کافی به گوشه بالا سمت چپ نزدیک نشدهحالا بیاید یک RandomForestClassifier ترین کنیم و عملکردش رو با SGDClassifier مقایسه کنیم. اول باید امتیاز هر نمونه در ترینینگ ست رو به‌دست بیاریم. اما بخاطر متفاوت بودن عملکرد این الگوریتم، تابع decision_function نداره و به جاش باید از predict_proba استفاده کنیم. خروجی این تابع یک آرایه شامل یک ردیف برای هر نمونه و یک ستون برای هر دسته هست و هر کدوم شامل احتمال قرار گرفتن یک نمونه در هر دسته رو نشون میدن.تابع roc_curve میتونه لیبل و امتیاز ها رو دریافت کنه ولی به جای امتیاز میتونیم احتمال هر دسته رو بدیم. توی کد زیر از احتمال دسته مثبت به عنوان امتیاز استفاده کردیم:همونطور که میبینید Random Forest بهتر از SGD عمل میکنه و بیشتر به گوشه بالا سمت جپ نزدیک هست:اگر بخوایم Precision و Recall رو هم حساب کنیم، اول باید تابع cross_val_predict رو فراخوانی کنیم تا حدس بزنه و بعد میتونیم اون ها رو محاسبه کنیم:حالا میدونید که چطور یک Binary Classifier درست کنید. معیار اندازه گیری مناسب رو انتخاب کنید، با استفاده از Cross-Validation ارزیابی کنید، Precision/Recall مناسب پروژه رو انتخاب کنید و از منحنی ROC و ROC AUC استفاده کنید تا مدل های مختلف رو با هم دیگه مقایسه کنید.حالا بیاید نمونه های دیگه رو هم تشخیص بدیم.طبقه بندی چند دسته‌ایهمونطور که دیدید Binary Classifier ها میتونن دو تا دسته رو از هم دیگه تشخیص بدن.Multiclass Classifier یا Multinomial Classifier ها میتونن بیش از دو دسته رو از هم تشخیص بدن.بعضی الگوریتم ها مثل SGD، Random Forest Classifier و Naive Bayes Classifier میتونن چند دسته رو از هم تشخیص بدن. الگوریتم های دیگه مثل Logistic Regression و Support Vector Machine فقط میتونن دو تا دسته رو از هم تشخیص بدن. البته استراتژی هایی وجود دارن که میتونید با استفاده از اونها طبقه بندی چند دسته‌ای  رو با چند تا طبقه بندی دوتایی انجام بدید.یک راه برای درست کردن یک سیستم که بتونه اعداد 0 تا 9 رو تشخیص بده و در یکی از 10 دسته قرار بده، این هست که بیایم 10 تا Binary Classifier درست کنیم؛ یعنی برای هر عدد، یک Classifier که بتونه اون عدد رو از بقیه تشخیص بده. بعد وقتی میخوایم یک عکس رو طبقه بندی کنیم، از هر Classifier امتیاز اون عکس رو میگیریم و در نهایت دسته ای رو انتخاب میکنیم که Classifier اون بیشترین امتیاز رو داره. به این استراتژی میگن One-Versus-The-Rest(OvR) یا One-Versus-All.یک استرتژی دیگه این هست که بیایم برای هر جفت عدد یک Classifier درست کنیم. به این صورت که یکی اعداد 0 و 1 رو تشخیص بده، یکی 0 و 2 ، یکی 1 و 2 و الی آخر. به این استراتژی میگن One-Versus-One. اگر N تا دسته داشته باشیم، باید 2/(N *N-1) تا Classifier درست کنیم. برای دیتاست MNIST یعنی 45 تا ! وقتی میخوایم یک عکس رو دسته بندی کنیم، باید اون عکس رو برای تمام Classifier ها بفرستیم. برتری این استراتژی در این هست که هر Classifier فقط لازمه که با قسمتی از ترینینگ ست ترین بشه که باید تشخیص بده.بعضی الگوریتم ها مثل SVM نمیتونن با دیتاست های بزرگ کار کنن. برای این الگوریتم ها OvO روش مناسبی هست چون ترین کردن تعداد زیادی Classifier روی ترینینگ ست های کوچیک، سریع تر از ترین کردن تعداد کمی Classifier روی ترینینگ ست های بزرگ هست. اگر چه برای بیشتر الگوریتم های Binary استراتژی OvR رو انتخاب میکنیم.خود Scikit-Learn تشخیص میده که میخواید از یک الگوریتم Binary Classification برای یک کار Multiclass Classification استفاده کنید و بسته به نوع الگوریتم، بصورت خودکار از OvR یا OvO استفاده میکنه. بیاید این رو با SVM Classifier امتحان کنیم:این کد SVC رو با استفاده از دسته هایی که توی y_train هست یعنی اعداد 0 تا 9، روی ترینینگ ست ترین میکنه. بعدش یک حدس درست میزنه. در واقع اتفاقی که پشت این کد میفته این هست که Scikit-Learn از استراتژی OvO استفاده میکنه: 45 تا Binary Classifier ترین میکنه و امتیاز تصمیم گیری هر کدوم رو برای عکس بدست میاره و دسته ای رو انتخاب میکنه که بیشترین امتیاز رو داره.با فراخوانی تابع decision_function میبینید که ده تا عدد برمیگردونه که این اعداد امتیاز هر دسته برای اون عکس هستند.وقتی فرایند ترین یک Classifier تموم میشه، دسته ها رو به ترتیب مقدار توی _classes ذخیره میکنه. توی مثال ما، ایندکس هر دسته برابر هست با خود مقدار اون دسته (ایندکس 5 برابر دسته 5 هست) اما خب این حالت برای همه دیتاست ها رخ نمیدهاگر بخوایم Scikit-Learn رو مجبور کنیم که از OvO یا OvR استفاده کنه، باید از کلاس OneVsOneClassifier یا OneVsRestClassifier استفاده کنیم. یک شیء میسازیم و یک Classifier رو به سازنده اون کلاس ارسال میکنیم (اجباری نیست که Binary Classifier باشه)برای مثال کد زیر یک SVC با استفاده از استراتژی OvR درست میکنه:ترین کردن یک SGDClassifier یا (RandomForestClassifier) به همین سادگیه. البته دقت کنید که مدل ما نتونست به خوبی 5 رو تشخیص بده و به جاش 3 حدس زده. وقتی تابع decision_function رو فراخوانی میکنیم تا امتیاز هر دسته رو ببینیم، میبینیم که مدل با اعتماد به نفس بالایی داره اشتباه حدس میزنه!از امتیاز ها مشخصه که مدل توی انتخاب دسته 3 با امتیاز 1823 و دسته 5 با امتیاز 1385- شک میکنه. حالا میخوایم که این مدل رو ارزیابی کنیم که مثل همیشه از cross_val_score استفاده میکنیم:دقت مدل توی تمام تست فولد ها بالای 85% هست. توی مطلب قبلی درباره تغییر مقیاس داده ها صحبت کردیم. اینجا هم میتونیم مقیاس داده های ورودی رو تغییر بدیم تا دقت مدل کمی افزایش پیدا کنه:آنالیز خطا ها اگر این یک پروژه ماشین لرنینگ واقعی بود، الان باید تنظیماتی رو که توی مرحله آماده‌سازی داده انجام دادیم، بررسی میکردیم، مدل های مختلف رو با تغییر هایپرپارامتر ها امتحان میکردیم و تا جایی که میشد روند انجام کار ها رو اتوماتیک میکردیم. ولی اینجا میخوایم فرض کنیم که این کار ها رو انجام دادیم و به یک مدل خوب رسیدیم و حالا دنبال راه هایی هستیم که عملکردش رو افزایش بدیم. یک راه برای انجام این کار آنالیز نوع خطا هایی هست که این مدل تولید میکنه.اول به Confusion Matrix نگاه میکنیم. باید با استفاده از تابع cross_val_predict حدس بزنیم و بعد تابع confusion_matrix رو فراخوانی کنیم:ماتریس درهم ریختگیبه جای نگاه کردن به این همه عدد، یک راه بهتر وجود داره. اون هم اینکه بیایم این ماتریس رو به یک عکس تبدیل کنیم:این عکس یک ماتریس خوب رو نشون میده چون بیشتر عکس ها توی قطر اصلی قرار دارن و به این معنی هست که به درستی تشخیص داده شدند. 5 ها به نسبت باقی اعداد تاریک تر هستند؛ این یا به این معناست که تعداد 5 کمی توی دیتاست هست یا به این معناست که مدل روی دسته 5 به خوبی باقی دسته ها عمل نمیکنه. یا شاید هم هر دوی این حدس ها درست باشند.بیاید خطا ها رو رسم کنیم. به جای بررسی تعداد خطا های یک دسته ، باید نسبت این خطا ها رو با تعداد نمونه های اون دسته بررسی کنیم. با استفاده از دو خط اول این کد، تعداد نمونه های هر دسته رو به دست میاریم. بعد عدد های توی ماتریس رو تقسیم بر تعداد نمونه های هر دسته میکنیم تا نرخ خطا رو بدست بیاریم. در خط بعدی قطر اصلی ماتریس رو با صفر پر میکنیم تا فقط خطا ها نشون داده بشن و بعد نتیجه نهایی رو رسم میکنیم:حالا به راحتی میتونیم خطا هایی رو که مدل انجام میده ببینیم. یادتون باشه ردیف ها نشون دهنده دسته های واقعی و ستون ها نشون دهنده دسته هایی که حدس زده شدن، هست. ستون 8 خیلی روشن هست یعنی خیلی از اعداد اشتباهاً بعنوان 8 شناخته میشن. وضعیت ردیف 8 زیاد بد نیست و نشون میده که 8 ها واقعا 8 شناسایی میشن. همونطور که میبینید Confusion Matrix لزوما متقارن نیست. مدل 3 ها و 5 ها رو هم با هم قاطی میکنن (در هر دو جهت)بررسی Confusion Matrix میتونه به ما تو برطرف کردن مشکلات مدل کمک کنه. اینطور که فهمیدیم، باید یک کاری کنیم تا تعداد 8 های غلط کم بشه. برای مثال میتونیم نمونه های بیشتری از اعدادی که شبیه به 8 هستند اما در واقع 8 نیستند جمع کنیم تا مدل با دیدن اونها متوجه تفاوت بین اونها و 8 واقعی بشه. یا میتونیم فیچر های جدیدی رو اضافه کنیم تا به مدل کمک کنه. مثلا الگوریتمی بنویسیم که تعداد حلقه های بسته رو بشماره. مثلا 8 دو تا حلقه بسته داره، 6 یکی و 5 هیچی. یا میتونیم تصاویر رو پیش‌پردازش کنیم (با استفاده از Scikit-Image , Pillow یا OpenCV) تا بتونیم پترن هایی رو مثل همین حلقه های بسته تشخیص بدیم.بررسی خطای هر نمونه هم راه خوبی هست چون میتونیم بفهمیم که چرا مدل یک نمونه رو نمیتونه درست تشخیص بده. البته این راه سخت تر هست و زمان بیشتری هم میبره. بیاید برای مثال دسته 3 و 5 رو رسم کنیم. کتاب برای رسم این دسته ها خودش یک تابع نوشته و توضیح خاصی نداده. میتونید این قسمت از کد رو کپی کنید:def plot_digits(instances, images_per_row=10, **options):       size = 28      images_per_row = min(len(instances), images_per_row)      images = [instance.reshape(size,size) for instance in instances]      n_rows = (len(instances) - 1) // images_per_row + 1      row_images = []      n_empty = n_rows * images_per_row - len(instances)            images.append(np.zeros((size, size * n_empty)))      for row in range(n_rows):            rimages = images[row * images_per_row : (row + 1) * images_per_row]            row_images.append(np.concatenate(rimages, axis=1))      image = np.concatenate(row_images, axis=0)      plt.imshow(image, cmap = mpl.cm.binary, **options)     plt.axis(&quot;off&quot;)کاری که میخوایم بکنیم این هست که دسته های 3 و 5 رو در دو متغیر ذخیره کنیم. بعد بیایم عکس هایی رو که 3 بودن و 3 هم تشخیص داده شدن، 3 بودن ولی 5 تشخیص داده شدن، 5 بودن ولی 3 تشخیص داده شدن و 5 بودن و 5 هم تشخیص داده شدن رو در متغیر های مربوط به خودشون ذخیره کنیم. بعد از تابعی که بالا گفته شد استفاده میکنیم تا هر کدوم از این عکس ها رو نمایش بدیم:نتیجه به این صورت هست:دو بلاک 5*5 در سمت چپ نشون دهنده اعدادی هستند که در دسته 5 قرار گرفتن و دو بلاک سمت راست اعدادی هستند که در دسته 3 قرار گرفتن. بلاک های بالا سمت راست و پایین سمت چپ نمونه هایی هستند که به اشتباه تشخیص داده شدن. بعضی از این نمونه ها واقعا بد نوشته شدند و حتی برای ما هم تشخیصش سخته. اما بیشتر این عکس ها به راحتی قابل تشخیص هستند و فهمیدن اینکه چرا مدل اشتباه میکنه یکمی سخته.اما یادتون باشه که مغز ما یک سیستم تشخیص الگوی فوق العاده قوی هست و سیستم بینایی ما قبل از اینکه اطلاعات رو به ضمیر خودآگاه ما برسونه، پیش‌پردازش های پیچیده ای رو انجام میده. پس اینکه حس میکنیم کار آسونی هست به این معنی نیست که واقعا کار آسونی هستعلت این اشتباه ها این هست که ما از یک SGDClassifier ساده استفاده کردیم که یک مدل خطی هست. این مدل تنها کاری که میکنه این هست که به هر پیکسل در هر دسته، یک وزن اختصاص میده و وقتی یک عکس جدید میبینه، وزن همه پیکسل ها رو جمع میکنه و به یک امتیاز برای هر دسته میرسه. از اونجایی که 3 و 5 فقط توی چند پیکسل با هم تفاوت دارن، به راحتی این دو تا دسته رو قاطی میکنه.تفاوت اصلی بین 3 و 5 در محل قرارگیری خط کوچیکی هست که خط بالا رو به منحنی پایین وصل میکنه. اگر 3 رو به این صورت رسم کنید که اون قسمت اتصال کمی به سمت چپ کشیده شده باشه، ممکنه مدل اون رو 5 در نظر بگیره و بر عکس. به عبارت دیگه، این مدل به جهت و تغییر مکان ها حساس هست. یک راه برای کاهش این اشتباه، این هست که عکس ها رو پیش‌پردازش کنیم تا مطمئن بشیم که حتما به خوبی در مرکز قرار گرفتن و زیاد به اطراف تغییر جهت ندادن. این کار احتمالا بقیه خطا ها رو هم کم میکنه.دسته بندی چند لیبلیتا به اینجا هر نمونه رو در یک دسته قرار دادیم. بعضی اوقات میخوایم Classifier هر نمونه رو تو چند دسته قرار بده. مثلا یک مدل تشخیص چهره رو در نظر بگیرید: اگر بتونه چند فرد رو در یک تصویر تشخیص بده باید دقیقا چه کاری انجام بده؟ خب باید اسم هر شخصی رو که میشناسه، روی اون شخص تگ کنه. مثلا مدل ترین شده تا چهره های آلیس، باب و چارلی رو تشخیص بده. وقتی این مدل چهره آلیس و چارلی رو میبینه، خروجیش باید [1,0,1] باشه؛ یعنی آلیس بله، باب نه ، چارلی بله. به همچین مدلی که خروجیش چند تگ باینری باشه میگن Multilabel Classification.فعلا نمیخوایم وارد تشخیص چهره بشیم. با یک مثال ساده تر شروع میکنیم.این کد یک آرایه به نام y_multilabel شامل دو لیبل برای هر عکس درست میکنه. اولین لیبل بررسی میکنه که آیا عدد بزرگ هست یا نه (7  و 8 و 9 ) و دومی بررسی میکنه که آیا فرد هست یا نه. خط بعدی یک شیء از کلاس KNeighborsClassifier درست میکنه و بعد اون رو با آرایه ای که گفتیم ترین میکنیم. بعد ازش میخوایم تا عددی که بهش میدیم رو حدس بزنه:راه های زیادی برای ارزیابی یک Multilabel CLassifier وجود داره و انتخاب هر کدوم از این راه ها بستگی به پروژه داره. یک راه، محاسبه F1 Score (یا هر کدوم از معیار هایی که تو این مطلب گفتیم) برای هر لیبل هست. بعد میانگین F1 Score برای همه لیبل ها رو محاسبه میکنیم:پیشفرض این تابع این هست که همه لیبل ها به یک اندازه مهم هستند اما خب بعضی اوقات شاید این صادق نباشه. اگر عکس های آلیس بیشتر از باب و چارلی باشن، بهتره که وزن بیشتری به امتیاز مدل در تشخیص عکس های آلیس بدیم. یک راه ساده اینه که به هر لیبل یک وزن برابر با support اون لیبل بدیم. support یعنی تعداد نمونه های اون لیبل. برای این کار باید بنویسیم average=weightedدسته بندی با چند خروجیآخرین نوع دسته بندی multioutput multiclass classification یا multioutput classification نام داره. در واقع نسخه تعمیم یافته multilabel classification هست. در این نوع دسته بندی، هر لیبل میتونه چند دسته باشه یعنی هر لیبل میتونه بیشتر از دو تا مقدار داشته باشه. مثال زیر رو در نظر بگیرید.یک مدل میخوایم بسازیم که وظیفه‌اش حذف نویز از تصاویر هست. یک عکس نویزدار رو بعنوان ورودی میگیره و یک عکس بدون نویز رو برمیگردونه. خروجی این مدل یک آرایه از شدت هر پیکسل هست (مثل چیزی که تو دیتاست MNIST دیدیم) توجه کنید که خروجی این مدل چند لیبلی هست (به ازای هر پیکسل یک لیبل) و هر لیبل میتونه مقادیر مختلفی داشته باشه (شدت هر پیکسل بین 0 تا 255 هست)بعضی اوقات مرز بین Classification و Regression شفاف نیست؛ مثل همین مثال. تخمین شدت یک پیکسل بهش میخوره بیشتر یک کار مربوط به Regression باشه تا Classification. همچنین مدل های Multioutput فقط محدود به Classification نیستند. حتی میشه یک مدل ساخت که خروجیش برای هر نمونه چند لیبل باشه و اون لیبل ها شامل دسته و مقدار باشنبیاید به تصاویر دیتاست MNIST نویز اضافه کنیم. این کار رو با استفاده از تابع randint در کتابخانه NumPy انجام میدیم:بیاید به یک عکس تست ست نگاهی بندازیم. سمت چپ همون عکس راست هست اما با نویز هایی که خودمون بهش اضافه کردیم. حالا بیاید مدل رو برای پاکسازی این عکس ترین کنیم:این عکس به اندازه کافی شبیه به عکس اصلی هست. خب اینجا مطلب مربوط به دسته بندی با استفاده از دیتاست MNIST تموم میشه. حالا باید اطلاعات خوبی در مورد معیار های ارزیابی یک مدل Classification، مبادله بین Precision و Recall و Classifier های مختلف داشته باشید.امیدوارم مفید واقع شده باشه :)</description>
                <category>مجتبی میر یعقوب زاده</category>
                <author>مجتبی میر یعقوب زاده</author>
                <pubDate>Fri, 24 Jul 2020 20:19:21 +0430</pubDate>
            </item>
            </channel>
</rss>