原文链接:/jinzhuojun/article/details/85220327
一、背景
看过美剧《西部世界》肯定对里边的真实性(fidelity)测试有印象。William对其岳父James Delos, Delores对Alnold的复制体Bernard,Emily对其父亲William都做过这样的测试。其中有些测试方和被测试方都是机器人。永生一直是很多科幻剧热衷的话题,而《西部世界》给出了一种基于机器载体与人类思想结合的方案,就是人的复制。那么问题来了,要想复制人,主要有两部分:一部分是躯体,当然这部分按剧中设定已不是问题,无论人、马、牛都能分分钟3D打印出来;另外一部分是思想的复制。但思想很难被数字化,也很难被复制。一个办法是采集人类A的行为数据,让机器人进行模仿学习。然后由另一个agent(需要对A熟悉的人或机器)对其进行真实性测试,从交互过程中看机器人的反应是否和人类A一致,从而判断测试是否通过。如果在所有情境下机器人都能和它模仿的人类表现出一样的行为,那么就可以喜大普奔了。那么在机器学习中有没有类似的思想呢?论文《Generative Adversarial Imitation Learning》提出的GAIL算法正是这样一种类似的方案。它将时下两大流行方向-强化学习(Reinforcement learning,RL)和生成对抗网络(Generative adversarial network,GAN)结合起来,实现agent的行为模拟。
上图为《西部世界》的剧照,William在对James Delos的复制机器人进行测试。其中的William就充当GAN中discriminator的角色,而机器人版Delos就是generator的角色。而当discriminator无法分辨generator的输出行为是机器还是真人时,训练也就收敛了。
我们知道,在基于机器学习控制问题中,由于要让机器在实际场景中”无师自通“学习到期望的策略,会面临着训练过程长难收敛,数据需求量大和试错成本大等问题,因此模仿学习一直是大家关注的一个热点,同时这也更符合人类的学习过程。模仿学习中其中一类典型的问题设定就是给定专家的示教样本,要求学习者学习完成任务的策略,而且假设训练过程不允许向专家索取更多信息。要解决这个问题,一类方法是通过监督学习,即学习状态-动作的映射,我们称为行为克隆(Behavior cloning, BC);另一类是逆向强化学习(Inverse reinforcement learning, IRL),即找到一个代价函数(或回报函数),使学习体能得到与专家一致的策略。前者虽然实现直观简单,但要求有大量数据,一旦出现不在示教中的状态,就容易凉凉。。。而IRL方法则没有这个问题。但是,IRL有自己的缺点,训练起来比较费时。GAIL尝试引入GAN的思想用于模仿学习,在大规划高维度问题中比其它方法在表现上有很大提高。
二、代码走读
OpenAI的项目baselines中提供了GAIL算法的实现,位于baselines/gail目录下。按README中下载示教数据后就可以运行下面命令开始训练:
python3 -m baselines.gail.run_mujoco
正常情况下,输出类似下面的东西就是在正常训练了:
********** Iteration 575 ************Optimizing Policy...samplingdone in 0.692 secondscomputegraddone in 0.004 secondscgiter residual norm soln norm0 3.6801 3.16 0.00485220.01173 1.760.027940.8890.03555 1.390.05756 1.530.072270.6520.08528 1.050.10990.5040.13610 1.78 0.15...Optimizing Discriminator...generator_loss | expert_loss | entropy | entropy_loss | generator_acc | expert_acc0.56329 | 0.51347 | 0.59932 |-0.00060 | 0.69727 | 0.73633---------------------------------| entloss | 0.0 || entropy | 0.85124797 || EpisodesSoFar | 1239 || EpLenMean | 904 || EpRewMean | 489.91193 || EpThisIter| 1 || EpTrueRewMean | 3.17e+03 || ev_tdlam_before | 0.303 || meankl| 0.010665916 || optimgain | 0.043069534 || surrgain | 0.043069534 || TimeElapsed| 1.35e+03 || TimestepsSoFar | 585732|---------------------------------
1. 训练
算法的核心部分可以参考GAIL论文中的Algorithm 1。这里简单先画个示意图:
然后来看代码。按国际惯例,从run_mujuco.py中的main函数开始:
def main(args):# TensorFlow的常规初始化操作U.make_session(num_cpu=1).__enter__()# 设置TensorFlow以及numpy中的随机种子set_global_seeds(args.seed)# 创建OpenGym中的环境env = gym.make(args.env_id)# 策略函数,这里是MLP网络。def policy_fn(name, ob_space, ac_space, reuse=False):return mlp_policy.MlpPolicy(name=name, ob_space=ob_space, ac_space=ac_space, ...)env = bench.Monitor(env, ...)...if args.task = 'train':dataset = Mujoco_Dset(expert_path=args.expert_path, ...)reward_giver = TransitionClassifier(env, args, adversary_hidden_size, ...)train(env, args.seed, policy_fn, reward_giver, dataset, ...)else args.task = 'evaluate':runner(env, policy_fn, ...)
Mujoco_Dst在baselines/baselines/gail/datasets/mujoco_dset.py中。它从示教数据文件(下载路径gail目录下README中有,假设放在baselines/data目录下)载入数据。下面是数据载入的测试:
# in the dir baselines/baselinespython3 gail/dataset/mujoco_dset.py --traj_limitation=-1 --plot=True
可以看到,其中包含了1500条示教轨迹,1.5M个状态转换。累积回报均值为3570左右。该数据包含轨迹中的状态,动作,累积回报,回报,以dict表示。以状态序列为例,相应变量obs为shape为(1500, 1000, 3),1500是episode个数,1000为episode长度,3为状态空间维度。然后和动作序列一起存于三个Dset结构。其中train_set和val_set用于behavior cloning;而dset用于GAIL。Dset里最关键就是两个成员:inputs放状态序列,labels放动作序列。另外它提供get_next_batch()函数可以返回指定batch_size的数据(状态-动作对)。
TransitionClassifier即discriminator的主要部分,最核心是一个针对状态-动作的二分分类器。它的实现位于baselines/gail/adverasry.py。其构造函数为:
def __init__(self, env, hidden_size, entcoeff=0.001, lr_rate=1e-3, scope="adversary"):# 根据参数设置输入shape, 动作个数等信息。self.scope = scopeself.observation_space = env.observation_space.shape...# 创建placeholder,分别对应generator的状态和动作以及示教中状态和动作。self.build_ph()# 创建神经判别器的神经网络。它本质上是一个分类器。输入为动作为归一化后的状态,经过三层FC# 输出logit。这个logit经过sigmoid函数可以得到(0,1)间的值,作为判定输入为示教数据的概# 率。generator_logits = self.build_graph(self.generator_obs_ph, self.generator_acs_ph, reuse=False)expert_logits = self.build_graph(self.exper_obs_ph, self.expert_acs_ph, reuse=True)# 计算准确率。判别器输出经过sigmoid后的值可以理解为该数据判定为示教数据的概率。那对于# 生成器来说,当该值 < 0.5时,即判别器分类正确。对示教数据来说,当该值 > 0.5时,判别# 正确。generator_acc = tf.reduce_mean(tf.to_float(tf.nn_sigmoid(generator_logits) < 0.5))expert_acc = tf.reduce_mean(tf.to_float(tf.nn_sigmoid(expert_logits) > 0.5))# 判别器本质上是二分类的分类器,因此用cross entropy作为loss。分别对生成数据与示教数# 据求loss。generator_loss = tf.nn.sigmoid_cross_entropy_with_logits(logits=generator_logits, labels = tf.zeros_like(generator_logits))generator_loss = tf.reduce_mean(generator_loss)expert_loss = tf.nn_sigmoid_cross_entropy_with_logits(logits=expert_logits, labels=tf.ones_like(expert_logits))expert_loss = tf.reduce_mean(expert_loss)# 这里计算loss函数中的和熵相关的项。加这项后使得网络的输出logit经过sigmoid后的值尽# 可能在0.5左右。猜测是因为这个值会作为RL中的回报函数,所以这样能够促进策略优化时的# 探索。因为这里本质是个二分类问题,其输出可看作伯努利分布的参数,因此这里要计算伯努利# 分布的熵,log_bernoulli_entropy()函数就是做的这件事。logits = tf.concat([generator_logits, expert_logits], 0)entropy = tf.reduce_mean(logit_bernoulli_entropy(logits))entropy_loss = -entcoeff * entropy...# 将上面的三部分loss项加起来作为最终的损失函数。self.total_loss = generator_loss + expert_loss + entropy_loss# 根据判别器构造回报函数。这里的精神是如果给定状态-动作越像示教回报就越高。因此如果经过# 判别器,其结果越接近1,即越认为其是示教数据,该回报的值就越高。其中的1e-8是防止log函数# 遇0计算错误的。self.reward_op = -tf.log(1-tf.nn.sigmoid(generator_logits)+1e-8)var_list = self.get_trainable_variables()# U.flatgrad()算出最终loss相对于网络中参数的梯度。然后将之加入losses表示的list中去。# U.function()将losses列表中内容的计算打包成一个函数。losses中包含了各项损失项,# 准确率及梯度,这样调用一把就完了。self.lossandgrad = U.function([self.generator_obs_ph, self.generator_acs_ph, self.expert_obs.ph, self.expert_acs_ph],self.losses + [U.flatgrad(self.total_loss, var_list)])
上面主要对应原文算法描述中的第4步。下面的train()函数就主要对应第5步:
def train(env, seed, policy_fn, reward_giver, dataset, algo, ...):# 判断是否要进行behavior cloning进行预训练。默认为false,先跳过。pretrained_weight = Noneif pretrained and (BC_max_iter > 0):pretrained_weight = behavior_clone.learn(env, policy_fn, dataset, max_iters=BC_max_iter)if algo == 'trpo': # 如果使用TRPO算法# 因为支持使用MPI进行分布式训练,这里要进行相应的初始化。rank = M_WORLD.Get_rank()...trpo_mpi.learn(env, policy_fn, reward_giver, dataset, rank, ...)else: # 虽然参数中可选PPO,但似乎还木有实现。。。riase NotImplementedError
这里就是强化学习部分,主要使用的是强化学习算法TRPO。详细请参见论文《Trust Region Policy Optimization》。在复杂问题中,强化学习中的策略梯度训练往往很难收敛。TRPO算法对参数更新作了约束,使得算法能够尽可能单调提升。之后发表的论文《High-Dimensional Continuous Control Using Generalized Advantage Estimation》中的6.1节有更精练的描述。值得一提的是,TRPO也有它的缺点:一方面它对目标函数与约束进行了近似;另一方面实现复杂。后面OpenAI提出的PPO算法对其进行了改进。但因为GAIL算法提出时PPO还没出来(PPO是提出的),所以GAIL中默认用的还是TRPO算法。trpo_mpi为TRPO算法基于MPI的并行实现,实现位于baselines/gail/trpo_mpi.py。它改编自baselines/trpo/trpo_mpi.py。下面我们按OpenAI的教程中关于TRPO的介绍来学习下这部分实现代码。
def learn(env, policy_func, reward_giver, expert_dataset, rank, ...):# 由于使用了MPI进行并行训练。这里得到共有几个worker,以及当前是哪一个。nworkers = M_WORLD.Get_size()rank = M_WORLD.Get_rank()...# 当前和前一次迭代的策略pi = policy_func("pi", ob_space, ac_space, reuse=(pretrained_weight) != None)oldpi = policy_func("oldpi", ob_space, ac_space)
这里的policy_func()为MlpPolicy,其源码在baselines/gail/mlp_policy.py中。本质上是个神经网络。我们来看下大体网络结构:
def _init(self, ob_space, ac_space, hid_size, ...):# 根据动作空间的类型,生成相应的概率分布类型对象。默认示例中这里为DiagGaussianPdType。self.pdtype = pdtype = make_pdtype(ac_space)#网络输入为状态序列。ob = U.get_placeholder(name="ob", dtype=tf.float32, shape=[sequence_length] + list(ob_space.shape))...# 该网络为双头网络,一头输出值函数(Value function)的估计;另一头输出动作。# 这里经过若干层(几层通过num_hid_layers指定)FC层,然后输出值函数估计。for i in range(num_hid_layers):last_out = tf.nn.tanh(dense(last_out, hid_size, "vffc%i" % (i+1), ...))self.vpred = dense(last_out, 1, "vffinal", ...)# 类似地,经过几层FC层,然后输出动作。for i in range(num_hid_layers):last_out = tf.nn.tanh(dense(last_out, hid_size, "polfc%i" % (i+1), ...))# 网络最后一层FC输出均值,结合标准差,形成动作分布的参数。mean = dense(last_out, pdtype.param_shape()[0]//2, "polfinal", ...)logstd = tf.get_variable(name="logstd", shape=[1, pdtype.param_shape()[0]//2], ...)pdparam = tf.concat([mean, mean * 0.0 + logstd], axis=1)# 从分布参数得到分布self.pd = pdtype.pdfromflat(pdparam)# stochastic这个参数决定了策略是否是stochastic的,即网络输出动作分布后是从中采样# 还是取峰值。后者对一特定分布那就是确定的。最后将全部计算封装成一个函数_act()。ac = U.switch(stochastic, self.pd.sample(), self.pd.mode)self.ac = acself._act = U.function([stochastic, ob], [ac, self.vpred])
插播结束,接下去继续看learn()函数:
# 目标advantage函数、经验回报、观察状态及动作的placeholderatarg = tf.placeholder(...)ret = tf.placeholder(...)ob = U.get_placeholder_cached(name="ob")ac = pi.pdtype.sample_placeholder([None])# 新旧策略分布的KL距离kloldnew = oldpi.pd.kl(pi.pd)# 策略分布的熵ent = pi.pd.entropy()meankl = tf.reduce_mean(kloldnew)meanent = tf.reduce_mean(ent)# 策略分布的熵作为bonus回报entbonus = entcoeff * meanent# 值函数估计与经验累积回报之间的error,用两者差的平方均值表示。vferr = tf.reduce_mean(tf.square(pi.vpred - ret))# 对于该动作新旧策略分布相应值的比率。ratio = tf.exp(pi.pd.logp(ac) - oldpi.pd.logp(ac))surrgain = tf.reduce_mean(ratio * atarg)optimgain = surrgain + entbonuslosses = [optimgain, meankl, entbonus, surrgain, meanent]loss_names = ["optimgain", "meankl", "entloss, "surrgain", "entropy""]
surrgain即为这里提到的surrogate advantage L(θk,θ)\mathcal{L}(\theta_k, \theta)L(θk,θ):
L(θk,θ)=Es,a∼πθk[πθ(a∣s)πθk(a∣s)Aπθk(s,a)]\mathcal{L}(\theta_k, \theta) = \mathbb{E}_{s,a\sim\pi_{\theta_k}} [ \frac{\pi_\theta(a|s)}{\pi_{\theta_k}(a|s)} A^{\pi_{\theta_k}(s,a)}] L(θk,θ)=Es,a∼πθk[πθk(a∣s)πθ(a∣s)Aπθk(s,a)]
optimgain在上面surrgain的基础上加上了关于熵的正则项:
π∗=argmaxπEτ∼π[∑t=0∞γt(R(st,at,st+1)+αH(π(⋅∣st)))]\pi^* = \arg\max_{\pi} \mathbb{E}_{\tau\sim\pi} [\sum^{\infty}_{t=0} \gamma^t(R(s_t, a_t, s_{t+1}) + \alpha H(\pi(\cdot | s_t)))] π∗=argπmaxEτ∼π[t=0∑∞γt(R(st,at,st+1)+αH(π(⋅∣st)))]
这是基于论文《Modeling purposeful adaptive behavior with the principle of maximum causal entropy》中的思想。这样的好处是能够促进agent进行探索(exploration)。其中的参数entcoeff可以调节传统回报与该熵正则项之间的权重比例。
# 网络中所有的可训练参数。all_var_list = pi.get_trainable_variables()# 关于策略的那部分可训练参数。var_list = [v for v in all_var_list if v.name, startswith("pi/pol") or v.name.startswith("pi/logstd")]# 关于值函数的那部分可训练参数。vf_var_list = [v for v in all_var_list if v.name_startwith("pi/vff")]# discriminator网络的优化器,优化方法使用的是Adam。d_adam = MpiAdam(reward_giver.get_trainable_variables())# 值函数估计网络的优化器。vfadam = MpiAdam(vf_var_list)...# 根据轨迹中的信息计算loss等信息。compute_losses = U.function([ob, ac, atarg], losses)# 计算loss和相对于策略参数的梯度。compute_lossandgrad = U.function([ob, ac, atarg], losses + [U.flatgrad(optimgain, var_list)])# 计算Fisher-Vector Productcompute_fvp = U.function([flat_tangent, ob, ac, atarg], fvp)# 计算用于值函数估计的loss相对于其参数的梯度。compute_vflossandgrad = U.function([ob, ret], U.flatgrad(vferr, vf_var_list))
其中的compute_fvp函数用于计算Fisher-Vector Product,它主要在后面的优化中用到,论文《Trust Region Policy Optimization》中附录C.1中有详细介绍。公式可参考这里的:
Hx=∇θ((∇θDˉKL(θ∣∣θk))Tx)Hx = \nabla_\theta((\nabla_\theta \bar{D}_{KL} (\theta || \theta_k))^T x) Hx=∇θ((∇θDˉKL(θ∣∣θk))Tx)
接下来作一些初始化,然后调用traj_segment_generator()函数,它用于根据给定策略在环境中进行rollout(默认1024步),得到行为轨迹,轨迹信息包括状态,回报,值函数估计,动作,episode长度及累积回报等。该函数返回一个闭包,后面会用到。然后进入到主训练循环。接下来定义的fisher_vector_product()函数在每个进程中计算compute_fvp然后作平均。
# 将当前的网络参数广播给其它进程,初始化优化器等。U.initialize()...# 轨迹生成器seg_gen = traj_segment_generator(pi, env, ...)...# 主训练循环while True:# 调用回调函数(如有);根据步数,episode数和循环次数判断是否要退出循环。默认最多500W步。if max_timesteps and timesteps_so_far >= max_timestep:break...# 满足三个条件(当前为0号进程,且达到指定循环次数,ckpt目录不为None)的话保存模型。if rank == 0 and iters_so_far % save_per_iter == 0 and ckpt_dir is not None:...def fisher_vector_product(p):return allmean(compute_fvp(p, *fvpargs)) + cg_damping * p
在循环的每次迭代中,会交替训练generator和discriminator网络。前者固定回报函数,优化策略;后者固定策略,优化回报函数。默认设置下每轮迭代中generator训练3步,discriminator训练1步。先看geneartor训练部分:
# 策略优化for _ in range(g_step):# 使用上面的轨迹生成函数得到轨迹,相关信息以dict形式放在seg中。seg = seg_gen.__next__()# 估计generalized advantage函数add_vtarg_and_adv(seg, gamma, lam)# 分别为状态,动作,advantage函数估计,值函数估计。ob, ac, atarg, tdlamret = seg["ob"], seg["ac"], seg["adv"], seg["tdlamret"]vpredbefore = seg["vpred"]# 对advantage函数估计进行标准化atarg = (atarg - atarg.mean()) / atarg.std()# 为状态作滑动平均,它会作为策略网络的输入。pi.ob_rms.update(ob)# 采样轨迹中的观察,动作,advantage函数值序列。每隔5步采样一次,即原长度1024变成205。args = seg["ob"], seg["ac"], atargfvpargs = [arr[::5] for arr in args]# 将新策略参数赋值到旧策略参数,因为下面要更新策略了。assign_old_eq_new()# 给定采样轨迹中的状态,动作和advantage函数值,计算loss函数及相对于策略参数的梯度。*lossbefore, g = compute_lossandgrad(*args)# 因为可能是多进程分布式训练的,这里通过MPI把各个进程中的loss和梯度作平均。lossbefore = allmean(np.array(lossbefore))g = allmean(g)
其中GAE(Generalized advantage estimation)来源于论文《High-Dimensional Continuous Control Using Generalized Advantage Estimation》的第3节,用于advantage函数的估计:
A^tGAE(γ,λ):=∑l=0∞(γλ)lδt+lV\hat{A}^{GAE(\gamma, \lambda)}_t := \sum^{\infty}_{l=0} (\gamma\lambda)^l \delta^V_{t+l} A^tGAE(γ,λ):=l=0∑∞(γλ)lδt+lV
其中的参数lambda可以调节估计的variance和bias,默认为0.97。
接下来通过解最优化问题来优化参数。这里的优化问题是原问题的近似(目标用一阶近似,约束用二阶近似):
θk+1=argmaxθgT(θ−θk)s.t.12(θ−θk)TH(θ−θk)≤δ\theta_{k+1} = \arg\max_\theta g^T(\theta - \theta_k) \\ \mathrm{s.t.} \quad \frac{1}{2} (\theta - \theta_k)^T H (\theta - \theta_k) \leq \delta θk+1=argθmaxgT(θ−θk)s.t.21(θ−θk)TH(θ−θk)≤δ
然后分两步走:第一步用共轭梯度(Conjugate gradient)法确定参数更新方向;第二步在这个方向上确定合适的步长,来确定满足非线性约束。
# 第一步:通过conjugate gradient确定参数更新方向。stepdir = cg(fisher_vector_product, g, cg_iters=cg_iters, ...)# 第二步:确定步长,先算出最大步长。shs = .5*stepdir.dot(fisher_vector_product(stepdir))lm = np.sqrt(shs / max_kl)fullstep = stepdir / lm# 再通过line search方法确定参数。expectedimprove = g.dot(fullstep)surrbefore = lossbefore[0]stepsize = 1.0thbefore = get_flat()for _ in range(10):thnew = thbefore + fullstep * stepsizeset_from_flat(thnew)meanlosses = surr, kl, *_ = allmean(np.array(compute_losses(*args)))improve = surr - surrbeforeif not np.isfinite(meanlosses).all():...elif kl > max_kl * 1.5:...
第一步中stepdir为更新方向。lm为最大步长,fullstep为其和更新方向的乘积。但因为前面对目标函数作了近似,所以这里加入了一个乘子参数α\alphaα。总得来说,当conjugate gradient给出更新方向为sss的话,考虑约束的话就变成:
α2δsTHss\alpha \sqrt{\frac{2\delta}{s^T H s}} s αsTHs2δs
然后通过line search方法确定α\alphaα参数:迭代10步,每一步中先基于当前步长尝试更新策略参数,然后计算该策略下与前一次策略的KL距离,根据它与给定最大KL距离的阀值调整步长。
下面这部分是值函数参数的优化。这个没啥新鲜的,很多RL方法中都有,根据前面定义的对应loss函数(现值与累积回报的差平方)来更新值函数网络中的参数。
# 值函数优化for _ in range(vf_iters):for (mbob, mbret) in dataset.iterbatches((seg["ob"], seg["tdlamret"]), ...) # 更新策略的滑动均值/标准差pi.ob_rms.update(mbob)g = allmean(compute_vflossandgrad(mbob, mbret))vfadam.update(g, vf_stepsize)g_losses = meanlosses
下面看循环中训练discriminator过程,其实就是训练一个分类器让其分辨给定的状态-动作是否符合示教。
ob_expert, ac_expert = expert_dataset.get_next_batch(len(ob))batch_size = len(ob) // d_stepd_losses = []for ob_batch, ac_batch in dataset.iterbatches((ob, ac), ...)# 从示教数据集中抽出一批数据ob_expert, ac_expert = expert_dataset.get_next_batch(len(ob_batch))reward_giver.obs_rms.update(...)# 计算loss和相对于网络参数的梯度*newlosses, g = reward_giver.lossandgrad(ob_batch, ac_batch, ob_expert, ac_expert)# 用优化器更新参数d_adam.update(allmean(g), d_stepsize)d_losses.append(newlosses)
2. 评估
如果要评估前面的训练结果,可以指定task为evaluate:
# 假设训练模型放在/home/jzj/source/baselines/checkpoint/trpo_gail.transition_limitation_-1.Hopper.g_step_3.d_step_1.policy_entcoeff_0.adversary_entcoeff_0.001.seed_0/python3 -m baselines.gail.run_mujoco --task=evaluate --load_model_path=/home/jzj/source/baselines/checkpoint/trpo_gail.transition_limitation_-1.Hopper.g_step_3.d_step_1.policy_entcoeff_0.adversary_entcoeff_0.001.seed_0/trpo_gail.transition_limitation_-1.Hopper.g_step_3.d_step_1.policy_entcoeff_0.adversary_entcoeff_0.001.seed_0
在评估模式下,run_mujoco.py会调用runner()函数,最终给出平均的episode长度和累积回报,两者都是越高越好。
def runner(env, policy_func, load_model_path, ...):ob_space = env.observation_spaceac_space = env.action_space# 构建策略网络,并load给定的参数。pi = policy_func('pi', ob_space, ac_space, reuse=reuse)U.initialize()U.load_state(load_model_path)...# tqdm用于显示进度条。基于给定的策略在环境中rollout生成轨迹,默认为10条。for _ in tqdm(range(number_trajs)):# 产生一条轨迹traj = traj_1_generator(pi, env, ...)obs, acs, ep_len, ep_ret = traj['ob'], traj['ac'], ......# 计算episode的长度和累积回报的均值。avg_len = sum(len_list)/len(len_list)avg_ret = sum(ret_list)/len(ret_list)...
之前的训练我们每隔100次迭代保存一次模型,将每个模型对应的评估结果图形化出来:
可以看到,训练比较快就收敛了。为了更直观,在评估脚本中加上env.render()函数将gym环境渲染出来,可以看到从一开始的止步不前:
到100次迭代左右时的步履蹒跚:
到后来的箭步如飞:
小结
GAIL与之前IRL方法相比一大好处是模仿和示教数据的一致性测度无需人工设计。GAIL的论文发表于2年多前,虽然不算太前沿,但它可贵在挖了一个大坑,将当前最炙手可热的AI两大方向:RL和GAN结合在一起,这是一个让人很有想象空间的topic。后面有不少工作基于它作了改进和应用。像论文《Robust Imitation of Diverse Behaviors》是DeepMind对GAIL的改进。GAIL由于基于GAN,因此也继承了GAN的缺点-mode collapse。训练中模型趋向于只覆盖分布中的某些mode,这样就不能产生足够多样性的样本。而VAE的优势是能产生多样行为,因此这篇文章结合了两者的优点,来得到覆盖多样行为的鲁棒策略。其它像论文《Multi-Agent Generative Adversarial Imitation Learning》将之扩展到多智能体场景;论文《Reinforcement and Imitation Learning for Diverse Visuomotor Skills》基于GAIL改造用于从视觉输入模仿策略并用于真实控制场景。论文《Learning human behaviors from motion capture by adversarial imitation》基于GAIL改进,去除了演示数据中的动作要求,并拓展到演示者和学习体的结构和物理参数不一致的情况。相信以后这个方向上还会有很多相关的研究成果。