implement conv2d

This commit is contained in:
Leon Chen
2016-08-25 18:03:44 -04:00
parent 656e590c5b
commit 99496e7037
4 changed files with 200 additions and 17 deletions
+5 -5
View File
@@ -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
}
+167
View File
@@ -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
View File
@@ -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
View File
@@ -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([])
}
}
}