Unity 它是世界上最受欢迎的游戏开发引擎之一,使用了大量的游戏开发者Unity开发他们的游戏。在这个AI、大数据等流行词遍布各行各业,Unity也没有被潮流抛弃,推出了自己的基于深度强化学习来训练游戏AI的工具包Unity ML-agents。该工具包功能丰富,非常强大。它可以帮助你在你的游戏中实现一个新的AI算法,并快速使用你的游戏。如此强大的工具包很难在一篇文章中总结其所有功能。本文先抛砖引玉,稍微讨论一下Unity ML-agents各种参数在训练中需要使用的意义,以及常用的值是什么。
参考本文的所有内容Unity ML-agents官方文件(地址:https://github.com/Unity-Technologies/ml-agents/tree/main/docs)
1. 设置训练参数
在你开始训练之前,你需要为你的训练任务设置一个训练参数文件(通常是一个.yaml文件)。
接下来就简单介绍一下ml-agents环境中的参数设置总结。本文主要参考ml-agents关于参数设置的官方文档的最新版本,做了一些一般的翻译,并添加了一定的个人理解。
具体文件地址:https://github.com/Unity-Technologies/ml-agents/blob/main/docs/Training-Configuration-File.md
训练参数主要分为常见训练参数(Common Trainer Configurations), 特殊参数的训练方法(Trainer-specific Configurations),超参数(hyper-parameters) ,奖励信号参数(Reward Signals), 行为克隆(Behavioral Cloning),使用RNN提高智能体记忆能力的参数(Memory-enhanced Agents using Recurrent Neural Networks),以及自我对抗训练参数(Self-Play)这些大模块。这些模块下面有一些小模块,后面会进一步说明,这些模块不需要总是设置。这些模块下面有一些小模块,后面会进一步解释,这些模块不需要总是设置。事实上,除了前三个模块是几乎每个环境训练的必要参数外,其他模块只用于需要使用相应功能的训练任务。接下来,详细说明每个参数的含义。
2. 常用的训练参数模块(Common Trainer Configurations)
-
trainer_type: (default =
ppo
) 该参数决定了使用什么智能体育训练算法,目前只支持Proximal Policy Gradient(PPO,具体应为OpenAI版本的PPO2),Soft Actor-Critic(SAC),以及MA-POCA。前两种只是单智能体育训练算法。 注:改变训练算法后,记得调整相应的参数。对于不同的算法,以下参数往往有不同的应用范围,而不是无缝连接。具体说明如下。 -
summary_freq: (default =
50000
) 这个参数决定了多少步数(step)之后,开始记录我们的训练统计数据。 -
: (default =
64
) 这个参数决定了在多少步数之后,将收集到的经验数据放入经验池(experience buffer)。这个数量也决定了用多少步采样来训练当前动作的预期奖励。简单来说,这个值越大,就越接近一局。(episode)游戏的真实回报使偏差更小。但是由于要进行一局游戏才能更新一个动作的奖励预期,这个过程相当的长,并且每局游戏可能情况变化很大。在不同的游戏中,做同样的动作最终可能会有很大的不同(因为这个动作可能对游戏没有太大的影响),从而导致更大的方差。另一方面,当你采样的步数太小时,最终的奖励预测可能会有很大的偏差,但可能会带来很小的方差。事实上,这也需要在方差和偏差之间取得平衡,就像机器学习中经典的简单模型复杂模型(过拟合欠拟合)一样。。 请注意,该参数决定了采样步数和batch_size、 buffer_size、 epoch等待参数也有联系。后面提到这些参数时,会说明关系。 -
max_steps: (default =
500000
) 这个参数决定了训练任务的总步数。如果你有多个相同动作的智能主体,每个步数都包含在这个总步数中;同样,如果你在多个服务器上平行运行多个环境,则考虑所有环境中智能主体的总步数。所以,当你平行训练时agent算法时,一定要把这个值设置得更大。 -
keep_checkpoints: (default =
5
) 这个参数决定了培训期间保留了多少次。checkpoint,其中每一个checkpoint都是在checkpoint_interval步数后产生。 -
checkpoint_interval: (default =
500000
) 正如前面所说,这个参数决定了每个步数后,您的模型将存储一个节点。 -
init_path: (default=None) 这个参数决定了你的模型是否从某个之前训练好的模型存储点继续训练。需要提供模型的确切位置,比如说,
./models/{run-id}/{behavior_name}
。事实上,在训练中,使用–initialize-from这个CLI参数足以让所有的训练任务从同一存储模型继续训练,以方便不同的训练从不同的模型(需要逐一设置)。 -
threaded:(default =
false
) 开启python多线程功能在训练时实现存储模型,避免I/O花太多时间。
3. 接下来是一些常见的超参数(hyper-parameters)的详细设定
-
hyperparameters → learning_rate: (default =
3e-4
),这个参数决定了梯度下降的学习率。当值过大时,训练会变得不稳定(reward不能稳步上升,经常大幅震荡)。 -
:这个参数决定了每次更新参数时会有多少步骤?(state,action, reward…) 状态元组用于学习。)。 对于连续动作空间的训练任务,该参数应设置在1000以上(因为动作空间需要尽可能多的采样才能获得不同的数据)。如果是离散空间,该值通常设置在几十到几百个(根据您的动作空间的大小)。
-
(default =
10240
for PPO and50000
for SAC) 。 PPO:对于PPO在算法方面,程序必须先装满buffer_size一轮训练将在多步之后开始。然后就像前面说的,每次收集就够了。buffer_size状态元组之后,我们把它放在一起buffer内部状态分成buffer/batch_size个batch,然后每个batch训练更新参数, 重复这个过程epoch(epoch也是超参数)次。。通常更大buffer_size训练结果会更稳定。 SAC:对于SAC算法来说, -
hyperparameters → learning_rate_schedule: (default =
linear
for PPO andconstant
for SAC) 该参数决定了是否使用学习率衰减来稳定训练。。一般来说,学习率越高,训练往往不稳定,学习率越小,训练时间越长。因此,使用学习衰减先用较大的学习率快速的找到一个极值点方向,然后再逐步收缩学习率使得训练稳定在极值附近,而不会来回摆动。
4. 接下来介绍一些常用的网络模型的超参数
-
:(default = 1
28
) 这个参数决定了你的训练网络隐藏层的神经元数量,或者说它的维度。这个数值的大小决定了神经网络对游戏状态的表达能力。简单来说,较大的隐藏层往往对更大的观察空间有着较好的表达能力。所以这个值应该随着观察空间的维度变大而变大。 -
network_settings → num_layers: (default =
2
) 这个参数决定了你的训练网络的层数。这个数字越大,你的模型越深。虽然多层叠加能够提高模型对环境的表达能力。但是模型并不是越深越好,过深的层数往往会带来梯度消失的问题,并且会使得训练速度变缓。建议优先加大hiddent_unitys再加大本参数。 -
network_settings → normalize: (default =
false
) 这个参数决定了模型是否对输入的观察向量进行归一化(normalization)。具体公式大概为 (obs - mean) / std,其中mean和std分别是observation的平均值和标准差。官方建议,对于复杂的连续动作空间任务,使用normalization可能有帮助,但是对于简单的离散任务使用normalization则可能带来负作用。 -
这个参数决定了你的编码器的结构。注意,ppo也好,sac也好,只是一个训练架构,不同的任务你使用ppo也需要不同的编码器。比如说一个卡牌类游戏,你可以把所有的观察向量用普通的全连接层处理,但是如果是一个动作游戏,你的观察可能是一张图片,那么这个时候你最好使用CNN作为你的特征提取编码器。这个参数就是在让你选择使用何种编码器。是一个两层的卷积网络;则是使用这篇文章的实现**;resent则是使用IMPALA RESNET这种架构**,这个网络的特点在于提取pattern能力强,不易梯度消失。则是更适合于卡牌游戏的一种CNN结构,特点是更小的结构但是可以捕捉空洞空间的表示信息。则是一个单层全连接层。 因为卷积核大小的匹配要求,每种编码方式有一个最小输入维度的下限。比如simple最小20 * 20;nature_cnn最小支持36 * 36;resent最小支持15 * 15,match3最小支持5 * 5, 注意:
-
network_settings → contioning_type: (default = hyper) 这个参数决定了是否使用hypernetwork来处理任务目标的信息。hyper网络的参数比较多,当使用hyper网络的时候请减少隐藏网络的层数。
5. Trainer-specific Configurations
接下来我们介绍一下针对不同训练算法的专门参数。
5.1 PPO-specific Configurations (PPO专门参数)
-
这个参数决定了鼓励探索多样化策略的熵正则项的强度(entropy regularization)。简单来说,熵正则化通常是为了丰富我们的策略函数探索不同策略和动作,我们使用一个KL散度去衡量新的策略和老策略的相似性,并通过增加entropy来让动作策略更加随机。换句话说,当beta越大,越鼓励探索。调节这个参数要结合TensorBoard里面的entropy和reward来看。当你的entropy下降的时候,reward仍然没有起色,那就把这个beta增大使得entropy能够再持续一段时间。
-
这个参数决定了策略更新的速度。这个参数就是ppo2论文里面的clip范围,这个范围限制了每一次参数更新偏离范围,以保证ppo能够依靠importance sampling作为一个on policy的方法继续训练。 可以发现当epsilon越大,我们的旧策略参数theta’变动较小。这可以使得训练更加稳定,但是随之而来的就是更慢的收敛速度。
-
lambd: (default = 0.95) 这个参数根据官方文档说明是决定了我们的算法多大程度上依赖预估的奖励值,又多大程度上依赖实际的奖励值。实际上这个laambda指的是GAE方法中的一个超参数lambda。GAE方法是在策略梯度下降中常用的一种Advantage,用于更新控制policy在gradient方向上的更新幅度。GAE方法简单来说就是求TD(0), TD(1) 一直到TD无穷(即蒙特卡洛采样)的一个加权平均值。lambda这个值介于0到1之间,当lambda等于0时就是TD(0)估计,当lambda等于1时就是蒙特卡洛采样。
-
num_epoch:(default = 3) 这个参数决定了每次进行梯度下降更新的次数。详见前文buffersize的部分。
6. Reward Signals
奖励信号可分为两种,一种是外在的(extrinsic)来自于环境的奖励,一种是内在的(intrinsic)来自于内部的奖励(比如好奇心奖励等)。不管是内部还是外部奖励信号,都至少要设定两个参数,一个是信号强度(strength),一个是信号衰减率(gamma)。并且,你需要至少设定一种奖励,不然没办法训练。
6.1 Extrinsic Rewards
-
extrinsic → strength: (default = 1.0) 这个参数决定了模型收到的外部环境奖励信号的强度。
-
extrinsic → gamma:(default = 0.99) 这个参数决定了远期奖励的衰减率。简单来说,加入我们的模型在某一步收到了100的奖励,那么我们前一步的奖励应该是多少呢?如果上一步没有得到其他的奖励,那么上一步我们的收益就应该是gamma * 100 = 99, 同理,再上一步的收益就是gamma^2 * 100, 以此类推。直观的理解,gamma约接近一,那么较后期的收益也能反馈到前期的动作。反之就是动作策略进行学习的时候会更加倚重于短期内的回报。对于那种奖励较为稀疏,必须通过一系列动作之后才能获得一次奖励的任务,务必将这个值设定得更接近于一,反之则可以稍微小一些。
6.2 Intrinsic Reward
-
curiosity → strength: (default = 1.0) 这个参数决定了好奇心奖励的强度。这个比例需要调整到一个刚好的程度,使得好奇心奖励既不会淹没了外部奖励的信号,又不会和外部奖励比起来过于不值一提。
-
curiosity → gamma: (default = 0.99)如前所述,这个参数决定了远期奖励的衰减率。详见上文。
-
curiosity → network_settings: 这个参数主要是用来决定ICM模型的隐藏层维度的。即不能太大也不能太小。(64 - 256)
-
curiosity → learning_rate: (default = 0.99) 这个参数决定了你的ICM模型的学习率。过大的学习率会导致训练不稳定,过小的学习率会导致收敛缓慢。
7. Memory-enhanced Agents Using Recurrent Neural Networks
7.1 可以通过增加记忆模块的办法来增加模型的表达能力。注意,memory section要加在network下面
-
这个参数决定了你的LSTM网络的隐藏层的维度。这个值必须是偶数。大小视你的状态的复杂程度而定。最好要能够足够大,才能学习到如何记忆之前的情况。
-
这个参数决定了你的记忆网络RNN循环的次数或者说是序列长度。为了能够训练这么长的序列,我们的采集到的经验也需要有这么长。所以根据我的猜测,尽管文档没有明说,但是这个参数一定要小于time_horizon的值。另外,这个数如果设定的太小,那么可能他无法记住太多的东西,反过来,如果太大,训练会很慢。
7.2 使用记忆网络需要注意以下几点
-
LSTM 网络在连续动作空间任务上表现不佳,官方建议更多的用在离散动作空间的任务上面。
-
添加了一个RNN层会增加神经网络的复杂度,务必适度降低神经网络的层数。
-
一定要记住把memory_size设定为偶数。
8. Self Play 参数设置
self play 这个section只有在对抗性训练的时候需要使用,如果仅仅只有一个agent,或者多个agent中没有任何意义上的交互,则不需要设定这一个参数。Unity ml-agent也是利用self play参数的加入来启动它自带的对抗性训练模块。
参考(https://github.com/Unity-Technologies/ml-agents/blob/ddfe054e204415d76b39be93f5bcbec1b456d928/docs/Training-Configuration-File.md#self-play)
-
: 在理解self play参数之前我们需要先理解两个概念,trainer_steps 和 ghost_steps。在一个对抗训练当中,我们往往需要固定住一些agent的参数,在一定的步骤里面让他们作为对手去训练我们的agents。,比如我们训练一个2v1的场景,这时候在学习的队伍是2个agent,对手可能就是1个agent。在这种情况下,trainer_steps的增长就会两倍快于ghost_steps,。理解了这两个概念之后,再来看下面的参数设定就会清楚很多,不然会一头雾水。
-
save_steps: 这个值决定了我们的learning agents,每save_steps个trainer steps会去存储一个当前策略的参数。另外,如果save_steps足够大,比如我们把刚才例子里的save_steps改成20480,那么在存储一次快照之前,参数就要进行至少80次更新,这样每个快照之间的难度曲线就会更陡峭,使得每次训练的不稳定性增大,但是带来的可能是更好的最终结果,以及在复杂任务上更好的表现。
-
:刚才我们已经讨论过了什么是trainer steps,什么是ghost steps。现在来看一下这两个值怎么在对抗训练中决定我们更换对手的频率。team_change 参数是决定我们要用同一个learning agent训练多少次的参数。比如我们现在有红蓝两队球队,如果我们设定team_change=10000,那红队就会先训练10000个trainer steps才会轮到蓝队。而swap_steps则决定了我们在一次team change之中,要更换几次对手。用上面的例子来看,就是红队在这10000个trainer steps里面要面对几个不同的蓝队的过去的快照。这里有一个简单的公式可以计算这种关系,
-
play_against_latest_model_ratio: (default =
0.5
) 这个参数决定了你和自己当前的模型对决的概率。这个值越大约容易和当前的自己的策略对决,也就更少的和自己以往的snapshot交战。
behaviors:
SoccerTwos: #游戏的名字
trainer_type: ppo #选定使用何种训练算法
hyperparameters: #PPO算法的超参数设置
batch_size: 2048
buffer_size: 20480 # buffer大小必须是batch大小的整数倍,这里是十倍
learning_rate: 0.0003
beta: 0.005 #熵正则的超参数
epsilon: 0.2 #PPO算法的clip范围
lambd: 0.95 #GAE算法的lambda
num_epoch: 8 #每次更新训练多少次
learning_rate_schedule: linear #学习率衰减方式,这里是线性衰减
network_settings: #处理observation信息的网络结构设置
normalize: false
hidden_units: 256
num_layers: 2
vis_encode_type: simple
reward_signals: #奖励超参数设置
extrinsic:
gamma: 0.99
strength: 1.0
keep_checkpoints: 5 #一共保留最近的五个checkpoint
checkpoint_interval: 100000 #每100000个timestep保存一个checkpoint
max_steps: 50000000 #最多训练这么多不(注意,这是多个agent加起来的值)
time_horizon: 1000
summary_freq: 5000
threaded: false # 是否使用线程,在使用self-play功能时最好关掉
self_play: #self-play相关参数设定
save_steps: 50000
team_change: 250000
swap_steps: 2000
window: 10 #一共保留十个过去过去的snapshot
play_against_latest_model_ratio: 0.5
initial_elo: 1200.0