TensorFlow1.x が公式の事前トレーニング モデルを使用する方法


環境: tensorflow1.13
モデル: 例として vgg19 を使用します

注: このドキュメントの結果は CPU モードで実行されています。グラフィックス カードが 30 シリーズでシステムが win10 であるため、GPU モードは tf1.13 バージョンでは使用できません。したがって、読者が GPU モードを使用して得た結果がこのドキュメントと多少異なる場合でも、それは正常な現象です。

ドキュメントの背景は、事前トレーニングされた vgg モデルを使用して、tensorflow1.x で vgg 損失を計算することです。

モデルのダウンロード

公式の事前トレーニング モデルは tensorflow のモデル ウェアハウスにあります。フルパスは次のとおりですtensorflow/models/research/slim。tf1.13 ブランチの選択に注意してください:
https://github.com/tensorflow/models/tree/r1.13.0/research/slim

vgg19 モデルのダウンロード アドレス:
http://download.tensorflow.org/models/vgg_19_2016_08_28.tar.gz
解凍後のファイル名は ですvgg_19.ckpt

ヘルパー関数

次のコードでは、異なるモデル使用法が一貫した結果をもたらすかどうかを比較するために、比較を容易にする視覚的特徴マップのヘルパー関数が記述されています。以下のコードは繰り返し使わずにそのまま使用しますので、この文書のコードを試したい場合は、読者がご自身で貼り付けてください。

def visualize_feature_map(feature_map,
                          col_nums=None,
                          gap_value=0.5,
                          gap_width=10,
                          gap_height=10):
    """
    Visualize feature map in one image.

    Parameters
    ----------
    feature_map: numpy array, shape is (height, width, channel)
    col_nums: number of feature map columns
    gap_value: value for feature map gap
    gap_width: width of gap
    gap_height: height of gap

    Returns
    -------
    image: image to show feature map
    """
    eps = 1e-6

    if feature_map.ndim == 4:
        if feature_map.shape[0] == 1:
            feature_map = np.squeeze(feature_map)
        else:
            raise ValueError("feature map must be 3 dims ndarray (height, "
                             "width, channel) or 4 dims ndarray whose shape "
                             "must be (1, height, width, channel)")

    # compute col_nums (if not set) and row_nums
    height, width, channel = feature_map.shape
    if col_nums is None:
        col_nums = int(round(np.sqrt(channel)))
    row_nums = int(np.ceil(channel / col_nums))

    # compute final image width and height
    image_width = col_nums * (width + gap_width) - gap_width
    image_height = row_nums * (height + gap_height) - gap_height

    image = np.ones(shape=(image_height, image_width),
                    dtype=feature_map.dtype) * gap_value
    cnt = 0
    while cnt < channel:
        row = cnt // col_nums
        col = cnt % col_nums

        row_beg = row * (height + gap_height)
        row_end = row_beg + height
        col_beg = col * (width + gap_width)
        col_end = col_beg + width

        image[row_beg:row_end, col_beg:col_end] = \
            feature_map[:, :, cnt] / (np.std(feature_map[:, :, cnt]) + eps)
        cnt += 1

    return image

モデル使用

公式モデルを使用するには、次の 3 つの一般的な方法があります。

  1. 公式モデル ファイルを使用してモデルをロードします。
    まずプレースホルダーを使用して計算グラフでモデルを定義し、次に tf.train.Saver() の復元メソッドを使用してモデル パラメーターをロードする必要があります。この方法では、新しく定義したモデルのノード名とパラメータ名が に保存vgg_19.ckptされているものと一致している必要があります。そのため、公式のモデル定義ファイルを直接使用することをお勧めします。

  2. Mokai の公式モデルファイル。
    畳み込み部分では、公式モデルファイルではrelu層の特徴マップのみが提供されていますが、場合によってはconv層の特徴マップが必要になる場合があり、その際にはマジック修正が必要となります。変更されたノード名とパラメータ名は、vgg_19.ckptに保存されているものと一致している必要があります。

  3. NewCheckpointReader を使用してモデルをカスタマイズして重みパラメーターを読み取り、モデル構造を再定義して重みパラメーターを過去に割り当てます
    pywrap_tensorflow.NewCheckpointReader(model_path)このようにモデルの構造やノード名を柔軟に定義できるのですが、コードを書くのが面倒です。(公式に定義されたモデルファイルはrelu層の特徴マップしか取得できず、conv層は取得できないため柔軟性は良くありません)

1.公式モデルファイルを使用してモデルをロードします

公式モデル定義ファイルのパス (URL):
https://github.com/tensorflow/models/blob/r1.13.0/research/slim/nets/vgg.py
vgg19 の定義は次のようになります。

def vgg_19(inputs,
           num_classes=1000,
           is_training=True,
           dropout_keep_prob=0.5,
           spatial_squeeze=True,
           scope='vgg_19',
           fc_conv_padding='VALID',
           global_pool=False):
    """
    Oxford Net VGG 19-Layers version E Example.
    Note: All the fully_connected layers have been transformed to conv2d
    layers. To use in classification mode, resize input to 224x224.

    Args:
        inputs: a tensor of size [batch_size, height, width, channels].
        num_classes: number of predicted classes. If 0 or None, the logits
            layer is omitted and the input features to the logits layer are
            returned instead.
        is_training: whether or not the model is being trained.
        dropout_keep_prob: the probability that activations are kept in the
            dropout layers during training.
        spatial_squeeze: whether or not should squeeze the spatial dimensions
            of the outputs. Useful to remove unnecessary dimensions for
            classification.
        scope: Optional scope for the variables.
        fc_conv_padding: the type of padding to use for the fully connected
            layer that is implemented as a convolutional layer. Use 'SAME'
            padding if you are applying the network in a fully convolutional
            manner and want to get a prediction map downsampled by a factor of
            32 as an output. Otherwise, the output prediction map will be
            (input / 32) - 6 in case of 'VALID' padding.
        global_pool: Optional boolean flag. If True, the input to the
            classification layer is avgpooled to size 1x1, for any input size.
            (This is not part of the original VGG architecture.)
    Returns:
        net: the output of the logits layer (if num_classes is a non-zero
            integer), or the non-dropped-out input to the logits layer (if
            num_classes is 0 or None).
        end_points: a dict of tensors with intermediate activations.
    """
    with tf.variable_scope(scope, 'vgg_19', [inputs]) as sc:
        end_points_collection = sc.original_name_scope + '_end_points'
        # Collect outputs for conv2d, fully_connected and max_pool2d.
        with slim.arg_scope(
                [slim.conv2d, slim.fully_connected, slim.max_pool2d],
                outputs_collections=end_points_collection):
            net = slim.repeat(inputs, 2, slim.conv2d, 64, [3, 3],
                              scope='conv1')
            net = slim.max_pool2d(net, [2, 2], scope='pool1')
            net = slim.repeat(net, 2, slim.conv2d, 128, [3, 3], scope='conv2')
            net = slim.max_pool2d(net, [2, 2], scope='pool2')
            net = slim.repeat(net, 4, slim.conv2d, 256, [3, 3], scope='conv3')
            net = slim.max_pool2d(net, [2, 2], scope='pool3')
            net = slim.repeat(net, 4, slim.conv2d, 512, [3, 3], scope='conv4')
            net = slim.max_pool2d(net, [2, 2], scope='pool4')
            net = slim.repeat(net, 4, slim.conv2d, 512, [3, 3], scope='conv5')
            net = slim.max_pool2d(net, [2, 2], scope='pool5')

            # Use conv2d instead of fully_connected layers.
            net = slim.conv2d(net, 4096, [7, 7], padding=fc_conv_padding,
                              scope='fc6')
            net = slim.dropout(net, dropout_keep_prob, is_training=is_training,
                               scope='dropout6')
            net = slim.conv2d(net, 4096, [1, 1], scope='fc7')
            # Convert end_points_collection into a end_point dict.
            end_points = slim.utils.convert_collection_to_dict(
                end_points_collection)
            if global_pool:
                net = tf.reduce_mean(net, [1, 2], keep_dims=True,
                                     name='global_pool')
                end_points['global_pool'] = net
            if num_classes:
                net = slim.dropout(net, dropout_keep_prob,
                                   is_training=is_training,
                                   scope='dropout7')
                net = slim.conv2d(net, num_classes, [1, 1],
                                  activation_fn=None,
                                  normalizer_fn=None,
                                  scope='fc8')
                if spatial_squeeze:
                    net = tf.squeeze(net, [1, 2], name='fc8/squeezed')
                end_points[sc.name + '/fc8'] = net
            return net, end_points

上記のモデル定義に関して、少し説明が必要な関数が 2 つあります。

  • Pycharm で Ctrl キーを押しながらクリックした後、 slim.conv2dの定義
    slim.conv2dが間違っていることが判明しました。実際の定義は、次のパスのファイルにあります:
    D:\Program\anaconda3\envs\tf13\Lib\site-packages \tensorflow\ contrib\layers\python\layers\layers.py
    (D:\Program\anaconda3\envs\tf13 は私のコンピュータ上の tf1.13 の環境パスであることに注意してください。これは独自の環境に応じて変更する必要があります)

    このうち、1117 行目はdef convolution2d関数の定義と実装で、3327 行目はconv2d = convolution2d短い名前になっています。convolution2dパラメータリストには があるactivation_fn=nn.reluため、この畳み込みにはデフォルトで活性化関数として relu が含まれます。

  • Slim.repeatの機能は
    、演算子を n 回繰り返すことです。関数の実装はslim.conv2dと同じファイル内にあり、ファイル内の関数定義と部分的な説明は次のとおりです。

    def repeat(inputs, repetitions, layer, *args, **kwargs):
      """Applies the same layer with the same arguments repeatedly.
    
    y = repeat(x, 3, conv2d, 64, [3, 3], scope='conv1')
    # It is equivalent to:
    
    x = conv2d(x, 64, [3, 3], scope='conv1/conv1_1')
    x = conv2d(x, 64, [3, 3], scope='conv1/conv1_2')
    y = conv2d(x, 64, [3, 3], scope='conv1/conv1_3')
    
    ......
    
    """
    

次のようにスクリプトを使用します。2 つの関数vgg_19visualize_feature_map2 つの関数はこのドキュメントにすでに登場しており、比較的長いため、次のスクリプトでは省略されていることに注意してください。

# -*- coding: utf-8 -*-
import os
import cv2
import tensorflow as tf
import numpy as np

os.environ['CUDA_VISIBLE_DEVICES'] = "/gpu:0"
slim = tf.contrib.slim


def vgg_19(inputs,
           num_classes=1000,
           is_training=True,
           dropout_keep_prob=0.5,
           spatial_squeeze=True,
           scope='vgg_19',
           fc_conv_padding='VALID',
           global_pool=False):
    # 见本文档前面
    pass


def visualize_feature_map(feature_map,
                          col_nums=None,
                          gap_value=0.5,
                          gap_width=10,
                          gap_height=10):
    # 见本文档前面
    pass


def main():
    image_file = r'E:\images\lena512color.tiff'
    model_path = r'E:\pretrained_model\tf1x\vgg_19.ckpt'
    inputs_ = tf.placeholder(dtype=tf.float32, shape=[None, None, None, 3])
    outputs, feature_map_dict = vgg_19(inputs_,
                                       num_classes=0,
                                       is_training=False,
                                       global_pool=True)

    # print trainable variables
    for var in tf.trainable_variables():
        print(var)

    # load pretrained model
    saver = tf.train.Saver()
    sess = tf.Session()
    saver.restore(sess, model_path)

    # running test
    inputs = cv2.imread(image_file)
    inputs = np.expand_dims(inputs, axis=0)
    out, feature_maps = sess.run([outputs, feature_map_dict],
                                 feed_dict={
    
    
                                     inputs_: inputs,
                                 })

    # print shape of feature maps
    for key in feature_maps.keys():
        print(key, feature_maps.get(key).shape)

    feature_map = feature_maps.get('vgg_19/conv3/conv3_4')
    feature_map = np.squeeze(feature_map)
    image = visualize_feature_map(feature_map)
    image = np.clip(image * 255, 0, 255).astype(np.uint8)
    # cv2.imwrite('lena_feature_map_vgg_conv3_4.png', image)

    # print statistics for feature map
    for i in range(5):
        mean_val = np.mean(feature_map[:, :, i])
        std = np.std(feature_map[:, :, i])
        min_val = np.min(feature_map[:, :, i])
        max_val = np.max(feature_map[:, :, i])
        print(i + 1, " min=%.4f, max=%.4f, mean=%.4f, std=%.4f" % (
            min_val, max_val, mean_val, std))

    # print part of final global feature vector
    feature_vec = feature_maps.get('global_pool')
    feature_vec = np.squeeze(feature_vec)
    for i in range(10):
        print(feature_vec[i])


if __name__ == '__main__':
    main()

上記のスクリプトは次のように説明する必要があります。

  1. vgg_19 のパラメータ設定にはさらに注意が必要です。
    推論を行うだけの場合は、is_training を False に設定する必要があります。
    私の使用目的は vgg 損失を計算することなので、完全な接続部分は必要ありません。そのため、完全な接続部分でエラーが報告されないように、num_classes を設定します。を 0 に設定し、global_pool を True に設定します。

  2. 重みは、プレースホルダーを使用して計算グラフが作成された後にのみ復元できます。スクリプトは、名前、形状、dtype を含む重み変数を
    計算グラフの中央部分 (セクション) に出力し、重みを復元します。# print trainable variables

    <tf.Variable 'vgg_19/conv1/conv1_1/weights:0' shape=(3, 3, 3, 64) dtype=float32_ref>
    <tf.Variable 'vgg_19/conv1/conv1_1/biases:0' shape=(64,) dtype=float32_ref>
    <tf.Variable 'vgg_19/conv1/conv1_2/weights:0' shape=(3, 3, 64, 64) dtype=float32_ref>
    <tf.Variable 'vgg_19/conv1/conv1_2/biases:0' shape=(64,) dtype=float32_ref>
    <tf.Variable 'vgg_19/conv2/conv2_1/weights:0' shape=(3, 3, 64, 128) dtype=float32_ref>
    <tf.Variable 'vgg_19/conv2/conv2_1/biases:0' shape=(128,) dtype=float32_ref>
    ......
    
  3. vgg_19出力は 2 つあります。
    最初のものは理解しやすく、ネットワーク推論の出力ですが、vgg 損失の計算には役に立ちません。
    2 番目の出力は、ネットワークの特徴マップを dict の形式で保存します。dict のキーは特徴マップのノード名で、値は特徴マップの値です。これは、を計算するために実際に必要なものです。 vggの損失。# print shape of feature maps特徴マップの名前と形状は一部印刷されておりconv3_4簡単かつ直感的なテストや検査のために絵にも描かれています。

    vgg_19/conv1/conv1_1 (1, 512, 512, 64)
    vgg_19/conv1/conv1_2 (1, 512, 512, 64)
    vgg_19/pool1 (1, 256, 256, 64)
    vgg_19/conv2/conv2_1 (1, 256, 256, 128)
    vgg_19/conv2/conv2_2 (1, 256, 256, 128)
    vgg_19/pool2 (1, 128, 128, 128)
    ......
    
  4. 特徴マップのいくつかの統計値を出力すると、次の事実をチェックして確認できます:
    特徴マップの最小値が 0.0 であるため、特徴マップには relu のみがあり、
    conv はありません; vgg は BN の前に表示されるため、BN は存在しません特徴マップの値は非常に大きいため (BN がある場合、通常、値は 5 を超えません)、vgg 損失を計算するときは、特定の状況に応じて小さな重み係数を乗算する必要があります。

    1  min=0.0000, max=9201.5811, mean=386.3745, std=737.3252
    2  min=0.0000, max=7389.5913, mean=1412.0540, std=616.6437
    3  min=0.0000, max=3323.7239, mean=400.2662, std=522.4063
    4  min=0.0000, max=4319.3765, mean=369.9904, std=644.4222
    5  min=0.0000, max=8997.2305, mean=905.1512, std=1288.8953
    ......
    
  5. モデル定義関数を変更した後、最終的な特徴ベクトルの一部を出力して正確さを確認します。

    0.00055606366
    0.0
    0.0
    0.15579844
    0.0
    1.0548652
    0.0
    0.0
    0.05207316
    0.29752082
    ......
    

2. Mokai公式モデルファイル

変更されたモデルとテスト コードは次のとおりです。これらもvisualize_feature_map上から貼り付ける必要があります。

# -*- coding: utf-8 -*-
import os
import cv2
import tensorflow as tf
import numpy as np

slim = tf.contrib.slim


def vgg19(inputs,
          num_classes=1000,
          is_training=True,
          dropout_keep_prob=0.5,
          spatial_squeeze=True,
          scope='vgg_19',
          fc_conv_padding='VALID',
          global_pool=False):
    with tf.variable_scope(scope, 'vgg_19', [inputs]) as sc:
        end_points_collection = sc.original_name_scope + '_end_points'
        # Collect outputs for conv2d, fully_connected and max_pool2d.
        with slim.arg_scope(
                [slim.conv2d, slim.fully_connected, slim.max_pool2d],
                outputs_collections=end_points_collection):
            # conv blocks are modified as follows
            net_config = [
                [64, 2],
                [128, 2],
                [256, 4],
                [512, 4],
                [512, 4],
            ]  # [filters, blocks]

            net = inputs
            relu_dict = {
    
    }
            for i, config in enumerate(net_config):
                filters = config[0]
                for j in range(config[1]):
                    conv_scope = 'conv%d/conv%d_%d' % (i + 1, i + 1, j + 1)
                    relu_name = 'conv%d/relu%d_%d' % (i + 1, i + 1, j + 1)
                    net = slim.conv2d(net, filters, [3, 3],
                                      activation_fn=None,
                                      scope=conv_scope)
                    net = tf.nn.relu(net, name=relu_name)
                    relu_dict[net.op.name] = net
                net = slim.max_pool2d(net, [2, 2], scope='pool%d' % (i + 1))

            # Use conv2d instead of fully_connected layers.
            net = slim.conv2d(net, 4096, [7, 7], padding=fc_conv_padding,
                              scope='fc6')
            net = slim.dropout(net, dropout_keep_prob, is_training=is_training,
                               scope='dropout6')
            net = slim.conv2d(net, 4096, [1, 1], scope='fc7')
            # Convert end_points_collection into a end_point dict.
            end_points = slim.utils.convert_collection_to_dict(
                end_points_collection)
            if global_pool:
                net = tf.reduce_mean(net, [1, 2], keep_dims=True,
                                     name='global_pool')
                end_points['global_pool'] = net
            if num_classes:
                net = slim.dropout(net, dropout_keep_prob,
                                   is_training=is_training,
                                   scope='dropout7')
                net = slim.conv2d(net, num_classes, [1, 1],
                                  activation_fn=None,
                                  normalizer_fn=None,
                                  scope='fc8')
                if spatial_squeeze:
                    net = tf.squeeze(net, [1, 2], name='fc8/squeezed')
                end_points[sc.name + '/fc8'] = net

            end_points.update(relu_dict)
            return net, end_points


def visualize_feature_map(feature_map,
                          col_nums=None,
                          gap_value=0.5,
                          gap_width=10,
                          gap_height=10):
    # 见本文档前面
    pass


def main():
    image_file = r'D:\data\test_images\lena512color.tiff'
    model_path = r'E:\pretrained_model\tensorflow1.13\vgg_19.ckpt'
    inputs_ = tf.placeholder(dtype=tf.float32, shape=[None, None, None, 3])
    outputs, feature_map_dict = vgg19(inputs_,
                                      num_classes=0,
                                      is_training=False,
                                      global_pool=True)

    # check trainable variables
    for var in tf.trainable_variables():
        print(var)

    # load pretrained model
    saver = tf.train.Saver()
    sess = tf.Session()
    saver.restore(sess, model_path)

    # running test
    inputs = cv2.imread(image_file)
    inputs = np.expand_dims(inputs, axis=0)
    out, feature_maps = sess.run([outputs, feature_map_dict],
                                 feed_dict={
    
    
                                     inputs_: inputs,
                                 })

    # print shape of feature maps
    for key in feature_maps.keys():
        print(key, feature_maps.get(key).shape)

    feature_map = feature_maps.get('vgg_19/conv3/relu3_4')
    feature_map = np.squeeze(feature_map)
    image = visualize_feature_map(feature_map)
    image = np.clip(image * 255, 0, 255).astype(np.uint8)
    cv2.imwrite('lena_feature_map_vgg_relu3_4--2.png', image)

    # print statistics for relu3_4
    for i in range(5):
        mean_val = np.mean(feature_map[:, :, i])
        std = np.std(feature_map[:, :, i])
        min_val = np.min(feature_map[:, :, i])
        max_val = np.max(feature_map[:, :, i])
        print(i + 1, " min=%.4f, max=%.4f, mean=%.4f, std=%.4f" % (
            min_val, max_val, mean_val, std))

    # print statistics for conv3_4
    print('\n')
    feature_map = feature_maps.get('vgg_19/conv3/conv3_4')
    feature_map = np.squeeze(feature_map)
    for i in range(5):
        mean_val = np.mean(feature_map[:, :, i])
        std = np.std(feature_map[:, :, i])
        min_val = np.min(feature_map[:, :, i])
        max_val = np.max(feature_map[:, :, i])
        print(i + 1, " min=%.4f, max=%.4f, mean=%.4f, std=%.4f" % (
            min_val, max_val, mean_val, std))

    feature_vec = feature_maps.get('global_pool')
    feature_vec = np.squeeze(feature_vec)
    for i in range(10):
        print(feature_vec[i])


if __name__ == '__main__':
    main()

次のように説明されています。

  1. モデル構造を変更する目的: conv 層の結果を vgg loss の入力として使用できるように、conv と relu を分離します。

  2. モデル定義関数変更のポイント: 構造が変更できないこと、ノード名が変更できないこと、元のコードではrelu層の特徴マップを直接収集できないため、辞書を定義する必要があるコレクション。

  3. conv3_4 の特徴マップ統計は以下の通りで、すでに min 値がマイナスになっていることがわかり、relu 層からの分離が確実に達成されています。

    1  min=-2909.7209, max=9201.5811, mean=92.2542, std=991.5225
    2  min=-431.0446, max=7389.5913, mean=1411.6982, std=617.5237
    3  min=-1092.2075, max=3323.7239, mean=339.5828, std=582.6731
    4  min=-2396.4478, max=4319.3765, mean=106.6278, std=852.0536
    5  min=-3547.4551, max=8997.2305, mean=699.2141, std=1488.1344
    
  4. feature_maps のコンテンツには、コードの最後で更新されるため、より多くの relu 部分があり、この部分は feature_maps の最後にあります。

    ......
    vgg_19/conv1/relu1_1 (1, 512, 512, 64)
    vgg_19/conv1/relu1_2 (1, 512, 512, 64)
    vgg_19/conv2/relu2_1 (1, 256, 256, 128)
    vgg_19/conv2/relu2_2 (1, 256, 256, 128)
    vgg_19/conv3/relu3_1 (1, 128, 128, 256)
    vgg_19/conv3/relu3_2 (1, 128, 128, 256)
    vgg_19/conv3/relu3_3 (1, 128, 128, 256)
    vgg_19/conv3/relu3_4 (1, 128, 128, 256)
    ......
    
  5. 他の出力変数もチェックされており、最初の方法と違いはなく、マジック変更の結果が正しいことを示しています。

3. NewCheckpointReader を使用してモデルをカスタマイズします

この方法は 2 つの部分で説明されます。
最初の部分では、事前トレーニングされたモデルから重みパラメーターを取得する方法を簡単に説明し、2 番目の部分では、事前トレーニングされた重み係数を新しく定義したモデルに割り当ててテストする方法を詳しく説明します。

最初の部分のコードは次のとおりです。

# -*- coding: utf-8 -*-
from tensorflow.python import pywrap_tensorflow as wrap


def main():
    model_path = r'E:\pretrained_model\tf1x\vgg_19.ckpt'
    reader = wrap.NewCheckpointReader(model_path)

    variables_shape = reader.get_variable_to_shape_map()
    variables_dtype = reader.get_variable_to_dtype_map()
    for key in variables_shape.keys():
        print(key, variables_shape.get(key), variables_dtype.get(key))

    print('\n')
    print(reader.has_tensor("vgg_19/mean_rgb"))
    rgb_mean = reader.get_tensor("vgg_19/mean_rgb")
    print(rgb_mean)


if __name__ == '__main__':
    main()

上記のコードにはいくつかの注意点があります。

  1. NewCheckpointReader は、事前トレーニングされたモデルの重みを読み込むために使用されます
  2. get_variable_to_shape_map() と get_variable_to_dtype_map() は、重みパラメータの形状と dtype を表示できます。
  3. get_tensor() は重みパラメータを取得してnumpy配列を返すことができます

