Interpreting LDA Topic Models
LDA discovers hidden topics in text. It needs a numeric document-word table, which CountVectorizer produces, before it can fit a model.
Running the Pipeline Before Defining It
This lesson starts with code that already works rather than with definitions. You will meet a short pipeline that takes a collection of text documents and returns two outputs: a per-document summary of which topics each document leans toward, and a per-topic summary of which words define each topic. Once you have seen the pipeline run, the rest of the lesson explains each step and shows where the results can go wrong.
The goal is to make sense of the output. Latent Dirichlet Allocation, usually abbreviated LDA, is a topic modeling technique whose purpose is to discover hidden topics in a body of text. The word hidden matters. Nobody labels the topics in advance. The model proposes them from patterns of word use across documents, and the person running it has to read the output and decide what each topic means.
From Raw Text to a Count Table
LDA does not read raw sentences. It needs a numeric table in which each row is a document and each column is a vocabulary word, and each cell holds how often that word appears in that document. scikit-learn's CountVectorizer builds this table. Creating the vectorizer and calling its fit_transform method both learn the vocabulary from the training texts and convert those texts into the table, which is stored in X.
['battery' 'camera' 'price' 'screen']
[[2 0 0 1]
[0 1 2 0]]max_featurescaps the vocabulary size so the table stays manageablemax_dfdrops words that appear in a large share of documents, since they say little about any particular subjectfit_transformlearns the vocabulary and returns the count table X
Hyperparameters You Choose Before Fitting
Each keyword argument in the model setup is a hyperparameter: a setting the person chooses before fitting, rather than something the model learns from the data. The lesson sets the number of topics to 10, which is few enough that every topic can be inspected by hand. It sets the learning method to batch. The default is online, which runs faster, but batch is slower and usually gives better results, so the lesson accepts the extra time. The maximum number of iterations is raised to 25, because allowing more passes over the data can also produce a better model. The random_state value fixes the random starting point, so rerunning the code should reproduce the same result.
n_componentssets the number of topics the model produceslearning_method='batch' trades speed for usually better resultsfit_transformfits the model to X and returns the document-level output in one step
The call to fit_transform does two jobs at once. It fits the model to X, and it returns the document-level output stored in document_topics. Doing both in one step avoids the second pass you would need if you fitted first and transformed afterward.
What components_ Stores
What do you think happens?
Before you look at the topic words, predict the shape of the components_ table for a model with 10 topics fitted on a vocabulary of 500 words.
Reveal answer
(10, 500), that is (n_topics, n_words)
The fitted model keeps one row per topic and one column per vocabulary word, so each cell records how important that word is for that topic.
The fitted model keeps a table called components_, and this table is the bridge between the model and its topics. Each cell answers a single question: how important is this word to this topic? A large value means the word carries a lot of weight in that topic, and a small value means it hardly matters. The table plays the same role as the components in the matrix decomposition methods covered earlier in the course, so if you remember those, you already know its layout.
Ranking Words Inside Each Topic
A row of numbers is hard to read, so the next step converts each topic row into a list of its strongest words. You sort each topic's word weights and then map the sorted positions back to vocabulary names. The critical detail is that np.argsort returns positions, not values. It answers the question: which word positions, in order from smallest weight to largest, make up this row? Reversing the columns with [:, ::-1] turns that ascending list into a descending one, so the first entry of each row points to the most important word. The feature names array then turns those positions into readable words.
- axis=1 sorts each topic row independently
- [:, ::-1] reverses the ascending positions into descending order
feature_namesmaps the positions back to vocabulary words from the same vectorizer
Reading the top words of one topic
A topic row of components_ has weights [0.2, 3.1, 0.4, 2.2] for the vocabulary battery, camera... (in column order: battery, camera, price, screen). Which words make up the top three of this topic?
- 1Argsort the row
np.argsorton the row gives the ascending positions [0, 2, 1, 3], from smallest weight to largest. - 2Reverse the order
Reversing gives [3, 1, 2, 0], so the first entry points to the largest weight.
- 3Map positions to words
Position 3 is screen (3.1), position 1 is camera (2.2), and position 2 is price (0.4).
The top three words are screen, camera, and price. Battery, the smallest weight, is last.
Tracing the Full Chain of Stages
The steps so far form a chain in which each stage depends on the one before. Documents become a count table, the count table is fitted to produce a document-level output and a topic-word table, and the topic-word table is sorted into readable lists. The chain is the right mental model when something goes wrong, because a problem at any earlier stage appears as a strange topic at the end.
Trace the diagram from left to right while reading the code. The handoff that matters most for debugging is the one from components_ to the ranked word list. Everything upstream determines which numbers are in components_, and everything downstream only reads them. If a topic's words look wrong, trace back to the count table and the hyperparameters; you do not need to change the sorting code when the real cause is earlier.
Choosing Which Topics to Inspect
The number of topics changes the picture you get. Ten topics are few enough that a reader can inspect all of them. A hundred topics produce a more fine-grained picture, but reading all 100 would be overwhelming, so the lesson fits a 100-topic model and then inspects a hand-picked subset of representative topics selected by index. Those selected topic numbers are not a sequence and carry no inherent meaning; they were picked because they are interesting and representative.
Choosing those indices is a judgment call. A reader who picks different topic numbers will see a different slice of the same model. When you report results from a large model, say which topics you selected and why, so the reader can judge how representative your slice is.
Mistakes That Make Topics Drift
When topic words look wrong, the fault usually sits at one identifiable stage. Each mistake below names the stage where the divergence starts, which is the question a debugger needs answered first.
Common mistake Treating the topic numbers as a ranking
What it looks likeAssuming topic 0 is the most important topic and reporting it first as the main finding.
Why it failsA topic has no built-in rank, and the numbering is arbitrary.
Do this insteadRank topics by a property you compute, such as how often they appear in the documents, and state that criterion explicitly.
Common mistake Sorting in the wrong direction or along the wrong axis
What it looks likeUsing np.argsort without the reversal, so the least important words appear first in each list.
Why it failsnp.argsortreturns positions from smallest weight to largest, so without [:, ::-1] the top of each list is the weakest word.Do this insteadReverse the columns after argsort and sort along the topic axis so each row is handled independently.
Common mistake Mapping positions with the wrong vocabulary
What it looks likeUsing a feature name array from a different vectorizer than the one that produced X.
Why it failsThe positions in components_ only mean something relative to the columns of the table that was fitted.
Do this insteadTake the feature names from the same vectorizer object used to build X.
Common mistake Changing the topic count without re-reading every topic
What it looks likeKeeping an interpretation from a 10-topic run after switching to 100 topics.
Why it failsChanging the number of topics changes all of them, so earlier readings no longer apply.
Do this insteadRe-read the topic word lists from scratch after any change to n_components.
Common mistake Forgetting that vectorizer settings shape the topics
What it looks likeLeaving out a frequency cutoff so words that appear in almost every document dominate the topics.
Why it failsWords that appear nearly everywhere say little about any particular subject, and X is the only input LDA receives.
Do this insteadReview max_df and max_features before blaming the model, and rerun the pipeline after adjusting them.
Fitting itself adds a further source of drift. The random starting point fixed by random_state makes a run reproducible, but a different seed or a changed setting can still produce different topics, so a surprising result should be checked against the same settings before you trust it.
Checking a Strange Topic in Order
When a topic looks strange, check the stages in the same order the data flows. Begin with the vocabulary and the shape of X, then confirm the shape of components_ against the number of topics you set, and then check the sorting and the feature names. Most divergences are found at the earliest stage that disagrees with your expectation.
A colleague reports that their LDA topics are dominated by words such as 'the' and 'and', and that topic 0 is the one they presented as the most important. Name the two stages where you would look first, and say which mistake from this lesson each one would reveal.
Hints
- Think about which vectorizer setting removes words that appear in almost every document.
- Think about whether the topic numbering carries any meaning.
Key Takeaways
- LDA discovers hidden topics from a numeric document-word count table produced by CountVectorizer, and the person running it must read the output and decide what each topic means.
- The components_ table has shape (
n_topics,n_words), stores how important each word is to each topic, and has no inherent topic order. - To read a topic, argsort each row, reverse the columns with [:, ::-1], and map the positions back to feature names from the same vectorizer.
- The number of topics, the learning method, the iteration count, and the vectorizer settings all change the topics you get, so a surprising result should be traced back stage by stage.