首页 > 解决方案 > 从零开始在 Python 中实现随机梯度下降。实施是否正确?

问题描述

我知道这看起来与之前就同一主题提出的许多问题相似。我对他们中的大多数人进行了调查,但他们并没有完全回答我的问题。我的问题是我的梯度没有收敛到最优值,它甚至在非常低的 alpha 值下发散和振荡。

我的数据生成功能如下

X = [[float(np.random.randn(1)) for i in range(0,100)] for j in range(0,5)]
X = np.array(X).transpose()
Y = [float(0) for i in range(0,100)]
Y = 2*X[:,0] + 3*X[:,1] + 1*X[:,2] + 4*X[:,3] + 1*X[:,4] + 5
fig, ax = plt.subplots(1,5)
fig.set_size_inches(20,5)
k = 0
for j in range(0,5):
    sns.scatterplot(X[:,k],Y,ax=ax[j])
    k += 1

我的 SGD 实现如下

def multilinreg(X,Y,epsilon = 0.000001,alpha = 0.01,K = 20):
    Xnot = [[1] for i in range(0,len(X))]
    Xnot = np.array(Xnot)
    X = np.append(Xnot,X, axis = 1)
    vars = X.shape[1]
    W = []
    W = [np.random.normal(1) for i in range(vars)]
    W = np.array(W)
    J = 0
    for i in range(len(X)):
      Yunit = 0
      for j in range(vars):
        Yunit = Yunit + X[i,j] * W[j]
        J = J + (0.5/(len(X)))*((Y[i]-Yunit)**2)
    err = 1
    iter = 0
    Weights = []
    Weights.append(W)
    Costs = []
    while err > epsilon:
      index = [np.random.randint(len(Y)) for i in range(K)]
      Xsample, Ysample = X[index,:], Y[index]
      m =len(Xsample)
      Ypredsample = []
      for i in range(len(Xsample)):
        Yunit = 0
        for j in range(vars):
          Yunit = Yunit + X[i,j] * W[j]
        Ypredsample.append(Yunit)
      Ypredsample = np.array(Ypredsample)
      for i in range(len(Xsample)):
        for j in range(vars):
          gradJunit = (-1)*(Xsample[i,j]*(Ysample[i] - Ypredsample[i]))
          W[j] = W[j] - alpha*gradJunit
      Jnew = 0
      for i in range(len(Xsample)):
        Yunit = 0
        for j in range(vars):
          Yunit = Yunit + Xsample[i,j]*W[j]
          Jnew = Jnew + (0.5/(len(Xsample)))*((Ysample[i]-Yunit)**2)
      Weights.append(W)
      err = abs(float(Jnew - J))
      J = Jnew 
      Costs.append(J)
      iter += 1
      if iter % 1000 == 0:
        print(iter)
        print(J)
    Costs = np.array(Costs)
    Ypred = []
    for i in range(len(X)):
      Yunit = 0
      for j in range(vars):
        Yunit = Yunit + X[i,j] * W[j]
      Ypred.append(Yunit)
    Ypred = np.array(Ypred)
    return Ypred, iter, Costs, W

超参数如下

epsilon = 1*(10)**(-20)
alpha = 0.0000001
K = 50

我不认为这是一个数据问题。我使用的是一个相当简单的线性函数。

我认为这是方程式,但我也仔细检查了它们,它们对我来说似乎很好。

标签: python-3.xmachine-learningstochastic-gradient

解决方案


在您的实施中需要纠正几件事(其中大部分是出于效率原因)。当然,您可以通过简单地定义来节省时间w = np.array([5, 2, 3, 1, 4, 1]),但这并不能回答为什么您的 SGD 实现不起作用的问题。

首先,您X通过以下方式定义:

X = [[float(np.random.randn(1)) for i in range(0,100)] for j in range(0,5)]
X = np.array(X).transpose()

执行此操作的更快方法是简单地执行以下操作:

X = np.random.randn(100, 5)

然后,您定义Y

Y = [float(0) for i in range(0,100)]
Y = 2*X[:,0] + 3*X[:,1] + 1*X[:,2] + 4*X[:,3] + 1*X[:,4] + 5

第一次初始化是没有用的,因为你立即用第二行Y = [float(0) for i in range(0,100)]覆盖。Y编写此行的更简洁的方式也可能是:

Y = X @ np.array([2, 3, 1, 4, 1]) + 5

现在,关于您的 SGD 实施。这些行:

    Xnot = [[1] for i in range(0,len(X))]
    Xnot = np.array(Xnot)
    X = np.append(Xnot,X, axis = 1)

可以更有效地重写为:

    X = np.hstack((np.ones(len(X)).reshape(-1, 1), X))

同样,行

    W = []
    W = [np.random.normal(1) for i in range(vars)]
    W = np.array(W)

可以使用numpy函数重写。请注意,第一行是无用的,因为您在不使用它之后立即W = []覆盖。可以使用关键字参数直接生成多个样本。另外,请注意,当使用 时,您正在从均值 1 和标准 1 的正态分布中采样,而您可能希望从均值 0 和标准 1 的正态分布中采样。因此,您应该定义:Wnp.random.normalsizenp.random.normal(1)

    W = np.random.normal(size=vars)

Yunit是您使用的预测W。根据定义,您可以通过执行以下操作来计算它:

    Yunit = X @ W

这避免了嵌套for循环。你计算的方式J很奇怪。如果我没记错的话,J对应你的损失函数。J但是,假设 MSE 损失的公式是J = 0.5 * sum from k=1 to len(X) of (y_k - w*x_k) ** 2。因此,这两个嵌套for循环可以重写为:

    Yunit = X @ W
    J = 0.5 * np.sum((Y - Yunit) ** 2)

作为旁注:err这样命名可能会误导我,因为error通常是成本,而它表示这里每一步取得的进展。这些行:

    Weights = []
    Weights.append(W)

可以改写为:

   Weights = [W]

J添加到您的列表中也是合乎逻辑的Costs,因为这是对应于W

    Costs = [J]

由于您要执行随机梯度下降,因此无需随机选择要从数据集中获取的样本。你有两个选择:要么更新每个样本的权重,要么计算J权重的梯度。后者实现起来更简单一些,并且通常比前者更优雅地收敛。但是,由于您选择了前者,因此我将使用它。请注意,即使在此版本中,您也不必随机挑选样本,但我将使用与您相同的方法,因为这也应该有效。关于您的抽样,我认为最好确保您不要两次采用相同的索引。因此,您可能希望这样定义index

    index = np.random.choice(np.arange(len(Y)), size=K, replace=False)

mK是没用的,因为在这种情况下它总是等于。如果您执行采样而不确保您没有两次相同的索引,则应该保留它。如果您想执行采样而不检查您对同一索引进行了两次采样,只需replace=True输入choice函数即可。

再一次,您可以使用矩阵乘法来Yunit更有效地计算。因此,您可以替换:

      Ypredsample = []
      for i in range(len(Xsample)):
        Yunit = 0
        for j in range(vars):
          Yunit = Yunit + X[i,j] * W[j]
        Ypredsample.append(Yunit)

经过:

    Ypredsample = X @ W

同样,您可以使用numpy函数计算权重更新。因此,您可以替换:

      for i in range(len(Xsample)):
        for j in range(vars):
          gradJunit = (-1)*(Xsample[i,j]*(Ysample[i] - Ypredsample[i]))
          W[j] = W[j] - alpha*gradJunit

经过:

    W -= alpha * np.sum((Ypredsample - Ysample).reshape(-1, 1) * Xsample, axis=0)

像以前一样,可以使用矩阵乘法来计算成本。但是请注意,您应该计算J整个数据集。因此,您应该替换:

      Jnew = 0
      for i in range(len(Xsample)):
        Yunit = 0
        for j in range(vars):
          Yunit = Yunit + Xsample[i,j]*W[j]
          Jnew = Jnew + (0.5/(len(Xsample)))*((Ysample[i]-Yunit)**2)

经过:

   Jnew = 0.5 * np.sum((Y - X @ W) ** 2)

最后,您可以使用矩阵乘法进行预测。因此,您的最终代码应如下所示:

import numpy as np

X = np.random.randn(100, 5)
Y = X @ np.array([2, 3, 1, 4, 1]) + 5

def multilinreg(X, Y, epsilon=0.00001, alpha=0.01, K=20):
    X = np.hstack((np.ones(len(X)).reshape(-1, 1), X))
    vars = X.shape[1]
    W = np.random.normal(size=vars)
    Yunit = X @ W
    J = 0.5 * np.sum((Y - Yunit) ** 2)
    err = 1
    Weights = [W]
    Costs = [J]
    iter = 0

    while err > epsilon:
        index = np.random.choice(np.arange(len(Y)), size=K, replace=False)
        Xsample, Ysample = X[index], Y[index]
        Ypredsample = Xsample @ W
        W -= alpha * np.sum((Ypredsample - Ysample).reshape(-1,1) * Xsample, axis=0)
        Jnew = 0.5 * np.sum((Y - X @ W) ** 2)
        Weights.append(Jnew)
        err = abs(Jnew - J)
        J = Jnew
        Costs.append(J)
        iter += 1

        if iter % 10 == 0:
            print(iter)
            print(J)

    Costs = np.array(Costs)
    Ypred = X @ W
    return Ypred, iter, Costs, W

运行它W=array([4.99956786, 2.00023614, 3.00000213, 1.00034205, 3.99963732, 1.00063196])以 61 次迭代返回,最终成本为 3.05e-05。

现在我们知道这段代码是正确的,我们可以用它来确定你的错误在哪里。在这段代码中:

      for i in range(len(Xsample)):
        Yunit = 0
        for j in range(vars):
          Yunit = Yunit + X[i,j] * W[j]
        Ypredsample.append(Yunit)
      Ypredsample = np.array(Ypredsample)

您使用X[i, j]而不是Xsample[i, j],这没有任何意义。另外,如果您在循环中和W一起打印,您可以看到程序很快找到正确的(一旦进行了先前的修复),但不会停止,可能是因为计算不正确。错误是这一行:JiterWJ

Jnew = Jnew + (0.5/(len(Xsample)))*((Ysample[i]-Yunit)**2)

没有正确缩进。实际上,它不应该是for j in range(vars)循环的一部分,而应该for i in range(len(Xsample))只是循环的一部分,如下所示:

      Jnew = 0
      for i in range(len(Xsample)):
        Yunit = 0
        for j in range(vars):
          Yunit = Yunit + Xsample[i,j]*W[j]
        Jnew = Jnew + (0.5/(len(Xsample)))*((Ysample[i]-Yunit)**2)

通过更正这一点,您的代码可以正常工作。此错误也出现在程序的开头,但只要完成两次以上的迭代就不会影响它。


推荐阅读