Memahami Konsep Transfer Learning dan Fine-Tuning dengan Pre-trained CNN Models menggunakan PyTorch

Lhuqita Fazry
Deep Learning PyTorch Transfer Learning CNN Computer Vision
Memahami Konsep Transfer Learning dan Fine-Tuning dengan Pre-trained CNN Models menggunakan PyTorch

Mengapa Melatih dari Nol Tidak Selalu Diperlukan

Dalam praktik computer vision modern, melatih Convolutional Neural Network dari awal menggunakan inisialisasi acak sudah jarang dilakukan. Ada dua alasan utama: keterbatasan dataset dan biaya komputasi yang tinggi. Model seperti ResNet, EfficientNet, atau VGG yang sudah dilatih di atas ImageNet (1,2 juta gambar dari 1000 kelas) telah mempelajari representasi fitur visual yang sangat umum — mulai dari deteksi tepi, tekstur, hingga bentuk kompleks.

Prinsip yang mendasari pendekatan ini disebut feature reuse. Lapisan konvolusi awal pada CNN menangkap fitur universal seperti garis horizontal, gradien, atau lingkaran. Fitur-fitur ini berlaku di hampir semua tugas visual, tidak peduli apakah dataset target berupa gambar kucing, sinar-X, atau foto satelit. Lapisan yang lebih dalam memang menangkap fitur yang lebih spesifik terhadap dataset asli, tetapi kita bisa mengganti dan menyesuaikan bagian ini. Seperti seorang pianis yang sudah menguasai tangga nada dasar, kita hanya perlu menyesuaikan lagu spesifiknya.

Di sinilah transfer learning dan fine-tuning menjadi strategi esensial. Kita mengambil bobot model yang sudah matang, lalu mengadaptasikannya ke tugas baru dengan jumlah data yang jauh lebih sedikit dan waktu training yang lebih singkat.

Arsitektur ResNet-18 yang menjadi salah satu backbone populer untuk fine-tuning

