Skip to content

Commit ae84ad4

Browse files
kparichayjijoongmoon
authored andcommitted
[resnet/test] Add resnet test for models unittest
This patch adds resnet models unittest for resnet18. The verification has been done offline for 2 iterations for the output of all layers with precision of 1.1e-4. Derivaitves and gradients have higher error because of relu: when some value is close to 0, it can be positive or negative with some error (of the order of e-7). Although this error is way within the error limit. however, this exacerbates the error in backwarding where derivatives (which are significant in values) can flow if the relu value was over 0, and not flow if under zero. This is manageable in smaller models but difficult to avoid in unittests for larger models. Other bug fixes in this patch: - max error reported by unittest_nntrainer_models has been fixed - error reporting now includes layer type as well - ModelTestOption MINIMUM has been renamed to NO_THROW_RUN Signed-off-by: Parichay Kapoor <[email protected]>
1 parent e1fba2d commit ae84ad4

File tree

1 file changed

+121
-20
lines changed

1 file changed

+121
-20
lines changed

test/unittest/unittest_nntrainer_models.cpp

Lines changed: 121 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,10 @@ void verify(const nntrainer::Tensor &actual, const nntrainer::Tensor &expected,
6666
std::cout << "\033[1;33mdifference\033[0m " << diff;
6767
std::cout << "number of data: " << diff.size() << std::endl;
6868
std::cout << "\033[4;33mMAX DIFF: "
69-
<< *std::max_element(diff_data, diff_data + diff.size())
69+
<< *std::max_element(diff_data, diff_data + diff.size(),
70+
[](auto v1, auto v2) {
71+
return std::fabs(v1) < std::fabs(v2);
72+
})
7073
<< "\033[0m\n";
7174
}
7275
std::stringstream ss;
@@ -199,12 +202,19 @@ class NodeWatcher {
199202
*
200203
* @return LayerType
201204
*/
202-
std::string getNodeType() { return node->getType(); }
205+
std::string getType() { return node->getType(); }
206+
207+
/**
208+
* @brief get Node type
209+
*
210+
* @return LayerType
211+
*/
212+
std::string getName() { return node->getName(); }
203213

204214
/**
205215
* @brief is loss type
206216
*
207-
* @return true if loss type node, else false\
217+
* @return true if loss type node, else false
208218
*/
209219
bool isLossType() { return node->requireLabel(); }
210220

@@ -275,28 +285,28 @@ void NodeWatcher::verifyGrad(const std::string &error_msg) {
275285

276286
void NodeWatcher::forward(int iteration, bool verify_forward) {
277287
std::stringstream ss;
278-
ss << "forward failed at " << node->getName() << " at iteration "
279-
<< iteration;
288+
ss << "forward failed at " << node->getName() << ", " << node->getType()
289+
<< " at iteration " << iteration;
280290
std::string err_msg = ss.str();
281291

282292
std::vector<nntrainer::Tensor> out;
283293
for (unsigned int idx = 0; idx < node->getNumOutputs(); idx++) {
284294
out.push_back(node->getOutput(idx));
285295
}
286296

287-
if (verify_forward && getNodeType() != nntrainer::MultiOutLayer::type)
297+
if (verify_forward && getType() != nntrainer::MultiOutLayer::type)
288298
verify(out, expected_output, err_msg + " at output");
289299
}
290300

291301
void NodeWatcher::backward(int iteration, bool verify_deriv, bool verify_grad) {
292302

293-
if (getNodeType() == nntrainer::MultiOutLayer::type) {
303+
if (getType() == nntrainer::MultiOutLayer::type) {
294304
return;
295305
}
296306

297307
std::stringstream ss;
298-
ss << "backward failed at " << node->getName() << " at iteration "
299-
<< iteration;
308+
ss << "backward failed at " << node->getName() << ", " << node->getType()
309+
<< " at iteration " << iteration;
300310
std::string err_msg = ss.str();
301311

302312
std::vector<nntrainer::Tensor> out;
@@ -510,7 +520,7 @@ GraphWatcher::prepareData(std::ifstream &f,
510520

511521
void GraphWatcher::readIteration(std::ifstream &f) {
512522
for (auto &i : nodes) {
513-
if (i.getNodeType() == nntrainer::MultiOutLayer::type) {
523+
if (i.getType() == nntrainer::MultiOutLayer::type) {
514524
continue;
515525
}
516526

@@ -534,8 +544,7 @@ typedef enum {
534544
COMPARE = 1 << 0, /**< Set this to compare the numbers */
535545
SAVE_AND_LOAD_INI = 1 << 1, /**< Set this to check if saving and constructing
536546
a new model works okay (without weights) */
537-
538-
MINIMUM = 0, /**< Minimum */
547+
NO_THROW_RUN = 0, /**< no comparison, only validate execution without throw */
539548
ALL = COMPARE | SAVE_AND_LOAD_INI /**< Set every option */
540549
} ModelTestOption;
541550

@@ -557,7 +566,7 @@ class nntrainerModelTest
557566
nntrainerModelTest() :
558567
iteration(0),
559568
name(""),
560-
options(ModelTestOption::MINIMUM) {}
569+
options(ModelTestOption::NO_THROW_RUN) {}
561570
virtual void SetUp() {
562571
auto param = GetParam();
563572

@@ -580,7 +589,9 @@ class nntrainerModelTest
580589
int getIteration() { return iteration; };
581590
nntrainer::TensorDim getLabelDim() { return label_dim; }
582591

583-
bool shouldCompare() { return options & ModelTestOption::COMPARE; }
592+
bool shouldCompare() {
593+
return (options & ModelTestOption::COMPARE) == ModelTestOption::COMPARE;
594+
}
584595
bool shouldSaveLoadIniTest() {
585596
return options & ModelTestOption::SAVE_AND_LOAD_INI;
586597
}
@@ -1411,6 +1422,93 @@ INI multiple_output_model(
14111422
}
14121423
);
14131424

1425+
/**
1426+
* @brief helper function to make model testcase
1427+
*
1428+
* @param nntrainer::TensorDim label dimension
1429+
* @param int Iteration
1430+
* @param options options
1431+
*/
1432+
auto mkResNet18Tc(const unsigned int iteration,
1433+
ModelTestOption options = ModelTestOption::ALL) {
1434+
unsigned int batch_size = 2;
1435+
unsigned int num_class = 100;
1436+
unsigned int count = 0;
1437+
nntrainer::IniWrapper::Sections layers;
1438+
1439+
/** get unique name for a layer */
1440+
auto getName = [&count]() -> std::string {
1441+
if (count == 21)
1442+
std::cout << "mimatch" << std::endl;
1443+
return "layer" + std::to_string(++count);
1444+
};
1445+
auto getPreviousName = [&count]() -> std::string { return "layer" + std::to_string(count); };
1446+
1447+
/** add blocks */
1448+
auto addBlock = [&count, &layers, &getName, &getPreviousName] (
1449+
unsigned int filters, unsigned int kernel_size, bool downsample) {
1450+
std::string filter_str = "filters=" + std::to_string(filters);
1451+
std::string kernel_str = "kernel_size=" + std::to_string(kernel_size) + "," + std::to_string(kernel_size);
1452+
std::string kernel1_str = "kernel_size=1,1";
1453+
std::string stride1_str = "stride=1,1";
1454+
std::string stride2_str = "stride=2,2";
1455+
std::string padding_str = "padding=same";
1456+
std::string input_name = getPreviousName();
1457+
std::string in_layer_str = "input_layers=" + input_name;
1458+
std::string stride_str = stride1_str;
1459+
if (downsample)
1460+
stride_str = stride2_str;
1461+
1462+
/** skip connection */
1463+
std::string b1_name = input_name;
1464+
if (downsample) {
1465+
b1_name = getName();
1466+
layers.push_back(I(b1_name) + conv_base + filter_str +
1467+
kernel1_str + stride_str + padding_str + in_layer_str);
1468+
}
1469+
1470+
/** main connection */
1471+
layers.push_back(I(getName()) + conv_base + filter_str +
1472+
kernel_str + stride_str + padding_str + in_layer_str);
1473+
layers.push_back(I(getName()) + bn_base);
1474+
layers.push_back(I(getName()) + relu_base);
1475+
std::string a1_name = getName();
1476+
layers.push_back(I(a1_name) + conv_base + filter_str +
1477+
kernel_str + stride1_str + padding_str);
1478+
1479+
/** add the two connections */
1480+
layers.push_back(I(getName()) + "type=addition" + ("input_layers=" + b1_name + "," + a1_name));
1481+
layers.push_back(I(getName()) + bn_base);
1482+
layers.push_back(I(getName()) + relu_base);
1483+
};
1484+
1485+
layers.push_back(nn_base + ("loss=cross | batch_size = " + std::to_string(batch_size)));
1486+
layers.push_back(sgd_base + "learning_rate = 0.1");
1487+
/** prefix for resnet model */
1488+
layers.push_back(I(getName()) + input_base + "input_shape = 3:32:32");
1489+
layers.push_back(I(getName()) + conv_base + "kernel_size=3,3 | filters=64 | padding=same");
1490+
layers.push_back(I(getName()) + bn_base);
1491+
layers.push_back(I(getName()) + relu_base);
1492+
/** add all the blocks */
1493+
addBlock(64, 3, false);
1494+
addBlock(64, 3, false);
1495+
addBlock(128, 3, true);
1496+
addBlock(128, 3, false);
1497+
addBlock(256, 3, true);
1498+
addBlock(256, 3, false);
1499+
addBlock(512, 3, true);
1500+
addBlock(512, 3, false);
1501+
/** add suffix for resnet model */
1502+
layers.push_back(I(getName()) + pooling_base + "pooling = average | pool_size=4,4");
1503+
layers.push_back(I(getName()) + "type=flatten");
1504+
layers.push_back(I(getName()) + fc_base + "unit=100");
1505+
layers.push_back(I(getName()) + softmax_base);
1506+
1507+
return std::tuple<const nntrainer::IniWrapper, const nntrainer::TensorDim,
1508+
const unsigned int, ModelTestOption>(
1509+
nntrainer::IniWrapper("ResNet18", layers), nntrainer::TensorDim({batch_size, 1,1, num_class}), iteration, options);
1510+
}
1511+
14141512
INSTANTIATE_TEST_CASE_P(
14151513
nntrainerModelAutoTests, nntrainerModelTest, ::testing::ValuesIn(
14161514
{
@@ -1435,7 +1533,7 @@ INSTANTIATE_TEST_CASE_P(
14351533
mkModelTc(conv_uneven_strides3, "3:1:1:10", 10, ModelTestOption::ALL),
14361534
mkModelTc(conv_bn, "3:1:1:10", 10, ModelTestOption::ALL),
14371535
mkModelTc(conv_same_padding_multi_stride, "3:1:1:10", 10, ModelTestOption::ALL),
1438-
mkModelTc(conv_no_loss, "3:1:1:10", 1, ModelTestOption::MINIMUM),
1536+
mkModelTc(conv_no_loss, "3:1:1:10", 1, ModelTestOption::NO_THROW_RUN),
14391537

14401538
/**< single pooling layer test */
14411539
mkModelTc(pooling_max_same_padding, "3:1:1:10", 10, ModelTestOption::ALL),
@@ -1453,17 +1551,17 @@ INSTANTIATE_TEST_CASE_P(
14531551

14541552
/**< augmentation layer */
14551553
#if defined(ENABLE_DATA_AUGMENTATION_OPENCV)
1456-
mkModelTc(preprocess_translate, "3:1:1:10", 10, ModelTestOption::MINIMUM),
1554+
mkModelTc(preprocess_translate, "3:1:1:10", 10, ModelTestOption::NO_THROW_RUN),
14571555
#endif
1458-
mkModelTc(preprocess_flip_validate, "3:1:1:10", 10, ModelTestOption::MINIMUM),
1556+
mkModelTc(preprocess_flip_validate, "3:1:1:10", 10, ModelTestOption::NO_THROW_RUN),
14591557

14601558
/**< Addition test */
14611559
mkModelTc(addition_resnet_like, "3:1:1:10", 10, ModelTestOption::COMPARE), // Todo: Enable option to ALL
14621560

14631561
/// #1192 time distribution inference bug
1464-
mkModelTc(fc_softmax_mse_distribute, "3:1:5:3", 1, ModelTestOption::MINIMUM),
1465-
mkModelTc(fc_softmax_cross_distribute, "3:1:5:3", 1, ModelTestOption::MINIMUM),
1466-
mkModelTc(fc_sigmoid_cross_distribute, "3:1:5:3", 1, ModelTestOption::MINIMUM),
1562+
mkModelTc(fc_softmax_mse_distribute, "3:1:5:3", 1, ModelTestOption::NO_THROW_RUN),
1563+
mkModelTc(fc_softmax_cross_distribute, "3:1:5:3", 1, ModelTestOption::NO_THROW_RUN),
1564+
mkModelTc(fc_sigmoid_cross_distribute, "3:1:5:3", 1, ModelTestOption::NO_THROW_RUN),
14671565
mkModelTc(lstm_basic, "1:1:1:1", 10, ModelTestOption::ALL),
14681566
mkModelTc(lstm_return_sequence, "1:1:2:1", 10, ModelTestOption::ALL),
14691567
mkModelTc(lstm_return_sequence_with_batch, "2:1:2:1", 10, ModelTestOption::ALL),
@@ -1482,6 +1580,9 @@ INSTANTIATE_TEST_CASE_P(
14821580

14831581
/**< multi output test */
14841582
mkModelTc(multiple_output_model, "3:1:1:10", 10, ModelTestOption::COMPARE) // Todo: Enable option to ALL
1583+
/** resnet model */
1584+
// this must match training (verify only forwarding output values) for 2 iterations with tolerance 1.2e-4
1585+
// mkResNet18Tc(2, ModelTestOption::COMPARE)
14851586
}
14861587
), [](const testing::TestParamInfo<nntrainerModelTest::ParamType>& info){
14871588
return std::get<0>(info.param).getName();

0 commit comments

Comments
 (0)