Adolatli taqqoslash: model qachon haqiqatan yaxshiroq
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:
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)
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:
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)}")
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:
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'}")
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:
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}]")
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:
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']}")
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.