Model kontrastif pada chapter sebelumnya mahir mencocokkan gambar dengan caption, tetapi tidak bisa menjawab pertanyaan. Vision-language model (VLM) mengisi celah itu dengan resep yang kini menjadi standar: ambil encoder visual sebagai “mata”, ambil large language model sebagai “otak”, lalu susun jembatan agar keluaran penglihatan dapat dibaca oleh mesin bahasa. Hasilnya adalah model yang menerima gambar plus instruksi teks lalu menghasilkan jawaban bebas — mulai dari deskripsi foto, pembacaan teks pada nota belanja, hingga penalaran sederhana atas isi diagram. Chapter ini membedah arsitekturnya, lalu membangun simulasi mekanis skala mainan yang benar-benar dilatih: satu blok decoder dengan cross-attention yang harus menjawab “bentuk apa” atau “warna apa” bergantung pertanyaannya.

Anatomi Tiga Komponen #

VLM modern tersusun dari tiga komponen dengan peran yang tegas. Pertama, vision encoder — biasanya vision transformer atau CNN terlatih — mengubah gambar menjadi barisan visual token, satu vektor per patch atau per wilayah. Kedua, adapter atau proyeksi memetakan visual token dari dimensi encoder visual ke dimensi embedding LLM; bentuk paling sederhana berupa satu lapisan linear. Ketiga, LLM memproses gabungan token instruksi dan visual token, lalu menghasilkan jawaban token demi token. Penggabungan terjadi di ruang token: visual token diperlakukan seperti kata-kata asing yang masuk ke awal konteks.

Diagram di atas memperlihatkan posisi adapter sebagai penerjemah dimensi. Mekanisme pembacaan isi gambar oleh token teks memakai cross-attention yang sudah kita bangun di chapter Transformer Decoder : query berasal dari token teks, sedangkan key dan value berasal dari visual token — sehingga setiap kata pertanyaan dapat menimang wilayah gambar yang relevan.

Pelatihan VLM modern umumnya memakai visual instruction tuning: jutaan pasangan (gambar, instruksi, jawaban) berformat percakapan digunakan untuk fine-tuning, mengikuti pola instruction tuning pada LLM yang sudah kita bahas di section Large Language Model . Kemampuan yang muncul dari proses ini mencakup VQA (menjawab pertanyaan tentang gambar), captioning terperinci, dan OCR reasoning — membaca angka pada foto struk lalu menjumlahkannya.

Demo: Simulasi Mekanis Mini #

Simulasi kita mengganti gambar 8x8 dengan vektor fitur 8 dimensi — empat angka pertama mengodekan bentuk (kotak/bulat/segitiga), empat berikutnya mengodekan warna (merah/biru). Vision encoder-nya dua proyeksi linear yang menghasilkan dua visual token, dan “LLM”-nya satu TransformerDecoderLayer dari PyTorch. Tugasnya nyata namun kecil: membaca pertanyaan dua kata, lalu memprediksi satu token jawaban yang benar:

python
"""Simulasi mekanis vision-language model (VLM) skala mainan.

"Gambar" = vektor fitur 8 dimensi (4 untuk bentuk: kotak/bulat/segitiga,
4 untuk warna: merah/biru). Vision encoder memproyeksikan kedua grup
menjadi 2 visual token (n, 2, 16). Pertanyaan teks ("bentuk apa" /
"warna apa") menjadi token embedding. Satu blok TransformerDecoder --
dengan cross-attention: query dari token pertanyaan, key/value dari
visual token -- membaca pertanyaan sambil menimang isi gambar, lalu
kepala linear memprediksi token jawaban.

Uji nyata: akurasi latih/uji pada 500 sampel + satu gambar yang sama
diberi dua pertanyaan berbeda harus dijawab dua jawaban berbeda.
CPU, PyTorch 2.14, dropout=0 agar deterministik.
"""

import torch
import torch.nn as nn
import torch.nn.functional as F

torch.manual_seed(3)

BENTUK = ["kotak", "bulat", "segitiga"]
WARNA = ["merah", "biru"]
VOCAB = {"bentuk": 0, "warna": 1, "apa": 2,
         "kotak": 3, "bulat": 4, "segitiga": 5, "merah": 6, "biru": 7}
KATA = list(VOCAB)
D = 16


def buat_data(n, generator):
    """Kembalikan (fitur_bentuk, fitur_warna, token_pertanyaan, id_jawaban)."""
    fb = torch.zeros(n, 4)
    fb[torch.arange(n), torch.randint(0, 3, (n,), generator=generator)] = 1.0
    fw = torch.zeros(n, 4)
    fw[torch.arange(n), torch.randint(0, 2, (n,), generator=generator)] = 1.0
    fb = fb + 0.2 * torch.randn(n, 4, generator=generator)
    fw = fw + 0.2 * torch.randn(n, 4, generator=generator)
    tipe = torch.randint(0, 2, (n,), generator=generator)
    jawab = torch.where(tipe == 0, 3 + fb.argmax(1), 6 + fw.argmax(1))
    q = torch.stack([tipe, torch.full((n,), VOCAB["apa"])], dim=1)
    return fb, fw, q, jawab


