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

目錄

Keras簡介

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

Keras模型分類

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

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

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

Keras模型部署準備

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

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

詳細準備工作,請參考以上文章中的部署準備部分。完整的代碼,請參考Github上的Notebook:deploy-keras.ipynb

默認部署Keras模型

  1. 基于Keras Sequence API訓練一個簡單的多分類模型,使用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是一個字典類型的結(jié)果,記錄了模型名稱,和發(fā)布的模型版本。

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

test_resp是一個字典類型的結(jié)果,記錄了測試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庫調(diào)用測試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庫調(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,對于TensorFlow,DaaS也同樣支持使用tf.keras API訓練的模型。

自定義部署Keras模型

  1. 基于tf.Keras Functional API訓練模型,使用Keras中的MNist數(shù)據(jù)來識別用戶輸入的數(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)

為了可以快速訓練出模型,修改原程序中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. 測試Keras模型。登陸DaaS Web客戶端,查看keras-mnist模型信息:
daas-model-overview.png

模型輸入字段input_1,維數(shù)為(,28,28,1),輸出字段dense,維數(shù)為(,10)。切換到測試標簽頁,我們看到DaaS自動存儲了一條測試數(shù)據(jù),點擊提交命令,測試該條數(shù)據(jù),如圖:

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

點擊作為API測試命令,頁面切換到測試頁面,修改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,選擇請求正文基于表單,輸入名稱input_1,選擇文件,點擊上傳測試圖像2.png,點擊提交,右側(cè)響應(yīng)頁面顯示結(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)頁面顯示結(jié)果為:

daas-model-script-test.png

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

daas-deployment-test.png

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

daas-deployment-curl-test.png
  1. 通過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'}
    
    • 測試ONNX模型:登陸DaaS Web客戶端,查看keras-onnx-mnist模型信息:
daas-onnx-model-overview.png

模型輸入字段input_1,維數(shù)為(N,28,28,1),輸出字段dense,維數(shù)為(N,10)。切換到測試標簽頁,我們看到DaaS自動存儲了一條測試數(shù)據(jù),點擊提交命令,測試該條數(shù)據(jù),如圖:

daas-onnx-model-test.png

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

總結(jié)

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

參考

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,963評論 6 542
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,348評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,083評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,706評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,442評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,802評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,795評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,983評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,542評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,287評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,486評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,030評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,710評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,116評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,412評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,224評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,462評論 2 378

推薦閱讀更多精彩內(nèi)容