神经网络基础:从零理解前向传播、反向传播与激活函数

1次阅读
没有评论

共计 2839 个字符,预计需要花费 8 分钟才能阅读完成。

image.webp

为什么需要神经网络?

传统机器学习模型(如逻辑回归、决策树)在处理图像、语音等高维非线性数据时表现有限。比如:

神经网络基础:从零理解前向传播、反向传播与激活函数

  • 线性不可分问题:XOR 分类任务中,单层感知机无法找到分离超平面
  • 特征工程依赖:手工设计特征耗时且难以捕捉复杂模式(如像素间的空间层次关系)
  • 维度灾难:当特征维度激增时,传统方法计算复杂度呈指数增长

神经网络通过多层非线性变换自动学习特征表示,其核心优势在于:

  1. 层次化特征提取:底层网络捕捉边缘 / 纹理等低级特征,高层组合出语义概念
  2. 端到端学习:从原始输入到最终输出无需人工干预特征设计
  3. 通用近似定理:单隐层网络即可逼近任意连续函数(需足够多神经元)

一、前向传播:数据如何流动

前向传播是输入数据逐层计算得到输出的过程。以单隐层网络为例:

# 输入数据 (batch_size, input_dim)
X = np.random.randn(100, 784)  

# 第一层参数 (input_dim, hidden_dim)
W1 = np.random.randn(784, 256) * 0.01
b1 = np.zeros(256)

# 隐藏层计算 (batch_size, hidden_dim)
Z1 = X.dot(W1) + b1  # 线性变换
A1 = np.maximum(0, Z1)  # ReLU 激活

# 输出层参数 (hidden_dim, output_dim)
W2 = np.random.randn(256, 10) * 0.01
b2 = np.zeros(10)

# 输出层计算 (batch_size, output_dim)
Z2 = A1.dot(W2) + b2
A2 = np.exp(Z2) / np.sum(np.exp(Z2), axis=1, keepdims=True)  # Softmax

维度变化关键点:

  • 矩阵乘法 (m,n) @ (n,p) → (m,p) 实现批量并行计算
  • 广播机制让偏置 b 自动扩展到所有样本
  • ReLU 激活函数引入非线性:$\text{ReLU}(x) = \max(0,x)$

二、反向传播:梯度如何回传

通过链式法则计算损失对参数的梯度,以下推导均针对单个样本:

  1. 输出层梯度(使用交叉熵损失):
    $$
    \frac{\partial L}{\partial Z_2} = A_2 – y \quad (\text{真实标签} y \text{的 one-hot 编码})
    $$

  2. 隐藏层梯度
    $$
    \frac{\partial L}{\partial W_2} = A_1^T \frac{\partial L}{\partial Z_2}, \quad
    \frac{\partial L}{\partial A_1} = \frac{\partial L}{\partial Z_2} W_2^T
    $$
    $$
    \frac{\partial L}{\partial Z_1} = \frac{\partial L}{\partial A_1} \odot \mathbb{I}(Z_1 > 0) \quad (\text{ReLU 导数为阶跃函数})
    $$

  3. 参数更新
    $$
    W_1 \leftarrow W_1 – \eta \frac{\partial L}{\partial W_1}, \quad
    \frac{\partial L}{\partial W_1} = X^T \frac{\partial L}{\partial Z_1}
    $$

Python 实现关键片段:

def backward(X, A1, W2, dZ2):
    dW2 = A1.T.dot(dZ2)  # (hidden_dim, output_dim)
    db2 = np.sum(dZ2, axis=0)  # (output_dim,)

    dA1 = dZ2.dot(W2.T)  # (batch_size, hidden_dim)
    dZ1 = dA1 * (Z1 > 0)  # ReLU 梯度

    dW1 = X.T.dot(dZ1)  # (input_dim, hidden_dim)
    db1 = np.sum(dZ1, axis=0)  # (hidden_dim,)

    return dW1, db1, dW2, db2

三、激活函数对比与选择

Sigmoid

$$
\sigma(x) = \frac{1}{1+e^{-x}}, \quad \sigma'(x) = \sigma(x)(1-\sigma(x))
$$

  • 优点 :输出在(0,1) 区间,适合二分类输出层
  • 缺点:梯度最大仅 0.25,易导致梯度消失

Tanh

$$
\tanh(x) = \frac{e^x – e^{-x}}{e^x + e^{-x}}, \quad \tanh'(x) = 1 – \tanh^2(x)
$$

  • 优点:输出以 0 为中心(缓解梯度震荡)
  • 缺点:饱和区梯度仍会趋近于 0

ReLU

$$
\text{ReLU}(x) = \max(0,x), \quad \text{ReLU}'(x) = \begin{cases}
1 & \text{if} x > 0 \
0 & \text{otherwise}
\end{cases}
$$

  • 优点:计算高效,缓解梯度消失
  • 风险:负半轴梯度为 0 可能导致神经元 ” 死亡 ”

避坑实践指南

梯度消失 / 爆炸对策

  1. 初始化方法
  2. Xavier 初始化:$W \sim \mathcal{N}(0, \sqrt{2/(n_{in} + n_{out})})$
  3. He 初始化(ReLU 专用):$W \sim \mathcal{N}(0, \sqrt{2/n_{in}})$

  4. 梯度裁剪

    grad_norm = np.linalg.norm(grad)
    if grad_norm > threshold:
        grad = grad * threshold / grad_norm

MNIST 实战建议

# 数据预处理
X_train = X_train.reshape(-1, 784) / 255.0
Y_train = np.eye(10)[y_train]  # one-hot 编码

# 超参数设置
learning_rate = 0.1
epochs = 50
batch_size = 64

for epoch in range(epochs):
    for i in range(0, X_train.shape[0], batch_size):
        X_batch = X_train[i:i+batch_size]
        Y_batch = Y_train[i:i+batch_size]

        # 前向传播
        Z1, A1, Z2, A2 = forward(X_batch, W1, b1, W2, b2)

        # 计算损失
        loss = -np.sum(Y_batch * np.log(A2 + 1e-8)) / batch_size

        # 反向传播
        dZ2 = (A2 - Y_batch) / batch_size
        dW1, db1, dW2, db2 = backward(X_batch, A1, W2, dZ2)

        # 更新参数
        W1 -= learning_rate * dW1
        b1 -= learning_rate * db1
        W2 -= learning_rate * dW2
        b2 -= learning_rate * db2

实验任务

  1. 将 ReLU 替换为 Sigmoid,观察训练收敛速度变化
  2. 尝试不同学习率(0.01, 0.1, 1.0)对准确率的影响
  3. 添加 L2 正则化项防止过拟合

核心收获:神经网络的基础实现并不复杂,关键在于理解数据流动与梯度传播的机制。建议读者动手实现一遍代码,比单纯阅读理论更能加深理解。

正文完
 0
评论(没有评论)