Как внедрить сократительный автоэнкодер в Pytorch? - PullRequest
1 голос
/ 05 октября 2019

Я пытаюсь создать contractive autoencoder в Pytorch. Я нашел этот поток и попробовал в соответствии с этим.
Это фрагмент, который я написал на основе упомянутого потока:

import datetime
import numpy as np 
import torch
import torchvision
from torchvision import datasets, transforms
from torchvision.utils import save_image, make_grid
import torch.nn as nn 
import torch.nn.functional as F 
import torch.optim as optim
import matplotlib.pyplot as plt 
%matplotlib inline

dataset_train = datasets.MNIST(root='MNIST',
                               train=True,
                               transform = transforms.ToTensor(),
                               download=True)
dataset_test  = datasets.MNIST(root='MNIST', 
                               train=False, 
                               transform = transforms.ToTensor(),
                               download=True)
batch_size = 128
num_workers = 2
dataloader_train = torch.utils.data.DataLoader(dataset_train,
                                               batch_size = batch_size,
                                               shuffle=True,
                                               num_workers = num_workers, 
                                               pin_memory=True)

dataloader_test = torch.utils.data.DataLoader(dataset_test,
                                               batch_size = batch_size,
                                               num_workers = num_workers,
                                               pin_memory=True)

def view_images(imgs, labels, rows = 4, cols =11):
    imgs = imgs.detach().cpu().numpy().transpose(0,2,3,1)
    fig = plt.figure(figsize=(8,4))
    for i in range(imgs.shape[0]):
        ax = fig.add_subplot(rows, cols, i+1, xticks=[], yticks=[])
        ax.imshow(imgs[i].squeeze(), cmap='Greys_r')
        ax.set_title(labels[i].item())


# now lets view some 
imgs, labels = next(iter(dataloader_train))
view_images(imgs, labels,13,10)

class Contractive_AutoEncoder(nn.Module):
    def __init__(self):
        super().__init__()
        self.encoder = nn.Linear(784, 512)
        self.decoder = nn.Linear(512, 784)

    def forward(self, input):
        # flatten the input
        shape = input.shape
        input = input.view(input.size(0), -1)
        output_e = F.relu(self.encoder(input))
        output = F.sigmoid(self.decoder(output_e))
        output = output.view(*shape)
        return output_e, output

def loss_function(output_e, outputs, imgs, device):
    output_e.backward(torch.ones(output_e.size()).to(device), retain_graph=True)
    criterion = nn.MSELoss()
    assert outputs.shape == imgs.shape ,f'outputs.shape : {outputs.shape} != imgs.shape : {imgs.shape}'

    imgs.grad.requires_grad = True 
    loss1 = criterion(outputs, imgs)
    print(imgs.grad)
    loss2 = torch.mean(pow(imgs.grad,2))
    loss = loss1 + loss2 
    return loss 

epochs = 50 
interval = 2000
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model = Contractive_AutoEncoder().to(device)
optimizer = optim.Adam(model.parameters(), lr =0.001)

for e in range(epochs):
    for i, (imgs, labels) in enumerate(dataloader_train):
        imgs = imgs.to(device)
        labels = labels.to(device)

        outputs_e, outputs = model(imgs)
        loss = loss_function(outputs_e, outputs, imgs,device)
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
        if i%interval: 
            print('')

    print(f'epoch/epoechs: {e}/{epochs} loss : {loss.item():.4f} ')

Ради краткости я просто использовал один слойдля кодера и декодера. Он должен работать независимо от количества слоев в любом из них!
Но подвох здесь в том, что я не знаю, правильный ли это способ (вычисление градиентов по отношению к входным данным), Я получаю сообщение об ошибке, которое делает предыдущее решение неправильным / неприменимым.
То есть imgs.grad.requires_grad = True создает ошибку:

AttributeError : объект NoneType имеетнет атрибута 'require_grad'

Я также попробовал второй метод, предложенный в этой теме, который выглядит следующим образом:

class Contractive_Encoder(nn.Module):
    def __init__(self):
        super().__init__()
        self.encoder = nn.Linear(784, 512)

    def forward(self, input):
        # flatten the input
        input = input.view(input.size(0), -1)
        output_e = F.relu(self.encoder(input))
        return output_e

class Contractive_Decoder(nn.Module):
    def __init__(self):
        super().__init__()
        self.decoder = nn.Linear(512, 784)

    def forward(self, input):
        # flatten the input
        output = F.sigmoid(self.decoder(input))
        output = output.view(-1,1,28,28)
        return output


epochs = 50 
interval = 2000
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

model_enc = Contractive_Encoder().to(device)
model_dec = Contractive_Decoder().to(device)

optimizer = optim.Adam([{"params":model_enc.parameters()},
                        {"params":model_dec.parameters()}], lr =0.001)

optimizer_cond = optim.Adam(model_enc.parameters(), lr = 0.001)

criterion = nn.MSELoss()

