Python حرفه‌ای: یادگیری ماشینی تشخیص تصویر

Professional Python: Machine Learning Image Recognition

14 فروردین 1400
درسنامه درس 43 از سری پایتون حرفه‌ای
Python حرفه ای: یادگیری ماشینی تشخیص تصویر (قسمت 43)

نمونه ای از یادگیری ماشینی واقعی

ما در جلسات قبل پروژه های کوچکی را با Jupyter Notebook ها انجام دادیم که بسیار محدود بودند و یادگیری ماشینی محسوب نمی شدند اما در این جلسه و چند جلسه آینده می خواهیم یک نمونه واقعی از یادگیری ماشینی را با هم کار کنیم. در نظر داشته باشید که این پروژه فقط یک نمونه ساده از یادگیری ماشینی است و اگر به این مبحث علاقه مند هستید باید انواع و اقسام پروژه های مختلف و کتاب های مختلف یا دوره های مختلف را مطالعه کنید. البته زمانی که از «سادگی» این پروژه صحبت می کنیم منظورمان سادگی نسبی یا نسبت به پروژه های سنگین تر است نه اینکه پروژه ما واقعا ساده باشد. اگر اولین باری است که با یادگیری ماشینی کار می کنید احتمالا برخی از مباحث کمی برایتان سنگین باشد اما اگر تلاش کنید متوجه تمام توضیحات خواهید شد.

ما در این پروژه از مجموعه داده خاصی به نام iris data set استفاده می کنیم. iris به معنای گل زنبق است و مجموعه داده گل زنبق یک مجموعه داده چند متغیره است که توسط رانلد فیشر در سال ۱۹۳۶ معرفی شد. ما می توانیم با استفاده از کتابخانه scikit-learn به این مجموعه داده دسترسی داشته باشیم. این کتابخانه به صورت پیش فرض چندین data set یا مجموعه داده را در خود دارد و ما می توانیم از هر کدام به صورت دلخواه استفاده کنیم. همچنین نیازی به نصب این کتابخانه نیست چرا که anaconda به صورت پیش ساخته تمام این کتابخانه ها را در خود دارد.

ساخت یک Notebook جدید

برای شروع پروژه به Jupyter Notebook خود رفته و یک Notebook جدید را بسازید. من نام این Notebook را iris می گذارم. اولین کاری که در این notebook انجام می دهیم وارد کردن مجموعه داده هایمان یا بارگذاری آن در اسکریپت است:

from sklearn.datasets import load_iris

iris = load_iris()

توجه کنید که کد بالا اولین input ما است. با صدا زدن متد load_iris در کد بالا و کلیک روی گزینه run، مجموعه داده مورد نظر خود را در مموری بارگذاری کرده ایم. این مرحله اول از یادگیری ماشینی بود: وارد کردن داده. مرحله دوم پاکسازی داده ها بود اما مجموعه داده گل زنبق از قبل تمیز و آماده شده است و هیچ نیازی به پاک سازی ندارد بنابراین مستقیما باید به سراغ مرحله سوم یا تقسیم داده ها به داده های تمرینی و تست برویم. من قبلا توضیح داده بودم که برای کدنویسی یادگیری ماشینی باید دو دسته داده داشته باشیم: training set و test set. دسته تمرینی یا training set داده هایی است که به عنوان ورودی به سیستم می دهیم تا تمرین کرده و بر اساس الگوریتمی خاص یاد بگیرد. زمانی که یادگیری انجام بشود، سیستم یک تابع را به ما برمیگرداند که می توانیم با آن دسته تست یا test set را تست کنیم تا ببینیم آیا نتایج تولید شده مد نظر ما است یا خیر.

در یادگیری ماشینی دو علامت خاص X و Y وجود دارد؛ X همان ورودی است که به الگوریتم یادگیری می دهیم و Y نیز همان هدف است، یعنی مقداری که می خواهیم به آن برسیم. در اینجا X همان داده های زیر است. من به input بعدی در Notebook رفته و می گویم:

iris.data

