Skip to content

Commit 2181d0a

Browse files
committed
Merge branch 'conv2d-forward-pass' into maxpool-layer
2 parents a1959f9 + f5eff3c commit 2181d0a

File tree

10 files changed

+371
-81
lines changed

10 files changed

+371
-81
lines changed

CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ add_library(neural
6868
src/nf_base_layer.f90
6969
src/nf_base_layer_submodule.f90
7070
src/nf_conv2d_layer.f90
71+
src/nf_conv2d_layer_submodule.f90
7172
src/nf_datasets_mnist.f90
7273
src/nf_datasets_mnist_submodule.f90
7374
src/nf_dense_layer.f90
@@ -99,7 +100,7 @@ string(REGEX REPLACE "^ | $" "" LIBS "${LIBS}")
99100

100101
# tests
101102
enable_testing()
102-
foreach(execid input1d_layer dense_layer dense_network)
103+
foreach(execid input1d_layer dense_layer conv2d_layer dense_network)
103104
add_executable(test_${execid} test/test_${execid}.f90)
104105
target_link_libraries(test_${execid} neural ${LIBS})
105106
add_test(test_${execid} bin/test_${execid})

src/nf.f90

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module nf
22
use nf_datasets_mnist, only: label_digits, load_mnist
33
use nf_layer, only: layer
4-
use nf_layer_constructors, only: dense, input
4+
use nf_layer_constructors, only: conv2d, dense, input
55
use nf_network, only: network
66
end module nf

src/nf_conv2d_layer.f90

Lines changed: 42 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
module nf_conv2d_layer
22

3-
!! This is a placeholder module that will later define a concrete conv2d
4-
!! layer type.
3+
!! This modules provides a 2-d convolutional `conv2d_layer` type.
54

65
use nf_base_layer, only: base_layer
76
implicit none
@@ -17,9 +16,9 @@ module nf_conv2d_layer
1716
integer :: window_size
1817
integer :: filters
1918

20-
real, allocatable :: biases(:) ! as many as there are filters
21-
real, allocatable :: kernel(:,:,:,:)
22-
real, allocatable :: output(:,:,:)
19+
real, allocatable :: biases(:) ! size(filters)
20+
real, allocatable :: kernel(:,:,:,:) ! filters x channels x window x window
21+
real, allocatable :: output(:,:,:) ! filters x output_width * output_height
2322

2423
contains
2524

@@ -30,55 +29,45 @@ module nf_conv2d_layer
3029
end type conv2d_layer
3130

3231
interface conv2d_layer
33-
module procedure :: conv2d_layer_cons
32+
pure module function conv2d_layer_cons(window_size, filters, activation) result(res)
33+
!! `conv2d_layer` constructor function
34+
integer, intent(in) :: window_size
35+
integer, intent(in) :: filters
36+
character(*), intent(in) :: activation
37+
type(conv2d_layer) :: res
38+
end function conv2d_layer_cons
3439
end interface conv2d_layer
3540

36-
contains
37-
38-
pure function conv2d_layer_cons(window_size, filters, activation) result(res)
39-
integer, intent(in) :: window_size
40-
integer, intent(in) :: filters
41-
character(*), intent(in) :: activation
42-
type(conv2d_layer) :: res
43-
res % window_size = window_size
44-
res % filters = filters
45-
call res % set_activation(activation)
46-
end function conv2d_layer_cons
47-
48-
49-
subroutine init(self, input_shape)
50-
class(conv2d_layer), intent(in out) :: self
51-
integer, intent(in) :: input_shape(:)
52-
53-
self % width = input_shape(1) - self % window_size + 1
54-
self % height = input_shape(2) - self % window_size + 1
55-
self % channels = input_shape(3)
56-
57-
allocate(self % output(self % width, self % height, self % filters))
58-
self % output = 0
59-
60-
allocate(self % kernel(self % window_size, self % window_size, &
61-
self % channels, self % filters))
62-
self % kernel = 0 ! TODO 4-d randn
63-
64-
allocate(self % biases(self % filters))
65-
self % biases = 0
66-
67-
end subroutine init
68-
69-
70-
subroutine forward(self, input)
71-
class(conv2d_layer), intent(in out) :: self
72-
real, intent(in) :: input(:,:,:)
73-
print *, 'Warning: conv2d forward pass not implemented'
74-
end subroutine forward
75-
76-
77-
subroutine backward(self, input, gradient)
78-
class(conv2d_layer), intent(in out) :: self
79-
real, intent(in) :: input(:,:,:)
80-
real, intent(in) :: gradient(:,:,:)
81-
print *, 'Warning: conv2d backward pass not implemented'
82-
end subroutine backward
41+
interface
42+
43+
module subroutine init(self, input_shape)
44+
!! Initialize the layer data structures.
45+
!!
46+
!! This is a deferred procedure from the `base_layer` abstract type.
47+
class(conv2d_layer), intent(in out) :: self
48+
!! A `conv2d_layer` instance
49+
integer, intent(in) :: input_shape(:)
50+
!! Input layer dimensions
51+
end subroutine init
52+
53+
pure module subroutine forward(self, input)
54+
!! Apply a forward pass on the `conv2d` layer.
55+
class(conv2d_layer), intent(in out) :: self
56+
!! A `conv2d_layer` instance
57+
real, intent(in) :: input(:,:,:)
58+
!! Input data
59+
end subroutine forward
60+
61+
module subroutine backward(self, input, gradient)
62+
!! Apply a backward pass on the `conv2d` layer.
63+
class(conv2d_layer), intent(in out) :: self
64+
!! A `conv2d_layer` instance
65+
real, intent(in) :: input(:,:,:)
66+
!! Input data (previous layer)
67+
real, intent(in) :: gradient(:,:,:)
68+
!! Gradient (next layer)
69+
end subroutine backward
70+
71+
end interface
8372

8473
end module nf_conv2d_layer

src/nf_conv2d_layer_submodule.f90

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
submodule(nf_conv2d_layer) nf_conv2d_layer_submodule
2+
3+
use nf_random, only: randn
4+
5+
implicit none
6+
7+
contains
8+
9+
pure module function conv2d_layer_cons(window_size, filters, activation) result(res)
10+
implicit none
11+
integer, intent(in) :: window_size
12+
integer, intent(in) :: filters
13+
character(*), intent(in) :: activation
14+
type(conv2d_layer) :: res
15+
res % window_size = window_size
16+
res % filters = filters
17+
call res % set_activation(activation)
18+
end function conv2d_layer_cons
19+
20+
21+
module subroutine init(self, input_shape)
22+
implicit none
23+
class(conv2d_layer), intent(in out) :: self
24+
integer, intent(in) :: input_shape(:)
25+
26+
self % channels = input_shape(1)
27+
self % width = input_shape(2) - self % window_size + 1
28+
self % height = input_shape(3) - self % window_size + 1
29+
30+
! Output of shape filters x width x height
31+
allocate(self % output(self % filters, self % width, self % height))
32+
self % output = 0
33+
34+
! Kernel of shape filters x channels x width x height
35+
allocate(self % kernel(self % filters, self % channels, &
36+
self % window_size, self % window_size))
37+
38+
! Initialize the kernel with random values with a normal distribution.
39+
self % kernel = randn(self % filters, self % channels, &
40+
self % window_size, self % window_size) &
41+
/ self % window_size**2 !TODO window_width * window_height
42+
43+
allocate(self % biases(self % filters))
44+
self % biases = 0
45+
46+
end subroutine init
47+
48+
49+
pure module subroutine forward(self, input)
50+
implicit none
51+
class(conv2d_layer), intent(in out) :: self
52+
real, intent(in) :: input(:,:,:)
53+
integer :: input_width, input_height, input_channels
54+
integer :: istart, iend
55+
integer :: jstart, jend
56+
integer :: i, j, n
57+
integer :: ii, jj
58+
integer :: iws, iwe, jws, jwe
59+
integer :: half_window
60+
61+
! Input dimensions are channels x width x height
62+
input_channels = size(input, dim=1)
63+
input_width = size(input, dim=2)
64+
input_height = size(input, dim=3)
65+
66+
! Half-window is 1 for window size 3; 2 for window size 5; etc.
67+
half_window = self % window_size / 2
68+
69+
! Determine the start and end indices for the width and height dimensions
70+
! of the input that correspond to the center of each window.
71+
istart = half_window + 1 ! TODO window_width
72+
jstart = half_window + 1 ! TODO window_height
73+
iend = input_width - istart + 1
74+
jend = input_height - jstart + 1
75+
76+
convolution: do concurrent(i = istart:iend, j = jstart:jend)
77+
78+
! Start and end indices of the input data on the filter window
79+
! iws and jws are also coincidentally the indices of the output matrix
80+
iws = i - half_window ! TODO window_width
81+
iwe = i + half_window ! TODO window_width
82+
jws = j - half_window ! TODO window_height
83+
jwe = j + half_window ! TODO window_height
84+
85+
! This computes the inner tensor product, sum(w_ij * x_ij), for each filter,
86+
! and we add bias b_n to it.
87+
inner_product: do concurrent(n = 1:self % filters)
88+
self % output(n,iws,jws) = &
89+
sum(self % kernel(n,:,:,:) * input(:,iws:iwe,jws:jwe)) &
90+
+ self % biases(n)
91+
end do inner_product
92+
93+
! TODO We may need to store self % output before we activate it for the backward
94+
! TODO pass, just like we do for the dense layer.
95+
96+
! Activate
97+
self % output(:,iws,jws) = self % activation(self % output(:,iws,jws))
98+
99+
end do convolution
100+
101+
end subroutine forward
102+
103+
104+
module subroutine backward(self, input, gradient)
105+
implicit none
106+
class(conv2d_layer), intent(in out) :: self
107+
real, intent(in) :: input(:,:,:)
108+
real, intent(in) :: gradient(:,:,:)
109+
print *, 'Warning: conv2d backward pass not implemented'
110+
end subroutine backward
111+
112+
end submodule nf_conv2d_layer_submodule

src/nf_layer.f90

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,17 @@ module nf_layer
2626

2727
procedure :: backward
2828
procedure :: forward
29-
procedure :: get_output
3029
procedure :: init
3130
procedure :: print_info
3231
procedure :: update
3332

33+
! Specific output subroutines for different array ranks,
34+
! available via generic `get_output`.
35+
procedure, private :: get_output_1d
36+
procedure, private :: get_output_3d
37+
38+
generic :: get_output => get_output_1d, get_output_3d
39+
3440
end type layer
3541

3642
interface
@@ -59,13 +65,22 @@ pure module subroutine forward(self, input)
5965
!! Input layer instance
6066
end subroutine forward
6167

62-
pure module subroutine get_output(self, output)
68+
pure module subroutine get_output_1d(self, output)
6369
!! Returns the output values (activations) from this layer.
6470
class(layer), intent(in) :: self
6571
!! Layer instance
6672
real, allocatable, intent(out) :: output(:)
6773
!! Output values from this layer
68-
end subroutine get_output
74+
end subroutine get_output_1d
75+
76+
pure module subroutine get_output_3d(self, output)
77+
!! Returns the output values (activations) from a layer with a 3-d output
78+
!! (e.g. input3d, conv2d)
79+
class(layer), intent(in) :: self
80+
!! Layer instance
81+
real, allocatable, intent(out) :: output(:,:,:)
82+
!! Output values from this layer
83+
end subroutine get_output_3d
6984

7085
impure elemental module subroutine init(self, input)
7186
!! Initialize the layer, using information from the input layer,

src/nf_layer_submodule.f90

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ pure module subroutine forward(self, input)
3939

4040
select type(this_layer => self % p)
4141

42-
! Only dense layer is supported for now
4342
type is(dense_layer)
4443

4544
! Input layers permitted: input1d, dense
@@ -50,12 +49,20 @@ pure module subroutine forward(self, input)
5049
call this_layer % forward(prev_layer % output)
5150
end select
5251

52+
type is(conv2d_layer)
53+
54+
! Input layers permitted: input3d
55+
select type(prev_layer => input % p)
56+
type is(input3d_layer)
57+
call this_layer % forward(prev_layer % output)
58+
end select
59+
5360
end select
5461

5562
end subroutine forward
5663

5764

58-
pure module subroutine get_output(self, output)
65+
pure module subroutine get_output_1d(self, output)
5966
class(layer), intent(in) :: self
6067
real, allocatable, intent(out) :: output(:)
6168

@@ -65,16 +72,39 @@ pure module subroutine get_output(self, output)
6572
allocate(output, source=this_layer % output)
6673
type is(dense_layer)
6774
allocate(output, source=this_layer % output)
75+
class default
76+
error stop '1-d output can only be read from an input1d or dense layer.'
6877

6978
end select
7079

71-
end subroutine get_output
80+
end subroutine get_output_1d
81+
82+
83+
pure module subroutine get_output_3d(self, output)
84+
class(layer), intent(in) :: self
85+
real, allocatable, intent(out) :: output(:,:,:)
86+
87+
select type(this_layer => self % p)
88+
89+
type is(input3d_layer)
90+
allocate(output, source=this_layer % output)
91+
type is(conv2d_layer)
92+
allocate(output, source=this_layer % output)
93+
class default
94+
error stop '3-d output can only be read from an input3d or conv2d layer.'
95+
96+
end select
97+
98+
end subroutine get_output_3d
7299

73100

74101
impure elemental module subroutine init(self, input)
75102
class(layer), intent(in out) :: self
76103
class(layer), intent(in) :: input
77104

105+
if (self % initialized) &
106+
error stop self % name // ' layer is already initialized.'
107+
78108
select type(this_layer => self % p); class default
79109
call this_layer % init(input % layer_shape)
80110
end select

0 commit comments

Comments
 (0)