Artigo original: An introduction to Bag of Words and how to code it in Python for NLP

Escrito por: Praveen Dubey

Bag of Words (BOW – ou, em português, sacola de palavras) é um método para extrair recursos de documentos de texto. Esses recursos podem ser usados para treinar algoritmos de aprendizado de máquina. Ele cria um vocabulário de todas as palavras únicas que ocorrem em todos os documentos do conjunto de treinamento.

Em termos simples, é uma coleção de palavras para representar uma frase, com contagem de palavras e, na maioria das vezes, desconsiderando a ordem em que aparecem.

O BOW é uma abordagem amplamente utilizada com:

  1. Processamento de Linguagem Natural
  2. Recuperação de informações de documentos
  3. Classificação de documentos

De modo geral, envolve as seguintes etapas:

qRGh8boBcLLQfBvDnWTXKxZIEAk5LNfNABHF
Limpeza do texto -> Tokenizar -> Criar o vocabulário -> Gerar vetores

Os vetores gerados podem ser inseridos em seu algoritmo de aprendizado de máquina.

Vamos começar com um exemplo para entender, pegando algumas frases e gerando vetores para elas.

Considere as duas frases abaixo.

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

Essas duas passagens também podem ser representadas com uma coleção de palavras.

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

Além disso, para cada passagem, remova ocorrências múltiplas da palavra e use a contagem de palavras para representá-la.

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}

Supondo que essas passagens façam parte de um documento, abaixo consta a frequência combinada de palavras para nosso documento inteiro. Ambas as frases são consideradas.

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

O vocabulário acima de todas as palavras de um documento, com suas respectivas contagens de palavras, será usado para criar vetores de cada uma das passagens.

O comprimento do vetor será sempre igual ao tamanho do vocabulário. Neste caso, o comprimento do vetor é 11.

Para representar nossas frases originais em um vetor, cada vetor é inicializado com zeros — [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

Isso é seguido por iteração e comparação com cada palavra em nosso vocabulário, incrementando o valor do vetor se a passagem tiver essa palavra.

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 exemplo, na passagem 1, a palavra likes aparece na segunda posição e aparece duas vezes. Assim, o segundo elemento de nosso vetor para a passagem 1 será 2: [1, 2, 1, 1, 2, 1, 1, 0, 0, 0]

O vetor é sempre proporcional ao tamanho do nosso vocabulário.

Um documento grande onde o vocabulário gerado é enorme pode resultar em um vetor com muitos valores 0. Isso é chamado de vetor esparso. Os vetores esparsos exigem mais memória e recursos computacionais durante a modelagem. O grande número de posições ou dimensões pode tornar o processo de modelagem muito desafiador para algoritmos tradicionais.

Programando nosso algoritmo BOW

A entrada para o nosso código será composta de diversas frases. A saída será os vetores.

O vetor de entrada é este:

["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"]

Etapa 1: Tokenizar uma frase

Vamos começar removendo stopwords das frases.

Stopwords são palavras que não contêm significância suficiente para serem usadas sem nosso algoritmo. Não gostaríamos que essas palavras ocupassem espaço em nosso banco de dados ou tomassem um tempo valioso de processamento. Para isso, podemos removê-las facilmente armazenando uma lista de palavras que você considere que sejam stopwords.

Tokenização é o ato de quebrar uma sequência de strings em pedaços como palavras, palavras-chave, frases, símbolos e outros elementos chamados tokens. Tokens podem ser palavras individuais, frases ou até mesmo passagens inteiras. No processo de tokenização, alguns caracteres como sinais de pontuação são descartados.

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 uma implementação mais robusta de stopwords, você pode usar a biblioteca python nltk. Ela possui um conjunto de palavras predefinidas por idioma. Aqui está um exemplo:

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

Etapa 2: Aplicar tokenização a todas as frases

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

O método percorre as frases e adiciona a palavra extraída em um vetor.

A saída do método será:

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

Etapa 3: Construir vocabulário e gerar vetores

Use os métodos definidos nas etapas 1 e 2 para criar o vocabulário do documento e extrair as palavras das frases.

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

Aqui está a entrada definida e a execução do nosso 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)

Os vetores de saída para cada frase são:

Resultado:
Joe waited for the 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.]

Como você pode ver, cada frase foi comparada com nossa lista de palavras gerada na Etapa 1. Com base na comparação, o valor do elemento do vetor pode ser incrementado. Esses vetores podem ser usados em algoritmos de ML para classificação de documentos e previsões.

Escrevemos nosso código e geramos vetores, mas agora vamos entender um pouco mais sobre o bag of words.

Insights sobre o bag of words

O modelo BOW considera apenas se uma palavra conhecida ocorre em um documento ou não. Não se importa com o significado, contexto e ordem em que aparecem.

Isso dá a ideia de que documentos semelhantes terão contagens de palavras semelhantes entre si. Em outras palavras, quanto mais semelhantes as palavras em dois documentos, mais semelhantes os documentos podem ser.

Limitações do BOW

  1. Significado semântico: a abordagem básica do BOW não considera o significado da palavra no documento. Ele ignora completamente o contexto em que ela é usada. A mesma palavra pode ser usada em vários lugares com base no contexto ou em palavras próximas.
  2. Tamanho do vetor: Para um documento grande, o tamanho do vetor pode ser enorme, resultando em muitos cálculos e muito tempo. Pode ser necessário ignorar palavras com base na relevância para o seu caso de uso.

Essa foi uma pequena introdução ao método BOW. O código mostrou como ele funciona de um modo simples. Há muito mais para entender sobre o BOW. Por exemplo, em vez de dividir nossa frase em uma única palavra (1-grama), você pode dividi-la em um par de duas palavras (bigrama ou 2-gramas). Às vezes, a representação em bigramas parece ser muito melhor do que usar 1-grama. Eles podem ser frequentemente representados usando a notação N-grama. Listei alguns artigos de pesquisa na seção de recursos para um conhecimento mais aprofundado.

Você não precisa codificar o BOW sempre que precisar. Já faz parte de muitos frameworks disponíveis, como o CountVectorizer no sci-kit learn.

Nosso código anterior pode ser substituído por:

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

É sempre bom entender como funcionam as bibliotecas dos frameworks, bem como os métodos por trás delas. Quanto melhor você entender os conceitos, melhor poderá usar os frameworks.

Agradecemos pela leitura do artigo. O código mostrado está disponível no GitHub do autor.

Você pode seguir o autor no Medium, Twitter e no LinkedIn. Para qualquer dúvida, você pode entrar em contato com o autor por e-mail.

Recursos para ler mais sobre bag of words (em inglês)

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