深度强化学习之迷宫DQN(NIPS 2015版)实践笔记——入门提升篇

1. 背景

在「强化学习」(RL)领域,早期大部分成功的算法都依赖于人工提取特征,并结合线性的值函数或策略表示,算法的表现很大程度上取决于特征提取的质量,隐匿于机器学习中。近年来,「深度学习」(DL)的发展使得我们可以直接从复杂高维的原始输入(例如图像和声音)中捕获特征,并取得较好的表现。然而,深度学习和强化学习又存在着一些差别,导致难以直接将深度神经网络应用于 RL:

  • DL 通常基于大量的人工标注训练数据进行训练,而 RL 则是基于可能存在「延时」的奖励进行学习,很难通过标准的网络结构将输入直接与奖励进行关联
  • 大部分 DL 算法都假定数据样本之间相互「独立」,而 RL 则一般应用于高度相关的状态序列
  • 在 RL 中当算法学习到新的行为后,数据分布可能发生改变,而 DL 通常假设数据分布是「不变」的

DQN(Deep Reinforcement Learning )算法是提出了一种「卷积神经网络」(CNN)以解决上述挑战,在复杂的 RL 环境中直接通过视频数据生成控制策略。该网络基于 Q-learning 算法的变种进行训练,通过随机梯度下降来更新权重。该算法增加了一种「经验回放机制」(experience replay mechanism)来随机采样之前的状态转移,以平滑训练数据的分布,缓解数据相关性以及分布的不稳定性。

2. 回顾Q-learning

我们仍以上篇文章[3]. 中简单的走迷宫小游戏为例继续,样例原型来自“莫烦PYTHON”的强化学习,让探索机器人(红色方框)学会走迷宫,图中黄色圆圈表示是天堂出口(reward为1),黑色方框表示是地狱陷阱(reward为-1)。我们给予机器人的引导只有奖励,如果走到天堂出口奖励给1分,继续重新开始学习,如果走到地狱陷阱处罚扣1分,重新开始学习,其他得零分持续走,行动为“上、下、左、右”四个方向移动。

2.1. 强化学习结构

在一个未知“迷宫”环境中,计算机算法软件(探索机器人控制大脑)基于自身的控制策略行动。基本结构包括:

(1)智能体(Agent):探索机器人大脑,智能体的结构可以是一个神经网络,也可以是一个简单的算法,智能体的输入通常是状态(State),输出通常是策略(Policy);
(2)动作(Actions):是指动作空间。对于机器人玩迷宫游戏,只有上下左右移动方向可行动,那Actions就是上、下、左、右;
(3)状态(State):就是智能体的输入,机器人在迷宫中的位置;
(4)奖励(Reward):机器人进入某个状态时,能给智能体带来正奖励或者负奖励;
(5)环境(Environment):就是指机器人所走的迷宫,能接收action,返回state和reward。
在这里插入图片描述

2.2. 强化学习基本要素

基于马尔可夫决策过程(MDP)模型,以及贝尔曼方程求解等所实现的强化学习,基本要素包括:

(1)环境的状态 S S S,状态是对环境的描述,正如机器人在迷宫中的位置,也就是 t t t时刻环境的状态 s t s_t st,体现为环境状态集中的某一个状态,在智能体做出动作后,状态会发生变化;MDP所有状态的集合是状态空间,状态空间可以是离散或连续的。
S = s 1 , s 2 , s 3 , . . . . . . , s π S = {s_1,s_2,s_3,......,s_π} S=s1,s2,s3,......,sπ

(2)机器人的动作 A A A, 动作是对智能体行为的描述,是智能体决策的结果。 t t t时刻机器人采取的动作 A t A_t At是它的动作集中某一个动作;MDP所有可能动作的集合是动作空间,动作空间可以是离散或连续的。
A = a 1 , a 2 , a 3 , . . . . . . , a π A = {a_1,a_2,a_3,......,a_π} A=a1,a2,a3,......,aπ

