Restored unit tests

This commit is contained in:
emeric
2020-02-13 20:04:20 +01:00
parent 15e53caa2d
commit f44addc2a2
16 changed files with 54 additions and 11 deletions
+1 -5
View File
@@ -2,8 +2,6 @@
add_library(lmsrecommendation SHARED
impl/Engine.cpp
impl/ProviderCreator.cpp
impl/features/som/DataNormalizer.cpp
impl/features/som/Network.cpp
)
target_include_directories(lmsrecommendation INTERFACE
@@ -16,9 +14,7 @@ target_include_directories(lmsrecommendation PRIVATE
target_link_libraries(lmsrecommendation PRIVATE
lmsdatabase
)
target_link_libraries(lmsrecommendation PUBLIC
lmssom
)
install(TARGETS lmsrecommendation DESTINATION lib)
@@ -1,120 +0,0 @@
/*
* Copyright (C) 2018 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include "DataNormalizer.hpp"
#include <algorithm>
#include <numeric>
#include <sstream>
namespace SOM
{
template<typename T>
static
T
variance(const std::vector<T>& vec)
{
std::size_t size {vec.size()};
if (size == 1)
return T {};
const T mean {std::accumulate(vec.begin(), vec.end(), T{}) / size};
return std::accumulate(vec.begin(), vec.end(), T {},
[mean, size] (T accumulator, const T& val)
{
return accumulator + ((val - mean) * (val - mean) / (size - 1));
});
}
DataNormalizer::DataNormalizer(std::size_t inputDimCount)
: _inputDimCount{inputDimCount}
{
}
const DataNormalizer::MinMax&
DataNormalizer::getValue(std::size_t index) const
{
return _minmax[index];
}
void
DataNormalizer::setValue(std::size_t index, const MinMax& minMax)
{
_minmax[index] = minMax;
}
void
DataNormalizer::computeNormalizationFactors(const std::vector<InputVector>& inputVectors)
{
if (inputVectors.empty())
throw Exception("Empty input vectors");
// For each dimension of the input, compute the min/max
_minmax.clear();
_minmax.resize(_inputDimCount);
for (std::size_t dimId {}; dimId < _inputDimCount; ++dimId)
{
std::vector<InputVector::value_type> values;
for (const auto& inputVector: inputVectors)
{
checkSameDimensions(inputVector, _inputDimCount);
values.push_back(inputVector[dimId]);
}
auto result {std::minmax_element(values.begin(), values.end())};
_minmax[dimId] = {*result.first, *result.second};
}
}
InputVector::value_type
DataNormalizer::normalizeValue(InputVector::value_type value, std::size_t dimId) const
{
// clamp
if (value > _minmax[dimId].max)
value = _minmax[dimId].max;
else if (value < _minmax[dimId].min)
value = _minmax[dimId].min;
return (value - _minmax[dimId].min) / (_minmax[dimId].max - _minmax[dimId].min);
}
void
DataNormalizer::normalizeData(InputVector& a) const
{
checkSameDimensions(a, _inputDimCount);
for (std::size_t dimId {}; dimId < _inputDimCount; ++dimId)
{
a[dimId] = normalizeValue(a[dimId], dimId);
}
}
void
DataNormalizer::dump(std::ostream& os) const
{
for (std::size_t i {}; i < _inputDimCount; ++i)
os << "(" << _minmax[i].min << ", " << _minmax[i].max << ")";
}
} // namespace SOM
@@ -1,61 +0,0 @@
/*
* Copyright (C) 2018 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <vector>
#include <ostream>
#include "Network.hpp"
namespace SOM
{
class DataNormalizer
{
public:
struct MinMax
{
InputVector::value_type min;
InputVector::value_type max;
};
DataNormalizer(std::size_t inputDimCount);
std::size_t getInputDimCount() const { return _inputDimCount; }
const MinMax& getValue(std::size_t index) const;
void setValue(std::size_t index, const MinMax& minMax);
void computeNormalizationFactors(const std::vector<InputVector>& dataSamples);
void normalizeData(InputVector& data) const;
void dump(std::ostream& os) const;
private:
InputVector::value_type normalizeValue(InputVector::value_type value, std::size_t dimensionId) const;
const std::size_t _inputDimCount;
std::vector<MinMax> _minmax; // Indexed min/max used to normalize data
};
} // namespace SOM
@@ -1,194 +0,0 @@
/*
* Copyright (C) 2018 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <vector>
#include <cmath>
namespace SOM
{
class Exception : public LmsException
{
public:
Exception(const std::string& msg) : LmsException(msg) {}
};
class InputVector
{
public:
using value_type = double;
using Norm = double;
using Distance = double;
InputVector(std::size_t nbDimensions, value_type defaultValue = value_type {}) : _values(nbDimensions, defaultValue) {}
bool hasSameDimension(const InputVector& other) const
{
return _values.size() == other._values.size();
}
std::size_t getNbDimensions() const
{
return _values.size();
}
value_type& operator[](std::size_t index)
{
if (index >= getNbDimensions())
throw Exception("Bad range");
return _values[index];
}
value_type operator[](std::size_t index) const
{
if (index >= getNbDimensions())
throw Exception("Bad range");
return _values[index];
}
InputVector& operator+=(const InputVector& other)
{
if (!hasSameDimension(other.getNbDimensions()))
throw Exception {"Not the same dimension count"};
for (std::size_t i {}; i < _values.size(); ++i)
{
_values[i] += other[i];
}
return *this;
}
InputVector& operator-=(const InputVector& other)
{
if (!hasSameDimension(other.getNbDimensions()))
throw Exception {"Not the same dimension count"};
for (std::size_t i {}; i < _values.size(); ++i)
{
_values[i] -= other[i];
}
return *this;
}
InputVector& operator*=(value_type factor)
{
for (std::size_t i {}; i < _values.size(); ++i)
{
_values[i] *= factor;
}
return *this;
}
Norm computeNorm() const
{
Norm res {};
for (value_type val : _values)
res += val * val;
return std::sqrt(res);
}
Distance computeEuclidianSquareDistance(const InputVector& other, const InputVector& weights) const
{
if (!hasSameDimension(other.getNbDimensions())
|| !hasSameDimension(weights.getNbDimensions()))
{
throw Exception {"Not the same dimension count"};
}
Distance res {};
for (std::size_t i {}; i < getNbDimensions(); ++i)
{
const InputVector::value_type diff {_values[i] - other._values[i]};
res += diff * diff * weights._values[i];
}
return res;
}
std::vector<value_type>::iterator begin()
{
return _values.begin();
}
std::vector<value_type>::const_iterator begin() const
{
return _values.cbegin();
}
std::vector<value_type>::const_iterator cbegin() const
{
return _values.cbegin();
}
std::vector<value_type>::iterator end()
{
return _values.end();
}
std::vector<value_type>::const_iterator end() const
{
return _values.cend();
}
std::vector<value_type>::const_iterator cend() const
{
return _values.cend();
}
private:
friend class InputVector operator-(const InputVector& a, const InputVector& b)
{
if (!a.hasSameDimension(b.getNbDimensions()))
throw Exception {"Not the same dimension count"};
InputVector res {a.getNbDimensions()};
for (std::size_t i {}; i < res._values.size(); ++i)
res._values[i] = a._values[i] - b._values[i];
return res;
}
friend std::ostream&
operator<<(std::ostream& os, const InputVector& a)
{
os << "[";
for (value_type val : a._values)
{
os << val << " ";
}
os << "]";
return os;
}
std::vector<value_type> _values;
};
}
@@ -1,118 +0,0 @@
/*
* Copyright (C) 2018 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <algorithm>
#include <cassert>
#include <sstream>
#include <vector>
namespace SOM
{
using Coordinate = unsigned;
using Norm = InputVector::value_type;
struct Position
{
Coordinate x;
Coordinate y;
bool operator<(const Position& other) const
{
if (x == other.x)
return y < other.y;
else
return x < other.x;
}
bool operator==(const Position& other) const
{
return x == other.x && y == other.y;
}
};
template <typename T>
class Matrix
{
public:
Matrix() = default;
Matrix(Coordinate width, Coordinate height)
: _width{width},
_height{height}
{
_values.resize(_width*_height);
}
template<typename... CtArgs>
Matrix(Coordinate width, Coordinate height, CtArgs... args)
: _width{width},
_height{height}
{
_values.resize(_width*_height, T{args...});
}
void clear()
{
std::vector<T> values(_width*_height);
_values.swap(values);
}
Coordinate getHeight() const { return _height; }
Coordinate getWidth() const { return _width; }
T& get(const Position& position)
{
assert(position.x < _width);
assert(position.y < _height);
return _values[position.x + _width*position.y];
}
const T& get(const Position& position) const
{
assert(position.x < _width);
assert(position.y < _height);
return _values[position.x + _width*position.y];
}
T& operator[](const Position& position) { return get(position); }
const T& operator[](const Position& position) const { return get(position); }
template <typename Func>
Position getPositionMinElement(Func func) const
{
assert(!_values.empty());
auto it {std::min_element(_values.begin(), _values.end(), std::move(func))};
auto index {static_cast<Coordinate>(std::distance(_values.begin(), it))};
return {index % _height, index / _height};
}
private:
Coordinate _width {};
Coordinate _height {};
std::vector<T> _values;
};
} // ns SOM
@@ -1,336 +0,0 @@
/*
* Copyright (C) 2018 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Network.hpp"
#include <algorithm>
#include <chrono>
#include <cmath>
#include <random>
#include <sstream>
#include "utils/Logger.hpp"
namespace SOM
{
void
checkSameDimensions(const InputVector& a, const InputVector& b)
{
if (!a.hasSameDimension(b))
throw Exception("Bad data dimension count");
}
void
checkSameDimensions(const InputVector& a, std::size_t inputDimCount)
{
if (a.getNbDimensions() != inputDimCount)
throw Exception("Bad data dimension count");
}
static LearningFactor
defaultLearningFactor(Network::CurrentIteration iteration)
{
static const LearningFactor initialValue{1};
return initialValue * exp(-((iteration.idIteration + 1) / static_cast<LearningFactor>(iteration.iterationCount)));
}
static InputVector::Distance
euclidianSquareDistance(const InputVector& a, const InputVector& b, const InputVector& weights)
{
return a.computeEuclidianSquareDistance(b, weights);
}
static
InputVector::value_type
sigmaFunc(Network::CurrentIteration iteration)
{
constexpr InputVector::value_type sigma0 {1};
return sigma0 * std::exp(- ((iteration.idIteration + 1) / static_cast<InputVector::value_type>(iteration.iterationCount)));
}
static
InputVector::value_type
defaultNeighbourhoodFunc(Norm norm, const Network::CurrentIteration& iteration)
{
InputVector::value_type sigma {sigmaFunc(iteration)};
return exp(-norm / (2 * sigma * sigma));
}
Network::Network(Coordinate width, Coordinate height, std::size_t inputDimCount)
:
_inputDimCount(inputDimCount),
_weights(inputDimCount, static_cast<InputVector::value_type>(1)),
_refVectors(width, height, _inputDimCount),
_distanceFunc(euclidianSquareDistance),
_learningFactorFunc(defaultLearningFactor),
_neighbourhoodFunc(defaultNeighbourhoodFunc)
{
auto now {std::chrono::system_clock::now()};
std::mt19937 randGenerator {static_cast<std::mt19937::result_type>(std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()).count())};
// init each vector with a random normalized value
std::uniform_real_distribution<InputVector::value_type> dist{0, 1};
for (Coordinate y {}; y < _refVectors.getHeight(); ++y)
{
for (Coordinate x {}; x < _refVectors.getWidth(); ++x)
{
for (InputVector::value_type& val : _refVectors.get({x,y}))
val = dist(randGenerator);
}
}
}
void
Network::setDataWeights(const InputVector& weights)
{
checkSameDimensions(weights, _inputDimCount);
_weights = weights;
}
void
Network::setRefVector(const Position& position, const InputVector& data)
{
checkSameDimensions(data, _inputDimCount);
_refVectors[position] = data;
}
InputVector::Distance
Network::getRefVectorsDistance(const Position& position1, const Position& position2) const
{
return _distanceFunc(_refVectors.get(position1), _refVectors.get(position2), _weights);
}
InputVector::Distance
Network::computeRefVectorsDistanceMean() const
{
std::vector<InputVector::Distance> values;
values.reserve(2 * _refVectors.getHeight()*_refVectors.getWidth() - _refVectors.getWidth() - _refVectors.getHeight());
for (Coordinate y {}; y < _refVectors.getHeight(); ++y)
{
for (Coordinate x {}; x < _refVectors.getWidth(); ++x)
{
if (x != _refVectors.getWidth() - 1)
values.emplace_back(getRefVectorsDistance( {x, y}, {x + 1, y}));
if (y != _refVectors.getHeight() - 1)
values.emplace_back(getRefVectorsDistance( {x, y}, {x, y + 1}));
}
}
return std::accumulate(values.begin(), values.end(), 0.) / values.size();
}
double
Network::computeRefVectorsDistanceMedian() const
{
std::vector<InputVector::Distance> values;
values.reserve(2*_refVectors.getHeight()*_refVectors.getWidth() - _refVectors.getWidth() - _refVectors.getHeight());
for (Coordinate y {}; y < _refVectors.getHeight(); ++y)
{
for (Coordinate x {}; x < _refVectors.getWidth(); ++x)
{
if (x != _refVectors.getWidth() - 1)
values.emplace_back(getRefVectorsDistance( {x, y}, {x + 1, y}));
if (y != _refVectors.getHeight() - 1)
values.emplace_back(getRefVectorsDistance( {x, y}, {x, y + 1}));
}
}
std::sort(values.begin(), values.end());
return values[values.size() > 1 ? values.size()/2 - 1 : 0];
}
void
Network::dump(std::ostream& os) const
{
os << "Width: " << _refVectors.getWidth() << ", Height: " << _refVectors.getHeight() << std::endl;;
for (Coordinate y {}; y < _refVectors.getHeight(); ++y)
{
for (Coordinate x {}; x < _refVectors.getWidth(); ++x)
{
os << _refVectors.get({x, y}) << " ";
}
os << std::endl;
}
os << std::endl;
}
Position
Network::getClosestRefVectorPosition(const InputVector& data) const
{
return _refVectors.getPositionMinElement([&](const auto& a, const auto& b)
{
return (_distanceFunc(a, data, _weights) < _distanceFunc(b, data, _weights));
});
}
std::optional<Position>
Network::getClosestRefVectorPosition(const InputVector& data, InputVector::Distance maxDistance) const
{
std::optional<Position> position {getClosestRefVectorPosition(data)};
if (_distanceFunc(data, _refVectors.get(*position), _weights) > maxDistance)
position.reset();
return position;
}
std::optional<Position>
Network::getClosestRefVectorPosition(const std::set<Position>& refVectorsPosition, InputVector::Distance maxDistance) const
{
std::set<Position> neighboursPosition;
for (const Position& refVectorPosition : refVectorsPosition)
{
if (refVectorPosition.y > 0)
neighboursPosition.insert({ refVectorPosition.x, refVectorPosition.y - 1 });
if (refVectorPosition.y < _refVectors.getHeight() - 1)
neighboursPosition.insert({ refVectorPosition.x, refVectorPosition.y + 1 });
if (refVectorPosition.x > 0)
neighboursPosition.insert({ refVectorPosition.x - 1, refVectorPosition.y });
if (refVectorPosition.x < _refVectors.getWidth() - 1)
neighboursPosition.insert({ refVectorPosition.x + 1, refVectorPosition.y });
}
// remove position that are in the input position
for (const auto& refVectorPosition : refVectorsPosition)
neighboursPosition.erase(refVectorPosition);
if (neighboursPosition.empty())
return std::nullopt;
// Now compute the distance for each neighbour
struct NeighbourInfo
{
Position position;
double distance;
};
std::vector<NeighbourInfo> neighboursInfo;
for (const Position& neighbourPosition : neighboursPosition)
{
auto min = std::min_element(refVectorsPosition.begin(), refVectorsPosition.end(),
[this, neighbourPosition](const auto& a, const auto& b)
{
return (this->getRefVectorsDistance(a, neighbourPosition) < this->getRefVectorsDistance(b, neighbourPosition));
});
InputVector::Distance distance {getRefVectorsDistance(neighbourPosition, *min)};
if (distance > maxDistance)
continue;
neighboursInfo.emplace_back(NeighbourInfo {neighbourPosition, distance});
}
if (neighboursInfo.empty())
return std::nullopt;
auto min {std::min_element(std::cbegin(neighboursInfo), std::cend(neighboursInfo),
[&](const auto& a, const auto& b)
{
return a.distance < b.distance;
})};
return min->position;
}
static Norm
computePositionNorm(const Position& c1, const Position& c2)
{
return std::sqrt((c1.x - c2.x) * (c1.x - c2.x) + (c1.y - c2.y) * (c1.y - c2.y));
}
void
Network::updateRefVectors(const Position& closestRefVectorPosition, const InputVector& input, LearningFactor learningFactor, const CurrentIteration& iteration)
{
for (Coordinate y {}; y < _refVectors.getHeight(); ++y)
{
for (Coordinate x {}; x < _refVectors.getWidth(); ++x)
{
InputVector& refVector {_refVectors.get({x, y})};
const Norm norm {computePositionNorm({x, y}, closestRefVectorPosition)};
InputVector delta {input - refVector};
delta *= (learningFactor * _neighbourhoodFunc(norm, iteration));
refVector += delta;
}
}
}
void
Network::train(const std::vector<InputVector>& inputData, std::size_t nbIterations, ProgressCallback progressCallback, RequestStopCallback requestStopCallback)
{
bool stopRequested {false};
std::vector<const InputVector*> inputDataShuffled;
inputDataShuffled.reserve(inputData.size());
for (const auto& input : inputData)
inputDataShuffled.push_back(&input);
auto now {std::chrono::system_clock::now()};
std::mt19937 randGenerator{static_cast<std::mt19937::result_type>(std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()).count())};
for (std::size_t i {}; i < nbIterations; ++i)
{
CurrentIteration curIter {i, nbIterations};
if (progressCallback)
progressCallback(curIter);
std::shuffle(inputDataShuffled.begin(), inputDataShuffled.end(), randGenerator);
const LearningFactor learningFactor {_learningFactorFunc(curIter)};
for (const InputVector* input : inputDataShuffled)
{
if (requestStopCallback)
stopRequested = requestStopCallback();
if (stopRequested)
return;
updateRefVectors(getClosestRefVectorPosition(*input), *input, learningFactor, curIter);
}
if (stopRequested)
return;
}
}
const InputVector&
Network::getRefVector(const Position& position) const
{
return _refVectors[position];
}
} // namespace SOM
@@ -1,110 +0,0 @@
/*
* Copyright (C) 2018 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <vector>
#include <set>
#include <optional>
#include <ostream>
#include <functional>
#include "utils/Exception.hpp"
#include "InputVector.hpp"
#include "Matrix.hpp"
namespace SOM
{
using LearningFactor = InputVector::value_type;
void checkSameDimensions(const InputVector& a, const InputVector& b);
void checkSameDimensions(const InputVector& a, std::size_t inputDimCount);
std::ostream& operator<<(std::ostream& os, const InputVector& a);
class Network
{
public:
// Init a network with random values
Network(Coordinate width, Coordinate height, std::size_t inputDimCount);
Coordinate getWidth() const { return _refVectors.getWidth(); }
Coordinate getHeight() const { return _refVectors.getHeight(); }
std::size_t getInputDimCount() const { return _inputDimCount; }
const InputVector& getDataWeights() const { return _weights; }
// Set weight for each dimension (default is 1 for each weight)
void setDataWeights(const InputVector& weights);
// use this to manually construct a network without training
void setRefVector(const Position& position, const InputVector& data);
// <!> data must be normalized
struct CurrentIteration
{
std::size_t idIteration;
std::size_t iterationCount;
};
using ProgressCallback = std::function<void(const CurrentIteration&)>;
using RequestStopCallback = std::function<bool()>;
void train(const std::vector<InputVector>& dataSamples, std::size_t nbIterations, ProgressCallback = ProgressCallback{}, RequestStopCallback = RequestStopCallback{});
const InputVector& getRefVector(const Position& position) const;
Position getClosestRefVectorPosition(const InputVector& data) const;
std::optional<Position> getClosestRefVectorPosition(const InputVector& data, InputVector::Distance maxDistance) const;
std::optional<Position> getClosestRefVectorPosition(const std::set<Position>& refVectorsPosition, InputVector::Distance maxDistance) const;
InputVector::Distance getRefVectorsDistance(const Position& position1, const Position& position2) const;
InputVector::Distance computeRefVectorsDistanceMean() const;
InputVector::Distance computeRefVectorsDistanceMedian() const;
void dump(std::ostream& os) const;
// For each ref vector, update formula is:
// i is the current iteration
// refVector(i+1) = refVector(i) + LearningFactor(i) * NeighbourhoodFunc(i) * (MatchingRefVector - refVector)
using DistanceFunc = std::function<InputVector::Distance(const InputVector& /* a */, const InputVector& /* b */, const InputVector& /* weights */)>;
void setDistanceFunc(DistanceFunc distanceFunc);
DistanceFunc getDistanceFunc() { return _distanceFunc; }
using LearningFactorFunc = std::function<LearningFactor(const CurrentIteration&)>;
void setLearningFactorFunc(LearningFactorFunc learningFactorFunc);
using NeighbourhoodFunc = std::function<InputVector::value_type(Norm /* norm(Position - CoordMatchingRefVector) */, const CurrentIteration&)>;
void setNeighbourhoodFunc(NeighbourhoodFunc neighbourhoodFunc);
private:
void updateRefVectors(const Position& closestRefVectorPosition, const InputVector& input, LearningFactor learningFactor, const CurrentIteration& iteration);
std::size_t _inputDimCount {};
InputVector _weights; // weight for each dimension
Matrix<InputVector> _refVectors;
DistanceFunc _distanceFunc;
LearningFactorFunc _learningFactorFunc;
NeighbourhoodFunc _neighbourhoodFunc;
};
} // namespace SOM