Progettazione e distribuzione di un’applicazione Python per l’apprendimento automatico (parte 2) |  di Noah Haglund |  Febbraio 2024

 | Intelligenza-Artificiale

Dato che non abbiamo ancora risolto i problemi principali, scaviamo ancora un po’ prima di addentrarci nei dettagli di basso livello. Come affermato da Heroku:

Le applicazioni Web che elaborano contemporaneamente le richieste HTTP in entrata fanno un uso molto più efficiente delle risorse dinamiche rispetto alle applicazioni Web che elaborano solo una richiesta alla volta. Per questo motivo, consigliamo di utilizzare server Web che supportino l’elaborazione di richieste simultanee durante lo sviluppo e l’esecuzione di servizi di produzione.

I framework web Django e Flask dispongono di comodi server web integrati, ma questi server di blocco elaborano solo una singola richiesta alla volta. Se esegui la distribuzione con uno di questi server su Heroku, le tue risorse di prova saranno sottoutilizzate e la tua applicazione non risponderà.

Siamo già in vantaggio utilizzando il multiprocessing dei lavoratori per l’attività ML, ma possiamo fare un ulteriore passo avanti utilizzando Gunicorn:

Gunicorn è un server HTTP puro Python per applicazioni WSGI. Ti consente di eseguire qualsiasi applicazione Python contemporaneamente eseguendo più processi Python all’interno di un singolo banco prova. Fornisce un perfetto equilibrio tra prestazioni, flessibilità e semplicità di configurazione.

Ok, fantastico, ora possiamo utilizzare ancora più processi, ma c’è un problema: ogni nuovo processo di lavoro Gunicorn rappresenterà una copia dell’applicazione, il che significa che anche loro utilizzeranno la RAM di base da ~ 150 MB Inoltre al processo Heroku. Quindi, supponiamo di eseguire l’installazione di gunicorn e ora di inizializzare il processo web di Heroku con il seguente comando:

gunicorn <DJANGO_APP_NAME_HERE>.wsgi:application --workers=2 --bind=0.0.0.0:$PORT

I circa 150 MB di RAM di base nel processo Web si trasformano in circa 300 MB di RAM (utilizzo della memoria di base moltiplicato per # gunicorn lavoratori).

Pur essendo cauti nei confronti delle limitazioni al multithreading di un’applicazione Python, possiamo aggiungere thread anche ai lavoratori utilizzando:

gunicorn <DJANGO_APP_NAME_HERE>.wsgi:application --threads=2 --worker-class=gthread --bind=0.0.0.0:$PORT

Anche con il problema n. 3, possiamo ancora trovare un utilizzo per i thread, poiché vogliamo garantire che il nostro processo web sia in grado di elaborare più di una richiesta alla volta, prestando attenzione all’impronta di memoria dell’applicazione. In questo caso, i nostri thread potrebbero elaborare richieste minuscole garantendo al contempo che l’attività ML sia distribuita altrove.

In ogni caso, utilizzando i lavoratori gunicorn, i thread o entrambi, stiamo impostando la nostra applicazione Python per elaborare più di una richiesta alla volta. Abbiamo più o meno risolto il problema n. 2 incorporando vari modi per implementare la concorrenza e/o la gestione di attività parallele garantendo al tempo stesso che l’attività ML critica della nostra applicazione non si basi su potenziali insidie, come il multithreading, impostandoci su scala e arrivando a la radice del problema n. 3.

Ok, allora che dire del complicato problema n. 1? Alla fine, i processi ML finiranno in genere per gravare sull’hardware in un modo o nell’altro, che si tratti di memoria, CPU e/o GPU. Tuttaviautilizzando un sistema distribuito, la nostra attività ML è integralmente collegata al processo web principale ma gestita in parallelo tramite un lavoratore Celery. Possiamo tracciare l’inizio e la fine dell’attività ML tramite il sedano scelto brokernonché rivedere le metriche in modo più isolato. In questo caso, la riduzione delle configurazioni dei processi di lavoro Celery e Heroku dipende da te, ma è un eccellente punto di partenza per integrare nella tua applicazione un processo ML a lunga esecuzione e ad uso intensivo di memoria.

Ora che abbiamo avuto la possibilità di approfondire e ottenere un quadro generale del sistema che stiamo costruendo, mettiamolo insieme e concentriamoci sui dettagli.

Per tua comodità, ecco il resoconto Ne parlerò in questa sezione.

Per prima cosa inizieremo configurando Django e Django Rest Framework, con le guide di installazione Qui E Qui rispettivamente. Tutti i requisiti per questa app possono essere trovati nel file require.txt del repository (e Detectron2 e Torch saranno creati dalle ruote Python specificate nel Dockerfile, per mantenere piccole le dimensioni dell’immagine Docker).

La parte successiva riguarderà l’impostazione dell’app Django, la configurazione del backend per il salvataggio su AWS S3 e l’esposizione di un endpoint utilizzando DRF, quindi se ti senti già a tuo agio nel farlo, sentiti libero di saltare avanti e andare direttamente al Configurazione e distribuzione delle attività ML sezione.

Configurazione di Django

Vai avanti e crea una cartella per il progetto Django e inseriscila. Attiva l’ambiente virtuale/conda che stai utilizzando, assicurati che Detectron2 sia installato secondo le istruzioni di installazione in Parte 1e installare anche i requisiti.

Emettere il seguente comando in un terminale:

django-admin startproject mltutorial

Questo creerà una directory root del progetto Django denominata “mltutorial”. Vai avanti e inseriscilo per trovare un file Manage.py e una sottodirectory mltutorial (che è l’effettivo pacchetto Python per il tuo progetto).

mltutorial/
manage.py
mltutorial/
__init__.py
settings.py
urls.py
asgi.py
wsgi.py

Apri settings.py e aggiungi ‘rest_framework’, ‘celery’ e ‘storages’ (necessari per boto3/AWS) nell’elenco INSTALLED_APPS per registrare tali pacchetti con il progetto Django.

Nella directory root, creiamo un’app che ospiterà le funzionalità principali del nostro backend. Emetti un altro comando da terminale:

python manage.py startapp docreader

Questo creerà un’app nella directory root chiamata docreader.

Creiamo anche un file in docreader intitolato mltask.py. In esso, definisci una semplice funzione per testare la nostra configurazione che accetta una variabile, file_path, e la stampa:

def mltask(file_path):
return print(file_path)

Venendo ora alla struttura, le app Django utilizzano il file Controller vista modello (MVC), design pattern, che definisce il Modello in modelli.pyVisualizza dentro views.pye Controller in Django Modelli E urls.py. Utilizzando Django Rest Framework, includeremo la serializzazione in questa pipeline, che fornirà un modo per serializzare e deserializzare le strutture dative native di Python in rappresentazioni come JSON. Pertanto, la logica dell’applicazione per l’esposizione di un endpoint è la seguente:

Database ← → models.py ← → serializers.py ← → views.py ← → urls.py

In docreader/models.py, scrivi quanto segue:

from django.db import models
from django.dispatch import receiver
from .mltask import mltask
from django.db.models.signals import(
post_save
)

class Document(models.Model):
title = models.CharField(max_length=200)
file = models.FileField(blank=False, null=False)

@receiver(post_save, sender=Document)
def user_created_handler(sender, instance, *args, **kwargs):
mltask(str(instance.file.file))

Ciò configura un modello di documento che richiederà un titolo e un file per ogni voce salvata nel database. Una volta salvato, il decoratore @receiver ascolta un segnale post-salvataggio, il che significa che il modello specificato, Documento, è stato salvato nel database. Una volta salvato, user_created_handler() prende il campo del file dell’istanza salvata e lo passa a quella che diventerà la nostra funzione di Machine Learning.

Ogni volta che vengono apportate modifiche a models.py, sarà necessario eseguire i due comandi seguenti:

python manage.py makemigrations
python manage.py migrate

Andando avanti, crea un file serializers.py in docreader, consentendo la serializzazione e la deserializzazione del titolo del documento e dei campi del file. Scrivici dentro:

from rest_framework import serializers
from .models import Document

class DocumentSerializer(serializers.ModelSerializer):
class Meta:
model = Document
fields = (
'title',
'file'
)

Successivamente in views.py, dove possiamo definire le nostre operazioni CRUD, definiamo la possibilità di creare, oltre che elencare, voci di documenti utilizzando visioni generiche (che essenzialmente ti consente di scrivere rapidamente visualizzazioni utilizzando un’astrazione di modelli di visualizzazione comuni):

from django.shortcuts import render
from rest_framework import generics
from .models import Document
from .serializers import DocumentSerializer

class DocumentListCreateAPIView(
generics.ListCreateAPIView):

queryset = Document.objects.all()
serializer_class = DocumentSerializer

Infine, aggiorna urls.py in mltutorial:

from django.contrib import admin
from django.urls import path, include

urlpatterns = (
path("admin/", admin.site.urls),
path('api/', include('docreader.urls')),
)

E crea urls.py nella directory dell’app docreader e scrivi:

from django.urls import path

from . import views

urlpatterns = (
path('create/', views.DocumentListCreateAPIView.as_view(), name='document-list'),
)

Ora siamo tutti pronti per salvare una voce di documento, con campi titolo e campo, nell’endpoint /api/create/, che chiamerà mltask() dopo il salvataggio! Quindi, proviamolo.

Per aiutare a visualizzare i test, registriamo il nostro modello Document con Django interfaccia di amministrazionecosì possiamo vedere quando è stata creata una nuova voce.

In docreader/admin.py scrivi:

from django.contrib import admin
from .models import Document

admin.site.register(Document)

Crea un utente che possa accedere all’interfaccia di amministrazione di Django utilizzando:

python manage.py createsuperuser

Ora testiamo l’endpoint che abbiamo esposto.

Per fare ciò senza un frontend, esegui il server Django e vai su Postman. Invia la seguente richiesta POST con un file PDF allegato:

Se controlliamo i nostri log Django, dovremmo vedere stampato il percorso del file, come specificato nella chiamata alla funzione post save mltask().

Configurazione di AWS

Noterai che il PDF è stato salvato nella directory principale del progetto. Assicuriamoci che qualsiasi supporto venga invece salvato su AWS S3, preparando la nostra app per la distribuzione.

Vai a Consolle S3 (e crea un account e ottieni il tuo account Chiavi di accesso e segrete se non l’hai già fatto). Crea un nuovo bucket, qui lo chiameremo “djangomltest”. Aggiorna le autorizzazioni per garantire che il bucket sia pubblico per i test (e ripristinalo, se necessario, per la produzione).

Ora configuriamo Django per funzionare con AWS.

Aggiungi il tuo model_final.pth, addestrato Parte 1nella directory del docreader. Crea un file .env nella directory root e scrivi quanto segue:

AWS_ACCESS_KEY_ID = <Add your Access Key Here>
AWS_SECRET_ACCESS_KEY = <Add your Secret Key Here>
AWS_STORAGE_BUCKET_NAME = 'djangomltest'

MODEL_PATH = './docreader/model_final.pth'

Aggiorna settings.py per includere le configurazioni AWS:

import os
from dotenv import load_dotenv, find_dotenv
load_dotenv(find_dotenv())

# AWS
AWS_ACCESS_KEY_ID = os.environ('AWS_ACCESS_KEY_ID')
AWS_SECRET_ACCESS_KEY = os.environ('AWS_SECRET_ACCESS_KEY')
AWS_STORAGE_BUCKET_NAME = os.environ('AWS_STORAGE_BUCKET_NAME')

#AWS Config
AWS_DEFAULT_ACL = 'public-read'
AWS_S3_CUSTOM_DOMAIN = f'{AWS_STORAGE_BUCKET_NAME}.s3.amazonaws.com'
AWS_S3_OBJECT_PARAMETERS = {'CacheControl': 'max-age=86400'}

#Boto3
STATICFILES_STORAGE = 'mltutorial.storage_backends.StaticStorage'
DEFAULT_FILE_STORAGE = 'mltutorial.storage_backends.PublicMediaStorage'

#AWS URLs
STATIC_URL = f'https://{AWS_S3_CUSTOM_DOMAIN}/static/'
MEDIA_URL = f'https://{AWS_S3_CUSTOM_DOMAIN}/media/'

Facoltativamente, con AWS che serve i nostri file statici e multimediali, dovrai eseguire il seguente comando per servire risorse statiche all’interfaccia di amministrazione utilizzando S3:

python manage.py collectstatic

Se eseguiamo nuovamente il server, il nostro amministratore dovrebbe apparire uguale a come sarebbe con i nostri file statici serviti localmente.

Ancora una volta, eseguiamo il server Django e testiamo l’endpoint per assicurarci che il file sia ora salvato su S3.

Configurazione e distribuzione delle attività ML

Con Django e AWS configurati correttamente, impostiamo il nostro processo ML in mltask.py. Poiché il file è lungo, vedere il repository Qui come riferimento (con commenti aggiunti per aiutare a comprendere i vari blocchi di codice).

Ciò che è importante vedere è che Detectron2 viene importato e il modello viene caricato solo quando viene richiamata la funzione. In questo caso, chiameremo la funzione solo tramite un’attività Celery, assicurando che la memoria utilizzata durante l’inferenza sarà isolata dal processo di lavoro Heroku.

Quindi, infine, impostiamo Celery e poi distribuiamolo su Heroku.

In mltutorial/_init__.py scrivi:

from .celery import app as celery_app
__all__ = ('celery_app',)

Crea celery.py nella directory mltutorial e scrivi:

import os

from celery import Celery

# Set the default Django settings module for the 'celery' program.
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mltutorial.settings')

# We will specify Broker_URL on Heroku
app = Celery('mltutorial', broker=os.environ('CLOUDAMQP_URL'))

# Using a string here means the worker doesn't have to serialize
# the configuration object to child processes.
# - namespace='CELERY' means all celery-related configuration keys
# should have a `CELERY_` prefix.
app.config_from_object('django.conf:settings', namespace='CELERY')

# Load task modules from all registered Django apps.
app.autodiscover_tasks()

@app.task(bind=True, ignore_result=True)
def debug_task(self):
print(f'Request: {self.request!r}')

Infine, crea un task.py in docreader e scrivi:

from celery import shared_task
from .mltask import mltask

@shared_task
def ml_celery_task(file_path):
mltask(file_path)
return "DONE"

Questa attività di Celery, ml_celery_task(), dovrebbe ora essere importata in models.py e utilizzata con il segnale post salvataggio invece della funzione mltask estratta direttamente da mltask.py. Aggiorna il blocco del segnale post_save come segue:

@receiver(post_save, sender=Document)
def user_created_handler(sender, instance, *args, **kwargs):
ml_celery_task.delay(str(instance.file.file))

E per testare Celery, schieriamoci!

Nella directory root del progetto, includi un file Dockerfile e un file heroku.yml, entrambi specificati nel file pronti contro termine. La cosa più importante è modificare il file heroku.yml comandi ti consentirà di configurare il processo web gunicorn e il processo di lavoro Celery, che può aiutare a mitigare ulteriormente potenziali problemi.

Crea un account Heroku e crea una nuova app chiamata “mlapp” e gitignora il file .env. Quindi inizializza git nella directory root del progetto e modifica lo stack dell’app Heroku in contenitore (per poterlo distribuire utilizzando Docker):

$ heroku login
$ git init
$ heroku git:remote -a mlapp
$ git add .
$ git commit -m "initial heroku commit"
$ heroku stack:set container
$ git push heroku master

Una volta inviato, dobbiamo solo aggiungere le nostre variabili env nell’app Heroku.

Vai alle impostazioni nell’interfaccia online, scorri verso il basso fino a Config Vars, fai clic su Reveal Config Vars e aggiungi ogni riga elencata nel file .env.

Potresti aver notato che in celery.py era specificata una variabile CLOUDAMQP_URL. Dobbiamo fornire un Celery Broker su Heroku, per il quale esistono diverse opzioni. userò CloudAMQP che ha un livello gratuito. Vai avanti e aggiungilo alla tua app. Una volta aggiunta, la variabile d’ambiente CLOUDAMQP_URL verrà inclusa automaticamente in Config Vars.

Infine, testiamo il prodotto finale.

Per monitorare le richieste, eseguire:

$ heroku logs --tail

Emetti un’altra richiesta POST di Postman all’URL dell’app Heroku sull’endpoint /api/create/. Vedrai arrivare la richiesta POST, Celery riceverà l’attività, caricherà il modello e inizierà a eseguire le pagine:

Continueremo a vedere “In esecuzione per la pagina…” fino alla fine del processo e potrai controllare il bucket AWS S3 mentre viene eseguito.

Congratulazioni! Ora hai distribuito ed eseguito un backend Python utilizzando il Machine Learning come parte di una coda di attività distribuite in esecuzione in parallelo al processo web principale!

Come accennato, ti consigliamo di modificare heroku.yml comandi per incorporare fili di gunicorn e/o processi lavorativi e mettere a punto il sedano. Per ulteriori approfondimenti, ecco a ottimo articolo sulla configurazione di gunicorn per soddisfare le esigenze della tua app, una in cui approfondire Sedano per la produzionee un altro per esplorare Celery pool di lavoratoriper aiutarti a gestire correttamente le tue risorse.

Buona programmazione!

Se non diversamente specificato, tutte le immagini utilizzate in questo articolo sono dell’autore

Fonte: towardsdatascience.com

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *