强化学习:OpenAI Gym 与 Q - 学习实战指南(上)
1. OpenAI Gym 工具包介绍
OpenAI Gym 是一个专门用于促进强化学习(RL)模型开发的工具包。它自带了多个预定义的环境,例如:
-
CartPole
:任务是平衡一根杆子。
-
MountainCar
:任务是将一辆汽车推上山顶。
此外,还有许多高级的机器人环境,可用于训练机器人抓取、推动和够取长凳上的物品,或者训练机械手握持方块、球或笔等。而且,OpenAI Gym 为开发新环境提供了一个方便、统一的框架,更多信息可查看其官方网站:https://gym.openai.com/。
要使用后续章节中的 OpenAI Gym 代码示例,需要安装
gym
库,可使用
pip
轻松完成安装:
pip install gym
如果在安装过程中需要额外帮助,请参考官方安装指南:https://gym.openai.com/docs/#installation。
2. 使用 OpenAI Gym 中的现有环境
为了练习使用 Gym 环境,我们以
CartPole-v1
环境为例,该环境在 OpenAI Gym 中已经存在。在这个示例环境中,有一根杆子连接在一个可以水平移动的小车上。
以下是该环境在强化学习中的一些属性:
-
状态(或观察)空间
:
Box(4,)
,表示一个四维空间,对应四个实值数,分别是小车的位置、小车的速度、杆子的角度以及杆子顶端的速度。
-
动作空间
:
Discrete(2)
,是一个离散空间,有两个选择,即向左或向右推动小车。
以下是使用该环境的代码示例:
import gym
env = gym.make('CartPole-v1')
print(env.observation_space) # 输出: Box(4,)
print(env.action_space) # 输出: Discrete(2)
环境对象
env
有一个
reset()
方法,可用于在每一轮训练前重新初始化环境,调用该方法会设置杆子的起始状态:
print(env.reset())
# 示例输出: array([-0.03908273, -0.00837535, 0.03277162, -0.0207195 ])
上述数组中的值表示小车的初始位置为 -0.039,速度为 -0.008,杆子的角度为 0.033 弧度,顶端的角速度为 -0.021。调用
reset()
方法时,这些值会在 [-0.05, 0.05] 范围内以均匀分布随机初始化。
重置环境后,可以通过选择一个动作并将其传递给
step()
方法来与环境进行交互:
print(env.step(action=0))
# 示例输出: (array([-0.03925023, -0.20395158, 0.03235723, 0.28212046]), 1.0, False, {})
print(env.step(action=1))
# 示例输出: (array([-0.04332927, -0.00930575, 0.03799964, -0.00018409]), 1.0, False, {})
通过上述两个命令,分别将小车向左(
action=0
)和向右(
action=1
)推动。每次调用
env.step()
方法时,会返回一个包含四个元素的元组:
| 元素 | 描述 |
| ---- | ---- |
| 数组 | 新的状态(或观察值) |
| 奖励 | 一个浮点型标量值 |
| 终止标志 | 布尔值(True 或 False) |
| Python 字典 | 包含辅助信息 |
环境对象
env
还有一个
render()
方法,可在每一步(或一系列步骤)后执行,以可视化环境以及杆子和小车的运动。
当杆子相对于垂直轴的角度大于 12 度(从任一侧),或者小车的位置距离中心位置超过 2.4 个单位时,本轮训练结束。此示例中定义的奖励是最大化小车和杆子在有效区域内的稳定时间,即通过最大化训练轮次的长度来最大化总奖励。
3. 网格世界示例
在介绍了
CartPole
环境作为使用 OpenAI Gym 工具包的热身练习后,我们将切换到另一个环境——网格世界环境。这是一个简单的环境,有
m
行和
n
列,以
m = 4
和
n = 6
为例,该环境有 30 种不同的可能状态。其中有四个终端状态:
-
黄金状态(状态 16)
:到达该状态可获得正奖励 +1。
-
陷阱状态(状态 10、15 和 22)
:到达这些状态会获得负奖励 -1。
其他所有状态的奖励为 0。智能体总是从状态 0 开始,每次重置环境时,智能体都会回到状态 0。动作空间包括四个方向:上、下、左、右。当智能体位于网格的外边界时,选择会导致其离开网格的动作不会改变状态。
4. 在 OpenAI Gym 中实现网格世界环境
为了通过 OpenAI Gym 试验网格世界环境,强烈建议使用脚本编辑器或集成开发环境(IDE),而不是交互式执行代码。
以下是实现步骤:
1. 创建一个名为
gridworld_env.py
的新 Python 脚本。
2. 导入必要的包和两个用于构建环境可视化的辅助函数。OpenAI Gym 库使用
Pyglet
库进行环境渲染,并提供了方便的包装类和函数,更多详细信息可查看:https://github.com/openai/gym/blob/master/gym/envs/classic_control/rendering.py。
以下是
gridworld_env.py
的代码示例:
import numpy as np
from gym.envs.toy_text import discrete
from collections import defaultdict
import time
import pickle
import os
from gym.envs.classic_control import rendering
CELL_SIZE = 100
MARGIN = 10
def get_coords(row, col, loc='center'):
xc = (col+1.5) * CELL_SIZE
yc = (row+1.5) * CELL_SIZE
if loc == 'center':
return xc, yc
elif loc == 'interior_corners':
half_size = CELL_SIZE//2 - MARGIN
xl, xr = xc - half_size, xc + half_size
yt, yb = xc - half_size, xc + half_size
return [(xl, yt), (xr, yt), (xr, yb), (xl, yb)]
elif loc == 'interior_triangle':
x1, y1 = xc, yc + CELL_SIZE//3
x2, y2 = xc + CELL_SIZE//3, yc - CELL_SIZE//3
x3, y3 = xc - CELL_SIZE//3, yc - CELL_SIZE//3
return [(x1, y1), (x2, y2), (x3, y3)]
def draw_object(coords_list):
if len(coords_list) == 1: # -> circle
obj = rendering.make_circle(int(0.45*CELL_SIZE))
obj_transform = rendering.Transform()
obj.add_attr(obj_transform)
obj_transform.set_translation(*coords_list[0])
obj.set_color(0.2, 0.2, 0.2) # -> black
elif len(coords_list) == 3: # -> triangle
obj = rendering.FilledPolygon(coords_list)
obj.set_color(0.9, 0.6, 0.2) # -> yellow
elif len(coords_list) > 3: # -> polygon
obj = rendering.FilledPolygon(coords_list)
obj.set_color(0.4, 0.4, 0.8) # -> blue
return obj
class GridWorldEnv(discrete.DiscreteEnv):
def __init__(self, num_rows=4, num_cols=6, delay=0.05):
self.num_rows = num_rows
self.num_cols = num_cols
self.delay = delay
move_up = lambda row, col: (max(row-1, 0), col)
move_down = lambda row, col: (min(row+1, num_rows-1), col)
move_left = lambda row, col: (row, max(col-1, 0))
move_right = lambda row, col: (
row, min(col+1, num_cols-1))
self.action_defs={0: move_up, 1: move_right,
2: move_down, 3: move_left}
nS = num_cols*num_rows
nA = len(self.action_defs)
self.grid2state_dict={(s//num_cols, s%num_cols):s
for s in range(nS)}
self.state2grid_dict={s:(s//num_cols, s%num_cols)
for s in range(nS)}
gold_cell = (num_rows//2, num_cols-2)
trap_cells = [((gold_cell[0]+1), gold_cell[1]),
(gold_cell[0], gold_cell[1]-1),
((gold_cell[0]-1), gold_cell[1])]
gold_state = self.grid2state_dict[gold_cell]
trap_states = [self.grid2state_dict[(r, c)]
for (r, c) in trap_cells]
self.terminal_states = [gold_state] + trap_states
print(self.terminal_states)
P = defaultdict(dict)
for s in range(nS):
row, col = self.state2grid_dict[s]
P[s] = defaultdict(list)
for a in range(nA):
action = self.action_defs[a]
next_s = self.grid2state_dict[action(row, col)]
if self.is_terminal(next_s):
r = (1.0 if next_s == self.terminal_states[0]
else -1.0)
else:
r = 0.0
if self.is_terminal(s):
done = True
next_s = s
else:
done = False
P[s][a] = [(1.0, next_s, r, done)]
isd = np.zeros(nS)
isd[0] = 1.0
super(GridWorldEnv, self).__init__(nS, nA, P, isd)
self.viewer = None
self._build_display(gold_cell, trap_cells)
def is_terminal(self, state):
return state in self.terminal_states
def _build_display(self, gold_cell, trap_cells):
screen_width = (self.num_cols+2) * CELL_SIZE
screen_height = (self.num_rows+2) * CELL_SIZE
self.viewer = rendering.Viewer(screen_width,
screen_height)
all_objects = []
bp_list = [
(CELL_SIZE-MARGIN, CELL_SIZE-MARGIN),
(screen_width-CELL_SIZE+MARGIN, CELL_SIZE-MARGIN),
(screen_width-CELL_SIZE+MARGIN,
screen_height-CELL_SIZE+MARGIN),
(CELL_SIZE-MARGIN, screen_height-CELL_SIZE+MARGIN)
]
border = rendering.PolyLine(bp_list, True)
border.set_linewidth(5)
all_objects.append(border)
for col in range(self.num_cols+1):
x1, y1 = (col+1)*CELL_SIZE, CELL_SIZE
x2, y2 = (col+1)*CELL_SIZE, (self.num_rows+1)*CELL_SIZE
line = rendering.PolyLine([(x1, y1), (x2, y2)], False)
all_objects.append(line)
for row in range(self.num_rows+1):
x1, y1 = CELL_SIZE, (row+1)*CELL_SIZE
x2, y2 = (self.num_cols+1)*CELL_SIZE, (row+1)*CELL_SIZE
line=rendering.PolyLine([(x1, y1), (x2, y2)], False)
all_objects.append(line)
for cell in trap_cells:
trap_coords = get_coords(*cell, loc='center')
all_objects.append(draw_object([trap_coords]))
gold_coords = get_coords(*gold_cell,
loc='interior_triangle')
all_objects.append(draw_object(gold_coords))
if (os.path.exists('robot-coordinates.pkl') and
CELL_SIZE==100):
agent_coords = pickle.load(
open('robot-coordinates.pkl', 'rb'))
starting_coords = get_coords(0, 0, loc='center')
agent_coords += np.array(starting_coords)
else:
agent_coords = get_coords(
0, 0, loc='interior_corners')
agent = draw_object(agent_coords)
self.agent_trans = rendering.Transform()
agent.add_attr(self.agent_trans)
all_objects.append(agent)
for obj in all_objects:
self.viewer.add_geom(obj)
def render(self, mode='human', done=False):
if done:
sleep_time = 1
else:
sleep_time = self.delay
x_coord = self.s % self.num_cols
y_coord = self.s // self.num_cols
x_coord = (x_coord+0) * CELL_SIZE
y_coord = (y_coord+0) * CELL_SIZE
self.agent_trans.set_translation(x_coord, y_coord)
rend = self.viewer.render(
return_rgb_array=(mode=='rgb_array'))
time.sleep(sleep_time)
return rend
def close(self):
if self.viewer:
self.viewer.close()
self.viewer = None
以下是该实现的详细说明:
- 使用 lambda 函数定义了四个不同的动作:
move_up()
、
move_down()
、
move_left()
和
move_right()
。
- NumPy 数组
isd
保存了起始状态的概率,当调用父类的
reset()
方法时,会根据这个分布随机选择一个起始状态。由于我们总是从状态 0(网格世界的左下角)开始,所以将状态 0 的概率设为 1.0,其他 29 个状态的概率设为 0.0。
- Python 字典
P
中定义的转移概率,决定了选择一个动作时从一个状态转移到另一个状态的概率,这使得环境具有概率性。为简单起见,我们只使用一个结果,即根据所选动作的方向改变状态。最终,这些转移概率将由
env.step()
函数用于确定下一个状态。
-
_build_display()
函数用于设置环境的初始可视化,
render()
函数用于显示智能体的移动。
可以在脚本末尾添加以下代码来测试这个实现:
if __name__ == '__main__':
env = GridWorldEnv(5, 6)
for i in range(1):
s = env.reset()
env.render(mode='human', done=False)
while True:
action = np.random.choice(env.nA)
res = env.step(action)
print('Action ', env.s, action, ' -> ', res)
env.render(mode='human', done=res[2])
if res[2]:
break
env.close()
执行该脚本后,应该可以看到网格世界环境的可视化效果。
在学习过程中,我们并不知道转移概率,目标是通过与环境交互来学习,因此在类定义之外无法访问
P
。
强化学习:OpenAI Gym 与 Q - 学习实战指南(下)
5. 使用 Q - 学习解决网格世界问题
在了解了强化学习算法的理论和开发过程,并通过 OpenAI Gym 工具包设置好环境后,我们将实现当前最流行的强化学习算法——Q - 学习。这里我们使用之前在
gridworld_env.py
脚本中实现的网格世界示例。
6. 实现 Q - 学习算法
首先,我们创建一个名为
agent.py
的新脚本,在其中定义一个用于与环境交互的智能体:
## Script: agent.py
from collections import defaultdict
import numpy as np
class Agent(object):
def __init__(
self, env,
learning_rate=0.01,
discount_factor=0.9,
epsilon_greedy=0.9,
epsilon_min=0.1,
epsilon_decay=0.95):
self.env = env
self.lr = learning_rate
self.gamma = discount_factor
self.epsilon = epsilon_greedy
self.epsilon_min = epsilon_min
self.epsilon_decay = epsilon_decay
## Define the q_table
self.q_table = defaultdict(lambda: np.zeros(self.env.nA))
def choose_action(self, state):
if np.random.uniform() < self.epsilon:
action = np.random.choice(self.env.nA)
else:
q_vals = self.q_table[state]
perm_actions = np.random.permutation(self.env.nA)
q_vals = [q_vals[a] for a in perm_actions]
perm_q_argmax = np.argmax(q_vals)
action = perm_actions[perm_q_argmax]
return action
def _learn(self, transition):
s, a, r, next_s, done = transition
q_val = self.q_table[s][a]
if done:
q_target = r
else:
q_target = r + self.gamma*np.max(self.q_table[next_s])
## Update the q_table
self.q_table[s][a] += self.lr * (q_target - q_val)
## Adjust the epislon
self._adjust_epsilon()
def _adjust_epsilon(self):
if self.epsilon > self.epsilon_min:
self.epsilon *= self.epsilon_decay
以下是对上述代码的详细解释:
-
__init__()
构造函数设置了各种超参数,如学习率、折扣因子(
γ
)以及
ε
- 贪婪策略的参数。最初,
ε
的值较高,但
_adjust_epsilon()
方法会逐渐减小它,直到达到最小值
ε_min
。
-
choose_action()
方法根据
ε
- 贪婪策略选择动作。通过选择一个随机均匀数来确定是随机选择动作,还是根据动作 - 值函数选择动作。
-
_learn()
方法实现了 Q - 学习算法的更新规则。它接收每个转移的元组,包括当前状态(
s
)、选择的动作(
a
)、观察到的奖励(
r
)、下一个状态(
s'
)以及一个标志,用于确定是否已到达本轮训练的结束。如果标志为训练结束,目标值等于观察到的奖励(
r
);否则,目标值为
r + γ * max_a Q(s', a)
。
7. 训练智能体
接下来,我们创建一个新脚本
qlearning.py
,将所有内容整合在一起,并使用 Q - 学习算法训练智能体:
## Script: qlearning.py
from gridworld_env import GridWorldEnv
from agent import Agent
from collections import namedtuple
import matplotlib.pyplot as plt
import numpy as np
np.random.seed(1)
Transition = namedtuple(
'Transition', ('state', 'action', 'reward',
'next_state', 'done'))
def run_qlearning(agent, env, num_episodes=50):
history = []
for episode in range(num_episodes):
state = env.reset()
env.render(mode='human')
final_reward, n_moves = 0.0, 0
while True:
action = agent.choose_action(state)
next_s, reward, done, _ = env.step(action)
agent._learn(Transition(state, action, reward,
next_s, done))
env.render(mode='human', done=done)
state = next_s
n_moves += 1
if done:
break
final_reward = reward
history.append((n_moves, final_reward))
print('Episode %d: Reward %.1f #Moves %d'
% (episode, final_reward, n_moves))
return history
def plot_learning_history(history):
fig = plt.figure(1, figsize=(14, 10))
ax = fig.add_subplot(2, 1, 1)
episodes = np.arange(len(history))
moves = np.array([h[0] for h in history])
plt.plot(episodes, moves, lw=4,
marker='o', markersize=10)
ax.tick_params(axis='both', which='major', labelsize=15)
plt.xlabel('Episodes', size=20)
plt.ylabel('# moves', size=20)
ax = fig.add_subplot(2, 1, 2)
rewards = np.array([h[1] for h in history])
plt.step(episodes, rewards, lw=4)
ax.tick_params(axis='both', which='major', labelsize=15)
plt.xlabel('Episodes', size=20)
plt.ylabel('Final rewards', size=20)
plt.savefig('q - learning - history.png', dpi=300)
plt.show()
if __name__ == '__main__':
env = GridWorldEnv(num_rows=5, num_cols=6)
agent = Agent(env)
history = run_qlearning(agent, env)
env.close()
plot_learning_history(history)
执行这个脚本将运行 50 轮 Q - 学习程序,智能体的行为将被可视化。在学习过程开始时,智能体大多会陷入陷阱状态,但随着时间的推移,它会从失败中学习,最终找到黄金状态(例如,在第 7 轮首次找到)。
以下是整个过程的流程图:
graph TD;
A[开始] --> B[创建网格世界环境];
B --> C[创建智能体];
C --> D[进行多轮训练];
D --> E[每轮训练开始,重置环境];
E --> F[选择动作];
F --> G[执行动作,获取新状态和奖励];
G --> H[更新 Q 表];
H --> I{是否到达终止状态};
I -- 否 --> F;
I -- 是 --> J[记录本轮训练的移动次数和奖励];
J --> K{是否完成所有轮次};
K -- 否 --> E;
K -- 是 --> L[绘制学习历史图];
L --> M[结束];
总结
本文详细介绍了 OpenAI Gym 工具包,包括如何使用其预定义的环境(如
CartPole
和网格世界),以及如何实现自定义环境。同时,我们还实现了 Q - 学习算法,并使用它来训练智能体在网格世界环境中寻找黄金状态。通过这些示例,我们可以看到强化学习在解决复杂决策问题中的强大能力。
关键步骤总结
| 步骤 | 描述 | 脚本 |
|---|---|---|
| 1 | 安装 OpenAI Gym 库 |
pip install gym
|
| 2 | 实现网格世界环境 |
gridworld_env.py
|
| 3 | 定义 Q - 学习智能体 |
agent.py
|
| 4 | 训练智能体并绘制学习历史图 |
qlearning.py
|
通过按照这些步骤操作,你可以在自己的项目中应用强化学习和 Q - 学习算法。
超级会员免费看
1123

被折叠的 条评论
为什么被折叠?



