Commit 99d1e44a authored by Vitaliy Lyudvichenko's avatar Vitaliy Lyudvichenko

Updating of RNN layer API and RNN

parent 45010af8
...@@ -172,28 +172,35 @@ namespace dnn ...@@ -172,28 +172,35 @@ namespace dnn
/** Setups learned weights. /** Setups learned weights.
Recurrent-layer behavior on each step is defined by current input x_t, previous state h_t and learned weights as follows: Recurrent-layer behavior on each step is defined by current input @f$ x_t @f$, previous state @f$ h_t @f$ and learned weights as follows:
@f{eqnarray*}{ @f{eqnarray*}{
h_t &= tanh&(W_{hh} h_{t-1} + W_{xh} x_t + b_h), \\ h_t &= tanh&(W_{hh} h_{t-1} + W_{xh} x_t + b_h), \\
o_t &= tanh&(W_{ho} h_t + b_o), o_t &= tanh&(W_{ho} h_t + b_o),
@f} @f}
@param Whh is @f$ W_{hh} @f$ matrix
@param Wxh is @f$ W_{xh} @f$ matrix @param Wxh is @f$ W_{xh} @f$ matrix
@param bh is @f$ b_{h} @f$ vector @param bh is @f$ b_{h} @f$ vector
@param Whh is @f$ W_{hh} @f$ matrix
@param Who is @f$ W_{xo} @f$ matrix @param Who is @f$ W_{xo} @f$ matrix
@param bo is @f$ b_{o} @f$ vector @param bo is @f$ b_{o} @f$ vector
*/ */
virtual void setWeights(const Blob &Whh, const Blob &Wxh, const Blob &bh, const Blob &Who, const Blob &bo) = 0; virtual void setWeights(const Blob &Wxh, const Blob &bh, const Blob &Whh, const Blob &Who, const Blob &bo) = 0;
/** @brief If this flag is set to true then layer will produce @f$ h_t @f$ as second output.
* @details Shape of the second output is the same as first output.
*/
virtual void setProduceHiddenOutput(bool produce = false) = 0;
/** Accepts two inputs @f$x_t@f$ and @f$h_{t-1}@f$ and compute two outputs @f$o_t@f$ and @f$h_t@f$. /** Accepts two inputs @f$x_t@f$ and @f$h_{t-1}@f$ and compute two outputs @f$o_t@f$ and @f$h_t@f$.
@param input could contain inputs @f$x_t@f$ and @f$h_{t-1}@f$. @param input should contain packed input @f$x_t@f$.
@param output should contain outputs @f$o_t@f$ and @f$h_t@f$. @param output should contain output @f$o_t@f$ (and @f$h_t@f$ if setProduceHiddenOutput() is set to true).
@p input[0] should have shape [`T`, `N`, `data_dims`] where `T` and `N` is number of timestamps and number of independent samples of @f$x_t@f$ respectively.
The first input @f$x_t@f$ is required whereas @f$h_{t-1}@f$ is optional. @p output[0] will have shape [`T`, `N`, @f$N_o@f$], where @f$N_o@f$ is number of rows in @f$ W_{xo} @f$ matrix.
If the second input @f$h_{t-1}@f$ isn't specified a layer will use internal @f$h_{t-1}@f$ from the previous calls, at the first call @f$h_{t-1}@f$ will be filled by zeros.
If setProduceHiddenOutput() is set to true then @p output[1] will contain a Blob with shape [`T`, `N`, @f$N_h@f$], where @f$N_h@f$ is number of rows in @f$ W_{hh} @f$ matrix.
*/ */
void forward(std::vector<Blob*> &input, std::vector<Blob> &output); void forward(std::vector<Blob*> &input, std::vector<Blob> &output);
}; };
......
...@@ -40,6 +40,7 @@ void gemmCPU(const Mat &A, const Mat &B, double alpha, Mat &C, double beta, int ...@@ -40,6 +40,7 @@ void gemmCPU(const Mat &A, const Mat &B, double alpha, Mat &C, double beta, int
CV_Assert(A.isContinuous() && B.isContinuous() && C.isContinuous()); CV_Assert(A.isContinuous() && B.isContinuous() && C.isContinuous());
CV_Assert(A.type() == CV_32F || A.type() == CV_64F); CV_Assert(A.type() == CV_32F || A.type() == CV_64F);
CV_Assert(A.type() == B.type() && B.type() == C.type()); CV_Assert(A.type() == B.type() && B.type() == C.type());
CV_Assert(A.data != C.data && B.data != C.data);
if (C.type() == CV_32F) if (C.type() == CV_32F)
{ {
......
...@@ -321,20 +321,28 @@ int LSTMLayer::outputNameToIndex(String outputName) ...@@ -321,20 +321,28 @@ int LSTMLayer::outputNameToIndex(String outputName)
class RNNLayerImpl : public RNNLayer class RNNLayerImpl : public RNNLayer
{ {
int nX, nH, nO, nSamples; int numX, numH, numO;
int numSamples, numTimestamps, numSamplesTotal;
int dtype; int dtype;
Mat Whh, Wxh, bh; Mat Whh, Wxh, bh;
Mat Who, bo; Mat Who, bo;
Mat hPrevInternal, dummyBiasOnes; Mat hCurr, hPrev, dummyBiasOnes;
bool produceH;
public: public:
RNNLayerImpl() RNNLayerImpl()
{ {
type = "RNN"; type = "RNN";
produceH = false;
} }
void setWeights(const Blob &W_hh, const Blob &W_xh, const Blob &b_h, const Blob &W_ho, const Blob &b_o) void setProduceHiddenOutput(bool produce = false)
{
produceH = produce;
}
void setWeights(const Blob &W_xh, const Blob &b_h, const Blob &W_hh, const Blob &W_ho, const Blob &b_o)
{ {
CV_Assert(W_hh.dims() == 2 && W_xh.dims() == 2); CV_Assert(W_hh.dims() == 2 && W_xh.dims() == 2);
CV_Assert(W_hh.size(0) == W_xh.size(0) && W_hh.size(0) == W_hh.size(1) && (int)b_h.total() == W_xh.size(0)); CV_Assert(W_hh.size(0) == W_xh.size(0) && W_hh.size(0) == W_hh.size(1) && (int)b_h.total() == W_xh.size(0));
...@@ -342,9 +350,9 @@ public: ...@@ -342,9 +350,9 @@ public:
CV_Assert(W_ho.size(1) == W_hh.size(1)); CV_Assert(W_ho.size(1) == W_hh.size(1));
blobs.resize(5); blobs.resize(5);
blobs[0] = W_hh; blobs[0] = W_xh;
blobs[1] = W_xh; blobs[1] = b_h;
blobs[2] = b_h; blobs[2] = W_hh;
blobs[3] = W_ho; blobs[3] = W_ho;
blobs[4] = b_o; blobs[4] = b_o;
} }
...@@ -353,72 +361,68 @@ public: ...@@ -353,72 +361,68 @@ public:
{ {
CV_Assert(input.size() >= 1 && input.size() <= 2); CV_Assert(input.size() >= 1 && input.size() <= 2);
Whh = blobs[0].matRefConst(); Wxh = blobs[0].matRefConst();
Wxh = blobs[1].matRefConst(); bh = blobs[1].matRefConst();
bh = blobs[2].matRefConst(); Whh = blobs[2].matRefConst();
Who = blobs[3].matRefConst(); Who = blobs[3].matRefConst();
bo = blobs[4].matRefConst(); bo = blobs[4].matRefConst();
nH = Wxh.rows; numH = Wxh.rows;
nX = Wxh.cols; numX = Wxh.cols;
nO = Who.rows; numO = Who.rows;
CV_Assert(input[0]->size(-1) == Wxh.cols); CV_Assert(input[0]->dims() >= 2);
nSamples = input[0]->total(0, input[0]->dims() - 1); CV_Assert((int)input[0]->total(2) == numX);
BlobShape xShape = input[0]->shape(); CV_Assert(input[0]->type() == CV_32F || input[0]->type() == CV_64F);
BlobShape hShape = xShape; dtype = input[0]->type();
BlobShape oShape = xShape; numTimestamps = input[0]->size(0);
hShape[-1] = nH; numSamples = input[0]->size(1);
oShape[-1] = nO; numSamplesTotal = numTimestamps * numSamples;
if (input.size() == 2)
{
CV_Assert(input[1]->shape() == hShape);
}
else
{
hPrevInternal.create(nSamples, nH, input[0]->type());
hPrevInternal.setTo(0);
}
output.resize(2); hCurr.create(numSamples, numH, dtype);
output[0].create(oShape, input[0]->type()); hPrev.create(numSamples, numH, dtype);
output[1].create(hShape, input[0]->type()); hPrev.setTo(0);
dummyBiasOnes.create(nSamples, 1, bh.type()); dummyBiasOnes.create(numSamples, 1, dtype);
dummyBiasOnes.setTo(1); dummyBiasOnes.setTo(1);
bh = bh.reshape(1, 1); //is 1 x nH mat bh = bh.reshape(1, 1); //is 1 x numH Mat
bo = bo.reshape(1, 1); //is 1 x nO mat bo = bo.reshape(1, 1); //is 1 x numO Mat
reshapeOutput(output);
} }
void forward(std::vector<Blob*> &input, std::vector<Blob> &output) void reshapeOutput(std::vector<Blob> &output)
{ {
Mat xCurr = input[0]->matRefConst(); output.resize((produceH) ? 2 : 1);
Mat hPrev = (input.size() >= 2) ? input[1]->matRefConst() : hPrevInternal; output[0].create(BlobShape(numTimestamps, numSamples, numO), dtype);
Mat oCurr = output[0].matRef(); if (produceH)
Mat hCurr = output[1].matRef(); output[1].create(BlobShape(numTimestamps, numSamples, numH), dtype);
}
//TODO: Check types void forward(std::vector<Blob*> &input, std::vector<Blob> &output)
{
Mat xTs = input[0]->reshaped(BlobShape(numSamplesTotal, numX)).matRefConst();
Mat oTs = output[0].reshaped(BlobShape(numSamplesTotal, numO)).matRef();
Mat hTs = (produceH) ? output[1].reshaped(BlobShape(numSamplesTotal, numH)).matRef() : Mat();
int xsz[] = {nSamples, nX}; for (int ts = 0; ts < numTimestamps; ts++)
int hsz[] = {nSamples, nH}; {
int osz[] = {nSamples, nO}; Range curRowRange = Range(ts * numSamples, (ts + 1) * numSamples);
if (xCurr.dims != 2) xCurr = xCurr.reshape(1, 2, xsz); Mat xCurr = xTs.rowRange(curRowRange);
if (hPrev.dims != 2) hPrev = hPrev.reshape(1, 2, hsz);
if (oCurr.dims != 2) oCurr = oCurr.reshape(1, 2, osz);
if (hCurr.dims != 2) hCurr = hCurr.reshape(1, 2, hsz);
gemmCPU(hPrev, Whh, 1, hCurr, 0, GEMM_2_T); // W_{hh} * h_{prev} gemmCPU(hPrev, Whh, 1, hCurr, 0, GEMM_2_T); // W_{hh} * h_{prev}
gemmCPU(xCurr, Wxh, 1, hCurr, 1, GEMM_2_T); //+W_{xh} * x_{curr} gemmCPU(xCurr, Wxh, 1, hCurr, 1, GEMM_2_T); //+W_{xh} * x_{curr}
gemmCPU(dummyBiasOnes, bh, 1, hCurr, 1); //+bh gemmCPU(dummyBiasOnes, bh, 1, hCurr, 1); //+bh
tanh(hCurr, hCurr); tanh(hCurr, hPrev);
Mat oCurr = oTs.rowRange(curRowRange);
gemmCPU(hPrev, Who, 1, oCurr, 0, GEMM_2_T); // W_{ho} * h_{prev} gemmCPU(hPrev, Who, 1, oCurr, 0, GEMM_2_T); // W_{ho} * h_{prev}
gemmCPU(dummyBiasOnes, bo, 1, oCurr, 1); //+b_o gemmCPU(dummyBiasOnes, bo, 1, oCurr, 1); //+b_o
tanh(oCurr, oCurr); tanh(oCurr, oCurr);
if (input.size() < 2) //save h_{prev} if (produceH)
hCurr.copyTo(hPrevInternal); hPrev.copyTo(hTs.rowRange(curRowRange));
}
} }
}; };
......
...@@ -254,7 +254,7 @@ TEST_F(Layer_LSTM_Test, get_set_test) ...@@ -254,7 +254,7 @@ TEST_F(Layer_LSTM_Test, get_set_test)
EXPECT_EQ(1, layer->outputNameToIndex("c")); EXPECT_EQ(1, layer->outputNameToIndex("c"));
} }
TEST(Layer_LSTM_Test_Accuracy_Reference_with_, CaffeRecurrent) TEST(Layer_LSTM_Test_Accuracy_with_, CaffeRecurrent)
{ {
Ptr<LSTMLayer> layer = LSTMLayer::create(); Ptr<LSTMLayer> layer = LSTMLayer::create();
...@@ -263,73 +263,70 @@ TEST(Layer_LSTM_Test_Accuracy_Reference_with_, CaffeRecurrent) ...@@ -263,73 +263,70 @@ TEST(Layer_LSTM_Test_Accuracy_Reference_with_, CaffeRecurrent)
Blob b = blobFromNPY(_tf("lstm.prototxt.w_1.npy")); Blob b = blobFromNPY(_tf("lstm.prototxt.w_1.npy"));
layer->setWeights(Wh, Wx, b); layer->setWeights(Wh, Wx, b);
Blob inp = blobFromNPY(_tf("blob.npy")); Blob inp = blobFromNPY(_tf("recurrent.input.npy"));
std::vector<Blob> inputs(1, inp), outputs; std::vector<Blob> inputs(1, inp), outputs;
runLayer(layer, inputs, outputs); runLayer(layer, inputs, outputs);
Blob &h_t_gathered = outputs[0];
Blob h_t_reference = blobFromNPY(_tf("lstm.prototxt.h_1.npy")); Blob h_t_reference = blobFromNPY(_tf("lstm.prototxt.h_1.npy"));
normAssert(h_t_reference, outputs[0]);
}
TEST(Layer_RNN_Test_Accuracy_with_, CaffeRecurrent)
{
Ptr<RNNLayer> layer = RNNLayer::create();
layer->setWeights(
blobFromNPY(_tf("rnn.prototxt.w_0.npy")),
blobFromNPY(_tf("rnn.prototxt.w_1.npy")),
blobFromNPY(_tf("rnn.prototxt.w_2.npy")),
blobFromNPY(_tf("rnn.prototxt.w_3.npy")),
blobFromNPY(_tf("rnn.prototxt.w_4.npy")) );
normAssert(h_t_reference, h_t_gathered); std::vector<Blob> output, input(1, blobFromNPY(_tf("recurrent.input.npy")));
runLayer(layer, input, output);
Blob h_ref = blobFromNPY(_tf("rnn.prototxt.h_1.npy"));
normAssert(h_ref, output[0]);
} }
class Layer_RNN_Test : public ::testing::Test class Layer_RNN_Test : public ::testing::Test
{ {
public: public:
int Nx, Nh, No; int nX, nH, nO, nT, nS;
Blob Whh, Wxh, bh, Who, bo; Blob Whh, Wxh, bh, Who, bo;
Ptr<RNNLayer> layer; Ptr<RNNLayer> layer;
std::vector<Blob> inputs, outputs; std::vector<Blob> inputs, outputs;
std::vector<Blob*> inputsPtr;
Layer_RNN_Test(int _Nx = 31, int _Nh = 64, int _No = 100) Layer_RNN_Test()
{ {
Nx = _Nx; nT = 3;
Nh = _Nh; nS = 5;
No = _No; nX = 31;
nH = 64;
Whh = Blob(BlobShape(Nh, Nh)); nO = 100;
Wxh = Blob(BlobShape(Nh, Nx));
bh = Blob(BlobShape(Nh, 1)); Whh = Blob(BlobShape(nH, nH));
Who = Blob(BlobShape(No, Nh)); Wxh = Blob(BlobShape(nH, nX));
bo = Blob(BlobShape(No, 1)); bh = Blob(BlobShape(nH, 1));
Who = Blob(BlobShape(nO, nH));
bo = Blob(BlobShape(nO, 1));
layer = RNNLayer::create(); layer = RNNLayer::create();
layer->setWeights(Whh, Wxh, bh, Who, bo); layer->setProduceHiddenOutput(true);
} layer->setWeights(Wxh, bh, Whh, Who, bo);
void allocateAndForward()
{
inputsPtr.clear();
for (size_t i = 0; i < inputs.size(); i++)
inputsPtr.push_back(&inputs[i]);
layer->allocate(inputsPtr, outputs);
layer->forward(inputsPtr, outputs);
} }
}; };
TEST_F(Layer_RNN_Test, BasicTest_1) TEST_F(Layer_RNN_Test, get_set_test)
{
inputs.push_back(Blob(BlobShape(1, 2, 3, Nx)));
allocateAndForward();
EXPECT_EQ(outputs.size(), 2);
EXPECT_EQ(outputs[0].shape(), BlobShape(1, 2, 3, No));
EXPECT_EQ(outputs[1].shape(), BlobShape(1, 2, 3, Nh));
}
TEST_F(Layer_RNN_Test, BasicTest_2)
{ {
inputs.push_back(Blob(BlobShape(1, 2, 3, Nx))); inputs.push_back(Blob(BlobShape(nT, nS, 1, nX)));
inputs.push_back(Blob(BlobShape(1, 2, 3, Nh))); runLayer(layer, inputs, outputs);
allocateAndForward();
EXPECT_EQ(outputs.size(), 2); EXPECT_EQ(outputs.size(), 2);
EXPECT_EQ(outputs[0].shape(), BlobShape(1, 2, 3, No)); EXPECT_EQ(outputs[0].shape(), BlobShape(nT, nS, nO));
EXPECT_EQ(outputs[1].shape(), BlobShape(1, 2, 3, Nh)); EXPECT_EQ(outputs[1].shape(), BlobShape(nT, nS, nH));
} }
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment