Topik ini membahas gradient clipping, yaitu pembatasan besar gradien sebelum gradien itu digunakan untuk memperbarui bobot. Perulangan pelatihan yang dikenal sejauh ini menempatkan loss.backward() tepat sebelum optimizer.step(), dan topik ini menyisipkan satu baris di antara keduanya. Fungsi torch.nn.utils.clip_grad_norm_ dibedah melalui pengukuran norm secara langsung, kemudian manfaatnya dibuktikan pada jaringan dalam yang sengaja dibuat tidak stabil. Urutan pemanggilan yang benar di dalam perulangan pelatihan juga ditetapkan agar clipping tidak berjalan sia-sia.
Gradien yang meledak#
Pada jaringan yang dalam, gradien yang mengalir mundur dari loss ke layer-layer awal dapat membesar berlipat di setiap layer, suatu kejadian yang disebut exploding gradient. Gejala ini paling mudah muncul pada jaringan berulang yang memproses sekuens panjang dan pada jaringan dalam yang tidak menggunakan normalisasi. Akibatnya langsung terlihat pada pelatihan, yaitu langkah pembaruan bobot yang raksasa, loss yang melompat ke inf, dan model yang berhenti belajar begitu angkanya berubah menjadi nan. Gradient clipping menahan gejala itu dengan membatasi besarnya gradien sebelum digunakan.
Penyebab pertumbuhannya berada pada aturan rantai, yaitu gradien layer awal merupakan perkalian kontribusi seluruh layer di depannya, sehingga faktor yang lebih besar dari satu pada setiap layer menumpuk menjadi nilai raksasa. Batch normalization pada arsitektur modern meredam sebagian besar kejadian ini, tetapi jaringan berulang dan arsitektur tanpa normalisasi masih menyisakan celah yang sama. Pada keadaan itulah clipping menjadi pengaman yang murah, karena biayanya satu baris kode dan akibatnya mudah dipahami.
Besaran yang dipantau adalah norm L2 dari seluruh gradien model yang dirangkai menjadi satu vektor :
Nilai inilah yang dibandingkan dengan batas yang ditetapkan, dan seluruh gradien direskalasi sekaligus apabila batasnya terlampaui.
clip_grad_norm_#
torch.nn.utils.clip_grad_norm_ menerima kumpulan parameter dan nilai max_norm, lalu memeriksa norm total gradien yang tersimpan pada atribut .grad parameter tersebut. Apabila norm total melebihi max_norm, seluruh gradien dikalikan dengan faktor sehingga normnya menjadi tepat max_norm. Apabila norm berada di bawah batas, gradien dibiarkan apa adanya. Arah vektor gradien tidak pernah diubah, sehingga arah pembaruan bobot tetap sama dan hanya besarnya yang dikurangi.
Skrip berikut mengukur norm sebelum dan sesudah clipping. Loss dibesarkan seribu kali agar gradiennya jauh melebihi batas, sehingga proses reskalasinya benar-benar terjadi.
import torch
import torch.nn as nn
torch.manual_seed(0)
model = nn.Linear(4, 2)
x = torch.randn(3, 4) # shape (3, 4)
y = torch.randn(3, 2) # shape (3, 2)
# loss dibesarkan seribu kali agar gradiennya jauh melebihi max_norm
loss = nn.functional.mse_loss(model(x), y) * 1000
loss.backward()
# clip_grad_norm_ mengembalikan norm total SEBELUM clipping
total = torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
print(f"Norm sebelum clipping : {total:.4f}")
norm_sesudah = torch.norm(torch.stack([p.grad.norm() for p in model.parameters()]))
print(f"Norm sesudah clipping : {norm_sesudah:.4f}")Norm sebelum clipping : 865.2150
Norm sesudah clipping : 1.0000Norm 865.2150 jauh melampaui batas 1.0, sehingga seluruh gradien direduksi menjadi norm 1.0000 tanpa mengubah arahnya. Fungsi ini mengembalikan norm sebelum clipping, sehingga angka tersebut dapat dicatat ke log untuk memantau seberapa sering clipping aktif. Nilai max_norm=1.0 adalah titik awal yang lazim digunakan, dan nilai tersebut dapat dinaikkan bila pelatihan terasa lambat berkembang.
Fungsi clipping bawaan lain yang sering muncul adalah clip_grad_value_. Keduanya bekerja pada objek yang sama tetapi dengan satuan yang berbeda.
| Aspek | clip_grad_norm_ | clip_grad_value_ |
|---|---|---|
| Besaran yang dibatasi | norm vektor gabungan | nilai setiap elemen |
| Batas lazim | 1.0 | 0.5 sampai 1.0 |
| Arah gradien | dipertahankan | dapat berubah |
| Pemakaian umum | pilihan utama untuk stabilisasi | kasus khusus |
Urutan backward, clipping, step#
Clipping membaca dan mengubah isi .grad, sehingga posisinya wajib berada setelah loss.backward() dan sebelum optimizer.step(). Apabila dipanggil sebelum backward, atribut .grad masih kosong atau masih berisi gradien lama, dan pemanggilan itu tidak memberi efek apa pun pada gradien yang baru. Posisi yang benar dalam satu iterasi tampak sebagai berikut.
# tidak-dijalankan: fragmen merujuk variabel dari skrip lengkap di bawah
optimizer.zero_grad()
loss = criterion(model(x_batch), y_batch)
loss.backward()
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
optimizer.step()Skrip berikut membuktikan manfaat clipping pada jaringan yang mudah menjadi tidak stabil, yaitu 24 layer linear kecil tanpa normalisasi yang bobot awalnya diperbesar. Pelatihan dijalankan dua kali pada data yang sama, sekali tanpa clipping dan sekali dengan clipping.
import torch
import torch.nn as nn
torch.manual_seed(0)
def buat_model():
# jaringan dalam: 24 lapisan linear kecil tanpa normalisasi
model = nn.Sequential()
for i in range(24):
model.add_module(f"linear{i}", nn.Linear(16, 16))
model.add_module(f"relu{i}", nn.ReLU())
with torch.no_grad():
# bobot diperbesar agar gradien mudah meledak
for m in model:
if isinstance(m, nn.Linear):
m.weight.mul_(3.0)
return model
def latih(gunakan_clip):
model = buat_model()
optimizer = torch.optim.SGD(model.parameters(), lr=0.1)
x = torch.randn(32, 16) # shape (32, 16)
y = torch.randn(32, 16) # shape (32, 16)
print("--- clipping aktif ---" if gunakan_clip else "--- clipping nonaktif ---")
for epoch in range(10):
optimizer.zero_grad()
loss = nn.functional.mse_loss(model(x), y)
loss.backward()
if gunakan_clip:
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
optimizer.step()
print(f"epoch {epoch + 1:2d}: loss {loss.item():.4f}")
latih(gunakan_clip=False)
latih(gunakan_clip=True)--- clipping nonaktif ---
epoch 1: loss 1420.7220
epoch 2: loss inf
epoch 3: loss nan
epoch 4: loss nan
epoch 5: loss nan
epoch 6: loss nan
epoch 7: loss nan
epoch 8: loss nan
epoch 9: loss nan
epoch 10: loss nan
--- clipping aktif ---
epoch 1: loss 46.5683
epoch 2: loss 121.2312
epoch 3: loss 17.6611
epoch 4: loss 20.5379
epoch 5: loss 22.8233
epoch 6: loss 18.1880
epoch 7: loss 7.6651
epoch 8: loss 7.0142
epoch 9: loss 5.9996
epoch 10: loss 6.8439Tanpa clipping, loss meluap menjadi inf pada epoch kedua dan berubah menjadi nan sejak epoch ketiga, sehingga pelatihan sesungguhnya sudah berakhir. Dengan clipping, loss tetap terhitung pada seluruh epoch dan cenderung menurun meskipun tidak rata, karena arah gradien dipertahankan tetapi besarnya selalu dibatasi. Fluktuasi angka pada awal pelatihan adalah hal yang wajar, karena bobot awal yang diperbesar memang menjadikan lanskap loss sangat curam.
Kesalahan umum#
Tiga kesalahan berikut paling sering muncul saat gradient clipping dipraktikkan.
Yang pertama adalah memanggil clip_grad_norm_ sebelum loss.backward(). Pada posisi itu, .grad masih kosong atau masih menyimpan gradien iterasi sebelumnya, sehingga clipping membatasi objek yang salah. Perbaikannya, pertahankan urutan backward, clipping, lalu step.
Yang kedua adalah memilih max_norm yang terlalu kecil. Pemotongan norm yang berlangsung terus-menerus mempertahankan arah gradien, tetapi langkah pembaruannya menjadi nyaris nol, sehingga loss turun sangat lambat. Perbaikannya, mulai dari 1.0, lalu naikkan nilainya bila norm yang dicatat biasanya jauh di bawah batas.
Yang ketiga adalah menganggap clipping sebagai pengobatan penyebab. Gradien yang meledak berakar pada inisialisasi bobot, learning rate yang besar, atau arsitektur tanpa normalisasi, sedangkan clipping hanya menahan gejalanya. Perbaikannya, gunakan clipping sebagai pengaman sambil meninjau penyebab utamanya.
Ringkasan#
- Gradient clipping membatasi norm gabungan seluruh gradien pada nilai
max_norm, lazimnya 1.0, dengan menjaga arah vektor gradien tetap sama. - Posisi
clip_grad_norm_wajib di antaraloss.backward()danoptimizer.step(), karena fungsi itu membaca dan mengubah isi.grad. - Tanpa clipping, jaringan yang tidak stabil dapat menghasilkan loss inf lalu nan dalam beberapa langkah, sedangkan dengan clipping pelatihan tetap berjalan dan loss tetap terhitung.
Referensi#
- torch.nn.utils.clip_grad_norm_ — PyTorch — Dokumentasi resmi fungsi clipping norm yang digunakan pada topik ini.
- torch.nn.utils.clip_grad_value_ — PyTorch — Dokumentasi resmi clipping per elemen sebagai pembanding.
- On the difficulty of training recurrent neural networks — arXiv — Pascanu, Mikolov, dan Bengio memperkenalkan gradient clipping untuk menahan gradien yang membesar.