수학 추론 튜토리얼
수학 추론 튜토리얼 (Math Reasoning)
dspy.ChainOfThought 모듈을 만들어 대수 문제를 풀도록 최적화하는 빠른 예시를 함께 살펴볼게요.
설치는 pip install -U dspy로 최신 DSPy를 받아서 따라오시면 되고, 추가로 pip install datasets도 필요해요.
권장사항: 내부에서 무슨 일이 벌어지는지 이해하려면 MLflow Tracing을 설정해 두세요.
출처: Math Reasoning
MLflow DSPy 통합
MLflow는 DSPy와 기본적으로 통합되는 LLMOps 도구로, 설명 가능성과 실험 추적을 제공해요. 이 튜토리얼에서 MLflow를 쓰면 프로그램 실행마다의 trace를 노트북에서 볼 수 있어요. 모델의 동작을 잘 드러내 주고, 튜토리얼을 진행하면서 DSPy 개념을 이해하는 데 큰 도움이 돼요. 설정은 네 단계로 끝나요.
- MLflow 설치
%pip install mlflow>=2.20
- 별도 터미널에서 MLflow UI 시작
mlflow ui --port 5000
- 노트북을 MLflow에 연결
import mlflow
mlflow.set_tracking_uri("http://localhost:5000")
mlflow.set_experiment("DSPy")
- 추적 활성화
mlflow.dspy.autolog()
통합에 대해 더 알고 싶다면 MLflow DSPy 문서도 함께 보세요.
모델 설정과 데이터 준비
DSPy에게 모듈에서 OpenAI의 gpt-4o-mini를 쓰겠다고 알려줄게요. 인증은 DSPy가 OPENAI_API_KEY에서 알아서 찾아요. 다른 프로바이더나 로컬 모델로도 쉽게 바꿀 수 있어요.
import dspy
gpt4o_mini = dspy.LM('openai/gpt-4o-mini', max_tokens=2000)
gpt4o = dspy.LM('openai/gpt-4o', max_tokens=2000)
dspy.configure(lm=gpt4o_mini) # default LM은 gpt-4o-mini, 특별히 지정하지 않으면 이것을 쓴다
gpt4o_mini는 기본 LM으로, gpt4o는 나중에 최적화의 teacher 모델로 쓸 거예요.
다음으로 MATH 벤치마크에서 데이터 예시를 불러올게요. 최적화에는 훈련 분할을, 평가에는 분리된 dev set을 써요. 먼저 데이터셋 패키지를 설치해야 해요.
%pip install git+https://github.com/hendrycks/math.git
from dspy.datasets import MATH
dataset = MATH(subset='algebra')
print(len(dataset.train), len(dataset.dev))
train과 dev 각각 350개가 나와요. 훈련 세트의 첫 예시를 하나 살펴볼게요.
example = dataset.train[0]
print("Question:", example.question)
print("Answer:", example.answer)
문제는 "운동 시작 시 체중이 244파운드인 사람이 10주 동안 매주 전주 말 체중의 1%씩 빠진다면, 10주 후 체중은 몇 파운드?" 같은 유형이고 답은 221이에요.
모듈 정의와 제로샷 평가
모듈은 아주 단순해요. question을 받아 answer를 만드는 chain-of-thought 한 단계뿐이에요.
module = dspy.ChainOfThought("question -> answer")
module(question=example.question)
ChainOfThought는 reasoning 단계를 거쳐 답을 내요. 위 문제에서는 매주 남는 체중이 99%니까 W_n = W_0 × (0.99)^n, 10주 후 244 × (0.99)^10 ≈ 220.5, 반올림하면 221로 계산해요.
이제 프롬프트 최적화 전에 제로샷 모듈의 평가기를 만들어 볼게요.
THREADS = 24
kwargs = dict(num_threads=THREADS, display_progress=True, display_table=5)
evaluate = dspy.Evaluate(devset=dataset.dev, metric=dataset.metric, **kwargs)
evaluate(module)
결과는 Average Metric: 259.00 / 350 (74.0%) 예요. 평가 표를 보면 각 dev 질문에 대한 정답(example_answer)과 예측(pred_answer)이 나열되고 맞으면 ✔️로 표시돼요.
평가 결과를 MLflow에 기록하기
시간에 따른 평가 결과를 추적·시각화하려면 MLflow Experiment에 기록하면 돼요.
import mlflow
# Start an MLflow Run to record the evaluation
with mlflow.start_run(run_name="math_evaluation"):
kwargs = dict(num_threads=THREADS, display_progress=True)
evaluate = dspy.Evaluate(devset=dataset.dev, metric=dataset.metric, **kwargs)
# Evaluate the program as usual
result = evaluate(module)
# Log the aggregated score
mlflow.log_metric("correctness", result.score)
# Log the detailed evaluation results as a table
mlflow.log_table(
{
"Question": [example.question for example in dataset.dev],
"Gold Answer": [example.answer for example in dataset.dev],
"Predicted Answer": [output[1] for output in result.results],
"Correctness": [output[2] for output in result.results],
},
artifact_file="eval_results.json",
)
result.score는 전체 점수를, result.results는 각 평가 항목의 상세 결과를 담아요.
MIPROv2로 최적화하기
이제 모듈을 최적화할게요. 강한 추론이 필요하니 큰 GPT-4o를 teacher 모델로 써요. teacher는 최적화 시점에 작은 LM의 reasoning을 부트스트랩하는 데 쓰이지만, 프롬프트를 만드는 prompt model이나 학습되는 task model로는 쓰이지 않아요. GPT-4o는 아주 적은 횟수만 호출되고, 최적화에 직접 참여하고 결과 프로그램에서 쓰이는 모델은 GPT-4o-mini예요.
max_bootstrapped_demos=4는 프롬프트에 부트스트랩된 예시를 최대 4개 쓰라는 뜻이고, max_labeled_demos=4는 부트스트랩+사전 라벨 예시 합쳐 최대 4개를 쓰라는 의미예요.
kwargs = dict(num_threads=THREADS, teacher_settings=dict(lm=gpt4o), prompt_model=gpt4o_mini)
optimizer = dspy.MIPROv2(metric=dataset.metric, auto="medium", **kwargs)
kwargs = dict(max_bootstrapped_demos=4, max_labeled_demos=4)
optimized_module = optimizer.compile(module, trainset=dataset.train, **kwargs)
최적화된 모듈을 다시 평가해 볼게요.
evaluate(optimized_module)
결과는 Average Metric: 310.00 / 350 (88.6%) 예요. 단순한 최적화만으로 보류된 dev set에서 정확도가 74%에서 88% 이상으로 올랐어요.
그렇지만 이런 추론 과제에서는 종종 더 고급 전략을 고려하게 돼요.
dspy.ReAct모듈에 계산기 함수나dspy.LocalSandbox를 붙이는 방법- 최적화된 여러 프롬프트를 상단에서 과반수 투표(또는 Aggregator 모듈)로 앙상블하는 방법
무엇이 바뀌었는지 이해하기 위해 최적화 후의 프롬프트를 확인해 볼게요. MLflow 추적을 켰다면 풍부한 trace UI에서 최적화 전후 프롬프트를 비교할 수도 있어요.
dspy.inspect_history()
최적화 후 프롬프트의 system message를 보면 좀 달라져 있어요. 목표가 "question을 분석하고 수학적 추론을 체계적으로 적용해 answer를 도출하라"처럼 다듬어져 있고, reasoning 필드에 각 사고 단계를 서술하도록 지시해요. 그 아래에는 부트스트랩된 few-shot 예시들이 실려 있어요.
예를 들어 |4x+2|=10이고 x<0일 때 x 값을 묻는 문제에는 abs 방정식을 두 가지로 나눠 풀고 x<0 조건을 만족하는 x=-3을 고르는 reasoning이 포함돼 있고, 이차방정식 근의 곱·합으로 b를 구하는 문제, 200피트 울타리로 최대 넓이 직사각형 놀이터를 만드는 문제, 동전 게임의 확률 문제 등 다양한 예시가 there 담겨 있어요. 이런 예시가 더해진 덕분에 모델의 추론 패턴이 개선된 거예요.
더 알아보기 (Learn more)
- MIPROv2 옵티마이저 — 프롬프트 최적화 상세
- ReAct 모듈 — 도구를 쓰는 추론 에이전트