为了更好地理解数据科学中的线性回归主题,我一直在尝试重现scikitlearn的LinearRegression模块的内部工作原理。我遇到的问题是,当我使用我的数据开始对斜率和截距进行梯度下降时,无论我使用什么步长或下降迭代次数,斜率和截距的值都无法收敛。我试图寻找线性关系的数据是NBA的投篮命中率和胜率百分比,可以在这里找到(数据只有大约250行,但我认为在pastebin上分享会更容易…)。您可以通过以下方式重新创建数据的初始图表:
import pandas as pdimport matplotlib.pyplot as pltfrom sklearn.linear_model import LinearRegressiondef graph1(axis = []): x = FG_pct y = W_L_pct plt.scatter(x, y) plt.title('NBA FG% vs. Win%') plt.xlabel('FG pct (%)') plt.ylabel('Win pct (%)') if len(axis) > 1: plt.axis(axis) plt.legend()
它看起来会像这样(没有颜色):
这两个变量之间存在非常明显的关系,您基本上可以很好地猜测最佳拟合线会是什么(我的猜测是斜率为5,截距约为-1.75)。
我使用的梯度下降方程,通过对损失函数关于斜率和截距的导数得出,是这样的:
def get_b_gradient(x_pts, y_pts, m, b): N = len(x_pts) tot = 0 for x, y in zip(x_pts, y_pts): tot += y - (m*x + b) gradient = (-2/N)*tot return gradientdef get_m_gradient(x_pts, y_pts, m, b): N = len(x_pts) tot = 0 for x, y in zip(x_pts, y_pts): tot += x * (y - (m*x + b)) gradient = (-2/N)*tot return gradientdef get_step(x_pts, y_pts, m, b, learning_rate): init_b = get_b_gradient(x_pts, y_pts, m, b) init_m = get_m_gradient(x_pts, y_pts, m, b) final_b = b - (init_b*learning_rate) final_m = m - (init_m*learning_rate) return final_m, final_bdef gradient_descent(x_pts, y_pts, m, b, learning_rate, num_iterations): for i in range(num_iterations): m, b = get_step(x_pts, y_pts, m, b, learning_rate) return m, b
获得这些后,关键在于找到正确的迭代次数和学习率,使斜率和截距收敛到最优值。由于我不确定如何系统地找到这些值,我只是尝试在gradient_descent函数中输入不同的数量级:
# 1000次迭代,学习率为0.1,初始斜率和截距猜测为0m, b = gradient_descent(df['FG%'], df['W/L%'], 0, 0, 0.1, 1000)
您可以使用这样的图表来跟踪斜率和截距的收敛情况:
def convergence_graph(iterations, learning_rate, m, b): plt.subplot(1, 2, 1) for i in range(iterations): plt.scatter(i,b, color='orange') plt.title('convergence of b') m, b = get_step(df['FG%'], df['W/L%'], m, b, learning_rate) plt.subplot(1, 2, 2) for i in range(iterations): plt.scatter(i,m, color='blue') plt.title('convergence of m') m, b = get_step(df['FG%'], df['W/L%'], m, b, learning_rate)
这正是问题明显的地方。使用与之前相同的迭代次数(1000)和学习率(0.1),您会看到这样的图表:
我认为这些图表的线性意味着它在那个点上仍在收敛,所以答案是增加学习率,但无论我为学习率选择什么数量级(一直到数百万),图表仍然保持线性,永远不会收敛。我也尝试使用较小的学习率并调整迭代次数…没有任何效果。最终我决定将其放入sklearn,看看它是否会遇到任何问题:
FG_pct = np.array(FG_pct)FG_pct = FG_pct.reshape(-1, 1)line_fitter = LinearRegression().fit(FG_pct, W_L_pct)win_loss_predict = line_fitter.predict(FG_pct)
它没有任何问题:
这篇文章已经很长了,很抱歉。我没有直接可以询问的数据科学人员,也没有教授在周围,所以我想我会把它放在这儿。最终,我不确定问题是出在1)我的梯度下降方程上,还是2)我在寻找适当的学习率和迭代次数的方法上。如果有人能指出发生了什么,为什么斜率和截距没有收敛,以及我做错了什么,那将非常感激!
回答:
我建议您从数据科学材料呈现这些主题的方式上退后一步。线性回归,梯度下降。这些不是数据科学的主题。这些是统计学概念。我建议您开始查找入门统计学材料。您拿到的任何材料几乎都会有一章关于普通线性回归(OLS)。
梯度下降是牛顿法寻找零点的一个更复杂的版本。我强烈建议您研究那个算法。如果您对微积分有很好的理解,这听起来您可能已经有了,那么它非常容易理解。如果您确实研究了它,请注意其中没有“学习率”。这个词让我感到恶心。在“数据科学”之前的日子,也就是大约10年前,它被称为步长。
步长对收敛速度至关重要。但是如果步长太大,您很可能永远不会收敛。假设您的步长是10,而您的导数(单变量情况)是0.1。您的猜测移动了1。但如果最小值距离当前猜测只有0.25单位呢?恭喜。您的解决方案刚刚变得更糟。您可以整天在最小值周围跳来跳去,永远找不到它(我怀疑这可能是您的代码中发生的情况)。许多算法使用的是递减的步长。通常与迭代次数成比例。例如,在第j次迭代中,您的步长可能是10/j。这也有一些问题,可以通过稳定值和对步长形状的额外边界来解决,随着迭代的演进。
您正在尝试做的事情实际上非常棒。有太多人在“做数据科学”,但他们对实际发生的事情一无所知。缺点是这条路并不容易走。我鼓励您继续前进!!这是值得的。但您需要认识到,您有点跳进了深水区。有更简单的算法,您可以从中获得更多,并为以后的更高级内容奠定基础。
编辑:更直接的回答
所以,您代码中唯一需要更改的是梯度。在两个梯度计算中,将
gradient = (-2/N)*tot
改为
gradient = (-2)*tot
梯度在分母中没有N
。一些推导可能会以这种方式显示,但那可能是因为它们是在推导闭合形式的解,并且已经将整个东西设为零。
看起来您的参数之所以会变得疯狂,是因为您的步长太大。使用那个更改后,它返回的参数是:
m, b = gradient_descent(FG_pct, W_L_pct, 6, -1, 0.003, 10000)m = 6.465b = -2.44
我认为在您的例子中,您使用初始猜测0, 0
来启动算法。一个好的初始猜测可以产生巨大的差异。
闭合形式的替代方案这里是一个使用闭合形式的例子。它在没有搜索的情况下产生精确的答案。
from matplotlib.pyplot import plot, scatterimport numpy as npY = np.array(W_L_pct)X = np.array([np.ones(len(FG_pct)), FG_pct]).reshape(2, 270).TA = np.linalg.inv(np.matmul(X.T, X))B = np.matmul(X.T, Y)beta = np.matmul(A, B)m, b = beta[1], beta[0]print(m, b)r = np.arange(0.4, 0.52, 0.01)scatter(FG_pct, Y)plot(r, m * r + b)