LDA, NMF and BERTopic, from the classical to the modern
a probabilistic story of how words are written
factorizes the TF-IDF matrix
sentence embeddings + UMAP + HDBSCAN + a variant of TF-IDF
Two corpora: 11 hand-written sentences, and 2,155 real posts from four forums (the forum is hidden from the models, to check them).
"Sports" gives high probability to team, game, goal, almost none to oven. A document is a mixture: 80% sports, 20% space.
LDA, and NMF with non-negative weights
Each document belongs to one topic, or to none; the topic's words are computed afterwards, to describe the cluster.
BERTopic
| doc | team | game | goal | rocket | launch | moon | bread | bake |
|---|---|---|---|---|---|---|---|---|
| 0 | 1 | 1 | 1 | 0 | 0 | 0 | 0 | 0 |
| 3 | 0 | 0 | 0 | 1 | 1 | 0 | 0 | 0 |
| 6 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 |
| 9 | 1 | 1 | 0 | 1 | 1 | 0 | 0 | 0 |
| 10 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 |
11 sentences, 53 words kept, a vocabulary of 33 (English stop words removed).
Three themes: sports, space, cooking; the last two sentences mix them.
Latent Dirichlet Allocation (Blei, Ng & Jordan, 2003):
Not a claim that people write this way. Word order plays no part: bag-of-words counts are enough.
A document 80% sports, 20% space:
\(p(\textit{team}) = 0.8 \times 0.40 + 0.2 \times 0.05 = 0.33\)
\(p(\textit{rocket}) = 0.8 \times 0.05 + 0.2 \times 0.40 = 0.12\)
Following the story, 4 of 12 slots drew the space topic, and they are exactly where moon, orbit, rocket, orbit appear.
| \(\alpha\) | largest share |
|---|---|
| 0.1 | 0.89 |
| 1 | 0.62 |
| 10 | 0.42 |
In scikit-learn: doc_topic_prior and topic_word_prior, both \(1/k\) by default: few topics per document, few words per topic.
Predict. With \(\alpha = 0.1\), will the documents you draw come out in one color or in all three?
Four forums of 20 Newsgroups (graphics, hockey, space, religion); 5,725 words of 3+ letters in at least 5 posts and at most half of them.
| jpeg don pts like think play period gif |
| god space people think just jesus time like |
| team game hockey season games nhl play year |
| image edu data graphics space available software program |
the topics: a weight for every word
how much of each topic a document contains
Non-negative matrix factorization (Lee & Seung, 1999): the same shape as PCA, plus the constraint that every entry is \(\ge 0\). Here \(X\) is TF-IDF.
Topics: launch rocket satellite orbit · bread oven bake fresh · team game goal won
Clean split, where LDA failed 19 times out of 20.
init='nndsvda' starts from an SVD, not a random point: one reason the result is stable.
| sentence | space | cooking | sports |
|---|---|---|---|
| 0 (sports) | 0.00 | 0.00 | 0.71 |
| 3 (space) | 0.62 | 0.00 | 0.00 |
| 9 team watched the rocket launch… | 0.29 | 0.00 | 0.45 |
| 10 astronauts ate fresh bread… | 0.10 | 0.41 | 0.00 |
Predict. From random weights, press step: will the NMF rows end up as whole themes or as contrasts like the SVD's? Will its error reach the SVD's?
| graphics thanks files image program file know windows |
| god jesus people believe christ faith bible christians |
| game team hockey players play games season nhl |
| space nasa launch shuttle moon orbit lunar earth |
One run on one corpus: the lab finds a corpus where the order is reversed.
No labels to cross-validate against: the score must come from the documents.
how surprised LDA is by held-out posts; lower is better; only for probabilistic models
do the top words co-occur in documents?
By hand: of 2,155 posts, 304 contain god, 159 jesus, 101 both; independent would be 1.04%, observed 4.69%: NPMI = 0.492.
At \(k = 4\): LDA coherence 0.224, diversity 0.85; NMF 0.369 and 1.00.
Perplexity only rises: read literally, it recommends \(k = 2\); and it does not track what people find interpretable.
First 20 words of each post: NMF 0.12, LDA 0.03.
On the 20-word posts, plain k-means on the embeddings reaches ARI 0.582, five times NMF's.
Each post → 384 numbers with all-MiniLM-L6-v2 (small, English, fast on a CPU).
min_dist=0.0 packs similar points tightly, which a density clusterer wants.random_state.min_cluster_size=10 and the densityHockey (516) and religion (554) are one big cluster each; graphics and space break into many small ones (file formats, graphics cards, the Moon…).
min_cluster_size, one value at a timePredict. Raise min_cluster_size from 2 to 40: will the number of topics fall smoothly?
Join the documents of a cluster into one pseudo-document; then a class-based TF-IDF:
\[ \text{c-TF-IDF}(t, c) = \frac{n_{t,c}}{\sum_{t'} n_{t',c}} \times \log\!\Big(1 + \frac{A}{f_t}\Big) \]By hand, three clusters of the hand-made sentences: 16, 13 and 14 words, \(A = 14\).
\(\tfrac{3}{16} \times \log(1 + 14/3) = 0.325\)
\(\tfrac{2}{16} \times \log 8 = 0.260\)
BERTopic's own transformer gives exactly the same matrix.
| cluster | posts | top words |
|---|---|---|
| −1 | 431 | space jpeg launch image (the outliers) |
| 1 | 567 | god jesus people church |
| 0 | 520 | team game hockey play |
| 15 | 66 | image graphics package processing |
| 9 | 55 | card mode vesa vga |
| 23 | 52 | moon lunar prize money |
The class BERTopic chains the same four steps: same partition (ARI 1.0), same top words, only renumbered by size.
| topic 2, named by… | top words |
|---|---|
| default vectorizer | image and for of to it or the graphics with |
| stop words removed | image graphics package processing software data analysis … |
| KeyBERTInspired | graphical graphics rtrace plots tools interactive plotting … |
update_topics changes them and the post-to-topic assignments stay identical.Same 2,155 posts, 24 topics for all three, two seeds each. Predict first: whose topics are purest?
| model | seed | topics | outliers | coherence | diversity | homogeneity |
|---|---|---|---|---|---|---|
| LDA | 42 | 24 | 0% | 0.265 | 0.72 | 0.526 |
| NMF | 42 | 24 | 0% | 0.391 | 0.92 | 0.646 |
| BERTopic | 42 | 24 | 20% | 0.251 | 0.88 | 0.927 |
| LDA | 43 | 24 | 0% | 0.276 | 0.70 | 0.574 |
| NMF | 43 | 24 | 0% | 0.407 | 0.91 | 0.644 |
| BERTopic | 43 | 16 | 10% | 0.295 | 0.82 | 0.662 |
| LDA | NMF | BERTopic | |
|---|---|---|---|
| a topic is | a distribution over words | a non-negative row of \(H\) | a cluster, named by c-TF-IDF |
| number of topics | fixed in advance | fixed in advance | found by HDBSCAN |
| short documents | weak | weak | strong |
| outliers | none | none | explicit (−1) |
| seed-dependence | strong | weaker (SVD start) | yes, even the number of topics |
| cost | cheap, CPU | cheapest, CPU | embedding model; a GPU helps |
Without a list, topics are named by the, of, to. A list in the wrong language does not help: topic 6 here is van, het, een, from posts in Dutch.
reduce_outliers reassigns all 431. Homogeneity on all posts: 0.822 (c-TF-IDF) or 0.882 (embeddings), down from 0.927.
min_cluster_size from 10 to 15: a few topics fewer, or a lot?| min_cluster_size | topics | outliers |
|---|---|---|
| 5 | 96 | 28% |
| 10 | 24 | 20% |
| 15 | 5 | 0% |
| 25 | 3 | 0% |
Scan a few values and read the topics; to merge topics without re-clustering, reduce_topics.
Add to the embeddings a noise of about one millionth (the size of CPU-vs-GPU differences), keep every seed:
| noise, seed 0 | 24 topics, ARI with the original 0.908 |
| noise, seed 1 | 24 topics, ARI 0.910 |
| noise, seed 2 | 3 topics, ARI 0.514 |
min_cluster_size.!pip install bertopic first.