IlmHamroh
Data Science va sun'iy intellekt/Nazoratsiz organish11/12-dars21 daqiqa
Mundarija (22)

16.11-dars: Qo'llanilishi

16-QISM — NAZORATSIZ O'RGANISH · 11-dars


1. Kirish va motivatsiya

Oldingi o'nta darsda algoritmlarni o'rgandik. Endi savol boshqacha: qaysi vazifada qaysi usul ishlaydi va natija nima berishi kerak?

Nazoratsiz o'rganishning amaliy qo'llanilishlari bir-biridan mezon bilan farq qiladi. Mijoz segmentatsiyasida muvaffaqiyat — marketing konversiyasi; mavzu modellashtirish — mavzularning tushunarliligi; siqishda — hajm va sifat muvozanati; tavsiya tizimida — bosish darajasi. Bir xil algoritm, butunlay boshqa baholash.

Bu darsda: mijoz segmentatsiyasi (RFM va xulq-atvor), mavzu modellashtirish (NMF va LDA), tavsiya tizimi asoslari (matritsani faktorlash), siqish (tasvir va vektor kvantlash) va belgi muhandisligi uchun nazoratsiz usullar.

Real vaziyat. Yangiliklar sayti 40 000 maqolani toifalarga ajratishi kerak edi. Qo'lda yorliqlash 3 oy va 12 mln so'm talab qilardi. NMF bilan 20 ta mavzu bir kunda ajratildi, muharrirlar ularga nom berdi va 94% maqola to'g'ri toifaga tushdi. Qolgan 6% qo'lda tuzatildi.

Bu darsda nazoratsiz o'rganishning qo'llanilishini o'rganamiz.

Bu darsda:

  • Mijoz segmentatsiyasi
  • Mavzu modellashtirish
  • Tavsiya tizimi asoslari
  • Siqish va kvantlash
  • Belgi muhandisligi
  • Qaysi vazifada qaysi mezon
  • Tuzoqlar
  • Amaliy: to'rt vazifa

ℹ Misollar real numpy/pandas/sklearn bilan (Python 3.14).


2. Nazariya — chuqur tushuntirish

2.1. Mijoz segmentatsiyasi

text
IKKI YONDASHUV:

1. RFM (Recency, Frequency, Monetary) - klassik, qoidaga asoslangan
   R = oxirgi xariddan o'tgan kun
   F = xaridlar soni
   M = jami summa
   Har birini 1-5 ballga bo'lib, 125 ta segment yoki guruhlash

2. XULQ-ATVOR klasterlashi - K-means/GMM bilan
   RFM + toifalar + kanal + vaqt naqshlari

AMALIY QOIDALAR:
  - log1p transformatsiya (summalar juda qiyshiq)
  - StandardScaler
  - 3-7 segment (boshqariladigan)
  - har segment > 5%
  - segmentlarni NOM bilan atash (marketing tili)

RFM ni bazaviy natija sifatida qo'ying: u sodda, tushunarli va ko'pincha klasterlashdan yomon emas. Klasterlash RFM dan sezilarli yaxshi bo'lmasa, RFM ni tanlang.

2.2. Mavzu modellashtirish

text
Hujjat-so'z matritsasini ikkiga ajratish:

  X (hujjat x so'z)  ~  W (hujjat x mavzu) @ H (mavzu x so'z)

NMF (Non-negative Matrix Factorization):
  + barcha qiymatlar MANFIY EMAS -> talqin oson
  + TF-IDF bilan yaxshi ishlaydi
  + tez
  sklearn: NMF(n_components=20, init="nndsvda")

LDA (Latent Dirichlet Allocation):
  + ehtimollik modeli, generativ
  + XOM SANOQ bilan ishlaydi (TF-IDF emas)
  - sekinroq, parametrlarga sezgir
  sklearn: LatentDirichletAllocation(n_components=20)

BAHOLASH: koherentlik (mavzu so'zlari birga uchraydimi), tushunarlilik

NMF odatda amaliy tanlov: u tezroq, TF-IDF bilan ishlaydi va mavzulari ko'pincha tushunarliroq. LDA nazariy jihatdan chiroyliroq, lekin sozlash ko'proq vaqt oladi.

2.3. Tavsiya tizimi asoslari

text
Foydalanuvchi-mahsulot matritsasini faktorlash:

  R (foydalanuvchi x mahsulot)  ~  U (foydalanuvchi x omil) @ V^T

  omillar = yashirin xususiyatlar (janr, narx darajasi, uslub)
  bo'sh kataklar BASHORAT qilinadi

sklearn:
  TruncatedSVD - siyrak matritsada ishlaydi
  NMF - manfiy bo'lmagan reytinglar uchun

MUHIM: haqiqiy tavsiya tizimlari murakkabroq
  implicit feedback, sovuq boshlash, vaqt, kontekst
  ixtisoslashgan kutubxonalar: implicit, LightFM, Surprise

BAHOLASH: precision@k, recall@k, NDCG - aniqlik emas

Matritsani faktorlash — tavsiya tizimining faqat yadrosi: real tizimda sovuq boshlash (yangi foydalanuvchi), xilma-xillik va yangilik muammolari ham hal qilinishi kerak.

2.4. Siqish va kvantlash

text
1. PCA siqish: X ~ Z @ components_
   saqlanadi: Z (n x k) + components_ (k x p)
   foyda: p >> k bo'lganda

2. VEKTOR KVANTLASH (K-means bilan):
   har nuqtani eng yaqin MARKAZ bilan almashtirish
   saqlanadi: markazlar (k x p) + yorliqlar (n ta indeks)
   tasvir siqishda: ranglar palitrasi

3. MAHSULOT KVANTLASH (product quantization):
   vektorni bo'laklarga bo'lib, har bo'lakni alohida kvantlash
   vektor qidiruv tizimlarida (FAISS) standart

Baholash: siqish nisbati va qayta tiklash xatosi (MSE, PSNR)

Vektor kvantlash — K-means ning eng eski amaliy qo'llanilishi: 16 mln rangni 32 ta rangga tushirish tasvirni 3 barobardan ko'proq siqadi va ko'z bilan farq deyarli sezilmaydi.

2.5. Belgi muhandisligi

python
# 1. Klaster yorlig'i - yangi kategoriyali belgi
X["klaster"] = KMeans(8, n_init=10, random_state=0).fit_predict(Xs)

# 2. Markazgacha masofalar - k ta yangi sonli belgi
masofalar = km.transform(Xs)       # (n, k)

# 3. PCA komponentlari - shovqinsiz belgilar
X_yangi = np.column_stack([X, PCA(5).fit_transform(Xs)])

# 4. Anomaliya bali - yangi belgi
X["anomaliya"] = -IsolationForest().fit(X).score_samples(X)

# 5. GMM ehtimolliklari - yumshoq segment belgilari
X_yangi = np.column_stack([X, gmm.predict_proba(Xs)])

Klaster yorlig'i belgi sifatida — nazoratsiz usullarning nazoratli modelga eng oddiy hissasi. Lekin buni Pipeline ichida qiling, aks holda leakage bo'ladi 12.9-bob.

2.6. Qaysi vazifada qaysi mezon

text
VAZIFA                MEZON                        ALGORITM
segmentatsiya         konversiya, tushunarlilik    K-means, GMM
mavzu modellashtirish koherentlik, tushunarlilik   NMF, LDA
tavsiya               precision@k, NDCG            TruncatedSVD, NMF
anomaliya             precision@k, PR AUC          IsolationForest, LOF
siqish                hajm va MSE                  PCA, K-means
vizualizatsiya        qo'shnilik saqlanishi        t-SNE, UMAP, PCA
belgi muhandisligi    keyingi modelning CV si      hammasi

QOIDA: mezonni ALGORITMDAN OLDIN tanlang

Mezonni algoritmdan oldin tanlash — nazoratsiz loyihaning eng muhim qoidasi: aks holda siz "chiroyli" natijani tanlaysiz, foydali natijani emas.

2.7. Tuzoqlar

Asosiy tuzoqlar: segmentatsiyada RFM bazaviy natijasini o'tkazib yuborish; LDA ga TF-IDF berish (xom sanoq kerak); tavsiyani accuracy bilan baholash; klaster belgisini Pipeline tashqarisida qo'shish (leakage); mavzular sonini koherentliksiz tanlash; siqishda qayta tiklash xatosini o'lchamaslik; sovuq boshlashni hisobga olmaslik; mezonni algoritmdan keyin tanlash.

2.8. Mezon algoritmdan muhimroq

Segmentatsiyada RFM bazaviy natija, mezon — konversiya va tushunarlilik. Mavzu modellashtirishda NMF (TF-IDF bilan) amaliy tanlov, mezon — koherentlik. Tavsiyada matritsani faktorlash yadro, mezon — precision@k. Siqishda hajm va qayta tiklash xatosi muvozanati. Belgi muhandisligida klaster yorlig'i va masofalar — lekin Pipeline ichida. Mezonni algoritmdan oldin tanlang. Keyingi dars — amaliyot.


3. Tez ma'lumotnoma

python
from sklearn.decomposition import NMF, PCA, LatentDirichletAllocation, TruncatedSVD
from sklearn.feature_extraction.text import CountVectorizer, TfidfVectorizer

# mavzu modellashtirish
tfidf = TfidfVectorizer(max_df=0.9, min_df=5)
X = tfidf.fit_transform(hujjatlar)
nmf = NMF(n_components=20, init="nndsvda", random_state=0).fit(X)
for i, mavzu in enumerate(nmf.components_):
    print(i, [tfidf.get_feature_names_out()[j] for j in mavzu.argsort()[-8:]])

# LDA uchun XOM sanoq
sanoq = CountVectorizer(max_df=0.9, min_df=5).fit_transform(hujjatlar)
LatentDirichletAllocation(n_components=20, random_state=0).fit(sanoq)

# tavsiya
TruncatedSVD(n_components=50, random_state=0).fit(siyrak_matritsa)
QOIDA: mezonni oldin tanla · RFM bazaviy · LDA ga xom sanoq ·
       belgini Pipeline ichida

Qo'llanilish xulosasi

Segmentatsiya: RFM bazaviy, K-means/GMM, mezon = konversiya
Mavzular: NMF (TF-IDF) yoki LDA (xom sanoq), mezon = koherentlik
Tavsiya: matritsani faktorlash, mezon = precision@k
Siqish: PCA yoki vektor kvantlash, mezon = hajm va MSE

4. Batafsil misollar

Misollar real numpy/pandas/sklearn bilan (Python 3.14).

Misol 1 — Mijoz segmentatsiyasi: RFM va klasterlash

python
"""Bazaviy natija bilan taqqoslash (real pandas/sklearn)."""

import numpy as np
import pandas as pd
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_score
from sklearn.preprocessing import StandardScaler


def yarat(seed: int = 11, n: int = 4000) -> pd.DataFrame:
    """Mijozlar: turli xulq-atvor naqshlari."""
    rng = np.random.default_rng(seed)
    tur = rng.choice(4, n, p=[0.35, 0.3, 0.2, 0.15])
    profil = {
        "recency": np.array([12.0, 60.0, 5.0, 120.0]),
        "frequency": np.array([9.0, 2.0, 22.0, 1.5]),
        "monetary": np.array([1_200_000.0, 400_000.0, 900_000.0, 2_500_000.0]),
    }
    df = pd.DataFrame({
        nom: np.clip(markaz[tur] * rng.lognormal(0, 0.35, n), 0.5, None)
        for nom, markaz in profil.items()
    })
    df["tur"] = tur
    return df


def rfm_ball(df: pd.DataFrame) -> pd.DataFrame:
    """Har o'lchov bo'yicha 1-5 ball (kvintil)."""
    natija = df.copy()
    natija["R"] = pd.qcut(df["recency"], 5, labels=[5, 4, 3, 2, 1]).astype(int)
    natija["F"] = pd.qcut(df["frequency"].rank(method="first"), 5,
                          labels=[1, 2, 3, 4, 5]).astype(int)
    natija["M"] = pd.qcut(df["monetary"], 5, labels=[1, 2, 3, 4, 5]).astype(int)
    return natija


def main() -> None:
    df = yarat()
    print("=== 1. Ma'lumot ===")
    print(f"  {len(df)} mijoz")
    for nom in ["recency", "frequency", "monetary"]:
        print(f"  {nom:<12}: mediana {df[nom].median():>12,.0f}, "
              f"qiyshiqlik {df[nom].skew():>6.2f}")

    print("\n=== 2. RFM ballari (bazaviy) ===")
    r = rfm_ball(df)
    r["rfm_segment"] = np.select(
        [(r["R"] >= 4) & (r["F"] >= 4),
         (r["R"] >= 4) & (r["F"] < 4),
         (r["R"] < 3) & (r["M"] >= 4),
         (r["R"] < 3)],
        ["sodiq", "yangi", "yo'qolgan qimmatli", "uyquda"],
        default="oddiy")
    print(f"  {'segment':<22} {'n':>7} {'ulush':>8} {'o_rt monetary':>16}")
    for nom, guruh in r.groupby("rfm_segment"):
        print(f"  {nom:<22} {len(guruh):>7} {len(guruh) / len(r):>7.1%} "
              f"{guruh['monetary'].mean():>16,.0f}")

    print("\n=== 3. Klasterlash ===")
    X = np.log1p(df[["recency", "frequency", "monetary"]].to_numpy())
    Xs = StandardScaler().fit_transform(X)
    print(f"  {'k':>3} {'silhouette':>12} {'eng kichik %':>14}")
    for k in range(2, 8):
        yorliq = KMeans(k, n_init=10, random_state=0).fit_predict(Xs)
        print(f"  {k:>3} {silhouette_score(Xs, yorliq):>12.4f} "
              f"{np.bincount(yorliq).min() / len(Xs):>13.1%}")

    print("\n=== 4. Ikki yondashuvni taqqoslash ===")
    from sklearn.metrics import adjusted_rand_score
    km = KMeans(4, n_init=10, random_state=0).fit_predict(Xs)
    kod = {nom: i for i, nom in enumerate(sorted(r["rfm_segment"].unique()))}
    rfm_yorliq = r["rfm_segment"].map(kod).to_numpy()
    print(f"  haqiqiy tur bilan ARI:")
    print(f"    RFM segmentlari:  "
          f"{adjusted_rand_score(df['tur'], rfm_yorliq):.4f}")
    print(f"    K-means (k=4):    "
          f"{adjusted_rand_score(df['tur'], km):.4f}")
    print(f"  ikkalasining o'zaro kelishuvi: "
          f"{adjusted_rand_score(rfm_yorliq, km):.4f}")
    print(f"\n  {'klaster':>8} {'n':>7} {'recency':>10} {'frequency':>11} "
          f"{'monetary':>14}")
    for k in range(4):
        m = km == k
        print(f"  {k:>8} {int(m.sum()):>7} {df.loc[m, 'recency'].mean():>10.1f} "
              f"{df.loc[m, 'frequency'].mean():>11.1f} "
              f"{df.loc[m, 'monetary'].mean():>14,.0f}")
    print("  ⭐ RFM ni bazaviy natija sifatida qo'ying")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Ma'lumot ===
  4000 mijoz
  recency     : mediana           17, qiyshiqlik   1.63
  frequency   : mediana            6, qiyshiqlik   1.69
  monetary    : mediana      943,194, qiyshiqlik   1.78

=== 2. RFM ballari (bazaviy) ===
  segment                      n    ulush    o_rt monetary
  oddiy                      800   20.0%        1,115,212
  sodiq                     1259   31.5%        1,073,454
  uyquda                    1049   26.2%          422,950
  yangi                      341    8.5%        1,262,477
  yo'qolgan qimmatli         551   13.8%        2,623,994

=== 3. Klasterlash ===
    k   silhouette   eng kichik %
    2       0.5404         44.5%
    3       0.6238         13.9%
    4       0.5271         13.9%
    5       0.4702         13.9%
    6       0.3718         13.8%
    7       0.3493          9.6%

=== 4. Ikki yondashuvni taqqoslash ===
  haqiqiy tur bilan ARI:
    RFM segmentlari:  0.5667
    K-means (k=4):    0.9377
  ikkalasining o'zaro kelishuvi: 0.5753

   klaster       n    recency   frequency       monetary
         0     554      126.4         1.6      2,615,560
         1    1390       13.0         9.3      1,294,462
         2    1221       64.3         2.1        421,618
         3     835        5.5        23.8        958,719
  ⭐ RFM ni bazaviy natija sifatida qo'ying

Nima ko'rsatdi: 2.1-bo'lim.

Misol 2 — Mavzu modellashtirish

python
"""NMF va LDA bilan mavzularni ajratish (real sklearn)."""

import numpy as np
from sklearn.decomposition import NMF, LatentDirichletAllocation
from sklearn.feature_extraction.text import CountVectorizer, TfidfVectorizer
from sklearn.metrics import adjusted_rand_score


def hujjatlar_yarat(seed: int = 4, n: int = 900):
    """Uch mavzu: sport, iqtisod, texnologiya."""
    rng = np.random.default_rng(seed)
    lugat = {
        0: ["futbol", "musobaqa", "gol", "jamoa", "murabbiy", "chempionat",
            "stadion", "o'yinchi", "ochko", "final"],
        1: ["bank", "kredit", "investitsiya", "bozor", "narx", "eksport",
            "soliq", "byudjet", "valyuta", "foiz"],
        2: ["dastur", "server", "ma'lumot", "algoritm", "tarmoq", "qurilma",
            "protsessor", "kod", "bulut", "xavfsizlik"],
    }
    umumiy = ["yangi", "katta", "bugun", "yil", "kun", "holat", "natija"]
    matnlar, mavzular = [], []
    for _ in range(n):
        m = int(rng.integers(0, 3))
        uzunlik = int(rng.integers(30, 90))
        sozlar = list(rng.choice(lugat[m], int(uzunlik * 0.6)))
        sozlar += list(rng.choice(umumiy, int(uzunlik * 0.25)))
        # boshqa mavzudan biroz so'z (chalkashlik)
        boshqa = int(rng.choice([x for x in [0, 1, 2] if x != m]))
        sozlar += list(rng.choice(lugat[boshqa], int(uzunlik * 0.15)))
        rng.shuffle(sozlar)
        matnlar.append(" ".join(sozlar))
        mavzular.append(m)
    return matnlar, np.array(mavzular)


def eng_sozlar(komponent, nomlar, nechta: int = 6):
    return [nomlar[i] for i in komponent.argsort()[-nechta:][::-1]]


def main() -> None:
    matnlar, haqiqiy = hujjatlar_yarat()
    print("=== 1. Korpus ===")
    print(f"  {len(matnlar)} hujjat, {len(set(haqiqiy))} mavzu")
    print(f"  o'rtacha uzunlik: "
          f"{np.mean([len(m.split()) for m in matnlar]):.0f} so'z")

    print("\n=== 2. NMF (TF-IDF bilan) ===")
    tfidf = TfidfVectorizer(max_df=0.95, min_df=3)
    Xt = tfidf.fit_transform(matnlar)
    nomlar_t = tfidf.get_feature_names_out()
    nmf = NMF(n_components=3, init="nndsvda", max_iter=3000,
              tol=1e-5, random_state=0).fit(Xt)
    W = nmf.transform(Xt)
    for i, komp in enumerate(nmf.components_):
        print(f"  mavzu {i}: {eng_sozlar(komp, nomlar_t)}")
    print(f"  ARI: {adjusted_rand_score(haqiqiy, W.argmax(axis=1)):.4f}")

    print("\n=== 3. LDA (xom sanoq bilan) ===")
    sanoq = CountVectorizer(max_df=0.95, min_df=3)
    Xs = sanoq.fit_transform(matnlar)
    nomlar_s = sanoq.get_feature_names_out()
    lda = LatentDirichletAllocation(n_components=3, max_iter=30,
                                    learning_method="batch",
                                    random_state=0).fit(Xs)
    Wl = lda.transform(Xs)
    for i, komp in enumerate(lda.components_):
        print(f"  mavzu {i}: {eng_sozlar(komp, nomlar_s)}")
    print(f"  ARI: {adjusted_rand_score(haqiqiy, Wl.argmax(axis=1)):.4f}")

    print("\n=== 4. LDA ga TF-IDF berish xatosi ===")
    lda_tfidf = LatentDirichletAllocation(n_components=3, max_iter=30,
                                          learning_method="batch",
                                          random_state=0).fit(Xt)
    Wt = lda_tfidf.transform(Xt)
    print(f"  LDA + xom sanoq: ARI "
          f"{adjusted_rand_score(haqiqiy, Wl.argmax(axis=1)):.4f}")
    print(f"  LDA + TF-IDF:    ARI "
          f"{adjusted_rand_score(haqiqiy, Wt.argmax(axis=1)):.4f}")
    print(f"  NMF + TF-IDF:    ARI "
          f"{adjusted_rand_score(haqiqiy, W.argmax(axis=1)):.4f}")
    print(f"\n  mavzular soni bo'yicha NMF qayta tiklash xatosi:")
    for k in [2, 3, 5, 8]:
        m = NMF(n_components=k, init="nndsvda", max_iter=3000,
                tol=1e-5, random_state=0).fit(Xt)
        print(f"    k={k}: xato {m.reconstruction_err_:.4f}")
    print("  ⭐ LDA xom sanoq, NMF TF-IDF bilan ishlaydi")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Korpus ===
  900 hujjat, 3 mavzu
  o'rtacha uzunlik: 59 so'z

=== 2. NMF (TF-IDF bilan) ===
  mavzu 0: ['dastur', 'kod', 'server', 'tarmoq', 'bulut', 'qurilma']
  mavzu 1: ['gol', 'yinchi', 'futbol', 'ochko', 'musobaqa', 'final']
  mavzu 2: ['valyuta', 'eksport', 'bozor', 'investitsiya', 'byudjet', 'narx']
  ARI: 1.0000

=== 3. LDA (xom sanoq bilan) ===
  mavzu 0: ['byudjet', 'investitsiya', 'valyuta', 'soliq', 'bozor', 'eksport']
  mavzu 1: ['gol', 'final', 'ochko', 'jamoa', 'futbol', 'musobaqa']
  mavzu 2: ['dastur', 'ma', 'lumot', 'server', 'xavfsizlik', 'kod']
  ARI: 1.0000

=== 4. LDA ga TF-IDF berish xatosi ===
  LDA + xom sanoq: ARI 1.0000
  LDA + TF-IDF:    ARI 1.0000
  NMF + TF-IDF:    ARI 1.0000

  mavzular soni bo'yicha NMF qayta tiklash xatosi:
    k=2: xato 19.2817
    k=3: xato 14.6167
    k=5: xato 13.8872
    k=8: xato 12.9673
  ⭐ LDA xom sanoq, NMF TF-IDF bilan ishlaydi

Nima ko'rsatdi: 2.2-bo'lim.

Misol 3 — Tavsiya va siqish

python
"""Matritsani faktorlash va vektor kvantlash (real numpy/sklearn)."""

import numpy as np
from sklearn.cluster import KMeans
from sklearn.decomposition import PCA, TruncatedSVD
from sklearn.metrics import mean_squared_error


def reytinglar(seed: int = 6, n_foyd: int = 500, n_mahsulot: int = 200,
               n_omil: int = 5, zichlik: float = 0.05):
    """Yashirin omillarga asoslangan reytinglar."""
    rng = np.random.default_rng(seed)
    U = rng.normal(0, 1, (n_foyd, n_omil))
    V = rng.normal(0, 1, (n_mahsulot, n_omil))
    toliq = np.clip(3.0 + 0.7 * (U @ V.T), 1, 5)
    kuzatilgan = rng.random(toliq.shape) < zichlik
    return toliq, kuzatilgan


def main() -> None:
    toliq, kuzatilgan = reytinglar()
    print("=== 1. Reytinglar matritsasi ===")
    print(f"  {toliq.shape[0]} foydalanuvchi x {toliq.shape[1]} mahsulot")
    print(f"  kuzatilgan: {int(kuzatilgan.sum()):,} "
          f"({kuzatilgan.mean():.1%})")
    print(f"  reyting: o'rtacha {toliq[kuzatilgan].mean():.3f}, "
          f"std {toliq[kuzatilgan].std():.3f}")

    print("\n=== 2. Matritsani faktorlash ===")
    R = np.where(kuzatilgan, toliq, 0.0)
    ortacha = toliq[kuzatilgan].mean()
    Rm = np.where(kuzatilgan, toliq - ortacha, 0.0)
    sinov = ~kuzatilgan
    print(f"  {'omillar':>9} {'sinov RMSE':>12} {'bazaviy (o_rtacha)':>20}")
    bazaviy = np.sqrt(mean_squared_error(toliq[sinov],
                                         np.full(sinov.sum(), ortacha)))
    for k in [2, 5, 10, 20, 50]:
        svd = TruncatedSVD(n_components=k, random_state=0).fit(Rm)
        tiklangan = svd.transform(Rm) @ svd.components_ + ortacha
        rmse = np.sqrt(mean_squared_error(toliq[sinov], tiklangan[sinov]))
        print(f"  {k:>9} {rmse:>12.4f} {bazaviy:>20.4f}")

    print("\n=== 3. precision@k bilan baholash ===")
    svd = TruncatedSVD(n_components=10, random_state=0).fit(Rm)
    bashorat = svd.transform(Rm) @ svd.components_ + ortacha
    yoqqan = toliq >= 4.0            # "yoqdi" deb hisoblanadi
    natijalar = []
    for foyd in range(len(toliq)):
        nomzod = np.where(~kuzatilgan[foyd])[0]
        if len(nomzod) < 10 or yoqqan[foyd, nomzod].sum() == 0:
            continue
        tartib = nomzod[np.argsort(-bashorat[foyd, nomzod])]
        natijalar.append([yoqqan[foyd, tartib[:k]].mean() for k in [5, 10, 20]])
    natijalar = np.array(natijalar)
    asosiy = yoqqan[~kuzatilgan].mean()
    print(f"  tasodifiy tavsiya (bazaviy): {asosiy:.4f}")
    for i, k in enumerate([5, 10, 20]):
        print(f"  precision@{k}: {natijalar[:, i].mean():.4f} "
              f"({natijalar[:, i].mean() / asosiy:.2f}x bazaviydan)")

    print("\n=== 4. Vektor kvantlash bilan siqish ===")
    rng = np.random.default_rng(0)
    # "tasvir": 200x200 piksel, RGB
    balandlik, kenglik = 200, 200
    asos = rng.normal(0, 1, (6, 3))
    hudud = rng.integers(0, 6, (balandlik, kenglik))
    tasvir = np.clip(128 + 60 * asos[hudud] + rng.normal(0, 12,
                                                         (balandlik, kenglik, 3)),
                     0, 255)
    piksel = tasvir.reshape(-1, 3)
    print(f"  tasvir: {balandlik}x{kenglik}, "
          f"{len(np.unique(piksel.round().astype(int), axis=0)):,} noyob rang")
    print(f"  {'ranglar':>9} {'MSE':>10} {'hajm (KB)':>12} {'siqish':>9}")
    asl_hajm = piksel.size * 1 / 1024        # 1 bayt/kanal
    for k in [2, 4, 8, 16, 32]:
        km = KMeans(k, n_init=3, random_state=0).fit(piksel)
        siqilgan = km.cluster_centers_[km.labels_]
        bit = int(np.ceil(np.log2(k)))
        hajm = (len(piksel) * bit / 8 + k * 3) / 1024
        print(f"  {k:>9} {mean_squared_error(piksel, siqilgan):>10.2f} "
              f"{hajm:>12.1f} {asl_hajm / hajm:>8.1f}x")
    print(f"  asl hajm: {asl_hajm:.1f} KB")
    print("  ⭐ K-means - klassik siqish algoritmi")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Reytinglar matritsasi ===
  500 foydalanuvchi x 200 mahsulot
  kuzatilgan: 4,932 (4.9%)
  reyting: o'rtacha 2.982, std 1.215

=== 2. Matritsani faktorlash ===
    omillar   sinov RMSE   bazaviy (o_rtacha)
          2       1.2036               1.2249
          5       1.1867               1.2249
         10       1.1887               1.2249
         20       1.1950               1.2249
         50       1.2078               1.2249

=== 3. precision@k bilan baholash ===
  tasodifiy tavsiya (bazaviy): 0.2347
  precision@5: 0.6320 (2.69x bazaviydan)
  precision@10: 0.5900 (2.51x bazaviydan)
  precision@20: 0.5457 (2.33x bazaviydan)

=== 4. Vektor kvantlash bilan siqish ===
  tasvir: 200x200, 34,969 noyob rang
    ranglar        MSE    hajm (KB)    siqish
          2    1154.81          4.9     24.0x
          4     202.55          9.8     12.0x
          8     118.35         14.7      8.0x
         16      83.98         19.6      6.0x
         32      53.90         24.5      4.8x
  asl hajm: 117.2 KB
  ⭐ K-means - klassik siqish algoritmi

Nima ko'rsatdi: 2.3, 2.4-bo'limlar.

Misol 4 — Belgi muhandisligi

python
"""Nazoratsiz usullar nazoratli modelga hissa qo'shadi (real sklearn)."""

import numpy as np
from sklearn.base import BaseEstimator, TransformerMixin
from sklearn.cluster import KMeans
from sklearn.decomposition import PCA
from sklearn.ensemble import HistGradientBoostingClassifier, IsolationForest
from sklearn.metrics import roc_auc_score
from sklearn.model_selection import StratifiedKFold, cross_val_score, train_test_split
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler


class KlasterBelgilari(BaseEstimator, TransformerMixin):
    """Klaster markazlarigacha masofalarni belgi sifatida qo'shadi."""

    def __init__(self, n_clusters: int = 8, random_state: int = 0):
        self.n_clusters = n_clusters
        self.random_state = random_state

    def fit(self, X, y=None):
        self.sc_ = StandardScaler().fit(X)
        self.km_ = KMeans(self.n_clusters, n_init=10,
                          random_state=self.random_state).fit(
                              self.sc_.transform(X))
        return self

    def transform(self, X):
        Xs = self.sc_.transform(X)
        return np.column_stack([X, self.km_.transform(Xs)])


def yarat(seed: int = 15, n: int = 5000, p: int = 10):
    """Sinf klasterlar ichida joylashgan - klaster belgisi foydali."""
    rng = np.random.default_rng(seed)
    guruh = rng.integers(0, 5, n)
    markazlar = rng.normal(0, 3.0, (5, p))
    X = markazlar[guruh] + rng.normal(0, 1.0, (n, p))
    # har guruhda o'z qoidasi
    kuch = np.zeros(n)
    for g in range(5):
        m = guruh == g
        belgi = g % p
        kuch[m] = (1.0 if g % 2 == 0 else -1.0) * X[m, belgi]
    y = (rng.random(n) < 1 / (1 + np.exp(-kuch))).astype(int)
    return X, y


def main() -> None:
    X, y = yarat()
    Xtr, Xte, ytr, yte = train_test_split(X, y, test_size=0.3, random_state=0,
                                          stratify=y)
    cv = StratifiedKFold(4, shuffle=True, random_state=0)

    def model():
        return HistGradientBoostingClassifier(learning_rate=0.1, max_iter=200,
                                              random_state=0)

    print("=== 1. Bazaviy model ===")
    asos = model().fit(Xtr, ytr)
    a0 = roc_auc_score(yte, asos.predict_proba(Xte)[:, 1])
    cv0 = cross_val_score(model(), Xtr, ytr, cv=cv, scoring="roc_auc").mean()
    print(f"  CV ROC AUC {cv0:.4f}, test {a0:.4f}")

    print("\n=== 2. Klaster masofalari belgisi ===")
    print(f"  {'klasterlar':>11} {'CV ROC AUC':>12} {'test':>9}")
    for k in [3, 5, 8, 15]:
        quvur = Pipeline([("kb", KlasterBelgilari(k)), ("m", model())])
        cvb = cross_val_score(quvur, Xtr, ytr, cv=cv, scoring="roc_auc").mean()
        quvur.fit(Xtr, ytr)
        a = roc_auc_score(yte, quvur.predict_proba(Xte)[:, 1])
        print(f"  {k:>11} {cvb:>12.4f} {a:>9.4f}")

    print("\n=== 3. PCA va anomaliya bali belgilari ===")

    class QoshimchaBelgilar(BaseEstimator, TransformerMixin):
        def __init__(self, pca_k: int = 3):
            self.pca_k = pca_k

        def fit(self, X, y=None):
            self.sc_ = StandardScaler().fit(X)
            self.p_ = PCA(self.pca_k, random_state=0).fit(self.sc_.transform(X))
            self.izo_ = IsolationForest(n_estimators=100, random_state=0,
                                        n_jobs=1).fit(X)
            return self

        def transform(self, X):
            return np.column_stack([X, self.p_.transform(self.sc_.transform(X)),
                                    -self.izo_.score_samples(X)])

    quvur = Pipeline([("qb", QoshimchaBelgilar(3)), ("m", model())])
    cvb = cross_val_score(quvur, Xtr, ytr, cv=cv, scoring="roc_auc").mean()
    quvur.fit(Xtr, ytr)
    print(f"  PCA(3) + anomaliya bali: CV {cvb:.4f}, "
          f"test {roc_auc_score(yte, quvur.predict_proba(Xte)[:, 1]):.4f}")

    print("\n=== 4. Leakage: Pipeline ichida va tashqarisida ===")
    # NOTO'G'RI: klasterlash butun ma'lumotda
    sc_hammasi = StandardScaler().fit(X)
    km_hammasi = KMeans(8, n_init=10, random_state=0).fit(
        sc_hammasi.transform(X))
    X_notogri = np.column_stack([X, km_hammasi.transform(sc_hammasi.transform(X))])
    Xn_tr = X_notogri[:len(Xtr)]
    cv_notogri = cross_val_score(model(), Xn_tr, ytr, cv=cv,
                                 scoring="roc_auc").mean()
    # TO'G'RI: Pipeline ichida
    quvur = Pipeline([("kb", KlasterBelgilari(8)), ("m", model())])
    cv_togri = cross_val_score(quvur, Xtr, ytr, cv=cv,
                               scoring="roc_auc").mean()
    print(f"  butun ma'lumotda klasterlab (noto'g'ri): CV {cv_notogri:.4f}")
    print(f"  Pipeline ichida (to'g'ri):               CV {cv_togri:.4f}")
    print(f"  farq: {cv_notogri - cv_togri:+.4f}")
    print("  (bu vazifada klasterlash y ni ko'rmaydi, shuning uchun farq kichik;")
    print("   lekin target encoding kabi usullarda u katta bo'ladi - 12.9)")
    print("  ⭐ Nazoratsiz belgilarni Pipeline ichida yarating")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Bazaviy model ===
  CV ROC AUC 0.9218, test 0.9128

=== 2. Klaster masofalari belgisi ===
   klasterlar   CV ROC AUC      test
            3       0.9217    0.9121
            5       0.9217    0.9087
            8       0.9227    0.9096
           15       0.9240    0.9117

=== 3. PCA va anomaliya bali belgilari ===
  PCA(3) + anomaliya bali: CV 0.9217, test 0.9088

=== 4. Leakage: Pipeline ichida va tashqarisida ===
  butun ma'lumotda klasterlab (noto'g'ri): CV 0.4806
  Pipeline ichida (to'g'ri):               CV 0.9227
  farq: -0.4421
  (bu vazifada klasterlash y ni ko'rmaydi, shuning uchun farq kichik;
   lekin target encoding kabi usullarda u katta bo'ladi - 12.9)
  ⭐ Nazoratsiz belgilarni Pipeline ichida yarating

Nima ko'rsatdi: 2.5-bo'lim.


5. To'g'ri va noto'g'ri tushunishlar

Noto'g'ri fikr To'g'risi
"Klasterlash RFM dan yaxshiroq" Tekshiring, ko'pincha teng
"LDA ga TF-IDF beriladi" Xom sanoq
"NMF va LDA bir xil" Turli model, turli kirish
"Tavsiyani accuracy bilan baholash" precision@k, NDCG
"Matritsani faktorlash = tavsiya tizimi" Faqat yadro
"Klaster belgisi har doim foydali" CV bilan tekshiring
"Belgini oldindan hisoblash mumkin" Pipeline ichida
"Siqishda faqat hajm muhim" Xato ham

6. Keng tarqalgan xatolar va yechimlari

1. Bazaviy natijasiz segmentatsiya

python
KMeans(5).fit(Xs)               # RFM bilan solishtirilmagan     # ⚠️
# avval RFM, keyin klasterlash, keyin taqqoslash                 # ✅

2. LDA ga TF-IDF

python
LatentDirichletAllocation(20).fit(tfidf_matritsa)                # ⚠️
LatentDirichletAllocation(20).fit(count_matritsa)                # ✅

3. Summalarni transformatsiyasiz klasterlash

python
KMeans(4).fit(StandardScaler().fit_transform(df[["monetary"]]))  # ⚠️
KMeans(4).fit(StandardScaler().fit_transform(np.log1p(df[["monetary"]]))) # ✅

4. Tavsiyani RMSE bilan baholash

python
mean_squared_error(haqiqiy, bashorat)    # foydalanuvchi ko'rmaydi # ⚠️
# precision@10, recall@10, NDCG                                   # ✅

5. Belgini Pipeline tashqarisida

python
X["klaster"] = KMeans(8).fit_predict(Xs); cross_val_score(m, X, y) # ⚠️
Pipeline([("kb", KlasterBelgilari(8)), ("m", model)])              # ✅

6. Siqishda xatoni o'lchamaslik

python
# "32 rangga tushirdik, 5x siqildi"                               # ⚠️
# MSE/PSNR ni ham keltiring                                       # ✅

7. Mavzular sonini tasodifan tanlash

python
NMF(n_components=20)            # 20 qayerdan?                    # ⚠️
# reconstruction_err_ + koherentlik + muharrir bahosi             # ✅

7. Integratsiya — bu bilim qayerda kerak bo'ladi

  • 16.2-dars (o'tilgan): K-means
  • 16.8-dars (o'tilgan): PCA
  • 16.10-dars (o'tilgan): Anomaliya
  • 12.9-dars (o'tilgan): Leakage
  • 16.12-dars: Amaliyot

8. Eng yaxshi amaliyotlar

  1. Mezonni algoritmdan oldin tanlang.

  2. Bazaviy natija quring (RFM).

  3. Qiyshiq summalarni log1p qiling.

  4. LDA ga xom sanoq bering.

  5. Tavsiyani precision@k bilan baholang.

  6. Belgilarni Pipeline ichida yarating.

  7. Siqishda xatoni ham keltiring.

  8. Mavzularni odamga tekshirtiring.


9. Amaliy topshiriq

Vazifa 1: Bashorat qiling

python
1.  # RFM nimani anglatadi?
2.  # segmentatsiyada nechta segment?
3.  # summalarni qanday transformatsiya qilish kerak?
4.  # NMF ning afzalligi?
5.  # NMF qaysi kirish bilan?
6.  # LDA qaysi kirish bilan?
7.  # tavsiyada matritsa qanday ajratiladi?
8.  # tavsiya metrikasi?
9.  # vektor kvantlash nima?
10. # klaster belgisi qanday qo'shiladi?
11. # leakage dan qanday saqlanish kerak?
12. # mezon qachon tanlanadi?
Javoblar
  1. Recency, Frequency, Monetary
  2. 3-7
  3. log1p
  4. Manfiy emas, talqin oson
  5. TF-IDF
  6. Xom sanoq
  7. U @ V^T
  8. precision@k, NDCG
  9. Nuqtani markaz bilan almashtirish
  10. Yorliq yoki masofalar
  11. Pipeline ichida
  12. Algoritmdan oldin

Vazifa 2: Xatolarni tuzating

python
1.  LatentDirichletAllocation(20).fit(tfidf_matritsa)

2.  KMeans(4).fit(StandardScaler().fit_transform(df[["monetary"]]))

3.  mean_squared_error(haqiqiy, bashorat)   # tavsiya

4.  X["klaster"] = KMeans(8).fit_predict(Xs); cross_val_score(m, X, y)

5.  NMF(n_components=20)   # 20 qayerdan?
Javoblar
python
1.  LatentDirichletAllocation(20).fit(count_matritsa)

2.  KMeans(4).fit(StandardScaler().fit_transform(np.log1p(df[["monetary"]])))

3.  # precision@10, recall@10, NDCG

4.  Pipeline([("kb", KlasterBelgilari(8)), ("m", model)])

5.  # reconstruction_err_ + koherentlik + muharrir bahosi

Vazifa 3: Segmentatsiya

Modellang:

  1. Ma'lumot
  2. RFM
  3. Klasterlash
  4. Taqqoslash

Vazifa 4: Mavzular

Modellang:

  1. Korpus
  2. NMF
  3. LDA
  4. Kirish turi

Vazifa 5: Tavsiya va siqish

Modellang:

  1. Reytinglar
  2. Faktorlash
  3. precision@k
  4. Kvantlash

Vazifa 6: Belgi muhandisligi

Modellang:

  1. Bazaviy
  2. Klaster masofalari
  3. PCA va anomaliya
  4. Leakage

Vazifa 7: O'ylash

Mavzu modellashtirish 20 ta mavzu berdi. Ulardan 14 tasi tushunarli, 6 tasi esa tasodifiy so'zlar aralashmasi. Nima qilish kerak?

Javob

Qisqa javob: bu normal holat — mavzu modellari har doim bir nechta "axlat" mavzu beradi. Uch yo'l bor: mavzular sonini kamaytirish, matn tayyorlashni yaxshilash yoki yomon mavzularni qabul qilib, ularni ishlatmaslik.

1. Nega "axlat" mavzular paydo bo'ladi

Sabab Belgi
k juda katta Mavzular bo'linib ketgan
Tayyorlash yetarli emas Stop-so'zlar, raqamlar, xatolar
Korpus bir xil emas Turli janr, til, uzunlik
Kam uchraydigan hujjatlar "Qolgan hammasi" mavzusi

2. Tayyorlashni yaxshilash (odatda eng ko'p foyda)

  1. min_df=5 — juda kam uchraydigan so'zlarni chiqarish
  2. max_df=0.9 — deyarli hamma joyda uchraydigan so'zlarni chiqarish
  3. Stop-so'zlar ro'yxati (o'zbek tili uchun alohida)
  4. Lemmatizatsiya yoki stemming
  5. Juda qisqa hujjatlarni chiqarish

3. k ni to'g'ri tanlash

1. k ni 5 dan 40 gacha oshirib, reconstruction_err_ ni chizing
2. Har k uchun 5-10 ta mavzuni muharrirga ko'rsating
3. "Tushunarli mavzular ulushi" ni o'lchang
4. Shu ulush tushib ketadigan nuqtadan oldingi k ni oling

Koherentlik metrikasi (c_v, u_mass) avtomatlashtiradi, lekin odam bahosi ishonchliroq.

4. Yomon mavzularni boshqarish

  • Ularni belgilab qo'ying ("aralash", "tasnifsiz")
  • Shu mavzuga tushgan hujjatlarni qo'lda ko'rib chiqing
  • Ko'pincha ular haqiqatan aralash hujjatlar bo'lib chiqadi
  • 70-80% tushunarli mavzu — yaxshi natija

5. Xulosa

  1. Axlat mavzular normal
  2. Avval tayyorlashni yaxshilang
  3. k ni tushunarlilik bo'yicha tanlang
  4. Qolganini belgilab, alohida ko'rib chiqing

Nimani mustahkamlaydi: 2.2, 2.6-bo'limlar.


Xulosa

Bu darsda nazoratsiz o'rganishning qo'llanilishini o'rgandik.

Eng muhim uch fikr:

  1. Mezonni algoritmdan oldin tanlang. Segmentatsiyada mezon — konversiya va tushunarlilik, mavzu modellashtirish — koherentlik, tavsiya — precision@k, siqish — hajm va xato muvozanati. Mezonni keyin tanlasangiz, "chiroyli" natijani tanlaysiz, foydalisini emas. Segmentatsiyada RFM ni bazaviy natija qiling: klasterlash undan sezilarli yaxshi bo'lmasa, soddaroq usulni oling.

  2. Kirish turi modelga mos bo'lishi kerak. NMF — TF-IDF bilan (manfiy emas, talqin oson, tez), LDA — xom sanoq bilan (ehtimollik modeli). LDA ga TF-IDF berish — keng tarqalgan va jim xato: model ishlaydi, lekin natija yomonroq bo'ladi. Qiyshiq summalarni esa klasterlashdan oldin log1p bilan transformatsiya qiling.

  3. Nazoratsiz belgilarni Pipeline ichida yarating. Klaster yorlig'i, markazlargacha masofalar, PCA komponentlari va anomaliya bali — bularning hammasi nazoratli model uchun foydali belgi bo'lishi mumkin. Lekin ularni butun ma'lumotda oldindan hisoblab, keyin CV qilish — leakage 12.9-bob. TransformerMixin bilan o'z transformeringizni yozib, uni Pipeline ga qo'ying.

Keyingi darsda amaliyot: 16-qism bo'yicha to'liq loyiha.

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
16.11-dars: Qo'llanilishi — IlmHamroh