引言

早就想试试用AI玩游戏,尤其是塔防游戏。现在从零开始,一点点前进,最终目标是搞定塔防。

环境

windows 10,Python 3.6, 用pip安装的gym,numpy

具体实现

来源

本次项目的代码思想来自深入浅出的强化学习笔记(二)——使用OpenAI Gym实现游戏AI,我主要对代码中的部分内容进行解释,并且进行一定改进。

解释

  1. 离散化处理。原文预先对小车的状态数据进行了离散化处理,四项数据每项都划分为四块,如小车位置的实际范围是[-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))]
  1. 状态转移。其实就是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!')
  1. 没什么好说的,提升训练性能而已。
if last_time_steps.mean() >= goal_average_steps-5: #(改动)为了提升训练性能,仅在最后阶段显示演示效果
  1. 重点改动。原文传递的是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作为奖励值
  1. 原文此处直接写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,用其他算法也行。