小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
PyTorch: Tensors and autograd(auto-gradient) 因为pytorch中可以使用autograd实现神经网络反向传播过程的自动计算。 当我们使用autograd的时候,前向传播会定义一个计算图,图中的节点都是张量,图的边是函数,用于从输入张量产生输出张量。通过这个图的反向传播就可以轻松获得gradient。
虽然听起来很复杂,但是用起来是很简单的,每个张量都代表计算图中的一个节点。如果x是一个张量,并且对其设置x.requires_grad=True
,那么x.grad
会存储x相对于某个标量梯度的张量。
1. .requires_grad=True
默认情况下Tensor的requires_grad属性都是False,这样就不会保留其gradient。当其设置为x.requires_grad=True
时,x上的操作就会被**追踪(track)**并保存在grad_fn
属性中,此外反向传播时候会将gradient保存在.grad
属性中(不执行反向传播的话.grad
是空的)。
import torch
# 默认情况下
x = torch.tensor([1.0, 2.0, 3.0])
print(x) # 输出:tensor([1., 2., 3.])
x += 1
print(x) # 输出:tensor([1., 2., 3.])
# 设置requires_grad=True
y = torch.tensor([1.0, 2.0, 3.0], requires_grad=True)
print(y) # 输出:tensor([1., 2., 3.], requires_grad=True)
y = y + 1.0
print(y) # 输出:tensor([2., 3., 4.], grad_fn=<AddBackward0>)
print(y.grad) # None并爆出警告
复制代码
注意:
- 对于叶子节点的张量不能执行
+=
之类的操作,会报错a leaf Variable that requires grad is being used in an in-place operation.
。 - 在这里最后一行代码会有个警告
UserWarning
大致意思错误访问了非叶节点的.grid
,我故意在这打印一下grad的,不用管它。 - 如果你实在是想看一下
.grid
,那可以在y = y + 1.0之后加上一句y.retain_grad()
,之后就不会出警告了。 默认情况下对于非叶节点张量是禁用该属性grad,计算完梯度之后就被释放回收内存,不会保存中间结果的梯度。
2. Tensor的 .grad_fn属性
上边提到当你设置.requires_grad=True
之后,该张量上的操作就会追踪。 可以看到上边代码y的输出和x是不一样的,第一次打印的时候后边显示requires_grad=True
,第二次打印的时候后边有个grad_fn=<AddBackward0>
,设置追踪之后,grad_fn
属性会保存加在上边的操作。这里上边执行了x = x+1
之后,就显示是进行了一个加的操作。 但是只有非叶节点才有该属性,叶子节点会显示None。
3. with torch.no_grad()
如果你不想某个操作被追踪,那你就需要使用with torch.no_grad():
。 被该语句包裹起来的代码就不会被追踪gradient。
import torch
# 设置requires_grad=True
y = torch.tensor([1.0, 2.0, 3.0], requires_grad=True)
print(y) # 1 tensor([1., 2., 3.], requires_grad=True)
y = y + 1.0
print(y) # 2 tensor([2., 3., 4.], grad_fn=<AddBackward0>)
y = y*1.2
print(y) # 3 tensor([2.4000, 3.6000, 4.8000], grad_fn=<MulBackward0>)
print(y.grad_fn) # 4 <MulBackward0 object at 0x000001FAA3B511C0>
with torch.no_grad():
y = y - 2
print(y) # 5 tensor([0.4000, 1.6000, 2.8000])
print(y.grad_fn) # 6 None
复制代码
- 第三个输出中
grad_fn
显示是追踪了一个乘法操作 - 第四个输出单独打印一下
grad_fn
,再次显示是追踪了乘法操作 - 第五个输出是在设定了
with torch.no_grad():
之后,发现输出中没有grad_fn
属性了 - 第六个输出单独打印一下
grad_fn
确实是不存在,即with torch.no_grad():
包裹之下,那个乘法操作没有被追踪
4. .grad
上边举例的xy都是非叶节点,是不会存储.grad
的,现在放到一个神经网络中试一下下。 我们用一个三节多项式
来拟合
只用一个简单的两层的神经网路。 代码如下:
import torch
import math
dtype = torch.float
device = torch.device("cpu")
# 取消下边这行的注释就可以在GPU上运行
# device = torch.device("cuda:0")
# Create Tensors to hold input and outputs.
# 默认情况下requires_grad=False, 表示我们不需要计算这个张量在反向传播过程中的梯度。
x = torch.linspace(-math.pi, math.pi, 2000, device=device, dtype=dtype)
y = torch.sin(x)
# 随机初始化参数,设置requires_grad=True表示我们希望将反向传播过程中的梯度保留
a = torch.randn((), device=device, dtype=dtype, requires_grad=True)
b = torch.randn((), device=device, dtype=dtype, requires_grad=True)
c = torch.randn((), device=device, dtype=dtype, requires_grad=True)
d = torch.randn((), device=device, dtype=dtype, requires_grad=True)
learning_rate = 1e-6
for t in range(2000):
y_pred = a + b * x + c * x ** 2 + d * x ** 3
# Now loss is a Tensor of shape (1,)
# loss.item() 获取loss中的标量值
loss = (y_pred - y).pow(2).sum()
# 使用autogrid计算,这个调用会计算所有的requires_grad=True的张量的gradient。
# 然后他们的值会分别存储在对应的张量中a.grad, b.grad. c.grad d.grad
loss.backward()
if t == 500:
print(a.grad) # tensor(-905.9598)
print(a.grad_fn) # None
print(a.is_leaf) # True
print(loss.grad) # None 并报警告
print(loss.grad_fn) # <SumBackward0 object at 0x0000023B59AA2190>
print(loss.is_leaf) # False
# 手动更新权重
# Wrap in torch.no_grad()因为之前设置了requires_grad=True,但是我们不希望在autograd记录下a-操作的gradient
with torch.no_grad():
a -= learning_rate * a.grad
b -= learning_rate * b.grad
c -= learning_rate * c.grad
d -= learning_rate * d.grad
if t == 500:
print(a.grad) # tensor(-905.9598)
print(a.grad_fn) # None
print(a.is_leaf) # True
print(loss.grad) # None 并报警告
print(loss.grad_fn) # <SumBackward0 object at 0x0000023B59AA2190>
print(loss.is_leaf) # False
# 更新权重之后手动清楚存储梯度gradient的张量
a.grad = None
b.grad = None
c.grad = None
d.grad = None
复制代码
上边代码我挑了循环中的一节在两个地方都打印了一下。两个地方的输出完全一样。
is_leaf
可以检验是否是叶子节点。a是叶子节点,loss是非叶节点。- with torch.no_grad()之前
- 可以看到a在backward之后的
.grad
,但是grad_fn
是None - 可以看到loss 的
grad_fn
是执行了umBackward,但是y.grad
被禁用(强行打印会输出None并报警告)
- 可以看到a在backward之后的
- with torch.no_grad()之后
- 对loss没影响,因为代码包裹中没对loss的操作
- a在这段代码之后
.grad
和grad_fn
都没变- 讲道理,这段就是简单的一个减法,按我的理解没执行反向操作,所以
.grad
里的值本来也不会改变。所以这个代码的唯一作用就是让grad_fn
继续保持None,所以现在有个疑问“加了这么一大句话就为了保持一个属性是None?有啥意义?” - 经过我查证还是有意义的:
- 讲道理,这段就是简单的一个减法,按我的理解没执行反向操作,所以
5. with torch.no_grad()有什么意义
在使用pytorch时,并不是所有的操作都需要进行计算图的生成。而对于tensor的计算操作,默认是要进行计算图的构建的,在这种情况下,可以使用 with torch.no_grad():
,强制之后的内容不进行计算图构建。在数据量大的情况下大概率提高运行速度的吧。
我是萝莉安,今天钻牛角尖貌似又成功了。(^-^)V 全凭手打自己查的资料,可能有漏误,欢迎指正。Thanks♪(・ω・)ノ