与矩阵有关的求导主要分为两类:
- 标量 f 对 矩阵 W的导数 (其结果是和W同纬度的矩阵,也就是f对W逐元素求导排成与W尺寸相同的矩阵)
- 矩阵 F 对 矩阵 W的导数 (其结果是一个四维的矩阵)
回到博文中提到的神经网络, 这里f 实际上就是loss(神经网络的损失),每个batch的训练集所获得的损失都是一个标量,他是网络参数W和b的函数 (f = LOSS(W,b)),因此想要完成对参数的更新就需要求 L这个标量对W这个矩阵的导数,在代码中简记为dW,如下所示,本文的目的就是手动完成dW_1和dW_2以及db_1,db_2的推导过程:
#正向传播
Z_1 = np.dot(W_1.T,X) + b_1 # 维度N1*M ,N1表示第一隐层的神经元数
A_1 = sigmoid(Z_1) # 维度N1*M
Z_2 = np.dot(W_2.T,A_1) + b_2 # 维度N2*M ,N2表示输出层的神经元数
A_2 = sigmoid(Z_2) # 维度N2*M
L = cross_entropy(A_2,Y) # 标量(具体实现待研究)
#反向传播
dZ_2 = A_2 - Y # 维度N2*M ,N2表示输出层的神经元数
dW_2 = 1/m* np.dot(dZ_2, A_1.T) # 维度N2*N1
db_2 = 1/m* np.sum(dZ_2,axis = 1,keepdims = true) # 维度N2*1
dZ_1 = np.dot(W_2.T,dZ_2) * A_1*(1-A_1) # 维度N1*M
dW_1 = 1/m* np.dot(dZ_1, X.T) # 维度N1*N0,N0表示单样本的特征数
db_1 = 1/m* np.sum(dZ_1,axis = 1,keepdims = true) # 维度N1*1
1.基础知识
首先回顾一下高数中的导数与微分的知识:
- 一元微积分中的微分df与导数(标量对标量的导数):
- 多元微积分中的微分df与梯度(标量对向量的导数):
- 受此启发,我们可以将微分df和矩阵导数(标量对矩阵的导数)视为:
2.运算法则
回想遇到的较复杂的一元函数.如:我们是如何求导的呢?通常不是从定义开始求极限,而是先建立了初等函数求导和四则运算、复合等法则,再来运用这些法则。故而,我们来创立常用的矩阵微分的运算法则:
- 加减法:
- 乘法: d(XY) = (dX)Y+XdY
- 转置:
- 迹:
- 逆: (可由两侧求微分证明)
- 行列式: 其中表示X的伴随矩阵,在X可逆时又可以写成此式可用laplace展开证明
- 逐元素乘法: 其中表示尺寸相同的矩阵X,Y进行元素乘法
- 逐元素函数:其中是逐元素标量函数计算是逐元素标量导数计算
我们试图利用微分与矩阵导数的联系在求出左侧的微分后,该如何写成右侧的形式并得到导数呢?这需要一些迹技巧(trace trick):
- 标量套上迹 : a = tr(a)
- 转置:
- 线性:
- 矩阵乘法交换: ,其中A与尺寸相同,两侧都等于
- 矩阵乘法/逐元素乘法交换: 其中ABC尺寸相同,两侧都等于
观察一下可以断言: 若标量函数f是矩阵X经加减乘法、行列式、逆、逐元素函数等运算构成,则使用相应的运算法则对f求微分,再使用迹技巧给df套上迹(df是标量tr(df) = df)并将其它项交换至dX左侧,即能得到导数。
3.三层神经网络反向传播推导
假定一共有M个样本,每个样本的特征值有N0个,第一隐层的神经元有N1个,输出层的神经元有N2个 ,正向传播得到损失L(标量)的过程如下:
#正向传播
Z_1 = np.dot(W_1.T,X) + b_1 # 维度N1*M ,N1表示第一隐层的神经元数
A_1 = sigmoid(Z_1) # 维度N1*M
Z_2 = np.dot(W_2.T,A_1) + b_2 # 维度N2*M ,N2表示输出层的神经元数
A_2 = sigmoid(Z_2) # 维度N2*M ,本例中N2=1
L = cross_entropy(A_2,Y) # 标量
具体到损失L的计算公式有:
(注:是一个(M,1)的单位向量,表示求均值的操作,用M个样本的loss均值表示一个batch的loss)
其中Y(N2,M), 是逐元素乘法,N2相当于样本可以分成N2个种类(本例中N2=1,也就是二分类.如果是多元的就得用softmax而不是cross_entropy,最后得到的同样是一个标量,不过公式不同了),M是样本总数.