iBetter Books
수정

학습·평가 반응형 파이프라인

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,        )

분기 안에서 해당 추정기가 받는 인자만 넘깁니다. RandomForestClassifierlearning_rate를 넘기거나, SVCn_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_modeltest_accuracy가 이후 셀(Ch 03의 기록 테이블, Ch 04의 모델 저장)에서 참조됩니다.

세 가지 방법을 어떻게 고를까

ML 실험 노트북에서는 세 방법을 상황에 따라 선택합니다.

파라미터 탐색 중일 때  @mo.cache 적용 (동일 조합 재학습 방지)  autorun 모드 유지 (즉각 피드백 원함)학습이 특별히 오래 걸릴 때  run_button으로 학습 셀만 게이트 (mo.stop과 같은 셀에!)  또는 pyproject.toml에서 lazy 모드 전환노트북을 정리하고 재현 실행할 때  lazy 모드 해제 후 전체 실행 한 번

이 파트의 예제는 와인 데이터셋으로 빠르게 돌아가지만, @mo.cache를 붙이는 습관은 데이터나 모델이 커졌을 때 바로 효과를 냅니다.

정리

  • 추정기 생성 함수에서 모델별로 다른 파라미터를 분기해 넘깁니다. RandomForestClassifier에는 n_estimatorsmax_depth, GradientBoostingClassifier에는 learning_rate 추가, SVC에는 Ckernel을 사용합니다.
  • mo.ui.run_button + mo.stop으로 학습을 수동 트리거할 때는 반드시 같은 셀에 두어야 합니다. marimo는 의존성 기반으로 실행하므로, 별도 셀의 mo.stop은 학습 셀을 게이트하지 못합니다.
  • lazy 모드는 pyproject.tomlon_cell_change = "lazy" 설정으로 노트북 전체를 수동 실행 방식으로 전환합니다.
  • @mo.cache는 학습 함수에 붙여서 동일 파라미터 조합의 재계산을 방지합니다. 파라미터를 왔다갔다하는 탐색 과정에서 재학습 횟수를 크게 줄입니다.
  • ml_experiment.py는 방법 3을 채택합니다. trained_modeltest_accuracy가 이후 챕터에서 참조됩니다.
  • SVC는 스케일링된 데이터를, 트리 계열은 원본 데이터를 씁니다.