当前位置: 首页 > 资讯

【原创】千呼万唤始出来——TensorFlow Eager Execution 开启动态图模型时代

论智       2017-11-01

TensorFlow饱受诟病的痛点就是只支持静态图模型。也就是说,在处理数据前必须预先定义好一个完整的模型。如果数据非常规整,那还好。但实际工程和研究项目中的数据,难免有一些边角的情况。很多项目,其实需要大量实验才能选择正确的图模型。这就很痛苦了。因此,很多项目转而采用了PyTorch等支持动态图模型的框架,以便在运行程序的时候动态修正模型。


TensorFlow


2016年,Google Brain的Yaroslav Bulatov尝试解决TensorFlow的这个痛点,给TensorFlow加了命令式模式。不过,这个项目依赖TensorFlow的私有API,维护起来十分不易。


2017年10月31日万圣节,Google发布了TensorFlow Eager Execution(贪婪执行),为TensorFlow添加了命令式编程的接口。启用贪婪执行后,TensorFlow操作会立刻执行,不用通过Session.run()执行一个预先定义的图。


例子


传统写法的TensorFlow:


import tensorflow as tf


# 预先定义一个图模型

x = tf.placeholder(tf.float32, shape=[1, 1])

m = tf.matmul(x, x)


with tf.Session() as sess:

 # 运行

 print(sess.run(m, feed_dict={x: [[2.]]}))

贪婪执行加持下的TensorFlow:


import tensorflow as tf

import tensorflow.contrib.eager as tfe


# 开启贪婪执行模式

tfe.enable_eager_execution()


# 不用预先定义图,再运行,直接来。

x = [[2.]]

m = tf.matmul(x, x)


print(m)

可以看到,贪婪执行使TensorFlow的代码更接近一般的命令式编程风格。


类似地,迭代数据集中的元素的时候,也不再使用make_one_shot_iterator()和get_next()创建迭代器:


dataset = tf.data.Dataset.range(100)

iterator = dataset.make_one_shot_iterator()

next_element = iterator.get_next()


for i in range(100):

 value = sess.run(next_element)

 assert i == value

而是使用更符合Python风格的迭代器类:


dataset = tf.data.Dataset.from_tensor_slices([1, 2, 3, 4, 5, 6])


dataset = dataset.map(tf.square).shuffle(2).batch(2)

for x in tfe.Iterator(dataset):

 print(x)

安装


TensorFlow最新的发布版还没有包括贪婪执行,需要另外安装:


pip install tf-nightly-gpu

当然,也可以直接通过docker安装:(依赖nvidia-docker)


nvidia-docker pull tensorflow/tensorflow:nightly-gpu

nvidia-docker run -it -p 8888:8888 tensorflow/tensorflow:nightly-gpu

配合Numpy使用


贪婪执行支持自动转换TensorFlow的Tensor对象和Numpy数组:


import numpy as np

import tensorflow as tf

import tensorflow.contrib.eager as tfe


x = tf.add(1, 1)


# Numpy数组自动转Tensor

y = tf.add(np.array(1), np.array(1))


# Tensor自动转Numpy数组

z = np.multiply(x, y)

当然也可以显式地转换:(为免重复,以下省略import语句)


np_x = np.array(2., dtype=np.float32)

x = tf.constant(np_x)


py_y = 3.

y = tf.constant(py_y)


z = x + y + 1


print(z)

print(z.numpy())

自动微分


自动微分是实现很多机器学习算法的常用技术。TensorFlow贪婪执行为自动微分提供了autograd风格的API.


def f(x):

 return tf.multiply(x, x)

assert 9 == f(3.).numpy()


# tfe.gradients_function是一个高阶函数,

# 接受一个Python函数`f`作为输入,

# 返还一个求导函数。

df = tfe.gradients_function(f)

assert 6 == df(3.)[0].numpy()


# 二阶导数

d2f = tfe.gradients_function(lambda x: df(x)[0])

assert 2 == d2f(3.)[0].numpy()


# 三阶导数

d3f = tfe.gradients_function(lambda x : d2f(x)[0])

assert 0 == d3f(3.)[0].numpy()


# 自定义梯度

@tfe.custom_gradient

def log1pexp(x):

 e = tf.exp(x)

 def grad(dy):

   return dy * (1 - 1 / (1 + e))

 return tf.log(1 + e), grad

grad_log1pexp = tfe.gradients_function(log1pexp)


配合Keras使用


Keras封装了TensorFlow等框架,提供了高层次的抽象,方便快速定义模型结构。贪婪执行一样支持Keras:


# 一个训练MNIST模型的例子

class MNISTModel(object):

 def __init__(self, data_format):

   self._input_shape = [-1, 1, 28, 28]

   self.conv1 = tf.layers.Conv2D(32, 5,

                                 padding='same',

                                 activation=tf.nn.relu,

                                 data_format=data_format)

   self.max_pool2d = tf.layers.MaxPooling2D(

       (2, 2), (2, 2), padding='same', data_format=data_format)

   self.conv2 = tf.layers.Conv2D(64, 5,

                                 padding='same',

                                 activation=tf.nn.relu,

                                 data_format=data_format)

   self.dense1 = tf.layers.Dense(1024, activation=tf.nn.relu)

   self.dropout = tf.layers.Dropout(0.5)

   self.dense2 = tf.layers.Dense(10)


 def predict(self, inputs):

   x = tf.reshape(inputs, self._input_shape)

   x = self.max_pool2d(self.conv1(x))

   x = self.max_pool2d(self.conv2(x))

   x = tf.layers.flatten(x)

   x = self.dropout(self.dense1(x))

   return self.dense2(x)


def loss(model, inputs, targets):

 return tf.reduce_mean(

     tf.nn.softmax_cross_entropy_with_logits(

         logits=model.predict(inputs), labels=targets))



# 加载训练和验证数据集

from tensorflow.examples.tutorials.mnist import input_data

data = input_data.read_data_sets("./mnist_data", one_hot=True)


# 训练

device = "gpu:0" if tfe.num_gpus() else "cpu:0"

model = MNISTModel('channels_first' if tfe.num_gpus() else 'channels_last')

optimizer = tf.train.AdamOptimizer(learning_rate=1e-4)

grad = tfe.implicit_gradients(loss)

for i in range(20001):

 with tf.device(device):

   (inputs, targets) = data.train.next_batch(50)

   optimizer.apply_gradients(grad(model, inputs, targets))

   if i % 100 == 0:

     print("Step %d: Loss on training set : %f" %

           (i, loss(model, inputs, targets).numpy()))

print("Loss on test set: %f" % loss(model, data.test.images, data.test.labels).numpy())


持久化共享状态


贪婪执行的tfe.Variable可用于表示模型的持久化共享状态。搭配tfe.Saver类,可以储存和还原进度。


# 状态

x = tfe.Variable(10., name='x')

y = tfe.Variable(5., name='y')


# 初始化Saver类

saver = tfe.Saver([x, y])


# 改变状态并保存

x.assign(2.)

saver.save('/tmp/ckpt')


# 保存后又改变状态

x.assign(11.)

assert 16. == (x + y).numpy()  # 11 + 5


# 还原

saver.restore('/tmp/ckpt')


assert 7. == (x + y).numpy()  # 2 + 5

上一节例子中的MNISTModel类直接继承Object,实际项目中的模型推荐继承tfe.Network。继承tfe.Network类,可以很方便地追踪所有的模型变量,以及储存和还原进度。


配合TensorBoard使用


开发中常用TensorBoard来调试和优化模型训练。TensorFlow在构建图时提供的tf.summary操作可以配合TensorBoard的可视化功能。但是,tf.summary不兼容贪婪执行!不过,贪婪执行提供了替代品tf.contrib.summary。


tf.train.get_or_create_global_step()

writer = tf.contrib.summary.create_summary_file_writer(logdir)


for _ in range(iterations):

 with writer.as_default():

   with tf.contrib.summary.record_summaries_every_n_global_steps(100):

     # 省略具体的模型代码

     tf.contrib.summary.scalar('loss', loss)

     # ...

类似地,tf.metrics也不兼容贪婪执行。贪婪执行也提供了相应的替代品tfe.metrics:


my_mean = tfe.metrics.Mean(name='my_mean')


# 调用metric,更新其内部状态

my_mean(0.0)

my_mean(10.0)


# 完成更新后,获取结果。

# 这里的结果是所有调用的简单平均。

# 如果有配套的summary writer,

# metric会写入相应的总结。

assert 5.0 == my_mean.result().numpy()

pre-alpha


TensorFlow的贪婪执行目前尚处于pre-alpha阶段,API和性能都不稳定。尤其值得注意的是,贪婪执行尚不支持分布式训练和导出模型,TensorFlow的很多内存优化和计算优化也不适用于贪婪执行。在实际工程中运用贪婪执行可能还需要等待一段时间。


机器人网原创文章,未经授权禁止转载。详情见转载须知

本文来自机器人网,如若转载,请注明出处:https://www.jqr.com/news/008363