torch.autograd
权值的更新需要求解梯度,pytorch提供了自动求导系统,我们只需要搭建前向传播计算图,由autograd的方法就可以得到所有张量的梯度。
其中最常用的方法是backward
torch.autograd.backward()
功能:自动求取各个节点的梯度
- tensors: 用于求导的张量,如 loss
- retain_graph :保存计算图(由于pytorch采用动态图机制,在每一次反向传播之后计算图都会被释放,如果后面还需要使用计算图,该参数需要为True)
- create_graph:创建导数计算图,用于高阶求导
- grad_tensors:多梯度权重(计算多个loss时,需要设置各个loss的权重)
回顾计算图与梯度求导
通过构建前向传播计算图和自动求导系统,对y执行backward方法,即可得到所有节点的梯度。
retain_graph
举例:
w = torch.tensor([1.],requires_grad=True)
x = torch.tensor([2.],requires_grad=True)
a = torch.add(w,x)
b = torch.add(w,1)
y = torch.mul(a,b)
y.backward()
print(w.grad)
结果:
tensor([5.])
张量中的y.backward()
实际上调用了torch.autograd.backward()
假设还需要执行一次反向传播:如果直接在上述代码后面加上y.backward()
则会报错,
因为计算图已经被释放。
解决方案:在上一次反向传播中保存计算图则可以解决上述问题,即retain_graph=True
y.backward(retain_graph=True)
print(w.grad) #tensor([5.])
y.backward()
grad_tensors
举例:
w = torch.tensor([1.],requires_grad=True)
x = torch.tensor([2.],requires_grad=True)
a = torch.add(w,x)
b = torch.add(w,1)
y0 = torch.mul(a,b) # y0 = (x+w) * (w+1) dy0/dw = 5
y1 = torch.add(a,b) # y1 = (x+w) + (w+1) dy1/dw = 2
loss = torch.cat([y0,y1],dim=0) # tensor([6., 5.])
# print(loss)
grad_tensors = torch.tensor([1.,2.]) # 多个梯度的权重
loss.backward(gradient=grad_tensors) # gradient 传入torch.autograd.backward()中的grad_tensors
print(w.grad) # dy0/dw * 1.+ dy1/dw * 2. = 9
tensor([9.])
torch.autograd.grad()
功能:求取梯度
- outputs: 用于求导的张量,如 loss
- inputs : 需要求梯度的张量
- create_graph : 创建导数计算图,用于高阶求导
- retain_graph : 保存计算图
- grad_outputs:多梯度权重
x = torch.tensor([3.],requires_grad=True)
y = torch.pow(x,2) #y = x**2, 二阶导数是2
# 只有在一阶导数中对导数创建计算图,才可以对导数求导
grad_1 = torch.autograd.grad(y,x,create_graph=True) # grad_1 = dy/dx =2x =2*3 =6
print(grad_1)# 元组
grad_2 = torch.autograd.grad(grad_1[0],x)# grad_2 = d(dy/dx)/dx = 2
print(grad_2)
结果:
(tensor([6.], grad_fn=<MulBackward0>),)
(tensor([2.]),)
autograd小贴士:
- 梯度不自动清零
- 依赖于叶子结点的结点, requires_grad默认为True
- 叶子结点不可执行in-place
- 梯度在每次反向传播之后会叠加
w = torch.tensor([1.],requires_grad=True)
x = torch.tensor([2.],requires_grad=True)
for i in range(4):
a = torch.add(w,x)
b = torch.add(w,1)
y = torch.mul(a,b)
y.backward()
print(w.grad)
结果:
tensor([5.])
tensor([10.])
tensor([15.])
tensor([20.])
为了避免梯度叠加,需要在代码最后加上w.grad.zero_()
(_下划线操作叫做原位操作)
这样每一次计算完都清零,梯度不会叠加
结果:
tensor([5.])
tensor([5.])
tensor([5.])
tensor([5.])
- 依赖于叶子结点的结点, requires_grad默认为True
w = torch.tensor([1.],requires_grad=True)
x = torch.tensor([2.],requires_grad=True)
a = torch.add(w,x)
b = torch.add(w,1)
y = torch.mul(a,b)
# 默认都是True
print(a.requires_grad,b.requires_grad,y.requires_grad)
- 叶子结点不可执行in-place
w = torch.tensor([1.],requires_grad=True)
x = torch.tensor([2.],requires_grad=True)
a = torch.add(w,x)
b = torch.add(w,1)
y = torch.mul(a,b)
w.add_(1)
y.backward()
运行会报错:
RuntimeError: a leaf Variable that requires grad has been used in an in-place operation.
什么是in-place operation?
a = torch.ones((1,))
print(id(a),a)
a = a + torch.ones((1,))
print(id(a),a)# 内存地址和上面的a不同
结果:开辟了新的内存地址,不是原位操作
1086097577608 tensor([1.])
1086068988888 tensor([2.])
若改为:
a += torch.ones((1,))
print(id(a),a)# 内存地址不变,也就是原位操作
结果:内存地址不变,也就是原位操作
596027098760 tensor([1.])
596027098760 tensor([2.])
可从计算图的梯度求解过程来理解:
求y对w的梯度,需要先求y对a的梯度,y对a求导得w+1,在反向传播时与叶子张量w有关,而在前向传播时会记录w的地址,在反向传播时,会根据记录的w的地址来得到w的值;如果在反向传播之前改变了w地址中的数据,那么梯度求解则会出错