numpy:同时max()和min()的函数

Stu*_*erg 91 python numpy

numpy.amax()将在数组中找到最大值,numpy.amin()对最小值执行相同的操作.如果我想找到max和min,我必须调用这两个函数,这需要两次遍历(非常大)数组,这似乎很慢.

numpy API中是否有一个函数可以通过数据只传递一次max和min?

Stu*_*erg 37

numpy API中是否有一个函数可以通过数据只传递一次max和min?

不.在撰写本文时,没有这样的功能.(是的,如果出现这样的功能,其性能将是显著比调用更好地numpy.amin()numpy.amax()一个大阵上先后).

  • 如果有人像我一样想知道,是否仅使用两个索引进行部分排序,如下所示:`x.partition([0, x.size-1])`会比两次单独调用`x.min()`和`x.max()`,那么不,不是。两个单独的呼叫总是更快。我测试了以下大小的“x”:[1e2,1e3,1e4,1e5,1e6,1e7,1e8]并测量了单独到部分的相对时间:[35.53%,5.10%,53.33%,36.89%,37.65 %、36.03%、42.89%]。因此,如果您的数据向量有 1 亿个样本大小,则与部分排序相比,单独调用 min 和 max 将仅使用 42.89% 的时间。 (2认同)

mgi*_*son 29

我不认为两次传递数组是一个问题. 考虑以下伪代码:

minval = array[0]
maxval = array[0]
for i in array:
    if i < minval:
       minval = i
    if i > maxval:
       maxval = i
Run Code Online (Sandbox Code Playgroud)

虽然这里只有1个循环,但仍有2个检查.(而不是有2个循环,每个1检查).真的,你唯一能节省的就是1循环的开销.如果数组真的很大,就像你说的那样,与实际循环的工作负载相比,这种开销很小.(注意,这都是用C实现的,所以循环或多或少都是免费的).


编辑对你们4个赞成我并对我有信心的人表示抱歉.你绝对可以优化这一点.

这里有一些可以编译成python模块的fortran代码f2py(可能是一个Cythonguru可以出现并将其与优化的C版本进行比较......):

subroutine minmax1(a,n,amin,amax)
  implicit none
  !f2py intent(hidden) :: n
  !f2py intent(out) :: amin,amax
  !f2py intent(in) :: a
  integer n
  real a(n),amin,amax
  integer i

  amin = a(1)
  amax = a(1)
  do i=2, n
     if(a(i) > amax)then
        amax = a(i)
     elseif(a(i) < amin) then
        amin = a(i)
     endif
  enddo
end subroutine minmax1

subroutine minmax2(a,n,amin,amax)
  implicit none
  !f2py intent(hidden) :: n
  !f2py intent(out) :: amin,amax
  !f2py intent(in) :: a
  integer n
  real a(n),amin,amax
  amin = minval(a)
  amax = maxval(a)
end subroutine minmax2
Run Code Online (Sandbox Code Playgroud)

通过以下方式编译:

f2py -m untitled -c fortran_code.f90
Run Code Online (Sandbox Code Playgroud)

现在我们在一个可以测试它的地方:

import timeit

size = 100000
repeat = 10000

print timeit.timeit(
    'np.min(a); np.max(a)',
    setup='import numpy as np; a = np.arange(%d, dtype=np.float32)' % size,
    number=repeat), " # numpy min/max"

print timeit.timeit(
    'untitled.minmax1(a)',
    setup='import numpy as np; import untitled; a = np.arange(%d, dtype=np.float32)' % size,
    number=repeat), '# minmax1'

print timeit.timeit(
    'untitled.minmax2(a)',
    setup='import numpy as np; import untitled; a = np.arange(%d, dtype=np.float32)' % size,
    number=repeat), '# minmax2'
Run Code Online (Sandbox Code Playgroud)

结果对我来说有点惊愕:

8.61869883537 # numpy min/max
1.60417699814 # minmax1
2.30169081688 # minmax2
Run Code Online (Sandbox Code Playgroud)

我不得不说,我不完全理解它.只是比较np.minminmax1minmax2仍然是一场败仗,所以它不只是一个内存问题...

