Skip to content
This repository has been archived by the owner on Jun 8, 2020. It is now read-only.

Latest commit

 

History

History
240 lines (162 loc) · 21.2 KB

logistic_regression_intuition.md

File metadata and controls

240 lines (162 loc) · 21.2 KB

লজিস্টিক রিগ্রেশন : পরিচিতি

বেশ কয়েকটি অধ্যায়ে আমরা কোন কিছুর মান প্রেডিক্ট করার চেষ্টা করেছি লিনিয়ার মডেল ব্যবহার করে, যাকে লিনিয়ার রিগ্রেশন বলা হচ্ছিল। এখন থেকে পরবর্তী বেশ কিছু অধ্যায়ে আলোচনা করা হবে লজিস্টিক রিগ্রেশন, বা ক্লাসিফিকেশন সমস্যা নিয়ে। ক্লাসিফিকশেন সমস্যা রিগ্রেশন সমস্যার থেকে একটু ভিন্ন এবং বেশ কিছু ক্ষেত্রে জটিল। শুরুর দিকে সহজ সরল মডেল নিয়ে আলোচনা করার পর শেষের অধ্যায়গুলিতে মাল্টিক্লাস রিগ্রেশন বা সফটম্যাক্স রিগ্রেশন, লিনিয়ার ডিস্ক্রিমিনেন্ট অ্যানালাইসিস এর ম্যাথমেটিক্যাল ফাউন্ডেশন নিয়ে বিস্তারিত আলোচনা করা হবে।


সাধারণত মেশিন লার্নিং সমস্যা দুই ধরণের হয়, মান প্রেডিক্ট করা অথবা ক্লাসিফাই করা। নিম্নের সমস্যাগুলো ক্লাসিফিকেশন সমস্যার অন্তর্গত।

ক্লাসিফিকেশন সমস্যা

  • ইমেইল : স্প্যাম নাকি সাধারণ?
  • অনলাইন ট্র্যান্স্যাকশন : ফ্রড (হ্যাঁ বা না)?
  • টিউমার : বিনাইন না ম্যালিগন্যান্ট (ক্যান্সার)?
  • সেন্টিমেন্ট : পজিটিভ কথা নাকি নেগেটিভ?

অর্থাৎ এতদিন আমাদের উত্তর এসেছিল একটা মান হিসেবে কিন্তু এখানে আসবে লজিকাল মান, যেমন $$1 , , 0$$

ইন্টেলিজেন্ট রেস্তোরাঁ রিভিউ সিস্টেম

ধরুন, আপনি মেশিন লার্নিং প্র্যাক্টিশনার এবং আপনার বন্ধু বিশাল টাকাওয়ালা ব্যক্তি। সে একটি ওয়েবসাইট বানাতে চায় যেখানে বিভিন্ন রেস্ট্যুরেন্টের রিভিউ ও রেটিং থাকবে। প্রতিটা রিভিউ পড়ে যাতে একজন ভিসিটরের গণণা করা না লাগে যে কয়টি নেগেটিভ ও পজিটিভ রিভিউ আছে এবং একটা রেস্ট্যুরেন্ট সম্পর্কে সহজেই ওভারঅল ধারণা পেয়ে যায় তাই আপনার বন্ধু আপনার কাছে আব্দার করল একটি সিস্টেম বিল্ড করে দিতে যেটা কিনা অটোমেটিক রিভিউ গুলো থেকে পজিটিভ ও নেগেটিভ রিভিউ আলাদা করবে এবং একটি কাউন্টারে শো করবে।


কয়েকটা উদাহরণ দেখা যাক,

pos_review_1

pos_review_2

neg_review_1

রিভিউগুলো স্টার কাবাবের এবং সংগ্রহ করা হয়েছে ট্রিপ অ্যাডভাইজর ওয়েবসাইট থেকে

আপনার কাজ হবে এমন একটি মডেল তৈরি করা যে এই রিভিউ গুলোকে পজিটিভ ও নেগেটিভ হিসেবে ক্লাসিফাই করতে পারে।

[অবশ্যই রেটিং থেকে আলাদা করা যায় কিন্তু আমরা এখানে রিভিউ থেকে ক্লাসিফাই করার চেষ্টা করব]

সিস্টেম ওভারভিউ

system_overview


লিনিয়ার ক্লাসিফায়ার : ইনটুইশন

এই সমস্যা কীভাবে সল্ভ করা যায়? আমরা একটা কাজ করতে পারি, একটা Weight টেবিল তৈরি করে তাতে ভাল ভাল শব্দ যেমন ভাল‍ , ‍অসাধারণ, অনন্যসাধারণ শব্দগুলোকে তাদের পজিটিভিটি অনুযায়ী পজিটিভ মান দিয়ে দিতে পারি এবং, খারাপ , বাজে , না ইত্যাদি শব্দগুলোকে নেগেটিভ মান দিতে পারি।

এভাবে একটি টেবিল তৈরি করা যায়।

শব্দ Weight
ভাল $$1.0$$
অসাধারণ $$2.0$$
অনন্যসাধারণ $$2.5$$
খারাপ $$-1.0$$
বাজে $$-1.1$$
জঘন্য $$-2.0$$
না $$-1.5$$
এটা, ওটা, আমি, কখন, কোথায় .... $$0.0$$

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 এর জ্যামিতি বইয়ের একটি অধ্যায়ে, যার নাম 'সরলরেখা'। সরলরেখা অধ্যায়ে বেশ কিছ জিনিস পড়ানো হয় কিন্তু বিশাল আফসোসের বিষয় হচ্ছে এই টপিক গুলো কোথায় অ্যাপ্লাই করা হয়, আদৌ দরকারী কিনা সেটা বলা হয় না। তেমনি একটি উপ-অধ্যায় হল একটি বিন্দু সরলরেখার কোন দিকে অবস্থান করছে। যদি না বোঝা যায় তাহলে নিচের চিত্র থেকে ব্যাখ্যা করা যাক।

একটা সরলরেখার সমীকরণ চিন্তা করা যাক, $$y = m \times x + c $$ । আরও মনে করি, $$ m = 2, c = 3$$

তাহলে আমি যদি $$x$$ এর চারটি মানের কথা চিন্তা করি, $$x = {1, 2, 3, 4 }$$ তাহলে $$y$$ এর মানগুলি কত হবে?

m = 2
x = [1, 2, 3, 4]
c = 3
y = [(m*i + c) for i in x ]

print(y)

# Output
## [5, 7, 9, 11]

একে প্লট করা হলে,

y=mx+c

এবার এই গ্রাফে দুইটা বিন্দু প্লট করি, যাদের কোঅর্ডিনেট যথাক্রমে, $$ (x_{1}, y_{1}) = (1, 6)$$ এবং $$(x_{2}, y_{2}) = (2, 10)$$

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 with points

দৃশ্যত, লাল বিন্দুটি সরলরেখাটির নিচে এবং নীল বিন্দুটি সরলরেখার উপরে। কিন্তু গাণিতিকভাবে কীভাবে আপনি সম্পর্ক স্থাপন করতে পারবেন?

এইচএসসি এর জ্যামিতি না মনে থাকলেও সমস্যা নাই। নিচের সূত্রটা দিয়ে সহজেই বের করা যাবে, $$ y - mx -c > 0 \ y - mx - c < 0 $$ $$(x_{1}, y_{1})$$ এর জন্য ক্যালকুলেট করা যাক, $$ \begin{align} y_{1} - mx_{1} - c &= 6 - 2 \times 1 - 3 \ &= -2 < 0 \end{align} $$ এবং $$(x_{2}, y_{2})$$ এর জন্য ক্যালকুলেট করলে, $$ \begin{align} y_{2} - mx_{2} - c &= 10 - 2 \times 2 - 3 \ &= 3 > 0 \end{align} $$ সংক্ষেপে আমার যদি ইনপুট $$(x)$$ এবং আউটপুট $$(y)$$ জানা থাকে তাহলে আমরা এমন একটি সরলরেখার সমীকরণ বের করতে পারব (যদি ডেটা একটা সরলরেখা দিয়ে আলাদা করা যায়) যেটা কিনা ঐ ডেটাসেট ক্লাসিফাই করতে পারে। $$x, y$$ দেয়া থাকলে কাজ হল $$m, c$$ এর মান বের করা।

