Skip to content

Commit a28289f

Browse files
authored
Merge branch '10-output-batch' into development
2 parents d6448a5 + 7d81f7b commit a28289f

File tree

7 files changed

+166
-47
lines changed

7 files changed

+166
-47
lines changed

CMakeLists.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,13 +72,13 @@ string(REGEX REPLACE "^ | $" "" LIBS "${LIBS}")
7272

7373
# tests
7474
enable_testing()
75-
foreach(execid mnist network_save network_sync)
75+
foreach(execid mnist network_save network_sync set_activation_function)
7676
add_executable(test_${execid} src/tests/test_${execid}.f90)
7777
target_link_libraries(test_${execid} neural ${LIBS})
7878
add_test(test_${execid} bin/test_${execid})
7979
endforeach()
8080

81-
foreach(execid mnist montesinos_uni montesinos_multi simple sine)
81+
foreach(execid mnist montesinos_uni montesinos_multi save_and_load simple sine)
8282
add_executable(example_${execid} src/tests/example_${execid}.f90)
8383
target_link_libraries(example_${execid} neural ${LIBS})
8484
add_test(example_${execid} bin/example_${execid})

README.md

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,8 @@ cmake .. -DCMAKE_BUILD_TYPE=debug
118118

119119
### Creating a network
120120

121-
Creating a network with 3 layers (one hidden layer)
121+
Creating a network with 3 layers,
122+
one input, one hidden, and one output layer,
122123
with 3, 5, and 2 neurons each:
123124

124125
```fortran
@@ -127,8 +128,10 @@ type(network_type) :: net
127128
net = network_type([3, 5, 2])
128129
```
129130

131+
### Setting the activation function
132+
130133
By default, the network will be initialized with the sigmoid activation
131-
function. You can specify a different activation function:
134+
function for all layers. You can specify a different activation function:
132135

133136
```fortran
134137
net = network_type([3, 5, 2], activation='tanh')
@@ -141,6 +144,16 @@ net = network_type([3, 5, 2])
141144
call net % set_activation('tanh')
142145
```
143146

147+
It's possible to set different activation functions for each layer.
148+
For example, this snippet will create a network with a Gaussian
149+
activation functions for all layers except the output layer,
150+
and a RELU function for the output layer:
151+
152+
```fortran
153+
net = network_type([3, 5, 2], activation='gaussian')
154+
call net % layers(3) % set_activation('relu')
155+
```
156+
144157
Available activation function options are: `gaussian`, `relu`, `sigmoid`,
145158
`step`, and `tanh`.
146159
See [mod_activation.f90](https://github.com/modern-fortran/neural-fortran/blob/master/src/lib/mod_activation.f90)

src/lib/mod_activation.f90

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,21 @@ module mod_activation
88

99
private
1010

11+
public :: activation_function
1112
public :: gaussian, gaussian_prime
1213
public :: relu, relu_prime
1314
public :: sigmoid, sigmoid_prime
1415
public :: step, step_prime
1516
public :: tanhf, tanh_prime
1617

18+
interface
19+
pure function activation_function(x)
20+
import :: rk
21+
real(rk), intent(in) :: x(:)
22+
real(rk) :: activation_function(size(x))
23+
end function activation_function
24+
end interface
25+
1726
contains
1827

1928
pure function gaussian(x) result(res)

src/lib/mod_layer.f90

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ module mod_layer
22

33
! Defines the layer type and its methods.
44

5+
use mod_activation
56
use mod_kinds, only: ik, rk
67
use mod_random, only: randn
78

@@ -15,6 +16,10 @@ module mod_layer
1516
real(rk), allocatable :: b(:) ! biases
1617
real(rk), allocatable :: w(:,:) ! weights
1718
real(rk), allocatable :: z(:) ! arg. to activation function
19+
procedure(activation_function), pointer, nopass :: activation => null()
20+
procedure(activation_function), pointer, nopass :: activation_prime => null()
21+
contains
22+
procedure, public, pass(self) :: set_activation
1823
end type layer_type
1924

2025
type :: array1d
@@ -110,4 +115,32 @@ subroutine dw_co_sum(dw)
110115
end do
111116
end subroutine dw_co_sum
112117

118+
pure subroutine set_activation(self, activation)
119+
! Sets the activation function. Input string must match one of
120+
! provided activation functions, otherwise it defaults to sigmoid.
121+
! If activation not present, defaults to sigmoid.
122+
class(layer_type), intent(in out) :: self
123+
character(len=*), intent(in) :: activation
124+
select case(trim(activation))
125+
case('gaussian')
126+
self % activation => gaussian
127+
self % activation_prime => gaussian_prime
128+
case('relu')
129+
self % activation => relu
130+
self % activation_prime => relu_prime
131+
case('sigmoid')
132+
self % activation => sigmoid
133+
self % activation_prime => sigmoid_prime
134+
case('step')
135+
self % activation => step
136+
self % activation_prime => step_prime
137+
case('tanh')
138+
self % activation => tanhf
139+
self % activation_prime => tanh_prime
140+
case default
141+
self % activation => sigmoid
142+
self % activation_prime => sigmoid_prime
143+
end select
144+
end subroutine set_activation
145+
113146
end module mod_layer

src/lib/mod_network.f90

Lines changed: 12 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,5 @@
11
module mod_network
22

3-
use mod_activation, only: gaussian, gaussian_prime,&
4-
relu, relu_prime,&
5-
sigmoid, sigmoid_prime,&
6-
step, step_prime,&
7-
tanhf, tanh_prime
83
use mod_kinds, only: ik, rk
94
use mod_layer, only: array1d, array2d, db_init, dw_init,&
105
db_co_sum, dw_co_sum, layer_type
@@ -19,8 +14,6 @@ module mod_network
1914

2015
type(layer_type), allocatable :: layers(:)
2116
integer, allocatable :: dims(:)
22-
procedure(activation_function), pointer, nopass :: activation => null()
23-
procedure(activation_function), pointer, nopass :: activation_prime => null()
2417

2518
contains
2619

@@ -49,14 +42,6 @@ module mod_network
4942
module procedure :: net_constructor
5043
endinterface network_type
5144

52-
interface
53-
pure function activation_function(x)
54-
import :: rk
55-
real(rk), intent(in) :: x(:)
56-
real(rk) :: activation_function(size(x))
57-
end function activation_function
58-
end interface
59-
6045
contains
6146

6247
type(network_type) function net_constructor(dims, activation) result(net)
@@ -105,13 +90,13 @@ pure subroutine backprop(self, y, dw, db)
10590
call dw_init(dw, dims)
10691

10792
n = size(dims)
108-
db(n) % array = (layers(n) % a - y) * self % activation_prime(layers(n) % z)
93+
db(n) % array = (layers(n) % a - y) * self % layers(n) % activation_prime(layers(n) % z)
10994
dw(n-1) % array = matmul(reshape(layers(n-1) % a, [dims(n-1), 1]),&
11095
reshape(db(n) % array, [1, dims(n)]))
11196

11297
do n = size(dims) - 1, 2, -1
11398
db(n) % array = matmul(layers(n) % w, db(n+1) % array)&
114-
* self % activation_prime(layers(n) % z)
99+
* self % layers(n) % activation_prime(layers(n) % z)
115100
dw(n-1) % array = matmul(reshape(layers(n-1) % a, [dims(n-1), 1]),&
116101
reshape(db(n) % array, [1, dims(n)]))
117102
end do
@@ -130,7 +115,7 @@ pure subroutine fwdprop(self, x)
130115
layers(1) % a = x
131116
do n = 2, size(layers)
132117
layers(n) % z = matmul(transpose(layers(n-1) % w), layers(n-1) % a) + layers(n) % b
133-
layers(n) % a = self % activation(layers(n) % z)
118+
layers(n) % a = self % layers(n) % activation(layers(n) % z)
134119
end do
135120
end associate
136121
end subroutine fwdprop
@@ -184,9 +169,9 @@ pure function output_single(self, x) result(a)
184169
real(rk), allocatable :: a(:)
185170
integer(ik) :: n
186171
associate(layers => self % layers)
187-
a = self % activation(matmul(transpose(layers(1) % w), x) + layers(2) % b)
172+
a = self % layers(2) % activation(matmul(transpose(layers(1) % w), x) + layers(2) % b)
188173
do n = 3, size(layers)
189-
a = self % activation(matmul(transpose(layers(n-1) % w), a) + layers(n) % b)
174+
a = self % layers(n) % activation(matmul(transpose(layers(n-1) % w), a) + layers(n) % b)
190175
end do
191176
end associate
192177
end function output_single
@@ -223,31 +208,15 @@ subroutine save(self, filename)
223208
end subroutine save
224209

225210
pure subroutine set_activation(self, activation)
226-
! Sets the activation functions. Input string must match one of
227-
! provided activation functions, otherwise it defaults to sigmoid.
228-
! If activation not present, defaults to sigmoid.
211+
! A thin wrapper around layer % set_activation().
212+
! This method can be used to set an activation function
213+
! for all layers at once.
229214
class(network_type), intent(in out) :: self
230215
character(len=*), intent(in) :: activation
231-
select case(trim(activation))
232-
case('gaussian')
233-
self % activation => gaussian
234-
self % activation_prime => gaussian_prime
235-
case('relu')
236-
self % activation => relu
237-
self % activation_prime => relu_prime
238-
case('sigmoid')
239-
self % activation => sigmoid
240-
self % activation_prime => sigmoid_prime
241-
case('step')
242-
self % activation => step
243-
self % activation_prime => step_prime
244-
case('tanh')
245-
self % activation => tanhf
246-
self % activation_prime => tanh_prime
247-
case default
248-
self % activation => sigmoid
249-
self % activation_prime => sigmoid_prime
250-
end select
216+
integer :: n
217+
do concurrent(n = 1:size(self % layers))
218+
call self % layers(n) % set_activation(activation)
219+
end do
251220
end subroutine set_activation
252221

253222
subroutine sync(self, image)
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
program example_save_and_load
2+
3+
use mod_network, only: network_type
4+
implicit none
5+
6+
type(network_type) :: net1, net2
7+
real, allocatable :: input(:), output(:)
8+
integer :: i
9+
10+
net1 = network_type([3, 5, 2])
11+
12+
input = [0.2, 0.4, 0.6]
13+
output = [0.123456, 0.246802]
14+
15+
! train network 1
16+
do i = 1, 500
17+
call net1 % train(input, output, eta=1.0)
18+
end do
19+
20+
! save network 1 to file
21+
call net1 % save('my_simple_net.txt')
22+
23+
! load network 2 from file
24+
!net2 = network_type([3, 5, 2])
25+
call net2 % load('my_simple_net.txt')
26+
call net2 % set_activation('sigmoid')
27+
28+
print *, 'Network 1 output: ', net1 % output(input)
29+
print *, 'Network 2 output: ', net2 % output(input)
30+
print *, 'Outputs match: ', all(net1 % output(input) == net2 % output(input))
31+
32+
end program example_save_and_load
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
program test_set_activation_function
2+
3+
! This program will test whether per-network and per-layer
4+
! setting of activation functions works as expected.
5+
! First we create an array of random variables.
6+
! Then we set different activation functions to different
7+
! layers in the network.
8+
! Finally, we test whether each function produces same
9+
! values as the activation functions set in the layers.
10+
11+
use mod_activation
12+
use mod_network, only: network_type
13+
use mod_random, only: randn
14+
15+
implicit none
16+
type(network_type) :: net
17+
real, allocatable :: x(:)
18+
integer :: n
19+
logical, allocatable :: tests(:)
20+
21+
tests = [logical ::]
22+
23+
x = randn(100)
24+
25+
! the network will be created with
26+
! sigmoid activation functions for all layers
27+
net = network_type([1, 1, 1, 1, 1])
28+
29+
do n = 1, size(net % layers)
30+
tests = [tests, all(sigmoid(x) == net % layers(n) % activation(x))]
31+
tests = [tests, all(sigmoid_prime(x) == net % layers(n) % activation_prime(x))]
32+
end do
33+
34+
! now set the various functions for other layers
35+
call net % layers(2) % set_activation('gaussian')
36+
call net % layers(3) % set_activation('step')
37+
call net % layers(4) % set_activation('tanh')
38+
call net % layers(5) % set_activation('relu')
39+
40+
tests = [tests, all(sigmoid(x) == net % layers(1) % activation(x))]
41+
tests = [tests, all(sigmoid_prime(x) == net % layers(1) % activation_prime(x))]
42+
43+
tests = [tests, all(gaussian(x) == net % layers(2) % activation(x))]
44+
tests = [tests, all(gaussian_prime(x) == net % layers(2) % activation_prime(x))]
45+
46+
tests = [tests, all(step(x) == net % layers(3) % activation(x))]
47+
tests = [tests, all(step_prime(x) == net % layers(3) % activation_prime(x))]
48+
49+
tests = [tests, all(tanhf(x) == net % layers(4) % activation(x))]
50+
tests = [tests, all(tanh_prime(x) == net % layers(4) % activation_prime(x))]
51+
52+
tests = [tests, all(relu(x) == net % layers(5) % activation(x))]
53+
tests = [tests, all(relu_prime(x) == net % layers(5) % activation_prime(x))]
54+
55+
print *, tests
56+
57+
if (all(tests)) then
58+
print *, 'All tests passed.'
59+
else
60+
error stop 'some tests failed.'
61+
end if
62+
63+
end program test_set_activation_function

0 commit comments

Comments
 (0)