Last active
December 23, 2017 16:42
-
-
Save fabiovila/830f1f195f46190ea65bce5f492e3c67 to your computer and use it in GitHub Desktop.
A feedforward neural network trainned with Stochastic Gradient Descent in Iris dataset. C++ program with Armadillo as dependency only.
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 <unordered_map> | |
#include "armadillo" | |
#include <algorithm> | |
#include <cctype> | |
#include <iostream> | |
#include <string> | |
#include <vector> | |
#include <fstream> | |
#include <string> | |
using namespace arma; | |
using namespace std; | |
// g++ -c -g -std=c++14 -march=native -I. -O2 -fPIC `pkg-config --cflags --libs Qt5Core` --pipe nn-iris.cpp -o nn-iris.o | |
// g++ -g -std=c++14 -march=native -I. -O2 -fPIC -lpthread -lm -larmadillo -lstdc++ `pkg-config --cflags --libs Qt5Core` -o nn-iris nn-iris.o -I. | |
class sgd { | |
private: | |
vector<mat> w; // weights | |
vector<rowvec> b; // bias | |
mat X; // X vectors | |
double alpha; | |
double momentum; | |
public: | |
sgd() { alpha = 0.01; momentum = 0.9;} | |
void layers(initializer_list<double> layers) { | |
for (auto it = layers.begin(); it != layers.end() - 1; ++it) { | |
w.push_back(mat(*it, *next(it))); | |
w.back() = randn(*it, *next(it)); // normal random weights | |
b.push_back(rowvec(*next(it))); | |
b.back() = randn(1,*next(it)); // random bias | |
} | |
}; | |
inline mat sigmoid (mat f) { return 1.0 / (1.0 + trunc_exp(-f)); } | |
inline mat dsigmoid (mat f) { return f % (1.0 - f); } | |
inline mat tanh (mat f) { return arma::tanh(-f); } | |
inline mat dtanh (mat f) { return 1.0 - arma::pow(f,2); } | |
rowvec prev (rowvec x){ | |
int i; | |
mat f = sigmoid(x*w[0]+b[0]); | |
for ( i = 1; i < w.size(); i++){ | |
f = sigmoid(f*w[i]+b[i]); | |
} | |
return f; | |
} | |
double trainstep (mat iX, mat iY) { | |
int i; | |
vec list = linspace(0,iX.n_rows - 1, iX.n_rows); | |
list = shuffle(list); | |
double esum = 0; | |
for (auto xy: list) { | |
mat x = iX.row(xy); mat y = iY.row(xy); | |
vector <rowvec> o; | |
// forward step | |
o.push_back(sigmoid(x * w[0] + b[0])); | |
for ( i = 1; i < w.size(); i++){ | |
o.push_back(sigmoid(o.back()*w[i] + b[i])); | |
} | |
// output error | |
mat e = o.back() - y; | |
esum += accu(pow(e,2)); | |
rowvec beta; | |
mat d; | |
// backward step | |
for (i = w.size() - 1; i > 0; i--){ | |
beta = e % dsigmoid(o[i]); | |
e = e * w[i].t(); | |
d = o[i-1].t() * beta; | |
w[i] -= d * alpha; | |
b[i] -= beta; | |
} | |
beta = e % dsigmoid(o[i]); | |
d = x.t() * beta; | |
w[i] -= d * alpha; | |
b[i] -= beta; | |
}; | |
return esum; | |
} | |
void input(mat in){ | |
X = move(in); // I'm new in C14. Does this really move? | |
} | |
void debug() { | |
cout << "Debug\n " << endl; | |
} | |
}; | |
void printm(const rowvec& M) { | |
for(int i = 0; i < M.n_cols; i++) { | |
printf ("%02.4f ", M(i)); | |
//cout << M(i) << ' '; | |
} | |
} | |
void testes(); | |
int main(int argc, char** argv) { | |
arma_rng::set_seed(9); | |
sgd s; | |
// Iris data Test | |
FILE *fp = fopen("iris.data.csv", "r"); | |
vector<vector<double>> vY; | |
vector<double> vA,vB,vC,vD; | |
unordered_map<string, int> uLabel; | |
string Label; | |
float a,b,c,d; | |
char l[255]; | |
int yhot = 0; | |
while (fscanf(fp,"%f,%f,%f,%f,%s",&a,&b,&c,&d,l) == 5){ | |
vA.push_back(a); | |
vB.push_back(b); | |
vC.push_back(c); | |
vD.push_back(d); | |
Label = l; | |
if ( uLabel.find(Label) == uLabel.end()) { | |
uLabel[Label] = yhot; | |
yhot = yhot + 1; | |
} | |
vector<double> onehot = {0,0,0}; | |
onehot[uLabel[Label]] = 1; | |
vY.push_back(onehot); | |
} | |
cout << "Read: " << vA.size() << " lines" << endl; | |
mat X(vA.size(),4); | |
mat Y(vY.size(),3); | |
int i; | |
for (i = 0; i < X.n_rows; i++) { | |
X.row(i) = rowvec ({vA[i],vB[i],vC[i],vD[i]}); | |
Y.row(i) = rowvec ({vY[i]}); | |
} | |
s.layers({4,8,3}); | |
s.input(X); | |
double e = 1.0; | |
int epochs = 60000; | |
for (i = 0; i < epochs && e > 0.001; i++) { | |
e = s.trainstep(X,Y); | |
if ( i % 1000 == 0) { cout << "Epoch: " << i << " Error: " << e << endl << flush;} | |
} | |
cout << "Epoch: " << i << " Error: " << e << endl << flush; | |
double acc = 0; | |
for (i = 0; i < X.n_rows; i++){ | |
rowvec p = s.prev(X.row(i)); | |
acc += accu(pow(p - Y.row(i),2)); | |
printm(Y.row(i)); cout << " | "; | |
printm(p); | |
cout << "\n"; | |
} | |
return 0; | |
} | |
void testes(){ | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment