A primeira versão de FastAPI foi lançada até ao final de 2018 e tem sido cada vez mais utilizado em muitas aplicações na produção desde então (ver o sítio Web de FastAPI). Isto é o que estamos a usar por detrás do capuz da NLP Cloud. É uma óptima forma de servir fácil e eficientemente os nossos centenas de modelos de Processamento de Linguagem Natural, para extracção de entidades (NER), classificação de textos, análise de sentimentos, pergunta resposta, sumarização... Descobrimos que FastAPI é uma óptima forma de servir transformadores profundos modelos de aprendizagem.
Neste artigo, pensamos que seria interessante mostrar-lhe como estamos a implementar uma API de Processamento de Linguagem Natural baseada sobre transformadores de abraçar a cara com FastAPI.
Antes do FastAPI, tínhamos essencialmente utilizado o Django Rest Framework para as nossas APIs Python, mas fomos rapidamente interessado em FastAPI pelas seguintes razões:
Estes excelentes desempenhos tornam o FastAPI perfeitamente adequado para APIs de aprendizagem de máquinas que servem modelos baseados em transformadores como os nossos.
Para que FastAPI funcione, estamos a associá-lo ao servidor ASGI da Uvicorn, que é a forma moderna de nativamente tratar pedidos assíncronos Python com asyncio. Pode optar por instalar FastAPI com Uvicorn manualmente ou descarregar uma imagem Docker pronta a usar. Vamos mostrar a imagem de instalação manual primeiro:
pip install fastapi[all]
Depois pode começar com ele:
uvicorn main:app
Sebastián Ramírez, o criador do FastAPI, fornece várias imagens Docker prontas a usar que o tornam muito fácil de usar FastAPI na produção. O Uvicórnio + Gunicórnio + FastAPI a imagem tira partido de Gunicorn para utilizar vários processos em paralelo (ver a imagem aqui). No final, graças a Uvicorn pode lidar com várias instâncias FastAPI dentro do mesmo processo Python, e graças a Gunicorn pode desovar vários processos Python.
A sua aplicação FastAPI começará automaticamente ao iniciar o contentor Docker com o seguinte:
docker run.
É importante ler correctamente a documentação destas imagens do Docker uma vez que existem algumas definições
pode querer afinar, como por exemplo o número de processos paralelos criados por Gunicorn. Por defeito,
a imagem gera tantos processos como o número de núcleos de CPU na sua máquina. Mas em caso de exigência
modelos de aprendizagem mecânica como Transformadores de Processamento de Linguagem Natural, pode conduzir rapidamente a dezenas de GBs de memória utilizados. Um
a estratégia seria aproveitar a opção Gunicorn (--preload), a fim de carregar
o seu modelo apenas uma vez em memória e partilhá-lo entre todos os processos FastAPI Python. Outra opção seria
ser a de limitar o número de processos Gunicorn. Ambos têm vantagens e inconvenientes, mas isso está para além da
âmbito deste artigo.
A classificação do texto é o processo de determinar do que um texto está a falar (Espaço? Negócios? Comida?...). Mais detalhes sobre o texto classificação aqui.
Queremos criar um endpoint API que execute a classificação de texto usando o Bart Large MNLI do Facebook modelo, que é um modelo pré-treinado baseado em transformadores Hugging Face, perfeitamente adequado para texto classificação.
O nosso parâmetro API tomará como input um pedaço de texto, juntamente com potenciais categorias (chamadas etiquetas), e devolverá uma pontuação para cada categoria (quanto mais alta, mais provável).
Solicitaremos o ponto final com pedidos POST como este:
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"]
}'
E em troca obteríamos uma resposta do género:
{
"labels": [
"job",
"space",
"nature"
],
"scores": [
0.9258803129196167,
0.19384843111038208,
0.010988432914018631
]
}
Aqui está como consegui-lo com FastAPI e 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)
Primeiro: estamos a carregar o Bart Large MNLI do Facebook a partir do repositório Hugging Face, e inicializando-a adequadamente para fins de classificação, graças ao Transformer Pipeline:
classifier = pipeline("zero-shot-classification",
model="facebook/bart-large-mnli")
E mais tarde estamos a utilizar o modelo fazendo isto:
classifier(user_request_in.text, user_request_in.labels)
Segunda coisa importante: estamos a efectuar a validação de dados graças a Pydantic. Pydantic força-o a
declarar antecipadamente o formato de entrada e saída do seu API, o que é óptimo a partir de uma documentação
ponto de vista, mas também porque limita potenciais erros. Em Go faria praticamente a mesma coisa
com o JSON desarranjo com estruturas. Aqui está uma maneira fácil de
declarar que o campo "texto" deve ter pelo menos 1 carácter: constr(min_length=1).
E o que se segue especifica que a lista de etiquetas de entrada deve conter um elemento:
conlist(str,
min_items=1).
Esta linha significa que o campo de saída "etiquetas" deve ser uma lista de cordas: List[str].
E esta significa que a pontuação deve ser uma lista de carros alegóricos: List[float].
Se o modelo devolver resultados que não sigam este formato, FastAPI irá automaticamente levantar um erro.
class UserRequestIn(BaseModel):
text: constr(min_length=1)
labels: conlist(str, min_items=1)
class ScoredLabelsOut(BaseModel):
labels: List[str]
scores: List[float]
Por último, o decorador seguinte facilita a especificação de que apenas aceita pedidos POST, sobre um ponto final específico:
@app.post("/entities", response_model=EntitiesOut).
Pode fazer muitas coisas mais complexas de validação, como por exemplo a composição. Por exemplo, digamos que
está a fazer o Reconhecimento de Entidade Nomeada (NER), pelo que o seu modelo está a devolver uma lista de entidades. Cada entidade
teria 4 campos: text, type, start e position. Eis como o poderia fazer:
class EntityOut(BaseModel):
start: int
end: int
type: str
text: str
class EntitiesOut(BaseModel):
entities: List[EntityOut]
@app.post("/entities", response_model=EntitiesOut)
# [...]
Até agora, deixámos a Pydantic tratar da validação. Funciona na maioria dos casos, mas por vezes pode querer para levantar dinamicamente um erro por si próprio com base em condições complexas que não são tratadas nativamente por Pydantic. Por exemplo, se quiser devolver manualmente um erro do HTTP 400, pode fazer o seguinte:
from fastapi import HTTPException
raise HTTPException(status_code=400,
detail="Your request is malformed")
Claro que pode fazer muito mais!
Se estiver a usar FastAPI atrás de um proxy invertido, muito provavelmente precisará de jogar com o caminho de raiz.
O mais difícil é que, por detrás de um proxy invertido, a aplicação não conhece todo o caminho do URL, por isso, temos de lhe dizer explicitamente qual é.
Por exemplo, aqui o URL completo para o nosso endpoint pode não ser simplesmente /classification. Mas poderia ser algo como /api/v1/classification. Não queremos codificar este URL completo de forma rígida para
o nosso código API a ser livremente acoplado com o resto da aplicação. Poderíamos fazer isto:
app = FastAPI(root_path="/api/v1")
Ou, em alternativa, poderia passar um parâmetro a Uvicorn quando o iniciar:
uvicorn main:app --root-path /api/v1
Espero que tenhamos mostrado com sucesso como FastAPI pode ser conveniente para um API de Processamento de Linguagem Natural. Pydantic faz o código muito expressivo e menos sujeito a erros.
FastAPI tem grandes desempenhos e torna possível utilizar Python asyncio out of the box, o que é óptimo para modelos exigentes de aprendizagem mecânica como os modelos de Processamento de Linguagem Natural baseados em Transformador. Temos vindo a utilizar FastAPI para quase 1 ano no NLP Cloud e nunca ficámos desapontados até agora.
Se qualquer pergunta, não hesite em fazer, será um prazer comentar!
Julien Salinas
CTO em NLP Cloud