Давайте учимся, соединяя теорию с кодом!

Согласно Книге глубокого обучения, автокодировщик - это нейронная сеть, которая обучена копировать свои входные данные в выходные. Внутри у него есть скрытый слой, который описывает код, используемый для представления ввода. Сеть можно рассматривать как состоящую из двух частей: кодирующей функции h = f (x) и декодера, который производит реконструкцию r = g (h).

Хорошо, хорошо, я знаю, о чем вы думаете! Просто еще один пост без надлежащего объяснения? Нет! Мы будем действовать не так . Давайте сделаем вдох и шаг за шагом соединим наши теоретические знания с кодом! Это поможет вам лучше понять и научиться интуитивно решать любую проблему машинного обучения в будущем.

Мы начнем с создания простого автокодировщика для сжатия набора данных MNIST. С помощью автокодировщиков мы передаем входные данные через кодировщик, который создает сжатое представление входных данных. Затем это представление проходит через декодер для восстановления входных данных. Как правило, кодер и декодер будут построены с использованием нейронных сетей, а затем обучены на примерах данных.

Но что на самом деле представляют собой кодеры и декодеры?

Общая структура автоэнкодера, отображение входа x на выход (называемое реконструкцией) «r» через внутреннее представление или код «h».
Автоэнкодер состоит из двух компонентов:
Кодировщик: Он имеет отображение от x до h, т.е. f (отображение x на h)
Декодер : имеет отображение из h в r, т.е. g (отображение h в r).

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

Итак, что на самом деле делает это «сжатое представление»?

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

Давайте построим простую сетевую архитектуру для кодировщика и декодера, чтобы понять автоэнкодер.

  • Мы всегда начинаем с импорта наших библиотек и получения набора данных.
  1. Преобразование данных в torch.FloatTensor
  2. загрузить обучающие и тестовые наборы данных
import torch
import numpy as np
from torchvision import datasets
import torchvision.transforms as transforms

# convert data to torch.FloatTensor
transform = transforms.ToTensor()

# load the training and test datasets
train_data = datasets.MNIST(root='data', train=True,
                                   download=True, transform=transform)
test_data = datasets.MNIST(root='data', train=False,
                                  download=True, transform=transform)
  • Затем, как обычно, создайте загрузчики обучающих и тестовых данных
  1. Количество подпроцессов, используемых для загрузки данных
  2. Сколько образцов в партии загружать
  3. Подготовьте загрузчики данных. Теперь, если у вас есть собственный набор данных, на котором вы хотите опробовать автоэнкодер, вам нужно будет создать загрузчик данных, специально предназначенный для этой цели. Я опубликовал блог, который поможет вам с этой целью: Как создать собственный загрузчик данных для вашего собственного набора данных.
# Create training and test dataloaders

# number of subprocesses to use for data loading
num_workers = 0
# how many samples per batch to load
batch_size = 20

# prepare data loaders
train_loader = torch.utils.data.DataLoader(train_data, batch_size=batch_size, num_workers=num_workers)
test_loader = torch.utils.data.DataLoader(test_data, batch_size=batch_size, num_workers=num_workers)
  • Визуализируйте данные. Теперь это необязательно, но всегда полезно проверить, правильно ли загружены ваши данные. Вы можете добиться этого,
  1. Получение одной партии обучающих образов
  2. Затем получаем одно изображение из партии
import matplotlib.pyplot as plt
%matplotlib inline
    
# obtain one batch of training images
dataiter = iter(train_loader)
images, labels = dataiter.next()
images = images.numpy()

# get one image from the batch
img = np.squeeze(images[0])

fig = plt.figure(figsize = (5,5)) 
ax = fig.add_subplot(111)
ax.imshow(img, cmap='gray')

В нашем случае вы должны увидеть что-то вроде:

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

Обратите внимание, что изображения в наборе данных MNIST имеют размер 28 * 28, поэтому мы обучим автокодировщик с этими изображениями, сведя их к 784 (т.е. 28 * 28 = 784) векторам длины. Кроме того, изображения из этого набора данных уже нормализованы, так что значения находятся в диапазоне от 0 до 1.

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

  • Архитектура модели: это самый важный шаг автоэнкодера, поскольку мы пытаемся достичь целей, которые совпадают с входными данными!
  1. Определите архитектуру NN:
  • ENCODER: кодировщик будет состоять из одного линейного слоя, где размеры глубины должны изменяться следующим образом: 784 входа - ›encoding_dim.

