1 // Copyright (C) 2018 Intel Corporation
2 // SPDX-License-Identifier: Apache-2.0
6 * @brief The entry point for Inference Engine validation application
7 * @file validation_app/main.cpp
9 #include <gflags/gflags.h>
23 #include <ext_list.hpp>
25 #include <samples/common.hpp>
26 #include <samples/slog.hpp>
28 #include "user_exception.hpp"
29 #include "calibrator_processors.h"
30 #include "SSDObjectDetectionProcessor.hpp"
31 #include "YOLOObjectDetectionProcessor.hpp"
32 #include "ie_icnn_network_stats.hpp"
33 #include "details/caseless.hpp"
36 using namespace InferenceEngine;
37 using namespace InferenceEngine::details;
39 using InferenceEngine::details::InferenceEngineException;
41 #define DEFAULT_PATH_P "./lib"
43 /// @brief Message for help argument
44 static const char help_message[] = "Print a help message";
45 /// @brief Message for images argument
46 static const char image_message[] = "Required. Path to a directory with validation images. For Classification models, the directory must contain"
47 " folders named as labels with images inside or a .txt file with"
48 " a list of images. For Object Detection models, the dataset must be in"
50 /// @brief Message for plugin_path argument
51 static const char plugin_path_message[] = "Path to a plugin folder";
52 /// @brief message for model argument
53 static const char model_message[] = "Required. Path to an .xml file with a trained model, including model name and "
55 /// @brief Message for plugin argument
56 static const char plugin_message[] = "Plugin name. For example, CPU. If this parameter is passed, "
57 "the sample looks for a specified plugin only.";
58 /// @brief Message for assigning cnn calculation to device
59 static const char target_device_message[] = "Target device to infer on: CPU (default), GPU, FPGA, or MYRIAD."
60 " The application looks for a suitable plugin for the specified device.";
61 /// @brief Message for label argument
62 static const char label_message[] = "Path to a file with labels for a model";
63 /// @brief M`essage for batch argumenttype
64 static const char batch_message[] = "Batch size value. If not specified, the batch size value is taken from IR";
65 /// @brief Message for dump argument
66 static const char dump_message[] = "Dump file names and inference results to a .csv file";
67 /// @brief Message for network type
68 static const char type_message[] = "Type of an inferred network (\"C\" by default)";
69 /// @brief Message for pp-type
70 static const char preprocessing_type[] = "Preprocessing type. Options: \"None\", \"Resize\", \"ResizeCrop\"";
71 /// @brief Message for pp-crop-size
72 static const char preprocessing_size[] = "Preprocessing size (used with ppType=\"ResizeCrop\")";
73 static const char preprocessing_width[] = "Preprocessing width (overrides -ppSize, used with ppType=\"ResizeCrop\")";
74 static const char preprocessing_height[] = "Preprocessing height (overrides -ppSize, used with ppType=\"ResizeCrop\")";
76 static const char obj_detection_annotations_message[] = "Required for Object Detection models. Path to a directory"
77 " containing an .xml file with annotations for images.";
79 static const char obj_detection_classes_message[] = "Required for Object Detection models. Path to a file with"
82 static const char obj_detection_subdir_message[] = "Directory between the path to images (specified with -i) and image name (specified in the"
83 " .xml file). For VOC2007 dataset, use JPEGImages.";
85 static const char obj_detection_kind_message[] = "Type of an Object Detection model. Options: SSD";
87 /// @brief Message for GPU custom kernels desc
88 static const char custom_cldnn_message[] = "Required for GPU custom kernels. "
89 "Absolute path to an .xml file with the kernel descriptions.";
91 /// @brief Message for user library argument
92 static const char custom_cpu_library_message[] = "Required for CPU custom layers. "
93 "Absolute path to a shared library with the kernel implementations.";
94 /// @brief Message for labels file
95 static const char labels_file_message[] = "Labels file path. The labels file contains names of the dataset classes";
97 static const char zero_background_message[] = "\"Zero is a background\" flag. Some networks are trained with a modified"
98 " dataset where the class IDs "
99 " are enumerated from 1, but 0 is an undefined \"background\" class"
100 " (which is never detected)";
102 static const char stream_output_message[] = "Flag for printing progress as a plain text.When used, interactive progress"
103 " bar is replaced with multiline output";
105 /// @brief Network type options and their descriptions
106 static const char* types_descriptions[][2] = {
107 { "C", "calibrate Classification network and write the calibrated network to IR" },
108 // { "SS", "semantic segmentation" }, // Not supported yet
109 { "OD", "calibrate Object Detection network and write the calibrated network to IR" },
110 { "RawC", "collect only statistics for Classification network and write statistics to IR. With this option, a model is not calibrated. For calibration "
111 "and statisctics collection, use \"-t C\" instead." },
112 { "RawOD", "collect only statistics for Object Detection network and write statistics to IR. With this option, a model is not calibrated. For calibration "
113 "and statisctics collection, use \"-t OD\" instead" },
117 static const char accuracy_threshold_message[] = "Threshold for a maximum accuracy drop of quantized model."
118 " Must be an integer number (percents)"
119 " without a percent sign. Default value is 1, which stands for accepted"
120 " accuracy drop in 1%";
121 static const char number_of_pictures_message[] = "Number of pictures from the whole validation set to"
122 "create the calibration dataset. Default value is 0, which stands for"
123 "the whole provided dataset";
124 static const char output_model_name[] = "Output name for calibrated model. Default is <original_model_name>_i8.xml|bin";
126 /// @brief Define flag for showing help message <br>
127 DEFINE_bool(h, false, help_message);
128 /// @brief Define parameter for a path to images <br>
129 /// It is a required parameter
130 DEFINE_string(i, "", image_message);
131 /// @brief Define parameter for a path to model file <br>
132 /// It is a required parameter
133 DEFINE_string(m, "", model_message);
134 /// @brief Define parameter for a plugin name <br>
135 /// It is a required parameter
136 DEFINE_string(p, "", plugin_message);
137 /// @brief Define parameter for a path to a file with labels <br>
139 DEFINE_string(OCl, "", label_message);
140 /// @brief Define parameter for a path to plugins <br>
142 DEFINE_string(pp, DEFAULT_PATH_P, plugin_path_message);
143 /// @brief Define paraneter for a target device to infer on <br>
144 DEFINE_string(d, "CPU", target_device_message);
145 /// @brief Define parameter for batch size <br>
146 /// Default is 0 (which means that batch size is not specified)
147 DEFINE_int32(b, 0, batch_message);
148 /// @brief Define flag to dump results to a file <br>
149 DEFINE_bool(dump, false, dump_message);
150 /// @brief Define parameter for a network type
151 DEFINE_string(t, "C", type_message);
153 /// @brief Define parameter for preprocessing type
154 DEFINE_string(ppType, "", preprocessing_type);
156 /// @brief Define parameter for preprocessing size
157 DEFINE_int32(ppSize, 0, preprocessing_size);
158 DEFINE_int32(ppWidth, 0, preprocessing_width);
159 DEFINE_int32(ppHeight, 0, preprocessing_height);
161 DEFINE_bool(Czb, false, zero_background_message);
163 DEFINE_string(ODa, "", obj_detection_annotations_message);
165 DEFINE_string(ODc, "", obj_detection_classes_message);
167 DEFINE_string(ODsubdir, "", obj_detection_subdir_message);
169 /// @brief Define parameter for a type of Object Detection network
170 DEFINE_string(ODkind, "SSD", obj_detection_kind_message);
172 /// @brief Define parameter for GPU kernels path <br>
174 DEFINE_string(c, "", custom_cldnn_message);
176 /// @brief Define parameter for a path to CPU library with user layers <br>
177 /// It is an optional parameter
178 DEFINE_string(l, "", custom_cpu_library_message);
180 /// @brief Define parameter for accuracy drop threshold
181 DEFINE_double(threshold, 1.0f, accuracy_threshold_message);
183 /// @brief Define path to output calibrated model
184 DEFINE_bool(stream_output, false, stream_output_message);
186 DEFINE_int32(subset, 0, number_of_pictures_message);
188 DEFINE_string(output, "", output_model_name);
190 DEFINE_string(lbl, "", labels_file_message);
193 * @brief This function shows a help message
195 static void showUsage() {
196 std::cout << std::endl;
197 std::cout << "Usage: calibration_tool [OPTION]" << std::endl << std::endl;
198 std::cout << "Available options:" << std::endl;
199 std::cout << std::endl;
200 std::cout << " -h " << help_message << std::endl;
201 std::cout << " -t <type> " << type_message << std::endl;
202 for (int i = 0; types_descriptions[i][0] != nullptr; i++) {
203 std::cout << " -t \"" << types_descriptions[i][0] << "\" to " << types_descriptions[i][1] << std::endl;
205 std::cout << " -i <path> " << image_message << std::endl;
206 std::cout << " -m <path> " << model_message << std::endl;
207 std::cout << " -lbl <path> " << labels_file_message << std::endl;
208 std::cout << " -l <absolute_path> " << custom_cpu_library_message << std::endl;
209 std::cout << " -c <absolute_path> " << custom_cldnn_message << std::endl;
210 std::cout << " -d <device> " << target_device_message << std::endl;
211 std::cout << " -b N " << batch_message << std::endl;
212 std::cout << " -ppType <type> " << preprocessing_type << std::endl;
213 std::cout << " -ppSize N " << preprocessing_size << std::endl;
214 std::cout << " -ppWidth W " << preprocessing_width << std::endl;
215 std::cout << " -ppHeight H " << preprocessing_height << std::endl;
216 std::cout << " --dump " << dump_message << std::endl;
217 std::cout << " -subset " << number_of_pictures_message << std::endl;
218 std::cout << " -output <output_IR> " << output_model_name << std::endl;
219 std::cout << " -threshold " << accuracy_threshold_message << std::endl;
221 std::cout << std::endl;
222 std::cout << " Classification-specific options:" << std::endl;
223 std::cout << " -Czb true " << zero_background_message << std::endl;
225 std::cout << std::endl;
226 std::cout << " Object detection-specific options:" << std::endl;
227 std::cout << " -ODkind <kind> " << obj_detection_kind_message << std::endl;
228 std::cout << " -ODa <path> " << obj_detection_annotations_message << std::endl;
229 std::cout << " -ODc <file> " << obj_detection_classes_message << std::endl;
230 std::cout << " -ODsubdir <name> " << obj_detection_subdir_message << std::endl << std::endl;
232 std::cout << std::endl;
233 std::cout << " -stream_output " << stream_output_message << std::endl;
244 std::string strtolower(const std::string& s) {
246 std::transform(res.begin(), res.end(), res.begin(), ::tolower);
250 void SaveCalibratedIR(const std::string &originalName,
251 const std::string &outModelName,
252 const std::map<std::string, bool>& layersToInt8,
253 const InferenceEngine::NetworkStatsMap& statMap) {
254 slog::info << "Layers profile for Int8 quantization\n";
255 CNNNetReader networkReader;
256 networkReader.ReadNetwork(originalName);
257 if (!networkReader.isParseSuccess())THROW_IE_EXCEPTION << "cannot load a failed Model";
259 /** Extract model name and load weights **/
260 std::string binFileName = fileNameNoExt(originalName)+ ".bin";
261 networkReader.ReadWeights(binFileName.c_str());
263 auto network = networkReader.getNetwork();
264 for (auto &&layer : network) {
265 if (CaselessEq<std::string>()(layer->type, "convolution")) {
266 auto it = layersToInt8.find(layer->name);
267 if (it != layersToInt8.end() && it->second == false) {
268 layer->params["quantization_level"] = "FP32";
269 std::cout << layer->name << ": " << "FP32" << std::endl;
271 layer->params["quantization_level"] = "I8";
272 std::cout << layer->name << ": " << "I8" << std::endl;
278 ICNNNetworkStats* pstats = nullptr;
279 StatusCode s = ((ICNNNetwork&)networkReader.getNetwork()).getStats(&pstats, nullptr);
280 if (s == StatusCode::OK && pstats) {
281 pstats->setNodesStats(statMap);
284 slog::info << "Write calibrated network to " << outModelName << ".(xml|bin) IR file\n";
285 networkReader.getNetwork().serialize(outModelName + ".xml", outModelName + ".bin");
289 * @brief The main function of inference engine sample application
290 * @param argc - The number of arguments
291 * @param argv - Arguments
292 * @return 0 if all good
294 int main(int argc, char *argv[]) {
296 slog::info << "InferenceEngine: " << GetInferenceEngineVersion() << slog::endl;
298 // ---------------------------Parsing and validating input arguments--------------------------------------
299 slog::info << "Parsing input parameters" << slog::endl;
301 bool noOptions = argc == 1;
303 gflags::ParseCommandLineNonHelpFlags(&argc, &argv, true);
304 if (FLAGS_h || noOptions) {
311 NetworkType netType = Undefined;
312 // Checking the network type
313 if (std::string(FLAGS_t) == "C") {
314 netType = Classification;
315 } else if (std::string(FLAGS_t) == "OD") {
316 netType = ObjDetection;
317 } else if (std::string(FLAGS_t) == "RawC") {
319 } else if (std::string(FLAGS_t) == "RawOD") {
322 ee << UserException(5, "Unknown network type specified (invalid -t option)");
325 // Checking required options
326 if (FLAGS_m.empty()) ee << UserException(3, "Model file is not specified (missing -m option)");
327 if (FLAGS_i.empty()) ee << UserException(4, "Images list is not specified (missing -i option)");
328 if (FLAGS_d.empty()) ee << UserException(5, "Target device is not specified (missing -d option)");
329 if (FLAGS_b < 0) ee << UserException(6, "Batch must be positive (invalid -b option value)");
331 if (netType == ObjDetection) {
332 // Checking required OD-specific options
333 if (FLAGS_ODa.empty()) ee << UserException(11, "Annotations folder is not specified for object detection (missing -a option)");
334 if (FLAGS_ODc.empty()) ee << UserException(12, "Classes file is not specified (missing -c option)");
337 if (!ee.empty()) throw ee;
338 // -----------------------------------------------------------------------------------------------------
340 // ---------------------Loading plugin for Inference Engine------------------------------------------------
341 slog::info << "Loading plugin" << slog::endl;
342 /** Loading the library with extensions if provided**/
343 InferencePlugin plugin = PluginDispatcher({ FLAGS_pp, "../../../lib/intel64", "" }).getPluginByDevice(FLAGS_d);
345 /** Loading default extensions **/
346 if (FLAGS_d.find("CPU") != std::string::npos) {
348 * cpu_extensions library is compiled from "extension" folder containing
349 * custom CPU plugin layer implementations. These layers are not supported
350 * by CPU, but they can be useful for inferring custom topologies.
352 plugin.AddExtension(std::make_shared<Extensions::Cpu::CpuExtensions>());
355 if (!FLAGS_l.empty()) {
356 // CPU extensions are loaded as a shared library and passed as a pointer to base extension
357 IExtensionPtr extension_ptr = make_so_pointer<IExtension>(FLAGS_l);
358 plugin.AddExtension(extension_ptr);
359 slog::info << "CPU Extension loaded: " << FLAGS_l << slog::endl;
361 if (!FLAGS_c.empty()) {
362 // GPU extensions are loaded from an .xml description and OpenCL kernel files
363 plugin.SetConfig({{PluginConfigParams::KEY_CONFIG_FILE, FLAGS_c}});
364 slog::info << "GPU Extension loaded: " << FLAGS_c << slog::endl;
367 printPluginVersion(plugin, std::cout);
369 CsvDumper dumper(FLAGS_dump);
371 std::shared_ptr<Processor> processor;
373 PreprocessingOptions preprocessingOptions;
374 if (strtolower(FLAGS_ppType.c_str()) == "none") {
375 preprocessingOptions = PreprocessingOptions(false, ResizeCropPolicy::DoNothing);
376 } else if (strtolower(FLAGS_ppType) == "resizecrop") {
377 size_t ppWidth = FLAGS_ppSize;
378 size_t ppHeight = FLAGS_ppSize;
380 if (FLAGS_ppWidth > 0) ppWidth = FLAGS_ppSize;
381 if (FLAGS_ppHeight > 0) ppHeight = FLAGS_ppSize;
383 if (FLAGS_ppSize > 0 || (FLAGS_ppWidth > 0 && FLAGS_ppHeight > 0)) {
384 preprocessingOptions = PreprocessingOptions(false, ResizeCropPolicy::ResizeThenCrop, ppWidth, ppHeight);
386 THROW_USER_EXCEPTION(2) << "Size must be specified for preprocessing type " << FLAGS_ppType;
388 } else if (strtolower(FLAGS_ppType) == "resize" || FLAGS_ppType.empty()) {
389 preprocessingOptions = PreprocessingOptions(false, ResizeCropPolicy::Resize);
391 THROW_USER_EXCEPTION(2) << "Unknown preprocessing type: " << FLAGS_ppType;
394 if (netType == Classification || netType == RawC) {
395 processor = std::shared_ptr<Processor>(
396 new ClassificationCalibrator(FLAGS_subset, FLAGS_m, FLAGS_d, FLAGS_i, FLAGS_b,
397 plugin, dumper, FLAGS_lbl, preprocessingOptions, FLAGS_Czb));
398 } else if (netType == ObjDetection || netType == RawOD) {
399 if (FLAGS_ODkind == "SSD") {
400 processor = std::shared_ptr<Processor>(
401 new SSDObjectDetectionCalibrator(FLAGS_subset, FLAGS_m, FLAGS_d, FLAGS_i, FLAGS_ODsubdir, FLAGS_b,
402 0.5, plugin, dumper, FLAGS_ODa, FLAGS_ODc));
403 /* } else if (FLAGS_ODkind == "YOLO") {
404 processor = std::shared_ptr<Processor>(
405 new YOLOObjectDetectionProcessor(FLAGS_m, FLAGS_d, FLAGS_i, FLAGS_ODsubdir, FLAGS_b,
406 0.5, plugin, dumper, FLAGS_ODa, FLAGS_ODc));
410 THROW_USER_EXCEPTION(2) << "Unknown network type specified" << FLAGS_ppType;
412 if (!processor.get()) {
413 THROW_USER_EXCEPTION(2) << "Processor pointer is invalid" << FLAGS_ppType;
416 Int8Calibrator* calibrator = dynamic_cast<Int8Calibrator*>(processor.get());
418 if (netType != RawC && netType != RawOD) {
419 slog::info << "Collecting accuracy metric in FP32 mode to get a baseline, collecting activation statistics" << slog::endl;
421 slog::info << "Collecting activation statistics" << slog::endl;
423 calibrator->collectFP32Statistic();
424 shared_ptr<Processor::InferenceMetrics> pIMFP32 = processor->Process(FLAGS_stream_output);
425 const CalibrationMetrics* mFP32 = dynamic_cast<const CalibrationMetrics*>(pIMFP32.get());
426 std:: cout << " FP32 Accuracy: " << OUTPUT_FLOATING(100.0 * mFP32->AccuracyResult) << "% " << std::endl;
428 InferenceEngine::NetworkStatsMap statMap;
429 std::map<std::string, bool> layersToInt8;
430 bool bAccuracy = false;
432 if (netType != RawC && netType != RawOD) {
433 slog::info << "Verification of network accuracy if all possible layers converted to INT8" << slog::endl;
434 float bestThreshold = 100.f;
435 float maximalAccuracy = 0.f;
436 for (float threshold = 100.0f; threshold > 95.0f; threshold -= 0.5) {
437 std::cout << "Validate int8 accuracy, threshold for activation statistics = " << threshold << std::endl;
438 InferenceEngine::NetworkStatsMap tmpStatMap = calibrator->getStatistic(threshold);
439 calibrator->validateInt8Config(tmpStatMap, {});
440 shared_ptr<Processor::InferenceMetrics> pIM_I8 = processor->Process(FLAGS_stream_output);
441 const CalibrationMetrics *mI8 = dynamic_cast<const CalibrationMetrics *>(pIM_I8.get());
442 if (maximalAccuracy < mI8->AccuracyResult) {
443 maximalAccuracy = mI8->AccuracyResult;
444 bestThreshold = threshold;
446 std::cout << " Accuracy is " << OUTPUT_FLOATING(100.0 * mI8->AccuracyResult) << "%" << std::endl;
449 statMap = calibrator->getStatistic(bestThreshold);
451 if ((mFP32->AccuracyResult - maximalAccuracy) > (FLAGS_threshold / 100)) {
452 slog::info << "Accuracy of all layers conversion does not correspond to the required threshold\n";
453 cout << "FP32 Accuracy: " << OUTPUT_FLOATING(100.0 * mFP32->AccuracyResult) << "% vs " <<
454 "all Int8 layers Accuracy: " << OUTPUT_FLOATING(100.0 * maximalAccuracy) << "%, " <<
455 "threshold for activation statistics: " << bestThreshold << "%" << std::endl;
456 slog::info << "Collecting intermediate per-layer accuracy drop" << slog::endl;
457 // getting statistic on accuracy drop by layers
458 calibrator->collectByLayerStatistic(statMap);
459 processor->Process(FLAGS_stream_output);
460 // starting to reduce number of layers being converted to Int8
461 std::map<std::string, float> layersAccuracyDrop = calibrator->layersAccuracyDrop();
463 std::map<float, std::string> orderedLayersAccuracyDrop;
464 for (auto d : layersAccuracyDrop) {
465 orderedLayersAccuracyDrop[d.second] = d.first;
466 layersToInt8[d.first] = true;
468 std::map<float, std::string>::const_reverse_iterator it = orderedLayersAccuracyDrop.crbegin();
470 shared_ptr<Processor::InferenceMetrics> pIM_I8;
471 const CalibrationMetrics *mI8;
472 while (it != orderedLayersAccuracyDrop.crend() && bAccuracy == false) {
473 slog::info << "Returning of '" << it->second << "' to FP32 precision, start validation\n";
474 layersToInt8[it->second] = false;
475 calibrator->validateInt8Config(statMap, layersToInt8);
476 pIM_I8 = processor->Process(FLAGS_stream_output);
477 mI8 = dynamic_cast<const CalibrationMetrics *>(pIM_I8.get());
478 maximalAccuracy = mI8->AccuracyResult;
479 if ((mFP32->AccuracyResult - maximalAccuracy) > (FLAGS_threshold / 100)) {
480 cout << "FP32 Accuracy: " << OUTPUT_FLOATING(100.0 * mFP32->AccuracyResult) << "% vs " <<
481 "current Int8 configuration Accuracy: " << OUTPUT_FLOATING(100.0 * maximalAccuracy) << "%" << std::endl;
492 slog::info << "Achieved required accuracy drop satisfying threshold\n";
493 cout << "FP32 accuracy: " << OUTPUT_FLOATING(100.0 * mFP32->AccuracyResult) << "% vs " <<
494 "current Int8 configuration accuracy: " << OUTPUT_FLOATING(100.0 * maximalAccuracy) << "% " <<
495 "with threshold for activation statistic: " << bestThreshold << "%" << std::endl;
496 std::string outModelName = FLAGS_output.empty() ? fileNameNoExt(FLAGS_m) + "_i8" : fileNameNoExt(FLAGS_output);
497 SaveCalibratedIR(FLAGS_m, outModelName, layersToInt8, statMap);
499 slog::info << "Required threshold of accuracy drop cannot be achieved with any int8 quantization\n";
502 std::cout << "Collected activation statistics, writing maximum values to IR" << std::endl;
503 statMap = calibrator->getStatistic(100.0f);
504 std::string outModelName = FLAGS_output.empty() ? fileNameNoExt(FLAGS_m) + "_i8" : fileNameNoExt(FLAGS_output);
505 SaveCalibratedIR(FLAGS_m, outModelName, layersToInt8, statMap);
508 if (dumper.dumpEnabled()) {
509 slog::info << "Dump file generated: " << dumper.getFilename() << slog::endl;
511 } catch (const InferenceEngineException& ex) {
512 slog::err << "Inference problem: \n" << ex.what() << slog::endl;
514 } catch (const UserException& ex) {
515 slog::err << "Input problem: \n" << ex.what() << slog::endl;
517 return ex.exitCode();
518 } catch (const UserExceptions& ex) {
519 if (ex.list().size() == 1) {
520 slog::err << "Input problem: " << ex.what() << slog::endl;
522 return ex.list().begin()->exitCode();
524 const char* s = ex.what();
525 slog::err << "Input problems: \n" << ex.what() << slog::endl;
527 return ex.list().begin()->exitCode();