Haben Sie Probleme mit KI oder Full-Stack-Entwicklung? Unsere Experten sind für Sie da: maßgeschneiderte Beratung, technische Integration und mehr. Erreichen Sie uns unter [email protected].

Produktionsreife NLP-API für maschinelles Lernen zur Klassifizierung mit FastAPI und Transformatoren

Die erste Version von FastAPI wurde Ende 2018 veröffentlicht Ende 2018 veröffentlicht und wird seither zunehmend in vielen Anwendungen in der Produktion eingesetzt.Das ist es was wir bei NLP Cloud hinter der Haube verwenden. Es ist eine großartige Möglichkeit, einfach und effizient unsere Hunderte von NLP-Modellen für Entity-Extraktion (NER), Textklassifikation, Sentiment-Analyse, Fragebeantwortung Beantwortung von Fragen, Zusammenfassungen... Wir haben festgestellt, dass FastAPI eine großartige Möglichkeit ist, Transformator-basierte Deep Learning Lernmodelle zu bedienen.

In diesem Artikel möchten wir Ihnen zeigen, wie wir eine NLP-API auf der Basis von basierend auf Hugging Face Transformers mit FastAPI implementieren.

Warum FastAPI verwenden?

Vor FastAPI haben wir im Wesentlichen das Django Rest Framework für unsere Python-APIs verwendet, aber wir waren schnell an FastAPI interessiert. Interesse an FastAPI aus folgenden Gründen:

Dank dieser hervorragenden Leistungen eignet sich FastAPI perfekt für APIs für maschinelles Lernen, die Transformer-basierte Modelle wie das unsere.

FastAPI installieren

Damit FastAPI funktioniert, koppeln wir es mit dem Uvicorn ASGI Server, der die moderne Art ist asynchrone Python-Anfragen mit asyncio nativ zu verarbeiten. Sie können sich entweder für FastAPI mit Uvicorn manuell zu installieren oder ein fertiges Docker-Image herunterzuladen. Lassen Sie uns zunächst die manuelle Installation vor:

pip install fastapi[all]

Dann können Sie es mit beginnen:

uvicorn main:app

Sebastián Ramírez, der Schöpfer von FastAPI, stellt mehrere gebrauchsfertige Docker-Images zur Verfügung, die den Einsatz von FastAPI in der Produktion sehr den Einsatz von FastAPI in der Produktion erleichtern. Das Uvicorn + Gunicorn + FastAPI Image nutzt die Vorteile von Gunicorn, um mehrere Prozesse parallel zu nutzen. Letztendlich können Sie dank Uvicorn können Sie mehrere FastAPI-Instanzen innerhalb desselben Python-Prozesses verwalten, und dank Gunicorn können Sie mehrere Python-Prozesse erzeugen.

Ihre FastAPI-Anwendung wird automatisch gestartet, wenn Sie den Docker-Container mit docker run.

Es ist wichtig, die Dokumentation dieser Docker-Images genau zu lesen, da es einige Einstellungen gibt, die Sie wie z.B. die Anzahl der parallelen Prozesse, die von Gunicorn erzeugt werden, anpassen möchten. Standardmäßig erzeugt das Image so viele Prozesse wie die Anzahl der CPU-Kerne auf Ihrem Rechner. Aber im Falle von anspruchsvollen maschinellen Lernmodellen wie NLP Transformers kann dies schnell zu einem Speicherverbrauch von mehreren Dutzend GB führen. Eine Strategie wäre es, die Gunicorn-Option --preload zu nutzen, um das Modell Modell nur einmal in den Speicher zu laden und es auf alle FastAPI-Python-Prozesse zu verteilen. Eine andere Möglichkeit wäre wäre, die Anzahl der Gunicorn-Prozesse zu begrenzen. Beide Optionen haben Vor- und Nachteile, aber das würde den Rahmen dieses Artikels.

Einfache FastAPI + Transformers API für Textklassifizierung

Bei der Textklassifizierung geht es darum, zu bestimmen, worum es in einem Text geht (Raum? Wirtschaft? Lebensmittel?...). Weitere Einzelheiten zur Text Klassifizierung hier.

Wir möchten einen API-Endpunkt erstellen, der eine Textklassifizierung unter Verwendung des Bart Large MNLI-Modells von Facebook durchführt. Modells durchführt, einem vortrainierten Modell, das auf Hugging Face Transformers basiert und sich perfekt für die Klassifizierung.

