Python实现随机梯度下降(SGD)时权重更新异常问题排查
嘿,你猜得太对了!问题核心就是权重更新的方式完全错了——你现在是把整个损失值直接统一加到所有权重上,这根本不是随机梯度下降(SGD)的正确逻辑。SGD要求我们针对每个权重单独计算它的梯度,再逐个更新。咱们一步步把代码改对:
修正后的SGD多项式回归实现
先看完整的修正代码,后面我会拆解关键修改点:
from matplotlib import pyplot as plt import math import numpy as np def epsilon(): '''Adds noise to the data points''' return np.random.normal(0, 0.3, 100) def yFunction(x): '''Function to predict''' return np.sin(2 * math.pi * x) + epsilon() def predict(x, thetas): '''Predict value of x with the given thetas''' # 用矩阵运算替代循环,更高效且易读 x_powers = np.array([x**i for i in range(thetas.size)]) return np.dot(x_powers, thetas) # 学习率(可根据收敛情况调整) alpha = 0.1 # 多项式阶数:这里是2阶,对应3个权重(x^0, x^1, x^2) poly_degree = 2 # 生成随机数据点 X = np.random.random_sample(100) y = yFunction(X) # 初始化权重:对应各次项的系数 thetas = np.random.normal(0, 0.5, poly_degree + 1) # 记录损失变化 lossHistory = [] for epoch in range(1000): # 取当前样本(用epoch%100实现循环遍历,SGD也可以随机选样本) idx = epoch % 100 x_sample = X[idx] y_true = y[idx] # 计算预测值 y_pred = predict(x_sample, thetas) # 计算单样本误差 error = y_pred - y_true # 记录单样本损失 loss = error ** 2 lossHistory.append(loss) # 核心:逐个更新每个权重 for i in range(thetas.size): # 每个权重对应x的i次幂特征 feature = x_sample ** i # 梯度下降更新:沿着负梯度方向调整权重 thetas[i] -= alpha * error * feature # 用排序后的X绘图,避免折线混乱 X_sorted = np.sort(X) Y_pred = predict(X_sorted, thetas) # 绘制原始数据与拟合曲线 fig = plt.figure() plt.scatter(X, y, label='Actual Data') plt.plot(X_sorted, Y_pred, "r-", label='Prediction') plt.legend() plt.suptitle("Prediction Line Over Actual Values") # 绘制损失曲线 fig = plt.figure() plt.plot(np.arange(0, len(lossHistory)), lossHistory) plt.suptitle("Training Loss") plt.show()
关键修改点说明
权重更新逻辑(最核心的错误)
原代码里的thetas += alpha * loss/thetas -= alpha * loss完全不符合梯度下降的原理。正确的单样本SGD更新规则是:
对于每个权重theta_i,它的梯度是误差 × 对应特征值(来自损失函数对theta_i的偏导数),所以更新公式为:thetas[i] -= alpha * error * (x_sample ** i)每个权重都要单独计算、单独更新,不能所有权重用同一个值调整。
绘图优化
原代码用随机无序的X值绘图,导致拟合线是混乱的折线。改成排序后的X_sorted绘图,能清晰展示拟合曲线的趋势。代码可读性优化
把predict函数里的循环改成矩阵点积运算,既高效又更符合Python/NumPy的写法习惯。
额外优化建议
- 调整学习率:如果损失曲线波动太大,可以尝试减小学习率(比如
alpha=0.01),或者添加学习率衰减(比如每100轮把学习率乘以0.9)。 - Mini-batch SGD:单样本SGD收敛波动较大,可以改成每次用5-10个样本计算平均梯度,收敛会更稳定。
- 特征归一化:多项式特征中,
x^2的取值范围可能远大于x,归一化特征能让权重更新更均衡,加快收敛速度。
内容的提问来源于stack exchange,提问作者hildebro
相关产品推荐
相关产品推荐

