Abilitare l'inferenza di Machine Learning in un dispositivo Azure IoT Edge

Azure IoT Edge
Hub IoT Azure

L'intelligenza artificiale sul perimetro è uno degli scenari perimetrali più diffusi. Le implementazioni di questo scenario includono la classificazione delle immagini, il rilevamento degli oggetti, il corpo, il viso e l'analisi dei movimenti e la manipolazione delle immagini. Questa guida all'architettura descrive come usare Azure IoT Edge per supportare questi scenari.

È possibile migliorare l'accuratezza dell'intelligenza artificiale aggiornando il modello di intelligenza artificiale, ma in alcuni scenari l'ambiente di rete perimetrale dei dispositivi non è ottimale. Ad esempio, nelle industrie eoliche e petrolifere, le attrezzature potrebbero trovarsi nel deserto o nell'oceano.

I moduli gemelli IoT Edge vengono usati per implementare il modello di intelligenza artificiale caricato dinamicamente. I moduli IoT Edge sono basati su Docker. Un'immagine per un modulo IoT Edge in un ambiente di intelligenza artificiale ha in genere una dimensione di almeno 1 GB, quindi l'aggiornamento incrementale del modello di intelligenza artificiale è importante in una rete a larghezza di banda ridotta. Questa considerazione è l'obiettivo principale di questo articolo. L'idea è creare un modulo di intelligenza artificiale IoT Edge in grado di caricare LiteRT (in precedenza TensorFlow Lite) o modelli di rilevamento oggetti ONNX (Open Neural Network Exchange). È anche possibile abilitare il modulo come API Web in modo che sia possibile usarlo per trarre vantaggio da altre applicazioni o moduli.

La soluzione descritta in questo articolo può essere utile in questi modi:

  • Abilitare l'inferenza di intelligenza artificiale nei dispositivi perimetrali.
  • Ridurre al minimo il costo di rete della distribuzione e dell'aggiornamento dei modelli di intelligenza artificiale sul perimetro. La soluzione può risparmiare denaro per l'utente o i clienti, in particolare in un ambiente di rete a larghezza di banda ridotta.
  • Creare e gestire un repository di modelli di intelligenza artificiale nell'archiviazione locale di un dispositivo IoT Edge.
  • Ottenere quasi zero tempi di inattività quando il dispositivo perimetrale commuta i modelli di intelligenza artificiale.

TensorFlow e LiteRT sono un marchio di Google Inc. Nessuna approvazione è implicita nell'uso di questo marchio.

Architettura

Diagramma che mostra un'architettura che supporta l'inferenza di Machine Learning.

Scaricare un file di Visio di questa architettura.

Flusso di dati

  1. Il modello di intelligenza artificiale viene caricato in Archiviazione BLOB di Azure o in un servizio Web. Il modello può essere un modello LiteRT o ONNX con training preliminare o un modello creato in Azure Machine Learning. Il modulo IoT Edge può accedere a questo modello e scaricarlo nel dispositivo perimetrale in un secondo momento. Se è necessaria una maggiore sicurezza, è consigliabile usare connessioni endpoint private tra l'archiviazione BLOB e il dispositivo perimetrale.
  2. hub IoT di Azure sincronizza automaticamente i moduli gemelli del dispositivo con le informazioni sul modello di intelligenza artificiale. La sincronizzazione si verifica anche se IoT Edge è offline. In alcuni casi, i dispositivi IoT sono connessi alle reti orarie, giornaliere o settimanali pianificate per risparmiare energia o ridurre il traffico di rete.
  3. Il modulo loader monitora gli aggiornamenti dei moduli gemelli tramite l'API. Quando rileva un aggiornamento, ottiene il token di firma di accesso condiviso del modello di Machine Learning e quindi scarica il modello di intelligenza artificiale.
  4. Il modulo loader salva il modello di intelligenza artificiale nell'archiviazione locale condivisa del modulo IoT Edge. È necessario configurare l'archiviazione locale condivisa nel file JSON di distribuzione IoT Edge.
  5. Il modulo loader carica il modello di intelligenza artificiale dall'archiviazione locale tramite l'API LiteRT o ONNX.
  6. Il modulo loader avvia un'API Web che riceve la foto binaria tramite richiesta POST e restituisce i risultati in un file JSON.

