word2vec

Arsitektur skip-gram dan CBOW pada tingkat intuisi beserta pelatihan pada korpus kecil.

Daftar isi

word2vec melatih jaringan kecil pada sebuah korpus sehingga setiap kata mendapat vektor padat (dense), dan kemiripan makna tinggal dihitung dari kedekatan antarvektor. Topik ini memperkenalkan dua arsitekturnya, skip-gram dan CBOW, pada tingkat intuisi, menyebut trik negative sampling yang membuat pelatihannya terjangkau, lalu melatih keduanya dengan Gensim pada korpus kecil yang deterministik. Keluaran sandbox ditampilkan apa adanya, termasuk bagian yang masih bising, agar cara membaca skor embedding tetap realistis.

Dua arsitektur: skip-gram dan CBOW#

Skip-gram melatih model untuk menebak kata konteks dari satu kata pusat. Pada kalimat mahasiswa membaca buku di perpustakaan dengan kata pusat buku, model ditugaskan menebak membaca dan di, lalu jendela digeser ke kata berikutnya dan tugas yang sama diulang. CBOW melakukan sebaliknya, yaitu menebak satu kata pusat dari gabungan tetangganya. Keduanya memakai jendela yang sama dan menghasilkan vektor dengan struktur informasi yang sama, tetapi arah tugasnya berlawanan.

Aspekskip-gramCBOW
Tugas pelatihanmenebak konteks dari kata pusatmenebak kata pusat dari konteks
Kecepatan pelatihanlebih lambat, satu pasangan per tetanggalebih cepat, satu prediksi per jendela
Karakter hasilmenguntungkan kata jarang dan data kecilmenghaluskan konteks menjadi satu masukan

Perbedaan karakter tersebut berasal dari jumlah sinyal yang dilewati setiap kata. Pada skip-gram, setiap kemunculan kata pusat memunculkan satu pasangan latihan per tetangga, sehingga kata jarang tetap dilatih dari beberapa pasangan sekaligus. Pada CBOW, tetangga dirata-rata menjadi satu masukan, sehingga detail kata pusat mudah tertelan konteksnya. Pada korpus sangat kecil, keduanya sering menghasilkan skor yang nyaris sama, dan hal itu juga terlihat pada demo di bawah.

Negative sampling sebagai trik pelatihan#

Menebak satu kata dari seluruh kosakata berarti menghitung skor untuk setiap kata kosakata pada setiap pasangan latihan, dan biaya itu berlipat ganda pada korpus besar. word2vec tidak melakukannya. Trik yang dipakai bernama negative sampling, yakni pelatihan klasifikasi biner: pasangan kata pusat dan konteks yang benar diberi label positif, lalu beberapa kata acak dipilih sebagai sampel negatif. Model hanya ditugaskan membedakan pasangan asli dari sampel negatif tersebut, sehingga biaya per pembaruan turun dari sebesar kosakata menjadi sebesar jumlah sampel.

Jumlah sampel negatif per pasangan positif biasa dilambangkan dengan k, dan Gensim menetapkan nilai 5 secara default. Kualitas hasilnya dijaga oleh mekanisme pembobotan, yaitu kata berfrekuensi sangat tinggi dipilih sebagai sampel negatif dengan peluang yang ditekan, sehingga kata seperti di tidak mendominasi. Ketebalan sinyalnya juga bergantung pada k: sampel negatif yang terlalu sedikit membuat batas kelas kasar, sedangkan yang terlalu banyak mengembalikan biaya komputasi. Dengan trik ini, pelatihan pada jutaan kalimat menjadi terjangkau, dan inilah alasan word2vec dapat dilatih di mesin biasa pada masanya.

Melatih word2vec pada korpus kecil#

Skrip berikut membangun 39 kalimat sintetis dari tiga tema, yaitu perpustakaan, pasar, dan jadwal belajar, lalu melatih model skip-gram dan CBOW dengan parameter identik. Korpusnya kecil dan templatnya tetap, sehingga keluarannya dapat direproduksi secara persis oleh pembaca di sandbox yang sama.

python latih_word2vec.py
import warnings
warnings.filterwarnings("ignore")
import logging
logging.getLogger("gensim").setLevel(logging.ERROR)

from gensim.models import Word2Vec

def buat_korpus():
    kalimat = []
    # tema perpustakaan
    for nama in ["Budi", "Citra", "Dewi"]:
        for objek in ["buku", "majalah"]:
            kalimat.append(f"{nama} membaca {objek} di perpustakaan")
            kalimat.append(f"{nama} meminjam {objek} di perpustakaan")
    kalimat.append("petugas menata buku di rak")
    kalimat.append("petugas menata majalah di rak")
    kalimat.append("Budi mencari buku di rak")
    kalimat.append("Citra mencari majalah di rak")
    # tema pasar
    for nama in ["Budi", "Citra", "Dewi"]:
        for objek in ["sayur", "buah"]:
            kalimat.append(f"{nama} membeli {objek} di pasar")
            kalimat.append(f"{nama} memilih {objek} di pasar")
    kalimat.append("Citra menghitung harga sayur")
    kalimat.append("Dewi menghitung harga buah")
    # tema jadwal belajar
    for nama in ["Budi", "Citra", "Dewi"]:
        kalimat.append(f"{nama} mencatat materi pelajaran")
        kalimat.append(f"{nama} mengerjakan latihan soal")
    kalimat.append("Citra menyalin jadwal belajar")
    kalimat.append("Dewi menempel jadwal belajar di dinding")
    kalimat.append("Budi mengisi jadwal belajar")
    return [k.split() for k in kalimat]