出力された結果は次のとおりです。ここには 2 つの賢いパラメータがあり、global_stepmean_rgbvgg_19/mean_rgbが特定の値で出力されます。

global_step [] <dtype: 'int64'>
vgg_19/conv2/conv2_2/biases [128] <dtype: 'float32'>
vgg_19/conv2/conv2_2/weights [3, 3, 128, 128] <dtype: 'float32'>
vgg_19/conv1/conv1_1/biases [64] <dtype: 'float32'>
vgg_19/conv1/conv1_1/weights [3, 3, 3, 64] <dtype: 'float32'>
vgg_19/conv1/conv1_2/biases [64] <dtype: 'float32'>
vgg_19/conv1/conv1_2/weights [3, 3, 64, 64] <dtype: 'float32'>
......
vgg_19/mean_rgb [3] <dtype: 'float32'>
......
vgg_19/fc8/weights [1, 1, 4096, 1000] <dtype: 'float32'>


[123.68 116.78 103.94]

2 番目の部分のコードは次のとおりです。

# -*- coding: utf-8 -*-
import os
import cv2
import tensorflow as tf
import numpy as np
from tensorflow.python import pywrap_tensorflow as wrap

os.environ['CUDA_VISIBLE_DEVICES'] = "/gpu:0"
slim = tf.contrib.slim


def vgg19(inputs,
          scope_name='vgg_19'):
    with tf.variable_scope(scope_name):

        net_config = [
            [64, 2],
            [128, 2],
            [256, 4],
            [512, 4],
            [512, 4],
        ]  # [filters, blocks]

        feature_maps = {
    
    }
        x = inputs
        for i, config in enumerate(net_config):
            filters = config[0]
            for j in range(config[1]):
                conv_name = 'conv%d_%d' % (i + 1, j + 1)
                relu_name = 'relu%d_%d' % (i + 1, j + 1)

                x = tf.layers.conv2d(x, filters, [3, 3],
                                     padding='same',
                                     name=conv_name)
                feat_map_name = x.op.name.replace('/BiasAdd', '')
                feature_maps[feat_map_name] = x

                x = tf.nn.relu(x, name=relu_name)
                feature_maps[x.op.name] = x

            x = tf.layers.max_pooling2d(x, (2, 2), (2, 2),
                                        name='pool%d' % (i + 1))
            feat_map_name = x.op.name.replace('/MaxPool', '')
            feature_maps[feat_map_name] = x

        return x, feature_maps


def visualize_feature_map(feature_map,
                          col_nums=None,
                          gap_value=0.5,
                          gap_width=10,
                          gap_height=10):
    # 见本文档前面
    pass


def _get_pretrained_tensor_name(name):
    block_num = int(name.split('/')[1][4:].split('_')[0])
    name = name.replace('vgg_19', 'vgg_19/conv%d' % block_num)
    name = name.replace('kernel', 'weights').replace('bias', 'biases')
    return name


def main():
    image_file = r'E:\images\lena512color.tiff'
    model_path = r'E:\pretrained_model\tf1x\vgg_19.ckpt'
    inputs_ = tf.placeholder(dtype=tf.float32, shape=[None, None, None, 3])
    outputs, feature_map_dict = vgg19(inputs_)
    trainable_vars = tf.trainable_variables()

    # use NewCheckpointReader to get weights
    reader = wrap.NewCheckpointReader(model_path)

    sess = tf.Session()
    sess.run(tf.global_variables_initializer())

    # print trainable variables before assignment
    for var in trainable_vars:
        print(var)
        print(sess.run(var)[:, :, 0, 0])
        break

    # trainable variables assignment
    print('\n')
    for i, var in enumerate(trainable_vars):
        name = _get_pretrained_tensor_name(var.op.name)
        sess.run(var.assign(reader.get_tensor(name)))

    # print trainable variables after assignment
    for var in trainable_vars:
        print(var)
        name = _get_pretrained_tensor_name(var.op.name)
        print(sess.run(var)[:, :, 0, 0])
        print('pretrained weight:')
        print(reader.get_tensor(name)[:, :, 0, 0])
        break

    # test case
    inputs = cv2.imread(image_file)
    inputs = np.expand_dims(inputs, axis=0)
    out, feature_maps = sess.run([outputs, feature_map_dict],
                                 feed_dict={
    
    
                                     inputs_: inputs,
                                 })

    # print shape of feature maps
    print('\n')
    for key in feature_maps.keys():
        print(key, feature_maps.get(key).shape)

    feature_map = feature_maps.get('vgg_19/relu3_4')
    feature_map = np.squeeze(feature_map)
    image = visualize_feature_map(feature_map)
    image = np.clip(image * 255, 0, 255).astype(np.uint8)
    cv2.imwrite('lena_feature_map_vgg_conv3_4--2.png', image)

    # print statistics for feature map
    print('\n')
    for i in range(5):
        mean_val = np.mean(feature_map[:, :, i])
        std = np.std(feature_map[:, :, i])
        min_val = np.min(feature_map[:, :, i])
        max_val = np.max(feature_map[:, :, i])
        print(i + 1, " min=%.4f, max=%.4f, mean=%.4f, std=%.4f" % (
            min_val, max_val, mean_val, std))


