В нашем предыдущем посте блога мы углубились в основы нейронных сетей, поняли их базовую структуру и то, как они работают. В этом блоге мы собираемся вывести эти знания на новый уровень, реализовав практический пример распознавания рукописного текста с помощью PyTorch. Итак, начнем!

Распознавание рукописного текста

Распознавание рукописного текста — увлекательное применение нейронных сетей. Он предполагает научить компьютер расшифровывать и понимать человеческий почерк. Эта технология имеет широкий спектр применений: от оцифровки исторических документов до улучшения пользовательского опыта на мобильных устройствах.

Подготовка данных

Чтобы начать путь к распознаванию рукописного текста, нам сначала понадобится набор данных рукописных символов. Для этой демонстрации мы будем использовать знаменитый набор данных MNIST, который содержит изображения рукописных цифр (0–9) размером 28x28 пикселей в оттенках серого.

К счастью, PyTorch упрощает процесс загрузки и хранения наборов данных с помощью torchvision.datasets. Это означает, что мы получаем доступ к чистым, хорошо подготовленным данным, что избавляет нас от необходимости дополнительной очистки данных. Это похоже на надежного друга, который подарит вам хорошо организованные инструменты для вашего проекта!

Прежде чем отправить данные в конвейер, у нас есть возможность выполнить некоторые преобразования. Этот шаг имеет решающее значение, поскольку он позволяет нам экспериментировать с различными параметрами, чтобы найти наилучшие результаты. В этом случае мы преобразуем наши входные данные в тензоры и применяем нормализацию, создавая основу для лучшей обработки данных и улучшения результатов.

import torchvision.transforms as transforms

transform = transforms.Compose([
    transforms.ToTensor(),
    transforms.Normalize((0.5,), (0.5,))
])

Давайте разберем каждый компонент transforms.Compose

  1. transforms.ToTensor():
  • Это преобразование преобразует изображение PIL (библиотека изображений Python) или массив NumPy в тензор PyTorch.
  • По сути, он преобразует данные изображения в формат, который может напрямую использоваться PyTorch.

2. ransforms.Normalize(mean, std):

  • Это преобразование используется для нормализации данных. Он вычитает среднее значение и делит его на стандартное отклонение, чтобы получить среднее значение данных 0 и стандартное отклонение 1. Это помогает стабилизировать и ускорить обучение нейронных сетей.

  • В предоставленном коде (0.5,) — это среднее значение, а (0.5,) — значение стандартного отклонения. Используется для одноканальных изображений (в оттенках серого). Если у вас многоканальное изображение (например, RGB), вы должны предоставить кортеж средних и стандартных отклонений для каждого канала (например, (mean_R, mean_G, mean_B) и (std_R, std_G, std_B)).
  • Установив среднее и стандартное отклонение на (0.5,) для изображения в оттенках серого, он эффективно масштабирует значения пикселей из диапазона [0, 1] в диапазон [-1, 1], что является обычной практикой для входных данных нейронной сети.

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

import torch
import torchvision

# Download and load the MNIST dataset
trainset = torchvision.datasets.MNIST(root='./data', train=True, transform=transform, download=True)
testset = torchvision.datasets.MNIST(root='./data', train=False, transform=transform, download=True)
# Create DataLoader objects for the training and testing data
batch_size = 64
trainloader = torch.utils.data.DataLoader(trainset, batch_size=batch_size, shuffle=True)
testloader = torch.utils.data.DataLoader(testset, batch_size=batch_size, shuffle=False)py

DataLoader — отличный способ управлять нашими данными и упростить обучение нашей нейронной сети. Как вы можете видеть в предоставленном коде, мы загружаем и загружаем набор данных MNIST, используя torchvision.datasets, а затем создаем объекты DataLoader для данных обучения и тестирования.

Сделав это, мы можем легко перебирать наши данные в пакетном режиме, что помогает как использовать память, так и повысить эффективность обучения. В коде мы установили размер пакета равным 64, но вы можете поэкспериментировать с разными размерами пакетов, чтобы найти тот, который лучше всего подходит для вашего конкретного проекта.

В целом, DataLoader — важный инструмент для любого проекта машинного обучения, и понимание того, как его эффективно использовать, поможет вам создавать более мощные и действенные модели.

Создание простой нейронной сети

Прежде чем мы углубимся в обучение нашей нейронной сети, давайте поближе посмотрим, что происходит на этом самом первом уровне. Если мы внимательно рассмотрим изображение, то заметим, что оно состоит из 784 аккуратно выстроенных пикселей, и каждый из этих пикселей играет особую роль в нашей нейронной сети. Думайте об этих пикселях как о крошечных нейронах в нашей сети, каждый из которых содержит число, которое говорит нам об интенсивности цвета в диапазоне от 0 (черный) до 255 (белый). Это похоже на красочный оркестр, который готовит почву для выступления нашей нейронной сети!

import torch.nn as nn
import torch.optim as optim

