Skip to content

Support pre-tokenized documents to avoid redundant tokenization across pipeline stages #2507

Description

@pidefrem

Feature request

BERTopic re-tokenizes documents from scratch at every stage of the pipeline. Add a tokenized_documents parameter (following the existing embeddings pattern) that threads through all pipeline methods:

# User pre-tokenizes once with their expensive tokenizer
tokenized = [my_spacy_tokenizer(doc) for doc in documents]

# Pass to BERTopic — vectorizer receives token lists, skips tokenization
topic_model.fit_transform(documents, tokenized_documents=tokenized, embeddings=embeddings)

# Same for downstream methods
topic_model.update_topics(documents, tokenized_documents=tokenized)
topic_model.reduce_outliers(documents, topics, tokenized_documents=tokenized)
topic_model.hierarchical_topics(documents, tokenized_documents=tokenized)

Note: This touches 13 method signatures (10 public + 3 internal). I'd recommend agreeing on the API surface before implementing.

Motivation

When using an expensive tokenizer (e.g., spaCy's transformer-based pipeline with lemmatization, or a custom CJK tokenizer), the same costly tokenization runs redundantly across 7+ methods:

  • fit_transform_c_tf_idf → vectorizer
  • _extract_representative_docs → vectorizer
  • reduce_outliers (distributions + c-tf-idf strategies) → vectorizer
  • approximate_distribution → analyzer
  • hierarchical_topics → vectorizer
  • update_topics → vectorizer
  • partial_fit → vectorizer

BERTopic already has an embeddings parameter that lets users pre-compute and reuse embeddings across calls. There is no equivalent for tokenization — the word "tokenized" does not appear once in _bertopic.py.

Use cases: multilingual pipelines with expensive tokenizers (spaCy transformer models), custom domain-specific tokenizers (medical, legal), consistency across pipeline stages, performance.

Your contribution

I can submit a PR that adds tokenized_documents to 13 methods. scikit-learn's CountVectorizer already supports pre-tokenized input when analyzer is a callable — no sklearn changes needed. Defaults to None — all existing behavior unchanged.

Given the size of this change, I've been prototyping it in my fork, but I'd rather agree the API surface with you (here or in a Discussion) before turning it into a PR.


Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions