python-3.x - 从零开始在 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
我不认为这是一个数据问题。我使用的是一个相当简单的线性函数。
我认为这是方程式,但我也仔细检查了它们,它们对我来说似乎很好。
解决方案
在您的实施中需要纠正几件事(其中大部分是出于效率原因)。当然,您可以通过简单地定义来节省时间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 的正态分布中采样。因此,您应该定义:W
np.random.normal
size
np.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)
m
K
是没用的,因为在这种情况下它总是等于。如果您执行采样而不确保您没有两次相同的索引,则应该保留它。如果您想执行采样而不检查您对同一索引进行了两次采样,只需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
一起打印,您可以看到程序很快找到正确的(一旦进行了先前的修复),但不会停止,可能是因为计算不正确。错误是这一行:J
iter
W
J
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)
通过更正这一点,您的代码可以正常工作。此错误也出现在程序的开头,但只要完成两次以上的迭代就不会影响它。
推荐阅读
- angular - How to remove marker by method, openlayers 4, angular 4
- javascript - 用纯js选择元素不包含指定子节点
- jmeter - 使用 JMeter 测试 OpenXava 应用程序
- scala - 是否有针对类型擦除的解决方法?
- mysql - MYSQL - 表格显示选项(不打印重复项)
- angular - Angular 6 返回 Observable
订阅 RxJs zip 后 - c - 使用 Frama-c 测试大文件中的中间变量
- swift - NSControl 的 addTarget() 方法在哪里?
- ios - 在运行时指定领域对象类型
- android - Android 程序类型已存在错误