arm_compute v17.12
[platform/upstream/armcl.git] / utils / GraphUtils.cpp
1 /*
2  * Copyright (c) 2017 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
25 #include "utils/GraphUtils.h"
26 #include "utils/Utils.h"
27
28 #ifdef ARM_COMPUTE_CL
29 #include "arm_compute/core/CL/OpenCL.h"
30 #include "arm_compute/runtime/CL/CLTensor.h"
31 #endif /* ARM_COMPUTE_CL */
32
33 #include "arm_compute/core/Error.h"
34 #include "arm_compute/core/PixelValue.h"
35
36 #include <algorithm>
37 #include <iomanip>
38 #include <ostream>
39 #include <random>
40
41 using namespace arm_compute::graph_utils;
42
43 PPMWriter::PPMWriter(std::string name, unsigned int maximum)
44     : _name(std::move(name)), _iterator(0), _maximum(maximum)
45 {
46 }
47
48 bool PPMWriter::access_tensor(ITensor &tensor)
49 {
50     std::stringstream ss;
51     ss << _name << _iterator << ".ppm";
52
53     arm_compute::utils::save_to_ppm(tensor, ss.str());
54
55     _iterator++;
56     if(_maximum == 0)
57     {
58         return true;
59     }
60     return _iterator < _maximum;
61 }
62
63 DummyAccessor::DummyAccessor(unsigned int maximum)
64     : _iterator(0), _maximum(maximum)
65 {
66 }
67
68 bool DummyAccessor::access_tensor(ITensor &tensor)
69 {
70     ARM_COMPUTE_UNUSED(tensor);
71     bool ret = _maximum == 0 || _iterator < _maximum;
72     if(_iterator == _maximum)
73     {
74         _iterator = 0;
75     }
76     else
77     {
78         _iterator++;
79     }
80     return ret;
81 }
82
83 PPMAccessor::PPMAccessor(const std::string &ppm_path, bool bgr, float mean_r, float mean_g, float mean_b)
84     : _ppm_path(ppm_path), _bgr(bgr), _mean_r(mean_r), _mean_g(mean_g), _mean_b(mean_b)
85 {
86 }
87
88 bool PPMAccessor::access_tensor(ITensor &tensor)
89 {
90     utils::PPMLoader ppm;
91     const float      mean[3] =
92     {
93         _bgr ? _mean_b : _mean_r,
94         _mean_g,
95         _bgr ? _mean_r : _mean_b
96     };
97
98     // Open PPM file
99     ppm.open(_ppm_path);
100
101     ARM_COMPUTE_ERROR_ON_MSG(ppm.width() != tensor.info()->dimension(0) || ppm.height() != tensor.info()->dimension(1),
102                              "Failed to load image file: dimensions [%d,%d] not correct, expected [%d,%d].", ppm.width(), ppm.height(), tensor.info()->dimension(0), tensor.info()->dimension(1));
103
104     // Fill the tensor with the PPM content (BGR)
105     ppm.fill_planar_tensor(tensor, _bgr);
106
107     // Subtract the mean value from each channel
108     Window window;
109     window.use_tensor_dimensions(tensor.info()->tensor_shape());
110
111     execute_window_loop(window, [&](const Coordinates & id)
112     {
113         const float value                                     = *reinterpret_cast<float *>(tensor.ptr_to_element(id)) - mean[id.z()];
114         *reinterpret_cast<float *>(tensor.ptr_to_element(id)) = value;
115     });
116
117     return true;
118 }
119
120 TopNPredictionsAccessor::TopNPredictionsAccessor(const std::string &labels_path, size_t top_n, std::ostream &output_stream)
121     : _labels(), _output_stream(output_stream), _top_n(top_n)
122 {
123     _labels.clear();
124
125     std::ifstream ifs;
126
127     try
128     {
129         ifs.exceptions(std::ifstream::badbit);
130         ifs.open(labels_path, std::ios::in | std::ios::binary);
131
132         for(std::string line; !std::getline(ifs, line).fail();)
133         {
134             _labels.emplace_back(line);
135         }
136     }
137     catch(const std::ifstream::failure &e)
138     {
139         ARM_COMPUTE_ERROR("Accessing %s: %s", labels_path.c_str(), e.what());
140     }
141 }
142
143 bool TopNPredictionsAccessor::access_tensor(ITensor &tensor)
144 {
145     ARM_COMPUTE_ERROR_ON_DATA_TYPE_CHANNEL_NOT_IN(&tensor, 1, DataType::F32);
146     ARM_COMPUTE_ERROR_ON(_labels.size() != tensor.info()->dimension(0));
147
148     // Get the predicted class
149     std::vector<float>  classes_prob;
150     std::vector<size_t> index;
151
152     const auto   output_net  = reinterpret_cast<float *>(tensor.buffer() + tensor.info()->offset_first_element_in_bytes());
153     const size_t num_classes = tensor.info()->dimension(0);
154
155     classes_prob.resize(num_classes);
156     index.resize(num_classes);
157
158     std::copy(output_net, output_net + num_classes, classes_prob.begin());
159
160     // Sort results
161     std::iota(std::begin(index), std::end(index), static_cast<size_t>(0));
162     std::sort(std::begin(index), std::end(index),
163               [&](size_t a, size_t b)
164     {
165         return classes_prob[a] > classes_prob[b];
166     });
167
168     _output_stream << "---------- Top " << _top_n << " predictions ----------" << std::endl
169                    << std::endl;
170     for(size_t i = 0; i < _top_n; ++i)
171     {
172         _output_stream << std::fixed << std::setprecision(4)
173                        << classes_prob[index.at(i)]
174                        << " - [id = " << index.at(i) << "]"
175                        << ", " << _labels[index.at(i)] << std::endl;
176     }
177
178     return false;
179 }
180
181 RandomAccessor::RandomAccessor(PixelValue lower, PixelValue upper, std::random_device::result_type seed)
182     : _lower(lower), _upper(upper), _seed(seed)
183 {
184 }
185
186 template <typename T, typename D>
187 void RandomAccessor::fill(ITensor &tensor, D &&distribution)
188 {
189     std::mt19937 gen(_seed);
190
191     if(tensor.info()->padding().empty())
192     {
193         for(size_t offset = 0; offset < tensor.info()->total_size(); offset += tensor.info()->element_size())
194         {
195             const T value                                    = distribution(gen);
196             *reinterpret_cast<T *>(tensor.buffer() + offset) = value;
197         }
198     }
199     else
200     {
201         // If tensor has padding accessing tensor elements through execution window.
202         Window window;
203         window.use_tensor_dimensions(tensor.info()->tensor_shape());
204
205         execute_window_loop(window, [&](const Coordinates & id)
206         {
207             const T value                                     = distribution(gen);
208             *reinterpret_cast<T *>(tensor.ptr_to_element(id)) = value;
209         });
210     }
211 }
212
213 bool RandomAccessor::access_tensor(ITensor &tensor)
214 {
215     switch(tensor.info()->data_type())
216     {
217         case DataType::U8:
218         {
219             std::uniform_int_distribution<uint8_t> distribution_u8(_lower.get<uint8_t>(), _upper.get<uint8_t>());
220             fill<uint8_t>(tensor, distribution_u8);
221             break;
222         }
223         case DataType::S8:
224         case DataType::QS8:
225         {
226             std::uniform_int_distribution<int8_t> distribution_s8(_lower.get<int8_t>(), _upper.get<int8_t>());
227             fill<int8_t>(tensor, distribution_s8);
228             break;
229         }
230         case DataType::U16:
231         {
232             std::uniform_int_distribution<uint16_t> distribution_u16(_lower.get<uint16_t>(), _upper.get<uint16_t>());
233             fill<uint16_t>(tensor, distribution_u16);
234             break;
235         }
236         case DataType::S16:
237         case DataType::QS16:
238         {
239             std::uniform_int_distribution<int16_t> distribution_s16(_lower.get<int16_t>(), _upper.get<int16_t>());
240             fill<int16_t>(tensor, distribution_s16);
241             break;
242         }
243         case DataType::U32:
244         {
245             std::uniform_int_distribution<uint32_t> distribution_u32(_lower.get<uint32_t>(), _upper.get<uint32_t>());
246             fill<uint32_t>(tensor, distribution_u32);
247             break;
248         }
249         case DataType::S32:
250         {
251             std::uniform_int_distribution<int32_t> distribution_s32(_lower.get<int32_t>(), _upper.get<int32_t>());
252             fill<int32_t>(tensor, distribution_s32);
253             break;
254         }
255         case DataType::U64:
256         {
257             std::uniform_int_distribution<uint64_t> distribution_u64(_lower.get<uint64_t>(), _upper.get<uint64_t>());
258             fill<uint64_t>(tensor, distribution_u64);
259             break;
260         }
261         case DataType::S64:
262         {
263             std::uniform_int_distribution<int64_t> distribution_s64(_lower.get<int64_t>(), _upper.get<int64_t>());
264             fill<int64_t>(tensor, distribution_s64);
265             break;
266         }
267         case DataType::F16:
268         {
269             std::uniform_real_distribution<float> distribution_f16(_lower.get<float>(), _upper.get<float>());
270             fill<float>(tensor, distribution_f16);
271             break;
272         }
273         case DataType::F32:
274         {
275             std::uniform_real_distribution<float> distribution_f32(_lower.get<float>(), _upper.get<float>());
276             fill<float>(tensor, distribution_f32);
277             break;
278         }
279         case DataType::F64:
280         {
281             std::uniform_real_distribution<double> distribution_f64(_lower.get<double>(), _upper.get<double>());
282             fill<double>(tensor, distribution_f64);
283             break;
284         }
285         default:
286             ARM_COMPUTE_ERROR("NOT SUPPORTED!");
287     }
288     return true;
289 }
290
291 NumPyBinLoader::NumPyBinLoader(std::string filename)
292     : _filename(std::move(filename))
293 {
294 }
295
296 bool NumPyBinLoader::access_tensor(ITensor &tensor)
297 {
298     const TensorShape          tensor_shape = tensor.info()->tensor_shape();
299     std::vector<unsigned long> shape;
300
301     // Open file
302     std::ifstream stream(_filename, std::ios::in | std::ios::binary);
303     ARM_COMPUTE_ERROR_ON_MSG(!stream.good(), "Failed to load binary data");
304     std::string header = npy::read_header(stream);
305
306     // Parse header
307     bool        fortran_order = false;
308     std::string typestr;
309     npy::parse_header(header, typestr, fortran_order, shape);
310
311     // Check if the typestring matches the given one
312     std::string expect_typestr = arm_compute::utils::get_typestring(tensor.info()->data_type());
313     ARM_COMPUTE_ERROR_ON_MSG(typestr != expect_typestr, "Typestrings mismatch");
314
315     // Validate tensor shape
316     ARM_COMPUTE_ERROR_ON_MSG(shape.size() != tensor_shape.num_dimensions(), "Tensor ranks mismatch");
317     if(fortran_order)
318     {
319         for(size_t i = 0; i < shape.size(); ++i)
320         {
321             ARM_COMPUTE_ERROR_ON_MSG(tensor_shape[i] != shape[i], "Tensor dimensions mismatch");
322         }
323     }
324     else
325     {
326         for(size_t i = 0; i < shape.size(); ++i)
327         {
328             ARM_COMPUTE_ERROR_ON_MSG(tensor_shape[i] != shape[shape.size() - i - 1], "Tensor dimensions mismatch");
329         }
330     }
331
332     // Read data
333     if(tensor.info()->padding().empty())
334     {
335         // If tensor has no padding read directly from stream.
336         stream.read(reinterpret_cast<char *>(tensor.buffer()), tensor.info()->total_size());
337     }
338     else
339     {
340         // If tensor has padding accessing tensor elements through execution window.
341         Window window;
342         window.use_tensor_dimensions(tensor_shape);
343
344         execute_window_loop(window, [&](const Coordinates & id)
345         {
346             stream.read(reinterpret_cast<char *>(tensor.ptr_to_element(id)), tensor.info()->element_size());
347         });
348     }
349     return true;
350 }