【问题标题】:How to implement contractive autoencoder in Pytorch?如何在 Pytorch 中实现收缩自动编码器?
【发布时间】:2019-10-05 14:15:09
【问题描述】:

我正在尝试在 Pytorch 中创建一个 contractive autoencoder。我找到了this thread 并据此尝试。这是我根据提到的线程写的sn-p:

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 let's 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”对象没有“requires_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 中解决这个问题?

【问题讨论】:

  • 首先,检查您的数据是否正确加载(可能使用image, label = next(iter(train_loader)),然后使用matplotlib检查image)。另一件事是您将 imgs 转换为 cuda,但我认为您需要先将其转换为 PyTorch 张量。因此,您应该使用imgs = torch.autograd.Variable(imgs) 执行此操作,然后您可以执行.to(device)
  • 关于加载等一切都是正确的。默认情况下,imgs 和标签是张量。因为在数据集中,它们使用 ToTensor() 转换转换为张量。张量从 0.4 开始与 Variable 合并(也就是说,它们可以具有梯度并被跟踪),并且 Variable 已经被弃用了相当长的一段时间。 torch.Tensor 几乎可以用于任何事情!
  • 尝试打印出imgs.grad 或它的形状并检查其中是否有东西。如果您想进行双重反向传递(这就是为什么要设置imgs 的梯度的requires_grad 参数),那么首先检查imgs.grad 是否是一个带有一些值的张量(而不是NoneType 如错误中所述)。
  • 问题是,它仍然是 NoneType ,我似乎找不到让它工作的方法。这就是我的问题的要点!我添加了 google colab 链接,您可以根据需要使用它来运行代码。
  • 我想我理解这个问题,虽然我不知道如何解决它,因为我不熟悉这种网络。问题是imgs.grad 将保持NoneType 直到您在计算图中具有imgs 的东西上调用backward。现在,您确实在 output_e 上向后调用,但这不能正常工作。我认为你应该在 PyTorch 论坛上问这个问题。

标签: python deep-learning pytorch autoencoder autograd


【解决方案1】:

总结

我写的收缩损失的最终实现如下:

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 在 cmets 中正确指出,在 Pytorch 论坛中发现的实现在多个地方都是错误的。值得注意的是,它没有实现Contractive Auto-Encoders:Explicit Invariance During Feature Extraction 论文中引入的实际收缩损失!除此之外,由于显而易见的原因,该实现根本不起作用,稍后将解释。

这些变化是显而易见的,所以我试着解释一下这里发生了什么。首先注意imgs不是叶子节点,所以渐变不会保留在图像.grad属性中。

为了保留非叶节点的梯度,您应该使用retain_graph()grad 仅用于叶张量。此外,imgs.retain_grad() 应在执行 forward() 之前调用,因为它会指示 autograd 将 grads 存储到非叶节点中。

更新

感谢@Michael指出Frobenius Norm的正确计算其实是(来自ScienceDirect):

所有矩阵项的平方之和的平方根

不是

所有的绝对值之和的平方根 矩阵条目解释here

【讨论】:

  • 之前,我只是根据您的代码和您的问题发表评论,但如果您参考原始论文并检查其他开源实现,例如this,会更好。只是为了确保您正确地复制了论文,因为即使您不完全遵循原始论文,这件事也可能仍然有效。
  • 你是对的!但是我们所做的应该是有效的,因为我们基本上在做同样的事情,但不同的是,我们不是构建整个雅可比矩阵,而是简单地使用 autograd 来推导相对于输入的梯度。关于检查其他示例,我到底应该检查什么,梯度或输出或损失?
  • 顺便说一句,在类似的配置中,使用您提供的链接与我们这里的链接的两个损失)如下:loss2 使用lam= 1e-10loss: 0.2316920906305313 loss2: 0.2316921353340149 loss: 0.22340171039104462 loss2: 0.2234017252922058 loss: 0.21272771060466766 loss2: 0.21272774040699005 loss: 0.19688208401203156 loss2: 0.19688212871551514 loss: 0.17924705147743225 loss2: 0.17924711108207703 loss: 0.1550494283437729 loss2: 0.15504947304725647 loss: 0.1335817575454712 loss2: 0.13358180224895477 loss: 0.11488725244998932 loss2: 0.1148873120546341 所以我说这是正确的
  • 最好通过在您的方法和我提供的链接中写出方程式来检查数学。但是,如果您已经这样做并且它们匹配,那么它是正确的。
  • 你说得对!我必须让 output_e 向后退,由于某种原因,它应该不起作用!而且我不应该需要从 loss1 支持!然而,这两种方法的损失是一样的,这真的很奇怪!
【解决方案2】:

实现收缩自动编码器的主要挑战是计算雅可比的 Frobenius 范数,它是代码或瓶颈层(向量)相对于输入层(向量)的梯度。这是损失函数中的正则化项。幸运的是,您为我解决了这个问题。谢谢!您在第一学期使用 MSE 损失。有时使用交叉熵损失代替。值得考虑。我认为您几乎可以使用 Frobenius 范数,除了您需要取 Jacobian 的 squares 之和的平方根,其中您正在计算 之和的平方根em>绝对值。以下是我如何定义损失函数(抱歉,我稍微更改了符号以保持自己的直截了当):

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

【讨论】:

  • 谢谢,我使用了mathworld 中给出的符号,它说使用绝对值而不是平方。我实际上更深入地挖掘,发现它应该是平方而不是绝对值! frobenius-norm
猜你喜欢
  • 2017-12-19
  • 2020-05-08
  • 1970-01-01
  • 2017-12-05
  • 2014-05-01
  • 2021-01-29
  • 2019-10-18
  • 2017-01-26
  • 1970-01-01
相关资源
最近更新 更多