PyTorch框架学习四——计算图与动态图机制

一、计算图

计算图是用来描述运算的有向无环图,它包含了两个主要元素:结点(Node)与边(Edge)。其中结点表示数据,如向量、矩阵、张量等,而边表示运算,如加减乘除卷积等。

下面用计算图来表示:y=(x+w)×(w+1)这样的一个运算。

令 a = x + w,b = w + 1,则y = a×b。

计算图如下图所示:
在这里插入图片描述
构建这样的计算图是很方便求解梯度的,以对w求偏导为例,假设x和w的初始值为2和1:
在这里插入图片描述
强调两个概念:

  1. 叶子结点:用户创建的结点,如x与w,tensor中的is_leaf属性就是指示张量是否为叶子结点。非叶子结点运算后会被释放,叶子结点的梯度会被保留,若想保留非叶子结点的梯度,可以用retain_grad()。
  2. grad_fn:记录创建张量时所用的方法(函数),在梯度的反向传播时会用到,以上述的计算为例:y.grad_fn = <MulBackward 0>,a.grad_fn = <AddBackward 0>。

二、动态图与静态图

动态图静态图
实现方式运算与搭建同时进行先搭建计算图,后计算
特点灵活易调节高效但不灵活
框架PyTorchTensorFlow

三、torch.autograd

这是一个自动求导系统,提供了类和函数用来对任意标量函数进行求导。
下面介绍autograd中两个自动求导的函数:

1.torch.autograd.backward()

没有返回值,但是已经对数据进行了自动求导。

torch.autograd.backward(tensors: Union[torch.Tensor, Sequence[torch.Tensor]], grad_tensors: Union[torch.Tensor, Sequence[torch.Tensor], None] = None, retain_graph: Optional[bool] = None, create_graph: bool = False, grad_variables: Union[torch.Tensor, Sequence[torch.Tensor], None] = None)

在这里插入图片描述

  1. tensors:用于求导的张量们。
  2. grad_tensors:多梯度权重,下面用例子理解。
  3. retain_graph:(布尔,可选)若为False,计算图计算完之后就会被释放,若为True,则会保留。
  4. create_graph:(布尔,可选)若为True,会创建导数的计算图,用于更高阶的求导,默认为False。

2.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], None] = None, retain_graph: Optional[bool] = None, create_graph: bool = False, only_inputs: bool = True, allow_unused: bool = False)

在这里插入图片描述

3.autograd小贴士

  1. 梯度不会自动清零,若不清零,则会叠加上原来的数据,需要手动清零:grad.zero_()。
  2. 依赖于叶子结点的结点,requires_grad默认为True。
  3. 叶子结点不可执行in-place操作,in-place操作为在原始内存中改变数据的操作,如a += torch.ones((1, )) (加等操作a的内存地址不变,所以对张量不能做这项操作)。这是因为,在前向传播时,会记录叶子结点的地址,反向求导时是会依据记录的地址去取值进行运算的,若在中途用in-place操作改变了值,则梯度求解会出错。

4.代码演示理解

(1)构建计算图并反向求导:

# 设置 x 和 w 的初始值
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, x.grad)

结果如下,与手动计算结果一致:

tensor([5.]) tensor([2.])

(2)grad_tensors的理解:

w = torch.tensor([1.], requires_grad=True)
x = torch.tensor([2.], requires_grad=True)

a = torch.add(w, x)     # retain_grad()
b = torch.add(w, 1)

y0 = torch.mul(a, b)    # y0 = (x+w) * (w+1)
y1 = torch.add(a, b)    # y1 = (x+w) + (w+1)    dy1/dw = 2

loss = torch.cat([y0, y1], dim=0)       # [y0, y1]
grad_tensors = torch.tensor([1., 2.])

loss.backward(gradient=grad_tensors)    # gradient 传入 torch.autograd.backward()中的grad_tensors

print(w.grad)

grad_tensors的作用就类似于一个权重,当要求导的对象有多个梯度时,它就是各个梯度加和的权重,比如这里 dy0 / dw = 5,dy1/dw = 2,那么w的总梯度值为 5×1 + 2×2 = 9。
结果如下:

tensor([9.])

(3)autograd.gard与create_graph的结合:

x = torch.tensor([3.], requires_grad=True)
y = torch.pow(x, 2)     # y = x**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 = d(2x)/dx = 2
print(grad_2)

其中y就是用于求导的张量,x就是需要梯度的张量,grad_1就是第一次求导后x的梯度,因为create_graph为True,所以已经构建了导数的计算图,可以对grad_1再次求导,得到第二次求导后x的梯度grad_2:

(tensor([6.], grad_fn=<MulBackward0>),)
(tensor([2.]),)

(4)小贴士1

这里我们构建了四次计算图,四次一模一样的计算:

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.])

因为每次的梯度都一样都为5,所以若不手动清零,则梯度会叠加起来。
我们在原来的基础上添加上手动清零:

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)

    w.grad.zero_()

结果为:

tensor([5.])
tensor([5.])
tensor([5.])
tensor([5.])

这样才是正确的。

(5)小贴士2

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)

print(a.requires_grad, b.requires_grad, y.requires_grad)

结果为:

True True True

(6)小贴士3

a = torch.ones((1, ))
print(id(a), a)

a += torch.ones((1, ))
print(id(a), a)

结果为:

2379593847576 tensor([1.])
2379593847576 tensor([2.])

可见加等操作是in-place操作,是在原始内存中改变数据的操作。
而加法操作就不是,如下所示:

a = torch.ones((1, ))
print(id(a), a)

a = a + torch.ones((1, ))
print(id(a), a)

内存不一样:

3019154559688 tensor([1.])
3019156480632 tensor([2.])

如果我们对叶子结点进行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 += torch.ones((1,))
# w.add_(1)

y.backward()

print(w.grad)

会报如下错误提示:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值