torch中的自动求导为什么要传个与被求导量同形状的gradient
y.backward(torch.ones(y.size())) 初学深度学习接触到自动求导时,被这个东西深深困惑住了,反向传播就反向传播呗,为啥要传入一个torch.ones(y.size())呢,这个与y相同形状的矩阵是啥玩意呢,带着思考,我查询了pytorch文档,心中有了答案。 参考自动求导文档
实际上,torch中的自动求导只对标量进行,如果你要backward的对象是一个矢量,那么就需要传入一个和这个矢量形状一样的矢量,作为它的gradient,原文档中用gradient肯定是有原因的:
比如我现在有
l
o
s
s
=
g
(
y
⃗
)
loss=g(\vec{y})
loss=g(y),
y
⃗
=
f
(
x
⃗
)
\vec{y}=f(\vec{x})
y=f(x),那么Loss对y的导数和y对x的导数就是:
∂
l
o
s
s
∂
y
⃗
=
v
⃗
=
(
∂
ℓ
∂
y
1
⋯
∂
ℓ
∂
y
m
)
T
∂
y
∂
x
⃗
=
J
=
(
∂
y
∂
x
1
⋯
∂
y
1
∂
x
n
⋮
⋱
⋮
∂
y
m
∂
x
1
⋯
∂
y
m
∂
x
n
)
\begin{aligned}\frac{\partial loss}{\partial\vec{y}}&=\vec{v}=\begin{pmatrix}\frac{\partial\ell}{\partial y_1}&\cdots&\frac{\partial\ell}{\partial y_m}\end{pmatrix}^T\\\frac{\partial y}{\partial\vec{x}}&=J=\begin{pmatrix}\frac{\partial\mathbf{y}}{\partial x_1}&\cdots&\frac{\partial y_1}{\partial x_n}\\\vdots&\ddots&\vdots\\\frac{\partial y_m}{\partial x_1}&\cdots&\frac{\partial y_m}{\partial x_n}\end{pmatrix}\end{aligned}
∂y∂loss∂x∂y=v=(∂y1∂ℓ⋯∂ym∂ℓ)T=J=
∂x1∂y⋮∂x1∂ym⋯⋱⋯∂xn∂y1⋮∂xn∂ym
于是乎,
∂
l
o
s
s
∂
x
⃗
\frac{\partial loss}{\partial \vec x}
∂x∂loss就可以使用链式法则了(注意J是使用的分子布局,就是优先按照分子的维度排列,换成分母法则的话,下面就不用转置了,其实本质一样)
∂
l
o
s
s
∂
x
⃗
=
(
∂
y
⃗
∂
x
⃗
)
T
∂
l
o
s
s
∂
y
⃗
=
J
T
v
⃗
=
(
∂
y
1
∂
x
1
⋯
∂
y
m
∂
x
1
⋮
⋱
⋮
∂
y
1
∂
x
n
⋯
∂
y
m
∂
x
n
)
(
∂
l
∂
y
1
⋮
∂
l
∂
y
n
)
\frac{\partial loss}{\partial\vec{x}}=(\frac{\partial\vec{y}}{\partial\vec{x}})^T\frac{\partial loss}{\partial\vec{y}}=J^T\vec{v}=\begin{pmatrix}\frac{\partial y_1}{\partial x_1}&\cdots&\frac{\partial y_m}{\partial x_1}\\\vdots&\ddots&\vdots\\\frac{\partial y_1}{\partial x_n}&\cdots&\frac{\partial y_m}{\partial x_n}\end{pmatrix}\begin{pmatrix}\frac{\partial l}{\partial y_1}\\\vdots\\\frac{\partial l}{\partial y_n}\end{pmatrix}
∂x∂loss=(∂x∂y)T∂y∂loss=JTv=
∂x1∂y1⋮∂xn∂y1⋯⋱⋯∂x1∂ym⋮∂xn∂ym
∂y1∂l⋮∂yn∂l
正常来说,对于标量值l,我们可以直接backward()并查看x的grad属性所存储的梯度 ∂ l o s s ∂ x ⃗ \frac{\partial loss}{\partial \vec x} ∂x∂loss,没问题,很顺利;
但是对于y呢,我要是相对y求导,并查看x的梯度 ∂ y ⃗ ∂ x ⃗ \frac{\partial \vec y}{\partial \vec x} ∂x∂y怎么办呢,很遗憾,不支持这么做!!!,需要我们自己传入一个 v ⃗ \vec v v才行,因为pytorch的只支持标量值的求导,而我们一般来说,损失函数loss也确实是个标量。
-----------------------------我会尽快给出代码
----------------------------------------------------------------------23.11.8.17:07
代码验证
假设x就是一个列向量,k是一个矩阵,y=k*x(矩阵相乘),得到的y也是个向量,loss=(y-0).sum(),就是y与零向量的差距,所以很明显loss是一个标量
这就可以简单模拟一下,我们分别从loss和y求导,因为梯度的累加,我下面写了个x用于测试loss对x的导数,x1用于测试y对x的导数
运行一下,是和我们上述的结论一样的
x=torch.tensor([[1.],[2.],[3.]],requires_grad=True)
print(x)
tensor([[1.],
[2.],
[3.]], requires_grad=True)
k=torch.tensor([[1.,2,3],[1.,2,3]])
print(k)
y=torch.matmul(k,x)
print(y)
l=y.sum()
l
l.backward()
print(x.grad)
tensor([[1., 2., 3.],
[1., 2., 3.]])
tensor([[14.],
[14.]], grad_fn=<MmBackward0>)
tensor([[2.],
[4.],
[6.]])
x1=torch.tensor([[1.],[2.],[3.]],requires_grad=True)
print(x1)
tensor([[1.],
[2.],
[3.]], requires_grad=True)
k=torch.tensor([[1.,2,3],[1.,2,3]])
print(k)
y=torch.matmul(k,x1)
print(y)
# y.backward()
# 不传入一个与y同形状的矩阵就会报错:RuntimeError: grad can be implicitly created only for scalar outputs
y.backward(torch.ones(2,1))
print(x1.grad)
tensor([[1., 2., 3.],
[1., 2., 3.]])
tensor([[14.],
[14.]], grad_fn=<MmBackward0>)
tensor([[2.],
[4.],
[6.]])