Dueling Network和DDQN都是三个文件,funcs.py、model.py和dueling.py或者ddpn.py。
对于funcs.py,其以前用于DDQN,所以再次使用。dueling.py代码也与ddpn.py相同(只是重命名)。因此,只需更改model.py。从DDQN复制相同的model.py文件,并总结对竞争网络结构所做的更改。所涉及的步骤如下:
首先在 model.py 中创建一个名为 DUELING 的布尔变量, 如果使用竞争网络结构, 则将其赋为 True; 否则, 将其赋为 False:
DUELING = True # False
使用 if 语句编写代码, 如果 DUELING 变量取值为 False, 使用之前在 DDQN 中使用的代码; 如果 DUELING 变量取值为 True, 则使用竞争网络。使用 flattened 对象, 即卷积层输出的扁平版本来创建两个子神经网络流。使用先前定义的 relu 激活函数和 winit 权重初始值设定项,分别将扁平化的数据发送到具有 512 个神经元的两个不同的全连接层中, 这些全连接层的输出值分别称为 valuestream 和 ad-vantagestream:
if (not DUELING):
# Q(s,a)
self.predictions = tf.contrib.layers.fully_connected(fc1, len(self.VALID_ACTIONS), activation_fn=None, weights_initializer=winit)
else:
# Deuling network
# branch out into two streams using flattened (i.e., ignore fc1 for Dueling DQN)
valuestream = tf.contrib.layers.fully_connected(flattened, 512, activation_fn=tf.nn.relu, weights_initializer=winit)
advantagestream = tf.contrib.layers.fully_connected(flattened, 512, activation_fn=tf.nn.relu, weights_initializer=winit)
这段代码是一个深度神经网络的定义,用于实现Dueling DQN算法中的网络结构。
Dueling DQN是一种改进的DQN算法,通过将Q值函数分解为状态值(Value)和动作优势值(Advantage)两个部分,从而提高了学习效果。
下面是对这段代码的解释:
①if (not DUELING)
:这个条件判断语句检查是否使用Dueling结构。
如果DUELING
为False,表示不使用Dueling结构,则会使用全连接层(fully connected layer)来表示Q值函数,并将其输出给self.predictions
。
②self.predictions:
这个变量表示网络的输出,即Q值函数的估计值。
在这里,self.predictions
通过一个全连接层(fully connected layer)得到,其中fc1
是前面的网络层的输出,通过tf.contrib.layers.fully_connected
函数定义,包括输入fc1
、输出动作空间的大小len(self.VALID_ACTIONS)
、激活函数为None,权重初始化方式为winit
。
③else:
:如果DUELING
为True,表示使用Dueling结构,则会分别定义状态值网络(Value Network)和动作优势值网络(Advantage Network)。
④valuestream
:这个变量表示状态值网络的输出,通过一个全连接层得到,其中flattened
是前面的网络层的输出,输入大小为512,激活函数为ReLU,权重初始化方式为winit
。
⑤advantagestream
:这个变量表示动作优势值网络的输出,同样通过一个全连接层得到,其中flattened
是前面的网络层的输出,输入大小为512,激活函数为ReLU,权重初始化方式为winit
。
这段代码实现了Dueling DQN算法中的网络结构,通过将状态值和动作优势值分别计算,并最后合并得到Q值函数的估计值。这种网络结构可以帮助算法更好地学习状态值和动作优势值之间的关系,从而提高学习效果。
advantagestream 对象被传递到一个全连接层中,该层的神经元数等于动作数,即 len(self.valid_actions)。 同样, valuestream 对象被传递到具有一个神经元的完全连接层中。
注意,不使用激活函数来计算优势函数和状态价值函数,因为 它们可以是正的,也可以是负的(relu会把所有的负值都设置为零)。最后,使用 tf.subtract()将优势流和价值流结合起来,以减去优势函数的平均值。平均值使用 advantage 函数中的 tf.reduce_mean( ) 计算:
# A(s,a)
self.advantage = tf.contrib.layers.fully_connected(advantagestream, len(self.VALID_ACTIONS), activation_fn=None, weights_initializer=winit)
# V(s)
self.value = tf.contrib.layers.fully_connected(valuestream, 1, activation_fn=None, weights_initializer=winit)
# Q(s,a) = V(s) + (A(s,a) - 1/|A| * sum A(s,a'))
self.predictions = self.value + tf.subtract(self.advantage, tf.reduce_mean(self.advantage, axis=1, keep_dims=True))
这段代码实现了Dueling DQN算法中的状态值网络(Value Network)和动作优势值网络(Advantage Network)之后,将它们合并得到Q值函数的估计值。
下面是对这段代码的解释:
①self.advantage
:这个变量表示动作优势值网络的输出,通过一个全连接层得到,其中advantagestream
是前面定义的动作优势值网络的输出,输入大小为动作空间的大小len(self.VALID_ACTIONS)
,激活函数为None,权重初始化方式为winit
。
②self.value
:这个变量表示状态值网络的输出,通过一个全连接层得到,其中valuestream
是前面定义的状态值网络的输出,输入大小为1(因为状态值是标量),激活函数为None,权重初始化方式为winit
。
③self.predictions
:这个变量表示最终的Q值函数的估计值,通过将状态值和动作优势值合并得到。其中,self.value
表示状态值,self.advantage
表示动作优势值。合并方式是将动作优势值减去其平均值,并加上状态值,从而得到最终的Q值函数的估计值。
④tf.subtract(self.advantage, tf.reduce_mean(self.advantage, axis=1,keep_dims=True))
:
这部分代码计算了动作优势值减去其平均值。tf.reduce_mean
函数计算了动作优势值的平均值,axis=1
表示按行进行平均,即对每个样本的动作优势值进行平均。keep_dims=True
表示保持维度不变,确保和状态值的维度一致。
⑤self.value + tf.subtract(self.advantage, tf.reduce_mean(self.advantage, axis=1, keep_dims=True))
:这部分代码将状态值和动作优势值减去其平均值后的值相加,得到最终的Q值函数的估计值。这种方式的合并方式是Dueling DQN算法中的一种常用方式,用于估计Q值函数的状态值和动作优势值。
这就是对竞争网络结构进行编码的方法。用竞争网络结构训练一个agent,并评估它在Atari Breakout问题中的性能。请注意,可以将竞争结构与DQN或DDQN结合使用。也就是说,只改变神经网络的结构,而不是实际的Bellman更新,所以竞争结构与DQN和DDQN一起工作。
(1)valuestream是前面定义的状态值网络的输出,输入大小为1(因为状态值是标量),为什么?
在Dueling DQN算法中,状态值网络(Value Network)用于估计状态的价值,而状态的价值通常是一个标量,表示当前状态对应的预期累积回报(expected cumulative reward)。因此,状态值网络的输出大小被设置为1,表示输出一个标量值作为状态的估计价值。
在这段代码中,valuestream
是通过一个全连接层计算得到的状态值网络的输出,输入是前面的flattened
,表示经过展平操作后的特征表示。valuestream
的输出大小设置为512,这是一个可调节的超参数,可以根据具体问题和实验设置不同的值。最终,self.value
作为状态值网络的输出,是一个标量值,表示当前状态的估计价值。
注意,Dueling DQN算法通过将状态值和动作优势值进行合并,得到最终的Q值函数的估计值。状态值和动作优势值的合并方式可以根据具体的网络架构和问题进行调整,这里的实现方式是其中一种常用的方式。
(2)tf.reduce_mean()
函数
tf.reduce_mean()
是 TensorFlow 中的一个函数,用于计算张量(Tensor)沿着指定维度的平均值。
函数的语法如下:
tf.reduce_mean(input_tensor, axis=None, keep_dims=False, name=None, reduction_indices=None, keepdims=None)
参数说明:
input_tensor
:输入的张量,可以是任意维度的张量。axis
:计算平均值的维度。可以是一个整数或者一个整数列表,表示沿着哪些维度计算平均值。默认为 None,表示对所有维度计算平均值。keep_dims
:是否保持结果的维度和输入张量一致。如果设置为 True,结果将保持和输入张量相同的维度;如果设置为 False(默认值),结果将减少维度。name
:操作的名称。reduction_indices
:已弃用,请使用 axis
替代。keepdims
:已弃用,请使用 keep_dims
替代。tf.reduce_mean()
函数用于在指定维度上计算平均值,并返回一个新的张量作为结果。这在神经网络中常用于计算损失函数的平均值、计算某一维度上的平均值等场景。
(3)tensorflow中axis的用法
在 TensorFlow 中,axis
参数用于指定在哪些维度上进行操作。它可以是一个整数或者一个整数列表,表示在哪些维度上进行操作。
具体来说,axis
参数可以用于以下情况:
计算沿着指定维度的平均值、总和、最大值、最小值等。
例如,tf.reduce_mean()
函数可以通过指定 axis
参数来计算沿着某个维度的平均值。
# 计算张量 x 沿着第一个维度的平均值
mean_x = tf.reduce_mean(x, axis=0)
例如,tf.reduce_mean()
函数可以通过指定 axis
参数为一个整数列表来计算多个维度上的平均值。
# 计算张量 x 沿着第一个维度和第三个维度的平均值
mean_x = tf.reduce_mean(x, axis=[0, 2])
# 沿着第一维度和第二维度计算平均值
mean_x_axis01 = tf.reduce_mean(x, axis=[0, 1])
# 沿着第一维度和第三维度计算平均值
mean_x_axis02 = tf.reduce_mean(x, axis=[0, 2])
# 沿着第二维度和第三维度计算平均值
mean_x_axis12 = tf.reduce_mean(x, axis=[1, 2])
tf.reduce_sum()
函数可以通过指定 axis
参数来计算沿着某个维度的总和。
# 计算张量 x 沿着第二个维度的总和
sum_x = tf.reduce_sum(x, axis=1)
(4)tf.subtract()函数
tf.subtract()
是 TensorFlow 中的一个函数,用于计算两个张量的差。它接受两个输入张量作为参数,并返回一个新的张量,其元素值为两个输入张量对应位置的差。
函数的语法如下:
tf.subtract(x, y, name=None)
其中,x
和 y
是输入的两个张量,可以具有相同的形状和数据类型。name
是一个可选的参数,用于为操作指定名称。
例如,可以使用 tf.subtract()
函数计算两个张量的差:
import tensorflow as tf
# 定义两个输入张量
a = tf.constant([1, 2, 3])
b = tf.constant([4, 5, 6])
# 计算两个张量的差
result = tf.subtract(a, b)
# 打印结果
print(result.numpy()) # 输出: [-3 -3 -3]
需要注意的是,输入张量的形状和数据类型应当相同,否则可能会出现类型不匹配或形状不一致的错误。