(3)环境的奖励 R R R,奖励是智能体给出动作后,环境对智能体的反馈。是当前时刻状态、动作和下个时刻状态的标量函数。 t t t时刻机器人在状态 s t s_t st采取的动作 a t a_t at对应的奖励 r t + 1 r_{t+1} rt+1,会在 t + 1 t+1 t+1时刻得到;
R = R ( s t , a t , s t + 1 ) R = R(s_t,a_t,s_{t+1}) R=R(st,at,st+1)

(4)机器人的策略(policy) π π π,策略是指代表机器人采取动作的依据,即机器人会依据策略 π π π来选择动作。最常见的策略表达方式是一个条件概率分布 π ( a ∣ s ) π(a|s) π(as), 即在状态 s s s时采取动作 a a a的概率。即 π ( a ∣ s ) = P ( A t = a ∣ s t = s ) π(a|s)=P(A_t=a|s_t=s) π(as)=P(At=ast=s),此时概率大的动作被机器人选择的概率较高。

(5)环境的状态转化模型,可以理解为一个概率状态机,它可以表示为一个概率模型,即在状态 s s s下采取动作 a a a,转到下一个状态 s ′ s′ s的概率,表示为 P s s ′ a P^a_{ss′} Pssa

(6)回报(return),回报是奖励随时间步的积累,在引入轨迹的概念后,回报也是轨迹上所有奖励的总和。
G = ∑ t = 0 π − 1 r t + 1 G = \sum_{t=0}^{π-1}r_{t+1} G=t=0π1rt+1

(7)折扣因素,奖励衰减因子( γ γ γ),在 [ 0 , 1 ] [0,1] [01]之间。如果为0,则是贪婪法,即价值只由当前延时奖励决定,如果是1,则所有的后续状态奖励和当前奖励一视同仁。大多数时候,我们会取一个0到1之间的数字,即当前延时奖励的权重比后续奖励的权重大。

折扣因素主要作用:

  • 避免连续任务造成回报 G G G无限大;
  • 区分即时奖励和未来奖励的重要程度。

(8)状态价值与动作价值

  • 状态值函数 V π ( s ) V_π(s) Vπ(s)
    机器人在策略 π π π和状态 s s s时,采取行动后的状态所处的最佳的(程度)价值(value),一般用 V π ( s ) Vπ(s) Vπ(s)表示,是一个期望函数。
    价值函数 V π ( s ) V_π(s) Vπ(s)一般可以表示为下式,不同的算法会有对应的一些价值函数变种,但思路相同:
    V π ( s ) = E π ( r t + 1 + γ r t + 2 + γ 2 r t + 3 + . . . ∣ s t = s ) V_π(s)=E_π(r_{t+1}+γr_{t+2}+γ^2r_{t+3}+...|s_t=s) Vπ(s)=Eπ(rt+1+γrt+2+γ2rt+3+...st=s)
  • 状态——行为值函数(Q函数)
    机器人在策略 π π π和状态 s s s时,采取行动后的行为的所处的最佳的程度,一般用 Q π ( s ) Q_π(s) Qπ(s)表示,也是一个期望函数。
    根据策略 π π π从状态 s s s开始采取行动 a a a所获得的期望回报,也就是贝尔曼方程,如下式所述:
    Q π ( s , a ) = E π [ ∑ k = 0 ∞ γ k r t + k + 1 ∣ s t = s , a t = a ] Q_π(s,a)=E_π[\sum_{k=0}^\infty γ^kr_{t+k+1}|s_t=s,a_t = a] Qπ(s,a)=Eπ[k=0γkrt+k+1st=s,at=a]

(9)探索率 ϵ ϵ ϵ,这个比率主要用在强化学习训练迭代过程中,由于我们一般会选择使当前轮迭代价值最大的动作,但是这会导致一些较好的但我们没有执行过的动作被错过。因此我们在训练选择最优动作时,会有一定的概率ϵ不选择使当前轮迭代价值最大的动作,而选择其他的动作。

2.3. Q-learning

在这里插入图片描述
Q-learning算法就是按贝尔曼方程一直不断更新 Q table 里的值,迭代过程中根据新的值来判断要在某个 state 采取怎样的 action。

3. DQN

