自動(dòng)部署深度神經(jīng)網(wǎng)絡(luò)模型TensorFlow(Keras)到生產(chǎn)環(huán)境中

目錄

Keras簡(jiǎn)介

Keras是一套由Python實(shí)現(xiàn)的高級(jí)神經(jīng)網(wǎng)絡(luò)API,可以基于不同的后臺(tái)(backend)實(shí)現(xiàn),包括TensoreFlow,CNTKMXNet(基于Keras-MXNet,處于孵化階段)和Theano(已停止開(kāi)發(fā))。Keras可以讓用戶能快速的開(kāi)發(fā)出原型,支持流行的卷積網(wǎng)絡(luò)(Convolutional Networks)、循環(huán)網(wǎng)絡(luò)(Recurrent Networks)或者兩者的組合,并且可以無(wú)縫的在CPU和GPU上運(yùn)行。

Keras模型分類

Keras提供了兩種類型的模型,順序(Sequential)模型和函數(shù)式(Functional)模型:

  • 順序模型,由一組線性的層堆棧組成(a linear stack of layers),是一種最簡(jiǎn)單的模型類型,API簡(jiǎn)單明了,詳情查看官方文檔:Guide to the Sequential model
  • 函數(shù)式模型,可以定義更復(fù)雜的模型網(wǎng)絡(luò)結(jié)構(gòu),比如多輸出模型,有向無(wú)環(huán)圖,具有共享層的多模型,詳情查看官方文檔:Guide to the Functional API

以上兩套API,生成的模型類型是keras.models.Sequential(繼承自Model)和keras.models.Model類,除了以上兩種類型模型,從Keras 2.2.0開(kāi)始,支持用戶繼承keras.models.Model來(lái)創(chuàng)建完全自定義化模型類型,詳情查看官方文檔模型自類化(Model subclassing)。本文中我們主要討論非子類化(non-subclassing)模型的部署,關(guān)于子類化模型(subclassing)的部署會(huì)在后面介 紹。

Keras模型部署準(zhǔn)備

在上一篇文章《自動(dòng)部署開(kāi)源AI模型到生產(chǎn)環(huán)境:Sklearn、XGBoost、LightGBM、和PySpark》中我們介紹了如何通過(guò)AutoDeployAI的AI模型自動(dòng)部署和管理系統(tǒng)DaaS(Deployment-as-a-Service)來(lái)自動(dòng)部署傳統(tǒng)開(kāi)源機(jī)器學(xué)習(xí)模型(包括Scikit-learn、XGBoost、LightGBM、和PySpark等),這里我們?cè)敿?xì)介紹如果通過(guò)DaaS來(lái)自動(dòng)部署Keras深度神經(jīng)網(wǎng)絡(luò)模型,同樣我們需要:

  • 安裝Python DaaS-Client
  • 初始化DaasClient
  • 創(chuàng)建項(xiàng)目

詳細(xì)準(zhǔn)備工作,請(qǐng)參考以上文章中的部署準(zhǔn)備部分。完整的代碼,請(qǐng)參考Github上的Notebook:deploy-keras.ipynb

默認(rèn)部署Keras模型

  1. 基于Keras Sequence API訓(xùn)練一個(gè)簡(jiǎn)單的多分類模型,使用Scikit-learn中的Iris數(shù)據(jù):
from keras.models import Sequential
from keras.layers import Dense
from keras.optimizers import Adam

model = Sequential()
model.add(Dense(10, input_shape=(4,), activation='relu'))
model.add(Dense(8, activation='relu'))
model.add(Dense(6, activation='relu'))
model.add(Dense(3, activation='softmax'))
model.compile(Adam(lr=0.04), 'categorical_crossentropy', ['accuracy'])
model.fit(X_train, pd.get_dummies(y_train).values, epochs=100)
  1. 發(fā)布Keras模型:
publish_resp = client.publish(model,
                              name='keras-iris',
                              mining_function='classification',
                              x_test=X_test,
                              y_test=y_test,
                              description='A Keras classification model')
pprint(publish_resp)

publish_resp是一個(gè)字典類型的結(jié)果,記錄了模型名稱,和發(fā)布的模型版本。

{'model_name': 'keras-iris', 'model_version': '1'}
  1. 測(cè)試Keras模型:
test_resp = client.test(publish_resp['model_name'], 
                        model_version=publish_resp['model_version'])
pprint(test_resp)

test_resp是一個(gè)字典類型的結(jié)果,記錄了測(cè)試REST API信息,如下:

{'access_token': 'A-LONG-STRING-OF-BEARER-TOKEN-USED-IN-HTTP-HEADER-AUTHORIZATION',
 'endpoint_url': 'https://daas.autodeploy.ai/api/v1/test/deployment-test/daas-python37-faas/test',
 'payload': {'args': {'X': [{'dense_1_input': [5.7, 4.4, 1.5, 0.4]}],
                      'model_name': 'keras-iris',
                      'model_version': '1'}}}

使用requests庫(kù)調(diào)用測(cè)試API:

response = requests.post(test_resp['endpoint_url'],
                         headers={'Authorization': 'Bearer {token}'.format(token=test_resp['access_token'])},
                         json=test_resp['payload'],
                         verify=False)
pprint(response.json())

返回結(jié)果:

{'result': [{'dense_4': [[0.996542751789093,
                          0.0034567660186439753,
                          4.955750227964018e-07]]}],
 'stderr': [],
 'stdout': []}
  1. 正式部署Keras模型:
deploy_resp = client.deploy(model_name='keras-iris', 
                            deployment_name='keras-iris-svc',
                            model_version=publish_resp['model_version'],
                            replicas=1)
pprint(deploy_resp)

返回結(jié)果:

{'access_token': 'A-LONG-STRING-OF-BEARER-TOKEN-USED-IN-HTTP-HEADER-AUTHORIZATION',
 'endpoint_url': 'https://daas.autodeploy.ai/api/v1/svc/deployment-test/keras-iris-svc/predict',
 'payload': {'args': {'X': [{'dense_1_input': [5.7, 4.4, 1.5, 0.4]}]}}}

使用requests庫(kù)調(diào)用正式API:

response = requests.post(deploy_resp['endpoint_url'],
                         headers={'Authorization': 'Bearer {token}'.format(token=deploy_resp['access_token'])},
                         json=deploy_resp['payload'],
                         verify=False)
pprint(response.json())

結(jié)果如下:

{'result': [{'dense_4': [[0.996542751789093,
                          0.0034567660186439753,
                          4.955750227964018e-07]]}]}

除了使用Keras API,對(duì)于TensorFlow,DaaS也同樣支持使用tf.keras API訓(xùn)練的模型。

自定義部署Keras模型

  1. 基于tf.Keras Functional API訓(xùn)練模型,使用Keras中的MNist數(shù)據(jù)來(lái)識(shí)別用戶輸入的數(shù)字,以下代碼參考Functional API on MNIST
# load MNIST dataset
(x_train, y_train), (x_test, y_test) = mnist.load_data()

# from sparse label to categorical
num_labels = len(np.unique(y_train))
y_train = to_categorical(y_train)
y_test = to_categorical(y_test)

# reshape and normalize input images
image_size = x_train.shape[1]
x_train = np.reshape(x_train,[-1, image_size, image_size, 1])
x_test = np.reshape(x_test,[-1, image_size, image_size, 1])
x_train = x_train.astype('float32') / 255
x_test = x_test.astype('float32') / 255

# network parameters
input_shape = (image_size, image_size, 1)
batch_size = 128
kernel_size = 3
filters = 64
dropout = 0.3

# use functional API to build cnn layers
inputs = Input(shape=input_shape)
y = Conv2D(filters=filters,
           kernel_size=kernel_size,
           activation='relu')(inputs)
y = MaxPooling2D()(y)
y = Conv2D(filters=filters,
           kernel_size=kernel_size,
           activation='relu')(y)
y = MaxPooling2D()(y)
y = Conv2D(filters=filters,
           kernel_size=kernel_size,
           activation='relu')(y)
# image to vector before connecting to dense layer
y = Flatten()(y)
# dropout regularization
y = Dropout(dropout)(y)
outputs = Dense(num_labels, activation='softmax')(y)

# build the model by supplying inputs/outputs
model = Model(inputs=inputs, outputs=outputs)
# network model in text
model.summary()

# classifier loss, Adam optimizer, classifier accuracy
model.compile(loss='categorical_crossentropy',
              optimizer='adam',
              metrics=['accuracy'])

# train the model with input images and labels
model.fit(x_train,
          y_train,
          validation_data=(x_test, y_test),
          epochs=3,
          batch_size=batch_size)

為了可以快速訓(xùn)練出模型,修改原程序中epochs=20為3。

  1. 發(fā)布Keras模型:
publish_resp = client.publish(model,
                              name='keras-mnist',
                              mining_function='classification',
                              x_test=x_test,
                              y_test=y_test,
                              description='A tf.Keras classification model')
pprint(publish_resp)

結(jié)果如下:

{'model_name': 'keras-mnist', 'model_version': '1'}
  1. 測(cè)試Keras模型。登陸DaaS Web客戶端,查看keras-mnist模型信息:
daas-model-overview.png

模型輸入字段input_1,維數(shù)為(,28,28,1),輸出字段dense,維數(shù)為(,10)。切換到測(cè)試標(biāo)簽頁(yè),我們看到DaaS自動(dòng)存儲(chǔ)了一條測(cè)試數(shù)據(jù),點(diǎn)擊提交命令,測(cè)試該條數(shù)據(jù),如圖:

daas-model-test.png
  1. 自定義部署Keras模型。keras-mnist模型輸入數(shù)據(jù)是由一張28*28的黑底白字灰度圖像生成的numpy數(shù)組,在REST API使用JSON傳輸數(shù)據(jù)時(shí),因?yàn)镴SON作為一種文本格式,在存儲(chǔ)傳輸大的列表時(shí)有性能劣勢(shì),并且需要調(diào)用端做圖像預(yù)處理工作,增加了客戶端使用的負(fù)擔(dān)。我們希望這些都可以在服務(wù)器端完成:接收二進(jìn)制圖像文件,預(yù)處理圖像,并且轉(zhuǎn)成模型需要的numpy數(shù)組。在DaaS中,我們可以通過(guò)創(chuàng)建自定義部署腳本來(lái)完成該該任務(wù),它允許用戶在模型部署中添加任意的數(shù)據(jù)預(yù)處理和后處理操作。切換到實(shí)時(shí)預(yù)測(cè)標(biāo)簽頁(yè),點(diǎn)擊命令生成自定義實(shí)時(shí)預(yù)測(cè)腳本,生成預(yù)定義腳本:
daas-model-realtime.png

點(diǎn)擊作為API測(cè)試命令,頁(yè)面切換到測(cè)試頁(yè)面,修改preprocess_files函數(shù),并且添加處理圖像的函數(shù):

def rgb2gray(rgb):
    """Convert the input image into grayscale"""
    import numpy as np
    return np.dot(rgb[...,:3], [0.299, 0.587, 0.114])


def resize_img(img_to_resize):
    """Resize image to MNIST model input dimensions"""
    import cv2
    r_img = cv2.resize(img_to_resize, dsize=(28, 28), interpolation=cv2.INTER_AREA)
    r_img.resize((1, 28, 28, 1))
    r_img = 1 - r_img
    return r_img


def preprocess_image(img_to_preprocess):
    """Resize input images and convert them to grayscale."""
    if img_to_preprocess.shape == (28, 28):
        img_to_preprocess.resize((1, 28, 28, 1))
        img_to_preprocess = 1 - img_to_preprocess / 255
        return img_to_preprocess

    grayscale = rgb2gray(img_to_preprocess)
    processed_img = resize_img(grayscale)
    return processed_img


def preprocess_files(args):
    """preprocess the uploaded files"""
    files = args.get('files')
    if files is not None:
        # get the first record object in X if it's present
        if 'X' in args:
            record = args['X'][0]
        else:
            record = {}
            args['X'] = [record]

        # TODO add your own custom opeartions, e.g. loading images, make transformation, then write back into X
        import matplotlib.image as mpimg
        for key, file in files.items():
            img = mpimg.imread(file)
            record[key] = preprocess_image(img)

    return args

