Redigera

Dela via


Aktivera maskininlärningsslutsats på en Azure IoT Edge-enhet

Azure IoT Edge
Azure IoT Hub

AI på gränsen är ett av de mest populära gränsscenarierna. Implementeringar av det här scenariot omfattar bildklassificering, objektidentifiering, kropps-, ansikts- och gestanalys samt bildmanipulering. Den här arkitekturguiden beskriver hur du använder Azure IoT Edge för att stödja dessa scenarier.

Du kan förbättra AI-noggrannheten genom att uppdatera AI-modellen, men i vissa fall är nätverksmiljön för gränsenheter inte bra. I vindkrafts- och oljeindustrin kan utrustning till exempel finnas i öknen eller havet.

IoT Edge-modultvillingar används för att implementera den dynamiskt inlästa AI-modellen. IoT Edge-moduler baseras på Docker. En avbildning för en IoT Edge-modul i en AI-miljö har vanligtvis en storlek på minst 1 GB, så inkrementell uppdatering av AI-modellen är viktigt i ett nätverk med smal bandbredd. Detta är huvudfokus i den här artikeln. Tanken är att skapa en IoT Edge AI-modul som kan läsa in TensorFlow Lite- eller Open Neural Network Exchange-objektidentifieringsmodeller (ONNX). Du kan också aktivera modulen som ett webb-API så att du kan använda den till att gynna andra program eller moduler.

Lösningen som beskrivs i den här artikeln kan hjälpa dig på följande sätt:

  • Aktivera AI-slutsatsdragning på gränsenheter.
  • Minimera nätverkskostnaden för att distribuera och uppdatera AI-modeller på gränsen. Lösningen kan spara pengar åt dig eller dina kunder, särskilt i en nätverksmiljö med begränsad bandbredd.
  • Skapa och hantera en AI-modelllagringsplats på en IoT Edge-enhets lokala lagringsplats.
  • Uppnå nästan noll stilleståndstid när gränsenheten växlar AI-modeller.

TensorFlow är ett varumärke som tillhör Google Inc. Inget godkännande understås av användningen av detta varumärke.

Arkitektur

Diagram som visar en arkitektur som stöder maskininlärningsinferens.

Ladda ned en Visio-fil med den här arkitekturen.

Dataflöde

  1. AI-modellen laddas upp till Azure Blob Storage eller en webbtjänst. Modellen kan vara en förtränad TensorFlow Lite- eller ONNX-modell eller en modell som skapats i Azure Mašinsko učenje. IoT Edge-modulen kan komma åt den här modellen och ladda ned den till gränsenheten senare. Om du behöver bättre säkerhet bör du överväga att använda privata slutpunktsanslutningar mellan Blob Storage och gränsenheten.
  2. Azure IoT Hub synkroniserar enhetsmodultvillingar automatiskt med AI-modellinformation. Synkroniseringen sker även om IoT Edge har varit offline. (I vissa fall är IoT-enheter anslutna till nätverk enligt schemalagda tider per timme, dag eller vecka för att spara ström eller minska nätverkstrafiken.)
  3. Inläsningsmodulen övervakar uppdateringarna av modultvillingarna via API:et. När den identifierar en uppdatering hämtar den SAS-token för maskininlärningsmodellen och laddar sedan ned AI-modellen.
    • Mer information finns i Skapa SAS-token för en container eller blob.
    • Du kan använda egenskapen ExpiresOn för att ange förfallodatum för resurser. Om enheten kommer att vara offline under en längre tid kan du förlänga förfallotiden.
  4. Inläsningsmodulen sparar AI-modellen i den delade lokala lagringen av IoT Edge-modulen. Du måste konfigurera den delade lokala lagringen i JSON-filen för IoT Edge-distribution.
  5. Inläsningsmodulen läser in AI-modellen från lokal lagring via TensorFlow Lite eller ONNX API.
  6. Inläsningsmodulen startar ett webb-API som tar emot det binära fotot via POST-begäran och returnerar resultatet i en JSON-fil.

Om du vill uppdatera AI-modellen kan du ladda upp den nya versionen till Blob Storage och synkronisera enhetsmodultvillingarna igen för en inkrementell uppdatering. Du behöver inte uppdatera hela IoT Edge-modulbilden.

Information om scenario

I den här lösningen används en IoT Edge-modul för att ladda ned en AI-modell och sedan aktivera maskininlärningsinferens. Du kan använda förtränade TensorFlow Lite- eller ONNX-modeller i den här lösningen.

De kommande två avsnitten beskriver några begrepp om moduler för maskininlärningsinferens, TensorFlow Lite och ONNX.

