Skip to content

Commit

Permalink
[ShogunBoard] Add a ParameterObserver (interface and implementation) …
Browse files Browse the repository at this point in the history
…and add TBOutputFormat.

Add interface and ParameterObserverScalar which are the classes needed to
add parameter watchers to an algorithm. Add also TBOutputFormat class
which converts an ObservedValue into a tensorflow::Event object.

Other:
* Add unit tests (ParameterObserverScalar and TBOutputFormat);
* Add CMake switch to find TFLogger (or download and build it if not present);
* Add SWIG code to make ParameterObserver visible to interfaces;
  • Loading branch information
geektoni authored and vigsterkr committed Jul 8, 2017
1 parent f9c53ff commit 758ab65
Show file tree
Hide file tree
Showing 14 changed files with 483 additions and 15 deletions.
21 changes: 21 additions & 0 deletions cmake/external/TFLogger.cmake
@@ -0,0 +1,21 @@
GetCompilers()

include(ExternalProject)
ExternalProject_Add(
rxcpp
PREFIX ${CMAKE_BINARY_DIR}/tflogger
DOWNLOAD_DIR ${THIRD_PARTY_DIR}/tflogger
URL https://github.com/shogun-toolbox/tflogger/archive/master.zip
CMAKE_ARGS
-DCMAKE_INSTALL_PREFIX:STRING=${CMAKE_BINARY_DIR}/src/shogun/lib/external
-DCMAKE_C_COMPILER:STRING=${C_COMPILER}
-DCMAKE_CXX_COMPILER:STRING=${CXX_COMPILER}
BUILD_COMMAND ""
)

add_dependencies(libshogun tflogger)

set(TFLogger_INCLUDE_DIR ${THIRD_PARTY_INCLUDE_DIR})

UNSET(C_COMPILER)
UNSET(CXX_COMPILER)
11 changes: 11 additions & 0 deletions src/interfaces/swig/ParameterObserver.i
@@ -0,0 +1,11 @@
%include "std_vector.i"
%include "std_string.i"
%template(ParameterList) std::vector<std::string>;

%{
#include <shogun/lib/ParameterObserverInterface.h>
#include <shogun/lib/ParameterObserverScalar.h>
%}

%include <shogun/lib/ParameterObserverInterface.h>
%include <shogun/lib/ParameterObserverScalar.h>
1 change: 1 addition & 0 deletions src/interfaces/swig/shogun.i
Expand Up @@ -83,6 +83,7 @@
%include "NeuralNets_includes.i"
%include "bagging_includes.i"
%include "Boost_includes.i"
%include "ParameterObserver.i"

%include "SGBase.i"
%include "Machine.i"
Expand Down
14 changes: 14 additions & 0 deletions src/shogun/CMakeLists.txt
Expand Up @@ -286,6 +286,20 @@ ELSE()
SHOGUN_INCLUDE_DIRS(SCOPE PUBLIC SYSTEM ${rxcpp_INCLUDE_DIR})
ENDIF()

# TFLogger package
FIND_PACKAGE(TFLogger 0.1.0 CONFIG)
IF (TFLogger_FOUND)
SHOGUN_INCLUDE_DIRS(SCOPE PRIVATE SYSTEM ${TFLogger_INCLUDE_DIR})
target_link_libraries(shogun PRIVATE tflogger::tflogger)
ELSE()
include(external/tflogger)
SHOGUN_INCLUDE_DIRS(SCOPE PRIVATE SYSTEM
$<BUILD_INTERFACE:${TFLogger_INCLUDE_DIR}>
$<INSTALL_INTERFACE:include/shogun/lib/external/tflogger>
)
target_link_libraries(shogun PRIVATE tflogger::tflogger)
ENDIF()

# prefer original LAPACK, if needed
OPTION(USE_ORIGINAL_LAPACK "Original LAPACK" OFF)
FIND_PACKAGE(LAPACK)
Expand Down
21 changes: 19 additions & 2 deletions src/shogun/base/SGObject.cpp
Expand Up @@ -807,7 +807,24 @@ bool CSGObject::type_erased_has(const BaseTag& _tag) const
return self->has(_tag);
}

void CSGObject::observe_scalar(const std::string& name, const Any& value)
void CSGObject::subscribe_to_parameters(ParameterObserverInterface* obs)
{
m_subscriber_params.on_next(std::make_pair(name, value));
auto sub = rxcpp::make_subscriber<ParameterObserverInterface::ObservedValue>(
[obs](ParameterObserverInterface::ObservedValue e) { obs->on_next(e); },
[obs](std::exception_ptr ep) { obs->on_error(ep); },
[obs]() { obs->on_complete(); });

// Create an observable which emits values only if they are about
// parameters selected by the observable.
auto subscription = m_observable_params
.filter([obs](ParameterObserverInterface::ObservedValue v) {
return obs->filter(v.second.first);
})
.subscribe(sub);
}

void CSGObject::observe_scalar(const int64_t step, const std::string& name, const Any& value)
{
auto tmp = std::make_pair(step, std::make_pair(name, value));
m_subscriber_params.on_next(tmp);
}
25 changes: 12 additions & 13 deletions src/shogun/base/SGObject.h
Expand Up @@ -13,15 +13,16 @@
#ifndef __SGOBJECT_H__
#define __SGOBJECT_H__

#include <shogun/lib/config.h>
#include <shogun/lib/common.h>
#include <shogun/lib/DataType.h>
#include <shogun/lib/ShogunException.h>
#include <shogun/base/Version.h>
#include <shogun/base/unique.h>
#include <shogun/io/SGIO.h>
#include <shogun/lib/tag.h>
#include <shogun/lib/DataType.h>
#include <shogun/lib/ParameterObserverInterface.h>
#include <shogun/lib/ShogunException.h>
#include <shogun/lib/any.h>
#include <shogun/lib/common.h>
#include <shogun/lib/config.h>
#include <shogun/lib/tag.h>

#include <rxcpp/rx.hpp>
#include <utility>
Expand Down Expand Up @@ -402,16 +403,14 @@ class CSGObject
* Get parameters observable
* @return RxCpp observable
*/
rxcpp::observable<std::pair<std::string, Any>> get_parameters_observable()
rxcpp::observable<ParameterObserverInterface::ObservedValue> get_parameters_observable()
{
return m_observable_params;
};
#endif

/** Subscribe a parameter observer to watch over params */
void subscribe_to_parameters()
{
}
void subscribe_to_parameters(ParameterObserverInterface* obs);

protected:
/** Can (optionally) be overridden to pre-initialize some member
Expand Down Expand Up @@ -568,7 +567,7 @@ class CSGObject
/** Observe the parameter and emits a value using the
* observable object
*/
void observe_scalar(const std::string& name, const Any& value);
void observe_scalar(const int64_t step, const std::string& name, const Any& value);

public:
/** io */
Expand Down Expand Up @@ -603,13 +602,13 @@ class CSGObject
RefCount* m_refcount;

/** Subject used to create the params observer */
rxcpp::subjects::subject<std::pair<std::string, Any>> m_subject_params;
rxcpp::subjects::subject<ParameterObserverInterface::ObservedValue> m_subject_params;

/** Parameter Observable */
rxcpp::observable<std::pair<std::string, Any>> m_observable_params;
rxcpp::observable<ParameterObserverInterface::ObservedValue> m_observable_params;

/** Subscriber used to call onNext, onComplete etc.*/
rxcpp::subscriber<std::pair<std::string, Any>> m_subscriber_params;
rxcpp::subscriber<ParameterObserverInterface::ObservedValue> m_subscriber_params;
};
}
#endif // __SGOBJECT_H__
59 changes: 59 additions & 0 deletions src/shogun/io/TBOutputFormat.cpp
@@ -0,0 +1,59 @@
/*
* Written (W) 2017 Giovanni De Toni
*/

#include <chrono>
#include <shogun/io/TBOutputFormat.h>
#include <shogun/lib/common.h>

using namespace shogun;

#define CHECK_TYPE(type)\
else if (\
value.second.type_info().hash_code() == typeid(type).hash_code())\
{\
summaryValue->set_simple_value(recall_type<type>(value.second));\
}

TBOutputFormat::TBOutputFormat(){};

TBOutputFormat::~TBOutputFormat(){};

tensorflow::Event TBOutputFormat::convert_scalar(
const int64_t& event_step, const std::pair<std::string, Any>& value,
std::string& node_name)
{
auto millisec = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::system_clock::now().time_since_epoch())
.count();

tensorflow::Event e;
e.set_wall_time(millisec);
e.set_step(event_step);

tensorflow::Summary* summary = e.mutable_summary();
auto summaryValue = summary->add_value();
summaryValue->set_tag(value.first);
summaryValue->set_node_name(node_name);

