aidoc.uz

1-qism · Matndan til modeligacha·01-bob

Matnni raqamga aylantirish

Kompyuter harfni tushunmaydi. Matn qanday qilib songa aylanadi, nega sodda usullar ishlamaydi va embedding nima uchun hamma narsani o'zgartirdi — ishlaydigan kod bilan.

Sizda 10 000 ta mijoz sharhi bor. Ularning qaysi biri norozilik, qaysi biri maqtov — bilishingiz kerak. Qo'lda o'qib chiqsangiz bir hafta ketadi.

Kompyuterga topshirmoqchisiz. Lekin muammo shundaki:

Kompyuter "xizmat yomon edi" degan matnni ko'rmaydi. U faqat sonlar bilan ishlaydi.

Demak birinchi vazifa aniq: matnni songa aylantirish. Va buni shunday qilish kerakki, sonlarda so'zning ma'nosi saqlansin.

Bu bob — butun kitobning poydevori. Bu yerda o'rganadigan uch narsa (vektor, o'xshashlik, ketma-ketlik) keyingi o'n bobda qayta-qayta uchraydi.

Muhitni tayyorlash#

Bu bobdagi hamma kod ishlaydi. Uni ishga tushirish uchun muhitni tayyorlaymiz.

  1. Python muhitini yarating

    Loyiha uchun alohida papka va virtual muhit:

    ~/loyiha
    mkdir aidoc-1bob && cd aidoc-1bob
    python3 -m venv venv
    source venv/bin/activate
    (venv) $ ← qator boshida shu paydo bo'lsa, muhit faol

    Windows'da oxirgi qator o'rniga: venv\Scripts\activate

  2. Kutubxonalarni o'rnating

    terminal
    pip install numpy scikit-learn gensim torch matplotlib nltk
    Successfully installed numpy-2.1.3 scikit-learn-1.6.0 gensim-4.3.3 torch-2.6.0 ...

    Nima uchun kerak:

    • numpy — vektor va matritsalar bilan ishlash
    • scikit-learn — tayyor TF-IDF va yordamchi funksiyalar
    • gensim — word2vec o'qitish
    • torch — neyron tarmoqlar
    • nltk — matnni tokenlarga ajratish
  3. Ishlayotganini tekshiring

    tekshir.py fayl yarating:

    import numpy as np
    import torch
     
    print("numpy:", np.__version__)
    print("torch:", torch.__version__)
    print("GPU bormi:", torch.cuda.is_available())
    terminal
    python tekshir.py
    numpy: 2.1.3 torch: 2.6.0 GPU bormi: False

    GPU bormi: False chiqsa xavotir olmang — bu bobdagi hamma narsa protsessorda ishlaydi.

1-bosqich: matnni bo'laklarga ajratish#

Har qanday ishdan oldin matnni tokenlarga ajratish kerak. Token — bu matnning eng kichik mustaqil bo'lagi.

"Bugun havo issiq."
xom matn — kompyuter uchun ma'nosiz belgilar ketma-ketligi
"bugun havo issiq."
kichik harfga o'tkazamiz — Havo va havo bitta so'z
["bugun", "havo", "issiq", "."]
bo'shliq va tinish belgisi bo'yicha ajratamiz
Tokenizatsiya: bir qator matn → mustaqil bo'laklar ro'yxati

Uchta qaror qabul qilinadi va har biri natijaga ta'sir qiladi:

1
Registr — katta harflarni tushirish
«Havo» va «havo» — bu bitta so'z. Kompyuter buni o'zi bilmaydi. Lekin ehtiyot bo'ling: «Olma» (shahar) va «olma» (meva) ham birlashib ketadi.
2
Tinish belgilari — saqlashmi yoki tashlashmi
Nuqta, so'roq va undov ma'no tashiydi: savolmi, his-hayajonmi, gap tugadimi. Kayfiyat tahlilida «!» juda foydali. Mavzu tasnifida esa ortiqcha.
3
Normallashtirish — so'z o'zagiga keltirish
«keldi», «kelaman», «kelgan» — bularning hammasi «kel» dan. Bu lemmatizatsiya deyiladi. Lug'atni kichraytiradi, lekin grammatik ma'lumot yo'qoladi.

Kodda eng sodda ko'rinishi:

def tokenlash(matn):
    """Eng sodda tokenizator: kichik harf + bo'shliq bo'yicha ajratish."""
    return matn.lower().split()
 
 
print(tokenlash("Bugun havo juda issiq edi"))
# ['bugun', 'havo', 'juda', 'issiq', 'edi']

To'g'riroq variant — muntazam ifoda bilan:

import re
 
 
def tokenlash(matn):
    """Tinish belgilarini alohida token qilib ajratadi."""
    matn = matn.lower()
    # \w+ — harf va raqamlar ketma-ketligi
    # [^\w\s] — harf ham, bo'shliq ham bo'lmagan belgi (tinish belgisi)
    return re.findall(r"\w+|[^\w\s]", matn)
 
 
print(tokenlash("Salom, dunyo!"))
# ['salom', ',', 'dunyo', '!']

2-bosqich: one-hot — eng sodda, eng ahmoq#

Tokenlarimiz bor. Endi ularni songa aylantiramiz.

Eng sodda g'oya: lug'atdagi har bir so'zga o'z raqami beriladi. Va so'z shu raqamli xonasida 1, qolgan hamma joyda 0 bo'lgan uzun qator bilan ifodalanadi.

choyxona
000001000
oshxona 
001000000
traktor  
010000000
One-hot: lug'at uzunligidagi qator, faqat bitta xonada 1

To'liq ishlaydigan kod:

import numpy as np
 
 
def one_hot(gap):
    """Gapdagi har bir so'zni one-hot vektorga aylantiradi."""
    sozlar = tokenlash(gap)
    lugat = sorted(set(sozlar))                        # takrorsiz so'zlar, alifbo tartibida
    indeks = {s: i for i, s in enumerate(lugat)}       # so'z → raqam
 
    matritsa = np.zeros((len(sozlar), len(lugat)), dtype=int)
    for qator, soz in enumerate(sozlar):
        matritsa[qator, indeks[soz]] = 1               # o'z joyiga 1 qo'yamiz
 
    return matritsa, lugat
 
 
matritsa, lugat = one_hot("choyxonaga boramizmi yoki oshxonaga")
print("Lug'at:", lugat)
print(matritsa)

Natija:

Lug'at: ['boramizmi', 'choyxonaga', 'oshxonaga', 'yoki']
[[0 1 0 0]     ← choyxonaga
 [1 0 0 0]     ← boramizmi
 [0 0 0 1]     ← yoki
 [0 0 1 0]]    ← oshxonaga

