关于TensorFlow 2.0,这里有你想知道的一切

1,111 阅读10分钟


全文共8094字,预计学习时长30分钟或更长

图片来源:pexels.com/@pixabay

TensorFlow是谷歌2015年开源的通用高性能计算库。最初主要是为构建神经网络(NNs)提供高性能的API。然而,随着时间的推移和机器学习(ML)社区的兴起,TensorFlow已经发展为一个完整的机器学习生态系统。

TensorFlow自诞生以来变化巨大。TensorFlow 2.0目前处于测试阶段,与TF1.x相比有了许多变化。下面是主要的几个变化。

1. 默认状态的Eager Execution

首先,eager execution是运行TF代码的默认方式。

为了在TF1.x中构建一个神经网络,需要定义一个名为图形(Graph)的抽象数据结构。另外,如果试图打印其中一个图节点,将看不到期望值,却可以看到对图节点的引用。实际上,要运行图形,需要使用一个名为会话(Session)的封装。使用Session.run()法,可以将Python数据传给图形,并对模型进行实际训练。

TF 1.x代码示例

利用eager execution可以进行改变。现在,TensorFlow代码可以像普通的Python代码一样运行。这意味着可以迅速创建并评估操作。

Tensorflow 2.0代码示例

TensorFlow 2.0代码看起来很像NumPy代码。事实上,TensorFlow和NumPy的对象可以很容易地切换。因此不必担心占位符、会话、feed_dictionaties等问题。

2. API清理

许多API,如tf.gans、tf.app、tf.contrib、tf.flags,会被清理或移动到单独的存储库。

然而,最重要的清理之一涉及到我们如何构建模型。TF1.x中有超过1或2种不同的构建/训练ML模型的方法。

tf.slim,tf.layers,tf.contrib.layers,tf.keras都是可以用来构建神经网络的API。不包括TF1.x中的序列到API序列。大多数情况下,每种情况下要选择哪一种尚不明确。

尽管许多API性能良好,但似乎并没有收敛出一种通用的开发方式。此外,如果在其中一个API中训练模型,那么使用其他API来再利用该代码并不简单。

在TF2.0中,tf.keras是推荐的高级API。

可以看到,目前正试图利用KerasAPI解决所有可能的用例。

3. 初学者API

与1.0版本相比,2.0版本中的初学者API变化不大。但是现在,Keras为默认和推荐的高级API。总之,Keras指的是一组图层,通过使用明确的标准来构建神经网络。使用pip安装TensorFlow时,一般会得到完整的Keras API和一些附加功能。

model= tf.keras.models.Sequential()
model.add(Flatten(input_shape=
(IMAGE_HEIGHT,IMAGE_WIDTH)))
model.add(Dense(units=32,activation='relu'))
model.add(Dropout(0.3))
model.add(Dense(units=32,activation='relu'))
model.add(Dense(units=10,activation='softmax')) 
#Configures the model for training.
#Define the model optimizer, the loss 
function and the accuracy metrics
model.compile(optimizer='adam',             
loss='sparse_categorical_crossentropy',
              metrics=['accuracy'])
model.summary()

初学者API称为序列化,一般会将神经网络定义为一组层。除了简单性,它还有其他优点。请注意,模型是根据数据结构(一组层)来定义的。因此,由于模型定义而产生错误的概率降至最低。

4. Keras-Tuner

Keras-tuner是一个对Keras模型进行超参数调整的专用库。在撰写本文时,该库处于α之前的状态,但是使用tf.keras和TensorFlow 2.0β可以在Colab上很好地运行。

传送门:https://github.com/keras-team/keras-tuner

这个概念非常简单。首先,需要定义一个返回编译后的Keras模型的建模函数。函数接受一个名为hp的参数作为输入。通过使用hp,可以定义一个候选值的范围,对超参数值进行采样。

下面建立了一个简单的模型,并对3个超参数进行了优化。为了查看隐藏单元,对预定义范围之间的整数值进行采样。为了计算丢失率和学习率,在一些给定值之间随机选择。

defbuild_model(hp): 
# define the hyper parameter ranges for 
thelearning rate, dropout and hidden unit  
hp_units = hp.Range('units', min_value=32,max_value=128, step=32)  
hp_lr = hp.Choice('learning_rate',values=[1e-2, 1e-3, 1e-4])  
hp_dropout = hp.Choice('dropout',values=[0.1,0.2,0.3])  
 
# build a Sequential model  
model = keras.Sequential() 
model.add(Flatten(input_shape=(IMAGE_HEIGHT,IMAGE_WIDTH)))  
model.add(Dense(units=hp_units,activation='relu'))  
model.add(Dropout(hp_dropout))  
model.add(Dense(units=32, activation='relu'))  
model.add(layers.Dense(10,activation='softmax'))   
# compile and return the model model.compile(optimizer=keras.optimize
rs.Adam(hp_lr),      
loss='sparse_categorical_crossentropy',     
metrics=['accuracy'])  

return model 
#create a Random Search tunertuner= RandomSearch(    
build_model,    
objective='val_accuracy', # define themetric to be optimized over    
max_trials=3,    
executions_per_trial=1,    
directory='my_logs') 
# define the outputlog/checkpoints folder  
# start hyper-parameter optmization searchtuner.search(x_train,y_train,             
epochs=2,             
validation_data=(x_test, y_test))

然后创建一个调谐器对象。在这种情况下,会实现随机搜索策略。最后可以使用search() 开始优化。它具有与 fit()相同的签名。

最后,可以检查调谐器的结论,并选择最佳模型。请注意,训练日志和模型检查点都保存在目录文件夹(my_logs)中。此外,最小化或最大化目标(验证精度)的选择是自动推断的。

5. 高级API

当你看到这种类型的implementation时,要追溯到面向对象编程。这里的模型是一个扩展tf.keras.Model的Python类。模型子类化是由Chainer提出的,与PyTorch如何定义模型有很大关联。

通过模型子类化来定义类构造函数中的模型层。call()负责正推法的定义和执行。

class Model(tf.keras.Model):  
def__init__(self):    
# Define thelayers here    
super(Model,self).__init__()    
self.conv1 =Conv2D(filters=8, kernel_size=4, padding="same", 
strides=1,input_shape=(IMAGE_HEIGHT,IMAGE_WIDTH,IMAGE_DEPTH))    
self.conv2 =Conv2D(filters=16, kernel_size=4, padding="same", strides=1)    
self.pool =MaxPool2D(pool_size=2, strides=2, padding="same")    
self.flat =Flatten()    
elf.probs =Dense(units=N_CLASSES, activation='softmax', name="output")   
def call(self,x):   

# Define theforward pass    
net =self.conv1(x)    
net =self.pool(net)    
net =self.conv2(net)    
net =self.pool(net)    
net =self.flat(net)    
net =self.probs(net)    
return net   

defcompute_output_shape(self, input_shape):    
# You need tooverride this function if you want to use the subclassed model    
# as part of afunctional-style model.    
# Otherwise,this method is optional.    
shape =tf.TensorShape(input_shape).as_list()    
shape[-1] =self.num_classes    
returntf.TensorShape(shape)

子类化优点很多:简化模型检查;(使用断点调试)在给定的行停止并检查模型的激活或逻辑。

然而,灵活性增加之后,随之而来的是更多的漏洞。

模型子类化需要程序员更多的关注和了解。

总之,代码的错误会更明显(如模型布线)。

6. 定义培训回路

在TF2.0中训练模型的最简便方法是使用 fit() 。fit()支持序列化模型和子类化模型。如果使用模型子类化,唯一需要调整的就是重写compute_output_shape()分类方法。除此之外,应该能够将fit()与tf.data.Dataset 或标准NumPynd-arrays一起输入。

但是,如果想清楚地了解梯度或丢失的情况,可以使用梯度带。这在研究中格外有效。

使用梯度带,就可以手动定义训练过程的每个步骤。可分别应用于训练神经网络的每个基本步骤,如:

· 正推法

· 损失函数求值

· 逆推法

· 梯度下降法

可以直观地了解神经网络是如何训练的。也可以直接输出损失值w.r.t、模型权重或梯度向量本身来进行检查。

梯度带提供了更大的灵活性。但是,就像子类化与序列化一样,灵活性越大,额外的成本越高。与fit()相比,需要手动定义一个训练回路。作为一个自然结果,它使代码的漏洞更加突出,并且更难调试。这是一个很好的折衷,和那些对开发新产品感兴趣的研究人员相比,更适合代码工程师(寻找标准化的代码)。

另外,使用fit() 可以简便地设置TensorBoard。

7. 设置TensorBoard

使用 fit()可以 简便地设置TensorBoard的实例。它也适用于Jupyter/Colab notebook。

添加TensorBoard,作为fit的回调函数。

fit()可以用于序列化API和子类化API。

# Load theTensorBoard notebook extension%load_exttensorboard 
# create thetensorboard callbacktensorboard= TensorBoard
(log_dir='logs/{}'.format(time.time()), histogram_freq=1) 
# train themodelmodel.fit(x=x_train,  
        
y=y_train,          
epochs=2,          
validation_data=(x_test, y_test),          
callbacks=[tensorboard]) 
# launchTensorBoard%tensorboard--logdir logs

如果选择使用模型子类化并自己编写训练回路(使用分级带),则还需要手动定义TensorBoard。包括使用 tf.summary.create_file_writer()创建摘要文件,并指定需要可视化的变量。

有许多回调函数可供使用,其中较为有效的是:

· EarlyStopping:顾名思义,它建立了一个规则——当监控量停止增加时,就停止培训。

· ReduceLROnPlateau:当测度停止增加时,降低学习速率。

· TerminateOnNaN:遇到NaN丢失时终止培训。

· LambdaCallback:用于创建简单的自定义联机回调。

可在 TensorFlow 2.0 callbacks查看完整目录。

传送门:https://www.tensorflow.org/versions/r2.0/api_docs/python/tf/keras/callbacks

