深度强化学习(DQN-Deep Q Network)之应用-Flappy Bird

          深度强化学习(DQN-Deep Q Network)之应用-Flappy Bird

                         本文系做者原创,转载请注明出处:http://www.javashuo.com/article/p-okusdevh-gy.htmlhtml

目录

1.达到的目的

2.思路

   2.1.强化学习(RL Reinforcement Learing)

   2.2.深度学习(卷积神经网络CNN)

3.踩过的坑

4.代码实现(python3.5)

5.运行结果与分析

 


 

1.达到的目的

游戏场景:障碍物以必定速度往左前行,小鸟拍打翅膀向上或向下飞翔来避开障碍物,若是碰到障碍物,游戏就GAME OVER!python

目的:小鸟经过训练,可以自动识别障碍物,作出正确的动做(向上或向下飞翔)。数组

 

 

2.思路

小鸟飞翔的难点是如何准确判断下一步的动做(向上或向下)?而这正是强化学习想要解决的问题。由于上一节案例网格的全部状态(state)数目是比较小的(16),因此能够经过遍历全部状态,计算全部状态的回报,生成 Q-Table(记录全部状态的价值)。可是本节的应用场景有所不一样,它的状态是图片中的像素,若是图片大小是 84 * 84,batch = 4,每一个像素大小在[0,255]范围内,有 256 种可能(256 个状态),那么最终 Q-Table 大小是网络

       

数据计算量是很是庞大的。这里咱们采用强化学习 + 深度学习(卷积神经网络),也就是 DQN(Deep Q Network)。app

卷积神经网络决策目的是预测当前状态全部行为的回报(Q-value)->目标预测值()以及参数的更新;dom

强化学习的目的是根据马尔科夫决策过程以及贝尔曼价值函数计算出当前状态全部行为的回报 ->目标真实值(ide

整张图片做为一个状态(由于小鸟不关心是像素仍是图片,它只关心它下一步动做的方向),4张图片就是 4 个状态,且这 4 张图片在时间上是连续的。将全部状态(States:80*80*4)以及行为(Actions:1*2)做为卷积神经网络的输入值,卷积神经网络输出为当前状态的全部行为的价值(1*2),结构以下图函数

 

2.1 强化学习

贝尔曼最优方程以下(当前状态全部行为价值 = 当前即时奖励 + 下一状态全部行为的价值)post

  

代码实现 学习

 

1   readout_j1_batch = sess.run(readout, feed_dict = {s : s_j1_batch})
2             for i in range(0, len(minibatch)):
3                 terminal = minibatch[i][4]
4                 # if terminal, only equals reward
5                 if terminal:  # 碰到障碍物,终止
6                     y_batch.append(r_batch[i])
7                 else: # 即时奖励 + 下一阶段回报
8                     y_batch.append(r_batch[i] + GAMMA * np.max(readout_j1_batch[i]))

minibatch保存了一个batch(32)下当前状态(s_j_batch)、当前行动(a_batch)、当前状态的即时奖励(r_batch)、当前状态下一时刻的状态(s_j1_batch)。

将当前状态下一时刻的状态(s_j1_batch)做为网络模型输入参数,就能获得下一状态(相对当前状态)全部行为的价值readout_j1_batch),而后经过贝尔曼最优方程计算获得当前状态的Q-value。

你们可能会有这样的疑问:为何当前状态价值要经过下一个状态价值获得,常规来讲都是上一状态价值来获得?

贝尔曼最优方程充分体现了尝试这一核心思想,计算下一个状态价值是为了更新当前状态价值,从而找到最优状态行为。

2.2 深度学习

在输入数据进入神经网络结构以前,须要对图片数据进行预处理,从而减小运算量。

须要安装opencv库:pip install opencv-python,若是下载较慢,能够用国内镜像代替

pip install opencv-python -i http://pypi.douban.com/simple --trusted-host pypi.douban.com。

