mirror of
https://github.com/wassname/keras-js.git
synced 2026-09-13 12:41:26 +08:00
implement conv2d
This commit is contained in:
+5
-5
@@ -1,6 +1,6 @@
|
||||
import ndarray from 'ndarray'
|
||||
import ops from 'ndarray-ops'
|
||||
import cwise from 'cwise'
|
||||
import Tensor from './tensor'
|
||||
|
||||
/**
|
||||
* Softmax activation function. In-place operation.
|
||||
@@ -69,16 +69,16 @@ export function relu (x, opts = {}) {
|
||||
const { alpha = 0, maxValue = null } = opts
|
||||
let neg
|
||||
if (alpha !== 0) {
|
||||
neg = ndarray(new x._type(x.tensor.data.length), x.tensor.shape)
|
||||
ops.mins(neg, x.tensor, 0.0)
|
||||
ops.mulseq(neg, alpha)
|
||||
neg = new Tensor([], x.tensor.shape)
|
||||
ops.mins(neg.tensor, x.tensor, 0.0)
|
||||
ops.mulseq(neg.tensor, alpha)
|
||||
}
|
||||
ops.maxseq(x.tensor, 0.0)
|
||||
if (maxValue) {
|
||||
ops.minseq(x.tensor, maxValue)
|
||||
}
|
||||
if (neg) {
|
||||
ops.addeq(x.tensor, neg)
|
||||
ops.addeq(x.tensor, neg.tensor)
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
@@ -0,0 +1,167 @@
|
||||
import * as activations from '../activations'
|
||||
import Tensor from '../tensor'
|
||||
import { Layer } from '../engine/topology'
|
||||
import ops from 'ndarray-ops'
|
||||
import gemm from 'ndarray-gemm'
|
||||
import unpack from 'ndarray-unpack'
|
||||
import flattenDeep from 'lodash/flattenDeep'
|
||||
|
||||
/**
|
||||
* Convolution2D layer class
|
||||
*/
|
||||
export class Convolution2D extends Layer {
|
||||
/**
|
||||
* Creates a Convolution2D layer
|
||||
* @param {number} nbFilter - Number of convolution filters to use.
|
||||
* @param {number} nbRow - Number of rows in the convolution kernel.
|
||||
* @param {number} nbCol - Number of columns in the convolution kernel.
|
||||
* @param {Object} [attrs] - layer attributes
|
||||
*/
|
||||
constructor (nbFilter, nbRow, nbCol, attrs = {}) {
|
||||
super(attrs)
|
||||
const {
|
||||
activation = 'linear',
|
||||
borderMode = 'valid',
|
||||
subsample = [1, 1],
|
||||
dimOrdering = 'tf',
|
||||
bias = true
|
||||
} = attrs
|
||||
|
||||
this.kernelShape = [nbFilter, nbRow, nbCol]
|
||||
|
||||
this.activation = activations[activation]
|
||||
|
||||
if (['valid', 'same'].indexOf(borderMode) > -1) {
|
||||
this.borderMode = borderMode
|
||||
} else {
|
||||
throw new Error(`${this.name} [Convolution2D layer] Invalid borderMode.`)
|
||||
}
|
||||
|
||||
this.subsample = subsample
|
||||
|
||||
if (['tf', 'th'].indexOf(dimOrdering) > -1) {
|
||||
this.dimOrdering = dimOrdering
|
||||
} else {
|
||||
throw new Error(`${this.name} [Convolution2D layer] Invalid dimOrdering.`)
|
||||
}
|
||||
|
||||
this.bias = bias
|
||||
|
||||
/**
|
||||
* Layer weights specification
|
||||
*/
|
||||
this.params = this.bias ? ['W', 'b'] : ['W']
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for computing output dimensions based on input dimensions and kernel size
|
||||
* @param {Tensor} x
|
||||
* @returns {number[]} [outputRows, outputCols, outputChannels]
|
||||
*/
|
||||
_calcOutputShape = x => {
|
||||
const inputRows = x.tensor.shape[0]
|
||||
const inputCols = x.tensor.shape[1]
|
||||
const [nbFilter, nbRow, nbCol] = this.kernelShape
|
||||
const paddingRow = this.borderMode === 'same' ? Math.floor(nbRow / 2) : 0
|
||||
const paddingCol = this.borderMode === 'same' ? Math.floor(nbCol / 2) : 0
|
||||
const outputRows = (inputRows + 2 * paddingRow - nbRow) / this.subsample[0] + 1
|
||||
const outputCols = (inputCols + 2 * paddingCol - nbCol) / this.subsample[1] + 1
|
||||
const outputChannels = nbFilter
|
||||
this.outputShape = [outputRows, outputCols, outputChannels]
|
||||
}
|
||||
|
||||
/**
|
||||
* image to column matrix
|
||||
* @param {Tensor} x
|
||||
* @returns {Tensor} x
|
||||
*/
|
||||
_im2col = x => {
|
||||
const inputChannels = x.tensor.shape[2]
|
||||
const nbRow = this.kernelShape[1]
|
||||
const nbCol = this.kernelShape[2]
|
||||
const outputRows = this.outputShape[0]
|
||||
const outputCols = this.outputShape[1]
|
||||
const nbPatches = outputRows * outputCols
|
||||
const patchLen = nbRow * nbCol * inputChannels
|
||||
|
||||
const imColsMat = new Tensor([], [patchLen, nbPatches])
|
||||
|
||||
let patch = new Tensor([], [patchLen])
|
||||
let n = 0
|
||||
for (let i = 0; i < outputRows; i += this.subsample[0]) {
|
||||
for (let j = 0; j < outputCols; j += this.subsample[1]) {
|
||||
const patchData = flattenDeep(unpack(
|
||||
x.tensor.hi(i + nbRow, j + nbCol, inputChannels).lo(i, j, 0)
|
||||
))
|
||||
patch.replaceTensorData(patchData)
|
||||
ops.assign(imColsMat.tensor.pick(null, n), patch.tensor)
|
||||
n += 1
|
||||
}
|
||||
}
|
||||
|
||||
return imColsMat
|
||||
}
|
||||
|
||||
/**
|
||||
* filters to row matrix
|
||||
* @param {Tensor} x
|
||||
* @returns {Tensor} x
|
||||
*/
|
||||
_w2row = x => {
|
||||
const inputChannels = x.tensor.shape[2]
|
||||
const [nbFilter, nbRow, nbCol] = this.kernelShape
|
||||
const patchLen = nbRow * nbCol * inputChannels
|
||||
|
||||
const wRowsMat = new Tensor([], [nbFilter, patchLen])
|
||||
|
||||
let patch = new Tensor([], [patchLen])
|
||||
for (let n = 0; n < nbFilter; n++) {
|
||||
const patchData = flattenDeep(unpack(
|
||||
this.weights.W.tensor.pick(null, null, null, n)
|
||||
))
|
||||
patch.replaceTensorData(patchData)
|
||||
ops.assign(wRowsMat.tensor.pick(n, null), patch.tensor)
|
||||
}
|
||||
|
||||
return wRowsMat
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for layer computational logic
|
||||
* @param {Tensor} x
|
||||
* @returns {Tensor} x
|
||||
*/
|
||||
call = x => {
|
||||
this._calcOutputShape(x)
|
||||
|
||||
const imColsMat = this._im2col(x)
|
||||
const wRowsMat = this._w2row(x)
|
||||
|
||||
const nbFilter = this.kernelShape[0]
|
||||
const outputRows = this.outputShape[0]
|
||||
const outputCols = this.outputShape[1]
|
||||
const nbPatches = outputRows * outputCols
|
||||
const matMul = new Tensor([], [nbFilter, nbPatches])
|
||||
if (this.bias) {
|
||||
for (let n = 0; n < nbFilter; n++) {
|
||||
ops.assigns(matMul.tensor.pick(n, null), this.weights.b.tensor.get(n))
|
||||
}
|
||||
}
|
||||
gemm(matMul.tensor, wRowsMat.tensor, imColsMat.tensor, 1, 1)
|
||||
|
||||
let output = new Tensor([], this.outputShape)
|
||||
let outputChannel = new Tensor([], [outputRows, outputCols])
|
||||
for (let n = 0; n < nbFilter; n++) {
|
||||
const outputChannelData = flattenDeep(unpack(
|
||||
matMul.tensor.pick(n, null)
|
||||
))
|
||||
outputChannel.replaceTensorData(outputChannelData)
|
||||
ops.assign(output.tensor.pick(null, null, n), outputChannel.tensor)
|
||||
}
|
||||
x.tensor = output.tensor
|
||||
|
||||
this.activation(x)
|
||||
|
||||
return x
|
||||
}
|
||||
}
|
||||
+11
-7
@@ -1,3 +1,12 @@
|
||||
export {
|
||||
LeakyReLU,
|
||||
PReLU,
|
||||
ELU,
|
||||
ParametricSoftplus,
|
||||
ThresholdedReLU,
|
||||
SReLU
|
||||
} from './advanced_activations'
|
||||
|
||||
export {
|
||||
Dense,
|
||||
Activation,
|
||||
@@ -10,10 +19,5 @@ export {
|
||||
} from './core'
|
||||
|
||||
export {
|
||||
LeakyReLU,
|
||||
PReLU,
|
||||
ELU,
|
||||
ParametricSoftplus,
|
||||
ThresholdedReLU,
|
||||
SReLU
|
||||
} from './advanced_activations'
|
||||
Convolution2D
|
||||
} from './convolutional'
|
||||
|
||||
+17
-5
@@ -19,19 +19,18 @@ export default class Tensor {
|
||||
*/
|
||||
constructor (data, shape, options = {}) {
|
||||
this._type = options.type || Float32Array
|
||||
const TypedArray = this._type
|
||||
|
||||
if (data && data.length && data instanceof TypedArray) {
|
||||
if (data && data.length && data instanceof this._type) {
|
||||
checkShape(data, shape)
|
||||
this.tensor = ndarray(data, shape)
|
||||
} else if (data && data.length && data instanceof Array) {
|
||||
checkShape(data, shape)
|
||||
this.tensor = ndarray(new TypedArray(data), shape)
|
||||
this.tensor = ndarray(new this._type(data), shape)
|
||||
} else if (!data.length && shape.length) {
|
||||
// if shape present but data not provided, initialize with 0s
|
||||
this.tensor = ndarray(new TypedArray(shape.reduce((a, b) => a * b, 1)), shape)
|
||||
this.tensor = ndarray(new this._type(shape.reduce((a, b) => a * b, 1)), shape)
|
||||
} else {
|
||||
this.tensor = ndarray(new TypedArray([]), [])
|
||||
this.tensor = ndarray(new this._type([]), [])
|
||||
}
|
||||
|
||||
// turn on weblas
|
||||
@@ -79,4 +78,17 @@ export default class Tensor {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces data in the underlying ndarray.
|
||||
*/
|
||||
replaceTensorData = data => {
|
||||
if (data && data.length && data instanceof this._type) {
|
||||
this.tensor.data = data
|
||||
} else if (data && data.length && data instanceof Array) {
|
||||
this.tensor.data = new this._type(data)
|
||||
} else {
|
||||
this.tensor = new this._type([])
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user