cartpole强化学习DQN实战

本文章通过keras实现DQN算法来解决倒立摆的平衡问题
一.环境
cartpole是一个经典的环境,可以验证许多的算法。这次我用的是cartpole-v0,一个离散动作空间的倒立摆环境,该环境有两个动作,即左和右,并且包含环境的四个状态观测值。
然后就瞅瞅这个环境:
cartpole强化学习DQN实战_第1张图片
黑色的载体可以左右移动,来保持平衡杆直立

然后看一下gym官方的描述:
cartpole强化学习DQN实战_第2张图片
这里面尤其要注意的一点就是关于“Reward”的描述,每一步都给“1”分的回报,而且最关键的是,即使这一步导致一轮交互结束了,也给予“1”分的回报。这样显然是不可取的,因此要在交互过程结束后,给这一步的Reward赋予“坏”的回报,这里我设置了-10。

reward = reward if not done else -10

二.DQN代码
代码部分我直接用了我上一篇文章解决“MountainCar”的主体代码部分,该文章链接在末尾会附上。

%%time
from tensorflow.keras import Sequential,layers
from tensorflow.keras.models import load_model
from tensorflow.keras.optimizers import Adam
from collections import deque
import numpy as np
import random
import gym 

class Agent(object):

def __init__(self,):
    '''
    初始化类的变量:
    (1)steps记录与环境交互的次数,用来控制target网络的参数更新以及模型的训练
    (2)var和e共同参与探索率的衰减
    (3)model和target网络,model是决策的主体,target网络作为一个“监督”
    (4)replay_memory是经验池
    '''
    self.steps = 0
    self.var = 1e-1
    self.e = 1e-5
    self.Model = self.model_()
    self.Target = self.model_()
    self.replay_memory = deque(maxlen=1000)

def model_(self):
    '''
    定义决策网络,这里采用了两层“relu”作为激活函数的全连接层
    最后一层不采取激活函数,也可使用linear,但用处不大
    最后一层的神经元数量为输出的动作类型的数量,这个模型有两个动作
    因此我们最后一层有两个神经元
    编译使用了均方差作为loss函数
    使用Adam优化器,并设置学习率为0.01
    '''
    model = Sequential(name="DQN")
    model.add(layers.Dense(64,"relu"))
    model.add(layers.Dense(64,"relu"))
    model.add(layers.Dense(2,"linear"))
    model.compile(optimizer=Adam(lr=0.01),loss = "mse")
    return model

def add(self,obs,action,reward,n_s,done):
    '''
    向经验池中添加交互序列
    '''
    self.replay_memory.append((obs,action,reward,n_s,done))

def sample(self,obs):
    '''
    动作选择函数,根据决策网络选择动作
    也有一定的概率随机选择动作,即探索
    随着训练的进行,探索率会逐渐降低
    因为是两个动作,所以随机取值设定为0或1
    '''
    self.var -= self.e
    if np.random.uniform() <= self.var:
        return np.random.randint(2)
    return np.argmax(self.Model.predict(obs))

def data(self):
    '''
    从经验池中选择数据,batch_size = 64
    这里可以根据需要自行调整
    每一条数据都包括五部分,即为
    (观测值,动作,回报,下一个观测值,结束状态)
    每次调用这个函数都返回一个batch的数据用来训练
    '''
    batch = random.sample(self.replay_memory,64)
    Obs,Action,Reward,N_s,Done = [],[],[],[],[]
    for (obs,action,reward,n_s,done) in batch:
        Obs.append(obs)
        Action.append(action)
        Reward.append(reward)
        N_s.append(n_s)
        Done.append(done)
    return np.array(Obs).astype("float32"),np.array(Action).astype("int64"),np.array(Reward).astype("float32"),\
    np.array(N_s).astype("float32"),np.array(Done).astype("float32")

def learn(self):
    '''
    训练的主体
    (1)与环境交互100次同步一哈target网络与决策网络的参数
         即复制决策网络到target网络
    (2)为了更高效的训练,在数据足够的前提下,每隔五步训练一次
    (3)通过obs得到Q,next_obs得到Target_Q,通过Q_learning的方法更新Q的
         数据如果done为True,说明环境终止,不需要考虑下一个动作。否则依据
         Q(s,a) <-- Q(s,a) + α{r + γ*max*Q(s', : ) - Q(s,a)}来更新Q
         最后将更新好的Q作为“标签”来进行训练
    
    '''
    if self.steps % 100 == 0:
        self.Target.set_weights(self.Model.get_weights())
        
    if self.steps % 5 == 0 and len(self.replay_memory) >= 200:
        #取出一个batch的数据
        Obs,Action,Reward,N_s,Done = self.data()
        Q = self.Model.predict(Obs.reshape(64,1,4))
        Q_ = self.Target.predict(N_s.reshape(64,1,4))
        '''
        因为编译模型里设置了学习率,在这里不重复设置
        '''
        for i in range(64):
            if Done[i]:
                Q[i][0][Action[i]] = Reward[i]
            Q[i][0][Action[i]] = (Reward[i] + 0.9*np.amax(Q_[i][0]))
       
        #训练决策网络
        self.Model.fit(Obs.reshape(64,1,4),Q,verbose=0)


env = gym.make("CartPole-v0")
agent = Agent()
Scores = []

for times in range(1000):
    s = env.reset()
    Score = 0
    while True:
        agent.steps += 1
        a = agent.sample(s.reshape(1,1,4))
        next_s, reward, done, _ = env.step(a)
        '''
        对于模型done之后的reward设置是必要的
        否则模型不会收敛,因为这个环境获得回报
        是根据稳定的时间来算的,即使done为True
        也会积累正值的回报,因此将done==True之
        后的reward设置为负值,以让模型收敛
        '''
        reward = reward if not done else -10
        agent.add(s,a,reward,next_s,done)
        agent.learn()
        Score += reward
        s = next_s
        if done:
            Scores.append(Score)
            print('episode:',times,'score:',Score,'max:',np.max(Scores))
            break
            
    #提前终止训练
    if np.mean(Scores[-6:])>= 180:
        agent.Model.save("cartpole_2.h5")
        break
        
agent.Model.save("cartpole_2.h5")

因为网络结构相对较小,以及采取不是逐步训练的方式,算法执行的效率还是很快的。最快的一次大概是10秒以内,就收敛到可取的范围。
训练的过程(57次训练并结束)
cartpole强化学习DQN实战_第3张图片
接着用训练好的模型进行六轮测试,结果都是满分(200),还不错
cartpole强化学习DQN实战_第4张图片
经过多次尝试,发现模型稳定在满分状态。

关于DQN解决的另一个“小车”环境:
https://blog.csdn.net/weixin_43968987/article/details/113057733

然后老习惯,附上Eason的果:

笑,何妨与你又重温,仍然我说我庆幸,你永远胜过别人 ——— 无条件

你可能感兴趣的:(练习,python,深度学习,tensorflow,神经网络,强化学习)