輸入函數(shù)名predict,選擇請(qǐng)求正文基于表單,輸入名稱input_1,選擇文件,點(diǎn)擊上傳測(cè)試圖像2.png,點(diǎn)擊提交,右側(cè)響應(yīng)頁(yè)面顯示結(jié)果為:

{
  "result": [
    {
      "dense": [
        [
          4.0412282942270394e-7,
          5.612335129967505e-8,
          0.9999896287918091,
          0.0000014349453749673557,
          2.1778572326623669e-13,
          3.3688251840913175e-12,
          1.8931906042851665e-10,
          1.7151558395767097e-8,
          0.000008489014362567104,
          6.33769703384246e-10
        ]
      ]
    }
  ],
  "stderr": [
    "Some warning messages here"
  ],
  "stdout": []
}

繼續(xù)修改postprocess函數(shù)為:

def postprocess(result):
    """postprocess the predicted results"""
    import numpy as np
    return [int(np.argmax(np.array(result).squeeze(), axis=0))]

重新提交,右側(cè)響應(yīng)頁(yè)面顯示結(jié)果為:

daas-model-script-test.png

測(cè)試完成后,可以創(chuàng)建正式的部署,切換到部署標(biāo)簽頁(yè),點(diǎn)擊命令添加網(wǎng)絡(luò)服務(wù),輸入服務(wù)名稱mnist-svc,其他使用默認(rèn)選項(xiàng),點(diǎn)擊創(chuàng)建。進(jìn)入到部署頁(yè)面后,點(diǎn)擊測(cè)試標(biāo)簽頁(yè),該界面類似之前的腳本測(cè)試界面,輸入函數(shù)名predict,請(qǐng)求正文選擇基于表單,輸入名稱input_1,類型選擇文件,點(diǎn)擊上傳測(cè)試的圖片后,點(diǎn)擊提交:

daas-deployment-test.png

到此,正式部署已經(jīng)測(cè)試和創(chuàng)建完成,用戶可以使用任意的客戶端程序調(diào)用該部署服務(wù)。點(diǎn)擊以上界面中的生成代碼命令,顯示如何通過(guò)curl命令調(diào)用該服務(wù),測(cè)試如下:

daas-deployment-curl-test.png
  1. 通過(guò)ONNX部署Keras模型:

    • 轉(zhuǎn)換模型到ONNX:
    import onnxmltools
    
    onnx_model = onnxmltools.convert_keras(model, model.name)
    
    • 發(fā)布ONNX模型:
    publish_resp = client.publish(onnx_model,
                                  name='keras-mnist-onnx',
                                  mining_function='classification',
                                  x_test=x_test,
                                  y_test=y_test,
                                  description='A tf.Keras classification model in ONNX')
    pprint(publish_resp)
    

    結(jié)果如下:

    {'model_name': 'keras-mnist-onnx', 'model_version': '1'}
    
    • 測(cè)試ONNX模型:登陸DaaS Web客戶端,查看keras-onnx-mnist模型信息:
daas-onnx-model-overview.png

模型輸入字段input_1,維數(shù)為(N,28,28,1),輸出字段dense,維數(shù)為(N,10)。切換到測(cè)試標(biāo)簽頁(yè),我們看到DaaS自動(dòng)存儲(chǔ)了一條測(cè)試數(shù)據(jù),點(diǎn)擊提交命令,測(cè)試該條數(shù)據(jù),如圖:

daas-onnx-model-test.png

我們看到,該ONNX模型和原生Keras模型測(cè)試結(jié)果是一致的。

總結(jié)

通過(guò)以上的演示,我們可以看到,DaaS在部署深度學(xué)習(xí)模型時(shí)的優(yōu)勢(shì):既可以一鍵式創(chuàng)建默認(rèn)部署,又可以靈活的自定義部署,滿足用戶多樣的部署需求。關(guān)于其他深度學(xué)習(xí)框架的部署,比如Pytorch,MXNet等,會(huì)在后續(xù)的文章中介紹。

參考

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容