Multiprocessing Intel Neural Computer Stick Access via REST

Problème de tâche unique


Dans la dernière série, j'ai mis Intel Neural Computer Stick 2 sur le réservoir et jeté tous les calculs de réseau neuronal dessus, abandonnant Tensorflow et OpenCV-DNN.

Il y avait un problème que j'ai déjà rencontré à l'époque - l'incapacité de travailler avec NCS à partir de plusieurs processus simultanément. Alors ce n'était pas critique, mais maintenant il est temps de le comprendre.

En essayant de charger un modèle à partir du deuxième processus, OpenVino a commencé à jurer:

E: [ncAPI] [ 926029] resetAll:348 Failed to connect to stalled device, rc: X_LINK_ERROR E: [ncAPI] [ 933282] ncDeviceOpen:672 Failed to find suitable device, rc: X_LINK_DEVICE_NOT_FOUND 

En recherchant sur le forum de support Intel, un problème similaire a été trouvé .

De là, nous avons été transférés à la documentation où il est clairement indiqué:

Un seul appareil ne peut pas être partagé entre plusieurs processus.

Dans cette expérience, vous pouvez réduire et commencer à effectuer un accès multiprocessus.

Service NCS


Il est tout à fait logique de placer le travail direct avec NCS dans un service distinct et de distribuer l'API à tous les clients via lesquels ils travailleront.

En général, c'était censé être un sujet sur le robot et ses nouvelles réalisations en termes de réseaux de neurones. Mais il s'est avéré que le matériel sur l'API NCS est assez attiré par un article séparé.

API NCS


À un niveau bas, l'API NCS est très simple:
- modèle de charge
- commencer le calcul
- obtenir une liste de modèles
- obtenir les propriétés du modèle

Si tout est sans ambiguïté lors du chargement du modèle, l'échappement du calcul est un tenseur contextuel dont le client peut ne pas avoir besoin.

L'obtention d'une liste de modèles est également assez transparente, et à partir des propriétés, la dimension du tenseur d'entrée vient immédiatement à l'esprit - en termes humains, cela signifie qu'il serait bien d'ajuster les images à l'avance aux paramètres du réseau.

De plus, un niveau bas est bon, mais si vous prenez en charge des opérations spécialisées, cela simplifie la logique et les données.

Ainsi, en plus de la base, il existe une tâche de prise en charge de l'API pour la classification, la détection et la segmentation.

Malheureusement, les modèles de segmentation les plus intéressants ne sont pas pris en charge sur le NCS, vous devez donc vous limiter au plus simple, avec la route et le balisage.

Chacune de ces opérations utilise le calcul de base du modèle, mais elles diffèrent dans l'interprétation du tenseur de sortie.

Interface principale


Ainsi, l'interface principale comprend des méthodes:

  • POST: / load - charge le modèle
  • POST: / décharger / $ modèle - supprimer le modèle (du service, il est impossible de le supprimer de l'appareil)
  • GET: / list - obtenir une liste de modèles
  • GET: / input / shape / $ model - découvrez la dimension du tenseur d'entrée
  • POST: / inference / file / $ model - faire un calcul avec les données de la mémoire
  • POST: / inference / path / $ model - faire un calcul avec des données dans le système de fichiers

Voici deux mots sur les données de la mémoire et le système de fichiers:

Si le service NCS et son utilisateur s'exécutent sur le même Raspberry, il est judicieux d'économiser sur le transfert de l'image et de transférer le chemin à la place afin que le service lui-même lise le fichier.
Si l'image est déjà en mémoire (ou n'existe pas dans le système de fichiers), nous la transférons directement à partir de là.

Les tests montrent que le transfert d'octets de la mémoire est beaucoup plus lent (mesure effectuée pour 1000 tentatives):

De la mémoire: 87,5 secondes
Chemin du fichier: 63,3150 secondes

Cependant, ces deux options sont prises en charge pour n'importe quelle méthode, à la fois pour le calcul général et pour les cas spéciaux ci-dessous.

En général, la méthode d'inférence prend une image sous la forme d'un tableau numpy en entrée et produit un tenseur au même format.
L'interprétation de l'échappement est déjà un problème client.
Pour faciliter cette tâche, le service prend en charge des méthodes spécialisées qui extraient des informations importantes sous forme humaine du tenseur de sortie.

Classification


Pour la classification, nous créons une méthode REST distincte, qui convertit le tenseur de sortie en un ensemble de paires (classe, score).

 def get_class_tensor(data): ret = [] thr = 0.01 while(True): cls = np.argmax(data) if data[cls] < thr: break; logging.debug(("Class", cls, "score", data[cls])) c = {"class" : int(cls), "score" : int(100 * data[cls])} data[cls] = 0 ret.append(c) return ret def classify(model_id, img): rc, out = run_inference(model_id, img) if not rc: return rc, out return True, get_class_tensor(out) 

Comme dans le cas d'une sortie normale, deux méthodes sont prises en charge - via un fichier en mémoire et un chemin sur le disque.

  • POST: / classify / file / $ model
  • POST: / classify / path / $ model

Détection


Le tenseur de sortie du détecteur contient un ensemble (classe, probabilité, coordonnées normalisées) et semble plutôt encombrant.

Nous le transformons en une forme compréhensible, tout en supprimant les options improbables:

 def get_detect_from_tensor(t, rows, cols): score = int(100 * t[2]) cls = int(t[1]) left = int(t[3] * cols) top = int(t[4] * rows) right = int(t[5] * cols) bottom = int(t[6] * rows) return {"class" : cls, "score" : score, "x" : left, "y" : top, "w" : (right - left), "h" : (bottom - top)} def build_detection(data, thr, rows, cols): T = {} for t in data: score = t[2] if score > thr: cls = int(t[1]) if cls not in T: T[cls] = get_detect_from_tensor(t, rows, cols) else: a = T[cls] if a["score"] < score: T[cls] = get_detect_from_tensor(t, rows, cols) return list(T.values()) def detect(model_id, img): rc, out = run_inference(model_id, img) if not rc: return rc, out rows, cols = img.shape[:2] return True, build_detection(out[0], 0.01, rows, cols) 

Comme d'habitude, les deux méthodes sont prises en charge:

  • POST: / detect / file / $ model
  • POST: / detect / path / $ model

Segmentation


Le tenseur de segmentation contient des probabilités par classe et même dans la dimension du réseau neuronal.
Convertissez-le simplement en masque de classe:

 def segment(model_id, img): rc, out = run_inference(model_id, img) if not rc: return rc, out out = np.argmax(out, axis=0) out = cv.resize(out, (img.shape[1], img.shape[0]),interpolation=cv.INTER_NEAREST) return True, out 

  • POST: / segment / fichier / $ modèle
  • POST: / segment / chemin / $ modèle

Conclusion


Comme déjà mentionné, j'avais initialement prévu de parler du service dans l'un des chapitres de l'article sur son utilisation, mais il s'est avéré que le volume tirait sur un document séparé.

Encore une fois, j'utilise le service sur le Raspberry Pi, mais il peut être exécuté sur n'importe quelle plate-forme qui a python et OpenVino avec NCS.

Les références


Source: https://habr.com/ru/post/fr469265/


All Articles