arm_compute v18.05
[platform/upstream/armcl.git] / examples / graph_resnet50.cpp
1 /*
2  * Copyright (c) 2017-2018 ARM Limited.
3  *
4  * SPDX-License-Identifier: MIT
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining a copy
7  * of this software and associated documentation files (the "Software"), to
8  * deal in the Software without restriction, including without limitation the
9  * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10  * sell copies of the Software, and to permit persons to whom the Software is
11  * furnished to do so, subject to the following conditions:
12  *
13  * The above copyright notice and this permission notice shall be included in all
14  * copies or substantial portions of the Software.
15  *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22  * SOFTWARE.
23  */
24 #include "arm_compute/graph.h"
25 #include "support/ToolchainSupport.h"
26 #include "utils/GraphUtils.h"
27 #include "utils/Utils.h"
28
29 #include <cstdlib>
30
31 using namespace arm_compute::utils;
32 using namespace arm_compute::graph::frontend;
33 using namespace arm_compute::graph_utils;
34
35 /** Example demonstrating how to implement ResNet50 network using the Compute Library's graph API
36  *
37  * @param[in] argc Number of arguments
38  * @param[in] argv Arguments ( [optional] Target (0 = NEON, 1 = OpenCL, 2 = OpenCL with Tuner), [optional] Path to the weights folder, [optional] image, [optional] labels, [optional] Fast math for convolution layer (0 = DISABLED, 1 = ENABLED) )
39  */
40 class GraphResNet50Example : public Example
41 {
42 public:
43     void do_setup(int argc, char **argv) override
44     {
45         std::string data_path; /* Path to the trainable data */
46         std::string image;     /* Image data */
47         std::string label;     /* Label data */
48
49         // Create a preprocessor object
50         const std::array<float, 3> mean_rgb{ { 122.68f, 116.67f, 104.01f } };
51         std::unique_ptr<IPreprocessor> preprocessor = arm_compute::support::cpp14::make_unique<CaffePreproccessor>(mean_rgb,
52                                                                                                                    false /* Do not convert to BGR */);
53
54         // Set target. 0 (NEON), 1 (OpenCL), 2 (OpenCL with Tuner). By default it is NEON
55         const int    target         = argc > 1 ? std::strtol(argv[1], nullptr, 10) : 0;
56         Target       target_hint    = set_target_hint(target);
57         FastMathHint fast_math_hint = FastMathHint::DISABLED;
58
59         // Parse arguments
60         if(argc < 2)
61         {
62             // Print help
63             std::cout << "Usage: " << argv[0] << " [target] [path_to_data] [image] [labels] [fast_math_hint]\n\n";
64             std::cout << "No data folder provided: using random values\n\n";
65         }
66         else if(argc == 2)
67         {
68             std::cout << "Usage: " << argv[0] << " " << argv[1] << " [path_to_data] [image] [labels] [fast_math_hint]\n\n";
69             std::cout << "No data folder provided: using random values\n\n";
70         }
71         else if(argc == 3)
72         {
73             data_path = argv[2];
74             std::cout << "Usage: " << argv[0] << " " << argv[1] << " " << argv[2] << " [image] [labels] [fast_math_hint]\n\n";
75             std::cout << "No image provided: using random values\n\n";
76         }
77         else if(argc == 4)
78         {
79             data_path = argv[2];
80             image     = argv[3];
81             std::cout << "Usage: " << argv[0] << " " << argv[1] << " " << argv[2] << " " << argv[3] << " [labels] [fast_math_hint]\n\n";
82             std::cout << "No text file with labels provided: skipping output accessor\n\n";
83         }
84         else if(argc == 5)
85         {
86             data_path = argv[2];
87             image     = argv[3];
88             label     = argv[4];
89             std::cout << "Usage: " << argv[0] << " " << argv[1] << " " << argv[2] << " " << argv[3] << " " << argv[4] << " [fast_math_hint]\n\n";
90             std::cout << "No fast math info provided: disabling fast math\n\n";
91         }
92         else
93         {
94             data_path      = argv[2];
95             image          = argv[3];
96             label          = argv[4];
97             fast_math_hint = (std::strtol(argv[5], nullptr, 1) == 0) ? FastMathHint::DISABLED : FastMathHint::ENABLED;
98         }
99
100         graph << target_hint
101               << fast_math_hint
102               << InputLayer(TensorDescriptor(TensorShape(224U, 224U, 3U, 1U), DataType::F32),
103                             get_input_accessor(image, std::move(preprocessor), false /* Do not convert to BGR */))
104               << ConvolutionLayer(
105                   7U, 7U, 64U,
106                   get_weights_accessor(data_path, "/cnn_data/resnet50_model/conv1_weights.npy"),
107                   std::unique_ptr<arm_compute::graph::ITensorAccessor>(nullptr),
108                   PadStrideInfo(2, 2, 3, 3))
109               .set_name("conv1/convolution")
110               << BatchNormalizationLayer(
111                   get_weights_accessor(data_path, "/cnn_data/resnet50_model/conv1_BatchNorm_moving_mean.npy"),
112                   get_weights_accessor(data_path, "/cnn_data/resnet50_model/conv1_BatchNorm_moving_variance.npy"),
113                   get_weights_accessor(data_path, "/cnn_data/resnet50_model/conv1_BatchNorm_gamma.npy"),
114                   get_weights_accessor(data_path, "/cnn_data/resnet50_model/conv1_BatchNorm_beta.npy"),
115                   0.0000100099996416f)
116               .set_name("conv1/BatchNorm")
117               << ActivationLayer(ActivationLayerInfo(ActivationLayerInfo::ActivationFunction::RELU)).set_name("conv1/Relu")
118               << PoolingLayer(PoolingLayerInfo(PoolingType::MAX, 3, PadStrideInfo(2, 2, 0, 1, 0, 1, DimensionRoundingType::FLOOR))).set_name("pool1/MaxPool");
119
120         add_residual_block(data_path, "block1", 64, 3, 2);
121         add_residual_block(data_path, "block2", 128, 4, 2);
122         add_residual_block(data_path, "block3", 256, 6, 2);
123         add_residual_block(data_path, "block4", 512, 3, 1);
124
125         graph << PoolingLayer(PoolingLayerInfo(PoolingType::AVG)).set_name("pool5")
126               << ConvolutionLayer(
127                   1U, 1U, 1000U,
128                   get_weights_accessor(data_path, "/cnn_data/resnet50_model/logits_weights.npy"),
129                   get_weights_accessor(data_path, "/cnn_data/resnet50_model/logits_biases.npy"),
130                   PadStrideInfo(1, 1, 0, 0))
131               .set_name("logits/convolution")
132               << FlattenLayer().set_name("predictions/Reshape")
133               << SoftmaxLayer().set_name("predictions/Softmax")
134               << OutputLayer(get_output_accessor(label, 5));
135
136         // Finalize graph
137         GraphConfig config;
138         config.use_tuner = (target == 2);
139         graph.finalize(target_hint, config);
140     }
141
142     void do_run() override
143     {
144         // Run graph
145         graph.run();
146     }
147
148 private:
149     Stream graph{ 0, "ResNet50" };
150
151     void add_residual_block(const std::string &data_path, const std::string &name, unsigned int base_depth, unsigned int num_units, unsigned int stride)
152     {
153         for(unsigned int i = 0; i < num_units; ++i)
154         {
155             std::stringstream unit_path_ss;
156             unit_path_ss << "/cnn_data/resnet50_model/" << name << "_unit_" << (i + 1) << "_bottleneck_v1_";
157             std::stringstream unit_name_ss;
158             unit_name_ss << name << "/unit" << (i + 1) << "/bottleneck_v1/";
159
160             std::string unit_path = unit_path_ss.str();
161             std::string unit_name = unit_name_ss.str();
162
163             unsigned int middle_stride = 1;
164
165             if(i == (num_units - 1))
166             {
167                 middle_stride = stride;
168             }
169
170             SubStream right(graph);
171             right << ConvolutionLayer(
172                       1U, 1U, base_depth,
173                       get_weights_accessor(data_path, unit_path + "conv1_weights.npy"),
174                       std::unique_ptr<arm_compute::graph::ITensorAccessor>(nullptr),
175                       PadStrideInfo(1, 1, 0, 0))
176                   .set_name(unit_name + "conv1/convolution")
177                   << BatchNormalizationLayer(
178                       get_weights_accessor(data_path, unit_path + "conv1_BatchNorm_moving_mean.npy"),
179                       get_weights_accessor(data_path, unit_path + "conv1_BatchNorm_moving_variance.npy"),
180                       get_weights_accessor(data_path, unit_path + "conv1_BatchNorm_gamma.npy"),
181                       get_weights_accessor(data_path, unit_path + "conv1_BatchNorm_beta.npy"),
182                       0.0000100099996416f)
183                   .set_name(unit_name + "conv1/BatchNorm")
184                   << ActivationLayer(ActivationLayerInfo(ActivationLayerInfo::ActivationFunction::RELU)).set_name(unit_name + "conv1/Relu")
185
186                   << ConvolutionLayer(
187                       3U, 3U, base_depth,
188                       get_weights_accessor(data_path, unit_path + "conv2_weights.npy"),
189                       std::unique_ptr<arm_compute::graph::ITensorAccessor>(nullptr),
190                       PadStrideInfo(middle_stride, middle_stride, 1, 1))
191                   .set_name(unit_name + "conv2/convolution")
192                   << BatchNormalizationLayer(
193                       get_weights_accessor(data_path, unit_path + "conv2_BatchNorm_moving_mean.npy"),
194                       get_weights_accessor(data_path, unit_path + "conv2_BatchNorm_moving_variance.npy"),
195                       get_weights_accessor(data_path, unit_path + "conv2_BatchNorm_gamma.npy"),
196                       get_weights_accessor(data_path, unit_path + "conv2_BatchNorm_beta.npy"),
197                       0.0000100099996416f)
198                   .set_name(unit_name + "conv2/BatchNorm")
199                   << ActivationLayer(ActivationLayerInfo(ActivationLayerInfo::ActivationFunction::RELU)).set_name(unit_name + "conv1/Relu")
200
201                   << ConvolutionLayer(
202                       1U, 1U, base_depth * 4,
203                       get_weights_accessor(data_path, unit_path + "conv3_weights.npy"),
204                       std::unique_ptr<arm_compute::graph::ITensorAccessor>(nullptr),
205                       PadStrideInfo(1, 1, 0, 0))
206                   .set_name(unit_name + "conv3/convolution")
207                   << BatchNormalizationLayer(
208                       get_weights_accessor(data_path, unit_path + "conv3_BatchNorm_moving_mean.npy"),
209                       get_weights_accessor(data_path, unit_path + "conv3_BatchNorm_moving_variance.npy"),
210                       get_weights_accessor(data_path, unit_path + "conv3_BatchNorm_gamma.npy"),
211                       get_weights_accessor(data_path, unit_path + "conv3_BatchNorm_beta.npy"),
212                       0.0000100099996416f)
213                   .set_name(unit_name + "conv2/BatchNorm");
214
215             if(i == 0)
216             {
217                 SubStream left(graph);
218                 left << ConvolutionLayer(
219                          1U, 1U, base_depth * 4,
220                          get_weights_accessor(data_path, unit_path + "shortcut_weights.npy"),
221                          std::unique_ptr<arm_compute::graph::ITensorAccessor>(nullptr),
222                          PadStrideInfo(1, 1, 0, 0))
223                      .set_name(unit_name + "shortcut/convolution")
224                      << BatchNormalizationLayer(
225                          get_weights_accessor(data_path, unit_path + "shortcut_BatchNorm_moving_mean.npy"),
226                          get_weights_accessor(data_path, unit_path + "shortcut_BatchNorm_moving_variance.npy"),
227                          get_weights_accessor(data_path, unit_path + "shortcut_BatchNorm_gamma.npy"),
228                          get_weights_accessor(data_path, unit_path + "shortcut_BatchNorm_beta.npy"),
229                          0.0000100099996416f)
230                      .set_name(unit_name + "shortcut/BatchNorm");
231
232                 graph << BranchLayer(BranchMergeMethod::ADD, std::move(left), std::move(right)).set_name(unit_name + "add");
233             }
234             else if(middle_stride > 1)
235             {
236                 SubStream left(graph);
237                 left << PoolingLayer(PoolingLayerInfo(PoolingType::MAX, 1, PadStrideInfo(middle_stride, middle_stride, 0, 0), true)).set_name(unit_name + "shortcut/MaxPool");
238
239                 graph << BranchLayer(BranchMergeMethod::ADD, std::move(left), std::move(right)).set_name(unit_name + "add");
240             }
241             else
242             {
243                 SubStream left(graph);
244                 graph << BranchLayer(BranchMergeMethod::ADD, std::move(left), std::move(right)).set_name(unit_name + "add");
245             }
246
247             graph << ActivationLayer(ActivationLayerInfo(ActivationLayerInfo::ActivationFunction::RELU)).set_name(unit_name + "Relu");
248         }
249     }
250 };
251
252 /** Main program for ResNet50
253  *
254  * @param[in] argc Number of arguments
255  * @param[in] argv Arguments ( [optional] Target (0 = NEON, 1 = OpenCL, 2 = OpenCL with Tuner), [optional] Path to the weights folder, [optional] image, [optional] labels, [optional] Fast math for convolution layer (0 = DISABLED, 1 = ENABLED) )
256  */
257 int main(int argc, char **argv)
258 {
259     return arm_compute::utils::run_example<GraphResNet50Example>(argc, argv);
260 }