大家好,今天给大家带来DQN的思路及实现方法。

        关于DQN,就不用我多做介绍了,我会以最简短明白的阐述讲解DQN,尽量让你在10分钟内理清思路。

        非常重要的一点!!!

        非常重要的一点!!!我在GitHub上下载了DQN代码,跑完后,我重写一次,删改了其中一些东西。比如epsilon,至于原因还有带来的一些结果,我放到后面来说,总之,我们先进入代码环节。先给出参考网址:

https://github.com/marload/DeepRL-TensorFlow2/blob/master/DQN/DQN_Discrete.py

下面是在 gym.make('CartPole-v1) 中实现的效果:

DQN的源代码每一步代表什么TensorFlow dqn代码实现_数据

下面我们按照DQN的算法来实现DQN

DQN的算法如下图:

DQN的源代码每一步代表什么TensorFlow dqn代码实现_初始化_02

 

第一步:初始化一个容量为N的存取器D

        我们希望D能够存放(def        insert)、拿取(def        get_sample)数据。

下面我们用collections.deque来实现:

from collections import deque
import numpy as np
import random

class buffer():

    def __init__(self):
        self.buffer = deque(maxlen=1000)

    def insert(self,state,next_state,action,reward,is_over):
        self.buffer.append([state,next_state,action,reward,is_over])

    def get_sample(self):
        sample = random.sample(self.buffer,32)
        state,next_state,action,reward,is_over = map(np.asarray,zip(*sample))
        state = np.array(state).reshape(32,-1)
        next_state = np.array(next_state).reshape(32,-1)
        return state,next_state,action,reward,is_over

    def length(self):
        return len(self.buffer)

好了,我们已经有了一个buffer可以存放数据和拿取数据了。

第二步:初始化动作奖励函数Q

        对于Q函数,我们输入一个状态(state)返回action-value。在 gym.make('CartPole-v1) 中,状态的shape为(4,),动作数量为2。你可以用下列代码查看:

import gym
env = gym.make('CartPole-v1',render_mode='human')
print(env.observation_space.shape)# 查看观测空间
print(env.action_space.n)# 查看动作数

       因为奖励要对应其中的一个动作, 所以,我们的Q函数输入大小为(None,4),输出为(None,2)就能够确定下来了。下面我们用全连接层来实现这个网络:

import tensorflow as tf
from tensorflow.keras import layers,Input,Model,optimizers

def model():
    size = (4,)
    input = Input(size)
    x = layers.Dense(32,activation='relu')(input)
    x = layers.Dense(16,activation='relu')(x)
    out = layers.Dense(2)(x)
    model = Model(inputs=input,outputs=out)
    model.compile(loss='mse',
                  optimizer=optimizers.Adam(0.005))
    return model

这里action-value函数也实现了。

第三步:得到一个初始化的游戏状态

       首先我们需要建立一个游戏,不然怎么得到游戏状态,对吗?关于游戏这块,我们不需要深究,我们只要把注意力放在算法上就行了,它如何实现的,不关我们的事。代码如下:

import gym

env = gym.make('CartPole-v1',render_mode='human')
env.reset() # 初始化游戏
state = env.state # 得到初始状态

第四步:理清并实现训练流程

       我们再理清一下思路:

       1、从上面我们已经得到一个state状态了,接下来我们将state状态reshape后放入Q函数内就能得到action-value (None,2)。

        2、我们要从这两个里面选取奖励最大的value值的index(数值为0或者1),这个index就对应着游戏里的操作action了。

        3、我们将得到的操作action传入游戏,就会得到一个新的state、奖励reward、是否结束is_over等等参数。

        4、我们将这些参数保存到最开始定义的buffer中,在从buffer中随机选取参数出来训练就可以了。

代码如下:

env = gym.make('CartPole-v1',render_mode='human')
buf = buffer()# 初始化buffer
network = model()# 初始化训练网络
lable_network = model()# 初始化标签网络
#这里我感觉定义lable_network网络有些多余
#因为network的参数给了lable_network
#我没有深究,DQN权当了解,因为还有DQN变种的模型
for i in range(1000):
    env.reset() # 初始化游戏
    state = env.state # 得到初始状态
    is_over = False #
    weights = network.get_weights()
    lable_network.set_weights(weights)
    while not is_over:
        state = np.reshape(state, [1, 4]) 
        action = np.argmax(network.predict(state)[0])
        next_state,reward,is_over,_,_ = env.step(action)#传入操作获取新的数据
        buf.insert(state,next_state,action,reward,is_over)#参数保存进buffer
        state = next_state 
    if buf.length()>=32:
        train()

好了,到这里DQN的流程算是走完了,但是这里还有一个函数train()没有定义。

第五步:定义train()函数

       我们要通过train()函数来更新network的权重weights,这是必需的。

       buffer里面的get_sample我们是不是还没用过呢?这就来了。

       按照DQN算法:当is_over为True的时候,奖励为reward本身

                                 当is_over为False的时候,奖励为reward+ k*next_reward

实现如下:

def train():
    for i in range(10):
        state,next_state,action,reward,is_over = buf.get_sample()
        value = lable_network.predict(state)
        next_max_value = np.max(lable_network.predict(next_state),axis=1)
        value[range(32),action] = reward+0.95*next_max_value*(1-is_over)
        network.train_on_batch(state,value)

终于,我们完成了DQN网络。

最后一些想说的话(选择阅读)

       如果你已经按照上面的代码完整打了下来,我想说的是,很遗憾,你不一定能训练处上面的效果。我假设你看过上面GitHub中的代码(那里没有去掉参数)。现在,我们回到最开始的那里,我说过,我去除掉了epsilon参数。因为我在训练出一个了一个稳定的模型后,大概ai玩了10分钟的时候,输掉了游戏。这是令我震惊的,因为前面10分钟,ai分明陷入了循环(周期假设为60s),按理来说会一直保持下去。我认为,输掉的原因就是epsilon。因为这导致了一种随机性,虽然很小,但玩的时间久了,总会出现随件的action,也就是很可能稳定的模型里,本来应该是0,但突然给你换成了1,打破了原有的模型。所以我去除了,之后,这个ai虽然确实没有输掉,但也出现了新的问题。

        这个问题就是,训练的效果不稳定了。最开始那个稳定的效果是我在晚上花30分钟跑出来的。出于严谨,我在第二天重新跑了一次。而这次,一个多小时也没有跑到稳定的效果。我认为,因为get_sample函数里是随机选取数据的,所以可能选不到合适的数据去train,不能让模型很好的稳定下来。我尝试过,不去随机选取,将每一个数据拿去train,但效果也不尽如人意。最后按我的经历来看,如果你想要得到一个稳定的模型,我建议先保留epsilon,在代码中添加一个保留weights的代码,等ai玩的稳定的时候,停住程序,删除epsilon,导入保存的weights,再运行程序,应该能得到想要的效果。