Convolution membawa asumsi kuat: pola penting bersifat lokal, jadi kernel kecil yang menyapu gambar sudah cukup. Vision Transformer (ViT) membuang asumsi itu dan memperlakukan gambar seperti sebuah kalimat — dipotong menjadi potongan kecil (patch), tiap patch menjadi token, lalu self-attention membiarkan setiap token “menatap” seluruh token lain sejak layer pertama. Hasilnya arsitektur yang seragam, mudah diskalakan, dan unggul saat data pelatihan melimpah. Pada chapter ini kita fokus pada sisi praktisnya: patchify dari piksel ke token, proyeksi linear, class token, dan positional embedding — seluruh alurnya kita jalankan dengan NumPy pada citra digits. Sisi arsitektur transformer-nya (self-attention, encoder, kompleksitas) dibahas penuh di chapter Vision Transformer pada section Attention dan Transformer ; dua chapter itu sengaja berbagi satu topik dari dua arah.

Patchify: Dari Piksel ke Urutan Token #

Transformer menerima urutan vektor, sedangkan gambar adalah kisi dua dimensi. Jembatannya bernama patchify: gambar H x W dipotong menjadi grid patch P x P (ViT asli memakai P = 16), tiap patch diratakan menjadi vektor P·P·C nilai, lalu seluruh patch disusun menjadi urutan dengan membaca grid baris demi baris. Untuk gambar 224x224 dan patch 16x16, hasilnya 196 token; untuk citra digits 8x8 dengan patch 4x4, hasilnya 4 token — cukup kecil untuk ditelusuri angka per angka.

Dua token tambahan melengkapi urutan. Pertama, token [CLS]: satu vektor terlatih yang diletakkan di depan urutan; setelah encoder selesai, nilai inilah yang dibaca classifier head — pola yang diwarisi dari BERT dan membebaskan kita dari merata-ratakan seluruh token. Kedua, positional embedding: vektor per posisi yang dijumlahkan ke tiap token, karena attention sendiri tidak peduli urutan — tanpa informasi posisi, token patch kiri atas dan kanan bawah tidak bisa dibedakan.

Diagram di atas adalah keseluruhan ViT: tidak ada convolution, tidak ada pooling, hanya urutan token dan blok encoder yang sama persis dengan transformer di NLP. Elemen yang dipelajari adalah matriks proyeksi patch, token [CLS], positional embedding, dan bobot encoder.

Eksperimen NumPy: Empat Token dari Satu Digit #

Berkas patchify_digits.py menjalankan alur masukan ViT dari awal sampai urutan siap masuk encoder: normalisasi skala, patchify 4x4, proyeksi linear ke dimensi 8, lalu penambahan class token dan positional embedding. Bobot proyeksi diisi acak (dari generator berseed) karena kita hanya mendemonstrasikan bentuk dan mekanismenya, bukan melatih model.

python
"""Patchify: mengubah gambar menjadi urutan token ala Vision Transformer.

Alur: citra digits 8x8 -> dinormalisasi -> dipotong patch 4x4 -> 4 token
-> proyeksi linear ke dimensi 8 -> class token + positional embedding
-> urutan masukan transformer berbentuk (5, 8).
"""

import numpy as np
from sklearn.datasets import load_digits

rng = np.random.default_rng(42)

digits = load_digits()
gambar = digits.images[0].astype(float) / 16.0  # skala 0..16 -> 0..1

print(f"gambar digits[0] (kelas {digits.target[0]}), bentuk {gambar.shape}:")
print(np.round(gambar, 2))
print()

# ---------- 1. Patchify: potong 4x4, urut baris demi baris ----------
P = 4  # ukuran patch (ViT asli: 16)
grid_h, grid_w = gambar.shape[0] // P, gambar.shape[1] // P

patch = []
print(f"patchify: patch {P}x{P} piksel, grid {grid_h}x{grid_w} "
      f"-> {grid_h * grid_w} token")
