haje01 노트

RLlib Models, Preprocessors, and Action Distributions

목차

RLlib의 컴포넌트들 간 데이터 흐름도
기본 동작
내장된 모델과 전처리기
TensorFlow 모델
순환(Recurrent) 모델
PyTorch 모델
커스텀 전처리기
커스텀 동작 분포(Custom Action Distributions)
지도 모델 손실(Supervised Model Losses)
TensorFlow
PyTorch
가변 길이/모수화된 동작 공간(Variable-length / Parametric Action Spaces)
자동회귀 동작 분포(Autoregressive Action Distributions)
참고
[ ray  study  ]

RLlib Models, Preprocessors, and Action Distributions

RLlib의 컴포넌트들 간 데이터 흐름도

기본 동작

내장된 모델과 전처리기

TensorFlow 모델

순환(Recurrent) 모델

PyTorch 모델

커스텀 전처리기

커스텀 동작 분포(Custom Action Distributions)

import ray
import ray.rllib.agents.ppo as ppo
from ray.rllib.models import ModelCatalog
from ray.rllib.models.preprocessors import Preprocessor

class MyActionDist(ActionDistribution):
    @staticmethod
    def required_model_output_shape(action_space, model_config):
        return 7  # 모델 출력 특성 벡터 크기를 조절

    def __init__(self, inputs, model):
        super(MyActionDist, self).__init__(inputs, model)
        assert model.num_outputs == 7

    def sample(self): ...
    def logp(self, actions): ...
    def entropy(self): ...

# 모델 카탈로그에 커스텀 동작 분포 등록
ModelCatalog.register_custom_action_dist("my_dist", MyActionDist)

ray.init()
trainer = ppo.PPOTrainer(env="CartPole-v0", config={
    "model": {
        # 커스텀 동작 분포 설정
        "custom_action_dist": "my_dist",
    },
})

지도 모델 손실(Supervised Model Losses)

TensorFlow

PyTorch

가변 길이/모수화된 동작 공간(Variable-length / Parametric Action Spaces)

1) 환경은 매 스텝마다 마스크와(또는) 유효한 동작의 임베딩을 관측의 일부로 함께 보내주어야 한다. * 배치가 가능하도록 동작의 수는 1에서 max 까지 변할 수 있다.

class MyParamActionEnv(gym.Env):
    def __init__(self, max_avail_actions):
        self.action_space = Discrete(max_avail_actions)
        self.observation_space = Dict({
            "action_mask": Box(0, 1, shape=(max_avail_actions, )),
            "avail_actions": Box(-1, 1, shape=(max_avail_actions, action_embedding_sz)),
            "real_obs": ...,
        })

2) 관측의 action_maskavail_actions 부분을 해석할 수 있는 커스텀 모델을 정의. 모델은 네트웍의 출력과 각 동작 임베딩의 내적으로 액션 로짓을 계산. 무효한 동작은 확률을 0으로 스케일링하여 제외(Mask out)될 수 있음.

class ParametricActionsModel(TFModelV2):
    def __init__(self,
                 obs_space,
                 action_space,
                 num_outputs,
                 model_config,
                 name,
                 true_obs_shape=(4,),
                 action_embed_size=2):
        super(ParametricActionsModel, self).__init__(
            obs_space, action_space, num_outputs, model_config, name)
        self.action_embed_model = FullyConnectedNetwork(...)

    def forward(self, input_dict, state, seq_lens):
        # 관측에서 유효한 동작 텐서를 추출.
        avail_actions = input_dict["obs"]["avail_actions"]
        action_mask = input_dict["obs"]["action_mask"]

        # 예측된 동작 임베딩을 계산
        action_embed, _ = self.action_embed_model({
            "obs": input_dict["obs"]["cart"]
        })

        # 임베딩을 [BATCH, 1, EMBED_SIZE]으로 확장. 유효한 동작 텐서는
        # [BATCH, MAX_ACTIONS, EMBED_SIZE] 형태인 것에 주목.
        intent_vector = tf.expand_dims(action_embed, 1)

        # 내적을 배치화 => 로짓의 형태는 [BATCH, MAX_ACTIONS].
        action_logits = tf.reduce_sum(avail_actions * intent_vector, axis=2)

        # 무효한 동작을 제외 (안정성을 위해 tf.float32.min 을 이용)
        # 가능한 동작 -> 0, 불가능한 동작 -> float32.min 으로 변경
        inf_mask = tf.maximum(tf.log(action_mask), tf.float32.min)

        # 불가능한 동작의 logit은 float32.min에 가깝게 된다.
        return action_logits + inf_mask, state

