FastAPIとTransformを用いた分類用のプロダクションレディな機械学習自然言語処理API

FastAPIの最初のバージョンがリリースされました が2018年末までにリリースされ、それ以降、本番の多くのアプリケーションで使用されることが増えています (FastAPIのウェブサイトを見る). それが NLPクラウドでは、ボンネットの裏でこれを使っています。これは、エンティティ抽出(NER)、テキスト分類、感情分析、質問などの エンティティ抽出(NER)、テキスト分類、感情分析、質問応答、要約など、何百もの自然言語処理モデルを簡単かつ効率的に提供する素晴らしい方法です。 質問応答、要約...。FastAPIは、変換ベースの深層学習モデルを提供するための優れた方法であることがわかりました。 モデルを提供する優れた方法であることがわかりました。

この記事では、FastAPIを使ってHugging Faceトランスフォーマーをベースにした自然言語処理APIをどのように実装しているかをご紹介します。 をベースにした自然言語処理APIをどのように実装しているかを紹介します。

FastAPIを使う理由は?

FastAPI以前は、PythonのAPIには基本的にDjango Rest Frameworkを使用していましたが、以下の理由ですぐにFastAPIに興味を持ちました。 以下の理由でFastAPIに興味を持ちました。

これらの優れた性能により、FastAPIは、私たちのような変換ベースのモデルを提供する機械学習APIに最適です。 我々のようなトランスフォーマーベースのモデルを提供する機械学習APIに最適です。

FastAPIのインストール

FastAPIを動作させるために、Uvicorn ASGIサーバと結合していますが、これはasyncioでPythonの非同期リクエストをネイティブに処理する最新の方法です。 asyncioでPythonの非同期リクエストをネイティブに処理する方法です。Uvicornと一緒にFastAPIをインストールすることもできます。 UvicornでFastAPIを手動でインストールするか、すぐに使えるDockerイメージをダウンロードします。まずは手動でインストールする方法をご紹介しましょう。 手動でインストールする方法を説明します。

pip install fastapi[all]

ならば、それを始めればいい。

uvicorn main:app

FastAPIの開発者であるSebastián Ramírez氏は、すぐに使えるDockerイメージをいくつか提供しています。 FastAPIを簡単に利用できるDockerイメージを提供しています。Uvicorn + Gunicorn + FastAPI イメージは、Gunicornを利用して、複数のプロセスを並行して使用します。 (画像はこちら). 最終的には Uvicornのおかげで、同じPythonプロセス内で複数のFastAPIインスタンスを扱うことができ、Gunicornのおかげで のおかげで、複数のPythonプロセスを生成することができます。

以下のようにDockerコンテナを起動すると、FastAPIアプリケーションが自動的に起動します。 docker run.

これらのDockerイメージのドキュメントをきちんと読むことが重要です。 例えば、Gunicornが作成する並列プロセスの数などです。デフォルトでは イメージはあなたのマシンのCPUコア数と同じ数のプロセスを生成します。しかし、負荷の高い 自然言語処理トランスフォーマーのような機械学習モデルの場合、すぐに数十GBのメモリを使用することになります。一つの Gunicornオプションを利用するのも一つの方法です。 (--preload), モデルをメモリに一度だけロードして モデルをメモリに一度だけロードして、すべてのFastAPI Pythonプロセスで共有するためです。他のオプションとしては Gunicornプロセスの数を制限することです。どちらも利点と欠点がありますが、それはこの記事の範囲を超えています。 この記事の範囲を超えています。

テキスト分類のためのシンプルなFastAPI + Transformers API

テキストの分類は、テキストの一部が何について話しているかを決定するプロセスです(宇宙?ビジネス? 食べ物?...)。 テキスト分類の詳細はこちら 分類はこちら

FacebookのBart Large MNLIモデルを使ってテキスト分類を行うAPIエンドポイントを作成したいと考えています。 モデルを使ってテキスト分類を行うAPIエンドポイントを作りたい。 分類に最適です。

私たちの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とトランスフォーマーを使って実現する方法を紹介します。

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の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を使うと Pydanticでは、APIの入力・出力フォーマットを事前に宣言することになっています。 これは文書化の観点からも素晴らしいことですが、潜在的なミスを抑えることができるからです。Goでもほぼ同じことができます。 JSONを構造体でアンマーシャリングするのと同じです。ここでは、以下のような簡単な方法を紹介します。 text "フィールドが少なくとも1文字であることを宣言します。 constr(min_length=1). また、次のように、ラベルの入力リストには1つの要素が含まれていなければならないと指定しています。 conlist(str, min_items=1). この行は、"labels "出力フィールドが文字列のリストであることを意味しています。 List[str]. これは、スコアがfloatのリストであることを意味します。 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(Named Entity Recognition)を行っていて、モデルがエンティティのリストを返しているとします。各エンティティ は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がネイティブに扱えない複雑な条件に基づいて、自分で動的にエラーを発生させたいこともあるでしょう。 Pydanticでは扱えない複雑な条件に基づいて、自分で動的にエラーを発生させたい場合があります。例えば、HTTP 400エラーを手動で返したい場合は、以下のようにします。

from fastapi import HTTPException

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

もちろん、それ以上のこともできます。

ルートパスの設定

リバースプロキシの後ろで FastAPI を使用している場合は、ほとんどの場合、ルートパスを変更する必要があります。

難しいのは、リバースプロキシの後ろでは、アプリケーションはURLパス全体を知らないということです。 そのため、それがどのパスなのかを明示的に教えなければなりません。

例えば、エンドポイントへの完全なURLは、単に次のようにはなりません。 /classification. しかし、それは次のようなものかもしれません。 /api/v1/classification. APIコードを他のアプリケーションと疎結合にするために、この完全なURLをハードコードしたくはありません。 APIコードをアプリケーションの他の部分と疎結合にするために、このフルURLをハードコードしたくありません。このようにすることもできます。

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

あるいは、Uvicornの起動時にパラメータを渡すという方法もあります。

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

結論

自然言語処理APIにFastAPIがどれだけ便利か、うまくお見せできたのではないでしょうか。Pydanticはコードを 表現力が豊かになり、エラーが少なくなります。

FastAPIは優れた性能を持ち、Python asyncioをすぐに使用することができます。 これは、Transformerベースの自然言語処理モデルのような要求の高い機械学習モデルに最適です。NLP Cloudでは、FastAPIを約1年間使用しています。 使っていますが、今のところ失望したことはありません。

ご質問があれば、遠慮なくお尋ねください。

Julien Salinas
NLP CloudのCTO