記事ディレクトリ
環境: 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 つの一般的な方法があります。
-
公式モデル ファイルを使用してモデルをロードします。
まずプレースホルダーを使用して計算グラフでモデルを定義し、次に tf.train.Saver() の復元メソッドを使用してモデル パラメーターをロードする必要があります。この方法では、新しく定義したモデルのノード名とパラメータ名が に保存vgg_19.ckpt
されているものと一致している必要があります。そのため、公式のモデル定義ファイルを直接使用することをお勧めします。 -
Mokai の公式モデルファイル。
畳み込み部分では、公式モデルファイルではrelu層の特徴マップのみが提供されていますが、場合によってはconv層の特徴マップが必要になる場合があり、その際にはマジック修正が必要となります。変更されたノード名とパラメータ名は、vgg_19.ckpt
に保存されているものと一致している必要があります。 -
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_19
とvisualize_feature_map
2 つの関数はこのドキュメントにすでに登場しており、比較的長いため、次のスクリプトでは省略されていることに注意してください。
# -*- 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()
上記のスクリプトは次のように説明する必要があります。
-
vgg_19 のパラメータ設定にはさらに注意が必要です。
推論を行うだけの場合は、is_training を False に設定する必要があります。
私の使用目的は vgg 損失を計算することなので、完全な接続部分は必要ありません。そのため、完全な接続部分でエラーが報告されないように、num_classes を設定します。を 0 に設定し、global_pool を True に設定します。 -
重みは、プレースホルダーを使用して計算グラフが作成された後にのみ復元できます。スクリプトは、名前、形状、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> ......
-
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) ......
-
特徴マップのいくつかの統計値を出力すると、次の事実をチェックして確認できます:
特徴マップの最小値が 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 ......
-
モデル定義関数を変更した後、最終的な特徴ベクトルの一部を出力して正確さを確認します。
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()
次のように説明されています。
-
モデル構造を変更する目的: conv 層の結果を vgg loss の入力として使用できるように、conv と relu を分離します。
-
モデル定義関数変更のポイント: 構造が変更できないこと、ノード名が変更できないこと、元のコードではrelu層の特徴マップを直接収集できないため、辞書を定義する必要があるコレクション。
-
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
-
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) ......
-
他の出力変数もチェックされており、最初の方法と違いはなく、マジック変更の結果が正しいことを示しています。
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()
上記のコードにはいくつかの注意点があります。
- NewCheckpointReader は、事前トレーニングされたモデルの重みを読み込むために使用されます
- get_variable_to_shape_map() と get_variable_to_dtype_map() は、重みパラメータの形状と dtype を表示できます。
- get_tensor() は重みパラメータを取得して
numpy
配列を返すことができます
出力された結果は次のとおりです。ここには 2 つの賢いパラメータがあり、global_step
mean_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 つは先ほどと同じテスト ケースです。
割り当てのプロセスを以下に説明します。
- プレースホルダーを使用して計算グラフを作成し、取得します。
trainable_vars
NewCheckpointReader
ロードされた事前トレーニング済みモデルの重みパラメータを使用する- セッションを作成し、グローバル変数を初期化する
- メソッドを使用して
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,并与之前的两种方法对比数值,结果是一样的,说明整体流程没什么问题)
最後に、コードに保存された機能マップを確認してください。