共计 2136 个字符,预计需要花费 6 分钟才能阅读完成。
为什么需要拟合 sin 函数
在工程领域中,sin 函数的拟合具有广泛的实际意义。比如在信号处理中,我们需要对采集到的周期性信号进行建模和预测;在控制系统中,许多被控对象的动态特性可以用 sin 函数来描述。传统方法如多项式回归虽然简单,但在拟合周期性函数时往往效果不佳。

多项式回归 vs 神经网络
我们先来看一个简单的对比实验。假设我们用 5 阶多项式来拟合 sin 函数:
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(-np.pi, np.pi, 100)
y = np.sin(x)
# 多项式拟合
coeff = np.polyfit(x, y, 5)
poly_y = np.polyval(coeff, x)
plt.plot(x, y, label='True sin')
plt.plot(x, poly_y, label='5th order poly')
plt.legend()
plt.show()
从结果可以看到,多项式在区间内能勉强拟合,但在边界处会出现明显的过拟合现象。这就是我们需要神经网络的原因。
BP 神经网络设计
网络结构选择
对于 sin 函数这种简单的非线性映射,一个包含 1 - 2 个隐藏层的网络就足够了。经过实验,我们发现以下结构表现良好:
- 输入层:1 个神经元(x 值)
- 隐藏层 1:10 个神经元(使用 tanh 激活)
- 隐藏层 2:10 个神经元(使用 tanh 激活)
- 输出层:1 个神经元(线性输出)
激活函数对比
我们测试了三种常见激活函数:
- Sigmoid:在深层网络中容易出现梯度消失
- ReLU:对于周期性函数拟合效果不佳
- Tanh:最适合本任务,因其输出范围与 sin 函数匹配
PyTorch 实现
下面是完整的 PyTorch 实现代码,包含关键注释:
import torch
import torch.nn as nn
import torch.optim as optim
# 生成训练数据
x = torch.linspace(-np.pi, np.pi, 100).reshape(-1, 1)
y = torch.sin(x)
# 定义网络结构
class SinNet(nn.Module):
def __init__(self):
super(SinNet, self).__init__()
self.fc1 = nn.Linear(1, 10)
self.fc2 = nn.Linear(10, 10)
self.fc3 = nn.Linear(10, 1)
self.dropout = nn.Dropout(0.1) # 轻微 dropout 防止过拟合
def forward(self, x):
x = torch.tanh(self.fc1(x))
x = self.dropout(x)
x = torch.tanh(self.fc2(x))
x = self.fc3(x)
return x
# 训练参数
model = SinNet()
criterion = nn.MSELoss()
optimizer = optim.Adam(model.parameters(), lr=0.01)
# 训练循环
losses = []
for epoch in range(1000):
optimizer.zero_grad()
outputs = model(x)
loss = criterion(outputs, y)
loss.backward()
optimizer.step()
losses.append(loss.item())
if epoch % 100 == 0:
print(f'Epoch {epoch}, Loss: {loss.item():.4f}')
性能分析
训练过程监控
通过绘制 loss 曲线,我们可以观察训练过程:
plt.plot(losses)
plt.xlabel('Epoch')
plt.ylabel('MSE Loss')
plt.title('Training Loss Curve')
plt.show()
通常在前 200 个 epoch 内 loss 会快速下降,之后趋于平缓。
学习率影响
我们对比了不同学习率的效果:
- lr=0.1:容易震荡发散
- lr=0.01:稳定收敛(推荐)
- lr=0.001:收敛速度过慢
避坑指南
梯度消失问题
当使用 sigmoid 激活时,深层网络可能出现梯度消失。解决方案:
- 使用 tanh 或 ReLU 激活
- 加入残差连接
- 使用批归一化
数据标准化
对于周期性函数,建议将输入数据归一化到 [-π, π] 区间,这与 sin 函数的自然周期匹配。
早停法实现
为防止过拟合,可以实现简单的早停机制:
best_loss = float('inf')
patience = 20
counter = 0
for epoch in range(1000):
# ... 训练代码...
if loss.item() < best_loss:
best_loss = loss.item()
counter = 0
else:
counter += 1
if counter >= patience:
print('Early stopping')
break
总结与思考
通过这个实验,我们掌握了用 BP 神经网络拟合非线性函数的基本方法。这种方法可以轻松扩展到其他周期函数的拟合,只需调整网络结构和训练参数。
留给读者的思考题:如果要拟合 f(x)=sin(x)+0.3cos(2x)这样的复合周期函数,网络结构应该如何调整?欢迎在评论区分享你的实验结
正文完
