DQNAgent
저번 글에서 가상환경을 만들었으니 이제는 강화학습을 직접 해볼 시간이다.
이번에는 강화학습을 사용하여 Ping Pong 게임을 구현해보았습니다. Ping Pong 게임은 두 플레이어가 위아래로 움직이는 paddle을 조작하여 중앙의 공을 튕겨내는 게임입니다. 강화학습을 통해 게임을 학습하는 프로젝트를 준비하고자 한다면, 아래 코드와 설명을 참고해보세요.
- pygame 초기화
- 게임 창 크기 설정
- 색상 설정
- 공과 paddle의 속도 설정
- 게임 창 생성
- 게임 창 제목 설정
- 게임 파라미터 설정
- 평가 결과 저장 리스트 초기화
- Paddle 클래스 정의
9.1. Paddle 초기화
9.2. Paddle 그리기
9.3. Paddle 이동 (위로/아래로) - Ball 클래스 정의
10.1. Ball 초기화
10.2. Ball 그리기
10.3. Ball 이동
10.4. Ball 튕김 처리 - DQNAgent 클래스 정의
11.1. 메모리 초기화
11.2. 모델 생성
11.3. 메모리에 경험 저장
11.4. 행동 선택
11.5. 경험 재생 - Paddle과 Ball 인스턴스 생성
- DQNAgent 인스턴스 생성
- 게임 루프
14.1. 상태 초기화
14.2. 종료 여부 초기화
14.3. 점수 초기화
14.4. 게임 종료 조건 만족할 때까지 반복
14.4.1. 이벤트 처리
14.4.2. 키 입력 처리하여 paddle 이동
14.4.3. 공과 paddle의 충돌 처리
14.4.4. 게임 종료 조건 확인 및 점수 갱신
14.4.5. 다음 상태 관측
14.4.6. 에이전트에 경험 저장
14.4.7. 에이전트 모델 학습
14.4.8. 게임 화면 업데이트
14.5. 점수 저장
14.6. 에피소드 결과 출력 - 게임 종료
1. 필요한 모듈 및 초기 설정
우선, 필요한 모듈과 초기 설정을 진행합니다. 코드를 실행하기 위해 pygame, sys, numpy, random, tensorflow 등의 모듈을 import하고, 게임 창의 크기, 색상, 속도 등을 설정합니다.
import pygame
import sys
from pygame.locals import *
import random
import numpy as np
from collections import deque
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense
from tensorflow.keras.optimizers import Adam
# 게임 초기화 및 창 크기 설정
pygame.init()
WINDOW_WIDTH = 800
WINDOW_HEIGHT = 600
# 색상 설정
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
# 공과 paddle의 속도 설정
BALL_SPEED_X = 5
BALL_SPEED_Y = 5
PADDLE_SPEED = 7
# 게임 창 생성
WINDOW = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT))
pygame.display.set_caption("Ping Pong 게임")
# 게임 파라미터 설정
EPISODES = 1000
MEMORY_SIZE = 2000
BATCH_SIZE = 32
GAMMA = 0.95
EPSILON = 1.0
EPSILON_DECAY = 0.995
EPSILON_MIN = 0.01
LEARNING_RATE = 0.001
# 평가 결과 저장
scores = []
2. Paddle 클래스와 Ball 클래스 정의
게임에서 사용될 Paddle과 Ball의 클래스를 정의합니다. 각각의 클래스는 위치, 크기, 이동 등의 정보를 가지며, 그에 맞게 움직이고 화면에 그려지는 기능을 가지고 있습니다.
class Paddle:
def __init__(self, x, y):
# 초기 위치 및 크기 설정
self.x = x
self.y = y
self.width = 10
self.height = 100
self.rect = pygame.Rect(self.x, self.y, self.width, self.height)
def draw(self):
# paddle 그리기
pygame.draw.rect(WINDOW, WHITE, self.rect)
def move_up(self):
# paddle 위로 이동
self.y -= PADDLE_SPEED
self.rect.y = self.y
def move_down(self):
# paddle 아래로 이동
self.y += PADDLE_SPEED
self.rect.y = self.y
class Ball:
def __init__(self, x, y):
# 초기 위치 및 크기 설정
self.x = x
self.y = y
self.width = 10
self.height = 10
self.rect = pygame.Rect(self.x, self.y, self.width, self.height)
self.speed_x = BALL_SPEED_X
self.speed_y = BALL_SPEED_Y
def draw(self):
# 공 그리기
pygame.draw.rect(WINDOW, WHITE, self.rect)
def move(self):
# 공 이동
self.x += self.speed_x
self.y += self.speed_y
self.rect.x = self.x
self.rect.y = self.y
def bounce(self):
# 공 튕기기
if self.y <= 0 or self.y >= WINDOW_HEIGHT - self.height:
self.speed_y *= -1
if self.x <= 0 or self.x >= WINDOW_WIDTH - self.width:
self.speed_x *= -1
3. DQNAgent 클래스 정의
DQN (Deep Q-Network) 에이전트를 정의합니다. DQN은 강화학습 알고리즘 중 하나로, Q-learning과 신경망을 결합하여 학습합니다. DQNAgent 클래스는 모델의 구성, 메모리 저장, 행동 선택, 학습 등의 기능을 가지고 있습니다.
class DQNAgent:
def __init__(self):
self.memory = deque(maxlen=MEMORY_SIZE)
self.epsilon = EPSILON
self.model = self.build_model()
def build_model(self):
# 신경망 모델 구성
model = Sequential()
model.add(Dense(24, input_shape=(4,), activation='relu'))
model.add(Dense(24, activation='relu'))
model.add(Dense(2, activation='linear'))
optimizer = Adam(learning_rate=LEARNING_RATE)
model.compile(loss='mse', optimizer=optimizer)
return model
def remember(self, state, action, reward, next_state, done):
# 메모리에 경험 저장
self.memory.append((state, action, reward, next_state, done))
def act(self, state):
# 행동 선택
if np.random.rand() <= self.epsilon:
return random.randrange(2)
q_values = self.model.predict(state)
return np.argmax(q_values[0])
def replay(self):
# 모델 학습
if len(self.memory) < BATCH_SIZE:
return
minibatch = random.sample(self.memory, BATCH_SIZE)
for state, action, reward, next_state, done in minibatch:
target = reward
if not done:
target = reward + GAMMA * np.amax(self.model.predict(next_state)[0])
target_f = self.model.predict(state)
target_f[0][action] = target
self.model.fit(state, target_f, epochs=1, verbose=0)
if self.epsilon > EPSILON_MIN:
self.epsilon *= EPSILON_DECAY
4. 게임 실행 및 학습
Paddle과 Ball 인스턴스를 생성하고, DQNAgent를 초기화합니다. 그 후, 게임 루프를 실행하여 게임을 플레이하고 DQN을 학습합니다. 또한, 게임 실행 과정에서 키 입력을 받아 paddle을 움직이고, 공의 움직임과 튕김을 실행합니다.
import pygame
import random
import numpy as np
from collections import deque
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense
from tensorflow.keras.optimizers import Adam
# 게임 설정
WINDOW_WIDTH = 480
WINDOW_HEIGHT = 360
PADDLE_SPEED = 5
BALL_SPEED_X = 3
BALL_SPEED_Y = 3
# DQN 설정
MEMORY_SIZE = 2000
BATCH_SIZE = 32
GAMMA = 0.95
EPSILON = 1.0
EPSILON_MIN = 0.01
EPSILON_DECAY = 0.995
LEARNING_RATE = 0.001
# 색상
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
# 게임 초기화
pygame.init()
WINDOW = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT))
pygame.display.set_caption('DQN Pong')
clock = pygame.time.Clock()
class Paddle:
def __init__(self, x):
self.x = x
self.y = WINDOW_HEIGHT // 2 - 25
self.width = 10
self.height = 50
self.rect = pygame.Rect(self.x, self.y, self.width, self.height)
def draw(self):
# 패들 그리기
pygame.draw.rect(WINDOW, WHITE, self.rect)
def move_up(self):
# 패들 위로 이동
if self.y > 0:
self.y -= PADDLE_SPEED
self.rect.y = self.y
def move_down(self):
# 패들 아래로 이동
if self.y < WINDOW_HEIGHT - self.height:
self.y += PADDLE_SPEED
self.rect.y = self.y
class Ball:
def __init__(self):
self.x = WINDOW_WIDTH // 2
self.y = WINDOW_HEIGHT // 2
self.width = 10
self.height = 10
self.rect = pygame.Rect(self.x, self.y, self.width, self.height)
self.speed_x = BALL_SPEED_X
self.speed_y = BALL_SPEED_Y
def draw(self):
# 공 그리기
pygame.draw.rect(WINDOW, WHITE, self.rect)
def move(self):
# 공 이동
self.x += self.speed_x
self.y += self.speed_y
self.rect.x = self.x
self.rect.y = self.y
def bounce(self):
# 공 튕기기
if self.y <= 0 or self.y >= WINDOW_HEIGHT - self.height:
self.speed_y *= -1
if self.x <= 0 or self.x >= WINDOW_WIDTH - self.width:
self.speed_x *= -1
class DQNAgent:
def __init__(self):
self.memory = deque(maxlen=MEMORY_SIZE)
self.epsilon = EPSILON
self.model = self.build_model()
def build_model(self):
# 신경망 모델 구성
model = Sequential()
model.add(Dense(24, input_shape=(4,), activation='relu'))
model.add(Dense(24, activation='relu'))
model.add(Dense(2, activation='linear'))
optimizer = Adam(learning_rate=LEARNING_RATE)
model.compile(loss='mse', optimizer=optimizer)
return model
def remember(self, state, action, reward, next_state, done):
# 메모리에 경험 저장
self.memory.append((state, action, reward, next_state, done))
def act(self, state):
# 행동 선택
if np.random.rand() <= self.epsilon:
return random.randrange(2)
q_values = self.model.predict(state)
return np.argmax(q_values[0])
def replay(self):
if len(self.memory) < BATCH_SIZE:
return
minibatch = random.sample(self.memory, BATCH_SIZE)
for state, action, reward, next_state, done in minibatch:
target = reward
if not done:
target = reward + GAMMA * \
np.amax(self.model.predict(next_state)[0])
target_f = self.model.predict(state)
target_f[0][action] = target
self.model.fit(state, target_f, epochs=1, verbose=0)
if self.epsilon > EPSILON_MIN:
self.epsilon *= EPSILON_DECAY
# Paddle과 Ball 인스턴스 생성
player1_paddle = Paddle(20, WINDOW_HEIGHT/2 - 50) # 왼쪽 paddle
player2_paddle = Paddle(WINDOW_WIDTH - 30, WINDOW_HEIGHT/2 - 50) # 오른쪽 paddle
ball = Ball(WINDOW_WIDTH/2 - 5, WINDOW_HEIGHT/2 - 5) # 공
# DQN 에이전트 생성
agent = DQNAgent()
# 게임 루프
for episode in range(EPISODES):
state = np.array([player1_paddle.y, player2_paddle.y, ball.y, ball.x])
state = np.reshape(state, [1, 4])
done = False
score = 0
while not done:
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
keys = pygame.key.get_pressed()
if keys[K_w] and player1_paddle.y > 0:
player1_paddle.move_up()
if keys[K_s] and player1_paddle.y < WINDOW_HEIGHT - player1_paddle.height:
player1_paddle.move_down()
if keys[K_UP] and player2_paddle.y > 0:
player2_paddle.move_up()
if keys[K_DOWN] and player2_paddle.y < WINDOW_HEIGHT - player2_paddle.height:
player2_paddle.move_down()
ball.bounce()
ball.move()
if ball.rect.colliderect(player1_paddle.rect):
ball.speed_x *= -1
if ball.rect.colliderect(player2_paddle.rect):
ball.speed_x *= -1
if ball.x <= ball.width:
done = True
score -= 1
if ball.x >= WINDOW_WIDTH - ball.width:
done = True
score += 1
next_state = np.array([player1_paddle.y, player2_paddle.y, ball.y, ball.x])
next_state = np.reshape(next_state, [1, 4])
agent.remember(state, 0, score, next_state, done)
state = next_state
agent.replay()
WINDOW.fill(BLACK)
player1_paddle.draw()
player2_paddle.draw()
ball.draw()
pygame.display.update()
scores.append(score)
print("Episode:", episode, "Score:", score, "Epsilon:", agent.epsilon)
pygame.quit()
이 코드는 Pygame을 사용하여 Ping Pong 게임을 구현하고, DQN (Deep Q-Network) 알고리즘을 사용하여 게임을 학습합니다. 게임 루프를 실행하면서 Paddle을 움직이고, Ball이 Paddle에 닿으면 보상을 얻고, Ball이 벽에 닿으면 게임이 종료됩니다. 각 에피소드마다 Score와 Epsilon 값을 출력합니다. 학습이 완료되면 Pygame 창이 닫힙니다.
이 코드는 게임 화면의 크기, 속도, DQN 설정 등을 수정할 수 있습니다. 또한, DQNAgent 클래스의 신경망 구조를 변경하여 더 복잡한 모델을 사용할 수도 있습니다.
실행 결과
되고 있는건지 모르겠다. 안움직이면 안되는 것인데; 내가 강화학습을 싫어하는 이유..
이번 글에선 야심차게 DQNAgent를 만들고 실패해보았고, 다음 글에서 다시 시도해보려고 합니다.