Once or twice a year, I revisit my from-scratch JavaScript implementations of a neural network. The system has enough complexity that there are dozens of ideas that can be explored.
My latest regression version makes many small changes from previous versions. The primary change was that I refactored the train() method from a single, very large method, to one that calls three helper functions — zeroOutGrads(), accumGrads(y), updateWeights(lrnRate). This change required me to store the hidden node and output node gradients as class member matrices and vectors rather than as objects local to the train() method.
For my demo program, I used one of my standard synthetic datasets. The goal is to predict a person’s income from sex, age, State, and political leaning. The 240-item tab-delimited raw data looks like:
F 24 michigan 29500.00 liberal M 39 oklahoma 51200.00 moderate F 63 nebraska 75800.00 conservative M 36 michigan 44500.00 moderate F 27 nebraska 28600.00 liberal . . .
I encoded sex as M = -1, F = 1, and State as Michigan = 100, Nebraska = 010, Oklahoma = 001, and political leaning as conservative = 100, moderate = 010, liberal = 001. I normalized the numeric data. I divided age values by 100, and divided the target income values by 100,000. The resulting encoded and normalized comma-delimited data looks like:
1, 0.24, 1, 0, 0, 0.2950, 0, 0, 1 -1, 0.39, 0, 0, 1, 0.5120, 0, 1, 0 1, 0.63, 0, 1, 0, 0.7580, 1, 0, 0 -1, 0.36, 1, 0, 0, 0.4450, 0, 1, 0 1, 0.27, 0, 1, 0, 0.2860, 0, 0, 1 . . .
I split the data into a 200-item set of training data and a 40-item set of test data.
My neural architecture was 8-25-3 with tanh() hidden node activation and identity a.k.a. linear a.k.a. none output node activation. For training I used a batch size of 10, a learning rate of 0.001, and 1,000 epochs. Because the target income values are all between 0.0 and 1.0, I probably should have used logistic sigmoid activation on the output nodes. In spite of decades of research on neural networks, there are still many concepts, including output node activation for regression networks, that just aren’t fully understood.
The resulting model scored 0.9100 accuracy on the training data (182 out of 200 correct) and 0.9500 accuracy on the test data (38 out of 40 correct) where a correct income prediction is one that is within 10% of the true target income. These results are similar to those achieved by a PyTorch neural network and a LightGBM tree-based system.
Good fun!

