Skip to content

Commit

Permalink
Merge pull request #85 from milancurcic/keras-reader-cnn
Browse files Browse the repository at this point in the history
Read Conv2D, MaxPooling2D, and Flatten layers from Keras
  • Loading branch information
milancurcic authored Jun 29, 2022
2 parents 94cc86b + 0577f82 commit a878d03
Show file tree
Hide file tree
Showing 18 changed files with 446 additions and 103 deletions.
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 = "[email protected]"
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

0 comments on commit a878d03

Please sign in to comment.