是什么决定了我的 Python 梯度下降算法是否收敛?

What determines whether my Python gradient descent algorithm converges?

我在 Python 中实现了一个单变量线性回归模型,它使用梯度下降来找到最佳拟合线的截距和斜率(我使用梯度下降而不是计算最优直接截距和斜率的值,因为我最终想推广到多元回归)。

我使用的数据如下。 sales 是因变量(美元),temp 是自变量(摄氏度)(想想冰淇淋销量与温度,或类似的东西)。

sales   temp
215     14.20
325     16.40
185     11.90
332     15.20
406     18.50
522     22.10
412     19.40
614     25.10
544     23.40
421     18.10
445     22.60
408     17.20

这是我标准化后的数据:

sales        temp 
0.06993007  0.174242424
0.326340326 0.340909091
0           0
0.342657343 0.25
0.515151515 0.5
0.785547786 0.772727273
0.529137529 0.568181818
1           1
0.836829837 0.871212121
0.55011655  0.46969697
0.606060606 0.810606061
0.51981352  0.401515152

我的算法代码:

import numpy as np
import pandas as pd
from scipy import stats

class SLRegression(object):
    def __init__(self, learnrate = .01, tolerance = .000000001, max_iter = 10000):

        # Initialize learnrate, tolerance, and max_iter.
        self.learnrate = learnrate
        self.tolerance = tolerance
        self.max_iter = max_iter

    # Define the gradient descent algorithm.
    def fit(self, data):
        # data   :   array-like, shape = [m_observations, 2_columns] 

        # Initialize local variables.
        converged = False
        m = data.shape[0]

        # Track number of iterations.
        self.iter_ = 0

        # Initialize theta0 and theta1.
        self.theta0_ = 0
        self.theta1_ = 0

        # Compute the cost function.
        J = (1.0/(2.0*m)) * sum([(self.theta0_ + self.theta1_*data[i][1] - data[i][0])**2 for i in range(m)])
        print('J is: ', J)

        # Iterate over each point in data and update theta0 and theta1 on each pass.
        while not converged:
            diftemp0 = (1.0/m) * sum([(self.theta0_ + self.theta1_*data[i][1] - data[i][0]) for i in range(m)])
            diftemp1 = (1.0/m) * sum([(self.theta0_ + self.theta1_*data[i][1] - data[i][0]) * data[i][1] for i in range(m)])

            # Subtract the learnrate * partial derivative from theta0 and theta1.
            temp0 = self.theta0_ - (self.learnrate * diftemp0)
            temp1 = self.theta1_ - (self.learnrate * diftemp1)

            # Update theta0 and theta1.
            self.theta0_ = temp0
            self.theta1_ = temp1

            # Compute the updated cost function, given new theta0 and theta1.
            new_J = (1.0/(2.0*m)) * sum([(self.theta0_ + self.theta1_*data[i][1] - data[i][0])**2 for i in range(m)])
            print('New J is: %s') % (new_J)

            # Test for convergence.
            if abs(J - new_J) <= self.tolerance:
                converged = True
                print('Model converged after %s iterations!') % (self.iter_)

            # Set old cost equal to new cost and update iter.
            J = new_J
            self.iter_ += 1

            # Test whether we have hit max_iter.
            if self.iter_ == self.max_iter:
                converged = True
                print('Maximum iterations have been reached!')

        return self

    def point_forecast(self, x):
        # Given feature value x, returns the regression's predicted value for y.
        return self.theta0_ + self.theta1_ * x


