【CS231n-4】反向传播和神经网络

如标题描述,本节笔记分为两个部分。

反向传播(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运行代码,可以观察到逐渐收敛的过程:


跟脑科学相关的部分本文不再赘述。