Una introducción a Bag of Words y cómo codificarlo en Python para PNL

Bag of Words (BOW) es un método para extraer características de documentos de texto. Estas funciones se pueden utilizar para entrenar algoritmos de aprendizaje automático. Crea un vocabulario de todas las palabras únicas que aparecen en todos los documentos del conjunto de formación.

En términos simples, es una colección de palabras para representar una oración con el recuento de palabras y, en su mayoría, sin tener en cuenta el orden en que aparecen.

BOW es un enfoque ampliamente utilizado con:

  1. Procesamiento natural del lenguaje
  2. Recuperación de información de documentos
  3. Clasificaciones de documentos

En un nivel alto, implica los siguientes pasos.

Los vectores generados se pueden ingresar en su algoritmo de aprendizaje automático.

Comencemos con un ejemplo para entender tomando algunas oraciones y generando vectores para ellas.

Considere las dos oraciones siguientes.

1. "John likes to watch movies. Mary likes movies too."
2. "John also likes to watch football games."

Estas dos oraciones también se pueden representar con una colección de palabras.

1. ['John', 'likes', 'to', 'watch', 'movies.', 'Mary', 'likes', 'movies', 'too.']
2. ['John', 'also', 'likes', 'to', 'watch', 'football', 'games']

Además, para cada oración, elimine las apariciones múltiples de la palabra y use el recuento de palabras para representar esto.

1. {"John":1,"likes":2,"to":1,"watch":1,"movies":2,"Mary":1,"too":1}
2. {"John":1,"also":1,"likes":1,"to":1,"watch":1,"football":1, "games":1}

Suponiendo que estas oraciones son parte de un documento, a continuación se muestra la frecuencia de palabras combinadas para todo nuestro documento. Se tienen en cuenta ambas frases.

 {"John":2,"likes":3,"to":2,"watch":2,"movies":2,"Mary":1,"too":1, "also":1,"football":1,"games":1}

El vocabulario anterior de todas las palabras de un documento, con su respectivo recuento de palabras, se utilizará para crear los vectores de cada una de las oraciones.

La longitud del vector siempre será igual al tamaño del vocabulario. En este caso, la longitud del vector es 11.

Para representar nuestras oraciones originales en un vector, cada vector se inicializa con todos ceros - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

A esto le sigue la iteración y comparación con cada palabra de nuestro vocabulario, y se incrementa el valor del vector si la oración tiene esa palabra.

John likes to watch movies. Mary likes movies too.[1, 2, 1, 1, 2, 1, 1, 0, 0, 0]
John also likes to watch football games.[1, 1, 1, 1, 0, 0, 0, 1, 1, 1]

Por ejemplo, en la oración 1 la palabra likesaparece en segunda posición y aparece dos veces. Entonces, el segundo elemento de nuestro vector para la oración 1 será 2: [1, 2, 1, 1, 2, 1, 1, 0, 0, 0]

El vector siempre es proporcional al tamaño de nuestro vocabulario.

Un documento grande donde el vocabulario generado es enorme puede resultar en un vector con muchos valores 0. Esto se llama vector disperso .Los vectores dispersos requieren más memoria y recursos computacionales al modelar. La gran cantidad de posiciones o dimensiones puede hacer que el proceso de modelado sea muy desafiante para los algoritmos tradicionales.

Codificando nuestro algoritmo BOW

La entrada a nuestro código serán múltiples oraciones y la salida serán los vectores.

La matriz de entrada es esta:

["Joe waited for the train", "The train was late", "Mary and Samantha took the bus",
"I looked for Mary and Samantha at the bus station",
"Mary and Samantha arrived at the bus station early but waited until noon for the bus"]

Paso 1: Tokeniza una oración

Comenzaremos eliminando las palabras vacías de las oraciones.

Las palabras vacías son palabras que no contienen suficiente significado para ser utilizadas sin nuestro algoritmo. No queremos que estas palabras ocupen espacio en nuestra base de datos, o que ocupen un valioso tiempo de procesamiento. Para ello, podemos eliminarlos fácilmente almacenando una lista de palabras que usted considera palabras vacías.

La tokenización es el acto de dividir una secuencia de cadenas en pedazos como palabras, palabras clave, frases, símbolos y otros elementos llamados tokens . Los tokens pueden ser palabras individuales, frases o incluso oraciones completas. En el proceso de tokenización, se descartan algunos caracteres como los signos de puntuación.

def word_extraction(sentence): ignore = ['a', "the", "is"] words = re.sub("[^\w]", " ", sentence).split() cleaned_text = [w.lower() for w in words if w not in ignore] return cleaned_text

Para una implementación más sólida de palabras vacías, puede usar la biblioteca nltk de python . Tiene un conjunto de palabras predefinidas por idioma. Aquí hay un ejemplo:

import nltkfrom nltk.corpus import stopwords set(stopwords.words('english'))

Paso 2: aplicar tokenización a todas las oraciones

def tokenize(sentences): words = [] for sentence in sentences: w = word_extraction(sentence) words.extend(w) words = sorted(list(set(words))) return words

El método itera todas las oraciones y agrega la palabra extraída a una matriz.

El resultado de este método será:

['and', 'arrived', 'at', 'bus', 'but', 'early', 'for', 'i', 'joe', 'late', 'looked', 'mary', 'noon', 'samantha', 'station', 'the', 'took', 'train', 'until', 'waited', 'was']

Paso 3: construye vocabulario y genera vectores

Utilice los métodos definidos en los pasos 1 y 2 para crear el vocabulario del documento y extraer las palabras de las oraciones.

def generate_bow(allsentences): vocab = tokenize(allsentences) print("Word List for Document \n{0} \n".format(vocab));
for sentence in allsentences: words = word_extraction(sentence) bag_vector = numpy.zeros(len(vocab)) for w in words: for i,word in enumerate(vocab): if word == w: bag_vector[i] += 1 print("{0}\n{1}\n".format(sentence,numpy.array(bag_vector)))

Aquí está la entrada definida y la ejecución de nuestro código:

allsentences = ["Joe waited for the train train", "The train was late", "Mary and Samantha took the bus",
"I looked for Mary and Samantha at the bus station",
"Mary and Samantha arrived at the bus station early but waited until noon for the bus"]
generate_bow(allsentences)

Los vectores de salida para cada una de las oraciones son:

Output:
Joe waited for the train train[0. 0. 0. 0. 0. 0. 1. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 2. 0. 1. 0.]
The train was late[0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 1. 0. 1. 0. 0. 1.]
Mary and Samantha took the bus[1. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 1. 0. 1. 0. 0. 1. 0. 0. 0. 0.]
I looked for Mary and Samantha at the bus station[1. 0. 1. 1. 0. 0. 1. 1. 0. 0. 1. 1. 0. 1. 1. 0. 0. 0. 0. 0. 0.]
Mary and Samantha arrived at the bus station early but waited until noon for the bus[1. 1. 1. 2. 1. 1. 1. 0. 0. 0. 0. 1. 1. 1. 1. 0. 0. 0. 1. 1. 0.]

As you can see, each sentence was compared with our word list generated in Step 1. Based on the comparison, the vector element value may be incremented. These vectors can be used in ML algorithms for document classification and predictions.

We wrote our code and generated vectors, but now let’s understand bag of words a bit more.

Insights into bag of words

The BOW model only considers if a known word occurs in a document or not. It does not care about meaning, context, and order in which they appear.

This gives the insight that similar documents will have word counts similar to each other. In other words, the more similar the words in two documents, the more similar the documents can be.

Limitations of BOW

  1. Semantic meaning: the basic BOW approach does not consider the meaning of the word in the document. It completely ignores the context in which it’s used. The same word can be used in multiple places based on the context or nearby words.
  2. Vector size: For a large document, the vector size can be huge resulting in a lot of computation and time. You may need to ignore words based on relevance to your use case.

This was a small introduction to the BOW method. The code showed how it works at a low level. There is much more to understand about BOW. For example, instead of splitting our sentence in a single word (1-gram), you can split in the pair of two words (bi-gram or 2-gram). At times, bi-gram representation seems to be much better than using 1-gram. These can often be represented using N-gram notation. I have listed some research papers in the resources section for more in-depth knowledge.

You do not have to code BOW whenever you need it. It is already part of many available frameworks like CountVectorizer in sci-kit learn.

Our previous code can be replaced with:

from sklearn.feature_extraction.text import CountVectorizervectorizer = CountVectorizer()X = vectorizer.fit_transform(allsentences)print(X.toarray())

It’s always good to understand how the libraries in frameworks work, and understand the methods behind them. The better you understand the concepts, the better use you can make of frameworks.

Thanks for reading the article. The code shown is available on my GitHub.

You can follow me on Medium, Twitter, and LinkedIn, For any questions, you can reach out to me on email (praveend806 [at] gmail [dot] com).

Resources to read more on bag of words

  1. Wikipedia-BOW
  2. Understanding Bag-of-Words Model: A Statistical Framework
  3. Semantics-Preserving Bag-of-Words Models and Applications