提问人:Krellex 提问时间:11/15/2023 最后编辑:Krellex 更新时间:11/17/2023 访问量:171
梯度下降卡在局部最小值?
Gradient descent stuck in local minima?
问:
我正在运行梯度下降以找到非线性方程组的根,我想知道您如何检测该方法是否停留在局部最小值,因为我相信使用我使用的设置可能是这种情况?我的初始值是 [-2, -1],公差为 10^-2 和 20 次迭代。我读到的一件事是,如果残差开始趋于平缓或开始缓慢下降,则可能表明该方法被困在局部最小值中,但我不完全确定。我已经将我的残差及其迭代绘制为每次迭代的迭代值,我想知道我如何知道它是否停留在局部最小值。
def system(x):
F = np.zeros((2,1), dtype=np.float64)
F[0] = x[0]*x[0] + 2*x[1]*x[1] + math.sin(2*x[0])
F[1] = x[0]*x[0] + math.cos(x[0]+5*x[1]) - 1.2
return F
def jacb(x):
J = np.zeros((2,2), dtype=np.float64)
J[0,0] = 2*(x[0]+math.cos(2*x[0]))
J[0,1] = 4*x[1]
J[1,0] = 2*x[0]-math.sin(x[0]+5*x[1])
J[1,1] = -5*math.sin(x[0]+5*x[1])
return J
iterates, residuals = GradientDescent('system', 'jacb', np.array([[-2],[-1]]), 1e-2, 20, 0);
FullGradientDescent.py GradientDescentWithMomentum
我通常进行 20 次迭代测试,但我做了 200 次来说明残差的减慢
Marat 建议使用 GD with momentum。 代码更改:
dn = 0
gamma = 0.8
dn_prev = 0
while (norm(F,2) > tol and n <= max_iterations):
J = eval(jac)(x,2,fnon,F,*fnonargs)
residuals.append(norm(F,2))
dn = gamma * dn_prev+2*(np.matmul(np.transpose(J), F))
dn_prev = dn
lamb = 0.01
x = x - lamb * dn
答:
1赞
lastchance
11/16/2023
#1
你的两个方程可以写成曲线y(x):
y=+-sqrt( (-sin(2x)-x^2) / 2 )
y = (arccos(1.2-x^2)-x)/5
分别是下图中的蓝线和红线。请注意,两者都有两个分支。交点是两个根。根源可以通过多维牛顿-拉夫森找到:
import numpy as np
from math import sin, cos
#=======================================================================
def solver( eqns, x ):
MAXITER = 1000
TOL = 1.0e-10
iter, residuals = 1, 1.0
while iter <= MAXITER:
print( x )
F = Function( eqns, x )
residuals = np.linalg.norm( F )
if residuals <= TOL: break
J = Jacobian( eqns, x )
try:
deltax = np.linalg.solve( J, F )
except np.LinAlgError:
print( "Unable to solve" )
break
x = x - deltax
iter += 1
return x, residuals <= TOL
#=======================================================================
def Function( eqns, x ):
F = np.zeros_like( x )
for i, f in enumerate( eqns ): F[i] = f( x )
return F
#=======================================================================
def Jacobian( eqns, x ):
SMALL = 1.0e-6
N = len( x )
J = np.zeros( ( N, N ) )
for i in range( N ):
for j in range( N ):
xplus = x.copy(); xplus [j] = x[j] + 0.5 * SMALL
xminus = x.copy(); xminus[j] = x[j] - 0.5 * SMALL
J[i,j] = ( eqns[i]( xplus ) - eqns[i]( xminus ) ) / SMALL
return J
#=======================================================================
eqns = [ lambda x: x[0] ** 2 + 2 * x[1] ** 2 + sin( 2 * x[0] ),
lambda x: x[0] ** 2 + cos( x[0] + 5 * x[1] ) - 1.2 ]
x = np.array( [-0.8, 0.5 ] )
#x = np.array( [-0.8, -0.1 ] ) # alternative root
x, result = solver( eqns, x )
if result:
print( "Root: ", x )
else:
print( "No root found" )
#=======================================================================
对于第一个起点,您将获得:
[-0.8 0.5]
[-0.85082789 0.38764034]
[-0.8407059 0.37917738]
[-0.84056296 0.37906059]
[-0.84056293 0.37906057]
Root: [-0.84056293 0.37906057]
对于第二个起点,您将获得:
[-0.8 -0.1]
[-1.01262867 -0.06737605]
[-0.96586428 -0.0665185 ]
[-0.96353139 -0.06643953]
[-0.96352546 -0.06643926]
[-0.96352546 -0.06643926]
Root: [-0.96352546 -0.06643926]
评论
0赞
Krellex
11/16/2023
感谢您在这篇文章中的详细工作!虽然牛顿的方法可能会收敛,但我感兴趣的是如何获得梯度下降以得到解决方案,如果它不能,我试图理解为什么它不能。
0赞
lastchance
11/16/2023
@Krellex,我不太使用梯度下降,所以不容易评论。但是,如果绘制两个函数 F0 和 F1 的等值线图,您会发现它们在根部附近大约相交 90 度。因此,如果您尝试快速减少一个,那么另一个几乎保持不变。此外,F1 的变化速度要快得多,因此对下降方向的影响要大得多。最后,您是否需要小至 0.01 的 lambda?
0赞
lastchance
11/16/2023
我也无法运行您链接到的 Python 代码,因此很难测试。您能否检查是否上传了最新的运行代码。
0赞
Krellex
11/16/2023
对不起。尝试运行时遇到什么错误?
0赞
lastchance
11/16/2023
好吧,首先我需要显式导入数学。在那之后,我收到一个弃用警告(在这里可能很重要),你设置了 F[]: 实际的失败错误是Conversion of an array with ndim > 0 to a scalar is deprecated, and will error in future. Ensure you extract a single element from your array before performing this operation.
arr, residuals = GradientDescent('system', 'jacb', np.array([[-2],[-1]]), 1e-2, 200, 0); in GradientDescent J = eval(jac)(x,2,fnon,F,*fnonargs) TypeError: jacb() takes 1 positional argument but 4 were given
1赞
jlandercy
11/16/2023
#2
按如下方式重写系统:
import numpy as np
from scipy import optimize
import matplotlib.pyplot as plt
def system(x):
return np.array([
x[0]*x[0] + 2*x[1]*x[1] + np.sin(2*x[0]),
x[0]*x[0] + np.cos(x[0] + 5*x[1]) - 1.2
])
创建一些空间来绘制最小值周围的轮廓:
xlin = np.linspace(-2, 2, 200)
ylin = np.linspace(-2, 2, 200)
X, Y = np.meshgrid(xlin, ylin)
Z = system([X, Y])
围绕您确定的潜在解决方案求解系统:
solutions = np.array([
optimize.fsolve(system, x0=[0, 0]),
optimize.fsolve(system, x0=[-1, 0]),
optimize.fsolve(system, x0=[-1, 0.5])
])
# array([[ 0. , 0. ],
# [-0.96352546, -0.06643926],
# [-0.84056293, 0.37906057]])
确认返回的点实际上是解决方案:
system(solutions.T).T
# array([[ 0.00000000e+00, -2.00000000e-01], # Not converged
# [ 8.88178420e-16, 6.66133815e-16],
# [-2.22044605e-15, -4.88498131e-15]])
绘制整个过程以确认解决方案:
fig, axe = plt.subplots()
axe.contour(X, Y, np.log10(np.sqrt(Z[0]**2 + Z[1]**2)), 30, cmap="jet")
axe.plot(*solutions.T, linestyle="none", marker="o")
axe.grid()
第一点不是解决方案,求解器失败:(0,0)
optimize.fsolve(system, x0=[0, 0], full_output=True)
# (array([0., 0.]),
# {'nfev': 15,
# 'fjac': array([[-1.00000000e+00, -2.54340978e-08],
# [ 2.54340978e-08, -1.00000000e+00]]),
# 'r': array([-3.12495738e+12, 2.10079410e+06, -5.44853621e-02]),
# 'qtf': array([5.08681955e-09, 2.00000000e-01]),
# 'fvec': array([ 0. , -0.2])},
# 5,
# 'The iteration is not making good progress, as measured by the improvement from the last ten iterations.')
基于文档:
解决方案(或不成功的最后一次迭代的结果 调用)。
根据这些信息,您可以确认您的代码确实在域上找到了系统的两个根之一,额外的临界点不是解决方案。(0,0)
评论