off-policy 정책 경사
off-policy 정책 경사
정책 경사 방법의 발전
지금까지 다룬 정책 경사 계열은 다음과 같이 발전해 왔습니다.
- 정책 경사(policy gradient): 정책 π를 직접 학습합니다.
- Actor-Critic(A2C 등): 정책과 함께 가치도 학습해서, 학습한 가치를 정책 학습에 활용합니다.
- PPO: 정책을 업데이트할 때 이전 정책에서 너무 많이 바뀌지 않도록 변화 폭을 일정 범위 안으로 제한합니다.
이 방법들은 모두 on-policy 학습입니다. on-policy는 내 정책으로 얻은 경험으로 내 정책을 학습하는 것이고, off-policy는 다른 정책으로 얻은 경험으로도 내 정책을 학습할 수 있는 것입니다. off-policy로 학습하면 리플레이 버퍼에 쌓아 둔 과거 경험을 재사용할 수 있습니다. 이 문서에서는 정책 경사를 off-policy로 학습하는 방법을 다룹니다.
Pendulum 환경
Gymnasium의 Pendulum은 한쪽 끝이 모터에 고정된 진자를 거꾸로 세우는 환경입니다. 모터에 적당한 토크를 걸어 진자를 똑바로 세워야 합니다. 행동은 토크의 크기이므로 연속적인 행동 공간(Box)입니다. 따라서 이산적인 행동만 다루는 DQN으로는 풀 수 없습니다.
보상은 진자의 각도로 정해집니다. 수직으로 서 있으면 보상이 가장 크고(0), 아래로 처지거나 옆으로 기울수록 기운 정도만큼 음의 보상을 받습니다. 에피소드 길이는 200단계로 고정되어 있어서, 빨리 세운다고 일찍 끝나지 않고 정해진 시간 동안 계속 서 있어야 합니다.
import gymnasium as gym
env = gym.make("Pendulum-v1", render_mode="rgb_array")
DDPG
**DDPG(Deep Deterministic Policy Gradient)**는 정책 경사에 DQN의 아이디어를 합쳐 off-policy로 학습할 수 있게 만든 방법입니다. 그 대가로 연속적인 행동 공간에서만 쓸 수 있습니다. Stable-Baselines3 문서에서도 DDPG는 관찰 공간은 무엇이든 되지만 행동 공간은 Box인 경우만 지원합니다. PPO가 딕셔너리를 제외한 대부분의 행동 공간을 지원하는 것에 비하면 용도가 제한적입니다.
| 방법 | 이산 행동 | 연속 행동 |
|---|---|---|
| DQN | O | X |
| DDPG | X | O |
DQN과의 차이
Q 학습의 목표값은 다음과 같습니다.
r+γa′maxQ(s′,a′)다음 상태에서 가장 좋은 행동을 할 것이라고 가정하는 것인데, max를 취하기 때문에 Q를 실제보다 크게 추정하는 **최대화 편향(maximization bias)**이 생깁니다. 이를 줄이기 위해 Q를 두 개 두고, 한쪽 Q의 argmax로 행동을 고른 다음 다른 쪽 Q로 그 행동의 가치를 평가합니다(이중 Q 학습). 한쪽 Q가 과대 추정하더라도 다른 쪽도 똑같이 과대 추정하기는 어렵기 때문입니다. 이 Q 학습을 딥러닝으로 구현한 것이 DQN입니다.
DDPG는 연속적인 행동 공간에서 max를 계산할 수 없으므로, 대신 결정론적 정책 μ(s)가 다음 행동을 바로 출력합니다.
r+γQ(s′,μ(s′))Q가 가장 큰 행동을 찾는 대신 μ가 행동을 직접 고르고, 이 μ를 정책 경사로 학습합니다.
탐색을 위한 노이즈
μ는 결정론적이므로 그대로 쓰면 탐색을 하지 않습니다. 그래서 행동에 노이즈를 더합니다. 행동이 연속적이므로 정규분포를 따르는 노이즈를 더하면 같은 상태에서도 행동이 조금씩 달라집니다.
아래 코드에서는 평균 0, 표준편차 0.1인 NormalActionNoise를 씁니다. 정규분포에서는 대부분의 값이 ±3 표준편차 안에 들어오므로, 정책이 토크 1.5를 출력하면 실제 행동은 대략 1.2~1.8 사이가 됩니다. 노이즈를 정하는 것 말고는 다른 알고리즘과 사용법이 같습니다.
import numpy as np
from stable_baselines3 import DDPG
from stable_baselines3.common.noise import NormalActionNoise, OrnsteinUhlenbeckActionNoise
# 탐색을 위한 노이즈
n_actions = env.action_space.shape[-1]
action_noise = NormalActionNoise(mean=np.zeros(n_actions), sigma=0.1 * np.ones(n_actions))
# 학습
model = DDPG("MlpPolicy", env, action_noise=action_noise, verbose=1)
model.learn(total_timesteps=10000, log_interval=10, progress_bar=True)
10,000단계 정도 학습하면 에피소드 보상이 −1000 근처에서 −700, −600 정도로 줄어듭니다. 보상의 절댓값이 줄어든다는 것은 학습이 잘 되고 있다는 뜻입니다.
CartPole처럼 실패하면 일찍 끝나는 환경과 달리 Pendulum은 에피소드마다 무조건 200단계를 진행해야 하므로, 궤적을 만드는 데 시간이 더 걸려 학습이 다소 느리게 느껴집니다.
학습한 정책을 눈으로 확인하려면 선박 제어 실습에서 만든 render_episode 함수를 그대로 쓰면 됩니다. 화면의 화살표는 진자에 걸린 토크를 나타냅니다.
TD3
**TD3(Twin Delayed DDPG)**는 DDPG를 개선한 방법입니다. 이름의 D가 세 개라서 TD3입니다. Stable-Baselines3에서는 DDPG가 따로 구현되어 있지 않고, TD3에서 몇 가지 파라미터를 고정한 것이 DDPG입니다. 따라서 굳이 DDPG를 쓸 이유는 없고 TD3를 쓰면 됩니다.
TD3는 이름 그대로 두 가지 아이디어를 DDPG에 더합니다. 둘 다 앞에서 이미 다룬 아이디어입니다.
Twin: 두 Q 중 작은 값
이중 Q 학습처럼 Q를 두 개 둡니다. 이중 Q 학습은 "한쪽에서 과대 추정해도 다른 쪽까지 과대 추정하지는 않을 것"이라고 가정했지만, 실제로는 두 쪽 모두 과대 추정할 수도 있습니다. 그래서 TD3는 두 Q로 각각 계산해 보고 더 작은 값을 목표값으로 씁니다. Q의 과대 추정을 최대한 억제하려는 것입니다.
r+γi=1,2minQi(s′,a~′)목표 정책 평활화
DDPG는 실제로 하는 행동에만 노이즈를 더하지만, TD3는 목표값을 계산할 때 쓰는 다음 행동 a~′에도 노이즈를 더합니다. 노이즈를 너무 많이 섞으면 이상한 행동이 나올 수 있으므로 노이즈의 범위를 잘라서(clip) 제한합니다.
Delayed: 지연된 정책 업데이트
DQN에서는 목표가 계속 바뀌면 학습이 불안정하므로 목표 Q를 고정해 두고 온라인 Q만 업데이트하다가 가끔 덮어썼습니다. TD3도 비슷한 아이디어를 씁니다.
DDPG도 결국 Actor-Critic입니다. 크리틱을 바꾼다고 정책이 바로 불안정해지지는 않으므로, 크리틱은 매 단계 업데이트합니다. 크리틱이 충분히 학습되어 가치 추정이 정확해지면 그때 액터를 업데이트합니다. 예를 들어 지연이 2라면 크리틱을 두 번 업데이트할 때마다 액터를 한 번 업데이트합니다.
폴리악 평균
목표망을 업데이트할 때는 새 값으로 한꺼번에 바꾸지 않고 **폴리악 평균(Polyak averaging)**으로 섞습니다. 예를 들어 τ=0.1이면 기존 값과 새 값을 9:1로 섞습니다.
학습 과정 요약
Stable-Baselines3의 TD3 train 함수는 다음 순서로 학습합니다.
- 리플레이 버퍼에서 데이터를 샘플링합니다.
- 목표 액터로 다음 행동을 계산하고, 노이즈를 더한 뒤 범위를 제한합니다.
- 두 목표 크리틱으로 Q를 계산해 더 작은 값을 취하고, TD 목표값 r+γQ′를 계산합니다.
- 크리틱의 예측값과 목표값의 차이를 평균제곱오차(MSE)로 계산해 크리틱을 최적화합니다.
- 정해진 주기마다(
단계 수 % 지연 == 0일 때) 액터 손실을 계산해 액터를 최적화합니다. - 폴리악 평균으로 목표 액터와 목표 크리틱을 업데이트합니다.
전체적으로는 복잡해 보이지만, 하나하나는 이미 다룬 개념들입니다. 이런 개념에 익숙해지면 새로운 알고리즘이 나와도 이름과 구조만 보고 어떤 특성이 있는지 짐작할 수 있습니다. 실제로 새로운 알고리즘들은 기존 아이디어(이중 학습, 지연 업데이트, 비동기 학습, 업데이트 폭 제한 등)를 조합해서 만들어지는 경우가 많습니다.
실습
DDPG 코드에서 DDPG를 TD3로 바꾸기만 하면 됩니다. DDPG는 TD3에서 twin과 delay를 뺀 것이므로, TD3도 Pendulum을 잘 학습합니다.
import numpy as np
from stable_baselines3 import TD3
from stable_baselines3.common.noise import NormalActionNoise, OrnsteinUhlenbeckActionNoise
# 탐색을 위한 노이즈
n_actions = env.action_space.shape[-1]
action_noise = NormalActionNoise(mean=np.zeros(n_actions), sigma=0.1 * np.ones(n_actions))
# 학습
model = TD3("MlpPolicy", env, action_noise=action_noise, verbose=1)
model.learn(total_timesteps=10000, log_interval=10, progress_bar=True)
SAC
**SAC(Soft Actor-Critic)**도 DDPG의 후속 알고리즘입니다. TD3와는 다른 방향으로 DDPG를 개선했습니다. SAC까지 다루면 Stable-Baselines3에 들어 있는 주요 알고리즘을 모두 살펴본 셈입니다.
확률적 정책과 엔트로피
DDPG와 TD3는 결정론적 정책에 외부에서 노이즈를 섞어 탐색합니다. SAC는 노이즈를 따로 섞지 않고 정책 자체를 확률적으로 만듭니다. 그리고 A2C처럼 목적 함수에 엔트로피를 넣어, 여러 행동을 다양하게 시도하는 것을 선호하게 합니다. 엔트로피가 높다는 것은 어질러진 방처럼 무질서한 상태를 뜻합니다.
이전 알고리즘에서는 엔트로피의 가중치가 고정이었지만, SAC는 엔트로피를 얼마나 반영할지(엔트로피 계수)도 학습합니다.
SAC도 Q를 두 개 두고 더 작은 값을 취해 과대 추정을 억제합니다. 다만 TD3의 목표 정책 평활화(다음 행동에 노이즈를 더하는 것)는 들어 있지 않습니다.
gSDE
Stable-Baselines3의 SAC에는 **gSDE(generalized State-Dependent Exploration)**라는 탐색 방법을 쓸 수 있습니다.
정규분포 노이즈는 매 단계 독립적으로 뽑히므로, 로봇에 적용하면 팔이 덜덜 떨리는 현상이 생깁니다. 팔을 한쪽으로 돌리려고 하는데 노이즈 때문에 갔다가 돌아왔다가를 반복하는 것입니다. 이런 떨림은 탐색에 별 의미가 없습니다. 한 방향으로 움직이고 있다면 그 방향으로 더 가 보는 식으로 탐색해야 자연스럽습니다.
gSDE는 노이즈를 고정된 분포에서 뽑지 않고 상태에 따라 달라지도록 학습합니다. 그러면 같은 탐색을 하더라도 움직임이 훨씬 매끄러워집니다.
학습 과정 요약
- 리플레이 버퍼에서 데이터를 샘플링합니다(off-policy).
- gSDE를 쓰는 경우 노이즈를 리셋합니다.
- 액터에서 행동과 그 확률을 뽑습니다.
- 엔트로피 계수를 학습합니다.
- 다음 행동을 확률적으로 선택하고, 두 Q 중 작은 값에 엔트로피 항을 더해 목표값 r+γQ′를 계산합니다.
- 크리틱 손실로 크리틱을, 액터 손실로 액터를 최적화합니다.
- 일정 주기마다 폴리악 평균으로 목표망을 업데이트합니다.
실습
알고리즘 이름을 SAC로 바꾸면 됩니다. SAC는 확률적 정책을 쓰므로 노이즈를 따로 넣지 않아도 됩니다.
import gymnasium as gym
from stable_baselines3 import SAC
env = gym.make("Pendulum-v1", render_mode="rgb_array")
model = SAC("MlpPolicy", env, verbose=1)
model.learn(total_timesteps=10000, log_interval=4)
정리
| 알고리즘 | 정책 | 탐색 | 과대 추정 억제 | 특징 |
|---|---|---|---|---|
| DDPG | 결정론적 | 행동에 외부 노이즈 | – | 정책 경사 + DQN, off-policy |
| TD3 | 결정론적 | 행동에 외부 노이즈 | 두 Q 중 최솟값 | 목표 정책 평활화, 지연된 액터 업데이트 |
| SAC | 확률적 | 엔트로피, gSDE | 두 Q 중 최솟값 | 엔트로피 계수 자동 학습 |
세 알고리즘 모두 연속적인 행동 공간에서만 쓸 수 있고, 리플레이 버퍼를 쓰는 off-policy 방법입니다.