Token ID menyelesaikan masalah komputasi, tetapi tidak menyentuh masalah makna: ID 23 untuk “teh” dan ID 7 untuk “enak” tidak membawa informasi bahwa keduanya kerap muncul dalam kalimat yang sama. Word embedding mengisi celah ini dengan memetakan setiap token ke sebuah vektor berdimensi rendah — biasanya 50 hingga 300 angka — sedemikian rupa sehingga kata yang maknanya mirip berjarak dekat dalam ruang vektor. Representasi semacam ini menjadi fondasi hampir semua arsitektur NLP modern, dari klasifikasi teks sampai model bahasa skala besar. Chapter ini membangun embedding kecil secara manual agar setiap angkanya bisa kita periksa, lalu membandingkannya dengan representasi one-hot.

Hipotesis Distribusional #

Dari mana makna sebuah kata bisa dihitung? Jawaban NLP klasiknya adalah hipotesis distribusional: kata yang muncul dalam konteks serupa cenderung bermakna serupa — “kopi” dan “teh” sama-sama dikelilingi kata “enak”, “panas”, “pagi”, sehingga keduanya mestinya berjarak dekat. Embedding belajar dari statistik kebersamaan kata ini: alih-alih menulis fitur secara manual, model Word2Vec yang akan kita bahas setelah ini mengambil seluruh angka vektor dari data.

Sebelum sampai ke sana, kita pakai embedding buatan tangan dengan enam kata dan lima dimensi fitur semantik. Pendekatan manual seperti ini mustahil menskalakan ke jutaan kata — justru itulah pekerjaan model — tetapi membuat mekanisme dasarnya transparan: setiap dimensi menyimpan satu aspek makna yang bisa kita baca.

Kemiripan Kosinus dan Aritmetika Vektor #

Kemiripan dua vektor diukur dengan kosinus sudut di antara keduanya: hasil 1 berarti sejajar (paling mirip), 0 berarti tegak lurus (tak berhubungan), dan -1 berarti berlawanan arah. Rumusnya adalah perkalian titik dibagi hasil kali panjang kedua vektor — fungsi np.linalg.norm menyediakan panjang vektor di penyebut. Berikut implementasi lengkapnya:

python
"""Word embedding manual: kata sebagai vektor fitur semantik, kemiripan
kosinus, aritmetika vektor (raja - pria + wanita = ratu), dan perbandingan
dengan representasi one-hot yang tidak menyimpan makna.

Enam kata dipetakan ke 5 dimensi fitur buatan tangan. Angka konkret
memperlihatkan: kata semantically mirip -> kosinus tinggi, kata tak
berhubungan -> kosinus rendah, analogi gender bekerja sebagai arah vektor.
"""

import numpy as np

# urutan dimensi: [bangsawan, maskulin, feminin, makanan, aktivitas]
NAMA_FITUR = ["bangsawan", "maskulin", "feminin", "makanan", "aktivitas"]
EMBEDDING = {
    "raja":   [1.0, 0.6, 0.0, 0.0, 0.2],
    "ratu":   [1.0, 0.0, 0.6, 0.0, 0.2],
    "pria":   [0.0, 0.6, 0.0, 0.1, 0.3],
    "wanita": [0.0, 0.0, 0.6, 0.1, 0.3],
    "makan":  [0.0, 0.05, 0.05, 0.95, 0.8],
    "minum":  [0.0, 0.05, 0.05, 0.75, 0.95],
}

kata_urut = list(EMBEDDING)  # raja, ratu, pria, wanita, makan, minum


def kemiripan_kosinus(a, b) -> float:
    """Kosinus sudut antara dua vektor: 1 sejajar, 0 tegak lurus."""
    a, b = np.asarray(a), np.asarray(b)
    return float(np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b)))


print("=== 1. Kata sebagai vektor 5 dimensi ===")
print("urutan fitur:", ", ".join(NAMA_FITUR))
for k in kata_urut:
    print(f"  {k:7s} = {EMBEDDING[k]}")
print()

print("=== 2. Kemiripan kosinus pasangan kata ===")
pasangan = [
    ("raja", "ratu"), ("raja", "pria"), ("ratu", "wanita"),
    ("makan", "minum"), ("raja", "makan"), ("ratu", "minum"),
]
for a, b in pasangan:
    nilai = kemiripan_kosinus(EMBEDDING[a], EMBEDDING[b])
    print(f"  kosinus({a:6s}, {b:6s}) = {nilai:.3f}")
print()

print("=== 3. Aritmetika vektor: raja - pria + wanita ===")
hasil = (
    np.array(EMBEDDING["raja"])
    - np.array(EMBEDDING["pria"])
    + np.array(EMBEDDING["wanita"])
)
print("  hasil        =", np.round(hasil, 3))
print("  vektor ratu  =", EMBEDDING["ratu"])
print("  kosinus(hasil, ratu) =",
      round(kemiripan_kosinus(hasil, EMBEDDING["ratu"]), 3))
print()

print("=== 4. One-hot: 6 dimensi tanpa makna ===")
V = len(kata_urut)
one_hot = np.eye(V)
for a, b in [("raja", "ratu"), ("raja", "pria"), ("raja", "makan")]:
    nilai = kemiripan_kosinus(one_hot[kata_urut.index(a)],
                              one_hot[kata_urut.index(b)])
    print(f"  kosinus({a}, {b}) [one-hot] = {nilai:.3f}")
