《边做边学深度强化学习:PyTorch程序设计实践》——5深度强化学习DQN

5.1 深度强化学习DQN

在表格表示的Q学习中,行号表示智能体的状态,列号对应智能体的动作,表格存储的时动作价值Q(s,a),这种表示的问题是,随着状态变量类型数量增加,每个变量被精细的离散化的话,表格中的行数会变得很大,如果以图像作为状态,每个像素对应于状态变量,50像素的方哥具有2500多个状态变量,用表格表示的强化学习解决大量状态的任务是不显示的。

采用神经网络表示动作价值函数,神经网络的输入时每个状态变量的值。输入层的神经元数和状态变量的数量相同。CartPole有位置、速度、角度、角速度四个变量,因此有四个输入神经元,输入神经网络时不需要离散化,输出层中的神经元数是动作类型的数量。CartPole有两种,左推和右推。输出层中神经元输出的值是动作价值函数 Q ( s t , a t ) Q(s_t,a_t) Q(st,at)的值,也就是说,它输出在采用对应于该神经元的动作之后所获得的折扣奖励总和。通过比较输出层各神经元输出的折扣奖励来确定行动,这种情况下,深度学习不是一个分类问题,而是一个回归问题,需要求解具体的数值。

更新Q学习动作价值函数Q的公式为:
Q ( s t , a t ) = Q ( s t , a t ) + η ∗ ( R t + 1 + γ max ⁡ a ∗ Q ( s t + 1 , a ) − Q ( s t , a t ) ) Q(s_t,a_t)=Q(s_t,a_t)+\eta*(R_{t+1}+\gamma\max\limits_a *Q(s_{t+1},a)-Q(s_t,a_t)) Q(st,at)=Q(st,at)+η(Rt+1+γamaxQ(st+1,a)Q(st,at))
Q ( s t , a t ) = R t + 1 + γ max ⁡ a Q ( s t + 1 , a ) Q(s_t,a_t)=R_{t+1}+\gamma\max\limits_a Q(s_{t+1},a) Q(st,at)=Rt+1+γamaxQ(st+1,a)
输出层输出的值是 Q ( s t , a t ) Q(s_t,a_t) Q(st,at),学习使得输出值和 R t + 1 + γ max ⁡ a Q ( s t + 1 , a ) R_{t+1}+\gamma\max\limits_a Q(s_{t+1},a) Rt+1+γamaxQ(st+1,a)接近。误差函数写为:
E ( s t , a t ) = ( R t + 1 + γ max ⁡ a Q ( s t + 1 , a ) − Q ( s t , a t ) ) 2 E(s_t,a_t)=(R_{t+1}+\gamma\max\limits_a Q(s_{t+1},a)-Q(s_t,a_t))^2 E(st,at)=(Rt+1+γamaxQ(st+1,a)Q(st,at))2
用图表示为:
在这里插入图片描述

5.2实现DQN的4个要点

为了让DQN能够稳定学习,第一个要点是经验回放(experience replay),DQN不像表格表示的Q学习意义,每一步都学习该步的内容,而是将每个步骤的内容储在经验池中并随机从经验池中提取内容(replay)让神经网络学习。每个步骤的内容也称为转换(transition),神经网络连续地学习时间上相关度高的内容,从而出现连接参数难以稳定的问题。借助经验回放,可以使用经验池多个步骤的经验,就可以使用小批量学习来训练神经网络。

第二个要点是固定目标网络(fixed target Q-Network)。有两种类型神经网络:确定动作的主网络(main network)和计算误差函数时确定动作价值的目标网络(target-network)。DQN在更新 Q ( s t , a ) Q(s_t,a) Q(st,a)时,必须要下一个状态的价值函数 Q ( s t + 1 , a ) Q(s_{t+1},a) Q(st+1,a),如果这两个值相同,则会出现Q函数的学习趋于不稳定。因此,当需要更新所需的 max ⁡ a Q ( s t + 1 , a ) \max\limits_a Q(s_{t+1},a) amaxQ(st+1,a),使用一段时间之前的另一个Q函数(固定目标Q网络),目标网络被主网络周期性覆盖。

第三个要点时奖励的裁剪(clipping)。这里采用将在每个步骤中获得的奖励固定为-1、0或1的方法。它具有以下优点:无论任务如何,都可以使用相同的超参数来执行DQN。

第四个要点损失函数是使用Huber函数,而不是平方误差函数,误差在-1~1之间取误差的平方,其他取绝对值

5.3实现DQN

  1. 实现小批量学习以实现经验回放和模拟固定目标Q网络。每个步骤的转换(状态,动作,下一个状态,奖励)存储在经验池中。小批量指的是从该经验池中随机地取出多个步骤的转换数据块。当杆掉落或连续站立200步时游戏结束,此时不存在下一个状态,因此需要针对下一个状态是否存在来设计实现方法。
  2. Pytorch可以有效处理小批量数据,熟悉方法
  3. 注意变量的类型。CartPole处理NumPy的变量,pytorch使用Tensor类型处理变量
  4. 注意变量的大小。
  5. 注意使用namedtuple。可以命名并保存CartPole观察到的状态变量值
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import animation
from IPython.display import HTML
import gym
import random
import numpy as np

# 动画显示函数
def display_frames_as_gif(frames):
    plt.figure(figsize=(frames[0].shape[1] / 72.0, frames[0].shape[0] / 72.0), dpi=72)
    patch = plt.imshow(frames[0])
    plt.axis('off')

    def animate(i):
        patch.set_data(frames[i])
    
    anim = animation.FuncAnimation(plt.gcf(), animate, frames=len(frames), interval=50)
    anim.save('./image/movie_cartpole_DQN.mp4')
    plt.close()  # 防止显示两个输出
    return HTML(anim.to_jshtml())
from collections import namedtuple
#namedtuple就是有名字的元组,使得元组有键名,以便在DQN访问状态和动作值

Tr = namedtuple('tr',('name_a','value_b'))
Tr_object = Tr('名称为A',100)
print(Tr_object)
print(Tr_object.value_b)
tr(name_a='名称为A', value_b=100)
100
from collections import namedtuple
#namedtuple就是有名字的元组,使得元组有键名,以便在DQN访问状态和动作值

Transition = namedtuple('Transition', ('state', 'action', 'next_state', 'reward'))

ENV = 'CartPole-v0'
GAMMA = 0.99
MAX_STEPS = 200
NUM_EPISODES = 500

#为了实现小批量学习,实现了内存类ReplayMemory来存储经验数据。ReplayMemory
class ReplayMemory:
    '''push函数保存步骤中的transition,随机选择的sample函数'''
    def __init__(self,CAPACITY) -> None:
        self.capacity = CAPACITY
        self.memory = []
        self.index = 0

    def push(self,state,action,state_next,reward):
        '''将transition(state,action,state_next,reward)保存在存储器中'''

        if len(self.memory) < self.capacity:
            self.memory.append(None)
        
        self.memory[self.index] = Transition(state,action,state_next,reward)

        self.index = (self.index + 1) % self.capacity #保存的index移动一位

    def sample(self,batch_size):
        return random.sample(self.memory,batch_size)
    
    def __len__(self):
        '''返回当前memory长度'''
        return len(self.memory)

第3章解释的表格表示的Q学习中,Brain类有一个表,但这里有一个神经网络,使用函数replay和函数decision_action。函数replay从内存类中获取小批量数据,学习神经网络连接参数,并更新Q函数。函数decision_action遵循ε-贪婪法,返回随机选取的动作或在当前状态下具有最高Q值的动作的索引index。

import random
import torch
from torch import nn
from torch import optim
import torch.nn.functional as F
BATCH_SIZE = 32
CAPACITY = 10000

# 它是成为智能体的大脑的类,它执行DQN
# 将Q函数定义为深度学习网络

import random
import torch
from torch import nn
from torch import optim
import torch.nn.functional as F

BATCH_SIZE = 32
CAPACITY = 10000


