# Part 8: FastAPI를 이용한 모델 서빙 (8주차) **⬅️ 이전 시간: [Part 7.5: LLM 애플리케이션 개발과 LangChain](./part_7.5_llm_application_development_with_langchain.md)** | **➡️ 다음 시간: [Part 9: 프로덕션 레벨 API와 Docker](./part_9_production_ready_api.md)** --- ## 📜 실습 코드 바로가기 - **`part_8_model_serving_with_fastapi`**: [바로가기](./source_code/part_8_model_serving_with_fastapi) - 본 파트에서 다루는 FastAPI 서버 예제 코드(`main.py`, `requirements.txt` 등)를 직접 실행하고 수정해볼 수 있습니다. --- 지금까지 우리는 Scikit-learn과 PyTorch를 이용해 강력한 AI 모델을 만드는 방법을 배웠습니다. 하지만 이 모델들은 아직 우리 컴퓨터 안에만 머물러 있습니다. 어떻게 하면 이 모델의 예측 능력을 다른 사람이나 서비스에게 제공할 수 있을까요? > **💡 비유: '나만 아는 맛집 레시피를 세상에 공개하기'** > > 우리가 AI 모델을 만든 것은, 세상에 없는 '비법 레시피'를 개발한 것과 같습니다. 하지만 이 레시피를 내 서랍 속에만 둔다면 아무도 그 맛을 볼 수 없습니다. > > **[API](./glossary.md#api-application-programming-interface) 서버**는 이 비법 레시피로 요리를 만들어 손님에게 판매하는 **'레스토랑'**을 차리는 과정입니다. 손님(클라이언트)은 주방(모델 서버)이 어떻게 돌아가는지 몰라도, 메뉴판(API 문서)을 보고 주문하면 맛있는 요리(예측 결과)를 받을 수 있습니다. > > 이번 주차에는 파이썬의 최신 웹 프레임워크인 **[FastAPI](./glossary.md#fastapi)**를 이용해 빠르고 세련된 AI 레스토랑을 여는 방법을 배워보겠습니다. --- ### 📖 8주차 학습 목표 - **API의 개념 이해**: '레스토랑 주문 카운터' 비유를 통해 API가 왜 필요하며, 어떤 역할을 하는지 이해합니다. - **FastAPI로 서버 구축**: FastAPI를 설치하고, uvicorn으로 내 컴퓨터에서 웹 서버를 실행하는 방법을 배웁니다. - **예측 API 구현**: 우리가 만든 머신러닝 모델을 FastAPI 서버에 탑재하여, 외부 요청에 따라 예측 결과를 반환하는 API를 만듭니다. - **데이터 유효성 검사**: **[Pydantic](./glossary.md#pydantic)**을 이용해 '깐깐한 주문서 양식'처럼 API로 들어오는 데이터의 형식을 정의하고 자동으로 검증하는 방법을 익힙니다. - **자동 API 문서 활용**: FastAPI의 강력한 기능인 **[Swagger UI](./glossary.md#swagger-ui--openapi)**를 통해, 코드를 건드리지 않고 브라우저에서 직접 API를 테스트하는 방법을 배웁니다. --- ### 1일차: FastAPI 입문 - 우리의 AI 레스토랑 개점 #### **API란 무엇일까?** [API(Application Programming Interface)](./glossary.md#api-application-programming-interface)는 소프트웨어들이 서로 정보를 주고받는 '약속된 방식'입니다. 우리 'AI 레스토랑'에서는 "이런 형식으로 주문(요청)하면, 저런 형식으로 요리(응답)를 내어드립니다"라고 정해놓은 **'주문 창구'** 와 같습니다. #### **왜 FastAPI일까?** **[FastAPI](./glossary.md#fastapi)**는 우리 레스토랑을 위한 최고의 선택지입니다. - **엄청난 속도**: 주문이 밀려들어도 막힘없이 처리하는 '베테랑 직원'처럼 매우 빠릅니다. (Node.js, Go와 비슷한 성능) - **쉬운 메뉴 관리**: '레시피(코드)'가 간결하고 명확해서, 실수를 줄이고 새로운 메뉴를 빠르게 추가하기 쉽습니다. - **스마트 메뉴판 자동 생성**: 코드를 짜면 손님들이 직접 보고 테스트할 수 있는 **자동 대화형 문서([Swagger UI](./glossary.md#swagger-ui--openapi))**를 공짜로 만들어줍니다. #### **개발 환경 준비** 먼저 FastAPI와 우리 레스토랑의 '서버 매니저' 역할을 할 [uvicorn](./glossary.md#uvicorn--gunicorn)을 설치합니다. ```bash pip install fastapi "uvicorn[standard]" ``` #### **우리의 첫 FastAPI 앱** `main.py` 파일을 만들어 레스토랑의 기본 틀을 잡아봅니다. ```python # main.py from fastapi import FastAPI # 1. FastAPI 인스턴스 생성 (레스토랑 개점) app = FastAPI() # 2. API 엔드포인트 정의 (메뉴판에 메뉴 추가) # "/" 주소로 GET 요청(가장 기본적인 방문)이 오면, 아래 함수를 실행 @app.get("/") def read_root(): return {"message": "어서오세요! 저의 AI 레스토랑입니다!"} ``` - `app = FastAPI()`: FastAPI 레스토랑을 개점합니다. - `@app.get("/")`: 손님이 가게의 정문(`/`)으로 들어와 **"여기는 뭐하는 곳인가요?"(GET 요청)** 라고 물어보면, 아래 `read_root` 직원이 응답하라는 의미입니다. #### **서버 실행하기** 터미널에서 `main.py`가 있는 디렉토리로 이동한 뒤, 서버 매니저 uvicorn을 실행시킵니다. ```bash uvicorn main:app --reload ``` - `main`: `main.py` 파일 (우리 레스토랑의 레시피 북) - `app`: 파일 안에 있는 `app` 객체 (레스토랑 그 자체) - `--reload`: 레시피를 수정할 때마다 가게를 재오픈할 필요 없이, 매니저가 알아서 반영해주는 편리한 옵션입니다. 서버가 실행되면 웹 브라우저에서 http://127.0.0.1:8000 주소로 접속해보세요. 직원의 환영 메시지가 보인다면 성공입니다! --- ### 2-3일차: 예측 API 구현 - 레스토랑의 시그니처 메뉴 만들기 이제 개점한 레스토랑에 6주차에서 만들었던 **붓꽃 품종 예측 모델(iris_model.pkl)**을 올린 '시그니처 메뉴'를 추가해보겠습니다. > **[중요]** `iris_model.pkl` 파일을 `main.py` 파일과 같은 폴더에 두어야 합니다. 필요한 라이브러리도 설치해주세요. > ```bash > pip install scikit-learn joblib numpy > ``` #### **모델과 데이터 형식 정의하기** 손님이 어떤 형식으로 주문해야 하는지 명확한 '주문서 양식'이 필요합니다. **[Pydantic](./glossary.md#pydantic)** 라이브러리가 이 역할을 멋지게 수행합니다. 또한, 무거운 주방기구(모델)를 손님이 올 때마다 껐다 켰다 하는 것은 비효율적입니다. 서버가 시작될 때(가게 오픈) **단 한번만** 모델을 메모리에 로드하도록 **lifespan** 이벤트를 사용합니다. ```python # main.py (수정) from fastapi import FastAPI, HTTPException from pydantic import BaseModel from typing import List import joblib import numpy as np from contextlib import asynccontextmanager # --- '가게 오픈/마감 준비'를 위한 Lifespan 이벤트 --- @asynccontextmanager async def lifespan(app: FastAPI): # 서버 시작 시 (가게 오픈) print("레스토랑 오픈: 시그니처 메뉴(모델)를 준비합니다.") try: # 무거운 주방기구(모델)를 미리 켜놓습니다. app.state.model = joblib.load("iris_model.pkl") app.state.class_names = ['setosa', 'versicolor', 'virginica'] print("모델 준비 완료!") except FileNotFoundError: print("경고: 모델 파일(iris_model.pkl)을 찾을 수 없습니다.") app.state.model = None yield # 여기서 애플리케이션이 실행됨 (손님 받기) # 서버 종료 시 (가게 마감) print("레스토랑 마감") # --- FastAPI 앱 생성 및 Lifespan 연결 --- app = FastAPI(lifespan=lifespan) # --- Pydantic으로 '주문서 양식' 정의 --- class IrisFeatures(BaseModel): features: List[float] # 4개의 float 값으로 이루어진 리스트 # 스마트 메뉴판에 보여줄 주문 예시 class Config: json_schema_extra = { "example": { "features": [5.1, 3.5, 1.4, 0.2] # setosa 품종 데이터 } } # --- 기본 엔드포인트 --- @app.get("/") def read_root(): return {"message": "붓꽃 품종 예측 레스토랑"} ``` #### **예측 엔드포인트 구현** 이제 실제 요리(예측)를 하고 결과를 내어주는 `/predict` 창구를 만듭니다. '요리 주문'은 새로운 것을 만들어달라는 요청이므로 POST 방식으로 받습니다. ```python # main.py (아래에 추가) # --- 예측 엔드포인트 (시그니처 메뉴 주문 창구) --- @app.post("/predict") def predict_iris(data: IrisFeatures): """ 붓꽃의 특징 4가지를 주문서(JSON)로 받아 요청하면, 예측된 품종과 그에 대한 신뢰도 점수를 요리(JSON)로 만들어 반환합니다. """ model = app.state.model if model is None: raise HTTPException(status_code=503, detail="죄송합니다. 주방(모델)이 준비되지 않았습니다.") # 주문서 양식 확인 if len(data.features) != 4: raise HTTPException(status_code=400, detail="주문서에 4개의 특징(float)을 정확히 기입해주세요.") # 주방(모델)이 알아듣는 형태로 재료 손질 model_input = np.array(data.features).reshape(1, -1) # 요리(예측) 시작 prediction_idx = model.predict(model_input)[0] prediction_proba = model.predict_proba(model_input)[0] # 결과 플레이팅 class_name = app.state.class_names[prediction_idx] confidence = prediction_proba[prediction_idx] return { "주문하신 재료": data.features, "예측된 요리": class_name, "셰프의 자신감": float(confidence) } ``` - `@app.post("/predict")`: `/predict` 창구에서는 **POST** 주문을 받습니다. - `data: IrisFeatures`: 손님의 주문(JSON)을 우리가 정의한 `IrisFeatures` 양식에 맞춰 자동으로 확인하고, 문제가 없으면 `data` 변수에 담아줍니다. 형식이 틀리면 FastAPI가 알아서 "주문 잘못하셨어요" 하고 오류를 보냅니다. --- ### 4-5일차: API 테스트와 종합 - 레스토랑 시식회 이제 완성된 우리 레스토랑의 시그니처 메뉴를 맛볼 시간입니다. FastAPI의 가장 강력한 기능, **스마트 메뉴판(자동 대화형 API 문서)**을 이용해봅시다. #### **Swagger UI로 API 테스트하기** 1. `uvicorn main:app --reload` 명령으로 서버가 실행 중인지 확인합니다. 2. 웹 브라우저에서 http://127.0.0.1:8000/docs 로 접속합니다. > **💡 비유: '스마트 메뉴판 Swagger UI'** > > `.../docs`는 단순한 메뉴판이 아닙니다. > - **모든 메뉴(엔드포인트)가 목록으로** 보입니다. > - 각 메뉴를 클릭하면 **필요한 재료(요청 형식)와 나오는 요리(응답 형식)**에 대한 상세한 설명이 나옵니다. > - 가장 멋진 기능! **`Try it out`** 버튼을 누르면 메뉴판에서 바로 **'맛보기 주문'**을 넣어볼 수 있습니다. 주방에 직접 가지 않아도 그 자리에서 바로 요리 결과를 확인할 수 있습니다. 3. `POST /predict` 메뉴를 클릭하여 펼친 뒤 `Try it out` 버튼을 누릅니다. 4. **Request body**에 예시로 채워진 주문서를 그대로 두거나 다른 값으로 바꿔봅니다. ```json { "features": [6.7, 3.0, 5.2, 2.3] } ``` 5. `Execute` 버튼을 눌러 주문을 주방으로 전송하고, 그 아래에서 방금 나온 따끈따끈한 요리(예측 결과)를 실시간으로 확인할 수 있습니다. --- ## 📝 8주차 요약 이번 주에는 FastAPI를 이용하여 머신러닝 모델을 API로 만드는 첫걸음을 떼었습니다. - **FastAPI**: 'AI 레스토랑'을 만드는 웹 서버 프레임워크 FastAPI를 배우고 **Uvicorn**으로 실행했습니다. - **lifespan**: 서버 시작 시 모델('주방기구')을 효율적으로 로드했습니다. - **Pydantic**: 입력 데이터('주문서')의 형식을 강제하고 유효성을 검사했습니다. - **Swagger UI**: '/docs'의 '스마트 메뉴판'을 통해 API를 쉽고 빠르게 테스트했습니다. 다음 시간에는 이 '1인 식당'을 여러 셰프와 직원이 협업하는 '프랜차이즈 레스토랑'으로 확장하는 방법을 배웁니다. 프로젝트의 구조를 체계적으로 개선하고, **[Docker](./glossary.md#docker)**를 이용해 우리 레스토랑을 어떤 쇼핑몰에든 쉽게 입점시키는 '밀키트' 기술을 배우겠습니다. ## 🔍 8주차 연습 문제 **문제 1: 7주차 Fashion-MNIST 모델 서빙하기** - 7주차에서 만들었던 PyTorch CNN 모델(`SimpleCNN`)을 저장하고, 이 모델을 로드하여 예측하는 FastAPI 서버를 만들어보세요. - **요구사항**: 1. `torch.save(model.state_dict(), 'fashion_mnist_cnn.pth')`로 모델의 가중치를 저장합니다. 2. 서버 시작 시(lifespan)에 모델 구조를 만들고 저장된 가중치를 `model.load_state_dict(...)`로 불러옵니다. `model.eval()` 모드로 설정하는 것을 잊지 마세요. 3. 입력 데이터는 28x28 픽셀 값을 펼친 784개의 float 리스트로 받도록 Pydantic 모델을 정의하세요. 4. 예측 엔드포인트(`POST /predict/fashion`)는 입력 리스트를 받아 `torch.Tensor`로 변환하고, 모델에 맞는 모양(`1, 1, 28, 28`)으로 reshape하여 예측을 수행해야 합니다. 5. 예측 결과로 가장 확률이 높은 클래스의 이름(e.g., 'T-shirt/top', 'Sneaker')을 반환하세요. **문제 2: 책 정보 조회 API 만들기** - 간단한 인-메모리(in-memory) 딕셔너리를 '데이터베이스'로 사용하여, 책 정보를 조회하는 API를 만들어보세요. - **요구사항**: 1. 서버에 `books_db = {1: {"title": "클린 코드", "author": "로버트 C. 마틴"}, 2: ...}` 와 같이 책 데이터를 미리 만들어두세요. 2. `GET /books/{book_id}` 엔드포인트를 만드세요. 3. **경로 매개변수**로 받은 `book_id`를 이용하여 `books_db`에서 책을 찾아 정보를 반환하세요. 4. 만약 해당 `book_id`가 DB에 없다면, FastAPI의 `HTTPException`을 이용하여 `404 Not Found` 오류와 함께 "해당 ID의 책을 찾을 수 없습니다." 메시지를 반환하세요. **➡️ 다음 시간: [Part 9: 프로덕션 레벨 API와 Docker](./part_9_production_ready_api.md)**