Лекция 3. Примеры обучения и тестирования простейших нейронных сетей с использованием PyTorch¶

1. Предварительная подготовка окружения¶

1.1. Загрузка пакетов¶

In [1]:
import torch
import torchmetrics
import torchvision
import torchvision.datasets
import torchvision.transforms
import torch.utils.data
import torch.nn
import os
from matplotlib import pyplot as plot
from time import time

1.2. Установка параметров, обеспечивающих параллелизм¶

In [2]:
# Количество потоков, используемых для параллелизма между независимыми операциями
# По умолчанию параметр не установлен и выбирается PyTorch автоматически
num_inter_threads = 2
# Количество потоков, используемых для параллелизма внутри операций
# По умолчанию параметр не установлен и выбирается PyTorch автоматически
num_intra_threads = 2

def set_thread_num(num_inter_threads, num_intra_threads):
    def validate(num):
        if num < 0:
            raise ValueError(f'Incorrect thread count: {num}')

    if num_inter_threads:
        validate(num_inter_threads)
        torch.set_num_interop_threads(num_inter_threads)
        print(f'The number of threads for inter-op parallelism: {num_inter_threads}')
    if num_intra_threads:
        validate(num_intra_threads)
        torch.set_num_threads(num_intra_threads)
        print(f'The number of threads for intra-op parallelism: {num_intra_threads}')


set_thread_num(num_inter_threads, num_intra_threads)
The number of threads for inter-op parallelism: 2
The number of threads for intra-op parallelism: 2

2. Загрузка набора данных MNIST¶

2.1. Скачивание данных в текущую директорию с использованием пакета torchvision.datasets¶

In [3]:
dir_name = os.getcwd() # получение текущей директории

# Чтение тренировочной и тестовой выборок набора данных MNIST.
# Данные представляются в виде пар (tuple), где первый элемент - 
# изображение в формате PIL.Image.Image, а второй - целочисленная
# метка класса.Параметр transform обеспечивает преобразование
# изображений в формат torch.Tensor для последующей работы
train_dataset = torchvision.datasets.MNIST(root = dir_name, train = True, download = True,
                                           transform = torchvision.transforms.ToTensor())
test_dataset = torchvision.datasets.MNIST(root = dir_name, train = False, download = True,
                                          transform = torchvision.transforms.ToTensor())
Downloading http://yann.lecun.com/exdb/mnist/train-images-idx3-ubyte.gz
Failed to download (trying next):
HTTP Error 403: Forbidden

Downloading https://ossci-datasets.s3.amazonaws.com/mnist/train-images-idx3-ubyte.gz
Downloading https://ossci-datasets.s3.amazonaws.com/mnist/train-images-idx3-ubyte.gz to C:\Users\kustikova.v\Documents\MyDocs\ITLab\projects\tvm\tensor-compilers-intro-course\lectures\sources\MNIST\raw\train-images-idx3-ubyte.gz
100.0%
Extracting C:\Users\kustikova.v\Documents\MyDocs\ITLab\projects\tvm\tensor-compilers-intro-course\lectures\sources\MNIST\raw\train-images-idx3-ubyte.gz to C:\Users\kustikova.v\Documents\MyDocs\ITLab\projects\tvm\tensor-compilers-intro-course\lectures\sources\MNIST\raw

Downloading http://yann.lecun.com/exdb/mnist/train-labels-idx1-ubyte.gz
Failed to download (trying next):
HTTP Error 403: Forbidden

Downloading https://ossci-datasets.s3.amazonaws.com/mnist/train-labels-idx1-ubyte.gz
Downloading https://ossci-datasets.s3.amazonaws.com/mnist/train-labels-idx1-ubyte.gz to C:\Users\kustikova.v\Documents\MyDocs\ITLab\projects\tvm\tensor-compilers-intro-course\lectures\sources\MNIST\raw\train-labels-idx1-ubyte.gz
100.0%
Extracting C:\Users\kustikova.v\Documents\MyDocs\ITLab\projects\tvm\tensor-compilers-intro-course\lectures\sources\MNIST\raw\train-labels-idx1-ubyte.gz to C:\Users\kustikova.v\Documents\MyDocs\ITLab\projects\tvm\tensor-compilers-intro-course\lectures\sources\MNIST\raw

Downloading http://yann.lecun.com/exdb/mnist/t10k-images-idx3-ubyte.gz
Failed to download (trying next):
HTTP Error 403: Forbidden

Downloading https://ossci-datasets.s3.amazonaws.com/mnist/t10k-images-idx3-ubyte.gz
Downloading https://ossci-datasets.s3.amazonaws.com/mnist/t10k-images-idx3-ubyte.gz to C:\Users\kustikova.v\Documents\MyDocs\ITLab\projects\tvm\tensor-compilers-intro-course\lectures\sources\MNIST\raw\t10k-images-idx3-ubyte.gz
100.0%
Extracting C:\Users\kustikova.v\Documents\MyDocs\ITLab\projects\tvm\tensor-compilers-intro-course\lectures\sources\MNIST\raw\t10k-images-idx3-ubyte.gz to C:\Users\kustikova.v\Documents\MyDocs\ITLab\projects\tvm\tensor-compilers-intro-course\lectures\sources\MNIST\raw

Downloading http://yann.lecun.com/exdb/mnist/t10k-labels-idx1-ubyte.gz
Failed to download (trying next):
HTTP Error 403: Forbidden

Downloading https://ossci-datasets.s3.amazonaws.com/mnist/t10k-labels-idx1-ubyte.gz
Downloading https://ossci-datasets.s3.amazonaws.com/mnist/t10k-labels-idx1-ubyte.gz to C:\Users\kustikova.v\Documents\MyDocs\ITLab\projects\tvm\tensor-compilers-intro-course\lectures\sources\MNIST\raw\t10k-labels-idx1-ubyte.gz
100.0%
Extracting C:\Users\kustikova.v\Documents\MyDocs\ITLab\projects\tvm\tensor-compilers-intro-course\lectures\sources\MNIST\raw\t10k-labels-idx1-ubyte.gz to C:\Users\kustikova.v\Documents\MyDocs\ITLab\projects\tvm\tensor-compilers-intro-course\lectures\sources\MNIST\raw


2.2. Проверка корректности загрузки данных¶

In [4]:
# функция для демонстрации примеров изображений
def show_images(images, title):
    num_showed_imgs_x = 5
    num_showed_imgs_y = 5
    
    figsize = (2, 2)
    fig, axes = plot.subplots(num_showed_imgs_y, num_showed_imgs_x, figsize = figsize)
    fig.suptitle(title)
    plot.setp(plot.gcf().get_axes(), xticks = [], yticks = [])
    for i, ax in enumerate(axes.flat):
        # images[i][0] - многомерный массив типа torch.Tensor
        # images[i][1] - число типа numpy.int32
        img = images[i][0].numpy().transpose(1, 2, 0).squeeze(axis = 2)
        ax.imshow(img, cmap = 'gray')
        
# Логирование информации о загруженных данных
print('Number of train samples: {}'.format(len(train_dataset)))
show_images(train_dataset, 'Train samples')

print('Number of test samples: {}'.format(len(test_dataset)))
show_images(test_dataset, 'Test samples')

# Создание объектов для последовательной загрузки пачек
# из тренировочной и тестовой выборок
train_batch_size = 64 # размер обрабатываемой пачки данных
train_data_loader = torch.utils.data.DataLoader(train_dataset, batch_size = train_batch_size,
                                                shuffle = True)
test_batch_size = 64
test_data_loader = torch.utils.data.DataLoader(test_dataset, batch_size = test_batch_size,
                                               shuffle = False)
Number of train samples: 60000
Number of test samples: 10000
No description has been provided for this image
No description has been provided for this image

3. Создание модели, соответствующей логистической регрессии¶

In [5]:
image_resolution = 28 * 28 # 28*28=784 - количество входных нейронов
num_classes = 10 # количество выходных нейронов

# Создание класса сети, соответствующей логистической регрессии
class LogisticRegressionModel(torch.nn.Module):
    # Объявление конструктора
    def __init__(self, input_dim, output_dim):
        super(LogisticRegressionModel, self).__init__()
        # Создание полносвязного слоя (softmax включается в вычисление кросс-энтропии)
        self.linear = torch.nn.Linear(input_dim, output_dim)

    # Переопределение метода, вызываемого в процессе прямого прохода
    def forward(self, x):
        out = self.linear(x)
        return out

# Создание объекта разработанного класса
logreg_nn = LogisticRegressionModel(image_resolution, num_classes)

# Логирование информации о параметрах модели
print('Weight matrix: {}. Biases: {}'.format(logreg_nn.linear.weight.shape, 
                                             logreg_nn.linear.bias.shape))
Weight matrix: torch.Size([10, 784]). Biases: torch.Size([10])

4. Создание однослойной сверточной сети¶