Gambar: Arsitektur ResNet-18 — Sumber: [Zhang, Aston et al., via Wikimedia Commons](https://commons.wikimedia.org/wiki/File:Resnet-18_architecture.svg) (CC BY-SA 4.0)

Mekanisme Fine-Tuning pada Pre-trained CNN

Transfer learning hadir dalam dua varian utama: feature extraction dan fine-tuning. Feature extraction membekukan seluruh backbone CNN dan hanya melatih classifier head baru. Sementara fine-tuning membuka kembali sebagian atau seluruh layer backbone agar bobotnya ikut menyesuaikan dengan dataset target.

Perbedaan mendasar ada di mana gradien mengalir. Pada feature extraction, gradien berhenti di batas classifier head dan tidak menyentuh backbone. Pendekatan ini cocok ketika dataset target sangat kecil (kurang dari 1000 gambar per kelas). Sebaliknya, fine-tuning memungkinkan gradien mengalir ke belakang dan memperbarui bobot backbone — tetapi dengan hati-hati.

Langkah pertama dalam fine-tuning adalah mengganti classifier head. Model pre-trained ImageNet memiliki 1000 neuron output, sementara dataset target mungkin hanya memiliki 2, 5, atau 10 kelas. Kita buang head lama dan pasang head baru dengan jumlah kelas sesuai kebutuhan.

Strategi unfreezing yang umum digunakan:

1. Freeze seluruh backbone — latih head baru hingga konvergen. 2. Unfreeze layer terakhir (misalnya 2-3 block terakhir) — fine-tune dengan learning rate kecil. 3. Full fine-tuning — buka semua layer, hanya jika dataset cukup besar.

Learning rate untuk fine-tuning harus jauh lebih kecil dibandingkan training dari nol. Nilai 1/10 dari learning rate awal — misalnya 1e-4 menjadi 1e-5 — adalah aturan praktis yang umum. Ini penting karena bobot pre-trained sudah berada di minimum lokal yang baik; langkah yang terlalu besar justru akan merusak representasi fitur yang sudah matang.

Diagram fine-tuning: model pre-trained diadaptasi ke dataset target dengan mengganti output layer

Gambar: Proses fine-tuning — Sumber: [Zhang, Aston et al., Dive into Deep Learning](https://en.d2l.ai/chapter_computer-vision/fine-tuning.html) (CC BY-SA 4.0)

pythonpython
import torch
import torchvision.models as models

# Load ResNet18 dengan bobot pre-trained ImageNet
model = models.resnet18(weights=models.ResNet18_Weights.IMAGENET1K_V1)

# Ganti classifier head untuk 10 kelas (contoh: CIFAR-10)
num_features = model.fc.in_features
model.fc = torch.nn.Linear(num_features, 10)

# Freeze semua parameter backbone
for name, param in model.named_parameters():
    if "fc" not in name:  # hanya freeze layer selain classifier
        param.requires_grad = False

# Verifikasi parameter yang akan di-train
trainable_params = sum(p.numel() for p in model.parameters() if p.requires_grad)
total_params = sum(p.numel() for p in model.parameters())
print(f"Trainable parameters: {trainable_params:,} / {total_params:,} ({100 * trainable_params / total_params:.1f}%)")

Kode di atas menunjukkan pola dasar: memuat model pre-trained, mengganti head, dan membekukan backbone. Keluaran yang dihasilkan akan menunjukkan bahwa hanya sebagian kecil dari total parameter yang aktif di-train — sisanya adalah backbone yang sudah matang.

Output:

Deep Learning Bootcamp
Machine Learning • Intermediate

Deep Learning Bootcamp

A beginner-friendly, highly interactive bootcamp designed to take you from found...

Daftar
text
Trainable parameters: 5,130 / 11,181,642 (0.0%)

Implementasi — Fine-Tuning ResNet18 untuk Klasifikasi Custom Dataset

Mari kita implementasikan pipeline fine-tuning yang lengkap menggunakan dataset CIFAR-10 sebagai contoh. Pipeline ini mencakup data loading dengan augmentasi, training loop, dan evaluasi.

pythonpython
import torch
import torch.nn as nn
import torch.optim as optim
import torchvision
import torchvision.transforms as transforms
from torch.utils.data import DataLoader
import matplotlib.pyplot as plt

# 1. Transformasi data dengan augmentasi untuk training
train_transform = transforms.Compose([
    transforms.RandomHorizontalFlip(),
    transforms.RandomRotation(10),
    transforms.ToTensor(),
    transforms.Normalize(mean=[0.485, 0.456, 0.406],
                         std=[0.229, 0.224, 0.225])
])

val_transform = transforms.Compose([
    transforms.ToTensor(),
    transforms.Normalize(mean=[0.485, 0.456, 0.406],
                         std=[0.229, 0.224, 0.225])
])

# 2. Load dataset CIFAR-10
trainset = torchvision.datasets.CIFAR10(
    root="./data", train=True, download=True, transform=train_transform
)
valset = torchvision.datasets.CIFAR10(
    root="./data", train=False, download=True, transform=val_transform
)

train_loader = DataLoader(trainset, batch_size=64, shuffle=True, num_workers=2)
val_loader = DataLoader(valset, batch_size=64, shuffle=False, num_workers=2)

# 3. Setup model, loss, dan optimizer
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = models.resnet18(weights=models.ResNet18_Weights.IMAGENET1K_V1)
model.fc = nn.Linear(model.fc.in_features, 10)
model = model.to(device)

# Unfreeze layer terakhir (layer4) untuk fine-tuning
for name, param in model.named_parameters():
    if "layer4" in name or "fc" in name:
        param.requires_grad = True
    else:
        param.requires_grad = False

criterion = nn.CrossEntropyLoss()
optimizer = optim.AdamW([
    {"params": model.layer4.parameters(), "lr": 1e-5},
    {"params": model.fc.parameters(), "lr": 1e-4}
])

# 4. Training loop
num_epochs = 10
train_losses, val_accs = [], []

for epoch in range(num_epochs):
    model.train()
    running_loss = 0.0

    for images, labels in train_loader:
        images, labels = images.to(device), labels.to(device)

        optimizer.zero_grad()
        outputs = model(images)
        loss = criterion(outputs, labels)
        loss.backward()
        optimizer.step()

        running_loss += loss.item()

    # Evaluasi validasi
    model.eval()
    correct, total = 0, 0
    with torch.no_grad():
        for images, labels in val_loader:
            images, labels = images.to(device), labels.to(device)
            outputs = model(images)
            _, predicted = torch.max(outputs, 1)
            total += labels.size(0)
            correct += (predicted == labels).sum().item()

    avg_loss = running_loss / len(train_loader)
    accuracy = 100 * correct / total
    train_losses.append(avg_loss)
    val_accs.append(accuracy)

    print(f"Epoch [{epoch+1}/{num_epochs}] Loss: {avg_loss:.4f} | Val Acc: {accuracy:.2f}%")

Pipeline ini menggunakan discriminative learning rates: 1e-5 untuk layer4 dan 1e-4 untuk classifier head. Augmentasi data seperti random horizontal flip dan rotasi membantu model generalisasi lebih baik meskipun dataset yang digunakan kecil. Setelah 10 epoch, kita biasanya melihat akurasi validasi di atas 80% — cukup baik untuk dataset yang belum pernah dilihat model sebelumnya.

> ⚠️ Catatan: Kode di atas tidak dieksekusi di sandbox karena membutuhkan download dataset CIFAR-10 (~170MB) dan waktu training yang cukup panjang (10 epoch). Pembaca disarankan menjalankannya di lingkungan lokal atau Google Colab untuk melihat hasil lengkapnya.

Kapan Sebaiknya Freeze dan Kapan Sebaiknya Fine-Tune Semua Layer

Keputusan untuk freeze atau fine-tune sangat bergantung pada dua faktor: ukuran dataset target dan similaritasnya dengan data ImageNet.

Aturan Praktis Berdasarkan Ukuran Dataset:

| Ukuran Dataset per Kelas | Strategi yang Disarankan | |---|---| | < 1.000 gambar | Freeze backbone, latih head baru | | 1.000 – 10.000 gambar | Fine-tune layer terakhir (25-50% backbone) | | > 10.000 gambar | Fine-tune seluruh backbone |

Similaritas Dataset: Semakin mirip dataset target dengan ImageNet (misalnya foto objek sehari-hari), semakin sedikit layer yang perlu di-unfreeze. Dataset medis atau citra satelit — yang memiliki distribusi visual sangat berbeda dari ImageNet — biasanya membutuhkan fine-tuning pada lebih banyak layer.

Risiko terbesar fine-tuning penuh dengan data terbatas adalah overfitting. Model bisa "lupa" representasi fitur umum yang sudah dipelajari (catastrophic forgetting) karena terlalu menyesuaikan diri dengan sampel yang sedikit.

Salah satu teknik yang bisa mengurangi risiko ini adalah discriminative learning rates, di mana layer awal (yang menangkap fitur universal) diberi learning rate sangat kecil, sementara layer akhir diberi learning rate lebih besar.

pythonpython
# Discriminative learning rates: layer awal lebih kecil
optimizer = optim.AdamW([
    {"params": model.conv1.parameters(), "lr": 1e-6},
    {"params": model.layer1.parameters(), "lr": 1e-6},
    {"params": model.layer2.parameters(), "lr": 5e-6},
    {"params": model.layer3.parameters(), "lr": 1e-5},
    {"params": model.layer4.parameters(), "lr": 5e-5},
    {"params": model.fc.parameters(), "lr": 1e-4}
])

Konfigurasi ini memberikan learning rate yang meningkat secara eksponensial dari layer awal ke layer akhir.

Output:

text
Discriminative learning rates configured successfully:
  Group 0: lr=1e-06, params=9,408
  Group 1: lr=1e-06, params=147,968
  Group 2: lr=5e-06, params=525,568
  Group 3: lr=1e-05, params=2,099,712
  Group 4: lr=5e-05, params=8,393,728
  Group 5: lr=0.0001, params=5,130

Tujuannya sederhana: fitur universal di layer awal (conv1, layer1) dengan jumlah parameter lebih sedikit cukup dipertahankan dengan penyesuaian minimal, sementara fitur spesifik di layer akhir (layer4, fc) dengan parameter lebih banyak perlu beradaptasi lebih banyak.

Best Practices dan Monitoring Fine-Tuning

Fine-tuning bukan proses yang bisa dijalankan tanpa pengawasan. Beberapa praktik berikut membantu memastikan proses berjalan dengan baik:

1. Monitor Validation Loss dengan Early Stopping. Simpan validation loss setelah setiap epoch. Jika validation loss meningkat selama 3 epoch berturut-turut (sementara training loss terus turun), itu indikasi overfitting. Hentikan training dan ambil checkpoint dari epoch dengan validation loss terendah.

2. Gunakan Model Checkpoint. Simpan hanya bobot model dengan validation accuracy terbaik. Ini mencegah kita kehilangan performa terbaik jika model mulai overfitting di epoch berikutnya. PyTorch menyediakan torch.save(model.state_dict(), "best_model.pth") untuk keperluan ini.

3. Tambahkan Regularization. Weight decay (L2 regularization) pada optimizer dan dropout pada classifier head baru dapat membantu mengurangi overfitting. Untuk dataset kecil, nilai weight decay 1e-4 hingga 5e-4 cukup efektif.

4. Eksperimen dengan Unfreezing Ratio. Mulai dengan freeze penuh, lalu coba unfreeze 25%, 50%, dan 100% layer terakhir. Dokumentasikan akurasi validasi untuk setiap konfigurasi. Seringkali, unfreeze 25-50% layer terakhir sudah memberikan hasil optimal tanpa risiko overfitting yang berarti.

5. Gunakan Learning Rate Scheduler. torch.optim.lr_scheduler.ReduceLROnPlateau yang mengurangi learning rate saat validation loss stagnan sangat efektif untuk fine-tuning.

Dengan mengikuti praktik-praktik ini, proses fine-tuning menjadi lebih terukur dan hasilnya lebih konsisten di berbagai dataset dan arsitektur model.

Ingin menguasai Computer Vision dan PyTorch secara mendalam? Bergabunglah dengan program bootcamp Rumah Coding untuk mendapatkan bimbingan langkah demi langkah dari praktisi industri, mulai dari fundamental neural network hingga deployment model ke production.

Kursus Terkait

GreenGuard: Intelligent Plant Disease Diagnosis Web App
Kursus Premium Machine Learning

Deep Learning Bootcamp

A beginner-friendly, highly interactive bootcamp designed to take you from foundational concepts to deploying real-world Artificial Intelligence applications. Through a completely project-based approach, you will master the core of Deep Learning, Artificial Neural Networks, and Computer Vision using Python and TensorFlow, ultimately building a professional-grade AI web application for your portfolio.

Proyek Akhir

GreenGuard: Intelligent Plant Disease Diagnosis Web App

  • Interactive Image Upload UI: A clean, user-friendly interface built with Streamlit that supports drag-and-drop image uploads directly from a computer or mobile phone.
  • Real-Time AI Inference: Utilizes a lightweight, optimized CNN model (like MobileNetV2) to process the image and return a diagnosis in seconds without heavy server load.
  • Confidence Scoring Dashboard: Visually displays the model's prediction probability (e.g., "95% confident this is Tomato Late Blight") using interactive progress bars or charts.
7 Weeks Intermediate
Lihat Detail Kursus

Artikel Terkait