for e in range(epochs):
    for i, (imgs, labels) in enumerate(dataloader_train):
        imgs = imgs.to(device)
        labels = labels.to(device)

        outputs_e = model_enc(imgs)
        outputs = model_dec(outputs_e)
        loss_rec = criterion(outputs, imgs)
        optimizer.zero_grad()
        loss_rec.backward()
        optimizer.step()

        imgs.requires_grad_(True)
        y = model_enc(imgs)
        optimizer_cond.zero_grad()
        y.backward(torch.ones(imgs.view(-1,28*28).size()))

        imgs.grad.requires_grad = True
        loss = torch.mean([pow(imgs.grad,2)])
        optimizer_cond.zero_grad()
        loss.backward()
        optimizer_cond.step()

        if i%interval: 
            print('')

    print(f'epoch/epoechs: {e}/{epochs} loss : {loss.item():.4f} ')

, но я сталкиваюсь с ошибкой:

RuntimeError: invalid gradient at index 0 - got [128, 784] but expected shape compatible with [128, 512]

Как мне поступить об этом в Pytorch?

Ответы [ 2 ]

0 голосов
/ 06 ноября 2019

Основная проблема при реализации сократительного автоэнкодера заключается в вычислении фробениусовой нормы якобиана, которая является градиентом кода или слоя узкого места (вектора) относительно входного слоя (вектора). Это член регуляризации в функции потерь. К счастью, вы проделали тяжелую работу по решению этого для меня. Спасибо! Вы используете потерю MSE на первый срок. Вместо этого иногда используется перекрестная потеря энтропии. Это стоит рассмотреть. Я думаю, что вы почти там с нормой Фробениуса, за исключением того, что вам нужно взять квадратный корень из суммы квадратов якобиана, где вы рассчитываете квадратный корень из суммы абсолютные значения . Вот как я могу определить функцию потерь (извините, я немного изменил нотацию, чтобы сохранить себя прямо):

def cae_loss_fcn(code, img_out, img_in, lamda=1e-4, device=torch.device('cuda')):

    # First term in the loss function, for ensuring representational fidelity
    criterion=nn.MSELoss()
    assert img_out.shape == img_in.shape, f'img_out.shape : {img_out.shape} != img_in.shape : {img_in.shape}'
    loss1 = criterion(img_out, img_in)

    # Second term in the loss function, for enforcing contraction of representation
    code.backward(torch.ones(code.size()).to(device), retain_graph=True)
    # Frobenius norm of Jacobian of code with respect to input image
    loss2 = torch.sqrt(torch.sum(torch.pow(img_in.grad, 2))) # THE CORRECTION
    img_in.grad.data.zero_()

    # Total loss, the sum of the two loss terms, with weight applied to second term
    loss = loss1 + (lamda*loss2)

    return loss
0 голосов
/ 07 октября 2019

TLDR
Окончательная реализация для сократительной потери, которую я написал, выглядит следующим образом:

def loss_function(output_e, outputs, imgs, lamda = 1e-4, device=torch.device('cuda')):

    criterion = nn.MSELoss()
    assert outputs.shape == imgs.shape ,f'outputs.shape : {outputs.shape} != imgs.shape : {imgs.shape}'
    loss1 = criterion(outputs, imgs)

    output_e.backward(torch.ones(outputs_e.size()).to(device), retain_graph=True)    
    # Frobenious norm, the square root of sum of all elements (square value)
    # in a jacobian matrix 
    loss2 = torch.sqrt(torch.sum(torch.pow(imgs.grad,2)))
    imgs.grad.data.zero_()
    loss = loss1 + (lamda*loss2) 
    return loss 

и внутри цикла обучения вам нужно сделать:

for e in range(epochs):
    for i, (imgs, labels) in enumerate(dataloader_train):
        imgs = imgs.to(device)
        labels = labels.to(device)

        imgs.retain_grad()
        imgs.requires_grad_(True)

        outputs_e, outputs = model(imgs)
        loss = loss_function(outputs_e, outputs, imgs, lam,device)

        imgs.requires_grad_(False)

        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
    print(f'epoch/epochs: {e}/{epochs} loss: {loss.item():.4f}')

Полное объяснение:
Как оказалось, и справедливо @ akshayk07 указал в комментариях, реализация, найденная на форуме Pytorch, была неправильной во многих местах. Примечательно, что он не реализовывал фактическую сжимающую потерю, которая была введена в Сократительные авто-кодеры: явная инвариантность при извлечении признаков ! и кроме того, реализация не будет работать вообще по очевидным причинам, которые будут объяснены через мгновение.

Изменения очевидны, поэтому я пытаюсь объяснить, что здесь происходит. Прежде всего, обратите внимание, что imgs не является листовым узлом , поэтому градиенты не будут сохранены в атрибуте image .grad.
Чтобы сохранить градиенты для неконечных узлов, вам следуетиспользуйте retain_graph(). grad заполняется только для листовых тензоров. Кроме того, imgs.retain_grad() следует вызывать перед выполнением forward(), поскольку он будет инструктировать autograd хранить грады в неконечных узлах.

Обновление :

Спасибо@Michael за указание на то, что правильный расчет нормы Фробениуса на самом деле (от ScienceDirect ):

квадратный корень из суммы квадратов всех элементов матрицы

и не

квадратный корень из суммы абсолютных значений всехматричные записи как объяснено здесь

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...