Skip to content

Commit 754f2fa

Browse files
committed
Prototyping RNN layer based on Dense
The dimensions don't match, but let's start with something that compile.
1 parent 118f795 commit 754f2fa

File tree

3 files changed

+276
-0
lines changed

3 files changed

+276
-0
lines changed

CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ add_library(neural-fortran
5959
src/nf/nf_random.f90
6060
src/nf/nf_reshape_layer.f90
6161
src/nf/nf_reshape_layer_submodule.f90
62+
src/nf/nf_rnn_layer.f90
63+
src/nf/nf_rnn_layer_submodule.f90
6264
src/nf/io/nf_io_binary.f90
6365
src/nf/io/nf_io_binary_submodule.f90
6466
src/nf/io/nf_io_hdf5.f90

src/nf/nf_rnn_layer.f90

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
module nf_rnn_layer
2+
3+
!! This module provides the concrete dense layer type.
4+
!! It is used internally by the layer type.
5+
!! It is not intended to be used directly by the user.
6+
7+
use nf_activation, only: activation_function
8+
use nf_base_layer, only: base_layer
9+
10+
implicit none
11+
12+
private
13+
public :: rnn_layer
14+
15+
type, extends(base_layer) :: rnn_layer
16+
17+
!! Concrete implementation of a dense (fully-connected) layer type
18+
19+
integer :: input_size
20+
integer :: output_size
21+
22+
real, allocatable :: weights(:,:)
23+
real, allocatable :: recurrent(:,:)
24+
real, allocatable :: biases(:)
25+
real, allocatable :: state(:)
26+
real, allocatable :: z(:) ! matmul(x, w) + b
27+
real, allocatable :: output(:) ! activation(z)
28+
real, allocatable :: gradient(:) ! matmul(w, db)
29+
real, allocatable :: dw(:,:) ! weight gradients
30+
real, allocatable :: db(:) ! bias gradients
31+
32+
class(activation_function), allocatable :: activation
33+
34+
contains
35+
36+
!procedure :: backward
37+
!procedure :: forward
38+
!procedure :: get_gradients
39+
procedure :: get_num_params
40+
!procedure :: get_params
41+
procedure :: init
42+
!procedure :: set_params
43+
44+
end type rnn_layer
45+
46+
interface rnn_layer
47+
elemental module function rnn_layer_cons(output_size, activation) &
48+
result(res)
49+
!! This function returns the `dense_layer` instance.
50+
integer, intent(in) :: output_size
51+
!! Number of neurons in this layer
52+
class(activation_function), intent(in) :: activation
53+
!! Instance of the activation_function to use;
54+
!! See nf_activation.f90 for available functions.
55+
type(rnn_layer) :: res
56+
!! dense_layer instance
57+
end function rnn_layer_cons
58+
end interface rnn_layer
59+
60+
interface
61+
62+
pure module subroutine backward(self, input, gradient)
63+
!! Apply the backward gradient descent pass.
64+
!! Only weight and bias gradients are updated in this subroutine,
65+
!! while the weights and biases themselves are untouched.
66+
class(rnn_layer), intent(in out) :: self
67+
!! Dense layer instance
68+
real, intent(in) :: input(:)
69+
!! Input from the previous layer
70+
real, intent(in) :: gradient(:)
71+
!! Gradient from the next layer
72+
end subroutine backward
73+
74+
pure module subroutine forward(self, input)
75+
!! Propagate forward the layer.
76+
!! Calling this subroutine updates the values of a few data components
77+
!! of `dense_layer` that are needed for the backward pass.
78+
class(rnn_layer), intent(in out) :: self
79+
!! Dense layer instance
80+
real, intent(in) :: input(:)
81+
!! Input from the previous layer
82+
end subroutine forward
83+
84+
pure module function get_num_params(self) result(num_params)
85+
!! Return the number of parameters in this layer.
86+
class(rnn_layer), intent(in) :: self
87+
!! Dense layer instance
88+
integer :: num_params
89+
!! Number of parameters in this layer
90+
end function get_num_params
91+
92+
pure module function get_params(self) result(params)
93+
!! Return the parameters (weights and biases) of this layer.
94+
!! The parameters are ordered as weights first, biases second.
95+
class(rnn_layer), intent(in) :: self
96+
!! Dense layer instance
97+
real, allocatable :: params(:)
98+
!! Parameters of this layer
99+
end function get_params
100+
101+
pure module function get_gradients(self) result(gradients)
102+
!! Return the gradients of this layer.
103+
!! The gradients are ordered as weights first, biases second.
104+
class(rnn_layer), intent(in) :: self
105+
!! Dense layer instance
106+
real, allocatable :: gradients(:)
107+
!! Gradients of this layer
108+
end function get_gradients
109+
110+
module subroutine set_params(self, params)
111+
!! Set the parameters of this layer.
112+
!! The parameters are ordered as weights first, biases second.
113+
class(rnn_layer), intent(in out) :: self
114+
!! Dense layer instance
115+
real, intent(in) :: params(:)
116+
!! Parameters of this layer
117+
end subroutine set_params
118+
119+
module subroutine init(self, input_shape)
120+
!! Initialize the layer data structures.
121+
!!
122+
!! This is a deferred procedure from the `base_layer` abstract type.
123+
class(rnn_layer), intent(in out) :: self
124+
!! Dense layer instance
125+
integer, intent(in) :: input_shape(:)
126+
!! Shape of the input layer
127+
end subroutine init
128+
129+
end interface
130+
131+
end module nf_rnn_layer

src/nf/nf_rnn_layer_submodule.f90

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
submodule(nf_rnn_layer) nf_rnn_layer_submodule
2+
3+
use nf_activation, only: activation_function
4+
use nf_base_layer, only: base_layer
5+
use nf_random, only: random_normal
6+
7+
implicit none
8+
9+
contains
10+
11+
elemental module function rnn_layer_cons(output_size, activation) &
12+
result(res)
13+
integer, intent(in) :: output_size
14+
class(activation_function), intent(in) :: activation
15+
type(rnn_layer) :: res
16+
17+
res % output_size = output_size
18+
res % activation_name = activation % get_name()
19+
allocate( res % activation, source = activation )
20+
21+
end function rnn_layer_cons
22+
23+
24+
pure module subroutine backward(self, input, gradient)
25+
class(rnn_layer), intent(in out) :: self
26+
real, intent(in) :: input(:)
27+
real, intent(in) :: gradient(:)
28+
real :: db(self % output_size)
29+
real :: dw(self % input_size, self % output_size)
30+
31+
db = gradient * self % activation % eval_prime(self % z)
32+
dw = matmul(reshape(input, [size(input), 1]), reshape(db, [1, size(db)]))
33+
self % gradient = matmul(self % weights, db)
34+
self % dw = self % dw + dw
35+
self % db = self % db + db
36+
37+
end subroutine backward
38+
39+
40+
pure module subroutine forward(self, input)
41+
class(rnn_layer), intent(in out) :: self
42+
real, intent(in) :: input(:)
43+
44+
self % z = matmul(input, self % weights) + self % biases
45+
self % output = self % activation % eval(self % z)
46+
47+
end subroutine forward
48+
49+
50+
pure module function get_num_params(self) result(num_params)
51+
class(rnn_layer), intent(in) :: self
52+
integer :: num_params
53+
54+
! Number of weigths times number of biases
55+
num_params = self % input_size * self % output_size + self % output_size
56+
57+
end function get_num_params
58+
59+
60+
pure module function get_params(self) result(params)
61+
class(rnn_layer), intent(in) :: self
62+
real, allocatable :: params(:)
63+
64+
params = [ &
65+
pack(self % weights, .true.), &
66+
pack(self % biases, .true.) &
67+
]
68+
69+
end function get_params
70+
71+
72+
pure module function get_gradients(self) result(gradients)
73+
class(rnn_layer), intent(in) :: self
74+
real, allocatable :: gradients(:)
75+
76+
gradients = [ &
77+
pack(self % dw, .true.), &
78+
pack(self % db, .true.) &
79+
]
80+
81+
end function get_gradients
82+
83+
84+
module subroutine set_params(self, params)
85+
class(rnn_layer), intent(in out) :: self
86+
real, intent(in) :: params(:)
87+
88+
! check if the number of parameters is correct
89+
if (size(params) /= self % get_num_params()) then
90+
error stop 'Error: number of parameters does not match'
91+
end if
92+
93+
! reshape the weights
94+
self % weights = reshape( &
95+
params(:self % input_size * self % output_size), &
96+
[self % input_size, self % output_size] &
97+
)
98+
99+
! reshape the biases
100+
self % biases = reshape( &
101+
params(self % input_size * self % output_size + 1:), &
102+
[self % output_size] &
103+
)
104+
105+
end subroutine set_params
106+
107+
108+
module subroutine init(self, input_shape)
109+
class(rnn_layer), intent(in out) :: self
110+
integer, intent(in) :: input_shape(:)
111+
112+
self % input_size = input_shape(1)
113+
114+
! Weights are a 2-d array of shape previous layer size
115+
! times this layer size.
116+
allocate(self % weights(self % input_size, self % output_size))
117+
call random_normal(self % weights)
118+
self % weights = self % weights / self % input_size
119+
120+
! Broadcast weights to all other images, if any.
121+
call co_broadcast(self % weights, 1)
122+
123+
allocate(self % biases(self % output_size))
124+
self % biases = 0
125+
126+
allocate(self % output(self % output_size))
127+
self % output = 0
128+
129+
allocate(self % z(self % output_size))
130+
self % z = 0
131+
132+
allocate(self % dw(self % input_size, self % output_size))
133+
self % dw = 0
134+
135+
allocate(self % db(self % output_size))
136+
self % db = 0
137+
138+
allocate(self % gradient(self % output_size))
139+
self % gradient = 0
140+
141+
end subroutine init
142+
143+
end submodule nf_rnn_layer_submodule

0 commit comments

Comments
 (0)