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