print("urutan patch (baris demi baris):")
for by in range(grid_h):
    for bx in range(grid_w):
        potongan = gambar[by * P:(by + 1) * P, bx * P:(bx + 1) * P]
        patch.append(potongan.reshape(-1))  # 4x4 -> vektor 16
        print(f"  token {len(patch) - 1}: blok (baris {by}, kolom {bx})")
print()

X = np.stack(patch)  # (4 token, 16 nilai)
print(f"matriks token mentah: {X.shape} "
      f"(4 token x {P * P} nilai)")
print()

print("token 0 sebagai blok 4x4 (piksel aslinya):")
print(np.round(X[0].reshape(P, P), 2))
print()
print("token 0 sebagai vektor 16 (input layer linear):")
print(np.round(X[0], 2))
print()

# ---------- 2. Proyeksi linear: 16 -> 8 dimensi ----------
D = 8  # dimensi embedding (ViT asli: 768)
W = rng.standard_normal((P * P, D)) * 0.1  # bobot proyeksi (acak demo)
token_proj = X @ W  # (4, 8)
print(f"proyeksi linear W {W.shape}: token {X.shape} -> {token_proj.shape}")
print(np.round(token_proj, 2))
print()

# ---------- 3. Class token + positional embedding ----------
cls = rng.standard_normal(D) * 0.1          # token khusus [CLS]
pos = rng.standard_normal((grid_h * grid_w + 1, D)) * 0.1  # (5, 8)

urutan = np.vstack([cls, token_proj]) + pos  # (5, 8)
print(f"class token: {cls.shape}, positional embedding: {pos.shape}")
print(f"urutan masukan transformer: {urutan.shape} "
      f"(1 class token + {grid_h * grid_w} token patch)")
print()
print("urutan lengkap (baris 0 = class token, dibulatkan):")
print(np.round(urutan, 2))

Output yang Dihasilkan:

text
gambar digits[0] (kelas 0), bentuk (8, 8):
[[0.   0.   0.31 0.81 0.56 0.06 0.   0.  ]
 [0.   0.   0.81 0.94 0.62 0.94 0.31 0.  ]
 [0.   0.19 0.94 0.12 0.   0.69 0.5  0.  ]
 [0.   0.25 0.75 0.   0.   0.5  0.5  0.  ]
 [0.   0.31 0.5  0.   0.   0.56 0.5  0.  ]
 [0.   0.25 0.69 0.   0.06 0.75 0.44 0.  ]
 [0.   0.12 0.88 0.31 0.62 0.75 0.   0.  ]
 [0.   0.   0.38 0.81 0.62 0.   0.   0.  ]]

patchify: patch 4x4 piksel, grid 2x2 -> 4 token
urutan patch (baris demi baris):
  token 0: blok (baris 0, kolom 0)
  token 1: blok (baris 0, kolom 1)
  token 2: blok (baris 1, kolom 0)
  token 3: blok (baris 1, kolom 1)

matriks token mentah: (4, 16) (4 token x 16 nilai)

token 0 sebagai blok 4x4 (piksel aslinya):
[[0.   0.   0.31 0.81]
 [0.   0.   0.81 0.94]
 [0.   0.19 0.94 0.12]
 [0.   0.25 0.75 0.  ]]

token 0 sebagai vektor 16 (input layer linear):
[0.   0.   0.31 0.81 0.   0.   0.81 0.94 0.   0.19 0.94 0.12 0.   0.25
 0.75 0.  ]

proyeksi linear W (16, 8): token (4, 16) -> (4, 8)
[[-0.03 -0.07 -0.03  0.1  -0.34 -0.04  0.27 -0.07]
 [-0.04 -0.14 -0.02  0.15 -0.19 -0.16  0.15 -0.  ]
 [ 0.02 -0.13  0.09  0.13 -0.15  0.06  0.16 -0.23]
 [-0.01 -0.03  0.07  0.06 -0.06  0.11  0.07 -0.11]]

