BP神经网络实战:鸢尾花分类从原理到实现

1次阅读
没有评论

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

image.webp

一、为什么选择 BP 神经网络做分类?

在机器学习中,分类问题就像教计算机区分不同种类的水果。鸢尾花数据集(包含 Setosa、Versicolor、Virginica 三种)是经典的入门案例,但新手常遇到:

BP 神经网络实战:鸢尾花分类从原理到实现

  • 数据特征尺度不一(如花瓣长度 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}$):

  1. 输出层梯度:
    $\frac{\partial L}{\partial W_2} = (\hat{y} – y) \cdot h^T$

  2. 隐藏层梯度:
    $\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 次

梯度问题解决方案

  1. 梯度爆炸:对梯度做裁剪
    grad_W1 = np.clip(grad_W1, -1, 1)
  2. 梯度消失:改用 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}")

五、新手避坑指南

  1. 错误 :忘记数据标准化
  2. 现象:模型完全不收敛
  3. 解决:添加 X = (X - np.mean(X)) / np.std(X)

  4. 错误 :学习率过大

  5. 现象:损失值上下跳动
  6. 解决:逐步降低学习率(0.1→0.01→0.001)

  7. 错误 :输出层用 Sigmoid 代替 Softmax

  8. 现象:各类别概率之和不等于 1
  9. 解决:多分类必须用 Softmax

六、下一步挑战

尝试以下进阶任务:
1. 增加隐藏层数量(注意梯度消失问题)
2. 在 MNIST 手写数字数据集上测试
3. 用 PyTorch 重构代码(体会框架的便捷性)

实际训练时,我的模型在测试集达到了 96% 准确率。关键是要耐心调整超参数,遇到问题不妨多打印中间变量的形状(shape)和数值,这能帮助快速定位问题所在。

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