IlmHamroh
Data Science va sun'iy intellekt/Generativ AI8/10-dars47 daqiqa
Mundarija (24)

26.8-dars: Matn-rasm va multimodal modellar

26-QISM — GENERATIV AI · 8-dars


1. Kirish va motivatsiya

26.6-darsda diffusion modelni sinf bilan boshqardik: "menga 7 raqami kerak" — va classifier-free guidance bilan so'ralgan sinf 0.985 holatda chiqdi. Lekin sinf — yopiq ro'yxat: 10 ta raqam, boshqa hech narsa. Foydalanuvchi esa "kichik ko'k uchburchak" yoki "qizil rangli katta doira" deb erkin matn yozadi — va, eng muhimi, o'quv ma'lumotida hech qachon birga uchramagan so'zlar kombinatsiyasini so'raydi. Matn-rasm modellarining asosiy va'dasi ham shu: so'zlarning ma'nosini alohida o'rganib, ularni yangi kombinatsiyalarda qo'shib ishlatish (kompozitsion umumlashma).

Bunga ikki qism kerak. Birinchisi — matn va rasmni bitta fazoga joylashtiradigan model: "qizil katta doira" matni va qizil katta doira rasmi yaqin vektorlarga aylansin. Bu CLIP g'oyasi — kontrastiv o'rganish. Ikkinchisi — shu matn vektori bilan boshqariladigan generator: diffusion yoki VAE, sharti matn embeddingi.

Real vaziyat. Onlayn do'kon mahsulot katalogi uchun "matn bo'yicha rasm qidiruvi" ni ishga tushirdi. Sinovda hammasi ajoyib edi: "qora charm sumka" — qora charm sumkalar. Keyin foydalanuvchilar "qora emas, jigarrang sumka" deb yozishdi va yana qora sumkalar chiqdi. Katalogda kam uchraydigan "yashil charm poyabzal" ga esa yashil matodan poyabzallar va boshqa rangdagi charm poyabzallar aralash chiqdi. Ikki muammo: matn encoderi inkorni tushunmaydi va model ko'rmagan atribut kombinatsiyalarida adashadi. Bu darsning 1- va 2-misollari aynan shu ikki holatni kichik, lekin to'liq nazorat qilinadigan ma'lumotda o'lchaydi.

Bu darsda matn-rasm tizimlarining umumiy arxitekturasini ko'rib chiqamiz, CLIP ni noldan quramiz — kod ichida chizilgan 16x16 shakllar va ularning o'zbekcha tavsiflari bilan — InfoNCE loss, zero-shot klassifikatsiya va matn → rasm qidiruvini o'lchaymiz, keyin matn sharti bilan kichik generator (shartli VAE) o'rgatib, o'quvda ko'rilmagan atribut kombinatsiyalarida natija qanchalik to'g'ri ekanini o'z atribut klassifikatorlarimiz bilan tekshiramiz.

Bu darsda:

  • Matn-rasm generatsiyasi arxitekturasi: matn encoder → shart → generator
  • Cross-attention bilan shart berish
  • CLIP: kontrastiv o'rganish va InfoNCE
  • Zero-shot klassifikatsiya va kompozitsion umumlashma
  • Matn → rasm qidiruvi: P@k, R-precision, R@k
  • Matn encoderining cheklovlari: tartib, inkor
  • Matn sharti bilan generator: kompozitsion shart va ID shart
  • CLIP bilan qayta saralash (best-of-K)
  • Real tizimlar: umumiy tuzilish
  • Tuzoqlar

ℹ Misollar real torch/numpy/sklearn bilan (Python 3.14, torch 2.14 CPU). Ma'lumot kod ichida chiziladi: 3 shakl x 3 rang x 2 o'lcham = 18 kombinatsiya, ulardan 4 tasi o'quvda umuman ko'rsatilmaydi.


2. Nazariya — chuqur tushuntirish

2.1. Matn-rasm generatsiyasi arxitekturasi

