Bequemlichkeit und Leistung korrelieren typischerweise umgekehrt. Wenn der Code einfach zu verwenden ist, ist er weniger optimiert. Wenn es optimiert ist, ist es weniger praktisch. Effizienter Code muss näher an die wesentlichen Details dessen herangehen, was tatsächlich läuft und wie.
Ich bin bei unserer laufenden Arbeit zur Durchführung und Optimierung der DeepCell-Zellsegmentierung für die Krebsforschung auf ein Beispiel gestoßen. Das DeepCell-KI-Modell sagt voraus, welche Pixel sich am wahrscheinlichsten in einer Zelle befinden. Von dort aus „fluten wir“ die wahrscheinlichsten Pixel, bis wir den Zellrand erreichen (unterhalb eines bestimmten Schwellenwerts).
Ein Teil dieses Prozesses besteht darin, kleine Lücken innerhalb der vorhergesagten Zellen zu glätten, was aus verschiedenen Gründen passieren kann, aber biologisch nicht möglich ist. (Denken Sie an Donut-Löcher, nicht an die poröse Membran einer Zelle.)
Der Lochfüllalgorithmus funktioniert folgendermaßen:
Hier ist ein Beispiel für Euler-Zahlen aus dem Wikipedia-Artikel; Ein Kreis (nur der Linienteil) hat eine Euler-Charakteristik von Null, während eine Scheibe (der „ausgefüllte“ Kreis) den Wert 1 hat.
Wir sind jedoch nicht hier, um über die Definition oder Berechnung von Euler-Zahlen zu sprechen. Wir werden darüber sprechen, dass der einfache Weg der Bibliothek zur Berechnung von Euler-Zahlen ziemlich ineffizient ist.
Das Wichtigste zuerst. Das Problem ist uns aufgefallen, als wir dieses Profil mit Speedscope betrachtet haben:
Es zeigt, dass ca. 32 ms (~15 %) in Regionprops verbracht wurden. Diese Ansicht ist linkslastig. Wenn wir zur Zeitleistenansicht gehen und hineinzoomen, erhalten wir Folgendes:
(Beachten Sie, dass wir dies zweimal tun, daher ~16 ms hier und ~16 ms anderswo, nicht gezeigt.)
Das ist sofort verdächtig: Der „interessante“ Teil beim Finden der Objekte mit find_objects ist der erste Splitter, 0,5 ms. Es gibt eine Liste von Tupeln zurück, keinen Generator. Wenn es also fertig ist, ist es fertig. Was ist mit all den anderen Sachen los? Wir erstellen RegionProperties-Objekte. Lassen Sie uns einen davon vergrößern.
Die winzigen Splitter (auf die wir nicht näher eingehen) sind benutzerdefinierte __setattr__-Aufrufe: Die RegionProperties-Objekte unterstützen Aliasing. Wenn Sie beispielsweise das Attribut ConvexArea festlegen, wird auf ein Standardattribut „area_convex“ umgeleitet. Auch wenn wir davon keinen Gebrauch machen, durchlaufen wir dennoch den Attributkonverter.
Außerdem: Wir nutzen die meisten der in den Regionseigenschaften berechneten Eigenschaften gar nicht. Uns interessiert nur die Euler-Zahl:
props = regionprops(np.squeeze(label_img.astype('int')), cache=False) for prop in props: if prop.euler_number < 1:
Dabei wird wiederum nur der grundlegendste Aspekt der Regionseigenschaften verwendet: die von find_objects erkannten Bildregionen (Teile des Originalbilds).
Also haben wir den Code in den Code „fill_holes“ geändert, um einfach die Allzweckfunktion „regionprops“ zu umgehen. Stattdessen rufen wir find_objects auf und übergeben die resultierenden Bildunterregionen an die Funktion euler_number (nicht die Methode für ein RegionProperties-Objekt).
Hier ist die Pull-Anfrage: deepcell-imaging#358 Regionprops-Konstruktion überspringen
Durch das Überspringen des Zwischenobjekts haben wir eine deutliche Leistungsverbesserung für die Operation „fill_holes“ erzielt:
Image size | Before | After | Speedup |
---|---|---|---|
260k pixels | 48ms | 40ms | 8ms (17%) |
140M pixels | 15.6s | 11.7s | 3.9s (25%) |
Für das größere Bild machen 4 Sekunden etwa 3 % der Gesamtlaufzeit aus – nicht der größte Teil davon, aber auch nicht allzu schäbig.
Das obige ist der detaillierte Inhalt vonLeistungsfalle: allgemeine Bibliotheken und Hilfsobjekte. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!