Incorporation de mots et modélisation de langage | Vers l'IA

Comment obtenir des vecteurs word2vec / doc2vec / paragraph déterministes

OK, bienvenue dans notre série d’incorporation de mots. Cet article est le premier récit de la série. Vous pouvez trouver que cette histoire convient aux personnes de niveau intermédiaire ou supérieur, qui ont été formées ou au moins essayé une fois sur Word2vec ou sur les vecteurs doc2vec / paragraphe. Mais pas de panique, je vais vous présenter le contexte, les conditions préalables et les connaissances, ainsi que la manière dont le code l’applique à partir d’articles dans les articles suivants.

Je ferai de mon mieux pour ne pas vous rediriger vers d'autres liens vous demandant de lire des tutoriels fastidieux et de finir par abandonner (croyez-moi, je suis la victime des formidables tutoriels en ligne :)). Je souhaite que vous compreniez avec moi les vecteurs de mots du niveau de codage afin que nous puissions savoir comment concevoir et mettre en œuvre notre modèle d'intégration de mots et de langage.

Si vous avez la possibilité d’entraîner vous-même des vecteurs de mots, vous constaterez peut-être que le modèle et la représentation vectorielle sont différents d’un entraînement à l’autre, même si vous introduisez les mêmes données d’entraînement. Ceci est dû au hasard introduit dans le temps de formation. Le code peut parler de lui-même, jetons un coup d’œil à l’origine de l’aléatoire et à la manière de l’éliminer complètement. J'utiliserai l'implémentation des vecteurs de paragraphe de DL4j pour afficher le code. Si vous voulez regarder l’autre paquet, allez à doc2vec de gensim, qui a la même méthode d’implémentation.

Où le hasard vient

L'initialisation des poids de modèle et de la représentation vectorielle

Nous savons qu'avant l'entraînement, les poids d'un modèle et de la représentation vectorielle seront initialisés de manière aléatoire, et le caractère aléatoire est contrôlé par la graine. Par conséquent, si nous fixons la valeur de départ à 0, nous obtiendrons la même initialisation à chaque fois. Voici l'endroit où la graine prend effet. Ici, le syn0 est le poids du modèle, et il est initialisé par Nd4j.rand

// Nd4j prend la configuration de départ ici
Nd4j.getRandom (). SetSeed (configuration.getSeed ());
// Nd4j initialise une matrice aléatoire pour syn0
syn0 = Nd4j.rand (new int [] {vocab.numWords (), vectorLength}, rng) .subi (0.5) .divi (vectorLength);

Algorithme PV-DBOW

Si nous utilisons l'algorithme PV-DBOW (j'expliquerai les détails de celui-ci dans les articles suivants) pour former des vecteurs de paragraphe, au cours des itérations d'apprentissage, il sous-échantillonne de manière aléatoire des mots à partir de la fenêtre de texte pour calculer et mettre à jour les pondérations. Mais ce n'est pas vraiment aléatoire. Regardons le code.

// next random est un AtomicLong initialisé par l'id du thread
this.nextRandom = new AtomicLong (this.threadId);

Et nextRandom est utilisé dans

trainSquence (sequence, nextRandom, alpha);

Où dans trainSequence, ça ira

nextRandom.set (nextRandom.get () * 25214903917L + 11);

Si nous approfondissons les étapes d’entraînement, nous verrons que cela génère nextRandom de la même manière, c’est-à-dire que nous effectuons la même opération mathématique (Allez à ceci et cela pour savoir pourquoi), de sorte que le nombre ne repose que sur l’identifiant du fil, où L'identifiant du fil est 0, 1, 2, 3,…. Par conséquent, ce n’est plus aléatoire.

Tokenisation parallèle

Il est également utilisé pour la création de jetons en parallèle, car le processus de rédaction de texte complexe peut nécessiter beaucoup de temps. En même temps, la création de jetons peut aider à améliorer les performances, tandis que la cohérence entre les formations n’est pas garantie. Les séquences traitées par tokenizer peuvent avoir un ordre aléatoire pour alimenter les threads à former. Comme vous pouvez le constater à partir du code, l’exécutable qui effectue la tokenization attendra jusqu’à ce qu’il se termine si nous définissons allowParallelBuilder sur false, où l’ordre d’alimentation des données peut être conservé.

if (! allowParallelBuilder) {
    essayer {
        runnable.awaitDone ();
    } catch (InterruptedException e) {
        Thread.currentThread (). Interrupt ();
        lancer une nouvelle exception RuntimeException (e);
    }
}

File d'attente qui fournit des séquences à chaque thread à former

Cette LinkedBlockingQueue obtient des séquences de l'itérateur de texte d'apprentissage et fournit ces séquences à chaque thread. Étant donné que chaque fil peut venir au hasard, à chaque moment de la formation, chaque fil peut avoir différentes séquences à entraîner. Regardons l’implémentation de ce fournisseur de données.

// initialise un séquenceur pour fournir des données aux threads
val sequencer = new AsyncSequencer (this.iterator, this.stopWords);
// chaque thread pointe vers le même séquenceur
// worker est le nombre de threads que nous voulons utiliser
pour (int x = 0; x 
// le séquenceur initialisera un tampon LinkedBlockingQueue
// et conserve la taille entre [limitLower, limitUpper]
final final privé LinkedBlockingQueue > tampon;
limitLower = workers * batchSize;
limitUpper = workers * batchSize * 2;
// les threads récupèrent les données de la file d'attente
buffer.poll (3L, TimeUnit.SECONDS);

Par conséquent, si nous fixons le nombre d'un travailleur à 1, celui-ci s'exécutera dans un seul thread et aura exactement le même ordre d'alimentation des données à chaque moment de la formation. Mais remarquez que le thread simple ralentira énormément la formation.

Résumer

En résumé, voici ce que nous devons faire pour exclure complètement le caractère aléatoire:
1. Définissez la graine sur 0;
2. Définissez allowParallelTokenization sur false;
3. Définissez le nombre de travailleurs (threads) sur 1.
Ensuite, nous aurons les mêmes résultats du vecteur de mots et du vecteur de paragraphes si nous introduisons les mêmes données.

Enfin, notre code à former est comme:

ParagraphVectors vec = new ParagraphVectors.Builder ()
                .minWordFrequency (1)
                .labels (labelsArray)
                .layerSize (100)
                .stopWords (nouvel ArrayList  ())
                .windowSize (5)
                .iterate (iter)
                .allowParallelTokenization (false)
                .travailleurs (1)
                Graine (0)
                .tokenizerFactory (t)
                .construire();

vec.fit ();

Si vous vous sentez comme

veuillez suivre les histoires suivantes sur l’intégration des mots et le modèle de langage, j’ai préparé le festin pour vous.

Référence

[1] Deeplearning4j, ND4J, DataVec et plus - apprentissage en profondeur et algèbre linéaire pour Java / Scala avec GPU + Spark - De Skymind http://deeplearning4j.org https://github.com/deeplearning4j/deeplearning4j
[2] Spécification de l'API Java ™ Platform, Standard Edition 8 https://docs.oracle.com/javase/8/docs/api/
[3] https://giphy.com/
[4] https://images.google.com/