Asosiy qismga o'tish

Adolatli taqqoslash: model qachon haqiqatan yaxshiroq

24-bob 5 kod bloki 2093 so'z

Bu bobdagi kod shu fayllar bilan ishlaydio'z kompyuteringizda ishga tushiring

Savol oddiy, javob qiyin

"Yangi model eskisidan yaxshiroqmi?" — loyihadagi eng ko'p berilgan savol. Va eng ko'p xato javob berilgan savol. Bu kitobda kamida uch marta noto'g'ri xulosa chiqarildi:

  • tez9 "yomon o'qitilgan" deb topildi — aslida grammar xatosi edi (9-bob);
  • tez4 va tez5 "tez3 dan farqi yo'q" — aslida ular tuzatgan va buzgan savollar har xil edi (19-bob);
  • eval_loss bo'yicha tez7 "eng yaxshi" ko'rinardi — aslida tez8 dan ancha past (20-bob).

Bu bob — shu xatolardan chiqqan qoidalar to'plami. Ular xatolar jurnalining boshida "har bir harakatdan oldin" bo'limida yozilgan:

python5 qator
jurnal = (TEZ.parent.parent / "12-mistakes-and-lessons.md").read_text(encoding="utf-8")
qoidalar = jurnal.split("## Before every action")[1].split("## Mistakes")[0]
for qator in qoidalar.strip().splitlines():
    if qator[:2] in ("3.", "4.", "7.", "9."):
        print(qator)
Chiqish
3. Changing tools.json, the schema or the grammar: generate the real llama.cpp grammar and check key order against the training data order. Rerun the current model before training a new one.
4. Comparing models: same engine, same grammar, same questions, same machine. If the grammar changed, rerun both models; rescoring old answers is not enough.
7. Time estimates: take them from the measured time of the last run of the same job, never from a guess.
9. Never call a model better until the pass rules are measured.

Bu qoidalarni birma-bir ochamiz.

1. Bir xil engine: saqlangan hukmlar eskiradi

Har bir test natijasi faylida modelning javobi va hukm saqlanadi: "same result", "different result" va hokazo. Hukm o'sha kungi engine bilan chiqarilgan. Lekin engine o'zgaradi — masalan, 10-bobdagi imloni hisobga oluvchi = qo'shilgandan keyin "Toshkent" va "Тошкент" bir xil bo'ldi. Eski hukm esa o'zgarmaydi.

Buni tekshiramiz: eski modellarning saqlangan javoblarini bugungi engine bilan qaytadan baholaymiz:

python22 qator
import json
from accuracy.run import open_data, expected_results, judge
from runtime.client import parse_answer

sozlamalar = json.load(open(TEZ / "accuracy/settings.json", encoding="utf-8"))
MANBA = {"loans": "loans.csv", "deposits": "deposits.csv", "cash_desk": "cash_desk.csv", "transactions": "bank.csv"}
ulanish = {t: open_data(f"/mnt/d/Tez_spikes/data/{f}") for t, f in MANBA.items()}
savollar = {t: json.load(open(TEZ / f"accuracy/suites/{t}/questions.json", encoding="utf-8")) for t in MANBA}
kutilgan = {t: expected_results(ulanish[t], savollar[t], sozlamalar) for t in MANBA}

for prefiks in ("ab_A", "pilot4b", "tez3_cpu", "tez4_cpu", "tez5_cpu"):
    saqlangan = qayta = 0
    ozgargan = []
    for t in MANBA:
        sav = {s["id"]: s for s in savollar[t]}
        for j in map(json.loads, open(TEZ / f"results/{prefiks}_{t}.jsonl", encoding="utf-8")):
            togri, sabab = judge(ulanish[t], sav[j["id"]], parse_answer(j["output"]), kutilgan[t], sozlamalar)
            saqlangan += j["correct"]
            qayta += togri
            if togri != j["correct"]:
                ozgargan.append(f"{t} #{j['id']}")
    print(f"{prefiks:9s} saqlangan hukm {saqlangan}, bugungi dvigatel {qayta}  {', '.join(ozgargan)}")
