14장. Streamlit 화면과 Docker 컨테이너
실습 1 — 예측 화면 만들기
13장 실습 1이 저장한 배송 지연 모델(out_delay_model.joblib)을 쓰는 Streamlit 화면을 out_app_predict.py 로 만듭니다. Streamlit 의 AppTest 로 브라우저 없이 화면을 실행해, 칸을 채우고 단추를 눌러 결과를 읽습니다. streamlit 이 설치되어 있어야 하고, 13장 실습 1을 먼저 실행해야 합니다.
1단계. 화면 파일 쓰기
from pathlib import Path
Path("out_app_predict.py").write_text('''"""배송 지연 예측 화면. 실행: streamlit run out_app_predict.py"""
import joblib
import pandas as pd
import streamlit as st
@st.cache_resource # 모델은 한 번만 불러 모든 사용자가 함께 쓴다
def load_bundle():
return joblib.load("out_delay_model.joblib")
bundle = load_bundle()
choices = bundle["범주값"]
st.title("배송 지연 예측")
st.caption(f"모델 판 {bundle['판']} · 시험 AUC {bundle['시험성능']['AUC']}")
with st.form("order"): # 폼: 칸을 다 채우고 제출을 눌러야 한 번에 다시 실행된다
left, right = st.columns(2)
with left:
지역 = st.selectbox("지역", choices["지역"], key="지역")
상품분류 = st.selectbox("상품분류", choices["상품분류"], key="상품분류")
택배사 = st.selectbox("택배사", choices["택배사"], key="택배사")
날씨 = st.selectbox("날씨", choices["날씨"], key="날씨")
with right:
주문시각 = st.slider("주문시각", 0, 23, 14, key="주문시각")
거리km = st.number_input("거리km", 0.5, 1000.0, 20.0, key="거리km")
무게kg = st.number_input("무게kg", 0.0, 100.0, 2.0, key="무게kg")
sent = st.form_submit_button("예측하기")
if sent:
row = pd.DataFrame([{"주문시각": 주문시각, "거리km": 거리km, "무게kg": 무게kg, "지역": 지역,
"상품분류": 상품분류, "날씨": 날씨, "택배사": 택배사}])
p = float(bundle["파이프라인"].predict_proba(row)[0, 1])
st.metric("지연 확률", f"{p:.1%}")
if p >= bundle["기준값"]:
st.warning("지연될 수 있습니다. 고객에게 미리 알려 주세요.")
else:
st.success("제때 도착할 것으로 보입니다.")
''', encoding="utf-8")
print("화면 파일:", Path("out_app_predict.py").stat().st_size, "바이트")화면 파일: 1854 바이트2단계. 브라우저 없이 화면 실행하기
from streamlit.testing.v1 import AppTest
# AppTest: 화면 스크립트를 이 프로그램 안에서 실행하고, 그려진 요소를 꺼내 볼 수 있게 한다
at = AppTest.from_file(str(Path("out_app_predict.py").resolve()), default_timeout=60).run()
print("제목:", at.title[0].value, "·", at.caption[0].value)
print("선택 상자:", [(s.label, s.value, len(s.options)) for s in at.selectbox])
print("단추:", [b.label for b in at.button], "· 지연 확률 칸:", len(at.metric)) # 제출 전이라 결과가 없다제목: 배송 지연 예측 · 모델 판 delay-lr-1 · 시험 AUC 0.695
선택 상자: [('지역', '경기', 4), ('상품분류', '가전', 5), ('택배사', '빠른별택배', 3), ('날씨', '눈', 5)]
단추: ['예측하기'] · 지연 확률 칸: 03단계. 칸을 채우고 제출하기
# 밤 10시, 지방, 눈, 도서 주문(13차시와 같은 주문)
at.selectbox(key="지역").set_value("지방")
at.selectbox(key="상품분류").set_value("도서")
at.selectbox(key="택배사").set_value("빠른별택배")
at.selectbox(key="날씨").set_value("눈")
at.slider(key="주문시각").set_value(22)
at.number_input(key="거리km").set_value(197.6)
at.number_input(key="무게kg").set_value(4.6)
at.button[0].click().run() # 제출 단추를 누르면 스크립트가 다시 실행된다
print(at.metric[0].label, at.metric[0].value, "·", [w.value for w in at.warning], [s.value for s in at.success])지연 확률 68.9% · ['지연될 수 있습니다. 고객에게 미리 알려 주세요.'] []4단계. 다른 주문으로 다시 제출하기
at.selectbox(key="지역").set_value("서울")
at.selectbox(key="날씨").set_value("맑음")
at.selectbox(key="택배사").set_value("한결택배")
at.slider(key="주문시각").set_value(9)
at.number_input(key="거리km").set_value(7.3)
at.button[0].click().run()
print(at.metric[0].label, at.metric[0].value, "·", [w.value for w in at.warning], [s.value for s in at.success])
print("오류:", [e.value for e in at.exception])지연 확률 8.3% · [] ['제때 도착할 것으로 보입니다.']
오류: []이 실습이 뜻하는 것
이 실습은 택배 접수 창구에 주문을 넣는 기계를 들이는 일과 같습니다. 먼저 기계 화면의 설계도를 써 두고(1단계), 손님 없이 기계를 켜서 칸과 단추가 제대로 나오는지 봅니다(2단계). 그다음 시험 손님이 칸을 채우고 "예측하기"를 누르고(3단계), 다른 주문으로 한 번 더 누릅니다(4단계). 여기서 창구 기계가 Streamlit 화면, 설계도가 out_app_predict.py, 시험 손님이 AppTest, 기계 안에서 판단하는 담당이 13장의 배송 지연 모델입니다. 비유와 다른 점은, 진짜 손님(브라우저)은 아직 오지 않았고 화면을 같은 파이썬 프로그램 안에서만 실행했다는 것입니다.
| 단계 | 출력 | 뜻 |
|---|---|---|
| 1. 화면 파일 | 1854 바이트 | 화면 설계도가 파이썬 파일로 저장되었습니다. 아직 실행하지 않고 글로만 써 둔 단계입니다 |
| 2. 제목 | 모델 판 delay-lr-1 · 시험 AUC 0.695 | 화면이 모델 파일을 읽어 모델의 이름표와 시험 성적을 보였습니다. AUC(Area Under the Curve, 곡선 아래 넓이)는 0.5 가 찍기 수준이고 1 에 가까울수록 늦는 주문과 제때 오는 주문을 잘 가른다는 점수입니다 |
| 2. 선택 상자 | 지역 4, 상품분류 5, 택배사 3, 날씨 5 | 상자마다 고를 수 있는 보기의 수입니다. 보기는 모델 파일에 저장해 둔 범주값에서 읽어 옵니다. 첫 값은 보기의 첫째인 "경기"입니다 |
| 2. 단추 | ['예측하기'], 지연 확률 칸 0 | 아직 아무도 누르지 않아 결과 칸이 없습니다. 화면은 제출한 뒤에야 결과를 그립니다 |
| 3. 제출 | 지연 확률 68.9%, 경고 문구 | 밤 10시, 지방, 눈 오는 날의 도서 주문은 열에 일곱쯤 늦는다고 봤고, 기준값을 넘어 노란 경고 상자를 띄웠습니다 |
| 4. 다시 제출 | 지연 확률 8.3%, 성공 문구 | 서울의 아침 주문은 열에 하나도 안 되게 늦는다고 봤고, 초록 성공 상자가 나왔습니다 |
| 4. 오류 | 오류: [] | 칸을 바꿔 두 번 제출하는 동안 파이썬 오류가 하나도 없었습니다 |
한 줄로 줄이면, 13장의 모델을 사람이 칸을 채워 쓰는 화면으로 감싸고, 브라우저 없이 끝까지 눌러 본 실습입니다. 이 화면은 모델 파일을 직접 들고 있습니다. 실습 3에서는 모델을 API(Application Programming Interface, 프로그램끼리 주고받는 창구) 뒤로 옮겨도 같은 주문에 같은 68.9% 가 나오는지 봅니다. 68.9% 는 모델의 추정일 뿐 실제로 늦는다는 보장이 아니고, 데이터도 연습용으로 지어낸 것입니다.
코드 설명
- 화면 파일은 위에서 아래로 읽히는 보통 스크립트입니다.
@st.cache_resource를 붙인load_bundle()은 처음 한 번만 모델을 불러오고, 다시 실행될 때는 저장해 둔 모델을 돌려줍니다. 선택 상자의 보기는 모델 파일에 저장해 둔 범주값(13장 실습 1)에서 읽습니다. AppTest.from_file(...).run()은 화면 스크립트를 한 번 실행하고, 그려진 요소를at.title,at.selectbox,at.metric처럼 종류별 목록으로 돌려줍니다. 처음 실행에서는 선택 상자 넷(지역 보기 4개, 상품분류 5개, 택배사 3개, 날씨 5개)과 "예측하기" 단추가 그려졌고, 아직 제출하지 않았으므로 지연 확률 칸은 0개입니다. 상자의 첫 값은 보기의 첫째인 "경기"입니다.- 3단계에서
set_value()로 칸을 채우고 단추의click().run()으로 제출하자, 13장와 같은 주문의 지연 확률 68.9% 와 노란 경고 상자가 그려졌습니다. 4단계의 서울 아침 주문은 8.3% 와 초록 성공 상자입니다. 같은at으로 칸만 바꿔 다시 제출할 수 있고, 오류는 없었습니다. st.form안의 위젯은 값을 바꿔도 바로 다시 실행되지 않고,st.form_submit_button을 누를 때 한 번에 다시 실행됩니다. 칸이 여럿인 입력에서 쓸데없는 다시 실행을 줄입니다.