UNPKG

@webwriter/neural-network

Version:

Deep learning visualization for feed-forward networks with custom datasets, training and prediction.

194 lines (177 loc) 6.63 kB
import { TemplateResult, html } from 'lit' import { customElement, property } from 'lit/decorators.js' import type { Position } from '@/types/position' import type { Activation } from '@/types/activation' import type { OutputLayerConf } from '@/types/output_layer_conf' import { CLayer } from '@/components/network/c_layer' import { NetworkUtils } from '@/utils/network_utils' import type { DataSet } from '@/types/data_set' import { AlertUtils } from '@/utils/alert_utils' import * as tf from '@tensorflow/tfjs' import { CNeuron } from './neuron' export class OutputLayer extends CLayer { static scopedElements = { "c-neuron": CNeuron } @property({attribute: false}) // @ts-ignore accessor conf: OutputLayerConf // LIFECYCLE - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - updated(changedProperties: Map<string, unknown>) { super.updated(changedProperties) if (changedProperties.has('conf')) { // check the updated data set keys if (!this.conf.labelDesc) { // if no label was assigned (should in theory not happen), notify the // network that this layer wants to be deleted this.dispatchEvent( new CustomEvent<OutputLayer>('query-layer-deletion', { detail: this, bubbles: true, composed: true, }) ) AlertUtils.spawn({ message: `Layer ${this.getCyId()} was deleted because no data could be assigned to it!`, variant: 'warning', icon: 'x-circle', }) } } if ( changedProperties.has('dataSet') && changedProperties.get('dataSet') && (<DataSet>changedProperties.get('dataSet')).name != this.dataSet.name ) { this.conf.labelDesc = this.dataSet.labelDesc console.log('UPDATED LABEL DESC') this.dispatchEvent( new Event('update-layer-confs', { bubbles: true, composed: true, }) ) } } // FACTORY - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // creates a new output layer with the optionally specified properties static create({ activation = NetworkUtils.actNone, pos = null, }: { activation?: Activation pos?: Position } = {}): OutputLayerConf { // create a new dense layer element with the specified properties const outputLayerConf: OutputLayerConf = { HTML_TAG: 'output-layer', LAYER_TYPE: 'Output', LAYER_NAME: 'Output layer', activation: activation, pos: pos, firstSpawn: true, // layer id and data set label will be added by the network layerId: undefined, labelDesc: undefined, } // emit an layer-conf-created event - the network listens to them, so it can // add a unique layer id to the layer and add it to the network array dispatchEvent( new CustomEvent<OutputLayerConf>('layer-conf-created', { detail: outputLayerConf, bubbles: true, composed: true, }) ) return outputLayerConf } // duplicate this layer duplicate(): void { AlertUtils.spawn({ message: `The selected layer can not be duplicated! Only a single output layer is currently supported!`, variant: 'warning', icon: 'x-circle', }) } // METHODS - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // -> INFO - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // overwrite getName for the outputKey getName(): string { return `${this.conf.labelDesc.key} ${this.conf.LAYER_NAME} ${ this.conf.activation != NetworkUtils.actNone ? `(${this.conf.activation.name})` : `` }` } // get description getDescription(): string { let description = 'An output layer in this simulation is just a normal dense layer, additionally equipped with the ability to output the incoming data out of the network.' if (this.dataSet.type == 'classification') { description += ' Since the purpose if this layer is to output a classifiction, we should combine it with a softmax activation function in order to provide a probability distribution.' } return description } // -> BUILD - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - build(inputs: tf.SymbolicTensor[]): tf.SymbolicTensor { // check if we have multiple inputs and concatenate them if necessary let input: tf.SymbolicTensor | tf.SymbolicTensor[] | tf.Tensor | tf.Tensor[] if (inputs.length > 1) { input = tf.layers .concatenate({ axis: 1, name: `concatinputs-${this.getTensorName()}` }) .apply(inputs) } else { input = inputs[0] } // create the layer's tensor const tensor = <tf.SymbolicTensor>tf.layers .dense({ units: Array.from(this._neurons).length, activation: this.conf.activation.tfName, name: this.getTensorName(), }) .apply(input) tensor['layer_id'] = this.conf.layerId this.tensor = tensor return tensor } // RENDER - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - render(): TemplateResult<1> { return html` ${super.render()} ${this.dataSet.type == 'regression' ? html`<c-neuron .layer="${this}" neuronId="1" .pos="${this.conf.pos}" key="${this.conf.labelDesc.key}" keyPos="top" bias="${this.bias ? this.bias[0] : null}" ></c-neuron>` : html``} ${this.dataSet.type == 'classification' && this.conf.labelDesc.classes ? html`${this.conf.labelDesc.classes.map( (clazz, i) => html` <c-neuron .layer="${this}" neuronId="${i + 1}" .pos="${{ x: this.conf.pos.x + i * (this.canvas.NEURON_SIZE + this.canvas.NEURON_DISTANCE) - ((this.conf.labelDesc.classes.length - 1) * (this.canvas.NEURON_SIZE + this.canvas.NEURON_DISTANCE)) / 2, y: this.conf.pos.y, }}" key="${clazz.id.toString()}" keyPos="top" bias="${this.bias ? this.bias[i] : null}" ></c-neuron> ` )}` : html``} ` } }