Brain.js: Running network from JSON seems inaccurate with hundreds of JSON objects?

Created on 12 Dec 2019  路  17Comments  路  Source: BrainJS/brain.js

I'm utilising the fromJSON option of Brain JS to output my network to a json file and load it back in, however, I'm confused as to how I'm meant to train this...

Am I meant to run one set of data through the network in the .run([]) and then use that one json file for other data running through the data, or, am I meant to train it and keep appending data to the JSON file, either way, the following seems to return mostly the same result all of the time, I've stripped most of the training data, but the array of objects would be thousands of objects:

// require packages
const brain = require('brain.js')
const fs = require('fs');
const path = require('path');
const args = require('minimist')(process.argv.slice(2))

// check if we have a trained network already
const checkIfNetworkExists = fs.existsSync('./js/generate/data/trained-network.json')

// Loan & Term: init Training Pattern
const myNetwork = new brain.NeuralNetwork({hiddenLayers: [7, 7]})
if (!checkIfNetworkExists) {
  const trainingData = [
    {input:[451,2200,42,0,650,200,150],output:[0,1,1,0,0,1,1]},{input:[851,1400,52,150,500,50,850],output:[0,1,1,0,0,0,0]}
  ]

  // train the network
  if (args['enableAsyncTraining'] && args['enableAsyncTraining'] === 'true') {
    myNetwork.trainAsync(trainingData, trainingOptions)
  } else {
    myNetwork.train(trainingData, trainingOptions)
  }

  // write trained network to file
  fs.writeFileSync('./js/generate/data/trained-network.json', JSON.stringify(myNetwork.toJSON(), null, '  '));
} else {

  // load network if it exists
  myNetwork.fromJSON(JSON.parse(fs.readFileSync('./js/generate/data/trained-network.json', 'utf8')));
}

// run the network
myNetwork.run([100, 2000, 40, 6, 650, 200, 100])

Most helpful comment

