WIP on the clusterer
This commit is contained in:
@@ -0,0 +1,181 @@
|
||||
/*
|
||||
* 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 <cmath>
|
||||
#include "SOM.hpp"
|
||||
#include "DataNormalizer.hpp"
|
||||
|
||||
/*
|
||||
* For each InputVector, associate vector<T> values
|
||||
*/
|
||||
template<typename T>
|
||||
class Clusterer
|
||||
{
|
||||
public:
|
||||
using SampleType = std::pair<SOM::InputVector /* key */, T /* value*/ >;
|
||||
using Cluster = std::vector<T>;
|
||||
|
||||
Clusterer(const std::vector<SampleType>& samples, std::size_t inputDimCount, std::size_t iterationCount);
|
||||
|
||||
const Cluster& getCluster(const SOM::InputVector& data) const;
|
||||
|
||||
// Sorted results (best first)
|
||||
std::vector<Cluster> getClusters(const SOM::InputVector& data, std::size_t nbClusters) const;
|
||||
|
||||
const std::vector<Cluster>& getAllClusters() const;
|
||||
|
||||
void dump(std::ostream& os) const;
|
||||
|
||||
private:
|
||||
|
||||
void train(const std::vector<std::pair<SOM::InputVector, T>>& samples, std::size_t iterationCount);
|
||||
|
||||
std::vector<T>& getValues(SOM::Coords coords);
|
||||
const std::vector<T>& getValues(SOM::Coords coords) const;
|
||||
|
||||
std::size_t _width;
|
||||
std::size_t _height;
|
||||
std::vector<std::vector<T>> _values; // Map of T vectors
|
||||
SOM::DataNormalizer _dataNormalizer;
|
||||
SOM::Network _network;
|
||||
};
|
||||
|
||||
|
||||
template<typename T>
|
||||
Clusterer<T>::Clusterer(const std::vector<SampleType>& samples, std::size_t inputDimCount, std::size_t iterationCount)
|
||||
:
|
||||
_width(std::sqrt(samples.size()/20)),
|
||||
_height(std::sqrt(samples.size()/20)),
|
||||
_dataNormalizer(inputDimCount),
|
||||
_network(_width, _height, inputDimCount)
|
||||
{
|
||||
_values.resize(_width * _height);
|
||||
train(samples, iterationCount);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
std::vector<T>&
|
||||
Clusterer<T>::getValues(SOM::Coords coords)
|
||||
{
|
||||
return _values[ coords.x + coords.y*_width ];
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
const std::vector<T>&
|
||||
Clusterer<T>::getValues(SOM::Coords coords) const
|
||||
{
|
||||
return _values[ coords.x + coords.y*_width ];
|
||||
}
|
||||
|
||||
|
||||
template<typename T>
|
||||
void
|
||||
Clusterer<T>::train(const std::vector<std::pair<SOM::InputVector, T>>& samples, std::size_t iterationCount)
|
||||
{
|
||||
// Train
|
||||
{
|
||||
std::vector<SOM::InputVector> inputVectors;
|
||||
inputVectors.reserve(samples.size());
|
||||
|
||||
for (const auto& sample : samples)
|
||||
{
|
||||
inputVectors.push_back(sample.first);
|
||||
}
|
||||
|
||||
_dataNormalizer.computeNormalizationFactors(inputVectors);
|
||||
|
||||
for (auto& inputVector : inputVectors)
|
||||
_dataNormalizer.normalizeData(inputVector);
|
||||
|
||||
_network.train(inputVectors, iterationCount);
|
||||
}
|
||||
|
||||
// Classify data
|
||||
for (const auto& sample : samples)
|
||||
{
|
||||
auto inputVector = sample.first;
|
||||
const auto& value = sample.second;
|
||||
|
||||
_dataNormalizer.normalizeData(inputVector);
|
||||
auto coords = _network.classify(inputVector);
|
||||
auto& values = getValues(coords);
|
||||
|
||||
values.push_back(value);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
const typename Clusterer<T>::Cluster&
|
||||
Clusterer<T>::getCluster(const SOM::InputVector& inputVector) const
|
||||
{
|
||||
auto inputVectorNormalized = inputVector;
|
||||
_dataNormalizer.normalizeData(inputVectorNormalized);
|
||||
|
||||
return getValues(_network.classify(inputVectorNormalized));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
std::vector<typename Clusterer<T>::Cluster>
|
||||
Clusterer<T>::getClusters(const SOM::InputVector& inputVector, std::size_t nbClusters) const
|
||||
{
|
||||
auto inputVectorNormalized = inputVector;
|
||||
_dataNormalizer.normalizeData(inputVectorNormalized);
|
||||
|
||||
std::vector<typename Clusterer<T>::Cluster> res;
|
||||
for (auto& cluster : _network.classify(inputVectorNormalized, nbClusters))
|
||||
{
|
||||
res.push_back(getValues(cluster));
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
const std::vector<typename Clusterer<T>::Cluster>&
|
||||
Clusterer<T>::getAllClusters() const
|
||||
{
|
||||
return _values;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void
|
||||
Clusterer<T>::dump(std::ostream& os) const
|
||||
{
|
||||
os << "Normalizer:" << std::endl;
|
||||
_dataNormalizer.dump(os);
|
||||
os << std::endl;
|
||||
os << "Internal network:" << std::endl;
|
||||
_network.dump(os);
|
||||
os << "Values: " << std::endl;
|
||||
for (std::size_t y = 0; y < _height; ++y)
|
||||
{
|
||||
for (std::size_t x = 0; x < _width; ++x)
|
||||
{
|
||||
os << "[";
|
||||
for (const auto& value : getValues({x, y}))
|
||||
os << value << " ";
|
||||
os << "] ";
|
||||
}
|
||||
os << std::endl;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* 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>
|
||||
|
||||
namespace SOM
|
||||
{
|
||||
|
||||
template<typename T>
|
||||
static
|
||||
T
|
||||
variance(const std::vector<T>& vec)
|
||||
{
|
||||
std::size_t size = vec.size();
|
||||
|
||||
if (size == 1)
|
||||
return T{0.};
|
||||
|
||||
T mean = std::accumulate(vec.begin(), vec.end(), T{0.}) / size;
|
||||
|
||||
return std::accumulate(vec.begin(), vec.end(), T{0.},
|
||||
[mean, size] (T accumulator, const T& val)
|
||||
{
|
||||
return accumulator + ((val - mean) * (val - mean) / (size - 1));
|
||||
});
|
||||
}
|
||||
|
||||
DataNormalizer::DataNormalizer(std::size_t inputDimCount)
|
||||
: _inputDimCount(inputDimCount)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
DataNormalizer::computeNormalizationFactors(const std::vector<InputVector>& inputVectors)
|
||||
{
|
||||
if (inputVectors.empty())
|
||||
throw SOMException("Empty input vectors");
|
||||
|
||||
// For each dimension of the input, compute the min/max
|
||||
_minmax.clear();
|
||||
_minmax.resize(_inputDimCount);
|
||||
|
||||
for (std::size_t dimId = 0; 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 = 0; dimId < _inputDimCount; ++dimId)
|
||||
{
|
||||
a[dimId] = normalizeValue(a[dimId], dimId);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
DataNormalizer::dump(std::ostream& os) const
|
||||
{
|
||||
for (std::size_t i = 0; i < _inputDimCount; ++i)
|
||||
os << "(" << _minmax[i].min << ", " << _minmax[i].max << ")";
|
||||
}
|
||||
|
||||
} // namespace SOM
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* 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 "SOM.hpp"
|
||||
|
||||
namespace SOM
|
||||
{
|
||||
|
||||
class DataNormalizer
|
||||
{
|
||||
public:
|
||||
DataNormalizer(std::size_t inputDimCount);
|
||||
|
||||
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;
|
||||
|
||||
std::size_t _inputDimCount;
|
||||
|
||||
struct minmax
|
||||
{
|
||||
InputVector::value_type min;
|
||||
InputVector::value_type max;
|
||||
};
|
||||
std::vector<minmax> _minmax; // Indexed min/max used to normalize data
|
||||
};
|
||||
|
||||
} // namespace SOM
|
||||
@@ -0,0 +1,340 @@
|
||||
/*
|
||||
* 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 "SOM.hpp"
|
||||
|
||||
#include <chrono>
|
||||
#include <random>
|
||||
#include <cmath>
|
||||
#include <algorithm>
|
||||
|
||||
namespace SOM
|
||||
{
|
||||
|
||||
void
|
||||
checkSameDimensions(const InputVector& a, const InputVector& b)
|
||||
{
|
||||
if (a.size() != b.size())
|
||||
throw SOMException("Bad data dimension count");
|
||||
}
|
||||
|
||||
void
|
||||
checkSameDimensions(const InputVector& a, std::size_t inputDimCount)
|
||||
{
|
||||
if (a.size() != inputDimCount)
|
||||
throw SOMException("Bad data dimension count");
|
||||
}
|
||||
|
||||
InputVector::value_type
|
||||
defaultLearningFactor(Network::Progress progress)
|
||||
{
|
||||
constexpr InputVector::value_type initialValue = 1;
|
||||
|
||||
return initialValue * exp(-((progress.idIteration + 1) / static_cast<InputVector::value_type>(progress.iterationCount)));
|
||||
}
|
||||
|
||||
InputVector::value_type
|
||||
euclidianSquareDistance(const InputVector& a, const InputVector& b, const InputVector& weights)
|
||||
{
|
||||
checkSameDimensions(a, b);
|
||||
checkSameDimensions(a, weights);
|
||||
|
||||
InputVector::value_type res = 0;
|
||||
|
||||
for (std::size_t i = 0; i < a.size(); ++i)
|
||||
{
|
||||
res += (a[i] - b[i]) * (a[i] - b[i]) * weights[i];
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static
|
||||
InputVector::value_type
|
||||
sigmaFunc(Network::Progress progress)
|
||||
{
|
||||
constexpr InputVector::value_type sigma0 = 1;
|
||||
|
||||
return sigma0 * exp(- ((progress.idIteration + 1) / static_cast<InputVector::value_type>(progress.iterationCount)));
|
||||
}
|
||||
|
||||
InputVector::value_type
|
||||
defaultNeighborhoodFunc(InputVector::value_type norm, Network::Progress progress)
|
||||
{
|
||||
auto sigma = sigmaFunc(progress);
|
||||
|
||||
return exp(-norm / (2 * sigma * sigma));
|
||||
}
|
||||
|
||||
|
||||
std::ostream&
|
||||
operator<<(std::ostream& os, const InputVector& a)
|
||||
{
|
||||
os << "[";
|
||||
for (const auto& val : a)
|
||||
{
|
||||
os << val << " ";
|
||||
}
|
||||
os << "]";
|
||||
|
||||
return os;
|
||||
}
|
||||
|
||||
|
||||
//static
|
||||
InputVector::value_type
|
||||
norm(const InputVector& a)
|
||||
{
|
||||
InputVector::value_type res = 0;
|
||||
|
||||
for (const auto& val : a)
|
||||
{
|
||||
res += val * val;
|
||||
}
|
||||
|
||||
return sqrt(res);
|
||||
}
|
||||
|
||||
//static
|
||||
InputVector
|
||||
operator+(const InputVector& a, const InputVector& b)
|
||||
{
|
||||
checkSameDimensions(a, b);
|
||||
|
||||
InputVector res(a.size(), 0);
|
||||
|
||||
for (std::size_t dimId = 0; dimId < a.size(); ++dimId)
|
||||
{
|
||||
res[dimId] = a[dimId] + b[dimId];
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static
|
||||
InputVector
|
||||
operator-(const InputVector& a, const InputVector& b)
|
||||
{
|
||||
checkSameDimensions(a, b);
|
||||
|
||||
InputVector res(a.size(), 0);
|
||||
|
||||
for (std::size_t dimId = 0; dimId < a.size(); ++dimId)
|
||||
{
|
||||
res[dimId] = a[dimId] - b[dimId];
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
//static
|
||||
InputVector
|
||||
operator*(const InputVector& a, InputVector::value_type factor)
|
||||
{
|
||||
InputVector res(a.size(), 0);
|
||||
|
||||
for (std::size_t dimId = 0; dimId < a.size(); ++dimId)
|
||||
{
|
||||
res[dimId] = a[dimId] * factor;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
Network::Network(std::size_t width, std::size_t height, std::size_t inputDimCount)
|
||||
: _width(width),
|
||||
_height(height),
|
||||
_inputDimCount(inputDimCount),
|
||||
_weights(inputDimCount, static_cast<InputVector::value_type>(1)),
|
||||
_distanceFunc(euclidianSquareDistance),
|
||||
_learningFactorFunc(defaultLearningFactor),
|
||||
_neighborhoodFunc(defaultNeighborhoodFunc)
|
||||
{
|
||||
_refVectors.resize(width * height);
|
||||
|
||||
auto now = std::chrono::system_clock::now();
|
||||
std::mt19937 randGenerator(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 (auto& refVector : _refVectors)
|
||||
{
|
||||
refVector.resize(inputDimCount);
|
||||
|
||||
for (auto& val : refVector)
|
||||
val = dist(randGenerator);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Network::setDataWeights(const InputVector& weights)
|
||||
{
|
||||
checkSameDimensions(weights, _inputDimCount);
|
||||
|
||||
_weights = weights;
|
||||
}
|
||||
|
||||
InputVector&
|
||||
Network::getRefVector(std::size_t x, std::size_t y)
|
||||
{
|
||||
return _refVectors[x + y*_width];
|
||||
}
|
||||
|
||||
const InputVector&
|
||||
Network::getRefVector(std::size_t x, std::size_t y) const
|
||||
{
|
||||
return _refVectors[x + y*_width];
|
||||
}
|
||||
|
||||
void
|
||||
Network::dump(std::ostream& os) const
|
||||
{
|
||||
os << "Width: " << _width << ", Height: " << _height << std::endl;;
|
||||
|
||||
for (std::size_t y = 0; y < _height; ++y)
|
||||
{
|
||||
for (std::size_t x = 0; x < _width; ++x)
|
||||
{
|
||||
os << getRefVector(x, y) << " ";
|
||||
}
|
||||
|
||||
os << std::endl;
|
||||
}
|
||||
os << std::endl;
|
||||
}
|
||||
|
||||
Coords
|
||||
Network::getClosestRefVector(const InputVector& data) const
|
||||
{
|
||||
auto it = std::min_element(_refVectors.begin(), _refVectors.end(),
|
||||
[&](const auto& a, const auto& b)
|
||||
{
|
||||
return (_distanceFunc(a, data, _weights) < _distanceFunc(b, data, _weights));
|
||||
});
|
||||
|
||||
auto index = std::distance(_refVectors.begin(), it);
|
||||
|
||||
return {index % _height, index / _height};
|
||||
}
|
||||
|
||||
Coords
|
||||
Network::classify(const InputVector& data) const
|
||||
{
|
||||
return getClosestRefVector(data);
|
||||
}
|
||||
|
||||
std::vector<Coords>
|
||||
Network::classify(const InputVector& data, std::size_t size) const
|
||||
{
|
||||
struct Entry
|
||||
{
|
||||
Coords coords;
|
||||
InputVector refVector;
|
||||
};
|
||||
std::vector<Entry> sortedEntries;
|
||||
|
||||
for (std::size_t x = 0; x < _width; ++x)
|
||||
{
|
||||
for (std::size_t y = 0; y < _height; ++y)
|
||||
{
|
||||
sortedEntries.push_back( Entry{{x, y}, getRefVector(x, y)} );
|
||||
}
|
||||
}
|
||||
|
||||
const InputVector& closestRefVector = getRefVector(getClosestRefVector(data));
|
||||
|
||||
std::sort(sortedEntries.begin(), sortedEntries.end(),
|
||||
[&](const Entry& a, const Entry& b)
|
||||
{
|
||||
return _distanceFunc(a.refVector, closestRefVector, _weights) < _distanceFunc(b.refVector, closestRefVector, _weights);
|
||||
});
|
||||
|
||||
std::vector<Coords> res;
|
||||
for (const Entry& entry : sortedEntries)
|
||||
{
|
||||
res.push_back(entry.coords);
|
||||
|
||||
if (res.size() == size)
|
||||
break;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static InputVector::value_type
|
||||
computeCoordsNorm(Coords c1, Coords c2)
|
||||
{
|
||||
std::vector<InputVector::value_type> a = { static_cast<InputVector::value_type>(c1.x), static_cast<InputVector::value_type>(c1.y) };
|
||||
std::vector<InputVector::value_type> b = { static_cast<InputVector::value_type>(c2.x), static_cast<InputVector::value_type>(c2.y) };
|
||||
|
||||
return norm(a - b);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
Network::updateRefVectors(Coords closestRefVectorCoords, const InputVector& input, Progress progress)
|
||||
{
|
||||
for (std::size_t y = 0; y < _height; ++y)
|
||||
{
|
||||
for (std::size_t x = 0; x < _width; ++x)
|
||||
{
|
||||
auto& refVector = getRefVector(x, y);
|
||||
|
||||
auto delta = input - refVector;
|
||||
auto n = computeCoordsNorm({x, y}, closestRefVectorCoords);
|
||||
|
||||
auto oldRefVector = refVector;
|
||||
refVector = refVector + delta * (_learningFactorFunc(progress) * _neighborhoodFunc(n, progress));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Network::train(const std::vector<InputVector>& inputData, std::size_t nbIterations)
|
||||
{
|
||||
|
||||
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(std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()).count());
|
||||
|
||||
for (std::size_t i = 0; i < nbIterations; ++i)
|
||||
{
|
||||
std::shuffle(inputDataShuffled.begin(), inputDataShuffled.end(), randGenerator);
|
||||
|
||||
for (auto input : inputDataShuffled)
|
||||
{
|
||||
Coords closestRefVectorCoords = getClosestRefVector(*input);
|
||||
|
||||
updateRefVectors(closestRefVectorCoords, *input, {i, nbIterations});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace SOM
|
||||
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* 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 <functional>
|
||||
|
||||
#include "utils/Exception.hpp"
|
||||
|
||||
namespace SOM
|
||||
{
|
||||
|
||||
using InputVector = std::vector<double>;
|
||||
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 SOMException : public LmsException
|
||||
{
|
||||
public:
|
||||
SOMException(const std::string& msg) : LmsException(msg) {}
|
||||
};
|
||||
|
||||
// Top Left is (0,0)
|
||||
struct Coords
|
||||
{
|
||||
std::size_t x;
|
||||
std::size_t y;
|
||||
};
|
||||
|
||||
class Network
|
||||
{
|
||||
public:
|
||||
|
||||
Network(std::size_t width, std::size_t height, std::size_t inputDimCount);
|
||||
|
||||
// Set weight for each dimension (default is 1 for each weight)
|
||||
void setDataWeights(const InputVector& weights);
|
||||
|
||||
// data must be normalized
|
||||
void train(const std::vector<InputVector>& dataSamples, std::size_t nbIterations);
|
||||
|
||||
// data must be normalized
|
||||
Coords classify(const InputVector& data) const;
|
||||
|
||||
// ordered from closest to farthest
|
||||
std::vector<Coords> classify(const InputVector& data, std::size_t size) 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) * NeighborhoodFunc(i) * (MatchingRefVector - refVector)
|
||||
|
||||
using DistanceFunc = std::function<InputVector::value_type(const InputVector& /* a */, const InputVector& /* b */, const InputVector& /* weights */)>;
|
||||
void setDistanceFunc(DistanceFunc distanceFunc);
|
||||
|
||||
struct Progress
|
||||
{
|
||||
std::size_t idIteration;
|
||||
std::size_t iterationCount;
|
||||
};
|
||||
|
||||
using LearningFactorFunc = std::function<InputVector::value_type(Progress)>;
|
||||
void setLearningFactorFunc(LearningFactorFunc learningFactorFunc);
|
||||
|
||||
using NeighborhoodFunc = std::function<InputVector::value_type(InputVector::value_type /* norm(Coords - CoordMatchingRefVector) */, Progress)>;
|
||||
void setNeighborhoodFunc(NeighborhoodFunc neighborhoodFunc);
|
||||
|
||||
private:
|
||||
|
||||
InputVector& getRefVector(std::size_t x, std::size_t y);
|
||||
const InputVector& getRefVector(std::size_t x, std::size_t y) const;
|
||||
const InputVector& getRefVector(Coords coords) const { return getRefVector(coords.x, coords.y); }
|
||||
Coords getClosestRefVector(const InputVector& data) const;
|
||||
|
||||
void updateRefVectors(Coords closestRefVectorCoords, const InputVector& input, Progress progress);
|
||||
|
||||
std::size_t _width;
|
||||
std::size_t _height;
|
||||
std::size_t _inputDimCount;
|
||||
|
||||
InputVector _weights;
|
||||
std::vector<InputVector> _refVectors; // reference vectors
|
||||
|
||||
DistanceFunc _distanceFunc;
|
||||
LearningFactorFunc _learningFactorFunc;
|
||||
NeighborhoodFunc _neighborhoodFunc;
|
||||
};
|
||||
|
||||
} // namespace SOM
|
||||
Reference in New Issue
Block a user