-
Notifications
You must be signed in to change notification settings - Fork 19
Added Operations and tests #92
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
63a9de1
d379b0a
00afb92
9f9c975
771f946
229c426
3964e03
6452e5d
2942c54
45fb31c
58a6f31
e1138e1
7c3a1db
6e3db56
4a2dce6
cb70aa9
965383e
df3ae43
a17c656
d5ddd00
2603863
d42d28b
a88191e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -208,6 +208,21 @@ def cast_func(*inputs): | |
|
|
||
| return cast_func | ||
|
|
||
| def visit_Clip(self, operation): | ||
| x_ = operation.x | ||
| if isinstance(x_, Operation): | ||
| x_ = self.visit(x_) | ||
| _min = operation.min | ||
| _max = operation.max | ||
|
|
||
| @self._cached | ||
| def clip_func(*inputs): | ||
| x = _concretize([x_], inputs) | ||
| x = tf.clip_by_value(x, _min, _max) | ||
| return x | ||
|
|
||
| return clip_func | ||
|
|
||
| def visit_Concat(self, operation): | ||
| tensors_ = [] | ||
| for x in operation.x: | ||
|
|
@@ -227,20 +242,24 @@ def visit_Conv(self, operation): | |
| x_ = operation.x | ||
| if isinstance(x_, Operation): | ||
| x_ = self.visit(x_) | ||
| w_ = operation.w | ||
| if isinstance(w_, Operation): | ||
| w_ = self.visit(w_) | ||
|
|
||
| @self._cached | ||
| def conv_func(*inputs): | ||
| x = _concretize([x_], inputs) | ||
| x, weights = _concretize([x_, w_], inputs) | ||
| if len(operation.kernel_shape) != 2: | ||
| raise NotImplementedError( | ||
| "Non 2d convolutions are not currently supported." | ||
| ) | ||
| weights = operation.w | ||
| if not isinstance(weights, np.ndarray): | ||
| weights = np.array(weights) | ||
| if operation.b is not None: | ||
| bias = operation.b | ||
| else: | ||
| bias = np.zeros((weights.shape[0],), dtype=weights.dtype) | ||
| assert np.all(operation.dilations == 1) | ||
| assert np.all(operation.group == 1) | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this necessary? Could you add support for this parameter, similar to dilations? |
||
| num_pads = len(operation.pads) | ||
| pads = tuple( | ||
| zip( | ||
|
|
@@ -258,6 +277,7 @@ def conv_func(*inputs): | |
| weights.transpose((2, 3, 1, 0)), | ||
| operation.strides, | ||
| padding="VALID", | ||
| dilations=operation.dilations, | ||
| ), | ||
| bias, | ||
| ) | ||
|
|
@@ -417,11 +437,13 @@ def visit_Expand(self, operation): | |
| x_ = operation.x | ||
| if isinstance(x_, Operation): | ||
| x_ = self.visit(x_) | ||
| shape_ = operation.shape | ||
| if isinstance(shape_, Operation): | ||
| shape_ = self.visit(shape_) | ||
|
|
||
| @self._cached | ||
| def expand_func(*inputs): | ||
| x = _concretize([x_], inputs) | ||
| shape = operation.shape | ||
| x, shape = _concretize([x_, shape_], inputs) | ||
| result = x * tf.ones(shape, x.dtype) | ||
| return result | ||
|
|
||
|
|
@@ -685,6 +707,21 @@ def pad_func(*inputs): | |
|
|
||
| return pad_func | ||
|
|
||
| def visit_ReduceL2(self, operation): | ||
| x_ = operation.x | ||
| if isinstance(x_, Operation): | ||
| x_ = self.visit(x_) | ||
| axes = operation.axes | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| keepdims = operation.keepdims | ||
|
|
||
| @self._cached | ||
| def reduceL2_func(*inputs): | ||
| x = _concretize([x_], inputs) | ||
| x = tf.norm(x, ord=2, axis=axes, keepdims=keepdims) | ||
| return x | ||
|
|
||
| return reduceL2_func | ||
|
|
||
| def visit_Relu(self, operation): | ||
| x_ = operation.x | ||
| if isinstance(x_, Operation): | ||
|
|
@@ -738,6 +775,7 @@ def resize_func(*inputs): | |
| assert operation.coordinate_transformation_mode in [ | ||
| "asymmetric", | ||
| "tf_crop_and_resize", | ||
| "align_corners", | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this okay to do? Please add tests for this new behavior. |
||
| ] | ||
| assert operation.mode in ["nearest", "linear"] | ||
| assert operation.exclude_outside == 0 | ||
|
|
@@ -873,6 +911,22 @@ def split_func(*inputs): | |
|
|
||
| return split_func | ||
|
|
||
| def visit_Squeeze(self, operation): | ||
| x_ = operation.x | ||
| if isinstance(x_, Operation): | ||
| x_ = self.visit(x_) | ||
| axes = operation.axes | ||
| if isinstance(axes, np.ndarray): | ||
| axes = axes.tolist() | ||
|
|
||
| @self._cached | ||
| def squeeze_func(*inputs): | ||
| x = _concretize([x_], inputs) | ||
| x = tf.squeeze(x, axis=axes) | ||
| return x | ||
|
|
||
| return squeeze_func | ||
|
|
||
| def visit_Sub(self, operation): | ||
| a_ = operation.a | ||
| if isinstance(a_, Operation): | ||
|
|
@@ -948,3 +1002,21 @@ def unsqueeze_func(*inputs): | |
| return x | ||
|
|
||
| return unsqueeze_func | ||
|
|
||
| def visit_Upsample(self, operation): | ||
| x_ = operation.x | ||
| if isinstance(x_, Operation): | ||
| x_ = self.visit(x_) | ||
| scales = operation.scales | ||
| mode = operation.mode | ||
|
|
||
| @self._cached | ||
| def upsample_func(*inputs): | ||
| x = _concretize([x_], inputs) | ||
| scaled_dim = [int(sd) for sd in x.shape[2:] * scales[2:]] | ||
| xr = tf.transpose(x, perm=[0, 2, 3, 1]) | ||
| xr = tf.image.resize(xr, scaled_dim, method="nearest") | ||
| xr = tf.transpose(xr, perm=[0, 3, 1, 2]) | ||
| return xr | ||
|
|
||
| return upsample_func | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -19,6 +19,23 @@ def from_onnx(cls, onnx_node, *inputs): | |||||||||||||
| return cls(*inputs, to=to, name=onnx_node.name) | ||||||||||||||
|
|
||||||||||||||
|
|
||||||||||||||
| class Clip(Operation): | ||||||||||||||
| def __init__(self, x, min=None, max=None, *, name: Optional[str] = None): | ||||||||||||||
| super().__init__(name=name) | ||||||||||||||
| self.x = x | ||||||||||||||
| self.min = min | ||||||||||||||
| self.max = max | ||||||||||||||
|
|
||||||||||||||
| @classmethod | ||||||||||||||
| def from_onnx(cls, onnx_node, *inputs): | ||||||||||||||
| attributes = {a.name: as_numpy(a) for a in onnx_node.attribute} | ||||||||||||||
| if len(inputs) == 1: | ||||||||||||||
| _min = attributes.get("min") | ||||||||||||||
| _max = attributes.get("max") | ||||||||||||||
| return cls(*inputs, min=_min, max=_max, name=onnx_node.name) | ||||||||||||||
| return cls(*inputs, name=onnx_node.name) | ||||||||||||||
|
|
||||||||||||||
|
|
||||||||||||||
| class Concat(Operation): | ||||||||||||||
| def __init__(self, x, axis, *, name: Optional[str] = None): | ||||||||||||||
| super().__init__(name=name) | ||||||||||||||
|
|
@@ -256,6 +273,48 @@ def from_onnx(cls, onnx_node, *inputs): | |||||||||||||
| return cls(*inputs, axes=axes, name=onnx_node.name) | ||||||||||||||
|
|
||||||||||||||
|
|
||||||||||||||
| class ReduceL2(Operation): | ||||||||||||||
| def __init__(self, x, axes, keepdims, *, name: Optional[str] = None): | ||||||||||||||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. axes and keepdims are attributes, so they should go on the right of
Suggested change
|
||||||||||||||
| super().__init__(name=name) | ||||||||||||||
| self.x = x | ||||||||||||||
| self.axes = axes | ||||||||||||||
| self.keepdims = keepdims | ||||||||||||||
|
|
||||||||||||||
| @classmethod | ||||||||||||||
| def from_onnx(cls, onnx_node, *inputs): | ||||||||||||||
| attributes = {a.name: as_numpy(a) for a in onnx_node.attribute} | ||||||||||||||
| axes = attributes.get("axes") | ||||||||||||||
| keepdims = attributes.get("keepdims") | ||||||||||||||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. keepdims should have a provided default value
Suggested change
|
||||||||||||||
| return cls(*inputs, axes=axes, keepdims=keepdims, name=onnx_node.name) | ||||||||||||||
|
|
||||||||||||||
|
|
||||||||||||||
| class Squeeze(Operation): | ||||||||||||||
| def __init__(self, x, axes, *, name: Optional[str] = None): | ||||||||||||||
| super().__init__(name=name) | ||||||||||||||
| self.x = x | ||||||||||||||
| self.axes = axes | ||||||||||||||
|
|
||||||||||||||
| @classmethod | ||||||||||||||
| def from_onnx(cls, onnx_node, *inputs): | ||||||||||||||
| attributes = {a.name: as_numpy(a) for a in onnx_node.attribute} | ||||||||||||||
| axes = attributes.get("axes") | ||||||||||||||
| return cls(*inputs, axes=axes, name=onnx_node.name) | ||||||||||||||
|
Comment on lines
+300
to
+301
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. axes has been an input since version 13, so we shouldn't get its value from the attribute dict unless we have a model from an earlier version. A quick and dirty method is to check the number of inputs.
Suggested change
|
||||||||||||||
|
|
||||||||||||||
|
|
||||||||||||||
| class Upsample(Operation): | ||||||||||||||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you add a comment stating that this operation is deprecated. Maybe it should also give a warning, but I think a comment is sufficient for now.
Suggested change
|
||||||||||||||
| def __init__(self, x, scales, mode, *, name: Optional[str] = None): | ||||||||||||||
| super().__init__(name=name) | ||||||||||||||
| self.x = x | ||||||||||||||
| self.scales = scales | ||||||||||||||
| self.mode = mode | ||||||||||||||
|
|
||||||||||||||
| @classmethod | ||||||||||||||
| def from_onnx(cls, onnx_node, *inputs): | ||||||||||||||
| attributes = {a.name: as_numpy(a) for a in onnx_node.attribute} | ||||||||||||||
| mode = attributes.get("mode") | ||||||||||||||
| return cls(*inputs, mode=mode, name=onnx_node.name) | ||||||||||||||
|
|
||||||||||||||
|
|
||||||||||||||
| __all__ = [ | ||||||||||||||
| "Cast", | ||||||||||||||
| "Concat", | ||||||||||||||
|
|
@@ -272,4 +331,8 @@ def from_onnx(cls, onnx_node, *inputs): | |||||||||||||
| "Tile", | ||||||||||||||
| "Transpose", | ||||||||||||||
| "Unsqueeze", | ||||||||||||||
| "ReduceL2", | ||||||||||||||
| "Clip", | ||||||||||||||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you put these in alphabetical order :) |
||||||||||||||
| "Squeeze", | ||||||||||||||
| "Upsample", | ||||||||||||||
| ] | ||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this necessary? What can it be other than a numpy array? Could it be
weights = np.asarray(weights)instead of the if block?