Ishladi. Matn songa aylandi. Lekin ikki jiddiy muammo bor va ikkalasi ham halokatli.

Muammo 1: ma'no butunlay yo'qoladi#

choyxona va oshxona — ikkalasi ham ovqat yeyiladigan joy. Bu ikki so'z bir-biriga yaqin bo'lishi kerak, to'g'rimi?

Keling, o'lchab ko'ramiz. Ikki vektorning yaqinligini skalyar ko'paytma bilan o'lchaymiz — juft-juft ko'paytirib qo'shamiz:

choyxona = np.array([0, 0, 0, 0, 0, 1, 0, 0, 0])
oshxona  = np.array([0, 0, 1, 0, 0, 0, 0, 0, 0])
traktor  = np.array([0, 1, 0, 0, 0, 0, 0, 0, 0])
 
print("choyxona · oshxona =", choyxona @ oshxona)   # 0
print("choyxona · traktor =", choyxona @ traktor)   # 0

Ikkalasi ham nol.

Kompyuter uchun choyxona bilan oshxona orasidagi masofa choyxona bilan traktor orasidagi masofa bilan bir xil. Hech qanday yaqinlik yo'q.

Muammo 2: hajm — hisoblab ko'ramiz#

Bu muammo yanada aniqroq. Haqiqiy raqamlar bilan hisoblaymiz.

lugat_hajmi = 200_000       # o'rtacha til lug'ati
bayt = 4                    # bitta butun son (int32)
 
bitta_soz = lugat_hajmi * bayt
print(f"Bitta so'z: {bitta_soz / 1024:.0f} KB")
 
# 100 so'zlik hujjat
hujjat = bitta_soz * 100
print(f"100 so'zlik hujjat: {hujjat / 1024**2:.0f} MB")
 
# 10 000 ta hujjat
korpus = hujjat * 10_000
print(f"10 000 hujjat: {korpus / 1024**4:.1f} TB")
Bitta so'z: 781 KB
100 so'zlik hujjat: 76 MB
10 000 hujjat: 0.7 TB

Bir necha sahifalik matn uchun 700 gigabayt. Va bu sonlarning 99.9995% i — nol.

One-hot nima uchun hali ham o'rgatiladi
  • Tushunish uchun eng sodda: bitta 1, qolgani 0
  • Kichik lug'atlarda (masalan 20 ta kategoriya) hamon ishlatiladi
  • Boshqa hamma usullar uni yaxshilash urinishi sifatida paydo bo'lgan
  • Zamonaviy modellarda ham chiqishda ishlatiladi — keyingi bobda ko'ramiz
Nima uchun matn uchun yaroqsiz
  • Ma'no butunlay yo'qoladi — har qanday ikki so'z bir xil darajada uzoq
  • Hajm lug'at bilan chiziqli o'sadi — 200 ming so'z = 200 ming o'lcham
  • Vektorlarning 99.99% i nol — xotira behuda ketadi
  • Lug'atda yo'q so'z bilan nima qilishni bilmaydi

Xulosa: one-hot — bu birinchi qadam, oxirgi emas. U muammoni ko'rsatib beradi: bizga ma'noni saqlaydigan va ixcham vektorlar kerak.


3-bosqich: bag-of-words — chastotani saqlash#

Endi bitta gap emas, butun hujjat bilan ishlaymiz.

G'oyasi nomida: hujjatdagi so'zlarni xaltaga solib silkitamiz. Tartib yo'qoladi, lekin har bir so'z necha marta uchragani saqlanib qoladi.

"bu film ajoyib ajoyib"
hujjat
lug'at: [ajoyib, bu, film, ...]
butun korpus bo'ylab takrorsiz so'zlar
[2, 1, 1, 0, 0, ...]
ajoyib 2 marta, bu 1 marta, film 1 marta
Bag-of-words: hujjat → lug'atdagi har bir so'zning sanog'i

Nega tartibni tashlaymiz? Chunki ko'p vazifalar uchun u kerak emas. Agar sharhda "ajoyib", "zo'r", "tavsiya qilaman" so'zlari ko'p bo'lsa — bu ijobiy sharh, so'zlar qaysi tartibda turgani muhim emas.

def bag_of_words(hujjatlar):
    """Hujjatlar ro'yxatidan chastota matritsasini quradi."""
    tokenlangan = [tokenlash(h) for h in hujjatlar]
 
    # Butun korpus bo'ylab yagona lug'at
    hamma_sozlar = [s for hujjat in tokenlangan for s in hujjat]
    lugat = sorted(set(hamma_sozlar))
    indeks = {s: i for i, s in enumerate(lugat)}
 
    matritsa = np.zeros((len(hujjatlar), len(lugat)), dtype=int)
    for qator, hujjat in enumerate(tokenlangan):
        for soz in hujjat:
            matritsa[qator, indeks[soz]] += 1        # 1 qo'ymaymiz — SANAYMIZ
 
    return lugat, matritsa
 
 
korpus = [
    "bu film ajoyib ajoyib",
    "yomon demayman lekin ajoyib ham emas",
    "ajoyib faqat nodon odam shunday deydi",
]
 
lugat, matritsa = bag_of_words(korpus)
 
# Chiroyli chiqarish
print(f"{'':>12}", " ".join(f"{s[:6]:>7}" for s in lugat))
for i, qator in enumerate(matritsa):
    print(f"{i + 1}-hujjat:  ", " ".join(f"{v:>7}" for v in qator))
              ajoyib      bu   deydi demayman   emas   faqat     film     ham
1-hujjat:          2       1       0        0      0       0        1       0
2-hujjat:          1       0       0        1      1       0        0       1
3-hujjat:          1       0       1        0      0       1        0       0

One-hot'dan farqi aniq: endi vektor butun hujjatni ifodalaydi va chastota saqlanadi.


4-bosqich: TF-IDF — qaysi so'z hujjatni ajratib turadi#

Bag-of-words'da yana bir muammo bor va u juda amaliy.

Tasavvur qiling, 1000 ta hujjatda qidiryapsiz. va so'zi hamma hujjatda bor — demak u hujjatlarni ajratib turmaydi. Aksincha, mitoxondriya so'zi faqat uchtasida bor — va aynan shu uchtasi sizga kerak.

TF-IDF ana shu mantiqni formulaga soladi.

TF
Term Frequency — chastota
So'z shu hujjatda necha marta uchradi. Ko'p uchrasa — hujjat uchun muhimroq. Odatda hujjat uzunligiga bo'linadi, aks holda uzun hujjatlar sun'iy ustunlik oladi.
IDF
Inverse Document Frequency — noyoblik
So'z NECHTA hujjatda umuman uchraydi. KAM hujjatda uchrasa — u ajratuvchi so'z, ballari yuqori. Hamma joyda uchrasa — ballari nolga yaqin.
×
Ikkalasini ko'paytiramiz
Yuqori ball olish uchun so'z shu hujjatda ko'p, boshqa hujjatlarda esa kam uchrashi kerak. Aynan shunday so'z hujjatning mavzusini bildiradi.

Raqamlar bilan ko'ramiz#

Formulani yodlashdan ko'ra bir marta qo'lda hisoblab ko'rish foydaliroq. Uchta hujjat:

1-hujjat:  "mushuk uyda uxlaydi"
2-hujjat:  "it uyda uxlaydi"
3-hujjat:  "mitoxondriya hujayrada energiya ishlab chiqaradi"

uyda so'zi uchun 1-hujjatda:

TF  = 1 / 3 = 0.333          (3 so'zdan 1 tasi)
DF  = 2                       (2 ta hujjatda uchraydi)
IDF = ln(3 / (1 + 2)) + 1 = ln(1.0) + 1 = 1.0
TF-IDF = 0.333 × 1.0 = 0.333

mitoxondriya so'zi uchun 3-hujjatda:

TF  = 1 / 5 = 0.200
DF  = 1                       (faqat 1 ta hujjatda)
IDF = ln(3 / (1 + 1)) + 1 = ln(1.5) + 1 = 1.405
TF-IDF = 0.200 × 1.405 = 0.281

Endi solishtiring: uyda ko'proq uchrasa ham (TF yuqori), mitoxondriya noyobligi uchun ballini deyarli ushlab qoldi. Agar hujjatlar soni ko'paysa, farq keskin ochiladi.

To'liq kod#

import numpy as np
 
 
def compute_tf(hujjatlar, lugat, indeks):
    """Har bir hujjatda har bir so'zning nisbiy chastotasi."""
    tf = np.zeros((len(hujjatlar), len(lugat)), dtype=np.float32)
 
    for i, hujjat in enumerate(hujjatlar):
        sozlar = tokenlash(hujjat)
        for soz in sozlar:
            tf[i, indeks[soz]] += 1 / len(sozlar)     # hujjat uzunligiga bo'lamiz
    return tf
 
 
def compute_idf(hujjatlar, lugat, indeks):
    """Har bir so'zning noyoblik koeffitsienti."""
    n = len(hujjatlar)
    idf = np.zeros(len(lugat), dtype=np.float32)
 
    for soz in lugat:
        df = sum(1 for h in hujjatlar if soz in tokenlash(h))
        idf[indeks[soz]] = np.log(n / (1 + df)) + 1
    return idf
 
 
def tf_idf(hujjatlar):
    tokenlangan = [tokenlash(h) for h in hujjatlar]
    lugat = sorted(set(s for h in tokenlangan for s in h))
    indeks = {s: i for i, s in enumerate(lugat)}
 
    tf = compute_tf(hujjatlar, lugat, indeks)
    idf = compute_idf(hujjatlar, lugat, indeks)
    return lugat, tf * idf
 
 
korpus = [
    "mushuk uyda uxlaydi",
    "it uyda uxlaydi",
    "mitoxondriya hujayrada energiya ishlab chiqaradi",
]
 
lugat, matritsa = tf_idf(korpus)
 
# Har bir hujjat uchun eng muhim 2 so'zni chiqaramiz
for i, qator in enumerate(matritsa):
    eng_muhim = np.argsort(qator)[::-1][:2]
    sozlar = [f"{lugat[j]} ({qator[j]:.3f})" for j in eng_muhim if qator[j] > 0]
    print(f"{i + 1}-hujjat →", ", ".join(sozlar))
1-hujjat → mushuk (0.469), uxlaydi (0.333)
2-hujjat → it (0.469), uxlaydi (0.333)
3-hujjat → mitoxondriya (0.281), hujayrada (0.281)

Diqqat qiling: TF-IDF har bir hujjat uchun uni boshqalardan ajratib turadigan so'zni topdi. uyda so'zi (ikki hujjatda bor) yuqoriga chiqmadi.

Amaliyotda: scikit-learn#

Noldan yozish tushunish uchun. Ishda tayyor vositadan foydalaniladi:

from sklearn.feature_extraction.text import TfidfVectorizer
 
vectorizer = TfidfVectorizer(
    lowercase=True,
    token_pattern=r"\w+",      # tinish belgilarini tashlaydi
    max_features=5000,         # eng ko'p uchraydigan 5000 so'z bilan cheklaymiz
    min_df=2,                  # kamida 2 hujjatda uchrasin (xato yozilgan so'zlarni kesadi)
    max_df=0.8,                # 80% dan ko'p hujjatda uchrasa — tashlaymiz (to'xtash so'zlari)
)
 
matritsa = vectorizer.fit_transform(korpus)
print(matritsa.shape)                        # (3, 11)
print(vectorizer.get_feature_names_out())    # lug'at
Amaliy topshiriq

Hujjat qidiruv tizimi quring

10-15 ta qisqa matn oling (yangiliklar sarlavhalari, kitob tavsiflari — nima bo'lsa ham). Foydalanuvchi so'rov yozganda unga eng mos 3 ta hujjatni qaytaradigan funksiya yozing.

Talablar:

  • TfidfVectorizer ishlatilsin
  • O'xshashlik kosinus bo'yicha o'lchansin
  • Natijada hujjat matni va o'xshashlik bali chiqsin

Bu — eng sodda qidiruv tizimi. 5-bobda aynan shu g'oya RAG'ning asosi bo'ladi.

Maslahatni ko'rsatish

Yechim yo'li: 1. TfidfVectorizer bilan barcha hujjatlarni fit_transform qiling 2. So'rovni transform qiling (fit_transform EMAS! Aks holda lug'at o'zgaradi) 3. sklearn.metrics.pairwise.cosine_similarity bilan o'xshashlikni hisoblang 4. np.argsort(...)[::-1][:3] bilan eng yuqori 3 tasini oling Asosiy tuzoq: so'rov uchun transform() ishlating, fit_transform() emas. Aks holda so'rov o'z lug'atini yaratadi va o'lchamlar mos kelmaydi.


5-bosqich: embedding — hamma narsa o'zgaradi#

Bu bobning eng muhim qismi. Diqqat bilan o'qing.

Muammoning mohiyati#

Yuqoridagi uch usulning hammasida bitta umumiy kamchilik bor:

one-hot
har so'z alohida xonada, ma'no yo'q
200 000 o'lcham
bag-of-words
chastota bor, ma'no yo'q
200 000 o'lcham
TF-IDF
muhimlik bor, ma'no yo'q
200 000 o'lcham
embedding
MA'NO BOR va vektor kichik
300 o'lcham
Sodda usullarning umumiy muammosi: har bir so'z o'z xonasida yolg'iz

Xarita tashbehi#

Tasavvur qiling, xarita bor. Unda Toshkent va Samarqand yaqin, Toshkent va Tokio uzoq. Har bir shaharning koordinatasi bor va koordinatalar orasidagi masofa haqiqiy yaqinlikni bildiradi.

Embedding — bu so'zlar uchun xuddi shunday xarita. Faqat ikki o'lchamli emas, 300 o'lchamli.

choyxona  = [ 0.21  -0.44   0.87   0.02  ...  0.13 ]
oshxona   = [ 0.19  -0.41   0.83   0.05  ...  0.15 ]     ← juda yaqin
traktor   = [-0.62   0.11  -0.29   0.77  ... -0.44 ]     ← uzoq

Farqni sezing:

one-hot:    200 000 son, ma'no yo'q
embedding:      300 son, to'liq ma'no

666 barobar kichik va cheksiz aqlliroq.

Word2vec bu xaritani qanday chizadi#

G'oya 2013-yilda Tomas Mikolov tomonidan taklif qilingan. Uning mohiyati hayratlanarli darajada oddiy:

So'zning ma'nosi uning qo'shnilari bilan belgilanadi.

Bu distributsion gipoteza deb ataladi. Uni tekshirish oson. Quyidagi gapda zvarch so'zi nimani anglatishini bilmaysiz:

"Kechqurun issiq zvarch ichib, uyquga ketdim."

Lekin taxmin qila olasiz: bu ichiladigan, issiq bo'ladigan, kechqurun iste'mol qilinadigan narsa. Choy? Sut? Sho'rva? Qo'shnilar sizga ma'noni aytib berdi.

Word2vec aynan shuni qiladi — faqat millionlab marta.

bugun havo juda issiq edi
markaz: juda
ijobiy misollar
bugun, havo, issiq, edi — ha, bular kontekstda
salbiy misollar
traktor, mitoxondriya — yo'q, bular kontekstda emas
Skip-gram: markazdagi so'zdan qo'shnilarini bashorat qilishni o'rganamiz

Jarayon to'rt qadamda:

  1. Oynani matn bo'ylab siljitamiz

    Markazda bitta so'z w, atrofida window ta qo'shni. Oyna bir so'zga siljiydi va jarayon takrorlanadi.

  2. Ijobiy va salbiy misollar yig'amiz

    Oyna ichidagi so'zlar — ijobiy misol ("ha, bu so'z shu kontekstda uchraydi"). Lug'atdan tasodifiy olingan so'zlar — salbiy misol ("yo'q, bu so'z bu yerda uchramaydi").

    Bu negative sampling deyiladi va aynan shu narsa word2vec'ni tez qiladi.

  3. Neyron tarmoqni o'qitamiz

    Vazifa juda oddiy — ikkilik tasniflash: "bu ikki so'z bir kontekstdami?" Ha yoki yo'q.

  4. Tarmoqning javobini TASHLAB YUBORAMIZ

    Va mana eng chiroyli qismi.

    Bizga tarmoqning javobi kerak emas. Bizga uning ichki og'irliklari kerak. O'qish davomida tarmoq ichida har bir so'z uchun shakllangan sonlar qatori — bu bizning embedding'imiz.

    Ya'ni: soxta vazifa qo'yib, uning yon mahsulotini olamiz. Bu mashina o'rganishdagi eng nafis hiylalardan biri.

O'z embeddingingizni o'qitish#

Nazariya yetarli. Kod yozamiz.

from gensim.models import Word2Vec
 
# Matn — jumlalar ro'yxati, har biri tokenlar ro'yxati
jumlalar = [
    tokenlash(matn) for matn in korpus
]
 
model = Word2Vec(
    sentences=jumlalar,
    sg=1,               # 1 = skip-gram, 0 = CBOW
    vector_size=100,    # har bir so'z uchun 100 ta son
    window=5,           # chapga 5, o'ngga 5 so'z
    min_count=2,        # 2 martadan kam uchragan so'zni tashlaymiz
    negative=5,         # har bir ijobiy misolga 5 ta salbiy
    workers=4,          # protsessor yadrolari
    epochs=10,          # korpusni necha marta ko'rib chiqamiz
)
 
model.save("word2vec.model")
print("Lug'at hajmi:", len(model.wv))

Parametrlarni tanlash bo'yicha amaliy qo'llanma:

ParametrKichik qiymatKatta qiymatTavsiya
vector_sizeMa'no sig'maydiOrtiqcha, sekin100–300
windowSo'z turkumini yaxshi ajratadiMavzuviy yaqinlikni topadi5
min_countLug'at shovqinga to'ladiKam uchraydigan so'zlar yo'qoladi2–5
epochsYetarli o'rganmaydiOrtiqcha moslashadi5–20

Embedding bilan nima qilish mumkin#

O'qitilgan modelda uchta asosiy amal bor:

# 1. Eng o'xshash so'zlarni topish
print(model.wv.most_similar("choy", topn=5))
# [('qahva', 0.82), ('piyola', 0.79), ('ichmoq', 0.74), ...]
 
# 2. Ikki so'zning yaqinligini o'lchash
print(model.wv.similarity("choy", "qahva"))     # 0.82
print(model.wv.similarity("choy", "traktor"))   # 0.11
 
# 3. Ortiqchasini topish
print(model.wv.doesnt_match(["choy", "qahva", "sut", "traktor"]))
# 'traktor'

Sehrli qism: ma'no arifmetikasi#

Ma'no koordinataga aylangach, u bilan arifmetika qilish mumkin bo'lib qoladi:

podshoh − erkak + ayol  ≈  malika
podshoh
boshlang'ich vektor: hukmdorlik + erkaklik
− erkak
erkaklik komponentini ayiramiz → 'hukmdor' qoladi
+ ayol
ayollik komponentini qo'shamiz
≈ malika
eng yaqin so'z shu bo'lib chiqadi
Vektor arifmetikasi: 'erkak' komponentini ayirib, 'ayol' komponentini qo'shamiz
natija = model.wv.most_similar(
    positive=["podshoh", "ayol"],
    negative=["erkak"],
    topn=3,
)
print(natija)

Bu shunchaki qiziqarli fokus emas. Bu embedding ma'noning ichki tuzilishini o'zlashtirganini isbotlaydi. Model hech kim aytmasdan turib "hukmdorlik" va "jins" degan tushunchalarni alohida o'lchamlarga ajratib olgan.

Amaliy topshiriq

O'z embeddingingizni o'qiting va tekshiring

Kamida 10 MB o'zbekcha yoki inglizcha matn toping va unda word2vec o'qiting.

Keyin tekshiring:

  1. most_similar 5 ta so'z uchun mantiqiy natija beryaptimi?
  2. Analogiya ishlaydimi? (erkak → ayol kabi juftliklar bilan sinang)
  3. 10-15 ta so'zni PCA bilan ikki o'lchamga tushirib chizing — o'xshash so'zlar guruhlanyaptimi?

Agar natija yomon bo'lsa, vector_size, window va epochs ni o'zgartirib ko'ring va farqni yozib boring.

Maslahatni ko'rsatish

Ma'lumot manbalari: - Vikipediya dump: dumps.wikimedia.org (uzwiki) - gensim ichidagi text8 korpusi (inglizcha, tez sinash uchun) - O'z Telegram eksportingiz (JSON dan matnni chiqarib oling) Agar natija yomon chiqsa — bu deyarli har doim MA'LUMOT KAMLIGI. Kamida 10 MB matn kerak. Kamroq bo'lsa fasttext'dan tayyorini yuklab oling. Vizualizatsiya uchun: from sklearn.decomposition import PCA import matplotlib.pyplot as plt sozlar = ["choy", "qahva", "sut", "mashina", "avtobus", "poyezd"] vektorlar = [model.wv[s] for s in sozlar] koord = PCA(n_components=2).fit_transform(vektorlar) plt.scatter(koord[:, 0], koord[:, 1]) for i, s in enumerate(sozlar): plt.annotate(s, koord[i]) plt.show()


6-bosqich: o'xshashlikni o'lchash#

Xarita bor. Endi undagi masofani o'lchash kerak. Bu — butun kitobdagi eng ko'p ishlatiladigan amal.

Skalyar ko'paytmadan boshlaymiz#

Ikki vektorni juft-juft ko'paytirib qo'shamiz:

a = np.array([1, 2, 3])
b = np.array([4, 5, 6])
 
print(a @ b)                    # 1*4 + 2*5 + 3*6 = 32
print(np.dot(a, b))             # xuddi shu

Uch muammosi bor:

1
Uzun vektorlarga ustunlik beradi
Vektorning uzunligi (magnitudasi) katta bo'lsa, ko'paytma ham katta chiqadi — ma'no yaqin bo'lmasa ham.
2
Katta qiymatlarga ustunlik beradi
Katta qiymatlar odatda ko'p uchraydigan (va shuning uchun kam ma'noli) so'zlarga tegishli bo'ladi.
3
Chegarasi yo'q
Natija 32 chiqdi — bu ko'pmi yoki ozmi? Bilib bo'lmaydi, chunki taqqoslash uchun shkala yo'q.

Kosinus o'xshashligi — yechim#

Skalyar ko'paytmani vektorlar uzunligiga bo'lamiz. Natijada faqat yo'nalish qoladi, uzunlik hisobga olinmaydi.

cos(a, b) = (a · b) / (|a| × |b|)
def kosinus(a, b):
    """Ikki vektor orasidagi kosinus o'xshashligi: -1 dan 1 gacha."""
    return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))

