Last updated on February 22, 2024 am
Automatic Grouping for MARL
论文标题:
《Vast: Value function factorization with variable agent
sub-teams》
《Automatic Grouping for Efficient Cooperative Multi-Agent
Reinforcement Learning》
论文代码:https://github.com/zyfsjycc/GoMARL
价值函数分解
价值函数的提出是为了缓解在传统的中心化训练和去中心化执行中的Critic网络出现的难以训练的问题,同时传统的合作型多智能体强化学习的方法难以解决信用分配的问题,即如何确定某个智能体对于全局任务而言的贡献程度 。
主体研究框架:
\[
Q_{tot} (τ,a)≈Ψ([Q_i (τ_i,a_i)]_{i=1}^N)
\]
目的就是学习到这样一个分解算子 \(Ψ\) ,能够将局部的价值函数 \(Q_i\) 拟合成全局的价值函数 \(Q_{tot}\) ,而这样的拟合过程学习到的分解算子\(Ψ\) 应当满足以下的约束条件: \[
\underset{\boldsymbol{a}}{\arg \max } Q_{\text {tot
}}(\boldsymbol{\tau}, \boldsymbol{a})=\left(\begin{array}{c}
\operatorname{argmax}_{a_1} Q_1\left(\tau_1, a_1\right) \\
\vdots \\
\operatorname{argmax}_{a_N} Q_N\left(\tau_N, a_N\right)
\end{array}\right)
\]
这个公式的本质是希望分解之后的局部和全局的最优动作能够最优一致,才能保证这样的算子分解方法是有效果的。
价值函数分解的基本思想
可变子团队的价值函数分解
研究动机:
大部分 价值函数方法对少量智能体 的任务效果好
VFF 方法过于扁平,存在性能瓶颈,没有考虑群组之间的一个关系,比较朴素且直接
忽略智能体之间关系导致智能体训练信息不足
因此,为了能够缓解朴素的价值函数分解方法中的缺乏群组信息的缺陷 ,文中提出能够分组处理多智能体系统的工作VAST,能够动态处理群组划分的算子来给出基于分组的智能体价值函数方法。
主要贡献:
基于 \(K ≤ N = |D|\)
的分解子团队值\(Q_{t,k}^G\) ,线性分解每个子团队成员$jG_{t,k}⊆D
$ 局部值\(Q_j\)
提出了一种分解方式动态并随时间变化,同时保持VFF方法的去中心性执行的特点
提供了元梯度 方法来优化子团队分组策略
实现方法:
子团队分组策略:
分组策略的本质是学习一个概率分布 \(\mathcal{X}(k∣i,τ_(t,i),s_t
)\) ,概率分布的本质思想是能够通过每个智能体的状态以及历史数据信息给出智能体
\(i\) 所在的组别 \(k\)
元梯度的更新方式:
\[
𝑔=𝐴 ̂(𝑘,𝑖,𝜏_{𝑡,𝑖},𝑠_𝑡 ) ∇_𝜃 log𝒳_{MetaGrad} (𝑘∣𝑖,𝜏_{𝑡,𝑖},𝑠_𝑡 )
\]
参考借鉴了强化学习的策略梯度更新的方式
在多智能体强化学习中利用强化学习 的方法学习一个组别的分组器
\[
𝐴 ̂(𝑘,𝑖,𝜏_{𝑡,𝑖},𝑠_𝑡 )=𝑄 ̂(𝑘,𝑖,𝜏_{𝑡,𝑖},𝑠_𝑡 )−𝑉 ̂(𝑖,𝜏_{𝑡,𝑖},𝑠_𝑡 )
\]
\[
𝑉 ̂(𝑖,𝜏_{𝑡,𝑖},𝑠_𝑡 )=∑_(𝑎_{𝑡,𝑖}∈𝒜_𝑖) 𝜋_𝑖 (𝑎_{𝑡,𝑖}∣𝜏_{𝑡,𝑖} ) 𝑄_𝑖
(𝜏_{𝑡,𝑖},𝑎_{𝑡,𝑖} )
\]
策略梯度更新的方式是基于优势函数的方法进行更新分组器
不同分类器的对比
这里还采用了不同的分组器方法加入实验的对比
算法思路
将D 分成\(K= ⌈ηN⌉≤ N =
|D|\) 个子团队\(G_{t,k}\)
其中比例\(η ∈ [
1⁄N,1]\) ,所有的团队集合 \(G_t=⟨G_{t,1},…,G_{t,K} ⟩\) ,
分组:\(G_{t,k}⊆D,G_{t,k}∩G_{t,k′}=∅
;D=U_{k=1}^K G_{t,k}\)
组内联合动作:\(a_{t,k}^G=a{t,j}
_j∈G_{t,k}\)
选择\(Ψ_{VDN}\) 算子分解(满足IGM):
\[
Q_{t,k}^G (τ_{t,k}^G,a_{t,k}^G )=Ψ_{VDN} (⋅)=∑_{j∈G_{t,k}} Q_j
(τ_{t,j},a_{t,j})
\]
最终的全局联合价值函数近似:
\[
Q_{tot} (τ_t,a_t )=Ψ(Q_{t,1}^G (τ_{t,1}^G,a_{t,1}^G ),…,Q_{t,K}^G
(τ_{t,K}^G,a_{t,K}^G ))
\]
IGM条件保持
如果以群组为单位的价值函数分解的方式能够保证IGM条件: \[
argmax_(𝑎_{𝑡,𝑘}^𝐺∈𝒜)〖𝑄_{tot}(𝜏_𝑡,𝑎_𝑡)〗=
⟨argmax_(⟨𝐚_{𝐭,𝐤}^𝐆 ⟩_(𝐺_{𝑡,𝑘}∈𝒢_𝑡 ))𝑄_{𝑡,𝑘}^𝐺 (𝜏_{𝑡,𝑘}^𝐺,𝑎_{𝑡,𝑘}^𝐺
)⟩_(𝐺_{𝑡,𝑘}∈𝒢_𝑡)
\] 希望能够满足对于局部智能体到全局价值函数的IGM条件的保持 \[
argmax_(𝑎_𝑡∈𝒜)𝑄_{tot}(𝜏_𝑡,𝑎_𝑡 )=
⟨argmax_(𝑎_{𝑡,𝑖}∈𝒜_𝑖 )𝑄_𝑖 (𝜏_{𝑡,𝑖},𝑎_{𝑡,𝑖})⟩_(𝑖∈𝒟)
\] 推导过程如下: \[
\begin{aligned}
& Q_{t, k}^G\left(\tau_{t, k}^G, a_{t, k}^G\right)=\sum_{j \in G_{t,
k}} Q_j\left(\tau_{t, j}, a_{t, j}\right) ; \operatorname{argmax}_{a_t
\in G_{t, k}} Q_{t, k}^G\left(\tau_{t, k}^G, a_{t,
k}^G\right)=\left\langle\operatorname{argmax}_{a_{t, i} \in
\mathcal{A}_i} Q_i\left(\tau_{t, i}, a_{t, i}\right)\right\rangle_{i \in
G_{t, k}} \\
& a_t \stackrel{\Psi}{=}\left\langle a_{t, k}^G\right\rangle_{G_{t,
k} \in \mathcal{G}_t} \stackrel{\Psi_{V D
N}}{=}\left\langle\left\langle\bar{a}_{t, i}\right\rangle_{i \in G_{t,
k}}\right\rangle_{G_{t, k} \in \mathcal{G}_t}
\stackrel{\mathcal{D}=G_{t, 1} \cup \cdots \cup G_{t,
K}}{=}\left\langle\bar{a}_{t, i}\right\rangle_{i \in \mathcal{D}}
\end{aligned}
\]
自适应分组的MARL
研究动机
通过任务分配实现隐式分组的方法仅解决结构清晰的任务,并且需要领域知识或先验设置
VAST 研究子团队对价值分解的影响,但需要先验的组数,ROMA
的学习依赖于智能体观察的动态角色
GoMARL算法的优势
算法不依赖先验领域知识
动态调整:在分组学习过程中根据智能体的表现逐步调整 分组划分。利用群体信息 来促进策略专业化和高效的团队合作
个体与群组关系假设
所有智能体集合: \(A={a_1,…,a_n }\)
分组集合 : \(G={g_1,…,g_m
},1≤m≤n\)
每个\(g_i\) 包含 \(n_j
(1≤n_j≤n)\) 个不同的智能体,有 \(g_i={a_(j_1 ),…,a_(j_(n_j )
)}\) ,且有 \(g_j∩g_k=∅\) ,且有 \(⋃_jg_j =A,j,k∈{1,2,…,m}\)
自动分组实现方法
自动分组的基本逻辑是学习\(f_g:A↦G\) ,这样的算子的目标是能够学习到系数权重
\[
Q_G^{tot} (s_t,u_t )=E_(s_{t+1:∞},u_{t+1:∞} ) [∑_(k=0)^∞ γ^k
r_{t+k}∣s_t,u_t;G]
\] 让总体的联合动作价值函数的值最大,分组的价值函数为: \[
𝑄^{𝑔_𝑗}=𝑓(𝑄^{𝑗_1} (𝜏^{𝑗_1},𝑢^{𝑗_1}),⋯,𝑄^{𝑗_{𝑛_𝑗}} (𝜏^{𝑗_(𝑛_𝑗
)},𝑢^{𝑗_{𝑛_𝑗}};𝑤_1^{𝑔_𝑗})
\] 学习到的贡献程度为:\(𝑤_1^{𝑔_𝑗}\) ,训练的目标函数为: \[
ℒ_𝑔 (𝜃_{w_1})=𝔼_((𝐳,𝐮,𝑟,𝐳′)∼ℬ∑_𝑖(∥𝑓_(w_1)^𝑖 (𝜏^𝑖 (𝑧^𝑖,𝑢^𝑖 );𝜃_(w_1)^𝑖
)∥_{𝑙_1})
\]
根据每次在分组中的贡献权重来依次调整智能体的分组,当贡献的权重过小的话,就认为智能体不属于分组中,进入下一轮的分组调整中。
自动分组的逻辑图
Agent Network的优化
智能体内嵌信息网络获取的群组相关信息\(e_i\)
Similarity-Diversity
objective :来自同一组的智能体的信息是相似的,来自不同群体的智能体信息之间保持多样性:
\[
\begin{gathered}
\mathcal{L}_{S
D}\left(\theta_e\right)=\mathbb{E}_{\mathcal{B}}\left(\sum_{i \neq j}
I(i, j) \cdot \operatorname{cosine}\left(f_e\left(h^i ; \theta_e\right),
f_e\left(h^j ; \theta_e\right)\right)\right) \\
\text { where } I(i, j)=\left\{\begin{array}{cc}
-1, & a^i, a^j \in g^k \\
1, & a^i \in g^k, a^j \in g^l, k \neq l
\end{array}\right.
\end{gathered}
\]
很直观的理解,对于合作型的强化学习任务而言,我们希望能够在同一个任务域内强化策略的合作,而不同的任务域内的策略应当保持多样性,能够让多智能体系统探索更多的可能性,防止陷入局部最优的情况。
Agent Network特点 :
多样化的策略,部分共享参数的策略去中心化
超网络\(f_d\) 将提取的智能体信息e集成到策略梯度中
合理性表示: \[
𝜕𝑄^{𝑡𝑜𝑡}/(𝜕𝜃_ℎ )=(𝜕𝑄^{𝑡𝑜𝑡})/(𝜕𝑄^𝑎 ) (𝜕𝑄^𝑎)/(𝜕𝜃_ℎ )=(𝜕𝑄^{𝑡𝑜𝑡})/(𝜕𝑄^𝑎
) (𝜕𝑄^𝑎)/(𝜕𝑣_ℎ^𝑎 ) (𝜕𝑣_ℎ^𝑎)/(𝜕𝜃_ℎ )=𝑓_𝑑 (𝑒^𝑎 )⋅(𝜕𝑄^{𝑡𝑜𝑡})/(𝜕𝑄^𝑎
) (𝜕𝑣_ℎ^𝑎)/(𝜕𝜃_ℎ )
\]
整体的训练框架
双层混合网络
群体状态下的群体行动价值信息,将全局的状态分解成局部的状态信息用于训练,这部分的局部状态信息是基于群组划分之后的状态信息
\(s^{g_j}\) 是分组后\(e_t^i (a_i∈g_j
)\) 按组别融合,建立分组器映射
\[
f_{w_2}(s^g ):s^g→w_2^g
\]
集中到策略梯度中,内嵌群组状态信息潜在促进组内合作: \[
𝜕𝑄^{𝑡𝑜𝑡}/(𝜕𝜃_ℎ )=(𝜕𝑄^{𝑡𝑜𝑡})/(𝜕𝑄^𝑎 ) (𝜕𝑄^𝑎)/(𝜕𝜃_ℎ )=(𝜕𝑄^{𝑡𝑜𝑡})/(𝜕𝑄^𝑎
) (𝜕𝑄^𝑎)/(𝜕𝑣_ℎ^𝑎 ) (𝜕𝑣_ℎ^𝑎)/(𝜕𝜃_ℎ )=𝑓_𝑑 (𝑒^𝑎 )⋅(𝜕𝑄^{𝑡𝑜𝑡})/(𝜕𝑄^𝑎
) (𝜕𝑣_ℎ^𝑎)/(𝜕𝜃_ℎ )
\]
按照全文的总共方法的总述可以得到全局分解目标: \[
ℒ(𝜃)=ℒ_𝑇𝐷 (𝜃)+𝜆_𝑔 ℒ_𝑔 (𝜃_(w_1 ) )+𝜆_𝑆𝐷 ℒ_𝑆𝐷 (𝜃_𝑒 )
\]
总体训练框架
代码实现
GoMARL代码的实现是基于pymarl多智能体的训练框架进行改进的,这里主要对其中
learning
mixing
的网络部分进行展开叙述
1 2 3 4 5 6 7 8 9 10 11 12 13 14 if buffer.can_sample(args.batch_size): next_episode = episode + args.batch_size_run if args.accumulated_episodes and next_episode % args.accumulated_episodes != 0 : continue episode_sample = buffer.sample(args.batch_size) max_ep_t = episode_sample.max_t_filled() episode_sample = episode_sample[:, :max_ep_t] if episode_sample.device != args.device: episode_sample.to(args.device) learner.train(episode_sample, runner.t_env, episode) del episode_sample
在这一步中通过调用
learner.train(episode_sample, runner.t_env, episode)
来对自动分组的学习器进行强化学习
在learner
中,通过使用其中的mixer
对联合的动作价值函数进行学习
1 2 3 4 5 chosen_action_qvals = th.gather(mac_out[:, :-1 ], dim=3 , index=actions).squeeze(3 ) chosen_action_qvals, w1_avg_list, sd_loss = self.mixer(chosen_action_qvals, batch["state" ][:, :-1 ], mac_hidden[:, :-1 ], mac_group_state[:, :-1 ], "eval" )
接下来我们将详细介绍 mixer
函数部分的实现
第一部分是对超参数的预设,从yaml
文件中读取了预设的超参数的数值
1 2 3 4 5 6 7 8 9 10 self.args = args self.abs = abs self.n_agents = args.n_agents self.embed_dim = args.mixing_embed_dim self.hypernet_dim = args.hypernet_embed self.grouping_hypernet_dim = args.grouping_hypernet_embed self.group = args.group if self.group is None : self.group = [[_ for _ in range (self.n_agents)]]
这一步是计算出整个状态空间的全部维度=44 10
1 self.state_dim = int (np.prod(args.state_shape))
定义网络结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 self.hyper_w1 = nn.ModuleList(nn.Sequential(nn.Linear(self.a_h_dim, self.grouping_hypernet_dim), nn.ReLU(inplace=True ), nn.Linear(self.grouping_hypernet_dim, self.embed_dim)) for i in range (self.n_agents)) self.hyper_b1 = nn.ModuleList([nn.Sequential(nn.Linear(self.a_h_dim, self.embed_dim))]) self.hyper_w2 = nn.ModuleList([nn.Sequential(nn.Linear(self.hypernet_dim, self.hypernet_dim), nn.ReLU(inplace=True ), nn.Linear(self.hypernet_dim, self.embed_dim))]) self.hyper_b2 = nn.ModuleList([nn.Sequential(nn.Linear(self.hypernet_dim, self.embed_dim), nn.ReLU(inplace=True ), nn.Linear(self.embed_dim, 1 ))]) self.hyper_w3 = nn.Sequential(nn.Linear(self.hypernet_dim, self.hypernet_dim), nn.ReLU(inplace=True ), nn.Linear(self.hypernet_dim, self.embed_dim)) self.hyper_b3 = nn.Sequential(nn.Linear(self.hypernet_dim, self.embed_dim)) self.hyper_w4 = nn.Sequential(nn.Linear(self.state_dim, self.hypernet_dim), nn.ReLU(inplace=True ), nn.Linear(self.hypernet_dim, self.embed_dim)) self.hyper_b4 = nn.Sequential(nn.Linear(self.state_dim, self.embed_dim), nn.ReLU(inplace=True ),
MyModule 类中有一个名为 linears 的 nn.ModuleList
属性,它包含了一个由五个 nn.Linear 层组成的列表。当 forward
方法被调用时,输入 x 会被序列化地通过这五个线性层。
nn.Sequential
的好处是,它简化了模块的顺序管理,你不需要手动编写复杂的
forward
方法来处理数据通过每个模块的过程。nn.Sequential
会自动为你完成这个过程
每次增加分组都会产生新的模块需要新的 mixing network
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 def add_new_net (self ): self.hyper_b1.append(nn.Sequential(nn.Linear(self.a_h_dim, self.embed_dim))) self.hyper_w2.append(nn.Sequential(nn.Linear(self.hypernet_dim, self.hypernet_dim), nn.ReLU(inplace=True ), nn.Linear(self.hypernet_dim, self.embed_dim))) self.hyper_b2.append(nn.Sequential(nn.Linear(self.hypernet_dim, self.embed_dim), nn.ReLU(inplace=True ), nn.Linear(self.embed_dim, 1 ))) if self.args.use_cuda: self.hyper_b1.cuda() self.hyper_w2.cuda() self.hyper_b2.cuda() print ("New Mixer Size:" ) print (get_parameters_num(self.parameters()))
同样对应的就是删除某个组的某个网络
1 2 3 4 5 6 7 ef del_net(self, idx): del self.hyper_b1[idx] del self.hyper_w2[idx] del self.hyper_b2[idx] print ("Del Group {} Network Mixer Size:" .format (idx)) print (get_parameters_num(self.parameters()))
划分成组为单位
1 2 3 4 for group_index, group_i in enumerate (self.group): group_state = all_group_state[:, group_i, :] group_state = th.max (group_state, dim=1 )[0 ] group_state_list.append(group_state)
生成最开始的 \(w_1\) 权重
1 2 3 4 5 6 7 for i in range (self.n_agents): w1 = self.hyper_w1[i](a_h[:, i, :]) if self.abs : w1 = w1.abs () w1_list.append(w1) w1 = th.stack(w1_list, dim=1 )
计算SD Loss
这部分主要是在计算组内相似性 和组间相似性从而得出SD loss的损失值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 sd_loss = 0 for group_index, group_i in enumerate (self.group): group_n = len (group_i) group_qs = qvals[:, :, group_i] group_qs = group_qs.reshape(b * t, 1 , group_n) group_a_h = a_h[:, group_i, :] group_w1 = w1[:, group_i, :] b1 = self.hyper_b1[group_index](group_a_h) b1 = th.sum (b1, dim=1 , keepdim=True ) group_j = [] for group_tmp in self.group: if group_tmp != group_i: group_j += group_tmp for agent_i in group_i: group_state_agent_i = all_group_state[:, agent_i, :].unsqueeze(1 ) group_state_sim = all_group_state[:, group_i, :] group_state_div = all_group_state[:, group_j, :] group_state_agent_i_sim = group_state_agent_i.expand_as(group_state_sim) group_state_agent_i_div = group_state_agent_i.expand_as(group_state_div) sim_loss = th.sum (group_state_agent_i_sim * group_state_sim, dim=-1 ) / \ ((th.sum (group_state_agent_i_sim ** 2 , dim=-1 ) ** 0.5 ) * (th.sum (group_state_sim ** 2 , dim=-1 ) ** 0.5 )) div_loss = th.sum (group_state_agent_i_div * group_state_div, dim=-1 ) / \ ((th.sum (group_state_agent_i_div ** 2 , dim=-1 ) ** 0.5 ) * (th.sum (group_state_div ** 2 , dim=-1 ) ** 0.5 )) sd_loss -= th.sum (sim_loss, dim=-1 ) sd_loss += th.sum (div_loss, dim=-1 )
生成组别为单位的价值函数
1 2 3 4 5 6 7 8 9 10 11 12 group_state = group_state_list[group_index] w2 = self.hyper_w2[group_index](group_state).view(b*t, self.embed_dim, 1 ) b2 = self.hyper_b2[group_index](group_state).view(b*t, 1 , 1 )if self.abs : w2 = w2.abs () group_w1_avg = th.mean(th.mean(group_w1, dim=2 , keepdim=False ), dim=0 , keepdim=False )if self.group_turn_now == group_index: w1_avg_list.append(group_w1_avg) hidden = F.elu(th.matmul(group_qs, group_w1) + b1) q_group = th.matmul(hidden, w2) + b2 group_q_list.append(q_group.view(b*t, 1 ))
生成总的价值函数
下面这一步是基于按照组划分得到的价值函数生成总的联合动作价值函数的过程
其中用到了两个生成参数的机器 w3和w4以及b3,b4
其实结构和之前的是一样的
最后返回值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 group_tot_q = th.stack(group_q_list, dim=-1 ) group_state_stack = th.stack(group_state_list, dim=1 ) w3 = self.hyper_w3(group_state_stack) b3 = self.hyper_b3(group_state_stack) b3 = th.sum (b3, dim=1 , keepdim=True ) w4 = self.hyper_w4(states).view(b*t, self.embed_dim, 1 ) b4 = self.hyper_b4(states).view(b*t, 1 , 1 ) if self.abs : w3 = w3.abs () w4 = w4.abs () tot_hidden = F.elu(th.matmul(group_tot_q, w3) + b3) tot_q = th.matmul(tot_hidden, w4) + b4
返回的东西分别是:总的联合动作价值函数、w1权重的平均值就是生成w1分类器的好坏、SDloss
1 return tot_q.view(b, t, -1 ) , w1_avg_list, sd_loss.view(b, t, -1 ) / (self.n_agents * self.n_agents)
损失函数
这个地方利用了三个部分组成的损失函数来计算最终的损失
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 lasso_loss = 0 for i in range (len (w1_avg_list)): group_w1_sum = th.sum (w1_avg_list[i]) lasso_loss += group_w1_sum * lasso_alpha[i] sd_loss = sd_loss * mask sd_loss = self.args.sd_alpha * sd_loss.sum () / mask.sum () td_error = (chosen_action_qvals - targets.detach()) td_error = 0.5 * td_error.pow (2 ) mask = mask.expand_as(td_error) masked_td_error = td_error * mask td_loss = masked_td_error.sum () / mask.sum () loss = td_loss + lasso_loss + sd_loss
改变分组的过程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 if change_group_i == self.args.change_group_batch_num - 1 : self.agent_w1_avg /= self.args.change_group_batch_num group_now = copy.deepcopy(self.mixer.group) group_nxt = copy.deepcopy(self.mixer.group) for group_index, group_i in enumerate (group_now): group_w1_avg = self.agent_w1_avg[group_i] group_avg = th.mean(group_w1_avg) relative_lasso_threshold = group_avg * self.args.change_group_value indices = th.where(group_w1_avg < relative_lasso_threshold)[0 ] if len (group_i) < 3 : continue if group_index+1 == len (group_now) and len (indices) != 0 : tmp = [] group_nxt.append(tmp) self.mixer.add_new_net() self.target_mixer.add_new_net() for i in range (len (indices)-1 , -1 , -1 ): idx = group_now[group_index][indices[i]] group_nxt[group_index+1 ].append(idx) del group_nxt[group_index][indices[i]] for m in self.mixer.hyper_w1[idx]: if type (m) != nn.ReLU: m.reset_parameters() whether_group_changed = True if group_now != group_nxt else False if not whether_group_changed: return for i in range (len (group_nxt)-1 , -1 , -1 ): if group_nxt[i] == []: del group_nxt[i] self.mixer.del_net(i) self.target_mixer.del_net(i) self.mixer.update_group(group_nxt) self.target_mixer.update_group(group_nxt) self._update_targets()
实验结果
在星际争霸的环境中的训练结果
SMAC训练结果
消融实验
消融实验对比
谷歌足球的训练结果
GRF的训练结果与可视化