【pyTorch学習記⑤】PyTorch基礎 その2

4. Tensor と Autograd

1. 自動導出

ニューラル ネットワークの重要な内容はパラメーターの学習であり、パラメーターの学習は導出と切り離せません。
PyTorch には自動導出の機能があります: Autograd パッケージの
torch.Tensor と torch.Function は Autograd の 2 つのコア クラスであり、これらを相互に接続して有向非巡回グラフ (「計算グラフ」と呼ばれる動的グラフ) を生成します。 )

2. 計算グラフ

計算グラフは演算子と変数の関係をグラフィカルに表現したもので、直感的かつ効率的です。
以下の図に示すように、式は
y = xw; z = y + by=xw; z=y+bです。y=x w ;z=y+b
= z
= wx + bz=wx+bz=w ×+b

mul
w
add
b
z
y
x

各リーフノードの勾配は、複合関数の連鎖導出ルールに従って更新できます。
∂ z ∂ x = ∂ zay ∂ y ∂ x = w \frac{\partial z}{\partial x}=\frac{\partial z}{ay}\frac{\partial y}{\partial x}=w×∂z _=そしてy∂z _×∂y _=w ∂ z ∂ w = ∂ z ∂ y ∂ y ∂ w = x \frac{\partial z}{\partial w}=\frac{\partial z}{\partial y}\frac{\partial y}{\部分的な w}=x∂w _∂z _=∂y _∂z _∂w _∂y _=x ∂ z ∂ b = 1 \frac{\partial z}{\partial b}=1∂b _∂z _=
図に示すように1 : (これは逆伝播プロセスです)

db
addBackward
dw
mulBackward
dx
dy
dz

3. スカラー逆伝播

x、w、b がすべてスカラー z=wx+b であると仮定すると、スカラー z に対してbackward() メソッドを呼び出します。

1) リーフノードとオペレーターノードを定義する

import torch

# 定义输入张量x,初始化权重参数w,偏移量b,并设置require_grad属性为True(为了自动求导)
x = torch.tensor([2])
w = torch.randn(1,requires_grad=True)
b = torch.randn(1,requires_grad=True)
# 实现向前传播
y = x.mul(w)
z = y.add(b)
# 查看x/w/b叶子节点的requite_grad属性
print("x,w,b的requite_grad属性为:{},{},{}".format(x.requires_grad,w.requires_grad,b.requires_grad))
>>>x,w,b的requite_grad属性为:FalseTrueTrue

2) リーフ ノードと非リーフ ノードの他の属性を表示する

#查看非叶子节点的requres grad属性,
print("y,z的requires grad属性分别为:{},{}".format(y.requires_grad,z.requires_grad))
#因与w,b有依赖关系,故y,z的requires grad属性也是: True,True
>>>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

# #查看叶子节点的qrad_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
>>>x,w, b的grad fn属性:None,None,None

#查看非叶子节点的arad_fn属性
print("y,z的是否为叶子节点: {},{}".format(y.grad_fn,z.grad_fn))
>>>y,z的是否为叶子节点: <MulBackward0 object at 0x000001F316579128>,<AddBackward0 object at 0x000001F316579160>

3) 勾配方向伝播、すなわち勾配逆伝播を実現するための自動導出

# 基于z张量进行梯度反向传播,执行backward之后计算图会自动清空
z.backward()
#如果需要多次使用backward,需要修改参数retain_graph为True,此时梯度是累加的
# #z.backward(retain_graph=True)

#查看叶子节点的梯度,x是叶子节点但它无须求导,故其梯度为None
print("参数w,b,x的梯度分别为:{},{},{})".format(w.grad,b.grad,x.grad))
>>>参数w,b,x的梯度分别为:tensor([2.]),tensor([1.]),None)

#非叶子节点的梯度,执行backward之后,会自动清空
print("非叶子节点y,z的梯度分别为:{},{}".format(y.grad,z.grad))
>>>非叶子节点y,z的梯度分别为:None,None

4. 非スカラー逆伝播

ターゲット テンソルがスカラーの場合、パラメーターを渡さずに backward() メソッドを呼び出すことができます。ターゲット テンソルは通常、よく使用する損失値 Loss などのスカラーです。

