Kemiripan dokumen

TF-IDF dan ruang embedding untuk pengelompokan serta deteksi dokumen hampir duplikat.

Daftar isi

Kemiripan dokumen mengukur seberapa dekat dua dokumen pada ruang vektornya, dan ukuran itu menjadi dasar deteksi dokumen hampir duplikat serta pengelompokan sederhana. Topik ini membahas kemiripan kosinus pada vektor TF-IDF, menampilkan matriks kemiripan penuh pada tujuh catatan sintetis yang memuat satu pasangan hampir duplikat, lalu menutupnya dengan konsep ruang embedding untuk kemiripan semantik. Seluruh keluaran demo diperoleh dari eksekusi nyata sehingga dapat diulang satu per satu.

Kemiripan kosinus pada vektor TF-IDF#

TF-IDF mengubah setiap dokumen menjadi satu vektor dengan satu dimensi untuk setiap kata kosakata, dan nilai di dalamnya adalah bobot TF-IDF kata itu pada dokumen tersebut. Dua dokumen yang memuat kata yang sama dengan bobot serupa memiliki arah vektor yang berdekatan, sedangkan dua dokumen tanpa kata bersama berdiri saling tegak lurus. Kemiripan antara dua vektor diukur dengan kosinus sudutnya.

cos⁡(θ)=a⋅b∥a∥ ∥b∥ \cos(\theta) = \frac{\mathbf{a} \cdot \mathbf{b}}{\lVert \mathbf{a} \rVert\, \lVert \mathbf{b} \rVert}

Skor kosinus bernilai antara nol dan satu pada vektor TF-IDF, karena seluruh bobotnya positif. Skor satu berarti kedua vektor searah, skor mendekati satu berarti kedua dokumen hampir sama, dan skor nol berarti kedua dokumen tidak berbagi satu kata pun. Sudut kecil pada diagram berikut menggambarkan dua dokumen yang mirip, karena vektornya hampir searah.

dokumen Adokumen Bθ kecilkedua vektor berangkat dari titik asal ruang kosakata
Kemiripan dua dokumen sebagai sudut antara dua vektor

Kosinus dipilih daripada jarak biasa karena panjang dokumen tidak memengaruhinya. Dokumen panjang memiliki bobot total yang besar, tetapi kosinus hanya membaca arah vektor setelah panjang vektornya dinormalisasi pada pembagian di rumus. Akibatnya, menyalin dokumen yang sama berkali-kali tidak menggeser skornya karena arah vektornya tetap.

Menemukan dokumen hampir duplikat#

Dokumen hampir duplikat adalah pasangan dokumen yang isinya nyaris sama, biasanya akibat penyalinan dengan perubahan kecil. Pada ruang TF-IDF, pasangan seperti ini menunjukkan skor kosinus yang jauh lebih tinggi daripada pasangan lainnya. Program berikut menghitung seluruh matriks kemiripan untuk tujuh catatan, mencari pasangan dengan skor tertinggi, lalu mengumpulkan pasangan yang melampaui ambang.

python cari-duplikat.py
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity

# Tujuh catatan; dokumen 0 dan 1 hampir sama isinya.
dokumen = [
    "jadwal piket ruang baca disusun ulang setiap awal bulan",
    "jadwal piket ruang baca disusun ulang pada setiap awal bulan",
    "sensor suhu gudang arsip membaca naik pada siang hari",
    "peminjaman buku referensi dicatat pada buku induk",
    "anggaran perbaikan rak arsip diajukan ke sekretariat",
    "hasil uji kalibrasi alat ukur dicatat pada berkas pengadaan",
    "notulen rapat persiapan lomba membaca diserahkan ke sekretariat",
]
n = len(dokumen)

# Setiap dokumen diubah menjadi satu vektor TF-IDF.
vectorizer = TfidfVectorizer()
matriks = vectorizer.fit_transform(dokumen)
print("Ukuran matriks TF-IDF:", matriks.shape)

# Kemiripan kosinis dihitung untuk setiap pasangan dokumen.
kemiripan = cosine_similarity(matriks)
for i in range(n):
    print(f"dok {i}:", [float(x) for x in kemiripan.round(2)[i]])

