强化学习实现的代码实现参考主要是增强模型的探索能力-强化学习NoisyNet原理及实现!
论文地址:https://arxiv.org/abs/1706.10295
基础知识储备
- 强化学习流程
- Q-learning算法
- DQN算法
- epsilon贪婪策略
NoisyNet目标
在强化学习算法中,为了增强模型对动作的探索能力,通常使用的策略是epsilon
贪婪策略,而NoisyNet论文结果显示可以通过在全连接层增加噪声来代替epsilon
贪婪策略
NoisyNet理论
噪声网络是权重(weight
)和偏差(bias
)受到噪声函数扰动的神经网络
如果用y
表示网络的输出结果,x
表示网络的输入,f
表示网络的映射,神经网络可以表示成
其中theta
表示网络参数,或者表示成
输入的x
的形状为(batch_size,feature_size)
,所以参数w
的形状就是(units,feature_size)
,参数b
的形状是(units,)
,x
与w.T
进行矩阵乘法,得到的形状为(batch_size,units)
,就是y
的形状
加噪声之后,函数表示为
其中,mu
、sigma
和epsilon
都是和w
和b
形状相同的,bigodot
表示元素乘法
函数在计算过程中,mu
和sigma
是网络需要更新学习的参数,而epsilon
是每次进行前向传播的时候随机噪声
生成噪声的两种方式
独立高斯噪声
每次进行前向传播的时候直接随机生成高斯(正态)噪声,噪声的形状和w
和b
相同,所以一个噪声的形状是(units,feature_size)
,一个噪声的形状是(units,)
,以共需要生成的噪声数量就是units * feature_size + units
因子分解高斯噪声
因子分解高斯噪声主要可以避免生成一个(units,feature_size)
的噪声,而通过因子分解的方式分别生成一个(units,1)
和(1,feature_size)
的噪声,经过噪声函数f
映射之后,进行矩阵乘积生成一个(units,feature_size)
的噪声作为权重的噪声,而偏差的噪声形状仍然不变,所以以共需要生成的噪声数量就是2 * units + feature_size
,与独立高斯噪声相比可以减少很多资源
代码实现
class Noisy(nn.HybridBlock):
def __init__(self, units, activation=None, use_bias=True, flatten=True, dtype='float32', weight_initializer=None, bias_initializer='zeros', in_units=0, noisy_distribution='independent', **kwargs):
super(Noisy, self).__init__(**kwargs)
self._flatten = flatten # 是否需要拍平,作为FullyConnected的输入参数
self.dtype = dtype
self.ctx = try_gpu(GPU_INDEX) # 指定计算环境GPU还是CPU
with self.name_scope():
self._units = units # 全连接层的网络输出节点数
self._in_units = in_units # 全连接层的网络输入节点数
self.weights = self.params.get('weights', shape=(units, in_units), init=weight_initializer, dtype=dtype,
allow_deferred_init=False) # 允许参数延迟初始化,权重参数
self.weight_sigma = self.params.get('weight_sigma', shape=(units, in_units),
init=weight_initializer, dtype=dtype,
allow_deferred_init=False) # 权重方差
if use_bias:
self.bias = self.params.get('bias', shape=(units, ), init=bias_initializer, dtype=dtype,
allow_deferred_init=False) # 偏差参数
self.bias_noise = self.params.get('bias_noise', shape=(1, units), init=bias_initializer, dtype=dtype,
allow_deferred_init=False) # 偏差方差
else:
self.bias = None
if activation is not None:
self.act = nn.Activation(activation, prefix=activation+'_') # 是否使用激活函数
else:
self.act = None
self.noisy_distribution = noisy_distribution # 生成噪声的方式,独立高斯还是因子分解
def hybrid_forward(self, F, x, weights, weight_sigma, bias=None, bias_noise=None):
# 因子分解中的噪声映射函数
def real_valued_f(e_list):
return F.multiply(F.sign(e_list), F.power(F.abs(e_list), 0.5))
global noise_1, b
if self.noisy_distribution == 'independent':
w = weights + F.multiply(F.random_normal(scale=0.1, shape=weight_sigma.shape, ctx=self.ctx, dtype=self.dtype),
weight_sigma) # 元素乘法
else:
noise_1 = real_valued_f(F.random_normal(scale=0.1, shape=(self._units, 1), ctx=self.ctx, dtype=self.dtype))
noise_2 = real_valued_f(F.random_normal(scale=0.1, shape=(1, self._in_units), ctx=self.ctx, dtype=self.dtype))
w = weights + F.multiply(F.dot(noise_1, noise_2), weight_sigma)
if self.bias is not None:
if self.noisy_distribution == 'independent':
b = bias + F.multiply(F.random_normal(scale=0.1, shape=bias.shape, ctx=self.ctx, dtype=self.dtype),
bias_noise.reshape(bias.shape))
else:
b = bias + F.multiply(noise_1.reshape(bias.shape), bias_noise.reshape(bias.shape))
act = F.FullyConnected(x, w, b, no_bias=bias is None, num_hidden=self._units,
flatten=self._flatten, name='fwd') # 处理之后的权重和偏差输入到全连接层
if self.act is not None:
act = self.act(act) # 指定是否加激活层
return act
类使用方法
if __name__ == '__main__':
net = nn.Sequential()
a = Noisy(5, in_units=10
, weight_initializer=init.Uniform(0.1),
bias_initializer=init.Constant(0.1)
)
net.add(Noisy(5, in_units=10
, weight_initializer=init.Uniform(0.1),
bias_initializer=init.Constant(0.1)
))
# net.add(nn.Dense(5, weight_initializer=init.Uniform(0.1)))
net.initialize(init=init.Xavier(), ctx=mx.cpu(1))
x = nd.arange(20 * 10).reshape((20, 10))
y = net(x)
print(y)
总结
- 使用噪声网络会增加网络参数,在计算资源有限的情况下,可能需要修改原来的网络参数,以减少网络参数所占用的资源
- 该代码的实现方式主要参考
nn.Dense
源代码的实现方式,关于HybridBlock
和Block
类之间的实现方式可以参考上一篇文章mxnet采坑记 -
Dense
层的源代码中allow_deferred_init
的参数设置为True
,表示支持延迟初始化,即等到第一个数据传入的时候才会根据数据推断网络的输入节点数,而噪声网络中由于前向网络的计算依赖于初始化的权重和偏差参数,所以不支持延迟初始化,需要在初始化该类的时候指定in_units
参数
网友评论