梯度下降的自我实现与SciPy最小化相比

Self-Implementation of Gradient Descent Compared to SciPy Minimize

这是我正在进行的凸优化 class 的作业。赋值如下:

Implement the gradient descent algorithm with backtracking line search to find the optimal step size. Your implementation will be compared to Python's scipy.optimize.minimize function.

The specific function to minimize is the least squares function. The error between the solution found by the Python library and your implementation must be smaller than 0.001.

我做了一个实现,但是错误值徘徊在1左右,一直在寻找改进的方法,但遇到了一些麻烦。这是我写的代码:

梯度下降+回溯线搜索实现

import numpy as np

# Gradient descent.
def min_gd(fun, x0, grad, args=()):
    alpha = 0.3
    beta = 0.8

    delta_x = -grad(x0, *args)
    t = backtracking_line_search(fun, x0, grad, delta_x, alpha, beta, args)
    x_new = x0 + (t * delta_x)

    if np.linalg.norm(x_new) ** 2 > np.linalg.norm(x0) ** 2:
        return min_gd(fun, x_new, grad, args)
    else:
        return x_new
    
# Line search function returns optimal step size.
def backtracking_line_search(fun, x, grad, delta_x, alpha, beta, args=()):
    t = 1
    derprod = grad(x, *args) @ delta_x

    while fun((x + (t * delta_x)), *args) > fun(x, *args) + (alpha * t * derprod):
        t *= beta

    return t

其他给定的功能

import numpy as np
from scipy.optimize import minimize
import gd

# Least Squares function
def LeastSquares(x, A, b):
    return np.linalg.norm(A @ x - b) ** 2

# gradient  
def grad_LeastSquares(x, A, b):
    return 2 * ((A.T @ A) @ x - A.T @ b)

两个结果的误差基本上是用L2范数计算出来的

我提出的一些想法是我的梯度下降函数中的公差检查点可能存在缺陷。现在我基本上只是检查下一步是否比上一步大。但是,我也无法思考如何改进它。

欢迎任何反馈。

编辑

万一有人对我为使其以所需方式工作而编写的最终代码感到好奇:

def min_gd(fun, x0, grad, args=()):
    alpha = 0.3
    beta = 0.8

    delta_x = -grad(x0, *args)
    t = backtracking_line_search(fun, x0, grad, delta_x, alpha, beta, args)
    x_new = x0 + (t * delta_x)
    
    if np.linalg.norm(grad(x_new, *args)) < 0.01:
        return x_new
    else:
        return min_gd(fun, x_new, grad, args)

我只是修复了条件语句,这样我不仅可以比较规范,还可以检查该值是否小于预定的容差水平。

希望这对以后的任何人都有帮助。

你对公差检查的猜测是正确的:当前向量的范数与收敛无关。一个典型的标准是小梯度,所以 min_gd 应该看起来像

def min_gd(fun, x0, grad, args=()):
    alpha = 0.3
    beta = 0.8
    eps = 0.001

    x_new = x0
    delta_x = -grad(x0, *args)
    while np.linalg.norm(delta_x) > eps:
        t = backtracking_line_search(fun, x_new, grad, delta_x, alpha, beta, args)
        x_new = x_new + (t * delta_x)
        delta_x = -grad(x_new, *args)

    return x_new

其中 eps 是一些小的正公差。