Робочий процес 🔥PyTorch

Технології комп’ютерного зору

Ігор Мірошниченко

КНУ імені Тараса Шевченка, ФІТ

Базовий робочий процес PyTorch

Основні кроки

  1. Перетворіть ваші дані, незалежно від їхнього типу, на числа (representation).
  2. Виберіть або створіть модель, щоб якнайкраще вивчити представлення.

Лінійна регресія: дані

# Відомі параметри
weight = 0.7
bias = 0.3

# Створення даних
start = 0
end = 1
step = 0.02
X = torch.arange(start, end, step).unsqueeze(dim=1)
y = weight * X + bias

X[:10], y[:10]
(tensor([[0.0000],
         [0.0200],
         [0.0400],
         [0.0600],
         [0.0800],
         [0.1000],
         [0.1200],
         [0.1400],
         [0.1600],
         [0.1800]]),
 tensor([[0.3000],
         [0.3140],
         [0.3280],
         [0.3420],
         [0.3560],
         [0.3700],
         [0.3840],
         [0.3980],
         [0.4120],
         [0.4260]]))

Лінійна регресія: train/test

train_split = int(0.8 * len(X))
X_train, y_train = X[:train_split], y[:train_split]
X_test, y_test = X[train_split:], y[train_split:]

len(X_train), len(y_train), len(X_test), len(y_test)
(40, 40, 10, 10)

Лінійна регресія: графік

def plot_predictions(train_data=X_train, 
                     train_labels=y_train, 
                     test_data=X_test, 
                     test_labels=y_test, 
                     predictions=None):
  """
  Plots training data, test data and compares predictions.
  """
  plt.scatter(train_data, train_labels, c=blue, s=4, label="Training data")
  plt.scatter(test_data, test_labels, c=turquoise, s=4, label="Testing data")

  if predictions is not None:
    plt.scatter(test_data, predictions, c=red_pink, s=4, label="Predictions")
    
  plt.legend(prop={"size": 14});

plot_predictions();

Лінійна регресія: графік

Лінійна регресія: ініціалізація моделі

1class LinearRegressionModel(nn.Module):
    def __init__(self):
        super().__init__()
2        self.weights = nn.Parameter(torch.randn(1,
3                                                dtype=torch.float),
4                                   requires_grad=True)

        self.bias = nn.Parameter(torch.randn(1,
                                             dtype=torch.float),
                                requires_grad=True)

5    def forward(self, x: torch.Tensor) -> torch.Tensor:
6        return self.weights * x + self.bias
1
Наслідуємо від nn.Module
2
Ініціалізуємо ваги та зсув (bias)
3
Вказуємо тип даних
4
Вказуємо, що параметри потрібно оптимізувати
5
Визначаємо метод прямого проходу (forward pass)
6
Визначаємо лінійну модель

Лінійна регресія: базова модель

torch.manual_seed(73)
model_0 = LinearRegressionModel()
list(model_0.parameters()), model_0.state_dict()
([Parameter containing:
  tensor([0.0591], requires_grad=True),
  Parameter containing:
  tensor([0.4445], requires_grad=True)],
 OrderedDict([('weights', tensor([0.0591])), ('bias', tensor([0.4445]))]))

Лінійна регресія: прогноз

  • torch.inference_mode() - контекстний менеджер, який вимикає обчислення градієнтів (еквівалент torch.no_grad():)
with torch.inference_mode(): 
    y_preds = model_0(X_test)
y_preds[:10]
tensor([[0.4918],
        [0.4930],
        [0.4942],
        [0.4954],
        [0.4966],
        [0.4978],
        [0.4989],
        [0.5001],
        [0.5013],
        [0.5025]])

Лінійна регресія: графік прогнозу

plot_predictions(predictions=y_preds);

Лінійна регресія: функція втрат

1loss_fn = nn.L1Loss()

2optimizer = torch.optim.SGD(params=model_0.parameters(),
3                        lr=0.01)
1
Використовуємо середню абсолютну помилку (MAE)
2
Використовуємо стохастичний градієнтний спуск (SGD)
3
Встановлюємо швидкість навчання (learning rate)

Лінійна регресія: цикл навчання

torch.manual_seed(42)

epochs = 100

train_loss_values = []
test_loss_values = []
epoch_count = []

for epoch in range(epochs):
    
1    model_0.train()
    
2    y_pred = model_0(X_train)
    
3    loss = loss_fn(y_pred, y_train)
    
4    optimizer.zero_grad()
    
5    loss.backward()
    
6    optimizer.step()
    
    if epoch % 10 == 0:
7        model_0.eval()
        with torch.inference_mode():
            test_pred = model_0(X_test)
            test_loss = loss_fn(test_pred, y_test.type(torch.float))
            train_loss_values.append(loss.detach().numpy())
            test_loss_values.append(test_loss.detach().numpy())
            epoch_count.append(epoch)
        print(f"Epoch: {epoch} | MAE train: {loss:.4f} | MAE test: {test_loss:.4f}")
1
Переключаємо модель у режим навчання
2
Робимо прогноз на навчальних даних
3
Обчислюємо втрати на навчальних даних
4
Обнуляємо градієнти
5
Обчислюємо градієнти
6
Оновлюємо параметри моделі
7
Переключаємо модель у режим оцінки
Epoch: 0 | MAE train: 0.1498 | MAE test: 0.4190
Epoch: 10 | MAE train: 0.1295 | MAE test: 0.3605
Epoch: 20 | MAE train: 0.1187 | MAE test: 0.3191
Epoch: 30 | MAE train: 0.1123 | MAE test: 0.2900
Epoch: 40 | MAE train: 0.1078 | MAE test: 0.2688
Epoch: 50 | MAE train: 0.1039 | MAE test: 0.2524
Epoch: 60 | MAE train: 0.1004 | MAE test: 0.2413
Epoch: 70 | MAE train: 0.0969 | MAE test: 0.2303
Epoch: 80 | MAE train: 0.0934 | MAE test: 0.2193
Epoch: 90 | MAE train: 0.0899 | MAE test: 0.2097

Лінійна регресія: графік втрат

# Plot the loss curves
plt.plot(epoch_count, train_loss_values, label="Train loss")
plt.plot(epoch_count, test_loss_values, label="Test loss")
plt.title("Training and test loss curves")
plt.ylabel("Loss")
plt.xlabel("Epochs")
plt.legend();

Лінійна регресія: параметри моделі

print("Модель навчилася наступним значенням для weights і bias:")
print(model_0.state_dict())
print("\nА оригінальні значення для weights і bias такі:")
print(f"weights: {weight}, bias: {bias}")
Модель навчилася наступним значенням для weights і bias:
OrderedDict([('weights', tensor([0.2696])), ('bias', tensor([0.4805]))])

А оригінальні значення для weights і bias такі:
weights: 0.7, bias: 0.3

Лінійна регресія: прогноз

  1. Встановіть модель у режим оцінювання: model.eval().
  2. Зробіть прогнози, використовуючи контекстний менеджер режиму виведення: with torch.inference_mode():.
  3. Усі прогнози слід робити з об’єктами на одному пристрої (наприклад, дані та модель тільки на GPU або дані та модель тільки на CPU).
model_0.eval()
with torch.inference_mode():
  y_preds = model_0(X_test)
y_preds, plot_predictions(predictions=y_preds);

Збереження моделей

  1. Створимо каталог для збереження моделей під назвою models за допомогою модуля pathlib.
  2. Створимо шлях до файлу, в якому буде збережено модель.
  3. Викличемо torch.save(obj, f), де obj — це state_dict() цільової моделі, а f — ім’я файлу, в якому буде збережено модель.
from pathlib import Path

HOME = Path.cwd()
MODEL_PATH = HOME / "models"
MODEL_PATH.mkdir(parents=True, exist_ok=True)

MODEL_NAME = "01_pytorch_workflow_model_0.pth"
MODEL_SAVE_PATH = MODEL_PATH / MODEL_NAME

torch.save(obj=model_0.state_dict(),
           f=MODEL_SAVE_PATH)

Завантаження моделей

loaded_model_0 = LinearRegressionModel()

loaded_model_0.load_state_dict(torch.load(f=MODEL_SAVE_PATH))
<All keys matched successfully>

Задача класифікації

Архітектура

Гіперпараметр Бінарна класифікація Багатокласова класифікація
Форма вхідного шару (in_features) Така сама, як кількість ознак (наприклад, 5 для віку, статі, зросту, ваги, статусу куріння при прогнозуванні серцевих захворювань) Така сама, як для бінарної класифікації
Прихований шар (шари) Залежить від конкретної задачі, мінімум = 1, максимум = необмежений Така сама, як для бінарної класифікації
Нейрони на прихований шар Залежно від проблеми, зазвичай від 10 до 512 Так само, як у бінарній класифікації
Форма вихідного шару (out_features) 1 (один клас або інший) 1 на клас (наприклад, 3 для фотографії їжі, людини або собаки)
Активація прихованого шару Зазвичай ReLU (випрямлена лінійна одиниця), але може бути багато інших Так само, як і в бінарній класифікації
Активація виходу Sigmoid (torch.sigmoid в PyTorch) Softmax (torch.softmax в PyTorch)
Функція втрат Бінарна крос-ентропія (torch.nn.BCELoss в PyTorch) Крос-ентропія (torch.nn.CrossEntropyLoss в PyTorch)
Оптимізатор SGD (стохастичний градієнтний спуск), Adam (дивіться torch.optim для додаткових опцій) Те саме, що й бінарна класифікація

Приклад: make_circles()

from sklearn.datasets import make_circles

n_samples = 1000

X, y = make_circles(n_samples,
                    noise=0.03,
                    random_state=73)

print(f"Перші 5 X даних:\n{X[:5]}")
print(f"\nПерші 5 y міток:\n{y[:5]}")
Перші 5 X даних:
[[-0.89208549 -0.47345393]
 [ 0.2247362   0.98047815]
 [-0.69231507 -0.34493576]
 [ 0.56443992 -0.81409248]
 [-0.80573886 -0.60074391]]

Перші 5 y міток:
[0 0 1 0 0]

Графік даних

import pandas as pd
circles = pd.DataFrame({"X1": X[:, 0],
    "X2": X[:, 1],
    "label": y
})
circles.head(10), circles.label.value_counts()
(         X1        X2  label
 0 -0.892085 -0.473454      0
 1  0.224736  0.980478      0
 2 -0.692315 -0.344936      1
 3  0.564440 -0.814092      0
 4 -0.805739 -0.600744      0
 5  0.458907  0.585375      1
 6  0.614026 -0.428267      1
 7 -0.541688  0.539944      1
 8 -0.955997  0.138724      0
 9 -0.835431 -0.129842      1,
 0    500
 1    500
 Name: label, dtype: int64)


import matplotlib.pyplot as plt
plt.scatter(x=X[:, 0], 
            y=X[:, 1], 
            c=y, 
            cmap=plt.cm.RdYlBu);

Розмірність даних

X_sample = X[0]
y_sample = y[0]
print(f"Значення X: {X_sample} і те саме для y: {y_sample}")
print(f"Форми для одного зразка X: {X_sample.shape} і те саме для y: {y_sample.shape}")
Значення X: [-0.89208549 -0.47345393] і те саме для y: 0
Форми для одного зразка X: (2,) і те саме для y: ()

Тензор та train/test split

import torch
X = torch.from_numpy(X).type(torch.float)
y = torch.from_numpy(y).type(torch.float)

X[:5], y[:5]
(tensor([[-0.8921, -0.4735],
         [ 0.2247,  0.9805],
         [-0.6923, -0.3449],
         [ 0.5644, -0.8141],
         [-0.8057, -0.6007]]),
 tensor([0., 0., 1., 0., 0.]))
from sklearn.model_selection import train_test_split

X_train, X_test, y_train, y_test = train_test_split(X, 
                                                    y, 
                                                    test_size=0.2,
                                                    random_state=73)

len(X_train), len(X_test), len(y_train), len(y_test)
(800, 200, 800, 200)

Побудова моделі

import torch
from torch import nn

device = "cuda" if torch.cuda.is_available() else "cpu"
print(f"Using device: {device}")

class CircleModelV0(nn.Module):
    def __init__(self):
        super().__init__()
        self.layer_1 = nn.Linear(in_features=2, out_features=5)
        self.layer_2 = nn.Linear(in_features=5, out_features=1)
    
    def forward(self, x):
        return self.layer_2(self.layer_1(x))

model_0 = CircleModelV0().to(device)
model_0
Using device: cuda
CircleModelV0(
  (layer_1): Linear(in_features=2, out_features=5, bias=True)
  (layer_2): Linear(in_features=5, out_features=1, bias=True)
)

Альтернатива: nn.Sequential

model_0 = nn.Sequential(
    nn.Linear(in_features=2, out_features=5),
    nn.Linear(in_features=5, out_features=1)
).to(device)

print(model_0)

untrained_preds = model_0(X_test.to(device))
print(f"Довжина предикторів: {len(untrained_preds)}, Форма: {untrained_preds.shape}")
print(f"Довжина тестових зразків: {len(y_test)}, Форма: {y_test.shape}")
print(f"\nПерші 10 прогнозів:\n{untrained_preds[:10]}")
print(f"\nПерші 10 тестових міток:\n{y_test[:10]}")
Sequential(
  (0): Linear(in_features=2, out_features=5, bias=True)
  (1): Linear(in_features=5, out_features=1, bias=True)
)
Довжина предикторів: 200, Форма: torch.Size([200, 1])
Довжина тестових зразків: 200, Форма: torch.Size([200])

Перші 10 прогнозів:
tensor([[ 0.2127],
        [ 0.1659],
        [-0.0255],
        [-0.0014],
        [-0.4615],
        [-0.0664],
        [-0.1857],
        [ 0.1789],
        [-0.4135],
        [-0.1713]], device='cuda:0', grad_fn=<SliceBackward0>)

Перші 10 тестових міток:
tensor([1., 0., 0., 1., 0., 1., 0., 1., 1., 1.])

Функція втрат та оптимізатор 1/3

Функція втрат/оптимізатор Тип задачі Код PyTorch
Оптимізатор стохастичного градієнтного спуску (SGD) Класифікація, регресія та багато інших. torch.optim.SGD()
Оптимізатор Adam Класифікація, регресія та багато інших. torch.optim.Adam()
Двійкова крос-ентропійна втрата Двійкова класифікація torch.nn.BCELossWithLogits або torch.nn.BCELoss
Втрата перехресної ентропії Багатокласова класифікація torch.nn.CrossEntropyLoss
Середня абсолютна похибка (MAE) або втрата L1 Регресія torch.nn.L1Loss
Середньоквадратична похибка (MSE) або втрата L2 Регресія torch.nn.MSELoss