print("  nilai non-nol dalam satu vektor one-hot:",
      int(one_hot[kata_urut.index("raja")].sum()))

Output yang Dihasilkan:

text
=== 1. Kata sebagai vektor 5 dimensi ===
urutan fitur: bangsawan, maskulin, feminin, makanan, aktivitas
  raja    = [1.0, 0.6, 0.0, 0.0, 0.2]
  ratu    = [1.0, 0.0, 0.6, 0.0, 0.2]
  pria    = [0.0, 0.6, 0.0, 0.1, 0.3]
  wanita  = [0.0, 0.0, 0.6, 0.1, 0.3]
  makan   = [0.0, 0.05, 0.05, 0.95, 0.8]
  minum   = [0.0, 0.05, 0.05, 0.75, 0.95]

=== 2. Kemiripan kosinus pasangan kata ===
  kosinus(raja  , ratu  ) = 0.743
  kosinus(raja  , pria  ) = 0.523
  kosinus(ratu  , wanita) = 0.523
  kosinus(makan , minum ) = 0.980
  kosinus(raja  , makan ) = 0.129
  kosinus(ratu  , minum ) = 0.153

=== 3. Aritmetika vektor: raja - pria + wanita ===
  hasil        = [1.  0.  0.6 0.  0.2]
  vektor ratu  = [1.0, 0.0, 0.6, 0.0, 0.2]
  kosinus(hasil, ratu) = 1.0

=== 4. One-hot: 6 dimensi tanpa makna ===
  kosinus(raja, ratu) [one-hot] = 0.000
  kosinus(raja, pria) [one-hot] = 0.000
  kosinus(raja, makan) [one-hot] = 0.000
  nilai non-nol dalam satu vektor one-hot: 1

Mari kita telaah kode di atas baris per baris:

  1. Baris 13-21 (NAMA_FITUR, EMBEDDING): setiap kata menyimpan lima angka dengan urutan fitur yang sama. “raja” kuat pada dimensi bangsawan dan maskulin, “makan” kuat pada dimensi makanan dan aktivitas — perbedaan aspek makna inilah yang nanti terbaca sebagai jarak.
  2. Baris 26-30 (kemiripan_kosinus): np.dot menghitung perkalian titik, np.linalg.norm menghitung panjang masing-masing vektor. Pembagian keduanya menormalkan hasil sehingga kata dengan angka besar tidak otomatis tampak “lebih mirip”.
  3. Baris 39-46: enam pasangan diuji — dua pasangan makna berdekatan, dua pasangan berbagi gender, dua pasangan lintas kategori.
  4. Baris 49-58: operasi pengurangan dan penjumlahan elemen demi elemen menghasilkan vektor baru; kosinus terhadap “ratu” menilai seberapa dekat hasil tersebut dengan jawaban yang diharapkan.
  5. Baris 61-62 (np.eye(V)): matriks identitas 6×6 memberi satu vektor one-hot per kata — satu angka 1 dan lima angka 0.

Keluaran bagian kedua membentuk pola yang masuk akal secara semantik: “raja” paling mirip “ratu” (0,743), “makan” sangat mirip “minum” (0,980), dan pasangan lintas kategori seperti “raja”-“makan” jatuh ke 0,129. Menariknya, “raja”-“pria” dan “ratu”-“wanita” sama-sama bernilai 0,523 — keduanya berbagi satu dimensi gender — sehingga struktur ruang vektor konsisten dengan intuisi kita tentang hubungan kata.

Bagian ketiga memperlihatkan sifat paling menarik dari embedding: makna sebagai arah. Vektor raja - pria + wanita menghasilkan [1.0, 0.0, 0.6, 0.0, 0.2] — tepat vektor “ratu” dengan kosinus 1.0. Operasi “raja dikurangi pria” mengambil arah bangsawan murni tanpa komponen gender; menambahkan “wanita” memasangkan arah itu kembali dengan gender berlawanan. Pada embedding nyata yang dilatih dari korpus besar, relasi analogi semacam ini bekerja secara statistik — tidak pernah persis 1.0, tetapi cukup dekat untuk mencari pasangan kata lewat aritmetika vektor.

One-hot versus Embedding #

Keluaran bagian keempat menunjukkan kegagalan one-hot secara gamblang: semua pasangan kata bernilai kosinus 0.000 karena setiap vektor one-hot tegak lurus terhadap semua yang lain — “raja” sama jauhnya dari “ratu” maupun dari “makan”. Perbandingan keduanya dirangkum pada tabel berikut:

AspekOne-hotEmbedding
Dimensisebesar vocabulary V (ribuan-jutaan)tetap kecil (d = 50-300)
Kepadatan1 nilai non-nol, sisanya nolseluruh sel terisi angka
Jarak antar katasemua pasangan sama (tegak lurus)bervariasi sesuai kemiripan makna
Aritmetika maknatidak dimungkinkananalogi seperti raja - pria + wanita bekerja

Embedding contoh kita dirakit enam kata secara manual. Pertanyaan berikutnya adalah bagaimana angka-angka seperti ini diperoleh secara otomatis dari korpus berisi jutaan kalimat — dan itulah pekerjaan Word2Vec yang akan kita bahas, lengkap dengan dua arsitektur pelatihannya serta varian fastText yang ramah terhadap imbuhan bahasa Indonesia.