Tokens And Embeddings

Keyword Plus Meaning: When Combining Two Searches Helps, and When It Hurts

0 of 12 complete

0%

Contents

Back|Tokens And EmbeddingsKeyword Plus Meaning: When Combining Two Searches Helps, and When It Hurts
1/12
38 min left
Prerequisites
Finding Neighbours Fast: Search a Few Clusters Instead of Everythingrequired
Related Topics
Is There Really a Best Chunk Size? Measured on This Course's Own LessonsRetrieval and RAG in ProductionHybrid Retrieval: When Keyword Search Beats Your EmbeddingsRetrieval and RAG in ProductionChunking: The First Lever on Retrieval QualityRetrieval and RAG in ProductionWhich Lessons Mention Kafka? Questions That Top-10 Search Cannot AnswerRetrieval and RAG in ProductionThe RAG Scale Cliff: What Breaks Between 100 and 5 Million DocumentsRetrieval and RAG in Production
1 of 12

The Index and the Idea

A thick book has an index at the back. If you know the exact word, the index takes you straight to the page. If you only know the idea, and the book uses other words for it, the index is no help at all.

A flat illustration of an engineer at a desk with a thick printed index book under a magnifying glass on one side, an open laptop on the other, and a notepad between them where she writes down results from both. A line says a book's index finds your exact words, a laptop search can find your meaning, and sometimes you want both.

Search on a computer can work either way. Keyword search matches the words themselves. Meaning search, which this whole chapter has built, matches . Many real systems run both and combine the results. This lesson measures whether combining them actually helps.

The Words You Need First

A hand-drawn word list. Keyword search: rank texts by the words they share with the question. BM25: a standard keyword recipe, rare shared words count more. Meaning search: rank texts by embedding closeness, as in lesson 4. Hybrid search: combine a keyword ranking and a meaning ranking. Rank fusion: a simple way to combine two rankings into one. Place: 1 is best, and a tie shares the worse place. A note says embedding, cosine, pieces and top 5 are from earlier lessons.

Keyword search. Rank texts by the words they share with the question. No model, no .

BM25. The standard recipe for keyword search. "BM" stands for Best Matching, and 25 was its number in a series of recipes tried in the 1990s. It is explained on a later slide.

Meaning search. Rank texts by how close their embeddings are to the question's, as in lesson 4.

Hybrid search. Run both searches and combine their rankings.

Rank fusion. A simple way to combine two rankings into one. It is also explained below.

Place. A text's position in a ranking: 1 is the best. If two texts tie, both get the worse place.

Signal. Useful information. A ranking with no signal puts texts in an order that says nothing about the question.

Baseline. A simple method you always run first, so you can see whether anything fancier is really better.

Try It: Three Questions, Four Texts

This file scores four short texts against three questions, by shared words and by meaning, and then combines the two rankings. Here it is, open in my VS Code.

A real screenshot of keyword_and_meaning.py open in VS Code, 35 lines: an embed function using bge-m3 through Ollama, a words function that lowercases a text and keeps its words, a dot function, a place function where a tie shares the worse place, four texts about rate limiting, HTTP 429, caching and sharding, and a loop over three questions that counts shared words, computes meaning scores, and combines the two places with 1 divided by 60 plus each place.

And this is what it printed:

A real screenshot of VS Code's terminal after running python keyword_and_meaning.py. For "What does status 429 mean?": HTTP 429 shares 1 word, meaning 0.637, combined place 1. For "How can I stop one customer from flooding my API?": rate limiting shares 1 word, meaning 0.562, combined place 1; sharding shares 1 word, meaning 0.417, combined place 3. For the Spanish question: every text shares 0 words; rate limiting has meaning 0.566 and combined place 1.

Shared words per text for each of the three questions. 429 question: only HTTP 429 shares a word. Flooding question: rate limiting and sharding share one word each. Spanish question: no text shares any word. A note says in Spanish no text shares a single word with the question, so keyword search has nothing to go on.

Look at the three questions one by one.

  • "What does status 429 mean?" The number 429 appears in one text. Both searches agree, and the right text wins.
  • "How can I stop one customer from flooding my API?" shares one word, but it is only "how". Sharding shares one word too, "one". Neither match means anything. Meaning search still put rate limiting first and scored sharding lowest of all.
  • The Spanish question shares no words with any text. Every keyword score is 0. Meaning search still found rate limiting.

