对神经网络有一定了解的同学知道:一般的神经网络的具体实现都是通过矩阵实现的,包括误差反向传导,梯度计算和更新等等,比如
y
=
w
∗
x
+
b
y=w*x+b
y
=
w
∗
x
+
b
,这里的所有变量都是矩阵,我们通常会叫
w
w
w
和
b
b
b
为参数矩阵,矩阵相乘首先效率比较高,然后也比较好操作,那么对于矩阵形式的导数该怎么计算?比如
w
w
w
矩阵的具体导数
d
w
dw
d
w
应该如何计算?
首先我们来看一个只有输入和输出层的网络,我们输入
X
X
X
矩阵大小为
N
∗
D
N*D
N
∗
D
,
N
N
N
为batch的大小,也就是一次性输入的样本数,
D
D
D
为输入数据的维度,也就是输入层神经元的个数,输出矩阵
Y
Y
Y
大小为
N
∗
M
N*M
N
∗
M
,
M
M
M
为输出数据的维度,也就是输出层神经元的个数, 可以看成输入数据X经过这个网络的变换,数据维度由
D
D
D
映射到了
M
M
M
维,那么
w
w
w
矩阵的大小自然就是
D
∗
M
D*M
D
∗
M
1. 当前层
d
x
dx
d
x
的求法
上图中
X
j
,
1
X_{j,1}
X
j
,
1
表示第j个样本输入数据的第一个值,我们现在就考虑这个值的梯度要如何求,
d
Y
j
,
1
dY_{j,1}
d
Y
j
,
1
表示第j个样本回传梯度的第一个值(通过链式法则,梯度是一层一层向后传递,你可以理解这个
d
Y
dY
d
Y
就是从下一层的网络传回的梯度),现在我们已知
w
w
w
和传回的
d
Y
dY
d
Y
,那么这个理论上分析
d
x
dx
d
x
肯定等于
w
∗
d
Y
w*dY
w
∗
d
Y
(因为
Y
Y
Y
对
X
X
X
的导数就是
w
w
w
,而回传的梯度
d
Y
dY
d
Y
其实是损失函数
L
L
L
对
Y
Y
Y
的导数, 即
d
L
/
d
x
=
d
L
/
d
Y
∗
d
Y
/
d
x
dL/dx=dL/dY * dY/dx
d
L
/
d
x
=
d
L
/
d
Y
∗
d
Y
/
d
x
,对于
d
X
j
,
1
dX_{j,1}
d
X
j
,
1
来说,它的梯度为跟它连接的
w
w
w
参数与第j个样本所有
d
Y
dY
d
Y
乘积的求和
那么我们把在公式中关于计算
d
X
j
,
1
d_{Xj,1}
d
X
j
,
1
梯度需要的w参数和
d
Y
dY
d
Y
在正向传播的矩阵计算中标示出来如下,根据计算公式,我们发现
d
X
j
,
1
dX_{j,1}
d
X
j
,
1
的值正好等于
d
Y
dY
d
Y
矩阵与
w
w
w
转置矩阵相乘后的第一行第一列的值,这不是偶然,其实
d
Y
dY
d
Y
矩阵与
w
w
w
转置矩阵的相乘正好就是
d
X
dX
d
X
梯度矩阵
那么我们得出结论:
当前
d
x
dx
d
x
矩阵由下一层传回的
d
Y
dY
d
Y
矩阵与当前参数矩阵
w
w
w
的乘积
:
用python实现就是:
dx = np.dot(dY, w.T) # (N,D)
2. 当前层
d
w
dw
d
w
和
d
b
db
d
b
的求法
与
d
x
dx
d
x
的公式类似,
d
w
1
,
1
dw_{1,1}
d
w
1
,
1
的计算公式我们也可以推出,因为
w
1
,
1
w_{1,1}
w
1
,
1
参数每次只与这个样本的第一个输入数据发生相乘关系,因此如果有
N
N
N
个样本,那么
d
w
1
,
1
dw_{1,1}
d
w
1
,
1
就等于相应样本的
d
Y
dY
d
Y
与此样本第一个输入数据的乘积然后求和
我们将计算
d
w
1
,
1
dw_{1,1}
d
w
1
,
1
所有相关的值在正向传播中标示出来如下,我们也正好发现
d
w
1
,
1
dw_{1,1}
d
w
1
,
1
的值就等于
X
X
X
的转置矩阵与
d
Y
dY
d
Y
相乘后矩阵的第一行第一列值,
因此:当前
d
w
dw
d
w
矩阵等于当前
X
X
X
转置矩阵与下一层传回的
d
Y
dY
d
Y
矩阵的乘积
对于
d
b
1
db_1
d
b
1
来说,
它的梯度直接为所有相应样本的
d
Y
dY
d
Y
的求和
, 因此直接对
d
Y
dY
d
Y
矩阵进行在维度为0的求和操作即可
我们用python实现即为:
dw = np.dot(X.T, dY) # (D,M)
db = np.sum(dY, axis=0) # (M,)
关于更加详细的实现请看这里:https://github.com/PENGZhaoqing/cs231n-assignment2/blob/master/cs231n/layers.py
思考- 使用mini-batch的优点
其实
d
w
dw
d
w
的计算可以这么理解,是这批(batch)样本里每个样本损失函数对w的梯度向量的累加,如下图所示,蓝线代表各个样本损失函数对
w
w
w
的梯度向量,红线代表这些梯度向量的求和,也就是我们最终更新
w
w
w
的方向,这个方向很好综合了所有单个样本更新的方向,因此更新的效果要比单个样本一个一个来更新要好的多,每次单个的更新会让整个更新过程非常动荡(每个样本的方向基本都不一样),而使用mini-batch的方法每次更新的方向是所有这些样本更新方向的折中,减少了每次更新的差异性:
那是不是batch的size越大越好呢,当然不是,当batch的样本数量过大后,
d
w
dw
d
w
会淹没一些少数的样本的方向,比如下图, 假设黑线代表某个样本更新
w
w
w
的梯度方向,这个方向会被其他蓝线方向覆盖,所以最终的
d
w
dw
d
w
方向基本上不会反映出这个样本的更新方向,当batch_size过大后,在每次epoch迭代中,黑线代表的样本都有很大可能性被覆盖,会导致精度会上不去