R3 认识环境

强化学习中的环境就等同于深度学习或者数据挖掘课题的“数据”,强化学习通过与环境交互来产生数据,所以对环境的认知直接关系到最终结果的好坏,在很多强化学习的研究和竞赛里往往对环境的trick比算法的改进效果更为明显。

R3.1 (由浅入深)倒立摆环境(carplot)讲解

运行一个倒立摆环境(CartPole) 观察环境返回什么 环境的动作有哪些? #补一个倒立摆的GIF图

#创建倒立摆'CartPole-v0' env
import gym
env = gym.make('CartPole-v0')

#初始化游戏环境
env.reset()

从上面的返回可以看到我们在执行环境初始化\重置时env返回给我们了初始化后的环境状态为: [ 0.03749292, -0.03226631, 0.01609263, -0.04661368] 这四个数字组成的状态变量(state variables)分别含义如下:

0.03749292: 小车在轨道上的位置(position of the cart on the track) -0.03226631: 杆子与竖直方向的夹角(angle of the pole with the vertical) 0.01609263: 小车速度(cart velocity) -0.04661368: 角度变化率(rate of change of the angle)

#环境包含的动作有哪些?
print("env.action_space: ", env.action_space)

从结果来看动作空间为2,也就是说倒立摆这个环境只有两个动作可以操作,分别是0和1 (向左和向右)从倒立摆的动画不难理解,通过左右移动来保持倒立摆不倒。

#执行一个向左的操作
obj, reward, done, info = env.step(1) #1 向右 0向左
print("obj", obj)
print("reward", reward)
print("done", done)
print("info", info)

一个动作执行后,环境会返回四个变量(obj:新的状态(对照前面环境初始化的状态理解)、reward:指定该动作获得的奖励值(在游戏中的得分)、done:回合是否结束(你控制的小人是不是死了,对应回合结束)、info:额外信息(该游戏较简单,info为空))

#随机获取一个动作
action = env.action_space.sample()
print(action)

通过sample()函数可以快速得到一个随机动作,由于该游戏动作空间为2,所以sample得到的值为0或1

# Virtual display
from pyvirtualdisplay import Display

virtual_display = Display(visible=0, size=(1400, 900))
virtual_display.start()
#运行1000组随机动作
env = gym.make('CartPole-v0')
from gym import wrappers
env = wrappers.Monitor(env,"./", force=True)
env.reset()
for _ in range(1000):
    env.render() #服务器上无display,不支持render
    obj, reward, done, info = env.step(env.action_space.sample()) # take a random action
    if done:
        env.reset()
env.close()


# from IPython import display
# import matplotlib
# import matplotlib.pyplot as plt
# %matplotlib inline

# env = gym.make('CartPole-v0')
# env.reset()
# img = plt.imshow(env.render(mode='rgb_array')) # only call this once
# for _ in range(100):
#     img.set_data(env.render(mode='rgb_array')) # just update the data
#     display.display(plt.gcf())
#     display.clear_output(wait=True)
#     action = env.action_space.sample()
#     env.step(action)
import gym
env = gym.make('CartPole-v0')
env = wrappers.Monitor(env,"./gym-results")#, force=True
env.reset()
for _ in range(1000):
#     env.render() #服务器上无display,不支持render
    obj, reward, done, info = env.step(env.action_space.sample()) # take a random action
    if done:
        env.reset()
env.close()

R3.2 超级玛丽环境讲解

超级玛丽主要区别于倒立摆游戏的是超级玛丽的obj观测值(状态)为当前帧图片(像素),和人类玩超级玛丽一致,通过观察每一帧图像(大脑/模型)输出要执行的action

#创建env
from nes_py.wrappers import JoypadSpace
import gym_super_mario_bros
from gym_super_mario_bros.actions import SIMPLE_MOVEMENT

#借助包gym_super_mario_bros创建
env = gym_super_mario_bros.make('SuperMarioBros-v0')

注:SuperMarioBros—v 其中:

是{1,2,3,4,5,6,7,8}中的一个数字,表示世界 是{1,2,3,4}中的一个数字,表示一个世界中的阶段 是{0,1,2,3}中的一个数字,指定要使用的rom模式 0:标准ROM 1:降采样ROM 2:像素rom 3:矩形ROM

#初始化env
obj = env.reset()
print(obj.shape)

由输出可以看到超级玛丽的观测值变成了一张240*256的rgb图片

为了验证,我们可视化出来

import matplotlib.pyplot as plt
plt.imshow(obj)

#接下来看一下动作空间
print("env.action_space: ", env.action_space)

默认情况下, gym_super_mario_bros环境使用完整的NES操作空间256 离散动作。为了解决这个问题,gym_super_mario_bros.actions提供 三个操作列表(RIGHT_ONLY、SIMPLE_MOVEMENT和COMPLEX_MOVEMENT) 对于nes_py.wrappers.JoypadSpace包装器

#我们选用SIMPLE_MOVEMENT来看下是否满足我们的通关需求
env = JoypadSpace(env, SIMPLE_MOVEMENT)
print("env.action_space: ", env.action_space)

7个基本动作包含了常用的操作 如上下左右,跳跃,右+跳,左+跳。由此其实已经基本满足了常用的操作,而选择更多的动作反而会增加模型学习的难度。所以我们选择SIMPLE_MOVEMENT模式即可

#随机执行一个操作
obj, reward, done, info = env.step(1) #这里随机选择执行动作1
print("obj.shape", obj.shape)
print("reward", reward)
print("done", done)
print("info", info)

强化学习执行step动作的返回一般是标准的,所以这里的返回同前面的倒立摆,动作执行后,环境返回四个变量(obj:新的观测值(一帧rgb图片)、reward:执行该动作获得的奖励值(在游戏中的得分)、done:回合是否结束(你控制的小人是不是死了,对应回合结束)、info:额外信息(比如’life’: 2,剩余2条命等)) 详细字段解释,参见https://www.cnpython.com/pypi/gym-super-mario-bros

##留一个空位 看下是否讲解reward

R3.3常用env Wrapper技巧

#先重新引入下相关包,防止报错
import gym_super_mario_bros
from gym.spaces import Box
from gym import Wrapper
from nes_py.wrappers import JoypadSpace#BinarySpaceToDiscreteSpaceEnv
from gym_super_mario_bros.actions import SIMPLE_MOVEMENT, COMPLEX_MOVEMENT, RIGHT_ONLY
import cv2
import numpy as np
import subprocess as sp
R3.3.1 rgb图像转灰度图

想象一下你在玩超级玛丽时如果把彩色图像换成灰度图,其实对你的操作并没有多大影响(只要能看出来障碍物即可判断路线和动作),反而在模型训练中,rgb图像对算力和训练时间的要求会成倍增长,所以综合考虑咱们转换成灰度图才输入网络

#借助cv2即(opencv)包快速转换COLOR_RGB2GRAY
def process_frame(frame):
    if frame is not None:
        frame = cv2.cvtColor(frame, cv2.COLOR_RGB2GRAY) #图像转换
        frame = cv2.resize(frame, (84, 84))[None, :, :] / 255. #裁剪合适大小,并归一化
        return frame
    else:
        return np.zeros((1, 84, 84))
R3.3.2 SkipFrame

由于超级玛丽等游戏开发是面向玩家的(人),而非电脑,所以面向人类通关设计时,很多游戏帧是被放慢了,比如执行一个action并不会立刻得到reard而是在接下来的几帧里才逐渐成效,换个通俗的说法,其实这么快速的游戏帧对我们并不需要,我们只需要每秒能看到几帧就足以通关了,所以我们根据经验,每四帧只取一帧即可

class CustomSkipFrame(Wrapper):
    def __init__(self, env, skip=4):
        super(CustomSkipFrame, self).__init__(env)
        self.observation_space = Box(low=0, high=255, shape=(4, 84, 84))
        self.skip = skip

    def step(self, action):
        total_reward = 0
        states = []
        state, reward, done, info = self.env.step(action)
        for i in range(self.skip):
            if not done:
                state, reward, done, info = self.env.step(action)
                total_reward += reward
                states.append(state)
            else:
                states.append(state)
        states = np.concatenate(states, 0)[None, :, :, :]
        return states.astype(np.float32), reward, done, info

    def reset(self):
        state = self.env.reset()
        states = np.concatenate([state for _ in range(self.skip)], 0)[None, :, :, :]
        return states.astype(np.float32)
R3.3.2 CustomReward

强化学习的优化目标必须是可量化的,所以在游戏里我们直接的优化目标就是最大化reward,但是很多时候游戏直接设定的reward并不完全切合我们的实际目的(比如通关),或者在某个特定场景下(关卡下)不合适,所以越是复杂的游戏场景,越是需要自定义reward来进行修正。

这里我们做了几个小优化如下:
1.reward += (info["score"] - self.curr_score) / 40.
原来的reward仅包含了对“离终点更近”的奖励和“时间消耗”、”死掉“的惩罚
为了让游戏更好玩,我们添加了info["score"],包含了对获得技能、金币的奖励,但不是重点,为了不影响整体要通关的属性,弱化他
2.if done:
            if info["flag_get"]:
                reward += 50
            else:
                reward -= 50
我们对回合结束时到达终点和未达到的奖励和惩罚进行放大,激励agent更快速的到达终点
3.这里仅仅是对reward修改的一些示例,后面自己在实战时可以自己根据实际情况进行定义,比如当agent有时陷入一个错误的路线卡住时,可以添加一个缓冲区让agent学会后退等
class CustomReward(Wrapper):
    def __init__(self, env=None):
        super(CustomReward, self).__init__(env)
        self.observation_space = Box(low=0, high=255, shape=(1, 84, 84))
        self.curr_score = 0

    def step(self, action):
        state, reward, done, info = self.env.step(action)
        state = process_frame(state)
        reward += (info["score"] - self.curr_score) / 40.
        self.curr_score = info["score"]
        if done:
            if info["flag_get"]:
                reward += 50
            else:
                reward -= 50
        return state, reward / 10., done, info

    def reset(self):
        self.curr_score = 0
        return process_frame(self.env.reset())
    
#至此,我们完成了超级玛丽环境的自定义,封装如下:
def create_train_env(world, stage, action_type, output_path=None):
    env = gym_super_mario_bros.make("SuperMarioBros-{}-{}-v0".format(world, stage))
    if action_type == "right":
        actions = RIGHT_ONLY
    elif action_type == "simple":
        actions = SIMPLE_MOVEMENT
    else:
        actions = COMPLEX_MOVEMENT
    env = JoypadSpace(env, actions)
    env = CustomReward(env)
    env = CustomSkipFrame(env)
    return env, env.observation_space.shape[0], len(actions)

#测试一下
custom_env = create_train_env(1,1,'simple')
print(custom_env)

参考来源

  1. rl_course2

更多推荐