카테고리 없음

패스트캠퍼스 환급챌린지 50일차 : 스크래치부터 시작하는 강화학습의 모든 것 강의 후기

dev-self 2025. 4. 23. 10:58

*본 포스팅은 패스트캠퍼스 환급 챌린지 참여를 위해 작성하였습니다.

 

|내용 정리|

<Q-learning 알고리즘 코드 구현 연습>

1. 기본 개념 설명(복습)

  • 강화학습 (Reinforcement Learning): 에이전트(Agent)가 환경(Environment)과 상호작용하며 보상(Reward)을 최대화하는 방향으로 행동(Action)을 학습하는 머신러닝의 한 분야이다. 시행착오(Trial-and-error)를 통해 학습한다.
  • 에이전트 (Agent): 학습하고 행동하는 주체 (이 코드에서는 격자 세계를 돌아다니는 개체).
  • 환경 (Environment): 에이전트가 상호작용하는 외부 세계 (이 코드에서는 4x4 격자).
  • 상태 (State, s): 환경의 현재 상황을 나타내는 정보 (에이전트의 위치).
  • 행동 (Action, a): 에이전트가 특정 상태에서 취할 수 있는 선택지 (상, 하, 좌, 우 이동).
  • 보상 (Reward, r): 에이전트가 특정 행동을 취한 결과로 환경으로부터 받는 피드백 신호 (목표 도달 시 +10, 그 외 0).
  • 정책 (Policy, π): 특정 상태에서 어떤 행동을 할지 결정하는 에이전트의 전략 또는 규칙.
  • 가치 함수 (Value Function): 특정 상태 또는 상태-행동 쌍이 장기적으로 얼마나 좋은지를 나타내는 함수.
    • 상태-가치 함수 (State-Value Function, V(s)): 상태 s에서 시작했을 때 앞으로 받을 총 보상의 기댓값.
    • 행동-가치 함수 (Action-Value Function, Q(s, a)): 상태 s에서 행동 a를 취했을 때 앞으로 받을 총 보상의 기댓값. Q-러닝은 이 Q함수를 학습한다.
  • Q-러닝 (Q-Learning): 대표적인 모델-프리(Model-Free), 오프-폴리시(Off-Policy) 강화학습 알고리즘이다.
    • 모델-프리: 환경의 작동 방식(상태 전이 확률, 보상 함수)을 미리 알 필요 없이 학습한다.
    • 오프-폴리시: 에이전트가 실제로 따르는 행동 정책(탐험 포함)과 학습하려는 목표 정책(최적 정책)이 달라도 학습이 가능하다. Q-러닝은 항상 다음 상태에서 가능한 Q-값 중 최댓값을 사용하여 업데이트하기 때문에 오프-폴리시이다.
    • Q-테이블: 상태와 행동 쌍에 대한 Q-값을 저장하는 테이블 (action_value_matrix).
    • 시간차 학습 (Temporal Difference, TD): 현재 추정치를 사용하여 다음 추정치를 업데이트하는 방식. Q-러닝은 TD 학습의 일종이다. r + gamma * max_a'(Q(s', a')) 부분이 TD 타겟이다.
  • Epsilon-Greedy 탐험 (Epsilon-Greedy Exploration): 학습 초반에는 무작위 행동(탐험)을 통해 다양한 경험을 쌓고, 학습이 진행됨에 따라 점차 최적이라고 생각되는 행동(활용)의 비율을 높이는 전략이다. epsilon 값이 이 비율을 조절한다.
  • 학습률 (Learning Rate, α): TD 에러를 얼마나 반영하여 Q-값을 업데이트할지 결정하는 비율. 너무 크면 학습이 불안정하고, 너무 작으면 학습이 느려진다. 이 코드에서는 점차 감소시킨다.
  • 할인율 (Discount Factor, γ): 미래 보상을 현재 가치로 환산할 때 얼마나 할인할지를 결정하는 값 (0~1). 1에 가까울수록 미래 보상을 중요하게 생각한다.

