使用modelarts部署bert命名實體識別模型(基于bert的命名實體識別)
當我們通過深度學習完成模型訓練后,有時希望能將模型落地于生產,能開發API接口被終端調用,這就涉及了模型的部署工作。ModelArts支持對tensorflow,mxnet,pytorch等模型的部署和在線預測,這里老山介紹下tensorflow的模型部署。
模型部署的工作實際上是將模型預測函數搬到了線上,通常一個典型的模型預測流程如下圖所示:
模型部署時,我們需要做的事情如下:
用戶的輸入輸出使用config.json文件來定義;
預處理模塊和后處理模塊customize_service.py通過復寫TfServingBaseService模塊的相關函數來實現;
tensorflow模型需要改寫成savedModel模型;
tensorflow模型本身作為一個黑盒子,不需關心也無法關心,也就是說你無法在服務啟動后對計算圖增加節點了。
由于模型部署是在模型預測的基礎上重新定義的,所以如果模型已經寫好了預測的函數,我們就很方便的通過改寫程序來進行模型部署工作。
下面便介紹下在modelarts部署bert模型的流程。
本文部署的模型是在華為云 ModelArts-Lab AI實戰營第七期的基礎上的,請大家重走一遍案例,但務必記得,與案例不同的是,開發環境請選擇Tensorflow 1.8,中間有段安裝tensorflow 1.11的代碼也請跳過。這是因為在本文成文時,modelarts暫時只支持tensorflow 1.8的模型。本章節的所有代碼都在modelarts的notebook上完成。
執行完案例后,在./ner/output路徑下有訓練模型的結果,但這模型還不能直接用于Tensorflow Serving,我們必須先把他轉成savedModel模型。
我們先找到./ner/src/terminal_predict.py文件,直接找到預測用的主函數(為了閱讀方便,略去了不關注的代碼)。
def predict_online():
global graph
with graph.as_default():
# ...bala bala nosense
# ------用戶輸入
sentence = str(input())
# nosence again
# ------------前處理
sentence = tokenizer.tokenize(sentence)
input_ids, input_mask, segment_ids, label_ids = convert(sentence)
feed_dict = {input_ids_p: input_ids,
input_mask_p: input_mask}
# run session get current feed_dict result
# -------------使用模型(黑盒子)
pred_ids_result = sess.run([pred_ids], feed_dict)
# -------------后處理
pred_label_result = convert_id_to_label(pred_ids_result, id2label)
result = strage_combined_link_org_loc(sentence, pred_label_result[0]) # 輸出被這個函數封裝了
# something useless
在模型使用這塊,程序使用了sess這個tf.Session的實例作為全局變量調用,在程序不多的執行代碼中,可以看到主要是做了重構模型計算圖。
graph = tf.get_default_graph()
with graph.as_default():
print("going to restore checkpoint")
#sess.run(tf.global_variables_initializer())
# -----定義了模型的兩個輸出張量
input_ids_p = tf.placeholder(tf.int32, [batch_size, max_seq_length], name="input_ids")
input_mask_p = tf.placeholder(tf.int32, [batch_size, max_seq_length], name="input_mask")
bert_config = modeling.BertConfig.from_json_file(os.path.join(bert_dir, 'bert_config.json'))
# ----定義了模型的輸出張量,由于后處理只用到pred_ids,其他不管
(total_loss, logits, trans, pred_ids) = create_model(
bert_config=bert_config, is_training=False, input_ids=input_ids_p, input_mask=input_mask_p, segment_ids=None,
labels=None, num_labels=num_labels, use_one_hot_embeddings=False, dropout_rate=1.0)
saver = tf.train.Saver()
saver.restore(sess, tf.train.latest_checkpoint(model_dir))
既然sess在可執行代碼中幫我們構建好了,我們源代碼一行不動,直接引用就可以生成savedModel模型
from ner.src.terminal_predict import *
export_path = './model'
builder = tf.saved_model.builder.SavedModelBuilder(export_path)
# 將輸入張量與名稱掛鉤
signature_inputs = {
'input_ids': tf.saved_model.utils.build_tensor_info(input_ids_p),
'input_mask': tf.saved_model.utils.build_tensor_info(input_mask_p),
}
# 將輸出張量與名稱掛鉤
signature_outputs = {
'pred_ids':tf.saved_model.utils.build_tensor_info(pred_ids),
}
# 簽名定義?不懂就往下看輸出結果
classification_signature_def = tf.saved_model.signature_def_utils.build_signature_def(
inputs=signature_inputs,
outputs=signature_outputs,
method_name=tf.saved_model.signature_constants.PREDICT_METHOD_NAME)
builder.add_meta_graph_and_variables(
sess,
[tf.saved_model.tag_constants.SERVING],
signature_def_map={
'root': classification_signature_def
},
)
builder.save()
這樣就在export_path路徑下生成了savedModel模型,模型文件如下
model
├── saved_model.pb
├── variables
│?? ├── variables.index
│?? └── variables.data-00000-of-00001
生成模型后我們可以進行預測,來判斷模型是否正確
首先我們輸出signature_def
sess = tf.Session()
meta_graph_def = tf.saved_model.loader.load(sess, [tf.saved_model.tag_constants.SERVING], export_dir)
signature = meta_graph_def.signature_def
signature['root']
signature_def:
inputs {
key: "input_ids"
value {
name: "input_ids:0"
dtype: DT_INT32
tensor_shape {
dim {
size: 1
}
dim {
size: 128
}
}
}
}
inputs {
key: "input_mask"
value {
name: "input_mask:0"
dtype: DT_INT32
tensor_shape {
dim {
size: 1
}
dim {
size: 128
}
}
}
}
outputs {
key: "pred_ids"
value {
name: "ReverseSequence_1:0"
dtype: DT_INT32
tensor_shape {
dim {
size: 1
}
dim {
size: 128
}
}
}
}
method_name: "tensorflow/serving/predict"
可見這個簽名定義了模型的輸入輸出格式的信息。由于模型上線封裝后,無法獲取具體節點張量,所以輸入輸出就用節點的名稱來替代,也就是里面的key值。
接下來,我們寫個預測函數,來看看結果
# 直接照抄
def convert(line):
feature = convert_single_example(0, line, label_list, max_seq_length, tokenizer, 'p')
input_ids = np.reshape([feature.input_ids],(batch_size, max_seq_length))
input_mask = np.reshape([feature.input_mask],(batch_size, max_seq_length))
segment_ids = np.reshape([feature.segment_ids],(batch_size, max_seq_length))
label_ids =np.reshape([feature.label_ids],(batch_size, max_seq_length))
return input_ids, input_mask, segment_ids, label_ids
# 基本照抄,改變了輸出,變成dict
def strage_combined_link_org_loc_2(tokens, tags):
def print_output(data, type):
line = []
for i in data:
line.append(i.word)
return [i.word for i in data]
params = None
eval = Result(params)
if len(tokens) > len(tags):
tokens = tokens[:len(tags)]
person, loc, org = eval.get_result(tokens, tags)
return {'LOC': print_output(loc, 'LOC'),
'PER': print_output(person, 'PER'),
'ORG': print_output(org, 'ORG'),}
# 線下調用模型的函數,得自己寫,不過測試完就扔掉了
def predict(f1, f2):
x1_tensor_name = signature['root'].inputs['input_ids'].name
x2_tensor_name = signature['root'].inputs['input_mask'].name
y1_tensor_name = signature['root'].outputs['pred_ids'].name
x1 = sess.graph.get_tensor_by_name(x1_tensor_name)
x2 = sess.graph.get_tensor_by_name(x2_tensor_name)
y1 = sess.graph.get_tensor_by_name(y1_tensor_name)
y1 = sess.run(y1, feed_dict={x1:f1,x2:f2})
return y1
# 輸入
sentence = '中國男籃與委內瑞拉隊在北京五棵松體育館展開小組賽最后一場比賽的爭奪,趙繼偉12分4助攻3搶斷、易建聯11分8籃板、周琦8分7籃板2蓋帽。'
# 前處理
input_ids, input_mask, segment_ids, label_ids = convert(sentence)
# 調用模型
y1 = predict(input_ids, input_mask)
# 后處理
pred_label_result = convert_id_to_label([y1], id2label)
result = strage_combined_link_org_loc_2(sentence, pred_label_result[0])
# 輸出
result
out:
{'LOC':?['北京五棵松體育館'],?'ORG':?['中國男籃',?'委內瑞拉隊'],?'PER':?['趙繼偉',?'易建聯',?'周琦']}
以上就線下預測的模塊。線上預測大體類似,但仍還需要少量的代碼更改,以及無用的代碼塊剔除。
config.json編寫可查看規范,其中apis中以json scheme定義了用戶輸入輸出方式,也是最頭疼的地方。老山看來,apis部分描述的作用大于對程序的實際影響,如果你本身熟悉程序的輸入方式,完全可以定義最外層即可,無須對內部仔細定義。dependencies模塊除非需要特定版本或是真的用了些不常見的工程,否則可以不寫。
config.json
{
"model_algorithm": "bert_ner",
"model_type": "TensorFlow",
"runtime": "python3.6",
"apis": [
{
"procotol": "http",
"url": "/",
"method": "post",
"request": {
"Content-type": "multipart/form-data",
"data": {
"type": "object",
"properties": {
"sentence": {
"type": "string"
}
}
}
},
"response": {
"Content-type": "applicaton/json",
"data": {
"type": "object",
"properties": {
}
}
}
}
]
}
這里規范了輸入必須是{"sentence":"需要輸入的句子"}這么個格式。
這個模塊定義了預處理和后處理,重要性不可謂不重要。同樣可以找到規范,這個也是你會花費最多時間去反復修改的程序。這里老山講一下幾點經驗,方便大家參考:
程序通過新建TfServingBaseService的子類來重寫_preprocess和_postprocess函數;
如果是.py文件,正常引用便是;如果是其他文件,在類內通過self.model_path獲得路徑;
如果用后處理后需要用到前處理的變量,把該變量變成類的屬性(現在因為都是同步的,如果以后加入異步功能,這樣簡單的處理方法有可能會引起線程安全問題);
引用其他.py文件時,命名請盡量刁鉆,如(utils.py -> utils_.py, config.py -> config_.py),避免和服務本身的模塊重名;
程序盡量剪枝,一些無關程序就刪除把;
customize_service.py
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
import tensorflow as tf
import os
import numpy as np
import json
from model_service.tfserving_model_service import TfServingBaseService
import tokenization
from utils_ import convert_, convert_id_to_label, strage_combined_link_org_loc
from config_ import do_lower_case, id2label
class BertPredictService(TfServingBaseService):
def _preprocess(self, data):
tokenizer = tokenization.FullTokenizer(
vocab_file=os.path.join(self.model_path, 'vocab.txt'), do_lower_case=do_lower_case)
sentence = data['sentence']
# 把sentence保存在類中,方便后處理時調用
self.sentence = sentence
input_ids, input_mask, *_ = convert_(sentence, tokenizer)
feed = {'input_ids': input_ids.astype(np.int32),
'input_mask': input_mask.astype(np.int32)}
print("feed:", feed)
return feed
def _postprocess(self, data):
pred_ids = data['pred_ids']
pred_label_result = convert_id_to_label([pred_ids], id2label)
result = strage_combined_link_org_loc(self.sentence, pred_label_result[0])
return result
這里引用了bert自帶的tokenization模塊,config_模塊里面都是些常量,utils_模塊基本就是把terminal_predict.py里的模型相關的全刪除掉,改吧改吧弄出來的,這里不再贅述了。
在部署之前,必須安裝規范存儲在obs中,這次老山存儲的目錄如下。
obs-name
└── ocr
└── model
├── config.json
├── config_.py
├── customize_service.py
├── saved_model.pb
├── tokenization.py
├── utils_.py
├── variables
│?? ├── variables.data-00000-of-00001
│?? └── variables.index
└── vocab.txt
在modelarts控制臺上左側導航欄選擇模型管理 -> 模型列表,在中間的模型列表中選擇導入
在導入模型頁面上,修改名稱,在元模型來源選擇從OBS中選擇,選擇元模型的路徑后,點擊立刻創建。
返回模型列表,等待模型狀態變成正常
在模型列表中選擇創建的模型,選擇部署
在部署界面上,只要更改名稱即可,然后就是一直確定,直到開始部署
部署會需要2-3分鐘不等的時間,等待部署成功后,點擊服務的名稱,進入在線服務的頁面
在線服務頁面選擇預測標簽欄,輸入預測代碼:{"sentence":"中國男籃與委內瑞拉隊在北京五棵松體育館展開小組賽最后一場比賽的爭奪,趙繼偉12分4助攻3搶斷、易建聯11分8籃板、周琦8分7籃板2蓋帽。"},點擊預測,在返回結果處可以看到結果與之前模型測試結果相同。
在調用指南標簽頁中給出了服務的API接口地址
官方文檔介紹了如何使用Postman和curl調用API接口,大家自行查閱,老山這個給出的是如何使用python來調用API。
首先是選擇認證方式。一個是AK/SK認證,也就是每次調用都直接使用AK/SK來請求,無疑要對AK/SK進行加密,這意味著基本上不折騰的方式就是使用官方的模塊。另外一種是X-Auth-Token認證,有時效,每次使用X-Auth-Token調用請求即可,但在獲取X-Auth-Token時請求結構體中要明文方式輸入賬戶和密碼,安全性上還值得商榷。但這里自然是選用第二種方法,相對靈活些。
首先是請求X-Auth-Token
import requests
import json
url = "https://iam.cn-north-1.myhuaweicloud.com/v3/auth/tokens"
headers = {"Content-Type":"application/json"}
data = {
"auth": {
"identity": {
"methods": ["password"],
"password": {
"user": {
"name": "your-username", # 賬戶
"password": "your-password", # 密碼
"domain": {
"name": "your-domainname:normally equal to your-username" #域賬戶,普通賬戶這里就還是填賬戶
}
}
}
},
"scope": {
"project": {
"name": "cn-north-1"
}
}
}
}
data = json.dumps(data)
r = requests.post(url, data = data, headers = headers)
print(r.headers['X-Subject-Token'])
data具體參數基本上就是賬號和密碼,具體細節可參考官網。
程序最后獲得的便是X-Auth-Token認證碼。獲得認證碼后便可進行預測了。
config.py
X_Auth_Token = "MIIZpAYJKoZIhvcNAQcCoIIZlTCC..." # 前面獲取的X-Auth-Token值
url = "https://39ae62200d7f439eaae44c7cabccf5de.apig..." #在調用指南頁面獲取的url值
predict.py
import requests
from config import url, X_Auth_Token
import json
def bertService(sentence):
data = {"sentence":sentence}
data = json.dumps(data)
headers = {"content-type": "application/json", 'X-Auth-Token': X_Auth_Token}
response = requests.request("POST", url, data = data, headers=headers)
return response.text
if __name__ == "__main__":
print(bertService('中國男籃與委內瑞拉隊在北京五棵松體育館展開小組賽最后一場比賽的爭奪,趙繼偉12分4助攻3搶斷、易建聯11分8籃板、周琦8分7籃板2蓋帽。'))
輸出結果:
{"LOC":?["北京五棵松體育館"],?"PER":?["趙繼偉",?"易建聯",?"周琦"],?"ORG":?["中國男籃",?"委內瑞拉隊"]}
當不需要使用服務時,請點擊在線服務頁面右上角的停止,以避免產生不必要的費用。
附件: 部署文件.zip 8.2KB 下載次數:48次
模型部署 ModelArts
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。