- Je berekent precision, recall en reciprocal rank op vaste relevante bronlabels.
- Je onderscheidt ontbrekend bewijs van een fout antwoord ondanks gevonden bewijs.
Probeer ook echte tekstvectoren in het embedding-lab.
Je RAG-systeem geeft soms een fout antwoord. Je kunt dan meer passages ophalen, de context veranderen of het antwoordmodel aanpassen. Eerst moet je weten welke stap faalt.
We bewaren daarom zowel gevonden bron-IDs als het uiteindelijke antwoord. Onze labels en deterministische maten zijn eigen onderwijsbeleid. Onderzoek zoals Ragas onderscheidt eveneens retrieval en generatie; we reproduceren hier geen Ragas-implementatie of LLM-beoordelaar.
1. Kijk naar bronnen én hun rang
Een relevante passage op plek één is eerder bruikbaar dan dezelfde passage op plek tien. Maar één gevonden bron zegt niet hoeveel andere relevante bronnen ontbreken. We meten beide aspecten.
Neem ranking a,x,b en relevante bronnen a,b. Binnen de eerste twee resultaten zit één relevante bron: H=1. Precision deelt door k=2, recall door de twee relevante IDs en reciprocal rank gebruikt de eerste relevante positie r=1.
G is de set gelabelde relevante bronnen. Zonder relevante hit is RR nul; zonder relevante labels zijn recall en RR hier None. Zulke vragen tellen niet mee in de gemiddelde retrievalscores.
for k in [2, 4]:
m = retrieval_metrics(
['a', 'x', 'b'], ['a', 'b'], k)
print(m['precision'],
m['recall'], m['rr'])
# 0.5 0.5 1.0
# 0.5 1.0 1.0
Bij k=4 geeft onze lijst maar drie resultaten. Toch blijft de precisionnoemer vier: lege plaatsen tellen volgens deze afspraak mee. RR kijkt uitsluitend naar de eerste relevante hit, niet naar een gemiddelde van alle relevante rangen.
Schrijf retrieval_metrics met unieke begrensde IDs en een echte integer k van 1 tot 1000. Ongeldige invoer geeft ValueError.
012. Geef iedere vraag hetzelfde gewicht
Vraag q1 heeft één relevante bron en vindt die. Vraag q2 heeft drie relevante bronnen en vindt er één. Het gemiddelde van de recalls is dus (1+1/3)/2=2/3.
Twee gevonden bronnen gedeeld door vier relevante bronnen geeft .5. Dat is een andere, gepoolde maat. Onze evaluate_rag gebruikt het gemiddelde per antwoordbare vraag.
Met m antwoordbare vragen middelen we hun afzonderlijke recalls R_i en reciprocal ranks RR_i. Ook een antwoordbare vraag met een gemiste bron blijft met nul meetellen.
def example(i, ranking, relevant,
answer, expected, ok):
return dict(
id=i, ranking=ranking,
relevant=relevant,
answer=answer,
expected=expected, supported=ok)
a = example('q1', ['a'], ['a'],
'ja', 'ja', True)
b = example(
'q2', ['b'], ['b', 'c', 'd'],
'nee', 'ja', False)
print(round(evaluate_rag(
[a, b], 1)['recall'], 3))
# 0.667
Voeg een antwoordbare vraag met een gemiste bron toe. Daarnaast zijn er twee expliciet onbeantwoordbare vragen: eenmaal onthoudt het systeem zich terecht, eenmaal verzint het een antwoord.
None betekent onthouden. De boolean supported komt uit een aparte beoordeling tegen de gebruikte context; gelijkheid met expected bepaalt die boolean niet. In onze oefenset zijn antwoorden exacte teksten of labels.
miss = example('q3', ['x'], ['e'],
None, 'ja', False)
unknown = example('q4', [], [],
None, None, False)
invented = example(
'q5', [], [],
'fantasie', None, False)
rows = [a, b, miss, unknown, invented]
r = evaluate_rag(rows, 1)
for key in ['accuracy', 'coverage',
'faithfulness',
'unknown_abstention']:
print(key, round(r[key], 3))
print(round(r['recall'], 3),
round(r['mrr'], 3))
# accuracy 0.4
# coverage 0.6
# faithfulness 0.333
# unknown_abstention 0.5
# 0.444 0.667
Van vijf vragen zijn q1 en q4 goed, drie krijgen antwoord en daarvan heeft alleen q1 ondersteuning. Van de twee onbeantwoordbare vragen krijgt er één terecht geen antwoord. De vier concrete verhoudingen hieronder gebruiken dus verschillende noemers.
A staat voor accuracy, C voor coverage, F voor faithfulness en U voor unknown_abstention. De gemiddelde recall is nu (1+1/3+0)/3=4/9; MRR is (1+1+0)/3=2/3. Alleen de drie antwoordbare vragen doen aan die gemiddelden mee.
Schrijf evaluate_rag met de gegeven rijvalidatie en verander de invoer niet. Gebruik None bij een lege groepsnoemer. Onbekende goldlabels betekenen in echte data niet automatisch onbeantwoordbaar: onze onderwijsset legt die status expliciet vast.
Drie vragen hebben één voldoende bron; één vraag is expliciet onbeantwoordbaar. Verander onderdelen en vergelijk de noemers.
Bron in top 1: jaAntwoord: tienjuist en ondersteund
Bron in top 1: neeAntwoord: onthoudenbron gemist
Bron in top 1: jaAntwoord: vijfverkeerd antwoord
Bron in top 1: neeAntwoord: onthoudenterecht onthouden
Alleen de zoekstap herstellen verandert vraag 2 in onnodig onthouden. Voor een goed antwoord moet ook de generator het aanwezige bewijs gebruiken. Extra gokken verhoogt dekking, maar kan juistheid en ondersteuning verlagen.
3. Leg de diagnosevolgorde vast
Bij q3 ontbreken relevante bronnen én een antwoord. Onze diagnose noemt eerst retrieval-miss. Daardoor verdwijnt de gemiste bron niet achter een latere onthoudingsbeslissing.
for result in diagnose_rag(rows, 1):
print(result['id'],
result['reason'])
# q1 supported-correct
# q2 wrong-answer
# q3 retrieval-miss
# q4 appropriate-abstention
# q5 unanswerable-answered
Schrijf diagnose_rag met de beschreven prioriteit. Controleer eerst onbeantwoordbaarheid, dan een retrieval-miss, vervolgens ontbrekend antwoord, antwoordgelijkheid en ondersteuning. Met een gevonden bron maar zonder antwoord volgt unnecessary-abstention; een goed maar onondersteund antwoord heet unsupported-answer.
Een diagnose bewijst geen causale oorzaak. Proberen met de juiste context kan helpen onderzoeken welke stap doorslaggevend was. Onze relevante IDs zijn alternatieve voldoende bronnen; vragen die twee bronnen tegelijk vereisen, vragen een ander bewijsprotocol.
03Wat neem je mee?
- Precision, recall en RR beantwoorden verschillende retrievalvragen.
- Accuracy, antwoorddekking en ondersteuning gebruiken verschillende cohorten.
- Een vaste foutprioriteit maakt vergelijken mogelijk, maar bewijst geen causaliteit.
In het notebook kiezen we op dev en meten we de bevroren RAG-variant op test.