Skip to content

Commit 1c45f9f

Browse files
authored
Allow dumping models first inference input/output data to pickle files (#4027)
* Add possibility to dump model first inference input ouput data * Present model status based on abs_threshold * Make pickle inder data independent from accuracy_checker
1 parent 44d42ef commit 1c45f9f

File tree

7 files changed

+79
-12
lines changed

7 files changed

+79
-12
lines changed

tools/accuracy_checker/accuracy_checker/config/config_validator.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,8 @@ class ConfigValidator(BaseValidator):
8484
WARN_ON_EXTRA_ARGUMENT = _ExtraArgumentBehaviour.WARN
8585
ERROR_ON_EXTRA_ARGUMENT = _ExtraArgumentBehaviour.ERROR
8686
IGNORE_ON_EXTRA_ARGUMENT = _ExtraArgumentBehaviour.IGNORE
87-
acceptable_unknown_options = ['connector', '_command_line_mapping', 'model']
87+
acceptable_unknown_options = ['connector', '_command_line_mapping', 'model',
88+
'_dump_first_infer_data', '_list_processed_image_infos']
8889

8990
def __init__(self, config_uri, on_extra_argument=WARN_ON_EXTRA_ARGUMENT, fields=None, **kwargs):
9091
super().__init__(**kwargs)

tools/accuracy_checker/accuracy_checker/dataset.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,10 @@ def validation_scheme(cls):
373373
})
374374
return [scheme]
375375

376+
def store_first_annotation(self, dump_infer_data_path):
377+
if self.data_provider:
378+
self.data_provider.store_first_annotation(dump_infer_data_path)
379+
376380
@property
377381
def metadata(self):
378382
return self.data_provider.metadata
@@ -595,6 +599,18 @@ def ibl_subset(pairs_set, subsample_set, ids):
595599

596600
return list(subsample_set)
597601

602+
def store_first_annotation(self, annotation, identifier, dump_infer_data_path):
603+
plain_annotation = {
604+
'class_name': annotation.__class__.__name__,
605+
**{
606+
key: value.tolist() if isinstance(value, np.ndarray) else value
607+
for key, value in annotation.__dict__.items()
608+
},
609+
}
610+
print_info(f"Storing first annotation to {dump_infer_data_path}")
611+
with open(dump_infer_data_path, mode='wb') as content:
612+
pickle.dump({'annotation': plain_annotation, 'identifier': identifier}, content)
613+
598614
@property
599615
def metadata(self):
600616
return deepcopy(self._meta) # read-only
@@ -834,6 +850,16 @@ def send_annotation_info(self, config):
834850

835851
return info
836852

853+
def store_first_annotation(self, dump_infer_data_path):
854+
if not self.annotation_provider or self.size == 0:
855+
return
856+
_, batch_annotations, _, batch_identifiers = self[0]
857+
if not batch_annotations:
858+
raise ConfigError('first dataset item does not contain annotations')
859+
self.annotation_provider.store_first_annotation(
860+
batch_annotations[0], batch_identifiers[0], dump_infer_data_path
861+
)
862+
837863

838864
class DatasetWrapper(DataProvider):
839865
pass

tools/accuracy_checker/accuracy_checker/evaluators/custom_evaluators/base_custom_evaluator.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,10 @@ def process_dataset(self, subset=None, num_images=None, check_progress=False, da
9191
check_progress, self.dataset.size
9292
)
9393

94+
dump_infer_data = kwargs.get('_dump_first_infer_data', None)
95+
if dump_infer_data:
96+
self.dataset.store_first_annotation(dump_infer_data)
97+
9498
self._process(output_callback, calculate_metrics, _progress_reporter, metric_config, kwargs.get('csv_result'))
9599

96100
if _progress_reporter:
@@ -178,6 +182,7 @@ def extract_metrics_results(self, print_results=True, ignore_results_formatting=
178182
if threshold_callback:
179183
abs_threshold, rel_threshold = threshold_callback(metric_result)
180184
metric_result = metric_result._replace(abs_threshold=abs_threshold, rel_threshold=rel_threshold)
185+
181186
result, metadata = presenter.extract_result(metric_result)
182187
if isinstance(result, list):
183188
extracted_results.extend(result)

tools/accuracy_checker/accuracy_checker/evaluators/custom_evaluators/base_models.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
from pathlib import Path
1717
from collections import OrderedDict
1818
import numpy as np
19-
19+
import pickle # nosec B403 # disable import-pickle check
2020
from ...config import ConfigError
2121
from ...utils import get_path, parse_partial_shape, contains_any
2222
from ...logging import print_info
@@ -137,6 +137,7 @@ def __init__(self, network_info, launcher, suffix=None, delayed_model_loading=Fa
137137
self.input_blob = None
138138
self.with_prefix = False
139139
self.is_dynamic = False
140+
self._dump_first_infer_data = network_info.get('_dump_first_infer_data', None)
140141
if not delayed_model_loading:
141142
self.load_model(network_info, launcher, log=True)
142143

@@ -406,6 +407,12 @@ def infer(self, input_data, raw_results=False):
406407
feed_dict = {tensors_mapping[name]: data for name, data in input_data.items()}
407408
outputs = self.infer_request.infer(feed_dict)
408409
res_outputs = {out_node.get_node().friendly_name: out_res for out_node, out_res in outputs.items()}
410+
if self._dump_first_infer_data:
411+
with open(self._dump_first_infer_data, 'wb') as file:
412+
print_info(f'Storing first inference data to {self._dump_first_infer_data}')
413+
dump_inf_data = {'input': feed_dict, 'output': res_outputs}
414+
pickle.dump(dump_inf_data, file)
415+
self._dump_first_infer_data = None
409416
if raw_results:
410417
return res_outputs, outputs
411418
return res_outputs

tools/accuracy_checker/accuracy_checker/evaluators/model_evaluator.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,10 @@ def process_dataset_sync(self, stored_predictions, progress_reporter, *args, **k
410410
(enable_profiling, compute_intermediate_metric_res, metric_interval, ignore_results_formatting,
411411
ignore_metric_reference) = metric_config
412412
self._resolve_undefined_shapes()
413+
dump_first_infer_data = kwargs.get('_dump_first_infer_data', None)
414+
if dump_first_infer_data:
415+
self.dataset.store_first_annotation(dump_first_infer_data)
416+
413417
for batch_id, (batch_input_ids, batch_annotation, batch_input, batch_identifiers) in enumerate(self.dataset):
414418
filled_inputs, batch_meta, _ = self._get_batch_input(batch_annotation, batch_input)
415419
batch_predictions = self.launcher.predict(filled_inputs, batch_meta, **kwargs)

tools/accuracy_checker/accuracy_checker/launcher/openvino_launcher.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
from pathlib import Path
2222
import re
2323
import numpy as np
24+
import pickle # nosec B403 # disable import-pickle check
2425
from openvino import Core, AsyncInferQueue, get_version, PartialShape, Type, Dimension
2526
from openvino.preprocess import PrePostProcessor
2627
from .dlsdk_launcher_config import (
@@ -115,6 +116,13 @@ def __init__(self, config_entry, model_name='', delayed_model_loading=False,
115116
if '_list_lstm_inputs' in self.config:
116117
self._configure_lstm_inputs()
117118
self.reset_memory_state = self.get_value_from_config('reset_memory_state')
119+
self._dump_first_infer_data = self.config.get('_dump_first_infer_data', None)
120+
if self._dump_first_infer_data:
121+
self.async_mode = False
122+
warning(
123+
"Async mode has been disabled because inference data dumping is enabled "
124+
"(_dump_first_infer_data is set). This may affect performance results."
125+
)
118126

119127
@classmethod
120128
def validate_config(cls, config, delayed_model_loading=False, fetch_only=False, uri_prefix=''):
@@ -181,7 +189,10 @@ def predict(self, inputs, metadata=None, return_raw=False, **kwargs):
181189
feed_dict = {self.input_to_tensor_name[layer_name]: data for layer_name, data in infer_inputs.items()}
182190
outputs = self.infer_request.infer(inputs=feed_dict)
183191
raw_results.append(outputs)
184-
results.append({out_node.get_node().friendly_name: out_res for out_node, out_res in outputs.items()})
192+
friendly_outputs = {out_node.get_node().friendly_name: out_res for out_node, out_res in outputs.items()}
193+
results.append(friendly_outputs)
194+
self._dump_first_inference(feed_dict, friendly_outputs)
195+
185196
if self.reset_memory_state:
186197
for state in self.infer_request.query_state():
187198
state.reset()
@@ -205,6 +216,7 @@ def _predict_sequential(self, inputs, metadata=None, return_raw=False, **kwargs)
205216
out_tensors = self.infer_request.infer(infer_inputs)
206217
output_result = {
207218
out_node.get_node().friendly_name: out_tensor for out_node, out_tensor in out_tensors.items()}
219+
self._dump_first_inference(infer_inputs, output_result)
208220
lstm_inputs_feed = self._fill_lstm_inputs(output_result)
209221
results.append(output_result)
210222
if return_raw:
@@ -1030,3 +1042,13 @@ def release(self):
10301042
del self.exec_network
10311043
if 'ie_core' in self.__dict__:
10321044
del self.ie_core
1045+
1046+
def _dump_first_inference(self, input_data, output_data):
1047+
if not self._dump_first_infer_data:
1048+
return
1049+
print_info(f'Storing first inference data to {self._dump_first_infer_data}')
1050+
dump_inf_data = {'input': input_data, 'output': output_data}
1051+
with open(self._dump_first_infer_data, 'wb') as file:
1052+
pickle.dump(dump_inf_data, file)
1053+
self._dump_first_infer_data = None
1054+

tools/accuracy_checker/accuracy_checker/presenters.py

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -207,18 +207,20 @@ def write_scalar_result(
207207
message = '{}: {}{}'.format(display_name, display_result, postfix)
208208

209209
if diff_with_ref and (diff_with_ref[0] or diff_with_ref[1]):
210-
abs_error = diff_with_ref[0] * scale
211-
rel_error = diff_with_ref[1]
212-
error_text = "abs error = {:.4} | relative error = {:.4}".format(abs_error, rel_error)
213-
214-
if not abs_threshold or not rel_threshold:
215-
result_message = "[RESULT: {}]".format(error_text)
210+
if abs_threshold is None:
211+
result_message = "[abs error = {:.4} | relative error = {:.4}]".format(
212+
diff_with_ref[0] * scale, diff_with_ref[1]
213+
)
216214
message = "{} {}".format(message, result_message)
217-
elif abs_threshold <= diff_with_ref[0] or rel_threshold <= diff_with_ref[1]:
218-
fail_message = "[FAILED: {}]".format(error_text)
215+
elif abs_threshold <= diff_with_ref[0] or (rel_threshold and rel_threshold <= diff_with_ref[1]):
216+
fail_message = "FAILED: [abs error = {:.4} | relative error = {:.4}]".format(
217+
diff_with_ref[0] * scale, diff_with_ref[1]
218+
)
219219
message = "{} {}".format(message, color_format(fail_message, Color.FAILED))
220220
else:
221-
pass_message = "[PASS: {}]".format(error_text)
221+
pass_message = "PASSED: [abs error = {:.4} | relative error = {:.4}]".format(
222+
diff_with_ref[0] * scale, diff_with_ref[1]
223+
)
222224
message = "{} {}".format(message, color_format(pass_message, Color.PASSED))
223225

224226
print_info(message)

0 commit comments

Comments
 (0)