Skip to content

Commit 1158371

Browse files
LpPool operator implementation (#685)
* LpPool operator implementation Implements LpPool by utilizing avg_pool: window_size = np.prod(ksize) input = tf.math.pow(input, self.p) * window_size pooled = tf.nn.avg_pool(input, ksize=ksize, strides=strides, padding=padding) pooled = tf.math.pow(pooled, 1.0 / self.p) * Fixed the test cases and few other issues with the code * fixed the test cases to run correct lp pooling * bug in the caculations - added abs to the formula * fixed a problem with the SAME_UPPER padding caculating wrong values Co-authored-by: Chin Huang <chhuang@us.ibm.com>
1 parent 06d56ad commit 1158371

File tree

7 files changed

+195
-26
lines changed

7 files changed

+195
-26
lines changed

doc/support_status.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ Notes:
8787
|LogSoftmax|**1**|1|1|1|1|1|1|1|1|1|**11**|11|**13**:small_red_triangle:|
8888
|Loop|**1**|1|1|1|1|1|1|1|1|1|**11**|11|11|
8989
|LpNormalization|**1**|1|1|1|1|1|1|1|1|1|1|1|1|
90-
|LpPool|**1**:small_red_triangle:|**2**:small_red_triangle:|2:small_red_triangle:|2:small_red_triangle:|2:small_red_triangle:|2:small_red_triangle:|2:small_red_triangle:|2:small_red_triangle:|2:small_red_triangle:|2:small_red_triangle:|**11**:small_red_triangle:|11:small_red_triangle:|11:small_red_triangle:|
90+
|LpPool|**1**|**2**|2|2|2|2|2|2|2|2|**11**|11|11|
9191
|MatMul|**1**|1|1|1|1|1|1|1|**9**|9|9|9|**13**:small_red_triangle:|
9292
|MatMulInteger|-|-|-|-|-|-|-|-|-|**10**|10|10|10|
9393
|Max|**1**|1|1|1|1|**6**|6|**8**|8|8|8|**12**|**13**|

onnx_tf/common/pooling_helper.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ def _pooling_output_shape(input_size, ksize, stride, dilation, pad, ceil_mode):
120120

121121
def py_pool(input, kernel_shape, strides=None, dilations=None,
122122
padding=None, ceil_mode=False, pooling_type="MAX",
123-
include_indices=True):
123+
include_indices=True, p=2):
124124
"""
125125
Implementation of Max and Average pool operations in Python
126126
Args:
@@ -133,8 +133,10 @@ def py_pool(input, kernel_shape, strides=None, dilations=None,
133133
[x1_begin, x2_begin...x1_end, x2_end,...]
134134
ceil_mode: whether to use ceil or floor (default) to compute
135135
the output shape.
136-
pooling_type: specify pooling type. Values can be "MAX" or "AVG".
136+
pooling_type: specifies pooling type. Values can be "MAX", "AVG" or
137+
"LP"
137138
include_indices: should indices be included in the output
139+
p: specifies the p parameter for LpPooling
138140
Return:
139141
pooled: output data from max pooling across the input
140142
ind: indices of the selected max values from the input
@@ -151,6 +153,9 @@ def py_pool(input, kernel_shape, strides=None, dilations=None,
151153
else:
152154
input_dtype_min = np.finfo(input_dtype).min
153155

156+
if pooling_type == "LP":
157+
rootN = (1.0 / p)
158+
154159
def _loop_over_output(batch, channel):
155160
dims = [range(output_sp_shape[d]) for d in range(spatial_size)]
156161
for counters in itertools.product(*dims):
@@ -167,7 +172,7 @@ def _loop_over_output(batch, channel):
167172
cur_range = [i for i in range(dim_start,
168173
dim_end, dilations[dim])]
169174
input_ranges.append(cur_range)
170-
if pooling_type == "AVG":
175+
if pooling_type in ["AVG", "LP"]:
171176
val_sum = 0
172177
val_count = 0
173178
else:
@@ -179,6 +184,8 @@ def _loop_over_output(batch, channel):
179184
if pooling_type == "AVG":
180185
val_sum += val
181186
val_count += 1
187+
elif pooling_type == "LP":
188+
val_sum += abs(val ** p)
182189
else:
183190
if val > maxval:
184191
maxval = val
@@ -192,6 +199,8 @@ def _loop_over_output(batch, channel):
192199
ind = (batch, channel) + counters
193200
if pooling_type == "AVG":
194201
out_pool[ind] = val_sum / val_count
202+
elif pooling_type == "LP":
203+
out_pool[ind] = val_sum ** rootN
195204
else:
196205
out_pool[ind] = maxval
197206
out_ind[ind] = maxind

onnx_tf/handlers/backend/dilated_pooling.py

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,8 @@ def __init__(self,
151151
padding="VALID",
152152
ceil_mode=False,
153153
count_include_pad=False,
154-
pooling_type="MAX"):
154+
pooling_type="MAX",
155+
p=2):
155156
self.input = tf.convert_to_tensor(input)
156157

157158
self.kernel_shape = kernel_shape
@@ -162,6 +163,7 @@ def __init__(self,
162163
self.ceil_mode = ceil_mode
163164
self.count_include_pad = count_include_pad
164165
self.pooling_type = pooling_type.upper()
166+
self.p = p
165167

166168
self.is_known_shape = self.input.shape.is_fully_defined()
167169
self.spatial_size = len(kernel_shape)
@@ -572,6 +574,15 @@ def dilated_maxpool_with_argmax(self, force_custom_impl=False):
572574

573575
return (pooled, new_ind)
574576

577+
def _lp_pool(self, input, ksize, strides, padding):
578+
window_size = np.prod(ksize)
579+
580+
input = tf.math.pow(tf.math.abs(input), self.p) * window_size
581+
pooled = tf.nn.avg_pool(input, ksize=ksize, strides=strides, padding=padding)
582+
pooled = tf.math.pow(pooled, 1.0 / self.p)
583+
584+
return pooled
585+
575586
def dilated_pool(self, force_custom_impl=False):
576587
"""
577588
Does N-D dilated max/avg pooling. Pads the input if explicit or
@@ -582,7 +593,7 @@ def dilated_pool(self, force_custom_impl=False):
582593

