IRIS-API-SERVER 라는 폴더(이름맘대로)를 하나 만들어주고 conda create --name iris-api python=3.11 으로 가상환경 만들어주고 activate 후 pip install poetry 으로 poetry 설치한 다음 poetry init
뭐 영어 뜨면 대충 엔터치고 no 선택하고 마지막은 yes로 셋팅하면 폴더에 pyproject.toml 파일이 생길 것이다.
저번 머신러닝 파이프라인 실습했던 환경과 일치시켜보자
poetry add fastapi
poetry add mlflow==1.27.0
poetry add boto3
poetry add sqlalchemy==1.4.52
poetry add psycopg2-binary
poetry add scikit-learn
poetry add pandas
poetry add 'uvicorn[standard]' - standard로 깔아야 속도빠름(uvloop)
history 치면 명령어 쳤던거 기록 볼 수 있다app폴더 생성하고 안에 main.py 만들자
먼저 FastAPI 몸체가 될 App 객체를 정의합니다.
배포 시 healthcheck를 위한 엔드포인트도 하나 정의해둡니다.
main.py 에서 작업했다
# app/main.py
from fastapi import FastAPI
from app import routers
app = FastAPI()
app.include_router(routers.router)
@app.get("/healthcheck")
async def healthcheck():
return {"status": "ok"}
작성 후 uvicorn app.main:app --reload 명령어로 서버 띄워보자
정상작동한 모습
그 후 http://127.0.0.1:8000/docs 가서 제대로 뜨는지 확인.
예측 API 함수를 작성합니다. API 함수는 별도의 Router 객체에 구현합니다.
기능 별로 API 엔드포인트를 모듈로 분리하여 개발하는 것이 유지보수성에 좋기 때문
routers/ml_router.py 파일에 작성합니다.
# app/routers/ml_router.py
import pandas as pd
from fastapi import APIRouter
from app.schemas.request import IrisReq
from app.schemas.response import ResponseModel, IrisResp
from app.core.model_registry import model_registry
router = APIRouter()
@router.post("/predict", response_model=ResponseModel)
def predict(request: IrisReq):
result = model_registry.get_model("iris_model").predict(
pd.DataFrame([request.model_dump()])
)
return ResponseModel(status_code=200, data=IrisResp(target=result))
예측 API 함수를 작성하기 전에 Request, Response Body로 사용할 Pydantic Model을 정의합니다.
app/schemas/request.py response.py에 작성합니다.
Pydantic Base Model을 사용함으로써 입/출력 값의 검증을 자동으로 수행할 수 있습니다.
# app/schemas/request.py
from pydantic import BaseModel, Field
class IrisReq(BaseModel):
sepal_length: float = Field(..., example=5.1,
description="Sepal length")
sepal_width: float = Field(..., example=3.5,
description="Sepal width")
petal_length: float = Field(..., example=1.4,
description="Petal length")
petal_width: float = Field(..., example=0.2,
description="Petal width")
# app/schemas/response.py
from pydantic import BaseModel, Field
class IrisResp(BaseModel):
target: int = Field(..., example=0,
description="Predicted class")
class ResponseModel(BaseModel):
status_code: int = Field(..., example=200,
description="Status code")
data: IrisResp = Field(...,
description="Response data")
/predict라는 경로를 갖는 POST API를 작성합니다.
# app/routers/ml_router.py
import pandas as pd
from fastapi import APIRouter
from app.schemas.request import IrisReq
from app.schemas.response import ResponseModel, IrisResp
from app.core.model_registry import model_registry
router = APIRouter()
@router.post("/predict", response_model=ResponseModel)
def predict(request: IrisReq):
result = model_registry.get_model("iris_model").predict(
pd.DataFrame([request.model_dump()])
)
return ResponseModel(status_code=200, data=IrisResp(target=result))
@router.post("/predict", response_model=ResponseModel)
-> 작성한 Response Model도 response_model로 지정해줍니다.
def predict(request: IrisReq):
->정의한 Pydantic Request Model을 request의 hint로 작성하여 Request Body Model로 지정합니다.
result = model_registry.get_model("iris_model").predict(
pd.DataFrame([request.model_dump()])
)
-> 요청 데이터를 DataFrame으로 만들고, 모델 예측을 진행합니다.
return ResponseModel(status_code=200, data=IrisResp(target=result))
->예측된 결과를 Request Model 구조에 맞게에 담아 결과를 반환합니다.
Mlflow에서 모델을 불러오는 함수를 작성합니다.
이미 Mlflow 및 Minio 연동은 되어있다고 가정하고 다음과 같이 작성합니다.
# app/utils.py
import mlflow
def load_model():
print("Loading model")
return mlflow.sklearn.load_model("models:/iris_model/production")
# app/core/model_registry.py
class ModelRegistry:
def __init__(self):
self.models = {}
def register_model(self, model_name, model):
self.models[model_name] = model
def get_model(self, model_name):
return self.models[model_name]
def clear(self):
self.models.clear()
model_registry = ModelRegistry()
Mlflow에서 불러온 모델을 저장해둘 Model Repository 라는 객체를 생성해둡니다.
서버 실행 시 해당 객체에 필요한 모델들을 저장하고 요청마다 모델을 꺼내 사용할 수 있도록 합니다.
# app/core/model_registry.py
class ModelRegistry:
def __init__(self):
self.models = {}
def register_model(self, model_name, model):
self.models[model_name] = model
def get_model(self, model_name):
return self.models[model_name]
def clear(self):
self.models.clear()
model_registry = ModelRegistry()
서버가 실행될 때 모델을 불러올 수 있도록 lifespan 함수를 작성합니다.
lifespan 함수는 API 서버가 실행될 때 수행할 사전, 사후 작업을 정의할 수 있습니다.
이제 서버가 실행 될 때 load_model 이라는 함수를 통해 모델을 불러옵니다.
모델을 불러와 사전에 정의된 model_registry에 저장합니다.
# app/main.py
from contextlib import asynccontextmanager
from fastapi import FastAPI
from app import routers
from app.core.model_registry import model_registry
from app.utils import load_model
@asynccontextmanager
async def lifespan(app: FastAPI):
model_registry.register_model("iris_model", load_model())
yield
model_registry.clear()
app = FastAPI(lifespan=lifespan)
app.include_router(routers.router)
코드 작성이 완료 되었으면, 준비된 다음 쉘 코드를 실행합니다.
bash start.sh 혹은 zsh start.sh로 실행합니다.
해당 쉘 코드에는 mlflow, minio의 접속 정보를 포함하고 있습니다.
# start.sh
export AWS_ACCESS_KEY_ID=mlflow_admin
export AWS_SECRET_ACCESS_KEY=mlflow_admin
export MLFLOW_S3_ENDPOINT_URL=http://localhost:9000
export MLFLOW_TRACKING_URI=http://localhost:5000
uvicorn app.main:app --reload


