1-qism · Matndan til modeligacha·02-bob
Transformer — barchasini o'zgartirgan g'oya
Attention mexanizmi qadam-baqadam, raqamlar bilan. Q/K/V nima, pozitsion kodlash nega kerak, dekodlash strategiyalari va BERT'ni o'z ma'lumotingizda moslash.
O'tgan bobda RNN oilasi bilan tanishdik. Ular ishlaydi — lekin uch muammosi bor edi va ular shunchalik jiddiy ediki, oxir-oqibat butun yondashuvdan voz kechishga to'g'ri keldi.
2017-yilda Google tadqiqotchilari maqola chiqardi. Sarlavhasi kesin edi: "Attention Is All You Need" — sizga faqat e'tibor kerak.
Ular RNN'ni yaxshilashga urinmadi. Ular uni butunlay olib tashladi.
Nima muammo edi#
Uchta to'siq bor edi va ularning har biri jiddiy.
1. Moslashuv muammosi#
Tarjimada kirish va chiqish uzunligi har xil bo'ladi:
ingliz: she doesn't like potatoes → 4 so'z
fransuz: elle n'aime pas les pommes de terre → 7 so'z
Qaysi so'z qaysi so'zga to'g'ri keladi? pommes de terre — bu uchta so'z bitta
potatoes ga to'g'ri keladi.
RNN buni bilmaydi. U butun gapni bitta qisqa vektorga siqadi, keyin undan tarjimani ochishga urinadi:
2. Gradient so'nishi#
O'tgan bobda hisoblab ko'rgan edik: 20 qadamdan keyin gradient 0.0000009 ga tushadi.
Model uzoq bog'lanishlarni o'rgana olmaydi.
3. Parallellashmaydi#
Bu eng qimmat cheklov. Raqamlar bilan ko'ramiz:
# RNN: 100 so'zlik gap, har qadam 1 ms
rnn_vaqt = 100 * 1 # 100 ms — qadamlar navbat bilan
# Transformer: hamma so'z bir vaqtda
tr_vaqt = 1 # ~1 ms — GPU hammasini parallel hisoblaydi
print(f"Tezlik farqi: {rnn_vaqt / tr_vaqt:.0f}x") # 100xGPU'da 10 000 yadro bor. RNN ularning bittasini ishlatadi, qolgani bo'sh turadi.
Attention: kerakli joyga qarash#
G'oya tarjimondan olingan. Tarjimon butun gapni yodlab, keyin qaytadan yozmaydi. U har bir so'zni yozayotganda manba matnning aynan kerakli joyiga qaraydi.
pommes de terre yozayotganda tarjimon potatoes ga qaraydi — she yoki doesn't ga emas.
Uch qadam#
- 1
- Ball berish (score)Har bir kirish so'zi hozirgi vazifaga qanchalik mos ekanini hisoblaymiz. Odatda skalyar ko'paytma bilan — birinchi bobdagi o'sha amal.
- 2
- SoftmaxBallarni 0 dan 1 gacha qiymatlarga aylantiramiz va yig'indisi 1 bo'ladi. Endi bu «e'tibor ulushi»: diqqatimning 70% i shu so'zda, 20% i qo'shnisida.
- 3
- Vaznli yig'indiHar bir so'zning vektorini o'z ulushiga ko'paytirib qo'shamiz. Natijada har qadam uchun YANGI kontekst vektori chiqadi.
Softmax nima — raqamlar bilan#
Softmax butun kitobda uchraydi, shuning uchun uni bir marta puxta tushunib olamiz.
U ixtiyoriy sonlar ro'yxatini ehtimolliklar ro'yxatiga aylantiradi:
import numpy as np
def softmax(x):
"""Sonlar ro'yxatini yig'indisi 1 bo'lgan ehtimolliklarga aylantiradi."""
e = np.exp(x - np.max(x)) # -max: katta sonlarda toshib ketmaslik uchun
return e / e.sum()
ballar = np.array([2.0, 1.0, 0.1])
print(softmax(ballar)) # [0.659 0.242 0.099]
print(softmax(ballar).sum()) # 1.0Softmax kuchaytiradi: kichik farqni katta farqqa aylantiradi.
print(softmax(np.array([1.0, 1.1]))) # [0.475 0.525] ← kichik farq
print(softmax(np.array([1.0, 3.0]))) # [0.119 0.881] ← katta farqSelf-attention: kutubxonadagi qidiruv#
Endi asosiy g'oyaga o'tamiz. Self-attention — matnning o'zi ichida, boshqa hech narsa bilan solishtirmasdan qidirish.
Eng yaxshi tashbeh — kutubxona:
Kutubxonaga kirdingiz, Fransuz inqilobi haqida kitob kerak. Siz hamma kitobni o'qib chiqmaysiz. Siz javondagi kitoblarning muqovasidagi nomlarga qaraysiz va keraklisini tanlab olasiz.
- Q
- Query — so'rov«Menga Fransuz inqilobi haqida kitob kerak». Bu — hozir e'tibor qaratilayotgan so'zning nima izlayotgani.
- K
- Key — kalitKitob muqovasidagi nom. Har bir so'zning «men bu haqdaman» degan yorlig'i. So'rov aynan shular bilan solishtiriladi.
- V
- Value — qiymatKitobning ichidagi matn. So'z mos kelsa, undan olinadigan haqiqiy ma'lumot.
Formula va uning ma'nosi#
Attention(Q, K, V) = softmax( Q · Kᵀ / √d ) · V
Bo'lib tushuntiramiz:
| Qism | Nima qiladi |
|---|---|
Q · Kᵀ | Har bir so'rovni har bir kalit bilan solishtiradi → ballar matritsasi |
/ √d | Ballarni jilovlaydi (pastda batafsil) |
softmax(...) | Ballarni e'tibor ulushlariga aylantiradi |
· V | Ulushlarga ko'ra ma'lumotni yig'adi |
Nima uchun √d ga bo'linadi#
Bu detal ko'pincha tushuntirilmaydi, lekin u muhim. Tajriba qilamiz:
import numpy as np
np.random.seed(0)
for d in [4, 64, 512]:
q = np.random.randn(d)
k = np.random.randn(d)
ball = q @ k
print(f"d={d:>3}: ball = {ball:>7.2f} √d ga bo'lgach = {ball / np.sqrt(d):>6.2f}")d= 4: ball = 1.83 √d ga bo'lgach = 0.92
d= 64: ball = -8.94 √d ga bo'lgach = -1.12
d=512: ball = 26.13 √d ga bo'lgach = 1.15
O'lcham oshgan sari skalyar ko'paytma kattalashadi. Va softmax katta sonlarni ko'rsa nima bo'ladi:
kichik = np.array([1.0, 2.0, 0.5])
katta = kichik * 20
print(softmax(kichik)) # [0.245 0.665 0.090] ← taqsimlangan
print(softmax(katta)) # [0.000 1.000 0.000] ← bittasiga yopishib qoldiSoftmax deyarli butun ehtimollikni bitta elementga beradi. Bu ikki muammo tug'diradi:
- Model faqat bitta so'zga qaraydi, kontekstni ko'rmaydi
- Gradient nolga tushadi — softmax to'yingan zonada hosila juda kichik, model o'rganmaydi
√d ga bo'lish ballarni taxminan -2 … 2 oralig'ida ushlab turadi. Oddiy, lekin butun
o'qitishni mumkin qiladigan detal.
Attention'ni noldan yozamiz#
Nazariyani kodga aylantiramiz. To'liq ishlaydigan misol:
import numpy as np
def softmax(x, axis=-1):
e = np.exp(x - np.max(x, axis=axis, keepdims=True))
return e / e.sum(axis=axis, keepdims=True)
def self_attention(X, W_q, W_k, W_v):
"""
X — kirish: (n_token, d_model)
W_* — o'rganiladigan matritsalar: (d_model, d_k)
"""
Q = X @ W_q # (n, d_k)
K = X @ W_k # (n, d_k)
V = X @ W_v # (n, d_k)
d_k = Q.shape[-1]
ballar = Q @ K.T / np.sqrt(d_k) # (n, n) — har so'z har so'zga
ulushlar = softmax(ballar) # (n, n) — qatorlar yig'indisi 1
return ulushlar @ V, ulushlar # (n, d_k), (n, n)
# 4 ta token, har biri 8 o'lchamli
np.random.seed(42)
X = np.random.randn(4, 8)
W_q, W_k, W_v = (np.random.randn(8, 8) * 0.1 for _ in range(3))
chiqish, ulushlar = self_attention(X, W_q, W_k, W_v)
print("chiqish shakli:", chiqish.shape) # (4, 8)
print("\ne'tibor matritsasi (har qator yig'indisi = 1):")
print(np.round(ulushlar, 3))
print("\nqatorlar yig'indisi:", np.round(ulushlar.sum(axis=1), 6))chiqish shakli: (4, 8)
e'tibor matritsasi (har qator yig'indisi = 1):
[[0.254 0.245 0.253 0.248]
[0.248 0.259 0.244 0.249]
[0.251 0.243 0.256 0.250]
[0.247 0.252 0.248 0.253]]
qatorlar yig'indisi: [1. 1. 1. 1.]
E'tibor matritsasini qo'lda hisoblang
Quyidagi kichik misolni qog'ozda hisoblang, keyin kod bilan tekshiring:
Q = np.array([[1, 0],
[0, 1],
[1, 1]], dtype=float)
K = np.array([[1, 0],
[0, 1],
[1, 1]], dtype=float)
V = np.array([[10, 0],
[0, 10],
[5, 5]], dtype=float)Q @ K.Tni hisoblang√2ga bo'ling- Har qatorga softmax qo'llang
Vga ko'paytiring
Uchinchi qator ([1,1]) natijasi nima uchun boshqalardan farq qiladi?
Maslahatni ko'rsatish
Yechim: Q @ K.T ni hisoblang → 3x3 matritsa Har bir elementni √2 ga bo'ling Har qatorga alohida softmax qo'llang Birinchi qator uchun: ballar = [1*1+0*0, 1*0+0*1, 1*1+0*1] = [1, 0, 1] /√2 → [0.707, 0, 0.707] softmax → [0.422, 0.208, 0.370] (taxminan) Diqqat: softmax har QATORGA alohida qo'llanadi, butun matritsaga emas. Agar butun matritsaga qo'llasangiz, qatorlar yig'indisi 1 bo'lmaydi.
Multi-head: bir vaqtda bir necha savol#
Bitta gapga bir nechta savol berish mumkin:
"Anvar kecha Toshkentda ukasiga kitob berdi"
kim harakat qildi? → Anvar
qachon? → kecha
qayerda? → Toshkentda
kimga? → ukasiga
nimani? → kitob
Bitta attention bularning hammasini bir vaqtda kuzata olmaydi. Shuning uchun bir nechta parallel attention ishlatiladi — har biri head (bosh) deb ataladi.
def multi_head_attention(X, n_head=4):
"""Soddalashtirilgan multi-head: har bir head alohida hisoblanadi."""
d_model = X.shape[-1]
d_head = d_model // n_head # har bir head ulushi
natijalar = []
for _ in range(n_head):
W_q, W_k, W_v = (np.random.randn(d_model, d_head) * 0.1 for _ in range(3))
chiqish, _ = self_attention(X, W_q, W_k, W_v)
natijalar.append(chiqish)
birlashgan = np.concatenate(natijalar, axis=-1) # (n, d_model)
W_o = np.random.randn(d_model, d_model) * 0.1
return birlashgan @ W_o
X = np.random.randn(4, 8)
print(multi_head_attention(X, n_head=4).shape) # (4, 8)Diqqat qiling: d_head = d_model // n_head. Head soni ko'paysa, har birining o'lchami
kichrayadi — umumiy hisob hajmi o'zgarmaydi. 4 ta katta head yoki 8 ta kichik head —
narx bir xil.
- Bir kirishdan bir nechta turli tasavvur — har head o'z munosabatini kuzatadi
- Hamma head parallel hisoblanadi — GPU to'liq band bo'ladi
- Uzoq so'zlar orasidagi bog'liqlik BITTA qadamda ko'rinadi, RNN'dagidek navbat kutmaydi
- E'tibor matritsasini ko'rish mumkin — model qaysi so'zga qarayotgani ma'lum
- Narx tokenlar soniga KVADRATIK: 2 barobar uzun matn = 4 barobar hisob
- Xotira ham kvadratik — kontekst oynasi shu sabab cheklangan
- Tartibni umuman bilmaydi — so'zlar to'plamini ko'radi, ketma-ketlikni emas
- Kichik ma'lumotda RNN'dan yomonroq ishlaydi — ko'p misol talab qiladi
Kvadratik narx — bugungi eng katta muammo. 100 ming tokenli kontekst uchun e'tibor matritsasi 10 milliard katakdan iborat bo'ladi. Shuning uchun FlashAttention, sliding window, linear attention kabi o'nlab optimizatsiya usullari o'ylab topilgan.
Pozitsion kodlash: tartibni qaytarish#
Self-attention tartibni bilmaydi. Buni isbotlash oson:
X1 = np.array([[1., 0.], [0., 1.]]) # ikki token
X2 = X1[::-1] # o'rinlari almashtirilgan
W = np.eye(2)
c1, _ = self_attention(X1, W, W, W)
c2, _ = self_attention(X2, W, W, W)
print(np.allclose(c1, c2[::-1])) # True — natija bir xil!Model uchun "It odamni qopdi" va "Odamni it qopdi" bir xil.
Yechim: o'rinni vektorga qo'shish#
Asl maqolada sinus va kosinus funksiyalari ishlatilgan:
PE(pos, 2i) = sin( pos / 10000^(2i/d) )
PE(pos, 2i+1) = cos( pos / 10000^(2i/d) )
Kodda:
def positional_encoding(max_uzunlik, d_model):
"""Har bir o'rin uchun takrorlanmas 'imzo' vektori."""
pos = np.arange(max_uzunlik)[:, None] # (uzunlik, 1)
i = np.arange(d_model)[None, :] # (1, d_model)
burchak = pos / np.power(10000, (2 * (i // 2)) / d_model)
pe = np.zeros((max_uzunlik, d_model))
pe[:, 0::2] = np.sin(burchak[:, 0::2]) # juft o'lchamlar
pe[:, 1::2] = np.cos(burchak[:, 1::2]) # toq o'lchamlar
return pe
pe = positional_encoding(50, 128)
print(pe.shape) # (50, 128)
# Embedding'ga QO'SHILADI, ulanmaydi
X_with_pos = X_embedding + pe[:len(X_embedding)]Uni ko'rish mumkin:
import matplotlib.pyplot as plt
pe = positional_encoding(100, 64)
plt.figure(figsize=(10, 4))
plt.imshow(pe.T, cmap="RdBu", aspect="auto")
plt.xlabel("pozitsiya")
plt.ylabel("o'lcham")
plt.colorbar()
plt.title("Pozitsion kodlash")
plt.show()Grafikda to'lqinsimon naqsh ko'rinadi: pastki o'lchamlar tez o'zgaradi (yaqin pozitsiyalarni ajratadi), yuqorigilari sekin (uzoq masofani ifodalaydi).
Transformer bloki#
Endi qismlarni yig'amiz. Bitta blok to'rt komponentdan iborat.
Qoldiq bog'lanish — eng muhim detal#
H = LayerNorm(X + Attention(X)) — bu + X qismi ko'rinishidan arzimas, aslida esa
chuqur tarmoqlarni umuman mumkin qilgan narsa.
Nima uchun? Gradient hisoblang:
# Qoldiq bog'lanishsiz: gradient har qatlamda ko'payadi
g = 1.0
for qatlam in range(50):
g *= 0.9 # har qatlam gradientni 0.9 ga ko'paytiradi
print(f"Qoldiqsiz, 50 qatlam: {g:.8f}") # 0.00515378
# Qoldiq bilan: qisqa yo'l bor, gradient 1 dan boshlanadi
g = 1.0
for qatlam in range(50):
g = g * 0.9 + g # + g — qoldiq bog'lanish orqali o'tadi
print(f"Qoldiq bilan, 50 qatlam: {g:.2e}") # juda katta, ya'ni signal yo'qolmaydiQoldiq bog'lanish gradientga qisqa yo'l beradi — u qatlamlar orqali o'tmasdan to'g'ridan-to'g'ri pastga tushadi.
Layer normalization#
Vektor qiymatlarini o'rtacha 0, standart chetlanish 1 bo'ladigan qilib keltiradi:
def layer_norm(x, eps=1e-5):
"""Har bir vektorni alohida normallashtiradi."""
ortacha = x.mean(axis=-1, keepdims=True)
std = x.std(axis=-1, keepdims=True)
return (x - ortacha) / (std + eps)
x = np.array([10.0, 20.0, 30.0, 40.0])
print(layer_norm(x)) # [-1.342 -0.447 0.447 1.342]
print(layer_norm(x).mean()) # ~0
print(layer_norm(x).std()) # ~1Nima uchun kerak: o'qitish davomida qiymatlar sudralib ketadi — ba'zi qatlamlarda juda katta, ba'zilarida juda kichik bo'lib qoladi. Bu o'qitishni beqaror qiladi. Normallashtirish ularni bir oraliqda ushlab turadi.
Feedforward qatlam#
Ikkita chiziqli qatlam, orasida ReLU:
FFN(x) = max(0, x·W₁ + b₁)·W₂ + b₂
def feedforward(x, d_model, d_ff=None):
"""Odatda d_ff = 4 × d_model."""
d_ff = d_ff or d_model * 4
W1 = np.random.randn(d_model, d_ff) * 0.02
W2 = np.random.randn(d_ff, d_model) * 0.02
yashirin = np.maximum(0, x @ W1) # ReLU
return yashirin @ W2To'liq blok#
class TransformerBlock:
"""Soddalashtirilgan transformer bloki (o'qitish qismisiz)."""
def __init__(self, d_model, n_head):
self.d_model = d_model
self.n_head = n_head
def forward(self, x):
# 1. Attention + qoldiq + norm
attn = multi_head_attention(x, self.n_head)
x = layer_norm(x + attn)
# 2. FFN + qoldiq + norm
ff = feedforward(x, self.d_model)
x = layer_norm(x + ff)
return x
blok = TransformerBlock(d_model=8, n_head=2)
X = np.random.randn(4, 8)
for i in range(3): # 3 ta blokni ketma-ket qo'llaymiz
X = blok.forward(X)
print(X.shape) # (4, 8) — o'lcham o'zgarmadiKirish va chiqish o'lchami bir xil — shuning uchun bloklarni xohlagancha ustma-ust qo'yish mumkin. GPT-3'da 96 ta blok bor va ularning tuzilishi aynan bir xil.
Encoder, decoder va niqob#
Asl transformer tarjima uchun ikki qismdan iborat edi.
Niqob (mask) — nima uchun kerak#
Decoder keyingi so'zni bashorat qilishi kerak. Agar u javobni oldindan ko'rsa, o'rganish ma'nosiz bo'ladi — bu ma'lumot sizib chiqishi.
Yechim: kelajakdagi tokenlarni -∞ bilan to'sish.
def causal_mask(n):
"""Yuqori uchburchakni -inf qiladi: har token faqat o'zidan oldingilarni ko'radi."""
mask = np.triu(np.ones((n, n)), k=1) # yuqori uchburchak = 1
return np.where(mask == 1, -np.inf, 0.0)
n = 4
mask = causal_mask(n)
print(mask)[[ 0. -inf -inf -inf]
[ 0. 0. -inf -inf]
[ 0. 0. 0. -inf]
[ 0. 0. 0. 0.]]
Ballar matritsasiga qo'shiladi, keyin softmax qo'llanadi:
ballar = np.random.randn(4, 4)
ulushlar = softmax(ballar + mask)
print(np.round(ulushlar, 3))[[1. 0. 0. 0. ]
[0.412 0.588 0. 0. ]
[0.244 0.377 0.379 0. ]
[0.301 0.198 0.259 0.242]]
exp(-inf) = 0 bo'lgani uchun kelajakdagi tokenlarning ulushi aniq nol. Birinchi
token faqat o'zini ko'radi, ikkinchisi birinchi ikkitasini va hokazo.
Model qanday o'qitiladi#
Bu murakkab modelni o'qitish kutilganidan ancha sodda.
Vazifa bitta: matnning bir qismini ber, keyingi so'zni bashorat qil.
kirish: "Bo'lish yoki bo'lmaslik — mana"
javob: "masala"
To'g'ri javob matnning o'zida turibdi. Hech kim qo'lda yorliq yozmaydi. Shuning uchun bunga self-supervised — o'z-o'zini nazorat qiluvchi o'qitish deyiladi.
Internetdagi har qanday matn tayyor o'quv materiali.
Cross-entropy — xatoni o'lchash#
def cross_entropy(bashorat, togri_indeks):
"""
bashorat — lug'atdagi har so'z uchun ehtimollik
togri_indeks — haqiqiy keyingi so'zning raqami
"""
return -np.log(bashorat[togri_indeks] + 1e-12)
lugat = ["masala", "savol", "traktor"]
bashorat = np.array([0.7, 0.2, 0.1])
for i, soz in enumerate(lugat):
print(f"agar to'g'ri javob '{soz}' bo'lsa → xato = {cross_entropy(bashorat, i):.3f}")agar to'g'ri javob 'masala' bo'lsa → xato = 0.357
agar to'g'ri javob 'savol' bo'lsa → xato = 1.609
agar to'g'ri javob 'traktor' bo'lsa → xato = 2.303
Mantiq aniq: model to'g'ri javobga qanchalik yuqori ehtimollik bergan bo'lsa, xato shunchalik kichik. Agar u to'g'ri javobni deyarli nol deb baholasa, xato juda katta bo'ladi va model kuchli tuzatiladi.
Dekodlash: javobni tanlash#
Modelda ehtimollik vektori bor. Undan qanday so'z tanlanadi? Bu tanlov natijaga hayratlanarli darajada ta'sir qiladi.
def greedy(ehtimolliklar):
"""Eng ehtimolli so'zni oladi."""
return int(np.argmax(ehtimolliklar))
def temperature_sample(logitlar, t=1.0):
"""t kichik → ishonchli va zerikarli; t katta → tavakkalchi va ijodiy."""
return softmax(logitlar / t)
def top_k(ehtimolliklar, k=5):
"""Faqat eng ehtimolli k ta so'zdan tanlaydi."""
indekslar = np.argsort(ehtimolliklar)[::-1][:k]
p = ehtimolliklar[indekslar]
p = p / p.sum() # qayta normallashtirish
return int(np.random.choice(indekslar, p=p))
def top_p(ehtimolliklar, p=0.9):
"""Ehtimolliklar yig'indisi p ga yetguncha so'z oladi (nucleus sampling)."""
indekslar = np.argsort(ehtimolliklar)[::-1]
yigindi = np.cumsum(ehtimolliklar[indekslar])
kesim = np.searchsorted(yigindi, p) + 1
tanlov = indekslar[:kesim]
q = ehtimolliklar[tanlov]
q = q / q.sum()
return int(np.random.choice(tanlov, p=q))Temperature qanday ishlashini ko'ramiz:
logitlar = np.array([3.0, 2.0, 1.0, 0.5, 0.1])
for t in [0.1, 0.5, 1.0, 2.0]:
p = temperature_sample(logitlar, t)
print(f"t={t:>4}: {np.round(p, 3)}")t= 0.1: [1. 0. 0. 0. 0. ] ← greedy bilan bir xil
t= 0.5: [0.84 0.114 0.015 0.006 0.003] ← ishonchli
t= 1.0: [0.578 0.213 0.078 0.047 0.032] ← muvozanatli
t= 2.0: [0.363 0.22 0.134 0.104 0.081] ← tavakkalchi
BPE: notanish so'z muammosi#
Lug'at cheklangan. Notanish so'z kelsa <UNK> bo'lib qoladi va ma'no yo'qoladi.
Yechim: so'zni butun deb hisoblamaslik.
BPE'ni noldan yozamiz#
Byte-Pair Encoding algoritmi hayratlanarli darajada oddiy. Uni to'liq yozamiz:
from collections import Counter
def bpe_orgatish(korpus, birlashmalar=10):
"""Eng ko'p uchraydigan harf juftliklarini ketma-ket birlashtiradi."""
# Har so'zni harflarga ajratamiz, oxiriga so'z tugashi belgisi
lugat = Counter()
for soz in korpus.split():
lugat[" ".join(soz) + " </w>"] += 1
qoidalar = []
for qadam in range(birlashmalar):
# Barcha qo'shni juftliklarni sanaymiz
juftlar = Counter()
for soz, son in lugat.items():
belgilar = soz.split()
for i in range(len(belgilar) - 1):
juftlar[(belgilar[i], belgilar[i + 1])] += son
if not juftlar:
break
eng_kop = juftlar.most_common(1)[0][0]
qoidalar.append(eng_kop)
# Shu juftlikni butun lug'atda birlashtiramiz
yangi = {}
eski = " ".join(eng_kop)
birlashgan = "".join(eng_kop)
for soz, son in lugat.items():
yangi[soz.replace(eski, birlashgan)] = son
lugat = yangi
print(f"{qadam + 1:>2}. birlashtirildi: {eng_kop[0]!r} + {eng_kop[1]!r} → {birlashgan!r}")
return qoidalar, lugat
korpus = "kitob kitoblar kitobxon kitobcha maktab maktablar maktabda"
qoidalar, lugat = bpe_orgatish(korpus, birlashmalar=8)
print("\nYakuniy bo'linish:")
for soz in lugat:
print(" ", soz)Natija:
1. birlashtirildi: 'k' + 'i' → 'ki'
2. birlashtirildi: 'ki' + 't' → 'kit'
3. birlashtirildi: 'kit' + 'o' → 'kito'
4. birlashtirildi: 'kito' + 'b' → 'kitob'
5. birlashtirildi: 'm' + 'a' → 'ma'
6. birlashtirildi: 'ma' + 'k' → 'mak'
7. birlashtirildi: 'mak' + 't' → 'makt'
8. birlashtirildi: 'makt' + 'a' → 'makta'
Yakuniy bo'linish:
kitob </w>
kitob l a r </w>
kitob x o n </w>
kitob c h a </w>
makta b </w>
makta b l a r </w>
makta b d a </w>
Algoritm hech kim aytmasdan turib kitob va makta o'zaklarini o'zi topdi.
BERT va niqoblangan o'qitish#
Asl transformer matnni faqat chapdan o'ngga o'qiydi. Lekin ba'zi vazifalarda ikki tomondan kontekst kerak:
"U bankka bordi va pul yechdi."
↑ qanday bank? Javob O'NGDA turibdi.
BERT shuni hal qildi. Lekin ikki tomonlama model uchun "keyingi so'zni bashorat qil" vazifasi ishlamaydi — javob ko'rinib turibdi.
Shuning uchun boshqa vazifa: Masked Language Model.
kirish: "Bugun havo juda [MASK] edi"
vazifa: [MASK] o'rnida nima bo'lishi kerak?
Tokenlarning 15% i tasodifiy yashiriladi.
- Ikki tomondan kontekst — ma'no noaniqligini yaxshi hal qiladi
- Tasniflash, NER, qidiruvda kuchli
- Kichik va tez — CPU'da ham ishlaydi
- Fine-tuning uchun kam misol yetarli
- Matn generatsiya qila olmaydi (2024 gacha shunday deb hisoblangan)
- O'qitish isrofgar — har o'tishda tokenlarning faqat 15% i o'rganishga hissa qo'shadi
- Kontekst oynasi kichik — odatda 512 token
- Ko'rsatmaga amal qilmaydi — har vazifa uchun alohida fine-tuning kerak
Aynan shu isrofgarlik sabab bugungi katta modellar decoder-only: u yerda HAR BIR token o'quv misoli bo'ladi, ya'ni bir xil hisob quvvatidan 6-7 barobar ko'p foyda olinadi.
Amaliyot: BERT'ni o'z ma'lumotingizda moslash#
Katta modelni noldan o'qitish imkonsiz — BERT 64 ta TPU'da 4 kun o'qitilgan. Lekin kerak ham emas.
Ilmiy maqola yozishni o'rganish uchun tilni qaytadan o'rganmaysiz. Bilgan tilingizni yangi vazifaga moslaysiz, xolos.
Kutubxonalarni o'rnating
terminalpip install transformers datasets accelerate evaluateSuccessfully installed transformers-4.48.0 datasets-3.2.0 ...Ma'lumotni tayyorlang
from datasets import Dataset import pandas as pd df = pd.read_csv("sharhlar.csv") # ustunlar: matn, belgi (0/1) ds = Dataset.from_pandas(df) ds = ds.train_test_split(test_size=0.2, seed=42) print(ds)DatasetDict({ train: Dataset({ features: ['matn', 'belgi'], num_rows: 8000 }) test: Dataset({ features: ['matn', 'belgi'], num_rows: 2000 }) })Tokenizatsiya qiling
from transformers import AutoTokenizer MODEL = "distilbert-base-multilingual-cased" # o'zbekchani ham tushunadi tokenizer = AutoTokenizer.from_pretrained(MODEL) def tokenlash(namunalar): return tokenizer( namunalar["matn"], truncation=True, # 512 tokendan uzunini kesadi max_length=256, padding="max_length", ) ds = ds.map(tokenlash, batched=True) ds = ds.rename_column("belgi", "labels") # Trainer shu nomni kutadi ds.set_format("torch", columns=["input_ids", "attention_mask", "labels"])Modelni yuklang va o'qiting
import numpy as np import evaluate from transformers import ( AutoModelForSequenceClassification, TrainingArguments, Trainer, ) model = AutoModelForSequenceClassification.from_pretrained(MODEL, num_labels=2) metrika = evaluate.load("accuracy") def hisobla(eval_pred): logitlar, yorliqlar = eval_pred bashorat = np.argmax(logitlar, axis=-1) return metrika.compute(predictions=bashorat, references=yorliqlar) args = TrainingArguments( output_dir="./natijalar", num_train_epochs=3, per_device_train_batch_size=16, per_device_eval_batch_size=32, learning_rate=2e-5, # fine-tuning uchun KICHIK bo'lishi shart warmup_steps=500, weight_decay=0.01, eval_strategy="epoch", save_strategy="epoch", load_best_model_at_end=True, logging_steps=50, ) trainer = Trainer( model=model, args=args, train_dataset=ds["train"], eval_dataset=ds["test"], compute_metrics=hisobla, ) trainer.train()Ishlatib ko'ring
from transformers import pipeline klassifikator = pipeline( "text-classification", model=model, tokenizer=tokenizer, device=0 if torch.cuda.is_available() else -1, ) for matn in ["Xizmat ajoyib edi, rahmat!", "Juda uzoq kutdik, boshqa kelmayman"]: natija = klassifikator(matn)[0] print(f"{natija['label']} {natija['score']:.0%} ← {matn}")
E'tiborni ko'rish#
Attention'ning ajoyib tomoni — uni ko'rish mumkin:
# pip install bertviz
from bertviz import head_view
from transformers import AutoTokenizer, AutoModel
model = AutoModel.from_pretrained("bert-base-uncased", output_attentions=True)
tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased")
matn = "The cat sat on the mat because it was tired"
kirish = tokenizer(matn, return_tensors="pt")
chiqish = model(**kirish)
tokenlar = tokenizer.convert_ids_to_tokens(kirish["input_ids"][0])
head_view(chiqish.attentions, tokenlar)Bu interaktiv vizualizatsiya ochadi. Unda ko'rasizki:
- Ba'zi head'lar olmoshni otga bog'laydi —
it→cat - Ba'zilari artikllarni otga bog'laydi
- Ba'zilari faqat qo'shni so'zga qaraydi
- Ba'zilari
[CLS]yoki[SEP]ga qaraydi (bu "hech narsa qilmayapman" degani)
O'z modelingizni o'qiting va e'tiborni tahlil qiling
- O'zbekcha yoki inglizcha kayfiyat to'plamini toping (kamida 2000 ta misol)
distilbert-base-multilingual-casedni fine-tuning qiling- Aniqlikni o'lchang va 1-bobdagi GRU modeli bilan solishtiring
bertvizbilan bir nechta gapning e'tibor matritsasini ko'ring
Savollar:
- Fine-tuning natijasi noldan o'qitilgan GRU'dan qanchalik yaxshi?
- Qaysi head'lar mantiqiy naqsh ko'rsatyapti, qaysilari shovqin?
- Model xato qilgan misollarni ko'ring — ularda umumiy narsa bormi?
Maslahatni ko'rsatish
Ma'lumot topish: - huggingface.co/datasets da o'zbekcha to'plamlar bor - Telegram kanal izohlarini yig'ing - Play Market ilova sharhlarini oling Agar aniqlik 60% dan past bo'lsa, tekshiring: 1. Yorliqlar to'g'ri joylashganmi? (0 va 1, matn emas) 2. max_length yetarlimi? Uzun sharhlar kesilmayaptimi? 3. Sinflar muvozanatlimi? 90/10 bo'lsa model doim ko'pchilikni tanlaydi 4. learning_rate 2e-5 mi? Katta bo'lsa model buziladi E'tiborni tahlil qilishda [CLS] va [SEP] ga qaragan head'lar ko'p bo'ladi — bu normal, ular 'bo'sh' head'lar deb ataladi.
Distillation: katta modeldan kichigiga#
Oxirgi texnika. Katta model juda ko'p narsa biladi, lekin bitta vazifa uchun bu ortiqcha va qimmat.
Knowledge distillation — bilimni "o'qituvchi" modeldan "o'quvchi" modelga o'tkazish.
Nima uchun bu shunchaki yorliqlarda o'qitishdan yaxshiroq?
Chunki o'qituvchining ehtimolliklari ko'proq ma'lumot beradi:
Quruq yorliq: "ijobiy"
O'qituvchi ehtimolligi: ijobiy 0.55, salbiy 0.45
Ikkinchisi qo'shimcha ma'lumot beradi: bu misol chegaraviy. O'quvchi model shundan "bu turdagi gaplar noaniq" degan bilimni oladi.
Bu soft labels yoki "dark knowledge" deb ataladi.
Xulosa#
- 01Q, K, V — kutubxonadagi qidiruv. So'rov, kalit, qiymat. Uchtaligi bejiz emas: so'zning qidirayotgani bilan taklif qilayotgani har xil narsa.
- 02√d ga bo'lish — arzimas ko'rinadigan, lekin butun o'qitishni mumkin qiladigan detal. Busiz softmax to'yinadi va gradient nolga tushadi.
- 03Qoldiq bog'lanish (+ X) — 96 qatlamli modelni o'qitish imkonini bergan narsa. Gradientga qisqa yo'l beradi.
- 04Parallellik — asosiy yutuq. Aynan shu modellarni milliardlab parametrgacha o'stirish imkonini berdi.
- 05Temperature va top-p — javob sifatiga eng ko'p ta'sir qiladigan sozlamalar. JSON kerak bo'lsa temperature=0 qo'ying.
- 06O'zbekcha matn ingliz tiliga qaraganda 2-3 barobar ko'p token yeydi. Byudjet hisoblashda buni unutmang.
Keyingi bobda bu bloklardan katta til modellari qanday yig'ilishini ko'ramiz: miqyos qonuni, kutilmagan qobiliyatlar, LoRA bilan arzon moslash va RLHF.