Tensorflow使用中的一些小技巧
Tensorflow 高阶API架构图
总结出来有如下几个方面:
- 用Eager模式搭建原型
- 用Datasets处理数据
- 用Feature Columns提取特征
- 用Keras搭建模型
- 借用Canned Estimators
- 用SavedModel打包模型
- 模型函数
- 用Tensorflow Serving发布模型
- Layers
- Head
下面我们依次来介绍。
用Eager模式搭建原型
作为计算机界的一份子,我们知道静态图的效率自然是快,但是动态图的使用为我们的使用带来的很多方便。2017年的时候,各大框架动态图大行其道,于是Google提出了tf.contrib.eager应对挑战。
使用Eager有什么好处呢?回想之前我们在调试tensorflow的程序时,不得不使用sess.run(),麻烦的要死,而使用Eager就可以直接的将变量打印出来,大大方便了我们的调试;好处不止这么多,在进行模型搭建的时候,以前我们需要仔细考虑下Tensor的shape,一旦出错要定位也很不容易。而使用Eager可以一边搭建网络结构,一边将shape打印出来确认下是否正确。这就使我们在搭建网络时更加方面快捷了;此外,使用Eager后,自定义Operation和Gradient也会方便很多。
下面举个简单的小例子。首先使用pip install tf-nightly(或GPU版本pip install tf-nightly-gpu)来安装Eager。
import tensorflow as tf
import tensorflow.contrib.eager as tfe
tfe.enable_eager_execution() #开启Eager模式
a = tf.constant([5], dtype=tf.int32)
for i in range(a):
print (i)
使用Eager后我们可以很顺利的执行上述代码。但是如果没有Eager,就会报Tensor对象不能解释为integer的错误。从缺点上来讲,Eager的引入也势必造成额外的成本。
用Datasets处理数据
tensorflow的数据读入有三种方式:通过feeding的方式;通过管道(pipeline)的方式;直接读取变量或常量中保存的数据。
Datasets属于上面提出的第二种方式,可以简化数据输入过程,而且能够提高数据的读入效率。
Datasets的组成如上如所示。其中:
- Dataset:创建和转换数据集的基本;
- TextLineDataset:从文本文件中读取行;
- TFRecordDataset:读取TFRecord文件;
- FixedLengthRecordDataset:从二进制文件读取固定大小的记录;
- Iterator:提供一种一次访问一个数据集元素的方法。
对于Datasets的使用,我们可以使用Dataset的子类提供的方法,也可以直接使用基类的方法:tf.data.Dataset.from_tensors()或者tf.data.Dataset.from_tensor_slices()。
用Feature Columns提取特征
Feature Columns实际上是一个数据结构,一个用于描述特征的数据结构。利用Feature Columns可以很方便的对输入训练模型前的特征进行处理。比如鸢尾花的识别,对于输入数据,每列表示不同的特征,如花瓣的长度,花萼的长度等等,我们想要对不同的列分别进行处理(或者对所有的列进行处理),使用Feature Columns就可以轻松的实现。
如上图所示,Feature Columns形成了对输入数据集的结构性描述。可以方便我们对每列数据进行处理,而且使得代码的可读性更强。
用Keras搭建模型
simple_model=Sequential()
simple_model.add(Dense(3,input_shape=x.shape[1],),activation='relu',name='layer1'))
simple_model.add(Dense(5,activation='relu',name='layer2'))
simple_model.add(Dense(1,activation='sigmoid',name='layer3'))
构建一个模型就是如上面这么简单,而且调用API中定义好的模型更是只需要一句话,极其的方便。
使用Canned Estimators
Estimators API提供了模型选择、评估、训练等一些列功能。其设计灵感来自于典典大名的Python机器学习库Scikit-learn。Estimator允许开发者自定义任意的模型结构、损失函数、优化方法以及如何对这个模型进行训练、评估和导出等内容,同时屏蔽了与底层硬件设备、分布式网络数据传输等相关的细节。
tf.estimator.Estimator(
model_fn=model_fn, # First-class function
params=params, # HParams
config=run_config # RunConfig
)
要创建Estimator,需要传入一个模型函数、一组参数和一些配置。
- 传入的参数应该是模型超参数的一个集合,可以是一个dictionary。
- 传入的配置用于指定模型如何运行训练和评估,以及在哪里存储结果。这个配置是一个RunConfig对象,该对象会把模型运行环境相关的信息告诉Estimator。
- 模型函数是一个Python函数,它根据给定的输入构建模型。
Estimator类有三个主要的方法:train/fit、evaluate、predict,分别表示模型的训练、评估和预测。三个方法都接受一个用户自定义的输入函数input_fn,执行input_fn获取输入数据。Estimator的这三个方法最终都会调用模型函数(model_fn)执行具体的操作,不同方法被调用时,传递给model_fn的mode参数也是不同的,如下一小节中描述的那样,mode参数是让用户在编写模型函数时知道当前定义的操作是用在模型生命周期的哪一个阶段。
在1.3版本后,Google又增加了一层,称之为Canned Estimators。只需要一行代码就能够创建深度模型。Estimators可以结合上面提到的Feature Columns一起使用。
tf.estimator.Estimator是基类;Pre-made Estimators是基类的子类,是已经定义好的模型,我们可以直接拿来使用;Custom Estimators是基类的实列,并不是定义好的,需要我们自己实现模型的定义。
对于这里的模型,由三部分组成:
- Input function:输入函数,即我们前面所说的Datasets,对于数据进行表示;
- Model function: 实验模型的训练、验证、测试以及监控模型的参数;
- Estimators: 控制数据流以及模型的各种运算。
用SavedModel打包模型
相比于tensorflow原版的tf.train.Saver保存模型的方式,SavedModel提供了更好的将模型部署到生成环境的手段,更适用于商业目的。
如上图右下方部分,在使用SavedModel打包模型时,可以产生两种模型:
对应于第一种模型,Tensorflow Model Analysis可以方便我们对模型进行分析,是不是存在参数的问题,抑或是模型哪里设计的不合适等等;通过分析后,感觉模型不错,我们就可以通过Tensorflow Serving进行部署。
此外,相比于Saver的方式,我们在inference时不需要再重新定义Graph(模型),如果使用Saver的话,在使用该模型时就需要再定义该模型,如果是一个程序猿设计并使用的还好,如果换成另一个猿去用这个模型,他又不知道模型的tensor的情况,那就尴尬了。所以使用SavedModel可以让我们更轻松地去使用模型。
模型函数
模型函数是用户自定义的一个python函数,它定义了模型训练、评估和预测所需的计算图节点(op)。
模型函数接受输入特征和标签作为参数,同时用mode参数来告知用户模型是在训练、评估或是在执行推理。mode是tf.estimator.ModeKeys对象,它有三个可取的值:TRAIN、EVAL、PREDICT。模型函数的最后一个参数是超参数集合,它们与传递给Estimator的超参数集合相同。模型函数返回一个EstimatorSpec对象,该对象定义了一个完整的模型。EstimatorSpec对象用于对操作进行预测、损失、训练和评估,因此,它定义了一个用于训练、评估和推理的完整的模型图。
一个简单的模型函数示例如下:
def model_fn(features, target, mode, params)
predictions = tf.stack(tf.fully_connected, [50, 50, 1])
loss = tf.losses.mean_squared_error(target, predictions)
train_op = tf.train.create_train_op(
loss, tf.train.get_global_step(),
params[’learning_rate’], params[’optimizer’])
return EstimatorSpec(mode=mode,
predictions=predictions,
loss=loss,
train_op=train_op)
Layers
Layer是一组简单的可重复利用的代码,表示神经网络模型中的“层”这个概念。Tensorflow中的layer可以认为是一系列操作(op)的集合,与op一样也是输入tensor并输出tensor的(tensor-in-tensor-out)。Tensorflow中即内置了全连接这样的简单layer,也有像inception网络那样的复杂layer。使用layers来搭建网络模型会更加方便。
Head
Head API对网络最后一个隐藏层之后的部分进行了抽象,它的主要设计目标是简化模型函数(model_fn)的编写。Head知道如何计算损失(loss)、评估度量标准(metric)、预测结果(prediction)。为了支持不同的模型,Head接受logits和labels作为参数,并生成表示loss、metric和prediction的张量。有时为了避免计算完整的logit张量,Head也接受最后一个隐藏的激活值作为输入。
一个使用Head简化model_fn编写的例子如下:
def model_fn(features, target, mode, params):
last_layer = tf.stack(tf.fully_connected, [50, 50])
head = tf.multi_class_head(n_classes=10)
return head.create_estimator_spec(
features,
mode,
last_layer,
label=target,
train_op_fn=lambda loss: my_optimizer.minimize(loss, tf.train.get_global_step())
我们也可以用一个Heads列表来创建一个特殊类型的Head,来完成多目标学习的任务,如下面的例子那样。
def model_fn(features, target, mode, params):
last_layer = tf.stack(tf.fully_connected, [50, 50])
head1 = tf.multi_class_head(n_classes=2,label_name=’y’, head_name=’h1’)
head2 = tf.multi_class_head(n_classes=10,label_name=’z’, head_name=’h2’)
head = tf.multi_head([head1, head2])
return head.create_model_fn_ops(
features,
mode,
last_layer,
label=target,
train_op_fn=lambda loss: my_optimizer.minimize(loss, tf.train.get_global_step())
TensorFlow Serving
使用TensorFlow进行模型的训练、验证和预测,但模型完善之后的生产上线流程,就变得五花八门了。针对这种情况Google提供了TensorFlow Servering,可以将训练好的模型直接上线并提供服务。在2017年的TensorFlow开发者Summit上便提出了TensorFlow Serving。
但那时候客户端和服务端的通信只支持gRPC。在实际的生产环境中比较广泛使用的C/S通信手段是基于RESTfull API的,幸运的是从TF1.8以后,TF Serving也正式支持RESTfull API通信方式了。
TFS服务框架
基于TF Serving的持续集成框架还是挺简明的,基本分三个步骤:
- 模型训练
这是大家最熟悉的部分,主要包括数据的收集和清洗、模型的训练、评测和优化; - 模型上线
前一个步骤训练好的模型在TF Server中上线; - 服务使用
客户端通过gRPC和RESTfull API两种方式同TF Servering端进行通信,并获取服务;
TF Serving工作流程
TF Serving的工作流程主要分为以下几个步骤:
- Source会针对需要进行加载的模型创建一个Loader,Loader中会包含要加载模型的全部信息;
- Source通知Manager有新的模型需要进行加载;
- Manager通过版本管理策略(Version Policy)来确定哪些模型需要被下架,哪些模型需要被加载;
- Manger在确认需要加载的模型符合加载策略,便通知Loader来加载最新的模型;
- 客户端像服务端请求模型结果时,可以指定模型的版本,也可以使用最新模型的结果;