ใน ep ก่อนเราพูดถึง Loss Function สำหรับงาน Regression กันไปแล้ว ในตอนนี้เราจะมาพูดถึง Loss Function อีกแบบหนึ่ง ที่สำคัญไม่แพ้กัน ก็คือ Loss Function สำหรับงาน Classification เรียกว่า Cross Entropy Loss หรือ Logistic Regression

ไอเดียของ Loss Function แบบ Classification คือ เราต้องการตัวชี้วัด เป็นตัวเลขค่าเดียว ที่บอกว่า โมเดลของเราทำงานได้ดีแค่ไหน โดยเปรียบเทียบ Output ของ โมเดล เช่น โมเดลทำนายว่ารูปภาพนี้ น่าจะเป็น Dog 80% น่าจะเป็น Cat 20% กับค่า Ground Truth รูป Dog 1 ตัว
เราไม่สามารถนำทั้งสองค่า มาบวกลบกันได้ตรง ๆ เหมือน MAE, MSE แต่ต้องเปรียบเทียบผ่านฟังก์ชัน Softmax และ Negative Log Likelihood ด้านล่างเสียก่อน
สูตร Softmax Function
\( \sigma(\mathbf{z})_i = \frac{e^{z_i}}{\sum_{j=1}^K e^{z_j}} \text{ for } i = 1, \dotsc , K \text{ and } \mathbf z=(z_1,\dotsc,z_K) \in R^K \)สูตร Binary Cross Entropy (Log Loss)
\( \begin{align}J(\mathbf{w})\ &=\ \frac1N\sum_{n=1}^N H(p_n,q_n)\ =\ -\frac1N\sum_{n=1}^N\ \bigg[y_n \log \hat y_n + (1 – y_n) \log (1 – \hat y_n)\bigg]\,,
\end{align} \)
ให้ฟังก์ชันเหล่านี้จัดการ Probability หรือ ความมั่นใจของโมเดล ว่า
- ทายถูก ด้วยความมั่นใจ ถึงจะเรียกว่าดี Loss ก็จะน้อย
- แต่ถ้าทายถูก แบบไม่มั่นใจ ก็จะทำโทษ ให้ Loss มาก
- หรือ แม้ทายผิด แต่ดันมั่นใจมาก ก็จะทำโทษให้ Loss มาก เช่นกัน
เรามาเริ่มกันเลยดีกว่า
Loss Function หรือ Cost Function คือ การคำนวน Error ว่า yhat ที่โมเดลทำนายออกมา ต่างจาก y ของจริง อยู่เท่าไร แล้วหาค่าเฉลี่ย เพื่อที่จะนำมาหา Gradient ของ Loss ขึ้นกับ Weight ต่าง ๆ ด้วย Backpropagation แล้วใช้อัลกอริทึม Gradient Descent ทำให้ Loss น้อยลง ในการเทรนรอบถัดไป
ในเคสนี้เราจะพูดถึง Loss Function สำหรับงาน Classification (Discrete ค่าไม่ต่อเนื่อง) ที่เป็นที่นิยมมากที่สุด ได้แก่ Cross Entropy Loss
- yhat เป็น Probability ที่ออกมาจากโมเดลที่ Layer สุดท้ายเป็น Softmax Function
- y เป็นข้อมูลที่อยู่ในรูปแบบ One Hot Encoding
0. Import¶
import torch
from torch import tensor
import matplotlib.pyplot as plt
1. Data¶
เราจะสร้างข้อมูลตัวอย่างขึ้นมา Dog = 0, Cat 1, Rat = 2
y¶
สมมติค่า y จากข้อมูลตัวอย่าง ที่เราต้องการจริง ๆ เป็นดังนี้
y = tensor([0, 1, 2, 0, 0, 1, 0, 2, 2, 1])
y
n, c = len(y), y.max()+1
y_onehot = torch.zeros(n, c)
y_onehot[torch.arange(n), y] = 1
y_onehot
yhat¶
สมมติว่า โมเดลเราทำนายออกมาได้ nn
yhat = tensor([[3., 2., 1.],
[5., 6., 2.],
[0., 0., 5.],
[2., 3., 1.],
[5., 4., 3.],
[1., 0., 3.],
[5., 3., 2.],
[2., 2., 4.],
[8., 5., 3.],
[3., 4., 0.]])
เราจะใช้ Softmax Function จาก ep ที่แล้ว แล้วเติม log เอาไว้สำหรับใช้ในขั้นตอนถัดไป
def log_softmax(z):
z = z - z.max(-1, keepdim=True)[0]
exp_z = torch.exp(z)
sum_exp_z = torch.sum(exp_z, -1, keepdim=True)
return (exp_z / sum_exp_z).log()
yhat กลายเป็น Probability ของ 3 Category
log_softmax(yhat)
argmax เปรียบเทียบ y และ yhat¶
argmax ใช้หาตำแหน่งที่ มีค่ามากที่สุด ในที่นี้ เราสนใจค่ามากที่สุดใน มิติที่ 1
yhat.argmax(1)
y
ตรงกัน 7 อัน
(yhat.argmax(1) == y).sum()
2. Cross Entropy Loss¶
Cross Entropy Loss (Logistic Regression) หรือ Log Loss คือ การคำนวน Error ว่า yhat ต่างจาก y อยู่เท่าไร ด้วยการนำ Probability มาคำนวน หมายถึง ทายถูก แต่ไม่มั่นใจก็จะ Loss มาก หรือ ยิ่งทายผิด แต่มั่นใจมาก ก็จะ Loss มาก โดยคำนวนทั้ง Batch แล้วหาค่าเฉลี่ย
- p(x) มีค่าระหว่าง 0 ถึง 1 (ทำให้ผ่าน log แล้วติดลบ เมื่อเจอกับเครื่องหมายลบด้านหน้า จะกลายเป็นบวก)
- Cross Entropy Loss มีค่าระหว่าง 0 ถึง Infinity (ถ้าเป็น 0 คือไม่ Error เลย)
2.1 สูตร Cross Entropy Loss¶
เรียกว่า Negative Log Likelihood
เนื่องจาก ค่า $x$ อยู่ในรูป One Hot Encoding เราสามารถเขียนใหม่ได้เป็น $-\log(p_{i})$ โดย i เป็น Index ของ y ที่เราต้องการ
2.2 โค้ด Negative Log Likelihood¶
# log_probs = log of probability, target = target
def nll(log_probs, target):
return -(log_probs[torch.arange(log_probs.size()[0]), target]).mean()
2.3 การใช้งาน Negative Log Likelihood¶
loss = nll(log_softmax(yhat), y)
loss
2.4 Optimize¶
เนื่องจาก
ทำให้เราแยก เศษและส่วน ออกเป็น 2 ก่อนลบกัน
และถ้า x ใหญ่เกินไป เมื่อนำมา exp จะทำให้ nan ได้ จากสูตรด้านล่าง
a คือ max(x) เราสามารถ exp(x-a) ให้ x เป็นค่าติดลบให้หมด เมื่อ exp จะได้ไม่เกิน 1 แล้วค่อยไปบวกก a กลับทีหลังได้
จาก 2 สูตรด้านบน เราสามารถ Optimize โค้ด ได้ดังนี้
def log_softmax2(z):
m = z.max(-1, keepdim=True)[0]
return z - ((z-m).exp().sum(-1, keepdim=True).log()+m)
หรือ
def log_softmax3(z):
return z - (z).logsumexp(-1, keepdim=True)
เปรียบเทียบผลลัพธ์กับ PyTorch¶
import torch.nn.functional as F
F.cross_entropy(yhat, y)
nll(log_softmax(yhat), y)
nll(log_softmax2(yhat), y)
nll(log_softmax3(yhat), y)
ผลลัพธ์ถูกต้อง ตรงกับ PyTorch F.cross_entropy
2.5 พล็อตกราฟ¶
เราจะสมมติว่า Dog = 0, Cat = 1 และในข้อมูลตัวอย่างมีแต่ Dog (0) อย่างเดียว เราจะลองดูว่าพล็อตกราฟไล่ตั้งแต่ ความน่าจะเป็น 0-100%
เราจะสร้างข้อมูลตัวอย่างขึ้นมา ให้ y เป็น 0 จำนวน 100 ตัว แทนรูปภาพ Dog 100 รูป เราจะได้เอาไว้พล็อตกราฟ
y = torch.zeros(100)
y[:10]
yhat คือ Output ของโมเดลว่า ความน่าจะเป็นรูป Dog (Column 0) และความน่าจะเป็นรูป Cat (Column 1) เราจะไล่ข้อมูลตั้งแต่ (หมา 0% แมว 100%) ไปยัง (หมา 100% แมว 0%)
yhat = torch.zeros(100, 2)
yhat[range(0, 100), 0] = torch.arange(0., 1., 0.01)
yhat[:, 1] = 1-yhat[:, 0]
yhat[:10]
คำนวนค่าความน่าจะเป็น ของทั้ง 2 Class เอาไว้พล็อตกราฟ
classes = torch.tensor([0., 1.])
yhat_classes = yhat @ classes.t()
yhat_classes[:10]
Log ค่า Probability (ของจริงจะมาจาก Softmax ตามตัวอย่างด้านบน) เตรียมไว้เข้าสูตร
log_probs = yhat.log()
log_probs[:10]
Negative Log Likelihood
loss = -(log_probs[torch.arange(log_probs.size()[0]), y.long()])
loss[:10]
พล็อตกราฟ y, yhat, loss และ log loss¶
- ข้อมูลตัวอย่าง y ที่สมมติว่าเท่ากับ 0 อย่างเดียว (เส้นสีแดง) เทียบกับ yhat ที่ทำนายไล่ตั้งแต่ 1 ไปถึง 0 (ทายผิดไล่ไปถึงทายถูก เส้นสีเขียว)
- สังเกต Loss สีส้ม เริ่มจากซ้ายสุด Ground Truth เท่ากับ 0 (เส้นสีแดง) แต่โมเดลทายผิด ทายว่าเป็น 1 (เส้นสีเขียว) ด้วยความมั่นใจ 100% ทำให้ Loss พุ่งขึ้นถึง Infinity
- เลื่อนมาตรงกลาง Loss จะลดลงอย่างรวดเร็ว เมื่อโมเดลทายผิด แต่ไม่ได้มั่นใจเต็มร้อย
- ด้านขวา Loss ลดลงเรื่อย ๆ จนเป็น 0 เมื่อโมเดลทายถูก ว่าเป็น 0 ด้วยความมั่นใจ 100%
- Log of Loss คือเปลี่ยน Loss ที่อยู่ช่วง Infinity ถึง 0 เป็น Log Scale จะได้ช่วง Infinity ถึง -Infinity จะได้ Balance ดูง่ายขึ้น
fig,ax = plt.subplots(figsize=(9, 9))
ax.scatter(yhat[:,0].numpy(), loss.log(), label="Log of Loss")
ax.scatter(yhat[:,0].numpy(), loss, label="Loss")
ax.plot(yhat[:,0].numpy(), yhat_classes.numpy(), label="yhat", color='green')
ax.plot(yhat[:,0].numpy(), y.numpy(), label="y", color='red')
ax.grid(True)
ax.legend(loc='upper right')
3. Loss Function อื่น ๆ¶
เราจะเป็นที่ต้องเข้าใจความเป็นมา และกลไกการทำงานภายใน ของ Loss Function เนื่องจากเมื่อเราต้องการออกแบบโมเดล ในการแก้ปัญหาที่ซับซ้อนมากขึ้น เราต้องออกแบบ Loss Function ให้เข้ากับงานนั้นด้วย เช่น อาจจะเอาหลาย ๆ Loss Function เช่น Regression Loss มาผสมกัน แล้ว Weight น้ำหนัก รวมเป็น Loss ที่เราต้องการ เป็นต้น