当前位置:   article > 正文

项目实战:使用Deep Q Network(DQN)算法让机器学习玩游戏(二)_dqn 游戏 c语言

dqn 游戏 c语言

这个项目用三篇文章进行介绍,各部分的内容如下:

项目实战:使用Deep Q Network(DQN)算法让机器学习玩游戏(一):总体介绍,游戏部分

项目实战:使用Deep Q Network(DQN)算法让机器学习玩游戏(二):算法部分

项目实战:使用Deep Q Network(DQN)算法让机器学习玩游戏(三):算法和游戏的交互部分,模型训练,模型评估,使用相同的算法和参数去玩另外一个不同的游戏


(二)算法部分

由于输入的数据是图像,所以项目中使用的神经网络是CNN。(CNN介绍

神经网络架构

使用的CNN的网络架构图如下:

我们从游戏那得到的输入数据是大小为700*700的彩色图像,由于棋子的具体颜色对算法没有帮助,所以我们将它进行灰度化处理,然后将大小减小到60*60.

  1. def pre_process(self, frame, crop_size):
  2. frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
  3. frame = cv2.resize(frame, crop_size, interpolation=cv2.INTER_CUBIC)
  4. return frame

神经网络一共包含3个卷积层,都是使用zero-padding。

在第一个卷积层,使用32个6*6*1的filter,以4*4的stride进行扫描,然后得到32个大小为15*15的feature map。

然后,使用一个最大池化层,它的filter尺寸为2*2, stride为2*2.

第二个卷积层使用64个尺寸为4*4*32的filter,stride是2*2.

第三个卷积层使用64个尺寸为3*3*64的filter,stride是1*1.

在这三个卷积层之后,三维矩阵数据被拍平成一个flatten layer,尺寸大小为1024*1.

在这之后是一个全连接层,有1024*512个神经元。

最后是输出层,由于我们每次可以从7列选择一个作为动作,所以输出层的神经元个数是7.

在神经网络中,每一个卷积层和全连接层都有一个激活函数。项目中使用的激活函数是ReLU函数。

DQN算法介绍中可知,DQN的输出是不同动作对应的Q value,所以它是一个回归问题。所以项目中采用mean squared error 函数作为loss function。

我们使用mini-batch 梯度下降,batch的大小为48.

优化函数使用adaptive moment estimation(Adam)方法。

  1. self.graph = tf.Graph()
  2. with self.graph.as_default():
  3. self.sess = tf.Session(graph=self.graph)
  4. with self.sess.as_default():
  5. with self.graph.as_default():
  6. self.inp = tf.placeholder("float", [None, 60, 60, self.image_channel], name=self.player + '_inp')
  7. with tf.variable_scope(self.player + '_net'):
  8. with tf.variable_scope('l1'):
  9. self.W_conv1 = tf.Variable(tf.truncated_normal([6, 6, self.image_channel, 32], stddev=0.02), name=self.player + '_w_conv1')
  10. self.b_conv1 = tf.Variable(tf.constant(0.01, shape=[32]), name=self.player + '_b_conv1')
  11. self.conv1 = tf.nn.relu(tf.nn.conv2d(self.inp, self.W_conv1, strides=[1, 4, 4, 1], padding="SAME") + self.b_conv1,
  12. name=self.player + '_conv1')
  13. self.pool1 = tf.nn.max_pool(self.conv1, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding="SAME",
  14. name=self.player + '_pool1')
  15. with tf.variable_scope('l2'):
  16. self.W_conv2 = tf.Variable(tf.truncated_normal([4, 4, 32, 64], stddev=0.02), name=self.player + '_w_conv2')
  17. self.b_conv2 = tf.Variable(tf.constant(0.01, shape=[64]), name=self.player + '_b_conv2')
  18. self.conv2 = tf.nn.relu(
  19. tf.nn.conv2d(self.pool1, self.W_conv2, strides=[1, 2, 2, 1], padding="SAME") + self.b_conv2,
  20. name=self.player + '_conv2')
  21. with tf.variable_scope('l3'):
  22. self.W_conv3 = tf.Variable(tf.truncated_normal([3, 3, 64, 64], stddev=0.02), name=self.player + '_w_conv3')
  23. self.b_conv3 = tf.Variable(tf.constant(0.01, shape=[64]), name=self.player + '_b_conv3')
  24. self.conv3 = tf.nn.relu(
  25. tf.nn.conv2d(self.conv2, self.W_conv3, strides=[1, 1, 1, 1], padding="SAME") + self.b_conv3,
  26. name=self.player + '_conv3')
  27. self.conv3_flat = tf.reshape(self.conv3, [-1, 1024])
  28. with tf.variable_scope('l4'):
  29. self.W_fc4 = tf.Variable(tf.truncated_normal([1024, 512], stddev=0.02), name=self.player + '_w_fc4')
  30. self.b_fc4 = tf.Variable(tf.constant(0.01, shape=[512]), name=self.player + '_b_fc4')
  31. self.fc4 = tf.nn.relu(tf.matmul(self.conv3_flat, self.W_fc4) + self.b_fc4,
  32. name=self.player + '_fc4')
  33. with tf.variable_scope('l5'):
  34. self.W_fc5 = tf.Variable(tf.truncated_normal([512, self.ACTIONS], stddev=0.02), name=self.player + '_w_fc5')
  35. self.b_fc5 = tf.Variable(tf.constant(0.01, shape=[self.ACTIONS]), name=self.player + '_b_fc5')
  36. self.out = tf.matmul(self.fc4, self.W_fc5) + self.b_fc5
  37. self.argmax = tf.placeholder("float", [None, self.ACTIONS],name=self.player + "_argmax")
  38. self.gt = tf.placeholder("float", [None], name=self.player + "_gt") # ground truth
  39. with tf.variable_scope(self.player + '_loss'):
  40. self.predict_q_value = tf.reduce_sum(tf.multiply(self.out, self.argmax), reduction_indices=1,
  41. name=self.player + '_action')
  42. # cost function we will reduce through backpropagation
  43. self.cost = tf.reduce_mean(tf.square(self.predict_q_value - self.gt), name=self.player + '_cost')
  44. # optimization fucntion to reduce our minimize our cost function
  45. with tf.variable_scope(self.player + '_train'):
  46. self.train_step = tf.train.AdamOptimizer(1e-6).minimize(self.cost, name=self.player+'_train_step')

超参数

这个算法中包含一些超参数,超参数的设置如下所示:


 

为了保证足够的不相关数据,DQN在前面50000步不会进行学习。

experience queue是用来存储经验回放数据。它的尺寸是我们可以从多少个之前的经验中选取数据。

discount rate是指后面动作对前面动作的影响有多大。

batch size是每次选择多少个数据放入到神经网络中训练。

adam learning rate是使用Adam优化算法时的学习率。

episilon相关的参数在下面一起介绍。

动作选择

从DQN算法介绍中,我们知道DQN的输出是所有可能的动作对应的Q value。在动作的选择上,我们使用exploitation-exploration。Exploitation-exploration的介绍请见这里。

我们以epsilon的概率选择Q value最高的动的工作,以1-epsilon的概率随机选择动作。epsilon是一个[0,1]区间的数。

有两种方法设置epsilon的值,一种是固定的值,另一种的变动的值。在这个项目中,我们选择非固定值。在开始的时候,epsilon比较大,agent能以很大的概率随机选择动作,可以对环境进行充分的探索。随着训练次数的处增加,agent对环境有一定的了解,所以我们逐渐选择最大Q value的动作。

参数表中 initail episilon表示episilon的初始值,final  episilon表示episilon的最终值,how many steps toanneal epsilon表示经过多少步epislon从初始值降低到最终值。epsilon每次训练更新一次。两个玩家的epsilon变化图如下:

  1. def choose_action(self, observation, mode):
  2. out_t = self.out.eval(session=self.sess, feed_dict={self.inp: [observation]})
  3. out_t = out_t[0]
  4. self.argmax_t = np.zeros([self.ACTIONS])
  5. if random.random() <= self.epsilon:
  6. maxIndex = choice(range(self.ACTIONS), 1)
  7. maxIndex = maxIndex[0]
  8. else:
  9. maxIndex = np.argmax(out_t)
  10. self.argmax_t[maxIndex] = 1
  11. if mode == 'use mode':
  12. self.epsilon = self.FINAL_EPSILON
  13. else:
  14. if self.epsilon > self.FINAL_EPSILON:
  15. self.epsilon -= (self.INITIAL_EPSILON - self.FINAL_EPSILON) / self.EXPLORE
  16. return maxIndex, self.epsilon

经验回放

在每一步中,我们都会得到一组数据(时间t时的status,action,reward,时间t+1时的status)。这组数据会被喂给DQN进行训练。

如果我们只使用当前的数据进行训练,在训练之后就丢弃他们的话,会有两个问题。

一个是之前经验数据在后面的训练过程中都不会再被使用,这些经验数据没有被充分利用。

第二个是这些数据之间存在相关性。他们之间存在时间上的前后关系。这一步的action是在前一步的Q value基础上计算来的,而它所得到的reward又会影响到下一步。而数据之间存在相关性对模型来说是不好的。

所以为了解决上面两个问题,我们使用了经验回放。

经验回放是指建立一个queue用来存放这些元组数据,每一步都会往里面存入数据,当数据的个数超出queue的大小时,将最开始的数据删掉。然后每次训练的时候,从experience queue里随机选择batch size的数据输入给DQN。

  1. def store_transition(self, inp_t, action, reward_t, inp_t1):
  2. action_list = np.zeros([self.ACTIONS])
  3. action_list[action] = 1
  4. self.D.append((inp_t, action_list, reward_t, inp_t1))
  5. if len(self.D) > self.REPLAY_MEMORY:
  6. self.D.popleft()

模型学习

1. 当我们从experience queue中获得输入数据时,将元组数据中的第一个,也就是时间t时的status(它表示目前棋盘的画面)输入到CNN,CNN会输出7个可能动作的Q value。

2. 根据Q value选择时间t所应该采取的动作。

3. 在游戏中执行这个动作,并得到游戏返回的对应reward。

4. 我们根据DQN算法的公式计算target Q value和损失函数值。

5. 利用优化算法对神经网络进行更新。

  1. def learn(self, step):
  2. minibatch = random.sample(self.D, self.BATCH)
  3. inp_batch = [d[0] for d in minibatch]
  4. argmax_batch = [d[1] for d in minibatch]
  5. reward_batch = [d[2] for d in minibatch]
  6. inp_t1_batch = [d[3] for d in minibatch]
  7. gt_batch = []
  8. out_batch = self.sess.run(self.out, feed_dict={self.inp: inp_t1_batch})
  9. for i in range(0, len(minibatch)):
  10. gt_batch.append(reward_batch[i] + self.GAMMA * np.max(out_batch[i]))
  11. bb_cost, bb_train_step = self.sess.run([self.cost, self.train_step],
  12. feed_dict={
  13. self.gt: gt_batch,
  14. self.argmax: argmax_batch,
  15. self.inp: inp_batch
  16. })
  17. if step % self.SAVE_STEP == 0:
  18. self.saver.save(self.sess, self.ckp_path + '/model.ckpt', global_step=step)
  19. print("save checkpoint %s:"%str(self.ckp_path + '/model.ckpt'))
  20. return bb_cost

同时训练两个模型

由于我们使用self-play方法,两个玩家都是一个模型,他们具有相同的网络结构和超参数,然后相互学习。所以我们需要同时训练两个模型。

在这个项目中,我们使用tensorflow来构建模型。tensorflow使用graph来定义数据流,使用session来进行graph上的操作。

所以我们需要使用两个不同graph和session来分隔两个模型。这样,这两个模型的参数才能正确得更新到它们对应的模型上。

参考:https://my.oschina.net/u/3800567/blog/1786556

https://www.tensorflow.org/guide/graphs?hl=zh-cn

保存模型

由于训练的时间特别长,所以我们需要将训练好的模型保存下以后使用。我们使用tensorflow提供的checkpoint机制来保存和恢复模型。

恢复模型

  1. self.saver = tf.train.Saver()
  2. checkpoint = tf.train.latest_checkpoint(self.ckp_path)
  3. # 从训练好的checkpoint中恢复模型
  4. if checkpoint != None and (self.mode == 'use mode'):
  5. print('%s Restore Checkpoint %s' % (str(self.player), checkpoint))
  6. self.saver.restore(self.sess, checkpoint)
  7. print("Model restored.")
  8. else:
  9. # 不使用已经训练好的数据,重新初始化变量
  10. init = tf.global_variables_initializer()
  11. self.sess.run(init)
  12. print("%s Initialized new Graph" % (str(self.player)))

保存checkpoint

  1. self.saver.save(self.sess, self.ckp_path + '/model.ckpt', global_step=step)
  2. print("save checkpoint %s:"%str(self.ckp_path + '/model.ckpt'))

 

完整代码及训练好的checkpoint数据 请见github:https://github.com/zm2229/use-DQN-to-play-a-simple-game

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/黑客灵魂/article/detail/984860
推荐阅读
相关标签
  

闽ICP备14008679号