Arsitektur klasik

Membangun ulang jaringan kecil bergaya VGG dari blok konvolusi.

Daftar isi

Topik ini menyusun konvolusi, ReLU, dan pooling menjadi jaringan kecil bergaya VGG untuk citra 28x28. Jalur shape dari masukan hingga keluaran ditelusuri blok demi blok, dan jumlah parameter totalnya dihitung, sehingga pola perancangan arsitektur klasik terbaca sebagai satu kesatuan.

Pola VGG: kernel kecil yang ditumpuk#

Keluarga jaringan VGG memperlihatkan bahwa konvolusi 3x3 yang ditumpuk dapat menggantikan kernel besar. Dua konvolusi 3x3 berurutan mencakup area 5×55 \times 5 pada masukannya, tetapi menuntut parameter 18C218C^2, sedangkan satu konvolusi 5x5 menuntut 25C225C^2 untuk CC kanal, dan ReLU yang disisipkan di antaranya menambah kapasitas nonlinier. Pola bloknya sederhana, yaitu dua paket konvolusi 3x3 dan ReLU diikuti satu max pooling, diulang dengan jumlah filter yang naik di setiap tahap.

Aturan besarnya adalah jumlah filter naik ketika ukuran spasial mengecil. Pada citra masukan 28x28, tiga blok dengan filter 32, 64, dan 128 menghasilkan jalur shape dari 28x28 menjadi 14x14, lalu 7x7, lalu 3x3. Jumlah kanal bertambah persis ketika jumlah posisi berkurang, sehingga beban komputasi tetap terkendali. Pola ini digunakan di banyak arsitektur klasik dan tetap menjadi titik awal yang baik untuk jaringan buatan sendiri.

Setiap blok dirapikan dengan nn.Sequential, yakni wadah yang menjalankan layer-layer secara berurutan. Dengan wadah ini, kelas model cukup memanggil tiga blok dan satu head, dan forward-nya terbaca persis seperti pola pada bagian sebelumnya.

Skrip berikut mendefinisikan kelas VGGKecil lengkap dengan tiga blok dan head klasifikasinya, lalu memberikan satu batch sintetis berukuran (2, 1, 28, 28) untuk menelusuri shape setelah setiap blok beserta jumlah parameternya. Tidak ada pelatihan yang dijalankan, sehingga skrip selesai dalam waktu singkat.

python vgg_kecil.py
import torch
import torch.nn as nn


class VGGKecil(nn.Module):
    """Jaringan kecil bergaya VGG untuk citra grayscale 28x28."""

    def __init__(self, jumlah_kelas: int = 10):
        super().__init__()
        self.blok1 = nn.Sequential(
            nn.Conv2d(1, 32, kernel_size=3, padding=1), nn.ReLU(),
            nn.Conv2d(32, 32, kernel_size=3, padding=1), nn.ReLU(),
            nn.MaxPool2d(kernel_size=2, stride=2),
        )
        self.blok2 = nn.Sequential(
            nn.Conv2d(32, 64, kernel_size=3, padding=1), nn.ReLU(),
            nn.Conv2d(64, 64, kernel_size=3, padding=1), nn.ReLU(),
            nn.MaxPool2d(kernel_size=2, stride=2),
        )
        self.blok3 = nn.Sequential(
            nn.Conv2d(64, 128, kernel_size=3, padding=1), nn.ReLU(),
            nn.Conv2d(128, 128, kernel_size=3, padding=1), nn.ReLU(),
            nn.MaxPool2d(kernel_size=2, stride=2),
        )
        self.kepala = nn.Sequential(
            nn.Flatten(),
            nn.Linear(128 * 3 * 3, 128),
            nn.ReLU(),
            nn.Linear(128, jumlah_kelas),
        )

    def forward(self, x):
        return self.kepala(self.blok3(self.blok2(self.blok1(x))))


torch.manual_seed(0)
model = VGGKecil()
x = torch.rand(2, 1, 28, 28)  # satu batch sintetis: dua citra grayscale 28x28
print(f"masukan: {tuple(x.shape)}")

for nama, blok in (("blok1", model.blok1), ("blok2", model.blok2), ("blok3", model.blok3)):
    x = blok(x)
    n = sum(p.numel() for p in blok.parameters())
    print(f"setelah {nama}: {tuple(x.shape)}, parameter {n}")

