sponsored links

神经网络中的反向传播的推导和python实现

事先声明一下,这篇博客的适用人群是对于卷积神经网络的基本结构和每个模块都基本了解的同学。当然,如果各位大神看到我这篇博客有什么不对的地方请大家积极指出哈,我一定好好改正,毕竟学习是一个不断改进的过程。

之前学习反向传播的时候,对于矩阵的求导有些疑问,最近看到cs231n上的assignment1和assignment2上都有对应的习题,我觉得是一个不错的机会来彻底搞清楚。

这里先开一个小差,我自己特别不喜欢推公式,因为推了几篇公式之后,编程实现就是这么几行,感觉超级不爽,终于明白之前最初涉及推公式的时候看到网上有人说这个现象的时候的不爽了,感觉有一种怀才不遇的感觉。。。废话少说,下面开始进入主题。

下面是这篇博客的内容:

  • affine backward
  • relu backward
  • svm loss backward
  • softmax loss backward

affine backward

先从最简单的说起,就是最简单的affine layer(全连接层)的反向传播,在这一层中的前向传播如下公式,x是这层的输入,w是这层的参数,b是这层的偏置, out是这层的输入,

out=xw+b

整个过程可由如下图所示,这里画出示意图是为了后面求导时的方便起见,
神经网络中的反向传播的推导和python实现

当这层的后一层反向传播到这一层的值为dout的时候,我们需要求得这一层的dx, dwdb, 整个的求解思路如下图所示,
神经网络中的反向传播的推导和python实现

先求wx, 而求他们的前提是求出y的导数,因而dy的求解根据链式法则如下,

dy=doutouty=dout

而后dwdx的求解如下所示,

dw=dyyw=dyx

dx=dyyx=dyw

同理db的求解和dy相同,

db=doutoutb=dout

注意上述的所有的变量都是向量,之后还要考虑矩阵的求导问题,现在我们开始着重讲一下这一点,假设输入xND大小的矩阵,wDH的矩阵, b1H的矩阵,公式的矩阵表示形式如下,
神经网络中的反向传播的推导和python实现

如何得到outx呢,我试着从一个最基本的角度来讲,拿x11来说吧,它对out的哪些成员有影响呢? 这里先抛开b不讲,且直接从out跳到xw,因为y只是一个中间媒介,

可以看到(out11,out12,...,out1H)都和x11有关,且影响程度分别为(w11,w12,...,w1H), 因而当反向传播的时候,该考虑如何计算dx了,那么理所应当的就是dout11w11+dout12w12+...+out1Hw1H, 而对于所有的dx来说,就有如下公式,

dx=doutw.T

而理所当然的,

dw=x.Tdout

而对于b,由于b是一个行向量,它的每个元素对于out的影响是一整列的,且影响程度一致(outb = 1),因而db的求解如下,

db=sum(dout,axis=0)

以上就是affine backward的求解,可能有些跳跃,但是总体思路还是清楚的。

下面是python 的代码:


def affine_backward(dout, cache):   
    """    
    Computes the backward pass for an affine layer.    
    Inputs:    
    - dout: Upstream derivative, of shape (N, M)    
    - cache: Tuple of: 
    - x: Input data, of shape (N, d_1, ... d_k)    
    - w: Weights, of shape (D, M)    
    Returns a tuple of:   
    - dx: Gradient with respect to x, of shape (N, d1, ..., d_k)    
    - dw: Gradient with respect to w, of shape (D, M) 
    - db: Gradient with respect to b, of shape (M,)    
    """    
    x, w, b = cache    
    dx, dw, db = None, None, None   
    dx = np.dot(dout, w.T)                       # (N,D)    
    dx = np.reshape(dx, x.shape)                 # (N,d1,...,d_k)   
    x_row = x.reshape(x.shape[0], -1)            # (N,D)    
    dw = np.dot(x_row.T, dout)                   # (D,M)    
    db = np.sum(dout, axis=0, keepdims=True)     # (1,M)    

    return dx, dw, db

relu_backward

relu的公式如下,

out=max(0,x)

当反向传播的量为dout的时候,max函数本身并不可导,但是我们可以分段求导,有如下公式,

