Handbook AI Engineering
Bagian II - Bab 9

Text Embedding dan Representasi Semantik

Bagaimana teks direpresentasikan sebagai vektor makna, cosine similarity, dan cara memilih model embedding yang tepat.

Apa yang Akan Kamu Pahami Setelah Bab Ini

Bab ini menjelaskan konsep yang menjadi jembatan antara teks yang manusia baca dan angka yang komputer proses: embedding. Memahami embedding secara konseptual adalah prasyarat untuk memahami cara kerja vector database dan RAG di bab-bab berikutnya. Tanpa pemahaman ini, sistem RAG akan terasa seperti kotak hitam yang sulit di-debug ketika hasilnya tidak sesuai ekspektasi.

9.1 Masalah yang Diselesaikan Embedding

Komputer tidak bisa memproses teks secara langsung. Teks harus dikonversi ke angka terlebih dahulu. Pertanyaannya adalah: cara konversi seperti apa yang bisa mempertahankan makna teks, bukan sekadar representasi teknisnya?

Pendekatan paling sederhana yang pernah digunakan adalah Bag-of-Words (BoW): setiap kata dalam kosakata diberi nomor urut, dan sebuah teks direpresentasikan sebagai vektor yang menghitung berapa kali setiap kata muncul. Pendekatan ini gagal menangkap makna karena dua alasan mendasar.

Pertama, ia tidak mempertimbangkan urutan kata. Kalimat "anjing menggigit orang" dan "orang menggigit anjing" menghasilkan representasi yang identik dalam BoW, padahal maknanya berlawanan.

Kedua, ia tidak memahami hubungan semantik antar kata. "Mobil" dan "kendaraan" akan menjadi dua vektor yang sepenuhnya berbeda meskipun maknanya sangat dekat. "Raja" dan "ratu" tidak memiliki hubungan matematis apapun dalam BoW.

Embedding menyelesaikan kedua masalah ini dengan cara yang elegan: alih-alih merepresentasikan setiap kata sebagai posisi dalam daftar kosakata, embedding merepresentasikan setiap teks sebagai posisi dalam ruang vektor berdimensi tinggi, di mana teks dengan makna serupa berada di posisi yang berdekatan.

9.2 Intuisi Ruang Vektor

Embedding merepresentasikan teks sebagai titik dalam ruang berdimensi tinggi, biasanya ratusan hingga ribuan dimensi. Setiap dimensi merepresentasikan sebuah "fitur" semantik yang dipelajari secara otomatis selama training model embedding, bukan yang didefinisikan secara manual.

Untuk memvisualisasikan konsep ini, bayangkan ruang 2D yang disederhanakan. Dalam ruang ini, kata-kata dengan makna serupa mengelompok berdekatan:

Aritmatika Vektor

Satu hal yang membuat embedding sangat kuat adalah sifat aritmatikanya. Hubungan semantik antar kata bisa direpresentasikan sebagai operasi vektor:

embedding("raja") - embedding("pria") + embedding("wanita") ≈ embedding("ratu").

Ini bukan kebetulan, ini adalah properti yang muncul secara alami dari cara model embedding dilatih. Model belajar menempatkan kata-kata dalam ruang vektor sedemikian rupa sehingga hubungan semantik tercermin dalam hubungan geometris.

9.3 Cosine Similarity: Mengukur Kedekatan Makna

Setelah teks direpresentasikan sebagai vektor, kita butuh cara untuk mengukur seberapa "dekat" dua vektor tersebut. Metrik yang paling umum digunakan adalah Cosine Similarity, yang mengukur sudut antara dua vektor, bukan jarak Euclidean di antara keduanya.

Mengapa sudut, bukan jarak? Karena dalam ruang vektor berdimensi tinggi, teks yang panjang dan pendek dengan makna yang sama akan menghasilkan vektor dengan magnitud berbeda tapi arah yang sama. Cosine similarity hanya memperhatikan arah, sehingga panjang teks tidak mempengaruhi hasil pengukuran.

Berikut implementasi langsung menghitung cosine similarity dan menggunakannya untuk mencari teks yang paling relevan:

Menghitung Cosine Similarity secara Manual

Implementasi cosine similarity dari nol menggunakan NumPy. Berguna untuk memahami mekanismenya sebelum menggunakan library yang sudah mengabstraksinya.

import numpy as np

def cosine_similarity(vektor_a: np.ndarray, vektor_b: np.ndarray) -> float:
    """
    Hitung cosine similarity antara dua vektor.
    Hasil: -1 (berlawanan) hingga 1 (identik).
    Untuk embedding teks, hasilnya biasanya antara 0 dan 1.
    """
    dot_product = np.dot(vektor_a, vektor_b)
    norm_a = np.linalg.norm(vektor_a)
    norm_b = np.linalg.norm(vektor_b)

    # Hindari pembagian dengan nol
    if norm_a == 0 or norm_b == 0:
        return 0.0

    return dot_product / (norm_a * norm_b)

# Contoh dengan vektor sederhana
mobil = np.array([0.2, 0.8, -0.1, 0.5])
kendaraan = np.array([0.19, 0.77, -0.09, 0.48])
demokrasi = np.array([-0.3, 0.1, 0.9, -0.2])

print(cosine_similarity(mobil, kendaraan))  # -> ~0.997 (sangat mirip)
print(cosine_similarity(mobil, demokrasi))  # -> ~0.04 (tidak mirip)

Dengan Sentence Transformers

Menggunakan Sentence Transformers untuk membuat embedding teks nyata dari model lokal tanpa memanggil API eksternal. Cocok untuk prototyping dan penggunaan offline.

# pip install sentence-transformers
from sentence_transformers import SentenceTransformer, util

# Load model embedding lokal
# Model ini diunduh sekali, lalu berjalan offline
model = SentenceTransformer("paraphrase-multilingual-MiniLM-L12-v2")
# Model ini mendukung Bahasa Indonesia!

# Buat embedding untuk beberapa kalimat
kalimat = [
    "Mobil merah itu melaju kencang.",
    "Kendaraan berwarna merah berjalan dengan cepat.",
    "Pemilu diadakan setiap lima tahun sekali.",
]

embeddings = model.encode(kalimat, convert_to_tensor=True)

# Hitung similarity antar semua pasang kalimat
similarity_matrix = util.cos_sim(embeddings, embeddings)

print("Kalimat 1 vs 2:", round(similarity_matrix[0][1].item(), 3))
# -> ~0.92 (sangat mirip, kalimat yang sama, kata berbeda)

print("Kalimat 1 vs 3:", round(similarity_matrix[0][2].item(), 3))
# -> ~0.08 (tidak mirip, topik berbeda sama sekali)

Pencarian Semantik

Mengimplementasikan pencarian semantik sederhana: diberikan query, temukan dokumen mana yang paling relevan berdasarkan cosine similarity. Ini adalah operasi inti dari sistem RAG.

from sentence_transformers import SentenceTransformer, util
import torch

model = SentenceTransformer("paraphrase-multilingual-MiniLM-L12-v2")

# Corpus dokumen yang akan dicari
dokumen = [
    "Kebijakan pengembalian barang berlaku 30 hari setelah pembelian.",
    "Pengiriman gratis untuk pembelian di atas Rp 200.000.",
    "Layanan pelanggan tersedia setiap hari pukul 08.00 - 22.00.",
    "Pembayaran bisa menggunakan kartu kredit, transfer bank, atau dompet digital.",
    "Produk yang rusak akan diganti tanpa biaya tambahan.",
]

# Buat embedding untuk semua dokumen sekali
# Dalam sistem RAG nyata, ini dilakukan saat indexing
embedding_dokumen = model.encode(dokumen, convert_to_tensor=True)

def cari_relevan(query: str, top_k: int = 2) -> list:
    """Temukan dokumen paling relevan untuk query."""
    embedding_query = model.encode(query, convert_to_tensor=True)

    # Hitung similarity query terhadap semua dokumen
    skor = util.cos_sim(embedding_query, embedding_dokumen)[0]

    # Ambil top_k dokumen dengan skor tertinggi
    top_hasil = torch.topk(skor, k=top_k)

    hasil = []
    for skor_val, idx in zip(top_hasil.values, top_hasil.indices):
        hasil.append({
            "dokumen": dokumen[idx],
            "skor": round(skor_val.item(), 3)
        })
    return hasil

# Test pencarian
hasil = cari_relevan("Bagaimana cara mengembalikan produk?")
for h in hasil:
    print(f"[{h['skor']}] {h['dokumen']}")
# [0.847] Kebijakan pengembalian barang berlaku 30 hari setelah pembelian.
# [0.612] Produk yang rusak akan diganti tanpa biaya tambahan.

9.4 Memilih Model Embedding: API vs Lokal

Ada dua pendekatan utama untuk membuat embedding: menggunakan model embedding via API dari penyedia layanan, atau menjalankan model embedding secara lokal di infrastrukturmu sendiri. Masing-masing memiliki trade-off yang berbeda.

Gemini, OpenAI, Cohere

