Streamlit 사용자를 위한 3 가지 팁

Streamlit은 사용자 친화적인 프레임워크이지만, 사용하다보면 제한적인 기능이 발목을 붙잡습니다. 제가 경험하고, 중요하다고 생각했던 세 가지 팁을 공유합니다.
Dec 09, 2023
Streamlit 사용자를 위한 3 가지 팁
(last update: 2023.12.12)

CSS Hack

Streamlit의 기본 UI가 간결하다는 점은 장점도, 단점도 될 수 있습니다. 다만 지금까지 경험상, 그리고 주변 Streamlit 사용자들의 피드백을 들어보면 UI 커스텀이 어렵다는 점이 가장 먼저 단점으로 꼽히곤 했습니다. 이를 극복할 수 있는 방법 중 하나는 st.markdown API를 이용해 직접 CSS를 수정하는 것입니다. HTML 문서에 CSS를 적용하는 것과 유사하게, Streamlit 페이지의 HTML 태그에 원하는 스타일을 직접 적용할 수 있습니다. 예를 들어, 페이지 내의 스크롤바를 좀 더 두껍게 하고 싶다면 아래와 같은 코드를 스크립트의 아무 곳에나 추가해주면 됩니다. 이 때, unsafe_allow_html=True 옵션을 필수적으로 적용해줘야 합니다.
st.markdown(""" <style> ::-webkit-scrollbar { width: 15px; } </style> """, unsafe_allow_html=True)
Streamlit을 통해 웹 페이지 배포까지 생각해보신 분들이라면, 우측 상단의 메뉴버튼이나 페이지 하단의 Footer가 신경쓰이셨을 수도 있어요. 아래와 같이 설정해 메뉴머튼, Footer 텍스트를 보이지 않게 설정해줄 수 있습니다.
st.markdown(""" <style> #MainMenu {visibility: hidden;} footer {visibility: hidden;} </style> """, unsafe_allow_html=True)
 

Matplotlib 대신 Altair 사용하기

Streamlit 앱에 차트를 포함할 때, 저는 Matplotlib 대신 Altair를 선호합니다. 선호할 뿐만 아니라, Matplotlib는 쓰지 말아야 한다고 생각하고 있습니다. Streamlit은 웹 페이지 배포를 위한 라이브러리로, 멀티스레드 환경에서 안정적으로 작동해야 합니다. 웹 페이지를 배포한다는 것은 필연적으로 여러 유저가 동시에 웹 페이지에 접속하게(멀티스레드를 사용하게) 됨을 의미합니다.
Matplotlib는 데이터 시각화를 위해 많이 사용되고 있지만, 스레드 안정성을 보장하지는 않습니다. Matplotlib 공식 문서에서도 멀티스레드 환경에 대한 조치는 개발자가 적절히 조치를 취해야 한다고 언급하고 있습니다. Altair가 스레드 세이프하게 동작한다는 언급은 (지금까지 찾아본 바로는)없지만, 경험적으로 Streamlit에서 연동을 지원하는 차트 관련 라이브러리들 중 빠르고 안정적인 편에 속했습니다. Altair 차트가 좀 더 깔끔하고 예쁜 것 같기도…!
  • 스레드 안정성: 멀티스레드 환경에서 공유된 자원에 대한 여러 스레드의 접근을 안전하게 관리하는 능력
 

Caching과 UnhashableParamError

데이터 사이언스 분야에서 파이썬이 많이 사용되고 있기도 하고, 최근 Streamlit 홈페이지에 LLM을 사용한 페이지가 많이 소개되기도 해서 그런지 Streamlit을 사용해 AI 모델을 서빙하는 앱을 만들어보려는 시도가 종종 보이는 것 같습니다. Streamlit Community Cloud에서는 하나의 앱에 리소스를 1GB 까지만 할당을 해주기도 하고, 다른 배포 방식을 사용하더라도 AI 모델의 배포에서 리소스 관리는 필수적이기 때문에, Streamlit에서 제공하는 @st.cache_data 혹은 @st.cache_resource를 사용하게 됩니다. 이 때 마주할 수 있는 오류 중 UnhashableParamError 을 해결하는 방법을 설명합니다.
UnhashableParamError가 발생할 수 있는 시나리오 중 하나는 커스텀 클래스 객체를 @st.cache_data 혹은 @st.cache_resource가 적용된 함수에 전달하는 것입니다.
 
예를 들어, 아래와 같은 시나리오를 가정해보겠습니다.
  • Streamlit으로 머신러닝/딥러닝 웹 앱을 만들어보고자 함
  • Hyperparameters를 커스텀하기 위해 ModelConfig 라는 커스텀 클래스를 정의
  • train_model 함수의 인자로 ModelConfig 클래스를 사용
  • train_model 함수에는 같은 하이퍼파라미터로 훈련을 반복하는 것을 막기 위해 @st.cache_resource 적용
 
위와 같은 시나리오 대로 간단한 예제를 아래와 같이 작성해보았습니다.
import streamlit as st from sklearn.ensemble import RandomForestClassifier from sklearn.datasets import load_iris class ModelConfig: def __init__(self, n_estimators: int, max_depth: int): self.n_estimators = n_estimators self.max_depth = max_depth @st.cache_resource def train_model(config: ModelConfig): data = load_iris() model = RandomForestClassifier(n_estimators=config.n_estimators, max_depth=config.max_depth) model.fit(data.data, data.target) return model n_estimators = st.slider("n_estimators", 10, 100, 30) max_depth = st.slider("max_depth", 1, 10, 5) model_config = ModelConfig(n_estimators, max_depth) if st.button("Train Model"): model = train_model(model_config) st.write("Model trained with configuration:", model.get_params())
 
위 코드를 실행하면 UnhashableParamError가 발생합니다. @st.cache_data , @st.chcae_resource는 함수의 반환 값을 캐시하고, 캐시는 함수의 매개변수를 기반으로 관리됩니다. 입력 매개변수가 해시 가능해야 하는데, ModelConfig는 커스텀 클래스이므로 해싱할 수 없기 때문에 에러가 발생하는거죠.
이를 해결하기 위한 첫 번째 방법은 아래와 같이 @st.cache_resourcehash_funcs 인자를 추가하고, lambda를 사용해n_estimatorsmax_depth 속성을 튜플로 반환해주면 UnhashableParamError 가 발생하지 않습니다.
... @st.cache_resource(hash_funcs={ModelConfig: lambda config: (config.n_estimators, config.max_depth)}) ...
두 번째 방법은 Python에서 표준으로 제공하는 dataclasses 라이브러리를 활용하는 것입니다. frozen=True로 설정된 dataclass는 Immutable Object로 처리되고, 해시 가능하게 됩니다. 이렇게 하면 hash_funcs를 사용하지 않아도 됩니다. @dataclass를 사용하면 __init__, __repr__, __eq__ 등의 메소드를 자동으로 생성해주므로, 속성은 클래스 레벨에서 정의합니다.
from dataclasses import dataclass @dataclass(frozen=True) class ModelConfig: n_estimators: int max_depth: int @st.cache_resource def train_model(config: ModelConfig): ...
  • 해당 방법 언급해주신 이근호님께 감사드립니다!

References

 
Share article
RSSPowered by inblog