Actor-Critic算法实现“MountainCar-v0”游戏

在这一节将使用 Actor-Critic 算法解决 Gym 的过山车(MountainCar)问题。如图 1 的左图所示,小车位于两座山之间,其目标是抵达右侧山峰插着黄色小旗的位置。小车的动力无法让其一次性到达目标位置,需要通过来回行驶增加动力。
“MountainCar-v0”游戏示意图
图 1:“MountainCar-v0”游戏示意图

游戏的环境信息如图 的右 1 侧表格所示,主要由小车的位置信息和速度信息两部分组成,黄色小旗在 0.5 的位置。在这个游戏中,我们的智能体(即小车)可以执行三个动作:施加一个向左的力、不施加力,以及施加一个向右的力,可以通过数值“0、1 和 2”来控制这三个力。

首先导入需要的包:
import gym
import numpy as np
import random

接着实现策略函数部分:
def policy_function(observation, theta):
    weight = np.dot(theta, observation)
    #采用ε贪心搜索对环境进行探索
    rand_num = random.random()
    epsilon = 0.8
    if rand_num > epsilon:
        #随机选择一个动作
        s = 0
        action = random.randint(0, 2)
    else :
        #策略函数:y= [e^x-e^(-x) ] / [e^x +e^(-x)]
        s = (np.exp(weight) - np.exp(-weight)) / (np.exp(-weight)+ np.exp(-weight))
        if s < -0.3:
            action = 0 #施加一个向左的力
        elif s > 0.3:
            action = 1   #不施加力
        else :
            action = 2    #施加一个向右的力
    return s, action

这里使用函数 y= [ex - e-x ] / [ex + e-x] 作为策略函数,第 2 行代码将参数 θ 与环境参数的点积作为策略函数的参数。我们使用了 ε 贪心搜索对环境进行探索,以 0.8 的概率根据当前的策略来选取动作,以 0.2 的概率随机选取动作。在第 13 行至第 18 行代码中,由于策略函数的值域为 (-1, 1),所以将其划分为三块,当“s”的值落在不同部分时执行不同的动作。

接下来实现 Actor 部分:
def actor(env, observation, theta, pre_phi, phi, df_gamma, df_lambda):
    #学习率
    alpha = 0.001
    while True:
        #根据当前策略选择动作
        s, action = policy_function(observation, theta)
        pre_observation = observation
        #执行选择的动作并得到反馈信息(新的环境状态、奖励等)
        observation, reward, done, info = env.step(action)
        #可视化游戏画面(重绘一帧画面)
        env.render()
        delta, pre_phi, phi = critic(pre_phi, phi, pre_observation, observation, reward, df_gamma, df_lambda)
        #更新策略函数的参数theta
        theta += alpha * df_lambda * delta * (1 - s * s) * (-pre_ observation)
        df_lambda *= df_gamma
        #游戏结束后重置环境
        if done:
            observation = env.reset()

Actor 和 Critic 部分的代码完全依照 Actor-Critic 算法实现。第 6 行代码根据策略选取待执行的动作,第 9 行代码通过执行动作获取环境的反馈信息。第 12 行代码调用 critic 函数,这里返回的 delta 值反映了当前动作的好坏,我们用该值来优化策略函数。第 14 行代码中的“(1- s * s) * (-pre_observation)”是策略函数 πθ(s, a) 对 θ 求导的结果。

def critic(pre_phi, phi, pre_observation, observation, reward, df_gamma, df_lambda):
    #学习率
    beta = 0.001
    #计算当前状态的价值
    v = np.dot(phi, observation)
    v = 1 / (1 + np.exp(-v))
    #计算上一状态的价值
    pre_v = np.dot(pre_phi, pre_observation)
    pre_v = 1 / (1 + np.exp(-pre_v))
    delta = reward + df_gamma * v - pre_v
    pre_phi = phi
    #更新价值函数的参数 phi
    phi += beta * df_lambda * delta * pre_v * (1 - pre_v) * (-pre_observation)
    return delta, pre_phi, phi
第 5 行至第 9 行代码计算了当前状态的价值和执行动作 a 之前的状态的价值。以 1/(1+e-x) 作为状态价值函数。第 10 行代码用 Qπ(s, a)-Vπ(s) 来评判动作 a 的好坏,并用其来优化价值函数。第 13 行代码中的“pre_v * (1- pre_v) * (-pre_observation)”是价值函数对参数 φ 的导数。

接着定义一个 actor_critic 函数:
def actor_critic(env):
    observation = env.reset()
    #随机初始化策略函数和状态价值函数的参数
    theta = np.random.rand(2)
    phi = np.random.rand(2)
    pre_phi = phi
    #折扣因子
    df_gamma = 0.9
    df_lambda = 1
    actor(env, observation, theta, pre_phi, phi, df_gamma, df_lambda)
第 4 行和第 5 行代码随机初始化了策略函数和状态价值函数的参数,因为游戏的环境由两个因素构成,因此这里参数定义为长度为 2 的数组。

最后让智能体开始学习:
if  __name __== "__main__":
    #注册游戏环境MountainCar-v0
    game_env = gym.make('MountainCar-v0')
    #取消限制
    game_env = game_env.unwrapped
    #开始学习玩"MountainCar-v0”游戏
    actor_critic(game_env)