kalimat_token = buat_korpus()
print("jumlah kalimat:", len(kalimat_token))

skipgram = Word2Vec(
    sentences=kalimat_token,
    vector_size=100,
    window=2,
    min_count=1,
    workers=1,
    seed=42,
    sg=1,
    epochs=30,
)

print()
print("skip-gram, kata paling mirip dengan buku:")
for kata, skor in skipgram.wv.most_similar("buku", topn=5):
    print(f"  {kata}: {skor:.3f}")
print("kemiripan buku-majalah:", round(skipgram.wv.similarity("buku", "majalah"), 3))
print("kemiripan buku-sayur :", round(skipgram.wv.similarity("buku", "sayur"), 3))

cbow = Word2Vec(
    sentences=kalimat_token,
    vector_size=100,
    window=2,
    min_count=1,
    workers=1,
    seed=42,
    sg=0,
    epochs=30,
)

print()
print("CBOW, kata paling mirip dengan buku:")
for kata, skor in cbow.wv.most_similar("buku", topn=5):
    print(f"  {kata}: {skor:.3f}")
print("kemiripan buku-majalah:", round(cbow.wv.similarity("buku", "majalah"), 3))
print("kemiripan buku-sayur :", round(cbow.wv.similarity("buku", "sayur"), 3))
text Keluaran
jumlah kalimat: 39

skip-gram, kata paling mirip dengan buku:
  memilih: 0.206
  majalah: 0.187
  mencari: 0.155
  Dewi: 0.133
  mengerjakan: 0.122
kemiripan buku-majalah: 0.187
kemiripan buku-sayur : -0.119

CBOW, kata paling mirip dengan buku:
  memilih: 0.202
  majalah: 0.190
  mencari: 0.149
  Dewi: 0.126
  mengerjakan: 0.112
kemiripan buku-majalah: 0.19
kemiripan buku-sayur : -0.116

Pasangan buku-majalah memperoleh skor 0.187 pada skip-gram, sedangkan buku-sayur bernilai negatif, -0.119. Arahnya sesuai hipotesis distribusional, tetapi skornya kecil dan daftar tetangga buku justru diawali memilih. Korpus 39 kalimat memang belum cukup untuk geometri yang bersih: tetangga seperti memilih dan Dewi muncul dari sisa-sisa kebetulan pelatihan, bukan dari makna. Yang dapat diandalkan pada tahap ini hanyalah urutan relatif, yaitu buku-majalah lebih tinggi daripada buku-sayur, sedangkan pada korpus berisi jutaan kalimat skor yang sama biasanya naik dan daftar tetangganya semakin sesuai makna.

Keluaran CBOW hampir tidak berbeda dari skip-gram, dengan urutan tetangga yang sama dan skor yang bergeser tipis. Pada skala data sekecil ini, perbedaan arsitektur belum terasa, dan perbedaan keduanya baru menonjol ketika korpus membesar.

Parameter yang menjaga hasil dapat direproduksi#

Parameter pada skrip ada demi kestabilan hasil. workers=1 memaksa pelatihan berjalan satu benang, karena pembaruan dari banyak benang tumpang tindih dengan urutan yang tidak dapat dijamin, sehingga hasilnya berubah dari satu eksekusi ke eksekusi berikutnya. seed=42 menetapkan titik awal acak untuk inisialisasi vektor dan pengambilan sampel negatif, dan kombinasi keduanya membuat dua eksekusi menghasilkan angka yang sama persis. vector_size=100 menetapkan panjang vektor, window=2 menetapkan jarak tetangga yang dihitung, dan epochs=30 menetapkan jumlah lintasan penuh atas korpus. min_count=1 dipakai karena korpusnya kecil, yaitu setiap kata yang pernah muncul ikut dilatih, sedangkan korpus nyata biasanya memakai 5 atau 10 agar kata yang muncul sekali tidak mengacaukan vektor. Nilai-nilai itu juga membuat pelatihan selesai dalam hitungan detik pada korpus kecil.

Kesalahan umum#

  1. Melatih dengan workers>1 lalu membandingkan hasilnya antareksekusi. Urutan pembaruan antarbenang menentukan vektor akhir, sehingga skor berubah tanpa sebab yang jelas. Gunakan workers=1 setiap kali hasil perlu dibandingkan.
  2. Menilai kualitas embedding dari nilai absolut skornya. Angka 0.187 pada korpus kecil bukan skor lemah yang gagal, melainkan skor rendah yang urutannya benar. Kemiripan dibandingkan antarpasangan pada model yang sama, bukan dibaca sebagai kebenaran mutlak.
  3. Mengulang pelatihan dengan harapan hasil identik tanpa menetapkan seed. Tanpa seed yang tetap, titik awal inisialisasi berubah dan seluruh vektor ikut berpindah. Tetapkan seed di awal dan catat nilainya bersama kode pelatihan.

Ringkasan#

  • skip-gram menebak konteks dari kata pusat, sedangkan CBOW menebak kata pusat dari konteksnya.
  • Negative sampling mengganti prediksi atas seluruh kosakata dengan klasifikasi biner terhadap beberapa sampel negatif.
  • Pada korpus 39 kalimat, buku-majalah memperoleh skor 0.187 dan buku-sayur -0.119, dengan urutan yang sesuai makna walaupun skornya kecil.
  • workers=1 dan seed tetap membuat pelatihan dapat direproduksi, sedangkan korpus kecil membuat perbedaan skip-gram dan CBOW nyaris tidak terlihat.

Referensi#