Fungsi loss

MSELoss dan CrossEntropyLoss beserta bentuk (shape) masukan yang dituntutnya.

Daftar isi

Fungsi loss (loss function) mengukur seberapa jauh prediksi model dari target, dan nilainya menjadi satu-satunya sumber gradien bagi pelatihan. Topik ini membahas dua fungsi loss yang lazim digunakan di awal, yaitu nn.MSELoss untuk regresi dan nn.CrossEntropyLoss untuk klasifikasi, dengan penekanan pada bentuk masukan yang dituntut masing-masingnya. Kesalahan bentuk masukan merupakan sumber error yang sering muncul pada penggunaan keduanya.

Peran fungsi loss dalam pelatihan#

Fungsi loss menerima dua tensor, yaitu prediksi model dan target, lalu mengembalikan satu nilai skalar. Skalar itulah yang nantinya diteruskan ke backward() untuk menghitung gradien terhadap seluruh parameter, sehingga pemilihan fungsi loss menentukan arah pembaruan bobot. Pemilihannya mengikuti jenis tugas: nn.MSELoss untuk regresi, yaitu tugas memprediksi nilai kontinu, dan nn.CrossEntropyLoss untuk klasifikasi multi-kelas.

nn.MSELoss menghitung rata-rata kuadrat selisih antara prediksi dan target:

ℓMSE=1n∑i=1n(yi−y^i)2\ell_{\mathrm{MSE}} = \frac{1}{n} \sum_{i=1}^{n} \left( y_i - \hat{y}_i \right)^2

dengan nn menyatakan banyak elemen pada kedua tensor. Nilai bawaan reduction="mean" menjadikan hasilnya rata-rata pada seluruh elemen, sehingga keluarannya selalu skalar. Contoh berikut menggunakan nilai yang kecil agar mudah dihitung ulang.

python nn.MSELoss pada prediksi dan target berbentuk (4,)
import torch
import torch.nn as nn

prediksi = torch.tensor([2.5, 0.0, 2.0, 7.0])
target = torch.tensor([3.0, -0.5, 2.0, 8.0])

mse = nn.MSELoss()
loss = mse(prediksi, target)

print(loss)
text Keluaran
tensor(0.3750)

Keluaran tersebut dapat dihitung ulang secara manual. Selisih prediksi terhadap target adalah -0.5, 0.5, 0.0, dan -1.0, dengan kuadrat 0.25, 0.25, 0.0, dan 1.0, sehingga rata-ratanya 0.3750.375. Pola perhitungan ini berlaku juga untuk tensor dua dimensi, misalnya prediksi dan target berbentuk (N, out_features) pada regresi multi-target, sebab rata-rata diambil atas seluruh elemen.

Bentuk masukan MSELoss#

nn.MSELoss menuntut prediksi dan target dengan bentuk yang sama persis, misalnya keduanya (N,) atau keduanya (N, 1). Syarat ini terlihat sepele, tetapi sering dilanggar tanpa disadari karena keluaran layer Linear berbentuk (N, out_features), sedangkan target yang dibuat dari daftar nilai sering berbentuk (N,).

Apabila bentuk keduanya berbeda tetapi masih dapat di-broadcast, misalnya (5, 1) dipasangkan dengan (5,), PyTorch tidak menghentikan perhitungan. Broadcasting memperluas kedua tensor menjadi (5, 5), sebuah UserWarning dicetak, dan nilai loss yang dihasilkan tidak mencerminkan jarak yang dimaksud. Peringatan semacam itu mudah terlewat karena pelatihan tampak berjalan normal, padahal arah gradiennya sudah keliru sejak iterasi pertama.

CrossEntropyLoss: logits dan indeks kelas#

nn.CrossEntropyLoss digunakan untuk klasifikasi multi-kelas. Masukan pertamanya adalah logits berbentuk (N, C) dengan C menyatakan banyak kelas, yaitu nilai mentah keluaran model tanpa softmax. Masukan keduanya adalah tensor target berbentuk (N,) bertipe int64, berisi indeks kelas mulai dari 0 sampai C-1. Untuk lima sampel dengan tiga kelas, bentuknya (5, 3) dan (5,).

Fungsi ini tidak menerima probabilitas. Softmax, yaitu fungsi yang mengubah sekumpulan nilai mentah menjadi distribusi probabilitas yang berjumlah satu, sudah diterapkan di dalamnya, ditambah logaritma natural. Untuk logits zz dan kelas target tertentu:

pc=ezc∑k=1Cezk,ℓ=−log⁡pkelas targetp_c = \frac{e^{z_c}}{\sum_{k=1}^{C} e^{z_k}}, \qquad \ell = -\log p_{\text{kelas target}}

Loss bernilai kecil apabila logit kelas target jauh lebih besar daripada kelas lain, dan mendekati log⁡C\log C apabila ketiga logits hampir sama. Kode berikut menggunakan logits acak, lalu membandingkannya dengan perhitungan manual log_softmax dan nll_loss untuk membuktikan isi di dalamnya.

