基于CS229实现Batch Gradient Descent结果异常求助
批量梯度下降实现问题排查与入门建议
核心代码bug
- 缺失梯度的1/m缩放项:CS229课程给出的批量梯度下降公式中,梯度项需要对所有样本的误差累加值取平均(乘以1/m,m为样本总数)。你的实现直接用未缩放的累加梯度做参数更新,等效于将设定的学习率放大了m倍。汽车MPG数据集样本量在300+,你设置的0.001学习率实际等效值超过0.3,直接触发梯度爆炸,参数指数增长溢出为NaN,这也是你必须把学习率压到1e-7才能避免溢出的根本原因。
- 参数未同步更新:标准批量梯度下降要求单轮迭代中,先计算完所有参数对应的梯度,再统一更新全部参数。你的实现是算完一个参数的梯度就立刻更新该参数,后续计算其他参数梯度时,调用的假设函数已经用了更新后的新参数,不符合批量梯度下降的逻辑,会导致收敛路径偏移,参数更新方向错乱。
- 未做特征归一化:你使用的汽车数据集中,车重字段数值范围在18005000区间,马力在50250区间,截距项对应特征恒为1,不同特征尺度差了3~4个数量级。此时损失函数等高线为极度狭长的椭圆,哪怕梯度计算逻辑正确,收敛速度也会极慢,且对学习率极其敏感。课程中提到的0.01/0.1可用学习率,默认前提是特征经过标准化缩放到相近尺度。
修正后可运行代码
def hypothesis(X: list, parameters: list) -> float: result = 0 for i in range(len(X)): result += X[i] * parameters[i] return result def batch_gradient_descent(dataset: list, learning_rate=0.01, steps=1000) -> list[float]: m = len(dataset[0]) n = len(dataset) - 1 parameters = [0.0] * (n + 1) for k in range(steps): gradients = [0.0] * (n + 1) # 先计算本轮所有参数的梯度,不提前更新参数 for j in range(n + 1): grad_sum = 0 for datapoint in range(m): x_i = [dataset[dim][datapoint] for dim in range(n+1)] h = hypothesis(x_i, parameters) y = dataset[-1][datapoint] grad_sum += (h - y) * dataset[j][datapoint] # 补全1/m缩放项 gradients[j] = (1 / m) * grad_sum # 所有梯度计算完成后,统一更新参数 for j in range(n + 1): parameters[j] -= learning_rate * gradients[j] return parameters # 特征标准化工具,训练前对除截距项外的所有特征调用 def normalize_feature(feature: list) -> tuple[list, float, float]: mean = sum(feature) / len(feature) std = (sum((x - mean) ** 2 for x in feature) / len(feature)) ** 0.5 normalized = [(x - mean) / std for x in feature] return normalized, mean, std
使用前先对马力、车重特征调用normalize_feature做标准化,学习率设为0.01~0.1即可正常收敛。
机器学习入门实践建议
- 实现算法前逐行对应公式项,不要凭记忆写代码。写完后先用35个手动构造的小样本,手算12轮迭代的梯度和参数值,和代码输出对比确认逻辑正确,再上真实数据集。
- 所有基于梯度下降的算法,训练前必须做特征缩放(标准化/归一化),除非所有特征天然处于同一数值区间。
- 调试训练过程时,每轮迭代打印均方误差损失值:正常收敛时损失应当稳定单调下降,如果损失上升、震荡,优先排查梯度计算错误、学习率过大问题,不要盲目压低学习率、增加迭代次数。
- 入门阶段优先用人工生成的、规律明确的小数据集验证代码正确性,确认输出符合预期后再替换为真实数据集。
内容的提问来源于stack exchange,提问作者Anirudh Kanaparthy
相关产品推荐
相关产品推荐

