Mundarija (21)
- 1. Kirish va motivatsiya
- 2. Nazariya — chuqur tushuntirish
- 2.1. Vazifa va eng oddiy bazaviy
- 2.2. Pipeline: vektorizator faqat o'quvda
- 2.3. Uch klassik model
- 2.4. So'z va belgi n-gramlari
- 2.5. CV bilan juftlashgan taqqoslash
- 2.6. Model nimani o'rgandi va xato tahlili
- 2.7. Tuzoqlar
- 3. Tez ma'lumotnoma
- 4. Batafsil misollar
- Misol 1 — Korpus, bazaviy va Pipeline
- Misol 2 — Uch model va n-gramlar: juftlashgan CV
- Misol 3 — Imlo xatolariga chidamlilik
- Misol 4 — Model nimani o'rgandi va xato tahlili
- 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.5-dars: Matn klassifikatsiyasi — klassik bazaviylar
23-QISM — NLP VA KETMA-KETLIKLAR · 5-dars
1. Kirish va motivatsiya
Oldingi to'rt darsda matnni songa aylantirishni o'rgandik: 23.1-darsda Unicode va o'zbekcha apostrof variantlarini normallashtirdik, 23.2-darsda tokenizatsiya va lug'at qurdik, 23.3-darsda BPE bilan so'z qismlariga ajratdik, 23.4-darsda esa matnni bag-of-words va TF-IDF vektoriga aylantirdik. Endi bu vektorlardan birinchi foydali natijani olamiz: matnni sinflarga ajratamiz.
Bu darsda ishlatadigan ma'lumot — o'zbekcha mahsulot sharhlari: "kecha telefon oldim ekrani juda zo'r lekin batareyasi sust". Har sharh uch sinfdan biriga tegishli: salbiy, neytral, ijobiy. Sharhlarni kod ichida yaratamiz (shablon + lug'at + tasodif), lekin ularni atayin real ko'rinishli qilamiz: turli uzunlik, sinonimlar ("zo'r", "a'lo", "ajoyib"), inkor ("sifatli emas", "muammo yo'q"), aralash fikrlar ("... lekin ...") va taxminan har 15 so'zdan birida imlo xatosi.
Nega "klassik" modellar? Chunki keyingi darslarda embedding, RNN va boshqa neyron modellarni ko'ramiz — va ularning har birini nimadir bilan solishtirish kerak. TF-IDF + logistik regressiya bir necha soniyada o'rganadi, deyarli sozlashsiz ishlaydi va ko'p amaliy vazifalarda hali ham kuchli raqib. Uni o'lchamay turib neyron tarmoq qurish — 20.12-darsda tabular ma'lumotda ko'rgan xatoning aynan o'zi.
Real vaziyat. Onlayn do'kon jamoasi sharhlarni avtomatik saralash uchun katta neyron model o'rgatdi va 84% aniqlikdan xursand bo'ldi. Keyinroq kimdir oddiy TF-IDF + logistik regressiyani sinab ko'rdi: 83%, o'rgatish vaqti 1 soniya, har bashorat mikrosekundlarda, va model qaysi so'zlarga qarab qaror qilishini ko'rsata oladi. Bir foiz punkt farq esa bir necha tasodifiy bo'linishda sezilarli chiqmadi. Bu darsda aynan shunday o'lchashni o'rganamiz.
Bu darsda matn klassifikatsiyasi uchun kuchli va halol bazaviyni quramiz.
Bu darsda:
- Vazifa va eng oddiy bazaviy
-
Pipeline: vektorizator faqat o'quvda - Uch klassik model: logistik regressiya, Naive Bayes, LinearSVC
- So'z va belgi n-gramlari
- CV bilan juftlashgan taqqoslash
- Model nimani o'rgandi va xato tahlili
- Tuzoqlar
ℹ Misollar real numpy/sklearn bilan (Python 3.14).
2. Nazariya — chuqur tushuntirish
2.1. Vazifa va eng oddiy bazaviy
MATN KLASSIFIKATSIYASI:
kirish: matn (turli uzunlikdagi so'zlar ketma-ketligi)
chiqish: sinf (salbiy / neytral / ijobiy, spam / spam emas, mavzu ...)
ZANJIR:
xom matn -> normallashtirish 23.1-bob -> tokenlar 23.2-bob
-> vektor (TF-IDF, 23.4) -> klassifikator -> sinf
ENG ODDIY BAZAVIY (DummyClassifier):
hamma sharhga eng ko'p uchraydigan sinfni aytadi
3 ta teng sinfda aniqlik ~ 0.33
har qanday model shundan ANCHA yaxshi bo'lishi kerak
BIZNING KORPUS:
3000 sharh, 3 sinf (taxminan teng)
yorliq = gaplar "ball"larining o'rtachasi (+1, 0, -1) + ozgina shovqin
o'rtacha > 0.35 -> ijobiy, < -0.35 -> salbiy, aks holda neytral
"... sifatli ham ... sifatli boshqa olmayman" -> (1 + 1 - 1) / 3 = 0.33
-> NEYTRAL (ikki maqtov va bitta shikoyat)
inkor: "sifatli emas" -> salbiy, "sust emas" -> ijobiy
imlo xatolari: so'zlarning ~7% ida (tushib qolgan, ikkilangan,
o'rin almashgan harf) Birinchi qadam — DummyClassifier — u "model hech narsa o'rganmaganda" qanday natija bo'lishini ko'rsatadi.
2.2. Pipeline: vektorizator faqat o'quvda
TF-IDF HAM O'RGANADI:
fit: lug'at (qaysi so'zlar/n-gramlar bor) + IDF (har so'z nechta
hujjatda uchraydi)
bu statistikalar - modelning bir qismi
NOTO'G'RI (leakage, 19-qism):
vek = TfidfVectorizer().fit(barcha_matnlar) # test ham ichida
X = vek.transform(barcha_matnlar)
train_test_split(X, y) ...
-> lug'atda FAQAT testda uchragan so'zlar bor
-> IDF test hujjatlarini ham sanagan
TO'G'RI:
model = Pipeline([("vek", TfidfVectorizer()),
("clf", LogisticRegression())])
model.fit(X_oquv, y_oquv) # vektorizator faqat o'quvda
cross_val_score(model, X, y) # har foldda QAYTA fit
AMALDA:
TF-IDF dagi leakage odatda kichik natija farqini beradi
lekin u "kichik" ekanini bilmaysiz - tekshirmaguncha
Pipeline bilan bu savol umuman tug'ilmaydi Vektorizator — modelning bir qismi; u Pipeline ichida faqat o'quv ma'lumotida fit qilinadi.
2.3. Uch klassik model
LOGISTIK REGRESSIYA (14-qism):
har sinf uchun: ball_k = w_k . x + b_k, softmax -> ehtimollar
C - regularizatsiya teskarisi (katta C = kuchsiz regularizatsiya)
+ ehtimol beradi, koeffitsiyentlarni o'qish mumkin 2.6-bob
MULTINOMIAL NAIVE BAYES:
P(sinf | matn) ~ P(sinf) * ko'paytma P(so'z | sinf)
"so'zlar sinf ichida bir-biriga bog'liq emas" degan sodda faraz
alpha - silliqlash (ko'rilmagan so'z uchun nol ehtimol bo'lmasin)
+ juda tez, juda kam ma'lumotda ham ishlaydi
- "sifatli" va "emas" ni alohida sanaydi - inkorni tushunmaydi
LinearSVC:
sinflar orasidagi chegarani maksimal "oraliq" bilan qidiradi
C - regularizatsiya teskarisi
+ siyrak, ko'p o'lchamli matn vektorlarida kuchli
- ehtimol bermaydi (faqat decision_function)
UMUMIY XOSSA:
uchalasi ham CHIZIQLI: har belgi (so'z/n-gram) o'z og'irligini oladi
matn vektori 10 000+ o'lchamli bo'lsa ham o'rgatish soniyalar oladiMatnda chiziqli modellar kuchli — o'lcham katta, ma'lumot siyrak, va har so'z o'z "ovozi"ni beradi.
2.4. So'z va belgi n-gramlari
SO'Z UNIGRAMLARI (ngram_range=(1, 1)):
"sifati sifatli emas" -> {sifati, sifatli, emas}
"sifatli" ijobiy og'irlik oladi, "emas" - deyarli neytral
-> inkor YO'QOLADI
SO'Z BIGRAMLARI (ngram_range=(1, 2)):
+ {sifati sifatli, sifatli emas}
"sifatli emas" - alohida belgi, o'z (salbiy) og'irligi bilan
BELGI N-GRAMLARI:
analyzer="char_wb", (2, 4): faqat SO'Z ICHIDA
"sifatli" -> " s", "si", "if", ... "atli ", ...
so'zlar chegarasidan o'tmaydi -> "emas" bilan bog'lanish yo'q
analyzer="char", (2, 4): bo'shliqdan ham o'tadi
"li em", "i ema" -> inkor ham ko'rinadi
IMLO XATOSI:
"sifatli" -> "sifatil" (harflar o'rin almashgan)
so'z darajasida: butunlay yangi so'z, lug'atda yo'q -> e'tiborsiz
belgi darajasida: "sif", "ifa", "fat" saqlanadi -> signal qisman qoladi
NARX:
har matnda nol bo'lmagan belgilar ~10 barobar ko'p
(bizning korpusda o'rtacha: so'z+bigram ~24, belgi (2, 4) ~230)
-> vektorlash, o'rgatish va bashorat sekinroqBigramlar inkorni, belgi n-gramlari imlo xatosini "ko'radi" — qaysi biri muhimligini ma'lumot va o'lchov hal qiladi.
2.5. CV bilan juftlashgan taqqoslash
BITTA BO'LINISH YETMAYDI:
1000 ta test sharhida aniqlikning SE si ~ sqrt(0.8*0.2/1000) ~ 0.013
0.01 farq - shovqin ichida bo'lishi mumkin
JUFTLASHGAN CV (18-qism):
cv = StratifiedKFold(5, shuffle=True, random_state=0)
HAMMA modelga BIR XIL foldlar
har foldda: d_k = aniq_A_k - aniq_B_k
o'rtacha d, SE = std(d, ddof=1) / sqrt(5)
|d| > 2 * SE -> sezilarli (taxminiy qoida)
QAROR QOIDASI:
1. eng yaxshi o'rtacha natijali modelni toping
2. har modelni u bilan juftlab solishtiring
3. "sezilarli yomon EMAS" modellar ichidan ENG SODDASINI tanlang
SODDALIK TARTIBI (bizning darsda):
NB so'z < LR so'z < NB so'z+bigram < LR so'z+bigram < SVC so'z+bigram
< LR belgi (so'z ichida) < LR belgi
(belgi modellari: lug'at katta, sekin, tushuntirish qiyin)Qarorni farq va SE hal qiladi — va teng natijalar ichida eng sodda model yutadi.
2.6. Model nimani o'rgandi va xato tahlili
KOEFFITSIYENTLAR (logistik regressiya):
clf.coef_ shakli (n_sinf, n_belgi)
har sinf uchun eng katta musbat og'irlikli belgilar -
"bu sinfga eng kuchli ovoz beradigan so'zlar"
vek.get_feature_names_out() - indeks -> so'z
NIMANI TEKSHIRAMIZ:
mantiqiy so'zlarmi? ("zo'r", "sust emas" -> ijobiy)
"shubhali" so'zlarmi? (mahsulot nomi, sana, do'kon nomi ->
ma'lumotda noxolis bog'liqlik yoki leakage belgisi)
XATO TAHLILI:
chalkashlik matritsasi: qaysi sinflar adashadi?
odatda neytral <-> ijobiy/salbiy, salbiy <-> ijobiy kam
guruhlar bo'yicha aniqlik: inkorli / inkorsiz, qisqa / uzun
eng ISHONCHLI xatolar: ko'pincha yorliq xatosi yoki model
tushunmaydigan tuzilish ("... emas", "... lekin ...")
KEYINGI QADAM:
xato guruhi -> belgi (bigram), ma'lumot yoki boshqa modelKoeffitsiyentlar va xatolar — modelning "tushuntirish xati"; ular raqamlar yashirgan muammolarni ochadi.
2.7. Tuzoqlar
Asosiy tuzoqlar: vektorizatorni butun ma'lumotda fit qilish (leakage); DummyClassifier bazaviysiz "84% — yaxshi" deyish; sukut token_pattern o'zbekcha apostrofli so'zlarni bo'lib yuborishi ("zo'r" → "zo"); unigramlar bilan inkorni yo'qotish; bitta bo'linishdagi kichik farqqa ishonish; turli modellarni turli foldlarda baholash; MultinomialNB ga manfiy qiymatli belgi berish (masalan, StandardScaler dan keyin); LinearSVC dan ehtimol kutish; koeffitsiyentlarni ko'rmay modelni ishlab chiqarishga chiqarish.
3. Tez ma'lumotnoma
from sklearn.dummy import DummyClassifier
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import StratifiedKFold, cross_val_score
from sklearn.naive_bayes import MultinomialNB
from sklearn.pipeline import make_pipeline
from sklearn.svm import LinearSVC
TP = r"[\w']+" # apostrofli so'zlar butun qoladi
soz = make_pipeline(
TfidfVectorizer(token_pattern=TP, ngram_range=(1, 2)),
LogisticRegression(C=10, max_iter=2000))
belgi = make_pipeline(
TfidfVectorizer(analyzer="char", ngram_range=(2, 4)),
LogisticRegression(C=10, max_iter=2000))
nb = make_pipeline(TfidfVectorizer(token_pattern=TP, ngram_range=(1, 2)),
MultinomialNB(alpha=0.1))
svc = make_pipeline(TfidfVectorizer(token_pattern=TP, ngram_range=(1, 2)),
LinearSVC(C=0.5))
cv = StratifiedKFold(5, shuffle=True, random_state=0)
a = cross_val_score(soz, matnlar, y, cv=cv) # bir xil foldlar
b = cross_val_score(belgi, matnlar, y, cv=cv)
d = b - a
se = d.std(ddof=1) / len(d) ** 0.5
vek, clf = soz.fit(X_oquv, y_oquv).named_steps.values()
nomlar = vek.get_feature_names_out()
top = nomlar[clf.coef_[2].argsort()[::-1][:10]] # ijobiy sinf so'zlariMatn klassifikatsiyasi xulosasi
DummyClassifier -> TF-IDF + LogReg (Pipeline) -> n-gramlar
token_pattern apostrofni saqlasin
bigram - inkor, belgi n-gram - imlo xatosi
juftlashgan CV: bir xil foldlar, farq + SE
sezilarli yomon bo'lmagan ENG SODDA model
koeffitsiyentlar va eng ishonchli xatolarni ko'ring4. Batafsil misollar
Misollar real numpy/sklearn bilan (Python 3.14).
Misol 1 — Korpus, bazaviy va Pipeline
"""Sintetik o'zbekcha sharhlar: bazaviy, Pipeline va leakage."""
import numpy as np
from sklearn.dummy import DummyClassifier
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score, f1_score
from sklearn.model_selection import train_test_split
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]}
SINFLAR = ["salbiy", "neytral", "ijobiy"]
TP = r"[\w']+"
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 -> gaplar -> yorliq (gaplar ballining o'rtachasi)."""
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 main() -> None:
matnlar, y, toza = korpus(3000)
print("=== 1. Korpus ===")
soni = np.bincount(y)
for k, nom in enumerate(SINFLAR):
print(f" {nom:<8} {soni[k]:>5} ({soni[k] / len(y):.1%})")
uzun = np.array([len(m.split()) for m in matnlar])
print(f" so'zlar soni: min {uzun.min()}, mediana "
f"{int(np.median(uzun))}, max {uzun.max()}")
toza_soz = [s for t in toza for s in t.split()]
xato_soz = [s for t in matnlar for s in t.split()]
ozgargan = np.mean([a != b for a, b in zip(toza_soz, xato_soz)])
print(f" imlo xatosi bor so'zlar: {ozgargan:.1%}")
for k in (2, 1, 0):
i = int(np.where(y == k)[0][0])
print(f" [{SINFLAR[k]}] {matnlar[i]}")
Xtr, Xte, ytr, yte = train_test_split(matnlar, y, test_size=0.2,
random_state=0, stratify=y)
print(f"\n o'quv {len(Xtr)}, test {len(Xte)}")
print("\n=== 2. Eng oddiy bazaviy ===")
dummy = DummyClassifier(strategy="most_frequent").fit(Xtr, ytr)
print(f" DummyClassifier aniqligi: "
f"{accuracy_score(yte, dummy.predict(Xte)):.4f}")
print("\n=== 3. Pipeline: TF-IDF (so'z + bigram) + LogReg ===")
model = make_pipeline(
TfidfVectorizer(token_pattern=TP, ngram_range=(1, 2)),
LogisticRegression(C=10, max_iter=2000))
model.fit(Xtr, ytr)
p = model.predict(Xte)
print(f" test aniqligi: {accuracy_score(yte, p):.4f}")
f1 = f1_score(yte, p, average=None)
for k, nom in enumerate(SINFLAR):
print(f" F1 {nom:<8} {f1[k]:.4f}")
print(f" lug'at hajmi (faqat o'quv): "
f"{len(model[0].vocabulary_)} belgi")
print("\n=== 4. Sukut token_pattern va apostrof ===")
sukut = TfidfVectorizer().fit(["sotuvchi zo'r va a'lo"])
print(f" sukut: {sorted(sukut.vocabulary_)}")
bizniki = TfidfVectorizer(token_pattern=TP).fit(["sotuvchi zo'r va a'lo"])
print(f" [\\w']+: {sorted(bizniki.vocabulary_)}")
print("\n=== 5. Leakage: vektorizator butun ma'lumotda ===")
vek_xato = TfidfVectorizer(token_pattern=TP, ngram_range=(1, 2))
vek_xato.fit(matnlar)
faqat_test = set(vek_xato.vocabulary_) - set(model[0].vocabulary_)
print(f" butun ma'lumot lug'ati: {len(vek_xato.vocabulary_)} belgi")
print(f" shundan faqat testda uchraganlari: {len(faqat_test)}")
misollar = sorted(f for f in faqat_test if " " not in f)[:5]
print(f" masalan: {misollar}")
clf = LogisticRegression(C=10, max_iter=2000)
clf.fit(vek_xato.transform(Xtr), ytr)
aniq_xato = accuracy_score(yte, clf.predict(vek_xato.transform(Xte)))
aniq_togri = accuracy_score(yte, p)
print(f" leakage bilan test aniqligi: {aniq_xato:.4f} "
f"(Pipeline: {aniq_togri:.4f})")
if abs(aniq_xato - aniq_togri) < 0.01:
print(" bu yerda farq 0.01 dan kichik - lekin buni faqat "
"o'lchab bildik")
else:
print(" farq sezilarli - leakage bahoni buzdi")
print(" ⭐ Vektorizator Pipeline ichida - leakage savoli tug'ilmaydi")
if __name__ == "__main__":
main()Natijaning muhim qismi:
=== 1. Korpus ===
salbiy 941 (31.4%)
neytral 1040 (34.7%)
ijobiy 1019 (34.0%)
so'zlar soni: min 4, mediana 13, max 25
imlo xatosi bor so'zlar: 6.6%
[ijobiy] sumka sotib oldim qadoqlash xunuk emas
[neytral] sovg'a uchun kurtka buyurtma qildim sifati sifatli ham yetkazib berish sifatli boshqa olmayman
[salbiy] akam uchun noutbuk oldim sifati a'lo emas lekin yetkazib berish sust va dizayni kutilganek ham batareyasi juda sust boshqa olmayman
o'quv 2400, test 600
=== 2. Eng oddiy bazaviy ===
DummyClassifier aniqligi: 0.3467
=== 3. Pipeline: TF-IDF (so'z + bigram) + LogReg ===
test aniqligi: 0.8150
F1 salbiy 0.8248
F1 neytral 0.7536
F1 ijobiy 0.8675
lug'at hajmi (faqat o'quv): 4612 belgi
=== 4. Sukut token_pattern va apostrof ===
sukut: ['lo', 'sotuvchi', 'va', 'zo']
[\w']+: ["a'lo", 'sotuvchi', 'va', "zo'r"]
=== 5. Leakage: vektorizator butun ma'lumotda ===
butun ma'lumot lug'ati: 5251 belgi
shundan faqat testda uchraganlari: 639
masalan: ['aakm', 'achinamaan', 'ahinaman', 'amteriali', 'batareasi']
leakage bilan test aniqligi: 0.8167 (Pipeline: 0.8150)
bu yerda farq 0.01 dan kichik - lekin buni faqat o'lchab bildik
⭐ Vektorizator Pipeline ichida - leakage savoli tug'ilmaydiNima ko'rsatdi: 2.1, 2.2-bo'limlar.
Misol 2 — Uch model va n-gramlar: juftlashgan CV
"""8 ta klassik model, bir xil 5 fold, juftlashgan farq va qaror."""
import warnings
import numpy as np
from sklearn.exceptions import ConvergenceWarning
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import StratifiedKFold, cross_validate
from sklearn.naive_bayes import MultinomialNB
from sklearn.pipeline import make_pipeline
from sklearn.svm import LinearSVC
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]}
TP = r"[\w']+"
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 soz(ng):
return TfidfVectorizer(token_pattern=TP, ngram_range=ng)
def belgi(analyzer):
return TfidfVectorizer(analyzer=analyzer, ngram_range=(2, 4))
def lr():
return LogisticRegression(C=10, max_iter=3000)
def main() -> None:
matnlar, y, _ = korpus(3000)
# modellar SODDALIK tartibida (2.5-bo'lim)
modellar = {
"NB so'z": make_pipeline(soz((1, 1)), MultinomialNB(alpha=0.1)),
"LR so'z": make_pipeline(soz((1, 1)), lr()),
"NB so'z+bi": make_pipeline(soz((1, 2)), MultinomialNB(alpha=0.1)),
"LR so'z+bi": make_pipeline(soz((1, 2)), lr()),
"SVC so'z+bi": make_pipeline(soz((1, 2)),
LinearSVC(C=0.5, max_iter=5000)),
"LR char_wb": make_pipeline(belgi("char_wb"), lr()),
"LR char": make_pipeline(belgi("char"), lr()),
}
cv = StratifiedKFold(5, shuffle=True, random_state=0)
natija, lugat = {}, {}
with warnings.catch_warnings():
# LinearSVC ba'zi foldlarda to'liq yaqinlashmasligi mumkin;
# natijaga ta'siri yo'q, ogohlantirish chiqishni ifloslamasin
warnings.simplefilter("ignore", ConvergenceWarning)
for nom, m in modellar.items():
r = cross_validate(m, matnlar, y, cv=cv, return_estimator=True)
natija[nom] = r["test_score"]
lugat[nom] = len(r["estimator"][0][0].vocabulary_)
print("=== 1. 5-fold CV aniqligi (bir xil foldlar) ===")
print(f" {'model':<13} {'lug_at':>7} {'o_rtacha':>9} {'min':>7} "
f"{'max':>7}")
for nom, s in natija.items():
print(f" {nom:<13} {lugat[nom]:>7} {s.mean():>9.4f} "
f"{s.min():>7.4f} {s.max():>7.4f}")
print(" (lug'at - 1-fold o'quv qismidagi belgilar soni)")
print("\n=== 2. Eng yaxshisi bilan juftlashgan farq ===")
eng = max(natija, key=lambda k: natija[k].mean())
print(f" eng yaxshi: {eng}")
print(f" {'model':<13} {'farq':>8} {'SE':>7} {'xulosa':>22}")
tanlov = None
for nom, s in natija.items():
if nom == eng:
print(f" {nom:<13} {'-':>8} {'-':>7} {'eng yaxshi':>22}")
if tanlov is None:
tanlov = nom
continue
d = s - natija[eng]
se = d.std(ddof=1) / np.sqrt(len(d))
yomon = d.mean() < -2 * se
xulosa = "sezilarli yomon" if yomon else "farq sezilarli emas"
print(f" {nom:<13} {d.mean():>+8.4f} {se:>7.4f} {xulosa:>22}")
if not yomon and tanlov is None:
tanlov = nom
print(f"\n QAROR (sezilarli yomon bo'lmagan eng sodda): {tanlov}")
print("\n=== 3. Bigram va belgi n-gramlari nima qo'shdi ===")
for a, b in [("LR so'z", "LR so'z+bi"), ("NB so'z", "NB so'z+bi"),
("LR char_wb", "LR char")]:
d = natija[b] - natija[a]
se = d.std(ddof=1) / np.sqrt(len(d))
belgi_ = "sezilarli" if abs(d.mean()) > 2 * se else "sezilarli emas"
print(f" {b:<11} - {a:<11} {d.mean():>+8.4f} SE {se:.4f} {belgi_}")
print(" ⭐ Qaror jadvaldan kod bilan chiqdi - oldindan yozilmagan")
if __name__ == "__main__":
main()Natijaning muhim qismi:
=== 1. 5-fold CV aniqligi (bir xil foldlar) ===
model lug_at o_rtacha min max
NB so'z 861 0.6627 0.6533 0.6767
LR so'z 861 0.7197 0.6967 0.7383
NB so'z+bi 4619 0.7667 0.7533 0.7783
LR so'z+bi 4619 0.8073 0.7950 0.8300
SVC so'z+bi 4619 0.8123 0.8000 0.8283
LR char_wb 4155 0.7327 0.7100 0.7450
LR char 5638 0.8287 0.8133 0.8350
(lug'at - 1-fold o'quv qismidagi belgilar soni)
=== 2. Eng yaxshisi bilan juftlashgan farq ===
eng yaxshi: LR char
model farq SE xulosa
NB so'z -0.1660 0.0056 sezilarli yomon
LR so'z -0.1090 0.0046 sezilarli yomon
NB so'z+bi -0.0620 0.0053 sezilarli yomon
LR so'z+bi -0.0213 0.0056 sezilarli yomon
SVC so'z+bi -0.0163 0.0041 sezilarli yomon
LR char_wb -0.0960 0.0034 sezilarli yomon
LR char - - eng yaxshi
QAROR (sezilarli yomon bo'lmagan eng sodda): LR char
=== 3. Bigram va belgi n-gramlari nima qo'shdi ===
LR so'z+bi - LR so'z +0.0877 SE 0.0064 sezilarli
NB so'z+bi - NB so'z +0.1040 SE 0.0034 sezilarli
LR char - LR char_wb +0.0960 SE 0.0034 sezilarli
⭐ Qaror jadvaldan kod bilan chiqdi - oldindan yozilmaganNima ko'rsatdi: 2.3, 2.4, 2.5-bo'limlar.
Misol 3 — Imlo xatolariga chidamlilik
"""Test sharhlariga ko'proq imlo xatosi qo'shsak: so'z va belgi modellari."""
import numpy as np
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]}
TP = r"[\w']+"
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 modellar():
lr = lambda: LogisticRegression(C=10, max_iter=3000)
soz = lambda: TfidfVectorizer(token_pattern=TP, ngram_range=(1, 2))
belgi = lambda: TfidfVectorizer(analyzer="char", ngram_range=(2, 4))
return {"so'z+bigram": make_pipeline(soz(), lr()),
"belgi (char)": make_pipeline(belgi(), lr())}
def main() -> None:
matnlar, y, toza = korpus(3000)
darajalar = [0.0, 0.15, 0.3]
cv = StratifiedKFold(5, shuffle=True, random_state=0)
nomlar = list(modellar())
natija = {n: {d: [] for d in darajalar} for n in nomlar}
oov = {d: [] for d in darajalar}
for k, (tr, te) in enumerate(cv.split(matnlar, y)):
oquv = [matnlar[i] for i in tr]
lugat = {s for m in oquv for s in m.split()}
testlar = {}
for d in darajalar:
rng = np.random.default_rng(100 + k)
testlar[d] = [imlo_xato(toza[i], d, rng) for i in te]
sozlar = [s for m in testlar[d] for s in m.split()]
oov[d].append(np.mean([s not in lugat for s in sozlar]))
for nom, m in modellar().items():
m.fit(oquv, y[tr])
for d in darajalar:
natija[nom][d].append(np.mean(m.predict(testlar[d]) == y[te]))
print("=== 1. Test so'zlarining lug'atdan tashqari (OOV) ulushi ===")
for d in darajalar:
print(f" xato darajasi {d:.2f}: OOV {np.mean(oov[d]):.1%}")
print("\n=== 2. Aniqlik (5 fold o'rtachasi) ===")
print(f" {'xato':>5} " + " ".join(f"{n:>13}" for n in nomlar))
for d in darajalar:
print(f" {d:>5.2f} " + " ".join(f"{np.mean(natija[n][d]):>13.4f}"
for n in nomlar))
print("\n=== 3. Aniqlik yo'qotishi (0.0 dan 0.3 gacha) ===")
for n in nomlar:
a0 = np.mean(natija[n][0.0])
a4 = np.mean(natija[n][0.3])
print(f" {n:<13} {a0:.4f} -> {a4:.4f} "
f"(yo'qotish {a0 - a4:.4f})")
print("\n=== 4. Juftlashgan farq: belgi - so'z+bigram ===")
print(f" {'xato':>5} {'farq':>8} {'SE':>7} {'xulosa':>22}")
for d in darajalar:
f = (np.array(natija["belgi (char)"][d])
- np.array(natija["so'z+bigram"][d]))
se = f.std(ddof=1) / np.sqrt(len(f))
if abs(f.mean()) <= 2 * se:
xulosa = "sezilarli emas"
elif f.mean() > 0:
xulosa = "belgi sezilarli yaxshi"
else:
xulosa = "so'z sezilarli yaxshi"
print(f" {d:>5.2f} {f.mean():>+8.4f} {se:>7.4f} {xulosa:>22}")
y0 = np.mean(natija["so'z+bigram"][0.0]) - np.mean(
natija["so'z+bigram"][0.3])
y1 = np.mean(natija["belgi (char)"][0.0]) - np.mean(
natija["belgi (char)"][0.3])
if y1 < y0:
print(f" belgi modeli {y0 / y1:.1f} barobar kam yo'qotdi")
else:
print(" belgi modeli kamroq yo'qotmadi")
print(" ⭐ Imlo xatosi ko'p bo'lsa - belgi n-gramlari")
if __name__ == "__main__":
main()Natijaning muhim qismi:
=== 1. Test so'zlarining lug'atdan tashqari (OOV) ulushi ===
xato darajasi 0.00: OOV 0.0%
xato darajasi 0.15: OOV 1.9%
xato darajasi 0.30: OOV 3.8%
=== 2. Aniqlik (5 fold o'rtachasi) ===
xato so'z+bigram belgi (char)
0.00 0.8320 0.8367
0.15 0.7883 0.8113
0.30 0.7267 0.7863
=== 3. Aniqlik yo'qotishi (0.0 dan 0.3 gacha) ===
so'z+bigram 0.8320 -> 0.7267 (yo'qotish 0.1053)
belgi (char) 0.8367 -> 0.7863 (yo'qotish 0.0503)
=== 4. Juftlashgan farq: belgi - so'z+bigram ===
xato farq SE xulosa
0.00 +0.0047 0.0051 sezilarli emas
0.15 +0.0230 0.0029 belgi sezilarli yaxshi
0.30 +0.0597 0.0083 belgi sezilarli yaxshi
belgi modeli 2.1 barobar kam yo'qotdi
⭐ Imlo xatosi ko'p bo'lsa - belgi n-gramlariNima ko'rsatdi: 2.4, 2.5-bo'limlar.
Misol 4 — Model nimani o'rgandi va xato tahlili
"""LogReg koeffitsiyentlari, chalkashlik matritsasi va eng ishonchli xatolar."""
import numpy as np
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import confusion_matrix
from sklearn.model_selection import train_test_split
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]}
SINFLAR = ["salbiy", "neytral", "ijobiy"]
TP = r"[\w']+"
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 main() -> None:
matnlar, y, _ = korpus(3000)
Xtr, Xte, ytr, yte = train_test_split(matnlar, y, test_size=0.2,
random_state=0, stratify=y)
model = make_pipeline(
TfidfVectorizer(token_pattern=TP, ngram_range=(1, 2)),
LogisticRegression(C=10, max_iter=3000))
model.fit(Xtr, ytr)
vek, clf = model[0], model[1]
nomlar = vek.get_feature_names_out()
print("=== 1. Har sinf uchun eng kuchli belgilar ===")
for k in (2, 0, 1):
top = np.argsort(clf.coef_[k])[::-1][:7]
print(f" {SINFLAR[k]}:")
print(" " + ", ".join(f"{nomlar[i]} ({clf.coef_[k, i]:.1f})"
for i in top[:4]))
print(" " + ", ".join(f"{nomlar[i]} ({clf.coef_[k, i]:.1f})"
for i in top[4:]))
print("\n=== 2. Inkor: bir so'z, ikki ma'no ===")
j = {n: i for i, n in enumerate(nomlar)}
for f in ["sifatli", "sifatli emas", "sust", "sust emas", "emas"]:
if f in j:
w = clf.coef_[2, j[f]] - clf.coef_[0, j[f]]
print(f" {f:<14} ijobiy - salbiy og'irlik: {w:+.2f}")
print(" unigram 'emas' deyarli neytral - ma'noni bigram tashiydi")
print("\n=== 3. Shubhali belgilar: mahsulot nomlari ===")
kuch = np.abs(clf.coef_).max(0)
tartib = np.argsort(-kuch)
rang = {nomlar[i]: r for r, i in enumerate(tartib)}
for m in ["telefon", "kurtka", "soat"]:
print(f" {m:<9} eng katta |og'irlik| {kuch[j[m]]:.2f}, "
f"o'rni {rang[m] + 1} / {len(nomlar)}")
print(" mahsulot nomi sinfga bog'liq emas - og'irligi kichik bo'lishi kerak")
print("\n=== 4. Chalkashlik matritsasi (qator - haqiqiy) ===")
p = model.predict_proba(Xte)
b = p.argmax(1)
M = confusion_matrix(yte, b)
print(" " + "".join(f"{s:>9}" for s in SINFLAR))
for i, s in enumerate(SINFLAR):
print(f" {s:<8} " + "".join(f"{M[i, k]:>9}" for k in range(3)))
print(f" aniqlik: {np.trace(M) / M.sum():.4f}")
print(f" salbiy <-> ijobiy xatolar: {M[0, 2] + M[2, 0]}, "
f"neytral bilan bog'liq: {M.sum() - np.trace(M) - M[0, 2] - M[2, 0]}")
print("\n=== 5. Guruhlar bo'yicha aniqlik ===")
togri = b == yte
inkor = np.array([" emas" in m for m in Xte])
gaplar = np.array([sum(m.count(f" {c} ") + m.startswith(c)
for c in JIHAT) for m in Xte])
for nom, g in [("inkorli", inkor), ("inkorsiz", ~inkor),
("1 jihat", gaplar <= 1), ("3+ jihat", gaplar >= 3)]:
print(f" {nom:<9} {int(g.sum()):>4} ta, aniqlik {togri[g].mean():.4f}")
print("\n=== 6. Eng ishonchli xatolar ===")
xato = np.where(~togri)[0]
ishonch = p[xato, b[xato]]
for i in xato[np.argsort(-ishonch)[:3]]:
print(f" haqiqiy {SINFLAR[yte[i]]}, bashorat {SINFLAR[b[i]]} "
f"(p={p[i, b[i]]:.2f})")
print(f" {Xte[i]}")
print(f" xatolardagi o'rtacha ishonch: {ishonch.mean():.3f}, "
f"to'g'rilarda: {p[togri, b[togri]].mean():.3f}")
print(" ⭐ Koeffitsiyent va xatolar - modelni tushunish yo'li")
if __name__ == "__main__":
main()Natijaning muhim qismi:
=== 1. Har sinf uchun eng kuchli belgilar ===
ijobiy:
nosoz emas 7.3-bob, noqulay emas 6.4-bob, yomon emas 5.7-bob, mo'rt emas 5.6-bob
sust emas 5.5-bob, sifatsiz emas 5.1-bob, zo'r 4.9-bob
salbiy:
mustahkam emas 6.3-bob, zo'r emas 5.9-bob, sifatli emas 5.7-bob, yaxshi emas 5.1-bob
chatoq 5.0-bob, chiroyli emas 4.9-bob, bor 4.9-bob
neytral:
odatiy 5.7-bob, o'rtacha 5.3-bob, oddiy 5.1-bob, kutilgandek 5.0-bob
normal 4.4-bob, ekrani odatiy 3.3-bob, lekin qadoqlash 2.8-bob
=== 2. Inkor: bir so'z, ikki ma'no ===
sifatli ijobiy - salbiy og'irlik: +7.12
sifatli emas ijobiy - salbiy og'irlik: -11.01
sust ijobiy - salbiy og'irlik: -6.77
sust emas ijobiy - salbiy og'irlik: +10.50
emas ijobiy - salbiy og'irlik: -2.25
unigram 'emas' deyarli neytral - ma'noni bigram tashiydi
=== 3. Shubhali belgilar: mahsulot nomlari ===
telefon eng katta |og'irlik| 0.63, o'rni 1470 / 4612
kurtka eng katta |og'irlik| 0.44, o'rni 2177 / 4612
soat eng katta |og'irlik| 0.83, o'rni 953 / 4612
mahsulot nomi sinfga bog'liq emas - og'irligi kichik bo'lishi kerak
=== 4. Chalkashlik matritsasi (qator - haqiqiy) ===
salbiy neytral ijobiy
salbiy 153 28 7
neytral 28 156 24
ijobiy 2 22 180
aniqlik: 0.8150
salbiy <-> ijobiy xatolar: 9, neytral bilan bog'liq: 102
=== 5. Guruhlar bo'yicha aniqlik ===
inkorli 190 ta, aniqlik 0.7684
inkorsiz 410 ta, aniqlik 0.8366
1 jihat 171 ta, aniqlik 0.8538
3+ jihat 283 ta, aniqlik 0.8269
=== 6. Eng ishonchli xatolar ===
haqiqiy neytral, bashorat ijobiy (p=0.99)
noutbuk sotib oldim materiali chiroyli ammo sifati mo'rt qadoqlash juda a'lo yana olaman
haqiqiy ijobiy, bashorat neytral (p=0.98)
bir oy oldin sumka oldim batareyasi kutilgandek lekin sifati chatoq emas ammo ovozi kutilgandek ammo sifati kutilgandek
haqiqiy neytral, bashorat salbiy (p=0.97)
akam uchun krossovka buyurtma qildim narxi juda mo'rt ham batareeyasi kutilgandek ham ekrani zo'r emas lekin sifati juuda sifatsiz
xatolardagi o'rtacha ishonch: 0.681, to'g'rilarda: 0.847
⭐ Koeffitsiyent va xatolar - modelni tushunish yo'liNima ko'rsatdi: 2.6-bo'lim.
5. To'g'ri va noto'g'ri tushunishlar
| Noto'g'ri fikr | To'g'risi |
|---|---|
| "Neyron model har doim TF-IDF dan yaxshi" | Avval klassik bazaviyni o'lchang — u ko'pincha juda yaqin |
"TF-IDF ni butun ma'lumotda fit qilish zararsiz" |
Bu leakage; Pipeline bilan savol umuman tug'ilmaydi |
| "Unigramlar sentimentga yetadi" | Inkor ("sifatli emas") faqat bigram yoki belgi n-gramda ko'rinadi |
| "Belgi n-gramlari har doim yaxshiroq" | Ular sekinroq; faqat o'lchov ularning foydasini ko'rsatadi |
| "Naive Bayes eskirgan" | Tez va kam ma'lumotda kuchli, lekin inkorni tushunmaydi |
| "0.005 farq — yaxshilanish" | SE bilan tekshirilmagan farq — shovqin bo'lishi mumkin |
| "Eng yuqori aniqlikli modelni olamiz" | Sezilarli yomon bo'lmagan eng soddasini oling |
| "Aniqlik yetarli ma'lumot beradi" | Koeffitsiyentlar va xatolar sababni ko'rsatadi |
6. Keng tarqalgan xatolar va yechimlari
1. Leakage: vektorizator butun ma'lumotda
X = TfidfVectorizer().fit_transform(matnlar); train_test_split(X, y) # ⚠️
model = make_pipeline(TfidfVectorizer(), LogisticRegression()) # ✅2. Apostrofli so'zlar bo'linadi
TfidfVectorizer() # "zo'r" -> "zo" # ⚠️
TfidfVectorizer(token_pattern=r"[\w']+") # "zo'r" butun # ✅3. Inkor yo'qoladi
TfidfVectorizer(ngram_range=(1, 1)) # "emas" alohida # ⚠️
TfidfVectorizer(ngram_range=(1, 2)) # "sifatli emas" # ✅4. Turli foldlar
cross_val_score(a, X, y, cv=5); cross_val_score(b, X, y, cv=KFold(5, shuffle=True)) # ⚠️
cv = StratifiedKFold(5, shuffle=True, random_state=0) # ikkalasiga bir xil # ✅5. Naive Bayes ga manfiy belgilar
make_pipeline(TfidfVectorizer(), StandardScaler(with_mean=False), MultinomialNB()) # ⚠️ o'lchov buziladi
make_pipeline(TfidfVectorizer(), MultinomialNB()) # sanoq/TF-IDF - manfiy emas # ✅6. LinearSVC dan ehtimol
LinearSVC().predict_proba(X) # AttributeError # ⚠️
LogisticRegression().predict_proba(X) # yoki CalibratedClassifierCV # ✅7. Bitta raqam bilan qaror
print("char yaxshi" if aniq_char > aniq_soz else "so'z yaxshi") # ⚠️
d = char_s - soz_s; abs(d.mean()) > 2 * d.std(ddof=1) / len(d) ** 0.5 # ✅7. Integratsiya — bu bilim qayerda kerak bo'ladi
- 14-qism (o'tilgan): Logistik regressiya, Naive Bayes, SVM
- 18-qism (o'tilgan): Juftlashgan CV taqqoslash va SE
- 19-qism (o'tilgan):
Pipeline,FeatureUnion, leakage - 23.4-dars (o'tilgan): Bag-of-words va TF-IDF
- Keyingi darslar: so'z embeddinglari, embedding bilan klassifikatsiya (bu darsdagi bazaviy bilan solishtiriladi), RNN
- Transformerlar qismida: katta oldindan o'rgatilgan modellar — ular ham shu bazaviy bilan o'lchanadi
8. Eng yaxshi amaliyotlar
Har doim
DummyClassifierdan boshlang.Vektorizatorni
Pipelineichiga qo'ying.token_patternni o'zbekcha apostrofga moslang (yoki 23.1 dagi kabi normallashtiring).Kamida so'z + bigram TF-IDF + LogReg ni bazaviy qiling.
Imlo xatosi ko'p bo'lsa belgi n-gramlarini sinang.
Barcha modellarni bir xil foldlarda, juftlashgan farq + SE bilan solishtiring.
Sezilarli yomon bo'lmagan eng sodda modelni tanlang.
Koeffitsiyentlar va eng ishonchli xatolarni ko'zdan kechiring.
9. Amaliy topshiriq
Vazifa 1: Bashorat qiling
1. # 3 ta teng sinfda DummyClassifier aniqligi taxminan?
2. # TfidfVectorizer ning fit qismi nimalarni o'rganadi?
3. # sukut token_pattern "zo'r" ni qanday bo'ladi?
4. # "sifatli emas" ni unigram modeli qanday ko'radi?
5. # analyzer="char_wb" va "char" farqi?
6. # MultinomialNB dagi alpha nima uchun?
7. # LinearSVC predict_proba beradimi?
8. # juftlashgan CV da nima bir xil bo'lishi shart?
9. # SE formulasi (5 fold)?
10. # qaror qoidasi: qaysi model tanlanadi?
11. # clf.coef_ shakli (3 sinf, 20000 belgi)?
12. # mahsulot nomi katta og'irlik olsa - nima belgisi?Javoblar
- ~0.33 (eng katta sinf ulushi)
- Lug'at (qaysi so'z/n-gramlar bor) va IDF og'irliklari
zo— apostrofdan keyingirbir harfli token sifatida tashlanadisifatli(ijobiy) vaemas(deyarli neytral) alohida — inkor yo'qoladichar_wbfaqat so'z ichida,charbo'shliqdan ham o'tadi- Silliqlash: ko'rilmagan so'z nol ehtimol bermasin
- Yo'q — faqat
decision_function - Foldlar (bo'linish) va metrika
std(d, ddof=1) / sqrt(5)- Eng yaxshisidan sezilarli yomon bo'lmagan eng soddasi
(3, 20000)- Ma'lumotda noxolis bog'liqlik yoki leakage
Vazifa 2: Xatolarni tuzating
1. X = TfidfVectorizer().fit_transform(matnlar)
Xtr, Xte, ytr, yte = train_test_split(X, y)
2. vek = TfidfVectorizer() # o'zbekcha sharhlar uchun
3. make_pipeline(TfidfVectorizer(), StandardScaler(with_mean=False),
MultinomialNB())
4. a = cross_val_score(m1, X, y, cv=KFold(5, shuffle=True))
b = cross_val_score(m2, X, y, cv=KFold(5, shuffle=True))
5. p = make_pipeline(TfidfVectorizer(), LinearSVC()).fit(X, y).predict_proba(X2)Javoblar
1. model = make_pipeline(TfidfVectorizer(), LogisticRegression())
Xtr, Xte, ytr, yte = train_test_split(matnlar, y)
model.fit(Xtr, ytr)
2. vek = TfidfVectorizer(token_pattern=r"[\w']+", ngram_range=(1, 2))
3. make_pipeline(TfidfVectorizer(), MultinomialNB())
4. cv = StratifiedKFold(5, shuffle=True, random_state=0)
a = cross_val_score(m1, X, y, cv=cv)
b = cross_val_score(m2, X, y, cv=cv)
5. p = make_pipeline(TfidfVectorizer(),
LogisticRegression()).fit(X, y).predict_proba(X2)Vazifa 3: Bazaviy va Pipeline
Modellang:
- Korpus statistikasi
DummyClassifier- TF-IDF + LogReg
Pipeline - Leakage bilan taqqoslash
Vazifa 4: Juftlashgan taqqoslash
Modellang:
- NB, LogReg, LinearSVC
- So'z va bigram
- Belgi n-gramlari
- Qaror qoidasi
Vazifa 5: Imlo xatolari
Modellang:
- OOV ulushi
- Xato darajalari
- So'z va belgi yo'qotishi
- Juftlashgan farq
Vazifa 6: Model nimani o'rgandi
Modellang:
- Top koeffitsiyentlar
- Inkor og'irliklari
- Chalkashlik matritsasi
- Eng ishonchli xatolar
Vazifa 7: O'ylash
Boshqa jamoa o'z sharhlarida uch variantni o'lchadi: so'z + bigram TF-IDF + LogReg (5 fold o'rtachasi 0.834), belgi n-gram LogReg 0.851-bob va ikkalasining FeatureUnion birlashmasi 0.853-bob. Menejer so'radi: "Eng yuqorisini — birlashmani olamiz, to'g'rimi?" Nima deysiz?
Javob
Qisqa javob: avval farqlarni SE bilan tekshiring, keyin ishlab chiqarish sharoitini hisobga oling. Eng yuqori o'rtacha — avtomatik g'olib emas.
1. Farq sezilarlimi? Birlashma va belgi modeli orasidagi 0.002 farq — deyarli shubhasiz shovqin ichida. Belgi modeli va so'z modeli orasidagi 0.017 esa sezilarli bo'lishi mumkin — lekin buni juftlashgan fold farqlari va SE ko'rsatadi, o'rtachalar emas:
d = char_s - soz_s # bir xil foldlar!
se = d.std(ddof=1) / np.sqrt(len(d))
print(d.mean(), se, abs(d.mean()) > 2 * se)2. Qaror qoidasi. Sezilarli yomon bo'lmagan eng sodda model. Agar birlashma belgi modelidan sezilarli yaxshi bo'lmasa — birlashmaning qo'shimcha murakkabligi (ikki vektorizator, ikki barobar katta lug'at) oqlanmaydi.
3. Ishlab chiqarish sharoiti. Real sharhlarda imlo xatosi ko'pmi? 3-misolda xato darajasi oshgani sari so'z modelining aniqligi belgi modelinikidan tezroq tushdi. Agar foydalanuvchilar telefondan tez yozsa, bu farq toza testdagidan katta bo'ladi. Uni ishlab chiqarishga o'xshash test to'plamida o'lchang (masalan, real sharhlardan 500 tasini qo'lda belgilab).
4. Xarajat. Belgi n-gram vektorida har matn uchun nol bo'lmagan qiymatlar so'z vektoridagidan taxminan 10 barobar ko'p: vektorlash va bashorat vaqti, xotira. Kuniga millionlab sharh bo'lsa, bu ham hisobga olinadi.
5. Tushuntirish. So'z modelining koeffitsiyentlari o'qiladi ("sifatli emas" → salbiy). Belgi n-gramlari ("li em") — kamroq tushunarli. Agar biznes "nega bu sharh salbiy deb topildi?" deb so'rasa, bu muhim.
Menejerga javob: "Uchalasi yaqin. Avval juftlashgan farqlarni SE bilan tekshiramiz: agar birlashma belgi modelidan sezilarli yaxshi bo'lmasa, soddaroq belgi modelini olamiz. Agar belgi modeli ham so'z modelidan sezilarli yaxshi bo'lmasa — so'z modelini olamiz, chunki u tezroq va tushuntiriladi. Qarorni imlo xatolari ko'p real sharhlar to'plamida yakuniy tekshiramiz."
Nimani mustahkamlaydi: 2.4, 2.5-bo'limlar.
Xulosa
Bu darsda matn klassifikatsiyasi uchun klassik bazaviylarni qurdik va halol taqqosladik.
Eng muhim uch fikr:
Bazaviy —
Pipelineichidagi TF-IDF + logistik regressiya. 1-misoldaDummyClassifier0.3467aniqlik berdi — shu "hech narsa o'rganmagan" chegara. So'z + bigram TF-IDF va logistik regressiyaPipelineichida0.8150ga chiqdi; eng qiyin sinf neytral bo'ldi (F10.7536, ijobiy0.8675). Vektorizatorni butun ma'lumotdafitqilganda lug'atga faqat testda uchragan 639 ta belgi (imlo xatoli so'zlar va yangi bigramlar) kirib qoldi; aniqlik farqi bu yerda kichik chiqdi (0.8167va0.8150) — lekin buni faqat o'lchab bildik,Pipelinebilan esa bu savol umuman tug'ilmaydi. Sukuttoken_patternesa "zo'r" ni "zo" ga, "a'lo" ni "lo" ga aylantirdi.N-gramlar ma'noni tashiydi. 2-misolda bir xil 5 foldda sakkiz model solishtirildi. Bigramlar logistik regressiyaga
+0.0877, Naive Bayes ga+0.1040qo'shdi — ikkalasi ham2 × SEdan ancha katta: inkor ("sifatli emas") faqat bigramda ko'rinadi. Bo'shliqdan o'tadigan belgi n-gramlari (char) so'z ichidagi (char_wb) variantdan+0.0960yaxshi chiqdi — chunkichar_wbham inkorni ko'rmaydi. Eng yaxshisiLR char(0.8287) bo'ldi va qolgan yetti modelning hammasi undan sezilarli yomon chiqdi, shuning uchun qaror qoidasi uni tanladi; eng yaqin raqibSVC so'z+bi(-0.0163, SE0.0041). 3-misolda imlo xatosi bo'lmagan testda belgi va so'z modellari orasidagi farq sezilarli emas edi (+0.0047, SE0.0051), lekin xato darajasi 0.30 ga yetganda so'z modeli0.1053, belgi modeli0.0503aniqlik yo'qotdi — 2.1 barobar kam.Model nimani o'rganganini ko'ring. 4-misolda logistik regressiya koeffitsiyentlari mantiqiy chiqdi: ijobiy sinfga eng kuchli ovoz "nosoz emas", "yomon emas", salbiyga — "zo'r emas", "sifatli emas". "sifatli" ning og'irligi
+7.12, "sifatli emas" niki-11.01— ma'noni bigram tashiydi. Mahsulot nomlari past o'rinlarda qoldi ("telefon" 4612 belgidan 1470-o'rinda) — noxolis bog'liqlik yo'q. Chalkashlik matritsasida 111 xatodan atigi 9 tasi salbiy va ijobiy orasida, qolgan 102 tasi neytral bilan bog'liq; inkorli sharhlarda aniqlik0.7684, inkorsizlarda0.8366. Eng ishonchli xatolar esa yorliq shovqinini ko'rsatdi: masalan, to'rt gapdan uchtasi salbiy bo'lgan sharh "neytral" deb belgilangan edi — model to'g'ri, yorliq xato.
Keyingi darsda so'z embeddinglari: taqsimot gipotezasi, birga uchrash matritsasi, PPMI va SVD, va word2vec ni noldan o'rgatish.
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!