• 动手实践丨基于ModelAtrs使用A2C算法制作登月器着陆小游戏


    摘要:在本案例中,我们将展示如何基于A2C算法,训练一个LunarLander小游戏。

    本文分享自华为云社区《使用A2C算法控制登月器着陆》,作者:HWCloudAI 。

    LunarLander是一款控制类的小游戏,也是强化学习中常用的例子。游戏任务为控制登月器着陆,玩家通过操作登月器的主引擎和副引擎,控制登月器降落。登月器平稳着陆会得到相应的奖励积分,如果精准降落在着陆平台上会有额外的奖励积分;相反地如果登月器坠毁会扣除积分。

    A2C全称为Advantage Actor-Critic,在本案例中,我们将展示如何基于A2C算法,训练一个LunarLander小游戏。

    整体流程:基于gym创建LunarLander环境->构建A2C算法->训练->推理->可视化效果

    A2C算法的基本结构

    A2C是openAI在实现baseline过程中提出的,是一种结合了Value-based (比如 Q learning) 和 Policy-based (比如 Policy Gradients) 的强化学习算法。

    Actor目的是学习策略函数π(θ)以得到尽量高的回报。 Critic目的是对当前策略的值函数进行估计,来评价。

    • Policy Gradients

    Policy Gradient算法的整个过程可以看作先通过策略π(θ)让agent与环境进行互动,计算每一步所能得到的奖励,并以此得到一局游戏的奖励作为累积奖励G,然后通过调整策略π,使得G最大化。所以使用了梯度提升的方法来更新网络参数θ,利用更新后的策略再采集数据,再更新,如此循环,达到优化策略的目的。

    • Actor Critic

    agent在于环境互动过程中产生的G值本身是一个随机变量,可以通过Q函数去估计G的期望值,来增加稳定性。即Actor-Critic算法在PG策略的更新过程中使用Q函数来代替了G,同时构建了Critic网络来计算Q函数,此时Actor相关参数的梯度为:

    而Critic的损失函数使用Q估计和Q实际值差的平方损失来表示:

    • A2C算法

    A2C在AC算法的基础上使用状态价值函数给Q值增加了基线V,使反馈可以为正或者为负,因此Actor的策略梯变为:

    同时Critic网络的损失函数使用实际状态价值和估计状态价值的平方损失来表示:

    LunarLander-v2游戏环境简介

    LunarLander-v2,是基于gym和box2d提供的游戏环境。游戏任务为玩家通过操作登月器的喷气主引擎和副引擎来控制登月器降落。

    gym:开源强化学习python库,提供了算法和环境交互的标准API,以及符合该API的标准环境集。

    box2d:gym提供的一种环境集合

    注意事项

    1. 本案例运行环境为 TensorFlow-1.13.1,且需使用 GPU 运行,请查看《ModelAtrs JupyterLab 硬件规格使用指南》了解切换硬件规格的方法;
    2. 如果您是第一次使用 JupyterLab,请查看《ModelAtrs JupyterLab使用指导》了解使用方法;
    3. 如果您在使用 JupyterLab 过程中碰到报错,请参考《ModelAtrs JupyterLab常见问题解决办法》尝试解决问题。

    实验步骤

    1. 程序初始化

    第1步:安装基础依赖

    要确保所有依赖都安装成功后,再执行之后的代码。如果某些模块因为网络原因导致安装失败,直接重试一次即可。

    1. !pip install gym
    2. !conda install swig -y
    3. !pip install box2d-py
    4. !pip install gym[box2d]

    第2步:导入相关的库

    1. import os
    2. import gym
    3. import numpy as np
    4. import tensorflow as tf
    5. import pandas as pd

    2. 参数设置¶

    本案例设置的 游戏最大局数 MAX_EPISODE = 100,保存模型的局数 SAVE_EPISODES = 20,以便快速跑通代码。

    你也可以调大 MAX_EPISODE 和 SAVE_EPISODES 的值,如1000和100,可以达到较好的训练效果,训练耗时约20分钟。

    1. MAX_EPISODE = 100 # 游戏最大局数
    2. DISPLAY_REWARD_THRESHOLD = 100 # 开启可视化的reward阈值
    3. SAVE_REWARD_THRESHOLD = 100 # 保存模型的reward阈值
    4. MAX_EP_STEPS = 2000 # 每局最大步长
    5. TEST_EPISODE = 10 # 测试局
    6. RENDER = False # 是否启用可视化(耗时)
    7. GAMMA = 0.9 # TD error中reward衰减系数
    8. RUNNING_REWARD_DECAY=0.95 # running reward 衰减系数
    9. LR_A = 0.001 # Actor网络的学习率
    10. LR_C = 0.01 # Critic网络学习率
    11. NUM_UNITS = 20 # FC层神经元个数
    12. SEED = 1 # 种子数,减小随机性
    13. SAVE_EPISODES = 20 # 保存模型的局数
    14. model_dir = './models' # 模型保存路径

    3. 游戏环境创建

    1. def create_env():
    2. env = gym.make('LunarLander-v2')
    3. # 减少随机性
    4. env.seed(SEED)
    5. env = env.unwrapped
    6. num_features = env.observation_space.shape[0]
    7. num_actions = env.action_space.n
    8. return env, num_features, num_actions

    4. Actor-Critic网络构建

    1. class Actor:
    2. """
    3. Actor网络
    4. Parameters
    5. ----------
    6. sess : tensorflow.Session()
    7. n_features : int
    8. 特征维度
    9. n_actions : int
    10. 动作空间大小
    11. lr : float
    12. 学习率大小
    13. """
    14. def __init__(self, sess, n_features, n_actions, lr=0.001):
    15. self.sess = sess
    16. # 状态空间
    17. self.s = tf.placeholder(tf.float32, [1, n_features], "state")
    18. # 动作空间
    19. self.a = tf.placeholder(tf.int32, None, "action")
    20. # TD_error
    21. self.td_error = tf.placeholder(tf.float32, None, "td_error")
    22. # actor网络为两层全连接层,输出为动作概率
    23. with tf.variable_scope('Actor'):
    24. l1 = tf.layers.dense(
    25. inputs=self.s,
    26. units=NUM_UNITS,
    27. activation=tf.nn.relu,
    28. kernel_initializer=tf.random_normal_initializer(0., .1),
    29. bias_initializer=tf.constant_initializer(0.1),
    30. name='l1'
    31. )
    32. self.acts_prob = tf.layers.dense(
    33. inputs=l1,
    34. units=n_actions,
    35. activation=tf.nn.softmax,
    36. kernel_initializer=tf.random_normal_initializer(0., .1),
    37. bias_initializer=tf.constant_initializer(0.1),
    38. name='acts_prob'
    39. )
    40. with tf.variable_scope('exp_v'):
    41. log_prob = tf.log(self.acts_prob[0, self.a])
    42. # 损失函数
    43. self.exp_v = tf.reduce_mean(log_prob * self.td_error)
    44. with tf.variable_scope('train'):
    45. # minimize(-exp_v) = maximize(exp_v)
    46. self.train_op = tf.train.AdamOptimizer(lr).minimize(-self.exp_v)
    47. def learn(self, s, a, td):
    48. s = s[np.newaxis, :]
    49. feed_dict = {self.s: s, self.a: a, self.td_error: td}
    50. _, exp_v = self.sess.run([self.train_op, self.exp_v], feed_dict)
    51. return exp_v
    52. # 生成动作
    53. def choose_action(self, s):
    54. s = s[np.newaxis, :]
    55. probs = self.sess.run(self.acts_prob, {self.s: s})
    56. return np.random.choice(np.arange(probs.shape[1]), p=probs.ravel())
    57. class Critic:
    58. """
    59. Critic网络
    60. Parameters
    61. ----------
    62. sess : tensorflow.Session()
    63. n_features : int
    64. 特征维度
    65. lr : float
    66. 学习率大小
    67. """
    68. def __init__(self, sess, n_features, lr=0.01):
    69. self.sess = sess
    70. # 状态空间
    71. self.s = tf.placeholder(tf.float32, [1, n_features], "state")
    72. # value
    73. self.v_ = tf.placeholder(tf.float32, [1, 1], "v_next")
    74. # 奖励
    75. self.r = tf.placeholder(tf.float32, None, 'r')
    76. # critic网络为两层全连接层,输出为value
    77. with tf.variable_scope('Critic'):
    78. l1 = tf.layers.dense(
    79. inputs=self.s,
    80. # number of hidden units
    81. units=NUM_UNITS,
    82. activation=tf.nn.relu,
    83. kernel_initializer=tf.random_normal_initializer(0., .1),
    84. bias_initializer=tf.constant_initializer(0.1),
    85. name='l1'
    86. )
    87. self.v = tf.layers.dense(
    88. inputs=l1,
    89. # output units
    90. units=1,
    91. activation=None,
    92. kernel_initializer=tf.random_normal_initializer(0., .1),
    93. bias_initializer=tf.constant_initializer(0.1),
    94. name='V'
    95. )
    96. with tf.variable_scope('squared_TD_error'):
    97. self.td_error = self.r + GAMMA * self.v_ - self.v
    98. # TD_error = (r+gamma*V_next) - V_eval
    99. self.loss = tf.square(self.td_error)
    100. with tf.variable_scope('train'):
    101. self.train_op = tf.train.AdamOptimizer(lr).minimize(self.loss)
    102. def learn(self, s, r, s_):
    103. s, s_ = s[np.newaxis, :], s_[np.newaxis, :]
    104. v_ = self.sess.run(self.v, {self.s: s_})
    105. td_error, _ = self.sess.run([self.td_error, self.train_op],
    106. {self.s: s, self.v_: v_, self.r: r})
    107. return td_error

    5. 创建训练函数

    1. def model_train():
    2. env, num_features, num_actions = create_env()
    3. render = RENDER
    4. sess = tf.Session()
    5. actor = Actor(sess, n_features=num_features, n_actions=num_actions, lr=LR_A)
    6. critic = Critic(sess, n_features=num_features, lr=LR_C)
    7. sess.run(tf.global_variables_initializer())
    8. saver = tf.train.Saver()
    9. for i_episode in range(MAX_EPISODE+1):
    10. cur_state = env.reset()
    11. cur_step = 0
    12. track_r = []
    13. while True:
    14. # notebook暂不支持该游戏的可视化
    15. # if RENDER:
    16. # env.render()
    17. action = actor.choose_action(cur_state)
    18. next_state, reward, done, info = env.step(action)
    19. track_r.append(reward)
    20. # gradient = grad[reward + gamma * V(next_state) - V(cur_state)]
    21. td_error = critic.learn(cur_state, reward,
    22. next_state)
    23. # true_gradient = grad[logPi(cur_state,action) * td_error]
    24. actor.learn(cur_state, action, td_error)
    25. cur_state = next_state
    26. cur_step += 1
    27. if done or cur_step >= MAX_EP_STEPS:
    28. ep_rs_sum = sum(track_r)
    29. if 'running_reward' not in locals():
    30. running_reward = ep_rs_sum
    31. else:
    32. running_reward = running_reward * RUNNING_REWARD_DECAY + ep_rs_sum * (1-RUNNING_REWARD_DECAY)
    33. # 判断是否达到可视化阈值
    34. # if running_reward > DISPLAY_REWARD_THRESHOLD:
    35. # render = True
    36. print("episode:", i_episode, " reward:", int(running_reward), " steps:", cur_step)
    37. break
    38. if i_episode > 0 and i_episode % SAVE_EPISODES == 0:
    39. if not os.path.exists(model_dir):
    40. os.mkdir(model_dir)
    41. ckpt_path = os.path.join(model_dir, '{}_model.ckpt'.format(i_episode))
    42. saver.save(sess, ckpt_path)

    6. 开始训练

    训练一个episode大约需1.2秒

    1. print('MAX_EPISODE:', MAX_EPISODE)
    2. model_train()
    3. # reset graph
    4. tf.reset_default_graph()

    7.使用模型推理

    由于本游戏内核可视化依赖于OpenGL,需要桌面化操作系统的窗口显示,但当前环境暂不支持弹窗,因此无法可视化,您可将代码下载到本地,取消 env.render() 这行代码的注释,查看可视化效果。

    1. def model_test():
    2. env, num_features, num_actions = create_env()
    3. sess = tf.Session()
    4. actor = Actor(sess, n_features=num_features, n_actions=num_actions, lr=LR_A)
    5. sess.run(tf.global_variables_initializer())
    6. saver = tf.train.Saver()
    7. saver.restore(sess, tf.train.latest_checkpoint(model_dir))
    8. for i_episode in range(TEST_EPISODE):
    9. cur_state = env.reset()
    10. cur_step = 0
    11. track_r = []
    12. while True:
    13. # 可视化
    14. # env.render()
    15. action = actor.choose_action(cur_state)
    16. next_state, reward, done, info = env.step(action)
    17. track_r.append(reward)
    18. cur_state = next_state
    19. cur_step += 1
    20. if done or cur_step >= MAX_EP_STEPS:
    21. ep_rs_sum = sum(track_r)
    22. print("episode:", i_episode, " reward:", int(ep_rs_sum), " steps:", cur_step)
    23. break
    24. model_test()
    25. episode: 0 reward: -31 steps: 196
    26. episode: 1 reward: -99 steps: 308
    27. episode: 2 reward: -273 steps: 533
    28. episode: 3 reward: -5 steps: 232
    29. episode: 4 reward: -178 steps: 353
    30. episode: 5 reward: -174 steps: 222
    31. episode: 6 reward: -309 steps: 377
    32. episode: 7 reward: 24 steps: 293
    33. episode: 8 reward: -121 steps: 423
    34. episode: 9 reward: -194 steps: 286

    8.可视化效果

    下面的视频为训练1000 episode模型的推理效果,该视频演示了在三个不同的地形情况下,登月器都可以安全着陆

    https://modelarts-labs-bj4-v2.obs.cn-north-4.myhuaweicloud.com/course/modelarts/reinforcement_learning/a2c_lunarlander/A2C_lunarlander.mp4

    点击关注,第一时间了解华为云新鲜技术~

  • 相关阅读:
    Java日志 -slf4j、logback、log4j2、springboot中日志
    一文搞懂RESTful开发
    java基础(面向对象,异常,类,抽象类,继承类,构造方法,接口,string类,==和equals,修饰符final,static,重写和重载)
    Spring Boot+Vue+阿里云OOS实现图片上传
    JavaEE UDP报文结构
    JS如何实现书签导入导出?我是这么做的
    java毕业设计大学生入学审核系统mybatis+源码+调试部署+系统+数据库+lw
    微服务之商城系统
    input 输入框限制只能输入两位小数正数
    NestJS学习:使用session实现登录验证
  • 原文地址:https://blog.csdn.net/devcloud/article/details/128003078