Asosiy qismga o'tish

Baholash — taxmin qilmang, o'lchang

20-bob 7 kod bloki 906 so'z

Tizim ishlayapti. Lekin qanchalik yaxshi?

"Yaxshi ishlayapti shekilli" — bu javob emas. O'zgartirish kiritganingizda yaxshilandimi yoki yomonlashdimi, bilmaysiz.

Avval retrieval, keyin generatsiya

Javob yomon bo'lsa, ikki sabab bor:

  1. Kerakli chunk topilmadi — LLM qanchalik yaxshi bo'lsa ham yo'q ma'lumotdan javob yasay olmaydi
  2. Chunk topildi, lekin LLM yomon javob yozdi

Birinchisini birinchi tekshiring. Ko'pchilik xatosi: javob yomon bo'lganda darhol promptni o'zgartirishga kirishadi. Agar muammo qidiruvda bo'lsa — prompt hech narsani o'zgartirmaydi.

Gold set

Sizga savol -> to'g'ri chunk juftliklari kerak. Qo'lda tayyorlanadi.

python7 qator
gold = {
    "Nihol narxi qancha?":        {"c_narx_1", "c_narx_2"},
    "Yetkazib berish necha kun?": {"c_yetkazish_1"},
    "Kafolat muddati?":           {"c_kafolat_1"},
    "Qanday to'lash mumkin?":     {"c_tolov_1"},
    "Ofis qayerda?":              {"c_manzil_1"},
}

Nechta savol kerak:

  • 20-30 ta — birinchi baholash uchun
  • 50-100 ta — ishonchli taqqoslash uchun
  • Har bir mavzudan kamida 3-5 ta

Bir marta tayyorlaysiz, keyin har bir o'zgarishda qayta ishlatasiz.

To'rtta metrika

python28 qator
import numpy as np


def hit_at_k(natija, gold_ids, k):
    """Top-k da kamida BITTA to'g'ri chunk bormi (0 yoki 1)."""
    return 1.0 if set(natija[:k]) & gold_ids else 0.0


def recall_at_k(natija, gold_ids, k):
    """Top-k da to'g'ri chunklarning ULUSHI."""
    return len(set(natija[:k]) & gold_ids) / len(gold_ids)


def mrr(natija, gold_ids):
    """Birinchi to'g'ri chunk qaysi o'rinda."""
    for i, chunk in enumerate(natija, 1):
        if chunk in gold_ids:
            return 1.0 / i
    return 0.0


def ndcg_at_k(natija, gold_ids, k):
    """Tartibni hisobga olgan sifat."""
    dcg = sum((1.0 if c in gold_ids else 0.0) / np.log2(i + 1)
              for i, c in enumerate(natija[:k], 1))
    ideal = sum(1.0 / np.log2(i + 1)
                for i in range(1, min(len(gold_ids), k) + 1))
    return dcg / ideal if ideal else 0.0

Nega Hit@k yetarli emas

Ikkita qidiruvni solishtiramiz. Ikkalasi ham kerakli chunkni topadi — lekin biri uni 1-o'ringa, ikkinchisi 5-o'ringa qo'yadi.

matn6 qator
Metrika        zaif   kuchli
-----------------------------
Recall@5      1.000    1.000
Hit@5         1.000    1.000     <- BIR XIL ko'rinadi
MRR           0.200    1.000     <- FARQ shu yerda
NDCG@5        0.387    1.000

Bu raqamlar quyidagi holatdan: to'g'ri chunk zaif da 5-o'rinda, kuchli da 1-o'rinda. MRR = 1/5 = 0.200.

Hit@5 va Recall@5 ikkalasida bir xil. Faqat Hit@k ni o'lchasangiz — ikki tizim bir xil ko'rinadi.

Lekin ular bir xil emas. Tartib muhim, chunki:

  1. LLM ga top-3 yoki top-5 beriladi. Chunk 5-o'rinda bo'lsa, kontekst oxirida qoladi — LLM lar o'rtadagi ma'lumotni yomonroq ishlatadi ("lost in the middle")
  2. top_k ni 3 ga tushirsangiz — chunk butunlay yo'qoladi

Xulosa: MRR va NDCG ishlating. Ular tartibni ko'radi.

Baholash funksiyasi

python17 qator
def baholash(natijalar, gold, nom=""):
    hisobot = {}
    for k in (1, 3, 5):
        hisobot[f"Recall@{k}"] = np.mean(
            [recall_at_k(natijalar[q], g, k) for q, g in gold.items()])
        hisobot[f"Hit@{k}"] = np.mean(
            [hit_at_k(natijalar[q], g, k) for q, g in gold.items()])
    hisobot["MRR"] = np.mean(
        [mrr(natijalar[q], g) for q, g in gold.items()])
    hisobot["NDCG@5"] = np.mean(
        [ndcg_at_k(natijalar[q], g, 5) for q, g in gold.items()])

    if nom:
        print(f"\n{nom}")
        for k, v in hisobot.items():
            print(f"  {k:<10} {v:.3f}")
    return hisobot

Haqiqiy bazada ishlatish

python11 qator
from app.database import SessionLocal
from app.services.search import search_chunks

db = SessionLocal()
natijalar = {
    savol: [c["id"] for c in search_chunks(db, savol, top_k=10)]
    for savol in gold
}
db.close()

baholash(natijalar, gold, "Hozirgi tizim")

Nimani yaxshilash kerak

python11 qator
Recall past (< 0.7)?
    -> chunking o'lchamini o'zgartiring
    -> boshqa embedding modeli sinang
    -> hybrid search qo'shing

Recall yaxshi, MRR past?
    -> RERANKER qo'shing (12-bob)
    -> aynan shu muammoni hal qiladi

Ikkalasi ham yaxshi, javob yomon?
    -> endi PROMPT ustida ishlang (13-bob)

Tartib muhim. Prompt — oxirgi bosqich, birinchi emas.

Reranker foydasini o'lchash

12-bobda aytilgan edi: reranker foydasi Recall@5 da ko'rinmaydi.

python5 qator
oldin = baholash(vektor_natijalari, gold, "Reranker DAN OLDIN")
keyin = baholash(rerank_natijalari, gold, "Reranker DAN KEYIN")

print(f"\nRecall@5 : {keyin['Recall@5'] - oldin['Recall@5']:+.3f}")
print(f"MRR      : {keyin['MRR'] - oldin['MRR']:+.3f}   <- ASOSIY")

Agar Recall@5 o'zgarmasa va MRR oshmasa — reranker sizga kerak emas. WDE Nihol botida aynan shunday: reranker yo'q va tizim yaxshi ishlaydi.

Qaror qabul qilishning to'g'ri yo'li shu: o'lchang, keyin qo'shing. Aksincha emas.


Bu bobni bepul o'qidingiz.

Kitob o'rgatadi, lekin sizning kodingiz nega ishlamaganini ayta olmaydi. Kursda uy vazifangiz tekshiriladi.

Telegramda yozish Narxlar