blob: 38c427bf090a1cce3d99cc1a5147e64d445b1e0c [file]
#-------------------------------------------------------------
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# 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.
#
#-------------------------------------------------------------
# uncomment to use the lstm dml script instead of the built-in operator
# source("nn/layers/lstm.dml") as lstm
/*
* Bidirectional-LSTM layer.
*/
forward = function(matrix[double] X, matrix[double] W, matrix[double] W_reverse, matrix[double] b,
matrix[double] b_reverse, int T, int D, boolean seq, matrix[double] out0, matrix[double] c0)
return (matrix[double] out, matrix[double] c,
matrix[double] cache_out, matrix[double] cache_c, matrix[double] cache_ifog) {
/*
* Computes the forward pass for an BI-LSTM layer.
* The input data has N sequences of T examples, each with D features.
*
* The BI-LSTM Layer processes the input tokens once in the normal order and once in the reverse order.
* The Layer uses different LSTM Cells for both passes. Therefore it contains 2*M neurons. The output of
* both passes is concatenated for each time step, which results in output of twice of the size of a standard
* LSTM Layer.
* The API is similiar to pytorch BI-LSTM, with the only difference that the input bias and hidden bias is combined
* to a single bias. The weights and biases for both directions are given separately (similiar to pytorch's API).
* It is possible to return only the output of the last cell (similiar to the normal LSTM layer). In that case the
* final output of both directions is concatenated.
*
* Reference:
* - Framewise phoneme classification with bidirectional LSTM networks; A. Graves, J. Schmidhuber; 2005
* - https://ieeexplore.ieee.org/document/1556215
* - Pytorch's LSTM / BILSTM
* - https://pytorch.org/docs/stable/generated/torch.nn.LSTM.html
*
* Inputs:
* - X: Inputs, of shape (N, T*D).
* - W: Weights, of shape (D+M, 4M).
* - W_reverse: Weights, of shape (D+M, 4M).
* - b: Biases, of shape (1, 4M).
* - b_reverse: Biases, of shape (1, 4M).
* - T: Length of example sequences (number of timesteps).
* - D: Dimensionality of the input features (number of features).
* - seq: Whether to return `out` at all timesteps,
* or just for the final timestep.
* - out0: Outputs from previous timestep for both passes, of shape (N*2, M).
* Note: This is *optional* and could just be an empty matrix.
* - c0: Initial cell state for both passes, of shape (N*2, M).
* Note: This is *optional* and could just be an empty matrix.
*
* Outputs:
* - out: If `return_sequences` is True, outputs for all timesteps,
* of shape (N, T*(M*2)). Else, outputs for the final timestep, of
* shape (N, M*2).
* - c: Cell state for final timestep, of shape (N*2, M).
* - cache_out: Cache of outputs, of shape (T*2, N*M).
* Note: This is used for performance during training.
* - cache_c: Cache of cell state, of shape (T*2, N*M).
* Note: This is used for performance during training.
* - cache_ifog: Cache of intermediate values, of shape (T*2, N*4M).
* Note: This is used for performance during training.
*/
M = ncol(out0)
N = nrow(X)
out0_forward = out0[1 : N,]
out0_reverse = out0[N + 1 : 2*N,]
c0_forward = c0[1 : N,]
c0_reverse = c0[N + 1 : 2*N,]
# normal lstm pass
[out, c, cache_out, cache_c, cache_ifog] = lstm(X, W, b, out0_forward, c0_forward, seq)
#[out, c, cache_out, cache_c, cache_ifog] = lstm::forward(X, W, b, T,D, seq, out0_forward, c0_forward)
# approach 1: reorder token by reversing X and weights of X
X_reverse = t(rev(t(X))) # reverse the elements inside a row
W_reverse[1:D,] = rev(W_reverse[1:D,]) # have to reverse the input weights as well
# approach 2 (slower): reorder tokens by slicing
# X_reverse = matrix(0, rows=nrow(X), cols=ncol(X))
# for (i in 1:T){X_reverse[,(T - i)*D+1:(T - i + 1)*D] = X[,(i-1)*D+1:i*D]}
[out_reverse, c_reverse, cache_out_reverse, cache_c_reverse, cache_ifog_reverse] = lstm(X_reverse, W_reverse, b_reverse, out0_reverse, c0_reverse, seq)
#[out_reverse, c_reverse, cache_out_reverse, cache_c_reverse, cache_ifog_reverse] = lstm::forward(X_reverse, W_reverse, b_reverse, T,D, seq, out0_reverse, c0_reverse)
# reorder the output of reverse lstm cell
if(seq){
out_reversed = matrix(0, nrow(out_reverse), ncol(out_reverse))
for (i in 1:T){out_reversed[,(T - i)*M+1:(T - i + 1)*M] = out_reverse[,(i-1)*M+1:i*M]}
} else {
out_reversed = out_reverse
}
cache_out = rbind(cache_out, cache_out_reverse)
cache_c = rbind(cache_c, cache_c_reverse)
cache_ifog = rbind(cache_ifog, cache_ifog_reverse)
c = rbind(c, c_reverse)
if(seq == FALSE){T=1}
out = matrix(out, rows = N*T, cols=M)
out_reversed = matrix(out_reversed, rows = N*T, cols=M)
out = cbind(out, out_reversed)
out = matrix(out, rows = N, cols = T*2*M)
}
backward = function(matrix[double] dout, matrix[double] dc,
matrix[double] X, matrix[double] W, matrix[double] W_reverse, matrix[double] b,
matrix[double] b_reverse, int T, int D, boolean seq, matrix[double] out0, matrix[double] c0,
matrix[double] cache_out, matrix[double] cache_c, matrix[double] cache_ifog)
return (matrix[double] dX, matrix[double] dW, matrix[double] db, matrix[double] dW_reverse,
matrix[double] db_reverse, matrix[double] dout0, matrix[double] dc0) {
/*
* Computes the backward pass for an BI-LSTM layer with M neurons.
*
* Inputs:
* - dout: Gradient wrt `out`. If `seq` is `True`,
* contains gradients on outputs for all timesteps, of
* shape (N, T*(2*M)). Else, contains the gradient on the output
* for the final timestep, of shape (N, 2*M).
* - dc: Gradient wrt `c` (from later in time), of shape (N*2, M).
* This would come from later in time if the cell state was used
* downstream as the initial cell state for another LSTM layer.
* Typically, this would be used when a sequence was cut at
* timestep `T` and then continued in the next batch. If `c`
* was not used downstream, then `dc` would be an empty matrix.
* - X: Inputs, of shape (N, T*D).
* - W: Weights, of shape (D+M, 4M).
* - W_reverse: Weights, of shape (D+M, 4M).
* - b: Biases, of shape (1, 4M).
* - b_reverse: Biases, of shape (1, 4M).
* - T: Length of example sequences (number of timesteps).
* - D: Dimensionality of the input features.
* - seq: Whether `dout` is for all timesteps,
* or just for the final timestep. This is based on whether
* `return_sequences` was true in the forward pass.
* - out0: Outputs from previous timestep, of shape (N*2, M).
* Note: This is *optional* and could just be an empty matrix.
* - c0: Initial cell state, of shape (N*2, M).
* Note: This is *optional* and could just be an empty matrix.
* - cache_out: Cache of outputs, of shape (T*2, N*M).
* Note: This is used for performance during training.
* - cache_c: Cache of cell state, of shape (T*2, N*M).
* Note: This is used for performance during training.
* - cache_ifog: Cache of intermediate values, of shape (T*2, N*4*M).
* Note: This is used for performance during training.
*
* Outputs:
* - dX: Gradient wrt `X`, of shape (N, T*D).
* - dW: Gradient wrt `W`, of shape (D+M, 4M).
* - dW_reverse: Gradient wrt `W_reverse`, of shape (D+M, 4M).
* - db: Gradient wrt `b`, of shape (1, 4M).
* - db_reverse: Gradient wrt `b_reverse`, of shape (1, 4M).
* - dout0: Gradient wrt `out0`, of shape (N*2, M).
* - dc0: Gradient wrt `c0`, of shape (N*2, M).
*/
M = ncol(out0)
N = nrow(X)
if(seq){dout = matrix(dout, rows=N*T, cols=2*M)}
dout_forward = dout[,1:M]
dout_reverse = dout[,M + 1 : 2*M]
if(seq){
dout_forward = matrix(dout_forward, rows=N, cols=T*M)
dout_reverse = matrix(dout_reverse, rows=N, cols=T*M)
dout_reversed = matrix(0, nrow(dout_reverse), ncol(dout_reverse))
for (i in 1:T){
dout_reversed[,(T - i)*M+1:(T - i + 1)*M] = dout_reverse[,(i-1)*M+1:i*M]
}
}
else{
dout_reversed = dout_reverse
}
dc_forward = dc[1:N,]
cache_out_forward = cache_out[1:T,]
cache_c_forward = cache_c[1:T,]
cache_ifog_forward = cache_ifog[1:T,]
out0_forward = out0[1 : nrow(out0) /2, ]
c0_forward = c0[1 : N, ]
[dx, dW, db, dout0, dc0] = lstm_backward(X, W, b, out0_forward, c0_forward, seq, dout_forward, dc_forward, cache_out_forward, cache_c_forward, cache_ifog_forward)
#[dx, dW, db, dout0, dc0] = lstm::backward(dout_forward, dc_forward, X, W,b,T,D,seq,out0_forward, c0_forward,cache_out_forward, cache_c_forward, cache_ifog_forward)
# for approach1:
X_reverse = t(rev(t(X))) # reverse the elements inside a row
W_reverse[1:D,] = rev(W_reverse[1:D,]) # have to reverse the input weights as well
# for approach2 (also slower for backward pass):
#X_reverse = X # matrix(0, rows=nrow(X), cols=ncol(X))
#for (i in 1:T){X_reverse[,(T - i)*D+1:(T - i + 1)*D] = X[,(i-1)*D+1:i*D]}
dc_reverse = dc[N+1 : 2*N,]
cache_out_reverse = cache_out[T+1 : 2*T,]
cache_c_reverse = cache_c[T+1 : 2*T,]
cache_ifog_reverse = cache_ifog[T+1 : 2*T,]
c0_reverse = c0[N + 1 : 2*N,]
out0_reverse = out0[N + 1 : 2*N,]
[dx_reverse, dW_reverse, db_reverse, dout0_reverse, dc0_reverse] = lstm_backward(X_reverse, W_reverse, b_reverse, out0_reverse, c0_reverse, seq, dout_reversed, dc_reverse, cache_out_reverse, cache_c_reverse, cache_ifog_reverse)
#[dx_reverse, dW_reverse, db_reverse, dout0_reverse, dc0_reverse] = lstm::backward(dout_reversed, dc_reverse, X_reverse, W_reverse,b_reverse,T,D,seq,out0_reverse, c0_reverse,cache_out_reverse, cache_c_reverse, cache_ifog_reverse)
# for approach1:
dx_reverse = t(rev(t(dx_reverse)))
dW_reverse[1:D,] = rev(dW_reverse[1:D,])
# for approach2:
#dx_reverse2 = matrix(0, rows=nrow(dx_reverse), cols=ncol(dx_reverse))
#for (i in 1:T){dx_reverse2[,(T - i)*D+1:(T - i + 1)*D] = dx_reverse[,(i-1)*D+1:i*D]}
#dx_reverse = dx_reverse2
dX = dx + dx_reverse
dout0 = rbind(dout0, dout0_reverse)
dc0 = rbind(dc0, dc0_reverse) #matrix(1, rows=1, cols=1)
}