y = model.kepala(x)
n = sum(p.numel() for p in model.kepala.parameters())
print(f"keluaran akhir: {tuple(y.shape)}, parameter kepala {n}")
print("total parameter:", sum(p.numel() for p in model.parameters()))
text Keluaran
masukan: (2, 1, 28, 28)
setelah blok1: (2, 32, 14, 14), parameter 9568
setelah blok2: (2, 64, 7, 7), parameter 55424
setelah blok3: (2, 128, 3, 3), parameter 221440
keluaran akhir: (2, 10), parameter kepala 148874
total parameter: 435306

Jalur shape-nya terbaca lurus. Masukan (2, 1, 28, 28) menjadi (2, 32, 14, 14) setelah blok pertama, (2, 64, 7, 7) setelah blok kedua, dan (2, 128, 3, 3) setelah blok ketiga, kemudian head mengembalikan (2, 10) untuk sepuluh kelas. Parameter tiap blok naik dari 9568 menjadi 221440 seiring jumlah filternya, persis mengikuti pola filter yang naik dan shape yang turun.

Head klasifikasi dan ukuran 1152#

Head klasifikasi menuntut ukuran masukan yang pasti. Tensor (2, 128, 3, 3) diratakan oleh nn.Flatten menjadi (2, 1152), sehingga layer pertama head dideklarasikan sebagai nn.Linear(1152, 128). Angka 1152 berasal dari 128 kanal dikalikan 3 dikalikan 3, yaitu hasil jalur shape pada skrip di atas. Perhitungan semacam ini dilakukan sekali saat menulis kelas, bukan ditebak.

python Contoh: memastikan ukuran masukan head
import torch

x = torch.zeros(2, 128, 3, 3)  # keluaran blok ketiga
print(x.flatten(start_dim=1).shape)
text Keluaran
torch.Size([2, 1152])

Kebiasaan memberikan tensor dummy dan membaca shape hasil flatten menghindari tebakan yang keliru. Pada arsitektur yang lebih modern, peringkasan seperti nn.AdaptiveAvgPool2d((1, 1)) sebelum head membuat ukuran masukan tidak lagi bergantung pada ukuran citra, sehingga angka 1152 tidak perlu dihitung ulang ketika masukan berubah. Head pada skrip di atas menyumbang 148874 parameter, angka yang besar dibandingkan dengan total 435306, dan peringkasan semacam itu adalah salah satu cara menekannya.

Kesalahan umum#

Kesalahan yang paling sering adalah mendeklarasikan nn.Linear dengan ukuran yang tidak cocok dengan keluaran blok terakhir, misalnya 128 * 7 * 7 padahal tiga pooling telah menurunkan 28x28 menjadi 3x3. Error yang muncul pada forward pertama adalah RuntimeError: addmm: mat1 and mat2 shapes cannot be multiplied, yang menyebut kedua shape yang bertabrakan. Perbaikannya adalah membaca shape keluaran blok terakhir dengan tensor dummy, lalu menyamakan argumen pertama nn.Linear dengannya.

Kesalahan kedua adalah memaksakan terlalu banyak pooling pada citra kecil. Ukuran 28x28 hanya menahan tiga pembagian dua sebelum sisa spasialnya memangkas informasi, yaitu 28 menjadi 14, lalu 7, lalu 3, dan pembagian keempat menyisakan 1. Untuk citra 28x28, tiga blok dengan pooling adalah batas yang wajar, sedangkan citra yang lebih besar baru mampu menampung blok tambahan.

Ringkasan#

  • Pola VGG menumpuk dua konvolusi 3x3 dan ReLU diikuti max pooling, dengan jumlah filter naik 32, 64, 128 saat ukuran spasial turun 28, 14, 7, 3.
  • nn.Sequential merapikan setiap blok sehingga forward model terbaca sebagai rangkaian blok dan head.
  • Keluaran blok terakhir (2, 128, 3, 3) diratakan menjadi 1152 fitur untuk head klasifikasinya.
  • Total parameter jaringan ini berjumlah 435306, dan head menyumbang 148874 di antaranya.

Referensi#