학습·평가 반응형 파이프라인
Ch 01에서 위젯 패널을 만들었습니다. 이제 그 위젯 값을 받아서 모델을 학습하고 평가하는 파이프라인을 연결합니다. 위젯이 바뀌면 학습이 다시 돌고 정확도가 즉시 갱신되는 흐름입니다.
그런데 여기서 중요한 질문이 생깁니다. 슬라이더를 드래그할 때마다 모델이 즉시 재학습되어야 할까요? 탐색 중 n_estimators를 100에서 200으로 옮기는 과정에서 110, 120, 130... 순서로 중간값에서도 학습이 일어납니다. 빠른 모델이라면 문제없지만, 트리 수가 많거나 데이터가 크면 조작 중에 계속 학습이 돌면서 노트북이 버벅이기 시작합니다.
이 챕터는 반응형 학습 파이프라인을 완성하면서, 동시에 비싼 학습을 제어하는 세 가지 방법을 소개합니다. 세 방법은 택일 대안입니다. ml_experiment.py에서는 방법 3(@mo.cache)을 기본으로 채택합니다.
추정기 생성
위젯 값을 읽어서 적절한 추정기를 만드는 함수를 정의합니다. 모델마다 받는 파라미터가 다르므로 분기합니다.
from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifierfrom sklearn.svm import SVCdef build_estimator( model_name: str, n_est: int, max_d: int, lr: float, c_val: float, kernel_val: str, seed: int = 42,): if model_name == "Random Forest": return RandomForestClassifier( n_estimators=n_est, max_depth=max_d, random_state=seed, ) elif model_name == "Gradient Boosting": return GradientBoostingClassifier( n_estimators=n_est, max_depth=max_d, learning_rate=lr, random_state=seed, ) else: # SVC — 스케일링된 데이터를 씀 return SVC( C=c_val, kernel=kernel_val, random_state=seed, )
분기 안에서 해당 추정기가 받는 인자만 넘깁니다. RandomForestClassifier에 learning_rate를 넘기거나, SVC에 n_estimators를 넘기면 TypeError가 발생합니다.
SVC에는 random_state를 넘기지만 kernel이 "linear" 또는 "rbf"일 때는 실제로 결과에 영향을 주지 않습니다. 코드 일관성을 위해 명시합니다.
방법 1: run_button으로 수동 트리거
가장 직접적인 방법입니다. 파라미터를 원하는 값으로 먼저 맞춰두고, 준비가 됐을 때 버튼을 눌러 학습을 시작합니다.
PART 03 Ch 01에서 소개한 mo.ui.run_button을 사용합니다.
run_button = mo.ui.run_button(label="학습 시작")run_button
버튼 위젯을 표시하는 셀과, 실제 학습을 수행하는 셀을 분리합니다. 핵심은 mo.stop과 학습 코드를 같은 셀에 두는 것입니다.
from sklearn.metrics import accuracy_score# run_button.value가 False이면 실행 중단# 이 셀이 run_button을 참조하므로 run_button이 눌릴 때만 학습이 실행됨mo.stop(not run_button.value, mo.md("파라미터를 설정한 뒤 **학습 시작** 버튼을 누르세요."))# 위젯 값으로 추정기 생성 및 학습estimator = build_estimator( model_name=model_selector.value, n_est=n_estimators_slider.value, max_d=max_depth_slider.value, lr=learning_rate_slider.value, c_val=C_slider.value, kernel_val=kernel_selector.value,)# SVC는 스케일링된 데이터, 트리 계열은 원본 데이터 사용if model_selector.value == "SVC": estimator.fit(X_train_scaled, y_train) y_pred = estimator.predict(X_test_scaled)else: estimator.fit(X_train, y_train) y_pred = estimator.predict(X_test)test_accuracy_btn = accuracy_score(y_test, y_pred)mo.md(f"""### 실험 결과| 항목 | 값 ||------|-----|| 모델 | **{model_selector.value}** || 테스트 정확도 | **{test_accuracy_btn:.4f}** |(예시 수치입니다. 실제 값은 파라미터와 실행 환경에 따라 다릅니다.)""")
왜 같은 셀이어야 할까요. marimo는 순서가 아니라 의존성으로 실행 여부를 결정합니다. mo.stop이 별도 셀에 있고 변수를 정의하지 않으면, 학습 셀은 그 셀에 아무 의존성이 없습니다. 결국 슬라이더가 바뀌면 게이트를 무시하고 바로 재실행됩니다. mo.stop과 학습 코드가 같은 셀 안에 있어야 run_button을 참조하는 하나의 실행 단위가 만들어집니다.
버튼을 누르기 전에는 mo.stop이 실행을 중단하고 안내 메시지를 보여줍니다. 버튼을 누르면 run_button.value가 잠깐 참이 되면서 학습이 실행됩니다. 파라미터를 바꿔도 버튼을 다시 누르기 전까지는 재실행되지 않습니다.
방법 2: lazy 모드
run_button은 셀 단위 제어입니다. 노트북 전체를 수동 실행 방식으로 바꾸고 싶다면 lazy 모드를 씁니다.
PART 02 Ch 04에서 설명한 방법입니다. pyproject.toml에 한 줄을 추가합니다.
[tool.marimo.runtime]
on_cell_change = "lazy"
이 설정 이후에는 슬라이더를 움직여도 의존 셀이 즉시 재실행되지 않습니다. 셀이 "stale" 상태로 표시되고, 사용자가 직접 실행 버튼을 클릭하거나 전체 실행 명령을 내릴 때 재계산됩니다.
run_button과 lazy 모드는 목적이 다릅니다.
| 방법 | 제어 범위 | 설정 위치 |
|---|---|---|
mo.ui.run_button + mo.stop |
특정 학습 셀만 게이트 | 코드 |
| lazy 모드 | 노트북 전체 실행 방식 전환 | pyproject.toml |
학습 셀 하나만 수동으로 제어하고 나머지는 반응형으로 두고 싶다면 run_button을 씁니다. 노트북 전체에 비싼 연산이 분산되어 있어서 전반적으로 수동 실행을 원한다면 lazy 모드가 편합니다.
방법 3: @mo.cache로 재계산 방지
슬라이더를 100에서 150으로 올렸다가 100으로 되돌리면, autorun 모드에서는 100으로 돌아왔을 때 다시 학습이 실행됩니다. 이미 한 번 했던 계산인데도요.
@mo.cache를 쓰면 같은 파라미터 조합에서는 이전 결과를 돌려줍니다. PART 04 Ch 05에서 소개한 패턴입니다.
from sklearn.metrics import accuracy_score@mo.cachedef train_and_evaluate( model_name: str, n_est: int, max_d: int, lr: float, c_val: float, kernel_val: str, seed: int = 42,): clf = build_estimator(model_name, n_est, max_d, lr, c_val, kernel_val, seed) if model_name == "SVC": clf.fit(X_train_scaled, y_train) y_pred = clf.predict(X_test_scaled) else: clf.fit(X_train, y_train) y_pred = clf.predict(X_test) acc = accuracy_score(y_test, y_pred) return clf, acc
# 현재 위젯 값으로 함수 호출# 같은 파라미터 조합이면 캐시에서 즉시 반환trained_model, test_accuracy = train_and_evaluate( model_name=model_selector.value, n_est=n_estimators_slider.value, max_d=max_depth_slider.value, lr=learning_rate_slider.value, c_val=C_slider.value, kernel_val=kernel_selector.value,)mo.md(f"""**{model_selector.value}** — 테스트 정확도: **{test_accuracy:.4f}**(예시 수치입니다. 실제 값은 데이터셋과 파라미터에 따라 다릅니다.)""")
train_and_evaluate 함수는 인자를 기반으로 캐시 키를 만듭니다. n_estimators를 100으로 했다가 200으로 바꿨다가 다시 100으로 돌아오면, 세 번째 호출에서는 첫 번째 결과를 캐시에서 가져옵니다. 재학습이 일어나지 않습니다.
ml_experiment.py는 방법 3을 채택합니다. trained_model과 test_accuracy가 이후 셀(Ch 03의 기록 테이블, Ch 04의 모델 저장)에서 참조됩니다.
세 가지 방법을 어떻게 고를까
ML 실험 노트북에서는 세 방법을 상황에 따라 선택합니다.
파라미터 탐색 중일 때 @mo.cache 적용 (동일 조합 재학습 방지) autorun 모드 유지 (즉각 피드백 원함)학습이 특별히 오래 걸릴 때 run_button으로 학습 셀만 게이트 (mo.stop과 같은 셀에!) 또는 pyproject.toml에서 lazy 모드 전환노트북을 정리하고 재현 실행할 때 lazy 모드 해제 후 전체 실행 한 번
이 파트의 예제는 와인 데이터셋으로 빠르게 돌아가지만, @mo.cache를 붙이는 습관은 데이터나 모델이 커졌을 때 바로 효과를 냅니다.
정리
- 추정기 생성 함수에서 모델별로 다른 파라미터를 분기해 넘깁니다.
RandomForestClassifier에는n_estimators와max_depth,GradientBoostingClassifier에는learning_rate추가,SVC에는C와kernel을 사용합니다. mo.ui.run_button+mo.stop으로 학습을 수동 트리거할 때는 반드시 같은 셀에 두어야 합니다. marimo는 의존성 기반으로 실행하므로, 별도 셀의mo.stop은 학습 셀을 게이트하지 못합니다.- lazy 모드는
pyproject.toml의on_cell_change = "lazy"설정으로 노트북 전체를 수동 실행 방식으로 전환합니다. @mo.cache는 학습 함수에 붙여서 동일 파라미터 조합의 재계산을 방지합니다. 파라미터를 왔다갔다하는 탐색 과정에서 재학습 횟수를 크게 줄입니다.ml_experiment.py는 방법 3을 채택합니다.trained_model과test_accuracy가 이후 챕터에서 참조됩니다.- SVC는 스케일링된 데이터를, 트리 계열은 원본 데이터를 씁니다.