FastAPIでMLflowのモデルを提供する方法

# はじめに
本記事では、 **FastAPI** を用いて **MLflow** で管理している機械学習モデルを利用できるAPIを構築する方法を紹介します。

- **MLflow**: 機械学習モデルの管理・トラッキングを行う
- **FastAPI**: 軽量なPythonのWebフレームワーク
- **Docker Compose**: コンテナベースで環境を構築

※FastAPI以外の箇所は前回書いた以下記事をご参照ください。
https://qiita.com/ebasuke0226/items/4b05c5a1b851a9349fc7

# 環境構築
## 1. **Docker Composeの設定**
`docker-compose.yml` で **FastAPI** を追加しました。

```yaml
version: '3.8'

services:
  fastapi:
    build: ./fastapi
    ports:
      - "8000:8000"
    depends_on:
      - mlflow
    command: ["tail", "-f", "/dev/null"]
    environment:
      MLFLOW_TRACKING_URI: http://mlflow:5000
    networks:
      - mlops_network
```

実装のポイント
* `fastapi` サービスを新規追加し、MLflowを利用できる環境を構築した。
* `MLFLOW_TRACKING_URI` を fastapi に設定し、モデル管理を容易にした。
* FastAPIのコンテナは、バックグラウンドで動作させるために `command: ["tail", "-f", "/dev/null"]` を設定した。


## 2. **FastAPIの実装**
ディレクトリ構成
```
/mlops_project
│
├── docker-compose.yml  # 各コンテナを統合するDocker Composeファイル
│
├── /fastapi
│   ├── Dockerfile  # fastapi用のDockerfile
│   ├── requirements_fastapi.txt  # fastapi用の依存ライブラリ
│   ├── /app
│       ├── main.py
│       ├── predict.py
│       ├── /templates
│           ├── index.html  # フォーム画面
```

`main.py` (FastAPIの実装)
```
from fastapi import FastAPI, Request, Form
from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates
import mlflow.pyfunc
import pandas as pd
from typing import Optional

app = FastAPI()
templates = Jinja2Templates(directory="templates")

# モデルをロードする関数
def load_model(model_name: str, model_version: Optional[str] = None):
    if model_version:
        model_uri = f"models:/{model_name}/{model_version}"
    else:
        model_uri = f"models:/{model_name}/Production"
    return mlflow.pyfunc.load_model(model_uri)

# ホーム画面（フォーム）
@app.get("/", response_class=HTMLResponse)
async def read_form(request: Request):
    return templates.TemplateResponse("index.html", {"request": request})

# フォームから予測
@app.post("/predict", response_class=HTMLResponse)
async def predict(
    request: Request,
    MedInc: float = Form(...),
    HouseAge: float = Form(...),
    AveRooms: float = Form(...),
    AveBedrms: float = Form(...),
    Population: float = Form(...),
    AveOccup: float = Form(...),
    Latitude: float = Form(...),
    Longitude: float = Form(...),
    model_name: str = Form("YourModelName"),
    model_version: Optional[str] = Form(None)
):
    input_data = pd.DataFrame([{
        "MedInc": MedInc,
        "HouseAge": HouseAge,
        "AveRooms": AveRooms,
        "AveBedrms": AveBedrms,
        "Population": Population,
        "AveOccup": AveOccup,
        "Latitude": Latitude,
        "Longitude": Longitude
    }])

    try:
        model = load_model(model_name, model_version)
        prediction = model.predict(input_data).tolist()[0]
    except Exception as e:
        return templates.TemplateResponse("index.html", {"request": request, "error": f"予測エラー: {e}"})

    return templates.TemplateResponse("index.html", {"request": request, "result": prediction})
```
**ポイント**
* Jinja2 を利用してフォームを提供（HTMLレンダリング）
* POST /predict エンドポイントでフォーム入力データを受け取り、MLflowで管理されているモデルを用いた推論を実施

`predict.py`
```
import mlflow.pyfunc
import pandas as pd

# Productionステージのモデルを取得
model_name = "CaliforniaHousingModel"
model_uri = f"models:/{model_name}/Production"

# モデルをロード
model = mlflow.pyfunc.load_model(model_uri)

# APIやシステムで予測処理を実行
data = pd.DataFrame([{
    "MedInc": 8.32,
    "HouseAge": 50.0,
    "AveRooms": 6.984,
    "AveBedrms": 1.023,
    "Population": 322.0,
    "AveOccup": 2.555,
    "Latitude": 37.88,
    "Longitude": -122.23
}])

prediction = model.predict(data)
print(prediction)
```
**ポイント**
MLflowのモデルを利用して、サンプルデータの予測を行うスクリプト


`index.html`(テンプレート )
```
<!DOCTYPE html>
<html>
<head>
    <title>予測フォーム</title>
</head>
<body>
    <h1>住宅価格予測フォーム</h1>
    {% if error %}
        <p style="color: red;">{{ error }}</p>
    {% endif %}
    {% if result %}
        <p style="color: green;">予測結果: {{ result }}</p>
    {% endif %}
    <form method="post" action="/predict">
        <label for="MedInc">MedInc:</label>
        <input type="number" step="any" name="MedInc" required><br>
        <label for="HouseAge">HouseAge:</label>
        <input type="number" step="any" name="HouseAge" required><br>
        <input type="submit" value="予測する">
    </form>
</body>
</html>
```
## 3. **FastAPIを起動**
以下のコマンドでFastAPIを起動します。
```
docker-compose up -d --build
```
ブラウザで http://localhost:8000/ にアクセスするとフォームが表示されます。

技術的なポイント
本記事では FastAPI を追加し、 MLflow で管理している機械学習モデルを用いた予測APIを提供する構成を構築しています。

## 4. **注意点**
* MLflowのモデルのバージョン管理
    * モデルは Production ステージ にあるものを取得
    * model_version をオプションで指定可能にし、特定のバージョンを利用することも可能
* エラーハンドリング
    * モデルロード失敗時のエラーハンドリング
    * 予測失敗時のエラーハンドリング（例: 入力データのフォーマット不正）

# まとめ
* FastAPI でMLflowのモデルを利用した推論APIを構築
* Docker Composeで統合し、FastAPIをコンテナとしてデプロイ
* HTMLフォームを通じて簡単にモデルにデータを送信・推論


この記事が参考になったら、ぜひ いいね や フォロー をお願いします！