動手實踐丨基於ModelAtrs使用A2C演算法制作登月器著陸小遊戲

華為雲開發者聯盟發表於2022-11-23
摘要:在本案例中,我們將展示如何基於A2C演算法,訓練一個LunarLander小遊戲。

本文分享自華為雲社群《使用A2C演算法控制登月器著陸》,作者:HWCloudAI 。

LunarLander是一款控制類的小遊戲,也是強化學習中常用的例子。遊戲任務為控制登月器著陸,玩家透過操作登月器的主引擎和副引擎,控制登月器降落。登月器平穩著陸會得到相應的獎勵積分,如果精準降落在著陸平臺上會有額外的獎勵積分;相反地如果登月器墜毀會扣除積分。

A2C全稱為Advantage Actor-Critic,在本案例中,我們將展示如何基於A2C演算法,訓練一個LunarLander小遊戲。

整體流程:基於gym建立LunarLander環境->構建A2C演算法->訓練->推理->視覺化效果

動手實踐丨基於ModelAtrs使用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相關引數的梯度為:

動手實踐丨基於ModelAtrs使用A2C演算法制作登月器著陸小遊戲

而Critic的損失函式使用Q估計和Q實際值差的平方損失來表示:

動手實踐丨基於ModelAtrs使用A2C演算法制作登月器著陸小遊戲
  • A2C演算法

A2C在AC演算法的基礎上使用狀態價值函式給Q值增加了基線V,使反饋可以為正或者為負,因此Actor的策略梯變為:

動手實踐丨基於ModelAtrs使用A2C演算法制作登月器著陸小遊戲

同時Critic網路的損失函式使用實際狀態價值和估計狀態價值的平方損失來表示:

動手實踐丨基於ModelAtrs使用A2C演算法制作登月器著陸小遊戲

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步:安裝基礎依賴

要確保所有依賴都安裝成功後,再執行之後的程式碼。如果某些模組因為網路原因導致安裝失敗,直接重試一次即可。

!pip install gym
!conda install swig -y
!pip install box2d-py
!pip install gym[box2d]

第2步:匯入相關的庫

import os
import gym
import numpy as np
import tensorflow as tf
import pandas as pd

2. 引數設定

本案例設定的 遊戲最大局數 MAX_EPISODE = 100,儲存模型的局數 SAVE_EPISODES = 20,以便快速跑通程式碼。

你也可以調大 MAX_EPISODE 和 SAVE_EPISODES 的值,如1000和100,可以達到較好的訓練效果,訓練耗時約20分鐘。

MAX_EPISODE = 100 # 遊戲最大局數
DISPLAY_REWARD_THRESHOLD = 100 # 開啟視覺化的reward閾值
SAVE_REWARD_THRESHOLD = 100 # 儲存模型的reward閾值
MAX_EP_STEPS = 2000 # 每局最大步長
TEST_EPISODE = 10 # 測試局
RENDER = False # 是否啟用視覺化(耗時)
GAMMA = 0.9 # TD error中reward衰減係數
RUNNING_REWARD_DECAY=0.95 # running reward 衰減係數
LR_A = 0.001 # Actor網路的學習率
LR_C = 0.01 # Critic網路學習率
NUM_UNITS = 20 # FC層神經元個數
SEED = 1 # 種子數,減小隨機性
SAVE_EPISODES = 20 # 儲存模型的局數
model_dir = './models' # 模型儲存路徑

3. 遊戲環境建立

def create_env():
    env = gym.make('LunarLander-v2')
 # 減少隨機性
 env.seed(SEED)
    env = env.unwrapped
 num_features = env.observation_space.shape[0]
 num_actions = env.action_space.n
 return env, num_features, num_actions

4. Actor-Critic網路構建

class Actor:
 """
    Actor網路
    Parameters
    ----------
 sess : tensorflow.Session()
 n_features : int
 特徵維度
 n_actions : int
 動作空間大小
 lr : float
 學習率大小
    """
 def __init__(self, sess, n_features, n_actions, lr=0.001):
 self.sess = sess
 # 狀態空間
 self.s = tf.placeholder(tf.float32, [1, n_features], "state")
 # 動作空間
 self.a = tf.placeholder(tf.int32, None, "action")
 # TD_error
 self.td_error = tf.placeholder(tf.float32, None, "td_error")
 # actor網路為兩層全連線層,輸出為動作機率
 with tf.variable_scope('Actor'):
            l1 = tf.layers.dense(
                inputs=self.s,
                units=NUM_UNITS,
                activation=tf.nn.relu,
 kernel_initializer=tf.random_normal_initializer(0., .1),
 bias_initializer=tf.constant_initializer(0.1),
                name='l1'
 )
 self.acts_prob = tf.layers.dense(
                inputs=l1,
                units=n_actions,
                activation=tf.nn.softmax,
 kernel_initializer=tf.random_normal_initializer(0., .1),
 bias_initializer=tf.constant_initializer(0.1),
                name='acts_prob'
 )
 with tf.variable_scope('exp_v'):
 log_prob = tf.log(self.acts_prob[0, self.a])
 # 損失函式
 self.exp_v = tf.reduce_mean(log_prob * self.td_error)
 with tf.variable_scope('train'):
 # minimize(-exp_v) = maximize(exp_v)
 self.train_op = tf.train.AdamOptimizer(lr).minimize(-self.exp_v)
 def learn(self, s, a, td):
        s = s[np.newaxis, :]
 feed_dict = {self.s: s, self.a: a, self.td_error: td}
        _, exp_v = self.sess.run([self.train_op, self.exp_v], feed_dict)
 return exp_v
 # 生成動作
 def choose_action(self, s):
        s = s[np.newaxis, :]
        probs = self.sess.run(self.acts_prob, {self.s: s}) 
 return np.random.choice(np.arange(probs.shape[1]), p=probs.ravel())
