(强化学习(二)--DQN算法

强化学习(二)--DQN算法

  • 1. DQN算法
    • 1.1 Experience replay (经验回放)
    • 1.2 Fixed Q target (固定Q目标)
    • 1.3 神经网络的LOSS函数
  • 2. DQN的代码实现
    • 2.1 代码的整体框架
    • 2.2 主函数
    • 2.3 神经网络的构建(Net类的实现)
    • 2.4 DQN类的实现
      • 2.4.1 choose_action 和 predict函数
      • 2.4.2 store_transition函数
      • 2.4.3 learn函数
    • 2.5 测试函数(test_episode函数的实现)
  • 3. DQN算法效果展示

上一节课的Q-learning算法和sarsa算法都是基于Q表格的,但是当状态较多时,Q表格占用大量内存且查找不便,针对这个问题,划时代的DQN算法由此诞生。DQN算法是Q-learning算法的改进,它是运用一个神经网络来拟合Q值,具有以下的优点:

  • 仅需储存有限的参数;
  • 状态泛华,相似的状态可以输出一样的Q值;

1. DQN算法

关于DQN算法的详细讲解可以看一下科老师的公开课(公开课地址),可以很清晰的了解整个DQN算法的详细流程。

这里展示了DQN算法的伪代码:
(强化学习(二)--DQN算法_第1张图片
DQN算法引入神经网络Q来拟合Q值,它有两大创新点:

  • Experience replay (经验回放)
  • Fixed Q target (固定Q目标)

1.1 Experience replay (经验回放)

经验回放是为了切断输入样本的相关性,并解决了样本利用率低的问题,具体做法是增加一个缓冲区(buffer)来存放与环境交互的数据,学习时再抽取batch来训练Q网络。

一组数据通常为 (s,a,r,s_),因此DQN是一个典型的off-policy算法。

1.2 Fixed Q target (固定Q目标)

固定Q目标是解决算法更新不平稳的问题,因为target_q值也是需要过一遍Q网络才能拿到的,而Q网络是在不定更新的,因此会造成算法的不平稳。举个例子,射箭是我们射击的兔子是在不断运动的,因此很难进行瞄准,而射击靶标时则更容易射中。

未解决这个问题,需要建立一个和Q网络一模一样的Target_Q网络,来输出target_q值,每隔一段时间再拷贝Q网络的参数到Target_Q网络。

1.3 神经网络的LOSS函数

熟悉监督学习的朋友都知道,神经网络学习的反向传递过程是需要计算网络预测值和label值的loss,而在强化学习中是没有label,我们则使用target_q值来作为label。

计算Q网络的预测值和target_q的loss,反向传递来优化神经网络,使其能更好的预测Q值。

2. DQN的代码实现

这里使用的环境是gym中的 ‘CartPole-v0’,它有向左和向右两个action,保持杆子不倒且不超过边界的时间越长,获得的奖励越大。
(强化学习(二)--DQN算法_第2张图片

2.1 代码的整体框架

代码的整体框架如下图:如果理解DQN算法按照这个框架可以很容易的实现算法。

  • Net类:神经网络的搭建;
  • DQN类:DQN算法的主要更新步骤和动作的选取函数;
  • test_episode函数:测试算法效果时的函数;
  • main函数:主函数。
    (强化学习(二)--DQN算法_第3张图片

2.2 主函数

主函数主要实现 run episode的过程,首先定义DQN的类,然后进行400个episode的循环,要注意的地方是:

  • 只有在replay memory中存在一定的交互数据后才进行训练;

我们每20个episode进行一下测试,也就是运行test_episode的函数,main函数的代码如下:

def main():
    dqn = DQN()
    print('\nCollecting experience...')
    for i_episode in range(400):
        s = env.reset()
        ep_r = 0
        while True:
            env.render()
            # 选择动作
            a = dqn.choose_action(torch.unsqueeze(torch.FloatTensor(s), 0))

            # 与环境交互
            s_, r, done, info = env.step(a)

            # 重新定义reward
            x, x_dot, theta, theta_dot = s_
            r1 = (env.x_threshold - abs(x)) / env.x_threshold - 0.8
            r2 = (env.theta_threshold_radians - abs(theta)) / env.theta_threshold_radians - 0.5
            r = r1 + r2

            # 存入数据到经验池
            dqn.store_transition(s, a, r, s_)

            ep_r += r

            # 存入一定的数据量再进行训练
            if dqn.memory_counter > MEMORY_CAPACITY:
                dqn.learn()
                if done:
                    print('Ep: ', i_episode,
                          '| Ep_r: ', round(ep_r, 2))

            if done:
                break
            s = s_  # 传递状态

            # 每10个episode测试一下训练成果
            if (i_episode > 200) and (i_episode % 10) == 0:
                eval_reward = test_episode(env, dqn, render=True)
                print('episode:{}    e_greed:{}   Test reward:{}'.format(i_episode, EPSILON, eval_reward))

2.3 神经网络的构建(Net类的实现)

神经网络就是常见的pytorch神经网络搭建方法,这里我们搭建了两层的神经网络:

  • input_dim: 状态的维度
  • output_dim: 动作的个数;

代码如下:

class Net(nn.Module):
    def __init__(self, ):
        super(Net, self).__init__()
        self.fc1 = nn.Linear(N_STATES, 50)
        self.fc1.weight.data.normal_(0, 0.1)   # initialization
        self.out = nn.Linear(50, N_ACTIONS)
        self.out.weight.data.normal_(0, 0.1)   # initialization

    def forward(self, x):
        x = self.fc1(x)
        x = F.relu(x)
        actions_value = self.out(x)
        return actions_value

2.4 DQN类的实现

DQN类的整体框架如下图:

  • choose_action函数和predict函数:是用来根据e_greed策略来选择动作的函数;
  • store_transition函数:是用来向replay memory存放数据的函数
  • learn函数:算法更新的函数;
    (强化学习(二)--DQN算法_第4张图片

2.4.1 choose_action 和 predict函数

e_greed策略选择动作的函数,有0.9的概率选择Q网络输出Q值最大的动作,0.1的概率随机选择动作,保持一定的探索。

2.4.2 store_transition函数

是用来存放交互数据的,很简单,创建双端队列buffer,往里append数据就好了。

2.4.3 learn函数

learn函数主要实现了三个功能:

  • Target_Q网络的更新:每隔一段时间拷贝Q网络的参数到Target_Q网络;
  • 从replay memory中取出用于学习的数据
  • 计算loss并反向传递更新Q网络

DQN类的代码如下:

class DQN(object):
    def __init__(self):
        self.eval_net, self.target_net = Net(), Net()
        self.learn_step_counter = 0                                     # for target updating
        self.memory_counter = 0                                         # for storing memory
        self.optimizer = torch.optim.Adam(self.eval_net.parameters(), lr=LR)
        self.loss_func = nn.MSELoss()
        self.buffer = collections.deque(maxlen=max_size)  # 创建一个双端队列,用于储存经验池

    def choose_action(self, obs):
        if np.random.uniform() < EPSILON:
            action = self.predict(obs)             # 根据神经网络的输出来选择动作
        else:
            action = np.random.choice(N_ACTIONS)   # 随机选择动作的策略
            action = action if ENV_A_SHAPE == 0 else action.reshape(ENV_A_SHAPE)
        return action

    # 根据神经网络输出Q值选择动作
    def predict(self,obs):
        pred_Q = self.eval_net.forward(obs)
        action = torch.max(pred_Q, 1)[1].data.numpy()
        action = action[0] if ENV_A_SHAPE == 0 else action.reshape(ENV_A_SHAPE)
        return action

    # 经验池的存储函数
    def store_transition(self, s, a, r, s_):
        self.buffer.append((s,a,r,s_))
        self.memory_counter += 1

    def learn(self):
        # target网络的更新,是每隔一段时间进行更新
        if self.learn_step_counter % TARGET_REPLACE_ITER == 0:
            self.target_net.load_state_dict(self.eval_net.state_dict())
        self.learn_step_counter += 1

        # 从经验池中取数据
        mini_batch = random.sample(self.buffer,BATCH_SIZE)
        obs_batch,action_batch,reward_batch,next_obs_batch,done_batch = [],[],[],[],[]

        for experience in mini_batch:
            s,a,r,s_p = experience
            obs_batch.append(s)
            action_batch.append(a)
            reward_batch.append(r)
            next_obs_batch.append(s_p)

        # 转换数据的格式
        obs_batch = np.array(obs_batch).reshape(-1, 4)
        action_batch = np.array(action_batch).reshape(-1, 1)
        reward_batch = np.array(reward_batch).reshape(-1,1)
        next_obs_batch = np.array(next_obs_batch).reshape(-1, 4)

        b_s = torch.FloatTensor(obs_batch)
        b_a = torch.LongTensor(action_batch)
        b_r = torch.FloatTensor(reward_batch)
        b_s_ = torch.FloatTensor(next_obs_batch)


        # 获取Q网络的输出值
        pred_q = self.eval_net(b_s).gather(1, b_a)
        # 从target_model中获取 max Q' 的值,用于计算target_Q
        q_next = self.target_net(b_s_).detach()  # 阻止梯度的传递
        target_q = b_r + GAMMA * q_next.max(1)[0].view(32,1)

        # 计算loss = predict_q - target_q
        loss = self.loss_func(pred_q,target_q)
        # 更新Q网络
        self.optimizer.zero_grad()
        loss.backward()
        self.optimizer.step()

2.5 测试函数(test_episode函数的实现)

test_episode函数过程和main函数中的过程有点类似,只不过它不再是用e_greed的方法来选择动作,而是用我们训练好的网络来直接输出动作,每次测试进行5个episode,求这5个episode的平均reward,代码如下:

def test_episode(env,agent,render=False):
    eval_reward = []
    for i in range(5):
        obs = env.reset()
        episode_reward = 0
        while True:
            action = agent.predict(torch.unsqueeze(torch.FloatTensor(obs),0))   # 选取动作全部由e-greed策略,不再有探索
            next_obs,reward,done,_ = env.step(action)
            episode_reward += reward
            obs = next_obs
            if render:
                env.render()
            if done:
                break
        eval_reward.append(episode_reward)
    return np.mean(eval_reward)

3. DQN算法效果展示

在一开始的episode,杆子只能立起很小的一段时间,获得的reward也很低,但是在当训练到250个episode后,reward值会逐渐增大。
(强化学习(二)--DQN算法_第5张图片

你可能感兴趣的:(强化学习)