共计 2943 个字符,预计需要花费 8 分钟才能阅读完成。
神经网络基础概念回顾
在开始构建我们的神经网络之前,让我们先回顾一下几个核心概念:

- 全连接层 :神经网络中的基础层类型,每个神经元都与上一层的所有神经元相连
- 激活函数 :引入非线性因素的关键组件,常见的有 ReLU、Sigmoid、Tanh 等
- 损失函数 :衡量预测结果与真实值差异的函数,如交叉熵、均方误差
两个隐藏层网络架构设计
我们设计的网络结构如下:
- 输入层:接收原始数据
- 隐藏层 1:第一个全连接层,使用 ReLU 激活函数
- 隐藏层 2:第二个全连接层,继续使用 ReLU 激活函数
- 输出层:最终预测层,使用 Softmax 激活函数(分类任务)
这个结构可以表示为:Input → FC1 → ReLU → FC2 → ReLU → Output → Softmax
反向传播数学原理详解
反向传播的核心是链式法则。让我们分解梯度计算过程:
- 前向传播计算损失
- 从输出层开始反向计算各层梯度
- 更新权重参数
关键公式:
对于输出层:
$$\frac{\partial L}{\partial W_3} = \frac{\partial L}{\partial a_3} \cdot \frac{\partial a_3}{\partial z_3} \cdot \frac{\partial z_3}{\partial W_3}$$
对于隐藏层 2:
$$\frac{\partial L}{\partial W_2} = \frac{\partial L}{\partial a_3} \cdot \frac{\partial a_3}{\partial z_3} \cdot \frac{\partial z_3}{\partial a_2} \cdot \frac{\partial a_2}{\partial z_2} \cdot \frac{\partial z_2}{\partial W_2}$$
对于隐藏层 1:
$$\frac{\partial L}{\partial W_1} = \frac{\partial L}{\partial a_3} \cdot … \cdot \frac{\partial a_1}{\partial z_1} \cdot \frac{\partial z_1}{\partial W_1}$$
完整的 Python 实现
以下是使用 NumPy 的实现代码:
import numpy as np
class TwoLayerNN:
def __init__(self, input_size, hidden1, hidden2, output_size):
# 初始化权重
self.W1 = np.random.randn(input_size, hidden1) * 0.01
self.b1 = np.zeros((1, hidden1))
self.W2 = np.random.randn(hidden1, hidden2) * 0.01
self.b2 = np.zeros((1, hidden2))
self.W3 = np.random.randn(hidden2, output_size) * 0.01
self.b3 = np.zeros((1, output_size))
def relu(self, x):
return np.maximum(0, x)
def softmax(self, x):
exp_x = np.exp(x - np.max(x, axis=1, keepdims=True))
return exp_x / np.sum(exp_x, axis=1, keepdims=True)
def forward(self, X):
# 前向传播
self.z1 = np.dot(X, self.W1) + self.b1
self.a1 = self.relu(self.z1)
self.z2 = np.dot(self.a1, self.W2) + self.b2
self.a2 = self.relu(self.z2)
self.z3 = np.dot(self.a2, self.W3) + self.b3
self.a3 = self.softmax(self.z3)
return self.a3
def backward(self, X, y, learning_rate):
# 反向传播
m = X.shape[0]
# 输出层梯度
dz3 = self.a3 - y
dw3 = np.dot(self.a2.T, dz3) / m
db3 = np.sum(dz3, axis=0, keepdims=True) / m
# 隐藏层 2 梯度
dz2 = np.dot(dz3, self.W3.T) * (self.z2 > 0)
dw2 = np.dot(self.a1.T, dz2) / m
db2 = np.sum(dz2, axis=0, keepdims=True) / m
# 隐藏层 1 梯度
dz1 = np.dot(dz2, self.W2.T) * (self.z1 > 0)
dw1 = np.dot(X.T, dz1) / m
db1 = np.sum(dz1, axis=0, keepdims=True) / m
# 更新权重
self.W3 -= learning_rate * dw3
self.b3 -= learning_rate * db3
self.W2 -= learning_rate * dw2
self.b2 -= learning_rate * db2
self.W1 -= learning_rate * dw1
self.b1 -= learning_rate * db1
def train(self, X, y, epochs, learning_rate):
for epoch in range(epochs):
# 前向传播
output = self.forward(X)
# 计算损失
loss = -np.sum(y * np.log(output + 1e-8)) / X.shape[0]
# 反向传播
self.backward(X, y, learning_rate)
if epoch % 100 == 0:
print(f"Epoch {epoch}, Loss: {loss:.4f}")
训练过程可视化
训练过程中,我们可以记录并可视化以下指标:
- 训练损失曲线:观察损失是否在下降
- 验证准确率:评估模型泛化能力
- 权重分布:检查梯度是否合理
import matplotlib.pyplot as plt
# 训练后绘制损失曲线
plt.plot(loss_history)
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.title('Training Loss Curve')
plt.show()
常见问题与调试技巧
- 梯度消失 :
- 解决方法:使用 ReLU 激活函数、批归一化、残差连接
-
检查:打印各层梯度大小,确保它们不会指数级减小
-
学习率选择 :
- 开始建议使用 1e- 3 到 1e- 5 之间的值
-
可以采用学习率衰减策略
-
初始化问题 :
- 使用 Xavier 或 He 初始化
- 避免全零初始化
延伸阅读建议
- 更复杂的网络结构:
- 卷积神经网络 (CNN)
- 循环神经网络 (RNN)
-
Transformer 结构
-
优化技巧:
- 批量归一化 (BatchNorm)
- Dropout 正则化
-
自适应优化器 (Adam, RMSprop)
-
高级主题:
- 注意力机制
- 自监督学习
- 元学习
总结
通过本文,我们从零实现了一个包含两个隐藏层的全连接神经网络,并详细讲解了反向传播算法的数学原理和实现细节。这个练习不仅帮助我们理解神经网络的底层工作原理,也为后续学习更复杂的模型打下了坚实基础。建议读者尝试修改网络结构或参数,观察对训练效果的影响,这是深入理解神经网络的最佳方式。
