LLM 엔지니어링13장 · REST API 와 FastAPI14 / 22← 이전목차다음 →MetaContents

13장. REST API 와 FastAPI

실습 1 — 배송 지연 모델을 파일로 저장하기

1장의 배송 데이터(delivery.csv, 가상 데이터)를 정제하고, 범주 열을 원-핫으로 바꾸는 단계와 로지스틱 회귀를 한 파이프라인으로 묶어 학습합니다. 학습한 파이프라인을 판, 기준값, 성능과 함께 out_delay_model.joblib 로 저장하고, 다시 불러 같은 답이 나오는지 확인합니다.

1단계. 데이터 정제하기(1차시와 같은 방법)

Python
import pandas as pd

df = pd.read_csv("delivery.csv")
df = df.drop_duplicates()                                  # 중복 행 지우기
df = df[df["거리km"] < 9999].copy()                        # 입력 실수(9999) 빼기
df["무게kg"] = df["무게kg"].fillna(df["무게kg"].median())  # 빈 무게는 가운데 값으로
df["날씨"] = df["날씨"].fillna("모름")                     # 빈 날씨는 "모름" 범주로
print(df.shape, "· 지연 비율", round(df["지연"].mean(), 3))
출력
(1194, 10) · 지연 비율 0.206

2단계. 전처리와 모델을 파이프라인으로 묶어 학습하기

Python
from sklearn.compose import ColumnTransformer
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score, recall_score, roc_auc_score
from sklearn.model_selection import train_test_split
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import OneHotEncoder

NUM = ["주문시각", "거리km", "무게kg"]
CAT = ["지역", "상품분류", "날씨", "택배사"]
X, y = df[NUM + CAT], df["지연"]
X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.25, random_state=42, stratify=y)

# ColumnTransformer: 범주 열은 원-핫으로 바꾸고 숫자 열은 그대로 둔다
# handle_unknown="ignore": 학습 때 없던 범주가 와도 오류 대신 모두 0 으로 둔다
prep = ColumnTransformer([("cat", OneHotEncoder(handle_unknown="ignore"), CAT)], remainder="passthrough")
pipe = Pipeline([("prep", prep), ("model", LogisticRegression(max_iter=1000))])
pipe.fit(X_tr, y_tr)                                       # 전처리와 학습을 한 번에

prob = pipe.predict_proba(X_te)[:, 1]
metrics = {"정확도": accuracy_score(y_te, prob >= 0.5), "재현율": recall_score(y_te, prob >= 0.5),
           "AUC": roc_auc_score(y_te, prob)}
print({k: round(v, 3) for k, v in metrics.items()})
출력
{'정확도': 0.819, '재현율': 0.226, 'AUC': 0.695}

3단계. 판과 함께 저장하기

Python
import joblib, sklearn
from pathlib import Path

bundle = {"파이프라인": pipe, "판": "delay-lr-1", "기준값": 0.5, "숫자열": NUM, "범주열": CAT,
          "범주값": {c: sorted(df[c].unique().tolist()) for c in CAT},      # API 의 입력 검사에 쓴다
          "시험성능": {k: round(v, 3) for k, v in metrics.items()}, "sklearn": sklearn.__version__}
joblib.dump(bundle, "out_delay_model.joblib")
print("저장:", f"{Path('out_delay_model.joblib').stat().st_size / 1024:.1f}KB")
print(bundle["범주값"])
출력
저장: 4.4KB
{'지역': ['경기', '서울', '인천', '지방'], '상품분류': ['가전', '도서', '생활용품', '식품', '의류'], '날씨': ['눈', '맑음', '모름', '비', '흐림'], '택배사': ['빠른별택배', '새벽길택배', '한결택배']}

4단계. 다시 불러 같은 답인지 보기

Python
loaded = joblib.load("out_delay_model.joblib")
order = pd.DataFrame([{"주문시각": 22, "거리km": 197.6, "무게kg": 4.6, "지역": "지방", "상품분류": "도서",
                       "날씨": "눈", "택배사": "빠른별택배"}])
p1 = pipe.predict_proba(order)[0, 1]
p2 = loaded["파이프라인"].predict_proba(order)[0, 1]
print("저장 전", round(p1, 4), "· 불러온 뒤", round(p2, 4), "· 같다:", p1 == p2, "· 판", loaded["판"])
출력
저장 전 0.6887 · 불러온 뒤 0.6887 · 같다: True · 판 delay-lr-1