Normalize them to between 0 and 1 for the best results (NOTE: this isn't a brain.js thing, it is just machine learning in general). Normalize the _most negative number (or least number) to 0_, and then the _highest number to 1_, everything else should be within that 0 to 1 range for effective normalization.

All 17 comments

Each time you train network with new data, network is updated. This means you just need to update/replace your json with newly generated one.

@mubaidr My JSON file is 292 lines long, do I need to add another parent object to the end with each training? How would I update the network whilst retaining the performance?

{
  "sizes": [
    7,
    7,
    7,
    7
  ],
  "layers": [
    {
      "0": {},
      "1": {},
      "2": {},
      "3": {},
      "4": {},
      "5": {},
      "6": {}
    },
    {
      "0": {
        "bias": 0.09439083188772202,
        "weights": {
          "0": -0.7670116424560547,
          "1": -0.6642861366271973,
          "2": -0.19578738510608673,
          "3": -0.21747833490371704,
          "4": -0.14496855437755585,
          "5": -0.11916668713092804,
          "6": -0.2401152402162552
        }
      },
      "1": {
        "bias": -0.15841266512870789,
        "weights": {
          "0": -0.5381118655204773,
          "1": -0.5877346992492676,
          "2": -0.04498208686709404,
          "3": 0.09252215921878815,
          "4": -0.4555858373641968,
          "5": 0.0008982665603980422,
          "6": 0.1640717089176178
        }
      },
      "2": {
        "bias": -0.04460155963897705,
        "weights": {
          "0": 1.3179415464401245,
          "1": 0.6098086833953857,
          "2": 0.1907377690076828,
          "3": 0.2269635796546936,
          "4": -0.11790905147790909,
          "5": 0.30080828070640564,
          "6": 0.1771688163280487
        }
      },
      "3": {
        "bias": -0.006974233780056238,
        "weights": {
          "0": 0.19547560811042786,
          "1": 0.14253273606300354,
          "2": 0.12966442108154297,
          "3": 0.15862159430980682,
          "4": 0.06666643172502518,
          "5": 0.06027138978242874,
          "6": 0.09067974984645844
        }
      },
      "4": {
        "bias": 0.04890609532594681,
        "weights": {
          "0": -0.26986971497535706,
          "1": -0.2122180163860321,
          "2": -0.19262979924678802,
          "3": 0.2195170819759369,
          "4": -0.09843645244836807,
          "5": 0.0943814292550087,
          "6": -0.11620646715164185
        }
      },
      "5": {
        "bias": 0.007766162045300007,
        "weights": {
          "0": 0.19123606383800507,
          "1": 0.17956386506557465,
          "2": -0.16825221478939056,
          "3": -0.2137739360332489,
          "4": 0.0667356327176094,
          "5": 0.20001071691513062,
          "6": 0.06469400972127914
        }
      },
      "6": {
        "bias": -0.14158064126968384,
        "weights": {
          "0": 0.269572913646698,
          "1": 2.356199026107788,
          "2": -0.04505547136068344,
          "3": 0.27069488167762756,
          "4": 1.0695784091949463,
          "5": 0.6510146260261536,
          "6": 1.067539930343628
        }
      }
    },
    {
      "0": {
        "bias": -0.7611159086227417,
        "weights": {
          "0": -0.0686710998415947,
          "1": -0.24292978644371033,
          "2": -0.8989506959915161,
          "3": -1.0361626148223877,
          "4": -3.3347342014312744,
          "5": -0.9127030372619629,
          "6": -0.7809993028640747
        }
      },
      "1": {
        "bias": -0.6161317229270935,
        "weights": {
          "0": 0.01872648298740387,
          "1": -0.44291770458221436,
          "2": -0.8336756229400635,
          "3": -0.8752111196517944,
          "4": -3.327821969985962,
          "5": -0.8654630184173584,
          "6": -1.1974551677703857
        }
      },
      "2": {
        "bias": -0.5951817631721497,
        "weights": {
          "0": -0.00733727402985096,
          "1": -0.6064631342887878,
          "2": -0.9993404746055603,
          "3": -0.6716686487197876,
          "4": -3.4572129249572754,
          "5": -0.9579042196273804,
          "6": -1.1782407760620117
        }
      },
      "3": {
        "bias": -0.5544703602790833,
        "weights": {
          "0": 0.03121502883732319,
          "1": -0.4008966386318207,
          "2": -1.0554537773132324,
          "3": -0.9632909297943115,
          "4": -3.0501139163970947,
          "5": -0.8849554061889648,
          "6": -0.8785363435745239
        }
      },
      "4": {
        "bias": -0.8543322682380676,
        "weights": {
          "0": 0.03028627671301365,
          "1": -0.08175212144851685,
          "2": -1.1896885633468628,
          "3": -0.7588884830474854,
          "4": -3.4440109729766846,
          "5": -0.9725902080535889,
          "6": -0.6348049640655518
        }
      },
      "5": {
        "bias": -0.895724356174469,
        "weights": {
          "0": 0.19649137556552887,
          "1": -0.44556358456611633,
          "2": -1.098259449005127,
          "3": -0.960195004940033,
          "4": -3.355997085571289,
          "5": -0.73044353723526,
          "6": -0.7049161791801453
        }
      },
      "6": {
        "bias": -0.7397876381874084,
        "weights": {
          "0": -0.113799549639225,
          "1": -0.2322213351726532,
          "2": -0.8513315916061401,
          "3": -0.8888430595397949,
          "4": -2.009086847305298,
          "5": -0.775073766708374,
          "6": -1.0452015399932861
        }
      }
    },
    {
      "0": {
        "bias": 0.4242694079875946,
        "weights": {
          "0": 1.6234968900680542,
          "1": 1.6203052997589111,
          "2": 1.6591789722442627,
          "3": 1.513173222541809,
          "4": 1.6682435274124146,
          "5": 1.6272711753845215,
          "6": 1.2238273620605469
        }
      },
      "1": {
        "bias": 6.9684882164001465,
        "weights": {
          "0": 1.5869325399398804,
          "1": 1.2068145275115967,
          "2": 1.763297438621521,
          "3": 1.39811110496521,
          "4": 1.450259804725647,
          "5": 1.299032211303711,
          "6": 0.8678433299064636
        }
      },
      "2": {
        "bias": 6.967097282409668,
        "weights": {
          "0": 1.4249857664108276,
          "1": 1.0491899251937866,
          "2": 1.8390330076217651,
          "3": 1.521957278251648,
          "4": 1.408325433731079,
          "5": 1.5694714784622192,
          "6": 1.032562017440796
        }
      },
      "3": {
        "bias": -0.10140286386013031,
        "weights": {
          "0": 0.7015955448150635,
          "1": 0.6939533948898315,
          "2": 0.7209612727165222,
          "3": 0.6339335441589355,
          "4": 0.7265103459358215,
          "5": 0.7037737965583801,
          "6": 0.4587821364402771
        }
      },
      "4": {
        "bias": -1.1545584201812744,
        "weights": {
          "0": -0.706428587436676,
          "1": -0.6880828142166138,
          "2": -0.7451974153518677,
          "3": -0.6021878719329834,
          "4": -0.7454286813735962,
          "5": -0.7167820334434509,
          "6": -0.07689554244279861
        }
      },
      "5": {
        "bias": -1.7900606393814087,
        "weights": {
          "0": 0.41949495673179626,
          "1": 0.4168839454650879,
          "2": 0.41511523723602295,
          "3": 0.3618548810482025,
          "4": 0.43424156308174133,
          "5": 0.40631112456321716,
          "6": 0.12862923741340637
        }
      },
      "6": {
        "bias": -1.7644935846328735,
        "weights": {
          "0": -2.5884010791778564,
          "1": -2.545736789703369,
          "2": -2.7074363231658936,
          "3": -2.383005142211914,
          "4": -2.6837635040283203,
          "5": -2.63854718208313,
          "6": -1.3939564228057861
        }
      }
    }
  ],
  "outputLookup": false,
  "inputLookup": false,
  "activation": "sigmoid",
  "trainOpts": {
    "iterations": 20000,
    "errorThresh": 0.005,
    "log": true,
    "logPeriod": 10,
    "learningRate": 0.3,
    "momentum": 0.1,
    "callbackPeriod": 10,
    "beta1": 0.9,
    "beta2": 0.999,
    "epsilon": 1e-8
  }
}

Just over-right JSON from network.TOJSON after each train:

// train network with new data
// write trained network to file
  fs.writeFileSync('./js/generate/data/trained-network.json', JSON.stringify(myNetwork.toJSON(), null, '  '));

@mubaidr So I just need to update the trainingData Array with new data and rerun?

Yes.

  • train network
  • save json
  • load network
  • train network with new data
  • replace old json file from network

while were here NN work better with numbers between 0 and 1 is dividing your training data by your highest input is a good way to get this.

@vorticalbox Are we saying that this: input:[451,2200,42,0,650,200,150] should be: input:[0.451,0.2200,0.42,0,0.650,0.200,0.150] ?

@vorticalbox Are we saying that this: input:[451,2200,42,0,650,200,150] should be: input:[0.451,0.2200,0.42,0,0.650,0.200,0.150] ?

Not that it should be but it's generally better to get value between 0 and 1.

in the code you posted in the issue I would divide by 3000 and get something like this

const trainingData = [
    {
        input:[ 0.15033333333333335,0.7333333333333333,0.014,0,0.21666666666666667,0.06666666666666667,0.05 ],
        output:[0,1,1,0,0,1,1]
    },
    {
        input:  [0.2836666666666667,0.4666666666666667,0.017333333333333333,0.05,0.16666666666666666,0.016666666666666666,0.2833333333333333 ],
        output:[0,1,1,0,0,0,0]
    }
  ]

@vorticalbox Sweet! How should I handle negative numbers that happen as a result of a division by X? Or can Brain JS handle this?

@vorticalbox When I run numbers through, would they need to be divided as well? myNetwork.run([100, 2000, 40, 6, 650, 200, 100])

Normalize them to between 0 and 1 for the best results (NOTE: this isn't a brain.js thing, it is just machine learning in general). Normalize the _most negative number (or least number) to 0_, and then the _highest number to 1_, everything else should be within that 0 to 1 range for effective normalization.

@robertleeplummerjr Thanks! Using this idea, would I potentially receive better results? E.g:

I have a random number generator that builds an array of 7 whole numbers, if a number is between 200 & 500, it scores 1 as the output, and a number less than 200 or greater than 500 scores 0. I need Brain to detect this and score a higher output in this bracket, and outside of it to score lower.

Normalizing the numbers and converting them would score better? What would you advise? Currently I'm seeing error thresholds of between 0.10 and 0.15, is this acceptable

@mubaidr So this is what I'm currently doing:

  1. Create a training data set (an Array of 35,000 objects containing various number combinations) which have been normalized between 0 and 1.
  2. Running normalized numbers in myNetwork.run([0.5, 0.54, 0, 0.69, 0.25, 0.9, 1]), it takes a while to run through 20,000 iterations
  3. A file is then generated using fs.writeFileSync using toJSON()
  4. I then load that file into the JS file, just after the training data Array, but before where the network is trained through: myNetwork.train(trainingData, trainingOptions)
  5. I then pass a different set of numbers into myNetwork.run([0.4, 0.15, 1, 0.3, 0.2, 0.95, 1]),
  6. a new file is created in replace of the initial one.

Is this correct? I've tried doing this loop 4 times, and the error threshold remains around 0.050 each time, and it takes the same amount of time?

Code Example

function normalize(val, max, min) {
  return (val - min) / (max - min);
}

function round(value, precision) {
  var multiplier = Math.pow(10, precision || 0)
  return Math.round(value * multiplier) / multiplier
}

// check if we have a trained network already
// NOTE: I would do an if/else using this variable to prevent taking 30 mins each time to get a result, if there's a network, use the file, but for development I'm not using this.
const checkIfNetworkExists = fs.existsSync('./js/generate/data/training/trained-network.json')

// init
const myNetwork = new brain.NeuralNetwork({hiddenLayers: [7, 7]})

// training data (contains 35,000 objects, stripped here for demo)
const trainingData = [
{input:[0.58,0.35,0.8,0.45,0.97,0.7,0.92],output:[0,0,1,1,1,0,0]},{input:[0.97,0.51,0.31,0.7,0.73,0.3,0.64],output:[0,0,0,1,0,0,0]}
]

// load network if it exists
myNetwork.fromJSON(JSON.parse(fs.readFileSync('./js/generate/data/training/trained-network-new.json', 'utf8')));

// train the network
myNetwork.train(trainingData, trainingOptions)

// write trained network to file
fs.writeFileSync('./js/generate/data/training/trained-network.json',JSON.stringify(myNetwork.toJSON(), '  '));

// run the network
var checkRisk = myNetwork.run([
  round(normalize(riskFactors.objItem, 1, 3000), 2),
  round(normalize(riskFactors.obj.objItem, 100, 3500), 2),
  round(normalize(riskFactors.obj.objItem2, 1, 60), 2),
  round(normalize(riskFactors.objIte3, 0, 1000), 2),
  round(normalize(riskFactors.objItem4, 0, 1500), 2),
  round(normalize(riskFactors.objItem5, 0, 1000), 2),
  round(normalize(riskFactors.objItem6, 0, 1250), 2)
]);

/cc @mubaidr

@sts-ryan-holton Can you please create a playground here, with your sample code: https://runkit.com/home

This will allow us to understand the code and we will be able to update code.

@mubaidr @robertleeplummerjr I've created a playground here: https://runkit.com/ryan-holton/brain-js-network-from-json

Granted, it's missing a few things that would otherwise be available on a physical environment such as filesystem read/write, you'll see some "WOULD BE" comments as to where this would be.

This looks good. My apologies looks like this is correct way to train with new data:

  • train network
  • save json
  • create new network
  • train network with (old + new data)
  • replace old json file from network
Was this page helpful?
0 / 5 - 0 ratings