Machine Learning · Insper · U4.1

Bag-of-Words

Contar as palavras

O jeito mais simples de transformar um documento em vetor e ainda um dos mais usados

A ideia

uma coluna

para cada palavra do vocabulário

uma linha

para cada documento

uma célula

quantas vezes aquela palavra aparece naquele documento

Nada é aprendido além do vocabulário: não há modelo nem treino.

O caminho desta aula

  1. Contar as palavrasa matriz de contagens, CountVectorizer, contagem ou presença
  2. Comparar dois sacossimilaridade de cosseno: o ângulo, não a distância
  3. O que o saco perdea ordem das palavras; n-gramas de palavras e de caracteres
  4. Um corpus realesparsidade e o que as contagens brutas põem em primeiro lugar
Cada etapa responde a uma pergunta: como um documento vira uma linha de números? → quão parecidos são dois documentos? → o que o saco joga fora? → o que acontece num corpus real?
Etapa 1 · como um documento vira uma linha de números?

Três documentos, cortados em tokens


"O gato comeu o peixe."               ['gato', 'comeu', 'peixe']
"O cachorro comeu a ração do gato."   ['cachorro', 'comeu', 'ração', 'do', 'gato']
"Gato dorme, gato sonha."             ['gato', 'dorme', 'gato', 'sonha']
  
Preveja. Quantas colunas a matriz vai ter? E que número vai na célula (d3, gato)?

Contagens, à mão

O vocabulário, em ordem alfabética: 8 palavras.

cachorrocomeudodormegatopeixeraçãosonha
d101001100
d211101010
d300012001
\[ \text{doc}_i \;\longrightarrow\; x_i \in \mathbb{R}^{|V|}, \qquad x_{ij} = \text{vezes que a palavra } j \text{ aparece no documento } i \]

O CountVectorizer monta a mesma matriz


bow = CountVectorizer()
X = bow.fit_transform(docs)
print(type(X).__name__, X.shape, 'non-zeros:', X.nnz)
print(bow.vocabulary_)
  

csr_matrix (3, 8) non-zeros: 11
{'gato': 4, 'comeu': 1, 'peixe': 5, 'cachorro': 0, 'ração': 6, 'do': 2, 'dorme': 3, 'sonha': 7}
  
"Bag" (saco) é literal: despeje as palavras num saco e sacuda: só as contagens sobrevivem. Salton, Wong & Yang (1975): documentos são pontos num espaço com um eixo por palavra.

O fit aprende o vocabulário, o transform só o aplica

O vetorizador acima foi ajustado nos três documentos. Agora, três documentos novos:

  • "O gato comeu o gato."
  • "O cachorro latiu."
  • "Um felino adormeceu."
Preveja. O que acontece com "latiu"? Que vetor "Um felino adormeceu." vira?

Palavras nunca vistas não têm coluna


[[0 1 0 0 2 0 0 0]
 [1 0 0 0 0 0 0 0]
 [0 0 0 0 0 0 0 0]]
  
  • "latiu" é descartada sem aviso.
  • "um felino adormeceu" é feita só de palavras nunca vistas: vira o vetor nulo.
Um vetorizador é uma etapa ajustada, como um escalonador: ajuste-o só nos documentos de treino. Palavras que só existem no teste não podem definir as colunas.

Contagem ou presença: binary=True


[[0 1 0 0 1 1 0 0]
 [1 1 1 0 1 0 1 0]
 [0 0 0 1 1 0 0 1]]
  

O 2 de d3 sob gato virou 1.

textos curtos

títulos, tweets, consultas: uma palavra raramente se repete, então as duas versões são quase idênticas

documentos longos

as contagens deixam uma única palavra muito repetida dominar o vetor

Etapa 2 · quão parecidos são dois documentos?

Comparando dois sacos: o cosseno

\[ \cos(x_a, x_b) = \frac{x_a \cdot x_b}{\lVert x_a \rVert\, \lVert x_b \rVert} \]

Para contagens: de 0 (nenhuma palavra em comum) a 1 (as mesmas palavras nas mesmas proporções).

Preveja. d1 compartilha comeu e gato com d2 e só gato com d3 (que a tem duas vezes). Qual par é mais parecido?

À mão, depois conferido

d1, d2: \(x_1 \cdot x_2 = 1 + 1 = 2\)

\[ \cos = \frac{2}{\sqrt{3}\,\sqrt{5}} = 0{,}516 \]

d1, d3: só gato, contagem 2 em d3

\[ \cos = \frac{2}{\sqrt{3}\,\sqrt{6}} = 0{,}471 \]

       d1     d2     d3
d1  1.000  0.516  0.471
d2  0.516  1.000  0.365
d3  0.471  0.365  1.000
      

cosine_similarity(X)

Por que o ângulo e não a distância?

Cole d1 duas vezes e compare o resultado com d1:


"O gato comeu o peixe."                          [0 1 0 0 1 1 0 0]
"O gato comeu o peixe. O gato comeu o peixe."    [0 2 0 0 2 2 0 0]
  
Preveja. Qual é a distância euclidiana entre eles? E o cosseno?

distância euclidiana

1,732
\(\sqrt{3}\): cresce com a diferença de comprimento

cosseno

1,000
só olha a direção

O comprimento de um documento não é o assunto dele

É por isso que a busca de vizinhos e o agrupamento de textos usam metric='cosine' ou dividem antes cada linha pelo seu comprimento (o que o vetorizador do TF-IDF faz por padrão).
Etapa 3 · o que o saco joga fora?

A ordem das palavras se perde

  • "O cachorro mordeu o homem."
  • "O homem mordeu o cachorro."
Preveja. Qual é o cosseno entre essas duas frases?
atributosvetorescosseno
palavras sozinhas (3 colunas)[1, 1, 1] e [1, 1, 1]1,00
+ bigramas (7 colunas)"cachorro mordeu", "mordeu homem" só na primeira; "homem mordeu", "mordeu cachorro" só na segunda0,60

Os bigramas recuperam a ordem local, com um preço

  • Com palavras sozinhas, as duas frases são o mesmo vetor: quem mordeu quem desapareceu.
  • ngram_range=(1, 2) acrescenta pares de tokens vizinhos e o cosseno cai para 0,60.
  • O preço: mais colunas, quase todas raras.
O bigrama junta tokens que ficam vizinhos depois da tokenização: o "o" foi descartado, então "mordeu o homem" deu "mordeu homem".

n-gramas de caracteres

analyzer='char_wb' corta cada palavra, com espaços nas bordas, em pedaços sobrepostos de 3 letras.

parcossenotrigramas compartilhados
sábado / sábados0,77' sá', 'ado', 'bad', 'sáb', 'ába'
gato / gatos0,67' ga', 'ato', 'gat'
gato / pato0,50'ato', 'to '
Toleram plurais, conjugações e erros de digitação, sem stemmer nenhum. O custo: "gato" e "pato" compartilham a grafia, não o sentido.
Etapa 4 · o que acontece num corpus real?

Esparsidade: um corpus de verdade

20 Newsgroups, parte de treino: 11.314 posts, sem cabeçalhos, assinaturas e respostas citadas.

Preveja. O vocabulário tem 101.631 palavras. Que fração das células é zero?

zeros

99,90%
um post mediano usa 61 colunas

como array denso

9,2 GB
float64

como CSR esparsa

13,3 MB
cerca de 700 vezes menos

Por que os vetorizadores devolvem matrizes esparsas

Esquerda: pontos marcando as células não nulas de 200 posts pelas 2.000 palavras mais frequentes, ainda 96,8% vazias. Direita: com bigramas, o vocabulário passa de 101.631 para 948.675 colunas e, com trigramas, para 2.524.547, enquanto o post mediano preenche só 61, 136 e 213 delas

O formato CSR (linhas esparsas comprimidas) guarda só os valores não nulos e suas colunas. Os bigramas multiplicam as colunas por 9 e os trigramas por 25; min_df=2 ou max_features controlam o tamanho. O .toarray() fica restrito a exemplos pequenos.

O que as contagens brutas põem em primeiro lugar

Some cada coluna dos 11.314 posts e ordene.

Preveja. Qual palavra vem primeiro? E depois de remover as stop words do inglês?
vetorizadorpalavras do topoa líder
padrãothe, ax, to, of, and, in, is, that"the": 106.381 vezes, em 9.475 posts
stop_words='english'ax, max, people, like, don, just, know, use"ax": 62.387 vezes, em 20 posts

Dois problemas, uma cura

Esquerda: as 15 palavras mais frequentes com o tokenizador padrão são palavras funcionais lideradas por the, 24% de todos os tokens. Direita: com a lista de stop words do inglês, ax lidera com 62.387 ocorrências em só 20 posts
  • Palavras presentes em quase todo post não dizem nada sobre o assunto.
  • "ax" se repete milhares de vezes em 20 posts com arquivos binários colados como texto: um único documento estranho domina uma coluna.
A cura, na próxima página (U4.2 TF-IDF): pesar cada palavra por quantos documentos a contêm, não só por quantas vezes ela aparece.

Simulador: monte o saco você mesmo

Preveja. Carregue o preset ordem: as duas primeiras frases vão receber vetores diferentes? Depois marque incluir bigramas.

Resumo

  1. Um documento vira as contagens das suas palavras, uma coluna por palavra do vocabulário.
  2. O fit fixa o vocabulário; palavras nunca vistas são descartadas. Ajuste só no treino.
  3. Compare documentos pelo cosseno: comprimento não é assunto.
  4. A ordem se perde; bigramas e n-gramas de caracteres recuperam um pouco, com muito mais colunas.
  5. A matriz tem 99,90% de zeros: o armazenamento esparso é obrigatório.
  6. As contagens brutas são lideradas por palavras que estão em toda parte ou num único documento estranho.
Lab · bem menos de um minuto, só scikit-learn

Bag-of-words em posts reais

  • Quatro newsgroups (computação gráfica, beisebol, eletrônica e cristianismo), cerca de 2.100 posts.
  • Uma pergunta: os posts mais parecidos são sobre o mesmo assunto?
  • Passos: carregar os posts · a matriz de contagens · do que cada newsgroup fala · encolhendo o vocabulário · contagens, presença, stop words · n-gramas de palavras e de caracteres · o saco não distingue um post de suas palavras embaralhadas.
  • Baixe o notebook do lab e abra no Jupyter ou no Colab.