Теперь для тех из вас, кто хоть немного запутался в том, что такое размерность кодирования (encoding_dim), рассмотрите его как промежуточное измерение между вводом и выводом, которым можно управлять в соответствии с вашими потребностями, но его размер должен оставаться между входными и выходными размерами.

В приведенном ниже коде мы выбрали encoding_dim = 32, который по сути является нашим сжатым представлением!

import torch.nn as nn
import torch.nn.functional as F

# define the NN architecture
class Autoencoder(nn.Module):
    def __init__(self, encoding_dim):
        super(Autoencoder, self).__init__()
        ## encoder ##
        # linear layer (784 -> encoding_dim)
        self.fc1 = nn.Linear(28 * 28, encoding_dim)
        
        ## decoder ##
        # linear layer (encoding_dim -> input size)
        self.fc2 = nn.Linear(encoding_dim, 28*28)
        

    def forward(self, x):
        # add layer, with relu activation function
        x = F.relu(self.fc1(x))
        # output layer (sigmoid for scaling from 0 to 1)
        x = F.sigmoid(self.fc2(x))
        return x

# initialize the NN
encoding_dim = 32
model = Autoencoder(encoding_dim)
print(model)

  • Обучение: здесь я напишу небольшой код для обучения сети. Меня здесь не слишком интересует проверка, поэтому давайте просто посмотрим на потерю обучения и потерю теста позже.

Нас также не интересуют метки, в данном случае просто изображения, которые мы можем получить из train_loader. Поскольку мы сравниваем значения пикселей во входных и выходных изображениях, будет наиболее полезно использовать потерю, которая предназначена для задачи регрессии. Регрессия - это сравнение количеств, а не вероятностных значений.

# specify loss function
criterion = nn.MSELoss()

# specify loss function
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)

Это довольно простое обучение с PyTorch. Мы сглаживаем изображения, передаем их в автоэнкодер и записываем потери при обучении по мере продвижения.

# number of epochs to train the model
n_epochs = 20

for epoch in range(1, n_epochs+1):
    # monitor training loss
    train_loss = 0.0
    
    ###################
    # train the model #
    ###################
    for data in train_loader:
        # _ stands in for labels, here
        images, _ = data
        # flatten images
        images = images.view(images.size(0), -1)
        # clear the gradients of all optimized variables
        optimizer.zero_grad()
        # forward pass: compute predicted outputs by passing inputs to the model
        outputs = model(images)
        # calculate the loss
        loss = criterion(outputs, images)
        # backward pass: compute gradient of the loss with respect to model parameters
        loss.backward()
        # perform a single optimization step (parameter update)
        optimizer.step()
        # update running training loss
        train_loss += loss.item()*images.size(0)
            
    # print avg training statistics 
    train_loss = train_loss/len(train_loader)
    print('Epoch: {} \tTraining Loss: {:.6f}'.format(
        epoch, 
        train_loss
        ))

Проверка результатов:

  1. Получите одну партию тестовых изображений
  2. Получить образцы выходных данных
  3. Подготовьте изображения для показа
  4. Размер вывода преобразуется в пакет изображений.
  5. Используйте detach, если для вывода требуется_grad
  6. Постройте первые десять входных изображений, а затем восстановленные изображения
  7. Входные изображения в верхнем ряду, реконструкции внизу
# obtain one batch of test images
dataiter = iter(test_loader)
images, labels = dataiter.next()

images_flatten = images.view(images.size(0), -1)
# get sample outputs
output = model(images_flatten)
# prep images for display
images = images.numpy()

# output is resized into a batch of images
output = output.view(batch_size, 1, 28, 28)
# use detach when it's an output that requires_grad
output = output.detach().numpy()

# plot the first ten input images and then reconstructed images
fig, axes = plt.subplots(nrows=2, ncols=10, sharex=True, sharey=True, figsize=(25,4))

# input images on top row, reconstructions on bottom
for images, row in zip([images, output], axes):
    for img, ax in zip(images, row):
        ax.imshow(np.squeeze(img), cmap='gray')
        ax.get_xaxis().set_visible(False)
        ax.get_yaxis().set_visible(False)

Если автоэнкодеру удается просто научиться везде устанавливать g (f (x)) = x, то это не особенно полезно. Вместо этого автоэнкодеры не могут научиться идеально копировать. Обычно они ограничены способами, которые позволяют им копировать только приблизительно, и копировать только ввод, который напоминает данные обучения. Поскольку модель вынуждена определять приоритеты, какие аспекты входных данных следует копировать, она часто узнает полезные свойства данных.

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

Если вы хотите опробовать код самостоятельно, обратитесь сюда: Линейные автоэнкодеры