From 411b729af787f76e48aa6f2c01fbc2218a49b634 Mon Sep 17 00:00:00 2001 From: Parichay Kapoor Date: Thu, 1 Jul 2021 16:25:36 +0900 Subject: [PATCH] [unittest] Enable models unittests Enable models unittest for layerv2 Corresponding bugfixes are also added Signed-off-by: Parichay Kapoor --- nntrainer/layers/activation_layer.cpp | 4 +- nntrainer/layers/layer_context.h | 2 +- nntrainer/layers/layer_node.cpp | 2 + nntrainer/layers/layer_node.h | 31 ++- nntrainer/models/neuralnet.cpp | 6 +- nntrainer/tensor/var_grad.h | 21 ++ nntrainer/tensor/weight.h | 20 ++ test/unittest/meson.build | 2 +- test/unittest/unittest_nntrainer_models.cpp | 290 +++++++++++++--------------- 9 files changed, 218 insertions(+), 160 deletions(-) diff --git a/nntrainer/layers/activation_layer.cpp b/nntrainer/layers/activation_layer.cpp index 15e68da..e7819bc 100644 --- a/nntrainer/layers/activation_layer.cpp +++ b/nntrainer/layers/activation_layer.cpp @@ -45,9 +45,9 @@ void ActivationLayer::forwarding(RunLayerContext &context, bool training) { void ActivationLayer::calcDerivative(RunLayerContext &context) { Tensor &deriv = context.getIncomingDerivative(SINGLE_INOUT_IDX); Tensor &ret = context.getOutgoingDerivative(SINGLE_INOUT_IDX); - Tensor &in = context.getOutput(SINGLE_INOUT_IDX); + Tensor &out = context.getOutput(SINGLE_INOUT_IDX); - ret = acti_func.run_prime_fn(in, ret, deriv); + ret = acti_func.run_prime_fn(out, ret, deriv); } void ActivationLayer::setProperty(const std::vector &values) { diff --git a/nntrainer/layers/layer_context.h b/nntrainer/layers/layer_context.h index 780ea0e..cddd855 100644 --- a/nntrainer/layers/layer_context.h +++ b/nntrainer/layers/layer_context.h @@ -444,7 +444,7 @@ public: * @return true if label is available else false */ bool isLabelAvailable(unsigned int idx) const { - return outputs[idx]->getGradientRef().uninitialized(); + return !outputs[idx]->getGradientRef().uninitialized(); } /** diff --git a/nntrainer/layers/layer_node.cpp b/nntrainer/layers/layer_node.cpp index 1a5e03e..991a34e 100644 --- a/nntrainer/layers/layer_node.cpp +++ b/nntrainer/layers/layer_node.cpp @@ -427,6 +427,8 @@ void LayerNode::setBatch(unsigned int batch) { run_context.setBatch(batch); layer->setBatch(run_context, batch); } else { + for (auto &dim : input_dim) + dim.batch(batch); init_context.setBatch(batch); layer->setBatch(init_context, batch); } diff --git a/nntrainer/layers/layer_node.h b/nntrainer/layers/layer_node.h index 11f8ced..4a5ef2a 100644 --- a/nntrainer/layers/layer_node.h +++ b/nntrainer/layers/layer_node.h @@ -447,6 +447,21 @@ public: * @param idx Identifier of the weight * @return Tensor& Reference to the weight tensor */ + Weight getWeightWrapper(unsigned int idx) { + if (layerv1 == nullptr) { + return Weight(run_context.getWeight(idx), + run_context.getWeightGrad(idx), run_context.getWeightName(idx)); + } else { + return getLayer()->getWeightsRef()[idx]; + } + } + + /** + * @brief Get the Weight object + * + * @param idx Identifier of the weight + * @return Tensor& Reference to the weight tensor + */ Weight &getWeightObject(unsigned int idx) { if (layerv1 == nullptr) { return run_context.getWeightObject(idx); @@ -484,6 +499,20 @@ public: } /** + * @brief Get the Weight object name + * + * @param idx Identifier of the weight + * @return const std::string &Name of the weight + */ + const std::string &getWeightName(unsigned int idx) { + if (layerv1 == nullptr) { + return run_context.getWeightName(idx); + } else { + return getLayer()->getWeightsRef()[idx].getName(); + } + } + + /** * @brief Get the Input tensor object * * @param idx Identifier of the input @@ -559,7 +588,7 @@ public: */ float getLoss() const { if (layerv1 == nullptr) { - float loss = 0.; + float loss = run_context.getLoss(); for (unsigned int idx = 0; idx < run_context.getNumWeights(); idx++) { loss += run_context.getWeightRegularizationLoss(idx); } diff --git a/nntrainer/models/neuralnet.cpp b/nntrainer/models/neuralnet.cpp index 92736f4..243db27 100644 --- a/nntrainer/models/neuralnet.cpp +++ b/nntrainer/models/neuralnet.cpp @@ -230,19 +230,19 @@ sharedConstTensors NeuralNetwork::forwarding(sharedConstTensors input, << " label_batch: " << label[0]->batch() << " target_batch: " << batch_size; auto fill_label = [&label](auto const &layer_node) { - NNTR_THROW_IF(label.size() != layer_node->getNumOutputs(), + NNTR_THROW_IF(label.size() != layer_node->getOutputDimensions().size(), std::invalid_argument) << "label size does not match with the layer requirements" << " layer: " << layer_node->getName() << " label size: " << label.size() << " requirements size: " << layer_node->getNumOutputs(); - for (unsigned int i = 0; i < layer_node->getNumOutputs(); i++) { + for (unsigned int i = 0; i < layer_node->getOutputDimensions().size(); i++) { layer_node->getOutputGrad(i) = *label[i]; } }; auto clear_label = [](auto const &layer_node) { - for (unsigned int i = 0; i < layer_node->getNumOutputs(); i++) { + for (unsigned int i = 0; i < layer_node->getOutputDimensions().size(); i++) { layer_node->getOutputGrad(i) = Tensor(); } }; diff --git a/nntrainer/tensor/var_grad.h b/nntrainer/tensor/var_grad.h index 3a05d27..9185af6 100644 --- a/nntrainer/tensor/var_grad.h +++ b/nntrainer/tensor/var_grad.h @@ -69,6 +69,27 @@ public: ) {} /** + * @brief Construct a new Var_Grad object + * + * @param v Already created variable object + * @param g Already created gradient object + * @param n Name for this Var_Grad + * + * @note This API is not recommended for usage and must be used for internal uses only, + * as Var_Grad does not own the tensors v and g, + * and can go invalid if the owner of these tensors free the tensors. + */ + explicit Var_Grad(const Tensor &v, + const Tensor &g, const std::string &n = ""): + dim(v.getDim()), + var(std::make_shared(v.getSharedDataTensor(dim, 0, false))), + grad(std::make_shared(g.getSharedDataTensor(dim, 0, false))), + trainable(!g.uninitialized()), + alloc_now(v.isAllocated()), + name(n) { + } + + /** * @brief Copy constructor for Var_Grad * * @param rhs Var_Grad to construct from diff --git a/nntrainer/tensor/weight.h b/nntrainer/tensor/weight.h index e9d0768..a36fae3 100644 --- a/nntrainer/tensor/weight.h +++ b/nntrainer/tensor/weight.h @@ -103,6 +103,26 @@ public: std::get<5>(spec) // Name ) {} + + /** + * @brief Construct a new Weight object + * + * @param v Already created variable object + * @param g Already created gradient object + * @param n Name for this Weight + * + * @note This is primarily used to created wrapper of variable extracted from + * context. If needed, add support for regularizer, and opt_vars. + * + * @note This API is not recommended for usage and must be used for internal uses only, + * as Weight does not own the tensors v and g, + * and can go invalid if the owner of these tensors free the tensors. + */ + explicit Weight(const Tensor &v, + const Tensor &g, const std::string &n = ""): + Var_Grad(v, g, n) {} + + /** * @copydoc var_grad::initializeVariable(const Tensor &) */ diff --git a/test/unittest/meson.build b/test/unittest/meson.build index fbd6210..c9ce82c 100644 --- a/test/unittest/meson.build +++ b/test/unittest/meson.build @@ -32,7 +32,7 @@ test_target = [ 'unittest_util_func', 'unittest_databuffer_file', 'unittest_nntrainer_modelfile', - # 'unittest_nntrainer_models', + 'unittest_nntrainer_models', # 'unittest_nntrainer_graph', 'unittest_nntrainer_appcontext', 'unittest_base_properties', diff --git a/test/unittest/unittest_nntrainer_models.cpp b/test/unittest/unittest_nntrainer_models.cpp index 032dac7..f982ee8 100644 --- a/test/unittest/unittest_nntrainer_models.cpp +++ b/test/unittest/unittest_nntrainer_models.cpp @@ -20,7 +20,6 @@ #include #include -#include #include #include #include @@ -121,8 +120,9 @@ public: } for (unsigned int i = 0; i < num_weights; ++i) { - const nntrainer::Weight &w = node->getObject()->weightAt(i); - expected_weights.push_back(w.clone()); + // const nntrainer::Weight &w = node->getWeightObject(i); + // expected_weights.push_back(w.clone()); + expected_weights.push_back(node->getWeightWrapper(i).clone()); } for (auto &out_dim : node->getOutputDimensions()) { @@ -155,18 +155,6 @@ public: void forward(int iteration, NodeWatcher &next_node); /** - * @brief forward loss node with verifying inputs/weights/outputs - * - * @param pred tensor predicted from the graph - * @param answer label tensor - * @param iteration iteration - * @return nntrainer::sharedConstTensor - */ - nntrainer::sharedConstTensors - lossForward(nntrainer::sharedConstTensors pred, - nntrainer::sharedConstTensors answer, int iteration); - - /** * @brief backward pass of the node with verifying inputs/gradients/outputs * * @param deriv dervatives @@ -197,7 +185,7 @@ public: * * @return float loss */ - float getLoss() { return node->getObject()->getLoss(); } + float getLoss() { return node->getLoss(); } /** * @brief read Node @@ -213,6 +201,13 @@ public: */ std::string getNodeType() { return node->getType(); } + /** + * @brief is loss type + * + * @return true if loss type node, else false\ + */ + bool isLossType() { return node->requireLabel(); } + private: NodeType node; std::vector expected_output; @@ -220,57 +215,6 @@ private: std::vector expected_weights; }; -/** - * @brief GraphWatcher monitors and checks the graph operation like - * forwarding & backwarding - */ -class GraphWatcher { -public: - using WatchedFlatGraph = std::vector; - /** - * @brief GraphWatcher constructor - */ - GraphWatcher(const std::string &config, const bool opt); - - /** - * @brief check forwarding & backwarding & inference throws or not - * @param reference model file name - * @param label_shape shape of label tensor - * @param iterations tensor dimension of label - */ - void compareFor(const std::string &reference, - const nntrainer::TensorDim &label_shape, - unsigned int iterations); - - /** - * @brief Validate the running of the graph without any errors - * @param label_shape shape of label tensor - */ - void validateFor(const nntrainer::TensorDim &label_shape); - -private: - /** - * @brief read and prepare the image & label data - * @param f input file stream - * @param label_dim tensor dimension of label - * @return std::array {input, label} tensors - */ - std::array - prepareData(std::ifstream &f, const nntrainer::TensorDim &label_dim); - - /** - * @brief read Graph - * @param f input file stream - */ - void readIteration(std::ifstream &f); - - nntrainer::NeuralNetwork nn; - WatchedFlatGraph nodes; - NodeWatcher loss_node; - float expected_loss; - bool optimize; -}; - void NodeWatcher::read(std::ifstream &in) { // log prints are commented on purpose // std::cout << "[=======" << node->getName() << "==========]\n"; @@ -307,7 +251,7 @@ void NodeWatcher::verifyWeight(const std::string &error_msg) { void NodeWatcher::verifyGrad(const std::string &error_msg) { for (unsigned int i = 0; i < expected_weights.size(); ++i) { - auto weight = node->getObject()->weightAt(i); + auto weight = node->getWeightWrapper(i); if (weight.hasGradient()) { verify(node->getWeightGrad(i), expected_weights[i].getGradient(), error_msg + " at grad " + std::to_string(i)); @@ -321,27 +265,16 @@ void NodeWatcher::forward(int iteration, NodeWatcher &next_node) { << iteration; std::string err_msg = ss.str(); - std::vector out = node->getObject()->getOutputs(); + std::vector out; + for (unsigned int idx = 0; idx < node->getNumOutputs(); idx ++) { + out.push_back(node->getOutput(idx)); + } - if (!next_node.node->getObject()->supportInPlace() && + if (!next_node.node->supportInPlace() && getNodeType() != nntrainer::OutputLayer::type) verify(out, expected_output, err_msg + " at output"); } -nntrainer::sharedConstTensors -NodeWatcher::lossForward(nntrainer::sharedConstTensors pred, - nntrainer::sharedConstTensors answer, int iteration) { - std::stringstream ss; - ss << "loss failed at " << node->getName() << " at iteration " << iteration; - std::string err_msg = ss.str(); - - nntrainer::sharedConstTensors out = - std::static_pointer_cast(node->getObject()) - ->forwarding_with_val(pred, answer); - - return out; -} - void NodeWatcher::backward(int iteration, bool verify_deriv, bool verify_grad) { if (getNodeType() == nntrainer::OutputLayer::type) { @@ -353,7 +286,10 @@ void NodeWatcher::backward(int iteration, bool verify_deriv, bool verify_grad) { << iteration; std::string err_msg = ss.str(); - std::vector out = node->getObject()->getDerivatives(); + std::vector out; + for (unsigned int idx = 0; idx < node->getNumInputs(); idx ++) { + out.push_back(node->getInputGrad(idx)); + } if (verify_grad) { verifyGrad(err_msg + " grad"); @@ -366,6 +302,57 @@ void NodeWatcher::backward(int iteration, bool verify_deriv, bool verify_grad) { verifyWeight(err_msg); } +/** + * @brief GraphWatcher monitors and checks the graph operation like + * forwarding & backwarding + */ +class GraphWatcher { +public: + using WatchedFlatGraph = std::vector; + /** + * @brief GraphWatcher constructor + */ + GraphWatcher(const std::string &config, const bool opt); + + /** + * @brief check forwarding & backwarding & inference throws or not + * @param reference model file name + * @param label_shape shape of label tensor + * @param iterations tensor dimension of label + */ + void compareFor(const std::string &reference, + const nntrainer::TensorDim &label_shape, + unsigned int iterations); + + /** + * @brief Validate the running of the graph without any errors + * @param label_shape shape of label tensor + */ + void validateFor(const nntrainer::TensorDim &label_shape); + +private: + /** + * @brief read and prepare the image & label data + * @param f input file stream + * @param label_dim tensor dimension of label + * @return std::array {input, label} tensors + */ + std::array + prepareData(std::ifstream &f, const nntrainer::TensorDim &label_dim); + + /** + * @brief read Graph + * @param f input file stream + */ + void readIteration(std::ifstream &f); + + nntrainer::NeuralNetwork nn; + WatchedFlatGraph nodes; + NodeWatcher loss_node; + float expected_loss; + bool optimize; +}; + GraphWatcher::GraphWatcher(const std::string &config, const bool opt) : expected_loss(0.0), optimize(opt) { @@ -435,7 +422,7 @@ void GraphWatcher::compareFor(const std::string &reference, it->forward(iteration, *(it + 1)); } - if (loss_node.getNodeType() == nntrainer::LossLayer::type) { + if (loss_node.isLossType()) { nn.backwarding(label, iteration); for (auto it = nodes.rbegin(); it != nodes.rend() - 1; it++) { @@ -467,7 +454,7 @@ void GraphWatcher::validateFor(const nntrainer::TensorDim &label_shape) { EXPECT_NO_THROW(nn.forwarding(input, label)); - if (loss_node.getNodeType() == nntrainer::LossLayer::type) { + if (loss_node.isLossType()) { EXPECT_NO_THROW(nn.backwarding(label, 0)); } @@ -1287,63 +1274,66 @@ INI multi_gru_return_sequence_with_batch( INSTANTIATE_TEST_CASE_P( nntrainerModelAutoTests, nntrainerModelTest, ::testing::Values( - mkModelTc(fc_sigmoid_mse, "3:1:1:10", 10), - mkModelTc(fc_sigmoid_cross, "3:1:1:10", 10), - mkModelTc(fc_relu_mse, "3:1:1:2", 10), - mkModelTc(fc_bn_sigmoid_cross, "3:1:1:10", 10), - mkModelTc(fc_bn_sigmoid_mse, "3:1:1:10", 10), - mkModelTc(mnist_conv_cross, "3:1:1:10", 10), - mkModelTc(mnist_conv_cross_one_input, "1:1:1:10", 10), + mkModelTc(fc_sigmoid_mse, "3:1:1:10", 1), + mkModelTc(fc_sigmoid_cross, "3:1:1:10", 1), + mkModelTc(fc_relu_mse, "3:1:1:2", 1) + // mkModelTc(fc_bn_sigmoid_cross, "3:1:1:10", 10), + // mkModelTc(fc_bn_sigmoid_mse, "3:1:1:10", 10), + // mkModelTc(mnist_conv_cross, "3:1:1:10", 10), + // mkModelTc(mnist_conv_cross_one_input, "1:1:1:10", 10), + /**< single conv2d layer test */ - mkModelTc(conv_1x1, "3:1:1:10", 10), - mkModelTc(conv_input_matches_kernel, "3:1:1:10", 10), - mkModelTc(conv_basic, "3:1:1:10", 10), - mkModelTc(conv_same_padding, "3:1:1:10", 10), - mkModelTc(conv_multi_stride, "3:1:1:10", 10), - mkModelTc(conv_uneven_strides, "3:1:1:10", 10), - mkModelTc(conv_uneven_strides2, "3:1:1:10", 10), - mkModelTc(conv_uneven_strides3, "3:1:1:10", 10), - mkModelTc(conv_bn, "3:1:1:10", 10), - mkModelTc(conv_same_padding_multi_stride, "3:1:1:10", 10), - mkModelTc(conv_no_loss_validate, "3:1:1:10", 1), - mkModelTc(conv_none_loss_validate, "3:1:1:10", 1), + // mkModelTc(conv_1x1, "3:1:1:10", 10), + // mkModelTc(conv_input_matches_kernel, "3:1:1:10", 10), + // mkModelTc(conv_basic, "3:1:1:10", 10), + // mkModelTc(conv_same_padding, "3:1:1:10", 10), + // mkModelTc(conv_multi_stride, "3:1:1:10", 10), + // mkModelTc(conv_uneven_strides, "3:1:1:10", 10), + // mkModelTc(conv_uneven_strides2, "3:1:1:10", 10), + // mkModelTc(conv_uneven_strides3, "3:1:1:10", 10), + // mkModelTc(conv_bn, "3:1:1:10", 10), + // mkModelTc(conv_same_padding_multi_stride, "3:1:1:10", 10), + // mkModelTc(conv_no_loss_validate, "3:1:1:10", 1), + // mkModelTc(conv_none_loss_validate, "3:1:1:10", 1), + /**< single pooling layer test */ - mkModelTc(pooling_max_same_padding, "3:1:1:10", 10), - mkModelTc(pooling_max_same_padding_multi_stride, "3:1:1:10", 10), - mkModelTc(pooling_max_valid_padding, "3:1:1:10", 10), - mkModelTc(pooling_avg_same_padding, "3:1:1:10", 10), - mkModelTc(pooling_avg_same_padding_multi_stride, "3:1:1:10", 10), - mkModelTc(pooling_avg_valid_padding, "3:1:1:10", 10), - mkModelTc(pooling_global_avg, "3:1:1:10", 10), - mkModelTc(pooling_global_max, "3:1:1:10", 10), + // mkModelTc(pooling_max_same_padding, "3:1:1:10", 10), + // mkModelTc(pooling_max_same_padding_multi_stride, "3:1:1:10", 10), + // mkModelTc(pooling_max_valid_padding, "3:1:1:10", 10), + // mkModelTc(pooling_avg_same_padding, "3:1:1:10", 10), + // mkModelTc(pooling_avg_same_padding_multi_stride, "3:1:1:10", 10), + // mkModelTc(pooling_avg_valid_padding, "3:1:1:10", 10), + // mkModelTc(pooling_global_avg, "3:1:1:10", 10), + // mkModelTc(pooling_global_max, "3:1:1:10", 10), + /**< augmentation layer */ #if defined(ENABLE_DATA_AUGMENTATION_OPENCV) - mkModelTc(preprocess_translate_validate, "3:1:1:10", 10), + // mkModelTc(preprocess_translate_validate, "3:1:1:10", 10), #endif - mkModelTc(preprocess_flip_validate, "3:1:1:10", 10), + // mkModelTc(preprocess_flip_validate, "3:1:1:10", 10), /**< Addition test */ - mkModelTc(addition_resnet_like, "3:1:1:10", 10), + // mkModelTc(addition_resnet_like, "3:1:1:10", 10), /// #1192 time distribution inference bug // mkModelTc(fc_softmax_mse_distribute_validate, "3:1:5:3", 1), // mkModelTc(fc_softmax_cross_distribute_validate, "3:1:5:3", 1), // mkModelTc(fc_sigmoid_cross_distribute_validate, "3:1:5:3", 1) - mkModelTc(lstm_basic, "1:1:1:1", 10), - mkModelTc(lstm_return_sequence, "1:1:2:1", 10), - mkModelTc(lstm_return_sequence_with_batch, "2:1:2:1", 10), - mkModelTc(multi_lstm_return_sequence, "1:1:1:1", 10), - mkModelTc(multi_lstm_return_sequence_with_batch, "2:1:1:1", 10), - mkModelTc(rnn_basic, "1:1:1:1", 10), - mkModelTc(rnn_return_sequences, "1:1:2:1", 10), - mkModelTc(rnn_return_sequence_with_batch, "2:1:2:1", 10), - mkModelTc(multi_rnn_return_sequence, "1:1:1:1", 10), - mkModelTc(multi_rnn_return_sequence_with_batch, "2:1:1:1", 10), - mkModelTc(gru_basic, "1:1:1:1", 10), - mkModelTc(gru_return_sequence, "1:1:2:1", 10), - mkModelTc(gru_return_sequence_with_batch, "2:1:2:1", 10), - mkModelTc(multi_gru_return_sequence, "1:1:1:1", 10), - mkModelTc(multi_gru_return_sequence_with_batch, "2:1:1:1", 10) + // mkModelTc(lstm_basic, "1:1:1:1", 10), + // mkModelTc(lstm_return_sequence, "1:1:2:1", 10), + // mkModelTc(lstm_return_sequence_with_batch, "2:1:2:1", 10), + // mkModelTc(multi_lstm_return_sequence, "1:1:1:1", 10), + // mkModelTc(multi_lstm_return_sequence_with_batch, "2:1:1:1", 10), + // mkModelTc(rnn_basic, "1:1:1:1", 10), + // mkModelTc(rnn_return_sequences, "1:1:2:1", 10), + // mkModelTc(rnn_return_sequence_with_batch, "2:1:2:1", 10), + // mkModelTc(multi_rnn_return_sequence, "1:1:1:1", 10), + // mkModelTc(multi_rnn_return_sequence_with_batch, "2:1:1:1", 10), + // mkModelTc(gru_basic, "1:1:1:1", 10), + // mkModelTc(gru_return_sequence, "1:1:2:1", 10), + // mkModelTc(gru_return_sequence_with_batch, "2:1:2:1", 10), + // mkModelTc(multi_gru_return_sequence, "1:1:1:1", 10), + // mkModelTc(multi_gru_return_sequence_with_batch, "2:1:1:1", 10) ), [](const testing::TestParamInfo& info){ return std::get<0>(info.param).getName(); }); @@ -1352,26 +1342,22 @@ INSTANTIATE_TEST_CASE_P( /** * @brief Read or save the model before initialize */ -TEST(nntrainerModels, read_save_01_n) { - nntrainer::NeuralNetwork NN; - std::shared_ptr layer = - nntrainer::createLayer(nntrainer::InputLayer::type); - layer->setProperty( - {"input_shape=1:1:62720", "normalization=true", "bias_initializer=zeros"}); - std::shared_ptr layer_node = - std::make_unique(layer); - - EXPECT_NO_THROW(NN.addLayer(layer_node)); - EXPECT_NO_THROW(NN.setProperty({"loss=mse"})); - - EXPECT_THROW(NN.readModel(), std::runtime_error); - EXPECT_THROW(NN.saveModel(), std::runtime_error); - - EXPECT_EQ(NN.compile(), ML_ERROR_NONE); - - EXPECT_THROW(NN.readModel(), std::runtime_error); - EXPECT_THROW(NN.saveModel(), std::runtime_error); -} +// TEST(nntrainerModels, read_save_01_n) { +// nntrainer::NeuralNetwork NN; +// std::shared_ptr layer_node = +// nntrainer::createLayerNode(nntrainer::InputLayer::type, {"input_shape=1:1:62720", "normalization=true"}); +// +// EXPECT_NO_THROW(NN.addLayer(layer_node)); +// EXPECT_NO_THROW(NN.setProperty({"loss=mse"})); +// +// EXPECT_THROW(NN.readModel(), std::runtime_error); +// EXPECT_THROW(NN.saveModel(), std::runtime_error); +// +// EXPECT_EQ(NN.compile(), ML_ERROR_NONE); +// +// EXPECT_THROW(NN.readModel(), std::runtime_error); +// EXPECT_THROW(NN.saveModel(), std::runtime_error); +// } /** * @brief Main gtest -- 2.7.4