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



[python] ผลบวกลบจริงปลอม, ความเที่ยงและความระลึกได้, ค่าคะแนน f1
เขียนเมื่อ 2017/10/14 20:14
แก้ไขล่าสุด 2022/07/21 15:13
ปกติแล้วเวลาที่เราทายอะไรสักอย่าง การที่ดูผลแล้วบอกประสิทธิภาพการทายว่าทายดีแค่ไหนนั้น มักจะคิดง่ายๆว่าเอาจำนวนที่ทายถูกหารด้วยจำนวนทั้งหมด คิดดูว่าเป็นสัดส่วนเท่าไหร่

นั่นคือการคำนวณความแม่นยำ (accuracy) ซึ่งเป็นการคำนวณพื้นฐานที่ใช้กันบ่อยทั่วไปมากที่สุด

แต่ว่าในทางสถิติแล้ว บางครั้งก็มีการใช้วิธีการคำนวณอย่างอื่นเพื่อให้คะแนนการทาย ซึ่งในบางกรณีก็อาจจะเหมาะมากกว่า

ในบทความนี้เราจะมาพูดถึงสิ่งที่เรียกว่า ความเที่ยง (precision) ความระลึกได้ (recall) และคะแนน f1

ปกติแล้ว ในการพิจารณาปัญหาซึ่งต้องทำนายผลที่มีคำตอบอยู่แค่ ๒ ตัวเลือก เราอาจเรียกผลการทำนาย ๒ แบบนั้นว่าเป็นผลบวกกับผลลบ

ผลบวกมักหมายถึงตรวจเจอสิ่งที่ต้องการหา ผลลบคือไม่เจอสิ่งที่กำลังตรวจหาอยู่

คำว่าบวกลบในที่นี้ไม่ได้หมายความว่าบวกจะต้องดี ลบจะต้องไม่ดี เช่นหากเราต้องการตรวจว่าคนคนหนึ่งเป็นโรคหรือเปล่า ผลบวกก็คือเป็นโรค ผลลบก็คือไม่เป็นโรค

บางทีบวกลบจะเป็นอะไรก็ได้ ไม่ได้ต้องตัดสินชัดเจนแน่นอน แต่โดยทั่วไปแล้วขึ้นอยู่กับว่าจะเน้นมองไปที่อะไรเป็นหลัก

เช่นหากเราต้องการพิจารณาคนกลุ่มหนึ่งซึ่งกำลังสนทนากันในเน็ต แล้วอยากจะหาว่าคนไหนเป็นผู้หญิง งั้นผลบวกก็คือเป็นผู้หญิง ผลลบคือผู้ชาย

ทีนี้เมื่อมีการทายเกิดขึ้น แล้วมีการเฉลยผลที่ถูกต้องจริงๆ พอเปรียบเทียบผลการทายกับผลเฉลยจริงเราอาจแบ่งผลลัพธ์ได้เป็น ๔ ดังตารางนี้
/人◕ ‿‿ ◕人\ ค่าจริง
บวก ลบ
ค่าทาย บวก บวกจริง
TP
บวกปลอม
FP
ลบ ลบปลอม
FN
ลบจริง
TN


- บวกจริง (true positive, TP) ทายว่าเป็นบวกแล้วก็เป็นบวกจริงๆ
- บวกปลอม (false positive, FP) ทายว่าเป็นบวกแต่ดันเป็นลบ
- ลบปลอม (false negative, FN) ทายว่าเป็นลบแต่ดันเป็นบวก
- ลบจริง (true negative, TN) ทายว่าเป็นลบแล้วก็เป็นลบจริงๆ

และเมื่อจะหาค่าความแม่นยำ ก็คือเอาผลที่เป็นจริง ไม่ว่าจะเป็นบวกหรือลบก็ตาม มาหารด้วยทั้งหมด กล่าวคือ
ความแม่นยำ = (บวกจริง+ลบจริง)/(บวกจริง+บวกปลอม+ลบปลอม+ลบจริง)

แต่นอกจากนี้ยังมีวิธีการพิจารณาโดยพิจารณาเฉพาะผลที่ทายได้เป็นบวก ดูว่าที่ทายมานั้นเป็นบวกจริงสักเท่าไหร่ นั่นจะเรียกว่า ความเที่ยง
ความเที่ยง = บวกจริง/(บวกจริง+บวกปลอม)