注意 - 增加一个因子10**a并将重复减少一个因子10**a(保持问题大小不变)确实会改变性能,但不会以看似一致的方式显示内存性能和函数调用开销之间存在一些相互作用.蟒蛇.甚至将minfortran中的简单实现与numpy的比较大约为2 ...

  • 单次通过的优点是内存效率.特别是如果你的阵列足够大,可以换掉,这可能是巨大的. (19认同)
  • 您并不总是需要两次检查。如果`i &lt; minval` 为真,则`i &gt; maxval` 始终为假,因此当第二个`if` 被替换为`elif` 时,平均每次迭代只需进行1.5 次检查。 (5认同)
  • numpy 1.8 min和max在amd64平台上进行矢量化,在我的core2duo numpy上执行以及这个fortran代码.但是,如果阵列超过较大的cpu高速缓存的大小,则单次传递将是有利的. (4认同)
  • 这不太正确,几乎快了一半,因为有了这些阵列,内存速度通常是限制因素,所以它可以快一半...... (3认同)
  • 小记:我怀疑Cython是获得最优化的Python可调用C模块的方法.Cython的目标是成为一种带类型注释的Python,然后将其机器翻译为C,而`f2py`只包含手工编码的Fortran,以便Python可以调用它.一个"更公平"的测试可能是手工编码C然后使用`f2py`(!)将其包装为Python.如果你允许使用C++,那么Shed Skin可能是平衡编码简易性和性能的最佳选择. (2认同)

jte*_*ace 20

有一个函数可以找到(max-min)numpy.ptp,如果它对你有用:

>>> import numpy
>>> x = numpy.array([1,2,3,4,5,6])
>>> x.ptp()
5
Run Code Online (Sandbox Code Playgroud)

但我不认为有一种方法可以通过一次遍历找到最小值和最大值.

编辑: ptp只是在引擎盖下调用min和max

  • @hayden结果是ptp只调用max和min (3认同)
  • 这很烦人,因为可能是ptp的实现方式,它必须跟踪最大值和最小值! (2认同)
  • 这就是屏​​蔽数组代码;主要的 ndarray 代码是用 C 编写的。但事实证明,C 代码也对数组进行了两次迭代:https://github.com/numpy/numpy/blob/29e6071d2376f9e96cd111adc7b274e07ac88e8d/numpy/core/src/multiarray/calculation.c# L318。 (2认同)

nor*_*ok2 18

鉴于以下方法,只是为了获得一些关于人们可以预期的数字的想法:

import numpy as np


def extrema_np(arr):
    return np.max(arr), np.min(arr)
Run Code Online (Sandbox Code Playgroud)
import numba as nb


@nb.jit(nopython=True)
def extrema_loop_nb(arr):
    n = arr.size
    max_val = min_val = arr[0]
    for i in range(1, n):
        item = arr[i]
        if item > max_val:
            max_val = item
        elif item < min_val:
            min_val = item
    return max_val, min_val
Run Code Online (Sandbox Code Playgroud)
import numba as nb


@nb.jit(nopython=True)
def extrema_while_nb(arr):
    n = arr.size
    odd = n % 2
    if not odd:
        n -= 1
    max_val = min_val = arr[0]
    i = 1
    while i < n:
        x = arr[i]
        y = arr[i + 1]
        if x > y:
            x, y = y, x
        min_val = min(x, min_val)
        max_val = max(y, max_val)
        i += 2
    if not odd:
        x = arr[n]
        min_val = min(x, min_val)
        max_val = max(x, max_val)
    return max_val, min_val
Run Code Online (Sandbox Code Playgroud)
%%cython -c-O3 -c-march=native -a
#cython: language_level=3, boundscheck=False, wraparound=False, initializedcheck=False, cdivision=True, infer_types=True


import numpy as np


cdef void _extrema_loop_cy(
        long[:] arr,
        size_t n,
        long[:] result):
    cdef size_t i
    cdef long item, max_val, min_val
    max_val = arr[0]
    min_val = arr[0]
    for i in range(1, n):
        item = arr[i]
        if item > max_val:
            max_val = item
        elif item < min_val:
            min_val = item
    result[0] = max_val
    result[1] = min_val


def extrema_loop_cy(arr):
    result = np.zeros(2, dtype=arr.dtype)
    _extrema_loop_cy(arr, arr.size, result)
    return result[0], result[1]
Run Code Online (Sandbox Code Playgroud)
%%cython -c-O3 -c-march=native -a
#cython: language_level=3, boundscheck=False, wraparound=False, initializedcheck=False, cdivision=True, infer_types=True


import numpy as np


cdef void _extrema_while_cy(
        long[:] arr,
        size_t n,
        long[:] result):
    cdef size_t i, odd
    cdef long x, y, max_val, min_val
    max_val = arr[0]
    min_val = arr[0]
    odd = n % 2
    if not odd:
        n -= 1
    max_val = min_val = arr[0]
    i = 1
    while i < n:
        x = arr[i]
        y = arr[i + 1]
        if x > y:
            x, y = y, x
        min_val = min(x, min_val)
        max_val = max(y, max_val)
        i += 2
    if not odd:
        x = arr[n]
        min_val = min(x, min_val)
        max_val = max(x, max_val)
    result[0] = max_val
    result[1] = min_val


