【Antorgrad】使用Tensor 及Antograd实现机器学习
使用Tensor 及Antograd实现机器学习节选自《python深度学习:基于pytorch》因对自动求导不太了解,所以特查看此书,并做好笔记.重点是非标量反向传播,对应的是多元函数的方向传播问题!!!要点:创建叶子节点(leaf node)的Tensor,使用requires_grad参数指定是否记录对其的操作,以便之后利用backward()方法进行梯度求解.requires_grad参数
使用Tensor 及Antograd实现机器学习
节选自《python深度学习:基于pytorch》
因对自动求导不太了解,所以特查看此书,并做好笔记.
重点是非标量反向传播,对应的是多元函数的方向传播问题!!!
要点:
- 创建叶子节点(leaf node)的Tensor,使用
requires_grad
参数指定是否记录对其的操作,以便之后利用backward()
方法进行梯度求解.requires_grad
参数的缺省值为False,如果对其求导设置为True,然后与之有依赖关系的节点会自动变为True. - 可利用
requires_grad_()
方法修改tensor的requires_grad
属性.可以调用.detach()
或with torch.no_grad():
,姜不再计算张量的梯度,跟踪张量的历史记录.这点在评估模型、测试模型阶段中常常用到. - 通过运算创建的Tensor(即非叶子节点会自动被赋予f
grad_fn
的书ing.该属性表示梯度函数.叶子节点的grad_fn
为None. - 最后得到的Tensor执行
backward()
函数,此时自动计算各变量的梯度,并将累加结果保存到grad
的属性中.计算完成后,非叶子节点的梯度自动释放. backward()
函数接受参数,该参数和调用函数的Tensor的维度相同,或者是可以broadcast的维度.如果求导的Tensor为标量(即一个数字),则backward中的参数可以省略.- 方向传播的中间缓存会被清空,如果需要进行多次方向传播,需要指点
backward
中的参数retarin_graph=True
.多次反向传播时,梯度时累加的. - 非叶子节点的梯度
backward
调用后即被清空. - 可以通过调用
torch.no_grad()
包裹代码块的形式来阻止requesgrad=True
的张量的历史记录.这步在测试阶段经常使用.
在整个过程中,pytorch采用计算图的形式进行组织,该计算图为动态图,且在每次钱箱传播时,将重新构建.其他深度学习框架,如Tensorflow\keras一般为静态图.接下来我们介绍计算图,用图的形式描述更直观了,该计算图为有向无环图.
计算图
下面通过代码实现这个计算图.
3标量反向传播
假设x、w、b都是标量,z=wx+b, 对标量z调用backward()
方法,我们无须对backward()
传入参数。 以下是实现自动求导的主要步骤:
1)定义叶子节点及算子节点:
#定义输入张量x
x=torch.Tensor([2])#初始化权重参数W,偏移量b、并设置require_ grad属性为True, 为自动求导
w=torch.randn(1 ,requires_grad=True)
b=torch.randn(1,requires_grad=True)
#实现前向传播
y=torch.mul(w,x) # 等价于w* x
z=torch.add(y,b) #等价于y+b
#查看x,W, b页子节点的requite_ grad属性
print(" x,w,b的require_ grad属性分 别为: {},{},{}".format(x.requires_grad,w.requires_grad, b.requires_grad))
out:
x,w,b的require_ grad属性分 别为: False,True,True
输入变量 == 叶子节点,默认没有梯度的。
算子后的量 =》 默认有梯度
输出变量 == 根节点
2)查看叶子节点、非叶子节点的其他属性。
#查看非叶子节点的requres_ grad属 性,
print("y, z的requires_grad属 性分别为: {},{}".format(y.requires_grad, z.requires_grad))
#因与w, b有依赖关系,故y, z的requires_ grad属性也是: True,True#查看各节点是否为叶子节点
print("x,w, b, y, z的是否为叶子节点: {},{},{},{}".format(x.is_leaf, w.is_leaf, b.is_leaf , y.is_leaf, z.is_leaf))
#x,w, b, y, z的是否为叶子节点: True,True,True,False,False
#查看叶子节点的grad_fn属 性
print("x, w,b的grad_fn属性: {},{},{}".format(x.grad_fn, w.grad_fn, b.grad_fn))
#因x, w, b为用户创建的,为通过其他张量计算得到,故x, w,b的grad_fn属性 :None,None,None
#查看非叶子节点的grad_ fn属 性
print("y,z的是否为叶子节点: {},{}".format(y.grad_fn,z.grad_fn))
#y, z的是否为叶子节点: <MulBackward0 object a0x7f923e85dda0>,<AddBackward0 object at 0x7f923e85d9b0>
3)自动求导,实现梯度方向传播,即梯度的反向传播。
#基于z张量进行梯度反向传播,执行backward之后计算图会自动清空,
z.backward()#如果需要多次使用backward,需要修改参数retain_graph为True,此时梯度是累加的
#z.backward(retain_ graph=True)
# 查看叶子节点的梯度,x是叶子节点但它无须求导,故其梯度为None
print("参数w,b的梯度分别为:{},{},{}".format(w.grad,b.grad,x.grad))
#参数w,b的梯度分别为:ensor(2.1),tensor([1 .),None
#非叶子节点的梯度,执行backward之后, 会自动清空
print("非叶子节点y,z的梯度分别为:{},{}".format(y.grad,z.grad))
#非叶子节点y,z的梯度分别为:None,None
4非标量 反向传播
在2.5.3节中介绍了当目标张量为标量时,可以调用backward()方法且无须传入参数。目标张量-般都是标量,如我们经常使用的损失值Loss,-般都是一个标量。但也有非标量的情况,后面将介绍的Deep Dream的目标值就是一个含多个元素的张量。那如何对非标量进行反向传播呢? PyTorch有个简单的规定,不让张量(Tensor)对张量求导,只允许标量对张量求导,因此,如果目标张量对一个非标量调用backward(),则需要传入一个gradient
参数, 该参数也是张量,而且需要与调用backward()的张量形状相同。那么为什么要传入一个张量gradient呢?
传入这个参数就是为了把张量对张量的求导转换为标量对张量的求导。这有点拗口,我们举一个例子来说, 假设目标值为Ioss=(y1,y2,...,ym)
,传入的参数为v=(v1,v2,...,vm)
,那么就可把对loss的求导,转换为对
l
o
s
s
∗
v
T
loss*v^T
loss∗vT标量的求导。 即把原来
∂
l
o
s
s
∂
x
\frac{\partial loss}{\partial x}
∂x∂loss得到的雅可比矩阵(Jacobian) 乘以张量
v
T
v^T
vT,便可得到我们需要的梯度矩阵。
backward函数的格式为:
backward(gradient=None, retain_graph=None, create_graph=False)
上面说的可能有点抽象,下面来通过一 个实例进行说明 。
1)定义叶子节点及计算节点。
import torch
#定义叶子节点张量x,形状为1x2
x= torch.tensor([[2, 3]], dtype=torch.float, requires_grad=True)
# 初始化雅可比矩阵
J= torch.zeros(2 ,2)
#初始化目标张量,形状为1x2
y = torch.zeros(1,2)
#2 Xy5xZli)AGJBJ#:
#y1=x1**2+3*x2, y2=x2**2+2*x1
y[0, 0]=x[0, 0]**2+3*x[0 ,1]
y[0, 1]=x[0, 1]**2+2*x[0, 0]
2)手工计算y对x的梯度
J
T
=
[
4
2
3
6
]
(2)
J^T=\left[ \begin{matrix} 4 & 2 \\ 3 & 6 \\ \end{matrix} \right]\tag{2}
JT=[4326](2)
3)调用backward来获取y对x的梯度
y.backward(torch.Tensor([[1, 1]]))
print(x.grad)#结果为tensor([6., 9.])
这个结果与我们手工运算的不符,显然这个结果是错误的,那错在哪里呢?这个结果的计算过程是:
由此可见,错在v的取值, 通过这种方式得到的并不是y对x的梯度。这里我们可以分成两步计算。首先让v=(1,0)得到y1对x的梯度,然后使v=(0,1), 得到y2对x的梯度。这里因需要重复使用backward(),需要使参数retain_ graph=True, 具体代码如下:
#生成y1对x的梯度
y.backward(torch.Tensor([[1,0]]),retain_graph=True)
J[0]=x.grad#梯度 是累加的,故需要对x的梯度清零
x.grad = torch.zeros_like(x.grad)
# 生成y2对x的梯度
y.backward(torch. Tensor([[0, 1]]))
J[1]=x.grad#显示jacobian矩阵的值
print(J)
out:
tensor([[4., 3.], [2., 6.]])
这个结果于手工运行的式(2-5)结果一致.
更多推荐
所有评论(0)