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

1次阅读
没有评论

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

image.webp

1. 背景与痛点

分类任务是机器学习中的经典问题,但在实际应用中常遇到几个挑战:

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

  • 数据不平衡 :某些类别样本量过少,导致模型偏向多数类
  • 特征选择 :无关特征会干扰模型学习,有效特征组合能显著提升性能
  • 模型过拟合 :在训练集表现良好但测试集差,泛化能力不足

鸢尾花数据集(含 setosa/versicolor/virginica 三类)是理想的入门案例,其特征明确(花萼 / 花瓣长宽)、样本均衡(每类 50 条),适合演示 BP 神经网络的基础实现。

2. 技术对比

不同分类算法各有优劣:

  • SVM:适合小样本、高维数据,但对大规模数据计算成本高
  • 决策树 :可解释性强,但容易过拟合
  • BP 神经网络 :能自动学习特征组合,适合复杂非线性关系,但需要调参经验

3. 核心实现

3.1 数据预处理

import numpy as np
from sklearn.datasets import load_iris

# 加载数据
iris = load_iris()
X = iris.data  # 特征 (150,4)
y = iris.target  # 标签 (150,)

# 标准化(均值 0,标准差 1)X = (X - np.mean(X, axis=0)) / np.std(X, axis=0)

# one-hot 编码
def one_hot(y):
    n_classes = len(np.unique(y))
    return np.eye(n_classes)[y]

y_onehot = one_hot(y)  # (150,3)

3.2 BP 神经网络数学原理

关键公式:

  1. 前向传播 :
    $$z^l = W^l a^{l-1} + b^l$$
    $$a^l = \sigma(z^l)$$

  2. 损失函数(交叉熵):
    $$L = -\frac{1}{N}\sum_{i=1}^N \sum_{j=1}^C y_{ij}\log(a_{ij}^L)$$

  3. 反向传播 :

  4. 输出层误差:
    $$\delta^L = a^L – y$$
  5. 隐藏层误差:
    $$\delta^l = (W^{l+1})^T \delta^{l+1} \odot \sigma'(z^l)$$
  6. 参数梯度:
    $$\frac{\partial L}{\partial W^l} = \delta^l (a^{l-1})^T$$
    $$\frac{\partial L}{\partial b^l} = \delta^l$$

4. 完整代码实现

class BPNetwork:
    def __init__(self, input_size, hidden_size, output_size):
        self.W1 = np.random.randn(input_size, hidden_size) * 0.01
        self.b1 = np.zeros((1, hidden_size))
        self.W2 = np.random.randn(hidden_size, output_size) * 0.01
        self.b2 = np.zeros((1, 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
        exp_z = np.exp(self.z2 - np.max(self.z2, axis=1, keepdims=True))
        self.a2 = exp_z / np.sum(exp_z, axis=1, keepdims=True)
        return self.a2

    def backward(self, X, y, lr=0.01):
        m = X.shape[0]
        # 输出层误差
        dz2 = self.a2 - y
        dw2 = np.dot(self.a1.T, dz2) / m
        db2 = np.sum(dz2, axis=0, keepdims=True) / m
        # 隐藏层误差
        dz1 = np.dot(dz2, self.W2.T) * self.a1 * (1 - self.a1)
        dw1 = np.dot(X.T, dz1) / m
        db1 = np.sum(dz1, axis=0, keepdims=True) / m
        # 更新参数
        self.W2 -= lr * dw2
        self.b2 -= lr * db2
        self.W1 -= lr * dw1
        self.b1 -= lr * db1

    def train(self, X, y, epochs=1000, lr=0.01):
        losses = []
        for i in range(epochs):
            output = self.forward(X)
            loss = -np.mean(y * np.log(output + 1e-8))
            losses.append(loss)
            self.backward(X, y, lr)
            if i % 100 == 0:
                print(f'Epoch {i}, Loss: {loss:.4f}')
        return losses

5. 调优指南

关键超参数设置 :

  1. 学习率:建议从 0.01 开始尝试,过大导致震荡,过小收敛慢
  2. 隐藏层节点数:通常取输入特征的 2 - 3 倍(本例选择 8 个)
  3. 迭代次数:观察损失曲线,当下降平缓时停止

防梯度消失技巧 :

  • 使用 Xavier 初始化权重
  • 添加 Batch Normalization 层
  • 改用 ReLU 激活函数

6. 常见错误排查

  1. 损失不下降 :检查学习率是否过小 / 数据未归一化
  2. 输出全零 :可能是初始化权重过大导致梯度消失
  3. 准确率波动大 :减小学习率或增加批量大小

7. 扩展思考

可以尝试:
1. 增加隐藏层数量(深度网络)
2. 更换激活函数(如 ReLU)
3. 应用于手写数字识别等其它数据集

通过这个案例,我们实现了从理论到实践的完整闭环。建议读者动手调整参数观察效果,这是理解神经网络行为的最佳方式。

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