深度强化学习实战例子:DQN实现控制倒立摆

DQN实现控制倒立摆

完整代码在文章结尾。

DQN算法内容

主要思想:用神经网络近似最优动作价值函数 Q ∗ ( s , a ) Q^*(s,a) Q(s,a)。该神经网络也称为深度Q网络(DQN)。

训练算法:时间差分算法(TD)

最优动作价值函数用最大化消除策略
Q ∗ ( s t , a t ) = max ⁡ π Q π ( s t , a t ) Q_*(s_t,a_t)=\max_πQ_π(s_t,a_t) Q(st,at)=πmaxQπ(st,at)
当得知了最优动作价值函数后,可以使用其进行控制,选择价值大的动作进行执行

神经网络结构:输入层为状态向量,输出层为动作个数的向量,每个元素代表对应动作的Q值。

训练流程

  1. 将一条轨迹划分成n个 ( s t , a t , r t , s t + 1 ) (s_t,a_t,r_t,s_{t+1}) (st,at,rt,st+1)四元组,存入经验回放数组
  2. 随机从经验回放数组中取出一个四元组,对DQN正向传播,得到 Q Q Q值: q ^ j = Q ( s j , a j ; w n o w ) q ^ j + 1 = max ⁡ a Q ( s j + 1 , a ; w n o w ) \hat{q}_j=Q(s_j,a_j;w_{now})\qquad\hat{q}_{j+1}=\max_aQ(s_{j+1},a;w_{now}) q^j=Q(sj,aj;wnow)q^j+1=amaxQ(sj+1,a;wnow)
  3. 计算TD目标和TD误差: y ^ j = r j + γ q ^ j + 1 δ j = q ^ j − y ^ j \hat{y}_j=r_j+\gamma\hat{q}_{j+1}\qquad \delta_j=\hat{q}_j-\hat{y}_j y^j=rj+γq^j+1δj=q^jy^j
  4. 对DQN反向传播,得到梯度: g j = ∇ w Q ( s j , a j ; w n o w ) g_j=\nabla_wQ(s_j,a_j;w_{now}) gj=wQ(sj,aj;wnow)
  5. 做梯度下降更新DQN参数: w n e w = w n o w − α ⋅ δ j ⋅ g j w_{new}=w_{now}-\alpha \cdot \delta_j \cdot g_j wnew=wnowαδjgj

实现流程

实现内容:

  • 经验回放数组
  • 神经网络,用来近似最优动作价值函数。
  • 智能体,即DQN算法;

经验回放数组类

需要实现的内容

由训练流程可知,经验回放数组应具备:

  1. 收集四元组,即能以元组形式存入 ( s t , a t , r t , s t + 1 ) (s_t,a_t,r_t,s_{t+1}) (st,at,rt,st+1)——可使用列表实现
  2. 能从数组中随机抽取成批四元组,用于计算 Q Q Q值——可使用random模块的sample()方法实现
具体代码
class ReplayBuffer:
    """经验回放"""
    def __init__(self,batch_size=64):
        self.data = [] # 用列表实现四元组的储存
        self.batch_size = batch_size # 设置抽取的批量数
    
    def add_data(self,state,action,reward,next_state,done):
        """
        向数组中添加数据,可再添加是否完成done这一参数,可用于之后判断下一状态是否需要动作。
        """
        self.data.append((state,action,reward,next_state,done))
    
    def sample(self):
        """
        抽取四(五)元组用于更新网络参数。
        使用*sample_data,将列表解包;再用zip()方法,将各元组相同位置的元素,打包成一个元组。
        """
        sample_data = random.sample(self.data,self.batch_size)
        state, action, reward, next_state, done = zip(*sample_data)
        return np.array(state), np.array(action), np.array(reward), np.array(next_state), np.array(done)

    def length(self):
        """
        返回经验回放数组的长度。
        """
        return len(self.data)

神经网络类

需要实现的内容
  • 输入维度;
  • 隐藏层大小;
  • 输入维度。

