本教程解决以下问题:
求一个变量(如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了。如下面所示:
总结
看一下Variable和Tensor有什么区别。先看一下官网对Variable和Tensor的定义。
Variable
(图片截自2020/1/6)
!!!我kao,Variable已经被弃用了!!!不过虽然已经被弃用了,但是Variabel(tensor), Variable(tensor, requires_grad)还是可以正常调用,只不过这个函数不返回Variable对象,而是返回一个Tensor对象。。。。。。
那么Variable返回的对象跟Tensor一模一样罗?那为毛这个会报错:
先将变量传入CUDA,在修改requires_grad属性就报错???
但我直接用Tensor是可以这么操作的:
所以,我觉得这两个东西还是不完全一样的!!!
Tensor
emmmm看一下几个关键的属性
也就是说grad这个值默认是None,但调用backward的时候这个变量可能会被填充。
这里他说,一个张量需要计算梯度的事实并不意味着会填充grad属性。。。。。看下一个
哦,这个意思是说,只有是叶子张量,在调用backward时,这个张量的grad属性才会被填充。。。。。
然后,我通过检查张量的is_leaf属性,最后我才发现了这个问题的问题所在。