Mundarija (21)
- 1. Kirish va motivatsiya
- 2. Nazariya — chuqur tushuntirish
- 2.1. Embedding + o'rtacha pooling
- 2.2. Padding va maska bilan o'rtacha
- 2.3. nn.EmbeddingBag va offsets
- 2.4. Tartibni ko'rmaslik va bigram tokenlar
- 2.5. Oldindan o'rgatilgan embedding: muzlatish va fine-tune
- 2.6. Halol taqqoslash
- 2.7. Tuzoqlar
- 3. Tez ma'lumotnoma
- 4. Batafsil misollar
- Misol 1 — Lug'at, padding, maska va EmbeddingBag
- Misol 2 — Noldan o'rgatish, tartib va bigram tokenlar
- Misol 3 — Oldindan o'rgatilgan embedding: muzlatish va fine-tune
- Misol 4 — TF-IDF bazaviysi bilan halol taqqoslash
- 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.7-dars: Embedding bilan klassifikatsiya
23-QISM — NLP VA KETMA-KETLIKLAR · 7-dars
1. Kirish va motivatsiya
23.5-darsda sharhlarni TF-IDF + logistik regressiya bilan klassifikatsiya qildik, 23.6-darsda esa so'zlarni zich vektorlarga aylantirishni o'rgandik. Endi ikkalasini birlashtiramiz: matnni so'z embeddinglaridan yig'ilgan vektor bilan ifodalab, uni neyron tarmoqqa beramiz.
Eng oddiy usul — o'rtacha pooling: sharhdagi har so'zning embeddingini olamiz va ularning o'rtachasini hisoblaymiz. Natija — sharh uzunligidan qat'i nazar bitta d o'lchovli vektor. Uning ustiga nn.Linear qo'ysak — klassifikator tayyor. Bu g'oya fastText kabi amaliy tizimlarning asosi bo'lgan: tez, sodda va ko'p vazifada kuchli.
Lekin bu yerda uchta amaliy savol bor. Birinchisi — texnik: sharhlar turli uzunlikda, batchga yig'ish uchun padding kerak, va o'rtacha hisoblaganda padding nollarini sanamaslik kerak (21.3-darsdagi collate_fn va maska). nn.EmbeddingBag bu muammoni padding siz, offsets bilan hal qiladi. Ikkinchisi — oldindan o'rgatilgan embedding: 23.6-darsdagi kabi belgilanmagan matnlarda o'rgatilgan vektorlar kam belgilangan ma'lumotda yordam beradimi? Uchinchisi va eng muhimi — halollik: bularning hammasi 23.5-darsdagi TF-IDF bazaviysidan yaxshimi?
Real vaziyat. Startap sharhlarni saralash uchun "zamonaviy" embedding modelini qurdi va uni hech narsa bilan solishtirmadi. Keyinroq yangi xodim TF-IDF + logistik regressiyani sinab ko'rdi: aniqlik bir xil, lekin model 10 barobar tez, va xatolarni tushuntirish oson. Yana bir kuzatuv: "sifati yaxshi emas" sharhini embedding modeli ijobiy deb topardi — o'rtacha pooling so'zlar tartibini ko'rmaydi. Bu darsda ikkala holatni ham o'lchaymiz.
Bu darsda embedding asosidagi klassifikatorni quramiz va uni kuchli bazaviy bilan halol solishtiramiz.
Bu darsda:
-
nn.Embedding+ o'rtacha pooling - Padding va maska bilan o'rtacha
nn.EmbeddingBagvaoffsets- Tartibni ko'rmaslik va bigram tokenlar
- Oldindan o'rgatilgan embedding: muzlatish va fine-tune
- TF-IDF bazaviysi bilan halol taqqoslash
- Tuzoqlar
ℹ Misollar real numpy/sklearn/torch bilan (Python 3.14, torch 2.14 CPU).
2. Nazariya — chuqur tushuntirish
2.1. Embedding + o'rtacha pooling
MATN -> INDEKSLAR -> VEKTORLAR -> BITTA VEKTOR -> SINF
"sifati juda zo'r" -> [57, 31, 102] -> (3, d) -> (d,) -> Linear -> 3 logit
LUG'AT 23.2-bob:
0 = <pad>, 1 = <unk>, qolganlari 2..V-1
faqat O'QUV to'plamida quriladi, kam uchraganlar -> <unk>
O'RTACHA POOLING:
h = (1 / L) * sum_{t=1..L} E[x_t]
so'zlar tartibi YO'QOLADI: "yaxshi emas" va "emas yaxshi" - bir xil h
BU NIMAGA TENG:
Linear(h) = W * (1/L) * sum E[x_t] = (1/L) * sum (W E)[x_t]
-> har so'zga (W E) orqali sinf "ovozi" beriladi
-> bag-of-words ustidagi chiziqli model, lekin og'irlik matritsasi
past rangli: (V, d) @ (d, 3)
-> kutilgan natija: TF-IDF unigram + LogReg ga yaqin
PARAMETRLAR:
V * d + d * 3 + 3 (V = 1000, d = 32 -> ~32 000)O'rtacha pooling — "yumshoq" bag-of-words; u so'zlar tartibini ko'rmaydi.
2.2. Padding va maska bilan o'rtacha
BATCH (21.3 dagi collate_fn):
sharh 1: [57, 31, 102] -> [57, 31, 102, 0, 0]
sharh 2: [12, 9, 44, 7, 88] -> [12, 9, 44, 7, 88]
mask: [1, 1, 1, 0, 0], [1, 1, 1, 1, 1]
NOTO'G'RI O'RTACHA:
e.mean(dim=1) -> padding vektorlari ham sanaladi
padding_idx=0 bo'lsa E[0] = 0, lekin maxraj 5 (3 emas)
-> qisqa sharh vektori L / L_max marta kichrayadi
-> natija BATCHDAGI eng uzun sharhga bog'liq bo'lib qoladi!
TO'G'RI O'RTACHA:
(e * mask.unsqueeze(-1)).sum(1) / mask.sum(1, keepdim=True)
padding_idx=0:
E[0] nol bilan boshlanadi va gradient olmaydi - o'zgarmaydi
lekin bu maskaning o'rnini BOSMAYDI (maxraj muammosi qoladi) Maska — padding ni o'rtachadan chiqarishning yagona to'g'ri yo'li; padding_idx faqat pad vektorini nol qiladi.
2.3. nn.EmbeddingBag va offsets
G'OYA: padding umuman kerak emas
barcha sharhlarni bitta uzun 1D tensorga ulaymiz
offsets - har sharh qayerdan boshlanishi
sharhlar: [57, 31, 102], [12, 9, 44, 7, 88], [5, 6]
ids: [57, 31, 102, 12, 9, 44, 7, 88, 5, 6]
offsets: [0, 3, 8]
bag = nn.EmbeddingBag(V, d, mode="mean")
h = bag(ids, offsets) # (3, d) - to'g'ridan-to'g'ri o'rtacha
AFZALLIKLAR:
padding va maska yo'q - xato qilish imkoni kam
(L, d) oraliq tensor qurilmaydi - tez va tejamkor
mode: "mean", "sum", "max"
TENGLIK:
EmbeddingBag(mean) == maskali o'rtacha (1-misolda tekshiramiz) EmbeddingBag — o'rtacha pooling ning tayyor, padding siz shakli.
2.4. Tartibni ko'rmaslik va bigram tokenlar
MUAMMO:
"sifati yaxshi emas" va "emas sifati yaxshi" -> bir xil vektor
"yaxshi" kuchli ijobiy, "emas" deyarli neytral -> "ijobiy"
YECHIM 1 - bigram tokenlar (fastText g'oyasi):
tokenlar: sifati, yaxshi, emas, sifati_yaxshi, yaxshi_emas
"yaxshi_emas" o'z embeddingini oladi -> inkor ko'rinadi
lug'at kattalashadi (min_soni bilan cheklanadi)
YECHIM 2 - tartibni ko'radigan modellar:
RNN (keyingi dars), LSTM/GRU, Transformerlar qismida - attention
23.5 BILAN PARALLEL:
TF-IDF unigram ~ embedding unigram
TF-IDF + bigram ~ embedding + bigram tokenBigram tokenlar — tartibning eng arzon "bo'lagi"; ular inkorni EmbeddingBag ga ham ko'rsatadi.
2.5. Oldindan o'rgatilgan embedding: muzlatish va fine-tune
QADAMLAR:
1. katta BELGILANMAGAN korpusda word2vec 23.6-bob -> E (V, d)
2. klassifikatorda: bag.weight.data.copy_(E)
3a. MUZLATISH: bag.weight.requires_grad_(False)
faqat Linear o'rganadi (d * 3 + 3 parametr)
3b. FINE-TUNE: hamma parametr o'rganadi, E - boshlang'ich nuqta
QACHON FOYDALI:
belgilangan ma'lumot KAM, belgilanmagani KO'P
testda o'quvda uchramagan so'zlar bor (sinonimlar) - ular
embedding orqali tanish so'zlarga yaqin
QACHON FOYDASIZ YOKI ZARARLI:
embedding vazifaga kerakli farqni saqlamagan bo'lsa:
word2vec "yaxshi" va "yomon" ni YAQIN qo'yadi - kontekstlari
bir xil ("sifati ___") -> sentiment uchun muhim farq yo'qolgan
muzlatilgan embedding bu farqni tiklay olmaydi
fine-tune qila oladi - lekin kam ma'lumotda qiyin
LUG'AT:
oldindan o'rgatilgan embedding lug'ati ishlatiladi
(klassifikator lug'ati u bilan bir xil bo'lishi SHART)Oldindan o'rgatilgan embedding — faraz, kafolat emas; u vazifaga kerakli farqni saqlaganini o'lchab tekshiring.
2.6. Halol taqqoslash
BAZAVIY: 23.5 dagi TF-IDF (so'z + bigram) + LogReg
sozlash deyarli yo'q, soniyalarda o'rganadi
HALOL SHARTLAR:
bir xil bo'linishlar (juftlashgan CV yoki bir xil qism to'plamlar)
tarmoq uchun bir necha seed
ikkala tomonga teng "imkoniyat": bigram faqat bir tomonga berilmaydi
kam ma'lumot va to'liq ma'lumot - alohida
QAROR QOIDASI 23.5-bob:
eng yaxshisidan sezilarli yomon bo'lmagan ENG SODDA model
soddalik: TF-IDF + LogReg < EmbeddingBag < oldindan o'rgatilgan + fine-tune
BAZAVIY YUTSA:
bu ham natija - uni yozing
"tarmoq zamonaviyroq" - dalil emasBazaviy yutsa — shuni yozing; bu embeddinglarni yomonlamaydi, balki qaysi sharoitda kerakligini ko'rsatadi.
2.7. Tuzoqlar
Asosiy tuzoqlar: padding ni o'rtachaga qo'shish (e.mean(1)); padding_idx maskaning o'rnini bosadi deb o'ylash; lug'atni butun ma'lumotda qurish; <unk> uchun joy qoldirmaslik; EmbeddingBag da offsets ni uzunliklar bilan adashtirish (offsets — kumulyativ boshlanish nuqtalari); oldindan o'rgatilgan embedding lug'atini klassifikator lug'atidan boshqa tartibda ishlatish; muzlatilgan embeddingni optimizatorga berib qo'yish (yoki aksincha — muzlatishni unutish); o'rtacha pooling tartibni ko'rmasligini unutish; TF-IDF bazaviysiz "embedding modeli yaxshi" deyish.
3. Tez ma'lumotnoma
import torch
import torch.nn as nn
from collections import Counter
def lugat_qur(matnlar, min_soni=2):
c = Counter(s for m in matnlar for s in m.split())
sozlar = sorted(s for s, k in c.items() if k >= min_soni)
return {"<pad>": 0, "<unk>": 1} | {s: i + 2 for i, s in enumerate(sozlar)}
def toldir(batch): # collate_fn: padding + maska
ketmalar, y = zip(*batch)
L = max(len(k) for k in ketmalar)
ids = torch.zeros(len(ketmalar), L, dtype=torch.long)
for i, k in enumerate(ketmalar):
ids[i, :len(k)] = torch.tensor(k)
return ids, (ids != 0).float(), torch.tensor(y)
emb = nn.Embedding(V, d, padding_idx=0)
e = emb(ids) # (B, L, d)
h = (e * mask.unsqueeze(-1)).sum(1) / mask.sum(1, keepdim=True)
bag = nn.EmbeddingBag(V, d, mode="mean")
ids1 = torch.tensor([i for k in ketmalar for i in k])
offsets = torch.tensor([0] + [len(k) for k in ketmalar[:-1]]).cumsum(0)
h = bag(ids1, offsets) # (B, d)
bag.weight.data.copy_(E_oldindan) # oldindan o'rgatilgan
bag.weight.requires_grad_(False) # muzlatish
opt = torch.optim.Adam([p for p in model.parameters() if p.requires_grad])Embedding bilan klassifikatsiya xulosasi
lug'at o'quvda: 0 = <pad>, 1 = <unk>
o'rtacha: maska bilan (yoki EmbeddingBag + offsets)
o'rtacha pooling tartibni ko'rmaydi -> bigram tokenlar
oldindan o'rgatilgan: muzlatish / fine-tune - o'lchang
TF-IDF + LogReg bilan juftlashgan taqqoslash, bazaviy yutsa - yozing4. Batafsil misollar
Misollar real numpy/sklearn/torch bilan (Python 3.14, torch 2.14 CPU).
Misol 1 — Lug'at, padding, maska va EmbeddingBag
"""O'rtacha pooling ning texnik qismi: collate_fn, maska, offsets."""
from collections import Counter
import numpy as np
import torch
import torch.nn as nn
from torch.utils.data import DataLoader
IJOBIY = ["yaxshi", "zo'r", "a'lo", "ajoyib", "sifatli", "qulay",
"chiroyli", "mustahkam"]
SALBIY = ["yomon", "sifatsiz", "noqulay", "xunuk", "nosoz", "mo'rt",
"sust", "chatoq"]
NEYTRAL = ["o'rtacha", "oddiy", "normal", "odatiy", "kutilgandek"]
JIHAT = ["sifati", "narxi", "ekrani", "batareyasi", "ovozi", "qadoqlash",
"yetkazib berish", "sotuvchi", "dizayni", "materiali"]
MAHSULOT = ["telefon", "noutbuk", "quloqchin", "soat", "choynak", "kurtka",
"krossovka", "sumka", "planshet", "kolonka"]
BOSH = ["kecha", "o'tgan hafta", "bir oy oldin", "do'kondan", "internetdan",
"sovg'a uchun", "akam uchun", ""]
FEL = ["oldim", "buyurtma qildim", "sotib oldim", "ishlatib ko'rdim"]
YAKUN = {1: ["tavsiya qilaman", "yana olaman", "hammaga maslahat beraman"],
-1: ["tavsiya qilmayman", "boshqa olmayman", "pulimga achinaman"],
0: ["hozircha shu", "vaqt ko'rsatadi", "yana ishlatib ko'ramiz"]}
ULUSH = {1: [0.8, 0.1, 0.1], -1: [0.1, 0.8, 0.1], 0: [0.15, 0.15, 0.7]}
def gap(qutb, rng):
j = rng.choice(JIHAT)
juda = "juda " if rng.random() < 0.3 else ""
r = rng.random()
if qutb == 1:
if r < 0.2:
return f"{j} {rng.choice(SALBIY)} emas"
if r < 0.3:
return f"{j} bilan muammo yo'q"
return f"{j} {juda}{rng.choice(IJOBIY)}"
if qutb == -1:
if r < 0.25:
return f"{j} {rng.choice(IJOBIY)} emas"
if r < 0.35:
return f"{j} bilan muammo bor"
return f"{j} {juda}{rng.choice(SALBIY)}"
return f"{j} {rng.choice(NEYTRAL)}"
def sharh(rng):
kayfiyat = int(rng.integers(-1, 2))
qismlar = [f"{rng.choice(BOSH)} {rng.choice(MAHSULOT)} "
f"{rng.choice(FEL)}".strip()]
ballar = []
for t in range(int(rng.integers(1, 5))):
q = int(rng.choice([1, -1, 0], p=ULUSH[kayfiyat]))
bog = rng.choice(["lekin", "va", "ammo", "ham", ""]) if t else ""
qismlar.append(f"{bog} {gap(q, rng)}".strip())
ballar.append(q)
if rng.random() < 0.5:
q = int(rng.choice([1, -1, 0], p=ULUSH[kayfiyat]))
qismlar.append(rng.choice(YAKUN[q]))
ballar.append(q)
ball = np.mean(ballar) + rng.normal(0, 0.1)
return " ".join(qismlar), 2 if ball > 0.35 else (0 if ball < -0.35 else 1)
def imlo_xato(matn, ulush, rng):
chiqish = []
for s in matn.split():
if len(s) >= 4 and rng.random() < ulush:
i = int(rng.integers(1, len(s) - 1))
tur = int(rng.integers(0, 3))
if tur == 0:
s = s[:i] + s[i + 1:]
elif tur == 1:
s = s[:i] + s[i] + s[i:]
else:
s = s[:i - 1] + s[i] + s[i - 1] + s[i + 1:]
chiqish.append(s)
return " ".join(chiqish)
def korpus(n, seed=0, xato=0.07):
rng = np.random.default_rng(seed)
toza, y = zip(*[sharh(rng) for _ in range(n)])
rng2 = np.random.default_rng(seed + 1000)
return [imlo_xato(t, xato, rng2) for t in toza], np.array(y), list(toza)
def lugat_qur(matnlar, min_soni=2):
c = Counter(s for m in matnlar for s in m.split())
sozlar = sorted(s for s, k in c.items() if k >= min_soni)
return {"<pad>": 0, "<unk>": 1} | {s: i + 2 for i, s in enumerate(sozlar)}
def kodla(matn, lugat):
return [lugat.get(s, 1) for s in matn.split()]
def toldir(batch):
"""collate_fn 21.3-bob: turli uzunlik -> padding + maska."""
ketmalar, y = zip(*batch)
L = max(len(k) for k in ketmalar)
ids = torch.zeros(len(ketmalar), L, dtype=torch.long)
for i, k in enumerate(ketmalar):
ids[i, :len(k)] = torch.tensor(k)
return ids, (ids != 0).float(), torch.tensor(y)
def main() -> None:
matnlar, y, _ = korpus(3000)
oquv, test = matnlar[:2400], matnlar[2400:]
print("=== 1. Lug'at (faqat o'quvda, min_soni=2) ===")
lugat = lugat_qur(oquv)
hammasi = len({s for m in oquv for s in m.split()})
print(f" o'quvdagi noyob so'zlar: {hammasi}, lug'at: {len(lugat)} "
f"(<pad>, <unk> bilan)")
test_soz = [s for m in test for s in m.split()]
print(f" test so'zlarining <unk> ulushi: "
f"{np.mean([s not in lugat for s in test_soz]):.1%}")
print(f" {test[0]}")
print(f" -> {kodla(test[0], lugat)}")
print("\n=== 2. collate_fn: padding va maska ===")
ds = [(kodla(m, lugat), int(t)) for m, t in zip(oquv, y[:2400])]
ids, mask, yb = next(iter(DataLoader(ds[:4], batch_size=4,
collate_fn=toldir)))
print(f" ids {tuple(ids.shape)}, mask {tuple(mask.shape)}, "
f"uzunliklar {mask.sum(1).int().tolist()}")
i = int(mask.sum(1).argmin())
print(f" eng qisqa ({i}-sharh) ids: {ids[i].tolist()}")
print(f" eng qisqa ({i}-sharh) mask: {mask[i].int().tolist()}")
print("\n=== 3. Maskali va maskasiz o'rtacha ===")
torch.manual_seed(0)
emb = nn.Embedding(len(lugat), 8, padding_idx=0)
e = emb(ids)
togri = (e * mask.unsqueeze(-1)).sum(1) / mask.sum(1, keepdim=True)
notogri = e.mean(1)
L = ids.shape[1]
for i in range(4):
n = int(mask[i].sum())
nisbat = (notogri[i].norm() / togri[i].norm()).item()
print(f" sharh {i}: uzunlik {n:>2}/{L}, |maskasiz| / |maskali| = "
f"{nisbat:.3f} (L/L_max = {n / L:.3f})")
print(f" pad vektori E[0]: {emb.weight[0].abs().max().item():.1f} "
f"(padding_idx=0)")
print(" E[0] = 0 bo'lsa ham maxraj noto'g'ri - vektor kichrayadi")
print("\n=== 4. EmbeddingBag + offsets ===")
ketmalar = [ds[i][0] for i in range(4)]
ids1 = torch.tensor([t for k in ketmalar for t in k])
offsets = torch.tensor([0] + [len(k) for k in ketmalar[:-1]]).cumsum(0)
print(f" ids1 shakli: {tuple(ids1.shape)}, offsets: {offsets.tolist()}")
bag = nn.EmbeddingBag(len(lugat), 8, mode="mean")
bag.weight.data.copy_(emb.weight.data)
h = bag(ids1, offsets)
farq = (h - togri).abs().max().item()
print(f" EmbeddingBag va maskali o'rtacha farqi: {farq:.2e}")
print(f" padding siz: {ids1.numel()} ta indeks, padding bilan: "
f"{ids.numel()} ta")
print("\n=== 5. padding_idx gradienti ===")
emb.zero_grad()
togri.sum().backward()
print(f" E[0] gradienti: {emb.weight.grad[0].norm().item():.1f}")
ishlatilgan = ids[ids > 1][0].item()
print(f" E[{ishlatilgan}] gradienti: "
f"{emb.weight.grad[ishlatilgan].norm().item():.3f}")
print("\n=== 6. Parametrlar (d = 32) ===")
model = nn.ModuleDict({"bag": nn.EmbeddingBag(len(lugat), 32),
"chiq": nn.Linear(32, 3)})
for nom, m in model.items():
print(f" {nom:<5} {sum(p.numel() for p in m.parameters()):>7}")
print(" ⭐ Deyarli hamma parametr - embedding jadvalida")
if __name__ == "__main__":
main()Natijaning muhim qismi:
=== 1. Lug'at (faqat o'quvda, min_soni=2) ===
o'quvdagi noyob so'zlar: 872, lug'at: 571 (<pad>, <unk> bilan)
test so'zlarining <unk> ulushi: 2.1%
akam uchun sumka ishlatib ko'rdim diizayni bilan muammo bor ovozi juda sifatli ham ovozi a'o ovozi sust emas tavsiya qilmayman
-> [16, 515, 495, 159, 191, 93, 49, 262, 55, 373, 170, 445, 134, 373, 7, 373, 499, 123, 504, 416]
=== 2. collate_fn: padding va maska ===
ids (4, 20), mask (4, 20), uzunliklar [13, 20, 17, 6]
eng qisqa (3-sharh) ids: [495, 470, 344, 396, 531, 123, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
eng qisqa (3-sharh) mask: [1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
=== 3. Maskali va maskasiz o'rtacha ===
sharh 0: uzunlik 13/20, |maskasiz| / |maskali| = 0.650 (L/L_max = 0.650)
sharh 1: uzunlik 20/20, |maskasiz| / |maskali| = 1.000 (L/L_max = 1.000)
sharh 2: uzunlik 17/20, |maskasiz| / |maskali| = 0.850 (L/L_max = 0.850)
sharh 3: uzunlik 6/20, |maskasiz| / |maskali| = 0.300 (L/L_max = 0.300)
pad vektori E[0]: 0.0 (padding_idx=0)
E[0] = 0 bo'lsa ham maxraj noto'g'ri - vektor kichrayadi
=== 4. EmbeddingBag + offsets ===
ids1 shakli: (56,), offsets: [0, 13, 33, 50]
EmbeddingBag va maskali o'rtacha farqi: 8.94e-08
padding siz: 56 ta indeks, padding bilan: 80 ta
=== 5. padding_idx gradienti ===
E[0] gradienti: 0.0
E[487] gradienti: 0.218
=== 6. Parametrlar (d = 32) ===
bag 18272
chiq 99
⭐ Deyarli hamma parametr - embedding jadvalidaNima ko'rsatdi: 2.1, 2.2, 2.3-bo'limlar.
Misol 2 — Noldan o'rgatish, tartib va bigram tokenlar
"""EmbeddingBag klassifikatori: unigram va bigram tokenlar, TF-IDF bilan."""
from collections import Counter
import numpy as np
import torch
import torch.nn as nn
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import make_pipeline
IJOBIY = ["yaxshi", "zo'r", "a'lo", "ajoyib", "sifatli", "qulay",
"chiroyli", "mustahkam"]
SALBIY = ["yomon", "sifatsiz", "noqulay", "xunuk", "nosoz", "mo'rt",
"sust", "chatoq"]
NEYTRAL = ["o'rtacha", "oddiy", "normal", "odatiy", "kutilgandek"]
JIHAT = ["sifati", "narxi", "ekrani", "batareyasi", "ovozi", "qadoqlash",
"yetkazib berish", "sotuvchi", "dizayni", "materiali"]
MAHSULOT = ["telefon", "noutbuk", "quloqchin", "soat", "choynak", "kurtka",
"krossovka", "sumka", "planshet", "kolonka"]
BOSH = ["kecha", "o'tgan hafta", "bir oy oldin", "do'kondan", "internetdan",
"sovg'a uchun", "akam uchun", ""]
FEL = ["oldim", "buyurtma qildim", "sotib oldim", "ishlatib ko'rdim"]
YAKUN = {1: ["tavsiya qilaman", "yana olaman", "hammaga maslahat beraman"],
-1: ["tavsiya qilmayman", "boshqa olmayman", "pulimga achinaman"],
0: ["hozircha shu", "vaqt ko'rsatadi", "yana ishlatib ko'ramiz"]}
ULUSH = {1: [0.8, 0.1, 0.1], -1: [0.1, 0.8, 0.1], 0: [0.15, 0.15, 0.7]}
def gap(qutb, rng):
j = rng.choice(JIHAT)
juda = "juda " if rng.random() < 0.3 else ""
r = rng.random()
if qutb == 1:
if r < 0.2:
return f"{j} {rng.choice(SALBIY)} emas"
if r < 0.3:
return f"{j} bilan muammo yo'q"
return f"{j} {juda}{rng.choice(IJOBIY)}"
if qutb == -1:
if r < 0.25:
return f"{j} {rng.choice(IJOBIY)} emas"
if r < 0.35:
return f"{j} bilan muammo bor"
return f"{j} {juda}{rng.choice(SALBIY)}"
return f"{j} {rng.choice(NEYTRAL)}"
def sharh(rng):
kayfiyat = int(rng.integers(-1, 2))
qismlar = [f"{rng.choice(BOSH)} {rng.choice(MAHSULOT)} "
f"{rng.choice(FEL)}".strip()]
ballar = []
for t in range(int(rng.integers(1, 5))):
q = int(rng.choice([1, -1, 0], p=ULUSH[kayfiyat]))
bog = rng.choice(["lekin", "va", "ammo", "ham", ""]) if t else ""
qismlar.append(f"{bog} {gap(q, rng)}".strip())
ballar.append(q)
if rng.random() < 0.5:
q = int(rng.choice([1, -1, 0], p=ULUSH[kayfiyat]))
qismlar.append(rng.choice(YAKUN[q]))
ballar.append(q)
ball = np.mean(ballar) + rng.normal(0, 0.1)
return " ".join(qismlar), 2 if ball > 0.35 else (0 if ball < -0.35 else 1)
def imlo_xato(matn, ulush, rng):
chiqish = []
for s in matn.split():
if len(s) >= 4 and rng.random() < ulush:
i = int(rng.integers(1, len(s) - 1))
tur = int(rng.integers(0, 3))
if tur == 0:
s = s[:i] + s[i + 1:]
elif tur == 1:
s = s[:i] + s[i] + s[i:]
else:
s = s[:i - 1] + s[i] + s[i - 1] + s[i + 1:]
chiqish.append(s)
return " ".join(chiqish)
def korpus(n, seed=0, xato=0.07):
rng = np.random.default_rng(seed)
toza, y = zip(*[sharh(rng) for _ in range(n)])
rng2 = np.random.default_rng(seed + 1000)
return [imlo_xato(t, xato, rng2) for t in toza], np.array(y), list(toza)
def tokenlar(matn, bigram):
s = matn.split()
return s + ([f"{a}_{b}" for a, b in zip(s, s[1:])] if bigram else [])
def lugat_qur(matnlar, bigram, min_soni=2):
c = Counter(t for m in matnlar for t in tokenlar(m, bigram))
sozlar = sorted(t for t, k in c.items() if k >= min_soni)
return {"<pad>": 0, "<unk>": 1} | {t: i + 2 for i, t in enumerate(sozlar)}
def paket(ketmalar):
offsets = torch.tensor([0] + [len(k) for k in ketmalar[:-1]]).cumsum(0)
return torch.tensor([t for k in ketmalar for t in k]), offsets
class BagModel(nn.Module):
def __init__(self, V, d=32):
super().__init__()
self.bag = nn.EmbeddingBag(V, d, mode="mean")
self.chiq = nn.Linear(d, 3)
def forward(self, ids, offsets):
return self.chiq(self.bag(ids, offsets))
def orgat(ketmalar, y, V, seed, davrlar=15):
torch.manual_seed(seed)
model = BagModel(V)
opt = torch.optim.Adam(model.parameters(), lr=0.01)
g = torch.Generator().manual_seed(seed)
yt = torch.tensor(y)
for _ in range(davrlar):
tartib = torch.randperm(len(ketmalar), generator=g).tolist()
for i in range(0, len(tartib), 64):
b = tartib[i:i + 64]
opt.zero_grad()
nn.functional.cross_entropy(
model(*paket([ketmalar[j] for j in b])), yt[b]).backward()
opt.step()
model.eval()
return model
def bashorat(model, ketmalar):
with torch.no_grad():
return model(*paket(ketmalar)).argmax(1).numpy()
def main() -> None:
matnlar, y, _ = korpus(3000)
Xtr, Xte, ytr, yte = matnlar[:2400], matnlar[2400:], y[:2400], y[2400:]
TP = r"[\w']+"
SINF = ["salbiy", "neytral", "ijobiy"]
print("=== 1. TF-IDF bazaviylari 23.5-bob ===")
tf = {}
for nom, ng in [("TF-IDF unigram", (1, 1)), ("TF-IDF + bigram", (1, 2))]:
m = make_pipeline(TfidfVectorizer(token_pattern=TP, ngram_range=ng),
LogisticRegression(C=10, max_iter=3000))
tf[nom] = m.fit(Xtr, ytr).score(Xte, yte)
print(f" {nom:<16} {tf[nom]:.4f}")
print("\n=== 2. EmbeddingBag (d=32, 15 davr, 3 seed) ===")
modellar, natija = {}, {}
for nom, bigram in [("Emb unigram", False), ("Emb + bigram", True)]:
lugat = lugat_qur(Xtr, bigram)
k_tr = [[lugat.get(t, 1) for t in tokenlar(m, bigram)] for m in Xtr]
k_te = [[lugat.get(t, 1) for t in tokenlar(m, bigram)] for m in Xte]
aniq = []
for s in range(3):
model = orgat(k_tr, ytr, len(lugat), seed=s)
aniq.append((bashorat(model, k_te) == yte).mean())
modellar[nom] = (model, lugat, bigram)
natija[nom] = np.array(aniq)
print(f" {nom:<14} lug'at {len(lugat):>5} aniqlik "
f"{np.mean(aniq):.4f} ± {np.std(aniq, ddof=1):.4f}")
print("\n=== 3. Juftlashgan farq: bigram - unigram (3 seed) ===")
d = natija["Emb + bigram"] - natija["Emb unigram"]
se = d.std(ddof=1) / np.sqrt(len(d))
print(f" {d.mean():+.4f}, SE {se:.4f}, "
f"{'sezilarli' if abs(d.mean()) > 2 * se else 'sezilarli emas'}")
for nom, t in [("unigram", "TF-IDF unigram"),
("bigram", "TF-IDF + bigram")]:
e = natija[f"Emb {nom}" if nom == "unigram" else "Emb + bigram"]
print(f" Emb {nom:<8} - {t:<16} {e.mean() - tf[t]:+.4f}")
print("\n=== 4. Tartib va inkor ===")
sinov = ["telefon oldim sifati yaxshi",
"telefon oldim sifati yaxshi emas",
"telefon oldim emas sifati yaxshi",
"telefon oldim sifati yomon emas"]
print(f" {'matn':<34} {'unigram':>9} {'bigram':>9}")
for m in sinov:
chiq = []
for nom in ["Emb unigram", "Emb + bigram"]:
model, lugat, bigram = modellar[nom]
k = [[lugat.get(t, 1) for t in tokenlar(m, bigram)]]
chiq.append(SINF[bashorat(model, k)[0]])
print(f" {m:<34} {chiq[0]:>9} {chiq[1]:>9}")
print(" unigram modeli uchun 2- va 3-matn AYNAN bir xil vektor")
print(" ⭐ O'rtacha pooling tartibni ko'rmaydi - bigram token yordam beradi")
if __name__ == "__main__":
main()Natijaning muhim qismi:
=== 1. TF-IDF bazaviylari 23.5-bob ===
TF-IDF unigram 0.7250
TF-IDF + bigram 0.8083
=== 2. EmbeddingBag (d=32, 15 davr, 3 seed) ===
Emb unigram lug'at 571 aniqlik 0.7189 ± 0.0025
Emb + bigram lug'at 1944 aniqlik 0.8044 ± 0.0035
=== 3. Juftlashgan farq: bigram - unigram (3 seed) ===
+0.0856, SE 0.0024, sezilarli
Emb unigram - TF-IDF unigram -0.0061
Emb bigram - TF-IDF + bigram -0.0039
=== 4. Tartib va inkor ===
matn unigram bigram
telefon oldim sifati yaxshi ijobiy ijobiy
telefon oldim sifati yaxshi emas ijobiy salbiy
telefon oldim emas sifati yaxshi ijobiy neytral
telefon oldim sifati yomon emas salbiy ijobiy
unigram modeli uchun 2- va 3-matn AYNAN bir xil vektor
⭐ O'rtacha pooling tartibni ko'rmaydi - bigram token yordam beradiNima ko'rsatdi: 2.1, 2.4-bo'limlar.
Misol 3 — Oldindan o'rgatilgan embedding: muzlatish va fine-tune
"""Belgilanmagan sharhlarda SGNS, keyin kam belgilangan ma'lumotda klassifikator."""
from collections import Counter
import numpy as np
import torch
import torch.nn as nn
IJOBIY = ["yaxshi", "zo'r", "a'lo", "ajoyib", "sifatli", "qulay",
"chiroyli", "mustahkam"]
SALBIY = ["yomon", "sifatsiz", "noqulay", "xunuk", "nosoz", "mo'rt",
"sust", "chatoq"]
NEYTRAL = ["o'rtacha", "oddiy", "normal", "odatiy", "kutilgandek"]
JIHAT = ["sifati", "narxi", "ekrani", "batareyasi", "ovozi", "qadoqlash",
"yetkazib berish", "sotuvchi", "dizayni", "materiali"]
MAHSULOT = ["telefon", "noutbuk", "quloqchin", "soat", "choynak", "kurtka",
"krossovka", "sumka", "planshet", "kolonka"]
BOSH = ["kecha", "o'tgan hafta", "bir oy oldin", "do'kondan", "internetdan",
"sovg'a uchun", "akam uchun", ""]
FEL = ["oldim", "buyurtma qildim", "sotib oldim", "ishlatib ko'rdim"]
YAKUN = {1: ["tavsiya qilaman", "yana olaman", "hammaga maslahat beraman"],
-1: ["tavsiya qilmayman", "boshqa olmayman", "pulimga achinaman"],
0: ["hozircha shu", "vaqt ko'rsatadi", "yana ishlatib ko'ramiz"]}
ULUSH = {1: [0.8, 0.1, 0.1], -1: [0.1, 0.8, 0.1], 0: [0.15, 0.15, 0.7]}
def gap(qutb, rng):
j = rng.choice(JIHAT)
juda = "juda " if rng.random() < 0.3 else ""
r = rng.random()
if qutb == 1:
if r < 0.2:
return f"{j} {rng.choice(SALBIY)} emas"
if r < 0.3:
return f"{j} bilan muammo yo'q"
return f"{j} {juda}{rng.choice(IJOBIY)}"
if qutb == -1:
if r < 0.25:
return f"{j} {rng.choice(IJOBIY)} emas"
if r < 0.35:
return f"{j} bilan muammo bor"
return f"{j} {juda}{rng.choice(SALBIY)}"
return f"{j} {rng.choice(NEYTRAL)}"
def sharh(rng):
kayfiyat = int(rng.integers(-1, 2))
qismlar = [f"{rng.choice(BOSH)} {rng.choice(MAHSULOT)} "
f"{rng.choice(FEL)}".strip()]
ballar = []
for t in range(int(rng.integers(1, 5))):
q = int(rng.choice([1, -1, 0], p=ULUSH[kayfiyat]))
bog = rng.choice(["lekin", "va", "ammo", "ham", ""]) if t else ""
qismlar.append(f"{bog} {gap(q, rng)}".strip())
ballar.append(q)
if rng.random() < 0.5:
q = int(rng.choice([1, -1, 0], p=ULUSH[kayfiyat]))
qismlar.append(rng.choice(YAKUN[q]))
ballar.append(q)
ball = np.mean(ballar) + rng.normal(0, 0.1)
return " ".join(qismlar), 2 if ball > 0.35 else (0 if ball < -0.35 else 1)
def imlo_xato(matn, ulush, rng):
chiqish = []
for s in matn.split():
if len(s) >= 4 and rng.random() < ulush:
i = int(rng.integers(1, len(s) - 1))
tur = int(rng.integers(0, 3))
if tur == 0:
s = s[:i] + s[i + 1:]
elif tur == 1:
s = s[:i] + s[i] + s[i:]
else:
s = s[:i - 1] + s[i] + s[i - 1] + s[i + 1:]
chiqish.append(s)
return " ".join(chiqish)
def korpus(n, seed=0, xato=0.07):
rng = np.random.default_rng(seed)
toza, y = zip(*[sharh(rng) for _ in range(n)])
rng2 = np.random.default_rng(seed + 1000)
return [imlo_xato(t, xato, rng2) for t in toza], np.array(y), list(toza)
def lugat_qur(matnlar, min_soni=2):
c = Counter(s for m in matnlar for s in m.split())
sozlar = sorted(s for s, k in c.items() if k >= min_soni)
return {"<pad>": 0, "<unk>": 1} | {s: i + 2 for i, s in enumerate(sozlar)}
def sgns(ketmalar, V, d=32, k=5, qadamlar=400, seed=0, oyna=2):
"""23.6-dars uslubida skip-gram + negative sampling."""
m, c = [], []
for ids in ketmalar:
for i, w in enumerate(ids):
for j in range(max(0, i - oyna), min(len(ids), i + oyna + 1)):
if j != i:
m.append(w)
c.append(ids[j])
M, C = torch.tensor(m), torch.tensor(c)
soni = np.bincount(m, minlength=V).astype(float)
P = torch.tensor(soni ** 0.75 / (soni ** 0.75).sum(), dtype=torch.float)
torch.manual_seed(seed)
markaz, kontekst = nn.Embedding(V, d), nn.Embedding(V, d)
nn.init.uniform_(markaz.weight, -0.5 / d, 0.5 / d)
nn.init.zeros_(kontekst.weight)
opt = torch.optim.Adam([markaz.weight, kontekst.weight], lr=0.01)
g = torch.Generator().manual_seed(seed)
qadam = 0
while qadam < qadamlar:
tartib = torch.randperm(len(M), generator=g)
for i in range(0, len(M), 2048):
b = tartib[i:i + 2048]
neg = torch.multinomial(P, len(b) * k, replacement=True,
generator=g).view(len(b), k)
vm = markaz(M[b])
ijobiy = (vm * kontekst(C[b])).sum(1)
salbiy = torch.bmm(kontekst(neg), vm.unsqueeze(2)).squeeze(2)
loss = -(nn.functional.logsigmoid(ijobiy).mean()
+ nn.functional.logsigmoid(-salbiy).sum(1).mean())
opt.zero_grad()
loss.backward()
opt.step()
qadam += 1
if qadam >= qadamlar:
break
return markaz.weight.detach().clone()
def paket(ketmalar):
offsets = torch.tensor([0] + [len(k) for k in ketmalar[:-1]]).cumsum(0)
return torch.tensor([t for k in ketmalar for t in k]), offsets
class BagModel(nn.Module):
def __init__(self, V, d, E=None, muzlat=False):
super().__init__()
self.bag = nn.EmbeddingBag(V, d, mode="mean")
if E is not None:
self.bag.weight.data.copy_(E)
self.bag.weight.requires_grad_(not muzlat)
self.chiq = nn.Linear(d, 3)
def forward(self, ids, offsets):
return self.chiq(self.bag(ids, offsets))
def orgat_bahola(k_tr, y_tr, k_te, y_te, V, E, muzlat, seed, qadamlar=200):
"""Hamma variant va hamma n uchun BIR XIL qadamlar soni."""
torch.manual_seed(seed)
model = BagModel(V, 32, E, muzlat)
opt = torch.optim.Adam([p for p in model.parameters() if p.requires_grad],
lr=0.01)
g = torch.Generator().manual_seed(seed)
yt = torch.tensor(y_tr)
qadam = 0
while qadam < qadamlar:
tartib = torch.randperm(len(k_tr), generator=g).tolist()
for i in range(0, len(tartib), 64):
b = tartib[i:i + 64]
opt.zero_grad()
nn.functional.cross_entropy(
model(*paket([k_tr[j] for j in b])), yt[b]).backward()
opt.step()
qadam += 1
if qadam >= qadamlar:
break
with torch.no_grad():
return (model(*paket(k_te)).argmax(1).numpy() == y_te).mean()
def main() -> None:
belgisiz, _, _ = korpus(8000, seed=7) # yorliqsiz katta korpus
matnlar, y, _ = korpus(3000)
lugat = lugat_qur(belgisiz) # oldindan o'rgatish lug'ati
V = len(lugat)
kod = lambda ms: [[lugat.get(s, 1) for s in m.split()] for m in ms]
print("=== 1. Oldindan o'rgatish (SGNS, 8000 belgisiz sharh) ===")
E = sgns(kod(belgisiz), V)
print(f" lug'at {V}, embedding {tuple(E.shape)}")
En = E / E.norm(dim=1, keepdim=True)
ortacha = lambda A, B: np.mean([float(En[lugat[a]] @ En[lugat[b]])
for a in A for b in B if a != b])
ij, sa = ["yaxshi", "zo'r", "a'lo", "ajoyib"], ["yomon", "sifatsiz",
"nosoz", "chatoq"]
print(f" cos ijobiy-ijobiy: {ortacha(ij, ij):.3f}")
print(f" cos salbiy-salbiy: {ortacha(sa, sa):.3f}")
print(f" cos ijobiy-salbiy: {ortacha(ij, sa):.3f}")
print(f" cos ijobiy-mahsulot: "
f"{ortacha(ij, ['telefon', 'soat', 'sumka', 'kurtka']):.3f}")
print(" word2vec 'yaxshi' va 'yomon' ni deyarli bir joyga qo'ydi -")
print(" ikkalasi ham 'sifati ___' kontekstida keladi")
print("\n=== 2. Kam belgilangan ma'lumot (test 1000, 3 seed, 200 qadam) ===")
k_te, y_te = kod(matnlar[2000:]), y[2000:]
variantlar = [("tasodifiy", None, False), ("muzlatilgan", E, True),
("fine-tune", E, False)]
natija = {}
print(f" {'n':>5} " + " ".join(f"{v[0]:>12}" for v in variantlar))
for n in [100, 300, 1000]:
natija[n] = {v[0]: [] for v in variantlar}
for s in range(3):
idx = np.random.default_rng(s).choice(2000, n, replace=False)
k_tr = kod([matnlar[i] for i in idx])
for nom, emb, muz in variantlar:
natija[n][nom].append(orgat_bahola(k_tr, y[idx], k_te, y_te,
V, emb, muz, seed=s))
print(f" {n:>5} " + " ".join(f"{np.mean(natija[n][v[0]]):>12.4f}"
for v in variantlar))
print("\n=== 3. Juftlashgan farq tasodifiy init ga nisbatan ===")
for n in natija:
for nom in ["muzlatilgan", "fine-tune"]:
d = np.array(natija[n][nom]) - np.array(natija[n]["tasodifiy"])
se = d.std(ddof=1) / np.sqrt(len(d))
if abs(d.mean()) <= 2 * se:
x = "sezilarli emas"
else:
x = "sezilarli yaxshi" if d.mean() > 0 else "sezilarli yomon"
print(f" n={n:>4} {nom:<12} {d.mean():+.4f} SE {se:.4f} {x}")
print(" ⭐ Oldindan o'rgatilgan embedding - faraz; uni o'lchab tekshiring")
if __name__ == "__main__":
main()Natijaning muhim qismi:
=== 1. Oldindan o'rgatish (SGNS, 8000 belgisiz sharh) ===
lug'at 993, embedding (993, 32)
cos ijobiy-ijobiy: 0.982
cos salbiy-salbiy: 0.983
cos ijobiy-salbiy: 0.955
cos ijobiy-mahsulot: 0.272
word2vec 'yaxshi' va 'yomon' ni deyarli bir joyga qo'ydi -
ikkalasi ham 'sifati ___' kontekstida keladi
=== 2. Kam belgilangan ma'lumot (test 1000, 3 seed, 200 qadam) ===
n tasodifiy muzlatilgan fine-tune
100 0.5710 0.5483 0.5747
300 0.6500 0.6190 0.6533
1000 0.7077 0.6313 0.7120
=== 3. Juftlashgan farq tasodifiy init ga nisbatan ===
n= 100 muzlatilgan -0.0227 SE 0.0042 sezilarli yomon
n= 100 fine-tune +0.0037 SE 0.0034 sezilarli emas
n= 300 muzlatilgan -0.0310 SE 0.0105 sezilarli yomon
n= 300 fine-tune +0.0033 SE 0.0043 sezilarli emas
n=1000 muzlatilgan -0.0763 SE 0.0045 sezilarli yomon
n=1000 fine-tune +0.0043 SE 0.0043 sezilarli emas
⭐ Oldindan o'rgatilgan embedding - faraz; uni o'lchab tekshiringNima ko'rsatdi: 2.5-bo'lim.
Misol 4 — TF-IDF bazaviysi bilan halol taqqoslash
"""Juftlashgan CV: TF-IDF + LogReg va EmbeddingBag, to'liq va kam ma'lumot."""
from collections import Counter
import numpy as np
import torch
import torch.nn as nn
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import StratifiedKFold
from sklearn.pipeline import make_pipeline
IJOBIY = ["yaxshi", "zo'r", "a'lo", "ajoyib", "sifatli", "qulay",
"chiroyli", "mustahkam"]
SALBIY = ["yomon", "sifatsiz", "noqulay", "xunuk", "nosoz", "mo'rt",
"sust", "chatoq"]
NEYTRAL = ["o'rtacha", "oddiy", "normal", "odatiy", "kutilgandek"]
JIHAT = ["sifati", "narxi", "ekrani", "batareyasi", "ovozi", "qadoqlash",
"yetkazib berish", "sotuvchi", "dizayni", "materiali"]
MAHSULOT = ["telefon", "noutbuk", "quloqchin", "soat", "choynak", "kurtka",
"krossovka", "sumka", "planshet", "kolonka"]
BOSH = ["kecha", "o'tgan hafta", "bir oy oldin", "do'kondan", "internetdan",
"sovg'a uchun", "akam uchun", ""]
FEL = ["oldim", "buyurtma qildim", "sotib oldim", "ishlatib ko'rdim"]
YAKUN = {1: ["tavsiya qilaman", "yana olaman", "hammaga maslahat beraman"],
-1: ["tavsiya qilmayman", "boshqa olmayman", "pulimga achinaman"],
0: ["hozircha shu", "vaqt ko'rsatadi", "yana ishlatib ko'ramiz"]}
ULUSH = {1: [0.8, 0.1, 0.1], -1: [0.1, 0.8, 0.1], 0: [0.15, 0.15, 0.7]}
def gap(qutb, rng):
j = rng.choice(JIHAT)
juda = "juda " if rng.random() < 0.3 else ""
r = rng.random()
if qutb == 1:
if r < 0.2:
return f"{j} {rng.choice(SALBIY)} emas"
if r < 0.3:
return f"{j} bilan muammo yo'q"
return f"{j} {juda}{rng.choice(IJOBIY)}"
if qutb == -1:
if r < 0.25:
return f"{j} {rng.choice(IJOBIY)} emas"
if r < 0.35:
return f"{j} bilan muammo bor"
return f"{j} {juda}{rng.choice(SALBIY)}"
return f"{j} {rng.choice(NEYTRAL)}"
def sharh(rng):
kayfiyat = int(rng.integers(-1, 2))
qismlar = [f"{rng.choice(BOSH)} {rng.choice(MAHSULOT)} "
f"{rng.choice(FEL)}".strip()]
ballar = []
for t in range(int(rng.integers(1, 5))):
q = int(rng.choice([1, -1, 0], p=ULUSH[kayfiyat]))
bog = rng.choice(["lekin", "va", "ammo", "ham", ""]) if t else ""
qismlar.append(f"{bog} {gap(q, rng)}".strip())
ballar.append(q)
if rng.random() < 0.5:
q = int(rng.choice([1, -1, 0], p=ULUSH[kayfiyat]))
qismlar.append(rng.choice(YAKUN[q]))
ballar.append(q)
ball = np.mean(ballar) + rng.normal(0, 0.1)
return " ".join(qismlar), 2 if ball > 0.35 else (0 if ball < -0.35 else 1)
def imlo_xato(matn, ulush, rng):
chiqish = []
for s in matn.split():
if len(s) >= 4 and rng.random() < ulush:
i = int(rng.integers(1, len(s) - 1))
tur = int(rng.integers(0, 3))
if tur == 0:
s = s[:i] + s[i + 1:]
elif tur == 1:
s = s[:i] + s[i] + s[i:]
else:
s = s[:i - 1] + s[i] + s[i - 1] + s[i + 1:]
chiqish.append(s)
return " ".join(chiqish)
def korpus(n, seed=0, xato=0.07):
rng = np.random.default_rng(seed)
toza, y = zip(*[sharh(rng) for _ in range(n)])
rng2 = np.random.default_rng(seed + 1000)
return [imlo_xato(t, xato, rng2) for t in toza], np.array(y), list(toza)
def tokenlar(matn):
s = matn.split()
return s + [f"{a}_{b}" for a, b in zip(s, s[1:])]
def paket(ketmalar):
offsets = torch.tensor([0] + [len(k) for k in ketmalar[:-1]]).cumsum(0)
return torch.tensor([t for k in ketmalar for t in k]), offsets
def emb_bahola(Xtr, ytr, Xte, yte, seed, davrlar=15):
"""EmbeddingBag + bigram tokenlar (2-misoldagi eng yaxshi variant)."""
c = Counter(t for m in Xtr for t in tokenlar(m))
sozlar = sorted(t for t, k in c.items() if k >= 2)
lugat = {t: i + 2 for i, t in enumerate(sozlar)}
kod = lambda ms: [[lugat.get(t, 1) for t in tokenlar(m)] for m in ms]
k_tr, k_te = kod(Xtr), kod(Xte)
torch.manual_seed(seed)
bag = nn.EmbeddingBag(len(lugat) + 2, 32, mode="mean")
chiq = nn.Linear(32, 3)
opt = torch.optim.Adam(list(bag.parameters()) + list(chiq.parameters()),
lr=0.01)
g = torch.Generator().manual_seed(seed)
yt = torch.tensor(ytr)
for _ in range(davrlar):
tartib = torch.randperm(len(k_tr), generator=g).tolist()
for i in range(0, len(tartib), 64):
b = tartib[i:i + 64]
opt.zero_grad()
nn.functional.cross_entropy(
chiq(bag(*paket([k_tr[j] for j in b]))), yt[b]).backward()
opt.step()
with torch.no_grad():
return (chiq(bag(*paket(k_te))).argmax(1).numpy() == yte).mean()
def tfidf_bahola(Xtr, ytr, Xte, yte):
m = make_pipeline(TfidfVectorizer(token_pattern=r"[\w']+",
ngram_range=(1, 2)),
LogisticRegression(C=10, max_iter=3000))
return m.fit(Xtr, ytr).score(Xte, yte)
def xulosa(a, b, nom_a, nom_b):
d = b - a
se = d.std(ddof=1) / np.sqrt(len(d))
print(f" o'rtacha: {nom_a} {a.mean():.4f}, {nom_b} {b.mean():.4f}")
print(f" farq ({nom_b} - {nom_a}): {d.mean():+.4f}, SE {se:.4f}")
if abs(d.mean()) <= 2 * se:
q = f"farq 2*SE ichida - soddaroq model ({nom_a}) tanlanadi"
elif d.mean() > 0:
q = f"{nom_b} sezilarli yaxshi"
else:
q = f"{nom_a} sezilarli yaxshi - bazaviy yutdi"
print(f" QAROR: {q}")
def main() -> None:
matnlar, y, _ = korpus(3000)
cv = StratifiedKFold(5, shuffle=True, random_state=0)
toliq = {"TF-IDF": [], "Emb": []}
kam = {"TF-IDF": [], "Emb": []}
for k, (tr, te) in enumerate(cv.split(matnlar, y)):
Xtr = [matnlar[i] for i in tr]
Xte = [matnlar[i] for i in te]
toliq["TF-IDF"].append(tfidf_bahola(Xtr, y[tr], Xte, y[te]))
toliq["Emb"].append(emb_bahola(Xtr, y[tr], Xte, y[te], seed=k))
kichik = np.random.default_rng(k).choice(len(tr), 300, replace=False)
Xk = [Xtr[i] for i in kichik]
kam["TF-IDF"].append(tfidf_bahola(Xk, y[tr][kichik], Xte, y[te]))
kam["Emb"].append(emb_bahola(Xk, y[tr][kichik], Xte, y[te], seed=k))
for sarlavha, nat in [("To'liq o'quv (2400 sharh)", toliq),
("Kam o'quv (300 sharh)", kam)]:
print(f"=== {sarlavha}, 5 fold ===")
print(f" {'fold':>5} {'TF-IDF':>8} {'Emb':>8}")
for i in range(5):
print(f" {i + 1:>5} {nat['TF-IDF'][i]:>8.4f} {nat['Emb'][i]:>8.4f}")
xulosa(np.array(nat["TF-IDF"]), np.array(nat["Emb"]), "TF-IDF", "Emb")
print()
print(" ⭐ Qarorni farq va SE belgiladi - bazaviy yutsa, shuni yozamiz")
if __name__ == "__main__":
main()Natijaning muhim qismi:
=== To'liq o'quv (2400 sharh), 5 fold ===
fold TF-IDF Emb
1 0.7950 0.7900
2 0.8150 0.8050
3 0.8017 0.7950
4 0.8300 0.8350
5 0.7950 0.7883
o'rtacha: TF-IDF 0.8073, Emb 0.8027
farq (Emb - TF-IDF): -0.0047, SE 0.0025
QAROR: farq 2*SE ichida - soddaroq model (TF-IDF) tanlanadi
=== Kam o'quv (300 sharh), 5 fold ===
fold TF-IDF Emb
1 0.7117 0.6900
2 0.7233 0.7067
3 0.6767 0.6450
4 0.7117 0.6883
5 0.6850 0.6533
o'rtacha: TF-IDF 0.7017, Emb 0.6767
farq (Emb - TF-IDF): -0.0250, SE 0.0029
QAROR: TF-IDF sezilarli yaxshi - bazaviy yutdi
⭐ Qarorni farq va SE belgiladi - bazaviy yutsa, shuni yozamizNima ko'rsatdi: 2.6-bo'lim.
5. To'g'ri va noto'g'ri tushunishlar
| Noto'g'ri fikr | To'g'risi |
|---|---|
"padding_idx=0 bo'lsa o'rtacha to'g'ri" |
Pad vektori nol, lekin maxraj noto'g'ri — maska kerak |
"EmbeddingBag boshqacha natija beradi" |
mode="mean" maskali o'rtacha bilan aynan bir xil |
| "O'rtacha pooling inkorni tushunadi" | Tartib yo'qoladi; bigram token yoki ketma-ket model kerak |
| "Embedding modeli TF-IDF dan yaxshi" | O'rtacha pooling — past rangli bag-of-words; o'lchang |
| "Oldindan o'rgatilgan embedding har doim yordam beradi" | Faqat vazifaga kerakli farqni saqlagan bo'lsa |
| "word2vec sentimentni biladi" | "yaxshi" va "yomon" kontekstlari bir xil — ular yaqin |
| "Muzlatish — kam ma'lumotda eng xavfsiz" | Embedding kerakli farqni saqlamagan bo'lsa, muzlatish uni qaytarmaydi |
| "Bazaviy yutsa, tajriba muvaffaqiyatsiz" | Bu ham natija — uni yozing |
6. Keng tarqalgan xatolar va yechimlari
1. Padding ham o'rtachaga kiradi
h = emb(ids).mean(1) # ⚠️
h = (emb(ids) * mask.unsqueeze(-1)).sum(1) / mask.sum(1, keepdim=True) # ✅2. offsets o'rniga uzunliklar
bag(ids1, torch.tensor([3, 5, 2])) # uzunliklar # ⚠️
bag(ids1, torch.tensor([0, 3, 8])) # boshlanishlar # ✅3.
lugat = {s: i for i, s in enumerate(sozlar)}; lugat[s] # KeyError # ⚠️
lugat = {"<pad>": 0, "<unk>": 1} | {...}; lugat.get(s, 1) # ✅4. Lug'atlar mos emas
bag.weight.data.copy_(E) # E boshqa lug'at tartibida qurilgan # ⚠️
# klassifikator E qurilgan lug'at bilan kodlaydi # ✅5. Muzlatish unutilgan
bag.weight.data.copy_(E) # hammasi o'rganadi # ⚠️
bag.weight.data.copy_(E); bag.weight.requires_grad_(False) # ✅6. Muzlatilgan parametr optimizatorda
torch.optim.Adam(model.parameters(), weight_decay=0.01) # E ham siqiladi # ⚠️
torch.optim.Adam([p for p in model.parameters() if p.requires_grad]) # ✅7. Bazaviysiz hisobot
print(f"Embedding modeli: {aniq:.3f}") # ⚠️
# TF-IDF + LogReg bilan bir xil foldlarda, farq + SE # ✅7. Integratsiya — bu bilim qayerda kerak bo'ladi
- 21.3-dars (o'tilgan):
collate_fn, padding va maska - 21.10-dars (o'tilgan):
nn.Embedding, noma'lum kategoriya uchun 0-indeks - 23.5-dars (o'tilgan): TF-IDF + LogReg bazaviysi va juftlashgan CV
- 23.6-dars (o'tilgan): word2vec — bu darsdagi oldindan o'rgatilgan vektorlar
- Keyingi dars: RNN — tartibni ko'radigan birinchi model
- Transformerlar qismida: attention bilan pooling va kontekstli embeddinglar
- Katta til modellari qismida: tayyor matn embeddinglari bilan klassifikatsiya va qidiruv
8. Eng yaxshi amaliyotlar
Lug'atni faqat o'quvda quring: 0 —
<pad>, 1 —<unk>.O'rtachani maska bilan yoki
EmbeddingBagbilan hisoblang.Yangi pooling ni maskali o'rtacha bilan son jihatdan solishtiring.
Inkor va tartib muhim bo'lsa — bigram tokenlar.
Oldindan o'rgatilgan embedding: muzlatilgan va fine-tune ni tasodifiy init bilan solishtiring.
Embedding vazifaga kerakli farqni saqlaganini tekshiring (masalan, ijobiy va salbiy so'zlar kosinusi).
TF-IDF + LogReg bilan bir xil foldlarda juftlashgan taqqoslash.
Kam va to'liq ma'lumotni alohida o'lchang.
9. Amaliy topshiriq
Vazifa 1: Bashorat qiling
1. # <pad> va <unk> indekslari?
2. # e.mean(1) nima uchun noto'g'ri?
3. # maskali o'rtacha formulasi?
4. # padding_idx=0 nima qiladi?
5. # sharhlar [3, 5, 2] so'zli bo'lsa offsets?
6. # EmbeddingBag(mean) va maskali o'rtacha farqi?
7. # "yaxshi emas" va "emas yaxshi" o'rtacha pooling da?
8. # bigram token misoli?
9. # muzlatish qanday qilinadi?
10. # nega word2vec "yaxshi" va "yomon" ni yaqin qo'yadi?
11. # V = 1000, d = 32 da embedding parametrlari?
12. # qaror qoidasi?Javoblar
- 0 va 1
- Padding ham sanaladi — maxraj
L_max, vektorL / L_maxmarta kichrayadi (e * mask.unsqueeze(-1)).sum(1) / mask.sum(1, keepdim=True)- Pad vektorini nol qiladi va uni yangilamaydi
[0, 3, 8]- Yo'q (son xatosi darajasida)
- Bir xil vektor
yaxshi_emasweight.requires_grad_(False)va optimizatorga faqatrequires_gradparametrlar- Kontekstlari bir xil ("sifati ___")
1000 * 32 = 32 000- Eng yaxshisidan sezilarli yomon bo'lmagan eng sodda model
Vazifa 2: Xatolarni tuzating
1. h = emb(ids).mean(dim=1)
2. offsets = torch.tensor([len(k) for k in ketmalar])
3. lugat = {s: i for i, s in enumerate(sorted(set(sozlar)))}
ids = [lugat[s] for s in test_matn.split()]
4. model.bag.weight.data.copy_(E)
opt = torch.optim.Adam(model.parameters()) # muzlatish kerak edi
5. print("Embedding modeli yaxshi:", aniq_emb)Javoblar
1. h = (emb(ids) * mask.unsqueeze(-1)).sum(1) / mask.sum(1, keepdim=True)
2. offsets = torch.tensor([0] + [len(k) for k in ketmalar[:-1]]).cumsum(0)
3. lugat = {"<pad>": 0, "<unk>": 1} | {s: i + 2 for i, s in
enumerate(sorted(set(sozlar)))}
ids = [lugat.get(s, 1) for s in test_matn.split()]
4. model.bag.weight.data.copy_(E)
model.bag.weight.requires_grad_(False)
opt = torch.optim.Adam([p for p in model.parameters() if p.requires_grad])
5. d = emb_s - tfidf_s # bir xil foldlar
se = d.std(ddof=1) / np.sqrt(len(d))
print(d.mean(), se, abs(d.mean()) > 2 * se)Vazifa 3: Texnik qism
Modellang:
- Lug'at va
<unk> collate_fn- Maskali o'rtacha
EmbeddingBagtengligi
Vazifa 4: Tartib
Modellang:
- Unigram model
- Bigram tokenlar
- TF-IDF bilan parallel
- Inkor sinovi
Vazifa 5: Oldindan o'rgatish
Modellang:
- SGNS
- Kosinus tekshiruvi
- Muzlatish va fine-tune
- Kam ma'lumot
Vazifa 6: Halol taqqoslash
Modellang:
- Juftlashgan CV
- To'liq ma'lumot
- Kam ma'lumot
- Qaror
Vazifa 7: O'ylash
Jamoangiz bir million belgilanmagan va 500 ta belgilangan sharhga ega. Kimdir taklif qildi: "Bir millionda word2vec o'rgatamiz, keyin 500 sharhda muzlatilgan embedding + Linear o'rgatamiz — kam ma'lumotda eng yaxshi yo'l shu." Nima deysiz?
Javob
Qisqa javob: g'oya to'g'ri yo'nalishda — belgilanmagan ma'lumotdan foydalanish kerak. Lekin "muzlatilgan word2vec" aynan sentiment uchun kuchsiz bo'lishi mumkin, va buni o'lchamasdan qaror qabul qilib bo'lmaydi.
1. word2vec nimani o'rganadi? Kichik oynali skip-gram "almashtirsa bo'ladigan" so'zlarni yaqin qo'yadi. Sharhlarda "sifati yaxshi" va "sifati yomon" — bir xil kontekst. 3-misolda ijobiy va salbiy so'zlar orasidagi kosinus ijobiy so'zlar orasidagisiga juda yaqin chiqdi. Muzlatilgan embeddingda bu farq deyarli yo'q — Linear qatlam uni tiklay olmaydi.
2. Tekshirish oson.
cos(ijobiy, ijobiy), cos(salbiy, salbiy), cos(ijobiy, salbiy)
# oxirgisi birinchi ikkitasiga yaqin bo'lsa - sentiment uchun xavfli3. Bazaviy. 500 sharhda TF-IDF + bigram + LogReg — birinchi raqib. 4-misolda 300 sharhli kam ma'lumotda TF-IDF bazaviysi embedding modelidan sezilarli yaxshi chiqdi.
4. Nimani sinash kerak (bir xil foldlarda):
# a) TF-IDF + bigram + LogReg
# b) EmbeddingBag, tasodifiy init
# c) word2vec, muzlatilgan
# d) word2vec, fine-tune (kichik lr)
# e) kattaroq oynali word2vec (sharh darajasidagi kontekst)5. Boshqa yo'llar. Belgilanmagan ma'lumotdan foydalanishning kuchliroq usullari bor: sharhdagi yulduzcha baho kabi "zaif yorliqlar", yoki kontekstga bog'liq katta modellar (Transformerlar qismida). Ular "yaxshi emas" kabi tuzilmalarni ham ko'radi.
Javob: "Belgilanmagan ma'lumotdan foydalanish — to'g'ri g'oya, lekin muzlatilgan word2vec sentiment farqini saqlamasligi mumkin. Avval ijobiy va salbiy so'zlar kosinusini tekshiramiz, keyin TF-IDF bazaviysi, tasodifiy init, muzlatilgan va fine-tune variantlarini bir xil foldlarda juftlab solishtiramiz. Qaror — o'lchovdan keyin."
Nimani mustahkamlaydi: 2.5, 2.6-bo'limlar.
Xulosa
Bu darsda embedding asosidagi matn klassifikatorini qurdik va uni TF-IDF bazaviysi bilan halol solishtirdik.
Eng muhim uch fikr:
O'rtacha pooling — maska bilan yoki
EmbeddingBagbilan. 1-misolda maskasize.mean(1)har sharh vektorini aynanL / L_maxmarta kichraytirdi: 6 so'zli sharh 20 so'zli sharh bilan bir batchda bo'lganda uning vektori0.300barobarga tushdi — garchipadding_idx=0pad vektorini nol qilgan va unga gradient bermagan bo'lsa ham.nn.EmbeddingBag(mode="mean")offsetsbilan maskali o'rtachaga son xatosi darajasida (8.94e-08) teng chiqdi va 80 o'rniga 56 ta indeks bilan ishladi. 2-misolda unigram EmbeddingBag0.7189berdi — TF-IDF unigram (0.7250) ga yaqin, chunki o'rtacha pooling ham bag-of-words. Bigram tokenlar aniqlikni+0.0856ga oshirdi (SE0.0024) va "sifati yaxshi emas" ni to'g'ri salbiy deb topdi; unigram modeli uni ijobiy deb bildi va "emas sifati yaxshi" bilan farqlay olmadi.Oldindan o'rgatilgan embedding — faraz, kafolat emas. 3-misolda 8000 belgisiz sharhda o'rgatilgan word2vec ijobiy va salbiy so'zlarni deyarli bir joyga qo'ydi: kosinus ijobiy-ijobiy
0.982, ijobiy-salbiy0.955— sentiment uchun eng kerakli farq yo'qolgan. Natija shunga mos keldi: bir xil 200 qadamli o'rgatishda muzlatilgan embedding hamma hajmda tasodifiy initdan sezilarli yomon chiqdi (n = 100 da-0.0227, n = 1000 da-0.0763), fine-tune esa hech bir hajmda sezilarli farq bermadi (+0.0033…+0.0043, har biri2 × SEichida). Oldindan o'rgatish "kam ma'lumotda albatta yordam beradi" degan faraz bu vazifada tasdiqlanmadi — sababini esa bitta kosinus tekshiruvi oldindan ko'rsatib bergan edi.Bazaviy bilan halol taqqoslash. 4-misolda bir xil 5 foldda to'liq ma'lumotda (2400 sharh) bigramli EmbeddingBag TF-IDF + bigram + LogReg dan
-0.0047farq qildi (SE0.0025) —2 × SEichida, shuning uchun qaror qoidasi soddaroq TF-IDF modelini tanladi. Kam ma'lumotda (300 sharh) esa TF-IDF sezilarli yaxshi chiqdi:0.7017va0.6767, farq-0.0250, SE0.0029. Bazaviy yutdi — va biz shuni yozdik. Bu embeddinglar foydasiz degani emas: ular tartibni ko'radigan modellarning (keyingi dars) va Transformerlarning kirish qatlami bo'ladi. Lekin o'rtacha pooling bilan ular TF-IDF dan ko'p narsa qo'shmaydi.
Keyingi darsda RNN: yashirin holat, nn.RNN, vaqt bo'ylab orqaga tarqalish, gradient so'nishi va clipping — so'zlar tartibini ko'radigan birinchi model.
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!