/* * ****************************************************************************** * * * * * * This program and the accompanying materials are made available under the * * terms of the Apache License, Version 2.0 which is available at * * https://www.apache.org/licenses/LICENSE-2.0. * * * * See the NOTICE file distributed with this work for additional * * information regarding copyright ownership. * * Unless required by applicable law or agreed to in writing, software * * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * * License for the specific language governing permissions and limitations * * under the License. * * * * SPDX-License-Identifier: Apache-2.0 * ***************************************************************************** */ // // @author Yurii Shyrma (iuriish@yahoo.com), created on 15.02.2018, Alex Black // #include #if NOT_EXCLUDED(OP_gru) // implementation of gated Recurrent Unit cell // (cf. https://arxiv.org/abs/1406.1078). // Kyunghyun Cho, Bart van Merrienboer, Caglar Gulcehre, Dzmitry Bahdanau, Fethi Bougares, Holger Schwenk, Yoshua Bengio // "Learning Phrase Representations using RNN Encoder-Decoder for Statistical Machine Translation" #include #include #include #include namespace sd { namespace ops { namespace helpers { ////////////////////////////////////////////////////////////////////////// void gruCell(sd::LaunchContext* context, NDArray* x, NDArray* hI, NDArray* W, NDArray* Wc, NDArray* b, NDArray* bc, NDArray* r, NDArray* u, NDArray* c, NDArray* h) { // Inputs: // x input [bS, nIn], nIn - input size // hI previous cell output [bS, nOut], that is at previous time step t-1, nOut - number of units // W RU weights - [nIn+nOut, 2*nOut] - reset and update gates // Wc C weights - [nIn+nOut, nOut] - cell gate // b r and u biases, [2*nOut] - reset and update gates // bc c biases, [nOut] - cell gate // Outputs: // r Reset gate output [bS, nOut] // u Update gate output [bS, nOut] // c Cell gate output [bS, nOut] // h current cell output [bS, nOut] /***************************************************************************************/ /************************ THIS IS NOT OPTIMIZED CODE ***********************************/ /** however it is more math-friendly and convenient for backprop formulas derivation) **/ const int bS = x->sizeAt(0); const int nIn = x->sizeAt(1); const int nOut = hI->sizeAt(1); NDArray *Wrx = (*W)({0, nIn, 0, nOut}); // [nIn, nOut] NDArray *Wux = (*W)({0, nIn, nOut, 2 * nOut}); // [nIn, nOut] NDArray *Wrh = (*W)({nIn, nIn + nOut, 0, nOut}); // [nOut, nOut] NDArray *Wuh = (*W)({nIn, nIn + nOut, nOut, 2 * nOut}); // [nOut, nOut] NDArray *Wcx = (*Wc)({0, nIn, 0, 0}); // reset cell weights [nIn, nOut] NDArray *Wch = (*Wc)({nIn, nIn + nOut, 0, 0}); // updates cell weights [nOut, nOut] NDArray *br = (*b)({0, nOut}); // [nOut] NDArray *bu = (*b)({nOut, 2 * nOut}); // [nOut] // × means matrix multiplication // * means element-wise product or so called Hadamard product // r = sigmoid(x × Wrx + hI × Wrh + br) auto xWrx = mmul(*x, *Wrx); auto hIWrh = mmul(*hI, *Wrh); auto* sum1 = *xWrx + *hIWrh; auto* rAssign = (*sum1) + (*br); delete sum1; delete hIWrh; r->assign(rAssign); // [bS, nIn] × [nIn, nOut] + [bS, nOut] × [nOut, nOut] + [nOut] = [bS, nOut] delete rAssign; r->applyTransform(transform::Sigmoid, r); // u = sigmoid(x × Wux + hI × Wuh + bu) auto xWux = mmul(*x, *Wux); auto hIWuh = mmul(*hI, *Wuh); auto* sum2 = *xWux + *hIWuh; auto* uAssign = (*sum2) + (*bu); delete sum2; delete xWux; u->assign(uAssign); // [bS, nIn] × [nIn, nOut] + [bS, nOut] × [nOut, nOut] + [nOut] = [bS, nOut] delete uAssign; u->applyTransform(transform::Sigmoid, u); // c = tanh(x × Wcx + (r * hI) × Wch + bc) auto* rTimesHi = (*r) * (*hI); auto xWcx = mmul(*x, *Wcx); auto rTimesHiWch = mmul(*rTimesHi, *Wch); delete rTimesHi; auto* sum3 = *xWcx + *rTimesHiWch; auto* cAssign = (*sum3) + (*bc); delete sum3; delete xWcx; delete rTimesHiWch; c->assign(cAssign); // [bS, nIn] × [nIn, nOut] + [bS, nOut] × [nOut, nOut] + [nOut] = [bS, nOut] delete cAssign; c->applyTransform(transform::Tanh, c); // h = (1 - u) * c + u * hI auto* uTimesHi = (*u) * (*hI); auto* oneMinusU = 1.f - (*u); auto* oneMinusUTimesC = (*oneMinusU) * (*c); delete oneMinusU; auto* hAssign = (*uTimesHi) + (*oneMinusUTimesC); delete uTimesHi; delete oneMinusUTimesC; h->assign(hAssign); delete hAssign; delete Wrx; delete Wux; delete Wrh; delete Wuh; delete Wcx; delete Wch; delete br; delete bu; } ////////////////////////////////////////////////////////////////////////// void gruCell(NDArray* x, NDArray* hI, NDArray* Wx, NDArray* Wh, NDArray* b, NDArray* gates, NDArray* h, bool linearBeforeReset) { if(linearBeforeReset) { THROW_EXCEPTION("GRU: Linear before reset not implemented. Please set to false."); } // Inputs: // x input [bS, nIn] // hI previous cell output [bS, nOut], that is at previous time step t-1 // Wx weights for x - [nIn, 3*nOut] // Wh weights for h - [nOut, 3*nOut] // b biases [3*nOut] // 3*nOut means following sequence: reset, update, cell // Outputs: // gates [bS, 3*nOut] = reset gate [bS, nOut] + update gate [bS, nOut] + cell gate [bS, nOut] // h current cell output [bS, nOut] // formulas: // zr = x × Wxr + hI × Whr + br // zu = x × Wxu + hI × Whu + bu // r = sigmoid(zr) // u = sigmoid(zu) // zc = x × Wxc + (r * hI) × Whc + bc // c = tanh(zc) // h = (1-u)*c + u*hI const int bS = x->sizeAt(0); const int nIn = x->sizeAt(1); const int nOut = hI->sizeAt(1); NDArray *gatesULike = gates->ulike(); NDArray temp = *gatesULike; MmulHelper::mmul(x, Wx, &temp); // [bS, nIn] × [nIn, 3*nOut] = [bS, 3*nOut] temp += *b; MmulHelper::mmul(hI, Wh, gates); // [bS, nOut] × [nOut, 3*nOut] = [bS, 3*nOut] NDArray *ru = (*gates)({0, 0, 0, 2 * nOut}); // [bS, 2*nOut] NDArray *r = (*gates)({0, 0, 0, nOut}); // [bS, nOut] NDArray *u = (*gates)({0, 0, nOut, 2 * nOut}); // [bS, nOut] NDArray *c = (*gates)({0, 0, 2 * nOut, 3 * nOut}); // [bS, nOut] NDArray *tempView1 = temp({0, 0, 0, 2 * nOut}); NDArray *tempView2 = temp({0, 0, 2 * nOut, 3 * nOut}); // reset and update gates *ru += *tempView1; ru->applyTransform(transform::Sigmoid, ru); // cell gate auto* cTimesR = (*c) * (*r); auto* cAssign = (*cTimesR) + (*tempView2); delete cTimesR; c->assign(cAssign); delete cAssign; c->applyTransform(transform::Tanh, c); // h = (1-u)*c + u*hI auto* uTimesHi = (*u) * (*hI); auto* oneMinusU = 1.f - (*u); auto* oneMinusUTimesC = (*oneMinusU) * (*c); delete oneMinusU; auto* hAssign = (*uTimesHi) + (*oneMinusUTimesC); delete uTimesHi; delete oneMinusUTimesC; h->assign(hAssign); delete hAssign; delete gatesULike; delete ru; delete r; delete u; delete c; delete tempView1; delete tempView2; } ////////////////////////////////////////////////////////////////////////// void gruTimeLoop(sd::LaunchContext* context, NDArray* x, NDArray* hI, NDArray* Wx, NDArray* Wh, NDArray* b, NDArray* h, bool linearBeforeReset) { // sL means time steps // x input [sL, bS, nIn] // hI initial cell output (at time step = 0) [bS, nOut] // Wx input-to-hidden weights, [nIn, 3*nOut] // Wh hidden-to-hidden weights, [nOut, 3*nOut] // b biases, [3*nOut] // h cell outputs at each time step [sL, bS, nOut] const int sL = x->sizeAt(0); const int bS = x->sizeAt(1); const int nOut = hI->sizeAt(1); std::vector shape = {bS, 3 * nOut}; NDArray gates(h->ordering(), shape, h->dataType(), context); auto xSet = x->allTensorsAlongDimension({1, 2}); // sub-arrays with shape [bS, nIn] auto hSet = h->allTensorsAlongDimension({1, 2}); // sub-arrays with shape [bS, nOut] // time loop for (int t = 0; t < sL; ++t) { gruCell(xSet.at(t), t == 0 ? hI : hSet.at(t - 1), Wx, Wh, b, &gates, hSet.at(t), linearBeforeReset); } } ////////////////////////////////////////////////////////////////////////// void gruCellBp(sd::LaunchContext* context, NDArray* x, NDArray* hLast, NDArray* W, NDArray* Wc, NDArray* b, NDArray* bc, NDArray* dLdr, NDArray* dLdu, NDArray* dLdc, NDArray* dLdh, NDArray* dLdx, NDArray* dLdhLast, NDArray* dLdW, NDArray* dLdWc, NDArray* dLdb, NDArray* dLdbc) { // Inputs: // x input [bS, iS] // hLast previous cell output [bS, nU], that is at previous time step t-1 // W weights - [iS+nU, 2*nU] - reset and update gates // Wc C weights - [iS+nU, nU] - cell gate // b r and u biases, [2*nU] - reset and update gates // bc c biases, [nU] - cell gate // dLdr gradient wrt reset gate, [bS, nU] // dLdu gradient wrt update gate, [bS, nU] // dLdc gradient wrt cell state, [bS, nU] // dLdh gradient wrt current cell output, [bS, nU] // Outputs: // dLdx gradient wrt x, [bS, iS], // dLdhLast gradient wrt hLast, [bS, nU] // dLdW gradient wrt W, [iS+nU, 2*nU] // dLdWc gradient wrt Wc, [iS+nU, nU] // dLdb gradient wrt bru [2*nU] // dLdbc gradient wrt bc [nU] // * means element-wise product or so called Hadamard product // × means matrix multiplication /************************************************************************************************/ /******************************* THIS IS NOT OPTIMIZED CODE *************************************/ /*** aim is to have math-readable code in order to keep track of backprop formulas derivation ***/ const int bS = x->sizeAt(0); const int iS = x->sizeAt(1); const int nU = hLast->sizeAt(1); NDArray *xT = x->transpose(); // [iS, bS] NDArray *hLastT = hLast->transpose(); // [nU, bS] NDArray *Wrx = (*W)({0, iS, 0, nU}); // [iS, nU] NDArray *Wux = (*W)({0, iS, nU, 2 * nU}); // [iS, nU] NDArray *Wrh = (*W)({iS, iS + nU, 0, nU}); // [nU, nU] NDArray *Wuh = (*W)({iS, iS + nU, nU, 2 * nU}); // [nU, nU] NDArray *Wcx = (*Wc)({0, iS, 0, 0}); // reset cell weights [iS, nU] NDArray *Wch = (*Wc)({iS, iS + nU, 0, 0}); // updates cell weights [nU, nU] NDArray *br = (*b)({0, nU}); // [nU] NDArray *bu = (*b)({nU, 2 * nU}); // [nU] NDArray *WrxT = Wrx->transpose(); // [nU, iS] NDArray *WuxT = Wux->transpose(); // [nU, iS] NDArray *WrhT = Wrh->transpose(); // [nU, nU] NDArray *WuhT = Wuh->transpose(); // [nU, nU] NDArray *WcxT = Wcx->transpose(); // [nU, iS] NDArray *WchT = Wch->transpose(); // [nU, nU] NDArray *dLdWrx = (*dLdW)({0, iS, 0, nU}); // [iS, nU] NDArray *dLdWux = (*dLdW)({0, iS, nU, 2 * nU}); // [iS, nU] NDArray *dLdWrh = (*dLdW)({iS, iS + nU, 0, nU}); // [nU, nU] NDArray *dLdWuh = (*dLdW)({iS, iS + nU, nU, 2 * nU}); // [nU, nU] NDArray *dLdWcx = (*dLdWc)({0, iS, 0, 0}); // [iS, nU] NDArray *dLdWch = (*dLdWc)({iS, iS + nU, 0, 0}); // [nU, nU] NDArray *dLdbr = (*dLdb)({0, nU}); // [nU] NDArray *dLdbu = (*dLdb)({nU, 2 * nU}); // [nU] // ***** feed forward step ***** // // r = sigmoid(x × Wrx + hLast × Wrh + br) auto xWrx = mmul(*x, *Wrx); auto hLastWrh = mmul(*hLast, *Wrh); auto* sum1 = *xWrx + *hLastWrh; auto* rTemp = (*sum1) + (*br); delete sum1; NDArray r = *rTemp; // [bS, iS] × [iS, nU] + [bS, nU] × [nU, nU] + [nU] = [bS, nU] delete rTemp; r.applyTransform(transform::Sigmoid, &r); // u = sigmoid(x × Wux + hLast × Wuh + bu) auto xWux = mmul(*x, *Wux); auto hLastWuh = mmul(*hLast, *Wuh); auto* sum2 = *xWux + *hLastWuh; auto* uTemp = (*sum2) + (*bu); delete sum2; NDArray u = *uTemp; // [bS, iS] × [iS, nU] + [bS, nU] × [nU, nU] + [nU] = [bS, nU] delete uTemp; delete xWux; u.applyTransform(transform::Sigmoid, &u); // c = tanh(x × Wcx + (r * hLast) × Wch + bc) auto* rTimesHLast2 = r * (*hLast); auto xWcx = mmul(*x, *Wcx); auto rTimesHLast2Wch = mmul(*rTimesHLast2, *Wch); delete rTimesHLast2; auto* sum3 = *xWcx + *rTimesHLast2Wch; auto* cTemp = (*sum3) + (*bc); delete sum3; delete xWcx; delete rTimesHLast2Wch; NDArray c = *cTemp; // [bS, iS] × [iS, nU] + [bS, nU] × [nU, nU] + [nU] = [bS, nU] delete cTemp; c.applyTransform(transform::Tanh, &c); // h = (1 - u) * c + u * hPrev // ***** back prop step ***** // auto* hLastMinusC = (*hLast) - c; auto* oneMinusU = 1.f - u; auto* dudZu = u * (*oneMinusU); delete oneMinusU; auto* oneMinusR = 1.f - r; auto* drdZr = r * (*oneMinusR); delete oneMinusR; auto* cSquared = c * c; auto* oneMinusCSquared = 1.f - (*cSquared); delete cSquared; auto dcdZc = *oneMinusCSquared; delete oneMinusCSquared; auto* dLdZc = (*dLdc) * dcdZc; auto* dLdZu = (*dLdu) * (*dudZu); delete dudZu; auto* dLdZr = (*dLdr) * (*drdZr); delete drdZr; NDArray *dhdc = 1.f - u; // [bS, nU] NDArray dhdu = *hLastMinusC; // [bS, nU] delete hLastMinusC; // dLdx = dLdZu × WuxT + dLdZc × WcxT + dLdZr × WrxT auto dLdZuWuxT = mmul(*dLdZu, *WuxT); auto dLdZcWcxT = mmul(*dLdZc, *WcxT); auto dLdZrWrxT = mmul(*dLdZr, *WrxT); auto* temp1 = *dLdZuWuxT + *dLdZcWcxT; auto* dLdxTemp = (*temp1) + *dLdZrWrxT; delete temp1; delete dLdZuWuxT; delete dLdZcWcxT; delete dLdZrWrxT; dLdx->assign(dLdxTemp); // [bS, iS] delete dLdxTemp; // dldZTimeR = dLdZc * r auto* dldZTimeR = (*dLdZc) * r; // dLdhLast = dLdh * u + dLdZu × WuhT + dldZTimeR × WchT + dLdZr × WrhT auto* dLdhTimesU = (*dLdh) * u; auto dLdZuWuhT = mmul(*dLdZu, *WuhT); auto dldZTimeRWchT = mmul(*dldZTimeR, *WchT); auto dLdZrWrhT = mmul(*dLdZr, *WrhT); auto* temp2 = (*dLdhTimesU) + *dLdZuWuhT; delete dLdhTimesU; delete dLdZuWuhT; auto* temp3 = (*temp2) + *dldZTimeRWchT; delete temp2; delete dldZTimeRWchT; auto* dLdhLastTemp = (*temp3) + *dLdZrWrhT; delete temp3; delete dLdZrWrhT; dLdhLast->assign(dLdhLastTemp); // [bS, nU] delete dLdhLastTemp; // dLdWrx = xT × dLdZr auto dLdWrxTemp = mmul(*xT, *dLdZr); dLdWrx->assign(dLdWrxTemp); // [iS, bS] × [bS, nU] = [iS, nU] delete dLdWrxTemp; // dLdWrh = hLastT × dLdZr auto dLdWrhTemp = mmul(*hLastT, *dLdZr); dLdWrh->assign(dLdWrhTemp); // [nU, bS] × [bS, nU] = [nU, nU] delete dLdWrhTemp; // dLdWux = xT × dLdZu auto dLdWuxTemp = mmul(*xT, *dLdZu); dLdWux->assign(dLdWuxTemp); // [iS, bS] × [bS, nU] = [iS, nU] delete dLdWuxTemp; // dLdWuh = hLastT × dLdZu auto dLdWuhTemp = mmul(*hLastT, *dLdZu); dLdWuh->assign(dLdWuhTemp); // [nU, bS] × [bS, nU] = [nU, nU] delete dLdWuhTemp; // dLdWcx = xT × dLdZc auto dLdWcxTemp = mmul(*xT, *dLdZc); dLdWcx->assign(dLdWcxTemp); // [iS, bS] × [bS, nU] = [iS, nU] delete dLdWcxTemp; // dLdWch = (r * hLast)T × dLdZc auto* rTimesHLast = r * (*hLast); NDArray* rTimesHLastT = rTimesHLast->transpose(); delete rTimesHLast; auto dLdWchTemp = mmul(*rTimesHLastT, *dLdZc); dLdWch->assign(dLdWchTemp); // [nU, bS] × [bS, nU] = [nU, nU] delete dLdWchTemp; // Calculate reduction for bias gradients std::vector zeroVec = {0}; auto* dLdbrTemp = dLdZr->reduceAlongDimension(reduce::Sum, &zeroVec); dLdbr->assign(dLdbrTemp); // [nU] delete dLdbrTemp; auto* dLdbuTemp = dLdZu->reduceAlongDimension(reduce::Sum, &zeroVec); dLdbu->assign(dLdbuTemp); // [nU] delete dLdbuTemp; auto* dLdbcTemp = dLdZc->reduceAlongDimension(reduce::Sum, &zeroVec); dLdbc->assign(dLdbcTemp); // [nU] delete dLdbcTemp; delete dhdc; delete dLdZc; delete dLdZu; delete dLdZr; delete dldZTimeR; delete Wrx; delete Wux; delete Wrh; delete Wuh; delete Wcx; delete Wch; delete br; delete bu; delete WrxT; delete WuxT; delete WrhT; delete WuhT; delete WcxT; delete WchT; delete dLdWrx; delete dLdWux; delete dLdWrh; delete dLdWuh; delete dLdWcx; delete dLdWch; delete dLdbr; delete dLdbu; delete xT; delete hLastT; delete rTimesHLastT; } ////////////////////////////////////////////////////////////////////////// void gruCellBp(sd::LaunchContext* context, NDArray* x, NDArray* hI, NDArray* Wx, NDArray* Wh, NDArray* b, NDArray* dLdh, NDArray* gates, NDArray* dLdx, NDArray* dLdhI, NDArray* dLdWx, NDArray* dLdWh, NDArray* dLdb) { // Inputs: // x input [bS, nIn] // hI previous cell output [bS, nOut], that nIn at previous time step t-1 // Wx input-to-hidden weights - [nIn, 3*nOut] // Wh hidden-to-hidden weights - [nOut, 3*nOut] // b biases, [3*nOut] - reset and update gates // dLdh gradient vs. ff output, [bS, nOut] // Outputs: // dLdx gradient vs. x, [bS, nIn], // dLdhI gradient vs. hI, [bS, nOut] // dLdWx gradient vs. W, [nIn, 3*nOut] // dLdWh gradient vs. Wc, [nOut, 3*nOut] // dLdb gradient vs. b [3*nOut] // 3*nOut means following sequence: reset, update, cell // * means element-wise product or so called Hadamard product // × means matrix multiplication const int nOut = hI->sizeAt(1); NDArray *gatesULike = gates->ulike(); NDArray dLdz = *gatesULike; // [bS, 3*nOut] NDArray *dLdzru = dLdz({0, 0, 0, 2 * nOut}); // [bS, 2*nOut] NDArray *dLdzr = dLdz({0, 0, 0, nOut}); // [bS, nOut] NDArray *dLdzu = dLdz({0, 0, nOut, 2 * nOut}); // [bS, nOut] NDArray *dLdzc = dLdz({0, 0, 2 * nOut, 3 * nOut}); // [bS, nOut] NDArray *r = (*gates)({0, 0, 0, nOut}); // [bS, nOut] NDArray *u = (*gates)({0, 0, nOut, 2 * nOut}); // [bS, nOut] NDArray *c = (*gates)({0, 0, 2 * nOut, 3 * nOut}); // [bS, nOut] NDArray *WhView = (*Wh)({0, 0, 2 * nOut, 3 * nOut}); NDArray *WhcT = WhView->transpose(); if (dLdh) *dLdhI += *dLdh; auto* oneMinusU = 1 - (*u); // [bS, nOut] // dLdzc = dLdhI * (1-u) * (1-c²) auto* cSquared = (*c) * (*c); auto* oneMinusCSquared = 1.f - (*cSquared); delete cSquared; auto* temp1 = (*dLdhI) * (*oneMinusU); auto* dLdzcTemp = (*temp1) * (*oneMinusCSquared); delete temp1; delete oneMinusCSquared; dLdzc->assign(dLdzcTemp); // [bS, nOut] delete dLdzcTemp; // dLdzu = dLdhI * (hI - c) * u * (1-u) auto* hIMinusC = (*hI) - (*c); auto* uTimesOneMinusU = (*u) * (*oneMinusU); auto* temp2 = (*dLdhI) * (*hIMinusC); auto* dLdzuTemp = (*temp2) * (*uTimesOneMinusU); delete temp2; delete hIMinusC; delete uTimesOneMinusU; dLdzu->assign(dLdzuTemp); // [bS, nOut] delete dLdzuTemp; delete oneMinusU; // dLdzr = (dLdzc * hI * r * (1-r)) × WhcT auto* oneMinusR = 1 - (*r); auto* rTimesOneMinusR = (*r) * (*oneMinusR); delete oneMinusR; auto* temp3 = (*dLdzc) * (*hI); auto* temp4 = (*temp3) * (*rTimesOneMinusR); delete temp3; delete rTimesOneMinusR; MmulHelper::mmul(temp4, WhcT, dLdzr); // [bS, nOut] x [nOut, nOut] = [bS, nOut] delete temp4; // dLdx = dLdz × WxT NDArray *WxT = Wx->transpose(); MmulHelper::mmul(&dLdz, WxT, dLdx); // [bS, 3*nOut] x [3*nOut, nIn] = [bS, nIn] // dLdWx += xT × dLdz NDArray *xT = x->transpose(); auto dLdWxAdd = mmul(*xT, dLdz); *dLdWx += *dLdWxAdd; // [nIn, bS] x [bS, 3*nOut] = [nIn, 3*nOut] delete dLdWxAdd; // dLdb += sum(dLdz, axis=0) std::vector zeroVec = {0}; auto* dLdbAdd = dLdz.reduceAlongDimension(reduce::Sum, &zeroVec); *dLdb += (*dLdbAdd); // [bS, 3*nOut] -> reduce -> [3*nOut]; delete dLdbAdd; *dLdzc *= (*r); // dLdhI = dLdhI * u + dLdz × WhT NDArray *WhT = Wh->transpose(); auto* dLdhIU = (*dLdhI) * (*u); auto mmulResult = mmul(dLdz, *WhT); auto* dLdhIAssign = (*dLdhIU) + *mmulResult; delete dLdhIU; delete mmulResult; dLdhI->assign(dLdhIAssign); // [bS, 3*nOut] x [3*nOut, nOut] = [bS, nOut] delete dLdhIAssign; // dLdWh += hIT × dLdz NDArray *hITranspose = hI->transpose(); auto dLdWhAdd = mmul(*hITranspose, dLdz); *dLdWh += *dLdWhAdd; // [nOut, bS] x [bS, 3*nOut] = [nOut, 3*nOut] delete dLdWhAdd; delete gatesULike; delete dLdzru; delete dLdzr; delete dLdzu; delete dLdzc; delete r; delete u; delete c; delete WhView; delete WhcT; delete hITranspose; delete WhT; delete xT; delete WxT; } ////////////////////////////////////////////////////////////////////////// void gruTimeLoopBp(sd::LaunchContext* context, NDArray* x, NDArray* hI, NDArray* Wx, NDArray* Wh, NDArray* b, NDArray* dLdh, NDArray* dLdx, NDArray* dLdhI, NDArray* dLdWx, NDArray* dLdWh, NDArray* dLdb) { // sL means time steps // x input [sL, bS, nIn] // hI initial cell output (at time step = 0) [bS, nOut] // Wx input-to-hidden weights, [nIn, 3*nOut] // Wh hidden-to-hidden weights, [nOut, 3*nOut] // b biases, [3*nOut] // dLdh gradient vs. ff output, [sL, bS, nOut] // dLdx gradient vs. x, [sL, bS, nIn], // dLdhI gradient vs. hI, [bS, nOut] // dLdWx gradient vs. W, [nIn, 3*nOut] // dLdWh gradient vs. Wc, [nOut, 3*nOut] // dLdb gradient vs. b [3*nOut] const int sL = x->sizeAt(0); const int bS = x->sizeAt(1); const int nOut = hI->sizeAt(1); std::vector shape = {bS, 3 * nOut}; std::vector hShape = {sL + 1, bS, nOut}; NDArray gates(x->ordering(), shape, dLdh->dataType(), x->getContext()); NDArray h(x->ordering(), hShape, dLdh->dataType(), x->getContext()); auto xSet = x->allTensorsAlongDimension({1, 2}); // sub-arrays with shape [bS, nIn] auto dLdhSet = dLdh->allTensorsAlongDimension({1, 2}); // sub-arrays with shape [bS, nOut] auto hSet = h.allTensorsAlongDimension({1, 2}); // sub-arrays with shape [bS, nOut] auto gatesSet = gates.allTensorsAlongDimension({1, 2}); // sub-arrays with shape [bS, nOut] auto dLdxSet = dLdx->allTensorsAlongDimension({1, 2}); // sub-arrays with shape [bS, nIn] hSet.at(0)->assign(hI); // forward time loop for (int t = 0; t < sL; ++t) gruCell(xSet.at(t), hSet.at(t), Wx, Wh, b, gatesSet.at(t), hSet.at(t + 1), false); // backward time loop for (int t = sL - 1; t >= 0; --t) gruCellBp(context, xSet.at(t), hSet.at(t), Wx, Wh, b, dLdhSet.at(t), gatesSet.at(t), dLdxSet.at(t), dLdhI, dLdWx, dLdWh, dLdb); } } // namespace helpers } // namespace ops } // namespace sd #endif