Logistic Regression ทำนายผู้รอดชีวิตบนเรือไททานิค : Machine Learning 101
อะไรที่ว่าใช่ ถ้าลองมองดีๆมันอาจไม่ใช่ก็ได้ ….
หลังจากที่ลองเล่น Machine Learning แบบ Regression มาจนเข้าใจแล้ว ทีนี้ก็กลับมาที่ Classification อีกรอบ แต่รอบนี้จะแปลกหน่อยเพราะชื่อ ML ตัวนี้คือ Logistic Regression แต่ทำงานเป็น Classification ….. เหมือนเจอดัก 5555
แต่มันมีเหตุผลอยู่ว่าทำไมถึงเป็น Regression ที่ได้ผลลัพธ์เป็น Classification โดยเจ้า LR เนี่ยมีคณิตศาสตร์เบื้องหลัง+output ที่แปลกที่สุดในบันดาล ML เลยก็ว่าได้ เพราะมันมี Sigmoid function และ Classification ที่สามารถทำนายได้แค่ 2 Class เท่านั้น ….
Logistic Regression
แปลไทยแบบตลกๆคือ การขนส่งแบบถดถอย… เออแปลกดีแต่ความจริงแล้วมันคือการทำ Classification โดย output จริงๆแล้วออกมาเป็น Regression (จำนวนตัวเลข) จากนั้นนำไป mapping กับ Sigmoid function (หลักคณิตศาสตร์) ก็จะได้ว่าข้อมูลอันนี้เป็นคลาสนี้ หรือ ไม่ได้เป็นคลาสนี้นั้นเอง (เป็นไปได้แค่ 2 คลาสเท่านั้น)
Regression ตรงไหน ?
Output ของ Logistic Regression ความจริงแล้วเป็นจำนวนตัวเลขนะครับแต่เมื่อนำมาใช้งานร่วมกับ Sigmoid หน้าตาแบบนี้
ทีนี้สมมุติ Output จริงๆของเราทายออกมาได้ 0
แกน Y คือ ความน่าจะเป็นที่จะเป็น Class นั้นๆ อยู่ในช่วง 0–1
แกน X คือ ค่า Regression ที่ทายออกมาได้
เราเอา Output มาเทียบกับกราฟในรูปก็จะได้แกน Y เป็น 0.5
ถ้าความน่าจะเป็นอยู่ในช่วง 0.0–0.49 ก็จะได้ว่าไม่เป็นคลาสนั้นๆ
ถ้าความน่าจะเป็นอยู่ในช่วง 0.49–1.00 ก็จะได้ว่าเป็นคลาสนั้นๆ
โดยที่ กฏของความน่าจะเป็นค่าจะต้องไม่เกิน 1 โดยเจ้า Logistic Regression สามารถให้ output ออกมาได้ว่าเป็นคลาสนี้ หรือ ไม่เป็นคลาสนี้โดยการใช้ Sigmoid function และมันยังสามารถให้ output ออกเป็น “ความน่าจะเป็น” ของคลาสนั้นๆได้อีกด้วย
ทำไมถึงทำนายได้แค่ 2 คลาส ?
จากรูปจะเห็นได้ว่าแกน y คือ ความน่าจะเป็นของคลาสนั้น โดยจุดกลางของกราฟคือ 0.5 ทำให้แบ่งได้ 2 คลาสคือ เป็นคลาสนั้น และไม่เป็นคลาสนั้น
มีวิธีทำนายได้หลายๆคลาสไหม ?
ทำได้ครับ โดยใช้ Sigmoid Function จำนวน X อัน โดยที่ X คือจำนวนคลาสที่ต้องการมาบอกว่าเป็นคลาสนั้นๆหรือไม่ แต่วิธีนี้เราจะได้เห็นชัดๆใน Neural Network
**Multi layer Percepton : ใช้ Sigmoid สำหรับทำ Multi-Class Classification
คณิตศาสตร์เบื้องหลัง
มาถึงตรงนี้ทุกคนคงจะเข้าใจหน้าที่ของ Sigmoid กันแล้วหน้าตาของมันก็ประมาณนี้
คือการนำ output ที่เป็นตัวเลขมาแมพกับ Sigmoid ฟังก์ชั่นนั้นเอง
ลงมือทดลอง
วันนี้จะใช้ Titanic Dataset โดยจะทำนายว่าผู้โดยสารจะมีโอกาสรอดจากเหตุการณ์เรือ Titanic ล่มมากขนาดไหน
โหลด dataset.csv ให้เรียบร้อยก่อน
ทีนี้เปิด Jupyter notebook / VSCode แล้วลองสำรวจไฟล์ dataset กันก่อน
import pandas as pd
f=pd.read_csv(‘titanic_data.csv’)
f.shape
891 คือจำนวนข้อมูลที่มี ส่วน 12 คือหัวข้อที่มี
แต่ในชีวิตการทำงานกับ data จริงๆเราจะต้องทำการตรวจสอบข้อมูลก่อน
ถ้าเราลองสังเกตุข้อมูลใน data ของเราจะพบว่ามันไม่ได้ prefect เหมือนกับ data ที่มีใน sklern เลยแม้แต่น้อย ถ้าเราลองสังเกตุค่า “ว่าง” (NULL)
f.isnull().sum()
ทีนี้เราจะทำยังไงกับค่าว่าง ?
ถ้าวิธีที่ง่ายที่สุดคือการลบทิ้ง แต่เราต้องคำนึงก่อนว่าถ้าลบแล้วข้อมูลมันหายขนาดไหน ? ตอนนี้เรามีข้อมูล 891 อัน ถ้าลบค่าว่างออก จะเหลือแค่ 714 อัน ไม่แย่มาก แต่เรามีวิธีแก้ที่ดีกว่านี้
นั้นคือการนำค่ามาแทนที่นั้นเองโดยการใช้คำสั่ง imputer อ่านเพิ่มเติมได้จากบทความด้านล่างเลย
ขั้นแรกให้แปลงข้อมูลอายุของเราให้เป็น array ก่อน
import numpy as np
age = f[‘Age’].values
age = np.reshape(age,(-1,1))
ทีนี้ใช้ฟังก์ชั่น SimpleImputer เพื่อนำค่าอื่นมาแทนที่ “NaN” โดยค่าที่เราจะให้มาแทนคือค่าที่ปรากฏบ่อยที่สุด
from sklearn.impute import SimpleImputer
imp = SimpleImputer(missing_values = np.nan , strategy=’most_frequent’)
imp.fit(age)
ทีนี้เราก็แปลงค่าว่างในคอลัมน์อายุของเราเป็นค่าอื่นได้เลย แล้วลองดูว่ายังมีค่าว่างอีกไหม ?
f[‘Age’] = imp.transform(age)
f[f[‘Age’].isnull()]
การเลือก Feature
ทำการเลือกเฉพาะข้อมูลที่น่าจะมีปัจจัย ตามหลักแล้วขั้นตอนนี้ข้อมูลที่เลือกจะต้องสอดคล้องกับเฉลยด้วย โดยผมจะเลือกจากหนัง Titanic ที่ฉายไปนั้นคือ คนที่รวยส่วนมากจะรอด คนที่จนก็รอดได้แต่ยากมาก และอีกวิธีที่ผมแนะนำคือใช้ lib ที่ชื่อว่า seaborn ด้วยคำสั่ง pairplot จะเป็นการบอกความสัมพันธ์ทั้งหมดของข้อมูลของเรานั้นเอง
แต่มันก็มีวิธีเลือกอีกแบบที่ง่ายๆคือการดูค่าความสัมพันธ์กัน Correlated โดยให้ดูแนวเดียวกัน ถ้าค่ายิ่งใกล้ 1 แสดงว่าค่านั้นสอดคล้องกัน
แต่เราจะเลือกวิธีบ้านๆด้วยโค้ดบ้านๆกัน ด้วยการเลือก Class ของคนที่ขึ้นเรือ (คลาส1คือหรูสุด)(Pclass) และราคาตั๋ว(Fare) และรอดชีวิตหรือไม่(Survived) โดยที่ X คือ Plcass,Fare และ Y คือเฉลย(Survived)
X = f[['Pclass','Fare']]
X.head()
y = f[‘Survived’]
y.head()
ทีนี้ให้ทำการแบ่งแยกข้อมูลชุดฝึกเพื่อไม่ให้เกิด over/under fitting
โดยแบ่งข้อมูลจาก 100% เป็น ฝึก 70% ทดสอบ 30%
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)
จากนั้นสร้างโมเดล Logistic Regression ด้วย sklearn โดยโมเดลนี้เป็นโมเดลแบบเบสิคก่อน เพื่อความง่ายในการทำครั้งแรก
from sklearn.linear_model import LogisticRegression
lr = LogisticRegression()
lr.fit(X_train,y_train)
ทีนี้ลองทดสอบโมเดลของเราด้วยชุดข้อมูล test วัดความถูกต้องด้วย accuracy
y_pred = lr.predict(X_test)
from sklearn.metrics import accuracy_score
accuracy_score(y_test, y_pred)
ผลออกมา 69% ซึ่งถือว่าน้อยมาก แต่มันมีวิธีแก้ซึ่งถ้าใครได้ดูรูปใน seaborn จะเห็นว่าเพศก็มีส่วนในการรอดด้วย แต่เอาไว้ก่อน ลองเล่นแบบนี้ดูก่อน
ลองทดสอบ ถ้าเกิดเราคือแจ็คในเรื่อง Titanic เป็นผู้โดยสารชั้นที่ 3 และได้ตั๋วมาฟรีๆ โอกาสรอดของเราคือ …. 0.24 โอเค เหมือนในหนังเป๊ะ พี่แจ็คไม่รอด….
ทีนี้ลองดูค่าเฉลี่ยของ dataset ชุดนี้ก่อน
f.describe()
ทีนี้ลองไปทดสอบโมเดลกับค่า mean คือชั้น 2 และค่าตั๋วประมาณ 32 เหรียญนิดๆ ซึ่งถ้าเป็นไปตามหลักสถิติถ้าเราใช้ค่ากลางโอกาศรอดของเราจะต้องใกล้เคียงกับ 0.5 ทีนี้ทดสอบจริงๆค่าที่ได้คือ 0.412 ยังพอรับได้อยู่
ทีนี้ลองเป็นคนรวยดูสิ อยากรู้ว่าจะรอดจากเรือล่มได้หรือไม่
ลองปรับค่าไปที่คนรวยๆคือผู้โดยสารชั้น 1 กับค่าตั๋ว 75 เหรียญ โอกาสรอดคือ 0.62 ถ้าเรายอมจ่ายค่าตั๋วจาก 32 -> 75 เหรียญ โอกาสรอดก็จะเพิ่มขึ้นประมาณ 20%
แต่ลองรวยมหาศาลแบบในตัวร้ายเรื่อง Titanic ดูสิ ถ้าจ่ายค่าตั๋วไปถึง 300 เหรียญโอกาสรอดคือ 0.86 (ในเรื่องตอนเรือจะล่ม ตัวร้ายเอาเงินให้พนักงานเพื่อให้รอด คนรวยจึงมีโอกาสรอดมากกว่า)
แต่ถ้าเราอยากให้แม่นยำขึ้นละ ? ก็เพิ่มปัจจัยเข้าไปอีกแต่ต้องสัมพันธ์กัน ที่เห็นได้ชัดเจนเลยในหนังคือ “ผู้หญิงขึ้นเรือก่อน” ทำให้รู้ว่าเพศก็มีผล เลยทำการเพิ่มเพศเข้าไปในชุดฝึกด้วย แต่ sklearn ไม่สามารถนำตัวอักษรเข้าไปฝึกได้ เลยใช้ concat ใน pandas ช่วยแปลงตัวอักษรเป็นตัวเลขนั้นเอง [คล้ายๆ one-hot]
อ่านเพิ่มเติมได้ที่บทความนี้
เมื่อเราเพิ่มข้อมูลเพศเข้ามาแล้ว ให้ทำการแยกข้อมูลใหม่อีกรอบ
X = f[[‘Pclass’,’Fare’,’Sex_female’,’Sex_male’]]
y = f[‘Survived’]
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)
ทีนี้ลองฝึกฝนโมเดลใหม่ โดยใช้โมเดลเดิมแค่เปลี่ยนข้อมูลโดยการเพิ่มเพศเข้ามา
lr.fit(X_train,y_train)y_pred = lr.predict(X_test)
from sklearn.metrics import accuracy_score
accuracy_score(y_test, y_pred)
ความแม่นยำของเราก็เพิ่มขึ้นเป็น 0.787 มากกว่าเดิมเกือบ 0.1
เช็คเมทริกซ์แห่งความสับสนหน่อย ว่าเพราะอะไรทำไมถึงไม่ 100%
conf = sklm.confusion_matrix(y_test, y_pred)
print(' Confusion matrix')
print(' Survie Die ')
print('Actual Survie %6d' % conf[0,0] + ' %5d' % conf[0,1] )
print('Actual Die %6d' % conf[1,0] + ' %5d' % conf[1,1] )
จะได้ว่าจำนวนคนที่ไม่รอดมีน้อยกว่าคนรอด แถมยังทายผิดไปเยอะอีกเลยโดนหักคะแนนไปเยอะพอตัว
ทีนี้ได้เวลาเพิ่มประสิทธิภาพของโมเดลแล้วด้วยการทำ Scale
สำหรับคำอธิบายส่วนนี้ผมเคยเขียนไว้แล้วที่
โดยรอบนี้เราจะใช้อายุเข้ามาเกี่ยวด้วย
X = f[[‘Pclass’,’Fare’,’Sex_female’,’Sex_male’,’Age’]]
y = f[‘Survived’]from sklearn.preprocessing import StandardScaler
scale = StandardScaler()
X = scale.fit_transform(X)
แบ่งข้อมูลอีกครั้งและทำการทดสอบด้วยโมเดลเดิมแค่เปลี่ยนข้อมูล
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)lr.fit(X_train,y_train)
y_pred = lr.predict(X_test)
from sklearn.metrics import accuracy_score
print('ACC ',accuracy_score(y_test, y_pred))
print(classification_report(y_test, y_pred))
จะสังเกตุว่าความแม่นยำนั้นเพิ่มขึ้นแล้วเพราะข้อมูลของเรามันเข้าที่เข้าทางแล้ว ให้ทำการจัดการกับโมเดลของเราต่อเพราะโมเดลที่ใช้เป็นแค่โมเดลเบื้องต้น ยังไม่ได้ปรับปรุงอะไรเลย ให้ทำการปรับปรุงโมเดลด้วยการใช้ GridSearhCV ได้เลย
from sklearn.model_selection import GridSearchCV
parameters = {‘C’: np.arange(1,10,0.5)}
lr_best = GridSearchCV(lr, parameters, cv=5)
lr_best.fit(X_train,y_train)
lr_best.best_estimator_
ทีนี้ลองเทรนใหม่ด้วยข้อมูลที่ scale แล้วและใช้ GridSearchCV ปรับปรุงโมเดลแล้ว
y_pred = lr_best.predict(X_test)
from sklearn.metrics import accuracy_score
print(‘ACC ‘,accuracy_score(y_test, y_pred))
print(classification_report(y_test, y_pred))
สาเหตุที่ไม่มีอะไรเปลี่ยนแปลงเลยคือ “โมเดลทำได้ดีที่สุดแล้ว” ต้องเข้าใจก่อนว่าไม่ใช่ว่าทุกโมเดลจะสามารถทำความแม่นยำได้ 100% หรือข้อมูลทุกอันสามารถสร้างโมเดลให้มีความแม่นยำ 100% บางครั้งโมเดลก็ทำได้สุดๆแค่นี้ ต่อให้ปรับปรุงอะไรก็ไม่สามารถเพิ่มได้มากกว่านี้แล้ว โดยสิ่งที่ผมแนะนำได้คือแนะนำให้โมเดลหลายๆอันแล้วมาเปรียบเทียบกันเพื่อหาว่าอันไหนดีที่สุด
แต่ที่ถ้าใครสนใจอยากจะ Challenge ตัวเองก็ลองทำตามบทความนี้แล้วให้ความแม่นมากกว่าที่ผมแอบไปทำก็ลองดูได้นะครับ (คำใบ้คือ เพิ่ม Feature) ใครทำได้แล้วลองคอมเม้นดูนะครับ
สรุปการทดลอง
ในการทดสอบ Logistic Regression กับ Titanic Dataset จะเห็นได้ว่าถ้าเราเพิ่มปัจจัยเข้าในชุดฝึก ความแม่นยำก็จะเพิ่มขึ้นนั้นเอง แต่ปัจจัยนั้นต้องเกี่ยวด้วยจริงๆ ถ้าเราเพิ่ม “ชื่อผู้โดยสาร” ความแม่นยำก็ไม่เพิ่มขึ้น เผลอๆจะ error มากด้วยซ้ำ แต่ถ้าเราเพิ่มอายุเข้าไปทำและทำการ Scale ข้อมูลความแม่นยำของเราก็ยังเพิ่มขึ้นได้อีกนิดหน่อยด้วย เพราะฉนั้นทุกๆครั้งที่ทำโมเดลไม่ควรข้ามขั้นตอนการทำ Scale และจูนโมเดลด้วยนั้นเอง !
บทความต่อไป :
Github : https://github.com/peeratpop/Machine_Learning_101
Medium : https://medium.com/@pingloaf
Linkedin : https://www.linkedin.com/in/peerat-limkonchotiwat/