Embedding via API

  • Kualitas tinggi, model besar dilatih pada data masif
  • Tidak butuh GPU, semua komputasi di sisi penyedia
  • Mudah diintegrasikan, satu baris kode sudah cukup
  • Biaya per request, bisa signifikan untuk indexing dokumen besar
  • Data keluar dari sistemmu, perlu dipertimbangkan untuk data sensitif
  • Bergantung koneksi, tidak bisa digunakan offline
Sentence Transformers, FastEmbed

Embedding Lokal

  • Gratis setelah diunduh, tidak ada biaya per request
  • Data tetap lokal, tidak keluar dari infrastrukturmu
  • Bisa offline, tidak bergantung koneksi internet
  • Kualitas lebih rendah, model kecil biasanya kalah dari API besar
  • Butuh resource: RAM dan opsional GPU untuk performa optimal
  • Setup lebih kompleks, perlu memilih dan mengelola model sendiri
Model Tipe Dimensi Dukungan Bahasa Indonesia Cocok untuk
text-embedding-3-small (OpenAI)API1536BaikPrototyping, produksi umum
text-embedding-004 (Google Gemini)API768BaikIntegrasi ekosistem Google
multilingual-MiniLM-L12-v2Lokal384Sangat baik (50+ bahasa)Prototyping lokal, data sensitif
all-MiniLM-L6-v2 (Sentence Transformers)Lokal384Terbatas (mayoritas Inggris)Kecepatan tinggi, teks Inggris

Kesalahan Umum

Jangan Mencampur Model Embedding yang Berbeda dalam Satu Vector Database

Embedding dari model yang berbeda tidak bisa dibandingkan, mereka hidup di ruang vektor yang berbeda. Jika kamu menggunakan Gemini embedding untuk mengindeks dokumen, kamu harus menggunakan Gemini embedding yang sama untuk mengubah query menjadi vektor saat pencarian. Mencampur model akan menghasilkan hasil retrieval yang acak dan tidak bermakna.

Pilih Model Embedding yang Mendukung Bahasa Indonesia

Tidak semua model embedding memiliki kemampuan yang sama untuk semua bahasa. Model yang dilatih mayoritas pada teks bahasa Inggris akan menghasilkan embedding yang buruk untuk teks Bahasa Indonesia, kata-kata yang bermakna mirip tidak akan berada di posisi yang berdekatan dalam ruang vektor. Gunakan model multilingual seperti multilingual-MiniLM atau model dari penyedia yang secara eksplisit mendukung Bahasa Indonesia.

Embedding Menangkap Makna Semantik, Bukan Kata Kunci

Dua kalimat bisa memiliki similarity tinggi meski tidak berbagi satu kata pun, dan dua kalimat bisa memiliki similarity rendah meski berbagi banyak kata yang sama jika konteksnya berbeda. Jangan berasumsi pencarian berbasis embedding bekerja seperti pencarian kata kunci konvensional.

Gunakan Model Lokal untuk Prototyping, API untuk Produksi

Saat membangun dan menguji sistem RAG, gunakan Sentence Transformers lokal agar tidak mengeluarkan biaya API setiap kali kamu mengubah kode dan menjalankan ulang. Setelah sistem stabil dan siap produksi, evaluasi apakah kualitas embedding dari API yang lebih besar memberikan peningkatan yang sepadan dengan biayanya. Kode untuk keduanya hampir identik, hanya baris inisialisasi model yang perlu diubah.

Ringkasan Bab 9

Checklist Pemahaman
  • Saya memahami mengapa Bag-of-Words gagal menangkap makna semantik, dan bagaimana embedding menyelesaikan masalah tersebut.
  • Saya bisa menjelaskan intuisi ruang vektor: teks dengan makna serupa berada di posisi yang berdekatan dalam ruang berdimensi tinggi.
  • Saya memahami cara kerja Cosine Similarity dan mengapa ia mengukur sudut antar vektor, bukan jarak Euclidean.
  • Saya bisa menghitung embedding teks menggunakan Sentence Transformers dan menghitung cosine similarity antar dua teks.
  • Saya bisa mengimplementasikan pencarian semantik sederhana: mencari dokumen paling relevan terhadap sebuah query berdasarkan cosine similarity.
  • Saya memahami trade-off antara embedding via API dan embedding lokal: kualitas, biaya, privasi data, dan ketergantungan koneksi.
  • Saya tahu bahwa model embedding harus konsisten antara proses indexing dan proses query, tidak boleh mencampur model yang berbeda.
  • Saya memahami pentingnya memilih model embedding yang mendukung Bahasa Indonesia untuk sistem yang akan digunakan dalam Bahasa Indonesia.