Versjon 3.0

Deterministisk kategorisering
av åpne surveysvar

On-Ramp Tematisk Analyse v3 erstatter manuell koding og ikke-reproduserbar LLM-koding med en matematisk pipeline som garanterer 100 % identiske resultater ved gjentatte kjøringer på samme data.

7-stegs pipeline
1
Segmentering
WtpSplit / SaT
2
Embedding
E5-large-instruct
3
Dimensjonsreduksjon
UMAP
4
Clustering
HDBSCAN
5
Hierarkisk gruppering
Agglomerativ
6
Navngiving
Claude API
7
Klassifisering
Cosine similarity
100 %
Reproduserbarhet
~2 min
Per 1 000 svar
100+
Språk støttet
7
State-of-the-art steg

Koding av åpne svar er et uløst problem

Åpne surveysvar inneholder rik innsikt — men å kategorisere dem har alltid krevd vanskelige kompromisser mellom hastighet, konsistens og kostnad.

Manuell koding

  • Inter-coder reliability typisk 70–85 % — to kodere gir ulike resultater
  • Timer eller dager for selv moderate datasett (1 000+ svar)
  • Koder-bias, tretthetseffekter, inkonsistent bruk av kodebok
  • Lineær kostnadsskalering — dobbelt så mange svar betyr dobbel tid
  • Umulig å reprodusere — forskerens vurdering varierer over tid

LLM-basert koding (AI-analyser)

  • ~0 % reproduserbarhet — temperaturvariasjon gir forskjellige resultater hver gang
  • Black box — ingen innsikt i hvorfor et svar fikk en gitt kategori
  • Hallusinering — LLM-er kan oppfinne kategorier eller feiltolke kontekst
  • Vilkårlig multi-koding — ingen prinsipielt grunnlag for å tilordne flere kategorier
  • Ikke-publiserbar — resultater uten reproduserbarhet mangler vitenskapelig grunnlag

7 steg fra råtekst til hierarkiske kategorier

Hver komponent er best-in-class fra nyeste forskning (2026). Hele pipelinen — med unntak av navngiving — er matematisk deterministisk.

1
Segmentering
Nytt i v3

Sammensatte svar splittes i meningsbærende segmenter før embedding — slik at hvert tema fanges separat.

WtpSplit / SaTEMNLP 202485 språk

Problemet: Et svar som «billig og god service og anbefalt av venn» inneholder tre distinkte temaer. Å embedde hele setningen som én vektor gir en «gjennomsnittsmening» som ikke fanger enkelt-temaene.

Bransjestatus: De fleste verktøy for automatisert temaanalyse — inkludert Qualtrics Text iQ, Caplena, Medallia, Canvs AI, MonkeyLearn, Kapiche og Chattermill — behandler hele svaret som én enhet og bruker multi-label-klassifisering på responsnivå. Ingen av disse segmenterer svar på sub-setningsnivå før embedding. Det nærmeste unntaket er Thematic (getthematic.com), som splitter på setningsnivå — men stopper der. Vår pipeline går et steg videre: vi segmenterer innenfor setninger ved hjelp av norske diskursmarkører, slik at «billig og god service» splittes til to separate tema-enheter. Selv nyere akademisk forskning (arXiv 2508.19836, 2025) beskriver setningssegmentering som en fremtidig retning, ikke nåværende praksis. Vår tilnærming — sub-setningssegmentering med diskursmarkør-splitting før embedding — går lenger enn noe annet verktøy i markedet og gir prinsipielt grunnlag for multi-koding med full transparens per segment.

Løsningen: WtpSplit / Segment Any Text (Minixhofer et al., EMNLP 2024) er state-of-the-art for setningssegmentering — slår alle baselines inkludert GPT-4. Modellen sat-3l-sm (~50 MB, 3 lag) er optimert for hastighet, MIT-lisensiert, og støtter 85 språk inkludert norsk.

I tillegg: Splitting på norske diskursmarkører som signaliserer temaoverganger — koordinerende («og», «eller», «samt»), adversative («men»), additive («i tillegg», «dessuten») og kausale («fordi»). Lingvistisk forankret i Halliday & Hasan (1976) og Mann & Thompson (1988).

