Skip to content

Commit a878d03

Browse files
authored
Merge pull request #85 from milancurcic/keras-reader-cnn
Read Conv2D, MaxPooling2D, and Flatten layers from Keras
2 parents 94cc86b + 0577f82 commit a878d03

18 files changed

+446
-103
lines changed

CMakeLists.txt

+8-1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ include(FetchContent)
1717
include(cmake/options.cmake)
1818
include(cmake/compilers.cmake)
1919

20+
include(cmake/functional.cmake)
2021
include(cmake/h5fortran.cmake)
2122
include(cmake/json.cmake)
2223

@@ -62,7 +63,13 @@ add_library(neural
6263
src/nf/io/nf_io_hdf5.f90
6364
src/nf/io/nf_io_hdf5_submodule.f90
6465
)
65-
target_link_libraries(neural PRIVATE h5fortran::h5fortran HDF5::HDF5 jsonfortran::jsonfortran)
66+
67+
target_link_libraries(neural PRIVATE
68+
functional::functional
69+
h5fortran::h5fortran
70+
HDF5::HDF5
71+
jsonfortran::jsonfortran
72+
)
6673

6774
install(TARGETS neural)
6875

README.md

+14-8
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
[![GitHub issues](https://img.shields.io/github/issues/modern-fortran/neural-fortran.svg)](https://github.com/modern-fortran/neural-fortran/issues)
44

5-
A parallel neural net microframework.
5+
A parallel framework for deep learning.
66
Read the paper [here](https://arxiv.org/abs/1902.06714).
77

88
* [Features](https://github.com/modern-fortran/neural-fortran#features)
@@ -18,9 +18,11 @@ Read the paper [here](https://arxiv.org/abs/1902.06714).
1818

1919
* Dense, fully connected neural layers
2020
* Convolutional and max-pooling layers (experimental, forward propagation only)
21+
* Flatten layers (forward and backward pass)
22+
* Loading dense and convolutional models from Keras h5 files
2123
* Stochastic and mini-batch gradient descent for back-propagation
2224
* Data-based parallelism
23-
* Several activation functions
25+
* Several activation functions and their derivatives
2426

2527
### Available layer types
2628

@@ -48,16 +50,18 @@ Required dependencies are:
4850
* A Fortran compiler
4951
* [HDF5](https://www.hdfgroup.org/downloads/hdf5/)
5052
(must be provided by the OS package manager or your own build from source)
51-
* [h5fortran](https://github.com/geospace-code/h5fortran),
53+
* [functional-fortran](https://github.com/wavebitscientific/functional-fortran),
54+
[h5fortran](https://github.com/geospace-code/h5fortran),
5255
[json-fortran](https://github.com/jacobwilliams/json-fortran)
53-
(both handled by neural-fortran's build systems, no need for a manual install)
56+
(all handled by neural-fortran's build systems, no need for a manual install)
5457
* [fpm](https://github.com/fortran-lang/fpm) or
5558
[CMake](https://cmake.org) for building the code
5659

5760
Optional dependencies are:
5861

5962
* OpenCoarrays (for parallel execution with GFortran)
60-
* BLAS, MKL (optional)
63+
* BLAS, MKL, or similar (for offloading `matmul` and `dot_product` calls)
64+
* curl (for downloading testing and example datasets)
6165

6266
Compilers tested include:
6367

@@ -200,13 +204,15 @@ examples, in increasing level of complexity:
200204
dataset
201205
4. [cnn](example/cnn.f90): Creating and running forward a simple CNN using
202206
`input`, `conv2d`, `maxpool2d`, `flatten`, and `dense` layers.
203-
5. [mnist_from_keras](example/mnist_from_keras.f90): Creating a pre-trained
204-
model from a Keras HDF5 file.
207+
5. [dense_from_keras](example/dense_from_keras.f90): Creating a pre-trained
208+
dense model from a Keras HDF5 file and running the inference.
209+
6. [cnn_from_keras](example/cnn_from_keras.f90): Creating a pre-trained
210+
convolutional model from a Keras HDF5 file and running the inference.
205211

206212
The examples also show you the extent of the public API that's meant to be
207213
used in applications, i.e. anything from the `nf` module.
208214

209-
The MNIST example uses [curl](https://curl.se/) to download the dataset,
215+
Examples 3-6 rely on [curl](https://curl.se/) to download the needed datasets,
210216
so make sure you have it installed on your system.
211217
Most Linux OSs have it out of the box.
212218
The dataset will be downloaded only the first time you run the example in any

cmake/functional.cmake

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
FetchContent_Declare(functional
2+
GIT_REPOSITORY https://github.com/wavebitscientific/functional-fortran
3+
GIT_TAG 0.6.1
4+
GIT_SHALLOW true
5+
)
6+
7+
FetchContent_Populate(functional)
8+
9+
add_library(functional ${functional_SOURCE_DIR}/src/functional.f90)
10+
target_include_directories(functional PUBLIC
11+
$<BUILD_INTERFACE:${CMAKE_BINARY_DIR}/include>
12+
$<INSTALL_INTERFACE:include>
13+
)
14+
15+
add_library(functional::functional INTERFACE IMPORTED GLOBAL)
16+
target_link_libraries(functional::functional INTERFACE functional)
17+
18+
install(TARGETS functional)

example/CMakeLists.txt

+14-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,16 @@
1-
foreach(execid cnn mnist mnist_from_keras simple sine)
1+
foreach(execid
2+
cnn
3+
cnn_from_keras
4+
dense_from_keras
5+
mnist
6+
simple
7+
sine
8+
)
29
add_executable(${execid} ${execid}.f90)
3-
target_link_libraries(${execid} PRIVATE neural h5fortran::h5fortran jsonfortran::jsonfortran ${LIBS})
10+
target_link_libraries(${execid} PRIVATE
11+
neural
12+
h5fortran::h5fortran
13+
jsonfortran::jsonfortran
14+
${LIBS}
15+
)
416
endforeach()

example/cnn_from_keras.f90

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
program cnn_from_keras
2+
3+
! This example demonstrates loading a convolutional model
4+
! pre-trained on the MNIST dataset from a Keras HDF5
5+
! file and running an inferrence on the testing dataset.
6+
7+
use nf, only: network, label_digits, load_mnist
8+
use nf_datasets, only: download_and_unpack, keras_cnn_mnist_url
9+
10+
implicit none
11+
12+
type(network) :: net
13+
real, allocatable :: training_images(:,:), training_labels(:)
14+
real, allocatable :: validation_images(:,:), validation_labels(:)
15+
real, allocatable :: testing_images(:,:), testing_labels(:)
16+
character(*), parameter :: keras_cnn_path = 'keras_cnn_mnist.h5'
17+
logical :: file_exists
18+
real :: acc
19+
20+
inquire(file=keras_cnn_path, exist=file_exists)
21+
if (.not. file_exists) call download_and_unpack(keras_cnn_mnist_url)
22+
23+
call load_mnist(training_images, training_labels, &
24+
validation_images, validation_labels, &
25+
testing_images, testing_labels)
26+
27+
print '("Loading a pre-trained CNN model from Keras")'
28+
print '(60("="))'
29+
30+
net = network(keras_cnn_path)
31+
32+
call net % print_info()
33+
34+
if (this_image() == 1) then
35+
acc = accuracy( &
36+
net, &
37+
reshape(testing_images(:,:), shape=[1,28,28,size(testing_images,2)]), &
38+
label_digits(testing_labels) &
39+
)
40+
print '(a,f5.2,a)', 'Accuracy: ', acc * 100, ' %'
41+
end if
42+
43+
contains
44+
45+
real function accuracy(net, x, y)
46+
type(network), intent(in out) :: net
47+
real, intent(in) :: x(:,:,:,:), y(:,:)
48+
integer :: i, good
49+
good = 0
50+
do i = 1, size(x, dim=4)
51+
if (all(maxloc(net % output(x(:,:,:,i))) == maxloc(y(:,i)))) then
52+
good = good + 1
53+
end if
54+
end do
55+
accuracy = real(good) / size(x, dim=4)
56+
end function accuracy
57+
58+
end program cnn_from_keras

example/mnist_from_keras.f90 renamed to example/dense_from_keras.f90

+11-10
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,32 @@
1-
program mnist_from_keras
1+
program dense_from_keras
22

3-
! This example demonstrates loading a pre-trained MNIST model from Keras
4-
! from an HDF5 file and running an inferrence on the testing dataset.
3+
! This example demonstrates loading a dense model
4+
! pre-trained on the MNIST dataset from a Keras HDF5
5+
! file and running an inferrence on the testing dataset.
56

67
use nf, only: network, label_digits, load_mnist
7-
use nf_datasets, only: download_and_unpack, keras_model_dense_mnist_url
8+
use nf_datasets, only: download_and_unpack, keras_dense_mnist_url
89

910
implicit none
1011

1112
type(network) :: net
1213
real, allocatable :: training_images(:,:), training_labels(:)
1314
real, allocatable :: validation_images(:,:), validation_labels(:)
1415
real, allocatable :: testing_images(:,:), testing_labels(:)
15-
character(*), parameter :: test_data_path = 'keras_dense_mnist.h5'
16+
character(*), parameter :: keras_dense_path = 'keras_dense_mnist.h5'
1617
logical :: file_exists
1718

18-
inquire(file=test_data_path, exist=file_exists)
19-
if (.not. file_exists) call download_and_unpack(keras_model_dense_mnist_url)
19+
inquire(file=keras_dense_path, exist=file_exists)
20+
if (.not. file_exists) call download_and_unpack(keras_dense_mnist_url)
2021

2122
call load_mnist(training_images, training_labels, &
2223
validation_images, validation_labels, &
2324
testing_images, testing_labels)
2425

25-
print '("Loading a pre-trained MNIST model from Keras")'
26+
print '("Loading a pre-trained dense model from Keras")'
2627
print '(60("="))'
2728

28-
net = network(test_data_path)
29+
net = network(keras_dense_path)
2930

3031
call net % print_info()
3132

@@ -48,4 +49,4 @@ real function accuracy(net, x, y)
4849
accuracy = real(good) / size(x, dim=2)
4950
end function accuracy
5051

51-
end program mnist_from_keras
52+
end program dense_from_keras

fpm.toml

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name = "neural-fortran"
2-
version = "0.5.0"
2+
version = "0.6.0"
33
license = "MIT"
44
author = "Milan Curcic"
55
maintainer = "[email protected]"
@@ -10,5 +10,6 @@ external-modules = "hdf5"
1010
link = ["hdf5", "hdf5_fortran"]
1111

1212
[dependencies]
13+
functional = { git = "https://github.com/wavebitscientific/functional-fortran" }
1314
h5fortran = { git = "https://github.com/geospace-code/h5fortran" }
1415
json-fortran = { git = "https://github.com/jacobwilliams/json-fortran" }

src/nf/io/nf_io_hdf5.f90

+12-2
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ module subroutine get_hdf5_dataset_real32_1d(filename, object_name, values)
3131
!! HDF5 file name
3232
character(*), intent(in) :: object_name
3333
!! Object (dataset) name
34-
real(real32), allocatable, intent(in out) :: values(:)
34+
real(real32), allocatable, intent(out) :: values(:)
3535
!! Array to store the dataset values into
3636
end subroutine get_hdf5_dataset_real32_1d
3737

@@ -41,10 +41,20 @@ module subroutine get_hdf5_dataset_real32_2d(filename, object_name, values)
4141
!! HDF5 file name
4242
character(*), intent(in) :: object_name
4343
!! Object (dataset) name
44-
real(real32), allocatable, intent(in out) :: values(:,:)
44+
real(real32), allocatable, intent(out) :: values(:,:)
4545
!! Array to store the dataset values into
4646
end subroutine get_hdf5_dataset_real32_2d
4747

48+
module subroutine get_hdf5_dataset_real32_4d(filename, object_name, values)
49+
!! Read a 4-d real32 array from an HDF5 dataset.
50+
character(*), intent(in) :: filename
51+
!! HDF5 file name
52+
character(*), intent(in) :: object_name
53+
!! Object (dataset) name
54+
real(real32), allocatable, intent(out) :: values(:,:,:,:)
55+
!! Array to store the dataset values into
56+
end subroutine get_hdf5_dataset_real32_4d
57+
4858
end interface get_hdf5_dataset
4959

5060
end module nf_io_hdf5

src/nf/io/nf_io_hdf5_submodule.f90

+27-20
Original file line numberDiff line numberDiff line change
@@ -50,23 +50,15 @@ module subroutine get_hdf5_dataset_real32_1d(filename, object_name, values)
5050

5151
character(*), intent(in) :: filename
5252
character(*), intent(in) :: object_name
53-
real(real32), allocatable, intent(in out) :: values(:)
53+
real(real32), allocatable, intent(out) :: values(:)
5454

5555
type(hdf5_file) :: f
5656
integer(int64), allocatable :: dims(:)
5757

5858
call f % open(filename, 'r')
5959
call f % shape(object_name, dims)
6060

61-
! If values is already allocated, re-allocate only if incorrect shape
62-
if (allocated(values)) then
63-
if (.not. all(shape(values) == dims)) then
64-
deallocate(values)
65-
allocate(values(dims(1)))
66-
end if
67-
else
68-
allocate(values(dims(1)))
69-
end if
61+
allocate(values(dims(1)))
7062

7163
call f % read(object_name, values)
7264
call f % close()
@@ -78,27 +70,42 @@ module subroutine get_hdf5_dataset_real32_2d(filename, object_name, values)
7870

7971
character(*), intent(in) :: filename
8072
character(*), intent(in) :: object_name
81-
real(real32), allocatable, intent(in out) :: values(:,:)
73+
real(real32), allocatable, intent(out) :: values(:,:)
8274

8375
type(hdf5_file) :: f
8476
integer(int64), allocatable :: dims(:)
8577

8678
call f % open(filename, 'r')
8779
call f % shape(object_name, dims)
8880

89-
! If values is already allocated, re-allocate only if incorrect shape
90-
if (allocated(values)) then
91-
if (.not. all(shape(values) == dims)) then
92-
deallocate(values)
93-
allocate(values(dims(1), dims(2)))
94-
end if
95-
else
96-
allocate(values(dims(1), dims(2)))
97-
end if
81+
allocate(values(dims(1), dims(2)))
9882

9983
call f % read(object_name, values)
10084
call f % close()
10185

86+
! Transpose the array to respect Keras's storage order
87+
values = transpose(values)
88+
10289
end subroutine get_hdf5_dataset_real32_2d
10390

91+
92+
module subroutine get_hdf5_dataset_real32_4d(filename, object_name, values)
93+
94+
character(*), intent(in) :: filename
95+
character(*), intent(in) :: object_name
96+
real(real32), allocatable, intent(out) :: values(:,:,:,:)
97+
98+
type(hdf5_file) :: f
99+
integer(int64), allocatable :: dims(:)
100+
101+
call f % open(filename, 'r')
102+
call f % shape(object_name, dims)
103+
104+
allocate(values(dims(1), dims(2), dims(3), dims(4)))
105+
106+
call f % read(object_name, values)
107+
call f % close()
108+
109+
end subroutine get_hdf5_dataset_real32_4d
110+
104111
end submodule nf_io_hdf5_submodule

src/nf/nf_datasets.f90

+8-2
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,19 @@ module nf_datasets
88

99
private
1010

11-
public :: download_and_unpack, keras_model_dense_mnist_url, mnist_url
11+
public :: &
12+
download_and_unpack, &
13+
keras_cnn_mnist_url, &
14+
keras_dense_mnist_url, &
15+
mnist_url
1216

1317
character(*), parameter :: keras_snippets_baseurl = &
1418
'https://github.com/neural-fortran/keras-snippets/files'
1519
character(*), parameter :: neural_fortran_baseurl = &
1620
'https://github.com/modern-fortran/neural-fortran/files'
17-
character(*), parameter :: keras_model_dense_mnist_url = &
21+
character(*), parameter :: keras_cnn_mnist_url = &
22+
keras_snippets_baseurl // '/8892585/keras_cnn_mnist.tar.gz'
23+
character(*), parameter :: keras_dense_mnist_url = &
1824
keras_snippets_baseurl // '/8788739/keras_dense_mnist.tar.gz'
1925
character(*), parameter :: mnist_url = &
2026
neural_fortran_baseurl // '/8498876/mnist.tar.gz'

0 commit comments

Comments
 (0)