模型部署介绍

当我们通过深度学习完成模型训练后,有时希望能将模型落地于生产,能开发API接口被终端调用,这就涉及了模型的部署工作。Modelarts支持对tensorflow,mxnet,pytorch等模型的部署和在线预测,这里老山介绍下tensorflow的模型部署。

模型部署的工作实际上是将模型预测函数搬到了线上,通常一个典型的模型预测流程如下图所示:

模型部署时,我们需要做的事情如下:

  1. 用户的输入输出使用config.json文件来定义;

  2. 预处理模块和后处理模块customize_service.py通过复写TfServingBaseService模块的相关函数来实现;

  3. tensorflow模型需要改写成savedModel模型;

  4. tensorflow模型本身作为一个黑盒子,不需关心也无法关心,也就是说你无法在服务启动后对计算图增加节点了。

由于模型部署是在模型预测的基础上重新定义的,所以如果模型已经写好了预测的函数,我们就很方便的通过改写程序来进行模型部署工作。

下面便介绍下在modelarts部署bert模型的流程。

savedModel模型生成

本文部署的模型是在华为云 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 graphwith 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_INT32tensor_shape {dim {size: 1}dim {size: 128}}}
}
inputs {key: "input_mask"value {name: "input_mask:0"dtype: DT_INT32tensor_shape {dim {size: 1}dim {size: 128}}}
}
outputs {key: "pred_ids"value {name: "ReverseSequence_1:0"dtype: DT_INT32tensor_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 = Noneeval = 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'].namex2_tensor_name = signature['root'].inputs['input_mask'].namey1_tensor_name = signature['root'].outputs['pred_ids'].namex1 = 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文件生成

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":"需要输入的句子"}这么个格式。

customize_service.py生成

这个模块定义了预处理和后处理,重要性不可谓不重要。同样可以找到规范,这个也是你会花费最多时间去反复修改的程序。这里老山讲一下几点经验,方便大家参考:

  1. 程序通过新建TfServingBaseService的子类来重写_preprocess和_postprocess函数;

  2. 如果是.py文件,正常引用便是;如果是其他文件,在类内通过self.model_path获得路径;

  3. 如果用后处理后需要用到前处理的变量,把该变量变成类的属性(现在因为都是同步的,如果以后加入异步功能,这样简单的处理方法有可能会引起线程安全问题);

  4. 引用其他.py文件时,命名请尽量刁钻,如(utils.py -> utils_.py, config.py -> config_.py),避免和服务本身的模块重名;

  5. 程序尽量剪枝,一些无关程序就删除把;

customize_service.py

from __future__ import absolute_import
from __future__ import division
from __future__ import print_functionimport tensorflow as tf
import os
import numpy as np
import jsonfrom model_service.tfserving_model_service import TfServingBaseServiceimport tokenization
from utils_ import convert_, convert_id_to_label, strage_combined_link_org_loc
from config_ import do_lower_case, id2labelclass 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 = sentenceinput_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 feeddef _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调用

在调用指南标签页中给出了服务的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 jsondef 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.textif __name__ == "__main__":print(bertService('中国男篮与委内瑞拉队在北京五棵松体育馆展开小组赛最后一场比赛的争夺,赵继伟12分4助攻3抢断、易建联11分8篮板、周琦8分7篮板2盖帽。'))

输出结果:

{"LOC": ["北京五棵松体育馆"], "PER": ["赵继伟", "易建联", "周琦"], "ORG": ["中国男篮", "委内瑞拉队"]}

关闭服务

当不需要使用服务时,请点击在线服务页面右上角的停止,以避免产生不必要的费用。

部署文件.zip

作者:山找海味

使用modelarts部署bert命名实体识别模型相关推荐

  1. 推荐30个以上比较好的命名实体识别模型

    命名实体识别模型是指识别文本中提到的特定的人名.地名.机构名等命名实体的模型.推荐的命名实体识别模型有: BERT(Bidirectional Encoder Representations from ...

  2. 推荐30个以上比较好的命名实体识别模型github源码?

    命名实体识别是自然语言处理中的一个重要任务,也是比较经典的应用.这里推荐几个比较流行的命名实体识别模型的GitHub源码: BERT-NER:基于BERT的命名实体识别模型,使用了CRF层来解码,在很 ...

  3. 知识图谱 基于CRF的命名实体识别模型

    基于CRF的命名实体识别模型 条件随机场 CRF ​ 条件随机场 CRF 是在已知一组输入随机变量条件的情况下,输出另一组随机变量的条件概率分布模型:其前提是假设输出随机变量构成马尔可夫随机场:条件随 ...

  4. 第15课:基于 CRF 的中文命名实体识别模型实现

    命名实体识别在越来越多的场景下被应用,如自动问答.知识图谱等.非结构化的文本内容有很多丰富的信息,但找到相关的知识始终是一个具有挑战性的任务,命名实体识别也不例外. 前面我们用隐马尔可夫模型(HMM) ...

  5. 基于BERT预训练的中文命名实体识别TensorFlow实现

    BERT-BiLSMT-CRF-NER Tensorflow solution of NER task Using BiLSTM-CRF model with Google BERT Fine-tun ...

  6. BERT-BiLSTM-CRF基于BERT预训练的中文命名实体识别TensorFlow实现

    向AI转型的程序员都关注了这个号???????????? 机器学习AI算法工程   公众号:datayx Tensorflow solution of NER task Using BiLSTM-CR ...

  7. 用深度学习做命名实体识别(五)-模型使用

    通过本文,你将了解如何基于训练好的模型,来编写一个rest风格的命名实体提取接口,传入一个句子,接口会提取出句子中的人名.地址.组织.公司.产品.时间信息并返回. 核心模块entity_extract ...

  8. 用深度学习做命名实体识别(四)——模型训练

    通过本文你将了解如何训练一个人名.地址.组织.公司.产品.时间,共6个实体的命名实体识别模型. 准备训练样本 下面的链接中提供了已经用brat标注好的数据文件以及brat的配置文件,因为标注内容较多放 ...

  9. 基于BERT+BiLSTM+CRF的中文景点命名实体识别

    赵平, 孙连英, 万莹, 葛娜. 基于BERT+BiLSTM+CRF的中文景点命名实体识别. 计算机系统应用, 2020, 29(6): 169-174.http://www.c-s-a.org.cn ...

最新文章

  1. 新上映的电影不在影院也一样能看到
  2. LoadRunner11_录制脚本时的浏览器版本
  3. Linux中环境变量文件profile、bashrc、bash_profile之间的区别和联系
  4. 图解Windows下开发Objective-C程序之一 - 搭建Objective-C开发环境
  5. fiddler抓包工具简介
  6. HashMap深度解析:一文让你彻底了解HashMap
  7. tensorflow 遇到的细节问题
  8. javaWEB知识总结——Ajax和Json
  9. Synchronized与ReentrantLock区别总结(简单粗暴,一目了然)
  10. 菜鸟程序员成长之路(七)——2020年,你奋斗了吗?
  11. Geforce 错误代码 ERROR CODE:0x0003问题方法
  12. 两台计算机互相共享一个网络,两台电脑共用一个路由器上网,但两台电脑不能互相访问共享,怎样设置啊?两台电脑系统都XP的...
  13. 企业邮箱哪个最好用?企业邮箱哪个安全?
  14. 计算机硬件工程师面试题集,硬件工程师笔试及面试问题
  15. 《富爸爸财务自由之路》阅读笔记
  16. easyrecovery新版64位下载一键轻松找回丢失数据
  17. java练习题---前五章
  18. 计算机2016基础知识,计算机基础知识2016.doc
  19. ai绘画,初级召唤师教程
  20. vue 实现前端excel导出表格携带token的两种方法

热门文章

  1. 投入3.6亿美元!加拿大启动国家量子战略
  2. 先验概率和后验概率的定义
  3. CSS3新特性总结及CSS组件、特效汇总
  4. python wasm_Go WebAssembly (Wasm) 简明教程
  5. Redis分片代理twemproxy快速搭建 | twemproxy Demo | twitter/ twemproxy 避坑指南 | autoconf-2.69下载
  6. 得了糖尿病就得戒水果?
  7. 提升deepin下2.4G频段WiFi网速
  8. iOS10适配及Xcode8配置
  9. it计算机工资,什么是it技术(it工资一般多少)
  10. android watch,android watch介绍