Perjalanan arsitektur CNN dari LeNet hingga ResNet menunjukkan satu tren: performa naik seiring ukuran model naik. AlexNet 60 juta parameter, VGG-16 138 juta, ResNet-152 60 juta dengan bantuan bottleneck. Namun arah ini tidak berkelanjutan — model raksasa lambat di ponsel, mahal di server, dan boros energi. Tahun 2019, tim Google menanyakan hal yang berbeda: jika ukuran harus dinaikkan, dimensi mana yang paling efisien untuk dinaikkan? EfficientNet menjawab dengan formula matematis sederhana yang menghasilkan keluarga model kecil namun kuat — dan fondasinya adalah teknik yang jauh lebih tua: depthwise separable convolution.

Fondasi: Depthwise Separable Convolution #

Convolution standar melakukan dua pekerjaan sekaligus di tiap operasinya: menyaring ruang (deteksi pola lokal per channel) dan menggabungkan channel (mencampur informasi antar channel). Ide depthwise separable adalah memecah dua pekerjaan itu menjadi dua operasi terpisah yang jauh lebih murah. Depthwise convolution (groups=in_channels) memberi tiap channel kernel spasialnya sendiri tanpa mencampur channel. Pointwise convolution (kernel 1x1) lalu menggabungkan channel tanpa menyaring ruang. MobileNet mempopulerkan teknik ini di dunia perangkat seluler sebelum EfficientNet mengangkatnya ke puncak akurasi.

Berkas depthwise_separable.py membuktikan penghematannya dengan angka konkret, menyusun blok MBConv lengkap, dan menghitung formula compound scaling resmi:

python
"""Depthwise separable convolution: fondasi hemat parameter EfficientNet.

Bagian A membandingkan conv biasa dengan padanan depthwise separable
(depthwise groups + pointwise 1x1): bentuk output sama, parameter jauh
lebih sedikit. Bagian B menyusun blok MBConv mini (expand 1x1 ->
depthwise 3x3 -> squeeze-and-excite -> project 1x1) lalu menelusuri
bentuk tensor melewatinya. Bagian C menghitung skala compound scaling
koefisien resmi EfficientNet untuk B0 sampai B7.
"""

import torch
import torch.nn as nn

torch.manual_seed(42)

# ---------- A. Conv biasa vs depthwise separable ----------
masuk, keluar, k = 8, 16, 3
x = torch.randn(1, masuk, 8, 8)

conv_biasa = nn.Conv2d(masuk, keluar, kernel_size=k, padding=k // 2)
depthwise = nn.Conv2d(masuk, masuk, kernel_size=k, padding=k // 2,
                      groups=masuk)          # tiap channel punya kernel sendiri
pointwise = nn.Conv2d(masuk, keluar, kernel_size=1)

param_biasa = sum(p.numel() for p in conv_biasa.parameters())
param_ds = sum(p.numel() for p in depthwise.parameters()) + \
    sum(p.numel() for p in pointwise.parameters())
with torch.no_grad():
    y1 = conv_biasa(x)
    y2 = pointwise(depthwise(x))
print("conv biasa 8 -> 16, kernel 3x3:")
print(f"  parameter = 16 x 8 x 3 x 3 + 16 = {param_biasa}")
print("depthwise (groups=8) + pointwise (1x1):")
print(f"  parameter = (8 x 9 + 8) + (16 x 8 + 16) = {param_ds}")
print(f"  bentuk output conv biasa         : {tuple(y1.shape)}")
print(f"  bentuk output depthwise separable: {tuple(y2.shape)}")
print(f"  rasio parameter                  = {param_ds / param_biasa:.3f}")
print(f"  rumus 1/k^2 + 1/C_keluar         = {1 / k**2 + 1 / keluar:.3f}\n")


# ---------- B. Blok MBConv mini ----------
class MBConvMini(nn.Module):
    """MBConv: expand 1x1 -> depthwise 3x3 -> SE -> project 1x1."""

    def __init__(self, channel_masuk, channel_keluar, rasio_expand=3.0):
        super().__init__()
        channel_expanded = int(channel_masuk * rasio_expand)
        self.expand = nn.Conv2d(channel_masuk, channel_expanded, kernel_size=1)
        self.depthwise = nn.Conv2d(channel_expanded, channel_expanded,
                                   kernel_size=3, padding=1,
                                   groups=channel_expanded)
        channel_se = max(1, channel_expanded // 4)
        self.se_rapatkan = nn.Conv2d(channel_expanded, channel_se, kernel_size=1)
        self.se_bentangkan = nn.Conv2d(channel_se, channel_expanded, kernel_size=1)
        self.project = nn.Conv2d(channel_expanded, channel_keluar, kernel_size=1)

    def forward(self, x):
        z = self.expand(x)
        z = self.depthwise(z)
        # squeeze-and-excite: ringkas spasial -> bobot per channel -> skala
        w = z.mean(dim=(2, 3), keepdim=True)
        w = torch.sigmoid(self.se_bentangan(torch.relu(self.se_rapatkan(w))))
        z = z * w
        return self.project(z)


blok = MBConvMini(channel_masuk=8, channel_keluar=16)
param_mbconv = sum(p.numel() for p in blok.parameters())
with torch.no_grad():
    jejak = x
    for nama, lapis in [("input", None), ("expand 1x1", blok.expand),
                        ("depthwise 3x3", blok.depthwise),
                        ("SE (skala)", None), ("project 1x1", blok.project)]:
        if nama == "SE (skala)":
            w = jejak.mean(dim=(2, 3), keepdim=True)
            w = torch.sigmoid(blok.se_bentangkan(torch.relu(blok.se_rapatkan(w))))
            jejak = jejak * w
        elif lapis is not None:
            jejak = lapis(jejak)
        print(f"  {nama:14s} {tuple(jejak.shape)}")
print(f"  parameter MBConvMini 8 -> 16: {param_mbconv}\n")

# ---------- C. Compound scaling EfficientNet ----------
# koefisien resmi: alpha=1.2 (depth), beta=1.1 (width), gamma=1.15 (resolusi)
alpha, beta, gamma = 1.2, 1.1, 1.15
print("compound scaling dari baseline B0 (konstrain alpha*beta^2*gamma^2 ~ 2):")
print(f"  {'model':>7s} {'phi':>4s} {'depth 1.2^phi':>13s} "
      f"{'width 1.1^phi':>13s} {'resolusi 224*1.15^phi':>21s}")
for phi in range(8):
    depth = alpha ** phi
    width = beta ** phi
    resolusi = 224 * gamma ** phi
    nama = f"B{phi}"
    print(f"  {nama:>7s} {phi:>4d} {depth:>13.2f} {width:>13.2f} "
          f"{resolusi:>21.0f}")
print("\nbentuk resolusi dibulatkan ke kelipatan 8 pada implementasi asli")
print("agar tetap kompatibel dengan downsampling berulang.")

Output yang Dihasilkan:

text
conv biasa 8 -> 16, kernel 3x3:
  parameter = 16 x 8 x 3 x 3 + 16 = 1168
depthwise (groups=8) + pointwise (1x1):
  parameter = (8 x 9 + 8) + (16 x 8 + 16) = 224
  bentuk output conv biasa         : (1, 16, 8, 8)
  bentuk output depthwise separable: (1, 16, 8, 8)
  rasio parameter                  = 0.192
  rumus 1/k^2 + 1/C_keluar         = 0.174

  input          (1, 8, 8, 8)
  expand 1x1     (1, 24, 8, 8)
  depthwise 3x3  (1, 24, 8, 8)
  SE (skala)     (1, 24, 8, 8)
  project 1x1    (1, 16, 8, 8)
  parameter MBConvMini 8 -> 16: 1174

compound scaling dari baseline B0 (konstrain alpha*beta^2*gamma^2 ~ 2):
    model  phi depth 1.2^phi width 1.1^phi resolusi 224*1.15^phi
       B0    0          1.00          1.00                   224
       B1    1          1.20          1.10                   258
       B2    2          1.44          1.21                   296
       B3    3          1.73          1.33                   341
       B4    4          2.07          1.46                   392
       B5    5          2.49          1.61                   451
       B6    6          2.99          1.77                   518
       B7    7          3.58          1.95                   596

bentuk resolusi dibulatkan ke kelipatan 8 pada implementasi asli
agar tetap kompatibel dengan downsampling berulang.

Mari kita telaah kode di atas baris per baris:

  1. Baris 15-21: convolution biasa 8→16 channel dengan kernel 3x3 dijumlahkan parameternya (1168) sebagai pembanding; depthwise memakai groups=masuk sehingga tiap channel hanya memiliki satu kernel 3x3 sendiri; pointwise kernel 1x1 mengubah jumlah channel.
  2. Baris 23-27: kedua varian dijalankan pada masukan yang sama — bentuk keluaran identik (1, 16, 8, 8) — sehingga perbandingan parameter adil: 224 berbanding 1168, rasio 0,192.
  3. Baris 31: rumus teori 1/k² + 1/C_keluar memprediksi rasio 0,174 — mendekati angka terukur 0,192 (selisih kecil berasal dari bias). Penghematan membesar pada channel yang lebar.
  4. Baris 36-58 (MBConvMini): blok bangunan EfficientNet — expand 1x1 melebarkan channel (rasio 3x), depthwise 3x3 menyaring ruang, squeeze-and-excite memberi bobot per channel dari ringkasan spasial, lalu project 1x1 mengembalikan lebar channel.
  5. Baris 61-75: jejak bentuk menunjukkan perjalanan tensor (1,8,8,8) → expand ke 24 channel → depthwise tetap 24 → SE menskalakan tanpa mengubah bentuk → project ke 16 channel.
  6. Baris 78-95: bagian C menghitung dimensi B0 sampai B7 dengan koefisien resmi α=1,2 (kedalaman), β=1,1 (lebar), γ=1,15 (resolusi) — ketiganya dinaikkan bersama sebagai pangkat dari satu parameter φ.

Membaca Hasil: Hemat yang Terukur #

Tiga temuan menonjol dari keluaran. Pertama, depthwise separable menghemat lebih dari 80 persen parameter (rasio 0,192) dengan bentuk keluaran yang persis sama — penghematan murni tanpa mengubah kapasitas spasial. Kedua, blok MBConv 8→16 hanya membutuhkan 1174 parameter; blok convolution biasa setara dengan lebar channel 24 di dalamnya akan jauh lebih mahal. Ketiga, tabel compound scaling memperlihatkan cara EfficientNet tumbuh: dari B0 (kedalaman 1,0, lebar 1,0, resolusi 224) hingga B7 (kedalaman 3,58x, lebar 1,95x, resolusi 596) — ketiga dimensi naik serentak dengan laju yang seimbang.

Compound Scaling: Menaikkan Ketiga Dimensi Sekaligus #

Sebelum EfficientNet, praktisi memperbesar model lewat satu dimensi saja: ResNet menambah kedalaman, WideResNet menambah lebar channel, atau masukan diperbesar resolusinya. Tim Google memakai pencarian neural architecture search lalu analisis scaling dan menemukan bahwa menaikkan ketiga dimensi secara berimbang mengalahkan menaikkan satu dimensi secara agresif. Konstrainnya dirumuskan sebagai α·β²·γ² ≈ 2 — naikkan semua hingga biaya komputasi kira-kira berlipat dua.

ModelφParameterTop-1 ImageNet (laporan resmi)
EfficientNet-B005,3 juta77,1%
EfficientNet-B3312 juta81,6%
EfficientNet-B7766 juta84,3%
ResNet-152 (pembanding)—60 juta78,3%

Tabel di atas memperlihatkan inti klaim EfficientNet: B3 dengan 12 juta parameter mengalahkan ResNet-152 yang 60 juta parameter — lima kali lebih hemat. Perbandingan angka diambil dari makalah aslinya, EfficientNet: Rethinking Model Scaling for Convolutional Neural Networks .

Diagram blok MBConv di atas memadatkan seluruh warisan arsitektur dalam satu struktur: bottleneck 1x1 dari Inception (chapter VGG dan Inception ), depthwise separable dari MobileNet, squeeze-and-excite untuk kalibrasi channel, dan lompatan residual dari ResNet (chapter Residual Network ).

Keluarga ide EfficientNet — mobilitas, efisiensi, dan scaling terukur — menjadi standar desain model modern, termasuk jaringan vision transformer efisien. Penerapan praktis paling umum dari CNN efisien ini adalah memakainya sebagai backbone pretrained untuk dataset kecil milik sendiri. Kita akan melatih pola transfer tersebut sampai tuntas pada chapter Transfer Learning untuk Gambar . Implementasi EfficientNet siap pakai tersedia di halaman model torchvision .