Natijani talqin qilish oson:

QiymatMa'nosi
1.0Bir xil yo'nalish — ma'no bir xil
0.7 – 0.9Juda yaqin — sinonim yoki bir mavzu
0.3 – 0.5Uzoqdan bog'liq
0.0Aloqasiz
−1.0Qarama-qarshi yo'nalish
abθ

burchak kichik → kosinus 1 ga yaqin → ma'no yaqin

Kosinus o'xshashligi vektorlar orasidagi BURCHAKNI o'lchaydi, uzunlikni emas

Amalda sklearn ishlatiladi — u matritsalar bilan tez ishlaydi:

from sklearn.metrics.pairwise import cosine_similarity
 
# Bir vektorni ko'plari bilan solishtirish
sorov = np.array([[0.2, 0.5, -0.1]])
baza = np.array([
    [0.3, 0.4, -0.2],
    [-0.8, 0.1, 0.6],
    [0.15, 0.55, -0.05],
])
 
ballar = cosine_similarity(sorov, baza)[0]
print(ballar)                        # [0.94  -0.31  0.99]
print("Eng mos:", np.argmax(ballar)) # 2

7-bosqich: ketma-ketlikni tushunadigan modellar#

Raqamlarimiz bor. Endi ular bilan ishlaydigan model kerak.