이 실습이 뜻하는 것

이 실습은 맛집의 요리를 밀키트로 포장해 두는 일과 같습니다. 재료를 손질하고(1단계), 손질법과 조리법을 한 상자에 묶어 맛을 봅니다(2단계). 상자에 제조 번호, 기준, 맛 평가표를 붙여 냉동실에 넣고(3단계), 다시 꺼냈을 때 같은 맛인지 확인합니다(4단계). 여기서 손질법이 원-핫 전처리, 조리법이 로지스틱 회귀, 한 상자가 파이프라인, 제조 번호가 판 delay-lr-1, 냉동실이 out_delay_model.joblib 파일입니다. 비유와 다른 점은, 음식은 다시 데우면 맛이 조금 변하지만 모델 파일은 불러와도 계산 결과가 똑같아야 한다는 것입니다.

단계출력뜻
1. 정제(1194, 10) · 지연 비율 0.206정제 뒤 주문 1,194건이 남았고, 다섯 건에 한 건꼴(0.206)이 지연입니다
2. 성능{'정확도': 0.819, '재현율': 0.226, 'AUC': 0.695}시험용 주문의 82%쯤을 맞혔지만, 실제 지연 주문 가운데 찾아낸 몫은 0.226 으로 네 건에 한 건이 안 됩니다. AUC(Area Under the Curve, 곡선 아래 넓이)는 0.5 가 동전 던지기, 1.0 이 완벽이니 0.695 는 가르는 힘이 그리 크지 않다는 뜻입니다
3. 저장저장: 4.4KB, 범주값 사전모델과 판, 기준값, 성능, 고를 수 있는 범주값까지 4.4KB(킬로바이트) 파일 하나에 담겼습니다
4. 다시 불러오기저장 전 0.6887 · 불러온 뒤 0.6887 · 같다: True밤 10시, 지방, 눈 오는 날의 도서 주문은 지연 확률이 약 0.689 이고, 파일에서 꺼낸 모델도 똑같은 값을 냈습니다

한 줄로 줄이면, 학습한 모델을 전처리까지 한 파일로 묶어 두면 다른 프로그램이 그대로 꺼내 같은 답을 낼 수 있다는 실습입니다. 실습 2부터 이 파일을 API 가 읽어 씁니다. 다만 지연 비율이 0.206 이라 모두 "정상"이라고만 해도 정확도가 0.79 쯤 나옵니다. 정확도 0.819 만 보고 좋은 모델이라고 읽으면 안 되고, "같은 답이 나온다"는 것도 "맞는 답"이라는 뜻은 아닙니다.

코드 설명

  • 1단계는 1장 실습 2와 같은 정제로 1,194행이 남고 지연 비율은 0.206 입니다. 빈 날씨는 1장와 달리 "모름"이라는 범주로 남겨, API 가 날씨를 모를 때 보낼 값으로 씁니다.
  • ColumnTransformer 는 열마다 다른 전처리를 붙이고, Pipeline 은 전처리와 모델을 하나로 묶습니다. 묶어 저장하면 API 는 원래 꼴의 표만 넘기면 되어, 학습 때와 다른 전처리를 하는 실수를 막습니다. 시험 성능은 정확도 0.819, 재현율 0.226, AUC 0.695 로, 1장처럼 지연을 놓치는 일이 많은 모델입니다. 이 장은 모델을 잘 만드는 것보다 내보이는 것이 목적이라 그대로 씁니다.
  • 3단계는 파이프라인과 함께 판(delay-lr-1), 기준값, 열 이름, 범주값, 시험 성능, scikit-learn 판(1.9.1)을 한 사전에 담아 joblib.dump 로 저장합니다. 파일은 4.4KB 입니다.
  • 4단계에서 불러온 모델은 저장 전과 같은 확률(0.6887)을 냈습니다. 밤 10시, 지방, 눈 오는 날의 도서 주문은 지연 확률이 0.689 입니다.
← 이전 절13장 · 실습 지도다음 절 →13장 · 실습 2 — FastAPI 로 예측 API 만들기