331 lines
9.1 KiB
C++
331 lines
9.1 KiB
C++
/*
|
|
* 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/Network.hpp"
|
|
|
|
#include <algorithm>
|
|
#include <chrono>
|
|
#include <cmath>
|
|
#include <random>
|
|
#include <sstream>
|
|
#include <unordered_set>
|
|
|
|
#include "utils/Logger.hpp"
|
|
#include "utils/Random.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}
|
|
{
|
|
// init each vector with a random normalized value
|
|
for (Coordinate y {}; y < _refVectors.getHeight(); ++y)
|
|
{
|
|
for (Coordinate x {}; x < _refVectors.getWidth(); ++x)
|
|
{
|
|
for (InputVector::value_type& val : _refVectors.get({x,y}))
|
|
val = Random::getRealRandom<InputVector::value_type>(0, 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
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::vector<Position>& refVectorsPosition, InputVector::Distance maxDistance) const
|
|
{
|
|
std::unordered_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);
|
|
|
|
for (std::size_t i {}; i < nbIterations; ++i)
|
|
{
|
|
CurrentIteration curIter {i, nbIterations};
|
|
|
|
if (progressCallback)
|
|
progressCallback(curIter);
|
|
|
|
Random::shuffleContainer(inputDataShuffled);
|
|
|
|
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
|
|
|
|
|