class Brain:
    def __init__(self, num_states, num_actions):
        self.num_actions = num_actions  # 获取CartPole的两个动作(向左或向右)

        # 创建存储经验的对象
        self.memory = ReplayMemory(CAPACITY)

        # 建立神经网络
        self.model = nn.Sequential()
        self.model.add_module('fc1', nn.Linear(num_states, 32))
        self.model.add_module('relu1', nn.ReLU())
        self.model.add_module('fc2', nn.Linear(32, 32))
        self.model.add_module('relu2', nn.ReLU())
        self.model.add_module('fc3', nn.Linear(32, num_actions))

        print(self.model)  # 输出网络形状

        # 设置优化方法
        self.optimizer = optim.Adam(self.model.parameters(), lr=0.0001)

    def replay(self):
        '''通过Experience Replay学习神经网络的连接参数'''

        # -----------------------------------------
        # 1. 检查经验池的大小
        # -----------------------------------------
        # 1.1 当内存大小小于小批量数据时不执行任何操作
        if len(self.memory) < BATCH_SIZE:
            return

        # -----------------------------------------
        # 2. 创建小批量数据
        # -----------------------------------------
        # 2.1 从经验池中获取小批量数据
        transitions = self.memory.sample(BATCH_SIZE)

        # 2.2 将每个变量转换为与小批量数据对应的形式
        # 得到的transitions存储了一个BATCH_SIZE的(state,action,state_next,reward)
        #即(state,action,state_next,reward)×BATCH_SIZE
        #想把它变成小批量数据。换句话说
        #设为(state×BATCH_SIZE,action×BATCH_SIZE,state_next×BATCH_SIZE,reward×BATCH_SIZE)
        batch = Transition(*zip(*transitions))

        # 2.3 将每个变量的元素转换为与小批量数据相对应的形式,并将其设置为变量,以便可以在网络中进行处理
        # 例如,对于state,形状为[torch.FloatTensor of size 1×4]
        # 将其转换为 torch.FloatTensor of size BATCH_SIZEx4 
        # 为状态,动作,奖励,非最终状态小批量创建变量
        # cat代表Concatenates(级联)
        state_batch = torch.cat(batch.state)
        action_batch = torch.cat(batch.action)
        reward_batch = torch.cat(batch.reward)
        non_final_next_states = torch.cat([s for s in batch.next_state
                                           if s is not None])

        # -----------------------------------------
        # 3. 求取Q(s_t,a_t)值作为监督信号
        # -----------------------------------------
        # 3.1 将网络切换到推理模式
        self.model.eval()

        # 3.2 求取网络输出的Q(s_t,a_t)
        # self.model(state_batch)输出左右两个Q值
        # [torch.FloatTensor of size BATCH_SIZEx2]になっている。
        # 为了求得与从此处执行的动作a_t对应的Q值,找到由action_batch执行的动作a_t是右还是左的index
        # 用gather获得相应的Q值。
        state_action_values = self.model(state_batch).gather(1, action_batch)

        # 3.3 求取max{Q(s_t+1, a)}値。但是,请注意以下状态

        # 创建一个索引掩码以检查cartpole是否未完成,且具有next_state
        non_final_mask = torch.ByteTensor(tuple(map(lambda s: s is not None,
                                                    batch.next_state)))
        # 首先全部设置为0
        next_state_values = torch.zeros(BATCH_SIZE)

        # 求取具有下一状态的index的最大Q值
        # 访问输出并通过max(1)查找列方向上的最大值[value,index]。
        # 并输出其Q值(index=0)
        # 用detach取出该值
        next_state_values[non_final_mask] = self.model(
            non_final_next_states).max(1)[0].detach()

        # 3.4 从Q公式中求取Q(s_t,a_t)值作为监督信息
        expected_state_action_values = reward_batch + GAMMA * next_state_values

        # -----------------------------------------
        # 4. 更新连接参数
        # -----------------------------------------
        # 4.1 将网络切换到训练模式
        self.model.train()

        # 4.2 计算损失函数(smooth_l1_loss是Huberloss)
        # expected_state_action_values的
        # size是[minbatch],通过unsqueeze得到[minibatch x 1]
        loss = F.smooth_l1_loss(state_action_values,
                                expected_state_action_values.unsqueeze(1))

        # 4.3 更新联接参数
        self.optimizer.zero_grad()  # 重置渐变
        loss.backward()  # 计算反向传播
        self.optimizer.step()  # 更新连接参数

    def decide_action(self, state, episode):
        '''根据当前状态确定动作'''
        # 采用ε-greedy法逐步采用最佳动作
        epsilon = 0.5 * (1 / (episode + 1))

        if epsilon <= np.random.uniform(0, 1):
            self.model.eval()  # 将网络切换到推理模式
            with torch.no_grad():
                action = self.model(state).max(1)[1].view(1, 1)
            # 获取网络最大值的索引index= max(1)[1]
            # .view(1,1)将[torch.LongTensor of size 1] 转换为 size 1x1 大小

        else:
            # 随机返回0,1的动作
            action = torch.LongTensor(
                [[random.randrange(self.num_actions)]])  # 随机返回0,1的动作
            # action的形式为[torch.LongTensor of size 1x1]

        return action

函数replay执行了以下四个步骤。1)检查经验池大小。2)创建小批量数据。3)求取将成为监督信息的Q(st,at)值。4)更新连接参数。
在这里插入图片描述

定义一个Agent类,它是一个带有杆的小车(Cart)对象。实现内容与第3章表格表示的Q学习几乎相同。与第3章中Q学习的不同之处在于存在函数memorize。使用此功能可将已经学过的数据(transition)存储在经验池中。其他函数与第3章中的表格表示的实现示例相同,但请注意参数略有不同。

class Agent:

    '''CartPole智能体,带有杆的小车'''
    def __init__(self,num_states,num_actions) -> None:
        self.brain = Brain(num_states,num_actions)
        #为智能体创建大脑以做出决策

    def update_q_function(self):
        '''Q函数的更新'''
        self.brain.replay

    def get_action(self,state,episode):
        '''动作的确定'''
        action = self.brain.decide_action(state,episode)
        return action
    
    def memorize(self,state,action,state_next,reward):

        self.brain.memory.push(state,action,state_next,reward)

接下来,定义运行CartPole的环境类。基本上与第3章中的Q学习相同,这里有一点变化。第3章与深度Q学习的主要区别在于CartPole的观察结果observation是按原样用于状态state的。不像表格表示那样进行离散化。此外,准备一个列表,该列表存储过去10轮内连续站立的步数,并通过查看其平均值使学习进度易于表示。

# 这是一个执行CartPole的环境类


class Environment:

    def __init__(self):
        self.env = gym.make(ENV)  # 设定要执行的任务
        num_states = self.env.observation_space.shape[0]  # 设定任务状态和动作的数量
        num_actions = self.env.action_space.n  # CartPole的动作(向做或向右)数量为2
        self.agent = Agent(num_states, num_actions)  # 创建Agent在环境中执行的动作

        
    def run(self):
        '''执行'''
        episode_10_list = np.zeros(10)  # 存储10个试验的连续站立步骤数,并使用平均步骤数进行输出
        complete_episodes = 0  # 持续站立195步或更多的试验次数
        episode_final = False  # 最终尝试目标
        frames = []  # 用于存储图像的变量,以使最后一轮成为动画

        for episode in range(NUM_EPISODES):  # 重复试验次数
            observation = self.env.reset()  # 环境初始化

            state = observation  # 直接使用观测作为状态state使用
            state = torch.from_numpy(state).type(
                torch.FloatTensor)  # 将NumPy变量转换为PyTorch Tensor
            state = torch.unsqueeze(state, 0)  # size 4转换为size 1x4

            for step in range(MAX_STEPS):  # 1 episode(轮)循环

                if episode_final is True:  # 在最终试验中,将各时刻图像添加到帧中
                    frames.append(self.env.render(mode='rgb_array'))

                action = self.agent.get_action(state, episode)  # 求取动作

                # 通过执行动作a_t求s_{t+1}和done标志
                # 从acttion中指定.item()并获取内容
                observation_next, _, done, _ = self.env.step(
                    action.item())  # 使用'_'是因为在面的流程中不适用reward和info

                # 给予奖励。另外,设置episode和state_next的结束评估
                if done:  # 如果step不超过200,或者如果倾斜超过某个角度,则done为true
                    state_next = None  # 没有下一个状态,因此存储为None

                    # 添加到最近的10轮的站立步数列表中
                    episode_10_list = np.hstack(
                        (episode_10_list[1:], step + 1))

                    if step < 195:
                        reward = torch.FloatTensor(
                            [-1.0])  # 如果您在途中倒下,给予奖励-1作为惩罚
                        complete_episodes = 0  # 重置连续成功记录
                    else:
                        reward = torch.FloatTensor([1.0])  # 一直站立直到结束时奖励为1
                        complete_episodes = complete_episodes + 1  # 更新连续记录
                else:
                    reward = torch.FloatTensor([0.0])  # 普通奖励为0
                    state_next = observation_next  # 保持观察不变
                    state_next = torch.from_numpy(state_next).type(
                        torch.FloatTensor)  # 将numpy变量转换为PyTorch Tensor
                    state_next = torch.unsqueeze(state_next, 0)  # size 4转换为size 1x4

                # 向经验池中添加经验
                self.agent.memorize(state, action, state_next, reward)

                # Experience Replay中更新Q函数
                self.agent.update_q_function()

                # 更新观测值
                state = state_next

                # 结束处理
                if done:
                    print('%d Episode: Finished after %d steps:10次试验的平均step数 = %.1lf' % (
                        episode, step + 1, episode_10_list.mean()))
                    break

            if episode_final is True:
                # 保存并绘制动画
                display_frames_as_gif(frames)
                break

            # 连续十轮成功
            if complete_episodes >= 10:
                print('10轮连续成功')
                episode_final = True  # 使下一次尝试成为最终绘制的动画

cartpole_env = Environment()
cartpole_env.run()
Sequential(
  (fc1): Linear(in_features=4, out_features=32, bias=True)
  (relu1): ReLU()
  (fc2): Linear(in_features=32, out_features=32, bias=True)
  (relu2): ReLU()
  (fc3): Linear(in_features=32, out_features=2, bias=True)
)
0 Episode: Finished after 17 steps:10次试验的平均step数 = 1.7
1 Episode: Finished after 11 steps:10次试验的平均step数 = 2.8
2 Episode: Finished after 10 steps:10次试验的平均step数 = 3.8
3 Episode: Finished after 9 steps:10次试验的平均step数 = 4.7
4 Episode: Finished after 8 steps:10次试验的平均step数 = 5.5
5 Episode: Finished after 8 steps:10次试验的平均step数 = 6.3
6 Episode: Finished after 10 steps:10次试验的平均step数 = 7.3
7 Episode: Finished after 11 steps:10次试验的平均step数 = 8.4
8 Episode: Finished after 11 steps:10次试验的平均step数 = 9.5
9 Episode: Finished after 9 steps:10次试验的平均step数 = 10.4
10 Episode: Finished after 10 steps:10次试验的平均step数 = 9.7
11 Episode: Finished after 10 steps:10次试验的平均step数 = 9.6
12 Episode: Finished after 9 steps:10次试验的平均step数 = 9.5
13 Episode: Finished after 10 steps:10次试验的平均step数 = 9.6
14 Episode: Finished after 10 steps:10次试验的平均step数 = 9.8
15 Episode: Finished after 10 steps:10次试验的平均step数 = 10.0
16 Episode: Finished after 10 steps:10次试验的平均step数 = 10.0
17 Episode: Finished after 9 steps:10次试验的平均step数 = 9.8
18 Episode: Finished after 10 steps:10次试验的平均step数 = 9.7
19 Episode: Finished after 10 steps:10次试验的平均step数 = 9.8
20 Episode: Finished after 11 steps:10次试验的平均step数 = 9.9
……
495 Episode: Finished after 9 steps:10次试验的平均step数 = 9.2
496 Episode: Finished after 10 steps:10次试验的平均step数 = 9.3
497 Episode: Finished after 10 steps:10次试验的平均step数 = 9.3
498 Episode: Finished after 9 steps:10次试验的平均step数 = 9.3
499 Episode: Finished after 9 steps:10次试验的平均step数 = 9.2
  • 19
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
《边边学深度强化学习PyTorch程序设计实践》是一本关于深度强化学习PyTorch实践的书籍,它提供了在PyTorch环境下进行深度强化学习的详细指南和实例代码。 本书以边边学的方式引导读者理解深度强化学习的基本概念和原理,并通过实践项目的方式加深对所学知识的理解和掌握。书中的案例覆盖了强化学习的主要应用领域,包括游戏、机器人控制和金融等,读者可以根据自己的兴趣和需求选择相应的案例进行学习。 在学习过程中,读者将使用PyTorch这一流行的深度学习框架来实现深度强化学习算法。书中给出了详细的代码示例和解释,以及实验结果的展示和讨论,读者可以通过自己动手编写代码来加深对算法原理的理解,并通过实验调优来提升算法的性能。 《边边学深度强化学习PyTorch程序设计实践》在设计上注重了实践和理论的结合,旨在帮助读者从零开始学习和实践深度强化学习,并具备独立设计、实现和优化应用模型的能力。书中还介绍了一些实用的工具和技巧,如数据预处理、模型评估和调参等,读者可以借鉴和应用到自己的项目中。 总之,《边边学深度强化学习PyTorch程序设计实践》是一本适合初学者和有一定基础的读者学习深度强化学习PyTorch实践的书籍,通过阅读这本书,读者将能够系统地学习和掌握深度强化学习算法,并能够利用PyTorch实现自己的深度强化学习应用模型。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

じょりゅう

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值