자동회귀 동작 분포(Autoregressive Action Distributions)

class BinaryAutoregressiveOutput(ActionDistribution):
    """동작 분포 P(a1, a2) = P(a1) * P(a2 | a1)"""

    @staticmethod
    def required_model_output_shape(self, model_config):
        return 16  # 모델 출력 특성 벡터 크기를 조절

    def sample(self):
        # 먼저 a1을 표집
        a1_dist = self._a1_distribution()
        a1 = a1_dist.sample()

        # a1에 조건화된 a2를 표집
        a2_dist = self._a2_distribution(a1)
        a2 = a2_dist.sample()

        # 동작 튜플을 반환
        return TupleActions([a1, a2])

    def logp(self, actions):
        a1, a2 = actions[:, 0], actions[:, 1]
        a1_vec = tf.expand_dims(tf.cast(a1, tf.float32), 1)
        a1_logits, a2_logits = self.model.action_model([self.inputs, a1_vec])
        return (Categorical(a1_logits, None).logp(a1) + Categorical(
            a2_logits, None).logp(a2))

    def _a1_distribution(self):
        BATCH = tf.shape(self.inputs)[0]
        a1_logits, _ = self.model.action_model(
            [self.inputs, tf.zeros((BATCH, 1))])
        a1_dist = Categorical(a1_logits, None)
        return a1_dist

    def _a2_distribution(self, a1):
        a1_vec = tf.expand_dims(tf.cast(a1, tf.float32), 1)
        _, a2_logits = self.model.action_model([self.inputs, a1_vec])
        a2_dist = Categorical(a2_logits, None)
        return a2_dist
class AutoregressiveActionsModel(TFModelV2):
    """위 코드에서 필요한 `.action_model` 브랜치를 구현."""

    def __init__(self, obs_space, action_space, num_outputs, model_config,
                 name):
        super(AutoregressiveActionsModel, self).__init__(
            obs_space, action_space, num_outputs, model_config, name)
        if action_space != Tuple([Discrete(2), Discrete(2)]):
            raise ValueError(
                "This model only supports the [2, 2] action space")

        # 입력
        obs_input = tf.keras.layers.Input(
            shape=obs_space.shape, name="obs_input")
        a1_input = tf.keras.layers.Input(shape=(1, ), name="a1_input")
        ctx_input = tf.keras.layers.Input(
            shape=(num_outputs, ), name="ctx_input")

        # 모델의 출력 (보통 'logits', 그러나 자동회귀 분포에서 이것은 관측을 인코딩하는 맥락/특성 레이어에 가까움)
        context = tf.keras.layers.Dense(
            num_outputs,
            name="hidden",
            activation=tf.nn.tanh,
            kernel_initializer=normc_initializer(1.0))(obs_input)

        # P(a1 | obs)
        a1_logits = tf.keras.layers.Dense(
            2,
            name="a1_logits",
            activation=None,
            kernel_initializer=normc_initializer(0.01))(ctx_input)

        # P(a2 | a1)
        # 주의: 보통은 다음처럼 P(a2 | a1, obs)을 구현하기를 원할 것:
        # a2_context = tf.keras.layers.Concatenate(axis=1)(
        #     [ctx_input, a1_input])
        a2_context = a1_input
        a2_hidden = tf.keras.layers.Dense(
            16,
            name="a2_hidden",
            activation=tf.nn.tanh,
            kernel_initializer=normc_initializer(1.0))(a2_context)
        a2_logits = tf.keras.layers.Dense(
            2,
            name="a2_logits",
            activation=None,
            kernel_initializer=normc_initializer(0.01))(a2_hidden)

        # 베이스 레이어
        self.base_model = tf.keras.Model(obs_input, context)
        self.register_variables(self.base_model.variables)
        self.base_model.summary()

        # 자동회귀 동작 샘플러
        self.action_model = tf.keras.Model([ctx_input, a1_input],
                                           [a1_logits, a2_logits])
        self.action_model.summary()
        self.register_variables(self.action_model.variables)

참고