1、环境初始化
使用`gym-super-mario-bros`库来设置游戏环境,这个库能让Mario游戏成为强化学习的实验环境:
1. 首先,它检查Gym库的版本。如果版本早于0.26,那么它将创建一个`SuperMarioBros-1-1-v0`的游戏环境,并不指定渲染模式,因为在0.26之前的版本中,默认就有渲染功能。然而,如果版本是0.26或更高,它会创建环境时明确指定`render_mode='rgb'`参数,这允许以RGB格式渲染游戏画面到屏幕上。apply_api_compatibility=True
是为了前向兼容旧版本接口
2. JoypadSpace它限制了操作空间(action space),只包含以下两种动作:
- 0: 向右走
- 1: 向右跳
使用`JoypadSpace`包装器来修改环境的操作空间,这样就只需要关注向右移动和跳跃这两个关键动作。
3. 通过调用`env.reset()`重置环境,准备开始新的游戏。
4. 使用`env.step(action=0)`进行一步游戏,其中`action=0`意味着执行“向右走”的动作。然后,它从环境中获取下一个状态(`next_state`)、奖励值(`reward`)、是否完成游戏(`done`)以及额外的信息(`info`)。
6. 最后,打印出这些信息,包括`next_state`的形状(即游戏画面的尺寸)、获得的奖励、游戏是否结束以及额外信息。
# Initialize Super Mario environment (in v0.26 change render mode to 'human' to see results on the screen)
if gym.__version__ < '0.26':
env = gym_super_mario_bros.make("SuperMarioBros-1-1-v0", new_step_api=True)
else:
env = gym_super_mario_bros.make("SuperMarioBros-1-1-v0", render_mode='rgb', apply_api_compatibility=True)
# Limit the action-space to
# 0. walk right
# 1. jump right
env = JoypadSpace(env, [["right"], ["right", "A"]])
env.reset()
next_state, reward, done, trunc, info = env.step(action=0)
print(f"{next_state.shape},\n {reward},\n {done},\n {info}")
2、环境预处理
# Apply Wrappers to environment
env = SkipFrame(env, skip=4)
env = GrayScaleObservation(env, keep_dim=False)
env = ResizeObservation(env, shape=84)
env = TransformObservation(env, f=lambda x: x / 255.)
env = FrameStack(env, num_stack=4)
2.1、SkipFrame类 用于包装一个OpenAI Gym环境,主要功能是跳过一系列帧并累积奖励,这在强化学习中,尤其是在处理视频游戏环境时非常有用,以减少动作频率并使学习过程更加高效。 函数接收接收两个参数:env和skip env 是要包装的原始Gym环境。 skip 是一个整数,表示只返回skip整数帧的动作和奖励。 重写step方法,当调用step方法时,它会重复执行相同的动作,并累积每次动作得到的奖励。如果在重复动作的过程中遇到 done=True(即环境进入终止状态),则立即停止重复,并返回当前观察值、累积的总奖励、终止状态和额外的信息字典。
class SkipFrame(gym.Wrapper):
def __init__(self, env, skip):
"""Return only every `skip`-th frame"""
super().__init__(env)
self._skip = skip
def step(self, action):
"""Repeat action, and sum reward"""
total_reward = 0.0
done = False
for i in range(self._skip):
# Accumulate reward and repeat the same action
obs, reward, done, info = self.env.step(action)
total_reward += reward
if done:
break
return obs, total_reward, done, info
2.2、GrayScaleObservation
类
gym
库中ObservationWrapper
的一个子类。ObservationWrapper
用于修改环境的观测值(observation)而不改变其底层逻辑,使环境适应特定类型的观测需求。
GrayScaleObservation
的作用是将彩色图像观测值转换为灰度图像。这在处理视觉输入的强化学习任务中很有用,因为灰度图像可以减少计算量和模型复杂度,同时保留大部分有用的信息。灰度图像是由单个通道组成的,而彩色图像通常有三个通道(红、绿、蓝)。
初始化:构造函数接受一个环境
env
作为参数,并且有一个可选参数keep_dim
,用于决定是否保持输出图像的维度与输入相同。如果keep_dim=True
,输出将是一个三维数组,其中最后一维为1;否则,输出将是一个二维数组。验证输入:初始化时,它会检查环境的观测空间是否为三维,且最后一个维度的大小为3,这意味着输入是一个RGB图像。
定义观测空间:根据
keep_dim
的值,它会重新定义观测空间。如果保持维度,则输出空间是一个具有单个通道的三维数组;如果不保持维度,则输出空间是一个二维数组。观察方法:
observation
方法负责实际的转换。它使用OpenCV库的cvtColor
函数将RGB图像转换为灰度图像。如果keep_dim
为真,它会在最后添加一个维度,以保持输出为三维。
这种转换可以简化神经网络的输入,使其更容易处理,尤其是在资源有限或需要加速训练的过程中。同时,灰度图像可以减少过拟合的风险,因为它们减少了输入特征的数量。
class GrayScaleObservation(ObservationWrapper):
r"""Convert the image observation from RGB to gray scale. """
def __init__(self, env, keep_dim=False):
super(GrayScaleObservation, self).__init__(env)
self.keep_dim = keep_dim
assert len(env.observation_space.shape) == 3 and env.observation_space.shape[-1] == 3
obs_shape = self.observation_space.shape[:2]
if self.keep_dim:
self.observation_space = Box(low=0, high=255, shape=(obs_shape[0], obs_shape[1], 1), dtype=np.uint8)
else:
self.observation_space = Box(low=0, high=255, shape=obs_shape, dtype=np.uint8)
def observation(self, observation):
import cv2
observation = cv2.cvtColor(observation, cv2.COLOR_RGB2GRAY)
if self.keep_dim:
observation = np.expand_dims(observation, -1)
return observation
2.3、ResizeObservation
的类
、gym
库中ObservationWrapper
的一个子类。这个类的主要功能是调整环境观测值(observation)的大小,通常用于图像数据,以适应特定的输入要求,如深度学习模型的输入尺寸。
初始化:
ResizeObservation
的构造函数接收一个环境env
和一个目标形状shape
。shape
可以是一个整数,表示期望的正方形尺寸,也可以是一个元组,表示宽度和高度。基于shape
,它会重新定义观测空间(observation_space
),使其匹配新的大小。定义观测空间:在构造函数中,
ResizeObservation
会检查shape
的类型并将其转换为一个元组。之后,它会根据新的形状和原有观测空间的其他维度(例如颜色通道)来定义一个新的观测空间。这里,Box
类创建了一个表示观测空间的新范围,其中低限为0,高限为255,数据类型为np.uint8
。观察方法:
observation
方法是实际进行图像尺寸调整的地方。它接收一个观测值(通常是图像),使用transform.resize
函数将其调整到所需的大小。由于transform.resize
返回的是浮点数,它需要将结果乘以255并将类型转换回np.uint8
,以恢复到图像数据常见的整数范围。
这种封装的ResizeObservation
类可以很容易地集成到任何基于gym
的环境中,以对图像观测值进行预处理。这对于深度学习模型尤其重要,因为它们往往需要固定大小的输入,而原始环境的观测值可能具有不同的尺寸。通过使用ResizeObservation
,可以确保所有的观测值都符合模型的输入规格,从而简化训练流程。
2.4、TransformObservation类
在深度学习中,特别是卷积神经网络(CNNs),输入数据经常需要被归一化到0到1的范围内,这样可以加快训练速度,提高模型性能,并且有助于数值稳定性。这是因为神经网络的权重和偏置通常是通过梯度下降等优化算法进行更新的,而这些算法在输入数据尺度统一时表现更好。
这里的lambda x: x / 255.
就是一个简单的匿名函数,它接收一个参数x
(即环境的观测值),并返回x
除以255的结果。在图像处理中,像素值通常在0到255之间,所以这个操作会将每个像素值转换到0到1的区间内。
将环境包装在TransformObservation
中后,每当智能体接收到观测值时,这些值都会自动经过这个转换。这确保了所有输入到智能体的数据都在一个合适的范围内,有利于后续的处理和学习。
2.5、FrameStack
类
gym
库中的一个封装器(Wrapper),用于在连续的时间步中堆叠多个观测值(frames)。这个技术在处理序列数据,特别是视频游戏或视觉输入的环境中非常有用,因为它可以帮助智能体捕捉动态变化和运动方向,这是单个静态帧无法提供的信息。
堆叠帧:
FrameStack
使用一个双端队列(deque)来存储最近的num_stack
个观测值。每当环境产生一个新的观测值,它就会被添加到队列的一端,而队列另一端的最老的观测值则被移除,以维持固定的长度。更新观测空间:在初始化
FrameStack
时,它会根据堆叠帧的数量更新观测空间。如果原始的观测空间是一个形状为(h, w, c)
的Box
(例如,高度、宽度和颜色通道),堆叠n
个这样的观测值将产生一个新的观测空间,形状为(h, w, n*c)
或(n, h, w, c)
,具体取决于实现细节。LazyFrames:为了节省内存,
FrameStack
使用LazyFrames
来存储堆叠的观测值。LazyFrames
是一个特殊的容器,它延迟执行帧的堆叠操作,直到真正需要时才进行,这可以显著减少内存使用,特别是在处理高分辨率图像时。step 和 reset 方法:在
step
方法中,每当环境产生一个新的观测值,FrameStack
都会更新其内部的帧队列,并返回堆叠后的观测值。而在reset
方法中,它会填充队列,以便在环境的初始状态下也能返回堆叠的观测值。
使用FrameStack
可以显著提高在基于视觉的环境中训练智能体的效果,因为智能体可以从连续的帧中学习物体的运动和变化趋势,这对于理解动态环境至关重要。例如,游戏环境中,堆叠4帧通常是标准做法,这有助于智能体识别玩家角色的运动方向和速度。