Epoch dan pelacakan kemajuan

Melacak kurva loss antara epoch dan menampilkan kemajuan dengan tqdm.

Daftar isi

Perulangan pelatihan yang benar belum tentu pelatihan yang berhasil, dan satu-satunya cara menilainya adalah mengamati kurva loss antar epoch. Topik ini membahas cara mencatat loss per epoch secara deterministik, cara menampilkan kemajuan pelatihan dengan tqdm, dan cara membaca laju penurunan loss sebagai alat diagnosis sebelum pelatihan penuh dijalankan. Setelah membaca topik ini, perulangan pelatihan bukan lagi kotak hitam, karena kemajuannya terbaca dari angka yang dicetaknya sendiri.

Satu epoch menelusuri seluruh data#

Satu epoch adalah satu penelusuran penuh atas seluruh data pelatihan, dan jumlah langkah pembaruan bobot dalam satu epoch sama dengan jumlah batch, kelompok sampel yang diproses bersama dalam satu langkah. Pelatihan sering membutuhkan puluhan hingga ratusan epoch, sehingga mencatat satu angka per epoch adalah kerangka pelacakan yang paling sederhana sekaligus paling mudah dibandingkan. Skrip berikut melatih regresi linear pada 128 sampel selama delapan epoch, lalu mencetak rata-rata loss setiap epoch beserta total penurunannya.

python
import torch
import torch.nn as nn
from torch.utils.data import TensorDataset, DataLoader

torch.manual_seed(0)

n = 128
x = torch.linspace(0.0, 1.0, n).unsqueeze(1)
y = 3.0 * x + 0.5 + 0.08 * torch.randn(n, 1)
loader = DataLoader(TensorDataset(x, y), batch_size=32, shuffle=True)

model = nn.Linear(1, 1)
optimizer = torch.optim.SGD(model.parameters(), lr=0.3)
loss_fn = nn.MSELoss()
riwayat = []

for epoch in range(8):
    total = 0.0
    for xb, yb in loader:
        optimizer.zero_grad()
        loss = loss_fn(model(xb), yb)
        loss.backward()
        optimizer.step()
        total += loss.item()
    rata = total / len(loader)
    riwayat.append(rata)
    print(f"epoch {epoch + 1}: loss {rata:.4f}")

penurunan = (riwayat[0] - riwayat[-1]) / riwayat[0] * 100
print(f"loss turun {penurunan:.1f}% dari epoch 1 ke epoch 8")
text Keluaran
epoch 1: loss 2.7917
epoch 2: loss 0.2501
epoch 3: loss 0.1818
epoch 4: loss 0.1331
epoch 5: loss 0.0989
epoch 6: loss 0.0741
epoch 7: loss 0.0530
epoch 8: loss 0.0420
loss turun 98.5% dari epoch 1 ke epoch 8

Daftar riwayat menyimpan satu angka per epoch, dan angka itulah kurva loss dalam bentuknya yang paling sederhana. Lompatan terbesar terjadi antara epoch pertama dan kedua, yaitu dari 2.7917 ke 0.2501, kemudian penurunannya melambat secara konsisten dari epoch ke epoch. Bentuk penurunan yang melambat seperti ini adalah perilaku normal, karena perbaikan yang tersisa memang makin kecil seiring model menghampiri batas terbaiknya. Tanpa grafik sekalipun, kolom angka yang rapi sudah menunjukkan bentuk kurvanya.

Menampilkan kemajuan dengan tqdm#

Pencetakan per epoch bekerja baik untuk pelatihan pendek, tetapi pelatihan panjang membutuhkan umpan balik yang lebih sering. tqdm, pustaka Python untuk bar kemajuan, membungkus perulangan mana pun dan menampilkan berapa persen pekerjaan yang selesai beserta perkiraan waktu sisa. Bar tersebut memperbarui dirinya di tempat yang sama di terminal, sehingga keluarannya interaktif dan tidak dapat direkam sebagai teks statis, apalagi dibandingkan antara eksekusi. Berikut pola penggunaannya pada perulangan pelatihan, dengan bar luar untuk epoch dan bar dalam untuk batch.

python Kemajuan epoch dan batch dengan tqdm
# tidak-dijalankan: bar tqdm menimpa dirinya di terminal, sehingga keluarannya tidak dapat direkam sebagai teks statis
from tqdm import tqdm

for epoch in tqdm(range(8), desc="epoch"):
    total = 0.0
    for xb, yb in tqdm(loader, desc="batch", leave=False):
        optimizer.zero_grad()
        loss = loss_fn(model(xb), yb)
        loss.backward()
        optimizer.step()
        total += loss.item()
    print(f"epoch {epoch + 1}: loss {total / len(loader):.4f}")