حالا اگر روی run کلیک کنید، می توانید خصوصیت data از متغیر iris را مشاهده کنید. یادتان باشد که متغیر iris حاوی مجموعه داده گل زنبق است و خصوصیت data آن یک آرایه بزرگ را برایمان برمی گرداند:

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],

       [5.2, 3.5, 1.5, 0.2],

       [5.2, 3.4, 1.4, 0.2],

       [4.7, 3.2, 1.6, 0.2],

       [4.8, 3.1, 1.6, 0.2],

      ....

       [7.2, 3.2, 6. , 1.8],

       [6.2, 2.8, 4.8, 1.8],

       [6.1, 3. , 4.9, 1.8],

       [6.4, 2.8, 5.6, 2.1],

       [7.2, 3. , 5.8, 1.6],

       [7.4, 2.8, 6.1, 1.9],

       [7.9, 3.8, 6.4, 2. ],

       [6.4, 2.8, 5.6, 2.2],

       [6.3, 2.8, 5.1, 1.5],

       [6.1, 2.6, 5.6, 1.4],

       [7.7, 3. , 6.1, 2.3],

       [6.3, 3.4, 5.6, 2.4],

       [6.4, 3.1, 5.5, 1.8],

       [6. , 3. , 4.8, 1.8],

       [6.9, 3.1, 5.4, 2.1],

       [6.7, 3.1, 5.6, 2.4],

       [6.9, 3.1, 5.1, 2.3],

       [5.8, 2.7, 5.1, 1.9],

       [6.8, 3.2, 5.9, 2.3],

       [6.7, 3.3, 5.7, 2.5],

       [6.7, 3. , 5.2, 2.3],

       [6.3, 2.5, 5. , 1.9],

       [6.5, 3. , 5.2, 2. ],

       [6.2, 3.4, 5.4, 2.3],

       [5.9, 3. , 5.1, 1.8]])

نتیجه برگردانده شده لیست عظیمی است که داده های ما را دارد. هر کدام از این مقادیر و لیست ها به نوعی ویژگی های یک گل زنبق را نشان می دهد (۳ نوع گل زنبق مختلف در این data set وجود دارد). شاید بگویید چطور یک سری اعداد نماینده یک گل هستند!؟ این اعداد هر کدام ویژگی خاصی را نشان می دهند. اگر به Kaggle برویم و به ستون های این داده نگاه کنیم متوجه می شویم که اعداد درون هر لیست به ترتیب طول و عرض گلبرگ و طول و عرض کاسبرگ هستند و الگوریتم ما باید بر این اساس بتواند نوع خاص زنبق را شناسایی کند. از طرفی Y نیز همان target یا هدف بود. اگر به جای کد بالا target را بنویسیم چه نتیجه ای را دریافت می کنیم:

iris.target

با اجرای این کد نتیجه زیر را دریافت می کنیم:

array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,

       0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,

       0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,

       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,

       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,

       2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,

       2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2])

این لیست هدفی است که باید به آن برسیم. یعنی چه؟ اگر از دوران دبیرستان یادتان باشد در درس ریاضی می گفتیم f(x) = y که یک تابع ساده است. یعنی تابعی داریم که اگر x را به عنوان ورودی به آن بدهیم،‌y را به عنوان خروجی دریافت می کنیم. چه چیزی ما را از x به y می رساند؟ الگوریتم.

در قدم اول باید x و y را مشخص کنیم بنابراین در همان input کدها را به شکل زیر ویرایش می کنم:

X = iris.data

y = iris.target

استفاده از X بزرگ و y کوچک یک قرارداد است؛ یعنی الزامی به پیروی از آن نیست اما بهتر است این کار را انجام بدهید. در مرحله بعدی باید نام این خصوصیات (طول و عرض کاسبرگ و گلبرگ) و همچنین نام هر دسته خاص از گل های زنبق را نیز مشخص کنیم بنابراین:

X = iris.data

y = iris.target




feature_names = iris.feature_names

target_names = iris.target_names




feature_names

خصوصیات feature_names و target_names به صورت پیش فرض در این data set موجود است. اگر کد بالا را اجرا کنیم مقدار feature_names برایمان چاپ می شود:

['sepal length (cm)',

 'sepal width (cm)',

 'petal length (cm)',

 'petal width (cm)']

sepal به معنی «کاسبرگ» و petal به معنی «گلبرگ» است. همچنین length در این مثال به معنی «طول» و width در این مثال به معنی «عرض» است. تمام اندازه ها نیز در واحد cm (سانتی متر) می باشند. در مرحله بعدی target names را چاپ می کنیم تا محتویاتش را مشاهده کنیم (هنوز به input بعدی نرفته ایم):

