Creating a custom recurrent neural network layer​
​by Harshal Gajjar
In this post, we will implement the LongShortTermMemoryLayer from scratch. This will work to lay the foundations to implement any recurrent layer variants. We will implement LSTM in 4 parts, each one having an image to guide.
To concretize things, let’s assume that hidden state is a 32 dimensional vector and the input to LSTM (at every time step) is 128 dimensional.
In[]:=
inputDimension=128;​​hiddenStateDimension=32;

Implementing first part

These neat images were taken from Christopher Olah’s blog, anyways getting back to its implementation:
In[]:=
lstmCell=NetGraph[{​​(*1*)LinearLayer[hiddenStateDimension,"Input"inputDimension],​​(*2*)LinearLayer[hiddenStateDimension,"Input"hiddenStateDimension],​​(*3*)TotalLayer[],​​(*4*)ElementwiseLayer[LogisticSigmoid](*f_t*)​​},{​​NetPort["old_h"]23,​​NetPort["Input"]13,​​34(*f_t*)​​},​​"old_h"{hiddenStateDimension}​​]
Out[]=
NetGraph
uniniti
aliz
ed
Number of inputs:
2
Outputport:
vector(size: 32)


Implementing second part

In[]:=
lstmCell=NetGraph[{​​(*1*)LinearLayer[hiddenStateDimension,"Input"inputDimension],​​(*2*)LinearLayer[hiddenStateDimension,"Input"hiddenStateDimension],​​(*3*)TotalLayer[],​​(*4*)ElementwiseLayer[LogisticSigmoid](*f_t*),​​​​(*5*)LinearLayer[hiddenStateDimension,"Input"inputDimension],​​(*6*)LinearLayer[hiddenStateDimension,"Input"hiddenStateDimension],​​(*7*)TotalLayer[],​​(*8*)ElementwiseLayer[LogisticSigmoid](*i_t*),​​​​(*9*)LinearLayer[hiddenStateDimension,"Input"inputDimension],​​(*10*)LinearLayer[hiddenStateDimension,"Input"hiddenStateDimension],​​(*11*)TotalLayer[],​​(*12*)ElementwiseLayer[Tanh](*c_tilde_t*)​​​​},{​​NetPort["Input"]13,​​NetPort["old_h"]23,​​34(*f_t*),​​​​NetPort["Input"]57,​​NetPort["old_h"]67,​​78(*i_t*),​​​​NetPort["Input"]911,​​NetPort["old_h"]1011,​​1112(*c_tilde_t*)​​},​​"old_h"{hiddenStateDimension}​​]
Out[]=
NetGraph
uniniti
aliz
ed
Number of inputs:
2
Number of outputs:
3


Implementing third part

In[]:=
lstmCell=NetGraph[{​​(*1*)LinearLayer[hiddenStateDimension,"Input"inputDimension],​​(*2*)LinearLayer[hiddenStateDimension,"Input"hiddenStateDimension],​​(*3*)TotalLayer[],​​(*4*)ElementwiseLayer[LogisticSigmoid](*f_t*),​​​​(*5*)LinearLayer[hiddenStateDimension,"Input"inputDimension],​​(*6*)LinearLayer[hiddenStateDimension,"Input"hiddenStateDimension],​​(*7*)TotalLayer[],​​(*8*)ElementwiseLayer[LogisticSigmoid](*i_t*),​​​​(*9*)LinearLayer[hiddenStateDimension,"Input"inputDimension],​​(*10*)LinearLayer[hiddenStateDimension,"Input"hiddenStateDimension],​​(*11*)TotalLayer[],​​(*12*)ElementwiseLayer[Tanh](*c_tilde_t*),​​​​(*13*)ThreadingLayer[Times],​​(*14*)ThreadingLayer[Times],​​(*15*)TotalLayer[](*new_c*)​​},{​​NetPort["Input"]13,​​NetPort["old_h"]23,​​34(*f_t*),​​​​NetPort["Input"]57,​​NetPort["old_h"]67,​​78(*i_t*),​​​​NetPort["Input"]911,​​NetPort["old_h"]1011,​​1112(*c_tilde_t*),​​​​413,NetPort["old_c"]13,​​814,1214,​​1315,1415(*new_c*),​​15NetPort["new_c"]​​},​​"old_h"{hiddenStateDimension},"old_c"{hiddenStateDimension}​​]
Out[]=
NetGraph
uniniti
aliz
ed
Number of inputs:
3
new_cport:
vector(size: 32)


Implementing last part

Wrapping into a function

Let’s wrap the above code into a function to generate new custom layers as and when needed:
Using NetFoldOperator to convert LSTM cell into a recurrent computation layer:

An example

This one is from LongShortTermMemoryLayer documentation but reworked using custom LSTM layer. It takes in a string “1+10” and outputs the result of that computation.
Creating the custom layers:
Creating a training net:
Extracting trained net and adding net encoder:
Getting network results:
Voila, you have a custom RNN layer implemented!

References

◼
  • https://colah.github.io/posts/2015-08-Understanding-LSTMs/
  • ◼
  • https://www.bioinf.jku.at/publications/older/2604.pdf
  • ◼
  • https://en.wikipedia.org/wiki/Long_short-term_memory