3.1. DQN思想

DQN与Q-leanring类似都是基于值迭代的算法,但是在普通的Q-learning中,当状态和动作空间是离散且维数不高时可使用Q-Table储存每个状态动作对的Q值,而当状态和动作空间是高维连续时,使用Q-Table不动作空间和状态太大十分困难。
所以在此处可以把Q-table更新转化为一函数拟合问题,通过拟合一个函数function来代替Q-table产生Q值,使得相近的状态得到相近的输出动作。因此我们可以想到深度神经网络对复杂特征的提取有很好效果,所以可以将DeepLearning与Reinforcement Learning结合,这就成为了DQN。

3.2. DQN 算法

我们所实践的DQN算法是指DeepMind的2015年改进型算法,算法伪代码描述如下:
在这里插入图片描述

E v e r y C s t e p s r e s e t Q ^ = Q Every C steps reset \widehat{Q} =Q EveryCstepsresetQ =Q
这其实就相当于是,我们在计算 y i y_i yi的值的时候,用的是target network,然后实际上被训练的是main network,然后每过C steps,target network的参数就会被“赋值”为main network的参数,也就是会被update一次。

3.3. DQN结构与组成

3.3.1. DQN结构概述

DQN结构整体上与强化学习(RL)结构是一致的,基于Q-learning算法结构对比,主要是智能控制的Agent内部发生改变,增加了experience replay 经验池和CNN深度神经网络。
在这里插入图片描述

3.3.2. experience replay 经验池

DQN中的经验池是学习的记忆库(类似固定行数的扩展Q-table),用来学习之前的经历,又因为Q learning 是一种离线学习法(off-policy ),它能学习实时经历,也能学习过去的经历,甚至可以学习别人的经历,所以在学习过程中随机的加入之前的经验会让神经网络更有效率。

experience replay其将系统探索环境得到的数据储存起来,这样经验池解决了相关性及非静态分布问题。他是通过在每个timestep下Agent与环境交互得到的转移样本 ( s t , a t , r t , s t + 1 ) (s_t,a_t,r_t,s_{t+1}) (st,at,rt,st+1) 储存到回放记忆网络,要训练时就随机取出一匹数据(minibatch)来训练,因此打乱样本中的相关性。

3.3.3. 深度神经网络CNN实现 Q ( s , a ) Q(s, a) Q(s,a)

如果我们以纯数学的角度来看动作值函数 Q ( s , a ) Q(s, a) Q(s,a),不过就是建立一个从状态空间 s s s到动作空间 a a a 的映射,而映射的具体形式是什么,完全可以自己定任何算法(比如有人使用随机森林),只要能够接近真实的最优 Q ( s , a ) Q(s, a) Q(s,a)就是胜利。于是用CNN完成这种映射的做法应运而生。

DQN改进版本的算法是建立两个相同结构的CNN,其中一个 CNN(MainNet)用于产生当前 Q 值,另外一个 CNN(Target)用于产生 Target Q 值,而这个 Target Q 值则是损失函数的比较基准。

(1) Q-target 目标网络
Q-targets的作用其实也是一种打乱相关性的机制,引入Q-targets会使得DQN中出现两个结构完全相同但是参数却不同的网络,预测Q估计的的网络MainNet使用的是最新的参数,而预测Q现实的神经网络

TargetNet使用的网络参数却是之前的参数, Q ( s , a ; θ i ) Q(s,a;θ_i) Q(s,a;θi)表示当前网络MainNet的输出,用来评估当前状态动作对的值函数; m a x a Q ( s ′ , a ′ ; θ ) max_aQ(s',a';θ) maxaQ(s,a;θ)表示TargetNet的输出,可以解出targetQ并根据LossFunction更新MainNet的参数,每经过一定次数的迭代,将MainNet的网络参数复制给TargetNet。

引入TargetNet后,在训练的某段时间里使目标Q值保持不变,一定程度上降低了当前Q值和目标Q值的相关性,提高了算法稳定性。

