欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

强化学习DQN算法实战之CartPole

程序员文章站 2024-03-22 13:09:10
...

简介

这篇笔记主要是记录了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会给出描述符。这些在下面的代码中会有说明。

算法介绍和说明

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

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

建议仔细阅读原文

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

还有一个说明的地方,在损失函数中,我们实际需要反向传播的,只有QDN选择的那个动作与评估之间的误差,而其他动作的误差不需要传播。
先给出MSE公式:
MSE=1Ni=1N(YiY^i)2 MSE = \frac{1}{N}\sum_{i=1}^N \left(Y_i-\hat{Y}_i\right)^2
举个例子: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)的输出[1.0, 0.8],因为我们之前存储了action,即知道是采取行动0,那么更新q_update参数为[0.7, 0.8]。
因为反向传递使用MSE,计算方式是:
Err=12[(0.71.0)2+(0.80.8)2]=12(0.71.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
这个方式很巧妙,只标出了具体行动的误差,其他方式在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,看一下效果:
强化学习DQN算法实战之CartPole
很明显的上升趋势,出现波动属于正常现象,而且20步之后平均分数能到150/。。所以是之前的学习速率设定的太高了。。。。。

在上述的基础上,使用batch-size=64,在此测试结果,得到下面的图像:
强化学习DQN算法实战之CartPole
平均值更好一些,而且收敛速度相对较快