【CV】第 16 章:结合计算机视觉和强化学习

  🔎大家好,我是Sonhhxg_柒,希望你看完之后,能对你有所帮助,不足请指正!共同学习交流🔎

📝个人主页-Sonhhxg_柒的博客_CSDN博客 📃

🎁欢迎各位→点赞👍 + 收藏⭐️ + 留言📝​

📣系列专栏 - 机器学习【ML】 自然语言处理【NLP】  深度学习【DL】

 🖍foreword

✔说明⇢本人讲解主要包括Python、机器学习(ML)、深度学习(DL)、自然语言处理(NLP)等内容。

如果你对这个系列感兴趣的话,可以关注订阅哟👋

文章目录

学习强化学习的基础知识

计算状态值

计算状态-动作值

实施 Q 学习

Q值

了解Gym环境

构建 Q 表

利用勘探开发

实施深度 Q 学习

使用固定目标模型实现深度 Q 学习

编写代理来玩 Pong

实现一个代理来执行自动驾驶

安装 CARLA 环境

安装 CARLA 二进制文件

安装 CARLA Gym 环境

训练自动驾驶代理

model.py

actor.py

使用固定目标训练 DQN

概括

问题


在上一章中,我们了解了如何将 NLP 技术(LSTM 和 Transformer)与基于计算机视觉的技术相结合。在本章中,我们将学习如何将基于强化学习的技术(主要是深度 Q 学习)与基于计算机视觉的技术相结合。

我们将首先学习强化学习的基础知识,然后学习与识别如何计算与在给定状态下采取行动相关的值(Q 值)相关的术语。接下来,我们将学习填充 Q 表,它有助于识别与给定状态下的各种操作相关的值。此外,我们将学习在由于大量可能状态而无法提出 Q 表的场景中识别各种动作的 Q 值;我们将使用 Deep Q-Network 来做到这一点。在这里,我们将了解如何结合强化学习来利用神经网络。接下来,我们将了解 Deep Q-Network 模型不起作用的场景,并通过将 Deep Q-Network 与固定目标模型一起使用来解决这个问题。这里,我们将通过利用 CNN 和强化学习来玩一个名为 Pong 的视频游戏。最后,我们将利用我们学到的知识来构建一个可以在模拟环境中自动驾驶汽车的代理–卡拉。

总之,在本章中,我们将涵盖以下主题:

  • 学习强化学习的基础知识
  • 实施 Q 学习
  • 实施深度 Q 学习
  • 使用固定目标实现深度 Q 学习
  • 实现一个代理来执行自动驾驶

学习强化学习的基础知识

强化学习RL ) 是机器学习的一个领域,它关注软件代理应该如何在给定的环境状态下采取行动以最大化累积奖励的概念。

要了解 RL 如何提供帮助,让我们考虑一个简单的场景。想象一下,您正在与计算机下棋(在我们的例子中,计算机是一个已经学习/正在学习如何下棋的代理)。游戏的设置(规则)构成环境。此外,当我们移动(采取行动)时,棋盘的状态(棋盘上各个棋子的位置)会发生变化。游戏结束时,代理会根据结果获得奖励。代理的目标是最大化奖励。

如果机器 (agent1) 正在与人类对战,它可以玩的游戏数量是有限的(取决于人类可以玩的游戏数量)。这可能会为智能体的学习造成瓶颈。但是,如果代理 1(正在学习游戏的代理)可以与代理 2 对战(代理 2 可能是另一个正在学习国际象棋的代理,或者它可能是一个已经预先编程好下棋的国际象棋软件)怎么办?从理论上讲,代理可以互相玩无限的游戏,从而最大限度地提高学习玩游戏的机会。这样,通过互相玩多个游戏,学习代理很可能学习如何很好地解决游戏的不同场景/状态。

让我们了解学习代理将遵循的学习过程:

  1. 最初,代理在给定状态下采取随机动作。
  2. 代理将它在游戏中的各种状态下采取的动作存储在内存中
  3. 然后,代理将各种状态下的动作结果与奖励相关联。
  4. 在玩了多个游戏之后,智能体可以通过重播其经验将某个状态下的动作与潜在奖励相关联。

接下来是量化在给定状态下采取行动所对应的价值的问题。我们将在下一节中学习如何计算它。

计算状态值

为了理解如何量化一个状态的价值,让我们使用一个简单的场景,我们将定义环境和目标如下:

环境是一个两行三列的网格。代理从开始单元格开始,如果代理到达右下角的网格单元格,它就会实现其目标(以 +1 的分数奖励)。如果代理去任何其他单元格,它不会得到奖励。代理可以通过向右、向左、底部或向上移动来执行操作,具体取决于操作的可行性(例如,代理可以在起始网格单元格中向右或向下移动)。到达除右下角单元格之外的任何剩余单元格的奖励为 0。

通过使用这些信息,让我们计算一个单元格的(代理在给定快照中所处的状态)。鉴于从一个细胞移动到另一个细胞消耗了一些能量,我们将到达一个细胞的价值打折 γ 因子,其中γ 负责从一个细胞移动到另一个细胞所花费的能量。此外,γ 的引入导致智能体学习得更快。有了这个,让我们形式化贝尔曼方程,它有助于计算单元格的值:

 有了前面的等式,让我们计算所有单元格的值(一旦确定了某个状态下的最佳动作),γ 的值为 0.9(γ 的典型值在 0.9 和 0.99 之间):

从前面的计算中,我们可以理解如何计算给定状态(单元格)中的值,当给定该状态下的最优动作时。以下是我们达到终端状态的简单场景:

有了这些价值,我们希望代理遵循价值增加的道路。

现在我们了解了如何计算状态值,在下一节中,我们将了解如何计算与状态-动作组合相关的值。

计算状态-动作值

在上一节中,我们提供了一个场景,我们已经知道代理正在采取最佳行动(这是不现实的)。在本节中,我们将看一个场景,在该场景中,我们可以识别对应于状态-动作组合的值。

在下图中,单元格中的每个子单元格代表在单元格中执行操作的值。最初,各种操作的单元格值如下:

请注意,在上图中,如果代理从单元格向右移动(因为它对应于终端单元格),则单元格b1(第 2行和第 2列)的值将为 1;其他动作的结果为 0。X 表示该动作是不可能的,因此没有与它相关联的值。

在四次迭代(步骤)中,给定状态下动作的更新单元格值如下:

然后,这将经过多次迭代,以提供使每个单元格的价值最大化的最佳操作。

让我们了解如何获取第二个表中的单元格值(上图中的迭代 2)。让我们将其缩小到 0.3,这是通过在第二个表的第一行和第二列中出现时采取向下动作获得的。当智能体采取向下行动时,它有 1/3 的机会在下一个状态下采取最优行动。因此,采取向下行动的价值如下:

 以类似的方式,我们可以获得在不同单元格中采取不同可能动作的值。

现在我们知道了如何计算给定状态下各种动作的值,在下一节中,我们将了解 Q-learning 以及如何利用它以及 Gym 环境,以便它可以玩各种游戏。

实施 Q 学习

在上一节中,我们手动计算了所有组合的状态-动作值。从技术上讲,既然我们已经计算了我们需要的各种状态-动作值,我们现在可以确定在每个状态下将采取的动作。然而,在更复杂的情况下——例如,在玩视频游戏时——获取状态信息会变得很棘手。OpenAI 的 Gym 环境在这种情况下会派上用场。它包含我们正在玩的游戏的预定义环境。在这里,它获取下一个状态信息,给定在当前状态下执行的操作。到目前为止,我们已经考虑了选择最优路径的场景。但是,在某些情况下,我们可能会陷入局部最小值。

在本节中,我们将学习 Q-learning,它有助于计算与状态中的动作相关的值,以及利用 Gym 环境来玩各种游戏。现在,我们来看看一个名为 Frozen Lake 的简单游戏。我们还将看看探索-开发,这有助于我们避免陷入局部最小值。但是,在我们这样做之前,我们将了解 Q 值。

Q值

Q-learning 或 Q-value 中的 Q 代表动作的质量。让我们学习如何计算它:

 我们已经知道我们必须不断更新给定状态的状态-动作值,直到它饱和。因此,我们将修改前面的公式,如下所示:

在前面的等式中,我们将 1 替换为学习率,以便我们可以更逐渐地更新在某个状态下采取的动作的值:

有了这个 Q 值的正式定义,在下一节中,我们将了解 Gym 环境以及它如何帮助我们获取 Q 表(该表存储有关在各种情况下已采取的各种操作的值的信息)状态),从而得出一个状态下的最优动作。

了解Gym环境

在本节中,我们将在玩 Gym 环境中的 Frozen Lake 游戏时探索 Gym 环境和其中存在的各种功能:

1.导入相关包:

import numpy as np
import gym
import random

2.打印 Gym 环境中存在的各种环境:

from gym import envs
print(envs.registry.all())

前面的代码打印了一个字典,其中包含 Gym 中可用的所有游戏。

3.为所选游戏创建环境:

env = gym.make('FrozenLake-v0', is_slippery=False)

4.检查创建的环境:

env.render()

前面的代码产生以下输出:

在上图中,代理从S开始。在这里,F指定细胞被冻结,而H指定细胞中有一个洞。如果代理进入单元格H并且游戏终止,则该代理将获得 0 的奖励。游戏的目标是让代理到达G

5.打印游戏中观察空间的大小(状态数):

env.observation_space.n

前面的代码为我们提供了 16 的输出。这表示游戏拥有的 16 个单元格。

6.打印可能的操作数:

env.action_space.n

前面的代码产生的值为 4,它表示可以采取的四种可能的操作。

7.在给定状态下对随机动作进行采样:

env.action_space.sample()

.sample()指定我们获取给定状态下可能的四个动作之一。每个动作对应的标量可以与动作的名称相关联。我们可以通过检查 GitHub 中的代码来做到这一点:https ://github.com/openai/gym/blob/master/gym/envs/toy_text/frozen_lake.py 。

8.将环境重置为其原始状态:

env.reset()

9.采取(步骤)一个动作:

env.step(env.action_space.sample())

前面的代码获取下一个状态、奖励、表示游戏是否已完成的标志以及其他信息。我们可以执行游戏,.step因为当给定一个带有动作的步骤时,环境很容易提供下一个状态。

这些步骤构成了我们构建 Q 表的基础,该 Q 表指示在每个状态下要采取的最佳行动。我们将在下一节中执行此操作。

构建 Q 表

在上一节中,我们学习了如何手动计算各种状态-动作对的 Q 值。在本节中,我们将利用 Gym 环境和与之关联的各种模块来填充 Q 表——其中行表示代理可以处于的状态,列表示代理可以采取的行动。Q 表的值表示在给定状态下采取行动的 Q 值。

我们可以使用以下策略填充 Q 表的值:

  1. 用零初始化游戏环境和 Q 表。
  2. 采取随机行动并获取下一个状态、奖励、说明游戏是否已完成的标志以及其他信息。
  3. 使用我们之前定义的贝尔曼方程更新 Q 值。
  4. 重复第 2 步第 3步,这样一集中最多有 50 个步骤。
  5. 对多个剧集重复步骤 2、3

让我们编写前面的策略:

1.初始化游戏环境:
import numpy as np 
import gym 
import random 
env = gym.make('FrozenLake-v0', is_slippery=False)
  • 用零初始化 Q 表:
action_size=env.action_space.n 
state_size=env.observation_space.n 
qtable=np.zeros((state_size,action_size))

前面的代码检查可用于构建 Q 表的可能操作和状态。Q 表的维度应该是状态数乘以动作数。

2.在采取随机动作的同时播放多个剧集。在这里,我们在每一集结束时重置环境:

episode_rewards = [] 
for i in range(10000): 
    state=env.reset()
  • 每集最多走 50 步:
    total_rewards = 0
    for step in range(50):

我们正在考虑每集最多 50 步,因为代理有可能永远在两个状态之间保持振荡(想想永远连续执行的左右动作)。因此,我们需要指定代理可以采取的最大步数。

  • 采样一个随机动作并采取(步骤)它:
        action=env.action_space.sample() 
        new_state,reward,done,info=env.step(action)
  • 更新对应于状态和动作的 Q 值:
qtable[state,action]+=0.1*(reward+0.9*np.max(\ 
                            qtable[new_state,:])\ 
                           -qtable[state,action])

np.max(qtable[new_state,:])在前面的代码中,我们指定学习率为 0.1,并且通过考虑下一个状态 ( )的最大 Q 值来更新状态-动作组合的 Q 值。

  • state将我们之前获得的值更新为new_state,并累积reward到total_rewards:
        state=new_state 
        total_rewards+=reward
  • 将奖励放入列表 ( episode_rewards) 并打印 Q 表 ( qtable):
    episode_rewards.append(total_rewards)
print(qtable)

前面的代码获取一个状态下各种动作的 Q 值:

我们将在下一节中了解如何利用获得的 Q 表。

到目前为止,我们每次都采取随机行动。然而,在现实场景中,一旦我们知道在某些状态下不能采取某些行动,反之亦然,我们就不需要再采取随机行动了。在这种情况下,探索-开发的概念就派上用场了。

利用勘探开发

在上一节中,我们探讨了在给定空间中可以采取的可能行动。在本节中,我们将学习探索-利用的概念,可以描述如下:

  • 探索是一种策略,我们在其中了解在给定状态下需要做什么(采取什么行动)。
  • 开发 是一种我们利用已经学到的东西的策略;也就是说,在给定状态下要采取的行动。

在初始阶段,进行大量探索是理想的,因为代理不知道最初要采取什么最佳行动。通过这些情节,随着代理随着时间的推移学习各种状态-动作组合的 Q 值,我们必须利用利用来执行导致高回报的动作。

有了这个直觉,让我们修改我们在上一节中构建的 Q 值计算,使其包括探索和利用:

episode_rewards = []
epsilon=1
max_epsilon=1
min_epsilon=0.01
decay_rate=0.005
for episode in range(1000):
    state=env.reset()
    total_rewards = 0
    for step in range(50):
        exp_exp_tradeoff=random.uniform(0,1)
        ## Exploitation:
        if exp_exp_tradeoff>epsilon:
            action=np.argmax(qtable[state,:])
        else:
            ## Exploration
            action=env.action_space.sample()
        new_state,reward,done,info=env.step(action)
        qtable[state,action]+=0.9*(reward+0.9*np.max(\
                                  qtable[new_state,:])\
                                   -qtable[state,action])
        state=new_state
        total_rewards+=reward
    episode_rewards.append(total_rewards)
    epsilon=min_epsilon+(max_epsilon-min_epsilon)\
                            *np.exp(decay_rate*episode)
print(qtable)

前面代码中的粗体行是添加到上一节中显示的代码中的内容。在这段代码中,我们指定,随着情节的增加,我们执行的利用多于探索。

一旦我们获得了 Q 表,我们就可以利用它来确定代理到达目的地所需采取的步骤:

env.reset()
for episode in range(1):
    state=env.reset()
    step=0
    done=False
    print("-----------------------")
    print("Episode",episode)
    for step in range(50):
        env.render()
        action=np.argmax(qtable[state,:])
        print(action)
        new_state,reward,done,info=env.step(action) 
        if done:
            print("Number of Steps",step+1)
            break
        state=new_state
env.close()

在前面的代码中,我们正在获取state代理所在的当前值,识别action在给定状态-动作组合中导致最大值的那个,采取动作 ( step) 来获取new_state代理所在的对象,然后重复这些步骤直到游戏完成(终止)。

前面的代码产生以下输出:

请注意,这是一个简化的示例,因为状态空间是离散的,因此我们构建了一个 Q 表。如果状态空间是连续的(例如,状态空间是游戏当前状态的快照图像)怎么办?构建 Q 表变得非常困难(因为可能的状态数量非常多)。在这种情况下,深度 Q 学习会派上用场。我们将在下一节中了解这一点。

实施深度 Q 学习

到目前为止,我们已经学会了如何构建一个 Q 表,它通过在多个剧集中重播游戏(在本例中为 Frozen Lake 游戏)提供与给定状态-动作组合相对应的值。然而,当状态空间是连续的(例如乒乓球游戏的快照)时,可能的状态空间的数量会变得巨大。我们将在本节以及接下来的内容中使用深度 Q 学习来解决这个问题。在本节中,我们将学习如何使用神经网络在没有 Q 表的情况下估计状态-动作组合的 Q 值——因此称为深度Q 学习。

与 Q 表相比,深度 Q 学习利用神经网络将任何给定的状态-动作(其中状态可以是连续或离散的)组合映射到 Q 值。

对于本练习,我们将使用 Gym 中的 CartPole 环境。在这里,我们的任务是尽可能长时间地平衡 CartPole。下图显示了 CartPole 环境的样子:

请注意,当小车向右移动时,杆向左移动,反之亦然。此环境中的每个状态都使用四个观察值定义,其名称以及最小值和最大值如下:

观察最小值最大值
Cart position-2.42.4
Cart velocity-infinf
Pole angle-41.8°41.8°
Pole velocity at the tip-infinf

请注意,表示状态的所有观察值都具有连续值。

在高层次上,用于 CartPole 平衡游戏的深度 Q 学习的工作原理如下:

  1. 获取输入值(游戏图像/游戏元数据)。
  2. 将输入值通过具有尽可能多的输出的网络传递可能的操作。
  3. 输出层预测对应于在给定状态下采取行动的行动值。

网络架构的高级概述如下:

在上图中,网络架构使用状态(四个观察值)作为输入,在当前状态下采取左右动作的 Q 值作为输出。我们训练神经网络如下:

  1. 在探索阶段,我们执行输出层中具有最高值的随机动作。
  2. 然后,我们将动作、下一个状态、奖励和表示游戏是否完成的标志存储在内存中。
  3. 在给定状态下,如果游戏没有完成,将计算在给定状态下采取行动的Q值;也就是说,奖励 + 折扣因子 x 下一个状态下所有动作的最大可能 Q 值。
  4. 当前状态-动作组合的 Q 值保持不变,除了在步骤 2中采取的动作。
  5. 多次执行步骤 14并存储经验。
  6. 拟合一个模型,将状态作为输入,将动作值作为预期输出(来自记忆和回放经验)并最小化 MSE 损失。
  7. 在降低探索率的同时,在多个剧集中重复上述步骤。

有了前面的策略,让我们编写深度 Q 学习代码,以便我们可以执行 CartPole 平衡:

1.导入相关包:
import gym
import numpy as np
import cv2
from collections import deque
import torch
import torch.nn as nn
import torch.nn.functional as F
import random
from collections import namedtuple, deque
import torch.optim as optim
device = 'cuda' if torch.cuda.is_available() else 'cpu'

2.定义环境:

env = gym.make('CartPole-v1')

3.定义网络架构:

class DQNetwork(nn.Module):
    def __init__(self, state_size, action_size):
        super(DQNetwork, self).__init__()
        
        self.fc1 = nn.Linear(state_size, 24)
        self.fc2 = nn.Linear(24, 24)
        self.fc3 = nn.Linear(24, action_size)
        
    def forward(self, state):       
        x = F.relu(self.fc1(state))
        x = F.relu(self.fc2(x))
        x = self.fc3(x)
        return x

请注意,该架构相当简单,因为它在两个隐藏层中仅包含 24 个单元。输出层包含尽可能多的单元,因为有可能的动作。

3.定义Agent类,如下:

  • 定义__init__具有各种参数、网络和经验的方法:
class Agent():
    def __init__(self, state_size, action_size):
        
        self.state_size = state_size
        self.action_size = action_size
        self.seed = random.seed(0)

        ## hyperparameters
        self.buffer_size = 2000
        self.batch_size = 64
        self.gamma = 0.99
        self.lr = 0.0025
        self.update_every = 4 

        # Q-Network
        self.local = DQNetwork(state_size, action_size)\
                                        .to(device)
        self.optimizer=optim.Adam(self.local.parameters(), \
                                        lr=self.lr)

        # Replay memory
        self.memory = deque(maxlen=self.buffer_size) 
        self.experience = namedtuple("Experience", \
                            field_names=["state", "action", \
                             "reward", "next_state", "done"])
        self.t_step = 0
  • 定义step函数,该函数从内存中获取数据并通过调用函数将其拟合到模型中learn:
    def step(self, state, action, reward, next_state, done):
        # Save experience in replay memory
        self.memory.append(self.experience(state, action, \
                                    reward, next_state, done)) 
        # Learn every update_every time steps.
        self.t_step = (self.t_step + 1) % self.update_every
        if self.t_step == 0:
        # If enough samples are available in memory, 
        # get random subset and learn
            if len(self.memory) > self.batch_size:
                experiences = self.sample_experiences()
                self.learn(experiences, self.gamma)
  • act给定状态,定义预测动作的函数:
    def act(self, state, eps=0.):
        # Epsilon-greedy action selection
        if random.random() > eps:
            state = torch.from_numpy(state).float()\
                         .unsqueeze(0).to(device)
            self.local.eval()
            with torch.no_grad():
                action_values = self.local(state)
            self.local.train()
            return np.argmax(action_values.cpu().data.numpy())
        else:
            return random.choice(np.arange(self.action_size))

请注意,在前面的代码中,我们在确定要采取的行动时执行探索-利用。

  • 定义learn适合模型的函数,以便在给定状态时预测动作值:
    def learn(self, experience, gamma): 
        states,actions,rewards,next_states,dones=experience 
        # 从本地模型中获取期望的 Q 值
        Q_expected = self.local(states).gather(1, actions) 

        # 获取最大的预测 Q 值(for next states) 
        # from local model 
        Q_targets_next = self.local(next_states).detach()\ 
                             .max(1)[0].unsqueeze(1) 
        # 计算当前状态的 Q 目标
        Q_targets = reward+(gamma*Q_targets_next* (1-dones)) 
        
        # 计算损失
        loss = F.mse_loss(Q_expected, Q_targets) 

        # 最小化损失
        self.optimizer.zero_grad() 
        loss.backward()
        self.optimizer.step()

在前面的代码中,我们正在获取采样体验并预测我们执行的操作的 Q 值。此外,鉴于我们已经知道下一个状态,我们可以预测下一个状态下动作的最佳 Q 值。这样,我们现在知道与在给定状态下采取的动作相对应的目标值。

最后,我们将计算在当前状态下采取的动作的 Q 值的期望值 ( Q_targets) 和预测值 ( ) 之间的损失。Q_expected

  • 定义sample_experiences函数以便从记忆中抽取经验:
    def sample_experiences(self):
        experiences = random.sample(self.memory, \
                                    k=self.batch_size) 
        states = torch.from_numpy(np.vstack([e.state \
                    for e in experiences if e is not None]))\
                        .float().to(device)
        actions = torch.from_numpy(np.vstack([e.action \
                    for e in experiences if e is not None]))\
                        .long().to(device)
        rewards = torch.from_numpy(np.vstack([e.reward \
                    for e in experiences if e is not None]))\
                        .float().to(device)
        next_states=torch.from_numpy(np.vstack([e.next_state \
                    for e in experiences if e is not None]))\
                        .float().to(device)
        dones = torch.from_numpy(np.vstack([e.done \
                    for e in experiences if e is not None])\
                        .astype(np.uint8)).float().to(device) 
        return (states, actions, rewards, next_states, dones)

5.定义agent对象:

agent = Agent(env.observation_space.shape[0], \
              env.action_space.n)

6.执行深度 Q 学习,如下所示:

  • 初始化您的列表:
scores = [] # list containing scores from each episode
scores_window = deque(maxlen=100) # last 100 scores
n_episodes=5000
max_t=5000
eps_start=1.0
eps_end=0.001
eps_decay=0.9995
eps = eps_start
  • 在每个情节中重置环境并获取状态的形状。此外,重塑它,以便我们可以将其传递给网络:
for i_episode in range(1, n_episodes+1):
    state = env.reset()
    state_size = env.observation_space.shape[0]
    state = np.reshape(state, [1, state_size])
    score = 0
  • 循环遍历max_t时间步,确定要执行的操作,并执行 ( step) 它。接下来,对其进行整形,以便将整形后的状态传递给神经网络:
    for i in range(max_t):
        action = agent.act(state, eps)
        next_state, reward, done, _ = env.step(action)
        next_state = np.reshape(next_state, [1, state_size])
  • agent.step通过在当前状态之上指定并将状态重置为下一个状态来拟合模型,以便它在下一次迭代中有用:
        reward = reward if not done or score == 499 else -10
        agent.step(state, action, reward, next_state, done)
        state = next_state
        score += reward
        if done:
            break 

  • 如果前 10 步的得分平均值大于 450,则存储、定期打印并停止训练:
    scores_window.append(score) # save most recent score 
    scores.append(score) # save most recent score
    eps = max(eps_end, eps_decay*eps) # decrease epsilon
    print('\rEpisode {}\tReward {} \tAverage Score: {:.2f} \
         \tEpsilon: {}'.format(i_episode,score, \
                        np.mean(scores_window), eps), end="")
    if i_episode % 100 == 0:
        print('\rEpisode {}\tAverage Score: {:.2f} \
        \tEpsilon: {}'.format(i_episode, \
                              np.mean(scores_window), eps))
    if i_episode>10 and np.mean(scores[-10:])>450:
        break

6.绘制随着剧集增加的分数变化:

import matplotlib.pyplot as plt
%matplotlib inline
plt.plot(scores)
plt.title('Scores over increasing episodes')

显示不同情节的分数变化的图如下:

从上图中我们可以看出,在第 2000 集之后,该模型在平衡 CartPole 时获得了高分。

现在我们已经学习了如何实现深度 Q 学习,在下一节中,我们将学习如何在不同的状态空间( Pong 中的视频帧)上工作,而不是在 CartPole 环境中定义状态的四个状态空间. 我们还将学习如何使用固定目标模型实现深度 Q 学习。

使用固定目标模型实现深度 Q 学习

在上一节中,我们学习了如何利用深度 Q 学习来解决 Gym 中的 CartPole 环境。在本节中,我们将研究一个更复杂的 Pong 游戏,并了解深度 Q 学习如何与固定目标模型一起解决该游戏。在处理此用例时,您还将学习如何利用基于 CNN 的模型(代替我们在上一节中使用的普通神经网络)来解决问题。

此用例的目标是构建一个可以与计算机(预先训练的非学习代理)对战并在乒乓球比赛中击败它的代理,该代理预计将获得 21 分。

我们将采用以下策略来解决为 Pong 游戏创建成功代理的问题:

裁剪图像的不相关部分以获取当前帧(状态):

请注意,在前面的图像中,我们拍摄了原始图像,并在处理后的图像中裁剪了原始图像的顶部和底部像素:

  • 堆叠四个连续的帧——代理需要状态序列来了解球是否正在接近它。
  • 让代理通过最初采取随机行动来玩游戏,并不断收集当前状态、未来状态、采取的行动和记忆中的奖励。仅将有关最后 10,000 个动作的信息保留在内存中,并将历史记录刷新到 10,000 个以上。
  • 构建一个网络(本地网络),从内存中获取状态样本并预测可能操作的值。
  • 定义作为本地网络副本的另一个网络(目标网络)。
  • 本地网络每更新 1000 次就更新一次目标网络。每 1000 个 epoch 结束时目标网络的权重与本地网络的权重相同。
  • 利用目标网络计算下一个状态下最佳动作的 Q 值。
  • 对于本地网络建议的动作,我们期望它预测即时奖励和下一个状态下最佳动作的 Q 值的总和。
  • 尽量减少本地网络的 MSE 损失。
  • 让代理继续玩,直到它的奖励最大化。

有了前面的策略,我们现在可以对代理进行编码,以便在玩 Pong 时最大化其奖励。

编写代理来玩 Pong

按照以下步骤编写代理程序,以便它自学如何玩 Pong:

1.导入相关包,搭建游戏环境:

import gym
import numpy as np
import cv2
from collections import deque
import matplotlib.pyplot as plt
import torch
import torch.nn as nn
import torch.nn.functional as F
import random
from collections import namedtuple, deque
import torch.optim as optim
import matplotlib.pyplot as plt
%matplotlib inline

device = 'cuda' if torch.cuda.is_available() else 'cpu'

env = gym.make('PongDeterministic-v0')

2.定义状态大小和动作大小:

state_size = env.observation_space.shape[0]
action_size = env.action_space.n

3.定义一个将预处理帧的函数,以便它删除不相关的底部和顶部像素:

def preprocess_frame(frame): 
    bkg_color = np.array([144, 72, 17]) 
    img = np.mean(frame[34:-16:2,::2]-bkg_color,axis=-1)/255. 
    resized_image = img 
    return resized_image

4.定义一个将堆叠四个连续帧的函数,如下所示:

  • 该函数将stacked_frames、当前state和 的标志is_new_episode作为输入:
def stack_frames(stacked_frames, state, is_new_episode):
    # Preprocess frame
    frame = preprocess_frame(state)
    stack_size = 4
  • 如果剧集是新的,我们将从一堆初始帧开始:
    if is_new_episode:
        # Clear our stacked_frames
        stacked_frames = deque([np.zeros((80,80), \
                         dtype=np.uint8) for i in \
                            range(stack_size)], maxlen=4)
        # Because we're in a new episode, 
        # copy the same frame 4x
        for i in range(stack_size):
            stacked_frames.append(frame) 
        # Stack the frames
        stacked_state = np.stack(stacked_frames, \
                                 axis=2).transpose(2, 0, 1)
  • 如果剧集不是新的,我们将从中删除最旧的帧stacked_frames并附加最新的帧:
    else: 
        # Append frame to deque, 
        # 自动移除#oldest frame 
        stack_frames.append(frame) 
        # 构建堆叠状态
        #(第一维指定#不同的帧)
        stacked_state = np.stack(stacked_frames, \ 
                                 axis=2).transpose (2, 0, 1)
    return stacked_state,stacked_frames

5.定义网络架构;即DQNetwork:

 
class DQNetwork(nn.Module):
    def __init__(self, states, action_size):
        super(DQNetwork, self).__init__()
        
        self.conv1 = nn.Conv2d(4, 32, (8, 8), stride=4)
        self.conv2 = nn.Conv2d(32, 64, (4, 4), stride=2)
        self.conv3 = nn.Conv2d(64, 64, (3, 3), stride=1)
        self.flatten = nn.Flatten()
        self.fc1 = nn.Linear(2304, 512)
        self.fc2 = nn.Linear(512, action_size)
        
    def forward(self, state): 
        x = F.relu(self.conv1(state))
        x = F.relu(self.conv2(x))
        x = F.relu(self.conv3(x))
        x = self.flatten(x)
        x = F.relu(self.fc1(x))
        x = self.fc2(x)
        return x

6.定义Agent类,就像我们在上一节中所做的那样,如下所示:

  • 定义__init__方法:
class Agent(): 
    def __init__(self, state_size, action_size): 
        
        self.state_size = state_size 
        self.action_size = action_size 
        self.seed = random.seed(0) 

        ## 超参数
        self.buffer_size = 10000 
        self.batch_size = 32 
        self. gamma = 0.99 
        self.lr = 0.0001 
        self.update_every = 4 
        self.update_every_target = 1000 
        self.learn_every_target_counter = 0
         # Q-Network 
        self.local = DQNetwork(state_size, \ 
                               action_size).to(device) 
        self.target = DQNetwork(state_size , \ 
                                action_size).to(device)
        self.optimizer=optim.Adam(self.local.parameters(), \ 
                                    lr=self.lr) 

        # 回放内存
        self.memory = deque(maxlen=self.buffer_size) 
        self.experience = namedtuple("Experience", \ 
                            field_names =["state", "action", \ 
                            "reward", "next_state", "done"]) 
        # 初始化时间步(每隔几步更新一次)
        self.t_step = 0

__init__请注意,与上一节中提供的代码相比,我们对前面代码中的方法所做的唯一添加是target网络和目标网络的更新频率(这些行以粗体显示在前面的代码中)。

  • 定义将更新权重 ( step) 的方法,就像我们在上一节中所做的那样:
    def step(self, state, action, reward, next_state, done): 
        # 在回放记忆中保存经验
        self.memory.append(self.experience(state[None], \ 
                                    action, reward, \ 
                                    next_state[None], done) ) 
        
        # 学习每个 update_every 时间步。
        self.t_step = (self.t_step + 1) % self.update_every 
        if self.t_step == 0: 
    # 如果内存中有足够的样本,获取随机
    # 子集并学习
            if len(self.memory) > self.batch_size:
                experiences = self.sample_experiences()
                self.learn(experiences, self.gamma)
  • 定义act方法,它将获取要在给定状态下执行的操作:
    def act(self, state, eps=0.):
        # Epsilon-greedy action selection
        if random.random() > eps:
            state = torch.from_numpy(state).float()\
                         .unsqueeze(0).to(device)
            self.local.eval()
            with torch.no_grad():
                action_values = self.local(state)
            self.local.train()
            return np.argmax(action_values.cpu()\
                                          .data.numpy())
        else:
            return random.choice(np.arange(self.action_size))
  • 定义learn函数,它将训练本地模型:
    def learn(self, experience, gamma): 
        self.learn_every_target_counter += 1
        states,actions,rewards,next_states,dones = experience 
        # 从本地模型中获取期望的 Q 值
        Q_expected = self.local(states).gather(1, actions) 

        # 获取最大预测 Q 值(用于下一个状态)
        # 从目标模型
        Q_targets_next = self.target(next_states).detach()\ 
                             .max(1)[0].unsqueeze(1)
         # 计算当前状态的 Q 目标
        Q_targets = reward+ (gamma*Q_targets_next*(1-dones)) 
        
        # 计算损失
        loss = F.mse_loss(Q_expected, Q_targets) 

        # 最小化损失
        self.optimizer.zero_grad() 
        loss.backward() 
        self.optimizer.step() 

        # ------------ 更新目标网络 ------------- # 
        if self.learn_every_target_counter%1000 ==0: 
            self.target_update()

请注意,在前面的代码中,Q_targets_next使用目标模型而不是上一节中使用的本地模型进行预测。我们还在每 1,000 步后更新目标网络learn_every_target_counter,帮助确定是否应该更新目标模型的计数器在哪里。

  • 定义一个target_update将更新目标模型的函数 ( ):
    def target_update(self):
        print('target updating')
        self.target.load_state_dict(self.local.state_dict())
  • 定义一个从记忆中采样经验的函数:
    def sample_experiences(self):
        experiences = random.sample(self.memory, \
                                    k=self.batch_size) 
        states = torch.from_numpy(np.vstack([e.state \
                    for e in experiences if e is not None]))\
                                    .float().to(device)
        actions = torch.from_numpy(np.vstack([e.action \
                    for e in experiences if e is not None]))\
                                    .long().to(device)
        rewards = torch.from_numpy(np.vstack([e.reward \
                    for e in experiences if e is not None]))\
                                    .float().to(device)
        next_states=torch.from_numpy(np.vstack([e.next_state \
                     for e in experiences if e is not None]))\
                                    .float().to(device)
        dones = torch.from_numpy(np.vstack([e.done \
                    for e in experiences if e is not None])\
                        .astype(np.uint8)).float().to(device) 
        return (states, actions, rewards, next_states, dones)

7.定义Agent对象:

agent = Agent(state_size, action_size)

8.定义将用于训练代理的参数:

n_episodes=5000 
max_t=5000 
eps_start=1.0 
eps_end=0.02 
eps_decay=0.995 
scores = [] # 包含每集得分的列表
score_window = deque(maxlen=100) # 最后 100 个得分
eps = eps_start 
stack_size = 
4Stacked_frames = deque([np .zeros((80,80), dtype=np.int) \ 
                        for i in range(stack_size)], \ 
                       maxlen=stack_size)

9.正如我们在上一节中所做的那样,在不断增加的情节上训练代理:

for i_episode in range(1, n_episodes+1):
    state = env.reset()
    state, frames = stack_frames(stacked_frames, \
                                 state, True)
    score = 0
    for i in range(max_t):
        action = agent.act(state, eps)
        next_state, reward, done, _ = env.step(action)
        next_state, frames = stack_frames(frames, \
                                          next_state, False)
        agent.step(state, action, reward, next_state, done)
        state = next_state
        score += reward
        if done:
            break 
    scores_window.append(score) # save most recent score
    scores.append(score) # save most recent score
    eps = max(eps_end, eps_decay*eps) # decrease epsilon
    print('\rEpisode {}\tReward {} \tAverage Score: {:.2f} \
    \tEpsilon: {}'.format(i_episode,score,\
                          np.mean(scores_window),eps),end="")
    if i_episode % 100 == 0:
        print('\rEpisode {}\tAverage Score: {:.2f} \
        \tEpsilon: {}'.format(i_episode, \
                              np.mean(scores_window), eps))

下图显示了随着剧集增加的分数变化:

从上图中我们可以看到,agent 逐渐学会了打 Pong,到了 800 集结束时,它在获得高额奖励的同时学会了如何打 Pong。

现在我们已经训练了一个能很好地玩 Pong 的代理,在下一节中,我们将训练一个代理,以便它可以在模拟环境中自动驾驶汽车。

实现一个代理来执行自动驾驶

既然您已经看到 RL 在逐渐具有挑战性的环境中工作,我们将通过演示相同的概念可以应用于自动驾驶汽车来结束本章。由于在实际汽车上看到这一点是不切实际的,我们将求助于模拟环境。环境将是一个成熟的交通城市,道路图像中有汽车和其他细节。演员(代理人)是一辆车。汽车的输入将是各种感官输入,例如行车记录仪、光检测和测距激光雷达) 传感器和 GPS 坐标。输出将是汽车移动的快/慢,以及转向水平。该模拟将尝试准确表示现实世界的物理学。因此,请注意,无论是汽车模拟还是真实汽车,基本原理都将保持不变。

请注意,我们要安装的环境需要一个图形用户界面GUI ) 来显示模拟。此外,培训至少需要一天,如果不是更多的话。由于视觉设置不可用和 Google-Colab 的时间使用限制,我们不会像迄今为止那样使用 Google-Colab 笔记本。这是本书中唯一需要活动 Linux 操作系统的部分,最好是 GPU 才能在几天的培训中获得可接受的结果。

安装 CARLA 环境

正如我们之前提到的,我们需要一个可以模拟复杂交互的环境,让我们相信我们实际上是在处理一个真实的场景。CARLA 就是这样一种环境。环境作者对 CARLA 的描述如下:

“CARLA 是从头开始开发的,旨在支持自动驾驶系统的开发、培训和验证。除了开源代码和协议之外,CARLA 还提供为此创建的开放数字资产(城市布局、建筑物和车辆)目的并且可以自由使用。仿真平台支持传感器套件的灵活规范、环境条件、对所有静态和动态参与者的完全控制、地图生成等等。

我们需要按照两个步骤来安装环境:

  1. 为模拟环境安装 CARLA 二进制文件。
  2. 安装 Gym 版本,它为模拟环境提供 Python 连接。
本节的步骤已在此处以视频演练的形式呈现:https ://tinyurl.com/mcvp-self-driving-agent 。

让我们开始吧!

安装 CARLA 二进制文件

在本节中,我们将学习如何安装必要的 CARLA 二进制文件:

1.访问Release CARLA 0.9.6 (development) · carla-simulator/carla · GitHub并下载CARLA_0.9.6.tar.gz编译后的版本文件。

2.将其移动到您希望 CARLA 存在于系统中的位置并解压缩。在这里,我们通过下载 CARLA 并将其解压缩到Documents文件夹中来演示这一点:

​$ mv CARLA_0.9.6.tar.gz ~/Documents/ 
$ cd ~/Documents/ 
$ tar -xf CARLA_0.9.6.tar.gz 
$ cd CARLA_0.9.6/

3.添加 CARLAPYTHONPATH以便您机器上的任何模块都可以导入 CARLA:

$ echo "export PYTHONPATH=$PYTHONPATH:/home/$(whoami)/Documents/CARLA_0.9.6/PythonAPI/carla/dist/carla-0.9.6-py3.5-linux-x86_64.egg" >> ~/.bashrc

在前面的代码中,我们将包含 CARLA 的目录添加到一个名为 的全局变量PYTHONPATH中,该变量是一个用于访问所有 Python 模块的环境变量。将其添加到~/.bashrc将确保每次打开终端时,它都可以访问这个新文件夹。运行上述代码后,重启终端并运行ipython -c "import carla; carla.__spec__". 您应该得到以下输出:

4.最后,提供必要的权限并执行CARLA,如下:

$ chmod +x /home/$(whoami)/Documents/CARLA_0.9.6/CarlaUE4.sh 
$ ./home/$(whoami)/Documents/CARLA_0.9.6/CarlaUE4.sh

一两分钟后,您应该会看到一个类似于下图的窗口,显示 CARLA 作为模拟运行,准备好接受输入:

在本节中,我们验证了这CARLA是一个模拟环境,其二进制文件按预期工作。让我们继续为它安装 Gym 环境。让终端保持原样运行,因为我们需要在整个练习过程中在后台运行二进制文件。

安装 CARLA Gym 环境

由于没有官方的 Gym 环境,我们将利用用户实现的 GitHub 存储库并从那里为 CARLA 安装 Gym 环境。按照以下步骤安装 CARLA 的 Gym 环境:

1.将 Gym 存储库克隆到您选择的位置并安装库:

$ cd /location/to/clone/repo/to
$ git clone https://github.com/cjy1992/gym-carla
$ cd gym-carla
$ pip install -r requirements.txt
$ pip install -e 

2.通过运行以下命令测试您的设置:

$ python test.py

应该会打开一个类似于以下内容的窗口,表明我们已向环境中添加了一辆假汽车。从这里,我们可以监控顶视图、LIDAR 传感器点云和我们的行车记录仪:

在这里,我们可以观察到以下几点:

  • 第一个视图包含与车载 GPS 系统在汽车中显示的非常相似的视图;也就是我们的车辆、各个航路点和道路车道。但是,我们不会将这个输入用于训练,因为它还会在视图中显示其他汽车,这是不现实的。
  • 第二种观点更有趣。有些人认为它是自动驾驶汽车的眼睛。激光雷达每秒多次向周围环境(各个方向)发射脉冲光。它捕获反射光以确定最近的障碍物在该方向上的距离。车载计算机整理所有最近的障碍物信息以重新创建 3D 点云,使其对其环境进行 3D 理解。
  • 在第一和第二个视图中,我们可以看到汽车前面有一条带。这是汽车应该去哪里的航路点指示。
  • 第三个视图是一个简单的仪表板摄像头。

除了这三个之外,CARLA 还提供额外的传感器数据,例如:

  • lateral-distance(偏离它应该在的车道)
  • delta-yaw(相对于前方道路的角度)
  • speed
  • 如果车辆前方有危险障碍物
  • 还有很多...

我们将使用前面提到的前四个传感器以及激光雷达和行车记录仪来训练模型。

我们现在准备好了解 CARLA 的组件并为自动驾驶汽车创建 DQN 模型。

训练自动驾驶代理

我们将在笔记本中开始训练过程之前创建两个文件;也就是说,model.py和actor.py。这些将Agent分别包含模型架构和类。该类Agent包含我们将用于训练代理的各种方法。

本节的代码说明Chapter16以Carla.md.

model.py

这将是一个 PyTorch 模型,它将接受提供给它的图像以及其他传感器输入。预计将返回最可能的操作:

from torch_snippets import *

class DQNetworkImageSensor(nn.Module):
    def __init__(self):
        super().__init__()
        self.n_outputs = 9
        self.image_branch = nn.Sequential(
                            nn.Conv2d(3, 32, (8, 8), stride=4), 
                            nn.ReLU(inplace=True),
                            nn.Conv2d(32, 64, (4, 4), stride=2), 
                            nn.ReLU(inplace=True),
                            nn.Conv2d(64,128,(3, 3),stride=1), 
                            nn.ReLU(inplace=True),
                            nn.AvgPool2d(8), 
                            nn.ReLU(inplace=True),
                            nn.Flatten(),
                            nn.Linear(1152, 512), 
                            nn.ReLU(inplace=True),
                            nn.Linear(512, self.n_outputs)
                        )

        self.lidar_branch = nn.Sequential(
                            nn.Conv2d(3, 32, (8, 8), stride=4), 
                            nn.ReLU(inplace=True),
                            nn.Conv2d(32,64,(4, 4),stride=2), 
                            nn.ReLU(inplace=True),
                            nn.Conv2d(64,128,(3, 3),stride=1), 
                            nn.ReLU(inplace=True),
                            nn.AvgPool2d(8), 
                            nn.ReLU(inplace=True),
                            nn.Flatten(),
                            nn.Linear(1152, 512), 
                            nn.ReLU(inplace=True),
                            nn.Linear(512, self.n_outputs)
                        )

        self.sensor_branch = nn.Sequential(
                                nn.Linear(4, 64), 
                                nn.ReLU(inplace=True),
                                nn.Linear(64, self.n_outputs)
                            )

    def forward(self, image, lidar=None, sensor=None):
        x = self.image_branch(image)
        if lidar is None:
            y = 0
        else:
            y = self.lidar_branch(lidar)
        z = self.sensor_branch(sensor)

        return x + y + z

如您所见,与前面部分相比,前向方法中输入的数据类型更多,我们只是接受图像作为输入。self.image_branch将期待来自汽车行车记录仪的self.lidar_branch图像,同时将接受由 LIDAR 传感器生成的图像。最后,self.sensor_branch将以 NumPy 数组的形式接受四个传感器输入。这四个项目分别是横向距离(偏离它应该在的车道)、delta-yaw(相对于前方道路的角度)、速度以及车辆前方是否有危险障碍物. 请参阅第 543 行gym_carla/envs/carla_env.py(已被 git 克隆的存储库)用于相同的输出。在神经网络中使用不同的分支将使模块为每个传感器提供不同级别的重要性,并将输出汇总为最终输出。请注意,有 9 个输出;我们稍后会看这些。

actor.py

很像前面的部分,我们将使用一些代码来存储回放信息并在需要训练时回放:

1.让我们准备好导入和超参数:

import numpy as np
import random
from collections import namedtuple, deque
import torch
import torch.nn.functional as F
import torch.optim as optim
from model1 import DQNetworkImageSensor

BUFFER_SIZE = int(1e3) # replay buffer size
BATCH_SIZE = 256 # minibatch size
GAMMA = 0.99 # discount factor
TAU = 1e-2 # for soft update of target parameters
LR = 5e-4 # learning rate 
UPDATE_EVERY = 50 # how often to update the network
ACTION_SIZE = 2

device = 'cuda' if torch.cuda.is_available() else 'cpu'

2.接下来,我们将初始化目标网络和本地网络。除了要导入的模块之外,这里没有对上一节的代码进行任何更改:

class Actor(): 
    def __init__(self):         
        # Q-Network 
        self.qnetwork_local=DQNetworkImageSensor().to(device) 
        self.qnetwork_target=DQNetworkImageSensor().to(device)
         self.optimizer = optim.Adam(self.qnetwork_local \ 
                                    .parameters(),lr=LR) 

        # 重放内存
        self.memory= ReplayBuffer(ACTION_SIZE,BUFFER_SIZE, \ 
                                   BATCH_SIZE, 10) 
        # 初始化时间步
        #(用于更新每个 UPDATE_EVERY 步)
        self.t_step = 0 
    
    def step(self, state, action, reward, next_state, done): 
        # 在回放记忆中保存经验
        self.memory.add(state, action, reward, \ 
                        next_state, done) 
        
        # 学习每个 UPDATE_EVERY 时间步。
        self.t_step = (self.t_step + 1) % UPDATE_EVERY 
        if self.t_step == 0: 
  # 如果内存中有足够的样本可用,
  # 获取随机子集并学习
            if len(self.memory) > BATCH_SIZE:
                experiences = self.memory.sample()
                self.learn(experiences, GAMMA)

3.由于有更多的传感器需要处理,我们将它们作为状态字典进行传输。状态包含我们在上一节中介绍的'image'、'lidar'和键。'sensor'我们在将它们发送到神经网络之前进行预处理,如以下代码所示:

    def act(self, state, eps=0.):
        images,lidars sensors=state['image'], \
                              state['lidar'],state['sensor']
        images = torch.from_numpy(images).float()\
                      .unsqueeze(0).to(device)
        lidars = torch.from_numpy(lidars).float()\
                      .unsqueeze(0).to(device)
        sensors = torch.from_numpy(sensors).float()\
                       .unsqueeze(0).to(device)
        self.qnetwork_local.eval()
        with torch.no_grad():
            action_values = self.qnetwork_local(images, \
                                lidar=lidars, sensor=sensors)
        self.qnetwork_local.train()
        # Epsilon-greedy action selection
        if random.random() > eps:
            return np.argmax(action_values.cpu().data.numpy())
        else:
            return random.choice(np.arange(\
                        self.qnetwork_local.n_outputs))

4.现在,我们需要从重放内存中获取项目。以下指令在以下代码中执行:

  1. 获取一批当前状态和下一个状态。
  2. Q_expected如果网络在当前状态下执行动作,则计算预期奖励。
  3. Q_targets将其与下一个状态馈送到网络时获得的目标奖励 进行比较。

5.使用本地网络定期更新目标网络:

    def learn(self, experiences, gamma):
        states,actions,rewards,next_states,dones= experiences
        images, lidars, sensors = states
        next_images, next_lidars, next_sensors = next_states
        # Get max predicted Q values (for next states) 
        # from target model
        Q_targets_next = self.qnetwork_target(next_images, \
                       lidar=next_lidars,sensor=next_sensors)\
                            .detach().max(1)[0].unsqueeze(1)
        # Compute Q targets for current states 
        Q_targets = rewards +(gamma*Q_targets_next*(1-dones))

        # Get expected Q values from local model
        # import pdb; pdb.set_trace()
        Q_expected=self.qnetwork_local(images,lidar=lidars, \
                   sensor=sensors).gather(1,actions.long())
        # Compute loss
        loss = F.mse_loss(Q_expected, Q_targets)
        # Minimize the loss
        self.optimizer.zero_grad()
        loss.backward()
        self.optimizer.step()

        # ------------ update target network ------------- #
        self.soft_update(self.qnetwork_local, \
                         self.qnetwork_target, TAU) 

    def soft_update(self, local_model, target_model, tau):
        for target_param, local_param in \
            zip(target_model.parameters(), \
            local_model.parameters()):
            target_param.data.copy_(tau*local_param.data + \
                                (1.0-tau)*target_param.data)

6.类中唯一的主要变化ReplayBuffer是数据的存储方式。由于我们有多个传感器,每个内存(states和next_states)都存储为一个数据元组;即states = [images, lidars, sensors]:

class ReplayBuffer:
    """用于存储经验元组的固定大小缓冲区。"""
    def __init__(self, action_size, buffer_size, \
                 batch_size, seed):
        self.action_size = action_size
        self.memory = deque(maxlen=buffer_size) 
        self.batch_size = batch_size
        self.experience = namedtuple("Experience", \
                          field_names=["state", "action", \
                                     "reward","next_state", \
                                       "done"])
        self.seed = random.seed(seed)
    
    def add(self, state, action, reward, next_state, done):
        """为记忆添加新体验。"""
        e = self.experience(state, action, reward, \
                            next_state, done)
        self.memory.append(e)
        
    
    def sample(self):
        experiences = random.sample(self.memory, \
                                    k=self.batch_size) 
        images = torch.from_numpy(np.vstack(\
                    [e.state['image'][None] \
                 for e in experiences if e is not None]))\
                    .float().to(device)
        lidars = torch.from_numpy(np.vstack(\
                    [e.state['lidar'][None] \
                 for e in experiences if e is not None]))\
                    .float().to(device)
        sensors = torch.from_numpy(np.vstack(\
                    [e.state['sensor'] \
                 for e in experiences if e is not None]))\
                    .float().to(device)
        states = [images, lidars, sensors]
        actions = torch.from_numpy(np.vstack(\
                    [e.action for e in experiences \
                     if e is not None])).long().to(device)
        rewards = torch.from_numpy(np.vstack(\
                    [e.reward for e in experiences \
                     if e is not None])).float().to(device)
        next_images = torch.from_numpy(np.vstack(\
                    [e.next_state['image'][None] \
                     for e in experiences if e is not None]))\
                    .float().to(device)
        next_lidars = torch.from_numpy(np.vstack(\
                    [e.next_state['lidar'][None] \
                     for e in experiences if e is not None]))\
                    .float().to(device)
        next_sensors = torch.from_numpy(np.vstack(\
                    [e.next_state['sensor'] \
                     for e in experiences if e is not None]))\
                    .float().to(device)
        next_states = [next_images, next_lidars, next_sensors]
        dones = torch.from_numpy(np.vstack([e.done \
                     for e in experiences if e is not None])\
                    .astype(np.uint8)).float().to(device)       

        return (states, actions, rewards, next_states, dones)

    def __len__(self):
        """返回内存的当前大小。"""
        return len(self.memory)

请注意,粗体代码行获取当前状态、操作、奖励和下一个状态的信息。

现在关键组件已经到位,让我们将 Gym 环境加载到 Python 笔记本中并开始训练。

使用固定目标训练 DQN

我们不需要在这里学习额外的理论。基础保持不变;我们只会对 Gym 环境、神经网络的架构以及我们的代理需要采取的行动进行更改:

1.首先,加载与环境相关的超参数。params请参阅以下代码中字典中出现的每个键值对旁边的每个注释。由于我们要模拟一个复杂的环境,我们需要选择环境的参数,例如城市中的汽车数量、步行者的数量、要模拟的城镇、行车记录仪图像的分辨率和 LIDAR 传感器:

import gym 
import gym_carla 
import 
carla from model import DQNetworkState 
from actor import Actor 
from torch_snippets import * 

