Sparse Matrices for Text Data
A feature in this context is simply a token that occurred somewhere in the training reviews.
Try it: Text Vectorization: Tokens to Vectors
The text-to-tensor pipeline: standardize, split into tokens, build a frequency-ranked vocabulary with padding and [UNK], map tokens to integer ids, pad or truncate, then one-hot or multi-hot encode and look the ids up in an embedding table.
How it works
- Standardize: lowercase and delete punctuation.
- Split on whitespace into tokens.
- Build the vocabulary: 0 = padding, 1 = [UNK], then words by frequency (ties: first appearance), capped at max tokens.
- Map each token to its id (1 if it did not make the vocabulary), then pad with 0 or truncate to the sequence length.
- Encode: one-hot rows per position or one multi-hot row per text; embed: id k selects row k of the embedding table.
Default run (18 steps): Raw text (59 characters): "The movie was great. The plot was great, the acting wasn't!". A model cannot read strings, so it is turned into numbers step by step. … Embedding lookup: id k selects row k of a fixed 8 x 3 table, giving a (8, 3) dense matrix instead of (8, 8) sparse one-hots. Row 0 (padding) is looked up too; a trained Embedding layer would learn these rows.
Simplified: Bounded ASCII text (up to 200 characters, 40 tokens), whitespace splitting only, and a small FIXED embedding table (a real Embedding layer learns its rows). The multi-hot vector keeps the padding slot (always 0) so its positions line up with the ids.
Loading the simulation…
A Surprising First Feature
Up to now, the bag-of-words process has been shown on small, hand-made examples. This article moves to a realistic setting: sentiment analysis of movie reviews from the IMDb data. The training and test reviews have already been loaded into lists of strings, and the same counting procedure is now applied to thousands of reviews at once.
When you run a vectorizer on real text, the output can look surprising. The most useful skill at this stage is knowing where a surprise comes from. Each stage of the process has one specific job, so a mismatch between what you expected and what you see always points back to one stage. This article builds that habit, starting with the vocabulary itself.
What do you think happens?
Before looking at the real vocabulary, predict: will a number such as a year, a catalog code, or '007' become a feature if it appears in the training reviews?
Reveal answer
Yes. The vectorizer records every token that occurs in the reviews, and numbers are among them.
The first ten entries of the real vocabulary are all numbers, and they are there because they occur in the reviews. Apart from '007', which in the context of movies probably refers to the James Bond character, most of these numbers have no immediate semantic meaning. The vectorizer did not judge them. It recorded them.
Why the Vocabulary Is Not a Curated Word List
A very common assumption at this point is that the vocabulary is a curated list of meaningful words. Under this picture, the vectorizer reads a review, recognizes the important words, and skips the rest. A number such as a year would never become a feature, because it carries no sentiment.
The mechanical reality is different. The vocabulary is built by scanning the training reviews and recording every token that appears. Whether a token is meaningful, a number, a misspelling, or a variant of another word, the process treats it the same way: it becomes a feature. The vectorizer does not evaluate meaning. It counts occurrences.
Fit Builds the Vocabulary, Transform Counts
The process has two distinct calls, and keeping them apart is the key to reading the output. The fit call scans the training reviews and builds the vocabulary. It determines which words exist and assigns each one a feature position. The transform call then takes the reviews and produces the count matrix, where each entry records how often a feature occurs in a given review.
When you trace these two lines, pay attention to which object each call returns and what it is responsible for. The vectorizer object holds the vocabulary after fitting, and the matrix comes out of the transform step. If a result is wrong, you can ask which of these two calls introduced it. A wrong vocabulary points to fitting; a wrong set of counts for a correct vocabulary points to transforming.
- fit scans the reviews and builds the vocabulary, assigning each token a feature position
- The vectorizer object now holds the vocabulary
- transform produces the count matrix, one row per review
Reading the Matrix Shape
The shape of the resulting training matrix is 25,000 by 74,849. The number of rows corresponds to the 25,000 training reviews, and the number of columns, 74,849, matches the number of vocabulary entries. This is the first check to make on any output: the column count should equal the vocabulary size, and the row count should equal the number of documents passed in.
| Dimension | What it counts | Expected value for this data |
|---|---|---|
| Rows | Documents passed to transform | 25,000 training reviews |
| Columns | Vocabulary entries from fit | 74,849 features |
| Each cell | How often a feature occurs in a review | Usually zero |
Each dimension of the training bag-of-words matrix and what it should match
Checking the training matrix against expectations
You have 25,000 training reviews and the vectorizer reports 74,849 vocabulary entries. Does the transformed matrix pass the first check?
- 1Identify the expected rows
One row should exist for each review passed to transform, so the expected row count is 25,000.
- 2Identify the expected columns
One column should exist for each vocabulary entry, so the expected column count is 74,849.
- 3Compare with the actual shape
The shape is 25,000 by 74,849, so both counts match and the first check passes.
The matrix passes the shape check. If either number had differed, the disagreement would tell you which stage to inspect.
Storing Only the Non-Zero Counts
The bag-of-words matrix is stored as a SciPy sparse matrix, specifically in Compressed Sparse Row format. A sparse matrix stores only the non-zero elements, which conserves memory when most entries are zero. Its printed description also reports how many stored elements it contains.
The printed description of X_train reports 3,431,196 stored elements. The full grid has 25,000 times 74,849 positions, which is $1{,}871{,}225{,}000$ cells. The ratio is $\frac{3{,}431{,}196}{1{,}871{,}225{,}000} \approx 0.0018$, so only about 0.18 percent of the cells hold stored values. Almost every cell is a count of zero, because almost every word in the vocabulary is absent from almost every review.
- 1stored elements is the count the printed description reports for X_train
- 2rows times columns is the total number of grid positions, which is 1,871,225,000
- 3The quotient is about 0.0018, meaning roughly 0.18 percent of cells hold a stored value
The diagram is a teaching simplification and not a literal memory layout. Its entries illustrate the principle: a stored element records a word that is present in a review and how often it occurs. Words that do not occur in the review simply have no entry, which is why the matrix can be large without being expensive to hold.
Reading Feature Names by Position
The vectorizer exposes the vocabulary through get_feature_names, which returns a list in which each entry corresponds to one feature. The list index is the feature's column position in the matrix. Because it is an ordinary Python list, you can take slices of it to inspect any region of the vocabulary without printing all 74,849 entries.
Key Takeaways
- The vocabulary is built mechanically: every token in the training reviews becomes a feature, including numbers and word variants.
- Fit builds the vocabulary and assigns positions; transform produces the count matrix, so a wrong result can be traced to one of these two calls.
- The training matrix is 25,000 by 74,849 and stored sparsely, with about 0.18 percent of cells holding values.
- When output surprises you, check the stages in order and find the earliest one that disagrees with your expectation.