def extrema_while_cy(arr):
    result = np.zeros(2, dtype=arr.dtype)
    _extrema_while_cy(arr, arr.size, result)
    return result[0], result[1]
Run Code Online (Sandbox Code Playgroud)

extrema_loop_*()方法类似于此处提出的extrema_while_*()方法,而方法基于此处的代码)

以下时间:

体重

表示extrema_while_*()速度最快,extrema_while_nb()速度最快。在任何情况下,extrema_loop_nb()extrema_loop_cy()解决方案也优于仅使用 NumPy 的方法(单独使用np.max()np.min())。

最后,请注意,这些都没有np.min()/灵活np.max()(在 n-dim 支持、axis参数等方面)。

(完整代码可在此处获得

  • 如果使用 @njit(fastmath=True) `extrema_while_nb ` 似乎你可以获得额外 10% 的速度 (3认同)

Peq*_*que 15

您可以使用Numba,这是一个使用LLVM的NumPy感知动态Python编译器.最终的实现非常简单明了:

import numpy
import numba


@numba.jit
def minmax(x):
    maximum = x[0]
    minimum = x[0]
    for i in x[1:]:
        if i > maximum:
            maximum = i
        elif i < minimum:
            minimum = i
    return (minimum, maximum)


numpy.random.seed(1)
x = numpy.random.rand(1000000)
print(minmax(x) == (x.min(), x.max()))
Run Code Online (Sandbox Code Playgroud)

它也应该比Numpy的min() & max()实现更快.而且无需编写单个C/Fortran代码行.

做自己的性能测试,因为它总是取决于您的架构,数据,包版本......

  • 刚刚遇到了这个问题,这在实际情况下并不重要,但是 `elif` 允许您的最小值大于最大值。例如,对于长度为 1 的数组,最大值将是该值,而最小值为 + 无穷大。对于一次性的来说没什么大不了的,但不是很好的代码,可以深入到生产野兽的肚子里。 (4认同)
  • &gt; 它也应该比 Numpy 的 min() 和 max() 实现更快,我认为这是不对的。numpy 不是原生 python——它是 C。``` x = numpy.random.rand(10000000) t = time() for i in range(1000): minmax(x) print('numba', time() - t) t = time() for i in range(1000): x.min() x.max() print('numpy', time() - t) ``` 结果:('numba', 10.299750089645386 ) ('numpy', 9.898081064224243) (2认同)
  • @AuthmanApatira:是的,基准测试总是这样,这就是为什么我说它“*应该*”(更快)和“*做你自己的性能测试,因为它总是依赖于你的架构,你的数据......* ”。就我而言,我尝试使用 3 台计算机并得到相同的结果(Numba 比 Numpy 更快),但在您的计算机中结果可能有所不同...您是否尝试在基准测试之前执行一次“numba”函数以确保它是JIT 编译?另外,如果您使用“ipython”,为了简单起见,我建议您使用“%timeitwhatever_code()”来测量执行时间。 (2认同)
  • @AuthmanApatira:无论如何,我试图用这个答案展示的是,有时Python代码(在这种情况下是用N​​umba进行JIT编译)可以和最快的C编译库一样快(至少我们在谈论相同的顺序)数量级),考虑到我们只编写了纯Python代码,这令人印象深刻,您同意吗?^^ (2认同)

小智 8

这是一个老线程,但无论如何,如果有人再次看到这个......

当同时寻找最小值和最大值时,可以减少比较次数.如果它是浮动的你正在比较(我猜它是)这可能会节省你一些时间,虽然不是计算复杂性.

而不是(Python代码):

_max = ar[0]
_min=  ar[0]
for ii in xrange(len(ar)):
    if _max > ar[ii]: _max = ar[ii]
    if _min < ar[ii]: _min = ar[ii]
Run Code Online (Sandbox Code Playgroud)

您可以先比较阵列中的两个相邻值,然后仅将较小的值与当前最小值进行比较,将较大的值与当前最大值进行比较:

## for an even-sized array
_max = ar[0]
_min = ar[0]
for ii in xrange(0, len(ar), 2)):  ## iterate over every other value in the array
    f1 = ar[ii]
    f2 = ar[ii+1]
    if (f1 < f2):
        if f1 < _min: _min = f1
        if f2 > _max: _max = f2
    else:
        if f2 < _min: _min = f2
        if f1 > _max: _max = f1
Run Code Online (Sandbox Code Playgroud)

这里的代码是用Python编写的,显然是为了你使用C或Fortran或Cython的速度,但这样你每次迭代进行3次比较,使用len(ar)/ 2次迭代,给出3/2*len(ar)比较.与此相反,进行比较"显而易见",每次迭代进行两次比较,导致2*len(ar)比较.为您节省25%的比较时间.

