2 * Copyright (C) 2019 Samsung Electronics Co., Ltd. All Rights Reserved.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 * http://www.apache.org/licenses/LICENSE-2.0
8 * Unless required by applicable law or agreed to in writing, software
9 * distributed under the License is distributed on an "AS IS" BASIS,
10 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 * See the License for the specific language governing permissions and
12 * limitations under the License.
16 * @date 04 December 2019
17 * @brief This is Neural Network Class
18 * @see https://github.com/nnstreamer/nntrainer
19 * @author Jijoong Moon <jijoong.moon@samsung.com>
20 * @bug No known bugs except for NYI items
24 #include "layer_context.h"
25 #include "model_common_properties.h"
33 #include <activation_realizer.h>
34 #include <common_properties.h>
35 #include <databuffer.h>
36 #include <flatten_realizer.h>
37 #include <ini_interpreter.h>
38 #include <ini_wrapper.h>
39 #include <input_realizer.h>
40 #include <model_loader.h>
41 #include <multiout_realizer.h>
42 #include <neuralnet.h>
43 #include <nntrainer_error.h>
44 #include <nntrainer_log.h>
45 #include <node_exporter.h>
46 #include <optimizer_context.h>
47 #include <previous_input_realizer.h>
49 #include <recurrent_realizer.h>
50 #include <remap_realizer.h>
51 #include <slice_realizer.h>
52 #include <util_func.h>
54 #ifdef ENABLE_TFLITE_INTERPRETER
55 #include <tflite_interpreter.h>
59 * @brief Internal enum values for nntrainer to summarize model accuracy & loss
61 #define ML_TRAIN_SUMMARY_MODEL_TRAIN_LOSS 101
62 #define ML_TRAIN_SUMMARY_MODEL_VALID_LOSS 102
63 #define ML_TRAIN_SUMMARY_MODEL_VALID_ACCURACY 103
67 NeuralNetwork::NeuralNetwork() :
68 model_props(props::LossType(), {}, {}, props::ClipGradByGlobalNorm()),
69 model_flex_props(props::Epochs(), props::TrainingBatchSize(),
70 props::SavePath(), props::ContinueTrain(),
71 props::SaveBestPath(), props::MemoryOptimization(),
72 props::MemorySwap(), props::MemorySwapPath()),
73 load_path(std::string()),
77 data_buffers({nullptr, nullptr, nullptr}),
80 loadedFromConfig(false) {
81 app_context = AppContext(AppContext::Global());
84 NeuralNetwork::NeuralNetwork(AppContext app_context_) :
85 model_props(props::LossType(), {}, {}, props::ClipGradByGlobalNorm()),
86 model_flex_props(props::Epochs(), props::TrainingBatchSize(),
87 props::SavePath(), props::ContinueTrain(),
88 props::SaveBestPath(), props::MemoryOptimization(),
89 props::MemorySwap(), props::MemorySwapPath()),
90 load_path(std::string()),
94 data_buffers({nullptr, nullptr, nullptr}),
97 loadedFromConfig(false),
98 app_context(app_context_) {}
100 int NeuralNetwork::loadFromConfig(const std::string &config) {
101 if (loadedFromConfig == true) {
102 ml_loge("cannnot do loadFromConfig twice");
103 return ML_ERROR_INVALID_PARAMETER;
106 ModelLoader loader(app_context);
107 NeuralNetwork tempNet(*this);
109 int status = loader.loadFromContext(tempNet);
110 if (status != ML_ERROR_NONE) {
114 status = loader.loadFromConfig(config, tempNet);
115 if (status != ML_ERROR_NONE) {
119 tempNet.loadedFromConfig = true;
120 swap(tempNet, *this);
122 return ML_ERROR_NONE;
125 unsigned int NeuralNetwork::getCurrentEpoch() {
127 ml_logd("[NNTrainer] Current epoch: %d", epoch_idx);
132 void NeuralNetwork::setProperty(const std::vector<std::string> &values) {
133 auto left_props = loadProperties(values, model_props);
134 setTrainConfig(left_props);
137 void NeuralNetwork::setTrainConfig(const std::vector<std::string> &values) {
138 auto left_props = loadProperties(values, model_flex_props);
139 NNTR_THROW_IF(left_props.size(), std::invalid_argument)
140 << "Model has unparsed properties, size: " << left_props.size()
141 << " of first element: " << left_props.front();
144 int NeuralNetwork::compile() {
145 std::string loss_type = std::get<props::LossType>(model_props).empty()
147 : std::get<props::LossType>(model_props);
149 auto &input_conn = std::get<std::vector<props::InputConnection>>(model_props);
150 /// @note label layer might need to be treated in the similar way as well
152 /// @todo make NetworkGraph compiled at the construction instead of having
153 /// graph.compile(), neuralnetwork have ownership of list of layer nodes,
154 /// which will be passed at compile time.
156 std::vector<std::unique_ptr<GraphRealizer>> realizers;
158 realizers.emplace_back(new PreviousInputRealizer(
159 std::vector<Connection>(input_conn.begin(), input_conn.end())));
160 realizers.emplace_back(new MultioutRealizer());
161 realizers.emplace_back(new FlattenRealizer());
162 realizers.emplace_back(new ActivationRealizer());
164 for (auto &realizer : realizers) {
165 graph_representation = realizer->realize(graph_representation);
168 bool memory_swap = std::get<props::MemorySwap>(model_flex_props);
169 const std::string memory_swap_path =
170 std::get<props::MemorySwapPath>(model_flex_props);
171 model_graph = NetworkGraph(memory_swap, memory_swap_path);
173 model_graph.setMemoryOptimizations(
174 std::get<props::MemoryOptimization>(model_flex_props));
175 for (auto &node : graph_representation) {
176 if (auto &prop = std::get<props::ClipGradByGlobalNorm>(model_props);
178 node->setProperty({"clip_grad_by_norm=" + to_string(prop)});
180 model_graph.addLayer(node);
183 int status = model_graph.compile(loss_type);
191 int NeuralNetwork::initialize() {
192 int status = ML_ERROR_NONE;
195 ml_loge("Error: Initializing the model again");
196 return ML_ERROR_NOT_SUPPORTED;
200 ml_loge("Error: Need to compile first");
201 return ML_ERROR_NOT_SUPPORTED;
204 unsigned int n_layers = (unsigned int)model_graph.size();
206 ml_logd("initializing neural network, layer size: %d", n_layers);
207 PROFILE_MEM_ANNOTATE("Initialize");
209 auto &input_conn_prop =
210 std::get<std::vector<props::InputConnection>>(model_props);
211 auto &label_layer_prop =
212 std::get<std::vector<props::LabelLayer>>(model_props);
214 std::vector<Connection> input_conn(input_conn_prop.begin(),
215 input_conn_prop.end());
216 std::vector<std::string> label_layers;
218 if (!label_layer_prop.empty()) {
219 label_layers = std::vector<std::string>(label_layer_prop.begin(),
220 label_layer_prop.end());
223 status = model_graph.initialize(
225 std::vector<Connection>(label_layers.begin(), label_layers.end()));
228 model_graph.setBatchSize(
229 std::get<props::TrainingBatchSize>(model_flex_props));
231 // initialize optimizer and related variables
232 /// @todo: initialize should take a mode and check if mode is train but
233 /// optimizer is not given, make it as a hard error
235 /** TODO: update request of optimizer to be of same format as
236 * Layer::requestTensor */
238 std::function<std::vector<TensorDim>(const TensorDim &)> cb =
239 [this](const TensorDim &dim) {
240 return opt->getOptimizerVariableDim(dim);
242 model_graph.requestOptimizerVariable(cb, true);
246 model_graph.allocateWeights();
250 if (!load_path.empty()) {
251 load(load_path, ml::train::ModelFormat::MODEL_FORMAT_BIN);
260 NeuralNetwork::~NeuralNetwork() = default;
263 * @brief forward propagation using layers object which has layer
266 NeuralNetwork::forwarding(bool training,
267 std::function<bool(void *userdata)> stop_cb) {
268 return model_graph.forwarding(training, stop_cb);
272 * @brief forward propagation using layers object which has layer
274 sharedConstTensors NeuralNetwork::forwarding(sharedConstTensors input,
275 sharedConstTensors label,
277 auto current_batch = model_graph.getBatchSize();
278 NNTR_THROW_IF(input[0]->batch() != current_batch ||
279 (!label.empty() && label[0]->batch() != current_batch),
281 << "Error: mismatch in batchsize for data and model."
282 << " input_batch: " << input[0]->batch()
283 << " label_batch: " << label[0]->batch()
284 << " target_batch: " << current_batch;
286 model_graph.setInputsLabels(input, label);
288 return forwarding(training);
292 * @brief back propagation
293 * Call backwarding function of layer in reverse order
294 * No need to call at first Input Layer (No data to be updated)
296 void NeuralNetwork::backwarding(int iteration,
297 std::function<bool(void *userdata)> stop_cb) {
300 NNTR_THROW_IF(!opt, std::invalid_argument) << "optimizer is null!";
303 std::function<void(std::shared_ptr<LayerNode>, int)> backwarding_op =
304 [this, stop_cb](std::shared_ptr<LayerNode> node, int iteration) -> void {
306 * Do not change this order:
309 * 3. applyGradientsOnLastAccess
312 model_graph.flushCacheExcept(std::get<1>(node->getExecutionOrder()));
313 PROFILE_MEM_ANNOTATE("CalcGradient: " + node->getName());
315 bool apply_gradient = true;
317 /** If gradient optimization mode, then calculate gradient first */
318 if (dynamic_training_opt.isGradientMode())
319 node->calcGradient();
322 * If optimization off, or gradient must be applied, then this will be
324 * @todo This apply gradient should be passed to the each weight and later
325 * be queried when updating gradient at once. (after moving apply_gradient
326 * out of this function)
329 // auto &layer = node->getObject();
330 // apply_gradient = dynamic_training_opt.checkIfApply(
331 // layer->getWeightsRef(), layer->net_input[0], layer->net_hidden[0],
334 /** If gradient must be applied and its not gradient mode, calculate
337 if (!dynamic_training_opt.isGradientMode() && apply_gradient)
338 node->calcGradient();
340 model_graph.flushCacheExcept(std::get<2>(node->getExecutionOrder()));
341 PROFILE_MEM_ANNOTATE("CalcDerivative: " + node->getName());
343 if (stop_cb(nullptr)) {
347 if (node->needsCalcDerivative())
348 node->calcDerivative();
350 if (apply_gradient) {
351 /// Apply gradient only at the end of the last shared weight access
352 model_graph.applyGradients(
353 node.get(), [iteration, opt_ = opt.get()](Weight &w) {
354 w.calcRegularizationGradient();
355 w.calcWeightDecayGradient();
356 RunOptimizerContext opt_context(&w, iteration,
357 opt_->getLearningRate(iteration));
358 opt_->applyGradient(opt_context);
363 std::function<void(Weight &, int)> apply_grad_clip_op =
364 [opt_ = opt.get()](Weight &w, int iteration) -> void {
365 w.calcRegularizationGradient();
366 w.calcWeightDecayGradient();
367 RunOptimizerContext opt_context(&w, iteration,
368 opt_->getLearningRate(iteration));
369 opt_->applyGradient(opt_context);
372 model_graph.backwarding(iteration, backwarding_op, apply_grad_clip_op,
376 void NeuralNetwork::save(const std::string &file_path,
377 ml::train::ModelFormat format) {
378 NNTR_THROW_IF(!initialized, std::runtime_error)
379 << "Cannot save model if not initialized yet, path: " << file_path
380 << " format: " << static_cast<unsigned>(format);
382 /// @todo this switch case should be delegating the function call only. It's
383 /// not delegating for now as required logics are managable for now.
385 case ml::train::ModelFormat::MODEL_FORMAT_BIN: {
386 auto model_file = checkedOpenStream<std::ofstream>(
387 file_path, std::ios::out | std::ios::binary | std::ios::trunc);
388 for (auto iter = model_graph.cbegin(); iter != model_graph.cend(); iter++) {
389 (*iter)->save(model_file);
391 if (opt && istrequal(opt->getType(), "adam")) {
392 std::string adam = "adam";
393 model_file.write(adam.c_str(), adam.size());
394 for (auto iter = model_graph.cbegin(); iter != model_graph.cend();
396 (*iter)->save(model_file, true);
400 model_file.write((char *)&epoch_idx, sizeof(epoch_idx));
401 model_file.write((char *)&iter, sizeof(iter));
406 case ml::train::ModelFormat::MODEL_FORMAT_INI:
407 saveModelIni(file_path);
410 case ml::train::ModelFormat::MODEL_FORMAT_INI_WITH_BIN: {
411 auto old_save_path = std::get<props::SavePath>(model_flex_props);
413 file_path.substr(0, file_path.find_last_of('.')) + ".bin";
415 std::get<props::SavePath>(model_flex_props).set(bin_file_name);
416 save(file_path, ml::train::ModelFormat::MODEL_FORMAT_INI);
417 save(bin_file_name, ml::train::ModelFormat::MODEL_FORMAT_BIN);
418 std::get<props::SavePath>(model_flex_props) = old_save_path;
422 throw nntrainer::exception::not_supported(
423 "saving with given format is not supported yet");
427 void NeuralNetwork::load(const std::string &file_path,
428 ml::train::ModelFormat format) {
429 /// @todo this switch case should be delegating the function call only. It's
430 /// not delegating for now as required logics are managable for now.
432 case ml::train::ModelFormat::MODEL_FORMAT_BIN: {
433 NNTR_THROW_IF(!initialized, std::runtime_error)
434 << "Cannot load if not initialized yet, path: " << file_path
435 << " format: " << static_cast<unsigned>(format);
437 auto model_file = checkedOpenStream<std::ifstream>(
438 file_path, std::ios::in | std::ios::binary);
439 for (auto iter = model_graph.cbegin(); iter != model_graph.cend(); iter++) {
440 (*iter)->read(model_file);
443 /// this is assuming that the failure is allowed at the end of the file
444 /// read. so, after this line, additional read shouldn't be called
445 if (opt && istrequal(opt->getType(), "adam")) {
446 std::string opt_type;
448 model_file.read((char *)&opt_type[0], 4);
449 if (istrequal(opt_type, "adam")) {
450 for (auto iter = model_graph.cbegin(); iter != model_graph.cend();
452 (*iter)->read(model_file, true);
457 checkedRead(model_file, (char *)&epoch_idx, sizeof(epoch_idx),
458 "[NeuralNetwork::readModel] failed to read epoch_idx");
459 checkedRead(model_file, (char *)&iter, sizeof(iter),
460 "[NeuralNetwork::readModel] failed to read iteration");
462 std::cerr << "failed to read additional data like optimizer variable, "
463 "iteration, proceeding with default\n";
466 ml_logi("read modelfile: %s", file_path.c_str());
469 case ml::train::ModelFormat::MODEL_FORMAT_INI_WITH_BIN: {
470 int ret = loadFromConfig(file_path);
472 auto &save_path = std::get<props::SavePath>(model_flex_props);
473 if (!save_path.empty()) {
474 checkedOpenStream<std::ifstream>(save_path,
475 std::ios::in | std::ios::binary);
476 load_path = save_path;
480 case ml::train::ModelFormat::MODEL_FORMAT_INI: {
481 int ret = loadFromConfig(file_path);
486 throw nntrainer::exception::not_supported(
487 "loading with given format is not supported yet");
491 float NeuralNetwork::getLoss() {
494 for (auto iter = model_graph.cbegin(); iter != model_graph.cend(); iter++) {
495 loss += (*iter)->getLoss();
500 void NeuralNetwork::setLoss(float l) { loss = l; }
502 NeuralNetwork &NeuralNetwork::copy(NeuralNetwork &from) {
504 model_props = from.model_props;
505 model_flex_props = from.model_flex_props;
509 model_graph.copy(from.model_graph);
514 void NeuralNetwork::saveModelIni(const std::string &file_path) {
515 NNTR_THROW_IF(isFileExist(file_path), std::invalid_argument)
516 << "There is already a file, overriding to the exisiting file is not "
520 std::vector<IniSection> sections;
522 IniSection model_section = IniSection::FromExportable("model", *this);
523 model_section.setEntry("type", "NeuralNetwork");
524 sections.push_back(model_section);
526 auto add_section_if_any = [§ions](const std::string §ion_name,
527 auto obj_ptr, auto pred) {
529 IniSection s = IniSection::FromExportable(section_name, *obj_ptr);
530 s.setEntry("type", obj_ptr->getType());
531 sections.push_back(s);
535 add_section_if_any("optimizer", opt,
536 [](const auto &obj) { return static_cast<bool>(obj); });
538 auto &[train_buffer, valid_buffer, test_buffer] = data_buffers;
539 auto data_buffer_valid = [](const auto &buffer) {
540 return buffer && buffer->isSerializable(
541 ml::train::ExportMethods::METHOD_STRINGVECTOR);
544 add_section_if_any("train_set", train_buffer, data_buffer_valid);
545 add_section_if_any("valid_set", valid_buffer, data_buffer_valid);
546 add_section_if_any("test_set", test_buffer, data_buffer_valid);
548 IniWrapper wrapper("model_saver", sections);
549 wrapper.save_ini(file_path);
551 IniGraphInterpreter interpreter;
552 interpreter.serialize(graph_representation, file_path);
555 bool NeuralNetwork::validateInput(sharedConstTensors X) {
556 auto input_dim = getInputDimension();
557 if (X.size() != input_dim.size()) {
558 ml_loge("Error: provided number of inputs %d, required %d", (int)X.size(),
559 (int)input_dim.size());
563 for (unsigned int dim = 0; dim < input_dim.size(); dim++) {
564 if (input_dim[dim] != X[dim]->getDim()) {
565 ml_loge("Error: provided input shape does not match required shape");
566 std::stringstream ss;
567 ss << X[dim]->getDim();
568 ml_loge("Provided tensor summary : %s", ss.str().c_str());
570 ss.str(std::string());
571 ss << input_dim[dim];
572 ml_loge("Required tensor summary : %s", ss.str().c_str());
580 sharedConstTensors NeuralNetwork::inference(sharedConstTensors X,
582 return inference(X, {}, free_mem);
585 sharedConstTensors NeuralNetwork::inference(sharedConstTensors X,
586 sharedConstTensors label,
588 if (model_graph.getBatchSize() != X[0]->batch()) {
589 model_graph.setBatchSize(X[0]->batch());
592 sharedConstTensors out;
593 if (!validateInput(X))
594 throw std::invalid_argument("Input validation failed.");
596 allocate(ExecutionMode::INFERENCE);
599 PROFILE_TIME_REGISTER_EVENT(nn_foward, "nn_forward");
600 PROFILE_TIME_START(nn_foward);
601 out = forwarding(X, label, false);
602 PROFILE_TIME_END(nn_foward);
606 * Free the memory needed for training before exiting.
607 * Note that this does not free the weights for the model.
608 * Weights of the model will be freed when the model is destroyed.
610 model_graph.deallocateTensors(false);
612 /** Clear the set inputs and labels */
613 model_graph.setInputsLabels({}, {});
619 NeuralNetwork::inference(unsigned int batch_size,
620 const std::vector<float *> &input,
621 const std::vector<float *> &label) {
622 sharedConstTensors input_tensors, output_tensors;
623 auto in_dim = getInputDimension();
625 input_tensors.reserve(input.size());
626 for (unsigned int idx = 0; idx < in_dim.size(); idx++) {
627 in_dim[idx].batch(batch_size);
628 input_tensors.emplace_back(MAKE_SHARED_TENSOR(Tensor::Map(
629 input[idx], in_dim[idx].getDataLen() * sizeof(float), in_dim[idx], 0)));
632 if (!label.empty()) {
633 sharedConstTensors label_tensors;
634 auto label_dim = getOutputDimension();
635 label_tensors.reserve(label.size());
636 for (unsigned int idx = 0; idx < label_dim.size(); idx++) {
637 label_dim[idx].batch(batch_size);
638 label_tensors.emplace_back(MAKE_SHARED_TENSOR(
639 Tensor::Map(label[idx], label_dim[idx].getDataLen() * sizeof(float),
640 label_dim[idx], 0)));
642 output_tensors = inference(input_tensors, label_tensors, false);
644 output_tensors = inference(input_tensors, false);
647 std::vector<float *> output;
648 output.reserve(output_tensors.size());
650 for (auto &out : output_tensors) {
651 auto out_t = *out.get();
652 output.push_back(out_t.getData());
658 int NeuralNetwork::setDataset(const DatasetModeType &mode,
659 std::shared_ptr<ml::train::Dataset> dataset) {
660 return setDataBuffer(mode, std::static_pointer_cast<DataBuffer>(dataset));
663 int NeuralNetwork::allocate(ExecutionMode mode) {
664 model_graph.deallocateTensors();
665 model_graph.allocateTensors(mode);
667 return ML_ERROR_NONE;
670 int NeuralNetwork::deallocate() {
671 model_graph.deallocateTensors(true);
673 return ML_ERROR_NONE;
676 int NeuralNetwork::train(const std::vector<std::string> &values,
677 std::function<bool(void *)> stop_cb) {
678 int status = ML_ERROR_NONE;
680 if (data_buffers[static_cast<int>(DatasetModeType::MODE_TRAIN)] == nullptr) {
681 ml_loge("Cannot initialize the model without the train data buffer.");
682 return ML_ERROR_INVALID_PARAMETER;
686 ml_loge("Cannot train network without optimizer.");
687 return ML_ERROR_INVALID_PARAMETER;
690 setTrainConfig(values);
692 /** set batch size just before training */
693 model_graph.setBatchSize(
694 std::get<props::TrainingBatchSize>(model_flex_props));
696 status = allocate(ExecutionMode::TRAIN);
699 status = train_run(stop_cb);
703 * Free the memory needed for training before exiting.
704 * Note that this does not free the weights for the model.
705 * Weights of the model will be freed when the model is destroyed.
707 model_graph.deallocateTensors(false);
712 * @brief Run NeuralNetwork train with callback function by user
714 int NeuralNetwork::train_run(std::function<bool(void *userdata)> stop_cb) {
715 int status = ML_ERROR_NONE;
717 if (!std::get<props::ContinueTrain>(model_flex_props)) {
720 for (auto iter = model_graph.cbegin(); iter != model_graph.cend(); iter++) {
721 (*iter)->clearOptVar();
725 auto batch_size = std::get<props::TrainingBatchSize>(model_flex_props);
727 auto const &outputs = model_graph.getOutputTensors();
728 auto in_dims = model_graph.getInputDimension();
729 auto label_dims = model_graph.getOutputDimension();
731 auto &[train_buffer, valid_buffer, test_buffer] = data_buffers;
733 if (train_buffer == nullptr) {
734 ml_loge("[NeuralNetworks] there is no train dataset!");
735 return ML_ERROR_INVALID_PARAMETER;
739 * @brief run a single epoch with given callback, @a auto is used instead of
740 * std::function for performance measure
741 * @param buffer buffer to run
742 * @param shuffle whether to shuffle or not
743 * @param on_iteration_fetch function that will recieve reference to stat,
744 * buffer which will be called every time data is fetched and set
745 * @param on_epoch_end function that will recieve reference to stat,
746 * buffer which will be called on the epoch end
748 auto run_epoch = [this, &in_dims, &label_dims, &outputs, batch_size](
749 DataBuffer *buffer, bool shuffle,
750 auto &&on_iteration_fetch, auto &&on_iteration_update_stat,
751 auto &&on_epoch_end) {
752 /// @todo managing metrics must be handled here as well!! for now it is
753 /// handled in individual callbacks
755 std::future<std::shared_ptr<IterationQueue>> future_iq =
756 buffer->startFetchWorker(in_dims, label_dims, shuffle);
758 ScopedView<Iteration> iter_view = buffer->fetch();
759 if (iter_view.isEmpty()) {
762 auto &iteration = iter_view.get();
763 if (iteration.batch() != batch_size) {
764 /// @todo support partial batch
768 auto const &labels = iteration.getLabelsRef();
769 auto const &inputs = iteration.getInputsRef();
770 model_graph.setInputsLabels(inputs, labels);
772 on_iteration_fetch(stat, *buffer);
773 on_iteration_update_stat(stat, outputs, labels);
776 on_epoch_end(stat, *buffer);
778 if (stat.num_iterations == 0) {
779 throw std::runtime_error("No data came while buffer ran");
785 auto train_for_iteration = [this, stop_cb](RunStats &stat,
786 DataBuffer &buffer) {
787 model_graph.flushCache();
789 forwarding(true, stop_cb);
790 backwarding(iter++, stop_cb);
792 if (!stop_cb(nullptr)) {
793 std::cout << "#" << epoch_idx << "/" << getEpochs();
794 ml_logi("# %d / %d", epoch_idx, getEpochs());
795 auto loss = getLoss();
796 buffer.displayProgress(stat.num_iterations, loss);
800 auto update_train_stat = [this](RunStats &stat,
801 const std::vector<Tensor> &outputs,
802 const std::vector<Tensor> &labels) {
803 stat.loss += getLoss();
804 stat.num_iterations++;
807 auto train_epoch_end = [this, stop_cb](RunStats &stat, DataBuffer &buffer) {
808 stat.loss /= static_cast<float>(stat.num_iterations);
809 auto &save_path = std::get<props::SavePath>(model_flex_props);
810 if (!stop_cb(nullptr)) {
811 if (!save_path.empty()) {
812 save(save_path, ml::train::ModelFormat::MODEL_FORMAT_BIN);
815 std::cout << "#" << epoch_idx << "/" << getEpochs()
816 << " - Training Loss: " << stat.loss;
817 ml_logi("# %d / %d - Training Loss: %f", epoch_idx, getEpochs(),
819 ml_logd("[NNTrainer] Training epoch %d / %d finished successfully.",
820 epoch_idx, getEpochs());
822 ml_logd("[NNTrainer] Training stopped by stop callback function during "
828 auto eval_for_iteration = [this, batch_size](RunStats &stat,
829 DataBuffer &buffer) {
833 auto update_eval_stat = [batch_size, &update_train_stat](
834 RunStats &stat, const std::vector<Tensor> &outputs,
835 const std::vector<Tensor> &labels) {
836 auto model_out = outputs[0].argmax();
837 auto label_out = labels[0].argmax();
839 for (unsigned int b = 0; b < batch_size; b++) {
840 if (model_out[b] == label_out[b])
841 stat.num_correct_predictions++;
844 update_train_stat(stat, outputs, labels);
847 auto eval_epoch_end = [this, batch_size, max_acc = 0.0f,
848 min_loss = std::numeric_limits<float>::max()](
849 RunStats &stat, DataBuffer &buffer) mutable {
850 stat.loss /= static_cast<float>(stat.num_iterations);
851 stat.accuracy = stat.num_correct_predictions /
852 static_cast<float>(stat.num_iterations * batch_size) *
855 if (stat.accuracy > max_acc ||
856 (stat.accuracy == max_acc && stat.loss < min_loss)) {
857 max_acc = stat.accuracy;
858 /// @note this is not actually 'the' min loss for whole time but records
860 min_loss = stat.loss;
861 auto &save_best_path = std::get<props::SaveBestPath>(model_flex_props);
862 if (!save_best_path.empty()) {
863 save(save_best_path);
866 std::cout << " >> [ Accuracy: " << stat.accuracy
867 << "% - Validation Loss : " << stat.loss << " ]";
868 ml_logi("[ Accuracy: %.2f %% - Validataion Loss: %.5f", stat.accuracy,
872 PROFILE_MEM_ANNOTATE("TRAIN START");
873 auto epochs = getEpochs();
874 ml_logd("[NNTrainer] Starts training. Current epoch: %d. Total epochs: %d.",
875 epoch_idx + 1, getEpochs());
876 for (epoch_idx = epoch_idx + 1; epoch_idx <= epochs; ++epoch_idx) {
877 if (stop_cb(nullptr)) {
881 training = run_epoch(train_buffer.get(), true, train_for_iteration,
882 update_train_stat, train_epoch_end);
884 validation = run_epoch(valid_buffer.get(), false, eval_for_iteration,
885 update_eval_stat, eval_epoch_end);
889 PROFILE_MEM_ANNOTATE("TRAIN END");
892 std::cout << "Evaluation with test data...\n";
893 testing = run_epoch(test_buffer.get(), false, eval_for_iteration,
894 update_eval_stat, eval_epoch_end);
897 /** Clear the set inputs and labels */
898 model_graph.setInputsLabels({}, {});
903 void swap(NeuralNetwork &lhs, NeuralNetwork &rhs) {
907 swap(lhs.model_props, rhs.model_props);
908 swap(lhs.model_flex_props, rhs.model_flex_props);
909 swap(lhs.load_path, rhs.load_path);
910 swap(lhs.epoch_idx, rhs.epoch_idx);
911 swap(lhs.iter, rhs.iter);
912 swap(lhs.loss, rhs.loss);
913 swap(lhs.opt, rhs.opt);
914 swap(lhs.data_buffers, rhs.data_buffers);
915 swap(lhs.initialized, rhs.initialized);
916 swap(lhs.model_graph, rhs.model_graph);
917 swap(lhs.graph_representation, rhs.graph_representation);
918 swap(lhs.compiled, rhs.compiled);
919 swap(lhs.loadedFromConfig, rhs.loadedFromConfig);
923 int NeuralNetwork::addLayer(NodeType layer) {
924 int status = ML_ERROR_NONE;
927 return ML_ERROR_NOT_SUPPORTED;
930 /** Insert the layer to the graph */
931 model_graph.addLayer(layer);
932 graph_representation.push_back(layer);
937 NeuralNetwork &NeuralNetwork::copyConfiguration(NeuralNetwork &from) {
939 model_props = from.model_props;
940 model_flex_props = from.model_flex_props;
944 NetworkGraph f_graph = from.getNetworkGraph();
945 for (auto &l_node : f_graph.getLayerNodes()) {
946 addLayer(static_cast<std::shared_ptr<ml::train::Layer>>(
947 l_node->cloneConfiguration()));
953 NeuralNetwork::GraphType
954 NeuralNetwork::getUnsortedLayers(const std::string &input_layer,
955 const std::string &output_layer) {
956 return model_graph.getUnsortedLayers(input_layer, output_layer);
959 int NeuralNetwork::setOptimizer(
960 std::shared_ptr<ml::train::Optimizer> optimizer) {
962 return ML_ERROR_NOT_SUPPORTED;
965 opt = std::static_pointer_cast<OptimizerWrapped>(optimizer);
967 return ML_ERROR_NONE;
970 int NeuralNetwork::setDataBuffer(const DatasetModeType &mode,
971 std::shared_ptr<DataBuffer> data_buffer) {
972 if (data_buffer == nullptr) {
973 return ML_ERROR_INVALID_PARAMETER;
976 this->data_buffers[static_cast<int>(mode)] = data_buffer;
978 return ML_ERROR_NONE;
981 int NeuralNetwork::getLayer(const char *name,
982 std::shared_ptr<ml::train::Layer> *layer) {
983 // We provide the layer change through the api with user's responsibility.
986 // ml_loge("Cannot get compiled layer.");
987 // return ML_ERROR_NOT_SUPPORTED;
990 *layer = std::static_pointer_cast<ml::train::Layer>(
991 model_graph.getLayerNode(std::string(name)));
992 return ML_ERROR_NONE;
995 void NeuralNetwork::printMetrics(std::ostream &out, unsigned int flags) {
997 case ML_TRAIN_SUMMARY_MODEL_TRAIN_LOSS:
998 out << training.loss << std::endl;
1001 case ML_TRAIN_SUMMARY_MODEL_VALID_LOSS:
1002 out << validation.loss << std::endl;
1005 case ML_TRAIN_SUMMARY_MODEL_VALID_ACCURACY:
1006 out << validation.accuracy << std::endl;
1014 void NeuralNetwork::printPreset(std::ostream &out, unsigned int preset) {
1015 /** print neuralnet metrics */
1016 printMetrics(out, preset);
1017 if (preset > ML_TRAIN_SUMMARY_TENSOR)
1020 LayerNode::PrintPreset layer_preset = LayerNode::PrintPreset::PRINT_NONE;
1022 ///@todo match flags with preset
1023 unsigned int flags = PRINT_INST_INFO | PRINT_GRAPH_INFO | PRINT_PROP |
1024 PRINT_OPTIMIZER | PRINT_METRIC;
1027 case ML_TRAIN_SUMMARY_TENSOR:
1028 layer_preset = LayerNode::PrintPreset::PRINT_ALL;
1030 case ML_TRAIN_SUMMARY_LAYER:
1031 layer_preset = initialized ? LayerNode::PrintPreset::PRINT_SUMMARY
1032 : LayerNode::PrintPreset::PRINT_SUMMARY_META;
1034 case ML_TRAIN_SUMMARY_MODEL:
1037 throw std::invalid_argument("given verbosity is invalid");
1040 print(out, flags, layer_preset);
1043 void NeuralNetwork::addWithReferenceLayers(
1044 const std::vector<std::shared_ptr<ml::train::Layer>> &reference,
1045 const std::string &scope, const std::vector<std::string> &input_layers,
1046 const std::vector<std::string> &start_layers,
1047 const std::vector<std::string> &end_layers,
1048 ml::train::ReferenceLayersType type,
1049 const std::vector<std::string> &type_properties) {
1050 std::vector<NodeType> casted_reference;
1051 casted_reference.reserve(reference.size());
1052 for (auto &node : reference) {
1053 casted_reference.emplace_back(std::static_pointer_cast<LayerNode>(node));
1056 addWithReferenceLayers(casted_reference, scope, input_layers, start_layers,
1057 end_layers, type, type_properties);
1059 void NeuralNetwork::addWithReferenceLayers(
1060 const std::vector<std::shared_ptr<LayerNode>> &reference,
1061 const std::string &scope, const std::vector<std::string> &input_layers,
1062 const std::vector<std::string> &start_layers,
1063 const std::vector<std::string> &end_layers,
1064 ml::train::ReferenceLayersType type,
1065 const std::vector<std::string> &type_properties) {
1066 /// @todo below configuration should be extracted as a free function to make
1067 /// it more testable, and reused inside graph interpreter
1069 /// @note we can exploit connection to connection more fine grained, for now
1070 /// it is not supported but we can easily make this supported
1071 std::vector<std::shared_ptr<LayerNode>> nodes;
1072 nodes.reserve(reference.size());
1073 for (auto &node : reference) {
1074 nodes.push_back(node->cloneConfiguration());
1078 std::vector<Connection>(start_layers.begin(), start_layers.end());
1080 std::vector<Connection>(input_layers.begin(), input_layers.end());
1082 std::vector<Connection>(end_layers.begin(), end_layers.end());
1084 std::vector<std::unique_ptr<GraphRealizer>> realizers;
1086 realizers.emplace_back(new PreviousInputRealizer(start_conns));
1087 realizers.emplace_back(new SliceRealizer(start_conns, end_conns));
1089 if (!input_conns.empty()) {
1090 realizers.emplace_back(new InputRealizer(start_conns, input_conns));
1093 if (type == ml::train::ReferenceLayersType::RECURRENT) {
1094 realizers.emplace_back(
1095 new RecurrentRealizer(type_properties, input_conns, end_conns));
1098 if (!scope.empty()) {
1099 realizers.emplace_back(
1100 new RemapRealizer([&scope, &input_conns](std::string &name) {
1101 for (auto &i : input_conns) {
1102 if (i.getName() == name) {
1106 name = scope + "/" + name;
1110 for (auto &realizer : realizers) {
1111 nodes = realizer->realize(nodes);
1114 for (auto &node : nodes) {
1119 void NeuralNetwork::exportTo(Exporter &exporter,
1120 const ml::train::ExportMethods &method) const {
1121 exporter.saveResult(model_props, method, this);
1122 exporter.saveResult(model_flex_props, method, this);
1125 void NeuralNetwork::print(std::ostream &out, unsigned int flags,
1126 LayerNode::PrintPreset layerPrintPreset) {
1127 if (flags & PRINT_INST_INFO) {
1128 /// @todo uncomment this after implement getProperty (#1875)
1129 // out << "===================";
1130 // printInstance(out, this);
1133 if (flags & PRINT_GRAPH_INFO) {
1134 unsigned int total_col_size = 80;
1135 std::vector<unsigned int> column_size = {20, 20, 20, 20};
1136 auto print_graph_layer_info =
1137 [column_size](std::ostream &out, std::vector<std::string> layer_info) {
1138 auto trim_string = [](std::string str, unsigned int column_width) {
1139 return str.size() < column_width ? str
1140 : str.substr(0, column_width - 1);
1143 for (unsigned int i = 0; i < column_size.size(); ++i) {
1144 out << std::setw(column_size[i])
1145 << trim_string(layer_info[i], column_size[i]);
1150 out << std::string(total_col_size, '=') << '\n';
1151 print_graph_layer_info(
1152 out, {"Layer name", "Layer type", "Input dimension", "Input layer"});
1153 out << std::string(total_col_size, '=') << '\n';
1155 props::GenericShape dim_property;
1157 for (auto iter = model_graph.cbegin(); iter != model_graph.cend();
1159 std::string first_dim;
1160 if (iter->getInputDimensions().empty()) {
1163 dim_property.set(iter->getInputDimensions()[0]);
1164 first_dim = to_string(dim_property);
1166 const std::vector<std::string> &input_layer_names =
1167 iter->getInputConnections();
1168 std::string first_input_name =
1169 input_layer_names.empty() ? "" : input_layer_names[0];
1170 print_graph_layer_info(
1171 out, {iter->getName(), iter->getType(), first_dim, first_input_name});
1172 for (unsigned int i = 1; i < input_layer_names.size(); ++i) {
1173 dim_property.set(iter->getInputDimensions()[i]);
1174 print_graph_layer_info(
1175 out, {"", "", to_string(dim_property), input_layer_names[i]});
1177 out << std::string(total_col_size,
1178 iter == model_graph.cend() - 1 ? '=' : '-')
1182 auto &input_connection =
1183 std::get<std::vector<props::InputConnection>>(model_props);
1184 auto model_input = std::vector<Connection>(input_connection.begin(),
1185 input_connection.end());
1186 auto is_actually_an_input_node =
1187 [model_input](graph_const_iterator<LayerNode> node) {
1188 return node->hasInputShapeProperty() or
1189 std::any_of(model_input.begin(), model_input.end(),
1190 [node](auto &conn) {
1191 return node->getName() == conn.getName();
1195 for (auto iter = model_graph.cbegin(); iter != model_graph.cend();
1197 const std::vector<std::string> &input_layer_names =
1198 iter->getInputConnections();
1200 /// @brief connection information.
1201 // Intended comment.
1202 // std::string first_input_name =
1203 // input_layer_names.empty()
1204 // ? (is_actually_an_input_node(iter) || iter ==
1205 // model_graph.cbegin()
1207 // : (iter - 1)->getName())
1208 // : input_layer_names[0];
1209 print_graph_layer_info(out, {iter->getName(), iter->getType(), "", ""});
1210 for (unsigned int i = 1; i < input_layer_names.size(); ++i) {
1211 print_graph_layer_info(out, {"", "", "", ""});
1213 out << std::string(total_col_size,
1214 iter == model_graph.cend() - 1 ? '=' : '-')
1220 if (flags & PRINT_PROP) {
1221 /// @todo print neuralnet property
1222 /// @todo print mode (if it is eval or training)
1225 if (flags & PRINT_OPTIMIZER) {
1226 /// @todo print optimizer (with print optimizer prop)
1229 if (flags & PRINT_METRIC) {
1230 /// @todo print metric (currently it is done at printPreset as a
1232 /// @todo print loss function when it is not initialized. (if it is
1233 /// initialized, loss layer will be printed)
1236 if (model_graph.empty()) {
1237 out << "model is empty!" << std::endl;
1241 /** print layer properties */
1242 for (auto iter = model_graph.cbegin(); iter != model_graph.cend(); iter++)
1243 (*iter)->printPreset(out, layerPrintPreset);
1245 /// @todo Add status to check neuralnet has been run. #290
1248 void NeuralNetwork::forEachLayer(
1249 std::function<void(ml::train::Layer &, RunLayerContext &, void *)> fn,
1251 for (auto iter = model_graph.cbegin(); iter != model_graph.cend(); iter++) {
1252 auto ln = std::static_pointer_cast<LayerNode>(*iter).get();
1253 fn(*ln, std::forward<RunLayerContext &>(ln->getRunContext()), user_data);
1257 void NeuralNetwork::exports(const ml::train::ExportMethods &method,
1258 const std::string file_path) {
1260 case ml::train::ExportMethods::METHOD_TFLITE: {
1261 #ifdef ENABLE_TFLITE_INTERPRETER
1262 nntrainer::TfliteInterpreter interpreter;
1264 /// We will call "serialize" method for the model which is already trained
1265 /// or allocated. So, we need to call deallocateTensors first to make sure
1266 /// `dealloc_weights == false`
1267 model_graph.deallocateTensors();
1268 model_graph.allocateTensors(ExecutionMode::INFERENCE);
1269 interpreter.serialize(graph_representation, file_path);
1270 model_graph.deallocateTensors();
1272 throw std::runtime_error{
1273 "Export methods METHOD_TFLITE is not supported. Please enable tflite "
1274 "interpreter by set ENABLE_TFLITE_INTERPRETER=1"};
1279 throw std::runtime_error{"Unsupported export method"};
1282 } /* namespace nntrainer */