X = iris.data

y = iris.target




feature_names = iris.feature_names

target_names = iris.target_names




target_names

با اجرای کد بالا نتیجه زیر را می گیریم:

array(['setosa', 'versicolor', 'virginica'], dtype='<U10')

setosa و versicolor و virginica سه دسته متفاوت از گل زنبق هستند و همان هدف های ما می باشند.

تقسیم داده ها به داده های تمرینی و تست

ما در این قسمت باید داده های خود را تقسیم کنیم‌ (این سومین مرحله از یادگیری ماشینی بود). من برای این کار به input سوم رفته و train_test_split را از کتابخانه sklearn وارد اسکریپت می کنیم:

from sklearn.model_selection import train_test_split

متد train_test_split آرایه ها یا ماتریس ها را به صورت تصادفی به داده های تمرین و تست تقسیم می کند. در صورتی که ترتیب input ها را فراموش کرده اید من تمام کدها را تا این قسمت برایتان قرار می دهم:

from sklearn.datasets import load_iris

iris = load_iris()







X = iris.data

y = iris.target




feature_names = iris.feature_names

target_names = iris.target_names




target_names







from sklearn.model_selection import train_test_split

من هر input را با سه خط از دیگر input ها جدا کرده ام بنابراین می بینید که سه input مختلف داریم و در حال حاضر در input سوم هستیم. در همین input از متد train_test_split استفاده می کنیم:

from sklearn.model_selection import train_test_split




X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.4)

این متد مقادیر test و train را برای X و y برمی گرداند بنابراین این متغیرها را در یک خط تعریف کرده ایم. همچنین باید X و y را به خود این متد بدهید. البته آرگومان های مختلف برای این متد وجود دارد که می توانید آن ها را در documentation رسمی scikit-learn مطالعه کنید اما من فقط از test_size استفاده کرده ام که مسئول تعیین اندازه داده های تست ما است و همیشه عددی بین 0.0 و 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.4)




print(X_train.shape)

print(X_test.shape)

خصوصیت shape تعداد ردیف ها و ستون های لیست های X را به ما می دهد بنابراین با اجرای این کد نتیجه زیر را می گیریم:

(90, 4)

(60, 4)

یعنی داده های تمرینی ما ۹۰ ردیف و ۴۰ ستون هستند و همچنین داده های تست ما ۶۰ ردیف و ۴ ستون می باشند. طبیعتا با تغییر آرگومان test_size در تابع train_test_split می توانید این نسبت را تغییر بدهید. هر چه مقدار test_size را بیشتر کنید، نسبت داده های تست نسبت به داده های تمرینی بیشتر می شود. به طور مثال مقدار 0.5 برای test_size به معنی تقسیم داده ها به صورت ۵۰ درصد است؛ یعنی نصف داده ها تمرینی و نصف داده ها تستی باشد. به طور کلی پیشنهاد می شود که داده های تمرینی همیشه بیشتر از داده های تستی باشد چرا که الگوریتم ما برای یادگیری به داده های بیشتری نیاز دارد تا اینکه بخواهیم آن را تست کنیم. با این حساب عدد test_size همیشه باید کمتر از 0.5 باشد.

سوال: چرا کل داده ها را به صورت داده تمرینی به الگوریتم نمی دهیم تا بعدا کل همان داده ها را تست کنیم؟

پاسخ: نتایج چنین تستی معتبر نخواهد بود چرا که الگوریتم داده هایی تکراری و از قبل تمرین شده را تست خواهد کرد. این کار مانند این است که به شما ۳۰ سوال امتحانی بدهیم و سپس همان ۳۰ سوال را از شما امتحان بگیریم! قطعا نمره کامل را خواهید گرفت اما مشخص نخواهد شد که آیا درس را یاد گرفته اید یا خیر!

ساخت مدل/الگوریتم

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

