[Примечания к изучению pyTorch ⑤] Основы PyTorch, часть 2

4. Тензор и Автоград

1. Автоматический вывод

Важным содержанием нейронной сети является обучение параметрам, а обучение параметрам неотделимо от вывода.
PyTorch имеет функцию автоматического вывода: пакет Autograd
torch.Tensor и torch.Function являются двумя основными классами Autograd. Они связаны друг с другом для создания ориентированного ациклического графа (называемого «графом вычислений», который представляет собой динамический граф). )

2. Расчетный график

Граф вычислений представляет собой интуитивно понятное и эффективное графическое представление связи между операторами и переменными.
Как показано на рисунке ниже: выражение
y = xw, z = y + by=xw, z=y+bу"="х ш ;г"="у+b

z = wx + bz=wx+bг"="ш х+б

mul
w
add
b
z
y
x

Градиент каждого конечного узла может быть обновлен в соответствии с правилом вывода цепочки составной функции.
∂ z ∂ x знак равно ∂ zay ∂ y ∂ x знак равно w \ гидроразрыва {\ парциальное г} {\ парциальное х} = \ гидроразрыва {\ парциальное г} {ау} \ гидроразрыва {\ парциальное у} {\ парциальное х} = шх г"="и ты гх у"="ш ∂ z ∂ ш знак равно ∂ z ∂ y ∂ y ∂ ш знак равно Икс \ гидроразрыва {\ парциальное г} {\ парциальное ш} = \ гидроразрыва {\ парциальное г} {\ парциальное у} \ гидроразрыва {\ парциальное у} {\ частичное w}=xш г"="у гш у"="Икс ∂ z ∂ б знак равно 1 \ гидроразрыва {\ парциальное г} {\ парциальное b} = 1б г"="1
, как показано на рисунке: (это процесс обратного распространения)

db
addBackward
dw
mulBackward
dx
dy
dz

3. Скалярное обратное распространение

Предполагая, что x, w и b все являются скалярами, z=wx+b, вызовите метод reverse() для скаляра z.

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. Нескалярное обратное распространение

Когда целевой тензор является скалярным, вы можете вызвать метод reverse() без передачи параметров.Целевой тензор обычно является скалярным, например значение потери, которое мы часто используем, Loss, которое обычно является скалярным.

Но есть и нескалярные случаи, и целевое значение Deep Dream — это тензор с несколькими элементами. Итак, как выполнить обратное распространение нескаляров?У PyTorch есть простое правило, согласно которому тензоры не могут получать тензоры, и только скаляры могут получать тензоры.Поэтому, если целевой тензор вызывает reverse() для нескаляра, вам нужно передать параметр градиента, который также является тензором и должен иметь ту же форму, что и тензор, используемый для вызова reverse0.

那么为什么要传人一个张量 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基础·中篇

Supongo que te gusta

Origin blog.csdn.net/qq_46319397/article/details/130452238
Recomendado
Clasificación