Skip to content

Commit a6bbeff

Browse files
authored
Merge pull request #11 from modern-fortran/7-allow-activation-per-layer
7 allow activation per layer
2 parents ef9efbc + ea47542 commit a6bbeff

File tree

6 files changed

+133
-46
lines changed

6 files changed

+133
-46
lines changed

CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ 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})

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

@@ -46,14 +39,6 @@ module mod_network
4639
module procedure :: net_constructor
4740
endinterface network_type
4841

49-
interface
50-
pure function activation_function(x)
51-
import :: rk
52-
real(rk), intent(in) :: x(:)
53-
real(rk) :: activation_function(size(x))
54-
end function activation_function
55-
end interface
56-
5742
contains
5843

5944
type(network_type) function net_constructor(dims, activation) result(net)
@@ -102,13 +87,13 @@ pure subroutine backprop(self, y, dw, db)
10287
call dw_init(dw, dims)
10388

10489
n = size(dims)
105-
db(n) % array = (layers(n) % a - y) * self % activation_prime(layers(n) % z)
90+
db(n) % array = (layers(n) % a - y) * self % layers(n) % activation_prime(layers(n) % z)
10691
dw(n-1) % array = matmul(reshape(layers(n-1) % a, [dims(n-1), 1]),&
10792
reshape(db(n) % array, [1, dims(n)]))
10893

10994
do n = size(dims) - 1, 2, -1
11095
db(n) % array = matmul(layers(n) % w, db(n+1) % array)&
111-
* self % activation_prime(layers(n) % z)
96+
* self % layers(n) % activation_prime(layers(n) % z)
11297
dw(n-1) % array = matmul(reshape(layers(n-1) % a, [dims(n-1), 1]),&
11398
reshape(db(n) % array, [1, dims(n)]))
11499
end do
@@ -127,7 +112,7 @@ pure subroutine fwdprop(self, x)
127112
layers(1) % a = x
128113
do n = 2, size(layers)
129114
layers(n) % z = matmul(transpose(layers(n-1) % w), layers(n-1) % a) + layers(n) % b
130-
layers(n) % a = self % activation(layers(n) % z)
115+
layers(n) % a = self % layers(n) % activation(layers(n) % z)
131116
end do
132117
end associate
133118
end subroutine fwdprop
@@ -181,9 +166,9 @@ pure function output(self, x) result(a)
181166
real(rk), allocatable :: a(:)
182167
integer(ik) :: n
183168
associate(layers => self % layers)
184-
a = self % activation(matmul(transpose(layers(1) % w), x) + layers(2) % b)
169+
a = self % layers(2) % activation(matmul(transpose(layers(1) % w), x) + layers(2) % b)
185170
do n = 3, size(layers)
186-
a = self % activation(matmul(transpose(layers(n-1) % w), a) + layers(n) % b)
171+
a = self % layers(n) % activation(matmul(transpose(layers(n-1) % w), a) + layers(n) % b)
187172
end do
188173
end associate
189174
end function output
@@ -206,31 +191,15 @@ subroutine save(self, filename)
206191
end subroutine save
207192

208193
pure subroutine set_activation(self, activation)
209-
! Sets the activation functions. Input string must match one of
210-
! provided activation functions, otherwise it defaults to sigmoid.
211-
! If activation not present, defaults to sigmoid.
194+
! A thin wrapper around layer % set_activation().
195+
! This method can be used to set an activation function
196+
! for all layers at once.
212197
class(network_type), intent(in out) :: self
213198
character(len=*), intent(in) :: activation
214-
select case(trim(activation))
215-
case('gaussian')
216-
self % activation => gaussian
217-
self % activation_prime => gaussian_prime
218-
case('relu')
219-
self % activation => relu
220-
self % activation_prime => relu_prime
221-
case('sigmoid')
222-
self % activation => sigmoid
223-
self % activation_prime => sigmoid_prime
224-
case('step')
225-
self % activation => step
226-
self % activation_prime => step_prime
227-
case('tanh')
228-
self % activation => tanhf
229-
self % activation_prime => tanh_prime
230-
case default
231-
self % activation => sigmoid
232-
self % activation_prime => sigmoid_prime
233-
end select
199+
integer :: n
200+
do concurrent(n = 1:size(self % layers))
201+
call self % layers(n) % set_activation(activation)
202+
end do
234203
end subroutine set_activation
235204

236205
subroutine sync(self, image)
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)