您的位置:首页 > 理论基础 > 计算机网络

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

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

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

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

下面是这篇博客的内容:

affine backward

relu backward

svm loss backward

softmax loss backward

affine backward

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

out=x∗w+bout=x∗w+b

整个过程可由如下图所示,这里画出示意图是为了后面求导时的方便起见,



当这层的后一层反向传播到这一层的值为doutdout的时候,我们需要求得这一层的dxdx, dwdw和dbdb, 整个的求解思路如下图所示,



先求ww和xx, 而求他们的前提是求出yy的导数,因而dydy的求解根据链式法则如下,

dy=dout∗∂out∂y=doutdy=dout∗∂out∂y=dout

而后dwdw和dxdx的求解如下所示,

dw=dy∗∂y∂w=dy∗xdw=dy∗∂y∂w=dy∗x

dx=dy∗∂y∂x=dy∗wdx=dy∗∂y∂x=dy∗w

同理dbdb的求解和dydy相同,

db=dout∗∂out∂b=doutdb=dout∗∂out∂b=dout

注意上述的所有的变量都是向量,之后还要考虑矩阵的求导问题,现在我们开始着重讲一下这一点,假设输入xx为N∗DN∗D大小的矩阵,ww 是D∗HD∗H的矩阵, bb为1∗H1∗H的矩阵,公式的矩阵表示形式如下,



如何得到∂out∂x∂out∂x呢,我试着从一个最基本的角度来讲,拿x11x11来说吧,它对outout的哪些成员有影响呢? 这里先抛开bb不讲,且直接从outout跳到xx和ww,因为yy只是一个中间媒介,

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

dx=dout∗w.Tdx=dout∗w.T

而理所当然的,

dw=x.T∗doutdw=x.T∗dout

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

db=sum(dout,axis=0)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)out=max(0,x)

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

dx={dout0ififx>0x<=0dx={doutifx>00ifx<=0

编程实现的时候只需注意一点,dxdx的取值取决于xx, 而不是取决于doutdout,这点一定要谨记,下面是对应的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),其中xixi是样本的值,yiyi是样本的标签,根据“一系列的计算”(这里说的就是上文的affine+relu)得到这个样本的得分向量s⃗ =f(xi,W)s→=f(xi,W), 而这个样本的svm_loss就是如下公式:

Li=∑j≠yimax(0,sj−syi+1)Li=∑j≠yimax(0,sj−syi+1)

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

L=1N∑i=1NLiL=1N∑i=1NLi

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

dsj=1N{1 if sj−syi+1>00 if sj−syi+1≤0dsj=1N{1 if sj−syi+1>00 if sj−syi+1≤0

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

∑1{sj−syi+1>0}∑1{sj−syi+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)=esk∑jesjP(Y=k|X=xi)=esk∑jesj

而LiLi表示如下:

Li=−logP(Y=yi|X=xi)Li=−logP(Y=yi|X=xi)

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

L=1N∑i=1NLiL=1N∑i=1NLi

需要求解dsj(j≠yi)dsj(j≠yi)和dsyidsyi,对于dsj(j≠yi)dsj(j≠yi),根据求导法则有如下公式:

∂Li∂sj=−∑jesjesyi⋅(−esyi⋅esj(∑jesj)2)=esj∑jesj∂Li∂sj=−∑jesjesyi⋅(−esyi⋅esj(∑jesj)2)=esj∑jesj

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

∂Li∂syi=esyi∑jesj−1∂Li∂syi=esyi∑jesj−1

希望大家能够仔细推导, 下面是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的代码实现,能够清楚的理清矩阵的求导运算,希望能够对大家在深度学习学习的路上有所帮助,如有不对的地方,希望大家能够多多指出。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: