Sliter du med AI eller fullstack-utvikling? Ekspertene våre er her for å veilede deg: skreddersydde råd, teknisk integrasjon og mer. Ta kontakt på [email protected].

Produksjonsklar maskinlærings-API for naturlig språkbehandling for klassifisering med FastAPI og transformatorer

Den første versjonen av FastAPI ble lansert i slutten av 2018. i slutten av 2018, og den har blitt stadig mer brukt i mange applikasjoner i produksjon siden da. (se FastAPIs nettsted). Det er hva vi bruker bak panseret på NLP Cloud. Det er en flott måte å enkelt og effektivt betjene våre hundrevis av hundrevis av Natural Language Processing-modeller, for entitetsekstraksjon (NER), tekstklassifisering, sentimentanalyse, spørsmål, oppsummering, ... svar, oppsummering ... Vi fant ut at FastAPI er en flott måte å betjene transformatorbaserte dyp modeller for dyp læring.

I denne artikkelen tenkte vi at det ville være interessant å vise deg hvordan vi implementerer et API for naturlig språkbehandling basert på på Hugging Face-transformatorer med FastAPI.

Hvorfor bruke FastAPI?

Før FastAPI hadde vi i hovedsak brukt Django Rest Framework for våre Python API-er, men vi ble raskt interessert i FastAPI av følgende grunner interessert i FastAPI av følgende grunner:

Disse gode ytelsene gjør FastAPI perfekt egnet for maskinlærings-API-er som serverer transformatorbaserte modeller som vår.

Installer FastAPI

For at FastAPI skal fungere, kobler vi den til Uvicorn ASGI-serveren, som er den moderne måten å håndtere asynkrone Python-forespørsler med asyncio. håndtere asynkrone Python-forespørsler med asyncio. Du kan enten bestemme deg for å installere FastAPI med Uvicorn manuelt eller laste ned et ferdig Docker-bilde. La oss vise manuell installasjon først:

pip install fastapi[all]

Da kan du begynne med det:

uvicorn main:app

Sebastián Ramírez, skaperen av FastAPI, tilbyr flere ferdige Docker-bilder som gjør det veldig enkelt å bruke FastAPI i produksjon. enkelt å bruke FastAPI i produksjon. Bildet Uvicorn + Gunicorn + FastAPI bildet utnytter Gunicorn for å bruke flere prosesser parallelt. (se bildet her). Til slutt, takket være Uvicorn kan du håndtere flere FastAPI-instanser i den samme Python-prosessen, og takket være Gunicorn du kan gyte flere Python-prosesser.

FastAPI-applikasjonen din starter automatisk når du starter Docker-containeren med følgende: docker run.

Det er viktig å lese dokumentasjonen av disse Docker-bildene ordentlig, da det er noen innstillinger du kanskje ønsker å justere, som for eksempel antall parallelle prosesser opprettet av Gunicorn. Som standard bildet gyter så mange prosesser som antall CPU-kjerner på maskinen din. Men i tilfelle krevende maskinlæringsmodeller som Natural Language Processing Transformers, kan det raskt føre til titalls GB minne som brukes. En strategi ville være å utnytte Gunicorn-alternativet (--preload), for å laste inn modellen din bare én gang i minnet og dele den mellom alle FastAPI Python-prosessene. Et annet alternativ ville være å begrense antall Gunicorn-prosesser. Begge har fordeler og ulemper, men det er utenfor denne artikkelens omfanget av denne artikkelen.

Enkel FastAPI + Transformers API for tekstklassifisering

Tekstklassifisering er prosessen med å bestemme hva en tekst handler om (Rom? Virksomhet? Mat?...). Mer informasjon om tekst klassifisering her.

Vi ønsker å lage et API-endepunkt som utfører tekstklassifisering ved hjelp av Facebooks Bart Large MNLI-modell. modell, som er en forhåndsopplært modell basert på Hugging Face-transformatorer, perfekt egnet for tekst klassifisering av tekst.

Vårt API-endepunkt vil ta et stykke tekst som input, sammen med potensielle kategorier (kalt etiketter), og det vil returnere en poengsum for hver kategori (jo høyere, jo mer sannsynlig).

Vi vil be om endepunktet med POST-forespørsler som dette:

curl "https://api.nlpcloud.io/v1/bart-large-mnli/classification" \
-H "Authorization: Token e7f6539e5a5d7a16e15" \
-X POST -d '{
    "text":"John Doe is a Go Developer at Google. He has been working there for 10 years and has been awarded employee of the year.",
    "labels":["job", "nature", "space"]
}'

Og til gjengjeld ville vi få et svar som:

{
    "labels": [
        "job",
        "space",
        "nature"
    ],
    "scores": [
        0.9258803129196167,
        0.19384843111038208,
        0.010988432914018631
    ]
}

Slik oppnår du det med FastAPI og Transformers:

from fastapi import FastAPI
from pydantic import BaseModel, constr, conlist
from typing import List
from transformers import pipeline

classifier = pipeline("zero-shot-classification",
                model="facebook/bart-large-mnli")
app = FastAPI()

class UserRequestIn(BaseModel):
    text: constr(min_length=1)
    labels: conlist(str, min_items=1)

class ScoredLabelsOut(BaseModel):
    labels: List[str]
    scores: List[float]

@app.post("/classification", response_model=ScoredLabelsOut)
def read_classification(user_request_in: UserRequestIn):
    return classifier(user_request_in.text, user_request_in.labels)

Første ting først: vi laster inn Facebooks Bart Large MNLI fra Hugging Face-arkivet, og initialiserer den riktig for klassifiseringsformål, takket være initialiserer den riktig for klassifiseringsformål, takket være Transformer Pipeline:

classifier = pipeline("zero-shot-classification",
                model="facebook/bart-large-mnli")

Og senere bruker vi modellen ved å gjøre dette:

classifier(user_request_in.text, user_request_in.labels)

Den andre viktige tingen: Vi utfører datavalidering takket være Pydantic. Pydantic tvinger deg til å på forhånd å erklære inngangs- og utdataformatet for API-en din, noe som er bra fra et dokumentasjonssynspunkt, men også fordi det begrenser potensielle feil. synspunkt, men også fordi det begrenser potensielle feil. I Go ville du gjøre ganske mye det samme med JSON unmarshalling med structs. Her er en enkel måte å erklære at "text"-feltet skal ha minst 1 tegn: constr(min_length=1). Og det følgende spesifiserer at inndatalisten med etiketter minst skal inneholde ett element: conlist(str, min_items=1). Denne linjen betyr at utdatafeltet "labels" skal være en liste med strenger: List[str]. Og denne betyr at poengene skal være en liste over flyter: List[float]. Hvis modellen returnerer resultater som ikke følger dette formatet, vil FastAPI automatisk gi en feilmelding.

class UserRequestIn(BaseModel):
    text: constr(min_length=1)
    labels: conlist(str, min_items=1)

class ScoredLabelsOut(BaseModel):
    labels: List[str]
    scores: List[float]

Til slutt gjør følgende dekoratør det enkelt å spesifisere at du bare godtar POST-forespørsler på et bestemt endepunkt: @app.post("/entities", response_model=EntitiesOut).

Mer avansert datavalidering

Du kan gjøre mange mer komplekse valideringsoppgaver, som for eksempel sammensetning. La oss for eksempel si at du gjør Named Entity Recognition (NER), slik at modellen din returnerer en liste over enheter. Hver enhet vil ha 4 felt: text, type, start og position. Slik kan du gjøre det:

class EntityOut(BaseModel):
    start: int
    end: int
    type: str
    text: str

class EntitiesOut(BaseModel):
    entities: List[EntityOut]

@app.post("/entities", response_model=EntitiesOut) 
# [...]

Frem til nå har vi latt Pydantic håndtere valideringen. Det fungerer i de fleste tilfeller, men noen ganger ønsker du kanskje å å dynamisk heve en feil selv basert på komplekse forhold som ikke håndteres av Pydantic. Pydantic. Hvis du for eksempel vil returnere en HTTP 400-feil manuelt, kan du gjøre følgende:

from fastapi import HTTPException

raise HTTPException(status_code=400, 
        detail="Your request is malformed")

Selvfølgelig kan du gjøre mye mer!

Innstilling av rotbanen

Hvis du bruker FastAPI bak en omvendt proxy, må du sannsynligvis spille med rotbanen.

Det vanskelige er at bak en omvendt proxy vet ikke applikasjonen om hele URL-banen, så vi må eksplisitt fortelle den hvilken det er.

Her kan for eksempel den fullstendige URL-en til endepunktet vårt ikke bare være /classification. Men det kan være noe sånt som /api/v1/classification. Vi ønsker ikke å hardkode denne fullstendige URL-en for å gjøre det mulig for API-koden vår skal være løst koblet til resten av applikasjonen. Vi kan gjøre dette:

app = FastAPI(root_path="/api/v1")

Alternativt kan du sende en parameter til Uvicorn når du starter den:

uvicorn main:app --root-path /api/v1

Konklusjon

Jeg håper vi har vist deg hvor praktisk FastAPI kan være for et Natural Language Processing API. Pydantic gjør koden veldig uttrykksfull og mindre feilutsatt.

FastAPI har god ytelse og gjør det mulig å bruke Python asyncio ut av esken, noe som er flott for krevende maskinlæringsmodeller som Transformer-baserte Natural Language Processing-modeller. Vi har brukt FastAPI i nesten 1 år på NLP Cloud, og vi har aldri blitt skuffet så langt.

Hvis du har spørsmål, ikke nøl med å spørre, det vil være en glede å kommentere!

Julien Salinas
CTO hos NLP Cloud