Išleista pirmoji FastAPI versija 2018 m. pabaigoje ir nuo to laiko ji vis dažniau naudojama daugelyje gamybinių programų. (žr. "FastAPI" svetainę). Tai tai, ką naudojame "NLP Cloud". Tai puikus būdas lengvai ir efektyviai aptarnauti mūsų šimtus natūralios kalbos apdorojimo modelių, skirtų esybių išskyrimui (NER), teksto klasifikavimui, nuotaikų analizei, klausimų atsakymams, apibendrinimui... Nustatėme, kad FastAPI yra puikus būdas aptarnauti transformatoriais pagrįstus giliuosius mokymosi modelius.
Šiame straipsnyje manėme, kad būtų įdomu parodyti, kaip įgyvendiname natūralios kalbos apdorojimo API, pagrįstą "Hugging Face" transformatoriais su FastAPI.
Prieš FastAPI savo "Python" API iš esmės naudojome "Django Rest Framework", tačiau greitai susidomėjome FastAPI dėl šių priežasčių:
Dėl šių puikių savybių FastAPI puikiai tinka mašinų mokymosi API, teikiančioms transformatoriais pagrįstus modelius, tokius kaip mūsų.
Kad FastAPI veiktų, mes jį siejame su "Uvicorn" ASGI serveriu, kuris yra šiuolaikinis būdas natūraliai tvarkyti asinchronines Python užklausas su asyncio. Galite nuspręsti įdiegti FastAPI su Uvicorn rankiniu būdu arba atsisiųsti paruoštą naudoti Docker atvaizdą. Parodykime pirmiausia rankinį diegimą:
pip install fastapi[all]
Tada galite pradėti nuo:
uvicorn main:app
"FastAPI" kūrėjas Sebastiánas Ramírezas pateikia keletą paruoštų naudoti "Docker" atvaizdų, kurie leidžia labai lengva naudoti FastAPI gamyboje. Uvicorn + Gunicorn + FastAPI atvaizdas naudoja Gunicorn privalumus, kad būtų galima lygiagrečiai naudoti kelis procesus (žr. paveikslėlį čia). Galiausiai, dėka Uvicorn galite valdyti kelis FastAPI egzempliorius tame pačiame Python procese, o dėl Gunicorn galite sukurti kelis "Python" procesus.
Jūsų FastAPI programa bus automatiškai paleista paleidžiant "Docker" konteinerį su:
docker run.
Svarbu tinkamai perskaityti šių "Docker" atvaizdų dokumentaciją, nes yra tam tikrų nustatymų, kuriuos
pavyzdžiui, "Gunicorn" sukurtų lygiagrečiųjų procesų skaičių. Pagal numatytuosius nustatymus,
atvaizdas sukuria tiek procesų, kiek jūsų kompiuteryje yra procesoriaus branduolių. Tačiau, jei reikia daug
mašininio mokymosi modelius, pavyzdžiui, natūralios kalbos apdorojimo transformatorius, tai gali greitai lemti dešimtis GB naudojamos atminties. Vienas
strategija būtų pasinaudoti parinktimi Gunicorn (--preload), kad būtų galima įkelti
modelį į atmintį įkelti tik vieną kartą ir dalytis juo su visais FastAPI Python procesais. Kita galimybė būtų
būtų apriboti "Gunicorn" procesų skaičių. Abu šie būdai turi privalumų ir trūkumų, bet tai jau ne šios
šio straipsnio apimtis.
Teksto klasifikavimas - tai procesas, kurio metu nustatoma, apie ką kalbama tekste (Erdvė? Verslas? Apie maistą?...). Daugiau informacijos apie tekstą klasifikacija čia.
Norime sukurti API galinį tašką, kuris atliktų teksto klasifikavimą naudodamas "Facebook" Bart Large MNLI modelį, kuris yra iš anksto apmokytas modelis, pagrįstas "Hugging Face" transformatoriais, puikiai tinkantis tekstui teksto klasifikavimui.
Mūsų API galiniame taške kaip įvestis bus priimamas teksto fragmentas ir galimos kategorijos (vadinamos etiketėmis), ir grąžins kiekvienos kategorijos balą (kuo didesnis, tuo didesnė tikimybė).
Galutinį tašką užklausime POST užklausomis taip:
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"]
}'
O atsakydami gaudavome tokį atsakymą:
{
"labels": [
"job",
"space",
"nature"
],
"scores": [
0.9258803129196167,
0.19384843111038208,
0.010988432914018631
]
}
Štai kaip tai pasiekti naudojant FastAPI ir transformatorius:
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)
Pirmiausia įkeliame "Facebook" Bart Large MNLI iš "Hugging Face" saugyklos ir tinkamai inicializuojame jį klasifikavimo tikslais, dėka Transformer Pipeline:
classifier = pipeline("zero-shot-classification",
model="facebook/bart-large-mnli")
Vėliau mes naudojame modelį, tai darydami:
classifier(user_request_in.text, user_request_in.labels)
Antras svarbus dalykas: "Pydantic" dėka atliekame duomenų patvirtinimą. Pydantic priverčia jus
iš anksto deklaruoti savo API įvesties ir išvesties formatą, o tai labai naudinga dokumentacijos požiūriu.
požiūriu, bet ir dėl to, kad tai apriboja galimas klaidas. Go kalboje darytumėte beveik tą patį
su JSON išskyrimu naudojant struktūras. Čia pateikiamas paprastas būdas
deklaruoti, kad laukas "tekstas" turėtų turėti bent 1 simbolį: constr(min_length=1).
Toliau nurodyta, kad įvesties etikečių sąraše turėtų būti vienas elementas:
conlist(str,
min_items=1).
Ši eilutė reiškia, kad išvesties laukas "Labels" turėtų būti eilučių sąrašas: List[str].
O šis reiškia, kad balai turėtų būti kintamųjų sąrašas: List[float].
Jei modelis grąžina rezultatus, kurie neatitinka šio formato, FastAPI automatiškai iškels klaidą.
class UserRequestIn(BaseModel):
text: constr(min_length=1)
labels: conlist(str, min_items=1)
class ScoredLabelsOut(BaseModel):
labels: List[str]
scores: List[float]
Galiausiai, naudojant šį dekoratorių lengva nurodyti, kad priimamos tik POST užklausos konkrečiame galiniame taške:
@app.post("/entities", response_model=EntitiesOut).
Galite atlikti daug sudėtingesnių patvirtinimo veiksmų, pavyzdžiui, kompoziciją. Pavyzdžiui, tarkime, kad
atliekate įvardytų esybių atpažinimą (NER), taigi jūsų modelis grąžina esybių sąrašą. Kiekviena esybė
turėtų 4 laukus: text, type, start ir position. Štai kaip galėtumėte tai padaryti:
class EntityOut(BaseModel):
start: int
end: int
type: str
text: str
class EntitiesOut(BaseModel):
entities: List[EntityOut]
@app.post("/entities", response_model=EntitiesOut)
# [...]
Iki šiol leidome "Pydantic" atlikti patvirtinimą. Daugeliu atvejų tai veikia, tačiau kartais galite norėti dinamiškai iškelti klaidą patys, remdamiesi sudėtingomis sąlygomis, kurių iš prigimties nesugeba apdoroti Pydantic. Pavyzdžiui, jei norite rankiniu būdu grąžinti HTTP 400 klaidą, galite padaryti taip:
from fastapi import HTTPException
raise HTTPException(status_code=400,
detail="Your request is malformed")
Žinoma, galite padaryti daug daugiau!
Jei naudojate "FastAPI" už atvirkštinio tarpinio serverio, greičiausiai reikės keisti šakninį kelią.
Sudėtinga tai, kad už atvirkštinio tarpinio serverio programa nežino viso URL kelio, todėl turime aiškiai nurodyti, kuris tai yra.
Pavyzdžiui, šiuo atveju pilnas mūsų galinio taško URL gali būti ne tik /classification. Tačiau tai gali būti kažkas panašaus į /api/v1/classification. Nenorime užkoduoti šio pilno URL adreso, kad
mūsų API kodas būtų laisvai susietas su likusia programos dalimi. Galėtume padaryti taip:
app = FastAPI(root_path="/api/v1")
Arba galite perduoti parametrą "Uvicorn" paleidimo metu:
uvicorn main:app --root-path /api/v1
Tikiuosi, kad sėkmingai parodėme, kokia patogi FastAPI gali būti natūralios kalbos apdorojimo API. Pydantic leidžia kodą labai išraiškingas ir mažiau linkęs į klaidas.
"FastAPI" pasižymi puikiomis savybėmis ir leidžia naudoti "Python" asyncio iškart, o tai yra puiku. reikliems mašininio mokymosi modeliams, pavyzdžiui, transformatoriais pagrįstiems natūralios kalbos apdorojimo modeliams. Mes naudojome FastAPI NLP Cloud beveik 1 metus ir iki šiol niekada nenusivylėme.
Jei turite klausimų, nedvejodami klauskite, bus malonu komentuoti!
Julien Salinas
NLP Cloud techninis direktorius