13장. REST API 와 FastAPI
모델을 애플리케이션과 잇기
빵집은 손님이 올 때마다 반죽부터 시작하지 않습니다. 아침에 구워 둔 빵을 진열하고, 주문이 오면 바로 내줍니다. 모델도 학습은 미리 해서 파일로 저장해 두고, 서비스는 그 파일을 한 번 불러 놓고 요청이 올 때마다 예측만 합니다.
왼쪽은 모델을 앱의 코드 안에서 바로 부르는 방법으로, 간단하지만 앱마다 모델과 라이브러리를 들고 있어야 합니다. 가운데는 모델을 API 서버로 따로 띄우고 여러 앱이 부르는 방법으로, 이 장에서 만듭니다. 오른쪽은 밤마다 모든 주문의 예측을 미리 계산해 표에 저장해 두는 방법(배치)으로, 실시간이 필요 없을 때 씁니다. 여기서 구워 둔 빵이 저장한 모델 파일입니다.
세 방법 견주기
| 방법 | 좋은 점 | 아쉬운 점 | 맞는 때 |
|---|---|---|---|
| 코드 안에 넣기 | 빠르고 간단하다 | 앱마다 모델을 들고, 바꾸려면 앱을 다시 배포 | 앱 하나, 작은 모델 |
| API 서버 | 여러 앱이 같은 모델을 쓴다. 모델만 따로 바꾼다 | 네트워크 시간, 서버 운영 | 여러 앱, 자주 바뀌는 모델, LLM |
| 미리 계산(배치) | 요청 때 계산이 없다 | 새 입력에는 답이 없다 | 정해진 대상의 하루치 점수 |
모델을 파일로 저장하고 불러오기
scikit-learn 모델은 joblib 으로 저장합니다. 모델과 함께 판, 기준값 같은 정보도 사전에 담아 저장해 두면 API 가 함께 알려 줄 수 있습니다.
import io
import joblib
from sklearn.linear_model import LogisticRegression
model = LogisticRegression().fit([[0], [1], [2], [3]], [0, 0, 1, 1])
buf = io.BytesIO() # 예시라 파일 대신 메모리에 쓴다
joblib.dump({"모델": model, "판": "demo-1", "기준값": 0.5}, buf)
buf.seek(0)
saved = joblib.load(buf) # 불러오면 학습한 모델이 그대로 돌아온다
print(saved["판"], saved["모델"].predict_proba([[2.5]]).round(3))코드에 나오는 함수
| 코드 | 쉬운 뜻 |
|---|---|
LogisticRegression().fit(X, y) | 로지스틱 회귀 모델을 만들고 학습한다. 여기서는 숫자 넷과 정답 넷의 아주 작은 예다 |
io.BytesIO() | 메모리 속에 만든 바이트 파일이다. 디스크에 파일을 남기지 않고 저장을 흉내 낸다 |
joblib.dump(객체, buf) | 객체를 바이트로 바꿔(직렬화) buf 에 쓴다 |
buf.seek(0) | 읽는 위치를 맨 앞으로 돌린다. 방금 쓴 끝에서 읽으면 아무것도 없다 |
joblib.load(buf) | 바이트를 읽어 원래 객체로 되살린다 |
predict_proba([[2.5]]) | predict probability(확률 예측)의 줄임이다. 입력 2.5 가 0 일 확률과 1 일 확률을 돌려준다 |
.round(3) | NumPy 배열의 숫자를 소수 셋째 자리까지 반올림한다 |
이 코드가 뜻하는 것
게임을 저장하면 캐릭터의 레벨과 장비가 저장 파일에 들어가고, 다음에 불러오면 그 상태에서 바로 이어 합니다. 저장 칸에 "1장 보스 앞" 같은 메모를 붙여 둘 수도 있습니다. 여기서 캐릭터가 학습한 모델, 저장 파일이 joblib 으로 쓴 바이트(이 예에서는 메모리), 저장 칸의 메모가 판과 기준값입니다. 다만 게임 저장 파일과 달리 joblib 파일은 불러올 때 안의 코드가 실행될 수 있어, 남이 준 파일은 함부로 불러오지 않습니다.
| 줄 | 하는 일 | 쉽게 말하면 |
|---|---|---|
model = LogisticRegression().fit(...) | 작은 모델을 학습한다 | 캐릭터를 키운다 |
buf = io.BytesIO() | 메모리 속 저장 자리를 만든다 | 빈 저장 칸 |
joblib.dump({...}, buf) | 모델과 판, 기준값을 사전으로 묶어 저장한다 | 저장하면서 메모를 붙인다 |
buf.seek(0) | 처음부터 읽을 준비를 한다 | 저장 파일을 맨 앞부터 연다 |
saved = joblib.load(buf) | 저장한 사전을 되살린다 | 불러오기 |
print(saved["판"], ...) | 판과 2.5 의 예측 확률을 보인다 | 메모와 캐릭터 상태 확인 |
실행하면 demo-1 [[0.277 0.723]] 이 나옵니다. 앞의 0.277 은 0 일 확률, 뒤의 0.723 은 1 일 확률입니다. 2.5 는 정답이 1 인 2, 3 쪽에 가까워서 1 일 확률이 더 높게 나왔고, 불러온 모델이 학습한 그대로 예측한다는 것을 보여 줍니다.
주의할 점
joblib,pickle파일은 불러올 때 그 안의 코드가 실행될 수 있습니다. 믿을 수 있는 곳에서 만든 모델 파일만 불러옵니다.- 저장할 때와 불러올 때의 scikit-learn 판이 다르면 경고가 나거나 결과가 달라질 수 있습니다. 판을 함께 기록합니다(실습 1).