从零使用强化学习训练AI玩儿游戏(8)——使用DQN(Keras)
上一篇我们讲了怎么用TensorFlow搭建DQN来玩游戏,这一篇我们使用Keras算法基本上跟上一篇一样,玩的游戏也一样
这几天,天天找工作面试,终于有点时间把Keras的版本给写了。从TensorFlow换到用Keras搭建的神经网络了,这样就有便于后面我们把神经网络换成CNN加LSTM的操作,因为如果用TensorFlow加这两的话实在是没有Keras方便。
首先我们来解决一个Keras可视化的问题,Keras的可视化需要额外的安装几个包代码如下
pip3 install pydot
pip3 install pydot-ng
pip3 install graphviz
但是光执行这个是会报错的,而且错误非常有误导性,其实是需要去graphviz的官网上下载一个安装包,安装过后还需要添加环境变量path C:\Program Files (x86)\Graphviz2.38\bin 一直到这个bin文件就好了
加入环境变量过后记得重启,就能正常的显示我们搭建的两个神经网络图片了(我就是忘记重启了搞了半天)
下面贴出我创建的简单神经网络的图:
在这里我们还是用两个神经网络加上记忆库来解决神经网络相关性等问题,今天在网上看到只只用了一个神经网络加记忆库的办法也是可以的。
用Keras搭建的代码如下,建造了两个全连接层第一个输入层的每个神经细胞都配置了一个**函数
最后的输出层没有配置**函数,如果输出层配置了**函数他就变成了一个分类的神经网络,但是仔细看DQN的定义,其实可以发现他是类似于线性的神经网络,因为需要输出的不是几个固定的类别,而是连续的Q值。
其实看Keras的代码,感觉比TensorFlow更能直观的理解DQN了。
def _build_net(self):
# ------------------ 建造估计层 ------------------
# 因为神经网络在这个地方只是用来输出不同动作对应的Q值,最后的决策是用Q表的选择来做的
# 所以其实这里的神经网络可以看做是一个线性的,也就是通过不同的输入有不同的输出,而不是确定类别的几个输出
# 这里我们先按照上一个例子造一个两层每层单个神经元的神经网络
self.model_eval = Sequential([
# 输入 并且给每一个神经元配一个**函数
Dense(self.first_layer_neurno, input_dim=self.n_features, activation='relu'),
# Activation('relu'),
# Dense(1, activation='tanh'),
# 输出
Dense(self.n_actions),
])
# 选择rms优化器,输入学习率参数
rmsprop = RMSprop(lr=self.lr, rho=0.9, epsilon=1e-08, decay=0.0)
self.model_eval.compile(loss='mse',
optimizer=rmsprop,
metrics=['accuracy'])
# ------------------ 构建目标神经网络 ------------------
# 目标神经网络的架构必须和估计神经网络一样,但是不需要计算损失函数
self.model_target = Sequential([
# 输入 并且给每一个神经元配一个**函数
Dense(self.first_layer_neurno, input_dim=self.n_features, activation='relu'),
# Activation('relu'),
# Dense(1, activation='tanh'),
# 输出
Dense(self.n_actions),
])
接下来看看learn函数,这样就能更直接的理解DQN,q_eval和q_target的转化在这一篇有讲,这儿就不说了,主要看一下训练估计神经网络这条
self.cost = self.model_eval.train_on_batch(batch_memory[:, :self.n_features], q_target)
这里跟TensorFlow的变化稍微有点大,但其实都一样,就是把q_target作为估计神经网络的期望值输入给他做训练,
q_next是目标神经网络的q值,q_eval是估计神经网络的q值
q_next是用现在状态得到的q值 q_eval是用这一步之前状态得到的q值,
也就是说用target神经网络对现在状态预测出的Q值乘以一个gamma + 这一步得到的奖励,作为期望输入给估计神经网络,通过损失函数更新神经网络参数,有点绕可以多理解一下。
def learn(self):
# 经过一定的步数来做参数替换
if self.learn_step_counter % self.replace_target_iter == 0:
self.model_target.set_weights(self.model_eval.get_weights())
print('\ntarget_params_replaced\n')
# 随机取出记忆
if self.memory_counter > self.memory_size:
sample_index = np.random.choice(self.memory_size, size=self.batch_size)
else:
sample_index = np.random.choice(self.memory_counter, size=self.batch_size)
batch_memory = self.memory[sample_index, :]
# 这里需要得到估计值加上奖励 成为训练中损失函数的期望值
# q_next是目标神经网络的q值,q_eval是估计神经网络的q值
# q_next是用现在状态得到的q值 q_eval是用这一步之前状态得到的q值
# print(batch_memory[:, -self.n_features:])
q_next = self.model_target.predict(batch_memory[:, -self.n_features:], batch_size=self.batch_size)
q_eval = self.model_eval.predict(batch_memory[:, :self.n_features], batch_size=self.batch_size)
# change q_target w.r.t q_eval's action
q_target = q_eval.copy()
batch_index = np.arange(self.batch_size, dtype=np.int32)
eval_act_index = batch_memory[:, self.n_features].astype(int)
reward = batch_memory[:, self.n_features + 1]
q_target[batch_index, eval_act_index] = reward + self.gamma * np.max(q_next, axis=1)
# 训练估计网络,用的是当前观察值训练,并且训练选择到的数据加奖励训练而不是没选择的
self.cost = self.model_eval.train_on_batch(batch_memory[:, :self.n_features], q_target)
self.cost_his.append(self.cost)
# increasing epsilon
self.epsilon = self.epsilon + self.epsilon_increment if self.epsilon < self.epsilon_max else self.epsilon_max
self.learn_step_counter += 1
运行程序,很快就能达到平衡,还出现过1600多秒,我还以为掉不下去了。
下面是得到的cost图,由于随机性没有像回归或者分类问题一样有一个趋于稳定的趋势。
这一篇做了重大改动,我们从TensorFlow跳转到了Keras,这样下一篇我们就能使用CNN,LSTM或更复杂得网络来玩更复杂的游戏了。
源代码,这里非需要下载积分,等过几天我上次到GitHub上就不需要积分了(GitHub上源代码)
其实我只想求点个赞就好了