UNPKG

@ai-on-browser/data-analysis-models

Version:

Data analysis model package without any dependencies

247 lines (238 loc) 6.94 kB
import Tensor from '../../../util/tensor.js' import Layer, { NeuralnetworkLayerException } from './base.js' /** * Convolutional layer */ export default class ConvLayer extends Layer { /** * @param {object} config object * @param {number | number[]} config.kernel Size of kernel * @param {number} [config.channel] Number of output channel * @param {number | number[]} [config.stride] Step of stride * @param {number | number[] | [number, number][]} [config.padding] size of padding * @param {number[][] | Tensor | string} [config.w] Weight of kernel * @param {string | object} [config.activation] Name of activation or activation layer object * @param {number} [config.l2_decay] L2 decay * @param {number} [config.l1_decay] L1 decay * @param {number} [config.channel_dim] Dimension of the channel */ constructor({ kernel, channel = null, stride = null, padding = null, w = null, activation = null, l2_decay = 0, l1_decay = 0, channel_dim = -1, ...rest }) { super(rest) this._in_channel = null this._out_channel = channel this._kernel = kernel this._stride = stride || 1 this._padding = padding || 0 this._channel_dim = channel_dim if (this._channel_dim !== -1 && this._channel_dim !== 1) { throw new NeuralnetworkLayerException('Invalid channel dimension.') } this._w = null this._wname = null if (typeof w === 'string') { this._wname = w } else if (w) { this._w = Tensor.fromArray(w) this._in_channel = this._w.sizes[1] if (!this._out_channel) { this._out_channel = this._w.sizes[0] } } if (typeof activation === 'string') { this._activation = Layer.fromObject({ type: activation }) } else if (activation) { this._activation = Layer.fromObject(activation) } this._l2_decay = l2_decay this._l1_decay = l1_decay } get dependentLayers() { const layers = [] if (this._wname) { layers.push(this._wname) } if (this._activation) { layers.push(...this._activation.dependentLayers) } return layers } _index(i, c, k) { return this._channel_dim === -1 ? [i, ...k, c] : [i, c, ...k] } calc(x) { if (x.dimension <= 2) { throw new NeuralnetworkLayerException('Invalid input.') } if (!Array.isArray(this._kernel)) { this._kernel = Array(x.dimension - 2).fill(this._kernel) } if (x.dimension !== this._kernel.length + 2) { throw new NeuralnetworkLayerException('Invalid kernel size', [this, x]) } if (!Array.isArray(this._stride)) { this._stride = Array(x.dimension - 2).fill(this._stride) } if (x.dimension !== this._stride.length + 2) { throw new NeuralnetworkLayerException('Invalid stride size', [this, x]) } if (!Array.isArray(this._padding)) { this._padding = Array.from({ length: x.dimension - 2 }, () => [this._padding, this._padding]) } else if (!Array.isArray(this._padding[0])) { this._padding = this._padding.map(p => [p, p]) } if (x.dimension !== this._padding.length + 2) { throw new NeuralnetworkLayerException('Invalid padding size', [this, x]) } if (this._wname) { this._w = this.graph.getNode(this._wname).outputValue if (!this._in_channel) { this._in_channel = this._w.sizes[1] } if (!this._out_channel) { this._out_channel = this._w.sizes[0] } } if (!this._w) { this._in_channel = x.sizes[this._channel_dim === -1 ? x.dimension - 1 : 1] if (!this._out_channel) { this._out_channel = this._in_channel * 2 } this._w = Tensor.randn([this._out_channel, this._in_channel, ...this._kernel]) } this._i = x const koff = this._channel_dim === -1 ? 1 : 2 const outSize = [ x.sizes[0], ...this._kernel.map( (k, d) => Math.ceil( Math.max(0, x.sizes[d + koff] + this._padding[d][0] + this._padding[d][1] - k) / this._stride[d] ) + 1 ), ] if (this._channel_dim === -1) { outSize.push(this._out_channel) } else if (this._channel_dim === 1) { outSize.splice(1, 0, this._out_channel) } this._o = new Tensor(outSize) for (let i = 0; i < x.sizes[0]; i++) { for (let b = 0; b < this._out_channel; b++) { const idx = Array(this._kernel.length).fill(0) do { let v = 0 const p = Array(this._kernel.length).fill(0) do { const pi = p.map((t, k) => idx[k] * this._stride[k] - this._padding[k][0] + t) if (pi.every((t, k) => 0 <= t && t < x.sizes[k + koff])) { for (let c = 0; c < this._in_channel; c++) { v += x.at(this._index(i, c, pi)) * this._w.at(b, c, ...p) } } for (let k = 0; k < p.length; k++) { p[k]++ if (p[k] < this._kernel[k]) { break } p[k] = 0 } } while (p.some(v => v > 0)) this._o.set(this._index(i, b, idx), v) for (let k = 0; k < idx.length; k++) { idx[k]++ if (idx[k] < outSize[k + koff]) { break } idx[k] = 0 } } while (idx.some(v => v > 0)) } } if (this._activation) { return this._activation.calc(this._o) } return this._o } grad(bo) { this._bo = bo if (this._activation) { this._bo = this._activation.grad(bo) } this._bi = new Tensor(this._i.sizes) this._dw = new Tensor(this._w.sizes) const koff = this._channel_dim === -1 ? 1 : 2 for (let i = 0; i < this._i.sizes[0]; i++) { for (let b = 0; b < this._out_channel; b++) { const idx = Array(this._kernel.length).fill(0) do { const p = Array(this._kernel.length).fill(0) do { const pi = p.map((t, k) => idx[k] * this._stride[k] - this._padding[k][0] + t) if (pi.every((t, k) => 0 <= t && t < this._i.sizes[k + koff])) { for (let c = 0; c < this._in_channel; c++) { this._bi.operateAt( this._index(i, c, pi), v => v + this._w.at(b, c, ...p) * this._bo.at(this._index(i, b, idx)) ) this._dw.operateAt( [b, c, ...p], v => v + this._i.at(this._index(i, c, pi)) * this._bo.at(this._index(i, b, idx)) ) } } for (let k = 0; k < p.length; k++) { p[k]++ if (p[k] < this._kernel[k]) { break } p[k] = 0 } } while (p.some(v => v > 0)) for (let k = 0; k < idx.length; k++) { idx[k]++ if (idx[k] < this._o.sizes[k + koff]) { break } idx[k] = 0 } } while (idx.some(v => v > 0)) } } if (this._wname) { return [this._bi, { [this._wname]: this._dw }] } return this._bi } update(optimizer) { if (this._wname) { return } this._w.broadcastOperate(optimizer.delta('w', this._dw), (a, b) => a - b) } toObject() { return { type: 'conv', w: this._wname || this._w?.toArray(), channel: this._out_channel, kernel: this._kernel, stride: this._stride, padding: this._padding, activation: this._activation?.toObject(), l2_decay: this._l2_decay, l1_decay: this._l1_decay, channel_dim: this._channel_dim, } } } ConvLayer.registLayer()