DQN

神经网络的作用

  • 将状态和动作当成神经网络的输入, 然后经过神经网络分析后得到动作的 Q 值, 这样我们就没必要在表格中记录 Q 值。而是直接使用神经网络生成 Q 值.
  • 也能只输入状态值, 输出所有的动作值, 然后按照 Q learning 的原则, 直接选择拥有最大值的动作当做下一步要做的动作.

神经网络接受外部的信息, 相当于眼睛鼻子耳朵收集信息, 然后通过大脑加工输出每种动作的值,最后通过强化学习的方式选择动作.

更新神经网络

基于第二种神经网络
在这里插入图片描述

  • 需要 a1, a2 正确的Q值, 这个 Q 值我们就用之前在 Q learning 中的 Q 现实来代替. 还需要一个 Q 估计来实现神经网络的更新.
  • 所以神经网络的的参数就是老的 NN 参数 加学习率 alpha 乘以 Q 现实 和 Q 估计 的差距.
    在这里插入图片描述
    通过 NN 预测出Q(s2, a1) 和 Q(s2,a2) 的值, 这就是 Q 估计. 然后我们选取 Q 估计中最大值的动作来换取环境中的奖励 reward. 而 Q 现实中也包含从神经网络分析出来的两个 Q 估计值, 不过这个 Q 估计是针对于下一步在 s’ 的估计. 最后再通过刚刚所说的算法更新神经网络中的参数.

Experience replay 和 Fixed Q-targets

Experience Replay

  • 深度神经网络,要求数据满足独立同分布。但 Q Learning 算法得到的样本前后是有关系的。为了打破数据之间的关联性,Experience Replay 方法通过存储-采样的方法将这个关联性打破了。

Fixed Q-targets

  • 也是一种打乱相关性的机理, 使用 fixed Q-targets, 就会在 DQN 中使用到两个结构相同但参数不同的神经网络, 预测 Q估计 的神经网络具备最新的参数, 而预测 Q 现实 的神经网络使用的参数则是很久以前的.

实例

在这里插入图片描述
建立两个神经网络eval_net用来训练,target_net用来暂存

并保存
在这里插入图片描述
RL_brain


import numpy as np
import pandas as pd
from RLearning.DQN.mazeEnv import Maze
import torch
import torch.nn as nn


class DeepQNetwork():
    def __init__(self, n_actions, n_features, learning_rate=0.01, reward_decay=0.9,
                 e_greedy=0.9, replace_target_iter=200, memory_size=2000, batch_size=32,
                 e_greedy_increment=None):

        self.n_actions = n_actions  # 输出多少个action的值
        self.n_features = n_features  #接受多少个observation
        self.lr = learning_rate
        self.gamma = reward_decay#衰减度
        self.epsilon_max = e_greedy#贪婪度
        # self.q_table = pd.DataFrame(columns=self.actions, dtype=np.float64)#纵轴是有多少个state横轴标签是可用action
        # 初始化qtable是一个空的dataframe
        self.replace_target_iter = replace_target_iter  # 多少步更换一次
        self.memory_size = memory_size  # 整个记忆库的数据
        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
        self.memory_counter=0
        self.learn_step_counter = 0  # 记录学了多少步 epsilon根据counter不断提高
        # 全0 memory [s, a, r, s_]记忆库
        self.memory = np.zeros((self.memory_size, n_features * 2 + 2))  # 行为200条记忆 列中+2为a,r
        # 建立[target_net, evaluate_net]
        self.eval_net, self.target_net = self.buildNet(n_features, n_actions), self.buildNet(n_features, n_actions)
        self.cost_his = []  # 记录误差
        self.optimizer = torch.optim.Adam(self.eval_net.parameters(), lr=self.lr)#优化器
        self.loss_func = nn.MSELoss()#损失函数

    def store_transition(self, s, a, r, s_):
        transition = np.hstack((s, [a, r], s_))  # 把所有的记忆捆在一起
        index = self.memory_counter % self.memory_size  # 如果memory_counter超过了MEMORY_CAPACITY(记忆上限)覆盖掉老的记忆
        self.memory[index, :] = transition  # 存到相对性的位置
        self.memory_counter += 1

    def choose_action(self, x):
        x = torch.unsqueeze(torch.FloatTensor(x), 0)
        if np.random.uniform() < self.epsilon:  # 如果随机数小于0.9
            # 根据最优选action
            actions_value = self.eval_net.forward(x)
            action = torch.max(actions_value, 1)[1]
            action = int(action)
        else:
            # 10%的情况随机选择action
            action = np.random.randint(0, self.n_actions)
        return action

    def buildNet(self, n_features, n_actions):
       return torch.nn.Sequential(
            torch.nn.Linear(n_features, 10),
            torch.nn.ReLU(),
            torch.nn.Linear(10, n_actions),
        )

    def learn(self):
        if self.learn_step_counter % self.replace_target_iter == 0:  # 到多少步数就更新
            self.target_net.load_state_dict(self.eval_net.state_dict())  # 将eval_net参数赋值到target_net

        self.learn_step_counter += 1
        # 让eval_net每步都要更新
        sample_index = np.random.choice(self.memory_size, self.batch_size)  # 从记忆库中随机抽取一些记忆
        b_memory = self.memory[sample_index, :]
        # 将存储的记忆打包
        b_s = torch.FloatTensor(b_memory[:, :self.n_features])
        b_a = torch.LongTensor(b_memory[:, self.n_features:self.n_features + 1].astype(int))
        b_r = torch.FloatTensor(b_memory[:, self.n_features + 1:self.n_features + 2])
        b_s_ = torch.FloatTensor(b_memory[:, -self.n_features:])

        q_eval = self.eval_net(b_s).gather(1, b_a)  # 输入现在的状态通过forward(self, x)生成abctions_value
        # 根据所有动作的价值选取当初施加动作上的动作价值的价值
        # 如:输出了两个向左走向右走动作上的价值然后根据当初选择动作(如向左),变成q_eval(当初向左走的价值是多少)
        q_next = self.target_net(b_s_).detach()
        q_target = b_r + self.gamma * q_next.max(1)[0].view(self.batch_size, 1)  # shape (batch, 1)
        loss = self.loss_func(q_eval, q_target)
        self.optimizer.zero_grad()
        loss.backward()
        self.optimizer.step()

    def save(self, save_path):
        torch.save(self.eval_net.state_dict(), save_path)  # 保存整个神经网络的参数


if __name__ == '__main__':
    # __name__ 获取的是当前文件的name属性,只有在自己的文件下这个name才是main,是以在别的文件调用时这部分不会被执行
    # 这里相当于本文件的测试区域,可以直接在这里执行
    env = Maze()
    dqn = DeepQNetwork(env.n_actions, env.dim_state)  
    print('\nCollecting experience...')
    for i_episode in range(10):

        s = env.reset()
        ep_r = 0
        while True:
            env.render()
            a = dqn.choose_action(s)
            s_, r, done = env.step(a)   # gym中的环境通常有4个返回值,最后一个是大抵就保存了一些日志信息
            dqn.store_transition(s, a, r, s_)
            ep_r += r   # 记录总的r,应该大抵可以算作一种衡量路径优劣的指标吧
           # if dqn.memory_counter > dqn.memory_size:
            if dqn.memory_counter > 200:
                dqn.learn()
                if done:
                    print('Ep: ', i_episode, '| Ep_r: ', round(ep_r, 2))
                    break
            s = s_
            dqn.save('data/models/dqn_params.pkl')

    print('\ntest...')
    # 要是直接在这里写load的画也没什么意义,因为现在dqn还是上面的dqn,还保留着这些参数




maze环境

# 构造gym Maze环境
import gym
from gym import spaces
import numpy as np
import time

from gym.envs.classic_control import rendering


class Maze(gym.Env):
    metadata = {
        'render.modes': ['human', 'rgb_array'],
        'video.frames_per_second': 2
    }

    # 初始化动作空间与状态空间等环境中会用的全局变量可以声明为类(self.)的变量
    def __init__(self):
        self.action_space = spaces.Discrete(5)  # 0, 1, 2,3,4: 不动,上下左右
        self.observation_space = spaces.Box(np.array([1, 1]), np.array([4, 4]), dtype=np.int)
        self.n_actions = self.action_space.n
        self.dim_state = self.observation_space.shape[0] # 转态向量维度
        self.state = None
        self.target = {(4,2): 20}   # 安全/目标状态
        self.danger = {(2,2): -20, (3,3): -20}  # 危险状态

        self.viewer = rendering.Viewer(500, 500, 'maze')
        self.gridsize = 100


    # 用来处理状态的转换逻辑
    # 返回动作的回报、下一时刻的状态、以及是否结束当前episode及调试信息
    def step(self, action):
        assert self.action_space.contains(action), "%r (%s) invalid" % (action, type(action))
        x, y = self.state
        if action == 0:  # 不动
            x = x
            y = y
        elif action == 1:  # 上
            x = x
            y = y + 1
        elif action == 2:  # 下
            x = x
            y = y - 1
        elif action == 3:  # 左
            x = x - 1
            y = y
        elif action == 4:  # 右
            x = x + 1
            y = y

        next_state = np.array([x,y])
        self.state = next_state if self.observation_space.contains(next_state) else self.state

        self.counts += 1

        s = tuple(self.state)
        if s in self.danger.keys():
            reward = self.danger[s]
            done = True
        elif s in self.target.keys():
            reward = self.target[s]
            done = True
        else:
            reward = -self._dis(list(self.target)[0])
            # print(reward)
            # reward = -1
            # reward = 0
            done = False

        return self.state, reward, done

    # 用于在每轮开始之前重置智能体的状态,把环境恢复到最开始
    def reset(self, startstate=None):
        '''
        :param startstate: (1,1)
        :return:
        '''
        if startstate==None:
            self.state = self.observation_space.sample()
        else:
            self.state = startstate
        self.counts = 0
        return self.state

    # metadata、render()、close()是与图像显示有关的
    # render()绘制可视化环境的部分
    def render(self, mode='human', draw_dict=False, states=None, states_label=None):
        # 绘制网格
        for i in range(5):
            # 竖线
            self.viewer.draw_line(
                (50, 50),
                (50, 450),
                color=(0, 0, 0)
            ).add_attr(rendering.Transform((100 * i, 0)))
            # 横线
            self.viewer.draw_line(
                (50, 50), (450, 50)).add_attr(rendering.Transform((0, 100 * i)))

        # 绘制出口(安全状态)
        for state in self.target:
            self.drawrectangle2(state, color=(0, 1, 0))

        # 绘制危险区域
        for state in self.danger:
            self.drawrectangle2(state, color=(1, 0, 0))

        # 绘制当前state的位置(圆)
        center = (
            50 + self.state[0] * self.gridsize - 0.5 * self.gridsize,
            50 + self.state[1] * self.gridsize - 0.5 * self.gridsize)
        self.viewer.draw_circle(
            48, 30, filled=True, color=(1, 1, 0)).add_attr(rendering.Transform(center))

        if draw_dict:
            for s, l in zip(states, states_label):
                self._drawDict(s, l, color=(.5, .5, 1))

        return self.viewer.render(return_rgb_array=mode == 'human')

    def close(self):
        if self.viewer:
            self.viewer.close()

    def drawrectangle(self, point, width, height, **attrs):
        '''
        画矩形
        :param point: 左下角的坐标(480,320)
        :param width: 横向长度
        :param height: 竖向长度
        :param attrs: 其他参数,such as  color=(0,0,255), linewidth=5
        :return:
        '''
        points = [point,
                  (point[0] + width, point[1]),
                  (point[0] + width, point[1] + height),
                  (point[0], point[1] + height)]
        self.viewer.draw_polygon(points, **attrs)

    def drawrectangle2(self, point, **attrs):
        '''
        画一个正多边形
        :param point: 格点坐标(4,3)
        :param attrs: 其他参数,such as  color=(0,0,1), linewidth=5
        :return:
        '''
        size = 100
        center = (50 + point[0] * size - 0.5 * size, 50 + point[1] * size - 0.5 * size)
        radius = 100 / np.sqrt(2)
        res = 4
        points = []
        for i in range(res):
            ang = 2 * np.pi * (i - 0.5) / res
            points.append((np.cos(ang) * radius, np.sin(ang) * radius))

        self.viewer.draw_polygon(points, filled=True, **attrs).add_attr(rendering.Transform(center))

    def _drawgrid(self, s, **attrs):
        point = (s[0] * self.gridsize, s[1] * self.gridsize)  # 方格坐标右上角的像素坐标
        angles = np.linspace(0, 2 * np.pi, 4 + 1)-0.25*np.pi  # 角度值的起始向上为0度方向,顺时针为角度增大方向
        points = [(0.5 * self.gridsize * np.sin(t), 0.5 * self.gridsize * np.cos(t)) for t in angles]
        self.viewer.draw_polygon(points, **attrs).add_attr(rendering.Transform((point[0], point[1])))


    def _drawDict(self, s, l, **attrs):
        if l==0:
            self._drawgrid(s, **attrs)
        else:
            point = (s[0] * self.gridsize, s[1] * self.gridsize)    # 方格坐标右上角的像素坐标
            angles = np.linspace(0, 2*np.pi, 3+1)   # 角度值的起始向上为0度方向,顺时针为角度增大方向
            if l==1:
                pass
            elif l==2:
                angles = angles+np.pi
            elif l==3:
                angles = angles+1.5*np.pi
            elif l==4:
                angles = angles+0.5*np.pi
            points = [(0.5*self.gridsize*np.sin(t), 0.5*self.gridsize*np.cos(t)) for t in angles]
            self.viewer.draw_polygon(points, **attrs).add_attr(rendering.Transform((point[0], point[1])))
            # 因为point是方格右上角的的坐标,也就巧合不用加画布边缘(5,5)的偏移

    def _dis(self, state):
        return np.sqrt((self.state[0]-state[0])**2+(self.state[1]-state[1])**2)