A hand-drawn pair: the question "stop ONE customer" and the sharding text "splits ONE big table". A note says sharding has 1 shared word; the word matches but the meaning does not, and meaning search scored sharding lowest.

How the Two Searches Work, and How to Combine Them

The example counted shared words. Real keyword search uses a better recipe, BM25:

BM25 in words, for each word the question and a text share. Rare words count more: a word found in few texts tells you more than a common one. Repeats help, but less and less: the tenth use adds little over the ninth. Long texts are scaled down, so a long text does not win by length alone. A note says no model and no embeddings, just counting words.

In a large collection the first rule deals with words like "one" and "how": they appear in many texts, so they count for very little. In the tiny four-text example each of them appears in only one text, so even BM25 could not tell them apart from a meaningful word.

The two searches give numbers of very different sizes. In the quiz test below, the top BM25 score for a question was usually between about 13 and 53, while the cosine scores in this chapter sit between 0 and 1. You cannot simply add them. Reciprocal rank fusion avoids the problem by using only each text's place. "Reciprocal" just means 1 divided by a number:

A hand-drawn formula in three boxes: 1 divided by 60 plus the keyword place, from the word ranking; 1 divided by 60 plus the meaning place, from the meaning ranking; add them, and a bigger sum is better. A note says only places are used, not scores, so the two searches never need the same scale, and 60 is the usual constant, not tuned here.

For each text, take 1 divided by (60 plus its keyword place), add 1 divided by (60 plus its meaning place), and sort the texts by that sum. A text near the top of either list rises. A text near the top of both rises most. The 60 comes from the 2009 paper that named the method, by Cormack, Clarke and Buettcher. Because of the 60, being first in one list gives only a small extra push over being second or third. This lesson did not tune it.

A sequence diagram with three columns: question, keyword (BM25) and meaning (embeddings). Step 1: rank by shared words. Step 2: rank by meaning. Step 3: add 1 divided by 60 plus the place from each. Step 4: sort by the sum. A note says a text near the top of either list rises, and one near the top of both rises most.

Two Tests

An isometric row of four blocks: the quiz test with 470 questions, 1,823 pieces, three rankings (keyword, meaning, both), and an own-words test of 60 questions in six languages. A note says these are lesson 6's quiz questions and lesson 4's questions in six languages.

Test 1: the quiz questions. The same setup as lessons 6 to 10: 470 quiz questions against 1,823 pieces of 112 lessons, with both models from this chapter, nomic-embed-text and bge-m3. The quiz questions were written by the same author as the lessons, so they often reuse the lessons' own words.

Test 2: other words. Lesson 4's 10 questions. I wrote them before running any test, and they do not use the words in the lesson titles. Each is asked in English, French, Spanish, Hindi, Japanese and Bengali. That is 60 searches against the title and description of 878 lessons, exactly as in lesson 4, with bge-m3, which reads all six languages. The lesson texts are in English. For keyword search, a word is any run of letters in any alphabet. Japanese is written without spaces, so a whole phrase counts as one word there, which makes keyword search weak in Japanese by nature. The two tests search different collections, so compare the three methods within a test, not across tests.

Both tests rank lessons by keyword search (BM25), by meaning search, and by both combined. If the right lesson ties with others, it gets the worst place in that tie.

Here is the lab's report, from the terminal.

A real terminal recording of hybrid.py's report. Quiz test, 470 questions: keyword BM25 with no model, 378 first and 453 in the top 5; meaning with nomic-embed-text, 339 and 441; both with nomic, 379 and 454; meaning with bge-m3, 356 and 439; both with bge-m3, 379 and 455. Question by question in the top 5, fusion found 15 that meaning missed and missed 2 that meaning found for nomic, and 17 and 1 for bge-m3. Second test, own wording, bge-m3, top 5 of 10 per language, with the number of searches where no word matched: English keyword 4, meaning 6, both 4, 0 no-match; French 1, 5, 4, 0; Spanish 0, 5, 3, 3; Hindi 0, 4, 4, 9; Japanese 0, 3, 3, 10; Bengali 0, 4, 4, 9.

