How to split a meeting transcript into topics and extract keywords
The short answer
Embed each speaker turn with a sentence-embedding model, compare each turn with the ones before it, and start a new topic where the similarity drops. Then run KeyBERT on each section to get keywords you can use as a heading. It runs on a laptop with all-MiniLM-L6-v2 and needs no labelled data.
Step by step
- 1
Clean the transcript
One line per speaker turn. Drop filler-only turns like 'yeah' or 'okay' so they don't break topics apart.
- 2
Embed each turn
all-MiniLM-L6-v2 turns each turn into a 384-dimension vector. It truncates text longer than 256 word pieces, so split long turns.
- 3
Find the topic changes
Compare each turn with the average of the previous few. Where similarity falls below a threshold, start a new section.
- 4
Label each section
Run KeyBERT on the section's text to pull the keyphrases that best describe it.
- 5
Tune on real meetings
Adjust the window and threshold on a few of your own transcripts until the sections match how a person would split them.
When to use something else
- Many meetings at once: BERTopic clusters turns from many transcripts into shared topics.
- A fixed list of topics (pricing, hiring, security): zero-shot or fine-tuned text classification labels each turn directly.
- Short summaries per section: pass each section to a summarisation model or an LLM instead of using keywords.
Split by topic and label each section
pip install sentence-transformers keybert. Both download their model from Hugging Face on first run.
import numpy as np
from keybert import KeyBERT
from sentence_transformers import SentenceTransformer
turns = [
"Let's start with the Q4 launch date. Marketing wants the 18th of November.",
"Engineering can hit the 18th if the pricing page is frozen by Friday.",
"Then we freeze pricing on Friday and keep the launch on the 18th.",
"Next, hiring. We still have two open backend roles.",
"Recruiting has three candidates in final interviews for the backend roles.",
"Let's aim to send offers for both roles before the end of the month.",
]
model = SentenceTransformer("sentence-transformers/all-MiniLM-L6-v2")
emb = model.encode(turns, normalize_embeddings=True)
WINDOW, THRESHOLD = 2, 0.3
sections, current = [], [0]
for i in range(1, len(turns)):
context = emb[max(0, i - WINDOW):i].mean(axis=0)
similarity = float(emb[i] @ context / np.linalg.norm(context))
if similarity < THRESHOLD:
sections.append(current)
current = []
current.append(i)
sections.append(current)
kw_model = KeyBERT(model=model)
for n, idx in enumerate(sections, 1):
text = " ".join(turns[i] for i in idx)
keywords = kw_model.extract_keywords(text, keyphrase_ngram_range=(1, 2), stop_words="english", top_n=3)
print(f"Topic {n}: turns {idx[0]}-{idx[-1]}", [k for k, _ in keywords])Common mistakes
- Using one threshold for every kind of meeting; standups and workshops change topic at very different rates.
- Leaving filler turns in, which creates false topic breaks.
- Feeding very long turns into a model that truncates them.
Acta's transcripts come with speaker names and timestamps, and its reports already group a meeting into decisions, next steps and action items, so you can start from clean, labelled text.
Try Acta free for 15 daysQuestions
What is topic segmentation?
Splitting a long text, like a meeting transcript, into sections that each cover one topic, so you can label, search or summarise them separately.
What is KeyBERT?
An open-source Python library (MIT licence) that finds the keywords and keyphrases most similar to a document, using BERT-style embeddings and cosine similarity.
Do I need a GPU?
No. all-MiniLM-L6-v2 is small enough to embed a one-hour meeting on a laptop CPU in seconds to a few minutes.