Skip to content

In this repository, I trained using the Duke University method and tested the MNIST dataset of handmade numbers using PyTorch

Notifications You must be signed in to change notification settings

Gando4lapi/-CNN-MNIST

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 

Repository files navigation

-CNN-Determining-the-handmade-number-using-PyTorch

In this repository, I trained using the Duke University method and tested the MNIST dataset of handmade numbers using PyTorch Коротко: код загружает датасет MNIST, строит простую сверточную сеть, обучает её 3 эпохи оптимизатором Adam с функцией потерь CrossEntropyLoss и оценивает accuracy на тесте. Ниже — разбор каждой строки и параметра простыми словами, с мини-иллюстрациями-пояснениями в тексте.

Импорты

  • import torch, import torch.nn as nn, import torch.nn.functional as F: подключают PyTorch и его модули для нейросетей; nn содержит готовые слои, F — функции активации и пулы без параметров.
  • from torchvision import datasets, transforms: даёт доступ к популярным датасетам (MNIST) и преобразованиям изображений.
  • from tqdm.notebook import tqdm, trange: прогресс-бары в ноутбуке для циклов по батчам и эпохам.

Иллюстрация-идея: “кубики” — torch (базовые тензоры), nn (слои), F (функции), datasets (данные), transforms (преобразования), tqdm (индикатор).

Устройство и данные

  • device = torch.device("cuda" if torch.cuda.is_available() else "cpu"): выбирает GPU при наличии, иначе CPU; перенос тензоров и модели на это устройство ускоряет расчёты на видеокарте.
  • transform = transforms.Compose([...]): конвейер преобразований.
    • transforms.ToTensor(): конвертирует PIL-изображение MNIST из в тензор float в диапазоне.
    • transforms.Normalize((0.1307,), (0.3081,)): нормирует по формуле $$x'=(x-\mu)/\sigma$$ с усреднением по всему датасету; значения 0.1307 и 0.3081 — среднее и стандартное отклонение MNIST после масштабирования к. Это ускоряет обучение.

Иллюстрация-идея: шкала серого 0…1 → “центрирование” вокруг 0 и “сжатие” по σ.

  • mnist_train = datasets.MNIST(root="./datasets", train=True, transform=transform, download=True): загружает обучающую часть MNIST в папку, применяет transform, скачивает при отсутствии.
  • mnist_test = datasets.MNIST(root="./datasets", train=False, transform=transform, download=True): тестовая часть с теми же преобразованиями.
  • train_loader = torch.utils.data.DataLoader(mnist_train, batch_size=100, shuffle=True): создаёт итератор по батчам размера 100 и перемешивает данные каждый эпохой для лучшей обобщающей способности.
  • test_loader = torch.utils.data.DataLoader(mnist_test, batch_size=100, shuffle=False): тест не перемешивают; размер батча 100 ускоряет оценку.

Пояснение параметров DataLoader:

  • batch_size: сколько образцов в одном шаге; компромисс между скоростью и стабильностью градиентов.
  • shuffle: True — случайный порядок в каждой эпохе (используется RandomSampler под капотом).

Модель CNN

  • class MNIST_CNN(nn.Module): объявление модели. Наследование от nn.Module позволяет регистрировать слои и параметры.

  • self.conv1 = nn.Conv2d(1, 32, 3, 1): сверточный слой 2D. Параметры: in_channels=1 (ч/б), out_channels=32 (фильтры), kernel_size=3 (3×3 окно), stride=1 (шаг 1). Результат: 32 карт признаков.

  • self.conv2 = nn.Conv2d(32, 64, 3, 1): второй свёрточный слой, вход 32 канала, выход 64, ядро 3×3, шаг 1.[1]

  • self.dropout1 = nn.Dropout(0.25): случайно зануляет 25% нейронов при обучении для регуляризации (борьба с переобучением).

  • self.dropout2 = nn.Dropout(0.5): зануляет 50% перед последним слоем.

  • self.fc1 = nn.Linear(9216, 128): полносвязный слой из 9216 признаков в 128. Число 9216 получается из размерности после свёрток и пулинга: вход 28×28 → conv3x3 stride1 без padding уменьшает на 2 пикселя с каждой стороны; дважды: 28→26→24; затем max-pool 2×2 делит пополам: 24→12; 64 карт по 12×12: $$64×12×12=9216$$.

  • self.fc2 = nn.Linear(128, 10): выходной слой на 10 классов цифр 0–9, выдаёт логиты (не вероятности).

  • def forward(self, x): прямой проход:

    • x = F.relu(self.conv1(x)): свёртка → ReLU добавляет нелинейность.
    • x = F.relu(self.conv2(x)): вторая свёртка + ReLU.
    • x = F.max_pool2d(x, 2): максимальный пулинг 2×2 уменьшает размер карт в 2 раза, выделяя “самые сильные” признаки.
    • x = self.dropout1(x): регуляризация.
    • x = torch.flatten(x, 1): разворачивает карты признаков в вектор, начиная с оси 1 (оставляя размер батча).
    • x = F.relu(self.fc1(x)): полносвязный слой + ReLU.
    • x = self.dropout2(x): ещё регуляризация.
    • x = self.fc2(x): логиты классов, без softmax. Для CrossEntropyLoss это правильно — softmax встроен в лосс.
    • return x: возврат логитов.

