IlmHamroh
Data Science va sun'iy intellekt/PyTorch10/12-dars22 daqiqa
Mundarija (21)

21.10-dars: Tabular ma'lumot va embedding

21-QISM — PYTORCH · 10-dars


1. Kirish va motivatsiya

20.12-darsda ko'rdik: tabular ma'lumotda tarmoq avtomatik tanlov emas, HistGradientBoosting ko'pincha undan yaxshi. Lekin bitta holat bor, bu yerda tarmoq o'z kuchini ko'rsata oladi: ko'p darajali kategoriyalar.

Mahsulot kodi (50 000 xil), foydalanuvchi ID si, pochta indeksi, do'kon raqami — bunday belgilarni one-hot bilan kodlash 50 000 ustunli siyrak matritsa beradi. target encoding (17-qism) ularni bitta songa siqadi va ma'lumotni yo'qotadi. Embedding esa har kategoriyaga kichik o'rganiladigan vektor beradi: 50 000 kategoriya × 16 o'lchov. Bu vektorlar o'rgatish davomida shunday joylashadiki, o'xshash ta'sirli kategoriyalar yaqin bo'lib qoladi.

nn.Embedding — texnik jihatdan juda oddiy narsa: bu indeks bo'yicha satr oladigan jadval. Embedding(n, d) ning og'irligi (n, d) matritsa, emb(torch.tensor([5])) esa uning 5-satri. Lekin shu oddiy vosita tarmoqqa kategoriyalar orasidagi o'xshashlikni o'zi topish imkonini beradi.

Bu darsda Embedding ni ichidan ko'ramiz, uni one-hot bilan taqqoslaymiz, son va kategoriyali belgilarni bitta modelda birlashtiramiz va natijani HistGradientBoosting bilan halol solishtiramiz.

Real vaziyat. Chakana savdo kompaniyasi 3 000 do'kon uchun sotuvni bashorat qilardi. one-hot bilan model 3 000 ustunli bo'ldi va kam sotuvli do'konlarda yomon ishladi. Do'konlarga 8 o'lchovli embedding berilgach, model o'xshash do'konlarni o'zi guruhladi — va kam ma'lumotli do'konlar "qo'shnilari" dan foydalana boshladi.

Bu darsda kategoriyalar uchun embedding ni o'rganamiz.

Bu darsda:

  • nn.Embedding nima
  • Embedding va one-hot
  • Son va kategoriyali belgilarni birlashtirish
  • Embedding o'lchamini tanlash
  • Noma'lum kategoriya
  • Tuzoqlar
  • Amaliy: tabular tarmoq

ℹ Misollar real torch/sklearn bilan (Python 3.14, torch 2.14 CPU).


2. Nazariya — chuqur tushuntirish

2.1. Embedding nima

text
nn.Embedding(n_kategoriya, d)
  og'irlik: (n_kategoriya, d) matritsa
  kirish:   butun sonlar (indekslar), shakl (B,) yoki (B, L)
  chiqish:  (B, d) yoki (B, L, d)

  emb(torch.tensor([3, 0, 3]))  ->  og'irlikning 3-, 0-, 3-satrlari

MATEMATIK JIHATDAN:
  embedding = one_hot(i) @ W
  lekin one-hot matritsa HECH QACHON quriladi - to'g'ridan-to'g'ri satr
  -> xotira va hisob tejaladi

GRADIENT:
  faqat batchda UCHRAGAN satrlar yangilanadi
  (siyrak gradient)

KIRISH TURI: torch.long (int64) - float EMAS

Embedding — one_hot @ W ning tejamkor ko'rinishi; natija bir xil, xarajat keskin kam.

2.2. Embedding va one-hot

text
                   ONE-HOT              EMBEDDING
o'lcham            n ustun              d ustun (d << n)
parametrlar        n * yashirin         n * d  (+ d * yashirin)
o'xshashlik        yo'q (hamma teng)    O'RGANILADI
yangi kategoriya   hamma nol            maxsus indeks 2.5-bob
kam uchraydigan    o'z og'irligi, kam   qo'shnilarga yaqinlashadi
                   ma'lumot bilan

QACHON ONE-HOT:
  kategoriya kam (< 10-20)
  chiziqli model / daraxt

QACHON EMBEDDING:
  kategoriya ko'p (> 50)
  tarmoq
  kategoriyalar orasida yashirin o'xshashlik bor

Kam kategoriyada one-hot, ko'pida embedding — chegara taxminan 20-50 daraja.

2.3. Son va kategoriyani birlashtirish

text
class TabularTarmoq(nn.Module):
    def __init__(self, n_son, kat_hajmlari, d_emb):
        self.embler = nn.ModuleList(
            [nn.Embedding(n, d) for n, d in zip(kat_hajmlari, d_emb)])
        self.tarmoq = nn.Sequential(
            nn.Linear(n_son + sum(d_emb), 128), ...)

    def forward(self, son, kat):         # kat: (B, n_kat) long
        e = [emb(kat[:, i]) for i, emb in enumerate(self.embler)]
        return self.tarmoq(torch.cat([son] + e, dim=1))

TARTIB:
  son belgilar - masshtablangan (StandardScaler)
  kategoriyalar - 0..n-1 indekslarga aylantirilgan
  embedding chiqishlari son belgilar bilan CONCAT

Har kategoriyali ustunga o'z Embedding, keyin hammasi son belgilar bilan birlashtiriladi.

2.4. Embedding o'lchamini tanlash

text
EMPIRIK QOIDALAR:
  d = min(50, (n + 1) // 2)                    (fast.ai)
  d = round(1.6 * n ** 0.56)                   (fast.ai yangi)

AMALDA:
  n = 10    -> d = 4..5
  n = 100   -> d = 8..16
  n = 10000 -> d = 32..64

KATTA d:
  ko'proq parametr, yodlash xavfi (kam uchraydigan kategoriyalarda)
KICHIK d:
  kategoriyalar orasidagi farqni ifodalay olmaydi

QOIDA: formulani boshlang'ich nuqta sifatida oling, validatsiyada tekshiring

d — giperparametr; formulalar faqat boshlang'ich nuqta.

2.5. Noma'lum va kam uchraydigan kategoriyalar

text
MUAMMO: o'quvda ko'rilmagan kategoriya -> indeks yo'q -> IndexError

YECHIM: maxsus "noma'lum" indeksi
  kodlash: {kat: i + 1 for i, kat in enumerate(oquv_kategoriyalar)}
  noma'lum -> 0
  nn.Embedding(n + 1, d)

KAM UCHRAYDIGAN KATEGORIYALAR:
  5 martadan kam uchraganlarni ham 0 ga (yoki "boshqa" ga) birlashtirish
  -> embedding ma'lumotsiz satrlarni yodlamaydi

KODLASH FAQAT O'QUV TO'PLAMIDA:
  lug'at o'quvda quriladi, val/test da faqat qo'llanadi
  (aks holda leakage - 17-qism)

padding_idx=0: shu indeksning gradienti nol, vektori o'zgarmaydi
  (ketma-ketliklarda padding uchun - 25-qism)

Indeks 0 — "noma'lum" uchun; lug'at faqat o'quv to'plamida quriladi.

2.6. Halol taqqoslash

text
TABULAR + KO'P KATEGORIYA - TARMOQ UCHUN ENG QULAY HOLAT
  lekin baribir bazaviy kerak:

HistGradientBoosting(categorical_features=[...])
  kategoriyalarni o'zi qayta ishlaydi (ichki kodlash)
  max 255 daraja (ko'pida - target encoding yoki ordinal)

TAQQOSLASH:
  bir xil bo'linish, bir xil metrika
  bir necha seed (tarmoq uchun)
  vaqt va murakkablik ham hisobga olinadi

KO'PINCHA NATIJA:
  farq kichik, ba'zan boosting, ba'zan tarmoq ustun
  TARMOQNING QO'SHIMCHA FOYDASI: embedding vektorlarining o'zi
  (qidiruv, klasterlash, boshqa modelga belgi sifatida)

Embedding ning qo'shimcha qiymati — vektorlarning o'zi; ular boshqa vazifalarda ham ishlatiladi.

2.7. Tuzoqlar

Asosiy tuzoqlar: embedding ga float indeks berish; noma'lum kategoriya uchun joy qoldirmaslik; lug'atni butun ma'lumotda qurish (leakage); kam uchraydigan kategoriyalarni alohida qoldirib, yodlashga yo'l qo'yish; d ni juda katta tanlash; son belgilarni masshtablamaslik; boosting bazaviysiz tarmoqni "g'olib" deb e'lon qilish; kategoriya indekslariga son sifatida qarash (nn.Linear ga to'g'ridan-to'g'ri).


3. Tez ma'lumotnoma

python
import torch
import torch.nn as nn


def lugat_qur(qiymatlar, min_soni=5):
    """0 - noma'lum/kam uchraydigan uchun ajratilgan."""
    uniq, soni = np.unique(qiymatlar, return_counts=True)
    return {k: i + 1 for i, k in enumerate(uniq[soni >= min_soni])}


def kodla(qiymatlar, lugat):
    return np.array([lugat.get(q, 0) for q in qiymatlar])


class TabularTarmoq(nn.Module):
    def __init__(self, n_son, kat_hajmlari, d_emb, yashirin=128):
        super().__init__()
        self.embler = nn.ModuleList(
            [nn.Embedding(n, d) for n, d in zip(kat_hajmlari, d_emb)])
        self.tarmoq = nn.Sequential(
            nn.Linear(n_son + sum(d_emb), yashirin), nn.ReLU(),
            nn.Linear(yashirin, 1))

    def forward(self, son, kat):
        e = [emb(kat[:, i]) for i, emb in enumerate(self.embler)]
        return self.tarmoq(torch.cat([son] + e, dim=1)).squeeze(-1)

Embedding xulosasi

Embedding(n, d): indeks -> satr, one_hot @ W bilan teng
kirish: long; noma'lum -> 0
har kat ustunga o'z Embedding, keyin concat
d ~ min(50, (n+1)//2) boshlang'ich nuqta
bazaviy: HistGradientBoosting(categorical_features=...)

4. Batafsil misollar

Misollar real torch/sklearn bilan (Python 3.14, torch 2.14 CPU).

Misol 1 — Embedding ichidan

python
"""Embedding = one_hot @ W, siyrak gradient (real torch)."""

import torch
import torch.nn as nn


def main() -> None:
    torch.manual_seed(0)
    n, d = 6, 3
    emb = nn.Embedding(n, d)

    print("=== 1. Og'irlik jadvali ===")
    print(f"  shakl: {tuple(emb.weight.shape)}  (kategoriya, o'lcham)")
    for i in range(n):
        print(f"  satr {i}: {emb.weight[i].detach().numpy().round(4)}")

    print("\n=== 2. Indeks bo'yicha satr olish ===")
    idx = torch.tensor([3, 0, 3])
    chiqish = emb(idx)
    print(f"  emb([3, 0, 3]) shakli: {tuple(chiqish.shape)}")
    print(f"  0-chiqish == 3-satr: {torch.equal(chiqish[0], emb.weight[3])}")
    print(f"  2-chiqish == 3-satr: {torch.equal(chiqish[2], emb.weight[3])}")

    print("\n=== 3. one_hot @ W bilan tenglik ===")
    oh = nn.functional.one_hot(idx, n).float()
    print(f"  one-hot shakli: {tuple(oh.shape)}")
    print(f"  one_hot @ W == emb(idx): "
          f"{torch.allclose(oh @ emb.weight, chiqish)}")
    print("  natija bir xil, lekin embedding one-hot matritsani QURMAYDI")

    print("\n=== 4. Siyrak gradient ===")
    emb.zero_grad()
    emb(torch.tensor([1, 4, 4])).sum().backward()
    g = emb.weight.grad
    print(f"  {'satr':>5} {'grad normasi':>14}")
    for i in range(n):
        print(f"  {i:>5} {g[i].norm().item():>14.4f}")
    print("  faqat batchda uchragan satrlar (1 va 4) gradient oldi")
    print("  4-satr ikki marta uchragani uchun gradienti ikki barobar")

    print("\n=== 5. Kirish turi ===")
    try:
        emb(torch.tensor([1.0, 2.0]))
        print("  float indeks ishladi (kutilmagan)")
    except RuntimeError as xato:
        print(f"  float indeks: RuntimeError - {str(xato)[:44]}")
    try:
        emb(torch.tensor([6]))
        print("  indeks 6 ishladi (kutilmagan)")
    except IndexError as xato:
        print(f"  indeks 6 (n=6): IndexError - {str(xato)[:40]}")

    print("\n=== 6. Parametrlar: embedding va one-hot + Linear ===")
    print(f"  {'n':>7} {'d':>4} {'one-hot + Linear(n,64)':>24} "
          f"{'Emb(n,d) + Linear(d,64)':>26}")
    for n_, d_ in [(10, 4), (1000, 16), (50000, 32)]:
        oh_param = n_ * 64 + 64
        emb_param = n_ * d_ + d_ * 64 + 64
        print(f"  {n_:>7} {d_:>4} {oh_param:>24} {emb_param:>26}")
    print("  ⭐ Ko'p kategoriyada embedding kamroq parametr bilan ishlaydi")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Og'irlik jadvali ===
  shakl: (6, 3)  (kategoriya, o'lcham)
  satr 0: [-1.1258 -1.1524  0.5667]
  satr 1: [ 0.7935  0.5988 -1.5551]
  satr 2: [-0.3414  1.853   0.4681]
  satr 3: [-0.1577 -0.1734  0.1835]
  satr 4: [1.3894 1.5863 0.9463]
  satr 5: [-0.8437  0.9318  1.259 ]

=== 2. Indeks bo'yicha satr olish ===
  emb([3, 0, 3]) shakli: (3, 3)
  0-chiqish == 3-satr: True
  2-chiqish == 3-satr: True

=== 3. one_hot @ W bilan tenglik ===
  one-hot shakli: (3, 6)
  one_hot @ W == emb(idx): True
  natija bir xil, lekin embedding one-hot matritsani QURMAYDI

=== 4. Siyrak gradient ===
   satr   grad normasi
      0         0.0000
      1         1.7321
      2         0.0000
      3         0.0000
      4         3.4641
      5         0.0000
  faqat batchda uchragan satrlar (1 va 4) gradient oldi
  4-satr ikki marta uchragani uchun gradienti ikki barobar

=== 5. Kirish turi ===
  float indeks: RuntimeError - Expected tensor for argument #1 'indices' to
  indeks 6 (n=6): IndexError - index out of range in self

=== 6. Parametrlar: embedding va one-hot + Linear ===
        n    d   one-hot + Linear(n,64)    Emb(n,d) + Linear(d,64)
       10    4                      704                        360
     1000   16                    64064                      17088
    50000   32                  3200064                    1602112
  ⭐ Ko'p kategoriyada embedding kamroq parametr bilan ishlaydi

Nima ko'rsatdi: 2.1, 2.2-bo'limlar.

Misol 2 — Lug'at, noma'lum va kam uchraydigan kategoriyalar

python
"""Kategoriyalarni indeksga aylantirish - faqat o'quv to'plamida."""

import numpy as np
import torch
import torch.nn as nn


def lugat_qur(qiymatlar, min_soni=1):
    uniq, soni = np.unique(qiymatlar, return_counts=True)
    return {k: i + 1 for i, k in enumerate(uniq[soni >= min_soni])}


def kodla(qiymatlar, lugat):
    return np.array([lugat.get(q, 0) for q in qiymatlar], dtype=np.int64)


def main() -> None:
    oquv = np.array(["A", "B", "A", "C", "A", "B", "D", "A", "B", "C"])
    test = np.array(["A", "E", "C", "F", "B"])

    print("=== 1. Lug'at faqat o'quvda ===")
    lugat = lugat_qur(oquv)
    print(f"  lug'at: {lugat}")
    print(f"  o'quv kodlari: {kodla(oquv, lugat).tolist()}")
    print(f"  test kodlari:  {kodla(test, lugat).tolist()}")
    print("  E va F o'quvda yo'q -> 0 (noma'lum)")

    print("\n=== 2. Embedding n + 1 bilan ===")
    torch.manual_seed(0)
    emb = nn.Embedding(len(lugat) + 1, 3)
    print(f"  Embedding({len(lugat) + 1}, 3) - 0-satr noma'lum uchun")
    chiqish = emb(torch.tensor(kodla(test, lugat)))
    print(f"  test chiqishi shakli: {tuple(chiqish.shape)}")
    print(f"  E va F bir xil vektor oldi: "
          f"{torch.equal(chiqish[1], chiqish[3])}")

    print("\n=== 3. 0-indekssiz nima bo'ladi ===")
    notogri = {k: i for i, k in enumerate(np.unique(oquv))}
    emb2 = nn.Embedding(len(notogri), 3)
    try:
        emb2(torch.tensor([notogri[q] for q in test]))
        print("  xato chiqmadi (kutilmagan)")
    except KeyError as xato:
        print(f"  KeyError: {xato} - test da yangi kategoriya")

    print("\n=== 4. Kam uchraydigan kategoriyalar ===")
    rng = np.random.default_rng(0)
    chastota = rng.zipf(1.6, 2000)
    chastota = chastota[chastota < 400]
    uniq, soni = np.unique(chastota, return_counts=True)
    print(f"  noyob kategoriyalar: {len(uniq)}")
    for m in [1, 3, 5, 10]:
        print(f"  {m:>2} martadan kam uchraganlar: "
              f"{int((soni < m).sum()):>4} ta "
              f"({int(soni[soni < m].sum())} qator)")
    lug5 = lugat_qur(chastota, min_soni=5)
    kod = kodla(chastota, lug5)
    print(f"  min_soni=5 bilan lug'at: {len(lug5)} + 1 (noma'lum)")
    print(f"  0 ga tushgan qatorlar: {(kod == 0).mean():.1%}")
    print("  kam ma'lumotli satrlar yodlanmaydi - ular umumiy 0 ni oladi")

    print("\n=== 5. Butun ma'lumotda lug'at qurish - leakage ===")
    hammasi = np.concatenate([oquv, test])
    lug_xato = lugat_qur(hammasi)
    print(f"  to'g'ri lug'at hajmi:  {len(lugat)}")
    print(f"  xato lug'at hajmi:     {len(lug_xato)}")
    print("  test kategoriyalari 'ma'lum' bo'lib qoladi, lekin ularning")
    print("  embedding satrlari HECH QACHON o'rgatilmaydi - tasodifiy")
    print("  ⭐ Lug'at o'quvda, 0 - noma'lum uchun")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Lug'at faqat o'quvda ===
  lug'at: {np.str_('A'): 1, np.str_('B'): 2, np.str_('C'): 3, np.str_('D'): 4}
  o'quv kodlari: [1, 2, 1, 3, 1, 2, 4, 1, 2, 3]
  test kodlari:  [1, 0, 3, 0, 2]
  E va F o'quvda yo'q -> 0 (noma'lum)

=== 2. Embedding n + 1 bilan ===
  Embedding(5, 3) - 0-satr noma'lum uchun
  test chiqishi shakli: (5, 3)
  E va F bir xil vektor oldi: True

=== 3. 0-indekssiz nima bo'ladi ===
  KeyError: np.str_('E') - test da yangi kategoriya

=== 4. Kam uchraydigan kategoriyalar ===
  noyob kategoriyalar: 122
   1 martadan kam uchraganlar:    0 ta (0 qator)
   3 martadan kam uchraganlar:   81 ta (99 qator)
   5 martadan kam uchraganlar:   91 ta (133 qator)
  10 martadan kam uchraganlar:  104 ta (221 qator)
  min_soni=5 bilan lug'at: 31 + 1 (noma'lum)
  0 ga tushgan qatorlar: 6.8%
  kam ma'lumotli satrlar yodlanmaydi - ular umumiy 0 ni oladi

=== 5. Butun ma'lumotda lug'at qurish - leakage ===
  to'g'ri lug'at hajmi:  4
  xato lug'at hajmi:     6
  test kategoriyalari 'ma'lum' bo'lib qoladi, lekin ularning
  embedding satrlari HECH QACHON o'rgatilmaydi - tasodifiy
  ⭐ Lug'at o'quvda, 0 - noma'lum uchun

Nima ko'rsatdi: 2.5-bo'lim.

Misol 3 — Embedding va one-hot o'rgatishda

python
"""Ko'p darajali kategoriya: one-hot va embedding (real torch)."""

import numpy as np
import torch
import torch.nn as nn
from sklearn.metrics import mean_squared_error


def yarat(n=6000, n_dokon=400, seed=0):
    """Sotuv = dokon ta'siri + son belgilar. Do'konlar 8 ta yashirin
    guruhga bo'lingan - o'xshash do'konlar o'xshash ta'sirga ega."""
    rng = np.random.default_rng(seed)
    guruh = rng.integers(0, 8, n_dokon)
    guruh_tasiri = rng.normal(0, 1.5, 8)
    dokon_tasiri = guruh_tasiri[guruh] + rng.normal(0, 0.3, n_dokon)
    # do'konlar notekis: p ~ 1/rang, ko'pchiligi kam uchraydi
    p = rng.permutation(1.0 / np.arange(1, n_dokon + 1))
    p = p / p.sum()
    dokon = rng.choice(n_dokon, n, p=p)
    son = rng.normal(0, 1, (n, 4)).astype(np.float32)
    y = (dokon_tasiri[dokon] + son[:, 0] - 0.5 * son[:, 1]
         + rng.normal(0, 0.5, n)).astype(np.float32)
    return son, dokon, y, guruh


class OneHotModel(nn.Module):
    def __init__(self, n_dokon):
        super().__init__()
        self.n = n_dokon
        self.tarmoq = nn.Sequential(nn.Linear(4 + n_dokon, 64), nn.ReLU(),
                                    nn.Linear(64, 1))

    def forward(self, son, kat):
        oh = nn.functional.one_hot(kat, self.n).float()
        return self.tarmoq(torch.cat([son, oh], 1)).squeeze(-1)


class EmbModel(nn.Module):
    def __init__(self, n_dokon, d):
        super().__init__()
        self.emb = nn.Embedding(n_dokon, d)
        self.tarmoq = nn.Sequential(nn.Linear(4 + d, 64), nn.ReLU(),
                                    nn.Linear(64, 1))

    def forward(self, son, kat):
        return self.tarmoq(torch.cat([son, self.emb(kat)], 1)).squeeze(-1)


def orgat(model, son, kat, y, davrlar=40, seed=0):
    opt = torch.optim.AdamW(model.parameters(), lr=0.01, weight_decay=1e-4)
    g = torch.Generator().manual_seed(seed)
    for _ in range(davrlar):
        model.train()
        tartib = torch.randperm(len(y), generator=g)
        for i in range(0, len(y), 128):
            idx = tartib[i:i + 128]
            opt.zero_grad()
            nn.functional.mse_loss(model(son[idx], kat[idx]),
                                   y[idx]).backward()
            opt.step()
    return model


def main() -> None:
    son, dokon, y, guruh = yarat()
    n_dokon = 400
    son_t = torch.tensor(son)
    kat_t = torch.tensor(dokon)
    y_t = torch.tensor(y)
    tr, te = slice(0, 4500), slice(4500, None)
    soni = np.bincount(dokon[:4500], minlength=n_dokon)
    print(f"  {len(y)} qator, {n_dokon} do'kon")
    print(f"  o'quvda 10 martadan kam uchragan do'konlar: "
          f"{int(((soni > 0) & (soni < 10)).sum())}")

    print("\n=== 1. Test MSE ===")
    print(f"  {'model':<18} {'parametr':>10} {'test MSE':>10} "
          f"{'kam do_kon MSE':>15}")
    kam = np.isin(dokon[te], np.where(soni < 10)[0])
    modellar = {}
    for nom, yasa in [("one-hot", lambda: OneHotModel(n_dokon)),
                      ("embedding d=4", lambda: EmbModel(n_dokon, 4)),
                      ("embedding d=16", lambda: EmbModel(n_dokon, 16))]:
        torch.manual_seed(0)
        m = orgat(yasa(), son_t[tr], kat_t[tr], y_t[tr])
        m.eval()
        with torch.no_grad():
            p = m(son_t[te], kat_t[te]).numpy()
        modellar[nom] = m
        param = sum(q.numel() for q in m.parameters())
        print(f"  {nom:<18} {param:>10} "
              f"{mean_squared_error(y[te], p):>10.4f} "
              f"{mean_squared_error(y[te][kam], p[kam]):>15.4f}")

    print("\n=== 2. Embedding yashirin guruhlarni topdimi ===")
    m = modellar["embedding d=4"]
    E = m.emb.weight.detach().numpy()
    kop = soni >= 20
    ichki, tashqi = [], []
    idx = np.where(kop)[0]
    for a in range(len(idx)):
        for b in range(a + 1, len(idx)):
            i, j = idx[a], idx[b]
            d = np.linalg.norm(E[i] - E[j])
            (ichki if guruh[i] == guruh[j] else tashqi).append(d)
    print(f"  ko'p uchragan do'konlar: {kop.sum()}")
    print(f"  bir guruhdagi juftlar o'rtacha masofasi:  "
          f"{np.mean(ichki):.3f}")
    print(f"  boshqa guruhdagi juftlar o'rtacha masofasi: "
          f"{np.mean(tashqi):.3f}")
    nisbat = np.mean(tashqi) / np.mean(ichki)
    print(f"  nisbat: {nisbat:.2f}")
    print(f"  bir guruhdagilar o'rtacha {100 * (1 - 1 / nisbat):.0f}% "
          f"yaqinroq")
    print("  guruh ma'lumoti modelga BERILMAGAN - signal zaif, lekin bor")
    print("  ⭐ Embedding o'xshashlikni qisman o'zi topadi")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
  6000 qator, 400 do'kon
  o'quvda 10 martadan kam uchragan do'konlar: 300

=== 1. Test MSE ===
  model                parametr   test MSE  kam do_kon MSE
  one-hot                 25985     0.3682          0.4908
  embedding d=4            2241     0.3158          0.3881
  embedding d=16           7809     0.3836          0.5839

=== 2. Embedding yashirin guruhlarni topdimi ===
  ko'p uchragan do'konlar: 35
  bir guruhdagi juftlar o'rtacha masofasi:  2.616
  boshqa guruhdagi juftlar o'rtacha masofasi: 2.910
  nisbat: 1.11
  bir guruhdagilar o'rtacha 10% yaqinroq
  guruh ma'lumoti modelga BERILMAGAN - signal zaif, lekin bor
  ⭐ Embedding o'xshashlikni qisman o'zi topadi

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

Misol 4 — To'liq tabular tarmoq va HistGradientBoosting

python
"""Son + bir necha kategoriya: tarmoq va boosting halol taqqoslash."""

import numpy as np
import pandas as pd
import torch
import torch.nn as nn
from sklearn.ensemble import HistGradientBoostingClassifier
from sklearn.metrics import roc_auc_score
from sklearn.model_selection import StratifiedKFold
from sklearn.preprocessing import StandardScaler


def yarat(n=8000, seed=0):
    rng = np.random.default_rng(seed)
    shahar = rng.integers(0, 60, n)
    kasb = rng.integers(0, 25, n)
    kanal = rng.integers(0, 4, n)
    shahar_t = rng.normal(0, 0.8, 60)
    kasb_t = rng.normal(0, 0.8, 25)
    kanal_t = np.array([0.0, 0.4, -0.3, 0.8])
    son = rng.normal(0, 1, (n, 5))
    kuch = (shahar_t[shahar] + kasb_t[kasb] + kanal_t[kanal]
            + 0.8 * son[:, 0] - 0.6 * son[:, 1] + 0.5 * son[:, 2] * son[:, 3]
            - 1.0)
    y = (kuch + rng.logistic(0, 1, n) > 0).astype(np.int64)
    df = pd.DataFrame(son, columns=[f"s{i}" for i in range(5)])
    df["shahar"], df["kasb"], df["kanal"] = shahar, kasb, kanal
    return df, y


KAT = ["shahar", "kasb", "kanal"]
SON = [f"s{i}" for i in range(5)]


class TabularTarmoq(nn.Module):
    def __init__(self, kat_hajmlari, d_emb, yashirin=64):
        super().__init__()
        self.embler = nn.ModuleList(
            [nn.Embedding(n, d) for n, d in zip(kat_hajmlari, d_emb)])
        self.tarmoq = nn.Sequential(
            nn.Linear(len(SON) + sum(d_emb), yashirin), nn.ReLU(),
            nn.Dropout(0.1), nn.Linear(yashirin, 2))

    def forward(self, son, kat):
        e = [emb(kat[:, i]) for i, emb in enumerate(self.embler)]
        return self.tarmoq(torch.cat([son] + e, dim=1))


def tarmoq_bahola(df, y, tr, va, seed):
    sc = StandardScaler().fit(df.iloc[tr][SON])
    lugatlar = [{k: i + 1 for i, k in
                 enumerate(np.unique(df.iloc[tr][c]))} for c in KAT]

    def tayyorla(idx):
        s = torch.tensor(sc.transform(df.iloc[idx][SON]),
                         dtype=torch.float32)
        k = torch.tensor(np.column_stack(
            [[l.get(q, 0) for q in df.iloc[idx][c]]
             for c, l in zip(KAT, lugatlar)]), dtype=torch.int64)
        return s, k

    s_tr, k_tr = tayyorla(tr)
    s_va, k_va = tayyorla(va)
    y_tr = torch.tensor(y[tr])
    hajmlar = [len(l) + 1 for l in lugatlar]
    d_emb = [min(16, (n + 1) // 2) for n in hajmlar]
    torch.manual_seed(seed)
    model = TabularTarmoq(hajmlar, d_emb)
    opt = torch.optim.AdamW(model.parameters(), lr=0.005,
                            weight_decay=1e-4)
    g = torch.Generator().manual_seed(seed)
    for _ in range(25):
        model.train()
        tartib = torch.randperm(len(y_tr), generator=g)
        for i in range(0, len(y_tr), 128):
            idx = tartib[i:i + 128]
            opt.zero_grad()
            nn.functional.cross_entropy(model(s_tr[idx], k_tr[idx]),
                                        y_tr[idx]).backward()
            opt.step()
    model.eval()
    with torch.no_grad():
        p = torch.softmax(model(s_va, k_va), 1)[:, 1].numpy()
    return roc_auc_score(y[va], p), hajmlar, d_emb


def main() -> None:
    df, y = yarat()
    print(f"  {len(df)} qator, son belgilar {len(SON)}, kategoriyalar "
          f"{[int(df[c].nunique()) for c in KAT]}")
    print(f"  musbat ulush: {y.mean():.3f}")

    cv = StratifiedKFold(4, shuffle=True, random_state=0)
    natija = {"HistGB": [], "Tarmoq": []}
    for k, (tr, va) in enumerate(cv.split(df, y)):
        gb = HistGradientBoostingClassifier(
            max_iter=200, learning_rate=0.05, categorical_features=KAT,
            early_stopping=False, random_state=0)
        gb.fit(df.iloc[tr], y[tr])
        natija["HistGB"].append(
            roc_auc_score(y[va], gb.predict_proba(df.iloc[va])[:, 1]))
        auc, hajmlar, d_emb = tarmoq_bahola(df, y, tr, va, seed=k)
        natija["Tarmoq"].append(auc)

    print("\n=== 1. Embedding o'lchamlari ===")
    for c, n, d in zip(KAT, hajmlar, d_emb):
        print(f"  {c:<8} {n:>3} daraja (+0 noma'lum) -> d = {d}")

    print("\n=== 2. Juftlashgan CV (4 fold) ===")
    print(f"  {'fold':>5} {'HistGB':>9} {'Tarmoq':>9}")
    for k in range(4):
        print(f"  {k + 1:>5} {natija['HistGB'][k]:>9.4f} "
              f"{natija['Tarmoq'][k]:>9.4f}")
    a = np.array(natija["HistGB"])
    b = np.array(natija["Tarmoq"])
    d = b - a
    se = d.std(ddof=1) / np.sqrt(len(d))
    print(f"  o'rtacha: HistGB {a.mean():.4f}, Tarmoq {b.mean():.4f}")
    print(f"  farq (Tarmoq - HistGB): {d.mean():+.4f}, SE {se:.4f}")
    if abs(d.mean()) <= 2 * se:
        xulosa = "farq 2*SE ichida - soddaroq model (HistGB) tanlanadi"
    elif d.mean() > 0:
        xulosa = "tarmoq sezilarli ustun"
    else:
        xulosa = "HistGB sezilarli ustun"
    print(f"  QAROR: {xulosa}")
    print("  ⭐ Qarorni farq va SE belgilaydi, bitta raqam emas")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
  8000 qator, son belgilar 5, kategoriyalar [60, 25, 4]
  musbat ulush: 0.387

=== 1. Embedding o'lchamlari ===
  shahar    61 daraja (+0 noma'lum) -> d = 16
  kasb      26 daraja (+0 noma'lum) -> d = 13
  kanal      5 daraja (+0 noma'lum) -> d = 3

=== 2. Juftlashgan CV (4 fold) ===
   fold    HistGB    Tarmoq
      1    0.7793    0.7785
      2    0.7837    0.7843
      3    0.7916    0.7719
      4    0.7850    0.7763
  o'rtacha: HistGB 0.7849, Tarmoq 0.7777
  farq (Tarmoq - HistGB): -0.0071, SE 0.0047
  QAROR: farq 2*SE ichida - soddaroq model (HistGB) tanlanadi
  ⭐ Qarorni farq va SE belgilaydi, bitta raqam emas

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


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

Noto'g'ri fikr To'g'risi
"Embedding murakkab qatlam" Indeks bo'yicha satr jadvali
"Embedding va one-hot boshqa natija beradi" one_hot @ W bilan bir xil
"Hamma satr har qadamda yangilanadi" Faqat uchragan satrlar
"Kategoriya indeksini son sifatida berish mumkin" Tartib ma'nosiz
"Lug'atni butun ma'lumotda qurish qulay" Leakage va o'rgatilmagan satrlar
"Noma'lum kategoriya kam uchraydi" Ishlab chiqarishda albatta chiqadi
"Katta d — yaxshi" Kam ma'lumotda yodlaydi
"Embedding bilan tarmoq boosting dan yaxshi" O'lchab ko'rish kerak

6. Keng tarqalgan xatolar va yechimlari

1. Float indeks

python
emb(torch.tensor([1.0, 2.0]))                      # ⚠️
emb(torch.tensor([1, 2]))                          # ✅ long

2. Noma'lum uchun joy yo'q

python
lugat = {k: i for i, k in enumerate(uniq)}         # ⚠️ KeyError
lugat = {k: i + 1 ...}; lugat.get(q, 0)            # ✅

3. Lug'at butun ma'lumotda

python
lugat_qur(np.concatenate([oquv, test]))            # ⚠️
lugat_qur(oquv)                                    # ✅

4. Indeksni son sifatida

python
torch.cat([son, kat.float()], 1)                   # ⚠️ 5 > 3 ma'nosiz
torch.cat([son, emb(kat)], 1)                      # ✅

5. Kam uchraydiganlarni qoldirish

python
lugat_qur(qiymatlar, min_soni=1)  # 1 martalik ham # ⚠️ yodlash
lugat_qur(qiymatlar, min_soni=5)                   # ✅

6. Embedding o'lchami kategoriyadan katta

python
nn.Embedding(4, 64)                                # ⚠️
nn.Embedding(4, 2)                                 # ✅ yoki one-hot

7. Bazaviysiz

python
# faqat tarmoq natijasini hisobot qilish            # ⚠️
HistGradientBoostingClassifier(categorical_features=[...])  # ✅

7. Integratsiya — bu bilim qayerda kerak bo'ladi

  • 17-qism (o'tilgan): Kategoriyali kodlash va target encoding
  • 20.12-dars (o'tilgan): Tarmoq va boosting taqqoslash
  • 21.5-dars (o'tilgan): Ko'p kirishli Trainer
  • 25-qism: So'z embeddinglari
  • 27-qism: Embedding va qidiruv (RAG)

8. Eng yaxshi amaliyotlar

  1. Ko'p darajali kategoriyaga embedding.

  2. Lug'atni faqat o'quvda quring.

  3. Indeks 0 — noma'lum.

  4. Kam uchraydiganlarni birlashtiring.

  5. d ni formuladan boshlab validatsiyada sozlang.

  6. Son belgilarni masshtablang.

  7. HistGradientBoosting bilan taqqoslang.

  8. Embedding vektorlarini tahlil qiling.


9. Amaliy topshiriq

Vazifa 1: Bashorat qiling

python
1.  # Embedding(n, d) og'irligi shakli?
2.  # emb(idx) nimaga teng?
3.  # qaysi satrlar gradient oladi?
4.  # kirish turi?
5.  # qachon one-hot?
6.  # qachon embedding?
7.  # noma'lum kategoriya qanday kodlanadi?
8.  # lug'at qayerda quriladi?
9.  # kam uchraydiganlar bilan nima qilinadi?
10. # d ni tanlash formulasi?
11. # son va kategoriya qanday birlashtiriladi?
12. # embeddingning qo'shimcha foydasi?
Javoblar
  1. (n, d)
  2. one_hot(idx) @ W
  3. Faqat batchda uchraganlar
  4. long (int64)
  5. Kategoriya kam (< 20)
  6. Kategoriya ko'p, tarmoq
  7. 0
  8. Faqat o'quvda
  9. 0 ga birlashtiriladi
  10. min(50, (n + 1) // 2)
  11. torch.cat([son] + embeddinglar)
  12. Vektorlarning o'zi (qidiruv, klaster)

Vazifa 2: Xatolarni tuzating

python
1.  emb(torch.tensor([1.0, 2.0]))

2.  lugat = {k: i for i, k in enumerate(uniq)}   # test da yangi kategoriya

3.  lugat_qur(np.concatenate([oquv, test]))

4.  torch.cat([son, kat.float()], 1)

5.  nn.Embedding(4, 64)
Javoblar
python
1.  emb(torch.tensor([1, 2]))

2.  lugat = {k: i + 1 for i, k in enumerate(uniq)}; lugat.get(q, 0)

3.  lugat_qur(oquv)

4.  torch.cat([son, emb(kat)], 1)

5.  nn.Embedding(4, 2)   # yoki one-hot

Vazifa 3: Ichidan

Modellang:

  1. Jadval
  2. Satr olish
  3. one-hot tenglik
  4. Siyrak gradient

Vazifa 4: Lug'at

Modellang:

  1. O'quvda
  2. n + 1
  3. 0 siz
  4. Kam uchraydigan

Vazifa 5: O'rgatish

Modellang:

  1. One-hot
  2. Embedding
  3. Kam do'konlar
  4. Yashirin guruhlar

Vazifa 6: Taqqoslash

Modellang:

  1. O'lchamlar
  2. Juftlashgan CV
  3. Farq va SE
  4. Qaror

Vazifa 7: O'ylash

Siz mahsulotlar uchun embedding o'rgatdingiz. Mahsulot menejeri so'radi: "Bu vektorlardan 'o'xshash mahsulotlar' ro'yxatini chiqarsa bo'ladimi?" Nima deysiz?

Javob

Qisqa javob: ha, lekin "o'xshash" bu yerda "model uchun bir xil ta'sirga ega" degan ma'noni bildiradi — "mijozga o'xshash ko'rinadi" degani emas.

Qanday qilinadi:

python
E = model.emb.weight.detach()                    # (n, d)
E = torch.nn.functional.normalize(E, dim=1)      # kosinus uchun
oxshash = E @ E[mahsulot_id]                     # (n,)
top = oxshash.topk(11).indices[1:]               # o'zidan tashqari 10 ta

Muhim cheklovlar:

1. Embedding faqat o'rgatilgan vazifani biladi. Sotuv hajmini bashorat qilish uchun o'rgatilgan bo'lsa, "o'xshash" = "sotuv hajmiga o'xshash ta'sir qiladi". Masalan, qishki kurtka va yangi yil bezagi bir-biriga yaqin chiqishi mumkin — chunki ikkalasi ham dekabrda sotiladi, lekin ular o'xshash mahsulot emas.

2. Kam uchragan mahsulotlarning vektorlari ishonchsiz. 3-misolda ko'rdik: faqat ko'p uchragan kategoriyalar uchun masofa tahlili ma'noga ega. Kam uchraganlar (yoki min_soni bo'yicha 0 ga birlashtirilganlar) tasodifiy yoki umumiy vektorda qoladi.

python
soni = np.bincount(oquv_kodlar, minlength=n)
ishonchli = soni >= 50        # faqat shular uchun tavsiya bering

3. Masofa o'lchovi muhim. Kosinus o'xshashlik odatda Evklid masofasidan yaxshiroq, chunki vektor uzunligi ko'pincha kategoriyaning chastotasini aks ettiradi, yo'nalish esa — ma'nosini.

4. Tekshiruv kerak.

python
# 20 ta mahsulotni tanlab, eng yaqin 5 tasini ko'rib chiqing
# mutaxassis bilan: "bular haqiqatan o'xshashmi?"

Nima taklif qilish kerak:

Maqsad Yondashuv
"Birga sotib olinadigan" Savat ma'lumotida alohida embedding (item2vec)
"Tashqi ko'rinishi o'xshash" Rasm embeddingi (24-qism)
"Tavsifi o'xshash" Matn embeddingi (25, 27-qism)
"Sotuv dinamikasi o'xshash" Hozirgi embedding — mos

Menejerga javob:

"Ha, bu vektorlardan o'xshashlik ro'yxatini chiqarish mumkin. Lekin ular sotuv bashorati uchun o'rgatilgan, shuning uchun 'o'xshash' = 'sotuvga o'xshash ta'sir qiladi'. Agar sizga 'mijoz uchun o'xshash' kerak bo'lsa, xarid tarixidan alohida embedding o'rgatishni taklif qilaman. Hozirgisini faqat ko'p sotilgan mahsulotlar uchun va mutaxassis tekshiruvi bilan ishlatish mumkin."

Nimani mustahkamlaydi: 2.2, 2.6-bo'limlar.


Xulosa

Bu darsda embedding va tabular tarmoqni ko'rdik.

Eng muhim uch fikr:

  1. Embedding — indeks bo'yicha satr jadvali. nn.Embedding(n, d) ning chiqishi one_hot(idx) @ W bilan aynan teng, lekin one-hot matritsa hech qachon qurilmaydi. Gradient faqat batchda uchragan satrlarga keladi. Ko'p kategoriyada bu one-hot + Linear ga nisbatan parametrlarni kamaytiradi — 1-misolda 50 000 kategoriyada taxminan ikki barobar.

  2. Lug'at o'quvda, indeks 0 — noma'lum. Test yoki ishlab chiqarishda yangi kategoriya albatta paydo bo'ladi; uning uchun ajratilgan 0-indeks bo'lmasa, kod KeyError yoki IndexError bilan to'xtaydi. Lug'atni butun ma'lumotda qurish esa leakage va hech qachon o'rgatilmagan tasodifiy satrlarni beradi. Kam uchraydigan kategoriyalarni 0 ga birlashtirish ularning yodlanishini oldini oladi.

  3. Embedding o'xshashlikni o'zi o'rganadi — lekin baribir o'lchang. 3-misolda d = 4 li embedding one-hot dan past xato berdi, d = 16 esa kam uchraydigan do'konlarni yodlab yomonlashdi. Guruh ma'lumoti modelga berilmagan bo'lsa ham, bir guruhdagi do'konlarning embeddinglari o'rtacha ~10% yaqinroq joylashdi — zaif, lekin haqiqiy signal. Shunga qaramay tarmoqni HistGradientBoosting(categorical_features=...) bilan juftlashgan CV da taqqoslash shart: 4-misolda qaror bitta raqam bilan emas, farq va uning SE si bilan qabul qilindi.

Keyingi darsda loyiha tuzilishi va takrorlanuvchanlik: torch loyihasini papkalarga ajratish, konfiguratsiya fayli, seed boshqaruvi va tajribalarni qayd qilish.

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
21.10-dars: Tabular ma'lumot va embedding — IlmHamroh