强化学习DQN算法实战之CartPole

简介

这篇笔记主要是记录了Deep Q-Learning Network的开发过程。开发环境是:Ubuntu18.04 、tensorflow-gpu 1.13.1 和 OpenAI gym
其中,这篇笔记记录了深度学习的开发环境。安装完成后,在虚拟环境执行pip install gym安装界面环境。

强化学习的一个困难的地方,在于数据收集和环境描述。而 OpenAI的gym给我们提供了一个非常强大的虚拟环境,这样我们就可以专注于算法本身的开发了。

这篇笔记主要参考了:

环境描述

基本环境可以参考:https://gym.openai.com/envs/CartPole-v1/
学习的目标是使得木棍在小车上树立的时间尽量长。action的选择只有向左或者是向右。环境会自动给出给出反馈,每一步后的得分,下一个局面的描述的状态,是否是结束。环境状态被gym自动封装成一个np.array,可以通过有关的API获取信息。

在这个例子中,环境的描述是一个4维的向量,我们不必管这4维向量的意义,只需要知道有这个描述即可(当然,如果你感兴趣,可以深究)。每个环境,gym都封装了一分数reward。而且,如果是结束状态,gym会给出描述符。这些在下面的代码中会有说明。

算法介绍和说明

先给出基本算法描述,算法来自上面的参考连接:

这是一个最基本的Off-Policy借助Replay-Buffer和神经网络实现的算法。上面的 ϕ \phi ϕ,是表示一个连贯的输入,因为上述的算法是输入了一系列的图片。不过在这个例子中,可以把 ϕ \phi ϕ理解成仅仅输入当前的局面,即 s t = ϕ ( s t ) s_t=\phi(s_t) st=ϕ(st)。之后会有exploration的操作,这是为了随机的选取那些评估分数比较低,但是可能会有较好表现的行动。 Q ( s , a ) Q(s,a) Q(s,a)表示一个Q-function,它的作用是给状态 s s s下的每个行动 a a a一个评估分数。实际操作中, Q Q Q是一个神经网络,每个状态作为神经网络的输入,神经网络的输出是所有的行动 a a a的评估分数。

算法给出了 y i y_i yi的计算法则。对神经网络进行BP的时候,就根据这个公式来即可。每次从buffer中选取一个批次的数据,执行随机梯度下降SGD算法,即可进行修正。

建议仔细阅读原文

一个说明点:在Deep-Q-Learning Nerwork (DQN)中,最后的输出层的激活函数都是线性的,而且损失函数是Mean Square Error (MSE)。即下面代码中提到的linear,原因参考自这篇博客
因为DQN需要计算的是采取某个行动后的评估值,所以最好是线性输出;而且使用MSE可以可以描述两个数据之间的差距,所以MSE是最佳的选择。这就处理一个回归问题。而对于分类问题,我们需要的是处理非线性的,因为最终的结果需要把数据进行概率的划分,一般使用softmax等的分类函数处理。对于DQN的内部隐藏层,一般采取Relu函数进行非线性映射,以增加函数可以表示的状态空间。

还有一个说明的地方,在损失函数中,我们实际需要反向传播的,只有QDN选择的那个动作与评估之间的误差,而其他动作的误差不需要传播。
先给出MSE公式:
M S E = 1 N ∑ i = 1 N ( Y i − Y ^ i ) 2 MSE = \frac{1}{N}\sum_{i=1}^N \left(Y_i-\hat{Y}_i\right)^2 MSE=N1i=1N(YiY^i)2
举个例子: Q ( s , a ) Q(s,a) Q(s,a)输出[1.0, 0.8],记为q_values。那么,这个表示动作0的分数是1.0,动作1的分数是0.8。这样来看,agent肯定会采取动作0。假设现在经过计算的该动作的分数是0.7,那么误差的绝对值是0.3,这是需要进行反向传播的。那么,问题是怎样进行反向传播呢?采取的技巧是这样的:新给出一个向量q_update,等于 Q ( s , a ) Q(s,a) Q(s,a)的输出[1.0, 0.8],因为我们之前存储了action,即知道是采取行动0,那么更新q_update参数为[0.7, 0.8]。
因为反向传递使用MSE,计算方式是:
E r r = 1 2 [ ( 0.7 − 1.0 ) 2 + ( 0.8 − 0.8 ) 2 ] = 1 2 ( 0.7 − 1.0 ) 2 Err= \frac{1}{2}\left[\left(0.7-1.0\right)^2+\left(0.8-0.8\right)^2\right]=\frac{1}{2}(0.7-1.0)^2 Err=21[(0.71.0)2+(0.80.8)2]=21(0.71.0)2
这个方式很巧妙,只标出了具体行动的误差,其他方式在MSE计算中,都成为0了。可以参考代码好好理解。

代码实例

代码借助深度学习框架tensorflow进行实现。在1.13.1以及将来要发布的版本中,继承了keras接口。个人的看法是,尽量使用高阶API,开发效率高,不易出错,代码简单易懂。

这个例子中,使用了一个全连接神经网络。输入层是4维向量,表示当前状态。两个隐藏层,都包含24个神经元。输出层是2维的,表示向左或者向右采取行动。

强化学习算法Agent.py介绍

import tensorflow as tf
from tensorflow import keras
from collections import deque
import numpy as np
import random

MAX_LEN = 2000
BATCH_SIZE = 64
GAMMA = 0.95
EXPLORATION_DECAY = 0.995
EXPLORATION_MIN = 0.1


class Agent(object):
    def __init__(self, input_space, output_space, lr=0.001, exploration=0.9):
        self._model = keras.Sequential()
        self._model.add(keras.layers.Dense(input_shape=(input_space,), units=24, activation=tf.nn.relu))
        self._model.add(keras.layers.Dense(units=24, activation=tf.nn.relu))
        # 注意这里输出层的激活函数是线性的!!!
        self._model.add(keras.layers.Dense(units=output_space, activation='linear'))
        self._model.compile(loss='mse', optimizer=keras.optimizers.Adam(lr))

        self._replayBuffer = deque(maxlen=MAX_LEN)  # replay buffer,最大200的容量
        self._exploration = exploration

    @property
    def exploration(self):
        return self._exploration

    def add_data(self, state, action, reward, state_next, done):
        self._replayBuffer.append((state, action, reward, state_next, done))

    def act(self, state):
        if np.random.uniform() <= self._exploration:  # 随机走出一步
            return np.random.randint(0, 2)
        action = self._model.predict(state)  # 使用神经网络评估的选择
        return np.argmax(action[0])

    def train_from_buffer(self):
        if len(self._replayBuffer) < BATCH_SIZE:
            return
        batch = random.sample(self._replayBuffer, BATCH_SIZE)  # 随机选取一个批次的数据
        for state, action, reward, state_next, done in batch:
            if done:  # 对应论文中的分数更新
                q_update = reward
            else:
                q_update = reward + GAMMA * np.amax(self._model.predict(state_next)[0])
            q_values = self._model.predict(state)  # 先赋值,为了减去不相关的行动得分
            q_values[0][action] = q_update  # 把采取了的行动的分数更新,那么只有这项在MSE中有效果
            self._model.fit(state, q_values, verbose=0)  # SGD训练模型
            self._exploration *= EXPLORATION_DECAY
            self._exploration = max(EXPLORATION_MIN, self._exploration)

train.py训练

import gym
from Agent import Agent
import numpy as np
import matplotlib.pyplot as plt


def train():
    env = gym.make("CartPole-v1")
    input_space = env.observation_space.shape[0]
    output_space = env.action_space.n
    print(input_space, output_space)
    agent = Agent(input_space, output_space)
    run = 0
    x = []
    y = []
    while run < 100:
        run += 1
        state = env.reset()
        state = np.reshape(state, [1, -1])
        step = 0
        while True:
            step += 1  # 步数越多,相当于站立的时间越长,比较容易理解。
            # env.render()
            action = agent.act(state)
            state_next, reward, done, _ = env.step(action)
            reward = reward if not done else -reward  # 棍子倒了,分数肯定是负数了
            state_next = np.reshape(state_next, [1, -1])
            agent.add_data(state, action, reward, state_next, done)
            state = state_next
            if done:
                print("Run: " + str(run) + ", exploration: " +
                      str(agent.exploration) + ", score:" + str(step))
                x.append(run)
                y.append(step)
                break
            agent.train_from_buffer()  # 每次都要执行训练
    plt.plot(x, y)
    plt.show()


if __name__ == "__main__":
    train()

训练结果

首先声明一个地方,强化学习不同于监督学习,曲线是下降的。RL的曲线会波动的很厉害,不过如果模型好的话,大体上会是上升的。这里训练,我测试了多种激活函数。每个进行100局的测试。硬件条件限制,结果肯定是不太准确的,只能看一下大体趋势。

初始情况 batch-size到32,buffer-size到10000。然后把学习速率设置到0.001,看一下效果:

很明显的上升趋势,出现波动属于正常现象,而且20步之后平均分数能到150/。。所以是之前的学习速率设定的太高了。。。。。

在上述的基础上,使用batch-size=64,在此测试结果,得到下面的图像:

平均值更好一些,而且收敛速度相对较快

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值