1. はじめに
以前、車の色を判定するAIモデルを構築しましたが、これをAPIとして提供する方法を模索していました。そこで、Pythonで簡単に実装できるFastAPIの技術を用いて、Web APIとして実装を試みました。本記事では、その際に得られた知見やコード、そして遭遇したエラーとその解決策について詳しく解説します。
2. 対象読者
本稿は、以下のような方々を対象としています。
- PythonだけでAPIを構築したいが、具体的な方法がわからない方。
- AIモデルの実装は行ったものの、それをどうAPI化すればよいかわからない方。
- APIを自力で実装し、その詳細な解説も求めている方。
3. 設計概要
今回のプロジェクトは、AIモデルをWeb APIとして公開することを目的としており、以下の3つの主要なファイルで構成されています。
- `predict.py`: AI予測モデルの実装を行います。事前に学習させたパラメータを`car_color_model.pth`から読み込み、予測処理を実行します。学習元データも`dataset_split`に格納します。
- `main.py`: FastAPIを用いてAPIのGetとPostのエンドポイントを実装します。基本的には画像ファイルを受け取り、予測結果をJSON形式で返却するAPIです。
- `client.py`: APIを呼び出すクライアント側のPythonコードを実装します。`test(2).jpg`という画像ファイルをAPIに送信し、その結果を受け取ります。
4. フォルダ構成
今回のプロジェクトは、以下の階層的なフォルダ構成で管理されています。これにより、コード、データ、モデルファイルが整理され、管理がしやすくなります。
app/
├─ dataset_split/
│ ├─ train/ # 学習用画像フォルダ
│ └─ test/ # テスト用画像フォルダ
├─ car_color_model.pth # 学習済みモデル
├─ predict.py # 画像予測処理の関数
├─ main.py # FastAPIアプリ本体
├─ client.py # テスト用クライアント
└─ test(2).jpg # テスト用画像
5. 各プログラムコードの解説
ここでは、各Pythonコードの役割と具体的な内容について説明します。
5-1. predict.py
説明:車の画像から、その車がどんな色なのかを判定するAIモデルです。
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader
from torchvision import transforms , datasets
from torchvision.datasets import ImageFolder
import timm
from PIL import Image
def predict_image(file):
# 画像の前処理を行うTransformを定義
transform = transforms.Compose([
# 256×256に画像サイズを変更
transforms.Resize((256,256)),
# 50%の確率で画像を左右反転
transforms.RandomHorizontalFlip(p=0.5),
# 画像をテンソル形式に変換
transforms.ToTensor(),
# 画像の正規化
transforms.Normalize([0.485,0.456,0.406],[0.229,0.224,0.225])
])
# 画像フォルダからデータセットを作成し、クラス名を取得
# 注:このパスは実行環境に合わせて修正してください
dataset = datasets.ImageFolder("C:/Users/yaku2/OneDrive/デスクトップ/car_color/dataset_split/train",transform=transform)
class_names = dataset.classes
num_classes = len(dataset.classes)
# timmライブラリを利用して、ResNet18モデルを作成
model = timm.create_model("resnet18",pretrained=False,num_classes=num_classes)
# 事前学習済みパラメータ「car_color_model.pth」を読み込み、CPUで起動
model.load_state_dict(torch.load(f"car_color_model.pth" ,map_location=torch.device('cpu')))
# 予測対象の画像を開き、前処理とバッチ次元を追加
image = Image.open(file).convert("RGB")
input_tensor = transform(image).unsqueeze(0)
# モデルの勾配計算をオフにして、推論モードに
with torch.no_grad():
# 入力テンソルをモデルに渡し、クラスごとのスコアを算出
outputs = model(input_tensor)
# スコアの中から最も高いもののインデックスを取得
preds = outputs.max(1)[1]
# テンソルから整数へ変換
predicted_class_index = preds.item()
# クラス名リストから予測結果を返却
predicted_class_name = class_names[predicted_class_index]
return predicted_class_name
5-2. main.py
説明:車の画像を受け取り、予測結果をJSON形式で返すAPIの実装。
ワンポイント解説:
-
FastAPIとは?
PythonでAPIを開発するためのWebフレームワーク。APIドキュメントの自動生成や、高速なパフォーマンスがメリットです。 -
Getリクエストとは?
情報を読み取り専用で取得したいときに利用するHTTPメソッド。 -
Postリクエストとは?
情報をもとに何かしらの処理(更新・追加など)を行った上で、情報を返却したいときに利用するHTTPメソッド。
from fastapi import FastAPI, UploadFile, File
from predict import predict_image
app = FastAPI()
# ルートエンドポイント(確認用)
@app.get("/") # HTTP GET リクエストで "/" にアクセスされたときに呼ばれる
def read_root():
# 単純にJSONでメッセージを返す
return {"message": "Car Color Prediction API"}
# 画像予測エンドポイント
@app.post("/predict") # HTTP POST リクエストで "/predict" にアクセスされたときに呼ばれる
async def predict(file: UploadFile = File(...)):
"""
file: クライアントが送信した画像ファイル
"""
prediction = predict_image(file.file)
return {"prediction": prediction}
5-3. client.py
説明:APIを呼び出すためのPythonクライアントコード。
ワンポイント解説:
-
requestsとは?
HTTPリクエストを簡単に送信できるPythonライブラリ。今回はAPIを構築したローカルホストにアクセスします。
import requests
url = "http://127.0.0.1:8001/predict/"
#APIへ画像ファイルのリクエストを送信
with open("test (2).jpg","rb") as f:
files = {"file":("test(2).jpg",f,"image/jpeg")}
res = requests.post(url , files=files)
print(res.status_code)
print(res.text)
6. APIの呼び出し
ここでは、構築したAPIを実際に呼び出す手順を解説します。
- 2つのターミナルを開き、どちらもアプリケーションのフォルダに移動します。
- 1つ目のターミナルで、以下のコマンドを実行してFastAPIサーバーを起動します。
uvicorn main:app --host 0.0.0.0 --port 8001
解説:
- `uvicorn`: FastAPIを起動するためのライブラリです。
- `main:app`: `main.py`ファイル内の`app`インスタンスを起動する指示です。
- `--host 0.0.0.0 --port 8001`: すべてのネットワークインターフェイスからポート8001でアクセス可能にします。
実行すると、以下のような結果が出力され、サーバーが起動します。
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Uvicorn running on http://0.0.0.0:8001 (Press CTRL+C to quit)
3. 2つ目のターミナルで、以下のコマンドを実行して`client.py`を動かします。
python client.py
実行後、APIへの接続情報と画像の判定結果がJSON形式で返却されます。
200
{"prediction":"rio red"}
7. 発生したエラーとその対処法
本プロジェクトの構築中に遭遇した、代表的なエラーとその解決策をまとめました。
1. ファイルパスが見つからないエラー
症状:
- `FileNotFoundError: [WinError 3] 指定されたパスが見つかりません。`というエラーが発生する。
原因:
- `predict.py`内のデータセットのパスが正しくない。
- パスの記述方法がWindows環境とPythonの仕様に合っていない(例: `\`ではなく`/`を使用)。
- Dockerを使用している場合に、ローカルのファイルがコンテナに正しくマウントされていない。
対処法:
- `ImageFolder`のパスを環境に合わせた正しい絶対パス、または相対パスに修正する。
- Dockerを使用する場合は、`docker-compose.yml`の`volumes`セクションに`- ./dataset_split:/app/dataset_split`を追加し、ファイルがコンテナ内にアクセス可能になるようにする。
2. ポート使用中によるバインドエラー
症状:
- `ERROR: [Errno 10048] error while attempting to bind on address ('0.0.0.0', 8000)` のように、指定したポートがすでに使用中であるというエラーが発生し、サーバーが起動できない。
原因:
- すでに`8000`番ポートが他のプロセスで使用されている。
対処法:
- `netstat -ano | findstr :8000`のようなコマンドで、ポートを使用しているプロセスID(PID)を特定し、`taskkill /PID [PID] /F`で終了させる。
- または、`uvicorn main:app --host 0.0.0.0 --port 8001`のように別のポートを指定して起動する。
3. `WatchFiles`による自動リロードの挙動
症状:
- `uvicorn --reload`コマンド実行後、`WARNING: WatchFiles detected changes...`や`INFO: Waiting for application startup.`のログが表示されたまま止まったように見える。
原因:
- `--reload`機能がファイルを監視しているため、一見止まっているように見える。特にWindowsのOneDrive環境では、ファイルの同期が原因でリロードがループすることがある。
対処法:
- サーバーは実際には起動しているため、ブラウザや`curl`でアクセスして動作を確認する。
- 開発環境を安定させたい場合は、`--reload`オプションをつけずに`uvicorn main:app --host 0.0.0.0 --port 8000`で起動する。
4. 相対インポートによるモジュール読み込みエラー
症状:
- `ERROR: Error loading ASGI app. Could not import module "main".`のように、モジュールのインポートに失敗する。
原因:
- `from .predict import predict_image`のような相対インポートは、直接`python main.py`で実行すると失敗することがある。
対処法:
- 絶対インポートに変更する。`main.py`と`predict.py`が同じディレクトリにあることを確認した上で、`from predict import predict_image`と記述する。
まとめ
本稿では、AIモデルをFastAPIでAPI化する一連の流れと、その際に発生しうる主要なエラーについて解説しました。
- FastAPIは`uvicorn`を使ってCLIで起動する必要があります。
- `volumes`と`ports`の設定を適切に行うことで、ローカルでの開発とブラウザからのアクセスが両立できます。
- エラー発生時は、ログメッセージをよく読み、ファイルパスやポート番号、Pythonのバージョンなどの環境設定を確認することが重要です。
これらのポイントを押さえることで、DockerとFastAPIを使ったAIモデルのAPI化を効率的に進めることができるでしょう。