fb_l, fw_l, q_l, j_l = buat_data(400, torch.Generator().manual_seed(1))
fb_u, fw_u, q_u, j_u = buat_data(100, torch.Generator().manual_seed(2))

print("=== 1. Data dan kosakata ===")
print(f"kosakata {len(VOCAB)} kata: {KATA}")
print(f"latih: {fb_l.shape[0]} sampel, uji: {fb_u.shape[0]} sampel")
print(f"pertanyaan hanya 2 pola: 'bentuk apa' / 'warna apa'")
print(f"contoh: bentuk={BENTUK[fb_u[0].argmax()]}, "
      f"warna={WARNA[fw_u[0].argmax()]}")


class VLMMini(nn.Module):
    def __init__(self):
        super().__init__()
        self.proj_bentuk = nn.Linear(4, D)   # vision encoder: grup bentuk
        self.proj_warna = nn.Linear(4, D)    # vision encoder: grup warna
        self.emb = nn.Embedding(len(VOCAB), D)
        self.pos = nn.Embedding(4, D)
        self.dec = nn.TransformerDecoderLayer(
            D, nhead=4, dim_feedforward=64, dropout=0.0, batch_first=True)
        self.kepala = nn.Linear(D, len(VOCAB))

    def visual_tokens(self, fb, fw):
        return torch.stack([self.proj_bentuk(fb), self.proj_warna(fw)], dim=1)

    def forward(self, fb, fw, q):
        mem = self.visual_tokens(fb, fw)          # (n, 2, 16)
        tgt = self.emb(q) + self.pos(torch.arange(q.shape[1]))
        h = self.dec(tgt, mem)                    # cross-attn: Q=tgt, K/V=mem
        return self.kepala(h[:, -1])              # prediksi token jawaban


model = VLMMini()
with torch.no_grad():
    mem = model.visual_tokens(fb_u[:1], fw_u[:1])
    tgt = model.emb(q_u[:1]) + model.pos(torch.arange(2))
print()
print("=== 2. Bentuk tensor arsitektur (1 contoh) ===")
print(f"masukan gambar : 2 grup fitur (1, 4) + (1, 4)")
print(f"visual token   : {tuple(mem.shape)}  <- memory cross-attention")
print(f"token pertanyaan: {tuple(tgt.shape)}  <- query cross-attention")
print(f"keluaran kepala: {tuple(model(fb_u[:1], fw_u[:1], q_u[:1]).shape)}"
      "  (skor {0} kata)".format(len(VOCAB)))
print(f"jumlah parameter: {sum(p.numel() for p in model.parameters())}")

print()
print("=== 3. Pelatihan (visual instruction tuning mini) ===")
opt = torch.optim.Adam(model.parameters(), lr=1e-3)
loss_fn = nn.CrossEntropyLoss()
jejak = []
for e in range(200):
    opt.zero_grad()
    loss = loss_fn(model(fb_l, fw_l, q_l), j_l)
    loss.backward()
    opt.step()
    if e in (0, 99, 199):
        jejak.append(loss.item())
print("loss: epoch 0 {:.4f} -> epoch 100 {:.4f} -> epoch 200 {:.4f}"
      .format(*jejak))

with torch.no_grad():
    ak_l = (model(fb_l, fw_l, q_l).argmax(1) == j_l).float().mean().item()
    ak_u = (model(fb_u, fw_u, q_u).argmax(1) == j_u).float().mean().item()
print(f"akurasi latih: {ak_l:.3f}, akurasi uji: {ak_u:.3f}")

print()
print("=== 4. Satu gambar, dua pertanyaan ===")
idx = int((q_u[:, 0] == 1).nonzero()[0])
dua_q = torch.tensor([[VOCAB["bentuk"], VOCAB["apa"]],
                      [VOCAB["warna"], VOCAB["apa"]]])
with torch.no_grad():
    p = F.softmax(model(fb_u[idx:idx+1].expand(2, 4),
                        fw_u[idx:idx+1].expand(2, 4), dua_q), dim=1)
print(f"gambar: bentuk={BENTUK[fb_u[idx].argmax()]}, "
      f"warna={WARNA[fw_u[idx].argmax()]}")
for i, qs in enumerate(dua_q):
    urut = p[i].argsort(descending=True)[:3]
    print(f"Q: '{KATA[qs[0]]} {KATA[qs[1]]}' -> "
          f"'{KATA[int(p[i].argmax())]}' "
          f"(p={p[i].max():.2f}; "
          + ", ".join(f"{KATA[int(k)]}={p[i][k]:.2f}" for k in urut) + ")")