# Pasangan dengan skor tertinggi dicari di atas diagonal utama.
terbaik = (0, 1)
for i in range(n):
    for j in range(i + 1, n):
        if kemiripan[i, j] > kemiripan[terbaik[0], terbaik[1]]:
            terbaik = (i, j)
print("Pasangan paling mirip:", terbaik,
      "dengan skor", round(float(kemiripan[terbaik[0], terbaik[1]]), 3))
print("Dokumen", terbaik[0], ":", dokumen[terbaik[0]])
print("Dokumen", terbaik[1], ":", dokumen[terbaik[1]])

# Pengelompokan sederhana: kumpulkan pasangan dengan skor di atas ambang.
ambang = 0.5
pasangan = [(i, j, round(float(kemiripan[i, j]), 2))
            for i in range(n) for j in range(i + 1, n)
            if kemiripan[i, j] >= ambang]
print("Pasangan dengan skor >= 0.5:", pasangan)
text Keluaran
Ukuran matriks TF-IDF: (7, 41)
dok 0: [1.0, 0.97, 0.0, 0.0, 0.0, 0.0, 0.0]
dok 1: [0.97, 1.0, 0.05, 0.05, 0.0, 0.05, 0.0]
dok 2: [0.0, 0.05, 1.0, 0.05, 0.1, 0.05, 0.09]
dok 3: [0.0, 0.05, 0.05, 1.0, 0.0, 0.13, 0.0]
dok 4: [0.0, 0.0, 0.1, 0.0, 1.0, 0.0, 0.21]
dok 5: [0.0, 0.05, 0.05, 0.13, 0.0, 1.0, 0.0]
dok 6: [0.0, 0.0, 0.09, 0.0, 0.21, 0.0, 1.0]
Pasangan paling mirip: (0, 1) dengan skor 0.971
Dokumen 0 : jadwal piket ruang baca disusun ulang setiap awal bulan
Dokumen 1 : jadwal piket ruang baca disusun ulang pada setiap awal bulan
Pasangan dengan skor >= 0.5: [(0, 1, 0.97)]

Matriks itu dibaca dengan tiga kebiasaan. Diagonalnya selalu satu karena setiap dokumen identik dengan dirinya sendiri, dan matriksnya simetris karena kemiripan dokumen i terhadap j sama dengan kemiripan j terhadap i, sehingga pencarian pasangan cukup berjalan di atas diagonal. Pasangan paling mirip adalah dokumen 0 dan 1 dengan skor 0.971, dan keduanya memang hanya berbeda satu kata sisipan. Skor tertinggi berikutnya hanya 0.21 pada pasangan dokumen 4 dan 6 yang berbagi sedikit kosakata, sehingga ambang 0.5 menghasilkan satu kelompok berisi dua dokumen dan lima dokumen tunggal. Perintah berikut mengulangi penghitungan itu dari terminal.

bash
# jalankan pencarian duplikat dari folder tempat berkas disimpan
python3 cari-duplikat.py
text Keluaran
Ukuran matriks TF-IDF: (7, 41)
dok 0: [1.0, 0.97, 0.0, 0.0, 0.0, 0.0, 0.0]
dok 1: [0.97, 1.0, 0.05, 0.05, 0.0, 0.05, 0.0]
dok 2: [0.0, 0.05, 1.0, 0.05, 0.1, 0.05, 0.09]
dok 3: [0.0, 0.05, 0.05, 1.0, 0.0, 0.13, 0.0]
dok 4: [0.0, 0.0, 0.1, 0.0, 1.0, 0.0, 0.21]
dok 5: [0.0, 0.05, 0.05, 0.13, 0.0, 1.0, 0.0]
dok 6: [0.0, 0.0, 0.09, 0.0, 0.21, 0.0, 1.0]
Pasangan paling mirip: (0, 1) dengan skor 0.971
Dokumen 0 : jadwal piket ruang baca disusun ulang setiap awal bulan
Dokumen 1 : jadwal piket ruang baca disusun ulang pada setiap awal bulan
Pasangan dengan skor >= 0.5: [(0, 1, 0.97)]

