日萌社
人工智能AI:Keras PyTorch MXNet TensorFlow PaddlePaddle 深度學習實戰(不定時更新)
4.11 綜合案例:模型導出與部署
學習目標
- 目標
- 掌握TensorFlow模型的導出(saved_model格式)
- 掌握Tensorflow模型的部署
- 掌握TensorFlow模型的客戶端調用
- 掌握TensorFlow模型的超參數調優使用
- 應用
- 無
4.11.1 TensorFlow 模型導出
在部署模型時,我們的第一步往往是將訓練好的整個模型完整導出爲一系列標準格式的文件,然後即可在不同的平臺上部署模型文件。這時,TensorFlow 爲我們提供了 SavedModel 這一格式。
- 與前面介紹的 Checkpoint 不同,SavedModel 包含了一個 TensorFlow 程序的完整信息: 不僅包含參數的權值,還包含計算的流程(即計算圖) 。
- 特點:當模型導出爲 SavedModel 文件時,無需建立模型的源代碼即可再次運行模型,這使得 SavedModel 尤其適用於模型的分享和部署。後文的 TensorFlow Serving(服務器端部署模型)、TensorFlow Lite(移動端部署模型)以及 TensorFlow.js 都會用到這一格式。
除了CheckPointTensorFlow還會有其他格式,這裏做統一介紹:部署在線服務(Serving)時官方推薦使用 SavedModel 格式,而部署到手機等移動端的模型一般使用 FrozenGraphDef 格式(最近推出的 TensorFlow Lite 也有專門的輕量級模型格式 *.lite,和 FrozenGraphDef 十分類似)。這些格式之間關係密切,可以使用 TensorFlow 提供的 API 來互相轉換。下面簡單介紹幾種格式:
- 1、GraphDef
- 這種格式文件包含 protobuf 對象序列化後的數據,包含了計算圖,可以從中得到所有運算符(operators)的細節,也包含張量(tensors)和 Variables 定義,但不包含 Variable 的值,因此只能從中恢復計算圖,但一些訓練的權值仍需要從 checkpoint 中恢復。
-
2、*.pb
- TensorFlow 一些例程中用到*.pb 文件作爲預訓練模型,這和上面 GraphDef 格式稍有不同,屬於凍結(Frozen)後的 GraphDef 文件,簡稱 FrozenGraphDef 格式。這種文件格式不包含 Variables 節點。將 GraphDef 中所有 Variable 節點轉換爲常量(其值從 checkpoint 獲取),就變爲 FrozenGraphDef 格式。
-
3、SavedModel
- 在使用 TensorFlow Serving 時,會用到這種格式的模型。該格式爲 GraphDef 和 CheckPoint 的結合體,另外還有標記模型輸入和輸出參數的 SignatureDef。從 SavedModel 中可以提取 GraphDef 和 CheckPoint 對象。
- 其中 saved_model.pb(或 saved_model.pbtxt)包含使用 MetaGraphDef protobuf 對象定義的計算圖;assets 包含附加文件;variables 目錄包含 tf.train.Saver() 對象調用 save() API 生成的文件。
使用下面的代碼即可將模型導出爲 SavedModel:
tf.saved_model.save(model, "保存的目標文件夾名稱")
在需要載入 SavedModel 文件時,使用即可
model = tf.saved_model.load("保存的目標文件夾名稱")
4.11.2 使用案例
1、將之前CIFAE100分類模型進行導出和導入,導出模型到 saved/mlp/1
文件夾中,mlp可以自己指定的一個模型名稱,1爲版本號,必須提供,後面開啓服務需要有版本號
import tensorflow as tf
import os
os.environ["TF_CPP_MIN_LOG_LEVEL"] = "2"
def main():
num_epochs = 1
batch_size = 32
learning_rate = 0.001
model = tf.keras.models.Sequential([
tf.keras.layers.Flatten(),
tf.keras.layers.Dense(120, activation=tf.nn.relu),
tf.keras.layers.Dense(100),
tf.keras.layers.Softmax()
])
(train, train_label), (test, test_label) = \
tf.keras.datasets.cifar100.load_data()
model.compile(
optimizer=tf.keras.optimizers.Adam(learning_rate=learning_rate),
loss=tf.keras.losses.sparse_categorical_crossentropy,
metrics=[tf.keras.metrics.sparse_categorical_accuracy]
)
model.fit(train, train_label, epochs=num_epochs, batch_size=batch_size)
tf.saved_model.save(model, "./saved/mlp/1")
2、並且模型加載出來,測試性能就能夠
- 注意:這裏加載模型可以不用初始化之前的模型(不需要),直接加載到model使用
def test():
model = tf.saved_model.load("./saved/mlp/1")
sparse_categorical_accuracy = tf.keras.metrics.SparseCategoricalAccuracy()
(train, train_label), (test, test_label) = \
tf.keras.datasets.cifar100.load_data()
y_pred = model(test)
sparse_categorical_accuracy.update_state(y_true=test_label,
y_pred=y_pred)
print("test accuracy: %f" % sparse_categorical_accuracy.result())
輸出結果
test accuracy: 0.010000
3、自定義的keras模型使用:
使用繼承 tf.keras.Model
類建立的 Keras 模型同樣可以以相同方法導出,唯須注意 call
方法需要以 @tf.function
修飾,以轉化爲 SavedModel 支持的計算圖,代碼如下:
def __init__(self):
super().__init__()
self.flatten = tf.keras.layers.Flatten()
self.dense1 = tf.keras.layers.Dense(units=100, activation=tf.nn.relu)
self.dense2 = tf.keras.layers.Dense(units=10)
@tf.function
def call(self, inputs): # [batch_size, 28, 28, 1]
x = self.flatten(inputs) # [batch_size, 784]
x = self.dense1(x) # [batch_size, 100]
x = self.dense2(x) # [batch_size, 10]
output = tf.nn.softmax(x)
return output
model = MLP()
4.11.3 TensorFlow Serving
- 背景:當我們將模型訓練完畢後,往往需要將模型在生產環境中部署。最常見的方式,是在服務器上提供一個 API,即客戶機向服務器的某個 API 發送特定格式的請求,服務器收到請求數據後通過模型進行計算,並返回結果。如果僅僅是做一個 Demo,不考慮高併發和性能問題,其實配合Django、Flask等 Python 下的 Web 框架就能非常輕鬆地實現服務器 API。不過,如果是在真的實際生產環境中部署,這樣的方式就顯得力不從心了。這時,TensorFlow 爲我們提供了 TensorFlow Serving 這一組件,能夠幫助我們在實際生產環境中靈活且高性能地部署機器學習模型。
TensorFlow Serving是一種靈活的高性能服務系統,適用於機器學習模型,專爲生產環境而設計。TensorFlow Serving可以輕鬆部署新算法和實驗,同時保持相同的服務器架構和API。TensorFlow Serving提供與TensorFlow模型的開箱即用集成,但可以輕鬆擴展以提供其他類型的模型和數據。
特點:TensorFlow Serving 支持熱更新模型,其典型的模型文件夾結構如下:
/saved_model_files
/1 # 版本號爲1的模型文件
/assets
/variables
saved_model.pb
...
/N # 版本號爲N的模型文件
/assets
/variables
saved_model.pb
上面 1~N 的子文件夾代表不同版本號的模型。當指定 --model_base_path
時,只需要指定根目錄的 絕對地址 (不是相對地址)即可。例如,如果上述文件夾結構存放在 home/snowkylin
文件夾內,則 --model_base_path
應當設置爲 home/snowkylin/saved_model_files
(不附帶模型版本號)。TensorFlow Serving 會自動選擇版本號最大的模型進行載入。
4.11.3.1 安裝Tensorflow Serving
安裝過程詳細參考官網
- 使用Docker安裝進行,首先你的電腦當中已經安裝過docker容器
TensorFlow Serving 可以使用 apt-get 或 Docker 安裝。在生產環境中,推薦 使用 Docker 部署 TensorFlow Serving 。
4.11.3.2 TensorFlow Serving Docker 使用介紹
-
獲取最新TF Serving docker鏡像
docker pull tensorflow/serving
-
查看docker鏡像
docker images
-
運行tf serving(即創建一個docker容器來運行)
docker run -p 8501:8501 -p 8500:8500 --mount type=bind,source=/home/ubuntu/detectedmodel/commodity,target=/models/commodity -e MODEL_NAME=commodity -t tensorflow/serving
說明:
-p 8501:8501
爲端口映射,-p 主機端口:docker容器程序(tf serving)使用端口
,訪問主機8501端口就相當於訪問了tf serving程序的8501端口- tf serving 使用8501端口對外提供HTTP服務,使用8500對外提供gRPC服務,這裏同時開放了兩個端口的使用
--mount type=bind,source=/home/ubuntu/detectedmodel/commodity,target=/models/commodity
爲文件映射,將主機(source)的模型文件映射到docker容器程序(target)的位置,以便tf serving使用模型,target
參數爲/models/我的模型
-e MODEL_NAME=commodity
設置了一個環境變量,名爲MODEL_NAME
,此變量被tf serving讀取,用來按名字尋找模型,與上面target參數中我的模型
對應-t
爲tf serving創建一個僞終端,供程序運行tensorflow/serving
爲鏡像名
常見docker命令:
docker ps:查看正在運行的容器
docker images:查看已下載的景象
docker stop 8779b492e4aa:停止正在運行的ID爲8779b492e4aa的容器,IP可以通過docker ps查看
4.11.3.3 案例操作:commodity模型服務運行
- 1、運行命令
docker run -p 8501:8501 -p 8500:8500 --mount type=bind,source=/root/cv_project/tf_example
/saved/mlp,target=/models/mlp -e MODEL_NAME=mlp -t tensorflow/serving &
- 2、查看是否運行
itcast:~$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS POR
TS NAMES
1354f9aeab33 tensorflow/serving "/usr/bin/tf_serving…" 7 seconds ago Up 5 seconds 0.0
.0.0:8500-8501->8500-8501/tcp gifted_jackson
4.11.5 在客戶端調用以 TensorFlow Serving 部署的模型
TensorFlow Serving 支持以 gRPC 和 RESTful API 調用以 TensorFlow Serving 部署的模型。RESTful API 以標準的 HTTP POST 方法進行交互,請求和回覆均爲 JSON 對象。爲了調用服務器端的模型,我們在客戶端向服務器發送以下格式的請求:服務器 URI: http://服務器地址:端口號/v1/models/模型名:predict
請求內容:
{
"signature_name": "需要調用的函數簽名(Sequential模式不需要)",
"instances": 輸入數據
}
回覆爲:
{
"predictions": 返回值
}
4.11.5.1 直接使用curl
運行下面命令
curl -d '{"instances": [image_data]}' \
-X POST http://localhost:8501/v1/models/mlp:predict
4.11.5.2 編寫客戶端代碼
示例使用Python 的 Requests 庫(你可能需要使用 pip install requests
安裝該庫)向本機的 TensorFlow Serving 服務器發送20張圖像並返回預測結果,同時與測試集的真實標籤進行比較。
def client():
import json
import numpy as np
import requests
(_, _), (test, test_label) = \
tf.keras.datasets.cifar100.load_data()
data = json.dumps({
"instances": test[0:20].tolist() # array轉換成列表形式
})
headers = {"content-type": "application/json"}
json_response = requests.post(
'http://localhost:8501/v1/models/mlp:predict',
data=data, headers=headers)
predictions = np.array(json.loads(json_response.text)['predictions'])
print(np.argmax(predictions, axis=-1))
print(test_label[0:20])
if __name__ == '__main__':
# main()
# test()
client()
輸出:
[67 67 67 67 67 67 67 67 67 67 67 67 67 67 67 67 67 67 67 67]
[[49]
[33]
[72]
[51]
[71]
[92]
[15]
[14]
[23]
[ 0]
[71]
[75]
[81]
[69]
[40]
[43]
[92]
[97]
[70]
[53]]
因爲模型並沒有訓練多久,只迭代一次,所以效果不好,主要是完成整個流程。
model_serving.py
import tensorflow as tf
import os
os.environ["TF_CPP_MIN_LOG_LEVEL"] = "2"
def main():
num_epochs = 1
batch_size = 32
learning_rate = 0.001
model = tf.keras.models.Sequential([
tf.keras.layers.Flatten(),
tf.keras.layers.Dense(120, activation=tf.nn.relu),
tf.keras.layers.Dense(100),
tf.keras.layers.Softmax()
])
(train, train_label), (test, test_label) = \
tf.keras.datasets.cifar100.load_data()
model.compile(
optimizer=tf.keras.optimizers.Adam(learning_rate=learning_rate),
loss=tf.keras.losses.sparse_categorical_crossentropy,
metrics=[tf.keras.metrics.sparse_categorical_accuracy]
)
model.fit(train, train_label, epochs=num_epochs, batch_size=batch_size)
tf.saved_model.save(model, "./saved/mlp/2")
def test():
model = tf.saved_model.load("./saved/mlp/2")
(_, _), (test, test_label) = \
tf.keras.datasets.cifar100.load_data()
y_predict = model(test)
sparse_categorical_accuracy = tf.keras.metrics.SparseCategoricalAccuracy()
sparse_categorical_accuracy.update_state(y_true=test_label, y_pred=y_predict)
print("測試集的準確率爲: %f" % sparse_categorical_accuracy.result())
def client():
import json
import numpy as np
import requests
(_, _), (test, test_label) = \
tf.keras.datasets.cifar100.load_data()
# 1、構造好請求
data = json.dumps({"instances": test[:10].tolist()})
headers = {"content-type": "application/json"}
json_response = requests.post('http://localhost:8501/v1/models/mlp:predict',
data=data, headers=headers)
predictions = np.array(json.loads(json_response.text)['predictions'])
print(predictions)
print(np.argmax(predictions, axis=-1))
print(test_label[:10])
if __name__ == '__main__':
"""
TensorFlow 模型導出
"""
main()
test()
"""
TensorFlow Serving
"""
# client()
4.11.5 HParams-超參數調優
構建深度學習模型時,需要選擇各種超參數,例如模型中的學習率,優化器,神經元個數等。這些決策會影響模型指標,例如準確性。因此,工作流程中的一個重要步驟是爲您的問題確定最佳的超參數,這通常涉及實驗。此過程稱爲“超參數優化”或“超參數調整”。TensorBoard中的HParams儀表板提供了多種工具,幫助確定最佳實驗或最有希望的超參數集。
注:Note: The HParams summary APIs and dashboard UI are in a preview stage and will change over time.
使用導入
from tensorboard.plugins.hparams import api as hp
4.11.5.1 案例:CIFAR100分類模型添加參數進行調優
- 使用步驟
- 1、通過hp設置HParams 實驗超參數
- 2、將試驗參數添加到模型指定結構當中,或者編譯訓練的過程參數中
- 3、使用超參數調優方法對不同的超參數集訓練每個實驗組合
1、設置HParams 實驗參數
- hp.Discrete:設置離散類型的參數值,比如神經元個數,優化方法
- 通過HP_NUM_UNITS.domain.values獲取所有的值
- hp.RealInterval:設置連續型類型的上下限,能夠獲取最大值最小值
- HP_DROPOUT.domain.min_value:獲取最小值
- HP_DROPOUT.domain.max_value:獲取最大值
HP_NUM_UNITS = hp.HParam('num_units', hp.Discrete([1024, 512]))
HP_DROPOUT = hp.HParam('dropout', hp.RealInterval(0.2, 0.3))
HP_OPTIMIZER = hp.HParam('optimizer', hp.Discrete(['adam', 'sgd']))
2、將試驗參數添加到模型指定結構當中,或者編譯訓練的過程參數中
- (1)在需要設置超參數的位置填入hparams參數值
- (2)添加記錄hparams的回調hp.KerasCallback('./logs/hparam_tuning/', hparams)
- 其中目錄自己設定即可
def train_test_model(self, hparams):
"""訓練驗證模型
:return:
"""
# 1、定義模型中加入了一個dropout
model = tf.keras.Sequential([
tf.keras.layers.Conv2D(32, kernel_size=5, strides=1, padding='same', activation=tf.nn.relu),
tf.keras.layers.MaxPool2D(pool_size=2, strides=2),
tf.keras.layers.Conv2D(64, kernel_size=5, strides=1, padding='same', activation=tf.nn.relu),
tf.keras.layers.MaxPool2D(pool_size=2, strides=2),
tf.keras.layers.Flatten(),
tf.keras.layers.Dense(hparams[HP_NUM_UNITS], activation=tf.nn.relu),
tf.keras.layers.Dropout(hparams[HP_DROPOUT]),
tf.keras.layers.Dense(100, activation=tf.nn.softmax)
])
# 2、模型設置以及訓練
model.compile(optimizer=hparams[HP_OPTIMIZER],
loss=tf.keras.losses.sparse_categorical_crossentropy,
metrics=['accuracy'])
tensorboard = tf.keras.callbacks.TensorBoard(log_dir='./graph', histogram_freq=1,
write_graph=True, write_images=True)
# 添加記錄hparams的回調hp.KerasCallback('./graph/', hparams)
model.fit(self.train, self.train_label,
epochs=1, batch_size=32,
callbacks=[tensorboard, hp.KerasCallback('./logs/hparam_tuning/', hparams)],
validation_data=(self.test, self.test_label))
return None
3、使用超參數調優方法對不同的超參數集訓練每個實驗組合
使用網格搜索:嘗試使用離散參數的所有組合或者連續型值參數的上限和下限。對於更復雜的場景,隨機選擇每個超參數值會更有效(隨機搜索)。
def hyper_parameter(self):
"""超參數調優
:return:
"""
session_num = 0
for num_units in HP_NUM_UNITS.domain.values:
for dropout_rate in (HP_DROPOUT.domain.min_value, HP_DROPOUT.domain.max_value):
for optimizer in HP_OPTIMIZER.domain.values:
hparams = {
HP_NUM_UNITS: num_units,
HP_DROPOUT: dropout_rate,
HP_OPTIMIZER: optimizer,
}
print('--- 開始 實驗: %s' % session_num)
print({h.name: hparams[h] for h in hparams})
self.train_test_model(hparams)
session_num += 1
return None
注:其中如果對於連續的值比如說你的模型中learning_rate,假設設置0.0001~0.1,可以這樣迭代使用
for lr_tate in tf.linspace(
HP_LR.domain.min_value,
HP_LR.domain.max_value,
res):
最終打印效果爲:
--- 開始 實驗: 0
{'num_units': 512, 'dropout': 0.2, 'optimizer': 'adam'}
Train on 50000 samples, validate on 10000 samples
Epoch 1/2
32/50000 [..............................] - ETA: 21:58 - loss: 4.6257 - accuracy: 0.0000e+002020-03-22 05:02:24.239954: E
50000/50000 [==============================] - 61s 1ms/sample - loss: 3.6983 - accuracy: 0.1391 - val_loss: 3.1219 - val_accuracy: 0.2460
Epoch 2/2
50000/50000 [==============================] - 60s 1ms/sample - loss: 2.9337 - accuracy: 0.2777 - val_loss: 2.7689 - val_accuracy: 0.3094
--- 開始 實驗: 1
{'num_units': 512, 'dropout': 0.2, 'optimizer': 'sgd'}
Train on 50000 samples, validate on 10000 samples
Epoch 1/2
32/50000 [..............................] - ETA: 9:12 - loss: 4.5919 - accuracy: 0.0000e+002020-03-22 05:04:26.409071: E
50000/50000 [==============================] - 55s 1ms/sample - loss: 4.3805 - accuracy: 0.0404 - val_loss: 4.1078 - val_accuracy: 0.0641
Epoch 2/2
50000/50000 [==============================] - 55s 1ms/sample - loss: 3.9086 - accuracy: 0.1072 - val_loss: 3.7098 - val_accuracy: 0.1479
--- 開始 實驗: 2
{'num_units': 512, 'dropout': 0.3, 'optimizer': 'adam'}
Train on 50000 samples, validate on 10000 samples
Epoch 1/2
32/50000 [..............................] - ETA: 11:06 - loss: 4.6464 - accuracy: 0.03122020-03-22 05:06:18.355198: E
50000/50000 [==============================] - 61s 1ms/sample - loss: 3.7051 - accuracy: 0.1400 - val_loss: 3.1361 - val_accuracy: 0.2422
Epoch 2/2
50000/50000 [==============================] - 60s 1ms/sample - loss: 2.9754 - accuracy: 0.2687 - val_loss: 2.7621 - val_accuracy: 0.3118
--- 開始 實驗: 3
{'num_units': 512, 'dropout': 0.3, 'optimizer': 'sgd'}
Train on 50000 samples, validate on 10000 samples
Epoch 1/2
32/50000 [..............................] - ETA: 9:03 - loss: 4.5909 - accuracy: 0.0000e+002020-03-22 05:08:20.864262: E
50000/50000 [==============================] - 55s 1ms/sample - loss: 4.3802 - accuracy: 0.0399 - val_loss: 4.0482 - val_accuracy: 0.0807
Epoch 2/2
50000/50000 [==============================] - 55s 1ms/sample - loss: 3.9227 - accuracy: 0.1036 - val_loss: 3.7022 - val_accuracy: 0.1423
--- 開始 實驗: 4
{'num_units': 1024, 'dropout': 0.2, 'optimizer': 'adam'}
Train on 50000 samples, validate on 10000 samples
Epoch 1/2
32/50000 [..............................] - ETA: 11:28 - loss: 4.6099 - accuracy: 0.0000e+002020-03-22 05:10:12.802252: E
50000/50000 [==============================] - 78s 2ms/sample - loss: 3.5546 - accuracy: 0.1683 - val_loss: 3.0440 - val_accuracy: 0.2580
Epoch 2/2
50000/50000 [==============================] - 77s 2ms/sample - loss: 2.7925 - accuracy: 0.3048 - val_loss: 2.7049 - val_accuracy: 0.3255
--- 開始 實驗: 5
{'num_units': 1024, 'dropout': 0.2, 'optimizer': 'sgd'}
Train on 50000 samples, validate on 10000 samples
Epoch 1/2
32/50000 [..............................] - ETA: 9:22 - loss: 4.5938 - accuracy: 0.0000e+002020-03-22 05:12:50.161457: E
50000/50000 [==============================] - 68s 1ms/sample - loss: 4.3278 - accuracy: 0.0477 - val_loss: 3.9872 - val_accuracy: 0.0849
Epoch 2/2
50000/50000 [==============================] - 67s 1ms/sample - loss: 3.8071 - accuracy: 0.1244 - val_loss: 3.6097 - val_accuracy: 0.1679
--- 開始 實驗: 6
{'num_units': 1024, 'dropout': 0.3, 'optimizer': 'adam'}
Train on 50000 samples, validate on 10000 samples
Epoch 1/2
32/50000 [..............................] - ETA: 11:14 - loss: 4.6129 - accuracy: 0.0000e+002020-03-22 05:15:07.480836: E
50000/50000 [==============================] - 78s 2ms/sample - loss: 3.6139 - accuracy: 0.1548 - val_loss: 2.9943 - val_accuracy: 0.2691
Epoch 2/2
50000/50000 [==============================] - 77s 2ms/sample - loss: 2.8460 - accuracy: 0.2921 - val_loss: 2.7325 - val_accuracy: 0.3195
--- 開始 實驗: 7
{'num_units': 1024, 'dropout': 0.3, 'optimizer': 'sgd'}
Train on 50000 samples, validate on 10000 samples
Epoch 1/2
32/50000 [..............................] - ETA: 9:22 - loss: 4.5689 - accuracy: 0.03122020-03-22 05:17:44.697445: E
50000/50000 [==============================] - 68s 1ms/sample - loss: 4.3251 - accuracy: 0.0469 - val_loss: 3.9708 - val_accuracy: 0.1038
Epoch 2/2
50000/50000 [==============================] - 67s 1ms/sample - loss: 3.8546 - accuracy: 0.1173 - val_loss: 3.6345 - val_accuracy: 0.1653
生成如下目錄
4、最終可以通過Tensoboard開啓讀取對應目錄的events文件看到對應結果
tensorboard --logdir './logs/hparam_tuning/'
可以在HPARAMS菜單中查看到下面的效果(官網截圖)
並且其中有提供了兩種主要的不同的查看方式:
- 1、表視圖(TABLE VIEW):列出了運行時,他們的超參數,和他們的指標。
- 2、平行座標視圖:每個運行爲線通過每個hyperparemeter和度量的軸線去。單擊並在任何軸上拖動鼠標以標記一個區域,該區域將僅突出顯示通過該區域的運行。這對於確定哪些超參數組最重要很有用
4.11.6 總結
- TensorFlow模型的導出(saved_model格式)
- Tensorflow模型的部署
- TensorFlow模型的客戶端調用
- TensorFlow模型的超參數調優