numpy.amax()は配列内の最大値を検索し、numpy.amin()は最小値に対して同じことを行います。maxとminの両方を検索する場合は、両方の関数を呼び出す必要があります。これには、(非常に大きい)配列を2回渡す必要があり、遅いようです。
numpy APIに、データを1回パスするだけで最大と最小の両方を見つける関数はありますか?
minmax
問題のライブラリーへの追加についてNumPyに尋ねました(github.com/numpy/numpy/issues/9836)。
numpy.amax()は配列内の最大値を検索し、numpy.amin()は最小値に対して同じことを行います。maxとminの両方を検索する場合は、両方の関数を呼び出す必要があります。これには、(非常に大きい)配列を2回渡す必要があり、遅いようです。
numpy APIに、データを1回パスするだけで最大と最小の両方を見つける関数はありますか?
minmax
問題のライブラリーへの追加についてNumPyに尋ねました(github.com/numpy/numpy/issues/9836)。
回答:
numpy APIに、データを1回パスするだけで最大と最小の両方を見つける関数はありますか?
いいえ。この記事の執筆時点では、そのような機能はありません。(そして、そうです、そのような関数があった場合、そのパフォーマンスは、大きな配列を連続して呼び出すよりもはるかに優れています。)numpy.amin()
numpy.amax()
配列を2回渡すことは問題ではないと思います。 次の疑似コードを考えてみます。
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で実装されているため、ループは多かれ少なかれフリーです)。
編集私に賛成して私を信じていたあなたの4人に申し訳ありません。あなたは間違いなくこれを最適化できます。
以下は、PythonモジュールにコンパイルできるFortranコードです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
まだ負けた戦いなので、それは単なるメモリの問題ではありません...
注 -サイズを1倍に増やし、10**a
繰り返しを1倍に減らして10**a
(問題のサイズを一定に保つ)と、パフォーマンスは変化しますが、一見一貫した方法ではありません。 python。min
Fortranでの単純な実装を比較しても、numpyの係数は約2倍です...
i < minval
trueで、その後、i > maxval
あなただけの第二の時に平均的に反復あたり1.5のチェックを行う必要があるので、常に偽でif
置き換えられますelif
。
f2py
れますが、Pythonから呼び出せるように、手動でコーディングしたFortranをラップするだけです。「より公正な」テストはおそらくCを手動でコーディングし、それをf2py
(!)を使用してPython用にラップすることです。C ++を許可している場合、Shed Skinはコーディングの容易さとパフォーマンスのバランスをとるためのスイートスポットになる可能性があります。
有用であれば、numpy.ptpと呼ばれる(max-min)を見つけるための関数があります。
>>> import numpy
>>> x = numpy.array([1,2,3,4,5,6])
>>> x.ptp()
5
しかし、1つのトラバーサルで最小値と最大値の両方を見つける方法はないと思います。
LLVMを使用するNumPy対応の動的PythonコンパイラであるNumbaを使用できます。結果の実装は非常にシンプルで明確です。
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のコードを1行も書く必要がありません。
アーキテクチャ、データ、パッケージバージョンに常に依存しているため、独自のパフォーマンステストを行ってください...
numba
ベンチマークの前に関数を1回実行して、JITコンパイルされていることを確認しましたか? ?。また、を使用する場合ipython
、簡単にするために、%timeit whatever_code()
時間実行の測定に使用することをお勧めします。
elif
で、最小値を最大値より大きくすることができます。たとえば、長さが1の配列の場合、最大値はその値になり、最小値は+無限大になります。1回限りの場合は大した問題ではありませんが、生産獣の腹に深く入り込むための良いコードではありません。
一般に、一度に2つの要素を処理し、小さい方を一時的な最小値と比較し、大きい方を一時的な最大値と比較するだけで、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
それはdefinetly速いという単純なアプローチよりもだ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
)
以下のアプローチを前提として、予想できる数についていくつかのアイデアを得るために:
import numpy as np
def extrema_np(arr):
return np.max(arr), np.min(arr)
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
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
%%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]
%%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]
(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
パラメーターなどに関して)。
extrema_while_nb
numpy.percentileについては誰も言及していなかったので、私はそう思いました。[0, 100]
パーセンタイルを要求すると、最小(0パーセンタイル)と最大(100パーセンタイル)の2つの要素の配列が表示されます。
ただし、OPの目的を満たしていません。最小値と最大値を個別に指定するよりも高速ではありません。これはおそらく、極端でないパーセンタイルを可能にするいくつかの機構によるものです(より長い時間がかかる、より難しい問題)。
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]
要求された場合に通常のパーセンタイル計算をスキップする可能性があります。インターフェースに何も追加せずに、1つの呼び出しでNumpyに最小と最大を要求する方法があります(承認された回答で述べられていることとは異なり)が、ライブラリの標準実装は、このケースを利用してそれを作成しません。価値がある。
これは古いスレッドですが、とにかく、これをもう一度見る人がいたら...
最小値と最大値を同時に検索する場合、比較の数を減らすことができます。フロートの場合、比較していると思いますが(これはそうだと思います)、計算は複雑ではありませんが、時間を節約できます。
(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]
最初に配列内の2つの隣接する値を比較してから、小さい方の値を現在の最小値と比較し、大きい方の値を現在の最大値と比較するだけです。
## 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つの比較が行われ、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()
独立。
np.histogram
戻り(amin, amax)
値はビンの最小値と最大値に対するものであるため、これは常に機能するとは限りません。たとえば、のa = np.zeros(10)
場合、np.histogram(a, bins=1)
が返されます(array([10]), array([-0.5, 0.5]))
。その場合、ユーザーは(amin, amax)
=(0、0)を探します。
とにかく私にとっては努力の価値があったので、興味がある人のために、ここで最も難しくてエレガントでない解決策を提案します。私の解決策は、C ++のワンパスアルゴリズムでマルチスレッドのmin-maxを実装し、これを使用してPython拡張モジュールを作成することです。この作業には、PythonおよびNumPy C / C ++ APIの使用方法を学習するための少しのオーバーヘッドが必要です。ここでは、コードを示し、このパスを使用したい人のためにいくつかの簡単な説明と参照を示します。
ここにはあまり興味深いものはありません。配列はサイズのチャンクに分割されますlength / workers
。最小値/最大値は、の各チャンクについて計算future
され、グローバル最小値/最大値がスキャンされます。
// mt_np.cc
//
// multi-threaded min/max algorithm
#include <algorithm>
#include <future>
#include <vector>
namespace mt_np {
/*
* Get {min,max} in interval [begin,end)
*/
template <typename T> std::pair<T, T> min_max(T *begin, T *end) {
T min{*begin};
T max{*begin};
while (++begin < end) {
if (*begin < min) {
min = *begin;
continue;
} else if (*begin > max) {
max = *begin;
}
}
return {min, max};
}
/*
* get {min,max} in interval [begin,end) using #workers for concurrency
*/
template <typename T>
std::pair<T, T> min_max_mt(T *begin, T *end, int workers) {
const long int chunk_size = std::max((end - begin) / workers, 1l);
std::vector<std::future<std::pair<T, T>>> min_maxes;
// fire up the workers
while (begin < end) {
T *next = std::min(end, begin + chunk_size);
min_maxes.push_back(std::async(min_max<T>, begin, next));
begin = next;
}
// retrieve the results
auto min_max_it = min_maxes.begin();
auto v{min_max_it->get()};
T min{v.first};
T max{v.second};
while (++min_max_it != min_maxes.end()) {
v = min_max_it->get();
min = std::min(min, v.first);
max = std::max(max, v.second);
}
return {min, max};
}
}; // namespace mt_np
ここからが醜くなります... PythonでC ++コードを使用する1つの方法は、拡張モジュールを実装することです。このモジュールは、distutils.core
標準モジュールを使用して構築およびインストールできます。これに伴うものの完全な説明は、Pythonのドキュメントhttps://docs.python.org/3/extending/extending.htmlでカバーされています。 注:https : //docs.python.org/3/extending/index.html#extending-indexを引用して、同様の結果を得る他の方法は確かにあります:
このガイドでは、このバージョンのCPythonの一部として提供される拡張機能を作成するための基本的なツールのみを扱います。Cython、cffi、SWIG、Numbaなどのサードパーティツールは、Python用のCおよびC ++拡張を作成するためのよりシンプルで洗練されたアプローチを提供します。
基本的に、このルートはおそらく実用的というよりも学術的です。それが言われて、私が次にしたことは、チュートリアルにかなり固執して、モジュールファイルを作成することでした。これは本質的に、distutilsがコードの処理方法を知り、それからPythonモジュールを作成するための定型文です。これを行う前に、システムパッケージを汚染しないようにPython 仮想環境を作成することをお勧めします(https://docs.python.org/3/library/venv.html#module-venvを参照)。
モジュールファイルは次のとおりです。
// mt_np_forpy.cc
//
// C++ module implementation for multi-threaded min/max for np
#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION
#include <python3.6/numpy/arrayobject.h>
#include "mt_np.h"
#include <cstdint>
#include <iostream>
using namespace std;
/*
* check:
* shape
* stride
* data_type
* byteorder
* alignment
*/
static bool check_array(PyArrayObject *arr) {
if (PyArray_NDIM(arr) != 1) {
PyErr_SetString(PyExc_RuntimeError, "Wrong shape, require (1,n)");
return false;
}
if (PyArray_STRIDES(arr)[0] != 8) {
PyErr_SetString(PyExc_RuntimeError, "Expected stride of 8");
return false;
}
PyArray_Descr *descr = PyArray_DESCR(arr);
if (descr->type != NPY_LONGLTR && descr->type != NPY_DOUBLELTR) {
PyErr_SetString(PyExc_RuntimeError, "Wrong type, require l or d");
return false;
}
if (descr->byteorder != '=') {
PyErr_SetString(PyExc_RuntimeError, "Expected native byteorder");
return false;
}
if (descr->alignment != 8) {
cerr << "alignment: " << descr->alignment << endl;
PyErr_SetString(PyExc_RuntimeError, "Require proper alignement");
return false;
}
return true;
}
template <typename T>
static PyObject *mt_np_minmax_dispatch(PyArrayObject *arr) {
npy_intp size = PyArray_SHAPE(arr)[0];
T *begin = (T *)PyArray_DATA(arr);
auto minmax =
mt_np::min_max_mt(begin, begin + size, thread::hardware_concurrency());
return Py_BuildValue("(L,L)", minmax.first, minmax.second);
}
static PyObject *mt_np_minmax(PyObject *self, PyObject *args) {
PyArrayObject *arr;
if (!PyArg_ParseTuple(args, "O", &arr))
return NULL;
if (!check_array(arr))
return NULL;
switch (PyArray_DESCR(arr)->type) {
case NPY_LONGLTR: {
return mt_np_minmax_dispatch<int64_t>(arr);
} break;
case NPY_DOUBLELTR: {
return mt_np_minmax_dispatch<double>(arr);
} break;
default: {
PyErr_SetString(PyExc_RuntimeError, "Unknown error");
return NULL;
}
}
}
static PyObject *get_concurrency(PyObject *self, PyObject *args) {
return Py_BuildValue("I", thread::hardware_concurrency());
}
static PyMethodDef mt_np_Methods[] = {
{"mt_np_minmax", mt_np_minmax, METH_VARARGS, "multi-threaded np min/max"},
{"get_concurrency", get_concurrency, METH_VARARGS,
"retrieve thread::hardware_concurrency()"},
{NULL, NULL, 0, NULL} /* sentinel */
};
static struct PyModuleDef mt_np_module = {PyModuleDef_HEAD_INIT, "mt_np", NULL,
-1, mt_np_Methods};
PyMODINIT_FUNC PyInit_mt_np() { return PyModule_Create(&mt_np_module); }
このファイルでは、PythonとNumPy APIの重要な使用方法があります。詳細については、https://docs.python.org/3/c-api/arg.html#c.PyArg_ParseTupleおよびNumPyを参照してください。:https : //docs.scipy.org/doc/numpy/reference/c-api.array.html。
次に行うことは、distutilsを利用してモジュールをインストールすることです。これにはセットアップファイルが必要です。
# setup.py
from distutils.core import setup,Extension
module = Extension('mt_np', sources = ['mt_np_module.cc'])
setup (name = 'mt_np',
version = '1.0',
description = 'multi-threaded min/max for np arrays',
ext_modules = [module])
最終的にモジュールをインストールするにpython3 setup.py install
は、仮想環境から実行します。
最後に、C ++実装が実際にNumPyの単純な使用よりも優れているかどうかをテストして確認できます。そのために、簡単なテストスクリプトを次に示します。
# timing.py
# compare numpy min/max vs multi-threaded min/max
import numpy as np
import mt_np
import timeit
def normal_min_max(X):
return (np.min(X),np.max(X))
print(mt_np.get_concurrency())
for ssize in np.logspace(3,8,6):
size = int(ssize)
print('********************')
print('sample size:', size)
print('********************')
samples = np.random.normal(0,50,(2,size))
for sample in samples:
print('np:', timeit.timeit('normal_min_max(sample)',
globals=globals(),number=10))
print('mt:', timeit.timeit('mt_np.mt_np_minmax(sample)',
globals=globals(),number=10))
これをすべて実行した結果は次のとおりです。
8
********************
sample size: 1000
********************
np: 0.00012079699808964506
mt: 0.002468645994667895
np: 0.00011947099847020581
mt: 0.0020772050047526136
********************
sample size: 10000
********************
np: 0.00024697799381101504
mt: 0.002037393998762127
np: 0.0002713389985729009
mt: 0.0020942929986631498
********************
sample size: 100000
********************
np: 0.0007130410012905486
mt: 0.0019842900001094677
np: 0.0007540129954577424
mt: 0.0029724110063398257
********************
sample size: 1000000
********************
np: 0.0094779249993735
mt: 0.007134920000680722
np: 0.009129883001151029
mt: 0.012836456997320056
********************
sample size: 10000000
********************
np: 0.09471094200125663
mt: 0.0453535050037317
np: 0.09436299200024223
mt: 0.04188535599678289
********************
sample size: 100000000
********************
np: 0.9537652180006262
mt: 0.3957935369980987
np: 0.9624398809974082
mt: 0.4019058070043684
これらは、スレッドの初期の結果が示す3.5倍のスピードアップとマルチスレッド化を組み込んでいない結果よりもはるかに有望ではありません。私が得た結果はある程度合理的です。スレッド化のオーバーヘッドが予想され、配列が非常に大きくなるまでの時間を支配します。その時点でパフォーマンスの向上はstd::thread::hardware_concurrency
xの増加に近づき始めます。
特にマルチスレッドに関しては、いくつかのNumPyコードに対してアプリケーション固有の最適化の余地は確かにあります。努力する価値があるかどうかは私には明らかではありませんが、確かに良い練習(または何か)のようです。Cythonのような「サードパーティのツール」のいくつかを学ぶことは、時間のより良い使い方かもしれませんが、誰もが知っていると思います。
v = min_max_it->get();
。get
メソッドのブロックの結果は準備ができているし、それを返すまで。ループはそれぞれの未来を通過するため、すべてが完了するまで終了しません。future.get()
私が思いついた最短の方法はこれです:
mn, mx = np.sort(ar)[[0, -1]]
ただし、配列をソートするため、最も効率的ではありません。
別の短い方法は次のとおりです。
mn, mx = np.percentile(ar, [0, 100])
これはより効率的ですが、結果が計算され、floatが返されます。
amax
andと比較するいくつかのテストを実行しますamin