text
UMUMIY QUVUR:
  matn ("kichik ko'k uchburchak")
    -> TOKENIZATOR -> MATN ENCODER (Transformer, 24-qism)
    -> matn embeddinglari (har token uchun vektor yoki bitta vektor)
    -> SHART sifatida generatorga
    -> GENERATOR (diffusion U-Net yoki Transformer; 26.5-26.6)
    -> (latent diffusion bo'lsa) VAE DEKODER -> rasm

SHART QANDAY BERILADI:
  1. qo'shish/konkatenatsiya: bitta matn vektori c -> h = h + W c
     (26.6-darsdagi sinf embeddingi kabi; 3-misoldagi VAE)
  2. CROSS-ATTENTION (24.3-dars): rasm xususiyatlari - so'rov (Q),
     matn tokenlari - kalit va qiymat (K, V)
       attention(Q = W_q h_rasm, K = W_k e_matn, V = W_v e_matn)
     har rasm joyi "qaysi so'zga qarash kerak"ni o'zi tanlaydi
  3. classifier-free guidance 26.6-bob: matn shartini 10-20% tashlab o'rgatish,
     samplingda eps_u + w * (eps_c - eps_u)

MATN ENCODERI QAYERDAN:
  oldindan o'rgatilgan CLIP matn encoderi yoki katta til modeli encoderi
  (odatda muzlatilgan); generator faqat uning chiqishini "o'qishni" o'rganadi

Matn-rasm modeli = matn encoder + shartli generator; matnni tushunish sifati asosan encoderga, rasm sifati generatorga bog'liq.

2.2. Cross-attention bilan shart berish

text
NEGA BITTA VEKTOR YETMAYDI:
  "chapda qizil doira, o'ngda ko'k kvadrat" - qaysi rang qaysi shaklga
  tegishli ekani so'zlar orasidagi munosabatda; bitta o'rtacha vektorda
  bu munosabat yo'qoladi (2-misol: so'zlar xaltasi tartibni ko'rmaydi)

CROSS-ATTENTION (24.3 dagi formula):
  Q = h_rasm W_q      (n_joy, d)       - rasmning har joyi
  K = e_matn W_k      (n_token, d)     - matn tokenlari
  V = e_matn W_v
  chiqish = softmax(Q K^T / sqrt(d)) V
  -> har joy o'ziga kerakli so'zlarni "o'qiydi"

DIFFUSION U-NET DA:
  har rezolyutsiya blokida: self-attention (rasm ichida) + cross-attention (matnga)
  attention xaritalari - qaysi so'z rasmning qaysi qismiga ta'sir qilgani
  (tahrirlash usullari shu xaritalarni boshqaradi)

Cross-attention — matnni token darajasida berish; bu dars kichik tajribalarida bitta vektor bilan cheklanamiz va uning chegaralarini o'lchaymiz.

2.3. CLIP: kontrastiv o'rganish va InfoNCE

text
G'OYA (Radford va boshq., 2021 - CLIP):
  ikki encoder: f_rasm(x) -> z_r, f_matn(t) -> z_t; ikkalasi ham normallangan
  maqsad: mos juftlik (x_i, t_i) yaqin, mos bo'lmagan (x_i, t_j) uzoq
  internetdan olingan juda ko'p rasm-matn juftliklarida o'rgatilgan

INFONCE (batchda B ta juftlik):
  S = tau * Z_r Z_t^T                   (B, B) o'xshashlik matritsasi
  tau = exp(log_harorat)                - o'rganiladigan harorat
  loss_r = CE(S, [0..B-1])              - har rasm uchun B ta matndan to'g'risi
  loss_t = CE(S^T, [0..B-1])            - har matn uchun B ta rasmdan to'g'risi
  loss = (loss_r + loss_t) / 2

  diagonal - musbat juftliklar, qolgan B-1 ta - manfiy
  tasodifiy boshlanishda loss ~ log B  (bizda log 128 = 4.852)

1-MISOL:
  loss 5.275 -> 2.001 (urug' 0), harorat 14.3 -> 19.3
  boshida log B dan yuqori: harorat tasodifiy o'xshashliklarni kuchaytiradi
  oxirida ~2: batchda bir kombinatsiyadan ~9 juftlik bor ->
  ular bir-biriga "soxta manfiy" -> pastki chegara ~ log 9 = 2.2 atrofida

BIZNING MODEL:
  rasm encoder: Conv(3->16) -> max-pool 4x4 -> 256 -> 64 -> 32
  matn encoder: so'z embeddinglari O'RTACHASI -> MLP -> 32  (so'zlar xaltasi)
  400 qadam, batch 128, 14 ta kombinatsiya, 6 ta tavsif shabloni

CLIP = ikki encoder + InfoNCE: batchdagi boshqa juftliklar tayyor manfiy misollar; o'rganilgan fazoda matn va rasmni to'g'ridan-to'g'ri solishtirish mumkin.

2.4. Zero-shot klassifikatsiya va kompozitsion umumlashma

text
ZERO-SHOT:
  har sinf uchun matn: "katta qizil doira", ..., (18 ta)
  bashorat = argmax_k cos(f_rasm(x), f_matn(matn_k))
  klassifikatorni qayta o'rgatish YO'Q - yangi sinf = yangi matn

KOMPOZITSION TEST (bizning tajriba):
  o'quvda 14 kombinatsiya; 4 tasi hech qachon ko'rsatilmagan:
    ko'k katta uchburchak, qizil kichik kvadrat,
    qizil kichik uchburchak, yashil katta doira
  lekin har so'z (ko'k, katta, uchburchak ...) o'quvda boshqa kombinatsiyalarda bor

1-MISOL (3 urug'):
  ko'rilgan kombinatsiyalar:   0.974
  ko'rilmagan kombinatsiyalar: 0.519      (tasodifiy 0.056)
  ko'rilmaganlarda: rang 1.000, o'lcham 0.948, shakl 0.540

  "qizil kichik kvadrat" -> 88% holatda "qizil kichik doira"
  sabab: o'quvda qizil kichik shakl FAQAT doira edi ->
  model "qizil + kichik => doira" yorlig'ini (shortcut) o'rgangan

ODDIY KLASSIFIKATOR BILAN FARQ:
  14 sinfli klassifikator ko'rilmagan kombinatsiyani umuman ayta olmaydi
  CLIP esa 0.519 - tasodifiydan 9 barobar yuqori, lekin 0.974 dan ancha past

Zero-shot — matn bilan yangi sinf qo'shish; kompozitsion umumlashma ishlaydi, lekin to'liq emas: ko'rilmagan kombinatsiyada model o'quvdagi korrelyatsiyalarga yopishib qoladi.

2.5. Matn → rasm qidiruvi va o'lchovlari

text
QIDIRUV: so'rov z_t, galereya {z_r}; kosinus bo'yicha tartiblash

O'LCHOVLAR:
  P@k (precision@k)  - top-k dagi to'g'ri natijalar ulushi
  R-precision        - top-R da, R = to'g'ri natijalar soni (bizda 40)
  R@k (recall@k)     - to'g'ri javob top-k ichidami (bitta to'g'ri bo'lsa)
  (25.7-25.9 darslardagi semantik qidiruv o'lchovlari bilan bir xil)

2-MISOL (18 so'rov, 720 rasm, 3 urug'):
                       ko'rilgan   ko'rilmagan
  matn->rasm P@10        0.955       0.758
  matn->rasm R-prec      0.923       0.673
  rasm->matn R@1         0.974       0.519
  rasm->matn R@3         0.999       0.898

  qidiruv (matn->rasm) ko'rilmaganlarda zero-shot dan yaxshiroq:
  top-10 uchun galereyadagi eng "ishonchli" 10 ta rasm yetarli

2.6. Matn encoderining cheklovlari

text
SO'ZLAR XALTASI (bizning encoder):
  "katta qizil doira" ~ "qizil katta doira"        kosinus 1.000 (tartib yo'q)
  "qizil katta doira" ~ "qizil emas katta doira"   kosinus 1.000 ("emas" lug'atda yo'q)
  so'rov "qizil emas katta doira" -> top-20 ning 1.00 qismi QIZIL

KATTA CLIP MODELLARIDA HAM MA'LUM ZAIF JOYLAR:
  inkor ("... emas", "... siz")
  sanash ("uchta olma")
  fazoviy munosabatlar ("chapda", "ustida")
  atributni to'g'ri obyektga bog'lash ("qizil kub va ko'k shar")
  sabab: kontrastiv maqsad ko'pincha "qaysi so'zlar bor" ni bilish bilan
  yechiladi - tartib va munosabatni o'rganishga majburlamaydi

AMALDA:
  tartibni ko'radigan encoder (Transformer) + munosabatli qiyin manfiy
  misollar (hard negatives) bilan o'rgatish

Kontrastiv encoder "qaysi so'zlar bor" ni yaxshi, "ular qanday bog'langan" ni yomon biladi; inkor va munosabatlarni alohida sinab ko'ring.

2.7. Matn sharti bilan generator

text
SHARTLI VAE (26.2 dagi VAE + shart c):
  encoder:  q(z | x, c)       dekoder: p(x | z, c)
  loss = ||x - x_hat||^2 / (2 * 0.1^2) + KL(q(z|x,c) || N(0, I))
  generatsiya: z ~ N(0, I), c = matn(tavsif) -> dekoder

IKKI XIL SHART (3-misol):
  matn:  c = MLP(so'z embeddinglari o'rtachasi)  - so'zlar orqali
  ID:    c = Embedding(18)[kombinatsiya raqami]   - har kombinatsiyaga alohida vektor
  ID shartida ko'rilmagan kombinatsiyaning vektori HECH QACHON o'rgatilmaydi

BAHOLASH:
  o'z atribut klassifikatorlarimiz (rang, o'lcham, shakl) - HAMMA 18
  kombinatsiyada o'rgatilgan hakam; generatsiyaning har atributi to'g'rimi

3-MISOL ('hammasi to'g'ri', 3 urug'):
             ko'rilgan   ko'rilmagan
  matn         0.396       0.304
  ID           0.473       0.000
  -> ko'rilganda ID biroz yaxshi, ko'rilmaganda faqat matn sharti ishlaydi

Kompozitsion shart — so'zlar orqali: ko'rilmagan kombinatsiyaga faqat so'z darajasida o'rgangan shart umumlasha oladi; "har kombinatsiyaga alohida ID" esa umuman umumlashmaydi.

2.8. CLIP bilan qayta saralash (best-of-K)

text
G'OYA (dastlabki matn-rasm tizimlarida ishlatilgan):
  bir so'rovga K ta nomzod generatsiya qilish
  CLIP bilan cos(f_rasm(nomzod), f_matn(so'rov)) hisoblash
  eng yuqorisini qaytarish

4-MISOL ('hammasi to'g'ri'):
    K     ko'rilgan   ko'rilmagan
    1       0.333       0.285
    8       0.460       0.375
  AUC (CLIP bahosi -> to'g'rilik): ko'rilgan 0.896, ko'rilmagan 0.764

NARX: K barobar generatsiya + K ta CLIP baholash
CHEKLOV: tanlovchi ham o'sha ma'lumotda o'rgangan - ko'r nuqtalari umumiy
  (ko'rilmagan kombinatsiyada foyda kichikroq)

2.9. Real tizimlar: umumiy tuzilish

text
(faqat umumiy ma'lum arxitektura; aniq o'lcham va xarajat raqamlari
 bu yerda keltirilmaydi)

STABLE DIFFUSION (latent diffusion, 26.6):
  VAE: rasm -> kichik latent (8 barobar kichraytirilgan)
  matn encoder: oldindan o'rgatilgan CLIP matn encoderi (muzlatilgan)
  U-Net: latentda diffusion, matn - cross-attention orqali
  classifier-free guidance, kam qadamli samplerlar (DDIM va boshq.)

DALL-E (OpenAI oilasi):
  birinchi versiya - rasm tokenlari ustida avtoregressiv Transformer
    + CLIP bilan qayta saralash 2.8-bob
  keyingi versiyalar - diffusion asosidagi dekoder, CLIP embeddinglari
  yoki til modeli bilan kuchaytirilgan matn tushunish

UMUMIY TENDENSIYALAR:
  kuchliroq matn encoderi -> so'rovga yaxshiroq moslik
  latent fazo -> arzonroq hisob
  guidance -> moslik va xilma-xillik murosasi 26.6-bob
  baholash: FID + CLIP-score + inson baholashi (26.7)

2.10. Tuzoqlar

Asosiy tuzoqlar: zero-shot aniqlikni faqat o'quvdagi kombinatsiyalarda o'lchab "model kompozitsion" deyish; InfoNCE da batchdagi bir xil sinfli juftliklar (soxta manfiylar) borligini unutish; haroratni juda katta yoki qat'iy qo'yish; matn va rasm vektorlarini normallamaslik; qidiruvni faqat bitta o'lchov bilan baholash; so'zlar xaltasi encoderida tartib va inkorni kutish; ko'rilmagan kombinatsiyalarni ID sharti bilan generatsiya qilishga urinish; generatsiyani o'sha CLIP bilan ham tanlab, ham baholash (o'z-o'zini tekshirish); baholovchi atribut klassifikatorining o'z aniqligini tekshirmaslik; real tizimlarning parametr soni va o'quv xarajati haqida tekshirilmagan raqamlarni keltirish.


3. Tez ma'lumotnoma

python
import torch
import torch.nn.functional as F

# ikki encoder, normallangan vektorlar
zi = F.normalize(rasm_encoder(X), dim=-1)
zt = F.normalize(matn_encoder(matnlar), dim=-1)

# InfoNCE (simmetrik), o'rganiladigan harorat
logit = log_harorat.exp().clamp(max=100) * zi @ zt.T
nishon = torch.arange(len(zi))
loss = (F.cross_entropy(logit, nishon) + F.cross_entropy(logit.T, nishon)) / 2

# zero-shot
bashorat = (zi @ matn_encoder(sinf_tavsiflari).T).argmax(1)

# matn -> rasm qidiruvi va P@10
top = (matn_encoder([sorov]) @ galereya.T)[0].argsort(descending=True)[:10]
p10 = (Y[top] == togri_sinf).float().mean()

# shartli VAE: shart - matn embeddingi
c = matn_shart(tavsiflar)
rasm = dekoder(torch.cat([torch.randn(n, z_dim, generator=g), c], 1))

# best-of-K: CLIP bahosi bo'yicha eng yaxshi nomzod
ball = (rasm_encoder(nomzodlar) * matn_encoder([sorov])).sum(1)
eng = nomzodlar[ball.argmax()]

Matn-rasm xulosasi

matn encoder -> shart (vektor yoki cross-attention) -> generator
CLIP: ikki encoder, InfoNCE, o'rganiladigan harorat
zero-shot: sinf = matn; kompozitsion umumlashma - qisman
qidiruv: P@k, R-precision, R@k; ko'rilgan va ko'rilmaganni alohida
so'zlar xaltasi: tartib va inkor yo'q
kompozitsion shart umumlashadi, ID shart - yo'q
best-of-K: CLIP bilan qayta saralash, narxi K barobar

4. Batafsil misollar

Misollar real torch/numpy/sklearn bilan (Python 3.14, torch 2.14 CPU). Hamma misollarda bir xil ma'lumot generatori: 16x16 RGB rasm, bitta shakl (joyi va rangi biroz tasodifiy), 6 xil o'zbekcha tavsif shabloni.

Misol 1 — CLIP noldan: InfoNCE va zero-shot klassifikatsiya

python
"""CLIP g'oyasi noldan: sintetik shakllar va o'zbekcha tavsiflar, InfoNCE, zero-shot klassifikatsiya."""

import math

import numpy as np
import torch
import torch.nn as nn
import torch.nn.functional as F

SHAKLLAR = ["doira", "kvadrat", "uchburchak"]
RANGLAR = ["qizil", "yashil", "ko'k"]
OLCHAMLAR = ["katta", "kichik"]
RGB = {"qizil": (0.9, 0.15, 0.15), "yashil": (0.15, 0.8, 0.2), "ko'k": (0.2, 0.3, 0.95)}
KOMBO = [(r, o, s) for s in SHAKLLAR for r in RANGLAR for o in OLCHAMLAR]      # 18 ta
KORILMAGAN = {("ko'k", "katta", "uchburchak"), ("qizil", "kichik", "kvadrat"),
              ("yashil", "katta", "doira"), ("qizil", "kichik", "uchburchak")}
SHABLONLAR = ["{r} {o} {s}", "{o} {r} {s}", "{r} rangli {o} {s}", "bu {o} {r} {s}",
              "{o} {s}, rangi {r}", "rasmda {r} {s}, o'lchami {o}"]


def chiz(rang, olcham, shakl, rng):
    """16x16 RGB rasm: fon + bitta shakl (joyi, rangi biroz tasodifiy)."""
    r = 6.0 if olcham == "katta" else 3.2
    cy, cx = rng.uniform(r + 0.5, 15.5 - r, size=2)
    y, x = np.mgrid[0:16, 0:16] + 0.5
    if shakl == "doira":
        m = (y - cy) ** 2 + (x - cx) ** 2 <= r ** 2
    elif shakl == "kvadrat":
        m = (np.abs(y - cy) <= 0.85 * r) & (np.abs(x - cx) <= 0.85 * r)
    else:
        m = (y >= cy - r) & (y <= cy + r) & (np.abs(x - cx) <= (y - (cy - r)) / 2)
    rasm = 0.1 + 0.05 * rng.standard_normal((3, 16, 16))
    rang_q = np.array(RGB[rang]) + rng.uniform(-0.08, 0.08, 3)
    rasm[:, m] = rang_q[:, None]
    return rasm.astype(np.float32)


def toplam(kombolar, n_har, seed):
    rng = np.random.default_rng(seed)
    X, T, Y = [], [], []
    for ki, (r, o, s) in enumerate(KOMBO):
        if (r, o, s) not in kombolar:
            continue
        for _ in range(n_har):
            X.append(chiz(r, o, s, rng))
            T.append(SHABLONLAR[rng.integers(len(SHABLONLAR))].format(r=r, o=o, s=s))
            Y.append(ki)
    return torch.tensor(np.array(X)), T, torch.tensor(Y)


class RasmEncoder(nn.Module):
    """Kichik CNN: 3x16x16 -> konvolyutsiya (16 kanal) -> max-pool 4x4 -> 256 -> 64 -> 32."""

    def __init__(self, d=32):
        super().__init__()
        self.tarmoq = nn.Sequential(
            nn.Conv2d(3, 16, 3, padding=1), nn.ReLU(), nn.MaxPool2d(4),
            nn.Flatten(), nn.Linear(16 * 16, 64), nn.ReLU(), nn.Linear(64, d))

    def forward(self, x):
        return F.normalize(self.tarmoq(x), dim=-1)


class MatnEncoder(nn.Module):
    """So'z embeddinglari o'rtachasi (bag of words) + MLP. So'z tartibini KO'RMAYDI."""

    def __init__(self, lugat, d=32):
        super().__init__()
        self.lugat = {w: i + 1 for i, w in enumerate(lugat)}          # 0 - to'ldiruvchi
        self.emb = nn.Embedding(len(lugat) + 1, 32, padding_idx=0)
        self.mlp = nn.Sequential(nn.Linear(32, 64), nn.ReLU(), nn.Linear(64, d))

    def tokenla(self, matnlar):
        idx = [[self.lugat.get(w, 0) for w in m.replace(",", "").split()] for m in matnlar]
        L = max(len(i) for i in idx)
        return torch.tensor([i + [0] * (L - len(i)) for i in idx])

    def forward(self, matnlar):
        t = self.tokenla(matnlar)
        e = self.emb(t)
        o = e.sum(1) / (t > 0).sum(1, keepdim=True).clamp(min=1)
        return F.normalize(self.mlp(o), dim=-1)


def lugat_yasash():
    sozlar = set()
    for r, o, s in KOMBO:
        for sh in SHABLONLAR:
            sozlar.update(sh.format(r=r, o=o, s=s).replace(",", "").split())
    return sorted(sozlar)


def orgat_clip(X, T, seed, qadamlar=400, B=128):
    torch.manual_seed(seed)
    re_, me = RasmEncoder(), MatnEncoder(lugat_yasash())
    log_harorat = nn.Parameter(torch.tensor(math.log(1 / 0.07)))
    opt = torch.optim.Adam(list(re_.parameters()) + list(me.parameters()) + [log_harorat], lr=2e-3)
    g = torch.Generator().manual_seed(seed)
    for q in range(qadamlar):
        i = torch.randperm(len(X), generator=g)[:B]
        zi, zt = re_(X[i]), me([T[j] for j in i.tolist()])
        logit = log_harorat.exp().clamp(max=100) * zi @ zt.T               # (B, B)
        nishon = torch.arange(B)
        loss = (F.cross_entropy(logit, nishon) + F.cross_entropy(logit.T, nishon)) / 2   # InfoNCE
        opt.zero_grad()
        loss.backward()
        opt.step()
        if q == 0:
            boshlangich = loss.item()
    re_.eval()
    me.eval()
    return re_, me, boshlangich, loss.item(), log_harorat.exp().item()


def main() -> None:
    torch.set_num_threads(1)
    korilgan = set(KOMBO) - KORILMAGAN
    X, T, _ = toplam(korilgan, 150, seed=0)
    Xt, _, Yt = toplam(set(KOMBO), 40, seed=1)
    print("=== 1. Ma'lumot: 16x16 shakllar va o'zbekcha tavsiflar ===")
    print(f"  atributlar: {len(SHAKLLAR)} shakl x {len(RANGLAR)} rang x {len(OLCHAMLAR)} o'lcham = {len(KOMBO)} kombinatsiya")
    print(f"  o'quvda ko'rilmagan {len(KORILMAGAN)} ta: "
          + "; ".join(" ".join(k) for k in sorted(KORILMAGAN)))
    print(f"  o'quv juftliklari: {len(X)}, test rasmlari: {len(Xt)} (har kombinatsiyadan 40)")
    print(f"  tavsif namunalari: {T[0]!r}, {T[1]!r}, {T[2]!r}")
    print(f"  lug'at: {len(lugat_yasash())} so'z")

    print("\n=== 2. InfoNCE bilan o'rgatish (3 urug') ===")
    yozuvlar = [f"{o} {r} {s}" for r, o, s in KOMBO]
    korilmagan_m = torch.tensor([KOMBO[y] in KORILMAGAN for y in Yt.tolist()])
    natija, birinchi = [], None
    for seed in range(3):
        re_, me, l0, l1, harorat = orgat_clip(X, T, seed)
        with torch.no_grad():
            bashorat = (re_(Xt) @ me(yozuvlar).T).argmax(1)
        tog = bashorat == Yt
        if birinchi is None:
            birinchi = bashorat
        attr = [np.mean([KOMBO[p][j] == KOMBO[y][j] for p, y in zip(bashorat.tolist(), Yt.tolist())
                         if KOMBO[y] in KORILMAGAN]) for j in range(3)]
        natija.append((tog[~korilmagan_m].float().mean().item(), tog[korilmagan_m].float().mean().item(), *attr))
        print(f"  urug' {seed}: loss {l0:.3f} -> {l1:.3f} (log B = {math.log(128):.3f}), harorat {harorat:.1f}")

    print("\n=== 3. Zero-shot: 18 ta tavsifdan eng o'xshashi ===")
    v = np.array(natija)
    se = v.std(0, ddof=1) / math.sqrt(3)
    print(f"  tasodifiy daraja: {1 / 18:.3f}")
    print(f"  ko'rilgan kombinatsiyalar:   aniqlik {v[:, 0].mean():.3f} +- {se[0]:.3f}")
    print(f"  ko'rilmagan kombinatsiyalar: aniqlik {v[:, 1].mean():.3f} +- {se[1]:.3f}")
    print(f"  ko'rilmaganlarda atribut bo'yicha: rang {v[:, 2].mean():.3f}, o'lcham {v[:, 3].mean():.3f}, "
          f"shakl {v[:, 4].mean():.3f}")
    f = v[:, 0] - v[:, 1]
    fse = f.std(ddof=1) / math.sqrt(3)
    print(f"  ko'rilgan - ko'rilmagan: {f.mean():+.3f} (SE {fse:.3f}) -> "
          + ("sezilarli" if f.mean() > 2 * fse else "sezilarli emas"))
    print("  oddiy 14 sinfli klassifikator ko'rilmagan kombinatsiyani umuman ayta olmaydi (sinf yo'q)")

    print("\n=== 4. Ko'rilmagan kombinatsiyalar: model nimani tanladi (urug' 0) ===")
    for kombo in sorted(KORILMAGAN):
        ki = KOMBO.index(kombo)
        p = birinchi[Yt == ki].tolist()
        eng = max(set(p), key=lambda c: (p.count(c), -c))
        print(f"  {' '.join(kombo):<24} to'g'ri {p.count(ki) / len(p):.2f}; eng ko'p tanlangan: "
              f"{' '.join(KOMBO[eng])} ({p.count(eng) / len(p):.2f})")
    print("  ⭐ rang va o'lcham kombinatsiyalanadi; shakl - eng qiyin qism")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Ma'lumot: 16x16 shakllar va o'zbekcha tavsiflar ===
  atributlar: 3 shakl x 3 rang x 2 o'lcham = 18 kombinatsiya
  o'quvda ko'rilmagan 4 ta: ko'k katta uchburchak; qizil kichik kvadrat; qizil kichik uchburchak; yashil katta doira
  o'quv juftliklari: 2100, test rasmlari: 720 (har kombinatsiyadan 40)
  tavsif namunalari: 'qizil katta doira', 'qizil katta doira', 'katta qizil doira'
  lug'at: 13 so'z

=== 2. InfoNCE bilan o'rgatish (3 urug') ===
  urug' 0: loss 5.275 -> 2.001 (log B = 4.852), harorat 19.3
  urug' 1: loss 5.106 -> 2.025 (log B = 4.852), harorat 20.3
  urug' 2: loss 5.590 -> 1.930 (log B = 4.852), harorat 18.8

=== 3. Zero-shot: 18 ta tavsifdan eng o'xshashi ===
  tasodifiy daraja: 0.056
  ko'rilgan kombinatsiyalar:   aniqlik 0.974 +- 0.010
  ko'rilmagan kombinatsiyalar: aniqlik 0.519 +- 0.043
  ko'rilmaganlarda atribut bo'yicha: rang 1.000, o'lcham 0.948, shakl 0.540
  ko'rilgan - ko'rilmagan: +0.455 (SE 0.050) -> sezilarli
  oddiy 14 sinfli klassifikator ko'rilmagan kombinatsiyani umuman ayta olmaydi (sinf yo'q)

=== 4. Ko'rilmagan kombinatsiyalar: model nimani tanladi (urug' 0) ===
  ko'k katta uchburchak    to'g'ri 0.82; eng ko'p tanlangan: ko'k katta uchburchak 0.82-bob
  qizil kichik kvadrat     to'g'ri 0.12; eng ko'p tanlangan: qizil kichik doira 0.88-bob
  qizil kichik uchburchak  to'g'ri 0.15; eng ko'p tanlangan: qizil kichik doira 0.80-bob
  yashil katta doira       to'g'ri 0.97; eng ko'p tanlangan: yashil katta doira 0.97-bob
  ⭐ rang va o'lcham kombinatsiyalanadi; shakl - eng qiyin qism

Nima ko'rsatdi: 1-bo'lim — ma'lumot: 18 kombinatsiyadan 14 tasida 2100 juftlik, 13 so'zli lug'at; tavsiflar bir xil kombinatsiya uchun turlicha (qizil katta doira, katta qizil doira). 2-bo'lim — InfoNCE: loss boshida 5.1–5.6 (log B = 4.852 dan yuqori — boshlang'ich harorat 14.3 tasodifiy o'xshashliklarni kuchaytiradi), oxirida ~`2.0; nolga tushmaydi, chunki 128 talik batchda har kombinatsiyadan o'rtacha ~9 juftlik bor va ular bir-biriga "soxta manfiy" — bu yerda loss ning pastki chegarasi ~log 9. Harorat o'z-o'zidan 19–20gacha o'sdi. 3-bo'lim — zero-shot: ko'rilgan kombinatsiyalarda0.974, ko'rilmaganlarda 0.519(tasodifiy0.056); farq +0.455, SE 0.050— sezilarli. Atributlar bo'yicha ko'rilmaganlarda rang1.000, o'lcham 0.948, shakl esa 0.540— rang va o'lcham kombinatsiyalanadi, shakl — yo'q. 4-bo'lim sababni ko'rsatadi:ko'k katta uchburchak (0.82) va yashil katta doira (0.97) yaxshi, lekin qizil kichik kvadratvaqizil kichik uchburchakning 88% va 80% iqizil kichik doira` deb tanildi. O'quvda qizil kichik shakl faqat doira bo'lgan — model "qizil + kichik → doira" yorlig'ini o'rgangan. Bog'liq bo'limlar: 2.3, 2.4.

Misol 2 — Matn → rasm qidiruvi va matn encoderining cheklovlari

python
"""Matn -> rasm qidiruvi (P@k, R-precision, R@k) va so'zlar xaltasi matn encoderining cheklovlari."""

import math

import numpy as np
import torch
import torch.nn as nn
import torch.nn.functional as F

SHAKLLAR = ["doira", "kvadrat", "uchburchak"]
RANGLAR = ["qizil", "yashil", "ko'k"]
OLCHAMLAR = ["katta", "kichik"]
RGB = {"qizil": (0.9, 0.15, 0.15), "yashil": (0.15, 0.8, 0.2), "ko'k": (0.2, 0.3, 0.95)}
KOMBO = [(r, o, s) for s in SHAKLLAR for r in RANGLAR for o in OLCHAMLAR]      # 18 ta
KORILMAGAN = {("ko'k", "katta", "uchburchak"), ("qizil", "kichik", "kvadrat"),
              ("yashil", "katta", "doira"), ("qizil", "kichik", "uchburchak")}
SHABLONLAR = ["{r} {o} {s}", "{o} {r} {s}", "{r} rangli {o} {s}", "bu {o} {r} {s}",
              "{o} {s}, rangi {r}", "rasmda {r} {s}, o'lchami {o}"]


def chiz(rang, olcham, shakl, rng):
    """16x16 RGB rasm: fon + bitta shakl (joyi, rangi biroz tasodifiy)."""
    r = 6.0 if olcham == "katta" else 3.2
    cy, cx = rng.uniform(r + 0.5, 15.5 - r, size=2)
    y, x = np.mgrid[0:16, 0:16] + 0.5
    if shakl == "doira":
        m = (y - cy) ** 2 + (x - cx) ** 2 <= r ** 2
    elif shakl == "kvadrat":
        m = (np.abs(y - cy) <= 0.85 * r) & (np.abs(x - cx) <= 0.85 * r)
    else:
        m = (y >= cy - r) & (y <= cy + r) & (np.abs(x - cx) <= (y - (cy - r)) / 2)
    rasm = 0.1 + 0.05 * rng.standard_normal((3, 16, 16))
    rang_q = np.array(RGB[rang]) + rng.uniform(-0.08, 0.08, 3)
    rasm[:, m] = rang_q[:, None]
    return rasm.astype(np.float32)


def toplam(kombolar, n_har, seed):
    rng = np.random.default_rng(seed)
    X, T, Y = [], [], []
    for ki, (r, o, s) in enumerate(KOMBO):
        if (r, o, s) not in kombolar:
            continue
        for _ in range(n_har):
            X.append(chiz(r, o, s, rng))
            T.append(SHABLONLAR[rng.integers(len(SHABLONLAR))].format(r=r, o=o, s=s))
            Y.append(ki)
    return torch.tensor(np.array(X)), T, torch.tensor(Y)


class RasmEncoder(nn.Module):
    """Kichik CNN: 3x16x16 -> konvolyutsiya (16 kanal) -> max-pool 4x4 -> 256 -> 64 -> 32."""

    def __init__(self, d=32):
        super().__init__()
        self.tarmoq = nn.Sequential(
            nn.Conv2d(3, 16, 3, padding=1), nn.ReLU(), nn.MaxPool2d(4),
            nn.Flatten(), nn.Linear(16 * 16, 64), nn.ReLU(), nn.Linear(64, d))

    def forward(self, x):
        return F.normalize(self.tarmoq(x), dim=-1)


class MatnEncoder(nn.Module):
    """So'z embeddinglari o'rtachasi (bag of words) + MLP. So'z tartibini KO'RMAYDI."""

    def __init__(self, lugat, d=32):
        super().__init__()
        self.lugat = {w: i + 1 for i, w in enumerate(lugat)}          # 0 - to'ldiruvchi
        self.emb = nn.Embedding(len(lugat) + 1, 32, padding_idx=0)
        self.mlp = nn.Sequential(nn.Linear(32, 64), nn.ReLU(), nn.Linear(64, d))

    def tokenla(self, matnlar):
        idx = [[self.lugat.get(w, 0) for w in m.replace(",", "").split()] for m in matnlar]
        L = max(len(i) for i in idx)
        return torch.tensor([i + [0] * (L - len(i)) for i in idx])

    def forward(self, matnlar):
        t = self.tokenla(matnlar)
        e = self.emb(t)
        o = e.sum(1) / (t > 0).sum(1, keepdim=True).clamp(min=1)
        return F.normalize(self.mlp(o), dim=-1)


def lugat_yasash():
    sozlar = set()
    for r, o, s in KOMBO:
        for sh in SHABLONLAR:
            sozlar.update(sh.format(r=r, o=o, s=s).replace(",", "").split())
    return sorted(sozlar)


def orgat_clip(X, T, seed, qadamlar=400, B=128):
    torch.manual_seed(seed)
    re_, me = RasmEncoder(), MatnEncoder(lugat_yasash())
    log_harorat = nn.Parameter(torch.tensor(math.log(1 / 0.07)))
    opt = torch.optim.Adam(list(re_.parameters()) + list(me.parameters()) + [log_harorat], lr=2e-3)
    g = torch.Generator().manual_seed(seed)
    for q in range(qadamlar):
        i = torch.randperm(len(X), generator=g)[:B]
        zi, zt = re_(X[i]), me([T[j] for j in i.tolist()])
        logit = log_harorat.exp().clamp(max=100) * zi @ zt.T               # (B, B)
        nishon = torch.arange(B)
        loss = (F.cross_entropy(logit, nishon) + F.cross_entropy(logit.T, nishon)) / 2   # InfoNCE
        opt.zero_grad()
        loss.backward()
        opt.step()
        if q == 0:
            boshlangich = loss.item()
    re_.eval()
    me.eval()
    return re_, me, boshlangich, loss.item(), log_harorat.exp().item()


def main() -> None:
    torch.set_num_threads(1)
    korilgan = set(KOMBO) - KORILMAGAN
    X, T, _ = toplam(korilgan, 150, seed=0)
    Xt, _, Yt = toplam(set(KOMBO), 40, seed=1)
    yozuvlar = [f"{o} {r} {s}" for r, o, s in KOMBO]
    yangi = torch.tensor([k in KORILMAGAN for k in KOMBO])

    print("=== 1. Matn -> rasm qidiruvi: 18 so'rov, galereya 720 rasm (har kombinatsiyadan 40) ===")
    print("  P@10 - top-10 dagi to'g'ri rasmlar ulushi; R-precision - top-40 da (40 ta to'g'ri bor)")
    print("  rasm -> matn R@1 / R@3 - to'g'ri tavsif 18 tadan top-1 / top-3 da")
    natija = []
    modellar = []
    for seed in range(3):
        re_, me, *_ = orgat_clip(X, T, seed)
        modellar.append((re_, me))
        with torch.no_grad():
            o = me(yozuvlar) @ re_(Xt).T                                 # (18, 720)
        top = o.argsort(1, descending=True)
        togri = Yt[top] == torch.arange(18)[:, None]                     # (18, 720)
        p10 = togri[:, :10].float().mean(1)
        rp = togri[:, :40].float().mean(1)
        rank = (o.T.argsort(1, descending=True) == Yt[:, None]).float().argmax(1)   # rasm -> matn
        r1 = (rank < 1).float()
        r3 = (rank < 3).float()
        yangi_r = yangi[Yt]
        natija.append([p10[~yangi].mean(), p10[yangi].mean(), rp[~yangi].mean(), rp[yangi].mean(),
                       r1[~yangi_r].mean(), r1[yangi_r].mean(), r3[~yangi_r].mean(), r3[yangi_r].mean()])
    v = np.array(natija, dtype=float)
    print("                        ko'rilgan         ko'rilmagan")
    for j, nom in enumerate(("matn->rasm P@10", "matn->rasm R-prec", "rasm->matn R@1", "rasm->matn R@3")):
        a, b = v[:, 2 * j], v[:, 2 * j + 1]
        print(f"  {nom:<19} {a.mean():.3f} +- {a.std(ddof=1) / math.sqrt(3):.3f}   "
              f"{b.mean():.3f} +- {b.std(ddof=1) / math.sqrt(3):.3f}")
    print(f"  tasodifiy daraja: P@10 {1 / 18:.3f}, R@1 {1 / 18:.3f}, R@3 {3 / 18:.3f}")

    print("\n=== 2. Ko'rilmagan so'rovlar: top-10 da qaysi rasmlar (urug' 0) ===")
    re_, me = modellar[0]
    with torch.no_grad():
        galereya = re_(Xt)
        for kombo in sorted(KORILMAGAN):
            so = me([" ".join((kombo[1], kombo[0], kombo[2]))])
            top = (so @ galereya.T)[0].argsort(descending=True)[:10]
            topilgan = [KOMBO[i] for i in Yt[top].tolist()]
            sanoq = {j: [t[j] == kombo[j] for t in topilgan].count(True) for j in range(3)}
            print(f"  {' '.join(kombo):<24} to'liq mos {topilgan.count(kombo):>2}/10; rang {sanoq[0]}/10, "
                  f"o'lcham {sanoq[1]}/10, shakl {sanoq[2]}/10")

    print("\n=== 3. Matn encoderining cheklovlari (so'zlar xaltasi) ===")
    with torch.no_grad():
        juftlar = [("katta qizil doira", "qizil katta doira"),
                   ("qizil katta doira", "qizil emas katta doira"),
                   ("qizil katta doira", "katta doira, rangi qizil"),
                   ("qizil katta doira", "yashil katta doira"),
                   ("qizil katta doira", "qizil kichik kvadrat")]
        for a, b in juftlar:
            kos = (me([a]) @ me([b]).T).item()
            print(f"  {a!r:<22} ~ {b!r:<28} kosinus {kos:.3f}")
        so = me(["qizil emas katta doira"])
        top = (so @ galereya.T)[0].argsort(descending=True)[:20]
        qizil = np.mean([KOMBO[i][0] == "qizil" for i in Yt[top].tolist()])
    print(f"  so'rov 'qizil emas katta doira': top-20 dagi qizil rasmlar ulushi {qizil:.2f}")
    print("  'emas' lug'atda yo'q -> e'tiborsiz; so'z tartibi ham ko'rinmaydi")
    print("  ⭐ inkor va munosabatlar ('chapda', 'emas') - CLIP turidagi modellarning ma'lum zaif joyi")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Matn -> rasm qidiruvi: 18 so'rov, galereya 720 rasm (har kombinatsiyadan 40) ===
  P@10 - top-10 dagi to'g'ri rasmlar ulushi; R-precision - top-40 da (40 ta to'g'ri bor)
  rasm -> matn R@1 / R@3 - to'g'ri tavsif 18 tadan top-1 / top-3 da
                        ko'rilgan         ko'rilmagan
  matn->rasm P@10     0.955 +- 0.006   0.758 +- 0.042
  matn->rasm R-prec   0.923 +- 0.013   0.673 +- 0.045
  rasm->matn R@1      0.974 +- 0.010   0.519 +- 0.043
  rasm->matn R@3      0.999 +- 0.001   0.898 +- 0.033
  tasodifiy daraja: P@10 0.056, R@1 0.056, R@3 0.167

=== 2. Ko'rilmagan so'rovlar: top-10 da qaysi rasmlar (urug' 0) ===
  ko'k katta uchburchak    to'liq mos 10/10; rang 10/10, o'lcham 10/10, shakl 10/10
  qizil kichik kvadrat     to'liq mos  6/10; rang 10/10, o'lcham 10/10, shakl 6/10
  qizil kichik uchburchak  to'liq mos  6/10; rang 7/10, o'lcham 10/10, shakl 9/10
  yashil katta doira       to'liq mos 10/10; rang 10/10, o'lcham 10/10, shakl 10/10

=== 3. Matn encoderining cheklovlari (so'zlar xaltasi) ===
  'katta qizil doira'    ~ 'qizil katta doira'          kosinus 1.000
  'qizil katta doira'    ~ 'qizil emas katta doira'     kosinus 1.000
  'qizil katta doira'    ~ 'katta doira, rangi qizil'   kosinus 0.891
  'qizil katta doira'    ~ 'yashil katta doira'         kosinus 0.614
  'qizil katta doira'    ~ 'qizil kichik kvadrat'       kosinus 0.167
  so'rov 'qizil emas katta doira': top-20 dagi qizil rasmlar ulushi 1.00
  'emas' lug'atda yo'q -> e'tiborsiz; so'z tartibi ham ko'rinmaydi
  ⭐ inkor va munosabatlar ('chapda', 'emas') - CLIP turidagi modellarning ma'lum zaif joyi

Nima ko'rsatdi: 1-bo'lim — qidiruv o'lchovlari (3 urug'): ko'rilgan so'rovlarda P@10 0.955, R-precision 0.923; ko'rilmaganlarda 0.758 va 0.673. Rasm → matn yo'nalishida R@1 0.974 va 0.519 (1-misoldagi zero-shot aniqlikning o'zi), R@3 esa ko'rilmaganlarda ham 0.898 — to'g'ri tavsif ko'pincha top-3 da, lekin birinchi emas. Matn → rasm qidiruvi ko'rilmagan kombinatsiyada zero-shot dan yaxshiroq, chunki top-10 uchun galereyadagi eng mos 10 ta rasmni topish kifoya. 2-bo'lim: ko'k katta uchburchak va yashil katta doira so'rovlarida top-10 ning 10 tasi ham to'liq mos; qizil kichik kvadrat da 6/10 — rang va o'lcham 10/10, xato faqat shaklda; qizil kichik uchburchak da ham 6/10. 3-bo'lim — so'zlar xaltasi encoderining cheklovlari: katta qizil doira va qizil katta doira kosinusi 1.000 (tartib ko'rinmaydi), qizil emas katta doira ham 1.000 — emas lug'atda yo'q va e'tiborsiz qoldi; shu so'rovning top-20 natijasining 1.00 qismi aynan qizil. Boshqa shablondagi katta doira, rangi qizil 0.891 — yaqin, rang almashtirilsa 0.614, hamma atribut boshqa bo'lsa 0.167. Bog'liq bo'limlar: 2.5, 2.6.

Misol 3 — Matn sharti bilan kichik generator (shartli VAE)

python
"""Matn sharti bilan kichik generator (shartli VAE): ko'rilmagan kombinatsiyalarda atributlar to'g'riligi."""

import math

import numpy as np
import torch
import torch.nn as nn
import torch.nn.functional as F

SHAKLLAR = ["doira", "kvadrat", "uchburchak"]
RANGLAR = ["qizil", "yashil", "ko'k"]
OLCHAMLAR = ["katta", "kichik"]
RGB = {"qizil": (0.9, 0.15, 0.15), "yashil": (0.15, 0.8, 0.2), "ko'k": (0.2, 0.3, 0.95)}
KOMBO = [(r, o, s) for s in SHAKLLAR for r in RANGLAR for o in OLCHAMLAR]      # 18 ta
KORILMAGAN = {("ko'k", "katta", "uchburchak"), ("qizil", "kichik", "kvadrat"),
              ("yashil", "katta", "doira"), ("qizil", "kichik", "uchburchak")}
SHABLONLAR = ["{r} {o} {s}", "{o} {r} {s}", "{r} rangli {o} {s}", "bu {o} {r} {s}",
              "{o} {s}, rangi {r}", "rasmda {r} {s}, o'lchami {o}"]


def chiz(rang, olcham, shakl, rng):
    """16x16 RGB rasm: fon + bitta shakl (joyi, rangi biroz tasodifiy)."""
    r = 6.0 if olcham == "katta" else 3.2
    cy, cx = rng.uniform(r + 0.5, 15.5 - r, size=2)
    y, x = np.mgrid[0:16, 0:16] + 0.5
    if shakl == "doira":
        m = (y - cy) ** 2 + (x - cx) ** 2 <= r ** 2
    elif shakl == "kvadrat":
        m = (np.abs(y - cy) <= 0.85 * r) & (np.abs(x - cx) <= 0.85 * r)
    else:
        m = (y >= cy - r) & (y <= cy + r) & (np.abs(x - cx) <= (y - (cy - r)) / 2)
    rasm = 0.1 + 0.05 * rng.standard_normal((3, 16, 16))
    rang_q = np.array(RGB[rang]) + rng.uniform(-0.08, 0.08, 3)
    rasm[:, m] = rang_q[:, None]
    return rasm.astype(np.float32)


def toplam(kombolar, n_har, seed):
    rng = np.random.default_rng(seed)
    X, T, Y = [], [], []
    for ki, (r, o, s) in enumerate(KOMBO):
        if (r, o, s) not in kombolar:
            continue
        for _ in range(n_har):
            X.append(chiz(r, o, s, rng))
            T.append(SHABLONLAR[rng.integers(len(SHABLONLAR))].format(r=r, o=o, s=s))
            Y.append(ki)
    return torch.tensor(np.array(X)), T, torch.tensor(Y)


class MatnEncoder(nn.Module):
    """So'z embeddinglari o'rtachasi (bag of words) + MLP. So'z tartibini KO'RMAYDI."""

    def __init__(self, lugat, d=32):
        super().__init__()
        self.lugat = {w: i + 1 for i, w in enumerate(lugat)}          # 0 - to'ldiruvchi
        self.emb = nn.Embedding(len(lugat) + 1, 32, padding_idx=0)
        self.mlp = nn.Sequential(nn.Linear(32, 64), nn.ReLU(), nn.Linear(64, d))

    def tokenla(self, matnlar):
        idx = [[self.lugat.get(w, 0) for w in m.replace(",", "").split()] for m in matnlar]
        L = max(len(i) for i in idx)
        return torch.tensor([i + [0] * (L - len(i)) for i in idx])

    def forward(self, matnlar):
        t = self.tokenla(matnlar)
        e = self.emb(t)
        o = e.sum(1) / (t > 0).sum(1, keepdim=True).clamp(min=1)
        return F.normalize(self.mlp(o), dim=-1)


def lugat_yasash():
    sozlar = set()
    for r, o, s in KOMBO:
        for sh in SHABLONLAR:
            sozlar.update(sh.format(r=r, o=o, s=s).replace(",", "").split())
    return sorted(sozlar)


class AtributKlassifikator(nn.Module):
    """Baholovchi: bitta CNN tanasi, uchta bosh (rang, o'lcham, shakl). BARCHA 18 kombinatsiyada o'rgatiladi."""

    def __init__(self):
        super().__init__()
        self.tana = nn.Sequential(nn.Conv2d(3, 16, 3, padding=1), nn.ReLU(), nn.MaxPool2d(4),
                                  nn.Flatten(), nn.Linear(256, 64), nn.ReLU())
        self.boshlar = nn.ModuleList([nn.Linear(64, 3), nn.Linear(64, 2), nn.Linear(64, 3)])

    def forward(self, x):
        h = self.tana(x)
        return [b(h) for b in self.boshlar]


def atribut_nishon(Y):
    k = torch.tensor([[RANGLAR.index(r), OLCHAMLAR.index(o), SHAKLLAR.index(s)] for r, o, s in KOMBO])
    return k[Y]


def orgat_atribut(X, Y, seed=0):
    torch.manual_seed(seed)
    m = AtributKlassifikator()
    opt = torch.optim.Adam(m.parameters(), lr=5e-3)
    A = atribut_nishon(Y)
    g = torch.Generator().manual_seed(seed)
    for _ in range(500):
        i = torch.randperm(len(X), generator=g)[:128]
        loss = sum(F.cross_entropy(o, A[i, j]) for j, o in enumerate(m(X[i])))
        opt.zero_grad()
        loss.backward()
        opt.step()
    m.eval()
    return m


class ShartliVAE(nn.Module):
    """Shartli VAE: shart c - matn embeddingi (kompozitsion) yoki kombinatsiya ID si (one-hot)."""

    def __init__(self, shart_turi, z=8, d=32):
        super().__init__()
        self.shart_turi = shart_turi
        if shart_turi == "matn":
            self.shart = MatnEncoder(lugat_yasash(), d)
        else:
            self.shart = nn.Embedding(len(KOMBO), d)
        self.enc = nn.Sequential(nn.Linear(768 + d, 128), nn.ReLU(), nn.Linear(128, 2 * z))
        self.dec = nn.Sequential(nn.Linear(z + d, 128), nn.ReLU(), nn.Linear(128, 768), nn.Sigmoid())
        self.z = z

    def c(self, matnlar, idx):
        if self.shart_turi == "matn":
            return self.shart.mlp(self._ortacha(matnlar))
        return self.shart(idx)

    def _ortacha(self, matnlar):
        t = self.shart.tokenla(matnlar)
        return self.shart.emb(t).sum(1) / (t > 0).sum(1, keepdim=True).clamp(min=1)

    def forward(self, x, c):
        mu, logvar = self.enc(torch.cat([x.flatten(1), c], 1)).chunk(2, 1)
        return mu, logvar

    def dekod(self, z, c):
        return self.dec(torch.cat([z, c], 1)).view(-1, 3, 16, 16)


def orgat_cvae(X, T, Y, shart_turi, seed, qadamlar=500, B=128):
    torch.manual_seed(seed)
    m = ShartliVAE(shart_turi)
    opt = torch.optim.Adam(m.parameters(), lr=2e-3)
    g = torch.Generator().manual_seed(seed)
    for _ in range(qadamlar):
        i = torch.randperm(len(X), generator=g)[:B]
        c = m.c([T[j] for j in i.tolist()], Y[i])
        mu, logvar = m(X[i], c)
        z = mu + (0.5 * logvar).exp() * torch.randn(mu.shape, generator=g)
        qayta = ((m.dekod(z, c) - X[i]) ** 2).sum((1, 2, 3)) / (2 * 0.1 ** 2)
        kl = 0.5 * (mu ** 2 + logvar.exp() - 1 - logvar).sum(1)
        loss = (qayta + kl).mean()
        opt.zero_grad()
        loss.backward()
        opt.step()
    m.eval()
    return m


def rangli_ascii(rasmlar):
    """Har piksel: nuqta - fon, Q/Y/K - eng kuchli kanal (qizil/yashil/ko'k)."""
    satrlar = []
    for r in range(16):
        qism = []
        for x in rasmlar:
            q = ""
            for c in range(16):
                p = x[:, r, c]
                q += "\u00b7" if p.max() < 0.4 else "QYK"[int(p.argmax())]
            qism.append(q)
        satrlar.append("    " + "   ".join(qism))
    return "\n".join(satrlar)


def main() -> None:
    torch.set_num_threads(1)
    korilgan = set(KOMBO) - KORILMAGAN
    X, T, Y = toplam(korilgan, 150, seed=0)
    Xa, _, Ya = toplam(set(KOMBO), 60, seed=2)             # baholovchi uchun: hamma kombinatsiya
    Xt, _, Yt = toplam(set(KOMBO), 20, seed=3)
    baholovchi = orgat_atribut(Xa, Ya)
    with torch.no_grad():
        aniq = [(o.argmax(1) == atribut_nishon(Yt)[:, j]).float().mean().item()
                for j, o in enumerate(baholovchi(Xt))]
    print("=== 1. Baholovchi: atribut klassifikatorlari (haqiqiy test rasmlarida) ===")
    print(f"  rang {aniq[0]:.3f}, o'lcham {aniq[1]:.3f}, shakl {aniq[2]:.3f}")
    print("  (baholovchi hamma 18 kombinatsiyani ko'rgan - u faqat hakam, generator emas)")

    print("\n=== 2. Shartli VAE: matn sharti va kombinatsiya ID si (3 urug') ===")
    print("  har kombinatsiyaga 40 namuna, shart - 'o'lcham rang shakl' tavsifi")
    yozuvlar = [f"{o} {r} {s}" for r, o, s in KOMBO]
    nishon = atribut_nishon(torch.arange(len(KOMBO)).repeat_interleave(40))
    yangi = torch.tensor([KOMBO[i] in KORILMAGAN for i in range(len(KOMBO))]).repeat_interleave(40)
    natija, rasmlar = {}, {}
    for tur in ("matn", "ID"):
        for seed in range(3):
            m = orgat_cvae(X, T, Y, tur, seed)
            g = torch.Generator().manual_seed(100 + seed)
            with torch.no_grad():
                idx = torch.arange(len(KOMBO)).repeat_interleave(40)
                c = m.c([yozuvlar[i] for i in idx.tolist()], idx)
                s = m.dekod(torch.randn((len(idx), m.z), generator=g), c)
                tog = torch.stack([o.argmax(1) == nishon[:, j] for j, o in enumerate(baholovchi(s))], 1)
            hamma = tog.all(1)
            natija.setdefault(tur, []).append([tog[~yangi].float().mean(0).tolist() + [hamma[~yangi].float().mean().item()],
                                               tog[yangi].float().mean(0).tolist() + [hamma[yangi].float().mean().item()]])
            if seed == 0:
                rasmlar[tur] = s
    print("  shart   to'plam        rang    o'lcham  shakl   hammasi")
    for tur, v in natija.items():
        v = np.array(v)                                      # (urug', to'plam, 4)
        for j, nom in enumerate(("ko'rilgan", "ko'rilmagan")):
            q = v[:, j].mean(0)
            print(f"  {tur:<6}  {nom:<12} {q[0]:>6.3f}  {q[1]:>6.3f}  {q[2]:>6.3f}  {q[3]:>7.3f}")

    print("\n=== 3. Ko'rilmagan kombinatsiyalarda 'hammasi to'g'ri': matn - ID (juftlashgan) ===")
    a = np.array(natija["matn"])[:, 1, 3]
    b = np.array(natija["ID"])[:, 1, 3]
    f = a - b
    se = f.std(ddof=1) / math.sqrt(3)
    print(f"  matn {a.mean():.3f}, ID {b.mean():.3f}, farq {f.mean():+.3f} (SE {se:.3f}) -> "
          + ("sezilarli" if abs(f.mean()) > 2 * se else "sezilarli emas"))
    print(f"  tasodifiy daraja (3 x 2 x 3 atribut): {1 / 18:.3f}")
    print("  ID sharti: ko'rilmagan kombinatsiya embeddingi hech qachon o'rgatilmagan (tasodifiy)")

    print("\n=== 4. Ko'rilmagan so'rov 'katta ko'k uchburchak' (urug' 0, 2 namuna) ===")
    ki = KOMBO.index(("ko'k", "katta", "uchburchak"))
    print("  chapda: matn sharti | o'ngda: ID sharti   (Q/Y/K - qizil/yashil/ko'k)")
    print(rangli_ascii([rasmlar["matn"][ki * 40], rasmlar["matn"][ki * 40 + 1],
                        rasmlar["ID"][ki * 40], rasmlar["ID"][ki * 40 + 1]]))


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Baholovchi: atribut klassifikatorlari (haqiqiy test rasmlarida) ===
  rang 1.000, o'lcham 1.000, shakl 0.958
  (baholovchi hamma 18 kombinatsiyani ko'rgan - u faqat hakam, generator emas)

=== 2. Shartli VAE: matn sharti va kombinatsiya ID si (3 urug') ===
  har kombinatsiyaga 40 namuna, shart - 'o'lcham rang shakl' tavsifi
  shart   to'plam        rang    o'lcham  shakl   hammasi
  matn    ko'rilgan     0.810   1.000   0.474    0.396
  matn    ko'rilmagan   0.642   0.994   0.417    0.304
  ID      ko'rilgan     0.911   1.000   0.501    0.473
  ID      ko'rilmagan   0.377   0.477   0.250    0.000

=== 3. Ko'rilmagan kombinatsiyalarda 'hammasi to'g'ri': matn - ID (juftlashgan) ===
  matn 0.304, ID 0.000, farq +0.304 (SE 0.015) -> sezilarli
  tasodifiy daraja (3 x 2 x 3 atribut): 0.056
  ID sharti: ko'rilmagan kombinatsiya embeddingi hech qachon o'rgatilmagan (tasodifiy)

=== 4. Ko'rilmagan so'rov 'katta ko'k uchburchak' (urug' 0, 2 namuna) ===
  chapda: matn sharti | o'ngda: ID sharti   (Q/Y/K - qizil/yashil/ko'k)
    ················   ················   ················   ················
    ················   ················   ·····Q·Q········   ················
    ················   ················   ················   ················
    ······KKK·······   ················   ················   ················
    ····KKKKKKKK····   ·····KKKKK······   ············Q···   ················
    ····KKKKKKKKK···   ········Q·K·K···   ················   ············Y···
    ····KKKKKKKK····   ·······QQK·KKK··   ·············Q··   ···········Y····
    ···KKKKKKKKKK···   ·······QQ·KKKK··   ···········Y····   ··········QY·Y··
    ···KKKKKKKKKK···   ·······KKKKKK···   ·······Y········   ···········YYY··
    ···KKKKKKKKKK···   ······QQKKKKKK··   ·····Y·Y········   ·········YYYYY··
    ···KKKKKKKKKK···   ····KKKKKKKKKK··   ·····YYYYYY·····   ········YYYYYY··
    ···KKKKKKKKKK···   ····KKKKKKKKKK··   ······YYY·······   ·······YYYYYYY··
    ····KKKKKKKKK···   ····KQKKKKKKKK··   ················   ·········Y······
    ····K·····QQQ···   ····QKKKKQKQKQ··   ················   ···········Q····
    ················   ······QQQKQQ····   ················   ················
    ················   ················   ················   ················

Nima ko'rsatdi: 1-bo'lim — hakam: atribut klassifikatorlari haqiqiy test rasmlarida rang va o'lchamda 1.000, shaklda 0.958 — ya'ni shakl bo'yicha hakamning o'zi ham ~4% xato qiladi. 2-bo'lim — ikki xil shart, 3 urug'. Ko'rilgan kombinatsiyalarda ikkalasi yaqin: "hammasi to'g'ri" matn sharti 0.396, ID sharti 0.473 (ID rangni yaxshiroq beradi: 0.911 va 0.810). Ko'rilmaganlarda farq keskin: matn sharti 0.304, ID sharti 0.000 — ID ning rang, o'lcham va shakl aniqligi tasodifiy darajada (0.377, 0.477, 0.250), chunki bu kombinatsiyalarning embeddingi hech qachon o'rgatilmagan. Matn sharti esa ko'rilmaganlarda o'lchamni deyarli mukammal (0.994) va rangni 0.642 beradi. Hamma joyda eng zaif atribut — shakl (0.417–0.501): kichik MLP dekoder o'rtacha, xira shakl chizadi; chegara aniqligi 16x16 da shaklni ajratish uchun hal qiluvchi. 3-bo'lim: ko'rilmaganlarda matn - ID farqi +0.304, SE 0.015 — sezilarli. 4-bo'lim — ASCII: katta ko'k uchburchak (o'quvda ko'rilmagan) so'roviga matn sharti ko'k (K) katta shakl chizdi (biri pastga kengayuvchi, uchburchakka o'xshash), ID sharti esa kichik yashil-qizil dog'lar — so'rovga aloqasiz. Bog'liq bo'lim: 2.7.

Misol 4 — CLIP bilan qayta saralash (best-of-K)

python
"""CLIP bilan qayta saralash: shartli VAE nomzodlaridan eng mosini tanlash (best-of-K)."""

import math

import numpy as np
import torch
import torch.nn as nn
import torch.nn.functional as F
from sklearn.metrics import roc_auc_score

SHAKLLAR = ["doira", "kvadrat", "uchburchak"]
RANGLAR = ["qizil", "yashil", "ko'k"]
OLCHAMLAR = ["katta", "kichik"]
RGB = {"qizil": (0.9, 0.15, 0.15), "yashil": (0.15, 0.8, 0.2), "ko'k": (0.2, 0.3, 0.95)}
KOMBO = [(r, o, s) for s in SHAKLLAR for r in RANGLAR for o in OLCHAMLAR]      # 18 ta
KORILMAGAN = {("ko'k", "katta", "uchburchak"), ("qizil", "kichik", "kvadrat"),
              ("yashil", "katta", "doira"), ("qizil", "kichik", "uchburchak")}
SHABLONLAR = ["{r} {o} {s}", "{o} {r} {s}", "{r} rangli {o} {s}", "bu {o} {r} {s}",
              "{o} {s}, rangi {r}", "rasmda {r} {s}, o'lchami {o}"]


def chiz(rang, olcham, shakl, rng):
    """16x16 RGB rasm: fon + bitta shakl (joyi, rangi biroz tasodifiy)."""
    r = 6.0 if olcham == "katta" else 3.2
    cy, cx = rng.uniform(r + 0.5, 15.5 - r, size=2)
    y, x = np.mgrid[0:16, 0:16] + 0.5
    if shakl == "doira":
        m = (y - cy) ** 2 + (x - cx) ** 2 <= r ** 2
    elif shakl == "kvadrat":
        m = (np.abs(y - cy) <= 0.85 * r) & (np.abs(x - cx) <= 0.85 * r)
    else:
        m = (y >= cy - r) & (y <= cy + r) & (np.abs(x - cx) <= (y - (cy - r)) / 2)
    rasm = 0.1 + 0.05 * rng.standard_normal((3, 16, 16))
    rang_q = np.array(RGB[rang]) + rng.uniform(-0.08, 0.08, 3)
    rasm[:, m] = rang_q[:, None]
    return rasm.astype(np.float32)


def toplam(kombolar, n_har, seed):
    rng = np.random.default_rng(seed)
    X, T, Y = [], [], []
    for ki, (r, o, s) in enumerate(KOMBO):
        if (r, o, s) not in kombolar:
            continue
        for _ in range(n_har):
            X.append(chiz(r, o, s, rng))
            T.append(SHABLONLAR[rng.integers(len(SHABLONLAR))].format(r=r, o=o, s=s))
            Y.append(ki)
    return torch.tensor(np.array(X)), T, torch.tensor(Y)


class RasmEncoder(nn.Module):
    """Kichik CNN: 3x16x16 -> konvolyutsiya (16 kanal) -> max-pool 4x4 -> 256 -> 64 -> 32."""

    def __init__(self, d=32):
        super().__init__()
        self.tarmoq = nn.Sequential(
            nn.Conv2d(3, 16, 3, padding=1), nn.ReLU(), nn.MaxPool2d(4),
            nn.Flatten(), nn.Linear(16 * 16, 64), nn.ReLU(), nn.Linear(64, d))

    def forward(self, x):
        return F.normalize(self.tarmoq(x), dim=-1)


class MatnEncoder(nn.Module):
    """So'z embeddinglari o'rtachasi (bag of words) + MLP. So'z tartibini KO'RMAYDI."""

    def __init__(self, lugat, d=32):
        super().__init__()
        self.lugat = {w: i + 1 for i, w in enumerate(lugat)}          # 0 - to'ldiruvchi
        self.emb = nn.Embedding(len(lugat) + 1, 32, padding_idx=0)
        self.mlp = nn.Sequential(nn.Linear(32, 64), nn.ReLU(), nn.Linear(64, d))

    def tokenla(self, matnlar):
        idx = [[self.lugat.get(w, 0) for w in m.replace(",", "").split()] for m in matnlar]
        L = max(len(i) for i in idx)
        return torch.tensor([i + [0] * (L - len(i)) for i in idx])

    def forward(self, matnlar):
        t = self.tokenla(matnlar)
        e = self.emb(t)
        o = e.sum(1) / (t > 0).sum(1, keepdim=True).clamp(min=1)
        return F.normalize(self.mlp(o), dim=-1)


def lugat_yasash():
    sozlar = set()
    for r, o, s in KOMBO:
        for sh in SHABLONLAR:
            sozlar.update(sh.format(r=r, o=o, s=s).replace(",", "").split())
    return sorted(sozlar)


def orgat_clip(X, T, seed, qadamlar=400, B=128):
    torch.manual_seed(seed)
    re_, me = RasmEncoder(), MatnEncoder(lugat_yasash())
    log_harorat = nn.Parameter(torch.tensor(math.log(1 / 0.07)))
    opt = torch.optim.Adam(list(re_.parameters()) + list(me.parameters()) + [log_harorat], lr=2e-3)
    g = torch.Generator().manual_seed(seed)
    for q in range(qadamlar):
        i = torch.randperm(len(X), generator=g)[:B]
        zi, zt = re_(X[i]), me([T[j] for j in i.tolist()])
        logit = log_harorat.exp().clamp(max=100) * zi @ zt.T               # (B, B)
        nishon = torch.arange(B)
        loss = (F.cross_entropy(logit, nishon) + F.cross_entropy(logit.T, nishon)) / 2   # InfoNCE
        opt.zero_grad()
        loss.backward()
        opt.step()
        if q == 0:
            boshlangich = loss.item()
    re_.eval()
    me.eval()
    return re_, me, boshlangich, loss.item(), log_harorat.exp().item()


class AtributKlassifikator(nn.Module):
    """Baholovchi: bitta CNN tanasi, uchta bosh (rang, o'lcham, shakl). BARCHA 18 kombinatsiyada o'rgatiladi."""

    def __init__(self):
        super().__init__()
        self.tana = nn.Sequential(nn.Conv2d(3, 16, 3, padding=1), nn.ReLU(), nn.MaxPool2d(4),
                                  nn.Flatten(), nn.Linear(256, 64), nn.ReLU())
        self.boshlar = nn.ModuleList([nn.Linear(64, 3), nn.Linear(64, 2), nn.Linear(64, 3)])

    def forward(self, x):
        h = self.tana(x)
        return [b(h) for b in self.boshlar]


def atribut_nishon(Y):
    k = torch.tensor([[RANGLAR.index(r), OLCHAMLAR.index(o), SHAKLLAR.index(s)] for r, o, s in KOMBO])
    return k[Y]


def orgat_atribut(X, Y, seed=0):
    torch.manual_seed(seed)
    m = AtributKlassifikator()
    opt = torch.optim.Adam(m.parameters(), lr=5e-3)
    A = atribut_nishon(Y)
    g = torch.Generator().manual_seed(seed)
    for _ in range(500):
        i = torch.randperm(len(X), generator=g)[:128]
        loss = sum(F.cross_entropy(o, A[i, j]) for j, o in enumerate(m(X[i])))
        opt.zero_grad()
        loss.backward()
        opt.step()
    m.eval()
    return m


class ShartliVAE(nn.Module):
    """Shartli VAE: shart c - matn embeddingi (kompozitsion) yoki kombinatsiya ID si (one-hot)."""

    def __init__(self, shart_turi, z=8, d=32):
        super().__init__()
        self.shart_turi = shart_turi
        if shart_turi == "matn":
            self.shart = MatnEncoder(lugat_yasash(), d)
        else:
            self.shart = nn.Embedding(len(KOMBO), d)
        self.enc = nn.Sequential(nn.Linear(768 + d, 128), nn.ReLU(), nn.Linear(128, 2 * z))
        self.dec = nn.Sequential(nn.Linear(z + d, 128), nn.ReLU(), nn.Linear(128, 768), nn.Sigmoid())
        self.z = z

    def c(self, matnlar, idx):
        if self.shart_turi == "matn":
            return self.shart.mlp(self._ortacha(matnlar))
        return self.shart(idx)

    def _ortacha(self, matnlar):
        t = self.shart.tokenla(matnlar)
        return self.shart.emb(t).sum(1) / (t > 0).sum(1, keepdim=True).clamp(min=1)

    def forward(self, x, c):
        mu, logvar = self.enc(torch.cat([x.flatten(1), c], 1)).chunk(2, 1)
        return mu, logvar

    def dekod(self, z, c):
        return self.dec(torch.cat([z, c], 1)).view(-1, 3, 16, 16)


def orgat_cvae(X, T, Y, shart_turi, seed, qadamlar=500, B=128):
    torch.manual_seed(seed)
    m = ShartliVAE(shart_turi)
    opt = torch.optim.Adam(m.parameters(), lr=2e-3)
    g = torch.Generator().manual_seed(seed)
    for _ in range(qadamlar):
        i = torch.randperm(len(X), generator=g)[:B]
        c = m.c([T[j] for j in i.tolist()], Y[i])
        mu, logvar = m(X[i], c)
        z = mu + (0.5 * logvar).exp() * torch.randn(mu.shape, generator=g)
        qayta = ((m.dekod(z, c) - X[i]) ** 2).sum((1, 2, 3)) / (2 * 0.1 ** 2)
        kl = 0.5 * (mu ** 2 + logvar.exp() - 1 - logvar).sum(1)
        loss = (qayta + kl).mean()
        opt.zero_grad()
        loss.backward()
        opt.step()
    m.eval()
    return m


def main() -> None:
    torch.set_num_threads(1)
    korilgan = set(KOMBO) - KORILMAGAN
    X, T, Y = toplam(korilgan, 150, seed=0)
    Xa, _, Ya = toplam(set(KOMBO), 60, seed=2)
    baholovchi = orgat_atribut(Xa, Ya)
    re_, me, *_ = orgat_clip(X, T, seed=0)                  # hakam EMAS - generatsiya qismi
    gen = orgat_cvae(X, T, Y, "matn", seed=0)
    yozuvlar = [f"{o} {r} {s}" for r, o, s in KOMBO]
    print("=== 1. Quvur: matn -> shartli VAE (K ta nomzod) -> CLIP bahosi -> eng yaxshisi ===")
    print("  CLIP va VAE faqat ko'rilgan 14 kombinatsiyada o'rgatilgan; baholovchi - alohida")
    G, K = 12, 8                                            # har so'rovga 12 guruh x 8 nomzod

    def namunalar(seed):
        g = torch.Generator().manual_seed(seed)
        idx = torch.arange(len(KOMBO)).repeat_interleave(G * K)
        with torch.no_grad():
            c = gen.c([yozuvlar[i] for i in idx.tolist()], idx)
            s = gen.dekod(torch.randn((len(idx), gen.z), generator=g), c)
            ball = (re_(s) * me(yozuvlar)[idx]).sum(1)       # kosinus: rasm va o'z so'rovi
            nishon = atribut_nishon(idx)
            tog = torch.stack([o.argmax(1) == nishon[:, j] for j, o in enumerate(baholovchi(s))], 1).all(1)
        return idx.view(len(KOMBO), G, K), ball.view(len(KOMBO), G, K), tog.view(len(KOMBO), G, K)

    yangi = torch.tensor([k in KORILMAGAN for k in KOMBO])
    print("\n=== 2. Best-of-K: CLIP bo'yicha eng yuqori nomzod (3 urug', 'hammasi to'g'ri' ulushi) ===")
    print("    K    ko'rilgan   ko'rilmagan")
    natija = {}
    for seed in range(3):
        _, ball, tog = namunalar(50 + seed)
        for k in (1, 2, 4, 8):
            eng = ball[:, :, :k].argmax(2, keepdim=True)
            tanlangan = tog[:, :, :k].gather(2, eng)[:, :, 0].float().mean(1)       # (18,)
            natija.setdefault(k, []).append((tanlangan[~yangi].mean().item(), tanlangan[yangi].mean().item()))
    for k, v in natija.items():
        v = np.array(v)
        print(f"  {k:>3}    {v[:, 0].mean():.3f}       {v[:, 1].mean():.3f}")
    for j, nom in enumerate(("ko'rilgan", "ko'rilmagan")):
        f = np.array(natija[8])[:, j] - np.array(natija[1])[:, j]
        se = f.std(ddof=1) / math.sqrt(3)
        print(f"  {nom:<12} K = 8 - K = 1: {f.mean():+.3f} (SE {se:.3f}) -> "
              + ("sezilarli" if abs(f.mean()) > 2 * se else "sezilarli emas"))

    print("\n=== 3. CLIP bahosi to'g'rilikni qanchalik ajratadi (AUC, urug' 50) ===")
    idx, ball, tog = namunalar(50)
    for nom, m in (("ko'rilgan", ~yangi), ("ko'rilmagan", yangi)):
        b, t = ball[m].flatten().numpy(), tog[m].flatten().numpy()
        print(f"  {nom:<12} to'g'ri ulushi {t.mean():.3f}, AUC(CLIP bahosi -> hammasi to'g'ri) "
              f"{roc_auc_score(t, b):.3f}")
    print("  AUC 0.5 - tasodifiy, 1.0 - mukammal ajratish")

    print("\n=== 4. Narx va xulosa ===")
    print(f"  best-of-{K}: {K} barobar ko'p generatsiya + {K} ta CLIP baholash har so'rovga")
    k8 = np.array(natija[8]).mean(0)
    k1 = np.array(natija[1]).mean(0)
    print(f"  ko'rilmagan: {k1[1]:.3f} -> {k8[1]:.3f}; ko'rilgan: {k1[0]:.3f} -> {k8[0]:.3f}")
    if k8[1] - k1[1] < k8[0] - k1[0]:
        print("  qayta saralash ko'rilmagan kombinatsiyada kamroq yordam berdi: tanlovchi CLIP ham")
        print("  bu kombinatsiyalarni yomonroq taniydi (1-misol) - ko'r nuqtalari generator bilan umumiy")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Quvur: matn -> shartli VAE (K ta nomzod) -> CLIP bahosi -> eng yaxshisi ===
  CLIP va VAE faqat ko'rilgan 14 kombinatsiyada o'rgatilgan; baholovchi - alohida

=== 2. Best-of-K: CLIP bo'yicha eng yuqori nomzod (3 urug', 'hammasi to'g'ri' ulushi) ===
    K    ko'rilgan   ko'rilmagan
    1    0.333       0.285
    2    0.361       0.333
    4    0.433       0.375
    8    0.460       0.375
  ko'rilgan    K = 8 - K = 1: +0.127 (SE 0.010) -> sezilarli
  ko'rilmagan  K = 8 - K = 1: +0.090 (SE 0.018) -> sezilarli

=== 3. CLIP bahosi to'g'rilikni qanchalik ajratadi (AUC, urug' 50) ===
  ko'rilgan    to'g'ri ulushi 0.337, AUC(CLIP bahosi -> hammasi to'g'ri) 0.896
  ko'rilmagan  to'g'ri ulushi 0.273, AUC(CLIP bahosi -> hammasi to'g'ri) 0.764
  AUC 0.5 - tasodifiy, 1.0 - mukammal ajratish

=== 4. Narx va xulosa ===
  best-of-8: 8 barobar ko'p generatsiya + 8 ta CLIP baholash har so'rovga
  ko'rilmagan: 0.285 -> 0.375; ko'rilgan: 0.333 -> 0.460
  qayta saralash ko'rilmagan kombinatsiyada kamroq yordam berdi: tanlovchi CLIP ham
  bu kombinatsiyalarni yomonroq taniydi (1-misol) - ko'r nuqtalari generator bilan umumiy

Nima ko'rsatdi: quvur uch qismdan: matn sharti bilan VAE nomzodlar yaratadi, 1-misoldagi CLIP ularni so'rovga mosligi bo'yicha baholaydi, alohida atribut klassifikatorlari esa hakam. 2-bo'lim — best-of-K: "hammasi to'g'ri" ulushi ko'rilganlarda 0.333 (K = 1) dan 0.460 (K = 8) ga, ko'rilmaganlarda 0.285 dan 0.375 ga oshdi; ikkala farq ham sezilarli (+0.127, SE 0.010 va +0.090, SE 0.018). Ko'rilmaganlarda K = 4 dan keyin yaxshilanish to'xtadi (0.375 va 0.375). 3-bo'lim sababni o'lchaydi: CLIP bahosi to'g'ri nomzodni ko'rilgan kombinatsiyalarda AUC 0.896 bilan ajratadi, ko'rilmaganlarda — 0.764: tanlovchi ham bu kombinatsiyalarni yomonroq taniydi (1-misolda zero-shot 0.519). 4-bo'lim: narxi — 8 barobar generatsiya va 8 ta CLIP baholash; foydasi ko'rilgan kombinatsiyalarda kattaroq. Muhim metodologik nuqta: agar natijani o'sha CLIP bilan baholaganimizda, best-of-K o'z-o'zidan "mukammal" ko'rinardi — shuning uchun hakam alohida model. Bog'liq bo'limlar: 2.8, 2.10.


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

Noto'g'ri fikr To'g'risi
"CLIP har qanday so'z kombinatsiyasini tushunadi" Ko'rilganlarda 0.974, ko'rilmaganlarda 0.519
"Zero-shot = o'quvsiz" Encoderlar o'rgatilgan; faqat yangi sinf uchun klassifikator o'rgatilmaydi
"InfoNCE loss nolga tushishi kerak" Batchdagi soxta manfiylar tufayli ~`2.0` da to'xtadi
"Model barcha atributlarni bir xil yaxshi kombinatsiyalaydi" Rang 1.000, shakl 0.540 (ko'rilmaganlarda)
"Qidiruv va zero-shot bir xil qiyin" Ko'rilmaganlarda P@10 0.758, R@1 0.519
"Matn encoderi inkorni tushunadi" qizil emas katta doira → top-20 ning 1.00 qismi qizil
"Har kombinatsiyaga alohida shart vektori — eng aniq" Ko'rilmaganlarda ID sharti 0.000, matn sharti 0.304
"Best-of-K har doim ko'p foyda beradi" Ko'rilmaganlarda K = 4 dan keyin o'smadi
"Generatsiyani CLIP bilan baholash yetarli" Tanlovchi va hakam bir model bo'lsa — o'z-o'zini tekshirish

6. Keng tarqalgan xatolar va yechimlari

1. Vektorlarni normallamaslik

python
logit = zi @ zt.T                                     # uzunlik ham ta'sir qiladi # ⚠️
zi, zt = F.normalize(zi, dim=-1), F.normalize(zt, dim=-1)                     # ✅
logit = log_harorat.exp().clamp(max=100) * zi @ zt.T

2. Faqat bir yo'nalishli InfoNCE

python
loss = F.cross_entropy(logit, nishon)                 # faqat rasm -> matn    # ⚠️
loss = (F.cross_entropy(logit, nishon) + F.cross_entropy(logit.T, nishon)) / 2  # ✅

3. Haroratni cheklamaslik

python
logit = log_harorat.exp() * zi @ zt.T                 # portlashi mumkin      # ⚠️
logit = log_harorat.exp().clamp(max=100) * zi @ zt.T                          # ✅

4. Kompozitsion umumlashmani ko'rilgan kombinatsiyalarda o'lchash

python
aniqlik = (bashorat == Y_test).float().mean()         # test = o'quv kombinatsiyalari # ⚠️
aniqlik_yangi = (bashorat[yangi] == Y_test[yangi]).float().mean()             # ✅

5. Ko'rilmagan kombinatsiyaga ID shart

python
c = nn.Embedding(18, 32)(torch.tensor([yangi_kombo]))   # o'rgatilmagan vektor  # ⚠️
c = matn_shart(["katta ko'k uchburchak"])               # so'zlar orqali       # ✅

6. Tanlovchi va hakam bir model

python
eng = nomzodlar[clip_ball.argmax()]; baho = clip_ball.max()                   # ⚠️
eng = nomzodlar[clip_ball.argmax()]; baho = atribut_hakam(eng)                # ✅

7. Noma'lum so'zlarni jimgina tashlash

python
idx = [lugat.get(w, 0) for w in matn.split()]         # "emas" yo'qoladi      # ⚠️
nomalum = [w for w in matn.split() if w not in lugat]                         # ✅
if nomalum:
    print("ogohlantirish: noma'lum so'zlar", nomalum)

7. Integratsiya — bu bilim qayerda kerak bo'ladi

  • 24.3-dars (o'tilgan): multi-head attention — cross-attention bilan matn sharti
  • 25.7-dars (o'tilgan): embeddinglar va semantik qidiruv — CLIP o'sha g'oyani rasm va matn orasida qo'llaydi
  • 26.2-dars (o'tilgan): VAE — 3-misoldagi shartli VAE ning asosi
  • 26.6-dars (o'tilgan): shartli diffusion, classifier-free guidance, latent diffusion
  • 26.7-dars (o'tilgan): baholash — alohida hakam, bir necha urug', juftlashgan farq
  • Keyingi darslar: 26.9 Generativ AI etikasi va xavfsizligi — matn-rasm tizimlarining suiiste'moli, yodlab olish, watermark va kelib chiqishni belgilash; 26.10 Amaliyot — generativ loyiha boshidan oxirigacha; MLOps va deploy qismida — embedding qidiruv xizmatlari va generatsiya kechikishi

8. Eng yaxshi amaliyotlar

  1. Kompozitsion umumlashmani alohida o'lchang: o'quvda ko'rilmagan kombinatsiyalarni oldindan ajrating.

  2. Zero-shot va qidiruv natijalarini atributlar bo'yicha ham ko'ring — qaysi qism umumlashmayotganini topasiz.

  3. InfoNCE da batch tarkibini tushuning: bir xil sinfli juftliklar soxta manfiy bo'ladi.

  4. Matn encoderini inkor, tartib va munosabat so'rovlarida alohida sinang.

  5. Generator shartini so'z/token darajasida bering (kompozitsion), kombinatsiya ID sida emas.

  6. Generatsiyani tanlovchidan mustaqil hakam bilan baholang; hakamning o'z aniqligini ham o'lchang.

  7. Best-of-K ni narx bilan birga baholang: K oshgan sari foyda kamayadi.

  8. Real tizimlar haqida faqat tekshirilgan faktlarni keltiring; parametr va xarajat raqamlarini to'qimang.


9. Amaliy topshiriq

Vazifa 1: Bashorat qiling

python
1.  # CLIP ning ikki encoderi va umumiy fazo nima uchun kerak?
2.  # InfoNCE da B = 128 bo'lsa tasodifiy boshlanishdagi loss taxminan?
3.  # nega bizning InfoNCE loss nolga tushmadi?
4.  # zero-shot da yangi sinf qo'shish uchun nima kerak?
5.  # 18 ta tavsifdan tasodifiy tanlashda aniqlik?
6.  # "qizil kichik kvadrat" nega "qizil kichik doira" deb tanildi?
7.  # R-precision ta'rifi (R = 40)?
8.  # so'zlar xaltasi encoderi uchun "katta qizil doira" va "qizil katta doira" kosinusi?
9.  # ko'rilmagan kombinatsiyada ID sharti nima beradi?
10. # cross-attention da Q, K, V qayerdan olinadi?
11. # best-of-8 ning narxi?
12. # nega tanlovchi CLIP bilan baholash noto'g'ri?
Javoblar
  1. Matn va rasmni bevosita kosinus bilan solishtirish uchun — qidiruv, zero-shot, qayta saralash
  2. log 128 ≈ 4.85 (harorat sababli biroz yuqori bo'lishi mumkin: bizda 5.1–5.6)
  3. Batchda bir kombinatsiyadan ~9 juftlik — soxta manfiylar; pastki chegara ~`log 9`
  4. Faqat yangi matn tavsifi
  5. 1/18 ≈ 0.056
  6. O'quvda qizil kichik shakl faqat doira edi — model yorliq (shortcut) o'rgangan
  7. Top-40 natijadagi to'g'ri natijalar ulushi
  8. 1.000 — tartib ko'rinmaydi
  9. Tasodifiy natija — embedding o'rgatilmagan (3-misol: "hammasi to'g'ri" 0.000)
  10. Q — rasm xususiyatlaridan, K va V — matn tokenlaridan
  11. 8 barobar generatsiya + 8 ta CLIP baholash har so'rovga
  12. Tanlov aynan shu bahoni maksimallashtiradi — natija o'z-o'zidan yaxshi ko'rinadi; mustaqil hakam kerak

Vazifa 2: Xatolarni tuzating

python
1.  zi, zt = rasm_enc(X), matn_enc(T)
    loss = F.cross_entropy(zi @ zt.T, torch.arange(len(X)))

2.  test_X, _, test_Y = toplam(korilgan, 40, seed=1)
    print("kompozitsion aniqlik:", zero_shot(test_X, test_Y))

3.  shart = nn.Embedding(18, 32)
    rasm = dekoder(z, shart(torch.tensor([KOMBO.index(("ko'k", "katta", "uchburchak"))])))

4.  nomzodlar = [generatsiya(sorov) for _ in range(8)]
    eng = max(nomzodlar, key=lambda r: clip_ball(r, sorov))
    print("sifat:", clip_ball(eng, sorov))

5.  top = (matn_enc(["qizil emas doira"]) @ galereya.T).argsort(descending=True)[:10]
    print("qizil bo'lmagan doiralar topildi")
Javoblar
python
1.  zi, zt = F.normalize(rasm_enc(X), dim=-1), F.normalize(matn_enc(T), dim=-1)
    logit = log_harorat.exp().clamp(max=100) * zi @ zt.T
    nishon = torch.arange(len(X))
    loss = (F.cross_entropy(logit, nishon) + F.cross_entropy(logit.T, nishon)) / 2

2.  test_X, _, test_Y = toplam(set(KOMBO), 40, seed=1)        # hamma 18 kombinatsiya
    yangi = torch.tensor([KOMBO[y] in KORILMAGAN for y in test_Y.tolist()])
    print("ko'rilgan:", zero_shot(test_X[~yangi], test_Y[~yangi]),
          "ko'rilmagan:", zero_shot(test_X[yangi], test_Y[yangi]))

3.  c = matn_shart(["katta ko'k uchburchak"])                 # kompozitsion shart
    rasm = dekoder(z, c)

4.  eng = max(nomzodlar, key=lambda r: clip_ball(r, sorov))
    print("sifat:", atribut_hakam(eng, sorov))                # mustaqil hakam

5.  # so'zlar xaltasi "emas" ni tushunmaydi: natijani tekshiring
    rang = [KOMBO[i][0] for i in Y[top].tolist()]
    print("qizil ulushi:", rang.count("qizil") / len(rang))    # kutilgan: yuqori

Vazifa 3: CLIP

Modellang:

  1. Batch hajmi 32, 128, 256 — zero-shot aniqlik va loss chegarasi
  2. Qat'iy harorat (10, 30, 100) va o'rganiladigan harorat
  3. Soxta manfiylarni niqoblash: bir xil kombinatsiyali juftliklarni loss dan chiqarish
  4. Ko'rilmagan kombinatsiyalarni boshqacha tanlang (masalan, har rangda bitta) — natija qanday o'zgaradi?

Vazifa 4: Matn encoderi

Modellang:

  1. So'zlar xaltasi o'rniga kichik Transformer yoki GRU encoder
  2. "emas" bilan tavsiflar qo'shing ("qizil emas, ko'k doira") — inkor o'rganiladimi?
  3. Ikki shakl bitta rasmda: "chapda qizil doira, o'ngda ko'k kvadrat"
  4. Atributni bog'lash testi: "qizil doira va ko'k kvadrat" va "ko'k doira va qizil kvadrat"

Vazifa 5: Shartli generator

Modellang:

  1. Shartli VAE o'rniga shartli diffusion (26.6 dagi model, 768 o'lcham)
  2. Classifier-free guidance bilan matn sharti — w bo'yicha atribut aniqligi
  3. Dekoderni kichik CNN ga almashtiring — shakl aniqligi o'zgaradimi?
  4. Ko'rilmagan kombinatsiyalar soni 2, 4, 8 — umumlashma qanday o'zgaradi?

Vazifa 6: Baholash

Modellang:

  1. CLIP-score (o'rtacha kosinus) va atribut hakami — ular qanchalik mos?
  2. Best-of-K: K = 1, 2, 4, 8, 16 va narx egri chizig'i
  3. Generatsiya uchun FD 26.7-bob atribut hakamining xususiyatlarida
  4. Yodlab olish tekshiruvi: generatsiya o'quv rasmlariga qanchalik yaqin?

Vazifa 7: O'ylash

Marketing bo'limi aytdi: "Bizning matn-rasm generatorimiz sinovda 95% so'rovlarga to'g'ri rasm chizdi. Endi mijozlar istalgan mahsulot va rang kombinatsiyasini so'rashi mumkin, deb reklama qilamiz." Siz nima deysiz?

Javob

Qisqa javob: 95% — ehtimol o'quv ma'lumotiga o'xshash so'rovlardagi natija. "Istalgan kombinatsiya" degan va'da aynan ko'rilmagan kombinatsiyalarda o'lchanishi kerak — va bizning tajribalarda u yerda natija ancha past.

1. Nimada o'lchangan. Sinov so'rovlari qanday tanlangan? Agar ular katalogdagi mavjud mahsulot-rang kombinatsiyalari bo'lsa, bu 1-misoldagi "ko'rilgan" holatga o'xshaydi (0.974). Ko'rilmagan kombinatsiyalarda zero-shot 0.519, generatorda "hammasi to'g'ri" 0.304 edi.

2. Qaysi qism buziladi. Atributlar teng umumlashmaydi: bizda rang va o'lcham yaxshi, shakl yomon; ayniqsa o'quvda kuchli korrelyatsiya bo'lgan joyda ("qizil kichik → doira") model yorliqqa yopishadi. Real katalogda ham "bu mahsulot odatda shu rangda" kabi korrelyatsiyalar bor.

3. Kim baholagan. Agar "to'g'ri" ni generatsiyani tanlagan o'sha CLIP aytgan bo'lsa — bu o'z-o'zini tekshirish. Mustaqil hakam (atribut klassifikatorlari yoki inson baholashi, 26.7) kerak.

4. Matn tushunish. "Qora emas", "logosiz", "ikkita" kabi so'rovlar — ma'lum zaif joylar (2-misol: inkor butunlay e'tiborsiz qoldi).

Tavsiya:

python
# 1. sinovni ikkiga ajrating: katalogda BOR va YO'Q kombinatsiyalar
# 2. atribut bo'yicha aniqlik (rang, material, shakl) - mustaqil hakam
# 3. inkor, sanash, munosabat so'rovlari uchun alohida to'plam
# 4. inson A/B: 26.7 dagi quvvat hisobi bilan

Marketingga javob: "95% — mavjud kombinatsiyalar uchun yaxshi natija. 'Istalgan kombinatsiya' deyishdan oldin katalogda yo'q kombinatsiyalarda alohida o'lchaymiz: kichik tajribalarimizda bunday so'rovlarda tanish aniqligi 0.97 dan 0.52 ga tushdi, ayniqsa shakl va inkor bo'yicha."

Nimani mustahkamlaydi: 2.3, 2.4, 2.6, 2.7, 2.8-bo'limlar.


Xulosa

Bu darsda matn-rasm tizimlarining arxitekturasini ko'rdik, CLIP ni noldan qurib zero-shot klassifikatsiya va qidiruvni o'lchadik, matn sharti bilan kichik generator o'rgatib, ko'rilmagan atribut kombinatsiyalarida uning to'g'riligini mustaqil hakam bilan tekshirdik.

Eng muhim uch fikr:

  1. CLIP — ikki encoder va InfoNCE, lekin kompozitsion umumlashma qisman. 14 kombinatsiyada o'rgatilgan kichik CLIP ko'rilgan kombinatsiyalarda zero-shot 0.974, ko'rilmaganlarda 0.519 aniqlik berdi (tasodifiy 0.056). Rang va o'lcham yangi kombinatsiyalarda deyarli mukammal (1.000, 0.948), shakl esa 0.540: o'quvda "qizil kichik" faqat doira bo'lgani uchun model shu yorliqni o'rgangan va qizil kichik kvadrat ning 88% ini doira deb tanidi.

  2. Matn encoderi nimani ko'rmasa, butun tizim ham ko'rmaydi. Qidiruvda ko'rilmagan so'rovlar P@10 0.758 (ko'rilganlarda 0.955) berdi. So'zlar xaltasi encoderi so'z tartibini va inkorni umuman ko'rmaydi: qizil emas katta doira so'rovi top-20 da faqat qizil rasmlarni qaytardi. Katta tizimlarda ham inkor, sanash va munosabatlar ma'lum zaif joylar.

  3. Generatsiyada shart kompozitsion bo'lishi kerak — va natija mustaqil hakam bilan baholanishi kerak. Ko'rilmagan kombinatsiyalarda so'zlar orqali berilgan matn sharti "hammasi to'g'ri" 0.304, kombinatsiya ID shart 0.000 berdi. CLIP bilan best-of-8 qayta saralash to'g'rilikni ko'rilganlarda 0.333 dan 0.460 ga, ko'rilmaganlarda 0.285 dan 0.375 ga oshirdi — lekin 8 barobar narxda, va ko'rilmaganlarda tanlovchining o'zi ham yomonroq ajratadi (AUC 0.764 va 0.896).

Keyingi darsda Generativ AI etikasi va xavfsizligi: bu qismda qurgan generatorlar qanday suiiste'mol qilinishi mumkinligi, yodlab olish va maxfiylik, sintetik kontentni aniqlash, watermark va kelib chiqishni belgilash usullarini himoya nuqtai nazaridan ko'rib chiqamiz.

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
26.8-dars: Matn-rasm va multimodal modellar — IlmHamroh