pytorch理解

计算图:

如下所示为一个pytorch简单模型:

class simpleNet(nn.Module):

    def __init__(self, in_dim, n_hidden_1, n_hidden_2, out_dim):
        super().__init__()
        self.layer1 = nn.Linear(in_dim, n_hidden_1)
        self.layer2 = nn.Linear(n_hidden_1, n_hidden_2)
        self.layer3 = nn.Linear(n_hidden_2, out_dim)

    def forward(self, x):
        x = self.layer1(x)
        x = self.layer2(x)
        x = self.layer3(x)

        return x

网络结构如图所示:
在这里插入图片描述
将神经网络模型想象成一个复杂的管道结构,不同的管道之间通过节点连接起来,这个管道有一个注水口和一个出水口。当我们在入口注入数据后,数据就沿设定好的管道路线缓缓流动到出水口,这时就完成了一次正向传播,这个管道就可以理解为计算图。

以下是pytorch网络结构的可视化(可以近似理解为计算图),图中包括两种元素,一个是tensor(上图中的边),指代数据,另一个是operation(上图中的节点),指代对于数据的各种操作,将上图放大,可以很容易看出来(注意看右上角):
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述在这里插入图片描述
在pytorch中,计算图通常包括两种元素,一个是tensor,另一个是function,function指代的是在计算图中某个节点,也就是上面图中的operation所进行的运算,比如加减乘除,function内部有forward()和backward()两个方法,分别应用于正向传播和反向传播。

import torch
a=torch.tensor(2.0,requires_grad=True)
b=a.exp()
print(b)

输出结果:
tensor(7.3891, grad_fn=<ExpBackward>)

在进行正向传播的时候,需要求导的变量除了执行forward()操作外,还会同时为反向传播做一些准备,为反向计算图添加一个function节点,在上面这个例子中,变量b在反向传播中需要进行的操作是ExpBackward

叶子张量:

在pytorch中,为了节省显存,引入了叶子张量的概念,在反向传播过程中,只有叶子张量的求导结果会被保存,因为其他的非叶子张量是由用户所定义的叶子张量的一系列运算所形成的(下面会解释),非叶子张量都是中间变量,一般情况下用户不会去使用这些中间变量的导数,所以为了节约显存,它们在用过之后就都被释放了。
对于任意一个张量来说,我们都可以用tensor.is_leaf来判断它是否是叶子张量,对于requires_grad=False的张量,我们默认为叶子张量,当requires_grad=True时,张量是否是叶子张量是这样进行判断的:
当一个tensor是用户创建的时候,它是一个叶子节点,当一个tensor是由其他运算操作产生的时候,它就不是一个叶子节点。

import torch

a=torch.ones([2,2],requires_grad=True)
print(a.is_leaf)

b=a+2
print(b.is_leaf)

输出:
True
False

#a是叶子张量,而b不是,因为b是通过运算得到的

通过一个具体的例子进行理解:

l1=input*w1
l2=l1+w2
l3=l1*w3
l4=l2*l3
loss=mean(l4)

其计算图如下所示:
在这里插入图片描述
在这些变量中,整张图中只有input一个变量不需要求导,因为w1,w2,w3这些变量是我们自己创建的,需要求导,其他中间变量由于链式求导法则,也需要求导,首先观察下正向传播过程中导数的存储情况:

import torch

input=torch.ones([2,2],requires_grad=False)
w1=torch.tensor(2.0,requires_grad=True)
w2=torch.tensor(3.0,requires_grad=True)
w3=torch.tensor(4.0,requires_grad=True)

l1=input*w1
l2=l1+w2
l3=l1*w3
l4=l2*l3
loss=l4.mean()

print(w1.data,w1.grad,w1.grad_fn)
print(l1.data,l1.grad,l1.grad_fn)
print(loss.data,loss.grad,loss.grad_fn)

输出:
tensor(2.) None None
tensor([[2., 2.],
        [2., 2.]]) None <MulBackward0 object at 0x0000013882B63358>
