1. 문제 설정 – 랜덤 벽이 있는 4×4 GridWorld
이번에는 랜덤하게 벽이 생기는 GridWorld에서 TD Learning으로 상태가치 함수 (V(s))를 학습해 보려고 합니다.
환경 조건
- 격자 크기: 4×4
- 시작 위치: (0, 0)
- 목표 위치: (3, 3)
- 행동:
0: 왼쪽1: 위2: 오른쪽3: 아래
- 보상:
- 일반 이동: −1
- 목표 도착: 에피소드 종료 (보상은 0으로 두어도 되고, 필요에 따라 조정 가능)
랜덤 벽 규칙
- 매 에피소드 시작마다 벽을 랜덤 생성합니다.
- 벽 개수: 2개 또는 3개
- 시작 위치
(0,0)에는 벽이 생기면 안 됨 - 목표 위치
(3,3)에는 벽이 생기면 안 됨 - 같은 위치에 벽이 중복 생성되면 안 됨
- 이동 규칙:
- 이동하려는 위치가 빈 칸이면 이동
- 격자를 벗어나면 원래 위치 유지
- 이동하려는 칸에 벽이 있으면 제자리 유지
즉, 에이전트 입장에서는 매 에피소드마다 지도가 조금씩 바뀌는 GridWorld를 만나는 셈이고,
우리는 이런 환경에서 랜덤 정책으로 움직이면서 TD Learning으로 (V(s))를 추정합니다.
2. 기본 4×4 GridWorld와 비교하기
상대적인 차이를 보기 위해 두 가지 환경을 준비했습니다.
- 기본 GridWorld – 벽이 전혀 없는 4×4
- 랜덤 벽 GridWorld – 위에서 설명한 랜덤 벽 규칙 적용
두 환경 모두:
- 시작:
(0,0) - 목표:
(3,3) - 정책은 완전 랜덤 (각 방향 25% 확률)
- TD(0) 업데이트:
\[
V(s) \leftarrow V(s) + \alpha \bigl( r + \gamma V(s') - V(s) \bigr)
\]
을 사용합니다.
3. 파이썬 코드 – 환경과 TD Learning 구현
3.1 기본 4×4 GridWorld
N = 4
START = (0, 0)
GOAL = (3, 3)
ACTIONS = [0, 1, 2, 3] # 0: left, 1: up, 2: right, 3: down
class BasicGridWorld:
def init(self):
self.reset()
def reset(self):
self.agent_pos = START
return self.agent_pos
def step(self, action):
x, y = self.agent_pos
if action == 0: # left
y = max(0, y - 1)
elif action == 1: # up
x = max(0, x - 1)
elif action == 2: # right
y = min(N - 1, y + 1)
elif action == 3: # down
x = min(N - 1, x + 1)
self.agent_pos = (x, y)
if self.agent_pos == GOAL:
return self.agent_pos, 0.0, True
else:
return self.agent_pos, -1.0, False
3.2 랜덤 벽 GridWorld
class RandomWallGridWorld:
def __init__(self):
self.walls = set()
self.reset()
def _generate_walls(self):
self.walls.clear()
num_walls = random.choice([2, 3])
candidates = [(i, j) for i in range(N) for j in range(N)
if (i, j) not in (START, GOAL)]
random.shuffle(candidates)
for pos in candidates:
if len(self.walls) >= num_walls:
break
self.walls.add(pos)
def reset(self):
self._generate_walls()
self.agent_pos = START
return self.agent_pos
def step(self, action):
x, y = self.agent_pos
nx, ny = x, y
if action == 0:
ny = max(0, y - 1)
elif action == 1:
nx = max(0, x - 1)
elif action == 2:
ny = min(N - 1, y + 1)
elif action == 3:
nx = min(N - 1, x + 1)
if (nx, ny) in self.walls:
nx, ny = x, y
self.agent_pos = (nx, ny)
if self.agent_pos == GOAL:
return self.agent_pos, 0.0, True
else:
return self.agent_pos, -1.0, False
3.3 TD(0) 학습 루프 (랜덤 정책)
from collections import defaultdict
GAMMA = 0.9
ALPHA = 0.1
EPISODES = 1000
def td_learn(env, episodes=EPISODES, alpha=ALPHA, gamma=GAMMA,
max_steps=100, name=""):
V = defaultdict(float)
for ep in range(episodes):
s = env.reset()
done = False
for t in range(max_steps):
if done:
break
a = random.choice(ACTIONS)
s_prime, r, done = env.step(a)
old_v = V[s]
target = r + (gamma * V[s_prime] if not done else r)
V[s] = old_v + alpha * (target - old_v)
s = s_prime
if (ep + 1) % 100 == 0 and name:
print(f"[{name}] episode {ep+1}/{episodes}")
return V
def print_value_table(V, title):
print(title)
for i in range(N):
row = []
for j in range(N):
v = V[(i, j)]
row.append(f"{v:6.2f}")
print(" ".join(row))
print()
if __name__ == "__main__":
random.seed(2026)
# 기본 GridWorld 학습
basic_env = BasicGridWorld()
V_basic = td_learn(basic_env, name="basic")
print_value_table(V_basic, "[기본 4x4 GridWorld 상태가치 V(s)]")
# 랜덤 벽 GridWorld 학습
wall_env = RandomWallGridWorld()
V_wall = td_learn(wall_env, name="wall")
print_value_table(V_wall, "[랜덤 벽 GridWorld 상태가치 V(s)]")
4. TD Learning 결과 – 일반 그리드 vs 랜덤 벽 그리드
4.1 기본 4×4 GridWorld
코드를 돌려보면 다음과 같은 상태가치 테이블이 나왔습니다.
[기본 4x4 GridWorld 상태가치 V(s)]
-9.33 -9.12 -8.79 -8.46
-9.20 -8.91 -8.56 -7.35
-8.81 -8.38 -7.10 -4.28
-8.62 -7.64 -4.59 0.00
- (3,3)은 목표 상태이므로 V(s) = 0
- 위쪽/왼쪽으로 갈수록 값이 점점 더 큰 음수
→ 랜덤 정책으로 움직였을 때, 목표까지 평균적으로 더 많은 -1 보상을 받는 위치 - 대체로 “목표까지의 거리(스텝 수)”가 그대로 V(s)에 반영된 모습입니다.
4.2 랜덤 벽 GridWorld
[랜덤 벽 GridWorld 상태가치 V(s)]
-9.51 -9.41 -9.09 -8.81
-9.39 -9.21 -8.59 -7.40
-9.19 -9.01 -7.34 -4.66
-9.07 -8.55 -5.23 0.00
여기서는 값들이 전반적으로 기본 환경보다 더 나쁘게(더 큰 음수) 나옵니다.
예를 들어:
- 시작점 (0,0)
- 기본: -9.33
- 벽: -9.51
- (2,2)
- 기본: -7.10
- 벽: -7.34
같은 위치라도 랜덤 벽 환경에서 V(s)가 더 작게(손해 보는 방향으로) 추정되고 있습니다.
4.3 해석
- 기본 4×4 GridWorld에서는
“목표까지의 거리”만 고려하면 되기 때문에,
목표에 가까워질수록 V(s)가 천천히 0에 가까워지는 전형적인 패턴이 나옵니다. - 랜덤 벽 GridWorld에서는 매 에피소드마다 벽이 바뀌면서
- 어떤 에피소드에서는 길이 열려 있고
- 어떤 에피소드에서는 막혀 있어서 빙빙 돌다가 끝나는 경우가 생깁니다.
그 결과:
같은 위치라도,
“자주 막히는 길 / 돌아가야 하는 길” 근처의 상태는
기본 그리드보다 V(s)가 더 나쁜 상태로 평가됩니다.
즉, TD Learning은 단순한 랜덤 정책만으로도
“이 위치는 자주 길이 막히는 위험한 상태인지, 아니면 비교적 곧장 목표로 갈 수 있는 안전한 상태인지” 를
상태가치 함수 V(s)에 자연스럽게 반영해 줍니다.
더보기
[ 오늘의 정리 ]
- 일반 GridWorld와 랜덤 벽 GridWorld에서 랜덤 정책으로 움직이면서 TD(0)로 상태가치 (V(s))를 학습해 보았습니다.
- 벽이 없는 환경에서는 목표까지의 거리(스텝 수) 가 가치에 거의 직접적으로 반영되는 반면,
랜덤 벽 환경에서는 “막힐 가능성”까지 함께 반영되면서 값이 바뀝니다. - 즉, TD Learning은 단순한 예제에서도 “길이 자주 막히는 불리한 위치 vs 상대적으로 안전한 위치”를
상태가치 함수 (V(s))로 자연스럽게 구분해 준다는 점을 확인할 수 있습니다.
'개발 기록실 > 실험 & 구현' 카테고리의 다른 글
| 데이콘 구조물 안정성 대회 도전기 – 전처리부터 Pseudo Labeling까지 (0) | 2026.03.30 |
|---|---|
| A3C & A2C – CartPole 구현 코드 뜯어보기 (PyTorch + 멀티프로세싱) (0) | 2026.03.17 |
| REINFORCE로 CartPole-v1 학습하기 – 정책 기반 에이전트 실습 (0) | 2026.03.13 |
| [YOLOv8 + RNN] 편의점/매장 이상행동(전도·파손) 탐지 파이프라인 만들기 (0) | 2026.03.09 |
| [OpenCV + Machine Learning] Kaggle 주조 제품 불량 이미지를 이용한 Random Forest 분류기 만들기 (0) | 2026.03.09 |