dx={doutifx>00ifx<=0

编程实现的时候只需注意一点,dx的取值取决于x, 而不是取决于dout,这点一定要谨记,下面是对应的python代码,


def relu_backward(dout, cache):   
    """  
    Computes the backward pass for a layer of rectified linear units (ReLUs).   
    Input:    
    - dout: Upstream derivatives, of any shape    
    - cache: Input x, of same shape as dout    
    Returns:    
    - dx: Gradient with respect to x    
    """    
    dx, x = None, cache    
    dx = dout    
    dx[x <= 0] = 0    

    return dx

SVM_loss backward

在说softmax_loss之前先说一个比较轻松的话题,就是svm_loss, 虽然在神经网络的实际运用中运用的着实不多,但是还是有必要介绍一下,来扩充一下大家的知识面,这里值得注意的是下文的推导和后面的softmax_loss的推导都没有添加正则项,但是正则项的求导很好求,这里不再赘述,希望大家谅解。

原公式是这样说的,一个训练样本(xi,yi),其中xi是样本的值,yi是样本的标签,根据“一系列的计算”(这里说的就是上文的affine+relu)得到这个样本的得分向量s=f(xi,W), 而这个样本的svm_loss就是如下公式:

Li=jyimax(0,sjsyi+1)

而所有样本的svm_loss是如下公式:

L=1Ni=1NLi

如今需要求解这个损失函数的dsj(jyi)dsyi,其实这是一个很简单的求导问题,只要细心都是可以做出来的,首先分析一下dsj(jyi), 从宏观上来看sjL的影响只有一项就是1NLi, 因为它和别的样本是没有关系的,而再具体一步,sj对于Li的影响当sjsyi+1>0的时候是1, 否则为0, 这就是dsj(jyi),即如下公式:

dsj=1N{1 if sjsyi+1>00 if sjsyi+10

而对于dsyi, 从宏观上来看syiL的影响也是只有一项1NLi, 具体一步,对于Li的影响为那些sjsyi+1>0sj个数之和的负数,从公式中就可以看出,因而dsyi为如下公式, 其中1{s}表示当s为真,表达式的值为1,否则为0,

1{sjsyi+1>0}

根据上文的描述,计算svm_loss的python代码如下:

def svm_loss(x, y):   
    """    
    Computes the loss and gradient using for multiclass SVM classification.    
    Inputs:    
    - x: Input data, of shape (N, C) where x[i, j] is the score for the jth class         
         for the ith input.    
    - y: Vector of labels, of shape (N,) where y[i] is the label for x[i] and         
         0 <= y[i] < C   
    Returns a tuple of:    
    - loss: Scalar giving the loss   
    - dx: Gradient of the loss with respect to x    
    """    
    N = x.shape[0]   
    correct_class_scores = x[np.arange(N), y]    
    margins = np.maximum(0, x - correct_class_scores[:, np.newaxis] + 1.0)    
    margins[np.arange(N), y] = 0   
    loss = np.sum(margins) / N   
    num_pos = np.sum(margins > 0, axis=1)    
    dx = np.zeros_like(x)   
    dx[margins > 0] = 1    
    dx[np.arange(N), y] -= num_pos    
    dx /= N    

    return loss, dx

softmax_loss backward

上文的svm_loss 已经把样本的准备情况交代清楚了,而后这一节来介绍softmax_loss, 具体的推导细节就不再赘述了,(如果大家想了解的再清楚一点,请参照cs231n lecture3的关于softmax_loss的解释,也可以参照cs229的softmax详解),

和上文类似,经过一系列推导得到的得分向量公式如下:

P(Y=k|X=xi)=eskjesj

Li表示如下:
Li=logP(Y=yi|X=xi)

而所有样本的softmax_loss是如下公式:

L=1Ni=1NLi

需要求解dsj(jyi)dsyi,对于dsj(jyi),根据求导法则有如下公式:

Lisj=jesjesyi(esyiesj(jesj)2)=esjjesj

而对于dsyi,求解起来相对困难,省略中间步骤,有如下公式:

Lisyi=esyijesj1

希望大家能够仔细推导, 下面是python的代码表示:

def softmax_loss(x, y):    
    """    
    Computes the loss and gradient for softmax classification.    Inputs:    
    - x: Input data, of shape (N, C) where x[i, j] is the score for the jth class         
    for the ith input.    
    - y: Vector of labels, of shape (N,) where y[i] is the label for x[i] and         
         0 <= y[i] < C   
    Returns a tuple of:    
    - loss: Scalar giving the loss    
    - dx: Gradient of the loss with respect to x   
    """    
    probs = np.exp(x - np.max(x, axis=1, keepdims=True))    
    probs /= np.sum(probs, axis=1, keepdims=True)    
    N = x.shape[0]   
    loss = -np.sum(np.log(probs[np.arange(N), y])) / N    
    dx = probs.copy()    
    dx[np.arange(N), y] -= 1    
    dx /= N    

    return loss, dx

总结

本片博客介绍了四种在神经网络中比较常用的函数的反向传播的推导和python的代码实现,能够清楚的理清矩阵的求导运算,希望能够对大家在深度学习学习的路上有所帮助,如有不对的地方,希望大家能够多多指出。

Tags: