-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathMLP_bn.cpp
1130 lines (939 loc) · 30.9 KB
/
MLP_bn.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#include <iostream>
#include <vector>
#include <cmath>
#include <cstdlib>
#include <ctime>
#include <iomanip>
#include <fstream>
#include <bits/stdc++.h> // use this for sort
using namespace std;
class Matrix {
private:
vector<vector<double>> data;
public:
// Constructors
// constructor/init
Matrix();
// copy constructor
Matrix(const Matrix& other);
//deconstructor
~Matrix();
// Functions
// We use alot of size_t since the vectors size can be large or small depending on topology
// https://www.geeksforgeeks.org/size_t-data-type-c-language/
// matrix.rows() returns size of rows
size_t rows() const;
// matrix.cols() returns size of cols
size_t cols() const;
//pushback does the same thing as a vector
void push_back(vector<double> row);
// resize is used to split a Matrix
void resize(size_t rows, size_t cols);
// this loops through with iterators to display the matrix
void display() const;
// operator overloading
vector<double>& operator[](size_t row);
const vector<double>& operator[](size_t row) const;
};
Matrix::Matrix() // constructor
{
// cout << "Matrix initilized" <<endl;
}
Matrix::Matrix(const Matrix& other) // copy constructor
{
// cout << "Matrix copy" <<endl;
data = other.data;
}
Matrix::~Matrix()
{
// cout << "Matrix destroyed" << endl;
}
// matrix.rows() returns size of rows
size_t Matrix::rows() const
{
size_t sz;
if (data.empty())
{
sz = 0;
}
else
{
sz = data.size();
}
return sz;
}
// matrix.cols() returns size of cols
size_t Matrix::cols() const
{
size_t sz;
if (data.empty())
{
sz = 0;
}
else
{
sz = data[0].size();
}
return sz;
}
void Matrix::push_back(vector<double> row)
{
data.push_back(row);
}
// need to resize both rows and cols
void Matrix::resize(size_t rows, size_t cols)
{
data.resize(rows);
for (auto& row : data)
{
row.resize(cols);
}
}
void Matrix::display() const
{
// print rows and cols via iterators
for (auto row_it = data.begin(); row_it != data.end(); ++row_it)
{
for (auto col_it = row_it->begin(); col_it != row_it->end(); ++col_it)
{
cout << *col_it << " ";
}
cout << endl;
}
}
// overload [] so we can get values with matrix[i][j]
vector<double>& Matrix::operator[](size_t row)
{
return data[row];
}
const vector<double>& Matrix::operator[](size_t row) const
{
return data[row];
}
// Vector Operations is used to evaluate performance
class VectorOps
{
private:
vector<double> sequence;
double median;
double mode;
double std_dev;
double variance;
public:
double mean;
//constructors
//constructor/init
VectorOps();
// copy constructor
VectorOps(const VectorOps& other);
//deconstructor
~VectorOps();
//functions
//add is like push_back
bool add(const double num);
bool calc_mean();
bool calc_median();
bool calc_mode();
bool calc_std_dev_var();
bool calc_stats();
// bool calc_variance();
//prints values in the vector
void display_vect() const;
void display_stats() const; //prints stats
};
VectorOps::VectorOps() // constructor
{
sequence = {};
mean = 0;
mode = 0;
std_dev = 0;
variance = 0;
}
VectorOps::VectorOps(const VectorOps& other) // copy constructor
{
sequence = other.sequence;
mean = other.mean;
median = other.median;
mode = other.mode;
std_dev = other.std_dev;
variance = other.variance;
}
VectorOps::~VectorOps() // deconstructor
{
//cout << "Vect is removed" << endl;
}
bool VectorOps::add(const double num)
{
sequence.push_back(num);
return true;
}
bool VectorOps::calc_mean()
{
// auto size = sequence.size(); //size type
double m=0;
for (auto it = sequence.cbegin(); it != sequence.cend(); ++it)
{
// cout << *it << " ";
m += *it;
}
mean = m/sequence.size();
return true;
}
bool VectorOps::calc_median()
{
vector<double> temp = sequence;
// sort the matrix before finding median
sort(temp.begin(), temp.end());
auto size = temp.size();
//odd
if (size%2)
{
median = temp.at(size/2);
}
//even
else
{
median = (temp.at(size/2 -1) + temp.at(size/2))/2;
}
return true;
}
bool VectorOps::calc_mode()
{
auto beg = sequence.cbegin(); //constant itr read only
auto end = sequence.cend(); //constant itr
auto size = sequence.size(); //size type
decltype(size) cur_count = 1; //make counter size type
decltype(size) count = 1;
double cur_mode = *beg; //derefrence to get first val
for (decltype(size) i = 1; i < sequence.size(); ++i)
{
if (sequence[i] == sequence[i-1])
{
count++;
}
else
{
if (count > cur_count)
{
cur_count = count;
cur_mode = sequence[i-1];
}
count = 1;
}
}
if (count > cur_count)
{
cur_count = count;
cur_mode = sequence[sequence.size()-1];
}
mode = cur_mode;
return true;
}
bool VectorOps::calc_std_dev_var()
{
auto size = sequence.size(); //size type
double sum = 0.0;
for (auto it = sequence.cbegin(); it != sequence.cend(); ++it)
{
sum += (*it - mean)* (*it - mean);
}
double var = sum / size;
std_dev = sqrt(var);
variance = var;
return true;
}
bool VectorOps::calc_stats()
{
this->calc_mean();
this->calc_median();
this->calc_mode();
this->calc_std_dev_var();
return true;
}
void VectorOps::display_vect() const
{
//constant itr read only
for (auto it = sequence.cbegin(); it != sequence.cend(); ++it)
{
// dereference iter to get value
cout << *it << " ";
}
cout << endl;
return;
}
void VectorOps::display_stats() const
{
cout << "Mean: " << mean << endl;
cout << "Median: " << median << endl;
// cout << "Mode: " << mode << endl;
cout << "Standard Deviation: " << std_dev << endl;
cout << "Variance: " << variance << endl;
return;
}
// end of VectorOps
double sigmoid(double x)
{
return 1.0 / (1.0 + exp(-x));
}
double sigmoid_derivative(double x)
{
double s = sigmoid(x);
return s * (1 - s);
}
double activationFunction(double x)
{
//use a fucntion with out range [ -1 to 1 ]
// ill use tanh for now
return tanh(x);
}
double activationFunctionDerivative(double x)
{
// this approximates the derivitive of tanh. pros: faster. con: less accurate
return 1.0 - x*x;
}
class Neuron
{
public:
double value, delta;
// keep track of which neurons are dropped in training
bool dropped;
vector<double> weights;
// constructor/init
Neuron(size_t num_outputs);
//deconstructor
~Neuron();
};
Neuron::Neuron(size_t num_outputs) //constructor
{
// initialize dropped as false
dropped = false;
// push back a an output with a rand weight until num_outputs is reached
for (size_t i = 0; i < num_outputs; ++i)
{
// add with rand weight
weights.push_back(((double)rand() / RAND_MAX) * 2 - 1);
}
}
Neuron::~Neuron()//deconstructor
{
// cout << "Neuron Dropped" << endl;
}
class Layer
{
public:
// a Layer is a vector of neurons
vector<Neuron> neurons;
double running_mean = 0.0;
double running_variance = 1.0;
//constructor/init
Layer(size_t num_neurons, size_t num_outputs);
// deconstructor
~Layer();
};
Layer::Layer(size_t num_neurons, size_t num_outputs) //constructor
{
// to construct a Layer by pushsing back Neurons until num_neurons is reached
for (size_t i = 0; i < num_neurons; ++i)
{
// add neurons with num_outputs number of outputs(with rand weight) to the layer
neurons.push_back(Neuron(num_outputs));
}
}
Layer::~Layer() //deconstructor
{
//cout << "Layer deleted" <<endl;
}
class MLP
{
private:
bool apply_batch;
void setInput(const vector<double> &input);
void calculateOutputLayerDeltas(const vector<double> &target);
// private functions
void feedForward(bool training);
void backProp();
void updateWeights(double learning_rate);
void applyDropout(bool training);
void normalizeBatch(const size_t layer_index, bool training, double momentum = 0.9, double epsilon = 1e-5);
void normalizeBatchBackward(const size_t layer_index, const double epsilon = 1e-5);
public:
// public variables
vector<Layer> layers;
double dropout_rate;
// Constructors
// constructor/init
MLP(const vector<size_t> &topology, double dropout_rate = 0.1, bool apply_batch = true);
// deconstructor
~MLP();
// Functions
void forward(const vector<double> &input, bool training);
void backward(const vector<double> &target, double learning_rate);
const vector<double> getOutVal() const;
};
MLP::MLP(const vector<size_t> &topology, double dropout_rate, bool apply_batch) //constructor
{
this->apply_batch = apply_batch;
// set dropout_rate to the rate passed by the user
if (dropout_rate != 0.0)
{
cout << "Dropout : " << dropout_rate*100 << "%" << endl;
}
else{
cout << "No Dropout" << endl;
}
this->dropout_rate = dropout_rate;
// loop through the topology from start to 1 before the output layer
for (size_t i = 0; i < topology.size() - 1; ++i)
{
// add a layer with topology[i] neurons and topology[i + 1] output neurons
layers.push_back(Layer(topology[i], topology[i + 1]));
}
// push back the output layer (which has topology.back() input neurons and 0 output neurons)
layers.push_back(Layer(topology.back(), 0));
}
MLP::~MLP()
{
//cout << "Multi Layer Perceptron deleted" <<endl;
}
void MLP::calculateOutputLayerDeltas(const vector<double> &target)
{
// loop through neurons in output layer (layer.back())
for (size_t i = 0; i < layers.back().neurons.size(); ++i)
{
// calculate delta
layers.back().neurons[i].delta = (target[i] - layers.back().neurons[i].value) * activationFunctionDerivative(layers.back().neurons[i].value);
}
}
void MLP::forward(const vector<double> &input, bool training)
{
// input vals are passed to input neurons
setInput(input);
// do forward propagation to get output of each neuron
feedForward(training);
// Apply batch normalization (except for the output layer)
if (apply_batch)
{
for (size_t i = 1; i < layers.size() - 1; ++i) // Exclude the input and output layers
{
normalizeBatch(i, training);
}
}
// when training apply dropout (which drops random neurons to prevent overfitting)
applyDropout(training);
}
void MLP::backward(const vector<double> &target, double learning_rate)
{
// calculate output layer error (aka delta) given the target vals
calculateOutputLayerDeltas(target);
// propagate backwards calculating delta along the way
backProp();
// Apply batch normalization backward pass (except for the output layer)
if (apply_batch)
{
for (size_t i = layers.size() - 2; i > 0; --i) // Exclude the input and output layers
{
normalizeBatchBackward(i);
}
}
// update the weights based on the calculated deltas and the learninging rate (alpha)
updateWeights(learning_rate);
}
// this function just reads neuron vals without changing vals, hince the const
const vector<double> MLP::getOutVal() const
{
// initilize a vecotor of doubles (called output)
vector<double> output;
// loop through output neurons (layers.back())
for (const Neuron &neuron : layers.back().neurons)
{
// add values to output vector
output.push_back(neuron.value);
}
// return the output vector that contains the output neuron values
return output; // this works even tho its declared in the function
}
void MLP::setInput(const vector<double> &input)
{
// for weather we have 13 input features
// Dew Max, Dew Avg, Dew Min, Humidity Max, Humidity Avg, Humidity Min, Wind Speed Max, Wind Speed Avg, Wind Speed Min, Pressure Max, Pressure Avg, Pressure Min, Precipitation Total
// loop through in vector and set the input neurons to the input vals
for (size_t i = 0; i < input.size(); ++i)
{
// input (layer 0) neurons values are set to the input vector values
// cout << "Input " << i << " : " << input[i] << endl;
layers[0].neurons[i].value = input[i];
}
}
void MLP::feedForward(bool training)
{
// loop through layers in the MLP (starting at the first hidden layer)
for (size_t i = 1; i < layers.size(); ++i)
{
// loop through neurons in the layer
for (size_t j = 0; j < layers[i].neurons.size(); ++j)
{
// going to get the weighted sum
double sum = 0;
// loop through the neurons in prev layer
for (size_t k = 0; k < layers[i - 1].neurons.size(); ++k)
{
// add the sum of the neurons in prev layer (including the weights in connection to current neuron)
sum += layers[i - 1].neurons[k].value * layers[i - 1].neurons[k].weights[j];
}
// send the sum to the transfer function and set the result to the neurons output val
layers[i].neurons[j].value = activationFunction(sum);
}
}
}
void MLP::backProp()
{
// loop through layers in the MLP in Reverse Order
for (size_t i = layers.size() - 2; i > 0; --i)
{
// loop through neurons in the layer
for (size_t j = 0; j < layers[i].neurons.size(); ++j)
{
// going to get the error
double error = 0;
// loop through the neurons in next layer
for (size_t k = 0; k < layers[i + 1].neurons.size(); ++k)
{
// add the error of the neurons in next layer (including the delta in connection to next neuron)
error += layers[i].neurons[j].weights[k] * layers[i + 1].neurons[k].delta;
}
// calculate delta by multiplying the error and the derivative of the transfer (activation) function
layers[i].neurons[j].delta = error * activationFunctionDerivative(layers[i].neurons[j].value);
}
}
}
void MLP::updateWeights(double learning_rate)
{
// loop through layers in the MLP
for (size_t i = 0; i < layers.size() - 1; ++i)
{
// loop through neurons in the layer
for (size_t j = 0; j < layers[i].neurons.size(); ++j)
{
// loop through weights in the neuron
for (size_t k = 0; k < layers[i].neurons[j].weights.size(); ++k)
{
// update weight via multipluing learning rate (alpha) with the current val amd the delta of next neuron
layers[i].neurons[j].weights[k] += learning_rate * layers[i].neurons[j].value * layers[i + 1].neurons[k].delta;
}
}
}
}
void MLP::applyDropout(bool training)
{
// cout << "dropout_rate " << dropout_rate <<endl;
// skip first and last layers
for (size_t i = 1; i < layers.size() - 1; ++i)
{
// loop through all nuerons
// for (auto &neuron : layers[i].neurons)
for (size_t j = 0; j < layers[i].neurons.size(); ++j)
{
// if in the training phase
if (training == true)
{
//get rand num between 1 and 0
double dropout = ((double)rand() / RAND_MAX);
//if that rand num is below dropout_rate then set neuron to 0
if (dropout < dropout_rate)
{
//this effectivly "drops" the neuron since it no longer contributes to network
layers[i].neurons[j].value = 0;
// set drop to true
layers[i].neurons[j].dropped = true;
}
else
{
layers[i].neurons[j].dropped = false;
}
}
else
{
layers[i].neurons[j].dropped = false; // this turns them back white on eval
}
}
}
}
void MLP::normalizeBatch(const size_t layer_index, bool training, double momentum, double epsilon)
{
Layer &layer = layers[layer_index];
double mean;
double variance;
if (training)
{
// calculate the mean and variance of the neuron values in the layer
mean = 0.0;
variance = 0.0;
// using iterators to change it up a bit
for (auto it = layer.neurons.begin(); it != layer.neurons.end(); ++it)
{
mean += it->value;
}
mean = mean / layer.neurons.size();
for (auto it = layer.neurons.begin(); it != layer.neurons.end(); ++it)
{
variance += (it->value - mean) * (it->value - mean);
}
variance = variance / layer.neurons.size();
// if training, update the running mean and variance here
layer.running_mean = momentum * layer.running_mean + (1.0 - momentum) * mean;
layer.running_variance = momentum * layer.running_variance + (1.0 - momentum) * variance;
}
else
{
// if testing, update the running mean and variance for normalization here
mean = layer.running_mean;
variance = layer.running_variance;
}
// normalize the neuron values based on the mean and variance
for (auto it = layer.neurons.begin(); it != layer.neurons.end(); ++it)
{
it->value = (it->value - mean) / sqrt(variance + epsilon);
}
}
void MLP::normalizeBatchBackward(const size_t layer_index, const double epsilon)
{
Layer &layer = layers[layer_index];
double mean = 0;
double variance = 0;
// using iterators to change it up a bit
for (auto it = layer.neurons.begin(); it != layer.neurons.end(); ++it)
{
mean += it->value;
}
mean = mean/ layer.neurons.size();
for (auto it = layer.neurons.begin(); it != layer.neurons.end(); ++it)
{
variance += (it->value - mean) * (it->value - mean);
}
variance = variance/layer.neurons.size();
// find standard devaition
double std_dev = sqrt(variance + epsilon);
// find inverse of it
double inv_std_dev = 1.0 / std_dev;
// calculate derivative of the loss with respect to x for each neuron
for (auto it = layer.neurons.begin(); it != layer.neurons.end(); ++it)
{
// multiply delta by the inverse of the standard dev
it->delta = it->delta * inv_std_dev;
}
}
// **** prototype functions (implemented below main) ****
// parsing fucntion takes in a csv and two empty matricies, fills them with the normalization vals and true vals and returns a matrix of the csv
Matrix parseCSV(const string& filename, Matrix& norm_vals, Matrix& true_vals);
// split function splits the matrix at a collumn and saves it to left_mtx and right_mtx
void splitMatrixAtColumn(const Matrix& inputMatrix, int split_col_idx, Matrix& left_mtx, Matrix& right_mtx);
// graphviz creates a dot file of the MLP and topology that is passed
void graphviz(const string& filename,const MLP& mlp, const vector<size_t> topology, bool display_layer_stats = false);
int main()
{
srand(time(0));
// vector<size_t> topology = {2, 3, 1}; //input {2 input neurons, 1 hidden layer (with 3 neurons), 1 p}
unsigned int num_outputs = 3; // unsigned since it will always be positive
// vector<size_t> topology = {3, 64, 32, 16, num_outputs};
vector<size_t> topology = {13, 16, 32, 16, num_outputs}; //13 input features, 3 outputs
// vector<size_t> topology = {5, 10, 12, 10, num_outputs}; //13 input features, 3 outputs
// MLP mlp(topology, .4);
// since droput is applied after it affects batch normalization
string extention;
double dropout_rate = 0.1;
size_t num_epochs = 2;
double learning_rate = 0.01; // This is my alpha
bool apply_batch = false;
cout << endl;
MLP mlp(topology, dropout_rate, apply_batch);
if (apply_batch)
{
extention = "_bn";
cout << "Batch Normalization Applied";
}
else
{
extention = "_sk";
cout << "Batch Normalization Skipped";
}
if (dropout_rate != 0)
{
extention += "_dr";
}
else
{
extention += "_ndr";
}
// ***************** TRAINING *****************************
string train_file_name = "Month_Data/April_Data.csv";
Matrix train_norm_vals; //init here and pass by refrence
Matrix train_true_vals;
Matrix train_mtx = parseCSV(train_file_name, train_norm_vals, train_true_vals); // this parses and normalizes the matrix
// train_mtx.display();
int split_idx = num_outputs;
Matrix left_mtx, right_mtx;
splitMatrixAtColumn(train_mtx, split_idx, left_mtx, right_mtx);
Matrix train_inputs = right_mtx;
Matrix targets = left_mtx;
// train_inputs.display();
// cout << "True : " << endl;
// true_vals.display();
// cout << endl;
// training loop
for (size_t epoch = 0; epoch < num_epochs; ++epoch)
{
// iterate through the samples
for (size_t i = 0; i < train_inputs.rows(); ++i)
{
// forward calles the setInput function
// then perform forward propagation
mlp.forward(train_inputs[i], true); // passing 'true' since this is the training step
// perform backwards popagation to update weights
mlp.backward(targets[i], learning_rate);
}
}
// if batch normalization is working then each hidden layer should:
// have a mean near 0
// and a variance near 1
graphviz("train"+ extention + ".dot", mlp, topology, true); // mlp and topology are passed by reference
// ***************** TESTING *****************************
string test_file_name = "Month_Data/March_2023.csv";
// string test_file_name = "Month_Data/April_2023.csv";
Matrix test_norm_vals; //init here and pass by refrence
Matrix test_true_vals;
Matrix test_mtx = parseCSV(test_file_name, test_norm_vals, test_true_vals); // this parses and normalizes the matrix
// test_mtx.display();
// test_norm_vals.display();
// test_true_vals.display();
// int split_idx = num_outputs;
Matrix test_left_mtx, test_right_mtx;
splitMatrixAtColumn(test_mtx, split_idx, test_left_mtx, test_right_mtx);
cout << endl;
Matrix test_inputs = test_right_mtx;
// test_inputs.display();
// Matrix test_targets = test_left_mtx; // Normalized test values
// Test the trained MLP
double tmax_min_val = test_norm_vals[0][0];
double tmax_max_val = test_norm_vals[0][1];
double tavg_min_val = test_norm_vals[1][0];
double tavg_max_val = test_norm_vals[1][1];
double tmin_min_val = test_norm_vals[2][0];
double tmin_max_val = test_norm_vals[2][1];
// cout << tmax_min_val << " " << tmax_max_val<< endl;
// cout << tavg_min_val << " " << tavg_max_val << endl;
// cout << tmin_min_val << " " << tmin_max_val << endl;
// set to two decimals
cout << fixed << setprecision(2);
long day = 1;
VectorOps tmax_delta;
VectorOps tavg_delta;
VectorOps tmin_delta;
double tmax_squared_sum = 0.0;
double tavg_squared_sum = 0.0;
double tmin_squared_sum = 0.0;
for (size_t i = 0; i < test_inputs.rows(); ++i)
{
// Access the i-th row in inputs
vector<double>& input = test_inputs[i];
// passing 'false' since this is the Evaluation step
mlp.forward(input, false);
double out_0 = mlp.getOutVal()[0] * (tmax_max_val - tmax_min_val) + tmax_min_val; // val * range + min
double out_1 = mlp.getOutVal()[1] * (tavg_max_val - tavg_min_val) + tavg_min_val; // val * range + min
double out_2 = mlp.getOutVal()[2] * (tmin_max_val - tmin_min_val) + tmin_min_val; // val * range + min
double real_out_0 = test_true_vals[day-1][0];
double real_out_1 = test_true_vals[day-1][1];
double real_out_2 = test_true_vals[day-1][2];
double diff_0 = abs(out_0 - real_out_0);
tmax_delta.add(diff_0);
tmax_squared_sum += diff_0 * diff_0;
double diff_1 = abs(out_1 - real_out_1);
tavg_delta.add(diff_1);
tavg_squared_sum += diff_1 * diff_1;
double diff_2 = abs(out_2 - real_out_2);
tmin_delta.add(diff_2);
tmin_squared_sum += diff_2 * diff_2;
// cout << "Input: (" << input[0] << ", " << input[1] << ", " << input[2] << ", " << input[3] << ", " << input[4 ]<< ", ... ) ";
cout << day << ": ";
cout << out_0 <<" - "<< real_out_0 << ": " << diff_0 << ", ";
cout << out_1 <<" - "<< real_out_1 << ": " << diff_1 << ", " ;
cout << out_2 <<" - "<< real_out_2 << ": " << diff_2 << endl;
day++;
}
graphviz("test"+ extention + ".dot", mlp, topology); // mlp and topology are passed by reference
cout << endl;
// Temp Max, delta between pred and real
cout << "Temp Max" << endl;
// tmax_delta.display_vect();
// calculate the stats after all data has been added for better performance
tmax_delta.calc_stats();
// Display performance data
// tmax_delta.display_stats();
// cout << endl;
// Display Mean Absolute Error
cout << " Temp Max MAE: " << tmax_delta.mean << endl;
// Display Root Mean Squared Error
cout << " Temp Max RMSE: " << sqrt(tmax_squared_sum / (day-1)) << endl;
cout << endl;
// Temp Avg, delta between pred and real
cout << "Temp Avg Delta" << endl;
// tavg_delta.display_vect();
// calculate the stats after all data has been added for better performance
tavg_delta.calc_stats();
// display calculated stats
// tavg_delta.display_stats();
// cout << endl;
// Display Mean Absolute Error
cout << " Temp Avg MAE: " << tavg_delta.mean << endl;
// Display Root Mean Squared Error
cout << " Temp Avg RMSE: " << sqrt(tavg_squared_sum / (day-1)) << endl;
cout << endl;
// Temp Min, delta between pred and real
cout << "Temp Min Delta" << endl;
// tmin_delta.display_vect();
// calculate the stats after all data has been added for better performance
tmin_delta.calc_stats();
// display calculated stats
// tmin_delta.display_stats();
// cout << endl;
// Display Mean Absolute Error
cout << " Temp Min MAE: " << tmin_delta.mean << endl;
// Display Root Mean Squared Error
cout << " Temp Min RMSE: " << sqrt(tmin_squared_sum / (day-1)) << endl;
cout << endl;
return 0;
}
Matrix parseCSV(const string& filename, Matrix& norm_vals, Matrix& true_vals)
{
ifstream input(filename);
vector<vector<string>> data;
string line;
long i = 0;
while (getline(input, line))
{
++i;
if (i <= 2) //skip first two rows
{
continue;
}
stringstream ss(line);
vector<string> row;
string value;
getline(ss, value, ','); // grab the date so that its not in the row data
while (getline(ss, value, ','))
{
row.push_back(value);
}
data.push_back(row);
}
Matrix matrix;