¿Usar el código tutorial de LSTM para predecir la siguiente palabra en una oración?

He estado tratando de entender el código de muestra con https://www.tensorflow.org/tutorials/recurrent que puede encontrar en https://github.com/tensorflow/models/blob/master/tutorials/rnn/ptb /ptb_word_lm.py

(Utilizando tensorflow 1.3.0.)

He resumido (lo que creo que son) las partes clave, para mi pregunta, a continuación:

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. 

Mi pregunta más importante es ¿cómo uso el modelo producido para generar una sugerencia de la siguiente palabra, dadas las primeras palabras de una oración ? Concretamente, me imagino que el flujo es así, pero no puedo entender cuál sería el código para las líneas comentadas:

 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) 

Mis sub-preguntas son:

  • ¿Por qué usar una inserción de palabras al azar (sin inicializar, sin capacitación)?
  • ¿Por qué usar softmax?
  • ¿La capa oculta tiene que coincidir con la dimensión de la entrada (es decir, la dimensión de las incrustaciones de word2vec)
  • ¿Cómo / Puedo traer un modelo pre-entrenado de word2vec, en lugar de uno no inicializado?

(Les pregunto a todos como una sola pregunta, ya que sospecho que están todos conectados y conectados a algún vacío en mi comprensión).

Lo que esperaba ver aquí era cargar un conjunto existente de word2vec de incrustaciones de palabras (por ejemplo, al usar KeyedVectors.load_word2vec_format KeyedVectors.load_word2vec_format() de KeyedVectors.load_word2vec_format() ), convertir cada palabra en el corpus de entrada a esa representación al cargar en cada oración, y luego la LSTM escupiría fuera un vector de la misma dimensión, y trataríamos de encontrar la palabra más similar (por ejemplo, usando similar_by_vector de similar_by_vector(y, topn=1) ).

¿El uso de softmax nos está salvando de una similar_by_vector(y, topn=1) relativamente lenta similar_by_vector(y, topn=1) ?


Por cierto, para la parte preexistente de word2vec de mi pregunta El uso de word2vec pre-entrenado con LSTM para la generación de palabras es similar. Sin embargo, las respuestas allí, actualmente, no son lo que estoy buscando. Lo que espero es una explicación simple en inglés que encienda la luz para mí, y que se salga del hueco que pueda haber en mi entendimiento. ¿Usar word2vec pre-entrenado en el modelo de lenguaje lstm? Es otra pregunta similar.

ACTUALIZACIÓN: Predecir la siguiente palabra usando el ejemplo del modelo de lenguaje tensorflow y Predecir la siguiente palabra usando el ejemplo del modelo TMTM ptb tensorflow son preguntas similares. Sin embargo, ninguno muestra el código para tomar realmente las primeras palabras de una oración e imprimir su predicción de la siguiente palabra. Intenté pegar el código de la segunda pregunta y de https://stackoverflow.com/a/39282697/841830 (que viene con una twig de github), pero tampoco puedo ejecutar sin errores. Creo que pueden ser para una versión anterior de TensorFlow?

OTRA ACTUALIZACIÓN: otra pregunta que hace básicamente lo mismo: Predicción de la próxima palabra del modelo LSTM a partir del ejemplo de Tensorflow Se enlaza a Predicción de la siguiente palabra usando el ejemplo del modelo del lenguaje tensorflow (y, nuevamente, las respuestas no son exactamente lo que estoy buscando) .

En caso de que aún no esté claro, lo que estoy intentando escribir es una función de alto nivel llamada getNextWord(model, sentencePrefix) , donde el model es un LSTM creado previamente que he cargado desde el disco, y sentencePrefix es una cadena, como como “Abrir el”, y podría devolver “pod”. Entonces podría llamarlo “Abrir el pod” y devolverá “bay”, y así sucesivamente.

Un ejemplo (con un carácter RNN y utilizando mxnet) es la función sample() que se muestra cerca del final de https://github.com/zackchase/mxnet-the-straight-dope/blob/master/chapter05_recurrent-neural-networks /simple-rnn.ipynb Puede llamar a sample() durante el entrenamiento, pero también puede llamarlo después del entrenamiento y con cualquier oración que desee.

Pregunta principal

Cargando palabras

Cargue datos personalizados en lugar de usar el conjunto de prueba:

 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 debe contener identificadores de palabras (imprima word_to_id para una asignación). Como ejemplo, debería verse como: [1, 52, 562, 246] …

Mostrando predicciones

Necesitamos devolver la salida de la capa FC ( logits ) en la llamada a 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 } 

Más adelante en la función, vals['top_word_id'] tendrá una matriz de enteros con el ID de la palabra superior. Busque esto en word_to_id para determinar la palabra predicha. Hice esto hace un tiempo con el modelo pequeño, y la precisión del primer puesto fue bastante baja (20-30% iirc), aunque la perplejidad fue lo que se predijo en el encabezado.

Sub-preguntas

¿Por qué usar una inserción de palabras al azar (sin inicializar, sin capacitación)?

