Hur man distribuerar TensorFlow-modeller till produktion med TF-servering

Introduktion

Att sätta Machine Learning (ML) modeller i produktion har blivit ett populärt, återkommande ämne. Många företag och ramar erbjuder olika lösningar som syftar till att ta itu med denna fråga.

För att ta itu med denna oro släppte Google TensorFlow (TF) Serving i hopp om att lösa problemet med att distribuera ML-modeller till produktion.

Denna bit erbjuder en praktisk handledning om att betjäna ett förutbildat Convolutional Semantic Segmentation Network. I slutet av den här artikeln kommer du att kunna använda TF Serving för att distribuera och göra förfrågningar till en Deep CNN utbildad i TF. Jag kommer också att presentera en översikt över de viktigaste blocken för TF Serving, och jag kommer att diskutera dess API: er och hur det fungerar.

En sak du kommer att märka direkt är att det kräver väldigt lite kod för att faktiskt kunna betjäna en TF-modell. Om du vill följa med självstudien och köra exemplet på din maskin, följ det som det är. Men om du bara vill veta mer om TensorFlow-servering kan du koncentrera dig på de två första avsnitten.

Denna bit betonar en del av det arbete vi gör här på Daitan Group.

TensorFlow Serving Libraries - En översikt

Låt oss ta lite tid att förstå hur TF Serving hanterar hela livscykeln för att servera ML-modeller. Här går vi över (på hög nivå) var och en av huvudbyggstenarna i TF Serving. Målet med detta avsnitt är att ge en mjuk introduktion till TF Serving APIs. För en fördjupad översikt, vänligen gå till dokumentationssidan för TF-servering.

TensorFlow Serving består av några abstraktioner. Dessa abstraktioner implementerar API: er för olika uppgifter. De viktigaste är Servable, Loader, Source och Manager. Låt oss gå igenom hur de interagerar.

I ett nötskal börjar serveringens livscykel när TF-servering identifierar en modell på disken. Källkomponenten tar hand om det. Det ansvarar för att identifiera nya modeller som ska laddas. I praktiken håller det ett öga på filsystemet för att identifiera när en ny modellversion kommer till disken. När den ser en ny version fortsätter den genom att skapa en Loader för den specifika versionen av modellen.

Sammanfattningsvis vet Loader nästan allt om modellen. Det inkluderar hur man laddar det och hur man beräknar modellens nödvändiga resurser, till exempel det begärda RAM- och GPU-minnet. Loader har en pekare till modellen på disken tillsammans med alla nödvändiga metadata för att ladda den. Men det finns en fångst här: Lastaren får inte ladda modellen ännu.

Efter att Loader har skapats skickar källan den till chefen som en aspirerad version.

Efter att ha mottagit modellens aspirerade version fortsätter chefen med serveringsprocessen. Här finns det två möjligheter. Det ena är att den första modellversionen drivs för distribution. I den här situationen kommer chefen att se till att de nödvändiga resurserna är tillgängliga. När de är det ger chefen lastaren tillstånd att ladda modellen.

Det andra är att vi driver en ny version av en befintlig modell. I det här fallet måste chefen konsultera plugin-programmet för versionspolicy innan han går vidare. Versionspolicyn avgör hur processen att ladda en ny modellversion sker.

När vi laddar en ny version av en modell kan vi specifikt välja att bevara (1) tillgänglighet eller (2) resurser. I det första fallet är vi intresserade av att se till att vårt system alltid är tillgängligt för inkommande kunders önskemål. Vi vet att chefen tillåter lastaren att starta den nya grafen med de nya vikterna.

Vid den här tiden har vi två modellversioner laddade samtidigt. Men Manager laddar ner den äldre versionen först efter att laddningen är klar och det är säkert att växla mellan modeller.

Å andra sidan, om vi vill spara resurser genom att inte ha extra buffert (för den nya versionen) kan vi välja att bevara resurser. Det kan vara användbart för mycket tunga modeller att ha lite mellanrum i tillgänglighet i utbyte mot att spara minne.

I slutet, när en klient begär ett handtag för modellen, returnerar chefen ett handtag till Servable.

Med den här översikten är vi inställda på att dyka in i en verklig applikation. I nästa avsnitt beskriver vi hur man tjänar ett Convolutional Neural Network (CNN) med hjälp av TF-servering.

Exportera en modell för servering

Det första steget för att betjäna en ML-modell inbyggd i TensorFlow är att se till att den är i rätt format. För att göra det tillhandahåller TensorFlow klassen SavedModel.

SavedModel är det universella serialiseringsformatet för TensorFlow-modeller. Om du är bekant med TF har du förmodligen använt TensorFlow Saver för att bestå modellens variabler.

TensorFlow Saver erbjuder funktioner för att spara / återställa modellens kontrollpunktsfiler till / från disk. Faktum är att SavedModel slår in TensorFlow Saver och det är tänkt att vara det vanliga sättet att exportera TF-modeller för servering.

SavedModel-objektet har några fina funktioner.

För det första kan du spara mer än en metagraf till ett SavedModel-objekt. Med andra ord tillåter det oss att ha olika grafer för olika uppgifter.

Anta till exempel att du precis har utbildat din modell. I de flesta situationer, för att utföra slutsatser, behöver din graf inte några träningsspecifika operationer. Dessa ops kan innehålla optimeringsvariablerna, inlärningshastighetsschemaläggningstensorer, extra förbehandlings-ops, och så vidare.

Dessutom kanske du vill servera en kvantiserad version av ett diagram för mobil distribution.

I detta sammanhang låter SavedModel dig spara grafer med olika konfigurationer. I vårt exempel skulle vi ha tre olika grafer med motsvarande taggar som "träning", "inferens" och "mobil". Dessa tre grafer skulle också alla dela samma uppsättning variabler - vilket betonar minneseffektivitet.

För inte så länge sedan, när vi ville distribuera TF-modeller på mobila enheter, behövde vi veta namnen på in- och utmatningstensorer för matning och att få data till / från modellen. Detta behov tvingade programmerare att söka efter den tensor de behövde bland alla tensorer i diagrammet. Om tensorerna inte namngavs ordentligt kan uppgiften vara mycket tråkig.

För att göra det enklare erbjuder SavedModel support för SignatureDefs. Sammanfattningsvis definierar SignatureDefs signaturen för en beräkning som stöds av TensorFlow. Den bestämmer rätt in- och utgångstensorer för ett beräkningsdiagram. Enkelt uttryckt, med dessa signaturer kan du ange exakta noder som ska användas för in- och utdata.

För att använda de inbyggda API: erna för servering kräver TF-serving att modellerna innehåller ett eller flera SignatureDefs.

För att skapa sådana signaturer måste vi tillhandahålla definitioner för ingångar, utgångar och önskat metodnamn. Ingångar och utgångar representerar en mappning från sträng till TensorInfo-objekt (mer om det senare). Här definierar vi standardtensorerna för matning och mottagning av data till och från en graf. Den METHOD_NAME parameter riktar en av TF på hög nivå som betjänar API: er.

För närvarande finns det tre serverings-API: er: klassificering, förutsägelse och regression. Varje signaturdefinition matchar ett specifikt RPC API. Classification SegnatureDef används för Classify RPC API. Predict SegnatureDef används för Predict RPC API och vidare.

För klassificeringssignaturen måste det finnas en ingångstensor (för att ta emot data) och minst en av två möjliga utgångstensorer: klasser och / eller poäng. Regression SignatureDef kräver exakt en tensor för inmatning och en annan för utdata. Slutligen möjliggör Predict-signaturen ett dynamiskt antal in- och utmatningstensorer.

Dessutom stöder SavedModel tillgångslagring för fall där initialisering av ops beror på externa filer. Det har också mekanismer för att rensa enheter innan du skapar SavedModel.

Låt oss nu se hur vi kan göra det i praktiken.

Ställa in miljön

Innan vi börjar klona den här TensorFlow DeepLab-v3- implementeringen från Github.

DeepLab är Googles bästa semantiska segmentering ConvNet. I grund och botten tar nätverket en bild som inmatning och matar ut en maskliknande bild som skiljer vissa objekt från bakgrunden.

Denna version utbildades i Pascal VOC-segmenteringsdataset. Således kan den segmentera och känna igen upp till 20 klasser. Om du vill veta mer om Semantic Segmentation och DeepLab-v3, ta en titt på Diving into Deep Convolutional Semantic Segmentation Networks och Deeplab_V3.

Alla filer relaterade till servering finns i: ./deeplab_v3/serving/. Där hittar du två viktiga filer: deeplab_saved_model.py och deeplab_client.ipynb

Innan du går vidare, se till att ladda ner Deeplab-v3 förutbildad modell. Gå till GitHub-förvaret ovan, klicka på länken kontrollpunkter och ladda ner mappen med namnet 16645 / .

I slutändan ska du ha en mapp med namnet tboard_logs / med 16645 / mappen placerad inuti den.

Nu måste vi skapa två virtuella Python-miljöer. En för Python 3 och en annan för Python 2. För varje env, se till att installera nödvändiga beroenden. Du hittar dem i filerna serving_requirements.txt och client_requirements.txt.

Vi behöver två Python-envs eftersom vår modell, DeepLab-v3, utvecklades under Python 3. TensorFlow Serving Python API publiceras dock endast för Python 2. För att exportera modellen och köra TF-servering använder vi Python 3 env . För att köra klientkoden med TF Serving python API använder vi PIP-paketet (endast tillgängligt för Python 2).

Observera att du kan avstå från Python 2-env genom att använda Serving APIs från bazel. Se TF Serving Instalation för mer information.

Med det steget klart, låt oss börja med det som verkligen betyder något.

Hur man gör det

För att använda SavedModel erbjuder TensorFlow en användarvänlig klass på hög nivå som heter SavedModelBuilder. Klassen SavedModelBuilder erbjuder funktioner för att spara flera metadiagram, tillhörande variabler och tillgångar.

Låt oss gå igenom ett löpande exempel på hur man exporterar en Deep Segmentation CNN-modell för servering.

Som nämnts ovan använder vi klassen SavedModelBuilder för att exportera modellen. Det genererar en SavedModel-protokollbuffertfil tillsammans med modellens variabler och tillgångar (om det behövs).

Låt oss dissekera koden.

SavedModelBuilder tar emot (som inmatning) katalogen där modellens data ska sparas. Här är variabeln export_path sammankopplingen av export_path_base och model_version . Som ett resultat sparas olika modellversioner i separata kataloger i mappen export_path_base .

Låt oss säga att vi har en basversion av vår modell i produktion, men vi vill distribuera en ny version av den. Vi har förbättrat modellens noggrannhet och vill erbjuda den här nya versionen till våra kunder.

För att exportera en annan version av samma diagram kan vi bara ställa in FLAGS.model_version till ett högre heltal. Sedan skapas en annan mapp (som innehåller den nya versionen av vår modell) i mappen export_path_base .

Nu måste vi ange in- och utgångstensorer för vår modell. För att göra det använder vi SignatureDefs. Signaturer definierar vilken typ av modell vi vill exportera. Det ger en mappning från strängar (logiska Tensor-namn) till TensorInfo-objekt. Tanken är att klienter istället för att referera till de faktiska tensornamnen för input / output kan hänvisa till de logiska namnen som definieras av signaturerna.

För att betjäna en semantisk segmentering CNN ska vi skapa en förutsäg signatur . Observera att build_signature_def () -funktionen tar kartläggningen för in- och utgångstensorer samt önskat API.

En SignatureDef kräver specifikation av: ingångar, utgångar och metodnamn. Observera att vi förväntar oss tre värden för ingångar - en bild och ytterligare två tensorer som anger dess dimensioner (höjd och bredd). För utgångarna definierade vi bara ett resultat - segmenteringsutmatningsmask.

Observera att strängarna 'bild', 'höjd', 'bredd' och 'segmentering_karta' inte är tensorer. Istället är de logiska namn som hänvisar till den faktiska tensors input_tensor , image_height_tensor och image_width_tensor . Således kan de vara vilken unik sträng du vill.

Kartläggningarna i SignatureDefs relaterar också till TensorInfo protobuf-objekt, inte faktiska tensorer. För att skapa TensorInfo-objekt använder vi verktygsfunktionen: tf.saved_model.utils.build_tensor_info (tensor) .

Nu räcker det. Nu kallar vi funktionen add_meta_graph_and_variables () för att bygga SavedModel-protokollbuffertobjektet. Sedan kör vi Save () -metoden och den kommer att bestå en ögonblicksbild av vår modell till disken som innehåller modellens variabler och tillgångar.

Vi kan nu köra deeplab_saved_model.py för att exportera vår modell.

Om allt gick bra ser du mappen ./serving/versions/1 . Observera att '1' representerar den aktuella versionen av modellen. Inuti varje underkatalog för version ser du följande filer:

  • sparad_modell.pb eller sparad_modell.pbtxt. Detta är den serierade SavedModel-filen. Den innehåller en eller flera grafdefinitioner av modellen samt signaturdefinitionerna.
  • Variabler. Den här mappen innehåller de seriella variablerna i graferna.

Nu är vi redo att lansera vår modellserver. För att göra det, kör:

$ tensorflow_model_server --port=9000 --model_name=deeplab --model_base_path=

Den model_base_path hänvisar till när den exporterade modellen sparades. Vi specificerar inte heller versionsmappen i sökvägen. Modellversionskontrollen hanteras av TF Serving.

Generera kundförfrågningar

Klientkoden är väldigt enkel. Ta en titt på den i: deeplab_client.ipynb.

Först läser vi bilden vi vill skicka till servern och konverterar den till rätt format.

Därefter skapar vi en gRPC-stub. Stubben tillåter oss att ringa fjärrservers metoder. För att göra det instanserar vi beta_create_PredictionService_stub- klassen i prediction_service_pb2- modulen. Vid denna tidpunkt har stubben den nödvändiga logiken för att ringa fjärrprocedurer (från servern) som om de vore lokala.

Nu måste vi skapa och ställa in begäran objekt. Eftersom vår server implementerar TensorFlow Predict API måste vi analysera en Predict-begäran. För att utfärda en Predict-begäran startar vi först PredictRequest- klassen från predict_pb2- modulen. Vi måste också ange parametrarna model_spec.name och model_spec.signature_name . Det namn param är "MODEL_NAME argument som vi definierade när vi lanserade servern. Och signature_name hänvisar till det logiska namnet tilldelats signature_def_map () parameter i add_meta_graph () rutin.

Därefter måste vi leverera indata som definierats i serverns signatur. Kom ihåg att vi på servern definierade ett förutsäg API för att förvänta oss en bild såväl som två skalarer (bildens höjd och bredd). För att mata indata till begäran objektet, TensorFlow tillhandahåller verktyget tf.make_tensor_proto () . Denna metod skapar ett TensorProto-objekt från ett numpy / Python-objekt. Vi kan använda den för att mata bilden och dess dimensioner till begäran objektet.

Det verkar som om vi är redo att ringa servern. För att göra det kallar vi metoden Predict () (med hjälp av stubben) och skickar begäran objektet som ett argument.

För förfrågningar som returnerar ett enda svar stöder gRPC både: synkrona och asynkrona samtal. Således, om du vill göra lite arbete medan begäran behandlas kan vi ringa Predict.future () istället för Predict () .

Nu kan vi hämta och njuta av resultaten.

Hoppas att du gillade den här artikeln. Tack för att du läser!

Om du vill ha mer, kolla in:

Hur du tränar ditt eget FaceID ConvNet med TensorFlow Eager-körning

Ansikten finns överallt - från foton och videor på sociala medier, till konsumenters säkerhetsapplikationer som ... medium.freecodecamp.org Dykning i Deep Convolutional Semantic Segmentation Networks och Deeplab_V3

Deep Convolutional Neural Networks (DCNNs) har uppnått anmärkningsvärd framgång i olika datorvisionsapplikationer ... medium.freecodecamp.org