Machine Learning · Insper · E2.1

Bag-of-Words

Fundamental approach

How a document becomes a row of counts, and how alike two rows are

The idea

a column

for each word of the vocabulary

a row

for each document

a cell

how many times that word occurs in that document

Nothing is learned except the vocabulary: no model, no training.

The path of this part

  1. Count the wordsthe count matrix, CountVectorizer, counts or presence
  2. Compare two bagscosine similarity: the angle, not the distance
  3. What the bag losesword order; bigrams buy part of it back
Each stage answers one question: how does a document become a row of numbers? → how alike are two documents? → what does the bag throw away?
Stage 1 · how does a document become a row of numbers?

Three documents, cut into 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']
  

the cat ate the fish · the dog ate the cat's food · cat sleeps, cat dreams

Predict. How many columns will the matrix have? And what number goes in the cell (d3, gato)?

CountVectorizer builds the matrix


bow = CountVectorizer()
X = bow.fit_transform(docs)
print(pd.DataFrame(X.toarray(), columns=bow.get_feature_names_out(), index=['d1', 'd2', 'd3']))
print(type(X).__name__, X.shape, 'non-zeros:', X.nnz)
  

    cachorro  comeu  do  dorme  gato  peixe  ração  sonha
d1         0      1   0      0     1      1      0      0
d2         1      1   1      0     1      0      1      0
d3         0      0   0      1     2      0      0      1
csr_matrix (3, 8) non-zeros: 11
  
\[ \text{doc}_i \;\longrightarrow\; x_i \in \mathbb{R}^{|V|}, \qquad x_{ij} = \text{times word } j \text{ occurs in document } i \]
8 columns, the vocabulary \(V\) in alphabetical order; (d3, gato) is 2. "Bag" is literal: pour the words into a bag, shake it, and only the counts survive.

fit learns the vocabulary, transform only applies it

The vectorizer above was fitted on the three documents. Now three new ones:

  • "O gato comeu o gato." (the cat ate the cat)
  • "O cachorro latiu." (the dog barked)
  • "Um felino adormeceu." (a feline fell asleep)
Predict. What happens to "latiu", and what vector does "Um felino adormeceu." become?

Unseen words have no column


[[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" is silently dropped.
  • "um felino adormeceu" is made only of unseen words: the zero vector.
A vectorizer is a fitted step, like a scaler: fit it on the training documents only. Words that exist only in the test set must not shape the columns.

Counts or presence: 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]]
  

d3's 2 under gato became 1.

short texts

titles, tweets, queries: a word rarely repeats, so both versions are almost identical

long documents

counts let one word that a post repeats many times dominate its vector

Stage 2 · how alike are two documents?

Comparing two bags: the cosine

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

For counts: 0 (no word in common) to 1 (the same words in the same proportions).

Predict. d1 shares comeu and gato with d2, and only gato with d3 (which has it twice). Which pair is more similar?

Counting and comparing, step by step

The three documents: each of the 12 tokens adds one to its cell, then each pair is compared by the cosine. Predict. Which pair is the most similar, and by how much?

Why the angle and not the distance?

Paste d1 twice and compare the result with 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]
  
Predict. What is their Euclidean distance? And their cosine?

Euclidean distance

1.732
\(\sqrt{3}\): grows with the difference in length

cosine

1.000
looks only at the direction

The length of a document is not what it is about

That is why nearest neighbors and clustering on text use metric='cosine', or first divide each row by its length (which the TF-IDF vectorizer, for term frequency × inverse document frequency, does by default).
Stage 3 · what does the bag throw away?

Word order is lost

  • "O cachorro mordeu o homem." (the dog bit the man)
  • "O homem mordeu o cachorro." (the man bit the dog)
Predict. What is the cosine between these two sentences?
featuresvectorscosine
single words (3 columns)[1, 1, 1] and [1, 1, 1]1.00
+ bigrams (7 columns)"cachorro mordeu", "mordeu homem" only in the first; "homem mordeu", "mordeu cachorro" only in the second0.60

Bigrams buy back local order, at a price

  • With single words the two sentences are the same vector: who bit whom is gone.
  • ngram_range=(1, 2) adds pairs of neighboring tokens and the cosine falls to 0.60.
  • The price: more columns, most of them rare.
The bigram joins tokens that are neighbors after tokenization: "o" was dropped, so "mordeu o homem" gave "mordeu homem".

Simulator: build the bag yourself

Predict. Load the word-order preset: will the first two sentences get different vectors? Then tick add bigrams.

Summary

  1. A document becomes its word counts, one column per vocabulary word; word order is thrown away.
  2. fit fixes the vocabulary; unseen words are dropped. Fit on the training documents only.
  3. binary=True keeps only presence, which matters for long documents.
  4. Compare documents with the cosine: length is not subject.
  5. Order is lost: "the dog bit the man" = "the man bit the dog". Bigrams buy back local order, with many more columns.
Fundamental lab · about 25 min, in pairs

Bag-of-words on real posts, steps 1 to 5

  • Four newsgroups (computer graphics, baseball, electronics, Christianity), about 2,100 posts; scikit-learn only.
  • One question: are the most similar posts about the same subject? The neighbor agreement answers it (0.251 by chance).
  • Steps: load the posts · the count matrix · what each newsgroup talks about · shrinking the vocabulary · counts, presence, stop words.
  • At every step, answer the Predict before running. Simulation: a stop list learned from the data (step 5).
  • Download the lab notebook and open it in Jupyter or Colab.

Pace: steps 1 and 2 in 5 min; steps 3 and 4 in 8 min; step 5 and the simulation in 12 min.