class token: (8,), positional embedding: (5, 8)
urutan masukan transformer: (5, 8) (1 class token + 4 token patch)

urutan lengkap (baris 0 = class token, dibulatkan):
[[-0.03 -0.01  0.16  0.15  0.1  -0.12 -0.36 -0.07]
 [-0.11 -0.12 -0.09  0.08 -0.23 -0.02  0.25 -0.17]
 [-0.21 -0.19 -0.02  0.33 -0.17 -0.06  0.1  -0.12]
 [-0.08 -0.21  0.3   0.05 -0.07 -0.03  0.25 -0.19]
 [-0.02 -0.04  0.01  0.1  -0.11 -0.01 -0.06 -0.09]]

Mari kita telaah kode di atas baris per baris:

  1. Baris 14: citra digits[0] dibagi 16.0 supaya nilai piksel berada di rentang 0-1 — normalisasi masukan standar sebelum masuk proyeksi.
  2. Baris 21-22: ukuran patch P = 4 menghasilkan grid 2x2; jumlah token patch = grid_h x grid_w = 4. Pada ViT asli, P = 16 menghasilkan 196 token dari gambar 224x224.
  3. Baris 28-32: dua loop membaca grid baris demi baris; tiap potongan 4x4 diratakan reshape(-1) menjadi vektor 16 nilai lalu disimpan berurutan — urutan inilah yang dibaca positional embedding.
  4. Baris 35-44: np.stack membentuk matriks token (4, 16); token 0 ditampilkan dua kali — sebagai blok 4x4 (piksel aslinya) dan sebagai vektor 16 (bentuk yang benar-benar diterima layer linear).
  5. Baris 48-50: proyeksi linear X @ W memetakan 16 dimensi ke 8 dimensi — pada ViT asli pemetaannya 768 dimensi ke 768 dimensi. Hasilnya matriks (4, 8), satu baris per token.
  6. Baris 56-59: class token (8,) disusun di depan hasil proyeksi dengan np.vstack, lalu positional embedding (5, 8) dijumlahkan ke seluruh baris — termasuk ke token [CLS] pada baris 0. Urutan akhir berbentuk (5, 8): lima token, masing-masing delapan dimensi, siap masuk encoder transformer.

CNN lawan ViT #

Perbedaan kedua arsitektur bukan sekadar komponen, melainkan prior yang dibawa sejak awal. CNN memasang bias lokalisasi (receptive field tumbuh bertingkat, filter menyapu semua posisi) sehingga belajar efisien dari data sedang, tetapi hubungan jarak jauh baru terjangkau di layer dalam. ViT hampir tanpa bias spasial — attention bebas menghubungkan dua patch mana pun sejak layer pertama — sehingga fleksibilitasnya harus dibayar dengan data pelatihan besar (ImageNet-21k atau JFT) sebelum unggul. Setelah pra-pelatihan besar itu, ViT menjadi bintang transfer learning: fine-tuning pada dataset kecil sering mengungguli CNN karena representasinya lebih umum.

AspekCNNVision Transformer
Unit dasarconvolution + poolingtoken patch + self-attention
Bias spasiallokal, translasi-invariannyaris tanpa bias, posisi lewat embedding
Reach hubungan fiturbertumbuh per layerglobal sejak layer pertama
Kebutuhan dataefisien pada data sedangkuat setelah pra-pelatihan besar
Peran transferumum namun sering kalah skalaunggul pada fine-tuning dataset kecil
Komponen khaskernel, stride, feature mappatchify, [CLS], positional embedding

Pengubahan gambar menjadi urutan token yang kita lakukan secara manual di atas ternyata juga cara pandang paling produktif untuk data yang memang berurutan: teks, audio, dan deret waktu. Section berikutnya, Sequence Modeling , membahas data sekuens dari fondasinya — dari representasi data berurutan hingga recurrent neural network — sementara mekanika attention yang menjadi jantung ViT dibedah tuntas di section Attention dan Transformer .