Skip to content

Commit

Permalink
Fix all lint errors
Browse files Browse the repository at this point in the history
  • Loading branch information
voidvoxel committed Jun 17, 2024
1 parent c5c8438 commit e8384a5
Show file tree
Hide file tree
Showing 6 changed files with 258 additions and 199 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 AutoencoderGPU from "./autoencoder";
import AutoencoderGPU from './autoencoder';

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

const xornet = new AutoencoderGPU<number[], number[]>(
{
inputSize: 3,
hiddenLayers: [ 5, 2, 5 ]
}
);
const xornet = new AutoencoderGPU<number[], number[]>({
inputSize: 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);
});
70 changes: 50 additions & 20 deletions src/autoencoder.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,23 @@
import { IKernelFunctionThis, KernelOutput, Texture, TextureArrayOutput } from "gpu.js";
import { IJSONLayer, INeuralNetworkData, INeuralNetworkDatum, INeuralNetworkTrainOptions, NeuralNetworkIO, NeuralNetworkRAM } 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 {
IKernelFunctionThis,
KernelOutput,
Texture,
TextureArrayOutput,
} from 'gpu.js';
import {
IJSONLayer,
INeuralNetworkData,
INeuralNetworkDatum,
INeuralNetworkTrainOptions,
NeuralNetworkIO,
NeuralNetworkRAM,
} from './neural-network';
import {
INeuralNetworkGPUOptions,
NeuralNetworkGPU,
} from './neural-network-gpu';
import { INeuralNetworkState } from './neural-network-types';
import { UntrainedNeuralNetworkError } from './errors/untrained-neural-network-error';

function loss(
this: IKernelFunctionThis,
Expand All @@ -16,7 +31,7 @@ function loss(
// if ( o ≈ i0 ) then return 10% of the loss value.
// Otherwise, return 1000% of the full loss value.
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
// @ts-expect-error
if (Math.round(actual) !== Math.round(inputs[this.thread.x])) error *= 32;
else error *= 0.03125;

Expand All @@ -26,12 +41,13 @@ function loss(
/**
* An autoencoder learns to compress input data down to relevant features and reconstruct input data from its compressed representation.
*/
export class AutoencoderGPU<DecodedData extends INeuralNetworkData, EncodedData extends INeuralNetworkData> extends NeuralNetworkGPU<DecodedData, DecodedData> {
export class AutoencoderGPU<
DecodedData extends INeuralNetworkData,
EncodedData extends INeuralNetworkData
> extends NeuralNetworkGPU<DecodedData, DecodedData> {
private decoder?: NeuralNetworkGPU<EncodedData, DecodedData>;

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

Expand All @@ -40,7 +56,7 @@ export class AutoencoderGPU<DecodedData extends INeuralNetworkData, EncodedData
// Define the denoiser subnet's input and output sizes.
options.inputSize = options.outputSize = decodedSize;

options.hiddenLayers ??= [ Math.round(decodedSize * 0.66) ];
options.hiddenLayers ??= [Math.round(decodedSize * 0.66)];

options.loss ??= loss;

Expand Down Expand Up @@ -92,7 +108,8 @@ export class AutoencoderGPU<DecodedData extends INeuralNetworkData, EncodedData
this.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 @@ -110,7 +127,7 @@ export class AutoencoderGPU<DecodedData extends INeuralNetworkData, EncodedData
* @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 @@ -119,7 +136,9 @@ export class AutoencoderGPU<DecodedData extends INeuralNetworkData, EncodedData

// 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 @@ -141,13 +160,24 @@ export class AutoencoderGPU<DecodedData extends INeuralNetworkData, EncodedData
* @param {Partial<INeuralNetworkTrainOptions>} options
* @returns {INeuralNetworkState}
*/
train(data: Partial<DecodedData>[] | INeuralNetworkDatum<Partial<DecodedData>, Partial<DecodedData>>[], options?: Partial<INeuralNetworkTrainOptions>): INeuralNetworkState {
const preprocessedData: INeuralNetworkDatum<Partial<DecodedData>, Partial<DecodedData>>[] = [];
train(
data:
| Array<Partial<DecodedData>>
| Array<INeuralNetworkDatum<Partial<DecodedData>, Partial<DecodedData>>>,
options?: Partial<INeuralNetworkTrainOptions>
): INeuralNetworkState {
const preprocessedData: Array<INeuralNetworkDatum<
Partial<DecodedData>,
Partial<DecodedData>
>> = [];

if (data.length && data.length > 0)
for (let datum of data) {
preprocessedData.push( { input: datum as Partial<DecodedData>, output: datum as Partial<DecodedData> } );
}
for (const datum of data) {
preprocessedData.push({
input: datum as Partial<DecodedData>,
output: datum as Partial<DecodedData>,
});
}

const results = super.train(preprocessedData, options);

Expand Down Expand Up @@ -179,7 +209,7 @@ export class AutoencoderGPU<DecodedData extends INeuralNetworkData, EncodedData

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

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

/**
Expand Down
14 changes: 8 additions & 6 deletions src/errors/untrained-neural-network-error.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { NeuralNetwork } from "../neural-network";
interface IErrorableNeuralNetworkConstructor {
name: string;
}

interface IErrorableNeuralNetwork {
constructor: Function;
constructor: IErrorableNeuralNetworkConstructor;
}

export class UntrainedNeuralNetworkError extends Error {
constructor (
neuralNetwork: IErrorableNeuralNetwork
) {
super(`Cannot run a ${neuralNetwork.constructor.name} before it is trained.`);
constructor(neuralNetwork: IErrorableNeuralNetwork) {
super(
`Cannot run a ${neuralNetwork.constructor.name} before it is trained.`
);
}
}
Loading

0 comments on commit e8384a5

Please sign in to comment.