Skip to content

Latent Dirichlet Allocation

LDA is one of the most well known and widely used topic models. It is a bag-of-words topic model that models topics as word distributions drawn from a Dirichlet prior, and documents as mixtures of topics, with the mixture weights also having a Dirichlet prior.

Figure 1: Plate notation of LDA..
Figure from Blei et al. (2003)

Note

The Turftopic implementation of LDA is a thin wrapper around the scikit-learn implementation mainly for convenience and benchmarking. We recommend that you refer to their documentation for further details.

Usage

from turftopic import LDA

documents = [...]

model = LDA(10)
doc_topic_matrix = model.fit_transform(documents)
model.print_topics()

Tip

Since LDA is a BoW topic model, it does not handle stop words particularly well. We recommend that you use a vectorizer that removes stop words or all too frequent words:

from turftopic import LDA
from sklearn.feature_extraction.text import CountVectorizer

model = LDA(10, vectorizer=CountVectorizer(min_df=10, stop_words="english", max_df=0.8))

Citation

Please cite the authors of the paper, and Turftopic when using LDA:

@article{Blei2003,
    author = {Blei, David M. and Ng, Andrew Y. and Jordan, Michael I.},
    title = {Latent dirichlet allocation},
    year = {2003},
    issue_date = {3/1/2003},
    publisher = {JMLR.org},
    volume = {3},
    number = {null},
    issn = {1532-4435},
    abstract = {We describe latent Dirichlet allocation (LDA), a generative probabilistic model for collections of discrete data such as text corpora. LDA is a three-level hierarchical Bayesian model, in which each item of a collection is modeled as a finite mixture over an underlying set of topics. Each topic is, in turn, modeled as an infinite mixture over an underlying set of topic probabilities. In the context of text modeling, the topic probabilities provide an explicit representation of a document. We present efficient approximate inference techniques based on variational methods and an EM algorithm for empirical Bayes parameter estimation. We report results in document modeling, text classification, and collaborative filtering, comparing to a mixture of unigrams model and the probabilistic LSI model.},
    journal = {J. Mach. Learn. Res.},
    month = mar,
    pages = {993–1022},
    numpages = {30}
}

