12.4 TRPO算法优化实战:基于矩阵低秩分解的TRPO
在现实应用中,强化学习中的大多数方法使用策略梯度(PG)方法来学习将状态映射到动作的参数化随机策略。标准方法是通过神经网络(NN)实现这种映射,其参数使用随机梯度下降进行优化。然而,PG方法容易产生大的策略更新,可能导致学习效率低下。而在本章介绍信赖区域策略优化(TRPO)这样的信赖区域算法限制了策略更新步骤,在本节的内容中,将介绍一个TRPO算法的优化策略:使用基于低秩矩阵的模型作为TRPO算法参数估计的高效替代方法,并通过具体实例进行了演示。通过将随机策略的参数收集到矩阵中并应用矩阵补全技术,促进并强制实施低秩。本实例的执行结果表明,基于低秩矩阵的策略模型有效地减少了与NN模型相比的计算和样本复杂性,同时保持了可比较的累积奖励。
实例12-5:TRPO算法优化:NN-TRPO 和 TRLRPO(源码路径:daima\12\matrix-low-rank-trpo-main)
12.4.1 优化策略:NN-TRPO 和 TRLRPO
NN-TRPO(Neural Network Trust Region Policy Optimization)和TRLRPO(Trust Region Linear Regression Policy Optimization)都是 TRPO(Trust Region Policy Optimization)算法的变种,用于训练强化学习代理的策略。
1. NN-TRPO
NN-TRPO使用神经网络(Neural Network)作为策略(policy)的函数近似器。神经网络模型可以灵活地表示复杂的策略,并且通常包括输入层、隐藏层和输出层。NN-TRPO通过神经网络来建模和优化策略。NN-TRPO的特点是使用了一种称为"Trust Region"的方法,以确保每次策略更新都是小幅度的,从而保持策略的稳定性。这可以防止策略在训练过程中发生剧烈的变化,从而提高了收敛性和稳定性。
NN-TRPO通过优化策略以最大化累积奖励,同时在每次更新时限制策略的变化范围。它尝试找到策略参数的小幅度变化,以最大化期望奖励,同时保持策略的稳定性。
2. TRLRPO
TRLRPO与NN-TRPO不同,它使用线性回归(Linear Regression)模型来表示策略。线性回归是一种简化的函数逼近方法,它将策略表示为输入特征的线性组合。TRLRPO的特点是其代理模型更简单,参数较少,通常适用于状态空间较小的问题。由于使用线性模型,TRLRPO的策略更新通常更加保守,不容易引入大的变化。
TRLRPO的核心思想与NN-TRPO相似,都是通过优化策略以最大化累积奖励。但由于策略是线性的,它的更新相对较为保守。
总的来说,NN-TRPO和TRLRPO都是用于训练强化学习代理的策略优化算法,它们的主要区别在于代理模型的选择和策略更新的保守程度。NN-TRPO通常适用于复杂的环境和大规模状态空间,而TRLRPO适用于状态空间相对较小的问题。选择哪种算法取决于问题的性质和需求。
12.4.2 项目介绍
本项目是一个强化学习(Reinforcement Learning,RL)项目,旨在研究和比较使用两种不同的 TRPO(Trust Region Policy Optimization)算法,即 NN-TRPO 和 TRLRPO,来训练代理在不同的强化学习环境中执行任务的性能。
1. 项目目标
- 项目旨在研究和比较使用 TRPO 算法训练的代理在三个不同的强化学习环境中的性能。
- 项目关注两种 TRPO 变种:NN-TRPO 和 TRLRPO。
2. 项目组成
- 项目包含多个 Python 脚本,用于定义强化学习环境、代理模型、训练算法以及结果可视化。
3. 强化学习环境
- 项目使用三个标准的强化学习环境进行实验:Pendulum、Acrobot 和 Mountain Car。
- 这些环境具有不同的复杂性和动力学特性。
4. 代理模型
- 项目定义了两种类型的代理模型:NN(神经网络)代理和 LR(线性回归)代理。
- NN 代理使用神经网络来建模策略和价值函数,而 LR 代理使用线性回归模型。
5. 训练算法
- 项目采用了 TRPO 算法的两种变种,分别是 NN-TRPO 和 TRLRPO。
- 训练算法遵循 TRPO 的核心思想,通过优化代理的策略以最大化累积回报。
6. 实验和性能比较
- 项目在不同环境中使用两种代理类型和两种 TRPO 变种进行多次实验。
- 记录了每个实验的性能指标,如返回值。
7. 结果可视化
- 使用 matplotlib 对实验结果进行可视化,生成性能曲线和置信区间图表。
- 图表用于比较不同代理在不同环境下的性能。
12.4.3 经验数据管理和状态空间离散化
编写文件src/utils.py,功能是实现经验数据管理和状态空间离散化处理,用于支持强化学习算法的实现。提供了对数据的存储和状态表示的转换功能,以便于在强化学习任务中使用。文件src/utils.py的具体实现代码如下所示。
from typing import Tuple, List
import numpy as np
class Buffer:
def __init__(self) -> None:
self.actions = []
self.states = []
self.logprobs = []
self.rewards = []
self.terminals = []
def clear(self) -> None:
self.actions = []
self.states = []
self.logprobs = []
self.rewards = []
self.terminals = []
def __len__(self) -> int:
return len(self.states)
class Discretizer:
def __init__(
self,
min_points: int,
max_points: int,
buckets: List[int],
dimensions: List[List[int]],
) -> None:
self.min_points = np.array(min_points)
self.max_points = np.array(max_points)
self.buckets = np.array(buckets)
self.dimensions = dimensions
self.range = self.max_points - self.min_points
self.spacing = self.range / self.buckets
self.n_states = np.round(self.buckets).astype(int)
self.row_n_states = [self.n_states[dim] for dim in self.dimensions[0]]
self.col_n_states = [self.n_states[dim] for dim in self.dimensions[1]]
self.N = np.prod(self.row_n_states)
self.M = np.prod(self.col_n_states)
self.row_offset = [int(np.prod(self.row_n_states[i + 1:])) for i in range(len(self.row_n_states))]
self.col_offset = [int(np.prod(self.col_n_states[i + 1:])) for i in range(len(self.col_n_states))]
def get_index(self, state: np.ndarray) -> Tuple[np.ndarray, np.ndarray]:
state = np.clip(state, a_min=self.min_points, a_max=self.max_points)
scaling = (state - self.min_points) / self.range
idx = np.round(scaling * (self.buckets - 1)).astype(int)
row_idx = idx[:, self.dimensions[0]]
row = np.sum(row_idx*self.row_offset, axis=1)
col = None
col_idx = idx[:, self.dimensions[1]]
col = np.sum(col_idx*self.col_offset, axis=1)
return row, col
上述代码包含了两个类:Buffer 和 Discretizer,具体说明如下所示:
(1)类Buffer
- 类Buffer是一个简单的数据缓存类,用于存储强化学习中的经验数据。
- actions 存储动作序列,states 存储状态序列,logprobs 存储对数概率序列,rewards 存储奖励序列,terminals 存储终止状态序列。
- clear 方法用于清空缓存,即将所有序列清空。
- __len__ 方法返回缓存中状态序列的长度。
(2)类Discretizer
- 类Discretizer用于将连续的状态空间离散化为有限数量的离散状态。
- 初始化方法接受一些参数,包括最小和最大状态值、分桶数、和状态空间的维度信息。
- get_index 方法接受一个连续状态作为输入,将其映射到离散状态的索引。该方法首先对输入状态进行剪切(限制在最小和最大状态值之间),然后将状态缩放到0到1之间,再将其映射到分桶的索引。
- get_index 方法返回两个分别表示行和列索引的数组,这可以用于在一个二维离散状态空间中定位状态的位置。
12.4.4 定义环境
编写文件src/environments.py,功能是自定义创建强化学习环境似,提供了对 Gym 中标准环境的一些扩展和修改,以适应特定的需求。具体实现代码如下所示。
import numpy as np
from gym.envs.classic_control.pendulum import PendulumEnv
from gym.envs.classic_control.acrobot import AcrobotEnv, wrap, bound, rk4
class CustomPendulumEnv(PendulumEnv):
def reset(self):
self.state = [np.random.rand()/100, np.random.rand()/100]
self.last_u = None
return self._get_obs(), {}
def _get_obs(self):
theta, thetadot = self.state
return np.array([theta, thetadot])
class CustomAcrobotEnv(AcrobotEnv):
def step(self, a):
s = self.state
assert s is not None, "Call reset before using AcrobotEnv object."
torque = np.clip(a, -1, 1)
if self.torque_noise_max > 0:
torque += self.np_random.uniform(
-self.torque_noise_max, self.torque_noise_max
)
s_augmented = np.append(s, torque)
ns = rk4(self._dsdt, s_augmented, [0, self.dt])
ns[0] = wrap(ns[0], -np.pi, np.pi)
ns[1] = wrap(ns[1], -np.pi, np.pi)
ns[2] = bound(ns[2], -self.MAX_VEL_1, self.MAX_VEL_1)
ns[3] = bound(ns[3], -self.MAX_VEL_2, self.MAX_VEL_2)
self.state = ns
terminated = self._terminal()
reward = -1.0 if not terminated else 100.0
return (self._get_ob(), reward, terminated, False, {})
def _get_ob(self):
s = self.state
return np.array([s[0], s[1], s[2], s[3]])
上述代码包含了两个自定义的环境类 CustomPendulumEnv 和 CustomAcrobotEnv,这些环境是基于 Gym 库的 PendulumEnv 和 AcrobotEnv 进行扩展和自定义的,具体说明如下所示:
(1)CustomPendulumEnv:继承自 Gym 中的 PendulumEnv 环境,具体说明如下:
- 重写了 reset 方法,用于初始化环境状态。在这里,状态被随机初始化为一个非常小的值。
- 重写了 _get_obs 方法,返回环境的观测值,这里只包括角度和角速度。
(2)CustomAcrobotEnv:继承自 Gym 中的 AcrobotEnv 环境,具体说明如下:
- 重写了 step 方法,该方法用于执行一步环境动态模拟。在这里,对动作进行了一些处理,包括截断动作的范围和添加扭矩噪声。
- 在 _get_ob 方法中返回环境的观测值,这里包括两个关节的角度和角速度。
12.4.5 创建强化学习模型
编写文件src/models.py,功能是创建机器学习模型,分别定义了两个神经网络模型的类PolicyNetwork 和 ValueNetwork,以及两个线性回归模型的类 PolicyLR 和 ValueLR。具体实现代码如下所示。
from typing import Tuple, List
import numpy as np
import torch
class PolicyNetwork(torch.nn.Module):
def __init__(
self,
num_inputs: int,
num_hiddens: List[int],
num_outputs: int
) -> None:
super(PolicyNetwork, self).__init__()
self.layers = torch.nn.ModuleList()
for h in num_hiddens:
self.layers.append(torch.nn.Linear(num_inputs, h))
self.layers.append(torch.nn.Tanh())
num_inputs = h
action_layer = torch.nn.Linear(num_inputs, num_outputs)
action_layer.weight.data.mul_(0.1)
action_layer.bias.data.mul_(0.0)
self.layers.append(action_layer)
self.log_sigma = torch.nn.Parameter(torch.zeros(1, num_outputs))
def forward(self, x: torch.Tensor) -> Tuple[torch.Tensor]:
for layer in self.layers:
x = layer(x)
return x, torch.clamp(self.log_sigma, min=-2.0, max=0.0)
class ValueNetwork(torch.nn.Module):
def __init__(self,
num_inputs: int,
num_hiddens: List[int],
num_outputs: int
) -> None:
super(ValueNetwork, self).__init__()
self.layers = torch.nn.ModuleList()
for h in num_hiddens:
self.layers.append(torch.nn.Linear(num_inputs, h))
self.layers.append(torch.nn.Tanh())
num_inputs = h
action_layer = torch.nn.Linear(num_inputs, num_outputs)
action_layer.weight.data.mul_(0.1)
action_layer.bias.data.mul_(0.0)
self.layers.append(action_layer)
def forward(self, x: torch.Tensor) -> torch.Tensor:
for layer in self.layers:
x = layer(x)
return x
class PolicyLR(torch.nn.Module):
def __init__(
self,
n: int,
m: int,
k: int,
scale: float=1.0
) -> None:
super().__init__()
L = scale*torch.randn(n, k, dtype=torch.float32, requires_grad=True)
R = scale*torch.randn(k, m, dtype=torch.float32, requires_grad=True)
self.L = torch.nn.Parameter(L)
self.R = torch.nn.Parameter(R)
self.log_sigma = torch.nn.Parameter(torch.zeros(1))
def forward(
self,
indices: Tuple[np.ndarray, np.ndarray]
) -> Tuple[torch.Tensor]:
rows, cols = indices
prod = self.L[rows, :] * self.R[:, cols].T
res = torch.sum(prod, dim=-1)
return res, torch.clamp(self.log_sigma, min=-2.5, max=0.0)
class ValueLR(torch.nn.Module):
def __init__(
self,
n: int,
m: int,
k: int,
scale: float=1.0
) -> None:
super().__init__()
L = scale*torch.randn(n, k, dtype=torch.float32, requires_grad=True)
R = scale*torch.randn(k, m, dtype=torch.float32, requires_grad=True)
self.L = torch.nn.Parameter(L)
self.R = torch.nn.Parameter(R)
def forward(
self,
indices: Tuple[np.ndarray, np.ndarray]
) -> Tuple[torch.Tensor]:
rows, cols = indices
prod = self.L[rows, :] * self.R[:, cols].T
return torch.sum(prod, dim=-1)
对上述代码的具体说明如下:
- 类PolicyNetwork:是一个用于表示策略的神经网络模型,通过神经网络的前向传播方法 forward 来计算策略。构造函数初始化了神经网络的结构,接受输入维度 num_inputs、隐藏层结构 num_hiddens 和输出维度 num_outputs。神经网络包含若干个全连接层和激活函数,最后输出策略的均值和对数标准差(用于连续动作空间的策略)。
- 类ValueNetwork:是一个用于表示值函数的神经网络模型,通过神经网络的前向传播方法 forward 来计算状态值。构造函数初始化了神经网络的结构,接受输入维度 num_inputs、隐藏层结构 num_hiddens 和输出维度 num_outputs。神经网络包含若干个全连接层和激活函数,最后输出状态值。
- 类PolicyLR:是一个用于表示策略的线性回归模型,其中构造函数初始化了两个矩阵 L 和 R,这些参数将用于计算策略。前向传播方法 forward 接受状态的索引,计算策略的输出。线性回归模型的输出是一个数值和对数标准差(用于连续动作空间的策略)。
- 类ValueLR:是一个用于表示值函数的线性回归模型。其中构造函数初始化了两个矩阵 L 和 R,这些参数将用于计算状态值。前向传播方法 forward 接受状态的索引,计算状态值的输出。