বেশ কয়েকটি অধ্যায়ে আমরা কোন কিছুর মান প্রেডিক্ট করার চেষ্টা করেছি লিনিয়ার মডেল ব্যবহার করে, যাকে লিনিয়ার রিগ্রেশন বলা হচ্ছিল। এখন থেকে পরবর্তী বেশ কিছু অধ্যায়ে আলোচনা করা হবে লজিস্টিক রিগ্রেশন, বা ক্লাসিফিকেশন সমস্যা নিয়ে। ক্লাসিফিকশেন সমস্যা রিগ্রেশন সমস্যার থেকে একটু ভিন্ন এবং বেশ কিছু ক্ষেত্রে জটিল। শুরুর দিকে সহজ সরল মডেল নিয়ে আলোচনা করার পর শেষের অধ্যায়গুলিতে মাল্টিক্লাস রিগ্রেশন বা সফটম্যাক্স রিগ্রেশন, লিনিয়ার ডিস্ক্রিমিনেন্ট অ্যানালাইসিস এর ম্যাথমেটিক্যাল ফাউন্ডেশন নিয়ে বিস্তারিত আলোচনা করা হবে।
সাধারণত মেশিন লার্নিং সমস্যা দুই ধরণের হয়, মান প্রেডিক্ট করা অথবা ক্লাসিফাই করা। নিম্নের সমস্যাগুলো ক্লাসিফিকেশন সমস্যার অন্তর্গত।
- ইমেইল : স্প্যাম নাকি সাধারণ?
- অনলাইন ট্র্যান্স্যাকশন : ফ্রড (হ্যাঁ বা না)?
- টিউমার : বিনাইন না ম্যালিগন্যান্ট (ক্যান্সার)?
- সেন্টিমেন্ট : পজিটিভ কথা নাকি নেগেটিভ?
অর্থাৎ এতদিন আমাদের উত্তর এসেছিল একটা মান হিসেবে কিন্তু এখানে আসবে লজিকাল মান, যেমন
ধরুন, আপনি মেশিন লার্নিং প্র্যাক্টিশনার এবং আপনার বন্ধু বিশাল টাকাওয়ালা ব্যক্তি। সে একটি ওয়েবসাইট বানাতে চায় যেখানে বিভিন্ন রেস্ট্যুরেন্টের রিভিউ ও রেটিং থাকবে। প্রতিটা রিভিউ পড়ে যাতে একজন ভিসিটরের গণণা করা না লাগে যে কয়টি নেগেটিভ ও পজিটিভ রিভিউ আছে এবং একটা রেস্ট্যুরেন্ট সম্পর্কে সহজেই ওভারঅল ধারণা পেয়ে যায় তাই আপনার বন্ধু আপনার কাছে আব্দার করল একটি সিস্টেম বিল্ড করে দিতে যেটা কিনা অটোমেটিক রিভিউ গুলো থেকে পজিটিভ ও নেগেটিভ রিভিউ আলাদা করবে এবং একটি কাউন্টারে শো করবে।
কয়েকটা উদাহরণ দেখা যাক,
রিভিউগুলো স্টার কাবাবের এবং সংগ্রহ করা হয়েছে ট্রিপ অ্যাডভাইজর ওয়েবসাইট থেকে
আপনার কাজ হবে এমন একটি মডেল তৈরি করা যে এই রিভিউ গুলোকে পজিটিভ ও নেগেটিভ হিসেবে ক্লাসিফাই করতে পারে।
[অবশ্যই রেটিং থেকে আলাদা করা যায় কিন্তু আমরা এখানে রিভিউ থেকে ক্লাসিফাই করার চেষ্টা করব]
এই সমস্যা কীভাবে সল্ভ করা যায়? আমরা একটা কাজ করতে পারি, একটা Weight
টেবিল তৈরি করে তাতে ভাল ভাল শব্দ যেমন ভাল
, অসাধারণ
, অনন্যসাধারণ
শব্দগুলোকে তাদের পজিটিভিটি অনুযায়ী পজিটিভ মান দিয়ে দিতে পারি এবং, খারাপ
, বাজে
, না
ইত্যাদি শব্দগুলোকে নেগেটিভ মান দিতে পারি।
এভাবে একটি টেবিল তৈরি করা যায়।
শব্দ | Weight |
---|---|
ভাল | |
অসাধারণ | |
অনন্যসাধারণ | |
খারাপ | |
বাজে | |
জঘন্য | |
না | |
এটা, ওটা, আমি, কখন, কোথায় .... |
এবার আমরা যদি বাক্যের এই Weight
এর ভিত্তিতে উপর্যুক্ত বাক্যটির সেন্টিমেন্ট ক্লাসিফাই করতে চাই তাহলে সেটা কীভাবে করব?
সহজ, একটা ডিকশনারিতে আমরা শব্দকে Key
এবং তার Weight
কে ভ্যালু হিসেবে রাখব এবং একটা লুপ চালিয়ে প্রতিটা শব্দের বিপরীতে যে Weight
আছে সেগুলো যোগ করতে থাকব। যেটা হবে ঐ বাক্যের স্কোর Score
।
কাজটা করার জন্য একটা ছোট পাইথন স্ক্রিপ্ট লেখা যাক,
# Simple Linear Classifier
words = [u'ভাল', u'অসাধারণ', u'অনন্যসাধারণ', u'খারাপ', u'বাজে', u'জঘন্য', u'না', u'কিন্তু']
weights = [1.0, 2.0, 2.5, -1.0, -1.1, -2.0, -1.5, 0.0]
# Building vocabulary given the list of words and weights
vocabulary = {word : weight for word, weight in zip(words, weights)}
sentence = u'খাবার ভাল ছিল কিন্তু সার্ভিস না'
# If a word is not present in the vocabulary we simply ignore it
score = 0.0
for word in sentence.split(' '):
if word in words:
score = score + vocabulary[word]
else:
pass
print("Score: {}".format(score))
if (score > 0) : print("Sentence is positive")
else: print("Sentence is negative")
এটার আউটপুট আসলো,
Score: -0.5
Sentence is negative
এখানে আমি যেটা করলাম সেটা হল, স্কোর যদি সামগ্রিকভাবে পজিটিভ হয় তাহলে আমি ধরে নিচ্ছি বাক্যটি পজিটিভ সেন্টিমেন্টের মধ্যে পড়ে। যদি স্কোর ঋণাত্নক হয় তাহলে বুঝব বাক্যটি নেগেটিভ! এইযে আমি Score > 0
হলে একটা ডিসিশন এবং Score < 0
হলে আরেকটি ডিসিশন নিচ্ছি বা বাউন্ডারি সেট করছি এটার আরেকনাম (স্পয়লার অ্যালার্ট) ডিসিশন বাউন্ডারি। একটা ডিসিশন কখন রিজেক্ট হবে, রিজেকশন রিজিওন কোনটা, কেন, কীভাবে সেটা নিয়ে পরে আলোচনা করা হবে।
এই পদ্ধতিতে বেশ কিছু সমস্যা আছে, প্রথমত; বাংলাভাষার শব্দভাণ্ডারে প্রচুর শব্দ আছে যেগুলো কিনা পজিটিভ, নেগেটিভ বা নিউট্রাল। এতশত শব্দ বের করা, তাদের তীব্রতা হিসেবে Weight
অ্যাসাইন করাও খুব জটিল, দীর্ঘমেয়াদী ও একঘেঁয়ে কাজ। শুধু তাই নয়, আরেকটি বিশাল সমস্যা আছে যেটা একটু পরেই দেখানো হবে।
আমরা যে সেন্টেন্স ক্লাসিফায়ার তৈরি করলাম, যদিও এটা বেশ সাধারণ এবং ব্যবহার উপযোগী নয় তাও এর একটা সুন্দর জ্যামিতিক ইন্টারপ্রিটেশন আছে যেটা বোঝা খুবই দরকারী। এটা বোঝার জন্য আমরা চলে যাব আমাদের HSC এর জ্যামিতি বইয়ের একটি অধ্যায়ে, যার নাম 'সরলরেখা'। সরলরেখা অধ্যায়ে বেশ কিছ জিনিস পড়ানো হয় কিন্তু বিশাল আফসোসের বিষয় হচ্ছে এই টপিক গুলো কোথায় অ্যাপ্লাই করা হয়, আদৌ দরকারী কিনা সেটা বলা হয় না। তেমনি একটি উপ-অধ্যায় হল একটি বিন্দু সরলরেখার কোন দিকে অবস্থান করছে। যদি না বোঝা যায় তাহলে নিচের চিত্র থেকে ব্যাখ্যা করা যাক।
একটা সরলরেখার সমীকরণ চিন্তা করা যাক,
তাহলে আমি যদি
m = 2
x = [1, 2, 3, 4]
c = 3
y = [(m*i + c) for i in x ]
print(y)
# Output
## [5, 7, 9, 11]
একে প্লট করা হলে,
এবার এই গ্রাফে দুইটা বিন্দু প্লট করি, যাদের কোঅর্ডিনেট যথাক্রমে, $$ (x_{1}, y_{1}) = (1, 6)$$ এবং
plt.plot(y, linewidth="5", color='g')
plt.scatter(1, 6, color='r', s=100)
plt.scatter(2, 10, color='b', s=100)
plt.title('Plot of ' + r'$y=mx+c$')
দৃশ্যত, লাল বিন্দুটি সরলরেখাটির নিচে এবং নীল বিন্দুটি সরলরেখার উপরে। কিন্তু গাণিতিকভাবে কীভাবে আপনি সম্পর্ক স্থাপন করতে পারবেন?
এইচএসসি এর জ্যামিতি না মনে থাকলেও সমস্যা নাই। নিচের সূত্রটা দিয়ে সহজেই বের করা যাবে,
$$
y - mx -c > 0 \
y - mx - c < 0
$$
এই থিওরি যে শুধু সরলরেখার ক্ষেত্রে সত্য তাই নয়, ভেক্টর স্পেস
কার্নেল বেজড মেথডগুলোতে এই থিওরি অহরহ ব্যবহার করা হয়। (পাশাপাশি আরও অনেক থিওরি) যেটা নিয়ে আবারও পরে একসময় আলোচনা করা হবে।
অনেক কথাবার্তা হল, এবার আসল পয়েন্টে আসা যাক। আমরা কি লিনিয়ার রিগ্রেশন ব্যবহার করে ক্লাসিফাই করতে পারি না? কেন, ওখানেও তো আমরা এমন একটা সমীকরণ বের করি $$ y = \theta_{0} + \theta_{1}x$$ , তাহলে আমরা Mean Square Error ক্যালকুলেট করে সহজেই তো প্যারামিটার আপডেট করে এটা দিয়েই ক্লাসিফাই করতে পারি! তবে কেন লজিস্টিক রিগ্রেশন?
(স্পয়লার অ্যালার্ট) : না!
আরেকটি হাইপোথেটিক্যাল ডেটাসেট এর কথা চিন্তা করা যাক। টিউমারের আকারের ভিত্তিতে সেটা ম্যালিগন্যান্ট (খুবই ক্ষতিকর) কিনা সেটার ডেটাসেট। এই কোডে আমি ডেটাসেট তৈরি করে লাইব্রেরি ব্যবহার করে লিনিয়ার রিগ্রেশন চালাব। যেহেতু আমরা বেসিক জানি তাই লাইব্রেরি ব্যবহার করতে সমস্যা নেই।
%matplotlib inline
import numpy as np
import pandas as pd
import matplotlib as mpl
import matplotlib.pyplot as plt
import seaborn as sns
sns.set(color_codes=True)
X_class_1 = np.arange(0, 50, 5)
X_class_2 = np.arange(70, 119, 5)
X_outlier = np.array([150])
X = np.concatenate((X_class_1, X_class_2, X_outlier))
# Create Y data set
Y_class_1 = np.array([.5] * len(X_class_1))
Y_class_2 = np.array([50.0] * (len(X_class_2) + 1))
Y = np.concatenate((Y_class_1, Y_class_2))
synthetic_data = {
"tumor_size" : pd.Series(X),
"malignant" : pd.Series(Y)
}
df = pd.DataFrame(synthetic_data)
sns.regplot(x="tumor_size", y="malignant", data=df, scatter_kws={"s": 80})
কোডে প্রথমে ক্লাস-১ এর জন্য আমি কিছু ফিচার জেনারেট করলাম outlier
। outlier
বলার কারণ হল, এর অবস্থান আমি বেশ একটু দুরেই দিলাম। এবং এর কারণে লিনিয়ার রিগ্রেশন চালালে আউটপুট আসবে এরকম!
সমস্যাটা বোঝা যাচ্ছে? এই রিগ্রেশন লাইনে হয়ত লস মিনিমাম কিন্তু মিসক্লাসিফিকেশন রেট অনেক অনেক বেশি। আর এটা হওয়ার আরেকটা কারণ outlier
। ঐ ডেটার কারণে লাইনটা এমনভাবে ফিট করেছে যে একই ক্লাসের মধ্যে সে ভাগ করে ফেলছে!
এই সমস্যার সমাধান করা খুব সহজ! আউটপুটকে কোন একটা মান না দিয়ে আমরা যদি তার রেঞ্জ
এইরকম অনেক ফাংশন আছে, তবে সাধারণত এই ফাংশনটা ব্যবহার করা হয়, যার নাম হল logit
ফাংশন বা sigmoid
ফাংশন, এর ডোমেইন
import numpy as np
def sigmoid(z):
return 1.00 / ( 1 + np.exp(-z))
test_sig = np.arange(-10, 10, .1)
sig_out = sigmoid(test_sig)
plt.plot(test_sig, sig_out)
plt.show()
তাহলে আউটপুট এটা করে লিনিয়ার রিগ্রেশন চালালেই তো এবার হবে তাই না? আবারও স্পয়লার অ্যালার্ট! না! আমাদের আরও একটু ক্রিয়েটিভ হতে হবে লজিস্টিক রিগ্রেশন কাজ করানোর জন্য। পাশাপাশি প্রব্যাবিলিটি, স্ট্যাটিসটিক্স ও ইনফর্মেশন থিওরির বিল্ডিং ব্লকগুলো সম্পর্কে ধারণা রাখতে হবে। যা নিয়ে বিস্তারিত পরবর্তী অধ্যায়ে আলোচনা করা হবে।