Skip to content

Read Conv2D, MaxPooling2D, and Flatten layers from Keras #85

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 20 commits into from
Jun 29, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
915cec5
Add URL to Keras CNN MNIST model; make URL constants more concise
milancurcic Jun 13, 2022
336eabb
Generalize input shape in Keras reader; allow reading Conv2D metadata
milancurcic Jun 13, 2022
878e973
Rename keras_layer attribute for consistency with Keras notation
milancurcic Jun 14, 2022
44cd48d
Add MaxPooling2D and Flatten layers to the Keras reader
milancurcic Jun 14, 2022
df3d8e2
Handle C->Fortran order with Keras layer dims
milancurcic Jun 14, 2022
d943b0c
Start the CNN from Keras test suite
milancurcic Jun 14, 2022
ff2321d
Enable Conv2D, Flatten, and MaxPooling2D in the network constructor f…
milancurcic Jun 15, 2022
87ebc11
Remove redundant if-block
milancurcic Jun 15, 2022
eb70deb
Read kernel and bias data for Conv2D layer from Keras; tranpose value…
milancurcic Jun 16, 2022
0268b26
Use reshape to transpose n-dim HDF5 arrays
milancurcic Jun 17, 2022
2ff343e
Don't transpose 4-d kernel; already stored correctly
milancurcic Jun 17, 2022
5592b9a
CNN from Keras test is passing
milancurcic Jun 27, 2022
925e306
Allow as 3-d output of networks; assign activation function to conv2d…
milancurcic Jun 27, 2022
3a5cc19
Remove unused import
milancurcic Jun 27, 2022
0941ebb
Add CMake rules for functional-fortran
milancurcic Jun 28, 2022
148892b
Add CNN from Keras example; rename the dense from Keras example
milancurcic Jun 28, 2022
0ada76a
Update the README feature and examples list
milancurcic Jun 28, 2022
eb992cb
Bump version to 0.6.0
milancurcic Jun 28, 2022
c0baf37
Update the project description (not so micro anymore)
milancurcic Jun 28, 2022
0577f82
Update list of dependencies
milancurcic Jun 28, 2022
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
9 changes: 8 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ include(FetchContent)
include(cmake/options.cmake)
include(cmake/compilers.cmake)

include(cmake/functional.cmake)
include(cmake/h5fortran.cmake)
include(cmake/json.cmake)

Expand Down Expand Up @@ -62,7 +63,13 @@ add_library(neural
src/nf/io/nf_io_hdf5.f90
src/nf/io/nf_io_hdf5_submodule.f90
)
target_link_libraries(neural PRIVATE h5fortran::h5fortran HDF5::HDF5 jsonfortran::jsonfortran)

target_link_libraries(neural PRIVATE
functional::functional
h5fortran::h5fortran
HDF5::HDF5
jsonfortran::jsonfortran
)

install(TARGETS neural)

Expand Down
22 changes: 14 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

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

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

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

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

### Available layer types

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

Optional dependencies are:

* OpenCoarrays (for parallel execution with GFortran)
* BLAS, MKL (optional)
* BLAS, MKL, or similar (for offloading `matmul` and `dot_product` calls)
* curl (for downloading testing and example datasets)

Compilers tested include:

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

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

The MNIST example uses [curl](https://curl.se/) to download the dataset,
Examples 3-6 rely on [curl](https://curl.se/) to download the needed datasets,
so make sure you have it installed on your system.
Most Linux OSs have it out of the box.
The dataset will be downloaded only the first time you run the example in any
Expand Down
18 changes: 18 additions & 0 deletions cmake/functional.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
FetchContent_Declare(functional
GIT_REPOSITORY https://github.com/wavebitscientific/functional-fortran
GIT_TAG 0.6.1
GIT_SHALLOW true
)

FetchContent_Populate(functional)

add_library(functional ${functional_SOURCE_DIR}/src/functional.f90)
target_include_directories(functional PUBLIC
$<BUILD_INTERFACE:${CMAKE_BINARY_DIR}/include>
$<INSTALL_INTERFACE:include>
)

add_library(functional::functional INTERFACE IMPORTED GLOBAL)
target_link_libraries(functional::functional INTERFACE functional)

install(TARGETS functional)
16 changes: 14 additions & 2 deletions example/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,16 @@
foreach(execid cnn mnist mnist_from_keras simple sine)
foreach(execid
cnn
cnn_from_keras
dense_from_keras
mnist
simple
sine
)
add_executable(${execid} ${execid}.f90)
target_link_libraries(${execid} PRIVATE neural h5fortran::h5fortran jsonfortran::jsonfortran ${LIBS})
target_link_libraries(${execid} PRIVATE
neural
h5fortran::h5fortran
jsonfortran::jsonfortran
${LIBS}
)
endforeach()
58 changes: 58 additions & 0 deletions example/cnn_from_keras.f90
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
program cnn_from_keras

! This example demonstrates loading a convolutional model
! pre-trained on the MNIST dataset from a Keras HDF5
! file and running an inferrence on the testing dataset.

use nf, only: network, label_digits, load_mnist
use nf_datasets, only: download_and_unpack, keras_cnn_mnist_url

implicit none

type(network) :: net
real, allocatable :: training_images(:,:), training_labels(:)
real, allocatable :: validation_images(:,:), validation_labels(:)
real, allocatable :: testing_images(:,:), testing_labels(:)
character(*), parameter :: keras_cnn_path = 'keras_cnn_mnist.h5'
logical :: file_exists
real :: acc

inquire(file=keras_cnn_path, exist=file_exists)
if (.not. file_exists) call download_and_unpack(keras_cnn_mnist_url)

call load_mnist(training_images, training_labels, &
validation_images, validation_labels, &
testing_images, testing_labels)

print '("Loading a pre-trained CNN model from Keras")'
print '(60("="))'

net = network(keras_cnn_path)

call net % print_info()

if (this_image() == 1) then
acc = accuracy( &
net, &
reshape(testing_images(:,:), shape=[1,28,28,size(testing_images,2)]), &
label_digits(testing_labels) &
)
print '(a,f5.2,a)', 'Accuracy: ', acc * 100, ' %'
end if

contains

real function accuracy(net, x, y)
type(network), intent(in out) :: net
real, intent(in) :: x(:,:,:,:), y(:,:)
integer :: i, good
good = 0
do i = 1, size(x, dim=4)
if (all(maxloc(net % output(x(:,:,:,i))) == maxloc(y(:,i)))) then
good = good + 1
end if
end do
accuracy = real(good) / size(x, dim=4)
end function accuracy

end program cnn_from_keras
21 changes: 11 additions & 10 deletions example/mnist_from_keras.f90 → example/dense_from_keras.f90
Original file line number Diff line number Diff line change
@@ -1,31 +1,32 @@
program mnist_from_keras
program dense_from_keras

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

use nf, only: network, label_digits, load_mnist
use nf_datasets, only: download_and_unpack, keras_model_dense_mnist_url
use nf_datasets, only: download_and_unpack, keras_dense_mnist_url

implicit none

type(network) :: net
real, allocatable :: training_images(:,:), training_labels(:)
real, allocatable :: validation_images(:,:), validation_labels(:)
real, allocatable :: testing_images(:,:), testing_labels(:)
character(*), parameter :: test_data_path = 'keras_dense_mnist.h5'
character(*), parameter :: keras_dense_path = 'keras_dense_mnist.h5'
logical :: file_exists

inquire(file=test_data_path, exist=file_exists)
if (.not. file_exists) call download_and_unpack(keras_model_dense_mnist_url)
inquire(file=keras_dense_path, exist=file_exists)
if (.not. file_exists) call download_and_unpack(keras_dense_mnist_url)

call load_mnist(training_images, training_labels, &
validation_images, validation_labels, &
testing_images, testing_labels)

print '("Loading a pre-trained MNIST model from Keras")'
print '("Loading a pre-trained dense model from Keras")'
print '(60("="))'

net = network(test_data_path)
net = network(keras_dense_path)

call net % print_info()

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

end program mnist_from_keras
end program dense_from_keras
3 changes: 2 additions & 1 deletion fpm.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name = "neural-fortran"
version = "0.5.0"
version = "0.6.0"
license = "MIT"
author = "Milan Curcic"
maintainer = "milancurcic@hey.com"
Expand All @@ -10,5 +10,6 @@ external-modules = "hdf5"
link = ["hdf5", "hdf5_fortran"]

[dependencies]
functional = { git = "https://github.com/wavebitscientific/functional-fortran" }
h5fortran = { git = "https://github.com/geospace-code/h5fortran" }
json-fortran = { git = "https://github.com/jacobwilliams/json-fortran" }
14 changes: 12 additions & 2 deletions src/nf/io/nf_io_hdf5.f90
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ module subroutine get_hdf5_dataset_real32_1d(filename, object_name, values)
!! HDF5 file name
character(*), intent(in) :: object_name
!! Object (dataset) name
real(real32), allocatable, intent(in out) :: values(:)
real(real32), allocatable, intent(out) :: values(:)
!! Array to store the dataset values into
end subroutine get_hdf5_dataset_real32_1d

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

module subroutine get_hdf5_dataset_real32_4d(filename, object_name, values)
!! Read a 4-d real32 array from an HDF5 dataset.
character(*), intent(in) :: filename
!! HDF5 file name
character(*), intent(in) :: object_name
!! Object (dataset) name
real(real32), allocatable, intent(out) :: values(:,:,:,:)
!! Array to store the dataset values into
end subroutine get_hdf5_dataset_real32_4d

end interface get_hdf5_dataset

end module nf_io_hdf5
47 changes: 27 additions & 20 deletions src/nf/io/nf_io_hdf5_submodule.f90
Original file line number Diff line number Diff line change
Expand Up @@ -50,23 +50,15 @@ module subroutine get_hdf5_dataset_real32_1d(filename, object_name, values)

character(*), intent(in) :: filename
character(*), intent(in) :: object_name
real(real32), allocatable, intent(in out) :: values(:)
real(real32), allocatable, intent(out) :: values(:)

type(hdf5_file) :: f
integer(int64), allocatable :: dims(:)

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

! If values is already allocated, re-allocate only if incorrect shape
if (allocated(values)) then
if (.not. all(shape(values) == dims)) then
deallocate(values)
allocate(values(dims(1)))
end if
else
allocate(values(dims(1)))
end if
allocate(values(dims(1)))

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

character(*), intent(in) :: filename
character(*), intent(in) :: object_name
real(real32), allocatable, intent(in out) :: values(:,:)
real(real32), allocatable, intent(out) :: values(:,:)

type(hdf5_file) :: f
integer(int64), allocatable :: dims(:)

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

! If values is already allocated, re-allocate only if incorrect shape
if (allocated(values)) then
if (.not. all(shape(values) == dims)) then
deallocate(values)
allocate(values(dims(1), dims(2)))
end if
else
allocate(values(dims(1), dims(2)))
end if
allocate(values(dims(1), dims(2)))

call f % read(object_name, values)
call f % close()

! Transpose the array to respect Keras's storage order
values = transpose(values)

end subroutine get_hdf5_dataset_real32_2d


module subroutine get_hdf5_dataset_real32_4d(filename, object_name, values)

character(*), intent(in) :: filename
character(*), intent(in) :: object_name
real(real32), allocatable, intent(out) :: values(:,:,:,:)

type(hdf5_file) :: f
integer(int64), allocatable :: dims(:)

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

allocate(values(dims(1), dims(2), dims(3), dims(4)))

call f % read(object_name, values)
call f % close()

end subroutine get_hdf5_dataset_real32_4d

end submodule nf_io_hdf5_submodule
10 changes: 8 additions & 2 deletions src/nf/nf_datasets.f90
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,19 @@ module nf_datasets

private

public :: download_and_unpack, keras_model_dense_mnist_url, mnist_url
public :: &
download_and_unpack, &
keras_cnn_mnist_url, &
keras_dense_mnist_url, &
mnist_url

character(*), parameter :: keras_snippets_baseurl = &
'https://github.com/neural-fortran/keras-snippets/files'
character(*), parameter :: neural_fortran_baseurl = &
'https://github.com/modern-fortran/neural-fortran/files'
character(*), parameter :: keras_model_dense_mnist_url = &
character(*), parameter :: keras_cnn_mnist_url = &
keras_snippets_baseurl // '/8892585/keras_cnn_mnist.tar.gz'
character(*), parameter :: keras_dense_mnist_url = &
keras_snippets_baseurl // '/8788739/keras_dense_mnist.tar.gz'
character(*), parameter :: mnist_url = &
neural_fortran_baseurl // '/8498876/mnist.tar.gz'
Expand Down
Loading