TensorFlow Lite

  • En *.tflite-fil är en förtränad AI-modell. Du kan ladda ned en från TensorFlow.org. Det är en allmän AI-modell som du kan använda i plattformsoberoende program som iOS och Android. Mer information om metadata och associerade fält (till exempel labels.txt) finns i Läsa metadata från modeller.

  • En objektidentifieringsmodell tränas för att identifiera förekomsten och platsen för flera objektklasser. En modell kan till exempel tränas med bilder som innehåller olika fruktbitar, tillsammans med en etikett som anger vilken klass av frukt som de representerar (till exempel äpple) och data som anger var varje objekt visas i bilden.

    När en bild tillhandahålls till modellen matar den ut en lista över de objekt som identifieras, platsen för en avgränsningsruta för varje objekt och en poäng som anger identifieringens konfidens.

  • Om du vill skapa eller anpassa en AI-modell kan du läsa TensorFlow Lite Model Maker.

  • Du kan få fler kostnadsfria förtränade identifieringsmodeller, med olika svarstider och precisionsegenskaper, på Detection Zoo. Varje modell använder indata- och utdatasignaturerna som visas i följande kodexempel.

ONNX

ONNX är ett öppet standardformat för att representera maskininlärningsmodeller. Det stöds av en grupp partner som har implementerat det i många ramverk och verktyg.

  • ONNX har stöd för verktyg för att skapa och distribuera modeller och för att utföra andra uppgifter. Mer information finns i ONNX-verktyg som stöds.
  • Du kan använda ONNX Runtime för att köra förtränade ONNX-modeller. Information om förtränade modeller finns i ONNX Model Zoo.
  • I det här scenariot kan du använda en objektidentifierings- och bildsegmenteringsmodell: Tiny YOLOv3.

ONNX-communityn innehåller verktyg som hjälper dig att skapa och distribuera din djupinlärningsmodell.

Ladda ned tränade AI-modeller

Om du vill ladda ned tränade AI-modeller rekommenderar vi att du använder enhetstvillingar för att ta emot meddelanden när en ny modell är klar. Även om enheten är offline kan meddelandet cachelagras i IoT Hub tills gränsenheten är online igen. Meddelandet synkroniseras automatiskt.

Följande är ett exempel på Python-kod som registrerar meddelanden för enhetstvillingarna och sedan laddar ned AI-modellen i en ZIP-fil. Den utför också ytterligare åtgärder på den nedladdade filen.

Koden utför följande uppgifter:

  1. Ta emot meddelandet enhetstvillingar. Meddelandet innehåller filnamnet, filnedladdningsadressen och MD5-autentiseringstoken. (I filnamnet kan du inkludera versionsinformation, till exempel 1.0.)
  2. Ladda ned AI-modellen som en ZIP-fil till lokal lagring.
  3. Du kan också utföra MD5-kontrollsumma. MD5-verifiering hjälper till att förhindra ZIP-filer som har manipulerats under nätverksöverföringen.
  4. Packa upp ZIP-filen och spara den lokalt.
  5. Skicka ett meddelande till IoT Hub eller ett routningsmeddelande för att rapportera att den nya AI-modellen är klar.
# 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"

            # TensorFlow flite
            # 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 )

Slutsatsdragning

När AI-modellen har laddats ned är nästa steg att använda modellen på gränsenheten. Du kan läsa in modellen dynamiskt och utföra objektidentifiering på gränsenheter. Följande kodexempel visar hur du använder TensorFlow Lite AI-modellen för att identifiera objekt på gränsenheter.

Koden utför följande uppgifter:

  1. Läs in TensorFlow Lite AI-modellen dynamiskt.
  2. Utför bildstandardisering.
  3. Identifiera objekt.
  4. Poäng för beräkningsidentifiering.
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)

Följande är ONNX-versionen av föregående kod. Stegen är mestadels desamma. Den enda skillnaden är hur identifieringspoängen hanteras, eftersom Labelmap utdataparametrarna och modellen skiljer sig.

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)

Om din IoT Edge-enhet innehåller föregående kod och funktioner har gränsenheten AI-bildobjektidentifiering och stöder dynamisk uppdatering av AI-modeller. Om du vill att edge-modulen ska tillhandahålla AI-funktioner till andra program eller moduler via ett webb-API kan du skapa ett webb-API i modulen.

Flask Framework är ett exempel på ett verktyg som du kan använda för att snabbt skapa ett API. Du kan ta emot bilder som binära data, använda en AI-modell för identifiering och sedan returnera resultatet i ett JSON-format. Mer information finns i Flask: Flask-självstudie i Visual Studio Code.

Deltagare

Den här artikeln underhålls av Microsoft. Det har ursprungligen skrivits av följande medarbetare.

Huvudförfattare:

  • Bo Wang | Senior programvarutekniker

Annan deltagare:

Om du vill se icke-offentliga LinkedIn-profiler loggar du in på LinkedIn.

Nästa steg