2. 환경 설정 (environment.py)

환경 코드는 4x4 격자 세계(Grid World)를 구현한다.

초기화 함수 (__init__)

이 코드는 강화학습 에이전트가 상호작용할 환경을 정의한다. 4x4 크기의 격자 세계(Grid World) 환경이다.

def __init__(self):
    '''
    state_space: 4x4 grid info using numpy
    value of the agent location: 1
    value of the goal location: -1

    action_space: {0, 1, 2, 3}
    0: up
    1: right
    2: down
    3: left
    '''
    self.agent_pos = {'y': 0, 'x': 0}
    self.goal_pos = {'y': 3, 'x': 3}
    self.y_min, self.x_min, self.y_max, self.x_max = 0, 0, 3, 3

    # set up state
    self.state = np.zeros([4, 4])
    self.state[self.goal_pos['y'], self.goal_pos['x']] = -1
    self.state[self.agent_pos['y'], self.agent_pos['x']] = 1

    self.state_space = list()
    for y in range(4):
        for x in range(4):
            state = np.zeros([4,4])
            state[self.goal_pos['y'], self.goal_pos['x']] = -1
            state[y, x] = 1
            self.state_space.append(state)

    self.action_space = [0, 1, 2, 3]
  • 에이전트 위치: (0,0)에서 시작
  • 목표 위치: (3,3)에 고정
  • 상태 표현: 4x4 NumPy 배열로, 에이전트 위치는 1, 목표는 -1, 나머지는 0
  • 상태 공간: 에이전트가 있을 수 있는 모든 위치를 미리 계산 (총 16개 상태)
  • 행동 공간: [0(상), 1(우), 2(하), 3(좌)]

환경 리셋 (reset)

def reset(self):
    self.agent_pos = {'y': 0, 'x': 0}
    self.state = np.zeros([4,4])
    self.state[self.goal_pos['y'], self.goal_pos['x']] = -1
    self.state[self.agent_pos['y'], self.agent_pos['x']] = 1
    
    return self.state
  • 에이전트 위치를 (0,0)으로 초기화
  • 상태 배열 재설정 후 반환

환경 진행 (step)

def step(self, action):
    # Update environmental variables 
    if action == 0:
        # 'y' should be decreased by 1 or stay the same when it is at the top row
        self.agent_pos['y'] = max(
            self.agent_pos['y'] - 1, 
            self.y_min
        ) 
    elif action == 1:
        # 'x' should be increased by 1 or stay the same when it is at the most right column
        self.agent_pos['x'] = min(
            self.agent_pos['x'] + 1, 
            self.x_max
        )
    elif action == 2:
        # 'y' should be increased by 1 or stay the same when it is at the bottom row
        self.agent_pos['y'] = min(
            self.agent_pos['y'] + 1, 
            self.y_max
        )
    elif action == 3:
        # 'x' should be decreased by 1 or stay the same when it is at the most left column
        self.agent_pos['x'] = max(
            self.agent_pos['x'] - 1, 
            self.x_min
        )
    else:
        assert False, "Invalid action value was fed to step."

    # Make a next state after transition
    prev_state = self.state
    self.state = np.zeros([4,4])
    self.state[self.goal_pos['y'], self.goal_pos['x']] = -1
    self.state[self.agent_pos['y'], self.agent_pos['x']] = 1

    done = False
    if self.agent_pos == self.goal_pos:
        done = True

    reward = self.reward(prev_state, action, self.state)

    return reward, self.state, done
  • 주어진 행동(action)에 따라 에이전트 위치 업데이트
  • 격자 경계를 벗어나지 않도록 처리 (min, max 함수 사용)
  • 상태 업데이트 후 이전 상태 저장
  • 목표 도달 여부 확인
  • 보상 계산 및 결과 반환 (보상, 다음 상태, 종료 여부)

보상 함수 (reward)

def reward(self, s, a, s_next):
    reward = 0
    y, x = np.where(s == 1)
    y_next, x_next = np.where(s_next == 1)
    if (
            (y_next == self.goal_pos['y'] and x_next == self.goal_pos['x']) and
            (y != self.goal_pos['y'] or x != self.goal_pos['x'])
        ):  # Reached the goal
        reward = 10
        
    return reward
  • 현재 상태와 다음 상태에서 에이전트 위치 추출
  • 목표에 도달했을 때만 보상 10 지급
  • 그 외에는 보상 0 (Sparse reward)

2. Q-learning 구현 (q_learning.py)

하이퍼파라미터 및 유틸리티 함수

gamma = 0.9  # 할인율
k_alpha = 2e-2  # 학습률 감쇠 계수
k_eps = 2e-4  # 탐험률 감쇠 계수

def get_state_index(state_space, state):
    for i_s, s in enumerate(state_space):
        if (s == state).all():
            return i_s
    assert False, "Couldn't find the state from the state space"
  • gamma: 미래 보상의 가치를 현재 기준으로 할인하는 비율
  • k_alpha: 학습률을 에피소드가 진행됨에 따라 감소시키는 계수
  • k_eps: 탐험률을 스텝이 진행됨에 따라 감소시키는 계수
  • get_state_index: 주어진 상태 배열이 상태 공간 내에서 어떤 인덱스에 해당하는지 찾음

Q-learning 함수

def q_learning(env):
    action_value_matrix = np.zeros([len(env.state_space), len(env.action_space)])

    def sample_action(eps, action_value):
        a_max = action_value.argmax()
        pi = np.zeros([len(env.action_space)])
        pi[:] = eps / len(env.action_space)
        pi[a_max] = pi[a_max] + 1 - eps
        a = np.random.choice(env.action_space, p=pi)
        return a

    def get_eps(total_step_count):
        return 1 / (1 + k_eps * total_step_count)
  • action_value_matrix: Q-테이블 초기화 (16x4 크기)
  • sample_action: 입실론-그리디 방식으로 행동 선택
    • 확률 eps로 무작위 행동 선택
    • 확률 1-eps로 최대 Q-값을 가진 행동 선택
  • get_eps: 전체 스텝 수에 따라 탐험률 감소

학습 과정 구현

total_step_count = 0
for loop_count in range(2000):
    done = False
    step_count = 0

    s = env.reset()
    i_s = get_state_index(env.state_space, s)

    # Generate an episode
    while not done:
        action_value = action_value_matrix[i_s]
        eps = get_eps(total_step_count)
        a = sample_action(eps, action_value)
        r, s_next, done = env.step(a)

        i_s_next = get_state_index(env.state_space, s_next)
        alpha = 1 / (1 + k_alpha * loop_count)
        td = r + gamma * action_value_matrix[i_s_next].max() - action_value_matrix[i_s][a]
        action_value_matrix[i_s][a] = action_value_matrix[i_s][a] + alpha * td
        
        if done:
            action_value_matrix[i_s_next] = 0

        step_count += 1
        total_step_count += 1
        
        s = s_next
        i_s = i_s_next
  1. 2000번의 에피소드를 반복
  2. 각 에피소드 내부:
    • 환경 초기화 (env.reset())
    • 에피소드 완료될 때까지 반복:
      • 현재 상태의 Q-값 가져오기
      • 현재 탐험률 계산
      • 행동 선택
      • 환경과 상호작용 (행동 실행)
      • 학습률 계산 (alpha)
      • TD(Temporal Difference) 에러 계산 및 Q-값 업데이트
      • 종료 상태 처리 (종료 상태의 Q-값은 0으로 설정)
      • 상태 업데이트 및 카운터 증가

정책 추출 및 반환

# Generate optimal policy from the action value function
policy = np.zeros([len(env.state_space), len(env.action_space)])
state_indexes = np.arange(len(env.state_space))
argmax_actions = action_value_matrix.argmax(axis=-1)
policy[state_indexes, argmax_actions] = 1.0

return action_value_matrix, policy
  • 학습된 Q-테이블에서 각 상태별 최적 행동 추출
  • 확정적 정책 생성 (최적 행동에 확률 1.0 부여)

메인 실행 부분

if __name__ == "__main__":
    np.set_printoptions(formatter={'float': '{: 0.3f}'.format})
    env = Env()
    action_value_matrix, policy = q_learning(env)

    argmax_actions = action_value_matrix.argmax(axis=-1)
    value_vector = np.sum(policy * action_value_matrix, axis=-1)

    value_table = value_vector.reshape(4, 4)
    argmax_actions_table = argmax_actions.reshape(4, 4)
    print(
        f"value_table: \n{value_table}\n"
        + f"argmax_actions: \n{argmax_actions.reshape(4, 4)}"
    )
  • 환경 생성
  • Q-learning 실행
  • 학습된 Q-테이블에서 최적 행동과 가치 계산
  • 결과를 4x4 격자 형태로 출력

3. 코드 구조도

  • 다이어그램은 Q-learning 알고리즘의 실행 흐름을 보여준다. 메인 함수에서 환경을 초기화하고, Q-learning 함수를 호출하며, 내부에서는 에피소드와 스텝을 반복하면서 Q-테이블을 업데이트하고 최종적으로 최적 정책을 추출한다.

실행 및 디버깅

출력으로 보이는 부분은 학습 과정 중 1999번째 에피소드에서의 Q-테이블(action_value_matrix) 값이다. 이 시점에서 epsilon(탐험률)은 0.2128, alpha(학습률)는 0.0257이다.

 

가치 테이블(value_table):

[[ 5.905  6.561  7.290  8.100]
 [ 6.561  7.290  8.100  9.000]
 [ 7.290  8.100  9.000 10.000]
 [ 8.100  9.000 10.000  0.000]]
  • 각 위치에서의 최적 가치를 보여준다.
  • 목표 위치(3,3)의 가치는 0.000으로 설정되어 있다(종료 상태).
  • 목표에 가까울수록 가치가 높아지는 패턴이 명확하다.

최적 행동(argmax_actions):

[[1 2 1 2]
 [1 1 1 2]
 [1 2 1 2]
 [1 1 1 0]]
  • 각 위치에서 에이전트가 취해야 할 최적 행동을 나타낸다.
  • 0: 위쪽, 1: 오른쪽, 2: 아래쪽, 3: 왼쪽
  • 대부분의 위치에서 오른쪽(1)이나 아래쪽(2)으로 이동하는 것이 최적인데, 이는 목표가 오른쪽 아래(3,3)에 있기 때문이다.
  • 마지막 행의 마지막 열(목표 위치, 3,3)에서는 위쪽(0)으로 이동하는 것이 표시되어 있지만, 이는 사실상 의미가 없다(종료 상태이므로).

해석:

  1. 학습이 성공적으로 이루어졌다. 목표 지점(3,3)에 가까울수록 가치가 높게 나타나며, 최적 행동들은 대체로 목표를 향해 움직이도록 설정되었다.
  2. 격자의 오른쪽과 아래쪽 경계를 따라 목표로 향하는 경로가 형성되었다. 에이전트는 대부분의 위치에서 오른쪽(1)이나 아래쪽(2)으로 이동하는 것을 선호한다.
  3. 가치 테이블에서 목표 상태(3,3)의 가치가 0인 것은 종료 상태 처리 로직(if done: action_value_matrix[i_s_next] = 0) 때문이다.
  4. 최적 정책은 에이전트가 어떤 위치에서든 최소한의 스텝으로 목표에 도달할 수 있도록 형성되었다.

 

|인증|

 

-------

패스트캠퍼스 링크

https://bit.ly/4hTSJNB