Unser API-Endpunkt nimmt einen Text als Eingabe sowie potenzielle Kategorien (so genannte Labels) entgegen, und gibt für jede Kategorie eine Punktzahl zurück (je höher, desto wahrscheinlicher).

Wir werden den Endpunkt mit POST-Anfragen wie folgt anfordern:

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

Und im Gegenzug bekämen wir eine Antwort wie:

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

So erreichen Sie dies mit FastAPI und 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)

Das Wichtigste zuerst: Wir laden den Bart Large MNLI von Facebook aus dem Hugging Face Repository und dank der Transformer Pipeline richtig für die Klassifizierung initialisieren:

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

Und später verwenden wir das Modell, indem wir dies tun:

classifier(user_request_in.text, user_request_in.labels)

Zweiter wichtiger Punkt: Wir führen dank Pydantic eine Datenvalidierung durch. Pydantic zwingt Sie dazu das Eingabe- und Ausgabeformat für Ihre API im Voraus zu deklarieren, was nicht nur aus Dokumentationsgründen Dokumentation, aber auch, weil es mögliche Fehler einschränkt. In Go würde man so ziemlich das Gleiche tun mit JSON unmarshalling mit structs. constr(min_length=1) ist ein einfacher Weg, um zu deklarieren, dass das "Text"-Feld mindestens 1 Zeichen enthalten soll. And conlist(str, min_items=1) gibt an, dass die Eingabeliste der Bezeichnungen ein Element enthalten soll. List[str] bedeutet, dass das Ausgabefeld "labels" eine Liste von Zeichenketten sein sollte, und List[float] bedeutet, dass die Punktzahlen eine Liste von Floats sein sollten. Wenn das Modell Ergebnisse liefert, die nicht diesem Format entsprechen, wird FastAPI automatisch einen Fehler auslösen.

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

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

Schließlich macht es der @app.post("/entities", response_model=EntitiesOut) -Dekorator einfach, festzulegen, dass Sie nur POST-Anfragen an einem bestimmten Endpunkt akzeptieren.

Erweiterte Datenvalidierung

Sie können viele komplexere Validierungen durchführen, wie zum Beispiel Komposition. Nehmen wir zum Beispiel an, Sie führen eine Erkennung von benannten Entitäten (NER) durch, so dass Ihr Modell eine Liste von Entitäten zurückgibt. Jede Entität würde 4 Felder haben: text, type, start and position. So könnten Sie es machen:

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

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

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

Bis jetzt haben wir die Validierung Pydantic überlassen. Das funktioniert in den meisten Fällen, aber manchmal möchten Sie vielleicht selbst dynamisch einen Fehler auslösen, der auf komplexen Bedingungen beruht, die nicht von Pydantic verarbeitet werden. Wenn Sie beispielsweise manuell einen HTTP-400-Fehler zurückgeben möchten, können Sie wie folgt vorgehen:

from fastapi import HTTPException

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

Natürlich können Sie noch viel mehr tun!

Festlegen des Wurzelpfads

Wenn Sie FastAPI hinter einem Reverse-Proxy verwenden, müssen Sie höchstwahrscheinlich mit dem Root-Pfad spielen.

Die Schwierigkeit besteht darin, dass die Anwendung hinter einem Reverse Proxy nicht den gesamten URL-Pfad kennt, so dass wir ihr explizit mitteilen müssen, welcher es ist.

Hier könnte die vollständige URL zu unserem Endpunkt zum Beispiel nicht einfach /classification lauten, sondern vielleicht etwas wie /api/v1/classification. Wir wollen diese vollständige URL nicht fest codieren, damit unser API-Code lose mit dem Rest der Anwendung gekoppelt ist. Wir könnten dies tun:

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

Alternativ können Sie Uvicorn beim Start einen Parameter übergeben:

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

Schlussfolgerung

Ich hoffe, wir haben Ihnen erfolgreich gezeigt, wie praktisch FastAPI für eine NLP-API sein kann. Pydantic macht den Code sehr ausdrucksstark und weniger fehleranfällig.

FastAPI hat eine großartige Leistung und ermöglicht es, Python asyncio out of the box zu verwenden, was für anspruchsvolle Machine-Learning-Modelle wie Transformer-basierte NLP-Modelle großartig ist. Wir verwenden FastAPI seit fast einem Jahr bei NLP Cloud und wurden bisher noch nie enttäuscht.

Wenn Sie Fragen haben, zögern Sie bitte nicht zu fragen, es wird ein Vergnügen sein, zu kommentieren!

Julien Salinas
CTO bei NLP Cloud