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' import { msg } from '@lit/localize' export class OutputLayer extends CLayer { static scopedElements = { 'c-neuron': CNeuron, } @property({ attribute: false }) // @ts-ignore accessor conf: OutputLayerConf // LIFECYCLE - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - updated(changedProperties: Map) { 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('query-layer-deletion', { detail: this, bubbles: true, composed: true, }), ) AlertUtils.spawn({ message: `${msg('Layer')} ${this.getCyId()} ${msg('was deleted because no data could be assigned to it!')}`, variant: 'warning', icon: 'x-circle', }) } } if ( changedProperties.has('dataSet') && changedProperties.get('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('layer-conf-created', { detail: outputLayerConf, bubbles: true, composed: true, }), ) return outputLayerConf } // duplicate this layer duplicate(): void { AlertUtils.spawn({ message: msg( '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 = msg( '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 += ' ' + msg( '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.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`` : html``} ${this.dataSet.type == 'classification' && this.conf.labelDesc.classes ? html`${this.conf.labelDesc.classes.map( (clazz, i) => html` `, )}` : html``} ` } }