Output yang Dihasilkan:

text
=== 1. Data dan kosakata ===
kosakata 8 kata: ['bentuk', 'warna', 'apa', 'kotak', 'bulat', 'segitiga', 'merah', 'biru']
latih: 400 sampel, uji: 100 sampel
pertanyaan hanya 2 pola: 'bentuk apa' / 'warna apa'
contoh: bentuk=kotak, warna=merah

=== 2. Bentuk tensor arsitektur (1 contoh) ===
masukan gambar : 2 grup fitur (1, 4) + (1, 4)
visual token   : (1, 2, 16)  <- memory cross-attention
token pertanyaan: (1, 2, 16)  <- query cross-attention
keluaran kepala: (1, 8)  (skor 8 kata)
jumlah parameter: 4888

=== 3. Pelatihan (visual instruction tuning mini) ===
loss: epoch 0 2.0553 -> epoch 100 0.2587 -> epoch 200 0.0584
akurasi latih: 1.000, akurasi uji: 1.000

=== 4. Satu gambar, dua pertanyaan ===
gambar: bentuk=kotak, warna=merah
Q: 'bentuk apa' -> 'kotak' (p=0.96; kotak=0.96, segitiga=0.02, bentuk=0.01)
Q: 'warna apa' -> 'merah' (p=0.95; merah=0.95, segitiga=0.01, biru=0.01)

Mari kita telaah kode di atas baris per baris:

  1. Baris 30-44 (buat_data): satu-hot bentuk dan warna diberi derau 0,2 — cukup untuk memaksa model membaca isi, bukan menghafal pola persis. jawab memilih token jawaban sesuai tipe pertanyaan, dan q merangkai pertanyaan dua kata.
  2. Baris 55-73 (VLMMini): proj_bentuk dan proj_warna adalah vision encoder yang menghasilkan dua visual token — padanan dua wilayah gambar; emb + pos menyiapkan token pertanyaan; nn.TransformerDecoderLayer menjadi “LLM” satu blok; kepala memproyeksikan hidden state terakhir ke skor kosakata.
  3. Baris 69-73 (forward): perhatikan pembagian peran yang menjadi inti VLM — mem (visual token) hanya masuk sebagai key/value cross-attention, sedangkan tgt (pertanyaan) menjadi query dan jalur self-attention. Baris h[:, -1] mengambil hidden state token terakhir sebagai perwakilan seluruh pertanyaan.
  4. Baris 76-87: sebelum pelatihan, satu contoh melewati jalur arsitektur untuk mencetak bentuk tensor — dua grup fitur 4 dimensi menjadi visual token (1, 2, 16) yang menghadapi token pertanyaan (1, 2, 16).
  5. Baris 91-105: pelatihan memakai CrossEntropyLoss atas token jawaban — persis pola supervised fine-tuning LLM, hanya sumber konteksnya kini memuat visual token.
  6. Baris 111-125: satu gambar uji diperluas menjadi dua sampel dengan pertanyaan berbeda; softmax mencetak tiga kandidat teratas agar keyakinan model terbaca.

Bagian keempat keluaran adalah bukti mekanisme yang dicari: gambar yang sama — bentuk kotak, warna merah — dijawab “kotak” (p=0,96) ketika ditanya bentuk dan “merah” (p=0,95) ketika ditanya warna. Model tidak sekadar menghafal pasangan tanya-jawab; ia belajar bahwa isi pertanyaan menentukan bagian gambar mana yang harus dibaca lewat cross-attention. Akurasi uji 1,000 pada 100 sampel menegaskan pola itu tergeneralisasi.

Keluarga VLM dan Kemampuannya #

PendekatanCara menyatukan modalitasKekuatan khas
Adapter linear sederhanavisual token diproyeksikan langsung ke ruang LLMmurah, mudah dilatih
Cross-attention tambahanlapisan khusus membaca visual token dari dalam LLMhemat panjang konteks
Flamingo-style perceiver resamplervisual token diringkas menjadi token tetapefisien untuk video
Penggabungan konteks penuhvisual token masuk sebagai awal konteks decoderseragam dengan arsitektur LLM

Pembacaan tabel ini perlu kehati-hatian: setiap keluarga terus berevolusi dan angka resminya bergantung pada versi model serta tolok ukur yang dipakai, sehingga perbandingan kuantitatif mutakhir sebaiknya dibaca langsung dari publikasi masing-masing. Yang stabil adalah polanya — vision encoder yang bagus, jembatan yang murah, LLM yang kuat.

Setelah penglihatan dan bahasa bersatu, pertanyaan wajar berikutnya menyangkut modalitas ketiga: suara. Chapter Audio-Text Model membedah bagaimana gelombang bunyi diubah menjadi representasi yang dapat dibaca model — dari spektrogram hingga arsitektur encoder-decoder ala Whisper.