torch.no_grad()两个作用:新增的tensor没有梯度,使带梯度的tensor能够进行原地运算。

1.使带有梯度的tensor能够原地运算(更新tensor)

from torch.autograd import Variable
x = Variable(torch.ones(1, 2),requires_grad=True)
x += 1 #或者调用x.add_(1) #注意add_下划线

报错:RuntimeError: a leaf Variable that requires grad has been used in an in-place operation.下面通过torch.no_grad()进行运算

    from torch.autograd import Variable
    x = Variable(torch.ones(1, 2),requires_grad=True)
    print(id(x)) #输出:140655195757952
    print(x) #输出 :tensor([[1., 1.]], requires_grad=True)
    
    with torch.no_grad():
        x += 1 #或者调用x.add_(1) #注意add_下划线

    print(id(x)) #输出:140655195757952  id没有改变
    print(x)# 输出:tensor([[2., 2.]], requires_grad=True) 梯度还未True
	x =x + 1 
    print(id(x)) #输出: 140655195756800 id改变
    print(x) #输出:tensor([[3., 3.]], grad_fn=<AddBackward0>) 没有requires_grad=True状态

从输出中可以看到:x += 1没有改变id,requires_grad,但是执行x =x + 1后id改变,且后边状态为grad_fn=<AddBackward0>,记录了加法状态AddBackward0,但是,这样之后执行backword()后求梯度为变为None,也就是普通赋值(非原地操作)不能对计算梯度的,如果想不用torch.no_grad(),可以利用x.data修改内容。如果:

from torch.autograd import Variable
import torch
x = Variable(torch.ones(1, 2),requires_grad=True)
y = x**2
c = x + 1 ##假如此处换为 x = x + 1,最后结果会输出什么?
z =  y**2
z.sum().backward()
print(z.grad)  ## 输出:None
print(y.grad)  ##输出:None
print(x.grad)  ##输出:tensor([[4., 4.]])
print(x)  ##输出:tensor([[1., 1.]], requires_grad=True
print(y)  ##:tensor([[1., 1.]], grad_fn=<PowBackward0>
print(z)  ##:输出:tensor([[1., 1.]], grad_fn=<PowBackward0>)
print(c)  ##:输出:tensor([[2., 2.]], grad_fn=<AddBackward0>)

从输出可以看到,只有requires_grad=True状态的tensor才有梯度,如果将c = x + 1替换为x = x + 1后,x.grad也会变为None,打印xtensor([[2., 2.]], grad_fn=<AddBackward0>),所以,梯度输出为None,其实,最开始的x还是存在梯度。一旦tensor的requires_grad不为True时候,就能够随意原地操作了,没有梯度限制。

2.使带有梯度的tensor能够原地运算(更新tensor),新建的tensor不能传递梯度。


from torch.autograd import Variable
import torch
x = Variable(torch.ones(1, 10),requires_grad=True)
y1 = (x)**2
y2 = (x)**2
z = y1**2 + y2**2
z.sum().backward()
print(x.grad) ## 输出:tensor([[8., 8., 8., 8., 8., 8., 8., 8., 8., 8.]])
print(z) ##输出:tensor([[2., 2., 2., 2., 2., 2., 2., 2., 2., 2.]], grad_fn=<AddBackward0>)

x = Variable(torch.ones(1, 10),requires_grad=True)
y1 = (x)**2
with torch.no_grad():
    y2 = (x)**2
z = y1**2 + y2**2
z.sum().backward() 
print(x.grad) ##输出:tensor([[4., 4., 4., 4., 4., 4., 4., 4., 4., 4.]])
print(z)##输出:tensor([[2., 2., 2., 2., 2., 2., 2., 2., 2., 2.]], grad_fn=<AddBackward0>)

x = Variable(torch.ones(1, 10),requires_grad=True)
with torch.no_grad():
    x += 1
    y2 = (x)**2
y1 = (x)**2
z = y1**2 + y2**2
z.sum().backward()
print(x.grad)##输出tensor([[32., 32., 32., 32., 32., 32., 32., 32., 32., 32.]])
print(z)##输出tensor([[32., 32., 32., 32., 32., 32., 32., 32., 32., 32.]],grad_fn=<AddBackward0>)

第一次通过y1y2的梯度全部计算;第二次仅仅计算了通过y1的梯度,y2没有传递梯度;第三次仅仅计算通过y1的梯度,但是x值改变导致y1的导数和y1改变,所以梯度为32

3.注意原地操作(更新tensor)之前不能用到相应tensor,如果用到,后面求梯度就不对之前用到tensor的tensor进行求导,否则报错:

from torch.autograd import Variable
import torch
x = Variable(torch.ones(1, 10),requires_grad=True)
y1 = (x)**2
with torch.no_grad():
    x += 1
    y2 = (x)**2
z = y1**2 + y2**2
z.sum().backward() ##这里会报错,报错内容见后面
print(x.grad)
print(z)

报错:RuntimeError: one of the variables needed for gradient computation has been modified by an inplace operation: [torch.FloatTensor [1, 10]] is at version 1; expected version 0 instead. Hint: enable anomaly detection to find the operation that failed to compute its gradient, with torch.autograd.set_detect_anomaly(True)意思是需要求梯度的tensor已经进行原地操作,进行了改变,(之前的x不存在了)不能求梯度,将y1 = (x)**2放在z = y1**2 + y2**2之前(with torch.no_grad():之外),则没问题。

4.每轮训练需要梯度清零操作optimizer.zero_grad()的原因

from torch.autograd import Variable
import torch
x = Variable(torch.ones(1, 2),requires_grad=True)
x.sum().backward()
print(x.grad)  #输出:tensor([[1., 1.]])
x.sum().backward()
print(x.grad)#输出:tensor([[2., 2.]])
x.sum().backward()
print(x.grad)#输出:tensor([[3., 3.]])

从输出中可以看出,每执行一次backward(),对应tensor的梯度都会自加1,即梯度自动累加,所以需要每个batch_size都执行梯度清零.zero_grad()操作。

参考:

实践·pytorch梯度计算

with torch.no_grad() 详解

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