This repository has been archived by the owner on Jan 13, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 13
/
op_zipmap.py
227 lines (193 loc) · 6.71 KB
/
op_zipmap.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
# -*- encoding: utf-8 -*-
# pylint: disable=E0203,E1101,C0111
"""
@file
@brief Runtime operator.
"""
import numpy
from ._op import OpRun
class ZipMapDictionary(dict):
"""
Custom dictionary class much faster for this runtime,
it implements a subset of the same methods.
"""
__slots__ = ['_rev_keys', '_values', '_mat']
@staticmethod
def build_rev_keys(keys):
res = {}
for i, k in enumerate(keys):
res[k] = i
return res
def __init__(self, rev_keys, values, mat=None):
"""
@param rev_keys returns by @see me build_rev_keys,
*{keys: column index}*
@param values values
@param mat matrix if values is a row index,
one or two dimensions
"""
if mat is not None:
if not isinstance(mat, numpy.ndarray):
raise TypeError( # pragma: no cover
f'matrix is expected, got {type(mat)}.')
if len(mat.shape) not in (2, 3):
raise ValueError( # pragma: no cover
f"matrix must have two or three dimensions but got {mat.shape}.")
dict.__init__(self)
self._rev_keys = rev_keys
self._values = values
self._mat = mat
def __getstate__(self):
"""
For pickle.
"""
return dict(_rev_keys=self._rev_keys,
_values=self._values,
_mat=self._mat)
def __setstate__(self, state):
"""
For pickle.
"""
if isinstance(state, tuple):
state = state[1]
self._rev_keys = state['_rev_keys']
self._values = state['_values']
self._mat = state['_mat']
def __getitem__(self, key):
"""
Returns the item mapped to keys.
"""
if self._mat is None:
return self._values[self._rev_keys[key]]
return self._mat[self._values, self._rev_keys[key]]
def __setitem__(self, pos, value):
"unused but used by pickle"
pass
def __len__(self):
"""
Returns the number of items.
"""
return len(self._values) if self._mat is None else self._mat.shape[1]
def __iter__(self):
for k in self._rev_keys:
yield k
def __contains__(self, key):
return key in self._rev_keys
def items(self):
if self._mat is None:
for k, v in self._rev_keys.items():
yield k, self._values[v]
else:
for k, v in self._rev_keys.items():
yield k, self._mat[self._values, v]
def keys(self):
for k in self._rev_keys.keys():
yield k
def values(self):
if self._mat is None:
for v in self._values:
yield v
else:
for v in self._mat[self._values]:
yield v
def asdict(self):
res = {}
for k, v in self.items():
res[k] = v
return res
def __str__(self):
return f"ZipMap({str(self.asdict())!r})"
class ArrayZipMapDictionary(list):
"""
Mocks an array without changing the data it receives.
Notebooks :ref:`onnxnodetimerst` illustrates the weaknesses
and the strengths of this class compare to a list
of dictionaries.
.. index:: ZipMap
"""
def __init__(self, rev_keys, mat):
"""
@param rev_keys dictionary *{keys: column index}*
@param mat matrix if values is a row index,
one or two dimensions
"""
if mat is not None:
if not isinstance(mat, numpy.ndarray):
raise TypeError( # pragma: no cover
f'matrix is expected, got {type(mat)}.')
if len(mat.shape) not in (2, 3):
raise ValueError( # pragma: no cover
f"matrix must have two or three dimensions but got {mat.shape}.")
list.__init__(self)
self._rev_keys = rev_keys
self._mat = mat
@property
def dtype(self):
return self._mat.dtype
def __len__(self):
return self._mat.shape[0]
def __iter__(self):
for i in range(len(self)):
yield self[i]
def __getitem__(self, i):
return ZipMapDictionary(self._rev_keys, i, self._mat)
def __setitem__(self, pos, value):
raise RuntimeError(
f"Changing an element is not supported (pos=[{pos}]).")
@property
def values(self):
"""
Equivalent to ``DataFrame(self).values``.
"""
if len(self._mat.shape) == 3:
return self._mat.reshape((self._mat.shape[1], -1))
return self._mat
@property
def columns(self):
"""
Equivalent to ``DataFrame(self).columns``.
"""
res = [(v, k) for k, v in self._rev_keys.items()]
if len(res) == 0:
if len(self._mat.shape) == 2:
res = [(i, 'c%d' % i) for i in range(self._mat.shape[1])]
elif len(self._mat.shape) == 3:
# multiclass
res = [(i, 'c%d' % i)
for i in range(self._mat.shape[0] * self._mat.shape[2])]
else:
raise RuntimeError( # pragma: no cover
"Unable to guess the right number of columns for "
"shapes: {}".format(self._mat.shape))
else:
res.sort()
return [_[1] for _ in res]
@property
def is_zip_map(self):
return True
def __str__(self):
return f"ZipMaps[{', '.join(map(str, self))}]"
class ZipMap(OpRun):
"""
The class does not output a dictionary as
specified in :epkg:`ONNX` specifications
but a @see cl ArrayZipMapDictionary which
is wrapper on the input so that it does not
get copied.
"""
atts = {'classlabels_int64s': [], 'classlabels_strings': []}
def __init__(self, onnx_node, desc=None, **options):
OpRun.__init__(self, onnx_node, desc=desc,
expected_attributes=ZipMap.atts,
**options)
if hasattr(self, 'classlabels_int64s') and len(self.classlabels_int64s) > 0:
self.rev_keys_ = ZipMapDictionary.build_rev_keys(
self.classlabels_int64s)
elif hasattr(self, 'classlabels_strings') and len(self.classlabels_strings) > 0:
self.rev_keys_ = ZipMapDictionary.build_rev_keys(
self.classlabels_strings)
else:
self.rev_keys_ = {}
def _run(self, x, attributes=None, verbose=0, fLOG=None): # pylint: disable=W0221
res = ArrayZipMapDictionary(self.rev_keys_, x)
return (res, )