小言_互联网的博客

AI又对游戏下手了,用强化学习通关超级马里奥兄弟

430人阅读  评论(0)

【飞桨开发者说】王子瑞,四川大学电气工程学院2018级自动化专业本科生,飞桨开发者技术专家PPDE,RoboMaster川大火锅战队成员,强化学习爱好者

 

超级马里奥兄弟作为几代人的童年回忆,陪伴了我们的成长。如今,随着深度强化学习的发展,越来越多的游戏已经被AI征服。今天,我们将以超级马里奥为例子,展示如何用深度强化学习试着通关游戏

 

马里奥游戏环境简介

 

游戏环境只给予3次机会通关,即玩家或AI需要在3次机会内通过游戏的32关。环境提供了 RIGHT_ONLY、SIMPLE_MOVEMENT和COMPLEX_MOVEMENT三种难度的操作模式。人们只需要对环境输入各种动作所代表的数值,就能实现对马里奥的各种操作。

 

 

马里奥游戏环境链接:

https://pypi.org/project/gym-super-mario-bros/

 

PPO算法简介

 

相信了解强化学习的各位一定听说过近端策略优化PPO算法吧。PPO算法是一种新型的 Policy Gradient算法,Policy Gradient算法对步长十分敏感。在训练过程中,若没有选择到合适的步长,新旧策略的变化可能会出现差异过大的现象,不利于模型的收敛。PPO提出了新的目标函数,可以在多个训练步骤中实现小幅度的更新,解决了Policy Gradient算法中步长难以确定的问题。

 

PPO算法论文链接:

https://arxiv.org/abs/1707.06347

 

 

基于飞桨框架2.0实现PPO

 

在此之前,我们先看看模型结构。模型是Actor-Critic结构,但是我们对模型结构做了一点简化,Actor和Critic只在输出层有所区别。由于模型处理的是图像信息,故我们在全连接层前加入了卷积层。下面就让我们用飞桨框架2.0实现PPO算法吧!

 


  
  1. class MARIO(Layer):
  2.      def __init__(self, input_num, actions):
  3.          super(MARIO,  self).__init_ _()
  4.          self.num_input = input_num
  5.          self.channels =  32
  6.          self.kernel =  3
  7.          self.stride =  2
  8.          self.padding =  1
  9.          self.fc =  32 *  6 *  6
  10.          self.conv 0 = Conv2D(out_channels= self.channels, 
  11.                                     kernel_size= self.kernel, 
  12.                                     stride= self.stride, 
  13.                                     padding= self.padding, 
  14.                                     dilation=[ 11], 
  15.                                     groups= 1
  16.                                     in_channels=input_num)
  17.          self.relu 0 = ReLU()
  18.          self.conv1 = Conv2D(out_channels= self.channels, 
  19.                                     kernel_size= self.kernel, 
  20.                                     stride= self.stride, 
  21.                                     padding= self.padding, 
  22.                                     dilation=[ 11], 
  23.                                     groups= 1
  24.                                     in_channels= self.channels)
  25.          self.relu1 = ReLU()
  26.          self.conv2 = Conv2D(out_channels= self.channels, 
  27.                                     kernel_size= self.kernel, 
  28.                                     stride= self.stride, 
  29.                                     padding= self.padding, 
  30.                                     dilation=[ 11], 
  31.                                     groups= 1
  32.                                     in_channels= self.channels)
  33.          self.relu2 = ReLU()
  34.          self.conv3 = Conv2D(out_channels= self.channels, 
  35.                                     kernel_size= self.kernel, 
  36.                                     stride= self.stride, 
  37.                                     padding= self.padding, 
  38.                                     dilation=[ 11], 
  39.                                     groups= 1
  40.                                     in_channels= self.channels)
  41.          self.relu3 = ReLU()
  42.          self.linear 0 = Linear(in_features=int( self.fc), out_features= 512)
  43.          self.linear1 = Linear(in_features= 512, out_features=actions)
  44.          self.linear2 = Linear(in_features= 512, out_features= 1)
  45.      def forward(self, x):
  46.         x = paddle.to_tensor(data=x)
  47.         x =  self.conv 0(x)
  48.         x =  self.relu 0(x)
  49.         x =  self.conv1(x)
  50.         x =  self.relu1(x)
  51.         x =  self.conv2(x)
  52.         x =  self.relu2(x)
  53.         x =  self.conv3(x)
  54.         x =  self.relu3(x)
  55.         x = paddle.reshape(x, [x.shape[ 0], - 1])
  56.         x =  self.linear 0(x)
  57.         logits =  self.linear1(x)
  58.         value =  self.linear2(x)
  59.          return logits, value

 

本文的PPO属于在线学习,大致分为以下三个模块:

  • 获取动作轨迹

  • 计算优势函数

  • 数据采样与模型参数更新

由于PPO是Policy Gradient算法,我们的智能体需要生成一个类别分布,即一个包含每个动作发生概率的向量。然后根据向量中的概率,选择我们的动作。最后与环境交互,并将返回的各种状态信息以及奖励存入列表当中备用。下面是获取动作轨迹模块的部分代码:

 


  
  1. for _  in range(num_local_steps):
  2.             logits, value = model(curr_states)
  3.             values.append( value.squeeze())
  4.             policy = F.softmax(logits, axis= 1)
  5.             old_m = Categorical(policy)  # 生成类别分布
  6.             action = old_m.sample([ 1])  # 采样
  7.             old_log_policy = old_m.log_prob(action)
  8.             old_log_policies.append(old_log_policy)
  9.             [ agent_conn.send(("step", act)) for agent_conn, act in zip(envs.agent_conns, action.numpy().astype("int8"))]
  10.             state, reward, done, info = zip(*[agent_conn.recv()  for agent_conn  in envs.agent_conns])

 

接着上面的代码,我们需要计算优势函数。具体来说,优势函数指当前状态s采用动作a的收益与当前状态s平均收益的差。优势越大,动作a收益就越高,同一状态下采用该动作的概率也就应该更高。

这里,我们用到了广义优势估计GAE(Generalized Advantage Estimator),几乎所有最先进的Policy Gradient算法实现都使用了该技术。这项技术主要用来修正我们Critic模型提供的价值,使其成为方差最小的无偏估计。

 


  
  1. for  value, reward,  done in list(zip(values, rewards, dones))[::-1]:
  2.             gae = gae * gamma * tau
  3.             gae = gae + reward + gamma * next_value.detach().numpy() * ( 1.0 - done) -  value.detach().numpy()
  4.             next_value =  value
  5.             R.append(paddle.to_tensor(gae +  value.detach().numpy()))
  6. advantages = R - values

 

最后,我们使用PPO算法更新模型参数。这里并没有计算KL散度,而是通过截断的方式实现小幅度的更新。

 


  
  1. for i in range(num_epochs):
  2.      indice = paddle.randperm(num_local_steps * num_processes)
  3.      for j in range(batch_size):
  4.          batch_indices = indice[
  5.                          int(j * (num_local_steps * num_processes / batch_size)): int((j +  1) * (
  6.                                  num_local_steps * num_processes / batch_size))]
  7.          logits, value = model(paddle.gather(states, batch_indices, axis= 0))
  8.          new_policy = F.softmax(logits, axis= 1)
  9.          new_m = Categorical(new_policy)
  10.          new_log_policy = new_m.log_prob(paddle.gather(actions, batch_indices, axis= 0))
  11.          ratio = paddle.exp(new_log_policy - paddle.gather(old_log_policies, batch_indices, axis= 0))
  12.          advantages = paddle.gather(advantages, batch_indices, axis= 0)
  13.          actor_loss = paddle.to_tensor(list((ratio * advantages).numpy() + (paddle.clip(ratio,  1. 0 - epsilon,  1. 0 + epsilon) * advantages).numpy()))
  14.          actor_loss = -paddle.mean(paddle.min(actor_loss, axis= 0))
  15.          critic_loss = F.smooth_l 1_loss(paddle.gather(R, batch_indices), value)
  16.          entropy_loss = paddle.mean(new_m.entropy())
  17.          total_loss = actor_loss + critic_loss - beta * entropy_loss
  18.          clip_grad = paddle.nn.ClipGradByNorm(clip_norm= 0. 25)
  19.          optimizer = paddle.optimizer.Adam(learning_rate=lr, parameters=model.parameters(), grad_clip=clip_grad)
  20.          optimizer.clear_grad()
  21.          total_loss.backward()
  22.          optimizer.step()

 

