Semua yang Anda ketahui tentang word2vec tidak benar

Penjelasan klasik dari word2vec sebagai arsitektur Skip-gram sampel negatif dalam artikel ilmiah asli dan posting blog yang tak terhitung jumlahnya terlihat seperti ini:

while(1) { 1. vf = vector of focus word 2. vc = vector of focus word 3. train such that (vc . vf = 1) 4. for(0 <= i <= negative samples): vneg = vector of word *not* in context train such that (vf . vneg = 0) } 

Memang, jika Anda google [word2vec skipgram], apa yang kita lihat:


Tetapi semua implementasi ini salah .

Implementasi asli word2vec di C bekerja secara berbeda dan secara fundamental berbeda dari ini. Mereka yang secara profesional menerapkan sistem dengan embeddings kata dari word2vec melakukan salah satu dari yang berikut:

  1. Langsung menyebut implementasi asli C.
  2. Gunakan implementasi gensim , yang ditransliterasikan dari sumber C sejauh nama-nama variabel cocok.

Memang, gensim adalah satu - satunya implementasi C yang benar yang saya tahu .

Implementasi C


Implementasi C sebenarnya mendukung dua vektor untuk setiap kata . Satu vektor untuk kata dalam fokus, dan yang kedua untuk kata dalam konteks. (Tampaknya familier? Benar, pengembang GloVe meminjam ide dari word2vec tanpa menyebutkan fakta ini!)

Implementasi dalam kode C sangat kompeten:

  • Array syn0 berisi embedding vektor kata jika itu tampil sebagai kata dalam fokus. Inilah inisialisasi acak .

     https://github.com/tmikolov/word2vec/blob/20c129af10659f7c50e86e3be406df663beff438/word2vec.c#L369 for (a = 0; a < vocab_size; a++) for (b = 0; b < layer1_size; b++) { next_random = next_random * (unsigned long long)25214903917 + 11; syn0[a * layer1_size + b] = (((next_random & 0xFFFF) / (real)65536) - 0.5) / layer1_size; } 
  • Array syn1neg lain berisi vektor kata ketika muncul sebagai kata konteks. Di sini inisialisasi adalah nol .
  • Selama pelatihan (Lewati-gram, sampel negatif, meskipun kasus lain hampir sama), kami pertama-tama memilih kata fokus. Itu dipertahankan sepanjang pelatihan tentang contoh-contoh positif dan negatif. Gradien vektor fokus terakumulasi dalam buffer dan diterapkan ke kata fokus setelah pelatihan pada contoh positif dan negatif.

     if (negative > 0) for (d = 0; d < negative + 1; d++) { // if we are performing negative sampling, in the 1st iteration, // pick a word from the context and set the dot product target to 1 if (d == 0) { target = word; label = 1; } else { // for all other iterations, pick a word randomly and set the dot //product target to 0 next_random = next_random * (unsigned long long)25214903917 + 11; target = table[(next_random >> 16) % table_size]; if (target == 0) target = next_random % (vocab_size - 1) + 1; if (target == word) continue; label = 0; } l2 = target * layer1_size; f = 0; // find dot product of original vector with negative sample vector // store in f for (c = 0; c < layer1_size; c++) f += syn0[c + l1] * syn1neg[c + l2]; // set g = sigmoid(f) (roughly, the actual formula is slightly more complex) if (f > MAX_EXP) g = (label - 1) * alpha; else if (f < -MAX_EXP) g = (label - 0) * alpha; else g = (label - expTable[(int)((f + MAX_EXP) * (EXP_TABLE_SIZE / MAX_EXP / 2))]) * alpha; // 1. update the vector syn1neg, // 2. DO NOT UPDATE syn0 // 3. STORE THE syn0 gradient in a temporary buffer neu1e for (c = 0; c < layer1_size; c++) neu1e[c] += g * syn1neg[c + l2]; for (c = 0; c < layer1_size; c++) syn1neg[c + l2] += g * syn0[c + l1]; } // Finally, after all samples, update syn1 from neu1e https://github.com/tmikolov/word2vec/blob/20c129af10659f7c50e86e3be406df663beff438/word2vec.c#L541 // Learn weights input -> hidden for (c = 0; c < layer1_size; c++) syn0[c + l1] += neu1e[c]; 

Mengapa inisialisasi acak dan nol?


Sekali lagi, karena ini tidak dijelaskan sama sekali dalam artikel asli dan di mana saja di Internet , saya hanya bisa berspekulasi.

Hipotesisnya adalah ketika sampel negatif berasal dari keseluruhan teks dan tidak diperhitungkan berdasarkan frekuensi, Anda dapat memilih kata apa saja , dan paling sering kata yang vektornya tidak dilatih sama sekali . Jika vektor ini memiliki makna, maka secara acak akan menggeser kata yang sangat penting menjadi fokus.

Intinya adalah untuk mengatur semua contoh negatif menjadi nol, sehingga hanya vektor yang muncul lebih atau kurang sering akan mempengaruhi penyajian vektor lain.

Ini sebenarnya cukup rumit, dan saya tidak pernah memikirkan betapa pentingnya strategi inisialisasi.

Kenapa saya menulis ini


Saya menghabiskan dua bulan hidup saya mencoba mereproduksi word2vec seperti yang dijelaskan dalam publikasi ilmiah asli dan banyak artikel di Internet, tetapi gagal. Saya tidak dapat mencapai hasil yang sama dengan word2vec, meskipun saya mencoba yang terbaik.

Saya tidak dapat membayangkan bahwa para penulis publikasi secara harfiah membuat sebuah algoritma yang tidak berfungsi, sementara implementasinya melakukan sesuatu yang sangat berbeda.

Pada akhirnya, saya memutuskan untuk mempelajari sumbernya. Selama tiga hari saya yakin bahwa saya salah mengerti kode, karena secara harfiah semua orang di Internet berbicara tentang implementasi yang berbeda.

Saya tidak tahu mengapa publikasi asli dan artikel di Internet tidak mengatakan apa-apa tentang mekanisme sebenarnya dari word2vec, jadi saya memutuskan untuk mempublikasikan informasi ini sendiri.

Ini juga menjelaskan pilihan radikal GloVe untuk mengatur vektor terpisah untuk konteks negatif - mereka hanya melakukan apa yang dilakukan word2vec, tetapi memberi tahu orang-orang tentang hal itu :).

Apakah ini trik ilmiah? Saya tidak tahu, pertanyaan yang sulit. Tapi jujur ​​saja, saya sangat marah. Mungkin, saya tidak akan pernah lagi dapat menganggap serius penjelasan algoritma dalam pembelajaran mesin: lain kali saya akan segera melihat sumbernya.

Source: https://habr.com/ru/post/id454926/


All Articles