模型部署介绍

当我们通过深度学习完成模型训练后,有时希望能将模型落地于生产,能开发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 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文件生成

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_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调用

在调用指南标签页中给出了服务的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

作者:山找海味

使用modelarts部署bert命名实体识别模型的更多相关文章

  1. 通俗理解BiLSTM-CRF命名实体识别模型中的CRF层

    虽然网上的文章对BiLSTM-CRF模型介绍的文章有很多,但是一般对CRF层的解读比较少. 于是决定,写一系列专门用来解读BiLSTM-CRF模型中的CRF层的文章. 我是用英文写的,发表在了gith ...

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

    BERT-BiLSMT-CRF-NERTensorflow solution of NER task Using BiLSTM-CRF model with Google BERT Fine-tuni ...

  3. 抛弃模板,一种Prompt Learning用于命名实体识别任务的新范式

    原创作者 | 王翔 论文名称: Template-free Prompt Tuning for Few-shot NER 文献链接: https://arxiv.org/abs/2109.13532 ...

  4. 命名实体识别,使用pyltp提取文本中的地址

    首先安装pyltp pytlp项目首页 单例类(第一次调用时加载模型) class Singleton(object): def __new__(cls, *args, **kwargs): if n ...

  5. 『深度应用』NLP命名实体识别(NER)开源实战教程

    近几年来,基于神经网络的深度学习方法在计算机视觉.语音识别等领域取得了巨大成功,另外在自然语言处理领域也取得了不少进展.在NLP的关键性基础任务—命名实体识别(Named Entity Recogni ...

  6. 使用哈工大LTP进行文本命名实体识别并保存到txt

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明.本文链接:https://blog.csdn.net/broccoli2/article/det ...

  7. 用深度学习做命名实体识别(六)-BERT介绍

    什么是BERT? BERT,全称是Bidirectional Encoder Representations from Transformers.可以理解为一种以Transformers为主要框架的双 ...

  8. 基于bert的命名实体识别,pytorch实现,支持中文/英文【源学计划】

    声明:为了帮助初学者快速入门和上手,开始源学计划,即通过源代码进行学习.该计划收取少量费用,提供有质量保证的源码,以及详细的使用说明. 第一个项目是基于bert的命名实体识别(name entity ...

  9. HMM(隐马尔科夫模型)与分词、词性标注、命名实体识别

    转载自 http://www.cnblogs.com/skyme/p/4651331.html HMM(隐马尔可夫模型)是用来描述隐含未知参数的统计模型,举一个经典的例子:一个东京的朋友每天根据天气{ ...

随机推荐

  1. 常用的webpack优化方法

    1. 前言 关于webpack,相信现在的前端开发人员一定不会陌生,因为它已经成为前端开发人员必不可少的一项技能,它的官方介绍如下: webpack 是一个模块打包器.webpack的主要目标是将 J ...

  2. 简单搭建DNS服务器——bind

    1安装bind yum install -y bind bind-utils bind-chroot 2 修改配置文件 # grep '^[^#]' /etc/named.conf options { ...

  3. servlet三大组件

    servlet大致可以分为三个:简单servlet.过滤servlet.监听servlet servlet: servlet的创建 创建一个类并实现Servlet接口. 重写service方法. 在服 ...

  4. 架构设计:"4+1"视图

    概念 "4+1"视图,是指从5个不同视角来描述软件体系结构. "4+1"分别指: 逻辑视图 过程视图 物理视图 开发视图 场景/用例 视图 逻辑架构的描述可以围 ...

  5. 第一篇: openJDK源码编译安装--mac版本

    1.为什么要编译JDK 想要一探JDK内部的实现机制,最便捷的路径之一就是自己编译一套JDK,通过阅读和跟踪调试JDK源码去了解Java技术体系的原理,虽然门槛高一点,但肯定比阅读各种书籍,文章,博客 ...

  6. C#winfrom打开指定的文件

    直接打开指定的文件 System.Diagnostics.Process.Start(v_OpenFilePath); 直接打开目录 string v_OpenFolderPath = @" ...

  7. 【集合系列】- 深入浅出的分析TreeMap

    一.摘要 在集合系列的第一章,咱们了解到,Map的实现类有HashMap.LinkedHashMap.TreeMap.IdentityHashMap.WeakHashMap.Hashtable.Pro ...

  8. Composer依赖管理 – PHP的利器

    别再到处搜PHP类扩展包了,对于现代语言而言,包管理器基本上是标配.Java 有 Maven,Python 有 pip,Ruby 有 gem,Nodejs 有 npm.PHP 的则是 PEAR,不过 ...

  9. 20191010-3 alpha week 1/2 Scrum立会报告+燃尽图 01

    此作业要求参见https://edu.cnblogs.com/campus/nenu/2019fall/homework/8746 一.小组情况 组长:迟俊文 组员:宋晓丽 梁梦瑶 韩昊 刘信鹏 队名 ...

  10. 使用Xdroid进行端口映射,出现adb server version (36) doesn't match this client (39); killing...的解决方案

    第一反应就是adb冲突了,因为Xdroid这个产品看起来就不像是给开发人员用的模拟器,因为不能选择各种版本进行适配,所以肯定自带了一个adb. whereis命令发现果然有两个adb,一个直接是安装在 ...