🔎大家好,我是Sonhhxg_柒,希望你看完之后,能对你有所帮助,不足请指正!共同学习交流🔎
📝个人主页-Sonhhxg_柒的博客_CSDN博客 📃
🎁欢迎各位→点赞👍 + 收藏⭐️ + 留言📝
📣系列专栏 - 机器学习【ML】 自然语言处理【NLP】 深度学习【DL】
🖍foreword
✔说明⇢本人讲解主要包括Python、机器学习(ML)、深度学习(DL)、自然语言处理(NLP)等内容。
如果你对这个系列感兴趣的话,可以关注订阅哟👋
文章目录
在上一章中,我们了解了如何将 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 的分数奖励)。如果代理去任何其他单元格,它不会得到奖励。代理可以通过向右、向左、底部或向上移动来执行操作,具体取决于操作的可行性(例如,代理可以在起始网格单元格中向右或向下移动)。到达除右下角单元格之外的任何剩余单元格的奖励为 0。
通过使用这些信息,让我们计算一个单元格的值(代理在给定快照中所处的状态)。鉴于从一个细胞移动到另一个细胞消耗了一些能量,我们将到达一个细胞的价值打折 γ 因子,其中γ 负责从一个细胞移动到另一个细胞所花费的能量。此外,γ 的引入导致智能体学习得更快。有了这个,让我们形式化贝尔曼方程,它有助于计算单元格的值:
![](https://i-blog.csdnimg.cn/blog_migrate/2462e18f3529def324d2c0f8f196a46f.png)
有了前面的等式,让我们计算所有单元格的值(一旦确定了某个状态下的最佳动作),γ 的值为 0.9(γ 的典型值在 0.9 和 0.99 之间):
![](https://i-blog.csdnimg.cn/blog_migrate/6b445ab446cc2e924fa8290c89e793b1.png)
从前面的计算中,我们可以理解如何计算给定状态(单元格)中的值,当给定该状态下的最优动作时。以下是我们达到终端状态的简单场景:
![](https://i-blog.csdnimg.cn/blog_migrate/176caedd497c04d13b75d9a42697bd77.png)
有了这些价值,我们希望代理遵循价值增加的道路。
现在我们了解了如何计算状态值,在下一节中,我们将了解如何计算与状态-动作组合相关的值。
计算状态-动作值
在上一节中,我们提供了一个场景,我们已经知道代理正在采取最佳行动(这是不现实的)。在本节中,我们将看一个场景,在该场景中,我们可以识别对应于状态-动作组合的值。
在下图中,单元格中的每个子单元格代表在单元格中执行操作的值。最初,各种操作的单元格值如下:
请注意,在上图中,如果代理从单元格向右移动(因为它对应于终端单元格),则单元格b1(第 2行和第 2列)的值将为 1;其他动作的结果为 0。X 表示该动作是不可能的,因此没有与它相关联的值。
在四次迭代(步骤)中,给定状态下动作的更新单元格值如下:
![](https://i-blog.csdnimg.cn/blog_migrate/bf6d81da1c6171e14b49a374d74b70a7.png)
然后,这将经过多次迭代,以提供使每个单元格的价值最大化的最佳操作。
让我们了解如何获取第二个表中的单元格值(上图中的迭代 2)。让我们将其缩小到 0.3,这是通过在第二个表的第一行和第二列中出现时采取向下动作获得的。当智能体采取向下行动时,它有 1/3 的机会在下一个状态下采取最优行动。因此,采取向下行动的价值如下:
![](https://i-blog.csdnimg.cn/blog_migrate/b921cdf615acdb7fe4b3257af7360bad.png)
以类似的方式,我们可以获得在不同单元格中采取不同可能动作的值。
现在我们知道了如何计算给定状态下各种动作的值,在下一节中,我们将了解 Q-learning 以及如何利用它以及 Gym 环境,以便它可以玩各种游戏。
实施 Q 学习
在上一节中,我们手动计算了所有组合的状态-动作值。从技术上讲,既然我们已经计算了我们需要的各种状态-动作值,我们现在可以确定在每个状态下将采取的动作。然而,在更复杂的情况下——例如,在玩视频游戏时——获取状态信息会变得很棘手。OpenAI 的 Gym 环境在这种情况下会派上用场。它包含我们正在玩的游戏的预定义环境。在这里,它获取下一个状态信息,给定在当前状态下执行的操作。到目前为止,我们已经考虑了选择最优路径的场景。但是,在某些情况下,我们可能会陷入局部最小值。
在本节中,我们将学习 Q-learning,它有助于计算与状态中的动作相关的值,以及利用 Gym 环境来玩各种游戏。现在,我们来看看一个名为 Frozen Lake 的简单游戏。我们还将看看探索-开发,这有助于我们避免陷入局部最小值。但是,在我们这样做之前,我们将了解 Q 值。
Q值
Q-learning 或 Q-value 中的 Q 代表动作的质量。让我们学习如何计算它:
我们已经知道我们必须不断更新给定状态的状态-动作值,直到它饱和。因此,我们将修改前面的公式,如下所示:
![](https://i-blog.csdnimg.cn/blog_migrate/42d97c6baecda6635f4af71ea77da23b.png)
在前面的等式中,我们将 1 替换为学习率,以便我们可以更逐渐地更新在某个状态下采取的动作的值:
![](https://i-blog.csdnimg.cn/blog_migrate/5d6f5da61948bb41daffc4995e1e04ee.png)
有了这个 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 表的值:
- 用零初始化游戏环境和 Q 表。
- 采取随机行动并获取下一个状态、奖励、说明游戏是否已完成的标志以及其他信息。
- 使用我们之前定义的贝尔曼方程更新 Q 值。
- 重复第 2 步和第 3步,这样一集中最多有 50 个步骤。
- 对多个剧集重复步骤 2、3和4 。
让我们编写前面的策略:
-
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 环境的样子:
![](https://i-blog.csdnimg.cn/blog_migrate/75b049ba76330d250a7537b5ccc62908.png)
请注意,当小车向右移动时,杆向左移动,反之亦然。此环境中的每个状态都使用四个观察值定义,其名称以及最小值和最大值如下:
观察 | 最小值 | 最大值 |
Cart position | -2.4 | 2.4 |
Cart velocity | -inf | inf |
Pole angle | -41.8° | 41.8° |
Pole velocity at the tip | -inf | inf |
请注意,表示状态的所有观察值都具有连续值。
在高层次上,用于 CartPole 平衡游戏的深度 Q 学习的工作原理如下:
- 获取输入值(游戏图像/游戏元数据)。
- 将输入值通过具有尽可能多的输出的网络传递可能的操作。
- 输出层预测对应于在给定状态下采取行动的行动值。
网络架构的高级概述如下:
![](https://i-blog.csdnimg.cn/blog_migrate/a21c356c154dbe219552438af4a41246.png)
在上图中,网络架构使用状态(四个观察值)作为输入,在当前状态下采取左右动作的 Q 值作为输出。我们训练神经网络如下:
- 在探索阶段,我们执行输出层中具有最高值的随机动作。
- 然后,我们将动作、下一个状态、奖励和表示游戏是否完成的标志存储在内存中。
- 在给定状态下,如果游戏没有完成,将计算在给定状态下采取行动的Q值;也就是说,奖励 + 折扣因子 x 下一个状态下所有动作的最大可能 Q 值。
- 当前状态-动作组合的 Q 值保持不变,除了在步骤 2中采取的动作。
- 多次执行步骤 1到4并存储经验。
- 拟合一个模型,将状态作为输入,将动作值作为预期输出(来自记忆和回放经验)并最小化 MSE 损失。
- 在降低探索率的同时,在多个剧集中重复上述步骤。
有了前面的策略,让我们编写深度 Q 学习代码,以便我们可以执行 CartPole 平衡:
-
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.fc
1
= nn.Linear(state_
size,
24)
-
self.fc
2
= nn.Linear(
24,
24)
-
self.fc
3
= nn.Linear(
24, action_
size)
-
-
def forward(
self, state):
-
x
= F.relu(
self.fc
1(state))
-
x
= F.relu(
self.fc
2(x))
-
x
=
self.fc
3(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()\
-
.m
ax(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.uint
8)).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')
显示不同情节的分数变化的图如下:
![](https://i-blog.csdnimg.cn/blog_migrate/9222bd0958f37a17deddd4d5883f6049.png)
从上图中我们可以看出,在第 2000 集之后,该模型在平衡 CartPole 时获得了高分。
现在我们已经学习了如何实现深度 Q 学习,在下一节中,我们将学习如何在不同的状态空间( Pong 中的视频帧)上工作,而不是在 CartPole 环境中定义状态的四个状态空间. 我们还将学习如何使用固定目标模型实现深度 Q 学习。
使用固定目标模型实现深度 Q 学习
在上一节中,我们学习了如何利用深度 Q 学习来解决 Gym 中的 CartPole 环境。在本节中,我们将研究一个更复杂的 Pong 游戏,并了解深度 Q 学习如何与固定目标模型一起解决该游戏。在处理此用例时,您还将学习如何利用基于 CNN 的模型(代替我们在上一节中使用的普通神经网络)来解决问题。
此用例的目标是构建一个可以与计算机(预先训练的非学习代理)对战并在乒乓球比赛中击败它的代理,该代理预计将获得 21 分。
我们将采用以下策略来解决为 Pong 游戏创建成功代理的问题:
裁剪图像的不相关部分以获取当前帧(状态):
![](https://i-blog.csdnimg.cn/blog_migrate/9a87dd009a92a1bb5cebf80c5e6c96a5.png)
请注意,在前面的图像中,我们拍摄了原始图像,并在处理后的图像中裁剪了原始图像的顶部和底部像素:
- 堆叠四个连续的帧——代理需要状态序列来了解球是否正在接近它。
- 让代理通过最初采取随机行动来玩游戏,并不断收集当前状态、未来状态、采取的行动和记忆中的奖励。仅将有关最后 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.uint
8)
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.conv
1
= nn.Conv
2d(
4,
32, (
8,
8), stride
=
4)
-
self.conv
2
= nn.Conv
2d(
32,
64, (
4,
4), stride
=
2)
-
self.conv
3
= nn.Conv
2d(
64,
64, (
3,
3), stride
=
1)
-
self.flatten
= nn.Flatten()
-
self.fc
1
= nn.Linear(
2304,
512)
-
self.fc
2
= nn.Linear(
512, action_
size)
-
-
def forward(
self, state):
-
x
= F.relu(
self.conv
1(state))
-
x
= F.relu(
self.conv
2(x))
-
x
= F.relu(
self.conv
3(x))
-
x
=
self.flatten(x)
-
x
= F.relu(
self.fc
1(x))
-
x
=
self.fc
2(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()\
-
.m
ax(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.uint
8)).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))
下图显示了随着剧集增加的分数变化:
![](https://i-blog.csdnimg.cn/blog_migrate/6b9f9de464dea50d99566e8798f34ae6.png)
从上图中我们可以看到,agent 逐渐学会了打 Pong,到了 800 集结束时,它在获得高额奖励的同时学会了如何打 Pong。
现在我们已经训练了一个能很好地玩 Pong 的代理,在下一节中,我们将训练一个代理,以便它可以在模拟环境中自动驾驶汽车。
实现一个代理来执行自动驾驶
既然您已经看到 RL 在逐渐具有挑战性的环境中工作,我们将通过演示相同的概念可以应用于自动驾驶汽车来结束本章。由于在实际汽车上看到这一点是不切实际的,我们将求助于模拟环境。环境将是一个成熟的交通城市,道路图像中有汽车和其他细节。演员(代理人)是一辆车。汽车的输入将是各种感官输入,例如行车记录仪、光检测和测距(激光雷达) 传感器和 GPS 坐标。输出将是汽车移动的快/慢,以及转向水平。该模拟将尝试准确表示现实世界的物理学。因此,请注意,无论是汽车模拟还是真实汽车,基本原理都将保持不变。
安装 CARLA 环境
正如我们之前提到的,我们需要一个可以模拟复杂交互的环境,让我们相信我们实际上是在处理一个真实的场景。CARLA 就是这样一种环境。环境作者对 CARLA 的描述如下:
我们需要按照两个步骤来安装环境:
- 为模拟环境安装 CARLA 二进制文件。
- 安装 Gym 版本,它为模拟环境提供 Python 连接。
让我们开始吧!
安装 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__". 您应该得到以下输出:
![](https://i-blog.csdnimg.cn/blog_migrate/a5c4a8a1f9e32ca129ab70be00853400.png)
4.最后,提供必要的权限并执行CARLA,如下:
-
$ chmod
+x
/home
/$(whoami)
/Documents
/CARLA_
0.9.6
/CarlaUE
4.sh
-
$ .
/home
/$(whoami)
/Documents
/CARLA_
0.9.6
/CarlaUE
4.sh
一两分钟后,您应该会看到一个类似于下图的窗口,显示 CARLA 作为模拟运行,准备好接受输入:
![](https://i-blog.csdnimg.cn/blog_migrate/7073ca95ab7a09efe3ec4ca80f92460c.png)
在本节中,我们验证了这CARLA是一个模拟环境,其二进制文件按预期工作。让我们继续为它安装 Gym 环境。让终端保持原样运行,因为我们需要在整个练习过程中在后台运行二进制文件。
安装 CARLA Gym 环境
由于没有官方的 Gym 环境,我们将利用用户实现的 GitHub 存储库并从那里为 CARLA 安装 Gym 环境。按照以下步骤安装 CARLA 的 Gym 环境:
1.将 Gym 存储库克隆到您选择的位置并安装库:
-
$ cd
/
location
/
to
/clone
/repo
/
to
-
$ git clone https:
/
/github.com
/cjy
1992
/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包含我们将用于训练代理的各种方法。
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.Conv
2d(
3,
32, (
8,
8), stride
=
4),
-
nn.ReLU(inplace
=
True),
-
nn.Conv
2d(
32,
64, (
4,
4), stride
=
2),
-
nn.ReLU(inplace
=
True),
-
nn.Conv
2d(
64,128,(
3,
3),stride
=
1),
-
nn.ReLU(inplace
=
True),
-
nn.AvgPool
2d(
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.Conv
2d(
3,
32, (
8,
8), stride
=
4),
-
nn.ReLU(inplace
=
True),
-
nn.Conv
2d(
32,64,(
4,
4),stride
=
2),
-
nn.ReLU(inplace
=
True),
-
nn.Conv
2d(
64,128,(
3,
3),stride
=
1),
-
nn.ReLU(inplace
=
True),
-
nn.AvgPool
2d(
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 model
1 import DQNetworkImageSensor
-
-
BUFFER_
SIZE
= int(
1e
3) # 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.现在,我们需要从重放内存中获取项目。以下指令在以下代码中执行:
- 获取一批当前状态和下一个状态。
- Q_expected如果网络在当前状态下执行动作,则计算预期奖励。
- 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().m
ax(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.uint
8)).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