Chiqish
ab_A      saqlangan hukm 164, bugungi dvigatel 165  deposits #3
pilot4b   saqlangan hukm 138, bugungi dvigatel 138  
tez3_cpu  saqlangan hukm 183, bugungi dvigatel 185  deposits #3, deposits #52
tez4_cpu  saqlangan hukm 186, bugungi dvigatel 188  deposits #3, deposits #52
tez5_cpu  saqlangan hukm 183, bugungi dvigatel 183

Bir nechta model uchun hukm o'zgardi — javobi bir xil, lekin bugungi engine uni boshqacha baholaydi. Farq kichik, lekin modellar orasidagi farq ham ko'pincha shunday kichik. Agar bitta model eski hukm bilan, ikkinchisi yangi hukm bilan solishtirilsa — solishtirish yolg'on.

Qoida: solishtirishdan oldin hamma saqlangan javoblar hozirgi engine bilan qaytadan baholanadi. Xatolar jurnalida bu 12-raqam: tez6 ning eski hukmlari bilan sanalgan "different result" soni noto'g'ri chiqqan.

2. Bir xil grammar: qayta baholash yetmaydi

Engine o'zgarsa — saqlangan javobni qayta baholash mumkin. Grammar o'zgarsa — mumkin emas. Chunki grammar modelning javobini o'zgartiradi: eski grammar bilan model boshqa narsa yozgan bo'lardi. 9-bobda tez8 ikkala grammar da bir xil ball oldi, tez9 esa butunlay boshqa. Bu oldindan bilib bo'lmaydi.

Qoida: grammar yoki schema o'zgarsa, solishtiriladigan ikkala model ham yangi grammar bilan qaytadan ishga tushiriladi. Bu qimmat — bitta to'liq benchmark taxminan ikki yarim-uch soat — lekin boshqa yo'l yo'q.

3. Bir xil kompyuter, bir xil sharoit

Tezlik ham test qismi. 6-bobda ko'rdik: GPU va CPU natijalari biroz farq qiladi, birinchi javob sovuq prompt tufayli sekin, va model faylini Windows diskidan o'qish daqiqalar olishi mumkin. Shuning uchun:

  • hamma modellar bir xil kompyuterda, bir xil oqimlar soni bilan;
  • mahsulot qarori — CPU natijasi bo'yicha (tahlilchining kompyuterida GPU yo'q);
  • tezlik — har to'plamning birinchi (sovuq) javobisiz, mediana bilan.

4. Farq tasodifdan kattami

Bu — eng nozik qism. 19-bobda tez3, tez4, tez5 uchalasi ham 200 dan 183 oldi, lekin har xil savollarda xato qildi. Ya'ni ikki training orasida chegaradagi savollar u yoqqa-bu yoqqa og'adi, va bir necha savollik farq hech narsani anglatmaydi.

21-bobda buning uchun bootstrap ishlatildi. Endi teskari savolni beramiz: real jadvallardagi 105 ta savolda yangi model eskisidan qancha yaxshi bo'lsa, farq ishonchli bo'ladi? tez8 ning haqiqiy javoblarini olamiz va tasavvur qilamiz: yangi model tez8 ning xato qilgan savollaridan k tasini to'g'rilaydi, boshqa hech narsani o'zgartirmaydi. Har bir k uchun 95 foiz oraliq:

python17 qator
import glob
import random

real = sorted(Path(f).parent.name for f in glob.glob(str(TEZ / "accuracy/suites/real_*/questions.json")))
tez8 = [int(j["correct"]) for t in real for j in map(json.loads, open(TEZ / f"results/tez8g_cpu_{t}.jsonl", encoding="utf-8"))]

def oraliq(farqlar, marta=10000):
    tasodif = random.Random(1)
    yigindi = sorted(sum(tasodif.choice(farqlar) for _ in farqlar) for _ in range(marta))
    return yigindi[int(0.025 * marta)], yigindi[int(0.975 * marta)]

xatolar = [i for i, togri in enumerate(tez8) if not togri]
print(f"real savollar: {len(tez8)}, tez8 to'g'ri: {sum(tez8)}, xato: {len(xatolar)}")
for k in (2, 4, 6, 8, 10):
    farqlar = [1 if i in xatolar[:k] else 0 for i in range(len(tez8))]
    past, yuqori = oraliq(farqlar)
    print(f"yangi model {k:2d} ta savolni tuzatsa: 95% oraliq [{past:+d}, {yuqori:+d}]  {'ishonchli' if past > 0 else 'tasodif bo`lishi mumkin'}")
Chiqish
real savollar: 105, tez8 to'g'ri: 81, xato: 24
yangi model  2 ta savolni tuzatsa: 95% oraliq [+0, +5]  tasodif bo`lishi mumkin
yangi model  4 ta savolni tuzatsa: 95% oraliq [+1, +8]  ishonchli
yangi model  6 ta savolni tuzatsa: 95% oraliq [+2, +11]  ishonchli
yangi model  8 ta savolni tuzatsa: 95% oraliq [+3, +14]  ishonchli
yangi model 10 ta savolni tuzatsa: 95% oraliq [+5, +16]  ishonchli

Bu eng optimistik holat — yangi model hech narsani buzmaydi. Shunda ikki savollik farq hali tasodif bo'lishi mumkin, to'rt savoldan boshlab esa ishonchli. Lekin haqiqiy hayotda yangi model ba'zi savollarni buzadi ham (19-bobda har safar buzdi), va oraliq kengayadi:

python4 qator
for tuzaldi, buzildi in ((6, 2), (8, 2), (10, 3)):
    farqlar = [1] * tuzaldi + [-1] * buzildi + [0] * (len(tez8) - tuzaldi - buzildi)
    past, yuqori = oraliq(farqlar)
    print(f"tuzaldi {tuzaldi:2d}, buzildi {buzildi}: farq {tuzaldi - buzildi:+d}, 95% oraliq [{past:+d}, {yuqori:+d}]")
Chiqish
tuzaldi  6, buzildi 2: farq +4, 95% oraliq [-1, +10]
tuzaldi  8, buzildi 2: farq +6, 95% oraliq [+0, +12]
tuzaldi 10, buzildi 3: farq +7, 95% oraliq [+0, +14]

Buzilishlar bilan rasm boshqacha: o'nta tuzatib, uchtani buzgan model — sof yutuq yetti savol — hali ham chegarada, pastki chegara nolda.

Qoida: real jadvallarda yangi model faqat farq oralig'ining pastki chegarasi noldan yuqori bo'lsa "yaxshiroq" deyiladi. 105 ta savol bilan, odatdagi buzilishlarni hisobga olsak, bu yettidan ko'p savollik sof yutuqni talab qiladi. Shuning uchun real to'plamni kattalashtirish — keyingi ishlardan biri (29-bob).

5. Ship qoidalari — oldindan

Eng muhim qoida — psixologik. Yangi model ustida haftalab ishlagandan keyin, uning natijasini "yaxshi" deb ko'rish istagi juda kuchli. tez9 bunga misol: birinchi natija kelganda "biroz yomon, lekin tool_gap da yutdi" degan fikr paydo bo'ldi.

Himoya — qoidalarni trainingdan oldin yozib qo'yish. tez9 dan beri har bir model uchun oldindan yozilgan:

Qoida Nima uchun
toza to'plamlar kamida 97 foiz asosiy ko'nikmalar yo'qolmasin
real to'plamlarda tez8 dan aniq yuqori haqiqiy hayotda yaxshiroq bo'lsin
hech bir tuzoq turi yomonlashmasin bitta yutuq boshqa yo'qotishni yashirmasin
aniq so'rovga ortiqcha savol ko'pi bilan 1 foiz model hamma narsani so'rab, foydalanuvchini charchatmasin
real jadvallarda javob aniqligi (precision) kamida 90 foiz javob berganda — to'g'ri bo'lsin; bank uchun bu asosiy ko'rsatkich

Oxirgi qator bu kitob yozilgan kuni qo'shildi. Sababi — tez8 ning real jadvallardagi natijasini confusion matrix ko'rinishida yozganimizda chiqqan rasm:

python9 qator
from collections import Counter

sabab = Counter(json.loads(q)["reason"].split(":")[0] for t in real
                for q in open(TEZ / f"results/tez8g_cpu_{t}.jsonl", encoding="utf-8"))
reja_berdi = sabab["same result"] + sabab["different result"] + sabab["guessed instead of asking"]
print(f"javob berish kerak edi:  to'g'ri reja {sabab['same result']}, noto'g'ri reja {sabab['different result']}, savol berdi {sabab['asked on a clear request']}")
print(f"savol berish kerak edi:  taxmin qildi {sabab['guessed instead of asking']}, savol berdi {sabab['asked']}")
print(f"javob aniqligi (precision): {sabab['same result']}/{reja_berdi} = {sabab['same result'] / reja_berdi:.1%}")
print(f"savol to'liqligi (ask recall): {sabab['asked']}/{sabab['asked'] + sabab['guessed instead of asking']}")
Chiqish
javob berish kerak edi:  to'g'ri reja 75, noto'g'ri reja 15, savol berdi 0
savol berish kerak edi:  taxmin qildi 9, savol berdi 6
javob aniqligi (precision): 75/99 = 75.8%
savol to'liqligi (ask recall): 6/15

Model deyarli hech qachon "bilmayman" demaydi — va buning narxi: har to'rtta javobdan biri noto'g'ri. Bank uchun bunday xato — eng qimmat xato (false positive): ishonchli ko'rinadigan noto'g'ri raqam. Shuning uchun asosiy ko'rsatkich — umumiy ball emas, javob aniqligi: model kamroq javob bersin, lekin bergan javobi to'g'ri bo'lsin.

tez9 bu qoidalar bo'yicha o'tmadi — natija kelgandan keyin qoidalarni o'zgartirish mumkin bo'lmagani uchun, bahs ham bo'lmadi. Bu qoidalarga keyinroq gallyutsinatsiya ulushi (noto'g'ri plan va berilmagan savol yig'indisi, 10-bob) ham qo'shildi.

6. Bitta o'zgarish

Va oxirgi qoida 21-bobdan: imkon qadar har bir modelda bitta narsani o'zgartirish. Bu solishtirishni adolatli qilmaydi — u solishtirishni tushunarli qiladi. tez10 shu qoida bilan yasalgan: tez8 ning aynan o'zi va 858 ta "qila olmayman" qatori. Uning natijasi har qanday bo'lsa ham, biz nima sababdan ekanini bilamiz.

Xulosa

  • Saqlangan hukmlar engine bilan birga eskiradi: solishtirishdan oldin hammasi qaytadan baholanadi.
  • Grammar o'zgarsa, qayta baholash yetmaydi: ikkala model ham qaytadan ishga tushiriladi.
  • Tezlik va aniqlik bir xil kompyuterda, bir xil sharoitda o'lchanadi; qaror CPU natijasi bo'yicha.
  • Farq bootstrap oralig'i bilan tekshiriladi; 105 ta real savolda ishonchli yutuq uchun taxminan yetti savol kerak.
  • Ship qoidalari trainingdan oldin yoziladi va natija kelgandan keyin o'zgarmaydi.
  • Bitta o'zgarish — natijani tushunarli qiladi.

Bu bobni bepul o'qidingiz.

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

Telegramda yozish Narxlar