Давайте учимся, соединяя теорию с кодом!
Согласно Книге глубокого обучения, автокодировщик - это нейронная сеть, которая обучена копировать свои входные данные в выходные. Внутри у него есть скрытый слой, который описывает код, используемый для представления ввода. Сеть можно рассматривать как состоящую из двух частей: кодирующей функции h = f (x) и декодера, который производит реконструкцию r = g (h).
Хорошо, хорошо, я знаю, о чем вы думаете! Просто еще один пост без надлежащего объяснения? Нет! Мы будем действовать не так . Давайте сделаем вдох и шаг за шагом соединим наши теоретические знания с кодом! Это поможет вам лучше понять и научиться интуитивно решать любую проблему машинного обучения в будущем.
Мы начнем с создания простого автокодировщика для сжатия набора данных MNIST. С помощью автокодировщиков мы передаем входные данные через кодировщик, который создает сжатое представление входных данных. Затем это представление проходит через декодер для восстановления входных данных. Как правило, кодер и декодер будут построены с использованием нейронных сетей, а затем обучены на примерах данных.
Но что на самом деле представляют собой кодеры и декодеры?
Общая структура автоэнкодера, отображение входа x на выход (называемое реконструкцией) «r» через внутреннее представление или код «h».
Автоэнкодер состоит из двух компонентов:
Кодировщик: Он имеет отображение от x до h, т.е. f (отображение x на h)
Декодер : имеет отображение из h в r, т.е. g (отображение h в r).
Вы поймете, как связать эту информацию и применить ее к коду через несколько абзацев.
Итак, что на самом деле делает это «сжатое представление»?
Сжатое представление обычно содержит важную информацию о входном изображении, и мы можем использовать его для шумоподавления изображений или других видов реконструкции и преобразования! Он отлично подходит для хранения и обмена любыми данными более практичным способом, чем хранение необработанных данных.
Давайте построим простую сетевую архитектуру для кодировщика и декодера, чтобы понять автоэнкодер.
- Мы всегда начинаем с импорта наших библиотек и получения набора данных.
- Преобразование данных в torch.FloatTensor
- загрузить обучающие и тестовые наборы данных
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)
- Затем, как обычно, создайте загрузчики обучающих и тестовых данных
- Количество подпроцессов, используемых для загрузки данных
- Сколько образцов в партии загружать
- Подготовьте загрузчики данных. Теперь, если у вас есть собственный набор данных, на котором вы хотите опробовать автоэнкодер, вам нужно будет создать загрузчик данных, специально предназначенный для этой цели. Я опубликовал блог, который поможет вам с этой целью: Как создать собственный загрузчик данных для вашего собственного набора данных.
# 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)
- Визуализируйте данные. Теперь это необязательно, но всегда полезно проверить, правильно ли загружены ваши данные. Вы можете добиться этого,
- Получение одной партии обучающих образов
- Затем получаем одно изображение из партии
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, нам нужно использовать сигмовидную активацию на выходном слое, чтобы получить значения, соответствующие этому диапазону входных значений.
- Архитектура модели: это самый важный шаг автоэнкодера, поскольку мы пытаемся достичь целей, которые совпадают с входными данными!
- Определите архитектуру 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 ))
Проверка результатов:
- Получите одну партию тестовых изображений
- Получить образцы выходных данных
- Подготовьте изображения для показа
- Размер вывода преобразуется в пакет изображений.
- Используйте detach, если для вывода требуется_grad
- Постройте первые десять входных изображений, а затем восстановленные изображения
- Входные изображения в верхнем ряду, реконструкции внизу
# 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, то это не особенно полезно. Вместо этого автоэнкодеры не могут научиться идеально копировать. Обычно они ограничены способами, которые позволяют им копировать только приблизительно, и копировать только ввод, который напоминает данные обучения. Поскольку модель вынуждена определять приоритеты, какие аспекты входных данных следует копировать, она часто узнает полезные свойства данных.
Поскольку здесь мы имеем дело с изображениями, мы можем (обычно) повысить производительность, используя сверточные слои. Следовательно, следующее, что вы можете сделать, это создать лучший автоэнкодер со сверточными слоями. Вы можете использовать полученные здесь основы в качестве основы для автоэнкодера со сверточными слоями.
Если вы хотите опробовать код самостоятельно, обратитесь сюда: Линейные автоэнкодеры