numpy: функция для одновременного max () и min()



numpy.amax () найти максимальное значение в массиве, и numpy.Амин() делает то же самое минимальное значение. Если я хочу найти как max, так и min, мне нужно вызвать обе функции, что требует прохождения через (очень большой) массив дважды, что кажется медленным.



есть ли функция в API numpy, которая находит как max, так и min только с одним проходом через данные?

585   8  

8 ответов:

есть ли функция в API numpy, которая находит как max, так и min только с одним проходом через данные?

нет. На момент написания этой статьи такой функции не было. (И да, если там были такая функция, ее производительность будет значительно лучше, чем звонить numpy.amin() и numpy.amax() последовательно на большом массиве.)

я не думаю, что передача массива дважды является проблемой. рассмотрим следующий псевдокод:

minval = array[0]
maxval = array[0]
for i in array:
    if i < minval:
       minval = i
    if i > maxval:
       maxval = i

в то время как есть только 1 цикл здесь, есть еще 2 проверки. (Вместо 2 петель с 1 проверить каждый). На самом деле единственное, что вы экономите, это накладные расходы на 1 цикл. Если массивы действительно большие, как вы говорите, что накладные расходы невелики по сравнению с фактической рабочей нагрузкой цикла. (Обратите внимание, что все это реализовано в C, поэтому циклы больше или в любом случае, менее бесплатно).


EDIT извините за 4 из вас, кто проголосовал и верил в меня. Вы определенно можете оптимизировать это.

вот некоторые fortran код, который может быть скомпилирован в модуль python через f2py (например,Cython гуру может прийти и сравнить это с оптимизированной версией 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

скомпилировать его через:

f2py -m untitled -c fortran_code.f90

и сейчас мы находимся в месте, где мы можем проверить это:

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'

результаты немного ошеломляют меня:

8.61869883537 # numpy min/max
1.60417699814 # minmax1
2.30169081688 # minmax2

Я должен сказать, я не совсем понимаю. Сравнение просто np.min и minmax1 и minmax2 еще проигранная битва, так что это не просто вопрос памяти ...

Примечания -- увеличение размера в несколько раз 10**a и уменьшение повторения в несколько раз 10**a (сохраняя размер проблемы постоянным) действительно изменяет производительность, но не в кажущемся последовательный способ, который показывает, что существует некоторое взаимодействие между производительностью памяти и служебными данными вызова функций в python. Даже сравнивая простой min реализация на Фортране бьет и NumPy это примерно 2 ...

есть функция для поиска (max-min) называется numpy.ПТП если это полезно для вас:

>>> import numpy
>>> x = numpy.array([1,2,3,4,5,6])
>>> x.ptp()
5

но я не думаю, что есть способ найти как min, так и max с одним обходом.

EDIT:ptp просто вызывает мин и Макс под капотом

вы могли бы использовать Numba, который является numpy-aware динамический компилятор Python с использованием LLVM. Полученная реализация довольно проста и понятна:

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()))

Он должен быть быстрее, чем NumPy и обратно в min() & max() реализация. И все это без необходимости писать одну строку кода C/Fortran.

сделайте свои собственные тесты производительности, так как это всегда зависит от вашей архитектуры, ваших данных, ваших версий пакетов...

Это старый поток, но в любом случае, если кто-нибудь когда-нибудь посмотрит на это снова...

при поиске мин и Макс одновременно, можно уменьшить количество сравнений. Если это поплавки, которые вы сравниваете (что, я думаю, так и есть), это может сэкономить вам некоторое время, хотя и не вычислительная сложность.

вместо (код 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]

вы можете сначала сравнить два соседних значения в массиве, а затем сравнить только меньшее против текущего минимума, а больший против текущего максимума:

## 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

код здесь написан на Python, очевидно, для скорости вы бы использовали C или Fortran или Cython, но таким образом Вы делаете 3 сравнения на итерацию, с LEN(ar)/2 итерациями, давая 3/2 * LEN(ar) сравнения. В отличие от этого, делая сравнение "очевидным способом", вы делаете два сравнения на итерацию, что приводит к сравнениям 2*len(ar). Вы экономите 25% времени сравнения.

может, кто-то один день найдет это полезным.

на первый взгляд numpy.histogramпоявляется сделать трюк:

count, (amin, amax) = numpy.histogram(a, bins=1)

... но если вы посмотрите на источник для этой функции, он просто называет a.min() и a.max() независимо, и поэтому не удается избежать проблем с производительностью, рассмотренных в этом вопросе. : - (

аналогично, scipy.ndimage.measurements.extrema похоже на возможность, но она тоже просто вызывает a.min() и a.max() самостоятельно.

В общем случае вы можете уменьшить количество сравнений для алгоритма minmax, обрабатывая два элемента одновременно и сравнивая только меньший с временным минимумом и больший с временным максимумом. В среднем требуется только 3/4 сравнений, чем наивный подход.

Это может быть реализовано на c или fortran (или любом другом языке низкого уровня) и должно быть почти непревзойденным с точки зрения производительности. Я использую numba для иллюстрации принцип и получить очень быстрый, 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

Это определенно быстрее, чем наивный подход 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

как и ожидалось, новая реализация minmax занимает примерно 3/4 времени, которое заняла наивная реализация (2.1 / 2.75 = 0.7636363636363637)

никто не упомянул numpy.процентиль, так я и думал. Если вы просите [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'

будущая версия Numpy может поместить в специальный случай, чтобы пропустить нормальное вычисление процентиля, если только [0, 100] просят. Не добавляя ничего в интерфейс, есть способ попросить Numpy для min и max в одном вызове (вопреки тому, что было сказано в принятом ответе), но стандартная реализация библиотеки не использует этот случай, чтобы сделать его стоящим.

Comments

    Ничего не найдено.