本
文
摘
要
数据样本
样本大概是这样的
question: 罗显敏的出生地是哪里 passage: 罗显敏,男,1989年11月19日出生,户籍地:广西壮族自治区宾阳县宾州镇顾明村委会大罗村2队513号,身份证号码:452123198911195219 answer: 广西壮族自治区宾阳县宾州镇顾明村委会大罗村2队513号模型
模型主要分为四层:Embed层、Encode层、Interaction层和Answer层。
Embed层负责将原文和问题中的tokens映射为向量表示。采用的是字+词+position embedding的混合向量表示方式。其中字向量随机初始化,而词向量采用预训练的百度百科词向量。Encode层主要对原文和问题进行编码,这样编码后每个token的向量表示就蕴含了上下文的语义信息。一般来说,会采用RNN对文本进行encoding,但是由于RNN训练速度比较慢,最终我们采用了膨胀卷积对输入文本进行encoding,由于CNN对位置信息并不敏感,因此我们在embed层还加入了position embedding作为补充。Interaction层主要负责捕捉问题和原文的上下文关系,并输出编码成相应的向量表示,即query-aware的原文表示。最终采用了BIDAF中的Attention计算方式。最后的Answer层则基于query-aware的原文表示来预测答案,与Squad数据不同,样本的answer可能是多个的,因此不能简单的预测answer的起始与终止位置。为了能够预测出多个答案,answer层的输出最终采用BIO的序列标注方式。整个流程如下图所示[绿色和红色是其它一些人工特征]:
服务
了解到目前深度学习的部署方式大概有两种,分别为:
将模型保存为pb形式,然后直接用C++或者java的API去调用使用docker + tf serving + web服务器的形式对于第一种,参考链接为:用keras训练模型并用Tensorflow的C++API调用模型
对于第二种,参考链接为:keras、tensorflow serving踩坑记;TensorFlow Serving + Docker + Tornado机器学习模型生产级快速部署;使用 TensorFlow Serving 和 Docker 快速部署机器学习服务;[译]TensorFlow Serving RESTful API
目前采用的是第二种形式,
由于只是测试,所以先省略了web服务器那一个环节
中间也遇到的几个坑,主要流程为:
1. 将模型保存为tf serving需要的格式,流程:keras训练保存为.h5文件 -> load h5文件 ->保存为pb格式
加载模型:
坑1:如果训练时,仅仅是model.add_loss(loss),没有在compile中指定loss,即model.compile(optimizer=Adma(1e-3), loss=None),那么在load_model时就是报错model has no loss类型的错误
坑2:load_model的时候,如果模型中存在自定义的层或者loss,那么在load的时候一定要指定,即:model = load_model(./tf_weight_version_5/best_model.h5, custom_objects={ MixEmbedding: MixEmbedding, DilatedGatedConv1D: DilatedGatedConv1D, Similarity: Similarity, C2QAttention: C2QAttention, Q2CAttention: Q2CAttention, MergedContext: MergedContext, customize_loss: customize_loss })构建签名# 这个signature的作用就是指定了请求的输入输出,那么最后部署完成请求模型时, # 给定对应签名的输入,得到对应签名的输出 # Keras signature = tf.saved_model.signature_def_utils.predict_signature_def( inputs = {q1_in: model.input[0], q2_in: model.input[1], p1_in: model.input[2], p2_in: model.input[3], k1_in: model.input[4], k2_in: model.input[5], pos_in: model.input[6]}, outputs = {pa: model.output} # Tensorflow(一个简单的NER)形式的为: signature = tf.saved_model.signature_def_utils.build_signature_def( inputs={ word_ids: tf.saved_model.utils.build_tensor_info(model.word_ids), sequence_lengths: tf.saved_model.utils.build_tensor_info(model.sequence_lengths), dropout_pl: tf.saved_model.utils.build_tensor_info(model.dropout_pl) }, outputs={ logits: tf.saved_model.utils.build_tensor_info(model.logits), transition_params: tf.saved_model.utils.build_tensor_info(model.transition_params) }, method_name=tf.saved_model.signature_constants.PREDICT_METHOD_NAME )确定名字与版本# Keras # 其中prod_models是模型名(最终呈现的是一个文件夹名),1是模型的版本 export_path = os.path.join( tf.compat.as_bytes(./tf_weight_version_5/prod_models), tf.compat.as_bytes(str(1)) ) # Tensorflow export_path_base = ./data_path_save/1597133881/serving/infoext model_version = 1 export_path = os.path.join( tf.compat.as_bytes(export_path_base), tf.compat.as_bytes(str(model_version)))创建builder# Keras # 关于这个legacy_init_op,目前还没搞懂,,貌似高版本的不用? builder = tf.saved_model.builder.SavedModelBuilder(export_path) legacy_init_op = tf.group(tf.tables_initializer(), name=legacy_init_op) # Tensorflow builder = tf.saved_model.builder.SavedModelBuilder(export_path)添加meta_graph并保存# signature_def_map对应一个签名map,一般要定义一个默认的签名, # 即DEFAULT_SERVING_SIGNATURE_DEF_KEY # 关于这个签名,可以理解是一个模型可以定义多组不同功能的输入输出,然后对应到不同的signature # 然后signature_def_map的作用就是为每个signature起一个名字 # 然后在发送请求时,指定不同的signature名字,就对应着不同的功能 # Keras builder.add_meta_graph_and_variables( sess=K.get_session(), tags=[tf.saved_model.tag_constants.SERVING], signature_def_map={ qa: signature, tf.saved_model.signature_constants.DEFAULT_SERVING_SIGNATURE_DEF_KEY: signature }, legacy_init_op=legacy_init_op ) builder.save() # Tensorflow builder.add_meta_graph_and_variables( sess, [tf.saved_model.tag_constants.SERVING], signature_def_map={ infoext: signature, tf.saved_model.signature_constants.DEFAULT_SERVING_SIGNATURE_DEF_KEY: signature }) builder.save()2. 利用docker启动tf serving
由于目前没有用到GPU,所以也就没有nvidia-docker,docker拉取镜像(cpu稳定版)docker pull tensorflow/serving:1.8.0docker启动tf servingdocker run -p 8500:8500 -p 8501:8501 --name tfserving_qa --mount type=bind, source=/Users/sunlianjie/pyproject/audit_shenhai/dgcnn_bio/tf_weight_version_5/prod_models, target=/models/prod_models -e MODEL_NAME=prod_models -t tensorflow/serving:1.8.0 8500为RPC端口、8501为rest api端口 --name docker容器的名字 --mount 将docker目录与实际路径对应,prod_models为上述对应的保存的模型名字 -e MODEL_NAME 模型名字 -t 对应的镜像3. 请求
请求URL = http://localhost:8501/v1/models/prod_models:predict,rest API的请求方式有predict,regression,classification三种,每一种都有具体的请求json格式与返回json格式,详见[译]TensorFlow Serving RESTful API
坑1:构造请求时,需要json.dumps# 如果没有json.dumps,那么就会报请求体错误,400错误码 predict_request = json.dumps({"signature_name": qa, "instances": [{q1_in: Q1.tolist(), q2_in: Q2.tolist(), p1_in: P1.tolist(), p2_in: P2.tolist(), k1_in: K1.tolist(), k2_in: K2.tolist(), pos_in: POS.tolist()}]})坑2:构建每一个样本时,是不需要加batch_size那一维的但是在构建输入时,例如p1_in是(-1, -1),第一个-1是batch_size, 但是在构建样本时batch_size那一维不要加(自己加就报维度错误), saved_model_cli show --dir ./prod_models/1 --all 结果为(太长没复制全): signature_def[qa]: The given SavedModel SignatureDef contains the following input(s): inputs[k1_in] tensor_info: dtype: DT_FLOAT shape: (-1, 1) name: input_5:0 inputs[k2_in] tensor_info: dtype: DT_FLOAT shape: (-1, 1) name: input_6:0 inputs[p1_in] tensor_info: dtype: DT_FLOAT shape: (-1, -1) name: input_3:0