Argumen desc memberi nama pada bar, sedangkan leave=False menghapus bar batch setiap ganti epoch agar layar tidak penuh sisa bar. Bar yang berjalan menampilkan persentase pekerjaan yang selesai, jumlah iterasi, kecepatan iterasi per detik, dan perkiraan waktu sisa. Seluruh kolom itu bersifat tampilan semata, sehingga mengganti range(8) dengan tqdm(range(8)) tidak mengubah nilai apa pun yang dihitung di dalam perulangan. Aturan praktisnya, tetapkan satu sumber kebenaran untuk angka, yaitu print per epoch atau pencatat eksperimen, dan perlakukan tqdm sebagai layar kemajuan semata.

Laju penurunan sebagai alat diagnosis#

Kurva loss bukan sekadar laporan, melainkan alat diagnosis yang paling murah. Perbandingan berikut melatih model yang sama pada data yang sama dengan dua nilai learning rate yang berbeda, yakni besarnya langkah pembaruan bobot, lalu mencatat loss pada empat titik langkah yang sama.

python
import torch
import torch.nn as nn

torch.manual_seed(0)

x = torch.linspace(0.0, 1.0, 64).unsqueeze(1)
y = 2.0 * x + 1.0

def latih(lr):
    torch.manual_seed(0)
    model = nn.Linear(1, 1)
    opt = torch.optim.SGD(model.parameters(), lr=lr)
    loss_fn = nn.MSELoss()
    riwayat = []
    for langkah in range(40):
        opt.zero_grad()
        loss = loss_fn(model(x), y)
        loss.backward()
        opt.step()
        riwayat.append(loss.item())
    return riwayat

a = latih(0.3)
b = latih(0.001)
for i in (0, 9, 19, 39):
    print(f"langkah {i + 1:>2}: lr=0.3 {a[i]:.4f}   lr=0.001 {b[i]:.4f}")
text Keluaran
langkah  1: lr=0.3 2.4995   lr=0.001 2.4995
langkah 10: lr=0.3 0.0771   lr=0.001 2.3947
langkah 20: lr=0.3 0.0336   lr=0.001 2.2837
langkah 40: lr=0.3 0.0064   lr=0.001 2.0779

Kedua jalur memulai dari loss yang identik, tetapi jalur dengan learning rate 0.3 turun ke 0.0064 dalam empat puluh langkah, sedangkan jalur dengan learning rate 0.001 nyaris tidak bergerak dan masih di 2.0779. Laju penurunan yang terlalu datar pada awal pelatihan, seperti kurva yang masih bertahan di dekat nilai awal setelah puluhan langkah, patut dicurigai sebelum pelatihan dibiarkan berjalan lama. Tiga bentuk kurva mencakup sebagian besar kasus.

  • Kurva menurun lalu mendatar di dekat lantai derau, bentuk yang sehat dan menandakan pelatihan dapat dilanjutkan.
  • Kurva mendatar terlalu cepat di dekat nilai awal, penanda learning rate terlalu kecil, baris pelatihan yang salah, atau model yang tidak cocok dengan data.
  • Kurva berayun atau justru naik, penanda learning rate terlalu besar atau gradien yang tercemar, misalnya akibat zero_grad() yang tidak dipanggil.

Membaca kurva pada tingkat ini membuat keputusan berikutnya terukur, apakah melanjutkan epoch, menaikkan learning rate, atau berhenti dan mencari bug terlebih dahulu.

Kesalahan umum#

Tiga kesalahan berikut paling sering muncul ketika kurva loss dicatat.

  1. Membagi akumulasi loss dengan jumlah sampel alih-alih jumlah batch. Variabel total adalah penjumlahan rata-rata loss per batch, sehingga pembagi yang benar adalah len(loader). Pembagian dengan len(dataset) menghasilkan angka yang selalu terlihat kecil dan tidak sebanding antara konfigurasi batch.
  2. Membandingkan kurva antara eksekusi yang berbeda seed, batch size, atau learning rate secara bersamaan. Perubahan yang bertumpuk membuat penyebab pergeseran kurva tidak dapat ditentukan. Ubah satu variabel dalam satu eksekusi, dan catat seed beserta konfigurasi bersama kurvanya.
  3. Menyimpan angka loss sebagai tensor yang masih terhubung graf komputasi. Riwayat yang memuat loss mentah menarik seluruh grafnya, sehingga memori membengkak pada pelatihan panjang. Panggil .item() pada saat mencatat agar yang tersimpan berupa angka Python biasa.

Ringkasan#

  • Satu epoch adalah satu penelusuran penuh data pelatihan, dan rata-rata loss per epoch diperoleh dengan membagi akumulasi loss batch oleh jumlah batch.
  • tqdm menampilkan bar kemajuan yang interaktif di terminal, sehingga berguna untuk mengamati jalannya pelatihan tetapi tidak cocok sebagai catatan yang direkam dan dibandingkan.
  • Laju penurunan loss antara epoch adalah alat diagnosis, dengan tiga bentuk utama, yaitu menurun lalu mendatar, mendatar di dekat nilai awal, dan berayun atau naik.

Referensi#