الگوریتمی که در این جلسه توسط ما استفاده می شود، الگوریتمی آماده به نام nearest neighbor (به معنی نزدیک ترین همسایه) است. این الگوریتم یک الگوریتم بسیار ساده می باشد که کار با آن نیازی به دانش تخصصی ندارد. احتمالا می توانید از نام «نزدیک ترین همسایه» نحوه عملکرد آن را حدس بزنید؛ نزدیک ترین همسایه یک الگوریتم instance-based (نمونه محور) می باشد، بدین معنی که سعی در ساخت یک مدل داخلی و کلی ندارد بلکه به سادگی نمونه های داده های تست را در خود ذخیره می کند. در مرحله بعدی نزدیک ترین همسایه های آن داده رای می دهند و رای بیشتر برنده خواهد شد. این مدل یا الگوریتم به صورت آماده در scikit-learn وجود دارد و ما از آن استفاده خواهیم کرد اما یادتان باشد که تعداد مدل های آماده در scikit-learn بسیار زیاد است و نمی توانیم تمام آن ها را بررسی کنیم. شما می توانید با مراجعه به صفحاتی مانند این صفحه و این صفحه تعداد زیاد این الگوریتم ها را مشاهده کنید.

من در یک input کدهای زیر را اضافه می کنم:

from sklearn.neighbors import KNeighborsClassifier




knn = KNeighborsClassifier(n_neighbors=3)

knn.fit(X_train, y_train)

ما در ابتدا متد KNeighborsClassifier را وارد اسکریپت خود کرده ایم. در مرحله بعدی این متد را صدا زده و تعداد همسایه ها یا data point (نقاط داده) لازم برای تعیین نوع گل زنبق را به آن پاس می دهیم. آرگومان n_neighbors یکی از آرگومان های مختلفی است که می توانیم به این متد پاس بدهیم اما فعلا فقط به همین آرگومان بسنده می کنیم. من عدد ۳ را انتخاب کرده ام. انتخاب تعداد نقاط داده کار یک مهندس داده و یادگیری ماشینی است و چیزی نیست که بتوانیم در این جلسه بررسی کنیم بنابراین من از روی تعداد دسته های گل زنبق عدد ۳ را انتخاب کرده ام (سه دسته گل زنبق وجود دارد). در نهایت باید متد fit را صدا زده و داده های تمرینی خود را به آن پاس بدهیم. طبیعتا می دانید که X_train و y_train همان متغیرهایی هستند که در input قبلی تعریف کرده بودیم. در حال حاضر اگر کد بالا را اجرا کنید نتیجه مهمی نمی گیرید بلکه فقط این داده را دریافت می کنید:

KNeighborsClassifier(n_neighbors=3)

در ظاهر چیزی اتفاق نمی افتد اما در پس زمینه مدل شما با داده های تمرینی ساخته شده است.

بررسی مدل ساخته شده

ما در بخش قبلی مدل خود را ساختیم اما حالا باید آن را تست کنیم. برای انجام این کار از متدی به نام predict استفاده می کنیم:

from sklearn.neighbors import KNeighborsClassifier




knn = KNeighborsClassifier(n_neighbors=3)

knn.fit(X_train, y_train)




y_pred = knn.predict(X_test)

ما در این کد متد predict (به معنی «پیشبینی») را صدا زده ایم و داده های تست را به آن پاس داده ایم. ما بر اساس داده های تمرینی یک مدل ساختیم و حالا باید با استفاده از داده های تست (داده هایی که پاسخشان را خودمان داریم، یعنی می دانیم کدام داده کدام گل زنبق است)، دقت این مدل در شناسایی را مقایسه می کنیم. ابتدا یک بار دیگر کد بالا را اجرا کنید تا کد اضافه شده (predict) اجرا شود و متغیر y_pred ساخته شود. در مرحله بعدی در یک input جدید می گوییم:

from sklearn import metrics




print(metrics.accuracy_score(y_test, y_pred))

متد  accuracy_score مسئول اندازه گیری دقت مدل ساخته شده است بنابراین y_test (داده های تست که جوابشان را می دانیم) و y_pred (پیشبینی انجام شده توسط مدل) را با هم مقایسه می کنیم. با اجرای کد بالا نتیجه زیر را می گیریم:

0.9666666666666667

بنابراین دقت ما ۹۶ درصد است! در قسمت بعد، در این باره بیشتر صحبت می کنیم.

تمام فصل‌های سری ترتیبی که روکسو برای مطالعه‌ی دروس سری پایتون حرفه‌ای توصیه می‌کند:
نویسنده شوید

دیدگاه‌های شما

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.