Функція втрат та оптимізатор 2/3

  • torch.nn.BCELoss(): створює функцію втрат, яка вимірює бінарну крос ентропію між ціллю (міткою) та вхідними даними (ознаками).
  • torch.nn.BCEWithLogitsLoss(): це те саме, що й вище, за винятком того, що він має вбудований сигмоїдний шар (nn.Sigmoid).

Порада

У документації до функції torch.nn.BCEWithLogitsLoss() зазначено, що вона є більш чисельно стабільною, ніж використання функції torch.nn.BCELoss() після шару nn.Sigmoid.

Функція втрат та оптимізатор 3/3

loss_fn = nn.BCEWithLogitsLoss()

optimizer = torch.optim.SGD(params=model_0.parameters(), 
                            lr=0.1)

Додамо метрику точності (accuracy) для оцінки моделі.

def accuracy_fn(y_true, y_pred):
    correct = torch.eq(y_true, y_pred).sum().item()
    acc = (correct / len(y_pred)) * 100 
    return acc

Цикл навчання

Етапи циклу навчання PyTorch

  1. Forward pass — модель проходить всі навчальні дані один раз, виконуючи обчислення функції forward() (model(x_train)).
  2. Calculate the loss — вихідні дані моделі (прогнози) порівнюються з фактичними даними і оцінюються, щоб визначити, наскільки вони помилкові (loss = loss_fn(y_pred, y_train)).
  3. Zero gradients — градієнти оптимізаторів встановлюються на нуль, щоб їх можна було перерахувати для конкретного етапу навчання (optimizer.zero_grad()).
  4. Backward pass — обчислює градієнт втрати щодо кожного параметра моделі, який потрібно оновити (кожен параметр з requires_grad=True). Це відоме як зворотне поширення, тому і називається “зворотним” (loss.backward()).
  5. Optimizer step — оновлення параметрів з requires_grad=True щодо градієнтів втрати, щоб поліпшити їх (optimizer.step()).

Передамо модель, дані та мітки на потрібний пристрій (CPU або GPU).

y_logits = model_0(X_test.to(device))[:5]
y_logits
tensor([[ 0.2127],
        [ 0.1659],
        [-0.0255],
        [-0.0014],
        [-0.4615]], device='cuda:0', grad_fn=<SliceBackward0>)

Отримаємо ймовірності за допомогою сигмоїдної функції активації.

y_pred_probs = torch.sigmoid(y_logits)
y_pred_probs
tensor([[0.5530],
        [0.5414],
        [0.4936],
        [0.4996],
        [0.3866]], device='cuda:0', grad_fn=<SigmoidBackward0>)

Отримаємо класові мітки (0 або 1) на основі ймовірностей.

  • якщо ймовірність > 0.5, то клас = 1
  • якщо ймовірність <= 0.5, то клас = 0
y_preds = torch.round(y_pred_probs)

y_pred_labels = torch.round(torch.sigmoid(model_0(X_test.to(device))[:5]))

print(torch.eq(y_preds.squeeze(), y_pred_labels.squeeze()))

y_preds.squeeze()
tensor([True, True, True, True, True], device='cuda:0')
tensor([1., 1., 0., 0., 0.], device='cuda:0', grad_fn=<SqueezeBackward0>)

Повний цикл навчання

torch.manual_seed(73)

epochs = 100

X_train, y_train = X_train.to(device), y_train.to(device)
X_test, y_test = X_test.to(device), y_test.to(device)

for epoch in range(epochs):
    model_0.train()

    # 1. Forward pass
    y_logits = model_0(X_train).squeeze()
    y_pred = torch.round(torch.sigmoid(y_logits))
  
    # 2. Calculate loss/accuracy
    loss = loss_fn(y_logits,
                   y_train) 
    acc = accuracy_fn(y_true=y_train, 
                      y_pred=y_pred) 

    # 3. Optimizer zero grad
    optimizer.zero_grad()

    # 4. Loss backwards
    loss.backward()

    # 5. Optimizer step
    optimizer.step()

    ### Testing
    model_0.eval()
    with torch.inference_mode():
        # 1. Forward pass
        test_logits = model_0(X_test).squeeze() 
        test_pred = torch.round(torch.sigmoid(test_logits))
        # 2. Caculate loss/accuracy
        test_loss = loss_fn(test_logits,
                            y_test)
        test_acc = accuracy_fn(y_true=y_test,
                               y_pred=test_pred)

    # Print out what's happening every 10 epochs
    if epoch % 10 == 0:
        print(f"Epoch: {epoch} | Loss: {loss:.5f}, Accuracy: {acc:.2f}% | Test loss: {test_loss:.5f}, Test acc: {test_acc:.2f}%")
Epoch: 0 | Loss: 0.70524, Accuracy: 48.12% | Test loss: 0.69210, Test acc: 54.50%
Epoch: 10 | Loss: 0.70192, Accuracy: 48.25% | Test loss: 0.69094, Test acc: 54.00%
Epoch: 20 | Loss: 0.69958, Accuracy: 47.75% | Test loss: 0.69039, Test acc: 54.00%
Epoch: 30 | Loss: 0.69790, Accuracy: 47.38% | Test loss: 0.69021, Test acc: 54.00%
Epoch: 40 | Loss: 0.69667, Accuracy: 47.62% | Test loss: 0.69027, Test acc: 53.50%
Epoch: 50 | Loss: 0.69575, Accuracy: 47.38% | Test loss: 0.69047, Test acc: 53.50%
Epoch: 60 | Loss: 0.69505, Accuracy: 47.38% | Test loss: 0.69077, Test acc: 53.50%
Epoch: 70 | Loss: 0.69453, Accuracy: 47.62% | Test loss: 0.69112, Test acc: 53.50%
Epoch: 80 | Loss: 0.69413, Accuracy: 47.62% | Test loss: 0.69149, Test acc: 54.00%
Epoch: 90 | Loss: 0.69382, Accuracy: 47.50% | Test loss: 0.69188, Test acc: 53.50%

Прогнози та оцінювання

Код
import requests
from pathlib import Path 

# Download helper functions from Learn PyTorch repo (if not already downloaded)
if Path("helper_functions.py").is_file():
  print("helper_functions.py already exists, skipping download")
else:
  print("Downloading helper_functions.py")
  request = requests.get("https://raw.githubusercontent.com/Aranaur/aranaur.rbind.io/refs/heads/main/lectures/cv/slides/2025/helper_functions.py")
  with open("helper_functions.py", "wb") as f:
    f.write(request.content)

from helper_functions import plot_predictions, plot_decision_boundary
helper_functions.py already exists, skipping download


plt.figure(figsize=(12, 6))
plt.subplot(1, 2, 1)
plt.title("Train")
plot_decision_boundary(model_0, X_train, y_train)
plt.subplot(1, 2, 2)
plt.title("Test")
plot_decision_boundary(model_0, X_test, y_test)

Покращення моделі 1/3

Техніка вдосконалення моделі Що вона робить?
Додавання додаткових шарів Кожен шар потенційно збільшує можливості навчання моделі, оскільки кожен шар здатний вивчати певні нові закономірності в даних. Більша кількість шарів часто називається поглибленням нейронної мережі.
Додавання більше прихованих одиниць Подібно до вищезазначеного, більше прихованих одиниць на шар означає потенційне збільшення навчальних можливостей моделі. Більше прихованих одиниць часто називають розширенням нейронної мережі.
Довше навчання (більше епох) Ваша модель може навчитися більше, якщо матиме більше можливостей для аналізу даних.
Зміна функцій активації Деякі дані просто не можна представити лише прямими лініями (як ми бачили), і в цьому може допомогти використання нелінійних функцій активації.
Зміна швидкості навчання Швидкість навчання оптимізатора визначає, наскільки модель повинна змінювати свої параметри на кожному кроці: занадто багато — і модель перескочить, занадто мало — і вона не навчиться достатньо.
Зміна функції втрат Різні проблеми вимагають різних функцій втрат. Наприклад, бінарна функція втрат перехресної ентропії не працюватиме з проблемою багатокласової класифікації.
Використовуйте перенесення навчання Візьміть попередньо навчену модель з області проблем, схожої на вашу, і налаштуйте її під свою проблему.

Покращення моделі 2/3

  • Додамо ще один прихований шар і збільшимо кількість нейронів у прихованих шарах.
class CircleModelV1(nn.Module):
    def __init__(self):
        super().__init__()
        self.layer_1 = nn.Linear(in_features=2, out_features=10)
        self.layer_2 = nn.Linear(in_features=10, out_features=10)
        self.layer_3 = nn.Linear(in_features=10, out_features=1)
        
    def forward(self, x):
        return self.layer_3(self.layer_2(self.layer_1(x)))

model_1 = CircleModelV1().to(device)
loss_fn = nn.BCEWithLogitsLoss()
optimizer = torch.optim.SGD(model_1.parameters(), lr=0.1)

model_1
CircleModelV1(
  (layer_1): Linear(in_features=2, out_features=10, bias=True)
  (layer_2): Linear(in_features=10, out_features=10, bias=True)
  (layer_3): Linear(in_features=10, out_features=1, bias=True)
)

Покращення моделі 3/3

torch.manual_seed(73)

epochs = 1000

X_train, y_train = X_train.to(device), y_train.to(device)
X_test, y_test = X_test.to(device), y_test.to(device)

for epoch in range(epochs):
    y_logits = model_1(X_train).squeeze()
    y_pred = torch.round(torch.sigmoid(y_logits))

    loss = loss_fn(y_logits, y_train)
    acc = accuracy_fn(y_true=y_train, 
                      y_pred=y_pred)

    optimizer.zero_grad()

    loss.backward()

    optimizer.step()

    model_1.eval()
    with torch.inference_mode():
        test_logits = model_1(X_test).squeeze() 
        test_pred = torch.round(torch.sigmoid(test_logits))
        test_loss = loss_fn(test_logits,
                            y_test)
        test_acc = accuracy_fn(y_true=y_test,
                               y_pred=test_pred)

    if epoch % 100 == 0:
        print(f"Epoch: {epoch} | Loss: {loss:.5f}, Accuracy: {acc:.2f}% | Test loss: {test_loss:.5f}, Test acc: {test_acc:.2f}%")
Epoch: 0 | Loss: 0.70776, Accuracy: 50.38% | Test loss: 0.71253, Test acc: 48.50%
Epoch: 100 | Loss: 0.69272, Accuracy: 51.88% | Test loss: 0.69693, Test acc: 46.50%
Epoch: 200 | Loss: 0.69256, Accuracy: 51.50% | Test loss: 0.69787, Test acc: 47.50%
Epoch: 300 | Loss: 0.69254, Accuracy: 51.38% | Test loss: 0.69836, Test acc: 48.00%
Epoch: 400 | Loss: 0.69254, Accuracy: 51.12% | Test loss: 0.69856, Test acc: 48.50%
Epoch: 500 | Loss: 0.69254, Accuracy: 51.00% | Test loss: 0.69864, Test acc: 48.00%
Epoch: 600 | Loss: 0.69254, Accuracy: 51.00% | Test loss: 0.69867, Test acc: 48.00%
Epoch: 700 | Loss: 0.69254, Accuracy: 51.00% | Test loss: 0.69868, Test acc: 48.00%
Epoch: 800 | Loss: 0.69254, Accuracy: 51.00% | Test loss: 0.69868, Test acc: 48.00%
Epoch: 900 | Loss: 0.69254, Accuracy: 51.00% | Test loss: 0.69869, Test acc: 48.00%

Прогнози та оцінювання (знову)

plt.figure(figsize=(12, 6))
plt.subplot(1, 2, 1)
plt.title("Train")
plot_decision_boundary(model_1, X_train, y_train)
plt.subplot(1, 2, 2)
plt.title("Test")
plot_decision_boundary(model_1, X_test, y_test)

Врахуємо нелінійність

  • Додамо нелінійність за допомогою функції активації ReLU.
from torch import nn
class CircleModelV2(nn.Module):
    def __init__(self):
        super().__init__()
        self.layer_1 = nn.Linear(in_features=2, out_features=10)
        self.layer_2 = nn.Linear(in_features=10, out_features=10)
        self.layer_3 = nn.Linear(in_features=10, out_features=1)
        self.relu = nn.ReLU()

    def forward(self, x):
       return self.layer_3(self.relu(self.layer_2(self.relu(self.layer_1(x)))))

model_3 = CircleModelV2().to(device)
loss_fn = nn.BCEWithLogitsLoss()
optimizer = torch.optim.SGD(model_3.parameters(), lr=0.1)

print(model_3)
CircleModelV2(
  (layer_1): Linear(in_features=2, out_features=10, bias=True)
  (layer_2): Linear(in_features=10, out_features=10, bias=True)
  (layer_3): Linear(in_features=10, out_features=1, bias=True)
  (relu): ReLU()
)

Навчання моделі з ReLU

torch.manual_seed(73)
epochs = 1000

X_train, y_train = X_train.to(device), y_train.to(device)
X_test, y_test = X_test.to(device), y_test.to(device)

for epoch in range(epochs):
    y_logits = model_3(X_train).squeeze()
    y_pred = torch.round(torch.sigmoid(y_logits))
    
    loss = loss_fn(y_logits, y_train)
    acc = accuracy_fn(y_true=y_train, 
                      y_pred=y_pred)
    
    optimizer.zero_grad()

    loss.backward()

    optimizer.step()

    model_3.eval()
    with torch.inference_mode():
      test_logits = model_3(X_test).squeeze()
      test_pred = torch.round(torch.sigmoid(test_logits))
      test_loss = loss_fn(test_logits, y_test)
      test_acc = accuracy_fn(y_true=y_test,
                             y_pred=test_pred)

    if epoch % 100 == 0:
        print(f"Epoch: {epoch} | Loss: {loss:.5f}, Accuracy: {acc:.2f}% | Test Loss: {test_loss:.5f}, Test Accuracy: {test_acc:.2f}%")
Epoch: 0 | Loss: 0.70067, Accuracy: 50.38% | Test Loss: 0.70503, Test Accuracy: 48.50%
Epoch: 100 | Loss: 0.68834, Accuracy: 56.62% | Test Loss: 0.68914, Test Accuracy: 53.00%
Epoch: 200 | Loss: 0.68451, Accuracy: 67.00% | Test Loss: 0.68626, Test Accuracy: 64.00%
Epoch: 300 | Loss: 0.67978, Accuracy: 71.50% | Test Loss: 0.68355, Test Accuracy: 65.00%
Epoch: 400 | Loss: 0.67283, Accuracy: 74.62% | Test Loss: 0.67970, Test Accuracy: 67.50%
Epoch: 500 | Loss: 0.66227, Accuracy: 73.62% | Test Loss: 0.67325, Test Accuracy: 67.50%
Epoch: 600 | Loss: 0.64496, Accuracy: 75.88% | Test Loss: 0.66066, Test Accuracy: 68.50%
Epoch: 700 | Loss: 0.61477, Accuracy: 82.88% | Test Loss: 0.63437, Test Accuracy: 74.50%
Epoch: 800 | Loss: 0.55790, Accuracy: 95.12% | Test Loss: 0.57768, Test Accuracy: 90.50%
Epoch: 900 | Loss: 0.46092, Accuracy: 99.00% | Test Loss: 0.48011, Test Accuracy: 97.50%

Візуалізація

model_3.eval()
with torch.inference_mode():
    y_preds = torch.round(torch.sigmoid(model_3(X_test))).squeeze()
y_preds[:10], y[:10]
(tensor([1., 0., 0., 1., 0., 1., 0., 1., 1., 1.], device='cuda:0'),
 tensor([0., 0., 1., 0., 0., 1., 1., 1., 0., 1.]))


# Plot decision boundaries for training and test sets
plt.figure(figsize=(12, 6))
plt.subplot(1, 2, 1)
plt.title("Train")
plot_decision_boundary(model_1, X_train, y_train) # model_1 = no non-linearity
plt.subplot(1, 2, 2)
plt.title("Test")
plot_decision_boundary(model_3, X_test, y_test) # model_3 = has non-linearity

Багатокласова класифікація

Генерація даних

  1. Створіть дані з декількома класами за допомогою make_blobs().
  2. Перетворіть дані в тензори (за замовчуванням make_blobs() використовує масиви NumPy).
  3. Розділіть дані на навчальні та тестові набори за допомогою train_test_split().
  4. Візуалізуйте дані.
import torch
import matplotlib.pyplot as plt
from sklearn.datasets import make_blobs
from sklearn.model_selection import train_test_split

NUM_CLASSES = 4
NUM_FEATURES = 2
RANDOM_SEED = 73

X_blob, y_blob = make_blobs(n_samples=1000,
    n_features=NUM_FEATURES,
    centers=NUM_CLASSES,
    cluster_std=1.5,
    random_state=RANDOM_SEED
)

X_blob = torch.from_numpy(X_blob).type(torch.float)
y_blob = torch.from_numpy(y_blob).type(torch.LongTensor)
print(X_blob[:5], y_blob[:5])

X_blob_train, X_blob_test, y_blob_train, y_blob_test = train_test_split(X_blob,
    y_blob,
    test_size=0.2,
    random_state=RANDOM_SEED
)

plt.figure(figsize=(10, 7))
plt.scatter(X_blob[:, 0], X_blob[:, 1], c=y_blob, cmap=plt.cm.RdYlBu);
tensor([[ 0.5750,  0.3512],
        [ 1.9594, -1.5031],
        [ 1.1964, -5.5619],
        [-1.3410,  0.9536],
        [ 2.6711,  2.3755]]) tensor([0, 0, 2, 1, 1])

Побудова моделі

device = "cuda" if torch.cuda.is_available() else "cpu"

from torch import nn

# Build model
class BlobModel(nn.Module):
    def __init__(self, input_features, output_features, hidden_units=8):
        super().__init__()
        self.linear_layer_stack = nn.Sequential(
            nn.Linear(in_features=input_features, out_features=hidden_units),
1            # nn.ReLU(),
            nn.Linear(in_features=hidden_units, out_features=hidden_units),
            # nn.ReLU(),
            nn.Linear(in_features=hidden_units, out_features=output_features),
        )
    
    def forward(self, x):
        return self.linear_layer_stack(x)

model_4 = BlobModel(input_features=NUM_FEATURES, 
                    output_features=NUM_CLASSES, 
                    hidden_units=8).to(device)
model_4
1
Чи потребує наш набір даних нелінійних шарів? (спробуйте видалити коментар і подивіться, чи зміняться результати)
BlobModel(
  (linear_layer_stack): Sequential(
    (0): Linear(in_features=2, out_features=8, bias=True)
    (1): Linear(in_features=8, out_features=8, bias=True)
    (2): Linear(in_features=8, out_features=4, bias=True)
  )
)

Функція втрат та оптимізатор

loss_fn = nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(model_4.parameters(), 
1                            lr=0.1)
1
Спробуйте змінити швидкість навчання тут і подивіться, що станеться з ефективністю моделі

Цикл навчання

torch.manual_seed(73)

epochs = 100

X_blob_train, y_blob_train = X_blob_train.to(device), y_blob_train.to(device)
X_blob_test, y_blob_test = X_blob_test.to(device), y_blob_test.to(device)

for epoch in range(epochs):
    model_4.train()

    y_logits = model_4(X_blob_train)
    y_pred = torch.softmax(y_logits, dim=1).argmax(dim=1)

    loss = loss_fn(y_logits, y_blob_train) 
    acc = accuracy_fn(y_true=y_blob_train,
                      y_pred=y_pred)

    optimizer.zero_grad()

    loss.backward()

    optimizer.step()

    model_4.eval()
    with torch.inference_mode():
      test_logits = model_4(X_blob_test)
      test_pred = torch.softmax(test_logits, dim=1).argmax(dim=1)
      test_loss = loss_fn(test_logits, y_blob_test)
      test_acc = accuracy_fn(y_true=y_blob_test,
                             y_pred=test_pred)

    if epoch % 10 == 0:
        print(f"Epoch: {epoch} | Loss: {loss:.5f}, Acc: {acc:.2f}% | Test Loss: {test_loss:.5f}, Test Acc: {test_acc:.2f}%") 
Epoch: 0 | Loss: 1.65909, Acc: 21.62% | Test Loss: 1.13461, Test Acc: 53.50%
Epoch: 10 | Loss: 0.52239, Acc: 81.00% | Test Loss: 0.49932, Test Acc: 85.50%
Epoch: 20 | Loss: 0.37974, Acc: 87.38% | Test Loss: 0.34724, Test Acc: 89.50%
Epoch: 30 | Loss: 0.32981, Acc: 88.00% | Test Loss: 0.29285, Test Acc: 90.00%
Epoch: 40 | Loss: 0.30699, Acc: 88.00% | Test Loss: 0.26929, Test Acc: 90.00%
Epoch: 50 | Loss: 0.29297, Acc: 88.25% | Test Loss: 0.25650, Test Acc: 90.00%
Epoch: 60 | Loss: 0.28279, Acc: 88.50% | Test Loss: 0.24836, Test Acc: 90.50%
Epoch: 70 | Loss: 0.27476, Acc: 88.62% | Test Loss: 0.24261, Test Acc: 91.50%
Epoch: 80 | Loss: 0.26820, Acc: 89.12% | Test Loss: 0.23830, Test Acc: 91.50%
Epoch: 90 | Loss: 0.26274, Acc: 89.12% | Test Loss: 0.23492, Test Acc: 91.50%

Прогнози

model_4.eval()
with torch.inference_mode():
    y_logits = model_4(X_blob_test)

y_logits[:10]
tensor([[ 4.9100,  2.2299, -6.8015, -8.9545],
        [ 1.8674, -2.5287,  9.2779,  0.0885],
        [ 3.8235,  1.7210, -4.7883, -6.9543],
        [ 1.9996,  1.8485, -4.4844, -4.6104],
        [ 0.0433,  1.3184, -2.0692, -1.4076],
        [-6.4226,  2.4281, -3.0514,  6.2232],
        [-4.2468,  1.0468,  0.4361,  4.6959],
        [-7.0228,  1.1969,  1.0367,  8.3090],
        [-0.9506,  2.6911, -5.9859, -1.4759],
        [-2.3060,  2.5857, -5.1328,  0.4727]], device='cuda:0')


y_pred_probs = torch.softmax(y_logits, dim=1)

y_preds = y_pred_probs.argmax(dim=1)

print(f"Predictions: {y_preds[:10]}\nLabels: {y_blob_test[:10]}")
print(f"Test accuracy: {accuracy_fn(y_true=y_blob_test, y_pred=y_preds)}%")
Predictions: tensor([0, 2, 0, 0, 1, 3, 3, 3, 1, 1], device='cuda:0')
Labels: tensor([0, 2, 0, 1, 1, 3, 3, 3, 1, 1], device='cuda:0')
Test accuracy: 91.0%

Візуалізація

plt.figure(figsize=(12, 6))
plt.subplot(1, 2, 1)
plt.title("Train")
plot_decision_boundary(model_4, X_blob_train, y_blob_train)
plt.subplot(1, 2, 2)
plt.title("Test")
plot_decision_boundary(model_4, X_blob_test, y_blob_test)

Метрики класифікації

Назва метрики/Метод оцінки Визначення Код
Accuracy Скільки з 100 прогнозів ваша модель вгадала правильно? Наприклад, точність 95% означає, що вона вгадала 95/100 прогнозів. torchmetrics.Accuracy() або sklearn.metrics.accuracy_score()
Precision Частка справжніх позитивних результатів у загальній кількості зразків. Вища точність призводить до меншої кількості помилкових позитивних результатів (модель прогнозує 1, коли мала б бути 0). torchmetrics.Precision() або sklearn.metrics.precision_score()
Recall Частка справжніх позитивних результатів від загальної кількості справжніх позитивних і помилкових негативних результатів (модель прогнозує 0, коли мала б бути 1). Вищий показник recall призводить до меншої кількості помилкових негативних результатів. torchmetrics.Recall() або sklearn.metrics.recall_score()
F1-score Поєднує точність і відкликання в один показник. 1 — найкращий результат, 0 — найгірший. torchmetrics. F1Score() або sklearn.metrics.f1_score()
Confusion matrix Порівнює прогнозовані значення з істинними значеннями у вигляді таблиці. Якщо правильність становить 100%, усі значення в матриці будуть розташовані від верхнього лівого кута до нижнього правого кута (діагональна лінія). torchmetrics.ConfusionMatrix або sklearn.metrics.plot_confusion_matrix()
Classification report Збірка деяких основних метрик класифікації, таких як точність, відтворюваність та f1-score. sklearn.metrics.classification_report()

Дякую за увагу!



Матеріали курсу

ihor.miroshnychenko@knu.ua

Data Mirosh

@ihormiroshnychenko

@aranaur

aranaur.rbind.io