web-dev-qa-db-ja.com

NumPy配列を適切にマッピングする

NumPy配列を所定の位置にマップすることは可能ですか?はいの場合、どのように?

与えられたa_values-2D配列-これは、現時点で私のためのトリックを行うコードのビットです:

for row in range(len(a_values)):
    for col in range(len(a_values[0])):
        a_values[row][col] = dim(a_values[row][col])

しかし、それは非常にいので、NumPyのどこかに、次のようなものと同じことをする関数があるはずです。

a_values.map_in_place(dim)

しかし、上記のようなものが存在する場合、私はそれを見つけることができませんでした。

50
mac

かなりのスペースの制約がある場合にのみ、これをインプレースで実行する価値があります。その場合は、フラット化された配列のビューを反復処理することで、コードを少し高速化することができます。 reshapeは新しいビューを返します 可能な場合 であるため、データ自体はコピーされません(元の構造が異常でない限り)。

私は、任意のPython関数の真正なインプレースアプリケーションを達成するより良い方法を知りません。

>>> def flat_for(a, f):
...     a = a.reshape(-1)
...     for i, v in enumerate(a):
...         a[i] = f(v)
... 
>>> a = numpy.arange(25).reshape(5, 5)
>>> flat_for(a, lambda x: x + 5)
>>> a

array([[ 5,  6,  7,  8,  9],
       [10, 11, 12, 13, 14],
       [15, 16, 17, 18, 19],
       [20, 21, 22, 23, 24],
       [25, 26, 27, 28, 29]])

いくつかのタイミング:

>>> a = numpy.arange(2500).reshape(50, 50)
>>> f = lambda x: x + 5
>>> %timeit flat_for(a, f)
1000 loops, best of 3: 1.86 ms per loop

ネストされたループバージョンの約2倍の速度です。

>>> a = numpy.arange(2500).reshape(50, 50)
>>> def nested_for(a, f):
...     for i in range(len(a)):
...         for j in range(len(a[0])):
...             a[i][j] = f(a[i][j])
... 
>>> %timeit nested_for(a, f)
100 loops, best of 3: 3.79 ms per loop

もちろん、ベクトル化は依然として高速なので、コピーを作成できる場合は、それを使用します。

>>> a = numpy.arange(2500).reshape(50, 50)
>>> g = numpy.vectorize(lambda x: x + 5)
>>> %timeit g(a)
1000 loops, best of 3: 584 us per loop

組み込みのufuncを使用してdimを書き換えることができる場合は、vectorizeを使用しないでください。

>>> a = numpy.arange(2500).reshape(50, 50)
>>> %timeit a + 5
100000 loops, best of 3: 4.66 us per loop

numpyは、+=期待どおりの場所にあるため、インプレースアプリケーションでufuncの速度を無料で取得できます。時にはそれはさらに速いです!例については here を参照してください。


ちなみに、編集履歴で表示できるこの質問に対する私の元の答えはばかげており、インデックスをaにベクトル化する必要がありました。 vectorizeの-​​ type-detection mechanism をバイパスするためにファンキーなものを実行する必要があるだけでなく、ネストされたループバージョンと同じくらい遅いことが判明しました。賢さのためにそんなに!

52
senderle

これは、回答とコメントに散らばっている貢献をまとめたもので、質問への回答を受け入れた後に書いたものです。賛成票はいつでも歓迎しますが、この答えに賛成票を投じる場合は、senderleおよび(if(s)he write one)eryksun、who以下の方法を提案しました。

Q:numpy配列を適切にマッピングすることはできますか?
A:はい、ただし単一の配列メソッドではありません。独自のコードを作成する必要があります。

以下は、スレッドで説明されているさまざまな実装を比較するスクリプトです。

import timeit
from numpy import array, arange, vectorize, rint

# SETUP
get_array = lambda side : arange(side**2).reshape(side, side) * 30
dim = lambda x : int(round(x * 0.67328))

# TIMER
def best(fname, reps, side):
    global a
    a = get_array(side)
        t = timeit.Timer('%s(a)' % fname,
                     setup='from __main__ import %s, a' % fname)
    return min(t.repeat(reps, 3))  #low num as in place --> converge to 1

# FUNCTIONS
def mac(array_):
    for row in range(len(array_)):
        for col in range(len(array_[0])):
            array_[row][col] = dim(array_[row][col])

def mac_two(array_):
    li = range(len(array_[0]))
    for row in range(len(array_)):
        for col in li:
            array_[row][col] = int(round(array_[row][col] * 0.67328))

def mac_three(array_):
    for i, row in enumerate(array_):
        array_[i][:] = [int(round(v * 0.67328)) for v in row]


def senderle(array_):
    array_ = array_.reshape(-1)
    for i, v in enumerate(array_):
        array_[i] = dim(v)

def eryksun(array_):
    array_[:] = vectorize(dim)(array_)

def ufunc_ed(array_):
    multiplied = array_ * 0.67328
    array_[:] = rint(multiplied)

# MAIN
r = []
for fname in ('mac', 'mac_two', 'mac_three', 'senderle', 'eryksun', 'ufunc_ed'):
    print('\nTesting `%s`...' % fname)
    r.append(best(fname, reps=50, side=50))
    # The following is for visually checking the functions returns same results
    tmp = get_array(3)
    eval('%s(tmp)' % fname)
    print tmp
tmp = min(r)/100
print('\n===== ...AND THE WINNER IS... =========================')
print('  mac (as in question)       :  %.4fms [%.0f%%]') % (r[0]*1000,r[0]/tmp)
print('  mac (optimised)            :  %.4fms [%.0f%%]') % (r[1]*1000,r[1]/tmp)
print('  mac (slice-assignment)     :  %.4fms [%.0f%%]') % (r[2]*1000,r[2]/tmp)
print('  senderle                   :  %.4fms [%.0f%%]') % (r[3]*1000,r[3]/tmp)
print('  eryksun                    :  %.4fms [%.0f%%]') % (r[4]*1000,r[4]/tmp)
print('  slice-assignment w/ ufunc  :  %.4fms [%.0f%%]') % (r[5]*1000,r[5]/tmp)
print('=======================================================\n')

少なくとも私のシステムでは、上記のスクリプトの出力は次のとおりです。

  mac (as in question)       :  88.7411ms [74591%]
  mac (optimised)            :  86.4639ms [72677%]
  mac (slice-assignment)     :  79.8671ms [67132%]
  senderle                   :  85.4590ms [71832%]
  eryksun                    :  13.8662ms [11655%]
  slice-assignment w/ ufunc  :  0.1190ms [100%]

ご覧のとおり、numpyのufuncを使用すると、速度が2倍以上、ほぼ3桁増加しますそれぞれ2番目に良い選択肢と2番目に悪い選択肢と比較します。

ufuncの使用がオプションではない場合、以下は他の選択肢のみの比較です。

  mac (as in question)       :  91.5761ms [672%]
  mac (optimised)            :  88.9449ms [653%]
  mac (slice-assignment)     :  80.1032ms [588%]
  senderle                   :  86.3919ms [634%]
  eryksun                    :  13.6259ms [100%]

HTH!

44
mac

なぜnumpy実装とout_トリックを使用しないのですか?

from numpy import array, arange, vectorize, rint, multiply, round as np_round 

def fmilo(array_):
    np_round(multiply(array_ ,0.67328, array_), out=array_)

得た:

===== ...AND THE WINNER IS... =========================
  mac (as in question)       :  80.8470ms [130422%]
  mac (optimised)            :  80.2400ms [129443%]
  mac (slice-assignment)     :  75.5181ms [121825%]
  senderle                   :  78.9380ms [127342%]
  eryksun                    :  11.0800ms [17874%]
  slice-assignment w/ ufunc  :  0.0899ms [145%]
  fmilo                      :  0.0620ms [100%]
=======================================================
3
fabrizioM

ufuncが不可能な場合は、cythonの使用を検討する必要があります。 numpy配列の特定の使用を統合し、大幅に高速化するのは簡単です。

2
LBarret

これは、Macの記事の更新版であり、Python 3.x、および numba および numpy.frompyfunc を追加して実現されています。 。

numpy.frompyfunc は任意のpython関数を取り、numpy.arrayにキャストされると、関数を要素ごとに適用する関数を返します。
ただし、配列のデータ型がオブジェクトに変更されるため、適切に配置されず、この配列に対する将来の計算は遅くなります。
この欠点を避けるため、テストでは numpy.ndarray.astype が呼び出され、データ型がintに返されます。

サイドノートとして:
NumbaはPythonの基本ライブラリに含まれていないため、テストする場合は外部からダウンロードする必要があります。このテストでは、実際には何も行いません。@ jit(nopython = True)で呼び出された場合、エラーメッセージが表示されます。そこには何も最適化できないと言っています。ただし、numbaは機能的なスタイルで記述されたコードを高速化できることが多いため、整合性のために含まれています。

import timeit
from numpy import array, arange, vectorize, rint, frompyfunc
from numba import autojit

# SETUP
get_array = lambda side : arange(side**2).reshape(side, side) * 30
dim = lambda x : int(round(x * 0.67328))

# TIMER
def best(fname, reps, side):
    global a
    a = get_array(side)
    t = timeit.Timer('%s(a)' % fname,
                     setup='from __main__ import %s, a' % fname)
    return min(t.repeat(reps, 3))  #low num as in place --> converge to 1

# FUNCTIONS
def mac(array_):
    for row in range(len(array_)):
        for col in range(len(array_[0])):
            array_[row][col] = dim(array_[row][col])

def mac_two(array_):
    li = range(len(array_[0]))
    for row in range(len(array_)):
        for col in li:
            array_[row][col] = int(round(array_[row][col] * 0.67328))

def mac_three(array_):
    for i, row in enumerate(array_):
        array_[i][:] = [int(round(v * 0.67328)) for v in row]


def senderle(array_):
    array_ = array_.reshape(-1)
    for i, v in enumerate(array_):
        array_[i] = dim(v)

def eryksun(array_):
    array_[:] = vectorize(dim)(array_)

@autojit
def numba(array_):
    for row in range(len(array_)):
        for col in range(len(array_[0])):
            array_[row][col] = dim(array_[row][col])


def ufunc_ed(array_):
    multiplied = array_ * 0.67328
    array_[:] = rint(multiplied)

def ufunc_frompyfunc(array_):
    udim = frompyfunc(dim,1,1)
    array_ = udim(array_)
    array_.astype("int")

# MAIN
r = []
totest = ('mac', 'mac_two', 'mac_three', 'senderle', 'eryksun', 'numba','ufunc_ed','ufunc_frompyfunc')
for fname in totest:
    print('\nTesting `%s`...' % fname)
    r.append(best(fname, reps=50, side=50))
    # The following is for visually checking the functions returns same results
    tmp = get_array(3)
    eval('%s(tmp)' % fname)
    print (tmp)
tmp = min(r)/100
results = list(Zip(totest,r))
results.sort(key=lambda x: x[1])

print('\n===== ...AND THE WINNER IS... =========================')
for name,time in results:
    Out = '{:<34}: {:8.4f}ms [{:5.0f}%]'.format(name,time*1000,time/tmp)
    print(Out)
print('=======================================================\n')



そして最後に、結果:

===== ...AND THE WINNER IS... =========================
ufunc_ed                          :   0.3205ms [  100%]
ufunc_frompyfunc                  :   3.8280ms [ 1194%]
eryksun                           :   3.8989ms [ 1217%]
mac_three                         :  21.4538ms [ 6694%]
senderle                          :  22.6421ms [ 7065%]
mac_two                           :  24.6230ms [ 7683%]
mac                               :  26.1463ms [ 8158%]
numba                             :  27.5041ms [ 8582%]
=======================================================
1
Sanitiy