6.3 Keras-rl常用对象

1.记忆体SequentialMemory

在DQN中需要将某一时刻的当前状态、动作、下一个状态和奖励等保存在记忆中。在Keras-rl中使用SequentialMemory作为记忆体。通常创建SequentialMemory对象指定两个参数,分别是:

·limit,记忆体大小,当超过记忆体大小时会按照先进先出的原则丢弃。

·window_length,窗口长度,通常设置为1。

2.选择策略Policy

Keras-rl的选择策略主要用于选择动作,常见的包括以下几种:

·EpsGreedyQPolicy,∈-贪婪算法,且∈为固定值。

·GreedyQPolicy,贪婪算法。

案例6-1:在Keras-rl下使用SARSA算法处理CartPole问题

我们尝试使用Keras-rl的SARSA算法来处理这个问题,本例代码在本书对应的GitHub的code/sarsa-cartpole-v0.py文件。CartPole问题的动作空间是离散型,状态空间是连续型,非常适合使用SARSA算法。

1.初始化环境

初始化CartPole-v0环境,设置随机数种子,获取当前环境的动作空间大小:


env = gym.make('CartPole-v0')
np.random.seed(0)
env.seed(0)
nb_actions = env.action_space.n

2.构造深度神经网络

常见的深度学习网络包括多层感知机(MLP),卷积神经网络(CNN)以及循环神经网络(RNN),本例中使用最基本的MLP,其结构如图6-2所示。使用Flatten层将输入层多维数据压成一个一维数据,然后接着是两个节点数均为24的隐藏层,输入层、隐藏层和输出层都是用全连接,其中输入层和隐藏层、隐藏层之间的激活函数都是用relu,隐藏层和输出层之间的激活函数是用linear函数。输出层的节点个数与动作空间的大小一致。代码如下:


