Torch requires_grad / backward /

2020-01-06  本文已影响0人  winddy_akoky

本教程解决以下问题:

求一个变量(如loss值)关于某个变量的梯度值是,即便变量的requires_grad属性设置成True,该变量的grad还是等于None。

介绍

如果你也跟我一样是在学习对抗样本,那肯定离不开要对loss函数关于输入样本求梯度值,进而构造对抗样本。但是我一开始并不知道如何求loss函数关于输入的梯度值,也不知道如何获取这个梯度值。即便我在网上找到了源代码,也很难理解。而这篇文章就记录了我的学习过程以及理解。

求对抗样本

下面是我整合网上的代码自己写的一份关于FSGM的代码。你会发现获取Loss函数关于输入的梯度的关键是这句:

input_for_gradient = Variable(input, requires_grad=True)

其中requires_grad=True属性设置成True后,使得我们在调用backward后,input_for_gradient 变量的梯度值能够被保存下来。因此,实际上,一般情况下只要把这个属性设置成True,我们就可以正常求关于某一个变量的梯度值了。

class FGSM:
    def __init__(self, model, criterion, epsilon, device):
        self.model = model
        self.criterion = criterion
        self.epsilon = epsilon
        self.device = device
        assert isinstance(model, torch.nn.Module), "Input parameter model is not nn.Module. Check the model"
        assert isinstance(criterion, torch.nn.Module), "Input parameter criterion is no Loss. Check the criterion"
        assert (0 <= epsilon <= 1), "episilon must be 0 <= epsilon <= 1"
        self.model.eval()


    def __call__(self, input, labels):
        # For calculating gradient
        input_for_gradient = Variable(input, requires_grad=True).to(self.device)
        out = self.model(input_for_gradient)
        loss = self.criterion(out, Variable(labels))

        # Calculate gradient
        loss.backward()

        # Calculate sign of gradient
        signs = torch.sign(input_for_gradient.grad.data)

        # Add
        input_for_gradient.data = input_for_gradient.data + (self.epsilon * signs)

        return input_for_gradient, signs

问题

上面说的情况都很理想,一旦你手动实践后,就很容易碰到各种各样的bug,比如下面这个bug困惑了我一下下午:

xx = Variable(torch.Tensor([1,2,3]), requires_grad=True)
# xx = torch.Tensor([1,2,3])
# xx.requires_grad = True
xx = xx.to(DEVICE)

yy = xx ** 2
zz = yy.mean()
# # zz.backward(xx, retain_graph=True)
zz.backward()

print(xx.grad)
print(xx.is_leaf)
print(xx.requires_grad)


None
False
True

xx 是一个Variable(后面会再来讨论这个),该变量的requires_grad属性设置成True,然后把这个变量移入CUDA中,再做几个简单的算术操作后,调用zz.backward()来计算梯度值。但是,从结果上看,xx的grad竟然为None!!!而且竟然不是叶子节点!!!

解决方法

先直接上代码:

# xx = Variable(torch.Tensor([1,2,3]), requires_grad=True)
xx = torch.Tensor([1,2,3])
xx = xx.to(DEVICE)
xx.requires_grad = True

yy = xx ** 2
zz = yy.mean()
# # zz.backward(xx, retain_graph=True)
zz.backward()

print(xx.grad)
print(xx.is_leaf)
print(xx.requires_grad)

tensor([0.6667, 1.3333, 2.0000], device='cuda:7')
True
True

也就是说,放弃用Variable,用Tensor就行,且,注意了(敲黑板),先将变量传入CUDA,再将变量的requires_grad属性设置成True!!!

你要是先设置requires_grad属性,再传入CUDA,那么这个变量的requires_grad属性设变成False了。如下面所示:


image.png

总结

看一下Variable和Tensor有什么区别。先看一下官网对Variable和Tensor的定义。

Variable

image.png

(图片截自2020/1/6)
!!!我kao,Variable已经被弃用了!!!不过虽然已经被弃用了,但是Variabel(tensor), Variable(tensor, requires_grad)还是可以正常调用,只不过这个函数不返回Variable对象,而是返回一个Tensor对象。。。。。。

那么Variable返回的对象跟Tensor一模一样罗?那为毛这个会报错:


image.png

先将变量传入CUDA,在修改requires_grad属性就报错???

但我直接用Tensor是可以这么操作的:


image.png

所以,我觉得这两个东西还是不完全一样的!!!

Tensor

image.png

emmmm看一下几个关键的属性

image.png

也就是说grad这个值默认是None,但调用backward的时候这个变量可能会被填充。

image.png

这里他说,一个张量需要计算梯度的事实并不意味着会填充grad属性。。。。。看下一个

image.png

哦,这个意思是说,只有是叶子张量,在调用backward时,这个张量的grad属性才会被填充。。。。。

然后,我通过检查张量的is_leaf属性,最后我才发现了这个问题的问题所在。

上一篇下一篇

猜你喜欢

热点阅读