Vetorizando palavras - NLP - count vectorizing
Count vectorization
Nesse processo, alguns passos são executados:
- Transformar um parágrafo em várias frases.
- Limpar o texto (de forma simples)
- Transformar tudo em lower case
- substituir simbolos especiais por representações
- remover espaços duplicados e antes e depois do texto
- remover pontuação
- Pegamos o array de frases que são um
list[str]
, uma lista de strings e convertemos para umlist[list[str]]
ou seja, um array onde cada array dentro dele representa uma frase e esse array terãos os tokens dessa frase - Capturamos todos os tokens únicos de todas as frases e obtemos um
list[str]
que é uma lista de tokens únicos - Então, criamos um hashset onde irá armazenar o número de ocorrências de cada token único para cada frase, então obtemos uma estrutura de dados no formato
dict[int, dict[str, int]]
, onde a chave do dicionário superior é o índice da frase no array de frases, o valor é um dicionário, onde seu índice é um token único e seu valor a quantidade de aparições na frase em questão - Transformamos essa estrutura em uma matrix no formato
list[list[int]]
, onde cada linha representa uma frase e cada item dessa linha é a quantidade de aparições de uma determinada palavra na frase em questão
Implementação manual
Para prosseguir, você precisará instalar uma biblioteca chamada nltk
.
Para instalar execute o seguinte comando:
pip install nltk
Então, vamos lá.
Primeiro vamos importar a biblioteca
import nltk
Agora, precisamos fazer o download de um modelo chamado 'punkt' do nltk para podermos usar a funcionalidade de tokenização (não queremos implementar essa parte na mão no momento).
nltk.download('punkt')
Após isso, vamos importar algumas dependências que usaremos mais tarde:
from io import BytesIO
from tokenize import tokenize
import re
from string import punctuation
Agora, vamos criar uma variável com uma frase que queremos vetorizar:
paragraph = 'I like to play football. Did you go outside to play tennis. John and I play tennis.'
Agora, vamos extrair as sentenças (frases) desse parágrafo:
raw_sentences = nltk.sent_tokenize(paragraph)
Uma parte importante do processo é limpar o nosso texto, removendo coisas como:
- Pontuação
- Emojis
- Espaços duplicados (antes ou depois do texto)
- entre outras técnicas
Nesse exemplo, nos contentaremos em:
- Transformar tudo em lower case
- substituir simbolos especiais por representações
- remover espaços duplicados e antes e depois do texto
- remover pontuação
Vamos criar uma função para isso:
def clear_text(sentences: list[str]) -> list[str]:
result = []
for sentence in sentences:
string = ' '.join(sentence.lower().casefold().split())
string = re.sub(f"[{re.escape(punctuation)}]", "", string)
result.append(string)
return result
Agora, vamos limpar nossas frases utilizando a função que acabamos de criar:
sentences = clear_text(raw_sentences)
E temos o seguinte resultado:
['i like to play football',
'did you go outside to play tennis',
'john and i play tennis']
Agora, precisamos tokenizar cada uma dessas frases e para isso, iremos criar mais uma função:
def sentence_into_words(sentences: list[str]) -> list[list[str]]:
result = []
for sentence in sentences:
tokens = tokenize(BytesIO(sentence.encode('utf-8')).readline)
tokens = [token.string for token in tokens if token.exact_type not in [0, 4, 63]]
tokens = [token for token in tokens if len(token) > 1]
result.append(tokens)
return result
Essa função utiliza a biblioteca nltk
para fazer a tokenização.
Podemos perceber que antes de chamar o método para tokenizar a frase em si, codificamos nossa string em "utf-8" e tranformamos ela em bytes.
Após isso aplicamos a função de tokenização.
Repare que logo após isso removemos os tokens que são identificados por "0", "4" e "63".
Isso é feito pelo simples motivo de que são tokens que representam o "encoding", "new-line" e "end", para nosso caso, não precisamos deles.
sentence_as_words = sentence_into_words(sentences)
Agora, vamos ter de extrair todas as palavras que são únicas entre todas as frases.
Para isso iremos criar outra função:
def get_unique_words_from_matrix(matrix: list[list[str]]) -> list[str]:
unique_words = set()
for row in matrix:
for word in row:
if word in unique_words:
continue
unique_words.add(word)
return list(unique_words)
E chamamos a mesma:
unique_words = get_unique_words_from_matrix(sentence_as_words)
Com isso temos o seguinte resultado:
['did',
'tennis',
'john',
'you',
'to',
'like',
'go',
'football',
'outside',
'play',
'and']
Isso é um dos importantes passos para nossa vetorização, pois, precisamos contar o número de ocorrências de cada um desses tokens em cada uma de nossas frases.
Então, vamos começar fazer isso.
Para fazer isso, vou criar uma função que a princípio vai ser bem complicada de entender:
def create_count_table(unique_words: list[str], sentence_as_words: list[list[str]]) -> dict[int, dict[str, int]]:
relation = { i: dict(zip(sorted(unique_words), [0] * len(unique_words))) for i in range(len(sentence_as_words))}
for index, sentence in enumerate(sentence_as_words):
for word in sentence:
relation[index][word] += 1
return relation
Tudo bem, o que raios está acontecendo nessa variável relation?
Bom, basicamente a estrutura que precisamos é a seguinte:
{
"indice-da-frase" {
"palavra-unica-1": "total-de-ocorrencia",
"palavra-unica-2": "total-de-ocorrencia",
"palavra-unica-3": "total-de-ocorrencia",
...
}
}
Para facilitar esse processo resolvi encurtar um pouco o código em python fazendo:
{ i: dict(zip(sorted(unique_words), [0] * len(unique_words))) for i in range(len(sentence_as_words))}
Vamos dividir isso em partes:
Primeiro esse dict(zip(sorted(unique_words), [0] * len(unique_words)))
.
Nesse trecho estamos organizando as palavras únicas por ordem alfabetica e criando um zip dessas palavras com um array de 0's do tamanho da nossa lista de palavras únicas e transformando isso em um dicionário, o que vai nos dar uma estrutura nesse formato:
{
"and":0,
"did":0,
"football":0,
"go":0,
"john":0,
"like":0,
"outside":0,
"play":0,
"tennis":0,
"to":0,
"you":0
}
Iniciando nosso hashmap onde tenho todos os tokens únicos com 0 ocorrências.
E então, replicamos isso para todas as frases e colocamos o índice da frase como chave e esse nosso hashmap como valor.
Agora que entendemos essa parte, vamos prosseguir.
unique_appearances = create_count_table(unique_words, sentence_as_words)
Agora, podemos visualizar quantas vezes cada palavra apareceu em cada frase:
for key in unique_appearances.keys():
print(key, sentences[key])
for word in unique_appearances[key].keys():
print(f'\t"{word}" appears {unique_appearances[key][word]} times')
um output parecido com:
0 i like to play football
"and" appears 0 times
"did" appears 0 times
"football" appears 1 times
"go" appears 0 times
"john" appears 0 times
"like" appears 1 times
"outside" appears 0 times
"play" appears 1 times
"tennis" appears 0 times
"to" appears 1 times
"you" appears 0 times
Se dermos uma olhada na variável "unique_appearances" teremos algo como:
{0: {'and': 0,
'did': 0,
'football': 1,
'go': 0,
'john': 0,
'like': 1,
'outside': 0,
'play': 1,
'tennis': 0,
'to': 1,
'you': 0},
1: {'and': 0,
'did': 1,
'football': 0,
'go': 1,
'john': 0,
'like': 0,
'outside': 1,
'play': 1,
'tennis': 1,
'to': 1,
'you': 1},
2: {'and': 1,
'did': 0,
'football': 0,
'go': 0,
'john': 1,
'like': 0,
'outside': 0,
'play': 1,
'tennis': 1,
'to': 0,
'you': 0}}
Agora, transformamos essa estrutura de dados em uma matrix:
def unique_appearances_to_matrix(appearances: dict[int, dict[str, int]]) -> list[list[int]]:
return [[appearance for appearance in appearances[key].values()] for key in appearances.keys()]
# Document term matrix
matrix = unique_appearances_to_matrix(unique_appearances)
E boom, temos o seguinte resultado:
[[0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0],
[0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1],
[1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0]]
Nosso parágrafo vetorizado.