diff --git a/.ci/ci.yaml b/.ci/ci.yaml index 739ba9906c..e0654b49e1 100644 --- a/.ci/ci.yaml +++ b/.ci/ci.yaml @@ -57,23 +57,6 @@ jobs: steps: - template: macos-steps.yaml -- job: WindowsVS14 - timeoutInMinutes: 360 - displayName: Windows VS14 - pool: - vmImage: vs2015-win2012r2 - strategy: - matrix: - Plain: - CMakeArgs: '-DDEBUG=ON -DPROFILE=OFF -DBUILD_PYTHON_BINDINGS=OFF' - CMakeGenerator: '-G "Visual Studio 14 2015 Win64"' - MSBuildVersion: '14.0' - ArchiveNoLibs: 'mlpack-windows-vs14-no-libs.zip' - ArchiveLibs: 'mlpack-windows-vs14.zip' - ArchiveTests: 'mlpack_test-vs14.xml' - steps: - - template: windows-steps.yaml - - job: WindowsVS15 timeoutInMinutes: 360 displayName: Windows VS15 diff --git a/CMakeLists.txt b/CMakeLists.txt index 8c21581fa1..4dc6d0beb8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -328,6 +328,7 @@ if (NOT STB_IMAGE_FOUND) install(FILES "${CMAKE_BINARY_DIR}/deps/${STB_DIR}/stb_image.h" DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}") install(FILES "${CMAKE_BINARY_DIR}/deps/${STB_DIR}/stb_image_write.h" DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}") add_definitions(-DHAS_STB) + set(STB_AVAILABLE "1") else () message(WARNING "stb/stb_image.h is not installed. Image utilities will not be available!") @@ -348,6 +349,7 @@ else () # Already has STB installed. add_definitions(-DHAS_STB) set(MLPACK_INCLUDE_DIRS ${MLPACK_INCLUDE_DIRS} ${STB_IMAGE_INCLUDE_DIR}) + set(STB_AVAILABLE "1") endif () diff --git a/HISTORY.md b/HISTORY.md index 5bf264ec8f..1e5bc7565d 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -2,8 +2,10 @@ ###### ????-??-?? * Updated terminal state for Pendulum environment (#2354). +### mlpack 3.3.0 +###### 2020-04-07 * Templated return type of `Forward function` of loss functions (#2339). - + * Added `R2 Score` regression metric (#2323). * Added `mean squared logarithmic error` loss function for neural networks @@ -40,6 +42,9 @@ * CMake fix for finding STB include directory (#2145). + * Add bindings for loading and saving images (#2019); `mlpack_image_converter` + from the command-line, `mlpack.image_converter()` from Python. + * Add normalization support for CF binding (#2136). * Add Mish activation function (#2158). @@ -55,7 +60,7 @@ * Add LiSHT activation function (#2182). * Add Valid and Same Padding for Transposed Convolution layer (#2163). - + * Add CELU activation function (#2191) * Add Log-Hyperbolic-Cosine Loss function (#2207) @@ -65,7 +70,7 @@ * Bump minimum Boost version to 1.58 (#2305). - * Refactor STB support so HAS_STB macro is not needed when compiling against + * Refactor STB support so `HAS_STB` macro is not needed when compiling against mlpack (#2312). * Add Hard Shrink Activation Function (#2186). @@ -74,6 +79,13 @@ * Add Hinge Embedding Loss Function (#2229). + * Add Cosine Embedding Loss Function (#2209). + + * Add Margin Ranking Loss Function (#2264). + + * Bugfix for incorrect parameter vector sizes in logistic regression and + softmax regression (#2359). + ### mlpack 3.2.2 ###### 2019-11-26 * Add `valid` and `same` padding option in `Convolution` and `Atrous diff --git a/README.md b/README.md index d204bb4b3a..d8f4c5c0ec 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ src="https://cdn.rawgit.com/mlpack/mlpack.org/e7d36ed8/mlpack-black.svg" style="

Download: - current stable version (3.2.2) + current stable version (3.3.0)