params = { 
    'number_of_vehicles': 10, 
    'number_of_walkers': 0, 
    'display_size': 256, # 鸟瞰图的屏幕尺寸
    'max_past_step' : 1, # 绘制过去的步数
    'dt': 0.1, # 两帧之间的时间间隔
    'discrete': True, # 是否使用离散控制空间
    # 加速度离散值
    'discrete_acc': [-1, 0 , 1], 
    # 转向角离散值
    'discrete_steer': [-0.3, 0.0, 0.3], 
    # 定义车辆
    'ego_vehicle_filter': 'vehicle.lincoln*', 
    'port': 2000, # 连接端口
    'town': 'Town03', # 模拟哪个城镇
    'task_mode': 'random', # 任务模式
    'max_time_episode': 1000, # 每集的最大时间步长
    'max_waypt': 12, # 最大航路点数
    'obs_range': 32, # 观察范围(米)
    'lidar_bin': 0.125, # 激光雷达传感器的 bin 大小(米)
    'd_behind': 12 ,#自我车辆后面的距离(米)
    'out_lane_thres':2.0,#车道外阈值
    'desired_speed':8,#所需速度(m / s)
    'max_ego_spawn_times':200,# max times to spawn vehicle 
    'display_route': True, # 是否渲染所需路线
    'pixor_size': 64, # 像素标签的大小
    'pixor': False, # 是否输出PIXOR观察
} 

# 设置gym-carla环境
env = gym.make('carla-v0', params=params)

在前面的params字典中,就动作空间而言,以下内容对于我们的模拟很重要:

  • 'discrete': True: 我们的行动位于一个离散的空间中。
  • 'discrete_acc':[-1,0,1]:在模拟过程中允许自动驾驶汽车进行所有可能的加速度。
  • 'discrete_steer':[-0.3,0,0.3]:所有可能的转向幅度,在模拟过程中允许自动驾驶汽车进行。
如您所见,discrete_acc和discrete_steer列表分别包含三个项目。这意味着汽车可以采取 3 x 3 种可能的独特动作。这意味着model.py文件中的网络有九个离散状态。

阅读官方文档后,请随意更改参数。

2.这样,我们就拥有了训练模型所需的所有组件。加载预训练模型(如果存在):

load_path = None # 'car-v1.pth' 
# 从现有模型继续训练
save_path = 'car-v2.pth' 

actor = Actor()
if load_path is not None:
    actor.qnetwork_local.load_state_dict(\
                            torch.load(load_path))
    actor.qnetwork_target.load_state_dict(\
                            torch.load(load_path))
else:
    pass

3.固定episode数量,定义dqn训练agent的函数,如下:

  • 重置状态:
n_episodes = 100000 
def dqn(n_episodes=n_episodes, max_t=1000, eps_start=1, \ 
        eps_end=0.01, eps_decay=0.995): 
    scores = [] # 包含每集得分的列表
    score_window = deque(maxlen=100) # last 100 score 
    eps = eps_start # Initialize epsilon 
    for i_episode in range(1, n_episodes+1):
        state = env.reset()
  • 将状态包装到字典中(如actor.py:Actor课程中所述)并act在其上:
        image, lidar, sensor = state['camera'], \
                               state['lidar'], \
                               state['state']
        image, lidar = preprocess(image), preprocess(lidar)
        state_dict = {'image': image, 'lidar': lidar, \
                      'sensor': sensor}
        score = 0
        for t in range(max_t):
            action = actor.act(state_dict, eps)
  • 存储从环境中获得的下一个状态,然后存储该state, next_state对(连同奖励和其他状态信息)以使用 DQN 训练参与者:
            next_state, reward, done, _ = env.step(action)
            image, lidar, sensor = next_state['camera'], \
                                   next_state['lidar'], \
                                   next_state['state']
            image,lidar = preprocess(image), preprocess(lidar)
            next_state_dict = {'image':image,'lidar':lidar, \
                               'sensor': sensor}
            actor.step(state_dict, action, reward, \
                       next_state_dict, done)
            state_dict = next_state_dict
            score += reward
            if done:
                break
        scores_window.append(score) # save most recent score
        scores.append(score) # save most recent score
        eps = max(eps_end, eps_decay*eps) # decrease epsilon
        if i_episode % 100 == 0:
            log.record(i_episode, \
                       mean_score=np.mean(scores_window))
            torch.save(actor.qnetwork_local.state_dict(), \
                       save_path)

我们必须重复循环,直到我们得到一个完成信号,之后我们重置环境并再次开始存储动作。每 100 集后,存储模型。

4.调用dqn函数训练模型:

dqn()

由于这是一个更复杂的环境,训练可能需要几天时间,所以请耐心等待,并使用load_path和save_path参数一次继续训练几个小时。通过足够的训练,车辆可以操纵并学习如何自行驾驶。这是我们经过两天训练后能够达到的训练结果的视频:https ://tinyurl.com/mcvp-self-driving-agent-result 。

概括

在本章中,我们学习了如何计算给定状态下各种动作的值。然后,我们了解了代理如何使用在给定状态下采取行动的折扣值更新 Q 表。在这样做的过程中,我们了解了 Q-table 在状态数量较多的情况下是如何不可行的。我们还学习了如何利用深度 Q 网络来解决可能状态数量较多的场景。接下来,我们继续利用基于 CNN 的神经网络,同时构建一个学习如何使用基于固定目标的 DQN 玩 Pong 的代理。最后,我们学习了如何利用具有固定目标的 DQN 来使用 CARLA 模拟器执行自动驾驶。正如我们在本章中反复看到的,您可以使用深度 Q 学习来学习非常不同的任务——例如CartPole 平衡、打乒乓球和自动驾驶导航——使用几乎相同的代码。虽然这并不是我们探索 RL 之旅的终点,但在这一点上,我们应该能够理解我们如何结合使用基于 CNN 和基于强化学习的算法来解决复杂问题和构建学习代理。

到目前为止,我们已经学会了如何将基于计算机视觉的技术与其他重要研究领域的技术相结合,包括元学习、自然语言处理和强化学习。除此之外,我们还学习了如何使用 GAN 执行对象分类、检测、分割和图像生成。在下一章中,我们将换个角度学习如何将深度学习模型投入生产。

问题

  1. 如何计算给定状态的值?
  2. Q 表是如何填充的?
  3. 为什么我们在状态-动作值计算中有折扣因子?
  4. 我们需要什么探索-开发战略?
  5. 为什么我们需要使用深度 Q 学习?
  6. 如何使用深度 Q 学习计算给定状态-动作组合的值?
  7. 一旦代理在 CartPole 环境中最大化了奖励,它以后是否有机会学习次优策略?
  • 12
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
强化学习计算机视觉结合是一个非常有潜力的研究方向。强化学习是一种通过试错和奖励来训练智能体进行决策的机器学习方法,而计算机视觉则是指让计算机通过图像或视频数据来理解和解释视觉信息。 在强化学习中,计算机视觉可以用来提供智能体的感知能力。通过使用计算机视觉技术,智能体可以从环境中获取图像或视频数据,并将其转化为对环境的理解。这些数据可以用于提供更丰富的状态表示,帮助智能体更好地理解环境、识别物体、检测动作等。 另一方面,强化学习可以为计算机视觉提供决策能力。计算机视觉任务通常需要进行决策,例如目标检测、图像分割等。强化学习可以通过训练一个智能体来自动地进行这些决策,并且可以根据任务的反馈来不断优化决策策略。 例如,在自动驾驶领域,强化学习可以与计算机视觉结合,让智能车辆通过感知图像数据来识别交通标志、车辆和行人,并根据这些信息做出决策,例如加速、刹车、转向等。这种结合可以帮助智能车辆更好地适应复杂的交通环境,并提高行驶的安全性和效率。 总的来说,强化学习计算机视觉结合可以为许多领域带来巨大的潜力,包括机器人技术、自动驾驶、智能监控等。通过结合这两个领域的技术,我们可以实现更智能、更自动化的系统。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Sonhhxg_柒

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

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

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

打赏作者

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

抵扣说明:

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

余额充值