@benp2325/benutils 0.0.0-stage → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.gitattributes +2 -0
- package/index.js +5 -0
- package/package.json +18 -4
- package/src/activations.js +117 -0
- package/src/arrmat.js +127 -0
- package/src/data.js +52 -0
- package/src/math.js +103 -0
- package/src/network.js +742 -0
- package/README.md +0 -3
package/.gitattributes
ADDED
package/index.js
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@benp2325/benutils",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "zero dependency personal library for math, array/matrix manipulation, and neural network modeling in JS",
|
|
5
|
+
"homepage": "https://github.com/BenPask/benutils",
|
|
6
|
+
"bugs": {
|
|
7
|
+
"url": "https://github.com/BenPask/benutils/issues"
|
|
8
|
+
},
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/BenPask/benutils.git"
|
|
12
|
+
},
|
|
13
|
+
"license": "ISC",
|
|
14
|
+
"author": "Benjamin Pask",
|
|
15
|
+
"type": "module",
|
|
16
|
+
"main": "index.js",
|
|
17
|
+
"scripts": {
|
|
18
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
function reluActivation(arr){
|
|
2
|
+
let arrOut = [];
|
|
3
|
+
arrOut = arr.map(x => x > 0 ? x : 0);
|
|
4
|
+
|
|
5
|
+
return arrOut
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function deriv_relu_activation(arr){
|
|
9
|
+
let arrOut = [];
|
|
10
|
+
|
|
11
|
+
for(const val of arr){
|
|
12
|
+
if(val <= 0){
|
|
13
|
+
arrOut.push(0);
|
|
14
|
+
}
|
|
15
|
+
else{
|
|
16
|
+
arrOut.push(1);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return arrOut;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function leaky_relu_activation(arr){
|
|
23
|
+
const leaky_constant = 0.01;
|
|
24
|
+
let arrOut = [];
|
|
25
|
+
|
|
26
|
+
arrOut = arr.map(x => x > 0 ? x : x*leaky_constant);
|
|
27
|
+
|
|
28
|
+
return arrOut;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function deriv_leaky_relu_activation(arr){
|
|
32
|
+
const leaky_constant = 0.01
|
|
33
|
+
let arrOut = [];
|
|
34
|
+
|
|
35
|
+
for(const val of arr){
|
|
36
|
+
if(val <= 0){
|
|
37
|
+
arrOut.push(leaky_constant);
|
|
38
|
+
}
|
|
39
|
+
else{
|
|
40
|
+
arrOut.push(1);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return arrOut;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function sigmoid_activation(arr){
|
|
47
|
+
let arrOut = [];
|
|
48
|
+
|
|
49
|
+
for(const val of arr){
|
|
50
|
+
arrOut.push(xpo(euler, val)/(xpo(euler, val)+1));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return arrOut;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function deriv_sigmoid_activation(arr){
|
|
57
|
+
let arrOut = [];
|
|
58
|
+
|
|
59
|
+
for(const val of arr){
|
|
60
|
+
arrOut.push(xpo(euler, val*-1)/xpo((xpo(euler, val*-1)+1), 2));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return arrOut;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function tanh_activation(arr){
|
|
67
|
+
let arrOut = [];
|
|
68
|
+
|
|
69
|
+
for(const val of arr){
|
|
70
|
+
const numerator = (xpo(euler, val)-xpo(euler, val*-1));
|
|
71
|
+
const denominator = (xpo(euler, val)+xpo(euler, -1*val));
|
|
72
|
+
arrOut.push(numerator/denominator);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return arrOut;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function deriv_tanh_activation(arr){
|
|
79
|
+
let arrOut = [];
|
|
80
|
+
|
|
81
|
+
for(const val of arr){
|
|
82
|
+
const numerator = (xpo(euler, val)-xpo(euler, val*-1));
|
|
83
|
+
const denominator = (xpo(euler, val)+xpo(euler, -1*val));
|
|
84
|
+
arrOut.push(1-xpo((numerator/denominator), 2));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return arrOut;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function no_activation(arr){
|
|
91
|
+
return arr;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function deriv_no_activation(arr){
|
|
95
|
+
return arr.map(x => 1);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const activation_map = {"none":no_activation,"relu":reluActivation,"sigmoid":sigmoid_activation,"tanh":tanh_activation,"leaky_relu":leaky_relu_activation};
|
|
99
|
+
const deriv_acitvation_map = {"none":deriv_no_activation,"relu":deriv_relu_activation,"sigmoid":deriv_sigmoid_activation,"tanh":deriv_tanh_activation,"leaky":deriv_leaky_relu_activation};
|
|
100
|
+
|
|
101
|
+
function activate(model, arr_in, o_layer=false){
|
|
102
|
+
if(o_layer == true){
|
|
103
|
+
const acitvation_func = activation_map[model.properties.output_layer_info.activation];
|
|
104
|
+
return acitvation_func(arr_in);
|
|
105
|
+
}
|
|
106
|
+
const acitvation_func = activation_map[model.properties.activation_type];
|
|
107
|
+
return acitvation_func(arr_in);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function deriv_activate(model, arr_in, o_layer=false){
|
|
111
|
+
if(o_layer == true){
|
|
112
|
+
const acitvation_func = deriv_acitvation_map[model.properties.output_layer_info.activation];
|
|
113
|
+
return acitvation_func(arr_in);
|
|
114
|
+
}
|
|
115
|
+
const acitvation_func = deriv_acitvation_map[model.properties.activation_type];
|
|
116
|
+
return acitvation_func(arr_in);
|
|
117
|
+
}
|
package/src/arrmat.js
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
//-------ARRAYS & MATRICES-------
|
|
2
|
+
export function array_to_matrix(array_in){
|
|
3
|
+
if(array_in.length == 0) return [[]];
|
|
4
|
+
|
|
5
|
+
let array_out = array_in;
|
|
6
|
+
|
|
7
|
+
//dealing with 1d array cases
|
|
8
|
+
if(!Array.isArray(array_in[0])){
|
|
9
|
+
array_out = [array_in];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
return array_out;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function array_mean(array_in){
|
|
16
|
+
let total = 0;
|
|
17
|
+
for(const value of array_in){
|
|
18
|
+
total += value;
|
|
19
|
+
}
|
|
20
|
+
return total/array_in.length;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function arrmult(array1, array2){
|
|
24
|
+
let array_out = [];
|
|
25
|
+
for(let i = 0; i<array1.length; i++){
|
|
26
|
+
array_out.push(array1[i]*array2[i]);
|
|
27
|
+
}
|
|
28
|
+
return array_out;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function transpose_matrix(m){
|
|
32
|
+
|
|
33
|
+
let output = [];
|
|
34
|
+
|
|
35
|
+
if(!Array.isArray(m[0])){
|
|
36
|
+
return m.map(x => [x])
|
|
37
|
+
}
|
|
38
|
+
//var for # of rows
|
|
39
|
+
const m_rows = m.length;
|
|
40
|
+
|
|
41
|
+
//var for # of columns
|
|
42
|
+
const m_cols = m[0].length;
|
|
43
|
+
|
|
44
|
+
//for # of columns in matrix
|
|
45
|
+
for(let c = 0; c<m_cols; c++){
|
|
46
|
+
const cur_row_out = [];
|
|
47
|
+
//for # of rows in matrix
|
|
48
|
+
for(let r = 0; r<m_rows; r++){
|
|
49
|
+
cur_row_out.push(m[r][c]);
|
|
50
|
+
}
|
|
51
|
+
output.push(cur_row_out);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
return output;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function matmult(m1, m2){
|
|
59
|
+
|
|
60
|
+
//transpose m2 for easier multiplication
|
|
61
|
+
m2 = transpose_matrix(m2);
|
|
62
|
+
|
|
63
|
+
m1 = array_to_matrix(m1);
|
|
64
|
+
m2 = array_to_matrix(m2);
|
|
65
|
+
|
|
66
|
+
let m1_rows = m1.length;
|
|
67
|
+
let m1_cols = m1[0].length;
|
|
68
|
+
|
|
69
|
+
let m2_rows = m2.length;
|
|
70
|
+
let m2_cols = m2[0].length;
|
|
71
|
+
|
|
72
|
+
if(m1[0].length!=m2[0].length){
|
|
73
|
+
throw new Error("The amount of columns in m1 must match the amount of rows in m2");
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
let product = [];
|
|
77
|
+
|
|
78
|
+
//for the # of rows in the first matrix
|
|
79
|
+
for(let r = 0; r<m1_rows; r++){
|
|
80
|
+
product.push([]);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
for(let r_m1 = 0; r_m1<m1_rows; r_m1++){
|
|
84
|
+
//for the # of columns in the transposition of m2
|
|
85
|
+
for(let r_m2 = 0; r_m2<m2_rows; r_m2++){
|
|
86
|
+
let cur_value_out = 0;
|
|
87
|
+
//for the # of columns in the first matrix
|
|
88
|
+
for(let c_m1 = 0; c_m1<m1_cols; c_m1++){
|
|
89
|
+
cur_value_out += m1[r_m1][c_m1]*m2[r_m2][c_m1];
|
|
90
|
+
}
|
|
91
|
+
product[r_m1].push(cur_value_out);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return product;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function matadd(matrix1, matrix2){
|
|
98
|
+
let matrix_out = [];
|
|
99
|
+
|
|
100
|
+
//deal with 1d array input cases (just formatting to [[m1]] instead of [m1])
|
|
101
|
+
matrix1 = array_to_matrix(matrix1)
|
|
102
|
+
matrix2 = array_to_matrix(matrix2)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
for(let r = 0; r<matrix1.length; r++){
|
|
106
|
+
matrix_out.push([])
|
|
107
|
+
for(let c = 0; c<matrix1[0].length; c++){
|
|
108
|
+
matrix_out[r].push(matrix1[r][c]+matrix2[r][c])
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return matrix_out;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function matsub(matrix1, matrix2){
|
|
116
|
+
matrix1 = array_to_matrix(matrix1)
|
|
117
|
+
matrix2 = array_to_matrix(matrix2)
|
|
118
|
+
|
|
119
|
+
const negative_matrix2 = [];
|
|
120
|
+
|
|
121
|
+
for(const array of matrix2){
|
|
122
|
+
negative_matrix2.push(array.map(x => x*(-1)));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const matrix_out = matadd(matrix1, negative_matrix2);
|
|
126
|
+
return matrix_out;
|
|
127
|
+
}
|
package/src/data.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
//-------DATA-------
|
|
2
|
+
export function normalize_array(arr, max_in, min_in){
|
|
3
|
+
const normalized_arr = []
|
|
4
|
+
|
|
5
|
+
for(const value of arr){
|
|
6
|
+
const normalized_value = (value-min_in)/(max_in-min_in);
|
|
7
|
+
normalized_arr.push(normalized_value);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
return normalized_arr;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function denormalize_array(arr, max_in, min_in){
|
|
14
|
+
const denormalized_arr = []
|
|
15
|
+
|
|
16
|
+
for(const value of arr){
|
|
17
|
+
const denormalized_value = (value*(max_in-min_in))+min_in;
|
|
18
|
+
denormalized_arr.push(denormalized_value);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return denormalized_arr;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function shuffle_feature_data(features_in, model=null){
|
|
25
|
+
|
|
26
|
+
const feat_names = Object.keys(features_in)
|
|
27
|
+
|
|
28
|
+
const observation_count = features_in[feat_names[0]].length;
|
|
29
|
+
|
|
30
|
+
const features_copy = {};
|
|
31
|
+
for(const name of feat_names){
|
|
32
|
+
features_copy[name] = [...features_in[name]];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
//fisher yates shuffle
|
|
36
|
+
for(let i = observation_count - 1; i > 0; i--){
|
|
37
|
+
|
|
38
|
+
let zto_gen = zto_random();
|
|
39
|
+
|
|
40
|
+
if(model!=null) zto_gen = zto_random(model);
|
|
41
|
+
|
|
42
|
+
const j = round_float((zto_gen * (i+1))-0.5);
|
|
43
|
+
|
|
44
|
+
for(const name of feat_names){
|
|
45
|
+
const temp = features_copy[name][i];
|
|
46
|
+
features_copy[name][i] = features_copy[name][j];
|
|
47
|
+
features_copy[name][j] = temp;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return features_copy;
|
|
52
|
+
}
|
package/src/math.js
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
//-------MATH-------
|
|
2
|
+
const euler = 2.718281828459045;
|
|
3
|
+
let global_seed = 75128;
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
export function zto_random(model=null){
|
|
7
|
+
const a = 49234;
|
|
8
|
+
const m = 1000000007; //large prime number
|
|
9
|
+
|
|
10
|
+
if(model!=null){
|
|
11
|
+
model.properties.current_seed = (model.properties.current_seed * a) % m;
|
|
12
|
+
return model.properties.current_seed / m;
|
|
13
|
+
}
|
|
14
|
+
//calc next seed
|
|
15
|
+
global_seed = (global_seed*a)%m;
|
|
16
|
+
|
|
17
|
+
//turn it into a number between 0 and 1
|
|
18
|
+
return global_seed/m;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function xpo(value, power){
|
|
22
|
+
let effective_power = power;
|
|
23
|
+
|
|
24
|
+
if(power<0){
|
|
25
|
+
effective_power = power * -1;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
let current = value;
|
|
29
|
+
for(let i = 0; i<effective_power-1; i++){
|
|
30
|
+
current = current*value;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if(power<0){
|
|
34
|
+
current = 1/current;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return current;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function round_float(float){
|
|
41
|
+
if(float<0){
|
|
42
|
+
return -1*(((-1*float)+0.5) | 0);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return float+0.5 | 0;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function absolute_value(value){
|
|
49
|
+
if(value<0){
|
|
50
|
+
return value * -1
|
|
51
|
+
}
|
|
52
|
+
return value
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function sqroot(value, tolerance=1e-15){
|
|
56
|
+
if(value<0){
|
|
57
|
+
throw new Error("Cannot take the square root of negative numbers")
|
|
58
|
+
}
|
|
59
|
+
if(value == 0){
|
|
60
|
+
return 0
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
let guess = value/2
|
|
64
|
+
|
|
65
|
+
let iter = 0
|
|
66
|
+
while(iter < 1000){
|
|
67
|
+
let next_guess = 0.5*(guess + value/guess)
|
|
68
|
+
|
|
69
|
+
if(absolute_value(guess - next_guess) < tolerance){
|
|
70
|
+
return next_guess
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
guess = next_guess
|
|
74
|
+
iter += 1;
|
|
75
|
+
}
|
|
76
|
+
return guess;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function closest_pow_of_two(val){
|
|
80
|
+
|
|
81
|
+
if(typeof val != 'number'){
|
|
82
|
+
throw new Error("non-number attempted to pass through closeset_pow_of_two");
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
let current = null;
|
|
86
|
+
let prev_current = 2;
|
|
87
|
+
|
|
88
|
+
while(true){
|
|
89
|
+
current = prev_current*2;
|
|
90
|
+
|
|
91
|
+
if(current>val){
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
prev_current = current;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if(absolute_value(val-prev_current)>absolute_value(val-current)){
|
|
98
|
+
return current;
|
|
99
|
+
}
|
|
100
|
+
else{
|
|
101
|
+
return prev_current;
|
|
102
|
+
}
|
|
103
|
+
}
|
package/src/network.js
ADDED
|
@@ -0,0 +1,742 @@
|
|
|
1
|
+
import './math.js'
|
|
2
|
+
import './arrmat.js'
|
|
3
|
+
import './activations.js'
|
|
4
|
+
import './data.js'
|
|
5
|
+
|
|
6
|
+
//-------NEURAL_NETWORK-------
|
|
7
|
+
function generate_weight(model, prev_layer_size){
|
|
8
|
+
//Utilizing he weight generation to prevent explosions to infinity
|
|
9
|
+
const limit = sqroot(6/prev_layer_size);
|
|
10
|
+
|
|
11
|
+
const zeroToOne = zto_random(model);
|
|
12
|
+
|
|
13
|
+
const weightOut = (zeroToOne*2*limit)-limit;
|
|
14
|
+
|
|
15
|
+
return weightOut;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function set_features(model, features_in){
|
|
19
|
+
const maxMins = model.properties.feature_data.maxMins;
|
|
20
|
+
const features = {};
|
|
21
|
+
|
|
22
|
+
for(const feat_name of Object.keys(features_in)){
|
|
23
|
+
const first_val = features_in[feat_name][0];
|
|
24
|
+
|
|
25
|
+
maxMins[feat_name] = {"max":first_val,"min":first_val};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
for(const feat_name of Object.keys(features_in)){
|
|
29
|
+
for(const value of features_in[feat_name]){
|
|
30
|
+
features[feat_name].push(value);
|
|
31
|
+
|
|
32
|
+
if(value>maxMins[feat_name]["max"]) maxMins[feat_name]["max"] = value;
|
|
33
|
+
if(value<maxMins[feat_name]["min"]) maxMins[feat_name]["min"] = value;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
features = shuffle_feature_data(features, model);
|
|
37
|
+
model.properties.features = features;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function set_features_csv(model, csvData, feature_selection){
|
|
41
|
+
csvData = csvData.trim().split('\n').map(row=>row.split(','));
|
|
42
|
+
|
|
43
|
+
//csv header safegaurd, default=0 or "no headers"
|
|
44
|
+
let start_index = 0;
|
|
45
|
+
|
|
46
|
+
const maxMins = model.properties.feature_data.maxMins
|
|
47
|
+
let features = {};
|
|
48
|
+
|
|
49
|
+
for(const feat_name of Object.keys(feature_selection)){
|
|
50
|
+
features[feat_name] = [];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
//safegaurd for csv headers, just removing them because feature_selection handles labels
|
|
54
|
+
if(isNaN(parseFloat(csvData[0][feature_selection["TARGET"]]))){
|
|
55
|
+
start_index = 1;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
//Initialize map of feature maximum and minimum values
|
|
59
|
+
for(const feat_name of Object.keys(feature_selection)){
|
|
60
|
+
const first_val = parseFloat(csvData[start_index][feature_selection[feat_name]]);
|
|
61
|
+
|
|
62
|
+
maxMins[feat_name] = {"max":first_val,"min":first_val};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
for(let row = start_index; row < csvData.length; row++){
|
|
66
|
+
for(const feat_name of Object.keys(feature_selection)){
|
|
67
|
+
|
|
68
|
+
const value = parseFloat(csvData[row][feature_selection[feat_name]]);
|
|
69
|
+
|
|
70
|
+
features[feat_name].push(value);
|
|
71
|
+
|
|
72
|
+
if(value>maxMins[feat_name]["max"]) maxMins[feat_name]["max"] = value;
|
|
73
|
+
if(value<maxMins[feat_name]["min"]) maxMins[feat_name]["min"] = value;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
features = shuffle_feature_data(features, model);
|
|
78
|
+
model.properties.features = features;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function normalize_features(model){
|
|
82
|
+
const features = model.properties.features;
|
|
83
|
+
|
|
84
|
+
for(const feat_name of Object.keys(features)){
|
|
85
|
+
normalize_feature(model, feat_name);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function normalize_feature(model, feat_name){
|
|
90
|
+
const maxMins = model.properties.feature_data.maxMins;
|
|
91
|
+
const features = model.properties.features;
|
|
92
|
+
//Create a key for this feature in normalized_features and assign the normalized values to it
|
|
93
|
+
features[feat_name] = normalize_array(features[feat_name], maxMins[feat_name]["max"], maxMins[feat_name]["min"]);
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
function fwd_pass(model, layer_index){
|
|
97
|
+
const indexed_layers = Object.values(model.layers);
|
|
98
|
+
|
|
99
|
+
const prev_layer_output = indexed_layers[layer_index-1]["output"];
|
|
100
|
+
const weight_matrix = indexed_layers[layer_index]["main"]["weights"];
|
|
101
|
+
const bias_arr = indexed_layers[layer_index]["main"]["biases"];
|
|
102
|
+
|
|
103
|
+
const t_prev_layer_output = transpose_matrix(prev_layer_output);
|
|
104
|
+
|
|
105
|
+
const pre_t_multiplied_output = matmult(weight_matrix, t_prev_layer_output);
|
|
106
|
+
|
|
107
|
+
const multiplied_output = transpose_matrix(pre_t_multiplied_output);
|
|
108
|
+
|
|
109
|
+
let arr_out = [];
|
|
110
|
+
|
|
111
|
+
for(let i = 0;i<multiplied_output[0].length;i++){
|
|
112
|
+
arr_out.push(multiplied_output[0][i]+bias_arr[i]);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
indexed_layers[layer_index]["preactivation"] = arr_out;
|
|
116
|
+
|
|
117
|
+
//dynamic activation function based on model.properties.activation_type
|
|
118
|
+
if(layer_index == indexed_layers.length-1){
|
|
119
|
+
arr_out = activate(model, arr_out, true);
|
|
120
|
+
}
|
|
121
|
+
else{
|
|
122
|
+
arr_out = activate(model, arr_out);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
indexed_layers[layer_index]["output"] = arr_out;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function calculate_mse(predictions, targets){
|
|
129
|
+
let total_error = 0;
|
|
130
|
+
|
|
131
|
+
for(let i = 0; i < predictions.length; i++){
|
|
132
|
+
let error = targets[i] - predictions[i];
|
|
133
|
+
total_error += error*error;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return total_error/predictions.length;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function calculate_rmse(model, predictions, targets){
|
|
140
|
+
const prediction_count = predictions.length;
|
|
141
|
+
|
|
142
|
+
let total_residuals = 0;
|
|
143
|
+
const targets_og_min = model.properties.feature_data.maxMins["TARGET"]["min"];
|
|
144
|
+
const targets_og_max = model.properties.feature_data.maxMins["TARGET"]["max"];
|
|
145
|
+
|
|
146
|
+
predictions = denormalize_array(predictions, targets_og_max, targets_og_min);
|
|
147
|
+
targets = denormalize_array(targets, targets_og_max, targets_og_min);
|
|
148
|
+
|
|
149
|
+
for(let i = 0; i < prediction_count; i++){
|
|
150
|
+
let residual = (targets[i]-predictions[i])*(targets[i]-predictions[i]);
|
|
151
|
+
total_residuals += residual;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return(sqroot(total_residuals/prediction_count));
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function calculate_mae(model, predictions, targets){
|
|
158
|
+
const prediction_count = predictions.length;
|
|
159
|
+
|
|
160
|
+
let total_error = 0;
|
|
161
|
+
|
|
162
|
+
const targets_og_min = model.properties.feature_data.maxMins["TARGET"]["min"];
|
|
163
|
+
const targets_og_max = model.properties.feature_data.maxMins["TARGET"]["max"];
|
|
164
|
+
|
|
165
|
+
predictions = denormalize_array(predictions, targets_og_max, targets_og_min);
|
|
166
|
+
targets = denormalize_array(targets, targets_og_max, targets_og_min);
|
|
167
|
+
|
|
168
|
+
for(let i = 0; i < prediction_count; i++){
|
|
169
|
+
let error = absolute_value(targets[i]-predictions[i]);
|
|
170
|
+
total_error += error;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return total_error/prediction_count;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function calculate_rsquared(model, predictions, targets){
|
|
177
|
+
const prediction_count = predictions.length;
|
|
178
|
+
|
|
179
|
+
let sum_residuals = 0;
|
|
180
|
+
let sum_squares = 0;
|
|
181
|
+
|
|
182
|
+
const target_mean = array_mean(targets);
|
|
183
|
+
|
|
184
|
+
for(let i = 0; i < prediction_count; i++){
|
|
185
|
+
sum_residuals += (targets[i]-predictions[i])*(targets[i]-predictions[i]);
|
|
186
|
+
sum_squares += (targets[i]-target_mean)*(targets[i]-target_mean);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return 1-(sum_residuals/sum_squares);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function calculate_loss_gradient(model, target_value, layer_index){
|
|
193
|
+
const indexed_layers = Object.values(model.layers);
|
|
194
|
+
|
|
195
|
+
if(layer_index==indexed_layers.length-1){
|
|
196
|
+
const loss_gradient = [];
|
|
197
|
+
const post_activation_final_output = model.layers["output_layer"]["output"];
|
|
198
|
+
|
|
199
|
+
for(const value of post_activation_final_output){
|
|
200
|
+
//CHANGE LATER if no longer regression
|
|
201
|
+
loss_gradient.push(value-target_value);
|
|
202
|
+
}
|
|
203
|
+
return loss_gradient;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const next_weights = indexed_layers[layer_index+1]["main"]["weights"];
|
|
207
|
+
const next_delta = indexed_layers[layer_index+1]["delta"]["layer_delta"];
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
const transposed_next_weights = transpose_matrix(next_weights);
|
|
211
|
+
|
|
212
|
+
const transposed_next_delta = transpose_matrix(next_delta);
|
|
213
|
+
|
|
214
|
+
let loss_gradient = matmult(transposed_next_weights, transposed_next_delta);
|
|
215
|
+
|
|
216
|
+
loss_gradient = transpose_matrix(loss_gradient);
|
|
217
|
+
|
|
218
|
+
return loss_gradient[0];
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function calculate_derivative_array(model, layer_index){
|
|
222
|
+
|
|
223
|
+
const indexed_layers = Object.values(model.layers);
|
|
224
|
+
|
|
225
|
+
if(layer_index==indexed_layers.length-1){
|
|
226
|
+
const pre_activation_output = model.layers["output_layer"]["preactivation"];
|
|
227
|
+
|
|
228
|
+
const derivative_array = deriv_activate(model, pre_activation_output, true);
|
|
229
|
+
|
|
230
|
+
return derivative_array;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
const pre_activation_output = indexed_layers[layer_index]["preactivation"];
|
|
235
|
+
|
|
236
|
+
const derivative_array = deriv_activate(model, pre_activation_output);
|
|
237
|
+
|
|
238
|
+
return derivative_array;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function bwd_pass(model, layer_index, target_value){
|
|
242
|
+
const indexed_layers = Object.values(model.layers);
|
|
243
|
+
|
|
244
|
+
const loss_gradient = calculate_loss_gradient(model, target_value, layer_index);
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
const derivative_array = calculate_derivative_array(model, layer_index);
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
const layer_delta = arrmult(loss_gradient, derivative_array);
|
|
251
|
+
indexed_layers[layer_index]["delta"]["layer_delta"] = layer_delta;
|
|
252
|
+
|
|
253
|
+
const previous_layer_output = indexed_layers[layer_index-1]["output"];
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
const delta_col = transpose_matrix(layer_delta);
|
|
257
|
+
|
|
258
|
+
const delta_weight_gradient = matmult(delta_col, previous_layer_output);
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
const current_delta_gradient = indexed_layers[layer_index]["delta"]["weights"];
|
|
263
|
+
const current_bias_array = indexed_layers[layer_index]["delta"]["biases"];
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
indexed_layers[layer_index]["delta"]["weights"] = matadd(current_delta_gradient, delta_weight_gradient);
|
|
267
|
+
for(let i = 0; i < layer_delta.length; i++){
|
|
268
|
+
current_bias_array[i] += layer_delta[i];
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function average_deltas(model, custom=0){
|
|
274
|
+
|
|
275
|
+
const learn_rate = model.properties.learn_rate;
|
|
276
|
+
|
|
277
|
+
const indexed_layers = Object.values(model.layers);
|
|
278
|
+
|
|
279
|
+
for(let i = 1; i<indexed_layers.length; i++){
|
|
280
|
+
let current_delta_weights = indexed_layers[i]["delta"]["weights"];
|
|
281
|
+
const current_delta_biases = indexed_layers[i]["delta"]["biases"];
|
|
282
|
+
let interval_divisor = model.properties.batch_size;
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
if(custom!=0){
|
|
286
|
+
interval_divisor = custom;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
let averaged_delta_weights = [];
|
|
290
|
+
|
|
291
|
+
current_delta_weights = array_to_matrix(current_delta_weights);
|
|
292
|
+
for(const array of current_delta_weights){
|
|
293
|
+
const row_out = [];
|
|
294
|
+
for(const value of array){
|
|
295
|
+
row_out.push((value/interval_divisor)*learn_rate);
|
|
296
|
+
}
|
|
297
|
+
averaged_delta_weights.push(row_out);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const averaged_delta_biases = [];
|
|
301
|
+
|
|
302
|
+
for(const value of current_delta_biases){
|
|
303
|
+
averaged_delta_biases.push((value/interval_divisor)*learn_rate);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
indexed_layers[i]["delta"]["weights"] = averaged_delta_weights;
|
|
307
|
+
indexed_layers[i]["delta"]["biases"] = averaged_delta_biases;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function update_weights_and_biases(model){
|
|
312
|
+
const indexed_layers = Object.values(model.layers);
|
|
313
|
+
|
|
314
|
+
for(let i = 1; i<indexed_layers.length; i++){
|
|
315
|
+
const current_weights = indexed_layers[i]["main"]["weights"];
|
|
316
|
+
const current_delta_weights = indexed_layers[i]["delta"]["weights"];
|
|
317
|
+
|
|
318
|
+
const current_biases = indexed_layers[i]["main"]["biases"];
|
|
319
|
+
const current_delta_biases = indexed_layers[i]["delta"]["biases"];
|
|
320
|
+
|
|
321
|
+
indexed_layers[i]["main"]["weights"] = matsub(current_weights, current_delta_weights);
|
|
322
|
+
indexed_layers[i]["main"]["biases"] = matsub(current_biases, current_delta_biases)[0];
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function reset_deltas(model){
|
|
327
|
+
const indexed_layers = Object.values(model.layers);
|
|
328
|
+
|
|
329
|
+
for(let i = 1; i<indexed_layers.length; i++){
|
|
330
|
+
const reset_delta_weights = [];
|
|
331
|
+
const reset_delta_biases = [];
|
|
332
|
+
|
|
333
|
+
const current_delta_biases = indexed_layers[i]["delta"]["biases"];
|
|
334
|
+
const current_delta_weights = indexed_layers[i]["delta"]["weights"];
|
|
335
|
+
|
|
336
|
+
for(const array of current_delta_weights){
|
|
337
|
+
const row_out = [];
|
|
338
|
+
for(const value of array){
|
|
339
|
+
row_out.push(0);
|
|
340
|
+
}
|
|
341
|
+
reset_delta_weights.push(row_out);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
for(const value of current_delta_biases){
|
|
345
|
+
reset_delta_biases.push(0);
|
|
346
|
+
}
|
|
347
|
+
indexed_layers[i]["delta"]["weights"] = reset_delta_weights;
|
|
348
|
+
indexed_layers[i]["delta"]["biases"] = reset_delta_biases;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function flat_build(model, num_of_layers){
|
|
353
|
+
|
|
354
|
+
const init_value = model.properties.hidden_layer_info.init_size;
|
|
355
|
+
|
|
356
|
+
const schema_values = [];
|
|
357
|
+
|
|
358
|
+
for(let i = 0; i < num_of_layers; i++){
|
|
359
|
+
schema_values.push(0);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
for(let i = 0; i < schema_values.length; i++){
|
|
363
|
+
schema_values[i] = init_value;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
return schema_values;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
function power_of_two_build(model, num_of_layers){
|
|
371
|
+
|
|
372
|
+
let init_value = closest_pow_of_two(model.properties.hidden_layer_info.init_size);
|
|
373
|
+
//How many incremental divisions by 2 will it take to get down to just 2 neurons
|
|
374
|
+
let max_increments = 0;
|
|
375
|
+
let temp_val = init_value;
|
|
376
|
+
|
|
377
|
+
while(temp_val > 2){
|
|
378
|
+
temp_val = temp_val/2;
|
|
379
|
+
max_increments += 1;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
let layers_per_increment = round_float((num_of_layers/max_increments)-0.5);
|
|
383
|
+
|
|
384
|
+
if(layers_per_increment == 0){
|
|
385
|
+
layers_per_increment = 1;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const schema_values = [];
|
|
389
|
+
|
|
390
|
+
for(let i = 0; i < num_of_layers; i++){
|
|
391
|
+
schema_values.push(0);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
let cur_increment_count = 0;
|
|
395
|
+
|
|
396
|
+
for(let i = 0; i < schema_values.length; i++){
|
|
397
|
+
|
|
398
|
+
cur_increment_count++;
|
|
399
|
+
|
|
400
|
+
schema_values[i] = init_value;
|
|
401
|
+
|
|
402
|
+
if(cur_increment_count>=layers_per_increment){
|
|
403
|
+
if(init_value/2 >= 2){
|
|
404
|
+
init_value = init_value/2;
|
|
405
|
+
cur_increment_count = 0;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
return schema_values;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
function hourglass_value(model, value, inverse=false){
|
|
413
|
+
let hourglass_operator = model.properties.hidden_layer_info.hourglass_operation;
|
|
414
|
+
|
|
415
|
+
const operation_map = {"/":"*", "sqroot":"**", "*":"/", "**":"sqroot"};
|
|
416
|
+
|
|
417
|
+
if(inverse){
|
|
418
|
+
hourglass_operator = operation_map[hourglass_operator];
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
switch (hourglass_operator) {
|
|
422
|
+
case "/":
|
|
423
|
+
return value/2;
|
|
424
|
+
case "*":
|
|
425
|
+
return value*2;
|
|
426
|
+
case "sqroot":
|
|
427
|
+
return sqroot(value);
|
|
428
|
+
case "**":
|
|
429
|
+
return value**2;
|
|
430
|
+
default:
|
|
431
|
+
return value/2;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
function hourglass_build(model, num_of_layers){
|
|
436
|
+
let init_value = model.properties.hidden_layer_info.init_size;
|
|
437
|
+
|
|
438
|
+
const schema_values = [];
|
|
439
|
+
|
|
440
|
+
for(let i = 0; i < num_of_layers; i++){
|
|
441
|
+
schema_values.push(0);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
for(let i = 0; i < num_of_layers; i++){
|
|
445
|
+
if(i<round_float(num_of_layers/2) && (!(num_of_layers%2!=0 && i==round_float((num_of_layers/2)-0.5)))){
|
|
446
|
+
schema_values[i] = init_value;
|
|
447
|
+
if(hourglass_value(model, init_value) >= 2){
|
|
448
|
+
init_value = round_float(hourglass_value(model, init_value));
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
else{
|
|
452
|
+
schema_values[i] = init_value;
|
|
453
|
+
if(hourglass_value(model, init_value) >= 2){
|
|
454
|
+
init_value = round_float(hourglass_value(model, init_value, true));
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
return schema_values;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
function build_matrices(model){
|
|
462
|
+
model.layers["input_layer"] = {"output":[]};
|
|
463
|
+
for(let i = 1;i<=model.properties.hidden_layer_info.count;i++){
|
|
464
|
+
model.layers[`hidden_layer_${i}`] = {"main":{"weights":[],"biases":[]}, "delta":{"weights":[],"biases":[]},"preactivation":[], "output":[]};
|
|
465
|
+
}
|
|
466
|
+
model.layers["output_layer"] = {"main":{"weights":[],"biases":[]}, "delta":{"weights":[],"biases":[]}, "preactivation":[], "output":[]};
|
|
467
|
+
|
|
468
|
+
//we have 1D arrays for the main & delta weights, we now add an amount of arrays(rows of matrix) for the size of the layer
|
|
469
|
+
//This next line just lets us call the layers in order via index. 0 is the input layer
|
|
470
|
+
const indexed_layers = Object.values(model.layers);
|
|
471
|
+
|
|
472
|
+
const style_map = {"flat":flat_build,"po2":power_of_two_build,"hourglass":hourglass_build};
|
|
473
|
+
|
|
474
|
+
const hidden_count = model.properties.hidden_layer_info.count;
|
|
475
|
+
const hidden_layer_schema = style_map[model.properties.hidden_layer_info.style](model, hidden_count);
|
|
476
|
+
|
|
477
|
+
//matrix builder, does not yet contain columns or weights ie: layers["hidden_layer_1"] = [[][][][][]] if hidden layer size was 5
|
|
478
|
+
for(let i=1; i<indexed_layers.length-1; i++){
|
|
479
|
+
const layer_size = hidden_layer_schema[i-1];
|
|
480
|
+
|
|
481
|
+
for(let r = 0; r<layer_size;r++){
|
|
482
|
+
//add empty rows to hidden layer's main weights
|
|
483
|
+
indexed_layers[i]["main"]["weights"].push([]);
|
|
484
|
+
|
|
485
|
+
//add empty rows to the delta version of hidden layer's weights
|
|
486
|
+
indexed_layers[i]["delta"]["weights"].push([]);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
for(let r = 0; r<model.properties.output_layer_info.init_size;r++){
|
|
490
|
+
//add empty rows to output layer weights
|
|
491
|
+
indexed_layers[indexed_layers.length-1]["main"]["weights"].push([]);
|
|
492
|
+
|
|
493
|
+
//add empty rows to delta version of the output layer weights
|
|
494
|
+
indexed_layers[indexed_layers.length-1]["delta"]["weights"].push([]);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
function fill_matrices(model, feature_count){
|
|
499
|
+
const indexed_layers = Object.values(model.layers);
|
|
500
|
+
|
|
501
|
+
//Because the amount of columns(weights) per layer are determined by the output of the previouse layer,
|
|
502
|
+
//initialize the count to be the size of the INPUT layer, then adjust as you move through the layers
|
|
503
|
+
let prev_layer_size = feature_count;
|
|
504
|
+
|
|
505
|
+
for(let i=1; i<indexed_layers.length; i++){
|
|
506
|
+
for(const m_row of indexed_layers[i]["main"]["weights"]){
|
|
507
|
+
//push a generated weight for the size of the previous layers output.
|
|
508
|
+
for(let i = 0;i<prev_layer_size;i++) m_row.push(generate_weight(model, prev_layer_size));
|
|
509
|
+
}
|
|
510
|
+
for(const m_row of indexed_layers[i]["delta"]["weights"]){
|
|
511
|
+
//push a 0 for the size of the previous layers output.
|
|
512
|
+
for(let i = 0;i<prev_layer_size;i++) m_row.push(0);
|
|
513
|
+
}
|
|
514
|
+
//update the previous layer size variable to be this layer before moving on
|
|
515
|
+
prev_layer_size = indexed_layers[i]["main"]["weights"].length;
|
|
516
|
+
|
|
517
|
+
for(let n = 0; n < indexed_layers[i]["main"]["weights"].length; n++){
|
|
518
|
+
indexed_layers[i]["main"]["biases"].push(0);
|
|
519
|
+
indexed_layers[i]["delta"]["biases"].push(0);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
function iteration(model, obs_num, feature_data, target_data, bp=true){
|
|
525
|
+
model.layers["input_layer"]["output"] = [];
|
|
526
|
+
|
|
527
|
+
const target_value = target_data["TARGET"][obs_num];
|
|
528
|
+
|
|
529
|
+
//set the model.layers["input_layer"]["output"] to the values of the feature's for this observation number
|
|
530
|
+
for(const feat_name of Object.keys(feature_data)){
|
|
531
|
+
model.layers["input_layer"]["output"].push(feature_data[feat_name][obs_num]);
|
|
532
|
+
};
|
|
533
|
+
|
|
534
|
+
for(let i = 1;i < Object.keys(model.layers).length; i++){
|
|
535
|
+
fwd_pass(model, i);
|
|
536
|
+
}
|
|
537
|
+
if(bp==true){
|
|
538
|
+
for(let i = Object.keys(model.layers).length-1;i >= 1; i--){
|
|
539
|
+
bwd_pass(model, i, target_value);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
function train(model){
|
|
546
|
+
const loss_list = [];
|
|
547
|
+
const rmse_list = model.properties.performance_metrics.training.rmse_list;
|
|
548
|
+
const mae_list = model.properties.performance_metrics.training.mae_list;
|
|
549
|
+
|
|
550
|
+
const target_data = {};
|
|
551
|
+
|
|
552
|
+
const feature_data = model.properties.features;
|
|
553
|
+
target_data["TARGET"] = model.properties.features.TARGET;
|
|
554
|
+
|
|
555
|
+
delete feature_data["TARGET"];
|
|
556
|
+
|
|
557
|
+
|
|
558
|
+
|
|
559
|
+
const training_observation_count = round_float(target_data["TARGET"].length * (1-model.properties.tt_split));
|
|
560
|
+
const testing_observation_count = target_data["TARGET"].length - training_observation_count;
|
|
561
|
+
|
|
562
|
+
//# of features being used NOT including target
|
|
563
|
+
const feature_count = Object.keys(feature_data).length;
|
|
564
|
+
|
|
565
|
+
const feature_training_data = {};
|
|
566
|
+
const feature_testing_data = {};
|
|
567
|
+
|
|
568
|
+
//guardrail to confirm all features including target have the same number of observations
|
|
569
|
+
for(const feat_name of Object.keys(feature_data)){
|
|
570
|
+
if(target_data["TARGET"].length != feature_data[feat_name].length){
|
|
571
|
+
throw new Error("Features and targets are not all the same size!(or wrong input format, etc.)");
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
for(const feat_name of Object.keys(feature_data)){
|
|
576
|
+
//Split feature data into training and testing sets
|
|
577
|
+
feature_training_data[feat_name] = feature_data[feat_name].slice(0, training_observation_count);
|
|
578
|
+
feature_testing_data[feat_name] = feature_data[feat_name].slice(training_observation_count, target_data["TARGET"].length);
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
const target_training_data = {};
|
|
582
|
+
target_training_data["TARGET"] = target_data["TARGET"].slice(0, training_observation_count);
|
|
583
|
+
const target_testing_data = {};
|
|
584
|
+
target_testing_data["TARGET"] = target_data["TARGET"].slice(training_observation_count, target_data["TARGET"].length);
|
|
585
|
+
|
|
586
|
+
|
|
587
|
+
model.properties.stored_testing_data.t_feature_data = feature_testing_data;
|
|
588
|
+
model.properties.stored_testing_data.t_target_data = target_testing_data;
|
|
589
|
+
|
|
590
|
+
|
|
591
|
+
|
|
592
|
+
//build simple structure without columns or weights, but including rows via the neuron count(size) of each layer
|
|
593
|
+
build_matrices(model);
|
|
594
|
+
fill_matrices(model, feature_count);
|
|
595
|
+
|
|
596
|
+
for(let e = 0; e<model.properties.epochs; e++){
|
|
597
|
+
|
|
598
|
+
let total_epoch_loss = 0;
|
|
599
|
+
|
|
600
|
+
let iterations_since_interval = 0;
|
|
601
|
+
|
|
602
|
+
const epoch_predictions = [];
|
|
603
|
+
const epoch_targets = [];
|
|
604
|
+
|
|
605
|
+
for(let i = 0; i<training_observation_count;i++){
|
|
606
|
+
|
|
607
|
+
iteration(model, i, feature_training_data, target_training_data);
|
|
608
|
+
|
|
609
|
+
//CHANGE if moving away from single output neuron regression model
|
|
610
|
+
epoch_predictions.push(model.layers["output_layer"]["output"][0]);
|
|
611
|
+
epoch_targets.push(target_training_data["TARGET"][i]);
|
|
612
|
+
|
|
613
|
+
iterations_since_interval += 1;
|
|
614
|
+
|
|
615
|
+
if(iterations_since_interval==model.properties.batch_size){
|
|
616
|
+
iterations_since_interval = 0;
|
|
617
|
+
average_deltas(model);
|
|
618
|
+
update_weights_and_biases(model);
|
|
619
|
+
reset_deltas(model);
|
|
620
|
+
}
|
|
621
|
+
total_epoch_loss += calculate_mse(epoch_predictions, epoch_targets);
|
|
622
|
+
|
|
623
|
+
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
if(iterations_since_interval%model.properties.metric_report_interval==0){
|
|
627
|
+
rmse_list.push(calculate_rmse(model, epoch_predictions, epoch_targets));
|
|
628
|
+
mae_list.push(calculate_mae(model, epoch_predictions, epoch_targets));
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
loss_list.push(total_epoch_loss/training_observation_count);
|
|
632
|
+
|
|
633
|
+
if(iterations_since_interval>0){
|
|
634
|
+
average_deltas(model, iterations_since_interval);
|
|
635
|
+
update_weights_and_biases(model);
|
|
636
|
+
reset_deltas(model);
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
}
|
|
640
|
+
model.properties.performance_metrics.training.loss_list = loss_list;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
function test(model){
|
|
644
|
+
|
|
645
|
+
const feature_testing_data = model.properties.stored_testing_data.t_feature_data;
|
|
646
|
+
const target_testing_data = model.properties.stored_testing_data.t_target_data;
|
|
647
|
+
|
|
648
|
+
const testing_observation_count = target_testing_data["TARGET"].length;
|
|
649
|
+
|
|
650
|
+
let total_test_loss = 0;
|
|
651
|
+
const test_predictions = [];
|
|
652
|
+
const test_targets = [];
|
|
653
|
+
|
|
654
|
+
for(let i = 0; i<testing_observation_count;i++){
|
|
655
|
+
|
|
656
|
+
|
|
657
|
+
iteration(model, i, feature_testing_data, target_testing_data, false);
|
|
658
|
+
|
|
659
|
+
//CHANGE if moving away from single output neuron regression model
|
|
660
|
+
test_predictions.push(model.layers["output_layer"]["output"][0])
|
|
661
|
+
test_targets.push(target_testing_data["TARGET"][i])
|
|
662
|
+
}
|
|
663
|
+
total_test_loss = calculate_mse(test_predictions, test_targets);
|
|
664
|
+
|
|
665
|
+
const metrics = model.properties.performance_metrics.testing;
|
|
666
|
+
|
|
667
|
+
metrics.loss = total_test_loss;
|
|
668
|
+
metrics.rmse = calculate_rmse(model, test_predictions, test_targets);
|
|
669
|
+
metrics.mae = calculate_mae(model, test_predictions, test_targets);
|
|
670
|
+
metrics.r2 = calculate_rsquared(model, test_predictions, test_targets);
|
|
671
|
+
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
export function neuralnet({
|
|
675
|
+
//default parameters for neurat net initialization
|
|
676
|
+
hidden_layer_info = {count:5, init_size:5, style:"flat", hourglass_operation:"/"},
|
|
677
|
+
activation_type = "relu",
|
|
678
|
+
learn_rate = 0.01,
|
|
679
|
+
output_layer_info = {init_size:1, activation:"none"},
|
|
680
|
+
epochs = 100,
|
|
681
|
+
batch_size = 10,
|
|
682
|
+
tt_split = 0.25,
|
|
683
|
+
random_seed = 58373,
|
|
684
|
+
features = {},
|
|
685
|
+
feature_data = {maxMins:{}},
|
|
686
|
+
stored_testing_data = {t_feature_data:null, t_target_data:null},
|
|
687
|
+
performance_metrics = {training:{loss_list:null, rmse_list:[], mae_list:[]},testing:{loss:null, rmse:null, r2:null, mae:null}},
|
|
688
|
+
normalization_statistics = {},
|
|
689
|
+
metric_report_interval = 10}={}) {
|
|
690
|
+
|
|
691
|
+
hidden_layer_info = {
|
|
692
|
+
count:5,
|
|
693
|
+
init_size:5,
|
|
694
|
+
style:"flat",
|
|
695
|
+
hourglass_operation:"/",
|
|
696
|
+
...hidden_layer_info
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
console.log(`now buidling model[SEED: ${random_seed}]
|
|
700
|
+
${epochs} epochs
|
|
701
|
+
${tt_split} train/test split
|
|
702
|
+
${activation_type} activation
|
|
703
|
+
${batch_size} batch size
|
|
704
|
+
${hidden_layer_info.style} architecture, hg_oper?: ${hidden_layer_info.hourglass_operation}
|
|
705
|
+
${learn_rate} learning rate`);
|
|
706
|
+
|
|
707
|
+
const model_properties = {
|
|
708
|
+
hidden_layer_info:hidden_layer_info,
|
|
709
|
+
activation_type:activation_type,
|
|
710
|
+
learn_rate:learn_rate,
|
|
711
|
+
output_layer_info:output_layer_info,
|
|
712
|
+
epochs:epochs,
|
|
713
|
+
batch_size:batch_size,
|
|
714
|
+
tt_split:tt_split,
|
|
715
|
+
random_seed:random_seed,
|
|
716
|
+
current_seed:random_seed,
|
|
717
|
+
features:features,
|
|
718
|
+
feature_data:feature_data,
|
|
719
|
+
metric_report_interval:metric_report_interval,
|
|
720
|
+
|
|
721
|
+
stored_testing_data:stored_testing_data,
|
|
722
|
+
performance_metrics:performance_metrics,
|
|
723
|
+
normalization_statistics:normalization_statistics};
|
|
724
|
+
|
|
725
|
+
const modelOut = {
|
|
726
|
+
layers:{},
|
|
727
|
+
properties:model_properties,
|
|
728
|
+
train(feature_data, target_data){
|
|
729
|
+
train(this, feature_data, target_data)
|
|
730
|
+
},
|
|
731
|
+
test(){
|
|
732
|
+
test(this)
|
|
733
|
+
},
|
|
734
|
+
set_features(){
|
|
735
|
+
set_features(this)
|
|
736
|
+
},
|
|
737
|
+
set_features_csv(){
|
|
738
|
+
set_features_csv(this)
|
|
739
|
+
}};
|
|
740
|
+
|
|
741
|
+
return modelOut;
|
|
742
|
+
}
|
package/README.md
DELETED