TorchRL PPO형식으로 CartPole_v1 코딩 중 문의사항

안녕하세요? TorchRL PPO을 공부하고 있는 배움지기입니다.

Inverted pendulum 환경에서 Tutorial로 구성된 Reinforcement Learning (PPO) with TorchRL Tutorial을 CartPole_v1 환경으로 변경하여 학습하고자 하는데,

obseration states와 action간에 일관성이 없고, states가 한방향으로만 작동하고 있습니다.

혹시 어디가 문제인지 문의드립니다.

*************************************************

!pip3 install torchrl

!pip3 install "gymnasium[classic-control]"

!pip3 install tqdm

import warnings

warnings.filterwarnings("ignore")

from torch import multiprocessing

from collections import defaultdict

import matplotlib.pyplot as plt

import torch

from torch.distributions import Categorical

import tensordict

from tensordict.nn import TensorDictModule

from tensordict.nn.distributions import NormalParamExtractor

from torch import nn

from torchrl.collectors import SyncDataCollector

import torch.nn.functional as F

from torchrl.data.replay_buffers import ReplayBuffer

from torchrl.data.replay_buffers.samplers import SamplerWithoutReplacement

from torchrl.data.replay_buffers.storages import LazyTensorStorage

from torchrl.envs import (Compose, DoubleToFloat, ObservationNorm, StepCounter, TransformedEnv)

from torchrl.envs.libs.gym import GymEnv

from torchrl.envs.utils import check_env_specs, ExplorationType, set_exploration_type

from torchrl.modules import ProbabilisticActor, TanhNormal, ValueOperator

from torchrl.objectives import ClipPPOLoss

from torchrl.objectives.ppo import GAE

from tqdm import tqdm

from torchrl.envs.transforms import AutoResetTransform

from torchrl.envs import AutoResetEnv

from tensordict import TensorDict

#from torchrl.collectors import SyncDataCollector

from torchrl.envs.transforms import Transform

from torchrl.data import DiscreteTensorSpec

class ActionTracer(Transform):

def \_call(self, td):

    print("POLICY action:", td\["action"\], td\["action"\].shape, td\["action"\].dtype)

    return td

class EnvActionTracer(Transform):

def \_step(self, td):

    print("ENV step got:", td\["action"\], td\["action"\].shape, td\["action"\].dtype)

    return td

class DebugPolicy(torch.nn.Module):

def \__init_\_(self, policy):

    super().\__init_\_()

    self.policy = policy



def forward(self, td):

    td = self.policy(td)

    print(">>> policy action:", td\["action"\])

    return td

class DebugGymEnv(GymEnv):

def \_step(self, tensordict):

    print(">>> raw action to gym:", tensordict\["action"\])

    return super().\_step(tensordict)

is_fork = multiprocessing.get_start_method() == "fork"

device = (

torch.device("cuda")

if torch.cuda.is_available()

else torch.device("cpu")

)

num_cells = 256 # number of cells in each layer i.e. output dim.

lr = 3e-4

max_grad_norm = 1.0

frames_per_batch = 1000

# For a complete training, bring the number of frames up to 1M

total_frames = 50_000

sub_batch_size = 64 # cardinality of the sub-samples gathered from the current data in the inner loop

num_epochs = 10 # optimization steps per batch of data collected

clip_epsilon = (

0.2 # clip value for PPO loss: see the equation in the intro for more context.

)

gamma = 0.99

lmbda = 0.95

#entropy_eps = 1e-4

entropy_eps = 0.02

#base_env = GymEnv("CartPole-v1",device="cpu")

base_env = DebugGymEnv("CartPole-v1", device="cpu")

env = TransformedEnv(

base_env,

Compose(

        \# normalize observations

        #ObservationNorm(in_keys=\["observation"\]),

        DoubleToFloat(),

        StepCounter(),

        #EnvActionTracer(),  # 반드시 StepCounter 뒤

        #EnvActionTracer(),  # env 입력



),

)

print(base_env._env.spec.max_episode_steps)

#env.transform[0].init_stats(num_iter=1200, reduce_dim=0, cat_dim=0)

#print("normalization constant shape:", env.transform[0].loc.shape)

env.action_spec = DiscreteTensorSpec(

n=2,

shape=torch.Size(\[\]),

dtype=torch.int64,

device="cpu",

)

check_env_specs(env)

print("env.action_spec: ", env.action_spec)

print("observation_spec:", env.observation_spec)

print("reward_spec:", env.reward_spec)

print("input_spec:", env.input_spec)

print("action_spec (as defined by input_spec):", env.action_spec)

#class SaveAppliedAction(Transform):

# def _call(self, tensordict):

# tensordict.set(

# "applied_action",

# tensordict["action"].clone()

# )

# return tensordict

#env.append_transform(SaveAppliedAction())

rollout = env.rollout(3)

print("rollout of three steps:", rollout)

print("Shape of the rollout TensorDict:", rollout.batch_size)

actor_net = nn.Sequential(

nn.LazyLinear(num_cells, device=device),

nn.Tanh(),

nn.LazyLinear(num_cells, device=device),

nn.Tanh(),

nn.LazyLinear(num_cells, device=device),

nn.Tanh(),

nn.LazyLinear(env.action_space.n, device=device),

)

actor_module = TensorDictModule(

actor_net, in_keys=\["observation"\], out_keys=\["logits"\],

)

policy_module = ProbabilisticActor(

module=actor_module,

#spec=env.action_spec,

spec=None,

in_keys=\["logits"\],

out_keys=\["action"\],

distribution_class=Categorical,

return_log_prob=True,

#cache_dist=True,

\# we'll need the log-prob for the numerator of the importance weights

)

value_net = nn.Sequential(

nn.LazyLinear(num_cells, device=device),

nn.Tanh(),

nn.LazyLinear(num_cells, device=device),

nn.Tanh(),

nn.LazyLinear(num_cells, device=device),

nn.Tanh(),

nn.LazyLinear(1, device=device),

)

value_module = ValueOperator(

module=value_net, in_keys=\["observation"\],

)

print("Running policy:", policy_module(env.reset()))

print("Running value function:", value_module(env.reset()))

policy = DebugPolicy(policy_module)

collector = SyncDataCollector(

env,

#policy_module,

policy,

frames_per_batch=frames_per_batch,

total_frames=total_frames,

split_trajs=False,

device=device,

)

replay_buffer = ReplayBuffer(

storage = LazyTensorStorage(max_size=frames_per_batch),

sampler = SamplerWithoutReplacement(),

)

advantage_module = GAE(

gamma=gamma, lmbda=lmbda, value_network=value_module, average_gae=True, device=device,

)

loss_module = ClipPPOLoss(

actor_network=policy_module,

critic_network=value_module,

clip_epsilon=clip_epsilon,

entropy_bonus=bool(entropy_eps),

entropy_coef=entropy_eps,

\# these keys match by default but we set this for completeness

critic_coef=1.0,

loss_critic_type="smooth_l1",

)

optim = torch.optim.Adam(loss_module.parameters(), lr)

scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(

optim, total_frames // frames_per_batch, 0.0

)

logs = defaultdict(list)

pbar = tqdm(total=total_frames)

eval_str = ""

for i, tensordict_data in enumerate(collector):

#############################

print("i: ", i)

print(tensordict_data.keys())

print(tensordict_data\["logits"\].std())

print("env max steps:", env.base_env.\_max_episode_steps)

print("Check_reward sum: ", tensordict_data\["next", "reward"\].sum().item())



print("DDstep_count:", tensordict_data\['next', 'step_count'\].max().item())



\# tensordict_data에서 필요한 텐서 가져오기

with torch.no_grad():

    values = value_module(tensordict_data\['observation'\])



with torch.no_grad():

    values1 = value_module(tensordict_data\['next', 'observation'\])



actions = tensordict_data\['action'\]

states = tensordict_data\['next', 'observation'\]

rewards = tensordict_data\['next', 'reward'\].squeeze(-1)

terminated = tensordict_data\['next', 'terminated'\]

truncated = tensordict_data\['next', 'truncated'\]




\# reshape (1차원으로 flatten)