Test 1: With the Same Words, Keywords Are Hard to Beat

A bar chart for the quiz test, right lesson in the top 5 of 470: keyword 453, nomic 441, nomic plus keyword 454, bge-m3 439, bge-m3 plus keyword 455. Titled on the lessons' own words, keyword search wins.

On the quiz questions, the simplest search won. Keyword search, with no model at all, found the right lesson in the top 5 for 453 of 470. Meaning search found 441 with nomic-embed-text and 439 with bge-m3.

Three boxes from the quiz test, top 5 of 470: keyword alone 453, bge-m3 plus keyword 455, bge-m3 alone 439. A note says the quiz questions reuse the lessons' words, which is what keyword search is built for.

This is not a failure of . The quiz questions share many words with their lessons, and that is exactly the case keyword search is built for. A later chapter of this course, on search for AI answers, found the same thing with chunks of many sizes.

Combining the two helped meaning search a lot:

A bar chart for the quiz test, right lesson ranked first of 470: nomic meaning only 339, both 379; bge-m3 meaning only 356, both 379. A note says keyword alone ranked 378 first.

Two panels for the quiz test, top 5, question by question against meaning alone: gained 15 for nomic and 17 for bge-m3; lost 2 and 1. A note says many found, very few lost.

Question by question, fusion found the right lesson for 17 questions that bge-m3 alone missed, and lost only 1. But against keyword search alone, the gain was tiny: 455 against 453 in the top 5, and 379 against 378 first.

Test 2: With Other Words, Wrong Matches Hurt

A bar chart for lesson 4's questions in other words, bge-m3, right lesson in the top 5 of 10 per language. English: keyword 4, meaning 6, both 4. French: 1, 5, 4. Spanish: 0, 5, 3. Hindi: 0, 4, 4. Japanese: 0, 3, 3. Bengali: 0, 4, 4. A note says in English, French and Spanish meaning found 16 and both 11, and in Hindi, Japanese and Bengali no word matched in 28 of 30 searches and both equals meaning.

Now the questions use other words, and five of the six languages are not English. The lesson texts are all in English.

  • Keyword search found the right lesson for 4 of 10 in English, 1 in French, and none in the other four languages.
  • Meaning search found 6 in English and 3 to 5 in each other language, 27 of 60 in all.
  • Both combined found 22 of 60.

Look at the two groups of languages separately.

  • Hindi, Japanese and Bengali: keyword search matched no word at all in 28 of 30 searches. Then every lesson ties in the keyword list, so it adds the same amount to every lesson, and the combined order is simply the meaning order. Both found exactly what meaning found: 4, 3 and 4.
  • English, French and Spanish: keyword search did match words, but mostly in the wrong lessons. Here combining pulled the right lesson down: meaning found 16, both found 11. In English, one right lesson fell from 4th to 9th and another from 5th to 26th.

A hand-drawn sketch: a keyword list with the wrong lessons first and a meaning list with a useful order both feed a fused list, which is pulled down. A note says each list counts the same, and where no word matched at all, every lesson tied and fusion changed nothing; wrong matches did the harm.

Count It Yourself

This box holds the real ranks from both tests, for bge-m3. In the second test, 878 means keyword search matched no word at all, so every lesson tied and the right one got the last place. Press Run to see the quiz test. Then change TEST to "WORDED" to see the other-words test, and TOP to 1.

When to Combine

A flowchart: do users type the same words as your documents? If often: try both and measure. If rarely, or in other languages: meaning search alone. A note says here, with the same words fusion helped, and with other words it hurt.

  1. Always run keyword search as a baseline. It needs no model, it is cheap, and on these quiz questions it beat both models.
  2. If your users type the same words as your documents (product names, error codes, part numbers, exact phrases), compare keyword search alone with both combined. Here they were almost level, 453 against 455, and either beat meaning search alone. If they are level for you too, the simpler keyword search may be enough.
  3. If they use other words or other languages, prefer meaning search alone. Here, keyword matches in the wrong lessons made the combined result worse.
  4. Measure on your own questions, with keyword, meaning and both side by side. You need questions where you already know the right answer.

