Skip to content

Commit dfa7d23

Browse files
sssshwanzironycho
authored andcommitted
close(#42) Feature sequence (#43)
* gradcam available in sequence(text)
1 parent ed6e334 commit dfa7d23

File tree

7 files changed

+135
-34
lines changed

7 files changed

+135
-34
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,5 +103,7 @@ ENV/
103103
*.swp
104104
cifar-10-batches-py/
105105
*.tar.gz
106+
*.tar
106107
tmp/
107108
*.ckpt
109+
test/data/sequence/

darkon/gradcam/candidate_ops.py

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,20 +15,33 @@
1515
import tensorflow as tf
1616
import numpy as np
1717

18-
_unusable_op_names = ('Shape', 'Reshape', 'Slice', 'Pack', 'Cast', 'ConcatV2')
18+
_unusable_op_names = (
19+
'Shape',
20+
'Reshape',
21+
'Slice',
22+
'Pack',
23+
'Cast',
24+
'ConcatV2',
25+
'Const',
26+
'Identity',
27+
'ZerosLike',
28+
'Assign',
29+
'VariableV2')
1930

2031

2132
def _unusable_ops(op):
2233
if len(op.outputs) == 0 \
2334
or 'save' in op.name \
35+
or 'gradients/' in op.name \
36+
or '/Initializer' in op.name \
2437
or op.op_def is None \
2538
or op.op_def.name in _unusable_op_names:
2639
return True
2740
else:
2841
return False
2942

3043

31-
def candidate_featuremap_op_names(sess, graph):
44+
def candidate_featuremap_op_names(sess, graph, feed_options):
3245
operations = []
3346
out_ranks = []
3447
out_shapes = []
@@ -41,32 +54,36 @@ def candidate_featuremap_op_names(sess, graph):
4154
out_shapes.append(tf.shape(op.outputs[0]))
4255
operations.append(op)
4356

44-
out_ranks_val, out_shapes_val = sess.run([out_ranks, out_shapes])
57+
out_ranks_val, out_shapes_val = sess.run([out_ranks, out_shapes], feed_dict=feed_options)
4558

4659
ret = []
4760
for out_rank, out_shape, op in zip(out_ranks_val, out_shapes_val, operations):
48-
if out_rank != 4 or out_shape[1] == 1 or out_shape[2] == 1 or out_shape[0] != 1:
61+
if out_rank != 4 or (out_shape[1] == 1 and out_shape[2] == 1) or out_shape[0] != 1:
4962
continue
5063

5164
ret.append(op.name)
5265
return ret
5366

5467

55-
def candidate_predict_op_names(sess, num_classes, graph):
68+
def candidate_predict_op_names(sess, num_classes, graph, feed_options):
5669
operations = []
70+
out_ranks = []
5771
out_shapes = []
5872

5973
for op in graph.get_operations():
6074
if _unusable_ops(op):
6175
continue
6276

77+
out_ranks.append(tf.rank(op.outputs[0]))
6378
out_shapes.append(tf.shape(op.outputs[0]))
6479
operations.append(op)
6580

66-
out_shapes_val = sess.run(out_shapes)
81+
out_ranks_val, out_shapes_val = sess.run([out_ranks, out_shapes], feed_dict=feed_options)
6782

6883
ret = []
69-
for out_shape, op in zip(out_shapes_val, operations):
84+
for out_rank, out_shape, op in zip(out_ranks_val, out_shapes_val, operations):
85+
if out_rank == 1:
86+
continue
7087
if np.prod(out_shape) != num_classes:
7188
continue
7289

darkon/gradcam/gradcam.py

Lines changed: 47 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import numpy as np
2828
import tensorflow as tf
2929
import cv2
30+
from skimage.transform import resize as skimage_resize
3031

3132
from .guided_grad import replace_grad_to_guided_grad
3233
from .candidate_ops import candidate_featuremap_op_names, candidate_predict_op_names
@@ -76,10 +77,11 @@ class Gradcam:
7677
def __init__(self, x_placeholder, num_classes, featuremap_op_name, predict_op_name=None, graph=None):
7778
self._x_placeholder = x_placeholder
7879
graph = graph if graph is not None else tf.get_default_graph()
80+
self.graph = graph
7981

8082
predict_op_name = self._find_prob_layer(predict_op_name, graph)
81-
self._prob_ts = graph.get_operation_by_name(predict_op_name).outputs
82-
self._target_ts = graph.get_operation_by_name(featuremap_op_name).outputs
83+
self._prob_ts = graph.get_operation_by_name(predict_op_name).outputs[0]
84+
self._target_ts = graph.get_operation_by_name(featuremap_op_name).outputs[0]
8385

8486
self._class_idx = tf.placeholder(tf.int32)
8587
top1 = tf.argmax(tf.reshape(self._prob_ts, [-1]))
@@ -91,10 +93,10 @@ def __init__(self, x_placeholder, num_classes, featuremap_op_name, predict_op_na
9193

9294
replace_grad_to_guided_grad(graph)
9395

94-
max_output = tf.reduce_max(self._target_ts, axis=3)
96+
max_output = tf.reduce_max(self._target_ts, axis=2)
9597
self._saliency_map = tf.gradients(tf.reduce_sum(max_output), x_placeholder)[0]
9698

97-
def gradcam(self, sess, input_data, target_index=None):
99+
def gradcam(self, sess, input_data, target_index=None, feed_options=dict()):
98100
""" Calculate Grad-CAM (class activation map) and Guided Grad-CAM for given input on target class
99101
100102
Parameters
@@ -106,6 +108,8 @@ def gradcam(self, sess, input_data, target_index=None):
106108
target_index : int
107109
Target class index
108110
If None, predicted class index is used
111+
feed_options : dict
112+
Optional parameters to graph
109113
110114
Returns
111115
-------
@@ -120,41 +124,54 @@ def gradcam(self, sess, input_data, target_index=None):
120124
* guided_backprop: Guided backprop result
121125
122126
"""
123-
124127
input_feed = np.expand_dims(input_data, axis=0)
125-
image_height, image_width = input_data.shape[:2]
128+
if input_data.ndim == 3:
129+
is_image = True
130+
image_height, image_width = input_data.shape[:2]
131+
if input_data.ndim == 1:
132+
is_image = False
133+
input_length = input_data.shape[0]
126134

127135
if target_index is not None:
128-
conv_out_eval, grad_eval = sess.run(
129-
[self._target_ts, self._grad_by_idx],
130-
feed_dict={self._x_placeholder: input_feed, self._class_idx: target_index})
136+
feed_dict = {self._x_placeholder: input_feed, self._class_idx: target_index}
137+
feed_dict.update(feed_options)
138+
conv_out_eval, grad_eval = sess.run([self._target_ts, self._grad_by_idx], feed_dict=feed_dict)
131139
else:
132-
conv_out_eval, grad_eval = sess.run(
133-
[self._target_ts, self._grad_by_top1],
134-
feed_dict={self._x_placeholder: input_feed})
140+
feed_dict = {self._x_placeholder: input_feed}
141+
feed_dict.update(feed_options)
142+
conv_out_eval, grad_eval = sess.run([self._target_ts, self._grad_by_top1], feed_dict=feed_dict)
135143

136144
weights = np.mean(grad_eval, axis=(0, 1, 2))
137145
conv_out_eval = np.squeeze(conv_out_eval, axis=0)
138-
cam = np.ones(conv_out_eval.shape[:2], dtype=np.float32)
146+
cam = np.zeros(conv_out_eval.shape[:2], dtype=np.float32)
139147

140148
for i, w in enumerate(weights):
141149
cam += w * conv_out_eval[:, :, i]
142-
cam = cv2.resize(cam, (image_height, image_width))
150+
151+
if is_image:
152+
cam += 1
153+
cam = cv2.resize(cam, (image_height, image_width))
154+
saliency_val = sess.run(self._saliency_map, feed_dict={self._x_placeholder: input_feed})
155+
saliency_val = np.squeeze(saliency_val, axis=0)
156+
else:
157+
cam = skimage_resize(cam, (input_length, 1), preserve_range=True, mode='reflect')
158+
cam = np.transpose(cam)
159+
143160
cam = np.maximum(cam, 0)
144161
heatmap = cam / np.max(cam)
145162

146-
saliency_val = sess.run(self._saliency_map, feed_dict={self._x_placeholder: input_feed})
147-
saliency_val = np.squeeze(saliency_val, axis=0)
163+
ret = {'heatmap': heatmap}
148164

149-
return {
150-
'gradcam_img': self.overlay_gradcam(input_data, heatmap),
151-
'guided_gradcam_img': _deprocess_image(saliency_val * heatmap[..., None]),
152-
'heatmap': heatmap,
153-
'guided_backprop': saliency_val
154-
}
165+
if is_image:
166+
ret.update({
167+
'gradcam_img': self.overlay_gradcam(input_data, heatmap),
168+
'guided_gradcam_img': _deprocess_image(saliency_val * heatmap[..., None]),
169+
'guided_backprop': saliency_val
170+
})
171+
return ret
155172

156173
@staticmethod
157-
def candidate_featuremap_op_names(sess, graph=None):
174+
def candidate_featuremap_op_names(sess, graph=None, feed_options=dict()):
158175
""" Returns the list of candidates for operation names of CNN feature map layer
159176
160177
Parameters
@@ -163,17 +180,19 @@ def candidate_featuremap_op_names(sess, graph=None):
163180
Tensorflow session
164181
graph: tf.Graph
165182
Tensorflow graph
183+
feed_options: dict
184+
Optional parameters to graph
166185
Returns
167186
-------
168187
list
169188
String list of candidates
170189
171190
"""
172191
graph = graph if graph is not None else tf.get_default_graph()
173-
return candidate_featuremap_op_names(sess, graph)
192+
return candidate_featuremap_op_names(sess, graph, feed_options)
174193

175194
@staticmethod
176-
def candidate_predict_op_names(sess, num_classes, graph=None):
195+
def candidate_predict_op_names(sess, num_classes, graph=None, feed_options=dict()):
177196
""" Returns the list of candidate for operation names of prediction layer
178197
179198
Parameters
@@ -184,14 +203,16 @@ def candidate_predict_op_names(sess, num_classes, graph=None):
184203
Number of prediction classes
185204
graph: tf.Graph
186205
Tensorflow graph
206+
feed_options: dict
207+
Optional parameters to graph
187208
Returns
188209
-------
189210
list
190211
String list of candidates
191212
192213
"""
193214
graph = graph if graph is not None else tf.get_default_graph()
194-
return candidate_predict_op_names(sess, num_classes, graph)
215+
return candidate_predict_op_names(sess, num_classes, graph, feed_options)
195216

196217
@staticmethod
197218
def overlay_gradcam(image, heatmap):

download-models.sh

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ test_model_dir="./test/data"
22

33
wget http://download.tensorflow.org/models/resnet_v1_50_2016_08_28.tar.gz
44
wget http://download.tensorflow.org/models/vgg_16_2016_08_28.tar.gz
5+
wget https://raw.githubusercontent.com/darkonhub/darkon-examples/master/gradcam/sequence.tar
6+
7+
mkdir -p $test_model_dir
58
tar -xf resnet_v1_50_2016_08_28.tar.gz -C $test_model_dir
69
tar -xf vgg_16_2016_08_28.tar.gz -C $test_model_dir
10+
tar -xf sequence.tar -C $test_model_dir
711

requirements.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
numpy
22
six
3-
opencv-python
3+
opencv-python
4+
scikit-image

test/test_gradcam.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,8 @@ def test_resnet(self):
101101
self.graph_origin = tf.get_default_graph()
102102
self.target_op_name = darkon.Gradcam.candidate_featuremap_op_names(sess, self.graph_origin)[-1]
103103
self.model_name = 'resnet'
104+
105+
self.assertEqual('resnet_v1_50/block4/unit_3/bottleneck_v1/Relu', self.target_op_name)
104106

105107
def test_vgg(self):
106108
with slim.arg_scope(vgg.vgg_arg_scope()):
@@ -116,3 +118,4 @@ def test_vgg(self):
116118
self.graph_origin = tf.get_default_graph()
117119
self.target_op_name = darkon.Gradcam.candidate_featuremap_op_names(sess, self.graph_origin)[-2]
118120
self.model_name = 'vgg'
121+
self.assertEqual('vgg_16/conv5/conv5_3/Relu', self.target_op_name)

test/test_gradcam_sequence.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# Copyright 2017 Neosapience, Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
# ========================================================================
15+
import unittest
16+
17+
import darkon
18+
import tensorflow as tf
19+
import numpy as np
20+
from tensorflow.contrib import learn
21+
22+
class TestGradcamSequence(unittest.TestCase):
23+
def setUp(self):
24+
tf.reset_default_graph()
25+
x_raw = ["a masterpiece of four years in the making"]
26+
vocab_path = "test/data/sequence/vocab"
27+
vocab_processor = learn.preprocessing.VocabularyProcessor.restore(vocab_path)
28+
self.x_test_batch = np.array(list(vocab_processor.transform(x_raw)))
29+
self.y_test_batch = [[1.0, 0.0]]
30+
31+
def test_text(self):
32+
sess = tf.InteractiveSession()
33+
checkpoint_file = "test/data/sequence/model-15000"
34+
saver = tf.train.import_meta_graph("{}.meta".format(checkpoint_file))
35+
saver.restore(sess, checkpoint_file)
36+
graph = tf.get_default_graph()
37+
input_x = graph.get_operation_by_name("input_x").outputs[0]
38+
dropout_keep_prob = graph.get_operation_by_name("dropout_keep_prob").outputs[0]
39+
input_y = graph.get_operation_by_name("input_y").outputs[0]
40+
41+
conv_op_names = darkon.Gradcam.candidate_featuremap_op_names(sess,
42+
feed_options={input_x: self.x_test_batch, input_y: self.y_test_batch ,dropout_keep_prob:1.0})
43+
44+
prob_op_names = darkon.Gradcam.candidate_predict_op_names(sess, 2,
45+
feed_options={input_x: self.x_test_batch, input_y: self.y_test_batch ,dropout_keep_prob:1.0})
46+
47+
conv_name = conv_op_names[-7]
48+
prob_name = prob_op_names[-1]
49+
self.assertEqual(conv_name, "conv-maxpool-3/relu")
50+
self.assertEqual(prob_name, "output/scores")
51+
52+
insp = darkon.Gradcam(input_x, 2, conv_name, prob_name, graph=graph)
53+
ret = insp.gradcam(sess, self.x_test_batch[0], feed_options={dropout_keep_prob: 1})

0 commit comments

Comments
 (0)