Hoppa till innehållet
AI-grafen
E· Universitetrag-informationssokning· ca 60 min· utvecklande· verifierad 2026-09-20

Hybridsökning och RRF

Kunna kombinera vektor- och nyckelordsträffar med reciprocal rank fusion.

Förkunskaper

Intuition

Vektorsökning och nyckelordssökning misslyckas på olika frågor. Det är precis därför de går att kombinera.

FrågaBM25Vektor
«felkod E404»hittarsuddar ut den exakta strängen
«hur fixar jag att sidan inte finns»missarhittar
«Södertälje kommun diarienummer 2026-114»hittarmissar
«vad kostar det att bo här»missar omskrivningarhittar

En hybrid som tar båda listorna och slår ihop dem hittar allt det som vardera hittar — plus att den rankar upp det som båda är eniga om.

Problemet med att slå ihop poäng: BM25 ger tal mellan 0 och 30, cosinuslikhet mellan −1 och 1. De är inte jämförbara, och normalisering är godtycklig eftersom skalorna varierar med frågan.

RRF löser det genom att kasta bort poängen och bara använda ordningen.

Formellt

RRF(d)=∑r∈R1k+rankr(d),k≈60\mathrm{RRF}(d) = \sum_{r \in R} \frac{1}{k + \mathrm{rank}_r(d)}, \qquad k \approx 60

Tre egenskaper som gör den svår att slå:

  1. Ingen kalibrering behövs. Bara rangordningen används, så olika poängskalor spelar ingen roll.
  2. Konsensus belönas. Ett dokument som är tvåa i båda listorna slår ett som är etta i den ena och femma i den andra.
  3. Robust mot utstickare. En enskild lista kan inte dominera, eftersom bidraget är begränsat till 1/(k+1)1/(k+1).

Vad k gör: stora kk plattar ut skillnaden mellan rangerna och gör fusionen mer demokratisk; små kk ger topplaceringarna mer vikt. k=60k = 60 kommer från originalartikeln och fungerar förvånansvärt bra utan justering.

Alternativ och när de är bättre:

MetodNär
RRFstandardvalet — inga poäng behövs
Viktad poängsummanär du har kalibrerade poäng och tid att ställa in vikterna
Omrankning med cross-encodernär kvalitet går före latens; slår ofta båda
Lärd fusion (LTR)när du har klickdata i mängd

Den mest effektiva uppsättningen i praktiken är tvåstegs: hämta brett med hybrid (RRF över BM25 och vektorsök), och ranka sedan om topp 50 med en cross-encoder. Den första delen är billig och har hög recall; den andra är dyr men körs bara på 50 dokument.

Utvärdera alltid hybriden mot varje del för sig. Ibland är den sämre — särskilt om den ena källan är mycket sämre än den andra, eftersom RRF då släpper in brus. Mät:

MåttFrågan
recall@k per källavad hittar var och en?
recall@k för hybridenhittar den mer?
nDCG@10ligger det rätta högt upp?
Andel frågor där hybriden är sämrefinns en regression?

Den sista raden är den som avslöjar när hybriden skadar: ett medelvärde kan förbättras samtidigt som en fjärdedel av frågorna blir sämre.

Kod

def rrf(rankningar: list[list[str]], k: int = 60, topp: int = 10) -> list[str]:
    poang: dict[str, float] = {}
    for lista in rankningar:
        for pos, doc in enumerate(lista):
            poang[doc] = poang.get(doc, 0.0) + 1.0 / (k + pos + 1)
    return sorted(poang, key=lambda d: (-poang[d], d))[:topp]

# Konsensus vinner över en ensam förstaplats
text  = ["a", "b", "c", "d"]
bild  = ["d", "b", "e", "f"]
print(rrf([text, bild], topp=3))        # ['b', 'd', 'a']
#  b är tvåa i båda → 1/62 + 1/62 = 0.03226
#  d är etta och fyra → 1/61 + 1/64 = 0.03202

def hybrid_sok(fraga, bm25_sok, vektor_sok, hamta=50, topp=10, k=60):
    b = bm25_sok(fraga, hamta)
    v = vektor_sok(fraga, hamta)
    return rrf([b, v], k=k, topp=topp)

# Utvärdera: hybriden MOT varje del för sig, och andelen regressioner
def utvardera(facit, bm25_sok, vektor_sok, k=10):
    resultat = {"bm25": 0, "vektor": 0, "hybrid": 0}
    samre = 0
    for fall in facit:
        b = bm25_sok(fall["fraga"], k)
        v = vektor_sok(fall["fraga"], k)
        h = rrf([b, v], topp=k)
        traff = {"bm25": fall["ratt"] in b, "vektor": fall["ratt"] in v,
                 "hybrid": fall["ratt"] in h}
        for namn, t in traff.items():
            resultat[namn] += int(t)
        if (traff["bm25"] or traff["vektor"]) and not traff["hybrid"]:
            samre += 1
    n = len(facit)
    return {namn: round(v / n, 3) for namn, v in resultat.items()} | {
        "regressioner": round(samre / n, 3)}

# Tvåstegs: bred hämtning med hybrid, sedan dyr omrankning av topp 50
def tva_stegs(fraga, bm25_sok, vektor_sok, cross_encoder, topp=10):
    kandidater = hybrid_sok(fraga, bm25_sok, vektor_sok, hamta=50, topp=50)
    poang = cross_encoder.predict([(fraga, d) for d in kandidater])
    return [d for _, d in sorted(zip(poang, kandidater), reverse=True)][:topp]

regressioner i utvärderingen är det tal som oftast saknas: andelen frågor där någon av delarna hittade rätt men hybriden inte gjorde det. Stiger medelvärdet samtidigt som den siffran är 0,15 har du gjort en sjättedel av frågorna sämre.

Behärskning innebär

  • Implementerar hybridsökning
  • Motiverar RRF framför poängsammanslagning
  • Utvärderar hybriden mot varje del för sig

Logga in för att göra övningarna och bygga upp din behärskning.

Källor

Alla källor och licenser