Commit 56adde23 authored by Petr Kubeš's avatar Petr Kubeš

finish network size controls

parent 700b4220
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -9,6 +9,7 @@
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm"
crossorigin="anonymous">
<link rel="stylesheet" href="main.css">
<script src="./dist/main.js"></script>
<title>Neural network visualized</title>
......@@ -38,16 +39,16 @@
</div>
<div class="col-md-4">
<h5>Train</h5>
Training rate: <input type="number" name="rate" min="0" max="10" value="1"><br>
Number of iterations: <input type="number" name="rate" min="0" max="1000000" value="5000"><br>
Training rate: <input id="rate-input" type="number" name="rate" min="0" max="10" value="1"><br>
Number of iterations: <input id="iters-input" type="number" name="rate" min="0" max="1000000" value="5000"><br>
Current iteration: TODO<br>
</div>
</div>
<hr>
<div class="row mb-3">
<div class="col-md-auto">
<button type="button" class="btn btn-primary mr-2" onclick="train(1000)">Train</button>
<button type="button" class="btn btn-secondary" onclick="train(1)">Step</button>
<button type="button" class="btn btn-primary mr-2" onclick="train(true)">Train</button>
<button type="button" class="btn btn-secondary" onclick="train(false)">Step</button>
<button type="button" class="btn btn-danger" onclick="reset()">Reset</button>
</div>
<div class="col-md">
......@@ -56,7 +57,7 @@
</div>
</div>
<canvas id="content" width="800" height="500" style="border: 5px solid #2E282A"></canvas>
<canvas id="content" width="1200" height="600" style="border: 5px solid #2E282A; margin-bottom: 70px;"></canvas>
</div>
</body>
......
b {
padding-right: 10px;
padding-left: 5px;
}
\ No newline at end of file
......@@ -44,7 +44,7 @@ export class Visualizer {
const x = leftMargin * (1 + lIdx);
const y = topMargin * (1 + nIdx);
const drawableNeuron = new DrawableNeuron(x, y, neuron.getActivation(), neuron.toString());
const drawableNeuron = new DrawableNeuron(x, y, neuron.getActivation(), neuron.getName());
drawableNeurons.push(drawableNeuron);
});
......@@ -68,11 +68,11 @@ export class Visualizer {
const inputNName =
(connection.getInputNeuron().getIsBias()) ?
`bias${lIdx}` :
connection.getInputNeuron().toString();
connection.getInputNeuron().getName();
this.drawConnection(
drawableNameMap.get(inputNName),
drawableNameMap.get(connection.getOutputNeuron().toString()),
drawableNameMap.get(connection.getOutputNeuron().getName()),
connection.getWeight()
);
});
......
......@@ -8,18 +8,28 @@ import { NeuralCore } from './neuralNetwork/NeuralCore';
}
(window as any).addOrRemoveLayer = (add: boolean) => {
neuralCore.addLayer(add);
neuralCore.addOrRemoveLayer(add);
neuralCore.evaluate(input);
updateUI();
visualizer.draw(neuralCore.getNeurons(), neuralCore.getConnections());
}
(window as any).addOrRemoveNeuron = (layer: number, add: boolean) => {
(window as any).addOrRemoveNeuron = (add: boolean, layerIdx: number) => {
neuralCore.addOrRemoveNeuron(add, layerIdx);
if (layerIdx == 0) {
input.push(1);
}
neuralCore.evaluate(input);
updateUI();
visualizer.draw(neuralCore.getNeurons(), neuralCore.getConnections());
}
(window as any).train = (iters: number) => {
(window as any).train = (multipleIters: boolean) => {
let iters = multipleIters ? Number.parseInt(itersInput.value) : 1;
neuralCore.setRate(Number.parseFloat(rateInput.value));
for (let i=0;i<iters;i++) {
neuralCore.train();
}
......@@ -45,22 +55,26 @@ let neuralCore: NeuralCore;
let visualizer: Visualizer;
let input: number[];
let inputSize = 2;
let inputSize = 4;
let hiddenSizes = [3];
let outputSize = 2;
let outputSize = 4;
let layerControls: HTMLElement;
let inputControls: HTMLElement;
let layerCnt: HTMLElement;
let cost: HTMLElement;
let rateInput: HTMLInputElement;
let itersInput: HTMLInputElement;
const main = () => {
const content: HTMLCanvasElement = document.getElementById('content') as HTMLCanvasElement;
inputControls = document.getElementById('input-controls');
layerControls = document.getElementById('layer-controls');
layerCnt = document.getElementById('layer-cnt');
cost = document.getElementById('cost');
rateInput = document.getElementById('rate-input') as HTMLInputElement;
itersInput = document.getElementById('iters-input') as HTMLInputElement;
visualizer = new Visualizer(content);
......@@ -70,10 +84,9 @@ const main = () => {
const initCore = () => {
neuralCore = new NeuralCore(inputSize, hiddenSizes, outputSize);
neuralCore.addTrainingSet([1,1], [1,1]);
neuralCore.addTrainingSet([1,0], [0,0]);
neuralCore.addTrainingSet([0,1], [0,0]);
neuralCore.addTrainingSet([0,0], [0,0]);
neuralCore.addTrainingSet([1,0,0,0], [0,1,0,0]);
neuralCore.addTrainingSet([0,1,0,0], [0,0,1,0]);
neuralCore.addTrainingSet([0,0,1,0], [0,0,0,1]);
// Set default values
input = new Array(neuralCore.getInputSize());
......@@ -86,23 +99,23 @@ const initCore = () => {
const updateUI = () => {
let content = '<table>';
content += `<tr><td align='right'>Input size:</td><td>
content += `<tr><td align='right'>Input size: <b>${inputSize}</b></td><td>
<div class="btn-group" role="group">
<button type="button" class="btn btn-secondary btn-sm" onclick="addOrRemoveNeuron(-1, false)">-</button>
<button type="button" class="btn btn-secondary btn-sm" onclick="addOrRemoveNeuron(-1, true)">+</button>
<button type="button" class="btn btn-secondary btn-sm" onclick="addOrRemoveNeuron(false, 0)">-</button>
<button type="button" class="btn btn-secondary btn-sm" onclick="addOrRemoveNeuron(true, 0)">+</button>
</div></td></tr>`;
for (let i = 0; i < neuralCore.getLayerCnt() - 2; i++) {
content += `<tr><td align='right'>Hidden layer size:</td><td>
content += `<tr><td align='right'>Hidden layer size: <b>${neuralCore.getHiddenLayerSizes()[i]}</b></td><td>
<div class="btn-group" role="group">
<button type="button" class="btn btn-secondary btn-sm" onclick="addOrRemoveNeuron(${i}, false)">-</button>
<button type="button" class="btn btn-secondary btn-sm" onclick="addOrRemoveNeuron(${i}, true)">+</button>
<button type="button" class="btn btn-secondary btn-sm" onclick="addOrRemoveNeuron(false, ${i+1})">-</button>
<button type="button" class="btn btn-secondary btn-sm" onclick="addOrRemoveNeuron(true, ${i+1})">+</button>
</div></td></tr>`;
}
content += `<tr><td align='right'>Output size:</td><td>
content += `<tr><td align='right'>Output size: <b>${outputSize}</b></td><td>
<div class="btn-group" role="group">
<button type="button" class="btn btn-secondary btn-sm" onclick="addOrRemoveNeuron(-2, false)">-</button>
<button type="button" class="btn btn-secondary btn-sm" onclick="addOrRemoveNeuron(-2, true)">+</button>
<button type="button" class="btn btn-secondary btn-sm" onclick="addOrRemoveNeuron(false, ${neuralCore.getLayerCnt()-1})">-</button>
<button type="button" class="btn btn-secondary btn-sm" onclick="addOrRemoveNeuron(true, ${neuralCore.getLayerCnt()-1})">+</button>
</div></td></tr>`;
content += '</table>';
......
......@@ -83,6 +83,10 @@ export class NeuralCore {
}
public getCost(): number {
if (this.trainSamples.length == 0) {
return 0;
}
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
......@@ -137,23 +141,17 @@ export class NeuralCore {
});
}
public addLayer(add: boolean) {
public addOrRemoveLayer(add: boolean) {
if (add) {
const newLayerSize = 3;
this.hiddenLayerSizes.push(newLayerSize);
this.layerCnt++;
// Create the new neurons
this.neurons[this.layerCnt - 2] = [];
for (let i = 0; i < newLayerSize; i++) {
this.neurons[this.layerCnt - 2][i] = new Neuron(`Neuron${this.layerCnt - 2}${i}`);
}
this.createLayerOfNeurons(this.layerCnt - 2, newLayerSize);
// Recreate the last layer
this.neurons[this.layerCnt - 1] = [];
for (let i = 0; i < this.outputSize; i++) {
this.neurons[this.layerCnt - 1][i] = new Neuron(`Neuron${this.layerCnt - 1}${i}`);
}
this.createLayerOfNeurons(this.layerCnt - 1, this.outputSize);
// Recreate all necessary connections
this.createConnections(this.layerCnt - 3, this.layerCnt - 1);
......@@ -168,18 +166,116 @@ export class NeuralCore {
this.connections.pop();
// Recreate the last layer
this.neurons[this.layerCnt - 1] = [];
for (let i = 0; i < this.outputSize; i++) {
this.neurons[this.layerCnt - 1][i] = new Neuron(`Neuron${this.layerCnt - 1}${i}`);
}
this.createLayerOfNeurons(this.layerCnt - 1, this.outputSize);
// Recreate all necessary connections
this.createConnections(this.layerCnt - 2, this.layerCnt - 1);
}
}
// This function is very long and ugly, I dont want to simply rebuild the network because I want to keep the weights
public addOrRemoveNeuron(add: boolean, layerIdx: number) {
const isInput = layerIdx == 0;
const isOutput = layerIdx == this.layerCnt - 1;
const isHidden = !isInput && !isOutput;
const sizeChange = (add) ? 1 : -1
if (isHidden) {
this.hiddenLayerSizes[layerIdx - 1] += sizeChange;
}
else if (isInput) {
this.inputSize += sizeChange;
this.trainSamples = [];
} else {
this.outputSize += sizeChange;
this.trainSamples = [];
}
if (add) {
let newNeuronIdx;
if (isHidden) {
newNeuronIdx = this.hiddenLayerSizes[layerIdx - 1] - 1;
}
else if (isInput) {
newNeuronIdx = this.inputSize - 1;
} else {
newNeuronIdx = this.outputSize - 1;
}
const newNeuron = new Neuron(`Neuron${layerIdx}${newNeuronIdx}`);
this.neurons[layerIdx][newNeuronIdx] = newNeuron;
if (isInput)
newNeuron.setAsInputNeuron(0);
//// Add connections from the prev layer
if (!isInput) {
this.neurons[layerIdx - 1].forEach((neuron) => {
const connection = new Connection(neuron, newNeuron);
neuron.addOutput(connection);
newNeuron.addInput(connection);
this.connections[layerIdx - 1].push(connection);
});
// Dont forget the bias
const connection = new Connection(this.biasNeuron, newNeuron);
newNeuron.addInput(connection);
this.connections[layerIdx - 1].push(connection);
}
if (!isOutput) {
//// Add connections to the next layer
this.neurons[layerIdx + 1].forEach((neuron) => {
const connection = new Connection(newNeuron, neuron);
neuron.addInput(connection);
this.connections[layerIdx].push(connection);
});
}
} else {
const removedNeuron = this.neurons[layerIdx].pop();
// Remove outputs from the prev layer
if (!isInput) {
this.neurons[layerIdx - 1].forEach((neuron) => {
neuron.setOutputs(neuron.getOutputs().filter((connection) => {
return connection.getOutputNeuron().getName() != removedNeuron.getName();
}));
});
}
// Remove input in the next layer
if (!isOutput) {
this.neurons[layerIdx + 1].forEach((neuron) => {
neuron.setInputs(neuron.getInputs().filter((connection) => {
return connection.getInputNeuron().getName() != removedNeuron.getName();
}));
});
}
// Remove the unused connections
if (!isInput) {
this.connections[layerIdx-1] = this.connections[layerIdx-1].filter((connection: Connection) => {
return connection.getOutputNeuron().getName() != removedNeuron.getName();
});
}
if (!isOutput) {
this.connections[layerIdx] = this.connections[layerIdx].filter((connection: Connection) => {
return connection.getInputNeuron().getName() != removedNeuron.getName();
});
}
}
}
public reset() {
this.createConnections(0, this.layerCnt-1);
this.createConnections(0, this.layerCnt - 1);
}
private createLayerOfNeurons(layerIdx: number, layerSize: number) {
this.neurons[layerIdx] = [];
for (let i = 0; i < layerSize; i++) {
this.neurons[layerIdx][i] = new Neuron(`Neuron${layerIdx}${i}`);
}
}
private createConnections(firstLayer, lastLayer) {
......@@ -188,8 +284,8 @@ export class NeuralCore {
this.connections[l] = [];
// Reset input & outputs
this.neurons[l + 1].forEach(nextNeuron => {nextNeuron.resetInputs()});
this.neurons[l].forEach(nextNeuron => {nextNeuron.resetOutputs()});
this.neurons[l + 1].forEach(nextNeuron => { nextNeuron.resetInputs() });
this.neurons[l].forEach(nextNeuron => { nextNeuron.resetOutputs() });
this.neurons[l + 1].forEach(nextNeuron => { // If you wonder why this cycles are switched, it's because of the bias
......@@ -224,4 +320,11 @@ export class NeuralCore {
return this.layerCnt;
}
public getHiddenLayerSizes() {
return this.hiddenLayerSizes;
}
public setRate(newRate: number) {
this.rate = newRate;
}
}
......@@ -5,8 +5,8 @@ export class Neuron {
private name: string;
private activation: number;
private inputs: Connection[];
private outputs: Connection[];
private inputs: Connection[] = [];
private outputs: Connection[] = [];
// The derivation of C with respect to z
private sigma: number;
......@@ -19,7 +19,7 @@ export class Neuron {
this.isBias = isBias;
};
public toString() {
public getName() {
return this.name;
}
......@@ -49,6 +49,10 @@ export class Neuron {
this.inputs.push(input);
};
public getInputs(): Connection[] {
return this.inputs;
}
public addOutput(output: Connection) {
this.outputs.push(output);
}
......@@ -57,6 +61,14 @@ export class Neuron {
return this.outputs;
}
public setOutputs(connections: Connection[]) {
this.outputs = connections;
}
public setInputs(connections: Connection[]) {
this.inputs = connections;
}
public resetInputs() {
this.inputs = [];
}
......
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