Занятие 8

Лабораторная 8

In [ ]:
import numpy as np
import seaborn as sns
from matplotlib import pyplot as plt
from sklearn.model_selection import train_test_split

import torch
from torch import nn
from torch.nn import functional as F
from torch.utils.data import TensorDataset, DataLoader

from torchvision import transforms as tfs
import os
from torchvision.datasets import MNIST

1.Свёртка

Сверточная операция(свёртка) в сверточной нейронной сети (CNN) используется для извлечения признаков из входных данных, таких как изображения. Эта операция выполняется с помощью фильтров (ядер).

Двумерная свертка

В случае изображений, мы имеем двумерное изображение $I$ с размерностью$H \times W$ и двумерное ядро (или фильтр) $F$ размерностью $M \times N$.

Двумерная свертка изображения с ядром выполняется путем скольжения фильтра $F$ по изображению и вычисления суммы поэлементных произведений пикселей изображения и элементов фильтра:

$(I * F)[i, j]$ = $\sum_{m=0}^{M-1} \sum_{n=0}^{N-1} I[i+m, j+n] \cdot F[m, n] $

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

Скорее всего сейчас ничего непонятно, давайте рассмотрим пример.

Предположим, у нас есть следующее изображение (представленное в виде матрицы): \begin{bmatrix} 1 & 2 & 3 & 4\\ 2 & 1 & 1 & 3\\ 4 & 3 & 1 & 0\\ 0 & 0 & 1 & 5\\ \end{bmatrix}

$a_{11} = 1, a_{12} = 2, a_{13} = 3, a_{14} = 4, a_{21} = 2$ и т.д

И пусть у нас будет следующее ядро свертки (или фильтр):

\begin{bmatrix} 1 & 2 \\ 7 & 1 \\ \end{bmatrix}

$b_{11} = 1, b_{12} = 2, b_{21} = 7, b_{22} = 1$

Выполним свертку изображения с этим ядром шаг за шагом.

Результатом свёртки будет матрица размером 3 $\times$ 3.

\begin{bmatrix} с_{11} & c_{12} & c_{13} \\ c_{21} & c_{22} & c_{23} \\ c_{31} & c_{32} & c_{33} \\ \end{bmatrix}

$c_{11} = a_{11} * b_{11} + a_{12} * b_{12} + a_{21} * b_{21} + a_{22} * b_{22}$

$c_{12} = a_{12} * b_{11} + a_{13} * b_{12} + a_{22} * b_{21} + a_{23} * b_{22}$

$c_{13} = a_{13} * b_{11} + a_{14} * b_{12} + a_{23} * b_{21} + a_{24} * b_{22}$

$c_{21} = a_{21} * b_{11} + a_{22} * b_{12} + a_{31} * b_{21} + a_{32} * b_{22}$

$c_{22} = a_{22} * b_{11} + a_{23} * b_{12} + a_{32} * b_{21} + a_{33} * b_{22}$

$c_{23} = a_{23} * b_{11} + a_{24} * b_{12} + a_{33} * b_{21} + a_{34} * b_{22}$

$c_{31} = a_{31} * b_{11} + a_{32} * b_{12} + a_{41} * b_{21} + a_{42} * b_{22}$

$c_{32} = a_{32} * b_{11} + a_{33} * b_{12} + a_{42} * b_{21} + a_{43} * b_{22}$

$c_{33} = a_{33} * b_{11} + a_{34} * b_{12} + a_{43} * b_{21} + a_{44} * b_{22}$

Процесс свёртки обычно включает в себя два основных гиперпараметра:

  • Размер окна свёртки: Это размер ядра. В примере выше размер ядра 2$×$2.

  • Шаг (Stride): Это расстояние между центрами соседних окон ядер. В примере выше stride = 1.

image.png

Задача1 (1 балл)

Дано входное изображение(двумерный тензор):

\begin{bmatrix} 1 & 2 & 3 & 4 & 6\\ 2 & 1 & 1 & 3 & 1\\ 4 & 3 & 1 & 0 & 0\\ 0 & 0 & 1 & 5 & 7\\ 3 & 10 & 4 & 5 & 1\\ \end{bmatrix}

Дана свёртка:

\begin{bmatrix} 1 & 0 & 0\\ 0 & 1 & 1\\ 1 & 0 & 1\\ \end{bmatrix}

Примените данную свёртку к изображению.

2.Pooling

Pooling (или пулинг) в сверточных нейронных сетях (CNN) используется для уменьшения размерности признаковых карт, получаемых после сверточных слоев. Он помогает упрощать и концентрировать информацию, уменьшая объем вычислений и сделав модель более устойчивой к масштабным вариациям входных данных.

Основные типы пулинга в CNN:

  1. Max Pooling: Для каждой области во входной признаковой карте выбирается максимальное значение. Max pooling помогает выделить самые активные признаки из пространственных областей.

  2. Average Pooling: Для каждой области входной признаковой карты вычисляется среднее значение. Average pooling позволяет усреднить информацию из пространственных областей.

Процесс пулинга обычно включает в себя два основных параметра:

  • Размер окна пулинга: Это размер области, для которой вычисляется максимальное или среднее значение. Обычно используются окна размером 2x2 или 3x3.

  • Шаг (Stride): Это расстояние между центрами соседних окон пулинга. Обычно используется шаг 2, что делает пулинг-слои в полтора раза меньше по размерам.

Рассмотрим пример:

Исходная матрица: \begin{bmatrix} 1 & 1 & 1 & 0 \\ 3 & 2 & 2 & 4 \\ 3 & 2 & 3 & 2 \\ 0 & 1 & 3 & 3 \\ \end{bmatrix}

Применение Max Pooling с окном 2x2 и шагом 2:

  1. Выбираем первую область 2x2 из исходной матрицы: \begin{bmatrix} 1 & 1 \\ 3 & 2 \\ \end{bmatrix} Максимальное значение здесь: 3

  2. Выбираем вторую область 2x2 (с пропуском каждой второй строки и столбца) из исходной матрицы: \begin{bmatrix} 1 & 0 \\ 2 & 1 \\ \end{bmatrix} Максимальное значение здесь: 2

  3. Выбираем третью область 2x2 (с пропуском каждой второй строки и столбца) из исходной матрицы: \begin{bmatrix} 3 & 2 \\ 0 & 1 \\ \end{bmatrix} Максимальное значение здесь: 3

Таким образом, после применения операции Max Pooling с окном 2x2 и шагом 2 получаем новую матрицу: \begin{bmatrix} 3 & 2 \\ 3 & 3 \\ \end{bmatrix}

pooling.png

Задача2 (2 балла)

Реализуйте функцию, принимающую на вход матрицу, свёртку для неё(размера 2 на 2) и вычисляющую результат свёртки с stride = 1.

3.Пэддинг

Пэддинг (padding) - это процесс добавления нулей или других значения по краям входной матрицы в глубоком обучении. Пэддинг обычно используется в сверточных нейронных сетях перед применением операции свертки или пулинга. Основная цель пэддинга состоит в том, чтобы сохранить размерность входных данных или упростить реализацию операций свертки и пулинга.

Задача3 (1 балл)

Реализуйте функцию, принимающую на вход матрицу и осуществяющую padding.

4.Свёрточная Нейронная сеть

Свёрточная нейронная сеть (Convolutional Neural Network, CNN) - это особый вид нейронных сетей, разработанный для обработки и анализа многомерных данных, таких как изображения. CNN успешно применяются в компьютерном зрении, распознавании образов, анализе временных рядов и других областях, где важна обработка многомерных данных.

Основные компоненты свёрточной нейронной сети:

  1. Сверточный слой (Convolutional Layer): Этот слой выполняет операцию свертки, когда ядро фильтра применяется к входным данным для извлечения признаков.
  2. Пулинговый слой (Pooling Layer): Данный слой уменьшает размерность пространства признаков путем объединения (например, максимум или среднее значения в каждой области).
  3. Полносвязные слои (Fully Connected Layers): Эти слои используются для принятия решений на основе признаков, извлеченных предыдущими слоями.
  4. Функции активации (Activation Functions): Обычно применяются функции активации, такие как ReLU (Rectified Linear Unit), чтобы вводить нелинейность в сеть и улучшить ее способность обобщения.

Преимущества свёрточных нейронных сетей:

  1. Работа с пространственной структурой данных: CNN способны учитывать пространственную локальность и общие закономерности во входных данных, таких как пиксели изображений.
  2. Работа с сокращенной размерностью: Пулинговые слои позволяют уменьшить размерность пространства признаков, упрощая задачу обработки.
  3. Способность извлекать признаки: Свёрточные слои способны извлекать локальные признаки из входных данных, позволяя сети автоматически выявлять особенности в данных.
In [ ]:
data_tfs = tfs.Compose([
    tfs.ToTensor(),
    tfs.Normalize((0.5), (0.5))
])

# install for train and test
root = './'
train_dataset = MNIST(root, train=True,  transform=data_tfs, download=True)
val_dataset  = MNIST(root, train=False, transform=data_tfs, download=True)
Downloading http://yann.lecun.com/exdb/mnist/train-images-idx3-ubyte.gz
Downloading http://yann.lecun.com/exdb/mnist/train-images-idx3-ubyte.gz to ./MNIST/raw/train-images-idx3-ubyte.gz
100%|██████████| 9912422/9912422 [00:00<00:00, 85762725.97it/s]
Extracting ./MNIST/raw/train-images-idx3-ubyte.gz to ./MNIST/raw