Tendría que preguntar a los autores, pero en mi opinión, el entrenamiento de las incrustaciones hace que sea más un tutorial independiente: en lugar de tratar la incrustación como una caja negra, muestra cómo funciona.

¿Por qué usar softmax?

La predicción final no está determinada por la similitud del coseno con la salida de la capa oculta. Hay una capa FC después de la LSTM que convierte el estado incrustado en una encoding de la palabra final.

Aquí hay un bosquejo de las operaciones y dimensiones en la neural network:

 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 capa oculta tiene que coincidir con la dimensión de la entrada (es decir, la dimensión de las incrustaciones de word2vec)

Técnicamente, no. Si observa las ecuaciones de LSTM, notará que x (la entrada) puede ser de cualquier tamaño, siempre que la matriz de peso se ajuste adecuadamente.

Ecuaciones LSTM

¿Cómo / Puedo traer un modelo pre-entrenado de word2vec, en lugar de uno no inicializado?

No lo se disculpa

Mi pregunta más importante es ¿cómo uso el modelo producido para generar una sugerencia de la siguiente palabra, dadas las primeras palabras de una oración?

Es decir, estoy intentando escribir una función con la firma: getNextWord (model, sentencePrefix)

Antes de explicar mi respuesta, primero un comentario sobre su sugerencia de # Call static_rnn(cell) once for each word in prefix to initialize state static_rnn # Call static_rnn(cell) once for each word in prefix to initialize state : static_rnn en cuenta que static_rnn no devuelve un valor como una matriz numpy, sino un tensor. Puede evaluar un tensor a un valor cuando se ejecuta (1) en una sesión (una sesión es el estado de su gráfico computacional, incluidos los valores de los parámetros de su modelo) y (2) con la entrada que es necesaria para calcular El valor tensorial. La entrada se puede proporcionar utilizando lectores de entrada (el enfoque en el tutorial), o utilizando marcadores de posición (que usaré a continuación).

Ahora sigue la respuesta real: el modelo en el tutorial fue diseñado para leer datos de entrada de un archivo. La respuesta de @ user3080953 ya mostraba cómo trabajar con su propio archivo de texto, pero como lo entiendo, necesita un mayor control sobre cómo se alimenta la información al modelo. Para hacer esto, deberá definir sus propios marcadores de posición y alimentar los datos a estos marcadores de posición al llamar a session.run() .

En el código que PTBModel continuación, PTBModel subclase de PTBModel y la responsabilizé de proporcionar datos explícitos al modelo. Introduje un PTBInteractiveInput especial que tiene una interfaz similar a PTBInput para que pueda reutilizar la funcionalidad en PTBModel . Para entrenar tu modelo todavía necesitas 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 

En la función __init__ de PTBModel , debe agregar esta línea:

 self.logits = logits 

¿Por qué usar una inserción de palabras al azar (sin inicializar, sin capacitación)?

Primero tenga en cuenta que, aunque las incrustaciones son aleatorias al principio, se entrenarán con el rest de la red. Las incrustaciones que obtenga después del entrenamiento tendrán propiedades similares a las incorporaciones que obtiene con los modelos word2vec, por ejemplo, la capacidad de responder preguntas de analogía con operaciones vectoriales (rey – hombre + mujer = reina, etc.) En las tareas en las que tiene una cantidad considerable De los datos de entrenamiento, como el modelado del lenguaje (que no necesita datos de entrenamiento anotados) o la traducción automática neuronal, es más común entrenar las incrustaciones desde cero.

¿Por qué usar softmax?

Softmax es una función que normaliza un vector de puntuaciones de similitud (los logits), a una distribución de probabilidad. Necesita una distribución de probabilidad para entrenar su modelo con pérdida de entropía cruzada y poder muestrear el modelo. Tenga en cuenta que si solo está interesado en las palabras más probables de un modelo entrenado, no necesita el softmax y puede usar los logits directamente.

¿La capa oculta tiene que coincidir con la dimensión de la entrada (es decir, la dimensión de las incrustaciones de word2vec)

No, en principio puede ser cualquier valor. Sin embargo, el uso de un estado oculto con una dimensión inferior a la dimensión de incrustación no tiene mucho sentido.

¿Cómo / Puedo traer un modelo pre-entrenado de word2vec, en lugar de uno no inicializado?

Aquí hay un ejemplo autocontenido de inicializar una incrustación con una matriz numpy dada. Si desea que la incrustación permanezca fija / constante durante el entrenamiento, establezca lo trainable en 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) 

Hay muchas preguntas, yo trataría de aclarar algunas de ellas.

¿Cómo uso el modelo producido para generar una sugerencia de la siguiente palabra, dadas las primeras palabras de una oración?

El punto clave aquí es que la siguiente generación de palabras es en realidad la clasificación de palabras en el vocabulario. Entonces necesitas un clasificador, por eso hay un softmax en la salida.

El principio es que, en cada paso del tiempo, el modelo generará la siguiente palabra según la inserción de la última palabra y la memoria interna de las palabras anteriores. tf.contrib.rnn.static_rnn combina automáticamente la entrada en la memoria, pero debemos proporcionar la última palabra incrustada y clasificar la siguiente palabra.

Podemos usar un modelo de Word2vec pre-entrenado, simplemente inicie la matriz de embedding con la pre-entrenada. Creo que el tutorial usa una matriz aleatoria para simplificar. El tamaño de la memoria no está relacionado con el tamaño de incrustación, puede usar un tamaño de memoria mayor para retener más información.

Estos tutoriales son de alto nivel. Si desea comprender a fondo los detalles, sugeriría que busque el código fuente en python / numpy.

Puedes encontrar todo el código al final de la respuesta.


Reconozco que la mayoría de sus preguntas (por qué un Softmax, cómo usar una capa de inserción pre-entrenada, etc.) fueron respondidas. Sin embargo, como todavía esperabas un código conciso para producir el texto generado a partir de una semilla, aquí trato de informar cómo terminé haciéndolo.

Luché, a partir del tutorial oficial de Tensorflow, para llegar al punto en el que podía generar fácilmente palabras a partir de un modelo producido. Afortunadamente, después de tomar algunas respuestas en prácticamente todas las respuestas que mencionó en su pregunta, obtuve una mejor visión del problema (y soluciones). Esto puede contener errores, pero al menos se ejecuta y genera algo de texto …

¿Cómo uso el modelo producido para generar una sugerencia de la siguiente palabra, dadas las primeras palabras de una oración?

Envolveré la siguiente sugerencia de palabras en un bucle, para generar una oración completa, pero la reducirás fácilmente a una sola palabra.

Digamos que siguió el tutorial actual proporcionado por tensorflow (v1.4 al momento de escribir) aquí , que guardará un modelo después de entrenarlo.

Entonces, lo que nos queda por hacer es cargarlo desde el disco y escribir una función que tome este modelo y algunas entradas de información inicial y devuelva el texto generado.


Generar texto desde el modelo guardado

Supongo que escribimos todo este código en un nuevo script de python. Todo el script en la parte inferior como resumen, aquí explico los pasos principales.

Primeros pasos necesarios

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

Ahora, lo que es más importante, creamos diccionarios para asignar identificaciones a palabras y viceversa (por lo que no tenemos que leer una lista de enteros …).

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

Luego num_steps la clase de configuración, también establecemos num_steps y batch_size en 1, ya que queremos muestrear 1 palabra a la vez, mientras que el LSTM procesará también 1 palabra a la vez. También creando la instancia de entrada sobre la marcha:

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

Gráfico de construcción

Para cargar el modelo guardado (tal como lo guardó el módulo Supervisor.saver en el tutorial), primero debemos reconstruir el gráfico (fácil con la clase PTBModel ) que debe usar la misma configuración que cuando se entrenó:

 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) 

Restaurando pesos guardados:

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

… Muestreando palabras de una semilla dada:

Primero necesitamos que el modelo contenga un acceso a las salidas de logits, o más precisamente la distribución de probabilidad en todo el vocabulario. Así que en el archivo ptb_lstm.py agregue la línea:

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

Luego, podemos diseñar algunas funciones de muestreo (puede usar lo que quiera aquí, el mejor enfoque es muestrear con una temperatura que tiende a aplanar o agudizar las distribuciones), aquí hay un método de muestreo aleatorio básico:

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

Y finalmente, una función que toma una semilla, su modelo, el diccionario que asigna palabras a ids, y viceversa, como entradas y salidas de la cadena de textos generada:

 def generate_text(session, model, word_to_index, index_to_word, seed='', 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['']: text += '.\n' sentence_cnt += 1 else: text += ' ' + index_to_word[sampled_word] input_wordid = [[sampled_word]] return text 

TL; DR

No te olvides de añadir la línea:

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

En el archivo ptb_lstm.py , en la definición PTBModel clase PTBModel , en cualquier lugar después de la línea logits = tf.reshape(logits, [self.batch_size, self.num_steps, vocab_size]) .

El script completo, simplemente ejecútelo desde el mismo directorio donde tiene 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='', 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['']: 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.restre(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 

Actualizar

En cuanto a la restauración de los puntos de control antiguos (para mí, el modelo guardado hace 6 meses, no estoy seguro de la versión exacta de TF utilizada en ese momento) con tensorflow reciente (al menos 1.6), podría generar un error sobre algunas variables que no se encuentran (ver comentario). En ese caso, debe actualizar sus puntos de control utilizando este script .

Además, tenga en cuenta que, para mí, tuve que modificar esto aún más, ya que me di cuenta de que la función saver.restre intentaba leer lstm_cell variables lstm_cell , aunque mis variables se transformaron en basic_lstm_cell que también me llevó a NotFound Error . Así que una solución fácil, solo un pequeño cambio en el script checkpoint_convert.py , línea 72-73, es eliminar basic_ en los nuevos nombres.

Una forma conveniente de verificar el nombre de las variables contenidas en sus puntos de control es ( CKPT_FILE es el sufijo que aparece antes de .index , .data0000-1000 , etc.):

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

De esta manera puede verificar que tiene los nombres correctos (o los malos en las versiones anteriores de los puntos de control).