Иллюстрация-идея: “конвейер” — изображение 28×28 → conv → conv → pool → flatten → fc → fc (10 логитов).

  • model = MNIST_CNN().to(device): создаёт модель и переносит на CPU или GPU.
  • criterion = nn.CrossEntropyLoss(): функция потерь для многоклассовой классификации; принимает логиты и целочисленные метки классов, внутри применяет LogSoftmax + NLLLoss, one-hot не нужен.
  • optimizer = torch.optim.Adam(model.parameters(), lr=0.001): оптимизатор Adam с шагом обучения 0.001; адаптивно настраивает скорость для каждого параметра.

Параметры CrossEntropyLoss и почему так:

  • Вход: логиты размера [batch, classes]. Цели: индексы классов [batch], а не one-hot.
  • Опции: weight для дисбаланса классов, ignore_index для пропуска, reduction (“mean” по умолчанию).

Обучение

  • for epoch in trange(3):: 3 эпохи обучения; trange показывает прогресс.
  • model.train(): включает режим обучения (активирует Dropout и, при наличии, BatchNorm).
  • for images, labels in tqdm(train_loader):: итерируемся по батчам изображений и меток.
  • images = images.to(device); labels = labels.to(device): переносим данные на устройство модели.
  • optimizer.zero_grad(): обнуляем прошлые градиенты (иначе будут накапливаться).
  • y = model(images): прямой проход, получаем логиты.
  • loss = criterion(y, labels): считаем кросс-энтропийную потерю между логитами и метками.
  • loss.backward(): обратное распространение, вычисление градиентов параметров модели.
  • optimizer.step(): обновление параметров по правилам Adam.

Иллюстрация-идея: цикл “прямой проход → loss → градиент → шаг оптимизатора” повторяется для каждого батча и эпохи.

Оценка точности

  • model.eval(): режим оценки (Dropout выключен).
  • correct = 0: счётчик верных предсказаний.
  • total = len(mnist_test): всего тестовых образцов (10000 в MNIST).
  • with torch.no_grad():: отключает градиенты для экономии памяти/скорости при инференсе.
  • for images, labels in tqdm(test_loader):: перебор тестовых батчей.
  • y = model(images): логиты.
  • predictions = torch.argmax(y, dim=1): выбираем индекс максимального логита — предсказанный класс.
  • correct += (predictions == labels).sum().item(): суммируем верные.
  • print('Test accuracy: {:.4f}'.format(correct / total)): выводим долю верных — accuracy.

Иллюстрация-идея: “столбик” верных предсказаний растёт по мере прохода по тестовым батчам, итог — доля верных.

Почему нормализация именно такая

  • ToTensor уже делит на 255 → значения в. Поэтому использовать среднее и std, предвычисленные для уже масштабированных пикселей: $$\mu≈0.1307,\ \sigma≈0.3081$$. Это общепринятые константы для MNIST в PyTorch-примерах.
  • Нормализация ускоряет сходимость и стабилизирует градиенты; CrossEntropyLoss ожидает логиты, softmax применять отдельно не нужно.

Объяснение ключевых параметров простыми словами

  • batch_size=100: учимся “порциями” по 100 картинок — быстрее и стабильнее, чем по одной; можно менять в зависимости от памяти GPU/CPU.
  • shuffle=True (на train): каждую эпоху порядок перемешан, чтобы сеть не запоминала последовательности и лучше обобщала.
  • lr=0.001: скорость обучения для Adam; слишком большая — скачет и не сходится, слишком маленькая — учится медленно.
  • Dropout(0.25/0.5): “выключает” часть нейронов случайно при обучении, чтобы сеть не переобучалась на шум и частные паттерны.
  • CrossEntropyLoss: измеряет “насколько уверенно” сеть ставит высокий логит правильному классу; чем выше логит правильного класса относительно остальных, тем меньше лосс.

Если хочется “ещё лучше”

  • Ускорить загрузку данных: добавить num_workers>0 и pin_memory=True в DataLoader при обучении на CUDA.
  • Добавить лёгкие аугментации (например, RandomRotation) — рукописные цифры могут быть повёрнуты/сдвинуты.
  • Увеличить эпохи до 5–10 — точность обычно вырастет.

Пример улучшенного DataLoader (для GPU):

  • train_loader = DataLoader(mnist_train, batch_size=128, shuffle=True, num_workers=2, pin_memory=True) — быстрее подаёт батчи на видеокарту при достаточных ресурсах.

Доп. справки:

  • Общая схема обучения и примеры CNN в PyTorch туториалах.
  • Детали CrossEntropyLoss и почему не нужен one-hot.
  • Объяснение Normalize и значений для MNIST.
  • Пояснения к DataLoader и shuffle.

About

In this repository, I trained using the Duke University method and tested the MNIST dataset of handmade numbers using PyTorch

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published