if (value.second.type_info().hash_code() == typeid(int8_t).hash_code())
{
summaryValue->set_simple_value(recall_type<int8_t>(value.second));
}
CHECK_TYPE(uint8_t)
CHECK_TYPE(int16_t)
CHECK_TYPE(uint16_t)
CHECK_TYPE(int32_t)
CHECK_TYPE(uint32_t)
CHECK_TYPE(int64_t)
CHECK_TYPE(uint64_t)
CHECK_TYPE(float32_t)
CHECK_TYPE(float64_t)
CHECK_TYPE(floatmax_t)
CHECK_TYPE(char)
else {
SG_ERROR("Unsupported type %s", value.second.type_info().name());
}

return e;
}
45 changes: 45 additions & 0 deletions src/shogun/io/TBOutputFormat.h
@@ -0,0 +1,45 @@
/*
* Written (W) 2017 Giovanni De Toni
*/

#ifndef SHOGUN_OUTPUTFORMAT_H
#define SHOGUN_OUTPUTFORMAT_H

#include <shogun/base/SGObject.h>
#include <shogun/lib/any.h>
#include <tflogger/event.pb.h>

#include <utility>

namespace shogun
{
/**
* Convert an std::pair<std::string, Any> to a tensorflow::Event,
* which can be written to file and used with tools like Tensorboard.
*/
class TBOutputFormat : public CSGObject
{

public:
TBOutputFormat();
~TBOutputFormat();

/**
* Generate a tensorflow::Event object give some informations
* @param event_step the current event step
* @param value the value which will be converted to tensorflow::Event
* @param node_name the node name (default: node)
* @return the newly created tensorflow::Event
*/
tensorflow::Event convert_scalar(
const int64_t& event_step, const std::pair<std::string, Any>& value,
std::string& node_name);

virtual const char * get_name() const
{
return "TFLogger";
}
};
}

#endif // SHOGUN_OUTPUTFORMAT_H
24 changes: 24 additions & 0 deletions src/shogun/lib/ParameterObserverInterface.cpp
@@ -0,0 +1,24 @@
#include <shogun/lib/ParameterObserverInterface.h>

using namespace shogun;

ParameterObserverInterface::ParameterObserverInterface()
: m_parameters(), m_writer("shogun")
{
}

ParameterObserverInterface::ParameterObserverInterface(
std::vector<std::string>& parameters)
: m_parameters(parameters), m_writer("shogun")
{
}

ParameterObserverInterface::ParameterObserverInterface(
const std::string& filename, std::vector<std::string>& parameters)
: m_parameters(parameters), m_writer(filename.c_str())
{
}

ParameterObserverInterface::~ParameterObserverInterface()
{
}
86 changes: 86 additions & 0 deletions src/shogun/lib/ParameterObserverInterface.h
@@ -0,0 +1,86 @@
#ifndef SHOGUN_PARAMETEROBSERVERINTERFACE_H
#define SHOGUN_PARAMETEROBSERVERINTERFACE_H

#include <stdexcept>
#include <utility>
#include <vector>

#include <rxcpp/rx-observable.hpp>
#include <shogun/lib/any.h>
#include <tflogger/tensorflow_logger.h>

namespace shogun
{
/**
* Interface for the parameter observer classes
*/
class ParameterObserverInterface
{

public:

/* One observed value, composed of:
* - step (for the graph x axis);
* - a pair composed of: parameter's name + parameter's value
*/
typedef std::pair<int64_t, std::pair<std::string, Any>> ObservedValue;

/**
* Default constructor
*/
ParameterObserverInterface();

/**
* Constructor
* @param parameters list of parameters which we want to watch over
*/
ParameterObserverInterface(std::vector<std::string>& parameters);

/**
* Constructor
* @param filename name of the generated output file
* @param parameters list of parameters which we want to watch over
*/
ParameterObserverInterface(
const std::string& filename, std::vector<std::string>& parameters);
/**
* Virtual destructor
*/
virtual ~ParameterObserverInterface();

/**
* Filter function, check if the parameter name supplied is what
* we want to monitor
* @param param the param name
* @return true if param is found inside of m_parameters list
*/
virtual bool filter(const std::string& param) = 0;

/**
* Method which will be called when the parameter observable emits a
* value.
* @param value the value emitted by the parameter observable
*/
virtual void on_next(const ObservedValue& value) = 0;
/**
* Method which will be called on errors
*/
virtual void on_error(std::exception_ptr) = 0;
/**
* Method which will be called on completion
*/
virtual void on_complete() = 0;

protected:
/**
* List of parameter's names we want to monitor
*/
std::vector<std::string> m_parameters;
/**
* Writer object which will be used to write tensorflow::Event files
*/
tflogger::TensorFlowLogger m_writer;
};
}

#endif // SHOGUN_PARAMETEROBSERVER_H

0 comments on commit 758ab65

Please sign in to comment.