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:
dengan 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.
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)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 . 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 dan kelas target tertentu:
Loss bernilai kecil apabila logit kelas target jauh lebih besar daripada kelas lain, dan mendekati 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.
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))tensor(1.1855)
torch.Size([5, 3]) torch.Size([5])
tensor(1.1855)
TrueNilai 1.1855 berada dekat dengan , 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.
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}")benar : 1.1855
salah : 1.0518Kedua 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.
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])RuntimeError: Expected floating point type for target with class probabilities, got Long
RuntimeError: expected target dtype to be Long or Byte, but got FloatPesan 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#
MSELossmenuntut prediksi dan target dengan bentuk sama dan mengembalikan skalar berupa rata-rata kuadrat selisih, sedangkan bentuk yang dapat di-broadcast tetapi berbeda memicuUserWarningdan nilai yang keliru.CrossEntropyLossmenerima logits berbentuk (N, C) tanpa softmax dan target berbentuk (N,) bertipe int64, lalu menerapkanlog_softmaxdannll_lossdi dalamnya.- Softmax ganda berjalan tanpa error tetapi menghasilkan loss yang salah, sedangkan target dua dimensi int64 dan target float satu dimensi memicu
RuntimeError.
Referensi#
- Deep Learning with PyTorch: A 60 Minute Blitz — PyTorch — Tutorial resmi yang membahas pemilihan fungsi loss pada bagian Optimizing Model Parameters.
- PyTorch Documentation — PyTorch
— Dokumentasi resmi
torch.nnuntukMSELossdanCrossEntropyLoss.