Skip to content

Commit a60ac7d

Browse files
spandantiwarilinkerzhang
authored andcommitted
Add OneHot op to ONNX. (#1567)
* Add OneHot op to ONNX. * Add function type annotation in onehot.py. * Remove dtype attribute and make 'values' required input. * Remove unused variable warning. * Update stale files. * Updates to shape inference code. Added shape inference test. * Test update for typecheck error. Update helper.make_tensor_value_info to handle dimensions without size. * Reverting helper.make_tensor_value_info change. Using type ignore instead. * Another fix to shape inference code. * Trigger notification to re-start stalled build.
1 parent f6c3a7e commit a60ac7d

17 files changed

Lines changed: 474 additions & 1 deletion

File tree

docs/Changelog.md

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8840,6 +8840,59 @@ This version of the operator has been available since version 9 of the default O
88408840
<dd>Constrain index tensor to int64</dd>
88418841
</dl>
88428842

8843+
### <a name="OneHot-9"></a>**OneHot-9**</a>
8844+
8845+
Produces a one-hot tensor based on inputs.
8846+
The locations represented by the index values in the 'indices' input tensor will have 'on_value'
8847+
and the other locations will have 'off_value' in the output tensor, where 'on_value' and 'off_value'
8848+
are specified as part of required input argument 'values', which is a two-element tensor of format
8849+
[off_value, on_value]. The rank of the output tensor will be one greater than the rank of the
8850+
input tensor. The additional dimension is for one-hot representation. The additional dimension will
8851+
be inserted at the position specified by 'axis'. If 'axis' is not specified then then additional
8852+
dimension will be inserted as the innermost dimension, i.e. axis=-1. The size of the additional
8853+
dimension is specified by required scalar input 'depth'. The type of the output tensor is the same
8854+
as the type of the 'values' input. Any entries in the 'indices' input tensor with values outside
8855+
the range [0, depth) will result in one-hot representation with all 'off_value' values in the
8856+
output tensor.
8857+
8858+
#### Version
8859+
8860+
This version of the operator has been available since version 9 of the default ONNX operator set.
8861+
8862+
#### Attributes
8863+
8864+
<dl>
8865+
<dt><tt>axis</tt> : int (default is -1)</dt>
8866+
<dd>(Optional) Axis along which one-hot representation in added. Default: axis=-1. axis=-1 means that the additional dimension will be inserted as the innermost/last dimension in the output tensor.</dd>
8867+
</dl>
8868+
8869+
#### Inputs
8870+
8871+
<dl>
8872+
<dt><tt>indices</tt> : T1</dt>
8873+
<dd>Input tensor containing indices. The values must be non-negative integers. Any entries in the 'indices' input tensor with values outside the range [0, depth) will result in one-hot representation with all 'off_value' values in the output tensor.In case 'indices' is of non-integer type, the values will be casted to int64 before use.</dd>
8874+
<dt><tt>depth</tt> : T1</dt>
8875+
<dd>Scalar specifying the number of classes in one-hot tensor. This is also the size of the one-hot dimension (specified by 'axis' attribute) added on in the output tensor and the values in the 'indices' input tensor are expected to be in the range [0, depth). TheIn case 'depth' is of non-integer type, it will be casted to int64 before use.</dd>
8876+
<dt><tt>values</tt> : T2</dt>
8877+
<dd>Rank 1 tensor containing exactly two elements, in the format [off_value, on_value], where 'on_value' is the value used for filling locations specified in 'indices' input tensor, and 'off_value' is the value used for filling locations other than those specified in 'indices' input tensor. </dd>
8878+
</dl>
8879+
8880+
#### Outputs
8881+
8882+
<dl>
8883+
<dt><tt>output</tt> : T2</dt>
8884+
<dd>Tensor of rank one greater than input tensor 'indices', i.e. rank(output) = rank(indices) + 1. The data type for the elements of the output tensor is the same as the type of input 'values' is used.</dd>
8885+
</dl>
8886+
8887+
#### Type Constraints
8888+
8889+
<dl>
8890+
<dt><tt>T1</tt> : tensor(uint8), tensor(uint16), tensor(uint32), tensor(uint64), tensor(int8), tensor(int16), tensor(int32), tensor(int64), tensor(float16), tensor(float), tensor(double)</dt>
8891+
<dd>Constrains input to only numeric types.</dd>
8892+
<dt><tt>T2</tt> : tensor(uint8), tensor(uint16), tensor(uint32), tensor(uint64), tensor(int8), tensor(int16), tensor(int32), tensor(int64), tensor(float16), tensor(float), tensor(double), tensor(string), tensor(bool), tensor(complex64), tensor(complex128)</dt>
8893+
<dd>Constrain to any tensor type.</dd>
8894+
</dl>
8895+
88438896
### <a name="PRelu-9"></a>**PRelu-9**</a>
88448897

88458898
PRelu takes input data (Tensor<T>) and slope tensor as input, and produces one

docs/Operators.md

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@
6666
* <a href="#Multinomial">Multinomial</a>
6767
* <a href="#Neg">Neg</a>
6868
* <a href="#Not">Not</a>
69+
* <a href="#OneHot">OneHot</a>
6970
* <a href="#Or">Or</a>
7071
* <a href="#PRelu">PRelu</a>
7172
* <a href="#Pad">Pad</a>
@@ -6748,6 +6749,111 @@ expect(node, inputs=[x], outputs=[np.logical_not(x)],
67486749
</details>
67496750

67506751

6752+
### <a name="OneHot"></a><a name="onehot">**OneHot**</a>
6753+
6754+
Produces a one-hot tensor based on inputs.
6755+
The locations represented by the index values in the 'indices' input tensor will have 'on_value'
6756+
and the other locations will have 'off_value' in the output tensor, where 'on_value' and 'off_value'
6757+
are specified as part of required input argument 'values', which is a two-element tensor of format
6758+
[off_value, on_value]. The rank of the output tensor will be one greater than the rank of the
6759+
input tensor. The additional dimension is for one-hot representation. The additional dimension will
6760+
be inserted at the position specified by 'axis'. If 'axis' is not specified then then additional
6761+
dimension will be inserted as the innermost dimension, i.e. axis=-1. The size of the additional
6762+
dimension is specified by required scalar input 'depth'. The type of the output tensor is the same
6763+
as the type of the 'values' input. Any entries in the 'indices' input tensor with values outside
6764+
the range [0, depth) will result in one-hot representation with all 'off_value' values in the
6765+
output tensor.
6766+
6767+
#### Version
6768+
6769+
This version of the operator has been available since version 9 of the default ONNX operator set.
6770+
6771+
#### Attributes
6772+
6773+
<dl>
6774+
<dt><tt>axis</tt> : int (default is -1)</dt>
6775+
<dd>(Optional) Axis along which one-hot representation in added. Default: axis=-1. axis=-1 means that the additional dimension will be inserted as the innermost/last dimension in the output tensor.</dd>
6776+
</dl>
6777+
6778+
#### Inputs
6779+
6780+
<dl>
6781+
<dt><tt>indices</tt> : T1</dt>
6782+
<dd>Input tensor containing indices. The values must be non-negative integers. Any entries in the 'indices' input tensor with values outside the range [0, depth) will result in one-hot representation with all 'off_value' values in the output tensor.In case 'indices' is of non-integer type, the values will be casted to int64 before use.</dd>
6783+
<dt><tt>depth</tt> : T1</dt>
6784+
<dd>Scalar specifying the number of classes in one-hot tensor. This is also the size of the one-hot dimension (specified by 'axis' attribute) added on in the output tensor and the values in the 'indices' input tensor are expected to be in the range [0, depth). TheIn case 'depth' is of non-integer type, it will be casted to int64 before use.</dd>
6785+
<dt><tt>values</tt> : T2</dt>
6786+
<dd>Rank 1 tensor containing exactly two elements, in the format [off_value, on_value], where 'on_value' is the value used for filling locations specified in 'indices' input tensor, and 'off_value' is the value used for filling locations other than those specified in 'indices' input tensor. </dd>
6787+
</dl>
6788+
6789+
#### Outputs
6790+
6791+
<dl>
6792+
<dt><tt>output</tt> : T2</dt>
6793+
<dd>Tensor of rank one greater than input tensor 'indices', i.e. rank(output) = rank(indices) + 1. The data type for the elements of the output tensor is the same as the type of input 'values' is used.</dd>
6794+
</dl>
6795+
6796+
#### Type Constraints
6797+
6798+
<dl>
6799+
<dt><tt>T1</tt> : tensor(uint8), tensor(uint16), tensor(uint32), tensor(uint64), tensor(int8), tensor(int16), tensor(int32), tensor(int64), tensor(float16), tensor(float), tensor(double)</dt>
6800+
<dd>Constrains input to only numeric types.</dd>
6801+
<dt><tt>T2</tt> : tensor(uint8), tensor(uint16), tensor(uint32), tensor(uint64), tensor(int8), tensor(int16), tensor(int32), tensor(int64), tensor(float16), tensor(float), tensor(double), tensor(string), tensor(bool), tensor(complex64), tensor(complex128)</dt>
6802+
<dd>Constrain to any tensor type.</dd>
6803+
</dl>
6804+
6805+
6806+
#### Examples
6807+
6808+
<details>
6809+
<summary>with_axis</summary>
6810+
6811+
```python
6812+
axisValue = 1
6813+
on_value = 3
6814+
off_value = 1
6815+
output_type = np.float32
6816+
node = onnx.helper.make_node(
6817+
'OneHot',
6818+
inputs=['indices', 'depth', 'values'],
6819+
outputs=['y'],
6820+
axis=axisValue
6821+
)
6822+
indices = np.array([[1, 9],
6823+
[2, 4]], dtype=np.float32)
6824+
depth = np.array([10], dtype=np.float32)
6825+
values = np.array([off_value, on_value], dtype=output_type)
6826+
y = one_hot(indices, depth, axis=axisValue, dtype=output_type)
6827+
y = y * (on_value - off_value) + off_value
6828+
expect(node, inputs=[indices, depth, values], outputs=[y], name='test_onehot_with_axis')
6829+
```
6830+
6831+
</details>
6832+
6833+
6834+
<details>
6835+
<summary>without_axis</summary>
6836+
6837+
```python
6838+
on_value = 5
6839+
off_value = 2
6840+
output_type = np.int32
6841+
node = onnx.helper.make_node(
6842+
'OneHot',
6843+
inputs=['indices', 'depth', 'values'],
6844+
outputs=['y']
6845+
)
6846+
indices = np.array([0, 7, 8], dtype=np.int64)
6847+
depth = np.array([12], dtype=np.float32)
6848+
values = np.array([off_value, on_value], dtype=output_type)
6849+
y = one_hot(indices, depth, dtype=output_type)
6850+
y = y * (on_value - off_value) + off_value
6851+
expect(node, inputs=[indices, depth, values], outputs=[y], name='test_onehot_without_axis')
6852+
```
6853+
6854+
</details>
6855+
6856+
67516857
### <a name="Or"></a><a name="or">**Or**</a>
67526858

67536859
Returns the tensor resulted from performing the `or` logical operation

docs/TestCoverage.md

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
* [Overall Test Coverage](#overall-test-coverage)
66
# Node Test Coverage
77
## Summary
8-
Node tests have covered 95/103 (92.23%, 5 generators excluded) common operators.
8+
Node tests have covered 96/104 (92.31%, 5 generators excluded) common operators.
99

1010
Node tests have covered 2/12 (16.67%, 0 generators excluded) experimental operators.
1111

@@ -3549,6 +3549,55 @@ expect(node, inputs=[x], outputs=[np.logical_not(x)],
35493549
</details>
35503550

35513551

3552+
### OneHot
3553+
There are 2 test cases, listed as following:
3554+
<details>
3555+
<summary>with_axis</summary>
3556+
3557+
```python
3558+
axisValue = 1
3559+
on_value = 3
3560+
off_value = 1
3561+
output_type = np.float32
3562+
node = onnx.helper.make_node(
3563+
'OneHot',
3564+
inputs=['indices', 'depth', 'values'],
3565+
outputs=['y'],
3566+
axis=axisValue
3567+
)
3568+
indices = np.array([[1, 9],
3569+
[2, 4]], dtype=np.float32)
3570+
depth = np.array([10], dtype=np.float32)
3571+
values = np.array([off_value, on_value], dtype=output_type)
3572+
y = one_hot(indices, depth, axis=axisValue, dtype=output_type)
3573+
y = y * (on_value - off_value) + off_value
3574+
expect(node, inputs=[indices, depth, values], outputs=[y], name='test_onehot_with_axis')
3575+
```
3576+
3577+
</details>
3578+
<details>
3579+
<summary>without_axis</summary>
3580+
3581+
```python
3582+
on_value = 5
3583+
off_value = 2
3584+
output_type = np.int32
3585+
node = onnx.helper.make_node(
3586+
'OneHot',
3587+
inputs=['indices', 'depth', 'values'],
3588+
outputs=['y']
3589+
)
3590+
indices = np.array([0, 7, 8], dtype=np.int64)
3591+
depth = np.array([12], dtype=np.float32)
3592+
values = np.array([off_value, on_value], dtype=output_type)
3593+
y = one_hot(indices, depth, dtype=output_type)
3594+
y = y * (on_value - off_value) + off_value
3595+
expect(node, inputs=[indices, depth, values], outputs=[y], name='test_onehot_without_axis')
3596+
```
3597+
3598+
</details>
3599+
3600+
35523601
### Or
35533602
There are 2 test cases, listed as following:
35543603
<details>
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
from __future__ import absolute_import
2+
from __future__ import division
3+
from __future__ import print_function
4+
from __future__ import unicode_literals
5+
6+
import numpy as np # type: ignore
7+
8+
import onnx
9+
from ..base import Base
10+
from . import expect
11+
12+
13+
def one_hot(indices, depth, axis=-1, dtype=np.float32): # type: ignore
14+
''' Compute one hot from indices at a specific axis '''
15+
values = np.asarray(indices)
16+
rank = len(values.shape)
17+
depth_range = np.arange(depth)
18+
if axis < 0:
19+
axis += (rank + 1)
20+
ls = values.shape[0:axis]
21+
rs = values.shape[axis:rank]
22+
targets = np.reshape(depth_range, (1,) * len(ls) + depth_range.shape + (1,) * len(rs))
23+
values = np.reshape(values, ls + (1,) + rs)
24+
return np.asarray(targets == values, dtype=dtype)
25+
26+
27+
class OneHot(Base):
28+
29+
@staticmethod
30+
def export_without_axis(): # type: () -> None
31+
on_value = 5
32+
off_value = 2
33+
output_type = np.int32
34+
node = onnx.helper.make_node(
35+
'OneHot',
36+
inputs=['indices', 'depth', 'values'],
37+
outputs=['y']
38+
)
39+
indices = np.array([0, 7, 8], dtype=np.int64)
40+
depth = np.array([12], dtype=np.float32)
41+
values = np.array([off_value, on_value], dtype=output_type)
42+
y = one_hot(indices, depth, dtype=output_type)
43+
y = y * (on_value - off_value) + off_value
44+
expect(node, inputs=[indices, depth, values], outputs=[y], name='test_onehot_without_axis')
45+
46+
@staticmethod
47+
def export_with_axis(): # type: () -> None
48+
axisValue = 1
49+
on_value = 3
50+
off_value = 1
51+
output_type = np.float32
52+
node = onnx.helper.make_node(
53+
'OneHot',
54+
inputs=['indices', 'depth', 'values'],
55+
outputs=['y'],
56+
axis=axisValue
57+
)
58+
indices = np.array([[1, 9],
59+
[2, 4]], dtype=np.float32)
60+
depth = np.array([10], dtype=np.float32)
61+
values = np.array([off_value, on_value], dtype=output_type)
62+
y = one_hot(indices, depth, axis=axisValue, dtype=output_type)
63+
y = y * (on_value - off_value) + off_value
64+
expect(node, inputs=[indices, depth, values], outputs=[y], name='test_onehot_with_axis')
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
 backend-test:�
2+
0
3+
indices
4+
depth
5+
valuesy"OneHot*
6+
axis�test_onehot_with_axisZ
7+
indices
8+

9+

10+
Z
11+
depth
12+
13+

14+
Z
15+
values
16+
17+

18+
b
19+
y
20+

21+

22+

23+
24+
B
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
 backend-test:�
2+
#
3+
indices
4+
depth
5+
valuesy"OneHottest_onehot_without_axisZ
6+
indices
7+
8+

9+
Z
10+
depth
11+
12+

13+
Z
14+
values
15+
16+

17+
b
18+
y
19+

20+

21+
 B

0 commit comments

Comments
 (0)