Two cards with logos: Ollama running nomic and bge-m3, and Python doing BM25 and fusion. A note says it is free and local.

What This Lesson Measured, and What It Did Not

Two columns. Measured: 470 quiz questions, 60 own-words searches, one fusion constant, 60. Not measured: weighted mixes, other keyword recipes, questions from real users.

Measured: keyword, meaning and fused search on 470 quiz questions and on 60 own-words searches in six languages; two models; one fusion constant, 60.

Not measured: weighted mixes that trust one list more than the other, other keyword recipes, and questions from real users. Only 60 searches in the second test, so its numbers can move by a few with other questions. Real users will likely do both: some use your words and some do not.

What to Do Next

A hand-drawn list of four things to do: run keyword_and_meaning.py and read the scores; run keyword search first as a baseline, it needs no model; compare keyword, meaning and both on your questions; and check whether your users share your documents' words. A note says combine only when both searches carry something.

  1. Run keyword_and_meaning.py and read the scores for each question.
  2. Run keyword search first as a baseline. It needs no model.
  3. Compare keyword, meaning and both on your own questions.
  4. Check the words: do your users share your documents' words, or not?

The number to keep: bge-m3 in the top 5 after adding keywords, 439 to 455 on quiz questions, and 16 to 11 on own-words questions in English, French and Spanish. A note says same words, it helped; other words, it hurt.

Knowledge Check

Knowledge Check

5 questions - Score 80% to pass

Q1

On the 470 quiz questions, keyword search alone found the right lesson in the top 5 for 453, and bge-m3 meaning search for 439. What is the best explanation?

Q2

How does reciprocal rank fusion combine a keyword ranking and a meaning ranking?

Q3

In English, French and Spanish, meaning search found 16 and both combined found 11. In Hindi, Japanese and Bengali, both found exactly what meaning found. Why the difference?

Q4

In the small example, why did the sharding text share a word with the question about flooding an API?

Q5

Your users type exact product codes like "XK-240" that appear in your documents. What does this lesson suggest?

To run it: install Ollama from ollama.com, open it and leave it running. Run ollama pull bge-m3, save the file below as keyword_and_meaning.py, and run python3 keyword_and_meaning.py (on Windows: python keyword_and_meaning.py). If you see "Connection refused", open the Ollama app first.

# Score the same texts by shared words and by meaning, then combine the two rankings.
# Needs Ollama (ollama.com) running, and:  ollama pull bge-m3
import json, re, urllib.request

def embed(texts):
    body = {"model": "bge-m3", "input": texts}
    req = urllib.request.Request("http://localhost:11434/api/embed", data=json.dumps(body).encode(),
                                 headers={"Content-Type": "application/json"})
    return json.loads(urllib.request.urlopen(req).read())["embeddings"]

def words(t): return set(re.findall(r"[a-z0-9]+", t.lower()))
def dot(a, b): return sum(x * y for x, y in zip(a, b))

def place(scores):                      # 1 = best; a tie shares the worse place
    return [sum(s >= x for s in scores) for x in scores]

names = ["rate limiting", "HTTP 429", "caching", "sharding"]
texts = ["Rate limiting caps how many requests each user may send.",
         "HTTP 429 means Too Many Requests: slow down and retry later.",
         "Caching keeps a copy of data close to where it is used.",
         "Sharding splits one big table across many machines."]
docs = embed(texts)

for question in ["What does status 429 mean?",
                 "How can I stop one customer from flooding my API?",
                 "¿Cómo evito que un cliente sature mi API?"]:
    q = embed([question])[0]
    shared = [len(words(question) & words(t)) for t in texts]
    meaning = [dot(q, d) for d in docs]
    kw, mn = place(shared), place(meaning)
    both = [1 / (60 + kw[i]) + 1 / (60 + mn[i]) for i in range(len(texts))]
    print(question)
    for i, n in enumerate(names):
        print(f"  {n:<14} shared words {shared[i]}   meaning {meaning[i]:.3f}   combined place {place(both)[i]}")

Rank fusion gives both lists an equal vote. An empty keyword list is harmless. A keyword list that confidently points at the wrong lessons does the damage.