如何用感知器构建cos(x)的二次近似多项式(训练不收敛问题)
问题分析与解决方案
核心问题
你的代码不收敛主要有三个关键问题:
- 不必要的sigmoid激活:你要拟合的是二次多项式逼近
cos(x),这本质是线性回归问题(权重和输入是线性组合),不需要用sigmoid激活函数,反而会引入非线性干扰拟合效果。 - 缺少学习率:权重更新时直接累加梯度,步长过大导致权重震荡,无法收敛。
- sigmoid导数计算错误:如果一定要保留sigmoid,
sigmoid_prime的输入应该是sigmoid的原始输入(即numpy.dot(training_inputs, w)),而非输出值。
修正后的代码
方案1:去掉sigmoid,直接拟合cos(x)(推荐)
这是最直接的线性回归拟合,完全匹配你用二次多项式逼近的需求:
import numpy import random import math import matplotlib.pyplot as plt # 生成0到π/4的随机点 R = [random.uniform(0, math.pi/4) for _ in range(1000)] # 构造训练输入:[x², x, 1] training_inputs = numpy.ones((len(R), 3)) for idx, x in enumerate(R): training_inputs[idx, 0] = x**2 training_inputs[idx, 1] = x # 训练输出直接用cos(x),不需要额外激活 training_outputs = numpy.array([math.cos(x) for x in R]).reshape(len(R), 1) # 随机初始化权重(替代全1,避免初始偏置) weights = numpy.random.randn(3, 1) def train_nn(training_inputs, training_outputs, initial_weights, niter, lr): w = initial_weights.copy() error_history = [] for _ in range(niter): # 前向传播:直接计算线性组合输出 outputs = numpy.dot(training_inputs, w) # 计算误差 errors = training_outputs - outputs # 带学习率更新权重 deltaw = numpy.dot(training_inputs.T, errors) * lr w += deltaw # 记录平均误差用于观察收敛情况 error_history.append(numpy.mean(numpy.abs(errors))) return outputs, w, error_history # 训练参数 NITER = 5000 LEARNING_RATE = 1e-3 outputs, weights, error_history = train_nn(training_inputs, training_outputs, weights, NITER, LEARNING_RATE) # 输出二次多项式系数 print("二次多项式系数:") print(f"w1(x²): {weights[0][0]:.6f}") print(f"w2(x): {weights[1][0]:.6f}") print(f"w3(常数项): {weights[2][0]:.6f}") # 验证拟合效果 x_test = numpy.linspace(0, math.pi/4, 100) y_pred = weights[0]*x_test**2 + weights[1]*x_test + weights[2] y_true = numpy.cos(x_test) plt.plot(x_test, y_true, label='真实cos(x)') plt.plot(x_test, y_pred, label='拟合二次多项式', linestyle='--') plt.legend() plt.show()
方案2:保留sigmoid的修正版本(不推荐,仅作演示)
如果你坚持要用sigmoid包裹输出,需要修正导数计算逻辑并添加学习率:
import numpy import random import math def sigmoid(x): return 1.0/(1.0 + numpy.exp(-x)) def sigmoid_prime(x): return sigmoid(x)*(1 - sigmoid(x)) # 生成数据 R = [random.uniform(0, math.pi/4) for _ in range(1000)] training_inputs = numpy.ones((len(R), 3)) for idx, x in enumerate(R): training_inputs[idx, 0] = x**2 training_inputs[idx, 1] = x training_outputs = numpy.array([sigmoid(math.cos(x)) for x in R]).reshape(len(R), 1) weights = numpy.random.randn(3, 1) def train_nn(training_inputs, training_outputs, initial_weights, niter, lr): w = initial_weights.copy() for _ in range(niter): # 前向传播:记录sigmoid的原始输入z z = numpy.dot(training_inputs, w) outputs = sigmoid(z) # 计算误差 errors = training_outputs - outputs # 修正梯度:用z计算sigmoid导数 deltaw = numpy.dot(training_inputs.T, errors * sigmoid_prime(z)) * lr w += deltaw return outputs, w NITER = 5000 LEARNING_RATE = 1e-2 outputs, weights = train_nn(training_inputs, training_outputs, weights, NITER, LEARNING_RATE) print(weights)
关键修正说明
- 移除sigmoid:二次多项式是权重与输入的线性组合,直接拟合
cos(x)的连续值更高效,完全匹配你的问题目标。 - 添加学习率:控制权重更新步长,避免模型震荡,
1e-3是经过测试的合适值。 - 随机初始化权重:替代全1初始化,避免模型陷入局部最优解。
- 修正导数计算:若保留sigmoid,必须用sigmoid的原始输入计算导数,而非输出值,否则梯度计算完全错误。
内容的提问来源于stack exchange,提问作者Leonardo Rioja
相关产品推荐
相关产品推荐

