共计 1988 个字符,预计需要花费 5 分钟才能阅读完成。
背景痛点:为什么需要 BP 神经网络
鸢尾花分类是经典的机器学习入门案例,传统方法如决策树或 SVM 虽然简单,但存在明显局限:

- 特征交互能力弱:花瓣 / 花萼的长宽组合特征需要人工设计
- 非线性边界处理差:当类别边界呈现复杂曲线时准确率骤降
- 泛化能力有限:对噪声数据和特征尺度变化敏感
而 BP 神经网络通过多层非线性变换能自动学习特征组合,但实现时会遇到:
- 梯度消失:深层网络在反向传播时梯度指数级衰减
- 参数敏感:学习率过大导致震荡,过小则收敛缓慢
- 过拟合:在 150 条的小数据集上尤为明显
技术实现:三层网络拆解
网络结构设计
采用 4 -6- 3 的三层结构:
- 输入层:4 个节点对应花萼长宽 + 花瓣长宽
- 隐藏层:6 个节点(经实验验证的平衡点)
- 输出层:3 个节点使用 Softmax 表示三类鸢尾花概率
激活函数选择
隐藏层使用 Sigmoid 而非 ReLU 的原因:
- 输出范围 (0,1) 适合概率场景
- 求导简单:$\sigma'(x) = \sigma(x)(1-\sigma(x))$
- 避免 ReLU 的 ” 死亡神经元 ” 问题
核心公式推导
关键步骤的数学表达:
- 前向传播:
$$h_j = \sigma(\sum_{i} w_{ij}x_i + b_j)$$ - 交叉熵损失:
$$L = -\sum_{k} y_k \log(p_k)$$ - 权重更新:
$$\Delta w_{ij} = -\eta \frac{\partial L}{\partial w_{ij}}$$
代码实现:从零搭建
import numpy as np
class BPNetwork:
def __init__(self, input_size, hidden_size, output_size):
# Xavier 初始化防止梯度消失
self.W1 = np.random.randn(input_size, hidden_size) * np.sqrt(1/input_size)
self.b1 = np.zeros(hidden_size)
self.W2 = np.random.randn(hidden_size, output_size) * np.sqrt(1/hidden_size)
self.b2 = np.zeros(output_size)
def sigmoid(self, x):
return 1 / (1 + np.exp(-x))
def forward(self, X):
self.z1 = np.dot(X, self.W1) + self.b1
self.a1 = self.sigmoid(self.z1)
self.z2 = np.dot(self.a1, self.W2) + self.b2
# Softmax 输出
exp_scores = np.exp(self.z2)
self.probs = exp_scores / np.sum(exp_scores, axis=1, keepdims=True)
return self.probs
def backward(self, X, y, learning_rate):
# 反向传播计算梯度
delta3 = self.probs
delta3[range(len(X)), y] -= 1
dW2 = np.dot(self.a1.T, delta3)
db2 = np.sum(delta3, axis=0)
delta2 = np.dot(delta3, self.W2.T) * (self.a1 * (1 - self.a1))
dW1 = np.dot(X.T, delta2)
db1 = np.sum(delta2, axis=0)
# 参数更新(含 L2 正则化)reg_lambda = 0.01
self.W2 -= learning_rate * (dW2 + reg_lambda * self.W2)
self.b2 -= learning_rate * db2
self.W1 -= learning_rate * (dW1 + reg_lambda * self.W1)
self.b1 -= learning_rate * db1
性能优化实战技巧
学习率对比实验
通过损失曲线可视化发现:
- η=0.1:快速收敛但后期震荡
- η=0.01:稳定下降(最终选择)
- η=0.001:收敛过慢需更多迭代
正则化实现
在反向传播代码中可见,L2 正则项通过 reg_lambda * self.W 实现,能有效控制权重幅度。
避坑指南
- 权重初始化:全零初始化会导致对称更新问题,推荐 Xavier/Glorot 初始化
- 批量归一化:在隐藏层后添加
(a1 - np.mean(a1))/np.std(a1) - 早停法:当验证集准确率连续 3 轮不提升时终止训练
延伸思考
- 部署为 API:使用 Flask 封装预测接口,输入特征返回 JSON 格式的类别概率
- PyTorch 重构:比较与手动实现的性能差异,特别注意:
- 自动求导功能
- GPU 加速效果
- 内置优化器 (Adam/SGD) 的表现
通过这个项目,你会发现神经网络的『黑箱』其实可以被逐步拆解。建议下一步尝试:
- 增加隐藏层观察深度网络的影响
- 用 PCA 降维可视化决策边界
- 在 sklearn 其他数据集上测试泛化能力
正文完
