직접 해 보기: 그리드월드 Q-러닝 (절벽 걷기로 Q-러닝 vs SARSA)
NumPy로 절벽 걷기 격자를 만들어 Q-러닝과 SARSA를 직접 돌려 비교하는 실습
먼저 알아야 할 것
다루는 내용
4×12 절벽 걷기 격자 환경과 표 형식 Q-러닝 · SARSA를 NumPy 60줄 남짓으로 직접 구현하고 실제 실행 결과를 읽습니다. Q-러닝은 최단 경로(−13)를, SARSA는 안전한 먼 길(−17)을 배우지만 학습 중 점수는 SARSA가 더 좋은 이유를 확인합니다. ε 감쇠 · 걸음 크기 변경 확장 과제와 탐욕 정책이 맴도는 이상 사례까지 다룹니다.
해설
직접 해 보기: 그리드월드 Q-러닝 (절벽 걷기로 Q-러닝 vs SARSA)
앞 수업에서 손으로 계산한 Q-러닝 · SARSA 갱신식을 파이썬 60줄 남짓으로 직접 돌려 봅니다. 무대는 Sutton · Barto의 유명한 절벽 걷기(Cliff Walking) 격자입니다. 같은 경험을 하고도 Q-러닝은 절벽 바로 옆 최단 경로를, SARSA는 멀찍이 돌아가는 안전한 경로를 배우는 모습을 출력으로 확인합니다. 필요한 것은 NumPy 하나뿐입니다.
| 항목 | 내용 |
|---|---|
| 참고 출처 | Sutton · Barto 『Reinforcement Learning: An Introduction』 2판 예제 6.6(절벽 걷기), Gymnasium 문서 Cliff Walking |
| 트랙 · 수준 | 강화학습 · 기초 (실습) |
| 이 수업이 다루는 범위 | 격자 환경 직접 만들기, ε-탐욕, 표 형식 Q-러닝 · SARSA 구현, 학습 곡선 숫자 읽기, 탐욕 정책 화살표 그리기, 씨앗(seed)별 비교, 확장 과제 |
| 준비물 | 파이썬 3 · NumPy (이 페이지의 출력은 Python 3.14 · NumPy 2.5에서 실제로 돌린 결과) |
들어가기 전에
- Hugging Face 딥 강화학습 강좌 — TD 오차, Q-러닝(다음 상태의 최댓값)과 SARSA(실제로 고른 다음 행동)의 차이를 손으로 계산해 봤다면 바로 시작할 수 있습니다.
- 탐험 심화: UCB · 톰슨 샘플링 · 후회 — 여기서는 가장 단순한 ε-탐욕만 씁니다. 탐험 방식이 결과를 얼마나 바꾸는지가 이번 실습의 핵심 관찰입니다.
한눈에 보기
- 4×12 격자 환경을 함수 하나(
step)로 직접 만듭니다. - Q-러닝과 SARSA가 목표값 한 줄만 다르다는 것을 코드로 확인합니다.
- 학습한 Q-표에서 화살표 지도(탐욕 정책)를 그려, 두 알고리즘이 고른 길을 비교합니다.
- "학습 중 받은 점수"와 "다 배운 뒤 탐욕적으로 걸은 점수"가 왜 다른지 설명할 수 있게 됩니다.
- 확장 과제로 ε 줄이기 · 걸음 크기 바꾸기를 해 보고 결과를 해석합니다.
1. 문제: 절벽 걷기
직관(해설용 비유). 낭떠러지 옆 오솔길을 지나 반대편 집에 가야 합니다. 낭떠러지 바로 옆으로 붙어 가면 가장 빠르지만, 가끔 발을 헛디디는(무작위 행동) 사람이라면 멀찍이 돌아가는 편이 낫습니다. 강화학습 에이전트도 "가끔 헛디디는" ε-탐욕으로 움직이므로 같은 고민을 합니다.
규칙(S&B 예제 6.6 · Gymnasium 문서 기준).
| 항목 | 값 |
|---|---|
| 격자 | 4행 × 12열 |
| 출발 S · 목표 G | 맨 아래 줄 왼쪽 끝 (3, 0) · 오른쪽 끝 (3, 11) |
| 절벽 C | 맨 아래 줄 (3, 1)~(3, 10) |
| 보상 | 한 걸음마다 −1, 절벽에 들어가면 −100 그리고 출발점으로 돌아감 |
| 행동 | 위 · 오른쪽 · 아래 · 왼쪽 (벽에 부딪히면 제자리) |
| 할인 | 없음(), 목표에 닿으면 한 판 끝 |
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
S C C C C C C C C C C G최단 경로는 위로 한 칸 → 오른쪽 11칸 → 아래로 한 칸, 모두 13걸음이라 반환이 입니다. 맨 위 줄로 돌아가면 3 + 11 + 3 = 17걸음, 반환 입니다.
2. 두 갱신식 다시 보기
| 기호 | 뜻 | 이번 실습 값 |
|---|---|---|
| 걸음 크기 | 0.5 | |
| 할인율 | 1 | |
| 무작위 행동 확률 | 0.1 | |
| 다음 상태에서 가장 좋은 행동의 값(실제로 그 행동을 할지는 상관없음) | — | |
| 다음 상태에서 실제로 고른 행동의 값(무작위 실수까지 포함) | — |
왜 결과가 갈리나. 절벽 옆 칸에서 ε-탐욕은 10% 확률로 무작위 행동을 하고, 그중 1/4은 '아래'라서 절벽으로 떨어집니다. SARSA는 이 실수까지 포함한 값을 배우므로 절벽 옆 칸의 가치가 낮아지고, 멀리 돌아가는 길을 택합니다(온폴리시). Q-러닝은 "다음부터는 최선만 한다"고 가정한 값을 배우므로 절벽 옆 최단 경로의 가치를 정확히 배우지만, 실제로 걸을 때는 ε 때문에 가끔 떨어집니다(오프폴리시).
3. 코드 (전체)
아래 코드를 cliff.py 로 저장해 python cliff.py 로 실행합니다. 각 부분에 번호를 붙였습니다.
import sys; sys.stdout.reconfigure(encoding="utf-8") # 윈도 콘솔에서 한글 · 화살표 출력용
import numpy as np
# 1) 환경: 4x12 절벽 걷기 (Sutton·Barto 예제 6.6, Gymnasium CliffWalking 과 같은 배치)
ROWS, COLS = 4, 12
START, GOAL = (3, 0), (3, 11)
ACTIONS = [(-1, 0), (0, 1), (1, 0), (0, -1)] # 0=위 1=오른쪽 2=아래 3=왼쪽
ARROWS = "↑→↓←"
def step(state, a):
r, c = state
dr, dc = ACTIONS[a]
r = min(max(r + dr, 0), ROWS - 1)
c = min(max(c + dc, 0), COLS - 1)
if r == 3 and 1 <= c <= 10: # 절벽: -100, 출발점으로
return START, -100, False
if (r, c) == GOAL:
return (r, c), -1, True
return (r, c), -1, False
# 2) ε-탐욕 행동 선택 (동점은 무작위)
def eps_greedy(Q, s, eps, rng):
if rng.random() < eps:
return int(rng.integers(4))
q = Q[s]
return int(rng.choice(np.flatnonzero(q == q.max())))
# 3) 학습: Q-러닝과 SARSA 는 목표값 한 줄만 다르다
def train(method, episodes=500, alpha=0.5, eps=0.1, gamma=1.0, seed=0):
rng = np.random.default_rng(seed)
Q = np.zeros((ROWS, COLS, 4))
returns = []
for _ in range(episodes):
s, G, done = START, 0, False
a = eps_greedy(Q, s, eps, rng)
while not done:
s2, r, done = step(s, a)
G += r
a2 = eps_greedy(Q, s2, eps, rng)
if done:
target = r
elif method == "q":
target = r + gamma * Q[s2].max() # 다음 상태의 최댓값
else:
target = r + gamma * Q[s2][a2] # 실제로 고를 다음 행동
Q[s][a] += alpha * (target - Q[s][a])
s, a = s2, a2
returns.append(G)
return Q, np.array(returns)
# 4) 결과 보기: 탐욕 정책 화살표와 탐욕 경로의 반환
def show_policy(Q):
lines = []
for r in range(ROWS):
row = ""
for c in range(COLS):
if (r, c) == START: row += "S"
elif (r, c) == GOAL: row += "G"
elif r == 3 and 1 <= c <= 10: row += "C"
else: row += ARROWS[int(Q[r, c].argmax())]
lines.append(row)
return "\n".join(lines)
def greedy_return(Q, max_steps=100):
s, G = START, 0
for _ in range(max_steps):
s, r, done = step(s, int(Q[s].argmax()))
G += r
if done: return G
return None # 목표에 못 감
for name, m in [("Q-러닝", "q"), ("SARSA", "sarsa")]:
Q, R = train(m)
print(f"== {name} ==")
print("마지막 100판 평균 반환(ε=0.1 탐험 포함):", round(R[-100:].mean(), 1))
print("탐욕 정책으로 한 판 걸었을 때 반환:", greedy_return(Q))
print(show_policy(Q))
# 5) 씨앗 10개 평균으로 다시 비교
for name, m in [("Q-러닝", "q"), ("SARSA", "sarsa")]:
avg = [train(m, seed=k)[1][-100:].mean() for k in range(10)]
gr = [greedy_return(train(m, seed=k)[0]) for k in range(10)]
print(name, "씨앗 10개: 마지막 100판 평균", round(float(np.mean(avg)), 1), "| 탐욕 경로 반환", gr)코드를 단계별로 읽기.
- 환경(
step): 위치와 행동을 받아 (다음 위치, 보상, 끝났는지)를 돌려줍니다. 격자 밖으로 나가려 하면min/max로 제자리에 묶습니다. Gymnasium의CliffWalking-v1을 써도 되지만, 환경을 직접 만들어 보면 "환경 = 상태 전이 + 보상 함수"라는 MDP 정의가 손에 잡힙니다. - ε-탐욕: 동점일 때 항상 첫 번째 행동을 고르면 초반에 한쪽으로 쏠리므로 무작위로 깹니다.
- 학습: 판을 시작할 때 행동을 하나 고르고, 걸을 때마다 다음 행동
a2를 미리 골라 둡니다. SARSA는 그a2를 목표값에 쓰고, Q-러닝은a2와 상관없이 최댓값을 씁니다. 다른 곳은 한 글자도 다르지 않습니다. - 결과 보기: 칸마다 Q값이 가장 큰 행동을 화살표로 찍고, 탐욕적으로(ε=0) 한 판 걸어 반환을 잽니다.
4. 실행 결과 (이 페이지를 위해 실제로 돌린 출력, seed=0)
== Q-러닝 ==
마지막 100판 평균 반환(ε=0.1 탐험 포함): -61.8
탐욕 정책으로 한 판 걸었을 때 반환: -13
↓→→→→→→→↓↓↓↓
→↓↓→↓→→↓→↓↓↓
→→→→→→→→→→→↓
SCCCCCCCCCCG
== SARSA ==
마지막 100판 평균 반환(ε=0.1 탐험 포함): -26.2
탐욕 정책으로 한 판 걸었을 때 반환: -17
→→→→→→→→→→↓↓
↑↑↑→↑↑→→↑↑→↓
↑↑↑→↑←←↑↑→→↓
SCCCCCCCCCCG
Q-러닝 씨앗 10개: 마지막 100판 평균 -49.5 | 탐욕 경로 반환 [-13, -13, -13, -13, -13, -13, -13, -13, -13, -13]
SARSA 씨앗 10개: 마지막 100판 평균 -26.2 | 탐욕 경로 반환 [-17, -17, -19, -17, -17, None, None, -19, -17, -17]난수 생성기 · 버전이 다르면 숫자는 조금 달라질 수 있습니다. 하지만 아래 경향은 S&B 예제 6.6이 설명하는 것과 같습니다.
출력 읽기.
- 화살표 지도. Q-러닝은 3행(절벽 바로 위 줄)을 따라 오른쪽으로 가는 최단 경로(반환 −13)를 배웠습니다. SARSA는 출발점에서 위로 올라가 맨 위 줄을 따라가는 먼 길(반환 −17)을 배웠습니다.
- 학습 중 점수는 거꾸로입니다. ε=0.1로 실제로 걸은 마지막 100판의 평균은 SARSA(−26.2)가 Q-러닝(−61.8, 씨앗 10개 평균 −49.5)보다 훨씬 좋습니다. Q-러닝은 절벽 옆을 걷다가 무작위 행동으로 종종 떨어져 −100을 받기 때문입니다. 책도 Q-러닝이 최적 정책의 값을 배우지만 온라인 성능은 SARSA보다 나쁘다고 설명합니다.
- Q-러닝의 출발점 Q값. 학습 뒤
Q[START]를 찍어 보면 (위, 오른쪽, 아래, 왼쪽) = (−13, −113, −14, −14)였습니다. '위'가 −13으로 최적 반환과 정확히 같고, '오른쪽'은 절벽(−100) + 출발점에서 다시 최단 경로(−13) = −113 입니다. 같은 실행에서 SARSA의 '위'는 −20 정도로, ε 실수를 포함한 값이라 더 낮습니다.
flowchart LR
A[출발점 S에서<br/>ε-탐욕으로 a 선택] --> B[step: s', r 받기]
B --> C[다음 행동 a' 미리 선택]
C --> D{알고리즘}
D -- Q-러닝 --> E[목표 = r + max Q s']
D -- SARSA --> F[목표 = r + Q s', a']
E --> G[Q s,a ← Q + α 목표 − Q]
F --> G
G --> H{목표 G 도착?}
H -- 아니오 --> B
H -- 예 --> I[반환 기록 · 다음 판]5. 이상한 결과 하나: 탐욕 경로가 None
SARSA 씨앗 10개 중 두 번은 탐욕 경로 반환이 None(100걸음 안에 도착 못 함)이었습니다. seed=5 를 들여다보면 맨 위 줄 (0, 4)에서 탐욕 행동이 '위'(벽에 부딪혀 제자리)로 나와 같은 칸을 맴돌았습니다.
왜 이런가. 맨 위 줄에서 '위'는 ε 무작위로만 가끔 시도되므로 갱신 횟수가 아주 적습니다. 그래서 그 Q값이 실제보다 덜 깎인 채(낙관적으로) 남아 있고, 탐욕적으로 고르면 그 행동이 1등이 됩니다. 학습 중에는 ε 덕분에 빠져나오지만, ε=0으로 걸으면 갇힙니다. 표 형식 학습에서는 자주 가 보지 않은 상태-행동 쌍의 값은 믿을 수 없다는 교훈입니다(탐험 수업의 '낙관적 초깃값'이 거꾸로 작동한 경우라고 볼 수도 있습니다 — 초깃값 0이 보상 −1보다 크기 때문입니다).
6. 확장 과제
아래 결과도 같은 코드를 조금 고쳐 실제로 돌린 값입니다(씨앗 0~9).
과제 1. ε 을 점점 줄이면? eps = 0.1 * (1 - ep / episodes) 로 판마다 ε 을 0까지 줄여 보세요.
- 결과: Q-러닝 탐욕 경로는 10개 모두 −13, 마지막 100판 평균은 −14.7로 크게 좋아졌습니다(탐험이 줄어 덜 떨어짐). SARSA는 탐욕 경로가 대부분 −17(한 번 −19), 마지막 100판 평균 −18.1이었습니다.
- 해석: 책은 "ε 을 점점 줄이면 두 방법 모두 결국 최적 정책으로 수렴한다"고 말합니다. 500판 안에 0까지 선형으로 줄이는 이번 설정에서는 SARSA가 아직 안전한 길에 머물렀습니다. ε 이 작아진 뒤에도 그동안 배운 '절벽 옆은 위험하다'는 값이 충분히 고쳐지려면 더 많은 판이 필요합니다.
과제 2. 걸음 크기 를 0.1로 줄이면?
- 결과: Q-러닝 탐욕 경로는 모두 −13, 마지막 100판 평균 −49.6. SARSA 탐욕 경로는 대부분 −15(두 번 −17), 평균 −21.5.
- 해석: 가 작으면 값이 천천히 변해 SARSA의 Q가 아직 덜 수렴했고, 절벽에서 두 줄 떨어진 중간 줄(반환 −15)을 고르는 경우가 많았습니다. "어느 길을 고르나"가 ε 뿐 아니라 학습이 얼마나 진행됐는지에도 달려 있음을 보여 줍니다.
과제 3(생각해 보기). Gymnasium의 CliffWalking-v1 으로 바꾸려면? env.reset() 이 상태 번호(행 × 12 + 열)를 주므로 Q-표를 (48, 4) 로 만들고, env.step(a) 의 반환값 (s2, r, terminated, truncated, info) 를 쓰면 됩니다. 나머지 학습 코드는 그대로입니다.
연습 문제
- 최단 경로와 맨 위 줄 경로의 반환은 각각 얼마이고, 왜 그런가요?
- 풀이: 최단 경로는 위 1 + 오른쪽 11 + 아래 1 = 13걸음이라 −13, 맨 위 줄 경로는 위 3 + 오른쪽 11 + 아래 3 = 17걸음이라 −17 입니다.
- 학습이 끝난 Q-러닝에서
Q[START]의 '오른쪽' 값이 −113인 이유는?- 풀이: 오른쪽은 바로 절벽이라 −100을 받고 출발점으로 돌아가며, Q-러닝은 그 뒤 최선(−13)을 가정하므로 −100 + (−13) = −113 입니다.
- 코드에서 Q-러닝을 SARSA로 바꾸려면 몇 줄을 고치면 되나요?
- 풀이: 목표값 한 줄(
Q[s2].max()→Q[s2][a2])입니다. 다음 행동을 미리 고르는 구조는 이미 두 방법이 함께 씁니다.
- 풀이: 목표값 한 줄(
- ε=0(처음부터 탐욕)으로 학습하면 어떤 일이 생길까요?
- 풀이: 초깃값 0이 보상 −1보다 크므로 아직 안 해 본 행동이 늘 더 좋아 보여 저절로 탐험합니다(낙관적 초깃값 효과). 실제로
eps=0.0으로 씨앗 0~4를 돌려 보니 두 방법 모두 탐욕 경로 반환 −13(최단 경로)을 찾았습니다. 또 ε=0이면 '실제로 고를 다음 행동'이 곧 최댓값 행동이라 SARSA와 Q-러닝의 목표값이 사실상 같아집니다(동점 처리만 다름). 다만 이런 탐험은 초깃값과 보상 크기에 기댄 것이라, 보상이 양수인 문제에서는 통하지 않으므로 일반적으로는 ε 이나 다른 탐험 장치를 둡니다.
- 풀이: 초깃값 0이 보상 −1보다 크므로 아직 안 해 본 행동이 늘 더 좋아 보여 저절로 탐험합니다(낙관적 초깃값 효과). 실제로
스스로 점검
- 같은 ε=0.1 환경에서 학습 중 평균 반환은 SARSA가 더 좋은데, 다 배운 탐욕 경로는 Q-러닝이 더 짧은 이유는?
- 답: SARSA는 ε 실수까지 포함한 '실제로 걷는 방식'의 가치를 배워 안전한 길을 택하고, Q-러닝은 '최선만 한다'고 가정한 가치를 배워 최단 경로를 택하지만 학습 중에는 ε 때문에 절벽에 떨어지기 때문입니다.
- 탐욕 경로가 맴돌며 목표에 못 가는 경우는 왜 생기나요?
- 답: 거의 시도되지 않은 상태-행동 쌍의 Q값이 초깃값 근처에 남아 실제보다 좋아 보이기 때문입니다.
- 실무에서 "학습 중 안전"이 중요하면 어느 쪽 성질이 유리한가요?
- 답: 학습 중 실제 행동 방식의 위험을 값에 반영하는 온폴리시(SARSA) 쪽입니다. 다만 학습 뒤 최적성은 오프폴리시(Q-러닝)가 유리할 수 있습니다.
용어 정리
| 용어 | 쉬운 뜻 |
|---|---|
| 절벽 걷기(Cliff Walking) | S&B 예제 6.6의 4×12 격자 문제 |
| 표 형식(tabular) 방법 | 상태-행동마다 칸을 하나씩 두고 값을 저장하는 방식 |
| 온폴리시(on-policy) | 지금 행동하는 정책 자신의 가치를 배움 (SARSA) |
| 오프폴리시(off-policy) | 행동하는 정책과 다른(최적) 정책의 가치를 배움 (Q-러닝) |
| 탐욕 정책(greedy policy) | 각 상태에서 Q값이 가장 큰 행동만 고르는 정책 |
| 씨앗(seed) | 난수 생성기의 시작값. 같으면 같은 결과가 재현됨 |
| 반환(return) | 한 판 동안 받은 보상의 합 |
| ε 감쇠(ε decay) | 학습이 진행될수록 무작위 행동 확률을 줄이는 것 |
더 깊이 보려면
- S&B 6.6절 기대 SARSA(Expected SARSA): 다음 행동 값의 '기댓값'을 쓰는 변형. 이 코드에서 목표값 한 줄을 로 바꿔 비교해 보세요.
- 딥 강화학습으로 아타리 게임하기 (DQN) — 오늘의 Q-표를 신경망으로 바꾸면 무엇이 달라지는지.
- 다음 수업 함수 근사: 표에서 신경망으로 — 칸이 너무 많아 표를 만들 수 없을 때의 방법.
다음에 볼 것
- 함수 근사: 표에서 신경망으로 — 표 대신 가중치 몇 개로 가치를 표현하는 방법과 그 위험(치명적 삼중주).
- OpenAI Spinning Up: 딥 강화학습 입문 — 가치 대신 정책을 직접 배우는 정책 경사.
라이선스
본문과 코드는 KAITABASE가 이 수업을 위해 직접 작성했습니다(코드는 자유롭게 고쳐 써도 됩니다). 환경 설정은 Sutton · Barto 2판(PDF 판권면 CC BY-NC-ND 2.0) 예제 6.6과 Gymnasium 문서의 설명을 따랐고, 책의 그림 · 수치는 옮기지 않았습니다. 실행 결과는 이 페이지를 위해 실제로 돌린 출력입니다.
출처
- Sutton · Barto, Reinforcement Learning: An Introduction 2판(2020 PDF) 예제 6.6: incompleteideas.net/book/RLbook2020.pdf
- Gymnasium, Cliff Walking 환경 문서: gymnasium.farama.org/environments/toy_tex…
AI 원리부터 이해하기 · 강화학습 모듈
강화학습 모듈 학습
이 글은 강화학습 모듈의 학습 자료입니다. 전체 31개 자료 중 4번째입니다.
출처·원문
원문 보기요약만