pytorch学习2--grad自动微分

本文详细介绍了PyTorch中张量的grad属性、如何启用和停止追踪,以及如何利用自动微分进行基础tensor操作和向量化计算。重点讲解了grad_fn属性的作用和反向传播的原理,以及如何使用`withtorch.no_grad()`来控制追踪过程。
摘要由CSDN通过智能技术生成

pytorch自动微分

grad属性

第一讲学习介绍了tensor张量,它有一个属性requires_grad,这个属性默认取值为false,他的意义是追踪针对tensor的所有操作,比如进行了加减乘除等等操作。
如果要取消对这个tensor的追踪,可以使用.detach(),将与计算的历史记录分离,并防止未来的计算被追踪
为了停止追踪历史记录,还可以使用代码块with torch.no_grad():来防止。

tensor和function

tensor和function相互连接并构建一个非循环图,保存完整的计算过程的历史信息,每个张量都有一个gead_fn属性保存着建立了张量的function的引用

相关函数使用

基础tensor使用

from __future__ import print_function
import torch

x=torch.empty(5,3)
x=torch.rand(5,3)
print(x)

#构造矩阵全为0,而且指定数据类型为long
x=torch.zeros(5,3,dtype=torch.long)
print(x)

#直接构造一个张量,直接使用数据
x=torch.tensor([5.5,3])
print(x)

#基于已存在的tensor创建一个新的Tensor
x=x.new_ones(5,3,dtype=torch.double)
print(x)
x=torch.randn_like(x,dtype=torch.float)
print(x)
#获取维度信息
print(x.size())

#加法1
y=torch.rand(5,3)
print(x+y)
#加法2
print(torch.add(x,y))
#加法指定输出参数
result=torch.empty(5,3)
torch.add(x,y,out=result)
print(result)
#in_place的add
y.add_(x)
print(y)

x=torch.randn(5,3)
#tensor支持类似于numpy的索引操作
print(x[:,1])
#改变tensor的大小/形状
x=torch.randn(4,4)
y=x.view(16)
#这里指定了第二个维度的大小为8,第一个的-1表示第一个维度的大小计算来得到,即总共的元素多少和第二个维度的大小来决定
z=x.view(-1,8)
print(x.size(),y.size(),z.size())
#只有一个元素的tensor可以通过下面的方式获取元素
x=torch.randn(1)
print(x.item())

自动微分相关使用和原理介绍(难+重要)

#借助torch.tensor的requires_grad来追踪tensor的所有操作
x=torch.ones(2,2,requires_grad=True)
print(x)
#y作为操作的结果被创建,它具有grad_fn的属性
y=x+2
print(y)
print(y.grad_fn)
z=y*y*3
out=z.mean()
print(z,out)
#.requires_grad_(...)会改变requires_grad标记
#输入的标记默认是false
a=torch.randn(2,2)
a=((a*3)/(a-1))
print(a.requires_grad)
a.requires_grad_(True)
print(a.requires_grad)

输出:
在这里插入图片描述
可以看到有记录相关操作的输出,这里的追踪就是因为我们设置了requires_grad为真。

继续上面的代码:

b=(a*a).sum()
print(b.grad_fn)
#现在后向传播/反馈,因为输出了一个标量
#out.backward()等同于out.backward(torch.tensor(1.))
#实际上backward函数的参数应该是一个和out相同size的tensor,但是因为这里是标量,所以如上
#out.backward()
out.backward(torch.tensor(1.))
print(x.grad)
#解释
#x=torch.ones(2,2);y=x+2;z=3*y^2;out=z.mean()
#因为x是一个2*2的张量,所以z也是,那么out=1/4*(sum(zi))
#实际上这里求得是out对x的导数,即dout/dx=3/2*(xi+2)=4.5

输出:
在这里插入图片描述
为什么是这样的4个4.5000请看代码注释,我解释了
有微分基础的应该可以看懂,看不懂的,请看这里详细解释,实在不想写了

这里请注意:当这个out也就是最后的结果是一个标量的时候,我们的额backward函数的参数可以为空,也可以等价为backward(torch.tensor(1.))。
实际上这个backward函数有一个参数,这个参数要和out的大小一致,为什么呢,我们继续追溯!
首先,这个最后的out.grad其实求解的就是dout/dx,这里的x就是最开始的那个tensor。

其次,我先根据上面这个代码里面的表达式画一个图。
代码中我们的表达式分别是
x=torch.ones[2,2];
y=x+2;
z=3*y^2;
out=z.mean()
对应下图
在这里插入图片描述
这里也说明了上面我们将的function和tensor的关系,他们能够完整记录过程的表现
在这个图中,我们的前向传播的输入为X,输出为out
而反向传播的输入是一个隐含创建的torch.tensor(1),输出是x的grad,也就是我上面说的这个backward函数求解的是什么。

接下来回归正题,为什么这里面的参数必须要和out的大小一致呢?
再举个例子,进一步说明backward函数干了什么
对于y = ( a + b ) ( b + c ) y=(a+b)(b+c)y=(a+b)(b+c)这个例子可以构建如下计算图:
在这里插入图片描述
在这里插入图片描述
(图片和例子来源:点击这里,尊重版权
实际上是先求出Jacobian矩阵中每一个元素的梯度值(每一个元素的梯度值的求解过程对应上面的计算图的求解方法),然后将这个Jacobian矩阵与grad_tensors参数对应的矩阵进行对应的点乘,得到最终的结果。
也就是说我们根据上面的图会求出每个元素的梯度值,最后是要和这个backward的参数做点积的,所以当然要保证大小的一致啦。

那么稍微总结一下:

  • 如果是标量,可以直接backward或者等价backward(torch.tensor(1.))
  • 如果是向量,那么参数的大小尺寸必须要保持一致,可以参考下面的例子
#向量的backward使用示例
x=torch.randn(3,requires_grad=True)
y=x*2
while y.data.norm()<1000:
    y=y*2;

print(y)
v=torch.ones_like(y)
y.backward(v)
print(x.grad)

输出:
在这里插入图片描述

停止过程追踪函数

#通过with torch.no_grad()来停止对历史中的.requires_grad=True的张量的自动求导
print(x.requires_grad)
print((x**2).requires_grad)

with torch.no_grad():
    print((x**2).requires_grad)

输出:
在这里插入图片描述
可以看到这个with的块里面确实是停止追踪了

PS:
刚才有个代码没解释

while y.data.norm()<1000:
    y=y*2;

这里的这个norm函数其实就是张量的范数,而且这里指的是L2范数。
那么先说明下什么是范数:
张量的范数:
在这里插入图片描述
范数是具有长度概念的函数,其为向量空间内的所有向量赋予非零的正长度或大小。(确实有点抽象,我同步学习CNN的时候经常看到,比如pooling汇合操作就可以理解为一个p范数作为非线性衣舍的卷积操作,特别当p趋近于正无穷的时候就是常见的最大值汇合,这里再解释就要说的更多了,简单来说就是取一个集合中的最大值)

欢迎交流,如有不足请指出!

—END

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值