Perhitungan cosine similarity membagi hasil perkalian dua vektor dengan panjang keduanya, dan pembagian itu dapat dihilangkan dengan menyiapkan vektor dalam bentuk ternormalisasi. Topik ini membahas cara normalisasi vektor, alasan vektor ternormalisasi membuat dot product dan cosine similarity menghasilkan nilai yang identik, serta bukti bahwa peringkat keduanya sama melalui satu skrip NumPy dan satu demo interaktif.
Normalisasi vektor#
Normalisasi membagi setiap komponen vektor dengan panjang vektornya. Panjang vektor dihitung sebagai akar kuadrat dari jumlah kuadrat komponennya. Hasil pembagian disebut vektor satuan, yaitu vektor dengan panjang tepat satu yang arahnya tetap sama dengan vektor asal.
Normalisasi hanya menyentuh panjang, bukan arah. Perbandingan antar komponen tetap terjaga, sehingga sudut antar vektor tidak berubah dan semua peringkat yang ditentukan oleh sudut tetap berlaku setelah normalisasi.
Sifat yang menghubungkan dot product dan cosine similarity muncul langsung dari rumus cosine. Pembagi rumus cosine adalah hasil kali panjang kedua vektor. Apabila kedua vektor sudah berpanjang satu, hasil kali pembaginya bernilai satu dan dot product langsung bernilai sama dengan cosine similarity.
Kesetaraan ini berlaku untuk setiap pasangan vektor satuan, termasuk pasangan yang nilainya negatif karena arah keduanya berlawanan. Dalam praktik, normalisasi dilakukan sekali pada saat embedding dihitung atau disimpan, sehingga biaya pembagiannya tidak muncul kembali pada setiap query. Koleksi yang seluruh vektornya sudah berbentuk satuan membuat vektor query cukup dinormalisasi satu kali di awal setiap pencarian.
Peringkat yang identik#
Mesin pencari vektor menentukan jawaban dari urutan skor, bukan dari nilai mentahnya. Kesetaraan nilai tersebut menimbulkan konsekuensi praktis pada peringkat. Pada koleksi yang seluruh vektornya ternormalisasi, pencarian dengan dot product menghasilkan peringkat yang sama dengan pencarian cosine similarity karena kedua ukuran bernilai sama untuk setiap pasangan vektor. Biaya perhitungannya juga berkurang karena pembagian panjang tidak perlu dilakukan lagi.
Sifat kedua berkaitan dengan kestabilan peringkat. Cosine similarity kebal terhadap skala, yaitu mengalikan satu vektor dengan konstanta positif tidak mengubah nilai cosine-nya karena konstanta tersebut muncul sama besar di pembilang dan penyebut. Akibatnya, normalisasi tidak menggeser posisi dokumen mana pun dalam peringkat cosine, dan urutan terbaik hingga terburuk sebelum serta sesudah normalisasi adalah urutan yang sama.
Kedua sifat ini menjelaskan mengapa banyak sistem pencarian vektor menyimpan embedding dalam bentuk ternormalisasi sejak awal dan menjadikan dot product sebagai satu-satunya ukuran yang dihitung ketika query dijalankan.
Percobaan dengan NumPy#
Skrip berikut menyiapkan satu vektor query, yaitu vektor yang menjadi acuan pencarian, dan empat vektor dokumen dengan panjang yang berbeda-beda. Skrip menghitung cosine similarity terhadap query, dot product mentah tanpa normalisasi, dan dot product setelah seluruh vektor dinormalisasi.
import numpy as np
q = np.array([1.0, 1.0])
dokumen = {
"d1": np.array([2.0, 2.0]),
"d2": np.array([2.0, 0.0]),
"d3": np.array([0.5, 3.0]),
"d4": np.array([-1.0, 2.0]),
}
def cosine(u, v):
return np.dot(u, v) / (np.linalg.norm(u) * np.linalg.norm(v))
def normalisasi(v):
return v / np.linalg.norm(v)
id_dok = list(dokumen)
qn = normalisasi(q)
print(f"{'dokumen':<9} {'cosine':>7} {'dot mentah':>10} {'dot ternormalisasi':>18}")
for nama in id_dok:
d = dokumen[nama]
dn = normalisasi(d)
print(f"{nama:<9} {cosine(q, d):7.4f} {np.dot(q, d):10.4f} {np.dot(qn, dn):18.4f}")
urutan_cos = list(np.argsort([-cosine(q, dokumen[n]) for n in id_dok]))
urutan_dot_n = list(np.argsort([-np.dot(qn, normalisasi(dokumen[n])) for n in id_dok]))
print("peringkat cosine :", ", ".join(id_dok[i] for i in urutan_cos))
print("peringkat dot ternormalisasi :", ", ".join(id_dok[i] for i in urutan_dot_n))dokumen cosine dot mentah dot ternormalisasi
d1 1.0000 4.0000 1.0000
d2 0.7071 2.0000 0.7071
d3 0.8137 3.5000 0.8137
d4 0.3162 1.0000 0.3162
peringkat cosine : d1, d3, d2, d4
peringkat dot ternormalisasi : d1, d3, d2, d4Kolom dot ternormalisasi dan kolom cosine bernilai sama untuk keempat dokumen, termasuk d2 yang vektornya sejajar dengan sumbu horizontal dan d4 yang arahnya menjauhi query. Kolom dot mentah menampilkan perilaku yang berbeda, misalnya d1 menghasilkan 4,0000 karena vektornya panjang, bukan karena arahnya paling tepat. Pada data nyata, perbedaan semacam ini muncul ketika dokumen panjang dan pendek dicampur dalam satu koleksi, karena model teks menghasilkan vektor yang panjangnya bervariasi mengikuti masukannya. Dua baris peringkat di bagian bawah keluaran menegaskan kesimpulan utamanya, yaitu peringkat cosine dan peringkat dot product ternormalisasi adalah urutan yang sama. Kesetaraan ini juga tidak bergantung pada jumlah dimensi, karena pembagian panjang berlaku sama pada vektor dua dimensi maupun pada vektor ribuan dimensi hasil model teks.
Demo interaktif#
Demo berikut menampilkan dua vektor satuan pada bidang dua dimensi. Geser kedua penggeser untuk mengubah sudut, lalu amati tiga nilai yang diperbarui secara langsung, yaitu cosine similarity, dot product pada vektor ternormalisasi, dan jarak L2. Nilai cosine similarity dan dot product selalu identik karena kedua vektor berpanjang satu, sedangkan jarak L2 ikut naik ketika sudut antara keduanya melebar.
Ketika kedua vektor searah, ketiga nilai menampilkan 1,0000, 1,0000, dan 0,0000, sedangkan ketika keduanya berlawanan arah, cosine similarity dan dot product turun menjadi -1,0000 dan jarak L2 mencapai 2,0000. Hubungan jarak L2 dengan cosine pada vektor satuan berbentuk rumus berikut, sehingga keduanya menyimpan informasi yang sama.
Rumus itu juga menjelaskan mengapa jarak L2 pada vektor satuan selalu berada pada rentang 0 hingga 2. Perubahan sudut mengubah nilai cosine, dan nilai tersebut langsung menentukan panjang garis penghubung kedua vektor. Apabila kedua sudut digeser hingga bernilai sama, jarak L2 mengecil menuju 0,0000 seiring kedua vektor saling menumpuk pada arah yang sama.
Kesalahan umum#
Tiga kesalahan berikut sering muncul ketika normalisasi diterapkan pada koleksi yang sudah berjalan.
- Menyamakan dot product mentah dengan cosine similarity pada vektor yang belum ternormalisasi membuat peringkat bergeser tanpa tanda, sebagaimana terlihat pada kolom dot mentah dalam percobaan di atas. Perbaikannya, normalisasi kedua sisi terlebih dahulu atau gunakan cosine similarity sebagai metrik pencarian, lalu bandingkan ulang peringkatnya.
- Menormalisasi hanya satu sisi, misalnya dokumen saja, menghasilkan peringkat yang tetap tidak sama dengan cosine similarity karena pembagi panjang belum saling menghilangkan. Pemeriksaan sederhananya, pastikan panjang setiap vektor bernilai satu sebelum pencarian dijalankan. Perbaikannya, terapkan normalisasi pada vektor query dan pada seluruh vektor dokumen agar keduanya berbentuk satuan.
- Menganggap normalisasi mengubah peringkat cosine similarity menimbulkan kekhawatiran yang tidak perlu, karena normalisasi hanya menyentuh panjang dan tidak mengubah sudut. Peringkat cosine sebelum dan sesudah normalisasi selalu sama. Perbaikannya, perlakukan normalisasi sebagai penyiapan bentuk data, bukan perubahan makna geometrisnya.
Ringkasan#
- Normalisasi membagi vektor dengan panjangnya sehingga menghasilkan vektor satuan tanpa mengubah arah.
- Pada vektor ternormalisasi, dot product dan cosine similarity bernilai sama dan menghasilkan peringkat yang sama.
- Cosine similarity kebal terhadap skala, sehingga normalisasi tidak menggeser peringkat dokumen.
- pgvector menyimpan cosine distance sebagai 1 dikurangi cosine similarity dan menyediakan fungsi
l2_normalize().
Referensi#
- pgvector — GitHub
— Fungsi
l2_normalize()dan operator jarak beserta definisi cosine distance. - Pencarian — Qdrant — Perbedaan metrik cosine dan dot product pada pencarian vektor.