前向传播,就是依据计算流程实现数据(data)的计算和计算图的构建:
反向传播,反向传播就是依据所构建计算图的反方向递归求导和赋值梯度
、
图来描述
其中,在前向传播过程中,是按照流程完成从初始输入(一般是训练数据+网络权重)直至最终输出(一般是损失函数)的计算过程,同步完成计算图的构建;而在反向传播过程中,则是通过调用loss.backward()函数,依据计算图的相反方向递归完成各级求导(本质上就是求导的链式法则)。同时,对于requires_grad=False的tensor,在反向传播过程中实际不予以求导和更新,相应的反向链条被切断。
Tensor 求导代码
1、求导相关函数
- 在一个Tensor数据结构中,最核心的属性是data,这里面存储了Tensor所代表的高维数组(当然,这里虽然称之为高维,但实际上可以是从0维开始的任意维度);
- 通过requires_grad参数控制两个属性,grad和grad_fn,其中前者代表当前Tensor的梯度,后者代表经过当前Tensor所需求导的梯度函数;当requires_grad=False时,grad和grad_fn都为None,且不会存在任何取值,而只有当requires_grad=True时,此时grad和grad_fn初始取值仍为None,但在后续反向传播中可以予以赋值更新。
- backward(),是一个函数,仅适用于标量Tensor,即维度为0的Tensor。
- is_leaf:标记了当前Tensor在所构建的计算图中的位置,其中计算图既可看做是一个有向无环图(DAG),也可视作是一个树结构。当Tensor是初始节点时,即为叶子节点,is_leaf=True,否则为False。
1.创建训练数据x, y和初始权重w, b
x = torch.tensor([1.,2.]) y = torch.tensor([5.,7.]) #初始权重, w = 1.0, b = 0.0 w = torch.tensor(1.0,requires_grad = True) b = torch.tensor(0.0,requires_grad = True)
# 1. 注意:x和y设置为requires_grad=False x.grad, x.grad_fn, x.is_leaf, y.grad, y.grad_fn, y.is_leaf # 输出:(None, None, True, None, None, True) # 2. w和b初始梯度均为None,且二者均为叶子节点 w.grad, w.grad_fn, w.is_leaf, b.grad, b.grad_fn, b.is_leaf # 输出:(None, None, True, None, None, True)
2.构建计算流程,实现前向传播
# 按计算流程逐步操作,实现前向传播 wx = w*x wx_b = wx+b loss = (wx_b -y) loss2 = loss**2 loss2_sum = sum(loss2)
# 1.查看是否叶子节点 wx.is_leaf, wx_b.is_leaf, loss.is_leaf, loss2.is_leaf, loss2_sum.is_leaf # 输出:(False, False, False, False, False) # 2.查看grad wx.grad, wx_b.grad, loss.grad, loss2.grad, loss2_sum.grad # 触发Warning # UserWarning: The .grad attribute of a Tensor that is not a leaf Tensor is being accessed. Its .grad attribute won't be populated during autograd.backward(). If you indeed want the .grad field to be populated for a non-leaf Tensor, use .retain_grad() on the non-leaf Tensor. If you access the non-leaf Tensor by mistake, make sure you access the leaf Tensor instead. See github.com/pytorch/pytorch/pull/30531 for more informations. (Triggered internally at aten\src\ATen/core/TensorBody.h:417.) return self._grad # 输出:(None, None, None, None, None) 3.查看grad_fn (梯度函数) wx.grad_fn, wx_b.grad_fn, loss.grad_fn, loss2.grad_fn, loss2_sum.grad_fn # 输出: (<MulBackward0 at 0x23a875ee550>, <AddBackward0 at 0x23a875ee8e0>, <SubBackward0 at 0x23a875ee4c0>, <PowBackward0 at 0x23a875ee490>, <AddBackward0 at 0x23a93dde040>)
3.对最终的loss调用backward,实现反向传播
loss2_sum.backward()
# 1. 中间变量(非叶子节点)的梯度仅用于反向传播,但不对外暴露 wx.grad, wx_b.grad, loss.grad, loss2.grad, loss2_sum.grad # 输出:(None, None, None, None, None) # 2. 检查叶子节点是否获得梯度:w, b均获得梯度,x, y不支持求导,仍为None w.grad, b.grad, x.grad, y.grad # 输出:(tensor(-28.), tensor(-18.), None, None)
至此,即通过前向传播的计算图和反向传播的梯度传递,完成了初始权重参数的梯度赋值过程。注意,这里w和b是网络待优化参数,而一旦二者有了梯度,则可进一步应用梯度下降法予以更新。
手动演示w.grad和b.grad如何得到
#(x, y)=(1, 5)和(x, y)=(2, 7) 代入 w的梯度:2*(1*1 + 0 - 5)*1 + 2*(1*2 + 0 - 7)*2 = -28 b的梯度:2*(1*1 + 0 - 5) + 2*(1*2 + 0 - 7) = -18
注意:在多个训练数据(batch_size)参与一次反向传播时,返回的参数梯度是在各训练数据上的求得的梯度之和。