大家好,我是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 的视频游戏。最后,我们将利用我们学到的知识来构建一个可以在模拟环境中自动驾驶汽车的代理–卡拉。
总之,在本章中,我们将涵盖以下主题:
强化学习( RL ) 是机器学习的一个领域,它关注软件代理应该如何在给定的环境状态下采取行动以最大化累积奖励的概念。
要了解 RL 如何提供帮助,让我们考虑一个简单的场景。想象一下,您正在与计算机下棋(在我们的例子中,计算机是一个已经学习/正在学习如何下棋的代理)。游戏的设置(规则)构成环境。此外,当我们移动(采取行动)时,棋盘的状态(棋盘上各个棋子的位置)会发生变化。游戏结束时,代理会根据结果获得奖励。代理的目标是最大化奖励。
如果机器 (agent1) 正在与人类对战,它可以玩的游戏数量是有限的(取决于人类可以玩的游戏数量)。这可能会为智能体的学习造成瓶颈。但是,如果代理 1(正在学习游戏的代理)可以与代理 2 对战(代理 2 可能是另一个正在学习国际象棋的代理,或者它可能是一个已经预先编程好下棋的国际象棋软件)怎么办?从理论上讲,代理可以互相玩无限的游戏,从而最大限度地提高学习玩游戏的机会。这样,通过互相玩多个游戏,学习代理很可能学习如何很好地解决游戏的不同场景/状态。
让我们了解学习代理将遵循的学习过程:
接下来是量化在给定状态下采取行动所对应的价值的问题。我们将在下一节中学习如何计算它。
为了理解如何量化一个状态的价值,让我们使用一个简单的场景,我们将定义环境和目标如下:
环境是一个两行三列的网格。代理从开始单元格开始,如果代理到达右下角的网格单元格,它就会实现其目标(以 +1 的分数奖励)。如果代理去任何其他单元格,它不会得到奖励。代理可以通过向右、向左、底部或向上移动来执行操作,具体取决于操作的可行性(例如,代理可以在起始网格单元格中向右或向下移动)。到达除右下角单元格之外的任何剩余单元格的奖励为 0。
通过使用这些信息,让我们计算一个单元格的值(代理在给定快照中所处的状态)。鉴于从一个细胞移动到另一个细胞消耗了一些能量,我们将到达一个细胞的价值打折 γ 因子,其中γ 负责从一个细胞移动到另一个细胞所花费的能量。此外,γ 的引入导致智能体学习得更快。有了这个,让我们形式化贝尔曼方程,它有助于计算单元格的值:
有了前面的等式,让我们计算所有单元格的值(一旦确定了某个状态下的最佳动作),γ 的值为 0.9(γ 的典型值在 0.9 和 0.99 之间):
从前面的计算中,我们可以理解如何计算给定状态(单元格)中的值,当给定该状态下的最优动作时。以下是我们达到终端状态的简单场景:
有了这些价值,我们希望代理遵循价值增加的道路。
现在我们了解了如何计算状态值,在下一节中,我们将了解如何计算与状态-动作组合相关的值。
在上一节中,我们提供了一个场景,我们已经知道代理正在采取最佳行动(这是不现实的)。在本节中,我们将看一个场景,在该场景中,我们可以识别对应于状态-动作组合的值。
在下图中,单元格中的每个子单元格代表在单元格中执行操作的值。最初,各种操作的单元格值如下:
请注意,在上图中,如果代理从单元格向右移动(因为它对应于终端单元格),则单元格b1(第 2行和第 2列)的值将为 1;其他动作的结果为 0。X 表示该动作是不可能的,因此没有与它相关联的值。
在四次迭代(步骤)中,给定状态下动作的更新单元格值如下:
然后,这将经过多次迭代,以提供使每个单元格的价值最大化的最佳操作。
让我们了解如何获取第二个表中的单元格值(上图中的迭代 2)。让我们将其缩小到 0.3,这是通过在第二个表的第一行和第二列中出现时采取向下动作获得的。当智能体采取向下行动时,它有 1/3 的机会在下一个状态下采取最优行动。因此,采取向下行动的价值如下:
以类似的方式,我们可以获得在不同单元格中采取不同可能动作的值。
现在我们知道了如何计算给定状态下各种动作的值,在下一节中,我们将了解 Q-learning 以及如何利用它以及 Gym 环境,以便它可以玩各种游戏。
在上一节中,我们手动计算了所有组合的状态-动作值。从技术上讲,既然我们已经计算了我们需要的各种状态-动作值,我们现在可以确定在每个状态下将采取的动作。然而,在更复杂的情况下——例如,在玩视频游戏时——获取状态信息会变得很棘手。OpenAI 的 Gym 环境在这种情况下会派上用场。它包含我们正在玩的游戏的预定义环境。在这里,它获取下一个状态信息,给定在当前状态下执行的操作。到目前为止,我们已经考虑了选择最优路径的场景。但是,在某些情况下,我们可能会陷入局部最小值。
在本节中,我们将学习 Q-learning,它有助于计算与状态中的动作相关的值,以及利用 Gym 环境来玩各种游戏。现在,我们来看看一个名为 Frozen Lake 的简单游戏。我们还将看看探索-开发,这有助于我们避免陷入局部最小值。但是,在我们这样做之前,我们将了解 Q 值。
Q-learning 或 Q-value 中的 Q 代表动作的质量。让我们学习如何计算它:
我们已经知道我们必须不断更新给定状态的状态-动作值,直到它饱和。因此,我们将修改前面的公式,如下所示:
在前面的等式中,我们将 1 替换为学习率,以便我们可以更逐渐地更新在某个状态下采取的动作的值:
有了这个 Q 值的正式定义,在下一节中,我们将了解 Gym 环境以及它如何帮助我们获取 Q 表(该表存储有关在各种情况下已采取的各种操作的值的信息)状态),从而得出一个状态下的最优动作。
在本节中,我们将在玩 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 值。在本节中,我们将利用 Gym 环境和与之关联的各种模块来填充 Q 表——其中行表示代理可以处于的状态,列表示代理可以采取的行动。Q 表的值表示在给定状态下采取行动的 Q 值。
我们可以使用以下策略填充 Q 表的值:
让我们编写前面的策略:
import numpy as np
import gym
import random
env = gym.make('FrozenLake-v0', is_slippery=False)
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()
total_rewards = 0
for step in range(50):
我们正在考虑每集最多 50 步,因为代理有可能永远在两个状态之间保持振荡(想想永远连续执行的左右动作)。因此,我们需要指定代理可以采取的最大步数。
action=env.action_space.sample()
new_state,reward,done,info=env.step(action)
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
total_rewards+=reward
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 表,它通过在多个剧集中重播游戏(在本例中为 Frozen Lake 游戏)提供与给定状态-动作组合相对应的值。然而,当状态空间是连续的(例如乒乓球游戏的快照)时,可能的状态空间的数量会变得巨大。我们将在本节以及接下来的内容中使用深度 Q 学习来解决这个问题。在本节中,我们将学习如何使用神经网络在没有 Q 表的情况下估计状态-动作组合的 Q 值——因此称为深度Q 学习。
与 Q 表相比,深度 Q 学习利用神经网络将任何给定的状态-动作(其中状态可以是连续或离散的)组合映射到 Q 值。
对于本练习,我们将使用 Gym 中的 CartPole 环境。在这里,我们的任务是尽可能长时间地平衡 CartPole。下图显示了 CartPole 环境的样子:
请注意,当小车向右移动时,杆向左移动,反之亦然。此环境中的每个状态都使用四个观察值定义,其名称以及最小值和最大值如下:
观察 | 最小值 | 最大值 |
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 学习的工作原理如下:
网络架构的高级概述如下:
在上图中,网络架构使用状态(四个观察值)作为输入,在当前状态下采取左右动作的 Q 值作为输出。我们训练神经网络如下:
有了前面的策略,让我们编写深度 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.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类,如下:
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
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)
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))
请注意,在前面的代码中,我们在确定要采取的行动时执行探索-利用。
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
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
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])
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
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 学习来解决 Gym 中的 CartPole 环境。在本节中,我们将研究一个更复杂的 Pong 游戏,并了解深度 Q 学习如何与固定目标模型一起解决该游戏。在处理此用例时,您还将学习如何利用基于 CNN 的模型(代替我们在上一节中使用的普通神经网络)来解决问题。
此用例的目标是构建一个可以与计算机(预先训练的非学习代理)对战并在乒乓球比赛中击败它的代理,该代理预计将获得 21 分。
我们将采用以下策略来解决为 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.定义一个将堆叠四个连续帧的函数,如下所示:
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)
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类,就像我们在上一节中所做的那样,如下所示:
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网络和目标网络的更新频率(这些行以粗体显示在前面的代码中)。
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)
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))
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,帮助确定是否应该更新目标模型的计数器在哪里。
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 坐标。输出将是汽车移动的快/慢,以及转向水平。该模拟将尝试准确表示现实世界的物理学。因此,请注意,无论是汽车模拟还是真实汽车,基本原理都将保持不变。
正如我们之前提到的,我们需要一个可以模拟复杂交互的环境,让我们相信我们实际上是在处理一个真实的场景。CARLA 就是这样一种环境。环境作者对 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 环境。让终端保持原样运行,因为我们需要在整个练习过程中在后台运行二进制文件。
由于没有官方的 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 传感器点云和我们的行车记录仪:
在这里,我们可以观察到以下几点:
除了这三个之外,CARLA 还提供额外的传感器数据,例如:
我们将使用前面提到的前四个传感器以及激光雷达和行车记录仪来训练模型。
我们现在准备好了解 CARLA 的组件并为自动驾驶汽车创建 DQN 模型。
我们将在笔记本中开始训练过程之前创建两个文件;也就是说,model.py和actor.py。这些将Agent分别包含模型架构和类。该类Agent包含我们将用于训练代理的各种方法。
这将是一个 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 个输出;我们稍后会看这些。
很像前面的部分,我们将使用一些代码来存储回放信息并在需要训练时回放:
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.现在,我们需要从重放内存中获取项目。以下指令在以下代码中执行:
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 笔记本中并开始训练。
我们不需要在这里学习额外的理论。基础保持不变;我们只会对 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字典中,就动作空间而言,以下内容对于我们的模拟很重要:
阅读官方文档后,请随意更改参数。
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()
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)
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 执行对象分类、检测、分割和图像生成。在下一章中,我们将换个角度学习如何将深度学习模型投入生产。