tensorflow(8)將h5文件轉化為pb文件並利用tensorflow/serving實現模型部署

2021-03-02 NLP奇幻之旅

  在文章NLP(三十四)使用keras-bert實現序列標註任務中,我們使用Keras和Keras-bert進行模型訓練、模型評估和模型預測。我們對人民日報實體數據集進行模型訓練,保存後的模型文件為example.h5,h5是Keras保存模型的一種文件格式。
  在文章Keras入門(七)使用Flask+Keras-bert構建模型預測服務,我們也介紹了如何使用Flask和example.h5文件來實現模型預測的HTTP服務。
  本文將會介紹如何將h5文件轉化為pb文件並利用tensorflow/serving實現模型部署。

將h5文件轉化為pb文件

  在Github項目keras_to_tensorflow(網址為:https://github.com/amir-abdi/keras_to_tensorflow)中,有專門介紹如何將普通的keras模型轉化為tensorflow模型的辦法。本文在此基礎上,略微修改轉換的腳本(change_keras_h5_file_to_pb_models.py)如下:

# -*- coding: utf-8 -*-
import os
import tensorflow as tf
from tensorflow.python.framework import graph_util
from tensorflow.python.framework import graph_io
from pathlib import Path
from absl import app
from absl import flags
from absl import logging
import keras
from keras import backend as K
from keras.models import model_from_json

from keras_bert import get_custom_objects
from keras_contrib.layers import CRF
from keras_contrib.losses import crf_loss
from keras_contrib.metrics import crf_accuracy

custom_objects = get_custom_objects()
for key, value in {'CRF': CRF, 'crf_loss': crf_loss, 'crf_accuracy': crf_accuracy}.items():
    custom_objects[key] = value

K.set_learning_phase(0)
FLAGS = flags.FLAGS

flags.DEFINE_string('input_model', "../example_ner.h5", 'Path to the input model.')
flags.DEFINE_string('input_model_json', None, 'Path to the input model '
                                              'architecture in json format.')
flags.DEFINE_string('output_model', "./example_ner.pb", 'Path where the converted model will '
                                          'be stored.')
flags.DEFINE_boolean('save_graph_def', False,
                     'Whether to save the graphdef.pbtxt file which contains '
                     'the graph definition in ASCII format.')
flags.DEFINE_string('output_nodes_prefix', None,
                    'If set, the output nodes will be renamed to '
                    '`output_nodes_prefix`+i, where `i` will numerate the '
                    'number of of output nodes of the network.')
flags.DEFINE_boolean('quantize', False,
                     'If set, the resultant TensorFlow graph weights will be '
                     'converted from float into eight-bit equivalents. See '
                     'documentation here: '
                     'https://github.com/tensorflow/tensorflow/tree/master/tensorflow/tools/graph_transforms')
flags.DEFINE_boolean('channels_first', False,
                     'Whether channels are the first dimension of a tensor. '
                     'The default is TensorFlow behaviour where channels are '
                     'the last dimension.')
flags.DEFINE_boolean('output_meta_ckpt', False,
                     'If set to True, exports the model as .meta, .index, and '
                     '.data files, with a checkpoint file. These can be later '
                     'loaded in TensorFlow to continue training.')

flags.mark_flag_as_required('input_model')
flags.mark_flag_as_required('output_model')


def load_model(input_model_path, input_json_path):
    if not Path(input_model_path).exists():
        raise FileNotFoundError(
            'Model file `{}` does not exist.'.format(input_model_path))
    try:
        # 下面一行已經修改,在改回普通的Keras加載模型時,需要去掉custom_objects
        model = keras.models.load_model(input_model_path, custom_objects=custom_objects)
        return model
    except FileNotFoundError as err:
        logging.error('Input mode file (%s) does not exist.', FLAGS.input_model)
        raise err
    except ValueError as wrong_file_err:
        if input_json_path:
            if not Path(input_json_path).exists():
                raise FileNotFoundError(
                    'Model description json file `{}` does not exist.'.format(
                        input_json_path))
            try:
                model = model_from_json(open(str(input_json_path)).read())
                model.load_weights(input_model_path)
                return model
            except Exception as err:
                logging.error("Couldn't load model from json.")
                raise err
        else:
            logging.error(
                'Input file specified only holds the weights, and not '
                'the model definition. Save the model using '
                'model.save(filename.h5) which will contain the network '
                'architecture as well as its weights. If the model is '
                'saved using model.save_weights(filename), the flag '
                'input_model_json should also be set to the '
                'architecture which is exported separately in a '
                'json format. Check the keras documentation for more details '
                '(https://keras.io/getting-started/faq/)')
            raise wrong_file_err


def main(args):
    logging.info("begin====================================================")
    # If output_model path is relative and in cwd, make it absolute from root
    output_model = FLAGS.output_model
    if str(Path(output_model).parent) == '.':
        output_model = str((Path.cwd() / output_model))

    output_fld = Path(output_model).parent
    output_model_name = Path(output_model).name
    output_model_stem = Path(output_model).stem
    output_model_pbtxt_name = output_model_stem + '.pbtxt'

    # Create output directory if it does not exist
    # print (Path(output_model).parent)
    if not os.path.exists(str(Path(output_model).parent)):
        Path(output_model).parent.mkdir(parents=True)

    if FLAGS.channels_first:
        K.set_image_data_format('channels_first')
    else:
        K.set_image_data_format('channels_last')

    model = load_model(FLAGS.input_model, FLAGS.input_model_json)

    input_node_names = [node.op.name for node in model.inputs]
    logging.info('Input nodes names are: %s', str(input_node_names))

    # TODO(amirabdi): Support networks with multiple inputs
    orig_output_node_names = [node.op.name for node in model.outputs]
    if FLAGS.output_nodes_prefix:  # 給模型節點編號
        num_output = len(orig_output_node_names)
        pred = [None] * num_output
        converted_output_node_names = [None] * num_output

        # Create dummy tf nodes to rename output
        for i in range(num_output):
            converted_output_node_names[i] = '{}{}'.format(
                FLAGS.output_nodes_prefix, i)
            pred[i] = tf.identity(model.outputs[i],
                                  name=converted_output_node_names[i])
    else:
        converted_output_node_names = orig_output_node_names
    logging.info('Converted output node names are: %s',
                 str(converted_output_node_names))

    sess = K.get_session()
    if FLAGS.output_meta_ckpt:  # 讓轉化的模型可以繼續被訓練
        saver = tf.train.Saver()
        saver.save(sess, str(output_fld / output_model_stem))

    if FLAGS.save_graph_def:  # 以ascii形式存儲模型
        tf.train.write_graph(sess.graph.as_graph_def(), str(output_fld),
                             output_model_pbtxt_name, as_text=True)
        logging.info('Saved the graph definition in ascii format at %s',
                     str(Path(output_fld) / output_model_pbtxt_name))

    if FLAGS.quantize:  # 將權重從float轉為八位比特
        from tensorflow.tools.graph_transforms import TransformGraph
        transforms = ["quantize_weights", "quantize_nodes"]
        transformed_graph_def = TransformGraph(sess.graph.as_graph_def(), [],
                                               converted_output_node_names,
                                               transforms)
        constant_graph = graph_util.convert_variables_to_constants(
            sess,
            transformed_graph_def,
            converted_output_node_names)
    else:  # float形式存儲權重
        constant_graph = graph_util.convert_variables_to_constants(
            sess,
            sess.graph.as_graph_def(),
            converted_output_node_names)

    graph_io.write_graph(constant_graph, str(output_fld), output_model_name,
                         as_text=False)
    logging.info('Saved the freezed graph at %s',
                 str(Path(output_fld) / output_model_name))


if __name__ == "__main__":
    app.run(main)

在該腳本中,input_model(輸入模型)的路徑為../example_ner.h5,output_model(輸出模型)的路徑為./example_ner.pb,同時加載了keras-bert訓練好的模型example_ner.h5。運行上述腳本,會在當前路徑下生成example_ner.pb,但很可惜,只有這一個腳本還無法實現在tensorflow/serving上的部署。
  再接再厲!我們將上述生成的pb文件轉化為tensorflow/serving支持的文件格式。轉化的腳本(get_tf_serving_file.py)如下:

# -*- coding: utf-8 -*-
import os
import tensorflow as tf
from tensorflow.python.saved_model import signature_constants
from tensorflow.python.saved_model import tag_constants
from keras_bert import get_custom_objects
from keras_contrib.layers import CRF
from keras_contrib.losses import crf_loss
from keras_contrib.metrics import crf_accuracy
from keras.models import load_model

custom_objects = get_custom_objects()
for key, value in {'CRF': CRF, 'crf_loss': crf_loss, 'crf_accuracy': crf_accuracy}.items():
    custom_objects[key] = value

export_dir = '../example_ner/1'
graph_pb = './example_ner.pb'
model = load_model('../example_ner.h5', custom_objects=custom_objects)

builder = tf.saved_model.builder.SavedModelBuilder(export_dir)
with tf.gfile.GFile(graph_pb, "rb") as f:
    graph_def = tf.GraphDef()
graph_def.ParseFromString(f.read())


sigs = {}
with tf.Session(graph=tf.Graph()) as sess:
    tf.import_graph_def(graph_def, name="")

    g = tf.get_default_graph()

    sigs[signature_constants.DEFAULT_SERVING_SIGNATURE_DEF_KEY] = \
        tf.saved_model.signature_def_utils.predict_signature_def(
            inputs={"input_1": g.get_operation_by_name('input_1').outputs[0], "input_2": g.get_operation_by_name('input_2').outputs[0]},
            outputs={"output": g.get_operation_by_name('crf_1/one_hot').outputs[0]}
        )

    builder.add_meta_graph_and_variables(sess,
                                        [tag_constants.SERVING],
                                        signature_def_map = sigs)

    builder.save()

運行上述腳本,會在上級目錄生成example_ner/1文件夾,結構如下:

example_ner
└── 1
    ├── saved_model.pb
    └── variables

2 directories, 1 file

  至此,我們已經生成了tensorflow/serving支持的模型部署的文件格式。

利用tensorflow/serving實現模型部署

  在文章tensorflow(5)將ckpt轉化為pb文件並利用tensorflow/serving實現模型部署及預測、tensorflow(6)利用tensorflow/serving實現模型部署及預測、tensorflow(7)利用tensorflow/serving實現BERT模型部署中,筆者已經給出了不少tensorflow/serving的使用說明,這裡不再詳細講述。
  利用tensorflow/serving部署example_ner模型的命令如下:

docker run -t --rm -p 8561:8501 -v "$path/example_ner:/models/example_ner" -e MODEL_NAME=example_ner tensorflow/serving:1.14.0

  模型調用腳本如下:

# -*- coding: utf-8 -*-
import json
import requests
import numpy as np
from pprint import pprint
from keras_bert import Tokenizer


# 讀取label2id字典
with open("../example_label2id.json", "r", encoding="utf-8") as h:
    label_id_dict = json.loads(h.read())

id_label_dict = {v: k for k, v in label_id_dict.items()}


# 載入數據
dict_path = '../chinese_L-12_H-768_A-12/vocab.txt'
token_dict = {}
with open(dict_path, 'r', encoding='utf-8') as reader:
    for line in reader:
        token = line.strip()
        token_dict[token] = len(token_dict)


class OurTokenizer(Tokenizer):
    def _tokenize(self, text):
        R = []
        for c in text:
            if c in self._token_dict:
                R.append(c)
            else:
                R.append('[UNK]')
        return R


# 將BIO序列轉化為JSON格式
def bio_to_json(string, tags):
    item = {"string": string, "entities": []}
    entity_name = ""
    entity_start = 0
    iCount = 0
    entity_tag = ""

    for c_idx in range(min(len(string), len(tags))):
        c, tag = string[c_idx], tags[c_idx]
        if c_idx < len(tags)-1:
            tag_next = tags[c_idx+1]
        else:
            tag_next = ''

        if tag[0] == 'B':
            entity_tag = tag[2:]
            entity_name = c
            entity_start = iCount
            if tag_next[2:] != entity_tag:
                item["entities"].append({"word": c, "start": iCount, "end": iCount + 1, "type": tag[2:]})
        elif tag[0] == "I":
            if tag[2:] != tags[c_idx-1][2:] or tags[c_idx-1][2:] == 'O':
                tags[c_idx] = 'O'
                pass
            else:
                entity_name = entity_name + c
                if tag_next[2:] != entity_tag:
                    item["entities"].append({"word": entity_name, "start": entity_start, "end": iCount + 1, "type": entity_tag})
                    entity_name = ''
        iCount += 1
    return item


tokenizer = OurTokenizer(token_dict)

# 測試HTTP響應時間
sentence = "井上雄彥的《灌籃高手》是一部作品,也是他自己的修行之路。"
token_ids, segment_is = tokenizer.encode(sentence, max_len=128)
tensor = {"instances": [{"input_1": token_ids, "input_2": segment_is}]}

url = "http://192.168.1.193:8561/v1/models/example_ner:predict"
req = requests.post(url, json=tensor)
if req.status_code == 200:
    t = np.asarray(req.json()['predictions'][0]).argmax(axis=1)
    tags = [id_label_dict[_] for _ in t]
    pprint(bio_to_json(sentence, tags[1:-1]))

輸出結果如下:

{'entities': [{'end': 4, 'start': 0, 'type': 'PER', 'word': '井上雄彥'}],
 'string': '井上雄彥的《灌籃高手》是一部作品,也是他自己的修行之路。'}

總結

  本文演示的所有腳本已經上傳至https://github.com/percent4/keras_bert_sequence_labeling/tree/master/h5_2_tensorflow_serving 。
  2021.1.16於上海浦東

相關焦點

  • tensorflow(7)利用tensorflow/serving實現BERT模型部署
    本文將會詳細介紹如何使用tensorflow/serving來實現BERT模型的部署及預測。  我們以Github上的bertNER為例,該項目使用BERT+Bi-LSTM+CRF結構實現中文序列標註,對BERT進行微調,並且提供了模型訓練、模型預測的辦法。
  • tensorflow(6)利用tensorflow/serving實現模型部署及預測
    在文章tensorflow(5)將ckpt轉化為pb文件並利用tensorflow/serving實現模型部署及預測中,筆者以一個簡單的例子,來介紹如何在tensorflow中將ckpt轉化為pb文件,並利用tensorflow/serving來實現模型部署及預測。本文將會介紹如何使用tensorflow/serving來實現單模型部署、多模型部署、模型版本控制以及模型預測。
  • Keras訓練的h5文件轉pb文件並用Tensorflow加載
    ,而pb格式的文件一般比較適合部署,pb模型文件的大小要比h5文件小一點,同時pb文件也適用於在TensorFlow Serving,所以需要把Keras保存的h5模型文件轉成TensorFlow加載的pb格式來使用。
  • TensorFlow 2.0 部署:TensorFlow Serving
    TensorFlow Serving 可以直接讀取 SavedModel 格式的模型進行部署(導出模型到 SavedModel 文件的方法見 @前文 )。Serving 支持熱更新模型,其典型的模型文件夾結構如下:/saved_model_files /1 # 版本號為1的模型文件 /assets /variables saved_model.pb ...
  • TensorFlow筆記:模型保存、加載和Fine-tune
    其中.meta文件(其實就是pb格式文件)用來保存模型結構,.data和.index文件用來保存模型中的各種變量,而checkpoint文件裡面記錄了最新的checkpoint文件以及其它checkpoint文件列表,在inference時可以通過修改這個文件,指定使用哪個model。那麼要如何保存呢?
  • 教程 | 從零開始:TensorFlow機器學習模型快速部署指南
    本文將介紹一種將訓練後的機器學習模型快速部署到生產種的方式。
  • 國外大神在Kubernetes上使用Istio部署TensorFlow模型
    在本文中,我們將使用經過預先訓練的導出的ResNet模型作為示例,以在服務基礎結構上進行部署。S3模型資料庫訪問ML模型,Tensorflow服務摘要裝載機任意文件系統路徑作為接口,與GCS和S3雲存儲文件系統外的所述盒實現。
  • TensorFlow Serving入門
    準備TF Serving的Docker環境目前TF Serving有Docker、APT(二級制安裝)和源碼編譯三種方式,但考慮實際的生產環境項目部署和簡單性,推薦使用Docker方式。docker pull tensorflow/serving2.
  • OpenCV DNN模塊——從TensorFlow模型導出到OpenCV部署詳解
    舉個例子,博主一般使用C/C++語言對相機進行二次開發及編寫工業軟體,使用Python語言的TensorFlow框架訓練模型,博主一般採用兩種方式在C/C++中調用訓練好的模型:本文將對使用TensorFlow訓練模型、導出模型、模型轉化以及OpenCV模型導入方法及過程詳細介紹。
  • 用 TFserving 部署深度學習模型
    你可能會考慮一些問題:目前流行的深度學習框架Tensorflow和Pytorch, Pytorch官方並沒有提供合適的線上部署方案;Tensorflow則提供了TFserving方案來部署線上模型推理。另外,Model Server for Apache MXNet 為MXNet模型提供推理服務。
  • 社區分享 | Spark 玩轉 TensorFlow 2.0
    利用 Spark 的分布式計算能力,從而可以讓訓練好的 TensorFlow 模型在成百上千的機器上分布式並行執行模型推斷。本案例以 TensorFlow 2.0 的 tf.keras 接口訓練的線性模型為例進行演示。在本例基礎上稍作修改則可以用 Spark 調用訓練好的各種複雜的神經網絡模型進行分布式模型推斷。
  • TensorFlow框架裡的各種模型們
    flow = tf.cast(output, tf.int8, 'out') with tf.Session() as sess:  #保存GraphDef  tf.train.write_graph(sess.graph_def, '
  • Tensorflow的C語言接口部署DeeplabV3+語義分割模型
    tensorflow框架一般都是基於Python調用,但是有些時候跟應用場景,我們希望調用tensorflow C語言的接口,在C++的應用開發中使用它。要這麼幹,首先需要下載tensorflow源碼,完成編譯,然後調用相關的API函數實現C語言版本的調用,完成模型的加載、前向推理預測與解析。
  • tensorflow+目標檢測:龍哥教你學視覺—LabVIEW深度學習教程
    2、學會使用imglabel軟體標註圖片,弄清楚怎麼樣標註目標3、學會利用labview調用tensorflow進行ssd/faster-rcnn模型的訓練4、學會利用labview實現觀察模型訓練過程loss曲線5、學會利用labview調用tensorflow進行ssd/faster-rcnn模型的評估6、學會利用labview
  • Python+Android進行TensorFlow開發
    Android使用Tensorflow框架需要引入兩個文件libtensorflow_inference.so、libandroid_tensorflow_inference_java.jar。這兩個文件可以使用官方預編譯的文件。如果預編譯的so不滿足要求(比如不支持訓練模型中的某些操作符運算),也可以自己通過bazel編譯生成這兩個文件。
  • 教程 | 如何用TensorFlow在安卓設備上實現深度學習推斷
    更重要的是,邊緣計算不僅為物聯網世界帶來了人工智慧,還提供了許多其他的可能性和好處。例如,我們可以在本地設備上將圖像或語音數據預處理為壓縮表示,然後將其發送到雲。這種方法解決了隱私和延遲問題。在 Insight 任職期間,我用 TensorFlow 在安卓上部署了一個預訓練的 WaveNet 模型。我的目標是探索將深度學習模型部署到設備上並使之工作的工程挑戰!
  • Tensorflow Object Detection API 終於支持tensorflow1.x與tensorflow2.x了
    基於tensorflow框架構建的快速對象檢測模型構建、訓練、部署框架,是針對計算機視覺領域對象檢測任務的深度學習框架。之前tensorflow2.x一直不支持該框架,最近Tensorflow Object Detection API框架最近更新了,同時支持tensorflow1.x與tensorflow2.x。
  • 深度學習入門篇——手把手教你用 TensorFlow 訓練模型
    訓練前準備:使用protobuf來配置模型和訓練參數,所以API正常使用必須先編譯protobuf庫,這裡可以下載直接編譯好的pb庫(https://github.com/google/protobuf/releases ),解壓壓縮包後,把protoc加入到環境變量中:$ cd tensorflow/models
  • 詳解Tensorflow模型量化(Quantization)原理及其實現方法
    因此,為了解決此類問題模型量化應運而生,本篇我們將探討模型量化的概念原理、優缺點及tensorflow模型量化的實現方法。二、什麼是模型量化模型量化的定義沒有統一的說法,但個人理解為:模型量化即以較低的推理精度損失將連續取值(或者大量可能的離散取值)的浮點型模型權重或流經模型的張量數據定點近似(通常為int8)為有限多個(或較少的)離散值的過程,它是以更少位數的數據類型用於近似表示32位有限範圍浮點型數據的過程
  • TensorFlow Object Detection API 實踐
    然而構建準確率高的、能定位和識別單張圖片裡多種物體的模型仍然是計算機視覺領域一大挑戰。TF Object Detection API 【1】是一個構建在 TensorFlow 之上的可以簡化構建、訓練、部署目標檢測模型的開源框架。TF Object Detection API 安裝步驟參考【2】。