WIP som network
This commit is contained in:
+2
-1
@@ -80,7 +80,8 @@ AC_CONFIG_FILES([Makefile
|
||||
src/Makefile
|
||||
test/Makefile
|
||||
tools/Makefile
|
||||
tools/metadata/Makefile])
|
||||
tools/metadata/Makefile
|
||||
tools/classifier/Makefile])
|
||||
|
||||
AC_OUTPUT
|
||||
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* 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 <iostream>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace SOM
|
||||
{
|
||||
|
||||
DataNormalizer::DataNormalizer(std::size_t inputDimCount)
|
||||
: _inputDimCount(inputDimCount)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
DataNormalizer::computeNormalizationFactors(const std::vector<InputVector>& inputVectors)
|
||||
{
|
||||
// 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};
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
DataNormalizer::normalizeData(InputVector& a) const
|
||||
{
|
||||
checkSameDimensions(a, _inputDimCount);
|
||||
|
||||
for (std::size_t dimId = 0; dimId < _inputDimCount; ++dimId)
|
||||
{
|
||||
// clamp
|
||||
if (a[dimId] > _minmax[dimId].max)
|
||||
a[dimId] = _minmax[dimId].max;
|
||||
else if (a[dimId] < _minmax[dimId].min)
|
||||
a[dimId] = _minmax[dimId].min;
|
||||
|
||||
a[dimId] = (a[dimId] - _minmax[dimId].min) / (_minmax[dimId].max - _minmax[dimId].min);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace SOM
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* 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 "SOM.hpp"
|
||||
|
||||
namespace SOM
|
||||
{
|
||||
|
||||
class DataNormalizer
|
||||
{
|
||||
public:
|
||||
DataNormalizer(std::size_t inputDimCount);
|
||||
|
||||
void computeNormalizationFactors(const std::vector<InputVector>& dataSamples);
|
||||
|
||||
void normalizeData(InputVector& data) const;
|
||||
|
||||
private:
|
||||
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,292 @@
|
||||
/*
|
||||
* 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)
|
||||
{
|
||||
InputVector::value_type res = 0;
|
||||
|
||||
for (std::size_t i = 0; i < a.size(); ++i)
|
||||
{
|
||||
res += (a[i] - b[i]) * (a[i] - b[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),
|
||||
_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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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) < _distanceFunc(b, data));
|
||||
});
|
||||
|
||||
auto index = std::distance(_refVectors.begin(), it);
|
||||
|
||||
return {index % _height, index / _height};
|
||||
}
|
||||
|
||||
Coords
|
||||
Network::classify(const InputVector& data) const
|
||||
{
|
||||
return getClosestRefVector(data);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
std::random_device randomDevice;
|
||||
std::mt19937 generator(randomDevice());
|
||||
|
||||
for (std::size_t i = 0; i < nbIterations; ++i)
|
||||
{
|
||||
std::shuffle(inputDataShuffled.begin(), inputDataShuffled.end(), generator);
|
||||
|
||||
for (auto input : inputDataShuffled)
|
||||
{
|
||||
Coords closestRefVectorCoords = getClosestRefVector(*input);
|
||||
|
||||
updateRefVectors(closestRefVectorCoords, *input, {i, nbIterations});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace SOM
|
||||
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* 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);
|
||||
|
||||
// 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;
|
||||
|
||||
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&, const InputVector&)>;
|
||||
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;
|
||||
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;
|
||||
|
||||
std::vector<InputVector> _refVectors; // indexed reference vectors
|
||||
|
||||
DistanceFunc _distanceFunc;
|
||||
LearningFactorFunc _learningFactorFunc;
|
||||
NeighborhoodFunc _neighborhoodFunc;
|
||||
};
|
||||
|
||||
} // namespace SOM
|
||||
+1
-1
@@ -1,2 +1,2 @@
|
||||
SUBDIRS = metadata
|
||||
SUBDIRS = metadata classifier
|
||||
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <stdexcept>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
#include "classifier/SOM.hpp"
|
||||
#include "classifier/DataNormalizer.hpp"
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
if (argc != 2)
|
||||
{
|
||||
std::cerr << "Usage: <file>" << std::endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
auto iterationCount = std::stoul(argv[1]);
|
||||
|
||||
using namespace SOM;
|
||||
|
||||
SOM::Network network(5, 5, 2);
|
||||
|
||||
network.dump(std::cout);
|
||||
|
||||
std::vector< std::vector<double> > inputValues =
|
||||
{
|
||||
{ 160, 1 },
|
||||
{ 80, -1 },
|
||||
{ 80, -0.75 },
|
||||
{ 240, 0.5 },
|
||||
{ 120, -0.5 },
|
||||
{ 140, -0.5 },
|
||||
};
|
||||
|
||||
std::cout << "Before normalization:" << std::endl;
|
||||
for (const auto& inputValue : inputValues)
|
||||
{
|
||||
std::cout << inputValue << std::endl;
|
||||
}
|
||||
std::cout << std::endl;
|
||||
|
||||
std::cout << "After normalization:" << std::endl;
|
||||
|
||||
SOM::DataNormalizer normalizer(2);
|
||||
|
||||
normalizer.computeNormalizationFactors(inputValues);
|
||||
|
||||
for (auto& inputValue : inputValues)
|
||||
normalizer.normalizeData(inputValue);
|
||||
|
||||
for (const auto& inputValue : inputValues)
|
||||
{
|
||||
std::cout << inputValue << std::endl;
|
||||
}
|
||||
|
||||
std::cout << std::endl;
|
||||
|
||||
for (const auto& inputValue : inputValues)
|
||||
{
|
||||
auto res = network.classify(inputValue);
|
||||
std::cout << "Found at " << res.x << ", " << res.y << std::endl;
|
||||
}
|
||||
|
||||
std::cout << "Training network for " << iterationCount << " iterations" << std::endl;
|
||||
|
||||
network.train(inputValues, iterationCount);
|
||||
|
||||
network.dump(std::cout);
|
||||
std::cout << "OK" << std::endl;
|
||||
|
||||
for (const auto& inputValue : inputValues)
|
||||
{
|
||||
auto res = network.classify(inputValue);
|
||||
std::cout << "Found at " << res.x << ", " << res.y << std::endl;
|
||||
}
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
bin_PROGRAMS = lms-classifier
|
||||
|
||||
lms_classifier_SOURCES = \
|
||||
$(srcdir)/LmsClassifier.cpp \
|
||||
$(top_srcdir)/src/classifier/DataNormalizer.cpp \
|
||||
$(top_srcdir)/src/classifier/SOM.cpp \
|
||||
$(top_srcdir)/src/utils/Logger.cpp \
|
||||
$(top_srcdir)/src/utils/Utils.cpp
|
||||
|
||||
lms_classifier_CXXFLAGS=-std=c++14 -Wall -I$(top_srcdir)/src -D_REENTRANT
|
||||
|
||||
Reference in New Issue
Block a user