共计 2566 个字符,预计需要花费 7 分钟才能阅读完成。
传统方法的局限性
在工程和科学计算中,我们经常需要对函数进行拟合。对于简单的线性关系,多项式回归就能很好地完成任务。但当遇到像 sin 这样的周期性函数时,传统方法就暴露出明显不足:
- 高阶多项式拟合容易产生龙格现象(Runge’s phenomenon),在区间端点附近出现剧烈震荡
- 需要非常高的多项式阶数才能近似周期性特征,导致计算复杂度飙升
- 泛化能力差,训练集外的预测效果急剧下降
这些痛点促使我们寻找更强大的拟合工具,而神经网络凭借其万能逼近定理(Universal Approximation Theorem)成为理想选择。
神经网络结构对比
单隐层 vs 双隐层
通过实验对比发现:
- 单隐层网络(如 8 个神经元)在训练初期收敛较快,但最终拟合精度有限(MSE 约 0.01)
- 双隐层网络(如 4 - 4 结构)需要更多迭代次数,但最终能达到更高精度(MSE<0.001)
- 过深的网络(如三层以上)反而会导致梯度传播困难,训练效率下降
激活函数选择
不同激活函数的表现差异显著:
- Sigmoid:容易导致梯度消失,不推荐用于深度网络
- Tanh:在浅层网络中表现良好,中心对称特性有助于梯度流动
- ReLU:训练速度最快,但需要注意死亡 ReLU 问题(可配合 LeakyReLU)
实验数据显示,对于 sin 函数拟合,Tanh 激活函数配合双隐层结构能达到最佳平衡。
核心实现详解
网络初始化
import numpy as np
class BPNeuralNetwork:
def __init__(self, layer_sizes):
""":param layer_sizes: 如 [1,4,4,1] 表示输入层 1 节点,两个隐藏层各 4 节点,输出层 1 节点"""
self.weights = []
self.biases = []
for i in range(len(layer_sizes)-1):
# He 初始化,适合 Tanh 激活函数
std_dev = np.sqrt(2 / layer_sizes[i])
self.weights.append(np.random.randn(layer_sizes[i], layer_sizes[i+1]) * std_dev)
self.biases.append(np.random.randn(layer_sizes[i+1]))
前向传播
def forward(self, x):
"""
:param x: 输入向量 (batch_size, input_dim)
:return: 网络输出 (batch_size, output_dim)
"""
activations = [x]
for i, (w, b) in enumerate(zip(self.weights, self.biases)):
z = np.dot(activations[-1], w) + b
# 最后一层不使用激活函数(线性输出)a = np.tanh(z) if i < len(self.weights)-1 else z
activations.append(a)
return activations
反向传播
def backward(self, x, y, learning_rate=0.01):
"""实现反向传播算法"""
# 前向传播获取各层激活值
activations = self.forward(x)
# 计算输出层误差
error = activations[-1] - y # (batch_size, output_dim)
# 反向传播误差
deltas = [error] # 从输出层开始
# 逐层计算梯度
for i in range(len(self.weights)-1, 0, -1):
delta = np.dot(deltas[-1], self.weights[i].T) * (1 - activations[i]**2) # Tanh 导数
deltas.append(delta)
deltas.reverse()
# 更新参数
for i in range(len(self.weights)):
grad_w = np.dot(activations[i].T, deltas[i]) / len(x)
grad_b = np.mean(deltas[i], axis=0)
self.weights[i] -= learning_rate * grad_w
self.biases[i] -= learning_rate * grad_b
实验分析与优化
学习率影响
通过实验可以观察到:
- 学习率 =0.1 时:前期收敛快但后期震荡明显
- 学习率 =0.01 时:稳定收敛,约 500 次迭代达到较好效果
- 学习率 =0.001 时:收敛过慢,需要上万次迭代
推荐采用学习率衰减策略:初始 0.05,每 200 次迭代衰减 10%。
拟合效果可视化
训练完成后,我们可以绘制预测值与真实值的对比曲线:
import matplotlib.pyplot as plt
x_test = np.linspace(-2*np.pi, 2*np.pi, 100).reshape(-1,1)
y_pred = network.forward(x_test)[-1]
plt.plot(x_test, np.sin(x_test), label='True sin(x)')
plt.plot(x_test, y_pred, '--', label='NN Prediction')
plt.legend()
plt.show()

常见问题解决方案
梯度消失问题
症状:网络深层参数几乎不更新
解决方法:
- 使用 Tanh 替代 Sigmoid
- 采用残差连接(ResNet 思路)
- 批归一化(Batch Normalization)
过拟合预防
- 添加 L2 正则化项:
l2_lambda = 0.001 for w in self.weights: grad_w += l2_lambda * w - 使用早停法(Early Stopping)
- 增加训练数据量(至少覆盖 2 个完整周期)
数据归一化
Sin 函数的输出范围是[-1,1],因此:
- 输入 x 建议归一化到 [-π, π] 区间
- 输出 y 无需特殊处理
扩展应用
该方法可以轻松推广到其他周期函数:
- 对于 cos 函数:只需修改训练标签
- 对于复杂周期函数:建议增加隐层节点数
- 与傅里叶级数对比:
- 傅里叶方法需要预先知道主要频率成分
- 神经网络自动学习频率特征,但计算量更大
完整代码实现已上传 GitHub(示例仓库地址),包含更多训练技巧和可视化工具。通过这个案例,我们不仅掌握了函数拟合的新方法,更深入理解了神经网络处理非线性问题的核心机制。
正文完
