Kemiripan Kosinus

Pengukuran kemiripan vektor dalam praktik.

Daftar isi

Kemiripan kosinus mengukur kemiripan dua vektor melalui sudut di antara keduanya, tanpa memperhatikan panjang masing-masing vektor. Topik ini membahas alasan sudut lebih tepat daripada dot product mentah sebagai ukuran kemiripan, perhitungannya dengan NumPy, dan penggunaannya pada pencarian dokumen serta pencocokan embedding. Setelah topik ini dibaca, kemiripan dua vektor dapat dihitung dan dibandingkan dengan benar.

Sudut sebagai ukuran kemiripan#

Membagi dot product dua vektor dengan hasil kali normanya menghasilkan ukuran kemiripan yang hanya bergantung pada arah, yaitu

cos⁡θ=x⃗⋅y⃗∥x⃗∥ ∥y⃗∥ \cos\theta = \frac{\vec{x} \cdot \vec{y}}{\|\vec{x}\| \, \|\vec{y}\|}

Karena kosinus selalu berada di antara -1 dan 1, nilainya dapat dibaca langsung sebagai skala kemiripan arah. Gambar berikut menampilkan dua vektor yang membentuk sudut 45 derajat. Pada sudut itu, cos⁡θ=22≈0,7071\cos\theta = \frac{\sqrt{2}}{2} \approx 0{,}7071, suatu nilai di antara searah penuh dan tegak lurus.

Sudut 45 derajat antara dua vektor

Tabel berikut merangkum pembacaan nilai kemiripannya.

Nilai kemiripanMakna arah
1Kedua vektor searah penuh
Mendekati 1Kedua vektor cenderung searah
0Kedua vektor tegak lurus, tidak ada keselarasan
-1Kedua vektor berlawanan arah

Pembagian oleh norma inilah yang membedakan ukuran ini dari dot product biasa. Dot product mentah membesar ketika salah satu vektornya dipanjangkan, padahal arahnya tidak berubah. Ukuran kosinus menghilangkan pengaruh panjang tersebut sehingga hanya sudut yang menentukan nilainya.

Ukuran jarak Euclidean kurang tepat untuk membandingkan isi dokumen karena dokumen panjang cenderung berjarak jauh dari dokumen pendek. Kemiripan kosinus mengatasi hal tersebut dengan membuang informasi panjang sejak awal. Dua versi tulisan yang sama, yaitu ringkas dan lengkap, berjarak jauh secara Euclidean tetapi bersudut nol dan kemiripannya satu. Perbandingan berbasis sudut juga tidak memerlukan penyesuaian apa pun ketika panjang vektornya berbeda jauh.

Perhitungan dengan NumPy#

Berkas kemiripan_kosinus.py berikut menghitung kemiripan kosinus dari tiga vektor frekuensi kata. Vektor tersebut berasal dari catatan sensor dengan urutan komponen suhu, angin, dan hujan. Vektor a dan b sama-sama menonjolkan suhu dan angin, sedangkan vektor c hanya mencatat hujan.

python kemiripan_kosinus.py
import numpy as np

def kemiripan_kosinus(x, y):
    pembilang = np.dot(x, y)
    penyebut = np.linalg.norm(x) * np.linalg.norm(y)
    return pembilang / penyebut

# frekuensi kata dari catatan sensor, urutan kata: (suhu, angin, hujan)
sensor_a = np.array([3.0, 2.0, 0.0])
sensor_b = np.array([2.0, 3.0, 0.0])
sensor_c = np.array([0.0, 0.0, 4.0])

print("kemiripan(a, b)  =", round(kemiripan_kosinus(sensor_a, sensor_b), 4))
print("kemiripan(a, c)  =", round(kemiripan_kosinus(sensor_a, sensor_c), 4))

# kemiripan kosinus kebal terhadap panjang vektor
print("kemiripan(a, 5b) =", round(kemiripan_kosinus(sensor_a, 5 * sensor_b), 4))

# pembanding: dot product biasa ikut berubah oleh panjang vektor
print("dot(a, b)        =", np.dot(sensor_a, sensor_b))
print("dot(a, 5b)       =", np.dot(sensor_a, 5 * sensor_b))
text Keluaran
kemiripan(a, b)  = 0.9231
kemiripan(a, c)  = 0.0
kemiripan(a, 5b) = 0.9231
dot(a, b)        = 12.0
dot(a, 5b)       = 60.0

Nilai kemiripan a dan b yang tinggi, yaitu 0,9231, sesuai dengan isi kedua catatan yang sama-sama membahas suhu dan angin. Kemiripan a dengan c bernilai nol karena kedua vektornya saling tegak lurus, yaitu tidak ada satu pun kata yang muncul pada kedua catatan tersebut. Baris ketiga menunjukkan ketahanan ukuran ini terhadap panjang, sebab kemiripan a dengan 5b tetap 0,9231. Dot product mentah pada dua baris terakhir justru naik lima kali lipat dari 12 menjadi 60 ketika b dipanjangkan, dan di situlah letak kekurangannya sebagai ukuran kemiripan. Fungsi kemiripan_kosinus pada skrip memuat rumusnya apa adanya, yaitu np.dot pada pembilang dan hasil kali np.linalg.norm pada penyebutnya.

Demo interaktif#

Demo berikut mengatur sudut antara vektor a dan vektor b dengan penggeser. Kedua vektor digambar berpanjang satu pada lingkaran satuan, sehingga perubahan sudut tidak disertai perubahan panjang. Geser penggeser dari 0 hingga 180 derajat, kemudian amati nilai kosinusnya menurun dari 1 hingga -1.

