Introdução a Bag of Words e TF-IDF

As principais formas de preparar seu texto para o aprendizado de máquina

Camilla Fonseca
Turing Talks
10 min readSep 20, 2020

--

Texto escrito por Alex Koji, Camilla Fonseca e Vitoria Rodrigues.

Olá, querido leitor. Bem vindo a mais um Turing Talks! Desta vez, vamos abordar dois conceitos muito importantes para a área de Processamento de Linguagem Natural: Bag of Words e TF-IDF!

O código desse artigo está armazenado no nosso GitHub, basta clicar aqui para visualizar. Antes de começarmos, é necessário já ter dado uma lida no nosso Turing Talks de Introdução ao Processamento de Linguagem Natural. Então se você ainda não viu, corre lá!

Já leu? Então vamos começar:

Feature extraction

Para usarmos um modelo estatístico ou de deep learning em NLP, precisamos de features: informações mensuráveis acerca de algum fenômeno, ou seja, uma forma estruturada de armazenar informações. Porém, textos são um tipo de dado não estruturado (não organizado de uma maneira pré-definida, fixa), assim, é difícil para o computador entendê-los e analisá-los. Por isso, realizamos a chamada feature extraction, ou seja, transformamos o texto em uma informação numérica de modo que seja possível utilizá-lo para alimentar um modelo. Uma das maneiras mais populares e simples de fazer isso é com Bag of Words (BoW).

Bag of Words

BoW é uma forma de representar o texto de acordo com a ocorrência das palavras nele. Traduzindo para o português, o “saco de palavras” recebe esse nome porque não leva em conta a ordem ou a estrutura das palavras no texto, apenas se ela aparece ou a frequência com que aparece nele.

Por exemplo, se a palavra TURING aparece muito num texto, ela se torna mais central e importante para a máquina. Portanto, BoW pode ser um ótimo método para determinar as palavras significativas de um texto com base no número de vezes que ela é usada. Bem simples, né?

Basicamente, para gerar um modelo de bag of words precisamos realizar três passos:

1) Selecionar os dados

2) Gerar o vocabulário

3) Formar vetores a partir do documento

Vamos ver cada um desses passos separadamente:

Selecionando seus dados

Essa parte diz respeito a selecionar os dados que serão usados — no nosso caso, o texto — e prepará-lo de forma que a máquina consiga processá-lo bem. Então, primeiro, vamos falar sobre os dados com qual trabalharemos nesse artigo. Nosso texto será o poema “No Meio do Caminho” do poeta Carlos Drummond de Andrade:

“No meio do caminho tinha uma pedra
tinha uma pedra no meio do caminho
tinha uma pedra
no meio do caminho tinha uma pedra.

Nunca me esquecerei desse acontecimento
na vida de minhas retinas tão fatigadas.
Nunca me esquecerei que no meio do caminho
tinha uma pedra
tinha uma pedra no meio do caminho
no meio do caminho tinha uma pedra.”

Drummond de Andrade segurando uma bolsa… de palavras?

Vamos colocar nosso texto em uma variável chamada ‘texto’, dessa forma fica mais fácil trabalharmos com ele:

Com nossos dados escolhidos precisamos prepará-los. Chamamos isso de pré-processamento, e se você deu uma lida no nosso artigo de Introdução a NLP já sabe que pré-processar um texto é essencial quando queremos trabalhar com ele, principalmente quando queremos aplicar um modelo de predição ou outros métodos estatísticos. São muitos os métodos de pré-processamento, mas aqui vamos realizar apenas alguns:

  • Colocar todas as letras em minúsculo
  • Selecionar apenas letras com regex
  • Juntar os tokens em um texto — já que, ao usar o método .findall do regex, nosso texto é dividido em tokens, ou seja, cada palavra vira uma string individual em uma lista

Colocando todas as letras em minúsculos

Colocar todas as letras em minúsculo é importante porque nossa máquina tende a interpretar palavras iguais mas com letras minúsculas e maiúsculas como sendo diferentes, por exemplo, para o computador ‘Turing’ e ‘turing’ não são a mesma palavra. Portanto, vamos utilizar a função .lower() do próprio python em cima da variável ‘texto’ e guardar esse novo texto na variável ‘texto_min’.

Selecionando apenas letras com REGEX

Esse passo é fundamental porque, para a máquina, a pontuação de um texto não importa e muitas vezes ela entende a pontuação como também sendo uma palavra. E para nossa análise com BoW, tão pouco importam os números. Nesse caso, vamos utilizar a biblioteca re e o método .findall, também vamos guardar esse texto com apenas letras na variável ‘apenas_letras’.

Juntando os tokens

Já que o .findall nos retorna todas as palavras como strings dentro de uma lista (tokens), vamos juntá-las com a função .join(), dessa forma nosso texto volta a ser um texto novamente. Por último, vamos guardá-lo na variável ‘novo_texto’.