class Critic:
 """
    Critic網路
    Parameters
    ----------
 sess : tensorflow.Session()
 n_features : int
 特徵維度
 lr : float
 學習率大小
    """
 def __init__(self, sess, n_features, lr=0.01):
 self.sess = sess
 # 狀態空間
 self.s = tf.placeholder(tf.float32, [1, n_features], "state")
 # value值 
 self.v_ = tf.placeholder(tf.float32, [1, 1], "v_next")
 # 獎勵 
 self.r = tf.placeholder(tf.float32, None, 'r')
 # critic網路為兩層全連線層,輸出為value值
 with tf.variable_scope('Critic'):
            l1 = tf.layers.dense(
                inputs=self.s,
 # number of hidden units
                units=NUM_UNITS,
                activation=tf.nn.relu, 
 kernel_initializer=tf.random_normal_initializer(0., .1), 
 bias_initializer=tf.constant_initializer(0.1), 
                name='l1'
 )
 self.v = tf.layers.dense(
                inputs=l1,
 # output units
                units=1,
                activation=None,
 kernel_initializer=tf.random_normal_initializer(0., .1), 
 bias_initializer=tf.constant_initializer(0.1), 
                name='V'
 )
 with tf.variable_scope('squared_TD_error'):
 self.td_error = self.r + GAMMA * self.v_ - self.v
 # TD_error = (r+gamma*V_next) - V_eval
 self.loss = tf.square(self.td_error)
 with tf.variable_scope('train'):
 self.train_op = tf.train.AdamOptimizer(lr).minimize(self.loss)
 def learn(self, s, r, s_):
        s, s_ = s[np.newaxis, :], s_[np.newaxis, :]
        v_ = self.sess.run(self.v, {self.s: s_})
 td_error, _ = self.sess.run([self.td_error, self.train_op],
 {self.s: s, self.v_: v_, self.r: r})
 return td_error

5. 建立訓練函式

def model_train():
    env, num_features, num_actions = create_env()
    render = RENDER
 sess = tf.Session()
    actor = Actor(sess, n_features=num_features, n_actions=num_actions, lr=LR_A)
    critic = Critic(sess, n_features=num_features, lr=LR_C)
 sess.run(tf.global_variables_initializer())
    saver = tf.train.Saver()
 for i_episode in range(MAX_EPISODE+1):
 cur_state = env.reset()
 cur_step = 0
 track_r = []
 while True:
 # notebook暫不支援該遊戲的視覺化
 # if RENDER:
 # env.render()
            action = actor.choose_action(cur_state)
 next_state, reward, done, info = env.step(action)
 track_r.append(reward)
 # gradient = grad[reward + gamma * V(next_state) - V(cur_state)]
 td_error = critic.learn(cur_state, reward,
 next_state)
 # true_gradient = grad[logPi(cur_state,action) * td_error]
 actor.learn(cur_state, action, td_error) 
 cur_state = next_state
 cur_step += 1
 if done or cur_step >= MAX_EP_STEPS:
 ep_rs_sum = sum(track_r)
 if 'running_reward' not in locals():
 running_reward = ep_rs_sum
 else:
 running_reward = running_reward * RUNNING_REWARD_DECAY + ep_rs_sum * (1-RUNNING_REWARD_DECAY)
 # 判斷是否達到視覺化閾值
 # if running_reward > DISPLAY_REWARD_THRESHOLD:
 #     render = True
 print("episode:", i_episode, "  reward:", int(running_reward), "  steps:", cur_step)
 break
 if i_episode > 0 and i_episode % SAVE_EPISODES == 0:
 if not os.path.exists(model_dir):
 os.mkdir(model_dir)
 ckpt_path = os.path.join(model_dir, '{}_model.ckpt'.format(i_episode))
 saver.save(sess, ckpt_path)

6. 開始訓練

訓練一個episode大約需1.2秒

print('MAX_EPISODE:', MAX_EPISODE)
model_train()
# reset graph
tf.reset_default_graph()

7.使用模型推理

由於本遊戲核心視覺化依賴於OpenGL,需要桌面化作業系統的視窗顯示,但當前環境暫不支援彈窗,因此無法視覺化,您可將程式碼下載到本地,取消 env.render() 這行程式碼的註釋,檢視視覺化效果。

def model_test():
    env, num_features, num_actions = create_env()
 sess = tf.Session()
    actor = Actor(sess, n_features=num_features, n_actions=num_actions, lr=LR_A)
 sess.run(tf.global_variables_initializer())
    saver = tf.train.Saver()
 saver.restore(sess, tf.train.latest_checkpoint(model_dir))
 for i_episode in range(TEST_EPISODE):
 cur_state = env.reset()
 cur_step = 0
 track_r = []
 while True:
 # 視覺化
 # env.render()
            action = actor.choose_action(cur_state)
 next_state, reward, done, info = env.step(action)
 track_r.append(reward)
 cur_state = next_state
 cur_step += 1
 if done or cur_step >= MAX_EP_STEPS:
 ep_rs_sum = sum(track_r)
 print("episode:", i_episode, "  reward:", int(ep_rs_sum), "  steps:", cur_step)
 break
model_test()
episode: 0   reward: -31   steps: 196
episode: 1   reward: -99   steps: 308
episode: 2   reward: -273   steps: 533
episode: 3   reward: -5   steps: 232
episode: 4   reward: -178   steps: 353
episode: 5   reward: -174   steps: 222
episode: 6   reward: -309   steps: 377
episode: 7   reward: 24   steps: 293
episode: 8   reward: -121   steps: 423
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

 

點選關注,第一時間瞭解華為雲新鮮技術~

相關文章