tensor(40.) None <MeanBackward0 object at 0x0000013882B63358>

输出结果也基本符合预期,变量l1的grad_fn储存着乘法操作符< MulBackward0 >,用于在反向传播中指导导数的计算;因为w1是用户自己定义的,不是通过计算得来的,所以其grad_fn为空,同时因为没有进行反向传播,grad的值也为空,接下来看下反向传播的计算图:
在这里插入图片描述
输出下反向传播导数情况:

import torch

input=torch.ones([2,2],requires_grad=False)
w1=torch.tensor(2.0,requires_grad=True)
w2=torch.tensor(3.0,requires_grad=True)
w3=torch.tensor(4.0,requires_grad=True)

l1=input*w1
l2=l1+w2
l3=l1*w3
l4=l2*l3
loss=l4.mean()
loss.backward()

print(w1.data,w1.grad,w1.grad_fn)
print(l1.data,l1.grad,l1.grad_fn)
print(w1.grad,w2.grad,w3.grad)
print(l1.grad,l2.grad,l3.grad,l4.grad)

输出:
tensor(2.) tensor(28.) None
tensor([[2., 2.],
        [2., 2.]]) None <MulBackward0 object at 0x000001F216B82780>
tensor(28.) tensor(8.) tensor(10.)
None None None None

可以看出和我们上述定义的叶子张量的导数存储规则一样,我们自定义的张量w1等导数被存储,而l1等中间张量的导数没有被存储下来。
如果要存储这些导数,可以使用retain_grad():

import torch

input=torch.ones([2,2],requires_grad=False)
w1=torch.tensor(2.0,requires_grad=True)
w2=torch.tensor(3.0,requires_grad=True)
w3=torch.tensor(4.0,requires_grad=True)

l1=input*w1
l2=l1+w2
l3=l1*w3
l4=l2*l3
loss=l4.mean()

l1.retain_grad()
l2.retain_grad()
l3.retain_grad()
l4.retain_grad()

loss.backward()

print(l1.grad,l2.grad,l3.grad,l4.grad)

输出:
tensor([[7., 7.], [7., 7.]]) 
tensor([[2., 2.], [2., 2.]]) 
tensor([[1.2500, 1.2500], [1.2500, 1.2500]]) 
tensor([[0.2500, 0.2500], [0.2500, 0.2500]])

动态图,静态图:

pytorch使用的是动态图的方式(dynamic computational graphs),而tensorflow使用的是静态图的方式(static computational graphs),以下探讨下静态图和动态图。
所谓动态图,是指每次当我们搭完一个计算图,然后在反向传播结束之后,整个计算图就在内存中被释放了,如果想再次使用的话,必须从头再搭一遍,而以tensorflow为代表的静态图,每次都先设计好计算图,需要的时候再实例化这个图,然后松土各种输入,重复使用,只有当回话结束的时候图才会被释放,就像之前管道的例子,设计好水管布局之后,需要用的时候就开始搭,搭好了就往入口加水,什么时候不需要了,再把管道都给拆了。
从理论上来说,静态图在效率上比动态图高,因为首先,静态图只用构建一次,然后重复使用就可以了;其次静态图因为是固定不需要改变的,所以在设计完了计算图之后,可以进一步的优化,比如可以将用户原本定义的Conv层和Relu层合并成ConvRelu层,提高效率,但是深度学习框架的速度不仅仅取决于图的类型,还有其他很多因素,比如底层代码质量,所使用的blas库等,从实际测试结果上说,pytroch有着不逊于动态图框架Caffe,TensorFlow的表现。
除了动态图外,pytorch还有一个特性,叫做eager execution,意思就是当遇到了tensor计算的时候,马上就去执行计算,也就是,pytorch实际上根本不会去构建正向计算图,而是遇到操作就执行,真正意义上的正向计算图是把所有的操作都添加完,构建好了之后,再运行神经网络的正向传播。

参考:
https://blog.csdn.net/byron123456sfsfsfa/article/details/92210253

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值