Downloading http://yann.lecun.com/exdb/mnist/train-labels-idx1-ubyte.gz
Downloading http://yann.lecun.com/exdb/mnist/train-labels-idx1-ubyte.gz to ./MNIST/raw/train-labels-idx1-ubyte.gz
100%|██████████| 28881/28881 [00:00<00:00, 29785024.30it/s]
Extracting ./MNIST/raw/train-labels-idx1-ubyte.gz to ./MNIST/raw

Downloading http://yann.lecun.com/exdb/mnist/t10k-images-idx3-ubyte.gz
Downloading http://yann.lecun.com/exdb/mnist/t10k-images-idx3-ubyte.gz to ./MNIST/raw/t10k-images-idx3-ubyte.gz
100%|██████████| 1648877/1648877 [00:00<00:00, 23144159.31it/s]
Extracting ./MNIST/raw/t10k-images-idx3-ubyte.gz to ./MNIST/raw

Downloading http://yann.lecun.com/exdb/mnist/t10k-labels-idx1-ubyte.gz
Downloading http://yann.lecun.com/exdb/mnist/t10k-labels-idx1-ubyte.gz to ./MNIST/raw/t10k-labels-idx1-ubyte.gz
100%|██████████| 4542/4542 [00:00<00:00, 5325839.75it/s]
Extracting ./MNIST/raw/t10k-labels-idx1-ubyte.gz to ./MNIST/raw

In [ ]:
train_dataloader =  DataLoader(train_dataset, batch_size=128)
valid_dataloader =  DataLoader(val_dataset, batch_size=128)

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

LeNet — это структура(архитектура) сверточной нейронной сети, предложенная в 1998 году.

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

Входное изображение размером 28(height)$×$28(widht)$×$(1 channel).

  1. Свёртка1(ядро: 5$×$5, пэддинг: 2, каналов на выходе: 6). На выходе: 28(height)$×$28(widht)$×$(6 channels)

  2. Pooling1(ядро: 2$×$2, stride: 2). На выходе: 14(height)$×$14(widht)$×$(6 channels)

  3. Свёртка2(ядро: 5$×$5, пэддинг: 0, каналов на выходе: 16). На выходе: 10(height)$×$10(widht)$×$(16 channels)

  4. Pooling2(ядро: 2$×$2, stride: 2). На выходе: 5(height)$×$5(widht)$×$(16 channels)

  5. Линейный слой1 (120 нейронов)

  6. Линейный слой2 (84 нейрона)

  7. Выходной слой (10 нейронов)

После Свёртки1 и Свёртки два следует функция активации.

После Pooling2 следует flatten.

Параметры LeNet

Поскольку обучение нейронной сети это поиск оптимальных параметров, давайте посчитаем количество параметров (весов) LeNet.

Свёртка1: 6 ядер размера 5$×$5 + 6 параметров bias = 156.

Pooling1: параметров нет!

Свёртка2: 16 ядер размера 5$×$5$×$6 + 16 параметров bias = 2416.

Pooling2: параметров нет!

Линейный слой1: входной слой: 5$×$5$×$16 = 400, выходной: 120. Число параметров 400$×$120 + 120 = 48120.

Линейный слой2: входной слой: 120, выходной: 84. Число параметров 120$×$84 + 84 = 10164.

Выходной слой: входной слой: 84, выходной: 10. Число параметров 84$×$10 + 10 = 850.

Итоговое число параметров: 156 + 2416 + 48120 + 10164 + 850 = 61706.

Реализуем архитектуру на python в виде класса LeNet.

In [ ]:
class LeNet(nn.Module):
    def __init__(self):
        super(LeNet, self).__init__()
        # Свёртка1
        self.conv1 = nn.Conv2d(in_channels=1, out_channels=6, kernel_size=5, padding = 2)
        # Свёртка2
        self.conv2 = nn.Conv2d(in_channels=6, out_channels=16, kernel_size=5)
        # Pooling1
        self.pool1 = nn.MaxPool2d(kernel_size=2, stride = 2)
        # Pooling2
        self.pool2 = nn.MaxPool2d(kernel_size=2, stride = 2)
        # Линейный слой1
        self.fc1 = nn.Linear(16 * 5 * 5, 120)
        # Линейный слой2
        self.fc2 = nn.Linear(120, 84)
        # Выходной слой
        self.fc3 = nn.Linear(84, 10)

    def forward(self, x):
        # Свёртка1 + активация Тангес
        x = F.tanh(self.conv1(x))
        # Pooling1
        x = self.pool1(x)
        # Свёртка1 + активация Тангес
        x = F.tanh(self.conv2(x))
        # Pooling2
        x = self.pool2(x)
        # Flatten
        size_ = int(x.nelement() / x.shape[0])
        x = x.view(-1, size_)
        # Линейный слой1 + активация Тангес
        x = F.tanh(self.fc1(x))
        # Линейный слой2 + активация Тангес
        x = F.tanh(self.fc2(x))
        # Выходной слой
        x = self.fc3(x)
        return x