Per aggiornare il modello di intelligenza artificiale, è possibile caricare la nuova versione in Archiviazione BLOB e sincronizzare nuovamente i moduli gemelli del dispositivo per un aggiornamento incrementale. Non è necessario aggiornare l'intera immagine del modulo IoT Edge.

Dettagli dello scenario

In questa soluzione viene usato un modulo IoT Edge per scaricare un modello di intelligenza artificiale e quindi abilitare l'inferenza di Machine Learning. In questa soluzione è possibile usare modelli LiteRT o ONNX con training preliminare.

LiteRT

  • Un .tflite file è un modello di intelligenza artificiale con training preliminare. È possibile scaricarlo da TensorFlow.org. Si tratta di un modello di intelligenza artificiale generico che è possibile usare in applicazioni multipiattaforma come iOS e Android. LiteRT supporta i modelli di TensorFlow, PyTorch, JAX e Keras. Per altre informazioni sui metadati e sui campi associati , labels.txtad esempio , vedere Leggere i metadati dai modelli.

  • Viene eseguito il training di un modello di rilevamento oggetti per rilevare la presenza e la posizione di più classi di oggetti. Ad esempio, è possibile eseguire il training di un modello con immagini che contengono vari pezzi di frutta, insieme a un'etichetta che specifica la classe di frutta che rappresentano (ad esempio, apple) e i dati che specificano dove ogni oggetto viene visualizzato nell'immagine.

    Quando un'immagine viene fornita al modello, restituisce un elenco degli oggetti rilevati, la posizione di un rettangolo di selezione per ogni oggetto e un punteggio che indica l'attendibilità del rilevamento.

  • Per creare o ottimizzare un modello di intelligenza artificiale personalizzato, vedere LiteRT Model Maker.

  • È possibile ottenere più modelli di rilevamento pre-training gratuiti, con varie caratteristiche di latenza e precisione, in Zoo di rilevamento. Ogni modello usa le firme di input e output illustrate negli esempi di codice seguenti.

ONNX

ONNX è un formato open-standard per la rappresentazione di modelli di Machine Learning. È supportata da una community di partner che l'hanno implementata in molti framework e strumenti.

  • ONNX supporta gli strumenti per la creazione e la distribuzione di modelli e per l'esecuzione di altre attività. Per altre informazioni, vedere Strumenti ONNX supportati.
  • È possibile usare ONNX Runtime per eseguire modelli con training preliminare ONNX. Per informazioni sui modelli con training preliminare, vedere ONNX Model Zoo.
  • Per questo scenario, è possibile usare un modello di rilevamento oggetti e segmentazione di immagini: Tiny YOLOv3.

La community ONNX offre strumenti che consentono di creare e distribuire il modello di Deep Learning.

Scaricare i modelli di intelligenza artificiale sottoposti a training

Per scaricare i modelli di intelligenza artificiale sottoposti a training, è consigliabile usare i dispositivi gemelli per ricevere notifiche quando un nuovo modello è pronto. Anche se il dispositivo è offline, il messaggio può essere memorizzato nella cache in hub IoT finché il dispositivo perimetrale non torna online. Il messaggio verrà sincronizzato automaticamente.

Di seguito è riportato un esempio di codice Python che registra le notifiche per i dispositivi gemelli e quindi scarica il modello di intelligenza artificiale in un file ZIP. Esegue anche ulteriori operazioni sul file scaricato.

Il codice esegue queste attività:

  1. Ricevere la notifica dei dispositivi gemelli. La notifica include il nome file, l'indirizzo di download del file e il token di autenticazione MD5. Nel nome del file è possibile includere informazioni sulla versione, ad esempio 1.0.
  2. Scaricare il modello di intelligenza artificiale come file ZIP nell'archiviazione locale.
  3. Facoltativamente, eseguire il checksum MD5. La verifica MD5 consente di evitare che i file ZIP manomessi durante la trasmissione di rete.
  4. Decomprimere il file ZIP e salvarlo in locale.
  5. Inviare una notifica a hub IoT o a un messaggio di routing per segnalare che il nuovo modello di intelligenza artificiale è pronto.
