本文基于我前面发的两篇文章,需要对ML-Agents有一定的了解,详情请见:Unity强化学习之ML-Agents的使用、ML-Agents命令及配置大全。
我前面的相关文章有:
ML-Agents案例之Crawler
ML-Agents案例之推箱子游戏
ML-Agents案例之跳墙游戏
ML-Agents案例之食物收集者
ML-Agents案例之双人足球
本案例为本文根据双人足球基础之上尝试改进的五人足球。本案例使用了Unity的ML-Agents组件开发而成,这里使用了多智能体强化学习的算法MA-POCA,双方队伍相互学习相互博弈,促进共同进步,可以不断自我进化,从而成长为更加聪明的AI。
先放上效果:
在本案例中每一队分为前锋Striker、后卫Guard、守门员Goalie三个职业。其中前锋两人、后卫两人,守门员一人,比起原来的双人足球,需要训练的模型多出来两个,并且人数上多出来三个,训练难度大大提高。
模型输入
对于模型的输出,我这里仍然采取离散输出的形式,对于模型输入,就有大大的讲究了。
首先模型的输入维度不能过高,否则将导致模型的复杂度大大提高,大大降低训练速度,对于电脑性能不太好的炼丹师更是如此。同时过于复杂的输入也会使得模型难以寻找真正有用的信息,导致Overfitting。同时输入要选择容易训练的数据,比如射线传感器,好处是直观,如果直接给位置信息一般是Train不起来的。
我这里尝试了多种输入:
-
首先是没有使用任何传感器,全部使用数据输入。
public override void CollectObservations(VectorSensor sensor) { if (useVecObs) { sensor.AddObservation(transform.rotation.y); sensor.AddObservation((ball.position - transform.position).x); sensor.AddObservation((ball.position - transform.position).z); sensor.AddObservation(ballRigdbody.velocity.x); sensor.AddObservation(ballRigdbody.velocity.z); if (team == Team.Blue) { sensor.AddObservation((GoalBlue.position - transform.position).x); sensor.AddObservation((GoalBlue.position - transform.position).z); sensor.AddObservation((GoalPurple.position - transform.position).x); sensor.AddObservation((GoalPurple.position - transform.position).z); } else { sensor.AddObservation((GoalPurple.position - transform.position).x); sensor.AddObservation((GoalPurple.position - transform.position).z); sensor.AddObservation((GoalBlue.position - transform.position).x); sensor.AddObservation((GoalBlue.position - transform.position).z); } // 添加每个智能体相对自己的位置信息,要求:列表中两队内部的排列顺序须一致 // 添加自己队伍的四个 foreach (var agentInfo in envController.AgentsList) { if(agentInfo.Agent != this) { if (team == Team.Blue) { if (agentInfo.Agent.team == Team.Blue) { sensor.AddObservation((agentInfo.Agent.transform.position - transform.position).x); sensor.AddObservation((agentInfo.Agent.transform.position - transform.position).z); } } else { if (agentInfo.Agent.team == Team.Purple) { sensor.AddObservation((agentInfo.Agent.transform.position - transform.position).x); sensor.AddObservation((agentInfo.Agent.transform.position - transform.position).z); } } } } // 添加对手队伍的五个 foreach (var agentInfo in envController.AgentsList) { if (team == Team.Blue) { if (agentInfo.Agent.team == Team.Purple) { sensor.AddObservation((agentInfo.Agent.transform.position - transform.position).x); sensor.AddObservation((agentInfo.Agent.transform.position - transform.position).z); } } else { if (agentInfo.Agent.team == Team.Blue) { sensor.AddObservation((agentInfo.Agent.transform.position - transform.position).x); sensor.AddObservation((agentInfo.Agent.transform.position - transform.position).z); } } } } }
相比于传感器的输入,这种输入方法的输入维度较少,所以网络规模也较小,缺点是难以训练,过于全面的数据在完全随机的网络参数的情况下反而难以找到重点,导致很长时间都Train不起来,但是一旦随机得好,智能体的上限是最高的。需要注意的点是由于双方使用的是同样的神经网络,所以我们在配置输入的时候需要根据队伍的不同有不同的配置,例如对于蓝方来说先输入蓝方球门位置,后输入紫方球门位置,对于紫方则需要先输入紫方球门位置,后输入蓝方球门位置。
-
使用Grid Sensor:
使用网格传感器时要注意:Grid Size的y应设置为1,x和z应当保持一致否则报错。
由于网格数量至少都有几百,在加上探测的众多标签,因此输入维度有上千之多。这种传感器推荐在电脑配置较高的情况下使用,否则运行一个环境都会掉帧。优点是探测较为全面,基本不会漏过任何关键信息。
-
使用Ray Perception Sensor 3D:
使用射线传感器的好处是既没有像网格一样有太高维度的输入,行为模式也比单纯的数据输入容易学习。缺点是射线会被阻挡而且在远端较为稀疏,因此往往探测不到较为全面的信息。
其中阻挡问题可以使用多个射线组件来进行弥补,每个组件探测其中的一层,实际上本案例就是这么处理的,其中第一个传感器探测墙壁球门以及球的位置,第二个传感器则探测敌我双方的智能体。
本人开了两个环境进行了两天的训练,训练接近一千万个step,其中三个模型的ELO均能到达1400分以上。Guard和Striker的训练结果比较令人满意,Goalie差强人意,主要问题是探测球的传感器射线数目太少,因此本人建议对于守门员应当把球的标签放到拥有更加茂密射线的第二个传感器中。
奖励函数设置
奖励设置也非常有讲究,好的奖励函数设置可以大大加快训练过程:
- 首先对于前锋Striker,我们应当鼓励其进行进攻,因此每当球更加接近敌方球门,应当给予其奖励,当相反球接近我方球门时,应当给予惩罚。
- 对于守门员这种非常有别于其他智能体的行动模式,应当设立更加特别的奖励函数。我们不鼓励守门员离球门过远,因此当智能体离球门超过一定距离时,给予惩罚,离得越远惩罚越高。并且我们鼓励守门员能很好防守接近球门的每一球,因此当球远离球门时应当给予加分,相反靠近球门给予减分。
void GiveReward()
{
switch (position)
{
case Position.Striker:
if (team == Team.Blue && Vector3.Distance(GoalPurple.position, ball.position) < halfRewardDistance)
{
AddReward(0.002f);
}
else if (team == Team.Purple && Vector3.Distance(GoalBlue.position, ball.position) < halfRewardDistance)
{
AddReward(0.002f);
}
else if (team == Team.Blue && Vector3.Distance(GoalBlue.position, ball.position) < halfRewardDistance)
{
AddReward(-0.002f);
}
else if (team == Team.Purple && Vector3.Distance(GoalPurple.position, ball.position) < halfRewardDistance)
{
AddReward(-0.002f);
}
break;
case Position.Goalie:
if(team == Team.Blue)
{
if (Vector3.Distance(transform.position, GoalBlue.position) >= goalieRewardDstance)
{
AddReward(-(Vector3.Distance(transform.position, GoalBlue.position) - goalieRewardDstance) / 500f);
}
else if (Vector3.Distance(ball.position, GoalBlue.position) <= goalieRewardDstance * 0.7)
{
Vector3 blueGoalToBall = ball.position - GoalBlue.position;
blueGoalToBall.y = 0;
var r = Vector3.Dot(ballRigdbody.velocity, blueGoalToBall) / 500;
AddReward(r);
}
}
else if (team == Team.Purple)
{
if (Vector3.Distance(transform.position, GoalPurple.position) >= goalieRewardDstance)
{
AddReward(-(Vector3.Distance(transform.position, GoalPurple.position) - goalieRewardDstance) / 500f);
}
else if (Vector3.Distance(ball.position, GoalPurple.position) <= goalieRewardDstance * 0.7)
{
Vector3 blueGoalToBall = ball.position - GoalPurple.position;
blueGoalToBall.y = 0;
var r = Vector3.Dot(ballRigdbody.velocity, blueGoalToBall) / 500;
if (r < 0)
{
r = r * 0.7f;
}
AddReward(r);
}
}
break;
case Position.Generic:
break;
}
}
对于集体奖励,在原有的进球得分的基础上,我们加上球往敌方球门方向移动得分,往己方球门移动减分的奖励函数,来使奖励变得更加稠密。添加此函数并在FixedUpdate中调用(前面需要获取球的刚体和双方球门的Transform):
void GiveReward()
{
var ballVelocity = ballRb.velocity;
ballVelocity.y = 0;
var ballToBlueGoalDir = GoalBlue.position - ball.transform.position;
var ballToPurpleGoalDir = GoalPurple.position - ball.transform.position;
ballToBlueGoalDir.y = 0;
ballToPurpleGoalDir.y = 0;
ballToBlueGoalDir = ballToBlueGoalDir.normalized;
ballToPurpleGoalDir = ballToPurpleGoalDir.normalized;
m_PurpleAgentGroup.AddGroupReward(Vector3.Dot(ballVelocity, ballToBlueGoalDir) / 1000);
m_BlueAgentGroup.AddGroupReward(Vector3.Dot(ballVelocity, ballToPurpleGoalDir) / 1000);
}
训练参数设置
同样的,训练使用了MA-POCA算法,并且在这种对称性博弈的环境中我们使用了self-play,并且我们应当使用课程学习来使训练更加平滑,在训练前期我们可以设置更大的碰球奖励来时智能体积极与球进行互动,在后期减少其奖励来使策略变得更加多元化。
我们需要配置前锋,后卫,守门员三种智能体的训练参数,以及课程各个阶段的参数。
behaviors:
Goalie:
trainer_type: poca
hyperparameters:
batch_size: 512
buffer_size: 20480
learning_rate: 0.0003
beta: 0.005
epsilon: 0.2
lambd: 0.95
num_epoch: 3
learning_rate_schedule: constant
network_settings:
normalize: false
hidden_units: 512
num_layers: 2
vis_encode_type: simple
reward_signals:
extrinsic:
gamma: 0.99
strength: 1.0
keep_checkpoints: 5
max_steps: 30000000
time_horizon: 1000
summary_freq: 20000
threaded: false
self_play:
save_steps: 50000
team_change: 200000
swap_steps: 1000
window: 10
play_against_latest_model_ratio: 0.5
initial_elo: 1200.0
Striker:
trainer_type: poca
hyperparameters:
batch_size: 512
buffer_size: 20480
learning_rate: 0.0003
beta: 0.005
epsilon: 0.2
lambd: 0.95
num_epoch: 3
learning_rate_schedule: constant
network_settings:
normalize: false
hidden_units: 512
num_layers: 2
vis_encode_type: simple
reward_signals:
extrinsic:
gamma: 0.99
strength: 1.0
keep_checkpoints: 5
max_steps: 30000000
time_horizon: 1000
summary_freq: 20000
threaded: false
self_play:
save_steps: 50000
team_change: 200000
swap_steps: 4000
window: 10
play_against_latest_model_ratio: 0.5
initial_elo: 1200.0
Guard:
trainer_type: poca
hyperparameters:
batch_size: 512
buffer_size: 20480
learning_rate: 0.0003
beta: 0.005
epsilon: 0.2
lambd: 0.95
num_epoch: 3
learning_rate_schedule: constant
network_settings:
normalize: false
hidden_units: 512
num_layers: 2
vis_encode_type: simple
reward_signals:
extrinsic:
gamma: 0.99
strength: 1.0
keep_checkpoints: 5
max_steps: 30000000
time_horizon: 1000
summary_freq: 20000
threaded: false
self_play:
save_steps: 50000
team_change: 200000
swap_steps: 4000
window: 10
play_against_latest_model_ratio: 0.5
initial_elo: 1200.0
environment_parameters:
ball_touch:
curriculum:
- name: Lesson0
completion_criteria:
measure: progress
behavior: Guard
signal_smoothing: true
min_lesson_length: 100
threshold: 0.05
value: 0.15
- name: Lesson1
completion_criteria:
measure: progress
behavior: Guard
signal_smoothing: true
min_lesson_length: 100
threshold: 0.1
value: 0.08
- name: Lesson3
value: 0.005