【问题标题】:scipy function always returns a numpy arrayscipy 函数总是返回一个 numpy 数组
【发布时间】:2012-09-24 13:09:54
【问题描述】:

我遇到了一个 scipy 函数,无论传递给它什么,它似乎都会返回一个 numpy 数组。在我的应用程序中,我只需要能够传递标量和列表,因此唯一的“问题”是当我将标量传递给函数时,会返回一个包含一个元素的数组(当我期望一个标量时)。我应该忽略这种行为,还是修改函数以确保在传递标量时返回标量?

示例代码:

#! /usr/bin/env python

import scipy
import scipy.optimize
from numpy import cos

# This a some function we want to compute the inverse of
def f(x):
    y = x + 2*cos(x)
    return y

# Given y, this returns x such that f(x)=y
def f_inverse(y):

    # This will be zero if f(x)=y
    def minimize_this(x):
        return y-f(x)

    # A guess for the solution is required
    x_guess = y
    x_optimized = scipy.optimize.fsolve(minimize_this, x_guess) # THE PROBLEM COMES FROM HERE
    return x_optimized

# If I call f_inverse with a list, a numpy array is returned
print f_inverse([1.0, 2.0, 3.0])
print type( f_inverse([1.0, 2.0, 3.0]) )

# If I call f_inverse with a tuple, a numpy array is returned
print f_inverse((1.0, 2.0, 3.0))
print type( f_inverse((1.0, 2.0, 3.0)) )

# If I call f_inverse with a scalar, a numpy array is returned
print f_inverse(1.0)
print type( f_inverse(1.0) )

# This is the behaviour I expected (scalar passed, scalar returned).
# Adding [0] on the return value is a hackey solution (then thing would break if a list were actually passed).
print f_inverse(1.0)[0] # <- bad solution
print type( f_inverse(1.0)[0] )

在我的系统上,这个输出是:

[ 2.23872989  1.10914418  4.1187546 ]
<type 'numpy.ndarray'>
[ 2.23872989  1.10914418  4.1187546 ]
<type 'numpy.ndarray'>
[ 2.23872989]
<type 'numpy.ndarray'>
2.23872989209
<type 'numpy.float64'>

我正在使用 MacPorts 提供的 SciPy 0.10.1 和 Python 2.7.3。

解决方案

阅读下面的答案后,我选择了以下解决方案。将f_inverse 中的返回行替换为:

if(type(y).__module__ == np.__name__):
    return x_optimized
else:
    return type(y)(x_optimized)

这里return type(y)(x_optimized) 导致返回类型与调用函数的类型相同。不幸的是,如果 y 是 numpy 类型,这将不起作用,因此 if(type(y).__module__ == np.__name__) 用于 detect numpy types using the idea presented here 并将它们排除在类型转换之外。

【问题讨论】:

  • 无法在 SciPy 0.7.2 上重现;我得到了np.float64 类型的标量返回。顺便说一句,那些不是optimize.fsolve 正在返回的列表;它们是 Numpy 数组。
  • 是的,你是对的。问题已更新。

标签: python numpy scipy duck-typing dynamic-typing


【解决方案1】:

scipy.optimize.fsolve中实现的第一行是:

x0 = array(x0, ndmin=1)

这意味着你的标量将变成一个单元素序列,而你的单元素序列将基本保持不变。

它似乎工作的事实是一个实现细节,我将重构您的代码以不允许将标量发送到fsolve。我知道这似乎违背了鸭子类型,但该函数要求该参数使用ndarray,因此您应该尊重接口对实现更改的鲁棒性。但是,我认为有条件地使用 x_guess = array(y, ndmin=1) 在包装函数中将标量转换为 ndarray 并在必要时将结果转换回标量没有任何问题。

这里是fsolve函数的docstring的相关部分:

def fsolve(func, x0, args=(), fprime=None, full_output=0,
           col_deriv=0, xtol=1.49012e-8, maxfev=0, band=None,
           epsfcn=0.0, factor=100, diag=None):
    """
    Find the roots of a function.

    Return the roots of the (non-linear) equations defined by
    ``func(x) = 0`` given a starting estimate.

    Parameters
    ----------
    func : callable f(x, *args)
        A function that takes at least one (possibly vector) argument.
    x0 : ndarray
        The starting estimate for the roots of ``func(x) = 0``.

    ----SNIP----

    Returns
    -------
    x : ndarray
        The solution (or the result of the last iteration for
        an unsuccessful call).

    ----SNIP----

【讨论】:

  • 相当多的 SciPy 函数接受“类似数组”的输入,并将其隐式转换为数组。不过,为文档字符串 +1。
  • 谢谢。不过,我仍然不确定该怎么做,因为我不知道忽略标量和带有一个元素的 numpy 数组之间的区别是否安全。
  • 在这种情况下,我认为它不安全,所以我的回答是,如果您真的想要标量标量的接口,您确实应该破解您的 f_inverse 函数出来。
  • 是的,重新阅读您的答案,我明白这是隐含的。如果没有“更好”出现,我会接受这个答案。
【解决方案2】:

以下是将 Numpy 数组转换为列表以及将 Numpy 标量转换为 Python 标量的方法:

>>> x = np.float32(42)
>>> type(x)
<type 'numpy.float32'>
>>> x.tolist()
42.0

换句话说,np.ndarray 上的 tolist 方法专门处理标量。

这仍然会给您留下单元素列表,但这些列表很容易以通常的方式处理。

【讨论】:

    【解决方案3】:

    我猜 wims 的回答确实已经说得差不多了,但也许这会让差异更加清晰。

    numpy 返回的标量应该与array[0] 应该(几乎?)完全兼容标准的python float:

    a = np.ones(2, dtype=float)
    isinstance(a[0], float) == True # even this is true.
    

    在大多数情况下,1 大小的数组已经兼容标量和列表,例如它是一个可变对象,而浮点不是:

    a = np.ones(1, dtype=float)
    import math
    math.exp(a) # works
    # it is not isinstance though
    isinstance(a, float) == False
    # The 1-sized array works sometimes more like number:
    bool(np.zeros(1)) == bool(np.asscalar(np.zeros(1)))
    # While lists would be always True if they have more then one element.
    bool([0]) != bool(np.zeros(1))
    
    # And being in place might create confusion:
    a = np.ones(1); c = a; c += 3
    b = 1.; c = b; c += 3
    a != b
    

    所以如果用户不应该知道它,我认为第一个很好,第二个很危险。

    您还可以使用np.asscalar(result) 将大小为 1 的数组(任意维度)转换为正确的 python 标量:

    在[29]中:类型(np.asscalar(a[0])) 出[29]:浮动

    如果您想确保不应该知道 numpy 的用户不会感到意外,如果传入了标量,您至少必须获取 0 的元素。如果用户应该知道 numpy,只是文档可能一样好。

    【讨论】:

    • isinstance 检查有效,因为 OP 传入 Python 浮点数:isinstance(np.float64(1), float) 为真,但 isinstance(np.float32(1), float) 为假。
    • 是的,但是 python 浮点数总是双倍的,如果有人真的传入np.float32(1),他们应该知道期待 numpy 对象返回。
    【解决方案4】:

    正如@wim 指出的那样,fsolve 将您的标量转换为形状为(1,)ndarray 并返回一个形状为(1,) 的数组。

    如果您真的想获得一个标量作为输出,您可以尝试将以下内容放在函数的末尾:

    if solution.size == 1:
        return solution.item()
    return solution
    

    item 方法复制一个数组元素并返回一个标准 Python 标量)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-08-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-12-25
      相关资源
      最近更新 更多