构建深度学习模型的基本流程就是:搭建计算图,求得损失函数,然后计算损失函数对模型参数的导数,再利用梯度下降法等方法来更新参数。搭建计算图的过程,称为“正向传播”,这个是需要我们自己动手的,因为我们需要设计我们模型的结构。由损失函数求导的过程,称为“反向传播”,求导是件辛苦事儿,所以自动求导基本上是各种深度学习框架的基本功能和最重要的功能之一,PyTorch也不例外。
一、pytorch自动求导初步认识
比如有一个函数,y=x的平方(y=x2),在x=3的时候它的导数为6,我们通过代码来演示这样一个过程。
x=torch.tensor(3.0,requires_grad=True)
y=torch.pow(x,2)
#判断x,y是否是可以求导的
print(x.requires_grad)
print(y.requires_grad)
#求导,通过backward函数来实现
y.backward()
#查看导数,也即所谓的梯度
print(x.grad)
最终的运行结果为:
True
True
tensor(6.) #这和我们自己算的是一模一样的。
这里有一些关键点
1.1 tensor的创建与属性设置
先来看一下tensor的定义:
tensor(data, dtype=None, device=None, requires_grad=False) -> Tensor
参数:
data: (array_like): tensor的初始值. 可以是列表,元组,numpy数组,标量等;
dtype: tensor元素的数据类型
device: 指定CPU或者是GPU设备,默认是None
requires_grad:是否可以求导,即求梯度,默认是False,即不可导的
(1)tensor对象的requires_grad属性
每一个tensor都有一个requires_grad属性,表示这个tensor是否是可求导的,如果是true则可以求导,否则不能求导,语法格式为:
x.requires_grad 判断一个tensor是否可以求导,返回布尔值
需要注意的是,只有当所有的“叶子变量”,即所谓的leaf variable都是不可求导的,那函数y才是不能求导的,什么是leaf variable呢?这其实涉及到“计算图”相关的知识,但是我们通过下面的例子一下就能明白了,如下:
#创建一个二元函数,即z=f(x,y)=x2+y2,x可求导,y设置不可求导
x=torch.tensor(3.0,requires_grad=True)
y=torch.tensor(4.0,requires_grad=False)
z=torch.pow(x,2)+torch.pow(y,2)
#判断x,y是否是可以求导的
print(x.requires_grad)
print(y.requires_grad)
print(z.requires_grad)
#求导,通过backward函数来实现
z.backward()
#查看导数,也即所谓的梯度
print(x.grad)
print(y.grad)
运行结果为:
True # x是可导的
False # y是不可导的
True # z是可导的,因为它有一个 leaf variable 是可导的,即x可导
tensor(6.) # x的导数
None # 因为y不可导,所以是none
如果是上面的 leaf variable变量x也设置为不可导的,那么z也不可导,因为x、y均不可导,那么z自然不可导了。
(2)leaf variable(也是tensor)的requires_grad_()方法
如果某一个叶子变量,开始时不可导的,后面想设置它可导,或者反过来,该怎么办呢?tensor提供了一个方法,即
x.requires_grad_(True/False) 设置tensor的可导与不可导,注意后面有一个下划线哦!
但是需要注意的是,我只能够设置叶子变量,即leaf variable的这个方法,否则会出现以下错误:
RuntimeError: you can only change requires_grad flags of leaf variables.
1.2 函数的求导方法——y.backward()方法
上面只演示了简单函数的求导法则,
需要注意的是:如果出现了复合函数,比如 y是x的函数,z是y的函数,f是z的函数,那么在求导的时候,会使用 f.backwrad()只会默认求f对于叶子变量leaf variable的导数值,而对于中间变量y、z的导数值是不知道的,直接通过x.grad是知道的、y.grad、z.grad的值为none。
下面来看一下这个函数backward的定义:
backward(gradient=None, retain_graph=None, create_graph=False)
它的三个参数都是可选的,上面的示例中还没有用到任何一个参数,关于这三个参数,我后面会详细说到,这里先跳过。
1.3 查看求得的导数的值——x.grad属性
通过tensor的grad属性查看所求得的梯度值。
总结:
(1)torch.tensor()设置requires_grad关键字参数
(2)查看tensor是否可导,x.requires_grad 属性
(3)设置叶子变量 leaf variable的可导性,x.requires_grad_()方法
(4)自动求导方法 y.backward() ,直接调用backward()方法,只会计算对计算图叶节点的导数。
(5)查看求得的到数值, x.grad 属性
易错点:
为什么上面的标量x的值是3.0和4.0,而不是整数呢?这是因为,要想使x支持求导,必须让x为浮点类型,也就是我们给初始值的时候要加个点:“.”。不然的话,就会报错。 即,不能定义[1,2,3],而应该定义成[1.,2.,3.],前者是整数,后者才是浮点数,浮点数才能求导。
二、求导的核心函数——backwrad函数详解
2.1 默认的求导规则
在pytorch里面,默认:只能是【标量】对【标量】,或者【标量】对向【量/矩阵】求导!这个很关键,很重要!
(1)标量对标量求导
参见上面的例子,x,y,z都是标量,所以求导过程也很简单,不再赘述。
(2)标量对向量/矩阵求导
为什么标量对于向量/矩阵是默认的呢?因为在深度学习中,我们一般在求导的时候是对损失函数求导,损失函数一般都是一个标量,即将所有项的损失加起来,但是参数又往往是向量或者是矩阵,所以这就是默认的了。看下面的例子。
比如有一个输入层为3节点的输入层,输出层为一个节点的输出层,这样一个简单的神经网络ÿ