也许某人有一天会觉得这很有用.

  • 在numpy中有一个minmax实现,由`np.bincount`使用,见[here](https://github.com/numpy/numpy/blob/maintenance/1.9.x/numpy/lib/src /_compiled_base.c#L110).它没有使用你指出的技巧,因为它比天真的方法慢了2倍.[PR](https://github.com/numpy/numpy/pull/4282)链接到两种方法的一些综合基准. (11认同)
  • 你有基准吗?在现代x86硬件上,您可以获得第一个版本中使用的min和max机器指令,这样可以避免在代码放入控制依赖项时需要分支,这可能不会映射到硬件. (5认同)

Jim*_*ski 8

没有人提到numpy.percentile,所以我想我会的。如果您要求[0, 100]百分位,它将为您提供两个元素的数组,最小(第0个百分位)和最大(第100个百分位)。

但是,它不能满足OP的目的:它不比单独的min和max快。这可能是由于一些机制将允许非极端百分位数(一个困难的问题,这应该需要更长的时间)。

In [1]: import numpy

In [2]: a = numpy.random.normal(0, 1, 1000000)

In [3]: %%timeit
   ...: lo, hi = numpy.amin(a), numpy.amax(a)
   ...: 
100 loops, best of 3: 4.08 ms per loop

In [4]: %%timeit
   ...: lo, hi = numpy.percentile(a, [0, 100])
   ...: 
100 loops, best of 3: 17.2 ms per loop

In [5]: numpy.__version__
Out[5]: '1.14.4'
Run Code Online (Sandbox Code Playgroud)

如果仅[0, 100]要求,Numpy的未来版本可能会出现特殊情况以跳过正常的百分位数计算。在不向接口添加任何内容的情况下,有一种方法可以在一次调用中向Numpy询问最小值和最大值(与接受的答案中所说的相反),但是该库的标准实现没有利用这种情况来实现这一点值得。


MSe*_*ert 7

通常,您可以通过一次处理两个元素并仅将较小的元素与临时最小值进行比较,将较大的元素与临时最大值进行比较来减少minmax算法的比较量.平均而言,只需要3/4的比较而不是天真的方法.

这可以用c或fortran(或任何其他低级语言)实现,并且在性能方面几乎是无与伦比的.我正在使用来说明原理并获得一个非常快速的,与dtype无关的实现:

import numba as nb
import numpy as np

@nb.njit
def minmax(array):
    # Ravel the array and return early if it's empty
    array = array.ravel()
    length = array.size
    if not length:
        return

    # We want to process two elements at once so we need
    # an even sized array, but we preprocess the first and
    # start with the second element, so we want it "odd"
    odd = length % 2
    if not odd:
        length -= 1

    # Initialize min and max with the first item
    minimum = maximum = array[0]

    i = 1
    while i < length:
        # Get the next two items and swap them if necessary
        x = array[i]
        y = array[i+1]
        if x > y:
            x, y = y, x
        # Compare the min with the smaller one and the max
        # with the bigger one
        minimum = min(x, minimum)
        maximum = max(y, maximum)
        i += 2

    # If we had an even sized array we need to compare the
    # one remaining item too.
    if not odd:
        x = array[length]
        minimum = min(x, minimum)
        maximum = max(x, maximum)

    return minimum, maximum
Run Code Online (Sandbox Code Playgroud)

绝对Peque提出的朴素方法更快:

arr = np.random.random(3000000)
assert minmax(arr) == minmax_peque(arr)  # warmup and making sure they are identical 
%timeit minmax(arr)            # 100 loops, best of 3: 2.1 ms per loop
%timeit minmax_peque(arr)      # 100 loops, best of 3: 2.75 ms per loop
Run Code Online (Sandbox Code Playgroud)

正如预期的那样,新的minmax实现仅花费大约3/4的时间实现天真的实现(2.1 / 2.75 = 0.7636363636363637)


Stu*_*erg 5

乍一看,似乎可以解决问题:numpy.histogram

count, (amin, amax) = numpy.histogram(a, bins=1)
Run Code Online (Sandbox Code Playgroud)

......但如果你看看为该函数,它只是简单地调用a.min()a.max()独立,因此无法避免业绩的担忧在这个问题解决。:-(

同样的,scipy.ndimage.measurements.extrema看起来像一个可能性,但它也只是调用a.min()a.max()独立。

  • np.histogram不总是适用于此,因为返回的(amin,amax)值是bin的最小值和最大值。例如,如果我有`a = np.zeros(10)`,则`np.histogram(a,bins = 1)`返回`(array([10]),array([-0.5,0.5])) `。在这种情况下,用户正在寻找`(amin,amax)`=(0,0)。 (3认同)