Text Representation with Bag-of-Words
CountVectorizer from scikit-learn converts a collection of text documents into a matrix of token counts, which the source calls a bag-of-words representation.
Predicting What the Output Shows
Machine learning algorithms work with numbers, not raw sentences. A sentence such as a line from a poem cannot be fed directly into a learning algorithm, so something has to convert the text into numbers first. This article shows one practical way to do that using the CountVectorizer tool from the scikit-learn library. CountVectorizer converts a collection of text documents into a matrix of token counts, and the source calls that matrix a bag-of-words representation. Each document becomes a row of counts, so the text can be processed numerically.
What do you think happens?
When you inspect the bag-of-words result, does its printed repr line show you the individual words and their counts?
Reveal answer
No, it shows only the shape of the matrix and that it is sparse
The repr line reports a sparse matrix with shape 2 by 13. It does not show the words or the individual counts, so you must look at the matrix contents or the vocabulary to see them.
Why Text Must Become Numbers
A learning algorithm needs each input as a set of numbers in a consistent layout. Text does not come in that layout: two documents can be different lengths and use different words. A bag-of-words representation solves this by fixing a list of distinct words across the whole collection, then recording, for each document, how many times each word appears. Every document is then described by the same set of columns, which is what makes the rows comparable.
The result of CountVectorizer is a matrix of token counts. Each row is one document, and each column is one word from the collection. The word 'bag' in the name is a useful reminder: the representation keeps counts and throws away the arrangement of words inside each document.
Tracing the Four-Line Workflow
The source program works on a variable named bards_words, which holds the collection of text documents used throughout the source. The program has four lines, and together they are the whole workflow. Read the code first, then trace what each line is responsible for before you look at the final printed result.
- Line 1 imports CountVectorizer from the
feature_extraction.text module of scikit-learn - Line 2 creates an instance named vect
- Line 3 fits vect to the documents so it learns which words it will count
- Line 4 transforms the same documents into the bag-of-words result, which is assigned to a variable
Trace the four lines in order. Each line hands its result to the next line, so the trace follows the data from the raw collection to the final matrix.- 1Line 1 the import makes the CountVectorizer class available. Nothing is computed about the documents yet.
- 2Line 2 vect is a new vectorizer object. It has not seen any documents, so it does not yet know any words.
- 3Line 3 fit examines
bards_wordsand records the distinct words it finds. The vectorizer now knows its vocabulary, but this call does not return the count matrix. - 4Line 4 transform uses the vocabulary learned in line 3 to turn
bards_wordsinto the matrix of counts, and the result is stored inbag_of_words. - 5Evaluating the name
bag_of_wordson its own, for example in an interactive session, displays the repr line that reports the shape of the result.
Fit Learns, Transform Counts
The workflow has two stages. The fit stage examines the documents so the vectorizer knows the words it will count. The transform stage then uses that knowledge to turn the documents into the matrix of counts. Keeping these stages separate in your head makes the program much easier to debug, because a problem usually belongs to one stage or the other.
Reading the 2 by 13 Shape
Read the shape $2 \times 13$ as two rows and thirteen columns. The two rows correspond to the two documents in the collection. The thirteen columns correspond to the thirteen distinct words the vectorizer found across the corpus. Each cell in the matrix therefore belongs to exactly one document (its row) and exactly one vocabulary term (its column).
The repr line gives the shape, but not the contents. It also tells you the matrix is sparse, which means most cells are expected to be zero for typical text. Because of that, the object stores only its nonzero entries rather than writing out every zero. The printed line therefore confirms the dimensions of the result without showing you which words occurred in which document.
Word order is also absent from this representation. Each row records how often each word occurs, not where it sits in the sentence. Two documents that contain the same words in a different order would therefore produce identical count rows. This follows directly from what a bag of words is: a collection of counts with the arrangement discarded.
Finding Where a Result Goes Wrong
Suppose you run the same program and the printed line reports a different shape from the one you predicted. Do not guess at the cause. Start at the top of the program and, at each line, ask what that line was supposed to produce. Then compare that expectation with what the next line receives. The first line where the expectation and the reality disagree is the place to look.
Locating the divergence in a three-document run
You expected a bag-of-words result with 3 rows, one per sentence you supplied, but the repr line reports 2 rows. Find the step where the result first goes wrong.
- 1Check the input
Count the documents in the collection passed to fit and transform. If the collection holds only 2 sentences, the problem starts before the vectorizer runs.
- 2Check the fit line
fit learns the vocabulary from the documents it receives. It does not set the number of rows, so a row mismatch is not caused by fit.
- 3Check the transform line
transform should return one row for each document it is given. If the collection passed to transform is the same as the one passed to fit and it has 3 sentences, the row count should be 3.
- 4Locate the first divergence
The first line whose output disagrees with expectation is where to look. Here, if the collection itself has 2 sentences, the divergence is at the input, not in the vectorizer calls.
The divergence is found by checking the input before the vectorizer. Only after the input is confirmed should you examine fit and transform.
Mistakes When Reading Bag-of-Words Output
Common mistake Expecting fit to return the count matrix
What it looks likeWriting result = vect.fit(bards_words) and then expecting result to hold counts.
Why it failsfit examines the documents so the vectorizer knows the words it will count. The counts come from the transform stage.
Do this insteadAssign the output of transform to the variable, as in bag_of_words = vect.transform(bards_words).
Common mistake Reading the repr line as a list of words
What it looks likeLooking at the repr output for the names of the words it contains.
Why it failsThe repr line reports the sparse matrix shape, here 2 by 13, and does not show the words or the counts.
Do this insteadRead the first number as the number of documents and the second as the number of distinct vocabulary words, then inspect the matrix contents or the learned vocabulary to see the terms.
Common mistake Assuming the column count is the number of words in each document
What it looks likeThinking 13 columns means each document contains 13 words.
Why it failsThe columns are the distinct words found across the whole corpus, not the word count of any single document.
Do this insteadTreat 13 as the size of the shared vocabulary for the entire collection.
Common mistake Guessing at the cause of a wrong shape
What it looks likeChanging the code at random when the printed shape differs from the prediction.
Why it failsRandom changes can hide the real problem and make it harder to find.
Do this insteadTrace from the top of the program and find the first line where the expected and actual values disagree, then check the input passed to that line.
Common mistake Forgetting that the same documents are passed to fit and transform, so a mismatch between the two calls can change the result
Practice: Predict the Row Count
Build a short list of two or three sentences of your own. Pass it to the same four-line workflow, and before you run it, predict the number of rows. Then predict the number of columns by counting the distinct words across the sentences. Compare your predictions with the printed shape. If they disagree, go back to the divergence check and find the first line responsible.
Hints
- The row count should match the number of sentences you supplied.
- The column count reflects the number of distinct words across all the sentences, not the number of words in one sentence.
- If the shape differs from your prediction, check the input passed to fit and transform before looking at the vectorizer itself.
Key Takeaways
- CountVectorizer converts a collection of text documents into a matrix of token counts, which is the bag-of-words representation, with one row per document.
- fit learns the vocabulary from the documents; transform uses that vocabulary to produce the count matrix that is assigned to a variable.
- The repr line reports a sparse matrix with shape 2 by 13: two rows for documents and thirteen columns for distinct corpus words, but not the words or the counts.
- When a result is unexpected, trace the program from the top and locate the first line where expectation and reality disagree, then check that line's input.