图片灰度处理:将彩色图片转变为灰度图片,图片大小设置成(80 * 80);

 

 x_t = cv2.cvtColor(cv2.resize(x_t, (80, 80)), cv2.COLOR_BGR2GRAY)

二值化:设置图片像素阈值为 1,大于 1 的像素值更新为 255(白色),反之为 0(黑色)。

 ret, x_t = cv2.threshold(x_t,1,255,cv2.THRESH_BINARY)

获取连续帧(4)图片:复制当前帧图片 -> 堆积成4帧图片 -> 将获取到得下一帧图片替换当前第4帧,如此循环就能保证当前的batch图片是连续的。

s_t = np.stack((x_t, x_t, x_t, x_t), axis=2)
s_t1
= np.append(x_t1, s_t[:, :, :3], axis=2)

卷积神经网络模型

这里采用了3个卷积层(8*8*4*32, 4*4*32*64,3*3*64*64),3个池化层,4个Relu激活函数,2个全链接层,具体以下图

(建议对照图看代码,注意数据流的变化)

 

注意:要注意每一个卷积层的Stride,由于padding = "SAME",与输入图片卷积后数据宽,高 = 输入图片宽,高/Stride。

好比,输入图片数据与第一个卷积层(8*8*4*32)卷积后,图片数据宽,高 = (80,80)/4 = (20,20),其余层卷积依次类推。

tensorboard可视流程图(具体生成操做步骤见 深度学习之卷积神经网络(CNN)详解与代码实现(二)

图片可能不是很清楚,在图片位置点击鼠标右键->在新标签页面打开图片,就能够放缩图片了。

3.踩过的坑

1.必定要弄明白深度强化学习的输入和输出。 

强化学习的核心思想是尝试,深度学习的核心思想是训练。经过不断的将预测值和真实值的残差计算,不断的更新训练模型的参数,使残差值愈来愈小,最终收敛于一个稳定值,从而获得最佳的训练参数模型。

这里的预测值是经过深度学习获得,而真实值是经过强化学习获得,因此才有了深度强化学习的概念(DQN-Deep Q Network)。

卷积神经网络前向传播输入:4帧连续图片做为不一样的状态States;

卷积神经网络前向传播输出:readout(2个不一样的方向对应的价值);

卷积神经网络反向传播(经过损失函数获取损失,计算梯度,更新参数)输入:

i.y_batch(32, 2):经过强化学习获得的真实目标值[32 表示神经网络训练时每次批量处理数目,2表示Action不一样方向对应的价值 ];

ii.a_batch(32, 2):每一个行动的不一样方向,在训练时更新步骤:初始化都为0 ->深度学习(卷积神经网络)输出readout_t(1, 2)-> 找到输出价值最大的索引 ->将a_batch中action相同索引置为1(表示最优价值的方向),达到更新得目的。

iii.s_j_batch(32, 80, 80, 4):下一个连续4帧,每一组是4帧,批量处理32组。

2.不要陷入常规的思惟模式。

通常常规的思惟模式是 A + B => C,这个 C 通常在计算或设计以前,在咱们脑海中会计算出来,可以具体化。可是深度学习是打破这一常规思惟模式的,它可以经过训练自发的学习,获取内在知识或规则。

以本节为例,在咱们脑海中,老是想着下面几个问题

1. 为何深度学习的结果就是行为的各个方向的价值,而不是其余?

解答:这是根据真实目标值决定的,卷积神经网络的要求是最后的输出值必定要跟真实目标值大小相同。损失函数计算损失,而后更新各个网络层的参数,不停的循环,使输出无限的逼近真实值,稳定后获取模型。

2. 在上一节强化学习时都是人为指定了方向的映射(0=up, 1=right, 2=down, 3=left),为何深度强化学习不须要指定,它本身就能识别?

解答:当前一组帧和下一组帧之间在时间上是连续的,小鸟的每一个动做在时间上也是连续的,经过深度学习后获取的模型其实已经学会了游戏的内在规则,知道在当前状态的下一步动做的方向,因此不须要咱们人为指定,这正是深度学习的神奇之处。

4.代码实现(python3.5)

入口在代码最下端main,代码流程分为三个阶段:观察、探索、训练。由 OBSERVE 和 EXPLORE 设定

这也符合通常逻辑,先观察环境,而后再看看怎么飞。因此观察次数通常偏小,其实在探索时就已经在训练了,为何要分开呢?

分开的目的是考虑更通常的状况,使模型更准确。好比某个状态向上和向下的价值同样,以前都是以向上的价值来计算整个价值,在探索时,咱们就考虑向下的价值,而后来更新Q-Table。可是这种探索是随着模型的稳定,次数会愈来愈少。

工程结构图(整个工程代码可在百度网盘下载: https://pan.baidu.com/s/1faj-BHeYt14g3bNtrzsqXA 提取码: vxeb)

train.py

  1 #!/usr/bin/env python
  2 from __future__ import print_function
  3 
  4 import tensorflow as tf
  5 import cv2
  6 import sys
  7 sys.path.append("game/")
  8 try:
  9     from . import wrapped_flappy_bird as game
 10 except Exception:
 11     import wrapped_flappy_bird as game
 12 import random
 13 import numpy as np
 14 from collections import deque
 15 '''
 16 先观察一段时间(OBSERVE = 1000 不能过大),
 17 获取state(连续的4帧) => 进入训练阶段(无上限)=> action
 18 
 19 '''
 20 GAME = 'bird' # the name of the game being played for log files
 21 ACTIONS = 2 # number of valid actions 往上  往下
 22 GAMMA = 0.99 # decay rate of past observations
 23 OBSERVE = 1000. # timesteps to observe before training
 24 EXPLORE = 3000000. # frames over which to anneal epsilon
 25 FINAL_EPSILON = 0.0001 # final value of epsilon 探索
 26 INITIAL_EPSILON = 0.1 # starting value of epsilon
 27 REPLAY_MEMORY = 50000 # number of previous transitions to remember
 28 BATCH = 32 # size of minibatch
 29 FRAME_PER_ACTION = 1
 30 
 31 # GAME = 'bird' # the name of the game being played for log files
 32 # ACTIONS = 2 # number of valid actions
 33 # GAMMA = 0.99 # decay rate of past observations
 34 # OBSERVE = 100000. # timesteps to observe before training
 35 # EXPLORE = 2000000. # frames over which to anneal epsilon
 36 # FINAL_EPSILON = 0.0001 # final value of epsilon
 37 # INITIAL_EPSILON = 0.0001 # starting value of epsilon
 38 # REPLAY_MEMORY = 50000 # number of previous transitions to remember
 39 # BATCH = 32 # size of minibatch
 40 # FRAME_PER_ACTION = 1
 41 
 42 def weight_variable(shape):
 43     initial = tf.truncated_normal(shape, stddev = 0.01)
 44     return tf.Variable(initial)
 45 
 46 def bias_variable(shape):
 47     initial = tf.constant(0.01, shape = shape)
 48     return tf.Variable(initial)
 49 # padding = ‘SAME’=> new_height = new_width = W / S (结果向上取整)
 50 # padding = ‘VALID’=> new_height = new_width = (W – F + 1) / S (结果向上取整)
 51 def conv2d(x, W, stride):
 52     return tf.nn.conv2d(x, W, strides = [1, stride, stride, 1], padding = "SAME")
 53 
 54 def max_pool_2x2(x):
 55     return tf.nn.max_pool(x, ksize = [1, 2, 2, 1], strides = [1, 2, 2, 1], padding = "SAME")
 56 """
 57  数据流:80 * 80 * 4  
 58  conv1(8 * 8 * 4 * 32, Stride = 4) + pool(Stride = 2)-> 10 * 10 * 32(height = width = 80/4 = 20/2 = 10)
 59  conv2(4 * 4 * 32 * 64, Stride = 2) -> 5 * 5 * 64 + pool(Stride = 2)-> 3 * 3 * 64
 60  conv3(3 * 3 * 64 * 64, Stride = 1) -> 3 * 3 * 64 = 576
 61  576 在定义h_conv3_flat变量大小时须要用到,以便进行FC全链接操做
 62 """
 63 
 64 def createNetwork():
 65     # network weights
 66     W_conv1 = weight_variable([8, 8, 4, 32])
 67     b_conv1 = bias_variable([32])
 68 
 69     W_conv2 = weight_variable([4, 4, 32, 64])
 70     b_conv2 = bias_variable([64])
 71 
 72     W_conv3 = weight_variable([3, 3, 64, 64])
 73     b_conv3 = bias_variable([64])
 74 
 75     W_fc1 = weight_variable([576, 512])
 76     b_fc1 = bias_variable([512])
 77     # W_fc1 = weight_variable([1600, 512])
 78     # b_fc1 = bias_variable([512])
 79 
 80     W_fc2 = weight_variable([512, ACTIONS])
 81     b_fc2 = bias_variable([ACTIONS])
 82 
 83     # input layer
 84     s = tf.placeholder("float", [None, 80, 80, 4])
 85 
 86     # hidden layers
 87     h_conv1 = tf.nn.relu(conv2d(s, W_conv1, 4) + b_conv1)
 88     h_pool1 = max_pool_2x2(h_conv1)
 89 
 90     h_conv2 = tf.nn.relu(conv2d(h_pool1, W_conv2, 2) + b_conv2)
 91     h_pool2 = max_pool_2x2(h_conv2)
 92 
 93     h_conv3 = tf.nn.relu(conv2d(h_conv2, W_conv3, 1) + b_conv3)
 94     h_pool3 = max_pool_2x2(h_conv3)
 95 
 96     h_pool3_flat = tf.reshape(h_pool3, [-1, 576])
 97     #h_conv3_flat = tf.reshape(h_conv3, [-1, 1600])
 98 
 99     h_fc1 = tf.nn.relu(tf.matmul(h_pool3_flat, W_fc1) + b_fc1)
100     #h_fc1 = tf.nn.relu(tf.matmul(h_conv3_flat, W_fc1) + b_fc1)
101 
102     # readout layer
103     readout = tf.matmul(h_fc1, W_fc2) + b_fc2
104 
105     return s, readout, h_fc1
106 
107 def trainNetwork(s, readout, h_fc1, sess):
108     # define the cost function
109     a = tf.placeholder("float", [None, ACTIONS])
110     y = tf.placeholder("float", [None])
111     # reduction_indices = axis  0 : 列  1: 行
112     # 因 y 是数值,而readout: 网络模型预测某个行为的回报 大小[1, 2] 须要将readout 转为数值,
113     # 因此有tf.reduce_mean(tf.multiply(readout, a), axis=1) 数组乘法运算,再求均值。
114     # 其实,这里readout_action = tf.reduce_mean(readout, axis=1) 直接求均值也是能够的。
115     readout_action = tf.reduce_mean(tf.multiply(readout, a), axis=1)
116     cost = tf.reduce_mean(tf.square(y - readout_action))
117     train_step = tf.train.AdamOptimizer(1e-6).minimize(cost)
118 
119     # open up a game state to communicate with emulator
120     game_state = game.GameState()
121     # 建立队列保存参数
122     # store the previous observations in replay memory
123     D = deque()
124 
125     # printing
126     a_file = open("logs_" + GAME + "/readout.txt", 'w')
127     h_file = open("logs_" + GAME + "/hidden.txt", 'w')
128 
129     # get the first state by doing nothing and preprocess the image to 80x80x4
130     do_nothing = np.zeros(ACTIONS)
131     do_nothing[0] = 1
132     x_t, r_0, terminal = game_state.frame_step(do_nothing)
133     #cv2.imwrite('x_t.jpg',x_t)
134     x_t = cv2.cvtColor(cv2.resize(x_t, (80, 80)), cv2.COLOR_BGR2GRAY)
135     ret, x_t = cv2.threshold(x_t,1,255,cv2.THRESH_BINARY)
136     s_t = np.stack((x_t, x_t, x_t, x_t), axis=2)
137 
138     # saving and loading networks
139     tf.summary.FileWriter("tensorboard/", sess.graph)
140     saver = tf.train.Saver()
141     sess.run(tf.initialize_all_variables())
142     checkpoint = tf.train.get_checkpoint_state("saved_networks")
143     """
144     if checkpoint and checkpoint.model_checkpoint_path:
145         saver.restore(sess, checkpoint.model_checkpoint_path)
146         print("Successfully loaded:", checkpoint.model_checkpoint_path)
147     else:
148         print("Could not find old network weights")
149     """
150     # start training
151     epsilon = INITIAL_EPSILON
152     t = 0
153     while "flappy bird" != "angry bird":
154         # choose an action epsilon greedily
155         # 预测结果(当前状态不一样行为action的回报,其实也就 往上,往下 两种行为)
156         readout_t = readout.eval(feed_dict={s : [s_t]})[0]
157         a_t = np.zeros([ACTIONS])
158         action_index = 0
159         if t % FRAME_PER_ACTION == 0:
160             # 加入一些探索,好比探索一些相同回报下其余行为,能够提升模型的泛化能力。
161             # 且epsilon是随着模型稳定趋势衰减的,也就是模型越稳定,探索次数越少。
162             if random.random() <= epsilon:
163                 # 在ACTIONS范围内随机选取一个做为当前状态的即时行为
164                 print("----------Random Action----------")
165                 action_index = random.randrange(ACTIONS)
166                 a_t[action_index] = 1
167             else:
168                 # 输出 奖励最大就是下一步的方向
169                 action_index = np.argmax(readout_t)
170                 a_t[action_index] = 1
171         else:
172             a_t[0] = 1 # do nothing
173 
174         # scale down epsilon 模型稳定,减小探索次数。
175         if epsilon > FINAL_EPSILON and t > OBSERVE:
176             epsilon -= (INITIAL_EPSILON - FINAL_EPSILON) / EXPLORE
177 
178         # run the selected action and observe next state and reward
179         x_t1_colored, r_t, terminal = game_state.frame_step(a_t)
180         # 先将尺寸设置成 80 * 80,而后转换为灰度图
181         x_t1 = cv2.cvtColor(cv2.resize(x_t1_colored, (80, 80)), cv2.COLOR_BGR2GRAY)
182         # x_t1 新获得图像,二值化 阈值:1
183         ret, x_t1 = cv2.threshold(x_t1, 1, 255, cv2.THRESH_BINARY)
184         x_t1 = np.reshape(x_t1, (80, 80, 1))
185         #s_t1 = np.append(x_t1, s_t[:,:,1:], axis = 2)
186         # 取以前状态的前3帧图片 + 当前获得的1帧图片
187         # 每次输入都是4幅图像
188         s_t1 = np.append(x_t1, s_t[:, :, :3], axis=2)
189 
190         # store the transition in D
191         # s_t: 当前状态(80 * 80 * 4)
192         # a_t: 即将行为 (1 * 2)
193         # r_t: 即时奖励
194         # s_t1: 下一状态
195         # terminal: 当前行动的结果(是否碰到障碍物 True => 是 False =>否)
196         # 保存参数,队列方式,超出上限,抛出最左端的元素。
197         D.append((s_t, a_t, r_t, s_t1, terminal))
198         if len(D) > REPLAY_MEMORY:
199             D.popleft()
200 
201         # only train if done observing
202         if t > OBSERVE:
203             # 获取batch = 32个保存的参数集
204             minibatch = random.sample(D, BATCH)
205             # get the batch variables
206             # 获取j时刻batch(32)个状态state
207             s_j_batch = [d[0] for d in minibatch]
208             # 获取batch(32)个行动action
209             a_batch = [d[1] for d in minibatch]
210             # 获取保存的batch(32)个奖励reward
211             r_batch = [d[2] for d in minibatch]
212             # 获取保存的j + 1时刻的batch(32)个状态state
213             s_j1_batch = [d[3] for d in minibatch]
214             # readout_j1_batch =>(32, 2)
215             y_batch = []
216             readout_j1_batch = sess.run(readout, feed_dict = {s : s_j1_batch})
217             for i in range(0, len(minibatch)):
218                 terminal = minibatch[i][4]
219                 # if terminal, only equals reward
220                 if terminal:  # 碰到障碍物,终止
221                     y_batch.append(r_batch[i])
222                 else: # 即时奖励 + 下一阶段回报
223                     y_batch.append(r_batch[i] + GAMMA * np.max(readout_j1_batch[i]))
224             # 根据cost -> 梯度 -> 反向传播 -> 更新参数
225             # perform gradient step
226             # 必需要3个参数,y, a, s 只是占位符,没有初始化
227             # 在 train_step过程当中,须要这3个参数做为变量传入
228             train_step.run(feed_dict = {
229                 y : y_batch,
230                 a : a_batch,
231                 s : s_j_batch}
232             )
233 
234         # update the old values
235         s_t = s_t1  # state 更新
236         t += 1
237 
238         # save progress every 10000 iterations
239         if t % 10000 == 0:
240             saver.save(sess, 'saved_networks/' + GAME + '-dqn', global_step = t)
241 
242         # print info
243         state = ""
244         if t <= OBSERVE:
245             state = "observe"
246         elif t > OBSERVE and t <= OBSERVE + EXPLORE:
247             state = "explore"
248         else:
249             state = "train"
250 
251         print("terminal", terminal, \
252               "TIMESTEP", t, "/ STATE", state, \
253             "/ EPSILON", epsilon, "/ ACTION", action_index, "/ REWARD", r_t, \
254             "/ Q_MAX %e" % np.max(readout_t))
255         # write info to files
256         '''
257         if t % 10000 <= 100:
258             a_file.write(",".join([str(x) for x in readout_t]) + '\n')
259             h_file.write(",".join([str(x) for x in h_fc1.eval(feed_dict={s:[s_t]})[0]]) + '\n')
260             cv2.imwrite("logs_tetris/frame" + str(t) + ".png", x_t1)
261         '''
262 
263 def playGame():
264     sess = tf.InteractiveSession()
265     s, readout, h_fc1 = createNetwork()
266     trainNetwork(s, readout, h_fc1, sess)
267 
268 def main():
269     playGame()
270 
271 if __name__ == "__main__":
272     main()
View Code

 

5.运行结果与分析

由于不能上传视频,因此只能截取几张典型图片了。我训练了2920000次生成的模型,以这个模型预测,小鸟可以自动识别障碍物,不会发生碰撞。按以下配置训练和预测:

训练:OBSERVE = 1000,EXPLORE = 3000000

预测:OBSERVE = 100000,EXPLORE = 3000000 (预测是引用模型,因此不须要训练,OBSERVE要尽量大)

            预测时在train.py文件中将下面引用模型注释打开

 """
    if checkpoint and checkpoint.model_checkpoint_path:
        saver.restore(sess, checkpoint.model_checkpoint_path)
        print("Successfully loaded:", checkpoint.model_checkpoint_path)
    else:
        print("Could not find old network weights")
"""

 

小鸟运行结果图片

     

 

在预测状态,运行代码,小鸟会自动飞翔,这时也会相应打印一些参数结果出来:

参数结果

terminal:是否碰撞到障碍物(True :是,False:否);

TIMESTEP:表示运行次数;

STATE:当前模型运行状态(observe:观察,explore:探索,train:训练);

EPSILON:表示进入探索阶段的阈值,是逐渐减少的;

ACTION:行动方向最大价值的索引;

REWARD:即时奖励;

Q_MAX:输出行动方向的最大价值;

 

 

 

不要让懒惰占据你的大脑,不要让妥协拖垮了你的人生。青春就是一张票,能不能遇上时代的快车,你的步伐就掌握在你的脚下。

相关文章
相关标签/搜索