0.4.0版教程。
上述关于梯度方面PyTorch提供的功能,感觉上都是针对神经网络的需求设计的,文档中给出的定义和解释很抽象,需要实例加以说明。
首先说一下叶子Tensor,这是我为了方便理解起的名字。例如我们的模型是这样的:
只要维度相协调,上述各个Tensor目前不必指定维度。实际上上述模型可以看做一个非常简单的神经网络,如下图所示。
x1,x2是神经网络的中间层,y是我们的输出层,Y是真值,L是我们的loss。w1和w2是对应于x1和x2的weight。通常我们会把x1,x2,y,w1和w2使用PyTorch的Tensor进行表示。L也可以用Tensor表示,可能与其他Tensor的维度都不同。在上述模型中,x1,w1和w2就是我所谓的叶子Tensor。
在PyTorch中,我们把上述模型描述出来。
import torch
import numpy as np
x1 = torch.from_numpy( 2*np.ones((2, 2), dtype=np.float32) )
x1.requires_grad_(True)
w1 = torch.from_numpy( 5*np.ones((2, 2), dtype=np.float32) )
w1.requires_grad_(True)
print("x1 =", x1)
print("w1 =", w1)
x2 = x1 * w1
w2 = torch.from_numpy( 6*np.ones((2,2), dtype=np.float32) )
w2.requires_grad_(True)
print("x2 =", x2)
print("w2 =", w2)
y = x2 * w2
Y = torch.from_numpy( 10*np.ones((2,2), dtype=np.float32) )
print("y =", y)
print("Y =", Y)
L = Y - y
print("L =", L)
为了更明确说明BP的过程,写出该模型的数学表达式和梯度表达式
事实上Tensor应当写成加粗直体,我这里简化为都是不加粗斜体了,请各位海涵。
式(1), (2)和(3)即是我们关心的梯度,在PyTorch的帮助下,这一求解过程变为
L.backward(torch.ones(2, 2, dtype=torch.float))
当然,此时backward()函数可不提供任何参数。
计算得出的梯度值分别为x1.grad,w1.grad和w2.grad。其他Tensor的grad属性是None。
此时直接再次调用L的backward()方法将导致错误,需要重新计算L,才能再次调用它的backward()方法。
backward()函数的第一个参数,在PyTorch的文档上解释的比较晦涩。该参数名为gradient,我理解这个参数在我上述的模型上表示
也就是element全为1的Tensor。为何这样设计,这样设计的意图可以理解为当我们不知道L关于y的函数表示,但知道L关于y的梯度时,我们可以通过
y.backward(-1 * torch.ones(2, 2, dtype=torch.float))
来完成BP过程。注意,y.backward()的第一个参数,即是L关于y的梯度的值。
通常情况下对于L为标量的情况,使用L.backward()时,不指定任何参数,PyTorch会使用维度相协调的全1Tensor作为参数。对于L为高于1维的情况,需要明确指定backward()的第一个参数。
执行完L.backward()函数后,x1,w1和w2的grad属性会得到更新。更新是“累加”的。所谓累加,举例说明,即当有另外一种定义好的L2是通过对x1的运算得到的,那么L2.backward()执行后梯度结果将累加到x1.grad中。
对于叶子Tensor的定义,实例中使用的古怪定义方法是有原因的。为了获得可以计算梯度的x1,需要在定义x1时就使其成为叶子,否则对于非叶子的x1执行L.backwar()后,x1.grad将为None。例如将实例中的x1定义改为
x1 = 2 * torch.ones(2, 2, requires_grad=True, dtype=torch.float)
那么,x1则不是一个叶子。应为在创建x1时,表达式中的torch.ones()的返回值才是叶子。
另外需要注意使用NumPy创建Torch的Tensor时,各自默认的数据格式(dtype)和程序打算使用的数据格式要统一好,否则Torch会报错。参考torch.dtype。利用NumPy创建好Tensor后,要对需要计算梯度的叶子调用它们的requires_grad_()方法以将它们的requires_grad属性配置好。
完整的实例代码如下
import torch
import numpy as np
x1 = torch.from_numpy( 2*np.ones((2, 2), dtype=np.float32) )
x1.requires_grad_(True)
w1 = torch.from_numpy( 5*np.ones((2, 2), dtype=np.float32) )
w1.requires_grad_(True)
print("x1 =", x1)
print("w1 =", w1)
x2 = x1 * w1
w2 = torch.from_numpy( 6*np.ones((2,2), dtype=np.float32) )
w2.requires_grad_(True)
print("x2 =", x2)
print("w2 =", w2)
y = x2 * w2
Y = torch.from_numpy( 10*np.ones((2,2), dtype=np.float32) )
print("y =", y)
print("Y =", Y)
L = Y - y
print("L =", L)
L.backward(torch.ones(2, 2, dtype=torch.float))
print("x1.grad =", x1.grad)
print("w1.grad =", w1.grad)
print("w2.grad =", w2.grad)
L2 = x1 * x1
L2.backward(torch.ones(2, 2, dtype=torch.float))
print("x1.grad =", x1.grad)