如标题描述,本节笔记分为两个部分。
反向传播(Backpropagation)
引入
已知$f(x,y,z) = (x+y)z$,求 $\frac{\partial f}{\partial x} $ , $\frac{\partial f}{\partial y} $, $\frac{\partial f}{\partial z} $.
显然,我们可以直接按照偏导数的定义求解,得$\frac{\partial f}{\partial x} = z$, $\frac{\partial f}{\partial y} = z$, $\frac{\partial f}{\partial z} = x+y$.
但是,神经网络往往非常庞大,对于特别复杂的函数式而言,为所有参数手写梯度公式是不切实际的,这就引入了反向传播的计算方法。
定义
Backpropagation : Recursive application of the chain rule along a computational graph to compute the gradients of all inputs/parameters/intermediates.
From CS231n_2017 Slides4 P81
即:反向传播是链式求导法则在计算图中的的递归应用,用于计算每个输入、参数和中间节点的梯度值。
接下来针对定义中的若干概念展开。
计算图(Computational graph)
可以理解成离散数学或数据结构中用树型结构表达的计算式,但因为在神经网络中,一个节点可以有多个父节点,所以这里和树的定义不同。
链式求导法则: \(\frac{\partial f}{\partial x} = \frac{\partial f}{\partial q} * \frac{\partial q}{\partial x}\) 常见的计算图如下:
常见模式(Pattrens)
- 加法节点:保持反向传入的梯度不变
- max函数节点:“梯度路由器”,大者获得传入的梯度。
- 乘法节点:反向传入的梯度乘变量原值,当有两个输入节点时值互换。
例如下图所示:
传入参数为向量时的计算方法
找到每个元素对结果的影响。例如下图中求$q$的梯度,则观察$f(q)$得知,每个$q$都独立影响着$L2$计算结果的输出。
再往前一步的计算略微复杂,首先写出正向计算式并观察: \(q = W\cdot x= \begin{pmatrix} W_{1,1}x_1+W_{1,2}x_2\\W_{2,1}x_1+W_{2,2}x_2 \end{pmatrix}\) 发现$W_{i,j}$只与$x_j$相关,即: \(\frac{\partial q_k}{\partial W_{i,j}}= \left \{ \begin{aligned} &0 &k\neq i\\ &x_j &k=i \end{aligned} \right.\) 利用链式法则求$W_{i,j}$的梯度: \(\frac{\partial f}{\partial W_{i,j}} = 2q_ix_j\) 对x同理,得如下结果。
算法实现
保持计算图的结构,为每个节点实现forward()和 backward()的API。
class ComutationalGraph(object)
#...
def forward(inputs):
#1.[pass inputs to input gates...]
#2.forward the computational graph:
for gate in self.graph.nodes_topologically_sorted():
gate.forward()
return loss #the final gate in the graph outputs the loss
def backward():
for gate in reversed(self.graph.nodes_topologically_sorted()):
gate.backward() #little piece of backprop(chain rule applied)
return inputs_gradients
神经网络(Neural Networks)
一个双层神经网络的例子:$f = W_2 max(0,W_1x)$
- 注意:$max$函数在这里是一个非线性函数,如果不添加类似的非线性函数,则起不到多层网络的作用,而坍缩(Collapse)为一个单层的线性函数。详细内容将会在后续课程中深入讨论。
一般而言,神经网络是一类由简单的函数堆叠而成的非线性函数。
例:快速实现一个双层神经网络
import numpy as np
from numpy.random import randn
N, D_in, H, D_out = 64, 1000, 100, 10
x, y = randn(N, D_in), randn(N, D_out)
w1, w2 = randn(D_in, H), randn(H, D_out)
for t in range(2000):
h = 1/(1 + np.exp(-x.dot(w1)))
y_pred = h.dot(w2)
loss = np.square(y_pred - y).sum()
print(t, loss)
#back propagation
grad_y_pred = 2.0 * (y_pred - y)
grad_w2 = h.T.dot(grad_y_pred)
grad_h = grad_y_pred.dot(w2.T)
grad_w1 = x.T.dot(grad_h * h * (1 - h))
#learn
w1 -= 1e-4 * grad_w1
w2 -= 1e-4 * grad_w2
其中,
N:样本数量
D_in:输入维度
H:中间层
D_out:输出维度(10个类别的Score)
X:数据集
Y:输出
w1:第一层网络的系数
w2:第二层网络的系数
用Jupiter-notebook运行代码,可以观察到逐渐收敛的过程:
跟脑科学相关的部分本文不再赘述。