关于 CS 285 深度强化学习 Homework 1 的笔记很少,百度到 前年 一些同学的笔记,感觉有点不太对。。

这里写一些个人理解,敬供各位批评。

策略(Policy)函数的实现

连续动作空间 & 高斯策略实现

首先明确,这里的 “连续动作空间” ( $\pi(a|s)$ ) 就是单峰的高斯分布。即 动作向量的每个分量连续、独立且分别服从不同参数的高斯分布。

因此首先如果是高斯函数 ( $\pi_{\mu,\sigma}(a|s)$ ) , 则 待估计的 未知参数为 期望和标准差。动作值期望随观测值不同而变化。因此反映在 Pytorch 的实现上就是如下可梯度优化的张量。

  • nn.Parameter 是一个 Tensor 的包装类。可理解为一个可参与反向传播的特殊张量。这个类可以帮助实现某一部分参数与网络输入无关。
  • 策略期望 $\mu$ 是与 observation 相关的参数。因此是承接观测输入的 MLP
class MLPPolicy(BasePolicy, nn.Module, metaclass=abc.ABCMeta):

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        # init vars
        if self.discrete:
            pass
        else:
            self.logits_na = None
            self.mean_net = ptu.build_mlp(
                input_size=self.ob_dim, output_size=self.ac_dim,
                n_layers=self.n_layers, size=self.size,
            )
            self.log_std = nn.Parameter(
                torch.zeros(self.ac_dim, dtype=torch.float32, device=ptu.device)
            )

离散动作空间 & 离散策略函数

Gym 框架(包括 Gymnasium)中离散动作空间用 Discrete 表示。

它只能表示 一个 维度的有限离散取值(多选一),而 MultiDiscrete 是表示多维的离散值(分别多选一)

因此在 CS285 Homework 1 代码中,self.logits_na 是一个动作维度 不同取值的 概率对数(log-likelihood)

class MLPPolicy(BasePolicy, nn.Module, metaclass=abc.ABCMeta):

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        # init vars
        if self.discrete:
            self.logits_na = ptu.build_mlp(
                input_size=self.ob_dim,
                output_size=self.ac_dim,
                n_layers=self.n_layers,
                size=self.size,
            )
            self.mean_net = None
            self.log_std = None
        else:
            pass

最后使用 torch.distributions.Distribution (的不同子类)定义分布。在 Pytorch 中分布本身是可以传递梯度的。

  • 使用 torch.distributions.Normal 定义一个正态分布(注意:是正态分布,而不是正态分布采样值)。
  • distributions.Categorical 定义离散分布。Categorical 本身可以定义多维离散分布
class MLPPolicy(BasePolicy, nn.Module, metaclass=abc.ABCMeta):

    # This function defines the forward pass of the network.
    # You can return anything you want, but you should be able to differentiate
    # through it. For example, you can return a torch.FloatTensor. You can also
    # return more flexible objects, such as a
    # `torch.distributions.Distribution` object. It's up to you!
    def forward(self, observation: torch.FloatTensor) -> Any:
        if self.discrete:
            return distributions.Categorical(self.logits_na(observation))
        else:
            mu = self.mean_net(observation)
            std = self.log_std.exp().expand_as(mu)
            return distributions.Normal(mu, std)

最终通过 sample() 方法对 Distribution 对象采样。注意这个采样值是没有梯度的。

def get_action(self, obs: np.ndarray) -> np.ndarray:
    if len(obs.shape) > 1:
        observation = obs
    else:
        observation = obs[None]
    observation = ptu.from_numpy(observation)
    distr = self.forward(observation)
    return ptu.to_numpy(distr.sample())

上述的实现方法可以参考 PPO 的实现方法。(但优化原理上是不同的)

策略概率函数的优化

Stochastic Policy 的优化实现

如下分析属于个人理解。

上述 离散、连续 只是策略函数的一个分类。还可以是 Deterministic 或 Stochastic(见 Lecture 4:Tradeoff)

  • Stochastic:策略函数是一个概率函数 $\pi(a_t|s_t)$ 。同一个 state 可能有不同的 action
  • Deterministic:state 与 action 是完全的函数映射。比如 Policy 网络的输出就是 动作值,或 Q-Learning 中查表确定动作。

又因为, 这里实现的的是模仿学习,是 learned policy 与 experts' policy 的 拟合 (与其他的 RL 算法不同)。因此本质上是一种 参数估计 。(概率论 DNA 动了(大雾))

  • 矩估计:需要对分布采样。learned policy 采样均值可以近似 一阶矩 $E(A)$,而 expert policy 采样均值是 $\bar{A}$ ,那么 两个 policy 采样并作 MSE Loss 即可。但是 Distribution.sample() 没有梯度信息
  • 所以这里的方法实际是 极大似然估计:极大化如下 对数似然函数。其中 $a_i$ 是 $\pi_{expert}(a|s_i)$ 的采样
    $$\log L(\mu,\sigma)=\log(\prod_{i=1}^n\pi_{\mu,\sigma}(a_i|s_i))=\sum_{i=1}^{n}\log(\pi_{\mu,\sigma}(a_i|s_i))$$

具体如下:

class MLPPolicySL(MLPPolicy):
    def __init__(self, ac_dim, ob_dim, n_layers, size, **kwargs):
        super().__init__(ac_dim, ob_dim, n_layers, size, **kwargs)
        self.loss = nn.MSELoss()

    def update(
            self, observations, actions,
            adv_n=None, acs_labels_na=None, qvals=None
    ):
        # TODO: update the policy and return the loss
        self.optimizer.zero_grad()
        observations = ptu.from_numpy(observations)
        actions = ptu.from_numpy(actions)
        action_distribution = self.forward(observations)
        loss = -action_distribution.log_prob(actions).mean()
        loss.backward()
        self.optimizer.step()
        
        return {
            # You can add extra logging information here, but keep this line
            'Training Loss': ptu.to_numpy(loss),
        }

Deterministic 和 Stochastic 不仅局限于 强化学习。

同样也是个人分析。

  • Deterministic:有些模型本身就是 输入和输出的函数,是输入空间和输出空间的绝对映射。
  • Stochastic:有些模型的输出 表示的是 概率分布。比如某些分类问题,网络输出表示的是每一类的概率,最终预测值选最大值的标号。

上述的 分类问题情形 常用的损失函数就是 交叉熵 $H(p, q)$:

$$H(p, q)=\mathrm{E}_{p}[-\log q]=H(p)+D_{\mathrm{KL}}(p | q)$$

它是目标分布 p 的熵,加上 p 与模型分布 q 的 KL散度。Stochastic 模型就是 概率分布之间 的趋近。而 Deterministic 模型 则是 值与值之间 的拟合,所以相应的目标函数就是输出预测值和真值的 误差

本文参与了 SegmentFault 思否年度征文「一名技术人的 2022」,欢迎正在阅读的你也加入。


Petrickstar
4 声望1 粉丝

不想单纯只会编码。。