Last active
August 20, 2018 22:49
-
-
Save EmmanuelMess/f690efd1bbbedc6ae4701b19b5540c1b to your computer and use it in GitHub Desktop.
Competitive unsupervised learning without Oja's algorithm
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <iostream> | |
#include <vector> | |
#include <random> | |
typedef double T; | |
class VectorWrongSizeException : public std::exception { | |
const char* what() const noexcept override { | |
return "Different vector sizes!"; | |
} | |
}; | |
class NonOneAfterNormalization : public VectorWrongSizeException { | |
T size; | |
const char* what() const noexcept override { | |
std::cerr << ("Vector size " + std::to_string(size) + " after normalization!").data(); | |
return ("Vector size " + std::to_string(size) + " after normalization!").data(); | |
} | |
public: | |
NonOneAfterNormalization(T size) : size(size) { } | |
}; | |
class Vector : public std::vector<T> { | |
public: | |
explicit Vector(size_t size) : std::vector<T>(size) { } | |
explicit Vector(const vector<T>& v) : std::vector<T>(v) { } | |
void operator+=(const Vector& o) { | |
if(size() != o.size()) { | |
throw VectorWrongSizeException(); | |
} | |
for (size_t i = 0; i < size(); i++) { | |
(*this)[i] += o[i]; | |
} | |
} | |
void operator-=(const Vector& o) { | |
if(size() != o.size()) { | |
throw VectorWrongSizeException(); | |
} | |
for (size_t i = 0; i < size(); i++) { | |
(*this)[i] -= o[i]; | |
} | |
} | |
friend Vector operator+(Vector s, const Vector& o) { | |
if(s.size() != o.size()) { | |
throw VectorWrongSizeException(); | |
} | |
Vector r(s); | |
for (size_t i = 0; i < s.size(); i++) { | |
r[i] += o[i]; | |
} | |
return r; | |
} | |
friend Vector operator-(Vector s, const Vector& o) { | |
if(s.size() != o.size()) { | |
throw VectorWrongSizeException(); | |
} | |
Vector r(s); | |
for (size_t i = 0; i < s.size(); i++) { | |
r[i] -= o[i]; | |
} | |
return r; | |
} | |
T operator*(const Vector& o) const {//scalar product | |
T r = 0; | |
for (size_t i = 0; i < size(); i++) { | |
r += (*this)[i] * o[i]; | |
} | |
return r; | |
} | |
Vector operator/(T o) const { | |
Vector newV(size()); | |
for (size_t i = 0; i < size(); i++) { | |
newV[i] = (*this)[i] / o; | |
} | |
return newV; | |
} | |
friend Vector operator*(T o, const Vector& v); | |
Vector operator-() { | |
Vector newV(size()); | |
for (size_t i = 0; i < size(); ++i) { | |
newV[i] = -(*this)[i]; | |
} | |
return newV; | |
} | |
void normalize(T to = 1) { | |
double len = length(); | |
if(len == 1) return; | |
for(auto& axis : *this) { | |
axis /= len; | |
axis *= to; | |
} | |
len = length(); | |
if(len <= (T)(to - 0.000001) || len >= (T)(to + 0.000001)) { | |
throw NonOneAfterNormalization(len); | |
} | |
} | |
Vector normalized() const { | |
T len = length(); | |
if(len == 1) return Vector(*this); | |
Vector r(*this); | |
r.normalize(); | |
return r; | |
} | |
T length() const { | |
T r = 0; | |
for (size_t i = 0; i < size(); ++i) { | |
r += (*this)[i]*(*this)[i]; | |
} | |
return static_cast<T>(sqrt(r)); | |
} | |
friend std::ostream &operator<<(std::ostream& strm, const Vector& obj); | |
}; | |
Vector operator*(T o, const Vector& v) { | |
Vector newV(v.size()); | |
for (size_t i = 0; i < v.size(); i++) { | |
newV[i] = v[i] * o; | |
} | |
return newV; | |
} | |
class Perceptron { | |
Vector w; | |
std::string name; | |
public: | |
explicit Perceptron(size_t size, std::string name) | |
: w(size), name(name) { | |
static std::random_device rd; | |
static std::mt19937 mt(rd()); | |
static std::uniform_real_distribution<double> dist(-1, 1); | |
do { | |
for (auto &axis : w) { | |
axis = dist(mt); | |
} | |
} while(w.length() <= 1); | |
w.normalize(); | |
} | |
void operator+=(const Vector& o) { | |
w += o; | |
} | |
friend Vector operator+(Perceptron p, const Vector& o) { | |
return p.w + o; | |
} | |
void operator-=(const Vector& o) { | |
w -= o; | |
} | |
Vector weights() const { | |
return w; | |
} | |
void setWeights(const Vector& weights) { | |
w = Vector(weights); | |
} | |
const std::string& getName() const { | |
return name; | |
} | |
friend std::ostream &operator<<(std::ostream& strm, const Perceptron& obj); | |
}; | |
std::ostream &operator<<(std::ostream& strm, const Perceptron& obj) { | |
strm << obj.w << "[Θ = 0]"; | |
} | |
std::ostream &operator<<(std::ostream& strm, const Vector& obj) { | |
strm << "[" << obj.size() << "]{"; | |
for (int i = 0; i < obj.size(); i++) { | |
strm << obj[i]; | |
if(i < obj.size()-1) { | |
strm << ", "; | |
} | |
} | |
strm << "}"; | |
} | |
std::vector<std::string> simpleDatapoints(std::vector<Vector>& inputs) { | |
inputs.push_back(Vector({1, 1})); | |
inputs.push_back(Vector({1.5, 2})); | |
inputs.push_back(Vector({1.5, 3})); | |
inputs.push_back(Vector({2, 1})); | |
inputs.push_back(Vector({-1, -1})); | |
inputs.push_back(Vector({-2, -1})); | |
inputs.push_back(Vector({-2, -2})); | |
inputs.push_back(Vector({1, -1})); | |
inputs.push_back(Vector({1.5, -1})); | |
inputs.push_back(Vector({1.5, -2})); | |
std::vector<std::string> clusters; | |
clusters.push_back("cluster a"); | |
clusters.push_back("cluster b"); | |
clusters.push_back("cluster c"); | |
return clusters; | |
} | |
int winnerTakesAllUpdate(Perceptron &perceptron, const Vector &x) { | |
perceptron.setWeights((perceptron + x).normalized()); | |
} | |
int withLearningConstantUpdate(Perceptron &perceptron, const Vector &x) { | |
static float constant = 0.01f; | |
perceptron.setWeights((perceptron + (constant*x)).normalized()); | |
if(constant > 0.0001f) constant -= 0.0001f; | |
} | |
int differenceUpdate(Perceptron &perceptron, const Vector &x) { | |
static float constant = 0.001f; | |
perceptron.setWeights((perceptron + (constant * (x - perceptron.weights()))).normalized()); | |
} | |
int main() { | |
using std::cout; | |
using std::cin; | |
using std::endl; | |
std::vector<Vector> inputs; | |
std::vector<std::string> clusters = simpleDatapoints(inputs);//Change datapoints for different classifications | |
for(auto& point : inputs) { | |
point.normalize(); | |
} | |
std::random_device rd; | |
std::mt19937 mt(rd()); | |
std::uniform_int_distribution<size_t> dist(0, inputs.size()-1); | |
std::vector<Perceptron> perceptrons; | |
for(const auto &cluster : clusters) { | |
perceptrons.push_back(Perceptron(inputs[0].size(), cluster)); | |
cout << perceptrons.back() << endl; | |
} | |
int total = 4000000; | |
for(size_t i = 0; i < total; i++) { | |
Vector x = inputs[dist(mt)]; | |
T maxResult = 0; | |
Perceptron *toUpdate = &perceptrons.back(); | |
for(auto& perceptron : perceptrons) { | |
T result = perceptron.weights() * x; | |
if(result > maxResult) { | |
maxResult = result; | |
toUpdate = &perceptron; | |
} | |
} | |
differenceUpdate(*toUpdate, x);//Change function for different update algorithms | |
} | |
cout << "Tries: " << total << endl; | |
std::vector<std::pair<T, T>> plotdata; | |
for (const auto &perceptron : perceptrons) { | |
cout << perceptron.getName() << " = " << perceptron.weights() << endl; | |
plotdata.push_back(std::pair<T, T>(perceptron.weights()[0], perceptron.weights()[1])); | |
} | |
for (const auto &x : inputs) { | |
T maxResult = 0; | |
std::string maxName; | |
for(auto& perceptron : perceptrons) { | |
T result = perceptron.weights() * x; | |
if(result > maxResult) { | |
maxResult = result; | |
maxName = perceptron.getName(); | |
} | |
} | |
cout << "(" << x[0] << ", " << x[1] << ") = " << maxName << endl; | |
plotdata.push_back(std::pair<T, T>(x[0], x[1])); | |
} | |
cout << endl << "---- start dump ----" << endl; | |
for(const auto& point : plotdata) { | |
cout << "(" << point.first << ", " << point.second << ")" << endl; | |
} | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment