Фаза 03 · урок 11

Введение в PyTorch

Цель урока: У вас есть работающий мини-фреймворк. Линейные слои, ReLU, dropout, batch norm, Adam, DataLoader , цикл обучения. Он обучает 4-слойную сеть на задаче классификации кругов на чистом Python.

Текущий релиз AlexBred.com: первые 100 уроков русскоязычной программы.

Курс
AI Engineering from Scratch
Фаза
Основы глубокого обучения
Чтение
20 мин.
Проверено
Содержание урока
  1. Цели обучения
  2. Проблема
  3. Концепция
  4. Почему победил PyTorch
  5. Тензоры
  6. Autograd
  7. nn.Module
  8. Функции потерь и оптимизаторы
  9. Цикл обучения
  10. Dataset и DataLoader
  11. Обучение на GPU
  12. Сравнение: мини-фреймворк, PyTorch и JAX
  13. Соберите это
  14. Шаг 1: загрузите MNIST из необработанных файлов
  15. Шаг 2: определите модель
  16. Шаг 3: цикл обучения
  17. Шаг 4: свяжите всё вместе
  18. Используйте это
  19. Быстрое сравнение: мини-фреймворк и PyTorch
  20. Сохранение и загрузка моделей
  21. Планирование скорости обучения
  22. Выпустите это
  23. Упражнения
  24. Ключевые термины
  25. Дополнительное чтение

Вы построили двигатель из поршней и коленвалов. Теперь научитесь пользоваться тем, на котором ездит каждый.

Тип: Сборка Языки: Python Предварительные требования: Урок 03.10 (Создайте собственный мини-фреймворк) Время: ~75 минут

Цели обучения

  • Создавать и обучать нейронные сети с помощью PyTorch: nn.Module, nn.Sequential и autograd
  • Использовать тензоры PyTorch, ускорение на GPU и стандартный цикл обучения (zero_grad, forward, loss, backward, step)
  • Переносить компоненты своего мини-фреймворка, созданного с нуля, на их эквиваленты в PyTorch
  • Профилировать и сравнивать скорость обучения вашего чисто Python-фреймворка и PyTorch на одной задаче

Проблема

У вас есть работающий мини-фреймворк. Линейные слои, ReLU, dropout, batch norm, Adam, DataLoader, цикл обучения. Он обучает 4-слойную сеть на задаче классификации кругов на чистом Python.

Он также в 500 раз медленнее PyTorch на той же задаче.

Ваш мини-фреймворк обрабатывает по одному примеру за раз во вложенных циклах Python. PyTorch отправляет те же операции в оптимизированные ядра C++/CUDA, работающие на GPU. На одном NVIDIA A100 PyTorch обучает ResNet-50 (25,6 млн параметров) на ImageNet (1,28 млн изображений) примерно за 6 часов. На эту же задачу вашему фреймворку потребовалось бы примерно 3 000 часов — если бы он прежде не исчерпал память.

Скорость — не единственный разрыв. У вашего фреймворка нет поддержки GPU. Нет автоматического дифференцирования: вы вручную писали backward() для каждого модуля. Нет сериализации. Нет распределённого обучения. Нет смешанной точности. Нет способа отладить поток градиентов без операторов print.

PyTorch закрывает каждый из этих пробелов. И делает это, сохраняя точно такую же ментальную модель, которую вы уже построили: Module, forward(), parameters(), backward(), optimizer.step(). Концепции переносятся один к одному. Синтаксис почти идентичен. Разница в том, что PyTorch скрывает за тем же интерфейсом десятилетие системной инженерии, которую вы спроектировали с нуля.

Концепция

Почему победил PyTorch

В 2015 году TensorFlow требовал определить статический вычислительный граф до выполнения чего-либо. Вы строили граф, компилировали его, затем подавали в него данные. Отладка означала разглядывание визуализаций графа. Изменение архитектуры означало перестроить граф с нуля.

PyTorch появился в 2017 году с другой философией: немедленным выполнением (eager execution). Вы пишете Python. Он выполняется сразу. y = model(x) действительно вычисляет y прямо сейчас, а не «добавляет узел в граф, который когда-нибудь вычислит y». Поэтому работали стандартные инструменты отладки Python. Работал print(). Работал pdb. Работал if/else в вашем прямом проходе.

К 2020 году рынок высказался. Доля PyTorch в исследовательских статьях по ML выросла с 7% (2017) до более 75% (2022). Meta, Google DeepMind, OpenAI, Anthropic и Hugging Face используют PyTorch как основной фреймворк. TensorFlow 2.x в ответ внедрил немедленное выполнение — молчаливое признание правильности дизайна PyTorch.

Урок: опыт разработчика накапливается. Фреймворк, который на 10% медленнее, но на 50% быстрее отлаживается, побеждает каждый раз.

Тензоры

Тензор — это многомерный массив с тремя критически важными свойствами: формой (shape), типом данных (dtype) и устройством (device).

import torch

x = torch.zeros(3, 4)           # shape: (3, 4), dtype: float32, device: cpu
x = torch.randn(2, 3, 224, 224) # batch of 2 RGB images, 224x224
x = torch.tensor([1, 2, 3])     # from a Python list

Форма — это размерность. Скаляр имеет форму (), вектор — (n,), матрица — (m, n), пакет изображений — (batch, channels, height, width).

Dtype управляет точностью и памятью.

dtypeБитыДиапазонВариант использования
float3232~7 десятичных знаковОбучение по умолчанию
float1616~3,3 десятичных знакаСмешанная точность
bfloat1616Тот же диапазон, что у float32, меньшая точностьОбучение LLM
int88от -128 до 127Квантованный инференс

Устройство определяет, где происходит вычисление.

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
x = torch.randn(3, 4, device=device)
x = x.to("cuda")
x = x.cpu()

Каждая операция требует, чтобы все тензоры находились на одном устройстве. Это ошибка PyTorch №1, с которой сталкиваются начинающие: RuntimeError: Expected all tensors to be on the same device. Исправьте её, переместив всё на одно устройство до вычисления.

Изменение формы выполняется за константное время: меняются метаданные, а не данные.

x = torch.randn(2, 3, 4)
x.view(2, 12)      # reshape to (2, 12) -- must be contiguous
x.reshape(6, 4)    # reshape to (6, 4) -- works always
x.permute(2, 0, 1) # reorder dimensions
x.unsqueeze(0)     # add dimension: (1, 2, 3, 4)
x.squeeze()        # remove size-1 dimensions

Autograd

Ваш мини-фреймворк требовал реализовать backward() для каждого модуля. PyTorch не требует. Он записывает каждую операцию над тензорами в ориентированный ациклический граф (вычислительный граф), а затем проходит этот граф в обратном порядке, чтобы автоматически вычислить градиенты.

Диаграмма к уроку «Введение в PyTorch»

Ключевое отличие от вашего фреймворка: PyTorch использует ленточное автоматическое дифференцирование (tape-based autodiff). Каждая операция добавляется на «ленту» во время прямого прохода. Вызов .backward() воспроизводит ленту в обратном порядке.

x = torch.randn(3, requires_grad=True)
y = x ** 2 + 3 * x
z = y.sum()
z.backward()
print(x.grad)  # dz/dx = 2x + 3

Три правила autograd:

  1. Только листовые тензоры с requires_grad=True накапливают градиенты.
  2. По умолчанию градиенты накапливаются — вызывайте optimizer.zero_grad() перед каждым обратным проходом.
  3. torch.no_grad() отключает отслеживание градиентов (используйте при оценке).

nn.Module

nn.Module — базовый класс для каждого компонента нейронной сети в PyTorch. Вы уже строили эту абстракцию в уроке 10. Версия PyTorch добавляет автоматическую регистрацию параметров, рекурсивное обнаружение модулей, управление устройствами и сериализацию state_dict.

import torch.nn as nn

class MLP(nn.Module):
    def __init__(self, input_dim, hidden_dim, output_dim):
        super().__init__()
        self.layer1 = nn.Linear(input_dim, hidden_dim)
        self.relu = nn.ReLU()
        self.layer2 = nn.Linear(hidden_dim, output_dim)

    def forward(self, x):
        x = self.layer1(x)
        x = self.relu(x)
        x = self.layer2(x)
        return x

Когда вы присваиваете nn.Module или nn.Parameter атрибуту в __init__, PyTorch автоматически регистрирует его. model.parameters() рекурсивно собирает каждый зарегистрированный параметр. Поэтому вам никогда не нужно вручную собирать веса, как в мини-фреймворке.

Ключевые строительные блоки:

МодульЧто делаетПараметры
nn.Linear(in, out)Wx + bin*out + out
nn.Conv2d(in_ch, out_ch, k)Двумерная свёрткаin_chout_chk*k + out_ch
nn.BatchNorm1d(features)Нормализует активации2 * features
nn.Dropout(p)Случайное зануление0
nn.ReLU()max(0, x)0
nn.GELU()Gaussian error linear0
nn.Embedding(vocab, dim)Таблица поискаvocab * dim
nn.LayerNorm(dim)Нормализация по примеру2 * dim

Функции потерь и оптимизаторы

PyTorch поставляет готовые к промышленному использованию версии всего, что вы построили.

Функции потерь (из torch.nn):

Функция потерьЗадачаВход
nn.MSELoss()РегрессияЛюбая форма
nn.CrossEntropyLoss()Многоклассовая классификацияЛогиты (не softmax)
nn.BCEWithLogitsLoss()Бинарная классификацияЛогиты (не sigmoid)
nn.L1Loss()Регрессия (робастная)Любая форма
nn.CTCLoss()Выравнивание последовательностейЛогарифмы вероятностей

Примечание: CrossEntropyLoss внутренне объединяет LogSoftmax + NLLLoss. Передавайте исходные логиты, а не выходы softmax. Это распространённая ошибка, которая незаметно производит неправильные градиенты.

Оптимизаторы (из torch.optim):

ОптимизаторКогда использоватьТипичная LR
SGD(params, lr, momentum)CNN, хорошо настроенные конвейеры0.01—0.1
Adam(params, lr)Стартовая точка по умолчанию1e-3
AdamW(params, lr, weight_decay)Трансформеры, дообучение1e-4—1e-3
LBFGS(params)Малый масштаб, второй порядок1.0

Цикл обучения

Каждый цикл обучения PyTorch следует одному и тому же пятишаговому шаблону. Вы уже знаете его из урока 10.

Диаграмма к уроку «Введение в PyTorch»

Канонический шаблон:

for epoch in range(num_epochs):
    model.train()
    for inputs, targets in train_loader:
        inputs, targets = inputs.to(device), targets.to(device)
        optimizer.zero_grad()
        outputs = model(inputs)
        loss = criterion(outputs, targets)
        loss.backward()
        optimizer.step()

Пять строк внутри цикла пакета. Пять строк, которые обучили GPT-4, Stable Diffusion и LLaMA. Меняется архитектура. Меняются данные. Эти пять строк не меняются.

Dataset и DataLoader

Dataset в PyTorch — абстрактный класс с двумя методами: __len__ и __getitem__. DataLoader оборачивает его, добавляя пакетирование, перемешивание и многопроцессную загрузку данных.

from torch.utils.data import Dataset, DataLoader

class MNISTDataset(Dataset):
    def __init__(self, images, labels):
        self.images = images
        self.labels = labels

    def __len__(self):
        return len(self.labels)

    def __getitem__(self, idx):
        return self.images[idx], self.labels[idx]

loader = DataLoader(dataset, batch_size=64, shuffle=True, num_workers=4)

num_workers=4 запускает 4 процесса, параллельно загружающих данные, пока GPU обучается на текущем пакете. На нагрузках, ограниченных диском (крупные изображения, аудио), уже одно это может удвоить скорость обучения.

Обучение на GPU

Перемещение модели на GPU:

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

Это рекурсивно переносит каждый параметр и буфер на GPU. Затем перемещайте каждый пакет при обучении:

inputs, targets = inputs.to(device), targets.to(device)

Смешанная точность сокращает потребление памяти вдвое и удваивает пропускную способность на современных GPU (A100, H100, RTX 4090), выполняя прямой/обратный проход в float16 при сохранении основных весов в float32:

from torch.amp import autocast, GradScaler

scaler = GradScaler()
for inputs, targets in loader:
    with autocast(device_type="cuda"):
        outputs = model(inputs)
        loss = criterion(outputs, targets)
    scaler.scale(loss).backward()
    scaler.step(optimizer)
    scaler.update()
    optimizer.zero_grad()

Сравнение: мини-фреймворк, PyTorch и JAX

ВозможностьМини-фреймворк (L10)PyTorchJAX
АвтодифференцированиеРучной backward()Ленточный autogradФункциональные преобразования
ВыполнениеНемедленное (циклы Python)Немедленное (ядра C++)Трассировка + JIT-компиляция
Поддержка GPUНетДа (CUDA, ROCm, MPS)Да (CUDA, TPU)
Скорость (MNIST MLP)~300 с/эпоху~0,5 с/эпоху~0,3 с/эпоху
Система модулейПользовательский класс Modulenn.ModuleФункции без состояния (Flax/Equinox)
Отладкаprint()print(), pdb, breakpoint()Сложнее (JIT-трассировка ломает print)
ЭкосистемаНетHugging Face, Lightning, timmFlax, Optax, Orbax
Кривая обученияВы её построилиУмереннаяКрутая (функциональная парадигма)
Использование в продакшенеИгрушечные задачиMeta, OpenAI, Anthropic, HFGoogle DeepMind, Midjourney
dropout-mask

Соберите это

3-слойный MLP, обученный на MNIST только с помощью примитивов PyTorch. Без высокоуровневых обёрток. Без torchvision.datasets. Мы сами загрузим и разберём необработанные данные.

Шаг 1: загрузите MNIST из необработанных файлов

MNIST поставляется в 4 сжатых gzip-файлах: обучающие изображения (60 000 x 28 x 28), обучающие метки, тестовые изображения (10 000 x 28 x 28), тестовые метки. Мы загружаем их и разбираем двоичный формат.

import torch
import torch.nn as nn
import struct
import gzip
import urllib.request
import os

def download_mnist(path="./mnist_data"):
    base_url = "https://storage.googleapis.com/cvdf-datasets/mnist/"
    files = [
        "train-images-idx3-ubyte.gz",
        "train-labels-idx1-ubyte.gz",
        "t10k-images-idx3-ubyte.gz",
        "t10k-labels-idx1-ubyte.gz",
    ]
    os.makedirs(path, exist_ok=True)
    for f in files:
        filepath = os.path.join(path, f)
        if not os.path.exists(filepath):
            urllib.request.urlretrieve(base_url + f, filepath)

def load_images(filepath):
    with gzip.open(filepath, "rb") as f:
        magic, num, rows, cols = struct.unpack(">IIII", f.read(16))
        data = f.read()
        images = torch.frombuffer(bytearray(data), dtype=torch.uint8)
        images = images.reshape(num, rows * cols).float() / 255.0
    return images

def load_labels(filepath):
    with gzip.open(filepath, "rb") as f:
        magic, num = struct.unpack(">II", f.read(8))
        data = f.read()
        labels = torch.frombuffer(bytearray(data), dtype=torch.uint8).long()
    return labels

Шаг 2: определите модель

3-слойный MLP: 784 -> 256 -> 128 -> 10. Активации ReLU. Dropout для регуляризации. Без batch norm, чтобы оставить пример простым.

class MNISTModel(nn.Module):
    def __init__(self):
        super().__init__()
        self.net = nn.Sequential(
            nn.Linear(784, 256),
            nn.ReLU(),
            nn.Dropout(0.2),
            nn.Linear(256, 128),
            nn.ReLU(),
            nn.Dropout(0.2),
            nn.Linear(128, 10),
        )

    def forward(self, x):
        return self.net(x)

Выходной слой выдаёт 10 необработанных логитов (по одному на цифру). Без softmax: CrossEntropyLoss обрабатывает его внутри.

Число параметров: 784256 + 256 + 256128 + 128 + 128*10 + 10 = 235 146. По современным меркам крошечная модель. У GPT-2 small 124M. Эта обучается за секунды.

Шаг 3: цикл обучения

Канонический шаблон прямой проход — потеря — обратный проход — шаг.

def train_one_epoch(model, loader, criterion, optimizer, device):
    model.train()
    total_loss = 0
    correct = 0
    total = 0
    for images, labels in loader:
        images, labels = images.to(device), labels.to(device)
        optimizer.zero_grad()
        outputs = model(images)
        loss = criterion(outputs, labels)
        loss.backward()
        optimizer.step()
        total_loss += loss.item() * images.size(0)
        _, predicted = outputs.max(1)
        correct += predicted.eq(labels).sum().item()
        total += labels.size(0)
    return total_loss / total, correct / total


