
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/