这里只使用一层隐藏层,实现简单的神经网络。

具体代码
class model(nn.Module):
    """定义Q神经网络"""
    def __init__(self,state_dim,hidden_dim,action_dim):
        super(model,self).__init__()
        self.net = nn.Sequential(
                nn.Linear(state_dim,hidden_dim),nn.ReLU(),
                nn.Linear(hidden_dim,action_dim)
                )

    def forward(self,x):
        return self.net(x)

智能体(DQN)类

需要实现的内容
  1. 行为策略,即做出动作与环境交互,这里使用 ϵ \epsilon ϵ-贪婪算法
  2. 更新网络,根据经验回放数组中的数据,进行参数更新;
  3. 目标网络,使用目标网络缓解自举产生的偏差。
具体代码
class DQN():
    """DQN智能体"""
    def __init__(self,state_dim,hidden_dim,action_dim,learning_rate,gamma,epsilon,target_update,device):
        """
        神经网络需要输入维度,隐藏层维度,输出维度。
        更新网络需要学习率,使用设备(cpu or gpu)
        行为策略需要epsilon概率大小
        计算回报需要gamma衰减因子
        目标网络更新频率
        """
        self.action_dim = action_dim
        self.net = model(state_dim,hidden_dim,action_dim).to(device)
        self.load() # 用来加载训练过的网络参数,没有也没关系
        self.target_net = model(state_dim,hidden_dim,action_dim).to(device) # 建立目标网络,用来计算下一状态时,对动作价值的估计。
        self.optimizer = torch.optim.Adam(self.net.parameters(),lr=learning_rate)
        self.gamma = gamma # 奖励折扣因子
        self.epsilon = epsilon #贪婪行为策略的参数
        self.target_update = target_update # 目标网络更新频率
        self.count = 0 # 记录更新次数
        self.device = device
        self.loss = nn.MSELoss(reduction='none')
        
    def take_action(self,state):
        """
        行为策略:实现epsilon-贪婪算法,用来决定动作。
        """
        if random.random() < self.epsilon:
            action = random.randint(0,self.action_dim-1)
        else:
            state = torch.tensor(state,dtype=torch.float).to(self.device)
            action = self.net(state).argmax().item() # 得到最大价值动作对应的索引值
        return action
    
    def update(self,states,actions,rewards,next_states,dones):
        """
        对神经网络进行更新。
        输入参数来自经验回放数组的批量抽取。
        """
        states = torch.tensor(states,dtype=torch.float).to(device=self.device)
        actions = torch.tensor(actions,dtype=torch.int64).reshape(-1,1).to(device=self.device) # 转换成2维多行一列,是为了之后提取对应动作的价值方便
        rewards = torch.tensor(rewards,dtype=torch.float).to(device=self.device)
        next_states = torch.tensor(next_states,dtype=torch.float).to(device=self.device)
        dones = torch.tensor(dones,dtype=torch.float).to(device=self.device)
        
        q_values = self.net(states).gather(-1,actions) # gather第二参数索引必须为tensor类型
        max_q_values = self.target_net(next_states).max(dim=-1)[0] # 指定维度后,返回值:最大值 + 索引
        q_target = rewards + self.gamma * max_q_values * (1-dones) # 当回合结束,即dones=1时,只有rewards,没有下一状态后的动作价值。下一状态存在,但没有动作了,所以直接舍去。
        q_target = q_target.unsqueeze(dim=-1) # 为了下面计算loss维度一样,注释该行也没事,因为有广播机制,会自动广播到相同维度。
        l = self.loss(q_values,q_target)
        self.optimizer.zero_grad()
        l.mean().backward()
        self.optimizer.step()
        
        # 当网络更新到一定次数,更新一次目标网络,减缓目标网络的更新频率
        if self.count % self.target_update == 0:
            self.target_net.load_state_dict(self.net.state_dict())
        self.count += 1
    
    def save(self):
        """
        用来保存网络的参数
        """
        torch.save(self.net.state_dict(),'DQN_CartPole.pth')
    
    def load(self):
        """
        加载训练好的网络参数
        """
        try:
            self.net.load_state_dict(torch.load('DQN_CartPole.pth'))
        except:
            pass

完整代码

import torch
import numpy as np
import torch.nn as nn
import torch.nn.functional as F
import random
import os
import gym

class ReplayBuffer:
    """经验回放"""
    def __init__(self,batch_size=64):
        self.data = []
        self.batch_size = batch_size
    
    def add_data(self,state,action,reward,next_state,done):
        self.data.append((state,action,reward,next_state,done))
    
    def sample(self):
        sample_data = random.sample(self.data,self.batch_size)
        state, action, reward, next_state, done = zip(*sample_data)
        return np.array(state), np.array(action), np.array(reward), np.array(next_state), np.array(done)

    def length(self):
        return len(self.data)

class model(nn.Module):
    """定义Q神经网络"""
    def __init__(self,state_dim,hidden_dim,action_dim):
        super(model,self).__init__()
        self.net = nn.Sequential(
            nn.Linear(state_dim,hidden_dim),nn.ReLU(),
            nn.Linear(hidden_dim,action_dim)
            )
        
    def forward(self,x):
        return self.net(x)

class DQN():
    """DQN智能体"""
    def __init__(self,state_dim,hidden_dim,action_dim,learning_rate,gamma,epsilon,target_update,device):
        self.action_dim = action_dim
        self.net = model(state_dim,hidden_dim,action_dim).to(device)
        self.load()
        self.target_net = model(state_dim,hidden_dim,action_dim).to(device)
        self.optimizer = torch.optim.Adam(self.net.parameters(),lr=learning_rate)
        self.gamma = gamma # 奖励折扣因子
        self.epsilon = epsilon #贪婪行为策略的参数
        self.target_update = target_update # 目标网络更新频率
        self.count = 0 # 记录更新次数
        self.device = device
        self.loss = nn.MSELoss(reduction='none')
        
    def take_action(self,state):
        if random.random() < self.epsilon:
            action = random.randint(0,self.action_dim-1)
        else:
            state = torch.tensor(state,dtype=torch.float).to(self.device)
            action = self.net(state).argmax().item()
        return action
    
    def update(self,states,actions,rewards,next_states,dones):
        states = torch.tensor(states,dtype=torch.float).to(device=self.device)
        actions = torch.tensor(actions,dtype=torch.int64).reshape(-1,1).to(device=self.device) # 转换成2维多行一列,是为了之后提取对应动作的价值方便
        rewards = torch.tensor(rewards,dtype=torch.float).to(device=self.device)
        next_states = torch.tensor(next_states,dtype=torch.float).to(device=self.device)
        dones = torch.tensor(dones,dtype=torch.float).to(device=self.device)
        
        q_values = self.net(states).gather(-1,actions) # gather第二参数索引必须为tensor类型
        max_q_values = self.target_net(next_states).max(dim=-1)[0] # 指定维度后,返回值:最大值 + 索引
        q_target = rewards + self.gamma * max_q_values * (1-dones)
        q_target = q_target.unsqueeze(dim=-1)
        l = self.loss(q_values,q_target)
        self.optimizer.zero_grad()
        l.mean().backward()
        self.optimizer.step()
        
        if self.count % self.target_update == 0:
            self.target_net.load_state_dict(self.net.state_dict())
        self.count += 1
    
    def save(self):
        torch.save(self.net.state_dict(),'DQN_CartPole.pth')
    
    def load(self):
        try:
            self.net.load_state_dict(torch.load('DQN_CartPole.pth'))
        except:
            pass

