φυβλαςのβλογ
บล็อกของ phyblas



[python] การค้นหาค่าไฮเพอร์พารามิเตอร์ที่เหมาะสมด้วยการตรวจสอบแบบไขว้
เขียนเมื่อ 2017/10/20 16:51
แก้ไขล่าสุด 2022/07/19 06:37
จากที่ตอนที่แล้วได้แนะนำการตรวจสอบแบบไขว้แบบ 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 แต่รายละเอียดจะยังไม่พูดถึงตรงนี้เพราะมีรายละเอียดเพิ่มเติมอีกมาก อาจเขียนถึงในภายหลัง



อ้างอิง


-----------------------------------------

囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧

ดูสถิติของหน้านี้

หมวดหมู่

-- คอมพิวเตอร์ >> ปัญญาประดิษฐ์
-- คอมพิวเตอร์ >> เขียนโปรแกรม >> python >> numpy
-- คอมพิวเตอร์ >> เขียนโปรแกรม >> python >> matplotlib
-- คอมพิวเตอร์ >> เขียนโปรแกรม >> python >> sklearn

ไม่อนุญาตให้นำเนื้อหาของบทความไปลงที่อื่นโดยไม่ได้ขออนุญาตโดยเด็ดขาด หากต้องการนำบางส่วนไปลงสามารถทำได้โดยต้องไม่ใช่การก๊อปแปะแต่ให้เปลี่ยนคำพูดเป็นของตัวเอง หรือไม่ก็เขียนในลักษณะการยกข้อความอ้างอิง และไม่ว่ากรณีไหนก็ตาม ต้องให้เครดิตพร้อมใส่ลิงก์ของทุกบทความที่มีการใช้เนื้อหาเสมอ

สารบัญ

รวมคำแปลวลีเด็ดจากญี่ปุ่น
มอดูลต่างๆ
-- numpy
-- matplotlib

-- pandas
-- manim
-- opencv
-- pyqt
-- pytorch
การเรียนรู้ของเครื่อง
-- โครงข่าย
     ประสาทเทียม
ภาษา javascript
ภาษา mongol
ภาษาศาสตร์
maya
ความน่าจะเป็น
บันทึกในญี่ปุ่น
บันทึกในจีน
-- บันทึกในปักกิ่ง
-- บันทึกในฮ่องกง
-- บันทึกในมาเก๊า
บันทึกในไต้หวัน
บันทึกในยุโรปเหนือ
บันทึกในประเทศอื่นๆ
qiita
บทความอื่นๆ

บทความแบ่งตามหมวด



ติดตามอัปเดตของบล็อกได้ที่แฟนเพจ

  ค้นหาบทความ

  บทความแนะนำ

ตัวอักษรกรีกและเปรียบเทียบการใช้งานในภาษากรีกโบราณและกรีกสมัยใหม่
ที่มาของอักษรไทยและความเกี่ยวพันกับอักษรอื่นๆในตระกูลอักษรพราหมี
การสร้างแบบจำลองสามมิติเป็นไฟล์ .obj วิธีการอย่างง่ายที่ไม่ว่าใครก็ลองทำได้ทันที
รวมรายชื่อนักร้องเพลงกวางตุ้ง
ภาษาจีนแบ่งเป็นสำเนียงอะไรบ้าง มีความแตกต่างกันมากแค่ไหน
ทำความเข้าใจระบอบประชาธิปไตยจากประวัติศาสตร์ความเป็นมา
เรียนรู้วิธีการใช้ regular expression (regex)
การใช้ unix shell เบื้องต้น ใน linux และ mac
g ในภาษาญี่ปุ่นออกเสียง "ก" หรือ "ง" กันแน่
ทำความรู้จักกับปัญญาประดิษฐ์และการเรียนรู้ของเครื่อง
ค้นพบระบบดาวเคราะห์ ๘ ดวง เบื้องหลังความสำเร็จคือปัญญาประดิษฐ์ (AI)
หอดูดาวโบราณปักกิ่ง ตอนที่ ๑: แท่นสังเกตการณ์และสวนดอกไม้
พิพิธภัณฑ์สถาปัตยกรรมโบราณปักกิ่ง
เที่ยวเมืองตานตง ล่องเรือในน่านน้ำเกาหลีเหนือ
ตระเวนเที่ยวตามรอยฉากของอนิเมะในญี่ปุ่น
เที่ยวชมหอดูดาวที่ฐานสังเกตการณ์ซิงหลง
ทำไมจึงไม่ควรเขียนวรรณยุกต์เวลาทับศัพท์ภาษาต่างประเทศ

บทความแต่ละเดือน

2024年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

2023年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

2022年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

2021年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

2020年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

ค้นบทความเก่ากว่านั้น

ไทย

日本語

中文