NumPy: the absolute basics for beginners
NumPy secukupnya untuk bekerja dengan embedding: array, operasi vektor, dot product, dan cosine similarity.
Intisari
- Untuk AI engineer, NumPy dipakai hampir hanya untuk satu hal: bekerja dengan embedding.
- Array adalah vektor; operasi matematika berlaku ke seluruh elemen sekaligus, tanpa loop.
np.dot()dannp.linalg.norm()adalah dua fungsi yang membentuk cosine similarity.- Operasi vektorisasi ratusan kali lebih cepat dari loop Python โ dan itu terasa nyata saat mencari di ribuan chunk.
- Lewati: indexing lanjutan, linear algebra dalam, struktured array.
Kenapa kamu butuh NumPy
Embedding adalah daftar angka โ biasanya 768 sampai 3.072 dimensi. Retrieval di RAG berarti membandingkan satu vektor query dengan ribuan vektor dokumen. Kalau dilakukan dengan list Python biasa, itu terlalu lambat.
uv add numpy
Array
import numpy as np
a = np.array([1.0, 2.0, 3.0])
a.shape # (3,)
a.dtype # dtype('float64')
a.ndim # 1
m = np.array([[1, 2, 3], [4, 5, 6]])
m.shape # (2, 3) โ 2 baris, 3 kolom
np.zeros(5)
np.ones((2, 3))
np.arange(0, 10, 2) # [0 2 4 6 8]
np.random.rand(768) # vektor acak, untuk uji coba
Operasi berlaku ke seluruh elemen
a = np.array([1.0, 2.0, 3.0])
b = np.array([4.0, 5.0, 6.0])
a + b # [5. 7. 9.]
a * 2 # [2. 4. 6.]
a * b # [4. 10. 18.] elemen-per-elemen
a > 2 # [False False True]
a.sum() # 6.0
a.mean() # 2.0
a.max() # 3.0
a.argmax() # 2 โ INDEKS dari nilai terbesar
argmax dan argsort adalah yang paling sering kamu pakai di RAG.
Kamu tidak butuh nilai skor tertingginya, tapi dokumen mana yang punya skor
itu โ dan itu adalah indeksnya.
Cosine similarity
import numpy as np
def cosine_similarity(a: np.ndarray, b: np.ndarray) -> float:
return float(np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b)))
Membaca rumusnya:
np.dot(a, b)โ dot product: jumlah dari hasil kali tiap pasangan elemennp.linalg.norm(a)โ panjang vektor- Hasilnya antara โ1 dan 1. Semakin dekat ke 1, semakin mirip arahnya.
Kenapa cosine, bukan jarak Euclidean: cosine mengukur arah, bukan besaran. Dua dokumen yang membahas topik sama tapi panjangnya berbeda akan punya vektor dengan arah serupa namun panjang berbeda. Cosine menganggapnya mirip; Euclidean tidak.
Mencari di banyak vektor sekaligus
def cari_teratas(query: np.ndarray, dokumen: np.ndarray, k: int = 5) -> np.ndarray:
"""query: (768,) dokumen: (N, 768) โ indeks k dokumen paling mirip."""
q = query / np.linalg.norm(query)
d = dokumen / np.linalg.norm(dokumen, axis=1, keepdims=True)
skor = d @ q # (N,) โ satu skor per dokumen
return np.argsort(skor)[::-1][:k] # urut menurun, ambil k teratas
Perhatikan: tidak ada loop. Satu operasi matriks d @ q menghitung
kemiripan untuk seluruh dokumen sekaligus. Untuk 10.000 chunk, versi loop Python
butuh detik; versi ini butuh milidetik.
Menormalisasi di awal
# Simpan vektor yang sudah dinormalisasi saat ingest
embedding_norm = embedding / np.linalg.norm(embedding, axis=1, keepdims=True)
# Setelah itu, cosine similarity cukup dot product
skor = embedding_norm @ (query / np.linalg.norm(query))
Ini trik standar: normalisasi sekali saat menyimpan, lalu setiap pencarian jadi lebih murah.
Broadcasting
m = np.array([[1, 2, 3],
[4, 5, 6]]) # (2, 3)
m * 2 # skalar disebarkan ke semua elemen
m + np.array([10, 20, 30]) # (3,) disebarkan ke tiap baris
m / np.array([[1], [2]]) # (2,1) disebarkan ke tiap kolom
Aturannya: NumPy menyejajarkan bentuk dari kanan, dan dimensi berukuran 1 "diregangkan".
Itulah kenapa keepdims=True ada di contoh normalisasi โ supaya bentuknya
(N, 1) dan bisa dibagi ke (N, 768).
Bentuk (shape) adalah sumber bug utama
a.reshape(2, 3)
a.reshape(-1, 768) # -1 = "hitung sendiri"
a.flatten() # jadikan 1 dimensi
a[np.newaxis, :] # (768,) โ (1, 768)
a.T # transpose
Saat sesuatu tidak jalan, cetak .shape-nya. Sembilan dari sepuluh error
NumPy adalah ketidakcocokan bentuk. print(f"{a.shape=} {b.shape=}") โ
memakai trik = dari Fase 1 โ biasanya langsung menunjukkan masalahnya.
Indexing
a[0] # elemen pertama
a[-1] # terakhir
a[1:4] # slice
m[0, 2] # baris 0, kolom 2
m[:, 1] # seluruh kolom ke-1
m[0, :] # seluruh baris ke-0
skor[skor > 0.7] # boolean mask โ hanya yang lolos ambang
indeks = np.where(skor > 0.7)[0] # indeks yang lolos
# Pola retrieval dengan ambang batas
lolos = np.where(skor >= AMBANG)[0]
teratas = lolos[np.argsort(skor[lolos])[::-1][:k]]
Berpindah dari dan ke list Python
v = np.array(response.embedding) # list[float] โ array
v.tolist() # array โ list[float], siap di-JSON
json.dumps tidak bisa menserialisasi array NumPy. Konversi dengan
.tolist() sebelum menyimpan ke database atau mengirim lewat API.
Tipe data
np.array([1.0, 2.0], dtype=np.float32) # setengah memori dari float64
embedding.astype(np.float32)
Untuk embedding, float32 hampir selalu cukup dan menghemat separuh memori โ
penting saat kamu menyimpan jutaan vektor.
Yang bisa dilewati
- Fancy indexing dan indexing lanjutan
- Modul
linalgselainnorm - Structured array dan record array
np.matrix(sudah usang)- Universal function (ufunc) kustom
- Memory mapping dan I/O array
Kalau nanti kamu benar-benar butuh, dokumentasinya ada. Untuk roadmap ini, apa yang di atas sudah cukup.
Rangkuman ini sengaja dipangkas ke bagian yang dipakai di roadmap. Buka sumber aslinya saat kamu butuh detail lengkap atau referensi parameter.