Different versions of a computer program can have a very different look and feel. Different book cover art has a big influence on my perception of a book. Here are covers for three versions of Tolkien’s “The Two Towers”, the second book in the Lord of the Rings trilogy. Left: By artist Jack Gaughan (1930-1985), for the 1965 accidentally unauthorized edition. Center: By artist Barbara Remington (1929-2020), for the first U.S. authorized paperback 1965 edition. Right: Art by Tolkien (1892-1973), adapted for the 1973 edition.
Demo code. Very long! Replace “lt” (less than), “gt”, “lte”, “gte”, “and” with Boolean operator symbols.
// people_income.js
// node.js ES6
// NN regression
// tanh, identity activation, MSE loss
let U = require("..\\Utils\\utilities_lib.js")
let FS = require("fs")
// ----------------------------------------------------------
class NeuralNet
{
constructor(numInput, numHidden, numOutput, seed)
{
this.rnd = new U.Erratic(seed); // pseudo-random
this.ni = numInput;
this.nh = numHidden;
this.no = numOutput;
this.iNodes = U.vecMake(this.ni, 0.0);
this.hNodes = U.vecMake(this.nh, 0.0);
this.oNodes = U.vecMake(this.no, 0.0);
this.ihWeights = U.matMake(this.ni, this.nh, 0.0);
this.hoWeights = U.matMake(this.nh, this.no, 0.0);
this.hBiases = U.vecMake(this.nh, 0.0);
this.oBiases = U.vecMake(this.no, 0.0); // [1]
this.ihGrads = U.matMake(this.ni, this.nh, 0.0);
this.hbGrads = U.vecMake(this.nh, 0.0);
this.hoGrads = U.matMake(this.nh, this.no, 0.0);
this.obGrads = U.vecMake(this.no, 0.0);
this.initWeights();
}
initWeights()
{
let lo = -0.10;
let hi = 0.10;
for (let i = 0; i "lt" this.ni; ++i) {
for (let j = 0; j "lt" this.nh; ++j) {
this.ihWeights[i][j] = (hi - lo) *
this.rnd.next() + lo;
}
}
for (let j = 0; j "lt" this.nh; ++j) {
for (let k = 0; k "lt" this.no; ++k) {
this.hoWeights[j][k] = (hi - lo) *
this.rnd.next() + lo;
}
}
}
computeOutput(X)
{
let hSums = U.vecMake(this.nh, 0.0);
let oSums = U.vecMake(this.no, 0.0);
this.iNodes = X;
for (let j = 0; j "lt" this.nh; ++j) {
for (let i = 0; i "lt" this.ni; ++i) {
hSums[j] += this.iNodes[i] * this.ihWeights[i][j];
}
hSums[j] += this.hBiases[j];
this.hNodes[j] = U.hyperTan(hSums[j]);
}
for (let k = 0; k "lt" this.no; ++k) {
for (let j = 0; j "lt" this.nh; ++j) {
oSums[k] += this.hNodes[j] * this.hoWeights[j][k];
}
oSums[k] += this.oBiases[k];
}
// this.oNodes = U.softmax(oSums); // multi-class
// this.oNodes = U.identity(oSums); // regression
for (let k = 0; k "lt" this.no; ++k) {
this.oNodes[k] = oSums[k];
// this.oNodes[k] = this.logSig(oSums[k]); // better?
}
let result = []; // or use U.vecMake
for (let k = 0; k "lt" this.no; ++k) {
result[k] = this.oNodes[k];
}
return result[0]; // a single scalar value
} // computeOutput()
setWeights(wts)
{
// order: ihWts, hBiases, hoWts, oBiases
let p = 0;
for (let i = 0; i "lt" this.ni; ++i) {
for (let j = 0; j "lt" this.nh; ++j) {
this.ihWeights[i][j] = wts[p++];
}
}
for (let j = 0; j "lt" this.nh; ++j) {
this.hBiases[j] = wts[p++];
}
for (let j = 0; j "lt" this.nh; ++j) {
for (let k = 0; k "lt" this.no; ++k) {
this.hoWeights[j][k] = wts[p++];
}
}
for (let k = 0; k "lt" this.no; ++k) {
this.oBiases[k] = wts[p++];
}
} // setWeights()
getWeights()
{
// order: ihWts, hBiases, hoWts, oBiases
let numWts = (this.ni * this.nh) + this.nh +
(this.nh * this.no) + this.no;
let result = U.vecMake(numWts, 0.0);
let p = 0;
for (let i = 0; i "lt" this.ni; ++i) {
for (let j = 0; j "lt" this.nh; ++j) {
result[p++] = this.ihWeights[i][j];
}
}
for (let j = 0; j "lt" this.nh; ++j) {
result[p++] = this.hBiases[j];
}
for (let j = 0; j "lt" this.nh; ++j) {
for (let k = 0; k "lt" this.no; ++k) {
result[p++] = this.hoWeights[j][k];
}
}
for (let k = 0; k "lt" this.no; ++k) {
result[p++] = this.oBiases[k];
}
return result;
} // getWeights()
shuffle(v)
{
// Fisher-Yates
let n = v.length;
for (let i = 0; i "lt" n; ++i) {
let r = this.rnd.nextInt(i, n);
let tmp = v[r];
v[r] = v[i];
v[i] = tmp;
}
}
// --------------------------------------------------------
// helpers for train(): zeroOutGrads(), accumGrads(y),
// updateWeights(lrnRate)
// --------------------------------------------------------
zeroOutGrads()
{
for (let i = 0; i "lt" this.ni; ++i)
for (let j = 0; j "lt" this.nh; ++j)
this.ihGrads[i][j] = 0.0;
for (let j = 0; j "lt" this.nh; ++j)
this.hbGrads[j] = 0.0;
for (let j = 0; j "lt" this.nh; ++j)
for (let k = 0; k "lt" this.no; ++k)
this.hoGrads[j][k] = 0.0;
for (let k = 0; k "lt" this.no; ++k)
this.obGrads[k] = 0.0;
}
accumGrads(y)
{
// y is target scalar
let oSignals = U.vecMake(this.no, 0.0);
let hSignals = U.vecMake(this.nh, 0.0);
// 1. compute output node scratch signals
for (let k = 0; k "lt" this.no; ++k) {
let derivative = 1.0; // identity activ.
// let derivative =
// this.oNodes[k] * (1 - this.oNodes[k]); // log-sig
oSignals[k] = derivative *
(this.oNodes[k] - y); // CEE
}
// 2. accum hidden-to-output gradients
for (let j = 0; j "lt" this.nh; ++j)
for (let k = 0; k "lt" this.no; ++k)
this.hoGrads[j][k] += oSignals[k] * this.hNodes[j];
// 3. accum output node bias gradients
for (let k = 0; k "lt" this.no; ++k)
this.obGrads[k] += oSignals[k] * 1.0; // 1.0 dummy
// 4. compute hidden node signals
for (let j = 0; j "lt" this.nh; ++j) {
let sum = 0.0;
for (let k = 0; k "lt" this.no; ++k)
sum += oSignals[k] * this.hoWeights[j][k];
let derivative =
(1 - this.hNodes[j]) *
(1 + this.hNodes[j]); // assumes tanh
hSignals[j] = derivative * sum;
}
// 5. accum input-to-hidden gradients
for (let i = 0; i "lt" this.ni; ++i)
for (let j = 0; j "lt" this.nh; ++j)
this.ihGrads[i][j] += hSignals[j] * this.iNodes[i];
// 6. accum hidden node bias gradients
for (let j = 0; j "lt" this.nh; ++j)
this.hbGrads[j] += hSignals[j] * 1.0; // 1.0 dummy
} // accumGrads
updateWeights(lrnRate)
{
// assumes all gradients computed
// 1. update input-to-hidden weights
for (let i = 0; i "lt" this.ni; ++i) {
for (let j = 0; j "lt" this.nh; ++j) {
let delta = -1.0 * lrnRate * this.ihGrads[i][j];
this.ihWeights[i][j] += delta;
}
}
// 2. update hidden node biases
for (let j = 0; j "lt" this.nh; ++j) {
let delta = -1.0 * lrnRate * this.hbGrads[j];
this.hBiases[j] += delta;
}
// 3. update hidden-to-output weights
for (let j = 0; j "lt" this.nh; ++j) {
for (let k = 0; k "lt" this.no; ++k) {
let delta = -1.0 * lrnRate * this.hoGrads[j][k];
this.hoWeights[j][k] += delta;
}
}
// 4. update output node biases
for (let k = 0; k "lt" this.no; ++k) {
let delta = -1.0 * lrnRate * this.obGrads[k];
this.oBiases[k] += delta;
}
} // updateWeights()
// --------------------------------------------------------
train(trainX, trainY, lrnRate, batSize, maxEpochs)
{
let n = trainX.length; // 200
let batchesPerEpoch = Math.trunc(n / batSize); // 20
let freq = Math.trunc(maxEpochs / 10); // progress
let indices = U.arange(n);
// ----------------------------------------------------
//
// n = 200; bs = 10
// batches per epoch = 200 / 10 = 20
// for epoch = 0; epoch "lt" maxEpochs; ++epoch
// for batch = 0; batch "lt" bpe; ++batch
// for item = 0; item "lt" bs; ++item
// compute output
// accum grads
// end-item
// update weights
// zero-out grads
// end-batches
// shuffle indices
// end-epochs
//
// ----------------------------------------------------
for (let epoch = 0; epoch "lt" maxEpochs; ++epoch) {
this.shuffle(indices);
let ptr = 0; // points into indices
for (let batIdx = 0; batIdx "lt" batchesPerEpoch;
++batIdx) // 0, 1, . . 19
{
for (let i = 0; i "lt" batSize; ++i) { // 0 . . 9
let ii = indices[ptr++]; // compute output
let x = trainX[ii];
let y = trainY[ii];
this.computeOutput(x); // into this.oNodes
this.accumGrads(y);
}
this.updateWeights(lrnRate);
this.zeroOutGrads(); // prep for next batch
} // batches
if (epoch % freq == 0) {
// let mse =
// this.meanSqErr(trainX, trainY).toFixed(4);
let mcee =
this.meanSqErr(trainX, trainY).toFixed(4);
let acc = this.accuracy(trainX, trainY,
0.10).toFixed(4);
let s1 = "epoch: " +
epoch.toString().padStart(6, ' ');
let s2 = " MCEE = " +
mcee.toString().padStart(8, ' ');
let s3 = " acc = " + acc.toString();
console.log(s1 + s2 + s3);
}
} // epoch
} // train
// --------------------------------------------------------
meanSqErr(dataX, dataY)
{
// for regression
let sumSE = 0.0;
for (let i = 0; i "lt" dataX.length; ++i) {
let X = dataX[i];
let Y = dataY[i]; // target income
let oupt = this.computeOutput(X);
sumSE += (Y - oupt) * (Y - oupt);
}
return sumSE / dataX.length; // consider Root MSE
}
accuracy(dataX, dataY, pctClose)
{
let nc = 0; let nw = 0;
for (let i = 0; i "lt" dataX.length; ++i) {
let X = dataX[i];
let y = dataY[i]; // target income
let pred = this.computeOutput(X);
if ( Math.abs(y - pred) "lt" Math.abs(y * pctClose) ) {
++nc;
}
else {
++nw;
}
}
return nc / (nc + nw);
}
saveWeights(fn)
{
let wts = this.getWeights();
let n = wts.length;
let s = "";
for (let i = 0; i "lt" n-1; ++i) {
s += wts[i].toString() + ",";
}
s += wts[n-1];
FS.writeFileSync(fn, s);
}
loadWeights(fn)
{
let n = (this.ni * this.nh) + this.nh +
(this.nh * this.no) + this.no;
let wts = U.vecMake(n, 0.0);
let all = FS.readFileSync(fn, "utf8");
let strVals = all.split(",");
let nn = strVals.length;
if (n != nn) {
throw("Size error in NeuralNet.loadWeights()");
}
for (let i = 0; i "lt" n; ++i) {
wts[i] = parseFloat(strVals[i]);
}
this.setWeights(wts);
}
} // NeuralNet
// ----------------------------------------------------------
function main()
{
// process.stdout.write("\033[0m"); // reset
// process.stdout.write("\x1b[1m" + "\x1b[37m"); // white
console.log("\nBegin JavaScript NN regression demo ");
console.log("Predict income from sex, age, State, politics ");
// 1. load data
// -1 0.29 1 0 0 0.65400 0 0 1
// 1 0.36 0 0 1 0.58300 1 0 0
console.log("\nLoading data into memory ");
let trainX = U.loadTxt(".\\Data\\people_train.txt", ",",
[0,1,2,3,4,6,7,8], "#");
let trainY = U.loadTxt(".\\Data\\people_train.txt", ",",
[5], "#");
trainY = U.matToVec(trainY);
let testX = U.loadTxt(".\\Data\\people_test.txt", ",",
[0,1,2,3,4,6,7,8], "#");
let testY = U.loadTxt(".\\Data\\people_test.txt", ",",
[5], "#");
testY = U.matToVec(testY);
// 2. create network
console.log("\nCreating 8-25-1 tanh, identity MSE NN ");
let seed = 0;
let nn = new NeuralNet(8, 25, 1, seed);
// 3. train network
let lrnRate = 0.001;
let maxEpochs = 1000;
console.log("\nSetting learn rate = 0.001 ");
console.log("Setting bat size = 10 ");
nn.train(trainX, trainY, lrnRate, 10, maxEpochs);
console.log("Training complete ");
// 4. evaluate model
console.log("\nComputing acc within 0.10 of true ");
let trainAcc = nn.accuracy(trainX, trainY, 0.10);
let testAcc = nn.accuracy(testX, testY, 0.10);
console.log("Accuracy on training data = " +
trainAcc.toFixed(4).toString());
console.log("Accuracy on test data = " +
testAcc.toFixed(4).toString());
// 5. save trained model
fn = ".\\Models\\people_income_wts.txt";
console.log("\nSaving model weights and biases to: ");
console.log(fn);
nn.saveWeights(fn);
// 6. use trained model
console.log("\nPredict for M 34 Oklahoma moderate ");
let x = [-1, 0.34, 0,0,1, 0,1,0];
let predicted = nn.computeOutput(x);
console.log("\nPredicted income: ");
console.log(predicted.toFixed(5).toString());
//process.stdout.write("\033[0m"); // reset
console.log("\nEnd demo");
}
main()
Code for utility functions:
// utilities_lib.js
// ES6
let FS = require('fs');
// ----------------------------------------------------------
function loadTxt(fn, delimit, usecols, comment) {
// efficient but mildly complicated
let all = FS.readFileSync(fn, "utf8"); // giant string
all = all.trim(); // strip final crlf in file
let lines = all.split("\n"); // array of lines
// count number non-comment lines
let nRows = 0;
for (let i = 0; i "lt" lines.length; ++i) {
if (!lines[i].startsWith(comment))
++nRows;
}
let nCols = usecols.length;
let result = matMake(nRows, nCols, 0.0);
let r = 0; // into lines
let i = 0; // into result[][]
while (r "lt" lines.length) {
if (lines[r].startsWith(comment)) {
++r; // next row
}
else {
let tokens = lines[r].split(delimit);
for (let j = 0; j "lt" nCols; ++j) {
result[i][j] = parseFloat(tokens[usecols[j]]);
}
++r;
++i;
}
}
return result;
}
// ----------------------------------------------------------
function arange(n)
{
let result = [];
for (let i = 0; i "lt" n; ++i) {
result[i] = Math.trunc(i);
}
return result;
}
// ----------------------------------------------------------
class Erratic
{
constructor(seed)
{
this.seed = seed + 0.5; // avoid 0
}
next()
{
let x = Math.sin(this.seed) * 1000;
let result = x - Math.floor(x); // [0.0,1.0)
this.seed = result; // for next call
return result;
}
nextInt(lo, hi)
{
let x = this.next();
return Math.trunc((hi - lo) * x + lo);
}
}
// ----------------------------------------------------------
function vecMake(n, val)
{
let result = [];
for (let i = 0; i "lt" n; ++i) {
result[i] = val;
}
return result;
}
function matMake(rows, cols, val)
{
let result = [];
for (let i = 0; i "lt" rows; ++i) {
result[i] = [];
for (let j = 0; j "lt" cols; ++j) {
result[i][j] = val;
}
}
return result;
}
function matToOneHot(m, n)
{
// convert ordinal (0,1,2 . .) to one-hot
let rows = m.length;
let cols = m[0].length;
let result = matMake(rows, n);
for (let i = 0; i "lt" rows; ++i) {
let k = Math.trunc(m[i][0]); // 0,1,2 . .
result[i] = vecMake(n, 0.0); // [0.0 0.0 0.0]
result[i][k] = 1.0; // [ 0.0 1.0 0.0]
}
return result;
}
function matToVec(m)
{
let r = m.length;
let c = m[0].length;
let result = vecMake(r*c, 0.0);
let k = 0;
for (let i = 0; i "lt" r; ++i) {
for (let j = 0; j "lt" c; ++j) {
result[k++] = m[i][j];
}
}
return result;
}
function vecShow(v, dec, len)
{
for (let i = 0; i "lt" v.length; ++i) {
if (i != 0 "and" i % len == 0) {
process.stdout.write("\n");
}
if (v[i] "gte" 0.0) {
process.stdout.write(" "); // + or - space
}
process.stdout.write(v[i].toFixed(dec));
process.stdout.write(" ");
}
process.stdout.write("\n");
}
function vecShow(vec, dec, wid, nl)
{
for (let i = 0; i "lt" vec.length; ++i) {
let x = vec[i];
if (Math.abs(x) "lt" 0.000001) x = 0.0 // avoid -0.00
let xx = x.toFixed(dec);
let s = xx.toString().padStart(wid, ' ');
process.stdout.write(s);
process.stdout.write(" ");
}
if (nl == true)
process.stdout.write("\n");
}
function matShow(m, dec, wid)
{
let rows = m.length;
let cols = m[0].length;
for (let i = 0; i "lt" rows; ++i) {
for (let j = 0; j "lt" cols; ++j) {
if (m[i][j] "gte" 0.0) {
process.stdout.write(" "); // + or - space
}
process.stdout.write(m[i][j].toFixed(dec));
process.stdout.write(" ");
}
process.stdout.write("\n");
}
}
function argmax(v)
{
let result = 0;
let m = v[0];
for (let i = 0; i "lt" v.length; ++i) {
if (v[i] "gt" m) {
m = v[i];
result = i;
}
}
return result;
}
function hyperTan(x)
{
if (x "lt" -10.0) {
return -1.0;
}
else if (x "gt" 10.0) {
return 1.0;
}
else {
return Math.tanh(x);
}
}
function logSig(x)
{
if (x "lt" -10.0) {
return 0.0;
}
else if (x "gt" 10.0) {
return 1.0;
}
else {
return 1.0 / (1.0 + Math.exp(-x));
}
}
function vecMax(vec)
{
let mx = vec[0];
for (let i = 0; i "lt" vec.length; ++i) {
if (vec[i] "gt" mx) {
mx = vec[i];
}
}
return mx;
}
function softmax(vec)
{
//let m = Math.max(...vec); // or 'spread' operator
let m = vecMax(vec);
let result = [];
let sum = 0.0;
for (let i = 0; i "lt" vec.length; ++i) {
result[i] = Math.exp(vec[i] - m);
sum += result[i];
}
for (let i = 0; i "lt" result.length; ++i) {
result[i] = result[i] / sum;
}
return result;
}
module.exports = {
vecMake,
matMake,
matToOneHot,
matToVec,
vecShow,
matShow,
argmax,
loadTxt,
arange,
Erratic,
hyperTan,
logSig,
vecMax,
softmax
};
Training data:
# people_train.txt # # sex (-1 = male, 1 = female), age / 100, # state (michigan = 100, nebraska = 010, oklahoma = 001), # income / 100_000, # politics (conservative = 100, moderate = 010, liberal = 001) # 1, 0.24, 1, 0, 0, 0.2950, 0, 0, 1 -1, 0.39, 0, 0, 1, 0.5120, 0, 1, 0 1, 0.63, 0, 1, 0, 0.7580, 1, 0, 0 -1, 0.36, 1, 0, 0, 0.4450, 0, 1, 0 1, 0.27, 0, 1, 0, 0.2860, 0, 0, 1 1, 0.50, 0, 1, 0, 0.5650, 0, 1, 0 1, 0.50, 0, 0, 1, 0.5500, 0, 1, 0 -1, 0.19, 0, 0, 1, 0.3270, 1, 0, 0 1, 0.22, 0, 1, 0, 0.2770, 0, 1, 0 -1, 0.39, 0, 0, 1, 0.4710, 0, 0, 1 1, 0.34, 1, 0, 0, 0.3940, 0, 1, 0 -1, 0.22, 1, 0, 0, 0.3350, 1, 0, 0 1, 0.35, 0, 0, 1, 0.3520, 0, 0, 1 -1, 0.33, 0, 1, 0, 0.4640, 0, 1, 0 1, 0.45, 0, 1, 0, 0.5410, 0, 1, 0 1, 0.42, 0, 1, 0, 0.5070, 0, 1, 0 -1, 0.33, 0, 1, 0, 0.4680, 0, 1, 0 1, 0.25, 0, 0, 1, 0.3000, 0, 1, 0 -1, 0.31, 0, 1, 0, 0.4640, 1, 0, 0 1, 0.27, 1, 0, 0, 0.3250, 0, 0, 1 1, 0.48, 1, 0, 0, 0.5400, 0, 1, 0 -1, 0.64, 0, 1, 0, 0.7130, 0, 0, 1 1, 0.61, 0, 1, 0, 0.7240, 1, 0, 0 1, 0.54, 0, 0, 1, 0.6100, 1, 0, 0 1, 0.29, 1, 0, 0, 0.3630, 1, 0, 0 1, 0.50, 0, 0, 1, 0.5500, 0, 1, 0 1, 0.55, 0, 0, 1, 0.6250, 1, 0, 0 1, 0.40, 1, 0, 0, 0.5240, 1, 0, 0 1, 0.22, 1, 0, 0, 0.2360, 0, 0, 1 1, 0.68, 0, 1, 0, 0.7840, 1, 0, 0 -1, 0.60, 1, 0, 0, 0.7170, 0, 0, 1 -1, 0.34, 0, 0, 1, 0.4650, 0, 1, 0 -1, 0.25, 0, 0, 1, 0.3710, 1, 0, 0 -1, 0.31, 0, 1, 0, 0.4890, 0, 1, 0 1, 0.43, 0, 0, 1, 0.4800, 0, 1, 0 1, 0.58, 0, 1, 0, 0.6540, 0, 0, 1 -1, 0.55, 0, 1, 0, 0.6070, 0, 0, 1 -1, 0.43, 0, 1, 0, 0.5110, 0, 1, 0 -1, 0.43, 0, 0, 1, 0.5320, 0, 1, 0 -1, 0.21, 1, 0, 0, 0.3720, 1, 0, 0 1, 0.55, 0, 0, 1, 0.6460, 1, 0, 0 1, 0.64, 0, 1, 0, 0.7480, 1, 0, 0 -1, 0.41, 1, 0, 0, 0.5880, 0, 1, 0 1, 0.64, 0, 0, 1, 0.7270, 1, 0, 0 -1, 0.56, 0, 0, 1, 0.6660, 0, 0, 1 1, 0.31, 0, 0, 1, 0.3600, 0, 1, 0 -1, 0.65, 0, 0, 1, 0.7010, 0, 0, 1 1, 0.55, 0, 0, 1, 0.6430, 1, 0, 0 -1, 0.25, 1, 0, 0, 0.4030, 1, 0, 0 1, 0.46, 0, 0, 1, 0.5100, 0, 1, 0 -1, 0.36, 1, 0, 0, 0.5350, 1, 0, 0 1, 0.52, 0, 1, 0, 0.5810, 0, 1, 0 1, 0.61, 0, 0, 1, 0.6790, 1, 0, 0 1, 0.57, 0, 0, 1, 0.6570, 1, 0, 0 -1, 0.46, 0, 1, 0, 0.5260, 0, 1, 0 -1, 0.62, 1, 0, 0, 0.6680, 0, 0, 1 1, 0.55, 0, 0, 1, 0.6270, 1, 0, 0 -1, 0.22, 0, 0, 1, 0.2770, 0, 1, 0 -1, 0.50, 1, 0, 0, 0.6290, 1, 0, 0 -1, 0.32, 0, 1, 0, 0.4180, 0, 1, 0 -1, 0.21, 0, 0, 1, 0.3560, 1, 0, 0 1, 0.44, 0, 1, 0, 0.5200, 0, 1, 0 1, 0.46, 0, 1, 0, 0.5170, 0, 1, 0 1, 0.62, 0, 1, 0, 0.6970, 1, 0, 0 1, 0.57, 0, 1, 0, 0.6640, 1, 0, 0 -1, 0.67, 0, 0, 1, 0.7580, 0, 0, 1 1, 0.29, 1, 0, 0, 0.3430, 0, 0, 1 1, 0.53, 1, 0, 0, 0.6010, 1, 0, 0 -1, 0.44, 1, 0, 0, 0.5480, 0, 1, 0 1, 0.46, 0, 1, 0, 0.5230, 0, 1, 0 -1, 0.20, 0, 1, 0, 0.3010, 0, 1, 0 -1, 0.38, 1, 0, 0, 0.5350, 0, 1, 0 1, 0.50, 0, 1, 0, 0.5860, 0, 1, 0 1, 0.33, 0, 1, 0, 0.4250, 0, 1, 0 -1, 0.33, 0, 1, 0, 0.3930, 0, 1, 0 1, 0.26, 0, 1, 0, 0.4040, 1, 0, 0 1, 0.58, 1, 0, 0, 0.7070, 1, 0, 0 1, 0.43, 0, 0, 1, 0.4800, 0, 1, 0 -1, 0.46, 1, 0, 0, 0.6440, 1, 0, 0 1, 0.60, 1, 0, 0, 0.7170, 1, 0, 0 -1, 0.42, 1, 0, 0, 0.4890, 0, 1, 0 -1, 0.56, 0, 0, 1, 0.5640, 0, 0, 1 -1, 0.62, 0, 1, 0, 0.6630, 0, 0, 1 -1, 0.50, 1, 0, 0, 0.6480, 0, 1, 0 1, 0.47, 0, 0, 1, 0.5200, 0, 1, 0 -1, 0.67, 0, 1, 0, 0.8040, 0, 0, 1 -1, 0.40, 0, 0, 1, 0.5040, 0, 1, 0 1, 0.42, 0, 1, 0, 0.4840, 0, 1, 0 1, 0.64, 1, 0, 0, 0.7200, 1, 0, 0 -1, 0.47, 1, 0, 0, 0.5870, 0, 0, 1 1, 0.45, 0, 1, 0, 0.5280, 0, 1, 0 -1, 0.25, 0, 0, 1, 0.4090, 1, 0, 0 1, 0.38, 1, 0, 0, 0.4840, 1, 0, 0 1, 0.55, 0, 0, 1, 0.6000, 0, 1, 0 -1, 0.44, 1, 0, 0, 0.6060, 0, 1, 0 1, 0.33, 1, 0, 0, 0.4100, 0, 1, 0 1, 0.34, 0, 0, 1, 0.3900, 0, 1, 0 1, 0.27, 0, 1, 0, 0.3370, 0, 0, 1 1, 0.32, 0, 1, 0, 0.4070, 0, 1, 0 1, 0.42, 0, 0, 1, 0.4700, 0, 1, 0 -1, 0.24, 0, 0, 1, 0.4030, 1, 0, 0 1, 0.42, 0, 1, 0, 0.5030, 0, 1, 0 1, 0.25, 0, 0, 1, 0.2800, 0, 0, 1 1, 0.51, 0, 1, 0, 0.5800, 0, 1, 0 -1, 0.55, 0, 1, 0, 0.6350, 0, 0, 1 1, 0.44, 1, 0, 0, 0.4780, 0, 0, 1 -1, 0.18, 1, 0, 0, 0.3980, 1, 0, 0 -1, 0.67, 0, 1, 0, 0.7160, 0, 0, 1 1, 0.45, 0, 0, 1, 0.5000, 0, 1, 0 1, 0.48, 1, 0, 0, 0.5580, 0, 1, 0 -1, 0.25, 0, 1, 0, 0.3900, 0, 1, 0 -1, 0.67, 1, 0, 0, 0.7830, 0, 1, 0 1, 0.37, 0, 0, 1, 0.4200, 0, 1, 0 -1, 0.32, 1, 0, 0, 0.4270, 0, 1, 0 1, 0.48, 1, 0, 0, 0.5700, 0, 1, 0 -1, 0.66, 0, 0, 1, 0.7500, 0, 0, 1 1, 0.61, 1, 0, 0, 0.7000, 1, 0, 0 -1, 0.58, 0, 0, 1, 0.6890, 0, 1, 0 1, 0.19, 1, 0, 0, 0.2400, 0, 0, 1 1, 0.38, 0, 0, 1, 0.4300, 0, 1, 0 -1, 0.27, 1, 0, 0, 0.3640, 0, 1, 0 1, 0.42, 1, 0, 0, 0.4800, 0, 1, 0 1, 0.60, 1, 0, 0, 0.7130, 1, 0, 0 -1, 0.27, 0, 0, 1, 0.3480, 1, 0, 0 1, 0.29, 0, 1, 0, 0.3710, 1, 0, 0 -1, 0.43, 1, 0, 0, 0.5670, 0, 1, 0 1, 0.48, 1, 0, 0, 0.5670, 0, 1, 0 1, 0.27, 0, 0, 1, 0.2940, 0, 0, 1 -1, 0.44, 1, 0, 0, 0.5520, 1, 0, 0 1, 0.23, 0, 1, 0, 0.2630, 0, 0, 1 -1, 0.36, 0, 1, 0, 0.5300, 0, 0, 1 1, 0.64, 0, 0, 1, 0.7250, 1, 0, 0 1, 0.29, 0, 0, 1, 0.3000, 0, 0, 1 -1, 0.33, 1, 0, 0, 0.4930, 0, 1, 0 -1, 0.66, 0, 1, 0, 0.7500, 0, 0, 1 -1, 0.21, 0, 0, 1, 0.3430, 1, 0, 0 1, 0.27, 1, 0, 0, 0.3270, 0, 0, 1 1, 0.29, 1, 0, 0, 0.3180, 0, 0, 1 -1, 0.31, 1, 0, 0, 0.4860, 0, 1, 0 1, 0.36, 0, 0, 1, 0.4100, 0, 1, 0 1, 0.49, 0, 1, 0, 0.5570, 0, 1, 0 -1, 0.28, 1, 0, 0, 0.3840, 1, 0, 0 -1, 0.43, 0, 0, 1, 0.5660, 0, 1, 0 -1, 0.46, 0, 1, 0, 0.5880, 0, 1, 0 1, 0.57, 1, 0, 0, 0.6980, 1, 0, 0 -1, 0.52, 0, 0, 1, 0.5940, 0, 1, 0 -1, 0.31, 0, 0, 1, 0.4350, 0, 1, 0 -1, 0.55, 1, 0, 0, 0.6200, 0, 0, 1 1, 0.50, 1, 0, 0, 0.5640, 0, 1, 0 1, 0.48, 0, 1, 0, 0.5590, 0, 1, 0 -1, 0.22, 0, 0, 1, 0.3450, 1, 0, 0 1, 0.59, 0, 0, 1, 0.6670, 1, 0, 0 1, 0.34, 1, 0, 0, 0.4280, 0, 0, 1 -1, 0.64, 1, 0, 0, 0.7720, 0, 0, 1 1, 0.29, 0, 0, 1, 0.3350, 0, 0, 1 -1, 0.34, 0, 1, 0, 0.4320, 0, 1, 0 -1, 0.61, 1, 0, 0, 0.7500, 0, 0, 1 1, 0.64, 0, 0, 1, 0.7110, 1, 0, 0 -1, 0.29, 1, 0, 0, 0.4130, 1, 0, 0 1, 0.63, 0, 1, 0, 0.7060, 1, 0, 0 -1, 0.29, 0, 1, 0, 0.4000, 1, 0, 0 -1, 0.51, 1, 0, 0, 0.6270, 0, 1, 0 -1, 0.24, 0, 0, 1, 0.3770, 1, 0, 0 1, 0.48, 0, 1, 0, 0.5750, 0, 1, 0 1, 0.18, 1, 0, 0, 0.2740, 1, 0, 0 1, 0.18, 1, 0, 0, 0.2030, 0, 0, 1 1, 0.33, 0, 1, 0, 0.3820, 0, 0, 1 -1, 0.20, 0, 0, 1, 0.3480, 1, 0, 0 1, 0.29, 0, 0, 1, 0.3300, 0, 0, 1 -1, 0.44, 0, 0, 1, 0.6300, 1, 0, 0 -1, 0.65, 0, 0, 1, 0.8180, 1, 0, 0 -1, 0.56, 1, 0, 0, 0.6370, 0, 0, 1 -1, 0.52, 0, 0, 1, 0.5840, 0, 1, 0 -1, 0.29, 0, 1, 0, 0.4860, 1, 0, 0 -1, 0.47, 0, 1, 0, 0.5890, 0, 1, 0 1, 0.68, 1, 0, 0, 0.7260, 0, 0, 1 1, 0.31, 0, 0, 1, 0.3600, 0, 1, 0 1, 0.61, 0, 1, 0, 0.6250, 0, 0, 1 1, 0.19, 0, 1, 0, 0.2150, 0, 0, 1 1, 0.38, 0, 0, 1, 0.4300, 0, 1, 0 -1, 0.26, 1, 0, 0, 0.4230, 1, 0, 0 1, 0.61, 0, 1, 0, 0.6740, 1, 0, 0 1, 0.40, 1, 0, 0, 0.4650, 0, 1, 0 -1, 0.49, 1, 0, 0, 0.6520, 0, 1, 0 1, 0.56, 1, 0, 0, 0.6750, 1, 0, 0 -1, 0.48, 0, 1, 0, 0.6600, 0, 1, 0 1, 0.52, 1, 0, 0, 0.5630, 0, 0, 1 -1, 0.18, 1, 0, 0, 0.2980, 1, 0, 0 -1, 0.56, 0, 0, 1, 0.5930, 0, 0, 1 -1, 0.52, 0, 1, 0, 0.6440, 0, 1, 0 -1, 0.18, 0, 1, 0, 0.2860, 0, 1, 0 -1, 0.58, 1, 0, 0, 0.6620, 0, 0, 1 -1, 0.39, 0, 1, 0, 0.5510, 0, 1, 0 -1, 0.46, 1, 0, 0, 0.6290, 0, 1, 0 -1, 0.40, 0, 1, 0, 0.4620, 0, 1, 0 -1, 0.60, 1, 0, 0, 0.7270, 0, 0, 1 1, 0.36, 0, 1, 0, 0.4070, 0, 0, 1 1, 0.44, 1, 0, 0, 0.5230, 0, 1, 0 1, 0.28, 1, 0, 0, 0.3130, 0, 0, 1 1, 0.54, 0, 0, 1, 0.6260, 1, 0, 0
Test data:
# people_test.txt # -1, 0.51, 1, 0, 0, 0.6120, 0, 1, 0 -1, 0.32, 0, 1, 0, 0.4610, 0, 1, 0 1, 0.55, 1, 0, 0, 0.6270, 1, 0, 0 1, 0.25, 0, 0, 1, 0.2620, 0, 0, 1 1, 0.33, 0, 0, 1, 0.3730, 0, 0, 1 -1, 0.29, 0, 1, 0, 0.4620, 1, 0, 0 1, 0.65, 1, 0, 0, 0.7270, 1, 0, 0 -1, 0.43, 0, 1, 0, 0.5140, 0, 1, 0 -1, 0.54, 0, 1, 0, 0.6480, 0, 0, 1 1, 0.61, 0, 1, 0, 0.7270, 1, 0, 0 1, 0.52, 0, 1, 0, 0.6360, 1, 0, 0 1, 0.3, 0, 1, 0, 0.3350, 0, 0, 1 1, 0.29, 1, 0, 0, 0.3140, 0, 0, 1 -1, 0.47, 0, 0, 1, 0.5940, 0, 1, 0 1, 0.39, 0, 1, 0, 0.4780, 0, 1, 0 1, 0.47, 0, 0, 1, 0.5200, 0, 1, 0 -1, 0.49, 1, 0, 0, 0.5860, 0, 1, 0 -1, 0.63, 0, 0, 1, 0.6740, 0, 0, 1 -1, 0.3, 1, 0, 0, 0.3920, 1, 0, 0 -1, 0.61, 0, 0, 1, 0.6960, 0, 0, 1 -1, 0.47, 0, 0, 1, 0.5870, 0, 1, 0 1, 0.3, 0, 0, 1, 0.3450, 0, 0, 1 -1, 0.51, 0, 0, 1, 0.5800, 0, 1, 0 -1, 0.24, 1, 0, 0, 0.3880, 0, 1, 0 -1, 0.49, 1, 0, 0, 0.6450, 0, 1, 0 1, 0.66, 0, 0, 1, 0.7450, 1, 0, 0 -1, 0.65, 1, 0, 0, 0.7690, 1, 0, 0 -1, 0.46, 0, 1, 0, 0.5800, 1, 0, 0 -1, 0.45, 0, 0, 1, 0.5180, 0, 1, 0 -1, 0.47, 1, 0, 0, 0.6360, 1, 0, 0 -1, 0.29, 1, 0, 0, 0.4480, 1, 0, 0 -1, 0.57, 0, 0, 1, 0.6930, 0, 0, 1 -1, 0.2, 1, 0, 0, 0.2870, 0, 0, 1 -1, 0.35, 1, 0, 0, 0.4340, 0, 1, 0 -1, 0.61, 0, 0, 1, 0.6700, 0, 0, 1 -1, 0.31, 0, 0, 1, 0.3730, 0, 1, 0 1, 0.18, 1, 0, 0, 0.2080, 0, 0, 1 1, 0.26, 0, 0, 1, 0.2920, 0, 0, 1 -1, 0.28, 1, 0, 0, 0.3640, 0, 0, 1 -1, 0.59, 0, 0, 1, 0.6940, 0, 0, 1

.NET Test Automation Recipes
Software Testing
SciPy Programming Succinctly
Keras Succinctly
R Programming
2026 Visual Studio Live
2025 Summer MLADS Conference
2026 DevIntersection Conference
2025 Machine Learning Week
2025 Ai4 Conference
2026 G2E Conference
2026 iSC West Conference
You must be logged in to post a comment.