一、计算图
计算图是描述运算的有向无环图,有两个主要的元素:节点(Node)和边(Edge),节点表示数据,如向量,矩阵,张量。边表示运算,如加减乘除等。
用计算图表示: y = ( x + w ) ∗ ( w + 1 ) y = \left ( x+w \right ) \ast \left ( w+1 \right ) y=(x+w)∗(w+1) ,如下所示:
从上面可以看出y = a × b,而a = w + x, b = w + 1,只要给出x和w的值,即可根据计算图得出y的值。
计算图与梯度求导
求y对w的导数,根据求导规则,如下:
体现到计算图中,就是根节点 y 到叶子节点 w 有两条路径 y -> a -> w和y ->b -> w。根节点依次对每条路径的叶子节点求导,一直到叶子节点w,最后把每条路径的导数相加即可。
x = torch.tensor([2.], requires_grad=True)
w = torch.tensor([1.], requires_grad=True)
# y = (x + w) * (w + 1)
a = torch.add(w, x)
b = torch.add(w, 1)
y = torch.mul(a, b)
#y求导
y.backward()
#w的梯度就是y对w的求导
print(w.grad)
###result
tensor([5.])
在tensor中包含一个is_leaf(叶子节点)属性,叶子节点就是用户创建的节点,在上面的例子中,x 和 w 是叶子节点,其他所有节点都依赖于叶子节点。叶子节点的概念主要是为了节省内存,在计算图中的一轮反向传播结束之后,非叶子节点的梯度是会被释放的。
#查看是否是叶子节点
print(w.is_leaf, x.is_leaf, a.is_leaf, b.is_leaf)
###result
True True False False
#查看梯度
print(w.grad, x.grad, a.grad, b.grad, y.grad)
输出结果:
只有叶子节点的梯度保留了下来,而非叶子的梯度为空,如果在反向传播之后仍需要保留非叶子节点的梯度,可以对节点使用retain_grad()方法。
grad_fn:记录创建该张量时所用的方法(函数),记录这个方法主要是用于【梯度的求导】
二、动态图机制
pytroch采用的是动态图机制,而tensorflow采用的是静态图机制
静态图是先搭建,后运算
动态图是运算和搭建同时进行,也就是可以先计算前面节点的值,再根据这些值搭建后面的计算图。优点是灵活,易调节,易调试。
三、自动求导机制
pytorch的自动求导机制是torch.autograd.backward()方法,功能是自动求取梯度
torch.autograd.backward(
tensors: Union[torch.Tensor, Sequence[torch.Tensor]],
grad_tensors: Union[torch.Tensor, Sequence[torch.Tensor], NoneType] = None,
retain_graph: Union[bool, NoneType] = None,
create_graph: bool = False,
grad_variables: Union[torch.Tensor, Sequence[torch.Tensor], NoneType] = None,
inputs: Union[torch.Tensor, Sequence[torch.Tensor], NoneType] = None,
) -> None
● tensor:表示用于求导的张量,如loss,
● grad_tensor: 设置梯度权重,在计算矩阵的梯度时会用到,也是一个tensor,shape和前面的tensor保持一致
● retain_graph:表示保存计算图,由于pytorch采用了动态图机制,在每一次反向传播之后,计算图都会被释放掉。如果不想释放,就设置这个参数为True
● create_graph:创建导数计算图,用于高阶求导
在上面对y求导 y.backward() 处设置断点,进行debug,可以看出调用的是 torch.autograd.backward() 方法,所以 torch.autograd.backward(y)==y.backward()
在每一次反向传播之后,计算图会释放掉,如果想要使用计算图,可以设置参数retain_graph=True
x = torch.tensor([2.], requires_grad=True)
w = torch.tensor([1.], requires_grad=True)
# y = (x + w) * (w + 1)
a = torch.add(w, x)
b = torch.add(w, 1)
y = torch.mul(a, b)
#y求导
y.backward()
#w的梯度就是y对w的求导
# print(w.grad)
y.backward()
输出结果:
设置梯度权重
x = torch.tensor([2.], requires_grad=True)
w = torch.tensor([1.], requires_grad=True)
# y = (x + w) * (w + 1)
a = torch.add(w, x)
b = torch.add(w, 1)
y0 = torch.mul(a, b)
y1 = torch.add(a, b)
loss = torch.cat([y0, y1], dim=0)
grad_tensor = torch.tensor([1., 1.])
loss.backward(gradient=grad_tensor)
print(w.grad)
# result
tensor([7.])
torch.autograd.grad()这个方法的功能也是求梯度,可以实现高阶的求导。
torch.autograd.grad(
outputs: Union[torch.Tensor, Sequence[torch.Tensor]],
inputs: Union[torch.Tensor, Sequence[torch.Tensor]],
grad_outputs: Union[torch.Tensor, Sequence[torch.Tensor], NoneType] = None,
retain_graph: Union[bool, NoneType] = None,
create_graph: bool = False,
only_inputs: bool = True,
allow_unused: bool = False,
) -> Tuple[torch.Tensor, ...]
- outputs:用于求导的张量;
- inputs: 需要梯度的张量;
- create_graph:创建导数计算图,用于高阶求导
- retain_graph:保存计算图
- grad_outputs:多梯度权重
使用autograd需要注意:
● 梯度不自动清零;
● 依赖于叶子结点的结点,默认requires_grad=True;
● 叶子结点不可执行in-place;
示例如下:
x = torch.tensor([2.], requires_grad=True)
w = torch.tensor([1.], 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)
# w.grad.zero_()
# 梯度不清零,运算结果
tensor([5.])
tensor([10.]