diff --git a/doc/examples/sample-ml-app/sample-ml-app/sample-ml-app.vcxproj b/doc/examples/sample-ml-app/sample-ml-app/sample-ml-app.vcxproj index fdc41c5d2a..aadce63f17 100644 --- a/doc/examples/sample-ml-app/sample-ml-app/sample-ml-app.vcxproj +++ b/doc/examples/sample-ml-app/sample-ml-app/sample-ml-app.vcxproj @@ -104,16 +104,16 @@ true _DEBUG;_CONSOLE;%(PreprocessorDefinitions) false - C:\boost\boost_1_66_0;C:\mlpack\armadillo-8.500.1\include;C:\mlpack\mlpack-3.2.1\build\include;%(AdditionalIncludeDirectories) + C:\boost\boost_1_66_0;C:\mlpack\armadillo-8.500.1\include;C:\mlpack\mlpack-3.3.0\build\include;%(AdditionalIncludeDirectories) Console true - C:\mlpack\mlpack-3.2.1\build\Debug\mlpack.lib;C:\boost\boost_1_66_0\lib64-msvc-14.1\libboost_serialization-vc141-mt-gd-x64-1_66.lib;C:\boost\boost_1_66_0\lib64-msvc-14.1\libboost_program_options-vc141-mt-gd-x64-1_66.lib;%(AdditionalDependencies) + C:\mlpack\mlpack-3.3.0\build\Debug\mlpack.lib;C:\boost\boost_1_66_0\lib64-msvc-14.1\libboost_serialization-vc141-mt-gd-x64-1_66.lib;C:\boost\boost_1_66_0\lib64-msvc-14.1\libboost_program_options-vc141-mt-gd-x64-1_66.lib;%(AdditionalDependencies) - xcopy /y "C:\mlpack\mlpack-3.2.1\build\Debug\mlpack.dll" $(OutDir) -xcopy /y "C:\mlpack\mlpack-3.2.1\packages\OpenBLAS.0.2.14.1\lib\native\bin\x64\*.dll" $(OutDir) + xcopy /y "C:\mlpack\mlpack-3.3.0\build\Debug\mlpack.dll" $(OutDir) +xcopy /y "C:\mlpack\mlpack-3.3.0\packages\OpenBLAS.0.2.14.1\lib\native\bin\x64\*.dll" $(OutDir) xcopy /y "$(ProjectDir)..\..\..\..\src\mlpack\tests\data\german.csv" "$(ProjectDir)data\german.csv*" diff --git a/doc/guide/build.hpp b/doc/guide/build.hpp index df5e2edb82..d54dd3274e 100644 --- a/doc/guide/build.hpp +++ b/doc/guide/build.hpp @@ -30,7 +30,7 @@ to build mlpack on Windows, see \ref build_windows (alternatively, you can read is based on older versions). You can download the latest mlpack release from here: -mlpack-3.2.2 +mlpack-3.3.0 @section build_simple Simple Linux build instructions @@ -38,9 +38,9 @@ Assuming all dependencies are installed in the system, you can run the commands below directly to build and install mlpack. @code -$ wget https://www.mlpack.org/files/mlpack-3.2.2.tar.gz -$ tar -xvzpf mlpack-3.2.2.tar.gz -$ mkdir mlpack-3.2.2/build && cd mlpack-3.2.2/build +$ wget https://www.mlpack.org/files/mlpack-3.3.0.tar.gz +$ tar -xvzpf mlpack-3.3.0.tar.gz +$ mkdir mlpack-3.3.0/build && cd mlpack-3.3.0/build $ cmake ../ $ make -j4 # The -j is the number of cores you want to use for a build. $ sudo make install @@ -65,8 +65,8 @@ configure mlpack. First we should unpack the mlpack source and create a build directory. @code -$ tar -xvzpf mlpack-3.2.2.tar.gz -$ cd mlpack-3.2.2 +$ tar -xvzpf mlpack-3.3.0.tar.gz +$ cd mlpack-3.3.0 $ mkdir build @endcode diff --git a/doc/guide/python_quickstart.hpp b/doc/guide/python_quickstart.hpp index 115eecc107..4a1da9152c 100644 --- a/doc/guide/python_quickstart.hpp +++ b/doc/guide/python_quickstart.hpp @@ -31,9 +31,9 @@ build and install mlpack. You can copy-paste the commands into your shell. @code{.sh} sudo apt-get install libboost-all-dev g++ cmake libarmadillo-dev python-pip wget sudo pip install cython setuptools distutils numpy pandas -wget https://www.mlpack.org/files/mlpack-3.2.1.tar.gz -tar -xvzpf mlpack-3.2.1.tar.gz -mkdir -p mlpack-3.2.1/build/ && cd mlpack-3.2.1/build/ +wget https://www.mlpack.org/files/mlpack-3.3.0.tar.gz +tar -xvzpf mlpack-3.3.0.tar.gz +mkdir -p mlpack-3.3.0/build/ && cd mlpack-3.3.0/build/ cmake ../ && make -j4 && sudo make install @endcode diff --git a/doc/guide/sample_ml_app.hpp b/doc/guide/sample_ml_app.hpp index 6e42a19c07..1cb4877037 100644 --- a/doc/guide/sample_ml_app.hpp +++ b/doc/guide/sample_ml_app.hpp @@ -29,18 +29,18 @@ mlpack and dependencies in Release Mode). @code - C:\boost\boost_1_71_0\lib\native\include - C:\mlpack\armadillo-9.800.3\include - - C:\mlpack\mlpack-3.2.2\build\include + - C:\mlpack\mlpack-3.3.0\build\include @endcode - Under Linker > Input > Additional Dependencies add: @code - - C:\mlpack\mlpack-3.2.2\build\Debug\mlpack.lib + - C:\mlpack\mlpack-3.3.0\build\Debug\mlpack.lib - C:\boost\boost_1_71_0\lib64-msvc-14.2\libboost_serialization-vc142-mt-gd-x64-1_71.lib - C:\boost\boost_1_71_0\lib64-msvc-14.2\libboost_program_options-vc142-mt-gd-x64-1_71.lib @endcode - Under Build Events > Post-Build Event > Command Line add: @code - - xcopy /y "C:\mlpack\mlpack-3.2.2\build\Debug\mlpack.dll" $(OutDir) - - xcopy /y "C:\mlpack\mlpack-3.2.2\packages\OpenBLAS.0.2.14.1\lib\native\bin\x64\*.dll" $(OutDir) + - xcopy /y "C:\mlpack\mlpack-3.3.0\build\Debug\mlpack.dll" $(OutDir) + - xcopy /y "C:\mlpack\mlpack-3.3.0\packages\OpenBLAS.0.2.14.1\lib\native\bin\x64\*.dll" $(OutDir) @endcode @note Recent versions of Visual Studio set "Conformance Mode" enabled by default. This causes some issues with diff --git a/doc/tutorials/image/image.txt b/doc/tutorials/image/image.txt new file mode 100644 index 0000000000..b64a86c24a --- /dev/null +++ b/doc/tutorials/image/image.txt @@ -0,0 +1,188 @@ +/*! +@file image.txt +@author Mehul Kumar Nirala +@brief Tutorial for how to load and save images in mlpack. + +@page imagetutorial Image Utilities tutorial + +@section intro_imagetut Introduction + +Image datasets are becoming increasingly popular in deep learning. + +mlpack's image saving/loading functionality is based on [stb/](https://github.com/nothings/stb). + +@section toc_imagetut Table of Contents + +This tutorial is split into the following sections: + + - \ref intro_imagetut + - \ref toc_imagetut + - \ref model_api_imagetut + - \ref imageinfo_api_imagetut + - \ref load_api_imagetut + - \ref save_api_imagetut + +@section model_api_imagetut Model API + +Image utilities supports loading and saving of images. + +It supports filetypes "jpg", "png", "tga","bmp", "psd", "gif", "hdr", "pic", "pnm" for loading and "jpg", "png", "tga", "bmp", "hdr" for saving. + +The datatype associated is unsigned char to support RGB values in the range 1-255. To feed data into the network typecast of `arma::Mat` may be required. Images are stored in matrix as (width * height * channels, NumberOfImages). Therefore imageMatrix.col(0) would be the first image if images are loaded in imageMatrix. + +@section imageinfo_api_imagetut ImageInfo + +ImageInfo class contains the metadata of the images. +@code + /** + * Instantiate the ImageInfo object with the image width, height, channels. + * + * @param width Image width. + * @param height Image height. + * @param channels number of channels in the image. + */ + ImageInfo(const size_t width, + const size_t height, + const size_t channels); +@endcode +Other public memebers include: + - quality Compression of the image if saved as jpg (0-100). + +@section load_api_imagetut Load + + +Standalone loading of images. +@code + /** + * Load the image file into the given matrix. + * + * @param filename Name of the image file. + * @param matrix Matrix to load the image into. + * @param info An object of ImageInfo class. + * @param fatal If an error should be reported as fatal (default false). + * @param transpose If true, flips the image, same as transposing the + * matrix after loading. + * @return Boolean value indicating success or failure of load. + */ + template + bool Load(const std::string& filename, + arma::Mat& matrix, + ImageInfo& info, + const bool fatal, + const bool transpose); +@endcode + +Loading a test image. It also fills up the ImageInfo class object. +@code +data::ImageInfo info; +data::Load("test_image.png", matrix, info, false, true); +@endcode + +ImageInfo requires height, width, number of channels of the image. + +@code +size_t height = 64, width = 64, channels = 1; +data::ImageInfo info(width, height, channels); +@endcode + +More than one image can be loaded into the same matrix. + +Loading multiple images: + +@code + /** + * Load the image file into the given matrix. + * + * @param files A vector consisting of filenames. + * @param matrix Matrix to save the image from. + * @param info An object of ImageInfo class. + * @param fatal If an error should be reported as fatal (default false). + * @param transpose If true, flips the image, same as transposing the + * matrix after loading. + * @return Boolean value indicating success or failure of load. + */ + template + bool Load(const std::vector& files, + arma::Mat& matrix, + ImageInfo& info, + const bool fatal, + const bool transpose); +@endcode + +@code + data::ImageInfo info; + std::vector> files{"test_image1.bmp","test_image2.bmp"}; + data::load(files, matrix, info, false, true); +@endcode + +@section save_api_imagetut Save + +Save images expects a matrix of type unsigned char in the form (width * height * channels, NumberOfImages). +Just like load it can be used to save one image or multiple images. Besides image data it also expects the shape of the image as input (width, height, channels). + +Saving one image: + +@code + /** + * Save the image file from the given matrix. + * + * @param filename Name of the image file. + * @param matrix Matrix to save the image from. + * @param info An object of ImageInfo class. + * @param fatal If an error should be reported as fatal (default false). + * @param transpose If true, flips the image, same as transposing the + * matrix after loading. + * @return Boolean value indicating success or failure of load. + */ + template + bool Save(const std::string& filename, + arma::Mat& matrix, + ImageInfo& info, + const bool fatal, + const bool transpose); +@endcode + +@code + data::ImageInfo info; + info.width = info.height = 25; + info.channels = 3; + info.quality = 90; + data::Save("test_image.bmp", matrix, info, false, true); +@endcode + +If the matrix contains more than one image, only the first one is saved. + +Saving multiple images: + +@code + /** + * Save the image file from the given matrix. + * + * @param files A vector consisting of filenames. + * @param matrix Matrix to save the image from. + * @param info An object of ImageInfo class. + * @param fatal If an error should be reported as fatal (default false). + * @param transpose If true, Flips the image, same as transposing the + * matrix after loading. + * @return Boolean value indicating success or failure of load. + */ + template + bool Save(const std::vector& files, + arma::Mat& matrix, + ImageInfo& info, + const bool fatal, + const bool transpose); +@endcode + +@code + data::ImageInfo info; + info.width = info.height = 25; + info.channels = 3; + info.quality = 90; + std::vector> files{"test_image1.bmp", "test_image2.bmp"}; + data::Save(files, matrix, info, false, true); +@endcode + +Multiple images are saved according to the vector of filenames specified. + +*/ diff --git a/src/mlpack/CMakeLists.txt b/src/mlpack/CMakeLists.txt index 02d60b0c1c..2b2513daff 100644 --- a/src/mlpack/CMakeLists.txt +++ b/src/mlpack/CMakeLists.txt @@ -44,7 +44,7 @@ target_link_libraries(mlpack ${MLPACK_LIBRARIES}) set_target_properties(mlpack PROPERTIES - VERSION 3.2 + VERSION 3.3 SOVERSION 3 ) diff --git a/src/mlpack/bindings/julia/get_julia_type.hpp b/src/mlpack/bindings/julia/get_julia_type.hpp index 096c420ab9..d320494ba2 100644 --- a/src/mlpack/bindings/julia/get_julia_type.hpp +++ b/src/mlpack/bindings/julia/get_julia_type.hpp @@ -3,6 +3,11 @@ * @author Ryan Curtin * * Get the Julia-named type of an mlpack C++ type. + * + * mlpack is free software; you may redistribute it and/or modify it under the + * terms of the 3-clause BSD license. You should have received a copy of the + * 3-clause BSD license along with mlpack. If not, see + * http://www.opensource.org/licenses/BSD-3-Clause for more information. */ #ifndef MLPACK_BINDINGS_JULIA_GET_JULIA_TYPE_HPP #define MLPACK_BINDINGS_JULIA_GET_JULIA_TYPE_HPP diff --git a/src/mlpack/bindings/julia/get_printable_type.hpp b/src/mlpack/bindings/julia/get_printable_type.hpp index 53747acdf1..c353497349 100644 --- a/src/mlpack/bindings/julia/get_printable_type.hpp +++ b/src/mlpack/bindings/julia/get_printable_type.hpp @@ -4,6 +4,11 @@ * * Get the printable type of a parameter. This type is not the C++ type but * instead the Julia type that a user would use. + * + * mlpack is free software; you may redistribute it and/or modify it under the + * terms of the 3-clause BSD license. You should have received a copy of the + * 3-clause BSD license along with mlpack. If not, see + * http://www.opensource.org/licenses/BSD-3-Clause for more information. */ #ifndef MLPACK_BINDINGS_JULIA_GET_PRINTABLE_TYPE_HPP #define MLPACK_BINDINGS_JULIA_GET_PRINTABLE_TYPE_HPP diff --git a/src/mlpack/bindings/julia/get_printable_type_impl.hpp b/src/mlpack/bindings/julia/get_printable_type_impl.hpp index 524fe62a0b..2df1a753ec 100644 --- a/src/mlpack/bindings/julia/get_printable_type_impl.hpp +++ b/src/mlpack/bindings/julia/get_printable_type_impl.hpp @@ -4,6 +4,11 @@ * * Get the printable type of a parameter. This type is not the C++ type but * instead the Julia type that a user would use. + * + * mlpack is free software; you may redistribute it and/or modify it under the + * terms of the 3-clause BSD license. You should have received a copy of the + * 3-clause BSD license along with mlpack. If not, see + * http://www.opensource.org/licenses/BSD-3-Clause for more information. */ #ifndef MLPACK_BINDINGS_JULIA_GET_PRINTABLE_TYPE_IMPL_HPP #define MLPACK_BINDINGS_JULIA_GET_PRINTABLE_TYPE_IMPL_HPP diff --git a/src/mlpack/bindings/julia/julia_util.cpp b/src/mlpack/bindings/julia/julia_util.cpp index faa639956f..ee3bf70681 100644 --- a/src/mlpack/bindings/julia/julia_util.cpp +++ b/src/mlpack/bindings/julia/julia_util.cpp @@ -3,6 +3,11 @@ * @author Ryan Curtin * * Implementations of Julia binding functionality. + * + * mlpack is free software; you may redistribute it and/or modify it under the + * terms of the 3-clause BSD license. You should have received a copy of the + * 3-clause BSD license along with mlpack. If not, see + * http://www.opensource.org/licenses/BSD-3-Clause for more information. */ #include #include diff --git a/src/mlpack/bindings/julia/print_doc.hpp b/src/mlpack/bindings/julia/print_doc.hpp index 522ea3576b..62a8acdde8 100644 --- a/src/mlpack/bindings/julia/print_doc.hpp +++ b/src/mlpack/bindings/julia/print_doc.hpp @@ -3,6 +3,11 @@ * @author Ryan Curtin * * Print inline documentation for a single option. + * + * mlpack is free software; you may redistribute it and/or modify it under the + * terms of the 3-clause BSD license. You should have received a copy of the + * 3-clause BSD license along with mlpack. If not, see + * http://www.opensource.org/licenses/BSD-3-Clause for more information. */ #ifndef MLPACK_BINDINGS_JULIA_PRINT_DOC_HPP #define MLPACK_BINDINGS_JULIA_PRINT_DOC_HPP diff --git a/src/mlpack/bindings/julia/print_input_param.hpp b/src/mlpack/bindings/julia/print_input_param.hpp index a76ba172a3..9f7ad35285 100644 --- a/src/mlpack/bindings/julia/print_input_param.hpp +++ b/src/mlpack/bindings/julia/print_input_param.hpp @@ -4,6 +4,11 @@ * * Print the declaration of an input parameter as part of a line in a Julia * function definition. + * + * mlpack is free software; you may redistribute it and/or modify it under the + * terms of the 3-clause BSD license. You should have received a copy of the + * 3-clause BSD license along with mlpack. If not, see + * http://www.opensource.org/licenses/BSD-3-Clause for more information. */ #ifndef MLPACK_BINDINGS_JULIA_PRINT_INPUT_PARAM_HPP #define MLPACK_BINDINGS_JULIA_PRINT_INPUT_PARAM_HPP diff --git a/src/mlpack/bindings/julia/print_input_processing.hpp b/src/mlpack/bindings/julia/print_input_processing.hpp index a5ae2412bf..317c796d65 100644 --- a/src/mlpack/bindings/julia/print_input_processing.hpp +++ b/src/mlpack/bindings/julia/print_input_processing.hpp @@ -3,6 +3,11 @@ * @author Ryan Curtin * * Print Julia code to handle input arguments. + * + * mlpack is free software; you may redistribute it and/or modify it under the + * terms of the 3-clause BSD license. You should have received a copy of the + * 3-clause BSD license along with mlpack. If not, see + * http://www.opensource.org/licenses/BSD-3-Clause for more information. */ #ifndef MLPACK_BINDINGS_JULIA_PRINT_INPUT_PROCESSING_HPP #define MLPACK_BINDINGS_JULIA_PRINT_INPUT_PROCESSING_HPP diff --git a/src/mlpack/bindings/julia/print_input_processing_impl.hpp b/src/mlpack/bindings/julia/print_input_processing_impl.hpp index 6839abf29d..cd71693f3e 100644 --- a/src/mlpack/bindings/julia/print_input_processing_impl.hpp +++ b/src/mlpack/bindings/julia/print_input_processing_impl.hpp @@ -3,6 +3,11 @@ * @author Ryan Curtin * * Print Julia code to handle input arguments. + * + * mlpack is free software; you may redistribute it and/or modify it under the + * terms of the 3-clause BSD license. You should have received a copy of the + * 3-clause BSD license along with mlpack. If not, see + * http://www.opensource.org/licenses/BSD-3-Clause for more information. */ #ifndef MLPACK_BINDINGS_JULIA_PRINT_INPUT_PROCESSING_IMPL_HPP #define MLPACK_BINDINGS_JULIA_PRINT_INPUT_PROCESSING_IMPL_HPP diff --git a/src/mlpack/bindings/julia/print_jl.cpp b/src/mlpack/bindings/julia/print_jl.cpp index 7f4fb86a02..23d0b5ee0e 100644 --- a/src/mlpack/bindings/julia/print_jl.cpp +++ b/src/mlpack/bindings/julia/print_jl.cpp @@ -3,6 +3,11 @@ * @author Ryan Curtin * * Implementation of utility PrintJL() function. + * + * mlpack is free software; you may redistribute it and/or modify it under the + * terms of the 3-clause BSD license. You should have received a copy of the + * 3-clause BSD license along with mlpack. If not, see + * http://www.opensource.org/licenses/BSD-3-Clause for more information. */ #include "print_jl.hpp" #include diff --git a/src/mlpack/bindings/julia/print_jl.hpp b/src/mlpack/bindings/julia/print_jl.hpp index 6b07dd7e3b..e712fc0a3c 100644 --- a/src/mlpack/bindings/julia/print_jl.hpp +++ b/src/mlpack/bindings/julia/print_jl.hpp @@ -3,6 +3,11 @@ * @author Ryan Curtin * * Definition of utility PrintJL() function. + * + * mlpack is free software; you may redistribute it and/or modify it under the + * terms of the 3-clause BSD license. You should have received a copy of the + * 3-clause BSD license along with mlpack. If not, see + * http://www.opensource.org/licenses/BSD-3-Clause for more information. */ #ifndef MLPACK_BINDINGS_JULIA_PRINT_JL_HPP #define MLPACK_BINDINGS_JULIA_PRINT_JL_HPP diff --git a/src/mlpack/bindings/julia/print_output_processing.hpp b/src/mlpack/bindings/julia/print_output_processing.hpp index 069b344b6e..6426eb293d 100644 --- a/src/mlpack/bindings/julia/print_output_processing.hpp +++ b/src/mlpack/bindings/julia/print_output_processing.hpp @@ -3,6 +3,11 @@ * @author Ryan Curtin * * Print Julia code to handle output arguments. + * + * mlpack is free software; you may redistribute it and/or modify it under the + * terms of the 3-clause BSD license. You should have received a copy of the + * 3-clause BSD license along with mlpack. If not, see + * http://www.opensource.org/licenses/BSD-3-Clause for more information. */ #ifndef MLPACK_BINDINGS_JULIA_PRINT_OUTPUT_PROCESSING_HPP #define MLPACK_BINDINGS_JULIA_PRINT_OUTPUT_PROCESSING_HPP diff --git a/src/mlpack/bindings/julia/print_output_processing_impl.hpp b/src/mlpack/bindings/julia/print_output_processing_impl.hpp index 407eb5b657..058bae5b87 100644 --- a/src/mlpack/bindings/julia/print_output_processing_impl.hpp +++ b/src/mlpack/bindings/julia/print_output_processing_impl.hpp @@ -3,6 +3,11 @@ * @author Ryan Curtin * * Print Julia code to handle output arguments. + * + * mlpack is free software; you may redistribute it and/or modify it under the + * terms of the 3-clause BSD license. You should have received a copy of the + * 3-clause BSD license along with mlpack. If not, see + * http://www.opensource.org/licenses/BSD-3-Clause for more information. */ #ifndef MLPACK_BINDINGS_JULIA_PRINT_OUTPUT_PROCESSING_IMPL_HPP #define MLPACK_BINDINGS_JULIA_PRINT_OUTPUT_PROCESSING_IMPL_HPP diff --git a/src/mlpack/bindings/julia/print_param_defn.hpp b/src/mlpack/bindings/julia/print_param_defn.hpp index 46dc604a2c..84e482431a 100644 --- a/src/mlpack/bindings/julia/print_param_defn.hpp +++ b/src/mlpack/bindings/julia/print_param_defn.hpp @@ -4,6 +4,11 @@ * * If the type is serializable, we need to define a special utility function to * set a CLI parameter of that type. + * + * mlpack is free software; you may redistribute it and/or modify it under the + * terms of the 3-clause BSD license. You should have received a copy of the + * 3-clause BSD license along with mlpack. If not, see + * http://www.opensource.org/licenses/BSD-3-Clause for more information. */ #ifndef MLPACK_BINDINGS_JULIA_PRINT_PARAM_DEFN_HPP #define MLPACK_BINDINGS_JULIA_PRINT_PARAM_DEFN_HPP diff --git a/src/mlpack/bindings/julia/strip_type.hpp b/src/mlpack/bindings/julia/strip_type.hpp index 3a2a179ac1..087e74e940 100644 --- a/src/mlpack/bindings/julia/strip_type.hpp +++ b/src/mlpack/bindings/julia/strip_type.hpp @@ -4,6 +4,11 @@ * * Given a C++ type name, turn it into something that has no special characters * that can simply be printed. + * + * mlpack is free software; you may redistribute it and/or modify it under the + * terms of the 3-clause BSD license. You should have received a copy of the + * 3-clause BSD license along with mlpack. If not, see + * http://www.opensource.org/licenses/BSD-3-Clause for more information. */ #ifndef MLPACK_BINDINGS_JULIA_STRIP_TYPE_HPP #define MLPACK_BINDINGS_JULIA_STRIP_TYPE_HPP diff --git a/src/mlpack/core/data/image_info.hpp b/src/mlpack/core/data/image_info.hpp index 9693127500..3fa91ef303 100644 --- a/src/mlpack/core/data/image_info.hpp +++ b/src/mlpack/core/data/image_info.hpp @@ -69,6 +69,15 @@ class ImageInfo //! Modify the image quality. size_t& Quality() { return quality; } + template + void serialize(Archive& ar, const unsigned int /* version */) + { + ar & BOOST_SERIALIZATION_NVP(width); + ar & BOOST_SERIALIZATION_NVP(channels); + ar & BOOST_SERIALIZATION_NVP(height); + ar & BOOST_SERIALIZATION_NVP(quality); + } + private: // To store the image width. size_t width; diff --git a/src/mlpack/core/data/load_image.cpp b/src/mlpack/core/data/load_image.cpp index 2796e9a1f4..7801a6d07a 100644 --- a/src/mlpack/core/data/load_image.cpp +++ b/src/mlpack/core/data/load_image.cpp @@ -3,19 +3,23 @@ * @author Mehul Kumar Nirala * * Implementation of image loading functionality via STB. + * + * mlpack is free software; you may redistribute it and/or modify it under the + * terms of the 3-clause BSD license. You should have received a copy of the + * 3-clause BSD license along with mlpack. If not, see + * http://www.opensource.org/licenses/BSD-3-Clause for more information. */ #include "load.hpp" #include "image_info.hpp" #ifdef HAS_STB +// The definition of STB_IMAGE_IMPLEMENTATION means that the implementation will +// be included here directly. #define STB_IMAGE_STATIC #define STB_IMAGE_IMPLEMENTATION -#include -#define STB_IMAGE_WRITE_STATIC -#define STB_IMAGE_WRITE_IMPLEMENTATION -#include +#include namespace mlpack { namespace data { diff --git a/src/mlpack/core/data/save_image.cpp b/src/mlpack/core/data/save_image.cpp index c1802073e4..abc8b701e9 100644 --- a/src/mlpack/core/data/save_image.cpp +++ b/src/mlpack/core/data/save_image.cpp @@ -3,18 +3,29 @@ * @author Mehul Kumar Nirala * * Implementation of image saving functionality via STB. + * + * mlpack is free software; you may redistribute it and/or modify it under the + * terms of the 3-clause BSD license. You should have received a copy of the + * 3-clause BSD license along with mlpack. If not, see + * http://www.opensource.org/licenses/BSD-3-Clause for more information. */ #include "save.hpp" #ifdef HAS_STB -#define STB_IMAGE_STATIC -#define STB_IMAGE_IMPLEMENTATION -#include - +// The implementation of the functions is included directly, so we need to make +// sure it doesn't get included twice. This is to work around a bug in old +// versions of STB where not all functions were correctly marked static. #define STB_IMAGE_WRITE_STATIC -#define STB_IMAGE_WRITE_IMPLEMENTATION +#ifndef STB_IMAGE_WRITE_IMPLEMENTATION + #define STB_IMAGE_WRITE_IMPLEMENTATION +#else + #undef STB_IMAGE_WRITE_IMPLEMENTATION +#endif #include +#ifndef STB_IMAGE_WRITE_IMPLEMENTATION + #define STB_IMAGE_WRITE_IMPLEMENTATION +#endif namespace mlpack { namespace data { @@ -54,6 +65,12 @@ bool SaveImage(const std::string& filename, Log::Warn << "Only the first image will be saved!" << std::endl; } + if (info.Width() * info.Height() * info.Channels() != image.n_elem) + { + Log::Fatal << "data::Save(): The given image dimensions do not match the " + << "dimensions of the matrix to be saved!" << std::endl; + } + bool status = false; unsigned char* imageMem = image.memptr(); diff --git a/src/mlpack/core/data/save_impl.hpp b/src/mlpack/core/data/save_impl.hpp index e19f8928bf..516e0aaf93 100644 --- a/src/mlpack/core/data/save_impl.hpp +++ b/src/mlpack/core/data/save_impl.hpp @@ -328,15 +328,11 @@ bool Save(const std::vector& files, } arma::Mat img; - bool status = Save(files[0], img, info, fatal); + bool status = true; - // Decide matrix dimension using the image height and width. - matrix.set_size(info.Width() * info.Height() * info.Channels(), files.size()); - matrix.col(0) = img; - - for (size_t i = 1; i < files.size() ; i++) + for (size_t i = 0; i < files.size() ; i++) { - arma::Mat colImg(matrix.colptr(i), matrix.n_rows, 1, + arma::Mat colImg(matrix.colptr(i), matrix.n_rows, 1, false, true); status &= Save(files[i], colImg, info, fatal); } diff --git a/src/mlpack/core/data/string_encoding.hpp b/src/mlpack/core/data/string_encoding.hpp index 6c529fe31f..9ed18cba6e 100644 --- a/src/mlpack/core/data/string_encoding.hpp +++ b/src/mlpack/core/data/string_encoding.hpp @@ -24,7 +24,8 @@ namespace data { /** * The class translates a set of strings into numbers using various encoding - * algorithms. + * algorithms. The encoder writes data either in the column-major order or + * in the row-major order depending on the output data type. * * @tparam EncodingPolicyType Type of the encoding algorithm itself. * @tparam DictionaryType Type of the dictionary. @@ -90,11 +91,17 @@ class StringEncoding void Clear(); /** - * Encode the given text and write the result to the given output. + * Encode the given text and write the result to the given output. The encoder + * writes data in the column-major order or in the row-major order depending + * on the output data type. + * + * If the output type is either arma::mat or arma::sp_mat then the function + * writes it in the column-major order. If the output type is 2D std::vector + * then the function writes it in the row major order. * * @tparam OutputType Type of the output container. The function supports * the following types: arma::mat, arma::sp_mat, - * std::vector>. + * std::vector>. * @tparam TokenizerType Type of the tokenizer. * * @param input Corpus of text to encode. @@ -132,11 +139,16 @@ class StringEncoding private: /** * A helper function to encode the given text and write the result to - * the given output. + * the given output. The encoder writes data in the column-major order or + * in the row-major order depending on the output data type. + * + * If the output type is either arma::mat or arma::sp_mat then the function + * writes it in the column-major order. If the output type is 2D std::vector + * then the function writes it in the row major order. * * @tparam OutputType Type of the output container. The function supports * the following types: arma::mat, arma::sp_mat, - * std::vector>. + * std::vector>. * @tparam TokenizerType Type of the tokenizer. * @tparam PolicyType The type of the encoding policy. It has to be * equal to EncodingPolicyType. @@ -153,9 +165,7 @@ class StringEncoding * 2. IsTokenEmpty() that accepts a token and returns true if the given * token is empty. */ - template + template void EncodeHelper(const std::vector& input, OutputType& output, const TokenizerType& tokenizer, @@ -164,11 +174,13 @@ class StringEncoding /** * A helper function to encode the given text and write the result to * the given output. This is an optimized overload for policies that support - * the one pass encoding algorithm. + * the one pass encoding algorithm. The encoder writes data in the row-major + * order. * * @tparam TokenizerType Type of the tokenizer. * @tparam PolicyType The type of the encoding policy. It has to be * equal to EncodingPolicyType. + * @tparam ElemType Type of the output values. * * @param input Corpus of text to encode. * @param output Output container to store the result. @@ -182,9 +194,9 @@ class StringEncoding * 2. IsTokenEmpty() that accepts a token and returns true if the given * token is empty. */ - template + template void EncodeHelper(const std::vector& input, - std::vector>& output, + std::vector>& output, const TokenizerType& tokenizer, PolicyType& policy, typename std::enable_if& input, { size_t numColumns = 0; + policy.Reset(); + // The first pass adds the extracted tokens to the dictionary. - for (const std::string& line : input) + for (size_t i = 0; i < input.size(); i++) { - boost::string_view strView(line); + boost::string_view strView(input[i]); auto token = tokenizer(strView); static_assert( @@ -127,9 +129,12 @@ EncodeHelper(const std::vector& input, if (!dictionary.HasToken(token)) dictionary.AddToken(std::move(token)); + policy.PreprocessToken(i, numTokens, dictionary.Value(token)); + token = tokenizer(strView); numTokens++; } + numColumns = std::max(numColumns, numTokens); } @@ -152,15 +157,17 @@ EncodeHelper(const std::vector& input, } template -template +template void StringEncoding:: EncodeHelper(const std::vector& input, - std::vector>& output, + std::vector>& output, const TokenizerType& tokenizer, PolicyType& policy, typename std::enable_if::onePassEncoding>::type*) { + policy.Reset(); + // The loop below extracts the tokens and writes the encoded values // at once. for (size_t i = 0; i < input.size(); i++) diff --git a/src/mlpack/core/data/string_encoding_policies/CMakeLists.txt b/src/mlpack/core/data/string_encoding_policies/CMakeLists.txt index 8a8c7ab65c..9f570e08d7 100644 --- a/src/mlpack/core/data/string_encoding_policies/CMakeLists.txt +++ b/src/mlpack/core/data/string_encoding_policies/CMakeLists.txt @@ -1,8 +1,10 @@ # Define the files that we need to compile. # Anything not in this list will not be compiled into mlpack. set(SOURCES + bag_of_words_encoding_policy.hpp dictionary_encoding_policy.hpp policy_traits.hpp + tf_idf_encoding_policy.hpp ) # add directory name to sources diff --git a/src/mlpack/core/data/string_encoding_policies/bag_of_words_encoding_policy.hpp b/src/mlpack/core/data/string_encoding_policies/bag_of_words_encoding_policy.hpp new file mode 100644 index 0000000000..d71a82ac6e --- /dev/null +++ b/src/mlpack/core/data/string_encoding_policies/bag_of_words_encoding_policy.hpp @@ -0,0 +1,171 @@ +/** + * @file bag_of_words_encoding_policy.hpp + * @author Jeffin Sam + * @author Mikhail Lozhnikov + * + * Definition of the BagOfWordsEncodingPolicy class. + * + * mlpack is free software; you may redistribute it and/or modify it under the + * terms of the 3-clause BSD license. You should have received a copy of the + * 3-clause BSD license along with mlpack. If not, see + * http://www.opensource.org/licenses/BSD-3-Clause for more information. + */ +#ifndef MLPACK_CORE_DATA_STR_ENCODING_POLICIES_BAG_OF_WORDS_ENCODING_POLICY_HPP +#define MLPACK_CORE_DATA_STR_ENCODING_POLICIES_BAG_OF_WORDS_ENCODING_POLICY_HPP + +#include +#include +#include + +namespace mlpack { +namespace data { + +/** + * Definition of the BagOfWordsEncodingPolicy class. + * + * BagOfWords is used as a helper class for StringEncoding. The encoder maps + * each dataset item to a vector of size N, where N is equal to the total unique + * number of tokens. The i-th coordinate of the output vector is equal to + * the number of times when the i-th token occurs in the corresponding dataset + * item. The order in which the tokens are labeled is defined by the dictionary + * used by the StringEncoding class. The encoder writes data either in the + * column-major order or in the row-major order depending on the output data + * type. + */ +class BagOfWordsEncodingPolicy +{ + public: + /** + * Clear the necessary internal variables. + */ + static void Reset() + { + // Nothing to do. + } + + /** + * The function initializes the output matrix. The encoder writes data + * in the column-major order. + * + * @tparam MatType The output matrix type. + * + * @param output Output matrix to store the encoded results (sp_mat or mat). + * @param datasetSize The number of strings in the input dataset. + * @param maxNumTokens The maximum number of tokens in the strings of the + * input dataset (not used). + * @param dictionarySize The size of the dictionary. + */ + template + static void InitMatrix(MatType& output, + const size_t datasetSize, + const size_t /* maxNumTokens */, + const size_t dictionarySize) + { + output.zeros(dictionarySize, datasetSize); + } + + /** + * The function initializes the output matrix. The encoder writes data + * in the row-major order. + * + * Overloaded function to save the result in vector>. + * + * @tparam ElemType Type of the output values. + * + * @param output Output matrix to store the encoded results. + * @param datasetSize The number of strings in the input dataset. + * @param maxNumTokens The maximum number of tokens in the strings of the + * input dataset (not used). + * @param dictionarySize The size of the dictionary. + */ + template + static void InitMatrix(std::vector>& output, + const size_t datasetSize, + const size_t /* maxNumTokens */, + const size_t dictionarySize) + { + output.resize(datasetSize, std::vector(dictionarySize)); + } + + /** + * The function performs the bag of words encoding algorithm i.e. it writes + * the encoded token to the output. The encoder writes data in the + * column-major order. + * + * @tparam MatType The output matrix type. + * + * @param output Output matrix to store the encoded results (sp_mat or mat). + * @param value The encoded token. + * @param line The line number at which the encoding is performed. + * @param index The token index in the line. + */ + template + static void Encode(MatType& output, + const size_t value, + const size_t line, + const size_t /* index */) + { + // The labels are assigned sequentially starting from one. + output(value - 1, line) += 1; + } + + /** + * The function performs the bag of words encoding algorithm i.e. it writes + * the encoded token to the output. The encoder writes data in the + * row-major order. + * + * Overloaded function to accept vector> as the output + * type. + * + * @tparam ElemType Type of the output values. + * + * @param output Output matrix to store the encoded results. + * @param value The encoded token. + * @param line The line number at which the encoding is performed. + * @param index The line token number at which the encoding is performed. + */ + template + static void Encode(std::vector>& output, + const size_t value, + const size_t line, + const size_t /* index */) + { + // The labels are assigned sequentially starting from one. + output[line][value - 1] += 1; + } + + /** + * The function is not used by the bag of words encoding policy. + * + * @param line The line number at which the encoding is performed. + * @param index The token sequence number in the line. + * @param value The encoded token. + */ + static void PreprocessToken(size_t /* line */, + size_t /* index */, + size_t /* value */) + { } + + /** + * Serialize the class to the given archive. + */ + template + void serialize(Archive& /* ar */, const unsigned int /* version */) + { + // Nothing to serialize. + } +}; + +/** + * A convenient alias for the StringEncoding class with BagOfWordsEncodingPolicy + * and the default dictionary for the given token type. + * + * @tparam TokenType Type of the tokens. + */ +template +using BagOfWordsEncoding = StringEncoding>; +} // namespace data +} // namespace mlpack + +#endif diff --git a/src/mlpack/core/data/string_encoding_policies/dictionary_encoding_policy.hpp b/src/mlpack/core/data/string_encoding_policies/dictionary_encoding_policy.hpp index 1e67c6d29f..d9a37cacac 100644 --- a/src/mlpack/core/data/string_encoding_policies/dictionary_encoding_policy.hpp +++ b/src/mlpack/core/data/string_encoding_policies/dictionary_encoding_policy.hpp @@ -25,65 +25,92 @@ namespace data { * The encoder assigns a positive integer number to each unique token and treats * the dataset as categorical. The numbers are assigned sequentially starting * from one. The order in which the tokens are labeled is defined by - * the dictionary used by the StringEncoding class. + * the dictionary used by the StringEncoding class. The encoder writes data + * either in the column-major order or in the row-major order depending on + * the output data type. */ class DictionaryEncodingPolicy { public: /** - * The function initializes the output matrix. - * - * @tparam MatType The output matrix type. - * - * @param output Output matrix to store the encoded results (sp_mat or mat). - * @param datasetSize The number of strings in the input dataset. - * @param maxNumTokens The maximum number of tokens in the strings of the - input dataset. - * @param dictionarySize The size of the dictionary (not used). - */ + * Clear the necessary internal variables. + */ + static void Reset() + { + // Nothing to do. + } + + /** + * The function initializes the output matrix. The encoder writes data + * in the column-major order. + * + * @tparam MatType The output matrix type. + * + * @param output Output matrix to store the encoded results (sp_mat or mat). + * @param datasetSize The number of strings in the input dataset. + * @param maxNumTokens The maximum number of tokens in the strings of the + * input dataset. + * @param dictionarySize The size of the dictionary (not used). + */ template static void InitMatrix(MatType& output, const size_t datasetSize, const size_t maxNumTokens, - const size_t /*dictionarySize*/) + const size_t /* dictionarySize */) { - output.zeros(datasetSize, maxNumTokens); + output.zeros(maxNumTokens, datasetSize); } - /** - * The function performs the dictionary encoding algorithm i.e. it writes - * the encoded token to the ouput. - * - * @tparam MatType The output matrix type. - * - * @param output Output matrix to store the encoded results (sp_mat or mat). - * @param value The encoded token. - * @param row The row number at which the encoding is performed. - * @param col The token index in the row. - */ + /** + * The function performs the dictionary encoding algorithm i.e. it writes + * the encoded token to the output. The encoder writes data in the + * column-major order. + * + * @tparam MatType The output matrix type. + * + * @param output Output matrix to store the encoded results (sp_mat or mat). + * @param value The encoded token. + * @param line The line number at which the encoding is performed. + * @param index The token index in the line. + */ template static void Encode(MatType& output, const size_t value, - const size_t row, - const size_t col) + const size_t line, + const size_t index) { - output(row, col) = value; + output(index, line) = value; } - /** + /** * The function performs the dictionary encoding algorithm i.e. it writes - * the encoded token to the ouput. This is an overload function which saves - * the result into the given vector to avoid padding. + * the encoded token to the output. This is an overloaded function which saves + * the result into the given vector to avoid padding. The encoder writes data + * in the row-major order. * - * @param output Output vector to store the encoded results. + * @tparam ElemType Type of the output values. + * + * @param output Output vector to store the encoded line. * @param value The encoded token. */ - static void Encode(std::vector& output, - const size_t value) + template + static void Encode(std::vector& output, size_t value) { output.push_back(value); } + /** + * The function is not used by the dictionary encoding policy. + * + * @param line The line number at which the encoding is performed. + * @param index The token sequence number in the line. + * @param value The encoded token. + */ + static void PreprocessToken(const size_t /* line */, + const size_t /* index */, + const size_t /* value */) + { } + /** * Serialize the class to the given archive. */ diff --git a/src/mlpack/core/data/string_encoding_policies/tf_idf_encoding_policy.hpp b/src/mlpack/core/data/string_encoding_policies/tf_idf_encoding_policy.hpp new file mode 100644 index 0000000000..853493e285 --- /dev/null +++ b/src/mlpack/core/data/string_encoding_policies/tf_idf_encoding_policy.hpp @@ -0,0 +1,349 @@ +/** + * @file tf_idf_encoding_policy.hpp + * @author Jeffin Sam + * @author Mikhail Lozhnikov + * + * Definition of the TfIdfEncodingPolicy class. + * + * mlpack is free software; you may redistribute it and/or modify it under the + * terms of the 3-clause BSD license. You should have received a copy of the + * 3-clause BSD license along with mlpack. If not, see + * http://www.opensource.org/licenses/BSD-3-Clause for more information. + */ +#ifndef MLPACK_CORE_DATA_STRING_ENCODING_POLICIES_TF_IDF_ENCODING_POLICY_HPP +#define MLPACK_CORE_DATA_STRING_ENCODING_POLICIES_TF_IDF_ENCODING_POLICY_HPP + +#include +#include +#include + +namespace mlpack { +namespace data { + +/** + * Definition of the TfIdfEncodingPolicy class. TfIdfEncodingPolicy is used + * as a helper class for StringEncoding. + * + * Tf-idf is a weighting scheme that takes into account the importance of + * encoded tokens. The tf-idf statistics is equal to term frequency (tf) + * multiplied by inverse document frequency (idf). + * The encoder assigns the corresponding tf-idf value to each token. The order + * in which the tokens are labeled is defined by the dictionary used by the + * StringEncoding class. The encoder writes data either in the column-major + * order or in the row-major order depending on the output data type. + */ +class TfIdfEncodingPolicy +{ + public: + /** + * Enum class used to identify the type of the term frequency statistics. + * + * The present implementation supports the following types: + * BINARY Term frequency equals 1 if the row contains the encoded + * token and 0 otherwise. + * RAW_COUNT Term frequency equals the number of times when the encoded + * token occurs in the row. + * TERM_FREQUENCY Term frequency equals the number of times when the encoded + * token occurs in the row divided by the total number of + * tokens in the row. + * SUBLINEAR_TF Term frequency equals \f$ 1 + log(rawCount), \f$ where + * rawCount is equal to the number of times when the encoded + * token occurs in the row. + */ + enum class TfTypes + { + BINARY, + RAW_COUNT, + TERM_FREQUENCY, + SUBLINEAR_TF, + }; + + /** + * Construct this using the term frequency type and the inverse document + * frequency type. + * + * @param tfType Type of the term frequency statistics. + * @param smoothIdf Used to indicate whether to use smooth idf or not. + * If idf is smooth it's calculated by the following formula: + * \f$ idf(T) = \log \frac{1 + N}{1 + df(T)} + 1, \f$ where + * \f$ N \f$ is the total number of strings in the document, + * \f$ T \f$ is the current encoded token, \f$ df(T) \f$ + * equals the number of strings which contain the token. + * If idf isn't smooth then the following rule applies: + * \f$ idf(T) = \log \frac{N}{df(T)} + 1. \f$ + */ + TfIdfEncodingPolicy(const TfTypes tfType = TfTypes::RAW_COUNT, + const bool smoothIdf = true) : + tfType(tfType), + smoothIdf(smoothIdf) + { } + + /** + * Clear the necessary internal variables. + */ + void Reset() + { + tokensFrequences.clear(); + numContainingStrings.clear(); + linesSizes.clear(); + } + + /** + * The function initializes the output matrix. The encoder writes data + * in the row-major order. + * + * @tparam MatType The output matrix type. + * + * @param output Output matrix to store the encoded results (sp_mat or mat). + * @param datasetSize The number of strings in the input dataset. + * @param maxNumTokens The maximum number of tokens in the strings of the + * input dataset (not used). + * @param dictionarySize The size of the dictionary. + */ + template + static void InitMatrix(MatType& output, + const size_t datasetSize, + const size_t /* maxNumTokens */, + const size_t dictionarySize) + { + output.zeros(dictionarySize, datasetSize); + } + + /** + * The function initializes the output matrix. The encoder writes data + * in the row-major order. + * + * Overloaded function to save the result in vector>. + * + * @tparam ElemType Type of the output values. + * + * @param output Output matrix to store the encoded results. + * @param datasetSize The number of strings in the input dataset. + * @param maxNumTokens The maximum number of tokens in the strings of the + * input dataset (not used). + * @param dictionarySize The size of the dictionary. + */ + template + static void InitMatrix(std::vector>& output, + const size_t datasetSize, + const size_t /* maxNumTokens */, + const size_t dictionarySize) + { + output.resize(datasetSize, std::vector(dictionarySize)); + } + + /** + * The function performs the TfIdf encoding algorithm i.e. it writes + * the encoded token to the output. The encoder writes data in the + * column-major order. + * + * @tparam MatType The output matrix type. + * + * @param output Output matrix to store the encoded results (sp_mat or mat). + * @param value The encoded token. + * @param line The line number at which the encoding is performed. + * @param index The token index in the line. + */ + template + void Encode(MatType& output, + const size_t value, + const size_t line, + const size_t /* index */) + { + const typename MatType::elem_type tf = + TermFrequency( + tokensFrequences[line][value], linesSizes[line]); + + const typename MatType::elem_type idf = + InverseDocumentFrequency( + output.n_cols, numContainingStrings[value]); + + output(value - 1, line) = tf * idf; + } + + /** + * The function performs the TfIdf encoding algorithm i.e. it writes + * the encoded token to the output. The encoder writes data in the + * row-major order. + * + * Overloaded function to accept vector> as the output + * type. + * + * @tparam ElemType Type of the output values. + * + * @param output Output matrix to store the encoded results. + * @param value The encoded token. + * @param line The line number at which the encoding is performed. + * @param index The token index in the line. + */ + template + void Encode(std::vector>& output, + const size_t value, + const size_t line, + const size_t /* index */) + { + const ElemType tf = TermFrequency( + tokensFrequences[line][value], linesSizes[line]); + + const ElemType idf = InverseDocumentFrequency( + output.size(), numContainingStrings[value]); + + output[line][value - 1] = tf * idf; + } + + /* + * The function calculates the necessary statistics for the purpose + * of the tf-idf algorithm during the first pass through the dataset. + * + * @param line The line number at which the encoding is performed. + * @param index The token sequence number in the line. + * @param value The encoded token. + */ + void PreprocessToken(const size_t line, + const size_t /* index */, + const size_t value) + { + if (line >= tokensFrequences.size()) + { + linesSizes.resize(line + 1); + tokensFrequences.resize(line + 1); + } + + tokensFrequences[line][value]++; + + if (tokensFrequences[line][value] == 1) + numContainingStrings[value]++; + + linesSizes[line]++; + } + + //! Return token frequencies. + const std::vector>& + TokensFrequences() const { return tokensFrequences; } + //! Modify token frequencies. + std::vector>& TokensFrequences() + { + return tokensFrequences; + } + + //! Get the number of containing strings depending on the given token. + const std::unordered_map& NumContainingStrings() const + { + return numContainingStrings; + } + + //! Modify the number of containing strings depending on the given token. + std::unordered_map& NumContainingStrings() + { + return numContainingStrings; + } + + //! Return the lines sizes. + const std::vector& LinesSizes() const { return linesSizes; } + //! Modify the lines sizes. + std::vector& LinesSizes() { return linesSizes; } + + //! Return the term frequency type. + TfTypes TfType() const { return tfType; } + //! Modify the term frequency type. + TfTypes& TfType() { return tfType; } + + //! Determine the idf algorithm type (whether it's smooth or not). + bool SmoothIdf() const { return smoothIdf; } + //! Modify the idf algorithm type (whether it's smooth or not). + bool& SmoothIdf() { return smoothIdf; } + + /** + * Serialize the class to the given archive. + */ + template + void serialize(Archive& ar, const unsigned int /* version */) + { + ar & BOOST_SERIALIZATION_NVP(tfType); + ar & BOOST_SERIALIZATION_NVP(smoothIdf); + } + + private: + /** + * The function calculates the term frequency statistics. + * + * @tparam ValueType Type of the returned value. + * + * @param numOccurrences The number of the given token occurrences in + * the line. + * @param numTokens The total number of tokens in the line. + */ + template + ValueType TermFrequency(const size_t numOccurrences, + const size_t numTokens) + { + switch (tfType) + { + case TfTypes::BINARY: + return numOccurrences > 0; + case TfTypes::RAW_COUNT: + return numOccurrences; + case TfTypes::TERM_FREQUENCY: + return static_cast(numOccurrences) / numTokens; + case TfTypes::SUBLINEAR_TF: + return std::log(static_cast(numOccurrences)) + 1; + default: + Log::Fatal << "Incorrect term frequency type!"; + return 0; + } + } + + /** + * The function calculates the inverse document frequency statistics. + * + * @tparam ValueType Type of the returned value. + * + * @param totalNumLines The total number of strings in the input dataset. + * @param numOccurrences The number of strings in the input dataset + * which contain the current token. + */ + template + ValueType InverseDocumentFrequency(const size_t totalNumLines, + const size_t numOccurrences) + { + if (smoothIdf) + { + return std::log(static_cast(totalNumLines + 1) / + (1 + numOccurrences)) + 1.0; + } + else + { + return std::log(static_cast(totalNumLines) / + numOccurrences) + 1.0; + } + } + + private: + //! Used to store the total number of tokens for each line. + std::vector> tokensFrequences; + /** + * Used to store the number of strings which contain a token depending + * on the given token. + */ + std::unordered_map numContainingStrings; + //! Used to store the number of tokens in each line. + std::vector linesSizes; + //! Type of the term frequency scheme. + TfTypes tfType; + //! Indicates whether the idf scheme is smooth or not. + bool smoothIdf; +}; + +/** + * A convenient alias for the StringEncoding class with TfIdfEncodingPolicy + * and the default dictionary for the given token type. + * + * @tparam TokenType Type of the tokens. + */ +template +using TfIdfEncoding = StringEncoding>; +} // namespace data +} // namespace mlpack + +#endif diff --git a/src/mlpack/core/util/arma_traits.hpp b/src/mlpack/core/util/arma_traits.hpp index 45e5dac125..155a6a0d82 100644 --- a/src/mlpack/core/util/arma_traits.hpp +++ b/src/mlpack/core/util/arma_traits.hpp @@ -81,14 +81,34 @@ struct IsVector > const static bool value = true; }; -// I'm not so sure about this one. An SpSubview object can be a row or column, -// but it can also be a matrix subview. -// template<> -template -struct IsVector > -{ - const static bool value = true; -}; +#if ((ARMA_VERSION_MAJOR >= 10) || \ + ((ARMA_VERSION_MAJOR == 9) && (ARMA_VERSION_MINOR >= 869))) + + // Armadillo 9.869+ has SpSubview_col and SpSubview_row + + template + struct IsVector > + { + const static bool value = true; + }; + + template + struct IsVector > + { + const static bool value = true; + }; + +#else + + // fallback for older Armadillo versions + + template + struct IsVector > + { + const static bool value = true; + }; + +#endif #endif diff --git a/src/mlpack/core/util/version.hpp b/src/mlpack/core/util/version.hpp index 14d8daa822..a1c421d0fa 100644 --- a/src/mlpack/core/util/version.hpp +++ b/src/mlpack/core/util/version.hpp @@ -17,8 +17,8 @@ // The version of mlpack. If this is a git repository, this will be a version // with higher number than the most recent release. #define MLPACK_VERSION_MAJOR 3 -#define MLPACK_VERSION_MINOR 2 -#define MLPACK_VERSION_PATCH 3 +#define MLPACK_VERSION_MINOR 3 +#define MLPACK_VERSION_PATCH 1 // The name of the version (for use by --version). namespace mlpack { diff --git a/src/mlpack/methods/ann/layer/layer.hpp b/src/mlpack/methods/ann/layer/layer.hpp index 6006073a99..44cc8b667f 100644 --- a/src/mlpack/methods/ann/layer/layer.hpp +++ b/src/mlpack/methods/ann/layer/layer.hpp @@ -12,32 +12,57 @@ #ifndef MLPACK_METHODS_ANN_LAYER_LAYER_HPP #define MLPACK_METHODS_ANN_LAYER_LAYER_HPP +#include "add.hpp" #include "add_merge.hpp" +#include "alpha_dropout.hpp" #include "atrous_convolution.hpp" +#include "base_layer.hpp" #include "batch_norm.hpp" +#include "bilinear_interpolation.hpp" +#include "c_relu.hpp" +#include "celu.hpp" #include "concat_performance.hpp" +#include "concat.hpp" +#include "concatenate.hpp" +#include "constant.hpp" #include "convolution.hpp" #include "dropconnect.hpp" +#include "dropout.hpp" +#include "elu.hpp" +#include "fast_lstm.hpp" +#include "flexible_relu.hpp" #include "glimpse.hpp" +#include "gru.hpp" +#include "hard_tanh.hpp" +#include "hardshrink.hpp" #include "highway.hpp" +#include "join.hpp" #include "layer_norm.hpp" #include "layer_types.hpp" +#include "leaky_relu.hpp" #include "linear.hpp" #include "linear_no_bias.hpp" +#include "log_softmax.hpp" +#include "lookup.hpp" #include "lstm.hpp" +#include "max_pooling.hpp" +#include "mean_pooling.hpp" #include "minibatch_discrimination.hpp" +#include "multiply_constant.hpp" #include "multiply_merge.hpp" #include "padding.hpp" -#include "gru.hpp" -#include "fast_lstm.hpp" -#include "recurrent.hpp" +#include "parametric_relu.hpp" #include "recurrent_attention.hpp" +#include "recurrent.hpp" +#include "reinforce_normal.hpp" #include "reparametrization.hpp" +#include "select.hpp" #include "sequential.hpp" +#include "softshrink.hpp" #include "subview.hpp" -#include "concat.hpp" -#include "vr_class_reward.hpp" #include "transposed_convolution.hpp" +#include "virtual_batch_norm.hpp" +#include "vr_class_reward.hpp" #include "weight_norm.hpp" #endif diff --git a/src/mlpack/methods/ann/layer/layer_types.hpp b/src/mlpack/methods/ann/layer/layer_types.hpp index 3c17021e45..5a07c59016 100644 --- a/src/mlpack/methods/ann/layer/layer_types.hpp +++ b/src/mlpack/methods/ann/layer/layer_types.hpp @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include diff --git a/src/mlpack/methods/ann/loss_functions/CMakeLists.txt b/src/mlpack/methods/ann/loss_functions/CMakeLists.txt index 8a2111091b..c20856a214 100644 --- a/src/mlpack/methods/ann/loss_functions/CMakeLists.txt +++ b/src/mlpack/methods/ann/loss_functions/CMakeLists.txt @@ -3,6 +3,8 @@ set(SOURCES cross_entropy_error.hpp cross_entropy_error_impl.hpp + cosine_embedding_loss.hpp + cosine_embedding_loss_impl.hpp dice_loss.hpp dice_loss_impl.hpp earth_mover_distance.hpp @@ -11,6 +13,8 @@ set(SOURCES huber_loss_impl.hpp kl_divergence.hpp kl_divergence_impl.hpp + margin_ranking_loss.hpp + margin_ranking_loss_impl.hpp mean_bias_error.hpp mean_bias_error_impl.hpp mean_squared_error.hpp diff --git a/src/mlpack/methods/ann/loss_functions/cosine_embedding_loss.hpp b/src/mlpack/methods/ann/loss_functions/cosine_embedding_loss.hpp new file mode 100644 index 0000000000..044fbbdd0e --- /dev/null +++ b/src/mlpack/methods/ann/loss_functions/cosine_embedding_loss.hpp @@ -0,0 +1,141 @@ +/** + * @file cosine_embedding_loss.hpp + * @author Kartik Dutt + * + * Definition of the Cosine Embedding loss function. + * + * mlpack is free software; you may redistribute it and/or modify it under the + * terms of the 3-clause BSD license. You should have received a copy of the + * 3-clause BSD license along with mlpack. If not, see + * http://www.opensource.org/licenses/BSD-3-Clause for more information. + */ +#ifndef MLPACK_METHODS_ANN_LOSS_FUNCTION_COSINE_EMBEDDING_HPP +#define MLPACK_METHODS_ANN_LOSS_FUNCTION_COSINE_EMBEDDING_HPP + +#include + +namespace mlpack { +namespace ann /** Artificial Neural Network. */ { + +/** + * Cosine Embedding Loss function is used for measuring whether two inputs are + * similar or dissimilar, using the cosine distance, and is typically used + * for learning nonlinear embeddings or semi-supervised learning. + * + * @f{eqnarray*}{ + * f(x) = 1 - cos(x1, x2) , for y = 1 + * f(x) = max(0, cos(x1, x2) - margin) , for y = -1 + * @f} + * + * @tparam InputDataType Type of the input data (arma::colvec, arma::mat, + * arma::sp_mat or arma::cube). + * @tparam OutputDataType Type of the output data (arma::colvec, arma::mat, + * arma::sp_mat or arma::cube). + */ +template < + typename InputDataType = arma::mat, + typename OutputDataType = arma::mat +> +class CosineEmbeddingLoss +{ + public: + /** + * Create the CosineEmbeddingLoss object. + * + * @param margin Increases cosine distance in case of dissimilarity. + * Refer definition of cosine-embedding-loss above. + * @param similarity Determines whether to use similarity or dissimilarity for + * comparision. + * @param takeMean Boolean variable to specify whether to take mean or not. + * Specifies reduction method i.e. sum or mean corresponding + * to 0 and 1 respectively. Default value = 0. + */ + CosineEmbeddingLoss(const double margin = 0.0, + const bool similarity = true, + const bool takeMean = false); + + /** + * Ordinary feed forward pass of a neural network. + * + * @param input Input data used for evaluating the specified function. + * @param target The target vector. + */ + template + typename InputType::elem_type Forward(const InputType& input, + const TargetType& target); + + /** + * Ordinary feed backward pass of a neural network. + * + * @param input The propagated input activation. + * @param target The target vector. + * @param output The calculated error. + */ + template + void Backward(const InputType& input, + const TargetType& target, + OutputType& output); + + //! Get the input parameter. + InputDataType& InputParameter() const { return inputParameter; } + //! Modify the input parameter. + InputDataType& InputParameter() { return inputParameter; } + + //! Get the output parameter. + OutputDataType& OutputParameter() const { return outputParameter; } + //! Modify the output parameter. + OutputDataType& OutputParameter() { return outputParameter; } + + //! Get the delta. + OutputDataType& Delta() const { return delta; } + //! Modify the delta. + OutputDataType& Delta() { return delta; } + + //! Get the value of takeMean. + bool TakeMean() const { return takeMean; } + //! Modify the value of takeMean. + bool& TakeMean() { return takeMean; } + + //! Get the value of margin. + double Margin() const { return margin; } + //! Modify the value of takeMean. + double& Margin() { return margin; } + + //! Get the value of similarity hyperparameter. + bool Similarity() const { return similarity; } + //! Modify the value of takeMean. + bool& Similarity() { return similarity; } + + /** + * Serialize the layer. + */ + template + void serialize(Archive& ar, const unsigned int /* version */); + + private: + //! Locally-stored delta object. + OutputDataType delta; + + //! Locally-stored input parameter object. + InputDataType inputParameter; + + //! Locally-stored output parameter object. + OutputDataType outputParameter; + + //! Locally-stored value of similarity hyper-parameter. + bool similarity; + + //! Locally-stored value of margin hyper-parameter. + double margin; + + //! Locally-stored value of takeMean hyper-parameter. + bool takeMean; +}; // class CosineEmbeddingLoss + +} // namespace ann +} // namespace mlpack + +// Include implementation. +#include "cosine_embedding_loss_impl.hpp" + +#endif diff --git a/src/mlpack/methods/ann/loss_functions/cosine_embedding_loss_impl.hpp b/src/mlpack/methods/ann/loss_functions/cosine_embedding_loss_impl.hpp new file mode 100644 index 0000000000..b708567b28 --- /dev/null +++ b/src/mlpack/methods/ann/loss_functions/cosine_embedding_loss_impl.hpp @@ -0,0 +1,120 @@ +/** + * @file cosine_embedding_loss_impl.hpp + * @author Kartik Dutt + * + * Implementation of the Cosine Embedding loss function. + * + * mlpack is free software; you may redistribute it and/or modify it under the + * terms of the 3-clause BSD license. You should have received a copy of the + * 3-clause BSD license along with mlpack. If not, see + * http://www.opensource.org/licenses/BSD-3-Clause for more information. + */ +#ifndef MLPACK_METHODS_ANN_LOSS_FUNCTION_COSINE_EMBEDDING_IMPL_HPP +#define MLPACK_METHODS_ANN_LOSS_FUNCTION_COSINE_EMBEDDING_IMPL_HPP + +// In case it hasn't yet been included. +#include "cosine_embedding_loss.hpp" + +namespace mlpack { +namespace ann /** Artificial Neural Network. */ { + +template +CosineEmbeddingLoss::CosineEmbeddingLoss( + const double margin, const bool similarity, const bool takeMean): + margin(margin), similarity(similarity), takeMean(takeMean) +{ + // Nothing to do here. +} + +template +template +typename InputType::elem_type +CosineEmbeddingLoss::Forward( + const InputType& input, + const TargetType& target) +{ + typedef typename InputType::elem_type ElemType; + + const size_t cols = input.n_cols; + const size_t batchSize = input.n_elem / cols; + if (arma::size(input) != arma::size(target)) + Log::Fatal << "Input Tensors must have same dimensions." << std::endl; + + arma::colvec inputTemp1 = arma::vectorise(input); + arma::colvec inputTemp2 = arma::vectorise(target); + ElemType loss = 0.0; + + for (size_t i = 0; i < inputTemp1.n_elem; i += cols) + { + const ElemType cosDist = kernel::CosineDistance::Evaluate( + inputTemp1(arma::span(i, i + cols - 1)), inputTemp2(arma::span(i, + i + cols - 1))); + if (similarity) + loss += 1 - cosDist; + else + { + const ElemType currentLoss = cosDist - margin; + loss += currentLoss > 0 ? currentLoss : 0; + } + } + + if (takeMean) + loss = (ElemType) loss / batchSize; + + return loss; +} + +template +template +void CosineEmbeddingLoss::Backward( + const InputType& input, + const TargetType& target, + OutputType& output) +{ + typedef typename InputType::elem_type ElemType; + + const size_t cols = input.n_cols; + const size_t batchSize = input.n_elem / cols; + if (arma::size(input) != arma::size(target)) + Log::Fatal << "Input Tensors must have same dimensions." << std::endl; + + arma::colvec inputTemp1 = arma::vectorise(input); + arma::colvec inputTemp2 = arma::vectorise(target); + output.set_size(arma::size(inputTemp1)); + + arma::colvec outputTemp(output.memptr(), inputTemp1.n_elem, + false, false); + for (size_t i = 0; i < inputTemp1.n_elem; i += cols) + { + const ElemType cosDist = kernel::CosineDistance::Evaluate(inputTemp1( + arma::span(i, i + cols -1)), inputTemp2(arma::span(i, i + cols -1))); + + if (cosDist < margin && !similarity) + outputTemp(arma::span(i, i + cols - 1)).zeros(); + else + { + const int multiplier = similarity ? 1 : -1; + outputTemp(arma::span(i, i + cols -1)) = -1 * multiplier * + (arma::normalise(inputTemp2(arma::span(i, i + cols - 1))) - + cosDist * arma::normalise(inputTemp1(arma::span(i, i + cols - + 1)))) / std::sqrt(arma::accu(arma::pow(inputTemp1(arma::span(i, i + + cols - 1)), 2))); + } + } +} + +template +template +void CosineEmbeddingLoss::serialize( + Archive& ar , + const unsigned int /* version */) +{ + ar & BOOST_SERIALIZATION_NVP(margin); + ar & BOOST_SERIALIZATION_NVP(similarity); + ar & BOOST_SERIALIZATION_NVP(takeMean); +} + +} // namespace ann +} // namespace mlpack + +#endif diff --git a/src/mlpack/methods/ann/loss_functions/margin_ranking_loss.hpp b/src/mlpack/methods/ann/loss_functions/margin_ranking_loss.hpp new file mode 100644 index 0000000000..e8798dd62b --- /dev/null +++ b/src/mlpack/methods/ann/loss_functions/margin_ranking_loss.hpp @@ -0,0 +1,102 @@ +/** + * @file margin_ranking_loss.hpp + * @author Andrei Mihalea + * + * Definition of the Margin Ranking Loss function. + * + * mlpack is free software; you may redistribute it and/or modify it under the + * terms of the 3-clause BSD license. You should have received a copy of the + * 3-clause BSD license along with mlpack. If not, see + * http://www.opensource.org/licenses/BSD-3-Clause for more information. + */ +#ifndef MLPACK_ANN_LOSS_FUNCTION_MARGIN_RANKING_LOSS_HPP +#define MLPACK_ANN_LOSS_FUNCTION_MARGIN_RANKING_LOSS_HPP + +#include + +namespace mlpack { +namespace ann /** Artificial Neural Network. */ { + +/** + * Margin ranking loss measures the loss given inputs and a label vector with + * values of 1 or -1. If the label is 1 then the first input should be ranked + * higher than the second input at a distance larger than a margin, and vice- + * versa if the label is -1. + * + * @tparam InputDataType Type of the input data (arma::colvec, arma::mat, + * arma::sp_mat or arma::cube). + * @tparam OutputDataType Type of the output data (arma::colvec, arma::mat, + * arma::sp_mat or arma::cube). + */ +template < + typename InputDataType = arma::mat, + typename OutputDataType = arma::mat +> +class MarginRankingLoss +{ + public: + /** + * Create the MarginRankingLoss object with Hyperparameter margin. + * Hyperparameter margin defines a minimum distance between correctly ranked + * samples. + */ + MarginRankingLoss(const double margin = 1.0); + + /** + * Computes the Margin Ranking Loss function. + * + * @param input Concatenation of the two inputs for evaluating the specified + * function. + * @param target The label vector which contains values of -1 or 1. + */ + template + typename InputType::elem_type Forward(const InputType& input, + const TargetType& target); + + /** + * Ordinary feed backward pass of a neural network. + * + * @param input The propagated concatenated input activation. + * @param target The label vector which contains -1 or 1 values. + * @param output The calculated error. + */ + template < + typename InputType, + typename TargetType, + typename OutputType + > + void Backward(const InputType& input, + const TargetType& target, + OutputType& output); + + //! Get the output parameter. + OutputDataType& OutputParameter() const { return outputParameter; } + //! Modify the output parameter. + OutputDataType& OutputParameter() { return outputParameter; } + + //! Get the margin parameter. + double Margin() const { return margin; } + //! Modify the margin parameter. + double& Margin() { return margin; } + + /** + * Serialize the layer. + */ + template + void serialize(Archive& ar, const unsigned int /* version */); + + private: + //! Locally-stored output parameter object. + OutputDataType outputParameter; + + //! The margin value used in calculating Margin Ranking Loss. + double margin; +}; // class MarginRankingLoss + +} // namespace ann +} // namespace mlpack + +// include implementation. +#include "margin_ranking_loss_impl.hpp" + +#endif diff --git a/src/mlpack/methods/ann/loss_functions/margin_ranking_loss_impl.hpp b/src/mlpack/methods/ann/loss_functions/margin_ranking_loss_impl.hpp new file mode 100644 index 0000000000..59649cbb3a --- /dev/null +++ b/src/mlpack/methods/ann/loss_functions/margin_ranking_loss_impl.hpp @@ -0,0 +1,74 @@ +/** + * @file margin_ranking_loss_impl.hpp + * @author Andrei Mihalea + * + * Implementation of the Margin Ranking Loss function. + * + * mlpack is free software; you may redistribute it and/or modify it under the + * terms of the 3-clause BSD license. You should have received a copy of the + * 3-clause BSD license along with mlpack. If not, see + * http://www.opensource.org/licenses/BSD-3-Clause for more information. + */ +#ifndef MLPACK_METHODS_ANN_LOSS_FUNCTION_MARGIN_IMPL_LOSS_HPP +#define MLPACK_METHODS_ANN_LOSS_FUNCTION_MARGIN_IMPL_LOSS_HPP + +// In case it hasn't been included. +#include "margin_ranking_loss.hpp" + +namespace mlpack { +namespace ann /** Artifical Neural Network. */ { + +template +MarginRankingLoss::MarginRankingLoss( + const double margin) : margin(margin) +{ + // Nothing to do here. +} + +template +template +typename InputType::elem_type +MarginRankingLoss::Forward( + const InputType& input, + const TargetType& target) +{ + const int inputRows = input.n_rows; + const InputType& input1 = input.rows(0, inputRows / 2 - 1); + const InputType& input2 = input.rows(inputRows / 2, inputRows - 1); + return arma::accu(arma::max(arma::zeros(size(target)), + -target % (input1 - input2) + margin)) / target.n_cols; +} + +template +template < + typename InputType, + typename TargetType, + typename OutputType +> +void MarginRankingLoss::Backward( + const InputType& input, + const TargetType& target, + OutputType& output) +{ + const int inputRows = input.n_rows; + const InputType& input1 = input.rows(0, inputRows / 2 - 1); + const InputType& input2 = input.rows(inputRows / 2, inputRows - 1); + output = -target % (input1 - input2) + margin; + output.elem(arma::find(output >= 0)).ones(); + output.elem(arma::find(output < 0)).zeros(); + output = (input2 - input1) % output / target.n_cols; +} + +template +template +void MarginRankingLoss::serialize( + Archive& ar, + const unsigned int /* version */) +{ + ar & BOOST_SERIALIZATION_NVP(margin); +} + +} // namespace ann +} // namespace mlpack + +#endif diff --git a/src/mlpack/methods/logistic_regression/logistic_regression_impl.hpp b/src/mlpack/methods/logistic_regression/logistic_regression_impl.hpp index 760af72acd..1f5b81eb33 100644 --- a/src/mlpack/methods/logistic_regression/logistic_regression_impl.hpp +++ b/src/mlpack/methods/logistic_regression/logistic_regression_impl.hpp @@ -25,7 +25,6 @@ LogisticRegression::LogisticRegression( const MatType& predictors, const arma::Row& responses, const double lambda) : - parameters(arma::rowvec(predictors.n_rows + 1, arma::fill::zeros)), lambda(lambda) { Train(predictors, responses); @@ -60,7 +59,6 @@ LogisticRegression::LogisticRegression( const arma::Row& responses, OptimizerType& optimizer, const double lambda) : - parameters(arma::rowvec(predictors.n_rows + 1, arma::fill::zeros)), lambda(lambda) { Train(predictors, responses, optimizer); @@ -85,9 +83,11 @@ double LogisticRegression::Train( OptimizerType& optimizer, CallbackTypes&&... callbacks) { - LogisticRegressionFunction errorFunction(predictors, - responses, - lambda); + LogisticRegressionFunction errorFunction(predictors, responses, + lambda); + + // Set size of parameters vector according to the input data received. + parameters = arma::rowvec(predictors.n_rows + 1, arma::fill::zeros); errorFunction.InitialPoint() = parameters; Timer::Start("logistic_regression_optimization"); diff --git a/src/mlpack/methods/preprocess/CMakeLists.txt b/src/mlpack/methods/preprocess/CMakeLists.txt index 2410d2b1cf..d1b1dd5816 100644 --- a/src/mlpack/methods/preprocess/CMakeLists.txt +++ b/src/mlpack/methods/preprocess/CMakeLists.txt @@ -40,4 +40,12 @@ add_markdown_docs(preprocess_imputer "cli" "preprocessing") add_cli_executable(preprocess_scale) add_python_binding(preprocess_scale) -add_markdown_docs(preprocess_scale "cli;python" "preprocessing") +add_julia_binding(preprocess_scale) +add_markdown_docs(preprocess_scale "cli;python;julia" "preprocessing") + +if (STB_AVAILABLE) + add_cli_executable(image_converter) + add_python_binding(image_converter) + add_julia_binding(image_converter) + add_markdown_docs(image_converter "cli;python;julia" "preprocessing") +endif () \ No newline at end of file diff --git a/src/mlpack/methods/preprocess/image_converter_main.cpp b/src/mlpack/methods/preprocess/image_converter_main.cpp new file mode 100644 index 0000000000..0d4beecf4b --- /dev/null +++ b/src/mlpack/methods/preprocess/image_converter_main.cpp @@ -0,0 +1,114 @@ +/** + * @file image_converter_main.cpp + * @author Jeffin Sam + * + * A CLI executable to load and save a image dataset. + * + * mlpack is free software; you may redistribute it and/or modify it under the + * terms of the 3-clause BSD license. You should have received a copy of the + * 3-clause BSD license along with mlpack. If not, see + * http://www.opensource.org/licenses/BSD-3-Clause for more information. + */ +#include +#include +#include +#include + +using namespace mlpack; +using namespace mlpack::util; +using namespace arma; +using namespace std; +using namespace mlpack::data; + +PROGRAM_INFO("Image Converter", + // Short description. + "A utility to load an image or set of images into a single dataset that" + " can then be used by other mlpack methods and utilities. This can also" + " unpack an image dataset into individual files, for instance after mlpack" + " methods have been used.", + // Long description. + "This utility takes an image or an array of images and loads them to a" + " matrix. You can optionally specify the height " + + PRINT_PARAM_STRING("height") + " width " + PRINT_PARAM_STRING("width") + + " and channel " + PRINT_PARAM_STRING("channels") + " of the images that" + " needs to be loaded; otherwise, these parameters will be automatically" + " detected from the image." + "\n" + "There are other options too, that can be specified such as " + + PRINT_PARAM_STRING("quality") + + ".\n\n" + + "You can also provide a dataset and save them as images using " + + PRINT_PARAM_STRING("dataset") + " and " + PRINT_PARAM_STRING("save") + + " as an parameter. An example to load an image : " + + "\n\n" + + PRINT_CALL("image_converter", "input", "X", "height", 256, "width", 256, + "channels", 3, "output", "Y") + + "\n\n" + + " An example to save an image is :" + + "\n\n" + + PRINT_CALL("image_converter", "input", "X", "height", 256, "width", 256, + "channels", 3, "dataset", "Y", "save", true), + SEE_ALSO("@preprocess_binarize", "#preprocess_binarize"), + SEE_ALSO("@preprocess_describe", "#preprocess_describe"), + SEE_ALSO("@preprocess_imputer", "#preprocess_imputer")); + +// DEFINE PARAM +PARAM_VECTOR_IN_REQ(string, "input", "Image filenames which have to " + "be loaded/saved.", "i"); + +PARAM_INT_IN("width", "Width of the image.", "w", 0); +PARAM_INT_IN("channels", "Number of channels in the image.", "c", 0); + +PARAM_MATRIX_OUT("output", "Matrix to save images data to, Only" + "needed if you are specifying 'save' option.", "o"); + +PARAM_INT_IN("quality", "Compression of the image if saved as jpg (0-100).", + "q", 90); + +PARAM_INT_IN("height", "Height of the images.", "H", 0); +PARAM_FLAG("save", "Save a dataset as images.", "s"); +PARAM_MATRIX_IN("dataset", "Input matrix to save as images.", "I"); + +static void mlpackMain() +{ + Timer::Start("Loading/Saving Image"); + // Parse command line options. + const vector fileNames = CLI::GetParam >("input"); + arma::mat out; + + if (!CLI::HasParam("save")) + { + ReportIgnoredParam("width", "Width of image is determined from file."); + ReportIgnoredParam("height", "Height of image is determined from file."); + ReportIgnoredParam("channels", "Number of channels determined from file."); + data::ImageInfo info; + Load(fileNames, out, info, true); + if (CLI::HasParam("output")) + CLI::GetParam("output") = std::move(out); + } + else + { + RequireNoneOrAllPassed({ "save", "width", "height", "channels", "dataset" } + , true, "Image size information is needed when 'save' is specified!"); + // Positive value for width. + RequireParamValue("width", [](int x) { return x >= 0;}, true, + "width must be positive"); + // Positive value for height. + RequireParamValue("height", [](int x) { return x >= 0;}, true, + "height must be positive"); + // Positive value for channel. + RequireParamValue("channels", [](int x) { return x >= 0;}, true, + "channels must be positive"); + // Positive value for quality. + RequireParamValue("quality", [](int x) { return x >= 0;}, true, + "quality must be positive"); + + const size_t height = CLI::GetParam("height"); + const size_t width = CLI::GetParam("width"); + const size_t channels = CLI::GetParam("channels"); + const size_t quality = CLI::GetParam("quality"); + data::ImageInfo info(width, height, channels, quality); + Save(fileNames, CLI::GetParam("dataset"), info, true); + } +} + diff --git a/src/mlpack/methods/preprocess/preprocess_scale_main.cpp b/src/mlpack/methods/preprocess/preprocess_scale_main.cpp index 44d26b3a4b..f4694655e0 100644 --- a/src/mlpack/methods/preprocess/preprocess_scale_main.cpp +++ b/src/mlpack/methods/preprocess/preprocess_scale_main.cpp @@ -10,9 +10,10 @@ * http://www.opensource.org/licenses/BSD-3-Clause for more information. */ #include +#include #include #include -#include +#include #include #include #include diff --git a/src/mlpack/methods/reinforcement_learning/q_networks/CMakeLists.txt b/src/mlpack/methods/reinforcement_learning/q_networks/CMakeLists.txt new file mode 100644 index 0000000000..3a8a010bfe --- /dev/null +++ b/src/mlpack/methods/reinforcement_learning/q_networks/CMakeLists.txt @@ -0,0 +1,14 @@ +# Define the files we need to compile +# Anything not in this list will not be compiled into mlpack. +set(SOURCES + simple_dqn.hpp +) + +# Add directory name to sources. +set(DIR_SRCS) +foreach(file ${SOURCES}) + set(DIR_SRCS ${DIR_SRCS} ${CMAKE_CURRENT_SOURCE_DIR}/${file}) +endforeach() +# Append sources (with directory name) to list of all mlpack sources (used at +# the parent scope). +set(MLPACK_SRCS ${MLPACK_SRCS} ${DIR_SRCS} PARENT_SCOPE) \ No newline at end of file diff --git a/src/mlpack/methods/reinforcement_learning/q_networks/simple_dqn.hpp b/src/mlpack/methods/reinforcement_learning/q_networks/simple_dqn.hpp new file mode 100644 index 0000000000..30d13180a5 --- /dev/null +++ b/src/mlpack/methods/reinforcement_learning/q_networks/simple_dqn.hpp @@ -0,0 +1,127 @@ +/** + * @file simple_dqn.hpp + * @author Nishant Kumar + * + * This file contains the implementation of the simple deep q network. + * + * mlpack is free software; you may redistribute it and/or modify it under the + * terms of the 3-clause BSD license. You should have received a copy of the + * 3-clause BSD license along with mlpack. If not, see + * http://www.opensource.org/licenses/BSD-3-Clause for more information. + */ +#ifndef MLPACK_METHODS_RL_SIMPLE_DQN_HPP +#define MLPACK_METHODS_RL_SIMPLE_DQN_HPP + +#include +#include +#include +#include +#include + +namespace mlpack { +namespace rl { + +using namespace mlpack::ann; + +/** + * @tparam NetworkType The type of network used for simple dqn. + */ +template , + GaussianInitialization>> +class SimpleDQN +{ + public: + /** + * Default constructor. + */ + SimpleDQN() : network() + { /* Nothing to do here. */ } + + /** + * Construct an instance of SimpleDQN class. + * + * @param inputDim Number of inputs. + * @param h1 Number of neurons in hiddenlayer-1. + * @param h2 Number of neurons in hiddenlayer-2. + * @param outputDim Number of neurons in output layer. + */ + SimpleDQN(const int inputDim, + const int h1, + const int h2, + const int outputDim) : network() + { + FFN, GaussianInitialization> model(MeanSquaredError<>(), + GaussianInitialization(0, 0.001)); + model.Add>(inputDim, h1); + model.Add>(); + model.Add>(h1, h2); + model.Add>(); + model.Add>(h2, outputDim); + network = model; + } + + SimpleDQN(NetworkType network) : network(std::move(network)) + { /* Nothing to do here. */ } + + /** + * Predict the responses to a given set of predictors. The responses will + * reflect the output of the given output layer as returned by the + * output layer function. + * + * If you want to pass in a parameter and discard the original parameter + * object, be sure to use std::move to avoid unnecessary copy. + * + * @param state Input state. + * @param actionValue Matrix to put output action values of states input. + */ + void Predict(const arma::mat state, arma::mat& actionValue) + { + network.Predict(state, actionValue); + } + + /** + * Perform the forward pass of the states in real batch mode. + * + * @param state The input state. + * @param target The predicted target. + */ + void Forward(const arma::mat state, arma::mat& target) + { + network.Forward(state, target); + } + + /** + * Resets the parameters of the network. + */ + void ResetParameters() + { + network.ResetParameters(); + } + + //! Return the Parameters. + const arma::mat& Parameters() const { return network.Parameters(); } + //! Modify the Parameters. + arma::mat& Parameters() { return network.Parameters(); } + + /** + * Perform the backward pass of the state in real batch mode. + * + * @param state The input state. + * @param target The training target. + * @return gradient The gradient. + */ + void Backward(const arma::mat state, arma::mat& target, +arma::mat& gradient) + { + network.Backward(state, target, gradient); + } + + private: + //! Locally-stored network. + NetworkType network; +}; + +} // namespace rl +} // namespace mlpack + +#endif diff --git a/src/mlpack/methods/softmax_regression/softmax_regression_impl.hpp b/src/mlpack/methods/softmax_regression/softmax_regression_impl.hpp index 30fc341cf1..a38831d657 100644 --- a/src/mlpack/methods/softmax_regression/softmax_regression_impl.hpp +++ b/src/mlpack/methods/softmax_regression/softmax_regression_impl.hpp @@ -65,7 +65,7 @@ double SoftmaxRegression::Train(const arma::mat& data, { SoftmaxRegressionFunction regressor(data, labels, numClasses, lambda, fitIntercept); - if (parameters.is_empty()) + if (parameters.n_elem != regressor.GetInitialPoint().n_elem) parameters = regressor.GetInitialPoint(); // Train the model. @@ -88,7 +88,7 @@ double SoftmaxRegression::Train(const arma::mat& data, { SoftmaxRegressionFunction regressor(data, labels, numClasses, lambda, fitIntercept); - if (parameters.is_empty()) + if (parameters.n_elem != regressor.GetInitialPoint().n_elem) parameters = regressor.GetInitialPoint(); // Train the model. diff --git a/src/mlpack/tests/CMakeLists.txt b/src/mlpack/tests/CMakeLists.txt index 32bc77b5d3..77e4fcc2e4 100644 --- a/src/mlpack/tests/CMakeLists.txt +++ b/src/mlpack/tests/CMakeLists.txt @@ -137,6 +137,7 @@ add_executable(mlpack_test main_tests/kfn_test.cpp main_tests/knn_test.cpp main_tests/linear_regression_test.cpp + main_tests/image_converter_test.cpp main_tests/linear_svm_test.cpp main_tests/logistic_regression_test.cpp main_tests/local_coordinate_coding_test.cpp diff --git a/src/mlpack/tests/image_load_test.cpp b/src/mlpack/tests/image_load_test.cpp index e7c90acb1a..b5172dbeb0 100644 --- a/src/mlpack/tests/image_load_test.cpp +++ b/src/mlpack/tests/image_load_test.cpp @@ -12,6 +12,8 @@ #include #include +#include "test_tools.hpp" +#include "serialization.hpp" using namespace mlpack; using namespace mlpack::data; @@ -43,7 +45,11 @@ BOOST_AUTO_TEST_CASE(LoadImageAPITest) arma::Mat matrix; data::ImageInfo info; BOOST_REQUIRE(data::Load("test_image.png", matrix, info, false) == true); - BOOST_REQUIRE_EQUAL(matrix.n_rows, 50 * 50 * 3); // width * height * channels. + // width * height * channels. + BOOST_REQUIRE_EQUAL(matrix.n_rows, 50 * 50 * 3); + BOOST_REQUIRE_EQUAL(info.Height(), 50); + BOOST_REQUIRE_EQUAL(info.Width(), 50); + BOOST_REQUIRE_EQUAL(info.Channels(), 3); BOOST_REQUIRE_EQUAL(matrix.n_cols, 1); } @@ -64,8 +70,91 @@ BOOST_AUTO_TEST_CASE(SaveImageAPITest) BOOST_REQUIRE_EQUAL(im1.n_cols, im2.n_cols); BOOST_REQUIRE_EQUAL(im1.n_rows, im2.n_rows); - for (size_t i = 10; i < im1.n_elem; ++i) + for (size_t i = 0; i < im1.n_elem; ++i) BOOST_REQUIRE_EQUAL(im1[i], im2[i]); + remove("APITest.bmp"); +} + +/** + * Test if an image with a wrong dimesion throws an expected + * exception while saving. + */ +BOOST_AUTO_TEST_CASE(SaveImageWrongInfo) +{ + data::ImageInfo info(5, 5, 3, 90); + + arma::Mat im1; + size_t dimension = info.Width() * info.Height() * info.Channels(); + im1 = arma::randi>(24 * 25 * 7, 1); + Log::Fatal.ignoreInput = true; + BOOST_REQUIRE_THROW(data::Save("APITest.bmp", im1, info, false), + std::runtime_error); + Log::Fatal.ignoreInput = false; +} + +/** + * Test that the image is loaded correctly into the matrix using the API + * for vectors. + */ +BOOST_AUTO_TEST_CASE(LoadVectorImageAPITest) +{ + arma::Mat matrix; + data::ImageInfo info; + std::vector files = {"test_image.png", "test_image.png"}; + BOOST_REQUIRE(data::Load(files, matrix, info, false) == true); + // width * height * channels. + BOOST_REQUIRE_EQUAL(matrix.n_rows, 50 * 50 * 3); + BOOST_REQUIRE_EQUAL(info.Height(), 50); + BOOST_REQUIRE_EQUAL(info.Width(), 50); + BOOST_REQUIRE_EQUAL(info.Channels(), 3); + BOOST_REQUIRE_EQUAL(matrix.n_cols, 2); +} + +/** + * Test if the image is saved correctly using API for arma mat. + */ +BOOST_AUTO_TEST_CASE(SaveImageMatAPITest) +{ + data::ImageInfo info(5, 5, 3); + + arma::Mat im1; + size_t dimension = info.Width() * info.Height() * info.Channels(); + im1 = arma::randi>(dimension, 1); + arma::mat input = arma::conv_to::from(im1); + BOOST_REQUIRE(Save("APITest.bmp", input, info, false) == true); + + arma::mat output; + BOOST_REQUIRE(Load("APITest.bmp", output, info, false) == true); + + BOOST_REQUIRE_EQUAL(input.n_cols, output.n_cols); + BOOST_REQUIRE_EQUAL(input.n_rows, output.n_rows); + for (size_t i = 0; i < input.n_elem; ++i) + BOOST_REQUIRE_CLOSE(input[i], output[i], 1e-5); + remove("APITest.bmp"); +} + +/** + * Serialization test for the ImageInfo class. + */ +BOOST_AUTO_TEST_CASE(ImageInfoSerialization) +{ + data::ImageInfo info(5, 5, 3, 90); + data::ImageInfo xmlInfo, textInfo, binaryInfo; + + SerializeObjectAll(info, xmlInfo, textInfo, binaryInfo); + + BOOST_REQUIRE_EQUAL(info.Width(), xmlInfo.Width()); + BOOST_REQUIRE_EQUAL(info.Height(), xmlInfo.Height()); + BOOST_REQUIRE_EQUAL(info.Channels(), xmlInfo.Channels()); + BOOST_REQUIRE_EQUAL(info.Quality(), xmlInfo.Quality()); + BOOST_REQUIRE_EQUAL(info.Width(), textInfo.Width()); + BOOST_REQUIRE_EQUAL(info.Height(), textInfo.Height()); + BOOST_REQUIRE_EQUAL(info.Channels(), textInfo.Channels()); + BOOST_REQUIRE_EQUAL(info.Quality(), textInfo.Quality()); + BOOST_REQUIRE_EQUAL(info.Width(), binaryInfo.Width()); + BOOST_REQUIRE_EQUAL(info.Height(), binaryInfo.Height()); + BOOST_REQUIRE_EQUAL(info.Channels(), binaryInfo.Channels()); + BOOST_REQUIRE_EQUAL(info.Quality(), binaryInfo.Quality()); } BOOST_AUTO_TEST_SUITE_END(); diff --git a/src/mlpack/tests/logistic_regression_test.cpp b/src/mlpack/tests/logistic_regression_test.cpp index d603004a28..f3bbe6b32d 100644 --- a/src/mlpack/tests/logistic_regression_test.cpp +++ b/src/mlpack/tests/logistic_regression_test.cpp @@ -1002,4 +1002,25 @@ BOOST_AUTO_TEST_CASE(LogisticRegressionTrainReturnObjective) BOOST_REQUIRE_EQUAL(std::isfinite(objVal), true); } +/** + * Test that construction *then* training works fine. Thanks @Trento89 for the + * test case (see #2358). + */ +BOOST_AUTO_TEST_CASE(ConstructionThenTraining) +{ + arma::mat myMatrix; + + // Four points, three dimensions. + myMatrix << 0.555950 << 0.274690 << 0.540605 << 0.798938 << arma::endr + << 0.948014 << 0.973234 << 0.216504 << 0.883152 << arma::endr + << 0.023787 << 0.675382 << 0.231751 << 0.450332 << arma::endr; + + arma::Row myTargets("1 0 1 0"); + + regression::LogisticRegression<> lr; + + // Make sure that training doesn't crash with invalid parameter sizes. + BOOST_REQUIRE_NO_THROW(lr.Train(myMatrix, myTargets)); +} + BOOST_AUTO_TEST_SUITE_END(); diff --git a/src/mlpack/tests/loss_functions_test.cpp b/src/mlpack/tests/loss_functions_test.cpp index e361c398d0..145ef3b418 100644 --- a/src/mlpack/tests/loss_functions_test.cpp +++ b/src/mlpack/tests/loss_functions_test.cpp @@ -22,11 +22,13 @@ #include #include #include +#include #include #include #include #include #include +#include #include #include @@ -578,4 +580,135 @@ BOOST_AUTO_TEST_CASE(HingeEmbeddingLossTest) BOOST_REQUIRE_EQUAL(output.n_rows, input.n_rows); BOOST_REQUIRE_EQUAL(output.n_cols, input.n_cols); } + +/** + * Simple test for the Cosine Embedding loss function. + */ +BOOST_AUTO_TEST_CASE(CosineEmbeddingLossTest) +{ + arma::mat input1, input2, y, output; + double loss; + CosineEmbeddingLoss<> module; + + // Test the Forward function. Loss should be 0 if input1 = input2 and y = 1. + input1 = arma::mat(1, 10); + input2 = arma::mat(1, 10); + input1.ones(); + input2.ones(); + y = arma::mat(1, 1); + y.ones(); + loss = module.Forward(input1, input1); + BOOST_REQUIRE_SMALL(loss, 1e-6); + + // Test the Backward function. + module.Backward(input1, input1, output); + BOOST_REQUIRE_SMALL(arma::accu(output), 1e-6); + + // Check for dissimilarity. + module.Similarity() = false; + loss = module.Forward(input1, input1); + BOOST_REQUIRE_CLOSE(loss, 1.0, 1e-4); + + // Test the Backward function. + module.Backward(input1, input1, output); + BOOST_REQUIRE_SMALL(arma::accu(output), 1e-6); + + input1 = arma::mat(3, 2); + input2 = arma::mat(3, 2); + input1.fill(1); + input1(4) = 2; + input2.fill(1); + input2(0) = 2; + input2(1) = 2; + input2(2) = 2; + loss = module.Forward(input1, input2); + // Calculated using torch.nn.CosineEmbeddingLoss(). + BOOST_REQUIRE_CLOSE(loss, 2.897367, 1e-3); + + // Test the Backward function. + module.Backward(input1, input2, output); + BOOST_REQUIRE_CLOSE(arma::accu(output), 0.06324556, 1e-3); + + // Check for correctness for cube. + CosineEmbeddingLoss<> module2(0.5, true); + + arma::cube input3(3, 2, 2); + arma::cube input4(3, 2, 2); + input3.fill(1); + input4.fill(1); + input3(0) = 2; + input3(1) = 2; + input3(4) = 2; + input3(6) = 2; + input3(8) = 2; + input3(10) = 2; + input4(2) = 2; + input4(9) = 2; + input4(11) = 2; + loss = module2.Forward(input3, input4); + // Calculated using torch.nn.CosineEmbeddingLoss(). + BOOST_REQUIRE_CLOSE(loss, 0.55395, 1e-3); + + // Test the Backward function. + module2.Backward(input3, input4, output); + BOOST_REQUIRE_CLOSE(arma::accu(output), -0.36649111, 1e-3); + + // Check Output for mean type of reduction. + CosineEmbeddingLoss<> module3(0.0, true, true); + loss = module3.Forward(input3, input4); + BOOST_REQUIRE_CLOSE(loss, 0.092325, 1e-3); + + // Check correctness for cube. + module3.Similarity() = false; + loss = module3.Forward(input3, input4); + BOOST_REQUIRE_CLOSE(loss, 0.90767498236, 1e-3); + + // Test the Backward function. + module3.Backward(input3, input4, output); + BOOST_REQUIRE_CLOSE(arma::accu(output), 0.36649111, 1e-4); +} + +/* + * Simple test for the Margin Ranking Loss function. + */ +BOOST_AUTO_TEST_CASE(MarginRankingLossTest) +{ + arma::mat input, input1, input2, target, output; + MarginRankingLoss<> module; + + // Test the Forward function on a user generator input and compare it against + // the manually calculated result. + input1 = arma::mat("1 2 5 7 -1 -3"); + input2 = arma::mat("-1 3 -4 11 3 -3"); + input = arma::join_cols(input1, input2); + target = arma::mat("1 -1 -1 1 -1 1"); + double error = module.Forward(input, target); + // Computed using torch.nn.functional.margin_ranking_loss() + BOOST_REQUIRE_CLOSE(error, 2.66667, 1e-3); + + // Test the Backward function. + module.Backward(input, target, output); + + CheckMatrices(output, arma::mat("-0.000000 0.166667 -1.500000 0.666667 " + "0.000000 -0.000000"), 1e-3); + BOOST_REQUIRE_EQUAL(output.n_rows, target.n_rows); + BOOST_REQUIRE_EQUAL(output.n_cols, target.n_cols); + + // Test the error function on another input. + input1 = arma::mat("0.4287 -1.6208 -1.5006 -0.4473 1.5208 -4.5184 9.3574 " + "-4.8090 4.3455 5.2070"); + input2 = arma::mat("-4.5288 -9.2766 -0.5882 -5.6643 -6.0175 8.8506 3.4759 " + "-9.4886 2.2755 8.4951"); + input = arma::join_cols(input1, input2); + target = arma::mat("1 1 -1 1 -1 1 1 1 -1 1"); + error = module.Forward(input, target); + BOOST_REQUIRE_CLOSE(error, 3.03530, 1e-3); + + // Test the Backward function on the second input. + module.Backward(input, target, output); + + CheckMatrices(output, arma::mat("0.000000 0.000000 0.091240 0.000000 " + "-0.753830 1.336900 0.000000 0.000000 -0.207000 0.328810"), 1e-6); +} + BOOST_AUTO_TEST_SUITE_END(); diff --git a/src/mlpack/tests/main_tests/image_converter_test.cpp b/src/mlpack/tests/main_tests/image_converter_test.cpp new file mode 100644 index 0000000000..cd77cbaf66 --- /dev/null +++ b/src/mlpack/tests/main_tests/image_converter_test.cpp @@ -0,0 +1,185 @@ +/** + * @file image_converter_test.cpp + * @author Jeffin Sam + * + * Test mlpackMain() of load_save_image_main.cpp. + * + * mlpack is free software; you may redistribute it and/or modify it under the + * terms of the 3-clause BSD license. You should have received a copy of the + * 3-clause BSD license along with mlpack. If not, see + * http://www.opensource.org/licenses/BSD-3-Clause for more information. + */ +#define BINDING_TYPE BINDING_TYPE_TEST + +#include +static const std::string testName = "ImageConverter"; + +#include +#include + +#include "test_helper.hpp" +#include +#include "../test_tools.hpp" + +using namespace mlpack; + +struct ImageConverterTestFixture +{ + public: + ImageConverterTestFixture() + { + // Cache in the options for this program. + CLI::RestoreSettings(testName); + } + + ~ImageConverterTestFixture() + { + // Clear the settings. + remove("test_image777.png"); + remove("test_image999.png"); + bindings::tests::CleanMemory(); + CLI::ClearSettings(); + } +}; + +BOOST_FIXTURE_TEST_SUITE(ImageConverterMainTest, + ImageConverterTestFixture); + +BOOST_AUTO_TEST_CASE(LoadImageTest) +{ + SetInputParam>("input", {"test_image.png", "test_image.png"}); + + mlpackMain(); + arma::mat output = CLI::GetParam("output"); + // width * height * channels. + BOOST_REQUIRE_EQUAL(output.n_rows, 50 * 50 * 3); + BOOST_REQUIRE_EQUAL(output.n_cols, 2); +} + +BOOST_AUTO_TEST_CASE(SaveImageTest) +{ + arma::mat testimage = arma::conv_to::from( + arma::randi>((5 * 5 * 3), 2)); + SetInputParam>("input", {"test_image777.png", + "test_image999.png"}); + SetInputParam("height", 5); + SetInputParam("width", 5); + SetInputParam("channels", 3); + SetInputParam("save", true); + SetInputParam("dataset", testimage); + mlpackMain(); + + CLI::ClearSettings(); + CLI::RestoreSettings(testName); + + SetInputParam>("input", {"test_image777.png", + "test_image999.png"}); + SetInputParam("height", 5); + SetInputParam("width", 5); + SetInputParam("channels", 3); + + mlpackMain(); + arma::mat output = CLI::GetParam("output"); + BOOST_REQUIRE_EQUAL(output.n_rows, 5 * 5 * 3); + BOOST_REQUIRE_EQUAL(output.n_cols, 2); + for (size_t i = 0; i < output.n_elem; ++i) + BOOST_REQUIRE_CLOSE(testimage[i], output[i], 1e-5); +} + +/** + * Check whether binding throws error if height, width or channel are not + * specified. + */ +BOOST_AUTO_TEST_CASE(IncompleteTest) +{ + arma::mat testimage = arma::conv_to::from( + arma::randi>((5 * 5 * 3), 2)); + SetInputParam>("input", {"test_image777.png", + "test_image999.png"}); + SetInputParam("save", true); + SetInputParam("height", 50); + SetInputParam("width", 50); + SetInputParam("dataset", testimage); + + Log::Fatal.ignoreInput = true; + BOOST_REQUIRE_THROW(mlpackMain(), std::runtime_error); + Log::Fatal.ignoreInput = false; +} + +/** + * Check for invalid height values. + */ +BOOST_AUTO_TEST_CASE(InvalidInputTest) +{ + arma::mat testimage = arma::conv_to::from( + arma::randi>((5 * 5 * 3), 2)); + SetInputParam>("input", {"test_image777.png", + "test_image999.png"}); + SetInputParam("save", true); + SetInputParam("dataset", testimage); + + SetInputParam("height", -50); + SetInputParam("width", 50); + SetInputParam("channels", 3); + + Log::Fatal.ignoreInput = true; + BOOST_REQUIRE_THROW(mlpackMain(), std::runtime_error); + Log::Fatal.ignoreInput = false; +} + +/** + * Check for invalid width values. + */ +BOOST_AUTO_TEST_CASE(InvalidWidthTest) +{ + arma::mat testimage = arma::conv_to::from( + arma::randi>((5 * 5 * 3), 2)); + SetInputParam>("input", {"test_image777.png", + "test_image999.png"}); + SetInputParam("save", true); + SetInputParam("dataset", testimage); + SetInputParam("height", 50); + SetInputParam("width", -50); + SetInputParam("channels", 3); + + Log::Fatal.ignoreInput = true; + BOOST_REQUIRE_THROW(mlpackMain(), std::runtime_error); + Log::Fatal.ignoreInput = false; +} + +/** + * Check for invalid channel values. + */ +BOOST_AUTO_TEST_CASE(InvalidChannelTest) +{ + arma::mat testimage = arma::conv_to::from( + arma::randi>((5 * 5 * 3), 2)); + SetInputParam>("input", {"test_image777.png", + "test_image999.png"}); + SetInputParam("save", true); + SetInputParam("dataset", testimage); + SetInputParam("height", 50); + SetInputParam("width", 50); + SetInputParam("channels", -1); + + Log::Fatal.ignoreInput = true; + BOOST_REQUIRE_THROW(mlpackMain(), std::runtime_error); + Log::Fatal.ignoreInput = false; +} + +/** + * Check for invalid input values. + */ +BOOST_AUTO_TEST_CASE(EmptyInputTest) +{ + SetInputParam>("input", {}); + SetInputParam("height", 50); + SetInputParam("width", 50); + SetInputParam("channels", 50); + + Log::Fatal.ignoreInput = true; + BOOST_REQUIRE_THROW(mlpackMain(), std::runtime_error); + Log::Fatal.ignoreInput = false; +} + +BOOST_AUTO_TEST_SUITE_END(); diff --git a/src/mlpack/tests/q_learning_test.cpp b/src/mlpack/tests/q_learning_test.cpp index 95a26f40ad..67a62d651d 100644 --- a/src/mlpack/tests/q_learning_test.cpp +++ b/src/mlpack/tests/q_learning_test.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -41,13 +42,7 @@ BOOST_AUTO_TEST_SUITE(QLearningTest); BOOST_AUTO_TEST_CASE(CartPoleWithDQN) { // Set up the network. - FFN, GaussianInitialization> model(MeanSquaredError<>(), - GaussianInitialization(0, 0.001)); - model.Add>(4, 128); - model.Add>(); - model.Add>(128, 128); - model.Add>(); - model.Add>(128, 2); + SimpleDQN<> model(4, 128, 128, 2); // Set up the policy and replay method. GreedyPolicy policy(1.0, 1000, 0.1, 0.99); @@ -107,13 +102,7 @@ BOOST_AUTO_TEST_CASE(CartPoleWithDQN) BOOST_AUTO_TEST_CASE(CartPoleWithDQNPrioritizedReplay) { // Set up the network. - FFN, GaussianInitialization> model(MeanSquaredError<>(), - GaussianInitialization(0, 0.001)); - model.Add>(4, 128); - model.Add>(); - model.Add>(128, 128); - model.Add>(); - model.Add>(128, 2); + SimpleDQN<> model(4, 128, 128, 2); // Set up the policy and replay method. GreedyPolicy policy(1.0, 1000, 0.1); @@ -182,13 +171,7 @@ BOOST_AUTO_TEST_CASE(CartPoleWithDoubleDQN) for (size_t trial = 0; trial < 4; ++trial) { // Set up the network. - FFN, GaussianInitialization> model(MeanSquaredError<>(), - GaussianInitialization(0, 0.001)); - model.Add>(4, 20); - model.Add>(); - model.Add>(20, 20); - model.Add>(); - model.Add>(20, 2); + SimpleDQN<> model(4, 20, 20, 2); // Set up the policy and replay method. GreedyPolicy policy(1.0, 1000, 0.1, 0.99); @@ -251,13 +234,7 @@ BOOST_AUTO_TEST_CASE(AcrobotWithDQN) for (size_t trial = 0; trial < 3; ++trial) { // Set up the network. - FFN, GaussianInitialization> model(MeanSquaredError<>(), - GaussianInitialization(0, 0.001)); - model.Add>(4, 64); - model.Add>(); - model.Add>(64, 32); - model.Add>(); - model.Add>(32, 3); + SimpleDQN<> model(4, 64, 32, 3); // Set up the policy and replay method. GreedyPolicy policy(1.0, 1000, 0.1, 0.99); @@ -328,13 +305,7 @@ BOOST_AUTO_TEST_CASE(MountainCarWithDQN) for (size_t trial = 0; trial < 3; trial++) { // Set up the network. - FFN, GaussianInitialization> model(MeanSquaredError<>(), - GaussianInitialization(0, 0.001)); - model.Add>(2, 64); - model.Add>(); - model.Add>(64, 32); - model.Add>(); - model.Add>(32, 3); + SimpleDQN<> model(2, 64, 32, 3); // Set up the policy and replay method. GreedyPolicy policy(1.0, 1000, 0.1, 0.99); @@ -404,7 +375,8 @@ BOOST_AUTO_TEST_CASE(DoublePoleCartWithDQN) bool success = false; for (size_t trial = 0; trial < 4; trial++) { - // Set up the network. + // Set up the network. Note that we use a custom model here, and + // pass it directly into the agent, without using SimpleDQN. FFN, GaussianInitialization> model(MeanSquaredError<>(), GaussianInitialization(0, 0.001)); model.Add>(6, 256); diff --git a/src/mlpack/tests/reward_clipping_test.cpp b/src/mlpack/tests/reward_clipping_test.cpp index 335b4b8200..e4b0c2b121 100644 --- a/src/mlpack/tests/reward_clipping_test.cpp +++ b/src/mlpack/tests/reward_clipping_test.cpp @@ -13,6 +13,7 @@ #include #include +#include #include #include #include @@ -63,13 +64,7 @@ BOOST_AUTO_TEST_CASE(RewardClippedAcrobotWithDQN) for (size_t trial = 0; trial < 3; ++trial) { // Set up the network. - FFN, GaussianInitialization> model(MeanSquaredError<>(), - GaussianInitialization(0, 0.001)); - model.Add>(4, 64); - model.Add>(); - model.Add>(64, 32); - model.Add>(); - model.Add>(32, 3); + SimpleDQN<> model(4, 64, 32, 3); // Set up the policy and replay method. GreedyPolicy> policy(1.0, 1000, 0.1, 0.99); diff --git a/src/mlpack/tests/string_encoding_test.cpp b/src/mlpack/tests/string_encoding_test.cpp index 9fb4e188dd..31a0f22713 100644 --- a/src/mlpack/tests/string_encoding_test.cpp +++ b/src/mlpack/tests/string_encoding_test.cpp @@ -16,6 +16,8 @@ #include #include #include +#include +#include #include #include #include "test_tools.hpp" @@ -54,6 +56,25 @@ static vector stringEncodingUtf8Input = { "\xE2\x93\x82\xE2\x93\x81\xE2\x93\x85\xE2\x92\xB6\xE2\x92\xB8\xE2\x93\x80" }; +/** + * Check the values of two 2D vectors. + */ +template +void CheckVectors(const vector>& a, + const vector>& b, + const ValueType tolerance = 1e-5) +{ + BOOST_REQUIRE_EQUAL(a.size(), b.size()); + + for (size_t i = 0; i < a.size(); i++) + { + BOOST_REQUIRE_EQUAL(a[i].size(), b[i].size()); + + for (size_t j = 0; j < a[i].size(); j++) + BOOST_REQUIRE_CLOSE(a[i][j], b[i][j], tolerance); + } +} + /** * Test the dictionary encoding algorithm. */ @@ -88,7 +109,7 @@ BOOST_AUTO_TEST_CASE(DictionaryEncodingTest) 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } }; - CheckMatrices(output, expected); + CheckMatrices(output, expected.t()); } /** @@ -122,7 +143,7 @@ BOOST_AUTO_TEST_CASE(UnicodeDictionaryEncodingTest) { 5, 2, 3, 5, 4 } }; - CheckMatrices(output, expected); + CheckMatrices(output, expected.t()); } /** @@ -222,8 +243,8 @@ BOOST_AUTO_TEST_CASE(SplitByAnyOfTokenizerUnicodeTest) } /** -* Test the CharExtract tokenizer. -*/ + * Test the CharExtract tokenizer. + */ BOOST_AUTO_TEST_CASE(DictionaryEncodingIndividualCharactersTest) { vector input = { @@ -242,7 +263,7 @@ BOOST_AUTO_TEST_CASE(DictionaryEncodingIndividualCharactersTest) { 2, 4, 3, 2, 4, 3, 5 }, { 1, 2, 4, 0, 0, 0, 0 } }; - CheckMatrices(output, target); + CheckMatrices(output, target.t()); } /** @@ -520,5 +541,889 @@ BOOST_AUTO_TEST_CASE(CharExtractDictionaryEncodingSerialization) CheckMatrices(output, xmlOutput, textOutput, binaryOutput); } +/** + * Test the Bag of Words encoding algorithm. + */ +BOOST_AUTO_TEST_CASE(BagOfWordsEncodingTest) +{ + using DictionaryType = StringEncodingDictionary; + + arma::mat output; + BagOfWordsEncoding encoder; + SplitByAnyOf tokenizer(" ,."); + + encoder.Encode(stringEncodingInput, output, tokenizer); + + const DictionaryType& dictionary = encoder.Dictionary(); + + // Checking that each token has a unique label. + std::unordered_map keysCount; + + for (auto& keyValue : dictionary.Mapping()) + { + keysCount[keyValue.second]++; + + BOOST_REQUIRE_EQUAL(keysCount[keyValue.second], 1); + } + +/* The expected values were obtained by the following Python script: + + from sklearn.feature_extraction.text import CountVectorizer + from collections import OrderedDict + import re + + string_encoding_input = [ + "mlpack is an intuitive, fast, and flexible C++ machine learning library " + "with bindings to other languages. ", + "It is meant to be a machine learning analog to LAPACK, and aims to " + "implement a wide array of machine learning methods and functions " + "as a \"swiss army knife\" for machine learning researchers.", + "In addition to its powerful C++ interface, mlpack also provides " + "command-line programs and Python bindings." + ] + + dictionary = OrderedDict() + + count = 0 + for line in string_encoding_input: + for word in re.split(' |,|\.', line): + if word and (not (word in dictionary)): + dictionary[word] = count + count += 1 + + def tokenizer(line): + return re.split(' |,|\.', line) + + vectorizer = CountVectorizer(strip_accents=False, lowercase=False, + preprocessor=None, tokenizer=tokenizer, stop_words=None, + vocabulary=dictionary, binary=False) + + X = vectorizer.fit_transform(string_encoding_input) + + for row in X.toarray(): + print("{ " + ", ".join(map(str, row)) + " },") +*/ + + arma::mat expected = { + { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 1, 0, 0, 0, 2, 0, 0, 3, 3, 0, 0, 0, 3, 0, 0, 1, 1, 1, 3, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 } + }; + + CheckMatrices(output, expected.t()); +} + +/** + * Test the Bag of Words encoding algorithm. The output is saved into a vector. + */ +BOOST_AUTO_TEST_CASE(VectorBagOfWordsEncodingTest) +{ + using DictionaryType = StringEncodingDictionary; + + vector> output; + BagOfWordsEncoding encoder( + (BagOfWordsEncodingPolicy())); + SplitByAnyOf tokenizer(" ,."); + + encoder.Encode(stringEncodingInput, output, tokenizer); + + const DictionaryType& dictionary = encoder.Dictionary(); + + // Checking that each token has a unique label. + std::unordered_map keysCount; + + for (auto& keyValue : dictionary.Mapping()) + { + keysCount[keyValue.second]++; + + BOOST_REQUIRE_EQUAL(keysCount[keyValue.second], 1); + } + + /* The expected values were obtained by the same script as in + BagOfWordsEncodingTest. */ + vector> expected = { + { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 1, 0, 0, 0, 2, 0, 0, 3, 3, 0, 0, 0, 3, 0, 0, 1, 1, 1, 3, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 } + }; + + BOOST_REQUIRE(output == expected); +} + +/** + * Test the Bag of Words algorithm for individual characters. + */ +BOOST_AUTO_TEST_CASE(BagOfWordsEncodingIndividualCharactersTest) +{ + vector input = { + "GACCA", + "ABCABCD", + "GAB" + }; + + arma::mat output; + BagOfWordsEncoding encoder; + + encoder.Encode(input, output, CharExtract()); + + arma::mat target = { + { 1, 2, 2, 0, 0 }, + { 0, 2, 2, 2, 1 }, + { 1, 1, 0, 1, 0 } + }; + + CheckMatrices(output, target.t()); +} + +/** + * Test the Bag of Words encoding algorithm in case of individual + * characters encoding. The output type is vector>. + */ +BOOST_AUTO_TEST_CASE(VectorBagOfWordsEncodingIndividualCharactersTest) +{ + std::vector input = { + "GACCA", + "ABCABCD", + "GAB" + }; + + vector> output; + BagOfWordsEncoding encoder; + + encoder.Encode(input, output, CharExtract()); + + vector> expected = { + { 1, 2, 2, 0, 0 }, + { 0, 2, 2, 2, 1 }, + { 1, 1, 0, 1, 0 } + }; + + BOOST_REQUIRE(output == expected); +} + +/** + * Test the Tf-Idf encoding algorithm with the raw count term frequency type + * and the smooth inverse document frequency type. These parameters are + * the default ones. + */ +BOOST_AUTO_TEST_CASE(RawCountSmoothIdfEncodingTest) +{ + using DictionaryType = StringEncodingDictionary; + + arma::mat output; + TfIdfEncoding encoder; + SplitByAnyOf tokenizer(" ,."); + + encoder.Encode(stringEncodingInput, output, tokenizer); + const DictionaryType& dictionary = encoder.Dictionary(); + + // Checking that each token has a unique label. + std::unordered_map keysCount; + + for (auto& keyValue : dictionary.Mapping()) + { + keysCount[keyValue.second]++; + + BOOST_REQUIRE_EQUAL(keysCount[keyValue.second], 1); + } + + /* The expected values were obtained by the following Python script: + + from sklearn.feature_extraction.text import TfidfVectorizer + from collections import OrderedDict + import re + + string_encoding_input = [ + "mlpack is an intuitive, fast, and flexible C++ machine learning library " + "with bindings to other languages. ", + "It is meant to be a machine learning analog to LAPACK, and aims to " + "implement a wide array of machine learning methods and functions " + "as a \"swiss army knife\" for machine learning researchers.", + "In addition to its powerful C++ interface, mlpack also provides " + "command-line programs and Python bindings." + ] + + smooth_idf = True + tf_type = 'raw_count' + + dictionary = OrderedDict() + + count = 0 + for line in string_encoding_input: + for word in re.split(' |,|\.', line): + if word and (not (word in dictionary)): + dictionary[word] = count + count += 1 + + def tokenizer(line): + return re.split(' |,|\.', line) + + if tf_type == 'raw_count': + binary = False + sublinear_tf = False + elif tf_type == 'binary': + binary = True + sublinear_tf = False + elif tf_type == 'sublinear_tf': + binary = False + sublinear_tf = True + + vectorizer = TfidfVectorizer(strip_accents=False, lowercase=False, + preprocessor=None, tokenizer=tokenizer, stop_words=None, + vocabulary=dictionary, binary=binary, norm=None, smooth_idf=smooth_idf, + sublinear_tf=sublinear_tf) + + X = vectorizer.fit_transform(string_encoding_input) + + def format_result(value): + if value == int(value): + return str(int(value)) + else: + return "{0:.8f}".format(value) + + for row in X.toarray(): + print("{ " + ", ".join(map(format_result, row)) + " },") + */ + arma::mat expected = { + { 1.28768207, 1.28768207, 1.69314718, 1.69314718, 1.69314718, 1, 1.69314718, + 1.28768207, 1.28768207, 1.28768207, 1.69314718, 1.69314718, 1.28768207, 1, + 1.69314718, 1.69314718, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 1.28768207, 0, 0, 0, 2, 0, 0, 3.86304622, 3.86304622, 0, 0, 0, 3, 0, + 0, 1.69314718, 1.69314718, 1.69314718, 5.07944154, 1.69314718, 1.69314718, + 1.69314718, 1.69314718, 1.69314718, 1.69314718, 1.69314718, 1.69314718, + 1.69314718, 1.69314718, 1.69314718, 1.69314718, 1.69314718, 1.69314718, + 1.69314718, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 1.28768207, 0, 0, 0, 0, 1, 0, 1.28768207, 0, 0, 0, 0, 1.28768207, 1, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1.69314718, + 1.69314718, 1.69314718, 1.69314718, 1.69314718, 1.69314718, 1.69314718, + 1.69314718, 1.69314718, 1.69314718 } + }; + + CheckMatrices(output, expected.t(), 1e-6); +} + +/** + * Test the Tf-Idf encoding algorithm with the raw count term frequency type + * and the smooth inverse document frequency type. These parameters are + * the default ones. The output type is vector>. + */ +BOOST_AUTO_TEST_CASE(VectorRawCountSmoothIdfEncodingTest) +{ + using DictionaryType = StringEncodingDictionary; + + vector> output; + TfIdfEncoding encoder( + (TfIdfEncodingPolicy())); + SplitByAnyOf tokenizer(" ,."); + + encoder.Encode(stringEncodingInput, output, tokenizer); + + const DictionaryType& dictionary = encoder.Dictionary(); + + // Checking that each token has a unique label. + std::unordered_map keysCount; + + for (auto& keyValue : dictionary.Mapping()) + { + keysCount[keyValue.second]++; + + BOOST_REQUIRE_EQUAL(keysCount[keyValue.second], 1); + } + + /* The expected values were obtained by the same script as in + RawCountSmoothIdfEncodingTest. */ + vector> expected = { + { 1.28768207, 1.28768207, 1.69314718, 1.69314718, 1.69314718, 1, 1.69314718, + 1.28768207, 1.28768207, 1.28768207, 1.69314718, 1.69314718, 1.28768207, 1, + 1.69314718, 1.69314718, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 1.28768207, 0, 0, 0, 2, 0, 0, 3.86304622, 3.86304622, 0, 0, 0, 3, 0, + 0, 1.69314718, 1.69314718, 1.69314718, 5.07944154, 1.69314718, 1.69314718, + 1.69314718, 1.69314718, 1.69314718, 1.69314718, 1.69314718, 1.69314718, + 1.69314718, 1.69314718, 1.69314718, 1.69314718, 1.69314718, 1.69314718, + 1.69314718, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 1.28768207, 0, 0, 0, 0, 1, 0, 1.28768207, 0, 0, 0, 0, 1.28768207, 1, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1.69314718, + 1.69314718, 1.69314718, 1.69314718, 1.69314718, 1.69314718, 1.69314718, + 1.69314718, 1.69314718, 1.69314718 } + }; + CheckVectors(output, expected, 1e-6); +} + +/** + * Test the Tf-Idf encoding algorithm for individual characters with the + * raw count term frequency type and the smooth inverse document frequency type. + * These parameters are the default ones. + */ +BOOST_AUTO_TEST_CASE(RawCountSmoothIdfEncodingIndividualCharactersTest) +{ + vector input = { + "GACCA", + "ABCABCD", + "GAB" + }; + + arma::mat output; + TfIdfEncoding encoder; + + encoder.Encode(input, output, CharExtract()); + + /* The expected values were obtained by the following Python script: + + from sklearn.feature_extraction.text import TfidfVectorizer + from collections import OrderedDict + import re + + input_string = [ + "GACCA", + "ABCABCD", + "GAB" + ] + + smooth_idf = True + tf_type = 'raw_count' + + dictionary = OrderedDict() + + count = 0 + for line in input_string: + for word in list(line): + if word and (not (word in dictionary)): + dictionary[word] = count + count += 1 + + def tokenizer(line): + return list(line) + + if tf_type == 'raw_count': + binary = False + sublinear_tf = False + elif tf_type == 'binary': + binary = True + sublinear_tf = False + elif tf_type == 'sublinear_tf': + binary = False + sublinear_tf = True + + vectorizer = TfidfVectorizer(strip_accents=False, lowercase=False, + preprocessor=None, tokenizer=tokenizer, stop_words=None, + vocabulary=dictionary, binary=binary, norm=None, smooth_idf=smooth_idf, + sublinear_tf=sublinear_tf) + + X = vectorizer.fit_transform(input_string) + + def format_result(value): + if value == int(value): + return str(int(value)) + else: + return "{0:.14f}".format(value) + + for row in X.toarray(): + print("{ " + ", ".join(map(format_result, row)) + " },") + */ + arma::mat target = { + { 1.28768207245178, 2, 2.57536414490356, 0, 0 }, + { 0, 2, 2.57536414490356, 2.57536414490356, 1.69314718055995 }, + { 1.28768207245178, 1, 0, 1.28768207245178, 0 } + }; + + CheckMatrices(output, target.t(), 1e-12); +} + +/** + * Test the Tf-Idf encoding algorithm for individual characters with the + * raw count term frequency type and the smooth inverse document frequency type. + * These parameters are the default ones. The output type is + * vector>. + */ +BOOST_AUTO_TEST_CASE(VectorRawCountSmoothIdfEncodingIndividualCharactersTest) +{ + std::vector input = { + "GACCA", + "ABCABCD", + "GAB" + }; + + vector> output; + TfIdfEncoding encoder; + + encoder.Encode(input, output, CharExtract()); + + /* The expected values were obtained by the same script as in + RawCountSmoothIdfEncodingIndividualCharactersTest. */ + vector> expected = { + { 1.28768207245178, 2, 2.57536414490356, 0, 0 }, + { 0, 2, 2.57536414490356, 2.57536414490356, 1.69314718055995 }, + { 1.28768207245178, 1, 0, 1.28768207245178, 0 } + }; + + CheckVectors(output, expected, 1e-12); +} + +/** + * Test the Tf-Idf encoding algorithm with the raw count term frequency type + * and the non-smooth inverse document frequency type. + */ +BOOST_AUTO_TEST_CASE(TfIdfRawCountEncodingTest) +{ + using DictionaryType = StringEncodingDictionary; + + arma::mat output; + TfIdfEncoding encoder( + TfIdfEncodingPolicy(TfIdfEncodingPolicy::TfTypes::RAW_COUNT, false)); + SplitByAnyOf tokenizer(" ,."); + + encoder.Encode(stringEncodingInput, output, tokenizer); + + const DictionaryType& dictionary = encoder.Dictionary(); + + // Checking that each token has a unique label. + std::unordered_map keysCount; + + for (auto& keyValue : dictionary.Mapping()) + { + keysCount[keyValue.second]++; + + BOOST_REQUIRE_EQUAL(keysCount[keyValue.second], 1); + } + + /* The expected values were obtained by almost the same script as in + RawCountSmoothIdfEncodingTest. The only difference is smooth_idf equals + False. */ + arma::mat expected = { + { 1.40546511, 1.40546511, 2.09861229, 2.09861229, 2.09861229, 1, 2.09861229, + 1.40546511, 1.40546511, 1.40546511, 2.09861229, 2.09861229, 1.40546511, 1, + 2.09861229, 2.09861229, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 1.40546511, 0, 0, 0, 2, 0, 0, 4.21639532, 4.21639532, 0, 0, 0, 3, 0, 0, + 2.09861229, 2.09861229, 2.09861229, 6.29583687, 2.09861229, 2.09861229, + 2.09861229, 2.09861229, 2.09861229, 2.09861229, 2.09861229, 2.09861229, + 2.09861229, 2.09861229, 2.09861229, 2.09861229, 2.09861229, 2.09861229, + 2.09861229, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 1.40546511, 0, 0, 0, 0, 1, 0, 1.40546511, 0, 0, 0, 0, 1.40546511, 1, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2.09861229, + 2.09861229, 2.09861229, 2.09861229, 2.09861229, 2.09861229, 2.09861229, + 2.09861229, 2.09861229, 2.09861229 } + }; + + CheckMatrices(output, expected.t(), 1e-6); +} + +/** + * Test the Tf-Idf encoding algorithm with the raw count term frequency type + * and the non-smooth inverse document frequency type. The output type is + * vector>. + */ +BOOST_AUTO_TEST_CASE(VectorTfIdfRawCountEncodingTest) +{ + using DictionaryType = StringEncodingDictionary; + + vector> output; + TfIdfEncoding + encoder(TfIdfEncodingPolicy::TfTypes::RAW_COUNT, false); + SplitByAnyOf tokenizer(" ,."); + + encoder.Encode(stringEncodingInput, output, tokenizer); + + const DictionaryType& dictionary = encoder.Dictionary(); + + // Checking that each token has a unique label. + std::unordered_map keysCount; + for (auto& keyValue : dictionary.Mapping()) + { + keysCount[keyValue.second]++; + + BOOST_REQUIRE_EQUAL(keysCount[keyValue.second], 1); + } + + /* The expected values were obtained by almost the same script as in + RawCountSmoothIdfEncodingTest. The only difference is smooth_idf equals + False. */ + vector> expected = { + { 1.40546511, 1.40546511, 2.09861229, 2.09861229, 2.09861229, 1, 2.09861229, + 1.40546511, 1.40546511, 1.40546511, 2.09861229, 2.09861229, 1.40546511, 1, + 2.09861229, 2.09861229, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 1.40546511, 0, 0, 0, 2, 0, 0, 4.21639532, 4.21639532, 0, 0, 0, 3, 0, 0, + 2.09861229, 2.09861229, 2.09861229, 6.29583687, 2.09861229, 2.09861229, + 2.09861229, 2.09861229, 2.09861229, 2.09861229, 2.09861229, 2.09861229, + 2.09861229, 2.09861229, 2.09861229, 2.09861229, 2.09861229, 2.09861229, + 2.09861229, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 1.40546511, 0, 0, 0, 0, 1, 0, 1.40546511, 0, 0, 0, 0, 1.40546511, 1, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2.09861229, + 2.09861229, 2.09861229, 2.09861229, 2.09861229, 2.09861229, 2.09861229, + 2.09861229, 2.09861229, 2.09861229 } + }; + CheckVectors(output, expected, 1e-6); +} + +/** + * Test the Tf-Idf encoding algorithm for individual characters with the + * raw count term frequency type and the non-smooth inverse document frequency + * type. + */ +BOOST_AUTO_TEST_CASE(RawCountTfIdfEncodingIndividualCharactersTest) +{ + vector input = { + "GACCA", + "ABCABCD", + "GAB" + }; + + arma::mat output; + TfIdfEncoding encoder( + TfIdfEncodingPolicy::TfTypes::RAW_COUNT, false); + + encoder.Encode(input, output, CharExtract()); + + /* The expected values were obtained by almost the same script as in + RawCountSmoothIdfEncodingIndividualCharactersTest. The only difference is + smooth_idf equals False. */ + arma::mat target = { + { 1.40546510810816, 2, 2.81093021621633, 0, 0 }, + { 0, 2, 2.81093021621633, 2.81093021621633, 2.09861228866811 }, + { 1.40546510810816, 1, 0, 1.40546510810816, 0 } + }; + + CheckMatrices(output, target.t(), 1e-12); +} + +/** + * Test the Tf-Idf encoding algorithm for individual characters with the + * raw count term frequency type and the non-smooth inverse document frequency + * type. The output type is vector>. + */ +BOOST_AUTO_TEST_CASE(VectorRawCountTfIdfEncodingIndividualCharactersTest) +{ + std::vector input = { + "GACCA", + "ABCABCD", + "GAB" + }; + + vector> output; + TfIdfEncoding encoder( + TfIdfEncodingPolicy::TfTypes::RAW_COUNT, false); + + encoder.Encode(input, output, CharExtract()); + + /* The expected values were obtained by almost the same script as in + RawCountSmoothIdfEncodingIndividualCharactersTest. The only difference is + smooth_idf equals False. */ + vector> expected = { + { 1.40546510810816, 2, 2.81093021621633, 0, 0 }, + { 0, 2, 2.81093021621633, 2.81093021621633, 2.09861228866811 }, + { 1.40546510810816, 1, 0, 1.40546510810816, 0 } + }; + + CheckVectors(output, expected, 1e-12); +} + +/** + * Test the Tf-Idf encoding algorithm for individual characters with the + * binary term frequency type and the smooth inverse document frequency type. + */ +BOOST_AUTO_TEST_CASE(BinarySmoothIdfEncodingIndividualCharactersTest) +{ + vector input = { + "GACCA", + "ABCABCD", + "GAB" + }; + + arma::mat output; + TfIdfEncoding encoder( + TfIdfEncodingPolicy::TfTypes::BINARY, true); + + encoder.Encode(input, output, CharExtract()); + + /* The expected values were obtained by almost the same script as in + RawCountSmoothIdfEncodingIndividualCharactersTest. The only difference is + tf_type equals 'binary'. */ + arma::mat target = { + { 1.28768207245178, 1, 1.28768207245178, 0, 0 }, + { 0, 1, 1.28768207245178, 1.28768207245178, 1.69314718055995 }, + { 1.28768207245178, 1, 0, 1.28768207245178, 0 } + }; + + CheckMatrices(output, target.t(), 1e-12); +} + +/** + * Test the Tf-Idf encoding algorithm for individual characters with the + * binary term frequency type and the smooth inverse document frequency type. + * The output type is vector>. + */ +BOOST_AUTO_TEST_CASE(VectorBinarySmoothIdfEncodingIndividualCharactersTest) +{ + std::vector input = { + "GACCA", + "ABCABCD", + "GAB" + }; + + vector> output; + TfIdfEncoding + encoder(TfIdfEncodingPolicy::TfTypes::BINARY, true); + + encoder.Encode(input, output, CharExtract()); + + /* The expected values were obtained by almost the same script as in + RawCountSmoothIdfEncodingIndividualCharactersTest. The only difference is + tf_type equals 'binary'. */ + vector> expected = { + { 1.28768207245178, 1, 1.28768207245178, 0, 0 }, + { 0, 1, 1.28768207245178, 1.28768207245178, 1.69314718055995 }, + { 1.28768207245178, 1, 0, 1.28768207245178, 0 } + }; + + CheckVectors(output, expected, 1e-12); +} + +/** + * Test the Tf-Idf encoding algorithm for individual characters with the + * binary term frequency type and the non-smooth inverse document frequency + * type. + */ +BOOST_AUTO_TEST_CASE(BinaryTfIdfEncodingIndividualCharactersTest) +{ + vector input = { + "GACCA", + "ABCABCD", + "GAB" + }; + + arma::mat output; + TfIdfEncoding encoder( + TfIdfEncodingPolicy::TfTypes::BINARY, false); + + encoder.Encode(input, output, CharExtract()); + + /* The expected values were obtained by almost the same script as in + RawCountSmoothIdfEncodingIndividualCharactersTest. The only difference is + tf_type equals 'binary' and smooth_idf equals False. */ + arma::mat target = { + { 1.40546510810816, 1, 1.40546510810816, 0, 0 }, + { 0, 1, 1.40546510810816, 1.40546510810816, 2.09861228866811 }, + { 1.40546510810816, 1, 0, 1.40546510810816, 0 } + }; + + CheckMatrices(output, target.t(), 1e-12); +} + +/** + * Test the Tf-Idf encoding algorithm for individual characters with the + * sublinear term frequency type and the smooth inverse document frequency + * type. + */ +BOOST_AUTO_TEST_CASE(SublinearSmoothIdfEncodingIndividualCharactersTest) +{ + vector input = { + "GACCA", + "ABCABCD", + "GAB" + }; + + arma::mat output; + TfIdfEncoding encoder( + TfIdfEncodingPolicy::TfTypes::SUBLINEAR_TF, true); + + encoder.Encode(input, output, CharExtract()); + + /* The expected values were obtained by almost the same script as in + RawCountSmoothIdfEncodingIndividualCharactersTest. The only difference is + tf_type equals 'sublinear_tf'. */ + arma::mat target = { + { 1.28768207245178, 1.69314718055995, 2.18023527042932, 0, 0 }, + { 0, 1.69314718055995, 2.18023527042932, 2.18023527042932, + 1.69314718055995 }, + { 1.28768207245178, 1, 0, 1.28768207245178, 0 } + }; + + CheckMatrices(output, target.t(), 1e-12); +} + +/** + * Test the Tf-Idf encoding algorithm for individual characters with the + * sublinear term frequency type and the non-smooth inverse document frequency + * type. + */ +BOOST_AUTO_TEST_CASE(SublinearTfIdfEncodingIndividualCharactersTest) +{ + vector input = { + "GACCA", + "ABCABCD", + "GAB" + }; + + arma::mat output; + TfIdfEncoding + encoder(TfIdfEncodingPolicy::TfTypes::SUBLINEAR_TF, false); + + encoder.Encode(input, output, CharExtract()); + + /* The expected values were obtained by almost the same script as in + RawCountSmoothIdfEncodingIndividualCharactersTest. The only difference is + tf_type equals 'sublinear_tf' and smooth_idf equals False. */ + arma::mat target = { + { 1.40546510810816, 1.69314718055995, 2.37965928516872, 0, 0 }, + { 0, 1.69314718055995, 2.37965928516872, 2.37965928516872, + 2.09861228866811 }, + { 1.40546510810816, 1, 0, 1.40546510810816, 0 } + }; + + CheckMatrices(output, target.t(), 1e-12); +} + +/** + * Test the Tf-Idf encoding algorithm for individual characters with the + * standard term frequency type and the smooth inverse document frequency + * type. + */ +BOOST_AUTO_TEST_CASE(TermFrequencySmoothIdfEncodingIndividualCharactersTest) +{ + vector input = { + "GACCA", + "ABCABCD", + "GAB" + }; + + arma::mat output; + TfIdfEncoding encoder( + TfIdfEncodingPolicy::TfTypes::TERM_FREQUENCY, true); + + encoder.Encode(input, output, CharExtract()); + + /* The expected values were obtained by the following Python script: + + from sklearn.feature_extraction.text import CountVectorizer + from sklearn.feature_extraction.text import TfidfTransformer + from collections import OrderedDict + import numpy as np + import re + + input_string = [ + "GACCA", + "ABCABCD", + "GAB" + ] + + smooth_idf = True + + dictionary = OrderedDict() + + count = 0 + for line in input_string: + for word in list(line): + if word and (not (word in dictionary)): + dictionary[word] = count + count += 1 + + def tokenizer(line): + return list(line) + + vectorizer = CountVectorizer(strip_accents=False, lowercase=False, + preprocessor=None, tokenizer=tokenizer, stop_words=None, + vocabulary=dictionary, binary=False) + + count = vectorizer.fit_transform(input_string) + + lens = np.array(list(map(len, input_string))).reshape(len(input_string), 1) + + tf = count.toarray() / lens + + transformer = TfidfTransformer(norm=None, smooth_idf=smooth_idf, + sublinear_tf=False) + + X = transformer.fit_transform(tf) + + def format_result(value): + if value == int(value): + return str(int(value)) + else: + return "{0:.16}".format(value) + + for row in X.toarray(): + print("{ " + ", ".join(map(format_result, row)) + " },") + */ + arma::mat target = { + { 0.2575364144903562, 0.4, 0.5150728289807124, 0, 0 }, + { 0, 0.2857142857142857, 0.3679091635576516, 0.3679091635576516, + 0.2418781686514208 }, + { 0.4292273574839269, 0.3333333333333333, 0, 0.4292273574839269, 0 } + }; + + CheckMatrices(output, target.t(), 1e-12); +} + +/** + * Test the Tf-Idf encoding algorithm for individual characters with the + * standard term frequency type and the non-smooth inverse document frequency + * type. + */ +BOOST_AUTO_TEST_CASE(TermFrequencyTfIdfEncodingIndividualCharactersTest) +{ + vector input = { + "GACCA", + "ABCABCD", + "GAB" + }; + + arma::mat output; + TfIdfEncoding encoder( + TfIdfEncodingPolicy::TfTypes::TERM_FREQUENCY, false); + + encoder.Encode(input, output, CharExtract()); + + /* The expected values were obtained by almost the same script as in + TermFrequencySmoothIdfEncodingIndividualCharactersTest. The only difference + is smooth_idf equals False. */ + arma::mat target = { + { 0.2810930216216329, 0.4, 0.5621860432432658, 0, 0 }, + { 0, 0.2857142857142857, 0.4015614594594755, 0.4015614594594755, + 0.2998017555240157 }, + { 0.4684883693693881, 0.3333333333333333, 0, 0.4684883693693881, 0 } + }; + + CheckMatrices(output, target.t(), 1e-12); +} + +/** + * Serialization test for the Tf-Idf encoding algorithm with + * the SplitByAnyOf tokenizer. + */ +BOOST_AUTO_TEST_CASE(SplitByAnyOfTfIdfEncodingSerialization) +{ + using EncoderType = TfIdfEncoding; + + EncoderType encoder; + SplitByAnyOf tokenizer(" ,.\""); + arma::mat output; + + encoder.Encode(stringEncodingInput, output, tokenizer); + + EncoderType xmlEncoder, textEncoder, binaryEncoder; + arma::mat xmlOutput, textOutput, binaryOutput; + + SerializeObjectAll(encoder, xmlEncoder, textEncoder, binaryEncoder); + + CheckDictionaries(encoder.Dictionary(), xmlEncoder.Dictionary()); + CheckDictionaries(encoder.Dictionary(), textEncoder.Dictionary()); + CheckDictionaries(encoder.Dictionary(), binaryEncoder.Dictionary()); + + xmlEncoder.Encode(stringEncodingInput, xmlOutput, tokenizer); + textEncoder.Encode(stringEncodingInput, textOutput, tokenizer); + binaryEncoder.Encode(stringEncodingInput, binaryOutput, tokenizer); + + CheckMatrices(output, xmlOutput, textOutput, binaryOutput); +} + BOOST_AUTO_TEST_SUITE_END();