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``}
`
}
}