Batch normalization

Apa yang distabilkan batch normalization dan perbedaan perilaku mode train dan mode eval.

Daftar isi

Topik ini membahas nn.BatchNorm2d, yaitu layer yang menstabilkan distribusi aktivasi antara layer jaringan CNN. Fokus utamanya adalah perbedaan perilaku mode train dan mode eval, sebab di situlah kesalahan yang paling sering terjadi.

Apa yang distabilkan#

Distribusi aktivasi yang masuk ke sebuah layer tidak diam selama pelatihan. Setiap pembaruan bobot pada layer-layer sebelumnya mengubah nilai yang tiba di layer berikutnya, sehingga layer tersebut bekerja dengan masukan yang terus bergeser. Batch normalization meredam pergeseran ini dengan menormalkan aktivasi per kanal, yaitu menghitung rata-rata dan varians dari batch, lalu menggeser dan menskalakan hasilnya.

Untuk aktivasi xx pada satu kanal, normalisasinya adalah x^=(x−μB)/σB2+ϵ\hat{x} = (x - \mu_B) / \sqrt{\sigma_B^2 + \epsilon} dengan μB\mu_B dan σB2\sigma_B^2 berturut-turut rata-rata serta varians batch, kemudian keluarannya adalah y=γx^+βy = \gamma \hat{x} + \beta. Nilai ϵ\epsilon, yang bawaannya 10−510^{-5}, menjaga pembagian tetap aman. Pasangan γ\gamma dan β\beta adalah parameter yang dipelajari, sehingga layer tetap dapat membentuk distribusi keluaran sesuai kebutuhannya, termasuk mengembalikannya seperti semula bila itu yang paling menguntungkan.

Dengan masukan yang distribusinya stabil, layer berikutnya tidak perlu menyesuaikan diri terhadap pergeseran besar, dan pelatihan menjadi lebih tahan terhadap pilihan learning rate. Pada jaringan yang dalam, efek yang sering terlihat adalah konvergensi yang lebih cepat dibandingkan dengan arsitektur yang sama tanpa normalisasi.

Mode train dan mode eval#

Perilaku nn.BatchNorm2d bergantung pada mode model. Pada mode train, normalisasi menggunakan statistik batch yang sedang diproses, dan dua buffer bernama running_mean serta running_var diperbarui sebagai rata-rata bergerak. Dengan nilai bawaan momentum 0.1, statistik berjalan yang baru adalah 0.9 kali statistik lama ditambah 0.1 kali statistik batch saat itu. Pada mode eval, keluaran dihitung dari statistik berjalan itu, dan tidak ada buffer yang diperbarui.

Konsekuensinya langsung terasa, yaitu keluaran model untuk masukan yang sama berbeda antara kedua mode. Inilah alasan teknis pemanggilan model.train() dan model.eval() di dalam perulangan pelatihan. Evaluasi memanggil model.eval() agar setiap batch dihitung dengan statistik yang sama, bukan dengan statistik batch masing-masing.

Demonstrasi statistik berjalan#

Perbedaan kedua mode dapat dibuktikan dengan program kecil. Satu batch tetap diberikan lima kali kepada layer batch normalization dalam mode train agar statistik berjalannya terisi, kemudian keluaran kedua mode dibandingkan untuk masukan yang sama.

python uji-batchnorm.py
import torch
import torch.nn as nn

torch.manual_seed(0)

# pasangan layer yang akan diperiksa: konvolusi, lalu batch normalization
conv = nn.Conv2d(1, 4, kernel_size=3, padding=1)
bn = nn.BatchNorm2d(4)

# satu batch tetap yang diberikan berulang kali
torch.manual_seed(1)
x = torch.rand(8, 1, 8, 8)
akt = conv(x)  # aktivasi masukan bagi batch normalization

