6.1. 몬테카를로 컨트롤
챕터 4에서 MDP를 알 때에 최적의 정책 함수를 구하는 방법론을 몇 가지 배웠다. 그 중 정책 이터레이션은 [임의의 정책에서 시작→정책 평가→밸류 계산→계산한 밸류에 대해 그리디 정책 만듦] 과정을 계속해서 반복하는 방법이었다. 모델-프리에서는 정책 이터레이션을 그대로 사용할 수 없을까?
6.1.1. 정책 이터레이션을 그대로 사용할 수 없는 이유

위와 같이 평가 단계에서는 반복적 정책 평가를, 개선 단계에서는 그리디 정책 생성을 이용했다. 이 방법을 모델 프리 상황에서 그대로 사용하기에는 문제가 있다.
1. 평가 단계에서 반복적 정책 평가를 사용할 수 없다.

반복적 정책 평가의 핵심이 되었던 벨만 기대 방정식 2단계를 생각해보자. 여기에는 MDP를 알아야만 채울 수 있는 값이 있다. 하지만 이런 정보를 모델-프리 상황에서는 알 수가 없다.
2. 정책 개선 단계에서 그리디 정책을 만들 수 없다.

어떻게든 정책 평가를 마친 상황이라고 생각해보자. 즉, 각 상태의 밸류를 알고있다고 하자. MDP의 모든 정보를 안다면 $s_0$에서의 그리디 정책은 $a_2$임을 쉽게 알 수 있다. 하지만, MDP의 상태 전이에 대한 정보를 모른다면 더 나은 액션을 선택할 수 없다.
6.1.2. 해결 방법
위와 같이 모델 프리 상황에서 정책 이터레이션을 그대로 적용할 수는 없다. 하지만 몇 가지 변형을 통해 적용할 수 있다.
1. 평가 자리에 MC
정책 평가 단계에서 반복적 정책 평가가 불가능했다. 하지만 챕터 5에서 MDP를 모를 때 밸류를 계산하는 2가지 방법론(MC, TD)을 배웠다. 그 중 하나를 골라 정책 평가 단계에 끼워 넣으면 된다. 몬테카를로 방법론을 평가 단계에서 사용하면 벨만 기대 방정식 2단계 수식들을 사용하지 않아도 각 상태의 밸류를 평가할 수 있게 된다.
2. V 대신 Q

v(s) 대신에 상태-액션 가치 함수인 q(s,a)를 이용하자. q(s,a)를 알게 되면 MDP에 대한 정보를 몰라도 그리디 액션을 선택할 수 있다.
→ 즉, MC를 이용하여 q(s,a)를 계산하고, 평가된 q(s,a)를 이용해 새로운 그리디 정책을 만들고, 이 정책에 MC를 이용하여 q(s,a)를 계산하는 과정을 반복하는 것이다.
3. greedy 대신 ε-greedy
강화학습에서 에이전트가 최적의 해를 찾으려면 에이전트는 주어진 MDP 안의 여러 상태를 충분히 탐색해야한다. 따라서 에이전트가 다양한 공간을 탐색할 수 있도록 보장해주는 장치가 필요하다. ε-greedy는 이러한 장치 중 가장 단순하면서도 충분히 강력한 방법론이다. ε이라는 작은 확률만큼 랜덤하게 액션을 선택하고, 1-ε이라는 나머지 확률은 원래처럼 그리디 액션을 선택한다.

여기서 조금 더 좋은 방법은 ε의 값을 처음에는 높게 하다가 점점 줄여주는 것이다. 처음에는 다양한 액션들을 선택하면서 환경에 대한 정보를 충분히 얻어야하므로 높게 설정하고, 학습이 어느 정도 진행되고 나면 이미 얻은 정보를 바탕으로 조금 더 최선의 선택을 내리는 데에 집중한다. 이런 방법론을 decaying ε-greedy라고 부른다. 몬테카를로 컨트롤을 구현할 때 바로 decaying ε-greedy를 이용할 것이다.

몬테카를로 컨트롤을 그림으로 정리하면 위와 같다. 정책 평가 단계에서는 MC를 이용하여 q(s,a)를 구하고, 정책 개선 단계에서는 q(s,a)에 대한 ε-greedy 정책을 만든다. 더 이상 정책이 바뀌지 않을 때까지 이를 반복하면 결국 최적의 정책을 얻게 된다.
디테일만 달라졌을 뿐 정책 이터레이션을 그대로 사용하는 방법론이다.
MDP 모델을 모른다고 가정하니까 직접 기대값을 계산할 수는 없으므로 대신 에피소드 경험을 가지고 추정한다:
- Step 1. 에피소드 동안 $(s, a)$ 쌍이 등장할 때마다, 그 시점부터 종료까지의 실제 리턴 $G_t$를 기록한다.
- Step 2. 여러 번의 에피소드에서 동일한 $(s, a)$ 쌍이 나올 수 있으니, 그때마다 $G_t$를 평균 낸다.
- Step 3. 그 평균값이 곧 $Q(s,a)$의 추정치가 된다.
즉, $Q(s,a)≈{{1}\over{N(s,a)}}∑_{i=1}^{N(s,a)}G_i$ (여기서 $N(s,a)$ 쌍이 등장한 횟수, $G_i$는 그때 얻은 리턴)
$Q(s,a)←Q(s,a)+α(G_t−Q(s,a))$
- MC에서 상태 가치 업데이트하던 수식을 그대로 가져오되, 상태 대신 (상태, 행동) 으로 확장
- MC Control에서는 정책 개선을 하려면 “어떤 행동이 더 좋은가?”를 알아야 해서 Q(s,a)를 사용
- $Q(s,a)$ 는 모델 기반 계산이 아니라, 경험으로 얻은 리턴을 (s,a) 쌍별로 평균해서 추정
- 수식으로는 $Q(s,a)←Q(s,a)+α(G_t−Q(s,a))$
6.1.3. 몬테카를로 컨트롤 구현
업그레이드된 그리드 월드 문제를 풀어보자.


수렴할 때까지 n번 반복
- 한 에피소드의 경험을 쌓고
- 경험한 데이터로 q(s,a) 테이블의 값을 업데이트하고 (정책 평가)
- 업데이트된 q(s,a) 테이블을 이용하여 ε-greedy 정책을 만들고 (정책 개선)
###라이브러리 ipmort
import random
import numpy as np
###GridWorld 클래스
class GridWorld():
def __init__(self):
self.x=0
self.y=0
def step(self, a):
#0번:왼쪽, 1번:위, 2번:오른쪽, 3번:아래쪽
if a==0:
self.move_left()
elif a==1:
self.move_up()
elif a==2:
self.move_right()
elif a==3:
self.move_down()
reward = -1
done = self.is_done()
return (self.x, self.y), reward, done
def move_left(self):
if self.y == 0:
pass
elif self.y==3 and self.x in [0,1,2]:
pass
elif self.y==5 and self.x in [2,3,4]:
pass
else:
self.y -= 1
def move_right(self):
if self.y==1 and self.x in [0,1,2]:
pass
elif self.y==3 and self.x in [2,3,4]:
pass
elif self.y==6:
pass
else:
self.y += 1
def move_up(self):
if self.x==0:
pass
elif self.x==3 and self.y==2:
pass
else:
self.x -= 1
def move_down(self):
if self.x==4:
pass
elif self.x==1 and self.y==4:
pass
else:
self.x += 1
def is_done(self):
if self.x == 4 and self.y == 6:
return True
else :
return False
def get_state(self):
return (self.x, self.y)
def reset(self):
self.x=0
self.y=0
return (self.x,self.y)
###QAgent 클래스
class QAgent():
def __init__(self):
self.q_table = np.zeros((5,7,4))
self.eps = 0.9
self.alpha=0.01
def select_action(self,s):
#eps-greedy로 액션을 선택해준다.
x,y = s
coin = random.random()
if coin < self.eps:
action = random.randint(0,3)
else:
action_val = self.q_table[x,y,:]
action = np.argmax(action_val)
return action
def update_table(self, history):
#한 에피소드에 해당하는 history를 입력으로 받아 q 테이블의 값을 업데이트한다.
cum_reward = 0
for transition in history[::-1]:
s,a,r,s_prime = transition
x,y = s
#몬테카를로 방식을 이용하여 업데이트
self.q_table[x,y,a] = self.q_table[x,y,a] + self.alpha * (cum_reward - self.q_table[x,y,a])
cum_reward = cum_reward + r
def anneal_eps(self):
self.eps -= 0.03
self.eps = max(self.eps, 0.1)
def show_table(self):
#학습이 각 위치에서 어느 액션의 q 값이 가장 높았는지 보여주는 함수
q_lst = self.q_table.tolist()
data = np.zeros((5,7))
for row_idx in range(len(q_lst)):
row = q_lst[row_idx]
for col_idx in range(len(row)):
col = row[col_idx]
action = np.argmax(col)
data[row_idx, col_idx] = action
print(data)
###메인 함수
def main():
env = GridWorld()
agent = QAgent()
for n_epi in range(1000): #총 1000번의 에피소드 동안 학습
done = False
history = []
s = env.reset()
while not done: #한 에피소드가 끝날 때 까지
a = agent.select_action(s)
s_prime, r, done = env.step(a)
history.append((s,a,r,s_prime))
s = s_prime
#한 번의 step이 진행되자마자 바로 테이블의 데이터를 업데이트 해줌
agent.update_table(history) #히스토리를 이용하여 에이전트를 업데이트
agent.anneal_eps()
agent.show_table()
main()
출력은 다음과 같이 나오게 된다.

출력값은 ε-greedy라는 확률적 요소를 갖기 때문에 매번 학습할 때마다 달라진다. 위 코드에서 사용된 하이퍼파라미터(입실론의 초깃값, alpha 등)들은 튜닝된 값이 아니므로 최적화한다면 더 안정적으로 학습이 되도록 만들 수 있다.
6.2. TD 컨트롤1 - SARSA
정책 이터레이션의 평가 단계에서 MC대신 TD를 해도 가능하다!
6.2.1. MC 대신 TD

위와 같이 정책 평가 단계에서 MC 대신 TD를 이용해보자. 즉 TD를 이용해 q(s,a)를 구해보자.

TD를 이용해 Q를 구하는 접근법은 위의 그림에 의해 SARSA라고 부른다.
TD를 이용한 V와 Q 학습은 아래의 식과 같다.

벨만 기대 방정식 0단계를 복습해보면 다음과 같다. 이를 통해서 TD 타깃이 모두 벨만 방정식을 통해 나온 것임을 알 수 있다.

6.2.2. SARSA 구현
###QAgent 클래스
class QAgent():
def __init__(self):
self.q_table = np.zeros((5,7,4))
self.eps = 0.9
def select_action(self,s):
x,y = s
coin = random.random()
if coin < self.eps:
action = random.randint(0,3)
else:
action_val = self.q_table[x,y,:]
action = np.argmax(action_val)
return action
def update_table(self, transition):
s,a,r,s_prime = transition
x,y = s
next_x, next_y = s_prime
a_prime = self.select_action(s_prime) #s'에서 선택할 액션(실제로 취한 액션이 아님)
#SARSA 업데이트 식을 이용
self.q_table[x,y,a] = self.q_table[x,y,a] + 0.1 * (r + self.q_table[next_x,next_y,a_prime] - self.q_table[x,y,a])
def anneal_eps(self):
self.eps -= 0.03
self.eps = max(self.eps, 0.1)
def show_table(self):
q_lst = self.q_table.tolist()
data = np.zeros((5,7))
for row_idx in range(len(q_lst)):
row = q_lst[row_idx]
for col_idx in range(len(row)):
col = row[col_idx]
action = np.argmax(col)
data[row_idx, col_idx] = action
print(data)
###메인 함수
def main():
env = GridWorld()
agent = QAgent()
for n_epi in range(1000):
done = False
s = env.reset()
while not done: #한 에피소드가 끝날 때 까지
a = agent.select_action(s)
s_prime, r, done = env.step(a)
agent.update_table((s,a,r,s_prime))
s = s_prime
agent.anneal_eps()
agent.show_table()
main()
출력은 다음과 같이 나오게 된다.

6.3. TD 컨트롤2 - Q러닝
위에서는 TD를 이용하여 최적의 정책을 찾는 방법을 살펴 보았다. 마찬가지로 TD를 이용하여 최적의 정책을 찾는 방법을 알아보자.
그 전에 SARSA와 Q러닝 모두 TD를 이용한 컨트롤 방법인데 그 차이가 무엇인지 알아보자.
6.3.1. Off-Policy와 On-Policy
- 타깃 정책 (target policy) : 강화하고자 하는 목표가 되는 정책
- 행동 정책 (behavior policy) : 실제로 환경과 상호 작용하며 경험을 쌓고 있는 정책
- On-policy : 타깃 정책과 행동 정책이 같은 경우 (직접 경험)
- Off-policy : 타깃 정책과 행동 정책이 다른 경우 (간접 경험)
따라서 지금까지 배운 내용은 모두 On-Policy 상황이었다.
off-policy가 지도학습과 비슷하게 느껴질 수 있겠지만 헷갈리면 안된다. 지도학습에서는 지도자를 그대로 따라하는 방식으로 학습했다면, off-policy는 결과가 좋았던 것은 그대로 따라하기도 하지만, 결과가 좋지 않았던 것은 수정하기도 한다.
6.3.2. Off-Policy 학습의 장점
- 과거의 경험을 재사용할 수 있다. : 타깃 정책과 행동 정책이 달라도 되기 때문에 과거의 정책이 경험한 샘플들을 그 다음 정책의 업데이트에 그대로 재사용할 수 있는 건 물론이고 더 다음에 있는 정책에서도 재사용할 수 있다. 따라서 효율성 측면에서 커다란 이득을 누릴 수 있다.
- 사람의 데이터로부터 학습할 수 있다. : off-policy 방법론의 행동 정책에는 어떤 정책을 가져다 놔도 된다. 따라서 기존 전문가가 만들어 내는 양질의 데이터를 학습에 사용하여 학습 속도를 끌어올릴 수 있다.
- 일대다, 다대일 학습이 가능하다. : 동시에 여러 개의 정책을 학습시킨다고 가정하자. off-policy 학습을 이용하면 이 중에서 단 1개의 정책만 경험을 쌓게 두고, 그로부터 생성된 데이터를 이용해 동시에 여러 개의 정책을 학습시킬 수 있다. 반대로 동시에 여러 개의 정책이 겪은 데이터를 모아서 단 1개의 정책을 업데이트할 수도 있다.
6.3.3. Q러닝의 이론적 배경 - 벨만 최적 방정식
가장 좋은 정책을 따를 때의 가치는 $q_(s,a)=\max\limits_{\pi}q_\pi(s,a)$이다. $q_$를 알면 $\pi_*=\argmax\limits_{a}q_(s,a)$와 같이 주어진 MDP에서 순간마다 최적의 행동을 선택하며 움직일 수 있다.
우리의 목적은 최적의 액션 가치-함수 $q_*$를 찾는 것!
벨만 최적 방정식 2단계의 식을 떠올려보자 : $q_∗(s,a)=r^a_s+γ\Sigma_{s'∈S}P^a_{ss'}\max\limits_{a'}q_∗(s',a')$ 하지만, MDP를 모르기 때문에 위 수식을 그대로 이용할 수 없으므로 0단계 식을 이용하자. 즉, 여러 개의 샘플을 이용해 계산하고, 여러 번 경험을 쌓아 각 경험을 평균내어 기댓값을 구한다(대수의 법칙). 벨만 최적 방정식 0단계: $q_∗(s,a)=E_{s'}[r +\max\limits_{a'}q_∗(s',a')]$ 위 과정을 적용하여 TD 학습을 해보자. Q러닝과 SARSA의 밸류 업데이트 식은 아래와 같다. (빨간 글씨가 TD 타깃)

Q 러닝과 SARSA의 차이점을 떠올리기 위하여 아래 표를 보자.

행동정책 : 에이전트가 실제로 탐색을 할 때(액션을 할 때) 따르는 정책
- Q러닝, SARSA : 실제 액션을 취할 땐 ϵ-Greedy 정책을 따름
타깃 정책 : Q값을 업데이트 하기 위해 사용하는 정책. 즉, 타깃에 사용하는 정책
- Q 러닝 : 가장 Q값이 높은 액션을 선택하는 Greedy 정책을 따름
- SARSA : 행동 정책과 타깃 정책 모두 ϵ값이 들어가 있는 ϵ-greedy 정책을 따름
여기서, SARSA와 Q러닝이 다른 이유는 기초가 되는 수식이 다르기 때문이다.

6.3.4. Q러닝 구현
###QAgent 클래스
class QAgent():
def __init__(self):
self.q_table = np.zeros((5,7,4))
self.eps = 0.9
def select_action(self,s):
x,y = s
coin = random.random()
if coin < self.eps:
action = random.randint(0,3)
else:
action_val = self.q_table[x,y,:]
action = np.argmax(action_val)
return action
def update_table(self, transition):
s,a,r,s_prime = transition
x,y = s
next_x, next_y = s_prime
self.q_table[x,y,a] = self.q_table[x,y,a] + 0.1 * (r + np.amax(self.q_table[next_x,next_y,:]) - self.q_table[x,y,a])
def anneal_eps(self):
self.eps -= 0.01
self.eps = max(self.eps, 0.2)
def show_table(self):
q_lst = self.q_table.tolist()
data = np.zeros((5,7))
for row_idx in range(len(q_lst)):
row = q_lst[row_idx]
for col_idx in range(len(row)):
col = row[col_idx]
action = np.argmax(col)
data[row_idx, col_idx] = action
print(data)
위 코드의 출력은 아래와 같다.
정리
MDP를 모르는 상황에서 최적의 정책을 찾기 위해 정책 이터레이션을 적용할 수 있을까??
MC control → 반복적 정책 평가의 부분을 MC로 변경, V 대신 Q를 적용
TD control → SARSA(on-policy), Q-Learning(Off-policy)
'ML,DL' 카테고리의 다른 글
| RNN, LSTM 이해하기 (PyTorch로 구현한 코드 포함) (0) | 2026.03.11 |
|---|---|
| [바닥부터 배우는 강화학습] Chapter 7 Deep RL 첫 걸음 (0) | 2026.03.11 |
| [바닥부터 배우는 강화학습] Chapter 5 MDP를 모를 때 밸류 평가하기 (0) | 2026.03.10 |
| [바닥부터 배우는 강화학습] Chapter 4 MDP를 알 때의 플래닝 (0) | 2026.03.09 |
| [바닥부터 배우는 강화학습] Chapter 3 벨만 방정식 (0) | 2026.02.21 |