Lekin matn — shunchaki so'zlar to'plami emas. U ketma-ketlik:

"It odamni qopdi"      ← oddiy holat
"Odam itni qopdi"      ← yangilik!

Bir xil so'zlar, butunlay boshqa ma'no. Bag-of-words bu ikkisini ajrata olmaydi — xaltada bir xil so'zlar.

Oddiy neyron tarmoqning muammosi shundaki, uning xotirasi yo'q — har bir kirishni mustaqil deb hisoblaydi.

RNN — xotirasi bor tarmoq#

RNN so'zlarni birin-ketin o'qiydi va har qadamda yashirin holat saqlaydi.

Bugun
h₁
havo
h₂ = f(h₁, havo)
juda
h₃ = f(h₂, juda)
issiq
h₄ → javob
RNN vaqt bo'ylab yoyilgan holda: har qadamda xotira yangilanadi va keyingi qadamga uzatiladi

Har qadamda bir xil amal bajariladi (shuning uchun rekurrent — takrorlanuvchi):

h(t) = tanh( W · x(t) + U · h(t-1) + b )
        └── yangi so'z ──┘  └─ eski xotira ─┘

PyTorch'da sinab ko'ramiz:

import torch
import torch.nn as nn
 
# 10 ta misol, har biri 5 ta so'z, har so'z 3 o'lchamli vektor
data = torch.rand(10, 5, 3)
 
rnn = nn.RNN(input_size=3, hidden_size=16, num_layers=1, batch_first=True)
chiqish, yashirin = rnn(data)
 
print("chiqish:", chiqish.shape)    # torch.Size([10, 5, 16]) — har qadam uchun
print("yashirin:", yashirin.shape)  # torch.Size([1, 10, 16]) — faqat oxirgi holat

RNN nima uchun yetarli emas#

Uch muammosi bor va ular jiddiy.

1. Unutadi. Bir necha qadamdan keyin boshlang'ich so'zlar xotiradan yo'qoladi.

2. Gradient so'nadi. Bu eng jiddiy muammo. Raqamlar bilan ko'ramiz:

# Har qadamda gradient 0.5 ga ko'payadi deb faraz qilaylik
gradient = 1.0
for qadam in range(1, 21):
    gradient *= 0.5
    if qadam in (1, 5, 10, 20):
        print(f"{qadam:>2} qadam orqaga: gradient = {gradient:.10f}")
 1 qadam orqaga: gradient = 0.5000000000
 5 qadam orqaga: gradient = 0.0312500000
10 qadam orqaga: gradient = 0.0009765625
20 qadam orqaga: gradient = 0.0000009537

20 qadamdan keyin gradient deyarli nol. Ya'ni gap boshidagi so'zlar model og'irliklariga umuman ta'sir qilmaydi. Model uzoq bog'lanishlarni o'rgana olmaydi.

3. Parallellashmaydi. 5-so'zni hisoblash uchun 4-so'z tayyor bo'lishi shart. GPU'da minglab yadro bo'lsa ham, ular bo'sh turadi.

LSTM — eshiklari bor xotira#

LSTM birinchi ikki muammoni hal qiladi: modelga nimani eslash, nimani unutish kerakligini o'rgatadi.

Buni shunday tasavvur qiling: sizda daftar bor va har yangi so'zni o'qiganda uch qaror qabul qilasiz.

f
Unutish eshigi (forget gate)
«Daftardagi nimani o'chiraman?» Eski, endi kerak bo'lmagan ma'lumotni tashlaydi. Masalan yangi gap boshlanganda oldingi gapning mavzusi kerak emas.
i
Kirish eshigi (input gate)
«Yangi nimani yozaman?» Hozirgi so'zdan qaysi ma'lumot uzoq muddatli xotiraga tushishi kerakligini hal qiladi.
o
Chiqish eshigi (output gate)
«Hozir nimani ishlataman?» Xotiradagi ma'lumotning qaysi qismi shu qadamdagi javob uchun kerakligini tanlaydi.

Eshiklar sigmoid funksiya orqali 0 dan 1 gacha qiymat oladi:

0.0  →  butunlay to'sadi
0.5  →  yarmini o'tkazadi
1.0  →  butunlay o'tkazadi

Va bu qiymatlar o'qitish davomida o'rganiladi — hech kim qo'lda belgilamaydi.

LSTM'ning kaliti — ikkita xotira:

h  →  qisqa muddatli holat (hozirgi javob uchun)
c  →  uzoq muddatli kontekst (butun gap bo'ylab saqlanadi)

c deyarli o'zgarmasdan oldinga uzatiladi — faqat eshiklar orqali ozgina tahrirlanadi. Aynan shu gradient so'nishini oldini oladi: signal c orqali uzoq masofaga buzilmasdan yetib boradi.

lstm = nn.LSTM(input_size=3, hidden_size=16, num_layers=1, batch_first=True)
chiqish, (h, c) = lstm(data)
 
print("chiqish:", chiqish.shape)   # [10, 5, 16]
print("h (qisqa):", h.shape)       # [1, 10, 16]
print("c (uzoq):", c.shape)        # [1, 10, 16]

GRU — yengil variant#

GRU xuddi shu g'oya, lekin uchta eshik o'rniga ikkita: yangilash va qayta o'rnatish.

gru = nn.GRU(input_size=3, hidden_size=16, num_layers=1, batch_first=True)
chiqish, yashirin = gru(data)
GRU
  • Kamroq parametr → tezroq o'qiydi
  • Kamroq xotira talab qiladi
  • Ko'p vazifalarda LSTM bilan bir xil natija
  • Kichik ma'lumotda ortiqcha moslashish xavfi kamroq
LSTM
  • Ko'proq parametr → sekinroq
  • Juda uzoq bog'lanishlarda biroz ustunroq
  • Murakkab naqshlarni yaxshiroq o'rganadi
  • Ko'proq ma'lumot talab qiladi

Amaliy tavsiya: GRU'dan boshlang. Agar natija yetarli bo'lmasa va sizda ko'p ma'lumot bo'lsa — LSTM'ga o'ting. Farq odatda 1-2 foizdan oshmaydi.

CNN — naqsh qidiruvchi oyna#

CNN aslida rasmlar uchun o'ylab topilgan, lekin matnda ham ishlaydi. Oyna matn ustidan siljiydi va mahalliy naqshlarni qidiradi:

[bugun  havo  juda  issiq  edi]
 └───oyna───┘                     ← "bugun havo juda" — naqsh bormi?
        └───oyna───┘              ← "havo juda issiq" — naqsh bormi?
               └───oyna───┘       ← "juda issiq edi" — naqsh bormi?
# Diqqat: Conv1d kanal tartibini boshqacha kutadi
data_conv = data.transpose(1, 2)      # [10, 5, 3] → [10, 3, 5]
 
conv = nn.Conv1d(in_channels=3, out_channels=16, kernel_size=3, padding=1)
chiqish = conv(data_conv)
print(chiqish.shape)                  # [10, 16, 5]

CNN RNN'dan ancha tez — oynalar parallel ishlaydi. Lekin u faqat mahalliy naqshlarni ko'radi: 3-5 so'zlik iboralarni yaxshi topadi, gap boshi bilan gap oxiri orasidagi bog'liqlikni ko'rmaydi.


Yakuniy loyiha: kayfiyat tasniflagichi#

Endi hamma narsani birlashtiramiz va ishlaydigan mahsulot quramiz: sharhni ijobiy yoki salbiyga ajratadigan model.

1. Ma'lumot
sharhlar + yorliqlar (ijobiy/salbiy)
2. Tozalash
kichik harf, tinish belgilari, ortiqcha bo'shliq
3. Tokenizatsiya
matn → so'zlar ro'yxati
4. Lug'at
so'z → raqam moslashuvi
5. Padding
hamma sharhni bir uzunlikka keltirish
6. Embedding + GRU
model
7. Bashorat
0 dan 1 gacha ehtimollik
To'liq quvur: xom matndan bashoratgacha
  1. Ma'lumotni tayyorlash

    import numpy as np
    import pandas as pd
    from sklearn.model_selection import train_test_split
     
    df = pd.read_csv("sharhlar.csv")      # ustunlar: review, sentiment
     
    # Yorliqni raqamga: ijobiy=1, salbiy=0
    df["belgi"] = (df["sentiment"] == "positive").astype(int)
     
    X = df["review"].values
    y = df["belgi"].values
     
    # Uchga bo'lamiz
    X_train, X_temp, y_train, y_temp = train_test_split(
        X, y, test_size=0.3, stratify=y, random_state=42
    )
    X_val, X_test, y_val, y_test = train_test_split(
        X_temp, y_temp, test_size=0.5, stratify=y_temp, random_state=42
    )
     
    print(f"o'qitish: {len(X_train)}  tekshirish: {len(X_val)}  sinov: {len(X_test)}")

    stratify=ymuhim detal. U ijobiy va salbiy sharhlar har uch to'plamda ham teng nisbatda bo'lishini ta'minlaydi. Busiz sinov to'plamiga tasodifan faqat ijobiy sharhlar tushib qolishi va natija noto'g'ri chiqishi mumkin.

    random_state=42 — bo'linish har safar bir xil bo'lishi uchun. Tajribalarni takrorlanadigan qilish uchun zarur.

  2. Lug'at qurish

    from collections import Counter
     
    def lugat_qur(matnlar, max_hajm=10_000):
        """Eng ko'p uchraydigan so'zlardan lug'at quradi."""
        sanoq = Counter()
        for matn in matnlar:
            sanoq.update(tokenlash(matn))
     
        # 0 va 1 xizmat tokenlari uchun band
        lugat = {"<pad>": 0, "<unk>": 1}
        for soz, _ in sanoq.most_common(max_hajm - 2):
            lugat[soz] = len(lugat)
        return lugat
     
     
    lugat = lugat_qur(X_train)          # DIQQAT: faqat o'qitish to'plamida!
    print("Lug'at hajmi:", len(lugat))

    Ikkita maxsus token nima uchun kerak:

    • <pad> — qisqa sharhlarni to'ldirish uchun
    • <unk> — lug'atda yo'q so'zlar uchun (sinov to'plamida albatta uchraydi)
  3. Matnni raqamlarga aylantirish

    def kodlash(matn, lugat, max_uzunlik=200):
        """Matnni sonlar ro'yxatiga aylantiradi va uzunlikni tenglashtiradi."""
        tokenlar = tokenlash(matn)
        raqamlar = [lugat.get(t, lugat["<unk>"]) for t in tokenlar]
     
        if len(raqamlar) > max_uzunlik:
            raqamlar = raqamlar[:max_uzunlik]                    # kesamiz
        else:
            raqamlar += [lugat["<pad>"]] * (max_uzunlik - len(raqamlar))   # to'ldiramiz
     
        return raqamlar
     
     
    X_train_kod = np.array([kodlash(m, lugat) for m in X_train])
    X_val_kod = np.array([kodlash(m, lugat) for m in X_val])
    X_test_kod = np.array([kodlash(m, lugat) for m in X_test])
     
    print(X_train_kod.shape)     # (N, 200)

    max_uzunlik ni qanday tanlash kerak? Ma'lumotingizga qarang:

    uzunliklar = [len(tokenlash(m)) for m in X_train]
    print("o'rtacha:", np.mean(uzunliklar))
    print("90-protsentil:", np.percentile(uzunliklar, 90))

    90-protsentil yaxshi tanlov: sharhlarning 90% i to'liq sig'adi, faqat eng uzunlari kesiladi.

  4. Modelni yozish

    import torch
    import torch.nn as nn
     
    class KayfiyatModel(nn.Module):
        def __init__(self, lugat_hajmi, embed_olcham=128, yashirin=128, qatlamlar=2, dropout=0.4):
            super().__init__()
     
            # So'z raqamini vektorga aylantiradi. padding_idx=0 → <pad> o'rganilmaydi
            self.embedding = nn.Embedding(lugat_hajmi, embed_olcham, padding_idx=0)
     
            self.gru = nn.GRU(
                embed_olcham,
                yashirin,
                num_layers=qatlamlar,
                batch_first=True,
                dropout=dropout if qatlamlar > 1 else 0,
                bidirectional=True,          # ikki tomondan o'qiydi
            )
     
            self.dropout = nn.Dropout(dropout)
            self.chiqish = nn.Linear(yashirin * 2, 1)     # *2 — bidirectional
     
        def forward(self, x):
            vektorlar = self.embedding(x)                 # [paket, uzunlik, embed]
            _, yashirin = self.gru(vektorlar)             # yashirin: [qatlam*2, paket, yashirin]
     
            # Oxirgi qatlamning ikki yo'nalishini birlashtiramiz
            oxirgi = torch.cat((yashirin[-2], yashirin[-1]), dim=1)
     
            return self.chiqish(self.dropout(oxirgi)).squeeze(1)

    Uch qaror izohga muhtoj:

    • padding_idx=0<pad> tokeni uchun gradient hisoblanmaydi. Busiz model bo'sh joyni "o'rganishga" urinadi va bu sifatni pasaytiradi.
    • bidirectional=True — model matnni chapdan o'ngga va o'ngdan chapga o'qiydi. Kayfiyat tahlilida bu sezilarli foyda beradi, chunki inkor ko'pincha oxirida keladi: "yaxshi film emas edi".
    • dropout=0.4 — o'qitish paytida neyronlarning 40% i tasodifiy o'chiriladi. Bu modelni yodlab olishdan (overfitting) saqlaydi.
  5. O'qitish sikli

    from torch.utils.data import TensorDataset, DataLoader
     
    qurilma = torch.device("cuda" if torch.cuda.is_available() else "cpu")
     
    def loader_yasa(X, y, paket=64, aralashtir=False):
        ds = TensorDataset(torch.tensor(X, dtype=torch.long),
                           torch.tensor(y, dtype=torch.float32))
        return DataLoader(ds, batch_size=paket, shuffle=aralashtir)
     
    train_loader = loader_yasa(X_train_kod, y_train, aralashtir=True)
    val_loader = loader_yasa(X_val_kod, y_val)
     
    model = KayfiyatModel(len(lugat)).to(qurilma)
    criterion = nn.BCEWithLogitsLoss()                    # ikkita sinf uchun
    optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)
     
    eng_yaxshi = 0.0
     
    for davr in range(1, 11):
        # --- o'qitish ---
        model.train()
        for X_paket, y_paket in train_loader:
            X_paket, y_paket = X_paket.to(qurilma), y_paket.to(qurilma)
     
            optimizer.zero_grad()                          # eski gradientni tozalaymiz
            bashorat = model(X_paket)
            xato = criterion(bashorat, y_paket)
            xato.backward()
     
            # Gradient portlashining oldini olamiz — RNN oilasida zarur
            nn.utils.clip_grad_norm_(model.parameters(), max_norm=5.0)
            optimizer.step()
     
        # --- tekshirish ---
        model.eval()
        togri, jami = 0, 0
        with torch.no_grad():                              # gradient hisoblanmaydi — tezroq
            for X_paket, y_paket in val_loader:
                X_paket, y_paket = X_paket.to(qurilma), y_paket.to(qurilma)
                bashorat = torch.sigmoid(model(X_paket)) > 0.5
                togri += (bashorat == y_paket.bool()).sum().item()
                jami += len(y_paket)
     
        aniqlik = togri / jami
        print(f"{davr}-davr  aniqlik: {aniqlik:.4f}")
     
        if aniqlik > eng_yaxshi:
            eng_yaxshi = aniqlik
            torch.save(model.state_dict(), "eng_yaxshi.pt")     # eng yaxshisini saqlaymiz
  6. Ishlatish

    model.load_state_dict(torch.load("eng_yaxshi.pt"))
    model.eval()
     
    def bashorat_qil(matn):
        kodlangan = torch.tensor([kodlash(matn, lugat)], dtype=torch.long).to(qurilma)
        with torch.no_grad():
            ehtimollik = torch.sigmoid(model(kodlangan)).item()
     
        yorliq = "ijobiy" if ehtimollik > 0.5 else "salbiy"
        ishonch = ehtimollik if ehtimollik > 0.5 else 1 - ehtimollik
        return yorliq, ishonch
     
     
    for sharh in ["Ajoyib film, hammaga tavsiya qilaman",
                  "Vaqtimni behuda sarfladim",
                  "Yomon emas, lekin kutganimdek ham emas"]:
        yorliq, ishonch = bashorat_qil(sharh)
        print(f"{yorliq:>7}  ({ishonch:.0%})  ← {sharh}")
     ijobiy  (94%)  ← Ajoyib film, hammaga tavsiya qilaman
     salbiy  (89%)  ← Vaqtimni behuda sarfladim
     salbiy  (58%)  ← Yomon emas, lekin kutganimdek ham emas
    

    Uchinchi misolga e'tibor bering — model ikkilanyapti (58%). Va bu to'g'ri, chunki sharhning o'zi ham noaniq.

Ishlamasa nima qilish kerak#

Amaliyotda deyarli har doim biror narsa noto'g'ri ketadi. Eng ko'p uchraydiganlari:

BelgiSababYechim
Aniqlik 50% da qotib qoldilr juda katta yoki juda kichik1e-3 dan boshlang, keyin 10 barobar o'zgartirib sinang
O'qitishda 99%, tekshirishda 65%Ortiqcha moslashishdropout ni oshiring, max_features ni kamaytiring, ma'lumot qo'shing
CUDA out of memoryPaket juda kattabatch_size ni ikki barobar kamaytiring
Xato nan bo'lib ketdiGradient portlashiclip_grad_norm_ qo'shing (yuqorida bor)
O'lchamlar mos kelmadibatch_first unutilgannn.GRU(..., batch_first=True)
Juda sekin o'qiyaptiProtsessorda ishlayaptiColab'da bepul GPU oling
Amaliy topshiriq

Modelni yaxshilang va farqni o'lchang

Yuqoridagi modelni oling va quyidagi o'zgarishlarni birma-bir sinab, har birining tekshirish aniqligiga ta'sirini yozib boring:

  1. bidirectional=False qiling — qancha tushdi?
  2. yashirin o'lchamini 256 ga oshiring
  3. GRU o'rniga LSTM ishlating
  4. dropout ni 0.2 va 0.6 qilib ko'ring
  5. Tayyor word2vec embeddingini yuklang va requires_grad=False bilan muzlating

Qaysi o'zgarish eng ko'p foyda berdi? Nima uchun deb o'ylaysiz?

Maslahatni ko'rsatish

Har bir o'zgarishni ALOHIDA sinang, hammasini birdan emas. Aks holda qaysi biri yordam berganini bilmaysiz. Natijalarni jadvalda yozib boring: variant val aniqlik ------------------------------------ asosiy (GRU, 128) 0.8642 + bidirectional=False 0.8401 + hidden=256 0.8703 + dropout=0.5 0.8688 ... Tayyor embedding uchun: from gensim.models import KeyedVectors w2v = KeyedVectors.load_word2vec_format("cc.uz.300.vec") # Embedding matritsasini to'ldiring matritsa = np.zeros((len(lugat), 300)) for soz, i in lugat.items(): if soz in w2v: matritsa[i] = w2v[soz] model.embedding.weight.data.copy_(torch.tensor(matritsa)) model.embedding.weight.requires_grad = False # muzlatamiz


Xulosa#

Biz matndan songa o'tish yo'lini to'liq bosib o'tdik:

one-hot
har so'z alohida xonada
ma'no yo'q
bag-of-words
chastota qo'shildi
ma'no yo'q
TF-IDF
noyoblik hisobga olindi
ma'no yo'q
embedding (word2vec)
ma'no koordinataga aylandi
300 o'lcham
kosinus o'xshashligi
ma'nolar orasidagi masofa
-1 … 1
RNN / LSTM / GRU / CNN
ketma-ketlikni tushunadi
sekin, unutuvchan
Bobning to'liq xaritasi
Yodda saqlang
  • 01Embedding — ma'noni koordinataga aylantirish. Bu zamonaviy AI'ning poydevori: RAG ham, bilim grafikasi ham, multimodal modellar ham aynan shunga tayanadi.
  • 02Kosinus o'xshashligi — ikki ma'no orasidagi masofa. Kitobda oltita turli joyda qaytariladigan yagona formula. Yodda saqlang.
  • 03So'z tartibi ma'noni o'zgartiradi. Bag-of-words buni ko'rmaydi; RNN oilasi ko'radi, lekin sekin va unutuvchan.
  • 04Gradient so'nishi — RNN'ning halokatli kamchiligi. 20 qadamdan keyin signal deyarli nolga aylanadi.
  • 05Parallellashmaslik — eng qimmat cheklov. Aynan shu narsa keyingi bobdagi transformer'ni tug'dirdi.

Keyingi bobda ko'ramizki, 2017-yilda Google tadqiqotchilari RNN'ni yaxshilashga urinmadi. Ular uni butunlay olib tashladi — va bugungi ChatGPT shundan boshlandi.