No fim, sua função deve ficar mais ou menos desta forma:

Gerando o vocabulário

Já pensou se você conseguisse memorizar todas as palavras que você visse? Diferente de nós que precisamos ver, rever uma, duas e provavelmente mais vezes para memorizar, o computador já consegue fazer isso de primeira. Então para gerar o vocabulário que nada mais é que a coleção de todas as palavras que ocorrem em um texto, basta passarmos todas elas uma vez só.

Então a ideia para o código é basicamente:

  • Separar nosso texto em tokens;
  • Criar uma lista para guardarmos o vocabulário;
  • Fazer um loop para percorrer o texto inteiro;
  • Criar uma condicional para verificar se a palavra está na lista —fazemos isso porque nosso vocabulário conta apenas as ocorrências únicas, sem repetições de palavras;
  • Caso não esteja, ela é adicionada.

Vamos ver como fica isso na prática? Olha só!

Assim temos nossa lista chamada Vocab, mostrando todas as palavras do texto e quantas são

Formando vetores

Como o vocabulário tem um número fixo de palavras, podemos usar uma representação de tamanho fixo equivalente a esse número: um vetor! Cada elemento desse vetor corresponderá a uma palavra do vocabulário. Há diversas formas de preencher nosso vetor com números para representar um documento (uma ‘amostra’ de um conjunto maior de textos), por exemplo, usar a contagem de vezes em que a palavra aparece nele. Mas a maneira mais básica de fazer isso é atribuindo um valor booleano/binário: 1 se a palavra aparece, 0 se não.

Podemos pensar nesse processo como uma tabulação do documento, veja o exemplo com os versos “Nunca me esquecerei que no meio do caminho/tinha uma pedra/tinha uma pedra no meio do caminho/no meio do caminho tinha uma pedra.”:

Assim, para codificar com codificação binária, o passo a passo é:

  • Criar uma lista que representa o vetor;
  • Fazer um loop para percorrer todas as palavras do vocabulário;
  • Se a palavra estiver no documento, adicionar 1 à lista; caso contrário, adicionar 0;
  • Transformar a lista final em um array do numpy e retornar.

Agora que você sabe como gerar um Bag of Words, vamos ao próximo assunto desse Turing Talks:

Term Frequency Inverse Document Frequency (TF-IDF)

TF-IDF vem para superar os problemas do Bag of Words. Trata-se de medidas estatísticas para medir o quão importante uma palavra é em um documento (texto), assim como BoW, mas com algumas diferenças.

Com ele, podemos perceber a importância de uma palavra por meio de uma pontuação, o TF-IDF de uma palavra em um texto é feito multiplicando duas métricas diferentes:

  • Term Frequency (a frequência do termo), que mede a frequência com que um termo ocorre num documento;
  • Inverse Document Frequency (inverso da frequência nos documentos), que mede o quão importante um termo é no contexto de todos os documentos.
A fórmula traduzida para o português fica assim!

Em outras palavras, para o TF-IDF, quanto mais frequente uma palavra é em seu documento, mais importante ela tende a ser. Entretanto, isso depende da repetição dela ao longo de todos os documentos que estão sendo analisados.

Por exemplo, suponhamos que estejamos analisando três documentos: uma revista de futebol, uma de vôlei e uma de basquete. Temos palavras que se repetem ao longo de todos esses documentos, por exemplo, a palavra “esporte” deve aparecer em todas as três revistas, certo? Então, provavelmente, não contribui muito para uma análise. Porém, palavras que se repetem muito em documentos individuais dizem mais a respeito dele, então a palavra “cesta”, por exemplo, que pode se repetir muito na revista sobre basquete, mas não nas outras, tende a se tornar mais importante para o TF-IDF. Por isso dizemos que a repetição das palavras importa com relação aos documentos que estão sendo analisados. Interessante, né?

No nosso poema, consideraremos cada estrofe como um documento (mas poderíamos fazer de outras formas também, considerando cada verso como um documento, por exemplo).

Implementando

Para a implementação de TF-IDF precisamos seguir passos muito semelhantes ao que fizemos com BoW anteriormente:

  1. Primeiro, é preciso selecionar seus dados e pré-processar seu texto;
  2. Depois, gerar um vocabulário, ou seja, uma lista com todos os termos do nosso texto;

Essas duas etapas já foram feitas lá em cima, então não vamos repeti-la — Lembre-se apenas que nosso vocabulário está armazenado na lista Vocab — Portanto, vamos direto para o último passo:

3. Gerar um dicionário de frequência desses termos

Então vamos para a implementação!

Para criar o dicionário vamos criar uma função dicionario_de_contagem, que retorna a palavra e a quantidade de ocorrências dela. Basicamente, essa função recebe como argumento a lista Vocab, que já criamos, e o documento tokenizado, e cria um dicionário com as palavras do poema seguidas do número de ocorrências delas. Primeiro vamos tokenizar nossos documentos (estrofes):

Agora nossa função dicionario_de_contagem:

Por fim, vamos passar nosso vocabulário e nossos documentos para serem analisados:

TF

Como sua definição diz, esse termo é dado pela divisão da quantidade de vezes que uma palavra aparece em um documento pela quantidade de palavras desse documento, ou seja, representa a frequência que esse termo tem em um documento. Assim, vemos que o TF (frequência do termo) é basicamente a implementação de um BoW para cada documento. Então pensando no exemplo das revistas, se pegarmos a de futebol, algumas palavras que possam ter um alto índice de TF são as que têm alta relação com o assunto (futebol) como: bola, jogador, gol, entre outros.

Agora, como fazer a implementação do código? A ideia é fazer como na função dicionario_de_contagem, porém passando como texto os documentos e ao invés de devolver a frequência, devolver esse valor normalizado, ou seja, dividido pela quantidade de palavras do documento.

Aqui criamos a função que calcula o TF
E aqui aplicamos aos nossos documentos!

Os outputs de cada estrofe devem ficar dessa forma:

Primeira estrofe
Segunda estrofe

IDF

Voltando ao nosso exemplo das revistas de futebol, vôlei e basquete, apenas a TF (frequência do termo) ainda vai dar maior ênfase à palavra “esporte”, que se repetirá mais vezes nas três revistas, do que a “cesta”. É aí então que entra o IDF (inverso da frequência nos documentos): uma medida, originalmente proposta pela cientista Karen Spärck Jones, para dar maior peso a palavras que ocorrem em menos documentos (no exemplo, estamos considerando cada revista como um documento). Dessa forma, esse peso será inversamente proporcional ao número de documentos em que uma palavra aparece e por isso o nome “inverso da frequência nos documentos”.

Mas como calcular essa medida? Há várias formas, algumas mais convenientes dependendo da aplicação do que outras, o necessário é apenas que seja uma função inversamente proporcional à frequência nos documentos, ou seja, decrescente em relação a ela. A função mais comum é a seguinte, que já apresentamos:

Sendo:

  • N o número de documentos no corpus;
  • t um termo do corpus;
  • df(t) o número de documentos em que o termo aparece.

Note que quanto maior df(t), menor será N/df(t), por isso temos uma função decrescente. Depois, passamos essa medida para a escala logarítimica, o que reduz a sua grandeza e portanto é útil principalmente quando temos um corpus grande. A função tem a seguinte curva:

Implementando o cálculo da IDF:

Aqui criamos a função que calcula o IDF
E aqui aplicamos aos nossos documentos

Nossos valores de IDF devem ficar assim:

Interessante, né?

Juntando TF e IDF

Para obter a pontuação TF-IDF, basta então multiplicar essas duas medidas. Assim, quanto maior a TF e a IDF, obteremos uma pontuação maior, ou seja, as palavras consideradas mais relevantes serão aquelas que aparecem muito em um documento, mas pouco em outros.

Vamos criar um DataFrame para visualizar as pontuações resultantes do poema:

Note que tanto os termos que não aparecem na estrofe como os que aparecem em todas as estrofes ficam com pontuação 0, porque se o termo aparece em todas as estrofes, df(t)=N, portando seu valor de idf será log(N/N)=log1=0. Por isso, na primeira estrofe, todas as pontuações deram 0.

Conclusão

Nesse Turing Talks abordamos uma implementação feita a mão de dois conceitos muito importantes para preparar seu texto: Bag of Words e TF-IDF. Ambos são muito importantes na área de Processamento de Linguagem Natural e podem ser melhores entendidos na prática, já que cada documento gera outputs diferentes. Além disso, a melhor abordagem depende do seu problema. Por exemplo, no caso do poema “No meio do caminho” pode ser que o TF-IDF não seja tão interessante, já que a repetição dos termos “pedra” e “caminho” ao longo da maioria dos versos é justamente o que os torna relevantes. Portanto, não tenha medo de treinar com seus próprios dados, isso ajudará muito na compreensão de cada um desses termos!

Na continuação desse Turing Talks, mostramos como aplicar essas técnicas em um dataset de reviews de produtos para fazer um modelo de análise de sentimentos:

Para mais textos como esse, não deixe de acompanhar o Grupo Turing no Facebook, Linkedin, Instagram e nossos posts do Medium =)

Agradecimentos especiais à Julia Pociotti.

--

--

Camilla Fonseca
Turing Talks

Statistics student at USP, pure and applied maths enthusiast and undergrad researcher interested in machine learning.