배우기 - computer로 하는거

[강화학습] chatgpt랑 공부하는 강화학습: Q-learning, FrozenLake - 3

taecheon 2025. 4. 13. 17:11

지난번까지는 학습 코드에 대해 살펴봤다. 이번 글에서는 학습이 끝난 agent가 어떻게 행동하는지 살펴보겠다.

우선 main함수를 다시 살펴보면

def main():
    train_env = gym.make("FrozenLake-v1", map_name="4x4", is_slippery=True)
    play_env = gym.make("FrozenLake-v1", map_name="4x4", is_slippery=True, render_mode="human")

    q_table = train_on_frozen_lake(train_env)  # 학습만 수행
    play_trained_agent(play_env, q_table)      # 학습된 q_table로 결과 보기
    
if __name__ == '__main__':
    main()

와 같은데, train_env와 전부 똑같은데 render_mode만 "human"으로 되어 있다. render_mode에 따른 차이는 지난 글을 참고하면 된다.

main에서 play_trained_agent를 호출하는데, agent를 실행하고 실행 과정을 확인하는 코드는 아래와 같다.

def play_trained_agent(env, q_table):
    state, _ = env.reset()
    done = False

    print("\n🚶 에이전트 경로:")
    env.render()
    time.sleep(0.05)

    while not done:
        action = np.argmax(q_table[state])
        state, reward, terminated, truncated, _ = env.step(action)
        done = terminated or truncated

        # print(env.render())
        env.render()
        time.sleep(0.05)

    env.close()

    if reward == 1:
        print("🎉 성공적으로 Goal 도달!")
    else:
        print("💥 실패! 구멍에 빠졌거나 종료됨")

실행결과를 확인할 환경(env)과 학습이 끝난 q_table을 입력으로 받아 q_table을 이용해 env안에서 action을 순서대로 실행한다.

while 문 안쪽은 학습 코드와 동일하다. 실제로 실행하면 결과는 아래와 같이 나타난다.

성공한 케이스


실패한 케이스


위와 같은 실행 결과를 동영상으로도 저장할 수 있다.

받은 코드:

from gymnasium.wrappers import RecordVideo

def main():
    train_env = gym.make("FrozenLake-v1", map_name="4x4", is_slippery=True)

    # 영상 저장용 환경: render_mode는 'rgb_array'
    play_env = gym.make(
        "FrozenLake-v1",
        map_name="4x4",
        is_slippery=True,
        render_mode="rgb_array"
    )

    # 영상 녹화 래퍼 적용
    play_env = RecordVideo(
        play_env,
        video_folder="./videos",             # 저장 경로
        name_prefix="frozenlake_final",      # 파일 이름 prefix
        episode_trigger=lambda x: True       # 모든 실행 저장
    )

    q_table = train_on_frozen_lake(train_env)
    play_trained_agent(play_env, q_table)
    play_env.close()  # 이게 호출되어야 영상 저장됨

위 코드로 메인함수를 수정하면 되는데, RecordVideo를 사용하려면 "pip install moviepy" 로 moviepy라는걸 설치해야 한다. 최초 에러메세지에는 

gymnasium.error.DependencyNotInstalled: MoviePy is not installed, run
pip install "gymnasium[other]"

라고 나오는데, chatGPT가 pip install "gymnasium[other]" 를 하면 쓸데없는 패키지도 깔리니까 그냥 pip install moviepy 로 설치하라고 한다.

어쨋든, 필요한 패키지를 설치하고 아래 코드를 실행하면 학습 후 실행 결과가 동영상으로 저장이 된다. 1편에서 올렸던 코드와 달라진 점은 main함수 부분인데, 1편에서는 학습 결과를 바로 실행하여 확인할수 있고, 이 버전은 동영상으로 저장하는 버전이다.

최종 코드 (동영상으로 저장):

import gymnasium as gym
import numpy as np
import time
import matplotlib.pyplot as plt
from tqdm import tqdm
from gymnasium.wrappers import RecordVideo


def play_trained_agent(env, q_table):
    state, _ = env.reset()
    done = False

    print("\n🚶 에이전트 경로:")
    env.render()
    time.sleep(0.05)

    while not done:
        action = np.argmax(q_table[state])
        state, reward, terminated, truncated, _ = env.step(action)
        done = terminated or truncated

        # print(env.render())
        env.render()
        time.sleep(0.05)

    env.close()

    if reward == 1:
        print("🎉 성공적으로 Goal 도달!")
    else:
        print("💥 실패! 구멍에 빠졌거나 종료됨")

def train_on_frozen_lake(env):
    rewards = []

    # 하이퍼파라미터 설정
    alpha = 0.8     # 학습률 (learning rate)
    gamma = 0.99    # 할인율
    epsilon = 1.0   # 탐험 확률 (epsilon-greedy)
    epsilon_min = 0.1
    epsilon_decay = 0.9995
    episodes = 10000 # 에피소드 수

    # Q-테이블 초기화: 상태 수 x 행동 수
    q_table = np.zeros([env.observation_space.n, env.action_space.n])

    # 학습 루프
    for _ in tqdm(range(episodes)):
        state, _ = env.reset()
        done = False
        total_reward = 0

        while not done:
            # 행동 선택: 탐험 or 활용 (epsilon-greedy)
            if np.random.uniform(0, 1) < epsilon:
                action = env.action_space.sample()  # 무작위 행동
            else:
                action = np.argmax(q_table[state])

            # 환경에 행동 적용 -> 다음 상태, 보상, 종료 여부 등 받기
            next_state, reward, terminated, truncated, _ = env.step(action)
            done = terminated or truncated

            # Q 테이블 업데이트
            q_table[state, action] = q_table[state, action] + alpha * (
                reward + gamma * np.max(q_table[next_state]) - q_table[state, action]
            )

            # 상태 업데이트
            state = next_state

            # epsilon 감소
            if epsilon > epsilon_min:
                epsilon *= epsilon_decay

            total_reward += reward

        rewards.append(total_reward)

    # 학습 완료 후 Q 테이블 출력
    print("Q-table")
    print(q_table)

    # 누적 성공률 (100 에피소드마다 평균)
    window = 100
    moving_avg = [np.mean(rewards[i - window:i + 1]) for i in range(window, len(rewards))]

    plt.plot(moving_avg)
    plt.title("🎯 Moving Average of Success Rate")
    plt.xlabel("Episode")
    plt.ylabel("Success Rate")
    plt.grid(True)
    plt.show()

    return q_table


def main():
    train_env = gym.make("FrozenLake-v1", map_name="4x4", is_slippery=True)
    # 영상 저장용 환경: render_mode는 'rgb_array'
    play_env = gym.make(
        "FrozenLake-v1",
        map_name="4x4",
        is_slippery=True,
        render_mode="rgb_array"
    )

    # 영상 녹화 래퍼 적용
    play_env = RecordVideo(
        play_env,
        video_folder="./result_video",  # 저장 경로
        name_prefix="frozenlake_final",  # 파일 이름 prefix
        episode_trigger=lambda x: True  # 모든 실행 저장
    )

    q_table = train_on_frozen_lake(train_env)  # 학습만 수행
    play_trained_agent(play_env, q_table)      # 학습된 q_table로 결과 보기
    play_env.close()  # 이게 호출되어야 영상 저장됨

if __name__ == '__main__':
    main()