DL基本知识(二)反向传播推导&python实现

本文详细介绍了自定义神经网络的搭建过程及反向传播中的矩阵求导、激活函数求导与正则化求导等内容,通过实例帮助读者深入理解神经网络的工作原理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

关键字: 神经网络、矩阵求导、链式法则、python

之前学习反向传播的时候,对于矩阵的求导、链式法则以及正则化的求导过程有些疑惑,我觉得是一个不错的机会来彻底搞清楚。

这里先开一个小差,我自己特别不喜欢推公式,因为推了几篇公式之后,编程实现就是这么几行,感觉超级不爽,。。。废话少说,下面开始进入主题。

下面是这篇博客的内容:

  • 自定义神经网络搭建过程
  • 反向传播过程中矩阵求导
  • 反向传播过程中激活函数求导
  • BONUS 反向传播过程中的正则化
  • 总结

自定义神经网络搭建过程

首先交代下整个神经网络的搭建过程。整个神经网络由三层组成:输入层,隐藏层和输出层组成。

  • 输入层

    输入的每条样本的维度为[1, 500], 每个样本的每一维都是从[-10,10]的区间内选取的随机整数。

  • 隐藏层

    隐藏层的维度为[500,3],每一维都是[0,1]之间均匀分布的随机浮点数。隐藏层输出之后会经过sigmoid激活函数。

  • 输出层

    输出层的维度为[3,3],每一维都是[0,1]之间均匀分布的随机浮点数。输出层输出之后会经过sigmoid激活函数。

  • 标签

    每个数据的标签是一个维度为[1,3]的向量,每个样本的每一维都是从[0, 1]的区间内选取的随机整数。因而从标签的形式可以看出,这个任务是一个多target分类任务。

  • 损失函数

    输出层与标签之间进行均方损失函数计算(这里说明的是,分类问题不应该是这么做,应该用分类问题特有的损失函数,这里只是为了方便起见)。

  • 网络整体结构图

    上述整个神经网络模型的示意图如下所示:
    nn_plot

反向传播过程中矩阵求导

先从最简单的说起,就是最简单的affine layer(全连接层)的反向传播,在这一层中的前向传播如下公式, x x x是这层的输入, w w w是这层的参数, b b b是这层的偏置, o u t out out是这层的输入,
o u t = x ∗ w + b out = x * w + b out=xw+b

这里为了简便,省略了偏置项( b b b),下图展示了反向传播过程中的计算流程,这里展示的是输入层和隐藏层矩阵相乘之后的反向传播状态。
bp
其中d_loss_input_layer代表的是输入层的梯度,d_loss_hidden_layer_w代表的是隐藏层权重矩阵的梯度,d_loss_hidden_layer_before_activation代表的是隐藏层输入到激活函数之前的结果的梯度。

根据矩阵求导的链式法则可得(其中 { x → y → z } \{x\rightarrow y\rightarrow z\} {xyz}代表的含义是由x推出y,再由y推出z, × \times ×代表的含义是矩阵相乘),
∂ z ∂ x = ( ∂ y ∂ x ) T × ∂ z ∂ y      { x → y → z } \frac{\partial z}{\partial x}=(\frac{\partial y}{\partial x})^T\times \frac{\partial z}{\partial y} \ \ \ \ \{x\rightarrow y\rightarrow z\} xz=(xy)T×yz    {xyz}
可得如下公式,
∂ l o s s ∂ i n p u t _ l a y e r = ( ∂ h i d d e n _ l a y e r _ b e f o r e _ a c t i v a t i o n ∂ i n p u t _ l a y e r ) T × ∂ l o s s ∂ h i d d e n _ l a y e r _ b e f o r e _ a c t i v a t i o n ∂ l o s s ∂ h i d d e n _ l a y e r _ w = ( ∂ h i d d e n _ l a y e r _ b e f o r e _ a c t i v a t i o n ∂ h i d d e n _ l a y e r _ w ) T × ∂ l o s s ∂ h i d d e n _ l a y e r _ b e f o r e _ a c t i v a t i o n \frac{\partial loss}{\partial input\_layer}=(\frac{\partial hidden\_layer\_before\_activation}{\partial input\_layer})^T\times\frac{\partial loss}{\partial hidden\_layer\_before\_activation} \\ \frac{\partial loss}{\partial hidden\_layer\_w }=(\frac{\partial hidden\_layer\_before\_activation}{\partial hidden\_layer\_w})^T\times\frac{\partial loss}{\partial hidden\_layer\_before\_activation} input_layerloss=(input_layerhidden_layer_before_activation)T×hidden_layer_before_activationlosshidden_layer_wloss=(hidden_layer_whidden_layer_before_activation)T×hidden_layer_before_activationloss
而反向传播过程中要解决的核心问题是权重矩阵的参数更新问题,因而这里只关注d_loss_hidden_layer_w的计算,又根据矩阵求导的法则
∂ β T x ∂ x = β \frac{\partial \beta ^Tx}{\partial x}=\beta xβTx=β
可得
∂ h i d d e n _ l a y e r _ b e f o r e _ a c t i v a t i o n ∂ h i d d e n _ l a y e r _ w = i n p u t _ l a y e r T \frac{\partial hidden\_layer\_before\_activation}{\partial hidden\_layer\_w}=input\_layer^T hidden_layer_whidden_layer_before_activation=input_layerT


∂ l o s s ∂ h i d d e n _ l a y e r _ w = i n p u t _ l a y e r T × ∂ l o s s ∂ h i d d e n _ l a y e r _ b e f o r e _ a c t i v a t i o n \frac{\partial loss}{\partial hidden\_layer\_w }=input\_layer^T\times\frac{\partial loss}{\partial hidden\_layer\_before\_activation} hidden_layer_wloss=input_layerT×hidden_layer_before_activationloss
这样只需要知道 ∂ l o s s ∂ h i d d e n _ l a y e r _ b e f o r e _ a c t i v a t i o n \frac{\partial loss}{\partial hidden\_layer\_before\_activation} hidden_layer_before_activationloss,就能够将隐藏层的权重矩阵的梯度求解出来,而该结果又是通过链式法则的前端传递过来的,因而需要计算前面的结果,这里才能够知道,具体计算结果这里就不再赘述,因为这里重点讲的是矩阵求导的过程。

反向传播过程中激活函数求导

激活函数的意义是为神经网络的计算添加非线性,这样神经网络就能够拟合更加复杂的曲线,不过这不是本文的重点,大家有兴趣的可以参考其他博客。这里重点介绍的是激活函数再反向传播中是如何求导的,这里以sigmoid函数为例来详细介绍。首先给出的是该种情况下反向传播的过程示意图,其中d_sigmoid代表的是sigmoid函数的求导函数。
在这里插入图片描述
由于这里在前向计算时是逐位点乘的,因而这里不涉及矩阵求导,但是这里依然存在链式法则,因而计算公式如下,
∂ l o s s ∂ h i d d e n _ l a y e r _ b e f o r e _ a c t i v a t i o n = d _ s i g m o i d ( h i d d e n _ l a y e r _ b e f o r e _ a c t i v a t i o n ) ∗ ∂ l o s s ∂ h i d d e n _ l a y e r \frac{\partial loss}{\partial hidden\_layer\_before\_activation}= \\ d\_sigmoid(hidden\_layer\_before\_activation)*\frac{\partial loss}{\partial hidden\_layer} hidden_layer_before_activationloss=d_sigmoid(hidden_layer_before_activation)hidden_layerloss
这里值得注意的是,上面公式中 ∗ * 代表的是矩阵的逐位点乘,而不是矩阵相乘。

到此,反向传播过程中激活函数求导讲述完毕。上述过程的完整代码可以参考我的github repo, 每一步都是手工计算,过程中只利用了numpy函数库,个人认为可以较为清晰的解答简单神经网络前向和反向传播过程中的细节问题。大家如果有兴趣,可以一起维护这个repo~

BONUS 反向传播过程中的正则化

正则化的基本思想是通过限制参数的野蛮生长而诞生的技术,说白了就是抑制过拟合,让模型的泛化能力更强。具体的做法是在计算损失时加入了权重矩阵的正则项,这样如果权重矩阵每个元素的取值太大的话,会导致整体的损失变大,因而为了使损失变小,在学习过程中会抑制参数变得很大,这里所说的"很大"指的是绝对值很大或者平方项很大,分别对应了l1正则项和l2正则项。因而下文会讲述这两个正则项的求导过程以及两个正则化项在实际运用时对结果的影响。

  • l1正则项
    反向传播的计算公式如下所示:
    ∂ l o s s ∂ w l 1 = { 1      i f   w > 0 − 1    i f   w < 0 \frac{\partial loss}{\partial w}_{l1}=\left\{\begin{matrix} 1 \ \ \ \ if \ w > 0\\ -1 \ \ if \ w < 0 \end{matrix}\right. wlossl1={1    if w>01  if w<0

  • l2正则项
    反向传播的计算公式如下所示:
    ∂ l o s s ∂ w l 2 = w \frac{\partial loss}{\partial w}_{l2}=w wlossl2=w

  • 实际应用

    将正则项运用到第一节所搭建的神经网络中,分别对比了不加正则项、l1正则项和l2正则项之间的训练和验证损失的差异,示意图如下所示:
    在这里插入图片描述
    可以看出,添加了正则项之后,验证集能够更快地收敛,而对于数值特征,上述示意图显示的不是特别明显,这里截取了训练过程中训练loss和验证loss的数值结果,如下所示(到1600次迭代时基本已经收敛),

    不加正则项:
    iteration 1600, train loss: 0.3432821160251056, valid_loss: 2.5690724565021563

    l1正则项:
    iteration 1600, train loss: 0.20726140239368068, valid_loss: 0.019843994548783522

    l2正则项:
    iteration 1600, train loss: 1.09977712566086, valid_loss: 0.21506450810326314

    这里可以明显地看出,不加正则项收敛时,训练loss会很低,但是验证loss比较大,而l1和l2正好相反,经过添加正则项的操作之后,验证loss更低,这就能说明添加正则项确实比不添加正则项的效果要好。而在这里为何l1比l2的效果要好,目前楼主还没有想到一个比较好的答案,等大段时间了可以好好想一下,或者大家可以一起来想一下~

总结

手动实现神经网络,可以消除之前对神经网络反向传播计算的恐惧,并且对矩阵求导以及链式法则有了一个更加深刻的认识,希望以后能够在这个领域由更多的见解,也希望大家如果对该博客有什么异议,一定提出来,共同学习,共同提高~

反向传播算法(Backpropagation)是一种用于训练神经网络的常见优化算法。它通过计算损失函数相对于每个参数的梯度,并使用梯度下降来更新参数。下面我将给出反向传播算法的公式推导及示例代码。 1. 反向传播算法公式推导: 首先,定义神经网络的损失函数为L,该函数是由网络输出和真实标签之间的差异计算得出。假设神经网络有多个隐藏层,每个隐藏层的参数为W和b。 1.1 前向传播: 首先,我们通过前向传播计算每一层的输出值。假设输入为x,第l层的输出为a[l],则有: a = x z[l] = W[l] * a[l-1] + b[l] a[l] = g(z[l]) 其中,g()是激活函数。 1.2 反向传播: 接下来,我们需要计算损失函数相对于每个参数的梯度,然后使用梯度下降更新参数。假设我们有L层神经网络,则有以下公式: 输出层的梯度: dz[L] = dL / da[L] * g'(z[L]) 隐藏层的梯度: dz[l] = (W[l+1]的转置 * dz[l+1]) * g'(z[l]) 参数梯度: dW[l] = dz[l] * a[l-1的转置] db[l] = dz[l] 更新参数: W[l] = W[l] - learning_rate * dW[l] b[l] = b[l] - learning_rate * db[l] 其中,dL / da[L]是损失函数对输出层输出的导数,g'()是激活函数的导数。 2. 反向传播算法示例代码: 下面是一个使用反向传播算法进行训练的示例代码: ```python # 假设网络有三个隐藏层 hidden_layers = [10, 20, 30] output_size = 2 # 初始化参数 parameters = {} layers_dims = [input_size] + hidden_layers + [output_size] L = len(layers_dims) - 1 for l in range(1, L + 1): parameters['W' + str(l)] = np.random.randn(layers_dims[l], layers_dims[l-1]) * 0.01 parameters['b' + str(l)] = np.zeros((layers_dims[l], 1)) # 前向传播 def forward_propagation(X, parameters): caches = [] A = X for l in range(1, L): Z = np.dot(parameters['W' + str(l)], A) + parameters['b' + str(l)] A = sigmoid(Z) cache = (Z, A) caches.append(cache) Z = np.dot(parameters['W' + str(L)], A) + parameters['b' + str(L)] AL = softmax(Z) cache = (Z, AL) caches.append(cache) return AL, caches # 反向传播 def backward_propagation(AL, Y, caches): grads = {} dZ = AL - Y m = AL.shape[1] grads['dW' + str(L)] = 1/m * np.dot(dZ, caches[-1][1].T) grads['db' + str(L)] = 1/m * np.sum(dZ, axis=1, keepdims=True) for l in reversed(range(1, L)): dA_prev = np.dot(parameters['W' + str(l+1)].T, dZ) dZ = dA_prev * sigmoid_derivative(caches[l-1][0]) grads['dW' + str(l)] = 1/m * np.dot(dZ, caches[l-1][1].T) grads['db' + str(l)] = 1/m * np.sum(dZ, axis=1, keepdims=True) return grads # 参数更新 def update_parameters(parameters, grads, learning_rate): for l in range(1, L+1): parameters['W' + str(l)] -= learning_rate * grads['dW' + str(l)] parameters['b' + str(l)] -= learning_rate * grads['db' + str(l)] return parameters # 训练模型 def train_model(X, Y, learning_rate, num_iterations): for i in range(num_iterations): AL, caches = forward_propagation(X, parameters) cost = compute_cost(AL, Y) grads = backward_propagation(AL, Y, caches) parameters = update_parameters(parameters, grads, learning_rate) if i % 100 == 0: print("Cost after iteration {}: {}".format(i, cost)) return parameters # 使用示例 parameters = train_model(X_train, Y_train, learning_rate=0.01, num_iterations=1000) ``` 这是一个简单的反向传播算法示例代码,其中的sigmoid()、softmax()、sigmoid_derivative()和compute_cost()函数需要根据具体情况自行实现
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值