# define behavior for receiving a twin patch
async def twin_patch_handler(patch):
    try:
        print( "######## The data in the desired properties patch was: %s" % patch)
        if "FileName" in patch:
            FileName = patch["FileName"]
        if "DownloadUrl" in patch:
            DownloadUrl = patch["DownloadUrl"]
        if "ContentMD5" in patch:
            ContentMD5 = patch["ContentMD5"]
        FilePath = "/iotedge/storage/" + FileName

        # download AI model
        r = requests.get(DownloadUrl)
        print ("######## download AI Model Succeeded.")
        ffw = open(FilePath, 'wb')
        ffw.write(r.content)
        ffw.close()
        print ("######## AI Model File: " + FilePath)

        # MD5 checksum
        md5str = content_encoding(FilePath)
        if md5str == ContentMD5:
            print ( "######## New AI Model MD5 checksum succeeded")
            # decompressing the ZIP file
            unZipSrc = FilePath
            targeDir = "/iotedge/storage/"
            filenamenoext = get_filename_and_ext(unZipSrc)[0]
            targeDir = targeDir + filenamenoext
            unzip_file(unZipSrc,targeDir)

            # ONNX
            local_model_path = targeDir + "/tiny-yolov3-11.onnx"
            local_labelmap_path = targeDir + "/coco_classes.txt"

            # LiteRT
            # local_model_path = targeDir + "/ssd_mobilenet_v1_1_metadata_1.tflite"
            # local_labelmap_path = targeDir + "/labelmap.txt"

            # message to module
            if client is not None:
                print ( "######## Send AI Model Info AS Routing Message")
                data = "{\"local_model_path\": \"%s\",\"local_labelmap_path\": \"%s\"}" % (filenamenoext+"/tiny-yolov3-11.onnx", filenamenoext+"/coco_classes.txt")
                await client.send_message_to_output(data, "DLModelOutput")
                # update the reported properties
                reported_properties = {"LatestAIModelFileName": FileName }
                print("######## Setting reported LatestAIModelName to {}".format(reported_properties["LatestAIModelFileName"]))
                await client.patch_twin_reported_properties(reported_properties)
        else:
            print ( "######## New AI Model MD5 checksum failed")

    except Exception as ex:
        print ( "Unexpected error in twin_patch_handler: %s" % ex )

Inferenza

Dopo aver scaricato il modello di intelligenza artificiale, il passaggio successivo consiste nell'usare il modello nel dispositivo perimetrale. È possibile caricare dinamicamente il modello ed eseguire il rilevamento degli oggetti nei dispositivi perimetrali. L'esempio di codice seguente illustra come usare il modello di intelligenza artificiale LiteRT per rilevare gli oggetti nei dispositivi perimetrali.

Il codice esegue queste attività:

  1. Caricare dinamicamente il modello di intelligenza artificiale LiteRT.
  2. Eseguire la standardizzazione delle immagini.
  3. Rilevare gli oggetti.
  4. Calcolare i punteggi di rilevamento.
class InferenceProcedure():

    def detect_object(self, imgBytes):

        results = []
        try:
            model_full_path = AI_Model_Path.Get_Model_Path()
            if(model_full_path == ""):
                raise Exception ("PLEASE SET AI MODEL FIRST")
            if '.tflite' in model_full_path:
                interpreter = tf.lite.Interpreter(model_path=model_full_path)
                interpreter.allocate_tensors()
                input_details = interpreter.get_input_details()
                output_details = interpreter.get_output_details()
                input_shape = input_details[0]['shape']

                # bytes to numpy.ndarray
                im_arr = np.frombuffer(imgBytes, dtype=np.uint8)
                img = cv2.imdecode(im_arr, flags=cv2.IMREAD_COLOR)
                im_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
                im_rgb = cv2.resize(im_rgb, (input_shape[1], input_shape[2]))
                input_data = np.expand_dims(im_rgb, axis=0)

                interpreter.set_tensor(input_details[0]['index'], input_data)
                interpreter.invoke()
                output_data = interpreter.get_tensor(output_details[0]['index'])
                detection_boxes = interpreter.get_tensor(output_details[0]['index'])
                detection_classes = interpreter.get_tensor(output_details[1]['index'])
                detection_scores = interpreter.get_tensor(output_details[2]['index'])
                num_boxes = interpreter.get_tensor(output_details[3]['index'])

                label_names = [line.rstrip('\n') for line in open(AI_Model_Path.Get_Labelmap_Path())]
                label_names = np.array(label_names)
                new_label_names = list(filter(lambda x : x != '???', label_names))

                for i in range(int(num_boxes[0])):
                    if detection_scores[0, i] > .5:
                        class_id = int(detection_classes[0, i])
                        class_name = new_label_names[class_id]
                        # top, left, bottom, right
                        results_json = "{'Class': '%s','Score': '%s','Location': '%s'}" % (class_name, detection_scores[0, i],detection_boxes[0, i])
                        results.append(results_json)
                        print(results_json)
        except Exception as e:
            print ( "detect_object unexpected error %s " % e )
            raise

        # return results
        return json.dumps(results)

