### 全连接神经网络中的反向传播公式推导
在全连接神经网络中,每一层的输入与输出之间存在线性关系,并通过激活函数引入非线性。对于一层来说,其正向传播可以表示为:
\[ z^{(l)} = W^{(l)}a^{(l-1)} + b^{(l)} \]
\[ a^{(l)} = g(z^{(l)}) \]
其中 \(W^{(l)}\) 是权重矩阵,\(b^{(l)}\) 是偏置项,\(g(\cdot)\) 表示激活函数。
为了计算梯度以便更新参数,在反向传播过程中需要计算损失函数关于各层权重和偏置的偏导数。设当前正在处理第 \( l \) 层,则有如下表达式用于描述这些偏导数[^1]:
\[ \delta^{(l)} = (\Theta^{(l)})^T\delta^{(l+1)} .* g'(z^{(l)}) \]
\[ \frac{\partial}{\partial W_{ij}^{(l)}} J(W,b;x,y)=a_j^{(l)}\delta_i^{(l+1)} \]
\[ \frac{\partial}{\partial b_i^{(l)}}J(W,b;x,y)=\delta_i^{(l+1)} \]
这里 \(\delta^{(l)}\) 称作误差项(error term),代表该层相对于最终输出产生的错误;\(.*\) 表明逐元素乘法操作;\(g'\) 则是指激活函数的一阶导数。
### 卷积神经网络(CNN)中的Backpropagation Derivation
卷积神经网络结构更加复杂一些,除了普通的全连接层外还包括卷积层、池化层等特殊组件。下面主要讨论卷积层内的反向传播过程。
假设有一个二维图像作为输入经过一次标准卷积运算得到特征图(output feature map), 正向传播可写作:
\[ O(i,j,k) = f(I * K(:,:,k)) + B(k) \]
这里的 \(O, I, K,\text{ 和 }B\) 分别对应于输出特征映射(Output Feature Map)、输入数据(Input Data)、滤波器(Filter/Kernels)以及偏差(Bias Term); 符号"\(*\)"表示卷积操作; 函数"f()"通常是一个ReLU这样的非线性变换.
当执行反向传播时,目标是要找到三个部分对应的梯度:即对输入I、核K还有bias B 的梯度。具体而言,
针对输入的数据 \(I\) 来说,
\[ dI = dO * rot180(K) \]
而对于滤波器 \(K\) 而言,
\[ dK = I * dO \]
最后是对于 bias \(B\),
\[ dB=\sum_dO \]
上述各式中,“rot180()”指的是旋转180度的操作,这一步骤是为了匹配卷积操作的特点使得能够正确地传递梯度信息回前一层节点[^2]。
另外值得注意的是,在实际应用当中可能会涉及到填充(padding)和平铺(stride)等因素的影响,所以在实现具体的算法时还需要考虑这些问题带来的变化[^4]。
```python
import numpy as np
def conv_backward(dZ, cache):
"""
Implement the backward propagation for a convolution function
Arguments:
dZ -- gradient of the cost with respect to output of the conv layer (Z), numpy array of shape (m, n_H, n_W, n_C)
cache -- cache of values needed for the conv_backward(), output of conv_forward()
Returns:
dA_prev -- gradient of cost with respect to input of the conv layer (A_prev), numpy array of shape (m, n_H_prev, n_W_prev, n_C_prev)
dW -- gradient of cost with respect to weights W, numpy array of shape (f, f, n_C_prev, n_C)
db -- gradient of cost with respect to biases b, numpy array of shape (1, 1, 1, n_C)
"""
# Retrieve information from "cache"
(A_prev, W, b, hparameters) = cache
# Retrieve dimensions from A_prev's shape and W's shape
(m, n_H_prev, n_W_prev, n_C_prev) = A_prev.shape
(f, f, n_C_prev, n_C) = W.shape
# Initialize gradients
dA_prev = np.zeros((m, n_H_prev, n_W_prev, n_C_prev))
dW = np.zeros((f, f, n_C_prev, n_C))
db = np.sum(dZ, axis=(0, 1, 2))
db = db.reshape((1, 1, 1, n_C))
# Compute dA_prev, dW using formulas above.
for i in range(m): # loop over the training examples
a_slice_prev = A_prev[i,:,:,:]
for h in range(n_H_prev): # loop through vertical axis of previous activation matrix
for w in range(n_W_prev): # loop through horizontal axis of previous activation matrix
for c in range(n_C): # loop over channels (=number of filters) of current layer
vert_start = h
vert_end = vert_start + f
horiz_start = w
horiz_end = horiz_start + f
a_slice = a_slice_prev[vert_start:vert_end, horiz_start:horiz_end, :]
da_prev_pad = sum(sum(np.dot(dZ[i,h,w,c], W[:,:, :,c])))
dW[:,:,:,c] += a_slice * dZ[i,h,w,c]
dA_prev[i, vert_start:vert_end, horiz_start:horiz_end, :] += W[:, :, :, c]*dZ[i,h,w,c]
return dA_prev, dW, db
```