ArmNN
 21.02
ModelAccuracyTool-Armnn.cpp
Go to the documentation of this file.
1 //
2 // Copyright © 2017 Arm Ltd. All rights reserved.
3 // SPDX-License-Identifier: MIT
4 //
5 
6 #include "../ImageTensorGenerator/ImageTensorGenerator.hpp"
7 #include "../InferenceTest.hpp"
10 #include <Filesystem.hpp>
11 
12 #include <cxxopts/cxxopts.hpp>
13 #include <map>
14 
15 using namespace armnn::test;
16 
17 /** Load image names and ground-truth labels from the image directory and the ground truth label file
18  *
19  * @pre \p validationLabelPath exists and is valid regular file
20  * @pre \p imageDirectoryPath exists and is valid directory
21  * @pre labels in validation file correspond to images which are in lexicographical order with the image name
22  * @pre image index starts at 1
23  * @pre \p begIndex and \p endIndex are end-inclusive
24  *
25  * @param[in] validationLabelPath Path to validation label file
26  * @param[in] imageDirectoryPath Path to directory containing validation images
27  * @param[in] begIndex Begin index of images to be loaded. Inclusive
28  * @param[in] endIndex End index of images to be loaded. Inclusive
29  * @param[in] blacklistPath Path to blacklist file
30  * @return A map mapping image file names to their corresponding ground-truth labels
31  */
32 map<std::string, std::string> LoadValidationImageFilenamesAndLabels(const string& validationLabelPath,
33  const string& imageDirectoryPath,
34  size_t begIndex = 0,
35  size_t endIndex = 0,
36  const string& blacklistPath = "");
37 
38 /** Load model output labels from file
39  *
40  * @pre \p modelOutputLabelsPath exists and is a regular file
41  *
42  * @param[in] modelOutputLabelsPath path to model output labels file
43  * @return A vector of labels, which in turn is described by a list of category names
44  */
45 std::vector<armnnUtils::LabelCategoryNames> LoadModelOutputLabels(const std::string& modelOutputLabelsPath);
46 
47 int main(int argc, char* argv[])
48 {
49  try
50  {
52  armnn::ConfigureLogging(true, true, level);
53 
54  std::string modelPath;
55  std::string modelFormat;
56  std::vector<std::string> inputNames;
57  std::vector<std::string> outputNames;
58  std::string dataDir;
59  std::string modelOutputLabelsPath;
60  std::string validationLabelPath;
61  std::string inputLayout;
62  std::vector<armnn::BackendId> computeDevice;
63  std::string validationRange;
64  std::string blacklistPath;
65 
66  const std::string backendsMessage = "Which device to run layers on by default. Possible choices: "
68 
69  try
70  {
71  cxxopts::Options options("ModeAccuracyTool-Armnn","Options");
72 
73  options.add_options()
74  ("h,help", "Display help messages")
75  ("m,model-path",
76  "Path to armnn format model file",
77  cxxopts::value<std::string>(modelPath))
78  ("f,model-format",
79  "The model format. Supported values: caffe, tensorflow, tflite",
80  cxxopts::value<std::string>(modelFormat))
81  ("i,input-name",
82  "Identifier of the input tensors in the network separated by comma with no space.",
83  cxxopts::value<std::vector<std::string>>(inputNames))
84  ("o,output-name",
85  "Identifier of the output tensors in the network separated by comma with no space.",
86  cxxopts::value<std::vector<std::string>>(outputNames))
87  ("d,data-dir",
88  "Path to directory containing the ImageNet test data",
89  cxxopts::value<std::string>(dataDir))
90  ("p,model-output-labels",
91  "Path to model output labels file.",
92  cxxopts::value<std::string>(modelOutputLabelsPath))
93  ("v,validation-labels-path",
94  "Path to ImageNet Validation Label file",
95  cxxopts::value<std::string>(validationLabelPath))
96  ("l,data-layout",
97  "Data layout. Supported value: NHWC, NCHW. Default: NHWC",
98  cxxopts::value<std::string>(inputLayout)->default_value("NHWC"))
99  ("c,compute",
100  backendsMessage.c_str(),
101  cxxopts::value<std::vector<armnn::BackendId>>(computeDevice)->default_value("CpuAcc,CpuRef"))
102  ("r,validation-range",
103  "The range of the images to be evaluated. Specified in the form <begin index>:<end index>."
104  "The index starts at 1 and the range is inclusive."
105  "By default the evaluation will be performed on all images.",
106  cxxopts::value<std::string>(validationRange)->default_value("1:0"))
107  ("b,blacklist-path",
108  "Path to a blacklist file where each line denotes the index of an image to be "
109  "excluded from evaluation.",
110  cxxopts::value<std::string>(blacklistPath)->default_value(""));
111 
112  auto result = options.parse(argc, argv);
113 
114  if (result.count("help") > 0)
115  {
116  std::cout << options.help() << std::endl;
117  return EXIT_FAILURE;
118  }
119 
120  // Check for mandatory single options.
121  std::string mandatorySingleParameters[] = { "model-path", "model-format", "input-name", "output-name",
122  "data-dir", "model-output-labels", "validation-labels-path" };
123  for (auto param : mandatorySingleParameters)
124  {
125  if (result.count(param) != 1)
126  {
127  std::cerr << "Parameter \'--" << param << "\' is required but missing." << std::endl;
128  return EXIT_FAILURE;
129  }
130  }
131  }
132  catch (const cxxopts::OptionException& e)
133  {
134  std::cerr << e.what() << std::endl << std::endl;
135  return EXIT_FAILURE;
136  }
137  catch (const std::exception& e)
138  {
139  // Coverity points out that default_value(...) can throw a bad_lexical_cast,
140  // and that desc.add_options() can throw boost::io::too_few_args.
141  // They really won't in any of these cases.
142  ARMNN_ASSERT_MSG(false, "Caught unexpected exception");
143  std::cerr << "Fatal internal error: " << e.what() << std::endl;
144  return EXIT_FAILURE;
145  }
146 
147  // Check if the requested backend are all valid
148  std::string invalidBackends;
149  if (!CheckRequestedBackendsAreValid(computeDevice, armnn::Optional<std::string&>(invalidBackends)))
150  {
151  ARMNN_LOG(fatal) << "The list of preferred devices contains invalid backend IDs: "
152  << invalidBackends;
153  return EXIT_FAILURE;
154  }
155  armnn::Status status;
156 
157  // Create runtime
160  std::ifstream file(modelPath);
161 
162  // Create Parser
163  using IParser = armnnDeserializer::IDeserializer;
164  auto armnnparser(IParser::Create());
165 
166  // Create a network
167  armnn::INetworkPtr network = armnnparser->CreateNetworkFromBinary(file);
168 
169  // Optimizes the network.
170  armnn::IOptimizedNetworkPtr optimizedNet(nullptr, nullptr);
171  try
172  {
173  optimizedNet = armnn::Optimize(*network, computeDevice, runtime->GetDeviceSpec());
174  }
175  catch (const armnn::Exception& e)
176  {
177  std::stringstream message;
178  message << "armnn::Exception (" << e.what() << ") caught from optimize.";
179  ARMNN_LOG(fatal) << message.str();
180  return EXIT_FAILURE;
181  }
182 
183  // Loads the network into the runtime.
184  armnn::NetworkId networkId;
185  status = runtime->LoadNetwork(networkId, std::move(optimizedNet));
186  if (status == armnn::Status::Failure)
187  {
188  ARMNN_LOG(fatal) << "armnn::IRuntime: Failed to load network";
189  return EXIT_FAILURE;
190  }
191 
192  // Set up Network
194 
195  // Handle inputNames and outputNames, there can be multiple.
196  std::vector<BindingPointInfo> inputBindings;
197  for(auto& input: inputNames)
198  {
200  inputBindingInfo = armnnparser->GetNetworkInputBindingInfo(0, input);
201 
202  std::pair<armnn::LayerBindingId, armnn::TensorInfo>
203  m_InputBindingInfo(inputBindingInfo.m_BindingId, inputBindingInfo.m_TensorInfo);
204  inputBindings.push_back(m_InputBindingInfo);
205  }
206 
207  std::vector<BindingPointInfo> outputBindings;
208  for(auto& output: outputNames)
209  {
211  outputBindingInfo = armnnparser->GetNetworkOutputBindingInfo(0, output);
212 
213  std::pair<armnn::LayerBindingId, armnn::TensorInfo>
214  m_OutputBindingInfo(outputBindingInfo.m_BindingId, outputBindingInfo.m_TensorInfo);
215  outputBindings.push_back(m_OutputBindingInfo);
216  }
217 
218  // Load model output labels
219  if (modelOutputLabelsPath.empty() || !fs::exists(modelOutputLabelsPath) ||
220  !fs::is_regular_file(modelOutputLabelsPath))
221  {
222  ARMNN_LOG(fatal) << "Invalid model output labels path at " << modelOutputLabelsPath;
223  }
224  const std::vector<armnnUtils::LabelCategoryNames> modelOutputLabels =
225  LoadModelOutputLabels(modelOutputLabelsPath);
226 
227  // Parse begin and end image indices
228  std::vector<std::string> imageIndexStrs = armnnUtils::SplitBy(validationRange, ":");
229  size_t imageBegIndex;
230  size_t imageEndIndex;
231  if (imageIndexStrs.size() != 2)
232  {
233  ARMNN_LOG(fatal) << "Invalid validation range specification: Invalid format " << validationRange;
234  return EXIT_FAILURE;
235  }
236  try
237  {
238  imageBegIndex = std::stoul(imageIndexStrs[0]);
239  imageEndIndex = std::stoul(imageIndexStrs[1]);
240  }
241  catch (const std::exception& e)
242  {
243  ARMNN_LOG(fatal) << "Invalid validation range specification: " << validationRange;
244  return EXIT_FAILURE;
245  }
246 
247  // Validate blacklist file if it's specified
248  if (!blacklistPath.empty() &&
249  !(fs::exists(blacklistPath) && fs::is_regular_file(blacklistPath)))
250  {
251  ARMNN_LOG(fatal) << "Invalid path to blacklist file at " << blacklistPath;
252  return EXIT_FAILURE;
253  }
254 
255  fs::path pathToDataDir(dataDir);
256  const map<std::string, std::string> imageNameToLabel = LoadValidationImageFilenamesAndLabels(
257  validationLabelPath, pathToDataDir.string(), imageBegIndex, imageEndIndex, blacklistPath);
258  armnnUtils::ModelAccuracyChecker checker(imageNameToLabel, modelOutputLabels);
259  using TContainer = mapbox::util::variant<std::vector<float>, std::vector<int>, std::vector<uint8_t>>;
260 
261  if (ValidateDirectory(dataDir))
262  {
264 
265  params.m_ModelPath = modelPath;
266  params.m_IsModelBinary = true;
267  params.m_ComputeDevices = computeDevice;
268  // Insert inputNames and outputNames into params vector
269  params.m_InputBindings.insert(std::end(params.m_InputBindings),
270  std::begin(inputNames),
271  std::end(inputNames));
272  params.m_OutputBindings.insert(std::end(params.m_OutputBindings),
273  std::begin(outputNames),
274  std::end(outputNames));
275 
276  using TParser = armnnDeserializer::IDeserializer;
277  // If dynamicBackends is empty it will be disabled by default.
278  InferenceModel<TParser, float> model(params, false, "");
279 
280  // Get input tensor information
281  const armnn::TensorInfo& inputTensorInfo = model.GetInputBindingInfo().second;
282  const armnn::TensorShape& inputTensorShape = inputTensorInfo.GetShape();
283  const armnn::DataType& inputTensorDataType = inputTensorInfo.GetDataType();
284  armnn::DataLayout inputTensorDataLayout;
285  if (inputLayout == "NCHW")
286  {
287  inputTensorDataLayout = armnn::DataLayout::NCHW;
288  }
289  else if (inputLayout == "NHWC")
290  {
291  inputTensorDataLayout = armnn::DataLayout::NHWC;
292  }
293  else
294  {
295  ARMNN_LOG(fatal) << "Invalid Data layout: " << inputLayout;
296  return EXIT_FAILURE;
297  }
298  const unsigned int inputTensorWidth =
299  inputTensorDataLayout == armnn::DataLayout::NCHW ? inputTensorShape[3] : inputTensorShape[2];
300  const unsigned int inputTensorHeight =
301  inputTensorDataLayout == armnn::DataLayout::NCHW ? inputTensorShape[2] : inputTensorShape[1];
302  // Get output tensor info
303  const unsigned int outputNumElements = model.GetOutputSize();
304  // Check output tensor shape is valid
305  if (modelOutputLabels.size() != outputNumElements)
306  {
307  ARMNN_LOG(fatal) << "Number of output elements: " << outputNumElements
308  << " , mismatches the number of output labels: " << modelOutputLabels.size();
309  return EXIT_FAILURE;
310  }
311 
312  const unsigned int batchSize = 1;
313  // Get normalisation parameters
314  SupportedFrontend modelFrontend;
315  if (modelFormat == "caffe")
316  {
317  modelFrontend = SupportedFrontend::Caffe;
318  }
319  else if (modelFormat == "tensorflow")
320  {
321  modelFrontend = SupportedFrontend::TensorFlow;
322  }
323  else if (modelFormat == "tflite")
324  {
325  modelFrontend = SupportedFrontend::TFLite;
326  }
327  else
328  {
329  ARMNN_LOG(fatal) << "Unsupported frontend: " << modelFormat;
330  return EXIT_FAILURE;
331  }
332  const NormalizationParameters& normParams = GetNormalizationParameters(modelFrontend, inputTensorDataType);
333  for (const auto& imageEntry : imageNameToLabel)
334  {
335  const std::string imageName = imageEntry.first;
336  std::cout << "Processing image: " << imageName << "\n";
337 
338  vector<TContainer> inputDataContainers;
339  vector<TContainer> outputDataContainers;
340 
341  auto imagePath = pathToDataDir / fs::path(imageName);
342  switch (inputTensorDataType)
343  {
345  inputDataContainers.push_back(
346  PrepareImageTensor<int>(imagePath.string(),
347  inputTensorWidth, inputTensorHeight,
348  normParams,
349  batchSize,
350  inputTensorDataLayout));
351  outputDataContainers = { vector<int>(outputNumElements) };
352  break;
354  inputDataContainers.push_back(
355  PrepareImageTensor<uint8_t>(imagePath.string(),
356  inputTensorWidth, inputTensorHeight,
357  normParams,
358  batchSize,
359  inputTensorDataLayout));
360  outputDataContainers = { vector<uint8_t>(outputNumElements) };
361  break;
363  default:
364  inputDataContainers.push_back(
365  PrepareImageTensor<float>(imagePath.string(),
366  inputTensorWidth, inputTensorHeight,
367  normParams,
368  batchSize,
369  inputTensorDataLayout));
370  outputDataContainers = { vector<float>(outputNumElements) };
371  break;
372  }
373 
374  status = runtime->EnqueueWorkload(networkId,
375  armnnUtils::MakeInputTensors(inputBindings, inputDataContainers),
376  armnnUtils::MakeOutputTensors(outputBindings, outputDataContainers));
377 
378  if (status == armnn::Status::Failure)
379  {
380  ARMNN_LOG(fatal) << "armnn::IRuntime: Failed to enqueue workload for image: " << imageName;
381  }
382 
383  checker.AddImageResult<TContainer>(imageName, outputDataContainers);
384  }
385  }
386  else
387  {
388  return EXIT_SUCCESS;
389  }
390 
391  for(unsigned int i = 1; i <= 5; ++i)
392  {
393  std::cout << "Top " << i << " Accuracy: " << checker.GetAccuracy(i) << "%" << "\n";
394  }
395 
396  ARMNN_LOG(info) << "Accuracy Tool ran successfully!";
397  return EXIT_SUCCESS;
398  }
399  catch (const armnn::Exception& e)
400  {
401  // Coverity fix: BOOST_LOG_TRIVIAL (typically used to report errors) may throw an
402  // exception of type std::length_error.
403  // Using stderr instead in this context as there is no point in nesting try-catch blocks here.
404  std::cerr << "Armnn Error: " << e.what() << std::endl;
405  return EXIT_FAILURE;
406  }
407  catch (const std::exception& e)
408  {
409  // Coverity fix: various boost exceptions can be thrown by methods called by this test.
410  std::cerr << "WARNING: ModelAccuracyTool-Armnn: An error has occurred when running the "
411  "Accuracy Tool: " << e.what() << std::endl;
412  return EXIT_FAILURE;
413  }
414 }
415 
416 map<std::string, std::string> LoadValidationImageFilenamesAndLabels(const string& validationLabelPath,
417  const string& imageDirectoryPath,
418  size_t begIndex,
419  size_t endIndex,
420  const string& blacklistPath)
421 {
422  // Populate imageFilenames with names of all .JPEG, .PNG images
423  std::vector<std::string> imageFilenames;
424  for (const auto& imageEntry : fs::directory_iterator(fs::path(imageDirectoryPath)))
425  {
426  fs::path imagePath = imageEntry.path();
427 
428  // Get extension and convert to uppercase
429  std::string imageExtension = imagePath.extension().string();
430  std::transform(imageExtension.begin(), imageExtension.end(), imageExtension.begin(), ::toupper);
431 
432  if (fs::is_regular_file(imagePath) && (imageExtension == ".JPEG" || imageExtension == ".PNG"))
433  {
434  imageFilenames.push_back(imagePath.filename().string());
435  }
436  }
437  if (imageFilenames.empty())
438  {
439  throw armnn::Exception("No image file (JPEG, PNG) found at " + imageDirectoryPath);
440  }
441 
442  // Sort the image filenames lexicographically
443  std::sort(imageFilenames.begin(), imageFilenames.end());
444 
445  std::cout << imageFilenames.size() << " images found at " << imageDirectoryPath << std::endl;
446 
447  // Get default end index
448  if (begIndex < 1 || endIndex > imageFilenames.size())
449  {
450  throw armnn::Exception("Invalid image index range");
451  }
452  endIndex = endIndex == 0 ? imageFilenames.size() : endIndex;
453  if (begIndex > endIndex)
454  {
455  throw armnn::Exception("Invalid image index range");
456  }
457 
458  // Load blacklist if there is one
459  std::vector<unsigned int> blacklist;
460  if (!blacklistPath.empty())
461  {
462  std::ifstream blacklistFile(blacklistPath);
463  unsigned int index;
464  while (blacklistFile >> index)
465  {
466  blacklist.push_back(index);
467  }
468  }
469 
470  // Load ground truth labels and pair them with corresponding image names
471  std::string classification;
472  map<std::string, std::string> imageNameToLabel;
473  ifstream infile(validationLabelPath);
474  size_t imageIndex = begIndex;
475  size_t blacklistIndexCount = 0;
476  while (std::getline(infile, classification))
477  {
478  if (imageIndex > endIndex)
479  {
480  break;
481  }
482  // If current imageIndex is included in blacklist, skip the current image
483  if (blacklistIndexCount < blacklist.size() && imageIndex == blacklist[blacklistIndexCount])
484  {
485  ++imageIndex;
486  ++blacklistIndexCount;
487  continue;
488  }
489  imageNameToLabel.insert(std::pair<std::string, std::string>(imageFilenames[imageIndex - 1], classification));
490  ++imageIndex;
491  }
492  std::cout << blacklistIndexCount << " images blacklisted" << std::endl;
493  std::cout << imageIndex - begIndex - blacklistIndexCount << " images to be loaded" << std::endl;
494  return imageNameToLabel;
495 }
496 
497 std::vector<armnnUtils::LabelCategoryNames> LoadModelOutputLabels(const std::string& modelOutputLabelsPath)
498 {
499  std::vector<armnnUtils::LabelCategoryNames> modelOutputLabels;
500  ifstream modelOutputLablesFile(modelOutputLabelsPath);
501  std::string line;
502  while (std::getline(modelOutputLablesFile, line))
503  {
505  armnnUtils::LabelCategoryNames predictionCategoryNames = armnnUtils::SplitBy(tokens.back(), ",");
506  std::transform(predictionCategoryNames.begin(), predictionCategoryNames.end(), predictionCategoryNames.begin(),
507  [](const std::string& category) { return armnnUtils::Strip(category); });
508  modelOutputLabels.push_back(predictionCategoryNames);
509  }
510  return modelOutputLabels;
511 }
static IRuntimePtr Create(const CreationOptions &options)
Definition: Runtime.cpp:37
DataLayout
Definition: Types.hpp:50
const TensorShape & GetShape() const
Definition: Tensor.hpp:187
void ConfigureLogging(bool printToStandardOutput, bool printToDebugOutput, LogSeverity severity)
Configures the logging behaviour of the ARMNN library.
Definition: Utils.cpp:18
NormalizationParameters GetNormalizationParameters(const SupportedFrontend &modelFormat, const armnn::DataType &outputType)
Get normalization parameters.
mapbox::util::variant< std::vector< float >, std::vector< int >, std::vector< unsigned char > > TContainer
std::unique_ptr< IRuntime, void(*)(IRuntime *runtime)> IRuntimePtr
Definition: IRuntime.hpp:26
virtual const char * what() const noexcept override
Definition: Exceptions.cpp:32
#define ARMNN_LOG(severity)
Definition: Logging.hpp:202
BackendRegistry & BackendRegistryInstance()
const armnn::BindingPointInfo & GetInputBindingInfo(unsigned int inputIndex=0u) const
std::vector< uint8_t > PrepareImageTensor< uint8_t >(const std::string &imagePath, unsigned int newWidth, unsigned int newHeight, const NormalizationParameters &normParams, unsigned int batchSize, const armnn::DataLayout &outputLayout)
armnn::BindingPointInfo BindingPointInfo
int NetworkId
Definition: IRuntime.hpp:20
std::string GetBackendIdsAsString() const
map< std::string, std::string > LoadValidationImageFilenamesAndLabels(const string &validationLabelPath, const string &imageDirectoryPath, size_t begIndex=0, size_t endIndex=0, const string &blacklistPath="")
Load image names and ground-truth labels from the image directory and the ground truth label file...
unsigned int GetOutputSize(unsigned int outputIndex=0u) const
std::vector< std::string > m_InputBindings
std::string Strip(const std::string &originalString, const std::string &characterSet)
Remove any preceding and trailing character specified in the characterSet.
DataType
Definition: Types.hpp:32
armnn::InputTensors MakeInputTensors(const std::vector< armnn::BindingPointInfo > &inputBindings, const std::vector< TContainer > &inputDataContainers)
std::vector< std::string > SplitBy(const std::string &originalString, const std::string &delimiter, bool includeEmptyToken)
Split a string into tokens by a delimiter.
IOptimizedNetworkPtr Optimize(const INetwork &network, const std::vector< BackendId > &backendPreferences, const IDeviceSpec &deviceSpec, const OptimizerOptions &options=OptimizerOptions(), Optional< std::vector< std::string > &> messages=EmptyOptional())
Create an optimized version of the network.
Definition: Network.cpp:1502
#define ARMNN_ASSERT_MSG(COND, MSG)
Definition: Assert.hpp:15
std::vector< std::string > m_OutputBindings
std::vector< armnn::BackendId > m_ComputeDevices
DataType GetDataType() const
Definition: Tensor.hpp:194
Status
enumeration
Definition: Types.hpp:26
std::unique_ptr< IOptimizedNetwork, void(*)(IOptimizedNetwork *network)> IOptimizedNetworkPtr
Definition: INetwork.hpp:174
std::vector< int > PrepareImageTensor< int >(const std::string &imagePath, unsigned int newWidth, unsigned int newHeight, const NormalizationParameters &normParams, unsigned int batchSize, const armnn::DataLayout &outputLayout)
armnn::OutputTensors MakeOutputTensors(const std::vector< armnn::BindingPointInfo > &outputBindings, std::vector< TContainer > &outputDataContainers)
int main(int argc, char *argv[])
std::pair< armnn::LayerBindingId, armnn::TensorInfo > BindingPointInfo
Definition: Tensor.hpp:261
Base class for all ArmNN exceptions so that users can filter to just those.
Definition: Exceptions.hpp:46
bool ValidateDirectory(std::string &dir)
std::vector< armnnUtils::LabelCategoryNames > LoadModelOutputLabels(const std::string &modelOutputLabelsPath)
Load model output labels from file.
std::unique_ptr< INetwork, void(*)(INetwork *network)> INetworkPtr
Definition: INetwork.hpp:173
LogSeverity
Definition: Utils.hpp:13
std::vector< std::string > LabelCategoryNames
std::vector< float > PrepareImageTensor< float >(const std::string &imagePath, unsigned int newWidth, unsigned int newHeight, const NormalizationParameters &normParams, unsigned int batchSize, const armnn::DataLayout &outputLayout)