Keras 是一種高級神經網絡接口,可以在多個后端上運行。其函數式 API 非常人性化且頗具靈活性,可構建各種應用。一經推出,Keras 便迅速受到青睞。2017 年,Keras API 以 tf.keras 的形式實現與核心 TensorFlow 的集成。雖然 tf.keras 和 Keras 擁有獨立代碼庫,但彼此之間緊密耦合。自 TensorFlow 1.9 起發布更新文檔和編程人員指南以來,tf.keras 顯然成為以 TensorFlow 構建神經網絡時要使用的高級 API。
注:更新文檔鏈接
https://www.tensorflow.org/tutorials/
編程人員指南鏈接
https://www.tensorflow.org/guide/keras
在這篇文章中,我們會介紹使用 tf.keras訓練、導出及提供神經網絡的整個流程。例如,我們將使用 Kaggle Planet 數據集訓練卷積神經網絡,以預測亞馬遜森林衛星圖像的標簽。我們的目的是說明真實用例的端到端管道。相關代碼可通過github上的可運行筆記本獲取。請注意,您將需要安裝最新版本的 TensorFlow(1.11.0,每日構建版),以根據指示完成全部操作。這只是 pip 安裝,且 requirements.txt 文件會在存儲區中提供。或者,您也可以通過Google Colab立即運行內容!
注:github 鏈接
https://github.com/sdcubber/keras-training-serving/blob/master/training-and-serving-with-tf-keras.ipynb
Google Colab 鏈接
https://colab.research.google.com/github/sdcubber/keras-training-serving/blob/master/training-and-serving-with-tf-keras.ipynb
您可在Kaggle下載數據。訓練數據由大約 40000 張帶標簽的亞馬遜雨林圖像組成,每張圖像均與多個標簽關聯:
注:Kaggle 鏈接
https://www.kaggle.com/c/planet-understanding-the-amazon-from-space/data
只有一個 “天氣” 標簽:晴朗、薄霧、多云或局部多云
一個或多個 “地面” 標簽:農田、裸地、住宅、道路、水域……
天氣標簽:多云 地面標簽:原始森林,道路
Pandas DataFrame 包含圖像名稱列、天氣標簽列和地面標簽列,而系統會將這些數據編碼為二進制向量。您可前往github以 .csv 文件的形式獲取相關內容:
注:github 鏈接
https://github.com/sdcubber/keras-training-serving/blob/master/KagglePlanetMCML.csv
我們希望訓練出的模型能夠準確預測新圖像的這些標簽。為此,我們會嘗試使用針對天氣和地面標簽提供兩種獨立輸出的網絡。預測天氣標簽是多類別分類問題的一個例子,而地面標簽可以建模為多標簽分類問題。因此,兩種輸出的損失函數有所不同。訓練模型后,我們會使用TensorFlow Serving導出和提供模型,,如此一來,我們就可以通過 HTTP 發送請求以獲得圖像的預測結果。
指定模型
我們將從頭開始構建自己的模型1。我們會采用十分經典的配置,包括一些卷積層、ReLU 激活函數和兩個位于頂層的密集分類器:
1import tensorflow as tf
2IM_SIZE = 128
3
4image_input = tf.keras.Input(shape=(IM_SIZE, IM_SIZE, 3), name='input_layer')
5
6# Some convolutional layers
7conv_1 = tf.keras.layers.Conv2D(32,
8kernel_size=(3, 3),
9padding='same',
10activation='relu')(image_input)
11conv_1 = tf.keras.layers.MaxPooling2D(padding='same')(conv_1)
12conv_2 = tf.keras.layers.Conv2D(32,
13kernel_size=(3, 3),
14padding='same',
15activation='relu')(conv_1)
16conv_2 = tf.keras.layers.MaxPooling2D(padding='same')(conv_2)
17
18# Flatten the output of the convolutional layers
19conv_flat = tf.keras.layers.Flatten()(conv_2)
20
21# Some dense layers with two separate outputs
22fc_1 = tf.keras.layers.Dense(128,
23activation='relu')(conv_flat)
24fc_1 = tf.keras.layers.Dropout(0.2)(fc_1)
25fc_2 = tf.keras.layers.Dense(128,
26activation='relu')(fc_1)
27fc_2 = tf.keras.layers.Dropout(0.2)(fc_2)
28
29# Output layers: separate outputs for the weather and the ground labels
30weather_output = tf.keras.layers.Dense(4,
31activation='softmax',
32name='weather')(fc_2)
33ground_output = tf.keras.layers.Dense(13,
34activation='sigmoid',
35 name='ground')(fc_2)
36
37# Wrap in a Model
38model = tf.keras.Model(inputs=image_input, outputs=[weather_output, ground_output])
我們有兩個輸出層,因此在指定模型時應將這些層以輸出列表的形式傳遞。請注意,天氣和地面輸出層的激活函數并不相同。很方便的是,Model 實現 tf.keras 時會采用簡便的 summary() 方法:
編譯模型時,系統會以字典的形式提供兩個不同的損失函數,而此字典會將張量名稱映射到損失:
1model.compile(optimizer='adam',
2loss={'weather': 'categorical_crossentropy',
3'ground': 'binary_crossentropy'})
編譯模型時,系統會以隨機權重對其進行初始化,并允許我們選擇優化算法來訓練網絡。
[1] 正如在 Kaggle 競賽中所證明的那樣,通過大型預訓練網絡進行遷移學習是取得成功的一大關鍵。但這里的重點并不是在 Kaggle 中獲勝。如需獲取有關如何實現絕佳性能的提示,請觀看介紹如何處理此數據集的精彩 fast.ai課程。
注:如何處理此數據集的精彩 fast.ai 鏈接
http://course.fast.ai/lessons/lesson3.html
模型訓練
我們開始訓練模型吧!我會在我的筆記本電腦上訓練此模型,但這臺電腦的內存不夠,無法存儲整個數據集。處理圖像數據時經常會出現這種情況。Keras 提供 model.fit_generator() 方法,而該方法可以使用自定義 Python 生成器從磁盤生成圖像以進行訓練。不過,從 Keras 2.0.6 開始,我們可以使用 Sequence 對象(而不是生成器)實現安全的多進程處理,這意味著您可以顯著提升運行速度并降低 GPU(如果您有)遇到瓶頸的風險。Keras 文檔已經提供出色的示例代碼,我會稍微自定義一下,以實現下列目的:
讓其使用將圖像名稱映射到標簽的 DataFrame
每隔一個周期打亂訓練數據
1import ast
2import numpy as np
3import math
4import os
5import random
6from tensorflow.keras.preprocessing.image import
7img_to_array as img_to_array
8
9from tensorflow.keras.preprocessing.image import load_img as load_img
def load_image(image_path, size):
10# data augmentation logic such as random rotations can be added here
11return img_to_array(load_img(image_path, target_size=(size, size))) / 255.
12
13class KagglePlanetSequence(tf.keras.utils.Sequence):
14"""
15Custom Sequence object to train a model on out-of-memory datasets.
16"""
17
18def __init__(self, df_path, data_path, im_size, batch_size, mode='train'):
19"""
20df_path: path to a .csv file that contains columns with image names and labels
21data_path: path that contains the training images
22im_size: image size
23mode: when in training mode, data will be shuffled between epochs
24"""
25self.df = pd.read_csv(df_path)
26self.im_size = im_size
27 self.batch_size = batch_size
28self.mode = mode
29
30# Take labels and a list of image locations in memory
31self.wlabels = self.df['weather_labels'].apply(lambda x: ast.literal_eval(x)).tolist()
32self.glabels = self.df['ground_labels'].apply(lambda x: ast.literal_eval(x)).tolist()
33self.image_list = self.df['image_name'].apply(lambda x: os.path.join(data_path, x + '.jpg')).tolist()
34
35def __len__(self):
36return int(math.ceil(len(self.df) / float(self.batch_size)))
37
38def on_epoch_end(self):
39# Shuffles indexes after each epoch
40self.indexes = range(len(self.image_list))
41if self.mode == 'train':
42self.indexes = random.sample(self.indexes, k=len(self.indexes))
43
44def get_batch_labels(self, idx):
45# Fetch a batch of labels
46return [self.wlabels[idx * self.batch_size: (idx + 1) * self.batch_size],
47self.glabels[idx * self.batch_size: (idx + 1) * self.batch_size]]
48
49def get_batch_features(self, idx):
50# Fetch a batch of images
51batch_images = self.image_list[idx * self.batch_size: (1 + idx) * self.batch_size]
52return np.array([load_image(im, self.im_size) for im in batch_images])
53
54def __getitem__(self, idx):
55batch_x = self.get_batch_features(idx)
56batch_y = self.get_batch_labels(idx)
57return batch_x, batch_y
您可以使用此 Sequence 對象(而不是自定義生成器)和 fit_generator() 來訓練模型。請注意,您無需提供每個周期的步驟數量,因為 __len__ 方法會實現生成器的相應邏輯。
1seq = KagglePlanetSequence('./KagglePlanetMCML.csv',
2'./data/train/',
3im_size=IM_SIZE,
4batch_size=32)
此外,tf.keras 讓您可以使用所有可用的 Keras 回調,以用于提升訓練循環。這些調用非常強大,可提供提前停止、安排學習速率及存儲 TensorBoard 文件等選項……在這里,我們將在每個周期結束后使用 ModelCheckPoint 回調來保存模型,如此一來,之后我們便可在有需要時繼續訓練。默認情況下,系統會存儲模型架構、訓練配置、優化器狀態和權重,以便通過單個文件重新創建整個模型。
我們開始訓練模型一個周期:
1callbacks = [
2tf.keras.callbacks.ModelCheckpoint('./model.h5', verbose=1)
3]
4
5model.fit_generator(generator=seq,
6verbose=1,
7epochs=1,
8use_multiprocessing=True,
9workers=4,
10callbacks=callbacks)
Epoch 1/1Epoch 00001: saving model to ./model.h51265/1265 [==============================] - 941s 744ms/step - loss: 0.8686 - weather_loss: 0.6571 - ground_loss: 0.2115
假設我們想在后期階段微調模型,只需讀取模型文件并繼續訓練,而無需重新編譯:
1another_model = tf.keras.models.load_model('./model.h5')
2another_model.fit_generator(generator=seq, verbose=1, epochs=1)
最后,比較好的做法是,實例化 Sequencein 測試模式(即不打亂)并將其用于為整個數據集進行預測,以驗證我們的 Sequence 是否有效傳遞所有數據:
1test_seq = KagglePlanetSequence('./KagglePlanetMCML.csv',
2'./data/train/',
3im_size=IM_SIZE,
4batch_size=32, mode='test')
5predictions = model.predict_generator(generator=test_seq, verbose=1)
6len(predictions[1]) == len(df_train) # This is True!
那么,數據集 API 又是什么呢?
tf.data API 是一個功能強大的內容庫,讓您可以使用來自各種來源的數據并將其傳遞給 TensorFlow 模型。我們可以使用 tf.data API 而不是 Sequence 對象來訓練 tf.keras 模型嗎?可以。首先,我們將圖像和標簽一起序列化為 TFRecord 文件,這是在 TensorFlow 中序列化數據所推薦生成的格式:
1# Serialize images, together with labels, to TF records
2def _bytes_feature(value):
3returntf.train.Feature(bytes_list=tf.train.BytesList(value=[value]))
4
5tf_records_filename = './data/KagglePlanetTFRecord_{}'.format(IM_SIZE)
6writer = tf.python_io.TFRecordWriter(tf_records_filename)
7
8# List of image paths, np array of labels
9im_list = [os.path.join('./data/train', v + '.jpg') for v in df_train['image_name'].tolist()]
10w_labels_arr = np.array([ast.literal_eval(l) for l in df_train['weather_labels']])
11g_labels_arr = np.array([ast.literal_eval(l) for l in df_train['ground_labels']])
12
13# Loop over images and labels, wrap in TF Examples, write away to TFRecord file
14for i in range(len(df_train)):
15w_labels = w_labels_arr[i].astype(np.float32)
16g_labels = g_labels_arr[i].astype(np.float32)
17im = np.array(img_to_array(load_img(im_list[i], target_size=(IM_SIZE, IM_SIZE))) / 255.)
18
19example = tf.train.Example(features=tf.train.Features(feature={'image': _bytes_feature(im.tostring()),
20 'weather_labels': _bytes_feature(w_labels.tostring()),
21 'ground_labels': _bytes_feature(g_labels.tostring())}))
22
23writer.write(example.SerializeToString())
24
25writer.close()
將圖像和標簽轉儲到 TFRecord 文件后,我們可以使用 tf.data API 設計另一個生成器。其理念是實例化我們文件中的 TFRecordDataset,并告訴它如何使用 map() 操作來解析序列化數據。
1featdef = {
2 'image': tf.FixedLenFeature(shape=[], dtype=tf.string),
3 'weather_labels': tf.FixedLenFeature(shape=[], dtype=tf.string),
4 'ground_labels': tf.FixedLenFeature(shape=[], dtype=tf.string)
5}
6
7def _parse_record(example_proto, clip=False):
8"""Parse a single record into image, weather labels, ground labels"""
9example = tf.parse_single_example(example_proto, featdef)
10im = tf.decode_raw(example['image'], tf.float32)
11im = tf.reshape(im, (IM_SIZE, IM_SIZE, 3))
12weather = tf.decode_raw(ex['weather_labels'], tf.float32)
13ground = tf.decode_raw(ex['ground_labels'], tf.float32)
14return im, weather, ground
15
16# Construct a TFRecordDataset
17ds_train = tf.data.TFRecordDataset('./data/KagglePlanetTFRecord_{}'.format(IM_SIZE)).map(_parse_record)
18ds_train = ds_train.shuffle(1000).batch(32)
Dataset 對象提供多種方法來生成迭代器對象以循環使用數據。不過,從 TensorFlow 1.9 開始,我們可以直接將 ds_train 傳遞給 model.fit() 以訓練模型:
1model = tf.keras.Model(inputs=image_input, outputs=[weather_output, ground_output])
2
3model.compile(optimizer='adam',
4loss={'weather': 'categorical_crossentropy',
5'ground': 'binary_crossentropy'})
6
7history = model.fit(ds_train,
8 steps_per_epoch=100, # let's take just a couple of steps
9 epochs=1)
Epoch 1/1
100/100 [==============================] - 76s 755ms/step - loss: 0.5460 - weather_loss: 0.3780 - ground_loss: 0.1680
效果不錯。這種運作方式讓習慣使用 TFRecords 的用戶注意到 tf.keras。如果您想使用驗證數據,只需通過驗證數據實例化另一個 Dataset,再同樣傳遞給 model.fit() 即可。
提供模型
什么是提供模型?我們的目的在于:在客戶端生成輸入圖像。我們希望將此圖像包裝在某種消息中,然后將其發送到托管我們訓練模型的遠程服務器,最后從服務器接收預測作為響應。
提供 ML 模型:客戶端發送輸入請求,服務器從模型中獲取預測結果并將其作為響應發回給客戶端
首先,我們希望以服務器可以處理的格式導出模型。TensorFlow 提供 SavedModel 格式作為導出模型的通用格式。在后臺,我們的 tf.keras 模型完全是根據 TensorFlow 對象來指定,因此我們可以使用 TensorFlow 方法將其導出。
導出模型背后的主要理念是通過簽名定義指定推理計算。SignatureDef 完全是根據輸入和輸出張量來指定,最終會與模型權重存儲在一起。不過,TensorFlow 提供了一個便利函數 tf.saved_model.simple_save(),抽離出部分相關細節,并適用于大多數用例:
1import tensorflow as tf
2
3# The export path contains the name and the version of the model
4tf.keras.backend.set_learning_phase(0) # Ignore dropout at inference
5model = tf.keras.models.load_model('./model.h5')
6export_path = './PlanetModel/1'
7
8# Fetch the Keras session and save the model
9# The signature definition is defined by the input and output tensors
10 # And stored with the default serving key
11with tf.keras.backend.get_session() as sess:
12tf.saved_model.simple_save(
13sess,
14export_path,
15inputs={'input_image': model.input},
16outputs={t.name:t for t in model.outputs})
INFO:tensorflow:No assets to save.INFO:tensorflow:No assets to write.INFO:tensorflow:SavedModel written to: ./PlanetModel/1/saved_model.pb
請注意,我已在導出路徑中指定版本號。原因在于,TensorFlow Serving 會根據存儲目錄的名稱推斷出模型版本。如果我們想出更好的模型,便可將其存儲在 PlanetModel/2 下,而 TF Serving 會自動更新以托管新模型。模型圖會存儲在版本子目錄中,而變量則存儲在另一個子目錄中:
$ tree
.└── 1 ├── saved_model.pb └── variables ├── variables.data-00000-of-00001 └── variables.index
在設置真實服務器之前,我想強調一下 TensorFlow 的 SavedModel 命令行工具,其有助于快速檢查我們模型的輸入和輸出規格:
$ saved_model_cli show --dir ./ --allThe given SavedModel SignatureDef contains the following input(s): inputs['input_image'] tensor_info: dtype: DT_FLOAT shape: (-1, 128, 128, 3) name: input_layer_2:0The given SavedModel SignatureDef contains the following output(s): outputs['ground_2/Sigmoid:0'] tensor_info: dtype: DT_FLOAT shape: (-1, 13) name: ground_2/Sigmoid:0 outputs['weather_2/Softmax:0'] tensor_info: dtype: DT_FLOAT shape: (-1, 4) name: weather_2/Softmax:0Method name is: tensorflow/serving/predict
我們甚至可以訪問 CLI 中的 Numpy(即 np),以向模型發送一些隨機輸入,進而驗證模型是否可正常運作:
$ saved_model_cli run --dir ./ --tag_set serve --signature_def serving_default --input_exp 'input_image=np.random.rand(1,128,128,3)'
Result for output key ground_2/Sigmoid:0:[[6.5955728e-01 9.8123280e-03 1.4992488e-02 1.9942504e-06 3.5892407e-07 3.2538961e-04 2.4094069e-02 6.0808718e-01 9.8486900e-01 7.9137814e-01 1.4336356e-05 1.6872218e-05 3.8697788e-01]]Result for output key weather_2/Softmax:0:[[7.1896911e-01 2.9373894e-04 2.5214682e-05 2.8071195e-01]]
看來可以正常運作!
使用 TensorFlow Serving 托管模型服務器
我們會使用TensorFlow Serving內容庫來托管模型:
TensorFlow Serving 是一種靈活的高性能服務系統,適用于機器學習模型,并專為生產環境而設計。
Servable是 TensorFlow Serving 的核心抽象概念,并將代表模型。除此之外,TF Serving還提供處理實際服務、加載新版本和卸載舊版本的來源、加載器和管理器。
在本教程中,我們將在本地設置服務器。在生產環境中,您可以在某些微服務架構(例如,在 Kubernetes 集群的 pod 上)中以完全相同的方式設置服務器。
只需一個命令,您便可以在頂層模型目錄中托管模型。我截斷了一些輸出并突出顯示 TF Serving 后端的一些組件,如下所示:
$ tensorflow_model_server --model_base_path=$(pwd) --rest_api_port=9000 --model_name=PlanetModelI tensorflow_serving/core/basic_manager] Successfully reserved resources to load servable {name: PlanetModel version: 1}I tensorflow_serving/core/loader_harness.cc] Loading servable version {name: PlanetModel version: 1}I external/org_tensorflow/tensorflow/cc/saved_model/loader.cc] Loading SavedModel with tags: { serve };I external/org_tensorflow/tensorflow/cc/saved_model/loader.cc] SavedModel load for tags { serve }; Status: success. Took 1048518 microseconds.I tensorflow_serving/core/loader_harness.cc] Successfully loaded servable version {name: PlanetModel version: 1}I tensorflow_serving/model_servers/main.cc] Exporting HTTP/REST API at:localhost:9000 ...
在服務器啟動并運行時,我們可以向其發出請求。從 TensorFlow Serving 1.8 開始,我們可以通過 gRPC 或 HTTP 調用受托管模型。這兩種情況的理念相同:我們希望填充有效負載消息并將其發送給服務器,然后服務器應返回包含預測結果的消息。對于 gRPC,有一些 Python 綁定可用于填充 protobuf 文件格式的消息。如要發送 HTTP 請求,只需使用 Python requests 模塊在有效負載 json 中包裝我們的輸出:
1import requests
2import json
3
4image = img_to_array(load_img('./data/train/train_10001.jpg', target_size=(128,128))) / 255.
5payload = {
6"instances": [{'input_image': image.tolist()}]
7}
8r = requests.post('http://localhost:9000/v1/models/PlanetModel:predict', json=payload)
9json.loads(r.content)
請求網址是根據 TF Serving 文檔中所述的一些規則而組成的。發送請求后,服務器會立即返回天氣和地面標簽的輸出列表:
{u'predictions': [ {u'ground_2/Sigmoid:0': [ 0.153237, 0.000527727, 0.00555856, 0.00542973, 0.00105254, 0.000256282, 0.103614, 0.0325185, 0.998204, 0.072204, 0.00745501, 0.00326175, 0.0942268], u'weather_2/Softmax:0': [ 0.963947, 0.000207846, 0.00113924, 0.0347063] }]}
預測:原始森林天氣晴朗,但缺少農田和道路的天氣狀況。請返回訓練
總結
tf.keras 讓 TensorFlow 用戶可以充分利用 Keras 的全部功能和靈活性。tf.keras 使用起來很有趣,其與核心 TensorFlow 的集成絕對使我們向更廣泛的受眾提供深度學習這一目標向前邁進一大步。事實上,它們像任何其他 TF 模型一樣,能夠以 SavedModel 格式導出并使用 TensorFlow Serving 提供模型,這使在生產環境中使用 tf.keras 變得簡單直觀。
-
神經網絡
+關注
關注
42文章
4774瀏覽量
100898 -
圖像
+關注
關注
2文章
1087瀏覽量
40501 -
tensorflow
+關注
關注
13文章
329瀏覽量
60542
原文標題:使用 tf.keras 訓練和提供 ML 模型
文章出處:【微信號:tensorflowers,微信公眾號:Tensorflowers】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論