결과 비교와 재현성 확보
파라미터를 바꿀 때마다 정확도가 즉시 표시됩니다. 하지만 슬라이더를 계속 움직이다 보면 "아까 n_estimators 150에서 좋은 결과가 나왔는데, 얼마였더라?" 하는 상황이 옵니다. 현재 값만 보여주는 화면으로는 이전 실험을 기억하기 어렵습니다.
이 챕터에서는 두 가지를 더합니다. 하나는 실험 결과를 데이터프레임에 누적해서 파라미터 조합별 성능을 나란히 비교하는 기록 테이블입니다. 다른 하나는 재현성 확보입니다. 같은 파라미터를 다시 입력했을 때 같은 결과가 나오려면 어떤 조건을 갖춰야 하는지 정리합니다.
실험 결과 누적 기록
학습 후 결과가 나오면 그 조합을 기록 테이블에 추가합니다. 여러 번 실험한 결과가 쌓이면 비교가 가능해집니다.
marimo의 반응형 모델에서 변경 가능한 상태를 유지하려면 mo.state를 사용합니다.
import pandas as pd# 실험 기록을 저장할 상태# 초깃값은 빈 데이터프레임get_history, set_history = mo.state( pd.DataFrame(columns=[ "모델", "n_estimators", "max_depth", "learning_rate", "C", "kernel", "정확도" ]))
결과 기록 버튼을 만들 때, on_change 콜백으로 클릭 즉시 히스토리를 추가하도록 연결합니다.
import pandas as pddef add_experiment_record(_click): current_df = get_history() new_row = pd.DataFrame([{ "모델": model_selector.value, "n_estimators": n_estimators_slider.value if model_selector.value != "SVC" else None, "max_depth": max_depth_slider.value if model_selector.value != "SVC" else None, "learning_rate": learning_rate_slider.value if model_selector.value == "Gradient Boosting" else None, "C": C_slider.value if model_selector.value == "SVC" else None, "kernel": kernel_selector.value if model_selector.value == "SVC" else None, "정확도": round(test_accuracy, 4), }]) updated = pd.concat([current_df, new_row], ignore_index=True) set_history(updated)log_button_with_action = mo.ui.button( label="결과 기록", on_change=add_experiment_record,)log_button_with_action
버튼을 클릭할 때마다 현재 파라미터 조합과 정확도가 히스토리 데이터프레임에 추가됩니다. mo.state로 관리하기 때문에 위젯이 바뀌어도 이미 기록된 결과는 사라지지 않습니다.
기록 테이블 출력
누적된 결과를 표로 보여줍니다.
import pandas as pdhistory_df = get_history()if history_df.empty: history_display = mo.md("아직 기록된 실험이 없습니다. 학습 후 **결과 기록** 버튼을 누르세요.")else: history_display = mo.vstack([ mo.md(f"### 실험 기록 ({len(history_df)}건)"), mo.ui.table(history_df), ])history_display
mo.ui.table로 출력하면 열 정렬과 필터링이 가능한 인터랙티브 테이블이 됩니다. 정확도 열을 클릭해 내림차순 정렬하면 어떤 조합이 가장 좋았는지 바로 보입니다.
결과 비교 차트
기록이 쌓이면 차트로 비교합니다.
import plotly.express as pximport pandas as pdhistory_df = get_history()if len(history_df) >= 2: # 실험 번호 컬럼 추가 chart_df = history_df.copy() chart_df["실험 번호"] = range(1, len(chart_df) + 1) chart_df["레이블"] = chart_df.apply( lambda r: f"{r['모델']} #{int(r['실험 번호'])}", axis=1 ) fig = px.bar( chart_df, x="레이블", y="정확도", color="모델", title="실험별 테스트 정확도 비교", range_y=[0, 1], text="정확도", ) fig.update_traces(texttemplate="%{text:.4f}", textposition="outside") fig.update_layout(xaxis_title="실험", yaxis_title="정확도") comparison_chart = figelse: comparison_chart = mo.md("실험을 두 건 이상 기록하면 비교 차트가 나타납니다.")comparison_chart
재현성 확보
실험을 재현한다는 것은 같은 코드, 같은 데이터, 같은 파라미터를 넣으면 언제나 같은 결과가 나온다는 뜻입니다. ML에서 재현성을 깨는 주요 원인은 무작위성입니다.
random_state 두 곳에 모두 고정
재현성을 위해 두 곳에 시드를 고정해야 합니다.
첫째, 데이터 분할입니다. train_test_split에 random_state를 주지 않으면 실행할 때마다 훈련 세트와 테스트 세트가 달라집니다. 파라미터가 같아도 데이터가 다르면 정확도가 다르게 나옵니다.
from sklearn.model_selection import train_test_split# random_state 고정 필수 — 없으면 실행마다 분할이 달라짐X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42, stratify=y)
둘째, 추정기 자체입니다. 트리 계열 알고리즘은 특성 선택, 노드 분할 등에 내부 무작위성이 있습니다. random_state를 고정해야 같은 파라미터에서 같은 모델이 만들어집니다.
from sklearn.ensemble import RandomForestClassifierclf = RandomForestClassifier( n_estimators=100, max_depth=5, random_state=42, # 이 값도 고정)clf.fit(X_train, y_train)
이 두 가지를 지키면, 같은 파라미터 조합을 다시 입력했을 때 기록 테이블의 이전 결과와 일치하는 정확도가 나옵니다.
@mo.cache와 재현성
Ch 02에서 소개한 @mo.cache는 재현성을 추가로 보장합니다. 같은 파라미터 조합으로 train_and_evaluate를 다시 호출하면 재계산 없이 캐시된 결과를 반환합니다. 학습 중 시드를 고정했으므로 새로 계산해도 같은 결과가 나오지만, 캐시를 쓰면 계산 자체를 건너뛰기 때문에 더 확실합니다.
비교 레이아웃 구성
기록 테이블과 비교 차트를 하나의 패널로 묶습니다.
results_panel = mo.vstack([ mo.hstack([log_button_with_action], justify="start"), history_display, comparison_chart,])results_panel
정리
mo.state로 실험 기록 데이터프레임을 유지합니다. 위젯이 바뀌어도 이미 누적된 결과는 사라지지 않습니다.mo.ui.button의on_change콜백으로 버튼 클릭 시 기록을 추가합니다.set_history로 상태를 갱신하면 기록 테이블이 즉시 갱신됩니다.- 정확도 수치는 파라미터, 데이터, 시드에 따라 달라집니다. 교재에 적힌 수치는 예시입니다.
- 재현성은
train_test_split과 추정기 생성 두 곳 모두에random_state를 고정해야 확보됩니다. - 실험 기록을
px.bar로 비교 차트로 만들면 어떤 파라미터 조합이 가장 좋은 성능을 냈는지 한눈에 파악할 수 있습니다.