本文将围绕一个实际的问题进行介绍:应用强化学习的社区可以如何从对脚本和单个案例的收集更进一步,实现一个强化学习 API——一个用于强化学习的 tf-learn 或 skikit-learn?在讨论 TensorForce 框架之前,我们将谈一谈启发了这个项目的观察和思想。如果你只想了解这个 API,你可以跳过这一部分。我们要强调一下:这篇文章并不包含对深度强化学习本身的介绍,也没有提出什么新模型或谈论最新的最佳算法,因此对于纯研究者来说,这篇文章可能并不会那么有趣。
10多年的定日网站建设经验,针对设计、前端、开发、售后、文案、推广等六对一服务,响应快,48小时及时工作处理。全网整合营销推广的优势是能够根据用户设备显示端的尺寸不同,自动调整定日建站的显示方式,使网站能够适用不同显示终端,在浏览器中调整网站的宽度,无论在任何一种浏览器上浏览网站,都能展现优雅布局与设计,从而大程度地提升浏览体验。创新互联公司从事“定日网站设计”,“定日网站推广”以来,每个客户项目都认真落实执行。
假设你是计算机系统、自然语言处理或其它应用领域的研究者,你一定对强化学习有一些基本的了解,并且有兴趣将深度强化学习(deep RL)用来控制你的系统的某些方面。
对深度强化学习、DQN、vanilla 策略梯度、A3C 等介绍文章已经有很多了,比如 Karpathy 的文章(http://karpathy.github.io/2016/05/31/rl/)对策略梯度方法背后的直观思想就进行了很好的描述。另外,你也能找到很多可以帮助上手的代码,比如 OpenAI 上手智能体(https://github.com/openai/baselines)、rllab(https://github.com/openai/rllab)以及 GitHub 上许多特定的算法。
但是,我们发现在强化学习的研究框架开发和实际应用之间还存在一个巨大的鸿沟。在实际应用时,我们可能会面临如下的问题:
明确一下,这些问题并不是要批评研究者写的代码,因为这些代码本来就没打算被用作 API 或用于其它应用。在这里我们介绍的是想要将强化学习应用到不同领域中的研究者的观点。
TensorForce 提供了一种声明式接口,它是可以使用深度强化学习算法的稳健实现。在想要使用深度强化学习的应用中,它可以作为一个库使用,让用户无需担心所有底层的设计就能实验不同的配置和网络架构。我们完全了解当前的深度强化学习方法往往比较脆弱,而且需要大量的微调,但这并不意味着我们还不能为强化学习解决方案构建通用的软件基础设施。
TensorForce 并不是原始实现结果的集合,因为这不是研究模拟,要将原始实现用在实际环境的应用中还需要大量的工作。任何这样的框架都将不可避免地包含一些结构决策,这会使得非标准的事情变得更加恼人(抽象泄漏(leaky abstractions))。这就是为什么核心强化学习研究者可能更倾向于从头打造他们的模型的原因。使用 TensorForce,我们的目标是获取当前最佳研究的整体方向,包含其中的新兴见解和标准。
接下来,我们将深入到 TensorForce API 的各个基本方面,并讨论我们的设计选择。
创建和配置智能体
我们首先开始用 TensorForce API 创建一个强化学习智能体:
from tensorforce import Configuration from tensorforce.agents import DQNAgent from tensorforce.core.networks import layered_network_builder # Define a network builder from an ordered list of layers layers = [dict(type='dense', size=32), dict(type='dense', size=32)] network = layered_network_builder(layers_config=layers) # Define a state states = dict(shape=(10,), type='float') # Define an action (models internally assert whether # they support continuous and/or discrete control) actions = dict(continuous=False, num_actions=5) # The agent is configured with a single configuration object agent_config = Configuration( batch_size=8, learning_rate=0.001, memory_capacity=800, first_update=80, repeat_update=4, target_update_frequency=20, states=states, actions=actions, network=network ) agent = DQNAgent(config=agent_config)
这个示例中的状态和动作是更一般的状态/动作的短形式(short-form)。比如由一张图像和一个描述构成多模态输入按如下方式定义。类似地,也可以定义多输出动作。注意在整个代码中,单个状态/动作的短形式必须被持续不断地用于与智能体的通信。
states = dict( image=dict(shape=(64, 64, 3), type='float'), caption=dict(shape=(20,), type='int') )
配置参数依赖于所用的基本智能体和模型。每个智能体的完整参数列表可见于这个示例配置:https://github.com/reinforceio/tensorforce/tree/master/examples/configs
TensorForce 目前提供了以下强化学习算法:
最后一项的意思是说并没有 A3CAgent 这样的东西,因为 A3C 实际上描述的是一种异步更新的机制,而不是一种特定的智能体。因此,使用分布式 TensorFlow 的异步更新机制是通用 Model 基类的一部分,所有智能体都衍生于此。正如论文《Asynchronous Methods for Deep Reinforcement Learning》中描述的那样,A3C 是通过为 VPGAgent 设置 distributed flag 而隐含地实现的。应该指出,A3C 并不是对每种模型而言都是最优的分布式更新策略(对一些模型甚至完全没意义),我们将在本文结尾处讨论实现其它方法(比如 PAAC)。重要的一点是要在概念上将智能体和更新语义的问题与执行语义区分开。
我们还想谈谈模型(model)和智能体(agent)之间的区别。Agent 类定义了将强化学习作为 API 使用的接口,可以管理传入观察数据、预处理、探索等各种工作。其中两个关键方法是 agent.act(state) 和 agent.observe(reward, terminal)。agent.act(state) 返回一个动作,而 agent.observe(reward, terminal) 会根据智能体的机制更新模型,比如离策略记忆回放(MemoryAgent)或在策略批处理(BatchAgent)。注意,要让智能体的内在机制正确工作,必须交替调用这些函数。Model 类实现了核心强化学习算法,并通过 get_action 和 update 方法提供了必要的接口,智能体可以在相关点处内在地调用。比如说,DQNAgent 是一个带有 DQNModel 和额外一行(用于目标网络更新)的 MemoryAgent 智能体。
def observe(self, reward, terminal): super(DQNAgent, self).observe(reward, terminal) if self.timestep >= self.first_update \ and self.timestep % self.target_update_frequency == 0: self.model.update_target()
神经网络配置
强化学习的一个关键问题是设计有效的价值函数。在概念上讲,我们将模型看作是对更新机制的描述,这有别于实际更新的东西——在深度强化学习的例子中是指一个(或多个)神经网络。因此,模型中并没有硬编码的网络,而是根据配置不同的实例化。
在上面的例子中,我们通过编程创造了一个网络配置作为描述每一层的词典列表。这样的配置也可以通过 JSON 给出,然后使用一个效用函数将其变成一个网络构建器(network constructor)。这里给出了一个 JSON 网络规范的例子:
[ { "type": "conv2d", "size": 32, "window": 8, "stride": 4 }, { "type": "conv2d", "size": 64, "window": 4, "stride": 2 }, { "type": "flatten" }, { "type": "dense", "size": 512 } ]
和之前一样,这个配置必须被添加到该智能体的配置(configuration)对象中:
from tensorforce.core.networks import from_json agent_config = Configuration( ... network=from_json('configs/network_config.json') ... )
默认的激活层是 relu,但也还有其它激活函数可用(目前有 elu、selu、softmax、tanh 和 sigmoid)。此外也可以修改层的其它性质,比如,可以将一个稠密层(dense layer)改成这样:
[ { "type": "dense", "size": 64, "bias": false, "activation": "selu", "l2_regularization": 0.001 } ]
我们选择不使用已有的层实现(比如来自 tf.layers),从而能对内部运算施加明确的控制,并确保它们能与 TensorForce 的其余部分正确地整合在一起。我们想要避免对动态 wrapper 库的依赖,因此仅依赖于更低层的 TensorFlow 运算。
我们的 layer 库目前仅提供了非常少的基本层类型,但未来还会扩展。另外你也可以轻松整合你自己的层,下面给出了一个批规范化层的例子:
def batch_normalization(x, variance_epsilon=1e-6): mean, variance = tf.nn.moments(x, axes=tuple(range(x.shape.ndims - 1))) x = tf.nn.batch_normalization(x, mean=mean, variance=variance, variance_epsilon=variance_epsilon) return x
{ "type": "[YOUR_MODULE].batch_normalization", "variance_epsilon": 1e-9 }
到目前为止,我们已经给出了 TensorForce 创建分层网络的功能,即一个采用单一输入状态张量的网络,具有一个层的序列,可以得出一个输出张量。但是在某些案例中,可能需要或更适合偏离这样的层堆叠结构。最显著的情况是当要处理多个输入状态时,这是必需的,使用单个处理层序列无法自然地完成这一任务。
我们目前还没有为自动创建对应的网络构建器提供更高层的配置接口。因此,对于这样的案例,你必须通过编程来定义其网络构建器函数,并像之前一样将其加入到智能体配置中。比如之前的多模态输入(image 和 caption)例子,我们可以按以下方式定义一个网络:
def network_builder(inputs): image = inputs['image'] # 64x64x3-dim, float caption = inputs['caption'] # 20-dim, int with tf.variable_scope('cnn'): weights = tf.Variable(tf.random_normal(shape=(3, 3, 3, 16), stddev=0.01)) image = tf.nn.conv2d(image, filter=weights, strides=(1, 1, 1, 1)) image = tf.nn.relu(image) image = tf.nn.max_pool(image, ksize=(1, 2, 2, 1), strides=(1, 2, 2, 1)) weights = tf.Variable(tf.random_normal(shape=(3, 3, 16, 32), stddev=0.01)) image = tf.nn.conv2d(image, filter=weights, strides=(1, 1, 1, 1)) image = tf.nn.relu(image) image = tf.nn.max_pool(image, ksize=(1, 2, 2, 1), strides=(1, 2, 2, 1)) image = tf.reshape(image, shape=(-1, 16 * 16, 32)) image = tf.reduce_mean(image, axis=1) with tf.variable_scope('lstm'): weights = tf.Variable(tf.random_normal(shape=(30, 32), stddev=0.01)) caption = tf.nn.embedding_lookup(params=weights, ids=caption) lstm = tf.contrib.rnn.LSTMCell(num_units=64) caption, _ = tf.nn.dynamic_rnn(cell=lstm, inputs=caption, dtype=tf.float32) caption = tf.reduce_mean(caption, axis=1) return tf.multiply(image, caption) agent_config = Configuration( ... network=network_builder ... )
内部状态和 Episode 管理
和经典的监督学习设置(其中的实例和神经网络调用被认为是独立的)不同,强化学习一个 episode 中的时间步取决于之前的动作,并且还会影响后续的状态。因此除了其每个时间步的状态输入和动作输出,可以想象神经网络可能有内部状态在 episode 内的对应于每个时间步的输入/输出。下图展示了这种网络随时间的工作方式:
这些内部状态的管理(即在时间步之间前向传播它们和在开始新 episode 时重置它们)可以完全由 TensorForce 的 agent 和 model 类处理。注意这可以处理所有的相关用例(在 batch 之内一个 episode,在 batch 之内多个 episode,在 batch 之内没有终端的 episode)。到目前为止,LSTM 层类型利用了这个功能:
[ { "type": "dense", "size": 32 }, { "type": "lstm" } ]
在这个示例架构中,稠密层的输出被送入一个 LSTM cell,然后其得出该时间步的最终输出。当向前推进该 LSTM 一步时,其内部状态会获得更新并给出此处的内部状态输出。对于下一个时间步,网络会获得新状态输入及这个内部状态,然后将该 LSTM 又推进一步并输出实际输出和新的内部 LSTM 状态,如此继续……
对于带有内部状态的层的自定义实现,该函数不仅必须要返回该层的输出,而且还要返回一个内部状态输入占位符的列表、对应的内部状态输出张量和一个内部状态初始化张量列表(这些都长度相同,并且按这个顺序)。以下代码片段给出了我们的 LSTM 层实现(一个简化版本),并说明了带有内部状态的自定义层的定义方式:
def lstm(x): size = x.get_shape()[1].value internal_input = tf.placeholder(dtype=tf.float32, shape=(None, 2, size)) lstm = tf.contrib.rnn.LSTMCell(num_units=size) state = tf.contrib.rnn.LSTMStateTuple(internal_input[:, 0, :], internal_input[:, 1, :]) x, state = lstm(inputs=x, state=state) internal_output = tf.stack(values=(state.c, state.h), axis=1) internal_init = np.zeros(shape=(2, size)) return x, [internal_input], [internal_output], [internal_init]
预处理状态
我们可以定义被应用于这些状态(如果指定为列表的词典,则可能是多个状态)的预处理步骤,比如,为了对视觉输入进行下采样。下面的例子来自 Arcade Learning Environment 预处理器,大多数 DQN 实现都这么用:
config = Configuration( ... preprocessing=[ dict( type='image_resize', kwargs=dict(width=84, height=84) ), dict( type='grayscale' ), dict( type='center' ), dict( type='sequence', kwargs=dict( length=4 ) ) ] ... )
这个 stack 中的每一个预处理器都有一个类型,以及可选的 args 列表和/或 kwargs 词典。比如 sequence 预处理器会取最近的四个状态(即:帧)然后将它们堆叠起来以模拟马尔可夫属性。随便一提:在使用比如之前提及的 LSTM 层时,这显然不是必需的,因为 LSTM 层可以通过内部状态建模和交流时间依赖。
探索
探索可以在 configuration 对象中定义,其可被智能体应用到其模型决定所在的动作上(以处理多个动作,同样,会给出一个规范词典)。比如,为了使用 Ornstein-Uhlenbeck 探索以得到连续的动作输出,下面的规范会被添加到配置中。
config = Configuration( ... exploration=dict( type='OrnsteinUhlenbeckProcess', kwargs=dict( sigma=0.1, mu=0, theta=0.1 ) ) ... )
以下几行代码添加了一个用于离散动作的 epsilon 探索,它随时间衰减到最终值:
config = Configuration( ... exploration=dict( type='EpsilonDecay', kwargs=dict( epsilon=1, epsilon_final=0.01, epsilon_timesteps=1e6 ) ) ... )
用 Runner 效用函数使用智能体
让我们使用一个智能体,这个代码是在我们测试环境上运行的一个智能体:https://github.com/reinforceio/tensorforce/blob/master/tensorforce/environments/minimal_test.py,我们将其用于连续积分——一个为给定智能体/模型的工作方式验证行动、观察和更新机制的最小环境。注意我们所有的环境实现(OpenAI Gym、OpenAI Universe、DeepMind Lab)都使用了同一个接口,因此可以很直接地使用另一个环境运行测试。
Runner 效用函数可以促进一个智能体在一个环境上的运行过程。给定任意一个智能体和环境实例,它可以管理 episode 的数量,每个 episode 的最大长度、终止条件等。Runner 也可以接受 cluster_spec 参数,如果有这个参数,它可以管理分布式执行(TensorFlow supervisors/sessions/等等)。通过可选的 episode_finished 参数,你还可以周期性地报告结果,还能给出在最大 episode 数之前停止执行的指标。
environment = MinimalTest(continuous=False) network_config = [ dict(type='dense', size=32) ] agent_config = Configuration( batch_size=8, learning_rate=0.001, memory_capacity=800, first_update=80, repeat_update=4, target_update_frequency=20, states=environment.states, actions=environment.actions, network=layered_network_builder(network_config) ) agent = DQNAgent(config=agent_config) runner = Runner(agent=agent, environment=environment) def episode_finished(runner): if runner.episode % 100 == 0: print(sum(runner.episode_rewards[-100:]) / 100) return runner.episode < 100 \ or not all(reward >= 1.0 for reward in runner.episode_rewards[-100:]) runner.run(episodes=1000, episode_finished=episode_finished)
为了完整,我们明确给出了在一个环境上运行一个智能体的最小循环:
episode = 0 episode_rewards = list() while True: state = environment.reset() agent.reset() timestep = 0 episode_reward = 0 while True: action = agent.act(state=state) state, reward, terminal = environment.execute(action=action) agent.observe(reward=reward, terminal=terminal) timestep += 1 episode_reward += reward if terminal or timestep == max_timesteps: break episode += 1 episode_rewards.append(episode_reward) if all(reward >= 1.0 for reward in episode_rewards[-100:]) \ or episode == max_episodes: break
正如在引言中说的一样,在一个给定应用场景中使用 runner 类取决于流程控制。如果使用强化学习可以让我们合理地在 TensorForce 中查询状态信息(比如通过一个队列或网络服务)并返回动作(到另一个队列或服务),那么它可被用于实现环境接口,并因此可以使用(或扩展)runner 效用函数。
更常见的情况可能是将 TensorForce 用作驱动控制的外部应用库,因此无法提供一个环境句柄。对研究者来说,这可能无足轻重,但在计算机系统等领域,这是一个典型的部署问题,这也是大多数研究脚本只能用于模拟,而无法实际应用的根本原因。
另外值得提及的一点是声明式的中心配置对象使得我们可以直接用超参数优化为强化学习模型的所有组件配置接口,尤其还有网络架构。
进一步思考
我们希望你能发现 TensorForce 很有用。到目前为止,我们的重点还是让架构先就位,我们认为这能让我们更持续一致地实现不同的强化学习概念和新的方法,并且避免探索新领域中的深度强化学习用例的不便。
在这样一个快速发展的领域,要决定在实际的库中包含哪些功能是很困难的。现在的算法和概念是非常多的,而且看起来在 Arcade Learning Environment (ALE) 环境的一个子集上,每周都有新想法得到更好的结果。但也有一个问题存在:许多想法都只在易于并行化或有特定 episode 结构的环境中才有效——对于环境属性以及它们与不同方法的关系,我们还没有一个准确的概念。但是,我们能看到一些明显的趋势:
总的来说,我们正在跟踪这些发展,并且将会将此前错过的已有技术(应该有很多)纳入进来;而一旦我们相信一种新想法有变成稳健的标准方法的潜力,我们也会将其纳入进来。在这个意义上,我们并没有与研究框架构成明确的竞争,而是更高程度的覆盖。
本文转自机器之心,原文来自reinforce,作者Michael Schaarschmidt、Alexander Kuhnle、Kai Fricke。
当前题目:基于TensorFlow打造强化学习API:TensorForce是怎样炼成的?
URL地址:http://www.mswzjz.cn/qtweb/news48/496448.html
攀枝花网站建设、攀枝花网站运维推广公司-贝锐智能,是专注品牌与效果的网络营销公司;服务项目有等
声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 贝锐智能