RUNLOCALAIv38
->Will it run?Best GPUCompareTroubleshootStartLearnPulseModelsHardwareToolsBench
Run check
RUNLOCALAI

Independently operated catalog for local-AI hardware and software. Hand-written verdicts. Source-cited claims. Reproducible commands when we have them.

OP·Eruo Fredoline
DIR
  • Models
  • Hardware
  • Tools
  • Benchmarks
TOOLS
  • Will it run?
  • Compare hardware
  • Cost vs cloud
  • Choose my GPU
  • Prompting kits
  • Quick answers
REF
  • All buyer guides
  • Learn local AI
  • Methodology
  • Glossary
  • Errors KB
  • Trust
EDITOR
  • About
  • Author
  • How we make money
  • Editorial policy
  • Contact
LEGAL
  • Privacy
  • Terms
  • Sitemap
MAIL · MONTHLY DIGEST
Get monthly local AI changes
Monthly recap. No spam.
DISCLOSURE

Some links on this site are affiliate links (Amazon Associates and other first-class retailers). When you buy through them, we earn a small commission at no extra cost to you. Affiliate links do not influence our verdicts — there are cards we rate highly that we don't have affiliate relationships with, and cards that sell well that we refuse to recommend. Read more →

© 2026 runlocalai.coIndependently operated
RUNLOCALAI · v38
  1. >
  2. Home
  3. /Learn
  4. /Courses
  5. /Advanced NLP with Local Models
  6. /Ch. 13
Advanced NLP with Local Models

13. Topic Modeling

Chapter 13 of 18 · 20 min
KEY INSIGHT

Topic modeling serves both exploratory analysis and document organization. LDA provides interpretable probabilistic topics but struggles with polysemy. Neural approaches like BERTopic capture semantic similarity better but may produce less interpretable clusters. Choose based on whether you need interpretability or semantic accuracy.

Topic modeling discovers latent themes in document collections without explicit labels. Latent Dirichlet Allocation (LDA) and neural approaches both see active use with local models.

LDA with Gensim

Traditional LDA remains useful for interpretable topic discovery:

from gensim import corpora
from gensim.models import LdaModel
import nltk
from nltk.corpus import stopwords
from nltk.tokenize import word_tokenize

class LDATopicModeler:
    def __init__(self, num_topics=10):
        self.num_topics = num_topics
        self.dictionary = None
        self.model = None
        self.stop_words = set(stopwords.words('english'))
        self.stop_words.update(['would', 'could', 'also', 'one', 'two'])
    
    def preprocess(self, documents: list[str]) -> list[list[str]]:
        """Tokenize and filter documents."""
        processed = []
        for doc in documents:
            tokens = word_tokenize(doc.lower())
            tokens = [t for t in tokens if t.isalpha() and t not in self.stop_words and len(t) > 3]
            processed.append(tokens)
        return processed
    
    def train(self, documents: list[str]):
        tokenized = self.preprocess(documents)
        
        self.dictionary = corpora.Dictionary(tokenized)
        self.dictionary.filter_extremes(no_below=5, no_above=0.5)
        
        corpus = [self.dictionary.doc2bow(doc) for doc in tokenized]
        
        self.model = LdaModel(
            corpus=corpus,
            id2word=self.dictionary,
            num_topics=self.num_topics,
            passes=10,
            alpha='auto',
            eta='auto',
            random_state=42
        )
    
    def get_topics(self) -> list[tuple[str, float]]:
        """Return all topics with top words."""
        topics = []
        for topic_id in range(self.num_topics):
            words = self.model.show_topic(topic_id, topn=10)
            topic_str = ", ".join([f"{word}" for word, _ in words])
            topics.append((f"Topic {topic_id}", topic_str))
        return topics
    
    def infer_topics(self, document: str, num_words: int = 5) -> list[tuple[str, float]]:
        """Get topic distribution for a new document."""
        if not self.model:
            raise ValueError("Model not trained")
        
        tokens = self.preprocess([document])[0]
        bow = self.dictionary.doc2bow(tokens)
        
        topic_dist = self.model.get_document_topics(bow)
        return [(f"Topic {tid}", prob) for tid, prob in topic_dist[:num_words]]

BERTopic for Neural Topic Modeling

BERTopic leverages sentence transformers for semantic clustering:

from bertopic import BERTopic
from sentence_transformers import SentenceTransformer
from umap import UMAP
from hdbscan import HDBSCAN

class NeuralTopicModeler:
    def __init__(self, num_topics=10):
        embedding_model = SentenceTransformer("all-MiniLM-L6-v2")
        
        umap_model = UMAP(n_neighbors=15, n_components=5, min_dist=0.0, metric='cosine')
        hdbscan_model = HDBSCAN(min_cluster_size=15, metric='euclidean', prediction_data=True)
        
        self.model = BERTopic(
            embedding_model=embedding_model,
            umap_model=umap_model,
            hdbscan_model=hdbscan_model,
            nr_topics=num_topics,
            verbose=True
        )
    
    def fit_transform(self, documents: list[str]) -> tuple:
        topics, probs = self.model.fit_transform(documents)
        return topics, probs
    
    def get_topic_info(self) -> dict:
        return self.model.get_topic_info()
    
    def find_similar_topics(self, query: str, top_n: int = 5) -> list[dict]:
        """Find topics similar to a query description."""
        similar_topics, similarity = self.model.find_topics(query, top_n=top_n)
        return [
            {"topic_id": tid, "similarity": sim, "keywords": self._get_topic_keywords(tid)}
            for tid, sim in zip(similar_topics, similarity)
        ]
    
    def _get_topic_keywords(self, topic_id: int) -> list[str]:
        if topic_id == -1:
            return ["outlier", "noise"]
        topic_words = self.model.get_topic(topic_id)
        return [word for word, _ in topic_words[:10]]
EXERCISE

Implement a dynamic topic model that tracks how topics evolve over time in a document collection. Visualize topic prevalence changes and identify emerging or declining themes.

← Chapter 12
Aspect-Based Sentiment
Chapter 14 →
Text Clustering