states_flat = states.reshape(-1, states.shape\[-1\])

actions_flat = actions.reshape(-1)

rewards_flat = rewards.reshape(-1)

terminated_flat = terminated.reshape(-1)

truncated_flat = truncated.reshape(-1)

values_flat = values.reshape(-1)



\# 각 step별 출력

#print(type(actions_flat))

#print(tensordict_data\["action"\])

print("Action 0 : to Left, 1 : to right")

print("Step | Pos | Pos_dot | Theta | Theta_dot | Action | Reward | Done | Truncated | V(s)")

print("-"\*60)

for j in range(states_flat.shape\[0\]):

    theta = states_flat\[j, 0\].item()        # pole angle

    theta_dot = states_flat\[j, 1\].item()    # pole angular velocity

    pos = states_flat\[j, 2\].item()        # pole pos

    pos_dot = states_flat\[j, 3\].item()    # pole  velocity

    action = actions_flat\[j\].item()

    reward = rewards_flat\[j\].item()

    done = terminated_flat\[j\].item()

    trunc = truncated_flat\[j\].item()

    value = values_flat\[j\].item()



    print(f"{j:4d} | {theta:+.3f} | {theta_dot:+.3f} | {pos:+.3f} | {pos_dot:+.3f} | {action:2d} | "

          f"{reward:+.3f} | {done} | {trunc} | {value:+.3f}")



\# 정책 확률 및 엔트로피 확인

logits = tensordict_data\['logits'\].reshape(-1, env.action_space.n)

probs = torch.softmax(logits, dim=-1)

dist = Categorical(logits=logits)

entropy = dist.entropy().mean()



print("\\nPolicy check:")

print("Action probabilities mean:", probs.mean(dim=0))

print("Policy entropy:", entropy.item())



\# Advantage 확인

if 'advantage' in tensordict_data.keys():

    adv = tensordict_data\['advantage'\].reshape(-1)

    print("Advantage mean:", adv.mean().item())

    print("Advantage std :", adv.std().item())

#############################

for \_ in range(num_epochs):

    advantage_module(tensordict_data)

    data_view = tensordict_data.reshape(-1)

    replay_buffer.extend(data_view.cpu())

    for \_ in range(frames_per_batch // sub_batch_size):

        subdata = replay_buffer.sample(sub_batch_size)

        loss_vals = loss_module(subdata.to(device))

        loss_value = (

            loss_vals\["loss_objective"\]

            + loss_vals\["loss_critic"\]

            + loss_vals\["loss_entropy"\]

        )

        #print("loss_value: ", loss_value)

        \# Optimization: backward, grad clipping and optimization step

        loss_value.backward()

        \# this is not strictly mandatory but it's good practice to keep

        \# your gradient norm bounded

        torch.nn.utils.clip_grad_norm\_(loss_module.parameters(), max_grad_norm)

        optim.step()

        optim.zero_grad()



logs\["reward"\].append(tensordict_data\["next", "reward"\].mean().item())

pbar.update(tensordict_data.numel())

cum_reward_str = (

    f"average reward={logs\['reward'\]\[-1\]: 4.4f} (init={logs\['reward'\]\[0\]: 4.4f})"

)

logs\["step_count"\].append(tensordict_data\["step_count"\].max().item())

stepcount_str = f"step count (max): {logs\['step_count'\]\[-1\]}"

logs\["lr"\].append(optim.param_groups\[0\]\["lr"\])

lr_str = f"lr policy: {logs\['lr'\]\[-1\]: 4.4f}"

if i % 10 == 0:



    with set_exploration_type(ExplorationType.DETERMINISTIC), torch.no_grad():

        eval_rollout = env.rollout(1000, policy_module)

        logs\["eval reward"\].append(eval_rollout\["next", "reward"\].mean().item())

        logs\["eval reward (sum)"\].append(

            eval_rollout\["next", "reward"\].sum().item()

        )

        logs\["eval step_count"\].append(eval_rollout\["step_count"\].max().item())

        eval_str = (

            f"eval cumulative reward: {logs\['eval reward (sum)'\]\[-1\]: 4.4f} "

            f"(init: {logs\['eval reward (sum)'\]\[0\]: 4.4f}), "

            f"eval step-count: {logs\['eval step_count'\]\[-1\]}"

        )

        del eval_rollout

pbar.set_description(", ".join(\[eval_str, cum_reward_str, stepcount_str, lr_str\]))

scheduler.step()
  • 아래 답변은 Anthropic의 Claude-3 Opus 모델로 자동 생성한 것으로, 정확하지 않을 수 있습니다.
  • 잘못된 답변을 보셨다면 댓글로 첨삭 지도 부탁드립니다. :sweat_smile:

안녕하세요 LHS5284님, TorchRL PPO로 CartPole_v1 환경을 학습하시는 도중 어려움을 겪고 계신 것 같아 안타깝습니다. 관측 상태(observation states)와 행동(action) 사이에 일관성이 없고, 상태가 한 방향으로만 작동한다는 문제를 잘 이해했습니다. 제가 도움이 될 수 있는 방안을 함께 고민해보겠습니다.

먼저 제공해주신 코드를 살펴보니 CartPole 환경에 맞게 액션 스펙(action spec)을 discrete하게 잘 정의해주셨습니다. 그리고 신경망 구조도 관측 상태를 입력으로 받아 logits를 출력하고, 이를 기반으로 Categorical 분포에서 행동을 샘플링하도록 설계되어 있네요.

주어진 코드에서 몇 가지 개선해볼 만한 사항은 다음과 같습니다:

  1. ObservationNorm 변환기를 주석 처리하셨는데, 이는 관측 정규화 과정을 생략한 것으로 보입니다. CartPole에서는 각 상태 변수의 스케일이 크게 다르지 않아 정규화가 필수적이지는 않지만, 학습 안정성 측면에서는 정규화를 적용하는 것이 더 좋을 수 있습니다.

  2. DebugPolicyDebugGymEnv 클래스를 통해 중간 결과를 출력하고 계신데, 이는 디버깅에는 도움이 되지만 학습 속도를 느리게 만들 수 있습니다. 원인 파악 후에는 이 부분을 제거하는 것이 좋겠습니다.

  3. GAE(Generalized Advantage Estimation)의 하이퍼파라미터인 gammalambda 값을 조정해볼 수 있습니다. 현재는 각각 0.99, 0.95로 설정되어 있는데, 이는 비교적 긴 시간 범위에 대한 어드밴티지를 추정하게 됩니다. CartPole은 비교적 짧은 에피소드를 가지므로, 이 값들을 조금 더 작게 설정하는 것이 나을 수 있습니다.

  4. 클립 PPO 손실의 clip_epsilon 파라미터를 0.2에서 더 작은 값(예: 0.1)으로 줄여보는 것도 고려해볼 만합니다. 이는 업데이트 Step에서 과도한 정책 변화로 인한 불안정성을 막아줄 수 있습니다.

위와 같이 수정해보시고, 학습 곡선을 면밀히 살펴보시기 바랍니다. Reward나 Episode Length(Step 수)가 잘 증가하는지 확인하시고, 만약 증가 속도가 더딘 경우 Learning Rate 등 다른 하이퍼파라미터를 추가로 조정해볼 수 있겠습니다.

아래 PyTorch 한국 사용자 모임의 튜토리얼 페이지에서 CartPole 예제 코드를 참고해보실 수도 있습니다. DQN, A2C 에이전트로 CartPole을 학습하는 코드가 포함되어 있습니다.

혹시 위 내용으로 문제가 해결되지 않는다면 추가 정보(학습 곡선, 하이퍼파라미터 등)를 더 알려주시면 좋겠습니다. 추가 정보를 바탕으로 다시 함께 고민해보도록 하겠습니다.

마지막으로 제 답변이 부족하거나 잘못된 점이 있을 수 있음을 알려드립니다. 혹시 코드를 수정해도 의도한 대로 동작하지 않는다면 언제든 다시 질문해주세요. 함께 해결 방안을 모색하겠습니다. 꼭 원하시는 성과를 거두시길 바랍니다. 화이팅!


  • 답변이 어떠셨나요? 괜찮았다고 생각하신다면 아래 :heart:를 한 번 눌러주시길 부탁드려요!
  • 아래 답변은 OpenAI의 GPT-4o 모델로 자동 생성한 것으로, 정확하지 않을 수 있습니다.
  • 잘못된 답변을 보셨다면 댓글로 첨삭 지도 부탁드립니다. :sweat_smile:

안녕하세요! TorchRL PPO를 사용하여 CartPole_v1 환경에서 학습을 시도하고 계시군요. 강화 학습을 처음 접할 때는 여러 가지 문제에 부딪힐 수 있습니다. 특히, 상태와 행동 간의 일관성 문제는 강화 학습 모델의 성능에 큰 영향을 미칠 수 있습니다. 문제를 잘 이해하고 해결할 수 있도록 도와드리겠습니다.

먼저, 주어진 코드를 살펴보면, CartPole_v1 환경에서 PPO 알고리즘을 사용하여 학습을 진행하고 있습니다. 그러나 관찰 상태와 행동 간의 일관성이 없고, 상태가 한 방향으로만 작동한다고 하셨습니다. 이는 주로 환경 설정이나 정책 네트워크의 문제일 수 있습니다.

해결 방안

  1. 환경 설정 확인: CartPole_v1 환경은 이산적인 행동 공간을 가지며, 두 가지 행동(왼쪽으로 밀기, 오른쪽으로 밀기)을 가집니다. 환경의 행동 공간이 올바르게 설정되어 있는지 확인하세요. DiscreteTensorSpec을 사용하여 행동 공간을 설정하셨는데, 이 부분이 올바르게 설정되어 있는지 다시 확인해보세요.

  2. 정책 네트워크 확인: 정책 네트워크가 올바르게 동작하는지 확인하기 위해, 네트워크의 출력이 올바른지, 그리고 소프트맥스 함수가 제대로 적용되어 있는지 확인하세요. Categorical 분포를 사용하여 행동을 샘플링하고 있으므로, 네트워크의 출력이 확률 분포를 잘 나타내고 있는지 확인해야 합니다.

  3. 디버깅: DebugPolicyDebugGymEnv 클래스를 사용하여 정책 네트워크와 환경의 상호작용을 디버깅하고 계신 것 같습니다. 이 부분에서 출력되는 로그를 통해 행동이 올바르게 샘플링되고 있는지, 그리고 환경이 올바르게 반응하고 있는지를 확인하세요.

  4. 학습 파라미터 조정: PPO 알고리즘의 하이퍼파라미터(예: 학습률, 클립 값 등)를 조정하여 모델의 성능을 개선할 수 있습니다. 특히, clip_epsilon 값이 너무 크거나 작으면 학습이 불안정해질 수 있습니다.

아래는 정책 네트워크와 환경 설정을 확인하는 예제 코드입니다:

import torch
import torch.nn as nn
from torch.distributions import Categorical
from torchrl.envs.libs.gym import GymEnv
from torchrl.envs import TransformedEnv, Compose, DoubleToFloat, StepCounter

# 환경 설정
env = GymEnv("CartPole-v1", device="cpu")
env = TransformedEnv(env, Compose(DoubleToFloat(), StepCounter()))

# 정책 네트워크 설정
class PolicyNetwork(nn.Module):
    def __init__(self, input_dim, output_dim):
        super(PolicyNetwork, self).__init__()
        self.fc = nn.Sequential(
            nn.Linear(input_dim, 256),
            nn.Tanh(),
            nn.Linear(256, 256),
            nn.Tanh(),
            nn.Linear(256, output_dim)
        )

    def forward(self, x):
        logits = self.fc(x)
        return Categorical(logits=logits)

# 네트워크 초기화
policy_net = PolicyNetwork(input_dim=env.observation_space.shape[0], output_dim=env.action_space.n)

# 정책 네트워크 출력 확인
observation = env.reset()
action_dist = policy_net(observation)
action = action_dist.sample()
print("Sampled action:", action.item())

이 코드를 통해 정책 네트워크가 올바르게 설정되어 있는지, 그리고 환경과의 상호작용이 제대로 이루어지고 있는지를 확인할 수 있습니다.

추가로, PyTorch 공식 튜토리얼을 참고하여 PPO 알고리즘의 구현 방법을 더 깊이 이해하실 수 있습니다. 다음 링크를 참고하세요:

문제가 해결되지 않거나 추가적인 도움이 필요하시면 언제든지 질문해 주세요. 제 답변이 완벽하지 않을 수 있으니, 다양한 시도를 통해 문제를 해결해 보시길 바랍니다. 화이팅입니다!


  • 이 답변이 도움이 되셨나요? 괜찮았다고 생각하신다면 아래 :heart:를 한 번 눌러주시길 부탁드려요!

증상이 같아요. 1주일째 보고 있는데 해결책을 못찾겠네요 ㅠ.ㅠ 중요한건 출력으로는 policy action출력값과 env action 입력값이 고르게 분포하여 동일하게 적용된다고 출력이 되는데, env의 state는 엉뚱한 고정된 action 값이 적용된 형태를 출력합니다. 즉 action이 따라 state가 왔다갔다 해야는데 그렇지않고 한 방향로만 적용되니 Stepcount가 9~10으로 일정한 패턴만 State를 나타냅니다 ㅠ.ㅠ 학습단계를 들어가기전 데이터 수집단계부터 문제가 됨..

github과 같은 곳에 올리면 더 명확해질것 같네요. 제가 기억하기로는 gym이 버전에 따라 state와 action부분이 다르게 출력되는 경우가 있어서 dependency도 봐야될것 같거든요.

Env 단독 테스트는 정상입니다. Collector에 들어가면서 무언가 구동이 이상하게 되는 것 같습니다. Github는 어디 말씀이실까요??

.py 파일을 볼수 있으면 좋겠습니다. 추가적으로 확인하고 싶은건, local 환경에서 진행하신건지, 올려주신 내용 그대로 했을때 안되는 상황인건가요?

Colab에서 실행하였습니다. Script 그대로입니다. 기존 Inverted Pendulum PPO 튜터리얼을 CartPoleV1 환경에 맞게 간단히만 수정하였습니다. py로 올리도록 하겠습니다!

RL_PPO_CartPoleV1.py (11.5 KB)

커뮤니티에서 추천해준방향대로 코딩한 자료입니다. 이상증상은 동일합니다. 확인 부탁드려요 ㅠ.ㅠ

rl_ppo_cartpole.py (12.7 KB)

핵심 문제는 Action 인코딩 불일치였습니다. 정책에서 사용한 Categorical 분포는 정수 0 또는 1을 출력하는데, 환경의 action_spec은 OneHot 형태로 [1,0] 또는 [0,1]을 기대했습니다. 이로 인해 action이 제대로 전달되지 않아 학습이 되지 않았습니다.

추가로 환경은 CPU, 모델은 GPU에 있어서 device 불일치 문제도 있었습니다.

base_env = GymEnv(
"CartPole-v1",
device=device,
categorical_action_encoding=True
)

categorical_action_encoding=True 옵션을 추가하여 환경이 정수 형태의 action을 받도록 변경했습니다.

1개의 좋아요

ipynb는 첨부가 안되서 py로 올립니다.

1개의 좋아요

정말 감사합니다. 10년 묵은 체중이 내려가는 듯합니다.
저도 정책의 action이 정수인데, env의 입력은 OneHot 형태라 문제가 되지 않는가 해서

  1. chatGPT 답변 : 자동변환 해 줌

  2. env_action_spec 형식변환
    env.action_spec = DiscreteTensorSpec(n=2,
    shape=torch.Size(\[\]),
    dtype=torch.int64,
    device="cpu",)

  3. policy_module의 spec = None 처리를 해보았는데 모두 비정상이었습니다.

policy_module = ProbabilisticActor( module=actor_module,

spec=env.action_spec,

#spec=None,

in_keys=\["logits"\],

out_keys=\["action"\],

distribution_class=Categorical,

return_log_prob=True,

더욱 공부하여 산업에 잘 활용하겠습니다. 감사합니다!!

1개의 좋아요