Proses yang sama sekaligus menjadi pengelompokan sederhana. Pasangan yang melampaui ambang digabungkan menjadi satu kelompok, dokumen yang tidak melampaui ambang dengan siapa pun tetap berdiri sendiri, dan ambangnya dapat disesuaikan sampai kelompok yang terbaca sesuai harapan. Ambang 0.5 pada demo ini dipilih karena jelas memisahkan skor 0.971 dari skor 0.21, dan nilainya boleh digeser bila korpusnya berbeda.

Ruang embedding untuk kemiripan semantik#

Kemiripan TF-IDF membaca kata yang sama, sehingga dua dokumen berbeda kata tetapi sama makna memperoleh skor rendah. Pasangan seperti peminjaman buku di perpustakaan dan anggota mengembalikan koleksi terbitan tidak berbagi kata satu pun, padahal keduanya membahas kegiatan perpustakaan. Kondisi seperti ini mendorong pendekatan lain, yaitu mewakili dokumen sebagai vektor padat (dense vector) hasil model embedding.

Model embedding dilatih agar teks dengan makna serupa menempati posisi yang berdekatan pada ruang vektor, sehingga kemiripan kosinus pada ruang itu membaca kemiripan semantik, bukan persamaan kata. Vektor padatnya dapat berasal dari rata-rata vektor kata pada dokumen atau dari model yang mewakili seluruh teks sekaligus. Bentuk ruangnya pun berbeda dari TF-IDF, yaitu dimensi yang jauh lebih sedikit dan tidak lagi berpadanan satu-satu dengan kata. Konsepnya memperluas pembahasan topik ini secara langsung, karena bentuk perhitungan kosinusnya tetap sama dan yang berganti hanya ruang vektornya. Demo pada ruang embedding menuntut bobot model yang dilatih di luar lingkup sandbox ini, sehingga cukup diperkenalkan sebagai arah pengembangan.

Kesalahan umum#

Mengira skor nol berarti bertentangan. Skor kosinis nol pada ruang TF-IDF berarti kedua dokumen tidak berbagi kata sama sekali, bukan berarti isinya saling bertentangan. Vektor TF-IDF berisi bobot positif, sehingga skor negatif tidak pernah muncul. Dua dokumen yang keputusannya saling berlawanan pun dapat memakai kosakata yang sama dan memperoleh skor tinggi, sehingga pertentangan makna tetap dibaca manusia.

Mengira skor tinggi berarti isi sama. Skor tinggi hanya menyatakan kemiripan kata, dan kalimat pembuka yang sama atau rumusan administratif yang berulang pun dapat mengangkatnya. Pasangan dengan skor tinggi tetap dibaca sekali sebelum dinyatakan sebagai duplikat. Pada demo, skor 0.21 antara dokumen 4 dan 6 lahir dari sedikit kosakata bersama, padahal topiknya berbeda.

Menetapkan ambang tanpa membaca distribusi skor. Ambang 0.5 pada korpus pendek tidak menjamin wajar pada korpus lain. Baca dahulu skor-skor pasangan pada korpusnya, lalu tetapkan ambang pada lembah yang memisahkan pasangan mirip dari pasangan biasa. Ambang yang terlalu rendah menggabungkan dokumen yang kebetulan berbagi kata, sedangkan ambang yang terlalu tinggi membiarkan duplikat lolos.

Ringkasan#

  • TF-IDF mengubah dokumen menjadi vektor, dan kemiripan kosinus membaca sudut antara dua vektor tersebut dengan skor antara nol dan satu.
  • Dokumen hampir duplikat menonjol sebagai pasangan dengan skor tertinggi pada matriks kemiripan, sebagaimana pasangan dokumen 0 dan 1 dengan skor 0.971 pada demo.
  • Pengelompokan sederhana terbentuk dengan menggabungkan pasangan yang melampaui ambang dan membiarkan sisanya berdiri sendiri.
  • Ruang embedding mengganti ruang kata dengan vektor padat yang berdekatan untuk makna serupa, sehingga kemiripan semantik dapat dihitung dengan kosinus yang sama.

Referensi#