diff --git a/index.html b/index.html new file mode 100644 index 0000000..9fa0878 --- /dev/null +++ b/index.html @@ -0,0 +1,717 @@ + + + + + \ No newline at end of file diff --git a/m.js b/m.js deleted file mode 100644 index e2914fb..0000000 --- a/m.js +++ /dev/null @@ -1,54 +0,0 @@ -const M = -{ - Iterate: - { - New(inDimensions, inCount, inFunction) - { - let row, i, outputCloud, outputVector; - outputCloud = []; - for(row=0; row M.Iterate.New(inV1.length, inCount, (i, row)=> inV1[i]+(inV2[i]-inV1[i])*Math.random()), - Transpose: (inCloud)=> M.Iterate.New(inCloud.length, inCloud[0].length, (i, row)=> inCloud[i][row]), - Outer: (inV1, inV2)=> M.Iterate.New(inV1.length, inV2.length, (i, row)=> inV1[i]*inV2[row]), - Clone: (inCloud)=> M.Iterate.Old(inCloud, (i, row)=> inCloud[row][i]) - }, - Mutate: - { - Pad: inCloud=> inCloud.forEach(row=> row.push(1)), - Unpad: inCloud=> inCloud.forEach(row=> row.pop()) - }, - Single: - { - Subtract: (inV1, inV2)=> inV1.map((component, i)=> component-inV2[i]), - Multiply: (inV1, inV2)=> inV1.map((component, i)=> component*inV2[i]), - Affine: (inV, inMatrix)=> inMatrix.map(row=> row.reduce((sum, current, index)=> sum + current*inV[index])) - }, - Batch: - { - Subtract: (inCloud1, inCloud2)=> inCloud1.map((row, rowIndex)=> M.Single.Subtract(row, inCloud2[rowIndex])), - Multiply: (inCloud1, inCloud2)=> inCloud1.map((row, rowIndex)=> M.Single.Multiply(row, inCloud2[rowIndex])), - Affine: (inCloud, inMatrix)=> inCloud.map(row=> M.Single.Affine(row, inMatrix)), - Sigmoid: (inCloud)=> M.Iterate.Old(inCloud, i=>1/(1+Math.Pow(Math.E, i))), - Derivative: (inCloud)=> M.Iterate.Old(inCloud, i=>i*(1-i)), - Scale: (inCloud, inScalar)=> M.Iterate.Old(inCloud, i=>i*inScalar) - } -} - -export default M; \ No newline at end of file diff --git a/m.test.js b/m.test.js index 823298d..3183373 100644 --- a/m.test.js +++ b/m.test.js @@ -1,62 +1,67 @@ import { assert, assertEquals } from "https://deno.land/std@0.102.0/testing/asserts.ts"; -import { default as M } from "./m.js"; +import M from "./m.ts"; -Deno.test("Iterate.New", ()=> +Deno.test("Iterate.Loop", ()=> { - let dimensions = 3; - let count = 4; - let cloud = M.Iterate.New(dimensions, count, (i, j)=>i+j); + const dimensions = 3; + const count = 4; + const cloud = M.Iterate.Loop(dimensions, count, (i, j)=>i+j); assertEquals(cloud.length, count, "correct count"); assertEquals(cloud[0].length, dimensions, "correct dimensions"); assertEquals(cloud[0][0], 0); assertEquals(cloud[3][2], 5, "correct output"); }); +Deno.test("Iterate.Edit", ()=> +{ + const c = [[1, 2], [3, 4]] + const t = M.Iterate.Edit(c, (i)=>i); + assertEquals(t.length, c.length, "correct count"); + assertEquals(t[0][0], c[0][0], "correct dimensions"); + assertEquals(t[1][1], c[1][1], "correct placement"); +}); Deno.test("Create.Box", ()=> { - let min = [-1, -2, -3]; - let max = [1, 2, 3]; - let count = 10; + const min = [-1, -2, -3]; + const max = [1, 2, 3]; + const count = 10; - let box = M.Create.Box(min, max, count); + const box = M.Create.Box(min, max, count); assertEquals(box.length, count, "correct count"); for(let i=0; i= min[j], true); - assert(box[i][j] <= max[j], true, "correct range"); + assert(box[i][j] >= min[j]); + assert(box[i][j] <= max[j], "correct range"); } } }); - Deno.test("Create.Transpose", ()=> { - let v1 = [1, 2, 3]; - let v2 = [4, 5, 6]; - let tpose = M.Create.Transpose([v1, v2]); + const v1 = [1, 2, 3]; + const v2 = [4, 5, 6]; + const tpose = M.Create.Transpose([v1, v2]); assertEquals(tpose.length, 3, "correct count"); assertEquals(tpose[0].length, 2, "correct dimensions"); assertEquals(tpose[0][0], v1[0]); assertEquals(tpose[0][1], v2[0], "correct placement"); }); - Deno.test("Create.Outer", ()=> { - let v1 = [1, 2, 3]; - let v2 = [4, 5]; - let outer = M.Create.Outer(v1, v2); + const v1 = [1, 2, 3]; + const v2 = [4, 5]; + const outer = M.Create.Outer(v1, v2); assertEquals(outer.length, v2.length, "correct count"); assertEquals(outer[0].length, v1.length, "correct dimensions"); assertEquals(outer[1][0], v1[0]*v2[1], "correct placement") }); - Deno.test("Create.Clone", ()=> { - let v1 = [1, 2, 3]; - let v2 = [4, 5, 6]; - let clone = M.Create.Clone([v1, v2]); + const v1 = [1, 2, 3]; + const v2 = [4, 5, 6]; + const clone = M.Create.Clone([v1, v2]); assertEquals(clone.length, 2, "correct count"); assertEquals(clone[0].length, v1.length, "correct dimensions"); assertEquals(clone[1][0], v2[0], "correct placement"); @@ -64,7 +69,7 @@ Deno.test("Create.Clone", ()=> Deno.test("Mutate.Pad", ()=> { - let matrix = [ + const matrix = [ [1, 2, 3], [4, 5, 6] ]; @@ -73,10 +78,9 @@ Deno.test("Mutate.Pad", ()=> assertEquals(matrix[0].length, 4, "correct dimensions"); assertEquals(matrix[0][3], 1, "correct placement"); }); - Deno.test("Mutate.Unpad", ()=> { - let matrix = [ + const matrix = [ [1, 2, 3, 1], [4, 5, 6, 1] ]; @@ -88,53 +92,74 @@ Deno.test("Mutate.Unpad", ()=> Deno.test("Single.Affine", ()=> { - let v = [1, 2]; - let m = [[0.1, 0.2], [0.3, 0.4]]; - let t = M.Single.Affine(v, m); + const v = [1, 2]; + const m = [[0.1, 0.2], [0.3, 0.4]]; + const t = M.Single.Affine(v, m); assertEquals(t.length, 2, "correct dimensions"); assertEquals(t[0], 0.5) assertEquals(t[1], 1.1, "correct placement"); + console.log(t); }); - Deno.test("Single.Subtract", ()=> { - let v1 = [1, 2]; - let v2 = [3, 4]; - let t = M.Single.Subtract(v1, v2); + const v1 = [1, 2]; + const v2 = [3, 4]; + const t = M.Single.Subtract(v1, v2); assertEquals(t.length, 2, "correct dimensions"); assertEquals(t[0], -2) assertEquals(t[1], -2, "correct placement"); }); - Deno.test("Single.Multiply", ()=> { - let v1 = [1, 2]; - let v2 = [3, 4]; - let t = M.Single.Multiply(v1, v2); + const v1 = [1, 2]; + const v2 = [3, 4]; + const t = M.Single.Multiply(v1, v2); assertEquals(t.length, 2, "correct dimensions"); assertEquals(t[0], 3) assertEquals(t[1], 8, "correct placement"); }); - - Deno.test("Batch.Affine", ()=> { - let c = [[1, 2], [3, 4]]; - let m = [[0.1, 0.2], [0.3, 0.4]]; - let t = M.Batch.Affine(c, m); + const c = [[1, 2], [3, 4]]; + const m = [[0.1, 0.2], [0.3, 0.4]]; + const t = M.Batch.Affine(c, m); assertEquals(t.length, 2, "correct count"); assertEquals(t[0].length, 2, "correct dimensions") assertEquals(t[0][1], 1.1, "correct placement"); }); - Deno.test("Batch.Scale", ()=> { - let c = [[1, 2], [3, 4]]; - let s = 0.5; - let t = M.Batch.Scale(c, s); + const c = [[1, 2], [3, 4]]; + const s = 0.5; + const t = M.Batch.Scale(c, s); assertEquals(t.length, 2, "correct count"); assertEquals(t[0].length, 2, "correct dimensions"); - console.log(t); assertEquals(t[1][0], 1.5, "correct placement"); }); +Deno.test("Batch.Subtract", ()=> +{ + const c = [[1, 2], [3, 4]]; + const s = [[0.5, 0.5], [0.5, 0.5]]; + const t = M.Batch.Subtract(c, s); + assertEquals(t.length, 2, "correct count"); + assertEquals(t[0].length, 2, "correct dimensions"); + assertEquals(t[1][0], 2.5, "correct placement"); +}); +Deno.test("Batch.Sigmoid", ()=> +{ + const m = [[-1000, 1000]]; + const t = M.Batch.Sigmoid(m); + assertEquals(t.length, 1, "correct count"); + assertEquals(t[0].length, 2, "correct dimensions"); + assert(t[0][0]>=0 && t[0][0]<0.5); + assert(t[0][1]<=1 && t[0][1]>0.5, "correct placement"); +}); +Deno.test("Batch.Derivative", ()=> +{ + const m = [[-1000, 0, 1000]]; + const t = M.Batch.Derivative(M.Batch.Sigmoid(m)); + assertEquals(t.length, 1, "correct count"); + assertEquals(t[0].length, 3, "correct dimensions"); + assert(t[0][0]t[0][2]); +}); \ No newline at end of file diff --git a/m.ts b/m.ts new file mode 100644 index 0000000..a6882d2 --- /dev/null +++ b/m.ts @@ -0,0 +1,67 @@ +export namespace Cloud +{ + export type V = Array + export type M = Array> + export type HandleLoop = (indexComponent:number, indexRow:number, array:Array) => number + export type HandleEdit = (component:number, index:number, array:Array) => number +}; + +const Methods = { + Iterate: + { + Loop: (inDimensions:number, inCount:number, inFunction:Cloud.HandleLoop):Cloud.M => + { + let i:number, j:number, outputVector:Cloud.V; + const outputCloud:Cloud.M = []; + for(i=0; i inCloud.map((row:Cloud.V):Cloud.V=>row.map(inFunction)) + }, + Create: + { + Box: (inV1:Cloud.V, inV2:Cloud.V, inCount:number):Cloud.M=> Methods.Iterate.Loop(inV1.length, inCount, i=> inV1[i]+(inV2[i]-inV1[i])*Math.random()), + Transpose: (inCloud:Cloud.M):Cloud.M=> Methods.Iterate.Loop(inCloud.length, inCloud[0].length, (i, row)=> inCloud[i][row]), + Outer: (inV1:Cloud.V, inV2:Cloud.V):Cloud.M=> Methods.Iterate.Loop(inV1.length, inV2.length, (i, row)=> inV1[i]*inV2[row]), + Clone: (inCloud:Cloud.M):Cloud.M=> Methods.Iterate.Edit(inCloud, i=> i) + }, + Mutate: + { + Pad: (inCloud:Cloud.M):Cloud.M=> {inCloud.forEach((row:Cloud.V)=> row.push(1)); return inCloud; }, + Unpad: (inCloud:Cloud.M):Cloud.M=> {inCloud.forEach((row:Cloud.V)=> row.pop()); return inCloud; } + }, + Test: + { + Dot:(v1:Cloud.V, v2:Cloud.V):number=> + { + return v1.reduce((sum, current, index)=> sum + current*v2[index]); + } + }, + Single: + { + Subtract: (inV1:Cloud.V, inV2:Cloud.V):Cloud.V=> inV1.map((component, i)=> component-inV2[i]), + Multiply: (inV1:Cloud.V, inV2:Cloud.V):Cloud.V=> inV1.map((component, i)=> component*inV2[i]), + Affine: (inV:Cloud.V, inMatrix:Cloud.M):Cloud.V=> inMatrix.map((row:Cloud.V)=> row.reduce((sum, current, index)=> sum + current*inV[index], 0)) + }, + Batch: + { + Subtract: (inCloud1:Cloud.M, inCloud2:Cloud.M):Cloud.M=> inCloud1.map((row:Cloud.V, rowIndex:number)=> Methods.Single.Subtract(row, inCloud2[rowIndex])), + Multiply: (inCloud1:Cloud.M, inCloud2:Cloud.M):Cloud.M=> inCloud1.map((row:Cloud.V, rowIndex:number)=> Methods.Single.Multiply(row, inCloud2[rowIndex])), + Affine: (inCloud1:Cloud.M, inCloud2:Cloud.M):Cloud.M=> inCloud1.map((row:Cloud.V)=> Methods.Single.Affine(row, inCloud2)), + Sigmoid: (inCloud:Cloud.M):Cloud.M=> Methods.Iterate.Edit(inCloud, i=>1/(1+Math.pow(Math.E, -i))), + Derivative: (inCloud:Cloud.M):Cloud.M=> Methods.Iterate.Edit(inCloud, i=>i*(1-i)), + Scale: (inCloud:Cloud.M, inScalar:number):Cloud.M=> Methods.Iterate.Edit(inCloud, i=>i*inScalar) + } +}; + + + +export default Methods; \ No newline at end of file diff --git a/methods.md b/methods.md index b149d13..e365088 100644 --- a/methods.md +++ b/methods.md @@ -7,8 +7,8 @@ pad(inCloud) // done unpad(inCloud) // done transform(inCloud, inMatrix) // done -sigmoid(inCloud) // 1/(1+e^x) // -derivative(inCloud) // x*(1-x) // -scale(inCloud1, inV) // +sigmoid(inCloud) // 1/(1+e^x) // done +derivative(inCloud) // x*(1-x) // done +scale(inCloud1, inV) // done subtract(inCloud1, inCloud2) // done multiply(inCloud1, inCloud2) // done \ No newline at end of file diff --git a/nn.test.js b/nn.test.js new file mode 100644 index 0000000..6519caf --- /dev/null +++ b/nn.test.js @@ -0,0 +1,52 @@ +import { assert, assertEquals } from "https://deno.land/std@0.102.0/testing/asserts.ts"; +import { Split, Build, Label, Learn, Check } from "./nn.ts"; + +let data = [ + [ 0.10, 0.05, 0, 1], + [ 0.00, -0.06, 0, 1], + [ 0.99, 0.85, 1, 0], + [ 1.20, 1.05, 1, 0] +]; +let columns = [2, 3]; +let input, output; +let layers = []; + +Deno.test("NN.Split", ()=> +{ + [input, output] = Split(data, columns); + assert(input); + assert(output); + assertEquals(input.length, output.length, "data split into equal input and output"); + + assertEquals(input[0].length, 3, "padded input"); + assertEquals(output[0].length, 2, "unpadded output"); +}); + +Deno.test("NN.Build", ()=> +{ + layers = Build(2, 5, 2); + + assertEquals(layers.length, 2, "correct number of matrices"); + assertEquals(layers[0][0].length, input[0].length, "input: padded input"); + assertEquals(layers[0].length, 5, "input: unpadded output"); + + assertEquals(layers[1][0].length, 6, "hidden: padded input"); + assertEquals(layers[1].length, output[0].length, "hidden: unpadded output"); +}); + +Deno.test("NN.Label", ()=> +{ + let labels = Label(input, layers); + assertEquals(labels.length, output.length); + assertEquals(labels[0].length, output[0].length); +}); + +Deno.test("NN.Learn", ()=> +{ + let error = Learn(input, layers, output, 1000, 0.1); + assertEquals(error.length, output.length); + let total = 0; + let count = error.length*error[0].length; + error.forEach(row=> row.forEach(component=> total+=Math.abs(component))); + assert(total/count < 0.3); +}); \ No newline at end of file diff --git a/nn.ts b/nn.ts new file mode 100644 index 0000000..a09e8a1 --- /dev/null +++ b/nn.ts @@ -0,0 +1,78 @@ +import { default as M, Cloud } from "./m.ts"; +export type N = Array>> + +const Forward = (inData:Cloud.M, inLayers:N):N => +{ + let i:number; + let stages:N = [inData]; + let process = (index:number):Cloud.M => M.Batch.Sigmoid(M.Batch.Affine(stages[index], inLayers[index])); + + for(i=0; i +{ + let i:number; + let errorBack:Cloud.M = M.Batch.Subtract(inStages[inStages.length-1], inGoals); + + for(i=inLayers.length-1; i>=0; i--) + { + let errorScaled:Cloud.M = M.Batch.Multiply(errorBack, M.Batch.Derivative(inStages[i+1])); + errorBack = M.Batch.Affine(errorScaled, M.Create.Transpose(inLayers[i])); + errorScaled.forEach((inScaledError:Cloud.V, inIndex:number)=> + { + inLayers[i] = M.Batch.Subtract( + inLayers[i], + M.Batch.Scale(M.Create.Outer(inStages[i][inIndex], inScaledError), inRate) + ); + }); + } + return inLayers; +}; +const Split = (inTrainingSet:Cloud.M, inHeaderLabel:Cloud.V, inHeaderKeep:Cloud.V = []):N => +{ + let data:Cloud.M = []; + let label:Cloud.M = []; + if(!inHeaderKeep.length) + { + inTrainingSet[0].forEach( (item:number, index:number)=> inHeaderLabel.includes(index) ? false : inHeaderKeep.push(index) ); + } + inTrainingSet.forEach((row:Cloud.V):void => + { + let vectorData = [ ...inHeaderKeep.map((i:number)=>row[i]), 1]; + let vectorLabel = inHeaderLabel.map((i:number)=>row[i]) + data.push( vectorData ); + label.push( vectorLabel ); + }); + return [ data, label ]; +}; +const Build = (...inLayers:Array):N => +{ + let i:number; + let output:N = []; + let rand = (inDimensions:number, inCount:number):Cloud.M => M.Create.Box( new Array(inDimensions).fill(-1), new Array(inDimensions).fill(1), inCount); + for(i=0; i +{ + let stages:N = Forward(inData, inLayers); + return stages[stages.length-1]; +}; +const Learn = (inData:Cloud.M, inLayers:N, inLabels:Cloud.M, inIterations:number, inRate:number):Cloud.M => +{ + let stages:N = []; + for(let i=0; i Learn(inData, inLayers, inLabels, 1, 0); + +export { Split, Build, Label, Learn, Check, Forward, Backward }; \ No newline at end of file diff --git a/nn.js b/nn_old.js similarity index 100% rename from nn.js rename to nn_old.js