Skip to content

Commit 9954ccd

Browse files
authored
Merge pull request #79 from milancurcic/hdf5-reader
Support for loading Keras models
2 parents c78c078 + 202e272 commit 9954ccd

28 files changed

+950
-146
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,6 @@
22
*.o
33
*.mod
44
*.dat
5+
*.h5
56
build
67
doc

CMakeLists.txt

+31-69
Original file line numberDiff line numberDiff line change
@@ -1,66 +1,24 @@
1-
# cmake version, project name, language
2-
cmake_minimum_required(VERSION 2.8 FATAL_ERROR)
3-
project(neural-fortran Fortran)
1+
# CMake version, project name, language
2+
cmake_minimum_required(VERSION 3.20)
43

5-
# set output paths for modules, archives, and executables
6-
set(CMAKE_Fortran_MODULE_DIRECTORY ${PROJECT_BINARY_DIR}/include)
7-
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
8-
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
9-
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
10-
11-
# if build type not specified, default to release
4+
# If build type not specified, default to release
125
if(NOT CMAKE_BUILD_TYPE)
13-
set(CMAKE_BUILD_TYPE "release")
14-
endif()
15-
16-
if(SERIAL)
17-
message(STATUS "Configuring build for serial execution")
18-
else()
19-
message(STATUS "Configuring build for parallel execution")
6+
set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Default build Release")
207
endif()
218

22-
# compiler flags for gfortran
23-
if(CMAKE_Fortran_COMPILER_ID MATCHES GNU)
24-
25-
if(SERIAL)
26-
message(STATUS "Configuring to build with -fcoarray=single")
27-
set(CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} -fcoarray=single")
28-
endif()
29-
30-
if(BLAS)
31-
set(CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} -fexternal-blas ${BLAS}")
32-
set(LIBS "${LIBS} blas")
33-
message(STATUS "Configuring build to use BLAS from ${BLAS}")
34-
endif()
35-
36-
set(CMAKE_Fortran_FLAGS_DEBUG "-O0 -g -fcheck=bounds -fbacktrace")
37-
set(CMAKE_Fortran_FLAGS_RELEASE "-Ofast -fno-frontend-optimize")
38-
endif()
39-
40-
# compiler flags for ifort
41-
if(CMAKE_Fortran_COMPILER_ID MATCHES Intel)
42-
43-
if(SERIAL)
44-
message(STATUS "Configuring to build with -coarray=single")
45-
set(CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} -coarray=single")
46-
endif()
9+
project(neural-fortran
10+
LANGUAGES C Fortran
11+
)
4712

48-
set(CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} -assume byterecl")
49-
set(CMAKE_Fortran_FLAGS_DEBUG "-O0 -g -C -traceback")
50-
set(CMAKE_Fortran_FLAGS_RELEASE "-O3")
13+
enable_testing()
5114

52-
if(NOT SERIAL)
53-
set(CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} -coarray=shared")
54-
endif()
15+
include(FetchContent)
5516

56-
endif()
17+
include(cmake/options.cmake)
18+
include(cmake/compilers.cmake)
5719

58-
# compiler flags for Cray ftn
59-
if(CMAKE_Fortran_COMPILER_ID MATCHES Cray)
60-
set(CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} -h noomp")
61-
set(CMAKE_Fortran_FLAGS_DEBUG "-O0 -g")
62-
set(CMAKE_Fortran_FLAGS_RELEASE "-O3")
63-
endif()
20+
include(cmake/h5fortran.cmake)
21+
include(cmake/json.cmake)
6422

6523
# library to archive (libneural.a)
6624
add_library(neural
@@ -70,6 +28,8 @@ add_library(neural
7028
src/nf/nf_base_layer_submodule.f90
7129
src/nf/nf_conv2d_layer.f90
7230
src/nf/nf_conv2d_layer_submodule.f90
31+
src/nf/nf_datasets.f90
32+
src/nf/nf_datasets_submodule.f90
7333
src/nf/nf_datasets_mnist.f90
7434
src/nf/nf_datasets_mnist_submodule.f90
7535
src/nf/nf_dense_layer.f90
@@ -80,8 +40,8 @@ add_library(neural
8040
src/nf/nf_input1d_layer_submodule.f90
8141
src/nf/nf_input3d_layer.f90
8242
src/nf/nf_input3d_layer_submodule.f90
83-
src/nf/nf_io.f90
84-
src/nf/nf_io_submodule.f90
43+
src/nf/nf_keras.f90
44+
src/nf/nf_keras_submodule.f90
8545
src/nf/nf_layer_constructors.f90
8646
src/nf/nf_layer_constructors_submodule.f90
8747
src/nf/nf_layer.f90
@@ -97,20 +57,22 @@ add_library(neural
9757
src/nf/nf_parallel_submodule.f90
9858
src/nf/nf_random.f90
9959
src/nf/nf_random_submodule.f90
60+
src/nf/io/nf_io_binary.f90
61+
src/nf/io/nf_io_binary_submodule.f90
62+
src/nf/io/nf_io_hdf5.f90
63+
src/nf/io/nf_io_hdf5_submodule.f90
10064
)
65+
target_link_libraries(neural PRIVATE h5fortran::h5fortran HDF5::HDF5 jsonfortran::jsonfortran)
66+
67+
install(TARGETS neural)
10168

10269
# Remove leading or trailing whitespace
10370
string(REGEX REPLACE "^ | $" "" LIBS "${LIBS}")
10471

105-
# tests
106-
enable_testing()
107-
foreach(execid input1d_layer input3d_layer dense_layer conv2d_layer maxpool2d_layer flatten_layer dense_network conv2d_network)
108-
add_executable(test_${execid} test/test_${execid}.f90)
109-
target_link_libraries(test_${execid} neural ${LIBS})
110-
add_test(test_${execid} bin/test_${execid})
111-
endforeach()
112-
113-
foreach(execid cnn mnist simple sine)
114-
add_executable(${execid} example/${execid}.f90)
115-
target_link_libraries(${execid} neural ${LIBS})
116-
endforeach()
72+
if(${PROJECT_NAME}_BUILD_TESTING)
73+
add_subdirectory(test)
74+
endif()
75+
76+
if(${PROJECT_NAME}_BUILD_EXAMPLES)
77+
add_subdirectory(example)
78+
endif()

README.md

+27-4
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,22 @@ git clone https://github.com/modern-fortran/neural-fortran
4141
cd neural-fortran
4242
```
4343

44-
Dependencies:
44+
### Dependencies
4545

46-
* Fortran 2018-compatible compiler
47-
* OpenCoarrays (optional, for parallel execution, GFortran only)
46+
Required dependencies are:
47+
48+
* A Fortran compiler
49+
* [HDF5](https://www.hdfgroup.org/downloads/hdf5/)
50+
(must be provided by the OS package manager or your own build from source)
51+
* [h5fortran](https://github.com/geospace-code/h5fortran),
52+
[json-fortran](https://github.com/jacobwilliams/json-fortran)
53+
(both handled by neural-fortran's build systems, no need for a manual install)
54+
* [fpm](https://github.com/fortran-lang/fpm) or
55+
[CMake](https://cmake.org) for building the code
56+
57+
Optional dependencies are:
58+
59+
* OpenCoarrays (for parallel execution with GFortran)
4860
* BLAS, MKL (optional)
4961

5062
Compilers tested include:
@@ -131,12 +143,21 @@ cafrun -n 4 bin/mnist # run MNIST example on 4 cores
131143
#### Building with a different compiler
132144

133145
If you want to build with a different compiler, such as Intel Fortran,
134-
specify `FC` when issuing `cmake`:
146+
set the `HDF5_ROOT` environment variable to the root path of your
147+
Intel HDF5 build, and specify `FC` when issuing `cmake`:
135148

136149
```
137150
FC=ifort cmake ..
138151
```
139152

153+
for a parallel build of neural-fortran, or
154+
155+
```
156+
FC=ifort cmake .. -DSERIAL=1
157+
```
158+
159+
for a serial build.
160+
140161
#### Building with BLAS or MKL
141162

142163
To use an external BLAS or MKL library for `matmul` calls,
@@ -180,6 +201,8 @@ examples, in increasing level of complexity:
180201
dataset
181202
4. [cnn](example/cnn.f90): Creating and running forward a simple CNN using
182203
`input`, `conv2d`, `maxpool2d`, `flatten`, and `dense` layers.
204+
5. [mnist_from_keras](example/mnist_from_keras.f90): Creating a pre-trained
205+
model from a Keras HDF5 file.
183206

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

cmake/compilers.cmake

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# compiler flags for gfortran
2+
if(CMAKE_Fortran_COMPILER_ID STREQUAL "GNU")
3+
4+
if(SERIAL)
5+
message(STATUS "Configuring to build with -fcoarray=single")
6+
add_compile_options("$<$<COMPILE_LANGUAGE:Fortran>:-fcoarray=single>")
7+
else()
8+
add_compile_options("$<$<COMPILE_LANGUAGE:Fortran>:-fcoarray=lib>")
9+
endif()
10+
11+
if(BLAS)
12+
add_compile_options("$<$<COMPILE_LANGUAGE:Fortran>:-fexternal-blas;${BLAS}>")
13+
list(APPEND LIBS "blas")
14+
message(STATUS "Configuring build to use BLAS from ${BLAS}")
15+
endif()
16+
17+
add_compile_options("$<$<AND:$<COMPILE_LANGUAGE:Fortran>,$<CONFIG:Debug>>:-fcheck=bounds;-fbacktrace>")
18+
add_compile_options("$<$<AND:$<COMPILE_LANGUAGE:Fortran>,$<CONFIG:Release>>:-Ofast;-fno-frontend-optimize;-fno-backtrace>")
19+
20+
elseif(CMAKE_Fortran_COMPILER_ID MATCHES "^Intel")
21+
# compiler flags for ifort
22+
23+
if(SERIAL)
24+
message(STATUS "Configuring to build with -coarray=single")
25+
if(WIN32)
26+
add_compile_options("$<$<COMPILE_LANGUAGE:Fortran>:/Qcoarray:single>")
27+
add_link_options("$<$<COMPILE_LANGUAGE:Fortran>:/Qcoarray:single>")
28+
else()
29+
add_compile_options("$<$<COMPILE_LANGUAGE:Fortran>:-coarray=single>")
30+
add_link_options("$<$<COMPILE_LANGUAGE:Fortran>:-coarray=single>")
31+
endif()
32+
else()
33+
if(WIN32)
34+
add_compile_options("$<$<COMPILE_LANGUAGE:Fortran>:/Qcoarray:shared>")
35+
add_link_options("$<$<COMPILE_LANGUAGE:Fortran>:/Qcoarray:shared>")
36+
else()
37+
add_compile_options("$<$<COMPILE_LANGUAGE:Fortran>:-coarray=shared>")
38+
add_link_options("$<$<COMPILE_LANGUAGE:Fortran>:-coarray=shared>")
39+
endif()
40+
endif()
41+
42+
if(WIN32)
43+
string(APPEND CMAKE_Fortran_FLAGS " /assume:byterecl")
44+
else()
45+
string(APPEND CMAKE_Fortran_FLAGS " -assume byterecl")
46+
endif()
47+
add_compile_options("$<$<AND:$<COMPILE_LANGUAGE:Fortran>,$<CONFIG:Debug>>:-check;-traceback>")
48+
# add_compile_options("$<$<AND:$<COMPILE_LANGUAGE:Fortran>,$<CONFIG:Release>>:-O3>")
49+
50+
elseif(CMAKE_Fortran_COMPILER_ID STREQUAL "Cray")
51+
# compiler flags for Cray ftn
52+
string(APPEND CMAKE_Fortran_FLAGS " -h noomp")
53+
add_compile_options("$<$<AND:$<COMPILE_LANGUAGE:Fortran>,$<CONFIG:Debug>>:-O0;-g>")
54+
add_compile_options("$<$<AND:$<COMPILE_LANGUAGE:Fortran>,$<CONFIG:Release>>:-O3>")
55+
endif()

cmake/h5fortran.cmake

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
set(h5fortran_BUILD_TESTING false)
2+
3+
FetchContent_Declare(h5fortran
4+
GIT_REPOSITORY https://github.com/geospace-code/h5fortran
5+
GIT_TAG v4.6.3
6+
GIT_SHALLOW true
7+
)
8+
9+
FetchContent_MakeAvailable(h5fortran)
10+
11+
file(MAKE_DIRECTORY ${h5fortran_BINARY_DIR}/include)
12+
13+
14+
list(APPEND CMAKE_MODULE_PATH ${h5fortran_SOURCE_DIR}/cmake/Modules)
15+
find_package(HDF5 COMPONENTS Fortran REQUIRED)

cmake/json.cmake

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# use our own CMake script to build jsonfortran instead of jsonfortran/CMakelists.txt
2+
3+
FetchContent_Declare(jsonfortran
4+
GIT_REPOSITORY https://github.com/jacobwilliams/json-fortran
5+
GIT_TAG 8.3.0
6+
GIT_SHALLOW true
7+
)
8+
9+
FetchContent_Populate(jsonfortran)
10+
11+
SET(JSON_REAL_KIND "REAL64")
12+
SET(JSON_INT_KIND "INT32")
13+
14+
set(_src ${jsonfortran_SOURCE_DIR}/src)
15+
16+
set (JF_LIB_SRCS
17+
${_src}/json_kinds.F90
18+
${_src}/json_parameters.F90
19+
${_src}/json_string_utilities.F90
20+
${_src}/json_value_module.F90
21+
${_src}/json_file_module.F90
22+
${_src}/json_module.F90
23+
)
24+
25+
add_library(jsonfortran ${JF_LIB_SRCS})
26+
target_compile_definitions(jsonfortran PRIVATE ${JSON_REAL_KIND} ${JSON_INT_KIND})
27+
target_include_directories(jsonfortran PUBLIC
28+
$<BUILD_INTERFACE:${CMAKE_BINARY_DIR}/include>
29+
$<INSTALL_INTERFACE:include>
30+
)
31+
32+
add_library(jsonfortran::jsonfortran INTERFACE IMPORTED GLOBAL)
33+
target_link_libraries(jsonfortran::jsonfortran INTERFACE jsonfortran)
34+
35+
install(TARGETS jsonfortran)

cmake/options.cmake

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
option(SERIAL "Serial execution")
2+
option(${PROJECT_NAME}_BUILD_TESTING "build ${PROJECT_NAME} tests" true)
3+
option(${PROJECT_NAME}_BUILD_EXAMPLES "build ${PROJECT_NAME} examples" true)
4+
5+
# Set output paths for modules, archives, and executables
6+
set(CMAKE_Fortran_MODULE_DIRECTORY ${CMAKE_BINARY_DIR}/include)
7+
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
8+
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
9+
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
10+
11+
if(SERIAL)
12+
message(STATUS "Configuring build for serial execution")
13+
else()
14+
message(STATUS "Configuring build for parallel execution")
15+
endif()
16+
17+
# --- Generally useful CMake project options
18+
19+
# Rpath options necessary for shared library install to work correctly in user projects
20+
set(CMAKE_INSTALL_NAME_DIR ${CMAKE_INSTALL_PREFIX}/lib)
21+
set(CMAKE_INSTALL_RPATH ${CMAKE_INSTALL_PREFIX}/lib)
22+
set(CMAKE_INSTALL_RPATH_USE_LINK_PATH true)
23+
24+
# Necessary for shared library with Visual Studio / Windows oneAPI
25+
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS true)
26+
27+
# --- auto-ignore build directory
28+
if(NOT EXISTS ${PROJECT_BINARY_DIR}/.gitignore)
29+
file(WRITE ${PROJECT_BINARY_DIR}/.gitignore "*")
30+
endif()

example/CMakeLists.txt

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
foreach(execid cnn mnist mnist_from_keras simple sine)
2+
add_executable(${execid} ${execid}.f90)
3+
target_link_libraries(${execid} PRIVATE neural h5fortran::h5fortran jsonfortran::jsonfortran ${LIBS})
4+
endforeach()

example/mnist_from_keras.f90

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
program mnist_from_keras
2+
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.
5+
6+
use nf, only: network, label_digits, load_mnist
7+
use nf_datasets, only: download_and_unpack, keras_model_dense_mnist_url
8+
9+
implicit none
10+
11+
type(network) :: net
12+
real, allocatable :: training_images(:,:), training_labels(:)
13+
real, allocatable :: validation_images(:,:), validation_labels(:)
14+
real, allocatable :: testing_images(:,:), testing_labels(:)
15+
character(*), parameter :: test_data_path = 'keras_dense_mnist.h5'
16+
logical :: file_exists
17+
18+
inquire(file=test_data_path, exist=file_exists)
19+
if (.not. file_exists) call download_and_unpack(keras_model_dense_mnist_url)
20+
21+
call load_mnist(training_images, training_labels, &
22+
validation_images, validation_labels, &
23+
testing_images, testing_labels)
24+
25+
print '("Loading a pre-trained MNIST model from Keras")'
26+
print '(60("="))'
27+
28+
net = network(test_data_path)
29+
30+
call net % print_info()
31+
32+
if (this_image() == 1) &
33+
print '(a,f5.2,a)', 'Accuracy: ', accuracy( &
34+
net, testing_images, label_digits(testing_labels)) * 100, ' %'
35+
36+
contains
37+
38+
real function accuracy(net, x, y)
39+
type(network), intent(in out) :: net
40+
real, intent(in) :: x(:,:), y(:,:)
41+
integer :: i, good
42+
good = 0
43+
do i = 1, size(x, dim=2)
44+
if (all(maxloc(net % output(x(:,i))) == maxloc(y(:,i)))) then
45+
good = good + 1
46+
end if
47+
end do
48+
accuracy = real(good) / size(x, dim=2)
49+
end function accuracy
50+
51+
end program mnist_from_keras

0 commit comments

Comments
 (0)