共计 3773 个字符,预计需要花费 10 分钟才能阅读完成。
背景与痛点
鸢尾花数据集(Iris Dataset)是机器学习领域的经典入门数据集,包含 150 个样本,每个样本有 4 个特征(花萼长度、花萼宽度、花瓣长度、花瓣宽度)和 3 个类别标签(Setosa、Versicolour、Virginica)。这个数据集非常适合用来学习分类算法,因为它的特征维度适中,类别区分度明显。

然而,初学者在使用 BP 神经网络实现鸢尾花分类时,常常会遇到以下问题:
- 梯度消失:当网络层数较多时,梯度在反向传播过程中会逐渐变小,导致权重更新缓慢甚至停滞。
- 过拟合:模型在训练集上表现很好,但在测试集上表现不佳,泛化能力差。
- 调参困难:学习率、隐藏层节点数等超参数的选择对模型性能影响很大,但缺乏明确的指导原则。
技术实现
网络结构设计
- 输入层:鸢尾花数据集有 4 个特征,因此输入层节点数设为 4。
- 隐藏层:选择 1 个隐藏层,节点数设为 10(经验法则:隐藏层节点数可以是输入层节点数的 2 - 3 倍)。
- 输出层:有 3 个类别,因此输出层节点数设为 3,使用 Softmax 函数输出概率分布。
激活函数选择
Sigmoid 函数是 BP 神经网络中常用的激活函数,其公式为:
$$
\sigma(x) = \frac{1}{1 + e^{-x}}
$$
选择 Sigmoid 的原因:
- 输出范围在 (0,1) 之间,适合概率输出。
- 导数容易计算:(\sigma'(x) = \sigma(x)(1 – \sigma(x)))。
反向传播过程
反向传播的核心是通过链式法则计算损失函数对权重的梯度。以下是关键步骤:
- 计算输出层误差:
$$
\delta^{(L)} = (y – \hat{y}) \odot \sigma'(z^{(L)})
$$ - 计算隐藏层误差:
$$
\delta^{(l)} = (W^{(l+1)})^T \delta^{(l+1)} \odot \sigma'(z^{(l)})
$$ - 更新权重:
$$
W^{(l)} \leftarrow W^{(l)} – \eta \delta^{(l)} (a^{(l-1)})^T
$$
代码示例
import numpy as np
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
# 加载数据
iris = load_iris()
X, y = iris.data, iris.target
# 数据标准化
scaler = StandardScaler()
X = scaler.fit_transform(X)
# 将标签转换为 one-hot 编码
def to_one_hot(y, num_classes):
return np.eye(num_classes)[y]
y_one_hot = to_one_hot(y, 3)
# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y_one_hot, test_size=0.2, random_state=42)
# 定义网络结构
input_size = 4
hidden_size = 10
output_size = 3
# 初始化权重
def initialize_weights(input_size, hidden_size, output_size):
W1 = np.random.randn(input_size, hidden_size) * 0.01
b1 = np.zeros((1, hidden_size))
W2 = np.random.randn(hidden_size, output_size) * 0.01
b2 = np.zeros((1, output_size))
return W1, b1, W2, b2
W1, b1, W2, b2 = initialize_weights(input_size, hidden_size, output_size)
# 定义激活函数
def sigmoid(x):
return 1 / (1 + np.exp(-x))
def sigmoid_derivative(x):
return sigmoid(x) * (1 - sigmoid(x))
# 定义损失函数(交叉熵)def cross_entropy_loss(y_true, y_pred):
m = y_true.shape[0]
return -np.sum(y_true * np.log(y_pred + 1e-15)) / m
# 前向传播
def forward_propagation(X, W1, b1, W2, b2):
z1 = np.dot(X, W1) + b1
a1 = sigmoid(z1)
z2 = np.dot(a1, W2) + b2
a2 = np.exp(z2) / np.sum(np.exp(z2), axis=1, keepdims=True)
return a1, a2
# 反向传播
def backward_propagation(X, y, a1, a2, W2):
m = X.shape[0]
dz2 = a2 - y
dW2 = np.dot(a1.T, dz2) / m
db2 = np.sum(dz2, axis=0, keepdims=True) / m
dz1 = np.dot(dz2, W2.T) * sigmoid_derivative(a1)
dW1 = np.dot(X.T, dz1) / m
db1 = np.sum(dz1, axis=0, keepdims=True) / m
return dW1, db1, dW2, db2
# 训练模型
def train(X, y, W1, b1, W2, b2, learning_rate=0.1, epochs=1000):
losses = []
for epoch in range(epochs):
a1, a2 = forward_propagation(X, W1, b1, W2, b2)
loss = cross_entropy_loss(y, a2)
losses.append(loss)
dW1, db1, dW2, db2 = backward_propagation(X, y, a1, a2, W2)
W1 -= learning_rate * dW1
b1 -= learning_rate * db1
W2 -= learning_rate * dW2
b2 -= learning_rate * db2
if epoch % 100 == 0:
print(f'Epoch {epoch}, Loss: {loss}')
return W1, b1, W2, b2, losses
# 训练模型
W1, b1, W2, b2, losses = train(X_train, y_train, W1, b1, W2, b2, learning_rate=0.1, epochs=1000)
# 预测函数
def predict(X, W1, b1, W2, b2):
_, a2 = forward_propagation(X, W1, b1, W2, b2)
return np.argmax(a2, axis=1)
# 评估模型
y_pred = predict(X_test, W1, b1, W2, b2)
y_true = np.argmax(y_test, axis=1)
accuracy = np.mean(y_pred == y_true)
print(f'Test Accuracy: {accuracy * 100:.2f}%')
优化实践
学习率调整
学习率是影响模型收敛速度的关键参数。以下是不同学习率的对比:
- 学习率 =0.01:收敛慢,可能需要更多 epoch。
- 学习率 =0.1:通常是一个较好的初始选择。
- 学习率 =1.0:可能导致震荡甚至发散。
L2 正则化
为了防止过拟合,可以在损失函数中加入 L2 正则化项:
def cross_entropy_loss_with_l2(y_true, y_pred, W1, W2, lambda_val=0.01):
m = y_true.shape[0]
cross_entropy = -np.sum(y_true * np.log(y_pred + 1e-15)) / m
l2_penalty = (np.sum(W1 ** 2) + np.sum(W2 ** 2)) * lambda_val / (2 * m)
return cross_entropy + l2_penalty
模型评估指标
除了准确率,还可以使用混淆矩阵来更详细地评估模型性能:
from sklearn.metrics import confusion_matrix
cm = confusion_matrix(y_true, y_pred)
print('Confusion Matrix:')
print(cm)
避坑指南
- 特征缩放:输入特征的范围差异较大时,务必进行标准化或归一化,否则可能导致梯度下降缓慢或不收敛。
- 隐藏层节点数:通常设置为输入层节点数的 2 - 3 倍,但可以通过交叉验证来调整。
- 训练终止条件:可以设置早停(early stopping)机制,当验证集上的损失不再下降时停止训练。
延伸思考
- 激活函数改进:可以尝试 ReLU 激活函数,它在深层网络中表现更好,能缓解梯度消失问题。
- 可视化权重:通过绘制权重分布图,可以直观地了解模型的学习情况。
- 增加隐藏层:对于更复杂的数据集,可以尝试增加隐藏层数,构建更深的网络。
总结
本文详细介绍了如何使用 BP 神经网络实现鸢尾花分类,从网络结构设计到代码实现,再到优化和评估,涵盖了整个流程的关键步骤。希望这篇指南能帮助初学者快速上手 BP 神经网络,并将其应用到实际项目中。
正文完