Resultat: «billig og god service og anbefalt av venn» → ["billig", "god service", "anbefalt av venn"] → 3 separate vektorer → 3 separate kategoritilordninger.

Python-moduler i dette steget
wtpsplit SaT("sat-3l-sm") torch re (regex)

WtpSplit laster en 3-lags neural nettverksmodell (~50 MB) via PyTorch. Modellen predikerer segmentgrenser karakter-for-karakter — dette er ikke regelbasert splitting, men en trent transformer som har lært segmenteringsmønstre fra 85 språk. Diskursmarkør-splittingen bruker kompilerte regex-mønstre som supplement.

2
Embedding

Konvertering av tekst til semantiske vektorer i 1024-dimensjonalt rom, med instruksjonsbasert domenetilpasning.

E5-large-instruct560M params1024-dim

Modell: intfloat/multilingual-e5-large-instruct (Microsoft Research) — 560M parametere, 24 transformer-lag, trent på over 1 milliard tekstpar på 100+ språk. Rangert blant toppmodellene på MTEB for flerspråklige oppgaver.

Hva skjer internt — steg for steg

Når et tekstsegment som «god kundeservice» sendes inn, skjer følgende prosessering:

Råtekst "god kundeservice"
Instruksjons-prefix Instruct: Classify...
Tokenizer (WordPiece) ["god", "kunde", "##service"]
Token IDs [3847, 12903, 7421]
+ Attention mask [1, 1, 1, 0, 0...]
24 Transformer-lag 560M parametere
Mean pooling Snitt over tokens
L2-normalisering [0.023, -0.041, ...]

Tokenizeren (XLM-RoBERTa WordPiece) splitter tekst i subword-tokens. Norske sammensatte ord som «kundeservice» deles i kjente sub-enheter («kunde» + «##service»). Hvert token konverteres til en heltalls-ID fra et vokabular på ~250 000 tokens. En attention mask indikerer hvilke posisjoner som er reelle tokens vs. padding.

Transformer-lagene: Token-representasjonene passerer gjennom 24 lag med multi-head self-attention. I hvert lag beregnes attention-vekter mellom alle token-par — slik at «god» kan «se» konteksten fra «kundeservice» og omvendt. Hvert lag har ~23M parametere (attention-matriser, feed-forward nettverk, layer normalization).

Mean pooling + L2-normalisering: Output fra siste transformer-lag er én vektor per token. Mean pooling beregner gjennomsnittsvektoren over alle tokens (vektet med attention mask) → én 1024-dimensjonal vektor per segment. L2-normalisering skalerer vektoren til enhetslengde — nødvendig for at cosine similarity skal fungere korrekt.

Instruksjonsformat: Hvert segment prefixes med Instruct: Classify this Norwegian survey response by theme Question: {spørsmålet}\nQuery: {segmenttekst} — dette gir modellen oppgavekontekst og forbedrer domenetilpasningen vesentlig.

Oppgradering fra v2: v2 brukte multilingual-e5-base (278M params, 768-dim, 12 lag). v3 bruker dobbelt så stor modell med instruksjonsstøtte — vesentlig bedre semantisk diskriminering for korte tekstfragmenter.

Determinisme: Modellen kjører i eval-modus uten dropout eller stokastiske operasjoner. Alle beregninger skjer på CPU med entrådskjøring. Identisk tekst → identisk 1024-dimensjonal vektor, ned til siste desimal (IEEE 754 floating point).

Python-moduler i dette steget
sentence-transformers transformers (HuggingFace) torch (PyTorch) tokenizers (Rust-basert) numpy safetensors

sentence-transformers orkestrerer hele flyten. Under panseret bruker den HuggingFace transformers for modell-arkitekturen (XLMRobertaModel), tokenizers (kompilert i Rust for hastighet) for WordPiece-tokenisering, og torch for all tensor-beregning gjennom 24 transformer-lag. Modellvektene lastes fra safetensors-format.

3
Dimensjonsreduksjon

1024 dimensjoner reduseres til 10 med bevart manifold-struktur — optimalt for etterfølgende clustering.

UMAPCosine-metrikkDeterministisk

Teknologi: UMAP — Uniform Manifold Approximation and Projection (McInnes, Healy & Melville, 2018). Bevarer både lokal nabostruktur og global topologi.

Hvorfor UMAP over alternativer? PCA er lineær og mister ikke-lineære relasjoner. t-SNE er ustabil for clustering i høyere dimensjoner og ikke-deterministisk uten pinning. UMAP bevarer manifold-struktur, er skalerbar, og deterministisk med faste parametere.

n_neighbors=15Balanse mellom lokal og global struktur
n_components=10Måldimensjonalitet
min_dist=0.0Tett pakking for bedre clustering
metric=cosineNaturlig for normaliserte embeddings
random_state=42Fast seed for reproduserbarhet
n_jobs=1Entrådskjøring — eliminerer nondeterminisme
Python-moduler i dette steget
umap-learn pynndescent numpy scipy.sparse numba (JIT-kompilert)

umap-learn bruker pynndescent for approximate nearest neighbor-søk (bygger en RP-tree + NNDescent-graf over 1024-dim vektorene). Internalt konstruerer UMAP en vektet k-naboskap-graf som representeres som scipy.sparse matrise, optimerer et lavdimensjonalt layout via stokastisk gradientnedstigning (SGD), og bruker numba for JIT-kompilering av de tunge løkkene til maskinkode.

4
Clustering

Automatisk oppdagelse av kategorier uten forhåndsspesifisert antall — robust mot støy og varierende klyngeformer.

HDBSCANDensity-basertAutomatisk K

Teknologi: HDBSCAN — Hierarchical Density-Based Spatial Clustering of Applications with Noise (Campello, Moulavi & Sander, 2013). Oppdager antall kategorier automatisk — trenger ikke forhåndsspesifisert K.

Algoritme: (1) Beregn gjensidig rekkeviddeavstand mellom alle punkter → (2) Bygg minimalt spenntre (MST) → (3) Konstruer klyngehierarki → (4) Ekstraher stabile klynger via excess of mass (EOM).

Støyhåndtering: Svar som ikke tilhører noen klynge (noise = -1) tilordnes nærmeste sentroid via cosine similarity. Fallback: Hvis HDBSCAN finner < 3 klynger, brukes KMeans (k=10, random_state=42) direkte på 1024-dim embeddings.

min_cluster_size=15Minimum svar per kategori
min_samples=5Konservativitet
core_dist_n_jobs=1Entrådskjøring for determinisme
Python-moduler i dette steget
hdbscan scikit-learn (KMeans fallback) scipy.spatial.distance scipy.sparse.csgraph (MST) numpy

hdbscan beregner mutual reachability distance via scipy.spatial.distance, bygger et minimalt spenntre (MST) via scipy.sparse.csgraph.minimum_spanning_tree, og ekstraher stabile klynger med excess-of-mass metoden. Fallback til sklearn.cluster.KMeans (k=10, random_state=42, n_init=10) hvis HDBSCAN finner < 3 klynger.

5
Hierarkisk gruppering
Nytt i v3

Nærliggende klynger grupperes i overordnede kategorier — gir to-nivå hierarki for både overblikk og detalj.

AgglomerativCosine-avstandHERCULES-inspirert

Metode: Etter HDBSCAN har identifisert underkategorier, grupperes nærliggende klynger i overordnede kategorier ved hjelp av AgglomerativeClustering med cosine-avstand og average linkage. Antall overordnede grupper bestemmes adaptivt.

Eksempel på resultat:

Pris og økonomi (hovedkategori)
   ↳ Spotpris / variabel pris
   ↳ Fastprisavtale
   ↳ Generelt billig
Kundeservice (hovedkategori)
   ↳ God service ved kontakt
   ↳ Enkel kundeportal
Python-moduler i dette steget
sklearn.cluster.AgglomerativeClustering sklearn.metrics.pairwise (cosine_distances) numpy

Sentroidene for hver HDBSCAN-klynge beregnes som gjennomsnittsvektoren av alle tilhørende embeddings. cosine_distances fra scikit-learn beregner avstandsmatrisen mellom sentroidene, og AgglomerativeClustering med average linkage bygger et dendrogram som kuttes ved optimalt antall grupper.

6
Navngiving

AI-genererte kategorinavn med inklusjonskriterier og nøkkelord — det eneste ikke-deterministiske steget i pipelinen.

Claude APIAnthropicRedigerbart

For hver klynge velges opptil 10 representative svar og sendes til Claude (Anthropic) med surveyspørsmålet som kontekst. Claude genererer for hvert nivå: kort kategorinavn (2–5 ord), inklusjonskriterium/definisjon, og nøkkelord inkludert dialektvarianter og bøyningsformer.

Viktig: Dette er det eneste ikke-deterministiske steget. Navnene kan variere mellom kjøringer, men grupperingene og frekvensfordelingene er alltid identiske. Brukeren kan manuelt redigere alle kategorinavn, beskrivelser og nøkkelord.

Python-moduler i dette steget
requests (Anthropic REST API) json re (JSON-ekstraksjon)

requests sender strukturerte HTTP-forespørsler til Anthropic REST API (api.anthropic.com/v1/messages) med representative svar og surveyspørsmål som kontekst. For hver klynge velges opptil 10 svar som tilhører klyngen, og Claude genererer navn, beskrivelser og nøkkelord.

7
Klassifisering
Nytt i v3

Hvert segment klassifiseres individuelt via cosine similarity — gir prinsipielt grunnlag for multi-koding med full transparens.

Cosine similaritySegment-basertDeterministisk

v3-tilnærming: (1) Hvert svar segmenteres → (2) Hvert segment embeddes separat → (3) Hvert segment klassifiseres individuelt mot klyngesentroider via cosine similarity → (4) Unike kategorier samles per svar → (5) Per-segment similarity-score bevares for transparens.

Multi-koding: Naturlig og prinsipielt — hvert segment matcher sin nærmeste kategori. Ikke lenger avhengig av vilkårlige gap-terskler.

Konfidensrapportering: For hvert svar vises hvilke segmenter som matchet hvilken kategori med hvilken score — muliggjør manuell kvalitetskontroll.

Fullstendig deterministisk: Matrisemultiplikasjon + argmax. Identisk input → identisk output.

Matematisk operasjon
# S = segment-embeddings matrise (n_segments × 1024)
# C = sentroid-matrise (n_clusters × 1024)
similarity = S @ C.T   # matrisemultiplikasjon → (n_segments × n_clusters)
labels = np.argmax(similarity, axis=1)   # nærmeste sentroid per segment
scores = np.max(similarity, axis=1)   # konfidensscorer
Python-moduler i dette steget
numpy (matrisemultiplikasjon) segment.py (WtpSplit) embed.py (E5-large-instruct)

Hele klassifiseringen koker ned til én numpy matrisemultiplikasjon (@-operatoren) etterfulgt av argmax. Fordi alle embeddings er L2-normalisert, er dot product identisk med cosine similarity — ingen ekstra normalisering trengs. Wilson 95 %-konfidensintervaller beregnes i frontend (JavaScript).

Hvorfor deterministisk koding er kritisk

Forskning krever at resultater kan reproduseres. Verken manuell koding eller LLM-koding kan tilby dette — On-Ramp Tematisk Analyse v3 kan.

Embedding: Identisk tekst → identisk vektor (IEEE 754, deterministisk PyTorch)
UMAP: random_state=42, n_jobs=1 — eliminerer multi-threading nondeterminisme
HDBSCAN: core_dist_n_jobs=1 — single-threaded MST-konstruksjon
KMeans fallback: random_state=42, n_init=10
Cosine similarity: Ren matrisemultiplikasjon — deterministisk per IEEE 754
Cache: SHA256-hash av sorterte svar + spørsmål → identisk input gir cache-treff
Verifiseringsprotokoll: Medfølgende testscript kjører pipelinen to ganger og verifiserer bit-for-bit identiske resultater

Tre kjøringer, samme data, forskjellig resultat

(Manuell koding/LLM-basert koding (AI-analyser))

LLM-koding (ChatGPT / Claude direkte)
Pris: 34 %
Service: 22 %
Anbefaling: 18 %
Kjøring 2
Økonomi: 29 %
Kundeservice: 25 %
Vanemessig: 15 %
Kjøring 3
Kostnad: 31 %
Tilfredshet: 20 %
Anbefalt: 21 %

Tre kjøringer, samme data, samme resultat

(On-Ramp v3)

On-Ramp v3 — Kjøring 1
Pris/økonomi: 32.4 %
Kundeservice: 23.1 %
Anbefaling: 19.7 %
Kjøring 2
Pris/økonomi: 32.4 %
Kundeservice: 23.1 %
Anbefaling: 19.7 %
Kjøring 3
Pris/økonomi: 32.4 %
Kundeservice: 23.1 %
Anbefaling: 19.7 %

Manuell koding vs. LLM vs. On-Ramp v3

Dimensjon Manuell koding LLM-koding On-Ramp v3
Reproduserbarhet 70–85 % inter-coder ~0 % 100 % deterministisk
Hastighet Timer / dager ~ Minutter, men upålitelig ~2 min / 1 000 svar
Skalerbarhet Lineær kostnad ~ API-kostnader øker 50–10 000+ svar
Transparens ~ Avhenger av koder Black box Similarity-scorer per segment
Multi-koding ~ Manuell vurdering ~ Ad hoc LLM-vurdering Prinsipielt segment-basert
Induktiv kategorisering Mulig men arbeidskrevende ~ Inkonsistent Automatisk bottom-up
Hierarkisk struktur Sjelden implementert ~ Mulig men ustabilt Automatisk to-nivå
Konfidensintervaller Nei Nei Wilson 95 % KI
Norskoptimert Morsmål ~ Variabelt Bokmål, nynorsk, dialekter
Publiserbar ~ Med forbehold Ikke reproduserbar Fullstendig audit trail

Fra råsvar til hierarkisk innsikt

Spørsmål: «Hvorfor valgte du din nåværende strømleverandør?»

1
Segmentering
«billig strøm og god kundeservice og anbefalt av naboen»
billig strøm
god kundeservice
anbefalt av naboen
2–4
Embedding → UMAP → HDBSCAN

Hvert segment konverteres til en 1024-dimensjonal vektor, reduseres til 10 dimensjoner via UMAP, og tilordnes en klynge via HDBSCAN. Systemet oppdager f.eks. 8 underkategorier automatisk fra 1 000 svar.

5–7
Hierarki → Navngiving → Klassifisering
billig strøm → Spotpris/variabel 0.87
god kundeservice → Kundeservice 0.91
anbefalt av naboen → Anbefaling 0.89

→ Svaret kodes til 3 kategorier med full transparens: eksakt similarity-score per segment.

Frekvensfordeling (n = 1 000)
HovedkategoriUnderkategoriFrekvens95 % KI
Pris og økonomi Spotpris / variabel pris
18.2 %
[15.8 – 20.8]
Fastprisavtale
8.4 %
[6.8 – 10.4]
Generelt billig
5.8 %
[4.5 – 7.5]
Kundeservice God service ved kontakt
14.7 %
[12.5 – 17.1]
Enkel kundeportal / app
8.4 %
[6.8 – 10.4]
Anbefaling Anbefalt av noen
19.7 %
[17.2 – 22.4]
Vane / bekvemmelighet Alltid hatt dem
13.5 %
[11.4 – 15.8]
Miljø / grønt Fornybar energi
11.3 %
[9.3 – 13.5]

Teknisk stack — 15+ spesialiserte moduler

Pipelinen orkestrerer et økosystem av state-of-the-art Python-biblioteker. Her er den fulle oversikten over hva som faktisk kjører.

PyTorch
torch 2.x

Grunnmotoren for all neural nettverksberegning. Kjører de 560M parametrene i embedding-modellen gjennom 24 transformer-lag med multi-head self-attention. Deterministisk via eval-modus og CPU-kjøring uten stokastiske operasjoner.

torch.nn torch.no_grad torch.eval()
HuggingFace Transformers
transformers

Laster og kjører XLM-RoBERTa-arkitekturen (E5-large-instruct). Håndterer tokenizer-initialisering, modellvekt-lasting fra safetensors, attention mask-konstruksjon, og forward pass gjennom alle 24 lag.

AutoModel AutoTokenizer XLMRobertaModel
Tokenizers
tokenizers (Rust)

Rust-kompilert WordPiece-tokenizer med ~250 000 tokens vokabular. Splitter tekst til subword-tokens («kundeservice» → «kunde» + «##service»), konverterer til token IDs, og genererer attention masks — alt i mikrosekunder.

WordPiece SentencePiece BPE
Sentence-Transformers
sentence-transformers

Orkestreringsbiblioteket som binder alt sammen: laster modellen, håndterer batching, kjører instruksjonsbasert encoding, utfører mean pooling over token-representasjoner, og L2-normaliserer til enhetsvektorer.

SentenceTransformer models.Pooling models.Normalize
WtpSplit
wtpsplit

Neural setningssegmentering (EMNLP 2024). Kjører en 3-lags modell (~50 MB) som predikerer segmentgrenser karakter-for-karakter. Suppleres med kompilerte regex-mønstre for norske diskursmarkører.

SaT("sat-3l-sm") split()
UMAP
umap-learn

Dimensjonsreduksjon fra 1024 → 10 via manifold-læring. Bygger k-NN graf med PyNNDescent (approximate nearest neighbors), konstruerer fuzzy simplicial set, og optimerer layout via SGD. JIT-kompilert med Numba.

umap.UMAP pynndescent numba
HDBSCAN
hdbscan

Density-basert hierarkisk clustering. Beregner mutual reachability distance, bygger minimalt spenntre via SciPy sparse grafalgoritmer, og ekstraherer stabile klynger med excess-of-mass metoden.

hdbscan.HDBSCAN scipy.sparse.csgraph
scikit-learn
sklearn

Brukes i 3 steg: AgglomerativeClustering for hierarkisk gruppering, KMeans som fallback-clustering, og cosine_similarity / cosine_distances for all avstandsberegning mellom vektorer.

AgglomerativeClustering KMeans cosine_similarity cosine_distances
NumPy + SciPy
numpy / scipy

Grunnfjellet for all numerisk beregning. NumPy for matrisemultiplikasjon (klassifisering), argmax, vektornormalisering. SciPy for sparse matriser (UMAP-grafer), MST-konstruksjon, avstandsmetrikker, og Wilson-konfidensintervaller.

numpy.dot / @ numpy.argmax scipy.spatial.distance scipy.sparse
Anthropic REST API
requests

Claude API for det eneste ikke-deterministiske steget: navngiving av klynger og generering av analyserapporter. HTTP-forespørsler via requests til Anthropic REST API med strukturerte prompts.

requests.post api.anthropic.com/v1/messages
Systemarkitektur
infrastruktur

Backend: Python 3.10, asynkron jobbhåndtering. Frontend: PHP 8.1 + vanilla JS. Cache: filbasert SHA256-nøklet for reproduserbarhet. PHP spawner Python-prosess, frontend poller for resultat.

Python 3.10 PHP 8.1 hashlib (SHA256) json subprocess

Bygget på publisert forskning

Hver komponent i pipelinen er forankret i peer-reviewed eller bredt sitert forskning.

Minixhofer, B., Pfeiffer, J., & Vulić, I. (2024). "Where's the Point? Self-Supervised Multilingual Punctuation-Agnostic Sentence Segmentation." EMNLP 2024.
Wang, L., et al. (2024). "Multilingual E5 Text Embeddings: A Technical Report." arXiv:2402.05672.
McInnes, L., Healy, J., & Melville, J. (2018). "UMAP: Uniform Manifold Approximation and Projection for Dimension Reduction." arXiv:1802.03426.
Campello, R.J.G.B., Moulavi, D., & Sander, J. (2013). "Density-Based Clustering Based on Hierarchical Density Estimates." PAKDD 2013.
Zhang, S., et al. (2024). "HERCULES: Hierarchical Clustering via LLM-Enhanced Summarization."
Pontiki, M., et al. (2016). "SemEval-2016 Task 5: Aspect Based Sentiment Analysis." — Segmentbasert tilnærming til multi-label klassifisering.
Wilson, E.B. (1927). "Probable Inference, the Law of Succession, and Statistical Inference." Journal of the American Statistical Association.
Halliday, M.A.K. & Hasan, R. (1976). "Cohesion in English." — Diskursmarkører som indikatorer for tematiske skifter.

Klar for deterministisk koding?

Tilgjengelig som SaaS-lisens, on-premise installasjon, eller API-integrasjon. Vi tilpasser løsningen til ditt behov.