In [6]:
class ConvolutionalNeuralNetwork(torch.nn.Module):
    # Объявление конструктора
    def __init__(self):
        super(ConvolutionalNeuralNetwork, self).__init__()
        # Свертка. Размер результирующей карты признаков:
        # 10 x ((28-3)/1+1) x ((28-3)/1+1) = 10 x 26 x 26
        self.conv = torch.nn.Conv2d(in_channels = 1, out_channels = 10, 
                                    kernel_size = 3, stride = 1)
        # Функция активации. Размер результирующей карты признаков:
        # 10 x 26 x 26
        self.activation = torch.nn.ReLU()
        # Пространственное объединение по максимуму. Размер результирующей карты признаков:
        # 10 x ((26-2)/1+1) x ((26-2)/1+1) = 10 x 25 x 25
        self.pooling = torch.nn.MaxPool2d(kernel_size = 2, stride = 1)
        # Создание полносвязного слоя (softmax включается в вычисление кросс-энтропии)
        self.linear = torch.nn.Linear(10 * 25 * 25, 10)

    # Переопределение метода, вызываемого в процессе прямого прохода
    def forward(self, x):
        # Свертка
        out = self.conv(x)
        # Функция активации
        out = self.activation(out)
        # Пространственное объединение
        out = self.pooling(out)
        # Изменение формата хранения тензора из (B, C, W, H) в (B, C*W*H)
        out = out.view(out.size(0), -1)
        # Полносвязный слой
        out = self.linear(out)
        return out

# Создание объекта разработанного класса
cnn_model = ConvolutionalNeuralNetwork()

# Логирование информации о параметрах модели
print('Parameters of convolutions:\n1. Kernels = {}\n2. Biases = {}'.
      format(cnn_model.conv.weight.shape, cnn_model.conv.bias.shape))
print('Parameters of fully-connected layer:\n1. Weight matrix = {}\n2. Biases = {}'.
      format(cnn_model.linear.weight.shape, cnn_model.linear.bias.shape))
Parameters of convolutions:
1. Kernels = torch.Size([10, 1, 3, 3])
2. Biases = torch.Size([10])
Parameters of fully-connected layer:
1. Weight matrix = torch.Size([10, 6250])
2. Biases = torch.Size([10])

5. Обучение нейронной сети¶

5.1. Реализация функции обучения¶

In [7]:
# Функции преобразования загрруженных данных к формату входа сети
def fcnn_transform_data(images):
    # Преобразование тензора [B, C, W, H] к формату [B, W * H]
    # (images.shape=[B, C, W, H], B - размер пачки, C=1 - число каналов
    # W, H - ширина и высота изображений в пачке)
    return images.view(-1, image_resolution)

def cnn_transform_data(images):
    # Для сверточной сети данные подаются в виде трехмерного тензора
    # (в исходном виде) [B, W, H]
    return images


def train_model(model, train_data_loader, transform_data, learning_rate=0.1, num_epochs=10):
    # Выбор устройства для вычислений
    device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
    model.to(device)
    
    print(f'Device for training: {device}')
    
    # Функция ошибки на этапе обучения
    loss_function = torch.nn.CrossEntropyLoss()
    
    # Метод оптимизации для обучения параметров
    optimizer = torch.optim.SGD(model.parameters(), lr = learning_rate)

    # Метрика качества решения задачи
    metric = torchmetrics.classification.Accuracy(task="multiclass", num_classes=10)
    metric.to(device)

    training_time = 0
    for epoch in range(num_epochs):  # проход по эпохам
        duration = 0.0
        
        for i, (images, labels) in enumerate(train_data_loader): # получение пачки тренировочных данных
            # Начало замера времени
            tstart = time()
            
            images = transform_data(images).requires_grad_().to(device)
            labels = labels.to(device)
            # Прямой проход
            outputs = model(images) # вычисление выхода сети
            loss = loss_function(outputs, labels) # вычисление функции ошибки, loss.item() дает значение
            accuracy = metric(outputs, labels) # вычисление точности для пачки тренировочных данных
            # Обратный проход
            optimizer.zero_grad() # обнуление всех вычисляемых градиентов
            loss.backward() # вычисление градиента функции ошибки
            optimizer.step() # обновление параметров модели
            
            # Конец замера времени
            tfinish = time()
            
            duration += (tfinish - tstart)

        # Логирование метрики качества на тренировочных данных по завершении эпохи
        accuracy = metric.compute()
        print(f'Epoch[{epoch}]: train accuracy = {accuracy:.3f}, duration = {duration:.3f} s, '
              f'FPS = {len(train_data_loader.dataset) / duration:.3f} frames/s')
        training_time += duration

    return training_time

5.2. Обучение логистической регрессии¶

In [8]:
print('Logistic regression model')
num_epochs=10
training_time = train_model(logreg_nn, train_data_loader, fcnn_transform_data,
                            num_epochs=num_epochs)
