线性回归梯度下降均收敛至(0,0)附近的问题排查
梯度下降算法拟合失败问题排查与修正
问题现象
自行实现线性回归梯度下降后,所有运行结果均收敛至(0,0)附近,最优成本对应的模型与数据不符,高成本模型反而更接近预期。已尝试调整学习率、随机化初始向量,数据范围为考试分数1-100、学习时长1-10,不确定是否需要归一化。
核心问题分析
- 特征与响应变量完全颠倒:测试代码中将考试分数(1-100)作为特征,学习时长(1-10)作为响应变量,既违背“用学习时长预测考试分数”的逻辑,又因特征值范围远大于响应值,导致参数更新时梯度比例严重失衡——斜率参数的梯度被大特征值稀释,更新幅度极小,最终收敛到接近0的无效值。
- 参数定义与注释混淆:类初始化注释标注
vector is gradient and y-intercept,但实际_cost函数中预测式为y = vector[0] + vector[1] * x,即vector[0]是截距、vector[1]是斜率,注释与实际逻辑完全相反,导致初始向量设置时参数含义混淆,影响收敛起点合理性。 - 未做特征归一化:特征值与响应值数量级差异大,梯度下降对不同参数的敏感程度不一致:截距参数的梯度量级远大于斜率参数,学习率无法同时适配两者的更新需求,要么截距更新过度,要么斜率更新不足。
- 学习率与迭代次数不足:当前使用的
learning_rate=0.0001、iterations=50对于未归一化的数据来说,学习率过小、迭代次数太少,参数还未收敛到最优值就停止更新。
修正方案与代码调整
1. 核心修正方向
- 调换特征与响应变量顺序,用学习时长预测考试分数;
- 明确参数定义,修正注释;
- 添加特征归一化,平衡参数更新敏感度;
- 调整学习率与迭代次数,确保充分收敛。
修正后的实现代码
class GradientDescent: def __init__(self, feature_data: list[float], response_data: list[float], initial_vector: list[float, float], learning_rate: float=0.01, iterations: int=1000, normalize: bool=True) -> None: ''' 初始化梯度下降类 参数说明: - feature_data: 特征数据(如学习时长) - response_data: 响应数据(如考试分数) - initial_vector: 初始参数向量,格式为[截距(y-intercept), 斜率(gradient)] ''' self.feature_data = feature_data self.response_data = response_data self.normalize = normalize # 特征归一化处理(Z-score标准化) if self.normalize: self.mean_x = sum(self.feature_data) / len(self.feature_data) self.std_x = (sum((x - self.mean_x)**2 for x in self.feature_data) / len(self.feature_data))**0.5 self.feature_data = [(x - self.mean_x) / self.std_x for x in self.feature_data] self.initial_vector = initial_vector self.learning_rate = learning_rate self.iterations = iterations def _partial_difference_quotient(self, vector: list[float, float], index: int, small_change: float=0.0001): modified_vector = vector.copy() modified_vector[index] += small_change return (self._cost(modified_vector) - self._cost(vector)) / small_change def _estimate_gradient(self, vector): gradient_estimates = [] for i in range(len(vector)): gradient_estimates.append(self._partial_difference_quotient(vector, i)) return gradient_estimates def _update_vector(self, vector: list[float, float]) -> list[float, float]: direction = self._estimate_gradient(vector) for i in range(len(vector)): vector[i] -= self.learning_rate * direction[i] return vector def _cost(self, vector: list[float, float]) -> float: '''计算均方误差损失''' cost = 0 for i, y_true in enumerate(self.response_data): y_pred = vector[0] + vector[1] * self.feature_data[i] error = y_true - y_pred cost += error ** 2 return cost / (2 * len(self.response_data)) def compute_gradient_descent(self) -> tuple[list[float], list[float]]: next_vector = self.initial_vector.copy() cost_values = [self._cost(next_vector)] for _ in range(self.iterations): next_vector = self._update_vector(next_vector) cost_values.append(self._cost(next_vector)) # 还原归一化后的参数到原始特征尺度 if self.normalize: next_vector[1] = next_vector[1] / self.std_x next_vector[0] = next_vector[0] - next_vector[1] * self.mean_x return next_vector, cost_values
修正后的测试代码
from random import randint def main(runs: int=5, **kwargs) -> None: learning_rate = kwargs.get("learning_rate", 0.01) iterations = kwargs.get("iterations", 1000) lowest_cost_value = float('inf') lowest_cost_vector = None for i in range(runs): # 初始向量:截距设为考试分数均值附近,斜率设为合理范围 starting_vector = [randint(5000, 8000)/100, randint(0, 200)/100] gradient_descent = GradientDescent( feature_data=data["Study Hours"], # 特征改为学习时长 response_data=data["Exam Score"], # 响应改为考试分数 initial_vector=starting_vector.copy(), learning_rate=learning_rate, iterations=iterations ) latest_vector, cost_values = gradient_descent.compute_gradient_descent() current_min_cost = min(cost_values) if current_min_cost < lowest_cost_value: lowest_cost_value = current_min_cost lowest_cost_vector = latest_vector print(f"最低损失: {lowest_cost_value:.4f}, 最优参数(截距,斜率): {[round(v,4) for v in latest_vector]}")
验证效果
修正后,梯度下降会收敛到符合预期的参数:比如截距在50左右(不学习时的基础分数),斜率在5-10之间(每多学1小时,分数提升5-10分),损失值会显著降低,拟合线与数据分布一致。
内容的提问来源于stack exchange,提问作者BFI01
相关产品推荐
相关产品推荐