In [ ]:
device = "cuda" if torch.cuda.is_available() else "cpu"
In [ ]:
model = LeNet().to(device)
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters())
loaders = {"train": train_dataloader, "valid": valid_dataloader}

Обратите внимание! Мы используем .to(device), что означает, что мы перемещаем наши объекты на GPU и обучаемся там же.

In [ ]:
max_epochs = 10
accuracy = {"train": [], "valid": []}
for epoch in range(max_epochs):
    for k, dataloader in loaders.items():
        epoch_correct = 0
        epoch_all = 0
        for x_batch, y_batch in dataloader:
            if k == "train":
                model.train()
                optimizer.zero_grad()
                outp = model(x_batch.to(device))
            else:
                model.eval()
                with torch.no_grad():
                    outp = model(x_batch.to(device))
            preds = outp.argmax(-1)
            correct =  (preds == y_batch).sum()
            all = y_batch.shape[0]
            epoch_correct += correct.item()
            epoch_all += all
            if k == "train":
                loss = criterion(outp, y_batch.to(device))
                loss.backward()
                optimizer.step()
        if k == "train":
            print(f"Epoch: {epoch+1}")
        print(f"Loader: {k}. Accuracy: {epoch_correct/epoch_all}")
        accuracy[k].append(epoch_correct/epoch_all)
Epoch: 1
Loader: train. Accuracy: 0.9143833333333333
Loader: valid. Accuracy: 0.9705
Epoch: 2
Loader: train. Accuracy: 0.9746833333333333
Loader: valid. Accuracy: 0.9783
Epoch: 3
Loader: train. Accuracy: 0.9833
Loader: valid. Accuracy: 0.9792
Epoch: 4
Loader: train. Accuracy: 0.9875
Loader: valid. Accuracy: 0.9803
Epoch: 5
Loader: train. Accuracy: 0.9903833333333333
Loader: valid. Accuracy: 0.9822
Epoch: 6
Loader: train. Accuracy: 0.9930333333333333
Loader: valid. Accuracy: 0.9813
Epoch: 7
Loader: train. Accuracy: 0.99485
Loader: valid. Accuracy: 0.9806
Epoch: 8
Loader: train. Accuracy: 0.9956333333333334
Loader: valid. Accuracy: 0.9806
Epoch: 9
Loader: train. Accuracy: 0.9964833333333334
Loader: valid. Accuracy: 0.9842
Epoch: 10
Loader: train. Accuracy: 0.99625
Loader: valid. Accuracy: 0.9832

Вспомните, какую точность давали другие алгоритмы на данном датасете. Свёрточная нейронная сеть показала лучший результат!

Задача (3 балла)

Для датасета CIFAR10, постройте и обучите следующую нейронную сеть.

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

Входное изображение размером 32(height)$×$32(widht)$×$(3 channel).

  1. Свёртка1(ядро: 3$×$3, пэддинг: 0, каналов на выходе: 6). На выходе: ВАШ ОТВЕТ.

  2. Pooling1(ядро: 2$×$2, stride: 2). На выходе: ВАШ ОТВЕТ.

  3. Свёртка2(ядро: 3$×$3, пэддинг: 0, каналов на выходе: 12). На выходе: ВАШ ОТВЕТ.

  4. Pooling2(ядро: 2$×$2, stride: 2). На выходе: ВАШ ОТВЕТ.

  5. Линейный слой1 (256 нейронов)

  6. Линейный слой2 (128 нейрона)

  7. Линейный слой3 (64 нейрона)

  8. Выходной слой (10 нейронов)

  • После Свёртки1 и Свёртки два следует функция активации: ReLU.

  • После Pooling2 следует flatten.

Сколько параметров имеет данная архитектура? ВАШ ОТВЕТ.

Если всё сделает правильно, то легко получите точность выше 60 на 10 эпохах!

In [ ]:

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

Рассмотрите файл(AlexNet) во вложении (скачать файл AlexNet.jpg).

Задача 5(3 балла)

Реализуйте класс, соответствующий архитектуре AlexNet