From d225c9a7ac75c12c273f5c70a26ef99d9ae6377f Mon Sep 17 00:00:00 2001 From: Richard Crowder Date: Fri, 20 Oct 2017 15:06:37 +0100 Subject: [PATCH] Release v1.2 --- CHANGES.md | 12 +- CMakeLists.txt | 61 +- Cs/CMakeLists.txt | 9 +- Cs/cseogmaneo.i | 14 +- Java/CMakeLists.txt | 9 +- Java/jeogmaneo.i | 14 +- Python/CMakeLists.txt | 9 +- Python/pyeogmaneo.i | 16 +- Python/setup.py | 10 +- README.md | 15 +- source/eogmaneo/Agent.cpp | 690 +++++++++++++++ source/eogmaneo/Agent.h | 177 ++++ source/eogmaneo/ComputeSystem.cpp | 11 - source/eogmaneo/ComputeSystem.h | 8 +- source/eogmaneo/Hierarchy.cpp | 71 +- source/eogmaneo/Hierarchy.h | 61 +- source/eogmaneo/Layer.cpp | 806 ++++++++---------- source/eogmaneo/Layer.h | 114 ++- source/eogmaneo/Preprocessing.cpp | 2 +- source/optional/CornerEncoder.cpp | 155 ---- source/optional/CornerEncoder.h | 133 --- source/optional/ImageEncoder.cpp | 22 +- source/optional/ImageEncoder.h | 3 +- .../{RandomEncoder.cpp => KMeansEncoder.cpp} | 225 +++-- .../{RandomEncoder.h => KMeansEncoder.h} | 65 +- source/optional/VisAdapter.cpp | 6 +- source/testing/TestSaveLoad.cpp | 123 +++ 27 files changed, 1683 insertions(+), 1158 deletions(-) create mode 100644 source/eogmaneo/Agent.cpp create mode 100644 source/eogmaneo/Agent.h delete mode 100644 source/eogmaneo/ComputeSystem.cpp delete mode 100644 source/optional/CornerEncoder.cpp delete mode 100644 source/optional/CornerEncoder.h rename source/optional/{RandomEncoder.cpp => KMeansEncoder.cpp} (53%) rename source/optional/{RandomEncoder.h => KMeansEncoder.h} (75%) create mode 100644 source/testing/TestSaveLoad.cpp diff --git a/CHANGES.md b/CHANGES.md index 37e7749..b6607f1 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,11 @@ +1.2 October, 2017 +=================== + +- Hierarchy predicting using a 'double reconstruction' algorithm +- Removal of Corner and Random Pre-Encoders +- Adding the KMeans Pre-Encoder +- Addition of a reinforcement learning Agent interface + 1.1.2 August, 2017 ================== @@ -9,14 +17,14 @@ - Reward averaging - Save/load bug fix -1.1 July, 2017 +1.1 July, 2017 =============== - Hierarchy and Layer updates - Tweaks to reinforcement learning code - Made pre-encoders (Random and Corner) optional - New OpenCV based FAST feature detector pre-encoder -- Bug fixes with language bindings using VisAdpater and OpenCVInterop +- Bug fixes with language bindings using VisAdapter and OpenCVInterop 1.0.1 July, 2017 ================ diff --git a/CMakeLists.txt b/CMakeLists.txt index 934a54d..547d9c6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,10 +13,9 @@ project(EOgmaNeo) set(CMAKE_VERBOSE_MAKEFILE OFF) set(EOGMANEO_MAJOR_VERSION 1) -set(EOGMANEO_MINOR_VERSION 1) -set(EOGMANEO_PATCH_VERSION 2) -set(EOGMANEO_VERSION - ${EOGMANEO_MAJOR_VERSION}.${EOGMANEO_MINOR_VERSION}.${EOGMANEO_PATCH_VERSION}) +set(EOGMANEO_MINOR_VERSION 2) +set(EOGMANEO_PATCH_VERSION 0) +set(EOGMANEO_VERSION ${EOGMANEO_MAJOR_VERSION}.${EOGMANEO_MINOR_VERSION}.${EOGMANEO_PATCH_VERSION}) set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMake") @@ -28,13 +27,13 @@ endif() message(STATUS "Bitness: ${BITNESS}") if(NOT CMAKE_BUILD_TYPE) - message("CMAKE_BUILD_TYPE not set, setting it to Release") - set(CMAKE_BUILD_TYPE Release) + message("CMAKE_BUILD_TYPE not set, setting it to Release") + set(CMAKE_BUILD_TYPE Release) endif() message(STATUS "Build type: ${CMAKE_BUILD_TYPE}") -option(BUILD_SHARED_LIBS OFF) -message(STATUS "Shared libs: ${BUILD_SHARED_LIBS}") +option(BUILD_TESTS OFF) +message(STATUS "Build tests: ${BUILD_TESTS}") option(BUILD_PREENCODERS OFF) message(STATUS "Build pre-encoders: ${BUILD_PREENCODERS}") @@ -57,8 +56,8 @@ endif() find_package(OpenCV HINTS /usr/local/opt/opencv3) if(OpenCV_FOUND) - #message(STATUS "Found existing OpenCV in ${OpenCV_INCLUDE_DIRS}") - include_directories(${OpenCV_INCLUDE_DIRS}) + #message(STATUS "Found existing OpenCV in ${OpenCV_INCLUDE_DIRS}") + include_directories(${OpenCV_INCLUDE_DIRS}) endif() @@ -75,14 +74,11 @@ file(GLOB_RECURSE EOGMANEO_SRC ) if (BUILD_PREENCODERS) - file(GLOB_RECURSE EOGMANEO_CORNERENCODER_SRC "source/optional/CornerEncoder.*") - list(APPEND EOGMANEO_SRC ${EOGMANEO_CORNERENCODER_SRC}) - - file(GLOB_RECURSE EOGMANEO_RANDOMENCODER_SRC "source/optional/RandomEncoder.*") - list(APPEND EOGMANEO_SRC ${EOGMANEO_RANDOMENCODER_SRC}) - file(GLOB_RECURSE EOGMANEO_IMAGEENCODER_SRC "source/optional/ImageEncoder.*") list(APPEND EOGMANEO_SRC ${EOGMANEO_IMAGEENCODER_SRC}) + + file(GLOB_RECURSE EOGMANEO_KMEANSENCODER_SRC "source/optional/KMeansEncoder.*") + list(APPEND EOGMANEO_SRC ${EOGMANEO_KMEANSENCODER_SRC}) endif() if(SFML_FOUND) @@ -100,40 +96,30 @@ if(SFML_FOUND) target_link_libraries(EOgmaNeo ${SFML_LIBRARIES}) endif() if(OpenCV_FOUND) - target_link_libraries(EOgmaNeo ${OpenCV_LIBS}) -endif() - -if(BUILD_SHARED_LIBS) - add_definitions(-DOGMA_DLL) - -# Use CMake to generate SharedLib.h instead? -# include (GenerateExportHeader) -# generate_export_header(OgmaNeo -# BASE_NAME OgmaNeo -# EXPORT_MACRO_NAME OgmaNeo_EXPORT -# EXPORT_FILE_NAME OgmaNeo_Export.h -# STATIC_DEFINE OgmaNeo_BUILT_AS_STATIC -# ) + target_link_libraries(EOgmaNeo ${OpenCV_LIBS}) endif() if(MSVC) - add_definitions(-D_CRT_SECURE_NO_WARNINGS) + add_definitions(-D_CRT_SECURE_NO_WARNINGS) endif() set_property(TARGET EOgmaNeo PROPERTY CXX_STANDARD 14) set_property(TARGET EOgmaNeo PROPERTY CXX_STANDARD_REQUIRED ON) if(CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX) - if(BITNESS EQUAL 64) - set_target_properties(EOgmaNeo PROPERTIES COMPILE_FLAGS "-m64" LINK_FLAGS "-m64") - endif() + if(BITNESS EQUAL 64) + set_target_properties(EOgmaNeo PROPERTIES COMPILE_FLAGS "-m64" LINK_FLAGS "-m64") + endif() endif() +if(BUILD_TESTS) + add_executable(TestSaveLoad source/testing/TestSaveLoad.cpp) + target_link_libraries(TestSaveLoad PUBLIC EOgmaNeo) +endif() # Offer the user the choice of overriding the installation directories set(INSTALL_LIB_DIR lib CACHE PATH "Installation directory for libraries") -set(INSTALL_INCLUDE_DIR include/oegmaneo CACHE PATH - "Installation directory for header files") +set(INSTALL_INCLUDE_DIR include/oegmaneo CACHE PATH "Installation directory for header files") message(STATUS "CMake install prefix = ${CMAKE_INSTALL_PREFIX}") @@ -199,7 +185,8 @@ install(DIRECTORY "${3RDPARTY_PATH}/include/" DESTINATION include/eogmaneo FILES_MATCHING PATTERN "*.h*") -install(EXPORT EOgmaNeoTargets DESTINATION "${INSTALL_CMAKE_DIR}") +install(EXPORT EOgmaNeoTargets + DESTINATION "${INSTALL_CMAKE_DIR}") # Uninstall target configure_file( diff --git a/Cs/CMakeLists.txt b/Cs/CMakeLists.txt index 38afe45..30e6287 100644 --- a/Cs/CMakeLists.txt +++ b/Cs/CMakeLists.txt @@ -66,14 +66,11 @@ file(GLOB_RECURSE EOGMANEO_SRC ) if(BUILD_PREENCODERS) - file(GLOB_RECURSE EOGMANEO_CORNERENCODER_SRC "../source/optional/CornerEncoder.*") - list(APPEND EOGMANEO_SRC ${EOGMANEO_CORNERENCODER_SRC}) - - file(GLOB_RECURSE EOGMANEO_RANDOMENCODER_SRC "../source/optional/RandomEncoder.*") - list(APPEND EOGMANEO_SRC ${EOGMANEO_RANDOMENCODER_SRC}) - file(GLOB_RECURSE EOGMANEO_IMAGEENCODER_SRC "../source/optional/ImageEncoder.*") list(APPEND EOGMANEO_SRC ${EOGMANEO_IMAGEENCODER_SRC}) + + file(GLOB_RECURSE EOGMANEO_KMEANSENCODER_SRC "../source/optional/KMeansEncoder.*") + list(APPEND EOGMANEO_SRC ${EOGMANEO_KMEANSENCODER_SRC}) endif() if(SFML_FOUND) diff --git a/Cs/cseogmaneo.i b/Cs/cseogmaneo.i index c071920..5896557 100644 --- a/Cs/cseogmaneo.i +++ b/Cs/cseogmaneo.i @@ -20,11 +20,11 @@ #include "ComputeSystem.h" #include "Layer.h" #include "Hierarchy.h" +#include "Agent.h" #include "Preprocessing.h" #ifdef BUILD_PREENCODERS -#include "RandomEncoder.h" #include "ImageEncoder.h" -#include "CornerEncoder.h" +#include "KMeansEncoder.h" #endif #ifdef SFML_FOUND #include "VisAdapter.h" @@ -39,21 +39,27 @@ %template(StdPairi) std::pair; %template(StdVecPairi) std::vector >; %template(StdVecLayerDesc) std::vector; +%template(StdVecQLayerDesc) std::vector; %template(StdVecf) std::vector; %template(Std2DVecf) std::vector >; %template(StdVecb) std::vector; %ignore eogmaneo::ForwardWorkItem; %ignore eogmaneo::BackwardWorkItem; +%ignore eogmaneo::PredictionWorkItem; + +%ignore eogmaneo::QForwardWorkItem; +%ignore eogmaneo::QBackwardWorkItem; +%ignore eogmaneo::QLearnWorkItem; %include "ComputeSystem.h" %include "Layer.h" %include "Hierarchy.h" +%include "Agent.h" %include "Preprocessing.h" #ifdef BUILD_PREENCODERS -%include "RandomEncoder.h" %include "ImageEncoder.h" -%include "CornerEncoder.h" +%include "KMeansEncoder.h" #endif #ifdef SFML_FOUND diff --git a/Java/CMakeLists.txt b/Java/CMakeLists.txt index eaee461..7a94798 100644 --- a/Java/CMakeLists.txt +++ b/Java/CMakeLists.txt @@ -64,14 +64,11 @@ file(GLOB_RECURSE EOGMANEO_SRC ) if (BUILD_PREENCODERS) - file(GLOB_RECURSE EOGMANEO_CORNERENCODER_SRC "../source/optional/CornerEncoder.*") - list(APPEND EOGMANEO_SRC ${EOGMANEO_CORNERENCODER_SRC}) - - file(GLOB_RECURSE EOGMANEO_RANDOMENCODER_SRC "../source/optional/RandomEncoder.*") - list(APPEND EOGMANEO_SRC ${EOGMANEO_RANDOMENCODER_SRC}) - file(GLOB_RECURSE EOGMANEO_IMAGEENCODER_SRC "../source/optional/ImageEncoder.*") list(APPEND EOGMANEO_SRC ${EOGMANEO_IMAGEENCODER_SRC}) + + file(GLOB_RECURSE EOGMANEO_KMEANSENCODER_SRC "../source/optional/KMeansEncoder.*") + list(APPEND EOGMANEO_SRC ${EOGMANEO_KMEANSENCODER_SRC}) endif() if(SFML_FOUND) diff --git a/Java/jeogmaneo.i b/Java/jeogmaneo.i index 57a58b4..887a853 100644 --- a/Java/jeogmaneo.i +++ b/Java/jeogmaneo.i @@ -20,11 +20,11 @@ #include "ComputeSystem.h" #include "Layer.h" #include "Hierarchy.h" +#include "Agent.h" #include "Preprocessing.h" #ifdef BUILD_PREENCODERS -#include "RandomEncoder.h" +#include "KMeansEncoder.h" #include "ImageEncoder.h" -#include "CornerEncoder.h" #endif #ifdef SFML_FOUND #include "VisAdapter.h" @@ -39,12 +39,18 @@ %template(StdPairi) std::pair; %template(StdVecPairi) std::vector >; %template(StdVecLayerDesc) std::vector; +%template(StdVecQLayerDesc) std::vector; %template(StdVecf) std::vector; %template(Std2DVecf) std::vector >; %template(StdVecb) std::vector; %ignore eogmaneo::ForwardWorkItem; %ignore eogmaneo::BackwardWorkItem; +%ignore eogmaneo::PredictionWorkItem; + +%ignore eogmaneo::QForwardWorkItem; +%ignore eogmaneo::QBackwardWorkItem; +%ignore eogmaneo::QLearnWorkItem; // Handle operator overloading %rename(get) operator(); @@ -52,11 +58,11 @@ %include "ComputeSystem.h" %include "Layer.h" %include "Hierarchy.h" +%include "Agent.h" %include "Preprocessing.h" #ifdef BUILD_PREENCODERS -%include "RandomEncoder.h" +%include "KMeansEncoder.h" %include "ImageEncoder.h" -%include "CornerEncoder.h" #endif #ifdef SFML_FOUND diff --git a/Python/CMakeLists.txt b/Python/CMakeLists.txt index 719bbf4..eb9ca6f 100644 --- a/Python/CMakeLists.txt +++ b/Python/CMakeLists.txt @@ -70,14 +70,11 @@ file(GLOB_RECURSE EOGMANEO_SRC ) if (BUILD_PREENCODERS) - file(GLOB_RECURSE EOGMANEO_CORNERENCODER_SRC "../source/optional/CornerEncoder.*") - list(APPEND EOGMANEO_SRC ${EOGMANEO_CORNERENCODER_SRC}) - - file(GLOB_RECURSE EOGMANEO_RANDOMENCODER_SRC "../source/optional/RandomEncoder.*") - list(APPEND EOGMANEO_SRC ${EOGMANEO_RANDOMENCODER_SRC}) - file(GLOB_RECURSE EOGMANEO_IMAGEENCODER_SRC "../source/optional/ImageEncoder.*") list(APPEND EOGMANEO_SRC ${EOGMANEO_IMAGEENCODER_SRC}) + + file(GLOB_RECURSE EOGMANEO_KMEANSENCODER_SRC "../source/optional/KMeansEncoder.*") + list(APPEND EOGMANEO_SRC ${EOGMANEO_KMEANSENCODER_SRC}) endif() if(SFML_FOUND) diff --git a/Python/pyeogmaneo.i b/Python/pyeogmaneo.i index 64c8584..1db4c93 100644 --- a/Python/pyeogmaneo.i +++ b/Python/pyeogmaneo.i @@ -20,11 +20,11 @@ #include "ComputeSystem.h" #include "Layer.h" #include "Hierarchy.h" +#include "Agent.h" #include "Preprocessing.h" #ifdef BUILD_PREENCODERS -#include "RandomEncoder.h" +#include "KMeansEncoder.h" #include "ImageEncoder.h" -#include "CornerEncoder.h" #endif #ifdef SFML_FOUND #include "VisAdapter.h" @@ -50,23 +50,28 @@ %template(StdPairi) std::pair; %template(StdVecPairi) std::vector >; %template(StdVecLayerDesc) std::vector; +%template(StdVecQLayerDesc) std::vector; %template(StdVecf) std::vector; %template(Std2DVecf) std::vector >; %template(StdVecb) std::vector; %ignore eogmaneo::ForwardWorkItem; %ignore eogmaneo::BackwardWorkItem; +%ignore eogmaneo::PredictionWorkItem; + +%ignore eogmaneo::QForwardWorkItem; +%ignore eogmaneo::QBackwardWorkItem; +%ignore eogmaneo::QLearnWorkItem; %include "ComputeSystem.h" %include "Layer.h" %include "Hierarchy.h" +%include "Agent.h" %include "Preprocessing.h" #ifdef BUILD_PREENCODERS -%include "RandomEncoder.h" +%include "KMeansEncoder.h" %include "ImageEncoder.h" -%include "CornerEncoder.h" #endif - #ifdef SFML_FOUND %ignore eogmaneo::SDR; %ignore eogmaneo::WeightSet; @@ -74,7 +79,6 @@ %ignore eogmaneo::Caret; %include "VisAdapter.h" #endif - #ifdef OPENCV_FOUND %include "OpenCVInterop.h" #endif diff --git a/Python/setup.py b/Python/setup.py index 9db065a..3cfb2d7 100644 --- a/Python/setup.py +++ b/Python/setup.py @@ -178,17 +178,15 @@ def get_outputs(self): sources=["pyeogmaneo.i", "../source/eogmaneo/ComputeSystem.cpp", "../source/eogmaneo/Hierarchy.cpp", - "../source/eogmaneo/Layer.cpp", + "../source/eogmaneo/Agent.cpp", "../source/eogmaneo/Preprocessing.cpp", - "../source/eogmaneo/ThreadPool.cpp", - "../source/optional/CornerEncoder.cpp", - "../source/optional/ImageEncoder.cpp", - "../source/optional/RandomEncoder.cpp",] + "../source/optional/KMeansEncoder.cpp", + "../source/optional/ImageEncoder.cpp",] ) setup( name="eogmaneo", - version="1.1.2", + version="1.2", description="Python bindings for the EOgmaNeo library", long_description='https://github.com/ogmacorp/EOgmaNeo', author='Ogma Intelligent Systems Corp', diff --git a/README.md b/README.md index b14c88f..c48d2b5 100644 --- a/README.md +++ b/README.md @@ -72,7 +72,7 @@ And retrieve predictions with: ``` Important note: Inputs are presented in a _chunked SDR_ format. This format consists of a list of active units, each corresponding to a chunk (or _tile_) of input. -This vector is in raveled form (of size width x height). +This vector is in raveled form (1-D array of size width x height). Here is an image to help describe the input format: [Chunked SDR](./chunkedSDR.png) @@ -81,9 +81,9 @@ All data must be presented in this form. To help with conversion, we included a Currently available pre-encoders: - ImageEncoder -- RandomEncoder -- CornerEncoder -- LineSegmentEncoder +- KMeansEncoder +- LineSegmentEncoder (OpenCV) +- FastFeatureDetector (OpenCV) You may need to develop your own pre-encoders depending on the task. Sometimes, data can be binned into chunks without any real pre-encoding, such as bounded scalars. @@ -122,7 +122,7 @@ Sending the current EOgmaNeo hierarchy state through the adapter to the NeoVis a #### OpenCV Interop -An [OpenCV](http://opencv.org/) interop class is built into the library if the CMake build process finds OpenCV on your system. +An [OpenCV](http://opencv.org/) interop class is built into the library if the CMake build process finds OpenCV on your system. **Note:** For the CMake build process to find OpenCV make sure that a `OpenCV_DIR` environment variable is set to the location of OpenCV, specifically the directory that contains the `OpenCVConfig.cmake` file. If this configuration file is not found the OpenCV interop code is not included in the library. @@ -139,7 +139,7 @@ Be aware that all these functions contain certain remapping of input arrays into **Note:** The `LineSegmentDetector` contains extra functionality that takes detected lines and forms them into a sparse chunked representation as it's output. Therefore, the LineSegmentDetector acts as a pre-encoder for an EOgmaNeo hierarchy. -**Note:** Similar to the `LineSegmentDetector`, the `FastFeatureDetector` contains extra functionality that takes detected corner keypoints and forms them into a sparse chunked representation as its output. Therefore, it acts as a pre-encoder for an EOgmaNeo hierarchy. +**Note:** Similar to the `LineSegmentDetector`, the `FastFeatureDetector` contains extra functionality that takes detected corner key points and forms them into a sparse chunked representation as its output. Therefore, it acts as a pre-encoder for an EOgmaNeo hierarchy. ## Requirements @@ -164,14 +164,13 @@ The following commands can be used to build the EOgmaNeo library: ```bash mkdir build; cd build -cmake -DBUILD_SHARED_LIBS=ON -DBUILD_PREENCODERS=ON .. +cmake -DBUILD_PREENCODERS=ON .. make ``` The `cmake` command can be passed the following optional settings: - `CMAKE_INSTALL_PREFIX` to determine where to install the library and header files. Default is a system-wide install location. -- `BUILD_SHARED_LIBS` boolean CMake option can be used to create dynamic/shared object library (default is to create a _static_ library). - `BUILD_PREENCODERS` to include the Random and Corner pre-encoders into the library. `make install` can be run to install the library. `make uninstall` can be used to uninstall the library. diff --git a/source/eogmaneo/Agent.cpp b/source/eogmaneo/Agent.cpp new file mode 100644 index 0000000..42837e8 --- /dev/null +++ b/source/eogmaneo/Agent.cpp @@ -0,0 +1,690 @@ +// ---------------------------------------------------------------------------- +// EOgmaNeo +// Copyright(c) 2017 Ogma Intelligent Systems Corp. All rights reserved. +// +// This copy of EOgmaNeo is licensed to you under the terms described +// in the EOGMANEO_LICENSE.md file included in this distribution. +// ---------------------------------------------------------------------------- + +#include "Agent.h" + +#include +#include +#include +#include + +using namespace eogmaneo; + +void QForwardWorkItem::run(size_t threadIndex) { + const Layer* pLayer = &_pAgent->_ph->getLayer(_l); + QLayer* pQLayer = &_pAgent->_qLayers[_l]; + + int hiddenChunkSize = pLayer->getChunkSize(); + + int hiddenChunksInX = pLayer->getHiddenWidth() / hiddenChunkSize; + int hiddenChunksInY = pLayer->getHiddenHeight() / hiddenChunkSize; + + int hiddenChunkX = _hiddenChunkIndex % hiddenChunksInX; + int hiddenChunkY = _hiddenChunkIndex / hiddenChunksInX; + + // Extract input views + float activation = 0.0f; + float count = 0.0f; + + int c = _pAgent->_hiddenStatesTemp[_l][_hiddenChunkIndex]; + + int dhx = c % hiddenChunkSize; + int dhy = c / hiddenChunkSize; + + int hIndex = (hiddenChunkX * hiddenChunkSize + dhx) + (hiddenChunkY * hiddenChunkSize + dhy) * pLayer->getHiddenWidth(); + + if (_l == 0) { + for (int a = 0; a < _pAgent->_actions.size(); a++) { + int visibleChunkSize = _pAgent->_actionChunkSizes[a]; + + int visibleChunksInX = std::get<0>(_pAgent->_actionSizes[a]) / visibleChunkSize; + int visibleChunksInY = std::get<1>(_pAgent->_actionSizes[a]) / visibleChunkSize; + + float toInputX = static_cast(visibleChunksInX) / static_cast(hiddenChunksInX); + float toInputY = static_cast(visibleChunksInY) / static_cast(hiddenChunksInY); + + int visibleChunkCenterX = (hiddenChunkX + 0.5f) * toInputX; + int visibleChunkCenterY = (hiddenChunkY + 0.5f) * toInputY; + + int visibleCenterX = (visibleChunkCenterX + 0.5f) * visibleChunkSize; + int visibleCenterY = (visibleChunkCenterY + 0.5f) * visibleChunkSize; + + int spatialVisibleRadius = _pAgent->_qLayerDescs[_l]._qRadius; + + int spatialVisibleDiam = spatialVisibleRadius * 2 + 1; + + int spatialChunkRadius = std::ceil(static_cast(spatialVisibleRadius) / static_cast(visibleChunkSize)); + + int lowerVisibleX = visibleCenterX - spatialVisibleRadius; + int lowerVisibleY = visibleCenterY - spatialVisibleRadius; + + int upperVisibleX = visibleCenterX + spatialVisibleRadius; + int upperVisibleY = visibleCenterY + spatialVisibleRadius; + + int i = a + _pAgent->_actions.size() * hIndex; + + for (int dcx = -spatialChunkRadius; dcx <= spatialChunkRadius; dcx++) + for (int dcy = -spatialChunkRadius; dcy <= spatialChunkRadius; dcy++) { + int cx = visibleChunkCenterX + dcx; + int cy = visibleChunkCenterY + dcy; + + if (cx >= 0 && cx < visibleChunksInX && cy >= 0 && cy < visibleChunksInY) { + int visibleChunkIndex = cx + cy * visibleChunksInX; + + int maxIndex = _pAgent->_actionsTemp[a][visibleChunkIndex]; + + int mdx = maxIndex % visibleChunkSize; + int mdy = maxIndex / visibleChunkSize; + + int vx = cx * visibleChunkSize + mdx; + int vy = cy * visibleChunkSize + mdy; + + if (vx >= lowerVisibleX && vx <= upperVisibleX && vy >= lowerVisibleY && vy <= upperVisibleY) { + int wi = (vx - lowerVisibleX) + (vy - lowerVisibleY) * spatialVisibleDiam; + + activation += pQLayer->_qWeights[i][wi]; + count += 1.0f; + } + } + } + } + } + else { + const Layer* pLayerPrev = &_pAgent->_ph->getLayer(_l - 1); + QLayer* pQLayerPrev = &_pAgent->_qLayers[_l - 1]; + + int visibleChunkSize = pLayerPrev->getChunkSize(); + + int visibleChunksInX = pLayerPrev->getHiddenWidth() / visibleChunkSize; + int visibleChunksInY = pLayerPrev->getHiddenHeight() / visibleChunkSize; + + float toInputX = static_cast(visibleChunksInX) / static_cast(hiddenChunksInX); + float toInputY = static_cast(visibleChunksInY) / static_cast(hiddenChunksInY); + + int visibleChunkCenterX = (hiddenChunkX + 0.5f) * toInputX; + int visibleChunkCenterY = (hiddenChunkY + 0.5f) * toInputY; + + int visibleCenterX = (visibleChunkCenterX + 0.5f) * visibleChunkSize; + int visibleCenterY = (visibleChunkCenterY + 0.5f) * visibleChunkSize; + + int spatialVisibleRadius = _pAgent->_qLayerDescs[_l]._qRadius; + + int spatialVisibleDiam = spatialVisibleRadius * 2 + 1; + + int spatialChunkRadius = std::ceil(static_cast(spatialVisibleRadius) / static_cast(visibleChunkSize)); + + int lowerVisibleX = visibleCenterX - spatialVisibleRadius; + int lowerVisibleY = visibleCenterY - spatialVisibleRadius; + + int upperVisibleX = visibleCenterX + spatialVisibleRadius; + int upperVisibleY = visibleCenterY + spatialVisibleRadius; + + int i = hIndex; + + for (int dcx = -spatialChunkRadius; dcx <= spatialChunkRadius; dcx++) + for (int dcy = -spatialChunkRadius; dcy <= spatialChunkRadius; dcy++) { + int cx = visibleChunkCenterX + dcx; + int cy = visibleChunkCenterY + dcy; + + if (cx >= 0 && cx < visibleChunksInX && cy >= 0 && cy < visibleChunksInY) { + int visibleChunkIndex = cx + cy * visibleChunksInX; + + int maxIndex = _pAgent->_hiddenStatesTemp[_l - 1][visibleChunkIndex]; + + int mdx = maxIndex % visibleChunkSize; + int mdy = maxIndex / visibleChunkSize; + + int vx = cx * visibleChunkSize + mdx; + int vy = cy * visibleChunkSize + mdy; + + if (vx >= lowerVisibleX && vx <= upperVisibleX && vy >= lowerVisibleY && vy <= upperVisibleY) { + int wi = (vx - lowerVisibleX) + (vy - lowerVisibleY) * spatialVisibleDiam; + + activation += pQLayer->_qWeights[i][wi] * pQLayerPrev->_hiddenActivations[visibleChunkIndex]; + count += 1.0f; + } + } + } + } + + pQLayer->_hiddenActivations[_hiddenChunkIndex] = activation / std::max(1.0f, count); +} + +void QBackwardWorkItem::run(size_t threadIndex) { + const Layer* pLayer = &_pAgent->_ph->getLayer(_l); + QLayer* pQLayer = &_pAgent->_qLayers[_l]; + + int hiddenChunkSize = pLayer->getChunkSize(); + + int hiddenChunksInX = pLayer->getHiddenWidth() / hiddenChunkSize; + int hiddenChunksInY = pLayer->getHiddenHeight() / hiddenChunkSize; + + int hiddenChunkX = _hiddenChunkIndex % hiddenChunksInX; + int hiddenChunkY = _hiddenChunkIndex / hiddenChunksInX; + + // Extract input views + int c = _pAgent->_hiddenStatesTemp[_l][_hiddenChunkIndex]; + + int dhx = c % hiddenChunkSize; + int dhy = c / hiddenChunkSize; + + int hIndex = (hiddenChunkX * hiddenChunkSize + dhx) + (hiddenChunkY * hiddenChunkSize + dhy) * pLayer->getHiddenWidth(); + + float error = pQLayer->_hiddenErrors[_hiddenChunkIndex] / std::max(1.0f, pQLayer->_hiddenCounts[_hiddenChunkIndex]); + + if (_l == 0) { + for (int a = 0; a < _pAgent->_actions.size(); a++) { + int visibleChunkSize = _pAgent->_actionChunkSizes[a]; + + int visibleBitsPerChunk = visibleChunkSize * visibleChunkSize; + + int visibleChunksInX = std::get<0>(_pAgent->_actionSizes[a]) / visibleChunkSize; + int visibleChunksInY = std::get<1>(_pAgent->_actionSizes[a]) / visibleChunkSize; + + float toInputX = static_cast(visibleChunksInX) / static_cast(hiddenChunksInX); + float toInputY = static_cast(visibleChunksInY) / static_cast(hiddenChunksInY); + + int visibleChunkCenterX = (hiddenChunkX + 0.5f) * toInputX; + int visibleChunkCenterY = (hiddenChunkY + 0.5f) * toInputY; + + int visibleCenterX = (visibleChunkCenterX + 0.5f) * visibleChunkSize; + int visibleCenterY = (visibleChunkCenterY + 0.5f) * visibleChunkSize; + + int spatialVisibleRadius = _pAgent->_qLayerDescs[_l]._qRadius; + + int spatialVisibleDiam = spatialVisibleRadius * 2 + 1; + + int spatialChunkRadius = std::ceil(static_cast(spatialVisibleRadius) / static_cast(visibleChunkSize)); + + int lowerVisibleX = visibleCenterX - spatialVisibleRadius; + int lowerVisibleY = visibleCenterY - spatialVisibleRadius; + + int upperVisibleX = visibleCenterX + spatialVisibleRadius; + int upperVisibleY = visibleCenterY + spatialVisibleRadius; + + int i = a + _pAgent->_actions.size() * hIndex; + + for (int dcx = -spatialChunkRadius; dcx <= spatialChunkRadius; dcx++) + for (int dcy = -spatialChunkRadius; dcy <= spatialChunkRadius; dcy++) { + int cx = visibleChunkCenterX + dcx; + int cy = visibleChunkCenterY + dcy; + + if (cx >= 0 && cx < visibleChunksInX && cy >= 0 && cy < visibleChunksInY) { + int visibleChunkIndex = cx + cy * visibleChunksInX; + + for (int index = 0; index < visibleBitsPerChunk; index++) { + int mdx = index % visibleChunkSize; + int mdy = index / visibleChunkSize; + + int vx = cx * visibleChunkSize + mdx; + int vy = cy * visibleChunkSize + mdy; + + if (vx >= lowerVisibleX && vx <= upperVisibleX && vy >= lowerVisibleY && vy <= upperVisibleY) { + int wi = (vx - lowerVisibleX) + (vy - lowerVisibleY) * spatialVisibleDiam; + + int vIndex = vx + vy * std::get<0>(_pAgent->_actionSizes[a]); + + _pAgent->_actionErrors[a][vIndex] += error * pQLayer->_qWeights[i][wi]; + _pAgent->_actionCounts[a][vIndex] += 1.0f; + } + } + } + } + } + } + else { + const Layer* pLayerPrev = &_pAgent->_ph->getLayer(_l - 1); + QLayer* pQLayerPrev = &_pAgent->_qLayers[_l - 1]; + + int visibleChunkSize = pLayerPrev->getChunkSize(); + + int visibleChunksInX = pLayerPrev->getHiddenWidth() / visibleChunkSize; + int visibleChunksInY = pLayerPrev->getHiddenHeight() / visibleChunkSize; + + float toInputX = static_cast(visibleChunksInX) / static_cast(hiddenChunksInX); + float toInputY = static_cast(visibleChunksInY) / static_cast(hiddenChunksInY); + + int visibleChunkCenterX = (hiddenChunkX + 0.5f) * toInputX; + int visibleChunkCenterY = (hiddenChunkY + 0.5f) * toInputY; + + int visibleCenterX = (visibleChunkCenterX + 0.5f) * visibleChunkSize; + int visibleCenterY = (visibleChunkCenterY + 0.5f) * visibleChunkSize; + + int spatialVisibleRadius = _pAgent->_qLayerDescs[_l]._qRadius; + + int spatialVisibleDiam = spatialVisibleRadius * 2 + 1; + + int spatialChunkRadius = std::ceil(static_cast(spatialVisibleRadius) / static_cast(visibleChunkSize)); + + int lowerVisibleX = visibleCenterX - spatialVisibleRadius; + int lowerVisibleY = visibleCenterY - spatialVisibleRadius; + + int upperVisibleX = visibleCenterX + spatialVisibleRadius; + int upperVisibleY = visibleCenterY + spatialVisibleRadius; + + int i = hIndex; + + for (int dcx = -spatialChunkRadius; dcx <= spatialChunkRadius; dcx++) + for (int dcy = -spatialChunkRadius; dcy <= spatialChunkRadius; dcy++) { + int cx = visibleChunkCenterX + dcx; + int cy = visibleChunkCenterY + dcy; + + if (cx >= 0 && cx < visibleChunksInX && cy >= 0 && cy < visibleChunksInY) { + int visibleChunkIndex = cx + cy * visibleChunksInX; + + int maxIndex = _pAgent->_hiddenStatesTemp[_l - 1][visibleChunkIndex]; + + int mdx = maxIndex % visibleChunkSize; + int mdy = maxIndex / visibleChunkSize; + + int vx = cx * visibleChunkSize + mdx; + int vy = cy * visibleChunkSize + mdy; + + if (vx >= lowerVisibleX && vx <= upperVisibleX && vy >= lowerVisibleY && vy <= upperVisibleY) { + int wi = (vx - lowerVisibleX) + (vy - lowerVisibleY) * spatialVisibleDiam; + + pQLayerPrev->_hiddenErrors[visibleChunkIndex] += error * pQLayer->_qWeights[i][wi]; + pQLayerPrev->_hiddenCounts[visibleChunkIndex] += 1.0f; + } + } + } + } +} + +void QLearnWorkItem::run(size_t threadIndex) { + const Layer* pLayer = &_pAgent->_ph->getLayer(_l); + QLayer* pQLayer = &_pAgent->_qLayers[_l]; + + int hiddenChunkSize = pLayer->getChunkSize(); + + int hiddenChunksInX = pLayer->getHiddenWidth() / hiddenChunkSize; + int hiddenChunksInY = pLayer->getHiddenHeight() / hiddenChunkSize; + + int hiddenChunkX = _hiddenChunkIndex % hiddenChunksInX; + int hiddenChunkY = _hiddenChunkIndex / hiddenChunksInX; + + // Extract input views + int c = _pAgent->_hiddenStatesTemp[_l][_hiddenChunkIndex]; + + int dhx = c % hiddenChunkSize; + int dhy = c / hiddenChunkSize; + + int hIndex = (hiddenChunkX * hiddenChunkSize + dhx) + (hiddenChunkY * hiddenChunkSize + dhy) * pLayer->getHiddenWidth(); + + float alphaError = _pAgent->_alpha * pQLayer->_hiddenErrors[_hiddenChunkIndex] / std::max(1.0f, pQLayer->_hiddenCounts[_hiddenChunkIndex]); + + if (_l == 0) { + for (int a = 0; a < _pAgent->_actions.size(); a++) { + int visibleChunkSize = _pAgent->_actionChunkSizes[a]; + + int visibleChunksInX = std::get<0>(_pAgent->_actionSizes[a]) / visibleChunkSize; + int visibleChunksInY = std::get<1>(_pAgent->_actionSizes[a]) / visibleChunkSize; + + float toInputX = static_cast(visibleChunksInX) / static_cast(hiddenChunksInX); + float toInputY = static_cast(visibleChunksInY) / static_cast(hiddenChunksInY); + + int visibleChunkCenterX = (hiddenChunkX + 0.5f) * toInputX; + int visibleChunkCenterY = (hiddenChunkY + 0.5f) * toInputY; + + int visibleCenterX = (visibleChunkCenterX + 0.5f) * visibleChunkSize; + int visibleCenterY = (visibleChunkCenterY + 0.5f) * visibleChunkSize; + + int spatialVisibleRadius = _pAgent->_qLayerDescs[_l]._qRadius; + + int spatialVisibleDiam = spatialVisibleRadius * 2 + 1; + + int spatialChunkRadius = std::ceil(static_cast(spatialVisibleRadius) / static_cast(visibleChunkSize)); + + int lowerVisibleX = visibleCenterX - spatialVisibleRadius; + int lowerVisibleY = visibleCenterY - spatialVisibleRadius; + + int upperVisibleX = visibleCenterX + spatialVisibleRadius; + int upperVisibleY = visibleCenterY + spatialVisibleRadius; + + int i = a + _pAgent->_actions.size() * hIndex; + + for (int dcx = -spatialChunkRadius; dcx <= spatialChunkRadius; dcx++) + for (int dcy = -spatialChunkRadius; dcy <= spatialChunkRadius; dcy++) { + int cx = visibleChunkCenterX + dcx; + int cy = visibleChunkCenterY + dcy; + + if (cx >= 0 && cx < visibleChunksInX && cy >= 0 && cy < visibleChunksInY) { + int visibleChunkIndex = cx + cy * visibleChunksInX; + + int maxIndex = _pAgent->_actionsTemp[a][visibleChunkIndex]; + + int mdx = maxIndex % visibleChunkSize; + int mdy = maxIndex / visibleChunkSize; + + int vx = cx * visibleChunkSize + mdx; + int vy = cy * visibleChunkSize + mdy; + + if (vx >= lowerVisibleX && vx <= upperVisibleX && vy >= lowerVisibleY && vy <= upperVisibleY) { + int wi = (vx - lowerVisibleX) + (vy - lowerVisibleY) * spatialVisibleDiam; + + int vIndex = vx + vy * std::get<0>(_pAgent->_actionSizes[a]); + + pQLayer->_qWeights[i][wi] += alphaError; + } + } + } + } + } + else { + const Layer* pLayerPrev = &_pAgent->_ph->getLayer(_l - 1); + QLayer* pQLayerPrev = &_pAgent->_qLayers[_l - 1]; + + int visibleChunkSize = pLayerPrev->getChunkSize(); + + int visibleChunksInX = pLayerPrev->getHiddenWidth() / visibleChunkSize; + int visibleChunksInY = pLayerPrev->getHiddenHeight() / visibleChunkSize; + + float toInputX = static_cast(visibleChunksInX) / static_cast(hiddenChunksInX); + float toInputY = static_cast(visibleChunksInY) / static_cast(hiddenChunksInY); + + int visibleChunkCenterX = (hiddenChunkX + 0.5f) * toInputX; + int visibleChunkCenterY = (hiddenChunkY + 0.5f) * toInputY; + + int visibleCenterX = (visibleChunkCenterX + 0.5f) * visibleChunkSize; + int visibleCenterY = (visibleChunkCenterY + 0.5f) * visibleChunkSize; + + int spatialVisibleRadius = _pAgent->_qLayerDescs[_l]._qRadius; + + int spatialVisibleDiam = spatialVisibleRadius * 2 + 1; + + int spatialChunkRadius = std::ceil(static_cast(spatialVisibleRadius) / static_cast(visibleChunkSize)); + + int lowerVisibleX = visibleCenterX - spatialVisibleRadius; + int lowerVisibleY = visibleCenterY - spatialVisibleRadius; + + int upperVisibleX = visibleCenterX + spatialVisibleRadius; + int upperVisibleY = visibleCenterY + spatialVisibleRadius; + + int i = hIndex; + + for (int dcx = -spatialChunkRadius; dcx <= spatialChunkRadius; dcx++) + for (int dcy = -spatialChunkRadius; dcy <= spatialChunkRadius; dcy++) { + int cx = visibleChunkCenterX + dcx; + int cy = visibleChunkCenterY + dcy; + + if (cx >= 0 && cx < visibleChunksInX && cy >= 0 && cy < visibleChunksInY) { + int visibleChunkIndex = cx + cy * visibleChunksInX; + + int maxIndex = _pAgent->_hiddenStatesTemp[_l - 1][visibleChunkIndex]; + + int mdx = maxIndex % visibleChunkSize; + int mdy = maxIndex / visibleChunkSize; + + int vx = cx * visibleChunkSize + mdx; + int vy = cy * visibleChunkSize + mdy; + + if (vx >= lowerVisibleX && vx <= upperVisibleX && vy >= lowerVisibleY && vy <= upperVisibleY) { + int wi = (vx - lowerVisibleX) + (vy - lowerVisibleY) * spatialVisibleDiam; + + pQLayer->_qWeights[i][wi] += alphaError * pQLayerPrev->_hiddenActivations[visibleChunkIndex]; + } + } + } + } +} + +void Agent::qForward(ComputeSystem &cs) { + for (int l = 0; l < _qLayers.size(); l++) { + int chunkSize = _ph->getLayer(l).getChunkSize(); + int chunksInX = _ph->getLayer(l).getHiddenWidth() / chunkSize; + int chunksInY = _ph->getLayer(l).getHiddenHeight() / chunkSize; + + int numChunks = chunksInX * chunksInY; + + std::uniform_int_distribution seedDist(0, 99999); + + // Queue tasks + for (int i = 0; i < numChunks; i++) { + std::shared_ptr item = std::make_shared(); + + item->_l = l; + item->_hiddenChunkIndex = i; + item->_pAgent = this; + item->_rng.seed(seedDist(cs._rng)); + + cs._pool.addItem(item); + } + + cs._pool.wait(); + } +} + +void Agent::qBackward(ComputeSystem &cs) { + for (int a = 0; a < _actionErrors.size(); a++) { + _actionErrors[a] = std::vector(_actionErrors[a].size(), 0.0f); + _actionCounts[a] = std::vector(_actionCounts[a].size(), 0.0f); + } + + // Clear errors for summation + for (int l = 0; l < _qLayers.size() - 1; l++) { + _qLayers[l]._hiddenErrors = std::vector(_qLayers[l]._hiddenErrors.size(), 0.0f); + _qLayers[l]._hiddenCounts = std::vector(_qLayers[l]._hiddenCounts.size(), 0.0f); + } + + for (int l = _qLayers.size() - 1; l >= 0; l--) { + int chunkSize = _ph->getLayer(l).getChunkSize(); + int chunksInX = _ph->getLayer(l).getHiddenWidth() / chunkSize; + int chunksInY = _ph->getLayer(l).getHiddenHeight() / chunkSize; + + int numChunks = chunksInX * chunksInY; + + std::uniform_int_distribution seedDist(0, 99999); + + // Queue tasks + for (int i = 0; i < numChunks; i++) { + std::shared_ptr item = std::make_shared(); + + item->_l = l; + item->_hiddenChunkIndex = i; + item->_pAgent = this; + item->_rng.seed(seedDist(cs._rng)); + + cs._pool.addItem(item); + } + + cs._pool.wait(); + } +} + +void Agent::qLearn(ComputeSystem &cs) { + for (int l = 0; l < _qLayers.size(); l++) { + int chunkSize = _ph->getLayer(l).getChunkSize(); + int chunksInX = _ph->getLayer(l).getHiddenWidth() / chunkSize; + int chunksInY = _ph->getLayer(l).getHiddenHeight() / chunkSize; + + int numChunks = chunksInX * chunksInY; + + std::uniform_int_distribution seedDist(0, 99999); + + // Queue tasks + for (int i = 0; i < numChunks; i++) { + std::shared_ptr item = std::make_shared(); + + item->_l = l; + item->_hiddenChunkIndex = i; + item->_pAgent = this; + item->_rng.seed(seedDist(cs._rng)); + + cs._pool.addItem(item); + } + + cs._pool.wait(); + } +} + +void Agent::create(Hierarchy* ph, const std::vector > &actionSizes, const std::vector &actionChunkSizes, const std::vector &qLayerDescs, unsigned long seed) { + std::mt19937 rng(seed); + + _ph = ph; + + _actionSizes = actionSizes; + _actionChunkSizes = actionChunkSizes; + + _qLayerDescs = qLayerDescs; + + _qLayers.resize(qLayerDescs.size()); + + assert(_qLayers.size() == ph->getNumLayers()); + + std::uniform_real_distribution initWeightDistQ(0.999f, 1.001f); + + _actions.resize(actionSizes.size()); + _actionErrors.resize(actionSizes.size()); + _actionCounts.resize(actionSizes.size()); + + for (int a = 0; a < _actions.size(); a++) { + _actions[a].resize((std::get<0>(actionSizes[a]) / actionChunkSizes[a]) * (std::get<1>(actionSizes[a]) / actionChunkSizes[a]), 0); + _actionErrors[a].resize(std::get<0>(actionSizes[a]) * std::get<1>(actionSizes[a]), 0.0f); + _actionCounts[a].resize(std::get<0>(actionSizes[a]) * std::get<1>(actionSizes[a]), 0.0f); + } + + for (int l = 0; l < _qLayers.size(); l++) { + int numHidden = ph->getLayer(l).getHiddenWidth() * ph->getLayer(l).getHiddenHeight(); + int numChunks = (ph->getLayer(l).getHiddenWidth() / ph->getLayer(l).getChunkSize()) * (ph->getLayer(l).getHiddenHeight() / ph->getLayer(l).getChunkSize()); + + _qLayers[l]._hiddenActivations.resize(numChunks, 0.0f); + _qLayers[l]._hiddenErrors.resize(numChunks, 0.0f); + _qLayers[l]._hiddenCounts.resize(numChunks, 0.0f); + + if (l == 0) { + _qLayers[l]._qWeights.resize(numHidden * _actions.size()); + + int qVecSize = qLayerDescs[l]._qRadius * 2 + 1; + + qVecSize *= qVecSize; + + for (int i = 0; i < _qLayers[l]._qWeights.size(); i++) { + _qLayers[l]._qWeights[i].resize(qVecSize); + + for (int j = 0; j < qVecSize; j++) + _qLayers[l]._qWeights[i][j] = initWeightDistQ(rng); + } + } + else { + _qLayers[l]._qWeights.resize(numHidden); + + int qVecSize = qLayerDescs[l]._qRadius * 2 + 1; + + qVecSize *= qVecSize; + + for (int i = 0; i < _qLayers[l]._qWeights.size(); i++) { + _qLayers[l]._qWeights[i].resize(qVecSize); + + for (int j = 0; j < qVecSize; j++) + _qLayers[l]._qWeights[i][j] = initWeightDistQ(rng); + } + } + } +} + +void Agent::step(const std::vector > &inputs, ComputeSystem &cs, float reward, bool learn) { + _ph->step(inputs, cs, learn); + + // Capture hidden state + std::vector > hiddenStates(_ph->getNumLayers()); + + for (int l = 0; l < hiddenStates.size(); l++) + hiddenStates[l] = _ph->getLayer(l).getHiddenStates(); + + // Find best action + _hiddenStatesTemp = hiddenStates; + + _qLayers.back()._hiddenErrors = std::vector(_qLayers.back()._hiddenErrors.size(), 1.0f); + _qLayers.back()._hiddenCounts = std::vector(_qLayers.back()._hiddenCounts.size(), 1.0f); + + qBackward(cs); + + // Find highest bits + std::uniform_real_distribution dist01(0.0f, 1.0f); + + for (int a = 0; a < _actions.size(); a++) { + int actionBits = _actionChunkSizes[a] * _actionChunkSizes[a]; + + for (int i = 0; i < _actions[a].size(); i++) { + if (dist01(cs._rng) < _epsilon) { + std::uniform_int_distribution chunkDist(0, actionBits - 1); + + _actions[a][i] = chunkDist(cs._rng); + } + else { + int index = 0; + + for (int j = 1; j < actionBits; j++) + if (_actionErrors[a][i * actionBits + j] / std::max(1.0f, _actionCounts[a][i * actionBits + j]) > _actionErrors[a][i * actionBits + index] / std::max(1.0f, _actionCounts[a][i * actionBits + index])) + index = j; + + _actions[a][i] = index; + } + } + } + + // Find Q values + _actionsTemp = _actions; + + qForward(cs); + + // Get state sample + HistorySample hs; + + hs._actions = _actions; + hs._hiddenStates = hiddenStates; + hs._reward = 0.0f; + + // Updates + if (learn && !_historySamples.empty()) { + std::uniform_int_distribution sampleDist(0, _historySamples.size() - 1); + + float nextQ = 0.0f; + + for (int i = 0; i < _qLayers.back()._hiddenActivations.size(); i++) + nextQ += _qLayers.back()._hiddenActivations[i]; + + nextQ /= _qLayers.back()._hiddenActivations.size(); + + std::vector qTargets(_historySamples.size()); + + _historySamples.front()._reward = reward; + + for (int t = 0; t < qTargets.size(); t++) { + float q = (1.0f - _gamma) * _historySamples[t]._reward + _gamma * nextQ; + + qTargets[t] = q; + + nextQ = q; + } + + for (int iter = 0; iter < _sampleIter; iter++) { + int index = sampleDist(cs._rng); + + // Activate + _hiddenStatesTemp = _historySamples[index]._hiddenStates; + _actionsTemp = _historySamples[index]._actions; + + qForward(cs); + + for (int i = 0; i < _qLayers.back()._hiddenActivations.size(); i++) { + _qLayers.back()._hiddenErrors[i] = qTargets[index] - _qLayers.back()._hiddenActivations[i]; + _qLayers.back()._hiddenCounts[i] = 1.0f; + } + + qBackward(cs); + + qLearn(cs); + } + } + + _historySamples.insert(_historySamples.begin(), hs); + + if (_historySamples.size() > _maxHistorySamples) + _historySamples.resize(_maxHistorySamples); +} \ No newline at end of file diff --git a/source/eogmaneo/Agent.h b/source/eogmaneo/Agent.h new file mode 100644 index 0000000..bfffc8d --- /dev/null +++ b/source/eogmaneo/Agent.h @@ -0,0 +1,177 @@ +// ---------------------------------------------------------------------------- +// EOgmaNeo +// Copyright(c) 2017 Ogma Intelligent Systems Corp. All rights reserved. +// +// This copy of EOgmaNeo is licensed to you under the terms described +// in the EOGMANEO_LICENSE.md file included in this distribution. +// ---------------------------------------------------------------------------- + +#pragma once + +#include "Hierarchy.h" + +namespace eogmaneo { + /*! + \brief Parameters for a Q layer. + Used during construction of an agent. + */ + struct QLayerDesc { + /*! + \brief Q radius + */ + int _qRadius; + + /*! + \brief Initialize defaults. + */ + QLayerDesc() + : _qRadius(12) + {} + }; + + /*! + \brief Q layer. + */ + struct QLayer { + std::vector > _qWeights; + + std::vector _hiddenActivations; + + std::vector _hiddenErrors; + std::vector _hiddenCounts; + }; + + /*! + \brief Forward work item, for internal use only. + */ + class QForwardWorkItem : public WorkItem { + public: + class Agent* _pAgent; + + int _l; + int _hiddenChunkIndex; + + std::mt19937 _rng; + + QForwardWorkItem() + : _pAgent(nullptr) + {} + + void run(size_t threadIndex) override; + }; + + /*! + \brief Backward work item, for internal use only. + */ + class QBackwardWorkItem : public WorkItem { + public: + class Agent* _pAgent; + + int _l; + int _hiddenChunkIndex; + + std::mt19937 _rng; + + QBackwardWorkItem() + : _pAgent(nullptr) + {} + + void run(size_t threadIndex) override; + }; + + /*! + \brief Learn work item, for internal use only. + */ + class QLearnWorkItem : public WorkItem { + public: + class Agent* _pAgent; + + int _l; + int _hiddenChunkIndex; + + std::mt19937 _rng; + + QLearnWorkItem() + : _pAgent(nullptr) + {} + + void run(size_t threadIndex) override; + }; + + /*! + \brief History sample. + */ + struct HistorySample { + std::vector > _actions; + std::vector > _hiddenStates; + + float _reward; + }; + + /*! + \brief An agent, attached to a hierarchy. + */ + class Agent { + private: + Hierarchy* _ph; + + std::vector _historySamples; + + std::vector > _actionSizes; + std::vector _actionChunkSizes; + + std::vector > _actionErrors; + std::vector > _actionCounts; + + std::vector > _actions; + + std::vector _qLayerDescs; + std::vector _qLayers; + + std::vector > _hiddenStatesTemp; + std::vector > _actionsTemp; + + void qForward(ComputeSystem &cs); + void qBackward(ComputeSystem &cs); + void qLearn(ComputeSystem &cs); + + public: + int _maxHistorySamples; + int _sampleIter; + float _alpha; + float _gamma; + float _epsilon; + + /*! + \brief Initialize defaults. + */ + Agent() + : _maxHistorySamples(60), _sampleIter(5), + _alpha(0.01f), _gamma(0.95f), _epsilon(0.02f) + {} + + /*! + \brief Create the agent. + \param qLayerDescs vector of QLayerDesc structures, describing each Q layer in sequence. + \param seed random number generator seed for generating the hierarchy. + */ + void create(Hierarchy* ph, const std::vector > &actionSizes, const std::vector &actionChunkSizes, const std::vector &qLayerDescs, unsigned long seed); + + /*! + \brief Simulation tick. + \param inputs vector of SDR vectors in chunked format. + \param cs compute system to be used. + \param reward reinforcement signal, defaults to 0. + \param learn whether learning should be enabled, defaults to true. + */ + void step(const std::vector > &inputs, ComputeSystem &cs, float reward, bool learn = true); + + const std::vector &getActions(int i) const { + return _actions[i]; + } + + friend class QForwardWorkItem; + friend class QBackwardWorkItem; + friend class QLearnWorkItem; + }; +} diff --git a/source/eogmaneo/ComputeSystem.cpp b/source/eogmaneo/ComputeSystem.cpp deleted file mode 100644 index 1efb0fa..0000000 --- a/source/eogmaneo/ComputeSystem.cpp +++ /dev/null @@ -1,11 +0,0 @@ -// ---------------------------------------------------------------------------- -// EOgmaNeo -// Copyright(c) 2017 Ogma Intelligent Systems Corp. All rights reserved. -// -// This copy of EOgmaNeo is licensed to you under the terms described -// in the EOGMANEO_LICENSE.md file included in this distribution. -// ---------------------------------------------------------------------------- - -#include "ComputeSystem.h" - -using namespace eogmaneo; diff --git a/source/eogmaneo/ComputeSystem.h b/source/eogmaneo/ComputeSystem.h index f9c7f28..1ed14a0 100644 --- a/source/eogmaneo/ComputeSystem.h +++ b/source/eogmaneo/ComputeSystem.h @@ -36,12 +36,10 @@ namespace eogmaneo { friend class Layer; friend class Hierarchy; + friend class Agent; + friend class KMeansEncoder; friend class ImageEncoder; - friend class RandomEncoder; - friend class GaborEncoder; - friend class CornerEncoder; - friend class LocalRegressor; - + friend std::vector whiten(const std::vector &src, int width, int radius, float strength, ComputeSystem &cs, int chunkSize); friend std::vector sobel(const std::vector &src, int width, float clip, ComputeSystem &cs, int chunkSize); }; diff --git a/source/eogmaneo/Hierarchy.cpp b/source/eogmaneo/Hierarchy.cpp index 4958a6f..8e1b0a4 100644 --- a/source/eogmaneo/Hierarchy.cpp +++ b/source/eogmaneo/Hierarchy.cpp @@ -10,11 +10,12 @@ #include #include +#include #include using namespace eogmaneo; -void Hierarchy::create(const std::vector> &inputSizes, const std::vector &inputChunkSizes, const std::vector &predictInputs, const std::vector &layerDescs, unsigned long seed) { +void Hierarchy::create(const std::vector > &inputSizes, const std::vector &inputChunkSizes, const std::vector &predictInputs, const std::vector &layerDescs, unsigned long seed) { std::mt19937 rng(seed); _layers.resize(layerDescs.size()); @@ -22,33 +23,25 @@ void Hierarchy::create(const std::vector> &inputSizes, const _ticks.assign(layerDescs.size(), 0); _histories.resize(layerDescs.size()); - + _ticksPerUpdate.resize(layerDescs.size()); - _alphas.resize(layerDescs.size()); + _alphas.resize(layerDescs.size()); _betas.resize(layerDescs.size()); - _deltas.resize(layerDescs.size()); _gammas.resize(layerDescs.size()); - _traceCutoffs.resize(layerDescs.size()); - _epsilons.resize(layerDescs.size()); - - _rewardSums.resize(layerDescs.size(), 0.0f); - _rewardCounts.resize(layerDescs.size(), 0.0f); _inputTemporalHorizon = layerDescs.front()._temporalHorizon; _numInputs = inputSizes.size(); + for (int l = 0; l < layerDescs.size(); l++) + _ticksPerUpdate[l] = l == 0 ? 1 : layerDescs[l]._ticksPerUpdate; // First layer always 1 + for (int l = 0; l < layerDescs.size(); l++) { _histories[l].resize(l == 0 ? inputSizes.size() * layerDescs[l]._temporalHorizon : layerDescs[l]._temporalHorizon); - _ticksPerUpdate[l] = l == 0 ? 1 : layerDescs[l]._ticksPerUpdate; // First layer always 1 - _alphas[l] = layerDescs[l]._alpha; _betas[l] = layerDescs[l]._beta; - _deltas[l] = layerDescs[l]._delta; _gammas[l] = layerDescs[l]._gamma; - _traceCutoffs[l] = layerDescs[l]._traceCutoff; - _epsilons[l] = layerDescs[l]._epsilon; std::vector visibleLayerDescs; @@ -62,9 +55,8 @@ void Hierarchy::create(const std::vector> &inputSizes, const visibleLayerDescs[index]._width = std::get<0>(inputSizes[i]); visibleLayerDescs[index]._height = std::get<1>(inputSizes[i]); visibleLayerDescs[index]._chunkSize = inputChunkSizes[i]; - visibleLayerDescs[index]._forwardRadius = layerDescs[l]._forwardRadius; - visibleLayerDescs[index]._backwardRadius = layerDescs[l]._backwardRadius; - visibleLayerDescs[index]._predict = predictInputs[i] && t == 0; + visibleLayerDescs[index]._radius = layerDescs[l]._radius; + visibleLayerDescs[index]._predict = t == 0 && predictInputs[i]; } } @@ -82,8 +74,7 @@ void Hierarchy::create(const std::vector> &inputSizes, const visibleLayerDescs[t]._width = layerDescs[l - 1]._width; visibleLayerDescs[t]._height = layerDescs[l - 1]._height; visibleLayerDescs[t]._chunkSize = layerDescs[l - 1]._chunkSize; - visibleLayerDescs[t]._forwardRadius = layerDescs[l]._forwardRadius; - visibleLayerDescs[t]._backwardRadius = layerDescs[l]._backwardRadius; + visibleLayerDescs[t]._radius = layerDescs[l]._radius; visibleLayerDescs[t]._predict = t < _ticksPerUpdate[l]; } @@ -91,7 +82,7 @@ void Hierarchy::create(const std::vector> &inputSizes, const _histories[l][v].resize((layerDescs[l - 1]._width / layerDescs[l - 1]._chunkSize) * (layerDescs[l - 1]._height / layerDescs[l - 1]._chunkSize), 0); } - _layers[l].create(layerDescs[l]._width, layerDescs[l]._height, layerDescs[l]._chunkSize, l < layerDescs.size() - 1, visibleLayerDescs, seed + l + 1); + _layers[l].create(layerDescs[l]._width, layerDescs[l]._height, layerDescs[l]._chunkSize, l < layerDescs.size() - 1 ? 2 : 1, visibleLayerDescs, seed + l + 1); } } @@ -114,13 +105,7 @@ bool Hierarchy::load(const std::string &fileName) { _alphas.resize(numLayers); _betas.resize(numLayers); - _deltas.resize(numLayers); _gammas.resize(numLayers); - _traceCutoffs.resize(numLayers); - _epsilons.resize(numLayers); - - _rewardSums.resize(numLayers); - _rewardCounts.resize(numLayers); s >> _inputTemporalHorizon; s >> _numInputs; @@ -145,7 +130,7 @@ bool Hierarchy::load(const std::string &fileName) { _ticksPerUpdate[l] = l == 0 ? 1 : ticksPerUpdate; // First layer always 1 - s >> _alphas[l] >> _betas[l] >> _deltas[l] >> _gammas[l] >> _traceCutoffs[l] >> _epsilons[l] >> _rewardSums[l] >> _rewardCounts[l]; + s >> _alphas[l] >> _betas[l] >> _gammas[l]; _layers[l].createFromStream(s); @@ -186,17 +171,11 @@ void Hierarchy::save(const std::string &fileName) { s << temporalHorizon << " " << _ticksPerUpdate[l] << std::endl; - s << _alphas[l] << " " << _betas[l] << " " << _deltas[l] << " " << _gammas[l] << " " << _traceCutoffs[l] << " " << _epsilons[l] << " " << _rewardSums[l] << " " << _rewardCounts[l] << std::endl; + s << _alphas[l] << " " << _betas[l] << " " << _gammas[l] << std::endl; _layers[l].writeToStream(s); for (int v = 0; v < _histories[l].size(); v++) { - int w = _layers[l].getVisibleLayerDesc(v)._width; - int h = _layers[l].getVisibleLayerDesc(v)._height; - int c = _layers[l].getVisibleLayerDesc(v)._chunkSize; - - _histories[l][v].resize((w / c) * (h / c), 0); - for (int i = 0; i < _histories[l][v].size(); i++) s << _histories[l][v][i] << " "; @@ -205,7 +184,7 @@ void Hierarchy::save(const std::string &fileName) { } } -void Hierarchy::step(const std::vector> &inputs, ComputeSystem &cs, bool learn, float reward) { +void Hierarchy::step(const std::vector> &inputs, ComputeSystem &cs, bool learn) { assert(inputs.size() == _numInputs); _ticks[0] = 0; @@ -223,18 +202,15 @@ void Hierarchy::step(const std::vector> &inputs, ComputeSystem _histories.front()[0 + temporalHorizon * in] = inputs[in]; } - std::vector update(_layers.size(), false); + std::vector updates(_layers.size(), false); for (int l = 0; l < _layers.size(); l++) { - _rewardSums[l] += reward; - _rewardCounts[l] += 1.0f; - if (l == 0 || _ticks[l] >= _ticksPerUpdate[l]) { _ticks[l] = 0; - update[l] = true; - - _layers[l].forward(_histories[l], cs, learn ? _alphas[l] : 0.0f); + updates[l] = true; + + _layers[l].forward(_histories[l], cs, learn ? _alphas[l] : 0.0f, learn ? _gammas[l] : 1.0f); // Add to next layer's history if (l < _layers.size() - 1) { @@ -254,20 +230,15 @@ void Hierarchy::step(const std::vector> &inputs, ComputeSystem // Backward for (int l = _layers.size() - 1; l >= 0; l--) { - if (update[l]) { + if (updates[l]) { std::vector> feedBack; if (l < _layers.size() - 1) feedBack = { _layers[l]._hiddenStates, _layers[l + 1]._predictions[_ticksPerUpdate[l + 1] - 1 - _ticks[l + 1]] }; else feedBack = { _layers[l]._hiddenStates }; - - float r = _rewardSums[l] / std::max(1.0f, _rewardCounts[l]); - - _layers[l].backward(feedBack, cs, r, learn ? _betas[l] : 0.0f, learn ? _deltas[l] : 0.0f, _gammas[l], _traceCutoffs[l], learn ? _epsilons[l] : 0.0f); - - _rewardSums[l] = 0.0f; - _rewardCounts[l] = 0.0f; + + _layers[l].backward(feedBack, cs, learn ? _betas[l] : 0.0f); } } } diff --git a/source/eogmaneo/Hierarchy.h b/source/eogmaneo/Hierarchy.h index b7e11f1..9609ed8 100644 --- a/source/eogmaneo/Hierarchy.h +++ b/source/eogmaneo/Hierarchy.h @@ -30,13 +30,10 @@ namespace eogmaneo { */ int _chunkSize; - //!@{ /*! - \brief Radii of forward and backward sparse weight matrices. + \brief Radius of forward and backward sparse weight matrices. */ - int _forwardRadius; - int _backwardRadius; - //!@} + int _radius; /*! \brief Number of ticks a layer takes to update (relative to previous layer). @@ -59,33 +56,18 @@ namespace eogmaneo { float _beta; /*! - \brief Q learning rate (decoder). - */ - float _delta; - - /*! - \brief Q discount factor (decoder). + \brief Solidification decay. */ float _gamma; - /*! - \brief Q trace cutoff value (minimum trace strength). - */ - float _traceCutoff; - - /*! - \brief Q exploration rate (decoder). - */ - float _epsilon; - /*! \brief Initialize defaults. */ LayerDesc() : _width(36), _height(36), _chunkSize(6), - _forwardRadius(9), _backwardRadius(9), + _radius(9), _ticksPerUpdate(2), _temporalHorizon(2), - _alpha(0.01f), _beta(0.05f), _delta(0.0f), _gamma(0.99f), _traceCutoff(0.01f), _epsilon(0.01f) + _alpha(0.5f), _beta(0.1f), _gamma(0.0f) {} }; @@ -98,15 +80,9 @@ namespace eogmaneo { std::vector > > _histories; - std::vector _alphas; + std::vector _alphas; std::vector _betas; - std::vector _deltas; std::vector _gammas; - std::vector _traceCutoffs; - std::vector _epsilons; - - std::vector _rewardSums; - std::vector _rewardCounts; std::vector _ticks; std::vector _ticksPerUpdate; @@ -119,7 +95,7 @@ namespace eogmaneo { \brief Create the hierarchy. \param inputSizes vector of input dimension tuples. \param inputChunkSizes vector of input chunk sizes (diameters). - \param predictInputs vector of booleans for which inputs should be predicted. + \param predictInputs flags for which inputs to generate predictions for. \param layerDescs vector of LayerDesc structures, describing each layer in sequence. \param seed random number generator seed for generating the hierarchy. */ @@ -144,7 +120,7 @@ namespace eogmaneo { \param learn whether learning should be enabled, defaults to true. \param reward reinforcement signal, defaults to 0. */ - void step(const std::vector > &inputs, ComputeSystem &cs, bool learn = true, float reward = 0.0f); + void step(const std::vector > &inputs, ComputeSystem &cs, bool learn = true); /*! \brief Get the number of (hidden) layers. @@ -162,7 +138,7 @@ namespace eogmaneo { return _layers.front()._predictions[index]; } - + //!@{ /*! \brief Accessors for layer parameters. @@ -174,18 +150,6 @@ namespace eogmaneo { float getBeta(int l) const { return _betas[l]; } - - float getDelta(int l) const { - return _deltas[l]; - } - - float getGamma(int l) const { - return _gammas[l]; - } - - float getEpsilon(int l) const { - return _epsilons[l]; - } //!@} /*! @@ -195,6 +159,13 @@ namespace eogmaneo { return _ticks[l]; } + /*! + \brief Get layer ticks per update, relative to previous layer. + */ + int getTicksPerUpdate(int l) const { + return _ticksPerUpdate[l]; + } + /*! \brief Get history of a layer's input. */ diff --git a/source/eogmaneo/Layer.cpp b/source/eogmaneo/Layer.cpp index d6fff74..5c58fe3 100644 --- a/source/eogmaneo/Layer.cpp +++ b/source/eogmaneo/Layer.cpp @@ -30,12 +30,20 @@ void ForwardWorkItem::run(size_t threadIndex) { int hiddenChunkX = _hiddenChunkIndex % hiddenChunksInX; int hiddenChunkY = _hiddenChunkIndex / hiddenChunksInX; - int statePrev = _pLayer->_hiddenStatesPrev[_hiddenChunkIndex]; - // Extract input views - std::vector chunkActivations(hiddenChunkSize * hiddenChunkSize, 0.0f); + int hiddenStatePrev = _pLayer->_hiddenStates[_hiddenChunkIndex]; + + int dhxPrev = hiddenStatePrev % hiddenChunkSize; + int dhyPrev = hiddenStatePrev / hiddenChunkSize; + + int hIndexPrev = (hiddenChunkX * hiddenChunkSize + dhxPrev) + (hiddenChunkY * hiddenChunkSize + dhyPrev) * _pLayer->_hiddenWidth; + + std::vector chunkPredictions(hiddenChunkSize * hiddenChunkSize, 0.0f); for (int v = 0; v < _pLayer->_visibleLayerDescs.size(); v++) { + if (!_pLayer->_visibleLayerDescs[v]._predict) + continue; + int visibleChunkSize = _pLayer->_visibleLayerDescs[v]._chunkSize; int visibleChunksInX = _pLayer->_visibleLayerDescs[v]._width / visibleChunkSize; @@ -50,7 +58,7 @@ void ForwardWorkItem::run(size_t threadIndex) { int visibleCenterX = (visibleChunkCenterX + 0.5f) * visibleChunkSize; int visibleCenterY = (visibleChunkCenterY + 0.5f) * visibleChunkSize; - int spatialVisibleRadius = _pLayer->_visibleLayerDescs[v]._forwardRadius; + int spatialVisibleRadius = _pLayer->_visibleLayerDescs[v]._radius; int spatialVisibleDiam = spatialVisibleRadius * 2 + 1; @@ -79,307 +87,327 @@ void ForwardWorkItem::run(size_t threadIndex) { int vy = cy * visibleChunkSize + mdy; if (vx >= lowerVisibleX && vx <= upperVisibleX && vy >= lowerVisibleY && vy <= upperVisibleY) { - for (int c = 0; c < chunkActivations.size(); c++) { + for (int c = 0; c < chunkPredictions.size(); c++) { int dhx = c % hiddenChunkSize; int dhy = c / hiddenChunkSize; int hIndex = (hiddenChunkX * hiddenChunkSize + dhx) + (hiddenChunkY * hiddenChunkSize + dhy) * _pLayer->_hiddenWidth; int i = v + _pLayer->_visibleLayerDescs.size() * hIndex; - + int wi = (vx - lowerVisibleX) + (vy - lowerVisibleY) * spatialVisibleDiam; - chunkActivations[c] += _pLayer->_feedForwardWeights[i][wi]; - } + chunkPredictions[c] += _pLayer->_predictionWeights.front()[i][wi]; + } } } } } - + // Find max element - int maxHiddenIndex = 0; + int predHiddenIndex = 0; - for (int c = 1; c < chunkActivations.size(); c++) { - if (chunkActivations[c] > chunkActivations[maxHiddenIndex]) - maxHiddenIndex = c; + for (int c = 1; c < chunkPredictions.size(); c++) { + if (chunkPredictions[c] > chunkPredictions[predHiddenIndex]) + predHiddenIndex = c; } - _pLayer->_hiddenStates[_hiddenChunkIndex] = maxHiddenIndex; + float learn = hiddenStatePrev != predHiddenIndex ? _pLayer->_alpha : 0.0f; - if (_pLayer->_alpha != 0.0f) { - int hIndexMax; + std::vector chunkActivations(hiddenChunkSize * hiddenChunkSize, 0.0f); - { - int c = maxHiddenIndex; - - int dhx = c % hiddenChunkSize; - int dhy = c / hiddenChunkSize; + for (int v = 0; v < _pLayer->_visibleLayerDescs.size(); v++) { + int visibleChunkSize = _pLayer->_visibleLayerDescs[v]._chunkSize; - hIndexMax = (hiddenChunkX * hiddenChunkSize + dhx) + (hiddenChunkY * hiddenChunkSize + dhy) * _pLayer->_hiddenWidth; - } + int visibleChunksInX = _pLayer->_visibleLayerDescs[v]._width / visibleChunkSize; + int visibleChunksInY = _pLayer->_visibleLayerDescs[v]._height / visibleChunkSize; - for (int v = 0; v < _pLayer->_visibleLayerDescs.size(); v++) { - int visibleChunkSize = _pLayer->_visibleLayerDescs[v]._chunkSize; + float toInputX = static_cast(visibleChunksInX) / static_cast(hiddenChunksInX); + float toInputY = static_cast(visibleChunksInY) / static_cast(hiddenChunksInY); - int visibleChunksInX = _pLayer->_visibleLayerDescs[v]._width / visibleChunkSize; - int visibleChunksInY = _pLayer->_visibleLayerDescs[v]._height / visibleChunkSize; + int visibleChunkCenterX = (hiddenChunkX + 0.5f) * toInputX; + int visibleChunkCenterY = (hiddenChunkY + 0.5f) * toInputY; - float toInputX = static_cast(visibleChunksInX) / static_cast(hiddenChunksInX); - float toInputY = static_cast(visibleChunksInY) / static_cast(hiddenChunksInY); + int visibleCenterX = (visibleChunkCenterX + 0.5f) * visibleChunkSize; + int visibleCenterY = (visibleChunkCenterY + 0.5f) * visibleChunkSize; - int visibleChunkCenterX = (hiddenChunkX + 0.5f) * toInputX; - int visibleChunkCenterY = (hiddenChunkY + 0.5f) * toInputY; + int spatialVisibleRadius = _pLayer->_visibleLayerDescs[v]._radius; - int visibleCenterX = (visibleChunkCenterX + 0.5f) * visibleChunkSize; - int visibleCenterY = (visibleChunkCenterY + 0.5f) * visibleChunkSize; + int spatialVisibleDiam = spatialVisibleRadius * 2 + 1; - int spatialVisibleRadius = _pLayer->_visibleLayerDescs[v]._forwardRadius; + int spatialChunkRadius = std::ceil(static_cast(spatialVisibleRadius) / static_cast(visibleChunkSize)); - int spatialVisibleDiam = spatialVisibleRadius * 2 + 1; + int lowerVisibleX = visibleCenterX - spatialVisibleRadius; + int lowerVisibleY = visibleCenterY - spatialVisibleRadius; - int spatialChunkRadius = std::ceil(static_cast(spatialVisibleRadius) / static_cast(visibleChunkSize)); + int upperVisibleX = visibleCenterX + spatialVisibleRadius; + int upperVisibleY = visibleCenterY + spatialVisibleRadius; - int lowerVisibleX = visibleCenterX - spatialVisibleRadius; - int lowerVisibleY = visibleCenterY - spatialVisibleRadius; + int iPrev = v + _pLayer->_visibleLayerDescs.size() * hIndexPrev; - int upperVisibleX = visibleCenterX + spatialVisibleRadius; - int upperVisibleY = visibleCenterY + spatialVisibleRadius; + for (int dcx = -spatialChunkRadius; dcx <= spatialChunkRadius; dcx++) + for (int dcy = -spatialChunkRadius; dcy <= spatialChunkRadius; dcy++) { + int cx = visibleChunkCenterX + dcx; + int cy = visibleChunkCenterY + dcy; - int i = v + _pLayer->_visibleLayerDescs.size() * hIndexMax; + if (cx >= 0 && cx < visibleChunksInX && cy >= 0 && cy < visibleChunksInY) { + int visibleChunkIndex = cx + cy * visibleChunksInX; - for (int dcx = -spatialChunkRadius; dcx <= spatialChunkRadius; dcx++) - for (int dcy = -spatialChunkRadius; dcy <= spatialChunkRadius; dcy++) { - int cx = visibleChunkCenterX + dcx; - int cy = visibleChunkCenterY + dcy; + int maxIndex = _pLayer->_inputs[v][visibleChunkIndex]; + int maxIndexPrev = _pLayer->_inputsPrev[v][visibleChunkIndex]; - if (cx >= 0 && cx < visibleChunksInX && cy >= 0 && cy < visibleChunksInY) { - int visibleChunkIndex = cx + cy * visibleChunksInX; + for (int dvx = 0; dvx < visibleChunkSize; dvx++) + for (int dvy = 0; dvy < visibleChunkSize; dvy++) { + int index = dvx + dvy * visibleChunkSize; - int maxIndex = _pLayer->_inputs[v][visibleChunkIndex]; - - // Reduce weight on all 0 inputs - for (int dvx = 0; dvx < visibleChunkSize; dvx++) - for (int dvy = 0; dvy < visibleChunkSize; dvy++) { - int index = dvx + dvy * visibleChunkSize; + int ovx = cx * visibleChunkSize + dvx; + int ovy = cy * visibleChunkSize + dvy; - if (index != maxIndex) { - int ovx = cx * visibleChunkSize + dvx; - int ovy = cy * visibleChunkSize + dvy; - - if (ovx >= lowerVisibleX && ovx <= upperVisibleX && ovy >= lowerVisibleY && ovy <= upperVisibleY) { - int wi = (ovx - lowerVisibleX) + (ovy - lowerVisibleY) * spatialVisibleDiam; + if (ovx >= lowerVisibleX && ovx <= upperVisibleX && ovy >= lowerVisibleY && ovy <= upperVisibleY) { + int wi = (ovx - lowerVisibleX) + (ovy - lowerVisibleY) * spatialVisibleDiam; - _pLayer->_feedForwardWeights[i][wi] = std::max(0.0f, _pLayer->_feedForwardWeights[i][wi] - _pLayer->_alpha); - } - } + int vIndex = ovx + ovy * _pLayer->_visibleLayerDescs[v]._width; + + float target = index == maxIndexPrev ? 1.0f : 0.0f; + + float recon = std::min(1.0f, 1.0f + std::tanh(_pLayer->_reconActivationsPrev[v][vIndex] / std::max(1.0f, _pLayer->_reconCountsPrev[v][vIndex]))); + + _pLayer->_feedForwardWeights[iPrev][wi] += learn * (target - recon); } - } - } - } - } -} + } -void BackwardWorkItem::run(size_t threadIndex) { - assert(_pLayer != nullptr); + int mdx = maxIndex % visibleChunkSize; + int mdy = maxIndex / visibleChunkSize; - int v = _visibleLayerIndex; - - if (!_pLayer->_visibleLayerDescs[v]._predict) - return; + int vx = cx * visibleChunkSize + mdx; + int vy = cy * visibleChunkSize + mdy; - int visibleWidth = _pLayer->_visibleLayerDescs[v]._width; - int visibleHeight = _pLayer->_visibleLayerDescs[v]._height; + if (vx >= lowerVisibleX && vx <= upperVisibleX && vy >= lowerVisibleY && vy <= upperVisibleY) { + for (int c = 0; c < chunkActivations.size(); c++) { + int dhx = c % hiddenChunkSize; + int dhy = c / hiddenChunkSize; - int visibleChunkSize = _pLayer->_visibleLayerDescs[v]._chunkSize; + int hIndex = (hiddenChunkX * hiddenChunkSize + dhx) + (hiddenChunkY * hiddenChunkSize + dhy) * _pLayer->_hiddenWidth; - int hiddenChunkSize = _pLayer->_chunkSize; + int i = v + _pLayer->_visibleLayerDescs.size() * hIndex; - int visibleChunksInX = visibleWidth / visibleChunkSize; - int visibleChunksInY = visibleHeight / visibleChunkSize; + int wi = (vx - lowerVisibleX) + (vy - lowerVisibleY) * spatialVisibleDiam; - int visibleChunkX = _visibleChunkIndex % visibleChunksInX; - int visibleChunkY = _visibleChunkIndex / visibleChunksInX; + chunkActivations[c] += _pLayer->_feedForwardWeights[i][wi]; + } + } + } + } + } - int hiddenChunksInX = _pLayer->_hiddenWidth / hiddenChunkSize; - int hiddenChunksInY = _pLayer->_hiddenHeight / hiddenChunkSize; + // Find max element + int maxHiddenIndex = 0; - // Extract input views - std::vector chunkActivations(visibleChunkSize * visibleChunkSize, 0.0f); + for (int c = 1; c < chunkActivations.size(); c++) { + if (chunkActivations[c] > chunkActivations[maxHiddenIndex]) + maxHiddenIndex = c; + } - int spatialHiddenRadius = _pLayer->_visibleLayerDescs[v]._backwardRadius; + _pLayer->_hiddenStates[_hiddenChunkIndex] = maxHiddenIndex; - int spatialHiddenDiam = spatialHiddenRadius * 2 + 1; + // Reconstruct + int dhx = maxHiddenIndex % hiddenChunkSize; + int dhy = maxHiddenIndex / hiddenChunkSize; - int spatialChunkRadius = std::ceil(static_cast(spatialHiddenRadius) / static_cast(hiddenChunkSize)); + int hIndex = (hiddenChunkX * hiddenChunkSize + dhx) + (hiddenChunkY * hiddenChunkSize + dhy) * _pLayer->_hiddenWidth; - float toInputX = static_cast(hiddenChunksInX) / static_cast(visibleChunksInX); - float toInputY = static_cast(hiddenChunksInY) / static_cast(visibleChunksInY); + for (int v = 0; v < _pLayer->_visibleLayerDescs.size(); v++) { + int visibleChunkSize = _pLayer->_visibleLayerDescs[v]._chunkSize; - int hiddenChunkCenterX = (visibleChunkX + 0.5f) * toInputX; - int hiddenChunkCenterY = (visibleChunkY + 0.5f) * toInputY; + int visibleChunksInX = _pLayer->_visibleLayerDescs[v]._width / visibleChunkSize; + int visibleChunksInY = _pLayer->_visibleLayerDescs[v]._height / visibleChunkSize; - int hiddenCenterX = (hiddenChunkCenterX + 0.5f) * hiddenChunkSize; - int hiddenCenterY = (hiddenChunkCenterY + 0.5f) * hiddenChunkSize; + float toInputX = static_cast(visibleChunksInX) / static_cast(hiddenChunksInX); + float toInputY = static_cast(visibleChunksInY) / static_cast(hiddenChunksInY); - int lowerHiddenX = hiddenCenterX - spatialHiddenRadius; - int lowerHiddenY = hiddenCenterY - spatialHiddenRadius; + int visibleChunkCenterX = (hiddenChunkX + 0.5f) * toInputX; + int visibleChunkCenterY = (hiddenChunkY + 0.5f) * toInputY; - int upperHiddenX = hiddenCenterX + spatialHiddenRadius; - int upperHiddenY = hiddenCenterY + spatialHiddenRadius; + int visibleCenterX = (visibleChunkCenterX + 0.5f) * visibleChunkSize; + int visibleCenterY = (visibleChunkCenterY + 0.5f) * visibleChunkSize; - // For each feedback layer - for (int dcx = -spatialChunkRadius; dcx <= spatialChunkRadius; dcx++) - for (int dcy = -spatialChunkRadius; dcy <= spatialChunkRadius; dcy++) { - int cx = hiddenChunkCenterX + dcx; - int cy = hiddenChunkCenterY + dcy; + int spatialVisibleRadius = _pLayer->_visibleLayerDescs[v]._radius; - if (cx >= 0 && cx < hiddenChunksInX && cy >= 0 && cy < hiddenChunksInY) { - int hiddenChunkIndex = cx + cy * hiddenChunksInX; + int spatialVisibleDiam = spatialVisibleRadius * 2 + 1; - for (int f = 0; f < _pLayer->_feedBack.size(); f++) { - int maxIndex = _pLayer->_feedBack[f][hiddenChunkIndex]; - int maxIndexPrev = _pLayer->_feedBackPrev[f][hiddenChunkIndex]; + int spatialChunkRadius = std::ceil(static_cast(spatialVisibleRadius) / static_cast(visibleChunkSize)); - int mdx = maxIndex % hiddenChunkSize; - int mdy = maxIndex / hiddenChunkSize; + int lowerVisibleX = visibleCenterX - spatialVisibleRadius; + int lowerVisibleY = visibleCenterY - spatialVisibleRadius; - int mdxPrev = maxIndexPrev % hiddenChunkSize; - int mdyPrev = maxIndexPrev / hiddenChunkSize; - - int hx = cx * hiddenChunkSize + mdx; - int hy = cy * hiddenChunkSize + mdy; + int upperVisibleX = visibleCenterX + spatialVisibleRadius; + int upperVisibleY = visibleCenterY + spatialVisibleRadius; - int hxPrev = cx * hiddenChunkSize + mdxPrev; - int hyPrev = cy * hiddenChunkSize + mdyPrev; - - if (hx >= lowerHiddenX && hx <= upperHiddenX && hy >= lowerHiddenY && hy <= upperHiddenY) { - for (int c = 0; c < chunkActivations.size(); c++) { - int dvx = c % visibleChunkSize; - int dvy = c / visibleChunkSize; + int i = v + _pLayer->_visibleLayerDescs.size() * hIndex; + + for (int dcx = -spatialChunkRadius; dcx <= spatialChunkRadius; dcx++) + for (int dcy = -spatialChunkRadius; dcy <= spatialChunkRadius; dcy++) { + int cx = visibleChunkCenterX + dcx; + int cy = visibleChunkCenterY + dcy; - int vIndex = (visibleChunkX * visibleChunkSize + dvx) + (visibleChunkY * visibleChunkSize + dvy) * visibleWidth; + if (cx >= 0 && cx < visibleChunksInX && cy >= 0 && cy < visibleChunksInY) { + int visibleChunkIndex = cx + cy * visibleChunksInX; + + for (int dvx = 0; dvx < visibleChunkSize; dvx++) + for (int dvy = 0; dvy < visibleChunkSize; dvy++) { + int index = dvx + dvy * visibleChunkSize; - int i = f + _pLayer->_feedBack.size() * vIndex; + int ovx = cx * visibleChunkSize + dvx; + int ovy = cy * visibleChunkSize + dvy; - int wi = (hx - lowerHiddenX) + (hy - lowerHiddenY) * spatialHiddenDiam; + if (ovx >= lowerVisibleX && ovx <= upperVisibleX && ovy >= lowerVisibleY && ovy <= upperVisibleY) { + int wi = (ovx - lowerVisibleX) + (ovy - lowerVisibleY) * spatialVisibleDiam; - chunkActivations[c] += _pLayer->_feedBackWeights[v][i][wi]; + int vIndex = ovx + ovy * _pLayer->_visibleLayerDescs[v]._width; + + // Reconstruction + _pLayer->_reconActivations[v][vIndex] += _pLayer->_feedForwardWeights[i][wi]; + _pLayer->_reconCounts[v][vIndex] += 1.0f; + } } - } } } - } - - // Find max element - int predMaxIndex = 0; - - for (int c = 0; c < chunkActivations.size(); c++) { - int dvx = c % visibleChunkSize; - int dvy = c / visibleChunkSize; + } +} - int vIndex = (visibleChunkX * visibleChunkSize + dvx) + (visibleChunkY * visibleChunkSize + dvy) * visibleWidth; +void BackwardWorkItem::run(size_t threadIndex) { + assert(_pLayer != nullptr); - _pLayer->_predictionActivations[v][vIndex] = chunkActivations[c]; + int hiddenChunkSize = _pLayer->_chunkSize; - if (chunkActivations[c] > chunkActivations[predMaxIndex]) - predMaxIndex = c; - } - - std::uniform_real_distribution dist01(0.0f, 1.0f); + int hiddenChunksInX = _pLayer->_hiddenWidth / hiddenChunkSize; + int hiddenChunksInY = _pLayer->_hiddenHeight / hiddenChunkSize; - if (_pLayer->_delta != 0.0f && dist01(_rng) < _pLayer->_epsilon) { - std::uniform_int_distribution chunkDist(0, chunkActivations.size() - 1); + int hiddenChunkX = _hiddenChunkIndex % hiddenChunksInX; + int hiddenChunkY = _hiddenChunkIndex / hiddenChunksInX; - _pLayer->_predictions[v][_visibleChunkIndex] = chunkDist(_rng); - } - else - _pLayer->_predictions[v][_visibleChunkIndex] = predMaxIndex; + for (int f = 0; f < _pLayer->_feedBack.size(); f++) { + int dhx = _pLayer->_feedBack[f][_hiddenChunkIndex] % hiddenChunkSize; + int dhy = _pLayer->_feedBack[f][_hiddenChunkIndex] / hiddenChunkSize; - int vIndexPredPrev; + int hIndex = (hiddenChunkX * hiddenChunkSize + dhx) + (hiddenChunkY * hiddenChunkSize + dhy) * _pLayer->_hiddenWidth; - { - int c = _pLayer->_predictionsPrev[v][_visibleChunkIndex]; + int dhxPrev = _pLayer->_feedBackPrev[f][_hiddenChunkIndex] % hiddenChunkSize; + int dhyPrev = _pLayer->_feedBackPrev[f][_hiddenChunkIndex] / hiddenChunkSize; - int dvx = c % visibleChunkSize; - int dvy = c / visibleChunkSize; + int hIndexPrev = (hiddenChunkX * hiddenChunkSize + dhxPrev) + (hiddenChunkY * hiddenChunkSize + dhyPrev) * _pLayer->_hiddenWidth; - vIndexPredPrev = (visibleChunkX * visibleChunkSize + dvx) + (visibleChunkY * visibleChunkSize + dvy) * visibleWidth; - } + for (int v = 0; v < _pLayer->_visibleLayerDescs.size(); v++) { + if (!_pLayer->_visibleLayerDescs[v]._predict) + continue; - for (int dcx = -spatialChunkRadius; dcx <= spatialChunkRadius; dcx++) - for (int dcy = -spatialChunkRadius; dcy <= spatialChunkRadius; dcy++) { - int cx = hiddenChunkCenterX + dcx; - int cy = hiddenChunkCenterY + dcy; + int visibleChunkSize = _pLayer->_visibleLayerDescs[v]._chunkSize; - if (cx >= 0 && cx < hiddenChunksInX && cy >= 0 && cy < hiddenChunksInY) { - int hiddenChunkIndex = cx + cy * hiddenChunksInX; + int visibleChunksInX = _pLayer->_visibleLayerDescs[v]._width / visibleChunkSize; + int visibleChunksInY = _pLayer->_visibleLayerDescs[v]._height / visibleChunkSize; - for (int f = 0; f < _pLayer->_feedBack.size(); f++) { - int maxIndexPrev = _pLayer->_feedBackPrev[f][hiddenChunkIndex]; + float toInputX = static_cast(visibleChunksInX) / static_cast(hiddenChunksInX); + float toInputY = static_cast(visibleChunksInY) / static_cast(hiddenChunksInY); - int mdxPrev = maxIndexPrev % hiddenChunkSize; - int mdyPrev = maxIndexPrev / hiddenChunkSize; + int visibleChunkCenterX = (hiddenChunkX + 0.5f) * toInputX; + int visibleChunkCenterY = (hiddenChunkY + 0.5f) * toInputY; - int hxPrev = cx * hiddenChunkSize + mdxPrev; - int hyPrev = cy * hiddenChunkSize + mdyPrev; - - if (hxPrev >= lowerHiddenX && hxPrev <= upperHiddenX && hyPrev >= lowerHiddenY && hyPrev <= upperHiddenY) { - for (int c = 0; c < chunkActivations.size(); c++) { - int dvx = c % visibleChunkSize; - int dvy = c / visibleChunkSize; + int visibleCenterX = (visibleChunkCenterX + 0.5f) * visibleChunkSize; + int visibleCenterY = (visibleChunkCenterY + 0.5f) * visibleChunkSize; - int vIndex = (visibleChunkX * visibleChunkSize + dvx) + (visibleChunkY * visibleChunkSize + dvy) * visibleWidth; + int spatialVisibleRadius = _pLayer->_visibleLayerDescs[v]._radius; - int i = f + _pLayer->_feedBack.size() * vIndex; + int spatialVisibleDiam = spatialVisibleRadius * 2 + 1; - int wi = (hxPrev - lowerHiddenX) + (hyPrev - lowerHiddenY) * spatialHiddenDiam; + int spatialChunkRadius = std::ceil(static_cast(spatialVisibleRadius) / static_cast(visibleChunkSize)); - float target = c == _pLayer->_inputs[v][_visibleChunkIndex] ? 1.0f : 0.0f; + int lowerVisibleX = visibleCenterX - spatialVisibleRadius; + int lowerVisibleY = visibleCenterY - spatialVisibleRadius; - float error = target - sigmoid(_pLayer->_predictionActivationsPrev[v][vIndex]); + int upperVisibleX = visibleCenterX + spatialVisibleRadius; + int upperVisibleY = visibleCenterY + spatialVisibleRadius; - _pLayer->_feedBackWeights[v][i][wi] += _pLayer->_beta * error; + int i = v + _pLayer->_visibleLayerDescs.size() * hIndex; + int iPrev = v + _pLayer->_visibleLayerDescs.size() * hIndexPrev; - // Spawn a trace - if (c == _pLayer->_predictionsPrev[v][_visibleChunkIndex]) - _pLayer->_feedBackTraces[v][i][wi] = 1.0f; - } + for (int dcx = -spatialChunkRadius; dcx <= spatialChunkRadius; dcx++) + for (int dcy = -spatialChunkRadius; dcy <= spatialChunkRadius; dcy++) { + int cx = visibleChunkCenterX + dcx; + int cy = visibleChunkCenterY + dcy; + + if (cx >= 0 && cx < visibleChunksInX && cy >= 0 && cy < visibleChunksInY) { + int visibleChunkIndex = cx + cy * visibleChunksInX; + + int maxIndex = _pLayer->_inputs[v][visibleChunkIndex]; + + for (int dvx = 0; dvx < visibleChunkSize; dvx++) + for (int dvy = 0; dvy < visibleChunkSize; dvy++) { + int index = dvx + dvy * visibleChunkSize; + + int ovx = cx * visibleChunkSize + dvx; + int ovy = cy * visibleChunkSize + dvy; + + if (ovx >= lowerVisibleX && ovx <= upperVisibleX && ovy >= lowerVisibleY && ovy <= upperVisibleY) { + int wi = (ovx - lowerVisibleX) + (ovy - lowerVisibleY) * spatialVisibleDiam; + + int vIndex = ovx + ovy * _pLayer->_visibleLayerDescs[v]._width; + + float target = index == maxIndex ? 1.0f : 0.0f; + + float recon = std::min(1.0f, 1.0f + std::tanh(_pLayer->_predictionActivationsPrev[v][vIndex])); + + _pLayer->_predictionWeights[f][iPrev][wi] += _pLayer->_beta * (target - recon); + + // Reconstruction + _pLayer->_predictionActivations[v][vIndex] += _pLayer->_predictionWeights[f][i][wi]; + _pLayer->_predictionCounts[v][vIndex] += 1.0f; + } + } } } - } } + } +} - float qNext = chunkActivations[_pLayer->_predictions[v][_visibleChunkIndex]]; +void PredictionWorkItem::run(size_t threadIndex) { + assert(_pLayer != nullptr); + + int v = _visibleLayerIndex; + + int visibleWidth = _pLayer->_visibleLayerDescs[v]._width; + int visibleHeight = _pLayer->_visibleLayerDescs[v]._height; - //float qNextMax = chunkActivations[predMaxIndex]; + int visibleChunkSize = _pLayer->_visibleLayerDescs[v]._chunkSize; + + int visibleChunksInX = visibleWidth / visibleChunkSize; + int visibleChunksInY = visibleHeight / visibleChunkSize; - float tdError = _pLayer->_reward + _pLayer->_gamma * qNext - _pLayer->_predictionActivationsPrev[v][vIndexPredPrev]; + int visibleChunkX = _visibleChunkIndex % visibleChunksInX; + int visibleChunkY = _visibleChunkIndex / visibleChunksInX; - // Update traces - for (int c = 0; c < chunkActivations.size(); c++) { + int visibleBitsPerChunk = visibleChunkSize * visibleChunkSize; + + float maxValue = -99999.0f; + int maxIndex = 0; + + for (int c = 0; c < visibleBitsPerChunk; c++) { int dvx = c % visibleChunkSize; int dvy = c / visibleChunkSize; int vIndex = (visibleChunkX * visibleChunkSize + dvx) + (visibleChunkY * visibleChunkSize + dvy) * visibleWidth; - for (int f = 0; f < _pLayer->_feedBack.size(); f++) { - int i = f + _pLayer->_feedBack.size() * vIndex; - - for (std::unordered_map::iterator it = _pLayer->_feedBackTraces[v][i].begin(); it != _pLayer->_feedBackTraces[v][i].end();) { - _pLayer->_feedBackWeights[v][i][it->first] += _pLayer->_delta * tdError * it->second; - - it->second *= _pLayer->_gamma; + _pLayer->_predictionActivations[v][vIndex] /= std::max(1.0f, _pLayer->_predictionCounts[v][vIndex]); - if (it->second < _pLayer->_traceCutoff) - it = _pLayer->_feedBackTraces[v][i].erase(it); - else - it++; - } + if (_pLayer->_predictionActivations[v][vIndex] > maxValue) { + maxValue = _pLayer->_predictionActivations[v][vIndex]; + maxIndex = c; } } + + _pLayer->_predictions[v][_visibleChunkIndex] = maxIndex; } -void Layer::create(int hiddenWidth, int hiddenHeight, int chunkSize, bool hasFeedBack, const std::vector &visibleLayerDescs, unsigned long seed) { +void Layer::create(int hiddenWidth, int hiddenHeight, int chunkSize, int numFeedBack, const std::vector &visibleLayerDescs, unsigned long seed) { std::mt19937 rng(seed); _hiddenWidth = hiddenWidth; @@ -389,40 +417,22 @@ void Layer::create(int hiddenWidth, int hiddenHeight, int chunkSize, bool hasFee _visibleLayerDescs = visibleLayerDescs; _feedForwardWeights.resize(hiddenWidth * hiddenHeight * visibleLayerDescs.size()); - _feedBackWeights.resize(visibleLayerDescs.size()); - _feedBackTraces.resize(visibleLayerDescs.size()); - _predictionActivations.resize(visibleLayerDescs.size()); - _predictionActivationsPrev.resize(visibleLayerDescs.size()); - - _predictions.resize(visibleLayerDescs.size()); - _predictionsPrev.resize(visibleLayerDescs.size()); - _inputs.resize(visibleLayerDescs.size()); - _inputsPrev.resize(visibleLayerDescs.size()); int hiddenChunksInX = hiddenWidth / chunkSize; int hiddenChunksInY = hiddenHeight / chunkSize; _hiddenStates.resize(hiddenChunksInX * hiddenChunksInY, 0); - _hiddenStatesPrev.resize(hiddenChunksInX * hiddenChunksInY, 0); - int numFeedBack = hasFeedBack ? 2 : 1; + _predictionWeights.resize(numFeedBack); - std::uniform_real_distribution initWeightDistHigh(0.001f, 1.0f); - std::uniform_real_distribution initWeightDistLow(-0.001f, 0.001f); - - for (int v = 0; v < visibleLayerDescs.size(); v++) { - _predictionActivations[v].resize(_visibleLayerDescs[v]._width * _visibleLayerDescs[v]._height, 0.0f); - _predictionActivationsPrev[v].resize(_visibleLayerDescs[v]._width * _visibleLayerDescs[v]._height, 0.0f); - - _predictions[v].resize((_visibleLayerDescs[v]._width / _visibleLayerDescs[v]._chunkSize) * (_visibleLayerDescs[v]._height / _visibleLayerDescs[v]._chunkSize), 0); - _predictionsPrev[v].resize((_visibleLayerDescs[v]._width / _visibleLayerDescs[v]._chunkSize) * (_visibleLayerDescs[v]._height / _visibleLayerDescs[v]._chunkSize), 0); + std::uniform_real_distribution initWeightDist(-0.01f, 0.0f); + for (int v = 0; v < visibleLayerDescs.size(); v++) { _inputs[v].resize((_visibleLayerDescs[v]._width / _visibleLayerDescs[v]._chunkSize) * (_visibleLayerDescs[v]._height / _visibleLayerDescs[v]._chunkSize), 0); - _inputsPrev[v].resize((_visibleLayerDescs[v]._width / _visibleLayerDescs[v]._chunkSize) * (_visibleLayerDescs[v]._height / _visibleLayerDescs[v]._chunkSize), 0); - - int forwardVecSize = _visibleLayerDescs[v]._forwardRadius * 2 + 1; + + int forwardVecSize = _visibleLayerDescs[v]._radius * 2 + 1; forwardVecSize *= forwardVecSize; @@ -435,40 +445,37 @@ void Layer::create(int hiddenWidth, int hiddenHeight, int chunkSize, bool hasFee _feedForwardWeights[i].resize(forwardVecSize); for (int j = 0; j < forwardVecSize; j++) - _feedForwardWeights[i][j] = initWeightDistHigh(rng); + _feedForwardWeights[i][j] = initWeightDist(rng); } - int backwardVecSize = _visibleLayerDescs[v]._backwardRadius * 2 + 1; - - backwardVecSize *= backwardVecSize; - - if (_visibleLayerDescs[v]._predict) { - _feedBackWeights[v].resize(_visibleLayerDescs[v]._width * _visibleLayerDescs[v]._height * numFeedBack); - _feedBackTraces[v].resize(_visibleLayerDescs[v]._width * _visibleLayerDescs[v]._height * numFeedBack); + for (int f = 0; f < numFeedBack; f++) { + _predictionWeights[f].resize(hiddenWidth * hiddenHeight * visibleLayerDescs.size()); - for (int f = 0; f < numFeedBack; f++) { - for (int x = 0; x < visibleLayerDescs[v]._width; x++) - for (int y = 0; y < visibleLayerDescs[v]._height; y++) { - int vIndex = x + y * visibleLayerDescs[v]._width; + if (_visibleLayerDescs[v]._predict) { + for (int x = 0; x < hiddenWidth; x++) + for (int y = 0; y < hiddenHeight; y++) { + int hIndex = x + y * hiddenWidth; - int i = f + numFeedBack * vIndex; + int i = v + visibleLayerDescs.size() * hIndex; - _feedBackWeights[v][i].resize(backwardVecSize); + _predictionWeights[f][i].resize(forwardVecSize); - for (int j = 0; j < backwardVecSize; j++) - _feedBackWeights[v][i][j] = initWeightDistLow(rng); + for (int j = 0; j < forwardVecSize; j++) + _predictionWeights[f][i][j] = initWeightDist(rng); } } } } + _inputsPrev = _inputs; + _predictions = _inputs; + _feedBack.resize(numFeedBack); - _feedBackPrev.resize(numFeedBack); - for (int f = 0; f < numFeedBack; f++) { + for (int f = 0; f < numFeedBack; f++) _feedBack[f].resize(hiddenChunksInX * hiddenChunksInY, 0); - _feedBackPrev[f].resize(hiddenChunksInX * hiddenChunksInY, 0); - } + + _feedBackPrev = _feedBack; } void Layer::createFromStream(std::istream &s) { @@ -481,62 +488,48 @@ void Layer::createFromStream(std::istream &s) { for (int v = 0; v < numVisibleLayerDescs; v++) { s >> _visibleLayerDescs[v]._width >> _visibleLayerDescs[v]._height >> _visibleLayerDescs[v]._chunkSize; - s >> _visibleLayerDescs[v]._forwardRadius >> _visibleLayerDescs[v]._backwardRadius; - s >> _visibleLayerDescs[v]._predict; + s >> _visibleLayerDescs[v]._radius >> _visibleLayerDescs[v]._radius; + + int predict; + s >> predict; + + _visibleLayerDescs[v]._predict = predict; } int numFeedForwardWeightSets; s >> numFeedForwardWeightSets; _feedForwardWeights.resize(numFeedForwardWeightSets); - _feedBackWeights.resize(_visibleLayerDescs.size()); - _feedBackTraces.resize(_visibleLayerDescs.size()); - - _predictionActivations.resize(_visibleLayerDescs.size()); - _predictionActivationsPrev.resize(_visibleLayerDescs.size()); - - _predictions.resize(_visibleLayerDescs.size()); - _predictionsPrev.resize(_visibleLayerDescs.size()); - + _inputs.resize(_visibleLayerDescs.size()); _inputsPrev.resize(_visibleLayerDescs.size()); + _predictions.resize(_visibleLayerDescs.size()); + int hiddenChunksInX = _hiddenWidth / _chunkSize; int hiddenChunksInY = _hiddenHeight / _chunkSize; - _hiddenStates.resize(hiddenChunksInX * hiddenChunksInY, 0); - _hiddenStatesPrev.resize(hiddenChunksInX * hiddenChunksInY, 0); - + _hiddenStates.resize(hiddenChunksInX * hiddenChunksInY); + // Load for (int i = 0; i < _hiddenStates.size(); i++) - s >> _hiddenStates[i] >> _hiddenStatesPrev[i]; + s >> _hiddenStates[i]; - bool hasFeedBack; - s >> hasFeedBack; + int numFeedBack; + s >> numFeedBack; - int numFeedBack = hasFeedBack ? 2 : 1; + _predictionWeights.resize(numFeedBack); for (int v = 0; v < _visibleLayerDescs.size(); v++) { - _predictionActivations[v].resize(_visibleLayerDescs[v]._width * _visibleLayerDescs[v]._height); - _predictionActivationsPrev[v].resize(_visibleLayerDescs[v]._width * _visibleLayerDescs[v]._height); - - _predictions[v].resize((_visibleLayerDescs[v]._width / _visibleLayerDescs[v]._chunkSize) * (_visibleLayerDescs[v]._height / _visibleLayerDescs[v]._chunkSize)); - _predictionsPrev[v].resize((_visibleLayerDescs[v]._width / _visibleLayerDescs[v]._chunkSize) * (_visibleLayerDescs[v]._height / _visibleLayerDescs[v]._chunkSize)); - _inputs[v].resize((_visibleLayerDescs[v]._width / _visibleLayerDescs[v]._chunkSize) * (_visibleLayerDescs[v]._height / _visibleLayerDescs[v]._chunkSize)); - _inputsPrev[v].resize((_visibleLayerDescs[v]._width / _visibleLayerDescs[v]._chunkSize) * (_visibleLayerDescs[v]._height / _visibleLayerDescs[v]._chunkSize)); + _inputsPrev[v].resize(_inputs[v].size()); + _predictions[v].resize(_inputs[v].size()); // Load - for (int i = 0; i < _predictionActivations[v].size(); i++) - s >> _predictionActivations[v][i] >> _predictionActivationsPrev[v][i]; - - for (int i = 0; i < _predictions[v].size(); i++) - s >> _predictions[v][i] >> _predictionsPrev[v][i]; - for (int i = 0; i < _inputs[v].size(); i++) - s >> _inputs[v][i] >> _inputsPrev[v][i]; + s >> _inputs[v][i] >> _inputsPrev[v][i] >> _predictions[v][i]; - int forwardVecSize = _visibleLayerDescs[v]._forwardRadius * 2 + 1; + int forwardVecSize = _visibleLayerDescs[v]._radius * 2 + 1; forwardVecSize *= forwardVecSize; @@ -552,38 +545,20 @@ void Layer::createFromStream(std::istream &s) { s >> _feedForwardWeights[i][j]; } - int backwardVecSize = _visibleLayerDescs[v]._backwardRadius * 2 + 1; - - backwardVecSize *= backwardVecSize; - - if (_visibleLayerDescs[v]._predict) { - _feedBackWeights[v].resize(_visibleLayerDescs[v]._width * _visibleLayerDescs[v]._height * numFeedBack); - _feedBackTraces[v].resize(_visibleLayerDescs[v]._width * _visibleLayerDescs[v]._height * numFeedBack); + for (int f = 0; f < numFeedBack; f++) { + _predictionWeights[f].resize(numFeedForwardWeightSets); - for (int f = 0; f < numFeedBack; f++) { + if (_visibleLayerDescs[v]._predict) { for (int x = 0; x < _visibleLayerDescs[v]._width; x++) for (int y = 0; y < _visibleLayerDescs[v]._height; y++) { - int vIndex = x + y * _visibleLayerDescs[v]._width; - - int i = f + numFeedBack * vIndex; - - _feedBackWeights[v][i].resize(backwardVecSize); - - for (int j = 0; j < backwardVecSize; j++) - s >> _feedBackWeights[v][i][j]; - - int numTraces; - - s >> numTraces; + int hIndex = x + y * _hiddenWidth; - for (int j = 0; j < numTraces; j++) { - int wi; - float strength; + int i = v + _visibleLayerDescs.size() * hIndex; - s >> wi >> strength; + _predictionWeights[f][i].resize(forwardVecSize); - _feedBackTraces[v][i][wi] = strength; - } + for (int j = 0; j < forwardVecSize; j++) + s >> _predictionWeights[f][i][j]; } } } @@ -593,8 +568,8 @@ void Layer::createFromStream(std::istream &s) { _feedBackPrev.resize(numFeedBack); for (int f = 0; f < numFeedBack; f++) { - _feedBack[f].resize(hiddenChunksInX * hiddenChunksInY, 0); - _feedBackPrev[f].resize(hiddenChunksInX * hiddenChunksInY, 0); + _feedBack[f].resize(hiddenChunksInX * hiddenChunksInY); + _feedBackPrev[f].resize(_feedBack[f].size()); // Load for (int i = 0; i < _feedBack[f].size(); i++) @@ -609,8 +584,9 @@ void Layer::writeToStream(std::ostream &s) { for (int v = 0; v < _visibleLayerDescs.size(); v++) { s << _visibleLayerDescs[v]._width << " " << _visibleLayerDescs[v]._height << " " << _visibleLayerDescs[v]._chunkSize << std::endl; - s << _visibleLayerDescs[v]._forwardRadius << " " << _visibleLayerDescs[v]._backwardRadius << std::endl; - s << _visibleLayerDescs[v]._predict << std::endl; + s << _visibleLayerDescs[v]._radius << " " << _visibleLayerDescs[v]._radius << std::endl; + + s << (_visibleLayerDescs[v]._predict ? 1 : 0) << std::endl; } s << _feedForwardWeights.size() << std::endl; @@ -620,32 +596,22 @@ void Layer::writeToStream(std::ostream &s) { // Save for (int i = 0; i < _hiddenStates.size(); i++) - s << _hiddenStates[i] << " " << _hiddenStatesPrev[i] << " "; + s << _hiddenStates[i] << " "; s << std::endl; - s << (_feedBack.size() > 1) << std::endl; + s << _feedBack.size() << std::endl; int numFeedBack = _feedBack.size(); for (int v = 0; v < _visibleLayerDescs.size(); v++) { // Save - for (int i = 0; i < _predictionActivations[v].size(); i++) - s << _predictionActivations[v][i] << " " << _predictionActivationsPrev[v][i] << " "; - - s << std::endl; - for (int i = 0; i < _predictions[v].size(); i++) - s << _predictions[v][i] << " " << _predictionsPrev[v][i] << " "; - - s << std::endl; - - for (int i = 0; i < _inputs[v].size(); i++) - s << _inputs[v][i] << " " << _inputsPrev[v][i] << " "; + s << _inputs[v][i] << " " << _inputsPrev[v][i] << " " << _predictions[v][i] << " "; s << std::endl; - int forwardVecSize = _visibleLayerDescs[v]._forwardRadius * 2 + 1; + int forwardVecSize = _visibleLayerDescs[v]._radius * 2 + 1; forwardVecSize *= forwardVecSize; @@ -661,27 +627,16 @@ void Layer::writeToStream(std::ostream &s) { s << std::endl; } - int backwardVecSize = _visibleLayerDescs[v]._backwardRadius * 2 + 1; - - backwardVecSize *= backwardVecSize; - if (_visibleLayerDescs[v]._predict) { for (int f = 0; f < numFeedBack; f++) { for (int x = 0; x < _visibleLayerDescs[v]._width; x++) for (int y = 0; y < _visibleLayerDescs[v]._height; y++) { - int vIndex = x + y * _visibleLayerDescs[v]._width; - - int i = f + numFeedBack * vIndex; - - for (int j = 0; j < backwardVecSize; j++) - s << _feedBackWeights[v][i][j] << " "; + int hIndex = x + y * _hiddenWidth; - s << std::endl; - - s << _feedBackTraces[v][i].size() << std::endl; + int i = v + _visibleLayerDescs.size() * hIndex; - for (std::unordered_map::const_iterator cit = _feedBackTraces[v][i].begin(); cit != _feedBackTraces[v][i].end(); cit++) - s << cit->first << " " << cit->second << " "; + for (int j = 0; j < forwardVecSize; j++) + s << _predictionWeights[f][i][j] << " "; s << std::endl; } @@ -698,13 +653,29 @@ void Layer::writeToStream(std::ostream &s) { } } -void Layer::forward(const std::vector> &inputs, ComputeSystem &cs, float alpha) { +void Layer::forward(const std::vector> &inputs, ComputeSystem &cs, float alpha, float gamma) { _inputsPrev = _inputs; _inputs = inputs; - _hiddenStatesPrev = _hiddenStates; - _alpha = alpha; + _gamma = gamma; + + _reconActivationsPrev = _reconActivations; + _reconCountsPrev = _reconCounts; + + // Clear recon buffers + _reconActivations.clear(); + _reconActivations.resize(_visibleLayerDescs.size()); + + for (int v = 0; v < _visibleLayerDescs.size(); v++) + _reconActivations[v].resize(_visibleLayerDescs[v]._width * _visibleLayerDescs[v]._height, 0.0f); + + _reconCounts = _reconActivations; + + if (_reconActivationsPrev.empty()) { + _reconActivationsPrev = _reconActivations; + _reconCountsPrev = _reconCounts; + } int chunksInX = _hiddenWidth / _chunkSize; int chunksInY = _hiddenHeight / _chunkSize; @@ -727,142 +698,71 @@ void Layer::forward(const std::vector> &inputs, ComputeSystem & cs._pool.wait(); } -void Layer::backward(const std::vector> &feedBack, ComputeSystem &cs, float reward, float beta, float delta, float gamma, float traceCutoff, float epsilon) { +void Layer::backward(const std::vector> &feedBack, ComputeSystem &cs, float beta) { _feedBackPrev = _feedBack; _feedBack = feedBack; - _predictionActivationsPrev = _predictionActivations; - _predictionsPrev = _predictions; - - _reward = reward; _beta = beta; - _delta = delta; - _gamma = gamma; - _traceCutoff = traceCutoff; - _epsilon = epsilon; - + + _predictionActivationsPrev = _predictionActivations; + + // Clear recon buffers + _predictionActivations.clear(); + _predictionActivations.resize(_visibleLayerDescs.size()); + + for (int v = 0; v < _visibleLayerDescs.size(); v++) + _predictionActivations[v].resize(_visibleLayerDescs[v]._width * _visibleLayerDescs[v]._height, 0.0f); + + _predictionCounts = _predictionActivations; + + if (_predictionActivationsPrev.empty()) + _predictionActivationsPrev = _predictionActivations; + std::uniform_int_distribution seedDist(0, 99999); - for (int v = 0; v < _visibleLayerDescs.size(); v++) { - int chunksInX = _visibleLayerDescs[v]._width / _visibleLayerDescs[v]._chunkSize; - int chunksInY = _visibleLayerDescs[v]._height / _visibleLayerDescs[v]._chunkSize; + { + int chunksInX = _hiddenWidth / _chunkSize; + int chunksInY = _hiddenHeight / _chunkSize; int numChunks = chunksInX * chunksInY; + std::uniform_int_distribution seedDist(0, 99999); + // Queue tasks for (int i = 0; i < numChunks; i++) { std::shared_ptr item = std::make_shared(); - item->_visibleChunkIndex = i; - item->_visibleLayerIndex = v; + item->_hiddenChunkIndex = i; item->_pLayer = this; item->_rng.seed(seedDist(cs._rng)); - + cs._pool.addItem(item); } - } - - cs._pool.wait(); -} - -std::vector Layer::getFeedBackWeights(int v, int f, int x, int y) const { - std::vector weights; - - // Reverse project - int hIndex = x + y * _hiddenWidth; - int hiddenChunkSize = _chunkSize; - - int visibleBitsPerChunk = _visibleLayerDescs[v]._chunkSize * _visibleLayerDescs[v]._chunkSize; - - int hiddenChunksInX = _hiddenWidth / hiddenChunkSize; - int hiddenChunksInY = _hiddenHeight / hiddenChunkSize; - - int hiddenChunkX = x / hiddenChunksInX; - int hiddenChunkY = y / hiddenChunksInY; - - // Project unit - int visibleChunkSize = _visibleLayerDescs[v]._chunkSize; - - int visibleChunksInX = _visibleLayerDescs[v]._width / visibleChunkSize; - int visibleChunksInY = _visibleLayerDescs[v]._height / visibleChunkSize; - - float toInputX1 = static_cast(visibleChunksInX) / static_cast(hiddenChunksInX); - float toInputY1 = static_cast(visibleChunksInY) / static_cast(hiddenChunksInY); - - int visibleChunkCenterX = (hiddenChunkX + 0.5f) * toInputX1; - int visibleChunkCenterY = (hiddenChunkY + 0.5f) * toInputY1; - - int visibleCenterX = (visibleChunkCenterX + 0.5f) * visibleChunkSize; - int visibleCenterY = (visibleChunkCenterY + 0.5f) * visibleChunkSize; - - int spatialReverseHiddenRadius = std::ceil(_visibleLayerDescs[v]._backwardRadius / std::min(toInputX1, toInputY1)) + 2; - - int spatialReverseHiddenDiam = spatialReverseHiddenRadius * 2 + 1; - - weights.resize(spatialReverseHiddenDiam * spatialReverseHiddenDiam, 0.0f); - - int spatialChunkRadius1 = std::ceil(static_cast(spatialReverseHiddenRadius) / static_cast(visibleChunkSize)); - - int lowerVisibleX = visibleCenterX - spatialReverseHiddenRadius; - int lowerVisibleY = visibleCenterY - spatialReverseHiddenRadius; - - int upperVisibleX = visibleCenterX + spatialReverseHiddenRadius; - int upperVisibleY = visibleCenterY + spatialReverseHiddenRadius; - - for (int dcx = -spatialChunkRadius1; dcx <= spatialChunkRadius1; dcx++) - for (int dcy = -spatialChunkRadius1; dcy <= spatialChunkRadius1; dcy++) { - int cx = visibleChunkCenterX + dcx; - int cy = visibleChunkCenterY + dcy; - - if (cx >= 0 && cx < visibleChunksInX && cy >= 0 && cy < visibleChunksInY) { - int visibleChunkIndex = cx + cy * visibleChunksInX; - - // Project this chunk back to hidden - int spatialHiddenRadius = _visibleLayerDescs[v]._backwardRadius; - - int spatialHiddenDiam = spatialHiddenRadius * 2 + 1; - - float toInputX2 = static_cast(hiddenChunksInX) / static_cast(visibleChunksInX); - float toInputY2 = static_cast(hiddenChunksInY) / static_cast(visibleChunksInY); - - int hiddenChunkCenterX = (cx + 0.5f) * toInputX2; - int hiddenChunkCenterY = (cy + 0.5f) * toInputY2; - - int hiddenCenterX = (hiddenChunkCenterX + 0.5f) * hiddenChunkSize; - int hiddenCenterY = (hiddenChunkCenterY + 0.5f) * hiddenChunkSize; - - int lowerHiddenX = hiddenCenterX - spatialHiddenRadius; - int lowerHiddenY = hiddenCenterY - spatialHiddenRadius; - - int upperHiddenX = hiddenCenterX + spatialHiddenRadius; - int upperHiddenY = hiddenCenterY + spatialHiddenRadius; - - if (x >= lowerHiddenX && x <= upperHiddenX && y >= lowerHiddenY && y <= upperHiddenY) { - for (int c = 0; c < visibleBitsPerChunk; c++) { - int mdx = c % visibleChunkSize; - int mdy = c / visibleChunkSize; - - int vx = cx * visibleChunkSize + mdx; - int vy = cy * visibleChunkSize + mdy; - - if (vx >= lowerVisibleX && vx <= upperVisibleX && vy >= lowerVisibleY && vy <= upperVisibleY) { - int vIndex = vx + vy * _visibleLayerDescs[v]._width; + cs._pool.wait(); + } - int i = f + _feedBack.size() * vIndex; + for (int v = 0; v < _visibleLayerDescs.size(); v++) { + if (!_visibleLayerDescs[v]._predict) + continue; - int wi = (x - lowerHiddenX) + (y - lowerHiddenY) * spatialHiddenDiam; + int chunksInX = _visibleLayerDescs[v]._width / _visibleLayerDescs[v]._chunkSize; + int chunksInY = _visibleLayerDescs[v]._height / _visibleLayerDescs[v]._chunkSize; - assert(wi >= 0 && wi < _feedBackWeights[v][i].size()); + int numChunks = chunksInX * chunksInY; - float weight = _feedBackWeights[v][i][wi]; + // Queue tasks + for (int i = 0; i < numChunks; i++) { + std::shared_ptr item = std::make_shared(); - weights[(vx - lowerVisibleX) + (vy - lowerVisibleY) * spatialReverseHiddenDiam] = weight; - } - } - } - } + item->_visibleChunkIndex = i; + item->_visibleLayerIndex = v; + item->_pLayer = this; + item->_rng.seed(seedDist(cs._rng)); + + cs._pool.addItem(item); } + } - return weights; -} + cs._pool.wait(); +} \ No newline at end of file diff --git a/source/eogmaneo/Layer.h b/source/eogmaneo/Layer.h index c53253f..80f8a86 100644 --- a/source/eogmaneo/Layer.h +++ b/source/eogmaneo/Layer.h @@ -40,18 +40,36 @@ namespace eogmaneo { }; /*! - \brief Backward work item, for internal use only. + \brief Forward work item, for internal use only. */ class BackwardWorkItem : public WorkItem { public: class Layer* _pLayer; + int _hiddenChunkIndex; + + std::mt19937 _rng; + + BackwardWorkItem() + : _pLayer(nullptr) + {} + + void run(size_t threadIndex) override; + }; + + /*! + \brief Prediction work item, for internal use only. + */ + class PredictionWorkItem : public WorkItem { + public: + class Layer* _pLayer; + int _visibleChunkIndex; int _visibleLayerIndex; std::mt19937 _rng; - BackwardWorkItem() + PredictionWorkItem() : _pLayer(nullptr) {} @@ -76,16 +94,13 @@ namespace eogmaneo { */ int _chunkSize; - //!@{ /*! - \brief Radii of forward and backward sparse weight matrices. + \brief Radius of sparse weight matrices. */ - int _forwardRadius; - int _backwardRadius; - //!@} - + int _radius; + /*! - \brief Whether this layer is predicted (has a backward pass). Only ever false for input layers and overflowing (temporalHorizon > ticksPerUpdate) visible layers. + \brief Whether or not this visible layer should be predicted (used to save processing power). */ bool _predict; @@ -94,8 +109,8 @@ namespace eogmaneo { */ VisibleLayerDesc() : _width(36), _height(36), _chunkSize(6), - _forwardRadius(9), _backwardRadius(9), - _predict(true) + _radius(9), + _predict(true) {} }; @@ -109,70 +124,64 @@ namespace eogmaneo { int _chunkSize; std::vector _hiddenStates; - std::vector _hiddenStatesPrev; - std::vector > _feedForwardWeights; + std::vector> _feedForwardWeights; + std::vector>> _predictionWeights; - std::vector _visibleLayerDescs; + std::vector> _reconActivations; + std::vector> _reconCounts; + std::vector> _reconActivationsPrev; + std::vector> _reconCountsPrev; - std::vector> _predictionActivations; - std::vector> _predictionActivationsPrev; + std::vector _visibleLayerDescs; std::vector> _predictions; - std::vector> _predictionsPrev; - - std::vector > > _feedBackWeights; - std::vector > > _feedBackTraces; + std::vector> _predictionActivations; + std::vector> _predictionCounts; + std::vector> _predictionActivationsPrev; + std::vector> _inputs; std::vector> _inputsPrev; std::vector> _feedBack; std::vector> _feedBackPrev; - + float _alpha; float _beta; - float _delta; float _gamma; - float _traceCutoff; - float _epsilon; - float _reward; - + void createFromStream(std::istream &s); void writeToStream(std::ostream &s); - public: + public: /*! \brief Create a layer. \param hiddenWidth width of the layer. \param hiddenHeight height of the layer. \param chunkSize chunk size of the layer. - \param hasFeedBack whether or not this layer receives feedback from a higher layer. + \param numFeedBack amount of feedback from a higher layer. \param visibleLayerDescs descriptor structures for all visible layers this (hidden) layer has. \param seed random number generator seed for layer generation. */ - void create(int hiddenWidth, int hiddenHeight, int chunkSize, bool hasFeedBack, const std::vector &visibleLayerDescs, unsigned long seed); + void create(int hiddenWidth, int hiddenHeight, int chunkSize, int numFeedBack, const std::vector &visibleLayerDescs, unsigned long seed); /*! - \brief Forward activation. + \brief Forward activation and learning. \param inputs vector of input SDRs in chunked format. \param cs compute system to be used. \param alpha feed forward learning rate. + \param gamma learning decay. */ - void forward(const std::vector > &inputs, ComputeSystem &cs, float alpha); + void forward(const std::vector > &inputs, ComputeSystem &cs, float alpha, float gamma); /*! \brief Backward activation. \param feedBack vector of feedback SDRs in chunked format. \param cs compute system to be used. - \param reward reinforcement signal. \param beta feedback learning rate. - \param delta Q learning rate (0.0f disables reinforcement learning). - \param gamma Q discount factor. - \param traceCutoff minimum eligibility trace strength before the trace is removed. - \param epsilon Q exploration rate. */ - void backward(const std::vector > &feedBack, ComputeSystem &cs, float reward, float beta, float delta, float gamma, float traceCutoff, float epsilon); + void backward(const std::vector > &feedBack, ComputeSystem &cs, float beta); //!@{ /*! @@ -222,13 +231,6 @@ namespace eogmaneo { return _hiddenStates; } - /*! - \brief Get previous timestep hidden states, in chunked format. - */ - const std::vector getHiddenStatesPrev() const { - return _hiddenStatesPrev; - } - /*! \brief Get inputs of a visible layer, in chunked format. */ @@ -236,13 +238,6 @@ namespace eogmaneo { return _inputs[v]; } - /*! - \brief Get previous timestep inputs of a visible layer, in chunked format. - */ - const std::vector getInputsPrev(int v) const { - return _inputsPrev[v]; - } - /*! \brief Get predictions of a visible layer, in chunked format. */ @@ -250,13 +245,6 @@ namespace eogmaneo { return _predictions[v]; } - /*! - \brief Get previous timestep predictions of a visible layer, in chunked format. - */ - const std::vector getPredictionsPrev(int v) const { - return _predictionsPrev[v]; - } - /*! \brief Get feedback layer, in chunked format. */ @@ -281,13 +269,17 @@ namespace eogmaneo { } /*! - \brief Get feedback weights of a particular unit. - This function is expensive, it reprojects weights such that they are addressed from the current layer instead of the visible layer. + \brief Get prediction weights of a particular unit. */ - std::vector getFeedBackWeights(int v, int f, int x, int y) const; + const std::vector &getPredictionWeights(int f, int v, int x, int y) const { + int i = v + _visibleLayerDescs.size() * (x + y * _hiddenWidth); + + return _predictionWeights[f][i]; + } friend class ForwardWorkItem; friend class BackwardWorkItem; + friend class PredictionWorkItem; friend class Hierarchy; }; } diff --git a/source/eogmaneo/Preprocessing.cpp b/source/eogmaneo/Preprocessing.cpp index b3dc278..7bafe5c 100644 --- a/source/eogmaneo/Preprocessing.cpp +++ b/source/eogmaneo/Preprocessing.cpp @@ -100,7 +100,7 @@ void eogmaneo::whiten(const std::vector &src, std::vector &dest, i int oy = y + dy; if (ox >= 0 && oy >= 0 && ox < width && oy < height) { - float centeredOther = src[ox + oy * width] - center; + float centeredOther = src[ox + oy * width] * 2.0f - 1.0f; covariance += centeredCurrent * centeredOther; } diff --git a/source/optional/CornerEncoder.cpp b/source/optional/CornerEncoder.cpp deleted file mode 100644 index 7417f28..0000000 --- a/source/optional/CornerEncoder.cpp +++ /dev/null @@ -1,155 +0,0 @@ -// ---------------------------------------------------------------------------- -// EOgmaNeo -// Copyright(c) 2017 Ogma Intelligent Systems Corp. All rights reserved. -// -// This copy of EOgmaNeo is licensed to you under the terms described -// in the EOGMANEO_LICENSE.md file included in this distribution. -// ---------------------------------------------------------------------------- - -#include "CornerEncoder.h" - -#include -#include - -using namespace eogmaneo; - -void CornerEncoderWorkItem::run(size_t threadIndex) { - _pEncoder->activate(_cx, _cy); -} - -void CornerEncoder::create(int inputWidth, int inputHeight, int chunkSize, int k) { - _inputWidth = inputWidth; - _inputHeight = inputHeight; - - _chunkSize = chunkSize; - _k = k; - - int chunksInX = inputWidth / chunkSize; - int chunksInY = inputHeight / chunkSize; - - _hiddenStates.resize(k); - - for (int order = 0; order < k; order++) - _hiddenStates[order].resize(chunksInX * chunksInY, 0); - - _hiddenScores.resize(inputWidth * inputHeight, 0); -} - -void CornerEncoder::activate(const std::vector &input, ComputeSystem &cs, float radius, float thresh, int samples) { - _input = input; - - _radius = radius; - - _thresh = thresh; - _samples = samples; - - int chunksInX = _inputWidth / _chunkSize; - int chunksInY = _inputHeight / _chunkSize; - - // Pre-compute deltas - const float pi = 3.141596f; - - float inc = 2.0f * pi / _samples; - - _deltas.resize(_samples); - - for (float i = 0; i < _samples; i++) { - float f = i * inc; - - float xf = std::cos(f) * _radius; - float yf = std::sin(f) * _radius; - - int dx = std::round(xf); - int dy = std::round(yf); - - _deltas[i] = std::make_pair(dx, dy); - } - - for (int cx = 0; cx < chunksInX; cx++) - for (int cy = 0; cy < chunksInY; cy++) { - std::shared_ptr item = std::make_shared(); - - item->_cx = cx; - item->_cy = cy; - item->_pEncoder = this; - - cs._pool.addItem(item); - } - - cs._pool.wait(); -} - -bool cmp(const std::pair &left, const std::pair &right) { - return std::get<0>(left) < std::get<0>(right); -} - -void CornerEncoder::activate(int cx, int cy) { - int chunksInX = _inputWidth / _chunkSize; - int chunksInY = _inputHeight / _chunkSize; - - std::vector> heap; - - // Go through image in this chunk - for (int rx = 0; rx < _chunkSize; rx++) - for (int ry = 0; ry < _chunkSize; ry++) { - int px = cx * _chunkSize + rx; - int py = cy * _chunkSize + ry; - - float vc = _input[px + py * _inputWidth]; - - int maxContiguous = 0; - - int contiguous = 0; - - bool prevBrighter = false; - bool prevDarker = false; - - for (int s = 0; s < _samples; s++) { - std::pair delta = _deltas[s]; - - int x = px + std::get<0>(delta); - int y = py + std::get<1>(delta); - - if (x >= 0 && x < _inputWidth && y >= 0 && y < _inputHeight) { - float v = _input[x + y * _inputWidth]; - - if (s == 0) { - prevBrighter = v > vc + _thresh; - prevDarker = v < vc - _thresh; - } - - bool brighter = v > vc + _thresh; - bool darker = v < vc - _thresh; - - if ((brighter && prevBrighter) || (darker && prevDarker)) - contiguous++; - else { - maxContiguous = std::max(maxContiguous, contiguous); - contiguous = 0; - } - - prevBrighter = brighter; - prevDarker = darker; - } - } - - maxContiguous = std::max(maxContiguous, contiguous); - - _hiddenScores[px + py * _inputWidth] = maxContiguous; - - heap.push_back(std::make_pair(maxContiguous, rx + ry * _chunkSize)); - } - - std::make_heap(heap.begin(), heap.end(), cmp); - - // Find max K scores - for (int order = 0; order < _k; order++) { - std::pop_heap(heap.begin(), heap.end(), cmp); - - int maxCoord = std::get<1>(heap.back()); - - heap.pop_back(); - - _hiddenStates[order][cx + cy * chunksInX] = maxCoord; - } -} \ No newline at end of file diff --git a/source/optional/CornerEncoder.h b/source/optional/CornerEncoder.h deleted file mode 100644 index 4f2d372..0000000 --- a/source/optional/CornerEncoder.h +++ /dev/null @@ -1,133 +0,0 @@ -// ---------------------------------------------------------------------------- -// EOgmaNeo -// Copyright(c) 2017 Ogma Intelligent Systems Corp. All rights reserved. -// -// This copy of EOgmaNeo is licensed to you under the terms described -// in the EOGMANEO_LICENSE.md file included in this distribution. -// ---------------------------------------------------------------------------- - -#pragma once - -#include "ComputeSystem.h" - -#include - -namespace eogmaneo { - class CornerEncoder; - - /*! - \brief Corner encoder work item. For internal use only. - */ - class CornerEncoderWorkItem : public WorkItem { - public: - CornerEncoder* _pEncoder; - - int _cx, _cy; - bool _useDistanceMetric; - - CornerEncoderWorkItem() - : _pEncoder(nullptr) - {} - - void run(size_t threadIndex) override; - }; - - /*! - \brief FAST-based corner encoder that looks for corners and assigns them to a chunked SDR. - */ - class CornerEncoder { - private: - int _inputWidth, _inputHeight; - int _chunkSize; - float _radius; - int _k; - float _thresh; - int _samples; - - std::vector _hiddenScores; - - std::vector > _hiddenStates; - - void activate(int cx, int cy); - - std::vector _input; - - std::vector > _deltas; - - public: - /*! - \brief Create the corner encoder. - \param inputWidth width of the input image. The output (encoded) SDR has the same size as the input. - \param inputHeight height of the input image. The output (encoded) SDR has the same size as the input. - \param chunkSize chunk diameter of the output (encoded) chunked SDR. - \param k number of corners per chunk (multiple SDRs can be generated). - */ - void create(int inputWidth, int inputHeight, int chunkSize, int k); - - /*! - \brief Zero the hidden states. - */ - void clearHiddenStates() { - for (int order = 0; order < _hiddenStates.size(); order++) { - int size = _hiddenStates[order].size(); - - _hiddenStates[order].clear(); - _hiddenStates[order].assign(size, 0); - } - } - - /*! - \brief Activate (encoded) from an image. - \param input the raveled input image. - \param cs compute system to be used. - \param radius radius of the corner detector. - \param thresh threshold of the corner detector. - \param samples number of samples around the radius. - */ - void activate(const std::vector &input, ComputeSystem &cs, float radius, float thresh, int samples); - - //!@{ - /*! - \brief Get input dimensions. - */ - int getInputWidth() const { - return _inputWidth; - } - - int getInputHeight() const { - return _inputHeight; - } - //!@} - - /*! - \brief Get chunk size of the output SDR. - */ - int getChunkSize() const { - return _chunkSize; - } - - /*! - \brief Get last used radius. - */ - float getRadius() const { - return _radius; - } - - /*! - \brief Get k - */ - int getK() const { - return _k; - } - - /*! - \brief Get hidden states at a certain order (order < k). - Lower indices are more strongly detected corners. - */ - const std::vector &getHiddenStates(int order) const { - return _hiddenStates[order]; - } - - friend class CornerEncoderWorkItem; - }; -} \ No newline at end of file diff --git a/source/optional/ImageEncoder.cpp b/source/optional/ImageEncoder.cpp index d0b6914..bde0664 100644 --- a/source/optional/ImageEncoder.cpp +++ b/source/optional/ImageEncoder.cpp @@ -40,7 +40,7 @@ void ImageEncoder::create(int inputWidth, int inputHeight, int hiddenWidth, int _radius = radius; - std::uniform_real_distribution weightDist(0.9999f, 1.0f); + std::uniform_real_distribution weightDist(0.99f, 1.0f); int diam = radius * 2 + 1; @@ -50,10 +50,12 @@ void ImageEncoder::create(int inputWidth, int inputHeight, int hiddenWidth, int _weights0.resize(hiddenWidth * hiddenHeight * weightsPerUnit); _weights1.resize(hiddenWidth * hiddenHeight * weightsPerUnit); + _weights2.resize(hiddenWidth * hiddenHeight * weightsPerUnit); for (int w = 0; w < _weights0.size(); w++) { _weights0[w] = weightDist(rng); _weights1[w] = weightDist(rng); + _weights2[w] = 1.0f; } int chunksInX = _hiddenWidth / _chunkSize; @@ -176,10 +178,10 @@ void ImageEncoder::activate(int cx, int cy) { int wi = index + weightsPerUnit * (x + y * _hiddenWidth); int ii = vx + vy * _inputWidth; - value += _weights0[wi] * _input[ii];// + _weights1[wi] * (1.0f - _input[ii]); + value += _input[ii] * _weights0[wi] + (1.0f - _input[ii]) * _weights1[wi]; } } - + _hiddenActivations[x + y * _hiddenWidth] = value; if (value > maxValue) { @@ -226,8 +228,8 @@ void ImageEncoder::reconstruct(int cx, int cy) { int vy = lowerY + sy; if (vx >= 0 && vy >= 0 && vx < _inputWidth && vy < _inputHeight) { - _recon[vx + vy * _inputWidth] += _weights0[index + weightsPerUnit * (x + y * _hiddenWidth)];// + (1.0f - _weights1[index + weightsPerUnit * (x + y * _hiddenWidth)]); - _count[vx + vy * _inputWidth] += 1.0f; // 2.0f + _recon[vx + vy * _inputWidth] += _weights0[index + weightsPerUnit * (x + y * _hiddenWidth)] + (1.0f - _weights1[index + weightsPerUnit * (x + y * _hiddenWidth)]); + _count[vx + vy * _inputWidth] += 2.0f; } } } @@ -265,9 +267,8 @@ void ImageEncoder::learn(int cx, int cy, float alpha) { int vy = lowerY + sy; if (vx >= 0 && vy >= 0 && vx < _inputWidth && vy < _inputHeight) { - _weights0[index + weightsPerUnit * (x + y * _hiddenWidth)] += alpha * (_input[vx + vy * _inputWidth] - _weights0[index + weightsPerUnit * (x + y * _hiddenWidth)]); - - //_weights1[index + weightsPerUnit * (x + y * _hiddenWidth)] += alpha * (1.0f - _input[vx + vy * _inputWidth] - _weights1[index + weightsPerUnit * (x + y * _hiddenWidth)]); + _weights0[index + weightsPerUnit * (x + y * _hiddenWidth)] += alpha * std::min(0.0f, _input[vx + vy * _inputWidth] - _weights0[index + weightsPerUnit * (x + y * _hiddenWidth)]); + _weights1[index + weightsPerUnit * (x + y * _hiddenWidth)] += alpha * std::min(0.0f, 1.0f - _input[vx + vy * _inputWidth] - _weights1[index + weightsPerUnit * (x + y * _hiddenWidth)]); } } } @@ -278,7 +279,7 @@ void ImageEncoder::save(const std::string &fileName) { s << _inputWidth << " " << _inputHeight << " " << _hiddenWidth << " " << _hiddenHeight << " " << _chunkSize << " " << _radius << std::endl; for (int w = 0; w < _weights0.size(); w++) - s << _weights0[w] << " " << _weights1[w] << std::endl; + s << _weights0[w] << " " << _weights1[w] << " " << _weights2[w] << std::endl; } bool ImageEncoder::load(const std::string &fileName) { @@ -297,9 +298,10 @@ bool ImageEncoder::load(const std::string &fileName) { _weights0.resize(_hiddenWidth * _hiddenHeight * weightsPerUnit); _weights1.resize(_hiddenWidth * _hiddenHeight * weightsPerUnit); + _weights2.resize(_hiddenWidth * _hiddenHeight * weightsPerUnit); for (int w = 0; w < _weights0.size(); w++) - s >> _weights0[w] >> _weights1[w]; + s >> _weights0[w] >> _weights1[w] >> _weights2[w]; int chunksInX = _hiddenWidth / _chunkSize; int chunksInY = _hiddenHeight / _chunkSize; diff --git a/source/optional/ImageEncoder.h b/source/optional/ImageEncoder.h index 3ebf9cb..569b3e4 100644 --- a/source/optional/ImageEncoder.h +++ b/source/optional/ImageEncoder.h @@ -81,6 +81,7 @@ namespace eogmaneo { std::vector _weights0; std::vector _weights1; + std::vector _weights2; void activate(int cx, int cy); void reconstruct(int cx, int cy); @@ -199,4 +200,4 @@ namespace eogmaneo { friend class ImageDecoderWorkItem; friend class ImageLearnWorkItem; }; -} \ No newline at end of file +} diff --git a/source/optional/RandomEncoder.cpp b/source/optional/KMeansEncoder.cpp similarity index 53% rename from source/optional/RandomEncoder.cpp rename to source/optional/KMeansEncoder.cpp index 42efea1..e314094 100644 --- a/source/optional/RandomEncoder.cpp +++ b/source/optional/KMeansEncoder.cpp @@ -6,26 +6,27 @@ // in the EOGMANEO_LICENSE.md file included in this distribution. // ---------------------------------------------------------------------------- -#include "RandomEncoder.h" +#include "KMeansEncoder.h" #include +#include using namespace eogmaneo; -void RandomEncoderWorkItem::run(size_t threadIndex) { - _pEncoder->activate(_cx, _cy, _useDistanceMetric); +void KMeansEncoderWorkItem::run(size_t threadIndex) { + _pEncoder->activate(_cx, _cy); } -void RandomDecoderWorkItem::run(size_t threadIndex) { +void KMeansDecoderWorkItem::run(size_t threadIndex) { _pEncoder->reconstruct(_cx, _cy); } -void RandomLearnWorkItem::run(size_t threadIndex) { - _pEncoder->learn(_cx, _cy, _alpha, _gamma); +void KMeansLearnWorkItem::run(size_t threadIndex) { + _pEncoder->learn(_cx, _cy, _alpha, _gamma, _minDistance); } -void RandomEncoder::create(int inputWidth, int inputHeight, int hiddenWidth, int hiddenHeight, int chunkSize, int radius, - float initMinWeight, float initMaxWeight, unsigned long seed, bool normalize) +void KMeansEncoder::create(int inputWidth, int inputHeight, int hiddenWidth, int hiddenHeight, int chunkSize, int radius, + float initMinWeight, float initMaxWeight, unsigned long seed) { std::mt19937 rng; rng.seed(seed); @@ -52,42 +53,17 @@ void RandomEncoder::create(int inputWidth, int inputHeight, int hiddenWidth, int for (int w = 0; w < _weights.size(); w++) _weights[w] = weightDist(rng); - if (normalize) { - // Normalize - for (int w = 0; w < units; w++) { - float sum2 = 0.0f; - - for (int dx = -radius; dx <= radius; dx++) - for (int dy = -radius; dy <= radius; dy++) { - int index = (dx + radius) + (dy + radius) * diam; - - float weight = _weights[w * weightsPerUnit + index]; - - sum2 += weight * weight; - } - - // Normalize - float scale = 1.0f / std::max(0.0001f, std::sqrt(sum2)); - - for (int dx = -radius; dx <= radius; dx++) - for (int dy = -radius; dy <= radius; dy++) { - int index = (dx + radius) + (dy + radius) * diam; - - _weights[w * weightsPerUnit + index] *= scale; - } - } - } - int chunksInX = _hiddenWidth / _chunkSize; int chunksInY = _hiddenHeight / _chunkSize; _hiddenStates.resize(chunksInX * chunksInY, 0); + _hiddenStatesPrev.resize(chunksInX * chunksInY, 0); _hiddenActivations.resize(hiddenWidth * hiddenHeight, 0.0f); _hiddenBiases.resize(hiddenWidth * hiddenHeight, 0.0f); } -const std::vector &RandomEncoder::activate(const std::vector &input, ComputeSystem &cs, bool useDistanceMetric) { +const std::vector &KMeansEncoder::activate(const std::vector &input, ComputeSystem &cs) { _input = input; int chunksInX = _hiddenWidth / _chunkSize; @@ -95,12 +71,11 @@ const std::vector &RandomEncoder::activate(const std::vector &input, for (int cx = 0; cx < chunksInX; cx++) for (int cy = 0; cy < chunksInY; cy++) { - std::shared_ptr item = std::make_shared(); + std::shared_ptr item = std::make_shared(); item->_cx = cx; item->_cy = cy; item->_pEncoder = this; - item->_useDistanceMetric = useDistanceMetric; cs._pool.addItem(item); } @@ -110,7 +85,7 @@ const std::vector &RandomEncoder::activate(const std::vector &input, return _hiddenStates; } -const std::vector &RandomEncoder::reconstruct(const std::vector &hiddenStates, ComputeSystem &cs) { +const std::vector &KMeansEncoder::reconstruct(const std::vector &hiddenStates, ComputeSystem &cs) { int chunksInX = _hiddenWidth / _chunkSize; int chunksInY = _hiddenHeight / _chunkSize; @@ -124,18 +99,16 @@ const std::vector &RandomEncoder::reconstruct(const std::vector &hid for (int cx = 0; cx < chunksInX; cx++) for (int cy = 0; cy < chunksInY; cy++) { - /*std::shared_ptr item = std::make_shared(); + std::shared_ptr item = std::make_shared(); item->_cx = cx; item->_cy = cy; item->_pEncoder = this; - cs._pool.addItem(item);*/ - - reconstruct(cx, cy); + cs._pool.addItem(item); } - //cs._pool.wait(); + cs._pool.wait(); // Rescale for (int i = 0; i < _recon.size(); i++) @@ -144,19 +117,20 @@ const std::vector &RandomEncoder::reconstruct(const std::vector &hid return _recon; } -void RandomEncoder::learn(float alpha, float gamma, ComputeSystem &cs) { +void KMeansEncoder::learn(float alpha, float gamma, float minDistance, ComputeSystem &cs) { int chunksInX = _hiddenWidth / _chunkSize; int chunksInY = _hiddenHeight / _chunkSize; for (int cx = 0; cx < chunksInX; cx++) for (int cy = 0; cy < chunksInY; cy++) { - std::shared_ptr item = std::make_shared(); + std::shared_ptr item = std::make_shared(); item->_cx = cx; item->_cy = cy; item->_pEncoder = this; item->_alpha = alpha; item->_gamma = gamma; + item->_minDistance = minDistance; cs._pool.addItem(item); } @@ -164,7 +138,7 @@ void RandomEncoder::learn(float alpha, float gamma, ComputeSystem &cs) { cs._pool.wait(); } -void RandomEncoder::activate(int cx, int cy, bool useDistanceMetric) { +void KMeansEncoder::activate(int cx, int cy) { int chunksInX = _hiddenWidth / _chunkSize; int chunksInY = _hiddenHeight / _chunkSize; @@ -184,73 +158,43 @@ void RandomEncoder::activate(int cx, int cy, bool useDistanceMetric) { int lowerX = centerX - _radius; int lowerY = centerY - _radius; - if (useDistanceMetric) { - for (int dx = 0; dx < _chunkSize; dx++) - for (int dy = 0; dy < _chunkSize; dy++) { - int x = cx * _chunkSize + dx; - int y = cy * _chunkSize + dy; + for (int dx = 0; dx < _chunkSize; dx++) + for (int dy = 0; dy < _chunkSize; dy++) { + int x = cx * _chunkSize + dx; + int y = cy * _chunkSize + dy; - // Compute value - float value = _hiddenBiases[x + y * _hiddenWidth]; + // Compute value + float value = 0.0f; - for (int sx = 0; sx < diam; sx++) - for (int sy = 0; sy < diam; sy++) { - int index = sx + sy * diam; + for (int sx = 0; sx < diam; sx++) + for (int sy = 0; sy < diam; sy++) { + int index = sx + sy * diam; - int vx = lowerX + sx; - int vy = lowerY + sy; + int vx = lowerX + sx; + int vy = lowerY + sy; - if (vx >= 0 && vy >= 0 && vx < _inputWidth && vy < _inputHeight) { - float delta = _weights[index + weightsPerUnit * (x + y * _hiddenWidth)] - _input[vx + vy * _inputWidth]; + if (vx >= 0 && vy >= 0 && vx < _inputWidth && vy < _inputHeight) { + float delta = _weights[index + weightsPerUnit * (x + y * _hiddenWidth)] - _input[vx + vy * _inputWidth]; - value += -delta * delta; - } - } + value += -delta * delta; + } + } - _hiddenActivations[x + y * _hiddenWidth] = value; - - if (value > maxValue) { - maxValue = value; - maxIndex = dx + dy * _chunkSize; - } - } - } - else { - for (int dx = 0; dx < _chunkSize; dx++) - for (int dy = 0; dy < _chunkSize; dy++) { - int x = cx * _chunkSize + dx; - int y = cy * _chunkSize + dy; - - // Compute value - float value = _hiddenBiases[x + y * _hiddenWidth]; - - for (int sx = 0; sx < diam; sx++) - for (int sy = 0; sy < diam; sy++) { - int index = sx + sy * diam; - - int vx = lowerX + sx; - int vy = lowerY + sy; - - if (vx >= 0 && vy >= 0 && vx < _inputWidth && vy < _inputHeight) - value += _weights[index + weightsPerUnit * (x + y * _hiddenWidth)] * _input[vx + vy * _inputWidth]; - } - - _hiddenActivations[x + y * _hiddenWidth] = value; - - if (value > maxValue) { - maxValue = value; - maxIndex = dx + dy * _chunkSize; - } - } - } + _hiddenActivations[x + y * _hiddenWidth] = value; - int winx = maxIndex % _chunkSize; - int winy = maxIndex / _chunkSize; + value += _hiddenBiases[x + y * _hiddenWidth]; + + if (value > maxValue) { + maxValue = value; + maxIndex = dx + dy * _chunkSize; + } + } - _hiddenStates[cx + cy * chunksInX] = winx + winy * _chunkSize; + _hiddenStatesPrev[cx + cy * chunksInX] = _hiddenStates[cx + cy * chunksInX]; + _hiddenStates[cx + cy * chunksInX] = maxIndex; } -void RandomEncoder::reconstruct(int cx, int cy) { +void KMeansEncoder::reconstruct(int cx, int cy) { int chunksInX = _hiddenWidth / _chunkSize; int chunksInY = _hiddenHeight / _chunkSize; @@ -295,7 +239,7 @@ void RandomEncoder::reconstruct(int cx, int cy) { } } -void RandomEncoder::learn(int cx, int cy, float alpha, float gamma) { +void KMeansEncoder::learn(int cx, int cy, float alpha, float gamma, float minDistance) { int chunksInX = _hiddenWidth / _chunkSize; int chunksInY = _hiddenHeight / _chunkSize; @@ -315,32 +259,79 @@ void RandomEncoder::learn(int cx, int cy, float alpha, float gamma) { int lowerX = centerX - _radius; int lowerY = centerY - _radius; - int winX = _reconHiddenStates[cx + cy * chunksInX] % _chunkSize; - int winY = _reconHiddenStates[cx + cy * chunksInX] / _chunkSize; + int winX = _hiddenStates[cx + cy * chunksInX] % _chunkSize; + int winY = _hiddenStates[cx + cy * chunksInX] / _chunkSize; for (int dx = 0; dx < _chunkSize; dx++) for (int dy = 0; dy < _chunkSize; dy++) { int x = cx * _chunkSize + dx; int y = cy * _chunkSize + dy; - _hiddenBiases[x + y * _hiddenWidth] += gamma * -_hiddenActivations[x + y * _hiddenWidth]; + _hiddenBiases[x + y * _hiddenWidth] += gamma * (1.0f / (_chunkSize * _chunkSize) - (dx == winX && dy == winY ? 1.0f : 0.0f)); } - { int x = cx * _chunkSize + winX; int y = cy * _chunkSize + winY; - // Compute value - for (int sx = 0; sx < diam; sx++) - for (int sy = 0; sy < diam; sy++) { - int index = sx + sy * diam; + if (_hiddenActivations[x + y * _hiddenWidth] < -minDistance) { // && _hiddenStates[cx + cy * chunksInX] != _hiddenStatesPrev[cx + cy * chunksInX] + // Compute value + for (int sx = 0; sx < diam; sx++) + for (int sy = 0; sy < diam; sy++) { + int index = sx + sy * diam; - int vx = lowerX + sx; - int vy = lowerY + sy; + int vx = lowerX + sx; + int vy = lowerY + sy; - if (vx >= 0 && vy >= 0 && vx < _inputWidth && vy < _inputHeight) - _weights[index + weightsPerUnit * (x + y * _hiddenWidth)] += alpha * (_input[vx + vy * _inputWidth] - _recon[vx + vy * _inputWidth]); - } + if (vx >= 0 && vy >= 0 && vx < _inputWidth && vy < _inputHeight) + _weights[index + weightsPerUnit * (x + y * _hiddenWidth)] += alpha * (_input[vx + vy * _inputWidth] - _weights[index + weightsPerUnit * (x + y * _hiddenWidth)]); + } + } } +} + +void KMeansEncoder::save(const std::string &fileName) { + std::ofstream s(fileName); + + s << _inputWidth << " " << _inputHeight << " " << _hiddenWidth << " " << _hiddenHeight << " " << _chunkSize << " " << _radius << std::endl; + + for (int i = 0; i < _weights.size(); i++) + s << _weights[i] << std::endl; + + for (int i = 0; i < _hiddenStates.size(); i++) + s << _hiddenStates[i] << " " << _hiddenStatesPrev[i] << std::endl; +} + +bool KMeansEncoder::load(const std::string &fileName) { + std::ifstream s(fileName); + + if (!s.is_open()) + return false; + + s >> _inputWidth >> _inputHeight >> _hiddenWidth >> _hiddenHeight >> _chunkSize >> _radius; + + int diam = _radius * 2 + 1; + + int weightsPerUnit = diam * diam; + + int units = _hiddenWidth * _hiddenHeight; + + _weights.resize(_hiddenWidth * _hiddenHeight * weightsPerUnit); + + for (int w = 0; w < _weights.size(); w++) + s >> _weights[w]; + + int chunksInX = _hiddenWidth / _chunkSize; + int chunksInY = _hiddenHeight / _chunkSize; + + _hiddenStates.resize(chunksInX * chunksInY); + _hiddenStatesPrev.resize(chunksInX * chunksInY); + + for (int i = 0; i < _hiddenStates.size(); i++) + s >> _hiddenStates[i] >> _hiddenStatesPrev[i]; + + _hiddenActivations.resize(_hiddenWidth * _hiddenHeight, 0.0f); + _hiddenBiases.resize(_hiddenWidth * _hiddenHeight, 0.0f); + + return true; } \ No newline at end of file diff --git a/source/optional/RandomEncoder.h b/source/optional/KMeansEncoder.h similarity index 75% rename from source/optional/RandomEncoder.h rename to source/optional/KMeansEncoder.h index 2f7af85..a631e83 100644 --- a/source/optional/RandomEncoder.h +++ b/source/optional/KMeansEncoder.h @@ -13,19 +13,18 @@ #include namespace eogmaneo { - class RandomEncoder; + class KMeansEncoder; /*! - \brief Random encoder work item. Internal use only. + \brief K-means encoder work item. Internal use only. */ - class RandomEncoderWorkItem : public WorkItem { + class KMeansEncoderWorkItem : public WorkItem { public: - RandomEncoder* _pEncoder; + KMeansEncoder* _pEncoder; int _cx, _cy; - bool _useDistanceMetric; - RandomEncoderWorkItem() + KMeansEncoderWorkItem() : _pEncoder(nullptr) {} @@ -33,15 +32,15 @@ namespace eogmaneo { }; /*! - \brief Random decoder work item. Internal use only. + \brief K-means decoder work item. Internal use only. */ - class RandomDecoderWorkItem : public WorkItem { + class KMeansDecoderWorkItem : public WorkItem { public: - RandomEncoder* _pEncoder; + KMeansEncoder* _pEncoder; int _cx, _cy; - RandomDecoderWorkItem() + KMeansDecoderWorkItem() : _pEncoder(nullptr) {} @@ -49,17 +48,18 @@ namespace eogmaneo { }; /*! - \brief Random learn work item. Internal use only. + \brief K-means learn work item. Internal use only. */ - class RandomLearnWorkItem : public WorkItem { + class KMeansLearnWorkItem : public WorkItem { public: - RandomEncoder* _pEncoder; + KMeansEncoder* _pEncoder; int _cx, _cy; float _alpha; float _gamma; + float _minDistance; - RandomLearnWorkItem() + KMeansLearnWorkItem() : _pEncoder(nullptr) {} @@ -67,9 +67,9 @@ namespace eogmaneo { }; /*! - \brief Encoders values to a chunked SDR through random transformation. + \brief Encoders values to a chunked SDR through linear transformation followed by winner-takes-all. */ - class RandomEncoder { + class KMeansEncoder { private: int _inputWidth, _inputHeight; int _hiddenWidth, _hiddenHeight; @@ -77,15 +77,16 @@ namespace eogmaneo { int _radius; std::vector _hiddenStates; + std::vector _hiddenStatesPrev; std::vector _hiddenActivations; std::vector _hiddenBiases; std::vector _weights; - void activate(int cx, int cy, bool useDistanceMetric); + void activate(int cx, int cy); void reconstruct(int cx, int cy); - void learn(int cx, int cy, float alpha, float gamma); + void learn(int cx, int cy, float alpha, float gamma, float maxDistance); std::vector _reconHiddenStates; std::vector _input; @@ -94,7 +95,7 @@ namespace eogmaneo { public: /*! - \brief Create the random encoder. + \brief Create the K-means encoder. \param inputWidth input image width. \param inputHeight input image height. \param hiddenWidth hidden SDR width. @@ -107,7 +108,7 @@ namespace eogmaneo { \param normalize whether to normalize (L2) the weights. */ void create(int inputWidth, int inputHeight, int hiddenWidth, int hiddenHeight, int chunkSize, int radius, - float initMinWeight, float initMaxWeight, unsigned long seed, bool normalize); + float initMinWeight, float initMaxWeight, unsigned long seed); /*! \brief Zero the hidden states. @@ -123,9 +124,8 @@ namespace eogmaneo { \brief Activate the encoder from an input (compute hidden states, perform encoding). \param input input vector/image. \param cs compute system to be used. - \param useDistanceMetric whether to activate based on euclidean distance (true) or dot product (false). Defaults to true. */ - const std::vector &activate(const std::vector &input, ComputeSystem &cs, bool useDistanceMetric = true); + const std::vector &activate(const std::vector &input, ComputeSystem &cs); /*! \brief Reconstruct (reverse) an encoding. @@ -136,13 +136,22 @@ namespace eogmaneo { const std::vector &reconstruct(const std::vector &hiddenStates, ComputeSystem &cs); /*! - \brief Experimental learning functionality. - Requires that reconstruct(...) has been called, without another call to activate(...). + \brief Learning functionality. \param alpha weight learning rate. \param gamma bias learning rate. \param cs compute system to be used. */ - void learn(float alpha, float gamma, ComputeSystem &cs); + void learn(float alpha, float gamma, float maxDistance, ComputeSystem &cs); + + /*! + \brief Save to file. + */ + void save(const std::string &fileName); + + /*1 + \brief Load from file. + */ + bool load(const std::string &fileName); //!@{ /*! @@ -191,8 +200,8 @@ namespace eogmaneo { return _hiddenStates; } - friend class RandomEncoderWorkItem; - friend class RandomDecoderWorkItem; - friend class RandomLearnWorkItem; + friend class KMeansEncoderWorkItem; + friend class KMeansDecoderWorkItem; + friend class KMeansLearnWorkItem; }; } diff --git a/source/optional/VisAdapter.cpp b/source/optional/VisAdapter.cpp index 572b8ab..ceafcbf 100644 --- a/source/optional/VisAdapter.cpp +++ b/source/optional/VisAdapter.cpp @@ -106,7 +106,7 @@ void VisAdapter::update(float waitSeconds) { ws._name = "ff_" + std::to_string(v); - ws._radius = _pHierarchy->getLayer(_caret._layer).getVisibleLayerDesc(v)._forwardRadius; + ws._radius = _pHierarchy->getLayer(_caret._layer).getVisibleLayerDesc(v)._radius; ws._weights = _pHierarchy->getLayer(_caret._layer).getFeedForwardWeights(v, caretX, caretY); @@ -120,7 +120,7 @@ void VisAdapter::update(float waitSeconds) { ws._name = "p_h_" + std::to_string(v); - ws._weights = _pHierarchy->getLayer(_caret._layer).getFeedBackWeights(v, 0, caretX, caretY); + ws._weights = _pHierarchy->getLayer(_caret._layer).getPredictionWeights(0, v, caretX, caretY); ws._radius = (std::sqrt(ws._weights.size()) - 1) / 2; @@ -133,7 +133,7 @@ void VisAdapter::update(float waitSeconds) { ws._name = "p_fb_" + std::to_string(v); - ws._weights = _pHierarchy->getLayer(_caret._layer).getFeedBackWeights(v, 1, caretX, caretY); + ws._weights = _pHierarchy->getLayer(_caret._layer).getPredictionWeights(1, v, caretX, caretY); ws._radius = (std::sqrt(ws._weights.size()) - 1) / 2; diff --git a/source/testing/TestSaveLoad.cpp b/source/testing/TestSaveLoad.cpp new file mode 100644 index 0000000..e3d4594 --- /dev/null +++ b/source/testing/TestSaveLoad.cpp @@ -0,0 +1,123 @@ +// ---------------------------------------------------------------------------- +// EOgmaNeo +// Copyright(c) 2017 Ogma Intelligent Systems Corp. All rights reserved. +// +// This copy of EOgmaNeo is licensed to you under the terms described +// in the EOGMANEO_LICENSE.md file included in this distribution. +// ---------------------------------------------------------------------------- + +#define _USE_MATH_DEFINES +#include + +#include +#include +#include +#include + +// define range function (only once) +template +std::vector range(T N1, T N2) { + std::vector numbers(N2 - N1); + iota(numbers.begin(), numbers.end(), N1); + return numbers; +} + +#include +using namespace eogmaneo; + +int main() { + const int hiddenWidth = 16; + const int hiddenHeight = 16; + + const int numLayers = 3; + const int layerSize = 16; + const int chunkSize = 8; + + const int unitsPerChunk = chunkSize; + + const std::vector bounds = { -1.0f, 1.0f }; + + ComputeSystem *system = new ComputeSystem(4); + + std::vector lds; + + for (int l = 0; l < numLayers; l++) + { + LayerDesc ld; + ld._width = layerSize; + ld._height = layerSize; + ld._chunkSize = chunkSize; + ld._alpha = 0.1f; + ld._beta = 0.01f; + ld._gamma = 0.0f; + ld._temporalHorizon = 2; + + lds.push_back(ld); + } + + std::vector> inputSizes; + inputSizes.push_back(std::pair{chunkSize, chunkSize}); + + std::vector inputChunkSizes; + inputChunkSizes.push_back(chunkSize); + + std::vector predictInputs; + predictInputs.push_back(true); + + Hierarchy h1; + h1.create(inputSizes, inputChunkSizes, predictInputs, lds, 123); + + Hierarchy h2; + h2.create(inputSizes, inputChunkSizes, predictInputs, lds, 123); + + // Present a sine wave sequence + std::vector arr = range(0, 5000); + for (auto t : arr) { + float valueToEncode = sinf(t * 0.02f * 2.0f * M_PI); + + std::vector> chunkedSDR(1); + chunkedSDR[0].push_back(int((valueToEncode - bounds[0]) / (bounds[1] - bounds[0]) * (unitsPerChunk - 1) + 0.5f)); + + h1.step(chunkedSDR, *system, true); + } + + h1.save("sineSave.eohr"); + h2.load("sineSave.eohr"); + + std::vector results1; + std::vector results2; + + // Recall the sequence + arr = range(0, 100); + for (auto t : arr) { + // First input layer prediction + const std::vector predSDR = h1.getPrediction(0); + + // Decode value + float value = predSDR[0] / float(unitsPerChunk - 1) * (bounds[1] - bounds[0]) + bounds[0]; + results1.push_back(value); + + std::vector> valueToEncode(1); + valueToEncode[0].push_back(value); + + h1.step(valueToEncode, *system, false); + } + + // Recall the sequence + for (auto t : arr) { + // First input layer prediction + const std::vector predSDR = h2.getPrediction(0); + + // Decode value + float value = predSDR[0] / float(unitsPerChunk - 1) * (bounds[1] - bounds[0]) + bounds[0]; + results2.push_back(value); + + std::vector> valueToEncode(1); + valueToEncode[0].push_back(value); + + h2.step(valueToEncode, *system, false); + } + + delete system; + return 0; +}