Blog
Las redes neuronales recurrentes también pueden usarse como modelos generativos. Esto significa que, además de ser utilizados para modelos predictivos (hacer predicciones), pueden aprender las secuencias de un problema y luego generar secuencias plausibles completamente nuevas para el dominio del problema. Los modelos generativos son útiles no solo para estudiar qué tan bien un modelo ha aprendido un problema, sino para aprender más sobre el dominio del problema en sí mismo. En este proyecto, descubrirá cómo crear un modelo generativo de texto, carácter por carácter, utilizando las redes neuronales recurrentes de LSTM en Python con Keras. Después de completar este proyecto usted sabrá:
- Dónde descargar un corpus de texto gratuito que se puede usar para entrenar modelos generativos de texto.
- Cómo enmarcar el problema de las secuencias de texto en un modelo generativo de red neuronal recurrente.
- Cómo desarrollar una red LSTM para generar secuencias de texto plausibles para un problema determinado.
Descripción del problema: Generación de texto
Muchos de los textos clásicos ya no están protegidos por derechos de autor. Esto significa que se pueden descargar todo el texto de estos libros de forma gratuita y utilizarlos en experimentos, como crear modelos generativos. En este tutorial vamos a utilizar un libro de la infancia como el conjunto de datos: Los tres cerditos
Para descargar el libro por la red o también desde aquí directamente tienes el archivo txt.
Vamos a aprender las dependencias entre los caracteres y las probabilidades condicionales de los personajes en las secuencias para que a su vez podamos generar secuencias de caracteres totalmente nuevas y originales. Este tutorial es muy divertido y recomiendo repetir estos experimentos con otros libros, puedes encontrar muchos libros en el Proyecto Gutenberg. Estos experimentos no están limitados al texto, también puede experimentar con otros datos ASCII, como el código fuente de la computadora, los documentos marcados en LaTeX, HTML, subtitulos de películas o hasta un partido de futbol.
Puede descargar el texto completo en formato ASCII (Plain Text UTF-8)3 para este libro de forma gratuita y colocarlo en su directorio de trabajo con el nombre de los3.txt. Ahora tenemos que preparar el conjunto de datos para el modelado. Nuestro libro de ejemplo no tiene encabezado o pie de pagina pero en caso de que lo tenga deberías de eliminarlo.
Desarrollar una pequeña Red Neuronal LSTM Recurrente
En esta sección desarrollaremos una sencilla red LSTM para aprender secuencias de personajes de Los tres cerditos. En la siguiente sección usaremos este modelo para generar nuevas secuencias de caracteres. Empecemos por importar las clases y funciones que pretendemos usar para entrenar a nuestro modelo.
import numpy from keras.models import Sequential from keras.layers import Dense from keras.layers import Dropout from keras.layers import LSTM from keras.callbacks import ModelCheckpoint from keras.utils import np_utils
A continuación tenemos que cargar el texto ASCII del libro en la memoria y convertir todos los caracteres a minúsculas para reducir el vocabulario que la red debe aprender.
# cargamos el texto y lo pasamos a minuscula filename = "los3.txt"; raw_text = open(filename).read() raw_text = raw_text.lower()
Ahora que el libro está cargado, debemos preparar los datos para el modelado de la red neuronal. No podemos modelar los caracteres directamente, sino que debemos convertirlos en enteros. Podemos hacer esto fácilmente creando primero un conjunto de todos los caracteres distintos en el libro, y luego creando un mapa de cada carácter con un número entero único.
#crear mapeo de caracteres únicos a enteros, y un mapeo inverso chars = sorted(list(set(raw_text))) char_to_int = dict((c, i) for i, c in enumerate(chars))
Por ejemplo, la lista de caracteres únicos en minúsculas ordenados en el libro es la siguiente:
['\n', '\r', ' ', '!', '"', "'", '(', ')', '*', ',', '-', '.', ':', ';', '?', '[', ']', '_', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '\xbb', '\xbf', '\xef']
Usted puede ver que puede haber algunos caracteres que podríamos eliminar para limpiar aún más el conjunto de datos que reducirá el vocabulario y puede mejorar el proceso de modelado. Ahora que el libro ha sido cargado y el mapeo preparado, podemos resumir el conjunto de datos.
n_chars = len(raw_text) n_vocab = len(chars) print("Total de caracteres:", n_chars) print("Total vocales:", n_vocab)
Corriendo el siguiente código podemos ver el siguiente resultado:
Total de caracteres: 3139 Total vocales: 38
Podemos ver que el libro tiene algo menos de 3139 caracteres y que cuando se convierte a minúsculas sólo hay 38 caracteres distintos en el vocabulario para que la red aprenda. Mucho más que los 26 del alfabeto. Ahora tenemos que analizar los datos de formación para la red. Hay mucha flexibilidad en la forma en que usted decide dividir el texto y exponerlo a la red durante la formación. En este tutorial dividiremos el texto del libro en las siguientes secciones con una longitud fija de 100 caracteres, una longitud arbitraria. Podríamos fácilmente dividir los datos por frases y rellenar las secuencias más cortas y truncar las más largas.
Cada patrón de formación de la red se compone de 100 pasos de tiempo de un carácter (X) seguido de la salida de un carácter (y). Al crear estas secuencias, deslizamos esta ventana a lo largo de todo el libro, un personaje a la vez, lo que permite que cada personaje tenga la oportunidad de aprender de los 100 caracteres que lo precedieron (excepto los primeros 100 caracteres, por supuesto). Por ejemplo, si la longitud de la secuencia es 5 (por simplicidad) entonces los dos primeros patrones de entrenamiento serían los siguientes:
CHAPT -> E HAPTE -> R
A medida que dividimos el libro en estas secuencias, convertimos los caracteres en enteros usando nuestra tabla de búsqueda que preparamos anteriormente.
#preparar el conjunto de datos de entrada para los pares de salida codificados como enteros seq_length = 100 dataX = [] dataY = [] for i in range(0, n_chars - seq_length, 1): seq_in = raw_text[i:i + seq_length] seq_out = raw_text[i + seq_length] dataX.append([char_to_int[char] for char in seq_in]) dataY.append(char_to_int[seq_out]) n_patterns = len(dataX) print("Total patrones: ", n_patterns)
La ejecución del código hasta este punto nos muestra que cuando dividimos el conjunto de datos en datos de formación para que la red se entere de que tenemos algo menos de Total Patterns: 3039 patrones de formación. Esto tiene sentido ya que excluyendo los primeros 100 caracteres, tenemos un patrón de entrenamiento para predecir cada uno de los caracteres restantes.
Total patrones: 3039
Ahora que hemos preparado nuestros datos de entrenamiento, necesitamos transformarlos para que sean adecuados para su uso con Keras. Primero debemos transformar la lista de secuencias de entrada en la forma[muestras, pasos de tiempo, características] esperada por una red LSTM. A continuación necesitamos reescalar los números enteros al rango 0-a-1 para hacer que los patrones sean más fáciles de aprender por la red LSTM que utiliza la función de activación sigmoide por defecto.
Finalmente, necesitamos convertir los patrones de salida (caracteres individuales convertidos en enteros) en una codificación en caliente. Esto es para que podamos configurar la red para predecir la probabilidad de cada uno de los 47 caracteres diferentes en el vocabulario (una representación más fácil) en lugar de tratando de forzarlo a predecir con precisión el siguiente personaje. Cada valor de y se convierte en un vector disperso con una longitud de 38, lleno de ceros excepto con un 1 en la columna para la letra (entero) que el patrón representa. Por ejemplo, cuando n (valor entero 31) es un valor codificado en caliente, se ve como como sigue:
0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0.
0. 0. 0. 0. 0. 0. 0. 0.]
Podemos implementar estos pasos como:
# remodelar X para que sea [muestras, pasos de tiempo, características] X = numpy.reshape(dataX, (n_patterns, seq_length, 1)) # normalizacion X = X / float(n_vocab) # codificacion en caliente con la variable de salida y = np_utils.to_categorical(dataY)
Ahora podemos definir nuestro modelo LSTM. Aquí definimos una única capa LSTM oculta con 256 unidades de memoria. La red utiliza el abandono con una probabilidad del 20%. La capa de salida es una capa densa que utiliza la función de activación de softmax para generar una predicción de probabilidad para cada uno de los 38 caracteres entre 0 y 1. El problema es en realidad un problema de clasificación de un solo carácter con 38 clases y, como tal, se define como la optimización del pérdida de registro (entropía cruzada), aquí estamos utilizando el algoritmo de optimización de ADAM para la velocidad.
# define el LSTM model model = Sequential() model.add(LSTM(256, input_shape=(X.shape[1], X.shape[2]))) model.add(Dropout(0.2)) model.add(Dense(y.shape[1], activation='softmax')) model.compile(loss='categorical_crossentropy', optimizer='adam')
No existe un conjunto de datos de prueba. Estamos modelando todo el conjunto de datos de entrenamiento para aprender la probabilidad de cada carácter en una secuencia. No nos interesa el modelo más preciso (precisión de clasificación) del conjunto de datos de formación. Este sería un modelo que predice cada carácter en el conjunto de datos de entrenamiento perfectamente. En su lugar, estamos interesados en una generalización del conjunto de datos que minimice la función de pérdida elegida. Buscamos un equilibrio entre la generalización y la adaptación, pero sin memorizar.
La red tarda en entrenarse (unos 35 segundos por época en mi pc usando la CPU). Porque de la lentitud y debido a nuestros requisitos de optimización, utilizaremos el modelo de checkpointing para registrar todos los pesos de la red cada vez que se observe una mejora en las pérdidas en el fin de la época. Usaremos el mejor conjunto de pesos (la pérdida más baja) para instanciar nuestro sistema generativo en la siguiente sección.
# se define el checkpoint filepath="pesos-los3-30-{epoch:02d}-{loss:.4f}.hdf5"; checkpoint = ModelCheckpoint(filepath, monitor='loss', verbose=1, save_best_only=True, mode='min') callbacks_list = [checkpoint]
Ahora podemos ajustar nuestro modelo a los datos. Aquí utilizamos 50 épocas y un tamaño de lote grande de 128 patrones.
# ajuste del modelo model.fit(X, y, epochs=50, batch_size=128, callbacks=callbacks_list)
Aquí presentamos el código completo de este apartado del proyecto
# Pqueña LSTM Network para generar texto para los 3 cerditos import sys import numpy from keras.models import Sequential from keras.layers import Dense from keras.layers import Dropout from keras.layers import LSTM from keras.callbacks import ModelCheckpoint from keras.utils import np_utils # cargamos el texto y lo pasamos a minuscula filename = "los3.txt" raw_text = open(filename).read() raw_text = raw_text.lower() # crear mapeo de caracteres únicos a enteros, y un mapeo inverso chars = sorted(list(set(raw_text))) char_to_int = dict((c, i) for i, c in enumerate(chars)) # sumarizamos los datos cargados n_chars = len(raw_text) n_vocab = len(chars) print("Total caracters: ", n_chars) print("Total vocabulario: ", n_vocab) # preparar el conjunto de datos de entrada para los pares de salida codificados como enteros seq_length = 100 dataX = [] dataY = [] for i in range(0, n_chars - seq_length, 1): seq_in = raw_text[i:i + seq_length] seq_out = raw_text[i + seq_length] dataX.append([char_to_int[char] for char in seq_in]) dataY.append(char_to_int[seq_out]) n_patterns = len(dataX) print("Total patrones: ", n_patterns) # remodelar X para que sea [muestras, pasos de tiempo, características] X = numpy.reshape(dataX, (n_patterns, seq_length, 1)) # normalizacion X = X / float(n_vocab) # codificacion en caliente con la variable de salida y = np_utils.to_categorical(dataY) # se define el LSTM modelo model = Sequential() model.add(LSTM(256, input_shape=(X.shape[1], X.shape[2]))) model.add(Dropout(0.2)) model.add(Dense(y.shape[1], activation='softmax')) model.compile(loss='categorical_crossentropy', optimizer='adam') # se define el checkpoint filepath="pesos-los3-30-{epoch:02d}-{loss:.4f}.hdf5"; checkpoint = ModelCheckpoint(filepath, monitor='loss', verbose=1, save_best_only=True, mode='min') callbacks_list = [checkpoint] # ajuste del modelo model.fit(X, y, epochs=50, batch_size=128, callbacks=callbacks_list)
Verá resultados diferentes debido a la naturaleza estocástica del modelo, y porque es difícil x la semilla aleatoria para los modelos LSTM para obtener resultados 100% reproducibles. Esto no es una preocupación para este modelo generativo. Después de ejecutar el ejemplo, debería tener un número de puntos de control de peso en el directorio local. Puede borrarlos todos excepto el que tenga el menor valor de pérdida. Por ejemplo, cuando corrí este ejemplo, debajo estaba el punto de control con la menor pérdida que logré.
pesos-los3-30-144-0.0181.hdf5
La pérdida de la red disminuyó casi todas las épocas y espero que la red pueda dejar de entrenar por muchas más épocas. En la siguiente sección veremos el uso de este modelo para generar nuevas secuencias de texto.
Generación de texto con una Red LSTM
La generación de texto utilizando la red LSTM es relativamente sencilla. En primer lugar, se cargan los datos y se define la red exactamente de la misma manera, excepto que los pesos de la red se cargan desde un punto de control y la red no necesita ser entrenada.
# Carga de los pesos de la red filename="pesos-los3-30-144-0.0181.hdf5" model.load_weights(filename) model.compile(loss='categorical_crossentropy', optimizer='adam')
Además, al preparar el mapeo de caracteres únicos a enteros, también debemos crear un mapeo inverso que podamos usar para convertir los enteros de nuevo en caracteres para que podamos entender las predicciones.
int_to_char = dict((i, c) for i, c in enumerate(chars))
Por último, tenemos que hacer predicciones. La forma más sencilla de usar el modelo LSTM de Keras para hacer predicciones es comenzar primero con una secuencia de semillas como entrada, generar el siguiente carácter y luego actualizar la secuencia de semillas para añadir el carácter generado al final y recortar el primer carácter. Este proceso se repite mientras queramos predecir nuevos caracteres (por ejemplo, una secuencia de 1.000 caracteres de longitud). Podemos elegir un patrón de entrada aleatorio como nuestra semilla y luego imprimir los caracteres generados a medida que los generamos.
# toma una semilla aleatoria start = numpy.random.randint(0, len(dataX)-1) pattern = dataX[start] print("Semilla:") print("\"", ''.join([int_to_char[value] for value in pattern]), "\"") # generate characters for i in range(1000): x = numpy.reshape(pattern, (1, len(pattern), 1)) x = x / float(n_vocab) prediction = model.predict(x, verbose=0) index = numpy.argmax(prediction) result = int_to_char[index] seq_in = [int_to_char[value] for value in pattern] sys.stdout.write(result) pattern.append(index) pattern = pattern[1:len(pattern)] #print(prediction) #print(index) print("\nHecho.")
El ejemplo de código completo para generar texto usando el modelo LSTM cargado se muestra a continuación para completar.
# Carga de la red LSTM pequeña para generar texto import sys import numpy from keras.models import Sequential from keras.layers import Dense from keras.layers import Dropout from keras.layers import LSTM from keras.utils import np_utils # load ascii text and covert to lowercase filename = "los3.txt" raw_text = open(filename).read() raw_text = raw_text.lower() # create mapping of unique chars to integers, and a reverse mapping chars = sorted(list(set(raw_text))) char_to_int = dict((c, i) for i, c in enumerate(chars)) int_to_char = dict((i, c) for i, c in enumerate(chars)) # summarize the loaded data n_chars = len(raw_text) n_vocab = len(chars) print("Total Characters: ", n_chars) print("Total Vocab: ", n_vocab) # prepare the dataset of input to output pairs encoded as integers seq_length = 100 dataX = [] dataY = [] for i in range(0, n_chars - seq_length, 1): seq_in = raw_text[i:i + seq_length] seq_out = raw_text[i + seq_length] dataX.append([char_to_int[char] for char in seq_in]) dataY.append(char_to_int[seq_out]) n_patterns = len(dataX) print("Total Patterns: ", n_patterns) # reshape X to be [samples, time steps, features] X = numpy.reshape(dataX, (n_patterns, seq_length, 1)) # normalizacion X = X / float(n_vocab) # codificacion en caliente de la variable de salida y = np_utils.to_categorical(dataY) # define la LSTM model model = Sequential() model.add(LSTM(256, input_shape=(X.shape[1], X.shape[2]))) model.add(Dropout(0.2)) model.add(Dense(y.shape[1], activation='softmax')) # Carga de los pesos de la red filename="pesos-los3-30-144-0.0181.hdf5" model.load_weights(filename) model.compile(loss='categorical_crossentropy', optimizer='adam') # toma una semilla aleatoria start = numpy.random.randint(0, len(dataX)-1) pattern = dataX[start] print("Semilla:") print("\"", ''.join([int_to_char[value] for value in pattern]), "\"") # generate characters for i in range(1000): x = numpy.reshape(pattern, (1, len(pattern), 1)) x = x / float(n_vocab) prediction = model.predict(x, verbose=0) index = numpy.argmax(prediction) result = int_to_char[index] seq_in = [int_to_char[value] for value in pattern] sys.stdout.write(result) pattern.append(index) pattern = pattern[1:len(pattern)] #print(prediction) #print(index) print("\nHecho.")
Ejecutando este ejemplo primero se produce la semilla aleatoria seleccionada, luego cada carácter a medida que se genera. Por ejemplo, a continuación se muestran los resultados de una ejecución de este generador de texto. La semilla aleatoria fue:
Semilla: " me abrís... ¡soplaré y soplaré y la casita derribaré! la madera crujió, y las paredes cayeron y lo "
El texto generado con la semilla aleatoria (limpiado para presentación) fue:
ca casi l posi e aloo neroz, a lobo e lobo! ¡quié eem lo lo le aa casi e ¡uuié eem la lobo feroz, al lobo feroz! al lobo e lobo! - ¡quié eem lo lo le aa casi e ¡uuié eem la lobo feroz, al lobo feroz! al lobo e lobo! - ¡quié eem lo lo le aa casi e ¡uuié eem la lobo feroz, al lobo feroz! al lobo e lobo! - ¡quié eem lo lo le aa casi e ¡uuié eem la lobo feroz, al lobo feroz! al lobo e lobo! - ¡quié eem lo lo le aa casi e ¡uuié eem la lobo feroz, al lobo feroz! al lobo e lobo! - ¡quié eem lo lo le aa casi e ¡uuié eem la lobo feroz, al lobo feroz! al lobo e lobo! - ¡quié eem lo lo le aa casi e ¡uuié eem la lobo feroz, al lobo feroz! al lobo e lobo! - ¡quié eem lo lo le aa casi e ¡uuié eem la lobo feroz, al lobo feroz! al lobo e lobo! - ¡quié eem lo lo le aa casi e ¡uuié eem la lobo feroz, al lobo feroz! al lobo e lobo! - ¡quié eem lo lo le aa casi e ¡uuié eem la lobo feroz, al lobo feroz! al lobo e lobo! - ¡quié eem lo Hecho.
Podemos notar algunas observaciones sobre el texto generado.
- Por lo general, se ajusta al formato de línea observado en el texto original de menos de 80 caracteres antes de una nueva línea.
- Algunas de las palabras en secuencia tienen sentido, pero muchas no lo tienen.
El hecho de que este modelo del libro, basado en caracteres, produzca resultados como estos es muy impresionante. Le da una idea de las capacidades de aprendizaje de las redes LSTM. Los resultados son no perfecto. En la siguiente sección se analiza la mejora de la calidad de los resultados mediante el desarrollo de un una red LSTM mucho más grande.
Red Neural Recurrente LSTM más grande
Ahora vamos ha hacer los mismo creando una red mucho más grande. Mantendremos el mismo número de unidades de memoria en 256, pero añadiremos una segunda capa.
# define la LSTM model model = Sequential() model.add(LSTM(256, input_shape=(X.shape[1], X.shape[2]), return_sequences=True)) model.add(Dropout(0.2)) model.add(LSTM(256)) model.add(Dropout(0.2)) model.add(Dense(y.shape[1], activation='softmax')) model.compile(loss='categorical_crossentropy', optimizer='adam')
También cambiaremos el nombre del peso de los puntos de control para que podamos determinar la diferencia entre los pesos de esta red y el anterior (agregando la palabra más grande en el nombre del archivo).
filename="pesos-los3grande-303-30-2.3081.hdf5"
Por último, aumentaremos el número de épocas de formación de 50 a 150 y reduciremos el tamaño del lote de 128 a 64 para dar a la red más oportunidades de actualización y aprendizaje. La lista completa de códigos se presenta a continuación para mayor información.
# Grande LSTM Network para generar texto para Alicia en el país de las maravillas import sys import numpy from keras.models import Sequential from keras.layers import Dense from keras.layers import Dropout from keras.layers import LSTM from keras.callbacks import ModelCheckpoint from keras.utils import np_utils # cargamos el texto y lo pasamos a minuscula filename = "los3.txt" raw_text = open(filename).read() raw_text = raw_text.lower() # crear mapeo de caracteres únicos a enteros, y un mapeo inverso chars = sorted(list(set(raw_text))) char_to_int = dict((c, i) for i, c in enumerate(chars)) # sumarizamos los datos cargados n_chars = len(raw_text) n_vocab = len(chars) print("Total caracters: ", n_chars) print("Total vocabulario: ", n_vocab) # preparar el conjunto de datos de entrada para los pares de salida codificados como enteros seq_length = 100 dataX = [] dataY = [] for i in range(0, n_chars - seq_length, 1): seq_in = raw_text[i:i + seq_length] seq_out = raw_text[i + seq_length] dataX.append([char_to_int[char] for char in seq_in]) dataY.append(char_to_int[seq_out]) n_patterns = len(dataX) print("Total patrones: ", n_patterns) # remodelar X para que sea [muestras, pasos de tiempo, características] X = numpy.reshape(dataX, (n_patterns, seq_length, 1)) # normalizacion X = X / float(n_vocab) # codificacion en caliente con la variable de salida y = np_utils.to_categorical(dataY) # se define el LSTM modelo model = Sequential() model.add(LSTM(256, input_shape=(X.shape[1], X.shape[2]), return_sequences=True)) model.add(Dropout(0.2)) model.add(LSTM(256)) model.add(Dropout(0.2)) model.add(Dense(y.shape[1], activation='softmax')) model.compile(loss='categorical_crossentropy', optimizer='adam') # se define el checkpoint filepath="pesos-los3grandes-303-{epoch:02d}-{loss:.4f}.hdf5"; checkpoint = ModelCheckpoint(filepath, monitor='loss', verbose=1, save_best_only=True, mode='min') callbacks_list = [checkpoint] # ajuste del modelo model.fit(X, y, epochs=150, batch_size=64, callbacks=callbacks_list)
La ejecución de este ejemplo lleva algún tiempo, dependiendo de tu pc. Después de ejecutar este ejemplo, puede lograr una pérdida de aproximadamente 0.018. Por ejemplo, el mejor resultado que obtuve al ejecutar este modelo se almacenó en un archivo de punto de control con el nombre:
filename="pesos-los3grandes-303-30-2.3081.hdf5"
Como en la sección anterior, podemos utilizar este mejor modelo de la ejecución para generar texto. El único cambio que necesitamos hacer en el script de generación de texto de la sección anterior está en la especificación de la topología de la red y desde qué archivo sembrar los pesos de la red. La lista completa de códigos se proporciona a continuación para mayor información.
# Carga de la red LSTM grande para generar texto import sys import numpy from keras.models import Sequential from keras.layers import Dense from keras.layers import Dropout from keras.layers import LSTM from keras.utils import np_utils # load ascii text and covert to lowercase filename = "los3.txt" raw_text = open(filename).read() raw_text = raw_text.lower() # create mapping of unique chars to integers, and a reverse mapping chars = sorted(list(set(raw_text))) char_to_int = dict((c, i) for i, c in enumerate(chars)) int_to_char = dict((i, c) for i, c in enumerate(chars)) # summarize the loaded data n_chars = len(raw_text) n_vocab = len(chars) print("Total Characters: ", n_chars) print("Total Vocab: ", n_vocab) # prepare the dataset of input to output pairs encoded as integers seq_length = 100 dataX = [] dataY = [] for i in range(0, n_chars - seq_length, 1): seq_in = raw_text[i:i + seq_length] seq_out = raw_text[i + seq_length] dataX.append([char_to_int[char] for char in seq_in]) dataY.append(char_to_int[seq_out]) n_patterns = len(dataX) print("Total Patterns: ", n_patterns) # reshape X to be [samples, time steps, features] X = numpy.reshape(dataX, (n_patterns, seq_length, 1)) # normalizacion X = X / float(n_vocab) # codificacion en caliente de la variable de salida y = np_utils.to_categorical(dataY) # define la LSTM model model = Sequential() model.add(LSTM(256, input_shape=(X.shape[1], X.shape[2]), return_sequences=True)) model.add(Dropout(0.2)) model.add(LSTM(256)) model.add(Dropout(0.2)) model.add(Dense(y.shape[1], activation='softmax')) model.compile(loss='categorical_crossentropy', optimizer='adam') # Carga de los pesos de la red filename="pesos-los3grandes-303-30-2.3081.hdf5" model.load_weights(filename) model.compile(loss='categorical_crossentropy', optimizer='adam') # toma una semilla aleatoria start = numpy.random.randint(0, len(dataX)-1) pattern = dataX[start] print("Semilla:") print("\"", ''.join([int_to_char[value] for value in pattern]), "\"") # generate characters for i in range(1000): x = numpy.reshape(pattern, (1, len(pattern), 1)) x = x / float(n_vocab) prediction = model.predict(x, verbose=0) index = numpy.argmax(prediction) result = int_to_char[index] seq_in = [int_to_char[value] for value in pattern] sys.stdout.write(result) pattern.append(index) pattern = pattern[1:len(pattern)] #print(prediction) #print(index) print("\nHecho.")
Un ejemplo de ejecución de este script de generación de texto produce la salida a continuación. El texto semilla elegido al azar fue:
Semilla: " rmanos que sería bueno que se pusieran a construir sus propias casas para estar protegidos. a los ot "
El texto generado con la semilla (limpiado para presentación) fue:
dos les pareció una buena idea, y se pusieron manos a la obra, cada uno construyó su casita. - la mía será de paja - dijo el más pequeño-, la paja es blanda y se puede sujetar con facilidad. terminaré muy pronto y podré ir a jugar. el hermano mediano decidió que su casa sería de madera: - puedo encontrar un montón de madera por los alrededores - explicó a sus hermanos, - construiré mi casa en un santiamén con todos estos troncos y me iré también a jugar. cuando las tres casitas estuvieron terminadas, los cerditos cantaban y bailaban en la puerta, felices por haber acabado con el problema: -¡quién teme al lobo feroz, al lobo, al lobo! - ¡quién teme al lobo feroz, al lobo feroz! - cantaban desde dentro los cerditos. el lobo estaba realmente enfadado y hambriento, y ahora deseaba comerse a los tres cerditos más que nunca, y frente a la puerta dijo: - ¡cerditos, abridme la puerta! - no, no, no, no te vamos a abrir. - pues si no me abrís... ¡soplaré y soplaré y la casita derriba Hecho.
Podemos ver que generalmente hay menos errores de ortografía y el texto parece más realista. Estos son mejores resultados, pero hay todavía hay mucho espacio para mejorar.
Ideas de extensión para mejorar el modelo
A continuación encontrará una muestra de ideas que puede que desee investigar para mejorar aún más el modelo:
- Predecir menos de 1.000 caracteres como salida para una semilla dada.
- Eliminar toda la puntuación del texto fuente y, por lo tanto, del vocabulario de los modelos.
- Prueba con una codificación en caliente para las secuencias de entrada.
- Entrene al modelo en oraciones acolchadas en lugar de secuencias aleatorias de caracteres.
- Aumentar el número de épocas de entrenamiento a 100 o cientos.
- Añada la pérdida de señal a la capa de entrada visible y considere la posibilidad de ajustar el porcentaje de pérdida de señal.
- Ajuste el tamaño del lote, pruebe un tamaño de lote de 1 como una línea de base (muy lenta) y tamaños más grandes desde allí.
- Añadir más unidades de memoria a las capas y/o más capas.
- Experimentar con los factores de escala (temperatura) al interpretar las probabilidades de predicción.
- Cambie las capas de LSTM para que estén en estado para mantener el estado en todos los lotes.
Disculpa la pregunta, en el articulo decis que en tu maquina tardo 30 segundos por epoca, en la mia tarda aproximadamente 5 minutos por epoca, siempre a CPU… ¿en que maquina lo corriste?
Hola Juan,
Pues yo lo acabo de correr y me tarda 50seg por época en un pc hp de 16gb de ram y procesador i7. Gracias por comentar!
Igualmente, y esperando la respuesa del mensaje anterior, los saludo y felicito por el sitio y sus post, realmente estan muy buenos.