안녕하세요! 오늘은 기존 작성한 frozen lake 문제를 SARSA로 진행하는 방법에 대해 포스팅 하겠습니다.
- SARSA 란?
강화 학습의 한 방법으로, Q-learning과 비슷하지만 행동 선택이 다른 방식입니다.
Q-learning이 미래의 최적 행동을 가정하고 학습한다면, SARSA는 실제로 선택한 행동을 기반으로 학습합니다.
즉 (현재 State, 현재 Action, 현재 Reward, 다음 State, 최적의 행동)이 Q-learning이었다면,
(현재 State, 현재 Action, 현재 Reward, 다음 State, 다음 Action)이 SARSA가 되고, 각각의 앞글자를 따와 SARSA라고 이름을 붙이게 된 것입니다.
frozen lake를 Q-learning으로 진행하는 방법은 아래 링크에서 확인해주세요.
https://yhj9855.com/entry/RL-gymnasium-frozen-lake-%EA%B0%95%ED%99%94-%ED%95%99%EC%8A%B5-2
[RL] gymnasium frozen lake 강화 학습 - 2
안녕하세요! 오늘은 기존에 작성한 frozen lake 문제를 Q-learning으로 진행하는 방법에 대해 포스팅 하겠습니다. Q-learning이란?강화 학습의 한 방법으로, Q라는 테이블을 이용하는 것입니다.Q 테이블
yhj9855.com
frozen lake에 관한 전체적인 설명은 아래 포스팅에서 진행하고 있으니, 먼저 확인해주세요!
https://yhj9855.com/entry/RL-gymnasium-frozen-lake-%EA%B0%95%ED%99%94-%ED%95%99%EC%8A%B5-1
[RL] gymnasium frozen lake 강화 학습 - 1
안녕하세요! 오늘은 gymnasium에서 제공하는 frozen lake 문제에 대한 설명에 대해 포스팅 하겠습니다. 강화 학습이란?행동을 통해 얻는 보상을 기반으로 학습하는 AI의 한 분야입니다.흔히 Reinforcemen
yhj9855.com
그럼 본격적으로 SARSA로 frozen lake 문제를 풀어보도록 하겠습니다.
SARSA로 frozen lake를 풀 때, 크게 두 가지를 생각하시면 됩니다.
- Q 값을 바탕으로 행동을 선택
- 선택한 행동을 SARSA 공식을 사용해서 Q 값 업데이트
위의 두 가지를 하나씩 자세히 살펴보겠습니다.
Q 값을 바탕으로 행동을 선택
해당 부분은 현재까지 업데이트 된 Q 값을 바탕으로 행동을 선택하는 것입니다.
기본적으로는 Q 값이 가장 높은 행동을 선택하면 됩니다.
하지만 여기에는 한 가지 문제점이 있습니다.
Q 값이 제대로 업데이트가 될 때까지 충분한 탐험을 진행하지 못했을 경우, 제대로 된 행동을 추출할 수 없다는 것입니다.
예를 들어, 초반에는 Q 값이 모두 0이기 때문에 (0, 0)에서 왼쪽으로 가는 행동만 선택하기 때문에 게임을 진행할 수가 없습니다.
저희는 이 문제를 해결하기 위해, ϵ-greedy 방법을 사용할 수 있습니다.
- ϵ-greedy란?
탐험과 이용의 균형을 맞추기 위한 행동 선택 방법으로, 아래 공식을 따릅니다.
여기서 ϵ은 0과 1 사이의 값으로 ϵ 확률 만큼은 랜덤하게 행동을 하게 하여 탐험을 진행하도록 하고, (1-ϵ) 확률 만큼 Q 값이 가장 높은 행동을 선택하도록 합니다.
해당 ϵ을 초반에 높게 설정하고 점차 ϵ을 줄임으로써, 초반에는 랜덤 행동을 통한 탐험을 하게 하고 점차 Q 값을 이용하도록 행동을 선택할 수 있습니다.
ϵ-greedy를 활용하여 행동을 선택하는 코드는 아래와 같습니다.
# 초기 값은 보통 1로 설정
epsilon = 1.0
train = True
# ϵ-greedy를 활용한 행동 선택
def select_action(state) :
# 훈련을 할 경우에는 ϵ-greedy 방법을 사용
# 테스트를 진행할 때는 온전히 Q 값만 사용
# np.random.rand()를 넣어, 후반에도 종종 탐험을 할 수 있도록 함
if np.random.rand() < epsilon and train :
action = np.random.choice([0, 1, 2, 3])
else :
action = np.argmax(Q[state])
return action
선택한 행동을 SARSA 공식을 사용해서 Q 값 업데이트
해당 부분은 위에서 선택한 행동을 환경에서 실행해보고, 그 결과 값을 SARSA 공식에 맞게 Q 값을 업데이트 하는 것입니다.
코드로 들어가기 전에 먼저 Q 테이블을 업데이트하는 공식을 먼저 살펴보겠습니다.
해당 수식은 Q(s, a)를 업데이트 하는데, 특정 학습률 α에 있어 (1- α)만큼 현재의 Q 값과 α만큼의 (보상값 r + 할인율 γ * 다음 state와 action의 Q값 Q(s', a'))를 반영한다는 의미입니다.
Q-learning을 배우신 분들은 아시겠지만, Q-learning에서의 maxQ값이 특정 행동 a'의 Q 값인 Q(s', a')로 바뀐 것을 알 수 있습니다.
- 학습률 α
값이 높을수록 다음 행동 값 즉, 새로운 정보를 더 많이 반영한다는 것이고, 낮을수록 현재의 Q 값 즉, 기존의 경험을 더 많이 유지한다는 의미입니다.
- 할인율 γ
미래 보상의 중요도를 나타내는 지표로, 보통은 미래의 보상에 너무 의존하지 않도록 1보다 약간 작은 수로 지정하는 것이 보통입니다.
- Q(s', a')
실제로 선택한 다음 행동 a'에 대한 Q 값으로, 위의 행동 선택을 기반하여 다음 state에서 실제 action을 고른 값입니다.
이렇게 실제 행동을 기반으로 Q 값을 업데이트 하기 때문에, 안정적이지만 그만큼 느릴 수 있습니다.
하지만, frozen lake는 공간이 작은 문제라서 Q-learning과 크게 결과 차이는 없으실 거예요:)
해당 공식을 바탕으로 SARSA를 진행하는 코드는 아래와 같습니다.
# 학습을 진행할 때는 render 모드 비활성화
env = gym.make('FrozenLake-v1', desc=None, map_name=map_size, is_slippery=is_slippery)
env.reset()
# 환경의 행동 범위 : 여기서는 상, 하, 좌, 우 총 4개
action_size = env.action_space.n
# defaultdict은 키가 없을 때 자동으로 기본값을 생성하기 때문에 강화 학습에서 많이 사용
Q = defaultdict(lambda: np.zeros(action_size))
alpha = 0.1
gamma = 0.99
# 총 학습을 진행할 에피소드 수
max_episode = 10000
def learn() :
reward_list = []
for i in range(1, max_episode+1) :
# 100번째 마다 학습이 진행되고 있음을 출력
if i % 100 == 0 :
# 해당 에피소드까지 진행된 모든 보상의 평균을 구함
avg_reward = sum(reward_list)/100
print("\rEpisode {}/{} || average reward {}".format(i, max_episode, avg_reward), end="")
reward_list = []
# 에피소드를 처음 시작할 때 reset
state, _ = env.reset()
done = False
all_reward = 0
# 에피소드가 종료될 때까지 반복
while not done :
# Q 테이블을 바탕으로 action을 고르는 함수
action = select_action(state)
# state, reward, done 외 사용하지 않기 때문에 _ 처리
new_state, reward, done, _, _ = env.step(action)
next_action = select_action(new_state)
# SARSA
Q[state][action] = (1-alpha)*Q[state][action] + alpha*(reward + gamma*Q[new_state][next_action])
all_reward += reward
state = new_state
# 50번째 에피소드 마다 ϵ 값을 줄여줌
if i % 50 == 0 :
epsilon *= 0.99
reward_list.append(all_reward)
위의 두 가지 과정을 합치면 SARSA로 frozen lake를 풀 수 있는 코드가 완성됩니다!
학습 후 테스트를 진행하고 싶으신 경우에는 render를 킨 환경을 다시 세팅해서 해주시면 됩니다.
전체 코드
행동 선택, 학습, 테스트 과정을 모두 포함한 전체 코드는 아래와 같습니다.
import gymnasium as gym
from collections import defaultdict
import numpy as np
# 미끄러짐 옵션 True/False 선택 가능
is_slippery = True
# 8x8 중에 선택 가능
map_size = '4x4'
env = gym.make('FrozenLake-v1', desc=None, map_name=map_size, is_slippery=is_slippery)
env.reset()
action_size = env.action_space.n
Q = defaultdict(lambda: np.zeros(action_size))
alpha = 0.1
gamma = 0.99
epsilon = 1.0
train = True
max_episode = 100000
def select_action(state) :
if np.random.rand() < epsilon and train :
action = np.random.choice([0, 1, 2, 3])
else :
action = np.argmax(Q[state])
return action
def learn() :
global epsilon
reward_list = []
for i in range(1, max_episode+1) :
# 100번째 마다 학습이 진행되고 있음을 출력
if i % 100 == 0 :
# 해당 에피소드까지 진행된 모든 보상의 평균을 구함
avg_reward = sum(reward_list)/100
print("\rEpisode {}/{} || average reward {}".format(i, max_episode, avg_reward), end="")
reward_list = []
state, _ = env.reset()
done = False
all_reward = 0
while not done :
action = select_action(state)
new_state, reward, done, _, _ = env.step(action)
next_action = select_action(new_state)
# SARSA
Q[state][action] = (1-alpha)*Q[state][action] + alpha*(reward + gamma*Q[new_state][next_action])
all_reward += reward
state = new_state
if i % 50 == 0 :
epsilon *= 0.99
reward_list.append(all_reward)
# 학습한 Q를 바탕으로 frozen lake 테스트
def testing_after_learning():
# render를 켜야 제대로 학습이 되었는지 확인할 수 있음
env = gym.make('FrozenLake-v1', desc=None, map_name=map_size, is_slippery=is_slippery, render_mode='human')
total_test_episode = 10
rewards = []
for episode in range(total_test_episode):
state, _ = env.reset()
episode_reward = 0
while True:
action = select_action(state)
new_state, reward, done, _, _ = env.step(action)
episode_reward += reward
if done:
rewards.append(episode_reward)
break
state = new_state
print("")
print("avg: " + str(sum(rewards) / total_test_episode))
if __name__ == "__main__" :
learn()
testing_after_learning()
테스트를 진행하면서 is_slippery 옵션을 껐을 경우에는 1.0 보상을 받으면 성공이고, is_slippery 옵션을 켰을 경우에는 70% 이상 1.0 보상을 받으면 성공이라고 보실 수 있습니다.
추가로 is_slippery 옵션을 켰을 경우에는 학습을 많이 진행해야 어느 정도 수렴하시는 걸 보실 수 있습니다!
아무래도 model-free로 진행을 하니까 많이 느리더라구요ㅠㅠ
- model-based
model-free가 아닌 어느 정도 model-based로 빠르게 학습을 하고 싶으신 경우 아래 상황을 고려할 수 있습니다.
- 행동 한 번을 진행할 때마다 reward에 - 진행
→ RL이 최단 경로로 진행하려는 경향을 학습할 수 있음 - 구멍에 빠졌을 경우, reward에 크게 - 진행
→ 구멍에 빠지지 않는 쪽으로 빠르게 학습할 수 있음 - 도착했을 경우, reward를 크게 추가
→ 도착 지점에 확실히 도착하기 위해 큰 reward를 지급
그 외에도 벽에 부딪히거나 하는 등 맵을 알고 있기 때문에 환경에 맞게 reward를 추가로 주거나 마이너스를 진행하여, model-based 모델을 만들 수도 있습니다.
그래도 강화 학습을 제대로 알기 위해서는 model-free로 진행해보는 것을 추천드립니다!
코드에 대해 궁금한 부분이 있으신 분들은 댓글로 남겨주시면, 답변 드리도록 하겠습니다.

★읽어주셔서 감사합니다★
'Python(파이썬) > RL (강화 학습)' 카테고리의 다른 글
[RL] gymnasium cart pole 강화 학습 - 1 (0) | 2025.02.18 |
---|---|
[RL] gymnasium frozen lake 강화 학습 - 2 (2) | 2025.02.03 |
[RL] gymnasium frozen lake 강화 학습 - 1 (1) | 2025.01.22 |