Commit 41374489 authored by Petr Kubeš's avatar Petr Kubeš

visualizer

parent 39ff82f7
This diff is collapsed.
......@@ -18,7 +18,8 @@
<div class="container mt-3">
<h1>Neural network visualized</h1>
<p class="lead">Visualization of the simplest possible neural network. :)</p>
<div id="content"></div>
<div id="controls"></div>
<canvas id="content" width="800" height="500" style="border: 5px solid #2E282A"></canvas>
</div>
</body>
......
export class Visualizer {
private content: HTMLElement;
constructor(content: HTMLElement) {
this.content = content;
}
public printNumber = (num: number) => {
this.content.innerText = `${num}`;
}
}
import { Neuron } from "./neuralNetwork/Neuron";
import { Connection } from "./neuralNetwork/Connection";
export class DrawableNeuron {
public x: number;
public y: number;
public activation: number;
public name: string;
public isBias: boolean;
constructor(x, y, activation, name, isBias = false) {
this.x = x;
this.y = y;
this.activation = activation;
this.name = name;
this.isBias = isBias;
}
}
export class Visualizer {
private content: HTMLCanvasElement;
private ctx: CanvasRenderingContext2D;
private height: number;
private width: number;
constructor(content: HTMLCanvasElement) {
this.content = content;
this.ctx = content.getContext('2d');
this.height = content.height;
this.width = content.width;
}
public draw(neurons: Neuron[][], connections: Connection[][]) {
this.ctx.clearRect(0, 0, this.width, this.height);
const drawableNeurons: DrawableNeuron[] = [];
const leftMargin = this.width / (neurons.length + 1);
// Neurons
neurons.forEach((layer, lIdx) => {
const topMargin = this.height / (layer.length + 2);
layer.forEach((neuron, nIdx) => {
const x = leftMargin * (1 + lIdx);
const y = topMargin * (1 + nIdx);
const drawableNeuron = new DrawableNeuron(x, y, neuron.getActivation(), neuron.toString());
drawableNeurons.push(drawableNeuron);
});
if (lIdx != neurons.length - 1) {
const x = leftMargin * (1 + lIdx);
const y = topMargin * (1 + neurons[lIdx].length);
const drawableNeuron = new DrawableNeuron(x, y, 1, `bias${lIdx}`, true);
drawableNeurons.push(drawableNeuron);
}
});
// Connections
const drawableNameMap = new Map<string, DrawableNeuron>();
drawableNeurons.forEach(
(drawableNeuron) => drawableNameMap.set(drawableNeuron.name, drawableNeuron)// WTF, I was not able to create map from 2d arr
);
connections.forEach((layer, lIdx) => {
layer.forEach((connection) => {
const inputNName =
(connection.getInputNeuron().getIsBias()) ?
`bias${lIdx}` :
connection.getInputNeuron().toString();
this.drawConnection(
drawableNameMap.get(inputNName),
drawableNameMap.get(connection.getOutputNeuron().toString()),
connection.getWeight()
);
});
});
drawableNeurons.forEach((neuron) => {
this.drawNeuron(neuron);
});
}
private drawNeuron(drawableNeuron: DrawableNeuron) {
// white background
this.ctx.beginPath();
this.ctx.arc(drawableNeuron.x, drawableNeuron.y, 25, 0, 2 * Math.PI);
this.ctx.fillStyle = `rgb(255,255,255)`;
this.ctx.fill();
this.ctx.beginPath();
if (drawableNeuron.isBias)
this.ctx.fillStyle = `rgba(46,40,42, 1)`;
else
this.ctx.fillStyle = `rgba(23, 190, 187, ${drawableNeuron.activation})`;
this.ctx.strokeStyle = `rgb(46,40,42, 1)`
this.ctx.lineWidth = 1;
this.ctx.arc(drawableNeuron.x, drawableNeuron.y, 25, 0, 2 * Math.PI);
this.ctx.fill();
this.ctx.stroke();
this.ctx.fillStyle = `rgb(46,40,42, 1)`
const height = 16;
this.ctx.font = `bold ${height}px serif`;
const text = drawableNeuron.activation.toFixed(2);
this.ctx.fillText(
text,
drawableNeuron.x - this.ctx.measureText(text).width / 2,
drawableNeuron.y + height / 3);
}
private drawConnection(inputNeuron: DrawableNeuron, outputNeuron: DrawableNeuron, weight: number) {
this.ctx.beginPath();
this.ctx.lineWidth = (weight > 0) ?
Math.log(weight) :
Math.log(-weight);
this.ctx.strokeStyle = (weight > 0) ?
`rgba(205, 83, 52, ${weight})` :
`rgba(61, 232, 255, ${weight * -1})`;
this.ctx.moveTo(inputNeuron.x, inputNeuron.y);
this.ctx.lineTo(outputNeuron.x, outputNeuron.y);
this.ctx.closePath();
this.ctx.stroke();
}
}
import {Visualizer} from './Visualize';
import {NeuralCore} from './NeuralCore';
import { Visualizer } from './Visualizer';
import { NeuralCore } from './neuralNetwork/NeuralCore';
(window as any).slide = (i: number, value: number) => {
input[i] = value;
neuralCore.evaluate(input);
visualizer.draw(neuralCore.getNeurons(), neuralCore.getConnections());
}
window.onload = () => {
main();
......@@ -7,24 +13,31 @@ window.onload = () => {
let neuralCore: NeuralCore;
let visualizer: Visualizer;
let input: number[];
const main = () => {
const content: HTMLElement = document.getElementById('content');
const content: HTMLCanvasElement = document.getElementById('content') as HTMLCanvasElement;
const controls: HTMLCanvasElement = document.getElementById('controls') as HTMLCanvasElement;
visualizer = new Visualizer(content);
neuralCore = new NeuralCore(2, [], 1);
neuralCore.addTrainingSet([1,1], [1])
neuralCore.addTrainingSet([1,0], [0]);
neuralCore.addTrainingSet([0,1], [0]);
neuralCore.addTrainingSet([0,0], [0])
for (let i = 0; i<2000; i++) {
neuralCore = new NeuralCore(2, [3], 1);
// Set default values
input = new Array(neuralCore.getInputSize());
input.fill(1);
neuralCore.addTrainingSet([1, 1], [0]);
neuralCore.addTrainingSet([1, 0], [1]);
neuralCore.addTrainingSet([0, 1], [1]);
neuralCore.addTrainingSet([0, 0], [0]);
for (let i = 0; i < 10000; i++) {
neuralCore.train();
console.log(neuralCore.getCost());
}
console.log(neuralCore.evaluate([1,1]));
console.log(neuralCore.evaluate([1,0]));
console.log(neuralCore.evaluate([0,1]));
console.log(neuralCore.evaluate([0,0]));
}
neuralCore.evaluate(input);
visualizer.draw(neuralCore.getNeurons(), neuralCore.getConnections());
for (let i = 0; i < neuralCore.getInputSize(); i++) {
controls.innerHTML += `<input type="range" min="0" max="1" value="1" step="0.05" id="slider${i}" oninput="slide(${i}, this.value);"><br>`;
}
}
\ No newline at end of file
import { Neuron } from "./Neuron";
export class Connection {
private weight: number = Math.random();
private inputNeuron: Neuron;
private outputNeuron: Neuron;
private sampleWeightChanges: number[] = [];
constructor(input: Neuron, output: Neuron) {
this.inputNeuron = input;
this.outputNeuron = output;
}
public addSampleWeightChange(weightChange: number) {
this.sampleWeightChanges.push(weightChange);
}
public applyAverageWeight() {
const change = (this.sampleWeightChanges.reduce((acc, val) => acc + val, 0) / this.sampleWeightChanges.length);
this.weight += change;
this.sampleWeightChanges = [];
}
public getWeight() {
return this.weight;
}
public calculateValue() {
return this.weight * this.inputNeuron.calculateActivation();
}
public getOutputNeuron() {
return this.outputNeuron;
}
public getInputNeuron() {
return this.inputNeuron;
}
}
\ No newline at end of file
export class Activations {
public static SIGMOID = {
output: (x: number): number => 1 / (1 + Math.exp(-x)),
der: (x: number): number => {
let output = Activations.SIGMOID.output(x);
return output * (1 - output);
}
};
}
export class TrainSample {
public input: number[];
public output: number[];
constructor(input: number[], output: number[]) {
this.input = input;
this.output = output;
}
}
\ No newline at end of file
import { Neuron } from "./Neuron";
import { Connection } from "./Connection";
import { TrainSample, Activations } from "./HelperClasses";
export class NeuralCore {
private inputSize: number;
private hiddenLayerSizes: number[];
......@@ -5,9 +9,9 @@ export class NeuralCore {
private layerCnt: number;
private rate = 1;
private rate = 5;
private biasNeuron = new Neuron('bias', )
private biasNeuron = new Neuron('bias', true);
private neurons: Neuron[][] = [];
private connections: Connection[][] = [];
......@@ -91,7 +95,7 @@ export class NeuralCore {
}
public getCost(): number {
const costSum = this.trainSamples.reduce((costSum, sample, idx) => { // Add up all samples
const costSum = this.trainSamples.reduce((costSum, sample) => { // Add up all samples
this.evaluate(sample.input);
return costSum + this.neurons[this.layerCnt - 1].reduce((acc, neuron, i) => { // Add up all output neurons
return acc + (neuron.getActivation() - sample.output[i]) ** 2;
......@@ -152,136 +156,8 @@ export class NeuralCore {
public getConnections() {
return this.connections;
}
}
export class Neuron {
private name: string;
private activation: number;
private inputs: Connection[] = [];
private outputs: Connection[] = [];
// The derivation of C with respect to z
private sigma: number;
private isInput: boolean = false;
private isCalculated: boolean = false;
private isBias: boolean = false;
constructor(name: string, isBias = false) {
this.name = name;
this.isBias = isBias;
};
public toString() {
return name;
}
public setAsInputNeuron(activation: number) {
this.isInput = true;
this.activation = activation;
this.inputs = null;
}
public setInput(activation: number) {
if (!this.isInput) {
throw 'Cannot set activation of non-input neuron';
}
this.activation = activation;
}
public setSigma(sigma: number) {
this.sigma = sigma;
}
public addInput(input: Connection) {
this.inputs.push(input);
};
public addOutput(output: Connection) {
this.outputs.push(output);
}
public getOutputs(): Connection[] {
return this.outputs;
}
public reset() {
this.isCalculated = false;
}
public getActivation(): number {
if (this.isBias) return 1;
return this.activation;
}
public getSigma() {
return this.sigma;
}
public calculateActivation(): number {
if (!this.isInput && !this.isCalculated && !this.isBias) {
this.activation = Activations.SIGMOID.output(this.inputs.reduce((acc, currConn) => acc + currConn.calculateValue(), 0));
this.isCalculated = true;
}
return this.getActivation();
}
}
export class Connection {
private weight: number = Math.random();
private inputNeuron: Neuron;
private outputNeuron: Neuron;
private sampleWeightChanges: number[] = [];
constructor(input: Neuron, output: Neuron) {
this.inputNeuron = input;
this.outputNeuron = output;
}
public addSampleWeightChange(weightChange: number) {
this.sampleWeightChanges.push(weightChange);
}
public applyAverageWeight() {
const change = (this.sampleWeightChanges.reduce((acc, val) => acc + val, 0) / this.sampleWeightChanges.length);
this.weight += change;
this.sampleWeightChanges = [];
}
public getWeight() {
return this.weight;
}
public calculateValue() {
return this.weight * this.inputNeuron.calculateActivation();
}
public getOutputNeuron() {
return this.outputNeuron;
}
public getInputNeuron() {
return this.inputNeuron;
}
}
export class Activations {
public static SIGMOID = {
output: (x: number): number => 1 / (1 + Math.exp(-x)),
der: (x: number): number => {
let output = Activations.SIGMOID.output(x);
return output * (1 - output);
}
};
}
export class TrainSample {
public input: number[];
public output: number[];
constructor(input: number[], output: number[]) {
this.input = input;
this.output = output;
public getInputSize() {
return this.inputSize;
}
}
import { Connection } from "./Connection";
import { Activations } from "./HelperClasses";
export class Neuron {
private name: string;
private activation: number;
private inputs: Connection[] = [];
private outputs: Connection[] = [];
// The derivation of C with respect to z
private sigma: number;
private isInput: boolean = false;
private isCalculated: boolean = false;
private isBias: boolean = false;
constructor(name: string, isBias = false) {
this.name = name;
this.isBias = isBias;
};
public toString() {
return this.name;
}
public getIsBias() {
return this.isBias;
}
public setAsInputNeuron(activation: number) {
this.isInput = true;
this.activation = activation;
this.inputs = null;
}
public setInput(activation: number) {
if (!this.isInput) {
throw 'Cannot set activation of non-input neuron';
}
this.activation = activation;
}
public setSigma(sigma: number) {
this.sigma = sigma;
}
public addInput(input: Connection) {
this.inputs.push(input);
};
public addOutput(output: Connection) {
this.outputs.push(output);
}
public getOutputs(): Connection[] {
return this.outputs;
}
public reset() {
this.isCalculated = false;
}
public getActivation(): number {
if (this.isBias) this.activation = 1;
return this.activation;
}
public getSigma() {
return this.sigma;
}
public calculateActivation(): number {
if (!this.isInput && !this.isCalculated && !this.isBias) {
this.activation = Activations.SIGMOID.output(this.inputs.reduce((acc, currConn) => acc + currConn.calculateValue(), 0));
this.isCalculated = true;
}
return this.getActivation();
}
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment