La commodité et les performances sont généralement inversement corrélées. Si le code est simple à utiliser, il est moins optimisé. Si c’est optimisé, c’est moins pratique. Un code efficace doit se rapprocher des moindres détails de ce qui est réellement exécuté, comment.
Je suis tombé sur un exemple dans notre travail en cours visant à exécuter et optimiser la segmentation cellulaire DeepCell pour la recherche sur le cancer. Le modèle DeepCell AI prédit quels pixels sont les plus susceptibles de se trouver dans une cellule. À partir de là, nous "inondons" les pixels les plus probables, jusqu'à atteindre la bordure de la cellule (en dessous d'un certain seuil).
Une partie de ce processus consiste à combler de petites lacunes à l'intérieur des cellules prédites, ce qui peut se produire pour diverses raisons mais n'est pas biologiquement possible. (Pensez aux trous de beignet, pas à la membrane poreuse d'une cellule.)
L'algorithme de remplissage des trous ressemble à ceci :
Voici un exemple de nombres d'Euler tirés de l'article Wikipédia ; un cercle (juste la partie de ligne) a une caractéristique d'Euler de zéro alors qu'un disque (le cercle "rempli") a la valeur 1.
Nous ne sommes cependant pas ici pour parler de définition ou de calcul des nombres d'Euler. Nous expliquerons en quoi le chemin simple de la bibliothèque pour calculer les nombres d'Euler est assez inefficace.
Tout d'abord. Nous avons remarqué le problème en examinant ce profil à l'aide de Speedscope :
Il montre ~ 32 ms (~ 15 %) dépensés en accessoires régionaux. Cette vue est lourde à gauche, si nous passons en vue chronologique et zoomons, nous obtenons ceci :
(Notez que nous faisons cela deux fois, donc ~16 ms ici et ~16 ms ailleurs, non illustré.)
C'est immédiatement suspect : la partie "intéressante" de la recherche des objets avec find_objects est ce premier ruban, 0,5 ms. Il renvoie une liste de tuples, pas un générateur, donc quand c'est fait, c'est fait. Alors, qu'est-ce qui se passe avec tous les autres trucs ? Nous construisons des objets RegionProperties. Zoomons sur l'un d'entre eux.
Les minuscules éclats (sur lesquels nous ne zoomerons pas) sont des appels __setattr__ personnalisés : les objets RegionProperties prennent en charge l'alias, par exemple si vous définissez l'attribut ConvexArea, il redirige vers un attribut standard area_convex. Même si nous ne l'utilisons pas, nous passons toujours par le convertisseur d'attributs.
De plus : nous n'utilisons même pas la plupart des propriétés calculées dans les propriétés de la région. Nous ne nous soucions que du numéro Euler :
props = regionprops(np.squeeze(label_img.astype('int')), cache=False) for prop in props: if prop.euler_numberà son tour, cela n'utilise que l'aspect le plus basique des propriétés de région : les régions d'image détectées par find_objects (tranches de l'image originale).
Nous avons donc modifié le code en code fill_holes pour simplement contourner la fonction générale regionprops. Au lieu de cela, nous appelons find_objects et transmettons les sous-régions d'image résultantes à la fonction euler_number (et non à la méthode sur un objet RegionProperties).
Voici la pull request : deepcell-imaging#358 Ignorer la construction des accessoires de région
En ignorant l'objet intermédiaire, nous avons obtenu une amélioration décente des performances pour l'opération fill_holes :
Taille de l'image Avant Après Accélération 260 000 pixels 48 ms 40 ms 8 ms (17 %) 140 millions de pixels 15,6 s 11,7s 3,9 s (25 %) Pour une image plus grande, 4s représente environ 3 % de la durée d'exécution globale – pas la majeure partie, mais pas trop mal non plus.
Clause de non-responsabilité: Toutes les ressources fournies proviennent en partie d'Internet. En cas de violation de vos droits d'auteur ou d'autres droits et intérêts, veuillez expliquer les raisons détaillées et fournir une preuve du droit d'auteur ou des droits et intérêts, puis l'envoyer à l'adresse e-mail : [email protected]. Nous nous en occuperons pour vous dans les plus brefs délais.
Copyright© 2022 湘ICP备2022001581号-3