LeNet: простая реализация сверточной нейронной сети PyTorch

искусственный интеллект глубокое обучение
LeNet: простая реализация сверточной нейронной сети PyTorch

Аккаунт WeChat: ilulaoshi / Личный сайт:lulaoshi.info

Предыдущие две статьи представилисверточный слойиобъединяющий слой, Свертка и объединение — две основные основы сверточных нейронных сетей. В этой статье мы познакомим вас с ранней сверточной нейронной сетью для распознавания изображений рукописных цифр: LeNet [1]. Название LeNet происходит от имени Янна Лекуна, первого автора статьи. В 1989 году LeNet использовал сверточные нейронные сети и градиентный спуск, чтобы вывести распознавание рукописных цифр на передовой уровень того времени. Эта новаторская работа впервые вывела сверточные нейронные сети на сцену истории и стала известна миру. Благодаря отличным характеристикам LeNet во многих банкоматах LeNet используется для распознавания цифровых символов.

Код этой статьи на основе PyTorch и TensorFlow 2 размещен в моемGitHubначальство.

Структура сетевой модели

Структура сети LeNet показана на следующем рисунке.

LetNet网络结构

LeNet разделен на две части: блок сверточного слоя и блок полносвязного слоя.

Базовой единицей в блоке сверточного слоя является сверточный слой, за которым следует слой максимального объединения: сверточный слой используется для идентификации пространственных шаблонов в изображении, таких как линии и части объекта, а последующий слой максимального объединения используется для уменьшить сверточный слой Чувствительность к местоположению. Блок сверточного слоя состоит из повторяющегося наложения двух таких базовых блоков сверточного слоя и объединяющего слоя. В блоке сверточных слоев каждый сверточный слой использует окно 5×5 и использует сигмовидную функцию активации на выходе. Входными данными для всей модели является одномерное черно-белое изображение размером 28×28. Количество выходных каналов первого сверточного слоя равно 6, а количество выходных каналов второго сверточного слоя увеличено до 16. Это связано с тем, что вход второго сверточного слоя меньше по высоте и ширине, чем первый сверточный слой, поэтому увеличение выходного канала делает размер параметра двух сверточных слоев одинаковым. Оба слоя максимального объединения блока сверточных слоев имеют форму окна 2×2 и шаг 2. Поскольку окно объединения имеет ту же форму, что и шаг, область, покрываемая каждым движением окна объединения на входе, не перекрывается друг с другом.

Мы используем PyTorchSequentialкласс для реализации модели LeNet.

class LeNet(nn.Module):
    def __init__(self):
        super(LeNet, self).__init__()
        
        # 输入 1 * 28 * 28
        self.conv = nn.Sequential(
            # 卷积层1
            # 在输入基础上增加了padding,28 * 28 -> 32 * 32
            # 1 * 32 * 32 -> 6 * 28 * 28
            nn.Conv2d(in_channels=1, out_channels=6, kernel_size=5, padding=2), nn.Sigmoid(),
            # 6 * 28 * 28 -> 6 * 14 * 14
            nn.MaxPool2d(kernel_size=2, stride=2), # kernel_size, stride
            # 卷积层2
            # 6 * 14 * 14 -> 16 * 10 * 10 
            nn.Conv2d(in_channels=6, out_channels=16, kernel_size=5), nn.Sigmoid(),
            # 16 * 10 * 10 -> 16 * 5 * 5
            nn.MaxPool2d(kernel_size=2, stride=2)
        )
        self.fc = nn.Sequential(
            # 全连接层1
            nn.Linear(in_features=16 * 5 * 5, out_features=120), nn.Sigmoid(),
            # 全连接层2
            nn.Linear(in_features=120, out_features=84), nn.Sigmoid(),
            nn.Linear(in_features=84, out_features=10)
        )

    def forward(self, img):
        feature = self.conv(img)
        output = self.fc(feature.view(img.shape[0], -1))
        return output

Необходимо разобраться с параметрами каждого слоя модели. Входная фигура представляет собой изображение с номером канала 1 (1-мерное черно-белое изображение) и размером 28×28. После первого слоя свертки 5×5 2 элемента используются как отступы для верхнего, нижнего , влево и вправо во время свертки, а выходная форма: (28 - 5 + 4 + 1) × (28 - 5 + 4 + 1) = 28 × 28. Первый сверточный слой выводит всего 6 каналов, а форма вывода: 6 × 28 × 28. Размер ядра максимального объединяющего слоя составляет 2 × 2, шаг равен 2, высота и ширина уменьшаются вдвое, а форма: 6 × 14 × 14. Ядро второго сверточного слоя также имеет размер 5 × 5, но без заполнения, поэтому выходная форма: (14 – 5 + 1) × (14 – 5 + 1) = 10 × 10. Выход второго ядра свертки составляет 16 каналов, поэтому он становится 16 × 10 × 10. После слоя максимального объединения высота и ширина уменьшаются вдвое, и конечный результат: 16 × 5 × 5.

Выходная форма блока сверточного слоя (batch_size, output_channels, height, width), в данном случае (batch_size, 16, 5, 5), где размер batch_size может изменяться. Когда выходные данные блока сверточного слоя передаются в блок полносвязного слоя, блок полносвязного слоя будет сглаживать каждую выборку в пакете. Исходная форма: (количество каналов × высота × ширина), и теперь она напрямую становится длинным вектором, а длина вектора равна количеству каналов × высоте × ширине. В этом примере сглаженная длина вектора составляет: 16 × 5 × 5 = 400. Блок полносвязных слоев содержит 3 полносвязных слоя. Их выходные номера равны 120, 84 и 10 соответственно, где 10 — количество выходных категорий.

Обучите модель

На основе приведенной выше сети мы начинаем обучение модели. Мы используем Fashion-MNIST в качестве обучающего набора данных. Многие фреймворки, такие как PyTorc, предоставляют модуль для чтения данных Fashion-MNIST. Я сделал простой пакет:

def load_data_fashion_mnist(batch_size, resize=None, root='~/Datasets/FashionMNIST'):
    """Use torchvision.datasets module to download the fashion mnist dataset and then load into memory."""
    trans = []
    if resize:
        trans.append(torchvision.transforms.Resize(size=resize))
    trans.append(torchvision.transforms.ToTensor())
    
    transform = torchvision.transforms.Compose(trans)
    mnist_train = torchvision.datasets.FashionMNIST(root=root, train=True, download=True, transform=transform)
    mnist_test = torchvision.datasets.FashionMNIST(root=root, train=False, download=True, transform=transform)
    if sys.platform.startswith('win'):
        num_workers = 0  
    else:
        num_workers = 4
    train_iter = torch.utils.data.DataLoader(mnist_train, batch_size=batch_size, shuffle=True, num_workers=num_workers)
    test_iter = torch.utils.data.DataLoader(mnist_test, batch_size=batch_size, shuffle=False, num_workers=num_workers)

    return train_iter, test_iter

load_data_fashion_mnist()Метод возвращает обучающий набор и тестовый набор.

Во время обучения мы хотим увидеть точность каждой итерации, построитьevaluate_accuracyметод расчета точности текущей итерации (размер ошибки между прогнозируемым значением модели и истинным значением):

def evaluate_accuracy(data_iter, net, device=None):
    if device is None and isinstance(net, torch.nn.Module):
        device = list(net.parameters())[0].device
    acc_sum, n = 0.0, 0
    with torch.no_grad():
        for X, y in data_iter:
            if isinstance(net, torch.nn.Module):
                # set the model to evaluation mode (disable dropout)
                net.eval() 
                # get the acc of this batch
                acc_sum += (net(X.to(device)).argmax(dim=1) == y.to(device)).float().sum().cpu().item()
                # change back to train mode
                net.train() 

            n += y.shape[0]
    return acc_sum / n

Далее мы можем построитьtrain()метод, используемый для обучения нейронной сети:

def try_gpu(i=0):
    if torch.cuda.device_count() >= i + 1:
        return torch.device(f'cuda:{i}')
    return torch.device('cpu')

def train(net, train_iter, test_iter, batch_size, optimizer, num_epochs, device=try_gpu()):
    net = net.to(device)
    print("training on", device)
    loss = torch.nn.CrossEntropyLoss()
    batch_count = 0
    for epoch in range(num_epochs):
        train_l_sum, train_acc_sum, n = 0.0, 0.0, 0
        for X, y in train_iter:
            X = X.to(device)
            y = y.to(device)
            y_hat = net(X)
            l = loss(y_hat, y)
            optimizer.zero_grad()
            l.backward()
            optimizer.step()
            train_l_sum += l.cpu().item()
            train_acc_sum += (y_hat.argmax(dim=1) == y).sum().cpu().item()
            n += y.shape[0]
            batch_count += 1
        test_acc = evaluate_accuracy(test_iter, net)
        if epoch % 10 == 0:
            print(f'epoch {epoch + 1} : loss {train_l_sum / batch_count:.3f}, train acc {train_acc_sum / n:.3f}, test acc {test_acc:.3f}')

В основной логике всей программы задаем необходимые параметры, считываем обучающие и тестовые данные и запускаем обучение:

def main():

    batch_size = 256
    lr, num_epochs = 0.9, 100

    net = LeNet()
    optimizer = torch.optim.SGD(net.parameters(), lr=lr)
    
    # load data
    train_iter, test_iter = load_data_fashion_mnist(batch_size=batch_size)
    # train
    train(net, train_iter, test_iter, batch_size, optimizer, num_epochs)

резюме

  1. LeNet — простейшая сверточная нейронная сеть, состоящая из сверточной блочной части и полносвязной слойной части.
  2. Сверточный блок состоит из сверточного слоя и объединяющего слоя.

использованная литература

  1. LeCun, Y., Bottou, L., Bengio, Y., & Haffner, P. (1998). Gradient-based learning applied to document recognition. Proceedings of the IEEE, 86(11), 2278-2324.

  2. 2 come.love/chapter_con…

  3. Тан Шусен Что/погружение-в-Д…