Tout ce que vous saviez sur word2vec n'est pas vrai

L'explication classique de word2vec en tant qu'architecture Skip-gram à échantillon négatif dans l'article scientifique d'origine et d'innombrables articles de blog ressemble à ceci:

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

En effet, si vous google [skipgram word2vec], ce que nous voyons:


Mais toutes ces implémentations sont fausses .

L'implémentation originale de word2vec en C fonctionne différemment et est fondamentalement différente de cela. Ceux qui implémentent professionnellement des systèmes avec des incorporations de mots à partir de word2vec font l'une des choses suivantes:

  1. Appelez directement l'implémentation d'origine de C.
  2. Utilisez l'implémentation gensim , qui est translittérée de la source C dans la mesure où les noms de variables correspondent.

En effet, gensim est la seule véritable implémentation C que je connaisse .

Implémentation C


L'implémentation C prend en charge deux vecteurs pour chaque mot . Un vecteur pour le mot est mis au point et le second pour le mot dans son contexte. (Cela vous semble familier? D'accord, les développeurs de GloVe ont emprunté une idée à word2vec sans mentionner ce fait!)

L'implémentation en code C est exceptionnellement compétente:

  • Le tableau syn0 contient le vecteur d'intégration du mot s'il apparaît comme un mot au point. Voici une initialisation aléatoire .

     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; } 
  • Un autre tableau syn1neg contient le vecteur du mot lorsqu'il apparaît en tant que mot de contexte. Ici, l' initialisation est nulle .
  • Pendant l'entraînement (Skip-gram, échantillon négatif, bien que d'autres cas soient à peu près les mêmes), nous sélectionnons d'abord le mot de focus. Il est maintenu tout au long de la formation sur des exemples positifs et négatifs. Les gradients du vecteur de mise au point sont accumulés dans le tampon et appliqués au mot de mise au point après une formation sur des exemples positifs et négatifs.

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

Pourquoi une initialisation aléatoire et nulle?


Encore une fois, comme cela n'est pas expliqué du tout dans les articles originaux et partout sur Internet , je ne peux que spéculer.

L'hypothèse est que lorsque des échantillons négatifs proviennent de l'ensemble du texte et ne sont pas pondérés par la fréquence, vous pouvez choisir n'importe quel mot , et le plus souvent un mot dont le vecteur n'est pas du tout formé . Si ce vecteur a une signification, alors il mettra au hasard le mot vraiment important au point.

L'essentiel est de mettre tous les exemples négatifs à zéro, de sorte que seuls les vecteurs qui se produisent plus ou moins souvent affectent la présentation d'un autre vecteur.

C'est en fait assez délicat, et je n'avais jamais pensé à l'importance des stratégies d'initialisation.

Pourquoi est-ce que j'écris ceci


J'ai passé deux mois de ma vie à essayer de reproduire word2vec comme décrit dans la publication scientifique originale et d'innombrables articles sur Internet, mais j'ai échoué. Je n'ai pas pu obtenir les mêmes résultats que word2vec, même si j'ai fait de mon mieux.

Je ne pouvais pas imaginer que les auteurs de la publication aient littéralement fabriqué un algorithme qui ne fonctionne pas, alors que l'implémentation fait quelque chose de complètement différent.

Finalement, j'ai décidé d'étudier la source. Pendant trois jours, j'étais confiant d'avoir mal compris le code, car littéralement tout le monde sur Internet parlait d'une implémentation différente.

Je ne sais pas pourquoi la publication et les articles originaux sur Internet ne disent rien sur le véritable mécanisme de word2vec, j'ai donc décidé de publier moi-même ces informations.

Cela explique également le choix radical de GloVe de définir des vecteurs séparés pour le contexte négatif - ils ont juste fait ce que fait word2vec, mais en ont parlé aux gens :).

Est-ce une astuce scientifique? Je ne sais pas, une question difficile. Mais pour être honnête, je suis incroyablement en colère. Probablement, je ne pourrai plus jamais prendre au sérieux l'explication des algorithmes en machine learning: la prochaine fois j'irai immédiatement regarder les sources.

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


All Articles