强化学习初探——以gym库中的CartPole环境为例
Cart-Pole问题中的相关概念、定义
Cart-Pole问题
Cart-Pole问题(即推车-杆问题)是Barto、Sutton和Anderson在论文《Neuronlike Adaptive Elements That Can Solve Difficult Learning Control Problem》中提出的一个问题:
一根杆子通过一个非驱动关节连接到一辆推车上,推车沿着一条无摩擦的轨道移动。钟摆直立在车上,目标是通过在推车上施加左右方向的力来平衡杆子,使其尽可能长时间地保持杆子直立。
行为空间(对应Action)
行为空间中有两个行为:
行为 | 含义 |
---|---|
0 | 对推车施加向左的力 |
1 | 对推车施加向右的力 |
同时,在上述行为的作用下,推车速度降低或提高的速度并不是固定的,而是取决于杆子所指向的方向(因为杆子重心的改变使得移动推车所需的能量改变)。
状态空间(对应State)
状态空间中有四个属性(因此每一个state都有四个值,state=[value0、value1、value2、value3]):
对应下标 | 属性 | 最小值 | 最大值 |
---|---|---|---|
0 | 推车位置 | -4.8 | 4.8 |
1 | 推车速度 | -Inf | -Inf |
2 | 杆子角度 | -0.418 rad (-24°) | 0.418 rad (-24°) |
3 | 杆子角速度 | -Inf | -Inf |
上述最小值~最大值范围表示每个元素状态空间的可能值,但是它并不反映在整个强化学习训练的迭代过程中状态空间的允许值(理解:某些值可以出现,但出现之后不被程序允许,可能导致事件终止),比如:
- 推车的位置可以取
-4.8 ~ 4.8
之间的值,但如果推车的位置不在-2.4 ~ 2.4
中,那么事件会终止。 - 杆子的角度可以在
-0.418 rad ~ 0. 418 rad
/-24° ~ 24°
之间,但如果杆子的角度不在-0.2095 rad ~ 0. 2095 rad
/-12° ~ 12°
之间,那么事件会终止。
奖励(对应Reward)
需要根据目标设置奖励的计算方法,在Cart-Pole问题中,目标是使得杆子尽可能长时间地保持直立,因此采取了在每轮的训练过程中,在未进行下一轮训练前,事件(包括终止事件)的长度
。
在v1环境中,奖励的最高阈值为475。
Cart-Pole问题的编程实现
初始状态设置
在设置初始状态1时,选择赋予所有的观察值一个均匀的随机值(-0.05, 0.05)
。
def reset(
self,
*,
seed: Optional[int] = None,
options: Optional[dict] = None,
):
super().reset(seed=seed)
# Note that if you use custom reset bounds, it may lead to out-of-bound
# state/observations.
low, high = utils.maybe_parse_reset_bounds(
options, -0.05, 0.05 # default low
) # default high
self.state = self.np_random.uniform(low=low, high=high, size=(4,))
self.steps_beyond_terminated = None
if self.render_mode == "human":
self.render()
return np.array(self.state, dtype=np.float32), {}
终止条件定义
如果出现以下任何一种情况,事件将会结束:
- 事件终止:杆子角度的绝对值大于12°;
- 事件终止:推车位置的绝对值大于2.4(推车中心到达图形界面的边缘);
- 事件截断:事件长度超过500(v0为200)。
def step(self, action):
err_msg = f"{action!r} ({type(action)}) invalid"
assert self.action_space.contains(action), err_msg
assert self.state is not None, "Call reset before using step method."
x, x_dot, theta, theta_dot = self.state # 获取各属性的值
force = self.force_mag if action == 1 else -self.force_mag # 根据action获取力的大小及方向
costheta = math.cos(theta)
sintheta = math.sin(theta)
# For the interested reader:
# https://coneural.org/florian/papers/05_cart_pole.pdf
temp = (
force + self.polemass_length * theta_dot**2 * sintheta
) / self.total_mass
thetaacc = (self.gravity * sintheta - costheta * temp) / (
self.length * (4.0 / 3.0 - self.masspole * costheta**2 / self.total_mass)
)
xacc = temp - self.polemass_length * thetaacc * costheta / self.total_mass
if self.kinematics_integrator == "euler":
x = x + self.tau * x_dot
x_dot = x_dot + self.tau * xacc
theta = theta + self.tau * theta_dot
theta_dot = theta_dot + self.tau * thetaacc
else: # semi-implicit euler
x_dot = x_dot + self.tau * xacc
x = x + self.tau * x_dot
theta_dot = theta_dot + self.tau * thetaacc
theta = theta + self.tau * theta_dot
self.state = (x, x_dot, theta, theta_dot)
# 事件终止标志
terminated = bool(
x < -self.x_threshold
or x > self.x_threshold
or theta < -self.theta_threshold_radians
or theta > self.theta_threshold_radians
)
if not terminated:
reward = 1.0
elif self.steps_beyond_terminated is None:
# Pole just fell!
self.steps_beyond_terminated = 0
reward = 1.0
else:
if self.steps_beyond_terminated == 0:
logger.warn(
"You are calling 'step()' even though this "
"environment has already returned terminated = True. You "
"should always call 'reset()' once you receive 'terminated = "
"True' -- any further steps are undefined behavior."
)
self.steps_beyond_terminated += 1
reward = 0.0
if self.render_mode == "human":
self.render()
return np.array(self.state, dtype=np.float32), reward, terminated, False, {}
Tips:
①事件是episode的翻译;
②上述部分定义仅针对于官方对于CartPole环境的实现,可以自己根据需要重新定义。