diff --git a/modules/dnn/src/layers/recurrent_layers.cpp b/modules/dnn/src/layers/recurrent_layers.cpp index 114487befc..3f9a229516 100644 --- a/modules/dnn/src/layers/recurrent_layers.cpp +++ b/modules/dnn/src/layers/recurrent_layers.cpp @@ -92,6 +92,7 @@ class LSTMLayerImpl CV_FINAL : public LSTMLayer bool produceCellOutput; float forgetBias, cellClip; bool useCellClip, usePeephole; + bool reverse; // If true, go in negative direction along the time axis public: @@ -133,6 +134,7 @@ public: cellClip = params.get("cell_clip", 0.0f); useCellClip = params.get("use_cell_clip", false); usePeephole = params.get("use_peephole", false); + reverse = params.get("reverse", false); allocated = false; outTailShape.clear(); @@ -288,7 +290,18 @@ public: Mat hOutTs = output[0].reshape(1, numSamplesTotal); Mat cOutTs = produceCellOutput ? output[1].reshape(1, numSamplesTotal) : Mat(); - for (int ts = 0; ts < numTimeStamps; ts++) + int tsStart, tsEnd, tsInc; + if (reverse) { + tsStart = numTimeStamps - 1; + tsEnd = -1; + tsInc = -1; + } + else { + tsStart = 0; + tsEnd = numTimeStamps; + tsInc = 1; + } + for (int ts = tsStart; ts != tsEnd; ts += tsInc) { Range curRowRange(ts*numSamples, (ts + 1)*numSamples); Mat xCurr = xTs.rowRange(curRowRange); diff --git a/modules/dnn/test/test_layers.cpp b/modules/dnn/test/test_layers.cpp index 6ce89e3033..71e4c1d80b 100644 --- a/modules/dnn/test/test_layers.cpp +++ b/modules/dnn/test/test_layers.cpp @@ -489,6 +489,55 @@ TEST(Layer_RNN_Test_Accuracy_with_, CaffeRecurrent) normAssert(h_ref, output[0]); } +TEST(Layer_LSTM_Test_Accuracy_, Reverse) +{ + // This handcrafted setup calculates (approximately) the prefix sum of the + // input, assuming the inputs are suitably small. + cv::Mat input(2, 1, CV_32FC1); + input.at(0, 0) = 1e-5f; + input.at(1, 0) = 2e-5f; + + cv::Mat Wx(4, 1, CV_32FC1); + Wx.at(0, 0) = 0.f; // Input gate + Wx.at(1, 0) = 0.f; // Forget gate + Wx.at(2, 0) = 0.f; // Output gate + Wx.at(3, 0) = 1.f; // Update signal + + cv::Mat Wh(4, 1, CV_32FC1); + Wh.at(0, 0) = 0.f; // Input gate + Wh.at(1, 0) = 0.f; // Forget gate + Wh.at(2, 0) = 0.f; // Output gate + Wh.at(3, 0) = 0.f; // Update signal + + cv::Mat bias(4, 1, CV_32FC1); + bias.at(0, 0) = 1e10f; // Input gate - always allows input to c + bias.at(1, 0) = 1e10f; // Forget gate - never forget anything on c + bias.at(2, 0) = 1e10f; // Output gate - always output everything + bias.at(3, 0) = 0.f; // Update signal + + LayerParams lp; + lp.set("reverse", true); + lp.set("use_timestamp_dim", true); + lp.blobs.clear(); + lp.blobs.push_back(Wh); + lp.blobs.push_back(Wx); + lp.blobs.push_back(bias); + + cv::Ptr layer = LSTMLayer::create(lp); + std::vector outputs; + std::vector inputs; + inputs.push_back(input); + runLayer(layer, inputs, outputs); + + ASSERT_EQ(1, outputs.size()); + cv::Mat out = outputs[0]; + ASSERT_EQ(3, out.dims); + ASSERT_EQ(shape(2, 1, 1), shape(out)); + float* data = reinterpret_cast(out.data); + EXPECT_NEAR(std::tanh(1e-5f) + std::tanh(2e-5f), data[0], 1e-10); + EXPECT_NEAR(std::tanh(2e-5f), data[1], 1e-10); +} + class Layer_RNN_Test : public ::testing::Test {