Mundarija (21)
- 1. Kirish va motivatsiya
- 2. Nazariya — chuqur tushuntirish
- 2.1. Token nima va tokenizatsiya turlari
- 2.2. Apostrofli so'zlar va tinish belgilari
- 2.3. Lug'at: <pad>, <unk> va faqat o'quvdan
- 2.4. Minimal chastota va OOV ulushi
- 2.5. Zipf qonuni
- 2.6. Agglutinativlik va lug'at portlashi
- 2.7. Tuzoqlar
- 3. Tez ma'lumotnoma
- 4. Batafsil misollar
- Misol 1 — So'z, belgi va regex tokenizatsiyasi
- Misol 2 — Lug'at, padding va OOV
- Misol 3 — Zipf qonuni
- Misol 4 — Agglutinativlik va lug'at portlashi
- 5. To'g'ri va noto'g'ri tushunishlar
- 6. Keng tarqalgan xatolar va yechimlari
- 7. Integratsiya — bu bilim qayerda kerak bo'ladi
- 8. Eng yaxshi amaliyotlar
- 9. Amaliy topshiriq
- Xulosa
23.2-dars: Tokenizatsiya
23-QISM — NLP VA KETMA-KETLIKLAR · 2-dars
1. Kirish va motivatsiya
23.1-darsda matnni izchil ko'rinishga keltirdik: apostrof variantlari birlashdi, kirill homogliflar tuzatildi, kichik harf va raqamlar normallashtirildi. Lekin model hali ham satrni emas, sonlarni kutadi. 21-qismda ko'rgan nn.Embedding ga butun son indekslar kerak, 22-qismda esa rasm allaqachon sonlar massivi edi. Matnda bu ko'prikni ikki qadam quradi: tokenizatsiya (matnni bo'laklarga — tokenlarga ajratish) va lug'at (har tokenga butun son berish).
Tokenizatsiya sodda ko'rinadi: matn.split() va tamom. Lekin o'zbek tilida bu tezda buziladi. Standart \w+ regex "o'qituvchi" ni o va qituvchi ga bo'lib yuboradi — chunki oddiy apostrof "harf" emas. Tinish belgilari so'zga yopishib qoladi: "bor!" va "bor" ikki xil token. Keyin lug'at muammosi: o'zbek tili agglutinativ — bitta asosga ketma-ket qo'shimchalar qo'shiladi (jamoa + lar + imiz + ning), va har yangi birikma lug'at uchun yangi so'z. Natijada lug'at portlaydi, testda esa ko'p so'z lug'atdan tashqarida (OOV — out of vocabulary) qoladi.
Bu darsda tokenizatorlarni taqqoslaymiz, lug'atni to'g'ri quramiz (faqat o'quv qismidan, <pad> va <unk> bilan), OOV ni o'lchaymiz, so'z chastotalaridagi Zipf qonunini ko'ramiz va o'zbekcha qo'shimchalar lug'atni qanchalik kattalashtirishini aniq sonlarda ko'rsatamiz.
Real vaziyat. Jamoa o'zbekcha sharhlar uchun sentiment modeli qurdi. Validatsiyada natija a'lo edi, ishlab chiqarishda esa model yangi sharhlarning katta qismini "neytral" deb belgilay boshladi. Tekshiruv ko'rsatdiki, lug'at butun ma'lumotdan (test bilan birga) qurilgan edi — shuning uchun validatsiyada OOV nol edi. Real sharhlarda esa "yoqmadi", "yoqmaganlarimdan", "yoqdimi" kabi shakllarning ko'pi lug'atda yo'q edi va <unk> ga aylanardi. Bu darsning 2-misoli aynan shu sizib ketishni o'lchaydi.
Bu darsda matnni tokenlarga bo'lishni va lug'at qurishni o'rganamiz.
Bu darsda:
- So'z, belgi va regex tokenizatsiyasi
- Apostrofli o'zbek so'zlari va tinish belgilari
- Lug'at:
<pad>,<unk>, faqat o'quvdan - Minimal chastota va OOV ulushi
- Zipf qonuni
- Agglutinativlik va lug'at portlashi
- Tuzoqlar
ℹ Misollar real Python, numpy va torch bilan (Python 3.14, torch 2.14 CPU).
2. Nazariya — chuqur tushuntirish
2.1. Token nima va tokenizatsiya turlari
TOKEN = modelning eng kichik birligi
so'z tokenlari "narx oshdi" -> ["narx", "oshdi"]
belgi tokenlari "narx" -> ["n", "a", "r", "x"]
subword tokenlar "narxlari" -> ["narx", "lari"] (23.3-dars)
SAVDO (trade-off):
lug'at hajmi ketma-ketlik uzunligi OOV
so'z katta (minglab) qisqa ko'p
belgi kichik (~30-100) uzun (x6) deyarli yo'q
subword o'rtacha o'rtacha deyarli yo'q
SO'Z TOKENIZATORLARI:
s.split() - faqat bo'sh joy; tinish so'zga yopishadi ("bor!")
re.findall(r"\w+") - harf/raqam ketma-ketligi; apostrofda bo'linadi
o'z regex - til qoidalarini hisobga oladiTokenizatsiya — lug'at hajmi, ketma-ketlik uzunligi va OOV orasidagi savdo — mukammal tanlov yo'q, vazifaga mos tanlov bor.
2.2. Apostrofli so'zlar va tinish belgilari
MUAMMO (\w+):
"o'qituvchi" -> ["o", "qituvchi"] U+0027 harf emas
"ma'lumot" -> ["ma", "lumot"]
U+02BB bilan -> bitta token U+02BB harf (Lm)
-> bir so'z yozilishiga qarab 1 yoki 2 token (23.1 ning davomi)
-> lug'atda "o" va "qituvchi" kabi ma'nosiz bo'laklar
YECHIM: avval normallashtirish 23.1-bob, keyin o'z regex:
TOKEN_RE = r"[a-z0-9]+(?:['-][a-z0-9]+)*|[^\w\s]"
[a-z0-9]+ - harf/raqam bo'lagi
(?:['-][a-z0-9]+)* - ICHIDA apostrof yoki chiziqcha bo'lsa davomi
[^\w\s] - har tinish belgisi alohida token
"o'qituvchi" -> ["o'qituvchi"]
"'yaxshi'" -> ["'", "yaxshi", "'"] qo'shtirnoq sifatida
"0-yilda" -> ["0-yilda"]
"bor!" -> ["bor", "!"]
TINISH BELGILARI:
alohida token qiling - "!" va "?" sentiment uchun foydali
so'zga yopishtirib qoldirmang - "bor", "bor!", "bor." uch xil so'z bo'ladiApostrof so'z ICHIDA bo'lsa — so'zning qismi, chetida bo'lsa — tinish belgisi — bitta regex ikkalasini ajratadi.
2.3. Lug'at: <pad>, <unk> va faqat o'quvdan
LUG'AT = token <-> butun son jadvali
itos: ["<pad>", "<unk>", ".", "!", "?", "0", "emas", ...] indeks -> token
stoi: {"<pad>": 0, "<unk>": 1, ".": 2, ...} token -> indeks
MAXSUS TOKENLAR:
<pad> = 0 - batchdagi qisqa ketma-ketliklarni to'ldirish
niqob: X != 0; Embedding(padding_idx=0) (21-qism)
<unk> = 1 - lug'atda yo'q har qanday token
QURISH TARTIBI:
1. tokenlarni O'QUV qismidan sanash
2. min_chastota dan kamlarini tashlash
3. chastota kamayishi, teng bo'lsa alifbo bo'yicha tartiblash (deterministik)
4. maxsus tokenlar oldinda
NIMA UCHUN FAQAT O'QUVDAN:
test/ishlab chiqarishdagi so'zlar oldindan ma'lum emas
lug'at hammasidan qurilsa -> test OOV = 0 (yolg'on optimizm)
bu 18-qismdagi sizib ketish (leakage) ning matndagi ko'rinishi Lug'at modelning bir qismi, shuning uchun u faqat o'quv ma'lumotidan quriladi — xuddi StandardScaler ning fit i kabi.
2.4. Minimal chastota va OOV ulushi
OOV ULUSHI = lug'atda yo'q tokenlar / barcha tokenlar
o'quvda (min_chastota=1) - har doim 0
testda - haqiqiy ko'rsatkich
MIN_CHASTOTA:
kattalashtirsak -> lug'at kichrayadi, OOV ortadi
1 marta uchragan so'z uchun embedding o'rganib bo'lmaydi
(bitta misoldan vektor - shovqin)
min_chastota=2..5 - odatiy tanlov
o'quvda ham OOV paydo bo'ladi -> model <unk> ni o'rganadi (FOYDALI)
OOV NIMADAN IBORAT:
imlo xatolari - normallashtirish tuzatmaydi
kam uchraydigan shakllar - o'zbekchada ASOSIY manba (qo'shimchalar)
yangi so'zlar - nomlar, atamalarTest OOV ulushi — lug'at sifatining asosiy ko'rsatkichi — va uni faqat o'quvdan qurilgan lug'at bilan o'lchash kerak.
2.5. Zipf qonuni
ZIPF QONUNI:
so'zlarni chastota bo'yicha tartiblang: rang r = 1, 2, 3, ...
chastota f(r) ~ C / r -> f(r) * r ~ C (taxminan o'zgarmas)
log-log grafikda: log f = log C - 1 * log r (qiyalik ~ -1)
OQIBATLARI:
bir necha so'z (the, a, is / emas, bor) juda ko'p
so'zlarning katta qismi bir-ikki marta uchraydi (uzun dum)
top-100 so'z tokenlarning ~yarmini qoplaydi
lekin qolgan yarmi uchun minglab so'z kerak
TEKSHIRISH:
qiyalikning o'zi yetarli emas - f * r ham barqaror bo'lishi kerak
kombinatsiyadan yasalgan sun'iy matn ham -1 ga yaqin qiyalik berishi
mumkin, lekin f * r unda keskin tebranadi
AMALIY MA'NOSI:
lug'atni kichraytirish tokenlarning kichik ulushini <unk> ga aylantiradi
lekin OOV ni to'liq yo'qotish mumkin emas - dum cheksizZipf: oz so'z ko'p, ko'p so'z oz — shuning uchun so'z darajasidagi lug'at hech qachon "to'liq" bo'lmaydi.
2.6. Agglutinativlik va lug'at portlashi
AGGLUTINATIV TIL: asosga qo'shimchalar ketma-ket yopishadi
jamoa + lar + imiz + ning = jamoalarimizning
asos ko'plik egalik kelishik
BITTA OT ASOSI UCHUN (soddalashtirilgan):
ko'plik: 2 (yo'q / -lar)
egalik: 5 (yo'q / -im / -ingiz / -imiz / -i)
kelishik: 7 (yo'q / -ning / -ni / -da / -dan / -ga / -dagi)
-> 2 * 5 * 7 = 70 shakl, haqiqiy tilda yuzlab
OQIBAT:
so'z lug'ati korpus bilan birga UZOQ o'sadi (Heaps qonuni)
asoslar lug'ati tez to'yinadi
test OOV ning katta qismi - imlo xatosi emas, TO'G'RI lekin kam shakl
YECHIMLAR:
stemming/lemmatizatsiya - qo'shimchani kesish (qo'lda qoidalar, xatoli)
subword tokenizatsiya - BPE qo'shimchalarni O'ZI topadi (23.3-dars)
belgi darajasi - OOV yo'q, lekin ketma-ketlik uzunO'zbek tilida so'z darajasidagi lug'at tabiiy ravishda portlaydi — muammo ma'lumot kamligida emas, til tuzilishida.
2.7. Tuzoqlar
Asosiy tuzoqlar: normallashtirmasdan tokenlash; \w+ bilan apostrofli so'zlarni bo'lish; tinish belgisini so'zga yopishtirib qoldirish; lug'atni o'quv va testdan birga qurish; <pad> va <unk> ni unutish yoki ularga boshqa indekslar berish; lug'at tartibini set tartibiga bog'lash (har ishga tushirishda boshqa indekslar); min_chastota=1 bilan o'quvda <unk> ni hech ko'rmaslik; test OOV ni o'lchamaslik; o'zbekcha matnga inglizcha stemmer yoki inglizcha lug'at hajmi tavsiyalarini ko'r-ko'rona qo'llash.
3. Tez ma'lumotnoma
import re
from collections import Counter
TOKEN_RE = re.compile(r"[a-z0-9]+(?:['-][a-z0-9]+)*|[^\w\s]")
def tokenla(matn):
return TOKEN_RE.findall(normalla(matn)) # 23.1 dagi normalla
class Lugat:
PAD, UNK = 0, 1
def __init__(self, hujjatlar, min_chastota=2):
sanoq = Counter(t for h in hujjatlar for t in h)
sozlar = sorted((s for s, n in sanoq.items() if n >= min_chastota),
key=lambda s: (-sanoq[s], s))
self.itos = ["<pad>", "<unk>"] + sozlar
self.stoi = {s: i for i, s in enumerate(self.itos)}
def kodla(self, tokenlar):
return [self.stoi.get(t, self.UNK) for t in tokenlar]
lugat = Lugat(oquv_tokenlar) # faqat o'quvdan!
oov = sum(t not in lugat.stoi for h in test for t in h) / sum(map(len, test))Tokenizatsiya xulosasi
avval normallashtirish, keyin tokenlash
apostrof ichkarida - so'z qismi; tinish belgilari - alohida token
lug'at: <pad>=0, <unk>=1, faqat o'quvdan, deterministik tartib
min_chastota - lug'at hajmi va OOV savdosi
test OOV ni har doim o'lchang
o'zbekchada OOV ning asosiy manbai - qo'shimchalar birikmasi4. Batafsil misollar
Misollar real Python, numpy va torch bilan (Python 3.14, torch 2.14 CPU).
Misollar 23.1-darsdagi sintetik korpus generatori va normalla funksiyasidan foydalanadi (har misolda to'liq keltirilgan — misollar mustaqil ishga tushadi).
Misol 1 — So'z, belgi va regex tokenizatsiyasi
"""So'z, belgi va regex tokenizatsiyasi: apostrofli so'zlar va tinish belgilari."""
import re
import unicodedata
from collections import Counter
import numpy as np
OTLAR = {
"sport": ["jamoa", "o'yinchi", "murabbiy", "stadion", "o'yin", "g'alaba",
"chempionat", "darvoza", "to'p", "musobaqa", "muxlis", "gol",
"hakam", "sportchi", "mag'lubiyat", "klub"],
"iqtisod": ["bank", "narx", "soliq", "kompaniya", "bozor", "kredit",
"eksport", "sarmoya", "byudjet", "daromad", "savdo",
"tadbirkor", "valyuta", "tarif", "sur'at", "da'vo"],
"texnologiya": ["telefon", "dastur", "kompyuter", "internet", "ilova",
"ma'lumot", "tarmoq", "qurilma", "server", "dasturchi",
"robot", "platforma", "algoritm", "surat", "ta'rif",
"xavfsizlik"],
"salomatlik": ["shifokor", "kasalxona", "dori", "bemor", "sog'liq",
"vaksina", "parhez", "uyqu", "yurak", "qon", "mashq",
"vitamin", "klinika", "davo", "tibbiyot", "og'riq"],
}
FELLAR = ["osh", "kamay", "o'zgar", "yaxshilan", "tayyorla", "ko'rsat",
"boshla", "ishla", "yut", "qo'lla", "tekshir", "ta'minla",
"o'rgan", "sot", "yoq"]
SIFATLAR = ["yangi", "katta", "muhim", "tez", "yaxshi", "kuchli", "zaif",
"arzon", "qimmat", "sog'lom", "xavfli", "a'lo", "yuqori", "past",
"mashhur", "qulay"]
UNLILAR = set("aeiou")
def ot_shakli(asos, rng):
"""Ot + ko'plik + egalik + kelishik (soddalashtirilgan qoidalar)."""
s = asos
if rng.random() < 0.3:
s += "lar"
if rng.random() < 0.3:
unli = s[-1] in UNLILAR
s += rng.choice(["si", "miz", "ngiz", "m"] if unli
else ["i", "imiz", "ingiz", "im"])
if rng.random() < 0.6:
k = str(rng.choice(["ning", "ni", "da", "dan", "ga", "dagi"]))
if k == "ga" and s[-1] in "kq":
k = "ka" if s[-1] == "k" else "qa"
s += k
return s
def fel_shakli(asos, rng):
"""Fe'l: o'tgan/hozirgi zamon, ba'zan -ma- inkori."""
inkor = rng.random() < 0.25
if rng.random() < 0.5:
return asos + ("madi" if inkor else "di")
if inkor:
return asos + "maydi"
return asos + ("ydi" if asos[-1] in UNLILAR else "adi")
APOSTROFLAR = ["'", "\u2018", "\u2019", "\u02bb", "\u02bc", "`"]
APOSTROF_P = [0.45, 0.12, 0.2, 0.13, 0.05, 0.05]
KIRILL = {"o": "\u043e", "a": "\u0430", "e": "\u0435", "c": "\u0441",
"p": "\u0440", "x": "\u0445"}
def jumla(mavzu, rng):
otlar = OTLAR[mavzu]
sozlar = [ot_shakli(str(rng.choice(otlar)), rng)]
if rng.random() < 0.5:
sozlar.append(ot_shakli(str(rng.choice(otlar)), rng))
tur = rng.random()
if tur < 0.3:
sozlar.append(str(rng.choice(SIFATLAR)))
if rng.random() < 0.3:
sozlar.append("emas")
return " ".join(sozlar)
if tur < 0.4:
sozlar.append(str(rng.choice(["bor", "yo'q"])))
return " ".join(sozlar)
if rng.random() < 0.25:
son = int(rng.integers(2, 100))
sozlar.append(str(rng.choice([f"{son} foizga", f"{1990 + son % 35}-yilda",
f"{son}.{son % 10} mln"])))
sozlar.append(fel_shakli(str(rng.choice(FELLAR)), rng))
return " ".join(sozlar)
def buz(soz, rng):
"""Imlo xatosi: harf tushib qolishi, takrorlanishi yoki o'rin almashishi."""
if len(soz) < 4:
return soz
i = int(rng.integers(1, len(soz) - 1))
tur = rng.integers(3)
if tur == 0:
return soz[:i] + soz[i + 1:]
if tur == 1:
return soz[:i] + soz[i] + soz[i:]
return soz[:i - 1] + soz[i] + soz[i - 1] + soz[i + 1:]
def hujjat(rng, mavzu=None):
if mavzu is None:
mavzu = str(rng.choice(sorted(OTLAR)))
jumlalar = [jumla(mavzu, rng) for _ in range(int(rng.integers(1, 5)))]
matn = ""
for j in jumlalar:
sozlar = [buz(s, rng) if rng.random() < 0.03 else s for s in j.split()]
j = " ".join(sozlar)
matn += j[0].upper() + j[1:] + str(rng.choice([".", ".", ".", "!", "?", "..."]))
matn += str(rng.choice([" ", " ", " ", " ", " ", "\u00a0", "\t"]))
matn = matn.strip()
odat = int(rng.choice(len(APOSTROFLAR), p=APOSTROF_P))
belgilar = []
for ch in matn:
if ch == "'":
k = odat if rng.random() < 0.85 else int(rng.integers(len(APOSTROFLAR)))
ch = APOSTROFLAR[k]
belgilar.append(ch)
matn = "".join(belgilar)
if rng.random() < 0.06:
matn = "".join(KIRILL[c] if c in KIRILL and rng.random() < 0.3 else c
for c in matn)
if rng.random() < 0.04:
matn = matn.upper()
if rng.random() < 0.05:
matn = matn.translate({ord(str(d)): 0xFF10 + d for d in range(10)})
return mavzu, matn
APOSTROF_RE = re.compile("[\u2018\u2019\u02bb\u02bc`]")
LOTINGA = {v: k for k, v in KIRILL.items()}
LOTINGA.update({v.upper(): k.upper() for k, v in KIRILL.items()})
def kirill_tuzat(soz):
yoz = {unicodedata.name(c).split()[0] for c in soz if c.isalpha()}
if "LATIN" in yoz and "CYRILLIC" in yoz:
return "".join(LOTINGA.get(c, c) for c in soz)
return soz
def normalla(matn):
"""23.1-darsdagi quvur: bo'sh joy, NFKC, apostrof, kirill, kichik harf, raqam."""
matn = re.sub(r"\s+", " ", matn).strip()
matn = unicodedata.normalize("NFKC", matn)
matn = APOSTROF_RE.sub("'", matn)
matn = " ".join(kirill_tuzat(s) for s in matn.split(" "))
return re.sub(r"[0-9]+", "0", matn.lower())
SOZ_RE = re.compile(r"\w+")
TOKEN_RE = re.compile(r"[a-z0-9]+(?:['-][a-z0-9]+)*|[^\w\s]")
def tokenizatorlar():
return {
"split()": lambda t: t.split(),
"regex \\w+": lambda t: SOZ_RE.findall(t),
"normalla+\\w+": lambda t: SOZ_RE.findall(normalla(t)),
"normalla+TOKEN_RE": lambda t: TOKEN_RE.findall(normalla(t)),
}
def main() -> None:
gap = "O\u2018qituvchi: \u00abBugun 2024-yilda 3 ta dars bor!\u00bb — dedi."
print("=== 1. Bitta gap - to'rt xil tokenizator ===")
print(f" gap: {ascii(gap)}")
for nom, f in tokenizatorlar().items():
tok = f(gap)
korinish = " | ".join(x if x.isascii() else ascii(x)[1:-1] for x in tok)
print(f" {nom:<18} {len(tok):>2} ta: {korinish}")
belgilar = list(normalla(gap))
print(f" {'belgilar':<18} {len(belgilar):>2} ta: {ascii(belgilar[:14])[:70]} ...")
print("\n=== 2. Apostrof qanday bo'linadi ===")
for s in ["o'qituvchi", "o\u02bbqituvchi", "o\u2019qituvchi", "'yaxshi'",
"ma'lumotlarimizning"]:
print(f" {ascii(s):<24} \\w+: {ascii(SOZ_RE.findall(s)):<28} "
f"TOKEN_RE: {TOKEN_RE.findall(normalla(s))}")
print("\n=== 3. Korpusda ===")
rng = np.random.default_rng(0)
korpus = [hujjat(rng)[1] for _ in range(3000)]
print(f" {'tokenizator':<18} {'tokenlar':>9} {'lug_at':>7} "
f"{'1-harfli tok':>13}")
for nom, f in tokenizatorlar().items():
tok = [x for t in korpus for x in f(t)]
bir = sum(len(x) == 1 and x.isalpha() for x in tok)
print(f" {nom:<18} {len(tok):>9} {len(set(tok)):>7} {bir:>13}")
sanoq = Counter(x for t in korpus for x in SOZ_RE.findall(normalla(t)))
print(f" normalla+\\w+ dagi eng ko'p tokenlar: "
f"{[s for s, _ in sanoq.most_common(6)]}")
print("\n=== 4. Belgi va so'z tokenizatsiyasi ===")
toza = [normalla(t) for t in korpus]
soz_tok = [TOKEN_RE.findall(t) for t in toza]
belgi_tok = [list(t) for t in toza]
uz_soz = np.array([len(x) for x in soz_tok])
uz_belgi = np.array([len(x) for x in belgi_tok])
print(f" lug'at: so'z {len({x for d in soz_tok for x in d})}, "
f"belgi {len({x for d in belgi_tok for x in d})}")
print(f" hujjat uzunligi (o'rtacha): so'z {uz_soz.mean():.1f}, "
f"belgi {uz_belgi.mean():.1f} ({uz_belgi.mean() / uz_soz.mean():.1f}x)")
print(f" eng uzun hujjat: so'z {uz_soz.max()}, belgi {uz_belgi.max()}")
print(" ⭐ So'z tokenlari - katta lug'at, qisqa ketma-ketlik; belgilar - aksincha")
if __name__ == "__main__":
main()Natijaning muhim qismi:
=== 1. Bitta gap - to'rt xil tokenizator ===
gap: 'O\u2018qituvchi: \xabBugun 2024-yilda 3 ta dars bor!\xbb \u2014 dedi.'
split() 9 ta: O\u2018qituvchi: | \xabBugun | 2024-yilda | 3 | ta | dars | bor!\xbb | \u2014 | dedi.
regex \w+ 10 ta: O | qituvchi | Bugun | 2024 | yilda | 3 | ta | dars | bor | dedi
normalla+\w+ 10 ta: o | qituvchi | bugun | 0 | yilda | 0 | ta | dars | bor | dedi
normalla+TOKEN_RE 14 ta: o'qituvchi | : | \xab | bugun | 0-yilda | 0 | ta | dars | bor | ! | \xbb | \u2014 | dedi | .
belgilar 50 ta: ['o', "'", 'q', 'i', 't', 'u', 'v', 'c', 'h', 'i', ':', ' ', '\xab', ' ...
=== 2. Apostrof qanday bo'linadi ===
"o'qituvchi" \w+: ['o', 'qituvchi'] TOKEN_RE: ["o'qituvchi"]
'o\u02bbqituvchi' \w+: ['o\u02bbqituvchi'] TOKEN_RE: ["o'qituvchi"]
'o\u2019qituvchi' \w+: ['o', 'qituvchi'] TOKEN_RE: ["o'qituvchi"]
"'yaxshi'" \w+: ['yaxshi'] TOKEN_RE: ["'", 'yaxshi', "'"]
"ma'lumotlarimizning" \w+: ['ma', 'lumotlarimizning'] TOKEN_RE: ["ma'lumotlarimizning"]
=== 3. Korpusda ===
tokenizator tokenlar lug_at 1-harfli tok
split() 21458 6910 0
regex \w+ 25450 5584 1276
normalla+\w+ 26295 3352 1636
normalla+TOKEN_RE 32269 3352 3
normalla+\w+ dagi eng ko'p tokenlar: ['0', 'o', 'emas', 'ta', 'yilda', 'mln']
=== 4. Belgi va so'z tokenizatsiyasi ===
lug'at: so'z 3352, belgi 32
hujjat uzunligi (o'rtacha): so'z 10.8, belgi 68.0 (6.3x)
eng uzun hujjat: so'z 27, belgi 172
⭐ So'z tokenlari - katta lug'at, qisqa ketma-ketlik; belgilar - aksinchaNima ko'rsatdi: 2.1, 2.2-bo'limlar.
Misol 2 — Lug'at, padding va OOV
"""Lug'at qurish: faqat o'quvda, <pad>=0, <unk>=1, min chastota va OOV ulushi."""
import re
import unicodedata
from collections import Counter
import numpy as np
import torch
OTLAR = {
"sport": ["jamoa", "o'yinchi", "murabbiy", "stadion", "o'yin", "g'alaba",
"chempionat", "darvoza", "to'p", "musobaqa", "muxlis", "gol",
"hakam", "sportchi", "mag'lubiyat", "klub"],
"iqtisod": ["bank", "narx", "soliq", "kompaniya", "bozor", "kredit",
"eksport", "sarmoya", "byudjet", "daromad", "savdo",
"tadbirkor", "valyuta", "tarif", "sur'at", "da'vo"],
"texnologiya": ["telefon", "dastur", "kompyuter", "internet", "ilova",
"ma'lumot", "tarmoq", "qurilma", "server", "dasturchi",
"robot", "platforma", "algoritm", "surat", "ta'rif",
"xavfsizlik"],
"salomatlik": ["shifokor", "kasalxona", "dori", "bemor", "sog'liq",
"vaksina", "parhez", "uyqu", "yurak", "qon", "mashq",
"vitamin", "klinika", "davo", "tibbiyot", "og'riq"],
}
FELLAR = ["osh", "kamay", "o'zgar", "yaxshilan", "tayyorla", "ko'rsat",
"boshla", "ishla", "yut", "qo'lla", "tekshir", "ta'minla",
"o'rgan", "sot", "yoq"]
SIFATLAR = ["yangi", "katta", "muhim", "tez", "yaxshi", "kuchli", "zaif",
"arzon", "qimmat", "sog'lom", "xavfli", "a'lo", "yuqori", "past",
"mashhur", "qulay"]
UNLILAR = set("aeiou")
def ot_shakli(asos, rng):
"""Ot + ko'plik + egalik + kelishik (soddalashtirilgan qoidalar)."""
s = asos
if rng.random() < 0.3:
s += "lar"
if rng.random() < 0.3:
unli = s[-1] in UNLILAR
s += rng.choice(["si", "miz", "ngiz", "m"] if unli
else ["i", "imiz", "ingiz", "im"])
if rng.random() < 0.6:
k = str(rng.choice(["ning", "ni", "da", "dan", "ga", "dagi"]))
if k == "ga" and s[-1] in "kq":
k = "ka" if s[-1] == "k" else "qa"
s += k
return s
def fel_shakli(asos, rng):
"""Fe'l: o'tgan/hozirgi zamon, ba'zan -ma- inkori."""
inkor = rng.random() < 0.25
if rng.random() < 0.5:
return asos + ("madi" if inkor else "di")
if inkor:
return asos + "maydi"
return asos + ("ydi" if asos[-1] in UNLILAR else "adi")
APOSTROFLAR = ["'", "\u2018", "\u2019", "\u02bb", "\u02bc", "`"]
APOSTROF_P = [0.45, 0.12, 0.2, 0.13, 0.05, 0.05]
KIRILL = {"o": "\u043e", "a": "\u0430", "e": "\u0435", "c": "\u0441",
"p": "\u0440", "x": "\u0445"}
def jumla(mavzu, rng):
otlar = OTLAR[mavzu]
sozlar = [ot_shakli(str(rng.choice(otlar)), rng)]
if rng.random() < 0.5:
sozlar.append(ot_shakli(str(rng.choice(otlar)), rng))
tur = rng.random()
if tur < 0.3:
sozlar.append(str(rng.choice(SIFATLAR)))
if rng.random() < 0.3:
sozlar.append("emas")
return " ".join(sozlar)
if tur < 0.4:
sozlar.append(str(rng.choice(["bor", "yo'q"])))
return " ".join(sozlar)
if rng.random() < 0.25:
son = int(rng.integers(2, 100))
sozlar.append(str(rng.choice([f"{son} foizga", f"{1990 + son % 35}-yilda",
f"{son}.{son % 10} mln"])))
sozlar.append(fel_shakli(str(rng.choice(FELLAR)), rng))
return " ".join(sozlar)
def buz(soz, rng):
"""Imlo xatosi: harf tushib qolishi, takrorlanishi yoki o'rin almashishi."""
if len(soz) < 4:
return soz
i = int(rng.integers(1, len(soz) - 1))
tur = rng.integers(3)
if tur == 0:
return soz[:i] + soz[i + 1:]
if tur == 1:
return soz[:i] + soz[i] + soz[i:]
return soz[:i - 1] + soz[i] + soz[i - 1] + soz[i + 1:]
def hujjat(rng, mavzu=None):
if mavzu is None:
mavzu = str(rng.choice(sorted(OTLAR)))
jumlalar = [jumla(mavzu, rng) for _ in range(int(rng.integers(1, 5)))]
matn = ""
for j in jumlalar:
sozlar = [buz(s, rng) if rng.random() < 0.03 else s for s in j.split()]
j = " ".join(sozlar)
matn += j[0].upper() + j[1:] + str(rng.choice([".", ".", ".", "!", "?", "..."]))
matn += str(rng.choice([" ", " ", " ", " ", " ", "\u00a0", "\t"]))
matn = matn.strip()
odat = int(rng.choice(len(APOSTROFLAR), p=APOSTROF_P))
belgilar = []
for ch in matn:
if ch == "'":
k = odat if rng.random() < 0.85 else int(rng.integers(len(APOSTROFLAR)))
ch = APOSTROFLAR[k]
belgilar.append(ch)
matn = "".join(belgilar)
if rng.random() < 0.06:
matn = "".join(KIRILL[c] if c in KIRILL and rng.random() < 0.3 else c
for c in matn)
if rng.random() < 0.04:
matn = matn.upper()
if rng.random() < 0.05:
matn = matn.translate({ord(str(d)): 0xFF10 + d for d in range(10)})
return mavzu, matn
APOSTROF_RE = re.compile("[\u2018\u2019\u02bb\u02bc`]")
LOTINGA = {v: k for k, v in KIRILL.items()}
LOTINGA.update({v.upper(): k.upper() for k, v in KIRILL.items()})
def kirill_tuzat(soz):
yoz = {unicodedata.name(c).split()[0] for c in soz if c.isalpha()}
if "LATIN" in yoz and "CYRILLIC" in yoz:
return "".join(LOTINGA.get(c, c) for c in soz)
return soz
def normalla(matn):
"""23.1-darsdagi quvur: bo'sh joy, NFKC, apostrof, kirill, kichik harf, raqam."""
matn = re.sub(r"\s+", " ", matn).strip()
matn = unicodedata.normalize("NFKC", matn)
matn = APOSTROF_RE.sub("'", matn)
matn = " ".join(kirill_tuzat(s) for s in matn.split(" "))
return re.sub(r"[0-9]+", "0", matn.lower())
def togri_shakllar():
"""Generator bera oladigan barcha to'g'ri (xatosiz) so'z shakllari."""
shakllar = set(SIFATLAR) | {"emas", "bor", "yo'q", "foizga", "mln",
"0", "0-yilda", "0.0"}
for asos in [a for otlar in OTLAR.values() for a in otlar]:
for kop in ["", "lar"]:
s1 = asos + kop
unli = s1[-1] in UNLILAR
for eg in [""] + (["si", "miz", "ngiz", "m"] if unli
else ["i", "imiz", "ingiz", "im"]):
s2 = s1 + eg
for k in ["", "ning", "ni", "da", "dan", "ga", "dagi"]:
if k == "ga" and s2[-1] in "kq":
k = "ka" if s2[-1] == "k" else "qa"
shakllar.add(s2 + k)
for asos in FELLAR:
shakllar |= {asos + "di", asos + "madi", asos + "maydi",
asos + ("ydi" if asos[-1] in UNLILAR else "adi")}
return shakllar
TOKEN_RE = re.compile(r"[a-z0-9]+(?:['-][a-z0-9]+)*|[^\w\s]")
def tokenla(matn):
return TOKEN_RE.findall(normalla(matn))
class Lugat:
PAD, UNK = 0, 1
def __init__(self, hujjatlar, min_chastota=1):
sanoq = Counter(t for h in hujjatlar for t in h)
# tartib: chastota kamayishi, teng bo'lsa alifbo - deterministik
sozlar = sorted((s for s, n in sanoq.items() if n >= min_chastota),
key=lambda s: (-sanoq[s], s))
self.itos = ["<pad>", "<unk>"] + sozlar
self.stoi = {s: i for i, s in enumerate(self.itos)}
def __len__(self):
return len(self.itos)
def kodla(self, tokenlar):
return [self.stoi.get(t, self.UNK) for t in tokenlar]
def dekodla(self, idlar):
return [self.itos[i] for i in idlar if i != self.PAD]
def oov(lugat, hujjatlar):
jami = sum(len(h) for h in hujjatlar)
return sum(t not in lugat.stoi for h in hujjatlar for t in h) / jami
def main() -> None:
rng = np.random.default_rng(0)
korpus = [tokenla(hujjat(rng)[1]) for _ in range(3000)]
tartib = np.random.default_rng(1).permutation(len(korpus))
oquv = [korpus[i] for i in tartib[:2400]]
test = [korpus[i] for i in tartib[2400:]]
print("=== 1. Lug'at faqat o'quv qismidan ===")
lugat = Lugat(oquv, min_chastota=2)
print(f" o'quv: {len(oquv)} hujjat, test: {len(test)} hujjat")
print(f" lug'at hajmi (min_chastota=2): {len(lugat)}")
print(f" birinchi 8 ta: {lugat.itos[:8]}")
print(f" stoi['<pad>']={lugat.stoi['<pad>']}, stoi['<unk>']={lugat.stoi['<unk>']}")
print("\n=== 2. Kodlash va dekodlash ===")
gap = tokenla("Jamoamizning O\u2018YINCHILARIMIZDAGI kuchi stadionda yutdi!")
idlar = lugat.kodla(gap)
print(f" tokenlar: {gap}")
print(f" idlar: {idlar}")
print(f" qaytarish: {lugat.dekodla(idlar)}")
print(f" <unk> ga tushgan: {[t for t, i in zip(gap, idlar) if i == Lugat.UNK]}")
print("\n=== 3. Padding: bir xil uzunlikdagi batch ===")
batch = [lugat.kodla(h) for h in test[:4]]
L = max(len(b) for b in batch)
X = torch.tensor([b + [Lugat.PAD] * (L - len(b)) for b in batch])
niqob = X != Lugat.PAD
print(f" uzunliklar: {[len(b) for b in batch]} -> tensor {tuple(X.shape)}")
print(f" 1-qator: {X[0].tolist()}")
print(f" niqob yig'indisi: {niqob.sum(1).tolist()}")
print("\n=== 4. min_chastota: lug'at hajmi va OOV ===")
print(f" {'min_ch':>6} {'lug_at':>7} {'OOV o_quv':>10} {'OOV test':>9}")
natija = {}
for m in [1, 2, 3, 5, 10, 20]:
lg = Lugat(oquv, min_chastota=m)
natija[m] = oov(lg, test)
print(f" {m:>6} {len(lg):>7} {oov(lg, oquv):>10.2%} {natija[m]:>9.2%}")
hamma = Lugat(oquv + test, min_chastota=1)
print(f" SIZIB KETISH: lug'at o'quv+testdan -> OOV test {oov(hamma, test):.2%}")
print(f" halol baho (min_ch=1): OOV test {natija[1]:.2%}")
print("\n=== 5. Testdagi OOV tokenlar qanday? ===")
lg = Lugat(oquv, min_chastota=1)
yoq = Counter(t for h in test for t in h if t not in lg.stoi)
print(f" noyob OOV tokenlar: {len(yoq)}, jami: {sum(yoq.values())}")
print(f" namunalar: {sorted(yoq)[::max(1, len(yoq) // 6)][:6]}")
togri = togri_shakllar()
t_soni = sum(n for t, n in yoq.items() if t in togri)
print(f" ulardan to'g'ri so'z shakli: {t_soni / sum(yoq.values()):.0%}, "
f"qolgani imlo xatosi va boshqalar")
print(" ⭐ Lug'at faqat o'quvdan; test OOV - model haqiqatda ko'radigan holat")
if __name__ == "__main__":
main()Natijaning muhim qismi:
=== 1. Lug'at faqat o'quv qismidan ===
o'quv: 2400 hujjat, test: 600 hujjat
lug'at hajmi (min_chastota=2): 1507
birinchi 8 ta: ['<pad>', '<unk>', '.', '!', '?', '0', 'emas', 'mln']
stoi['<pad>']=0, stoi['<unk>']=1
=== 2. Kodlash va dekodlash ===
tokenlar: ['jamoamizning', "o'yinchilarimizdagi", 'kuchi', 'stadionda', 'yutdi', '!']
idlar: [1, 1, 1, 1368, 25, 3]
qaytarish: ['<unk>', '<unk>', '<unk>', 'stadionda', 'yutdi', '!']
<unk> ga tushgan: ['jamoamizning', "o'yinchilarimizdagi", 'kuchi']
=== 3. Padding: bir xil uzunlikdagi batch ===
uzunliklar: [9, 9, 6, 3] -> tensor (4, 9)
1-qator: [59, 657, 11, 3, 1, 1, 8, 36, 2]
niqob yig'indisi: [9, 9, 6, 3]
=== 4. min_chastota: lug'at hajmi va OOV ===
min_ch lug_at OOV o_quv OOV test
1 2953 0.00% 6.59%
2 1507 5.63% 10.67%
3 1011 9.50% 13.69%
5 624 14.57% 18.37%
10 248 24.16% 26.89%
20 146 29.04% 30.60%
SIZIB KETISH: lug'at o'quv+testdan -> OOV test 0.00%
halol baho (min_ch=1): OOV test 6.59%
=== 5. Testdagi OOV tokenlar qanday? ===
noyob OOV tokenlar: 401, jami: 435
namunalar: ['0-yila', 'dasturchingizni', 'jamamdan', 'musobaqamda', 'serverimizga', 'tbbiyot']
ulardan to'g'ri so'z shakli: 73%, qolgani imlo xatosi va boshqalar
⭐ Lug'at faqat o'quvdan; test OOV - model haqiqatda ko'radigan holatNima ko'rsatdi: 2.3, 2.4-bo'limlar.
Misol 3 — Zipf qonuni
"""Zipf qonuni: haqiqiy matnda (stdlib docstringlari) va sintetik korpusda."""
import ast
import pathlib
import re
import sysconfig
import unicodedata
import warnings
from collections import Counter
import numpy as np
OTLAR = {
"sport": ["jamoa", "o'yinchi", "murabbiy", "stadion", "o'yin", "g'alaba",
"chempionat", "darvoza", "to'p", "musobaqa", "muxlis", "gol",
"hakam", "sportchi", "mag'lubiyat", "klub"],
"iqtisod": ["bank", "narx", "soliq", "kompaniya", "bozor", "kredit",
"eksport", "sarmoya", "byudjet", "daromad", "savdo",
"tadbirkor", "valyuta", "tarif", "sur'at", "da'vo"],
"texnologiya": ["telefon", "dastur", "kompyuter", "internet", "ilova",
"ma'lumot", "tarmoq", "qurilma", "server", "dasturchi",
"robot", "platforma", "algoritm", "surat", "ta'rif",
"xavfsizlik"],
"salomatlik": ["shifokor", "kasalxona", "dori", "bemor", "sog'liq",
"vaksina", "parhez", "uyqu", "yurak", "qon", "mashq",
"vitamin", "klinika", "davo", "tibbiyot", "og'riq"],
}
FELLAR = ["osh", "kamay", "o'zgar", "yaxshilan", "tayyorla", "ko'rsat",
"boshla", "ishla", "yut", "qo'lla", "tekshir", "ta'minla",
"o'rgan", "sot", "yoq"]
SIFATLAR = ["yangi", "katta", "muhim", "tez", "yaxshi", "kuchli", "zaif",
"arzon", "qimmat", "sog'lom", "xavfli", "a'lo", "yuqori", "past",
"mashhur", "qulay"]
UNLILAR = set("aeiou")
def ot_shakli(asos, rng):
"""Ot + ko'plik + egalik + kelishik (soddalashtirilgan qoidalar)."""
s = asos
if rng.random() < 0.3:
s += "lar"
if rng.random() < 0.3:
unli = s[-1] in UNLILAR
s += rng.choice(["si", "miz", "ngiz", "m"] if unli
else ["i", "imiz", "ingiz", "im"])
if rng.random() < 0.6:
k = str(rng.choice(["ning", "ni", "da", "dan", "ga", "dagi"]))
if k == "ga" and s[-1] in "kq":
k = "ka" if s[-1] == "k" else "qa"
s += k
return s
def fel_shakli(asos, rng):
"""Fe'l: o'tgan/hozirgi zamon, ba'zan -ma- inkori."""
inkor = rng.random() < 0.25
if rng.random() < 0.5:
return asos + ("madi" if inkor else "di")
if inkor:
return asos + "maydi"
return asos + ("ydi" if asos[-1] in UNLILAR else "adi")
APOSTROFLAR = ["'", "\u2018", "\u2019", "\u02bb", "\u02bc", "`"]
APOSTROF_P = [0.45, 0.12, 0.2, 0.13, 0.05, 0.05]
KIRILL = {"o": "\u043e", "a": "\u0430", "e": "\u0435", "c": "\u0441",
"p": "\u0440", "x": "\u0445"}
def jumla(mavzu, rng):
otlar = OTLAR[mavzu]
sozlar = [ot_shakli(str(rng.choice(otlar)), rng)]
if rng.random() < 0.5:
sozlar.append(ot_shakli(str(rng.choice(otlar)), rng))
tur = rng.random()
if tur < 0.3:
sozlar.append(str(rng.choice(SIFATLAR)))
if rng.random() < 0.3:
sozlar.append("emas")
return " ".join(sozlar)
if tur < 0.4:
sozlar.append(str(rng.choice(["bor", "yo'q"])))
return " ".join(sozlar)
if rng.random() < 0.25:
son = int(rng.integers(2, 100))
sozlar.append(str(rng.choice([f"{son} foizga", f"{1990 + son % 35}-yilda",
f"{son}.{son % 10} mln"])))
sozlar.append(fel_shakli(str(rng.choice(FELLAR)), rng))
return " ".join(sozlar)
def buz(soz, rng):
"""Imlo xatosi: harf tushib qolishi, takrorlanishi yoki o'rin almashishi."""
if len(soz) < 4:
return soz
i = int(rng.integers(1, len(soz) - 1))
tur = rng.integers(3)
if tur == 0:
return soz[:i] + soz[i + 1:]
if tur == 1:
return soz[:i] + soz[i] + soz[i:]
return soz[:i - 1] + soz[i] + soz[i - 1] + soz[i + 1:]
def hujjat(rng, mavzu=None):
if mavzu is None:
mavzu = str(rng.choice(sorted(OTLAR)))
jumlalar = [jumla(mavzu, rng) for _ in range(int(rng.integers(1, 5)))]
matn = ""
for j in jumlalar:
sozlar = [buz(s, rng) if rng.random() < 0.03 else s for s in j.split()]
j = " ".join(sozlar)
matn += j[0].upper() + j[1:] + str(rng.choice([".", ".", ".", "!", "?", "..."]))
matn += str(rng.choice([" ", " ", " ", " ", " ", "\u00a0", "\t"]))
matn = matn.strip()
odat = int(rng.choice(len(APOSTROFLAR), p=APOSTROF_P))
belgilar = []
for ch in matn:
if ch == "'":
k = odat if rng.random() < 0.85 else int(rng.integers(len(APOSTROFLAR)))
ch = APOSTROFLAR[k]
belgilar.append(ch)
matn = "".join(belgilar)
if rng.random() < 0.06:
matn = "".join(KIRILL[c] if c in KIRILL and rng.random() < 0.3 else c
for c in matn)
if rng.random() < 0.04:
matn = matn.upper()
if rng.random() < 0.05:
matn = matn.translate({ord(str(d)): 0xFF10 + d for d in range(10)})
return mavzu, matn
APOSTROF_RE = re.compile("[\u2018\u2019\u02bb\u02bc`]")
LOTINGA = {v: k for k, v in KIRILL.items()}
LOTINGA.update({v.upper(): k.upper() for k, v in KIRILL.items()})
def kirill_tuzat(soz):
yoz = {unicodedata.name(c).split()[0] for c in soz if c.isalpha()}
if "LATIN" in yoz and "CYRILLIC" in yoz:
return "".join(LOTINGA.get(c, c) for c in soz)
return soz
def normalla(matn):
"""23.1-darsdagi quvur: bo'sh joy, NFKC, apostrof, kirill, kichik harf, raqam."""
matn = re.sub(r"\s+", " ", matn).strip()
matn = unicodedata.normalize("NFKC", matn)
matn = APOSTROF_RE.sub("'", matn)
matn = " ".join(kirill_tuzat(s) for s in matn.split(" "))
return re.sub(r"[0-9]+", "0", matn.lower())
TOKEN_RE = re.compile(r"[a-z0-9]+(?:['-][a-z0-9]+)*")
def docstring_matni():
"""Python standart kutubxonasining yuqori darajali modullari docstringlari."""
papka = pathlib.Path(sysconfig.get_paths()["stdlib"])
qismlar = []
for f in sorted(papka.glob("*.py")):
try:
with warnings.catch_warnings():
warnings.simplefilter("ignore")
daraxt = ast.parse(f.read_text(encoding="utf-8"))
except (SyntaxError, UnicodeDecodeError):
continue
for tugun in ast.walk(daraxt):
if isinstance(tugun, (ast.Module, ast.ClassDef, ast.FunctionDef,
ast.AsyncFunctionDef)):
d = ast.get_docstring(tugun)
if d:
qismlar.append(d)
return "\n".join(qismlar)
def zipf_tahlil(nom, tokenlar):
sanoq = Counter(tokenlar)
chastota = np.array(sorted(sanoq.values(), reverse=True), dtype=float)
rang = np.arange(1, len(chastota) + 1)
print(f"\n=== {nom} ===")
print(f" tokenlar: {len(tokenlar)}, lug'at: {len(sanoq)}")
eng = sorted(sanoq.items(), key=lambda kv: (-kv[1], kv[0]))[:8]
print(f" eng ko'p: {[s for s, _ in eng]}")
print(f" {'rang':>6} {'chastota':>9} {'chastota x rang':>16}")
for r in [1, 2, 5, 10, 50, 100, 500, 1000]:
if r <= len(chastota):
print(f" {r:>6} {chastota[r - 1]:>9.0f} {chastota[r - 1] * r:>16.0f}")
oraliq = (rang >= 10) & (rang <= min(1000, len(rang)))
qiyalik = np.polyfit(np.log(rang[oraliq]), np.log(chastota[oraliq]), 1)[0]
top10 = chastota[:10].sum() / chastota.sum()
yakka = (chastota == 1).sum() / len(chastota)
fr = chastota[oraliq] * rang[oraliq]
tebranish = fr.max() / fr.min()
print(f" log-log qiyalik (rang 10..1000): {qiyalik:.2f}")
print(f" chastota x rang (rang 10..1000): min {fr.min():.0f}, "
f"max {fr.max():.0f}, nisbat {tebranish:.1f}")
print(f" top-10 so'z ulushi: {top10:.1%}, bir marta uchraganlar: {yakka:.1%}")
qamrov = [chastota[:n].sum() / chastota.sum() for n in (100, 1000)]
print(f" qamrov: top-100 so'z {qamrov[0]:.1%}, top-1000 so'z {qamrov[1]:.1%} "
f"tokenlarni qoplaydi")
return qiyalik, tebranish
def main() -> None:
matn = docstring_matni()
haqiqiy = TOKEN_RE.findall(matn.lower())
q1 = zipf_tahlil("1. Haqiqiy matn: stdlib docstringlari (inglizcha)", haqiqiy)
rng = np.random.default_rng(0)
sintetik = [t for _ in range(12000)
for t in TOKEN_RE.findall(normalla(hujjat(rng)[1]))]
q2 = zipf_tahlil("2. Sintetik o'zbekcha korpus (12000 hujjat)", sintetik)
print("\n=== 3. Xulosa ===")
for nom, (q, t) in [("haqiqiy", q1), ("sintetik", q2)]:
qiya = "qiyalik -1 ga yaqin" if abs(q + 1) < 0.25 else "qiyalik -1 dan uzoq"
barqaror = "f x r barqaror" if t < 2.5 else "f x r barqaror EMAS"
print(f" {nom:<9} qiyalik {q:+.2f}, nisbat {t:.1f}: {qiya}, {barqaror}")
print(" ⭐ Bir necha so'z juda ko'p, ko'pchilik so'z juda kam - lug'at dumi uzun")
if __name__ == "__main__":
main()Natijaning muhim qismi:
=== 1. Haqiqiy matn: stdlib docstringlari (inglizcha) ===
tokenlar: 136289, lug'at: 7513
eng ko'p: ['the', 'a', 'is', 'to', 'of', 'and', 'if', 'for']
rang chastota chastota x rang
1 8166 8166
2 4423 8846
5 2734 13670
10 1418 14180
50 397 19850
100 198 19800
500 40 20000
1000 18 18000
log-log qiyalik (rang 10..1000): -0.99
chastota x rang (rang 10..1000): min 13079, max 20340, nisbat 1.6
top-10 so'z ulushi: 22.2%, bir marta uchraganlar: 36.1%
qamrov: top-100 so'z 50.6%, top-1000 so'z 83.1% tokenlarni qoplaydi
=== 2. Sintetik o'zbekcha korpus (12000 hujjat) ===
tokenlar: 86626, lug'at: 5979
eng ko'p: ['0', 'emas', 'mln', 'bor', '0-yilda', "yo'q", 'foizga', 'zaif']
rang chastota chastota x rang
1 4518 4518
2 2630 5260
5 1482 7410
10 572 5720
50 399 19950
100 140 14000
500 29 14500
1000 13 13000
log-log qiyalik (rang 10..1000): -0.96
chastota x rang (rang 10..1000): min 5720, max 20043, nisbat 3.5
top-10 so'z ulushi: 18.8%, bir marta uchraganlar: 36.2%
qamrov: top-100 so'z 50.1%, top-1000 so'z 83.6% tokenlarni qoplaydi
=== 3. Xulosa ===
haqiqiy qiyalik -0.99, nisbat 1.6: qiyalik -1 ga yaqin, f x r barqaror
sintetik qiyalik -0.96, nisbat 3.5: qiyalik -1 ga yaqin, f x r barqaror EMAS
⭐ Bir necha so'z juda ko'p, ko'pchilik so'z juda kam - lug'at dumi uzunNima ko'rsatdi: 2.5-bo'lim.
Misol 4 — Agglutinativlik va lug'at portlashi
"""Agglutinativlik: o'zbekcha qo'shimchalar lug'atni qanday portlatadi."""
import re
import unicodedata
from collections import Counter, defaultdict
import numpy as np
OTLAR = {
"sport": ["jamoa", "o'yinchi", "murabbiy", "stadion", "o'yin", "g'alaba",
"chempionat", "darvoza", "to'p", "musobaqa", "muxlis", "gol",
"hakam", "sportchi", "mag'lubiyat", "klub"],
"iqtisod": ["bank", "narx", "soliq", "kompaniya", "bozor", "kredit",
"eksport", "sarmoya", "byudjet", "daromad", "savdo",
"tadbirkor", "valyuta", "tarif", "sur'at", "da'vo"],
"texnologiya": ["telefon", "dastur", "kompyuter", "internet", "ilova",
"ma'lumot", "tarmoq", "qurilma", "server", "dasturchi",
"robot", "platforma", "algoritm", "surat", "ta'rif",
"xavfsizlik"],
"salomatlik": ["shifokor", "kasalxona", "dori", "bemor", "sog'liq",
"vaksina", "parhez", "uyqu", "yurak", "qon", "mashq",
"vitamin", "klinika", "davo", "tibbiyot", "og'riq"],
}
FELLAR = ["osh", "kamay", "o'zgar", "yaxshilan", "tayyorla", "ko'rsat",
"boshla", "ishla", "yut", "qo'lla", "tekshir", "ta'minla",
"o'rgan", "sot", "yoq"]
SIFATLAR = ["yangi", "katta", "muhim", "tez", "yaxshi", "kuchli", "zaif",
"arzon", "qimmat", "sog'lom", "xavfli", "a'lo", "yuqori", "past",
"mashhur", "qulay"]
UNLILAR = set("aeiou")
def ot_shakli(asos, rng):
"""Ot + ko'plik + egalik + kelishik (soddalashtirilgan qoidalar)."""
s = asos
if rng.random() < 0.3:
s += "lar"
if rng.random() < 0.3:
unli = s[-1] in UNLILAR
s += rng.choice(["si", "miz", "ngiz", "m"] if unli
else ["i", "imiz", "ingiz", "im"])
if rng.random() < 0.6:
k = str(rng.choice(["ning", "ni", "da", "dan", "ga", "dagi"]))
if k == "ga" and s[-1] in "kq":
k = "ka" if s[-1] == "k" else "qa"
s += k
return s
def fel_shakli(asos, rng):
"""Fe'l: o'tgan/hozirgi zamon, ba'zan -ma- inkori."""
inkor = rng.random() < 0.25
if rng.random() < 0.5:
return asos + ("madi" if inkor else "di")
if inkor:
return asos + "maydi"
return asos + ("ydi" if asos[-1] in UNLILAR else "adi")
APOSTROFLAR = ["'", "\u2018", "\u2019", "\u02bb", "\u02bc", "`"]
APOSTROF_P = [0.45, 0.12, 0.2, 0.13, 0.05, 0.05]
KIRILL = {"o": "\u043e", "a": "\u0430", "e": "\u0435", "c": "\u0441",
"p": "\u0440", "x": "\u0445"}
def jumla(mavzu, rng):
otlar = OTLAR[mavzu]
sozlar = [ot_shakli(str(rng.choice(otlar)), rng)]
if rng.random() < 0.5:
sozlar.append(ot_shakli(str(rng.choice(otlar)), rng))
tur = rng.random()
if tur < 0.3:
sozlar.append(str(rng.choice(SIFATLAR)))
if rng.random() < 0.3:
sozlar.append("emas")
return " ".join(sozlar)
if tur < 0.4:
sozlar.append(str(rng.choice(["bor", "yo'q"])))
return " ".join(sozlar)
if rng.random() < 0.25:
son = int(rng.integers(2, 100))
sozlar.append(str(rng.choice([f"{son} foizga", f"{1990 + son % 35}-yilda",
f"{son}.{son % 10} mln"])))
sozlar.append(fel_shakli(str(rng.choice(FELLAR)), rng))
return " ".join(sozlar)
def buz(soz, rng):
"""Imlo xatosi: harf tushib qolishi, takrorlanishi yoki o'rin almashishi."""
if len(soz) < 4:
return soz
i = int(rng.integers(1, len(soz) - 1))
tur = rng.integers(3)
if tur == 0:
return soz[:i] + soz[i + 1:]
if tur == 1:
return soz[:i] + soz[i] + soz[i:]
return soz[:i - 1] + soz[i] + soz[i - 1] + soz[i + 1:]
def hujjat(rng, mavzu=None):
if mavzu is None:
mavzu = str(rng.choice(sorted(OTLAR)))
jumlalar = [jumla(mavzu, rng) for _ in range(int(rng.integers(1, 5)))]
matn = ""
for j in jumlalar:
sozlar = [buz(s, rng) if rng.random() < 0.03 else s for s in j.split()]
j = " ".join(sozlar)
matn += j[0].upper() + j[1:] + str(rng.choice([".", ".", ".", "!", "?", "..."]))
matn += str(rng.choice([" ", " ", " ", " ", " ", "\u00a0", "\t"]))
matn = matn.strip()
odat = int(rng.choice(len(APOSTROFLAR), p=APOSTROF_P))
belgilar = []
for ch in matn:
if ch == "'":
k = odat if rng.random() < 0.85 else int(rng.integers(len(APOSTROFLAR)))
ch = APOSTROFLAR[k]
belgilar.append(ch)
matn = "".join(belgilar)
if rng.random() < 0.06:
matn = "".join(KIRILL[c] if c in KIRILL and rng.random() < 0.3 else c
for c in matn)
if rng.random() < 0.04:
matn = matn.upper()
if rng.random() < 0.05:
matn = matn.translate({ord(str(d)): 0xFF10 + d for d in range(10)})
return mavzu, matn
APOSTROF_RE = re.compile("[\u2018\u2019\u02bb\u02bc`]")
LOTINGA = {v: k for k, v in KIRILL.items()}
LOTINGA.update({v.upper(): k.upper() for k, v in KIRILL.items()})
def kirill_tuzat(soz):
yoz = {unicodedata.name(c).split()[0] for c in soz if c.isalpha()}
if "LATIN" in yoz and "CYRILLIC" in yoz:
return "".join(LOTINGA.get(c, c) for c in soz)
return soz
def normalla(matn):
"""23.1-darsdagi quvur: bo'sh joy, NFKC, apostrof, kirill, kichik harf, raqam."""
matn = re.sub(r"\s+", " ", matn).strip()
matn = unicodedata.normalize("NFKC", matn)
matn = APOSTROF_RE.sub("'", matn)
matn = " ".join(kirill_tuzat(s) for s in matn.split(" "))
return re.sub(r"[0-9]+", "0", matn.lower())
SOZ_RE = re.compile(r"[a-z]+(?:'[a-z]+)*")
QOSHIMCHALAR = sorted(["lar", "imiz", "ingiz", "miz", "ngiz", "im", "si", "i",
"ning", "ni", "da", "dan", "ga", "ka", "qa", "dagi"],
key=len, reverse=True)
def asoslar():
return sorted({a for o in OTLAR.values() for a in o} | set(FELLAR)
| set(SIFATLAR), key=len, reverse=True)
def haqiqiy_asos(soz, asos_royxat):
"""Generator bilganidan: eng uzun mos keladigan asos (topilmasa None)."""
for a in asos_royxat:
if soz.startswith(a):
return a
return None
def sodda_stemmer(soz):
"""Oxirdan ma'lum qo'shimchalarni ochko'z tarzda kesadi."""
for _ in range(4):
for q in QOSHIMCHALAR:
if soz.endswith(q) and len(soz) - len(q) >= 3:
soz = soz[:-len(q)]
break
else:
break
return soz
def main() -> None:
rng = np.random.default_rng(0)
korpus = [SOZ_RE.findall(normalla(hujjat(rng)[1])) for _ in range(9000)]
test, oquv = korpus[:1000], korpus[1000:]
royxat = asoslar()
asos_of = {}
for h in korpus:
for s in h:
if s not in asos_of:
asos_of[s] = haqiqiy_asos(s, royxat)
print("=== 1. Bitta asos - ko'p shakl ===")
print(" jamoa + lar + imiz + ning = jamoalarimizning")
shakl = defaultdict(set)
for h in oquv:
for s in h:
if asos_of[s]:
shakl[asos_of[s]].add(s)
for a in ["jamoa", "o'yinchi", "bank", "sog'liq"]:
print(f" {a:<9} korpusda {len(shakl[a]):>3} xil shakl, masalan: "
f"{sorted(shakl[a], key=lambda s: (len(s), s))[-2:]}")
otlar = [a for o in OTLAR.values() for a in o]
print(f" ot asoslari: o'rtacha {np.mean([len(shakl[a]) for a in otlar]):.1f} "
f"shakl (nazariy maksimum 70)")
print("\n=== 2. Lug'at o'sishi (Heaps qonuni) ===")
test_sozlar = [s for h in test for s in h]
print(f" {'hujjat':>7} {'so_z shakllari':>15} {'asoslar':>8} "
f"{'asossiz':>8} {'OOV shakl':>10} {'OOV asos':>9}")
for n in [250, 500, 1000, 2000, 4000, 8000]:
sozlar = {s for h in oquv[:n] for s in h}
asos_set = {asos_of[s] or s for s in sozlar}
haqiqiy = sum(asos_of[s] is None for s in sozlar)
oov_s = np.mean([s not in sozlar for s in test_sozlar])
oov_a = np.mean([(asos_of[s] or s) not in asos_set for s in test_sozlar])
print(f" {n:>7} {len(sozlar):>15} {len(asos_set) - haqiqiy:>8} "
f"{haqiqiy:>8} {oov_s:>10.2%} {oov_a:>9.2%}")
print(" asossiz - asosi tanilmagan so'zlar (asos ichidagi imlo xatosi,")
print(" shuningdek emas, bor, yo'q kabi bir nechta yordamchi so'z)")
print("\n=== 3. Sodda qo'shimcha kesuvchi (stemmer) ===")
for s in ["jamoalarimizning", "o'yinchilarda", "banklarimizdagi", "o'yinchi"]:
print(f" {s:<18} -> {sodda_stemmer(s):<12} (haqiqiy: {asos_of.get(s) or haqiqiy_asos(s, royxat)})")
ot_sozlar = sorted({s for h in oquv for s in h if asos_of[s] in otlar})
togri = np.mean([sodda_stemmer(s) == asos_of[s] for s in ot_sozlar])
print(f" ot shakllarida to'g'ri asos: {togri:.1%} ({len(ot_sozlar)} shakl)")
xato = Counter(asos_of[s] for s in ot_sozlar if sodda_stemmer(s) != asos_of[s])
print(f" eng ko'p adashgan asoslar: {[a for a, _ in xato.most_common(4)]}")
stem_lug = {sodda_stemmer(s) for h in oquv for s in h}
oov_stem = np.mean([sodda_stemmer(s) not in stem_lug for s in test_sozlar])
print(f" lug'at: shakllar {len({s for h in oquv for s in h})} -> "
f"stem {len(stem_lug)}; test OOV {oov_stem:.2%}")
print(" ⭐ Agglutinativ tilda so'z darajasidagi lug'at portlaydi - asos kichik")
if __name__ == "__main__":
main()Natijaning muhim qismi:
=== 1. Bitta asos - ko'p shakl ===
jamoa + lar + imiz + ning = jamoalarimizning
jamoa korpusda 63 xil shakl, masalan: ['jamoalaringizdagi', 'jamoalaringizning']
o'yinchi korpusda 62 xil shakl, masalan: ["o'yinchilaringizdagi", "o'yinchilaringizning"]
bank korpusda 67 xil shakl, masalan: ['banklaringizdagi', 'banklaringizning']
sog'liq korpusda 64 xil shakl, masalan: ["sog'liqlaringizdan", "sog'liqlaringzdagi"]
ot asoslari: o'rtacha 63.6 shakl (nazariy maksimum 70)
=== 2. Lug'at o'sishi (Heaps qonuni) ===
hujjat so_z shakllari asoslar asossiz OOV shakl OOV asos
250 820 95 45 31.68% 1.68%
500 1274 95 74 22.53% 1.65%
1000 1930 95 140 15.63% 1.58%
2000 2768 95 275 9.91% 1.49%
4000 3861 95 510 6.03% 1.37%
8000 5151 95 907 3.65% 1.27%
asossiz - asosi tanilmagan so'zlar (asos ichidagi imlo xatosi,
shuningdek emas, bor, yo'q kabi bir nechta yordamchi so'z)
=== 3. Sodda qo'shimcha kesuvchi (stemmer) ===
jamoalarimizning -> jamoa (haqiqiy: jamoa)
o'yinchilarda -> o'yinch (haqiqiy: o'yinchi)
banklarimizdagi -> bank (haqiqiy: bank)
o'yinchi -> o'yinch (haqiqiy: o'yinchi)
ot shakllarida to'g'ri asos: 82.2% (4068 shakl)
eng ko'p adashgan asoslar: ['dori', "o'yinchi", 'sportchi', 'dasturchi']
lug'at: shakllar 5151 -> stem 1250; test OOV 1.57%
⭐ Agglutinativ tilda so'z darajasidagi lug'at portlaydi - asos kichikNima ko'rsatdi: 2.6-bo'lim.
5. To'g'ri va noto'g'ri tushunishlar
| Noto'g'ri fikr | To'g'risi |
|---|---|
"split() — yetarli tokenizator" |
Tinish belgilari so'zga yopishadi, lug'at dublikatlar bilan to'ladi |
"\w+ har tilda to'g'ri ishlaydi" |
O'zbekcha apostrofli so'zlarni ikkiga bo'ladi |
| "Lug'atni butun ma'lumotdan qurish qulay va zararsiz" | Test OOV nolga tushadi — yolg'on optimizm |
| "Katta lug'at har doim yaxshi" | Kam uchragan so'zlar uchun embedding o'rganilmaydi |
| "OOV — asosan imlo xatolari" | O'zbekchada asosan to'g'ri, lekin kam uchragan qo'shimcha birikmalari |
| "Ko'proq ma'lumot yig'sak, lug'at to'yinadi" | Shakllar lug'ati uzoq o'sadi, faqat asoslar tez to'yinadi |
| "Belgi tokenizatsiyasi hamma muammoni hal qiladi" | OOV yo'qoladi, lekin ketma-ketlik ~6 barobar uzayadi |
| "Sodda qo'shimcha kesuvchi asosni to'g'ri topadi" | -i bilan tugaydigan asoslarda ("o'yinchi") adashadi |
6. Keng tarqalgan xatolar va yechimlari
1. Normallashtirmasdan tokenlash
tokenlar = TOKEN_RE.findall(matn) # U+2019, katta harf # ⚠️
tokenlar = TOKEN_RE.findall(normalla(matn)) # ✅2. \w+ apostrofli so'zni bo'ladi
re.findall(r"\w+", "o'qituvchi") # ['o', 'qituvchi'] # ⚠️
re.findall(r"[a-z0-9]+(?:['-][a-z0-9]+)*", "o'qituvchi") # ✅3. Lug'at o'quv va testdan
lugat = Lugat(oquv + test) # test OOV = 0 # ⚠️
lugat = Lugat(oquv) # ✅4. Deterministik bo'lmagan tartib
itos = ["<pad>", "<unk>"] + list(set(tokenlar)) # har safar boshqa # ⚠️
itos = ["<pad>", "<unk>"] + sorted(sanoq, key=lambda s: (-sanoq[s], s)) # ✅5. <pad> indeksi boshqa
itos = sozlar + ["<pad>"] # pad = oxirgi indeks # ⚠️
itos = ["<pad>", "<unk>"] + sozlar # pad = 0, unk = 1 # ✅6. Noma'lum so'z KeyError beradi
[lugat.stoi[t] for t in tokenlar] # KeyError # ⚠️
[lugat.stoi.get(t, Lugat.UNK) for t in tokenlar] # ✅7. OOV ni o'quvda o'lchash
oov(lugat, oquv) # min_ch=1 da doim 0 # ⚠️
oov(lugat, test) # ✅7. Integratsiya — bu bilim qayerda kerak bo'ladi
- 23.1-dars (o'tilgan): normallashtirish — tokenizatsiyadan oldingi majburiy qadam
- 18-qism (o'tilgan): ma'lumot sizib ketishi — lug'atni faqat o'quvdan qurish shu qoidaning davomi
- 21-qism (o'tilgan):
nn.Embedding(len(lugat), d, padding_idx=0)— lug'at indekslari aynan shu yerga kiradi - 17-qism (o'tilgan): kam uchraydigan kategoriyalarni birlashtirish —
min_chastotaning tabular ko'rinishi - Keyingi darslar: BPE (qo'shimchalarni avtomatik topish), Bag-of-words va TF-IDF
- Transformerlar qismida: har model o'z tokenizatori va lug'ati bilan keladi; ularni aralashtirib bo'lmaydi
8. Eng yaxshi amaliyotlar
Tokenlashdan oldin 23.1-darsdagi normallashtirishni qo'llang.
Tokenizatorni tilga moslang: apostrof ichkarida — so'z, chetda — tinish.
Tinish belgilarini alohida token qiling.
Lug'atni faqat o'quv qismidan quring va uni model bilan birga saqlang.
<pad>=0,<unk>=1— va tartibni deterministik qiling.Test OOV ulushini o'lchang va hisobotga yozing.
OOV tarkibini ko'ring: imlo xatosimi yoki to'g'ri, lekin kam shaklmi.
Agglutinativ tilda subword tokenizatsiyani ko'rib chiqing (23.3-dars).
9. Amaliy topshiriq
Vazifa 1: Bashorat qiling
1. # "bor! bor. bor".split() - nechta noyob token?
2. # re.findall(r"\w+", "ma'lumot") ?
3. # TOKEN_RE.findall("'yaxshi'") ?
4. # TOKEN_RE.findall("0-yilda") ?
5. # Lugat da <pad> va <unk> indekslari?
6. # min_chastota=1 da o'quv OOV ulushi?
7. # lug'at o'quv+testdan qurilsa test OOV?
8. # min_chastota oshsa lug'at va OOV qanday o'zgaradi?
9. # Zipf qonunida f(r) * r ?
10. # log-log grafikda Zipf qiyaligi?
11. # jamoa + lar + imiz + ning = ?
12. # bitta ot asosidan (2 * 5 * 7) nechta shakl?Javoblar
- 3 ta —
bor!,bor.,bor ['ma', 'lumot']["'", 'yaxshi', "'"]— chetdagi apostrof tinish belgisi['0-yilda']0va1- 0 — har o'quv tokeni lug'atda bor
- 0 — bu yolg'on, sizib ketish
- Lug'at kichrayadi, OOV ortadi
- Taxminan o'zgarmas
- Taxminan
-1 jamoalarimizning- 70
Vazifa 2: Xatolarni tuzating
1. tokenlar = re.findall(r"\w+", matn)
2. lugat = Lugat(oquv + test, min_chastota=2)
3. itos = ["<pad>", "<unk>"] + list(set(barcha_tokenlar))
4. idlar = [lugat.stoi[t] for t in tokenlar]
5. X = torch.tensor([lugat.kodla(h) for h in batch]) # uzunliklar har xilJavoblar
1. tokenlar = TOKEN_RE.findall(normalla(matn))
2. lugat = Lugat(oquv, min_chastota=2)
3. sanoq = Counter(barcha_tokenlar)
itos = ["<pad>", "<unk>"] + sorted(sanoq, key=lambda s: (-sanoq[s], s))
4. idlar = [lugat.stoi.get(t, Lugat.UNK) for t in tokenlar]
5. L = max(len(h) for h in batch)
X = torch.tensor([lugat.kodla(h) + [0] * (L - len(h)) for h in batch])Vazifa 3: Tokenizatorlar
Modellang:
split()va\w+- Normallashtirish + o'z regex
- Apostrofli so'zlar
- Belgi va so'z uzunliklari
Vazifa 4: Lug'at
Modellang:
<pad>va<unk>- Kodlash va dekodlash
- Padding va niqob
min_chastotava OOV jadvali
Vazifa 5: Zipf
Modellang:
- Rang va chastota jadvali
- Log-log qiyalik
f * rbarqarorligi- Top-N qamrovi
Vazifa 6: Agglutinativlik
Modellang:
- Bir asosning shakllari
- Lug'at o'sishi
- Shakl va asos OOV
- Sodda stemmer
Vazifa 7: O'ylash
Hamkasbingiz aytdi: "Bizning o'zbekcha sharhlar modelida test OOV 10% chiqdi. Demak ma'lumot kam — yana ikki barobar sharh yig'amiz va muammo hal bo'ladi." Siz nima deysiz?
Javob
Qisqa javob: ko'proq ma'lumot OOV ni kamaytiradi, lekin sekin va oxirigacha emas. Avval OOV nimadan iboratligini ko'rish kerak.
1. OOV tarkibini tekshiring. 2-misolda testdagi OOV tokenlarning 73% i imlo xatosi emas, balki to'g'ri so'z shakli edi — shunchaki o'quvda uchramagan qo'shimcha birikmasi. Bu shuni anglatadiki, muammo so'zlarning o'zida emas, ularning shakllarida.
2. Ma'lumot ikki barobar oshsa nima bo'ladi — o'lchab ko'ring. 4-misolda o'quv hujjatlari 1000 dan 2000 ga oshganda shakl darajasidagi OOV 15.63% dan 9.91% ga, 4000 dan 8000 ga oshganda 6.03% dan 3.65% ga tushdi. Har ikki barobar ko'paytirish OOV ni atigi ~1.6 barobar kamaytiradi — va har safar ikki barobar ko'p ma'lumot kerak. Shu bilan birga shakllar lug'ati 1930 dan 5151 gacha o'sdi.
3. Asoslar darajasida manzara boshqa. O'sha jadvalda asos darajasidagi OOV 250 hujjatdayoq 1.68% edi va barcha 95 asos allaqachon ko'rilgan edi. Ya'ni model uchun zarur bilim — asoslar — tez to'planadi. Qiyinchilik — qo'shimchalar kombinatsiyasi.
4. Yechim — tokenizatsiyani o'zgartirish. Sodda qo'shimcha kesuvchi lug'atni 5151 dan 1250 ga kichraytirib, test OOV ni 1.57% ga tushirdi — lekin ot shakllarining atigi 82.2% ida asosni to'g'ri topdi ("o'yinchi" -> "o'yinch"). Keyingi darsdagi BPE esa qo'shimchalarni qo'lda yozilgan qoidalarsiz, ma'lumotdan o'zi o'rganadi.
Tavsiya:
# 1. OOV tokenlarni ajrating: imlo xatosi / to'g'ri shakl / yangi so'z
# 2. Ma'lumot hajmi bo'yicha OOV egri chizig'ini chizing (Heaps)
# 3. Subword tokenizatsiyani (BPE) sinab ko'ring - OOV deyarli 0
# 4. Shundan keyin ma'lumot yig'ish qarorini qabul qilingHamkasbga javob: "Keling, avval OOV ichida nima borligini ko'raylik. Agar u asosan to'g'ri qo'shimcha shakllari bo'lsa, ma'lumotni ikki barobar oshirish OOV ni atigi taxminan 1.6 barobar kamaytiradi. Subword tokenizatsiya bu muammoni bugunoq, qo'shimcha ma'lumotsiz hal qiladi."
Nimani mustahkamlaydi: 2.4, 2.6-bo'limlar.
Xulosa
Bu darsda matnni tokenlarga bo'lishni, lug'at qurishni va OOV ni o'lchashni o'rgandik.
Eng muhim uch fikr:
Tokenizator tilga moslanishi kerak. 1-misolda standart
\w+"o'qituvchi" niovaqituvchiga bo'ldi, U+02BB bilan yozilganini esa bitta token qoldirdi. 3000 hujjatli korpusda normallashtirish +\w+1636 ta bir harfli ma'nosiz token berdi vaoeng ko'p uchraydigan ikkinchi tokenga aylandi. Normallashtirish + o'z regex (TOKEN_RE) bilan bunday tokenlar 3 taga tushdi, tinish belgilari esa alohida tokenlarga ajraldi. Belgi tokenizatsiyasida lug'at atigi 32 ta, lekin hujjat 6.3 barobar uzun bo'ldi.Lug'at faqat o'quvdan, OOV esa testda o'lchanadi. 2-misolda o'quv qismidan qurilgan lug'at bilan test OOV
6.59%chiqdi, lug'at test bilan birga qurilganda esa0.00%— sizib ketish muammoni butunlay yashirdi.min_chastota1 dan 20 gacha oshganda lug'at 2953 dan 146 tagacha kichraydi, test OOV esa30.60%gacha o'sdi. Eng muhimi: test OOV tokenlarining 73% i imlo xatosi emas, balki to'g'ri, lekin o'quvda uchramagan so'z shakli edi.Zipf va agglutinativlik so'z lug'atini "cheksiz" qiladi. 3-misolda haqiqiy matnda (stdlib docstringlari) log-log qiyalik
-0.99,f × resa 1.6 barobar oraliqda barqaror bo'ldi; top-100 so'z tokenlarning50.6%ini qopladi. Sintetik korpus ham-0.96qiyalik berdi, lekinf × r3.5 barobar tebrandi — qiyalikning o'zi dalil emas. 4-misolda har ot asosi korpusda o'rtacha 63.6 xil shaklda uchradi: 8000 hujjatda shakllar lug'ati 5151 ga yetdi va hali o'sishda edi (shakl OOV3.65%), barcha 95 asos esa 250 hujjatdayoq ko'rilgan edi (asos OOV1.68%). Sodda qo'shimcha kesuvchi lug'atni 1250 ga kichraytirdi, lekin ot shakllarining atigi82.2%ida asosni to'g'ri topdi.
Keyingi darsda subword tokenizatsiya: BPE ni noldan yozamiz — juftlarni sanash, eng ko'p juftni birlashtirish va qoidalar ro'yxati — va o'zbekcha qo'shimchalar (-lar, -ning, -dagi) qanday qilib o'z-o'zidan alohida tokenlarga aylanishini ko'ramiz.
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!