8. EagerCode的提取性能

如果选择使用梯度带进行模型训练,性能会显著下降。

执行TF代码有助于理解,但性能不佳。为了避免这个问题,TF2.0引入了tf.function。

基本上,如果用tf.function修饰Python函数,TensorFlow会接收函数并将其转换为一个TF高性能抽象。

@tf.functiondeftrain_step(images, labels):   
with tf.GradientTape() as tape:    
# forward pass    
predictions = model(images)  
     
# compute the loss    
loss = cross_entropy(tf.one_hot(labels,N_CLASSES), predictions)   
# get the gradients w.r.t the model's weights  
gradients = tape.gradient(loss,model.trainable_variables)  
 
# perform a gradient descent step  
optimizer.apply_gradients(zip(gradients,model.trainable_variables))   
# accumulates the training loss and accuracy  train_loss(loss)  
train_accuracy(labels, predictions)

这意味着该函数将标记为JIT编辑,以便TensorFlow将其作为图形运行。因此,可以获得TF1.x(图形)的性能优势,如节点修剪、内核融合等。

简而言之,TF2.0旨在将代码设计为更小的函数。然后,可以使用tf.function对所需的代码进行标记以获得额外的性能。最适用于修饰表示最大计算瓶颈的函数。这些通常是训练回路或模型的正推。

注意,用tf.function修饰函数时,会失去eager execution的一些优势。也就是说,无法在该代码段中设置断点或使用print()。

9. 保存和修复模型

TF1.x对于如何保存/加载经过培训的模型以用于生产缺乏一致的标准。TF2.0试图通过定义单个API来解决这个问题。

TF2.0保存模型的方法不多,而是对一个称为SavedModel的抽象进行标准化。

这里无需多言。如果使用tf.keras.模型创建序列化模型或扩展类,则类将从tf.train.Checkpoints继承。因此,可以将模型序列化为SavedModel对象。

# serialize your model to a SavedModel object
# It includes the entire graph, all variables 
andweightsmodel.save('/tmp/model', save_format='tf') 
# load your saved 

modelmodel = tf.keras.
models.load_
model('/tmp/model')

SavedModel与Tensorflow生态系统结合到一起。换句话说,能够将其分配到许多不同的设备,包括移动电话、边缘设备和服务器。


10. 转换至TF-Lite

如果要将SavedModel分配到Raspberry Pi、Edge TPU或手机等嵌入式设备,请使用TF Lite转换器。

请注意,在2.0版本中中,TFLite转换器不支持冻结的GraphDef(通常在TF1.x中生成)。如果要将冻结的GraphDefs转换为在TF2.0中运行,可以使用tf.compat.v1.TFLite转换器。

在分配到嵌入式设备之前,一般会进行训练后量化。要使用TFLite转换器执行此操作,请将优化标志设置为“OPTIMIZE_FOR_SIZE”。这将把模型的权重从浮点量化到8位精度。可在不降低模型精度的情况下减小模型尺寸并改善延迟。

# create a TF Lite converter
converter 
=tf.lite.TFLiteConverter.from_keras_mode
l(model) 

# performs model quantization to reduce 
the size of themodel and improve latency

converter.optimizations 
=[tf.lite.Optimize.OPTIMIZE_FOR_SIZE] 
tflite_model = converter.convert()

请注意,这是一个实验性标志,可能会发生更改。

11. 转换至TensorFlow.js

如要关闭,可以使用相同的SavedModel对象并将其转换为TensorFlow.js格式。然后可使用JavaScript进行加载,并在浏览器上运行模型。

!tensorflowjs_converter\    
--input_format=tf_saved_model \    
--saved_model_tags=serve \    
--output_format=tfjs_graph_model \    
/tmp/model \    
/tmp/web_model

首先,需要通过pip安装TensorFlow.js。然后使用tensorflowjs_converter脚本获取训练模型并转换为Javascript兼容的代码。最后,可进行加载并用Javascript进行推断。

还可以在浏览器上使用Tesnorflow.js来训练模型。

结语

最后来介绍2.0版本的其他功能。首先,向序列化模型或子类化模型添加更多层是非常简单的。尽管TF覆盖了Conv2D、TransposeConv2D等层,但总会出现不可用的情况。对于论文再现或研究来说尤为如此。

好消息是,可以开发自定义层。通过遵循相同的Keras API,可以创建一个类并将其扩展到tf.keras.Layer。实际上,可以按照非常相似的模式创建自定义激活函数、正则化层或测度。

资源传送门:https://www.tensorflow.org/tutorials/eager/custom_layers

此外,还可以将现有的TensorFlow 1.x代码转换为TF2.0。为此,TF团队创建了tf_upgrade_v2。

此脚本不将TF1.x代码转换为2.0惯用代码。它基本上将tf.compat.v1模块用于更改名称空间的函数。另外,如果旧代码使用tf.contrib,将无法进行转换。可能需要使用其他库或缺失函数的新TF2.0版本。

留言 点赞 发个朋友圈

我们一起分享AI学习与发展的干货