if __name__ == '__main__':
    env = Maze()
    env.reset()

    states = [(x, y) for x in range(1, 5) for y in range(1, 5)]
    labels = [3] * len(states)  # 方向测试
    env.render(draw_dict=True, states=states, states_label=labels)
    time.sleep(50)

    env.close()

run_this测试部分

from RL_brain import DeepQNetwork
from mazeEnv import Maze
import torch
import time


def train():
    env = Maze()
    dqn = DeepQNetwork(env.n_actions, env.dim_state)
    print('\nCollecting experience...')
    for i_episode in range(200):
        s = env.reset()#清空环境
        env.render()
        ep_r = 0
        while True:
            a = dqn.choose_action(s)
            #选择动作
            s_, r, done = env.step(a)  # gym中的环境通常有4个返回值,最后一个是大抵就保存了一些日志信息
            env.render()
            dqn.store_transition(s, a, r, s_)
            ep_r += r  # 记录总的r,应该大抵可以算作一种衡量路径优劣的指标吧

            # if dqn.memory_counter > dqn.memory_size:
            if dqn.memory_counter > 200:
                dqn.learn()
                if done:
                    print('Ep: ', i_episode, '| Ep_r: ', round(ep_r, 2))
                    break
            s = s_
            dqn.save('data/models/dqn_params.pkl')#保存训练结果
    env.close()

def test():
    env = Maze()
    dqn = DeepQNetwork(env.n_actions, env.dim_state)  # 这里是新建的一个,它的参数都是初始化的
    dqn.eval_net.load_state_dict(torch.load('data/models/dqn_params.pkl'))
    print('\nTesting...')
    for i_episode in range(10):
        s = env.reset()
        time.sleep(0.5)     # test时做延时,为了更好的观察效果
        ep_r = 0
        while True:
            a = dqn.choose_action(s)
            # take action
            s_, r, done = env.step(a)  # gym中的环境通常有4个返回值,最后一个是大抵就保存了一些日志信息
            env.render()
            time.sleep(0.5)
            ep_r += r  # 这个的意思是记录一下总的r,应该大抵可以算作一种衡量路径优劣的指标吧
            # 测试过程就没有存储状态和学习的过程了
            if done:
                print('Epoch{} | ep_r: {}'.format(i_episode, ep_r))
                break
            s = s_

def test2():
    env = Maze()
    dqn = DeepQNetwork(env.n_actions, env.dim_state)  # 这里是新建的一个,它的参数都是初始化的
    dqn.eval_net.load_state_dict(torch.load('data/models/dqn_params.pkl'))

    # 测试部分
    dqn.epsilon = 1     # 这里就取消了随机选动作的可能,全部由网络生成
    states = [(x, y) for x in range(1, 5) for y in range(1, 5)] # 所有的动作空间
    t_states = torch.tensor(states).type(torch.float)   # 必须转换成张量才能被pytorch的网络识别,cpu下默认得是float的才行
    labels = torch.max(dqn.eval_net(t_states), 1)[1]
    env.render(draw_dict=True, states=states, states_label=labels)  # 出图
    time.sleep(50)

    env.close()


if __name__ == '__main__':
    train()
    # test()
    test2()
    print('over~')
  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值