使用Scipy检查梯度

2 投票
2 回答
5868 浏览
提问于 2025-04-17 16:52

我想用 scipy.optimize.check_grad 来检查我实现的 sigmoid 函数 的梯度;这是我的 Python 函数:

def sigmoid(x, gradient=False):
    y = 1 / (1 + numpy.exp(-x))
    return numpy.multiply(y, 1 - y) if gradient else y

这里是传给 check_grad 的参数和调用:

x0 = numpy.random.uniform(-30, 30, (4, 5))
func = sigmoid
grad = lambda x: sigmoid(x, gradient=True)
error = scipy.optimize.check_grad(func, grad, x0)

我遇到了下面的错误。这个形状不匹配是指操作 xk+d。你知道可能是什么原因吗?

文件 "scipy\optimize\optimize.py",第 597 行,在 approx_fprime
grad[k] = (f(*((xk+d,)+args)) - f0) / d[k]
ValueError: 操作数无法一起广播,形状为 (4,5) (4)

2 个回答

0

关于你的代码(需要向量化 - 如果需要的话,可以使用flatten()ravel()):

import numpy as np
from scipy import optimize

def sigmoid_obj(x, gradient=False):
    y = 1 / (1 + np.exp(-1*x))
    res= np.multiply(y, 1 - y) if gradient else y
    return sum(res)  # must return scalar !

def num_gradient(x):
    return optimize.approx_fprime(x, sigmoid_obj, eps)

x = np.array(np.random.uniform(-30, 30, (4, 5)))     # shape(4,5)
##x0= [0,]        # guess !! var(s) used in objective_func

eps = np.sqrt(np.finfo(float).eps)

error = optimize.check_grad(sigmoid_obj, num_gradient, x.flatten() )
print(error)

但通常情况下,objective_func是用来处理一个 x_pointy_target的投影,从x0=guess开始优化。在你代码的最简单版本中:

import numpy as np
from scipy import optimize

def sigmoid_obj(x, gradient=False):
    y = 1 / (1 + np.exp(-1*x))
    res= np.multiply(y, 1 - y) if gradient else y
    return res

def num_gradient(x):
    return optimize.approx_fprime(x, sigmoid_obj, eps)

x = np.array(np.random.uniform(-30, 30, (4, 5)))
x0= [0,]        # guess !! init_var(s) used in objective_func

eps = np.sqrt(np.finfo(float).eps)

error = optimize.check_grad(sigmoid_obj, num_gradient, x0 )
print(error)

? 如果你的初始猜测只假设为一个x,那么guess=x0必须是[0,]

附注:optimize.check_grad()内部使用optimize.approx_fprime(),后者是通过一个点投影到y来计算的[就像是局部的]... 或者可以通过解析的方法计算(使用SymPy或者手动,像文档中的例子那样):

import numpy as np from scipy import optimize
def func(x):
    return x[0]**2 - 0.5 * x[1]**3
def grad(x):
    return [2 * x[0], -1.5 * x[1]**2]   # here can be numerical_gradient=optimize.approx_fprime(...)
from scipy.optimize import check_grad
print(check_grad(func, grad, x0= [1.5, -1.5]))
1

你遇到的错误是因为 check_gradient 只接受一维的数组。也就是说,你应该用一个形状为 (20,) 的数组 x0,而不是 (4, 5) 的数组。不过,它还是不行!

这是我安装的 approx_fprime 的实现代码(scipy.__version__ = '0.9.0'):

def approx_fprime(xk,f,epsilon,*args):
    f0 = f(*((xk,)+args))
    grad = numpy.zeros((len(xk),), float)
    ei = numpy.zeros((len(xk),), float)
    for k in range(len(xk)):
        ei[k] = epsilon
        grad[k] = (f(*((xk+ei,)+args)) - f0)/epsilon
        ei[k] = 0.0
    return grad

我看了好几遍,真不敢相信这样的糟糕代码会出现在 scipy 里,我觉得我一定漏掉了什么……但我担心这就是错的。如果你把它换成:

def approx_fprime(xk,f,epsilon,*args):
    return (f(*((xk + epsilon,) + args)) - f(*((xk,) + args))) / epsilon

现在对我来说可以用了。用 x0.shape = (20,) 我得到了:

In [2]: error
Out[2]: 1.746097524556073e-08

而用 x0.shape = (4, 5)

In [4]: error
Out[4]: 
array([  1.03560895e-08,   1.45994321e-08,   8.54143390e-09,
         1.09225833e-08,   9.85988655e-09])

所以看来在其他地方也确实不支持非一维数组。不过无论如何,这个实现都是有问题的:你应该提交一个错误报告。

撰写回答