github代码地址: https://github.com/yanpanlau/Keras-FlappyBird
这是一个使用keras实现的DQN算法玩FlappyBird的算法。
代码结构非常简单,除了游戏相关代码,与算法相关的一共只有两个函数。
buildmodel() 和 trainNetwork()
其中,buildmodel()定义了一个卷积神经网络(CNN),该神经网络使用了灰度图做输入,但是连续拼接4帧动画,也就是让CNN可以知道小鸟当前的速度信息。
网络最终输出为两个神经元,分别表示两个动作(什么都不做 / 跳一下)对应的q值。在游戏的每一步,我们选取q值更高的动作作为agent实际执行的动作。为了让游戏尽可能得到高分,我们的目标则是让CNN逼近真实的状态-动作值函数。
trainNetwork()是该算法的重点部分
首先,定义了一个列表。
D = deque()
我们使用这个列表来储存所有曾经经历过的转移模型 (St,At)→(St+1,Rt)。St为当前状态,At为当前执行动作,St+1为下一状态,Rt为当前动作带来的奖励。
在刚开始的时候,先让小鸟随机执行动作,并把所有的状态转移模型及奖励存入D。
达到指定次数之后,开始一边从D中随机采样训练神经网络(更新CNN参数),一边利用最新的CNN参数输出来指导小鸟行为,并把最新的转移模型存入D,这就是记忆回放机制。为了让动作依然具有一定随机性,定义了一个值epsilon来决定动作由网络指导还是随机采样。具体代码如下:
if random.random() <= epsilon:
print("----------Random Action----------")
action_index = random.randrange(ACTIONS)
a_t[action_index] = 1
else:
q = model.predict(s_t) #input a stack of 4 images, get the prediction
max_Q = np.argmax(q)
action_index = max_Q
a_t[max_Q] = 1
之所以要使用回放,是因为连续的状态S具有高度的相关性,如果总是使用最新的环境交互数据来训练会导致CNN很不稳定(个人认为这和神经网络训练中所谓的遗忘灾难是同一个问题),如果使用回放随机采样,则可以消除该不稳定性,让训练变得平滑。
CNN的训练只有5行代码比较重要:
minibatch = random.sample(D, BATCH) #1、从历史经验随机采样
inputs = np.zeros((BATCH, s_t.shape[1], s_t.shape[2], s_t.shape[3])) #32, 80, 80, 4
targets = np.zeros((inputs.shape[0], ACTIONS)) #32, 2
#Now we do the experience replay
for i in range(0, len(minibatch)):
state_t = minibatch[i][0]
action_t = minibatch[i][1]
reward_t = minibatch[i][2]
state_t1 = minibatch[i][3]
terminal = minibatch[i][4]
# if terminated, only equals reward
inputs[i:i + 1] = state_t #I saved down s_t
targets[i] = model.predict(state_t) # Hitting each buttom probability #2、预测的Q(St,At)值
Q_sa = model.predict(state_t1)#3、预测Q(St+1,At+1)
if terminal:
targets[i, action_t] = reward_t
else:
targets[i, action_t] = reward_t + GAMMA * np.max(Q_sa) #4、Rt + gamma*Qt+1 作为标签
# targets2 = normalize(targets)
loss += model.train_on_batch(inputs, targets) #5、训练CNN
可以看出来,这其实是一个有监督训练,大部分情况下(游戏没有结束),我们使用St作为输入,理论上来说,应该用Rt + γ*Rt+1 + γ^2 Rt+2 + ... 作为CNN标签,但需要注意的是,实际上我们并不知道后续的Rt+k,如果要知道真实Rt+1的话,需要继续执行At+1,以此类推,这就变成了一个无限递归的过程,理论证明,后续累计奖励等于γQt+1——这就是著名的贝尔曼方程。
贝尔曼方程:
Qt = Rt + γRt+1 + γ^2*Rt+2 + γ^3*Rt+3 = ...
= Rt + γ*Qt+1
一些思考:
1、这里CNN应该是一个 状态-行为值函数Q(St,At),但是神经网络输入只有S,乍一看具有一些迷惑性,其实因为输出限定了各个维度代表对应的动作(这里只有两个动作,所以两个维度),同时输出了q(St, a1_t), q(St, a2_t)...,因此本质上还是Q(St,At)。
2、实际执行训练的过程中,会发现小鸟非常容易在很长一段时间内连第一个柱子都过不去,这里可以考虑一些优化手段来获取更加高质量的样本。比如随机初始化大量小鸟同时运行,仅仅保留分数较高的作为样本加入记忆D。同样,当记忆容量打到上限时,可以优先删除R较低的样本。
3、对每个样本可以增加采样权重,如果奖励更高,则被采样几率更大一些。
欢迎加入keras/tensorflow深度学习qq群:272437322
网友评论