查看原文
其他

【他山之石】不会强化学习,只会numpy,能解决多难的RL问题?

“他山之石,可以攻玉”,站在巨人的肩膀才能看得更高,走得更远。在科研的道路上,更需借助东风才能更快前行。为此,我们特别搜集整理了一些实用的代码链接,数据集,软件,编程技巧等,开辟“他山之石”专栏,助你乘风破浪,一路奋勇向前,敬请关注。

作者:知乎—Firework

地址:https://www.zhihu.com/people/zhou-bo-54-20

强化学习能够解决的问题难度一直在提升,从入门级别的cartpole,到后来的Atari,Mujoco 一系列复杂环境,但不会深度学习框架,更别说实现PPO/IMPALA等复杂的算法,那还有希望继续解决这些很复杂的问题吗?答案是可以的---进化算法。

今天的主题是进化算法用于强化学习问题。进化算法(Evolution Strategies) 作为搜索算法一个大分支,最早是在上个世纪90年代提出来的,较为大家熟知的遗传算法就是其中的代表。以往的进化算法是直接作用于解的空间,直接寻找更优的决策方式,比如在解决经典的旅行商TSP问题过程中,直接搜索新的旅行轨迹。OpenAI[1]直接把这类算法应用在神经网络参数的搜索上,简单来说就是以下几个步骤:

  • 初始化神经网络参数

  • 在原始参数的基础上,增加多组噪声,得到多组新的神经网络参数

  • 在环境中评估每一组新参数的效果(跑一个episode看reward)

  • 根据reward来挑选出可以提升模型效果的噪声,作为模型更新参数的方向

以下这个图可以对ES算法的更新过程一目了然:

ES 算法更新过程

这个算法虽然简单,但是它的实际效果是不差于强化学习算法。OpenAI在多个benchmark验证了这个算法,从下图来看效果还是接近TRPO算法。
OpenAI论文中的实验结果


算法示例

从之前描述的ES算法更新过程来看,ES不需要像传统的机器学习算法一样,根据梯度进行更新。所以在没有自动求导梯度、在不依赖于Torch等神经网络框架的情况下,只会numpy,我们也可以解决Mujoco这类物理仿真的问题。接下来介绍下如何从0到1,通过numpy写ES算法,解决经典的cartpole问题。
首先需要实现一个可以预测、托管参数、更新参数的agent,主要的函数如下:
  • predict:根据当前状态预测observation
  • get_flat_weights: 获取当前参数,拍平成一个向量,用于增加噪声
  • set_flat_weights: 设定当前参数,一般传入增加噪声后的参数或者经过梯度更新后的参数。
  • learn:根据对于增加噪声后的参数的评估结果以及添加的噪声方向,计算出梯度更新参数。
class CartpoleAgent(object): def __init__(self, obs_dim, act_dim): # init weights self.w = np.random.random((act_dim, obs_dim)) * 0.1 self.b = np.zeros(act_dim) self.weights_total_size = self.w.size + self.b.size
def predict(self, obs): out = np.dot(self.w, obs) + self.b action = np.argmax(out) return action
def get_flat_weights(self): flat_weights = np.concatenate(([self.w.ravel(), self.b]), axis=0) return flat_weights
def set_flat_weights(self, flat_weights): self.w = flat_weights[:self.w.size].reshape(self.w.shape) self.b = flat_weights[self.w.size:]
def learn(self, rewards, noises): # 根据reward、noise计算梯度的加权求和,得出总的梯度更新方向 gradient = np.dot( np.asarray(rewards, dtype=np.float32), np.asarray(noises, dtype=np.float32)) gradient /= rewards.size
flat_weights = self.get_flat_weights() learning_rate = 0.1 # Compute the new weights. new_weights = flat_weights + learning_rate * gradient self.set_flat_weights(new_weights)
之后,整体的训练流程、代码如下:
  • 跑一百个epoch,每个epoch里面跑10次进化
  • 每次进化是通过增加一个均值为0、标准差为0.05的高斯噪声
  • 之后再评估每次进化的模型效果(直接跑一个episode测试下结果就好),记录添加的噪声以及相应的评估reward
  • 根据上一步记录的reward和noise
for epoch in range(100): rewards = [] noises = [] lastest_flat_weights = agent.get_flat_weights()
for episode in range(10): noise = np.random.randn(agent.weights_total_size) perturbation = noise * 0.05 # 产生均值为0,标准差为0.05的噪声
agent.set_flat_weights(lastest_flat_weights + perturbation) # 跑一个episode,评估下新参数下的效果 ep_reward = evaluate(env, agent)
noises.append(noise) rewards.append(ep_reward)
normalized_rewards = reward_normalize(rewards) agent.set_flat_weights(lastest_flat_weights) # 恢复进化前的参数 agent.learn(normalized_rewards, noises)# 根据进化噪声以及其结果,计算进化的梯度


代码以及效果

代码托管在github,可以直接copy下来,安装好numpy就可以跑了。几轮迭代后,我们可视化下结果:
https://github.com/PaddlePaddle/PARL/blob/develop/examples/others/deepes.py
参考
1. Evolution Strategies as a Scalable Alternative to Reinforcement Learning 
https://arxiv.org/pdf/1703.03864.pdf

本文目的在于学术交流,并不代表本公众号赞同其观点或对其内容真实性负责,版权归原作者所有,如有侵权请告知删除。


“他山之石”历史文章


更多他山之石专栏文章,

请点击文章底部“阅读原文”查看



分享、点赞、在看,给个三连击呗!

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存