Python para NLP: desarrollo de un relleno de texto automático usando N-Grams

Este es el artículo número 15 de mi serie de artículos sobre Python para PNL. En mi artículo anterior, expliqué cómo implementar el enfoque TF-IDF desde cero en Python...

Este es el artículo número 15 de mi serie de artículos sobre Python para PNL. En mi Artículo anterior, expliqué cómo implementar [TF-FDI](https://en.wikipedia.org /wiki/Tf%E2%80%93idf) Enfoque de Scratch a Python. Antes de eso, estudiamos cómo implementar [el enfoque de bolsa de palabras desde cero en Python] (/python-for-nlp-creating-a-bag-of-words-model-from-scratch/).

Hoy, estudiaremos el enfoque de N-Grams y veremos cómo se puede usar el enfoque de N-Grams para crear un relleno de texto automático simple o un motor de sugerencias. El relleno de texto automático es una aplicación muy útil y es ampliamente utilizada por Google y diferentes teléfonos inteligentes donde un usuario ingresa un texto y la aplicación completa o sugiere automáticamente el texto restante.

Problemas con TF-IDF y el enfoque Bag of Words

Antes de implementar el modelo N-Grams, analicemos primero el inconveniente de la bolsa de palabras y los enfoques TF-IDF.

En el enfoque de bolsa de palabras y TF-IDF, las palabras se tratan individualmente y cada palabra se convierte en su equivalente numérico. La información de contexto de la palabra no se conserva. Considere dos oraciones "gran máquina y alfombra roja" y "gran alfombra roja y máquina". Si usa un enfoque de bolsa de palabras, obtendrá los mismos vectores para estas dos oraciones. Sin embargo, podemos ver claramente que en la primera oración estamos hablando de una "gran máquina roja", mientras que la segunda oración contiene información sobre la "gran alfombra roja". Por lo tanto, la información de contexto es muy importante. El modelo N-Grams básicamente nos ayuda a capturar la información de contexto.

Teoría del modelo de N-gramas

Wikipedia define un N-Gram como "Una secuencia contigua de N elementos de una muestra determinada de texto o voz". Aquí, un elemento puede ser un carácter, una palabra o una oración y N puede ser cualquier número entero. Cuando N es 2, llamamos bigrama a la secuencia. De manera similar, una secuencia de 3 elementos se llama trigrama, y ​​así sucesivamente.

Para comprender el modelo N-Grams, primero debemos comprender cómo funcionan las cadenas de Markov.

Conexión de N-Gramos con Cadenas de Markov {#conexión de ngramas con cadenas de Markov}

Una cadena de Markov es una secuencia de estados. Considere un sistema de Markov con 2 estados, X e Y. En una cadena de Markov, puede quedarse en un estado o moverse al otro estado. En nuestro ejemplo, nuestros estados tienen el siguiente comportamiento:

  1. La probabilidad de pasar de X a Y es del 50 % y, de manera similar, la probabilidad de permanecer en X es del 50 %.
  2. Asimismo, la probabilidad de quedarse en Y es del 50% mientras que la posibilidad de volver a X también es del 50%.

De esta manera se puede generar una secuencia de Markov, como XXYX, etc.

En un modelo N-Grams, un elemento de una secuencia se puede tratar como un estado de Markov. Veamos un ejemplo simple de bigramas de caracteres donde cada carácter es un estado de Markov.

1
Football is a very famous game

Los bigramas de caracteres para la oración anterior serán: fo, oo, ot, tb, ba, al, ll, l , i, is y pronto. Puede ver que los bigramas son básicamente una secuencia de dos caracteres que ocurren consecutivamente.

De manera similar, los trigramas son una secuencia de tres caracteres contiguos, como se muestra a continuación:

foo, oot, otb, tba y así sucesivamente.

En los dos ejemplos anteriores, vimos bigramas y trigramas de caracteres. También podemos tener bigramas y trigramas de palabras.

Volvamos a nuestro ejemplo anterior, "gran máquina roja y alfombra". El bigrama de esta oración será "gran rojo", "máquina roja", "máquina y", "y alfombra". De manera similar, los bigramas para la oración "gran alfombra roja y máquina" serán "gran alfombra roja", "alfombra roja", "alfombra y", "y máquina".

Aquí, en este caso con bigramas, obtenemos una representación vectorial diferente para ambas oraciones.

En la siguiente sección, implementaremos el modelo N-Grams desde cero en Python y veremos cómo podemos crear un relleno de texto automático usando N-Grams como estos.

N-Gramos desde cero en Python

Crearemos dos tipos de modelos de N-Grams en esta sección: un modelo de N-Grams de caracteres y un modelo de N-Grams de palabras.

Caracteres N-Grams Model

En esta sección, explicaré cómo crear un modelo N-Gram de caracteres simples. En la siguiente sección, veremos cómo implementar el modelo Word N-Gram.

Para crear nuestro corpus, rasparemos el artículo de Wikipedia sobre tenis. Primero importemos las bibliotecas que necesitamos para descargar y analizar el artículo de Wikipedia.

1
2
3
4
5
6
7
8
import nltk
import numpy as np
import random
import string

import bs4 as bs
import urllib.request
import re

Usaremos la biblioteca Hermosasopa4 para analizar los datos de Wikipedia. Además, la biblioteca de expresiones regulares de Python, re, se utilizará para algunas tareas de preprocesamiento del texto.

Como dijimos anteriormente, usaremos el artículo de Wikipedia sobre Tenis para crear nuestro corpus. El siguiente script recupera el artículo de Wikipedia y extrae todos los párrafos del texto del artículo. Finalmente, el texto se convierte a minúsculas para facilitar el procesamiento.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
raw_html = urllib.request.urlopen('https://en.wikipedia.org/wiki/Tennis')
raw_html = raw_html.read()

article_html = bs.BeautifulSoup(raw_html, 'lxml')
article_paragraphs = article_html.find_all('p')
article_text = ''

for para in article_paragraphs:
    article_text += para.text

article_text = article_text.lower()

A continuación, eliminamos todo de nuestro conjunto de datos excepto letras, puntos y espacios:

1
article_text = re.sub(r'[^A-Za-z. ]', '', article_text)

Hemos preprocesado nuestro conjunto de datos y ahora es el momento de crear un modelo de N-Grams. Crearemos un modelo de trigrama de personajes. Ejecute el siguiente script:

1
2
3
4
5
6
7
8
9
ngrams = {}
chars = 3

for i in range(len(article_text)-chars):
    seq = article_text[i:i+chars]
    print(seq)
    if seq not in ngrams.keys():
        ngrams[seq] = []
    ngrams[seq].append(article_text[i+chars])

En el script anterior, creamos un diccionario ngrams. Las claves de este diccionario serán los trigramas de caracteres de nuestro corpus y los valores serán los caracteres que se encuentren junto a los trigramas. A continuación, dado que estamos creando N-Gram de tres caracteres, declaramos una variable chars. Después de eso, iteramos a través de todos los caracteres de nuestro corpus, comenzando desde el cuarto carácter.

A continuación, dentro del bucle, extraemos el trigrama filtrando los siguientes tres caracteres. El trigrama se almacena en la variable seq. Luego verificamos si el trigrama existe en el diccionario. Si no existe en el diccionario ngrams, agregamos el trigrama al diccionario. Después de eso, asignamos una lista vacía como valor al trigrama. Finalmente, el carácter que existe después del trigrama se agrega como valor a la lista.

Si abre el diccionario ngrams en el explorador de variables de Spyder. Debería ver algo como esto:

{.img-responsive}

Puede ver los trigramas como claves y los caracteres correspondientes, que aparecen después de los trigramas en el texto, como valores. Es posible que vea claves con dos caracteres en el diccionario, pero en realidad no son dos caracteres. El tercer carácter es en realidad un espacio.

Intentemos ahora generar texto usando los primeros tres caracteres de nuestro corpus como entrada. Los tres primeros caracteres de nuestro corpus son "diez". Mira el siguiente guión:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
curr_sequence = article_text[0:chars]
output = curr_sequence
for i in range(200):
    if curr_sequence not in ngrams.keys():
        break
    possible_chars = ngrams[curr_sequence]
    next_char = possible_chars[random.randrange(len(possible_chars))]
    output += next_char
    curr_sequence = output[len(output)-chars:len(output)]

print(output)

En el script anterior, primero almacenamos el primer trigrama, es decir, diez en la variable curr_sequence. Generaremos un texto de doscientos caracteres, por lo tanto inicializamos un ciclo que itera 200 veces. Durante cada iteración, verificamos si curr_sequence o el trigrama está en el diccionario ngrams. Si el trigrama no se encuentra en el diccionario ngrams, simplemente salimos del bucle.

A continuación, el trigrama curr_sequence se pasa como clave al diccionario ngrams, que devuelve la lista de posibles caracteres siguientes. De la lista de posibles caracteres siguientes, se elige aleatoriamente un índice, que se pasa a la lista possible_chars para obtener el siguiente carácter del trigrama actual. Luego, el siguiente carácter se agrega a la variable salida que contiene la salida final.

Finalmente, curr_sequence se actualiza con el siguiente trigrama del corpus de texto. Si imprime la variable output que contiene doscientos caracteres generados automáticamente, debería ver algo como esto (es importante mencionar que dado que el siguiente carácter se elige al azar, su salida puede ser diferente):

Producción:

1
tent pointo somensiver tournamedal pare the greak in the next peak sweder most begal tennis sport. the be has siders with sidernaments as was that adming up is coach rackhanced ball of ment. a game and

La salida no tiene mucho sentido aquí en este caso. Si aumenta el valor de la variable chars a 4, debería ver resultados similares a los siguientes:

1
tennis ahead with the club players under.most coaching motion us . the especific at the hit and events first predomination but of ends on the u.s. cyclops have achieved the end or net inches call over age

Puede ver que los resultados son un poco mejores que los que obtuvimos usando 3 gramos. Nuestra sugerencia/relleno de texto seguirá mejorando a medida que aumentemos el número de N-Gram.

En la siguiente sección, implementaremos el modelo Words N-Grams. Verá que el texto generado tendrá mucho más sentido en el caso del modelo Words N-Grams.

Modelo de N-gramas de palabras

En el modelo Words N-Grams, cada palabra del texto se trata como un elemento individual. En esta sección, implementaremos el modelo Words N-Grams y lo usaremos para crear un relleno de texto automático.

El conjunto de datos que vamos a usar es el mismo que usamos en la última sección.

Primero creemos un diccionario que contenga trigramas de palabras como claves y la lista de palabras que aparecen después de los trigramas como valores.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
ngrams = {}
words = 3

words_tokens = nltk.word_tokenize(article_text)
for i in range(len(words_tokens)-words):
    seq = ' '.join(words_tokens[i:i+words])
    print(seq)
    if  seq not in ngrams.keys():
        ngrams[seq] = []
    ngrams[seq].append(words_tokens[i+words])

En el guión anterior, creamos un modelo de trigrama de Words. El proceso es similar al que se sigue para utilizar trigramas de caracteres. Sin embargo, en el script anterior, primero tokenizamos nuestro corpus en palabras.

A continuación, iteramos a través de todas las palabras y luego unimos las tres palabras actuales para formar un trigrama. Después de eso, verificamos si la palabra trigrama existe en el diccionario ngrams. Si el trigrama aún no existe, simplemente lo insertamos en el diccionario ngrams como una clave.

Finalmente, anexamos la lista de palabras que siguen al trigrama en todo el corpus, como valor en el diccionario.

Ahora, si miras el diccionario ngrams, en el explorador de variables, se verá así:

{.img-responsive}

Puede ver los trigramas como claves de diccionario y las palabras correspondientes como valores de diccionario.

Ahora vamos a crear un relleno de texto automático, usando los trigramas de palabras que acabamos de crear.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
curr_sequence = ' '.join(words_tokens[0:words])
output = curr_sequence
for i in range(50):
    if curr_sequence not in ngrams.keys():
        break
    possible_words = ngrams[curr_sequence]
    next_word = possible_words[random.randrange(len(possible_words))]
    output += ' ' + next_word
    seq_words = nltk.word_tokenize(output)
    curr_sequence = ' '.join(seq_words[len(seq_words)-words:len(seq_words)])

print(output)

En el script anterior, inicializamos la variable curr_sequence con el primer trigrama del corpus. El primer trigrama es "el tenis es un". Generaremos 50 palabras usando el primer trigrama como entrada. Para ello, ejecutamos un bucle for que se ejecuta 50 veces. Durante cada iteración, primero se comprueba si la palabra trigrama existe en el diccionario ngrams. Si no, el bucle se rompe. De lo contrario, la lista de palabras que probablemente seguirán al trigrama se recupera del diccionario ngrams pasando trigrama como valor. De la lista de posibles palabras, se elige una palabra al azar y se agrega al final de la salida. Finalmente, la variable curr_sequence se actualiza con el valor del siguiente trigrama en el diccionario.

El texto generado se ve así. Puede ver que en el caso de los trigramas de palabras, el texto generado automáticamente tiene mucho más sentido.

Producción:

1
tennis is a racket sport that can be played individually against a single opponent singles or between two teams of two players each doubles. each player uses a tennis racket include a handle known as the grip connected to a neck which joins a roughly elliptical frame that holds a matrix of

Si establece el valor de la variable de palabras en 4 (use 4 gramos) para generar texto, su resultado se verá aún más sólido, como se muestra a continuación:

1
tennis is a racket sport that can be played individually against a single opponent singles or between two teams of two players each doubles . each player uses a tennis racket that is strung with cord to strike a hollow rubber ball covered with felt over or around a net and into the opponents

Puede ver que la salida tiene aún más sentido con 4 gramos. Esto se debe en gran parte a que nuestro generador está regenerando principalmente el mismo texto del artículo de Wikipedia, pero con algunas mejoras leves al generador y un corpus más grande, nuestro generador también podría generar fácilmente oraciones nuevas y únicas.

Yendo más lejos: proyecto de extremo a extremo portátil

¿Tu naturaleza inquisitiva te hace querer ir más allá? Recomendamos consultar nuestro Proyecto guiado: ["Subtítulos de imágenes con CNN y Transformers con Keras"](https://wikihtp.com/courses/image-captioning-with-cnns-and -transformadores-con-keras/#cta){target="_blank"}.

En este proyecto guiado, aprenderá a crear un modelo de subtítulos de imágenes, que acepta una imagen como entrada y produce un subtítulo de texto como salida.

Aprenderás a:

  • Preprocesar texto
  • Vectorizar la entrada de texto fácilmente
  • Trabaje con la API tf.data y cree conjuntos de datos de alto rendimiento
  • Cree Transformers desde cero con TensorFlow/Keras y KerasNLP: la adición horizontal oficial a Keras para crear modelos NLP de última generación
  • Cree arquitecturas híbridas donde la salida de una red se codifica para otra

¿Cómo enmarcamos los subtítulos de las imágenes? La mayoría lo considera un ejemplo de aprendizaje profundo generativo, porque estamos enseñando a una red a generar descripciones. Sin embargo, me gusta verlo como una instancia de traducción automática neuronal: estamos traduciendo las características visuales de una imagen en palabras. A través de la traducción, estamos generando una nueva representación de esa imagen, en lugar de simplemente generar un nuevo significado. Verlo como traducción, y solo por generación de extensión, enfoca la tarea bajo una luz diferente y la hace un poco más intuitiva.

Enmarcar el problema como uno de traducción hace que sea más fácil averiguar qué arquitectura querremos usar. Los transformadores solo de codificador son excelentes para comprender el texto (análisis de opinión, clasificación, etc.) porque los codificadores codifican representaciones significativas. Los modelos de solo decodificador son excelentes para la generación (como GPT-3), ya que los decodificadores pueden inferir representaciones significativas en otra secuencia con el mismo significado. La traducción generalmente se realiza mediante una arquitectura de codificador-decodificador, donde los codificadores codifican una representación significativa de una oración (o imagen, en nuestro caso) y los decodificadores aprenden a convertir esta secuencia en otra representación significativa que es más interpretable para nosotros (como una oración).

Conclusión

El modelo N-Grams es uno de los modelos de oración a vector más utilizados, ya que captura el contexto entre N-palabras en una oración. En este artículo, vio la teoría detrás del modelo N-Grams. También vio cómo implementar el modelo N-Grams de caracteres y N-Grams de palabras. Finalmente, estudió cómo crear un relleno de texto automático usando ambos enfoques.