引言
早就想试试用AI玩游戏,尤其是塔防游戏。现在从零开始,一点点前进,最终目标是搞定塔防。
环境
windows 10,Python 3.6, 用pip安装的gym,numpy
具体实现
来源
本次项目的代码思想来自深入浅出的强化学习笔记(二)——使用OpenAI Gym实现游戏AI,我主要对代码中的部分内容进行解释,并且进行一定改进。
解释
- 离散化处理。原文预先对小车的状态数据进行了离散化处理,四项数据每项都划分为四块,如小车位置的实际范围是[-2.4,2.4],将其划分4块,第一块为[-2.4,-1.2],第二块为[-1.2,0],以此类推。当从gym的env获取到当前状态后,比如当前小车的位置是2.0,就将2.0用numpy.digitize映射过去,发现在第四块,以此锁定。
因此,如果你觉得范围划分太过粗糙,可以将其具体划分到10块,这样有助于提升训练精度和稳定性。
digitized = [np.digitize(cart_pos, bins=bins(-2.4, 2.4, 4)),
np.digitize(cart_v, bins=bins(-3.0, 3.0, 4)),
np.digitize(pole_angle, bins=bins(-0.5, 0.5, 4)),
np.digitize(pole_v, bins=bins(-2.0, 2.0, 4))]
- 状态转移。其实就是Q-Learning的状态转移函数,用q_table实现。每一个单元内存储对应状态-操作下的预期收入,每次根据状态选择最佳行动。
q_table[state, action] = (1 - alpha) * q_table[state, action] + alpha * (reward + gamma * q_table[next_state, next_action])
改动
实际上,按照原文作者所说,大约在1000次左右会训练成功,但是我尝试后平均分数一直在80上下浮动。因此我做出了如下三处改动。
for episode in range(num_episodes):
observation = env.reset() # 初始化本场游戏的环境
state = digitize_state(observation) # 获取初始状态值
action = np.argmax(q_table[state]) # 根据状态值作出行动决策
episode_reward = 0
# 一场游戏分为一个个时间步
for t in range(max_number_of_steps):
if last_time_steps.mean() >= goal_average_steps-5: #(改动)为了提升训练性能,仅在最后阶段显示演示效果
env.render() # 更新并渲染游戏画面
observation, reward, done, info = env.step(action) # 获取本次行动的反馈结果
action, state = get_action(state, action, observation, episode_reward*0.9**np.min([t,30])) # (改动)以每局的积累reward作为奖励值
episode_reward += reward
if done:
if t < max_number_of_steps-1:
get_action(state, action, observation, -200) #(改动)若当前得分不够200且失败,则将分数记为负数,帮助修改
print('%d Episode finished after %f time steps / mean %f' % (episode, t + 1, last_time_steps.mean()))
last_time_steps = np.hstack((last_time_steps[1:], episode_reward)) # 更新最近100场游戏的得分stack
break
# 如果最近100场平均得分高于195
if (last_time_steps.mean() >= goal_average_steps):
print('Episode %d train agent successfuly!' % episode)
break
print('Failed!')
- 没什么好说的,提升训练性能而已。
if last_time_steps.mean() >= goal_average_steps-5: #(改动)为了提升训练性能,仅在最后阶段显示演示效果
- 重点改动。原文传递的是reward,我输出后发现每一个步骤,只要不导致死亡,游戏范围的reward一直只有1,即+1s。于是我作出一个假设:只要续的时间够长,越往后的行为就具有越大的价值。于是我将reward改成episode_reward(当局游戏累积奖励)。但是这么改动后训练到一定程度会出现抖动现象,导致后续权重大幅度提升,不利于训练。所以我乘以0.9^t,使其在后续时间步内具有一定衰减;又不想衰减过度,设置了一个30的上限,结果在500次左右就能完成训练。参数是随便选的,大家可以试试其他参数。
action, state = get_action(state, action, observation, episode_reward*0.9**np.min([t,30])) # (改动)以每局的积累reward作为奖励值
- 原文此处直接写reward=-200,但是我实在看不到后续哪里跑了这一步,所以改为get_action。
if t < max_number_of_steps-1:
get_action(state, action, observation, -200) #(改动)若当前得分不够200且失败,则将分数记为负数,帮助修改
结果与总结
……
621 Episode finished after 200.000000 time steps / mean 192.290000
622 Episode finished after 200.000000 time steps / mean 192.910000
623 Episode finished after 200.000000 time steps / mean 192.910000
624 Episode finished after 200.000000 time steps / mean 193.360000
625 Episode finished after 200.000000 time steps / mean 193.630000
626 Episode finished after 200.000000 time steps / mean 194.360000
627 Episode finished after 200.000000 time steps / mean 194.360000
628 Episode finished after 200.000000 time steps / mean 194.690000
Episode 628 train agent successfuly!
这次的结果在628次,可以看到最后几次的结果都达到了200s。有时候会发生抖动现象,导致在1k次以外收敛,稳定性不太好,可能算法还有可以优化的地方。我这里采用的仍然是4分块的代码,参数也没优化,大家可以试试优化,或者不要使用Q-Learning,用其他算法也行。