실제 게임에 강화학습을 적용하면서 배운 내용 정리
1. 학습 vs 실제 게임 실행 — 왜 학습이 빠른가?
핵심: 학습은 실제 게임을 돌리지 않는다. 완전히 별개의 방식이다.
비교표
| 구분 | 학습 (train.py) | 실제 게임 (rl_bridge.py) |
| 환경 | Python game_env.py (수학 시뮬레이션) | 브라우저 Phaser 3 (렌더링+물리) |
| 1스텝 소요 | ~0.001초 | ~0.5초 (decision_interval) |
| 그래픽 | 없음 | 스프라이트, 투사체, 애니메이션 |
| 전투 | total_dps × dt 단순 곱셈 | 개별 유닛이 타겟 탐색→투사체 발사→충돌 판정 |
| 적 이동 | HP 숫자만 존재 | 원형 궤도 물리 이동 |
| 병렬 | 8개 환경 동시 실행 | 1개 브라우저 |
학습 환경의 전투 로직
# game_env.py - _apply_combat()
damage_pool = total_dps * dt # 이게 전투의 전부. 그래픽 없음
for enemy in self.enemies:
if enemy['hp'] <= damage_pool:
killed.append(enemy)
실제 게임에서는 유닛마다 사거리 체크 → 타겟 탐색 → 투사체 생성 → 충돌 판정 → 데미지 적용을 매 프레임(60fps) 수행하지만, 학습 환경에서는 "총 DPS × 시간 = 데미지"라는 수학 한 줄로 모든 전투를 대체한다.
속도 차이
- 10M 스텝 학습: ~1시간 45분
- 같은 양을 실제 게임으로 실행 시: 수만 시간 소요 예상
이것이 가능한 이유는 학습 환경이 게임의 핵심 의사결정 구조만 추상화했기 때문이다. 그래픽, 물리, 애니메이션은 AI가 최적의 결정을 내리는 법을 배우는 데 필요하지 않다.
2. 사용 알고리즘: MaskablePPO
2-1 강화학습 알고리즘 계보
강화학습(RL)
├── Value-based (가치 기반)
│ ├── Q-Learning
│ └── DQN (Deep Q-Network)
│
├── Policy-based (정책 기반)
│ ├── REINFORCE
│ └── Policy Gradient
│
└── Actor-Critic (가치 + 정책 결합) ★ 우리가 사용한 방식
├── A2C / A3C
├── PPO (Proximal Policy Optimization)
└── MaskablePPO ← 실제 프로젝트 적용 모델
2-2 세 가지 방법론 설명
Value-based (가치 기반) — "각 행동의 가치를 매긴다"
- 상태-행동 쌍 $Q(s, a)$의 가치를 학습한다.
- 가장 높은 Q값을 가진 행동을 선택한다.
- 대표 알고리즘: DQN
Policy-based (정책 기반) — "행동 확률을 직접 학습한다"
- 정책 $\pi(a|s)$ (상태 $s$에서 행동 $a$를 할 확률)를 직접 학습한다.
- 확률 분포를 출력하므로 연속적인 행동 공간에도 적합하다.
- 대표 알고리즘: REINFORCE, Policy Gradient
Actor-Critic (결합) — "둘 다 사용한다"
- Actor (정책 네트워크): "어떤 행동을 할까?" → 행동 확률 출력
- Critic (가치 네트워크): "이 상태가 얼마나 좋은가?" → 가치 평가
- Actor가 행동하면, Critic이 "그 행동이 예상보다 좋았는지/나빴는지" 피드백을 준다.
- 대표 알고리즘: A2C, PPO, MaskablePPO
DQN vs PPO — 왜 DQN을 안 쓰는가?
DQN (Deep Q Network) 가치 기반 알고리즘 중에 괜찮다는 얘기를 듣고 찾아봤다.
간단하게 설명하자면, "신경망을 이용하여 Q-value를 근사하는 방법입니다. Atari 게임, 로봇 제어, 복잡한 환경같은 문제에서 사용됩니다."
| 구분 | DQN | PPO (현재 사용) |
| 방식 | $Q(s,a)$ 테이블로 각 행동의 가치 학습 | 정책 $\pi(a|s)$를 직접 학습 |
| 행동 공간 | 수십 개까지 적합 | 170개 이상의 이산 행동도 원활히 처리 |
| Action Masking | 구현이 매우 까다로움 | MaskablePPO로 네이티브 지원 |
| 안정성 | 불안정함 (Overestimation 문제) | 클리핑을 통해 매우 안정적으로 업데이트 |
| 샘플 효율 | 리플레이 버퍼를 사용해 효율적임 | 상대적으로 덜 효율적이나 학습이 안정적 |
이 게임에서 DQN이 부적합한 핵심 이유
170개 행동 중 대부분이 특정 시점에 '무효'하다:
- 그리드에 유닛이 없으면 → PLACE/SELL 불가
- 골드가 부족하면 → SUMMON 불가
- 재료 유닛이 없으면 → COMBINE 불가
MaskablePPO는 무효 행동을 마스킹(Action Masking)하여 AI가 유효한 행동만 고려하도록 강제한다. 이를 통해 학습 효율을 비약적으로 높일 수 있다.
PPO가 학습하는 방식 (Step-by-Step)
- 경험 수집: 8개 병렬 환경에서 각 4,096스텝씩 데이터를 모은다 (총 32,768개 경험).
- 이점(Advantage) 계산: 각 행동이 "예상(Critic의 판단)보다 얼마나 더 좋았는지" 계산한다.
- $Advantage > 0$: 예상보다 좋았던 행동
- $Advantage < 0$: 예상보다 나빴던 행동
- 정책 업데이트: "예상보다 좋았던 행동"의 확률은 올리고, 나빴던 행동의 확률은 내린다. 단, 정책이 급격히 변해 망가지는 것을 막기 위해 클리핑(clip_range=0.2)을 적용한다.
- 가치 네트워크 업데이트: Critic이 현재 상태의 가치를 더 정확하게 예측하도록 학습시킨다.
- 반복: 위 과정을 수백~수천 번 반복하며 최적의 전략을 찾아낸다.
PPO 핵심 수식
- $r_t(\theta) = \frac{\pi_\theta(a_t|s_t)}{\pi_{\theta_{old}}(a_t|s_t)}$: 새로운 정책과 이전 정책의 확률 비율
- $\hat{A}_t$: 이점 추정값 (Advantage)
- $\epsilon$: 클리핑 범위 (clip_range=0.2)
핵심 아이디어: 정책을 너무 급격히 바꾸면 학습이 산으로 갈 수 있다. 그래서 변화율 $r_t$가 $[0.8, 1.2]$ 범위를 벗어나면 강제로 잘라내어(clip) 안정성을 확보한다.
3. 하이퍼파라미터 가이드
config.py 설정값의 의미와 역할:
| 파라미터 | 값 | 역할 |
| N_STEPS | 4096 | 환경당 경험 수집량. 클수록 안정적이지만 학습 속도가 느려짐 |
| BATCH_SIZE | 2048 | 한 번에 학습하는 경험의 묶음 크기. GPU 메모리에 맞춰 조절 |
| N_EPOCHS | 10 | 수집한 데이터를 몇 번 반복해서 학습할 것인가 |
| CLIP_RANGE | 0.2 | 정책 변화의 제한폭. 작을수록 보수적으로 학습함 |
| ENT_COEF | 0.03 | 엔트로피 계수. 높으면 탐색(Exploration)을 장려, 낮으면 활용(Exploitation) 중시 |
| GAMMA | 0.998 | 할인 계수. 1에 가까울수록 먼 미래의 보상을 중요하게 여김 |
| LEARNING_RATE | 1e-4 | 학습 속도. 파인튜닝 시 낮게 설정하면 세밀한 조정 가능 |
| GAE_LAMBDA | 0.95 | Generalized Advantage Estimation. 이점 추정의 분산과 편향 조절 |
| N_ENVS | 8 | 동시에 실행할 병렬 환경 수. CPU 코어 수에 최적화 |
실제 튜닝 경험
- 엔트로피(ENT_COEF): 초기엔 다양한 시도를 위해 높게(0.05), 학습 후반부 파인튜닝 시에는 특정 전략에 집중하도록 낮게(0.03) 조정했다.
- 할인 계수(GAMMA): 이 게임은 90라운드까지 버티는 것이 목표이므로, 눈앞의 보상보다 최종 생존을 위해 0.998이라는 높은 값을 사용했다.
4. 학습 로그 읽는 법 (Tensorboard)
| rollout/ep_rew_mean | 553 | ← 평균 에피소드 보상 (높을수록 성능 우수)
| rollout/ep_len_mean | 1660 | ← 평균 에피소드 길이 (길수록 오래 생존)
| train/entropy_loss | -0.527 | ← 엔트로피 (절댓값이 작아질수록 확신 있는 정책)
| train/clip_fraction | 0.033 | ← 클리핑 발생 비율 (낮으면 안정적으로 수렴 중)
| train/explained_var | 0.997 | ← 가치 추정 정확도 (1에 가까울수록 Critic이 정확함)
| train/approx_kl | 0.0047 | ← KL 발산 (정책 변화량, 급격한 변화 여부 판단)
핵심 지표 해석 방법
- explained_variance ≈ 1.0:
- Critic이 보상을 거의 완벽하게 예측하고 있다는 신호로, 모델이 게임의 구조를 잘 이해하고 있음을 뜻한다.
- entropy_loss 점진적 감소:
- 처음엔 무작위로 행동하다가 점점 특정 상황에서 최선의 수를 찾아가고 있다는 증거다.
- approx_kl < 0.01:
- 업데이트가 너무 튀지 않고 안정적으로 진행되고 있음을 의미한다.
5. 프로젝트 구조 요약
학습 파이프라인
- game_data.py: 유닛 능력치, 조합 레시피, 보스 데이터 정의
- game_env.py: Python 기반의 경량 시뮬레이션 환경 (Gymnasium 표준)
- train.py: MaskablePPO 알고리즘으로 모델 학습 (Stable Baselines3 기반)
- models/: 학습이 완료된 가중치 파일 저장 (.zip)
실행(Inference) 파이프라인
- 모델 로드: 학습된 .zip 모델을 불러옴
- rl_bridge.py: 모델과 실제 게임 간의 인터페이스 (WebSocket 통신)
- server.js: Node.js 서버를 통한 데이터 중계 (Socket.IO)
- game.js: 브라우저에서 실행되는 실제 Phaser 3 게임 환경
https://github.com/gonida1010/MyDefenseGame
GitHub - gonida1010/MyDefenseGame: A fan-made, web-based Demon Slayer random defense game built with Phaser.js, featuring real-t
A fan-made, web-based Demon Slayer random defense game built with Phaser.js, featuring real-time multiplayer co-op, strategic unit combinations, and diverse game modes. [Phaser.js 기반의 팬메이드 '귀멸의...
github.com
'3. 자습 & 메모(실전, 실습, 프로젝트) > 3-2 메모(실전, 프로젝트)' 카테고리의 다른 글
| [MEMO] 실사용 모드 (0) | 2026.02.08 |
|---|---|
| [Memo] PaddleOCR v5 REC 파이프라인 재구축 회고 (0) | 2026.01.04 |
| [PaddleOCR] 학습용 REC 데이터셋 생성 스크립트(업데이트 버전) (0) | 2026.01.04 |
| [PaddleOCR] 학습용 REC 데이터셋 생성 스크립트 (0) | 2026.01.02 |
| [MEMO] PaddleOCR 코드 문법 인식 모델 학습 (0) | 2026.01.01 |
