یادگیری ماشین: خواندن دستخط بدون یادگیری عمیق

دسته‌بندی تصاویر (Image Classification) به فرآیندی در بینایی کامپیوتر گفته می‌شود که در آن تصاویر بر مبنای محتوایی که دارند طبقه‌بندی می‌شوند؛ به عنوان مثال یک الگوریتم دسته‌بندی تصاویر می‌تواند هدف‌ش این باشد که بودن یا نبودن انسان داخل یک تصویر را تشخیص دهد.

یکی از مثال‌های جالب یادگیری ماشین، استفاده از دسته‌بندی تصاویر برای تشخیص اعداد در دست‌خط‌های افراد است. دیتابیس پرکاربرد MNIST برای این منظور می‌تواند بسیار مفید واقع شود: این داده شامل ۷۰هزار عکس ۲۸*۲۸ است که از اسکن کردن اعداد دستنویس توسط اداره آمار ایالات متحده آمریکا و دانش‌آموزان دبیرستانی آمریکایی به‌دست آمده است. این داده به صورت وسیعی برای توسعه و تست سیستم‌های پردازش تصویر و الگوریتم های یادگیری ماشین به کار رفته است. نمونه‌ای از داده‌های MNIST را در تصویر پایین مشاهده می‌کنید:

نمونه‌هایی از داده MNIST که برای یادگیری ماشین استفاده می‌شود.
نمونه‌هایی از داده MNIST که برای یادگیری ماشین استفاده می‌شود.

جستجوی ساده‌ای در گوگل نشان می‌دهد که رایج‌ترین روش یادگیری ماشین برای دسته‌بندی این داده، استفاده از شبکه‌های عصبی پیچشی (Convolutional Neural Network) است که دسته‌ای از الگوریتم‌های یادگیری عمیق (Deep Learning) محسوب می‌شوند. اما ما در ادامه می‌خواهیم از روش متفاوت و جذاب استفاده کنیم که در عین حال دقت آن هم تا حد خوبی بالا است!

تشخیص اعداد: روند کار

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

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

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

گام ۱: بررسی داده‌ها

داده‌های مورد استفاده ما مربوط به مسابقه‌ای در سایت Kaggle بوده و می‌توانید آن‌ها را از اینجا دریافت کنید. داده‌ها به دو قسمت Train و Test تقسیم شده‌اند:

داده‌های Train هم شامل داده‌های اصلی می‌شود (که به عنوان ورودی استفاده می‌شود) و هم خروجی مورد انتظار (که مدل ما باید بتواند پیش‌بینی کند) را در خود دارد. از داده‌های Train برای یادگیری اولیه استفاده می‌کنیم. داده‌های Test تنها بخش اول (داده‌های اصلی) را دارد و خروجی‌ها از آن حذف شده تا کاربران برای شرکت در مسابقه آن‌ها را به دست بیاورند و برای داوری ارسال کنند؛ چون داده Test نیز در قسمتی از روند مسئله به کار ما می‌آید آن را حذف نکرده‌ایم.

یک عکس در داده‌های Kaggle
یک عکس در داده‌های Kaggle

داده Train جدولی شامل ۷۸۵ ستون و ۴۲۰۰۰ سطر است. هر سطر از داده در فایل‌های Train و Test نشان دهنده یک عکس ۲۸*۲۸ سیاه و سفید که پیکسل‌های آن مقداری بین ۰ و ۲۵۵ دارند و همه‌ی پیکسل‌های عکس در یک سطر از داده پشت هم قرار گرفته‌اند. در داده Train علاوه بر پیکسل های عکس ستون Label نیز وجود دارد که شامل عددی بین ۰ تا ۹ است که نشان میدهد در این عکس چه عددی نوشته شده است.

train_df = pd.read_csv('train.csv')
test_df = pd.read_csv('test.csv')
چند سطر اول از داده Train
چند سطر اول از داده Train

برای یادگیری بهتر مدل لازم است تا تعداد تقریباً یکسانی از هر کلاس داده وجود داشته باشد؛ که در اینجا به این معنی است که از هر عدد تعداد تقریباً یکسانی نمونه در داده‌های ما وجود داشته باشد. به وسیله کد زیر نمودار تعداد نمونه‌های موجود از هر کلاس را رسم می‌کنیم:

fig = plt.figure()
plt.hist(train_df['label'],histtype='bar',rwidth=0.8)
fig.suptitle('Class distribution in Data',fontsize=15)
plt.xlabel('classes')
plt.ylabel('count')

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

نمودار تعداد نمونه‌های موجود از هر داده
نمودار تعداد نمونه‌های موجود از هر داده

گام ۲: مدل پایه

ابتدا سعی می‌کنیم تا یک مدل ساده مانند درخت تصمیم را روی داده امتحان کنیم تا ببینیم بدون استفاده از مدل‌های پیچیده به چه دقتی می‌توانیم برسیم. پس از آن سعی می‌کنیم تا به وسیله تکنیک‌هایی این مدل را بهبود دهیم.

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

features = [col for col in train_df.columns if col.startswith('pixel')]
X_train, X_val, y_train, y_val = train_test_split(train_df[features], 
                                                  train_df['label'], 
                                                  test_size=0.25)

یکی از چیزهایی که باید به آن دقت کنیم جلوگیری از بیش‌برازش (Overfitting) است. Overfitting یعنی مدل ما بیش از حد مورد نیاز به داده‌هایی که برای Train در اختیارش گذاشته شده وابسته است. به عبارتی از داده‌های آموزشی، ویژگی‌هایی برای تصمیم‌گیری دریافت کرده که در اصل مهم نبوده‌اند. مدل پایه ما یک درخت تصمیم است که حداکثر عمق آن را ۱۰ تعیین کرده‌ایم تا روی داده‌ها Overfit نشود:

clf = DecisionTreeClassifier(max_depth=10, random_state=seed)
clf.fit(X_train, y_train)

برای اندازه گیری دقت مدل از تابع زیر استفاده می‌کنیم:

def acc(y_true, y_pred):
    return round(accuracy_score(y_true, y_pred) * 100, 2)
y_predict = clf.predict(X_val)
print(acc(y_val,y_predict))
print(acc(y_train,clf.predict(X_train)))

کد بالا نشان می‌دهد مدل ساده ما روی داده Train دقت ٪۹۰.۸۶ و روی داده Validation دقت ٪۸۴.۵۸ را به ما می‌دهد. حال باید این مدل را بهبود دهیم.

گام ۳: کاهش بعد

داده ما برای مدل‌هایی مانند درخت تصمیم بعد بالایی دارد و علاوه بر احتمال زیاد Overfit روی داده احتمالاً محاسابات زیادی خواهد داشت. بنابراین ابتدا باید بعد آن را از ۷۸۴ بعد کاهش دهیم.

برای کاهش بعد می‌توانیم از دو الگوریتم PCA و TSVD استفاده کنیم. به این خاطر که الگوریتم PCA تکنیک بهتری برای کاهش بعد داده‌های چگال(Dense) و TSVD تکنیک بهتری برای کاهش بعد داده‌های تُنُک (Sparse) است و داده‌های ما نیز تنک هستند از الگوریتم TSVD استفاده می‌کنیم و بعد داده را از ۷۸۴ بعد به ۵۰ بعد کاهش می‌دهیم.

concat_df = pd.concat([train_df,test_df])
del concat_df['label']
tsvd = TruncatedSVD(n_components=50).fit_transform(concat_df)

حال داده‌های با بعد پایین در متغیر TSVD با بعد (70000,50) موجود است و دوباره درخت تصمیم را با داده‌ی جدید که بعدش کاهش یافته می‌سازیم و دقت آن را اندازه می‌‌گیریم:

X_train, X_val, y_train, y_val = train_test_split(tsvd[:42000], train_df['label'], test_size=0.25, random_state=seed)
clf = DecisionTreeClassifier(max_depth=10, random_state=seed)
clf.fit(X_train, y_train)
print(acc(y_val,y_predict))
print(acc(y_train,clf.predict(X_train)))

درخت تصمیم ساخته‌ شده با داده جدید روی Validation دقت ٪۸۴.۵۸ و روی Trainدقت ٪۸۵.۰۷ دارد. بنابراین می‌توان گفت این کاهش بُعد کمکی نکرده و دقت را ٪۵ نیز پایین آورده است. با این حال جلوتر خواهیم دید که با اضافه کردن TSNE به همین داده‌ها و با استفاده از همین مدل پایه پیشرفت محسوسی در دقت مدل ایجاد خواهد شد:

tsne = TSNE(n_components=2)
transformed = tsne.fit_transform(tsvd)

با استفاده از الگوریتم t-SNE مخلوط داده Train و Test را از ۵۰ بعد به ۲ بعد می‌بریم. t-SNE خاصیتی دارد که اگر داده ها در بعد بالا قابل دسته‌بندی باشند، پس از کاهش بعد نیز این قابلیت را تا حدودی حفظ می‌کنند. لازم به ذکر است به دلیل طبیعت نظارت‌نشده (Unsupervised) الگوریتم t-SNE با ورود مخلوط داده Train و Test به آن اشکالی ایجاد نمی‌شود.

پس از استفاده از الگوریتم TSNE روی داده‌ها دوباره دو قسمت Train و Test را از هم جدا می‌کنیم.

tsne_train = pd.DataFrame(transformed[:len(train_df)], columns=['component1', 'component2'])
tsne_test = pd.DataFrame(transformed[len(train_df):], columns=['component1', 'component2'])

در کنار فشرده‌سازی داده و کاراتر کردن الگوریتم‌ها روی داده‌های دارای بُعد بالا، از الگوریتم t-SNE برای رسم نمودارهای مختلف نیز استفاده می‌شود. در اینجا نیز ما نمودار قسمت Train داده پس از عبور از t-SNE (که جواب‌ها را برای آن داریم) می‌کشیم و می‌توان دید که کلاس‌های مختلف در فضای دو بعدی به وضوح از هم جدا‌ شده‌اند. همچنین، اعداد شبیه به هم در نمودار به هم نزدیک‌اند .برای مثال ۳ و ۹ در نمودار از ۰ و ۷ به هم نزدیک‌تر هستند.

نمودار داده‌های train (جواب ها با رنگ مشخص شده‌اند)
نمودار داده‌های train (جواب ها با رنگ مشخص شده‌اند)

از مضرات استفاده از داده‌ای که کاهش بعد داده شده در مدل‌های دسته‌بندی این است که تفسیرپذیری مدل‌هایی مانند درخت تصمیم را کاهش می‌دهد. در این حالت نمی‌دانیم کدام ویژگی تاثیر بیشتری روی جواب دارد، چون همه ويژگی‌ها در داده نهایی از ترکیبی از ویژگی‌ها در داده اصلی ساخته شده‌اند. می‌توان با فشرده‌سازی ویژگی‌های شبیه به هم یا ویژگی‌هایی که با هم ارتباط (Correlation) زیادی دارند با این مسئله مقابله کرد.

گام ۴: ساختن مدل نهایی و بررسی آن

حالا دوباره همان مدل درخت تصمیم را برای داده‌های حاصل شده از الگوریتم TSNE می‌سازیم:

X_train, X_val, y_train, y_val = train_test_split(tsne_train, 
                                                  train_df['label'], 
                                                  test_size=0.25, 
                                                  random_state=seed)
clf = DecisionTreeClassifier(max_depth=10, random_state=seed)
clf.fit(X_train, y_train)

و این مدل روی داده Validation به ما دقت ٪۹۷.۰۲ و روی داده Train به ما دقت ٪۹۸.۱۷ می‌دهد که تفاوت چندانی با دقت شبکه‌های عصبی ندارد.

حالا جواب مدل نهایی را برای داده‌های Test می‌گیریم.

predictions = clf.predict(tsne_test)

چون برای داده Test جواب‌های درست را نداریم نمی‌توانیم دقت مدل را حساب کنیم. ولی می‌توانیم حسی از درستی مدل با مقایسه کردن نمودار توزیع کلاس‌های ‌‌پیش‌بینی شده توسط مدل برای Test و نمودار توزیع کلاس‌های داده Train داشته باشیم.

توزیع کلاس‌های پیشبینی شده توسط مدل
توزیع کلاس‌های پیشبینی شده توسط مدل

می‌بینیم که نمودار به نموداری که در بالا کشیدیم شباهت دارد.

همچنین این عکس به خوبی می‌تواند نزدیک بودن بعضی عکس‌های داده MNIST به دو عدد مختلف را نشان دهد.(منبع عکس)

حاصل اعمال الگوریتم t-SNE رو داده MNIST
حاصل اعمال الگوریتم t-SNE رو داده MNIST

برای مطالعه بیشتر در مورد الگوریتم t-SNE می‌توانید به مقاله اصلی t-SNE که توسط Laurens van der Maaten و Geoffrey Hinton نوشته شده، مراجعه کنید.

? اگر به یادگیری کاربردهای هوش مصنوعی برای تحلیل داده علاقه‌مند شدید، پیشنهاد می‌کنیم سری هم به دوره جدید کوئراکالج، یعنی «دوره هوش مصنوعی و یادگیری ماشین» بزنید. این دوره که با داده‌های واقعی از شرکت پوشه و تمرین‌های بسیار سر و کار دارد، الآن در مرحله پیش‌ثبت‌نام رایگان قرار دارد!


مطلب‌های دیگر کوئرامگ در زمینه یادگیری ماشین:

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