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