Eksport: tekshirilgan juftliklardan training fayliga
Oxirgi bosqich
13-bobda plan yasaldi, 12-bobda unga matn yozildi, 14-bobda matn tekshirildi. Endi bizda minglab "so'rov → plan" juftliklari bor. Ularni train qilish fayliga aylantirish kerak — model ko'radigan aniq shaklda: tizim prompti, foydalanuvchi so'rovi va javob.
Bu ishni training/export.py qiladi. U oddiy "nusxa ko'chirish" emas: bu yerda yana uchta
filtr, test savollarini to'sish, sarlavhalarni qayta nomlash, shovqin qo'shish va ikki tuzoq bor.
Bu bobda eksportni kichik hajmda, haqiqiy kod bilan, haqiqiy saqlangan juftliklar ustida
ishga tushiramiz.
Kichik eksport
12-partiyadan tekshiruvdan o'tgan uch yuzta juftlikni olamiz va eksportning har bir bosqichidan o'tkazamiz:
import json import random import glob from prompt import build from training.export import (blocked_texts, canonical_answer, clean_pairs, contradicts, load_coded, states_numbers, training_rows, valid_pairs) from training.rename import load_headers sozlama = json.load(open(TEZ / "training/settings.json", encoding="utf-8")) eksport = {**sozlama["export"], "rename": {**sozlama["export"]["rename"], "code_share": 0.0}} juftliklar = [json.loads(q) for q in open("/mnt/d/Tez_spikes/batch12/kept_conditions.jsonl", encoding="utf-8")][:300] jadvallar = {j["domain"] for j in juftliklar} kontekstlar = {d: {**build.load_context(TEZ / "prompt/domains" / d), "coded": load_coded(TEZ / "prompt/domains" / d)} for d in jadvallar} tasodif = random.Random(1) tasodif.shuffle(juftliklar) toza = clean_pairs(juftliklar, kontekstlar, sozlama["raw_name_marker"]) sozlar = json.load(open(TEZ / "training/operator_words.json", encoding="utf-8")) sonlar = json.load(open(TEZ / "training/number_words.json", encoding="utf-8")) mos = [j for j in toza if not contradicts(j["text"], j["plan"], sozlar) and states_numbers(j["text"], j["plan"], sonlar)] yaroqli = valid_pairs(mos, kontekstlar) shovqin = {**json.load(open(TEZ / "accuracy/noise.json", encoding="utf-8")), **eksport["noise"]} sarlavhalar = {d: load_headers(TEZ / "prompt/domains" / d) for d in jadvallar} toplamlar = glob.glob(str(TEZ / "accuracy/suites/*/questions.json")) tosilgan = blocked_texts(toplamlar) qatorlar = training_rows(yaroqli, kontekstlar, tosilgan, eksport, tasodif, sarlavhalar, shovqin) print(f"juftliklar: {len(juftliklar)}") print(f"ustun nomi qolib ketgan matnsiz: {len(toza)}") print(f"amal so'zi va son mos kelganlari: {len(mos)}") print(f"schema ga mos rejalar: {len(yaroqli)}") print(f"o'qitish qatorlari: {len(qatorlar)}") print(f"to'silgan test matnlari: {len(tosilgan)}")
juftliklar: 300 ustun nomi qolib ketgan matnsiz: 300 amal so'zi va son mos kelganlari: 297 schema ga mos rejalar: 297 o'qitish qatorlari: 286 to'silgan test matnlari: 516
Har bosqich nimani tekshiradi:
- Ustun nomi — 12-bobdagi
leakedtekshiruvi yana bir marta, eksport paytida. - Amal so'zlari — matnda "kamida" (
>=) deyilib, planda>bo'lsa, juftlik tashlanadi. Tekshiruvchi bunday nozik farqni ko'pincha sezmaydi, chunki ko'p jadvallarda natija bir xil chiqadi. - Sonlar — planda "eng katta 5 ta" bo'lsa, matnda 5 (yoki "besh", "пять", "five") bo'lishi kerak.
- Schema — plan hozirgi toollar schema siga mos bo'lishi kerak. Tool o'zgarsa, eski planlar shu yerda ushlanadi.
Oxirgi bosqichda yana bir nechta juftlik tushib qoldi — takrorlar va test savollari.
Test savollari hech qachon trainingga kirmaydi
Bu — eng muhim qoidalardan biri. Agar biror test savolining matni train qilish ma'lumotida bo'lsa, model uni yodlab oladi va test natijasi yolg'on bo'ladi. Shuning uchun eksport hamma test to'plamlaridagi savollar matnini oladi va ular bilan bir xil juftliklarni tashlaydi. Solishtirish normallashtirilgan matn bo'yicha: kichik harf, tinish belgilarisiz, apostroflar bir xil.
Bu qoida ishlaganmi? tez8 ning training fayli bilan hamma 745 test savolini solishtiramiz:
from training.export import normalise oqitish = {normalise([x for x in json.loads(q)["messages"] if x["role"] == "user"][-1]["content"].split("\n[")[0]) for q in open(Path.home() / "tez_runs/tez8/data/train.jsonl", encoding="utf-8")} savollar = [normalise(s["text"]) for f in toplamlar for s in json.load(open(f, encoding="utf-8"))] print(f"o'qitish so'rovlari: {len(oqitish):,}, test savollari: {len(savollar)}") print(f"o'qitishda aynan uchraydigan test savollari: {sum(s in oqitish for s in savollar)}")
o'qitish so'rovlari: 18,304, test savollari: 745 o'qitishda aynan uchraydigan test savollari: 0
Nol. Lekin bu to'liq kafolat emas: to'siq faqat aynan bir xil matnni ushlaydi. Ma'nosi bir xil, lekin boshqacha yozilgan so'rov o'tib ketishi mumkin. Shuning uchun 6-qismdagi eng muhim test to'plamlari — real jadvallar ustidagi savollar: trainingda bu jadvallarning o'zi yo'q.
Bitta qator ichida
Endi tayyor training qatorlaridan birini to'liq ko'raylik:
qator = qatorlar[0] tizim = qator["messages"][0]["content"] print("TIZIM (oxiri):") print(tizim[tizim.index("Table "):][:900]) print() print("FOYDALANUVCHI:", qator["messages"][1]["content"]) print("JAVOB: ", qator["messages"][2]["content"])
TIZIM (oxiri):
Table "bank_shares" (5,000 rows). Columns with type and sample values:
- bank (text), all values: ["Aloqa bank", "Agrobank", "Ipoteka-bank", "Asaka bank", "Hamkorbank", "Kapital bank", "Turon bank", "Davr bank", "Trast bank", "Xalq banki"]
- viloyat (text), all values: ["Toshkent sh.", "Farg'ona", "Buxoro", "Xorazm", "Namangan", "Qashqadaryo", "Andijon", "Samarqand"]
- sana (date), samples: ["2024-09-23", "2025-02-13", "2025-03-30"]
- kredit_mlrd (number), samples: [10371.1, 10896.2, 11129.7]
- kredit_ulushi (number), samples: [0.22, 0.024, 0.117]
- depozit_ulushi (number), samples: [0.075, 0.233, 0.236]
- npl_ulushi (number), samples: [0.086, 0.116, 0.038]
- osish (number), samples: [-0.06, -0.05, -0.1]
Glossary of business words:
- ulush / доля / share: share columns are fractions: 0.18 means 18%
- muammoli kreditlar / проблемные кредиты / NPL: column npl_ulushi
- o'sish / рост / grow
FOYDALANUVCHI: Илтимос, фақат жорий йилга оид қаторларни сақланг.
JAVOB: {"steps":[{"tool":"filter_dates","args":{"column":"sana","period":"this_year"}}]}
Bu yerda eksportning uchta "o'zgartirishi" ko'rinishi mumkin, va har biri modelni haqiqiy hayotga tayyorlash uchun:
Lug'at va misollarni tushirib qoldirish. Qatorlarning bir qismida lug'at yo'q, ko'pchiligida misollar yo'q:
print("lug'atni tushirish ulushi:", eksport["drop_glossary_share"]) print("misollarni tushirish ulushi:", eksport["drop_examples_share"]) lugatsiz = sum("Glossary of business words:\n\n" in q["messages"][0]["content"] for q in qatorlar) print(f"bu eksportda lug'atsiz qatorlar: {lugatsiz} / {len(qatorlar)}")
lug'atni tushirish ulushi: 0.3 misollarni tushirish ulushi: 0.5 bu eksportda lug'atsiz qatorlar: 78 / 286
Haqiqiy tahlilchining faylida lug'at bo'lmaydi — u yangi fayl. Model lug'atsiz ham ishlashni o'rganishi kerak.
Sarlavhalarni qayta nomlash. Qatorlarning bir qismida ustun nomlari boshqacha: ruscha ("Сумма (UZS)"), inglizcha ("Amount UZS"), sarlavha uslubida yoki aralash. Jadval va plan birga qayta nomlanadi, shuning uchun juftlik to'g'riligicha qoladi. Shu tufayli tez8 trainingda 14 ta jadvalni ko'rgan bo'lsa ham, promptlarda 499 xil ustun nomini ko'rgan.
Shovqin. Qatorlarning 15 foizida so'rovga shovqin qo'shiladi: apostroflar tushib qoladi, harf xatosi, hammasi katta yoki kichik harf:
print(json.dumps(eksport["noise"], ensure_ascii=False))
{"share": 0.15, "kinds": {"no_apostrophes": 3, "typo": 3, "uppercase": 1, "lowercase": 1}}
Haqiqiy tahlilchi shoshilib yozadi: "ozbekiston" (apostrofsiz), "kredtlar" (harf tushgan).
Kalitlar tartibi
Javobdagi kalitlar tartibi ham eksportda belgilanadi — va 9-bobdan keyin bu nega muhimligini bilamiz.
canonical_answer har bir toolning argumentlarini tools.json dagi tartibda qayta tartiblaydi:
asboblar = build.load_context(TEZ / "prompt/domains/retail_sales")["tools"] aralash = {"steps": [{"tool": "group_and_total", "args": {"agg": "sum", "value_column": "summa_uzs", "group_by": ["dokon"]}}]} print("kirish: ", json.dumps(aralash["steps"][0]["args"])) print("chiqish:", json.dumps(canonical_answer(aralash, asboblar)["steps"][0]["args"]))
kirish: {"agg": "sum", "value_column": "summa_uzs", "group_by": ["dokon"]}
chiqish: {"group_by": ["dokon"], "value_column": "summa_uzs", "agg": "sum"}
Model har doim bitta tartibni ko'radi — group_by birinchi. Grammar ham (9-bobdagi tuzatishdan
keyin) aynan shu tartibni saqlaydi. Training va grammar bir xil tartibda bo'lishi kerak; tez9 da ular
farqlangan edi.
Birinchi tuzoq: bitta seed hammasini aralashtiradi
Eksport hamma juftliklarni bitta tasodifiy son generatori bilan aralashtiradi, va keyin har bir qator uchun shu generatordan "lug'atni tushiraymi", "qayta nomlaymi", "shovqin qo'shaymi" degan qarorlarni oladi. Bu bitta muhim oqibatga olib keladi: yangi fayl qo'shilsa, eski qatorlar ham o'zgaradi.
Buni o'lchaymiz: bir xil 250 ta juftlikni ikki marta eksport qilamiz — bir marta o'zi, bir marta qolgan juftliklarni qo'shib. Ikkala holatda ham seed bir xil:
def eksport_qil(juft): t = random.Random(1) juft = list(juft) t.shuffle(juft) return training_rows(juft, kontekstlar, tosilgan, eksport, t, sarlavhalar, shovqin) asosiy = yaroqli[:250] qoshimcha = yaroqli[250:] birinchi = {q["messages"][1]["content"] + q["messages"][2]["content"]: q for q in eksport_qil(asosiy)} ikkinchi = eksport_qil(asosiy + qoshimcha) bir_xil = sum(1 for q in ikkinchi if birinchi.get(q["messages"][1]["content"] + q["messages"][2]["content"]) == q) print(f"asosiy juftliklar: {len(asosiy)}, qo'shilgani: {len(qoshimcha)}") print(f"asosiy qatorlardan hech o'zgarmay qolgani: {bir_xil}")
asosiy juftliklar: 250, qo'shilgani: 47 asosiy qatorlardan hech o'zgarmay qolgani: 17
Atigi 47 ta juftlik qo'shildi — va eski qatorlarning katta qismi o'zgardi: boshqa sarlavhalar, boshqa shovqin, lug'at boshqa qatorlardan tushdi. Matn bir xil bo'lsa ham, model ko'radigan qator boshqa.
Bu tez10 tayyorlanayotganda topildi. tez10 ning maqsadi — "bitta o'zgarish": tez8 ning ma'lumoti va qo'shimcha "qila olmayman" qatorlari, boshqa hech narsa. Agar "qila olmayman" fayli oddiy eksportga qo'shilganda, tez8 ning 19 886 qatoridan faqat 4 531 tasining so'rov va javobi avvalgidek qolardi (o'sha kuni o'lchangan) — ya'ni "bitta o'zgarish" emas, minglab o'zgarish bo'lardi. Yechim: tez8 ning qatorlarini hozirgi kod bilan alohida, o'zi qayta eksport qilish, "qila olmayman" qatorlarini ham alohida eksport qilish va ikkalasini keyin birlashtirish.
Ikkinchi tuzoq: oxirgi 5 foiz
Training dasturi (Soup) faylning oxirgi 5 foizini validation (tekshirish) uchun ajratadi — tasodifiy emas, joylashuv bo'yicha. Demak yangi qatorlarni oddiygina fayl oxiriga qo'shsak, ular trainingga emas, tekshirishga tushadi. Va tez8 ning tekshirish qatorlari esa trainingga o'tadi.
tez10 ning fayli buni hisobga olib yig'ilgan: tez8 ning training qismi, keyin yangi qatorlarning training ulushi, keyin tez8 ning tekshirish qismi, keyin yangi qatorlarning tekshirish ulushi. Buni saqlangan ikkala faylni solishtirib tekshiramiz:
t8 = [json.loads(q) for q in open(Path.home() / "tez_runs/tez8/data/train.jsonl", encoding="utf-8")] t10 = [json.loads(q) for q in open(Path.home() / "tez_runs/tez10/data/train.jsonl", encoding="utf-8")] suhbat = lambda q: json.dumps(q["messages"][1:], ensure_ascii=False) chegara8, chegara10 = int(len(t8) * 0.95), int(len(t10) * 0.95) print(f"tez8: {len(t8):,} qator, o'qitish {chegara8:,}, tekshirish {len(t8) - chegara8:,}") print(f"tez10: {len(t10):,} qator, o'qitish {chegara10:,}, tekshirish {len(t10) - chegara10:,}") print("tez10 boshidagi qatorlar tez8 o'qitish qismi bilan bir xil:", all(suhbat(a) == suhbat(b) for a, b in zip(t8[:chegara8], t10[:chegara8]))) print("tez10 tekshirish qismi tez8 tekshirish qismi bilan boshlanadi:", [suhbat(q) for q in t10[chegara10:chegara10 + len(t8) - chegara8]] == [suhbat(q) for q in t8[chegara8:]]) farq = [s for s in t8[0]["messages"][0]["content"].splitlines() if s not in t10[0]["messages"][0]["content"].splitlines()] print("tizim promptidagi yagona farq (tez8 dagi qator):") print(" ", farq[0][:150])
tez8: 19,886 qator, o'qitish 18,891, tekshirish 995 tez10: 20,744 qator, o'qitish 19,706, tekshirish 1,038 tez10 boshidagi qatorlar tez8 o'qitish qismi bilan bir xil: True tez10 tekshirish qismi tez8 tekshirish qismi bilan boshlanadi: True tizim promptidagi yagona farq (tez8 dagi qator): - group_and_total(group_by: columns, value_column: column, agg: sum|count|avg|min|max, share: boolean?): Group rows by columns and aggregate a value c
tez8 ning training qatorlari tez10 da o'sha tartibda, o'zgarmagan. Tekshirish qismi ham o'sha. Tizim
promptida faqat bitta qator farq qiladi — group_and_total ning tavsifi, chunki u keyinroq
yangilangan va hozirgi engine ishlatadigan prompt bilan bir xil bo'lishi kerak. Bu ikki tuzoq
xatolar jurnaliga qoida sifatida yozilgan.
Xulosa
- Eksport — oxirgi filtr: ustun nomlari, amal so'zlari, sonlar va schema yana bir marta tekshiriladi.
- Test savollarining matni train qilishga kira olmaydi; tez8 da 745 savoldan nol tasi trainingda bor. To'siq faqat aynan bir xil matnni ushlaydi.
- Lug'at va misollar ba'zan tushirib qoldiriladi, sarlavhalar qayta nomlanadi, shovqin qo'shiladi — model haqiqiy, notanish faylga tayyorlanadi.
- Kalitlar tartibi
tools.jsonbo'yicha qotiriladi — grammar bilan bir xil. - Ikki tuzoq: bitta seed yangi fayl qo'shilganda eski qatorlarni ham o'zgartiradi; Soup oxirgi 5 foizni tekshirishga oladi. tez10 ikkalasini hisobga olib yig'ilgan.
Keyingi bobda trainingdan oldingi oxirgi darvoza: qamrov tekshiruvi — ma'lumotda nima yetishmasligini train qilishdan oldin topish.
Bu bobni bepul o'qidingiz.
Kitob o'rgatadi, lekin sizning kodingiz nega ishlamaganini ayta olmaydi. Kursda uy vazifangiz tekshiriladi.