Kode sumber demo
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="utf-8">
<title>Demo sudut dan kemiripan kosinus</title>
<style>
  body { font-family: sans-serif; margin: 12px; }
  canvas { border: 1px solid #ccc; display: block; margin-bottom: 8px; background: #fff; }
  p { margin: 6px 0; }
  label { font-size: 14px; }
  input { vertical-align: middle; }
  .nilai { font-weight: bold; }
</style>
</head>
<body>
<canvas id="kanvas" width="440" height="260"></canvas>
<p>
  <label>Sudut antara a dan b (derajat):
    <input type="range" id="sudut" min="0" max="180" value="45">
    <span class="nilai" id="tampilan-sudut">45</span>
  </label>
</p>
<p class="nilai" id="kesimpulan"></p>
<script>
  const kvs = document.getElementById("kanvas").getContext("2d");
  const lebar = 440, tinggi = 260;
  const cx = 110, cy = 130;       // titik nol agak ke kiri agar label lega
  const r = 75;                   // jari-jari lingkaran satuan

  function panah(x1, y1, warna, label) {
    // vektor selalu berpangkal di titik nol dan berpanjang satu
    const X1 = cx + x1 * r, Y1 = cy - y1 * r;
    kvs.strokeStyle = warna;
    kvs.fillStyle = warna;
    kvs.lineWidth = 2;
    kvs.beginPath();
    kvs.moveTo(cx, cy);
    kvs.lineTo(X1, Y1);
    kvs.stroke();
    const sudut = Math.atan2(Y1 - cy, X1 - cx);
    kvs.beginPath();
    kvs.moveTo(X1, Y1);
    kvs.lineTo(X1 - 9 * Math.cos(sudut - 0.4), Y1 - 9 * Math.sin(sudut - 0.4));
    kvs.lineTo(X1 - 9 * Math.cos(sudut + 0.4), Y1 - 9 * Math.sin(sudut + 0.4));
    kvs.closePath();
    kvs.fill();
    kvs.fillText(label, X1 + 6, Y1 - 6);
  }

  function gambar() {
    const derajat = Number(document.getElementById("sudut").value);
    const theta = derajat * Math.PI / 180;
    const a = [Math.cos(theta), Math.sin(theta)];

    kvs.clearRect(0, 0, lebar, tinggi);

    // sumbu koordinat
    kvs.strokeStyle = "#999";
    kvs.lineWidth = 1;
    kvs.beginPath(); kvs.moveTo(0, cy); kvs.lineTo(lebar, cy); kvs.stroke();
    kvs.beginPath(); kvs.moveTo(cx, 0); kvs.lineTo(cx, tinggi); kvs.stroke();

    // lingkaran satuan
    kvs.strokeStyle = "#ccc";
    kvs.beginPath();
    kvs.arc(cx, cy, r, 0, 2 * Math.PI);
    kvs.stroke();

    // busur sudut antara a dan b
    kvs.strokeStyle = "#555";
    kvs.beginPath();
    kvs.arc(cx, cy, 26, -theta, 0);
    kvs.stroke();

    panah(1, 0, "#1a56b0", "b");
    panah(a[0], a[1], "#d97706", "a");

    const kos = Math.cos(theta);
    let sifat = "berlawanan arah";
    if (kos > 0.9) {
      sifat = "hampir searah";
    } else if (kos > 0.3) {
      sifat = "searis";
    } else if (kos >= -0.3) {
      sifat = "hampir tegak lurus";
    } else if (kos >= -0.9) {
      sifat = "cenderung berlawanan";
    }
    document.getElementById("tampilan-sudut").textContent = String(derajat);
    document.getElementById("kesimpulan").textContent =
      "kosinus = " + kos.toFixed(2).replace(".", ",") + "  →  " + sifat;
  }

  document.getElementById("sudut").addEventListener("input", gambar);
  gambar();
</script>
</body>
</html>
Sudut awal penggeser disetel pada 45 derajat, sama seperti pada diagram di bagian pertama, sehingga nilai kosinus yang tampil adalah sekitar 0,71. Kode demonya memperlihatkan bahwa kosinus dihitung langsung dari sudut penggeser tanpa proses tambahan. Kosinus menurun secara mulus sepanjang pergeseran tanpa lompatan pada sudut tertentu. Sifat kontinu ini membuat kemiripan kosinus nyaman digunakan sebagai angka peringkat, karena selisih kecil pada sudut selalu menghasilkan selisih kecil pada kemiripannya.

Di mana hal ini ditemui dalam kode#

Pencarian dokumen menggunakan kemiripan kosinus sebagai ukuran peringkat utama. Setiap dokumen direpresentasikan sebagai vektor frekuensi kata, kueri pencarian diubah menjadi vektor serupa, lalu dokumen diurutkan menurut kemiripan kosinusnya dengan kueri. Panjang dokumen tidak menggeser peringkat karena ukurannya hanya melihat arah, dan dokumen panjang pun berpeluang menyamai dokumen pendek yang bertema sama. Mesin pencari internal pada aplikasi catatan atau dokumentasi perangkat lunak kerap menggunakan skema sederhana ini sebagai langkah pertamanya.

Embedding modern melanjutkan ide yang sama. Kata, kalimat, dan gambar dipetakan menjadi vektor berdimensi tinggi, kemudian pencarian tetangga terdekat membandingkan vektor-vektor tersebut dengan kemiripan kosinus. Ukuran ini dipilih karena panjang vektor embedding dalam praktiknya tidak membawa makna, sedangkan arahnya yang memuat makna. Pencocokan preferensi pada sistem rekomendasi juga menggunakan ukuran ini, yaitu membandingkan vektor riwayat pilihan dengan vektor karakteristik setiap item, dan item dengan kemiripan tertinggi ditempatkan paling depan.

Ringkasan#

  • Kemiripan kosinus adalah dot product dua vektor dibagi hasil kali normanya, sehingga panjang vektor tidak memengaruhi nilainya.
  • Nilainya berada di antara -1 dan 1, dengan 1 untuk searah, 0 untuk tegak lurus, dan -1 untuk berlawanan arah.
  • Penggandaan salah satu vektor dengan skalar positif tidak mengubah kemiripannya, sedangkan dot product mentah ikut berubah.
  • Pada vektor satuan, kemiripan kosinus sama dengan dot product biasa.

Referensi#