Handle dynamic shapes and reshape the input according to the model (#2986)

Put type validation in separate method.
This commit is contained in:
Dmitri Smirnov 2020-02-06 16:46:07 -08:00 committed by GitHub
parent ec07fdc5b0
commit 4f4f4bcd92
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -45,42 +45,68 @@ class DataFrameTool():
self._sess_options = sess_options
self._sess = onnxrt.InferenceSession(self._model_path, self._sess_options)
def _reshape_input(self, input_array, expected_shape):
"""
:param - input_array numpy array. This one is obtained from DataFrame and expected to have
: a rank if 1.
:expected_shape - shape fetched from the model which may include dynamic elements.
: expected_shape may at most have one -1, None or zero which will be computed from
: the size of the input_array. We replace None and zeros to -1 and let np.ndarray.reshape deal with it.
"""
# expected_shape rank is one, we will let onnxruntime to deal with it
if len(expected_shape) == 1:
return input_array
inferred_shape = [dim if dim else -1 for dim in expected_shape]
return input_array.reshape(inferred_shape)
def _validate_type(self, input_meta, col_type):
"""
: input_meta - meta info obtained from the model for the given input
: col_type - dtype of the column
: throws if conditions are not met
float16 and bool will always require exact match
We attempt to convert any type to a string if it is required.
With strings we always want to put this into a flat array, cast to np.object and then reshape as object
Any other type to qualify for casting must match either integer or floating point types
"""
expected_type = types_dict[input_meta.type]
if input_meta.type == 'tensor(string)':
return
elif expected_type == col_type:
return
elif expected_type in ort_float_set and str(col_type) in pd_float_set:
return
elif expected_type in ort_int_set and str(col_type) in pd_int_set:
return
raise TypeError("Input {} requires type {} unable to cast column type {} ".format(
input_meta.name, expected_type, col_type))
def _process_input_list(self, df, input_metas, require):
"""
Return a dictionary of input_name : a typed and shaped np.array of values for a given input_meta
The function does the heavy lifting for _get_input_feeds()
:param df: See :class:`pandas.DataFrame`.
:param df: See :class:`pandas.DataFrame`.
:param input_metas: a list of name/type pairs
:require is a boolean. If True this helper throws on a missing input.
"""
feeds = {}
# Process mandadory inputs. Raise an error if anything is not present
for input_meta in input_metas:
shape = [dim if dim else 1 for dim in input_meta.shape]
# We fully expect all the types are in the above dictionary
assert input_meta.type in types_dict, "Update types_dict for the new type"
if input_meta.name in df.columns:
expected_type = types_dict[input_meta.type]
# float16 and bool will always require exact match
# We attempt to convert any type to a string if it is required.
# With strings we always want to put this into a flat array, cast to np.object and then reshape as object
if input_meta.type == 'tensor(string)':
#print('Col: {} processed as string type: {} '.format(input_meta.name, df[input_meta.name].dtype))
feeds[input_meta.name] = np.array([df[input_meta.name][0]]).astype(expected_type).reshape(shape)
elif expected_type == df[input_meta.name].dtype: # If there is an exact match we take as is
#print('Col: {} processed exact match type: {} '.format(input_meta.name, df[input_meta.name].dtype))
feeds[input_meta.name] = np.array([df[input_meta.name][0]]).astype(expected_type).reshape(shape)
elif expected_type in ort_float_set and str(df[input_meta.name].dtype) in pd_float_set:
#print('Col: {} processed as floating type: {} '.format(input_meta.name, df[input_meta.name].dtype))
feeds[input_meta.name] = np.array([df[input_meta.name][0]]).astype(expected_type).reshape(shape)
elif expected_type in ort_int_set and str(df[input_meta.name].dtype) in pd_int_set:
#print('Col: {} processed as integer type: {} '.format(input_meta.name, df[input_meta.name].dtype))
feeds[input_meta.name] = np.array([df[input_meta.name][0]]).astype(expected_type).reshape(shape)
else:
raise TypeError("Input {} expected to be of type: {} got {} ".format(
input_meta.name, expected_type, df[input_meta.name].dtype))
self._validate_type(input_meta, df[input_meta.name].dtype)
# With strings we must cast first to np.object then then reshape
# so we do it for everything
input_array = np.array(df[input_meta.name]).astype(types_dict[input_meta.type])
feeds[input_meta.name] = self._reshape_input(input_array, input_meta.shape)
elif require:
raise RuntimeError("This model requires input {} of type {} but it is not found in the DataFrame".format(
input_meta.name, types_dict[input_meta.type]))
@ -102,7 +128,7 @@ class DataFrameTool():
and feeds the data to the appropriate model inputs.
:param sess: See :class:`onnxruntime.InferenceSession`.
::
For example: pd.DataFrame([[0], [4],[20]],index=[0], columns=['A', 'B', 'C'])