# 创建自定义环境


创建自定义环境

此文档将展示如何创建一个自定义的强化学习环境,用以测试和训练强化学习算法以解决各种问题。强化学习工具箱的环境以 gym 为框架,因此在使用前可以对 gym 进行一定的学习从而更好地理解。

在创建一个强化学习环境时,通常需要实现以下几个核心功能。

  • Rset:这个函数用于将环境重置为初始状态,并返回一个初始状态。这个函数通常在每个回合开始时调用;
  • Step:这是环境的主要函数,用于执行智能体采取的动作并返回与动作相关的信息。它接受一个动作作为输入,并返回环境的新状态、奖励、是否终止等信息;
  • CloseEnv:关闭环境;
  • 状态/动作空间信息:这些信息用于定义状态空间和动作空间的特性。

# 示例

使用 Julia 构建 Car2D 环境

创建一个连续状态空间、离散动作空间的环境 Car2D,环境的说明如下:

  • 描述:一个在二维平面上移动的小车,state 代表点当前坐标 [x,y],小车每次随机向上下左右移动一个单位,目标是到达原点附近;
  • 状态:[x,y],小车坐标。初始时小车在横坐标 -10~10,纵坐标 -10~10 范围内的随机位置(小车坐标虽然是离散取值,但此处仍将其视作连续输入);
  • 动作:[1,2,3,4],分别代表向上、下、左、右移动一个单位;
  • 奖励:小车到达目的地(横纵坐标绝对值之和小于等于 1)获得 +10 奖励,每移动一次获得 -0.1 奖励;
  • 结束:当 x 和 y 的绝对值之和小于等于 1 或者步数超过 200 后结束。

首先要创建环境文件“Car2D.jl”,以下是完整的文件代码,可以直接复制使用。

import TyReinforcementLearning: Reset, Step, ActionDims, ActionSize, StateSize ,CloseEnv
using TyReinforcementLearning

Base.@kwdef mutable struct Car2D <: CustomEnv
    state::Vector{Float64} = [0, 0]
    steps::Int64 = 0
    stateInfo = Dict("frame" => "CustomEnv", "envtype" => "continuous", "size" => (2,), "OriginalLowerLimit" => [-Inf, -Inf], "OriginalUpperLimit" => [Inf, Inf], "dtype" => "float")
    actionInfo = Dict("frame" => "CustomEnv", "envtype" => "discrete", "start" => 1, "number" => 4, "dtype" => "int")
end

function Reset(env::Car2D)
    x = 20 * rand() - 10
    y = 20 * rand() - 10
    env.state = [x, y]
    env.steps = 0
    return env.state, nothing
end

function Step(env::Car2D, action::Int64)
    env.steps += 1
    x, y = env.state
    if action == 1
        y += 1
    elseif action == 2
        y -= 1
    elseif action == 3
        x -= 1
    elseif action == 4
        x += 1
    else
        error("Invalid action")
    end
    env.state = [x, y]
    if env.steps == 1000
        done = true
    else
        done = false
    end
    if sum(abs.(env.state)) <= 1
        reward = 10
        done = true
    else
        reward = -0.1
    end
    return env.state, reward, done, nothing, nothing
end

function ActionDims(env::Car2D)
    return 4
end

function ActionSize(env::Car2D)
    return (1,)
end

function StateSize(env::Car2D)
    return (2,)
end

function CloseEnv(env::Car2D)
    return nothing
end

以下对代码各个部分进行解释。

定义环境结构体,其中 state 为环境当前状态,steps 为环境自上次Reset以来执行的步数。

Base.@kwdef mutable struct Car2D
    state::Vector{Float64} = [0, 0]
    steps::Int64 = 0
end

定义环境重置函数,输入为自定义环境对象。这里一定要对输入的变量进行类型声明,防止与工具箱内部的函数冲突。输出环境初始状态、辅助信息。其中辅助信息不起实际用途,可定义为任意值。

function Reset(env::Car2D)
    x = 20 * rand() - 10
    y = 20 * rand() - 10
    env.state = [x, y]
    env.steps = 0
    return env.state, nothing
end

定义环境执行函数,输入变量为自定义环境对象,动作。这里一定要对输入的变量进行类型声明,防止与工具箱内部的函数冲突。函数输出环境新状态、奖励、是否结束、是否截断、辅助信息。其中后两个变量不起实际用途,可定义为任意值。

function Step(env::Car2D, action::Int64)
    env.steps += 1
    x, y = env.state
    if action == 1
        y += 1
    elseif action == 2
        y -= 1
    elseif action == 3
        x -= 1
    elseif action == 4
        x += 1
    else
        error("Invalid action")
    end
    env.state = [x, y]
    if env.steps == 1000
        done = true
    else
        done = false
    end
    if sum(abs.(env.state)) <= 1
        reward = 10
        done = true
    else
        reward = -0.1
    end
    return env.state, reward, done, nothing, nothing
end

定义动作空间信息。环境动作空间为离散,因此要定义ActionDims函数(如果是连续的,则要定义ActionRange函数),共有 [1,2,3,4] 四个动作,因此返回的动作数目为 4。在 Julia 中,环境动作应当设定以 1 为开始的连续正整数。

function ActionDims(env::Car2D)
    return 4
end

定义状态空间信息。环境状态空间为连续,因此要定义StateSize函数(当状态空间为离散时,推荐将其转化为连续输入进行处理)。状态向量长度为 2,因此返回的形状为(2,)。

function StateSize(env::Car2D)
    return (2,)
end

在创建了环境文件“Car2D.jl”之后,便可使用其对合适的智能体进行训练。

首先重启命令行,在包加载之后使用 import 包名: 函数名 的方式对包内函数进行扩展。然后是导入环境,此处 include 的是环境文件,可以自行替换为本地文件的保存路径。此时便完整实现了环境的导入。

下面给出一个示例来对环境进行使用。

using Flux
using TyReinforcementLearning
include("Car2D.jl")

env = Car2D()
critic_net = BuildDefaultNet(64, StateSize(env)[1], ActionDims(env))

models = rlDQNModels(
    criticNet=critic_net,
    criticOptimizer=Flux.Adam(),
)

option = rlDQNAgentOptions(
    stateSize=StateSize(env),
    actionNum=ActionDims(env)
)

agent = rlDQNAgent(models,option)

train_options = rlTrainOptions(
    max_episodes=1000,
    learning_interval=10,
    path=joinpath(pwd(), "result")
)

result = train!(agent, env, train_options)
plot_result(result)
使用 Julia 构建 CarContinuous 环境

创建一个连续状态空间、连续动作空间的环境 CarContinuous,环境的说明如下:

  • 描述:一个在一维数轴上移动的小车,state 代表点当前坐标 [x],小车每次左右移动一定距离,目标是到达原点附近;
  • 状态:[x],小车坐标。初始时小车在 -20~-10 与 10~20 的随机位置;
  • 动作:[deta_x],小车移动的距离,取值范围为 [-2,2],正数代表向右移动,负数代表向左移动;
  • 奖励:小车到达目的地(坐标绝对值小于 0.1)获得 +20 奖励,每移动一次获得 -0.1*abs(x)奖励;
  • 结束:当 x 的绝对值小于 0.1 或者步数超过 200 后结束。

首先要创建环境文件“CarContinuous.jl”,以下是完整的文件代码,可以直接复制使用。

import TyReinforcementLearning: Reset, Step, ActionRange, ActionSize, StateSize, CloseEnv
using TyReinforcementLearning

Base.@kwdef mutable struct CarContinuous <: CustomEnv
    state::Vector{Float64} = [0.0]
    steps::Int64 = 0
    stateInfo = Dict("frame" => "CustomEnv", "envtype" => "continuous", "size" => (1,), "OriginalLowerLimit" => [-Inf], "OriginalUpperLimit" => [Inf], "dtype" => "float")
    actionInfo = Dict("frame" => "CustomEnv", "envtype" => "continuous", "size" => (1,), "OriginalLowerLimit" => [-2], "OriginalUpperLimit" => [2], "dtype" => "float")
end

function Reset(env::CarContinuous)
    env.state = rand([-1,1]) .* [10 * rand() + 10]
    env.steps = 0
    return env.state, nothing
end

function Step(env::CarContinuous, action::AbstractFloat)
    env.steps += 1
    if abs(action) > 2
        error("Invalid action")
    end
    env.state .+= action
    if env.steps == 200
        done = true
    else
        done = false
    end

    if abs(env.state[1]) < 0.1
        reward = 20
        done = true
    else
        reward = -0.1 * abs(env.state[1])
    end
    return env.state, reward, done, nothing, nothing
end

function ActionRange(env::CarContinuous)
    return 2
end

function ActionSize(env::CarContinuous)
    return (1,)
end

function StateSize(env::CarContinuous)
    return (1,)
end

function CloseEnv(env::CarContinuous)
    return nothing
end

环境定义情况与上一示例类似,下面列出几个不同点:

  • 连续动作空间时,应使动作取值范围关于原点对称;
  • 对于连续动作空间,应当对ActionRange函数进行扩展,返回动作取值范围 [-a,a] 的边界 a。

下面给出一个示例来对环境进行使用。

using Flux
using TyReinforcementLearning
include("CarContinuous.jl")

env = CarContinuous()
actor_net = Flux.Chain(
    Flux.Dense(StateSize(env)[1], 16, relu),
    Flux.Dense(16, 1, tanh),
    ScaleLayer(ActionRange(env))
) |> f64
critic_net = Flux.Chain(
    Join(vcat,
        ScaleLayer(1),
        ScaleLayer(1)),
    Flux.Dense(StateSize(env)[1] + 1, 16, relu),
    Flux.Dense(16, 1)
) |> f64
models = rlDDPGModels(
    actorNet=actor_net,
    criticNet=critic_net,
    actorOptimizer=Flux.Adam(0.0001),
    criticOptimizer=Flux.Adam(0.0001)
)
option = rlDDPGAgentOptions(
    stateSize=StateSize(env),
    actionRange=ActionRange(env),
    maxStepsPerEpisode=200,
)
agent = rlDDPGAgent(models, option)
train_options = rlTrainOptions(
    max_episodes=200,
    learning_interval=1,
    batch_size=256,
    path=joinpath(pwd(), "result")
)

result = train!(agent, env, train_options)
plot_result(result)

此外,还可以使用 gym 的自定义环境功能,将 python 代码文件放到本地 gym 文件夹中,并修改注册文件,实现BuildEnv函数通过 id 调用本地自定义环境。

# 另请参阅

BuildEnv| ActionDims| ActionRange| Reset| StateDims| StateSize| Step