Ok, bentornato! Poiché sai che distribuirai questo modello tramite Docker in Lambda, ciò determina come dovrebbe essere strutturata la pipeline di inferenza.
È necessario costruire un “gestore”. Di cosa si tratta, esattamente? È semplicemente una funzione che accetta l’oggetto JSON che viene passato a Lambda e restituisce qualunque siano i risultati del modello, sempre in un payload JSON. Quindi, tutto ciò che farà la tua pipeline di inferenza dovrà essere chiamato all’interno di questa funzione.
Nel caso del mio progetto, ho un’intera base di codice di funzioni di ingegneria delle funzionalità: montagne di cose che coinvolgono incorporamenti semantici, un mucchio di aggregazioni, espressioni regolari e altro ancora. Li ho consolidati in a FeatureEngineering
class, che ha un sacco di metodi privati ma solo uno pubblico, feature_eng
. Quindi, a partire dal JSON che viene passato al modello, quel metodo può eseguire tutti i passaggi necessari per portare i dati da “grezzi” a “funzionalità”. Mi piace impostare in questo modo perché elimina molta complessità dalla stessa funzione del gestore. Posso letteralmente semplicemente chiamare:
fe = FeatureEngineering(input=json_object)
processed_features = fe.feature_eng()
E vado alle gare, i miei lineamenti risultano puliti e pronti a partire.
Attenzione: ho scritto test unitari esaustivi su tutti gli aspetti interni di questa classe perché, sebbene sia chiaro scriverlo in questo modo, devo comunque essere estremamente consapevole di eventuali cambiamenti che potrebbero verificarsi sotto il cofano. Scrivi i tuoi test unitari! Se apporti una piccola modifica, potresti non essere in grado di dire immediatamente che hai rotto qualcosa in cantiere finché non sta già causando problemi.
La seconda metà è il lavoro di inferenza e nel mio caso questa è una classe separata. Ho optato per un approccio molto simile, che accetta solo alcuni argomenti.
ps = PredictionStage(features=processed_features)
predictions = ps.predict(
feature_file="feature_set.json",
model_file="classifier",
)
L’inizializzazione della classe accetta il risultato del metodo della classe di ingegneria delle funzionalità, in modo che l’handshake sia chiaramente definito. Quindi il metodo di previsione prende due elementi: il set di funzionalità (un file JSON che elenca tutti i nomi delle funzionalità) e l’oggetto del modello, nel mio caso un classificatore CatBoost che ho già addestrato e salvato. Sto utilizzando il metodo di salvataggio nativo CatBoost, ma qualunque cosa tu usi e qualunque algoritmo del modello usi va bene. Il punto è che questo metodo astrae un mucchio di cose sottostanti e restituisce ordinatamente il file predictions
oggetto, che è ciò che il mio Lambda ti darà quando verrà eseguito.
Quindi, per ricapitolare, la mia funzione di “gestore” è essenzialmente proprio questa:
def lambda_handler(json_object, _context):fe = FeatureEngineering(input=json_object)
processed_features = fe.feature_eng()
ps = PredictionStage(features=processed_features)
predictions = ps.predict(
feature_file="feature_set.json",
model_file="classifier",
)
return predictions.to_dict("records")
Niente di più! Potresti voler aggiungere alcuni controlli per input non validi, in modo che se il tuo Lambda ottiene un JSON vuoto, o un elenco o qualche altra cosa strana, è pronto, ma non è richiesto. Assicurati comunque che il tuo output sia in JSON o in un formato simile (qui sto restituendo un dict).
È tutto fantastico, abbiamo un progetto Poetry con un ambiente completamente definito e tutte le dipendenze, oltre alla possibilità di caricare i moduli che creiamo, ecc. Roba buona. Ma ora dobbiamo tradurlo in un’immagine Docker che possiamo inserire su AWS.
Qui ti mostro uno scheletro del dockerfile per questa situazione. Innanzitutto, utilizziamo AWS per ottenere l’immagine di base corretta per Lambda. Successivamente, dobbiamo impostare la struttura dei file che verrà utilizzata all’interno dell’immagine Docker. Questo potrebbe essere o meno esattamente come quello che hai nel tuo progetto Poesia – il mio no, perché ho un mucchio di spazzatura extra qua e là che non è necessaria per la pipeline di inferenza della produzione, incluso il mio codice di formazione . Devo solo inserire le inferenze in questa immagine, tutto qui.
L’inizio del dockerfile
FROM public.ecr.aws/lambda/python:3.9ARG YOUR_ENV
ENV NLTK_DATA=/tmp
ENV HF_HOME=/tmp
In questo progetto, tutto ciò che copi vivrà in un file /tmp
cartella, quindi se nel tuo progetto sono presenti pacchetti che proveranno a salvare i dati in qualsiasi momento, devi indirizzarli nel posto giusto.
Devi anche assicurarti che Poetry venga installato direttamente nella tua immagine Docker: questo è ciò che farà funzionare correttamente tutte le tue dipendenze attentamente curate. Qui sto impostando la versione e raccontando pip
per installare Poetry prima di andare oltre.
ENV YOUR_ENV=${YOUR_ENV} \
POETRY_VERSION=1.7.1
ENV SKIP_HACK=trueRUN pip install "poetry==$POETRY_VERSION"
Il prossimo problema è assicurarsi che tutti i file e le cartelle utilizzati localmente dal tuo progetto vengano aggiunti correttamente a questa nuova immagine: la copia Docker a volte appiattirà le directory in modo irritante, quindi se lo crei e inizi a vedere problemi di “modulo non trovato”, controlla per fare certo che a te non sta succedendo Suggerimento: aggiungi RUN ls -R
nel dockerfile una volta che è stato tutto copiato per vedere come appare la directory. Sarai in grado di visualizzare tali registri in Docker e potrebbe rivelare eventuali problemi.
Inoltre, assicurati di copiare tutto ciò di cui hai bisogno! Ciò include il file Lambda, i file Poetry, il file dell’elenco delle funzionalità e il modello. Tutto ciò sarà necessario a meno che non li memorizzi altrove, come su S3, e fai in modo che Lambda li scarichi al volo. (Questa è una strategia perfettamente ragionevole per sviluppare qualcosa di simile, ma non quello che stiamo facendo oggi.)
WORKDIR ${LAMBDA_TASK_ROOT}COPY /poetry.lock ${LAMBDA_TASK_ROOT}
COPY /pyproject.toml ${LAMBDA_TASK_ROOT}
COPY /new_package/lambda_dir/lambda_function.py ${LAMBDA_TASK_ROOT}
COPY /new_package/preprocessing ${LAMBDA_TASK_ROOT}/new_package/preprocessing
COPY /new_package/tools ${LAMBDA_TASK_ROOT}/new_package/tools
COPY /new_package/modeling/feature_set.json ${LAMBDA_TASK_ROOT}/new_package
COPY /data/models/classifier ${LAMBDA_TASK_ROOT}/new_package
Abbiamo quasi finito! L’ultima cosa che dovresti fare è installare il tuo ambiente Poetry e quindi impostare il tuo gestore per l’esecuzione. Ci sono un paio di flag importanti qui, incluso --no-dev
che dice a Poetry di non aggiungere nessuno strumento di sviluppo che hai nel tuo ambiente, magari come pytest o black.
La fine del dockerfile
RUN poetry config virtualenvs.create false
RUN poetry install --no-devCMD ( "lambda_function.lambda_handler" )
Questo è tutto, hai il tuo dockerfile! Ora è il momento di costruirlo.
- Assicurati che Docker sia installato e in esecuzione sul tuo computer. Potrebbe volerci un secondo ma non sarà troppo difficile.
- Vai alla directory in cui si trova il tuo dockerfile, che dovrebbe essere il livello più alto del tuo progetto, ed esegui
docker build .
Lascia che Docker faccia il suo dovere e, una volta completata la compilazione, smetterà di restituire messaggi. Puoi vedere nella console dell’applicazione Docker se è stata creata correttamente. - Tornate al terminale e correte
docker image ls
e vedrai la nuova immagine che hai appena creato e avrà un numero ID allegato. - Dal terminale ancora una volta, corri
docker run -p 9000:8080 IMAGE ID NUMBER
con il tuo numero ID del passaggio 3 compilato. Ora la tua immagine Docker inizierà a essere eseguita! - Apri un nuovo terminale (Docker è collegato alla tua vecchia finestra, lascialo lì) e puoi passare qualcosa al tuo Lambda, ora in esecuzione tramite Docker. Personalmente mi piace inserire i miei input in un file JSON, come ad esempio
lambda_cases.json
ed eseguirli in questo modo:
curl -d @lambda_cases.json http://localhost:9000/2015-03-31/functions/function/invocations
Se il risultato finale corrisponde alle previsioni del modello, allora sei pronto per il rock. In caso contrario, controlla gli errori e vedi cosa potrebbe esserci che non va. È probabile che dovrai eseguire un po’ di debug e risolvere alcuni problemi prima che tutto funzioni senza intoppi, ma fa tutto parte del processo.
La fase successiva dipenderà molto dalla configurazione della tua organizzazione e non sono un esperto di devops, quindi dovrò essere un po’ vago. Il nostro sistema utilizza AWS Elastic Container Registry (ECR) per archiviare l’immagine Docker creata e Lambda vi accede da lì.
Quando sarai completamente soddisfatto dell’immagine Docker del passaggio precedente, dovrai crearla ancora una volta, utilizzando il formato seguente. Il primo flag indica la piattaforma che stai utilizzando per Lambda. (Inserisci uno spillo, verrà visualizzato di nuovo più tardi.) L’elemento dopo il flag -t è il percorso in cui vanno le immagini AWS ECR: inserisci il numero di account, la regione e il nome del progetto corretti.
docker build . --platform=linux/arm64 -t accountnumber.dkr.ecr.us-east-1.amazonaws.com/your_lambda_project:latest
Successivamente, dovresti autenticarti su un registro Amazon ECR nel tuo terminale, probabilmente utilizzando il comando aws ecr get-login-password
e utilizzando i flag appropriati.
Infine, puoi inviare la tua nuova immagine Docker fino a ECR:
docker push accountnumber.dkr.ecr.us-east-1.amazonaws.com/your_lambda_project:latest
Se ti sei autenticato correttamente, l’operazione dovrebbe richiedere solo un momento.
C’è ancora un passaggio prima di essere pronto, ovvero la configurazione di Lambda nell’interfaccia utente di AWS. Accedi al tuo account AWS e trova il prodotto “Lambda”.
Apri il menu a sinistra e trova “Funzioni”.
Qui è dove andrai a trovare il tuo progetto specifico. Se non hai ancora configurato un Lambda, premi “Crea funzione” e segui le istruzioni per creare una nuova funzione basata sull’immagine del contenitore.
Se hai già creato una funzione, vai a trovarla. Da lì, tutto ciò che devi fare è premere “Distribuisci nuova immagine”. Indipendentemente dal fatto che si tratti di una funzione completamente nuova o semplicemente di una nuova immagine, assicurati di selezionare la piattaforma che corrisponde a ciò che hai fatto nella build Docker! (Ricordi quella spilla?)
L’ultimo compito, e il motivo per cui ho continuato a spiegare fino a questa fase, è testare la tua immagine nell’effettivo ambiente Lambda. Questo può far emergere bug che non hai riscontrato nei tuoi test locali! Passa alla scheda Test e crea un nuovo test inserendo un corpo JSON che riflette ciò che il tuo modello vedrà in produzione. Esegui il test e assicurati che il tuo modello faccia ciò che è previsto.
Se funziona, allora ce l’hai fatta! Hai distribuito il tuo modello. Congratulazioni!
Tuttavia, ci sono una serie di possibili intoppi che potrebbero verificarsi qui. Ma non farti prendere dal panico, se hai un errore! Ci sono soluzioni.
- Se la tua Lambda esaurisce la memoria, vai alla scheda Configurazioni e aumenta la memoria.
- Se l’immagine non ha funzionato perché troppo grande (10 GB è il massimo), torna alla fase di creazione di Docker e prova a ridurre le dimensioni dei contenuti. Non confezionare file estremamente grandi se il modello può farne a meno. Nel peggiore dei casi, potrebbe essere necessario salvare il modello su S3 e farlo caricare dalla funzione.
- Se hai problemi a navigare in AWS, non sei il primo. Consulta il tuo team IT o Devops per ottenere assistenza. Non commettere errori che costeranno un sacco di soldi alla tua azienda!
- Se hai un altro problema non menzionato, pubblica un commento e farò del mio meglio per consigliarti.
Buona fortuna, buon modellismo!
Fonte: towardsdatascience.com