def evaluate(model, loader, criterion, device):
    model.eval()
    total_loss = 0
    correct = 0
    total = 0
    with torch.no_grad():
        for images, labels in loader:
            images, labels = images.to(device), labels.to(device)
            outputs = model(images)
            loss = criterion(outputs, labels)
            total_loss += loss.item() * images.size(0)
            _, predicted = outputs.max(1)
            correct += predicted.eq(labels).sum().item()
            total += labels.size(0)
    return total_loss / total, correct / total

Обратите внимание на torch.no_grad() при оценке. Он отключает autograd, уменьшая потребление памяти и ускоряя инференс. Без него PyTorch строит вычислительный граф, который вы никогда не используете.

Шаг 4: свяжите всё вместе

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

    download_mnist()
    train_images = load_images("./mnist_data/train-images-idx3-ubyte.gz")
    train_labels = load_labels("./mnist_data/train-labels-idx1-ubyte.gz")
    test_images = load_images("./mnist_data/t10k-images-idx3-ubyte.gz")
    test_labels = load_labels("./mnist_data/t10k-labels-idx1-ubyte.gz")

    train_dataset = torch.utils.data.TensorDataset(train_images, train_labels)
    test_dataset = torch.utils.data.TensorDataset(test_images, test_labels)
    train_loader = torch.utils.data.DataLoader(
        train_dataset, batch_size=64, shuffle=True
    )
    test_loader = torch.utils.data.DataLoader(
        test_dataset, batch_size=256, shuffle=False
    )

    model = MNISTModel().to(device)
    criterion = nn.CrossEntropyLoss()
    optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)

    num_params = sum(p.numel() for p in model.parameters())
    print(f"Device: {device}")
    print(f"Parameters: {num_params:,}")
    print(f"Train samples: {len(train_dataset):,}")
    print(f"Test samples: {len(test_dataset):,}")
    print()

    for epoch in range(10):
        train_loss, train_acc = train_one_epoch(
            model, train_loader, criterion, optimizer, device
        )
        test_loss, test_acc = evaluate(
            model, test_loader, criterion, device
        )
        print(
            f"Epoch {epoch+1:2d} | "
            f"Train Loss: {train_loss:.4f} | Train Acc: {train_acc:.4f} | "
            f"Test Loss: {test_loss:.4f} | Test Acc: {test_acc:.4f}"
        )

    torch.save(model.state_dict(), "mnist_mlp.pt")
    print(f"\nModel saved to mnist_mlp.pt")
    print(f"Final test accuracy: {test_acc:.4f}")

Ожидаемый результат после 10 эпох: ~97,8% точности на тесте. Время обучения на CPU: ~30 секунд. На GPU: ~5 секунд. В вашем мини-фреймворке с той же архитектурой: ~45 минут.

Используйте это

Быстрое сравнение: мини-фреймворк и PyTorch

Мини-фреймворк (урок 10)PyTorch
model = Sequential(Linear(784, 256), ReLU(), ...)model = nn.Sequential(nn.Linear(784, 256), nn.ReLU(), ...)
pred = model.forward(x)pred = model(x)
optimizer.zero_grad()optimizer.zero_grad()
grad = criterion.backward() then model.backward(grad)loss.backward()
optimizer.step()optimizer.step()
Нет GPUmodel.to("cuda")
Ручной обратный проход для каждого модуляAutograd обрабатывает всё

Интерфейс почти идентичен. Разница — во всём, что находится под капотом.

Сохранение и загрузка моделей

torch.save(model.state_dict(), "model.pt")

model = MNISTModel()
model.load_state_dict(torch.load("model.pt", weights_only=True))
model.eval()

Всегда сохраняйте state_dict() (словарь параметров), а не объект модели. Сохранение объекта модели использует pickle, который ломается при рефакторинге кода. State dict переносимы.

Планирование скорости обучения

scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(
    optimizer, T_max=10
)
for epoch in range(10):
    train_one_epoch(model, train_loader, criterion, optimizer, device)
    scheduler.step()

PyTorch поставляет более 15 планировщиков: StepLR, ExponentialLR, CosineAnnealingLR, OneCycleLR, ReduceLROnPlateau. Все подключаются к одному интерфейсу оптимизатора.

Выпустите это

Этот урок создаёт два артефакта:

  • outputs/prompt-pytorch-debugger.md — промпт для диагностики распространённых сбоев обучения PyTorch
  • outputs/skill-pytorch-patterns.md — справочник-практика по шаблонам обучения PyTorch

Упражнения

  1. Добавьте batch normalization. Вставьте nn.BatchNorm1d после каждого линейного слоя (перед активацией). Сравните точность теста и скорость обучения с версией только с dropout. Batch norm должен достичь 98%+ за меньшее число эпох.

  2. Реализуйте поиск скорости обучения. Обучайте одну эпоху с экспоненциально возрастающей скоростью обучения (от 1e-7 до 1.0). Постройте функцию потерь относительно LR. Оптимальная LR находится непосредственно перед тем, как функция потерь начинает расти. Используйте это для выбора лучшей LR для модели MNIST.

  3. Перенесите на GPU со смешанной точностью. Добавьте torch.amp.autocast и GradScaler в цикл обучения. Измерьте пропускную способность (примеров в секунду) с смешанной точностью и без неё на GPU. На A100 ожидайте ~2x ускорение.

  4. Создайте пользовательский Dataset. Скачайте Fashion-MNIST (тот же формат, что и MNIST, но с предметами одежды). Реализуйте класс FashionMNISTDataset(Dataset) с __getitem__ и __len__. Обучите тот же MLP и сравните точность. Fashion-MNIST сложнее: ожидайте ~88% против ~98%.

  5. Замените Adam на SGD + momentum. Обучайте с SGD(params, lr=0.01, momentum=0.9). Сравните кривые сходимости. Затем добавьте планировщик CosineAnnealingLR и проверьте, догонит ли SGD Adam к 10-й эпохе.

Ключевые термины

ТерминКак обычно говорятЧто это действительно означает
Тензор«Многомерный массив»Типизированный массив, учитывающий устройство и имеющий поддержку автоматического дифференцирования в каждой операции
Autograd«Автоматическое обратное распространение»Ленточная система, записывающая операции во время прямого прохода, затем воспроизводящая их в обратном порядке для вычисления точных градиентов
nn.Module«Слой»Базовый класс для любого блока дифференцируемых вычислений: регистрирует параметры, поддерживает вложение, обрабатывает режимы train/eval
state_dict«Веса модели»OrderedDict, сопоставляющий имена параметров с тензорами: переносимое сериализуемое представление обученной модели
.backward()«Вычислить градиенты»Обойти вычислительный граф в обратном порядке, вычисляя и накапливая градиенты для каждого листового тензора с requires_grad=True
.to(device)«Перенести на GPU»Рекурсивно перенести все параметры и буферы на указанное устройство (CPU, CUDA, MPS)
DataLoader«Конвейер данных»Итератор, который пакетирует, перемешивает и при необходимости параллелит загрузку данных из Dataset
Смешанная точность«Использовать float16»Обучать с float16 в прямом/обратном проходе для скорости, сохраняя основные веса float32 для численной стабильности
Немедленное выполнение«Выполнить сейчас»Операции выполняются сразу при вызове, а не откладываются до последующей компиляции: ключевой выбор дизайна, отличающий PyTorch от TF 1.x
zero_grad«Сбросить градиенты»Установить все градиенты параметров в ноль перед следующим обратным проходом, поскольку PyTorch по умолчанию накапливает градиенты

Дополнительное чтение

  • Paszke et al., «PyTorch: An Imperative Style, High-Performance Deep Learning Library» (2019) — оригинальная статья, объясняющая компромиссы дизайна PyTorch
  • PyTorch Tutorials: «Learning PyTorch with Examples» (https://pytorch.org/tutorials/beginner/pytorch_with_examples.html) — официальный путь от тензоров к nn.Module
  • PyTorch Performance Tuning Guide (https://pytorch.org/tutorials/recipes/recipes/tuning_guide.html) — смешанная точность, воркеры DataLoader, закреплённая память и другие оптимизации для продакшена
  • Horace He, «Making Deep Learning Go Brrrr» (https://horace.io/brrr_intro.html) — почему обучение на GPU быстрое, с оптимизациями, специфичными для PyTorch

Источник: Introduction to PyTorch 03.10 — Создайте собственный мини-фреймворк · Фаза 3 — Основы глубокого обучения · Полный каталог · 03.12 — Введение в JAX