Имате проблеми с ИИ или разработката на пълен пакет? Нашите експерти са тук, за да ви напътстват: индивидуални съвети, техническа интеграция и др. Свържете се с [email protected].

Готов за производство приложен програмен интерфейс за машинно обучение за обработка на естествен език за класификация с FastAPI и трансформатори

Публикувана е първата версия на FastAPI в края на 2018 г. и оттогава тя се използва все по-често в много приложения в производство (вижте уебсайта на FastAPI). Това е това, което използваме зад капака на NLP Cloud. Това е чудесен начин за лесно и ефективно обслужване на нашите стотици модели за обработка на естествен език, за извличане на същности (NER), класификация на текст, анализ на настроенията, въпроси отговаряне на въпроси, обобщаване... Открихме, че FastAPI е чудесен начин да обслужваме дълбоки модели за обучение.

В тази статия решихме, че ще е интересно да ви покажем как реализираме API за обработка на естествен език, базиран на на базата на трансформатори за прегръщане на лица с FastAPI.

Защо да използвате FastAPI?

Преди FastAPI използвахме основно Django Rest Framework за нашите API на Python, но бързо се заинтересувахме от FastAPI поради следните причини:

Благодарение на тези отлични характеристики FastAPI е напълно подходящ за API за машинно обучение. модели, базирани на трансформатори, като нашия.

Инсталиране на FastAPI

За да работи FastAPI, ние го свързваме с ASGI сървъра на Uvicorn, който е модерният начин за асинхронните заявки на Python да се обработват по естествен начин с asyncio. Можете да решите да да инсталирате FastAPI с Uvicorn ръчно или да изтеглите готово за използване изображение на Docker. Нека да покажем първо ръчната инсталация:

pip install fastapi[all]

След това можете да го започнете с:

uvicorn main:app

Себастиан Рамирес, създателят на FastAPI, предоставя няколко готови за използване образа на Docker, които го правят много лесно да използвате FastAPI в производството. Uvicorn + Gunicorn + FastAPI се възползва от Gunicorn, за да използва няколко паралелни процеса (вижте изображението тук). В крайна сметка благодарение на Uvicorn можете да работите с няколко инстанции на FastAPI в рамките на един и същ Python процес, а благодарение на Gunicorn можете да създавате няколко процеса Python.

Вашето FastAPI приложение ще се стартира автоматично при стартирането на контейнера Docker със следното: docker run.

Важно е да прочетете правилно документацията на тези образи на Docker, тъй като има някои настройки, които трябва да като например броя на паралелните процеси, създадени от Gunicorn. По подразбиране, изображението създава толкова процеси, колкото е броят на процесорните ядра на вашата машина. Но в случай на взискателни модели за машинно обучение, като например трансформатори за обработка на естествен език, това може бързо да доведе до десетки GB използвана памет. Един стратегия би било да се използва опцията Gunicorn (--preload), за да заредите моделът ви да се зарежда само веднъж в паметта и да се споделя между всички процеси на FastAPI Python. Друга възможност е е да ограничите броя на процесите Gunicorn. И двата варианта имат предимства и недостатъци, но това е извън рамките на обхвата на тази статия.

Прост FastAPI + API за трансформатори за класификация на текст

Класификацията на текста е процесът на определяне на това, за какво се говори в даден текст (Space? Бизнес? Храна?...). Повече информация за текста класификация тук.

Искаме да създадем крайна точка на API, която извършва класификация на текст, като използва Bart Large MNLI на Facebook който е предварително обучен модел, базиран на трансформатори за прегръщане на лица, напълно подходящ за текст. за класификация на текстове.

Нашата крайна точка на API ще приема като входна информация част от текст, заедно с потенциални категории (наречени етикети), и ще връща оценка за всяка категория (колкото по-висока, толкова по-вероятна).

Ще поискаме крайната точка с POST заявки по следния начин:

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"]
}'

И в замяна на това ще получим отговор като:

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

Ето как да го постигнете с FastAPI и 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)

Първо: зареждаме Facebook's Bart Large MNLI от хранилището Hugging Face и и го инициализираме правилно за целите на класификацията благодарение на Transformer Pipeline:

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

И по-късно използваме модела, като правим това:

classifier(user_request_in.text, user_request_in.labels)

Второ важно нещо: извършваме валидиране на данните благодарение на Pydantic. Pydantic ви принуждава да предварително да декларирате входния и изходния формат на вашия API, което е чудесно от гледна точка на документацията. гледна точка, но и защото ограничава потенциалните грешки. В Go бихте направили почти същото нещо с JSON unmarshalling със структури. Ето един лесен начин да да декларирате, че полето "текст" трябва да има поне 1 символ: constr(min_length=1). А следното указва, че входният списък с етикети трябва да съдържа един елемент: conlist(str, min_items=1). Този ред означава, че изходното поле "labels" трябва да бъде списък от низове: List[str]. А това означава, че резултатите трябва да са списък от плаващи числа: List[float]. Ако моделът върне резултати, които не отговарят на този формат, FastAPI автоматично ще обяви грешка.

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

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

И накрая, следният декоратор улеснява определянето на това, че приемате само POST заявки в определена крайна точка: @app.post("/entities", response_model=EntitiesOut).

По-разширено валидиране на данни

Можете да правите много по-сложни действия за валидиране, като например композиция. Например, нека кажем, че извършвате разпознаване на назовани същности (NER), така че вашият модел връща списък със същности. Всяка същност ще има 4 полета: text, type, start и position. Ето как можете да го направите:

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

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

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

Досега позволявахме на Pydantic да се справя с валидирането. Това работи в повечето случаи, но понякога може да искате да повдигате динамично грешка самостоятелно въз основа на сложни условия, които не се обработват естествено от Pydantic. Например, ако искате ръчно да върнете грешка HTTP 400, можете да направите следното:

from fastapi import HTTPException

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

Разбира се, можете да направите много повече!

Задаване на пътя на корена

Ако използвате FastAPI зад обратен прокси сървър, най-вероятно ще трябва да си поиграете с пътя до корена.

Трудното е, че зад обратния прокси сървър приложението не знае целия път на URL адреса, така че трябва изрично да му кажем коя е тя.

Например тук пълният URL адрес на нашата крайна точка може да не е просто /classification. Но може да е нещо като /api/v1/classification. Не искаме да кодираме твърдо този пълен URL адрес, за да API кодът ни да бъде свободно свързан с останалата част от приложението. Можем да направим това:

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

Или пък можете да подадете параметър на Uvicorn при стартирането му:

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

Заключение

Надявам се, че успешно ви показахме колко удобен може да бъде FastAPI за API за обработка на естествен език. Pydantic прави кода много изразителен и по-малко склонен към грешки.

FastAPI има страхотни характеристики и дава възможност за използване на Python asyncio out of the box, което е страхотно за взискателни модели за машинно обучение, като например модели за обработка на естествен език, базирани на трансформатори. Ние използваме FastAPI за почти 1 година в NLP Cloud и досега никога не сме оставали разочаровани.

Ако има въпроси, моля, не се колебайте да попитате, ще бъде удоволствие да коментирате!

Julien Salinas
Технически директор в NLP Cloud