用 TensorFlow2.0 配合 keras 实现了一个 DeepQNetwork,玩了一下 CartPole
完整的代码在这里:Github: Aeonni/MyGarage (opens new window)
强化学习介绍
B站有一大堆视频,自己去看~
前期准备
安装一些依赖库
注意:Tensorflow2.0 目前还是 Alpha 版本,最好装在Virtualenv中,以下两篇文章或许可以帮到你:
Import
import sys, os
import numpy as np
import tensorflow as tf
import tensorflow.keras.layers as layers
import tensorflow.keras.losses as losses
import tensorflow.keras.optimizers as optim
from collections import deque
import random
import gym
tf.__version__, tf.executing_eagerly()
('2.0.0-dev20190324', True)
游戏介绍:CartPole-v0
CartPole-v0
是一款让小车平衡木棒的游戏,小车可以左右运动(两个输入维度),对于每一次动作环境会给出四个维度的状态反馈。同时给出奖励值。
在这个游戏中,小车每撑过一帧就可以得到1点奖励,撑不过就是直接结束游戏,不会有负的奖励值。且只要玩到200帧游戏便会自动结束。
为此需要对网络以及训练步骤进行一部分的调整。
网络搭建
在网络搭建的过程中,参考了以下两篇文章:
所有的代码在 TF2.0 Alpha 环境下运行。
这里主要叙述我对于以上两个模型修改的部分:
- DQN 中的两个网络具有相同的结构与不同的权重,在编写模块时,我将定义神经网络结构的部分代码打包成函数
__define_network
这样在__build_network
函数中只要调用两次就可以实现两个网络的生成,而不需要把同一份代码写两遍。 - 模型的 Memory 使用了
collections
库中的deque
来实现。 - 依然是对 Memory 的调整:将正记忆与负记忆按照一定比例分开存储、遗忘。
- q_target 的处理中,对于那些导致游戏结束的操作,直接赋值为 reward。
对于以上第三、四点调整的补充说明:
- 前文有提到说
CartPole-v0
不会有负的奖励值,对于网络来说就只有奖励多少的区别。 - 我们的目标是让这个小杆子长久平衡下去,但如果这么做,记忆都会变成正向的,这个时候网络就会“自负”“膨胀”“牛逼哄哄”,所以必须时刻牢记那些负面的记忆。
- 怀疑以上两点可以用 Baseline 算法解决,但是鉴于现在还不会,就先这么调整着,
到时候可以再水一篇博客。
class DQN_Model:
def __init__(self, num_actions, num_features, learning_rate=0.02,
reward_decay=0.95, e_greedy=0.95, replace_target_iter=500,
memory_size=5000, batch_size=32, e_greedy_increment=None,
output_graph=False, memory_neg_p = 0.5):
# ____define_some_parameters____
# *** 【参数保存】代码在此省略 ***
# ____Call_fn_build_network____
self.__build_network()
def __define_network(self, name):
inputs = layers.Input(shape=(self.num_features,))
x = layers.Dense(32, activation='sigmoid')(inputs)
x = layers.Dense(32, activation='sigmoid')(x)
return inputs, layers.Dense(self.num_actions, name=name+'_output')(x)
def __build_network(self):
# 构建eval/target网络,注意这个target层输出是q_next而不是,算法中的q_target
eval_inputs, self.q_eval = self.__define_network('Eval') # 具有最新的参数
target_inputs, self.q_next = self.__define_network('Target') # 被冻结的参数
self.targetNet = tf.keras.Model(target_inputs, self.q_next)
self.evalNet = tf.keras.Model(eval_inputs, self.q_eval, name = 'DQN_Eval_Net')
rmsprop = optim.RMSprop(lr=self.lr)
self.targetNet.compile(loss='mean_squared_error', optimizer=rmsprop, metrics=['accuracy'])
self.evalNet.compile(loss='mean_squared_error', optimizer=rmsprop, metrics=['accuracy'])
def do_target_replacement(self):
self.targetNet.set_weights(self.evalNet.get_weights())
print("!! Params has changed!")
def store_transition(self, s, a, r, s_, terminal):
if terminal:
self.memory_neg.append((s, a, r, s_, terminal))
else:
self.memory_pos.append((s, a, r, s_, terminal))
def replay_transition(self):
# *** 【记忆回放】代码在此省略 ***
def choose_action(self, obs):
# *** 【动作选择】代码在此省略,可直接参考莫凡代码 ***
def learn(self):
# ____Replace_Target_weights____
if self.learn_step_counter % self.replace_target_iter == 0:
self.do_target_replacement()
# ____Sample_Memory____
if len(self.memory_neg)+len(self.memory_pos) > self.batch_size:
s, eval_act, reward, s_, t = self.replay_transition()
# *** 【训练】代码在此省略,可直接参考我的完整代码 ***
训练
训练没什么好说的,就是对于这个游戏来说,因为坚持到 200 帧时游戏会自动停止,所以 200 帧时的 “Game Over” 不算是失败,并且还应该 Print 一个 'Good Job!' 来奖励一下~
env = gym.make('CartPole-v0')
model = DQN_Model(num_actions=2, num_features=4)
obs = env.reset()
step = 0
for times in range(10000):
obs = env.reset()
total_reward = 0
while True:
env.render()
action = model.choose_action(obs[None, :])
obs_, reward, terminal, _ = env.step(action)
total_reward += reward
if total_reward < 200:
model.store_transition(obs, action, reward, obs_, terminal)
else:
print('Good Job!')
if (step > 1000) and (step % 3 == 0):
model.learn()
obs = obs_
if terminal:
env.render()
# print(reward, terminal)
break
step += 1
print('Time: ', times, 'Gain_reward: ', total_reward, 'Steps: ', step)
if times%200 == 199:
model.evalNet.save_weights('./dqn.new.weights.h5')
效果
这个视频比较了区别两种记忆和不区分两种记忆的效果差异,右边的是区分之后的:
不过这个视频是在没有加上 200 帧时的 “Game Over” 不算是失败
的情况下截取的,所以不算是最好的状态,加上后的状态如下图(左边的是区分之后的):