if __name__ == '__main__':
    os.system('cls' if os.name == 'nt' else 'clear')
    lr = 2e-3
    num_episodes = 20
    gamma = 0.9
    hidden_dim = 128
    epsilon = 0.05
    target_update = 10
    buffer_size = 10000
    minimal_size = 500
    batch_size = 64
    device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
    
    env = gym.make('CartPole-v1',render_mode='human')
    # env = gym.make('CartPole-v1')
    replay_buffer = ReplayBuffer(batch_size)
    state_dim = env.observation_space.shape[0]
    print(state_dim)
    action_dim = env.action_space.n
    agent = DQN(state_dim,hidden_dim,action_dim,lr,gamma,epsilon,target_update,device)
    return_list = []
    
    for i in range(10):
        for i_episode in range(num_episodes):
            episode_return = 0
            state, info = env.reset()
            done = False
            while not done:
                action = agent.take_action(state)
                next_state, reward, done, _, __ = env.step(action)
                replay_buffer.add_data(state,action,reward,next_state,done)
                state = next_state
                episode_return += reward
                if replay_buffer.length() > minimal_size:
                    b_s, b_a, b_r, b_ns, b_d = replay_buffer.sample()
                    agent.update(b_s, b_a, b_r, b_ns, b_d)
            print(episode_return)
            return_list.append(episode_return)
    agent.save()
### 基于Simulink的一阶倒立摆DQN实现方法 #### 1. 深度Q网络(DQN)简介 深度Q网络(Deep Q-Network, DQN)是一种结合了深度学习和Q-Learning的强化学习算法,能够有效处理高维状态空间下的控制问题[^1]。通过神经网络近似动作价值函数 \( Q(s,a) \),DQN能够在复杂环境中找到最优策略。 #### 2. Simulink环境搭建 为了在Simulink中实现一阶倒立摆DQN控制,需完成以下设置: - **物理模型构建**:利用Simulink中的Simscape模块库建立一阶倒立摆的动力学方程。 - **传感器与执行器配置**:定义角度、角速度作为输入信号;力矩或推力作为输出信号。 - **奖励函数设计**:设定合理的奖励机制以引导智能体学习稳定行为。例如,当摆杆接近垂直位置时给予正向奖励,偏离过大则施加惩罚[^1]。 #### 3. MATLAB代码示例 以下是基于MATLAB Deep Learning Toolbox 和 Reinforcement Learning Toolbox 的DQN实现代码: ```matlab % 创建环境对象 env = rlPredefinedEnv('CartPoleContinuous'); obsInfo = getObservationInfo(env); actInfo = getActionInfo(env); % 定义深度Q网络结构 numInputs = obsInfo.Dimension(1); % 输入维度 (状态数) numOutputs = numel(actInfo.Elements); % 输出维度 (动作数) dqnNet = [ featureInputLayer(numInputs,'Name','state') fullyConnectedLayer(24,'Name','fc1') reluLayer('Name','relu1') fullyConnectedLayer(24,'Name','fc2') reluLayer('Name','relu2') fullyConnectedLayer(numOutputs,'Name','qValues') ]; % 构造DQN代理 agentOptions = rlDQNAgentOptions; agentOptions.SampleTime = 0.05; % 设置采样时间 agentOptions.DiscountFactor = 0.99; % 折扣因子 agentOptions.EpsilonGreedyExploration.EpsilonDecay = 1e-5; critic = dlnetwork(dqnNet); agent = rlDQNAgent(critic, agentOptions); % 训练参数与停止条件 trainOpts = rlTrainingOptions; trainOpts.MaxEpisodes = 1000; % 最大训练回合数 trainOpts.StopOnError = 'on'; % 开始训练过程 trainingStats = train(agent, env, trainOpts); ``` 此代码片段展示了如何初始化环境、定义神经网络架构以及配置DQN代理的相关超参数[^1]。 #### 4. 关键技术细节说明 - **经验回放池**:存储历史交互数据用于随机梯度下降优化,减少样本关联性影响。 - **目标网络同步**:定期更新目标网络权重以提高稳定性。 - **探索与开发平衡**:采用ε-greedy策略,在初期增加随机性以便充分探索可能的动作组合。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值