web-dev-qa-db-fra.com

Utiliser le code du didacticiel LSTM pour prédire le prochain mot dans une phrase?

J'ai essayé de comprendre l'exemple de code avec https://www.tensorflow.org/tutorials/recurrent que vous pouvez trouver sur https://github.com/tensorflow/ models/blob/master/tutorials/rnn/ptb/ptb_Word_lm.py

(Utilisation de tensorflow 1.3.0.)

J'ai résumé (ce que je pense) les éléments clés, pour ma question, ci-dessous:

 size = 200
 vocab_size = 10000
 layers = 2
 # input_.input_data is a 2D tensor [batch_size, num_steps] of
 #    Word ids, from 1 to 10000

 cell = tf.contrib.rnn.MultiRNNCell(
    [tf.contrib.rnn.BasicLSTMCell(size) for _ in range(2)]
    )

 embedding = tf.get_variable(
      "embedding", [vocab_size, size], dtype=tf.float32)
 inputs = tf.nn.embedding_lookup(embedding, input_.input_data)

inputs = tf.unstack(inputs, num=num_steps, axis=1)
outputs, state = tf.contrib.rnn.static_rnn(
    cell, inputs, initial_state=self._initial_state)

output = tf.reshape(tf.stack(axis=1, values=outputs), [-1, size])
softmax_w = tf.get_variable(
    "softmax_w", [size, vocab_size], dtype=data_type())
softmax_b = tf.get_variable("softmax_b", [vocab_size], dtype=data_type())
logits = tf.matmul(output, softmax_w) + softmax_b

# Then calculate loss, do gradient descent, etc.

Ma plus grande question est comment utiliser le modèle produit pour générer une prochaine suggestion Word, étant donné les premiers mots d'une phrase? Concrètement, j'imagine que le flux est comme ça, mais je n'arrive pas à comprendre ce que serait le code des lignes commentées:

prefix = ["What", "is", "your"]
state = #Zeroes
# Call static_rnn(cell) once for each Word in prefix to initialize state
# Use final output to set a string, next_Word
print(next_Word)

Mes sous-questions sont:

  • Pourquoi utiliser une intégration de mots aléatoire (non initialisée, non formée)?
  • Pourquoi utiliser softmax?
  • La couche cachée doit-elle correspondre à la dimension de l'entrée (c'est-à-dire la dimension des intégrations Word2vec)
  • Comment/puis-je apporter un modèle Word2vec pré-formé, au lieu de celui non initialisé?

(Je leur pose tous une question, car je soupçonne qu'ils sont tous connectés et connectés à une lacune dans ma compréhension.)

Ce que je m'attendais à voir ici, c'était de charger un ensemble Word2vec existant d'intégration de mots (par exemple en utilisant KeyedVectors.load_Word2vec_format() de gensim), de convertir chaque mot du corpus d'entrée en cette représentation lors du chargement dans chaque phrase, puis le LSTM cracher un vecteur de la même dimension, et nous essaierions de trouver le mot le plus similaire (par exemple en utilisant similar_by_vector(y, topn=1) de gensim).

L'utilisation de softmax nous évite-t-elle l'appel relativement lent similar_by_vector(y, topn=1)?


BTW, pour la partie pré-existante de Word2vec de ma question tilisation de Word2vec pré-formé avec LSTM pour la génération de Word est similaire. Cependant, les réponses là-bas, actuellement, ne sont pas ce que je cherche. Ce que j'espère, c'est une explication simple en anglais qui allume la lumière pour moi, et colmate tout le vide dans ma compréhension. tiliser Word2vec pré-formé dans le modèle de langage lstm? est une autre question similaire.

MISE À JOUR: Prédiction du prochain mot en utilisant l'exemple du modèle de langage tensorflow et Prédiction du prochain mot en utilisant le modèle ptb LSTM exemple tensorflow sont des questions similaires. Cependant, ni l'un ni l'autre ne montre que le code prend réellement les premiers mots d'une phrase et imprime sa prédiction du mot suivant. J'ai essayé de coller du code à partir de la 2e question et de https://stackoverflow.com/a/39282697/8418 (qui est fourni avec une branche github), mais je n'arrive pas à faire fonctionner sans erreur. Je pense qu'ils peuvent être pour une version antérieure de TensorFlow?

UNE AUTRE MISE À JOUR: Encore une autre question demandant essentiellement la même chose: Prédiction du prochain mot du modèle LSTM à partir de l'exemple Tensorflow Il est lié à - Prédiction de Word suivant en utilisant l'exemple du modèle de langage tensorflow (et, encore une fois, les réponses ne sont pas tout à fait ce que je cherche).

Dans le cas où ce n'est toujours pas clair, ce que j'essaie d'écrire une fonction de haut niveau appelée getNextWord(model, sentencePrefix), où model est un LSTM précédemment construit que j'ai chargé à partir du disque, et sentencePrefix est une chaîne, telle que "Ouvrir le", et elle peut renvoyer "pod". Je pourrais alors l'appeler avec "Open the pod" et il retournera "bay", et ainsi de suite.

Un exemple (avec un caractère RNN et en utilisant mxnet) est la fonction sample() affichée vers la fin de https://github.com/zackchase/mxnet-the-straight-Dope/blob/ master/chapter05_recurrent-neural-networks/simple-rnn.ipynb Vous pouvez appeler sample() pendant l'entraînement, mais vous pouvez également l'appeler après l'entraînement et avec la phrase de votre choix.

21
Darren Cook

Ma plus grande question est de savoir comment utiliser le modèle produit pour générer réellement une prochaine suggestion Word, étant donné les premiers mots d'une phrase?

C'est à dire. J'essaie d'écrire une fonction avec la signature: getNextWord (model, sentencePrefix)

Avant d'expliquer ma réponse, d'abord une remarque sur votre suggestion à # Call static_rnn(cell) once for each Word in prefix to initialize state: Gardez à l'esprit que static_rnn Ne renvoie pas une valeur comme un tableau numpy, mais un tenseur. Vous pouvez évaluer un tenseur à une valeur lorsqu'il est exécuté (1) dans une session (une session conserve l'état de votre graphique de calcul, y compris les valeurs des paramètres de votre modèle) et (2) avec l'entrée nécessaire pour calculer la valeur du tenseur. L'entrée peut être fournie à l'aide de lecteurs d'entrée (l'approche du didacticiel) ou à l'aide d'espaces réservés (ce que j'utiliserai ci-dessous).

Suit maintenant la réponse réelle: le modèle du didacticiel a été conçu pour lire les données d'entrée d'un fichier. La réponse de @ user3080953 a déjà montré comment travailler avec votre propre fichier texte, mais si je comprends bien, vous avez besoin de plus de contrôle sur la façon dont les données sont transmises au modèle. Pour ce faire, vous devrez définir vos propres espaces réservés et fournir les données à ces espaces réservés lors de l'appel de session.run().

Dans le code ci-dessous, j'ai sous-classé PTBModel et je l'ai chargé de fournir explicitement des données au modèle. J'ai introduit un PTBInteractiveInput spécial qui a une interface similaire à PTBInput afin que vous puissiez réutiliser la fonctionnalité dans PTBModel. Pour former votre modèle, vous avez encore besoin de PTBModel.

class PTBInteractiveInput(object):
  def __init__(self, config):
    self.batch_size = 1
    self.num_steps = config.num_steps
    self.input_data = tf.placeholder(dtype=tf.int32, shape=[self.batch_size, self.num_steps])
    self.sequence_len = tf.placeholder(dtype=tf.int32, shape=[])
    self.targets = tf.placeholder(dtype=tf.int32, shape=[self.batch_size, self.num_steps])

class InteractivePTBModel(PTBModel):

  def __init__(self, config):
    input = PTBInteractiveInput(config)
    PTBModel.__init__(self, is_training=False, config=config, input_=input)
    output = self.logits[:, self._input.sequence_len - 1, :]
    self.top_Word_id = tf.argmax(output, axis=2)

  def get_next(self, session, prefix):
    prefix_array, sequence_len = self._preprocess(prefix)
    feeds = {
      self._input.sequence_len: sequence_len,
      self._input.input_data: prefix_array,
    }
    fetches = [self.top_Word_id]
    result = session.run(fetches, feeds)
    self._postprocess(result)

  def _preprocess(self, prefix):
    num_steps = self._input.num_steps
    seq_len = len(prefix)
    if seq_len > num_steps:
      raise ValueError("Prefix to large for model.")
    prefix_ids = self._prefix_to_ids(prefix)
    num_items_to_pad = num_steps - seq_len
    prefix_ids.extend([0] * num_items_to_pad)
    prefix_array = np.array([prefix_ids], dtype=np.float32)
    return prefix_array, seq_len

  def _prefix_to_ids(self, prefix):
    # should convert your prefix to a list of ids
    pass

  def _postprocess(self, result):
    # convert ids back to strings
    pass

Dans la fonction __init__ De PTBModel vous devez ajouter cette ligne:

self.logits = logits

Pourquoi utiliser une intégration de mots aléatoire (non initialisée, non formée)?

Notez tout d'abord que, bien que les plongements soient aléatoires au début, ils seront formés avec le reste du réseau. Les plongements que vous obtenez après la formation auront des propriétés similaires à celles que vous obtenez avec les modèles Word2vec, par exemple, la possibilité de répondre à des questions d'analogie avec des opérations vectorielles (roi - homme + femme = reine, etc.) Dans les tâches, vous aviez une quantité considérable des données de formation comme la modélisation du langage (qui n'a pas besoin de données de formation annotées) ou la traduction automatique de neurones, il est plus courant de former des plongements à partir de zéro.

Pourquoi utiliser softmax?

Softmax est une fonction qui normalise un vecteur de scores de similitude (les logits), à une distribution de probabilité. Vous avez besoin d'une distribution de probabilité pour former votre modèle à la perte d'entropie croisée et pouvoir échantillonner à partir du modèle. Notez que si vous n'êtes intéressé que par les mots les plus probables d'un modèle formé, vous n'avez pas besoin du softmax et vous pouvez utiliser les logits directement.

La couche cachée doit-elle correspondre à la dimension de l'entrée (c'est-à-dire la dimension des intégrations Word2vec)

Non, en principe, cela peut avoir n'importe quelle valeur. Cependant, l'utilisation d'un état masqué avec une dimension inférieure à votre dimension d'intégration n'a pas beaucoup de sens.

Comment/puis-je apporter un modèle Word2vec pré-formé, au lieu de celui non initialisé?

Voici un exemple autonome d'initialisation d'une incorporation avec un tableau numpy donné. Si vous souhaitez que l'incorporation reste fixe/constante pendant l'entraînement, définissez trainable sur False.

import tensorflow as tf
import numpy as np
vocab_size = 10000
size = 200
trainable=True
embedding_matrix = np.zeros([vocab_size, size]) # replace this with code to load your pretrained embedding
embedding = tf.get_variable("embedding",
                            initializer=tf.constant_initializer(embedding_matrix),
                            shape=[vocab_size, size],
                            dtype=tf.float32,
                            trainable=trainable)
7
GeertH

Question principale

Chargement des mots

Chargez des données personnalisées au lieu d'utiliser l'ensemble de test:

reader.py@ptb_raw_data

test_path = os.path.join(data_path, "ptb.test.txt")
test_data = _file_to_Word_ids(test_path, Word_to_id)  # change this line

test_data doit contenir des identifiants Word (imprimez Word_to_id pour un mappage). À titre d'exemple, il devrait ressembler à: [1, 52, 562, 246] ...

Affichage des prédictions

Nous devons renvoyer la sortie de la couche FC (logits) dans l'appel à sess.run

ptb_Word_lm.py@PTBModel.__init__

    logits = tf.reshape(logits, [self.batch_size, self.num_steps, vocab_size])
    self.top_Word_id = tf.argmax(logits, axis=2)  # add this line

ptb_Word_lm.py@run_Epoch

  fetches = {
      "cost": model.cost,
      "final_state": model.final_state,
      "top_Word_id": model.top_Word_id # add this line
  }

Plus loin dans la fonction, vals['top_Word_id'] aura un tableau d'entiers avec l'ID du mot supérieur. Recherchez ceci dans Word_to_id pour déterminer le mot prédit. Je l'ai fait il y a quelque temps avec le petit modèle, et la précision du top 1 était assez faible (20-30% iirc), même si la perplexité était ce qui était prévu dans l'en-tête.

Sous-questions

Pourquoi utiliser une intégration de mots aléatoire (non initialisée, non formée)?

Vous devriez demander aux auteurs, mais à mon avis, la formation des incorporations en fait un didacticiel autonome: au lieu de traiter l'incorporation comme une boîte noire, cela montre comment cela fonctionne.

Pourquoi utiliser softmax?

La prédiction finale n'est pas déterminée par la similitude du cosinus avec la sortie de la couche cachée. Il y a une couche FC après le LSTM qui convertit l'état incorporé en un codage à chaud du mot final.

Voici un croquis des opérations et des dimensions dans le réseau neuronal:

Word -> one hot code (1 x vocab_size) -> embedding (1 x hidden_size) -> LSTM -> FC layer (1 x vocab_size) -> softmax (1 x vocab_size)

La couche cachée doit-elle correspondre à la dimension de l'entrée (c'est-à-dire la dimension des intégrations Word2vec)

