PyTorch创建模型的一般的写法是: outputs = Your_model(data_x) optimizer.zero_grad() loss = loss_function(data_y,outputs) loss.backward() optimizer.step() 这里的loss不是一个tensor吗? 这个tensor就是存了一个值, loss.backward()会实现一个什么样的过程? 是计算梯度吗? 感觉这里的loss和网络没什么关系啊? 是outputs 经过loss_function使得loss这个tensor和网络间接产生了关联, 所以程序可以自动识别网络的参数结构并以此来求梯度吗?
首先是构建计算图,loss.backward()的时候就是走一遍反向图。
举个例子就明白了:
例子定义
为了简单起见,就假设只有一个训练样本( x , t )。网络模型是一个线性模型,带有一个非线形的sigmoid
层,然后用均方差作为其Loss
函数,这个模型用公式可以表示为如下形式:
其中λ 是超参数。
想要做梯度下降的话,我们就需要对w和b求偏微分:∂ E / ∂ w 和∂ E / ∂ b 。
将目标函数展开可以表示为:
对w 求偏导数可以得到:
对b 求偏导数可以得到:
述方法确实能够得出正确的解,但是有以下缺陷:
- 计算非常冗余复杂;
- 上述计算过程中有很多地方是重复计算的,比如w x + b 计算了四次, ( σ ( w x + b ) − t ) σ ′ ( w x + b ) 计算了两次。
多变量链式求导:简单的例子
其实上述方法就是在计算一元链式求导多次,一元链式求导可以定义为如下形式:
多变量的求导可以定义为:
为了方便叙述,定义一个符号,比如对v 的导数可以定义为:
多变量求导:计算图模式
上述带有正则化的模型用计算图可以表示为:
整个模型可以表示为:
反向传播的时候,我们需要去计算得到w ˉ 和b ˉ, 就需要反复利用链式求导计算偏微分。
也就是要从结果(这里定义为E ,一步一步往前去计算它的前一个节点的导数。定义v 1 , ⋯ , v N是计算图中的所有节点,并且以输入到输出的拓扑顺序进行排序的。
我们期望去计算得到所有节点的偏导数v ˉ , 神经网络工作的时候就是走一遍前向传播,然后走一遍反向传播。最末尾的这个节点是v N, 我们也需去得到它的偏导数,为了方便计算,我们通常令其偏导数为1。也就是v N ˉ = 1 。
此时整个算法的逻辑可以表示为:
我们再进行反向传播,来计算y ˉ 和z ˉ
最后到了我们需要更新的参数w 和b
总结一下推导过程为:
总结一下最终结果为:
可以看到相比之前的推导偏微分方程的方式,这种反向传播的方式更为简洁。
PyTorch中loss.backward()的时候就是把上述这个最终结果走一遍,因为创建model的时候是调用模块的,所以计算偏导数的时候,之前就计算好了偏导数是多少,把值带入进去就可以了。
下面这篇文章有兴趣可以看
- Automatic differentiation in machine learning: a survey 【https://arxiv.org/abs/1502.05767】