Fine-tuning adalah tahap menyesuaikan bobot pretrained pada tugas baru, dan keputusan utamanya adalah seberapa banyak jaringan yang dibuka beserta laju belajar masing-masing bagiannya. Topik ini menetapkan tiga strategi pembukaan, memeragakan pembekuan dan laju belajar diskriminatif dengan kode, dan menutup dengan tanda kapan setiap strategi dipilih. Setelah selesai, pilihan strategi fine-tuning berhenti menjadi pilihan selera dan menjadi keputusan yang dipandu ukuran data serta jarak domain.
Tiga strategi pembukaan#
Strategi pertama adalah melatih head saja, yaitu membekukan seluruh tubuh model dan hanya melatih layer keluaran baru, sehingga jumlah parameter yang bergerak paling kecil dan risiko merusak representasi yang sudah baik paling rendah. Strategi kedua membuka sebagian tubuh, biasanya layer-layer atas yang paling dekat dengan tugas, sementara layer dasar tetap beku, dan strategi ketiga membuka seluruh model dengan learning rate kecil, yang disebut full fine-tuning dan menuntut data terawasi yang paling banyak.
Pemilihan mengikuti dua besaran yang dapat dinilai sebelum melatih. Besaran pertama adalah ukuran data tugas, karena pembukaan yang luas dengan data puluhan contoh menghasilkan overfitting hampir pasti, sedangkan puluhan ribu contoh cukup untuk full fine-tuning. Besaran kedua adalah jarak domain antara pretraining dan tugas, karena klasifikasi citra umum menuju kelas citra umum lain jauh lebih dekat daripada citra satelit menuju citra medis, dan domain yang jauh menuntut pembukaan yang lebih luas agar representasinya dapat menyesuaikan.
Membekukan parameter dengan kode#
Pembekuan diwujudkan bendera requires_grad per parameter, dan autograd dari topik Mekanika pelatihan akan melewatkan parameter yang beku sepenuhnya. Kode berikut membangun encoder transformer mini, membekukan sebagian layer, dan menghitung parameter yang benar-benar dilatih, sehingga efek keputusan pembukaan terlihat sebagai angka.
import torch
import torch.nn as nn
torch.manual_seed(0)
encoder = nn.TransformerEncoder(
nn.TransformerEncoderLayer(d_model=32, nhead=4, dim_feedforward=64,
batch_first=True, norm_first=True),
num_layers=4, enable_nested_tensor=False)
head = nn.Linear(32, 2)
for p in encoder.parameters():
p.requires_grad = False
for p in encoder.layers[-2:].parameters():
p.requires_grad = True
terlatih = sum(p.numel() for p in list(encoder.parameters()) + list(head.parameters()) if p.requires_grad)
total = sum(p.numel() for p in list(encoder.parameters()) + list(head.parameters()))
print(f"parameter terlatih : {terlatih}")
print(f"parameter total : {total}")
print(f"persentase dibuka : {terlatih / total:.3f}")parameter terlatih : 17154
parameter total : 34242
persentase dibuka : 0.501Dua layer teratas dibuka dan sisanya beku, dan persentase yang dilatih sekitar sepertiga dari total, padahal layer-nya empat dari empat punya ukuran sama. Angka ini menjelaskan daya tarik pembukaan sebagian, yaitu memori dan waktu pelatihan mengecil tanpa mengorbankan seluruh kedalaman penyesuaian. Perhatikan bahwa head selalu diperlakukan terlatih, karena head adalah bagian yang lahir baru dan tidak punya pengetahuan pretrained sama sekali.
Laju belajar diskriminatif#
Layer yang berbeda membutuhkan kehati-hatian yang berbeda, karena layer dasar menyimpan pengetahuan umum yang rapuh dan layer atas menampung penyesuaian tugas. Laju belajar diskriminatif menanggapi perbedaan itu dengan memberi tiap kelompok layer learning rate sendiri, yang kecil pada dasar dan besar pada atas, dan penerapannya adalah dua grup parameter pada optimizer sebagaimana pola weight decay pada topik Regularisasi dan penalaan.
import torch
import torch.nn as nn
encoder = nn.TransformerEncoder(
nn.TransformerEncoderLayer(32, nhead=4, dim_feedforward=64,
batch_first=True, norm_first=True), num_layers=4, enable_nested_tensor=False)
head = nn.Linear(32, 2)
dasar = encoder.layers[:2].parameters()
atas = list(encoder.layers[2:].parameters()) + list(head.parameters())
opt = torch.optim.AdamW([
{"params": dasar, "lr": 1e-5},
{"params": atas, "lr": 1e-3},
])
print("laju grup dasar:", opt.param_groups[0]["lr"])
print("laju grup atas :", opt.param_groups[1]["lr"])laju grup dasar: 1e-05
laju grup atas : 0.001Selisih seratus kali antara kedua grup adalah rentang yang lazim, dan nilainya disetel pada validasi dengan kaidah yang sama seperti strategi hyperparameter. Pola ini juga menangani head yang baru lahir dengan otomatis, karena head menumpang grup atas dengan learning rate besar yang memang dibutuhkan bobot tanpa inisialisasi pretrained.
Bandingkan strategi pada data yang sama#
Keputusan strategi akhirnya diuji dengan perbandingan yang adil, yaitu tiga strategi dilatih pada data dan benih yang sama, dan hasilnya mengikuti pola yang dapat diantisipasi. Pada data kecil, head-saja unggul karena representasi tak tersentuh, pada data menengah pembukaan sebagian menang, dan pada data besar full fine-tuning menang asalkan learning rate-nya kecil. Kode berikut menjalankan perbandingan dua ujungnya pada masalah sintetis dengan dua puluh contoh berlabel, sehingga pola terkecilnya terlihat nyata.
import torch
import torch.nn as nn
torch.manual_seed(0)
X = torch.randn(120, 4, 8)
label = ((X[:, 0].sum(dim=1) - X[:, 3].sum(dim=1)) > 0).long()
X_tr, l_tr, X_va, l_va = X[:20], label[:20], X[20:], label[20:]
def latih(buka_tubuh):
encoder = nn.TransformerEncoder(
nn.TransformerEncoderLayer(d_model=8, nhead=2, dim_feedforward=16,
batch_first=True, norm_first=True), num_layers=2, enable_nested_tensor=False)
head = nn.Linear(8, 2)
for p in encoder.parameters():
p.requires_grad = buka_tubuh
params = [p for p in list(encoder.parameters()) + list(head.parameters()) if p.requires_grad]
opt = torch.optim.AdamW(params, lr=0.01)
for _ in range(400):
opt.zero_grad()
logits = head(encoder(X_tr)[:, 0])
nn.functional.cross_entropy(logits, l_tr).backward()
opt.step()
with torch.no_grad():
return float((head(encoder(X_va)[:, 0]).argmax(1) == l_va).float().mean())
print(f"head saja : akurasi = {latih(False):.3f}")
print(f"full fine-tune : akurasi = {latih(True):.3f}")head saja : akurasi = 0.570
full fine-tune : akurasi = 0.510Berkas dijalankan dengan python3 bandingkan_strategi.py, dan head-saja unggul pada jatah dua puluh contoh karena full fine-tuning dengan learning rate yang sama telah menggerakkan representasi di arah yang tidak dapat dipulihkan oleh data sebanyak itu. Perbandingan ini juga menegaskan dua kebiasaan yang menyertai strategi apa pun, yaitu benih yang sama untuk semua kandidat dan evaluasi pada data validasi yang identik.
Tanda-tanda pilihan yang salah#
Strategi yang keliru punya gejala yang khas dan dapat dibaca dari kurvanya. Head-saja yang underfit menunjukkan train dan validasi yang sama-sama rendah kinerjanya, dan obatnya membuka layer atas. Full fine-tuning yang overfit menunjukkan train yang sangat baik dengan validasi yang memburuk cepat, dan obatnya menutup kembali atau menurunkan learning rate tubuhnya. Kedua gejala itulah yang dipantau pada kurva pembelajaran sebagaimana diajarkan modul Disiplin pelatihan, dan perubahan strategi diterapkan satu per satu agar penyebabnya tetap terbaca.
Ringkasan#
- Tiga strategi pembukaan, yaitu head saja, sebagian tubuh, dan full fine-tuning, dipilih dari ukuran data dan jarak domain.
- Pembekuan diwujudkan
requires_grad=Falseper parameter, dan jumlah parameter terlatih dapat dihitung langsung dari bendera itu. - Laju belajar diskriminatif membagi layer menjadi grup dengan learning rate berbeda, kecil pada dasar dan besar pada atas beserta head.
- Pada data dua puluh contoh, head-saja mengungguli full fine-tuning, dan gejala kesalahan strategi terbaca dari kurva train-validasi.
Referensi#
- Fine-Tuning BERT for Text Classification — Hugging Face Tutorials — Panduan ekosistem yang memeragakan pembukaan layer pada tugas nyata.
- ULMFiT: Universal Language Model Fine-tuning — ACL Anthology — Makalah yang memperkenalkan laju belajar diskriminatif dan pembekuan bertahap.
- Transfer Learning — Dive into Deep Learning — Bab yang menetapkan pola head-saja dan penyesuaian bobot pretrained.