ただし、非スカラーの場合もあり、Deep Dream のターゲット値は複数の要素を持つテンソルです。では、非スカラーを逆伝播するにはどうすればよいでしょうか? PyTorch には、テンソルはテンソルを導出することができず、スカラーのみがテンソルを導出することが許可されるという単純なルールがあります。勾配パラメーターを渡します。これもテンソルであり、backward0 の呼び出しに使用されるテンソルと同じ形状を持つ必要があります。

那么为什么要传人一个张量 gradient 呢?
传入这个参数就是为了把张量对张量的求导转换为标量对张量的求导。
举例子来说,假设目标值为 loss=(y1,y2,…,ym),传入的参数为 v=(v1,v2,…,vm),那么就可把对 loss 的求导,转换为对 loss* v T v^{T} vT标量的求导。即把原来一0(Jacobian)乘以张量T,便可得到我们需要的梯度矩阵。
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)
#初始化Jacobian矩阵
J= torch.zeros(2 ,2)
#初始化目标张量,形状为1x2
y = torch.zeros(1, 2)
#定义y与x之间的映射关系:
#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的梯度

先手工计算一下y对x的梯度,验证 PyTorch 的 backward 的结果是否正确。
y对 x 的梯度是一个雅可比矩阵,我们可通过以下方法进行计算各项的值。假设x= ( x 1 = 2 , x 2 = 3 ) , y = ( y 1 = x 1 2 + 3 x 2 , y 2 = x 2 2 + 2 x 1 ) x_{1}= 2,x_{2} = 3),y = (y_{1}=x_{1}^{2} +3x_2, y_2=x_2^2+ 2x_1) x1=2,x2=3),y=(y1=x12+3x2,y2=x22+2x1) ,不难得到:
J = ( ∂ y 1 ∂ x 1 ∂ y 1 ∂ x 2 ∂ y 2 ∂ x 1 ∂ x 2 ∂ y 2 ) = ( 2 x 1 3 2 2 x 2 ) J=\left(\begin{matrix} \frac{\partial y_1}{\partial x_1}&\frac{\partial y_1 } {\partial x_2} \\ \\ \frac{\partial y_2}{\partial x_1}&\frac{\partial x_2} {\partial_{y_2}} \end{matrix}\right)= \left(\begin{matrix}2x_1&3\\\\2&2x_2\\\end{matrix}\right) J= x1y1x1y2x2y1y2x2 = 2x1232x2
x 1 = 2 , x 2 = 3 x_1=2,x_2=3 x1=2,x2=3
J = ( 4 3 2 6 ) J = \left(\begin{matrix} 4&3\\2&6 \end{matrix}\right) J=(4236)
所以:
J T = ( 4 2 3 6 ) J^T=\left(\begin{matrix} 4&2\\3&6\end{matrix}\right) JT=(4326)

3)调用backward来获取y对x的梯度

y.backward(torch.Tensor([[1,1]]))
print(x.grad)
>>>tensor([[6., 9.]])

这个结果与我们手工运算的不符,显然这个结果是错误的,那错在哪里呢?这个结果的计算过程是:
J T ⋅ v T = ( 4 2 3 6 ) ( 1 1 ) = ( 6 9 ) J^T·v^T=\left(\begin{matrix} 4&2 \\3&6\end{matrix}\right)\left(\begin{matrix} 1 \\1\end{matrix}\right)=\left(\begin{matrix} 6 \\9\end{matrix}\right) JTvT=(4326)(11)=(69)
由此可见,错在v的取值,通过这种方式得到的并不是y对 x 的梯度。
这里我们可以分成两步计算。
首先让 v=(1,0)得到 y 1 y_1 y1对x的梯度,然后使 v=(0,1),得到 y 2 y_2 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)

>>>tensor([[4., 3.],
        [2., 6.]])

与手工计算一致。

相关推荐

【pyTorch学习笔记①】Numpy基础·上篇
【pyTorch学习笔记②】Numpy基础·下篇
【pyTorch学习笔记③】PyTorch基础·上篇
【pyTorch学习笔记④】PyTorch基础·中篇

おすすめ

転載: blog.csdn.net/qq_46319397/article/details/130452238