# Run the algorithm on a data set.
if __name__ == '__main__':
    # Load in the .csv file.
    data = np.squeeze(np.array(pd.read_csv('sales_normalized.csv')))

    # Create a regression model with the default learning rate, tolerance, and maximum number of iterations.
    slregression = SLRegression()

    # Call the fit function and pass in the data.
    slregression.fit(data)

    # Print out the results.
    print('After %s iterations, the model converged on Theta0 = %s and Theta1 = %s.') % (slregression.iter_, slregression.theta0_, slregression.theta1_)
    # Compare our model to scipy linregress model.
    slope, intercept, r_value, p_value, slope_std_error = stats.linregress(data[:,1], data[:,0])
    print('Scipy linear regression gives intercept: %s and slope = %s.') % (intercept, slope)

    # Test the model with a point forecast.
    print('As an example, our algorithm gives y = %s given x = .87.') % (slregression.point_forecast(.87)) # Should be about .83.
    print('The true y-value for x = .87 is about .8368.')

我无法准确理解是什么让算法收敛,而 return 值是完全错误的。给定 learnrate = .01tolerance = .0000000001max_iter = 10000,结合归一化数据,我可以使梯度下降算法收敛。但是,当我使用未归一化的数据时,没有算法 returning NaN 的最小学习率是 .005。这将成本函数从迭代到迭代的变化降低到 614 左右,但我无法让它降低。

这种类型的算法是否确实需要规范化数据,如果是,为什么?此外,考虑到算法需要标准化值,采用非标准化形式的小说 x-value 并将其插入点预测的最佳方法是什么?例如,如果我要将这个算法提供给客户,这样他们就可以做出自己的预测(我不是,但为了争论..),我难道不希望他们能够简单地插入在非标准化 x-value?

总而言之,玩弄 tolerancemax_iterlearnrate 大多数时候都会给我不收敛的结果。这是正常现象,还是我的算法中存在导致此问题的缺陷?

Given learnrate = .01, tolerance = .0000000001, and max_iter = 10000, in combination with normalized data, I can get the gradient descent algorithm to converge. However, when I use the un-normalized data, the smallest I can make the learning rate without the algorithm returning NaN is .005

您设置算法的方式是可以预料到的。

数据的归一化使得最佳拟合的 y 截距为 大约 0.0。否则,您的 y 轴截距可能比初始猜测大出数千个单位,并且在您真正开始优化部分之前必须跋涉到那里。

Is it definitely a requirement of this type of algorithm to have normalized data, and if so, why?

不,绝对不是,但如果你不归一化,你应该更聪明地选择一个起点(你从 (m,b) = (0,0) 开始)。如果您不对数据进行归一化,您的学习率也可能太小,这与您的容忍度相同。

Also, what would be the best way to take a novel x-value in non-normalized form and plug it into the point forecast, given that the algorithm needs normalized values?

应用您对原始数据应用的任何转换,将规范化数据应用于您的新 x 值。 (规范化代码不在您所展示的范围内)。如果这个测试点落在原始数据的 (minx,maxx) 范围内,一旦转换,它应该落在 0 <= x <= 1 内。一旦你有了这个标准化测试点,将它插入你的 theta 方程(请记住,您的 theta 是直线方程的 y 截距形式的 m,b)。

All and all, playing around with the tolerance, max_iter, and learnrate gives me non-convergent results the majority of the time.

对于一个格式良好的问题,如果你实际上发散,这通常意味着你的步长太大。尝试降低它。

如果它在达到最大迭代次数之前根本没有收敛,那可能是一些问题:

  • 你的步长太小了,
  • 你的容忍度太小了,
  • 您的最大迭代次数太小,
  • 你的出发点选错了

在您的情况下,使用非标准化数据会导致 (0,0) 的起点非常遥远(非标准化数据的 (m,b) 在 (-159, 30) 附近而标准化数据的 (m,b) 是 (0.10,0.79)),所以大多数(如果不是全部)迭代都用于到达感兴趣的区域。

这样做的问题是,通过增加步长以更快地到达感兴趣的区域也使得它更少-到达那里后找到收敛的可能性。

为了解决这个问题,一些梯度下降算法具有动态步长(或学习率),以便在开始时采用较大的步长,并在接近收敛时采用较小的步长。


在整个算法中保留 theta 对的历史记录,然后绘制它们可能对您有所帮助。您将能够立即看到使用规范化和非规范化输入数据之间的区别。