print(f'Training time: {training_time:.3f} s')
Logistic regression model
Device for training: cpu
Epoch[0]: train accuracy = 0.877, duration = 2.594 s, FPS = 23132.056 frames/s
Epoch[1]: train accuracy = 0.892, duration = 3.090 s, FPS = 19417.975 frames/s
Epoch[2]: train accuracy = 0.898, duration = 2.283 s, FPS = 26284.146 frames/s
Epoch[3]: train accuracy = 0.902, duration = 2.305 s, FPS = 26033.729 frames/s
Epoch[4]: train accuracy = 0.906, duration = 4.258 s, FPS = 14090.011 frames/s
Epoch[5]: train accuracy = 0.908, duration = 2.486 s, FPS = 24137.992 frames/s
Epoch[6]: train accuracy = 0.910, duration = 3.002 s, FPS = 19988.143 frames/s
Epoch[7]: train accuracy = 0.911, duration = 2.803 s, FPS = 21409.314 frames/s
Epoch[8]: train accuracy = 0.912, duration = 1.917 s, FPS = 31294.899 frames/s
Epoch[9]: train accuracy = 0.914, duration = 2.021 s, FPS = 29695.179 frames/s
Training time: 26.757 s

5.3. Обучение сверточной нейронной сети¶

In [9]:
print('Convolutional neural network')
num_epochs = 10
training_time = train_model(cnn_model, train_data_loader, cnn_transform_data,
                            num_epochs=num_epochs)
print(f'Training time: {training_time:.3f} s')
Convolutional neural network
Device for training: cpu
Epoch[0]: train accuracy = 0.923, duration = 32.598 s, FPS = 1840.615 frames/s
Epoch[1]: train accuracy = 0.948, duration = 32.928 s, FPS = 1822.184 frames/s
Epoch[2]: train accuracy = 0.958, duration = 26.939 s, FPS = 2227.230 frames/s
Epoch[3]: train accuracy = 0.964, duration = 28.062 s, FPS = 2138.153 frames/s
Epoch[4]: train accuracy = 0.968, duration = 28.570 s, FPS = 2100.105 frames/s
Epoch[5]: train accuracy = 0.971, duration = 40.987 s, FPS = 1463.896 frames/s
Epoch[6]: train accuracy = 0.973, duration = 37.145 s, FPS = 1615.290 frames/s
Epoch[7]: train accuracy = 0.975, duration = 28.113 s, FPS = 2134.215 frames/s
Epoch[8]: train accuracy = 0.977, duration = 27.522 s, FPS = 2180.081 frames/s
Epoch[9]: train accuracy = 0.978, duration = 29.464 s, FPS = 2036.353 frames/s
Training time: 312.328 s

6. Тестирование обученной модели¶

6.1. Функция вычисления точности для набора данных¶

In [10]:
def get_accuracy(data_loader, transform_data, model):
    # Выбор устройства для вычислений
    device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
    model.to(device)
    
    print(f'Device for testing: {device}')
    
    # Метрика качества решения задачи
    metric = torchmetrics.classification.Accuracy(task="multiclass", num_classes=10)
    metric.to(device)

    # Время начала вывода
    tstart = time()
    for i, (images, labels) in enumerate(data_loader): # получение пачки данных
        images = transform_data(images).requires_grad_().to(device)
        labels = labels.to(device)

        outputs = model(images) # вычисление выхода сети
        accuracy = metric(outputs, labels) # вычисление точности для пачки данных
    # Время окончания вывода
    tfinish = time()

    duration = tfinish - tstart
    return metric.compute(), duration, len(data_loader.dataset) / duration

6.2. Определение точности для логистической регрессии¶

In [11]:
print('Logistic regression model')
accuracy, duration, fps = get_accuracy(test_data_loader, fcnn_transform_data, logreg_nn)
print(f'Test accuracy:                  {accuracy:.3f}')
print(f'Inference time on test dataset: {duration:.3f} s')
print(f'FPS:                            {fps:.3f} frames/s')
Logistic regression model
Device for testing: cpu
Test accuracy:                  0.922
Inference time on test dataset: 2.399 s
FPS:                            4168.068 frames/s

6.3. Определение точности для сверточной сети¶

In [12]:
print('Convolutional neural network')
accuracy, duration, fps = get_accuracy(test_data_loader, cnn_transform_data, cnn_model)
print(f'Test accuracy:                  {accuracy:.3f}')
print(f'Inference time on test dataset: {duration:.3f} s')
print(f'FPS:                            {fps:.3f} frames/s')
Convolutional neural network
Device for testing: cpu
Test accuracy:                  0.984
Inference time on test dataset: 5.084 s
FPS:                            1966.836 frames/s