Round-trip check (qaytish tekshiruvi): matn planni bildiradimi
Muammo
12-bobda o'qituvchi planni so'rov matniga aylantirdi. 13-bobda plan to'g'ri ekanini ko'rdik: uni kod yasaydi va DuckDB tekshiradi. Lekin matn-chi? O'qituvchi planni noto'g'ri tushunishi, biror filtrni tashlab ketishi yoki o'zidan qo'shishi mumkin. Unda training ma'lumotida "so'rov A → plan B" juftligi paydo bo'ladi — va model A ni ko'rganda B ni yozishni o'rganadi. Bu eng xavfli xato: modelga yolg'on o'rgatiladi.
Uni ushlash uchun Tezda round-trip check (qaytish tekshiruvi) bor. G'oyasi oddiy:
- O'qituvchi plandan matn yozdi.
- Endi matnni boshqa "o'quvchi"ga beramiz — u planni ko'rmaydi, faqat matnni va jadvalni. U matndan o'z planini yozadi.
- Ikkala plan DuckDB da bajariladi. Natijalar bir xil bo'lsa — matn planni to'g'ri ifodalaydi. Farq qilsa — juftlik tashlanadi.
Matnni tekshiruvchi — yana Gemma, lekin endi boshqa rolda: Tez modeli o'rnida, Tez promptini olib, foydalanuvchi so'rovini planga aylantiradi.
Tekshiruv kodi
training/roundtrip.py dagi verify funksiyasi:
import inspect from training import roundtrip print(inspect.getsource(roundtrip.verify))
def verify(con, table, plan, answer, settings, history=()):
if not isinstance(answer, dict) or not ({"ask", "steps"} & answer.keys()):
return False, "invalid answer"
if "ask" in answer:
return False, "asked instead of acting"
if plan.get("on") == "table":
answer = {**answer, "on": "table"}
try:
result = run_plan(con, table, effective_steps(history, answer), settings)
except (ToolError, KeyError, TypeError) as error:
return False, f"plan failed: {error}"
expected_ops, answer_ops = operators(plan["steps"]), operators(answer["steps"])
if any(key in answer_ops and answer_ops[key] != op for key, op in expected_ops.items()):
return False, "different operator"
expected = run_plan(con, table, effective_steps(history, plan), settings)
return (True, "same result") if same_result(result, expected, settings) else (False, "different result")
To'rtta rad etish sababi bor:
- invalid answer / asked instead of acting — tekshiruvchi plan o'rniga savol berdi. Demak matn noaniq: aniq so'rovdan plan chiqishi kerak edi.
- plan failed — tekshiruvchining plani bajarilmadi.
- different operator — natija tasodifan bir xil bo'lsa ham, bitta ustun va bitta qiymatga
boshqa amal qo'llangan (masalan,
=o'rnigacontains). Bu ataylab qat'iy qoida: "Sergeli" bilancontainsva=bu jadvalda bir xil natija berishi mumkin, lekin ular boshqa ma'no. - different result — planlar boshqa natija berdi.
Haqiqiy natijalar
Saqlangan partiyalardan sakkizta qismni olamiz — ularning hammasida jarayon bir xil: o'qituvchi matnlari, birinchi tekshiruv (Gemma), rad etilganlar uchun ikkinchi tekshiruv. Fayllar o'zgartirilmasdan o'qiladi:
import json import pandas as pd QISMLAR = [("batch10", "short45"), ("batch12", "conditions"), ("batch12", "newtables"), ("batch12", "ranges"), ("batch12", "totals"), ("batch13", "newshort"), ("batch13", "newshort2"), ("batch13", "toolshort")] def oqi(fayl): return [json.loads(q) for q in open(fayl, encoding="utf-8")] jadval = [] for partiya, qism in QISMLAR: papka = f"/mnt/d/Tez_spikes/{partiya}" jadval.append({ "qism": f"{partiya}/{qism}", "matnlar": len(oqi(f"{papka}/phrases_{qism}.jsonl")), "birinchida_otdi": len(oqi(f"{papka}/kept_{qism}.jsonl")), "qutqarildi": len(oqi(f"{papka}/rescued_{qism}.jsonl")), "tashlandi": len(oqi(f"{papka}/final_rejected_{qism}.jsonl")), }) jadval = pd.DataFrame(jadval) show(jadval) jami = jadval.sum(numeric_only=True) print(f"jami matn: {jami['matnlar']:,}") print(f"birinchi tekshiruvdan o'tdi: {jami['birinchida_otdi'] / jami['matnlar']:.0%}") print(f"oxirida tashlandi: {jami['tashlandi'] / jami['matnlar']:.0%}")
jami matn: 6,040 birinchi tekshiruvdan o'tdi: 77% oxirida tashlandi: 13%
| qism | matnlar | birinchida_otdi | qutqarildi | tashlandi |
|---|---|---|---|---|
| batch10/short45 | 1 358 | 909 | 142 | 307 |
| batch12/conditions | 449 | 360 | 42 | 47 |
| batch12/newtables | 1 511 | 1 216 | 179 | 116 |
| batch12/ranges | 400 | 378 | 11 | 11 |
| batch12/totals | 533 | 441 | 41 | 51 |
| batch13/newshort | 1 018 | 762 | 136 | 120 |
| batch13/newshort2 | 323 | 240 | 38 | 45 |
| batch13/toolshort | 448 | 327 | 25 | 96 |
Har to'rt-besh matndan biri birinchi tekshiruvdan o'tmadi. Eng ko'p rad etilgan qismlar — "short45" va "toolshort", ya'ni ataylab qisqa yozilgan so'rovlar: qisqa matnda ma'no oson yo'qoladi.
Rad etish sabablari:
from collections import Counter birinchi, oxirgi = Counter(), Counter() for partiya, qism in QISMLAR: papka = f"/mnt/d/Tez_spikes/{partiya}" birinchi.update(y["check"].split(":")[0] for y in oqi(f"{papka}/rejected_{qism}.jsonl")) oxirgi.update(y["check"].split(":")[0] for y in oqi(f"{papka}/final_rejected_{qism}.jsonl")) print("birinchi tekshiruvda:", dict(birinchi)) print("oxirida tashlanganlar:", dict(oxirgi))
birinchi tekshiruvda: {'different result': 1148, 'plan failed': 87, 'different operator': 167, 'asked instead of acting': 5}
oxirida tashlanganlar: {'different result': 697, 'asked instead of acting': 12, 'plan failed': 17, 'different operator': 67}
Ko'pchilik — "different result": matn boshqa planga olib keldi.
Tashlangan matnlarni o'qiymiz
Raqam yetarli emas — tashlangan matnlarning o'zini ko'rish kerak:
for y in oqi("/mnt/d/Tez_spikes/batch12/final_rejected_conditions.jsonl")[:3]: print(f"[{y['language']}] {y['text']}") print(f" reja: {json.dumps(y['plan']['steps'], ensure_ascii=False)[:150]}") print(f" tekshiruvchi: {y['checker_output'][:150]}") print(f" sabab: {y['check']}") print()
[en] keep rows where amount is greater than 0
reja: [{"args": {"column": "summa_uzs", "op": ">", "value": 0}, "tool": "filter"}]
tekshiruvchi: {"steps":[{"tool":"filter","args":{"column":"miqdor","op":">","value":0}}]}
sabab: different result
[uz_latin] Faqat 'Sergeli' bor do'konlarni qoldir. Har bir do'kon bo'yicha miqdorning o'rtachasini hisobla va eng yuqori 3 tasini ko'rsat.
reja: [{"args": {"column": "dokon", "op": "contains", "value": "Sergeli"}, "tool": "filter"}, {"args": {"by": "miqdor", "descending": true, "group": {"agg":
tekshiruvchi: {"steps":[{"tool":"filter","args":{"column":"dokon","op":"=","value":"Sergeli"}},{"tool":"top_n","args":{"by":"miqdor","n":3,"descending":true,"group"
sabab: different operator
[uz_cyrillic] Фақат 'Sergeli' бор дўконларни қолдир. Ҳар бир дўкон бўйича миқдорнинг ўртачасини ҳисобла ва энг юқори 3 тасини кўрсат.
reja: [{"args": {"column": "dokon", "op": "contains", "value": "Sergeli"}, "tool": "filter"}, {"args": {"by": "miqdor", "descending": true, "group": {"agg":
tekshiruvchi: {"steps":[{"tool":"filter","args":{"column":"dokon","op":"=","value":"Sergeli"}},{"tool":"top_n","args":{"by":"miqdor","n":3,"descending":true,"group"
sabab: different operator
Birinchi misol juda ibratli: "keep rows where amount is greater than 0". Planda — summa_uzs.
Tekshiruvchi esa "amount" ni "miqdor" (dona soni) deb tushundi. Inglizcha "amount" ikkala ma'noni
beradi — matn haqiqatan noaniq, va tekshiruv uni to'g'ri tashladi.
Ikkinchi va uchinchi: matn va plan ma'nosi bir xil ko'rinadi, lekin planda contains, tekshiruvchida
=. "different operator" qoidasi ularni tashladi. Bu qoida bu yerda ortiqcha qattiq bo'lishi mumkin
— lekin biz bir nechta yaxshi juftlikni yo'qotishni yomon juftlikni o'tkazib yuborishdan afzal
ko'rdik.
Endi 12-bobdagi misolni eslang: o'sha planning o'zbekcha matni — "amount dan katta 0" — o'tgan edi. Bir xil so'z, bir xil plan: inglizcha matn tashlandi, o'zbekcha matn o'tdi. Tekshiruvchi ikkinchi holatda to'g'ri ustunni tanlagan. Bu tekshiruvning tabiati: u har bir matnni alohida, bitta tekshiruvchining bitta javobi bilan baholaydi. U ma'noni tekshiradi — til sifatini emas, va bitta urinish bilan.
Qutqarish
Ikkinchi tekshiruv — rescue (qutqarish). Birinchi tekshiruvda rad etilgan matnlar boshqa tekshiruvchiga beriladi, va u ham shu planni tiklasa — matn qaytariladi. Ikkinchi tekshiruvchi — o'sha paytdagi eng yaxshi Tez modeli, tez8.
Nega? Chunki Gemma ham xato qiladi. Ba'zi matnlar to'g'ri, lekin Gemma ularni noto'g'ri o'qigan. Qutqarilgan misollar:
for y in oqi("/mnt/d/Tez_spikes/batch12/rescued_conditions.jsonl")[:3]: print(f"[{y['language']}] {y['text']}") print(f" reja: {json.dumps(y['plan']['steps'], ensure_ascii=False)[:140]}") print() qutqarilgan = jami["qutqarildi"] qabul = jami["birinchida_otdi"] + qutqarilgan print(f"qabul qilingan juftliklarning qutqarilgan ulushi: {qutqarilgan / qabul:.0%}")
[uz_cyrillic] Фойда бўйича 0 дан катта бўлган қаторларни олиб ташла.
reja: [{"args": {"column": "foyda_uzs", "op": ">", "value": 0}, "tool": "remove_rows"}]
[uz_latin] Iltimos, 'Sardor Tursunov' so'zini o'z ichiga oluvchi sotuvchi qatorlarini olib tashlang, qolgan barcha qatorlar qolsin.
reja: [{"args": {"column": "sotuvchi", "op": "contains", "value": "Tursunov"}, "tool": "remove_rows"}]
[uz_latin] Faqat miqdor 0 dan kichik bo'lgan qatorlarni va mahsulot hamda do'kon ustunlariga ega natijani jadval ko'rinishida bera olasizmi?
reja: [{"args": {"column": "miqdor", "op": "<", "value": 0}, "tool": "filter"}, {"args": {"columns": ["mahsulot", "dokon"]}, "tool": "select_colum
qabul qilingan juftliklarning qutqarilgan ulushi: 12%
Qutqarishning yashirin narxi
Bu yerda bitta nozik muammo bor, va uni ochiq aytish kerak. Qutqaruvchi — tez8, ya'ni o'quvchining o'zi. U faqat o'zi allaqachon to'g'ri tushunadigan matnlarni qutqaradi. Demak qutqarilgan juftliklar modelga yangi narsa o'rgatmaydi — ular uning hozirgi bilimini takrorlaydi. Model noto'g'ri tushunadigan, lekin to'g'ri matnlar esa — aynan eng foydali o'quv materiali — ikkala tekshiruvdan ham o'tmaydi va tashlanadi.
Bu xato emas, lekin cheklov: tekshiruv tizimi training ma'lumotini modelning hozirgi imkoniyatlariga qarab biroz "siljitadi". Bu ulushni o'lchab bordik (yuqorida), lekin uning modelga ta'sirini alohida o'lchamaganmiz.
Tillar bo'yicha
Oxirgi savol: tekshiruv biror tilni boshqasidan ko'proq tashlaydimi? Har bir til uchun qabul qilingan ulush:
tillar = {} for partiya, qism in QISMLAR: papka = f"/mnt/d/Tez_spikes/{partiya}" for y in oqi(f"{papka}/phrases_{qism}.jsonl"): tillar.setdefault(y["language"], [0, 0])[0] += 1 for fayl in (f"{papka}/kept_{qism}.jsonl", f"{papka}/rescued_{qism}.jsonl"): for y in oqi(fayl): tillar[y["language"]][1] += 1 for til, (hammasi, qabul_) in tillar.items(): print(f"{til:12s} {qabul_:,} / {hammasi:,} = {qabul_ / hammasi:.0%}")
uz_latin 990 / 1,145 = 86% uz_cyrillic 982 / 1,145 = 86% ru 1,126 / 1,325 = 85% en 1,156 / 1,274 = 91% mixed 993 / 1,151 = 86%
Ulushlar yaqin. Inglizcha biroz yuqori. Ehtimol sababi — o'qituvchi ham, tekshiruvchi ham ingliz tilida eng ko'p o'rgatilgan modellar; buni alohida tekshirmaganmiz. O'zbek kirill lotin bilan deyarli bir xil — ajablanarli emas: kirill matni lotin matnidan kod bilan o'girilgan (12-bob), ma'nosi bir xil.
Xulosa
- Qaytish tekshiruvi: boshqa model matndan planni qayta tiklaydi; natija bir xil bo'lmasa, juftlik tashlanadi. Bu trainingga yolg'on juftlik kirishining asosiy to'sig'i.
- Saqlangan sakkizta qismda har to'rt-besh matndan biri birinchi tekshiruvdan o'tmagan; asosiy sabab — boshqa natija.
- Tekshiruv ma'noni tekshiradi, til sifatini emas, va har matnga bitta urinish beradi.
- Qutqarish ba'zi to'g'ri matnlarni qaytaradi, lekin qutqaruvchi — o'quvchining o'zi; bu ma'lumotni model allaqachon biladigan narsaga siljitadi.
- Tillar bo'yicha qabul ulushi yaqin; ingliz tili biroz yuqori.
Keyingi bobda tekshirilgan juftliklar training fayliga qanday aylanishini ko'ramiz: eksport, sarlavhalarni qayta nomlash va shovqin.
Bu bobni bepul o'qidingiz.
Kitob o'rgatadi, lekin sizning kodingiz nega ishlamaganini ayta olmaydi. Kursda uy vazifangiz tekshiriladi.