diff --git a/lib/nn.js b/lib/nn.js index 0cb4527..0778869 100644 --- a/lib/nn.js +++ b/lib/nn.js @@ -1,178 +1,166 @@ -// Other techniques for learning - -class ActivationFunction { - constructor(func, dfunc) { - this.func = func; - this.dfunc = dfunc; - } -} - -let sigmoid = new ActivationFunction( - x => 1 / (1 + Math.exp(-x)), - y => y * (1 - y) -); - -let tanh = new ActivationFunction( - x => Math.tanh(x), - y => 1 - (y * y) -); - +let Matrix = require("./matrix") class NeuralNetwork { - /* - * if first argument is a NeuralNetwork the constructor clones it - * USAGE: cloned_nn = new NeuralNetwork(to_clone_nn); - */ - constructor(in_nodes, hid_nodes, out_nodes) { - if (in_nodes instanceof NeuralNetwork) { - let a = in_nodes; - this.input_nodes = a.input_nodes; - this.hidden_nodes = a.hidden_nodes; - this.output_nodes = a.output_nodes; - - this.weights_ih = a.weights_ih.copy(); - this.weights_ho = a.weights_ho.copy(); - - this.bias_h = a.bias_h.copy(); - this.bias_o = a.bias_o.copy(); - } else { - this.input_nodes = in_nodes; - this.hidden_nodes = hid_nodes; - this.output_nodes = out_nodes; - - this.weights_ih = new Matrix(this.hidden_nodes, this.input_nodes); - this.weights_ho = new Matrix(this.output_nodes, this.hidden_nodes); - this.weights_ih.randomize(); - this.weights_ho.randomize(); - - this.bias_h = new Matrix(this.hidden_nodes, 1); - this.bias_o = new Matrix(this.output_nodes, 1); - this.bias_h.randomize(); - this.bias_o.randomize(); + constructor(arr, lr) { + this.nodes = arr + this.lr = lr || 0.01 + this.activation = NeuralNetwork.sigmoid + this.dactivation = NeuralNetwork.dsigmoid + this.weights = [] + this.biases = [] + for (let i = 0; i < this.nodes.length - 1; i++) { + this.weights.push(new Matrix(this.nodes[i + 1], this.nodes[i]).randomize()) + } + for (let i = 1; i < this.nodes.length; i++) { + this.biases.push(new Matrix(this.nodes[i], 1).randomize()) } - - // TODO: copy these as well - this.setLearningRate(); - this.setActivationFunction(); - - } - - predict(input_array) { - - // Generating the Hidden Outputs - let inputs = Matrix.fromArray(input_array); - let hidden = Matrix.multiply(this.weights_ih, inputs); - hidden.add(this.bias_h); - // activation function! - hidden.map(this.activation_function.func); - - // Generating the output's output! - let output = Matrix.multiply(this.weights_ho, hidden); - output.add(this.bias_o); - output.map(this.activation_function.func); - - // Sending back to the caller! - return output.toArray(); + static tanh(x) { + var y = Math.tanh(x); + return y; } - - setLearningRate(learning_rate = 0.1) { - this.learning_rate = learning_rate; + static dtanh(x) { + var y = 1 / (pow(Math.cosh(x), 2)); + return y; } - - setActivationFunction(func = sigmoid) { - this.activation_function = func; + static sigmoid(x) { + return 1 / (1 + Math.exp(-x)); } - - train(input_array, target_array) { - // Generating the Hidden Outputs - let inputs = Matrix.fromArray(input_array); - let hidden = Matrix.multiply(this.weights_ih, inputs); - hidden.add(this.bias_h); - // activation function! - hidden.map(this.activation_function.func); - - // Generating the output's output! - let outputs = Matrix.multiply(this.weights_ho, hidden); - outputs.add(this.bias_o); - outputs.map(this.activation_function.func); - - // Convert array to matrix object - let targets = Matrix.fromArray(target_array); - - // Calculate the error - // ERROR = TARGETS - OUTPUTS - let output_errors = Matrix.subtract(targets, outputs); - - // let gradient = outputs * (1 - outputs); - // Calculate gradient - let gradients = Matrix.map(outputs, this.activation_function.dfunc); - gradients.multiply(output_errors); - gradients.multiply(this.learning_rate); - - - // Calculate deltas - let hidden_T = Matrix.transpose(hidden); - let weight_ho_deltas = Matrix.multiply(gradients, hidden_T); - - // Adjust the weights by deltas - this.weights_ho.add(weight_ho_deltas); - // Adjust the bias by its deltas (which is just the gradients) - this.bias_o.add(gradients); - - // Calculate the hidden layer errors - let who_t = Matrix.transpose(this.weights_ho); - let hidden_errors = Matrix.multiply(who_t, output_errors); - - // Calculate hidden gradient - let hidden_gradient = Matrix.map(hidden, this.activation_function.dfunc); - hidden_gradient.multiply(hidden_errors); - hidden_gradient.multiply(this.learning_rate); - - // Calcuate input->hidden deltas - let inputs_T = Matrix.transpose(inputs); - let weight_ih_deltas = Matrix.multiply(hidden_gradient, inputs_T); - - this.weights_ih.add(weight_ih_deltas); - // Adjust the bias by its deltas (which is just the gradients) - this.bias_h.add(hidden_gradient); - - // outputs.print(); - // targets.print(); - // error.print(); + static dsigmoid(y) { + // return sigmoid(x) * (1s - sigmoid(x)); + return y * (1 - y); } - - serialize() { - return JSON.stringify(this); + query(input_arr) { + let input = Matrix.fromArray(input_arr) + for (let i = 0; i < this.weights.length; i++) { + input = Matrix.multiply(this.weights[i], input) + input.add(this.biases[i]) + input.map(this.activation) + } + return input.toArray() } - - static deserialize(data) { - if (typeof data == 'string') { - data = JSON.parse(data); + learn(input_arr, target_arr) { + let target = Matrix.fromArray(target_arr) + let output = Matrix.fromArray(this.query(input_arr)) + let O = [] + let input = Matrix.fromArray(input_arr) + for (let i = 0; i < this.weights.length; i++) { + O.push(input) + input = Matrix.multiply(this.weights[i], input) + input.add(this.biases[i]) + input.map(this.activation) + } + let error = Matrix.subtract(target, output) + let gradient = Matrix.map(output, this.dactivation) + gradient.multiply(error) + gradient.multiply(this.lr) + for (let i = O.length - 1; i >= 0; i--) { + let dw = Matrix.multiply(gradient, Matrix.transpose(O[i])) + this.weights[i].add(dw) + this.biases[i].add(gradient) + error = Matrix.multiply(Matrix.transpose(this.weights[i]), error) + gradient = Matrix.map(O[i], this.dactivation) + gradient.multiply(error) + gradient.multiply(this.lr) } - let nn = new NeuralNetwork(data.input_nodes, data.hidden_nodes, data.output_nodes); - nn.weights_ih = Matrix.deserialize(data.weights_ih); - nn.weights_ho = Matrix.deserialize(data.weights_ho); - nn.bias_h = Matrix.deserialize(data.bias_h); - nn.bias_o = Matrix.deserialize(data.bias_o); - nn.learning_rate = data.learning_rate; - return nn; } - - - // Adding function for neuro-evolution + getModel() { + let model = this + let k = { + nodes: model.nodes, + lr: model.lr, + activation: model.activation, + dactivation: model.dactivation, + weights: [], + biases: [] + } + for (let weight of model.weights) { + let s = { + rows: weight.rows, + cols: weight.cols, + data: [] + } + for (let d of weight.data) { + let a = [] + for (let l of d) { + a.push(l) + } + s.data.push(a) + } + k.weights.push(s) + } + for (let bias of model.biases) { + let s = { + rows: bias.rows, + cols: bias.cols, + data: bias.data + } + k.biases.push(s) + } + return k + } + static formModel(model) { + let nn = new NeuralNetwork(model.nodes, model.lr) + nn.nodes = model.nodes + nn.lr = model.lr + nn.activation = model.activation + nn.dactivation = model.dactivation + for (let i = 0; i < nn.weights.length; i++) { + nn.weights[i].rows = model.weights[i].rows + nn.weights[i].cols = model.weights[i].cols + for (let j = 0; j < model.weights[i].rows; j++) { + for (let k = 0; k < model.weights[i].cols; k++) { + nn.weights[i].data[j][k] = model.weights[i].data[j][k] + } + } + nn.weights[i].rows = model.weights[i].rows + } + return nn + } copy() { - return new NeuralNetwork(this); + let model = this.getModel() + return NeuralNetwork.formModel(model) } - - // Accept an arbitrary function for mutation mutate(func) { - this.weights_ih.map(func); - this.weights_ho.map(func); - this.bias_h.map(func); - this.bias_o.map(func); + for (let weight of this.weights) { + weight.map(func) + } + for (let bias of this.biases) { + bias.map(func) + } + } + merge(net, ratio = 0.5){ + let r1 = 1- ratio + let r2 = ratio + for(let i=0; i