583594
if self.is_explicit_padding or self.padding.lower() == "same_lower" \
584595
or (self.padding.lower() == "same_upper" and
585-
self.count_include_pad):
596+
self.count_include_pad) or self.pooling_type.upper() == "LP":
586597
# pad the input
587598
self._pad_input()
588599

@@ -614,8 +625,8 @@ def dilated_pool(self, force_custom_impl=False):
614625
elif self.spatial_size < 4 and (self.strides == [1] * self.spatial_size or
615626
self.dilations == [1] * self.spatial_size) and \
616627
not force_custom_impl:
617-
# if strides == 1 use tf.nn.pool directly
618-
if self.strides == [1] * self.spatial_size:
628+
# if strides == 1 and not LpPool use tf.nn.pool directly
629+
if self.strides == [1] * self.spatial_size and self.pooling_type != "LP":
619630
pooled = tf.nn.pool(
620631
self.input,
621632
window_shape=self.kernel_shape,
@@ -629,6 +640,8 @@ def dilated_pool(self, force_custom_impl=False):
629640
op = tf.nn.max_pool
630641
elif self.pooling_type == "AVG":
631642
op = tf.nn.avg_pool
643+
elif self.pooling_type == "LP":
644+
op = self._lp_pool
632645
else:
633646
raise ValueError("%d-D %s pooling is not supported." %
634647
(self.spatial_size, self.pooling_type))
@@ -644,12 +657,20 @@ def dilated_pool(self, force_custom_impl=False):
644657
# pad the input
645658
self._pad_input()
646659
input_ = self._remove_dilations()
647-
pooled = tf.nn.pool(
648-
input_,
649-
window_shape=self.kernel_shape,
650-
strides=self.kernel_shape,
651-
padding="VALID",
652-
pooling_type=self.pooling_type)
660+
if self.pooling_type=="LP":
661+
pooled = self._lp_pool(
662+
input_,
663+
ksize=self.kernel_shape,
664+
strides=self.kernel_shape,
665+
padding="VALID")
666+
667+
else:
668+
pooled = tf.nn.pool(
669+
input_,
670+
window_shape=self.kernel_shape,
671+
strides=self.kernel_shape,
672+
padding="VALID",
673+
pooling_type=self.pooling_type)
653674
return pooled
654675

655676
def is_supported(self):
@@ -658,7 +679,8 @@ def is_supported(self):
658679
supported for average pool
659680
"""
660681
# check for maxpool
661-
if self.pooling_type.startswith("MAX"):
682+
if self.pooling_type.startswith("MAX") or \
683+
self.pooling_type=="LP":
662684
return True
663685
else:
664686
# if count_include_pad is true it is fully supported

onnx_tf/handlers/backend/lp_pool.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
from onnx_tf.handlers.backend_handler import BackendHandler
2+
from onnx_tf.handlers.handler import onnx_op
3+
from .pool_mixin import PoolMixin
4+
5+
6+
@onnx_op("LpPool")
7+
class LpPool(PoolMixin, BackendHandler):
8+
9+
@classmethod
10+
def _common(cls, node, **kwargs):
11+
return cls.pool(node, kwargs["tensor_dict"], "LP",
12+
kwargs.get("strict", True))
13+
14+
@classmethod
15+
def version_1(cls, node, **kwargs):
16+
return cls._common(node, **kwargs)
17+
18+
@classmethod
19+
def version_2(cls, node, **kwargs):
20+
return cls._common(node, **kwargs)
21+
22+
@classmethod
23+
def version_11(cls, node, **kwargs):
24+
return cls._common(node, **kwargs)

onnx_tf/handlers/backend/pool_mixin.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ def pool(cls, node, input_dict, pooling_type, strict=True):
2626
dilations = node.attrs.get("dilations", [1] * spatial_size)
2727
ceil_mode = bool(node.attrs.get("ceil_mode", 0))
2828
pads = node.attrs.get("auto_pad", "NOTSET")
29+
p = node.attrs.get("p", 2)
30+
2931
if pads == "NOTSET":
3032
pads = node.attrs.get("pads", [0] * spatial_size * 2)
3133
# In case shape is fully defined, check if pads match
@@ -44,6 +46,8 @@ def pool(cls, node, input_dict, pooling_type, strict=True):
4446
pooling_name = "MaxPool"
4547
elif pooling_type == "MAX_WITH_ARGMAX":
4648
pooling_name = "MaxPoolWithArgmax"
49+
elif pooling_type == "LP":
50+
pooling_name = "LpPool"
4751

4852
if spatial_size > 3:
4953
exception.OP_UNSUPPORTED_EXCEPT(
@@ -71,16 +75,16 @@ def pool(cls, node, input_dict, pooling_type, strict=True):
7175
padding=pads,
7276
ceil_mode=ceil_mode,
7377
pooling_type=pooling_type,
74-
count_include_pad=count_include_pad)
78+
count_include_pad=count_include_pad,
79+
p=p)
7580
if not dp.is_supported():
7681
if strict:
7782
logger.warning("Using the pooling op in compatibility mode. "
78-
"This means your graph cannot be serialized.",
79-
UserWarning)
83+
"This means your graph cannot be serialized.")
8084

8185
result = tf.numpy_function(py_pool, [
8286
orig_x, kernel_shape, strides, dilations, pads, ceil_mode,
83-
"AVG", False
87+
pooling_type, False
8488
], orig_x.dtype)
8589

8690
if orig_x.shape.is_fully_defined():
@@ -92,15 +96,15 @@ def pool(cls, node, input_dict, pooling_type, strict=True):
9296
result.set_shape(output_shape)
9397
return [result]
9498
else:
95-
exception.OP_UNSUPPORTED_EXCEPT("strict == 0 and average pool"
99+
exception.OP_UNSUPPORTED_EXCEPT("strict == 0 and " + pooling_name +
96100
" arguments not compatible",
97101
"Tensorflow")
98102

99103
def dilated_pool():
100104
return (dp.dilated_pool(), None)
101105

102106
# select correct op depending on the pooling type
103-
pooling_op = dilated_pool if pooling_type in ["MAX", "AVG"] else \
107+
pooling_op = dilated_pool if pooling_type in ["MAX", "AVG", "LP"] else \
104108
dp.dilated_maxpool_with_argmax
105109

106110
# select the correct transpose ops depending on the input storage format

onnx_tf/opset_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@
8686
'LogSoftmax': [1, 11],
8787
'Loop': [1, 11],
8888
'LpNormalization': [1],
89-
'LpPool': [],
89+
'LpPool': [1, 2, 11],
9090
'MatMul': [1, 9],
9191
'MatMulInteger': [10],
9292
'Max': [1, 6, 8, 12, 13],

0 commit comments

Comments
 (0)