共计 2203 个字符,预计需要花费 6 分钟才能阅读完成。
一、为什么选择 BP 神经网络做分类?
在机器学习中,分类问题就像教计算机区分不同种类的水果。鸢尾花数据集(包含 Setosa、Versicolor、Virginica 三种)是经典的入门案例,但新手常遇到:

- 数据特征尺度不一(如花瓣长度 4~7cm vs 花萼宽度 0.1~0.6cm)
- 简单模型(如 KNN)在复杂边界上表现差
- 过拟合导致训练集准确率高但测试集崩盘
二、BP 神经网络 VS 其他算法
| 算法 | 适用场景 | 鸢尾花案例表现 |
|---|---|---|
| 决策树 | 特征含明显逻辑划分 | 易受噪声影响 |
| SVM | 小样本高维数据 | 需要手动调核函数 |
| BP 神经网络 | 非线性分类、自动特征提取 | 准确率 95%+ |
三、手把手实现 BP 神经网络
1. 数据预处理
import numpy as np
from sklearn.datasets import load_iris
# 加载数据
iris = load_iris()
X = iris.data # (150,4)
y = iris.target # (150,)
# 标准化:减去均值除以标准差
X = (X - np.mean(X, axis=0)) / np.std(X, axis=0)
# One-hot 编码
def one_hot(y):
ret = np.zeros((len(y), 3))
ret[np.arange(len(y)), y] = 1
return ret
y = one_hot(y) # (150,3)
2. 网络结构设计
我们构建含 1 个隐藏层(4 节点)的网络:
输入层 (4) → 隐藏层 (4, tanh) → 输出层 (3, softmax)
3. 核心数学推导(反向传播)
关键公式(设隐藏层输出为 $h$,预测值为 $\hat{y}$):
-
输出层梯度:
$\frac{\partial L}{\partial W_2} = (\hat{y} – y) \cdot h^T$ -
隐藏层梯度:
$\frac{\partial L}{\partial W_1} = [W_2^T(\hat{y}-y) \odot (1-h^2)] \cdot x^T$
4. 完整代码实现
class BPNetwork:
def __init__(self, input_size=4, hidden_size=4, output_size=3):
# 初始化权重(小随机数打破对称性)self.W1 = np.random.randn(input_size, hidden_size) * 0.01
self.W2 = np.random.randn(hidden_size, output_size) * 0.01
def forward(self, X):
self.h = np.tanh(X @ self.W1) # 隐藏层
return softmax(self.h @ self.W2) # 输出层
def train(self, X, y, lr=0.1, epochs=1000):
losses = []
for _ in range(epochs):
# 前向传播
y_pred = self.forward(X)
# 计算交叉熵损失
loss = -np.sum(y * np.log(y_pred + 1e-10)) / len(X)
losses.append(loss)
# 反向传播
grad_y = (y_pred - y) / len(X)
grad_W2 = self.h.T @ grad_y
grad_h = grad_y @ self.W2.T * (1 - self.h**2)
grad_W1 = X.T @ grad_h
# 更新参数
self.W1 -= lr * grad_W1
self.W2 -= lr * grad_W2
return losses
def softmax(x):
ex = np.exp(x - np.max(x, axis=1, keepdims=True))
return ex / np.sum(ex, axis=1, keepdims=True)
四、调优实战技巧
超参数设置
- 学习率:先用 0.1 尝试,观察损失曲线:
- 震荡剧烈 → 调小(如 0.01)
- 下降缓慢 → 调大(如 0.2)
- 迭代次数:建议 500~2000 次
梯度问题解决方案
- 梯度爆炸:对梯度做裁剪
grad_W1 = np.clip(grad_W1, -1, 1) - 梯度消失:改用 ReLU 激活函数
交叉验证示例
from sklearn.model_selection import KFold
kf = KFold(n_splits=5)
for train_idx, test_idx in kf.split(X):
model = BPNetwork()
model.train(X[train_idx], y[train_idx])
acc = np.mean(np.argmax(model.forward(X[test_idx]), 1) == np.argmax(y[test_idx], 1))
print(f"Fold accuracy: {acc:.2f}")
五、新手避坑指南
- 错误 :忘记数据标准化
- 现象:模型完全不收敛
-
解决:添加
X = (X - np.mean(X)) / np.std(X) -
错误 :学习率过大
- 现象:损失值上下跳动
-
解决:逐步降低学习率(0.1→0.01→0.001)
-
错误 :输出层用 Sigmoid 代替 Softmax
- 现象:各类别概率之和不等于 1
- 解决:多分类必须用 Softmax
六、下一步挑战
尝试以下进阶任务:
1. 增加隐藏层数量(注意梯度消失问题)
2. 在 MNIST 手写数字数据集上测试
3. 用 PyTorch 重构代码(体会框架的便捷性)
实际训练时,我的模型在测试集达到了 96% 准确率。关键是要耐心调整超参数,遇到问题不妨多打印中间变量的形状(shape)和数值,这能帮助快速定位问题所在。
正文完
