模型预测控制MPC

第16章 模型预测控制

16.1 简介

之前几章介绍了基于值函数的方法DQN、基于策略的方法REINFORCE以及两者结合的方法Actor-Critic。他们都是无模型的方法,即没有建立一个环境模型来帮助智能体决策。而在深度强化学习领域,基于模型的方法通常用神经网络学习一个环境模型,然后利用该环境模型来帮助智能体训练和决策。利用环境模型帮助智能体训练和决策的方法有很多种,例如可以利用与之前的Dyna类似的思想生成一些数据来加入策略训练中。本章要介绍的模型预测控制(model predictive control,MPC)算法并不构建一个显式的策略,只根据环境模型来选择当前步要采取的动作。

16.2 打靶法

首先,让我们用一个形象的比喻来帮助理解模型预测控制方法。假设我们在下围棋,现在根据棋盘的布局,我们要选择现在落子的位置。一个优秀的棋手会根据目前局势来推演落子几步可能发生的局势,然后选择局势最好的一种情况来决定当前落子位置。

模型预测控制方法就是这样一种迭代的、基于模型的控制方法。值得注意的是,MPC方法中不存在一个显式的策略。具体而言,MPC方法在每次采取动作时,首先会生成一些候选动作序列,然后根据当前状态来确定每一条候选序列能得到的多好的结果,最终选择结果最好的那条动作序列的第一个动作来执行。因此,在使用MPC方法时,主要在两个过程中迭代,一是根据历史数据学习环境模型 P ^ ( s , a ) \widehat P(s,a) P (s,a),二是在和真实环境交互过程中用环境模型来选择动作。

首先,我们定义模型预测控制方法的目标。在第 k k k步时,我们要想做的就是最大化智能体的累积奖励,具体来说就是:

a r g m a x a k : k + H Σ t = k k + H r ( s t , a t ) s . t . s t + 1 = P ^ ( s t , a t ) arg \underset{a_{k:k+H}}{max}\overset{k+H}{\underset{t=k}\varSigma }r(s_t,a_t) s.t. s_{t+1}=\widehat P(s_t,a_t) argak:k+Hmaxt=kΣk+Hr(st,at)s.t.st+1=P (st,at)

其中H为推演的长度, a r g m a x a k : k + H arg{}max_{a_k:k+H} argmaxak:k+H表示从所有动作序列中选取累积奖励最大的序列。我们每次取最优序列中的第一个动作 a k a_k ak来与环境交互。MPC方法中的一个关键是如何生成一些候选动作的序列,候选动作生成的好坏将直接影响到MPC方法得到的动作。生成候选序列的过程我们称为打靶。

16.2.1 随机打靶法

随机打靶法的做法便是随机生成N条动作序列,即在生成每条动作序列的每一个动作时,都是从动作空间中随机采样一个动作,最终组合成N条长度为H的动作序列。

对于一些简单的环境,这个方法不但十分简单,而且效果还不错。那么,能不能在随机的基础上,根据已有的结果做的更好一些呢?

16.2.2 交叉熵方法选取动作序列

**交叉熵方法(cross entropy method ,CEM)**是一种进化策略的方法,它的核心思想是维护一个带参数的分布,根据每次采样的结果来更新分布中的参数,使得分布中能够获得较高的累计奖励的动作序列的概率比较高。相比于随机打靶法,交叉熵方法能够利用之前采样得到的比较好的结果,在一定程度上减少采样到的一些较差动作的概率,从而使得算法更加高效。对于一个与连续动作交互的环境来说,每次交互时交叉熵方法的做法如下:

  • for 次数 e = 1 → E e=1\rightarrow E e=1E do
  • ​ 从分布 P ( A ) P(\boldsymbol A) P(A)中选取N条动作序列 A 1 , … … A N \boldsymbol {A_1},……\boldsymbol A_N A1,……AN
  • ​ 对于每条动作序列 A 1 , … … A N \boldsymbol {A_1},……\boldsymbol A_N A1,……AN,用环境模型评估累积奖励
  • ​ 根据评估结果保留M条最优的动作序列 A i 1 , … … A i M \boldsymbol {A_{i_1}},……\boldsymbol A_{i_M} Ai1,……AiM
  • ​ 用这些动作序列 A i 1 , … … A i M \boldsymbol {A_{i_1}},……\boldsymbol A_{i_M} Ai1,……AiM去更新分布 p ( A ) p(\boldsymbol A) p(A)
  • end for
  • 计算所有最优动作序列的第一个动作的均值,作为当前时刻采取的动作

我们可以使用如下的代码来实现交叉熵方法,其中将采用截断正态分布。

class CEM:
    def __init__(self, n_sequence, elite_ratio, fake_env, upper_bound, lower_bound):
        self.n_sequence = n_sequence  # n个序列
        self.elite_ratio = elite_ratio  # 比率
        self.upper_bound = upper_bound  # 动作的上界
        self.lower_bound = lower_bound  # 动作的下界
        self.fake_env = fake_env

    def optimize(self, state, init_mean, init_var):  # 优化   状态  均值 标准差
        mean, var = init_mean, init_var

        # print(np.zeros_like(mean))
        # print(np.zeros_like(var))
        # 截断正态分布   mu+2sigma  mu-2sigma  mu=loc sigma=scale
        X = truncnorm(-2, 2, loc=np.zeros_like(mean), scale=np.ones_like(var))  # loc是一个和mean大小一样且全为0的矩阵 scale同理
        # plt.hist(X.rvs(25))  # 截断正态分布的直方图
        state = np.tile(state, (self.n_sequence, 1))  # 将状态根据序列的个数进行拷贝
        # print("n_sequence", n_sequence)
        # print("state", state)

        for _ in range(5):
            # 计算均值到上下界的距离 lb_dist 和 ub_dist。
            lb_dist, ub_dist = mean - self.lower_bound, self.upper_bound - mean
            # 计算受约束的方差 constrained_var,限制在均值到上下界距离的一半和当前方差之间,选取较小值
            constrained_var = np.minimum(np.minimum(np.square(lb_dist / 2), np.square(ub_dist / 2)), var)
            # 生成动作序列  X.rvs :从x中随机生成数据
            # 通过 X.rvs() 生成噪声序列,乘以方差的平方根,与均值相加,得到动作序列。
            action_sequences = [X.rvs() for _ in range(self.n_sequence)] * np.sqrt(constrained_var) + mean
            print(action_sequences)
            # 计算每条动作序列的累积奖励
            returns = self.fake_env.propagate(state, action_sequences)[:, 0]
            print(returns)
            # 选取累积奖励最高的若干条动作序列   这一部分是先将动作序列进行从小到大排序   然后选取后面较大的奖励的动作序列
            # elites = action_sequences[np.argsort(returns)]
            # elites=elites[-int(self.elite_ratio * self.n_sequence):]
            elites = action_sequences[np.argsort(returns)][-int(self.elite_ratio * self.n_sequence):]
            print("elites",elites)
            new_mean = np.mean(elites, axis=0)  # 求均值
            new_var = np.var(elites, axis=0)   # 求方差
            # 更新动作序列分布
            mean = 0.1 * mean + 0.9 * new_mean
            var = 0.1 * var + 0.9 * new_var

        return mean

整体逻辑

optimize 方法:执行优化过程。接收一些参数,包括当前状态 state、初始均值 init_mean 和初始方差 init_var

  • meanvar 初始化为 init_meaninit_var
  • 使用截断正态分布 truncnorm 生成动作序列的噪声 X,限制在 [-2, 2] 范围内。
  • 将状态 state 复制成 self.n_sequence 个相同的副本,形成一个矩阵。
  • 进行 5 次迭代优化过程:
    • 计算均值到上下界的距离 lb_distub_dist
    • 计算受约束的方差 constrained_var,限制在均值到上下界距离的一半和当前方差之间,选取较小值。
    • 通过 X.rvs() 生成噪声序列,与均值相加,乘以方差的平方根,得到动作序列。
    • 使用这些动作序列在模拟环境中计算累积奖励。
    • 选取累积奖励最高的一部分动作序列作为精英序列(elites)。
    • 更新均值和方差,使用新的均值和方差对动作序列分布进行更新。
  • 最终返回更新后的均值 mean

此优化过程的核心思想是通过不断生成和评估动作序列,筛选出累积奖励高的一部分序列,然后通过这部分精英序列更新均值和方差,以期望优化得到更好的动作序列分布。

16.3 PETS算法——需要学习的是环境模型本身

带有轨迹采样的概率集成(probabilistic ensembles with trajectory sampling, PETS)是一种使用MPC的基于模型的强化学习算法,在PETS算法中,环境模型采用了集成学习的方法,即会构建多个模型环境,然后用这多个环境模型来进行预测,最后使用CEM进行模型预测控制。接下来,我们来详细介绍模型构建与模型预测的方法。

在强化学习中,与智能体交互的环境是一个动态系统,所以拟合它的环境模型也通常是一个动态模型。PE代表环境模型的概率集成,以刻画模型对环境的两种不确定性,分别是偶然不确定性(aleatoric uncertainty)和认知不确定性(epistemic uncertainty)。偶然不确定性是由于系统中本身存在的随机性引起的,而认知不确定性是由“见”过的数据较少导致的自身认知的不足而引起的,如图16-1所示。

模型预测控制MPC_第1张图片

在PETS算法中,环境模型的构建会考虑到这两种不确定性。首先,我们定义环境模型的输出为一个高斯分布,用来捕捉偶然不确定性。令环境模型为 P ^ \widehat P P ,其参数为 θ \theta θ,那么基于当前状态动作对 ( s t , a t ) (s_t,a_t) (st,at),下一个状态 s t s_t st的分布可以写为

P ^ ( s t , a t ) = N ( μ θ ( s t , a t ) , Σ θ ( s t , a t ) ) \widehat P(s_t,a_t)=\mathcal N(\mu_\theta(s_t,a_t),\varSigma _\theta(s_t,a_t)) P (st,at)=N(μθ(st,at),Σθ(st,at))

这里我们可以采用神经网络来构建 μ θ \mu_\theta μθ Σ θ \varSigma_\theta Σθ。这样神经网络的损失函则为:

L ( θ ) = Σ n = 1 N [ μ θ ( s n , a n ) − s n + 1 ] T Σ θ − 1 ( s n , a n ) [ μ θ ( s n , a n ) − s n + 1 ] + l o g d e t Σ θ ( s n , a n ) \mathcal L(\theta)=\overset N{\underset {n=1}\varSigma}[\mu_\theta(s_n,a_n)-s_{n+1}]^T\varSigma^{-1}_{\theta}(s_n,a_n)[\mu_\theta(s_n,a_n)-s_{n+1}]+log det\varSigma _\theta(s_n,a_n) L(θ)=n=1ΣN[μθ(sn,an)sn+1]TΣθ1(sn,an)[μθ(sn,an)sn+1]+logdetΣθ(sn,an)

这样我们就得到了一个由神经网络表示的环境模型。在此基础上,我们选择用集成(ensemble)方法来捕捉认知不确定性。具体而言,我们构建B个网络框架一样的神经网络,它们的输入都是状态动作对,输出都是下一个状态的高斯分布的均值向量和协方差矩阵。但是它们的参数采用不同的随机初始化方式,并且当每次训练时,会从真实数据中随机采样不用的数据来训练。

有了环境模型的集成后,MPC算法会用其来预测奖励和下一个状态。具体来说,每一次预测会从B个模型中挑选一个来进行预测,因此一条轨迹的采样会使用多个模型环境,如图16-2所示。

模型预测控制MPC_第2张图片

通过不同的模型采样不同的状态和动作

16.4 PETS算法实践

首先,为了搭建这样一个较为复杂的模型,我们定义模型中每一层的构造。在定义时就必须考虑每一层都是一个集成。

这段代码定义了一个 FakeEnv 类,用于模拟环境的行为。这个模拟环境使用了一个模型(可能是神经网络),在给定当前观测和动作的情况下,模拟环境会返回模型预测的奖励和下一个观测。

逐行解释:

  1. 初始化方法 (__init__):
    • 接收一个 model 参数,表示环境的模型。
  2. step 方法:
    • 接收当前观测 obs 和动作 act
    • 将观测和动作拼接成一个输入,传递给模型进行预测。
    • 模型返回的是一个集成模型的均值和方差。
    • 将均值中的部分加上原始观测,得到下一个状态的预测。
    • 计算集成模型的标准差。
    • 通过正态分布随机抽样,生成多个可能的下一个状态的样本。
    • 随机选择一个模型用于采样样本。
    • 返回样本的奖励和下一个状态。
  3. propagate 方法:
    • 接收当前观测 obs 和一系列动作 actions
    • 在一个循环中,依次对每个动作进行模拟,累积奖励,并更新观测。
    • 返回累积奖励。

这个 FakeEnv 类的主要目的是用于强化学习中的模型预测控制算法(可能是类似 PETS 的算法)。通过模拟环境,它提供了一个与真实环境交互的方式,但是使用了一个模型来模拟环境的动力学。这种方法可以用于模型预测控制等算法的训练和评估。


device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu")

class Swish(nn.Module):
    ''' Swish激活函数 '''
    def __init__(self):
        super(Swish, self).__init__()

    def forward(self, x):
        return x * torch.sigmoid(x)


def init_weights(m):
    ''' 初始化环境模型权重 '''
    def truncated_normal_init(t, mean=0.0, std=0.01):
        torch.nn.init.normal_(t, mean=mean, std=std)
        while True:
            cond = (t < mean - 2 * std) | (t > mean + 2 * std)
            if not torch.sum(cond):
                break
            t = torch.where(cond, torch.nn.init.normal_(torch.ones(t.shape, device=device), mean=mean, std=std), t)
        return t

    if type(m) == nn.Linear or isinstance(m, FCLayer):
        truncated_normal_init(m.weight, std=1 / (2 * np.sqrt(m._input_dim)))
        m.bias.data.fill_(0.0)


class FCLayer(nn.Module):
    ''' 集成之后的全连接层 '''
    def __init__(self, input_dim, output_dim, ensemble_size, activation):
        super(FCLayer, self).__init__()
        self._input_dim, self._output_dim = input_dim, output_dim
        self.weight = nn.Parameter(torch.Tensor(ensemble_size, input_dim, output_dim).to(device))
        self._activation = activation
        self.bias = nn.Parameter(torch.Tensor(ensemble_size, output_dim).to(device))

    def forward(self, x):
        return self._activation(torch.add(torch.bmm(x, self.weight), self.bias[:, None, :]))


# 使用高斯分布的概率模型来定义一个集成模型
class EnsembleModel(nn.Module):
    ''' 环境模型集成 '''
    def __init__(self, state_dim, action_dim, ensemble_size=5, learning_rate=1e-3):
        super(EnsembleModel, self).__init__()
        # 输出包括均值和方差,因此是状态与奖励维度之和的两倍
        self._output_dim = (state_dim + 1) * 2
        self._max_logvar = nn.Parameter((torch.ones((1, self._output_dim // 2)).float() / 2).to(device), requires_grad=False)
        self._min_logvar = nn.Parameter((-torch.ones((1, self._output_dim // 2)).float() * 10).to(device), requires_grad=False)

        self.layer1 = FCLayer(state_dim + action_dim, 200, ensemble_size, Swish())
        self.layer2 = FCLayer(200, 200, ensemble_size, Swish())
        self.layer3 = FCLayer(200, 200, ensemble_size, Swish())
        self.layer4 = FCLayer(200, 200, ensemble_size, Swish())
        self.layer5 = FCLayer(200, self._output_dim, ensemble_size, nn.Identity())
        self.apply(init_weights)  # 初始化环境模型中的参数
        self.optimizer = torch.optim.Adam(self.parameters(), lr=learning_rate)

    def forward(self, x, return_log_var=False):
        ret = self.layer5(self.layer4(self.layer3(self.layer2(self.layer1(x)))))
        mean = ret[:, :, :self._output_dim // 2]
        # 在PETS算法中,将方差控制在最小值和最大值之间
        logvar = self._max_logvar - F.softplus(self._max_logvar - ret[:, :, self._output_dim // 2:])
        logvar = self._min_logvar + F.softplus(logvar - self._min_logvar)
        return mean, logvar if return_log_var else torch.exp(logvar)

    def loss(self, mean, logvar, labels, use_var_loss=True):
        inverse_var = torch.exp(-logvar)
        if use_var_loss:
            mse_loss = torch.mean(torch.mean(torch.pow(mean - labels, 2) *
                                             inverse_var,
                                             dim=-1),
                                  dim=-1)
            var_loss = torch.mean(torch.mean(logvar, dim=-1), dim=-1)
            total_loss = torch.sum(mse_loss) + torch.sum(var_loss)
        else:
            mse_loss = torch.mean(torch.pow(mean - labels, 2), dim=(1, 2))
            total_loss = torch.sum(mse_loss)
        return total_loss, mse_loss

    def train(self, loss):
        self.optimizer.zero_grad()
        loss += 0.01 * torch.sum(self._max_logvar) - 0.01 * torch.sum(self._min_logvar)
        loss.backward()
        self.optimizer.step()

class EnsembleDynamicsModel:
    ''' 环境模型集成,加入精细化的训练 '''
    def __init__(self, state_dim, action_dim, num_network=5):
        self._num_network = num_network
        self._state_dim, self._action_dim = state_dim, action_dim
        self.model = EnsembleModel(state_dim,
                                   action_dim,
                                   ensemble_size=num_network)
        self._epoch_since_last_update = 0

    def train(self,
              inputs,
              labels,
              batch_size=64,
              holdout_ratio=0.1,
              max_iter=20):
        # 设置训练集与验证集
        permutation = np.random.permutation(inputs.shape[0])  # 生成一个随机排列的索引数组
        inputs, labels = inputs[permutation], labels[permutation]  # 使用生成的随机索引数组对输入数据和标签进行洗牌
        num_holdout = int(inputs.shape[0] * holdout_ratio)  # 计算验证集样本数量
        train_inputs, train_labels = inputs[num_holdout:], labels[num_holdout:]  # 划分训练集  从索引num_holdout开始到数据集末尾
        holdout_inputs, holdout_labels = inputs[:num_holdout], labels[:num_holdout]  # 划分验证集 从数据开始到索引num_holdout-1结束
        holdout_inputs = torch.from_numpy(holdout_inputs).float().to(device)
        holdout_labels = torch.from_numpy(holdout_labels).float().to(device)
        holdout_inputs = holdout_inputs[None, :, :].repeat([self._num_network, 1, 1])
        holdout_labels = holdout_labels[None, :, :].repeat([self._num_network, 1, 1])

        # 保留最好的结果
        self._snapshots = {i: (None, 1e10) for i in range(self._num_network)}

        for epoch in itertools.count():
            # 定义每一个网络的训练数据
            train_index = np.vstack([
                np.random.permutation(train_inputs.shape[0])
                for _ in range(self._num_network)
            ])
            # 所有真实数据都用来训练
            for batch_start_pos in range(0, train_inputs.shape[0], batch_size):
                batch_index = train_index[:, batch_start_pos:batch_start_pos + batch_size]
                train_input = torch.from_numpy(train_inputs[batch_index]).float().to(device)
                train_label = torch.from_numpy(train_labels[batch_index]).float().to(device)

                mean, logvar = self.model(train_input, return_log_var=True)
                loss, _ = self.model.loss(mean, logvar, train_label)
                self.model.train(loss)
            # 使用验证集来评估模型性能
            with torch.no_grad():
                mean, logvar = self.model(holdout_inputs, return_log_var=True)
                _, holdout_losses = self.model.loss(mean,
                                                    logvar,
                                                    holdout_labels,
                                                    use_var_loss=False)
                holdout_losses = holdout_losses.cpu()
                break_condition = self._save_best(epoch, holdout_losses)
                if break_condition or epoch > max_iter:  # 结束训练
                    break
    # 用于保存性能最佳的模型快照,并在满足一定条件时停止训练
    def _save_best(self, epoch, losses, threshold=0.1):
        updated = False
        for i in range(len(losses)):
            current = losses[i]
            _, best = self._snapshots[i]
            improvement = (best - current) / best
            if improvement > threshold:
                self._snapshots[i] = (epoch, current)
                updated = True
        self._epoch_since_last_update = 0 if updated else self._epoch_since_last_update + 1
        return self._epoch_since_last_update > 5
    # 用于对新输入的数据进行预测。
    def predict(self, inputs, batch_size=64):
        mean, var = [], []
        for i in range(0, inputs.shape[0], batch_size):
            input = torch.from_numpy(inputs[i:min(i + batch_size, inputs.shape[0])]).float().to(device)
            cur_mean, cur_var = self.model(input[None, :, :].repeat([self._num_network, 1, 1]), return_log_var=False)
            mean.append(cur_mean.detach().cpu().numpy())
            var.append(cur_var.detach().cpu().numpy())
        return np.hstack(mean), np.hstack(var)


# 用于模拟一个环境,其中包含了一个集成模型,可以根据输入观测和动作预测奖励和下一个观测
class FakeEnv:
    def __init__(self, model):
        self.model = model

    # 模拟环境的一个时间步。接收当前观测(obs)和动作(act)作为输入,将他们链接起来形成输入数据,然后使用集成模型进行预测。通过对集成模型的均值和方差进行采样,生成多个样本,并对样本进行随机选择
    def step(self, obs, act):
        inputs = np.concatenate((obs, act), axis=-1)
        ensemble_model_means, ensemble_model_vars = self.model.predict(inputs)
        ensemble_model_means[:, :, 1:] += obs.numpy()
        ensemble_model_stds = np.sqrt(ensemble_model_vars)
        ensemble_samples = ensemble_model_means + np.random.normal(size=ensemble_model_means.shape) * ensemble_model_stds

        num_models, batch_size, _ = ensemble_model_means.shape
        models_to_use = np.random.choice(
            [i for i in range(self.model._num_network)], size=batch_size)
        batch_inds = np.arange(0, batch_size)
        samples = ensemble_samples[models_to_use, batch_inds]
        rewards, next_obs = samples[:, :1], samples[:, 1:]
        # 返回从集成样本中选择奖励和下一个观测
        return rewards, next_obs
    # 用于模拟一系列连续的时间步。接收初始观测和一系列动作,然后对每个时间步调用step方法,累计奖励,返回总的奖励
    def propagate(self, obs, actions):
        with torch.no_grad():
            obs = np.copy(obs)
            total_reward = np.expand_dims(np.zeros(obs.shape[0]), axis=-1)
            obs, actions = torch.as_tensor(obs), torch.as_tensor(actions)
            for i in range(actions.shape[1]):
                action = torch.unsqueeze(actions[:, i], 1)
                rewards, next_obs = self.step(obs, action)
                total_reward += rewards
                obs = torch.as_tensor(next_obs)
            return total_reward

class ReplayBuffer:
    def __init__(self, capacity):
        self.buffer = collections.deque(maxlen=capacity)

    def add(self, state, action, reward, next_state, done):
        self.buffer.append((state, action, reward, next_state, done))

    def size(self):
        return len(self.buffer)

    def return_all_samples(self):
        all_transitions = list(self.buffer)
        state, action, reward, next_state, done = zip(*all_transitions)
        return np.array(state), action, reward, np.array(next_state), done


class PETS:
    ''' PETS算法 '''
    def __init__(self, env, replay_buffer, n_sequence, elite_ratio,
                 plan_horizon, num_episodes):
        self._env = env
        self._env_pool = replay_buffer

        obs_dim = env.observation_space.shape[0]
        self._action_dim = env.action_space.shape[0]
        self._model = EnsembleDynamicsModel(obs_dim, self._action_dim)
        self._fake_env = FakeEnv(self._model)
        self.upper_bound = env.action_space.high[0]
        self.lower_bound = env.action_space.low[0]

        self._cem = CEM(n_sequence, elite_ratio, self._fake_env,
                        self.upper_bound, self.lower_bound)
        self.plan_horizon = plan_horizon
        self.num_episodes = num_episodes
    # 用于从缓冲区中收集样本,并利用这些样本训练集成动力学模型
    def train_model(self):
        env_samples = self._env_pool.return_all_samples()
        obs = env_samples[0]
        actions = np.array(env_samples[1])
        rewards = np.array(env_samples[2]).reshape(-1, 1)
        next_obs = env_samples[3]
        inputs = np.concatenate((obs, actions), axis=-1)
        labels = np.concatenate((rewards, next_obs - obs), axis=-1)
        self._model.train(inputs, labels)

    # 执行模型预测控制(mpc)。在每个时间步中,通过调用CEM优化器得到一系列动作序列,选择第一个动作执行,并更新观测、回放缓冲区和总回报
    def mpc(self):
        mean = np.tile((self.upper_bound + self.lower_bound) / 2.0,
                       self.plan_horizon)
        var = np.tile(
            np.square(self.upper_bound - self.lower_bound) / 16,
            self.plan_horizon)
        obs, done, episode_return = self._env.reset(), False, 0
        while not done:
            actions = self._cem.optimize(obs, mean, var)
            action = actions[:self._action_dim]  # 选取第一个动作
            next_obs, reward, done, _ = self._env.step(action)
            self._env_pool.add(obs, action, reward, next_obs, done)
            obs = next_obs
            episode_return += reward
            mean = np.concatenate([
                np.copy(actions)[self._action_dim:],
                np.zeros(self._action_dim)
            ])
        return episode_return

    # 执行随机策略的探索,用于初始化回放缓冲区
    def explore(self):
        obs, done, episode_return = self._env.reset(), False, 0
        while not done:
            action = self._env.action_space.sample()
            next_obs, reward, done, _ = self._env.step(action)
            self._env_pool.add(obs, action, reward, next_obs, done)
            obs = next_obs
            episode_return += reward
        return episode_return

    # 主要训练循环,包括随机策略的探索和PETS算法的迭代训练。
    def train(self):
        return_list = []
        explore_return = self.explore()  # 先进行随机策略的探索来收集一条序列的数据
        print('episode: 1, return: %d' % explore_return)
        return_list.append(explore_return)

        for i_episode in range(self.num_episodes - 1):
            self.train_model()
            episode_return = self.mpc()
            return_list.append(episode_return)
            print('episode: %d, return: %d' % (i_episode + 2, episode_return))
        return return_list

buffer_size = 100000
n_sequence = 50
elite_ratio = 0.2
plan_horizon = 25
num_episodes = 10
env_name = 'Pendulum-v0'
env = gym.make(env_name)

replay_buffer = ReplayBuffer(buffer_size)
pets = PETS(env, replay_buffer, n_sequence, elite_ratio, plan_horizon,
            num_episodes)
return_list = pets.train()

episodes_list = list(range(len(return_list)))
plt.plot(episodes_list, return_list)
plt.xlabel('Episodes')
plt.ylabel('Returns')
plt.title('PETS on {}'.format(env_name))
plt.show()
episode: 1, return: -1263
episode: 2, return: -1038
episode: 3, return: -894
episode: 4, return: -782
episode: 5, return: -251
episode: 6, return: -122
episode: 7, return: -253
episode: 8, return: -256
episode: 9, return: -123
episode: 10, return: -129

模型预测控制MPC_第3张图片

可以看出PETS算法的效果非常好,但是由于每次选取动作都需要再环境模型上进行大量的模拟,因此运行速度非常慢。与SAC算法的结果进行对比可以看出,PETS算法大大提高了样本效率,在比SAC算法的环境交互次数少得多的情况下就取得了差不多的效果。

16.5总结

通过学习与实践,我们可以看出模型预测控制(MPC)方法有着其独特的优势,例如他不用构建和训练策略,可以更好地利用环境,可以进行更长步数的的规划。但是MPC也有其局限性,例如模型在多步推演之后的准确性会大大降低,简单的控制策略对于复杂系统可能不够。MPC还有一个更为严重的问题,即每次计算动作的复杂度太大,这使其在一些策略及时性要求较高的系统中应用就变得不大现实。

python

np.zeros_like()函数

w_update = np.zeros_like(x)

函数要实现构造一个和x矩阵大小一样的全零矩阵

import numpy as np
import torch

x = torch.rand(2, 3)
print(x)
w_update = np.zeros_like(x)
print(w_update)

输出:

tensor([[0.0647, 0.8316, 0.5232],
        [0.9895, 0.5264, 0.8084]])
[[0. 0. 0.]
 [0. 0. 0.]]

截断正态分布stats.truncnorm()

就是在均值和方差之外,再指定正态分布随机数群的上下限,如 [ μ − 3 σ , μ + 3 σ ] [\mu-3\sigma,\mu+3\sigma] [μ3σ,μ+3σ]

stats.truncnorm()参数

X = stats.truncnorm(-2, 2, loc=mu, scale=sigma)

-2 2是截断的正态分布的方差倍数大小,loc是均值,scale是方差 这个的截断的大小是 [ μ − 2 σ , μ + 2 σ ] [\mu-2\sigma,\mu+2\sigma] [μ2σ,μ+2σ]

                 N. scipy.stats as stats
import pylab
from pylab import *

mu, sigma = 5, 0.7
lower, upper = mu - 2 * sigma, mu + 2 * sigma  # 截断在[μ-2σ, μ+2σ]
X = stats.truncnorm(-2, 2, loc=mu, scale=sigma)
N = stats.norm(loc=mu, scale=sigma)

figure(1)
subplot(2, 1, 1)
plt.hist(X.rvs(10000))  # 截断正态分布的直方图
subplot(2, 1, 2)
plt.hist(N.rvs(10000))  # 常规正态分布的直方图
plt.show()

这个代码中的X.rvs(10000)是说在X中随机选取10000个数据

np.tile(A,res)

np.tile(A,res)函数可对输入的数组,元组或列表进行重复构造,其输出是数组

该函数有两个参数

A:输入的数组,元组或列表

reps:重复构造的形状,可为数组,元组或列表

[ A ⋯ A ⋮ ⋱ ⋮ A ⋯ A ] m × n \left[ \begin{matrix} A& \cdots& A\\ \vdots& \ddots& \vdots\\ A& \cdots& A\\ \end{matrix} \right]_{m×n} AAAA m×n

A为基数组,m×n为元组res的大小

import numpy as np
a = np.array([0,1,2,3])
b = np.tile(a, (2, 3))   # shape可以是一维的,此时可以不用元组表示
print(b)

输出:

[[0 1 2 3 0 1 2 3 0 1 2 3]
 [0 1 2 3 0 1 2 3 0 1 2 3]]

tile中的(2,3)是说a的排列为两行三列

np.argsort()函数

np.argsort()函数将x中的元素从小到大排列,提取对应的index(索引),然后输出

x = torch.rand(2, 3)
print(x)
y = x.argsort()
print(y)

输出:

tensor([[0.5703, 0.3709, 0.2373],
        [0.7083, 0.0542, 0.0816]])
tensor([[2, 1, 0],
        [1, 2, 0]])

torch.where()

torch.where(condition, x, y):
condition:判断条件
x:若满足条件,则取x中元素
y:若不满足条件,则取y中元素

thon
import numpy as np
a = np.array([0,1,2,3])
b = np.tile(a, (2, 3)) # shape可以是一维的,此时可以不用元组表示
print(b)


输出:

[[0 1 2 3 0 1 2 3 0 1 2 3]
[0 1 2 3 0 1 2 3 0 1 2 3]]


tile中的(2,3)是说a的排列为两行三列



## np.argsort()函数

np.argsort()函数将x中的元素从小到大排列,提取对应的index(索引),然后输出

```python
x = torch.rand(2, 3)
print(x)
y = x.argsort()
print(y)

输出:

tensor([[0.5703, 0.3709, 0.2373],
        [0.7083, 0.0542, 0.0816]])
tensor([[2, 1, 0],
        [1, 2, 0]])

torch.where()

torch.where(condition, x, y):
condition:判断条件
x:若满足条件,则取x中元素
y:若不满足条件,则取y中元素

你可能感兴趣的:(python,pytorch,人工智能)