De eerste versie van FastAPI is uitgebracht eind 2018 en wordt sindsdien steeds meer gebruikt in veel toepassingen in productie (zie FastAPI's website). Dat is wat we achter de motorkap gebruiken bij NLP Cloud. Het is een geweldige manier om gemakkelijk en efficiënt onze honderden Natural Language Processing-modellen, voor entiteitextractie (NER), tekstclassificatie, sentimentanalyse, vraag beantwoording, samenvattingen... We ontdekten dat FastAPI een geweldige manier is om transformer-gebaseerde deep modellen te bedienen.
In dit artikel, dachten we dat het interessant zou zijn om te laten zien hoe we een Natural Language Processing API implementeren gebaseerd op Hugging Face transformers met FastAPI.
Voor FastAPI, hadden we in wezen Django Rest Framework gebruikt voor onze Python API's, maar we waren al snel geïnteresseerd in FastAPI om de volgende redenen:
Deze geweldige prestaties maken FastAPI perfect geschikt voor machine learning API's die transformator-gebaseerde modellen zoals de onze.
Om FastAPI te laten werken, koppelen we het met de Uvicorn ASGI server, dat is de moderne manier om om asynchrone Python verzoeken af te handelen met asyncio. U kunt besluiten om FastAPI met Uvicorn handmatig te installeren of een kant-en-klaar Docker image te downloaden. Laten we eerst de handmatige installatie eerst laten zien:
pip install fastapi[all]
Dan kun je ermee beginnen:
uvicorn main:app
Sebastián Ramírez, de maker van FastAPI, biedt verschillende kant-en-klare Docker images aan die het heel gemakkelijk maken om FastAPI in productie te gebruiken. De Uvicorn + Gunicorn + FastAPI image maakt gebruik van Gunicorn om verschillende processen parallel te gebruiken (zie de afbeelding hier). Uiteindelijk, dankzij Uvicorn kun je verschillende FastAPI instanties binnen hetzelfde Python proces afhandelen, en dankzij Gunicorn kun je meerdere Python processen spawnen.
Uw FastAPI toepassing zal automatisch starten bij het starten van de Docker container met het volgende:
docker run.
Het is belangrijk om de documentatie van deze Docker images goed te lezen omdat er enkele instellingen zijn die je
wilt aanpassen, zoals bijvoorbeeld het aantal parallelle processen dat door Gunicorn wordt aangemaakt. Standaard,
spawnt de image evenveel processen als het aantal CPU cores op je machine. Maar in het geval van veeleisende
machine learning modellen zoals Natural Language Processing Transformers, kan dit al snel leiden tot tientallen GB's aan gebruikt geheugen. Een
strategie zou zijn om gebruik te maken van de Gunicorn optie (--preload), om uw model
uw model slechts eenmaal in het geheugen te laden en het te delen tussen alle FastAPI Python processen. Een andere optie zou zijn
is om het aantal Gunicorn processen te beperken. Beide hebben voor- en nadelen, maar dat valt buiten het
bereik van dit artikel.
Tekstclassificatie is het proces van het bepalen waar een stuk tekst over gaat (Ruimte? Zaken? Voedsel?...). Meer details over tekst classificatie hier.
We willen een API-eindpunt maken dat tekstclassificatie uitvoert met behulp van Facebook's Bart Large MNLI model, dat een vooraf getraind model is gebaseerd op Hugging Face transformatoren, perfect geschikt voor tekst classificatie.
Ons API eindpunt neemt een stuk tekst als invoer, samen met mogelijke categorieën (labels genoemd), en het zal een score teruggeven voor elke categorie (hoe hoger, hoe waarschijnlijker).
We zullen het eindpunt opvragen met POST verzoeken zoals dit:
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"]
}'
En in ruil daarvoor zouden we een antwoord krijgen als:
{
"labels": [
"job",
"space",
"nature"
],
"scores": [
0.9258803129196167,
0.19384843111038208,
0.010988432914018631
]
}
Hier is hoe je dat kunt bereiken met FastAPI en 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)
Eerst het belangrijkste: we laden Facebook's Bart Large MNLI uit de Hugging Face repository, en initialiseren het op de juiste manier voor classificatie doeleinden, dankzij Transformer Pipeline:
classifier = pipeline("zero-shot-classification",
model="facebook/bart-large-mnli")
En later gebruiken we het model door dit te doen:
classifier(user_request_in.text, user_request_in.labels)
Tweede belangrijk ding: we voeren gegevensvalidatie uit dankzij Pydantic. Pydantic dwingt je om
van te voren het input en output formaat voor je API aan te geven, wat geweldig is vanuit een documentatie
oogpunt, maar ook omdat het potentiële fouten beperkt. In Go zou je ongeveer hetzelfde doen
met JSON unmarshalling met structs. Hier is een eenvoudige manier om
te verklaren dat het "tekst" veld tenminste 1 karakter moet hebben: constr(min_length=1).
En het volgende specificeert dat de invoerlijst van labels in de lijst één element moet bevatten:
conlist(str,
min_items=1).
Deze regel betekent dat het "labels" uitvoerveld een lijst van tekenreeksen moet zijn: List[str].
En deze betekent dat de scores een lijst van vlotters moet zijn: List[float].
Als het model resultaten retourneert die dit formaat niet volgen, zal FastAPI automatisch een foutmelding geven.
class UserRequestIn(BaseModel):
text: constr(min_length=1)
labels: conlist(str, min_items=1)
class ScoredLabelsOut(BaseModel):
labels: List[str]
scores: List[float]
Tenslotte maakt de volgende decorator het gemakkelijk om aan te geven dat je alleen POST verzoeken accepteert, op een specifiek eindpunt:
@app.post("/entities", response_model=EntitiesOut).
Je kunt veel complexere validatie dingen doen, zoals bijvoorbeeld samenstelling. Bijvoorbeeld, laten we zeggen dat
je Named Entity Recognition (NER) doet, dus je model geeft een lijst van entiteiten terug. Elke entiteit
zou 4 velden hebben: text, type, start en position. Hier is hoe je het zou kunnen doen:
class EntityOut(BaseModel):
start: int
end: int
type: str
text: str
class EntitiesOut(BaseModel):
entities: List[EntityOut]
@app.post("/entities", response_model=EntitiesOut)
# [...]
Tot nu toe hebben we Pydantic de validatie laten afhandelen. Het werkt in de meeste gevallen, maar soms wil je misschien zelf dynamisch een fout willen oproepen gebaseerd op complexe condities die niet door Pydantic. Bijvoorbeeld, als je handmatig een HTTP 400 error wilt teruggeven, kun je het volgende doen:
from fastapi import HTTPException
raise HTTPException(status_code=400,
detail="Your request is malformed")
Natuurlijk kunt u nog veel meer doen!
Als u FastAPI achter een reverse proxy gebruikt, zult u waarschijnlijk moeten spelen met het rootpad.
Het lastige is dat, achter een reverse proxy, de applicatie niet het hele URL pad kent, dus moeten we het expliciet vertellen welke het is.
Bijvoorbeeld hier zou de volledige URL naar ons eindpunt niet eenvoudigweg kunnen zijn /classification. Maar het zou iets kunnen zijn als /api/v1/classification. We willen deze volledige URL niet hardcoderen om
onze API code losjes gekoppeld is met de rest van de applicatie. We zouden dit kunnen doen:
app = FastAPI(root_path="/api/v1")
Of u kunt een parameter doorgeven aan Uvicorn wanneer u het start:
uvicorn main:app --root-path /api/v1
Ik hoop dat we je met succes hebben laten zien hoe handig FastAPI kan zijn voor een Natural Language Processing API. Pydantic maakt de code zeer expressief en minder foutgevoelig.
FastAPI heeft geweldige prestaties en maakt het mogelijk om Python asyncio out of the box te gebruiken, wat geweldig is voor veeleisende machine learning modellen zoals Transformer-gebaseerde Natural Language Processing modellen. We gebruiken FastAPI al bijna 1 jaar bij NLP Cloud en we zijn tot nu toe nog nooit teleurgesteld geweest.
Als er vragen zijn, aarzel dan niet om te vragen, het zal een genoegen zijn om te reageren!
Julien Salinas
CTO bij NLP Cloud