Techniquement, non. Si vous regardez les équations LSTM, vous remarquerez que x (l'entrée) peut être de n'importe quelle taille, tant que la matrice de poids est ajustée de manière appropriée.

LSTM equations

Comment/puis-je apporter un modèle Word2vec pré-formé, au lieu de celui non initialisé?

Je ne sais pas, désolé.

7
c2huc2hu

Il y a beaucoup de questions, j'essaierais de clarifier certaines d'entre elles.

comment utiliser le modèle produit pour générer réellement une prochaine suggestion Word, étant donné les premiers mots d'une phrase?

Le point clé ici est que la prochaine génération de mots est en fait la classification des mots dans le vocabulaire. Vous avez donc besoin d'un classificateur, c'est pourquoi il y a un softmax dans la sortie.

Le principe est que, à chaque pas de temps, le modèle afficherait le mot suivant en fonction de la dernière intégration des mots et de la mémoire interne des mots précédents. tf.contrib.rnn.static_rnn combine automatiquement les entrées dans la mémoire, mais nous devons fournir le dernier Word incorporé et classer le prochain Word.

Nous pouvons utiliser un modèle Word2vec pré-formé, il suffit d'initier la matrice embedding avec la matrice pré-formée. Je pense que le tutoriel utilise une matrice aléatoire pour des raisons de simplicité. La taille de la mémoire n'est pas liée à la taille d'intégration, vous pouvez utiliser une plus grande taille de mémoire pour conserver plus d'informations.

Ces didacticiels sont de haut niveau. Si vous voulez comprendre les détails en profondeur, je vous suggère de regarder le code source en python/numpy ordinaire.

4
THN

Vous pouvez trouver tout le code à la fin de la réponse.


La plupart de vos questions (pourquoi un Softmax, comment utiliser une couche d'intégration pré-entraînée, etc.) ont reçu une réponse, je pense. Cependant, comme vous attendiez toujours un code concis pour produire du texte généré à partir d'une graine, j'essaie ici de rapporter comment j'ai fini par le faire moi-même.

J'ai eu du mal, à partir du tutoriel officiel Tensorflow, à en arriver au point où je pouvais facilement générer des mots à partir d'un modèle produit. Heureusement, après avoir pris quelques éléments de réponse dans pratiquement toutes les réponses que vous avez mentionnées dans votre question, j'ai eu une meilleure vue du problème (et des solutions). Cela peut contenir des erreurs, mais au moins il s'exécute et génère du texte ...

comment utiliser le modèle produit pour générer réellement une prochaine suggestion Word, étant donné les premiers mots d'une phrase?

Je vais encapsuler la prochaine suggestion de Word dans une boucle, pour générer une phrase entière, mais vous la réduirez facilement à un seul mot.

Disons que vous avez suivi le tutoriel actuel donné par tensorflow (v1.4 au moment de la rédaction) ici , qui enregistrera un modèle après l'avoir entraîné.

Ensuite, ce qui nous reste à faire est de le charger à partir du disque, et d'écrire une fonction qui prend ce modèle et une entrée de base et renvoie le texte généré.


Générer du texte à partir du modèle enregistré

Je suppose que nous écrivons tout ce code dans un nouveau script python. Tout le script en bas comme récapitulation, ici j'explique les principales étapes.

Premières étapes nécessaires

FLAGS = tf.flags.FLAGS
FLAGS.model = "medium" # or whatever size you used

Maintenant, ce qui est très important, nous créons des dictionnaires pour mapper les identifiants aux mots et vice-versa (nous n'avons donc pas à lire une liste d'entiers ...).

Word_to_id = reader._build_vocab('../data/ptb.train.txt') # here we load the Word -> id dictionnary ()
id_to_Word = dict(Zip(Word_to_id.values(), Word_to_id.keys())) # and transform it into id -> Word dictionnary
_, _, test_data, _ = reader.ptb_raw_data('../data')

Ensuite, nous chargeons la classe de configuration, en définissant également num_steps Et batch_size Sur 1, comme nous voulons échantillonner 1 mot à la fois alors que le LSTM traitera également 1 mot à la fois. Création également de l'instance d'entrée à la volée:

eval_config = get_config()
eval_config.num_steps = 1
eval_config.batch_size = 1
model_input = PTBInput(eval_config, test_data)

Graphique de construction

Pour charger le modèle enregistré (comme enregistré par le module Supervisor.saver Dans le tutoriel), nous devons d'abord reconstruire le graphique (facile avec la classe PTBModel) qui doit utiliser la même configuration comme lors de la formation:

sess = tf.Session()
initializer = tf.random_uniform_initializer(-eval_config.init_scale, eval_config.init_scale)
# not sure but seems to need the same name for variable scope as when saved ....!!
with tf.variable_scope("Model", reuse=None, initializer=initializer):
    tf.global_variables_initializer()
    mtest = PTBModel(is_training=False, config=eval_config, input=model_input)

Restauration des poids enregistrés:

sess.run(tf.global_variables_initializer())
saver = tf.train.Saver()
saver.restore(sess, tf.train.latest_checkpoint('../Whatever_folder_you_saved_in')) # the path must point to the hierarchy where your 'checkpoint' file is

... Échantillonnage des mots d'une graine donnée:

Nous avons d'abord besoin que le modèle contienne un accès aux sorties logits, ou plus précisément la distribution de probabilité sur l'ensemble du vocabulaire. Donc dans le fichier ptb_lstm.py Ajoutez la ligne:

# the line goes somewhere below the reshaping "logits = tf.reshape(logits, [self.batch_size, ..."
self.probas = tf.nn.softmax(logits, name="probas")

Ensuite, nous pouvons concevoir une fonction d'échantillonnage (vous êtes libre d'utiliser ce que vous voulez ici, la meilleure approche est l'échantillonnage avec une température qui a tendance à aplatir ou à affiner les distributions), voici un aléatoire de base méthode d'échantillonnage:

def sample_from_pmf(probas):
    t = np.cumsum(probas)
    s = np.sum(probas)
    return int(np.searchsorted(t, np.random.Rand(1) * s))

Et enfin une fonction qui prend une graine, votre modèle, le dictionnaire qui mappe Word aux ids, et vice versa, comme entrées et sorties de la chaîne de texte générée:

def generate_text(session, model, Word_to_index, index_to_Word, 
                  seed='</s>', n_sentences=10):
    sentence_cnt = 0
    input_seeds_id = [Word_to_index[w] for w in seed.split()]
    state = session.run(model.initial_state)

    # Initiate network with seeds up to the before last Word:
    for x in input_seeds_id[:-1]:
        feed_dict = {model.initial_state: state,
                     model.input.input_data: [[x]]}
        state = session.run([model.final_state], feed_dict)

    text = seed
    # Generate a new sample from previous, starting at last Word in seed
    input_id = [[input_seeds_id[-1]]]
    while sentence_cnt < n_sentences:
        feed_dict = {model.input.input_data: input_id,
                     model.initial_state: state}
        probas, state = session.run([model.probas, model.final_state],
                                 feed_dict=feed_dict)
        sampled_Word = sample_from_pmf(probas[0])
        if sampled_Word == Word_to_index['</s>']:
            text += '.\n'
            sentence_cnt += 1
        else:
            text += ' ' + index_to_Word[sampled_Word]
        input_wordid = [[sampled_Word]]

    return text

TL; DR

N'oubliez pas d'ajouter la ligne:

self.probas = tf.nn.softmax(logits, name='probas')

Dans le fichier ptb_lstm.py, Dans la définition __init__ De la classe PTBModel, n'importe où après la ligne logits = tf.reshape(logits, [self.batch_size, self.num_steps, vocab_size]).

Le script entier, il suffit de l'exécuter à partir du même répertoire où vous avez reader.py, ptb_lstm.py:

import reader
import numpy as np
import tensorflow as tf
from ptb_lstm import PTBModel, get_config, PTBInput

FLAGS = tf.flags.FLAGS
FLAGS.model = "medium"

def sample_from_pmf(probas):
    t = np.cumsum(probas)
    s = np.sum(probas)
    return int(np.searchsorted(t, np.random.Rand(1) * s))

def generate_text(session, model, Word_to_index, index_to_Word, 
                  seed='</s>', n_sentences=10):
    sentence_cnt = 0
    input_seeds_id = [Word_to_index[w] for w in seed.split()]
    state = session.run(model.initial_state)

    # Initiate network with seeds up to the before last Word:
    for x in input_seeds_id[:-1]:
        feed_dict = {model.initial_state: state,
                     model.input.input_data: [[x]]}
        state = session.run([model.final_state], feed_dict)

    text = seed
    # Generate a new sample from previous, starting at last Word in seed
    input_id = [[input_seeds_id[-1]]]
    while sentence_cnt < n_sentences:
        feed_dict = {model.input.input_data: input_id,
                     model.initial_state: state}
        probas, state = sess.run([model.probas, model.final_state],
                                 feed_dict=feed_dict)
        sampled_Word = sample_from_pmf(probas[0])
        if sampled_Word == Word_to_index['</s>']:
            text += '.\n'
            sentence_cnt += 1
        else:
            text += ' ' + index_to_Word[sampled_Word]
        input_wordid = [[sampled_Word]]

    print(text)

if __name__ == '__main__':

    Word_to_id = reader._build_vocab('../data/ptb.train.txt') # here we load the Word -> id dictionnary ()
    id_to_Word = dict(Zip(Word_to_id.values(), Word_to_id.keys())) # and transform it into id -> Word dictionnary
    _, _, test_data, _ = reader.ptb_raw_data('../data')

    eval_config = get_config()
    eval_config.batch_size = 1
    eval_config.num_steps = 1
    model_input = PTBInput(eval_config, test_data, name=None)

    sess = tf.Session()
    initializer = tf.random_uniform_initializer(-eval_config.init_scale,
                                            eval_config.init_scale)
    with tf.variable_scope("Model", reuse=None, initializer=initializer):
        tf.global_variables_initializer()
        mtest = PTBModel(is_training=False, config=eval_config, 
                         input_=model_input)

    sess.run(tf.global_variables_initializer())

    saver = tf.train.Saver()
    saver.restore(sess, tf.train.latest_checkpoint('../models'))

    while True:
        print(generate_text(sess, mtest, Word_to_id, id_to_Word, seed="this sentence is"))
        try:
            raw_input('press Enter to continue ...\n')
        except KeyboardInterrupt:
            print('\b\bQuiting now...')
            break

Mise à jour

Quant à la restauration d'anciens points de contrôle (pour moi, le modèle a été enregistré il y a 6 mois, je ne suis pas sûr de la version exacte de TF utilisée à l'époque) avec un tensorflow récent (1.6 au moins), cela pourrait générer une erreur sur certaines variables non trouvées (voir commentaire). Dans ce cas, vous devez mettre à jour vos points de contrôle en utilisant ce script .

Notez également que pour moi, j'ai dû encore modifier cela, car j'ai remarqué que la fonction saver.restore Essayait de lire les variables lstm_cell Bien que mes variables aient été transformées en basic_lstm_cell Qui conduit également à NotFound Error. Ainsi, une solution simple, juste un petit changement dans le script checkpoint_convert.py, Ligne 72-73, consiste à supprimer basic_ Dans les nouveaux noms.

Un moyen pratique de vérifier le nom des variables contenues dans vos points de contrôle est (CKPT_FILE Est le suffixe qui précède .index, .data0000-1000, Etc.):

reader = tf.train.NewCheckpointReader(CKPT_FILE)
reader.get_variable_to_shape_map()

De cette façon, vous pouvez vérifier que vous avez bien les bons noms (ou les mauvais dans les anciennes versions des points de contrôle).

2
H. Rev.