共计 2839 个字符,预计需要花费 8 分钟才能阅读完成。
为什么需要神经网络?
传统机器学习模型(如逻辑回归、决策树)在处理图像、语音等高维非线性数据时表现有限。比如:

- 线性不可分问题:XOR 分类任务中,单层感知机无法找到分离超平面
- 特征工程依赖:手工设计特征耗时且难以捕捉复杂模式(如像素间的空间层次关系)
- 维度灾难:当特征维度激增时,传统方法计算复杂度呈指数增长
神经网络通过多层非线性变换自动学习特征表示,其核心优势在于:
- 层次化特征提取:底层网络捕捉边缘 / 纹理等低级特征,高层组合出语义概念
- 端到端学习:从原始输入到最终输出无需人工干预特征设计
- 通用近似定理:单隐层网络即可逼近任意连续函数(需足够多神经元)
一、前向传播:数据如何流动
前向传播是输入数据逐层计算得到输出的过程。以单隐层网络为例:
# 输入数据 (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)$
二、反向传播:梯度如何回传
通过链式法则计算损失对参数的梯度,以下推导均针对单个样本:
-
输出层梯度(使用交叉熵损失):
$$
\frac{\partial L}{\partial Z_2} = A_2 – y \quad (\text{真实标签} y \text{的 one-hot 编码})
$$ -
隐藏层梯度:
$$
\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 导数为阶跃函数})
$$ -
参数更新:
$$
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 可能导致神经元 ” 死亡 ”
避坑实践指南
梯度消失 / 爆炸对策
- 初始化方法:
- Xavier 初始化:$W \sim \mathcal{N}(0, \sqrt{2/(n_{in} + n_{out})})$
-
He 初始化(ReLU 专用):$W \sim \mathcal{N}(0, \sqrt{2/n_{in}})$
-
梯度裁剪:
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
实验任务:
- 将 ReLU 替换为 Sigmoid,观察训练收敛速度变化
- 尝试不同学习率(0.01, 0.1, 1.0)对准确率的影响
- 添加 L2 正则化项防止过拟合
核心收获:神经网络的基础实现并不复杂,关键在于理解数据流动与梯度传播的机制。建议读者动手实现一遍代码,比单纯阅读理论更能加深理解。
