DatasetvəDataLoaderilə verilənləri paketlərə bölmək- Tapşırığa uyğun itki funksiyası və optimallaşdırıcı seçmək
- Tam öyrətmə dövrünü yazmaq və modeli validasiya/test verilənlərində qiymətləndirmək
- Modeli
state_dictilə saxlamaq və yenidən yükləmək
Artıq bütün hissələr əlimizdədir: tenzorlar, autograd və nn.Module. İndi onları peşəkarların hər gün yazdığı standart resept şəklində birləşdirək: verilənlər → DataLoader → model → itki → optimallaşdırıcı → öyrətmə dövrü → qiymətləndirmə → saxlama. Maraqlıdır ki, bu skelet kiçik təsnifatçı üçün də, milyardlarla parametrli dil modeli üçün də demək olar ki, eynidir — yalnız verilənlərin və modelin ölçüsü dəyişir.
Dataset və DataLoader
Dataset nümunələrin siyahısı kimi davranan obyektdir: __len__ nümunələrin sayını, __getitem__(i) isə i-ci (x, y) cütünü qaytarır. Verilənlər artıq tenzordadırsa, hazır TensorDataset kifayətdir; fayllardan (şəkillər, mətnlər) oxumaq üçün öz sinfini yazırsan. DataLoader isə Dataset-dən paketlər düzəldir, lazım olsa hər epoxada nümunələri qarışdırır (shuffle=True) və num_workers ilə yükləməni paralel apara bilir.
import torch
from torch.utils.data import Dataset, DataLoader
class SquaresDataset(Dataset):
def __init__(self, n):
self.x = torch.arange(n, dtype=torch.float32)
def __len__(self):
return len(self.x)
def __getitem__(self, i):
return self.x[i], self.x[i] ** 2
ds = SquaresDataset(10)
print(len(ds), ds[3])
loader = DataLoader(ds, batch_size=4)
for xb, yb in loader:
print(xb.tolist(), yb.tolist())10 (tensor(3.), tensor(9.)) [0.0, 1.0, 2.0, 3.0] [0.0, 1.0, 4.0, 9.0] [4.0, 5.0, 6.0, 7.0] [16.0, 25.0, 36.0, 49.0] [8.0, 9.0] [64.0, 81.0]
Paket — bir yeniləmədə birlikdə emal olunan nümunələr qrupudur. Addım və ya iterasiya — bir paket üzrə bir parametr yeniləməsidir. Epoxa — öyrədici dəstin bütün nümunələrinin bir dəfə «görülməsidir». Öyrətmə adətən onlarla epoxa davam edir və hər epoxa çoxlu addımdan ibarətdir.
- Nöyrədici dəstdəki nümunələrin sayı
- Bpaketin ölçüsü (batch size)
- ⌈ ⌉yuxarı yuvarlaqlaşdırma: son natamam paket də bir addımdır
İtki funksiyaları və optimallaşdırıcılar
| Tapşırıq | Son qat | İtki | Hədəflər |
|---|---|---|---|
| Reqressiya | nn.Linear(h, 1) | nn.MSELoss | float32 ədədlər |
| İkili təsnifat | nn.Linear(h, 1) | nn.BCEWithLogitsLoss | 0,0 və ya 1,0 (float32) |
| K sinifli təsnifat | nn.Linear(h, K) | nn.CrossEntropyLoss | sinif nömrələri 0…K−1 (int64) |
- zmodelin xam logitləri
- tdüzgün sinfin nömrəsi
nn.CrossEntropyLoss log-softmax ilə çarpaz entropiyanı birləşdirir: −ln softmax(z)ₜ = −zₜ + ln ∑ eᶻʲ. Paket üçün itkilərin orta qiyməti götürülür.
a) Logitlər z = (2; 0,5; −1), düzgün sinif 0. CrossEntropyLoss nədir?
b) Proqnozlar (2,5; 0), həqiqi qiymətlər (3; −0,5). MSELoss nədir?
Həllini göstərHəllini gizlət
L = −2 + 2,2413 = 0,2413.
b) Səhvlər −0,5 və 0,5, kvadratları 0,25 və 0,25; orta qiymət 0,25.
import torch
from torch import nn
logits = torch.tensor([[2.0, 0.5, -1.0]])
target = torch.tensor([0])
print(round(nn.CrossEntropyLoss()(logits, target).item(), 4))
pred = torch.tensor([2.5, 0.0])
true = torch.tensor([3.0, -0.5])
print(round(nn.MSELoss()(pred, true).item(), 4))0.2413 0.25
Optimallaşdırıcı qradiyentlərə əsasən parametrləri yeniləyir. Ən sadəsi torch.optim.SGD-dir: onun step() metodu θ ← θ − η · g düsturunu tətbiq edir. Aşağıdakı kod birinci dərsdəki «bir addım» nümunəsini (x = 2, y = 6, w = 1, η = 0,1) təkrarlayır və eyni w = 2,6 cavabını alır. zero_grad() isə qradiyentləri None-a sıfırlayır.
import torch
w = torch.tensor([1.0], requires_grad=True)
optimizer = torch.optim.SGD([w], lr=0.1)
loss = ((2 * w - 6) ** 2).sum()
loss.backward()
print(w.grad)
optimizer.step()
print(w)
optimizer.zero_grad()
print(w.grad)tensor([-16.]) tensor([2.6000], requires_grad=True) None
- gₜt addımındakı qradiyent
- mₜ, vₜqradiyentin və onun kvadratının sürüşən orta qiymətləri (impuls və miqyas)
- m̂ₜ, v̂ₜbaşlanğıc sürüşməsi düzəldilmiş qiymətlər: mₜ/(1 − β₁ᵗ), vₜ/(1 − β₂ᵗ)
- β₁, β₂, ε, ηPyTorch-da susmaya görə 0,9; 0,999; 10⁻⁸; 0,001
Adam optimallaşdırıcısı. Hər parametr öz addım ölçüsünü alır: qradiyenti daim böyük olan parametr ehtiyatla, kiçik olan isə daha cəsarətlə yenilənir. SGD-nin impulslu (momentum) variantı isə v ← μ·v + g, θ ← θ − η·v düsturu ilə işləyir.
Öyrətmə dövrü
İndi əsl tapşırığa keçək. İki «aypara» (two moons) formasında nöqtələr yaradırıq: hər sinif 500 nöqtədir və onları düz xətlə ayırmaq mümkün deyil — deməli, qeyri-xətti model lazımdır. Verilənləri 800 öyrədici və 200 test nümunəsinə bölürük.
import math
import torch
from torch import nn
from torch.utils.data import TensorDataset, DataLoader, random_split
torch.manual_seed(42)
def make_moons(n, noise=0.2):
t = torch.rand(n) * math.pi
upper = torch.stack([torch.cos(t), torch.sin(t)], dim=1)
lower = torch.stack([1 - torch.cos(t), 0.5 - torch.sin(t)], dim=1)
X = torch.cat([upper, lower]) + noise * torch.randn(2 * n, 2)
y = torch.cat([torch.zeros(n), torch.ones(n)]).long()
return X, y
X, y = make_moons(500)
train_set, test_set = random_split(TensorDataset(X, y), [800, 200])
train_loader = DataLoader(train_set, batch_size=32, shuffle=True)
test_loader = DataLoader(test_set, batch_size=100)
xb, yb = next(iter(train_loader))
print(len(train_set), len(test_set), len(train_loader))
print(xb.shape, yb.shape, yb.dtype)800 200 25 torch.Size([32, 2]) torch.Size([32]) torch.int64
int64-dür, çünkü CrossEntropyLoss sinif nömrələri gözləyir. Test verilənləri qarışdırılmır.- 1Qradiyentləri sıfırla
optimizer.zero_grad()— əvvəlki addımın qradiyentlərini təmizlə. - 2İrəli keçid
logits = model(xb)— paket üçün proqnozlar. - 3İtki
loss = loss_fn(logits, yb)— bir skalyar. - 4Geri keçid
loss.backward()— bütün parametrlərin qradiyentləri. - 5Addım
optimizer.step()— parametrləri yenilə.
device = 'cuda' if torch.cuda.is_available() else 'cpu'
model = nn.Sequential(
nn.Linear(2, 16), nn.ReLU(),
nn.Linear(16, 16), nn.ReLU(),
nn.Linear(16, 2),
).to(device)
loss_fn = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.01)
for epoch in range(1, 21):
model.train()
total = 0.0
for xb, yb in train_loader:
xb, yb = xb.to(device), yb.to(device)
optimizer.zero_grad()
loss = loss_fn(model(xb), yb)
loss.backward()
optimizer.step()
total += loss.item() * len(xb)
if epoch == 1 or epoch % 5 == 0:
print(f'epoch {epoch:2d} loss {total / len(train_set):.4f}')epoch 1 loss 0.4741 epoch 5 loss 0.1200 epoch 10 loss 0.0864 epoch 15 loss 0.0715 epoch 20 loss 0.0823
İtki ilk epoxalarda sürətlə düşür, sonra 0,07–0,09 ətrafında tərəddüd edir. 20-ci epoxadakı kiçik artım normaldır: mini-paketlər təsadüfi olduğundan addımlar küylüdür. loss.item() * len(xb) hasillərini toplayıb N-ə bölməklə epoxanın dəqiq orta itkisini alırıq (son paket kiçik olsa belə).
Paket ölçüsü B də hiperparametrdir. Böyük paket qradiyenti daha dəqiq qiymətləndirir və GPU-nu daha səmərəli yükləyir, amma daha çox yaddaş tələb edir və bir epoxada daha az yeniləmə verir. Kiçik paket isə küylüdür, lakin bu küy bəzən ümumiləşdirməyə kömək edir. Praktikada 32–256 arası qiymətlərlə başlanır; paketi 2 dəfə böyüdəndə adətən öyrənmə sürətini də bir qədər artırmaq olar.
Real layihələrdə dövrə daha iki element əlavə olunur. Öyrənmə sürətinin cədvəli (scheduler) η-nı zamanla azaldır: məsələn, torch.optim.lr_scheduler.CosineAnnealingLR, hər epoxanın sonunda scheduler.step() çağırılır. Erkən dayandırma (early stopping) isə hər epoxadan sonra validasiya itkisini ölçür, ən yaxşı modeli saxlayır və itki bir neçə epoxa ərzində yaxşılaşmayanda öyrətməni dayandırır — bu, həddən artıq uyğunlaşmaya qarşı ən sadə müdafiədir.
Qiymətləndirmə: eval rejimi və dəqiqlik
Qiymətləndirmədə iki şey vacibdir. model.eval() bəzi qatların davranışını dəyişir: Dropout söndürülür, BatchNorm isə öyrətmə zamanı toplanmış statistikalardan istifadə edir. torch.no_grad() isə qraf qurmağı dayandırır. Bunlar fərqli işlərdir və adətən ikisi birlikdə lazımdır. Təsnifatda əsas metrika dəqiqlikdir:
- argmax(zᵢ)i-ci nümunə üçün ən böyük logitin sinfi — proqnoz
- [ … ]şərt doğrudursa 1, əks halda 0
def accuracy(model, loader):
model.eval()
correct = 0
with torch.no_grad():
for xb, yb in loader:
xb, yb = xb.to(device), yb.to(device)
correct += (model(xb).argmax(dim=1) == yb).sum().item()
return correct / len(loader.dataset)
print(f'train accuracy: {accuracy(model, train_loader):.3f}')
print(f'test accuracy: {accuracy(model, test_loader):.3f}')train accuracy: 0.974 test accuracy: 0.975
a) 800 nümunə və paket ölçüsü 64 olanda bir epoxada neçə addım var? 20 epoxada neçə yeniləmə olur?
b) 200 test nümunəsindən 195-i düzgün təsnif olunub. Dəqiqlik nədir?
Həllini göstərHəllini gizlət
b) 195 / 200 = 0,975, yəni 97,5% — məhz bizim modelin nəticəsi.
Modelin saxlanması və yüklənməsi
model.state_dict() — «parametrin adı → tenzor» lüğətidir. Tövsiyə olunan üsul bütün obyekti yox, məhz bu lüğəti saxlamaqdır. Yükləmək üçün eyni arxitekturalı modeli yenidən yaradır, load_state_dict çağırırsan; map_location GPU-da saxlanmış faylı CPU-da açmağa imkan verir.
torch.save(model.state_dict(), 'moons.pt')
restored = nn.Sequential(
nn.Linear(2, 16), nn.ReLU(),
nn.Linear(16, 16), nn.ReLU(),
nn.Linear(16, 2),
).to(device)
restored.load_state_dict(torch.load('moons.pt', map_location=device))
print(list(restored.state_dict().keys()))
print(f'restored test accuracy: {accuracy(restored, test_loader):.3f}')['0.weight', '0.bias', '2.weight', '2.bias', '4.weight', '4.bias'] restored test accuracy: 0.975
Uzun öyrətməni yarımçıq dayandırıb sonra davam etdirmək üçün checkpoint saxlanır: torch.save({'epoch': epoch, 'model': model.state_dict(), 'optimizer': optimizer.state_dict()}, 'ckpt.pt'). Adam-ın impuls statistikaları da optimallaşdırıcının state_dict-indədir — onsuz davam etdirmə «soyuq başlanğıc» olar.
Əsas fikirlər
Datasetnümunələri verir,DataLoaderonları paketlərə bölür və qarışdırır; bir epoxada ⌈N / B⌉ addım olur.- Reqressiya —
MSELoss, K sinifli təsnifat — logitlər vəint64nişanlarlaCrossEntropyLoss. - Dövr:
zero_grad→ irəli keçid → itki →backward→step; yeni tapşırıqda Adam (lr = 0,001) yaxşı başlanğıcdır. - Qiymətləndirmə
model.eval()vətorch.no_grad()ilə aparılır; dəqiqlik = düzgün proqnozlar / N. - Model
state_dictilə saxlanılır və eyni arxitekturayaload_state_dictilə yüklənir.
Özünü yoxla
10 sual. Hər düzgün cavab XP qazandırır.