From 4088c675695d68317f2138e91e2a296537ba9f20 Mon Sep 17 00:00:00 2001 From: Amol Lele <19983848+leleamol@users.noreply.github.com> Date: Thu, 15 Nov 2018 17:02:41 -0800 Subject: [PATCH 1/6] [MXNET-1083] Add the example to demonstrate the inference workflow using C++ API --- cpp-package/example/inference/Makefile | 43 ++ .../example/inference/inception_inference.cpp | 456 ++++++++++++++++++ .../unit_test_inception_inference.sh | 39 ++ 3 files changed, 538 insertions(+) create mode 100644 cpp-package/example/inference/Makefile create mode 100644 cpp-package/example/inference/inception_inference.cpp create mode 100755 cpp-package/example/inference/unit_test_inception_inference.sh diff --git a/cpp-package/example/inference/Makefile b/cpp-package/example/inference/Makefile new file mode 100644 index 000000000000..af6ba576e95c --- /dev/null +++ b/cpp-package/example/inference/Makefile @@ -0,0 +1,43 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + + +prebuild : + $(shell cp -r ../../../lib ./) + $(shell wget -nc -O mean_224.nd https://github.com/dmlc/web-data/raw/master/mxnet/example/feature_extract/mean_224.nd) +CPPEX_SRC = $(wildcard *.cpp) +CPPEX_EXE = $(patsubst %.cpp, %, $(CPPEX_SRC)) +OPENCV_CFLAGS=`pkg-config --cflags opencv` +OPENCV_LDFLAGS=`pkg-config --libs opencv` + +CXX=g++ + + +CFLAGS=$(COMMFLAGS) -I../../../3rdparty/tvm/nnvm/include -I../../../3rdparty/dmlc-core/include -I ../../include -I ../../../include -Wall -O3 -msse3 -funroll-loops -Wno-unused-parameter -Wno-unknown-pragmas +CPPEX_EXTRA_LDFLAGS := -L../../../lib -lmxnet $(OPENCV_LDFLAGS) + +all: prebuild $(CPPEX_EXE) + +debug: CPPEX_CFLAGS += -DDEBUG -g +debug: prebuild all + + +$(CPPEX_EXE):% : %.cpp + $(CXX) -std=c++0x $(CFLAGS) $(CPPEX_CFLAGS) -o $@ $(filter %.cpp %.a, $^) $(CPPEX_EXTRA_LDFLAGS) + +clean: + rm -f $(CPPEX_EXE) diff --git a/cpp-package/example/inference/inception_inference.cpp b/cpp-package/example/inference/inception_inference.cpp new file mode 100644 index 000000000000..9b27fdaab4a8 --- /dev/null +++ b/cpp-package/example/inference/inception_inference.cpp @@ -0,0 +1,456 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/* + * This example demonstrates image classification workflow with pre-trained models using MXNet C++ API. + * The example performs following tasks. + * 1. Load the pre-trained model, + * 2. Load the parameters of pre-trained model, + * 3. Load the image to be classified in to NDArray. + * 4. Normalize the image using the mean of images that were used for training. + * 5. Run the forward pass and predict the input image. + */ + +#include +#include +#include +#include +#include +#include "mxnet-cpp/MxNetCpp.h" +#include +using namespace std; +using namespace mxnet::cpp; + + +/* + * class Predictor + * + * This class encapsulates the functionality to load the model, process input image and run the forward pass. + */ + +class Predictor { + public: + Predictor() {} +<<<<<<< HEAD + Predictor(const std::string& model_json, + const std::string& model_params, +======= + Predictor(const std::string model_json, + const std::string model_params, +>>>>>>> 04c8b404a1c35fa9b5ee4db0695cd8e235f63d52 + const Shape& input_shape, + bool gpu_context_type = false, + const std::string& synset_file = "", + const std::string& mean_image_file = ""); + void PredictImage(const std::string& image_file); + ~Predictor(); + + private: + void LoadModel(const std::string& model_json_file); + void LoadParameters(const std::string& model_parameters_file); + void LoadSynset(const std::string& synset_file); +<<<<<<< HEAD + NDArray LoadInputImage(const std::string& image_file); + void LoadMeanImageData(); +======= + void LoadInputImage(const std::string& image_file); +>>>>>>> 04c8b404a1c35fa9b5ee4db0695cd8e235f63d52 + void NormalizeInput(const std::string& mean_image_file); + + NDArray mean_img; + map args_map; + map aux_map; + std::vector output_labels; + Symbol net; + Executor *executor; + Shape input_shape; +<<<<<<< HEAD + NDArray mean_image_data; +======= + NDArray image_data; +>>>>>>> 04c8b404a1c35fa9b5ee4db0695cd8e235f63d52 + Context global_ctx = Context::cpu(); + string mean_image_file; +}; + + +/* + * The constructor takes following parameters as input: + * 1. model_json: The model in json formatted file. + * 2. model_params: File containing model parameters + * 3. synset_file: File containing the list of image labels + * 4. input_shape: Shape of input data to the model. Since this class will be running one inference at a time, + * the input shape is required to be in format Shape(1, number_of_channels, height, width) + * The input image will be resized to (height x width) size before running the inference. + * The constructor will: + * 1. Load the model and parameter files. + * 2. Load the synset file. + * 3. Invoke the SimpleBind to bind the input argument to the model and create an executor. + * + * The SimpleBind is expected to be invoked only once. + */ +<<<<<<< HEAD +Predictor::Predictor(const std::string& model_json, + const std::string& model_params, +======= +Predictor::Predictor(const std::string model_json, + const std::string model_params, +>>>>>>> 04c8b404a1c35fa9b5ee4db0695cd8e235f63d52 + const Shape& input_shape, + bool gpu_context_type, + const std::string& synset_file, + const std::string& mean_image_file): + input_shape(input_shape), + mean_image_file(mean_image_file) { + if (gpu_context_type) { + global_ctx = Context::gpu(); + } +<<<<<<< HEAD + +======= +>>>>>>> 04c8b404a1c35fa9b5ee4db0695cd8e235f63d52 + // Load the model + LoadModel(model_json); + + // Load the model parameters. + LoadParameters(model_params); + + /* + * Load the synset file containing the image labels, if provided. + * The data will be used to output the exact label that matches highest output of the model. + */ + if (!synset_file.empty()) { + LoadSynset(synset_file); + } +<<<<<<< HEAD + + /* + * Load the mean image data if specified. + */ + if (!mean_image_file.empty()) { + LoadMeanImageData(); + } + +======= +>>>>>>> 04c8b404a1c35fa9b5ee4db0695cd8e235f63d52 + // Create an executor after binding the model to input parameters. + args_map["data"] = NDArray(input_shape, global_ctx, false); + executor = net.SimpleBind(global_ctx, args_map, map(), + map(), aux_map); +} + +<<<<<<< HEAD + +======= +>>>>>>> 04c8b404a1c35fa9b5ee4db0695cd8e235f63d52 +/* + * The following function loads the model from json file. + */ +void Predictor::LoadModel(const std::string& model_json_file) { + LG << "Loading the model from " << model_json_file << std::endl; + net = Symbol::Load(model_json_file); +} + + +/* + * The following function loads the model parameters. + */ +void Predictor::LoadParameters(const std::string& model_parameters_file) { + LG << "Loading the model parameters from " << model_parameters_file << std::endl; + map paramters; + NDArray::Load(model_parameters_file, 0, ¶mters); + for (const auto &k : paramters) { + if (k.first.substr(0, 4) == "aux:") { + auto name = k.first.substr(4, k.first.size() - 4); + aux_map[name] = k.second.Copy(global_ctx); + } + if (k.first.substr(0, 4) == "arg:") { + auto name = k.first.substr(4, k.first.size() - 4); + args_map[name] = k.second.Copy(global_ctx); + } + } + /*WaitAll is need when we copy data between GPU and the main memory*/ + NDArray::WaitAll(); +} + + +/* + * The following function loads the synset file. + * This information will be used later to report the label of input image. + */ +void Predictor::LoadSynset(const string& synset_file) { + LG << "Loading the synset file."; + std::ifstream fi(synset_file.c_str()); + if (!fi.is_open()) { + std::cerr << "Error opening synset file " << synset_file << std::endl; + assert(false); + } + std::string synset, lemma; + while (fi >> synset) { + getline(fi, lemma); + output_labels.push_back(lemma); + } + fi.close(); +} + + +/* +<<<<<<< HEAD + * The following function loads the mean data from mean image file. + * This data will be used for normalizing the image before running the forward + * pass. + * + */ +void Predictor::LoadMeanImageData() { + LG << "Load the mean image data that will be used to normalize " + << "the image before running forward pass."; + mean_image_data = NDArray(input_shape, global_ctx, false); + mean_image_data.SyncCopyFromCPU( + NDArray::LoadToMap(mean_image_file)["mean_img"].GetData(), + input_shape.Size()); + NDArray::WaitAll(); +} + + +/* + * The following function loads the input image. + */ +NDArray Predictor::LoadInputImage(const std::string& image_file) { +======= + * The following function loads the input image. + */ +void Predictor::LoadInputImage(const std::string& image_file) { +>>>>>>> 04c8b404a1c35fa9b5ee4db0695cd8e235f63d52 + LG << "Loading the image " << image_file << std::endl; + vector array; + cv::Mat mat = cv::imread(image_file); + /*resize pictures to (224, 224) according to the pretrained model*/ + int height = input_shape[2]; + int width = input_shape[3]; + int channels = input_shape[1]; + cv::resize(mat, mat, cv::Size(height, width)); + for (int c = 0; c < channels; ++c) { + for (int i = 0; i < height; ++i) { + for (int j = 0; j < width; ++j) { + array.push_back(static_cast(mat.data[(i * height + j) * 3 + c])); + } + } + } +<<<<<<< HEAD + NDArray image_data = NDArray(input_shape, global_ctx, false); + image_data.SyncCopyFromCPU(array.data(), input_shape.Size()); + NDArray::WaitAll(); + return image_data; +======= + image_data = NDArray(input_shape, global_ctx, false); + image_data.SyncCopyFromCPU(array.data(), input_shape.Size()); + NDArray::WaitAll(); +} + + +/* + * The following function normalizes input image data by substracting the + * mean data. + */ +void Predictor::NormalizeInput(const std::string& mean_image_file) { + LG << "Normalizing image using " << mean_image_file; + mean_img = NDArray(input_shape, global_ctx, false); + mean_img.SyncCopyFromCPU( + NDArray::LoadToMap(mean_image_file)["mean_img"].GetData(), + input_shape.Size()); + NDArray::WaitAll(); + image_data.Slice(0, 1) -= mean_img; + return; +>>>>>>> 04c8b404a1c35fa9b5ee4db0695cd8e235f63d52 +} + + +/* + * The following function runs the forward pass on the model. + * The executor is created in the constructor. + * + */ +void Predictor::PredictImage(const std::string& image_file) { + // Load the input image +<<<<<<< HEAD + NDArray image_data = LoadInputImage(image_file); + + // Normalize the image + if (!mean_image_file.empty()) { + image_data.Slice(0, 1) -= mean_image_data; +======= + LoadInputImage(image_file); + + // Normalize the image + if (!mean_image_file.empty()) { + NormalizeInput(mean_image_file); +>>>>>>> 04c8b404a1c35fa9b5ee4db0695cd8e235f63d52 + } + + LG << "Running the forward pass on model to predict the image"; + /* + * The executor->arg_arrays represent the arguments to the model. + * + * Copying the image_data that contains the NDArray of input image + * to the arg map of the executor. The input is stored with the key "data" in the map. + * + */ + image_data.CopyTo(&(executor->arg_dict()["data"])); + NDArray::WaitAll(); + + // Run the forward pass. + executor->Forward(false); + + // The output is available in executor->outputs. + auto array = executor->outputs[0].Copy(global_ctx); + NDArray::WaitAll(); + + float best_accuracy = 0.0; + std::size_t best_idx = 0; + + // Find out the maximum accuracy and the index associated with that accuracy. + for (std::size_t i = 0; i < array.Size(); ++i) { + if (array.At(0, i) > best_accuracy) { + best_accuracy = array.At(0, i); + best_idx = i; + } + } + + if (output_labels.empty()) { + LG << "The model predicts the highest accuracy of " << best_accuracy << " at index " + << best_idx; + } else { + LG << "The model predicts the input image to be a [" << output_labels[best_idx] + << " ] with Accuracy = " << array.At(0, best_idx) << std::endl; + } +} + + +Predictor::~Predictor() { + if (executor) { + delete executor; + } + MXNotifyShutdown(); +} + + +/* + * Convert the input string of number of hidden units into the vector of integers. + */ +std::vector getShapeDimensions(const std::string& hidden_units_string) { + std::vector dimensions; + char *pNext; + int num_unit = strtol(hidden_units_string.c_str(), &pNext, 10); + dimensions.push_back(num_unit); + while (*pNext) { + num_unit = strtol(pNext, &pNext, 10); + dimensions.push_back(num_unit); + } + return dimensions; +} + +void printUsage() { + std::cout << "Usage:" << std::endl; + std::cout << "inception_inference --symbol " + << "--params " + << "--image input_dimensions = getShapeDimensions(input_shape); + + /* + * Since we are running inference for 1 image, add 1 to the input_dimensions so that + * the shape of input data for the model will be + * {no. of images, channels, height, width} + */ + input_dimensions.insert(input_dimensions.begin(), 1); + + Shape input_data_shape(input_dimensions); + + try { + // Initialize the predictor object + Predictor predict(model_file_json, model_file_params, input_data_shape, gpu_context_type, + synset_file, mean_image); + + // Run the forward pass to predict the image. + predict.PredictImage(input_image); + } catch (...) { + /* + * If underlying MXNet code has thrown an exception the error message is + * accessible through MXGetLastError() function. + */ + LG << MXGetLastError(); + } + return 0; +} diff --git a/cpp-package/example/inference/unit_test_inception_inference.sh b/cpp-package/example/inference/unit_test_inception_inference.sh new file mode 100755 index 000000000000..a375970c4ef8 --- /dev/null +++ b/cpp-package/example/inference/unit_test_inception_inference.sh @@ -0,0 +1,39 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +# Downloading the data and model +mkdir -p model +wget -nc http://data.dmlc.ml/mxnet/models/imagenet/inception-bn.tar.gz +wget -nc -O model/dog.jpg https://github.com/dmlc/web-data/blob/master/mxnet/doc/tutorials/python/predict_image/dog.jpg?raw=true +wget -nc -O model/mean_224.nd https://github.com/dmlc/web-data/raw/master/mxnet/example/feature_extract/mean_224.nd +tar -xvzf inception-bn.tar.gz -C model + +# Building +make all + + +# Running the example with dog image. +LD_LIBRARY_PATH=../../../lib ./inception_inference --symbol "./model/Inception-BN-symbol.json" --params "./model/Inception-BN-0126.params" --synset "./model/synset.txt" --mean "./model/mean_224.nd" --image "./model/dog.jpg" 2&> inception_inference.log +result=`grep -c "pug-dog" inception_inference.log` +if [ $result == 1 ]; +then + echo "PASS: inception_inference correctly identified the image." + exit 0 +else + echo "FAIL: inception_inference FAILED to identify the image." + exit 1 +fi From 425b15900fc4e7b97005cc105a5f2a448882f9cf Mon Sep 17 00:00:00 2001 From: Amol Lele <19983848+leleamol@users.noreply.github.com> Date: Thu, 15 Nov 2018 17:09:45 -0800 Subject: [PATCH 2/6] [MXNET-1083] Add the example to demonstrate the inference workflow using C++ API --- .../example/inference/inception_inference.cpp | 65 ------------------- 1 file changed, 65 deletions(-) diff --git a/cpp-package/example/inference/inception_inference.cpp b/cpp-package/example/inference/inception_inference.cpp index 9b27fdaab4a8..3c0a3e5a7bf5 100644 --- a/cpp-package/example/inference/inception_inference.cpp +++ b/cpp-package/example/inference/inception_inference.cpp @@ -47,13 +47,8 @@ using namespace mxnet::cpp; class Predictor { public: Predictor() {} -<<<<<<< HEAD Predictor(const std::string& model_json, const std::string& model_params, -======= - Predictor(const std::string model_json, - const std::string model_params, ->>>>>>> 04c8b404a1c35fa9b5ee4db0695cd8e235f63d52 const Shape& input_shape, bool gpu_context_type = false, const std::string& synset_file = "", @@ -65,12 +60,8 @@ class Predictor { void LoadModel(const std::string& model_json_file); void LoadParameters(const std::string& model_parameters_file); void LoadSynset(const std::string& synset_file); -<<<<<<< HEAD NDArray LoadInputImage(const std::string& image_file); void LoadMeanImageData(); -======= - void LoadInputImage(const std::string& image_file); ->>>>>>> 04c8b404a1c35fa9b5ee4db0695cd8e235f63d52 void NormalizeInput(const std::string& mean_image_file); NDArray mean_img; @@ -80,11 +71,7 @@ class Predictor { Symbol net; Executor *executor; Shape input_shape; -<<<<<<< HEAD NDArray mean_image_data; -======= - NDArray image_data; ->>>>>>> 04c8b404a1c35fa9b5ee4db0695cd8e235f63d52 Context global_ctx = Context::cpu(); string mean_image_file; }; @@ -105,13 +92,8 @@ class Predictor { * * The SimpleBind is expected to be invoked only once. */ -<<<<<<< HEAD Predictor::Predictor(const std::string& model_json, const std::string& model_params, -======= -Predictor::Predictor(const std::string model_json, - const std::string model_params, ->>>>>>> 04c8b404a1c35fa9b5ee4db0695cd8e235f63d52 const Shape& input_shape, bool gpu_context_type, const std::string& synset_file, @@ -121,10 +103,6 @@ Predictor::Predictor(const std::string model_json, if (gpu_context_type) { global_ctx = Context::gpu(); } -<<<<<<< HEAD - -======= ->>>>>>> 04c8b404a1c35fa9b5ee4db0695cd8e235f63d52 // Load the model LoadModel(model_json); @@ -138,7 +116,6 @@ Predictor::Predictor(const std::string model_json, if (!synset_file.empty()) { LoadSynset(synset_file); } -<<<<<<< HEAD /* * Load the mean image data if specified. @@ -147,18 +124,12 @@ Predictor::Predictor(const std::string model_json, LoadMeanImageData(); } -======= ->>>>>>> 04c8b404a1c35fa9b5ee4db0695cd8e235f63d52 // Create an executor after binding the model to input parameters. args_map["data"] = NDArray(input_shape, global_ctx, false); executor = net.SimpleBind(global_ctx, args_map, map(), map(), aux_map); } -<<<<<<< HEAD - -======= ->>>>>>> 04c8b404a1c35fa9b5ee4db0695cd8e235f63d52 /* * The following function loads the model from json file. */ @@ -211,7 +182,6 @@ void Predictor::LoadSynset(const string& synset_file) { /* -<<<<<<< HEAD * The following function loads the mean data from mean image file. * This data will be used for normalizing the image before running the forward * pass. @@ -232,11 +202,6 @@ void Predictor::LoadMeanImageData() { * The following function loads the input image. */ NDArray Predictor::LoadInputImage(const std::string& image_file) { -======= - * The following function loads the input image. - */ -void Predictor::LoadInputImage(const std::string& image_file) { ->>>>>>> 04c8b404a1c35fa9b5ee4db0695cd8e235f63d52 LG << "Loading the image " << image_file << std::endl; vector array; cv::Mat mat = cv::imread(image_file); @@ -252,32 +217,10 @@ void Predictor::LoadInputImage(const std::string& image_file) { } } } -<<<<<<< HEAD NDArray image_data = NDArray(input_shape, global_ctx, false); image_data.SyncCopyFromCPU(array.data(), input_shape.Size()); NDArray::WaitAll(); return image_data; -======= - image_data = NDArray(input_shape, global_ctx, false); - image_data.SyncCopyFromCPU(array.data(), input_shape.Size()); - NDArray::WaitAll(); -} - - -/* - * The following function normalizes input image data by substracting the - * mean data. - */ -void Predictor::NormalizeInput(const std::string& mean_image_file) { - LG << "Normalizing image using " << mean_image_file; - mean_img = NDArray(input_shape, global_ctx, false); - mean_img.SyncCopyFromCPU( - NDArray::LoadToMap(mean_image_file)["mean_img"].GetData(), - input_shape.Size()); - NDArray::WaitAll(); - image_data.Slice(0, 1) -= mean_img; - return; ->>>>>>> 04c8b404a1c35fa9b5ee4db0695cd8e235f63d52 } @@ -288,19 +231,11 @@ void Predictor::NormalizeInput(const std::string& mean_image_file) { */ void Predictor::PredictImage(const std::string& image_file) { // Load the input image -<<<<<<< HEAD NDArray image_data = LoadInputImage(image_file); // Normalize the image if (!mean_image_file.empty()) { image_data.Slice(0, 1) -= mean_image_data; -======= - LoadInputImage(image_file); - - // Normalize the image - if (!mean_image_file.empty()) { - NormalizeInput(mean_image_file); ->>>>>>> 04c8b404a1c35fa9b5ee4db0695cd8e235f63d52 } LG << "Running the forward pass on model to predict the image"; From 48074fe5d39e8ad83f93d8a33a6a0e0bb4e9984f Mon Sep 17 00:00:00 2001 From: Amol Lele <19983848+leleamol@users.noreply.github.com> Date: Mon, 26 Nov 2018 16:57:19 -0800 Subject: [PATCH 3/6] Updated the code to address the review comments. --- cpp-package/example/inference/Makefile | 7 +-- .../example/inference/inception_inference.cpp | 63 +++++++++++++------ .../unit_test_inception_inference.sh | 6 +- 3 files changed, 50 insertions(+), 26 deletions(-) diff --git a/cpp-package/example/inference/Makefile b/cpp-package/example/inference/Makefile index af6ba576e95c..5efe6cfb68e5 100644 --- a/cpp-package/example/inference/Makefile +++ b/cpp-package/example/inference/Makefile @@ -16,9 +16,6 @@ # under the License. -prebuild : - $(shell cp -r ../../../lib ./) - $(shell wget -nc -O mean_224.nd https://github.com/dmlc/web-data/raw/master/mxnet/example/feature_extract/mean_224.nd) CPPEX_SRC = $(wildcard *.cpp) CPPEX_EXE = $(patsubst %.cpp, %, $(CPPEX_SRC)) OPENCV_CFLAGS=`pkg-config --cflags opencv` @@ -30,10 +27,10 @@ CXX=g++ CFLAGS=$(COMMFLAGS) -I../../../3rdparty/tvm/nnvm/include -I../../../3rdparty/dmlc-core/include -I ../../include -I ../../../include -Wall -O3 -msse3 -funroll-loops -Wno-unused-parameter -Wno-unknown-pragmas CPPEX_EXTRA_LDFLAGS := -L../../../lib -lmxnet $(OPENCV_LDFLAGS) -all: prebuild $(CPPEX_EXE) +all: $(CPPEX_EXE) debug: CPPEX_CFLAGS += -DDEBUG -g -debug: prebuild all +debug: all $(CPPEX_EXE):% : %.cpp diff --git a/cpp-package/example/inference/inception_inference.cpp b/cpp-package/example/inference/inception_inference.cpp index 3c0a3e5a7bf5..d4cff7473943 100644 --- a/cpp-package/example/inference/inception_inference.cpp +++ b/cpp-package/example/inference/inception_inference.cpp @@ -27,6 +27,7 @@ * 5. Run the forward pass and predict the input image. */ +#include #include #include #include @@ -63,7 +64,10 @@ class Predictor { NDArray LoadInputImage(const std::string& image_file); void LoadMeanImageData(); void NormalizeInput(const std::string& mean_image_file); - + inline bool FileExists(const std::string& name) { + struct stat buffer; + return (stat(name.c_str(), &buffer) == 0); + } NDArray mean_img; map args_map; map aux_map; @@ -110,18 +114,18 @@ Predictor::Predictor(const std::string& model_json, LoadParameters(model_params); /* - * Load the synset file containing the image labels, if provided. * The data will be used to output the exact label that matches highest output of the model. */ - if (!synset_file.empty()) { - LoadSynset(synset_file); - } + LoadSynset(synset_file); /* * Load the mean image data if specified. */ if (!mean_image_file.empty()) { LoadMeanImageData(); + } else { + LG << "Mean image file for normalizing the input is not provide." + << " It may affect the accuracy of the prediction."; } // Create an executor after binding the model to input parameters. @@ -134,6 +138,10 @@ Predictor::Predictor(const std::string& model_json, * The following function loads the model from json file. */ void Predictor::LoadModel(const std::string& model_json_file) { + if (!FileExists(model_json_file)) { + LG << "Model file " << model_json_file << " does not exist"; + throw std::runtime_error("Model file does not exist"); + } LG << "Loading the model from " << model_json_file << std::endl; net = Symbol::Load(model_json_file); } @@ -143,21 +151,25 @@ void Predictor::LoadModel(const std::string& model_json_file) { * The following function loads the model parameters. */ void Predictor::LoadParameters(const std::string& model_parameters_file) { - LG << "Loading the model parameters from " << model_parameters_file << std::endl; - map paramters; - NDArray::Load(model_parameters_file, 0, ¶mters); - for (const auto &k : paramters) { - if (k.first.substr(0, 4) == "aux:") { - auto name = k.first.substr(4, k.first.size() - 4); - aux_map[name] = k.second.Copy(global_ctx); - } - if (k.first.substr(0, 4) == "arg:") { - auto name = k.first.substr(4, k.first.size() - 4); - args_map[name] = k.second.Copy(global_ctx); - } + if (!FileExists(model_parameters_file)) { + LG << "Parameter file " << model_parameters_file << " does not exist"; + throw std::runtime_error("Model parameters does not exist"); + } + LG << "Loading the model parameters from " << model_parameters_file << std::endl; + map paramters; + NDArray::Load(model_parameters_file, 0, ¶mters); + for (const auto &k : paramters) { + if (k.first.substr(0, 4) == "aux:") { + auto name = k.first.substr(4, k.first.size() - 4); + aux_map[name] = k.second.Copy(global_ctx); + } + if (k.first.substr(0, 4) == "arg:") { + auto name = k.first.substr(4, k.first.size() - 4); + args_map[name] = k.second.Copy(global_ctx); } - /*WaitAll is need when we copy data between GPU and the main memory*/ - NDArray::WaitAll(); + } + /*WaitAll is need when we copy data between GPU and the main memory*/ + NDArray::WaitAll(); } @@ -166,6 +178,10 @@ void Predictor::LoadParameters(const std::string& model_parameters_file) { * This information will be used later to report the label of input image. */ void Predictor::LoadSynset(const string& synset_file) { + if (!FileExists(synset_file)) { + LG << "Synset file " << synset_file << " does not exist"; + throw std::runtime_error("Synset file does not exist"); + } LG << "Loading the synset file."; std::ifstream fi(synset_file.c_str()); if (!fi.is_open()) { @@ -202,6 +218,10 @@ void Predictor::LoadMeanImageData() { * The following function loads the input image. */ NDArray Predictor::LoadInputImage(const std::string& image_file) { + if (!FileExists(image_file)) { + LG << "Image file " << image_file << " does not exist"; + throw std::runtime_error("Image file does not exist"); + } LG << "Loading the image " << image_file << std::endl; vector array; cv::Mat mat = cv::imread(image_file); @@ -256,7 +276,7 @@ void Predictor::PredictImage(const std::string& image_file) { auto array = executor->outputs[0].Copy(global_ctx); NDArray::WaitAll(); - float best_accuracy = 0.0; + float best_accuracy = 0.0f; std::size_t best_idx = 0; // Find out the maximum accuracy and the index associated with that accuracy. @@ -380,11 +400,14 @@ int main(int argc, char** argv) { // Run the forward pass to predict the image. predict.PredictImage(input_image); + } catch (std::runtime_error &error) { + LG << "Execution failed with ERROR: " << error.what(); } catch (...) { /* * If underlying MXNet code has thrown an exception the error message is * accessible through MXGetLastError() function. */ + LG << "Execution failed with following MXNet error"; LG << MXGetLastError(); } return 0; diff --git a/cpp-package/example/inference/unit_test_inception_inference.sh b/cpp-package/example/inference/unit_test_inception_inference.sh index a375970c4ef8..4f40b496bbd3 100755 --- a/cpp-package/example/inference/unit_test_inception_inference.sh +++ b/cpp-package/example/inference/unit_test_inception_inference.sh @@ -27,7 +27,11 @@ make all # Running the example with dog image. -LD_LIBRARY_PATH=../../../lib ./inception_inference --symbol "./model/Inception-BN-symbol.json" --params "./model/Inception-BN-0126.params" --synset "./model/synset.txt" --mean "./model/mean_224.nd" --image "./model/dog.jpg" 2&> inception_inference.log +if [ "$(uname)" == "Darwin" ]; then + DYLD_LIBRARY_PATH=${DYLD_LIBRARY_PATH}:../../../lib ./inception_inference --symbol "./model/Inception-BN-symbol.json" --params "./model/Inception-BN-0126.params" --synset "./model/synset.txt" --mean "./model/mean_224.nd" --image "./model/dog.jpg" 2&> inception_inference.log +else + LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:../../../lib ./inception_inference --symbol "./model/Inception-BN-symbol.json" --params "./model/Inception-BN-0126.params" --synset "./model/synset.txt" --mean "./model/mean_224.nd" --image "./model/dog.jpg" 2&> inception_inference.log +fi result=`grep -c "pug-dog" inception_inference.log` if [ $result == 1 ]; then From 09bc9b36ccb9a3f64cb19524a6a88c201756b973 Mon Sep 17 00:00:00 2001 From: Amol Lele <19983848+leleamol@users.noreply.github.com> Date: Tue, 27 Nov 2018 11:43:46 -0800 Subject: [PATCH 4/6] Added the README file for the folder. --- cpp-package/example/README.md | 7 ++-- cpp-package/example/inference/README.md | 35 +++++++++++++++++++ .../example/inference/inception_inference.cpp | 6 ++-- 3 files changed, 42 insertions(+), 6 deletions(-) create mode 100644 cpp-package/example/inference/README.md diff --git a/cpp-package/example/README.md b/cpp-package/example/README.md index 64f604469a72..e465d93a9807 100644 --- a/cpp-package/example/README.md +++ b/cpp-package/example/README.md @@ -2,7 +2,8 @@ ## Building C++ examples -The examples are built while building the MXNet library and cpp-package from source . However, they can be built manually as follows +The examples in this folder demonstrate the **training** workflow. The **inference workflow** related examples can be found in [inference]() folder. +The examples in this folder are built while building the MXNet library and cpp-package from source . However, they can be built manually as follows From cpp-package/examples directory @@ -18,7 +19,7 @@ The examples that are built to be run on GPU may not work on the non-GPU machine The makefile will also download the necessary data files and store in a data folder. (The download will take couple of minutes, but will be done only once on a fresh installation.) -## Examples +## Examples demonstrating training workflow This directory contains following examples. In order to run the examples, ensure that the path to the MXNet shared library is added to the OS specific environment variable viz. **LD\_LIBRARY\_PATH** for Linux, Mac and Ubuntu OS and **PATH** for Windows OS. @@ -97,7 +98,7 @@ build/lenet_with_mxdataiter 10 In addition, there is `run_lenet_with_mxdataiter.sh` that downloads the mnist data and run `lenet_with_mxdataiter` example. -###[inception_bn.cpp]() +### [inception_bn.cpp]() The code implements an Inception network using the C++ API with batch normalization. The example uses MNIST data to train the network. The model trains for 100 epochs. The example can be run by executing the following command: diff --git a/cpp-package/example/inference/README.md b/cpp-package/example/inference/README.md new file mode 100644 index 000000000000..5d16c7c07544 --- /dev/null +++ b/cpp-package/example/inference/README.md @@ -0,0 +1,35 @@ +# MXNet C++ Package Inference Workflow Examples + +## Building C++ Inference examples + +The examples in this folder demonstrate the **inference** workflow. +To build examples use following commands: + +- Release: **make all** +- Debug: **make debug all** + + +## Examples demonstrating inference workflow + +This directory contains following examples. In order to run the examples, ensure that the path to the MXNet shared library is added to the OS specific environment variable viz. **LD\_LIBRARY\_PATH** for Linux, Mac and Ubuntu OS and **PATH** for Windows OS. + +### [inception_inference.cpp]() + +This example demonstrates image classification workflow with pre-trained models using MXNet C++ API. The command line parameters the example can accept are as shown below: + + ``` + ./inception_inference --help + Usage: + inception_inference --symbol --params --image ) downloads the pre-trained **Inception** model and a test image. The users can invoke this script as follows: + + ``` + ./unit_test_inception_inference.sh + ``` diff --git a/cpp-package/example/inference/inception_inference.cpp b/cpp-package/example/inference/inception_inference.cpp index d4cff7473943..8ac4e7d06211 100644 --- a/cpp-package/example/inference/inception_inference.cpp +++ b/cpp-package/example/inference/inception_inference.cpp @@ -325,8 +325,8 @@ void printUsage() { std::cout << "inception_inference --symbol " << "--params " << "--image Date: Thu, 29 Nov 2018 10:05:07 -0800 Subject: [PATCH 5/6] Addressed the review comments --- .../example/inference/inception_inference.cpp | 58 +++++++++---------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/cpp-package/example/inference/inception_inference.cpp b/cpp-package/example/inference/inception_inference.cpp index 8ac4e7d06211..88ebf42862d4 100644 --- a/cpp-package/example/inference/inception_inference.cpp +++ b/cpp-package/example/inference/inception_inference.cpp @@ -35,7 +35,7 @@ #include #include "mxnet-cpp/MxNetCpp.h" #include -using namespace std; + using namespace mxnet::cpp; @@ -69,15 +69,15 @@ class Predictor { return (stat(name.c_str(), &buffer) == 0); } NDArray mean_img; - map args_map; - map aux_map; + std::map args_map; + std::map aux_map; std::vector output_labels; Symbol net; Executor *executor; Shape input_shape; NDArray mean_image_data; Context global_ctx = Context::cpu(); - string mean_image_file; + std::string mean_image_file; }; @@ -130,8 +130,8 @@ Predictor::Predictor(const std::string& model_json, // Create an executor after binding the model to input parameters. args_map["data"] = NDArray(input_shape, global_ctx, false); - executor = net.SimpleBind(global_ctx, args_map, map(), - map(), aux_map); + executor = net.SimpleBind(global_ctx, args_map, std::map(), + std::map(), aux_map); } /* @@ -156,7 +156,7 @@ void Predictor::LoadParameters(const std::string& model_parameters_file) { throw std::runtime_error("Model parameters does not exist"); } LG << "Loading the model parameters from " << model_parameters_file << std::endl; - map paramters; + std::map paramters; NDArray::Load(model_parameters_file, 0, ¶mters); for (const auto &k : paramters) { if (k.first.substr(0, 4) == "aux:") { @@ -177,7 +177,7 @@ void Predictor::LoadParameters(const std::string& model_parameters_file) { * The following function loads the synset file. * This information will be used later to report the label of input image. */ -void Predictor::LoadSynset(const string& synset_file) { +void Predictor::LoadSynset(const std::string& synset_file) { if (!FileExists(synset_file)) { LG << "Synset file " << synset_file << " does not exist"; throw std::runtime_error("Synset file does not exist"); @@ -223,7 +223,7 @@ NDArray Predictor::LoadInputImage(const std::string& image_file) { throw std::runtime_error("Image file does not exist"); } LG << "Loading the image " << image_file << std::endl; - vector array; + std::vector array; cv::Mat mat = cv::imread(image_file); /*resize pictures to (224, 224) according to the pretrained model*/ int height = input_shape[2]; @@ -310,11 +310,11 @@ Predictor::~Predictor() { */ std::vector getShapeDimensions(const std::string& hidden_units_string) { std::vector dimensions; - char *pNext; - int num_unit = strtol(hidden_units_string.c_str(), &pNext, 10); + char *p_next; + int num_unit = strtol(hidden_units_string.c_str(), &p_next, 10); dimensions.push_back(num_unit); - while (*pNext) { - num_unit = strtol(pNext, &pNext, 10); + while (*p_next) { + num_unit = strtol(p_next, &p_next, 10); dimensions.push_back(num_unit); } return dimensions; @@ -324,20 +324,20 @@ void printUsage() { std::cout << "Usage:" << std::endl; std::cout << "inception_inference --symbol " << "--params " - << "--image " + << "--synset " + << "[--input_shape ] " + << "[--mean ] " + << "[--gpu ]" << std::endl; } int main(int argc, char** argv) { - string model_file_json; - string model_file_params; - string synset_file = ""; - string mean_image = ""; - string input_image = ""; + std::string model_file_json; + std::string model_file_params; + std::string synset_file = ""; + std::string mean_image = ""; + std::string input_image = ""; bool gpu_context_type = false; std::string input_shape = "3 224 224"; @@ -345,22 +345,22 @@ int main(int argc, char** argv) { while (index < argc) { if (strcmp("--symbol", argv[index]) == 0) { index++; - model_file_json = argv[index]; + model_file_json = (index < argc ? argv[index]:""); } else if (strcmp("--params", argv[index]) == 0) { index++; - model_file_params = argv[index]; + model_file_params = (index < argc ? argv[index]:""); } else if (strcmp("--synset", argv[index]) == 0) { index++; - synset_file = argv[index]; + synset_file = (index < argc ? argv[index]:""); } else if (strcmp("--mean", argv[index]) == 0) { index++; - mean_image = argv[index]; + mean_image = (index < argc ? argv[index]:""); } else if (strcmp("--image", argv[index]) == 0) { index++; - input_image = argv[index]; + input_image = (index < argc ? argv[index]:""); } else if (strcmp("--input_shape", argv[index]) == 0) { index++; - input_shape = argv[index]; + input_shape = (index < argc ? argv[index]:input_shape); } else if (strcmp("--gpu", argv[index]) == 0) { gpu_context_type = true; } else if (strcmp("--help", argv[index]) == 0) { From 0ab6c54a91f7aa712562a2fbc2441f845763e081 Mon Sep 17 00:00:00 2001 From: Amol Lele <19983848+leleamol@users.noreply.github.com> Date: Wed, 5 Dec 2018 15:13:30 -0800 Subject: [PATCH 6/6] Addressed the review comments to use argmax and default mean values. --- cpp-package/example/inference/README.md | 28 +++-- .../example/inference/inception_inference.cpp | 104 ++++++++++++------ 2 files changed, 85 insertions(+), 47 deletions(-) diff --git a/cpp-package/example/inference/README.md b/cpp-package/example/inference/README.md index 5d16c7c07544..79831b40b6bd 100644 --- a/cpp-package/example/inference/README.md +++ b/cpp-package/example/inference/README.md @@ -17,19 +17,25 @@ This directory contains following examples. In order to run the examples, ensure This example demonstrates image classification workflow with pre-trained models using MXNet C++ API. The command line parameters the example can accept are as shown below: - ``` - ./inception_inference --help - Usage: - inception_inference --symbol --params --image + --params + --image ) downloads the pre-trained **Inception** model and a test image. The users can invoke this script as follows: - ``` - ./unit_test_inception_inference.sh - ``` +``` +./unit_test_inception_inference.sh +``` diff --git a/cpp-package/example/inference/inception_inference.cpp b/cpp-package/example/inference/inception_inference.cpp index 88ebf42862d4..7005e745b2f4 100644 --- a/cpp-package/example/inference/inception_inference.cpp +++ b/cpp-package/example/inference/inception_inference.cpp @@ -20,8 +20,8 @@ /* * This example demonstrates image classification workflow with pre-trained models using MXNet C++ API. * The example performs following tasks. - * 1. Load the pre-trained model, - * 2. Load the parameters of pre-trained model, + * 1. Load the pre-trained model. + * 2. Load the parameters of pre-trained model. * 3. Load the image to be classified in to NDArray. * 4. Normalize the image using the mean of images that were used for training. * 5. Run the forward pass and predict the input image. @@ -38,7 +38,9 @@ using namespace mxnet::cpp; - +static mx_float DEFAULT_MEAN_R = 123.675; +static mx_float DEFAULT_MEAN_G = 116.28; +static mx_float DEFAULT_MEAN_B = 103.53; /* * class Predictor * @@ -48,8 +50,8 @@ using namespace mxnet::cpp; class Predictor { public: Predictor() {} - Predictor(const std::string& model_json, - const std::string& model_params, + Predictor(const std::string& model_json_file, + const std::string& model_params_file, const Shape& input_shape, bool gpu_context_type = false, const std::string& synset_file = "", @@ -63,6 +65,7 @@ class Predictor { void LoadSynset(const std::string& synset_file); NDArray LoadInputImage(const std::string& image_file); void LoadMeanImageData(); + void LoadDefaultMeanImageData(); void NormalizeInput(const std::string& mean_image_file); inline bool FileExists(const std::string& name) { struct stat buffer; @@ -76,6 +79,7 @@ class Predictor { Executor *executor; Shape input_shape; NDArray mean_image_data; + NDArray std_dev_image_data; Context global_ctx = Context::cpu(); std::string mean_image_file; }; @@ -83,8 +87,8 @@ class Predictor { /* * The constructor takes following parameters as input: - * 1. model_json: The model in json formatted file. - * 2. model_params: File containing model parameters + * 1. model_json_file: The model in json formatted file. + * 2. model_params_file: File containing model parameters * 3. synset_file: File containing the list of image labels * 4. input_shape: Shape of input data to the model. Since this class will be running one inference at a time, * the input shape is required to be in format Shape(1, number_of_channels, height, width) @@ -96,8 +100,8 @@ class Predictor { * * The SimpleBind is expected to be invoked only once. */ -Predictor::Predictor(const std::string& model_json, - const std::string& model_params, +Predictor::Predictor(const std::string& model_json_file, + const std::string& model_params_file, const Shape& input_shape, bool gpu_context_type, const std::string& synset_file, @@ -108,10 +112,10 @@ Predictor::Predictor(const std::string& model_json, global_ctx = Context::gpu(); } // Load the model - LoadModel(model_json); + LoadModel(model_json_file); // Load the model parameters. - LoadParameters(model_params); + LoadParameters(model_params_file); /* * The data will be used to output the exact label that matches highest output of the model. @@ -125,7 +129,8 @@ Predictor::Predictor(const std::string& model_json, LoadMeanImageData(); } else { LG << "Mean image file for normalizing the input is not provide." - << " It may affect the accuracy of the prediction."; + << " We will use the default mean values for R,G and B channels."; + LoadDefaultMeanImageData(); } // Create an executor after binding the model to input parameters. @@ -156,9 +161,9 @@ void Predictor::LoadParameters(const std::string& model_parameters_file) { throw std::runtime_error("Model parameters does not exist"); } LG << "Loading the model parameters from " << model_parameters_file << std::endl; - std::map paramters; - NDArray::Load(model_parameters_file, 0, ¶mters); - for (const auto &k : paramters) { + std::map parameters; + NDArray::Load(model_parameters_file, 0, ¶meters); + for (const auto &k : parameters) { if (k.first.substr(0, 4) == "aux:") { auto name = k.first.substr(4, k.first.size() - 4); aux_map[name] = k.second.Copy(global_ctx); @@ -186,7 +191,7 @@ void Predictor::LoadSynset(const std::string& synset_file) { std::ifstream fi(synset_file.c_str()); if (!fi.is_open()) { std::cerr << "Error opening synset file " << synset_file << std::endl; - assert(false); + throw std::runtime_error("Error in opening the synset file."); } std::string synset, lemma; while (fi >> synset) { @@ -201,7 +206,7 @@ void Predictor::LoadSynset(const std::string& synset_file) { * The following function loads the mean data from mean image file. * This data will be used for normalizing the image before running the forward * pass. - * + * The output data has the same shape as that of the input image data. */ void Predictor::LoadMeanImageData() { LG << "Load the mean image data that will be used to normalize " @@ -215,7 +220,36 @@ void Predictor::LoadMeanImageData() { /* - * The following function loads the input image. + * The following function loads the default mean values for + * R, G and B channels into NDArray that has the same shape as that of + * input image. + */ +void Predictor::LoadDefaultMeanImageData() { + LG << "Loading the default mean image data"; + std::vector array; + /*resize pictures to (224, 224) according to the pretrained model*/ + int height = input_shape[2]; + int width = input_shape[3]; + int channels = input_shape[1]; + std::vector default_means; + default_means.push_back(DEFAULT_MEAN_R); + default_means.push_back(DEFAULT_MEAN_G); + default_means.push_back(DEFAULT_MEAN_B); + for (int c = 0; c < channels; ++c) { + for (int i = 0; i < height; ++i) { + for (int j = 0; j < width; ++j) { + array.push_back(default_means[c]); + } + } + } + mean_image_data = NDArray(input_shape, global_ctx, false); + mean_image_data.SyncCopyFromCPU(array.data(), input_shape.Size()); + NDArray::WaitAll(); +} + + +/* + * The following function loads the input image into NDArray. */ NDArray Predictor::LoadInputImage(const std::string& image_file) { if (!FileExists(image_file)) { @@ -254,9 +288,7 @@ void Predictor::PredictImage(const std::string& image_file) { NDArray image_data = LoadInputImage(image_file); // Normalize the image - if (!mean_image_file.empty()) { - image_data.Slice(0, 1) -= mean_image_data; - } + image_data.Slice(0, 1) -= mean_image_data; LG << "Running the forward pass on model to predict the image"; /* @@ -276,23 +308,22 @@ void Predictor::PredictImage(const std::string& image_file) { auto array = executor->outputs[0].Copy(global_ctx); NDArray::WaitAll(); - float best_accuracy = 0.0f; - std::size_t best_idx = 0; + /* + * Find out the maximum accuracy and the index associated with that accuracy. + * This is done by using the argmax operator on NDArray. + */ + auto predicted = array.ArgmaxChannel(); + NDArray::WaitAll(); - // Find out the maximum accuracy and the index associated with that accuracy. - for (std::size_t i = 0; i < array.Size(); ++i) { - if (array.At(0, i) > best_accuracy) { - best_accuracy = array.At(0, i); - best_idx = i; - } - } + int best_idx = predicted.At(0, 0); + float best_accuracy = array.At(0, best_idx); if (output_labels.empty()) { LG << "The model predicts the highest accuracy of " << best_accuracy << " at index " << best_idx; } else { LG << "The model predicts the input image to be a [" << output_labels[best_idx] - << " ] with Accuracy = " << array.At(0, best_idx) << std::endl; + << " ] with Accuracy = " << best_accuracy << std::endl; } } @@ -322,12 +353,13 @@ std::vector getShapeDimensions(const std::string& hidden_units_string) void printUsage() { std::cout << "Usage:" << std::endl; - std::cout << "inception_inference --symbol " - << "--params " - << "--image " - << "--synset " - << "[--input_shape ] " + std::cout << "inception_inference --symbol " << std::endl + << "--params " << std::endl + << "--image " << std::endl + << "--synset " << std::endl + << "[--input_shape ] " << std::endl << "[--mean ] " + << std::endl << "[--gpu ]" << std::endl; }