model = Sequential()
model.add(Flatten(input_shape=(1,) + env.observation_space.shape))
model.add(Dense(24,activation='relu'))
model.add(Dense(24, activation='relu'))
model.add(Dense(nb_actions, activation='linear')

图6-2 MLP结构图

3.创建SARSAAgent

创建记忆体,记忆体的大小为2000,创建贪婪算法EpsGreedyQPolicy对象:


memory = SequentialMemory(limit=2000, window_length=1)
policy = EpsGreedyQPolicy()

创建SARSAAgent,使用之前构造的MLP,编译MLP,优化器使用Adam,评估器使用mae。测试阶段使用 贪婪算法,测试阶段使用默认的贪婪算法:


sarsa = SARSAAgent(model=model, nb_actions=nb_actions, nb_steps_warmup=10, policy=policy)
sarsa.compile(Adam(lr=1e-2), metrics=['mae'])

4.训练和测试

训练20000步,打印详细调试信息。测试5次,可视化效果:


sarsa.fit(env, nb_steps=20000, visualize=False, verbose=2)
sarsa.test(env, nb_episodes=5, visualize=True)

输出结果如下,经过20000步的训练,耗时81秒,测试5次,奖励稳定在200,持续时间也达到了200步:


done, took 80.718 seconds
Testing for 5 episodes ...
Episode 1: reward: 200.000, steps: 200
Episode 2: reward: 200.000, steps: 200
Episode 3: reward: 200.000, steps: 200
Episode 4: reward: 200.000, steps: 200
Episode 5: reward: 200.000, steps: 200

案例6-2:在Keras-rl下使用DQN算法处理CartPole问题

我们尝试使用Keras-rl的DQN算法来处理这个问题,本例代码在本书对应的GitHub的code/dqn-cartpole-v1.py文件。CartPole问题的动作空间是离散型,状态空间是连续型,也非常适合使用DQN算法。

1.初始化环境

初始化CartPole-v0环境,设置随机数种子,获取当前环境的动作空间大小:


env = gym.make('CartPole-v0')
np.random.seed(0)
env.seed(0)
nb_actions = env.action_space.n

2.构造深度神经网络

构造方式与上例相同,不再赘述。

3.创建DQNAgent

创建记忆体,记忆体的大小为2000,创建 贪婪算法EpsGreedyQPolicy对象:


memory = SequentialMemory(limit=2000, window_length=1)
policy = EpsGreedyQPolicy()

创建DQNAgent,使用之前构造的MLP,编译MLP,优化器使用Adam,评估器使用mae。测试阶段使用 贪婪算法,测试阶段使用默认的贪婪算法:


dqn = DQNAgent(model=model, nb_actions=nb_actions, memory=memory, nb_steps_warmup=10,
        target_model_update=1e-2, policy=policy)
dqn.compile(Adam(lr=1e-3), metrics=['mae'])

4.训练和测试

训练20000步,打印详细调试信息。测试5次,可视化效果:


dqn.fit(env, nb_steps=20000, visualize=False, verbose=2)
dqn.test(env, nb_episodes=5, visualize=True)

输出结果如下,经过2万步的训练,耗时110秒,测试5次,奖励稳定在200,持续时间也达到了200步:


done, took 110.171 seconds
Testing for 5 episodes ...
Episode 1: reward: 200.000, steps: 200
Episode 2: reward: 200.000, steps: 200
Episode 3: reward: 200.000, steps: 200
Episode 4: reward: 200.000, steps: 200
Episode 5: reward: 200.000, steps: 200

案例6-3:在Keras-rl下使用DQN算法玩Atari游戏

1976年Atari公司在美国推出了Atari 2600游戏机(见图6-3),是史上第一部真正意义上的家用游戏主机系统。Atari 2600游戏机基本上可以称得上是现代游戏机的始祖。这套游戏机在其长达170个月的生命周期中一共售出了3000万台。用现在的眼光来看Atari 2600的硬件机能实在不敢恭维,但是在当年显然是非常强悍,早期采用1.19 MHz MOS 8位元6507处理器,后期升级到2 MHz 6502处理器。支持160×192分辨率屏幕,最高128色,当然,还有主机上有128 bytes的RAM和6 KB的ROM内存。游戏盘每个售价25美元,容量4 KB,不过通过技术手段可以使卡带达到10 KB的容量。正是这样的一台主机创立了现在上千亿美元的家用游戏机产业。

OpenAI Gym支持了常见的几种Atari 2600游戏:

·Pong,Atari第一款游戏(见图6-4)。

图6-3 Atari 2600游戏机

图6-4 Pong游戏

·Breakout,打砖块游戏(见图6-5)。

图6-5 Breakout游戏

·SpaceInvaders,太空入侵者游戏(见图6-6)。

图6-6 SpaceInvaders游戏

·Seaquest,海洋争霸游戏(见图6-7)。

图6-7 Seaquest游戏

·BeamRider,激光炮游戏(见图6-8)。

图6-8 BeamRider游戏

1.Pong环境简介

我们以最经典的Pong游戏为例介绍如何使用Keras-rl在OpenAI Gym下玩Atari游戏。如图6-4所示,在Pong游戏中,屏幕两侧分别有两个弹球板,其中左侧的是游戏机控制,左侧是玩家控制,游戏机使用弹球板将弹球弹给右侧的玩家,玩家需要上下移动弹球板来接住弹球,并将球反弹给游戏机,谁没接住,对方得一分,以规定时间内分数最高者为赢家。在OpenAI Gym下,如图6-9所示,玩家是DQNAgent,环境为Pong-v0。DQNAgent根据当前的状态,即当前的游戏屏幕的截屏图片,根据一定策略选择对应的动作执行;Pong-v0根据动作反馈对应的得分以及下一个状态对应的游戏屏幕的截屏图片,这样对应的运作。需要特别说明的是,这里的动作空间根据移动的程度细化成了6个动作,而不是简单的向上或者向下。

图6-9 DQNAgent与Pong-v0交互原理图

2.初始化环境

本例代码在GitHub的code/dqn_atari-v0.py文件。初始化Pong-v0环境,设置随机数种子,获取当前环境的动作空间大小:


env = gym.make(‘Pong-v0’)
np.random.seed(123)
env.seed(123)
nb_actions = env.action_space.n

3.图像处理

由于Pong-v0环境中的状态都是用当时的游戏的图像截屏图片,所以这里需要简单介绍一下图像处理方面的知识。

Pong-v0环境中图像都以RGB模式提供,图像分辨率为84×84。在Python中图像处理的入门库为PIL,我们这里就以PIL为例介绍如何处理图像。

PIL中有几种图像模式,所谓模式就是图像的类型和像素的位宽,当前支持如下模式 [1]

·1,1位像素,表示黑和白,但是存储的时候每个像素存储为8 bit。

·L,8位像素,表示黑白程度的灰度图像。

·P,8位像素,使用调色板映射到其他模式。

·RGB,3×8位像素,为真彩色。

·RGBA,4×8位像素,有透明通道的真彩色。

·CMYK,4×8位像素,颜色分离。

·YCbCr,3×8位像素,彩色视频格式。

·I,32位整型像素。

·F,32位浮点型像素。

我们后面会遇到的是1、L和RGB模式。我们以小猪佩琪和乔治的图片(见图6-10)为例进行说明。

图6-10 小猪佩琪和乔治原始图片(RGB模式)

使用PIL加载该图片,打印对应的模式和图片大小,分别为RGB和1024×768:


from PIL import Image
file_name="pig.jpeg"
pig=Image.open(file_name)
print pig.mode
print pig.size

将该图片转换成L模式,即灰度图像模式,如图6-11所示,代码如下:


pig_L=pig.convert("L")
pig_L.save("pig-L.jpeg")

图6-11 小猪佩琪和乔治图片(L模式)

将该图片转换成1模式,如图6-12所示,该图片与灰度图像相比,区别是只有黑与白:


pig_1=pig.convert("1")
pig_1.save("pig-1.jpeg")

在Pong-v0环境中,图片的颜色不影响决策下一步的动作,所以可以把图像转换成灰度图像,并最终把图像转换成一个字节数组。代码如下:


def process_observation(self, observation):
    img = Image.fromarray(observation)
    img = img.resize(INPUT_SHAPE).convert('L') 
    processed_observation = np.array(img)
    return processed_observation.astype('uint8')

4.构造深度神经网络

处理图像数据时,通常我们会使用卷积神经网络(CNN)。CNN通过共享参数和池化大大减少计算量,并自动地从低级特征中提取高级特征。CNN的结构如图6-13所示,输入层结点数与图像大小相同,然后是3个卷积层:第一个卷积层32个结点,卷积大小为8×8;步长为4×4;第二个卷积层64个结点,卷积大小为4×4,步长为2×2;第三个卷积层64个结点,卷积大小为3×3,步长为1×1。每层的激活函数都是relu。然后是一个全连接,结点数为512,最后连接输出层,对应输出层节点数为6,与动作空间的大小保持一致。这里需要强调的是,我们的Keras底层使用的是TensorFlow,TensorFlow处理图像时,返回的RGB顺序和正常定义的RGB顺序不一样,需要使用Permute函数转换。代码如下:

图6-12 小猪佩琪和乔治图片(1模式)


input_shape = (WINDOW_LENGTH,) + INPUT_SHAPE
model = Sequential()
model.add(Permute((2, 3, 1), input_shape=input_shape))
model.add(Convolution2D(32, 8, 8, subsample=(4, 4)))
model.add(Activation('relu'))
model.add(Convolution2D(64, 4, 4, subsample=(2, 2)))
model.add(Activation('relu'))
model.add(Convolution2D(64, 3, 3, subsample=(1, 1)))
model.add(Activation('relu'))
model.add(Flatten())
model.add(Dense(512))
model.add(Activation('relu'))
model.add(Dense(nb_actions))
model.add(Activation('linear'))

5.创建DQNAgent

创建记忆体,记忆体的大小为100万,创建贪婪算法EpsGreedyQPolicy对象:


memory = SequentialMemory(limit=1000000, window_length=WINDOW_LENGTH)
policy = EpsGreedyQPolicy()

图6-13 Pong游戏中使用的CNN结构

创建DQNAgent,使用之前构造的CNN,编译CNN,优化器使用Adam,评估器使用mae。测试阶段使用贪婪算法,测试阶段使用默认的贪婪算法:


dqn = DQNAgent(model=model, nb_actions=nb_actions, policy=policy, memory= memory,
               processor=processor, nb_steps_warmup=50000, 
             gamma=.99,    target_model_update=10000,
               train_interval=4, delta_clip=1.)
dqn.compile(Adam(lr=.00025), metrics=['mae'])

6.训练和测试

训练150万步,打印详细调试信息。测试10次,可视化效果。将日志和训练得到的模型持久化保存。由于训练需要花费的时间比较长,需要设置checkpoint点,便于异常中断学习时可以继续学习下去,代码如下:


weights_filename = 'dqn_{}_weights.h5f'.format(args.env_name)
checkpoint_weights_filename = 'dqn_' + args.env_name + '_weights_{step}.h5f'
log_filename = 'dqn_{}_log.json'.format(args.env_name)
callbacks = [ModelIntervalCheckpoint(checkpoint_weights_filename, interval= 250000)]
callbacks += [FileLogger(log_filename, interval=100)]
dqn.fit(env, callbacks=callbacks, nb_steps=1500000, log_interval=10000)
dqn.save_weights(weights_filename, overwrite=True)
dqn.test(env, nb_episodes=10, visualize=True)

[1] http://blog.csdn.net/icamera0/article/details/50647465