python nn.CrossEntropyLoss dengan bentuk masukan yang benar
import torch
import torch.nn as nn
import torch.nn.functional as F

torch.manual_seed(0)

logits = torch.randn(5, 3)                 # (N, C) = (5, 3), tanpa softmax
target = torch.tensor([0, 2, 1, 2, 0])     # (N,) = (5,), int64

ce = nn.CrossEntropyLoss()
loss = ce(logits, target)

print(loss)
print(logits.shape, target.shape)

# padanan manual: log_softmax + nll_loss
padanan = F.nll_loss(F.log_softmax(logits, dim=1), target)
print(padanan)
print(torch.allclose(loss, padanan))
text Keluaran
tensor(1.1855)
torch.Size([5, 3]) torch.Size([5])
tensor(1.1855)
True

Nilai 1.1855 berada dekat dengan log⁡3≈1.0986\log 3 \approx 1.0986, wajar untuk logits acak yang belum dilatih. Baris terakhir bernilai True, sehingga CrossEntropyLoss benar-benar merupakan log_softmax diikuti nll_loss. Kedua langkah itu sudah tercakup di dalam fungsi, sehingga tidak ada alasan menerapkannya sendiri terlebih dahulu.

Jebakan pada bentuk masukan#

Jebakan pertama adalah menerapkan softmax sebelum logits diberikan ke CrossEntropyLoss. Perhitungan tetap berjalan tanpa error, tetapi softmax diterapkan dua kali sehingga distribusinya terdorong mendekati seragam, nilai lossnya keliru, dan gradien yang dihasilkan menyusut. Kode berikut memperlihatkan perbedaan nilainya.

python Loss benar dibandingkan loss dengan softmax ganda
import torch
import torch.nn as nn

torch.manual_seed(0)

logits = torch.randn(5, 3)
target = torch.tensor([0, 2, 1, 2, 0])
ce = nn.CrossEntropyLoss()

loss_benar = ce(logits, target)
loss_salah = ce(torch.softmax(logits, dim=1), target)   # softmax ganda

print(f"benar : {loss_benar.item():.4f}")
print(f"salah : {loss_salah.item():.4f}")
text Keluaran
benar : 1.1855
salah : 1.0518

Kedua nilai tampak mirip, sehingga kesalahannya sulit dikenali dari angkanya saja. Pelatihan tetap berjalan, loss tercatat, tetapi nilainya salah dan gradien yang mengalir ke model tidak sesuai. Perbedaan ini baru terasa pada akurasi, bukan pada pesan error.

Jebakan kedua adalah bentuk atau tipe target yang keliru. Target dua dimensi berbentuk (N, C) dibaca oleh PyTorch sebagai probabilitas kelas, sedangkan target bertipe float ditolak karena tipe yang dituntut adalah int64. Kode berikut menangkap kedua error tersebut agar pesan aslinya terlihat.

python Demonstrasi error pada target berbentuk dan bertipe keliru
import torch
import torch.nn as nn

torch.manual_seed(0)

logits = torch.randn(5, 3)
ce = nn.CrossEntropyLoss()

try:
    target = torch.tensor([[0, 1, 0], [1, 0, 0], [0, 0, 1], [1, 0, 0], [0, 1, 0]])  # (N, C), int64
    ce(logits, target)
except RuntimeError as e:
    print("RuntimeError:", str(e).splitlines()[0])

try:
    target = torch.tensor([0.0, 2.0, 1.0, 2.0, 0.0])   # (N,), float32
    ce(logits, target)
except RuntimeError as e:
    print("RuntimeError:", str(e).splitlines()[0])
text Keluaran
RuntimeError: Expected floating point type for target with class probabilities, got Long
RuntimeError: expected target dtype to be Long or Byte, but got Float

Pesan pertama menunjukkan bahwa target dua dimensi ditafsirkan sebagai probabilitas kelas, sehingga PyTorch menuntut tipe float untuk target semacam itu. Pesan kedua menegaskan bahwa target satu dimensi harus bertipe int64. Target berbentuk one-hot, yaitu representasi kelas sebagai vektor berisi nol dengan tepat satu elemen bernilai satu, tetap harus diubah terlebih dahulu menjadi indeks kelas dengan argmax(dim=1) sebelum diberikan ke fungsi loss.

Ringkasan#

  • MSELoss menuntut prediksi dan target dengan bentuk sama dan mengembalikan skalar berupa rata-rata kuadrat selisih, sedangkan bentuk yang dapat di-broadcast tetapi berbeda memicu UserWarning dan nilai yang keliru.
  • CrossEntropyLoss menerima logits berbentuk (N, C) tanpa softmax dan target berbentuk (N,) bertipe int64, lalu menerapkan log_softmax dan nll_loss di dalamnya.
  • Softmax ganda berjalan tanpa error tetapi menghasilkan loss yang salah, sedangkan target dua dimensi int64 dan target float satu dimensi memicu RuntimeError.

Referensi#