if __name__ == '__main__':
    main()

たとえば、vgg 損失の要件の場合、通常は最後の完全接続層は必要ないため、計算能力とビデオ メモリを節約する目的で、上記の新しく定義されたモデルは完全接続部分と機能の名前を削除します。マップ / 変数も定義済みの名前に変更されます。この場合、復元関数を使用して事前トレーニング パラメーターをロードすることはできず、割り当て方法のみを使用できます。

上記のコードは全体で 2 つの部分に分かれており、1 つは重みパラメーターの割り当てで、もう 1 つは先ほどと同じテスト ケースです。

割り当てのプロセスを以下に説明します。

  1. プレースホルダーを使用して計算グラフを作成し、取得します。trainable_vars
  2. NewCheckpointReaderロードされた事前トレーニング済みモデルの重みパラメータを使用する
  3. セッションを作成し、グローバル変数を初期化する
  4. メソッドを使用してvar.assign()重みパラメータに値を割り当てます

上記のコードによって出力される結果は次のとおりです。

<tf.Variable 'vgg_19/conv1_1/kernel:0' shape=(3, 3, 3, 64) dtype=float32_ref>
[[ 0.04975817 -0.0374901  -0.04425776]
 [ 0.03555809  0.08642714  0.05649987]
 [-0.07783681 -0.03184588 -0.07609541]]
(sess.run(tf.global_variables_initializer())之后打印了kernel的一部分,为随机初始化的结果)

<tf.Variable 'vgg_19/conv1_1/kernel:0' shape=(3, 3, 3, 64) dtype=float32_ref>
[[ 0.39416704  0.37740308 -0.04594866]
 [ 0.2671299   0.09986369 -0.34100872]
 [-0.07573577 -0.2803425  -0.41602272]]
pretrained weight:
[[ 0.39416704  0.37740308 -0.04594866]
 [ 0.2671299   0.09986369 -0.34100872]
 [-0.07573577 -0.2803425  -0.41602272]]
 (权重参数赋值之后,有一次打印了kernel的一部分,同时也打印了预训练模型中对应的部分,可以看到kernel被成功赋值)


vgg_19/conv1_1 (1, 512, 512, 64)
vgg_19/relu1_1 (1, 512, 512, 64)
vgg_19/conv1_2 (1, 512, 512, 64)
vgg_19/relu1_2 (1, 512, 512, 64)
vgg_19/pool1 (1, 256, 256, 64)
......
vgg_19/conv5_4 (1, 32, 32, 512)
vgg_19/relu5_4 (1, 32, 32, 512)
vgg_19/pool5 (1, 16, 16, 512)
(检查featuremap的名字和shape)


1  min=0.0000, max=9201.5811, mean=386.3745, std=737.3252
2  min=0.0000, max=7389.5913, mean=1412.0540, std=616.6437
3  min=0.0000, max=3323.7239, mean=400.2662, std=522.4063
4  min=0.0000, max=4319.3765, mean=369.9904, std=644.4222
5  min=0.0000, max=8997.2305, mean=905.1512, std=1288.8953
(打印 relu3_4,并与之前的两种方法对比数值,结果是一样的,说明整体流程没什么问题)

最後に、コードに保存された機能マップを確認してください。
ここに画像の説明を挿入

おすすめ

転載: blog.csdn.net/bby1987/article/details/119942007