게시판Atari Pong: 화면 추적 상태 벡터로 DQN 학습하기예제 소스

예제 소스

Atari Pong: 화면 추적 상태 벡터로 DQN 학습하기 · 파일 12개

cartpol-origin.py

import os
import pickle
import random
import sys

import gymnasium as gym
import numpy as np
import tensorflow as tf
from collections import deque

from tensorflow.keras.layers import Dense
from tensorflow.keras.initializers import RandomUniform
from tensorflow.keras.optimizers import Adam


SAVE_DIR = os.path.join(os.path.dirname(__file__), "save_model_cartpol_origin")
WEIGHTS_PATH = os.path.join(SAVE_DIR, "model.weights.h5")
TARGET_WEIGHTS_PATH = os.path.join(SAVE_DIR, "target_model.weights.h5")
REPLAY_PATH = os.path.join(SAVE_DIR, "replay.pkl")


class DQN(tf.keras.Model):
    def __init__(self, action_size):
        super().__init__()
        self.fc1 = Dense(24, activation="relu")
        self.fc2 = Dense(24, activation="relu")
        self.fc_out = Dense(action_size, kernel_initializer=RandomUniform(-1e-3, 1e-3))

    def call(self, x):
        x = self.fc1(x)
        x = self.fc2(x)
        return self.fc_out(x)


class DQNAgent:
    def __init__(self, state_size, action_size):
        self.state_size = state_size
        self.action_size = action_size
        self.discount_factor = 0.99
        self.learning_rate = 0.001
        self.epsilon = 1.0
        self.epsilon_decay = 0.999
        self.epsilon_min = 0.01
        self.batch_size = 64
        self.train_start = 1000
        self.memory = deque(maxlen=2000)
        self.model = DQN(action_size)
        self.target_model = DQN(action_size)
        self.optimizer = Adam(learning_rate=self.learning_rate)
        self.update_target_model()

    def update_target_model(self):
        self.target_model.set_weights(self.model.get_weights())

    def remember(self, state, action, reward, next_state, done):
        self.memory.append((state, action, reward, next_state, done))

    def choose_action(self, state):
        if np.random.rand() <= self.epsilon:
            return random.randrange(self.action_size)
        return int(np.argmax(self.model.predict(state, verbose=0)))

    def train_model(self):
        if self.epsilon > self.epsilon_min:
            self.epsilon *= self.epsilon_decay

        mini_batch = random.sample(self.memory, self.batch_size)
        states = np.array([sample[0][0] for sample in mini_batch])
        actions = np.array([sample[1] for sample in mini_batch])
        rewards = np.array([sample[2] for sample in mini_batch])
        next_states = np.array([sample[3][0] for sample in mini_batch])
        dones = np.array([sample[4] for sample in mini_batch])

        with tf.GradientTape() as tape:
            predicts = self.model(states)
            one_hot_action = tf.one_hot(actions, self.action_size)
            predicts = tf.reduce_sum(one_hot_action * predicts, axis=1)
            target_predicts = tf.stop_gradient(self.target_model(next_states))
            max_q = np.amax(target_predicts, axis=-1)
            targets = rewards + (1 - dones) * self.discount_factor * max_q
            loss = tf.reduce_mean(tf.square(targets - predicts))

        trainable_vars = self.model.trainable_variables
        grads = tape.gradient(loss, trainable_vars)
        grads_and_vars = [(g, v) for g, v in zip(grads, trainable_vars) if g is not None]
        self.optimizer.apply_gradients(grads_and_vars)


def _build_models(agent, flat_dim):
    dummy = np.zeros((1, flat_dim), dtype=np.float32)
    agent.model(dummy)
    agent.target_model(dummy)


def save_checkpoint(agent):
    os.makedirs(SAVE_DIR, exist_ok=True)
    _build_models(agent, agent.state_size)
    agent.model.save_weights(WEIGHTS_PATH)
    agent.target_model.save_weights(TARGET_WEIGHTS_PATH)
    payload = {
        "memory": list(agent.memory),
        "epsilon": float(agent.epsilon),
    }
    with open(REPLAY_PATH, "wb") as f:
        pickle.dump(payload, f, protocol=pickle.HIGHEST_PROTOCOL)
    print(f"Saved checkpoint → {SAVE_DIR}")


def load_checkpoint(agent, flat_dim):
    if not (os.path.isfile(WEIGHTS_PATH) and os.path.isfile(TARGET_WEIGHTS_PATH) and os.path.isfile(REPLAY_PATH)):
        return False
    _build_models(agent, flat_dim)
    agent.model.load_weights(WEIGHTS_PATH)
    agent.target_model.load_weights(TARGET_WEIGHTS_PATH)
    with open(REPLAY_PATH, "rb") as f:
        payload = pickle.load(f)
    agent.memory.clear()
    for t in payload["memory"]:
        agent.memory.append(t)
    agent.epsilon = float(payload.get("epsilon", agent.epsilon))
    print(f"Loaded checkpoint from {SAVE_DIR} (memory len={len(agent.memory)}, epsilon={agent.epsilon:.4f})")
    return True


if __name__ == "__main__":
    env = gym.make("CartPole-v1")
    state_size = int(np.prod(env.observation_space.shape))
    action_size = int(env.action_space.n)
    agent = DQNAgent(state_size, action_size)

    load_checkpoint(agent, state_size)

    num_episodes = 15
    for _ in range(num_episodes):
        state, _ = env.reset()
        state = np.asarray(state, dtype=np.float32).reshape(1, -1)
        done = False

        while not done:
            action = agent.choose_action(state)
            next_state, reward, terminated, truncated, info = env.step(action)
            done = terminated or truncated
            next_state = np.asarray(next_state, dtype=np.float32).reshape(1, -1)
            agent.remember(state, action, reward, next_state, done)
            state = next_state
            if len(agent.memory) >= agent.train_start:
                agent.train_model()

        agent.update_target_model()

    save_checkpoint(agent)
    env.close()