2016-05-27 25 views
1

Ich habe eine große 2D NumPy-Array, sagen wir 5M Zeilen und 10 Spalten. Ich möchte ein paar mehr Spalten nach einigen Stateful Logik mit Numba @jitclass implementiert bauen. Nehmen wir an, dass 50 neue Spalten erstellt werden müssen. Die Idee besteht darin, über alle Zeilen mit 10 Spalten in einer Numba @jit-Funktion zu iterieren und für jede Zeile jeden meiner 50 "Filter" anzuwenden, um jeweils eine neue Zelle zu generieren. Also:Anwenden mehrerer Funktionen auf jede Zeile mit Numba

Source1..Source10 Derived1..Derived50 
[array of 10 inputs] [array of 50 outputs] 
    ... 5 million rows like this ... 

Das Problem ist, ich kann nicht eine Liste oder Tupel meiner „Filter“ auf eine @jit(nopython=True) Funktion übergeben, weil sie nicht homogen sind:

@numba.jit(nopython=True) 
def calc_derived(source, derived, filters): 
    for srcidx, src in enumerate(source): 
     for filtidx, filt in enumerate(filters): # doesn't work 
      derived[srcidx,filtidx] = filt.transform(src) 

Die oben nicht arbeiten, weil filters eine Reihe von verschiedenen Klassen sind. Soweit ich das beurteilen kann, ist es nicht gut genug, sie sogar von einer gemeinsamen Basisklasse ableiten zu können.

Ich habe die Möglichkeit, die Reihenfolge der Schleifen zu vertauschen, und die Schleife über die 50 Filter außerhalb der @jit Funktion, aber dies würde bedeuten, dass das gesamte Quell-Dataset 50 Mal statt einmal geladen würde, was ist sehr verschwenderisch.

Haben Sie eine Technik, um die Anforderung von Numba "homogene Listen nur" zu umgehen?

+0

Ich habe mir das kurz angeschaut und konnte nicht genau herausfinden, was das Problem ist. Die Aufzählung über 'source' und' filters' sollte schnell sein, da sie nur 10 oder 50 Elemente lang sind. Wie auch immer, 'src' ist 5M Elemente und so wird die eigentliche Arbeit von' filt.transform' erledigt (wenn ich das richtig verstehe)? Daher hatte ich Probleme mit einem anschaulichen Testfall, bei dem es einen Unterschied machte, wie man die äußeren Schleifen machte - vorausgesetzt, dass 'filt.transform' optimiert ist, ist alles sehr ähnlich ... – DavidW

Antwort

2

Sie ursprünglich über das tun dies mit einer einzigen Funktion gefragt, die über Reihen Schleifen und legt eine Liste von Filtern jede Reihe. Eine Herausforderung bei diesem Ansatz besteht darin, dass Numba die Eingabe/Ausgabe-Typen jeder Funktion kennen oder in der Lage sein muss, daraus abzuleiten. Ich bin mir nicht bewusst, wie man die Anforderungen von Numba in dieser Situation erfüllen kann (was nicht heißen soll, dass es keine gibt). Wenn es einen Weg gäbe, dies zu tun, könnte es eine bessere Lösung sein (und ich würde gerne wissen, was es ist).

Eine Alternative besteht darin, den Code, der die Zeilen durchläuft, in die Filter selbst zu verschieben. Da die Filter Numba-Funktionen sind, sollte dies die Geschwindigkeit beibehalten. Die Funktion, die die Filter anwendet, würde länger numba verwenden; Es würde einfach die Filterliste durchlaufen. Da jedoch die Anzahl der Filter im Verhältnis zur Größe der Datenmatrix klein ist, wird sich die Geschwindigkeit hoffentlich nicht zu sehr auf die Geschwindigkeit auswirken. Da diese Funktion nicht mehr Numba verwendet, ist das Problem der "heterogenen Liste" kein Problem mehr.

Dieser Ansatz funktionierte, als ich es getestet habe (nopython Modus ist in Ordnung). In Testfällen waren Filter, die als numba-Funktionen implementiert wurden, 10-18x schneller als Filter, die als Klassenmethoden implementiert wurden (obwohl Klassen als numba jitclasses implementiert wurden; nicht sicher, was dort vor sich ging). Um ein wenig Modularität zu erreichen, können Filter als Verschlüsse konstruiert werden, so dass ähnliche Filter unter Verwendung verschiedener Parameter definiert werden können.

Zum Beispiel, hier sind Filter, die Summen von Leistungen berechnen. Bei einer Matrix x arbeitet der Filter über die Spalten von x und gibt eine Ausgabe für jede Zeile aus.Es gibt einen Vektor v, wo v[i] = sum(x[i, :] ** power)

# filter constructor 
def sumpow(power): 

    @numba.jit(nopython=True) 
    def run_filter(x): 
     (nrows, ncols) = x.shape 
     result = np.zeros(nrows) 
     for i in range(nrows): 
      for j in range(ncols): 
       result[i] += x[i,j] ** power 
     return result 

    return run_filter 

# define filters 
sum1 = sumpow(1) # sum of elements 
sum2 = sumpow(2) # sum of elements squared 

# apply a single filter 
v = sum2(x) 

Die Funktion mehrere Filter anwenden sieht wie folgt aus. Der Ausgang jedes Filters ist in einer Spalte des Ausgangs gestapelt.

def apply_filters(x, filters): 

    result = np.empty((x.shape[0], len(filters))) 

    for (i, f) in enumerate(filters): 
     result[:, i] = f(x) 

    return result 


y = apply_filters(x, [sum1, sum2]) 

Zeitgebungsergebnisse

  • Datenmatrix: zufällige Einträge aus Standardnormalverteilung gezogen, float64, 5 Millionen Zeilen x 10 Spalten. Alle Methoden wurden mit der gleichen Matrix getestet.
  • Filters: sum2 Filter oben, wiederholt 20x in einer Liste: [sum2, sum2, ...]
  • Timed mit IPython der% timeit Funktion, am besten von 3 Läufen
  • Numerische Ausgänge aller Methoden stimmen
  • Numba Funktionsfilter (wie oben gezeigt): 2.25s
  • Numba jitclass Filter: 28.3s
  • Pure NumPy (Vektorisiert ops verwenden, keine Loops): 8.64s

Ich stelle mir vor Numba könnte für komplexere Filter zu NumPy relativ gewinnen.

+0

Dies ist eine gute und hilfreiche Antwort. Ich habe versucht, es zu vermeiden, mehrmals über den Datensatz zu iterieren, weil er nicht in den Cache passt. Also müssen wir mit Ihrer Lösung aus dem Hauptspeicher N mal neu laden, was bedauerlich ist. Es scheint jedoch, dass dies immer noch schneller als die Alternative mit Python ist. –

0

Um eine homogene Liste zu erhalten, könnten Sie eine Liste der transform Funktionen aller Filter erstellen. In diesem Fall hätten alle Listenelemente den Typ method.

# filters = list of filters 
transforms = [x.transform for x in filters] 

Dann transforms-calc_derived() statt filters passieren.

Edit: Auf meinem System, sieht aus wie numba wird dies akzeptieren, aber nur, wenn nopython = False

+0

Wie Ihr" Edit "bereits sagt, dies funktioniert nicht mit 'nopython = True'. Und wenn ich 'nopython = True' entferne, dauert das Programm 5x länger, um über die Zeilen zu iterieren. Das klappt leider nicht. –

+0

@JohnZwinck Meine Vermutung ist, dass das Problem ist, dass Numba die Eingabe/Ausgabe-Typen für jede Funktion kennen oder in der Lage sein muss, zu schließen, wenn der nopython-Modus verwendet wird. Wenn das stimmt, wäre dies eine Einschränkung für jede Herangehensweise an das Problem (oder es könnte einen Hinweis geben, wie es weitergehen soll). Hoffentlich gibt es einen Weg dahin, aber ich weiß es nicht aus der Nähe. – user20160