《零基础入门AI:深度学习基础核心概念解析(从激活函数到反向传播)》
深度学习作为人工智能领域的重要分支,其核心在于通过多层神经网络学习数据的特征表示。本文将系统讲解深度学习中的几个核心概念:激活函数、参数初始化、损失函数和反向传播算法,帮助初学者建立扎实的理论基础。
一、激活函数
激活函数是神经网络的核心组件之一,赋予了模型学习非线性关系的能力。没有激活函数,无论神经网络有多少层,都只能表示线性关系。
基础概念
线性理解:线性关系是指输入与输出之间呈正比例关系,可以表示为 y=wx+by = wx + by=wx+b,线其中 www 是权重,bbb 是偏置。性模型的特点是简单直观,但问题在于,无论叠加多少层,最终结果仍然是线性的,无法拟合复杂数据(如图像、语音),所以无法处理现实世界中复杂的非线性问题。
非线性理解:非线性关系是指输入与输出之间不是简单的正比例关系,例如 y=sin(x)y = sin(x)y=sin(x) 或 y=x2y = x^2y=x2。在神经网络中,通过在每一层加入非线性激活函数,使得整个网络可以拟合任意复杂的非线性函数,这也是深度学习能够处理图像、语音等复杂数据的关键。
常见激活函数
- sigmoid 函数
sigmoid 函数的数学表达式为: σ(x)=11+e−x\sigma(x) = \frac{1}{1 + e^{-x}}σ(x)=1+e−x1,其特点是将输入值映射到 [0, 1] 区间,具有平滑可导的特性。在早期的神经网络中广泛使用,适合作为二分类问题的输出层激活函数,因为它可以表示概率。
但 sigmoid 存在两个主要问题:
- 梯度消失:当输入值很大或很小时,函数的梯度接近 0(梯度消失),导致反向传播时梯度难以更新(计算成本高)
- 输出不是以 0 为中心:会导致权重更新效率降低
示例:
import numpy as npdef sigmoid(x):# 防止指数溢出,对大的负数做截断x = np.clip(x, -500, 500)return 1 / (1 + np.exp(-x))# 测试
x = np.array([-1, 0, 1, 10])
print(sigmoid(x)) # 输出: [0.26894142 0.5 0.73105858 0.9999546 ]
- tanh 函数
tanh 函数(双曲正切函数)的数学表达式为:tanh(x)=ex−e−xex+e−xtanh(x) = \frac{e^x - e^{-x}}{e^x + e^{-x}}tanh(x)=ex+e−xex−e−x
tanh 函数将输入映射到 [-1, 1] 区间,解决了 sigmoid 函数输出不是以 0 为中心的问题。但它仍然存在梯度消失的问题。
特点:
- 优点:输出以 0 为中心,梯度比 sigmoid 大
- 缺点:仍存在梯度消失问题
示例:
def tanh(x):x = np.clip(x, -500, 500)return (np.exp(x) - np.exp(-x)) / (np.exp(x) + np.exp(-x))# 测试
x = np.array([-1, 0, 1, 10])
print(tanh(x)) # 输出: [-0.76159416 0. 0.76159416 0.99999999]
- ReLU 函数
ReLU(Rectified Linear Unit,修正线性单元)的数学表达式为:ReLU(x)=max(0,x)ReLU(x) = max(0, x)ReLU(x)=max(0,x),ReLU 函数在 x > 0 时直接输出 x,在 x ≤ 0 时输出 0。它的出现解决了梯度消失问题,计算速度快,成为目前深度学习中最常用的激活函数之一。
ReLU 的缺点是可能出现 “死亡 ReLU” 问题:当输入长期为负时,神经元将永远无法激活(梯度为 0,权重无法更新)。
示例:
def relu(x):return np.maximum(0, x)# 测试
x = np.array([-1, 0, 1, 10])
print(relu(x)) # 输出: [ 0 0 1 10]
- Leaky ReLU 函数
Leaky ReLU 是为解决死亡 ReLU 问题而提出的变体:LeakyReLU(x)=max(αx,x)LeakyReLU(x) = max(\alpha x, x)LeakyReLU(x)=max(αx,x),其中 α 是一个很小的常数(通常取 0.01)
当 x < 0 时,Leaky ReLU 不会输出 0,而是输出一个很小的负值,从而避免了神经元完全死亡的问题。
示例:
def leaky_relu(x, alpha=0.01):return np.where(x > 0, x, alpha * x)# 测试
x = np.array([-1, 0, 1, 10])
print(leaky_relu(x)) # 输出: [-0.01 0. 1. 10. ]
- softmax 函数
softmax 函数通常用于多分类问题的输出层,其数学表达式为:
softmax(z)i=ezi∑j=1Kezjsoftmax(z)_i = \frac{e^{z_i}}{\sum_{j=1}^{K} e^{z_j}}softmax(z)i=∑j=1Kezjezi
,其中 z 是输入向量,K 是类别数量
softmax 函数将输入向量转换为概率分布,每个元素的值在 [0, 1] 之间,且所有元素之和为 1,非常适合表示多分类问题中每个类别的概率。
示例:
def softmax(z):# 减去最大值防止指数溢出exp_z = np.exp(z - np.max(z))return exp_z / np.sum(exp_z, axis=0)# 测试:3个类别的得分
z = np.array([2.0, 1.0, 0.1])
print(softmax(z)) # 输出: [0.65900114 0.24243297 0.09856589](和为1)
如何选择激活函数
隐藏层选择:
- 优先选择 ReLU 或其变体(如 Leaky ReLU),它们计算高效且能有效缓解梯度消失问题
- 如果 ReLU 导致了死亡神经元问题,可以尝试 Leaky ReLU
- 在循环神经网络(RNN)中,tanh 有时表现更好
输出层选择:
- 二分类问题:使用 sigmoid 函数,输出单个值表示属于正类的概率
- 多分类问题:使用 softmax 函数,输出每个类别的概率分布
- 回归问题:通常不使用激活函数,直接输出连续值
二 、参数初始化
神经网络的参数(权重和偏置)初始化方式对模型的训练效率和最终性能有重要影响。合适的初始化可以加速收敛,避免梯度消失或爆炸问题。
固定值初始化
固定值初始化是指将所有参数初始化为相同的值(如 0 或 1)。例如:
wij=0或wij=1w_{ij} = 0 \quad \text{或} \quad w_{ij} = 1wij=0或wij=1
这种方法看似简单,却存在严重缺陷:在反向传播时,所有神经元会有相同的梯度,导致权重更新后仍然保持对称,本质上相当于只有一个神经元在工作,无法学习到有意义的特征。因此,固定值初始化在实际中很少使用。
示例:
def fixed_initialization(input_size, output_size, value=0):W = np.full((output_size, input_size), value)b = np.full((output_size, 1), value)return W, b
随机初始化
随机初始化是指从某种概率分布中随机采样来初始化参数。常见的有:
- 均匀分布初始化: wij∼U(−a,a)w_{ij} \sim U(-a, a)wij∼U(−a,a)
,其中 UUU 表示均匀分布,aaa 是一个较小的常数(如0.01) - 正态分布初始化:
wij∼N(0,σ2)w_{ij} \sim \mathcal{N}(0, \sigma^2)wij∼N(0,σ2)
,其中 N\mathcal{N}N 表示正态分布,σ\sigmaσ 是标准差(如0.01)
随机初始化解决了对称性问题,但标准差的选择很关键:
- 标准差过大会导致激活值迅速饱和(对于 sigmoid 和 tanh)
- 标准差过小会导致信号在传播过程中逐渐减弱
示例:
def random_initialization(input_size, output_size, method='normal'):if method == 'normal':# 正态分布:均值0,标准差0.01W = np.random.normal(0, 0.01, size=(output_size, input_size))else:# 均匀分布:[-0.01, 0.01]W = np.random.uniform(-0.01, 0.01, size=(output_size, input_size))b = np.zeros((output_size, 1)) # 偏置通常初始化为0return W, b
Xavier 初始化
Xavier 初始化(也称为 Glorot 初始化)是一种更智能的初始化方法,其核心思想是使前向传播和反向传播中信号的方差保持一致,避免梯度消失或爆炸。
对于使用 tanh 或 sigmoid 等激活函数的网络,Xavier 初始化建议:
- 均匀分布:
wij∼U(−6nin+nout,6nin+nout)w_{ij} \sim U\left(-\frac{\sqrt{6}}{\sqrt{n_{in} + n_{out}}}, \frac{\sqrt{6}}{\sqrt{n_{in} + n_{out}}}\right)wij∼U(−nin+nout6,nin+nout6) - 正态分布:
wij∼N(0,2nin+nout)w_{ij} \sim \mathcal{N}\left(0, \frac{2}{n_{in} + n_{out}}\right)wij∼N(0,nin+nout2)
,其中 ninn_{in}nin 是输入神经元数量,noutn_{out}nout 是输出神经元数量。
示例:
def xavier_initialization(input_size, output_size):limit = np.sqrt(6 / (input_size + output_size))W = np.random.uniform(-limit, limit, size=(output_size, input_size))b = np.zeros((output_size, 1))return W, b
He 初始化
He 初始化是针对 ReLU 及其变体设计的初始化方法,考虑到 ReLU 会将一半的输入置零的特性。
He 初始化建议:
- 均匀分布:
wij∼U(−6nin,6nin)w_{ij} \sim U\left(-\sqrt{\frac{6}{n_{in}}}, \sqrt{\frac{6}{n_{in}}}\right)wij∼U(−nin6,nin6) - 正态分布:
wij∼N(0,2nin)w_{ij} \sim \mathcal{N}\left(0, \frac{2}{n_{in}}\right)wij∼N(0,nin2)
,其中 ninn_{in}nin 是输入神经元数量。
示例:
def he_initialization(input_size, output_size):limit = np.sqrt(6 / input_size)W = np.random.uniform(-limit, limit, size=(output_size, input_size))b = np.zeros((output_size, 1))return W, b
总结
- 避免使用固定值初始化
- 当网络使用 sigmoid 或 tanh 激活函数时,优先选择 Xavier 初始化
- 当网络使用 ReLU 或其变体时,优先选择 He 初始化
- 随机初始化时,应使用较小的标准差(如 0.01)
三、损失函数
损失函数(Loss Function)是衡量模型预测值与真实值之间差异的指标,是模型优化的目标(最小化损失)。模型通过最小化损失函数来学习最优参数。
线性回归损失函数
线性回归用于预测连续值,常用的损失函数有:
- MAE 损失
MAE(Mean Absolute Error,平均绝对误差)的数学表达式为:
MAE=1n∑i=1n∣yi−y^i∣MAE = \frac{1}{n} \sum_{i=1}^{n} |y_i - \hat{y}_i|MAE=n1i=1∑n∣yi−y^i∣
,其中 yiy_iyi 是真实值,y^i\hat{y}_iy^i 是预测值,nnn 是样本数量。
MAE 的特点是对异常值不敏感,但在零点处不可导,可能影响梯度下降的效率。
示例:
def mae_loss(y_true, y_pred):"""y_true: 真实值,形状(n, 1)y_pred: 预测值,形状(n, 1)"""return np.mean(np.abs(y_true - y_pred))
- MSE 损失
MSE(Mean Squared Error,均方误差)的数学表达式为:
MSE=1n∑i=1n(yi−y^i)2MSE = \frac{1}{n} \sum_{i=1}^{n} (y_i - \hat{y}_i)^2MSE=n1i=1∑n(yi−y^i)2
MSE 对预测值与真实值差异较大的样本(异常值)惩罚更严重,因为误差被平方了。MSE 处处可导,计算方便,是线性回归中最常用的损失函数。
示例:
def mse_loss(y_true, y_pred):return np.mean((y_true - y_pred) **2)
CrossEntropyLoss(交叉熵损失)
交叉熵损失主要用于分类问题,需要先理解几个相关概念:
- 信息量
信息量用于衡量一个事件的不确定性,事件发生的概率越小,其信息量越大:
I(x)=−log(p(x))I(x) = -\log(p(x))I(x)=−log(p(x))
,其中 p(x)p(x)p(x) 是事件 xxx 发生的概率。
- 信息熵
信息熵(Entropy)是衡量一个概率分布的不确定性的期望:
H(p)=−∑ip(xi)log(p(xi))H(p) = -\sum_{i} p(x_i) \log(p(x_i))H(p)=−i∑p(xi)log(p(xi))
信息熵越大,表示分布的不确定性越高。
- KL 散度
KL 散度(Kullback-Leibler Divergence)用于衡量两个概率分布之间的差异:
KL(p∣∣q)=∑ip(xi)log(p(xi)q(xi))KL(p||q) = \sum_{i} p(x_i) \log\left(\frac{p(x_i)}{q(x_i)}\right)KL(p∣∣q)=i∑p(xi)log(q(xi)p(xi))
,其中 ppp 是真实分布,qqq 是模型预测的分布。
- 交叉熵
将 KL 散度展开:
KL(p∣∣q)=∑ip(xi)log(p(xi))−∑ip(xi)log(q(xi))=−H(p)+H(p,q)KL(p||q) = \sum_{i} p(x_i) \log(p(x_i)) - \sum_{i} p(x_i) \log(q(x_i)) = -H(p) + H(p, q)KL(p∣∣q)=i∑p(xi)log(p(xi))−i∑p(xi)log(q(xi))=−H(p)+H(p,q)
其中 H(p,q)=−∑ip(xi)log(q(xi))H(p, q) = -\sum_{i} p(x_i) \log(q(x_i))H(p,q)=−∑ip(xi)log(q(xi)) 就是交叉熵。 在模型训练中,真实分布 ppp 是固定的,因此最小化KL散度等价于最小化交叉熵。这也是为什么在分类问题中常用交叉熵作为损失函数。
对于多分类问题(配合softmax使用),交叉熵损失的表达式为:
CrossEntropyLoss=−∑i=1Cyilog(y^i)CrossEntropyLoss = -\sum_{i=1}^{C} y_i \log(\hat{y}_i)CrossEntropyLoss=−i=1∑Cyilog(y^i)
,其中 CCC 是类别数,yiy_iyi 是真实标签(通常为one-hot编码),y^i\hat{y}_iy^i 是模型预测的概率(通过softmax输出)。
示例:
def cross_entropy_loss(y_true, y_pred_softmax):"""y_true: one-hot编码的真实标签,形状(n, C)y_pred_softmax: softmax输出的概率,形状(n, C)"""# 加epsilon防止log(0)epsilon = 1e-10return -np.mean(np.sum(y_true * np.log(y_pred_softmax + epsilon), axis=1))
BCELoss(二元交叉熵损失)
BCELoss(Binary Cross Entropy Loss)用于二分类问题,其表达式为:
BCELoss=−1n∑i=1n[yilog(y^i)+(1−yi)log(1−y^i)]BCELoss = -\frac{1}{n} \sum_{i=1}^{n} [y_i \log(\hat{y}_i) + (1 - y_i) \log(1 - \hat{y}_i)]BCELoss=−n1i=1∑n[yilog(y^i)+(1−yi)log(1−y^i)]
,其中 yi∈{0,1}y_i \in \{0, 1\}yi∈{0,1} 是真实标签,y^i∈[0,1]\hat{y}_i \in [0, 1]y^i∈[0,1] 是模型预测的正类概率(通过sigmoid输出)。
示例:
def bce_loss(y_true, y_pred_sigmoid):"""y_true: 真实标签(0或1),形状(n, 1)y_pred_sigmoid: sigmoid输出的概率,形状(n, 1)"""epsilon = 1e-10return -np.mean(y_true * np.log(y_pred_sigmoid + epsilon) + (1 - y_true) * np.log(1 - y_pred_sigmoid + epsilon))
总结
- 回归问题:使用 MSE 或 MAE,MSE 更常用但对异常值敏感
- 二分类问题:使用 BCELoss,配合 sigmoid 激活函数
- 多分类问题:使用 CrossEntropyLoss,配合 softmax 激活函数
四、反向传播算法
反向传播(Backpropagation,简称 BP)算法是训练神经网络的核心算法,它通过计算损失函数对各参数的梯度,然后利用梯度下降法更新参数,从而最小化损失函数。
前向传播(从输入到输出)
前向传播是指输入数据从输入层经过隐藏层,最终到达输出层并得到预测结果的过程。
数学表达:
假设一个简单的神经网络有输入层 xxx,隐藏层 hhh 和输出层 y^\hat{y}y^,则:
隐藏层计算: h=σ(W1x+b1)h = \sigma(W_1 x + b_1)h=σ(W1x+b1)
输出层计算: y^=σ(W2h+b2)\hat{y} = \sigma(W_2 h + b_2)y^=σ(W2h+b2)
,其中 W1,W2W_1, W_2W1,W2 是权重矩阵,b1,b2b_1, b_2b1,b2 是偏置向量,σ\sigmaσ 是激活函数。
作用:
- 计算模型的预测结果
- 为反向传播提供中间计算结果,用于梯度计算
示例:
def forward_propagation(x, W1, b1, W2, b2):# 隐藏层计算z1 = np.dot(W1, x) + b1 # 线性变换h = relu(z1) # 激活# 输出层计算(假设回归问题,无激活)z2 = np.dot(W2, h) + b2y_pred = z2# 存储中间结果,供反向传播使用cache = (x, z1, h, z2)return y_pred, cache
BP 基础之梯度下降算法(参数更新)
梯度下降是一种优化算法,通过沿着损失函数的负梯度方向更新参数,以找到损失函数的最小值。
数学描述:
参数更新公式:
θ=θ−η∇θL(θ)\theta = \theta - \eta \nabla_\theta L(\theta)θ=θ−η∇θL(θ)
,其中 θ\thetaθ 是模型参数,η\etaη 是学习率,∇θL(θ)\nabla_\theta L(\theta)∇θL(θ) 是损失函数 LLL 对参数 θ\thetaθ 的梯度。
过程阐述:
- 计算当前参数下的损失函数值
- 计算损失函数对各参数的梯度(偏导数)
- 沿着梯度的反方向更新参数(学习率控制步长)
- 重复步骤 1-3,直到损失函数收敛到最小值
传统下降方式:
-
批量梯度下降(Batch Gradient Descent)
- 计算所有训练样本的平均梯度来更新参数
- 优点:梯度估计准确,收敛稳定
- 缺点:计算量大,内存消耗大,无法处理大规模数据
示例:
def batch_gradient_descent(X, y, W, b, learning_rate):n = X.shape[1] # 样本数y_pred, cache = forward_propagation(X, W, b)loss = mse_loss(y, y_pred)# 计算梯度(简化版,实际需根据网络结构推导)dW = (1/n) * np.dot((y_pred - y), X.T)db = (1/n) * np.sum(y_pred - y)# 更新参数W -= learning_rate * dWb -= learning_rate * dbreturn W, b, loss
-
随机梯度下降(Stochastic Gradient Descent, SGD)
- 每次随机选择一个样本计算梯度并更新参数
- 优点:计算量小,能在线学习,有机会跳出局部最优
- 缺点:梯度估计噪声大,收敛路径不稳定
示例:
def stochastic_gradient_descent(X, y, W, b, learning_rate):n = X.shape[1]total_loss = 0for i in range(n):x_i = X[:, i:i+1] # 单个样本y_i = y[:, i:i+1]y_pred, cache = forward_propagation(x_i, W, b)total_loss += mse_loss(y_i, y_pred)# 计算梯度dW = np.dot((y_pred - y_i), x_i.T)db = np.sum(y_pred - y_i)# 更新参数W -= learning_rate * dWb -= learning_rate * dbreturn W, b, total_loss / n
-
小批量梯度下降(Mini-batch Gradient Descent)
- 每次使用一小批样本(如 32、64、128 个)计算梯度并更新参数
- 优点:兼顾批量梯度下降和随机梯度下降的优点,是实际中最常用的方法
- 缺点:需要额外选择批次大小(batch size)
存在问题:
- 学习率选择困难:太小收敛慢,太大可能跳过最优解
- 所有参数使用相同的学习率,不适合不同特征的更新需求
- 在鞍点(某些方向梯度为正,某些为负)处容易停滞
- 收敛速度可能较慢,尤其是在损失函数的平坦区域
优化下降方式
-
指加权平均
指数加权平均通过对历史梯度进行加权平均来减少噪声,公式为:
vt=βvt−1+(1−β)θtv_t = \beta v_{t-1} + (1 - \beta) \theta_tvt=βvt−1+(1−β)θt,其中 β\betaβ 是权重参数(通常取0.9),vtv_tvt 是第t时刻的加权平均值。
-
Momentum(动量法)
动量法模拟物理中的动量概念,积累之前的梯度方向,加速收敛:
vt=γvt−1+η∇θL(θ)v_t = \gamma v_{t-1} + \eta \nabla_\theta L(\theta)vt=γvt−1+η∇θL(θ) θ=θ−vt\theta = \theta - v_tθ=θ−vt,其中 γ\gammaγ 是动量参数(通常取0.9),vtv_tvt 是累积的梯度。
示例:
def momentum_update(W, b, dW, db, v_W, v_b, learning_rate, gamma=0.9):# v是累积的动量v_W = gamma * v_W + learning_rate * dWv_b = gamma * v_b + learning_rate * dbW -= v_Wb -= v_breturn W, b, v_W, v_b
-
AdaGrad
AdaGrad 自适应地为每个参数调整学习率,对频繁更新的参数使用较小的学习率,对稀疏参数使用较大的学习率:
Gt=Gt−1+(∇θL(θ))2G_t = G_{t-1} + (\nabla_\theta L(\theta))^2Gt=Gt−1+(∇θL(θ))2 θ=θ−ηGt+ϵ∇θL(θ)\theta = \theta - \frac{\eta}{\sqrt{G_t + \epsilon}} \nabla_\theta L(\theta)θ=θ−Gt+ϵη∇θL(θ),其中 GtG_tGt 是梯度平方的累积和,ϵ\epsilonϵ 是防止除零的小常数。
-
RMSProp
RMSProp 解决了 AdaGrad 学习率不断减小的问题,使用指数加权平均来调整梯度累积:
E[g2]t=0.9E[g2]t−1+0.1(∇θL(θ))2E[g^2]_t = 0.9 E[g^2]_{t-1} + 0.1 (\nabla_\theta L(\theta))^2E[g2]t=0.9E[g2]t−1+0.1(∇θL(θ))2 θ=θ−ηE[g2]t+ϵ∇θL(θ)\theta = \theta - \frac{\eta}{\sqrt{E[g^2]_t + \epsilon}} \nabla_\theta L(\theta)θ=θ−E[g2]t+ϵη∇θL(θ) -
Adam
Adam 结合了 Momentum 和 RMSProp 的优点,同时考虑梯度的一阶矩(均值)和二阶矩(方差):
mt=β1mt−1+(1−β1)∇θL(θ)m_t = \beta_1 m_{t-1} + (1 - \beta_1) \nabla_\theta L(\theta)mt=β1mt−1+(1−β1)∇θL(θ)vt=β2vt−1+(1−β2)(∇θL(θ))2v_t = \beta_2 v_{t-1} + (1 - \beta_2) (\nabla_\theta L(\theta))^2vt=β2vt−1+(1−β2)(∇θL(θ))2
m^t=mt1−β1t\hat{m}_t = \frac{m_t}{1 - \beta_1^t}m^t=1−β1tmt
v^t=vt1−β2t\hat{v}_t = \frac{v_t}{1 - \beta_2^t}v^t=1−β2tvt
θ=θ−ηv^t+ϵm^t\theta = \theta - \frac{\eta}{\sqrt{\hat{v}_t} + \epsilon} \hat{m}_tθ=θ−v^t+ϵηm^t
,其中 β1\beta_1β1 通常取0.9,β2\beta_2β2 通常取0.999,m^t\hat{m}_tm^t 和 v^t\hat{v}_tv^t 是偏差修正项。
示例:
def adam_update(W, b, dW, db, m_W, m_b, v_W, v_b, t, learning_rate=0.001, beta1=0.9, beta2=0.999, eps=1e-8):# 一阶矩(均值)更新m_W = beta1 * m_W + (1 - beta1) * dWm_b = beta1 * m_b + (1 - beta1) * db# 二阶矩(方差)更新v_W = beta2 * v_W + (1 - beta2) * (dW **2)v_b = beta2 * v_b + (1 - beta2) * (db** 2)# 偏差修正m_W_hat = m_W / (1 - beta1 **t)m_b_hat = m_b / (1 - beta1** t)v_W_hat = v_W / (1 - beta2 **t)v_b_hat = v_b / (1 - beta2** t)# 参数更新W -= learning_rate * m_W_hat / (np.sqrt(v_W_hat) + eps)b -= learning_rate * m_b_hat / (np.sqrt(v_b_hat) + eps)return W, b, m_W, m_b, v_W, v_b
反向传播完整流程(单隐藏层网络示例)
def backward_propagation(y_true, y_pred, cache, W2):x, z1, h, z2 = cachen = y_true.shape[1] # 样本数# 输出层梯度dz2 = y_pred - y_true # 假设输出层无激活,导数为1dW2 = (1/n) * np.dot(dz2, h.T)db2 = (1/n) * np.sum(dz2, axis=1, keepdims=True)# 隐藏层梯度(ReLU导数:x>0时为1,否则为0)dz1 = np.dot(W2.T, dz2) * (z1 > 0) # 链式法则dW1 = (1/n) * np.dot(dz1, x.T)db1 = (1/n) * np.sum(dz1, axis=1, keepdims=True)return dW1, db1, dW2, db2# 完整训练循环
def train(X, y, epochs=1000, learning_rate=0.01):input_size = X.shape[0]hidden_size = 10output_size = y.shape[0]# 初始化参数(用He初始化,因为隐藏层用ReLU)W1, b1 = he_initialization(input_size, hidden_size)W2, b2 = he_initialization(hidden_size, output_size)for i in range(epochs):# 前向传播y_pred, cache = forward_propagation(X, W1, b1, W2, b2)loss = mse_loss(y, y_pred)# 反向传播计算梯度dW1, db1, dW2, db2 = backward_propagation(y, y_pred, cache, W2)# 更新参数(简单SGD)W1 -= learning_rate * dW1b1 -= learning_rate * db1W2 -= learning_rate * dW2b2 -= learning_rate * db2if i % 100 == 0:print(f"Epoch {i}, Loss: {loss:.4f}")return W1, b1, W2, b2
总结
反向传播算法是神经网络训练的基石,其核心是通过链式法则计算损失函数对各参数的梯度,然后使用梯度下降法更新参数。
在实际应用中:
- 优先选择 Adam 优化器,它在大多数情况下表现优异
- 小批量梯度下降是训练神经网络的标准方法
- 学习率是关键超参数,需要根据具体问题调整
- 没有放之四海而皆准的优化器,应根据实际情况尝试不同的优化算法