Asosiy qismga o'tish

Eksport: tekshirilgan juftliklardan training fayliga

15-bob 8 kod bloki 2397 so'z

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:

python33 qator
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)}")
Chiqish
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 leaked tekshiruvi 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:

python7 qator
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)}")
Chiqish
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:

python7 qator
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"])
Chiqish
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:

python4 qator
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)}")
Chiqish
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:

python1 qator
print(json.dumps(eksport["noise"], ensure_ascii=False))
Chiqish
{"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:

python4 qator
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"]))
Chiqish
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:

python13 qator
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}")
Chiqish
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:

python13 qator
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])
Chiqish
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.json bo'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.

Telegramda yozish Narxlar