Skip to content
Merged
Show file tree
Hide file tree
Changes from 36 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
1fb279a
linear2d_layer forward implementation
OneAdder Feb 2, 2025
d997b6b
implement backward
OneAdder Feb 2, 2025
9919c01
introduce concurrency, outtroduce stupidity
OneAdder Feb 2, 2025
43d1a1f
fix style
OneAdder Feb 2, 2025
906f21b
add parameters api to linear2d_layer
OneAdder Feb 3, 2025
e1b4695
add constructor for linear2d_layer
OneAdder Feb 3, 2025
0fe2ef0
add integration for linear2d layer
OneAdder Feb 3, 2025
957095d
set usage rules for linear2d_layer
OneAdder Feb 3, 2025
eff36fe
add linear2d_layer to public api
OneAdder Feb 3, 2025
b6f3c97
update tests for linear2d layer
OneAdder Feb 3, 2025
541d943
remove extra comment
OneAdder Feb 3, 2025
a27ec09
remove rubbish
OneAdder Feb 3, 2025
79abce3
move linear2d layer logic into submodule
OneAdder Feb 4, 2025
2168ec9
update cmake for linear2d_layer
OneAdder Feb 4, 2025
9a13af3
update tests for linear2d_layer
OneAdder Feb 5, 2025
0db76db
update linear2d_layer tests
OneAdder Feb 5, 2025
f28ecc0
update linear2d_layer tests for batch last
OneAdder Feb 5, 2025
9386aa3
make linear2d_layer with batch as last dimension (performance)
OneAdder Feb 5, 2025
07750db
linear2d_layer: fix gradient updates
OneAdder Feb 12, 2025
b5a600a
linear2d_layer: make it 2d
OneAdder Feb 14, 2025
dd1297e
linear2d_layer: forgot a file
OneAdder Feb 14, 2025
e4cb526
linear2d_layer: temporarily remove api
OneAdder Feb 14, 2025
86ec628
Don't expose the concrete layer type via nf
milancurcic Feb 16, 2025
d40aebb
Report success to stdout
milancurcic Feb 16, 2025
b803553
Include linear2d test in cmake
milancurcic Feb 16, 2025
f1a01a6
Add Linear2d to README
milancurcic Feb 16, 2025
b39e6da
Plumbing of linear2d with input2d and linear2d
milancurcic Feb 16, 2025
1bec531
linear2d_layer: add flatten2d layer
OneAdder Feb 16, 2025
d01a174
linear2d_layer: make linear2d layer work with input2d and flatten2d
OneAdder Feb 16, 2025
141fe57
update cmake
OneAdder Feb 16, 2025
c4b8fc7
linear2d_layer: use flatten layer instead of flatten2d
OneAdder Feb 16, 2025
54d1bb0
linear2d_layer: remove flatten2d layer
OneAdder Feb 16, 2025
9a4422f
linear2d_layer: remove public api
OneAdder Feb 16, 2025
7606d2c
linear2d_layer: update cmakelists
OneAdder Feb 16, 2025
7d271fe
linear2d_layer: workaround cpu imprecision to make ci happy
OneAdder Feb 16, 2025
539fde8
Add linear2d example
milancurcic Feb 17, 2025
a97f141
linear2d_layer: remove redundant constructor args
OneAdder Feb 17, 2025
bbfaf3c
linear2d_layer: make example converge
OneAdder Feb 17, 2025
4d28a0a
linear2d_layer: make weighs init with normal distribution
OneAdder Feb 17, 2025
bfc69d5
linear2d_layer: add loss stopping and more iterations
OneAdder Feb 17, 2025
119a6c8
linear2d_layer: update tests
OneAdder Feb 17, 2025
6f33ebe
Tidy up
milancurcic Feb 17, 2025
678b2c0
Require passing only out_features to linear2d(); tidy up
milancurcic Feb 17, 2025
e78ef62
Remove linear2d example
milancurcic Feb 17, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ add_library(neural-fortran
src/nf/nf_layer_constructors_submodule.f90
src/nf/nf_layer.f90
src/nf/nf_layer_submodule.f90
src/nf/nf_linear2d_layer.f90
src/nf/nf_linear2d_layer_submodule.f90
src/nf/nf_loss.f90
src/nf/nf_loss_submodule.f90
src/nf/nf_maxpool2d_layer.f90
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ Read the paper [here](https://arxiv.org/abs/1902.06714).
| Convolutional (2-d) | `conv2d` | `input3d`, `conv2d`, `maxpool2d`, `reshape` | 3 | ✅ | ✅(*) |
| Max-pooling (2-d) | `maxpool2d` | `input3d`, `conv2d`, `maxpool2d`, `reshape` | 3 | ✅ | ✅ |
| Flatten | `flatten` | `input2d`, `input3d`, `conv2d`, `maxpool2d`, `reshape` | 1 | ✅ | ✅ |
| Linear (2-d) | `linear2d` | `input2d` | 2 | ✅ | ✅ |
| Reshape (1-d to 3-d) | `reshape` | `input1d`, `dense`, `flatten` | 3 | ✅ | ✅ |

(*) See Issue [#145](https://github.com/modern-fortran/neural-fortran/issues/145) regarding non-converging CNN training on the MNIST dataset.
Expand Down
1 change: 1 addition & 0 deletions example/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ foreach(execid
cnn_mnist
dense_mnist
get_set_network_params
linear2d
network_parameters
simple
sine
Expand Down
29 changes: 29 additions & 0 deletions example/linear2d.f90
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
program linear2d_example

use nf, only: input, network, sgd, linear2d, mse, flatten
implicit none

type(network) :: net
real :: x(3, 4) = reshape( &
[0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2], &
[3, 4])
real :: y(3) = [0.12, 0.1, 0.3]
integer, parameter :: num_iterations = 500
integer :: n

net = network([ &
input(3, 4), &
linear2d(3, 4, 1), &
flatten() &
])

call net % print_info()

do n = 1, num_iterations
call net % forward(x)
call net % backward(y, mse())
call net % update(optimizer=sgd(learning_rate=1.))
print '(i4,3(3x,f8.6))', n, net % predict(x)
end do

end program linear2d_example
2 changes: 1 addition & 1 deletion src/nf.f90
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module nf
use nf_datasets_mnist, only: label_digits, load_mnist
use nf_layer, only: layer
use nf_layer_constructors, only: &
conv2d, dense, flatten, input, maxpool2d, reshape
conv2d, dense, flatten, input, maxpool2d, reshape, linear2d
use nf_loss, only: mse, quadratic
use nf_metrics, only: corr, maxabs
use nf_network, only: network
Expand Down
7 changes: 6 additions & 1 deletion src/nf/nf_layer_constructors.f90
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ module nf_layer_constructors
implicit none

private
public :: conv2d, dense, flatten, input, maxpool2d, reshape
public :: conv2d, dense, flatten, input, maxpool2d, reshape, linear2d

interface input

Expand Down Expand Up @@ -185,6 +185,11 @@ module function reshape(output_shape) result(res)
!! Resulting layer instance
end function reshape

module function linear2d(sequence_length, in_features, out_features) result(res)
integer, intent(in) :: sequence_length, in_features, out_features
type(layer) :: res
end function linear2d

end interface

end module nf_layer_constructors
11 changes: 11 additions & 0 deletions src/nf/nf_layer_constructors_submodule.f90
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use nf_input3d_layer, only: input3d_layer
use nf_maxpool2d_layer, only: maxpool2d_layer
use nf_reshape_layer, only: reshape3d_layer
use nf_linear2d_layer, only: linear2d_layer
use nf_activation, only: activation_function, relu, sigmoid

implicit none
Expand Down Expand Up @@ -71,6 +72,7 @@ module function flatten() result(res)
end function flatten



module function input1d(layer_size) result(res)
integer, intent(in) :: layer_size
type(layer) :: res
Expand Down Expand Up @@ -148,4 +150,13 @@ module function reshape(output_shape) result(res)

end function reshape

module function linear2d(sequence_length, in_features, out_features) result(res)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here we should be requesting only out_features at layer constructor invocation, and sequence_length and in_features are obtained later from the layer that feeds into this one.

Suggested change
module function linear2d(sequence_length, in_features, out_features) result(res)
module function linear2d(out_features) result(res)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, makes sense

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It turns out that we cannot avoid passing sequence_length. We need it to determine the output shape. I think it's not a big deal and can be left like this

  module function linear2d(sequence_length, out_features) result(res)
    integer, intent(in) :: sequence_length, out_features
    type(layer) :: res

    res % name = 'linear2d'
    res % layer_shape = [sequence_length, out_features]
    allocate(res % p, source=linear2d_layer(out_features))
  end function linear2d

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just gave it a try; unless I'm mistaken, I think it can work. See this commit: 678b2c0

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll try to explain; the process goes like this:

  1. linear2d(out_features) constructs the generic layer instance which at this time does not yet know its layer_shape.
  2. network constructor from an array of generic layers loops over each layer in order and calls layer % init(prev_layer).
  3. Inside layer % init the output shape of the previous layer is passed to the concrete linear2d_layer % init(input_shape) and inside the concrete init, all parameters are now known (sequence_length, in_features, out_features.
  4. After the concrete layer init call, back inside the generic layer % init, we set the generic layer % layer_shape to be the same as the shape of the concrete linear2d_layer % output.

I hope this make sense and I'm sorry that it had to be so complicated. However, we are essentially hacking around Fortran's very limited generic features.

integer, intent(in) :: sequence_length, in_features, out_features
type(layer) :: res

res % name = 'linear2d'
res % layer_shape = [sequence_length, out_features]
allocate(res % p, source=linear2d_layer(sequence_length, in_features, out_features))
end function linear2d

end submodule nf_layer_constructors_submodule
49 changes: 43 additions & 6 deletions src/nf/nf_layer_submodule.f90
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use nf_input3d_layer, only: input3d_layer
use nf_maxpool2d_layer, only: maxpool2d_layer
use nf_reshape_layer, only: reshape3d_layer
use nf_linear2d_layer, only: linear2d_layer
use nf_optimizers, only: optimizer_base_type

contains
Expand Down Expand Up @@ -47,6 +48,8 @@ pure module subroutine backward_1d(self, previous, gradient)
call this_layer % backward(prev_layer % output, gradient)
type is(maxpool2d_layer)
call this_layer % backward(prev_layer % output, gradient)
type is(linear2d_layer)
call this_layer % backward(prev_layer % output, gradient)
end select

end select
Expand All @@ -60,9 +63,19 @@ pure module subroutine backward_2d(self, previous, gradient)
class(layer), intent(in) :: previous
real, intent(in) :: gradient(:,:)

! Backward pass from a 2-d layer downstream currently implemented
! only for dense and flatten layers
! CURRENTLY NO LAYERS, tbd: pull/197 and pull/199
select type(this_layer => self % p)

type is(linear2d_layer)

select type(prev_layer => previous % p)
type is(input2d_layer)
call this_layer % backward(prev_layer % output, gradient)
type is(linear2d_layer)
call this_layer % backward(prev_layer % output, gradient)
end select

end select

end subroutine backward_2d


Expand Down Expand Up @@ -182,6 +195,8 @@ pure module subroutine forward(self, input)
call this_layer % forward(prev_layer % output)
type is(reshape3d_layer)
call this_layer % forward(prev_layer % output)
type is(linear2d_layer)
call this_layer % forward(prev_layer % output)
end select

type is(reshape3d_layer)
Expand All @@ -196,6 +211,16 @@ pure module subroutine forward(self, input)
call this_layer % forward(prev_layer % output)
end select

type is(linear2d_layer)

! Upstream layers permitted: input2d, linear2d
select type(prev_layer => input % p)
type is(input2d_layer)
call this_layer % forward(prev_layer % output)
type is(linear2d_layer)
call this_layer % forward(prev_layer % output)
end select

end select

end subroutine forward
Expand Down Expand Up @@ -231,8 +256,10 @@ pure module subroutine get_output_2d(self, output)

type is(input2d_layer)
allocate(output, source=this_layer % output)
type is(linear2d_layer)
allocate(output, source=this_layer % output)
class default
error stop '1-d output can only be read from an input1d, dense, or flatten layer.'
error stop '2-d output can only be read from an input2d or linear2d layer.'

end select

Expand Down Expand Up @@ -285,7 +312,7 @@ impure elemental module subroutine init(self, input)
self % layer_shape = shape(this_layer % output)
end select

self % input_layer_shape = input % layer_shape
self % input_layer_shape = input % layer_shape
self % initialized = .true.

end subroutine init
Expand Down Expand Up @@ -328,6 +355,8 @@ elemental module function get_num_params(self) result(num_params)
num_params = 0
type is (reshape3d_layer)
num_params = 0
type is (linear2d_layer)
num_params = this_layer % get_num_params()
class default
error stop 'Unknown layer type.'
end select
Expand Down Expand Up @@ -355,6 +384,8 @@ module function get_params(self) result(params)
! No parameters to get.
type is (reshape3d_layer)
! No parameters to get.
type is (linear2d_layer)
params = this_layer % get_params()
class default
error stop 'Unknown layer type.'
end select
Expand All @@ -379,9 +410,11 @@ module function get_gradients(self) result(gradients)
type is (maxpool2d_layer)
! No gradients to get.
type is (flatten_layer)
! No gradients to get.
! No parameters to get.
type is (reshape3d_layer)
! No gradients to get.
type is (linear2d_layer)
gradients = this_layer % get_gradients()
class default
error stop 'Unknown layer type.'
end select
Expand Down Expand Up @@ -429,6 +462,9 @@ module subroutine set_params(self, params)
type is (conv2d_layer)
call this_layer % set_params(params)

type is (linear2d_layer)
call this_layer % set_params(params)

type is (maxpool2d_layer)
! No parameters to set.
write(stderr, '(a)') 'Warning: calling set_params() ' &
Expand All @@ -446,6 +482,7 @@ module subroutine set_params(self, params)

class default
error stop 'Unknown layer type.'

end select

end subroutine set_params
Expand Down
79 changes: 79 additions & 0 deletions src/nf/nf_linear2d_layer.f90
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
module nf_linear2d_layer

use nf_activation, only: activation_function
use nf_base_layer, only: base_layer

implicit none

private
public :: linear2d_layer

type, extends(base_layer) :: linear2d_layer
integer :: sequence_length, in_features, out_features, batch_size

real, allocatable :: weights(:, :)
real, allocatable :: biases(:)
real, allocatable :: output(:, :)
real, allocatable :: gradient(:, :) ! input gradient
real, allocatable :: dw(:, :) ! weight gradients
real, allocatable :: db(:) ! bias gradients

contains

procedure :: backward
procedure :: forward
procedure :: init
procedure :: get_num_params
procedure :: get_params
procedure :: get_gradients
procedure :: set_params

end type linear2d_layer

interface linear2d_layer
module function linear2d_layer_cons(&
sequence_length, in_features, out_features&
) result(res)
integer, intent(in) :: sequence_length, in_features, out_features
type(linear2d_layer) :: res
end function linear2d_layer_cons
end interface linear2d_layer

interface
pure module subroutine forward(self, input)
class(linear2d_layer), intent(in out) :: self
real, intent(in) :: input(:, :)
end subroutine forward

pure module subroutine backward(self, input, gradient)
class(linear2d_layer), intent(in out) :: self
real, intent(in) :: input(:, :)
real, intent(in) :: gradient(:, :)
end subroutine backward

module subroutine init(self, input_shape)
class(linear2d_layer), intent(in out) :: self
integer, intent(in) :: input_shape(:)
end subroutine init

pure module function get_num_params(self) result(num_params)
class(linear2d_layer), intent(in) :: self
integer :: num_params
end function get_num_params

module function get_params(self) result(params)
class(linear2d_layer), intent(in), target :: self
real, allocatable :: params(:)
end function get_params

module function get_gradients(self) result(gradients)
class(linear2d_layer), intent(in), target :: self
real, allocatable :: gradients(:)
end function get_gradients

module subroutine set_params(self, params)
class(linear2d_layer), intent(in out) :: self
real, intent(in), target :: params(:)
end subroutine set_params
end interface
end module nf_linear2d_layer
Loading