BP神经网络拟合sin曲线:从数学原理到Python实战

1次阅读
没有评论

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

image.webp

背景介绍

在机器学习中,使用神经网络拟合非线性函数是一项基础但重要的任务。sin 函数因其周期性、光滑性和非线性特性,成为验证神经网络能力的理想选择。特别是,sin 函数的非线性特性可以很好地测试神经网络的拟合能力,而不会因为过于复杂而让初学者感到困惑。

BP 神经网络拟合 sin 曲线:从数学原理到 Python 实战

  • 非线性挑战:sin 函数的输出在输入变化时是非线性的,这意味着简单的线性回归无法准确拟合它。神经网络通过引入非线性激活函数,能够学习这种复杂的映射关系。
  • 周期性特性:sin 函数的周期性也要求神经网络能够捕捉重复模式,这对隐藏层的设计提出了要求。

网络架构设计

为了拟合 sin 函数,我们设计一个简单的三层神经网络(输入层、隐藏层、输出层)。

  1. 输入层:1 个节点,对应输入 x 值(如 0 到 2π 范围内的点)。
  2. 隐藏层:建议使用 1 - 2 层,每层节点数在 5 -20 之间。过多的节点可能导致过拟合,而过少则可能无法捕捉 sin 的复杂性。
  3. 输出层 :1 个节点,输出预测的 sin(x) 值。

关键实现细节

激活函数选择

激活函数决定了网络的非线性能力。以下是常见激活函数在 sin 拟合中的表现:

  • sigmoid:输出范围(0,1),可能导致梯度消失问题。
  • tanh:输出范围 (-1,1),更适合 sin 函数,因为 sin 的输出也在[-1,1] 之间。
  • ReLU:简单高效,但可能在某些区域“死亡”。

对于 sin 拟合,tanh通常是更好的选择。

损失函数定义

我们使用均方误差(MSE)作为损失函数:

$$
MSE = \frac{1}{N} \sum_{i=1}^{N} (y_i – \hat{y_i})^2
$$

其中,$y_i$ 是真实值,$\hat{y_i}$ 是预测值。

反向传播的数学推导

反向传播通过链式法则计算梯度。以下是关键公式:

  1. 输出层误差
    $$
    \delta^{L} = (\hat{y} – y) \cdot f'(z^{L})
    $$
  2. 隐藏层误差
    $$
    \delta^{l} = (W^{l+1})^T \delta^{l+1} \cdot f'(z^{l})
    $$
  3. 权重更新
    $$
    W^{l} = W^{l} – \eta \cdot \delta^{l} (a^{l-1})^T
    $$

完整 Python 代码实现

以下是使用 NumPy 从零实现的代码:

import numpy as np
import matplotlib.pyplot as plt

# 数据生成
def generate_data(num_points=100):
    x = np.linspace(0, 2 * np.pi, num_points)
    y = np.sin(x)
    return x.reshape(-1, 1), y.reshape(-1, 1)

# 初始化网络
def initialize_network(input_size, hidden_size, output_size):
    np.random.seed(42)
    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': W1, 'b1': b1, 'W2': W2, 'b2': b2}

# 激活函数(tanh)def tanh(x):
    return np.tanh(x)

def tanh_derivative(x):
    return 1 - np.tanh(x) ** 2

# 前向传播
def forward_propagation(x, params):
    W1, b1, W2, b2 = params['W1'], params['b1'], params['W2'], params['b2']
    z1 = np.dot(x, W1) + b1
    a1 = tanh(z1)
    z2 = np.dot(a1, W2) + b2
    return {'a1': a1, 'z2': z2}

# 反向传播
def backward_propagation(x, y, params, forward_cache):
    W1, W2 = params['W1'], params['W2']
    a1, z2 = forward_cache['a1'], forward_cache['z2']
    m = x.shape[0]

    delta2 = (z2 - y)  # MSE 导数
    dW2 = np.dot(a1.T, delta2) / m
    db2 = np.sum(delta2, axis=0, keepdims=True) / m

    delta1 = np.dot(delta2, W2.T) * tanh_derivative(a1)
    dW1 = np.dot(x.T, delta1) / m
    db1 = np.sum(delta1, axis=0, keepdims=True) / m

    return {'dW1': dW1, 'db1': db1, 'dW2': dW2, 'db2': db2}

# 参数更新
def update_params(params, grads, learning_rate):
    params['W1'] -= learning_rate * grads['dW1']
    params['b1'] -= learning_rate * grads['db1']
    params['W2'] -= learning_rate * grads['dW2']
    params['b2'] -= learning_rate * grads['db2']
    return params

# 训练函数
def train(x, y, params, epochs, learning_rate):
    loss_history = []
    for epoch in range(epochs):
        # 前向传播
        cache = forward_propagation(x, params)
        z2 = cache['z2']
        loss = np.mean((z2 - y) ** 2)
        loss_history.append(loss)

        # 反向传播
        grads = backward_propagation(x, y, params, cache)

        # 更新参数
        params = update_params(params, grads, learning_rate)

        if epoch % 100 == 0:
            print(f'Epoch {epoch}, Loss: {loss}')

    return params, loss_history

# 主程序
if __name__ == '__main__':
    x, y = generate_data()
    params = initialize_network(1, 10, 1)
    params, loss_history = train(x, y, params, 1000, 0.01)

    # 可视化
    plt.plot(loss_history)
    plt.xlabel('Epoch')
    plt.ylabel('Loss')
    plt.title('Training Loss')
    plt.show()

    # 预测
    cache = forward_propagation(x, params)
    y_pred = cache['z2']

    plt.plot(x, y, label='True sin(x)')
    plt.plot(x, y_pred, label='Predicted sin(x)')
    plt.legend()
    plt.show()

调优与避坑指南

学习率设置

  • 学习率过大:可能导致损失震荡甚至发散。
  • 学习率过小:训练速度慢,可能陷入局部最优。

建议从 0.01 开始尝试,根据损失曲线调整。

梯度消失问题

  • 识别:如果损失长时间不下降,可能是梯度消失。
  • 解决:使用 tanh 或 ReLU 激活函数,避免深层网络。

过拟合预防

  • 数据量:确保足够的训练数据。
  • 正则化:可以尝试 L2 正则化或 Dropout(虽然本例中不必要)。

效果验证

通过 Matplotlib 绘制训练过程中损失下降曲线和最终拟合效果。可以看到,随着 epoch 增加,预测曲线逐渐逼近真实的 sin 曲线。

思考题

如何将本方法推广到其他周期函数的拟合?

  • 可以尝试调整隐藏层大小或激活函数。
  • 对于更复杂的周期函数(如方波),可能需要更深的网络或不同的激活函数。

希望这篇文章能帮助你理解 BP 神经网络如何拟合非线性函数,并为你未来的机器学习项目打下基础!

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