辞書のリストがあります。 np.vectorizeを使用して、リスト内の各辞書の辞書要素を変更する関数を適用しようとしています。 結果は、vectorizeが最初の要素に2回作用していることを示しているようです。 これは修正できるバグですか?(おそらく、ベクトル化が最初の要素のタイプをチェックするという事実に関連していますか?)以下にいくつかの例と出力を示します。
辞書を変更しない単純なテストケース:
def fcn1(x):
return x['b']
a = [{'b': 1} for _ in range(3) ]
print(a)
print(np.vectorize(fcn1)(a))
print(a, '\n\n')
出力:
[{'b': 1}, {'b': 1}, {'b': 1}]
[1 1 1]
[{'b': 1}, {'b': 1}, {'b': 1}]
次に、辞書を変更して、関数が最初の要素に2回適用されていることを確認します。
def fcn2(x):
x['b'] += 1
return x['b']
a = [{'b': 1} for _ in range(3) ]
print(a)
print(np.vectorize(fcn2)(a))
print(a, '\n\n')
出力:
[{'b': 1}, {'b': 1}, {'b': 1}]
[3 2 2]
[{'b': 3}, {'b': 2}, {'b': 2}]
別の変更を試して、バグの一貫性を確認してください。
def fcn3(x):
x['b'] *= 2
return x['b']
a = [{'b': 1} for _ in range(3) ]
print(a)
print(np.vectorize(fcn3)(a))
print(a, '\n\n')
出力:
[{'b': 1}, {'b': 1}, {'b': 1}]
[4 2 2]
[{'b': 4}, {'b': 2}, {'b': 2}]
実際に戻り値を提供しなくても同じことができます(これが私のユースケースで使用しようとしている方法です):
def fcn4(x):
x['b'] += 1
a = [{'b': 1} for _ in range(3) ]
print(a)
np.vectorize(fcn4)(a)
print(a, '\n\n')
出力:
[{'b': 1}, {'b': 1}, {'b': 1}]
[{'b': 3}, {'b': 2}, {'b': 2}]
ちなみに、長さ3のリストについては特別なことは何もありません。これを変更すると、最初の要素のみが二重に変更された場合の同じ動作を確認できます。
Numpyバージョン1.11.3と1.12.0の両方を使用して動作を確認しました
編集:
「最初の要素でタイプをテストする」問題も確認する回避策を見つけました。 otypes
引数を指定した場合、最初の要素は2回ヒットしません。
def fcn(x):
x['b'] += 1
return x['b']
a = [{'b': 1} for _ in range(3)]
print a
print np.vectorize(fcn, otypes=[dict])(a)
print a, '\n\n'
出力:
[{'b': 1}, {'b': 1}, {'b': 1}]
[2 2 2]
[{'b': 2}, {'b': 2}, {'b': 2}]
最初のアイテムタイプをチェックすることが問題の原因であることを確認するように見える新しいテストで投稿を編集しました
より単純なテストケース:
a = np.array([1, 2, 3])
def f(x):
print('got', x)
return x
fv = np.vectorize(f)
y = fv(a)
与える:
got 1
got 1
got 2
got 3
「otypesが指定されていない場合、最初の引数を使用した関数の呼び出しを使用して出力の数が決定されます。キャッシュがTrueの場合、この呼び出しの結果はキャッシュされ、関数が2回呼び出されないようにします。ただし、実装するにはキャッシュ、元の関数をラップする必要があります。これにより、後続の呼び出しが遅くなるため、関数が高価な場合にのみこれを実行してください。」
https://docs.scipy.org/doc/numpy/reference/generated/numpy.vectorize.html
したがって、これは十分に文書化された動作ですが、直感に反しているように見えます。 したがって、バグ修正というよりは、おそらくそれ以上の機能強化です。
ああ、明らかに私はすべてのドキュメントを十分に読んでいませんでした。 これで二重呼び出しの問題は修正されますが、実行が遅くなります。 それで、パフォーマンスを維持しながら、この二重呼び出しを防ぐ方法はないと思いますか?
たとえば、 vectorize
クラス関数_get_ufunc_and_otypes()
numpy/lib/function_base.py
では、次の行を変更できると素朴に思います。
inputs = [arg.flat[0] for arg in args]
outputs = func(*inputs)
に:
#earlier
import copy
...
inputs = copy.deepcopy([arg.flat[0] for arg in args])
outputs = func(*inputs)
そして、キャッシュを使用したり、otypeを指定したりする必要はありませんが、実際の可変要素を2回ヒットすることは避けたいと思います。 しかし、私は、キャッシュと比較してどれだけのパフォーマンスヒットが得られるかについては知りません。
キャッシュの動作は、関数が可変オブジェクトを変更する場合を考えているのではなく、高価な関数の実行時間を考慮して設計されていると感じています。 関数の実行時間が長いことを念頭に置いたキャッシュのパフォーマンスへの影響なしに、可変オブジェクトの変更に対応できる可能性があると思います。
非常に大きなオブジェクトの配列に対するベクトル化された操作の場合、関数を適用するよりも、最初の要素のディープコピーを作成する方が実際には計算量が多いと思います。 したがって、ユーザーがotype
指定しない場合は、その機能を使用することをお勧めしますが、ドキュメントで、大きな配列にotype
を指定しない限り、パフォーマンスが低下する可能性があると述べています。オブジェクト。 @ eric-wieserどう思いますか?
copy.deepcopy()
は多くの入力で安全ではないため、残念ながらそれは実行可能なオプションではありません。
これを修正する唯一の方法は、 vectorize
のコアを書き直すことです。これは、現在numpy.frompyfunc
を使用して、実際にnumpyufuncを作成しています。 np.apply_along_axis
使用するfrompyfunc
が構築するufuncで実行するように)。
最も参考になるコメント
「otypesが指定されていない場合、最初の引数を使用した関数の呼び出しを使用して出力の数が決定されます。キャッシュがTrueの場合、この呼び出しの結果はキャッシュされ、関数が2回呼び出されないようにします。ただし、実装するにはキャッシュ、元の関数をラップする必要があります。これにより、後続の呼び出しが遅くなるため、関数が高価な場合にのみこれを実行してください。」
https://docs.scipy.org/doc/numpy/reference/generated/numpy.vectorize.html
したがって、これは十分に文書化された動作ですが、直感に反しているように見えます。 したがって、バグ修正というよりは、おそらくそれ以上の機能強化です。