Short text¶
Tweets, headlines, search queries, and open-ended survey answers break standard LDA: documents are too short for a stable mixture of topics to be estimated. topica has two models built for this regime.
GSDMM — one topic per document¶
The Gibbs Sampling Dirichlet Multinomial Mixture, a.k.a. the Movie Group
Process (Yin & Wang 2014), assumes each short document belongs to a single
topic. You give it an upper bound K, and it lets clusters shrink during
sampling, so it effectively infers the number of topics.
GSDMM is for genuinely short text. On long, multi-topic documents (news
articles, press releases, speeches) it neither infers a topic count below your
cap nor honours its one-topic-per-document assumption; reach for LDA or STM
there instead, or reduce each document to a short field (a title or first
sentence). fit warns you when the average document is long. It is also
single-threaded by design (its cluster-count discovery is inherently
sequential), so a large corpus fits slowly — subsample while you explore, and
pass fit(..., verbose=True) to print per-sweep progress (iteration, cluster
count, log-likelihood) to stderr so a long fit does not look hung.
To report which topics load on a covariate — the "which topics do Democrats vs Republicans write?" table — cross the assignment against a metadata column:
topica.inspect.topic_crosstab(model, corpus, "party") # counts
topica.inspect.topic_crosstab(model, corpus, "party", normalize="index") # row %
It uses doc_cluster for GSDMM (and any hard-clustering model) and the dominant
doc_topic topic for mixed-membership models; corpus.metadata stays row-aligned
through pruning, so no manual realignment is needed.
Two caveats to read the discovered count honestly. First, with the default
alpha=0.1 an emptied cluster keeps alpha-proportional mass and can revive, so
clusters are not permanently pruned (that holds only for the paper's alpha=0
variant). Second, the number GSDMM settles on is sensitive to the K upper
bound, to beta, and to how separable your texts are: on clean, well-separated
short text it collapses cleanly toward the true count, but on messy overlapping
text it can sit near your K cap. Treat the discovered count as an
upper-bound-conditioned estimate, not a guaranteed recovery of the true K. If
the fit uses close to all K clusters, topica warns you to raise K and
refit until the count stabilises below the cap.
import topica
model = topica.GSDMM(num_topics=30, seed=1) # 30 is the MAX number of clusters
model.fit(short_docs, iters=30)
print(model.num_topics, "clusters used") # usually far fewer than 30
model.top_words(8)
model.doc_cluster # one cluster id per document
topic_word and doc_topic cover only the non-empty clusters; doc_cluster
gives the hard assignment, since GSDMM places each document in exactly one group.
Prefer doc_cluster for the label: doc_topic is an in-sample soft score that
over-peaks, and its argmax can disagree with doc_cluster on a small fraction
of documents.
Because each document commits to one cluster, GSDMM's top-word lists tend to be
less diverse than a mixed-membership model's on the same overlapping corpus
(shared words concentrate in one cluster rather than spreading across several).
That is expected, not a defect, but it is worth knowing when you compare top-word
diversity across models. Watch, too, for very small clusters (one or two
documents): their top words, FREX/exclusivity, and coherence are computed from
almost no data and look deceptively strong, so topica warns when a fit produces
them — treat clusters that small as noise or merge them. Documents left empty
after vocabulary pruning are dropped from the fit; corpus.kept_indices maps the
rows of doc_topic/doc_cluster back to your original documents.
PT — pseudo-document aggregation¶
The Pseudo-document Topic model (Zuo et al. 2016) aggregates short texts into a smaller set of pseudo-documents, recovering the longer-document statistics LDA needs while still mixing topics within a text.
BTM — biterm co-occurrence¶
The Biterm Topic Model (Yan, Guo, Lan & Cheng 2013) attacks short-text sparsity
from the word side. Instead of estimating a topic mixture per document (too few
words to pin down), it models the corpus as a bag of biterms — unordered word
pairs co-occurring within a window — and learns one global topic distribution
plus per-topic word distributions from those co-occurrences. Both words of a
biterm are drawn from the same topic, so the topic-word distributions absorb the
co-occurrence signal directly. Document topics are read back out afterward by
summing each document's biterms (p(z|d) = Σ_b p(z|b) p(b|d)).
model = topica.BTM(num_topics=20, seed=1) # alpha defaults to 50/k
model.fit(short_docs, iters=1000)
model.topic_word # per-topic word distributions
model.theta # the global topic distribution p(z)
scores = model.transform(new_docs) # document topics for held-out texts
window (default 15) sets how far apart two words may be to form a biterm;
background=True reserves topic 0 for common words (the empirical word
distribution), which can sharpen the remaining topics. Validated against the
reference R BTM package (see the validation record).
Which to use¶
GSDMMwhen each short text is plausibly about one thing (most tweets, most survey answers) and you want the model to find how many groups there are.PTwhen texts may still blend a few topics and you want LDA-style mixed membership that holds up on short texts.BTMwhen documents are very short and you want the topics driven by corpus-wide word co-occurrence rather than any per-document mixture — the standard choice for tweet-length text.
All three feed the same diagnostics and validation as every other model.