φυβλαςのβλογ
บล็อกของ 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)
หอดูดาวโบราณปักกิ่ง ตอนที่ ๑: แท่นสังเกตการณ์และสวนดอกไม้
พิพิธภัณฑ์สถาปัตยกรรมโบราณปักกิ่ง
เที่ยวเมืองตานตง ล่องเรือในน่านน้ำเกาหลีเหนือ
ตระเวนเที่ยวตามรอยฉากของอนิเมะในญี่ปุ่น
เที่ยวชมหอดูดาวที่ฐานสังเกตการณ์ซิงหลง
ทำไมจึงไม่ควรเขียนวรรณยุกต์เวลาทับศัพท์ภาษาต่างประเทศ

ไทย

日本語

中文