TensorFlow:ckptファイルをpbファイルに固化


本稿では,yolo 3ターゲット検出フレームワークで訓練したckptファイルをpbファイルに固化し,主にGitHub上のこの項目を利用した.
なぜ最終的にpbファイルを生成するのですか?簡単に言えばtfを直接通過する.saverはストロークのckptファイルを保存し、変数データと図は別々です.TensorFlowはまず図を描き、placeholdeを通じて図にデータを与えることを知っています.このデカップリング形式の存在方法は,今後の移行学習およびプログラムの微小な変更に極めて便利性を提供する.しかし訓練に対して良くて、後で更に変わらないならばこのような存在はもう必要ありません.一方、ckptファイルに格納されているデータは変数であり、変更しない以上、定数にして図の中に直接「焼く」べきだ.一方,線上のモデルについては,C++またはC言語で作成されたプログラムで呼び出すのが一般的である.したがって、一般的なモデルの最終形式はpbファイルに書くべき形式である.
今回のプログラムはGitHubから直接ダウンロードした後、変更が小さいので実行できます.つまり、自分でプログラムの一部を書いたのです.そのため、デバッグを行う際には、以前は気づかなかった小さな問題も発生し、TensorFlowについてもっと詳しく研究する必要があることに気づきました.
まずプログラムを保存するときはsaver=tfを利用する.train.Saver(), saver.save(sess,checkpoint_path,global_step=global_step)は、トレーニングのデータを保存し、ckpt形式で保存する.ただし、リカバリ時に問題が発生していることが示唆されています(そのリカバリ文は、saver=tf.train.Saver()、saver.restore(sess,ckpt_path)、ここでckpt_pathはckptを保存するフォルダパスです).問題の原因は、固定回数のbatchではなく、50個のepochごとに保存しているためだと思います.この固定batch回数の保存システムでは、最近5回のckptファイルが自動的に保存されます(この方法のckpt_path=tf.train、latest_checkpoint('ckpt/')では、epochを利用した回数をどのように保存するか(この保存は5回近くの保存ではなく、1回保存するたびに当時保存していたckptが残り、batchに従ったものはn回目に保存され、n-5回の削除があり、n>5).
ckpt=tfを利用することができます.train.get_checkpoint_state(ckpt_path)は、最新のckptpointファイルを取得しsaverを利用する.restore(sess,ckpt.checkpoint_path)は、リカバリを行います.もちろん安全のためにckptとckptに対して.checkpoint_pathが存在するかどうかを判断した後、リカバリ文の呼び出しを行い、ckptpointを開いて見ることをお勧めします.中に記録されている最近5回のモデルの経路は、一目瞭然です.次のようになります.
    saver = tf.train.Saver()
    ckpt = tf.train.get_checkpoint_state(model_path)
    if ckpt and ckpt.model_checkpoint_path:
        saver.restore(sess, ckpt.model_checkpoint_path)

硬化ネットワークについては、ネット上で多くの紹介があります.再紹介したのは、やはり自分のネットではなく他人のネットを使って出会った穴のためだ.硬化するときはtensorを出力する名前を知る必要がありますが、回復するときはplaceholderの名前を知る必要があります.しかし,ネットワークが複雑であるか,他人のネットワークネーミングが複雑であるか,name=であるかのように,自分でネーミングしたシステムカスタマイズがまったくない場合,このようにしごくのは骨が折れる.当時、ネット上で検索したいくつかの方法は、ネット変数全体を印刷する方法(出力されたネット名にかかわらず、勝手に名前をつけて、まずpbファイルを硬化して、それからpbファイルを読み取り、最後に印刷操作の名前を印刷します:
  graph = tf.get_default_graph()
    input_graph_def = graph.as_graph_def()

    output_graph_def = graph_util.convert_variables_to_constants(
        sess,
        input_graph_def,
        ['cls_score/cls_score', 'cls_prob']  # We split on comma for convenience
    )
    with tf.gfile.GFile(output_graph, "wb") as f:
        f.write(output_graph_def.SerializeToString())
    print ('        ')
    for op in graph.get_operations():
        print(op.name)
    print("%d ops in the final graph." % len(output_graph_def.node))

コード1
これでできるだけ印刷できます(出力名は勝手に命名されていますが).しかし、印刷されたのはすべてのノードの名前で、あまり多くはありません.このように探すと、一方では間違っているかもしれないが、他方では面倒だ.
では、どうしましょうか.答えが簡単で私も無言です.実は、ckptに対してデータ復旧を行う場合は、出力のtensor名をそのまま印刷すればよいのです.例えばsaverおよびplaceholder定義の場合:output=model.yolo_inference(images,config.num_anchors/3,config.num_classes,is_training)では、印刷された情報から表示できるprint outputと後に続きます.placeholderの表示方法も同様です.
ネットワークの固化:
コード:
    input_image_shape = tf.placeholder(dtype = tf.int32, shape = (2,))
    input_image = tf.placeholder(shape = [None, 416, 416, 3], dtype = tf.float32)
    predictor = yolo_predictor(config.obj_threshold, config.nms_threshold, config.classes_path, config.anchors_path)
    boxes, scores, classes = predictor.predict(input_image, input_image_shape)
    sess = tf.Session(config=tf.ConfigProto(allow_soft_placement=True))
    saver = tf.train.Saver()
    ckpt = tf.train.get_checkpoint_state(model_path)
    if ckpt and ckpt.model_checkpoint_path:
        saver.restore(sess, ckpt.model_checkpoint_path)

    #   meta     ,         
    # saver = tf.train.import_meta_graph(model_path, clear_devices=True) 
    #    model_path model.ckpt.meta      
    # ckpt_model_path            
    # saver.restore(sess, tf.train.latest_checkpoint(ckpt_model_path))

    graph = tf.get_default_graph()
    input_graph_def = graph.as_graph_def()
    output_graph_def = graph_util.convert_variables_to_constants(
        sess,
        input_graph_def,
        ['concat_11','concat_12','concat_13']  # We split on comma for convenience
    )
    # # Finally we serialize and dump the output graph to the filesystem
    with tf.gfile.GFile(output_graph, "wb") as f:
        f.write(output_graph_def.SerializeToString())

硬化するときはckptネットワークを復元する必要があるので、だからrestoreの前にplaceholderと出力tensorの定義を書きました(注意が必要なのは、私たちが保存しているckptファイルは訓練段階のgraphや変数などであり、そのinference出力と最終predictの出力のTensorが異なるため、predictはinferenceの出力と比較して、nmsなどの後処理も含まれており、これらの後処理もTensorFlowフレームワーク内の方法で書かれているだけで、最終的に形成されたpbファイルを画像を入力し、最終結果を直接出力することができます.したがって,ターゲット検出タスクについては,後処理タスクもTensorFlow内のapiに任せることで実現し,プラットフォームがpbファイルを読み込んだ後も後処理を再開する必要があるなどの関連プログラムの作成に伴う不要な手間を省くことができる).変数を保存するファイル(ckpt)と組み合わせて、変数をinferenceプロセスに復元するために必要な変数データ(predictはinferenceとevalの2つのプロセスを含み、訓練プロセスはinferenceとlossプロセスのみが関与し、予測プロセスは1つの後処理evalプロセスが多くなり、evalプロセスは変数がない.これによりpbファイルを生成する際にも後処理evalを硬化させる.ネットワークデータに与えると出力tensorが得られる.
読者がここで聞いたので分からなかった11','concat_12','concat_13'はどのように来たのか、私はここで詳しく話しています.
そうですね.私たちがネットワークを復元するときはsaverというオブジェクトを知る必要があります.ここでは2つの方法でこのオブジェクトを生成する方法を紹介します.
1:
saver = tf.train.import_meta_graph(meta_graph_location, clear_devices=True)

ここでmeta_graph_locationはモデルを保存するときのものです.metaファイルのパス.保存後は4つのファイル(checkpoint、.index、.data-00000-of-00001、.metaファイル)があります..metaファイルはTensorFlow全体の構造図です.
2:
saver = tf.train.Saver()

本稿では,第2の方法(上に詳細なコードがある)を採用したが,この方法で得られたsaverオブジェクトは,具体的な図がどのようなものか分からないため,復元前に次のコードを用いた.
predictor = yolo_predictor(config.obj_threshold, config.nms_threshold, config.classes_path, config.anchors_path) boxes, scores, classes = predictor.predict(input_image, input_image_shape)
構造全体をもう一度ロードした.第1の方法を採用する場合、この2行のコードを書き換える必要はありません.
私たちが望んでいるのはboxes,scores,classesの3つのtensorの結果で、彼らの3つのtensorの名前を知りたいです.print(boxes,scores,classes)を直接利用して印刷すると、この3つのtensorから3つのtensorの具体的な情報(名前、shape、dtypeなどを含む)が出てきます.これは、saverオブジェクトを第2の方法で取得し、ckptファイルを復元するだけであり、pbファイルの硬化の問題には関与しない.硬化pbファイルはこの3つのtensorの名前を知る必要があるので、印刷してみる必要があります.
もし、保存された4つのファイル(checkpoint、.index、.data-00000-of-00001、.metaファイル)しか手に入らなかったら、その適用コードが書かれた構造図は分かりません.例えば、この2行のコードを利用しています.
predictor = yolo_predictor(config.obj_threshold, config.nms_threshold, config.classes_path, config.anchors_path) boxes, scores, classes = predictor.predict(input_image, input_image_shape)
描いた構造図がどんなものか、私にはわかりません.では、具体的なplaceholdとtensorの名前を出力するには、コード1でOP操作ノードをすべて印刷し、手動で遍歴するしかありません.
pbファイルを読み込む:
コード:
def pb_detect(image_path, pb_model_path):

    os.environ["CUDA_VISIBLE_DEVICES"] = config.gpu_index
    image = Image.open(image_path)
    resize_image = letterbox_image(image, (416, 416))
    image_data = np.array(resize_image, dtype = np.float32)
    image_data /= 255.
    image_data = np.expand_dims(image_data, axis = 0)
    with tf.Graph().as_default():
        output_graph_def = tf.GraphDef()
        with open(pb_model_path, "rb") as f:
            output_graph_def.ParseFromString(f.read())
            tf.import_graph_def(output_graph_def, name="")
        with tf.Session() as sess:
            sess.run(tf.global_variables_initializer())
            input_image_tensor = sess.graph.get_tensor_by_name("Placeholder_1:0")
            input_image_tensor_shape = sess.graph.get_tensor_by_name("Placeholder:0")
            #          
            #output_tensor_name = sess.graph.get_tensor_by_name("InceptionV3/Logits/SpatialSqueeze:0")
            boxes = sess.graph.get_tensor_by_name("concat_11:0")
            scores = sess.graph.get_tensor_by_name("concat_12:0")
            classes = sess.graph.get_tensor_by_name("concat_13:0")
            #       
            #             ,                tensor   (        :0),         
            out_boxes, out_scores, out_classes= sess.run([boxes,scores,classes],
                           feed_dict={
                               input_image_tensor: image_data,
                               input_image_tensor_shape: [image.size[1], image.size[0]]
            })

pbファイルの読み取りはckptファイルの復元よりも容易であり,placeholderの名前を直接取得し,データを復元したネットワークに入力し,出力を読み取るだけでよいことがわかる.
メモ:
TensorFlowバージョンの更新またはその他の理由で、後の作業でpbファイルをロードするのはエラーです:ValueError:Fetch argument cannot be interpreted as a Tensor.(tf.Tensor 'shuffle_batch:0' shape=(1, 300, 1024), dtype=float32) is not an element of this graph.)
pbファイルのコードwith tfを上から読み出す.Graph().as_default():に変更global graph graph = tf.get_default_graph() with graph.as_default():