Skip to content

Commit

Permalink
fix: Ts & lint
Browse files Browse the repository at this point in the history
  • Loading branch information
robertleeplummerjr committed Jul 8, 2024
1 parent c8a62f1 commit b396559
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 83 deletions.
105 changes: 47 additions & 58 deletions src/autoencoder.test.ts
Original file line number Diff line number Diff line change
@@ -1,79 +1,68 @@
import AE from "./autoencoder";
import AE from './autoencoder';

const trainingData = [
[0, 0, 0],
[0, 1, 1],
[1, 0, 1],
[1, 1, 0]
[1, 1, 0],
];

const xornet = new AE<number[], number[]>(
{
decodedSize: 3,
hiddenLayers: [ 5, 2, 5 ]
}
);
const xornet = new AE<number[], number[]>({
decodedSize: 3,
hiddenLayers: [5, 2, 5],
});

const errorThresh = 0.011;

const result = xornet.train(
trainingData, {
iterations: 100000,
errorThresh
}
);

test(
"denoise a data sample",
async () => {
expect(result.error).toBeLessThanOrEqual(errorThresh);

function xor(...args: number[]) {
return Math.round(xornet.denoise(args)[2]);
}
const result = xornet.train(trainingData, {
iterations: 100000,
errorThresh,
});

const run1 = xor(0, 0, 0);
const run2 = xor(0, 1, 1);
const run3 = xor(1, 0, 1);
const run4 = xor(1, 1, 0);
test('denoise a data sample', async () => {
expect(result.error).toBeLessThanOrEqual(errorThresh);

expect(run1).toBe(0);
expect(run2).toBe(1);
expect(run3).toBe(1);
expect(run4).toBe(0);
function xor(...args: number[]) {
return Math.round(xornet.denoise(args)[2]);
}
);

test(
"encode and decode a data sample",
async () => {
expect(result.error).toBeLessThanOrEqual(errorThresh);
const run1 = xor(0, 0, 0);
const run2 = xor(0, 1, 1);
const run3 = xor(1, 0, 1);
const run4 = xor(1, 1, 0);

const run1$input = [0, 0, 0];
const run1$encoded = xornet.encode(run1$input);
const run1$decoded = xornet.decode(run1$encoded);
expect(run1).toBe(0);
expect(run2).toBe(1);
expect(run3).toBe(1);
expect(run4).toBe(0);
});

const run2$input = [0, 1, 1];
const run2$encoded = xornet.encode(run2$input);
const run2$decoded = xornet.decode(run2$encoded);
test('encode and decode a data sample', async () => {
expect(result.error).toBeLessThanOrEqual(errorThresh);

for (let i = 0; i < 3; i++) expect(Math.round(run1$decoded[i])).toBe(run1$input[i]);
for (let i = 0; i < 3; i++) expect(Math.round(run2$decoded[i])).toBe(run2$input[i]);
}
);
const run1$input = [0, 0, 0];
const run1$encoded = xornet.encode(run1$input);
const run1$decoded = xornet.decode(run1$encoded);

const run2$input = [0, 1, 1];
const run2$encoded = xornet.encode(run2$input);
const run2$decoded = xornet.decode(run2$encoded);

test(
"test a data sample for anomalies",
async () => {
expect(result.error).toBeLessThanOrEqual(errorThresh);
for (let i = 0; i < 3; i++)
expect(Math.round(run1$decoded[i])).toBe(run1$input[i]);
for (let i = 0; i < 3; i++)
expect(Math.round(run2$decoded[i])).toBe(run2$input[i]);
});

function includesAnomalies(...args: number[]) {
expect(xornet.likelyIncludesAnomalies(args)).toBe(false);
}
test('test a data sample for anomalies', async () => {
expect(result.error).toBeLessThanOrEqual(errorThresh);

includesAnomalies(0, 0, 0);
includesAnomalies(0, 1, 1);
includesAnomalies(1, 0, 1);
includesAnomalies(1, 1, 0);
function includesAnomalies(...args: number[]) {
expect(xornet.likelyIncludesAnomalies(args)).toBe(false);
}
);

includesAnomalies(0, 0, 0);
includesAnomalies(0, 1, 1);
includesAnomalies(1, 0, 1);
includesAnomalies(1, 1, 0);
});
60 changes: 40 additions & 20 deletions src/autoencoder.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
import { KernelOutput, Texture, TextureArrayOutput } from "gpu.js";
import { IJSONLayer, INeuralNetworkData, INeuralNetworkDatum, INeuralNetworkTrainOptions } from "./neural-network";
import { INeuralNetworkGPUOptions, NeuralNetworkGPU } from "./neural-network-gpu";
import { INeuralNetworkState } from "./neural-network-types";
import { UntrainedNeuralNetworkError } from "./errors/untrained-neural-network-error";
import { KernelOutput, Texture, TextureArrayOutput } from 'gpu.js';
import {
IJSONLayer,
INeuralNetworkData,
INeuralNetworkDatum,
INeuralNetworkTrainOptions,
} from './neural-network';
import {
INeuralNetworkGPUOptions,
NeuralNetworkGPU,
} from './neural-network-gpu';
import { INeuralNetworkState } from './neural-network-types';
import { UntrainedNeuralNetworkError } from './errors/untrained-neural-network-error';

export interface IAEOptions {
binaryThresh: number;
Expand All @@ -13,13 +21,14 @@ export interface IAEOptions {
/**
* An autoencoder learns to compress input data down to relevant features and reconstruct input data from its compressed representation.
*/
export class AE<DecodedData extends INeuralNetworkData, EncodedData extends INeuralNetworkData> {
export class AE<
DecodedData extends INeuralNetworkData,
EncodedData extends INeuralNetworkData
> {
private decoder?: NeuralNetworkGPU<EncodedData, DecodedData>;
private denoiser: NeuralNetworkGPU<DecodedData, DecodedData>;
private readonly denoiser: NeuralNetworkGPU<DecodedData, DecodedData>;

constructor (
options?: Partial<IAEOptions>
) {
constructor(options?: Partial<IAEOptions>) {
// Create default options for the autoencoder.
options ??= {};

Expand All @@ -32,7 +41,9 @@ export class AE<DecodedData extends INeuralNetworkData, EncodedData extends INeu
denoiserOptions.hiddenLayers = options.hiddenLayers;

// Define the denoiser subnet's input and output sizes.
if (options.decodedSize) denoiserOptions.inputSize = denoiserOptions.outputSize = options.decodedSize;
if (options.decodedSize)
denoiserOptions.inputSize = denoiserOptions.outputSize =
options.decodedSize;

// Create the denoiser subnet of the autoencoder.
this.denoiser = new NeuralNetworkGPU<DecodedData, DecodedData>(options);
Expand Down Expand Up @@ -82,7 +93,8 @@ export class AE<DecodedData extends INeuralNetworkData, EncodedData extends INeu
this.denoiser.run(input);

// Get the auto-encoded input.
let encodedInput: TextureArrayOutput = this.encodedLayer as TextureArrayOutput;
let encodedInput: TextureArrayOutput = this
.encodedLayer as TextureArrayOutput;

// If the encoded input is a `Texture`, convert it into an `Array`.
if (encodedInput instanceof Texture) encodedInput = encodedInput.toArray();
Expand All @@ -100,7 +112,7 @@ export class AE<DecodedData extends INeuralNetworkData, EncodedData extends INeu
* @param {DecodedData} input
* @returns {boolean}
*/
likelyIncludesAnomalies(input: DecodedData, anomalyThreshold: number = 0.2): boolean {
likelyIncludesAnomalies(input: DecodedData, anomalyThreshold = 0.2): boolean {
// Create the anomaly vector.
const anomalies: number[] = [];

Expand All @@ -109,7 +121,9 @@ export class AE<DecodedData extends INeuralNetworkData, EncodedData extends INeu

// Calculate the anomaly vector.
for (let i = 0; i < (input.length ?? 0); i++) {
anomalies[i] = Math.abs((input as number[])[i] - (denoised as number[])[i]);
anomalies[i] = Math.abs(
(input as number[])[i] - (denoised as number[])[i]
);
}

// Calculate the sum of all anomalies within the vector.
Expand All @@ -131,11 +145,17 @@ export class AE<DecodedData extends INeuralNetworkData, EncodedData extends INeu
* @param {Partial<INeuralNetworkTrainOptions>} options
* @returns {INeuralNetworkState}
*/
train(data: DecodedData[], options?: Partial<INeuralNetworkTrainOptions>): INeuralNetworkState {
const preprocessedData: INeuralNetworkDatum<Partial<DecodedData>, Partial<DecodedData>>[] = [];

for (let datum of data) {
preprocessedData.push( { input: datum, output: datum } );
train(
data: DecodedData[],
options?: Partial<INeuralNetworkTrainOptions>
): INeuralNetworkState {
const preprocessedData: Array<INeuralNetworkDatum<
Partial<DecodedData>,
Partial<DecodedData>
>> = [];

for (const datum of data) {
preprocessedData.push({ input: datum, output: datum });
}

const results = this.denoiser.train(preprocessedData, options);
Expand Down Expand Up @@ -168,7 +188,7 @@ export class AE<DecodedData extends INeuralNetworkData, EncodedData extends INeu

const decoder = new NeuralNetworkGPU().fromJSON(json);

return decoder as unknown as NeuralNetworkGPU<EncodedData, DecodedData>;
return (decoder as unknown) as NeuralNetworkGPU<EncodedData, DecodedData>;
}

/**
Expand Down
12 changes: 7 additions & 5 deletions src/errors/untrained-neural-network-error.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
export class UntrainedNeuralNetworkError extends Error {
constructor (
neuralNetwork: any
) {
super(`Cannot run a ${neuralNetwork.constructor.name} before it is trained.`);
export class UntrainedNeuralNetworkError<
T extends { constructor: { name: string } }
> extends Error {
constructor(neuralNetwork: T) {
super(
`Cannot run a ${neuralNetwork.constructor.name} before it is trained.`
);
}
}

0 comments on commit b396559

Please sign in to comment.