Skip to content

Instantly share code, notes, and snippets.

@fabiovila
Last active December 23, 2017 16:42
Show Gist options
  • Save fabiovila/830f1f195f46190ea65bce5f492e3c67 to your computer and use it in GitHub Desktop.
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.
#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