import { TemplateResult, html } from 'lit'
import { customElement, property } from 'lit/decorators.js'
import type { DataSet } from '@/types/data_set'
import type { InputLayerConf } from '@/types/input_layer_conf'
import type { Position } from '@/types/position'
import type { Activation } from '@/types/activation'
import { CLayer } from '@/components/network/c_layer'
import { NetworkUtils } from '@/utils/network_utils'
import { AlertUtils } from '@/utils/alert_utils'
import * as tf from '@tensorflow/tfjs'
import { CNeuron } from './neuron'
import { msg } from '@lit/localize'
// an input layer is a special type of a neuron layer. We do not allow
// activation functions and provide methods to assign features from the
// dataSet to this input layer. We do not allow manual editing of the neurons
// and other layers can not connect to an input layer.
export class InputLayer extends CLayer {
static scopedElements = {
"c-neuron": CNeuron
}
@property() // @ts-ignore
accessor conf: InputLayerConf
// LIFECYCLE - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
updated(changedProperties: Map) {
super.updated(changedProperties)
if (changedProperties.has('conf')) {
// check the updated data set keys
if (!this.conf.featureKeys.length) {
// if no key was assigned, notify the network that this layer wants to
// be deleted
this.delete()
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.featureKeys = this.dataSet.featureDescs.map(
(featureDesc) => featureDesc.key
)
this.dispatchEvent(
new Event('update-layer-confs', {
bubbles: true,
composed: true,
})
)
}
}
// METHODS - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// -> INFO - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// overwrite getName function because activation function is always 'None' for
// input layer
getName(): string {
return `${this.conf.layerId} - ${this.conf.LAYER_NAME}`
}
// get description
getDescription(): string {
return msg('An input layer is a layer that just takes data provided from outside the network and passes it on to the next layer(s)')
}
// -> CREATING - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// creates a new input layer with the optionally specified properties
static create({
activation = NetworkUtils.actNone,
featureKeys = undefined,
pos = undefined,
}: {
activation?: Activation
featureKeys?: string[]
pos?: Position
} = {}): InputLayerConf {
// create a new dense layer configuration with the specified properties
const inputLayerConf: InputLayerConf = {
HTML_TAG: 'input-layer',
LAYER_TYPE: 'Input',
LAYER_NAME: 'Input layer',
activation: activation,
pos: pos,
firstSpawn: true,
// layer id and data set keys will be added by the layer
layerId: undefined,
featureKeys: featureKeys,
}
// emit an layer-conf-created event - the network listens to them, so it can
// add a unique layer id to the layer conf and add it to the network array
dispatchEvent(
new CustomEvent('layer-conf-created', {
detail: inputLayerConf,
bubbles: true,
composed: true,
})
)
return inputLayerConf
}
// duplicate this layer
duplicate(): void {
const newPos = { ...this.conf.pos }
newPos.y -=
this.canvas.getHeight(this.getCyId()) + this.canvas.LAYER_DISTANCE
InputLayer.create({
activation: this.conf.activation,
featureKeys: this.conf.featureKeys,
pos: newPos,
})
}
// -> DATASET - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// assigning inputs
getAssignedInputs(): string[] {
return Array.from(this._neurons).map((neuron) => neuron.key)
}
// -> BUILD - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
build(inputs: tf.SymbolicTensor[]): tf.SymbolicTensor {
<[]>inputs
// create our input tensor
const tensor = tf.input({
shape: [this.conf.featureKeys.length],
name: this.getTensorName(),
})
tensor['layer_id'] = this.conf.layerId
this.tensor = tensor
return tensor
}
// RENDER - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
render(): TemplateResult<1> {
return html`
${super.render()}
${this.conf.pos
? html`${this.conf.featureKeys.map(
(dataSetKey, i) => html`
`
)}`
: html``}
`
}
}