Numpy: BUG: Die Funktionen von np.vectorize () arbeiten zweimal mit dem ersten Element (wie zu sehen ist, wenn die Funktion ein veränderbares Objekt ändert).

Erstellt am 8. März 2017  ·  7Kommentare  ·  Quelle: numpy/numpy

Ich habe eine Liste von Wörterbüchern. Ich versuche, mit np.vectorize eine Funktion anzuwenden, die Wörterbuchelemente für jedes Wörterbuch in der Liste ändert. Die Ergebnisse scheinen zu zeigen, dass Vektorisierung zweimal auf das erste Element einwirkt. Ist dies ein Fehler, der behoben werden kann? (Möglicherweise im Zusammenhang mit der Tatsache, dass der Prüftyp für das erste Element vektorisiert wird?) Nachfolgend einige Beispielfälle und Ausgaben:

Ein einfacher Testfall ohne Wörterbuchänderungen:

def fcn1(x):
    return x['b']
a = [{'b': 1} for _ in range(3) ]
print(a)
print(np.vectorize(fcn1)(a))
print(a, '\n\n')

Ausgabe:

[{'b': 1}, {'b': 1}, {'b': 1}]
[1 1 1]
[{'b': 1}, {'b': 1}, {'b': 1}]

Ändern Sie nun das Wörterbuch und stellen Sie sicher, dass die Funktion zweimal auf das erste Element angewendet wird:

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

Ausgabe:

[{'b': 1}, {'b': 1}, {'b': 1}]
[3 2 2]
[{'b': 3}, {'b': 2}, {'b': 2}]  

Versuchen Sie eine andere Modifikation, um die Konsistenz des Fehlers zu überprüfen:

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

Ausgabe:

[{'b': 1}, {'b': 1}, {'b': 1}]
[4 2 2]
[{'b': 4}, {'b': 2}, {'b': 2}]    

Sie können dasselbe tun, ohne tatsächlich einen Rückgabewert anzugeben (so versuche ich, ihn in meinem Anwendungsfall zu verwenden):

def fcn4(x):
    x['b'] += 1
a = [{'b': 1} for _ in range(3) ]
print(a)
np.vectorize(fcn4)(a)
print(a, '\n\n')

Ausgabe:

[{'b': 1}, {'b': 1}, {'b': 1}]
[{'b': 3}, {'b': 2}, {'b': 2}]

Übrigens ist eine Liste mit der Länge 3 nichts Besonderes. Sie können dies ändern und sehen, dass nur das erste Element doppelt modifiziert wird.

Ich habe das Verhalten mit Numpy Version 1.11.3 und 1.12.0 bestätigt

BEARBEITEN:
Ich habe eine Problemumgehung gefunden, die auch bestätigt, dass es sich um ein Problem handelt, bei dem der Typ auf das erste Element getestet wird. Wenn Sie das Argument otypes angeben, wird das erste Element nicht zweimal getroffen:

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'

Ausgabe:

[{'b': 1}, {'b': 1}, {'b': 1}]
[2 2 2]
[{'b': 2}, {'b': 2}, {'b': 2}]
00 - Bug

Hilfreichster Kommentar

"Wenn otypes nicht angegeben ist, wird ein Aufruf der Funktion mit dem ersten Argument verwendet, um die Anzahl der Ausgaben zu bestimmen. Die Ergebnisse dieses Aufrufs werden zwischengespeichert, wenn der Cache True ist, um zu verhindern, dass die Funktion zweimal aufgerufen wird Im Cache muss die ursprüngliche Funktion verpackt werden, wodurch nachfolgende Aufrufe verlangsamt werden. Tun Sie dies also nur, wenn Ihre Funktion teuer ist. "
https://docs.scipy.org/doc/numpy/reference/generated/numpy.vectorize.html

Es ist also ein gut dokumentiertes Verhalten, scheint aber nicht intuitiv zu sein. Vielleicht ist es eher eine Verbesserung als eine Fehlerbehebung.

Alle 7 Kommentare

Ich habe gerade meinen Beitrag mit einem neuen Test bearbeitet, der zu bestätigen scheint, dass der erste Elementtyp IS ist, der das Problem verursacht

Einfacherer Testfall:

a = np.array([1, 2, 3])
def f(x):
    print('got', x)
    return x
fv = np.vectorize(f)
y = fv(a)

Gibt:

got 1
got 1
got 2
got 3

"Wenn otypes nicht angegeben ist, wird ein Aufruf der Funktion mit dem ersten Argument verwendet, um die Anzahl der Ausgaben zu bestimmen. Die Ergebnisse dieses Aufrufs werden zwischengespeichert, wenn der Cache True ist, um zu verhindern, dass die Funktion zweimal aufgerufen wird Im Cache muss die ursprüngliche Funktion verpackt werden, wodurch nachfolgende Aufrufe verlangsamt werden. Tun Sie dies also nur, wenn Ihre Funktion teuer ist. "
https://docs.scipy.org/doc/numpy/reference/generated/numpy.vectorize.html

Es ist also ein gut dokumentiertes Verhalten, scheint aber nicht intuitiv zu sein. Vielleicht ist es eher eine Verbesserung als eine Fehlerbehebung.

Ah, klar, ich habe nicht alle Dokumente gut genug gelesen. Dadurch wird das Problem von Doppelaufrufen behoben, jedoch auf Kosten einer langsameren Ausführung. Ich denke also, es gibt keine Möglichkeit, dieses Doppelanrufen zu verhindern, während die Leistung erhalten bleibt.

Zum Beispiel würde ich in numpy/lib/function_base.py in der vectorize Klassenfunktion _get_ufunc_and_otypes() naiv denken, dass Sie diese Zeilen ändern könnten:

inputs = [arg.flat[0] for arg in args]
outputs = func(*inputs)

zu:

#earlier
import copy

...

inputs = copy.deepcopy([arg.flat[0] for arg in args])
outputs = func(*inputs)

Und dann müssten Sie nicht das Cacheing verwenden oder otypes angeben, aber ich denke, Sie vermeiden es, die tatsächlichen veränderlichen Elemente zweimal zu treffen. Aber ich weiß nicht, wie viel Leistung ein Treffer im Vergleich zum Cacheing bringen würde.

Ich habe nur das Gefühl, dass das Caching-Verhalten unter Berücksichtigung der teuren Ausführungszeit von Funktionen entwickelt wurde, ohne an den Fall einer Funktion zu denken, die ein veränderliches Objekt modifiziert. Ich würde denken, dass es möglicherweise möglich ist, veränderbare Objektmodifikationen ohne den Leistungseinbruch des Caching zu berücksichtigen, der eine lange Ausführungszeit für Funktionen im Auge hatte.

Ich denke, dass es für vektorisierte Operationen an Arrays sehr großer Objekte möglicherweise rechenintensiver ist, eine tiefe Kopie des ersten Elements zu erstellen, als es kosten würde, die Funktion anzuwenden. Daher ist es möglicherweise eine gute Idee, über diese Funktionalität zu verfügen, wenn der Benutzer nicht otype angibt. In der Dokumentation wird jedoch angegeben, dass die Leistung möglicherweise beeinträchtigt wird, es sei denn, für große Arrays ist otype angegeben Objekte. @ eric-wieser was denkst du?

copy.deepcopy() ist bei vielen Eingaben nicht sicher, daher ist dies leider keine praktikable Option.

Die einzige Möglichkeit, dies zu beheben, besteht darin, den Kern von vectorize neu zu schreiben, der derzeit numpy.frompyfunc , um einen tatsächlich numpy ufunc zu erstellen. Es müsste eine alternative innere Schleife erstellt werden, ähnlich wie wir sie für np.apply_along_axis . Um Leistungseinbußen zu vermeiden, müssten wir leider die Schleife in C ausführen (wie frompyfunc in dem von ihm erstellten Ufunc).

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen