1.全连接层的推导

  全连接层的每一个结点都与上一层的所有结点相连,用来把前边提取到的特征综合起来。由于其全相连的特性,一般全连接层的参数也是最多的。

2.全连接层的前向计算

  下图中连线最密集的2个地方就是全连接层,这很明显的可以看出全连接层的参数的确很多。在前向计算过程,也就是一个线性的加权求和的过程,全连接层的每一个输出都可以看成前一层的每一个结点乘以一个权重系数W,最后加上一个偏置值b得到。如下图中最后一个全连接层,输入有100个神经元结点,输出有10个结点,则一共需要100*10=1000个权值参数W和10个偏置参数b。

  

python实现卷积滑动窗口快速计算 python卷积层_全连接

2.1 推导过程

  下面用一个简单的网络具体介绍一下推导过程



python实现卷积滑动窗口快速计算 python卷积层_全连接_02


其中,x1、x2、x3为全连接层的输入,a1、a2、a3为输出,有



python实现卷积滑动窗口快速计算 python卷积层_python实现卷积滑动窗口快速计算_03

  

可以写成如下矩阵形式:



python实现卷积滑动窗口快速计算 python卷积层_结点_04


 若我们的一次训练10张图片,即batch_size=10,则我们可以把前向传播计算转化为如下矩阵形式。

python实现卷积滑动窗口快速计算 python卷积层_反向传播_05

2.2 全连接层的前向计算代码

def forward(self, in_data):
        self.bottom_val = in_data
        self.top_val = in_data.dot(self.w) + self.b
        return self.top_val

3 全连接层的反向传播

  以我们的最后一个全连接层为例,该层有100个输入结点和10个输出结点。



python实现卷积滑动窗口快速计算 python卷积层_python实现卷积滑动窗口快速计算_06


  由于需要对W和b进行更新,还要向前传递梯度,所以我们需要计算如下三个偏导数

【注:一般情况下每层前向传播都会更新三个参数】

3.1 对上一层的输出(即当前层的输入)求导

∂loss∂a ∂ l o s s ∂ a ,则我们可以通过链式法则求得loss对x的偏导数。
  首先需要求得该层的输出ai a i 对输入xj x j 的偏导数:∂ai∂xj=∑100jwij∗xj∂xj ∂ a i ∂ x j = ∑ j 100 w i j ∗ x j ∂ x j

∂loss∂xk=∑100j∂loss∂aj∂aj∂xk=∑100j∂loss∂aj∗wkj ∂ l o s s ∂ x k = ∑ j 100 ∂ l o s s ∂ a j ∂ a j ∂ x k = ∑ j 100 ∂ l o s s ∂ a j ∗ w k j
【注意:这里的xk x k 是向量,xk x k 可以是任何一个xj x j 】

  上边求导的结果也印证了我前边那句话:在反向传播过程中,若第x层的a节点通过权值W对x+1层的b节点有贡献,则在反向传播过程中,梯度通过权值W从b节点传播回a节点。

  若我们的一次训练10张图片,即batch_size=10,则我们可以把计算转化为如下矩阵形式。



python实现卷积滑动窗口快速计算 python卷积层_反向传播_07


3.2 对权重系数W求导

我们前向计算的公式如下图



python实现卷积滑动窗口快速计算 python卷积层_python实现卷积滑动窗口快速计算_03

  

由图可知∂ai∂wij=xj ∂ a i ∂ w i j = x j ,所以:∂loss∂wkj=∂loss∂ak∂ak∂wkj=∂loss∂ak∗xj ∂ l o s s ∂ w k j = ∂ l o s s ∂ a k ∂ a k ∂ w k j = ∂ l o s s ∂ a k ∗ x j

当batch_size=10时,写成矩阵形式:



python实现卷积滑动窗口快速计算 python卷积层_全连接_09


3.3 对偏置系数b求导

∂ai∂bi=1 ∂ a i ∂ b i = 1

  即loss对偏置系数的偏导数等于对上一层输出的偏导数。

  当batch_size=10时,将不同batch对应的相同b的偏导相加即可,写成矩阵形式即为乘以一个全1的矩阵:



python实现卷积滑动窗口快速计算 python卷积层_python实现卷积滑动窗口快速计算_10


小结:

注意,这里也可以从矩阵求导的方面的出上边的结论:
0.正向传播矩阵计算公式是:


Top_data=Bottom_data∗W+c∗Bias T o p _ d a t a = B o t t o m _ d a t a ∗ W + c ∗ B i a s

这里c = [1,1,1,1,1,1,1,1,1,1]的列向量,Bias是行向量。

1.对输入数据的求导:


Bottom_diff=Top_diff∗WT B o t t o m _ d i f f = T o p _ d i f f ∗ W T


2.对weight的求导:



weight_diff=Bottom_dataT∗Top_diff w e i g h t _ d i f f = B o t t o m _ d a t a T ∗ T o p _ d i f f


3.对bias求导:



bias_diff=cT∗Top_diff b i a s _ d i f f = c T ∗ T o p _ d i f f

可以看到,对各个参数矩阵求导时,得到的是将参数前的系数矩阵转置,然后在乘以从后边传来的Top_diff T o p _ d i f f 。

4.代码

具体代码请访问我的github仓库:csdn/FClayer/

4.1.全连接层:正向传播,随机初始化w,b

# 全连接层:正向传播

import numpy as np

x = np.arange(1,11,1).reshape(2,5)

print ("上层输出是2个batch,每个batch有5个向量:\n" + str(x))



ww = std * np.random.randn(5,3)

bb = std * np.zeros(3)

print ("\n随机初始化w的参数的shape是:\n" + str(ww.shape))

print ("\n随机初始化w的参数是:\n" + str(ww))

print ("\n随机初始化b的参数的shape是:\n" + str(bb.shape))

print ("\n随机初始化b的参数是:\n" + str(bb))

a = x.dot(ww) + bb

print ("\n全连接之后输出层的shape是:\n" + str(a.shape))

print ("\n全连接之后输出是:\n" + str(a))

输出:

上层输出是2个batch,每个batch有5个向量:
[[ 1  2  3  4  5]
 [ 6  7  8  9 10]]

随机初始化w的参数的shape是:
(5, 3)

随机初始化w的参数是:
[[ -2.02246258e-04  -2.86134686e-05   5.67536552e-05]
 [ -2.65147732e-05   3.46693399e-05  -4.84865213e-05]
 [ -8.56741462e-05  -1.36582254e-04  -1.63278178e-04]
 [  7.67300884e-05   4.15051110e-05   3.05435127e-05]
 [  2.03954211e-05   7.60062287e-05  -2.58280679e-04]]

随机初始化b的参数的shape是:
(3,)

随机初始化b的参数是:
[ 0.  0.  0.]

全连接之后输出层的shape是:
(2, 3)

全连接之后输出是:
[[-0.0001034   0.00017703 -0.00169928]
 [-0.00118995  0.00011195 -0.00361302]]

4.2 全连接,反向传播

# 1.对上一层的输出(即当前层的输入)求导

loss = np.arange(0,1.2,0.2).reshape(2,3)

print ("假设下层传过来的loss是:\n" + str(loss))

residual_x = loss.dot(ww.T)

print ("\n1.对上层的输出求导值的shape:\n" + str(residual_x.shape))

print ("\n对上层的输出求导:\n" + str(residual_x))







# 2.对权重系数W求导

lr = 0.01

reg = 0.75

prev_grad_w = np.zeros_like(ww)

ww -=  lr * (x.T.dot(loss) + prev_grad_w * reg)

prev_grad_w = ww



print ("\n2.对权重系数W求导之后更新的W值的shape:\n" + str(prev_grad_w.shape))

print ("\n对权重系数W求导之后更新的W:\n" + str(prev_grad_w))



# 3.对偏置系数b求导

prev_grad_b = np.zeros_like(bb)

bb -= lr * (np.sum(loss, axis=0))

prev_grad_b = bb



print ("\n2.对偏置系数b求导之后更新的b值的shape:\n" + str(prev_grad_b.shape))

print ("\n对偏置系数b求导之后更新的b:\n" + str(prev_grad_b))

输出:

假设下层传过来的loss是:
[[ 0.   0.2  0.4]
 [ 0.6  0.8  1. ]]

1.对上层的输出求导值的shape:
(2, 5)

对上层的输出求导:
[[-0.14238302 -0.17281246 -0.20329263 -0.23357948 -0.26408811]
 [-0.50248748 -0.60483666 -0.70752395 -0.80949021 -0.91218524]]

2.对权重系数W求导之后更新的W值的shape:
(5, 3)

对权重系数W求导之后更新的W:
[[-0.18020225 -0.25002861 -0.31994325]
 [-0.21002651 -0.29996533 -0.39004849]
 [-0.24008567 -0.35013658 -0.46016328]
 [-0.26992327 -0.39995849 -0.52996946]
 [-0.2999796  -0.44992399 -0.60025828]]

3.对偏置系数b求导之后更新的b值的shape:
(3,)

对偏置系数b求导之后更新的b:
[-0.03 -0.05 -0.07]