در جلسه قبل از الگوریتم nearest neighbor استفاده کردیم و با ۹۶ درصد موفقیت توانستیم انواع گل های مورد نظرمان را شناسایی کنیم اما آخرین مرحله در نوشتن یادگیری ماشینی ارتقاء مدل یا همان الگوریتم مورد استفاده است. به نظر شما چطور می توانیم این کار را انجام بدهیم؟ در حال حاضر ما داده هایمان را به داده های تست و داده های تمرینی تقسیم کرده ایم. یکی از وظایف ما به عنوان مهندس یادگیری ماشینی این است که مشخص کنیم چه مقدار از داده هایمان باید تمرینی باشد و چه مقداری باید داده تست باشد. به طور مثال اگر من بگویم که ۸۰ درصد داده ها باید داده تست باشد و فقط ۲۰ درصد صرف تمرین و یادگیری الگوریتم شود چه می شود؟ شما می توانید این کار را در کدهای خودتان انجام بدهید. من Jupyter Notebook خودم را باز کرده و به جای 40 درصد داده تمرینی از 90 درصد داده تمرینی استفاده می کنم:
from sklearn.model_selection import train_test_split X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.9) print(X_train.shape) print(X_test.shape)
یادآوری می کنیم که بلوک های کد در Jupyter Notebook مستقل هستند بنابراین برای دریافت نتیجه صحیح باید از بلوکی که ویرایش شده است شروع کرده و آن را run کنید (دوباره اجرا کنید تا مقادیر موجود در مموری تغییر کنند) و سپس با بلوک های بعدی نیز همین کار را انجام بدهید تا به انتهای برنامه برسید. این مسئله یعنی با انجام این ویرایش باید تمام بلوک های زیر را یکی پس از دیگری انتخاب کرده و دکمه run بالای صفحه را بزنید:
from sklearn.model_selection import train_test_split X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.9) print(X_train.shape) print(X_test.shape)
و سپس:
from sklearn.neighbors import KNeighborsClassifier knn = KNeighborsClassifier(n_neighbors=3) knn.fit(X_train, y_train) y_pred = knn.predict(X_test)
و سپس:
from sklearn import metrics print(metrics.accuracy_score(y_test, y_pred))
با انجام این کار نتیجه کار و درصد موفقیت الگوریتم تغییر می کند و برابر با 0.9185185185185185 می شود که به مقدار قابل توجهی از ۹۶ الی ۹۷ درصد کمتر است. از طرف دیگر اگر تعداد داده های تست را بسیار کمتر کنیم چطور؟ من برای تست این نظریه از ۱۰ درصد داده تست استفاده می کنم و ۹۰ درصد دیگر را به داده های تمرینی اختصاص می دهم:
from sklearn.model_selection import train_test_split X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.1) print(X_train.shape) print(X_test.shape)
من با انجام این کار عدد 1.0 را به عنوان درصد موفقیت دریافت می کنم که یعنی ۱۰۰ درصد از کار ما موفق بوده است! آیا به نظر شما این کار بهترین روش بهبود الگوریتم است و واقعا الگوریتم ما ۱۰۰ درصد کار می کند؟ اگر فقط به این اعداد سطحی نگاه نکنید، متوجه خواهید شد که این درصد موفقیت یک عدد کاذب است. زمانی که درصد داده های تمرینی را روی ۱۰ درصد گذاشتید و بلوک آن را run کردید مقادیر زیر را دریافت می کنید:
(135, 4)
(15, 4)
یعنی فقط ۱۵ ردیف داده را به عنوان تست در نظر گرفته ایم و در عوض از ۱۳۵ ورودی (داده تمرینی) برای یادگیری استفاده کرده ایم. به عبارت ساده تر داده های تمرینی ما آنچنان کوچک است که نمی توانیم به عدد «1.0» اعتماد کنیم. مثلا اگر قرار باشد شما در مدرسه امتحان بدهید و آزمون شما تنها ۳ سوال داشته باشد، حتی اگر شما نمره کامل را بگیرید، آیا می توان گفت که درس را واقعا یاد گرفته اید؟ از طرف دیگر هر چه تعداد داده های تمرینی بیشتر باشد، الگوریتم یادگیری ما نیز قوی تر است. بنابراین مسئله نسبت داده های تمرینی و تست مانند دو کفه یک ترازو هستند و هر کدام مزایا و معایبی دارند. وظیفه شما به عنوان مهندس یادگیری ماشینی این است که این نسبت را به خوبی مشخص کنید.
یکی دیگر از راه های ارتقاء مدل در یادگیری ماشینی تغییر دادن پارامترهای مختلف مدل یا همان الگوریتم است. من ابتدا داده های تست را روی ۲۰ درصد می گذارم تا سایز آن ها بیش از حد کوچک نباشد:
from sklearn.model_selection import train_test_split X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2) print(X_train.shape) print(X_test.shape)
در مرحله بعدی به سراغ یکی از پارامترهای مهم در nearest neighbor می رویم و آن تعداد مناطق بررسی داده است. به طور مثال به کد زیر توجه کنید:
from sklearn.neighbors import KNeighborsClassifier knn = KNeighborsClassifier(n_neighbors=3) knn.fit(X_train, y_train) y_pred = knn.predict(X_test)
پارامتر n_neighbors تعداد مناطق همسایه برای بررسی داده ها را انتخاب می کند و فعلا روی ۳ منطقه قرار دارد. با اجرای این کدها درصد موفقیت ما روی 0.9333333333333333 خواهد بود. حالا بیایید این مقدار را به ۴ تغییر بدهیم:
from sklearn.neighbors import KNeighborsClassifier knn = KNeighborsClassifier(n_neighbors=4) knn.fit(X_train, y_train) y_pred = knn.predict(X_test)
با انجام این کار درصد موفقیت ما به 0.9666666666666667 ارتقاء پیدا می کند! در نظر داشته باشید که مسئله به سادگی اضافه کردن تعداد مناطق تحلیل نیست و اینطور نیست که لزوما با بالا بردن تعداد این مناطق نتایج بهتری به دست بیاورید. به مثال زیر توجه کنید:
from sklearn.neighbors import KNeighborsClassifier knn = KNeighborsClassifier(n_neighbors=25) knn.fit(X_train, y_train) y_pred = knn.predict(X_test)
ما ۲۵ منطقه مختلف را تعریف کرده ایم. در این حالت اگر کدها را اجرا کنیم درصد موفقیت الگوریتم 0.9666666666666667 خواهد بود بنابراین هیچ تغییری در درصد موفقیت نداشته ایم. بیایید این مقدار را کمی بالاتر ببریم:
from sklearn.neighbors import KNeighborsClassifier knn = KNeighborsClassifier(n_neighbors=99) knn.fit(X_train, y_train) y_pred = knn.predict(X_test)
این بار با اجرای کد بالا درصد موفقیت ما 0.8666666666666667 خواهد بود که یعنی با افت ۱۰ درصدی مواجه شده ایم!
نکته: ممکن است اعدادی که شما دریافت می کنید تفاوت داشته باشند. نتایج برگردانده شده از یادگیری ماشینی لزوما برای من و شما یکی نیست چرا که الگوریتم در شرایط مختلف نتایج مختلفی تولید می کند، مخصوصا زمانی که الگوریتم آپدیت شود (مثلا شما چند ماه بعد از نگارش این مقاله آن را مطالعه کنید) یا هر تغییری که به صورت مستقیم بر روی الگوریتم تاثیر بگذارد. بنابراین به یادگیری ماشینی به عنوان یک انسان نگاه کنید که در هر بار یادگیری ممکن است چیز های مختلفی را یاد بگیرد.
من این آرگومان را به عدد ۳ برمی گردانم.
همانطور که گفتم هر کدام از داده های ما تعداد ستون های خاصی دارد. به طور مثال در مجموعه داده گل زنبق که در حال استفاده از آن هستیم داده ها به شکل زیر بودند:
array([[5.1, 3.5, 1.4, 0.2], [4.9, 3. , 1.4, 0.2], [4.7, 3.2, 1.3, 0.2], [4.6, 3.1, 1.5, 0.2], [5. , 3.6, 1.4, 0.2], [5.4, 3.9, 1.7, 0.4], [4.6, 3.4, 1.4, 0.3], [5. , 3.4, 1.5, 0.2], [4.4, 2.9, 1.4, 0.2], [4.9, 3.1, 1.5, 0.1], [5.4, 3.7, 1.5, 0.2], [4.8, 3.4, 1.6, 0.2], [4.8, 3. , 1.4, 0.1], [4.3, 3. , 1.1, 0.1], [5.8, 4. , 1.2, 0.2], [5.7, 4.4, 1.5, 0.4], [5.4, 3.9, 1.3, 0.4], [5.1, 3.5, 1.4, 0.3], [5.7, 3.8, 1.7, 0.3], [5.1, 3.8, 1.5, 0.3], [5.4, 3.4, 1.7, 0.2], [5.1, 3.7, 1.5, 0.4], [4.6, 3.6, 1. , 0.2], [5.1, 3.3, 1.7, 0.5], [4.8, 3.4, 1.9, 0.2], [5. , 3. , 1.6, 0.2], [5. , 3.4, 1.6, 0.4], // بقیه داده ها
همانطور که مشخص است داده های ما چهار ستون دارند که یعنی چهار ویژگی خاص برای هر گیاه زنبق تعریف شده است. وب سایت Kaggle ستون های این مجموعه داده را به زبان ساده ای توصیف کرده است؛ اعداد درون لیست های بالا به ترتیب طول و عرض گلبرگ و طول و عرض کاسبرگ هستند و الگوریتم ما باید بر این اساس بتواند نوع خاص زنبق را شناسایی کند. البته وب سایت Kaggle یک ستون به نام id را نیز دارد که ما در بالا نشان نداده ایم:
اگر ما به غیر از طول و عرض گلبرگ و کاسبرگ ویژگی دیگری از گیاه زنبق را نیز داشتیم (مثلا الگوی رنگ ها در گلبرگ های گیاه) درصد موفقیت ما به احتمال زیاد بالاتر می رفت چرا که الگوریتم ویژگی های بیشتری را برای شناسایی گیاه دارد. به طور مثال اگر به شما بگویم از میان جمعی فردی را پیدا کنید که پیراهن مشکی و موی بلند دارد، فقط همین دو مشخصه را برای تشخیص آن فرد خواهید داشت اما اگر بگویم فردی با پیراهن مشکی، موی بلند و کفش های سفید و قد متوسط و حدودا ۲۵ سال را پیدا کنید کارتان بسیار راحت تر می شود و احتمال اینکه بتوانید حدس درستی بزنید بسیار بیشتر می شود.
مورد آخر که شاید مهم ترین مورد باشد انتخاب الگوریتم صحیح برای یادگیری ماشینی شما است. ما در این پروژه از الگوریتم KNeighborsClassifier استفاده کردیم چرا که الگوریتم مناسبی محسوب می شود. احتمالا می پرسید از کجا بدانیم یک الگوریتم، الگوریتم مناسبی محسوب می شود؟ برای پاسخ به این سوال ابتدا باید نوع داده هایتان را در نظر بگیرید و سپس در مورد الگوریتم های مختلف مطالعه کنید تا ببینید چه الگوریتمی با ساختار داده های شما سازگار است. به طور مثال اگر به صفحه توضیحات الگوریتم ها در وب سایت scikit-learn مراجعه کنید متوجه خواهید شد که nearest neighbor فقط یکی از الگوریتم های موجود در این پکیج است.
من در همین صفحه به الگوریتم های موجود و توضیحات آن ها نگاهی می اندازم و با الگوریتمی به نام decision tree آشنا می شوم. در قسمت توضیحات این الگوریتم آمده است که Decision Trees (DTs) are a non-parametric supervised learning method used for classification and regression بنابراین برای کار ما (classification یا دسته بندی) مناسب است. بیایید در پروژه خود از این الگوریتم استفاده کنیم. در حال حاضر یکی از بلوک های کد شما به شکل زیر است:
from sklearn.neighbors import KNeighborsClassifier knn = KNeighborsClassifier(n_neighbors=3) knn.fit(X_train, y_train) y_pred = knn.predict(X_test)
این بلوک همان بخشی است که در آن از nearest neighbor استفاده کرده ایم، بنابراین اگر بخواهیم به جای آن از decision tree استفاده کنیم باید آن را به شکل زیر ویرایش نماییم:
from sklearn.tree import DecisionTreeClassifier knn = DecisionTreeClassifier() knn.fit(X_train, y_train) y_pred = knn.predict(X_test)
ما در این بلوک تغییری ایجاد کرده ایم بنابراین باید این بلوک و بلوک های بعدی آن را run کنیم. با انجام این کار درصد موفقیت 0.9333333333333333 را دریافت می کنیم (باز هم می گویم که شما ممکن است عدد دیگری را دریافت کنید). در پروژه ما تغییر الگوریتم تفاوت زیادی ایجاد نکرد اما در پروژه های دیگر و بسته به داده های شما ممکن است تفاوت ایجاد شده بسیار زیاد باشد و حتی ممکن است با تغییر الگوریتم درصد موفقیت شما سقوط کند و به ۲۰ درصد برسد.
حالا که تمام مراحل یادگیری ماشینی را انجام داده ایم سوالی برایمان مطرح می شود. فرض کنید ما چنین پروژه ای را نوشته ایم و حالا می خواهیم از آن استفاده کنیم، مثلا تصویر گل زنبقی را از کاربر گرفته و به او بگوییم که این تصویر متعلق به کدام دسته از گل های زنبق است. طبیعتا دریافت داده از کاربر نیاز به یک UI دارد (مثلا یک وب سایت یا یک برنامه دسکتاپ برای ویندوز یا لینوکس یا یک برنامه اندرویدی و الی آخر) و من قصد ساخت UI را ندارم بنابراین باید به فکر دیگری باشیم. ما می توانیم دریافت اطلاعات از کاربر را شبیه سازی کنیم. یعنی چه؟ یعنی در Jupyter Notebook خودمان وارد بلوک بعدی می شویم و یک داده ساده را به دلخواه ایجاد می کنیم:
sample = [[3,5,4,2], [2,3,5,4]]
من ساختار داده ها را در بخش قبلی به شما نشان دادم. بر اساس این ساختار دو تصویر گل زنبق را داریم که اندازه های مختلفی دارند و طبیعتا هر دو باید در لیست (آرایه) باشند. حالا می توانیم با list comprehension یا حلقه های ساده (هر کدام که خودتان بخواهید) روی این دو گل گردش کرده و نوع دقیق آن ها را به کاربر بگوییم:
sample = [[3,5,4,2], [2,3,5,4]] predictions = knn.predict(sample) pred_species = [iris.target_names[p] for p in predictions] print("predictions: ", pred_species)
یعنی ما هر دو گل را به تابع knn.predict داده ایم که مسئول انجام پیشبینی ها است. نتیجه این پیش بینی در predictions ذخیره شده است. در صورتی که predictions را چاپ کنید محتویات درون آن به شکل زیر است:
[1 2]
یعنی گل اول از دسته ۱ و گل دوم از دسته ۲ است. حالا با list comprehension خود هر کدام از این دسته ها را به target_names داده ایم تا نام آن دسته برایمان نمایش داده شود. با اجرای دستور بالا نتیجه زیر را می گیرید:
predictions: ['versicolor', 'virginica']
versicolor و virginica هر دو دسته هایی از گل زنبق هستند.
با اینکه این مسئله برای ما جذاب است و تا حد درستی کار می کند اما مشکلی وجود دارد. داده های ما بسیار کم است! اگر به مجموعه داده گل زنبق نگاهی بیندازید متوجه می شوید که تعداد کل ردیف های ما ۱۵۰ ردیف است اما در برنامه های واقعی معمولا چند میلیون ردیف داده داریم. طبیعتا زمانی که چنین حجم داده ای داشته باشیم تابعی مانند knn.fit که داده ها را بارگذاری می کند به زمان بسیار زیادی نیاز خواهد داشت تا بتواند با این حجم از داده کار کند. به همین دلیل است که انجام چنین پردازش های سنگینی معمولا روی یک GPU (واحد پردازشگر گرافیکی) انجام می شود نه اینکه بخواهیم آن را روی یک لپتاپ ساده انجام بدهیم.
با توجه به چیزی که گفتم، برنامه ما باید طراحی خاصی داشته باشد که نیازی به اجرای knn.fit در هر درخواست نداشته باشد. مثلا اگر قرار باشد هر بار که کاربر تصویری را برای الگوریتم ما ارسال کرد، مجبور به اجرای دوباره knn.fit باشیم، پردازش درخواست ها چند روز طول می کشد! راه حل استفاده از مبحثی به نام model persistence یا پایداری مدل است. در این روش ما مدل خود را در یک فایل ذخیره می کنیم تا نیازی به محاسبه دوباره نباشد. مثلا زمانی که در حال استفاده از قابلیت هوش مصنوعی دوربین تلفن همراه خود هستید، گوشی شما الگوریتم را همانجا تمرین نمی دهد بلکه مدل از قبل روی گوشی شما قرار دارد.
سوال اینجاست که چطور می توانیم از مدل خودمان خروجی بگیریم؟ برای انجام این کار باز هم از پکیج scikit-learn استفاده می کنیم:
import joblib joblib.dump(knn, 'mlbrain.joblib')
ماژول joblib در scikit-learn متدی به نام dump دارد که به شما اجازه می دهد از مدل خود به صورت باینری خروجی بگیرید. آرگومان اولی که به این متد پاس داده ام همان مدل ما است (knn) و آرگومان دوم، نام فایلی است که مدل باید در آن ذخیره شوند. شما می توانید هر نامی را انتخاب کنید اما پسوندش باید joblib باشد. اگر این کد را اجرا کنید چنین فایلی در سیستم شما ظاهر می شود که همان مدل شما است.
در صورتی که با اجرای کد بالا با خطا روبرو شدید بهتر است از دستور import زیر به جای import مستقیم joblib استفاده کنید:
from sklearn.externals import joblib
حالا هر زمانی که بخواهید از این مدل استفاده کنید می توانیم به روش زیر عمل کنیم:
model = joblib.load('mlbrain.joblib') model.predict(X_test)
همانطور که می بینید من متد load را صدا زده ام و فایل مدل خودمان را بارگذاری کرده ام. برای تست کردن آن نیز می توانیم از همان داده های تست خودمان استفاده کنیم. با اجرای کد بالا نتایج زیر را می گیریم:
array([0, 1, 0, 2, 0, 1, 1, 0, 1, 2, 0, 2, 1, 2, 2, 2, 0, 2, 0, 1, 1, 1, 0, 1, 1, 2, 2, 1, 1, 0])
خطایی نگرفته ایم بنابراین مدل ما به درستی بارگذاری شده است. ما می توانیم همان تست قبلی خومان را نیز در این بخش بنویسیم:
model = joblib.load('mlbrain.joblib') sample = [[3,5,4,2], [2,3,5,4]] predictions = model.predict(sample) pred_species = [iris.target_names[p] for p in predictions] print("predictions: ", pred_species)
با اجرای این کد نتیجه زیر را می گیریم:
predictions: ['versicolor', 'virginica']
بنابراین به راحتی توانستیم مدل خودمان را ذخیره کرده و از آن استفاده کنیم.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.