Skip to main content

SCPTM — Structural Contextual Probabilistic Topic Model

A topic model technique that combines heterogeneous graph neural networks over syntactic dependency graphs with contextual SBERT word embeddings.


Architecture overview

Documents ──SBERT──► doc embeddings ┐
                                    ├─► HeteroConv/GAT ──► μ, logσ² ──► z ──► θ (topic mix)
Vocabulary ──SBERT──► word embeddings ┘                                          │
                           │                                                     │
                     K-means init                                                 │
                           │                                                     ▼
                     topic_embeddings ──cosine/T──► β (topic×vocab) ──θ·β──► recon loss

Installation

From PyPI:

pip install scptm

# With comparison benchmarks (BERTopic, CTM)
pip install "scptm[benchmark]"

# All optional dependencies
pip install "scptm[full]"

For development (editable install):

git clone https://github.com/a-meneghini/scptm.git
cd scptm
pip install -e ".[dev]"

Required spaCy models:

python -m spacy download en_core_web_sm   # English
python -m spacy download it_core_news_sm  # Italian

Note on torch-geometric: SCPTM depends on PyTorch Geometric (torch-geometric>=2.4), which is available on standard PyPI. If you need CUDA-accelerated graph operations, install the CUDA-specific wheel first following the official PyG installation guide before installing SCPTM. CPU-only installs work out of the box with pip install scptm.


Quick start

from scptm import SCPTM, SCPTMConfig

documents = [
    "Machine learning is transforming healthcare diagnostics.",
    "Deep neural networks achieve state-of-the-art performance in NLP.",
    "Climate change accelerates biodiversity loss in tropical regions.",
    # ...  more
]

# One-liner with defaults (10 topics, filtered syntax graph, English)
model = SCPTM()
theta = model.fit_transform(documents)    # (n_docs, K) topic mixtures

# Topic overview
model.get_topic_info(top_k=10)

# Out-of-sample inference
new_theta = model.transform(["A new text about politics in development countries"])

# Evaluation
metrics = model.evaluate()
print(metrics)
# → {'npmi_coherence': 0.12, 'topic_diversity': 0.87, ...}

# Save and reload your model
model.save("my_model.pkl")
model2 = SCPTM.load("my_model.pkl")

Configuration

All hyper-parameters are defined in SCPTMConfig. Passing keyword arguments to SCPTM() directly is a shorthand for SCPTM(config=SCPTMConfig(...)).

from scptm import SCPTM, SCPTMConfig

cfg = SCPTMConfig(
    # ── Model ──────────────────────────────────────────────────────────────
    num_topics          = 10,
    hidden_channels     = 64,       # GNN/MLP hidden size per attention head

    # ── Graph ──────────────────────────────────────────────────────────────
    graph_mode          = "filtered",
    # "none"      — no graph
    # "no_syntax" — doc-word edges only, no word-word edges
    # "full_dep"  — all content dependency types
    # "filtered"  — dependencies that connect content words (nsubj, obj/dobj, amod, nmod, compound, conj, xcomp) only (default, recommended)

    # ── Training ───────────────────────────────────────────────────────────
    epochs              = 50,
    lr                  = 5e-3,
    batch_size          = 256,
    kl_max              = 1.0,
    kl_warmup_epochs    = 20,
    kl_strategy         = "linear",   # "linear" | "cyclical" | "constant"
    free_bits           = 0.1,        # per-dimension KL floor
    n_mc_samples        = 1,          # >1 enables MC uncertainty report

    # ── Beta ───────────────────────────────────────────────────────────────
    beta_temperature    = 0.1,        # softmax sharpening (lower = sharper)
    beta_refresh_epochs = 5,          # recompute contextual beta every N epochs
    max_ctx_occurrences = 50,         # max SBERT contexts stored per word

    # ── Regularisation ─────────────────────────────────────────────────────
    topic_diversity_weight = 0.1,     # cosine repulsion between topic embeddings

    # ── Corpus ─────────────────────────────────────────────────────────────
    lang                = "eng",      # "eng" | "ita"
    min_df              = 5,
    max_features        = 15_000,
    apply_chunking      = True,
    max_chunk_chars     = 800,

    # ── Keyword extraction ─────────────────────────────────────────────────
    bow_normalization   = "tf",       # "none" | "tf" | "log1p"
    keyword_method      = "cosine",   # "cosine" | "ctfidf"

    # ── Hardware ───────────────────────────────────────────────────────────
    use_mixed_precision = True,       # AMP on CUDA
    use_neighbor_sampling = False,    # NeighborLoader for large corpora

    # ── Reproducibility ────────────────────────────────────────────────────
    random_state        = 42,
)

model = SCPTM(config=cfg)

Parse and embedding cache

spaCy lemmatisation, dependency parsing, and contextual SBERT embeddings are highly impacting when working on large corpora. Passing edge_cache_path persists all of them to a single pickle file and skips re-computation on subsequent runs.

# First run — parses corpus, encodes contextual embeddings, writes cache
theta = model.fit_transform(documents, edge_cache_path="corpus.pkl")

# Subsequent runs — skips spaCy and SBERT contextual pass entirely
model2 = SCPTM(config=cfg)
theta2 = model2.fit_transform(documents, edge_cache_path="corpus.pkl")

The cache stores: vocabulary, BoW matrix, dependency edge lists, and the per-word contextual SBERT embeddings. If the corpus size or vocabulary changes, the stale cache is detected automatically and rebuilt.


Keyword extraction methods

# Set globally
cfg = SCPTMConfig(keyword_method="cosine")

# Or override per call
model.get_topic_info(top_k=10, method="cosine")
model.get_topic_info(top_k=10, method="ctfidf")
model.get_topics_dict(top_k=5)          # returns single words + bigrams/trigrams
Method Ranks by Best for
"cosine" (default) Cosine similarity between topic embedding and context-pooled word embedding Semantically central terms
"ctfidf" Class-based TF-IDF (each topic treated as a document class) similar to BERTopic Useful to retrieve discriminative terms

Visualisations

model.plot_training()     # loss + KL annealing + NPMI + diversity curves
model.visualize_3d()      # interactive Plotly 3D semantic constellation
model.visualize_2d()      # high-res PNG for papers (300 dpi)

Metrics

NPMI coherence measures how often a topic's top words co-occur in documents.

Topic diversity = fraction of unique words across all topic top-word lists. Score in [0, 1]


Citation

@software{meneghini2026scptm,
  author  = {Meneghini, Alessandro},
  title   = {{SCPTM}: Structural Contextual Probabilistic Topic Model},
  year    = {2026},
  url     = {https://github.com/a-meneghini/scptm}
}

License

MIT

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

scptm-0.2.1.tar.gz (51.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

scptm-0.2.1-py3-none-any.whl (53.6 kB view details)

Uploaded Python 3

File details

Details for the file scptm-0.2.1.tar.gz.

File metadata

  • Download URL: scptm-0.2.1.tar.gz
  • Upload date:
  • Size: 51.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.13.2

File hashes

Hashes for scptm-0.2.1.tar.gz
Algorithm Hash digest
SHA256 1422cc8a6ced33284de048b9d3cb01057739805f3e36846d1e79aec8809303ab
MD5 4c85c450638828f40399957417d1374a
BLAKE2b-256 c4344778254d4ec5c9ed584516b0821affd52203067e40e1a42cf4824fdcd2b8

See more details on using hashes here.

File details

Details for the file scptm-0.2.1-py3-none-any.whl.

File metadata

  • Download URL: scptm-0.2.1-py3-none-any.whl
  • Upload date:
  • Size: 53.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.13.2

File hashes

Hashes for scptm-0.2.1-py3-none-any.whl
Algorithm Hash digest
SHA256 6763c9fd97dc706b0a4272846364c69645734d2fdc3170ab1a2056a229783e53
MD5 09d903b66896efef04bd1bfd6094b63e
BLAKE2b-256 3afef5c5663770a20a7afce88bdfb666e1c6ccea00c70801dc99cbb2a9605990

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.2.1 This release

2 files

0.2.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page