Alles, was Sie über word2vec wussten, ist nicht wahr

Die klassische Erklärung von word2vec als Skip-Gramm-Architektur mit negativem Beispiel im ursprünglichen wissenschaftlichen Artikel und in unzähligen Blog-Posts sieht folgendermaßen aus:

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) } 

In der Tat, wenn Sie [word2vec skipgram] googeln, was wir sehen:


Aber all diese Implementierungen sind falsch .

Die ursprüngliche Implementierung von word2vec in C funktioniert anders und unterscheidet sich grundlegend davon. Diejenigen, die Systeme mit Worteinbettungen von word2vec professionell implementieren, führen eine der folgenden Aktionen aus:

  1. Rufen Sie direkt die ursprüngliche Implementierung von C auf.
  2. Verwenden Sie die gensim Implementierung, die von der Quelle C gensim , gensim die Variablennamen übereinstimmen.

In der Tat ist gensim die einzige echte C-Implementierung, die mir bekannt ist .

C Implementierung


Die C-Implementierung unterstützt tatsächlich zwei Vektoren für jedes Wort . Ein Vektor für das Wort steht im Fokus und der zweite für das Wort im Kontext. (Kommt Ihnen das bekannt vor? Richtig, GloVe-Entwickler haben eine Idee von word2vec ausgeliehen, ohne diese Tatsache zu erwähnen!)

Die Implementierung in C-Code ist außerordentlich kompetent:

  • Das syn0 Array enthält die Vektoreinbettung des Wortes, wenn es als fokussiertes Wort syn0 . Hier ist eine zufällige Initialisierung .

     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; } 
  • Ein anderes syn1neg Array enthält den Vektor des Wortes, wenn es als Kontextwort erscheint. Hier ist die Initialisierung Null .
  • Während des Trainings (Skip-Gramm, negative Stichprobe, obwohl andere Fälle ungefähr gleich sind) wählen wir zuerst das Fokuswort aus. Es wird während des gesamten Trainings an positiven und negativen Beispielen beibehalten. Die Gradienten des Fokusvektors werden im Puffer akkumuliert und nach dem Training an positiven und negativen Beispielen auf das Fokuswort angewendet.

     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]; 

Warum zufällige und Null-Initialisierung?


Noch einmal, da dies in den Originalartikeln und irgendwo im Internet überhaupt nicht erklärt wird, kann ich nur spekulieren.

Die Hypothese ist, dass Sie, wenn negative Stichproben aus dem gesamten Text stammen und nicht nach Häufigkeit gewichtet werden, ein beliebiges Wort auswählen können , und meistens ein Wort, dessen Vektor überhaupt nicht trainiert wird . Wenn dieser Vektor eine Bedeutung hat, verschiebt er das wirklich wichtige Wort zufällig in den Fokus.

Unter dem Strich werden alle negativen Beispiele auf Null gesetzt, sodass nur Vektoren, die mehr oder weniger häufig auftreten , die Darstellung eines anderen Vektors beeinflussen.

Das ist eigentlich ziemlich knifflig und ich hatte nie darüber nachgedacht, wie wichtig Initialisierungsstrategien sind.

Warum schreibe ich das?


Ich habe zwei Monate meines Lebens damit verbracht, word2vec wie in der wissenschaftlichen Originalveröffentlichung und in unzähligen Artikeln im Internet beschrieben zu reproduzieren, bin aber gescheitert. Ich konnte nicht die gleichen Ergebnisse wie word2vec erzielen, obwohl ich mein Bestes versucht habe.

Ich konnte mir nicht vorstellen, dass die Autoren der Veröffentlichung buchstäblich einen Algorithmus erfunden haben, der nicht funktioniert, während die Implementierung etwas völlig anderes macht.

Am Ende entschied ich mich, die Quelle zu studieren. Drei Tage lang war ich zuversichtlich, den Code falsch verstanden zu haben, da buchstäblich alle im Internet über eine andere Implementierung sprachen.

Ich habe keine Ahnung, warum die Originalveröffentlichung und die Artikel im Internet nichts über den tatsächlichen Mechanismus von word2vec aussagen. Deshalb habe ich beschlossen, diese Informationen selbst zu veröffentlichen.

Dies erklärt auch die radikale Entscheidung von GloVe, separate Vektoren für den negativen Kontext festzulegen - sie haben einfach das getan, was word2vec tut, aber den Leuten davon erzählt :).

Ist das ein wissenschaftlicher Trick? Ich weiß nicht, eine schwierige Frage. Aber um ehrlich zu sein, bin ich unglaublich wütend. Wahrscheinlich werde ich die Erklärung von Algorithmen beim maschinellen Lernen nie wieder ernst nehmen können: Beim nächsten Mal werde ich mir sofort die Quellen ansehen.

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


All Articles