@article{
  Kardos2025,
  title = {Turftopic: Topic Modelling with Contextual Representations from Sentence Transformers},
  doi = {10.21105/joss.08183},
  url = {https://doi.org/10.21105/joss.08183},
  year = {2025},
  publisher = {The Open Journal},
  volume = {10},
  number = {111},
  pages = {8183},
  author = {Kardos, Márton and Enevoldsen, Kenneth C. and Kostkan, Jan and Kristensen-McLachlan, Ross Deans and Rocca, Roberta},
  journal = {Journal of Open Source Software} 
}

API Reference

turftopic.models.bow.lda.LDA

Bases: ContextualModel

Turftopic wrapper around sklearns's implementation of Latent Dirichlet Allocation. Note that LDA is a Bag-of-words model and NOT a contextual model and uses static word representations! The encoder and passed embeddings will be ignored. For interpretation of hyperparameters refer to sklearn's documentation

from turftopic import LDA
corpus: list[str] = ["some text", "more text", ...]

model = LDA(10).fit(corpus)
model.print_topics()

Parameters:

Name Type Description Default
n_components int

Number of topics.

required
encoder

Ignored.

None
vectorizer Optional[CountVectorizer]

Vectorizer used for term extraction. Can be used to prune or filter the vocabulary.

None
random_state Optional[int]

Random state to use so that results are exactly reproducible.

None
trf_kwargs

Ignored.

None
encode_kwargs

Ignored.

None
Source code in turftopic/models/bow/lda.py
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
class LDA(ContextualModel):
    """Turftopic wrapper around sklearns's implementation of Latent Dirichlet Allocation.
    Note that LDA is a Bag-of-words model and NOT a contextual model and uses static word representations!
    The encoder and passed embeddings will be ignored.
    For interpretation of hyperparameters refer to [sklearn's documentation](https://scikit-learn.org/stable/modules/generated/sklearn.decomposition.LatentDirichletAllocation.html)

    ```python
    from turftopic import LDA
    corpus: list[str] = ["some text", "more text", ...]

    model = LDA(10).fit(corpus)
    model.print_topics()
    ```

    Parameters
    ----------
    n_components: int
        Number of topics.
    encoder: None
        Ignored.
    vectorizer: CountVectorizer, default None
        Vectorizer used for term extraction.
        Can be used to prune or filter the vocabulary.
    random_state: int, default None
        Random state to use so that results are exactly reproducible.
    trf_kwargs: dict, default None
        Ignored.
    encode_kwargs: dict, default None
        Ignored.

    """

    def __init__(
        self,
        n_components: int,
        encoder=None,
        vectorizer: Optional[CountVectorizer] = None,
        random_state: Optional[int] = None,
        doc_topic_prior=None,
        topic_word_prior=None,
        learning_method="batch",
        learning_decay=0.7,
        learning_offset=10.0,
        max_iter=10,
        batch_size=128,
        evaluate_every=-1,
        total_samples=1000000.0,
        perp_tol=0.1,
        mean_change_tol=0.001,
        max_doc_update_iter=100,
        n_jobs=None,
        verbose=0,
        trf_kwargs=None,
        encode_kwargs=None,
    ):
        self.n_components = n_components
        self.encoder = encoder
        self.encode_kwargs = encode_kwargs
        self.trf_kwargs = trf_kwargs
        self.random_state = random_state
        if vectorizer is None:
            self.vectorizer = default_vectorizer()
        else:
            self.vectorizer = vectorizer
        self.doc_topic_prior = doc_topic_prior
        self.topic_word_prior = topic_word_prior
        self.learning_method = learning_method
        self.learning_decay = learning_decay
        self.learning_offset = learning_offset
        self.max_iter = max_iter
        self.batch_size = batch_size
        self.evaluate_every = evaluate_every
        self.total_samples = total_samples
        self.perp_tol = perp_tol
        self.mean_change_tol = mean_change_tol
        self.max_doc_update_iter = max_doc_update_iter
        self.n_jobs = n_jobs
        self.verbose = verbose

    @property
    def components_(self):
        return self.model_.components_

    def fit_transform(
        self,
        raw_documents,
        y=None,
        embeddings: Optional[np.ndarray] = None,
    ) -> np.ndarray:
        console = Console()
        with console.status("Fitting model") as status:
            status.update("Extracting terms.")
            self.doc_term_matrix = self.vectorizer.fit_transform(raw_documents)
            console.log("Term extraction done.")
            status.update("Fitting the model")
            self.model_ = LatentDirichletAllocation(
                self.n_components,
                random_state=self.random_state,
                doc_topic_prior=self.doc_topic_prior,
                topic_word_prior=self.topic_word_prior,
                learning_method=self.learning_method,
                learning_decay=self.learning_decay,
                learning_offset=self.learning_offset,
                max_iter=self.max_iter,
                batch_size=self.batch_size,
                evaluate_every=self.evaluate_every,
                total_samples=self.total_samples,
                perp_tol=self.perp_tol,
                mean_change_tol=self.mean_change_tol,
                max_doc_update_iter=self.max_doc_update_iter,
                n_jobs=self.n_jobs,
                verbose=self.verbose,
            )
            self.doc_topic_matrix = self.model_.fit_transform(
                self.doc_term_matrix
            )
            console.log("Model fitting done.")
            self.top_documents = self.get_top_documents(
                raw_documents, document_topic_matrix=self.doc_topic_matrix
            )
        return self.doc_topic_matrix

    def transform(
        self, raw_documents, embeddings: Optional[np.ndarray] = None
    ) -> np.ndarray:
        """Infers topic importances for new documents based on a fitted model.

        Parameters
        ----------
        raw_documents: iterable of str
            Documents to fit the model on.
        embeddings: None
            Ignored.

        Returns
        -------
        ndarray of shape (n_dimensions, n_topics)
            Document-topic matrix.
        """
        doc_term_matrix = self.vectorizer.transform(raw_documents)
        return self.model_.transform(doc_term_matrix)

transform(raw_documents, embeddings=None)

Infers topic importances for new documents based on a fitted model.

Parameters:

Name Type Description Default
raw_documents

Documents to fit the model on.

required
embeddings Optional[ndarray]

Ignored.

None

Returns:

Type Description
ndarray of shape (n_dimensions, n_topics)

Document-topic matrix.

Source code in turftopic/models/bow/lda.py
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
def transform(
    self, raw_documents, embeddings: Optional[np.ndarray] = None
) -> np.ndarray:
    """Infers topic importances for new documents based on a fitted model.

    Parameters
    ----------
    raw_documents: iterable of str
        Documents to fit the model on.
    embeddings: None
        Ignored.

    Returns
    -------
    ndarray of shape (n_dimensions, n_topics)
        Document-topic matrix.
    """
    doc_term_matrix = self.vectorizer.transform(raw_documents)
    return self.model_.transform(doc_term_matrix)