(2)Q-Main 估计网络
Q-Main 估计网络就是训练个能够接近真实的最优 Q ( s , a ) Q(s, a) Q(s,a)的无限逼近函数,
基于Q-learning 确定Loss Function

  • Q-learning 更新公式为:
    n e w Q ( s , a ) = Q ( s , a ) + α [ R ( s , a ) + γ m a x a ′ Q ′ ( s ′ , a ′ ) − Q ( s , a ) ] newQ(s,a)=Q(s,a)+\alpha[R(s,a)+\gamma max_{a'}Q'(s',a')-Q(s,a)] newQ(s,a)=Q(s,a)+α[R(s,a)+γmaxaQ(s,a)Q(s,a)]

  • DQN 的 loss function:
    L ( θ ) = E [ ( T a r g e t Q − Q ( s , a ; θ ) ) 2 ] L(θ)=E[(TargetQ−Q(s,a;θ))^2] L(θ)=E[(TargetQQ(s,a;θ))2]
    其中 θ θ θ是网络参数,目标为: T a r g e t Q = r + γ m a x Q ( s ′ , a ′ ; θ ) TargetQ=r+γmaxQ(s′,a′;θ) TargetQ=r+γmaxQ(s,a;θ)

4. 迷宫游戏DQN代码结构及解读

通过上述背景知识的介绍,下面我开始解读来自“莫烦PYTHON”代码。由于源代码关于深度学习部分为了教学只是适合处理一般的二维数据,不是为图像或复杂数据设计的。我们为了深入学习深度强化学习,试探的改为识别图像,增加环境获取图像,修改神经网络为卷积神经网络,修改记忆存储方法。

4.1. 代码结构

迷宫游戏代码有三部分组成:

  • maze_env 是迷宫环境,基于Python标准GUI库Tkinter开发
  • DQN_modified 是DQN的核心实现,是RL_brain的修改版
  • run_this 是控制执行算法的代码

代码使用工具包比较少、简洁,主要有Tensorflow和numpy,以及python自带的Tkinter 。

在run_this中,首先我们先 import 两个模块,maze_env 是我们的迷宫环境模块,maze_env 模块我们可以不深入研究,如果你对编辑环境感兴趣,可以去修改迷宫的大小和布局。DQN_modified 模块是 RL 核心的大脑部分。

实践修改代码Github地址:https://github.com/xiaoyw71/Reinforcement-learning-practice

4.2. 关于迷宫环境

Tkinter 是 Python 的标准 GUI 库。Python 使用 Tkinter 可以快速的创建 GUI 应用程序。

由于 Tkinter 是内置到 python 的安装包中、只要安装好 Python 之后就能 import Tkinter 库、而且 IDLE 也是用 Tkinter 编写而成、对于简单的图形界面 Tkinter 还是能应付自如。
在这里插入图片描述
考虑到数据平衡问题,莫烦给出只有一个陷阱和出口的迷宫环境。源代码环境状态表示是坐标数据,这样深度网络采用2层NN,为了深入学习需要,我把环境状态表示改为图片,返回50×50灰度图片做为模型的输入。

这个网络的输入实践了两种情况:

  • 一次输入 1 个 50×50 的灰度游戏屏幕(默认程序);
  • 一次输入 3 个 50×50 的灰度游戏屏幕;

获取当前窗口为图片的方法如下所示,适用于Windows环境。

    def get_image(self):
        HWND = win32gui.GetFocus() #获取当前窗口句柄
        rect=win32gui.GetWindowRect(HWND) #获取当前窗口坐标
        img=ImageGrab.grab(rect) #截取目标图像
        img = img.resize((50, 50),Image.ANTIALIAS) # 缩小尺寸
        img = img.convert('L') #转为灰度
        #img.save(str(self.fn) + ".jpeg",'jpeg') #前面一个参数是保存路径,后面一个参数是保存格式
        img = np.array(img)
        img = (img - 128) / 128 - 1  #

        return img.reshape(50,50,1)

保存到本地效果如下图所示:
在这里插入图片描述
修改后的环境代码:

import numpy as np
import time
import sys
import tkinter as tk
import win32gui
from PIL import ImageGrab , Image

UNIT = 40   # pixels
MAZE_H = 4  # grid height
MAZE_W = 4  # grid width

class Maze(tk.Tk, object):
    def __init__(self):
        super(Maze, self).__init__()
        self.action_space = ['u', 'd', 'l', 'r']
        self.n_actions = len(self.action_space)
        self.n_features = 2
        self.title('maze')
        self.geometry('{0}x{1}'.format(MAZE_H * UNIT, MAZE_H * UNIT))
        self._build_maze()

    def _build_maze(self):
        self.canvas = tk.Canvas(self, bg='white',
                           height=MAZE_H * UNIT,
                           width=MAZE_W * UNIT)
        # create grids
        for c in range(0, MAZE_W * UNIT, UNIT):
            x0, y0, x1, y1 = c, 0, c, MAZE_H * UNIT
            self.canvas.create_line(x0, y0, x1, y1)
        for r in range(0, MAZE_H * UNIT, UNIT):
            x0, y0, x1, y1 = 0, r, MAZE_W * UNIT, r
            self.canvas.create_line(x0, y0, x1, y1)
        # create origin
        origin = np.array([20, 20])
        # hell
        hell1_center = origin + np.array([UNIT * 2, UNIT])
        self.hell1 = self.canvas.create_rectangle(
            hell1_center[0] - 15, hell1_center[1] - 15,
            hell1_center[0] + 15, hell1_center[1] + 15,
            fill='black')
        # create oval
        oval_center = origin + UNIT * 2
        self.oval = self.canvas.create_oval(
            oval_center[0] - 15, oval_center[1] - 15,
            oval_center[0] + 15, oval_center[1] + 15,
            fill='yellow')

        # create red rect
        self.rect = self.canvas.create_rectangle(
            origin[0] - 15, origin[1] - 15,
            origin[0] + 15, origin[1] + 15,
            fill='red')

        # pack all
        self.canvas.pack()

    def reset(self):
        self.update()
        time.sleep(0.1)
        self.canvas.delete(self.rect)
        origin = np.array([20, 20])
        self.rect = self.canvas.create_rectangle(
            origin[0] - 15, origin[1] - 15,
            origin[0] + 15, origin[1] + 15,
            fill='red')
        # return observation
        return self.get_image()

    def step(self, action):
        s = self.canvas.coords(self.rect)
        base_action = np.array([0, 0])
        if action == 0:   # up
            if s[1] > UNIT:
                base_action[1] -= UNIT
        elif action == 1:   # down
            if s[1] < (MAZE_H - 1) * UNIT:
                base_action[1] += UNIT
        elif action == 2:   # right
            if s[0] < (MAZE_W - 1) * UNIT:
                base_action[0] += UNIT
        elif action == 3:   # left
            if s[0] > UNIT:
                base_action[0] -= UNIT

        self.canvas.move(self.rect, base_action[0], base_action[1])  # move agent

        next_coords = self.canvas.coords(self.rect)  # next state

        # reward function
        if next_coords == self.canvas.coords(self.oval):
            reward = 1
            done = True
        elif next_coords in [self.canvas.coords(self.hell1)]:
            reward = -1
            done = True
        else:
            reward = 0
            done = False

        s_ = self.get_image()
        
        return s_, reward, done

    def render(self):
        # time.sleep(0.01)
        self.update()

    def get_image(self):
        HWND = win32gui.GetFocus() #获取当前窗口句柄
        rect=win32gui.GetWindowRect(HWND) #获取当前窗口坐标
        img=ImageGrab.grab(rect) #截取目标图像
        img = img.resize((50, 50),Image.ANTIALIAS)

        img = img.convert('L') #转换为灰度图像
        img = np.array(img)
        img = (img - 128) / 128 - 1

        return img.reshape(50,50,1)

4.3. 深度强化学习

深度强化学习部分源代码很清晰,主要包括:

  • 网络结构定义—— “_build_net”
  • 存储记忆——“store_transition”
  • 选择行为——“choose_action”
  • 学习过程——“learn”

对源代码替换部分为:
(1)网络定义替换的归集函数q_network();
(2)网络定义名称分别更换为mainQ、targetQ;
(3)存储记忆替换为self.replay_buffer = deque() ,处理过程适应多维图片存取;
(4)网络模型输入替换为图片数据

import numpy as np
import tensorflow as tf
from tensorflow.contrib.layers import flatten, conv2d, fully_connected
from collections import deque  # 用于Memory
import random

np.random.seed(1)
tf.set_random_seed(1)

# Deep Q Network off-policy
class DeepQNetwork:
    def __init__(
            self,
            n_actions,
            n_features= (None, 50, 50, 1), # 输入灰度图像尺寸与维度
            learning_rate=0.01,
            reward_decay=0.9,
            e_greedy=0.9,
            replace_target_iter=300,
            memory_size=500,
            batch_size=32, # 按CNN训练经验,缩写batch
            e_greedy_increment=None,
            output_graph=False,
    ):
        self.n_actions = n_actions
        self.X_shape = n_features # (None, 50, 50, 1) #输入图像尺寸(maze_env为200*200,resize为100*100)
        self.lr = learning_rate
        self.gamma = reward_decay
        self.epsilon_max = e_greedy
        self.replace_target_iter = replace_target_iter
        self.memory_size = memory_size
        # init experience replay, the deque is a list that first-in & first-out
        self.replay_buffer = deque()        
        self.batch_size = batch_size
        self.epsilon_increment = e_greedy_increment
        self.epsilon = 0 if e_greedy_increment is not None else self.epsilon_max
        
        # total learning step
        self.learn_step_counter = 0

        # consist of [target_net, evaluate_net]
        e_params ,t_params =self._build_net()
        with tf.variable_scope('hard_replacement'):
            self.target_replace_op = [tf.assign(t, e) for t, e in zip(t_params, e_params)]

        self.sess = tf.Session()

        if output_graph:
            tf.summary.FileWriter("logs/", self.sess.graph)

        self.sess.run(tf.global_variables_initializer())
        self.cost_his = []
    # 定义卷积神经网络,详见后续网络图
    def _build_net(self):
        self.s = tf.placeholder(tf.float32, shape=self.X_shape, name='s')  # input State
        self.s_ = tf.placeholder(tf.float32, shape=self.X_shape, name='s_')  # input Next State
        self.r = tf.placeholder(tf.float32, [None, ], name='r')  # input Reward
        self.a = tf.placeholder(tf.int32, [None, ], name='a')  # input Action

        def q_network(X, name_scope):            
            # Initialize layers
            initializer = tf.contrib.layers.variance_scaling_initializer()        
            with tf.variable_scope(name_scope) as scope:         
                # initialize the convolutional layers
                layer_1 = conv2d(X, num_outputs=32, kernel_size=(5,5), stride=4, padding='SAME', weights_initializer=initializer) 
                tf.summary.histogram('layer_1',layer_1)                
                layer_2 = conv2d(layer_1, num_outputs=64, kernel_size=(4,4), stride=2, padding='SAME', weights_initializer=initializer)
                tf.summary.histogram('layer_2',layer_2)                
                layer_3 = conv2d(layer_2, num_outputs=64, kernel_size=(3,3), stride=1, padding='SAME', weights_initializer=initializer)
                tf.summary.histogram('layer_3',layer_3)
                
                # Flatten the result of layer_3 before feeding to the fully connected layer
                flat = flatten(layer_3)
       
                fc = fully_connected(flat, num_outputs=128, weights_initializer=initializer)
                tf.summary.histogram('fc',fc)
                
                output = fully_connected(fc, num_outputs=self.n_actions, activation_fn=None, weights_initializer=initializer)
                tf.summary.histogram('output',output)                        
                params = tf.get_collection(tf.GraphKeys.GLOBAL_VARIABLES, scope=scope.name)
                
                return params, output

        # we build our Q network, which takes the input X and generates Q values for all the actions in the state
        mainQ, self.q_eval = q_network(self.s, 'mainQ')
        targetQ, self.q_next = q_network(self.s, 'targetQ')

        with tf.variable_scope('q_target'):
            q_target = self.r + self.gamma * tf.reduce_max(self.q_next, axis=1, name='Qmax_s_')    # shape=(None, )
            self.q_target = tf.stop_gradient(q_target)
        with tf.variable_scope('q_eval'):
            a_indices = tf.stack([tf.range(tf.shape(self.a)[0], dtype=tf.int32), self.a], axis=1)
            self.q_eval_wrt_a = tf.gather_nd(params=self.q_eval, indices=a_indices)    # shape=(None, )
        with tf.variable_scope('loss'):
            self.loss = tf.reduce_mean(tf.squared_difference(self.q_target, self.q_eval_wrt_a, name='TD_error'))
        with tf.variable_scope('train'):
            self._train_op = tf.train.RMSPropOptimizer(self.lr).minimize(self.loss)
        
        return mainQ,targetQ


    def store_transition(self, s, a, r, s_):
        # store all the elements
        self.replay_buffer.append((s, a, r, s_))
        # if the length of replay_buffer is bigger than REPLAY_SIZE
        # delete the left value, make the len is stable
        if len(self.replay_buffer) > self.memory_size:
            self.replay_buffer.popleft()

    def choose_action(self, observation):
        # to have batch dimension when feed into tf placeholder
        if np.random.uniform() < self.epsilon:
            # forward feed the observation and get q value for every actions
            actions_value = self.sess.run(self.q_eval, feed_dict={self.s: [observation]})  #后加中括号
            action = np.argmax(actions_value)
        else:
            action = np.random.randint(0, self.n_actions)
        return action

    def learn(self):
        # check to replace target parameters
        if self.learn_step_counter % self.replace_target_iter == 0:
            self.sess.run(self.target_replace_op)
            print('\ntarget_params_replaced\n')

        # sample batch memory from all memory
        batch_memory = random.sample(self.replay_buffer, self.batch_size)
        state_batch = [data[0] for data in batch_memory]
        action_batch = [data[1] for data in batch_memory]
        reward_batch = [data[2] for data in batch_memory]
        next_state_batch = [data[3] for data in batch_memory]

        _, cost = self.sess.run(
            [self._train_op, self.loss],
            feed_dict={
                self.s: state_batch, self.a: action_batch, self.r: reward_batch, self.s_: next_state_batch,
            })

        self.cost_his.append(cost)

        # increasing epsilon
        self.epsilon = self.epsilon + self.epsilon_increment if self.epsilon < self.epsilon_max else self.epsilon_max
        self.learn_step_counter += 1

    def plot_cost(self):
        import matplotlib.pyplot as plt
        plt.plot(np.arange(len(self.cost_his)), self.cost_his)
        plt.ylabel('Cost')
        plt.xlabel('training steps')
        plt.show()

运行计算图如下图所示:
在这里插入图片描述

中输入代码路径下,运行:tensorboard --logdir logs

4.4. 运行训练策略

运行部分代码,仍旧使用原代码,未做改变。

注:由于软件是使用当前活动窗口,训练期间不要动其他程序,而且关闭屏幕保护等可能导致改变当前活动窗口的情况发生的设置或操作。

from study.DQN.maze_env import Maze
from study.DQN.DQN_modified import DeepQNetwork

def run_maze():
    step = 0 
    for episode in range(300):
        # initial observation
        observation = env.reset()

        while True:
            # fresh env
            env.render()

            # RL choose action based on observation
            action = RL.choose_action(observation)

            # RL take action and get next observation and reward
            observation_, reward, done = env.step(action)

            RL.store_transition(observation, action, reward, observation_)

            if (step > 200) and (step % 5 == 0):
                RL.learn()

            # swap observation
            observation = observation_

            # break while loop when end of this episode
            if done:
                break
            step += 1

    # end of game
    print('game over')
    env.destroy()

if __name__ == "__main__":
    # maze game
    env = Maze()
    RL = DeepQNetwork(env.n_actions,
                      learning_rate=0.01,
                      reward_decay=0.9,
                      e_greedy=0.9,
                      replace_target_iter=200,
                      memory_size=2000,
                      output_graph=True
                      )
    env.after(100, run_maze)
    env.mainloop()
    
    RL.plot_cost()

4.5. 模型训练经验小节

我们观测在不同输入、不同网络情况下,Cost曲线及模型收敛情况。

(1)这是本文中代码输出结果,对于状态的输入,一次为一张图,也就是当前图及对应动作与下一张图关联,接近3500步完成300轮,达到如下误差效果。
在这里插入图片描述
(2)这是对本文中代码再微调后输出结果,对于状态的输入,一次为三张图,也就是当前图及对应动作不仅与下一张图关联,也与前两步(图)相关,2500多步完成300轮,达到如下误差效果。

代码详见Hub:https://github.com/xiaoyw71/Reinforcement-learning-practice/tree/main/DQN_axis
在这里插入图片描述
Cost is 0.002506856806576252
合并前两张图的处理方法是使用Numpy构建多维数据,输入的定义为:

n_features= (None, 50, 50, 3), # 输入“带前两张”灰度图像尺寸与维度

图像是由环境部分修改而来:

    def reset(self):
        ......
        img = self.get_image()
        self.memory_img = np.stack((img,img,img),axis=2)
        
        return self.memory_img

    def step(self, action):
        ......
        img = self.get_image()
        self.memory_img = np.append(img.reshape(50,50,1),self.memory_img[:,:,:2],axis=2)        
        s_ = self.memory_img
        
        return s_, reward, done

(3)这是莫烦源代码输出结果,对于状态的输入,一次为一组坐标数字,也就是当前图及对应动作与下一张图关联,2500多步完成300轮,达到如下误差效果。

源代码为2层神经网络,数据维度少,速度快。
在这里插入图片描述

5. 展望

事实上,对于状态 s ∈ S s∈S sS和动作 a ∈ A a∈A aA、值函数 v ∈ Q v∈Q vQ,存在这样一个映射: S × A → Q S \times A \rightarrow Q S×AQ,由此求解值函数的问题转化为监督学习的问题,而监督学习是一种常见且易于解决的问题。线性回归(Linear Regression) 、支持向量机(Support Vector Machine)、决策树(Decision Tree), 以及神经网络(Neural Network )等都可以用来解决此类问题,DQN利用深度卷积网络(Convolutional Neural Networks,CNN)来逼近值函数。

通过上述实践可以认识到,采用适合的逼近值函数,将产生事半功倍的效果。

强化学习在解决问题是比较有探索性的,对于数据分析,“State”可是使用图片表示状态,例如客户加油/购物记录(集)代表某种状态(如流失预警、价格敏感等),也可以直接使用数据表示,例如客户全生命周期管理流程中所有状态,从新客户开始,到流失预警,直到流失,以及流失挽回。

参考:

[1].《【强化学习】Deep Q Network(DQN)算法详解》 CSDN博客 , shura_R ,2018年6月
[2].《深度强化学习之DQN-深度学习与强化学习的成功结合》 OSCHINA 社区 ,CristianoC , 2019年7月
[3].《强化学习之迷宫Q-Learning实践笔记——入门篇》 CSDN博客 ,肖永威 ,2021年1月
[4].《DQN 论文解读》 知乎 ,口仆的学习笔记 ,2020年8月
[5].《论文趣读:人工智能里程碑?回顾2015年登上Nature的DQN(全文翻译+批注)》 CSDN博客,PiperNest (同公众号),2020年10月
[6].《深度强化学习——DQN》 CSDN博客 , 草帽B-O-Y ,2017年6月
[7].《深度强化学习(DQN-Deep Q Network)之应用-Flappy Bird》 腾讯云 ,用户7225427, 2020年9月
[8].《DQN 思维决策》 莫烦PYTHON ,莫烦 ,2017年02月
[9].《深度强化学习(三):从Q-Learning到DQN》 简书 ,fromeast ,2019年04月
[10].《使用强化学习建立下一个最佳活动(或称行动营销)模型【译文初稿】》 CSDN博客 , 肖永威 ,2021年01月

已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 酷酷鲨 设计师:CSDN官方博客 返回首页