จากที่ตอนที่แล้วได้แนะนำการตรวจสอบแบบไขว้แบบ k-fold ไป
https://phyblas.hinaboshi.com/20171018 ต่อไปมาดูการใช้ k-fold เพื่อพิจารณาปรับค้นหาค่าไฮเพอร์พารามิเตอร์ของแบบจำลองดู
ไฮเพอร์พารามิเตอร์คือค่าที่เป็นตัวกำหนดคุณสมบัติของแบบจำลองของเรา ในแบบจำลองแต่ละชนิดมีไฮเพอร์พารามิเตอร์ไม่เหมือนกัน เช่น สำหรับการถดถอยโลจิสติกก็จะได้แก่ อัตราการเรียนรู้ (η), อัตราเรกูลาไรซ์ (λ) เป็นต้น ค่าที่ไม่เหมือนกันอาจทำให้ผลการเรียนรู้ของโปรแกรมออกมาแตกต่างกันได้
การเลือกไฮเพอร์พารามิเตอร์ให้เหมาะสมนั้นจึงเป็นสิ่งหนึ่งที่สำคัญมากอย่างหนึ่งในการเขียนโปรแกรมสร้างแบบจำลองการเรียนรู้ของเครื่องเพื่อทำนายสิ่งที่ต้องการ
แต่ถ้าหากว่าลองทำซ้ำโดยรันใหม่ไปเรื่อยๆโดยปรับค่าไปทีละนิดเพื่อเทียบดูผลละก็แบบนั้นอาจจะช้า ดังนั้นวิธีที่สะดวกกว่านั้นก็คือเขียนโปรแกรมให้วนซ้ำโดยแต่ละรอบเปลี่ยนค่าไปเรื่อยๆแล้วเอามาวาดกราฟเทียบความแม่นยำที่ได้ในแต่ละค่า
ขอยกตัวอย่างเป็นการหาพารามิเตอร์การเรกูลาไรซ์ของแบบจำลองการถดถอยโลจิสติกที่ใช้คลาส LogisticRegression ของ sklearn ซึ่งกล่าวไปใน
https://phyblas.hinaboshi.com/20171010 ในตัวอย่างสุดท้ายในบทความนั้นได้ใช้ลองหาความแม่นของของแบบจำลองเมื่อปรับค่า C (ส่วนกลับของค่าอัตราเรกูลาไรซ์ λ) ต่างๆกันไป คราวนี้จะลองทำเหมือนกันแต่จะใช้การตรวจสอบแบบไขว้โดยใช้ StratifiedKFold แบ่งเป็น ๕ ส่วน วนซ้ำเพื่อลอง ๕ ครั้ง แล้วนำมาหาค่าเฉลี่ยและส่วนเบี่ยงเบนมาตรฐาน
ลองเขียนดูได้ดังนี้
import numpy as np
import matplotlib.pyplot as plt
from sklearn import datasets
from sklearn.linear_model import LogisticRegression as Lori
from sklearn.model_selection import StratifiedKFold
X,z = datasets.make_blobs(n_samples=100,n_features=2,centers=3,random_state=0)
skf = StratifiedKFold(n_splits=5,shuffle=True)
ccc = 10**np.linspace(-5,3,18) # กำหนดค่า C ที่ต้องการลอง
khanaen = [] # ลิสต์เก็บค่าคะแนนทั้งหมด
for C in ccc:
k = [] # ลิสต์เก็บคะแนนในรอบนั้นๆ
lori = Lori(C=C) # ป้อนค่า C ซึ่งแต่ละรอบไม่เหมือนกัน
for f,t in skf.split(X,z):
X_fuek,z_fuek,X_truat,z_truat = X[f],z[f],X[t],z[t]
lori.fit(X_fuek,z_fuek)
k.append(lori.score(X_truat,z_truat))
khanaen.append(k)
mean = np.mean(khanaen,1) # ค่าเฉลี่ย
std = np.std(khanaen,1) # ส่วนเบี่ยงเบนมาตรฐาน
plt.axes(xscale='log')
plt.plot(ccc,mean,'o-',color='#117733') # กราฟค่าเฉลี่ย
plt.fill_between(ccc,mean-std,mean+std,color='#AAFFCC') # ขอบเขตในช่วงส่วนเบี่ยงเบนมาตรฐาน
plt.show()
ผลที่ได้ เทียบดูแล้วก็จะรู้ได้ว่าใช้ค่าไหนดีกว่า
จากตรงนี้จะเห็นว่าค่า C สัก 1 ขึ้นไปน่าจะดี ถ้าต่ำไปจะเรกูลาไรซ์แรงไปทำให้การเรียนรู้ไม่คืบหน้า
ด้วยการพิจารณาในลักษณะแบบนี้ เราก็จะได้ผลลัพธ์ที่มั่นใจได้มากกว่าการลองแค่ครั้งเดียว
การใช้ cross_val_score sklearn มักมีอุปกรณ์ที่ช่วยให้งานที่ดูซับซ้อนยืดยาวสามารถเขียนด้วยโค้ดที่สั้นลง
สำหรับโค้ดข้างต้นนั้น จริงๆแล้วหากใช้คำสั่ง cross_val_score ของ sklearn แล้วก็จะทำให้ดูแล้วสั้นลงไปได้มาก
หากเขียนใหม่ด้วย cross_val_score จะเขียนได้แบบนี้
from sklearn.model_selection import cross_val_score
X,z = datasets.make_blobs(n_samples=100,n_features=2,centers=3,random_state=0)
ccc = 10**np.linspace(-5,3,18)
khanaen = []
for C in ccc:
khanaen.append(cross_val_score(Lori(C=C),X,z,cv=5,scoring='accuracy'))
khanaen = np.array(khanaen)
mean = np.mean(khanaen,1)
std = np.std(khanaen,1)
plt.axes(xscale='log')
plt.plot(ccc,mean,'o-',color='#117733')
plt.fill_between(ccc,mean-std,mean+std,color='#AAFFCC')
plt.show()
จะเห็นว่าสั้นลงมาก คือไม่ต้องเรียกใช้ StratifiedKFold ไม่ต้องมีการสร้างวังวน for ด้านใน แต่ทั้งหมดถูกแทนด้วย cross_val_score บรรทัดเดียว
การทำงานของ cross_val_score ก็คือจะทำการแบ่งข้อมูลออกเป็นกลุ่มโดยใช้ StratifiedKFold แล้วทำการวนซ้ำให้โดยอัตโนมัติ
อาร์กิวเมนต์ที่จำเป็นต้องใส่มี ๓ ตัว ไล่ตามลำดับดังนี้
1. estimator คือ ตัวออบเจ็กต์แบบจำลองที่เราต้องการตรวจสอบค่า ในที่นี้คือ Lori(C=C) คือแบบจำลองการถดถอยโลจิสติกที่กำหนดค่า C ให้เป็นตามที่ต้องการในแต่ละรอบ
2. X คือ ค่าตัวแปรต้น
3. y คือ คำตอบจริง
ส่วนที่เหลือก็คือคีย์เวิร์ดต่างๆสำหรับปรับแต่ง ในที่นี้ใส่ cv=5 ก็คือกำหนดให้แบ่งเป็น 5 กลุ่ม
ส่วน scoring='accuracy' หมายความว่าจะให้คะแนนเป็นความแม่นยำ ซึ่งที่จริงนี่ก็เป็นค่าตั้งต้นอยู่แล้ว จะไม่ใส่ก็ได้
แต่หากใส่เป็น scoring='f1' ก็จะใช้เป็นคะแนน f1 ถ้าใส่ scoring='roc_auc' ก็จะใช้พื้นที่ใต้กราฟ ROC
เพียงแต่ว่ากรณีตัวอย่างนี้เป็นการแบ่งกลุ่ม ๓ กลุ่มขึ้นไป จะไม่สามารถใช้ roc_auc ได้ ส่วน f1 นั้นแบบธรรมดาจะใช้ไม่ได้ ต้องใช้เป็น f1_macro หรือ f1_weighted
เช่น
print(cross_val_score(Lori(),X,z,cv=5,scoring='f1_macro'))
# ได้ [ 0.96497811 0.95 0.95996397 0.94997999 0.93994595]
นอกจากนี้ยังมีอีกหลายแบบที่ใช้ได้ อาจดูได้ใน
http://scikit-learn.org/stable/modules/model_evaluation.html รายละเอียดเกี่ยวกับเรื่องคะแนน f1 ดูได้ใน
https://phyblas.hinaboshi.com/20171014 ส่วนเรื่องของ ROC ดูได้ใน
https://phyblas.hinaboshi.com/20171016 นอกจากนี้ scoring ยังอาจใส่เป็นฟังก์ชันของการให้คะแนนก็ได้ โดยใช้คำสั่ง make_scorer เช่นถ้าต้องการคะแนนเป็น ความแม่นคูณ 2
from sklearn.metrics import make_scorer
maen_x2 = lambda y,t:(y==t).mean()*2
print(cross_val_score(Lori(),X,z,cv=5,scoring=make_scorer(maen_x2)))
# ได้ [ 1.93 1.9 1.92 1.9 1.88]
การใช้ validation_curve ขั้นตอนการประเมินแบบจำลองที่มีค่าไฮเพอร์พารามิเตอร์ต่างกันดังที่ได้ทำมาในตัวอย่างข้างต้นนั้นสามารถทำให้ง่ายขึ้นได้อีกโดยใช้คำสั่ง validation_curve
validation_curve เป็นคำสั่งที่มีไว้สำหรับสร้างกราฟแสดงการประเมินผลของความเปลี่ยนแปลงค่าไฮเพอร์พารามิเตอร์ที่มีต่อประสิทธิภาพของแบบจำลอง
หากใช้ validation_curve แล้ว จะเขียนสั้นเหลือแค่นี้
from sklearn.model_selection import validation_curve
X,z = datasets.make_blobs(n_samples=100,n_features=2,centers=2,random_state=0)
ccc = 10**np.linspace(-5,3,18)
khanaen_fuek,khanaen_truat = validation_curve(Lori(),X,z,'C',ccc,cv=5)
อาร์กิวเมนต์ที่ต้องใส่เรียงลำดับตามนี้
1. estimator คือ ออบเจ็กต์แบบจำลองที่จะประเมิน
2. X คือ ตัวแปรต้น
3. y คือ ค่าคำตอบ
4. param_name คือ ชื่อของไฮเพอร์พารามิเตอร์ที่ต้องการให้ปรับค่าไปเรื่อยๆเพื่อประเมิน
5. param_range คือ ค่าของไฮเพอร์พารามิเตอร์ที่ต้องการพิจารณา
นอกจากนี้ก็มีคีย์เวิร์ดที่สำคัญคือ cv และ scoring ซึ่งตรงนี้จะเหมือนกันกับ cross_val_score
ในที่นี้ใช้ cv เป็น 5 แบ่งเป็น 5 ส่วนเหมือนเดิม ส่วน scoring ถ้าไม่ใส่ก็คือใช้คะแนนเป็นความแม่นยำธรรมดา
ค่าที่คืนกลับมาจะมี ๒ ตัวคือ ผลคะแนนในการทายชุดข้อมูลที่ใช้ฝึก และผลคะแนนในการทายชุดข้อมูลตรวจสอบ
ทั้ง ๒ ตัวนั้นจะเป็นอาเรย์สองมิติซึ่งมีขนาดเป็น (จำนวนค่าไฮเพอร์พารามิเตอร์, ค่า cv)
ในตัวอย่างก่อนหน้าซึ่งใช้ cross_val_score นั้นเราจะได้มาแต่ผลคะแนนของข้อมูลตรวจสอบ แต่ว่าถ้าใช้ validation_curve เราจะได้ผลคะแนนของข้อมูลฝึกมาด้วย
ลองนำค่าทั้ง ๒ มาวาดกราฟดูได้ดังนี้
plt.axes(xscale='log')
mean = np.mean(khanaen_fuek,1)
std = np.std(khanaen_fuek,1)
plt.plot(ccc,mean,'o-',color='#771133')
plt.fill_between(ccc,mean-std,mean+std,color='#FFAACC',alpha=0.4)
mean = np.mean(khanaen_truat,1)
std = np.std(khanaen_truat,1)
plt.plot(ccc,mean,'o-',color='#117733')
plt.fill_between(ccc,mean-std,mean+std,color='#AAFFCC',alpha=0.4)
plt.legend([u'ฝึกฝน',u'ตรวจสอบ'],prop={'family':'Tahoma'})
plt.show()
ค่าไฮเพอร์พารามิเตอร์ไม่จำเป็นจะต้องเป็นตัวเลขเสมอไป เช่นหากต้องการพิจารณาความต่างระหว่างเรกูลาไรซ์แบบ l1 และ l2 ที่ค่า C หนึ่งๆ ในที่นี้ C เป็นค่าที่กำหนดตายตัวตั้งแต่เริ่มแรกและไม่มีการเปลี่ยน
ก็อาจเขียนแบบนี้
k = validation_curve(Lori(C=0.01),X,z,'penalty',['l1','l2'],cv=5)
print(u'C=0.01, l1: %s, เฉลี่ย: %.3f\nC=0.01, l2: %s, เฉลี่ย: %.3f'%(k[1][0],k[1][0].mean(),k[1][1],k[1][1].mean()))
ได้
C=0.01, l1: [ 0.5 0.5 0.5 0.5 0.5], เฉลี่ย: 0.500
C=0.01, l2: [ 0.95 0.8 0.75 0.8 0.9 ], เฉลี่ย: 0.840
แต่ว่า validation_curve สามารถมีไฮเพอร์พารามิเตอร์ที่แปรค่าไปเรื่อยๆได้แค่ตัวเดียว หากต้องการพิจารณา ๒ ค่าพร้อมๆกันก็ต้องสร้างวังวน for เพื่อเปลี่ยนแปลงไฮเพอร์พารามิเตอร์ตัวนึงไปด้วย เช่น
plt.axes(xscale='log',xlabel='C').set_ylabel(u'คะแนน',family='Tahoma')
for p in ['l1','l2']:
_,khanaen_truat = validation_curve(Lori(penalty=p),X,z,'C',ccc,cv=5)
plt.plot(ccc,np.mean(khanaen_truat,1),'o-',alpha=0.5)
plt.legend(['l1','l2'])
plt.show()
นอกจากนี้แล้ว sklearn ยังมีคำสั่งที่ช่วยหาไฮเพอร์พารามิเตอร์ที่เหมาะสมที่สุดได้อย่างสะดวกมาก คือ GridSearchCV แต่รายละเอียดจะยังไม่พูดถึงตรงนี้เพราะมีรายละเอียดเพิ่มเติมอีกมาก อาจเขียนถึงในภายหลัง
อ้างอิง