class SimpleNN(nn.Module):
    def __init__(self):
        super(SimpleNN, self).__init__()
        self.fc1 = nn.Linear(784, 128)
        self.fc2 = nn.Linear(128, 10)
    def forward(self, x):
        x = x.view(-1, 784)
        x = torch.relu(self.fc1(x))
        x = self.fc2(x)
        return x
# Create an instance of the neural network
net = SimpleNN()

Теперь, когда у нас настроен первый слой, пришло время определиться с количеством нейронов и слоев для следующего шага. В этом примере мы упростили задачу, используя всего один слой, содержащий 128 нейронов. Итак, если разложить по полочкам: наш первый слой создаст 128 объектов, и эти объекты станут входными данными для следующего слоя. Эти 128 нейронов затем сыграют свою роль в создании последних 10 функций.

Итак, общее количество параметров, которые будут обучены, составляет 1 01 770. Но как?

self.fc1: Этот слой имеет 784 входных нейрона, и каждое соединение между входным объектом и нейроном скрытого слоя имеет один обучаемый параметр (вес). Кроме того, для каждого из 128 нейронов существует смещение. Итак, общее количество параметров в этом слое равно (784 * 128) + 128.

self.fc2: Этот слой принимает выходные данные предыдущего слоя (входного слоя) в качестве входных данных и создает выходные данные. Он выполняет все расчеты, чтобы найти скрытые особенности и закономерности. Как и в предыдущем слое, каждое соединение имеет один обучаемый вес, и для каждого из 10 нейронов существует член смещения. Итак, общее количество параметров в этом слое равно (128 * 10) + 10.

Всего параметров = Параметры в self.fc1 + Параметры в self.fc2

Всего параметров = ((784 * 128) + 128) + ((128 * 10) + 10)

Общий процесс простой нейронной сети можно резюмировать следующим образом:

  1. Входные данные передаются на входной слой.
  2. Входные значения умножаются на веса и суммируются в скрытых слоях.
  3. Суммированные значения затем передаются через функции активации в скрытых слоях.
  4. Этот процесс повторяется для каждого скрытого слоя.
  5. Наконец, выходной уровень выдает прогноз сети.

Обучение

criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(net.parameters(), lr=0.01, momentum=0.9)

for epoch in range(10):
    running_loss = 0.0
    for i, data in enumerate(trainloader, 0):
        inputs, labels = data
        optimizer.zero_grad()
        outputs = net(inputs)
        loss = criterion(outputs, labels)
        loss.backward()
        optimizer.step()
        running_loss += loss.item()
    print(f"Epoch {epoch + 1}, Loss: {running_loss / len(trainloader)}")

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

Оценка модели

correct = 0
total = 0

# Disable gradient computation during evaluation
with torch.no_grad():
    for data in testloader:
        images, labels = data

        # Forward pass: Get predictions from the neural network
        outputs = net(images)

        # Use softmax to obtain class probabilities
        _, predicted = torch.max(outputs, 1)  # Extract class with the highest probability

        # Update total count
        total += labels.size(0)

        # Count the number of correctly classified samples
        correct += (predicted == labels).sum().item()

# Calculate and print the accuracy
print(f"Model Accuracy: {100 * correct / total}%")

После того, как мы обучили нашу нейронную сеть, важно оценить ее производительность на невидимых данных, чтобы понять, насколько хорошо она обобщает. Этот сегмент кода представляет собой этап оценки нашей обученной модели.

  • correct и total инициализируются для отслеживания количества правильно классифицированных выборок и общего количества выборок соответственно.
  • Мы используем torch.no_grad(), чтобы отключить вычисление градиента во время оценки, поскольку нам не нужно обновлять параметры модели на этом этапе. Это ускоряет процесс и экономит память.
  • В цикле (for data in testloader) мы перебираем тестовый набор данных. На каждой итерации мы загружаем пакет тестовых изображений (images) и соответствующие им метки (labels).
  • Затем нейронная сеть используется для прогнозирования (outputs) пакета изображений посредством прямого прохода. Функция torch.max(outputs.data, 1) извлекает предсказанные классы.
  • Мы обновляем total, добавляя размер пакета (т. е. labels.size(0)), представляющий количество образцов в текущем пакете.
  • Строка (predicted == labels).sum().item() подсчитывает количество правильно классифицированных образцов в текущей партии и добавляет это количество к переменной correct.
  • Наконец, мы рассчитываем и печатаем точность нашей модели PyTorch, разделив количество правильных прогнозов (correct) на общее количество выборок (total) и умножив на 100, чтобы выразить это в процентах. Это дает нам ценную информацию о том, насколько хорошо наша модель работает на новых, ранее неизвестных данных.

Заключение

В этом блоге мы сделали первые шаги в мир нейронных сетей, применив наши знания для практической задачи распознавания рукописного текста. Мы начали с понимания основ, изучили подготовку данных и создали простую нейронную сеть. Благодаря обучению и оценке мы стали свидетелями силы этих сетей в действии.

Спасибо за прочтение!