Примеры использования Adept

Обучение MLP на MNIST

Полный пример обучения многослойного перцептрона (MLP) для классификации изображений из датасета MNIST с поддержкой CPU/GPU, сохранением чекпоинтов и тестированием.


1. Импорт зависимостей

import sys
from pathlib import Path
from tqdm import tqdm
import time
from argparse import ArgumentParser

from adept import (
    Variable,
    Tensor,
    TensorProperties,
    Shape,
    device_t,
    dtype_t,
    no_grad,
)
from adept.nn import Linear, Module, relu
from adept.optim import SGD
from adept.data import MNISTDataset, CPPDataLoader
from adept.loss import cross_entropy_with_logits
from adept.serialize import FileInput, FileOutput

Что импортируем:

  • Variable — тензор с поддержкой автоматического дифференцирования
  • Tensor, TensorProperties, Shape — базовые структуры для работы с тензорами
  • device_t, dtype_t — типы устройства (CPU/GPU) и данных
  • no_grad — контекст для отключения градиентов
  • Linear, Module, relu — модули нейронной сети
  • SGD — оптимизатор стохастического градиентного спуска
  • MNISTDataset, CPPDataLoader — загрузчики данных
  • cross_entropy_with_logits — функция потерь
  • FileInput, FileOutput — сериализация для чекпоинтов

2. Глобальные параметры

device = device_t.CPU
dtype = dtype_t.Float32

epochs = 5
batch_size = 32
lr = 0.1
lr_decay = 0.5

Настройки:

  • Устройство по умолчанию — CPU (можно изменить через -g)
  • Тип данных — Float32
  • 5 эпох обучения
  • Размер батча — 32
  • Начальный learning rate — 0.1
  • Затухание LR — 0.5 после каждой эпохи

3. Определение модели

class MLP(Module):
    def __init__(self):
        super().__init__()
        self.l1 = Linear(28 * 28, 512, dtype=dtype)
        self.l2 = Linear(512, 256, dtype=dtype)
        self.l3 = Linear(256, 10, dtype=dtype)

    def forward(self, x):
        out = relu(self.l1(x))
        out = relu(self.l2(out))
        out = self.l3(out)
        return out

Архитектура:

  • Вход: 784 нейрона (28×28 пикселей)
  • Скрытый слой 1: 512 нейронов + ReLU
  • Скрытый слой 2: 256 нейронов + ReLU
  • Выход: 10 нейронов (классы цифр 0-9)

4. Парсинг аргументов

def main():
    parser = ArgumentParser()
    parser.add_argument("mnist_path")
    parser.add_argument("-c", "--checkpoint", type=str)
    parser.add_argument("-g", "--gpu", action="store_true")
    args = parser.parse_args()

    mnist_path = Path(args.mnist_path)

    if args.gpu:
        global device
        device = device_t.GPU

Аргументы командной строки:

  • mnist_path — путь к датасету MNIST
  • -c, --checkpoint — путь к чекпоинту для продолжения обучения
  • -g, --gpu — использование GPU вместо CPU

5. Загрузка данных

    train_images_file = str(mnist_path / "train-images.idx3-ubyte")
    train_labels_file = str(mnist_path / "train-labels.idx1-ubyte")

    train_dataset = MNISTDataset(train_images_file, train_labels_file, dtype, True)
    train_dataloader = CPPDataLoader(train_dataset, batch_size)

    test_images_file = str(mnist_path / "t10k-images.idx3-ubyte")
    test_labels_file = str(mnist_path / "t10k-labels.idx1-ubyte")

    test_dataset = MNISTDataset(test_images_file, test_labels_file, dtype, True)
    test_dataloader = CPPDataLoader(test_dataset, batch_size)

Датасеты:

  • Отдельные загрузчики для тренировочных и тестовых данных
  • CPPDataLoader — высокопроизводительный загрузчик на C++

6. Инициализация модели и оптимизатора

    mlp = MLP()

    global lr
    if args.checkpoint:
        input = FileInput(args.checkpoint)
        mlp.load(input)
        lr = input.read("lr", dtype_t.Float32)

    mlp.to(device)

    optimizer = SGD(mlp.parameters(), lr)

Что происходит:

  • Создание экземпляра модели
  • Загрузка чекпоинта (если указан)
  • Перенос модели на целевое устройство
  • Инициализация оптимизатора SGD

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

    for epoch in tqdm(range(epochs), unit="epoch"):
        mlp.train()
        pbar = tqdm(train_dataloader, unit="batch")
        for b_i, batch in enumerate(pbar):
            x, y = batch
            out = mlp.forward(Variable(x.to(device), requires_grad=False))

            loss = cross_entropy_with_logits(
                out, Variable(y.to(device), requires_grad=False)
            )
            if b_i % 64 == 0:
                pbar.set_postfix(loss=loss.data().cpu().at([0, 0]))

            loss.backward()
            optimizer.step()
            optimizer.zero_grad()
        optimizer.set_lr(optimizer.lr() * lr_decay)

Шаги обучения:

  1. Переключение модели в режим обучения (train())
  2. Прямой проход (forward pass)
  3. Вычисление функции потерь
  4. Обратное распространение (backward())
  5. Шаг оптимизатора (step())
  6. Обнуление градиентов (zero_grad())
  7. Затухание learning rate после эпохи

8. Сохранение чекпоинта

        # save checkpoint
        checkpoint_path = f"checkpoint_{epoch}_{int(time.time()*1000.0)}.pt"
        output = FileOutput(checkpoint_path)
        mlp.save(output)
        output.write("lr", optimizer.lr())

Сохраняется:

  • Веса модели
  • Текущий learning rate
  • Уникальное имя файла с меткой времени

9. Тестирование

        # test
        mlp.eval()
        with no_grad():
            total_loss = Tensor.zero(TensorProperties(Shape([1]), device, dtype))
            for b_i, batch in enumerate(test_dataloader):
                x, y = batch
                out = mlp.forward(Variable(x.to(device)))
                loss = cross_entropy_with_logits(out, Variable(y.to(device)))
                total_loss += loss.data()
            pbar.set_postfix(test_loss=total_loss.cpu().at([0]) / b_i)

Особенности:

  • Переключение в режим оценки (eval())
  • Контекст no_grad() для экономии памяти
  • Подсчёт среднего значения функции потерь на тестовом наборе

10. Запуск

if __name__ == "__main__":
    main()

Запуск примера

# Обучение на CPU
python mlp_mnist.py /path/to/mnist

# Обучение на GPU
python mlp_mnist.py /path/to/mnist -g

# Продолжение с чекпоинта
python mlp_mnist.py /path/to/mnist -c checkpoint_2_1234567890.pt