在Carla上应用深度强化学习实现自动驾驶(三)
以下代码参考自:(如有侵权,请联系我立即删除)、
使用 Carla 和 Python 的自动驾驶汽车第 4 部分 —— 强化学习代理_强化学习与carla是如何联合的过程-CSDN博客
本篇文章是小编在pycharm上自己手敲代码学习自动驾驶的第三篇文章,主要讲述如何编写Agent类,它将与我们设计的强化学习环境交互,并容纳我们实际的强化学习模型。
1、创建DQNAgent类,首先定义init方法
该方法的主要内容如下:
(1)构造两个网络model和target_model,一个为不断进化的主网络(又叫策略网络),一个为目标网络,刚开始两个网络的权重相同
(2)构造经验回放内存存储区,由一个双端队列deque构成。
该区域用于存储一系列的经验(或称为转换、transitions),每个transition通常包含四个或五个元素:当前状态(state)、采取的动作(action)、获得的奖励(reward)、新状态(next state),以及一个布尔值表示该状态是否是终止状态(done);
这里使用deque构造函数创建了一个双端队列,并通过maxlen参数设置了队列的最大长度。这意味着当队列中的元素数量超过 REPLAY_MEMORY_SIZE 时,最旧的元素将被自动从队列的一端移除,以便为新元素腾出空间。这样做可以限制内存的使用,并确保经验回放内存中的经验保持最新。
经验回放内存的主要作用是允许算法在训练时随机地采样一批经验,而不是仅仅使用最新的经验。这样做可以打破经验之间的时间相关性,并减少训练过程中的波动。此外,由于经验被存储在内存中,算法可以多次使用这些经验来训练模型,从而提高数据的利用效率。
(3)设置一些参数:tf.get_defaut_graph这个函数可以获取当前默认的计算图;self.training_initialized用于跟踪TensorFlow何时准备就绪
class DQNAgent:
def __init__(self):
self.model = self.create_model()
self.target_model = self.create_model()
self.target_model.set_weights(self.model.get_weights())
self.replay_memory = deque(maxlen=REPLAY_MEMORY_SIZE)
self.tensorboard = ModifiedTensorBoard(log_dir=f"logs/{MODEL_NAME}-{int(time.time())}")
self.target_update_counter = 0
self.graph = tf.get_default_graph()
self.terminate = False
self.last_logged_episode = 0
self.training_initialized = False
2、定义创建网络模型的方法create_model
该方法的主要内容如下:
(1)导入基础模型Xception模型,但是您也可以制作其他模型,或者导入一个不同的模型。
weights=None的意思是:不加载任何预训练的权重。这意味着Xception模型的所有权重(即,网络中的连接参数)将被随机初始化(根据层的默认初始化策略,除非你在层定义中指定了不同的初始化器)。然后,这些权重将在训练过程中通过反向传播算法进行更新,以最小化指定的损失函数;
include_top=False将加载预训练模型的所有卷积层(或其他类型的特征提取层),但不包括顶部的全连接层,这意味着模型不包括用于分类的输出层;
最后一个参数表示输入图像的尺寸为 (IM_HEIGHT, IM_WIDTH, 3),即图像的高度、宽度和颜色通道数。
Xception模型-CSDN博客Xception模型具体内容参考我的另一篇文章:Xception模型-CSDN博客
(2)针对实际情况对基础模型进行修改:通过 base_model.output 获取基础模型的输出,然后使用 GlobalAveragePooling2D 对特征图进行全局平均池化,这有助于减少参数数量,并减少过拟合的风险。使用 Dense 层添加一个具有3个神经元的全连接层( 3 表示这个层有3个神经元(或称为单元)。这通常对应于您想要模型预测的输出值的数量。在您的情况下,这可能意味着您正在处理一个回归问题,并且您希望模型预测3个连续值),激活函数为 "linear",这意味着输出是连续的数值。
(3)定义一个新的Keras模型,指定了模型的输入和输出。
(4)编译模型:使用均方误差(MSE)作为损失函数,这适用于回归问题。使用 Adam 优化器,并设置学习率为 0.001。监控模型的准确率。
(5)最后返回编译好的模型。
总之,这个函数创建了一个基于 Xception 的深度学习模型,用于处理图像数据并输出三个连续的预测值。
def create_model(self):
base_model = Xception(weights=None, include_top=False, input_shape=(IM_HEIGHT, IM_WIDTH,3))
x = base_model.output
x = GlobalAveragePooling2D()(x)
predictions = Dense(3, activation="linear")(x)
model = Model(inputs=base_model.input, outputs=predictions)
model.compile(loss="mse", optimizer=Adam(lr=0.001), metrics=["accuracy"])
return model
让我们为这个方法做一些必需的导入:
from keras.applications.xception import Xception
from keras.layers import Dense, GlobalAveragePooling2D
from keras.optimizers import Adam
from keras.models import Model
3、定义更新经验回放内存的方法update_replay_memory
def update_replay_memory(self, transition):
# transition = (current_state, action, reward, new_state, done)
self.replay_memory.append(transition)
4、定义训练方法train
该方法的主要内容如下:
(1)当经验回放池中的经验数目小于最小经验数目时,不进行训练。训练只开始于经验数目大于等于最小经验数目时
(2)从经验回放池中随机抽取一批经验作为本次训练的样本数据
(3)获取当前状态的Q值:
从一个名为minibatch的列表中提取每个transition的第一个部分,即当前状态current_state。然后,将这些状态转换成一个NumPy数组,并除以255进行归一化处理,使得所有状态值都在0到1之间。这是深度学习中常用的预处理步骤,可以帮助模型更好地学习。
(回忆一下过渡是: transition = (current_state, action, reward, new_state, done))
with语句用来确保接下来的操作是在特定的TensorFlow图中执行的。这是TensorFlow 1.x版本的语法,用于指定操作的上下文环境。
然后使用训练中的模型(self.model)对当前状态进行预测,得到每个动作的Q值(即预期的未来奖励)。PREDICTION_BATCH_SIZE指定了每次预测时处理的批次大小。
(4)获取下一个状态的Q值(注意这不是目标Q值,只是用于计算目标Q值的一部分):
与获取当前Q值的代码类似,不过这里提取的事transition的第四个部分,即执行某个动作后的下一个状态new_state,同样,也需要归一化。
然后使用目标模型(self.target_model)对下一个状态进行预测,得到对下一个状态下各个动作Q值的估计。在DQN中,目标模型是主模型的一个副本,用于计算目标Q值,这有助于稳定训练过程。
(5)创建输入x和输出y,然后计算目标Q值
先介绍一下计算目标Q值的方法:
- 首先,根据主网络model选择当前状态下的最优动作。
- 然后,将该最优动作以及下一个状态输入到目标网络target_model中,以估计执行该动作后可能获得的未来总回报。
- 目标Q值通常是即时奖励加上经过折扣的未来最大预期奖励(即下一个状态的折现最大Q值)。
在这里,根据done值的不同计算目标Q值的方法也不同:
- 如果
done为False,表示当前状态不是序列的最后一个状态,那么您需要计算未来奖励的折现值。您通过查找future_qs_list[index](即目标模型对下一个状态new_state下所有可能动作的Q值预测)中的最大值(max_future_q),并将其乘以折现因子DISCOUNT,然后加上即时奖励reward,来得到目标Q值new_q。 -
如果
done为True,表示当前状态是序列的最后一个状态,那么未来没有更多的奖励可以获取,因此目标Q值new_q就简单地等于即时奖励reward。
(6)更新Q值
这里解释一下为什么有current_qs[action] = new_q这行代码:(我刚开始看的时候也不是很明白,所以查了一下资料)
-
用当前Q值作为伪标签:在实际操作中,我们通常会使用在线模型对当前状态(
current_state)下所有动作的Q值进行预测,并将这些预测值存储在current_qs_list中。然后,对于每个样本,我们将目标Q值(new_q)放在对应动作的位置上,而将其他动作的Q值保持不变(尽管在技术上,这些值也可以被更新为某种形式的“伪”Q值,但这通常不是DQN的标准做法)。 -
current_qs[action] =new_q的作用:这行代码的作用是将current_qs数组中当前动作对应的Q值更新为目标Q值。这样,我们就为在线模型提供了一个标签,该标签告诉模型在给定当前状态下,采取当前动作是“正确”的,因为它能够带来最大的未来总回报(根据目标网络的预测)。 -
训练过程:然后,我们使用这些输入-输出对(即
X和y列表)来训练在线模型。具体来说,我们计算在线模型预测的Q值与目标Q值之间的差异,并使用这个差异来更新在线模型的参数。
需要注意的是,尽管我们在准备训练数据时更新了current_qs数组,但这并不会影响在线模型的实际参数或状态。在线模型的参数是通过梯度下降等优化算法根据损失函数来更新的,而不是直接通过修改current_qs来更新的。
(7)我们只尝试记录每一集,而不是实际的训练步骤,所以我们设置了日志记录条件:
首先,检查是否应该记录当前步骤(或称为“集话”或“回合”)的日志。这是通过比较self.tensorboard.step(TensorBoard记录的当前步骤)和self.last_logged_episode(上一次记录日志的集话)来实现的。
如果self.tensorboard.step大于self.last_logged_episode,则将log_this_step设置为True,并更新self.last_log_episode为当前步骤,以便下次比较。
(8)利用fit方法进行模型训练:
使用with self.graph.as_default():确保在正确的图(Graph)上下文中执行操作,这在TensorFlow 1.x中很常见,但在TensorFlow 2.x中通常不是必需的,因为默认启用了Eager Execution。调用self.model.fit()来训练模型。这里,X和y分别是训练数据的输入和标签,它们被转换为NumPy数组并归一化(np.array(X)/255)。归一化是一种常见的预处理步骤,特别是在处理图像数据时,它有助于模型更快地收敛。batch_size=TRAINING_BATCH_SIZE指定了每个批次中的样本数。verbose=0表示在训练过程中不输出日志信息。shuffle=False表示不打乱训练数据的顺序。如果log_this_step为True,则使用self.tensorboard作为回调(callback),以便在TensorBoard中记录训练过程。如果log_this_step为False,则不记录。
(9)设置目标网络的参数的更新条件:
如果log_this_step为True,则增加self.target_update_counter的计数。当self.target_update_counter大于某个阈值UPDATE_TARGET_EVERY时,将在线模型(self.model)的权重复制到目标模型(self.target_model)中,并重置self.target_update_counter为0。这是DQN(深度Q网络)中的一个常见做法,用于稳定训练过程。
def train(self):
if len(self.replay_memory) < MIN_REPLAY_MEMORY_SIZE:
return
minibatch = random.sample(self.replay_memory, MINIBATCH_SIZE)
current_states = np.array([transition[0] for transition in minibatch])/255
with self.graph.as_default():
current_qs_list = self.model.predict(current_states, PREDICTION_BATCH_SIZE)
new_current_states = np.array([transition[3] for transition in minibatch])/255
with self.graph.as_default():
future_qs_list = self.target_model.predict(new_current_states, PREDICTION_BATCH_SIZE)
X = []
y = []
for index, (current_state, action, reward, new_state, done) in enumerate(minibatch):
if not done:
max_future_q = np.max(future_qs_list[index])
new_q = reward + DISCOUNT * max_future_q
else:
new_q = reward
current_qs = current_qs_list[index]
current_qs[action] = new_q
X.append(current_state)
y.append(current_qs)
log_this_step = False
if self.tensorboard.step > self.last_logged_episode:
log_this_step = True
self.last_log_episode = self.tensorboard.step
with self.graph.as_default():
self.model.fit(np.array(X)/255, np.array(y), batch_size=TRAINING_BATCH_SIZE, verbose=0, shuffle=False, callbacks=[self.tensorboard] if log_this_step else None)
if log_this_step:
self.target_update_counter += 1
if self.target_update_counter > UPDATE_TARGET_EVERY:
self.target_model.set_weights(self.model.get_weights())
self.target_update_counter = 0
5、定义获取Q值的方法get_qs
这个方法接收一个 state(状态),将其转换为模型可以接受的格式(即,归一化并重塑为模型输入的形状),然后通过模型进行预测,并返回预测的第一个Q值(假设模型输出的是每个可能动作的Q值数组)。
def get_qs(self, state):
return self.model.predict(np.array(state).reshape(-1, *state.shape)/255)[0]
6、定义无限循环的方法train_in_loop: 首先使用一些随机数据来初始化训练数据,然后开始无限循环
def train_in_loop(self):
X = np.random.uniform(size=(1, IM_HEIGHT, IM_WIDTH, 3)).astype(np.float32)
y = np.random.uniform(size=(1, 3)).astype(np.float32)
with self.graph.as_default():
self.model.fit(X,y, verbose=False, batch_size=1)
self.training_initialized = True
while True:
if self.terminate:
return
self.train()
time.sleep(0.01)
好的,本篇文章到这里就结束了,完整代码如下:‘
import glob
import os
import sys
import random
import time
import numpy as np
import cv2
import math
import tensorflow as tf
from collections import deque
from keras.applications.xception import Xception
from keras.layers import Dense, GlobalAveragePooling2D
from keras.optimizers import Adam
from keras.models import Model
from keras.callbacks import TensorBoard
try:
sys.path.append(glob.glob(
r'D:\postgraduate\code\CARLA_0.9.14\WindowsNoEditor\PythonAPI\carla\dist\carla-0.9.14-py3.7-win-amd64.egg')[0])
except IndexError:
pass
import carla
SHOW_PREVIEW = False
IM_WIDTH = 640
IM_HEIGHT = 480
SECONDS_PER_EPISODE = 10
REPLAY_MEMORY_SIZE = 5_000
MIN_REPLAY_MEMORY_SIZE = 1_000
MINIBATCH_SIZE = 16
PREDICTION_BATCH_SIZE = 1
TRAINING_BATCH_SIZE = MINIBATCH_SIZE // 4
UPDATE_TARGET_EVERY = 5
MODEL_NAME = "Xception"
MEMORY_FRACTION = 0.8
MIN_REWARD = -200
EPISODES = 100
DISCOUNT = 0.99
epsilon = 1
EPSILON_DECAY = 0.95 ## 0.9975 99975
MIN_EPSILON = 0.001
AGGREGATE_STATS_EVERY = 10
...
# Own Tensorboard class
class ModifiedTensorBoard(TensorBoard):
# Overriding init to set initial step and writer (we want one log file for all .fit() calls)
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.step = 1
self.writer = tf.summary.FileWriter(self.log_dir)
# Overriding this method to stop creating default log writer
def set_model(self, model):
pass
# Overrided, saves logs with our step number
# (otherwise every .fit() will start writing from 0th step)
def on_epoch_end(self, epoch, logs=None):
self.update_stats(**logs)
# Overrided
# We train for one batch only, no need to save anything at epoch end
def on_batch_end(self, batch, logs=None):
pass
# Overrided, so won't close writer
def on_train_end(self, _):
pass
# Custom method for saving own metrics
# Creates writer, writes custom metrics and closes writer
def update_stats(self, **stats):
self._write_logs(stats, self.step)
class DQNAgent:
def __init__(self):
self.model = self.create_model()
self.target_model = self.create_model()
self.target_model.set_weights(self.model.get_weights())
self.replay_memory = deque(maxlen=REPLAY_MEMORY_SIZE)
self.tensorboard = ModifiedTensorBoard(log_dir=f"logs/{MODEL_NAME}-{int(time.time())}")
self.target_update_counter = 0
self.graph = tf.get_default_graph()
self.terminate = False
self.last_logged_episode = 0
self.training_initialized = False
def create_model(self):
base_model = Xception(weights=None, include_top=False, input_shape=(IM_HEIGHT, IM_WIDTH,3))
x = base_model.output
x = GlobalAveragePooling2D()(x)
predictions = Dense(3, activation="linear")(x)
model = Model(inputs=base_model.input, outputs=predictions)
model.compile(loss="mse", optimizer=Adam(lr=0.001), metrics=["accuracy"])
return model
def update_replay_memory(self, transition):
# transition = (current_state, action, reward, new_state, done)
self.replay_memory.append(transition)
def train(self):
if len(self.replay_memory) < MIN_REPLAY_MEMORY_SIZE:
return
minibatch = random.sample(self.replay_memory, MINIBATCH_SIZE)
current_states = np.array([transition[0] for transition in minibatch])/255
with self.graph.as_default():
current_qs_list = self.model.predict(current_states, PREDICTION_BATCH_SIZE)
new_current_states = np.array([transition[3] for transition in minibatch])/255
with self.graph.as_default():
future_qs_list = self.target_model.predict(new_current_states, PREDICTION_BATCH_SIZE)
X = []
y = []
for index, (current_state, action, reward, new_state, done) in enumerate(minibatch):
if not done:
max_future_q = np.max(future_qs_list[index])
new_q = reward + DISCOUNT * max_future_q
else:
new_q = reward
current_qs = current_qs_list[index]
current_qs[action] = new_q
X.append(current_state)
y.append(current_qs)
log_this_step = False
if self.tensorboard.step > self.last_logged_episode:
log_this_step = True
self.last_log_episode = self.tensorboard.step
with self.graph.as_default():
self.model.fit(np.array(X)/255, np.array(y), batch_size=TRAINING_BATCH_SIZE, verbose=0, shuffle=False, callbacks=[self.tensorboard] if log_this_step else None)
if log_this_step:
self.target_update_counter += 1
if self.target_update_counter > UPDATE_TARGET_EVERY:
self.target_model.set_weights(self.model.get_weights())
self.target_update_counter = 0
def get_qs(self, state):
return self.model.predict(np.array(state).reshape(-1, *state.shape)/255)[0]
def train_in_loop(self):
X = np.random.uniform(size=(1, IM_HEIGHT, IM_WIDTH, 3)).astype(np.float32)
y = np.random.uniform(size=(1, 3)).astype(np.float32)
with self.graph.as_default():
self.model.fit(X,y, verbose=False, batch_size=1)
self.training_initialized = True
while True:
if self.terminate:
return
self.train()
time.sleep(0.01)
更多推荐
所有评论(0)