এই থিওরি যে শুধু সরলরেখার ক্ষেত্রে সত্য তাই নয়, ভেক্টর স্পেস $$n$$ ডিমেনশনাল হলে তাকে $$n-1$$ ডিমেনশনাল হাইপারপ্লেন দিয়ে আলাদা করা যাবে। (সবসময় না, ডেটাসেটের উপর নির্ভর করে) যেমন $$3D$$ এর ক্ষেত্রে ডেটাসেটের $$(x_{1}, x_{2}, y)$$ এই তিনটি কোঅর্ডিনেট থাকবে যেখানে দুইটি হল ফিচার ভেক্টর আরেকটি হল আউটপুট এবং একে সেপারেট করার জন্য $$3-1$$ বা $$2$$ ডিমেনশন বিশিষ্ট লাইন লাগবে যাকে কিনা বলা হয় Plane

কার্নেল বেজড মেথডগুলোতে এই থিওরি অহরহ ব্যবহার করা হয়। (পাশাপাশি আরও অনেক থিওরি) যেটা নিয়ে আবারও পরে একসময় আলোচনা করা হবে।

অনেক কথাবার্তা হল, এবার আসল পয়েন্টে আসা যাক। আমরা কি লিনিয়ার রিগ্রেশন ব্যবহার করে ক্লাসিফাই করতে পারি না? কেন, ওখানেও তো আমরা এমন একটা সমীকরণ বের করি $$ 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})

কোডে প্রথমে ক্লাস-১ এর জন্য আমি কিছু ফিচার জেনারেট করলাম $$np.arange$$ দিয়ে, এবং ক্লাস-২ এর জন্যও তাই। এবং আমি আরেকটা ডেটা তৈরি করলাম যেটার নাম দিলাম outlieroutlier বলার কারণ হল, এর অবস্থান আমি বেশ একটু দুরেই দিলাম। এবং এর কারণে লিনিয়ার রিগ্রেশন চালালে আউটপুট আসবে এরকম!

trying_lin_reg

সমস্যাটা বোঝা যাচ্ছে? এই রিগ্রেশন লাইনে হয়ত লস মিনিমাম কিন্তু মিসক্লাসিফিকেশন রেট অনেক অনেক বেশি। আর এটা হওয়ার আরেকটা কারণ outlier । ঐ ডেটার কারণে লাইনটা এমনভাবে ফিট করেছে যে একই ক্লাসের মধ্যে সে ভাগ করে ফেলছে!

প্রতিকার?

এই সমস্যার সমাধান করা খুব সহজ! আউটপুটকে কোন একটা মান না দিয়ে আমরা যদি তার রেঞ্জ $$[0, 1]$$ এর মধ্যে নিয়ে আসি? তাহলে নিশ্চয়ই সমস্যা হবে না? এমন একটা ফাংশন তৈরি করতে হবে যেটা নেগেটিভ মানকে শূন্যের কাছাকাছি নিয়ে আসে এবং পজিটিভ মানকে ১ এর কাছাকাছি নিয়ে যায়। তাহলে $$0$$ আউটপুট আসলে আমরা বলতে পারব ঐ ডেটা এক ক্লাসের অন্তর্গত এবং $$1$$ আউটপুট আসলে বলতে পারব সেটা আরেক ক্লাসের।

এইরকম অনেক ফাংশন আছে, তবে সাধারণত এই ফাংশনটা ব্যবহার করা হয়, যার নাম হল logit ফাংশন বা sigmoid ফাংশন, এর ডোমেইন $$(-\infty, \infty)$$ এবং রেঞ্জ $$(0, 1)$$ । $$ \sigma(x) = \frac{1}{1 + e^{-x}} $$ এর প্রোপার্টি প্লট করেও দেখা যায়,

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()

আউটপুট

sigmoid

তাহলে আউটপুট এটা করে লিনিয়ার রিগ্রেশন চালালেই তো এবার হবে তাই না? আবারও স্পয়লার অ্যালার্ট! না! আমাদের আরও একটু ক্রিয়েটিভ হতে হবে লজিস্টিক রিগ্রেশন কাজ করানোর জন্য। পাশাপাশি প্রব্যাবিলিটি, স্ট্যাটিসটিক্স ও ইনফর্মেশন থিওরির বিল্ডিং ব্লকগুলো সম্পর্কে ধারণা রাখতে হবে। যা নিয়ে বিস্তারিত পরবর্তী অধ্যায়ে আলোচনা করা হবে।