作者 | Himanshu Rawlani
来源 | Medium
编辑 | 代码医生团队
谷歌于2019年3月6日和7日在其年度TensorFlow开发者峰会上发布了最新版本的TensorFlow机器学习框架。这一新版本使用TensorFlow的方式进行了重大改进。TensorFlow拥有最大的开发者社区之一,从机器学习库到完善的机器学习生态系统已经走过了漫长的道路。
TensorFlow 2.0中的所有新增内容及其教程均可在YouTube频道及其改版网站上找到。但是今天在本教程中,将介绍在TF 2.0中构建和部署图像分类器的端到端管道。本教程将有助于汇总此alpha版本中的一些新增内容。具体涵盖:
https://www.youtube.com/channel/UC0rqucBdTuFTjJiefW5t-IQ/playlists
本教程中的所有代码都可以在Jupyter笔记本中的GitHub存储库中找到。
https://github.com/himanshurawlani/practical_intro_to_tf2
在开始之前,需要使用以下命令安装TF nightly preview,其中包含TensorFlow 2.0 alpha版本:
$ pip install -U --pre tensorflow
1.使用TensorFlow数据集下载和预处理数据
TensorFlow数据集提供了一组可用于TensorFlow的数据集。它处理下载和准备数据并构建数据tf.data.Dataset。详细了解如何使用tf.Data此处加载图像数据集。首先通过pip安装TensorFlow Datasets python包:
https://www.tensorflow.org/alpha/tutorials/load_data/images
$ pip install tfds-nightly
下载数据集
有许多可用的数据集,也可以按照此处的指南添加自己的数据集。要列出可用的数据集,请执行以下python代码:
https://github.com/tensorflow/datasets/blob/master/docs/add_dataset.md
import tensorflow_datasets as tfds
print(tfds.list_builders())
在下载任何数据集之前,建议了解一些详细信息,例如数据集的功能和统计信息。在本教程中,将下载tf_flowers数据集,因此转到TensorFlow数据集网页并查找tf_flowers数据集。以下链接里的内容如下:
https://www.tensorflow.org/datasets/datasets
该tf_flowers数据集是218MB的,给了一个FeaturesDict对象,没有任何分割。由于tf_flowers没有定义任何标准分割,使用subsplit功能将其分别用于80%,10%,10%的数据进行训练,验证和测试。使用tfds.load()函数来下载数据集。指定as_supervised=True下载具有2元组结构的数据集(input, label)而不是FeaturesDict。传递with_info=True至tfds.load()。下面是python代码:
import tensorflow_datasets as tfds
SPLIT_WEIGHTS = (8, 1, 1)
splits = tfds.Split.TRAIN.subsplit(weighted=SPLIT_WEIGHTS)
(raw_train, raw_validation, raw_test), metadata = tfds.load(name="tf_flowers",
with_info=True,
split=list(splits),
# specifying batch_size=-1 will load full dataset in the memory
# batch_size=-1,
# as_supervised: `bool`, if `True`, the returned `tf.data.Dataset`
# will have a 2-tuple structure `(input, label)`
as_supervised=True)
预处理数据集
下载的数据集中的图像可以具有不同的尺寸。需要将所有图像的大小调整为给定的高度和宽度,并将像素值标准化为0到1之间的范围。这样做是因为为了训练卷积神经网络,必须指定输入维度。最终致密层的形状取决于CNN的输入尺寸。定义一个函数format_exmaple(),并把它传递到的地图功能raw_train,raw_validation以及raw_test对象。论点format_example()取决于传递给参数tfds.load()。具体而言,如果as_supervised=True再(image, labels)元组对将其他下载的钥匙一个字典image,并label会获得通过。
def format_example(image, label):
image = tf.cast(image, tf.float32)
# Normalize the pixel values
image = image / 255.0
# Resize the image
image = tf.image.resize(image, (IMG_SIZE, IMG_SIZE))
return image, label
train = raw_train.map(format_example)
validation = raw_validation.map(format_example)
test = raw_test.map(format_example)
清洗数据集,通过调用.shuffle(BUFFER_SIZE)的train对象来实现。设置与数据集一样大的shuffle缓冲区大小可确保数据完全清洗。然后通过调用创建一个大小为32批次.batch(BATCH_SIZE)上train,validation并test套。使用.prefetch()在模型训练时在后台获取批量数据集。
如果没有预取,CPU和GPU / TPU大部分时间都处于空闲状态
通过预取,空闲时间显着减少
这里有几点需要注意:
最后一点可以通过使tf.data.Dataset.apply()融合tf.data.experimental.shuffle_and_repeat()函数的方法来解决:
ds = image_label_ds.apply( tf.data.experimental.shuffle_and_repeat(buffer_size=image_count))
ds = ds.batch(BATCH_SIZE)
ds = ds.prefetch(buffer_size=AUTOTUNE)
执行数据扩充
数据增强是训练健壮的深度学习模型的重要技术。它可以防止过度拟合,并帮助模型理解数据集中类的独特功能。例如希望模型学会区分向日葵和郁金香,那么只学习花的颜色可能是不够的。希望模型能够了解花瓣的形状和相对大小,是否存在圆盘小花等。因此想要阻止模型使用颜色作为主要的区分参数,可以使用黑白照片或更改亮度参数。为了避免方向偏差,可以随机旋转数据集中的图像,依此类推。
在训练期间将这些数据增强实时应用于数据集非常有用,而不是手动创建这些图像并将其添加到数据集中。使用相同的map函数来应用不同的扩充:
def augment_data(image, label):
print("Augment data called!")
image = tf.image.random_flip_left_right(image)
image = tf.image.random_contrast(image, lower=0.0, upper=1.0)
# Add more augmentation of your choice
return image, label
train = train.map(augment_data)
可视化数据集
在图像数据集中发现异常/偏差的方法之一是通过可视化它的一些随机样本。特定类的图像在给定数据集中的变化/相似程度。获取数据集非常简单。可以使用train.take()方法批量获取数据集并将其转换为numpy数组,或者可以使用tfds.as_numpy(train)而不是train.take()直接获取numpy数组。
plt.figure(figsize=(12,12))
for batch in train.take(1):
for i in range(9):
image, label = batch[0][i], batch[1][i]
plt.subplot(3, 3, i+1)
plt.imshow(image.numpy())
plt.title(get_label_name(label.numpy()))
plt.grid(False)
# OR
for batch in tfds.as_numpy(train):
for i in range(9):
image, label = batch[0][i], batch[1][i]
plt.subplot(3, 3, i+1)
plt.imshow(image)
plt.title(get_label_name(label))
plt.grid(False)
# We need to break the loop else the outer loop
# will loop over all the batches in the training set
break
代码运行后,输出以下结果:
2.使用tf.keras构建一个简单的CNN
tf.keras是TensorFlow实现的Keras API规范。这是一个用于构建和训练模型的高级API,其中包括对TensorFlow特定功能的一流支持,例如动态图和tf.data管道。tf.keras使TensorFlow更易于使用而不会牺牲灵活性和性能。
下面的6行代码使用公共模式定义卷积网络:堆栈Conv2D和MaxPooling2D层。作为输入,CNN采用形状张量(image_height, image_width, color_channels),忽略批量大小。灰度图像具有一个颜色通道,而彩色图像具有三个(R,G,B)。对于数据集,将配置CNN以处理形状输入(128,128,3)。通过将参数传递shape给第一层来完成此操作。
为了完成模型,将最后的输出张量从卷积基(形状(28,28,64))馈送到一个或多个密集层中以执行分类。密集层将矢量作为输入(1D),而当前输出是3D张量。首先将3D输出展平(或展开)为1D,然后在顶部添加一个或多个Dense图层。数据集有5个类,从下载的数据集的元数据中获取该值。因此添加了一个带有5个输出和softmax激活的最终Dense层。
from tensorflow import keras
# Creating a simple CNN model in keras using functional API
def create_model():
img_inputs = keras.Input(shape=IMG_SHAPE)
conv_1 = keras.layers.Conv2D(32, (3, 3), activation='relu')(img_inputs)
maxpool_1 = keras.layers.MaxPooling2D((2, 2))(conv_1)
conv_2 = keras.layers.Conv2D(64, (3, 3), activation='relu')(maxpool_1)
maxpool_2 = keras.layers.MaxPooling2D((2, 2))(conv_2)
conv_3 = keras.layers.Conv2D(64, (3, 3), activation='relu')(maxpool_2)
flatten = keras.layers.Flatten()(conv_3)
dense_1 = keras.layers.Dense(64, activation='relu')(flatten)
output = keras.layers.Dense(metadata.features['label'].num_classes, activation='softmax')(dense_1)
model = keras.Model(inputs=img_inputs, outputs=output)
return model
上面的模型是使用Keras的Functional API创建的。然而在Keras中创建模型的另一种方法是使用Keras的Model Subclassing API,它遵循面向对象的结构来构建模型并定义它的前向传递。
编译和训练模型
在Keras中,编译模型只是将其配置为训练,即它设置在训练期间使用的优化器,损失函数和度量。为了训练给定数量的时期(数据集的迭代)的模型,.fit()在model对象上调用该函数。可以通过调用它们直接将train和validation对象传递给.fit()函数,.repeat()以便训练在指定数量的历元数据集上循环。在调用之前,.fit()需要计算一些要传递给它的参数:
# Calculating number of images in train, val and test sets
num_train, num_val, num_test = (
metadata.splits['train'].num_examples * weight/10
for weight in SPLIT_WEIGHTS
)
steps_per_epoch = round(num_train)//BATCH_SIZE
validation_steps = round(num_val)//BATCH_SIZE
在这里由于下载的数据集没有定义任何标准分割,使用8:1:1的subsplit比率来计算列车,验证和测试分割中的示例数量。
def train_model(model):
model.compile(optimizer='adam',
loss='sparse_categorical_crossentropy',
metrics=['accuracy'])
# Creating Keras callbacks
tensorboard_callback = keras.callbacks.TensorBoard(log_dir=log_dir, histogram_freq=1)
model_checkpoint_callback = keras.callbacks.ModelCheckpoint(
'training_checkpoints/weights.{epoch:02d}-{val_loss:.2f}.hdf5', period=5)
os.makedirs('training_checkpoints/', exist_ok=True)
early_stopping_checkpoint = keras.callbacks.EarlyStopping(patience=5)
history = model.fit(train.repeat(),
epochs=epochs,
steps_per_epoch=steps_per_epoch,
validation_data=validation.repeat(),
validation_steps=validation_steps,
callbacks=[tensorboard_callback,
model_checkpoint_callback,
early_stopping_checkpoint])
return history
可视化训练指标
绘制由例程train_model()或manually_train_model()例程返回的训练和验证度量。使用Matplotlib绘制图形:
训练和验证指标在训练在Keras的简单CNN的所有层之后
这些图表深入了解了模型的训练程度。有必要确保训练和验证准确度增加,损失减少。
TF2.0的另一个新功能是能够在Jupyter笔记本中使用功能齐全的TensorBoard。在开始模型训练之前启动TensorBoard,以便可以将指标视为模型训练。使用以下命令(确保logs/预先创建目录):
%load_ext tensorboard.notebook
%tensorboard --logdir logs/
TensorBoard里面有Jupyter笔记本
3.使用预先训练的网络
在上一节中,训练了一个简单的CNN,它给出了约70%的准确度。通过使用更大,更复杂的架构,可以轻松做得更好。有许多开源预训练网络可用于我们的类似图像分类任务。一个预先训练模型是以前训练的大型数据集,通常在大型图像分类任务保存的网络。既可以使用预先训练的模型,也可以使用预先训练过的convents进行迁移学习。迁移学习背后的直觉如果这个模型是在一个足够大且通用的数据集上训练的,那么这个模型将有效地作为视觉世界的通用模型。可以利用这些学到的特征映射,而无需在大型数据集上训练新的大型模型。
下载预先训练的模型
将从Google开发的InceptionV3模型中创建一个基础模型,并在ImageNet数据集上进行预训练,这是一个包含1.4M图像和1000类Web图像的大型数据集。该模型已经学习了每天看到的1000个对象中常见的基本功能。因此它具有强大的特征提取能力。通过指定include_top=False参数来下载顶部不包含分类层的网络,因为只想使用这些预训练的网络(卷积基础)的特征提取部分,因为它们可能是通用特征和学习图片上的概念。预训练模型的分类部分通常特定于原始分类任务,并且随后特定于训练模型的类集。
from tensorflow import keras
# Create the base model from the pre-trained model MobileNet V2
base_model = keras.applications.InceptionV3(input_shape=IMG_SHAPE,
# We cannot use the top classification layer of the pre-trained model as it contains 1000 classes.
# It also restricts our input dimensions to that which this model is trained on (default: 299x299)
include_top=False,
weights='imagenet')
此基本模型充当特征提取器,它将每个(128,128,3)输入图像转换为(2,2,280)特征块。可以将特征视为输入的一些多维表示,可以通过模型理解,并且有助于将输入图像分类为训练模型的许多类之一。
添加分类层
在下载预训练模型时,通过指定include_top=False参数删除了它的分类部分,因为它特定于训练模型的类集。现在添加一个新的分类层,它将特定于tf_flowers数据集。使用Keras的Sequential API将这些新图层堆叠在基础模型之上。
def build_model():
# Using Sequential API to stack up the layers
model = keras.Sequential([
base_model,
keras.layers.GlobalAveragePooling2D(),
keras.layers.Dense(metadata.features['label'].num_classes,
activation='softmax')
])
# Compile the model to configure training parameters
model.compile(optimizer='adam',
loss='sparse_categorical_crossentropy',
metrics=['accuracy'])
return model
inception_model = build_model()
代码非常明白:
在编译和训练模型之前冻结卷积基是很重要的,通过设置来实现base_model.trainable = False。通过冻结可以防止在训练期间更新基础模型中的权重。现在编译模型以使用训练参数对其进行配置。编译模型后,现在可以在鲜花数据集上进行训练。
训练分类层
使用与训练简单CNN相同的步骤训练模型。绘制了训练和验证指标。
训练分类负责预训练网络后的训练和验证指标
可以看到验证是准确性略高于训练准确性。这是一个好兆头,因为可以得出结论,模型在看不见的数据(验证集)上表现良好。可以通过使用测试集来评估模型来确认这一点。但是,仍然可以通过执行微调来改善此模型的性能。
微调预先训练好的网络
在上一步中,仅在Inception V3基础模型的基础上训练了几层。训练期间未预先更新预训练基础网络的权重。进一步提高性能的一种方法是与顶级分类器的训练一起“微调”预训练模型的顶层的权重。此训练过程将强制将基本模型权重从通用要素图调整为专门与数据集关联的要素。阅读更多这里官方TensorFlow网站上。
https://www.tensorflow.org/alpha/tutorials/images/transfer_learning#fine_tuning
下面的代码片段解冻了基本模型的各个层,以使其可训练。由于对模型进行了更改,因此需要在调用.fit函数之前重新编译模型。
# Un-freeze the top layers of the model
base_model.trainable = True
# Let's take a look to see how many layers are in the base model
print("Number of layers in the base model: ", len(base_model.layers))
# Fine tune from this layer onwards
fine_tune_at = 249
# Freeze all the layers before the `fine_tune_at` layer
for layer in base_model.layers[:fine_tune_at]:
layer.trainable = False
# Compile the model using a much lower learning rate.
inception_model.compile(optimizer = tf.keras.optimizers.RMSprop(lr=0.0001),
loss='sparse_categorical_crossentropy',
metrics=['accuracy'])
history_fine = inception_model.fit(train.repeat(),
steps_per_epoch = steps_per_epoch,
epochs = finetune_epochs,
initial_epoch = initial_epoch,
validation_data = validation.repeat(),
validation_steps = validation_steps)
微调的目标是使这些专用功能适应新数据集。如果之前接受过融合训练,这将使准确度提高几个百分点。但是如果训练数据集相当小,并且与初始数据集类似于Inception V3的训练,那么微调可能会导致过度拟合。在微调后再次绘制训练和验证指标。
注意:只有在训练顶级分类器并将预先训练的模型设置为不可训练后,才应尝试此操作。如果在预先训练的模型上添加一个随机初始化的分类器并尝试联合训练所有图层,则渐变更新的幅度将太大(由于分类器的随机权重),并且预训练模型将忘记它所学到的一切。
微调预先训练的网络后的训练和验证指标
训练和验证集的准确性都有所提高。虽然在第一个微调时代之后的损失确实飙升,但它最终还是下降了。造成这种情况的一个原因可能是权重可能比需要的更积极地更新。这就是为什么与分类层训练相比,保持较低的微调学习率非常重要。
4.使用TensorFlow服务提供模型
使用TensorFlow服务服务器,可以通过提供URL端点来部署训练有素的花卉图像分类模型,任何人都可以使用该端点发出POST请求,并且将获得模型推断的JSON响应,而不必担心其技术性。
安装TensorFlow服务
1.添加TensorFlow服务分发URI作为包源(一次性设置)
$ echo "deb [arch=amd64] http://storage.googleapis.com/tensorflow-serving-apt stable tensorflow-model-server tensorflow-model-server-universal" | sudo tee /etc/apt/sources.list.d/tensorflow-serving.list && \
$ curl https://storage.googleapis.com/tensorflow-serving-apt/tensorflow-serving.release.pub.gpg | sudo apt-key add -
2.安装和更新TensorFlow ModelServer
$ apt-get update && apt-get install tensorflow-model-server
安装后,可以使用该命令调用二进制文件$tensorflow_model_server。
将Keras模型导出为SavedModel格式
要将训练过的模型加载到TensorFlow服务器中,首先需要以SavedModel格式导出它。TensorFlow提供SavedModel格式作为导出模型的通用格式。在引擎盖下,Keras模型完全按照TensorFlow对象进行指定,因此可以将其导出得很好。
这将在定义良好的目录层次结构中创建protobuf文件,并包含版本号。TensorFlow Serving允许在制作推理请求时选择想要使用的模型版本或“可服务”版本。每个版本将导出到给定路径下的不同子目录。
from tensorflow import keras
# '/1' specifies the version of a model, or "servable" we want to export
path_to_saved_model = 'SavedModel/inceptionv3_128_tf_flowers/1'
# Saving the keras model in SavedModel format
keras.experimental.export_saved_model(inception_model, path_to_saved_model)
# Load the saved keras model back
restored_saved_model = keras.experimental.load_from_saved_model(path_to_saved_model)
启动TensorFlow服务器
要在本地计算机上启动TensorFlow服务器,请运行以下命令:
$ tensorflow_model_server --model_base_path=/home/ubuntu/Desktop/Medium/TF2.0/SavedModel/inceptionv3_128_tf_flowers/ --rest_api_port=9000 --model_name=FlowerClassifier
Failed to start server. Error: Invalid argument: Expected model ImageClassifier to have an absolute path or URI; got base_path()=./inceptionv3_128_tf_flowers
向TensorFlow服务器发出REST请求
TensorFlow ModelServer支持RESTful API。将一个预测请求作为POST发送到服务器的REST端点。但在发出POST请求之前,需要加载并预处理示例图像。TensorFlow服务服务器期望输入图像尺寸为(1,128,128,3),其中“1”是批量大小。使用Keras库中的图像预处理工具将输入图像加载并转换为所需的尺寸。
服务器的REST端点的URL遵循以下格式:
http://host:port/v1/models/${MODEL_NAME}[/versions/${MODEL_VERSION}]:predict
哪里/versions/${MODEL_VERSION}是可选的。以下代码加载并预处理输入图像,并使用上面的REST端点发出POST请求。
import json, requests
from tensorflow.keras.preprocessing.image import img_to_array, load_img
import numpy as np
image_path = 'sunflower.jpg'
# Loading and pre-processing our input image
img = image.img_to_array(image.load_img(image_path, target_size=(128, 128))) / 255.
img = np.expand_dims(img, axis=0)
payload = {"instances": img.tolist()}
# sending post request to TensorFlow Serving server
json_response = requests.post('http://localhost:9000/v1/models/FlowerClassifier:predict', json=payload)
pred = json.loads(json_response.content.decode('utf-8'))
# Decoding the response using decode_predictions() helper function
# You can pass "k=5" to get top 5 predicitons
get_top_k_predictions(pred, k=3)
上面的代码产生以下输出:
Top 3 predictions:
[('sunflowers', 0.978735), ('tulips', 0.0145516), ('roses', 0.00366251)]
结论
总结这里是在上面的教程中介绍的在TF2.0中构建和部署图像分类器的内容: