我正在参加Coursera上的这门课程,学习机器学习/线性回归。他们描述用于求解估计OLS系数的梯度下降算法如下:
他们使用w
表示系数,H
表示设计矩阵(或他们称之为特征),y
表示因变量。他们的收敛标准是RSS梯度的范数小于容差epsilon;也就是说,他们定义的“未收敛”是:
我无法使这个算法收敛,想知道我的实现中是否遗漏了什么。以下是我的代码。请注意,我还将我使用的样本数据集(df
)通过了statsmodels回归库,只是为了确认回归可以收敛,并获取系数值以进行对比。确实可以,并且它们是:
Intercept 4.344435x1 4.387702x2 0.450958
这是我的实现。在每次迭代时,它会打印RSS梯度的范数:
import numpy as npimport numpy.linalg as LAimport pandas as pdfrom pandas import DataFrame# 首先定义grad函数:grad(RSS) = -2H'(y-Hw)def grad_rss(df, var_name_y, var_names_h, w): # 设置特征矩阵H H = DataFrame({"Intercept" : [1 for i in range(0,len(df))]}) for var_name_h in var_names_h: H[var_name_h] = df[var_name_h] # 设置y向量 y = df[var_name_y] # 计算RSS的梯度: -2H'(y - Hw) result = -2 * np.transpose(H.values) @ (y.values - H.values @ w) return resultdef ols_gradient_descent(df, var_name_y, var_names_h, epsilon = 0.0001, eta = 0.05): # 将所有初始w值设置为0.0001(与我们的epsilon选择无关) w = np.array([0.0001 for i in range(0, len(var_names_h) + 1)]) # 迭代计数器 t = 0 # 基本算法:持续从w中减去eta * grad(RSS),直到 # ||grad(RSS)|| < epsilon。 while True: t = t + 1 grad = grad_rss(df, var_name_y, var_names_h, w) norm_grad = LA.norm(grad) if norm_grad < epsilon: break else: print("{} : {}".format(t, norm_grad)) w = w - eta * grad if t > 10: raise Exception ("未能收敛") return w# ########################################## df = DataFrame({ "y" : [20,40,60,80,100] , "x1" : [1,5,7,9,11] , "x2" : [23,29,60,85,99] })# 运行ols_gradient_descent(df, "y", ["x1", "x2"])
遗憾的是,这没有收敛,实际上每次迭代都会打印一个爆炸的范数:
1 : 44114.315060513332 : 98203544.030678123 : 218612547944.953864 : 486657040646682.95 : 1.083355358314664e+186 : 2.411675439503567e+217 : 5.368670935963926e+248 : 1.1951287949674022e+289 : 2.660496151835357e+3110 : 5.922574875391406e+3411 : 1.3184342751414824e+38---------------------------------------------------------------------------Exception Traceback (most recent call last)......Exception: 未能收敛
如果我将最大迭代次数增加到足够多,它不会收敛,而是会爆炸到无穷大。
这里是否有实现错误,或者我是否误解了课程笔记中的解释?
更新及答案
正如@Kant建议的,每次迭代都需要更新eta
。课程本身提供了一些用于此的示例公式,但这些公式都没有帮助算法收敛。维基百科关于梯度下降的这一部分提到了Barzilai-Borwein方法作为更新eta
的好方法。我实现了它,并修改了我的代码以在每次迭代时使用它来更新eta
,回归成功收敛了。以下是我将维基百科版本的公式翻译成回归中使用的变量,以及实现它的代码。同样,这段代码在我的原始ols_gradient_descent
循环中被调用以更新eta
。
def eta_t (w_t, w_t_minus_1, grad_t, grad_t_minus_1): delta_w = w_t - w_t_minus_1 delta_grad = grad_t - grad_t_minus_1 eta_t = (delta_w.T @ delta_grad) / (LA.norm(delta_grad))**2 return eta_t
回答:
尝试降低eta的值。如果eta过高,梯度下降可能会发散。