with torch.no_grad():
    print("awal   running_mean:", bn.running_mean)
    print("awal   running_var :", bn.running_var)
    for _ in range(5):
        y_train = bn(akt)  # mode train: statistik batch, statistik berjalan diperbarui
    print("sesudah 5 forward running_mean:", bn.running_mean)
    print("sesudah 5 forward running_var :", bn.running_var)

    # mode eval: statistik berjalan yang dipakai, bukan statistik batch
    bn.eval()
    y_eval = bn(akt)
    selisih = (y_train - y_eval).abs().max().item()
    print(f"selisih maksimum train vs eval: {selisih:.4f}")
    print("y_train[0, 0, 0, :4]:", y_train[0, 0, 0, :4])
    print("y_eval [0, 0, 0, :4]:", y_eval[0, 0, 0, :4])
text Keluaran
awal   running_mean: tensor([0., 0., 0., 0.])
awal   running_var : tensor([1., 1., 1., 1.])
sesudah 5 forward running_mean: tensor([-0.1039,  0.0312,  0.1444, -0.1519])
sesudah 5 forward running_var : tensor([0.6001, 0.5996, 0.6067, 0.6167])
selisih maksimum train vs eval: 2.6881
y_train[0, 0, 0, :4]: tensor([ 0.8151, -0.2172,  1.0152, -0.1121])
y_eval [0, 0, 0, :4]: tensor([-0.0328, -0.2363,  0.0066, -0.2156])

Keluarannya memperlihatkan tiga hal. Buffer yang mula-mula berisi nol dan satu bergeser mendekati statistik batch setelah lima forward, karena momentumnya 0.1. Keluaran mode train dinormalisasi dengan statistik batch, sedangkan keluaran mode eval menggunakan statistik berjalan, sehingga selisih maksimumnya mencapai 2.6881 untuk masukan yang sama. Empat elemen pertama pada kedua keluaran menegaskan bahwa perbedaannya nyata, bukan sekadar selisih presisi.

Kesalahan umum#

Batch yang terlalu kecil membuat statistik batch tidak stabil, karena rata-rata dan variansnya dihitung dari sedikit sampel. Pada kasus ekstrem satu nilai per kanal, mode train menolak menghitungnya sama sekali. Contoh berikut memicu error tersebut dengan batch berukuran satu dan ukuran spasial 1x1.

python Contoh: batch normalization pada batch berukuran satu
import torch
import torch.nn as nn

bn = nn.BatchNorm2d(4)
x = torch.rand(1, 4, 1, 1)  # satu sampel, spasial 1x1
try:
    bn(x)  # masih mode train
except ValueError as e:
    print("ValueError:", e)
text Keluaran
ValueError: Expected more than 1 value per channel when training, got input size torch.Size([1, 4, 1, 1])

Perbaikannya adalah memperbesar batch, atau berpindah ke mode eval apabila perhitungan memang berupa inferensi. Untuk arsitektur yang memang bekerja dengan batch kecil, layer alternatif seperti nn.GroupNorm menormalkan aktivasi tanpa bergantung pada batch sama sekali. Kesalahan kedua yang sering terjadi adalah lupa memanggil model.train() kembali setelah evaluasi, sehingga pelatihan berlanjut dengan statistik beku dan loss tidak turun. Pembagian mode sebaiknya ditulis sebagai pola baku di awal dan akhir setiap perulangan, bukan diletakkan sesuai keinginan.

Ringkasan#

  • Batch normalization menormalkan aktivasi per kanal menggunakan statistik batch, lalu menskalakan dan menggesernya dengan parameter yang dipelajari.
  • Mode train menggunakan statistik batch dan memperbarui running_mean serta running_var, sedangkan mode eval menggunakan statistik berjalan tanpa pembaruan.
  • Keluaran untuk masukan yang sama berbeda antara kedua mode, sehingga evaluasi wajib didahului model.eval().
  • Batch kecil membuat statistik batch tidak stabil, dan batch dengan satu nilai per kanal ditolak pada mode train.

Referensi#