WriteMySEO / Blog / Cosine Similarity Scores Don't Mean What Tools Imply
SEO technology

Cosine Similarity Scores Don't Mean What Tools Imply

Embedding similarity scores are model-relative, not absolute. Why anisotropy and chunking distort them, and how to normalize against a reference set of ranking pages.

A tool tells you your page scores 0.82 cosine similarity against the target query, and the competitor scores 0.86. What are you supposed to do with that? The implied answer is "add more semantically related content until the number goes up." That instruction assumes the number is on a meaningful scale, that the gap of 0.04 represents something, and that the model producing the score resembles the model doing the retrieval. None of those three assumptions survive contact with how embeddings actually work.

Cosine similarity is a useful tool. It is just not a score, in the sense of a grade. It is a coordinate in a space defined entirely by the model that produced it, and it only becomes interpretable when you compare it to other coordinates from the same model.

What a cosine score actually measures

An embedding model maps text to a fixed-length vector. Cosine similarity between two vectors is the cosine of the angle between them after normalizing for length — a number from -1 to 1, though in practice most text embedding models produce values crowded into a much narrower positive band.

What the model learned to put close together depends entirely on its training objective. A model trained with a contrastive objective on query–passage pairs (the typical retrieval setup) learns that a question and the passage answering it should be close. A model trained on sentence-pair similarity learns that paraphrases should be close. A model trained on next-token prediction and then pooled into a vector learns something fuzzier: topical and stylistic co-occurrence. Those are different notions of "similar," and they disagree constantly. A page about the risks of a procedure and a page about the benefits of that procedure are topically near-identical and pragmatically opposite. Most embedding models score them as very similar, because most embedding models were never trained to care about the distinction.

Why the absolute number isn't interpretable

Embedding spaces are anisotropic: the vectors do not spread evenly across the sphere, they occupy a narrow cone. This is a well-documented property of contextual embedding models in the NLP literature, and you can reproduce it in ten minutes. Embed a hundred deliberately unrelated sentences with a general-purpose model and look at the pairwise similarities. You will not get a distribution centered on zero. With many models you get a tight band of clearly positive values, because there is a dominant direction that every vector partly shares.

That has three consequences for anyone reading a similarity readout:

Why the model scoring you isn't the model ranking you

Google has documented that it uses neural language understanding in ranking — BERT-based systems, neural matching, MUM — and its published research describes dense retrieval and reranking architectures. What it has never published is the production model, its training data, or its scoring scale. Your similarity score is produced by a public model that shares a general family resemblance with what large retrieval systems use, and nothing more.

The architectural gap matters more than the weights. Dense retrieval with a bi-encoder is a first stage. It exists to pull a few hundred candidates out of billions cheaply, because each document can be embedded once and indexed. Final ordering typically comes from rerankers that see the query and document together, plus lexical signals, plus everything else in the ranking stack.

MethodWhat it seesWhere it's usedWhat a score means
Bi-encoder (cosine)Query and doc encoded separatelyCandidate retrieval, clusteringRelative proximity in one model's space
Cross-encoder / rerankerQuery and doc togetherReordering a shortlistCalibrated relevance, still model-specific
Lexical (BM25)Term overlap, term rarityRetrieval, hybrid blendsTerm-match strength, not meaning

Optimizing a page to raise a bi-encoder cosine score is optimizing for the recall stage of a system whose precision stage you cannot see.

Normalize against a reference set instead of a threshold

The fix is to stop reading the raw number and start reading its rank within a comparable population. Embed the query, embed the pages that currently rank for it, embed yours, and ask where you sit in that distribution.

import numpy as np

# all vectors L2-normalized, from ONE model
sims = ranking_pages @ q        # shape (n,)
mine = float(my_page @ q)
pct  = (sims < mine).mean() * 100

print(f"raw {mine:.3f} | baseline {sims.mean():.3f} "
      f"| {pct:.0f}th pct of {len(sims)} ranking pages")

Now the output means something: below the median of pages Google already chose. Add a control set of clearly off-topic pages to establish the floor, and the spread between floor and ranking-set median tells you how much signal the model has for this query at all. If your off-topic control scores 0.64 and the ranking set averages 0.69, this model is not discriminating for this query and no amount of editing will make its score informative.

Chunking decides the score before the model does

If you embed a 3,000-word page as one vector, you get an average of everything on it, and the specific paragraph that answers the query gets diluted by the other twenty. Run the same comparison chunk by chunk and the picture usually inverts: the page's best chunk beats a competitor whose whole-page score was higher.

Since retrieval systems generally operate on passages rather than whole documents, the max-over-chunks score is the more faithful measurement. Report both. A page with a strong max and a weak mean is focused; a page with a mediocre max and a high mean is uniformly vague, which is the more common failure.

What to do with this

  1. Pin the model. Record model name and version alongside every score. Never compare scores across models or across a vendor's silent upgrade.
  2. Establish the floor. Embed 50 deliberately unrelated documents once per model. That mean is your zero, not 0.0.
  3. Report percentile, not raw cosine. Position against pages that already rank for the query, and state the sample size.
  4. Score chunks, keep the max and the mean. Use the same chunk boundaries you would expect a retriever to use — headed sections of a few hundred words.
  5. Use cosine for what it's good at: finding pages that compete with each other, surfacing internal link candidates, grouping a large corpus before a human looks at it. These are relative-order tasks, which is exactly what the measure supports.
  6. Treat a low score as a question, not a verdict. It tells you the model finds your page distant from the query. Read the page and decide whether that reflects an actual gap in coverage or an artifact of length, boilerplate, or a model that doesn't discriminate here.
embeddingsvector searchcontent optimizationretrieval

WriteMySEO produces marketing content, not legal, medical, financial, or compliance advice. Figures cited reflect publicly reported industry data at time of writing and shift over time.

Get started

We write this well about your industry, every month.

AI-drafted, human-reviewed SEO content on a flat subscription. Blog posts, metadata, schema, and internal links, shipped on a monthly rhythm.

See plans

More from the blog