ค่านี้จะบอกให้รู้ว่าที่ทายว่าเป็นบวกนั้นน่าเชื่อถือแค่ไหน เช่นถ้าคนไข้ถูกตรวจว่าเป็นโรคขึ้นมา เขาจะมีโอกาสเป็นโรคจริงๆแค่ไหน

นอกจากนี้ก็มีอีกค่า ซึ่งพิจารณาเฉพาะของที่จริงๆแล้วเป็นบวก เราจะตรวจพบว่ามันเป็นบวกได้จริงๆแค่ไหน ค่านั้นเรียกว่า ความระลึกได้
ความระลึกได้ = บวกจริง/(บวกจริง+ลบปลอม)

เช่น ถ้าเรากำลังตรวจหาโรคบางอย่างอยู่ ค่านี้จะบอกว่าเรามีโอกาสตรวจเจอมันแค่ไหน

ลบปลอม ก็หมายความว่าจริงๆเป็นบวก แต่กลับถูกตรวจว่าเป็นลบ คือหาไม่เจอ พลาดสิ่งที่ต้องการตรวจเจอไป



บ่อยครั้งที่ค่าความเที่ยงกับค่าความระลึกได้นั้นสวนทางกัน

ยกตัวอย่างเช่น เราค้นหาเว็บที่ต้องการในกูเกิล ยิ่งผลการค้นหาออกมามากก็มีโอกาสที่ของที่ต้องการจะอยู่ในนั้นมาก แต่ของที่ไม่จำเป็นก็โผล่ออกมาเยอะตาม

นั่นก็คือ ถ้าแค่เข้าข่ายสักหน่อยก็ตัดสินว่าใช่แล้ว ความระลึกได้จะสูง แต่ความเที่ยงจะต่ำ เพราะหาเจอสิ่งที่ต้องการได้มาก แต่ก็มีสิ่งที่ไม่ใช่โผล่มาเพิ่ม

แต่ถ้าต้องรอให้มั่นใจมากๆจึงจะตัดสินใจว่าใช่ ความเที่ยงจะสูง แต่ความระลึกได้จะสูง เพราะสิ่งที่หาได้ส่วนใหญ่ใช่หมด แต่ก็ตกหล่นของที่ควรจะหาได้ไปอีกเยอะ

ค่าไหนจะสำคัญมากหรือน้อยกว่าก็อาจขึ้นกับกรณี เช่นถ้าเป็นการตรวจหาโรคละก็ กรณีนี้การตรวจให้เจอโรคจะสำคัญ ดังนั้นความระลึกได้ควรจะต้องสูง

ถ้ามีใครที่เป็นโรคแต่กลับตัดสินว่าไม่เป็น แบบนั้นอันตราย แต่ถ้าคนที่ไม่เป็นแต่ถูกตัดสินว่าเป็น แบบนั้นอาจไม่เสียหายเท่า ดังนั้นกรณีแบบนี้ถ้าให้ผลเป็นบวกง่ายไว้จะดีกว่า



เมื่อนำความเที่ยงกับความระลึกได้มาเฉลี่ยกันก็จะได้เป็นค่าที่เรียกว่าคะแนน f1

เพียงแต่ว่าไม่ใช่การเฉลี่ยโดยตรง แต่เป็นการเฉลี่ยส่วนกลับ เหมือนเวลาบวกความต่างศักย์ของถ่านที่ต่อกันแบบขนาน แบบนี้เรียกว่าค่าเฉลี่ยฮาร์มอนิก

กล่าวคือ
1/f1 = 0.5×(1/ความเที่ยง + 1/ความระลึกได้)

หรือจะคำนวณได้ว่า
f1 = 2×บวกจริง/(2×บวกจริง+บวกปลอม+ลบปลอม)
    = 2×จำนวนที่ทายว่าเป็นบวกและเป็นบวกจริง/(จำนวนที่ทายว่าเป็นบวก+จำนวนที่ผลจริงๆเป็นบวก)




เราอาจสร้างฟังก์ชันคำนวณค่าต่างๆเหล่านี้ในไพธอนได้โดย
maenyam  = lambda y,t:(t==y).mean()
thiang = lambda y,t:(1==y[t==1]).mean()
raluek = lambda y,t:(t[y==1]==1).mean()
f1 = lambda y,t:2*(t*y).sum()/(y.sum()+t.sum())

นอกจากนี้ใน sklearn ก็ได้มีเตรียมฟังก์ชันพวกนี้ไว้อยู่แล้ว จะนำมาใช้ก็ได้เช่นกัน
from sklearn.metrics import accuracy_score as maenyam
from sklearn.metrics import precision_score as thiang
from sklearn.metrics import recall_score as raluek
from sklearn.metrics import f1_score as f1

ลองยกตัวอย่าง เช่น ใช้เครื่องตรวจอาการป่วยวินิจฉัยโรคให้ผู้ป่วย ๑๐ คนโดยในนั้นมีผู้ป่วยจริงๆอยู่ ๓ คน

แต่ว่าอุปกรณ์วัดที่ใช้มีอยู่หลายตัว ให้ผลวินิยฉัยต่างกันไป เมื่อนำมาเปรียบเทียบกับค่าจริง ผลคะแนนที่ได้ออกมาเป็นดังนี้
bok = lambda y,t:u'แม่น=%.3f : เที่ยง=%.3f : ระลึก=%.3f : f1=%.3f'%(maenyam(y,t),thiang(y,t),raluek(y,t),f1(y,t))
y = np.array([0,0,0,0,0,0,0,1,1,1])
print(bok(y,np.array([0,0,0,0,0,0,0,0,0,1])))
print(bok(y,np.array([0,0,0,0,0,1,1,1,1,1])))
print(bok(y,np.array([0,1,1,1,1,1,1,1,1,1])))
print(bok(y,np.array([0,0,0,0,1,1,1,1,1,0])))

ผลที่ได้
แม่น=0.800 : เที่ยง=1.000 : ระลึก=0.333 : f1=0.500
แม่น=0.800 : เที่ยง=0.600 : ระลึก=1.000 : f1=0.750
แม่น=0.400 : เที่ยง=0.333 : ระลึก=1.000 : f1=0.500
แม่น=0.600 : เที่ยง=0.400 : ระลึก=0.667 : f1=0.500

อันที่ 2 ทายแม่นเท่าอันที่ 1 แต่กลับได้คะแนน f1 เยอะกว่า

ส่วนอันที่ 3 กับ 4 ทายแม่นน้อยกว่าอันที่ 1 เยอะแต่กลับได้ค่า f1 เท่ากัน

ดังนั้นจะเห็นได้ว่าในบางกรณีคะแนนที่พิจารณาจาก f1 จะต่างจากค่าความแม่นยำ ดังนั้นการเลือกว่าจะให้คะแนนด้วยวิธีไหนจึงเห็นผลได้ชัด



คะแนน f1 ยังอาจใช้เพื่อพิจารณาปัญหาการแบ่งกลุ่มเป็นหลายกลุ่มได้ด้วย เพียงแต่ว่ากรณีนี้จะมีความคลุมเครือ เพราะถ้ามีแค่ ๒ เราจะแบ่งให้ผลอันหนึ่งเป็นบวก และอีกอันเป็นลบ

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

เพียงแต่ว่าการเฉลี่ยนั้นอาจเฉลี่ยโดยคิดน้ำหนักตามจำนวนผลที่แต่ละกลุ่มมี ก็จะได้คำตอบต่างกันออกไป

กรณีที่เฉลี่ยทุกกลุ่มด้วยน้ำหนักเท่ากันอาจลองเขียนฟังก์ชันได้ดังนี้
def f1(y,t):
    k = len(set(y)^set(t))
    y_1h = y[:,None]==range(k)
    t_1h = t[:,None]==range(k)
    return ((2*(t_1h*y_1h).sum(0)/(y_1h.sum(0)+t_1h.sum(0)))).mean()

ส่วนฟังก์ชัน f1_score ของ sklearn นั้นโดยทั่วไปแล้วหากเราป้อนค่าที่ไม่ใช่มีแค่​ ๒ กลุ่ม (ไม่ได้มีแค่ 0,1) จำเป็นจะต้องระบุด้วยว่าจะเฉลี่ยแบบไหน

หากต้องการเฉลี่ยให้เท่ากันก็ใส่เป็น average='macro' แต่ถ้าต้องการถ่วงน้ำหนักให้ใส่ average='weighted'

ตัวอย่าง
from sklearn.metrics import f1_score as f1
y = np.array([1,1,1,1,2,0,2,1,1,1])
t = np.array([1,1,1,1,0,2,2,1,1,1])
print(f1(y,t,average='macro')) # ได้ 0.5
print(f1(y,t,average='weighted')) # ได้ 0.8

weighted จะได้เยอะกว่า macro เพราะค่า 1 ซึ่งทายถูกหมดนั้นมีจำนวนมาก หากเฉลี่ยโดยถ่วงน้ำหนักคะแนนก็ย่อมสูง แต่ถ้าไม่ถ่วงน้ำหนักก็จะโดนคะแนนของเลข 0 และ 2 ซึ่งทายผิดเยอะดึงลง



ต่อมาลองเอามาใช้ในการวิเคราะห์ผลของปัญหาการวิเคราะห์การถดถอยโลจิสติกดู

สมมุติว่าข้อมูลการตรวจโรคจากสารในร่างกายของคน 1000 คนออกมาเป็นดังนี้ โดยแกนนอนคือปริมาณสาร a แกนนอนคือปริมาณสาร b ส่วนสีของจุดบ่งบอกถึงผล โดยสีเขียวคือผลบวก (มีโรค) สีน้ำเงินคือผลลบ (ไม่มีโรค)



ข้อมูลถูกสร้างโดยโค้ดตามนี้
np.random.seed(2)
X,z = datasets.make_blobs(n_samples=1000,n_features=2,centers=2,cluster_std=2.5,center_box=[2,20])
X = np.abs(X)
plt.axes(aspect=1)
plt.xlabel('ปริมาณสาร a',family='Tahoma')
plt.ylabel('ปริมาณสาร b',family='Tahoma')
plt.scatter(X[:,0],X[:,1],c=z,s=30,alpha=0.3,edgecolor='k',cmap='winter')
plt.show()

จากนั้นลองหาพารามิเตอร์ค่าน้ำหนักและไบแอสโดยใช้ LogisticRegression ของ sklearn เพื่อหาเส้นแบ่ง
from sklearn.linear_model import LogisticRegression as Lori
lori = Lori()
lori.fit(X,z)
w = lori.coef_[0]
b = lori.intercept_[0]
x_sen = np.array([X[:,0].min(),X[:,0].max()])
y_sen = -(b+x_sen*w[0])/w[1]
plt.axes(aspect=1)
plt.xlabel('ปริมาณสาร a',family='Tahoma')
plt.ylabel('ปริมาณสาร b',family='Tahoma')
plt.scatter(X[:,0],X[:,1],c=z,s=30,alpha=0.3,edgecolor='k',cmap='winter')
plt.plot(x_sen,y_sen,'--r')
plt.show()
เราก็จะได้เส้นแบ่งตามนี้ออกมา



ลองเอาผลการแบ่งนี้มาใช้ทำนายผลของข้อมูลชุดนี้
t = (np.dot(w,X.T)+b)>0
# หรือ t = lori.predict(X) ก็ได้
print(bok(z,t))

ได้
แม่น=0.934 : เที่ยง=0.929 : ระลึก=0.940 : f1=0.934


แต่ว่าหากลองปรับไบแอสให้เส้นเลื่อนขึ้นบนไปสักหน่อย โดยที่น้ำหนักเหมือนเดิม
b2 = b-3
t2 = (np.dot(w,X.T)+b2)>0

เส้นก็จะเปลี่ยนไปตามนี้



แล้วผลที่ได้ก็จะกลายเป็น
แม่น=0.807 : เที่ยง=0.997 : ระลึก=0.616 : f1=0.761
ความเที่ยงจะเพิ่มขึ้น แต่ความระลึกได้จะลดลง หมายความว่าเจอผลบวกที่ต้องการยากขึ้น แต่ของที่เจอส่วนใหญ่ถูกเกือบหมด



ในทางตรงกันข้ามหากปรับไบแอสให้เส้นเลื่อนขึ้นบน
b3 = b+3
t3 = (np.dot(w,X.T)+b3)>0
จะได้
แม่น=0.788 : เที่ยง=0.703 : ระลึก=0.996 : f1=0.825




นั่นคือความระลึกได้จะเพิ่มขึ้น แต่ความเที่ยงจะลดลง หมายความว่าหาผลบวกที่ต้องการได้เกือบหมด แต่ก็มีที่ไม่ใช่ปนมาด้วยเยอะ



อ้างอิง


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

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

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

หมวดหมู่

-- คอมพิวเตอร์ >> ปัญญาประดิษฐ์
-- คอมพิวเตอร์ >> เขียนโปรแกรม >> 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月

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

ไทย

日本語

中文