exports.start = function (res) { // res being the Express response object, being the server package I used const network = new NN.NeuralNetwork(); let dataTypes = [] let listOfTrainingData = [] let listOfTestData = [] let trainingDataRaw = [] let testDataRaw = [] const trainingOptions = { /* config options */ } var parser = csv({ delimiter: ',' }, function (err, data) { // in general, I find processing of arbitrary data files hard to do with pure functions - so I took some short cuts... if (err || data.length === 0) { return null } else { dataTypes = data[0].slice(0) // not very pure parseTrainingData(data.slice(1)) // this should normally return the completed data - see in-function comments below } }) try { // not really pure either, but I don't know a better way of handling contact with the file system. At least the event-driven code is all in one place const stream = fs.createReadStream(path.join(__dirname, 'data.csv')) stream.pipe(parser) stream.on('close', () => { network.train(definedTrainingDataset(trainingDataRaw), trainingOptions) const nnConfig = network.toJSON() const significantInputs = identifySignificantInputs(nnConfig) fs.writeFileSync('savedNN.json', JSON.stringify(nnConfig, null, 2)) res.send({ msg: '', result: generatedTestResults(testData), keyInputData: significantInputs, testedData: listOfTestData }) }) stream.on('error', (error) => { throw(error) }) } catch (err) { res.send({ msg: err }) } function parseTrainingData (data) { if (data[0][0] === 'TEST') { return parseTestData(data.slice(1)) } else if (data.length === 0) { return // normally a recursive function would return the completed data, but see next comment for why not in this case } else { listOfTrainingData.push(data[0][0]) // not very pure but I'm OK with it for now. Hard to return two new arrays and then two more test data arrays trainingDataRaw.push(data[0].slice(1)) return parseTrainingData(data.slice(1)) } } function parseTestData (data) { if (data.length === 0) { return // see above regarding return values } else { listOfTestData.push(data[0][0]) // not very pure either! testDataRaw.push(data[0].slice(1)) return parseTestData(data.slice(1)) } } function identifySignificantInputs (nnConfigData) { const listOfHiddenNodes = Object.values(nnConfigData.layers[1]) const summedWeights = listOfHiddenNodes.map((node) => { return Object.values(node.weights).reduce(function sumWeights (sum, weight) { return sum + weight }) }) const numWeights = summedWeights.length > 10 ? 10 : summedWeights.length const topTen = summedWeights.sort().slice(summedWeights.length - numWeights) return topTen.map((summedWeight) => { // what is actually wanted are the indexes of the highest 10 summedWeights values return dataTypes[summedWeights.indexOf(summedWeight)] // then match these to the original data (column) names }) } function definedTrainingDataset(rawData) { return rawData.map((data) => { const input = data.slice(0, data.length - 1) const output = data.slice(data.length - 1) // the CSV defines the desired neural network output as the last item in a row return { input, output } // Brain.js specifies this type of object for the training set }) } function generatedTestResults(dataToTest) { return dataToTest.map((dataPoint) => { const input = dataPoint.slice(0, dataPoint.length - 1) const output = dataPoint.slice(dataPoint.length - 1) // output is last item in a row return { actual: network.run(input), desired: output } // not pure, as running the test function here - probably should segregate this better }) } }