A fost lansată prima versiune a FastAPI la sfârșitul anului 2018 și de atunci a fost din ce în ce mai mult utilizată în multe aplicații în producție (consultați site-ul FastAPI). Asta e... ceea ce folosim în spatele capotei la NLP Cloud. Este o modalitate excelentă de a ne servi cu ușurință și în mod eficient serviciile de sute de modele de procesare a limbajului natural, pentru extragerea entităților (NER), clasificarea textelor, analiza sentimentelor, întrebări și răspunsuri. răspuns la întrebări, rezumare... Am constatat că FastAPI este o modalitate excelentă de a servi modelele de tip transformer-based deep de învățare bazate pe transformatoare.
În acest articol, ne-am gândit că ar fi interesant să vă arătăm cum implementăm un API de procesare a limbajului natural bazat pe pe transformatoare Hugging Face cu FastAPI.
Înainte de FastAPI, am folosit în esență Django Rest Framework pentru API-urile noastre Python, dar am fost rapid interesați de FastAPI din următoarele motive:
Aceste performanțe deosebite fac ca FastAPI să fie perfect potrivită pentru API-urile de învățare automată care deservesc modele bazate pe transformatoare, cum este al nostru.
Pentru ca FastAPI să funcționeze, îl cuplăm cu serverul Uvicorn ASGI, care este modul modern de a a gestiona în mod nativ cererile asincrone Python cu asyncio. Puteți decide fie să să instalați manual FastAPI cu Uvicorn sau să descărcați o imagine Docker gata de utilizare. Haideți să arătăm imaginea instalarea manuală mai întâi:
pip install fastapi[all]
Apoi puteți începe cu:
uvicorn main:app
Sebastián Ramírez, creatorul FastAPI, oferă mai multe imagini Docker gata de utilizare, care îl fac foarte ușor de utilizat. ușor de utilizat FastAPI în producție. Uvicorn + Gunicorn + FastAPI profită de Gunicorn pentru a utiliza mai multe procese în paralel (vezi imaginea aici). În cele din urmă, datorită Uvicorn puteți gestiona mai multe instanțe FastAPI în cadrul aceluiași proces Python, iar datorită lui Gunicorn puteți genera mai multe procese Python.
Aplicația FastAPI va porni automat la pornirea containerului Docker cu următoarele:
docker run.
Este important să citiți în mod corespunzător documentația acestor imagini Docker, deoarece există anumite setări pe care trebuie să le
s-ar putea să doriți să modificați, cum ar fi, de exemplu, numărul de procese paralele create de Gunicorn. În mod implicit,
imaginea generează atâtea procese câte nuclee de procesare există pe calculatorul dvs. Dar, în cazul în care se solicită
modele de învățare automată, cum ar fi Natural Language Processing Transformers, aceasta poate duce rapid la zeci de GB de memorie utilizată. Unul
strategie ar fi aceea de a profita de opțiunea Gunicorn (--preload), pentru a încărca
modelul dvs. o singură dată în memorie și să îl partajați între toate procesele FastAPI Python. O altă opțiune ar fi
ar fi limitarea numărului de procese Gunicorn. Ambele au avantaje și dezavantaje, dar acest lucru depășește cadrul
domeniul de aplicare al acestui articol.
Clasificarea textului este procesul de determinare a ceea ce vorbește un text (Spațiu? Afaceri? Mâncare?...). Mai multe detalii despre text clasificare aici.
Dorim să creăm un endpoint API care să efectueze clasificarea textului folosind Bart Large MNLI de la Facebook care este un model preformat bazat pe transformatoare Hugging Face, perfect adaptat pentru text. clasificarea textului.
Punctul final al API-ului nostru va primi un text ca intrare, împreună cu categorii potențiale (numite etichete), și va returna un scor pentru fiecare categorie (cu cât este mai mare, cu atât este mai probabil).
Vom solicita endpoint-ul cu cereri POST, astfel:
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"]
}'
Și în schimb am primi un răspuns de genul:
{
"labels": [
"job",
"space",
"nature"
],
"scores": [
0.9258803129196167,
0.19384843111038208,
0.010988432914018631
]
}
Iată cum se poate realiza acest lucru cu FastAPI și 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)
În primul rând: încărcăm Facebook's Bart Large MNLI din depozitul Hugging Face și îl inițializăm în mod corespunzător în scopul clasificării, datorită Transformer Pipeline:
classifier = pipeline("zero-shot-classification",
model="facebook/bart-large-mnli")
Și mai târziu vom folosi modelul făcând acest lucru:
classifier(user_request_in.text, user_request_in.labels)
Al doilea lucru important: efectuăm validarea datelor datorită Pydantic. Pydantic vă obligă să
declarați în avans formatul de intrare și ieșire pentru API-ul dvs., ceea ce este grozav din punct de vedere al documentației
punct de vedere al documentației, dar și pentru că limitează potențialele greșeli. În Go ați face cam același lucru
cu JSON unmarshalling cu structurile. Iată o modalitate simplă de a
declarați că câmpul "text" trebuie să aibă cel puțin 1 caracter: constr(min_length=1).
Iar următorul text specifică faptul că lista de etichete de intrare trebuie să conțină un singur element:
conlist(str,
min_items=1).
Această linie înseamnă că câmpul de ieșire "labels" trebuie să fie o listă de șiruri de caractere: List[str].
Iar aceasta înseamnă că scorurile trebuie să fie o listă de flotoare: List[float].
În cazul în care modelul returnează rezultate care nu respectă acest format, FastAPI va genera automat o eroare.
class UserRequestIn(BaseModel):
text: constr(min_length=1)
labels: conlist(str, min_items=1)
class ScoredLabelsOut(BaseModel):
labels: List[str]
scores: List[float]
În cele din urmă, următorul decorator facilitează specificarea faptului că acceptați numai cereri POST, pe un anumit endpoint:
@app.post("/entities", response_model=EntitiesOut).
Puteți face multe alte validări mai complexe, cum ar fi, de exemplu, compoziția. De exemplu, să spunem că
faceți Recunoașterea entităților numite (Named Entity Recognition - NER), astfel încât modelul dumneavoastră returnează o listă de entități. Fiecare entitate
ar avea 4 câmpuri: text, type, start și position. Iată cum ați putea face acest lucru:
class EntityOut(BaseModel):
start: int
end: int
type: str
text: str
class EntitiesOut(BaseModel):
entities: List[EntityOut]
@app.post("/entities", response_model=EntitiesOut)
# [...]
Până acum, am lăsat Pydantic să se ocupe de validare. Funcționează în majoritatea cazurilor, dar uneori este posibil să doriți să să ridicați singuri în mod dinamic o eroare pe baza unor condiții complexe care nu sunt gestionate în mod nativ de către Pydantic. De exemplu, dacă doriți să returnați manual o eroare HTTP 400, puteți face următoarele:
from fastapi import HTTPException
raise HTTPException(status_code=400,
detail="Your request is malformed")
Bineînțeles că puteți face mult mai mult!
Dacă utilizați FastAPI în spatele unui proxy invers, cel mai probabil va trebui să vă jucați cu calea de acces la rădăcină.
Problema este că, în spatele unui proxy invers, aplicația nu cunoaște întreaga cale URL, așa că trebuie să-i spunem în mod explicit care este aceasta.
De exemplu, aici, URL-ul complet al punctului nostru final nu poate fi pur și simplu /classification. Dar ar putea fi ceva de genul /api/v1/classification. Nu dorim să codificăm acest URL complet pentru ca
codul nostru API să fie liber cuplat cu restul aplicației. Am putea face acest lucru:
app = FastAPI(root_path="/api/v1")
Sau, alternativ, ați putea trece un parametru către Uvicorn atunci când îl porniți:
uvicorn main:app --root-path /api/v1
Sper că v-am arătat cu succes cât de convenabil poate fi FastAPI pentru un API de procesare a limbajului natural. Pydantic face codul foarte expresiv și mai puțin predispus la erori.
FastAPI are performanțe mari și face posibilă utilizarea Python asyncio din start, ceea ce este grozav. pentru modelele de învățare automată solicitante, cum ar fi modelele de procesare a limbajului natural bazate pe Transformer. Am utilizat FastAPI pentru aproape 1 an la NLP Cloud și nu am fost niciodată dezamăgiți până acum.
Dacă aveți vreo întrebare, nu ezitați să întrebați, va fi o plăcere să comentați!
Julien Salinas
CTO la NLP Cloud