Este es el artículo número 21 de mi serie de artículos sobre Python para PNL. En el artículo anterior, expliqué cómo usar la biblioteca FastText de Facebook para obtener similitudes semánticas y clasificar el texto. En este artículo, verá cómo generar texto usando una técnica de aprendizaje profundo en Python usando el Biblioteca de Keras.
La generación de texto es una de las aplicaciones más avanzadas de la PNL. Las técnicas de aprendizaje profundo se están utilizando para una variedad de tareas de generación de texto, como escribir poesía, generar guiones para películas e incluso componer música. En este artículo, sin embargo, veremos un ejemplo muy simple de generación de texto donde se nos da una cadena de entrada de palabra, predeciremos la siguiente palabra. Usaremos el texto en bruto de la famosa novela de Shakespeare «Macbeth» y lo usaremos para predecir la siguiente palabra después de una secuencia de palabras de entrada.
Después de completar este artículo, podrá generar texto utilizando el conjunto de datos de su elección. Así que comencemos sin más preámbulos.
Importar bibliotecas y conjuntos de datos
Contenido
El primer paso son las bibliotecas necesarias para ejecutar los scripts de este artículo, así como el conjunto de datos. El siguiente código importa las bibliotecas necesarias:
import numpy as np
from keras.models import Sequential, load_model
from keras.layers import Dense, Embedding, LSTM, Dropout
from keras.utils import to_categorical
from random import randint
import re
El siguiente paso es descargar el conjunto de datos. Usaremos la biblioteca Python NLTK para descargar el conjunto de datos. Usaremos el Conjunto de datos de Gutenberg, que contiene 3036 libros en inglés escritos por 142 autores, incluido «Macbeth» de Shakespeare.
El siguiente script descarga el conjunto de datos de Gutenberg e imprime los nombres de todos los archivos en el conjunto de datos.
import nltk
nltk.download('gutenberg')
from nltk.corpus import gutenberg as gut
print(gut.fileids())
Debería ver el siguiente resultado:
['austen-emma.txt', 'austen-persuasion.txt', 'austen-sense.txt', 'bible-kjv.txt', 'blake-poems.txt', 'bryant-stories.txt', 'burgess-busterbrown.txt', 'carroll-alice.txt', 'chesterton-ball.txt', 'chesterton-brown.txt', 'chesterton-thursday.txt', 'edgeworth-parents.txt', 'melville-moby_dick.txt', 'milton-paradise.txt', 'shakespeare-caesar.txt', 'shakespeare-hamlet.txt', 'shakespeare-macbeth.txt', 'whitman-leaves.txt']
El archivo shakespeare-macbeth.txt
hay un texto en bruto para la novela «Macbeth». Para leer el texto de este archivo, el raw
método de gutenberg
se puede usar una clase:
macbeth_text = nltk.corpus.gutenberg.raw('shakespeare-macbeth.txt')
Imprimamos los primeros 500 caracteres del conjunto de datos:
print(macbeth_text[:500])
Aquí está el resultado:
[The Tragedie of Macbeth by William Shakespeare 1603]
Actus Primus. Scoena Prima.
Thunder and Lightning. Enter three Witches.
1. When shall we three meet againe?
In Thunder, Lightning, or in Raine?
2. When the Hurley-burley's done,
When the Battaile's lost, and wonne
3. That will be ere the set of Sunne
1. Where the place?
2. Vpon the Heath
3. There to meet with Macbeth
1. I come, Gray-Malkin
All. Padock calls anon: faire is foule, and foule is faire,
Houer through
Verá que hay muchos caracteres especiales y números en el texto. El siguiente paso es borrar el conjunto de datos.
Preprocesamiento de datos
Para eliminar la puntuación y los caracteres especiales, definiremos una función con nombre preprocess_text()
:
def preprocess_text(sen):
# Remove punctuations and numbers
sentence = re.sub('[^a-zA-Z]', ' ', sen)
# Single character removal
sentence = re.sub(r"s+[a-zA-Z]s+", ' ', sentence)
# Removing multiple spaces
sentence = re.sub(r's+', ' ', sentence)
return sentence.lower()
El es preprocess_text
la función toma una cadena de texto como parámetro y devuelve una cadena de texto limpia en minúsculas.
Limpiemos nuestro texto ahora e imprimamos los primeros 500 caracteres nuevamente:
macbeth_text = preprocess_text(macbeth_text)
macbeth_text[:500]
Aquí está el resultado:
the tragedie of macbeth by william shakespeare actus primus scoena prima thunder and lightning enter three witches when shall we three meet againe in thunder lightning or in raine when the hurley burley done when the battaile lost and wonne that will be ere the set of sunne where the place vpon the heath there to meet with macbeth come gray malkin all padock calls anon faire is foule and foule is faire houer through the fogge and filthie ayre exeunt scena secunda alarum within enter king malcom
Convertir palabras en números
Los modelos de aprendizaje en profundidad se basan en algoritmos estadísticos. Por lo tanto, para trabajar con modelos de aprendizaje profundo, necesitamos convertir palabras en números.
Te puede interesar:Corutinas en PythonEn este artículo, utilizaremos un enfoque muy simple en el que las palabras se convierten en números enteros individuales. Antes de que podamos convertir palabras en números enteros, necesitamos significar nuestro texto en palabras individuales. Para hacer eso, el word_tokenize()
método de nltk.tokenize
se puede utilizar un módulo.
El siguiente script señala el texto en nuestro conjunto de datos y luego imprime el número total de palabras en el conjunto de datos, así como el número total de palabras únicas en el conjunto de datos:
from nltk.tokenize import word_tokenize
macbeth_text_words = (word_tokenize(macbeth_text))
n_words = len(macbeth_text_words)
unique_words = len(set(macbeth_text_words))
print('Total Words: %d' % n_words)
print('Unique Words: %d' % unique_words)
La salida se ve así:
Total Words: 17250
Unique Words: 3436
Nuestro texto contiene un total de 17250 palabras, de las cuales hay 3436 palabras únicas. Para convertir palabras de signos en números, el Tokenizer
clase de keras.preprocessing.text
se puede utilizar un módulo. Necesitas llamar al fit_on_texts
método y pasarle una lista de palabras. Se creará un diccionario en el que se mostrarán las claves de palabras, pero los números enteros mostrarán los valores correspondientes del diccionario.
Vea el siguiente script:
from keras.preprocessing.text import Tokenizer
tokenizer = Tokenizer(num_words=3437)
tokenizer.fit_on_texts(macbeth_text_words)
Para acceder al diccionario que contiene las palabras y los índices correspondientes, el word_index
el atributo del objeto tokenizer se puede utilizar:
vocab_size = len(tokenizer.word_index) + 1
word_2_index = tokenizer.word_index
Si verifica la longitud del diccionario, será 3436 palabras, que es el número total de palabras únicas en nuestro conjunto de datos.
Imprimamos ahora la palabra número 500 y su valor entero de word_2_index
diccionario.
print(macbeth_text_words[500])
print(word_2_index[macbeth_text_words[500]])
Aquí está el resultado:
comparisons
1456
A la palabra «comparaciones» se le asigna aquí un valor entero de 1456.
Modificar la forma de los datos
La generación de texto entra en la categoría de muchos problemas de secuencia, ya que la entrada es una secuencia de palabras y la salida es una sola palabra. Usaremos el Red de memoria corta a largo plazo (LSTM), un tipo de red neuronal recurrente para crear nuestro modelo de generación de texto. LSTM toma datos en formato tridimensional (número de muestras, número de pasos de tiempo, características por paso de tiempo). Dado que la salida será una sola palabra, la forma de la salida será bidimensional (número de muestras, número de palabras únicas en el corpus).
El siguiente script cambia la forma de las capas de entrada y salida correspondientes.
input_sequence = []
output_words = []
input_seq_length = 100
for i in range(0, n_words - input_seq_length , 1):
in_seq = macbeth_text_words[i:i + input_seq_length]
out_seq = macbeth_text_words[i + input_seq_length]
input_sequence.append([word_2_index[word] for word in in_seq])
output_words.append(word_2_index[out_seq])
En el script anterior, declaramos dos listas vacías input_sequence
y output_words
. El es input_seq_length
establecido en 100, lo que significa 100 palabras en nuestra secuencia de entrada. Luego, ejecutamos un ciclo donde los valores enteros de las primeras 100 palabras del texto se adjuntan a la primera versión. input_sequence
lista. La 101a palabra se adjunta al output_words
lista. Durante la segunda versión, se almacena en el texto una secuencia de palabras que comienza en la segunda palabra del texto y termina en la 101ª palabra del texto. input_sequence
lista, y la palabra 102 se almacena en el output_words
editar, y así sucesivamente. Se generará un total de 17150 secuencias de entrada ya que el conjunto de datos contiene un total de 17250 palabras (100 menos que las palabras completas).
Imprimamos ahora el valor de la primera secuencia en el input_sequence
lista:
print(input_sequence[0])
Salida:
[1, 869, 4, 40, 60, 1358, 1359, 408, 1360, 1361, 409, 265, 2, 870, 31, 190, 291, 76, 36, 30, 190, 327, 128, 8, 265, 870, 83, 8, 1362, 76, 1, 1363, 1364, 86, 76, 1, 1365, 354, 2, 871, 5, 34, 14, 168, 1, 292, 4, 649, 77, 1, 220, 41, 1, 872, 53, 3, 327, 12, 40, 52, 1366, 1367, 25, 1368, 873, 328, 355, 9, 410, 2, 410, 9, 355, 1369, 356, 1, 1370, 2, 874, 169, 103, 127, 411, 357, 149, 31, 51, 1371, 329, 107, 12, 358, 412, 875, 1372, 51, 20, 170, 92, 9]
Normalicemos nuestras secuencias de entrada dividiendo los números enteros de las secuencias por el valor entero más grande. La siguiente secuencia de comandos convierte la salida a formato bidimensional.
Te puede interesar:Implementación de aplicaciones Django en Heroku desde GitHubX = np.reshape(input_sequence, (len(input_sequence), input_seq_length, 1))
X = X / float(vocab_size)
y = to_categorical(output_words)
El siguiente script imprime la forma de las entradas y salidas correspondientes.
print("X shape:", X.shape)
print("y shape:", y.shape)
Salida:
X shape: (17150, 100, 1)
y shape: (17150, 3437)
Entrenando el modelo
El siguiente paso es entrenar nuestro modelo. No existe una regla estricta sobre la cantidad de capas y neuronas que deben usarse para entrenar el modelo. Seleccionamos aleatoriamente los tamaños de capa y neurona. Puede jugar con los hiperparámetros para ver si puede obtener mejores resultados.
Crearemos tres conjuntos de LSTM con 800 neuronas cada uno. Se agregará una capa densa final con 1 neurona para predecir el índice de la siguiente palabra, como se muestra a continuación:
model = Sequential()
model.add(LSTM(800, input_shape=(X.shape[1], X.shape[2]), return_sequences=True))
model.add(LSTM(800, return_sequences=True))
model.add(LSTM(800))
model.add(Dense(y.shape[1], activation='softmax'))
model.summary()
model.compile(loss="categorical_crossentropy", optimizer="adam")
Dado que la palabra de salida puede ser una de 3436 palabras únicas, nuestro problema es un problema de clasificación de clases múltiples, por lo que categorical_crossentropy
función de pérdida utilizada. En el caso de la clasificación binaria, el binary_crossentropy
función utilizada. Cuando ejecute el script anterior, debería ver un resumen del modelo:
Model: "sequential_1"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
lstm_1 (LSTM) (None, 100, 800) 2566400
_________________________________________________________________
lstm_2 (LSTM) (None, 100, 800) 5123200
_________________________________________________________________
lstm_3 (LSTM) (None, 800) 5123200
_________________________________________________________________
dense_1 (Dense) (None, 3437) 2753037
=================================================================
Total params: 15,565,837
Trainable params: 15,565,837
Non-trainable params: 0
Para entrenar el modelo, solo podemos fit()
método.
model.fit(X, y, batch_size=64, epochs=10, verbose=1)
Aquí de nuevo, puede jugar con diferentes valores para batch_size
y el epochs
. El modelo tardará algún tiempo en entrenarse.
Hacer una predicción
Para hacer una predicción, seleccionaremos aleatoriamente una secuencia de la input_sequence
lista, conviértala a una forma tridimensional y luego reenvíela a la predict()
método del modelo entrenado. El modelo devolverá una matriz codificada one-hot donde el índice que contiene 1 será el valor de índice de la siguiente palabra. El valor del índice se envía al index_2_word
diccionario, donde la palabra índice se utiliza como clave. El es index_2_word
dictionary devuelve la palabra asociada con la ejecución del índice como clave del diccionario.
El siguiente script selecciona aleatoriamente una secuencia de números enteros y luego imprime la secuencia de palabras correspondiente:
random_seq_index = np.random.randint(0, len(input_sequence)-1)
random_seq = input_sequence[random_seq_index]
index_2_word = dict(map(reversed, word_2_index.items()))
word_sequence = [index_2_word[value] for value in random_seq]
print(' '.join(word_sequence))
Para el guión de este artículo, se seleccionó al azar la siguiente secuencia. Es probable que la secuencia generada para usted sea diferente a esta:
amen when they did say god blesse vs lady consider it not so deepely mac but wherefore could not pronounce amen had most need of blessing and amen stuck in my throat lady these deeds must not be thought after these wayes so it will make vs mad macb me thought heard voyce cry sleep no more macbeth does murther sleepe the innocent sleepe sleepe that knits vp the rauel sleeue of care the death of each dayes life sore labors bath balme of hurt mindes great natures second course chiefe nourisher in life feast lady what doe you meane
En el guión de arriba, el index_2_word
solo se crea un diccionario word_2_index
diccionario. En este caso, invertir un diccionario se refiere al proceso de intercambio de claves con valores.
Luego imprimiremos las siguientes 100 palabras que siguen la secuencia de palabras anterior:
for i in range(100):
int_sample = np.reshape(random_seq, (1, len(random_seq), 1))
int_sample = int_sample / float(vocab_size)
predicted_word_index = model.predict(int_sample, verbose=0)
predicted_word_id = np.argmax(predicted_word_index)
seq_in = [index_2_word[index] for index in random_seq]
word_sequence.append(index_2_word[ predicted_word_id])
random_seq.append(predicted_word_id)
random_seq = random_seq[1:len(random_seq)]
El es word_sequence
Nuestra secuencia de entrada de palabras ahora está en la variable, junto con las siguientes 100 palabras predichas. El es word_sequence
la variable contiene una secuencia de palabras en forma de lista. Solo podemos combinar las palabras de la lista para obtener la secuencia de salida final, como se muestra a continuación:
final_output = ""
for word in word_sequence:
final_output = final_output + " " + word
print(final_output)
Aquí está el resultado final:
amen when they did say god blesse vs lady consider it not so deepely mac but wherefore could not pronounce amen had most need of blessing and amen stuck in my throat lady these deeds must not be thought after these wayes so it will make vs mad macb me thought heard voyce cry sleep no more macbeth does murther sleepe the innocent sleepe sleepe that knits vp the rauel sleeue of care the death of each dayes life sore labors bath balme of hurt mindes great natures second course chiefe nourisher in life feast lady what doe you meane and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and
El resultado no se ve muy bien todavía y nuestro modelo parece estar aprendiendo solo de la última palabra, es decir. and
. Sin embargo, tienes la idea de cómo crear un modelo de generación de texto con Keras. Para mejorar los resultados, tengo las siguientes sugerencias para ti:
- Cambie los hiperparámetros, incluido el tamaño y la cantidad de filas LSTM y la cantidad de claves para ver si obtiene mejores resultados.
- Prueba las palabras para como
is
,am
,are
desde el conjunto de entrenamiento hasta la generación de palabras en lugar de palabras vacías en el conjunto de prueba (aunque esto dependerá del tipo de aplicación). - Cree un modelo de generación de texto a nivel de personaje para predecir el próximo
N
personaje.
Para practicar más, le sugiero que intente desarrollar un modelo de generación de texto con los otros conjuntos de datos del corpus de Gutenberg.
Te puede interesar:Autoencoders para la reconstrucción de imágenes en Python y KerasConclusión
En este artículo, vimos cómo crear un modelo de generación de texto utilizando el aprendizaje profundo con la biblioteca Keras de Python. Si bien el modelo desarrollado en este artículo no es perfecto, el artículo ofrece la idea de cómo generar texto con aprendizaje profundo.