Di seguito è riportata la versione ONNX del codice precedente. I passaggi sono per lo più uguali. L'unica differenza è il modo in cui viene gestito il punteggio di rilevamento, perché i parametri di output del Labelmap modello e sono diversi.

class InferenceProcedure():

    def letterbox_image(self, image, size):
        '''resize image with unchanged aspect ratio using padding'''
        iw, ih = image.size
        w, h = size
        scale = min(w/iw, h/ih)
        nw = int(iw*scale)
        nh = int(ih*scale)

        image = image.resize((nw,nh), Image.BICUBIC)
        new_image = Image.new('RGB', size, (128,128,128))
        new_image.paste(image, ((w-nw)//2, (h-nh)//2))
        return new_image

    def preprocess(self, img):
        model_image_size = (416, 416)
        boxed_image = self.letterbox_image(img, tuple(reversed(model_image_size)))
        image_data = np.array(boxed_image, dtype='float32')
        image_data /= 255.
        image_data = np.transpose(image_data, [2, 0, 1])
        image_data = np.expand_dims(image_data, 0)
        return image_data

    def detect_object(self, imgBytes):
        results = []
        try:
            model_full_path = AI_Model_Path.Get_Model_Path()
            if(model_full_path == ""):
                raise Exception ("PLEASE SET AI MODEL FIRST")
            if '.onnx' in model_full_path:

                # input
                image_data = self.preprocess(imgBytes)
                image_size = np.array([imgBytes.size[1], imgBytes.size[0]], dtype=np.float32).reshape(1, 2)

                labels_file = open(AI_Model_Path.Get_Labelmap_Path())
                labels = labels_file.read().split("\n")

                # Loading ONNX model
                print("loading Tiny YOLO...")
                start_time = time.time()
                sess = rt.InferenceSession(model_full_path)
                print("loaded after", time.time() - start_time, "s")

                input_name00 = sess.get_inputs()[0].name
                input_name01 = sess.get_inputs()[1].name
                pred = sess.run(None, {input_name00: image_data,input_name01:image_size})

                boxes = pred[0]
                scores = pred[1]
                indices = pred[2]

                results = []
                out_boxes, out_scores, out_classes = [], [], []
                for idx_ in indices[0]:
                    out_classes.append(idx_[1])
                    out_scores.append(scores[tuple(idx_)])
                    idx_1 = (idx_[0], idx_[2])
                    out_boxes.append(boxes[idx_1])
                    results_json = "{'Class': '%s','Score': '%s','Location': '%s'}" % (labels[idx_[1]], scores[tuple(idx_)],boxes[idx_1])
                    results.append(results_json)
                    print(results_json)

        except Exception as e:
            print ( "detect_object unexpected error %s " % e )
            raise

        # return results
        return json.dumps(results)

Se il dispositivo IoT Edge incorpora il codice e le funzionalità precedenti, il dispositivo perimetrale ha il rilevamento degli oggetti immagine di intelligenza artificiale e supporta l'aggiornamento dinamico dei modelli di intelligenza artificiale. Se si vuole che il modulo perimetrale fornisca funzionalità di intelligenza artificiale ad altre applicazioni o moduli tramite un'API Web, è possibile creare un'API Web nel modulo.

Il framework Flask è un esempio di uno strumento che è possibile usare per creare rapidamente un'API. È possibile ricevere immagini come dati binari, usare un modello di intelligenza artificiale per il rilevamento e quindi restituire i risultati in un formato JSON. Per altre informazioni, vedere Flask: Flask Tutorial in Visual Studio Code.

Collaboratori

Questo articolo viene gestito da Microsoft. Originariamente è stato scritto dai seguenti contributori.

Autore principale:

  • Bo Wang | Senior Software Engineer

Altro collaboratore:

Per visualizzare i profili LinkedIn non pubblici, accedere a LinkedIn.

Passaggi successivi