由于篇幅有限,此部分只呈现了简要思路与部分删减后的代码,感兴趣的同学可以直接查看源码。

 

通关小技巧

 

马里奥的通关小技巧有很多,这里主要给大家提供三个方向的思路:

  • 原始输入图像预处理

  • 奖励函数重设置

  • 多线程/并行训练

原始输入图像预处理:简化图像特征,叠合连续4帧图像作为输入,可以起到捕捉游戏环境的动态性的作用。

 

奖励函数重设置:不同的奖励函数所鼓励的行为是不同的,例如提高踩怪的奖励,就可以使模型更倾向于踩怪。本文重新分配了一下各种奖励的权重,对于通关也有更丰厚的额外奖励。

 


  
  1. def step(self, action):
  2.         state, reward, done, info =  self.env.step(action)
  3.          if  self. monitor:
  4.              self.monitor.record(state)
  5.         state = process_frame(state)
  6.         reward += (info[ "score"] -  self.curr_score) /  40.
  7.          self.curr_score = info[ "score"]
  8.          if  done:
  9.              if info[ "flag_get"]:
  10.                 reward +=  50
  11.              else:
  12.                 reward -=  50
  13.              self.env.reset()
  14.          return state, reward /  10., done, info

 

多线程/并行训练:并行化可以有效提高模型的训练效率,同时也是目前强化学习的趋势之一。本文通过 Python 的 multiprocess 模块实现并行化。

 


  
  1. class MultipleEnvironments:
  2.      def __init__(self, world, stage, action_type, num_envs, output_path=None):
  3.         self.agent_conns, self.env_conns = zip(*[mp.Pipe()  for _  in range(num_envs)])
  4.          '''选择操作模式
  5.         '''
  6.          if action_type ==  "right":
  7.             actions = RIGHT_ONLY
  8.          elif action_type ==  "simple":
  9.             actions = SIMPLE_MOVEMENT
  10.          else:
  11.             actions = COMPLEX_MOVEMENT
  12.          '''创建多环境
  13.         '''
  14.         self.envs = [create_train_env(world, stage, actions, output_path=output_path)  for _  in range(num_envs)]
  15.         self.num_states = self.envs[ 0].observation_space.shape[ 0]
  16.         self.num_actions = len(actions)
  17.          '''创建多进程
  18.         '''
  19.          for index  in range(num_envs):
  20.             process = mp.Process(target=self.run, args=(index,))
  21.             process.start()
  22.             self.env_conns[index].close()
  23.      def run(self, index):
  24.         self.agent_conns[index].close()
  25.          while  True:
  26.             request, action = self.env_conns[index].recv()
  27.              if request ==  "step":
  28.                 self.env_conns[index].send(self.envs[index].step(int(action)))
  29.              elif request ==  "reset":
  30.                 self.env_conns[index].send(self.envs[index].reset())
  31.              else:
  32.                  raise NotImplementedError

 

 

效果展示:本文以关卡1-1为例。目前多线程训练的马里奥已经正式通过测试。在 8 线程下,训练过程中我们的马里奥能够获得的Reward值变化趋势如下图所示。

 

 

最后,诚邀大家收看超级马里奥兄弟1-1通关全过程:

 

全文回顾

 

我们在这篇文章里,先简单介绍超级马里奥兄弟的游戏环境,然后补充一些与PPO算法有关的知识,并基于Paddle2.0进行实现了该算法。

 

除此之外,本文还总结了一些通关小技巧,并以游戏第一关为例,展示了训练过程。最后,我们向大家展现了训练完成后的效果。

 

训练代码项目链接:

https://aistudio.baidu.com/aistudio/projectdetail/1434971

通关展示项目链接:

https://aistudio.baidu.com/aistudio/projectdetail/1434950

 

2021年2月3日作者在飞桨PaddlePaddle B站直播间分享:《用深度强化学习轻松通关马里奥》

欢迎收看~ 哔哩哔哩直播,二次元弹幕直播平台 (bilibili.com)

 

 


转载:https://blog.csdn.net/PaddlePaddle/article/details/113604905
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场